Skip to content

feat: mount live preview to document root #12860

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 27 commits into from
Jun 27, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
3fee1a7
wip: mount live preview to document root
jacobsfletch Jun 18, 2025
2f3194f
more
jacobsfletch Jun 18, 2025
736b4c9
fix toggle
jacobsfletch Jun 23, 2025
1f5a6fe
fix popup functionality
jacobsfletch Jun 23, 2025
9c33f1f
improve design
jacobsfletch Jun 23, 2025
8aec09b
fix globals
jacobsfletch Jun 24, 2025
80688f8
fix tests
jacobsfletch Jun 24, 2025
f723a1e
does not render when creating new doc
jacobsfletch Jun 24, 2025
0cd85d5
fix build
jacobsfletch Jun 24, 2025
ff0869e
saves to prefs
jacobsfletch Jun 24, 2025
cf260a7
rm irrelevant tests and safely parse url
jacobsfletch Jun 24, 2025
30b4b03
combine preview btn
jacobsfletch Jun 24, 2025
e5ba711
fix tests
jacobsfletch Jun 24, 2025
324d0bc
fix tests
jacobsfletch Jun 24, 2025
05b17f9
perf: ensures live preview events are not firing when inactive
jacobsfletch Jun 24, 2025
55a5ef5
fix live preview message handler
jacobsfletch Jun 24, 2025
b876894
fix useAsTitle
jacobsfletch Jun 24, 2025
0b7b479
fix number field tests
jacobsfletch Jun 24, 2025
fd24117
fix listeningForMessages condition
jacobsfletch Jun 25, 2025
4db35c8
ui
jacobsfletch Jun 25, 2025
b96106a
fix hydration
jacobsfletch Jun 25, 2025
b58336d
Merge branch 'main' into feat/mount-live-preview-to-doc-root
jacobsfletch Jun 26, 2025
858bf69
renames pref to editViewType
jacobsfletch Jun 26, 2025
4ca3859
Merge branch 'main' into feat/mount-live-preview-to-doc-root
jacobsfletch Jun 27, 2025
aa7da4c
fix pref set
jacobsfletch Jun 27, 2025
a9c694b
revert unrelated changes to _community
jacobsfletch Jun 27, 2025
2c42569
use new type
jacobsfletch Jun 27, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion docs/custom-components/document-views.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ export const MyCollectionOrGlobalConfig: CollectionConfig = {
// - api
// - versions
// - version
// - livePreview
// - [key: string]
// See below for more details
},
Expand Down
26 changes: 0 additions & 26 deletions packages/next/src/elements/DocumentHeader/Tabs/tabs/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,32 +28,6 @@ export const getTabs = ({
},
viewPath: '/',
},
{
tab: {
condition: ({ collectionConfig, config, globalConfig }) => {
if (collectionConfig) {
return Boolean(
config?.admin?.livePreview?.collections?.includes(collectionConfig.slug) ||
collectionConfig?.admin?.livePreview,
)
}

if (globalConfig) {
return Boolean(
config?.admin?.livePreview?.globals?.includes(globalConfig.slug) ||
globalConfig?.admin?.livePreview,
)
}

return false
},
href: '/preview',
label: ({ t }) => t('general:livePreview'),
order: 200,
...(customViews?.['livePreview']?.tab || {}),
},
viewPath: '/preview',
},
{
tab: {
condition: ({ collectionConfig, globalConfig, permissions }) =>
Expand Down
26 changes: 0 additions & 26 deletions packages/next/src/views/Document/getDocumentView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import type { ViewToRender } from './index.js'

import { APIView as DefaultAPIView } from '../API/index.js'
import { EditView as DefaultEditView } from '../Edit/index.js'
import { LivePreviewView as DefaultLivePreviewView } from '../LivePreview/index.js'
import { UnauthorizedViewWithGutter } from '../Unauthorized/index.js'
import { VersionView as DefaultVersionView } from '../Version/index.js'
import { VersionsView as DefaultVersionsView } from '../Versions/index.js'
Expand Down Expand Up @@ -112,7 +111,6 @@ export const getDocumentView = ({
}

// --> /collections/:collectionSlug/:id/api
// --> /collections/:collectionSlug/:id/preview
// --> /collections/:collectionSlug/:id/versions
// --> /collections/:collectionSlug/:id/<custom-segment>
case 4: {
Expand All @@ -125,17 +123,6 @@ export const getDocumentView = ({
break
}

case 'preview': {
// --> /collections/:collectionSlug/:id/preview
if (
(collectionConfig && collectionConfig?.admin?.livePreview) ||
config?.admin?.livePreview?.collections?.includes(collectionConfig?.slug)
) {
View = getCustomViewByKey(views, 'livePreview') || DefaultLivePreviewView
}
break
}

case 'versions': {
// --> /collections/:collectionSlug/:id/versions
if (docPermissions?.readVersions) {
Expand Down Expand Up @@ -234,7 +221,6 @@ export const getDocumentView = ({

case 3: {
// --> /globals/:globalSlug/api
// --> /globals/:globalSlug/preview
// --> /globals/:globalSlug/versions
// --> /globals/:globalSlug/<custom-segment>
switch (segment3) {
Expand All @@ -247,18 +233,6 @@ export const getDocumentView = ({
break
}

case 'preview': {
// --> /globals/:globalSlug/preview
if (
(globalConfig && globalConfig?.admin?.livePreview) ||
config?.admin?.livePreview?.globals?.includes(globalConfig?.slug)
) {
View = getCustomViewByKey(views, 'livePreview') || DefaultLivePreviewView
}

break
}

case 'versions': {
// --> /globals/:globalSlug/versions
if (docPermissions?.readVersions) {
Expand Down
9 changes: 0 additions & 9 deletions packages/next/src/views/Document/getMetaBySegment.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import type { GenerateViewMetadata } from '../Root/index.js'
import { getNextRequestI18n } from '../../utilities/getNextRequestI18n.js'
import { generateAPIViewMetadata } from '../API/metadata.js'
import { generateEditViewMetadata } from '../Edit/metadata.js'
import { generateLivePreviewViewMetadata } from '../LivePreview/metadata.js'
import { generateNotFoundViewMetadata } from '../NotFound/metadata.js'
import { generateVersionViewMetadata } from '../Version/metadata.js'
import { generateVersionsViewMetadata } from '../Versions/metadata.js'
Expand Down Expand Up @@ -50,10 +49,6 @@ export const getMetaBySegment: GenerateEditViewMetadata = async ({
// `/:collection/:id/api`
fn = generateAPIViewMetadata
break
case 'preview':
// `/:collection/:id/preview`
fn = generateLivePreviewViewMetadata
break
case 'versions':
// `/:collection/:id/versions`
fn = generateVersionsViewMetadata
Expand Down Expand Up @@ -89,10 +84,6 @@ export const getMetaBySegment: GenerateEditViewMetadata = async ({
// `/:global/api`
fn = generateAPIViewMetadata
break
case 'preview':
// `/:global/preview`
fn = generateLivePreviewViewMetadata
break
case 'versions':
// `/:global/versions`
fn = generateVersionsViewMetadata
Expand Down
82 changes: 62 additions & 20 deletions packages/next/src/views/Document/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type {
AdminViewServerProps,
CollectionPreferences,
Data,
DocumentViewClientProps,
DocumentViewServerProps,
Expand All @@ -9,7 +10,12 @@ import type {
RenderDocumentVersionsProperties,
} from 'payload'

import { DocumentInfoProvider, EditDepthProvider, HydrateAuthProvider } from '@payloadcms/ui'
import {
DocumentInfoProvider,
EditDepthProvider,
HydrateAuthProvider,
LivePreviewProvider,
} from '@payloadcms/ui'
import { RenderServerComponent } from '@payloadcms/ui/elements/RenderServerComponent'
import { isEditing as getIsEditing } from '@payloadcms/ui/shared'
import { buildFormState } from '@payloadcms/ui/utilities/buildFormState'
Expand All @@ -21,6 +27,7 @@ import React from 'react'
import type { GenerateEditViewMetadata } from './getMetaBySegment.js'

import { DocumentHeader } from '../../elements/DocumentHeader/index.js'
import { getPreferences } from '../../utilities/getPreferences.js'
import { NotFoundView } from '../NotFound/index.js'
import { getDocPreferences } from './getDocPreferences.js'
import { getDocumentData } from './getDocumentData.js'
Expand Down Expand Up @@ -84,6 +91,7 @@ export const renderDocument = async ({
payload: {
config,
config: {
admin: { livePreview: livePreviewConfig },
routes: { admin: adminRoute, api: apiRoute },
serverURL,
},
Expand Down Expand Up @@ -119,6 +127,7 @@ export const renderDocument = async ({
docPreferences,
{ docPermissions, hasPublishPermission, hasSavePermission },
{ currentEditor, isLocked, lastUpdateTime },
entityPreferences,
] = await Promise.all([
// Get document preferences
getDocPreferences({
Expand Down Expand Up @@ -146,8 +155,18 @@ export const renderDocument = async ({
isEditing,
req,
}),

// get entity preferences
getPreferences<CollectionPreferences>(
collectionSlug ? `collection-${collectionSlug}` : `global-${globalSlug}`,
payload,
req.user.id,
req.user.collection,
),
])

const operation = (collectionSlug && idFromArgs) || globalSlug ? 'update' : 'create'

const [
{ hasPublishedDoc, mostRecentVersionIsAutosaved, unpublishedVersionCount, versionCount },
{ state: formState },
Expand All @@ -171,7 +190,7 @@ export const renderDocument = async ({
fallbackLocale: false,
globalSlug,
locale: locale?.code,
operation: (collectionSlug && idFromArgs) || globalSlug ? 'update' : 'create',
operation,
renderAllFields: true,
req,
schemaPath: collectionSlug || globalSlug,
Expand Down Expand Up @@ -310,6 +329,22 @@ export const renderDocument = async ({
viewType,
}

const livePreviewURL =
typeof livePreviewConfig?.url === 'function'
? await livePreviewConfig.url({
collectionConfig,
data: doc,
globalConfig,
locale,
req,
/**
* @deprecated
* Use `req.payload` instead. This will be removed in the next major version.
*/
payload: initPageResult.req.payload,
})
: livePreviewConfig?.url

return {
data: doc,
Document: (
Expand Down Expand Up @@ -337,24 +372,31 @@ export const renderDocument = async ({
unpublishedVersionCount={unpublishedVersionCount}
versionCount={versionCount}
>
{showHeader && !drawerSlug && (
<DocumentHeader
collectionConfig={collectionConfig}
globalConfig={globalConfig}
i18n={i18n}
payload={payload}
permissions={permissions}
/>
)}
<HydrateAuthProvider permissions={permissions} />
<EditDepthProvider>
{RenderServerComponent({
clientProps,
Component: View,
importMap,
serverProps: documentViewServerProps,
})}
</EditDepthProvider>
<LivePreviewProvider
breakpoints={livePreviewConfig?.breakpoints}
isLivePreviewing={entityPreferences?.value?.editViewType === 'live-preview'}
operation={operation}
url={livePreviewURL}
>
{showHeader && !drawerSlug && (
<DocumentHeader
collectionConfig={collectionConfig}
globalConfig={globalConfig}
i18n={i18n}
payload={payload}
permissions={permissions}
/>
)}
<HydrateAuthProvider permissions={permissions} />
<EditDepthProvider>
{RenderServerComponent({
clientProps,
Component: View,
importMap,
serverProps: documentViewServerProps,
})}
</EditDepthProvider>
</LivePreviewProvider>
</DocumentInfoProvider>
),
}
Expand Down
Loading
Loading