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:
@@ -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'
|
||||||
|
|||||||
@@ -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.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -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 && (
|
||||||
|
|||||||
@@ -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}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -125,7 +125,9 @@ const DraftPosts: CollectionConfig = {
|
|||||||
],
|
],
|
||||||
versions: {
|
versions: {
|
||||||
drafts: {
|
drafts: {
|
||||||
schedulePublish: true,
|
schedulePublish: {
|
||||||
|
timeFormat: 'HH:mm',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
maxPerDoc: 0,
|
maxPerDoc: 0,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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', () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user