Skip to content

Commit 1ddeb60

Browse files
authored
fix(ui): unpublish button missing for globals and version count incorrectly incrementing after unpublish (#16203)
# Overview Fixes the unpublish button not appearing for globals, and fixes the version count tab incorrectly incrementing after unpublishing. ## Key Changes - Extended `showDotMenu` in `DocumentControls` to render for globals with drafts enabled - Fixed `incrementVersionCount` being called unconditionally on unpublish — it should only fire when unpublishing a specific locale, not all locales ## Design Decisions Collection-only dot menu actions (create, duplicate, delete) self-guard via permissions that are always false for globals, so no extra conditions were needed. #### Before <img width="1221" height="402" alt="Screenshot 2026-04-07 at 3 44 28 PM" src="https://github.com/user-attachments/assets/e60984f9-6abd-4110-9374-d5d22027a608" /> #### After <img width="1217" height="434" alt="Screenshot 2026-04-07 at 3 40 53 PM" src="https://github.com/user-attachments/assets/94e38404-0d70-47dd-b28a-f8e76a52d275" />
1 parent ac085cd commit 1ddeb60

8 files changed

Lines changed: 159 additions & 9 deletions

File tree

packages/ui/src/elements/DocumentControls/index.tsx

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,6 @@ export const DocumentControls: React.FC<{
133133
admin: { dateFormat },
134134
localization,
135135
routes: { admin: adminRoute },
136-
serverURL,
137136
} = config
138137

139138
// Settings these in state to avoid hydration issues if there is a mismatch between the server and client
@@ -160,15 +159,17 @@ export const DocumentControls: React.FC<{
160159
? docHasTrashPermission || docHasDeletePermission
161160
: collectionDeletePermission
162161

163-
const showDotMenu = Boolean(
164-
collectionConfig && id && !disableActions && (hasCreatePermission || hasDeletePermission),
165-
)
166-
167162
const unsavedDraftWithValidations =
168163
!id && collectionConfig?.versions?.drafts && collectionConfig.versions?.drafts.validate
169164

170165
const globalHasDraftsEnabled = hasDraftsEnabled(globalConfig)
171166
const collectionHasDraftsEnabled = hasDraftsEnabled(collectionConfig)
167+
168+
const showDotMenu = Boolean(
169+
!disableActions &&
170+
((collectionConfig && id && (hasCreatePermission || hasDeletePermission)) ||
171+
(globalConfig && (globalHasDraftsEnabled || localization))),
172+
)
172173
const collectionAutosaveEnabled = hasAutosaveEnabled(collectionConfig)
173174
const globalAutosaveEnabled = hasAutosaveEnabled(globalConfig)
174175
const autosaveEnabled = collectionAutosaveEnabled || globalAutosaveEnabled

packages/ui/src/elements/UnpublishButton/index.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,9 @@ export function UnpublishButton({
110110
})
111111

112112
toast.success(t('version:unpublishedSuccessfully'))
113-
incrementVersionCount()
113+
if (!unpublishAll) {
114+
incrementVersionCount()
115+
}
114116
setUnpublishedVersionCount(1)
115117
setMostRecentVersionIsAutosaved(false)
116118
setHasPublishedDoc(false)

test/localization/e2e.spec.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -611,6 +611,19 @@ describe('Localization', () => {
611611
'English block text content',
612612
)
613613
})
614+
615+
test('should show copy to locale in dot menu for globals without drafts', async () => {
616+
const globalURL = new AdminUrlUtil(serverURL, 'global-text')
617+
await page.goto(globalURL.global('global-text'))
618+
619+
await openDocControls(page)
620+
621+
await expect(page.locator('#copy-locale-data__button')).toBeVisible()
622+
await expect(page.locator('#action-create')).not.toBeAttached()
623+
await expect(page.locator('#action-delete')).not.toBeAttached()
624+
await expect(page.locator('#action-duplicate')).not.toBeAttached()
625+
await expect(page.locator('#action-unpublish')).not.toBeAttached()
626+
})
614627
})
615628

