feat(richtext-lexical): link markdown transformers (#6543)

Closes https://github.com/payloadcms/payload/issues/6507

---------

Co-authored-by: ShawnVogt <41651465+shawnvogt@users.noreply.github.com>
This commit is contained in:
Alessio Gravili
2024-05-28 23:28:26 -04:00
committed by GitHub
parent e0b201c810
commit 33d53121a2
8 changed files with 234 additions and 72 deletions

View File

@@ -14,6 +14,7 @@ import { LinkIcon } from '../../lexical/ui/icons/Link/index.js'
import { getSelectedNode } from '../../lexical/utils/getSelectedNode.js' import { getSelectedNode } from '../../lexical/utils/getSelectedNode.js'
import { createClientComponent } from '../createClientComponent.js' import { createClientComponent } from '../createClientComponent.js'
import { toolbarFeatureButtonsGroupWithItems } from '../shared/toolbar/featureButtonsGroup.js' import { toolbarFeatureButtonsGroupWithItems } from '../shared/toolbar/featureButtonsGroup.js'
import { LinkMarkdownTransformer } from './markdownTransformer.js'
import { AutoLinkNode } from './nodes/AutoLinkNode.js' import { AutoLinkNode } from './nodes/AutoLinkNode.js'
import { $isLinkNode, LinkNode, TOGGLE_LINK_COMMAND } from './nodes/LinkNode.js' import { $isLinkNode, LinkNode, TOGGLE_LINK_COMMAND } from './nodes/LinkNode.js'
import { AutoLinkPlugin } from './plugins/autoLink/index.js' import { AutoLinkPlugin } from './plugins/autoLink/index.js'
@@ -84,6 +85,7 @@ const LinkFeatureClient: FeatureProviderProviderClient<ClientProps> = (props) =>
clientFeatureProps: props, clientFeatureProps: props,
feature: () => ({ feature: () => ({
clientFeatureProps: props, clientFeatureProps: props,
markdownTransformers: [LinkMarkdownTransformer],
nodes: [LinkNode, AutoLinkNode], nodes: [LinkNode, AutoLinkNode],
plugins: [ plugins: [
{ {

View File

@@ -12,6 +12,7 @@ import { convertLexicalNodesToHTML } from '../converters/html/converter/index.js
import { createNode } from '../typeUtilities.js' import { createNode } from '../typeUtilities.js'
import { LinkFeatureClientComponent } from './feature.client.js' import { LinkFeatureClientComponent } from './feature.client.js'
import { i18n } from './i18n.js' import { i18n } from './i18n.js'
import { LinkMarkdownTransformer } from './markdownTransformer.js'
import { AutoLinkNode } from './nodes/AutoLinkNode.js' import { AutoLinkNode } from './nodes/AutoLinkNode.js'
import { LinkNode } from './nodes/LinkNode.js' import { LinkNode } from './nodes/LinkNode.js'
import { transformExtraFields } from './plugins/floatingLinkEditor/utilities.js' import { transformExtraFields } from './plugins/floatingLinkEditor/utilities.js'
@@ -110,6 +111,7 @@ export const LinkFeature: FeatureProviderProviderServer<LinkFeatureServerProps,
return schemaMap return schemaMap
}, },
i18n, i18n,
markdownTransformers: [LinkMarkdownTransformer],
nodes: [ nodes: [
createNode({ createNode({
converters: { converters: {

View File

@@ -0,0 +1,53 @@
/**
* Code taken from https://github.com/facebook/lexical/blob/main/packages/lexical-markdown/src/MarkdownTransformers.ts#L357
*/
// Order of text transformers matters:
//
// - code should go first as it prevents any transformations inside
import type { TextMatchTransformer } from '@lexical/markdown'
import { $createTextNode, $isTextNode } from 'lexical'
import { $createLinkNode, $isLinkNode, LinkNode } from './nodes/LinkNode.js'
// - then longer tags match (e.g. ** or __ should go before * or _)
export const LinkMarkdownTransformer: TextMatchTransformer = {
type: 'text-match',
dependencies: [LinkNode],
export: (_node, exportChildren, exportFormat) => {
if (!$isLinkNode(_node)) {
return null
}
const node: LinkNode = _node
const { url } = node.getFields()
const linkContent = `[${node.getTextContent()}](${url})`
const firstChild = node.getFirstChild()
// Add text styles only if link has single text node inside. If it's more
// then one we ignore it as markdown does not support nested styles for links
if (node.getChildrenSize() === 1 && $isTextNode(firstChild)) {
return exportFormat(firstChild, linkContent)
} else {
return linkContent
}
},
importRegExp: /\[([^[]+)\]\(([^()\s]+)(?:\s"((?:[^"]*\\")*[^"]*)"\s*)?\)/,
regExp: /\[([^[]+)\]\(([^()\s]+)(?:\s"((?:[^"]*\\")*[^"]*)"\s*)?\)$/,
replace: (textNode, match) => {
const [, linkText, linkUrl] = match
const linkNode = $createLinkNode({
fields: {
doc: null,
linkType: 'custom',
newTab: false,
url: linkUrl,
},
})
const linkTextNode = $createTextNode(linkText)
linkTextNode.setFormat(textNode.getFormat())
linkNode.append(linkTextNode)
textNode.replace(linkNode)
},
trigger: ')',
}

19
pnpm-lock.yaml generated
View File

@@ -1677,6 +1677,12 @@ importers:
test: test:
devDependencies: devDependencies:
'@lexical/headless':
specifier: 0.15.0
version: 0.15.0
'@lexical/markdown':
specifier: 0.15.0
version: 0.15.0
'@payloadcms/db-mongodb': '@payloadcms/db-mongodb':
specifier: workspace:* specifier: workspace:*
version: link:../packages/db-mongodb version: link:../packages/db-mongodb
@@ -5585,7 +5591,6 @@ packages:
'@lexical/selection': 0.15.0 '@lexical/selection': 0.15.0
'@lexical/utils': 0.15.0 '@lexical/utils': 0.15.0
lexical: 0.15.0 lexical: 0.15.0
dev: false
/@lexical/code@0.15.0: /@lexical/code@0.15.0:
resolution: {integrity: sha512-n185gjinGhz/M4BW1ayNPYAEgwW4T/NEFl2Wey/O+07W3zvh9k9ai7RjWd0c8Qzqc4DLlqvibvWPebWObQHA4w==} resolution: {integrity: sha512-n185gjinGhz/M4BW1ayNPYAEgwW4T/NEFl2Wey/O+07W3zvh9k9ai7RjWd0c8Qzqc4DLlqvibvWPebWObQHA4w==}
@@ -5593,7 +5598,6 @@ packages:
'@lexical/utils': 0.15.0 '@lexical/utils': 0.15.0
lexical: 0.15.0 lexical: 0.15.0
prismjs: 1.29.0 prismjs: 1.29.0
dev: false
/@lexical/devtools-core@0.15.0(react-dom@19.0.0-rc-f994737d14-20240522)(react@19.0.0-rc-f994737d14-20240522): /@lexical/devtools-core@0.15.0(react-dom@19.0.0-rc-f994737d14-20240522)(react@19.0.0-rc-f994737d14-20240522):
resolution: {integrity: sha512-kK/IVEiQyqs2DsY4QRYFaFiKQMpaAukAl8PXmNeGTZ7cfFVsP29E4n0/pjY+oxmiRvxbO1s2i14q58nfuhj4VQ==} resolution: {integrity: sha512-kK/IVEiQyqs2DsY4QRYFaFiKQMpaAukAl8PXmNeGTZ7cfFVsP29E4n0/pjY+oxmiRvxbO1s2i14q58nfuhj4VQ==}
@@ -5636,7 +5640,6 @@ packages:
resolution: {integrity: sha512-soLjCphUEHw+z2ulV9cOtisTWmGj6k7TU+O/6nzgn7E1FlvskrrykGhYFrXDsXqB1wJRaILHKlHxQSoNzf931A==} resolution: {integrity: sha512-soLjCphUEHw+z2ulV9cOtisTWmGj6k7TU+O/6nzgn7E1FlvskrrykGhYFrXDsXqB1wJRaILHKlHxQSoNzf931A==}
dependencies: dependencies:
lexical: 0.15.0 lexical: 0.15.0
dev: false
/@lexical/history@0.15.0: /@lexical/history@0.15.0:
resolution: {integrity: sha512-r+pzR2k/51AL6l8UfXeVe/GWPIeWY1kEOuKx9nsYB9tmAkTF66tTFz33DJIMWBVtAHWN7Dcdv0/yy6q8R6CAUQ==} resolution: {integrity: sha512-r+pzR2k/51AL6l8UfXeVe/GWPIeWY1kEOuKx9nsYB9tmAkTF66tTFz33DJIMWBVtAHWN7Dcdv0/yy6q8R6CAUQ==}
@@ -5651,21 +5654,18 @@ packages:
'@lexical/selection': 0.15.0 '@lexical/selection': 0.15.0
'@lexical/utils': 0.15.0 '@lexical/utils': 0.15.0
lexical: 0.15.0 lexical: 0.15.0
dev: false
/@lexical/link@0.15.0: /@lexical/link@0.15.0:
resolution: {integrity: sha512-KBV/zWk5FxqZGNcq3IKGBDCcS4t0uteU1osAIG+pefo4waTkOOgibxxEJDop2QR5wtjkYva3Qp0D8ZyJDMMMlw==} resolution: {integrity: sha512-KBV/zWk5FxqZGNcq3IKGBDCcS4t0uteU1osAIG+pefo4waTkOOgibxxEJDop2QR5wtjkYva3Qp0D8ZyJDMMMlw==}
dependencies: dependencies:
'@lexical/utils': 0.15.0 '@lexical/utils': 0.15.0
lexical: 0.15.0 lexical: 0.15.0
dev: false
/@lexical/list@0.15.0: /@lexical/list@0.15.0:
resolution: {integrity: sha512-JuF4k7uo4rZFOSZGrmkxo1+sUrwTKNBhhJAiCgtM+6TO90jppxzCFNKur81yPzF1+g4GWLC9gbjzKb52QPb6cQ==} resolution: {integrity: sha512-JuF4k7uo4rZFOSZGrmkxo1+sUrwTKNBhhJAiCgtM+6TO90jppxzCFNKur81yPzF1+g4GWLC9gbjzKb52QPb6cQ==}
dependencies: dependencies:
'@lexical/utils': 0.15.0 '@lexical/utils': 0.15.0
lexical: 0.15.0 lexical: 0.15.0
dev: false
/@lexical/mark@0.15.0: /@lexical/mark@0.15.0:
resolution: {integrity: sha512-cdePA98sOJRc4/HHqcOcPBFq4UDwzaFJOK1N1E6XUGcXH1GU8zHtV1ElTgmbsGkyjBRwhR+OqKm9eso1PBOUkg==} resolution: {integrity: sha512-cdePA98sOJRc4/HHqcOcPBFq4UDwzaFJOK1N1E6XUGcXH1GU8zHtV1ElTgmbsGkyjBRwhR+OqKm9eso1PBOUkg==}
@@ -5684,7 +5684,6 @@ packages:
'@lexical/text': 0.15.0 '@lexical/text': 0.15.0
'@lexical/utils': 0.15.0 '@lexical/utils': 0.15.0
lexical: 0.15.0 lexical: 0.15.0
dev: false
/@lexical/offset@0.15.0: /@lexical/offset@0.15.0:
resolution: {integrity: sha512-VO1f3m8+RRdRjuXMtCBhi1COVKRC2LhP8AFYxnFlvbV+Waz9R5xB9pqFFUe4RtyqyTLmOUj6+LtsUFhq+23voQ==} resolution: {integrity: sha512-VO1f3m8+RRdRjuXMtCBhi1COVKRC2LhP8AFYxnFlvbV+Waz9R5xB9pqFFUe4RtyqyTLmOUj6+LtsUFhq+23voQ==}
@@ -5746,26 +5745,22 @@ packages:
'@lexical/selection': 0.15.0 '@lexical/selection': 0.15.0
'@lexical/utils': 0.15.0 '@lexical/utils': 0.15.0
lexical: 0.15.0 lexical: 0.15.0
dev: false
/@lexical/selection@0.15.0: /@lexical/selection@0.15.0:
resolution: {integrity: sha512-S+AQC6eJiQYSa5zOPuecN85prCT0Bcb8miOdJaE17Zh+vgdUH5gk9I0tEBeG5T7tkSpq6lFiEqs2FZSfaHflbQ==} resolution: {integrity: sha512-S+AQC6eJiQYSa5zOPuecN85prCT0Bcb8miOdJaE17Zh+vgdUH5gk9I0tEBeG5T7tkSpq6lFiEqs2FZSfaHflbQ==}
dependencies: dependencies:
lexical: 0.15.0 lexical: 0.15.0
dev: false
/@lexical/table@0.15.0: /@lexical/table@0.15.0:
resolution: {integrity: sha512-3IRBg8IoIHetqKozRQbJQ2aPyG0ziXZ+lc8TOIAGs6METW/wxntaV+rTNrODanKAgvk2iJTIyfFkYjsqS9+VFg==} resolution: {integrity: sha512-3IRBg8IoIHetqKozRQbJQ2aPyG0ziXZ+lc8TOIAGs6METW/wxntaV+rTNrODanKAgvk2iJTIyfFkYjsqS9+VFg==}
dependencies: dependencies:
'@lexical/utils': 0.15.0 '@lexical/utils': 0.15.0
lexical: 0.15.0 lexical: 0.15.0
dev: false
/@lexical/text@0.15.0: /@lexical/text@0.15.0:
resolution: {integrity: sha512-WsAkAt9T1RH1iDrVuWeoRUeMCOAWar5oSFtnQ4m9vhT/zuf5b8efK87GiqCH00ZAn4DGzOuAfyXlMFqBVCQdkQ==} resolution: {integrity: sha512-WsAkAt9T1RH1iDrVuWeoRUeMCOAWar5oSFtnQ4m9vhT/zuf5b8efK87GiqCH00ZAn4DGzOuAfyXlMFqBVCQdkQ==}
dependencies: dependencies:
lexical: 0.15.0 lexical: 0.15.0
dev: false
/@lexical/utils@0.15.0: /@lexical/utils@0.15.0:
resolution: {integrity: sha512-/6954LDmTcVFgexhy5WOZDa4TxNQOEZNrf8z7TRAFiAQkihcME/GRoq1en5cbXoVNF8jv5AvNyyc7x0MByRJ6A==} resolution: {integrity: sha512-/6954LDmTcVFgexhy5WOZDa4TxNQOEZNrf8z7TRAFiAQkihcME/GRoq1en5cbXoVNF8jv5AvNyyc7x0MByRJ6A==}
@@ -5774,7 +5769,6 @@ packages:
'@lexical/selection': 0.15.0 '@lexical/selection': 0.15.0
'@lexical/table': 0.15.0 '@lexical/table': 0.15.0
lexical: 0.15.0 lexical: 0.15.0
dev: false
/@lexical/yjs@0.15.0(yjs@13.6.14): /@lexical/yjs@0.15.0(yjs@13.6.14):
resolution: {integrity: sha512-Rf4AIu620Cq90li6GU58gkzlGRdntHP4ZeZrbJ3ToW7vEEnkW6Wl9/HhO647GG4OL5w46M0iWvx1b1b8xjYT1w==} resolution: {integrity: sha512-Rf4AIu620Cq90li6GU58gkzlGRdntHP4ZeZrbJ3ToW7vEEnkW6Wl9/HhO647GG4OL5w46M0iWvx1b1b8xjYT1w==}
@@ -15437,7 +15431,6 @@ packages:
/prismjs@1.29.0: /prismjs@1.29.0:
resolution: {integrity: sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q==} resolution: {integrity: sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q==}
engines: {node: '>=6'} engines: {node: '>=6'}
dev: false
/process-nextick-args@2.0.1: /process-nextick-args@2.0.1:
resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==}

View File

@@ -1,5 +1,11 @@
import type { ServerEditorConfig } from '@payloadcms/richtext-lexical'
import type { SerializedEditorState } from 'lexical'
import type { CollectionConfig } from 'payload/types' import type { CollectionConfig } from 'payload/types'
import { createHeadlessEditor } from '@lexical/headless'
import { $convertToMarkdownString } from '@lexical/markdown'
import { getEnabledNodes } from '@payloadcms/richtext-lexical'
import { sanitizeServerEditorConfig } from '@payloadcms/richtext-lexical'
import { import {
BlocksFeature, BlocksFeature,
FixedToolbarFeature, FixedToolbarFeature,
@@ -7,6 +13,7 @@ import {
LinkFeature, LinkFeature,
TreeViewFeature, TreeViewFeature,
UploadFeature, UploadFeature,
defaultEditorFeatures,
lexicalEditor, lexicalEditor,
} from '@payloadcms/richtext-lexical' } from '@payloadcms/richtext-lexical'
@@ -23,6 +30,58 @@ import {
UploadAndRichTextBlock, UploadAndRichTextBlock,
} from './blocks.js' } from './blocks.js'
const editorConfig: ServerEditorConfig = {
features: [
...defaultEditorFeatures,
//TestRecorderFeature(),
TreeViewFeature(),
//HTMLConverterFeature(),
FixedToolbarFeature(),
LinkFeature({
fields: ({ defaultFields }) => [
...defaultFields,
{
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(),
},
],
},
},
}),
BlocksFeature({
blocks: [
RichTextBlock,
TextBlock,
UploadAndRichTextBlock,
SelectFieldBlock,
RelationshipBlock,
RelationshipHasManyBlock,
SubBlockBlock,
RadioButtonsBlock,
ConditionalLayoutBlock,
],
}),
],
}
export const LexicalFields: CollectionConfig = { export const LexicalFields: CollectionConfig = {
slug: lexicalFieldsSlug, slug: lexicalFieldsSlug,
admin: { admin: {
@@ -70,56 +129,44 @@ export const LexicalFields: CollectionConfig = {
admin: { admin: {
hideGutter: false, hideGutter: false,
}, },
features: ({ defaultFeatures }) => [ features: editorConfig.features,
...defaultFeatures,
//TestRecorderFeature(),
TreeViewFeature(),
//HTMLConverterFeature(),
FixedToolbarFeature(),
LinkFeature({
fields: ({ defaultFields }) => [
...defaultFields,
{
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(),
},
],
},
},
}),
BlocksFeature({
blocks: [
RichTextBlock,
TextBlock,
UploadAndRichTextBlock,
SelectFieldBlock,
RelationshipBlock,
RelationshipHasManyBlock,
SubBlockBlock,
RadioButtonsBlock,
ConditionalLayoutBlock,
],
}),
],
}), }),
}, },
{
name: 'lexicalWithBlocks_markdown',
type: 'textarea',
hooks: {
afterRead: [
async ({ data, req, siblingData }) => {
const yourSanitizedEditorConfig = await sanitizeServerEditorConfig(
editorConfig,
req.payload.config,
)
const headlessEditor = createHeadlessEditor({
nodes: getEnabledNodes({
editorConfig: yourSanitizedEditorConfig,
}),
})
const yourEditorState: SerializedEditorState = siblingData.lexicalWithBlocks
try {
headlessEditor.setEditorState(headlessEditor.parseEditorState(yourEditorState))
} catch (e) {
/* empty */
}
// Export to markdown
let markdown: string
headlessEditor.getEditorState().read(() => {
markdown = $convertToMarkdownString(
yourSanitizedEditorConfig?.features?.markdownTransformers,
)
})
return markdown
},
],
},
},
], ],
} }

View File

@@ -220,6 +220,62 @@ describe('Lexical', () => {
expect((uploadNode.value.media as any).filename).toStrictEqual('payload.png') expect((uploadNode.value.media as any).filename).toStrictEqual('payload.png')
}) })
}) })
it('ensure link nodes convert to markdown', async () => {
const newLexicalDoc = await payload.create({
collection: lexicalFieldsSlug,
data: {
title: 'Lexical Markdown Test',
lexicalWithBlocks: {
root: {
type: 'root',
format: '',
indent: 0,
version: 1,
children: [
{
children: [
{
children: [
{
detail: 0,
format: 0,
mode: 'normal',
style: '',
text: 'link to payload',
type: 'text',
version: 1,
},
],
direction: 'ltr',
format: '',
indent: 0,
type: 'autolink',
version: 2,
fields: {
linkType: 'custom',
url: 'https://payloadcms.com',
},
},
],
direction: 'ltr',
format: '',
indent: 0,
type: 'paragraph',
version: 1,
},
],
direction: 'ltr',
},
},
},
})
expect(newLexicalDoc.lexicalWithBlocks_markdown).toEqual(
'[link to payload](https://payloadcms.com)',
)
})
describe('converters and migrations', () => { describe('converters and migrations', () => {
it('htmlConverter: should output correct HTML for top-level lexical field', async () => { it('htmlConverter: should output correct HTML for top-level lexical field', async () => {
const lexicalDoc: LexicalMigrateField = ( const lexicalDoc: LexicalMigrateField = (

View File

@@ -100,6 +100,7 @@ export interface LexicalField {
}; };
[k: string]: unknown; [k: string]: unknown;
}; };
lexicalWithBlocks_markdown?: string | null;
updatedAt: string; updatedAt: string;
createdAt: string; createdAt: string;
} }
@@ -851,7 +852,7 @@ export interface GroupField {
nestedField?: string | null; nestedField?: string | null;
}; };
}; };
groups: { groups?: {
groupInRow?: { groupInRow?: {
field?: string | null; field?: string | null;
secondField?: string | null; secondField?: string | null;
@@ -1207,16 +1208,16 @@ export interface TabsField {
}[] }[]
| null; | null;
}; };
namedTabWithDefaultValue: { namedTabWithDefaultValue?: {
defaultValue?: string | null; defaultValue?: string | null;
}; };
localizedTab: { localizedTab?: {
text?: string | null; text?: string | null;
}; };
accessControlTab: { accessControlTab?: {
text?: string | null; text?: string | null;
}; };
hooksTab: { hooksTab?: {
beforeValidate?: boolean | null; beforeValidate?: boolean | null;
beforeChange?: boolean | null; beforeChange?: boolean | null;
afterChange?: boolean | null; afterChange?: boolean | null;
@@ -1224,7 +1225,7 @@ export interface TabsField {
}; };
textarea?: string | null; textarea?: string | null;
anotherText: string; anotherText: string;
nestedTab: { nestedTab?: {
text?: string | null; text?: string | null;
}; };
updatedAt: string; updatedAt: string;
@@ -1262,6 +1263,8 @@ export interface Upload {
filesize?: number | null; filesize?: number | null;
width?: number | null; width?: number | null;
height?: number | null; height?: number | null;
focalX?: number | null;
focalY?: number | null;
} }
/** /**
* This interface was referenced by `Config`'s JSON-Schema * This interface was referenced by `Config`'s JSON-Schema
@@ -1280,6 +1283,8 @@ export interface Uploads2 {
filesize?: number | null; filesize?: number | null;
width?: number | null; width?: number | null;
height?: number | null; height?: number | null;
focalX?: number | null;
focalY?: number | null;
} }
/** /**
* This interface was referenced by `Config`'s JSON-Schema * This interface was referenced by `Config`'s JSON-Schema
@@ -1312,6 +1317,8 @@ export interface Uploads3 {
filesize?: number | null; filesize?: number | null;
width?: number | null; width?: number | null;
height?: number | null; height?: number | null;
focalX?: number | null;
focalY?: number | null;
} }
/** /**
* This interface was referenced by `Config`'s JSON-Schema * This interface was referenced by `Config`'s JSON-Schema
@@ -1363,7 +1370,7 @@ export interface PayloadMigration {
*/ */
export interface TabsWithRichText { export interface TabsWithRichText {
id: string; id: string;
tab1: { tab1?: {
rt1?: { rt1?: {
root: { root: {
type: string; type: string;
@@ -1380,7 +1387,7 @@ export interface TabsWithRichText {
[k: string]: unknown; [k: string]: unknown;
} | null; } | null;
}; };
tab2: { tab2?: {
rt2?: { rt2?: {
root: { root: {
type: string; type: string;
@@ -1413,6 +1420,6 @@ export interface LexicalBlocksRadioButtonsBlock {
declare module 'payload' { declare module 'payload' {
// @ts-ignore // @ts-ignore
export interface GeneratedTypes extends Config {} export interface GeneratedTypes extends Config {}
} }

View File

@@ -12,6 +12,8 @@
"typecheck": "pnpm turbo build --filter test && tsc --project tsconfig.typecheck.json" "typecheck": "pnpm turbo build --filter test && tsc --project tsconfig.typecheck.json"
}, },
"devDependencies": { "devDependencies": {
"@lexical/headless": "0.15.0",
"@lexical/markdown": "0.15.0",
"@payloadcms/db-mongodb": "workspace:*", "@payloadcms/db-mongodb": "workspace:*",
"@payloadcms/db-postgres": "workspace:*", "@payloadcms/db-postgres": "workspace:*",
"@payloadcms/email-nodemailer": "workspace:*", "@payloadcms/email-nodemailer": "workspace:*",