feat: add support for time format config on scheduled publish (#12073)

This PR adds a new `SchedulePublish` config type on our schedulePublish
configuration in versions from being just boolean.

Two new options are supported:
- `timeFormat` which controls the formatting of the time slots, allowing
users to change from a 12-hour clock to a 24-hour clock (default to 12
hour)
- `timeIntervals` which controls the generated time slots (default 5)

Example configuration:

```
versions: {
  drafts: {
    schedulePublish: {
      timeFormat: 'HH:mm',
      timeIntervals: 5,
    },
  },
},
```
This commit is contained in:
Paul
2025-04-10 18:22:21 +01:00
committed by GitHub
parent 4d7c1d45fa
commit eab9770315
6 changed files with 69 additions and 11 deletions

View File

@@ -1508,5 +1508,5 @@ export { getLatestCollectionVersion } from './versions/getLatestCollectionVersio
export { getLatestGlobalVersion } from './versions/getLatestGlobalVersion.js' export { getLatestGlobalVersion } from './versions/getLatestGlobalVersion.js'
export { saveVersion } from './versions/saveVersion.js' export { saveVersion } from './versions/saveVersion.js'
export type { SchedulePublishTaskInput } from './versions/schedule/types.js' export type { SchedulePublishTaskInput } from './versions/schedule/types.js'
export type { TypeWithVersion } from './versions/types.js' export type { SchedulePublish, TypeWithVersion } from './versions/types.js'
export { deepMergeSimple } from '@payloadcms/translations/utilities' export { deepMergeSimple } from '@payloadcms/translations/utilities'

View File

@@ -8,6 +8,23 @@ export type Autosave = {
interval?: number interval?: number
} }
export type SchedulePublish = {
/**
* Define a date format to use for the time picker.
*
* @example 'hh:mm' will give a 24 hour clock
*
* @default 'h:mm aa' which is a 12 hour clock
*/
timeFormat?: string
/**
* Intervals for the time picker.
*
* @default 5
*/
timeIntervals?: number
}
export type IncomingDrafts = { export type IncomingDrafts = {
/** /**
* Enable autosave to automatically save progress while documents are edited. * Enable autosave to automatically save progress while documents are edited.
@@ -17,7 +34,7 @@ export type IncomingDrafts = {
/** /**
* Allow for editors to schedule publish / unpublish events in the future. * Allow for editors to schedule publish / unpublish events in the future.
*/ */
schedulePublish?: boolean schedulePublish?: boolean | SchedulePublish
/** /**
* Set validate to true to validate draft documents when saved. * Set validate to true to validate draft documents when saved.
* *
@@ -35,7 +52,7 @@ export type SanitizedDrafts = {
/** /**
* Allow for editors to schedule publish / unpublish events in the future. * Allow for editors to schedule publish / unpublish events in the future.
*/ */
schedulePublish: boolean schedulePublish: boolean | SchedulePublish
/** /**
* Set validate to true to validate draft documents when saved. * Set validate to true to validate draft documents when saved.
* *

View File

@@ -1,7 +1,7 @@
/* eslint-disable no-console */ /* eslint-disable no-console */
'use client' 'use client'
import type { Column, Where } from 'payload' import type { Column, SchedulePublish, Where } from 'payload'
import { TZDateMini as TZDate } from '@date-fns/tz/date/mini' import { TZDateMini as TZDate } from '@date-fns/tz/date/mini'
import { useModal } from '@faceless-ui/modal' import { useModal } from '@faceless-ui/modal'
@@ -28,8 +28,8 @@ import { Drawer } from '../../Drawer/index.js'
import { Gutter } from '../../Gutter/index.js' import { Gutter } from '../../Gutter/index.js'
import { ReactSelect } from '../../ReactSelect/index.js' import { ReactSelect } from '../../ReactSelect/index.js'
import { ShimmerEffect } from '../../ShimmerEffect/index.js' import { ShimmerEffect } from '../../ShimmerEffect/index.js'
import { Table } from '../../Table/index.js'
import './index.scss' import './index.scss'
import { Table } from '../../Table/index.js'
import { TimezonePicker } from '../../TimezonePicker/index.js' import { TimezonePicker } from '../../TimezonePicker/index.js'
import { buildUpcomingColumns } from './buildUpcomingColumns.js' import { buildUpcomingColumns } from './buildUpcomingColumns.js'
@@ -37,6 +37,7 @@ const baseClass = 'schedule-publish'
type Props = { type Props = {
defaultType?: PublishType defaultType?: PublishType
schedulePublishConfig?: SchedulePublish
slug: string slug: string
} }
@@ -45,7 +46,7 @@ const defaultLocaleOption = {
value: 'all', value: 'all',
} }
export const ScheduleDrawer: React.FC<Props> = ({ slug, defaultType }) => { export const ScheduleDrawer: React.FC<Props> = ({ slug, defaultType, schedulePublishConfig }) => {
const { toggleModal } = useModal() const { toggleModal } = useModal()
const { const {
config: { config: {
@@ -331,7 +332,8 @@ export const ScheduleDrawer: React.FC<Props> = ({ slug, defaultType }) => {
onChange={(e) => onChangeDate(e)} onChange={(e) => onChangeDate(e)}
pickerAppearance="dayAndTime" pickerAppearance="dayAndTime"
readOnly={processing} readOnly={processing}
timeIntervals={5} timeFormat={schedulePublishConfig?.timeFormat}
timeIntervals={schedulePublishConfig?.timeIntervals ?? 5}
value={displayedValue} value={displayedValue}
/> />
{supportedTimezones.length > 0 && ( {supportedTimezones.length > 0 && (

View File

@@ -63,14 +63,16 @@ export function PublishButton({ label: labelProp }: PublishButtonClientProps) {
const hasNewerVersions = unpublishedVersionCount > 0 const hasNewerVersions = unpublishedVersionCount > 0
const schedulePublish =
typeof entityConfig?.versions?.drafts === 'object' &&
entityConfig?.versions?.drafts.schedulePublish
const canPublish = const canPublish =
hasPublishPermission && hasPublishPermission &&
(modified || hasNewerVersions || !hasPublishedDoc) && (modified || hasNewerVersions || !hasPublishedDoc) &&
uploadStatus !== 'uploading' uploadStatus !== 'uploading'
const scheduledPublishEnabled = const scheduledPublishEnabled = Boolean(schedulePublish)
typeof entityConfig?.versions?.drafts === 'object' &&
entityConfig?.versions?.drafts.schedulePublish
const canSchedulePublish = Boolean( const canSchedulePublish = Boolean(
scheduledPublishEnabled && scheduledPublishEnabled &&
@@ -239,6 +241,7 @@ export function PublishButton({ label: labelProp }: PublishButtonClientProps) {
{canSchedulePublish && isModalOpen(drawerSlug) && ( {canSchedulePublish && isModalOpen(drawerSlug) && (
<ScheduleDrawer <ScheduleDrawer
defaultType={!hasNewerVersions ? 'unpublish' : 'publish'} defaultType={!hasNewerVersions ? 'unpublish' : 'publish'}
schedulePublishConfig={typeof schedulePublish === 'object' && schedulePublish}
slug={drawerSlug} slug={drawerSlug}
/> />
)} )}

View File

@@ -125,7 +125,9 @@ const DraftPosts: CollectionConfig = {
], ],
versions: { versions: {
drafts: { drafts: {
schedulePublish: true, schedulePublish: {
timeFormat: 'HH:mm',
},
}, },
maxPerDoc: 0, maxPerDoc: 0,
}, },

View File

@@ -691,6 +691,40 @@ describe('Versions', () => {
page.locator('.payload-toast-item:has-text("Deleted successfully.")'), page.locator('.payload-toast-item:has-text("Deleted successfully.")'),
).toBeVisible() ).toBeVisible()
}) })
test('schedule publish config is respected', async () => {
await page.goto(url.create)
await page.locator('#field-title').fill('scheduled publish')
await page.locator('#field-description').fill('scheduled publish description')
// schedule publish should not be available before document has been saved
await page.locator('#action-save-popup').click()
await expect(page.locator('#schedule-publish')).toBeHidden()
// save draft then try to schedule publish
await saveDocAndAssert(page)
await page.locator('#action-save-popup').click()
await page.locator('#schedule-publish').click()
// drawer should open
await expect(page.locator('.schedule-publish__drawer-header')).toBeVisible()
// nothing in scheduled
await expect(page.locator('.drawer__content')).toContainText('No upcoming events scheduled.')
// set date and time
await page.locator('.date-time-picker input').click()
const listItem = page
.locator('.react-datepicker__time-list .react-datepicker__time-list-item')
.first()
// We customised it in config to not contain a 12 hour clock
await expect(async () => {
await expect(listItem).toHaveText('00:00')
}).toPass({
timeout: POLL_TOPASS_TIMEOUT,
})
})
}) })
describe('Collections - publish specific locale', () => { describe('Collections - publish specific locale', () => {