From af4778c49e67782c2fc3045beacc51a5d7797c36 Mon Sep 17 00:00:00 2001 From: Aapo Laakkio Date: Fri, 5 Sep 2025 17:46:27 +0300 Subject: [PATCH 1/3] fix: Version Diff view shows correct doc title in Step Nav The title also works when useAsTitle field is nested inside group or tabs --- .../src/views/Version/Default/SetStepNav.tsx | 28 ++++--------------- .../next/src/views/Version/Default/index.tsx | 2 -- .../next/src/views/Version/Default/types.ts | 1 - packages/next/src/views/Version/index.tsx | 6 ---- test/versions/collections/Drafts.ts | 17 +++++++---- test/versions/e2e.spec.ts | 24 ++++++++++++---- test/versions/int.spec.ts | 2 +- 7 files changed, 35 insertions(+), 45 deletions(-) diff --git a/packages/next/src/views/Version/Default/SetStepNav.tsx b/packages/next/src/views/Version/Default/SetStepNav.tsx index c1fbcc114b6..d46f9def2ec 100644 --- a/packages/next/src/views/Version/Default/SetStepNav.tsx +++ b/packages/next/src/views/Version/Default/SetStepNav.tsx @@ -4,8 +4,8 @@ import type { ClientCollectionConfig, ClientGlobalConfig } from 'payload' import type React from 'react' import { getTranslation } from '@payloadcms/translations' -import { useConfig, useLocale, useStepNav, useTranslation } from '@payloadcms/ui' -import { fieldAffectsData, formatAdminURL } from 'payload/shared' +import { useConfig, useDocumentTitle, useLocale, useStepNav, useTranslation } from '@payloadcms/ui' +import { formatAdminURL } from 'payload/shared' import { useEffect } from 'react' export const SetStepNav: React.FC<{ @@ -15,7 +15,6 @@ export const SetStepNav: React.FC<{ readonly isTrashed?: boolean versionToCreatedAtFormatted?: string versionToID?: string - versionToUseAsTitle?: Record | string }> = ({ id, collectionConfig, @@ -23,12 +22,12 @@ export const SetStepNav: React.FC<{ isTrashed, versionToCreatedAtFormatted, versionToID, - versionToUseAsTitle, }) => { const { config } = useConfig() const { setStepNav } = useStepNav() const { i18n, t } = useTranslation() const locale = useLocale() + const { title } = useDocumentTitle() useEffect(() => { const { @@ -38,24 +37,7 @@ export const SetStepNav: React.FC<{ if (collectionConfig) { const collectionSlug = collectionConfig.slug - const useAsTitle = collectionConfig.admin?.useAsTitle || 'id' const pluralLabel = collectionConfig.labels?.plural - let docLabel = `[${t('general:untitled')}]` - - const fields = collectionConfig.fields - - const titleField = fields.find( - (f) => fieldAffectsData(f) && 'name' in f && f.name === useAsTitle, - ) - - if (titleField && versionToUseAsTitle) { - docLabel = - 'localized' in titleField && titleField.localized - ? versionToUseAsTitle?.[locale.code] || docLabel - : versionToUseAsTitle - } else if (useAsTitle === 'id') { - docLabel = String(id) - } const docBasePath: `/${string}` = isTrashed ? `/collections/${collectionSlug}/trash/${id}` @@ -83,7 +65,7 @@ export const SetStepNav: React.FC<{ nav.push( { - label: docLabel, + label: title, url: formatAdminURL({ adminRoute, path: docBasePath, @@ -139,7 +121,7 @@ export const SetStepNav: React.FC<{ i18n, collectionConfig, globalConfig, - versionToUseAsTitle, + title, versionToCreatedAtFormatted, versionToID, ]) diff --git a/packages/next/src/views/Version/Default/index.tsx b/packages/next/src/views/Version/Default/index.tsx index 96e1e31969e..dbf85896b15 100644 --- a/packages/next/src/views/Version/Default/index.tsx +++ b/packages/next/src/views/Version/Default/index.tsx @@ -40,7 +40,6 @@ export const DefaultVersionView: React.FC = ({ VersionToCreatedAtLabel, versionToID, versionToStatus, - versionToUseAsTitle, }) => { const { config, getEntityConfig } = useConfig() const { code } = useLocale() @@ -275,7 +274,6 @@ export const DefaultVersionView: React.FC = ({ isTrashed={isTrashed} versionToCreatedAtFormatted={versionToCreatedAtFormatted} versionToID={versionToID} - versionToUseAsTitle={versionToUseAsTitle} /> locale.name) }}> diff --git a/packages/next/src/views/Version/Default/types.ts b/packages/next/src/views/Version/Default/types.ts index 7014f33f461..5017ff2d097 100644 --- a/packages/next/src/views/Version/Default/types.ts +++ b/packages/next/src/views/Version/Default/types.ts @@ -21,5 +21,4 @@ export type DefaultVersionsViewProps = { VersionToCreatedAtLabel: React.ReactNode versionToID?: string versionToStatus?: string - versionToUseAsTitle?: string } diff --git a/packages/next/src/views/Version/index.tsx b/packages/next/src/views/Version/index.tsx index 189213d52cc..a885fb6ecfe 100644 --- a/packages/next/src/views/Version/index.tsx +++ b/packages/next/src/views/Version/index.tsx @@ -411,11 +411,6 @@ export async function VersionView(props: DocumentViewServerProps) { }) } - const useAsTitleFieldName = collectionConfig?.admin?.useAsTitle || 'id' - const versionToUseAsTitle = - useAsTitleFieldName === 'id' - ? String(versionTo.parent) - : versionTo.version?.[useAsTitleFieldName] return ( ) } diff --git a/test/versions/collections/Drafts.ts b/test/versions/collections/Drafts.ts index e2879dbb6c3..474c9746124 100644 --- a/test/versions/collections/Drafts.ts +++ b/test/versions/collections/Drafts.ts @@ -55,12 +55,17 @@ const DraftPosts: CollectionConfig = { }, fields: [ { - name: 'title', - type: 'text', - label: 'Title', - localized: true, - required: true, - unique: true, + type: 'group', + fields: [ + { + name: 'title', + type: 'text', + label: 'Title', + localized: true, + required: true, + unique: true, + }, + ], }, { name: 'description', diff --git a/test/versions/e2e.spec.ts b/test/versions/e2e.spec.ts index b8a0afe03d8..1e3ede16a0a 100644 --- a/test/versions/e2e.spec.ts +++ b/test/versions/e2e.spec.ts @@ -1482,6 +1482,7 @@ describe('Versions', () => { describe('Versions diff view', () => { let postID: string let versionID: string + let oldVersionID: string let diffID: string let versionDiffID: string @@ -1507,7 +1508,7 @@ describe('Versions', () => { draft: true, depth: 0, data: { - title: 'draft post', + title: 'current draft post title', description: 'draft description', blocksField: [ { @@ -1521,7 +1522,7 @@ describe('Versions', () => { const versions = await payload.findVersions({ collection: draftCollectionSlug, - limit: 1, + limit: 2, depth: 0, where: { parent: { equals: postID }, @@ -1529,6 +1530,7 @@ describe('Versions', () => { }) versionID = versions.docs[0].id + oldVersionID = versions.docs[1].id const diffDoc = ( await payload.find({ @@ -1554,7 +1556,7 @@ describe('Versions', () => { versionDiffID = versionDiff.id }) - async function navigateToDraftVersionView() { + async function navigateToDraftVersionView(versionID: string) { const versionURL = `${serverURL}/admin/collections/${draftCollectionSlug}/${postID}/versions/${versionID}` await page.goto(versionURL) await expect(page.locator('.render-field-diffs').first()).toBeVisible() @@ -1567,12 +1569,22 @@ describe('Versions', () => { } test('should render diff', async () => { - await navigateToDraftVersionView() + await navigateToDraftVersionView(versionID) expect(true).toBe(true) }) + test('should show the current version title in step nav for all versions', async () => { + await navigateToDraftVersionView(versionID) + // Document title part of the step nav should be the current version title + await expect(page.locator('.step-nav')).toContainText('current draft post title') + + await navigateToDraftVersionView(oldVersionID) + // Document title part of the step nav should still be the current version title + await expect(page.locator('.step-nav')).toContainText('current draft post title') + }) + test('should render diff for nested fields', async () => { - await navigateToDraftVersionView() + await navigateToDraftVersionView(versionID) const blocksDiffLabel = page.getByText('Blocks Field', { exact: true }) await expect(blocksDiffLabel).toBeVisible() @@ -1591,7 +1603,7 @@ describe('Versions', () => { }) test('should render diff collapser for nested fields', async () => { - await navigateToDraftVersionView() + await navigateToDraftVersionView(versionID) const blocksDiffLabel = page.getByText('Blocks Field', { exact: true }) await expect(blocksDiffLabel).toBeVisible() diff --git a/test/versions/int.spec.ts b/test/versions/int.spec.ts index 561cef0f7f0..f48ccca376e 100644 --- a/test/versions/int.spec.ts +++ b/test/versions/int.spec.ts @@ -752,7 +752,7 @@ describe('Versions', () => { expect(updateManyResult.docs).toHaveLength(0) expect(updateManyResult.errors).toStrictEqual([ - { id: doc.id, message: 'The following field is invalid: Title' }, + { id: doc.id, message: 'The following field is invalid: Group > Title' }, ]) }) From e0ba0e5a4cec0c5d1ee4f59dd626259f35e770ea Mon Sep 17 00:00:00 2001 From: Aapo Laakkio Date: Fri, 5 Sep 2025 21:35:01 +0300 Subject: [PATCH 2/3] fix: diff relationship component doesn't flatten fields --- .../fields/Relationship/generateLabelFromValue.ts | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/packages/next/src/views/Version/RenderFieldsToDiff/fields/Relationship/generateLabelFromValue.ts b/packages/next/src/views/Version/RenderFieldsToDiff/fields/Relationship/generateLabelFromValue.ts index b638ee424be..ec47b7db947 100644 --- a/packages/next/src/views/Version/RenderFieldsToDiff/fields/Relationship/generateLabelFromValue.ts +++ b/packages/next/src/views/Version/RenderFieldsToDiff/fields/Relationship/generateLabelFromValue.ts @@ -1,6 +1,11 @@ import type { PayloadRequest, RelationshipField, TypeWithID } from 'payload' -import { fieldAffectsData, fieldIsPresentationalOnly, fieldShouldBeLocalized } from 'payload/shared' +import { + fieldAffectsData, + fieldIsPresentationalOnly, + fieldShouldBeLocalized, + flattenTopLevelFields, +} from 'payload/shared' import type { PopulatedRelationshipValue } from './index.js' @@ -32,7 +37,12 @@ export const generateLabelFromValue = ({ const relatedCollection = req.payload.collections[relationTo].config const useAsTitle = relatedCollection?.admin?.useAsTitle - const useAsTitleField = relatedCollection.fields.find( + + const flattenedRelatedCollectionFields = flattenTopLevelFields(relatedCollection.fields, { + moveSubFieldsToTop: true, + }) + + const useAsTitleField = flattenedRelatedCollectionFields.find( (f) => fieldAffectsData(f) && !fieldIsPresentationalOnly(f) && f.name === useAsTitle, ) let titleFieldIsLocalized = false From 2cc2dc1802541eaf6fafe18a46167e429c552f76 Mon Sep 17 00:00:00 2001 From: Aapo Laakkio Date: Fri, 5 Sep 2025 22:00:37 +0300 Subject: [PATCH 3/3] fix: fix locators --- test/versions/e2e.spec.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/versions/e2e.spec.ts b/test/versions/e2e.spec.ts index 1e3ede16a0a..7761cfb5847 100644 --- a/test/versions/e2e.spec.ts +++ b/test/versions/e2e.spec.ts @@ -236,7 +236,7 @@ describe('Versions', () => { const row2 = page.locator('tbody .row-2') const versionID = await row2.locator('.cell-id').textContent() await page.goto(`${savedDocURL}/versions/${versionID}`) - await expect(page.locator('.render-field-diffs')).toBeVisible() + await expect(page.locator('.render-field-diffs').first()).toBeVisible() await page.locator('.restore-version__restore-as-draft-button').click() await page.locator('button:has-text("Confirm")').click() await page.waitForURL(savedDocURL) @@ -259,7 +259,7 @@ describe('Versions', () => { const row2 = page.locator('tbody .row-2') const versionID = await row2.locator('.cell-id').textContent() await page.goto(`${savedDocURL}/versions/${versionID}`) - await expect(page.locator('.render-field-diffs')).toBeVisible() + await expect(page.locator('.render-field-diffs').first()).toBeVisible() await page.locator('.restore-version .popup__trigger-wrap button').click() await page.getByRole('button', { name: 'Restore as draft' }).click() await page.locator('button:has-text("Confirm")').click()