feat(richtext-lexical): make req available to html converters, use req.dataLoader instead of payload.findByID for upload node population (#6858)
This commit is contained in:
@@ -21,7 +21,7 @@ export const BlockquoteFeature: FeatureProviderProviderServer<undefined, undefin
|
||||
createNode({
|
||||
converters: {
|
||||
html: {
|
||||
converter: async ({ converters, node, parent, payload }) => {
|
||||
converter: async ({ converters, node, parent, req }) => {
|
||||
const childrenText = await convertLexicalNodesToHTML({
|
||||
converters,
|
||||
lexicalNodes: node.children,
|
||||
@@ -29,7 +29,7 @@ export const BlockquoteFeature: FeatureProviderProviderServer<undefined, undefin
|
||||
...node,
|
||||
parent,
|
||||
},
|
||||
payload,
|
||||
req,
|
||||
})
|
||||
|
||||
return `<blockquote>${childrenText}</blockquote>`
|
||||
|
||||
@@ -5,7 +5,7 @@ import type { HTMLConverter } from '../types.js'
|
||||
import { convertLexicalNodesToHTML } from '../index.js'
|
||||
|
||||
export const ParagraphHTMLConverter: HTMLConverter<SerializedParagraphNode> = {
|
||||
async converter({ converters, node, parent, payload }) {
|
||||
async converter({ converters, node, parent, req }) {
|
||||
const childrenText = await convertLexicalNodesToHTML({
|
||||
converters,
|
||||
lexicalNodes: node.children,
|
||||
@@ -13,7 +13,7 @@ export const ParagraphHTMLConverter: HTMLConverter<SerializedParagraphNode> = {
|
||||
...node,
|
||||
parent,
|
||||
},
|
||||
payload,
|
||||
req,
|
||||
})
|
||||
return `<p>${childrenText}</p>`
|
||||
},
|
||||
|
||||
@@ -1,26 +1,58 @@
|
||||
import type { SerializedEditorState, SerializedLexicalNode } from 'lexical'
|
||||
import type { Payload } from 'payload'
|
||||
import type { Payload, PayloadRequest } from 'payload'
|
||||
|
||||
import { createLocalReq } from 'payload'
|
||||
|
||||
import type { HTMLConverter, SerializedLexicalNodeWithParent } from './types.js'
|
||||
|
||||
export type ConvertLexicalToHTMLArgs = {
|
||||
converters: HTMLConverter[]
|
||||
data: SerializedEditorState
|
||||
} & (
|
||||
| {
|
||||
/**
|
||||
* This payload property will only be used if req is undefined.
|
||||
*/
|
||||
payload?: Payload
|
||||
/**
|
||||
* When the converter is called, req CAN be passed in depending on where it's run.
|
||||
* If this is undefined and config is passed through, lexical will create a new req object for you. If this is null or
|
||||
* config is undefined, lexical will not create a new req object for you and local API / server-side-only
|
||||
* functionality will be disabled.
|
||||
*/
|
||||
req?: null | undefined
|
||||
}
|
||||
| {
|
||||
/**
|
||||
* This payload property will only be used if req is undefined.
|
||||
*/
|
||||
payload?: never
|
||||
/**
|
||||
* When the converter is called, req CAN be passed in depending on where it's run.
|
||||
* If this is undefined and config is passed through, lexical will create a new req object for you. If this is null or
|
||||
* config is undefined, lexical will not create a new req object for you and local API / server-side-only
|
||||
* functionality will be disabled.
|
||||
*/
|
||||
req: PayloadRequest
|
||||
}
|
||||
)
|
||||
|
||||
export async function convertLexicalToHTML({
|
||||
converters,
|
||||
data,
|
||||
payload,
|
||||
}: {
|
||||
converters: HTMLConverter[]
|
||||
data: SerializedEditorState
|
||||
/**
|
||||
* When the converter is called, payload CAN be passed in depending on where it's run.
|
||||
*/
|
||||
payload: Payload | null
|
||||
}): Promise<string> {
|
||||
req,
|
||||
}: ConvertLexicalToHTMLArgs): Promise<string> {
|
||||
if (data?.root?.children?.length) {
|
||||
if (req === undefined && payload) {
|
||||
req = await createLocalReq({}, payload)
|
||||
}
|
||||
|
||||
return await convertLexicalNodesToHTML({
|
||||
converters,
|
||||
lexicalNodes: data?.root?.children,
|
||||
parent: data?.root,
|
||||
payload,
|
||||
req,
|
||||
})
|
||||
}
|
||||
return ''
|
||||
@@ -30,15 +62,15 @@ export async function convertLexicalNodesToHTML({
|
||||
converters,
|
||||
lexicalNodes,
|
||||
parent,
|
||||
payload,
|
||||
req,
|
||||
}: {
|
||||
converters: HTMLConverter[]
|
||||
lexicalNodes: SerializedLexicalNode[]
|
||||
parent: SerializedLexicalNodeWithParent
|
||||
/**
|
||||
* When the converter is called, payload CAN be passed in depending on where it's run.
|
||||
* When the converter is called, req CAN be passed in depending on where it's run.
|
||||
*/
|
||||
payload: Payload | null
|
||||
req: PayloadRequest | null
|
||||
}): Promise<string> {
|
||||
const unknownConverter = converters.find((converter) => converter.nodeTypes.includes('unknown'))
|
||||
|
||||
@@ -55,7 +87,7 @@ export async function convertLexicalNodesToHTML({
|
||||
converters,
|
||||
node,
|
||||
parent,
|
||||
payload,
|
||||
req,
|
||||
})
|
||||
}
|
||||
return '<span>unknown node</span>'
|
||||
@@ -65,7 +97,7 @@ export async function convertLexicalNodesToHTML({
|
||||
converters,
|
||||
node,
|
||||
parent,
|
||||
payload,
|
||||
req,
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('Error converting lexical node to HTML:', error, 'node:', node)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { SerializedLexicalNode } from 'lexical'
|
||||
import type { Payload } from 'payload'
|
||||
import type { Payload, PayloadRequest } from 'payload'
|
||||
|
||||
export type HTMLConverter<T extends SerializedLexicalNode = SerializedLexicalNode> = {
|
||||
converter: ({
|
||||
@@ -7,16 +7,16 @@ export type HTMLConverter<T extends SerializedLexicalNode = SerializedLexicalNod
|
||||
converters,
|
||||
node,
|
||||
parent,
|
||||
payload,
|
||||
req,
|
||||
}: {
|
||||
childIndex: number
|
||||
converters: HTMLConverter[]
|
||||
node: T
|
||||
parent: SerializedLexicalNodeWithParent
|
||||
/**
|
||||
* When the converter is called, payload CAN be passed in depending on where it's run.
|
||||
* When the converter is called, req CAN be passed in depending on where it's run.
|
||||
*/
|
||||
payload: Payload | null
|
||||
req: PayloadRequest | null
|
||||
}) => Promise<string> | string
|
||||
nodeTypes: string[]
|
||||
}
|
||||
|
||||
@@ -209,7 +209,7 @@ export const lexicalHTML: (
|
||||
return await convertLexicalToHTML({
|
||||
converters: finalConverters,
|
||||
data: lexicalFieldData,
|
||||
payload: req.payload,
|
||||
req,
|
||||
})
|
||||
},
|
||||
],
|
||||
|
||||
@@ -36,7 +36,7 @@ export const HeadingFeature: FeatureProviderProviderServer<
|
||||
createNode({
|
||||
converters: {
|
||||
html: {
|
||||
converter: async ({ converters, node, parent, payload }) => {
|
||||
converter: async ({ converters, node, parent, req }) => {
|
||||
const childrenText = await convertLexicalNodesToHTML({
|
||||
converters,
|
||||
lexicalNodes: node.children,
|
||||
@@ -44,7 +44,7 @@ export const HeadingFeature: FeatureProviderProviderServer<
|
||||
...node,
|
||||
parent,
|
||||
},
|
||||
payload,
|
||||
req,
|
||||
})
|
||||
|
||||
return '<' + node?.tag + '>' + childrenText + '</' + node?.tag + '>'
|
||||
|
||||
@@ -128,7 +128,7 @@ export const LinkFeature: FeatureProviderProviderServer<LinkFeatureServerProps,
|
||||
createNode({
|
||||
converters: {
|
||||
html: {
|
||||
converter: async ({ converters, node, parent, payload }) => {
|
||||
converter: async ({ converters, node, parent, req }) => {
|
||||
const childrenText = await convertLexicalNodesToHTML({
|
||||
converters,
|
||||
lexicalNodes: node.children,
|
||||
@@ -136,7 +136,7 @@ export const LinkFeature: FeatureProviderProviderServer<LinkFeatureServerProps,
|
||||
...node,
|
||||
parent,
|
||||
},
|
||||
payload,
|
||||
req,
|
||||
})
|
||||
|
||||
const rel: string = node.fields.newTab ? ' rel="noopener noreferrer"' : ''
|
||||
@@ -162,7 +162,7 @@ export const LinkFeature: FeatureProviderProviderServer<LinkFeatureServerProps,
|
||||
createNode({
|
||||
converters: {
|
||||
html: {
|
||||
converter: async ({ converters, node, parent, payload }) => {
|
||||
converter: async ({ converters, node, parent, req }) => {
|
||||
const childrenText = await convertLexicalNodesToHTML({
|
||||
converters,
|
||||
lexicalNodes: node.children,
|
||||
@@ -170,7 +170,7 @@ export const LinkFeature: FeatureProviderProviderServer<LinkFeatureServerProps,
|
||||
...node,
|
||||
parent,
|
||||
},
|
||||
payload,
|
||||
req,
|
||||
})
|
||||
|
||||
const rel: string = node.fields.newTab ? ' rel="noopener noreferrer"' : ''
|
||||
|
||||
@@ -8,7 +8,7 @@ import type { HTMLConverter } from '../converters/html/converter/types.js'
|
||||
import { convertLexicalNodesToHTML } from '../converters/html/converter/index.js'
|
||||
|
||||
export const ListHTMLConverter: HTMLConverter<SerializedListNode> = {
|
||||
converter: async ({ converters, node, parent, payload }) => {
|
||||
converter: async ({ converters, node, parent, req }) => {
|
||||
const childrenText = await convertLexicalNodesToHTML({
|
||||
converters,
|
||||
lexicalNodes: node.children,
|
||||
@@ -16,7 +16,7 @@ export const ListHTMLConverter: HTMLConverter<SerializedListNode> = {
|
||||
...node,
|
||||
parent,
|
||||
},
|
||||
payload,
|
||||
req,
|
||||
})
|
||||
|
||||
return `<${node?.tag} class="list-${node?.listType}">${childrenText}</${node?.tag}>`
|
||||
@@ -25,7 +25,7 @@ export const ListHTMLConverter: HTMLConverter<SerializedListNode> = {
|
||||
}
|
||||
|
||||
export const ListItemHTMLConverter: HTMLConverter<SerializedListItemNode> = {
|
||||
converter: async ({ converters, node, parent, payload }) => {
|
||||
converter: async ({ converters, node, parent, req }) => {
|
||||
const hasSubLists = node.children.some((child) => child.type === 'list')
|
||||
|
||||
const childrenText = await convertLexicalNodesToHTML({
|
||||
@@ -35,7 +35,7 @@ export const ListItemHTMLConverter: HTMLConverter<SerializedListItemNode> = {
|
||||
...node,
|
||||
parent,
|
||||
},
|
||||
payload,
|
||||
req,
|
||||
})
|
||||
|
||||
if ('listType' in parent && parent?.listType === 'check') {
|
||||
|
||||
@@ -84,7 +84,7 @@ export const RelationshipFeature: FeatureProviderProviderServer<
|
||||
populationPromises.push(
|
||||
populate({
|
||||
id,
|
||||
collection,
|
||||
collectionSlug: collection.config.slug,
|
||||
currentDepth,
|
||||
data: node,
|
||||
depth: populateDepth,
|
||||
|
||||
@@ -30,7 +30,7 @@ export const relationshipPopulationPromiseHOC = (
|
||||
populationPromises.push(
|
||||
populate({
|
||||
id,
|
||||
collection,
|
||||
collectionSlug: collection.config.slug,
|
||||
currentDepth,
|
||||
data: node,
|
||||
depth: populateDepth,
|
||||
|
||||
@@ -101,18 +101,28 @@ export const UploadFeature: FeatureProviderProviderServer<
|
||||
createNode({
|
||||
converters: {
|
||||
html: {
|
||||
converter: async ({ node, payload }) => {
|
||||
converter: async ({ node, req }) => {
|
||||
// @ts-expect-error
|
||||
const id = node?.value?.id || node?.value // for backwards-compatibility
|
||||
|
||||
if (payload) {
|
||||
let uploadDocument: TypeWithID & FileData
|
||||
if (req?.payload) {
|
||||
const uploadDocument: {
|
||||
value?: TypeWithID & FileData
|
||||
} = {}
|
||||
|
||||
try {
|
||||
uploadDocument = (await payload.findByID({
|
||||
await populate({
|
||||
id,
|
||||
collection: node.relationTo,
|
||||
})) as TypeWithID & FileData
|
||||
collectionSlug: node.relationTo,
|
||||
currentDepth: 0,
|
||||
data: uploadDocument,
|
||||
depth: 1,
|
||||
draft: false,
|
||||
key: 'value',
|
||||
overrideAccess: false,
|
||||
req,
|
||||
showHiddenFields: false,
|
||||
})
|
||||
} catch (ignored) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(
|
||||
@@ -124,20 +134,23 @@ export const UploadFeature: FeatureProviderProviderServer<
|
||||
return `<img />`
|
||||
}
|
||||
|
||||
const url = getAbsoluteURL(uploadDocument?.url, payload)
|
||||
const url = getAbsoluteURL(uploadDocument?.value?.url, req?.payload)
|
||||
|
||||
/**
|
||||
* If the upload is not an image, return a link to the upload
|
||||
*/
|
||||
if (!uploadDocument?.mimeType?.startsWith('image')) {
|
||||
return `<a href="${url}" rel="noopener noreferrer">${uploadDocument.filename}</a>`
|
||||
if (!uploadDocument?.value?.mimeType?.startsWith('image')) {
|
||||
return `<a href="${url}" rel="noopener noreferrer">${uploadDocument.value?.filename}</a>`
|
||||
}
|
||||
|
||||
/**
|
||||
* If the upload is a simple image with no different sizes, return a simple img tag
|
||||
*/
|
||||
if (!uploadDocument?.sizes || !Object.keys(uploadDocument?.sizes).length) {
|
||||
return `<img src="${url}" alt="${uploadDocument?.filename}" width="${uploadDocument?.width}" height="${uploadDocument?.height}"/>`
|
||||
if (
|
||||
!uploadDocument?.value?.sizes ||
|
||||
!Object.keys(uploadDocument?.value?.sizes).length
|
||||
) {
|
||||
return `<img src="${url}" alt="${uploadDocument?.value?.filename}" width="${uploadDocument?.value?.width}" height="${uploadDocument?.value?.height}"/>`
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -146,10 +159,10 @@ export const UploadFeature: FeatureProviderProviderServer<
|
||||
let pictureHTML = '<picture>'
|
||||
|
||||
// Iterate through each size in the data.sizes object
|
||||
for (const size in uploadDocument.sizes) {
|
||||
for (const size in uploadDocument.value?.sizes) {
|
||||
const imageSize: FileSize & {
|
||||
url?: string
|
||||
} = uploadDocument.sizes[size]
|
||||
} = uploadDocument.value?.sizes[size]
|
||||
|
||||
// Skip if any property of the size object is null
|
||||
if (
|
||||
@@ -162,13 +175,13 @@ export const UploadFeature: FeatureProviderProviderServer<
|
||||
) {
|
||||
continue
|
||||
}
|
||||
const imageSizeURL = getAbsoluteURL(imageSize?.url, payload)
|
||||
const imageSizeURL = getAbsoluteURL(imageSize?.url, req?.payload)
|
||||
|
||||
pictureHTML += `<source srcset="${imageSizeURL}" media="(max-width: ${imageSize.width}px)" type="${imageSize.mimeType}">`
|
||||
}
|
||||
|
||||
// Add the default img tag
|
||||
pictureHTML += `<img src="${url}" alt="Image" width="${uploadDocument.width}" height="${uploadDocument.height}">`
|
||||
pictureHTML += `<img src="${url}" alt="Image" width="${uploadDocument.value?.width}" height="${uploadDocument.value?.height}">`
|
||||
pictureHTML += '</picture>'
|
||||
return pictureHTML
|
||||
} else {
|
||||
@@ -228,7 +241,7 @@ export const UploadFeature: FeatureProviderProviderServer<
|
||||
populationPromises.push(
|
||||
populate({
|
||||
id,
|
||||
collection,
|
||||
collectionSlug: collection.config.slug,
|
||||
currentDepth,
|
||||
data: node,
|
||||
depth: populateDepth,
|
||||
|
||||
@@ -36,7 +36,7 @@ export const uploadPopulationPromiseHOC = (
|
||||
populationPromises.push(
|
||||
populate({
|
||||
id,
|
||||
collection,
|
||||
collectionSlug: collection.config.slug,
|
||||
currentDepth,
|
||||
data: node,
|
||||
depth: populateDepth,
|
||||
|
||||
@@ -15,7 +15,7 @@ type Arguments = {
|
||||
|
||||
export const populate = async ({
|
||||
id,
|
||||
collection,
|
||||
collectionSlug,
|
||||
currentDepth,
|
||||
data,
|
||||
depth,
|
||||
@@ -25,7 +25,7 @@ export const populate = async ({
|
||||
req,
|
||||
showHiddenFields,
|
||||
}: Arguments & {
|
||||
collection: Collection
|
||||
collectionSlug: string
|
||||
id: number | string
|
||||
}): Promise<void> => {
|
||||
const shouldPopulate = depth && currentDepth <= depth
|
||||
@@ -38,7 +38,7 @@ export const populate = async ({
|
||||
|
||||
const doc = await req.payloadDataLoader.load(
|
||||
createDataloaderCacheKey({
|
||||
collectionSlug: collection.config.slug,
|
||||
collectionSlug,
|
||||
currentDepth: currentDepth + 1,
|
||||
depth,
|
||||
docID: id as string,
|
||||
|
||||
@@ -672,6 +672,8 @@ export interface TextField {
|
||||
text: string;
|
||||
localizedText?: string | null;
|
||||
i18nText?: string | null;
|
||||
defaultString?: string | null;
|
||||
defaultEmptyString?: string | null;
|
||||
defaultFunction?: string | null;
|
||||
defaultAsync?: string | null;
|
||||
overrideLength?: string | null;
|
||||
@@ -915,6 +917,17 @@ export interface JsonField {
|
||||
| number
|
||||
| boolean
|
||||
| null;
|
||||
group?: {
|
||||
jsonWithinGroup?:
|
||||
| {
|
||||
[k: string]: unknown;
|
||||
}
|
||||
| unknown[]
|
||||
| string
|
||||
| number
|
||||
| boolean
|
||||
| null;
|
||||
};
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user