616629
describe('locale change', () => {

test/versions/config.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import DraftUnlimitedGlobal from './globals/DraftUnlimited.js'
3131
import DraftWithMaxGlobal from './globals/DraftWithMax.js'
3232
import LocalizedGlobal from './globals/LocalizedGlobal.js'
3333
import { MaxVersions } from './globals/MaxVersions.js'
34+
import SimpleDraftGlobal from './globals/SimpleDraft.js'
3435
import { seed } from './seed.js'
3536
import { BASE_PATH } from './shared.js'
3637
process.env.NEXT_BASE_PATH = BASE_PATH
@@ -73,6 +74,7 @@ export default buildConfigWithDefaults({
7374
LocalizedGlobal,
7475
MaxVersions,
7576
DraftUnlimitedGlobal,
77+
SimpleDraftGlobal,
7678
],
7779
indexSortableFields: true,
7880
localization: {

test/versions/e2e.spec.ts

Lines changed: 82 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ import {
7979
localizedCollectionSlug,
8080
localizedGlobalSlug,
8181
postCollectionSlug,
82+
simpleDraftGlobalSlug,
8283
textCollectionSlug,
8384
versionCollectionSlug,
8485
} from './slugs.js'
@@ -858,6 +859,35 @@ describe('Versions', () => {
858859
})
859860
})
860861

862+
test('collections — should not increment version count when unpublishing', async () => {
863+
const publishedDoc = await payload.create({
864+
collection: draftCollectionSlug,
865+
data: {
866+
_status: 'published',
867+
title: 'unpublish version count test',
868+
description: 'description',
869+
},
870+
})
871+
872+
await page.goto(url.edit(publishedDoc.id))
873+
874+
const versionsCountSelector = `.doc-tabs__tabs .pill-version-count`
875+
await page.locator(versionsCountSelector).waitFor({ state: 'visible' })
876+
const countBefore = await page.locator(versionsCountSelector).textContent()
877+
878+
await openDocControls(page)
879+
await page.locator('#action-unpublish').click()
880+
await page.locator('[id^="confirm-un-publish-"] #confirm-action').click()
881+
await expect(page.locator('.payload-toast-item')).toBeVisible()
882+
883+
await expect.poll(() => page.locator(versionsCountSelector).textContent()).toBe(countBefore)
884+
885+
await payload.delete({
886+
collection: draftCollectionSlug,
887+
id: publishedDoc.id,
888+
})
889+
})
890+
861891
test('should show documents title in relationship even if draft document', async () => {
862892
await payload.create({
863893
collection: autosaveCollectionSlug,
@@ -1219,15 +1249,65 @@ describe('Versions', () => {
12191249
await expect(page.locator('#action-save')).not.toBeAttached()
12201250
})
12211251

1252+
test('globals — should show unpublish button after publishing', async () => {
1253+
await payload.updateGlobal({
1254+
slug: simpleDraftGlobalSlug,
1255+
data: { title: 'published global', _status: 'published' },
1256+
})
1257+
1258+
const globalURL = new AdminUrlUtil(serverURL, simpleDraftGlobalSlug)
1259+
await page.goto(globalURL.global(simpleDraftGlobalSlug))
1260+
1261+
await openDocControls(page)
1262+
await expect(page.locator('#action-unpublish')).toBeVisible()
1263+
})
1264+
1265+
test('globals — dot menu should only contain unpublish and copy to locale options', async () => {
1266+
await payload.updateGlobal({
1267+
slug: simpleDraftGlobalSlug,
1268+
data: { title: 'published global', _status: 'published' },
1269+
})
1270+
1271+
const globalURL = new AdminUrlUtil(serverURL, simpleDraftGlobalSlug)
1272+
await page.goto(globalURL.global(simpleDraftGlobalSlug))
1273+
1274+
await openDocControls(page)
1275+
1276+
await expect(page.locator('#action-unpublish')).toBeVisible()
1277+
await expect(page.locator('#copy-locale-data__button')).toBeVisible()
1278+
await expect(page.locator('#action-create')).not.toBeAttached()
1279+
await expect(page.locator('#action-delete')).not.toBeAttached()
1280+
await expect(page.locator('#action-duplicate')).not.toBeAttached()
1281+
})
1282+
1283+
test('globals — should not increment version count when unpublishing', async () => {
1284+
await payload.updateGlobal({
1285+
slug: simpleDraftGlobalSlug,
1286+
data: { title: 'unpublish version count test', _status: 'published' },
1287+
})
1288+
1289+
const globalURL = new AdminUrlUtil(serverURL, simpleDraftGlobalSlug)
1290+
await page.goto(globalURL.global(simpleDraftGlobalSlug))
1291+
1292+
const versionsCountPill = page.locator(`.doc-tabs__tabs .pill-version-count`)
1293+
await versionsCountPill.waitFor({ state: 'visible' })
1294+
const countBefore = await versionsCountPill.textContent()
1295+
1296+
await openDocControls(page)
1297+
await page.locator('#action-unpublish').click()
1298+
await page.locator('[id^="confirm-un-publish-"] #confirm-action').click()
1299+
await expect(page.locator('.payload-toast-item')).toBeVisible()
1300+
1301+
await expect.poll(() => versionsCountPill.textContent()).toBe(countBefore)
1302+
})
1303+
12221304
test('globals — should hide unpublish button when access control prevents update', async () => {
1223-
// Then publish it with override access to create a published version
12241305
await payload.updateGlobal({
12251306
slug: disablePublishGlobalSlug,
12261307
data: {
12271308
_status: 'published',
12281309
title: 'published global',
12291310
},
1230-
overrideAccess: true,
12311311
})
12321312

12331313
const url = new AdminUrlUtil(serverURL, disablePublishGlobalSlug)
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import type { GlobalConfig } from 'payload'
2+
3+
import { simpleDraftGlobalSlug } from '../slugs.js'
4+
5+
const SimpleDraftGlobal: GlobalConfig = {
6+
slug: simpleDraftGlobalSlug,
7+
label: 'Simple Draft Global',
8+
versions: {
9+
drafts: true,
10+
},
11+
fields: [
12+
{
13+
name: 'title',
14+
type: 'text',
15+
required: true,
16+
},
17+
],
18+
}
19+
20+
export default SimpleDraftGlobal

test/versions/payload-types.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,7 @@ export interface Config {
136136
'localized-global': LocalizedGlobal;
137137
'max-versions': MaxVersion;
138138
'draft-unlimited-global': DraftUnlimitedGlobal;
139+
'simple-draft-global': SimpleDraftGlobal;
139140
};
140141
globalsSelect: {
141142
'autosave-global': AutosaveGlobalSelect<false> | AutosaveGlobalSelect<true>;
@@ -146,6 +147,7 @@ export interface Config {
146147
'localized-global': LocalizedGlobalSelect<false> | LocalizedGlobalSelect<true>;
147148
'max-versions': MaxVersionsSelect<false> | MaxVersionsSelect<true>;
148149
'draft-unlimited-global': DraftUnlimitedGlobalSelect<false> | DraftUnlimitedGlobalSelect<true>;
150+
'simple-draft-global': SimpleDraftGlobalSelect<false> | SimpleDraftGlobalSelect<true>;
149151
};
150152
locale: 'en' | 'es' | 'de';
151153
widgets: {
@@ -1502,6 +1504,17 @@ export interface DraftUnlimitedGlobal {
15021504
updatedAt?: string | null;
15031505
createdAt?: string | null;
15041506
}
1507+
/**
1508+
* This interface was referenced by `Config`'s JSON-Schema
1509+
* via the `definition` "simple-draft-global".
1510+
*/
1511+
export interface SimpleDraftGlobal {
1512+
id: string;
1513+
title: string;
1514+
_status?: ('draft' | 'published') | null;
1515+
updatedAt?: string | null;
1516+
createdAt?: string | null;
1517+
}
15051518
/**
15061519
* This interface was referenced by `Config`'s JSON-Schema
15071520
* via the `definition` "autosave-global_select".
@@ -1591,6 +1604,17 @@ export interface DraftUnlimitedGlobalSelect<T extends boolean = true> {
15911604
createdAt?: T;
15921605
globalType?: T;
15931606
}
1607+
/**
1608+
* This interface was referenced by `Config`'s JSON-Schema
1609+
* via the `definition` "simple-draft-global_select".
1610+
*/
1611+
export interface SimpleDraftGlobalSelect<T extends boolean = true> {
1612+
title?: T;
1613+
_status?: T;
1614+
updatedAt?: T;
1615+
createdAt?: T;
1616+
globalType?: T;
1617+
}
15941618
/**
15951619
* This interface was referenced by `Config`'s JSON-Schema
15961620
* via the `definition` "collections_widget".

test/versions/slugs.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,11 +50,19 @@ export const autosaveWithDraftButtonGlobal = 'autosave-with-draft-button-global'
5050

5151
export const draftGlobalSlug = 'draft-global'
5252

53+
export const simpleDraftGlobalSlug = 'simple-draft-global'
54+
5355
export const draftUnlimitedGlobalSlug = 'draft-unlimited-global'
5456

5557
export const draftWithMaxGlobalSlug = 'draft-with-max-global'
5658

57-
export const globalSlugs = [autoSaveGlobalSlug, draftGlobalSlug]
59+
export const globalSlugs = [
60+
autoSaveGlobalSlug,
61+
draftGlobalSlug,
62+
simpleDraftGlobalSlug,
63+
draftUnlimitedGlobalSlug,
64+
draftWithMaxGlobalSlug,
65+
]
5866

5967
export const localizedCollectionSlug = 'localized-posts'
6068

0 commit comments

Comments
 (0)