Alternative solution to https://github.com/payloadcms/payload/pull/11104. Big thanks to @andershermansen and @GermanJablo for kickstarting work on a solution and bringing this to our attention. This PR copies over the live-preview test suite example from his PR. Fixes https://github.com/payloadcms/payload/issues/5285, https://github.com/payloadcms/payload/issues/6071 and https://github.com/payloadcms/payload/issues/8277. Potentially fixes #11801 This PR completely gets rid of our client-side live preview field traversal + population and all logic related to it, and instead lets the findByID endpoint handle it. The data sent through the live preview message event is now passed to findByID via the newly added `data` attribute. The findByID endpoint will then use this data and run hooks on it (which run population), instead of fetching the data from the database. This new API basically behaves like a `/api/populate?data=` endpoint, with the benefit that it runs all the hooks. Another use-case for it will be rendering lexical data. Sometimes you may only have unpopulated data available. This functionality allows you to then populate the lexical portion of it on-the-fly, so that you can properly render it to JSX while displaying images. ## Benefits - a lot less code to maintain. No duplicative population logic - much faster - one single API request instead of one request per relationship to populate - all payload features are now correctly supported (population and hooks) - since hooks are now running for client-side live preview, this means the `lexicalHTML` field is now supported! This was a long-running issue - this fixes a lot of population inconsistencies that we previously did not know of. For example, it previously populated lexical and slate relationships even if the data was saved in an incorrect format ## [Method Override (POST)](https://payloadcms.com/docs/rest-api/overview#using-method-override-post) change The population request to the findByID endpoint is sent as a post request, so that we can pass through the `data` without having to squeeze it into the url params. To do that, it uses the `X-Payload-HTTP-Method-Override` header. Previously, this functionality still expected the data to be sent through as URL search params - just passed to the body instead of the URL. In this PR, I made it possible to pass it as JSON instead. This means: - the receiving endpoint will receive the data under `req.data` and is not able to read it from the search params - this means existing endpoints won't support this functionality unless they also attempt to read from req.data. - for the purpose of this PR, the findByID endpoint was modified to support this behavior. This functionality is documented as it can be useful for user-defined endpoints as well. Passing data as json has the following benefits: - it's more performant - no need to serialize and deserialize data to search params via `qs-esm`. This is especially important here, as we are passing large amounts of json data - the current implementation was serializing the data incorrectly, leading to incorrect data within nested lexical nodes **Note for people passing their own live preview `requestHandler`:** instead of sending a GET request to populate documents, you will now need to send a POST request to the findByID endpoint and pass additional headers. Additionally, you will need to send through the arguments as JSON instead of search params and include `data` as an argument. Here is the updated defaultRequestHandler for reference: ```ts const defaultRequestHandler: CollectionPopulationRequestHandler = ({ apiPath, data, endpoint, serverURL, }) => { const url = `${serverURL}${apiPath}/${endpoint}` return fetch(url, { body: JSON.stringify(data), credentials: 'include', headers: { 'Content-Type': 'application/json', 'X-Payload-HTTP-Method-Override': 'GET', }, method: 'POST', }) } ``` --- - To see the specific tasks where the Asana app for GitHub is being used, see below: - https://app.asana.com/0/0/1211124793355068 - https://app.asana.com/0/0/1211124793355066
67 lines
2.1 KiB
TypeScript
67 lines
2.1 KiB
TypeScript
import { fileURLToPath } from 'node:url'
|
|
import path from 'path'
|
|
const filename = fileURLToPath(import.meta.url)
|
|
const dirname = path.dirname(filename)
|
|
import { buildConfigWithDefaults } from '../buildConfigWithDefaults.js'
|
|
import { MediaBlock } from './blocks/MediaBlock/index.js'
|
|
import { Categories } from './collections/Categories.js'
|
|
import { CollectionLevelConfig } from './collections/CollectionLevelConfig.js'
|
|
import { Media } from './collections/Media.js'
|
|
import { Pages } from './collections/Pages.js'
|
|
import { Posts } from './collections/Posts.js'
|
|
import { SSR } from './collections/SSR.js'
|
|
import { SSRAutosave } from './collections/SSRAutosave.js'
|
|
import { Tenants } from './collections/Tenants.js'
|
|
import { Users } from './collections/Users.js'
|
|
import { Footer } from './globals/Footer.js'
|
|
import { Header } from './globals/Header.js'
|
|
import { seed } from './seed/index.js'
|
|
import {
|
|
desktopBreakpoint,
|
|
mobileBreakpoint,
|
|
pagesSlug,
|
|
postsSlug,
|
|
ssrAutosavePagesSlug,
|
|
ssrPagesSlug,
|
|
} from './shared.js'
|
|
import { formatLivePreviewURL } from './utilities/formatLivePreviewURL.js'
|
|
|
|
export default buildConfigWithDefaults({
|
|
localization: {
|
|
defaultLocale: 'en',
|
|
locales: ['en', 'es'],
|
|
},
|
|
admin: {
|
|
importMap: {
|
|
baseDir: path.resolve(dirname),
|
|
},
|
|
livePreview: {
|
|
// You can define any of these properties on a per collection or global basis
|
|
// The Live Preview config cascades from the top down, properties are inherited from here
|
|
url: formatLivePreviewURL,
|
|
breakpoints: [mobileBreakpoint, desktopBreakpoint],
|
|
collections: [pagesSlug, postsSlug, ssrPagesSlug, ssrAutosavePagesSlug],
|
|
globals: ['header', 'footer'],
|
|
},
|
|
},
|
|
cors: ['http://localhost:3000', 'http://localhost:3001'],
|
|
csrf: ['http://localhost:3000', 'http://localhost:3001'],
|
|
collections: [
|
|
Users,
|
|
Pages,
|
|
Posts,
|
|
SSR,
|
|
SSRAutosave,
|
|
Tenants,
|
|
Categories,
|
|
Media,
|
|
CollectionLevelConfig,
|
|
],
|
|
globals: [Header, Footer],
|
|
onInit: seed,
|
|
typescript: {
|
|
outputFile: path.resolve(dirname, 'payload-types.ts'),
|
|
},
|
|
blocks: [MediaBlock],
|
|
})
|