chore: slate upload ssr
This commit is contained in:
@@ -14,7 +14,7 @@ import ol from './ol'
|
||||
// import relationship from './relationship'
|
||||
// import textAlign from './textAlign'
|
||||
import ul from './ul'
|
||||
// import upload from './upload'
|
||||
import upload from './upload'
|
||||
|
||||
const elements: Record<string, RichTextCustomElement> = {
|
||||
blockquote,
|
||||
@@ -31,7 +31,7 @@ const elements: Record<string, RichTextCustomElement> = {
|
||||
// relationship,
|
||||
// textAlign,
|
||||
ul,
|
||||
// upload,
|
||||
upload,
|
||||
}
|
||||
|
||||
export default elements
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
'use client'
|
||||
|
||||
import type { FormState } from '@payloadcms/ui'
|
||||
|
||||
import { useModal } from '@faceless-ui/modal'
|
||||
import { getTranslation } from '@payloadcms/translations'
|
||||
import {
|
||||
Button,
|
||||
FormState,
|
||||
Popup,
|
||||
Translation,
|
||||
getFormState,
|
||||
@@ -21,11 +22,13 @@ import React, { useCallback, useEffect, useState } from 'react'
|
||||
import { Editor, Node, Transforms } from 'slate'
|
||||
import { ReactEditor, useSlate } from 'slate-react'
|
||||
|
||||
import type { LinkElementType } from '../types'
|
||||
|
||||
import { useElement } from '../../../providers/ElementProvider'
|
||||
import { LinkDrawer } from '../LinkDrawer'
|
||||
import { linkFieldsSchemaPath } from '../shared'
|
||||
import { unwrapLink } from '../utilities'
|
||||
import './index.scss'
|
||||
import { LinkElementType } from '../types'
|
||||
|
||||
const baseClass = 'rich-text-link'
|
||||
|
||||
@@ -58,10 +61,10 @@ export const LinkElement = () => {
|
||||
const { attributes, children, editorRef, element, fieldProps, schemaPath } =
|
||||
useElement<LinkElementType>()
|
||||
|
||||
const linkFieldsSchemaPath = `${schemaPath}.link.fields`
|
||||
const fieldMapPath = `${schemaPath}.${linkFieldsSchemaPath}`
|
||||
|
||||
const { richTextComponentMap } = fieldProps
|
||||
const fieldMap = richTextComponentMap.get(linkFieldsSchemaPath)
|
||||
const fieldMap = richTextComponentMap.get(fieldMapPath)
|
||||
|
||||
const editor = useSlate()
|
||||
const config = useConfig()
|
||||
@@ -102,7 +105,7 @@ export const LinkElement = () => {
|
||||
data,
|
||||
docPreferences,
|
||||
operation: 'update',
|
||||
schemaPath: linkFieldsSchemaPath,
|
||||
schemaPath: fieldMapPath,
|
||||
},
|
||||
serverURL: config.serverURL,
|
||||
})
|
||||
@@ -111,7 +114,7 @@ export const LinkElement = () => {
|
||||
}
|
||||
|
||||
awaitInitialState()
|
||||
}, [renderModal, element, user, locale, t, getDocPreferences, config])
|
||||
}, [renderModal, element, user, locale, t, getDocPreferences, config, id, fieldMapPath])
|
||||
|
||||
return (
|
||||
<span className={baseClass} {...attributes}>
|
||||
|
||||
@@ -42,9 +42,9 @@ const UploadButton: React.FC<ButtonProps> = ({ enabledCollectionSlugs }) => {
|
||||
})
|
||||
|
||||
const onSelect = useCallback(
|
||||
({ collectionConfig, docID }) => {
|
||||
({ collectionSlug, docID }) => {
|
||||
insertUpload(editor, {
|
||||
relationTo: collectionConfig.slug,
|
||||
relationTo: collectionSlug,
|
||||
value: {
|
||||
id: docID,
|
||||
},
|
||||
|
||||
@@ -9,49 +9,50 @@ import {
|
||||
Form,
|
||||
FormSubmit,
|
||||
RenderFields,
|
||||
buildStateFromSchema,
|
||||
fieldTypes,
|
||||
getFormState,
|
||||
useAuth,
|
||||
useConfig,
|
||||
useDocumentInfo,
|
||||
useLocale,
|
||||
useTranslation,
|
||||
} from '@payloadcms/ui'
|
||||
import { sanitizeFields } from 'payload/config'
|
||||
import { deepCopyObject } from 'payload/utilities'
|
||||
import React, { useCallback, useEffect, useState } from 'react'
|
||||
import { Transforms } from 'slate'
|
||||
import { ReactEditor, useSlateStatic } from 'slate-react'
|
||||
|
||||
import type { ElementProps } from '..'
|
||||
import type { FormFieldBase } from '../../../../../../../ui/src/forms/fields/shared'
|
||||
import type { UploadElementType } from '../../types'
|
||||
|
||||
export const UploadDrawer: React.FC<
|
||||
ElementProps & {
|
||||
drawerSlug: string
|
||||
relatedCollection: SanitizedCollectionConfig
|
||||
import { FieldPathProvider } from '../../../../../../../ui/src/forms/FieldPathProvider'
|
||||
import { uploadFieldsSchemaPath } from '../../shared'
|
||||
|
||||
export const UploadDrawer: React.FC<{
|
||||
drawerSlug: string
|
||||
element: UploadElementType
|
||||
fieldProps: FormFieldBase & {
|
||||
name: string
|
||||
richTextComponentMap: Map<string, React.ReactNode>
|
||||
}
|
||||
> = (props) => {
|
||||
relatedCollection: SanitizedCollectionConfig
|
||||
schemaPath: string
|
||||
}> = (props) => {
|
||||
const editor = useSlateStatic()
|
||||
|
||||
const { drawerSlug, element, fieldProps, relatedCollection } = props
|
||||
const { drawerSlug, element, fieldProps, relatedCollection, schemaPath } = props
|
||||
|
||||
const { i18n, t } = useTranslation()
|
||||
const { code: locale } = useLocale()
|
||||
const { user } = useAuth()
|
||||
const { closeModal } = useModal()
|
||||
const { getDocPreferences } = useDocumentInfo()
|
||||
const { id, getDocPreferences } = useDocumentInfo()
|
||||
const [initialState, setInitialState] = useState({})
|
||||
const fieldSchemaUnsanitized =
|
||||
fieldProps?.admin?.upload?.collections?.[relatedCollection.slug]?.fields
|
||||
const config = useConfig()
|
||||
const { richTextComponentMap } = fieldProps
|
||||
|
||||
// Sanitize custom fields here
|
||||
const validRelationships = config.collections.map((c) => c.slug) || []
|
||||
const fieldSchema = sanitizeFields({
|
||||
config: config,
|
||||
fields: fieldSchemaUnsanitized,
|
||||
validRelationships,
|
||||
})
|
||||
const relatedFieldSchemaPath = `${uploadFieldsSchemaPath}.${relatedCollection.slug}`
|
||||
const fieldMap = richTextComponentMap.get(relatedFieldSchemaPath)
|
||||
|
||||
const config = useConfig()
|
||||
|
||||
const handleUpdateEditData = useCallback(
|
||||
(_, data) => {
|
||||
@@ -68,31 +69,38 @@ export const UploadDrawer: React.FC<
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
// Sanitize custom fields here
|
||||
const validRelationships = config.collections.map((c) => c.slug) || []
|
||||
const fieldSchema = sanitizeFields({
|
||||
config: config,
|
||||
fields: fieldSchemaUnsanitized,
|
||||
validRelationships,
|
||||
})
|
||||
const data = deepCopyObject(element?.fields || {})
|
||||
|
||||
const awaitInitialState = async () => {
|
||||
const preferences = await getDocPreferences()
|
||||
const state = await buildStateFromSchema({
|
||||
config,
|
||||
data: deepCopyObject(element?.fields || {}),
|
||||
fieldSchema,
|
||||
locale,
|
||||
operation: 'update',
|
||||
preferences,
|
||||
t,
|
||||
user,
|
||||
const docPreferences = await getDocPreferences()
|
||||
|
||||
const state = await getFormState({
|
||||
apiRoute: config.routes.api,
|
||||
body: {
|
||||
id,
|
||||
data,
|
||||
docPreferences,
|
||||
operation: 'update',
|
||||
schemaPath: `${schemaPath}.${uploadFieldsSchemaPath}.${relatedCollection.slug}`,
|
||||
},
|
||||
serverURL: config.serverURL,
|
||||
})
|
||||
|
||||
setInitialState(state)
|
||||
}
|
||||
|
||||
awaitInitialState()
|
||||
}, [fieldSchemaUnsanitized, config, element.fields, user, locale, t, getDocPreferences])
|
||||
}, [
|
||||
config,
|
||||
element?.fields,
|
||||
user,
|
||||
locale,
|
||||
t,
|
||||
getDocPreferences,
|
||||
id,
|
||||
schemaPath,
|
||||
relatedCollection.slug,
|
||||
])
|
||||
|
||||
return (
|
||||
<Drawer
|
||||
@@ -101,10 +109,12 @@ export const UploadDrawer: React.FC<
|
||||
label: getTranslation(relatedCollection.labels.singular, i18n),
|
||||
})}
|
||||
>
|
||||
<Form initialState={initialState} onSubmit={handleUpdateEditData}>
|
||||
<RenderFields fieldSchema={fieldSchema} fieldTypes={fieldTypes} readOnly={false} />
|
||||
<FormSubmit>{t('fields:saveChanges')}</FormSubmit>
|
||||
</Form>
|
||||
<FieldPathProvider path="" schemaPath="">
|
||||
<Form initialState={initialState} onSubmit={handleUpdateEditData}>
|
||||
<RenderFields fieldMap={Array.isArray(fieldMap) ? fieldMap : []} />
|
||||
<FormSubmit>{t('fields:saveChanges')}</FormSubmit>
|
||||
</Form>
|
||||
</FieldPathProvider>
|
||||
</Drawer>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
'use client'
|
||||
|
||||
import type { SanitizedCollectionConfig } from 'payload/types'
|
||||
import type { HTMLAttributes } from 'react'
|
||||
|
||||
import { getTranslation } from '@payloadcms/translations'
|
||||
import {
|
||||
@@ -20,9 +19,12 @@ import React, { useCallback, useReducer, useState } from 'react'
|
||||
import { Transforms } from 'slate'
|
||||
import { ReactEditor, useFocused, useSelected, useSlateStatic } from 'slate-react'
|
||||
|
||||
import type { FieldProps } from '../../../../types'
|
||||
import type { FormFieldBase } from '../../../../../../ui/src/forms/fields/shared'
|
||||
import type { UploadElementType } from '../types'
|
||||
|
||||
import { useElement } from '../../../providers/ElementProvider'
|
||||
import { EnabledRelationshipsCondition } from '../../EnabledRelationshipsCondition'
|
||||
import { uploadFieldsSchemaPath, uploadName } from '../shared'
|
||||
import { UploadDrawer } from './UploadDrawer'
|
||||
import './index.scss'
|
||||
|
||||
@@ -32,23 +34,22 @@ const initialParams = {
|
||||
depth: 0,
|
||||
}
|
||||
|
||||
export type ElementProps = {
|
||||
attributes: HTMLAttributes<HTMLDivElement>
|
||||
children: React.ReactNode
|
||||
element: any
|
||||
enabledCollectionSlugs: string[]
|
||||
fieldProps: FieldProps
|
||||
type Props = FormFieldBase & {
|
||||
name: string
|
||||
richTextComponentMap: Map<string, React.ReactNode>
|
||||
}
|
||||
|
||||
const Element: React.FC<ElementProps> = (props) => {
|
||||
const Element: React.FC<Props & { enabledCollectionSlugs?: string[] }> = ({
|
||||
enabledCollectionSlugs,
|
||||
}) => {
|
||||
const {
|
||||
attributes,
|
||||
children,
|
||||
element: { relationTo, value },
|
||||
element,
|
||||
enabledCollectionSlugs,
|
||||
fieldProps,
|
||||
} = props
|
||||
schemaPath,
|
||||
} = useElement<UploadElementType>()
|
||||
|
||||
const {
|
||||
collections,
|
||||
@@ -83,7 +84,7 @@ const Element: React.FC<ElementProps> = (props) => {
|
||||
{ initialParams },
|
||||
)
|
||||
|
||||
const thumbnailSRC = useThumbnail(relatedCollection, data)
|
||||
const thumbnailSRC = useThumbnail(relatedCollection.upload, data)
|
||||
|
||||
const removeUpload = useCallback(() => {
|
||||
const elementPath = ReactEditor.findPath(editor, element)
|
||||
@@ -103,8 +104,6 @@ const Element: React.FC<ElementProps> = (props) => {
|
||||
|
||||
Transforms.setNodes(editor, newNode, { at: elementPath })
|
||||
|
||||
// setRelatedCollection(collections.find((coll) => coll.slug === collectionConfig.slug));
|
||||
|
||||
setParams({
|
||||
...initialParams,
|
||||
cacheBust, // do this to get the usePayloadAPI to re-fetch the data even though the URL string hasn't changed
|
||||
@@ -117,17 +116,17 @@ const Element: React.FC<ElementProps> = (props) => {
|
||||
)
|
||||
|
||||
const swapUpload = React.useCallback(
|
||||
({ collectionConfig, docID }) => {
|
||||
({ collectionSlug, docID }) => {
|
||||
const newNode = {
|
||||
children: [{ text: ' ' }],
|
||||
relationTo: collectionConfig.slug,
|
||||
type: 'upload',
|
||||
relationTo: collectionSlug,
|
||||
type: uploadName,
|
||||
value: { id: docID },
|
||||
}
|
||||
|
||||
const elementPath = ReactEditor.findPath(editor, element)
|
||||
|
||||
setRelatedCollection(collections.find((coll) => coll.slug === collectionConfig.slug))
|
||||
setRelatedCollection(collections.find((coll) => coll.slug === collectionSlug))
|
||||
|
||||
Transforms.setNodes(editor, newNode, { at: elementPath })
|
||||
|
||||
@@ -137,7 +136,8 @@ const Element: React.FC<ElementProps> = (props) => {
|
||||
[closeListDrawer, editor, element, collections],
|
||||
)
|
||||
|
||||
const customFields = fieldProps?.admin?.upload?.collections?.[relatedCollection.slug]?.fields
|
||||
const relatedFieldSchemaPath = `${uploadFieldsSchemaPath}.${relatedCollection.slug}`
|
||||
const customFieldsMap = fieldProps.richTextComponentMap.get(relatedFieldSchemaPath)
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -157,10 +157,10 @@ const Element: React.FC<ElementProps> = (props) => {
|
||||
{getTranslation(relatedCollection.labels.singular, i18n)}
|
||||
</div>
|
||||
<div className={`${baseClass}__actions`}>
|
||||
{customFields?.length > 0 && (
|
||||
{Boolean(customFieldsMap) && (
|
||||
<DrawerToggler
|
||||
className={`${baseClass}__upload-drawer-toggler`}
|
||||
disabled={fieldProps?.admin?.readOnly}
|
||||
disabled={fieldProps?.readOnly}
|
||||
slug={drawerSlug}
|
||||
>
|
||||
<Button
|
||||
@@ -177,11 +177,11 @@ const Element: React.FC<ElementProps> = (props) => {
|
||||
)}
|
||||
<ListDrawerToggler
|
||||
className={`${baseClass}__list-drawer-toggler`}
|
||||
disabled={fieldProps?.admin?.readOnly}
|
||||
disabled={fieldProps?.readOnly}
|
||||
>
|
||||
<Button
|
||||
buttonStyle="icon-label"
|
||||
disabled={fieldProps?.admin?.readOnly}
|
||||
disabled={fieldProps?.readOnly}
|
||||
el="div"
|
||||
icon="swap"
|
||||
onClick={() => {
|
||||
@@ -194,7 +194,7 @@ const Element: React.FC<ElementProps> = (props) => {
|
||||
<Button
|
||||
buttonStyle="icon-label"
|
||||
className={`${baseClass}__removeButton`}
|
||||
disabled={fieldProps?.admin?.readOnly}
|
||||
disabled={fieldProps?.readOnly}
|
||||
icon="x"
|
||||
onClick={(e) => {
|
||||
e.preventDefault()
|
||||
@@ -215,12 +215,12 @@ const Element: React.FC<ElementProps> = (props) => {
|
||||
{children}
|
||||
{value?.id && <DocumentDrawer onSave={updateUpload} />}
|
||||
<ListDrawer onSelect={swapUpload} />
|
||||
<UploadDrawer drawerSlug={drawerSlug} relatedCollection={relatedCollection} {...props} />
|
||||
<UploadDrawer {...{ drawerSlug, element, fieldProps, relatedCollection, schemaPath }} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default (props: ElementProps): React.ReactNode => {
|
||||
export default (props: Props): React.ReactNode => {
|
||||
return (
|
||||
<EnabledRelationshipsCondition {...props} uploads>
|
||||
<Element {...props} />
|
||||
|
||||
@@ -1,9 +1,15 @@
|
||||
import type { RichTextCustomElement } from '../../..'
|
||||
|
||||
import Button from './Button'
|
||||
import Element from './Element'
|
||||
import plugin from './plugin'
|
||||
import { WithUpload } from './plugin'
|
||||
import { uploadName } from './shared'
|
||||
|
||||
export default {
|
||||
const upload: RichTextCustomElement = {
|
||||
name: uploadName,
|
||||
Button,
|
||||
Element,
|
||||
plugins: [plugin],
|
||||
plugins: [WithUpload],
|
||||
}
|
||||
|
||||
export default upload
|
||||
|
||||
@@ -1,10 +1,18 @@
|
||||
const withRelationship = (incomingEditor) => {
|
||||
const editor = incomingEditor
|
||||
const { isVoid } = editor
|
||||
'use client'
|
||||
|
||||
editor.isVoid = (element) => (element.type === 'upload' ? true : isVoid(element))
|
||||
import type React from 'react'
|
||||
|
||||
return editor
|
||||
import { useSlatePlugin } from '../../../utilities/useSlatePlugin'
|
||||
import { uploadName } from './shared'
|
||||
|
||||
export const WithUpload: React.FC = () => {
|
||||
useSlatePlugin('withUpload', (incomingEditor) => {
|
||||
const editor = incomingEditor
|
||||
const { isVoid } = editor
|
||||
|
||||
editor.isVoid = (element) => (element.type === uploadName ? true : isVoid(element))
|
||||
|
||||
return editor
|
||||
})
|
||||
return null
|
||||
}
|
||||
|
||||
export default withRelationship
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
export const uploadName = 'upload'
|
||||
export const uploadFieldsSchemaPath = 'upload.fields'
|
||||
@@ -0,0 +1,9 @@
|
||||
import type { Element } from 'slate'
|
||||
|
||||
export type UploadElementType = Element & {
|
||||
fields: Record<string, unknown>
|
||||
relationTo: string
|
||||
value: {
|
||||
id: number | string
|
||||
} | null
|
||||
}
|
||||
@@ -9,7 +9,9 @@ import React from 'react'
|
||||
import type { AdapterArguments, RichTextCustomElement, RichTextCustomLeaf } from '.'
|
||||
|
||||
import elementTypes from './field/elements'
|
||||
import { linkFieldsSchemaPath } from './field/elements/link/shared'
|
||||
import { transformExtraFields } from './field/elements/link/utilities'
|
||||
import { uploadFieldsSchemaPath } from './field/elements/upload/shared'
|
||||
import leafTypes from './field/leaves'
|
||||
|
||||
export const getGenerateComponentMap =
|
||||
@@ -81,13 +83,44 @@ export const getGenerateComponentMap =
|
||||
readOnly: false,
|
||||
})
|
||||
|
||||
componentMap.set('link.fields', mappedFields)
|
||||
componentMap.set(linkFieldsSchemaPath, mappedFields)
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
case 'upload':
|
||||
case 'upload': {
|
||||
const uploadEnabledCollections = config.collections.filter(
|
||||
({ admin: { enableRichTextRelationship, hidden }, upload }) => {
|
||||
if (hidden === true) {
|
||||
return false
|
||||
}
|
||||
|
||||
return enableRichTextRelationship && Boolean(upload) === true
|
||||
},
|
||||
)
|
||||
|
||||
uploadEnabledCollections.forEach((collection) => {
|
||||
if (args?.admin?.upload?.collections[collection.slug]?.fields) {
|
||||
const uploadFields = sanitizeFields({
|
||||
config,
|
||||
fields: args?.admin?.upload?.collections[collection.slug]?.fields,
|
||||
validRelationships,
|
||||
})
|
||||
|
||||
const mappedFields = mapFields({
|
||||
config,
|
||||
fieldSchema: uploadFields,
|
||||
operation: 'update',
|
||||
permissions: {},
|
||||
readOnly: false,
|
||||
})
|
||||
|
||||
componentMap.set(`${uploadFieldsSchemaPath}.${collection.slug}`, mappedFields)
|
||||
}
|
||||
})
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
case 'relationship':
|
||||
break
|
||||
|
||||
@@ -9,6 +9,7 @@ import type { AdapterArguments, RichTextCustomElement } from '.'
|
||||
import elementTypes from './field/elements'
|
||||
import { linkFieldsSchemaPath } from './field/elements/link/shared'
|
||||
import { transformExtraFields } from './field/elements/link/utilities'
|
||||
import { uploadFieldsSchemaPath } from './field/elements/upload/shared'
|
||||
|
||||
export const getGenerateSchemaMap =
|
||||
(args: AdapterArguments): RichTextAdapter['generateSchemaMap'] =>
|
||||
@@ -29,18 +30,44 @@ export const getGenerateSchemaMap =
|
||||
switch (element.name) {
|
||||
case 'link': {
|
||||
const linkFields = sanitizeFields({
|
||||
config: config,
|
||||
config,
|
||||
fields: transformExtraFields(args.admin?.link?.fields, config, i18n),
|
||||
validRelationships,
|
||||
})
|
||||
|
||||
schemaMap.set(`${schemaPath}.${linkFieldsSchemaPath}`, linkFields)
|
||||
|
||||
return
|
||||
break
|
||||
}
|
||||
|
||||
case 'upload':
|
||||
case 'upload': {
|
||||
const uploadEnabledCollections = config.collections.filter(
|
||||
({ admin: { enableRichTextRelationship, hidden }, upload }) => {
|
||||
if (hidden === true) {
|
||||
return false
|
||||
}
|
||||
|
||||
return enableRichTextRelationship && Boolean(upload) === true
|
||||
},
|
||||
)
|
||||
|
||||
uploadEnabledCollections.forEach((collection) => {
|
||||
if (args?.admin?.upload?.collections[collection.slug]?.fields) {
|
||||
const uploadFields = sanitizeFields({
|
||||
config,
|
||||
fields: args?.admin?.upload?.collections[collection.slug]?.fields,
|
||||
validRelationships,
|
||||
})
|
||||
|
||||
schemaMap.set(
|
||||
`${schemaPath}.${uploadFieldsSchemaPath}.${collection.slug}`,
|
||||
uploadFields,
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
case 'relationship':
|
||||
break
|
||||
|
||||
Reference in New Issue
Block a user