perf(richtext-lexical)!: greatly simplify lexical loading and improve performance (#8041)
We noticed that we can bring functions down to the client directly without having to wrap them in a component first. This greatly simplifies the loading of all lexical client components **BREAKING:** - `createClientComponent` is no longer exported as it's not needed anymore - The exported `ClientComponentProps` type has been renamed to `BaseClientFeatureProps`. - The order of arguments in `sanitizeClientEditorConfig` has changed
This commit is contained in:
@@ -3,9 +3,9 @@ import type { EditorConfig as LexicalEditorConfig } from 'lexical'
|
|||||||
import type { CellComponentProps, RichTextFieldClient } from 'payload'
|
import type { CellComponentProps, RichTextFieldClient } from 'payload'
|
||||||
|
|
||||||
import { createHeadlessEditor } from '@lexical/headless'
|
import { createHeadlessEditor } from '@lexical/headless'
|
||||||
import { useClientFunctions, useTableCell } from '@payloadcms/ui'
|
import { useTableCell } from '@payloadcms/ui'
|
||||||
import { $getRoot } from 'lexical'
|
import { $getRoot } from 'lexical'
|
||||||
import React, { useEffect, useState } from 'react'
|
import React, { useEffect, useMemo } from 'react'
|
||||||
|
|
||||||
import type { FeatureProviderClient } from '../features/typesClient.js'
|
import type { FeatureProviderClient } from '../features/typesClient.js'
|
||||||
import type { SanitizedClientEditorConfig } from '../lexical/config/types.js'
|
import type { SanitizedClientEditorConfig } from '../lexical/config/types.js'
|
||||||
@@ -24,7 +24,7 @@ export const RichTextCell: React.FC<
|
|||||||
> = (props) => {
|
> = (props) => {
|
||||||
const {
|
const {
|
||||||
admin,
|
admin,
|
||||||
field: { _schemaPath, richTextComponentMap },
|
field: { richTextComponentMap },
|
||||||
lexicalEditorConfig,
|
lexicalEditorConfig,
|
||||||
} = props
|
} = props
|
||||||
|
|
||||||
@@ -32,90 +32,35 @@ export const RichTextCell: React.FC<
|
|||||||
|
|
||||||
const { cellData } = useTableCell()
|
const { cellData } = useTableCell()
|
||||||
|
|
||||||
const clientFunctions = useClientFunctions()
|
const finalSanitizedEditorConfig = useMemo<SanitizedClientEditorConfig>(() => {
|
||||||
const [hasLoadedFeatures, setHasLoadedFeatures] = useState(false)
|
const clientFeatures: GeneratedFeatureProviderComponent[] = richTextComponentMap.get(
|
||||||
|
'features',
|
||||||
|
) as GeneratedFeatureProviderComponent[]
|
||||||
|
|
||||||
const [featureProviders, setFeatureProviders] = useState<
|
const featureProvidersLocal: FeatureProviderClient<any, any>[] = []
|
||||||
FeatureProviderClient<unknown, unknown>[]
|
for (const clientFeature of clientFeatures) {
|
||||||
>([])
|
featureProvidersLocal.push(clientFeature.clientFeature(clientFeature.clientFeatureProps))
|
||||||
|
}
|
||||||
|
|
||||||
const [finalSanitizedEditorConfig, setFinalSanitizedEditorConfig] =
|
const finalLexicalEditorConfig = lexicalEditorConfig
|
||||||
useState<SanitizedClientEditorConfig>(null)
|
? lexicalEditorConfig
|
||||||
|
: defaultEditorLexicalConfig
|
||||||
|
|
||||||
const featureProviderComponents: GeneratedFeatureProviderComponent[] = (
|
const resolvedClientFeatures = loadClientFeatures({
|
||||||
richTextComponentMap.get('features') as GeneratedFeatureProviderComponent[]
|
unSanitizedEditorConfig: {
|
||||||
).sort((a, b) => a.order - b.order) // order by order
|
features: featureProvidersLocal,
|
||||||
|
lexical: finalLexicalEditorConfig,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
let featureProvidersAndComponentsToLoad = 0 // feature providers and components
|
return sanitizeClientEditorConfig(resolvedClientFeatures, finalLexicalEditorConfig)
|
||||||
for (const featureProvider of featureProviderComponents) {
|
}, [richTextComponentMap, lexicalEditorConfig])
|
||||||
const featureComponentKeys = Array.from(richTextComponentMap.keys()).filter((key) =>
|
|
||||||
key.startsWith(`feature.${featureProvider.key}.components.`),
|
|
||||||
)
|
|
||||||
|
|
||||||
featureProvidersAndComponentsToLoad += 1
|
finalSanitizedEditorConfig.admin = admin
|
||||||
featureProvidersAndComponentsToLoad += featureComponentKeys.length
|
|
||||||
}
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!hasLoadedFeatures) {
|
|
||||||
const featureProvidersLocal: FeatureProviderClient<unknown, unknown>[] = []
|
|
||||||
let featureProvidersAndComponentsLoaded = 0 // feature providers and components only
|
|
||||||
|
|
||||||
Object.entries(clientFunctions).forEach(([key, plugin]) => {
|
|
||||||
if (key.startsWith(`lexicalFeature.${_schemaPath}.`)) {
|
|
||||||
if (!key.includes('.lexical_internal_components.')) {
|
|
||||||
featureProvidersLocal.push(plugin)
|
|
||||||
}
|
|
||||||
featureProvidersAndComponentsLoaded++
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
if (featureProvidersAndComponentsLoaded === featureProvidersAndComponentsToLoad) {
|
|
||||||
setFeatureProviders(featureProvidersLocal)
|
|
||||||
setHasLoadedFeatures(true)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Loaded feature provided => create the final sanitized editor config
|
|
||||||
*/
|
|
||||||
|
|
||||||
const resolvedClientFeatures = loadClientFeatures({
|
|
||||||
clientFunctions,
|
|
||||||
schemaPath: _schemaPath,
|
|
||||||
unSanitizedEditorConfig: {
|
|
||||||
features: featureProvidersLocal,
|
|
||||||
lexical: lexicalEditorConfig,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
setFinalSanitizedEditorConfig(
|
|
||||||
sanitizeClientEditorConfig(
|
|
||||||
lexicalEditorConfig ? lexicalEditorConfig : defaultEditorLexicalConfig,
|
|
||||||
resolvedClientFeatures,
|
|
||||||
admin,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [
|
|
||||||
admin,
|
|
||||||
featureProviderComponents,
|
|
||||||
hasLoadedFeatures,
|
|
||||||
clientFunctions,
|
|
||||||
_schemaPath,
|
|
||||||
featureProviderComponents.length,
|
|
||||||
featureProviders,
|
|
||||||
finalSanitizedEditorConfig,
|
|
||||||
lexicalEditorConfig,
|
|
||||||
richTextComponentMap,
|
|
||||||
featureProvidersAndComponentsToLoad,
|
|
||||||
])
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!hasLoadedFeatures) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
let dataToUse = cellData
|
let dataToUse = cellData
|
||||||
if (dataToUse == null || !hasLoadedFeatures || !finalSanitizedEditorConfig) {
|
if (dataToUse == null || !finalSanitizedEditorConfig) {
|
||||||
setPreview('')
|
setPreview('')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -159,38 +104,7 @@ export const RichTextCell: React.FC<
|
|||||||
|
|
||||||
// Limiting the number of characters shown is done in a CSS rule
|
// Limiting the number of characters shown is done in a CSS rule
|
||||||
setPreview(textContent)
|
setPreview(textContent)
|
||||||
}, [cellData, lexicalEditorConfig, hasLoadedFeatures, finalSanitizedEditorConfig])
|
}, [cellData, finalSanitizedEditorConfig])
|
||||||
|
|
||||||
if (!hasLoadedFeatures) {
|
|
||||||
return (
|
|
||||||
<React.Fragment>
|
|
||||||
{Array.isArray(featureProviderComponents) &&
|
|
||||||
featureProviderComponents.map((featureProvider) => {
|
|
||||||
// get all components starting with key feature.${FeatureProvider.key}.components.{featureComponentKey}
|
|
||||||
const featureComponentKeys = Array.from(richTextComponentMap.keys()).filter((key) =>
|
|
||||||
key.startsWith(
|
|
||||||
`lexical_internal_feature.${featureProvider.key}.lexical_internal_components.`,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
const featureComponents: React.ReactNode[] = featureComponentKeys.map((key) => {
|
|
||||||
return richTextComponentMap.get(key)
|
|
||||||
}) as React.ReactNode[]
|
|
||||||
|
|
||||||
return (
|
|
||||||
<React.Fragment key={featureProvider.key}>
|
|
||||||
{featureComponents?.length
|
|
||||||
? featureComponents.map((FeatureComponent) => {
|
|
||||||
return FeatureComponent
|
|
||||||
})
|
|
||||||
: null}
|
|
||||||
{featureProvider.ClientFeature}
|
|
||||||
</React.Fragment>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
</React.Fragment>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return <span>{preview}</span>
|
return <span>{preview}</span>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ export { RichTextCell } from '../../cell/index.js'
|
|||||||
export { AlignFeatureClient } from '../../features/align/client/index.js'
|
export { AlignFeatureClient } from '../../features/align/client/index.js'
|
||||||
export { BlockquoteFeatureClient } from '../../features/blockquote/client/index.js'
|
export { BlockquoteFeatureClient } from '../../features/blockquote/client/index.js'
|
||||||
export { BlocksFeatureClient } from '../../features/blocks/client/index.js'
|
export { BlocksFeatureClient } from '../../features/blocks/client/index.js'
|
||||||
export { createClientComponent } from '../../features/createClientComponent.js'
|
|
||||||
export { TestRecorderFeatureClient } from '../../features/debug/testRecorder/client/index.js'
|
export { TestRecorderFeatureClient } from '../../features/debug/testRecorder/client/index.js'
|
||||||
export { TreeViewFeatureClient } from '../../features/debug/treeView/client/index.js'
|
export { TreeViewFeatureClient } from '../../features/debug/treeView/client/index.js'
|
||||||
export { BoldFeatureClient } from '../../features/format/bold/feature.client.js'
|
export { BoldFeatureClient } from '../../features/format/bold/feature.client.js'
|
||||||
|
|||||||
@@ -1,19 +0,0 @@
|
|||||||
'use client'
|
|
||||||
|
|
||||||
import type React from 'react'
|
|
||||||
|
|
||||||
import type { ClientComponentProps, FeatureProviderProviderClient } from './typesClient.js'
|
|
||||||
|
|
||||||
import { useLexicalFeature } from '../utilities/useLexicalFeature.js'
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Utility function to create a client component for the client feature
|
|
||||||
*/
|
|
||||||
export const createClientComponent = <ClientFeatureProps,>(
|
|
||||||
clientFeature: FeatureProviderProviderClient<ClientFeatureProps>,
|
|
||||||
): React.FC<ClientComponentProps<ClientFeatureProps>> => {
|
|
||||||
return (props) => {
|
|
||||||
useLexicalFeature(props.featureKey, clientFeature(props))
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
'use client'
|
|
||||||
|
|
||||||
import type React from 'react'
|
|
||||||
|
|
||||||
import { useAddClientFunction, useFieldProps, useTableCell } from '@payloadcms/ui'
|
|
||||||
|
|
||||||
const useLexicalFeatureProp = <T,>(featureKey: string, componentKey: string, prop: T) => {
|
|
||||||
const { schemaPath: schemaPathFromFieldProps } = useFieldProps()
|
|
||||||
const tableCell = useTableCell()
|
|
||||||
|
|
||||||
const schemaPathFromCellProps = tableCell?.cellProps?.field?._schemaPath
|
|
||||||
|
|
||||||
const schemaPath = schemaPathFromCellProps || schemaPathFromFieldProps // schemaPathFromCellProps needs to have priority, as there can be cells within fields (e.g. list drawers) and the cell schemaPath needs to be used there - not the parent field schemaPath. There cannot be fields within cells.
|
|
||||||
|
|
||||||
useAddClientFunction(
|
|
||||||
`lexicalFeature.${schemaPath}.${featureKey}.lexical_internal_components.${componentKey}`,
|
|
||||||
prop,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export const createFeaturePropComponent = <T = unknown,>(
|
|
||||||
prop: T,
|
|
||||||
): React.FC<{ componentKey: string; featureKey: string }> => {
|
|
||||||
return (props) => {
|
|
||||||
useLexicalFeatureProp(props.featureKey, props.componentKey, prop)
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
import { createClientFeature } from '../../../utilities/createClientFeature.js'
|
import { createClientFeature } from '../../../utilities/createClientFeature.js'
|
||||||
import { UnknownConvertedNode } from './nodes/unknownConvertedNode/index.js'
|
import { UnknownConvertedNode } from './nodes/unknownConvertedNode/index.js'
|
||||||
|
|
||||||
export const SlateToLexicalFeatureClient = createClientFeature(({ clientFunctions }) => {
|
export const SlateToLexicalFeatureClient = createClientFeature(() => {
|
||||||
return {
|
return {
|
||||||
nodes: [UnknownConvertedNode],
|
nodes: [UnknownConvertedNode],
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import type { ToolbarGroup } from './toolbars/types.js'
|
|||||||
export type FeatureProviderProviderClient<
|
export type FeatureProviderProviderClient<
|
||||||
UnSanitizedClientFeatureProps = undefined,
|
UnSanitizedClientFeatureProps = undefined,
|
||||||
ClientFeatureProps = UnSanitizedClientFeatureProps,
|
ClientFeatureProps = UnSanitizedClientFeatureProps,
|
||||||
> = (props: ClientComponentProps<ClientFeatureProps>) => FeatureProviderClient<ClientFeatureProps>
|
> = (props: BaseClientFeatureProps<ClientFeatureProps>) => FeatureProviderClient<ClientFeatureProps>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* No dependencies => Features need to be sorted on the server first, then sent to client in right order
|
* No dependencies => Features need to be sorted on the server first, then sent to client in right order
|
||||||
@@ -27,10 +27,9 @@ export type FeatureProviderClient<
|
|||||||
/**
|
/**
|
||||||
* Return props, to make it easy to retrieve passed in props to this Feature for the client if anyone wants to
|
* Return props, to make it easy to retrieve passed in props to this Feature for the client if anyone wants to
|
||||||
*/
|
*/
|
||||||
clientFeatureProps: ClientComponentProps<UnSanitizedClientFeatureProps>
|
clientFeatureProps: BaseClientFeatureProps<UnSanitizedClientFeatureProps>
|
||||||
feature:
|
feature:
|
||||||
| ((props: {
|
| ((props: {
|
||||||
clientFunctions: Record<string, any>
|
|
||||||
/** unSanitizedEditorConfig.features, but mapped */
|
/** unSanitizedEditorConfig.features, but mapped */
|
||||||
featureProviderMap: ClientFeatureProviderMap
|
featureProviderMap: ClientFeatureProviderMap
|
||||||
// other resolved features, which have been loaded before this one. All features declared in 'dependencies' should be available here
|
// other resolved features, which have been loaded before this one. All features declared in 'dependencies' should be available here
|
||||||
@@ -105,7 +104,7 @@ export type ClientFeature<ClientFeatureProps> = {
|
|||||||
/**
|
/**
|
||||||
* Return props, to make it easy to retrieve passed in props to this Feature for the client if anyone wants to
|
* Return props, to make it easy to retrieve passed in props to this Feature for the client if anyone wants to
|
||||||
*/
|
*/
|
||||||
sanitizedClientFeatureProps?: ClientComponentProps<ClientFeatureProps>
|
sanitizedClientFeatureProps?: BaseClientFeatureProps<ClientFeatureProps>
|
||||||
slashMenu?: {
|
slashMenu?: {
|
||||||
/**
|
/**
|
||||||
* Dynamic groups allow you to add different groups depending on the query string (so, the text after the slash).
|
* Dynamic groups allow you to add different groups depending on the query string (so, the text after the slash).
|
||||||
@@ -143,7 +142,7 @@ export type ClientFeature<ClientFeatureProps> = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ClientComponentProps<ClientFeatureProps> = ClientFeatureProps extends undefined
|
export type BaseClientFeatureProps<ClientFeatureProps> = ClientFeatureProps extends undefined
|
||||||
? {
|
? {
|
||||||
featureKey: string
|
featureKey: string
|
||||||
order: number
|
order: number
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ import type {
|
|||||||
import type { ServerEditorConfig } from '../lexical/config/types.js'
|
import type { ServerEditorConfig } from '../lexical/config/types.js'
|
||||||
import type { AdapterProps } from '../types.js'
|
import type { AdapterProps } from '../types.js'
|
||||||
import type { HTMLConverter } from './converters/html/converter/types.js'
|
import type { HTMLConverter } from './converters/html/converter/types.js'
|
||||||
import type { ClientComponentProps } from './typesClient.js'
|
import type { BaseClientFeatureProps } from './typesClient.js'
|
||||||
|
|
||||||
export type PopulationPromise<T extends SerializedLexicalNode = SerializedLexicalNode> = ({
|
export type PopulationPromise<T extends SerializedLexicalNode = SerializedLexicalNode> = ({
|
||||||
context,
|
context,
|
||||||
@@ -282,7 +282,7 @@ export type NodeWithHooks<T extends LexicalNode = any> = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type ServerFeature<ServerProps, ClientFeatureProps> = {
|
export type ServerFeature<ServerProps, ClientFeatureProps> = {
|
||||||
ClientFeature?: PayloadComponent<never, ClientComponentProps<ClientFeatureProps>>
|
ClientFeature?: PayloadComponent<never, BaseClientFeatureProps<ClientFeatureProps>>
|
||||||
/**
|
/**
|
||||||
* This determines what props will be available on the Client.
|
* This determines what props will be available on the Client.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ import {
|
|||||||
} from 'lexical'
|
} from 'lexical'
|
||||||
import React, { useCallback, useEffect, useId, useReducer, useRef, useState } from 'react'
|
import React, { useCallback, useEffect, useId, useReducer, useRef, useState } from 'react'
|
||||||
|
|
||||||
import type { ClientComponentProps } from '../../../typesClient.js'
|
import type { BaseClientFeatureProps } from '../../../typesClient.js'
|
||||||
import type { UploadData } from '../../server/nodes/UploadNode.js'
|
import type { UploadData } from '../../server/nodes/UploadNode.js'
|
||||||
import type { UploadFeaturePropsClient } from '../feature.client.js'
|
import type { UploadFeaturePropsClient } from '../feature.client.js'
|
||||||
import type { UploadNode } from '../nodes/UploadNode.js'
|
import type { UploadNode } from '../nodes/UploadNode.js'
|
||||||
@@ -138,38 +138,41 @@ const Component: React.FC<ElementProps> = (props) => {
|
|||||||
},
|
},
|
||||||
[isSelected, nodeKey],
|
[isSelected, nodeKey],
|
||||||
)
|
)
|
||||||
const onClick = useCallback(
|
|
||||||
(event: MouseEvent) => {
|
|
||||||
// Check if uploadRef.target or anything WITHIN uploadRef.target was clicked
|
|
||||||
if (event.target === uploadRef.current || uploadRef.current?.contains(event.target as Node)) {
|
|
||||||
if (event.shiftKey) {
|
|
||||||
setSelected(!isSelected)
|
|
||||||
} else {
|
|
||||||
if (!isSelected) {
|
|
||||||
clearSelection()
|
|
||||||
setSelected(true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
},
|
|
||||||
[isSelected, setSelected, clearSelection],
|
|
||||||
)
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
return mergeRegister(
|
return mergeRegister(
|
||||||
editor.registerCommand<MouseEvent>(CLICK_COMMAND, onClick, COMMAND_PRIORITY_LOW),
|
editor.registerCommand<MouseEvent>(
|
||||||
|
CLICK_COMMAND,
|
||||||
|
(event: MouseEvent) => {
|
||||||
|
// Check if uploadRef.target or anything WITHIN uploadRef.target was clicked
|
||||||
|
if (
|
||||||
|
event.target === uploadRef.current ||
|
||||||
|
uploadRef.current?.contains(event.target as Node)
|
||||||
|
) {
|
||||||
|
if (event.shiftKey) {
|
||||||
|
setSelected(!isSelected)
|
||||||
|
} else {
|
||||||
|
if (!isSelected) {
|
||||||
|
clearSelection()
|
||||||
|
setSelected(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
},
|
||||||
|
COMMAND_PRIORITY_LOW,
|
||||||
|
),
|
||||||
|
|
||||||
editor.registerCommand(KEY_DELETE_COMMAND, $onDelete, COMMAND_PRIORITY_LOW),
|
editor.registerCommand(KEY_DELETE_COMMAND, $onDelete, COMMAND_PRIORITY_LOW),
|
||||||
editor.registerCommand(KEY_BACKSPACE_COMMAND, $onDelete, COMMAND_PRIORITY_LOW),
|
editor.registerCommand(KEY_BACKSPACE_COMMAND, $onDelete, COMMAND_PRIORITY_LOW),
|
||||||
)
|
)
|
||||||
}, [clearSelection, editor, isSelected, nodeKey, $onDelete, setSelected, onClick])
|
}, [clearSelection, editor, isSelected, nodeKey, $onDelete, setSelected])
|
||||||
|
|
||||||
const hasExtraFields = (
|
const hasExtraFields = (
|
||||||
editorConfig?.resolvedFeatureMap?.get('upload')
|
editorConfig?.resolvedFeatureMap?.get('upload')
|
||||||
?.sanitizedClientFeatureProps as ClientComponentProps<UploadFeaturePropsClient>
|
?.sanitizedClientFeatureProps as BaseClientFeatureProps<UploadFeaturePropsClient>
|
||||||
).collections?.[relatedCollection.slug]?.hasExtraFields
|
).collections?.[relatedCollection.slug]?.hasExtraFields
|
||||||
|
|
||||||
const onExtraFieldsDrawerSubmit = useCallback(
|
const onExtraFieldsDrawerSubmit = useCallback(
|
||||||
@@ -272,7 +275,7 @@ const Component: React.FC<ElementProps> = (props) => {
|
|||||||
</DocumentDrawerToggler>
|
</DocumentDrawerToggler>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{value && <DocumentDrawer onSave={updateUpload} />}
|
{value ? <DocumentDrawer onSave={updateUpload} /> : null}
|
||||||
{hasExtraFields ? (
|
{hasExtraFields ? (
|
||||||
<FieldsDrawer
|
<FieldsDrawer
|
||||||
data={fields}
|
data={fields}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import { ShimmerEffect, useClientFunctions, useFieldProps } from '@payloadcms/ui'
|
import { ShimmerEffect } from '@payloadcms/ui'
|
||||||
import React, { lazy, Suspense, useEffect, useState } from 'react'
|
import React, { lazy, Suspense, useEffect, useState } from 'react'
|
||||||
|
|
||||||
import type { FeatureProviderClient } from '../features/typesClient.js'
|
import type { FeatureProviderClient } from '../features/typesClient.js'
|
||||||
@@ -21,117 +21,38 @@ export const RichTextField: React.FC<LexicalRichTextFieldProps> = (props) => {
|
|||||||
field: { richTextComponentMap },
|
field: { richTextComponentMap },
|
||||||
lexicalEditorConfig,
|
lexicalEditorConfig,
|
||||||
} = props
|
} = props
|
||||||
const { schemaPath } = useFieldProps()
|
|
||||||
const clientFunctions = useClientFunctions()
|
|
||||||
const [hasLoadedFeatures, setHasLoadedFeatures] = useState(false)
|
|
||||||
|
|
||||||
const [featureProviders, setFeatureProviders] = useState<
|
|
||||||
FeatureProviderClient<unknown, unknown>[]
|
|
||||||
>([])
|
|
||||||
|
|
||||||
const [finalSanitizedEditorConfig, setFinalSanitizedEditorConfig] =
|
const [finalSanitizedEditorConfig, setFinalSanitizedEditorConfig] =
|
||||||
useState<SanitizedClientEditorConfig>(null)
|
useState<SanitizedClientEditorConfig>(null)
|
||||||
|
|
||||||
let featureProviderComponents: GeneratedFeatureProviderComponent[] = richTextComponentMap.get(
|
|
||||||
'features',
|
|
||||||
) as GeneratedFeatureProviderComponent[]
|
|
||||||
// order by order
|
|
||||||
featureProviderComponents = featureProviderComponents.sort((a, b) => a.order - b.order)
|
|
||||||
|
|
||||||
let featureProvidersAndComponentsToLoad = 0 // feature providers and components
|
|
||||||
for (const featureProvider of featureProviderComponents) {
|
|
||||||
const featureComponentKeys = Array.from(richTextComponentMap.keys()).filter((key) =>
|
|
||||||
key.startsWith(
|
|
||||||
`lexical_internal_feature.${featureProvider.key}.lexical_internal_components.`,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
featureProvidersAndComponentsToLoad += 1
|
|
||||||
featureProvidersAndComponentsToLoad += featureComponentKeys.length
|
|
||||||
}
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!hasLoadedFeatures) {
|
if (finalSanitizedEditorConfig) {
|
||||||
const featureProvidersLocal: FeatureProviderClient<unknown, unknown>[] = []
|
return
|
||||||
let featureProvidersAndComponentsLoaded = 0
|
|
||||||
|
|
||||||
Object.entries(clientFunctions).forEach(([key, plugin]) => {
|
|
||||||
if (key.startsWith(`lexicalFeature.${schemaPath}.`)) {
|
|
||||||
if (!key.includes('.lexical_internal_components.')) {
|
|
||||||
featureProvidersLocal.push(plugin)
|
|
||||||
}
|
|
||||||
|
|
||||||
featureProvidersAndComponentsLoaded++
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
if (featureProvidersAndComponentsLoaded === featureProvidersAndComponentsToLoad) {
|
|
||||||
setFeatureProviders(featureProvidersLocal)
|
|
||||||
setHasLoadedFeatures(true)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Loaded feature provided => create the final sanitized editor config
|
|
||||||
*/
|
|
||||||
|
|
||||||
const resolvedClientFeatures = loadClientFeatures({
|
|
||||||
clientFunctions,
|
|
||||||
schemaPath,
|
|
||||||
unSanitizedEditorConfig: {
|
|
||||||
features: featureProvidersLocal,
|
|
||||||
lexical: lexicalEditorConfig,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
setFinalSanitizedEditorConfig(
|
|
||||||
sanitizeClientEditorConfig(
|
|
||||||
lexicalEditorConfig ? lexicalEditorConfig : defaultEditorLexicalConfig,
|
|
||||||
resolvedClientFeatures,
|
|
||||||
admin,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}, [
|
const clientFeatures: GeneratedFeatureProviderComponent[] = richTextComponentMap.get(
|
||||||
admin,
|
'features',
|
||||||
hasLoadedFeatures,
|
) as GeneratedFeatureProviderComponent[]
|
||||||
clientFunctions,
|
|
||||||
schemaPath,
|
|
||||||
featureProviderComponents.length,
|
|
||||||
featureProviders,
|
|
||||||
finalSanitizedEditorConfig,
|
|
||||||
lexicalEditorConfig,
|
|
||||||
featureProvidersAndComponentsToLoad,
|
|
||||||
])
|
|
||||||
|
|
||||||
if (!hasLoadedFeatures) {
|
const featureProvidersLocal: FeatureProviderClient<any, any>[] = []
|
||||||
return (
|
for (const clientFeature of clientFeatures) {
|
||||||
<React.Fragment>
|
featureProvidersLocal.push(clientFeature.clientFeature(clientFeature.clientFeatureProps))
|
||||||
{Array.isArray(featureProviderComponents) &&
|
}
|
||||||
featureProviderComponents.map((featureProvider) => {
|
|
||||||
// get all components starting with key feature.${FeatureProvider.key}.components.{featureComponentKey}
|
|
||||||
const featureComponentKeys = Array.from(richTextComponentMap.keys()).filter((key) =>
|
|
||||||
key.startsWith(
|
|
||||||
`lexical_internal_feature.${featureProvider.key}.lexical_internal_components.`,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
const featureComponents: React.ReactNode[] = featureComponentKeys.map((key) => {
|
|
||||||
return richTextComponentMap.get(key)
|
|
||||||
}) as React.ReactNode[] // TODO: Type better
|
|
||||||
|
|
||||||
return (
|
const finalLexicalEditorConfig = lexicalEditorConfig
|
||||||
<React.Fragment key={featureProvider.key}>
|
? lexicalEditorConfig
|
||||||
{featureComponents?.length
|
: defaultEditorLexicalConfig
|
||||||
? featureComponents.map((FeatureComponent) => {
|
|
||||||
return FeatureComponent
|
const resolvedClientFeatures = loadClientFeatures({
|
||||||
})
|
unSanitizedEditorConfig: {
|
||||||
: null}
|
features: featureProvidersLocal,
|
||||||
{featureProvider.ClientFeature}
|
lexical: finalLexicalEditorConfig,
|
||||||
</React.Fragment>
|
},
|
||||||
)
|
})
|
||||||
})}
|
|
||||||
</React.Fragment>
|
setFinalSanitizedEditorConfig(
|
||||||
|
sanitizeClientEditorConfig(resolvedClientFeatures, finalLexicalEditorConfig, admin),
|
||||||
)
|
)
|
||||||
}
|
}, [lexicalEditorConfig, richTextComponentMap, admin, finalSanitizedEditorConfig]) // TODO: Optimize this and use useMemo for this in the future. This might break sub-richtext-blocks from the blocks feature. Need to investigate
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Suspense fallback={<ShimmerEffect height="35vh" />}>
|
<Suspense fallback={<ShimmerEffect height="35vh" />}>
|
||||||
|
|||||||
@@ -889,7 +889,7 @@ export { InlineToolbarFeature } from './features/toolbars/inline/server/index.js
|
|||||||
|
|
||||||
export type { ToolbarGroup, ToolbarGroupItem } from './features/toolbars/types.js'
|
export type { ToolbarGroup, ToolbarGroupItem } from './features/toolbars/types.js'
|
||||||
export type {
|
export type {
|
||||||
ClientComponentProps,
|
BaseClientFeatureProps,
|
||||||
ClientFeature,
|
ClientFeature,
|
||||||
ClientFeatureProviderMap,
|
ClientFeatureProviderMap,
|
||||||
FeatureProviderClient,
|
FeatureProviderClient,
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
import type {
|
import type {
|
||||||
ClientFeatureProviderMap,
|
ClientFeatureProviderMap,
|
||||||
FeatureProviderClient,
|
ResolvedClientFeature,
|
||||||
ResolvedClientFeatureMap,
|
ResolvedClientFeatureMap,
|
||||||
} from '../../../features/typesClient.js'
|
} from '../../../features/typesClient.js'
|
||||||
import type { ClientEditorConfig } from '../types.js'
|
import type { ClientEditorConfig } from '../types.js'
|
||||||
@@ -12,12 +12,8 @@ import type { ClientEditorConfig } from '../types.js'
|
|||||||
* @param unSanitizedEditorConfig
|
* @param unSanitizedEditorConfig
|
||||||
*/
|
*/
|
||||||
export function loadClientFeatures({
|
export function loadClientFeatures({
|
||||||
clientFunctions,
|
|
||||||
schemaPath,
|
|
||||||
unSanitizedEditorConfig,
|
unSanitizedEditorConfig,
|
||||||
}: {
|
}: {
|
||||||
clientFunctions?: Record<string, any>
|
|
||||||
schemaPath: string
|
|
||||||
unSanitizedEditorConfig: ClientEditorConfig
|
unSanitizedEditorConfig: ClientEditorConfig
|
||||||
}): ResolvedClientFeatureMap {
|
}): ResolvedClientFeatureMap {
|
||||||
for (const featureProvider of unSanitizedEditorConfig.features) {
|
for (const featureProvider of unSanitizedEditorConfig.features) {
|
||||||
@@ -37,50 +33,32 @@ export function loadClientFeatures({
|
|||||||
(a, b) => a.clientFeatureProps.order - b.clientFeatureProps.order,
|
(a, b) => a.clientFeatureProps.order - b.clientFeatureProps.order,
|
||||||
)
|
)
|
||||||
|
|
||||||
const featureProviderMap: ClientFeatureProviderMap = new Map(
|
const featureProviderMap: ClientFeatureProviderMap = new Map()
|
||||||
unSanitizedEditorConfig.features.map(
|
for (const feature of unSanitizedEditorConfig.features) {
|
||||||
(f) =>
|
featureProviderMap.set(feature.clientFeatureProps.featureKey, feature)
|
||||||
[f.clientFeatureProps.featureKey, f] as [string, FeatureProviderClient<unknown, unknown>],
|
}
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
const resolvedFeatures: ResolvedClientFeatureMap = new Map()
|
const resolvedFeatures: ResolvedClientFeatureMap = new Map()
|
||||||
|
|
||||||
// Make sure all dependencies declared in the respective features exist
|
// Make sure all dependencies declared in the respective features exist
|
||||||
let loaded = 0
|
let loaded = 0
|
||||||
for (const featureProvider of unSanitizedEditorConfig.features) {
|
for (const featureProvider of unSanitizedEditorConfig.features) {
|
||||||
/**
|
const feature: Partial<ResolvedClientFeature<any>> =
|
||||||
* Load relevant clientFunctions scoped to this feature and then pass them to the client feature
|
|
||||||
*/
|
|
||||||
const relevantClientFunctions: Record<string, any> = {}
|
|
||||||
Object.entries(clientFunctions).forEach(([key, plugin]) => {
|
|
||||||
if (
|
|
||||||
key.startsWith(
|
|
||||||
`lexicalFeature.${schemaPath}.${featureProvider.clientFeatureProps.featureKey}.components.`,
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
const featureComponentKey = key.split(
|
|
||||||
`${schemaPath}.${featureProvider.clientFeatureProps.featureKey}.components.`,
|
|
||||||
)[1]
|
|
||||||
relevantClientFunctions[featureComponentKey] = plugin
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const feature =
|
|
||||||
typeof featureProvider.feature === 'function'
|
typeof featureProvider.feature === 'function'
|
||||||
? featureProvider.feature({
|
? featureProvider.feature({
|
||||||
clientFunctions: relevantClientFunctions,
|
|
||||||
featureProviderMap,
|
featureProviderMap,
|
||||||
resolvedFeatures,
|
resolvedFeatures,
|
||||||
unSanitizedEditorConfig,
|
unSanitizedEditorConfig,
|
||||||
})
|
})
|
||||||
: featureProvider.feature
|
: featureProvider.feature
|
||||||
|
|
||||||
resolvedFeatures.set(featureProvider.clientFeatureProps.featureKey, {
|
feature.key = featureProvider.clientFeatureProps.featureKey
|
||||||
...feature,
|
feature.order = loaded
|
||||||
key: featureProvider.clientFeatureProps.featureKey,
|
|
||||||
order: loaded,
|
resolvedFeatures.set(
|
||||||
})
|
featureProvider.clientFeatureProps.featureKey,
|
||||||
|
feature as ResolvedClientFeature<any>,
|
||||||
|
)
|
||||||
|
|
||||||
loaded++
|
loaded++
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -212,8 +212,8 @@ export const sanitizeClientFeatures = (
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function sanitizeClientEditorConfig(
|
export function sanitizeClientEditorConfig(
|
||||||
lexical: LexicalEditorConfig,
|
|
||||||
resolvedClientFeatureMap: ResolvedClientFeatureMap,
|
resolvedClientFeatureMap: ResolvedClientFeatureMap,
|
||||||
|
lexical?: LexicalEditorConfig,
|
||||||
admin?: LexicalFieldAdminProps,
|
admin?: LexicalFieldAdminProps,
|
||||||
): SanitizedClientEditorConfig {
|
): SanitizedClientEditorConfig {
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -126,18 +126,21 @@ export function setTargetLine(
|
|||||||
/**
|
/**
|
||||||
* Properly reset previous targetBlockElem styles
|
* Properly reset previous targetBlockElem styles
|
||||||
*/
|
*/
|
||||||
lastTargetBlock.elem.style.opacity = ''
|
if (lastTargetBlock?.elem) {
|
||||||
|
lastTargetBlock.elem.style.opacity = ''
|
||||||
|
|
||||||
if (lastTargetBlock?.elem === targetBlockElem) {
|
if (lastTargetBlock?.elem === targetBlockElem) {
|
||||||
if (isBelow) {
|
if (isBelow) {
|
||||||
lastTargetBlock.elem.style.marginTop = ''
|
lastTargetBlock.elem.style.marginTop = ''
|
||||||
|
} else {
|
||||||
|
lastTargetBlock.elem.style.marginBottom = ''
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
lastTargetBlock.elem.style.marginBottom = ''
|
lastTargetBlock.elem.style.marginBottom = ''
|
||||||
|
lastTargetBlock.elem.style.marginTop = ''
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
lastTargetBlock.elem.style.marginBottom = ''
|
|
||||||
lastTargetBlock.elem.style.marginTop = ''
|
|
||||||
}
|
}
|
||||||
|
|
||||||
animationTimer = 0
|
animationTimer = 0
|
||||||
return {
|
return {
|
||||||
isBelow,
|
isBelow,
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
import type { EditorConfig as LexicalEditorConfig, SerializedEditorState } from 'lexical'
|
import type { EditorConfig as LexicalEditorConfig, SerializedEditorState } from 'lexical'
|
||||||
import type { RichTextAdapter, RichTextFieldProps, SanitizedConfig } from 'payload'
|
import type { RichTextAdapter, RichTextFieldProps, SanitizedConfig } from 'payload'
|
||||||
import type React from 'react'
|
|
||||||
|
|
||||||
|
import type {
|
||||||
|
BaseClientFeatureProps,
|
||||||
|
FeatureProviderProviderClient,
|
||||||
|
} from './features/typesClient.js'
|
||||||
import type { FeatureProviderServer } from './features/typesServer.js'
|
import type { FeatureProviderServer } from './features/typesServer.js'
|
||||||
import type { SanitizedServerEditorConfig } from './lexical/config/types.js'
|
import type { SanitizedServerEditorConfig } from './lexical/config/types.js'
|
||||||
|
|
||||||
@@ -78,7 +81,6 @@ export type AdapterProps = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type GeneratedFeatureProviderComponent = {
|
export type GeneratedFeatureProviderComponent = {
|
||||||
ClientFeature: React.ReactNode
|
clientFeature: FeatureProviderProviderClient<any, any>
|
||||||
key: string
|
clientFeatureProps: BaseClientFeatureProps<object>
|
||||||
order: number
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
import type React from 'react'
|
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
ClientComponentProps,
|
BaseClientFeatureProps,
|
||||||
ClientFeature,
|
ClientFeature,
|
||||||
ClientFeatureProviderMap,
|
ClientFeatureProviderMap,
|
||||||
FeatureProviderClient,
|
FeatureProviderClient,
|
||||||
@@ -10,14 +8,11 @@ import type {
|
|||||||
} from '../features/typesClient.js'
|
} from '../features/typesClient.js'
|
||||||
import type { ClientEditorConfig } from '../lexical/config/types.js'
|
import type { ClientEditorConfig } from '../lexical/config/types.js'
|
||||||
|
|
||||||
import { createClientComponent } from '../features/createClientComponent.js'
|
|
||||||
|
|
||||||
export type CreateClientFeatureArgs<UnSanitizedClientProps, ClientProps> =
|
export type CreateClientFeatureArgs<UnSanitizedClientProps, ClientProps> =
|
||||||
| ((props: {
|
| ((props: {
|
||||||
clientFunctions: Record<string, any>
|
|
||||||
/** unSanitizedEditorConfig.features, but mapped */
|
/** unSanitizedEditorConfig.features, but mapped */
|
||||||
featureProviderMap: ClientFeatureProviderMap
|
featureProviderMap: ClientFeatureProviderMap
|
||||||
props: ClientComponentProps<UnSanitizedClientProps>
|
props: BaseClientFeatureProps<UnSanitizedClientProps>
|
||||||
// other resolved features, which have been loaded before this one. All features declared in 'dependencies' should be available here
|
// other resolved features, which have been loaded before this one. All features declared in 'dependencies' should be available here
|
||||||
resolvedFeatures: ResolvedClientFeatureMap
|
resolvedFeatures: ResolvedClientFeatureMap
|
||||||
// unSanitized EditorConfig,
|
// unSanitized EditorConfig,
|
||||||
@@ -30,7 +25,7 @@ export const createClientFeature: <
|
|||||||
ClientProps = UnSanitizedClientProps,
|
ClientProps = UnSanitizedClientProps,
|
||||||
>(
|
>(
|
||||||
args: CreateClientFeatureArgs<UnSanitizedClientProps, ClientProps>,
|
args: CreateClientFeatureArgs<UnSanitizedClientProps, ClientProps>,
|
||||||
) => React.FC<ClientComponentProps<ClientProps>> = (feature) => {
|
) => FeatureProviderProviderClient<UnSanitizedClientProps, ClientProps> = (feature) => {
|
||||||
const featureProviderProvideClient: FeatureProviderProviderClient<any, any> = (props) => {
|
const featureProviderProvideClient: FeatureProviderProviderClient<any, any> = (props) => {
|
||||||
const featureProviderClient: Partial<FeatureProviderClient<any, any>> = {
|
const featureProviderClient: Partial<FeatureProviderClient<any, any>> = {
|
||||||
clientFeatureProps: props,
|
clientFeatureProps: props,
|
||||||
@@ -38,13 +33,11 @@ export const createClientFeature: <
|
|||||||
|
|
||||||
if (typeof feature === 'function') {
|
if (typeof feature === 'function') {
|
||||||
featureProviderClient.feature = ({
|
featureProviderClient.feature = ({
|
||||||
clientFunctions,
|
|
||||||
featureProviderMap,
|
featureProviderMap,
|
||||||
resolvedFeatures,
|
resolvedFeatures,
|
||||||
unSanitizedEditorConfig,
|
unSanitizedEditorConfig,
|
||||||
}) => {
|
}) => {
|
||||||
const toReturn = feature({
|
const toReturn = feature({
|
||||||
clientFunctions,
|
|
||||||
featureProviderMap,
|
featureProviderMap,
|
||||||
props,
|
props,
|
||||||
resolvedFeatures,
|
resolvedFeatures,
|
||||||
@@ -72,5 +65,5 @@ export const createClientFeature: <
|
|||||||
return featureProviderClient as FeatureProviderClient<any, any>
|
return featureProviderClient as FeatureProviderClient<any, any>
|
||||||
}
|
}
|
||||||
|
|
||||||
return createClientComponent(featureProviderProvideClient)
|
return featureProviderProvideClient
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { getComponent } from '@payloadcms/ui/shared'
|
|||||||
import { createClientFields } from '@payloadcms/ui/utilities/createClientConfig'
|
import { createClientFields } from '@payloadcms/ui/utilities/createClientConfig'
|
||||||
import { deepCopyObjectSimple } from 'payload'
|
import { deepCopyObjectSimple } from 'payload'
|
||||||
|
|
||||||
|
import type { FeatureProviderProviderClient } from '../features/typesClient.js'
|
||||||
import type { ResolvedServerFeatureMap } from '../features/typesServer.js'
|
import type { ResolvedServerFeatureMap } from '../features/typesServer.js'
|
||||||
import type { GeneratedFeatureProviderComponent } from '../types.js'
|
import type { GeneratedFeatureProviderComponent } from '../types.js'
|
||||||
|
|
||||||
@@ -104,37 +105,28 @@ export const getGenerateComponentMap =
|
|||||||
}
|
}
|
||||||
|
|
||||||
const ClientComponent = resolvedFeature.ClientFeature
|
const ClientComponent = resolvedFeature.ClientFeature
|
||||||
const ResolvedClientComponent = getComponent({
|
const resolvedClientFeature = getComponent({
|
||||||
identifier: 'lexical-clientComponent',
|
identifier: 'lexical-clientComponent',
|
||||||
importMap,
|
importMap,
|
||||||
payloadComponent: ClientComponent,
|
payloadComponent: ClientComponent,
|
||||||
})
|
})
|
||||||
const clientComponentProps = resolvedFeature.clientFeatureProps
|
const featureProviderProviderClient =
|
||||||
|
resolvedClientFeature.Component as unknown as FeatureProviderProviderClient<any, any>
|
||||||
|
|
||||||
if (!ClientComponent) {
|
if (!ClientComponent) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const clientFeatureProps = resolvedFeature.clientFeatureProps ?? {}
|
||||||
|
clientFeatureProps.featureKey = resolvedFeature.key
|
||||||
|
clientFeatureProps.order = resolvedFeature.order
|
||||||
|
if (resolvedClientFeature.clientProps) {
|
||||||
|
clientFeatureProps.clientProps = resolvedClientFeature.clientProps
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
ClientFeature:
|
clientFeature: featureProviderProviderClient,
|
||||||
clientComponentProps && typeof clientComponentProps === 'object' ? (
|
clientFeatureProps,
|
||||||
<ResolvedClientComponent.Component
|
|
||||||
{...clientComponentProps}
|
|
||||||
featureKey={resolvedFeature.key}
|
|
||||||
key={resolvedFeature.key}
|
|
||||||
order={resolvedFeature.order}
|
|
||||||
{...(ResolvedClientComponent?.clientProps || {})}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<ResolvedClientComponent.Component
|
|
||||||
featureKey={resolvedFeature.key}
|
|
||||||
key={resolvedFeature.key}
|
|
||||||
order={resolvedFeature.order}
|
|
||||||
{...(ResolvedClientComponent?.clientProps || {})}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
key: resolvedFeature.key,
|
|
||||||
order: resolvedFeature.order,
|
|
||||||
} as GeneratedFeatureProviderComponent
|
} as GeneratedFeatureProviderComponent
|
||||||
})
|
})
|
||||||
.filter((feature) => feature !== null),
|
.filter((feature) => feature !== null),
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import type { GitCommit, RawGitCommit } from 'changelogen'
|
import type { GitCommit } from 'changelogen'
|
||||||
|
|
||||||
import chalk from 'chalk'
|
|
||||||
import { execSync } from 'child_process'
|
import { execSync } from 'child_process'
|
||||||
import fse from 'fs-extra'
|
import fse from 'fs-extra'
|
||||||
import minimist from 'minimist'
|
import minimist from 'minimist'
|
||||||
@@ -63,9 +62,10 @@ export const updateChangelog = async (args: Args = {}): Promise<ChangelogResult>
|
|||||||
|
|
||||||
const conventionalCommits = await getLatestCommits(fromVersion, toVersion)
|
const conventionalCommits = await getLatestCommits(fromVersion, toVersion)
|
||||||
|
|
||||||
const sections: Record<'breaking' | 'feat' | 'fix', string[]> = {
|
const sections: Record<'breaking' | 'feat' | 'fix' | 'perf', string[]> = {
|
||||||
feat: [],
|
feat: [],
|
||||||
fix: [],
|
fix: [],
|
||||||
|
perf: [],
|
||||||
breaking: [],
|
breaking: [],
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,7 +75,7 @@ export const updateChangelog = async (args: Args = {}): Promise<ChangelogResult>
|
|||||||
sections.breaking.push(formatCommitForChangelog(c, true))
|
sections.breaking.push(formatCommitForChangelog(c, true))
|
||||||
}
|
}
|
||||||
|
|
||||||
if (c.type === 'feat' || c.type === 'fix') {
|
if (c.type === 'feat' || c.type === 'fix' || c.type === 'perf') {
|
||||||
sections[c.type].push(formatCommitForChangelog(c))
|
sections[c.type].push(formatCommitForChangelog(c))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -89,6 +89,9 @@ export const updateChangelog = async (args: Args = {}): Promise<ChangelogResult>
|
|||||||
if (sections.feat.length) {
|
if (sections.feat.length) {
|
||||||
changelog += `### 🚀 Features\n\n${sections.feat.join('\n')}\n\n`
|
changelog += `### 🚀 Features\n\n${sections.feat.join('\n')}\n\n`
|
||||||
}
|
}
|
||||||
|
if (sections.perf.length) {
|
||||||
|
changelog += `### ⚡ Performance\n\n${sections.perf.join('\n')}\n\n`
|
||||||
|
}
|
||||||
if (sections.fix.length) {
|
if (sections.fix.length) {
|
||||||
changelog += `### 🐛 Bug Fixes\n\n${sections.fix.join('\n')}\n\n`
|
changelog += `### 🐛 Bug Fixes\n\n${sections.fix.join('\n')}\n\n`
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user