fix(ui): infinite loading states when adding blocks or array rows (#10175)
Fixes #10070. Adding new blocks or array rows can randomly get stuck within an infinite loading state. This was because the abort controllers responsible for disregarding duplicate `onChange` and `onSave` events was not properly resetting its refs across invocations. This caused subsequent event handlers to incorrectly abort themselves, leading to unresolved requests and a `null` form state. Similarly, the cleanup effects responsible for aborting these requests on component unmount were also referencing its `current` property directly off the refs, which can possible be stale if not first set as a variable outside the return function. This PR also carries over some missing `onSave` logic from the default edit view into the live preview view. In the future the logic between these two views should be standardized, as they're nearly identical but often become out of sync. This can likely be done through the use of reusable hooks, such as `useOnSave`, `useOnChange`, etc. Same with the document locking functionality which is complex and deeply integrated into each of these views.
This commit is contained in:
@@ -20,7 +20,7 @@ import {
|
||||
useServerFunctions,
|
||||
useTranslation,
|
||||
} from '@payloadcms/ui'
|
||||
import { abortAndIgnore } from '@payloadcms/ui/shared'
|
||||
import { abortAndIgnore, handleAbortRef } from '@payloadcms/ui/shared'
|
||||
import React, { useEffect } from 'react'
|
||||
|
||||
export const CreateFirstUserClient: React.FC<{
|
||||
@@ -43,16 +43,13 @@ export const CreateFirstUserClient: React.FC<{
|
||||
const { t } = useTranslation()
|
||||
const { setUser } = useAuth()
|
||||
|
||||
const formStateAbortControllerRef = React.useRef<AbortController>(null)
|
||||
const abortOnChangeRef = React.useRef<AbortController>(null)
|
||||
|
||||
const collectionConfig = getEntityConfig({ collectionSlug: userSlug }) as ClientCollectionConfig
|
||||
|
||||
const onChange: FormProps['onChange'][0] = React.useCallback(
|
||||
async ({ formState: prevFormState }) => {
|
||||
abortAndIgnore(formStateAbortControllerRef.current)
|
||||
|
||||
const controller = new AbortController()
|
||||
formStateAbortControllerRef.current = controller
|
||||
const controller = handleAbortRef(abortOnChangeRef)
|
||||
|
||||
const response = await getFormState({
|
||||
collectionSlug: userSlug,
|
||||
@@ -64,6 +61,8 @@ export const CreateFirstUserClient: React.FC<{
|
||||
signal: controller.signal,
|
||||
})
|
||||
|
||||
abortOnChangeRef.current = null
|
||||
|
||||
if (response && response.state) {
|
||||
return response.state
|
||||
}
|
||||
@@ -76,8 +75,10 @@ export const CreateFirstUserClient: React.FC<{
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const abortOnChange = abortOnChangeRef.current
|
||||
|
||||
return () => {
|
||||
abortAndIgnore(formStateAbortControllerRef.current)
|
||||
abortAndIgnore(abortOnChange)
|
||||
}
|
||||
}, [])
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import type {
|
||||
ClientGlobalConfig,
|
||||
ClientUser,
|
||||
Data,
|
||||
FormState,
|
||||
LivePreviewConfig,
|
||||
} from 'payload'
|
||||
|
||||
@@ -25,21 +26,25 @@ import {
|
||||
useDocumentDrawerContext,
|
||||
useDocumentEvents,
|
||||
useDocumentInfo,
|
||||
useEditDepth,
|
||||
useServerFunctions,
|
||||
useTranslation,
|
||||
useUploadEdits,
|
||||
} from '@payloadcms/ui'
|
||||
import {
|
||||
abortAndIgnore,
|
||||
formatAdminURL,
|
||||
handleAbortRef,
|
||||
handleBackToDashboard,
|
||||
handleGoBack,
|
||||
handleTakeOver,
|
||||
} from '@payloadcms/ui/shared'
|
||||
import { useRouter } from 'next/navigation.js'
|
||||
import { useRouter, useSearchParams } from 'next/navigation.js'
|
||||
import React, { Fragment, useCallback, useEffect, useRef, useState } from 'react'
|
||||
|
||||
import { useLivePreviewContext } from './Context/context.js'
|
||||
import { LivePreviewProvider } from './Context/index.js'
|
||||
import './index.scss'
|
||||
import { LivePreviewProvider } from './Context/index.js'
|
||||
import { LivePreview } from './Preview/index.js'
|
||||
import { usePopupWindow } from './usePopupWindow.js'
|
||||
|
||||
@@ -75,10 +80,12 @@ const PreviewView: React.FC<Props> = ({
|
||||
disableLeaveWithoutSaving,
|
||||
docPermissions,
|
||||
documentIsLocked,
|
||||
getDocPermissions,
|
||||
getDocPreferences,
|
||||
globalSlug,
|
||||
hasPublishPermission,
|
||||
hasSavePermission,
|
||||
incrementVersionCount,
|
||||
initialData,
|
||||
initialState,
|
||||
isEditing,
|
||||
@@ -88,11 +95,10 @@ const PreviewView: React.FC<Props> = ({
|
||||
setDocumentIsLocked,
|
||||
unlockDocument,
|
||||
updateDocumentEditor,
|
||||
updateSavedDocumentData,
|
||||
} = useDocumentInfo()
|
||||
|
||||
const { getFormState } = useServerFunctions()
|
||||
|
||||
const { onSave: onSaveFromProps } = useDocumentDrawerContext()
|
||||
const { onSave: onSaveFromContext } = useDocumentDrawerContext()
|
||||
|
||||
const operation = id ? 'update' : 'create'
|
||||
|
||||
@@ -103,13 +109,21 @@ const PreviewView: React.FC<Props> = ({
|
||||
},
|
||||
} = useConfig()
|
||||
const router = useRouter()
|
||||
const params = useSearchParams()
|
||||
const locale = params.get('locale')
|
||||
const { t } = useTranslation()
|
||||
const { previewWindowType } = useLivePreviewContext()
|
||||
const { refreshCookieAsync, user } = useAuth()
|
||||
const { reportUpdate } = useDocumentEvents()
|
||||
const { resetUploadEdits } = useUploadEdits()
|
||||
const { getFormState } = useServerFunctions()
|
||||
|
||||
const docConfig = collectionConfig || globalConfig
|
||||
|
||||
const entitySlug = collectionConfig?.slug || globalConfig?.slug
|
||||
|
||||
const depth = useEditDepth()
|
||||
|
||||
const lockDocumentsProp = docConfig?.lockDocuments !== undefined ? docConfig?.lockDocuments : true
|
||||
const isLockingEnabled = lockDocumentsProp !== false
|
||||
|
||||
@@ -118,10 +132,19 @@ const PreviewView: React.FC<Props> = ({
|
||||
typeof lockDocumentsProp === 'object' ? lockDocumentsProp.duration : lockDurationDefault
|
||||
const lockDurationInMilliseconds = lockDuration * 1000
|
||||
|
||||
const autosaveEnabled = Boolean(
|
||||
(collectionConfig?.versions?.drafts && collectionConfig?.versions?.drafts?.autosave) ||
|
||||
(globalConfig?.versions?.drafts && globalConfig?.versions?.drafts?.autosave),
|
||||
)
|
||||
|
||||
const preventLeaveWithoutSaving =
|
||||
typeof disableLeaveWithoutSaving !== 'undefined' ? !disableLeaveWithoutSaving : !autosaveEnabled
|
||||
|
||||
const [isReadOnlyForIncomingUser, setIsReadOnlyForIncomingUser] = useState(false)
|
||||
const [showTakeOverModal, setShowTakeOverModal] = useState(false)
|
||||
|
||||
const formStateAbortControllerRef = useRef(new AbortController())
|
||||
const abortOnChangeRef = useRef<AbortController>(null)
|
||||
const abortOnSaveRef = useRef<AbortController>(null)
|
||||
|
||||
const [editSessionStartTime, setEditSessionStartTime] = useState(Date.now())
|
||||
|
||||
@@ -140,10 +163,12 @@ const PreviewView: React.FC<Props> = ({
|
||||
})
|
||||
|
||||
const onSave = useCallback(
|
||||
(json) => {
|
||||
async (json): Promise<FormState> => {
|
||||
const controller = handleAbortRef(abortOnSaveRef)
|
||||
|
||||
reportUpdate({
|
||||
id,
|
||||
entitySlug: collectionSlug,
|
||||
entitySlug,
|
||||
updatedAt: json?.result?.updatedAt || new Date().toISOString(),
|
||||
})
|
||||
|
||||
@@ -153,38 +178,91 @@ const PreviewView: React.FC<Props> = ({
|
||||
void refreshCookieAsync()
|
||||
}
|
||||
|
||||
// Unlock the document after save
|
||||
if ((id || globalSlug) && isLockingEnabled) {
|
||||
setDocumentIsLocked(false)
|
||||
incrementVersionCount()
|
||||
|
||||
if (typeof updateSavedDocumentData === 'function') {
|
||||
void updateSavedDocumentData(json?.doc || {})
|
||||
}
|
||||
|
||||
if (typeof onSaveFromProps === 'function') {
|
||||
void onSaveFromProps({
|
||||
if (typeof onSaveFromContext === 'function') {
|
||||
void onSaveFromContext({
|
||||
...json,
|
||||
operation: id ? 'update' : 'create',
|
||||
})
|
||||
}
|
||||
|
||||
if (!isEditing && depth < 2) {
|
||||
// Redirect to the same locale if it's been set
|
||||
const redirectRoute = formatAdminURL({
|
||||
adminRoute,
|
||||
path: `/collections/${collectionSlug}/${json?.doc?.id}${locale ? `?locale=${locale}` : ''}`,
|
||||
})
|
||||
router.push(redirectRoute)
|
||||
} else {
|
||||
resetUploadEdits()
|
||||
}
|
||||
|
||||
await getDocPermissions(json)
|
||||
|
||||
if ((id || globalSlug) && !autosaveEnabled) {
|
||||
const docPreferences = await getDocPreferences()
|
||||
|
||||
const { state } = await getFormState({
|
||||
id,
|
||||
collectionSlug,
|
||||
data: json?.doc || json?.result,
|
||||
docPermissions,
|
||||
docPreferences,
|
||||
globalSlug,
|
||||
operation,
|
||||
renderAllFields: true,
|
||||
returnLockStatus: false,
|
||||
schemaPath: entitySlug,
|
||||
signal: controller.signal,
|
||||
})
|
||||
|
||||
// Unlock the document after save
|
||||
if (isLockingEnabled) {
|
||||
setDocumentIsLocked(false)
|
||||
}
|
||||
|
||||
abortOnSaveRef.current = null
|
||||
|
||||
return state
|
||||
}
|
||||
},
|
||||
[
|
||||
adminRoute,
|
||||
collectionSlug,
|
||||
depth,
|
||||
docPermissions,
|
||||
entitySlug,
|
||||
getDocPermissions,
|
||||
getDocPreferences,
|
||||
getFormState,
|
||||
globalSlug,
|
||||
id,
|
||||
incrementVersionCount,
|
||||
isEditing,
|
||||
isLockingEnabled,
|
||||
onSaveFromProps,
|
||||
locale,
|
||||
onSaveFromContext,
|
||||
operation,
|
||||
refreshCookieAsync,
|
||||
reportUpdate,
|
||||
resetUploadEdits,
|
||||
router,
|
||||
setDocumentIsLocked,
|
||||
updateSavedDocumentData,
|
||||
user,
|
||||
userSlug,
|
||||
autosaveEnabled,
|
||||
],
|
||||
)
|
||||
|
||||
const onChange: FormProps['onChange'][0] = useCallback(
|
||||
async ({ formState: prevFormState }) => {
|
||||
abortAndIgnore(formStateAbortControllerRef.current)
|
||||
|
||||
const controller = new AbortController()
|
||||
formStateAbortControllerRef.current = controller
|
||||
const controller = handleAbortRef(abortOnChangeRef)
|
||||
|
||||
const currentTime = Date.now()
|
||||
const timeSinceLastUpdate = currentTime - editSessionStartTime
|
||||
@@ -242,6 +320,8 @@ const PreviewView: React.FC<Props> = ({
|
||||
}
|
||||
}
|
||||
|
||||
abortOnChangeRef.current = null
|
||||
|
||||
return state
|
||||
},
|
||||
[
|
||||
@@ -308,8 +388,12 @@ const PreviewView: React.FC<Props> = ({
|
||||
])
|
||||
|
||||
useEffect(() => {
|
||||
const abortOnChange = abortOnChangeRef.current
|
||||
const abortOnSave = abortOnSaveRef.current
|
||||
|
||||
return () => {
|
||||
abortAndIgnore(formStateAbortControllerRef.current)
|
||||
abortAndIgnore(abortOnChange)
|
||||
abortAndIgnore(abortOnSave)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -372,12 +456,7 @@ const PreviewView: React.FC<Props> = ({
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{((collectionConfig &&
|
||||
!(collectionConfig.versions?.drafts && collectionConfig.versions?.drafts?.autosave)) ||
|
||||
(globalConfig &&
|
||||
!(globalConfig.versions?.drafts && globalConfig.versions?.drafts?.autosave))) &&
|
||||
!disableLeaveWithoutSaving &&
|
||||
!isReadOnlyForIncomingUser && <LeaveWithoutSaving />}
|
||||
{!isReadOnlyForIncomingUser && preventLeaveWithoutSaving && <LeaveWithoutSaving />}
|
||||
<SetDocumentStepNav
|
||||
collectionSlug={collectionSlug}
|
||||
globalLabel={globalConfig?.label}
|
||||
|
||||
@@ -17,7 +17,7 @@ import { useEditDepth } from '../../../providers/EditDepth/index.js'
|
||||
import { OperationProvider } from '../../../providers/Operation/index.js'
|
||||
import { useServerFunctions } from '../../../providers/ServerFunctions/index.js'
|
||||
import { useUploadEdits } from '../../../providers/UploadEdits/index.js'
|
||||
import { abortAndIgnore } from '../../../utilities/abortAndIgnore.js'
|
||||
import { abortAndIgnore, handleAbortRef } from '../../../utilities/abortAndIgnore.js'
|
||||
import { formatAdminURL } from '../../../utilities/formatAdminURL.js'
|
||||
import { useDocumentDrawerContext } from '../../DocumentDrawer/Provider.js'
|
||||
import { DocumentFields } from '../../DocumentFields/index.js'
|
||||
@@ -56,7 +56,7 @@ export function EditForm({ submitted }: EditFormProps) {
|
||||
getEntityConfig,
|
||||
} = useConfig()
|
||||
|
||||
const formStateAbortControllerRef = React.useRef<AbortController>(null)
|
||||
const abortOnChangeRef = React.useRef<AbortController>(null)
|
||||
|
||||
const collectionConfig = getEntityConfig({ collectionSlug: docSlug }) as ClientCollectionConfig
|
||||
const router = useRouter()
|
||||
@@ -111,12 +111,10 @@ export function EditForm({ submitted }: EditFormProps) {
|
||||
|
||||
const onChange: NonNullable<FormProps['onChange']>[0] = useCallback(
|
||||
async ({ formState: prevFormState }) => {
|
||||
abortAndIgnore(formStateAbortControllerRef.current)
|
||||
|
||||
const controller = new AbortController()
|
||||
formStateAbortControllerRef.current = controller
|
||||
const controller = handleAbortRef(abortOnChangeRef)
|
||||
|
||||
const docPreferences = await getDocPreferences()
|
||||
|
||||
const { state: newFormState } = await getFormState({
|
||||
collectionSlug,
|
||||
docPermissions,
|
||||
@@ -127,14 +125,18 @@ export function EditForm({ submitted }: EditFormProps) {
|
||||
signal: controller.signal,
|
||||
})
|
||||
|
||||
abortOnChangeRef.current = null
|
||||
|
||||
return newFormState
|
||||
},
|
||||
[collectionSlug, schemaPath, getDocPreferences, getFormState, docPermissions],
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
const abortOnChange = abortOnChangeRef.current
|
||||
|
||||
return () => {
|
||||
abortAndIgnore(formStateAbortControllerRef.current)
|
||||
abortAndIgnore(abortOnChange)
|
||||
}
|
||||
}, [])
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ import { LoadingOverlay } from '../../elements/Loading/index.js'
|
||||
import { useConfig } from '../../providers/Config/index.js'
|
||||
import { useServerFunctions } from '../../providers/ServerFunctions/index.js'
|
||||
import { useTranslation } from '../../providers/Translation/index.js'
|
||||
import { abortAndIgnore } from '../../utilities/abortAndIgnore.js'
|
||||
import { abortAndIgnore, handleAbortRef } from '../../utilities/abortAndIgnore.js'
|
||||
import { DocumentDrawerContextProvider } from './Provider.js'
|
||||
|
||||
export const DocumentDrawerContent: React.FC<DocumentDrawerProps> = ({
|
||||
@@ -37,7 +37,7 @@ export const DocumentDrawerContent: React.FC<DocumentDrawerProps> = ({
|
||||
collections.find((collection) => collection.slug === collectionSlug),
|
||||
)
|
||||
|
||||
const documentViewAbortControllerRef = React.useRef<AbortController>(null)
|
||||
const abortGetDocumentViewRef = React.useRef<AbortController>(null)
|
||||
|
||||
const { closeModal } = useModal()
|
||||
const { t } = useTranslation()
|
||||
@@ -50,10 +50,7 @@ export const DocumentDrawerContent: React.FC<DocumentDrawerProps> = ({
|
||||
|
||||
const getDocumentView = useCallback(
|
||||
(docID?: number | string) => {
|
||||
abortAndIgnore(documentViewAbortControllerRef.current)
|
||||
|
||||
const controller = new AbortController()
|
||||
documentViewAbortControllerRef.current = controller
|
||||
const controller = handleAbortRef(abortGetDocumentViewRef)
|
||||
|
||||
const fetchDocumentView = async () => {
|
||||
setIsLoading(true)
|
||||
@@ -81,6 +78,8 @@ export const DocumentDrawerContent: React.FC<DocumentDrawerProps> = ({
|
||||
closeModal(drawerSlug)
|
||||
// toast.error(data?.errors?.[0].message || t('error:unspecific'))
|
||||
}
|
||||
|
||||
abortGetDocumentViewRef.current = null
|
||||
}
|
||||
|
||||
void fetchDocumentView()
|
||||
@@ -154,8 +153,10 @@ export const DocumentDrawerContent: React.FC<DocumentDrawerProps> = ({
|
||||
|
||||
// Cleanup any pending requests when the component unmounts
|
||||
useEffect(() => {
|
||||
const abortGetDocumentView = abortGetDocumentViewRef.current
|
||||
|
||||
return () => {
|
||||
abortAndIgnore(documentViewAbortControllerRef.current)
|
||||
abortAndIgnore(abortGetDocumentView)
|
||||
}
|
||||
}, [])
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ import { useRouteCache } from '../../providers/RouteCache/index.js'
|
||||
import { SelectAllStatus, useSelection } from '../../providers/Selection/index.js'
|
||||
import { useServerFunctions } from '../../providers/ServerFunctions/index.js'
|
||||
import { useTranslation } from '../../providers/Translation/index.js'
|
||||
import { abortAndIgnore } from '../../utilities/abortAndIgnore.js'
|
||||
import { abortAndIgnore, handleAbortRef } from '../../utilities/abortAndIgnore.js'
|
||||
import { mergeListSearchAndWhere } from '../../utilities/mergeListSearchAndWhere.js'
|
||||
import { parseSearchParams } from '../../utilities/parseSearchParams.js'
|
||||
import './index.scss'
|
||||
@@ -156,7 +156,7 @@ export const EditMany: React.FC<EditManyProps> = (props) => {
|
||||
const router = useRouter()
|
||||
const [initialState, setInitialState] = useState<FormState>()
|
||||
const hasInitializedState = React.useRef(false)
|
||||
const formStateAbortControllerRef = React.useRef<AbortController>(null)
|
||||
const abortFormStateRef = React.useRef<AbortController>(null)
|
||||
const { clearRouteCache } = useRouteCache()
|
||||
|
||||
const collectionPermissions = permissions?.collections?.[slug]
|
||||
@@ -193,10 +193,7 @@ export const EditMany: React.FC<EditManyProps> = (props) => {
|
||||
|
||||
const onChange: FormProps['onChange'][0] = useCallback(
|
||||
async ({ formState: prevFormState }) => {
|
||||
abortAndIgnore(formStateAbortControllerRef.current)
|
||||
|
||||
const controller = new AbortController()
|
||||
formStateAbortControllerRef.current = controller
|
||||
const controller = handleAbortRef(abortFormStateRef)
|
||||
|
||||
const { state } = await getFormState({
|
||||
collectionSlug: slug,
|
||||
@@ -208,14 +205,18 @@ export const EditMany: React.FC<EditManyProps> = (props) => {
|
||||
signal: controller.signal,
|
||||
})
|
||||
|
||||
abortFormStateRef.current = null
|
||||
|
||||
return state
|
||||
},
|
||||
[getFormState, slug, collectionPermissions],
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
const abortFormState = abortFormStateRef.current
|
||||
|
||||
return () => {
|
||||
abortAndIgnore(formStateAbortControllerRef.current)
|
||||
abortAndIgnore(abortFormState)
|
||||
}
|
||||
}, [])
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ import type { Column } from '../Table/index.js'
|
||||
import { useConfig } from '../../providers/Config/index.js'
|
||||
import { usePreferences } from '../../providers/Preferences/index.js'
|
||||
import { useServerFunctions } from '../../providers/ServerFunctions/index.js'
|
||||
import { abortAndIgnore } from '../../utilities/abortAndIgnore.js'
|
||||
import { abortAndIgnore, handleAbortRef } from '../../utilities/abortAndIgnore.js'
|
||||
|
||||
export interface ITableColumns {
|
||||
columns: Column[]
|
||||
@@ -78,11 +78,12 @@ export const TableColumnsProvider: React.FC<Props> = ({
|
||||
const { getPreference } = usePreferences()
|
||||
|
||||
const [tableColumns, setTableColumns] = React.useState(columnState)
|
||||
const tableStateControllerRef = React.useRef<AbortController>(null)
|
||||
const abortTableStateRef = React.useRef<AbortController>(null)
|
||||
const abortToggleColumnRef = React.useRef<AbortController>(null)
|
||||
|
||||
const moveColumn = useCallback(
|
||||
async (args: { fromIndex: number; toIndex: number }) => {
|
||||
abortAndIgnore(tableStateControllerRef.current)
|
||||
const controller = handleAbortRef(abortTableStateRef)
|
||||
|
||||
const { fromIndex, toIndex } = args
|
||||
const withMovedColumn = [...tableColumns]
|
||||
@@ -91,9 +92,6 @@ export const TableColumnsProvider: React.FC<Props> = ({
|
||||
|
||||
setTableColumns(withMovedColumn)
|
||||
|
||||
const controller = new AbortController()
|
||||
tableStateControllerRef.current = controller
|
||||
|
||||
const result = await getTableState({
|
||||
collectionSlug,
|
||||
columns: sanitizeColumns(withMovedColumn),
|
||||
@@ -108,6 +106,8 @@ export const TableColumnsProvider: React.FC<Props> = ({
|
||||
setTableColumns(result.state)
|
||||
setTable(result.Table)
|
||||
}
|
||||
|
||||
abortTableStateRef.current = null
|
||||
},
|
||||
[
|
||||
tableColumns,
|
||||
@@ -123,7 +123,7 @@ export const TableColumnsProvider: React.FC<Props> = ({
|
||||
|
||||
const toggleColumn = useCallback(
|
||||
async (column: string) => {
|
||||
abortAndIgnore(tableStateControllerRef.current)
|
||||
const controller = handleAbortRef(abortToggleColumnRef)
|
||||
|
||||
const { newColumnState, toggledColumns } = tableColumns.reduce<{
|
||||
newColumnState: Column[]
|
||||
@@ -155,9 +155,6 @@ export const TableColumnsProvider: React.FC<Props> = ({
|
||||
|
||||
setTableColumns(newColumnState)
|
||||
|
||||
const controller = new AbortController()
|
||||
tableStateControllerRef.current = controller
|
||||
|
||||
const result = await getTableState({
|
||||
collectionSlug,
|
||||
columns: toggledColumns,
|
||||
@@ -172,6 +169,8 @@ export const TableColumnsProvider: React.FC<Props> = ({
|
||||
setTableColumns(result.state)
|
||||
setTable(result.Table)
|
||||
}
|
||||
|
||||
abortToggleColumnRef.current = null
|
||||
},
|
||||
[
|
||||
tableColumns,
|
||||
@@ -281,8 +280,10 @@ export const TableColumnsProvider: React.FC<Props> = ({
|
||||
}, [columnState])
|
||||
|
||||
useEffect(() => {
|
||||
const abortTableState = abortTableStateRef.current
|
||||
|
||||
return () => {
|
||||
abortAndIgnore(tableStateControllerRef.current)
|
||||
abortAndIgnore(abortTableState)
|
||||
}
|
||||
}, [])
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ export { mergeFieldStyles } from '../../fields/mergeFieldStyles.js'
|
||||
export { reduceToSerializableFields } from '../../forms/Form/reduceToSerializableFields.js'
|
||||
export { PayloadIcon } from '../../graphics/Icon/index.js'
|
||||
export { PayloadLogo } from '../../graphics/Logo/index.js'
|
||||
export { abortAndIgnore } from '../../utilities/abortAndIgnore.js'
|
||||
export { abortAndIgnore, handleAbortRef } from '../../utilities/abortAndIgnore.js'
|
||||
export { requests } from '../../utilities/api.js'
|
||||
export { findLocaleFromCode } from '../../utilities/findLocaleFromCode.js'
|
||||
export { formatAdminURL } from '../../utilities/formatAdminURL.js'
|
||||
|
||||
@@ -130,7 +130,9 @@ export const ArrayRow: React.FC<ArrayRowProps> = ({
|
||||
}
|
||||
header={
|
||||
<div className={`${baseClass}__row-header`}>
|
||||
{isLoading ? null : (
|
||||
{isLoading ? (
|
||||
<ShimmerEffect height="1rem" width="8rem" />
|
||||
) : (
|
||||
<RowLabel
|
||||
CustomComponent={CustomRowLabel}
|
||||
label={fallbackLabel}
|
||||
|
||||
@@ -140,9 +140,10 @@ export const BlockRow: React.FC<BlocksFieldProps> = ({
|
||||
: undefined
|
||||
}
|
||||
header={
|
||||
isLoading
|
||||
? null
|
||||
: Label || (
|
||||
isLoading ? (
|
||||
<ShimmerEffect height="1rem" width="8rem" />
|
||||
) : (
|
||||
Label || (
|
||||
<div className={`${baseClass}__block-header`}>
|
||||
<span className={`${baseClass}__block-number`}>
|
||||
{String(rowIndex + 1).padStart(2, '0')}
|
||||
@@ -157,6 +158,7 @@ export const BlockRow: React.FC<BlocksFieldProps> = ({
|
||||
{fieldHasErrors && <ErrorPill count={errorCount} i18n={i18n} withMessage />}
|
||||
</div>
|
||||
)
|
||||
)
|
||||
}
|
||||
isCollapsed={row.collapsed}
|
||||
key={row.id}
|
||||
|
||||
@@ -30,7 +30,7 @@ import { useLocale } from '../../providers/Locale/index.js'
|
||||
import { useOperation } from '../../providers/Operation/index.js'
|
||||
import { useServerFunctions } from '../../providers/ServerFunctions/index.js'
|
||||
import { useTranslation } from '../../providers/Translation/index.js'
|
||||
import { abortAndIgnore } from '../../utilities/abortAndIgnore.js'
|
||||
import { abortAndIgnore, handleAbortRef } from '../../utilities/abortAndIgnore.js'
|
||||
import { requests } from '../../utilities/api.js'
|
||||
import {
|
||||
FormContext,
|
||||
@@ -93,7 +93,7 @@ export const Form: React.FC<FormProps> = (props) => {
|
||||
const [submitted, setSubmitted] = useState(false)
|
||||
const formRef = useRef<HTMLFormElement>(null)
|
||||
const contextRef = useRef({} as FormContextType)
|
||||
const resetFormStateAbortControllerRef = useRef<AbortController>(null)
|
||||
const abortResetFormRef = useRef<AbortController>(null)
|
||||
|
||||
const fieldsReducer = useReducer(fieldReducer, {}, () => initialState)
|
||||
|
||||
@@ -483,10 +483,7 @@ export const Form: React.FC<FormProps> = (props) => {
|
||||
|
||||
const reset = useCallback(
|
||||
async (data: unknown) => {
|
||||
abortAndIgnore(resetFormStateAbortControllerRef.current)
|
||||
|
||||
const controller = new AbortController()
|
||||
resetFormStateAbortControllerRef.current = controller
|
||||
const controller = handleAbortRef(abortResetFormRef)
|
||||
|
||||
const docPreferences = await getDocPreferences()
|
||||
|
||||
@@ -507,6 +504,8 @@ export const Form: React.FC<FormProps> = (props) => {
|
||||
contextRef.current = { ...initContextState } as FormContextType
|
||||
setModified(false)
|
||||
dispatchFields({ type: 'REPLACE_STATE', state: newState })
|
||||
|
||||
abortResetFormRef.current = null
|
||||
},
|
||||
[
|
||||
collectionSlug,
|
||||
@@ -576,8 +575,10 @@ export const Form: React.FC<FormProps> = (props) => {
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
const abortOnChange = abortResetFormRef.current
|
||||
|
||||
return () => {
|
||||
abortAndIgnore(resetFormStateAbortControllerRef.current)
|
||||
abortAndIgnore(abortOnChange)
|
||||
}
|
||||
}, [])
|
||||
|
||||
|
||||
@@ -95,6 +95,7 @@ const DocumentInfo: React.FC<
|
||||
const [mostRecentVersionIsAutosaved, setMostRecentVersionIsAutosaved] = useState(
|
||||
mostRecentVersionIsAutosavedFromProps,
|
||||
)
|
||||
|
||||
const [versionCount, setVersionCount] = useState(versionCountFromProps)
|
||||
|
||||
const [hasPublishedDoc, setHasPublishedDoc] = useState(hasPublishedDocFromProps)
|
||||
|
||||
@@ -1,9 +1,26 @@
|
||||
export function abortAndIgnore(controller: AbortController) {
|
||||
if (controller) {
|
||||
export function abortAndIgnore(abortController: AbortController) {
|
||||
if (abortController) {
|
||||
try {
|
||||
controller.abort()
|
||||
abortController.abort()
|
||||
} catch (_err) {
|
||||
// swallow error
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function handleAbortRef(
|
||||
abortControllerRef: React.RefObject<AbortController>,
|
||||
): AbortController {
|
||||
if (abortControllerRef.current) {
|
||||
try {
|
||||
abortControllerRef.current.abort()
|
||||
abortControllerRef.current = null
|
||||
} catch (_err) {
|
||||
// swallow error
|
||||
}
|
||||
} else {
|
||||
const controller = new AbortController()
|
||||
abortControllerRef.current = controller
|
||||
return controller
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ import { useEditDepth } from '../../providers/EditDepth/index.js'
|
||||
import { OperationProvider } from '../../providers/Operation/index.js'
|
||||
import { useServerFunctions } from '../../providers/ServerFunctions/index.js'
|
||||
import { useUploadEdits } from '../../providers/UploadEdits/index.js'
|
||||
import { abortAndIgnore } from '../../utilities/abortAndIgnore.js'
|
||||
import { abortAndIgnore, handleAbortRef } from '../../utilities/abortAndIgnore.js'
|
||||
import { formatAdminURL } from '../../utilities/formatAdminURL.js'
|
||||
import { handleBackToDashboard } from '../../utilities/handleBackToDashboard.js'
|
||||
import { handleGoBack } from '../../utilities/handleGoBack.js'
|
||||
@@ -120,8 +120,8 @@ export const DefaultEditView: React.FC<ClientSideEditViewProps> = ({
|
||||
const { resetUploadEdits } = useUploadEdits()
|
||||
const { getFormState } = useServerFunctions()
|
||||
|
||||
const onChangeAbortControllerRef = useRef<AbortController>(null)
|
||||
const onSaveAbortControllerRef = useRef<AbortController>(null)
|
||||
const abortOnChangeRef = useRef<AbortController>(null)
|
||||
const abortOnSaveRef = useRef<AbortController>(null)
|
||||
|
||||
const locale = params.get('locale')
|
||||
|
||||
@@ -142,22 +142,13 @@ export const DefaultEditView: React.FC<ClientSideEditViewProps> = ({
|
||||
typeof lockDocumentsProp === 'object' ? lockDocumentsProp.duration : lockDurationDefault
|
||||
const lockDurationInMilliseconds = lockDuration * 1000
|
||||
|
||||
let preventLeaveWithoutSaving = true
|
||||
let autosaveEnabled = false
|
||||
const autosaveEnabled = Boolean(
|
||||
(collectionConfig?.versions?.drafts && collectionConfig?.versions?.drafts?.autosave) ||
|
||||
(globalConfig?.versions?.drafts && globalConfig?.versions?.drafts?.autosave),
|
||||
)
|
||||
|
||||
if (collectionConfig) {
|
||||
autosaveEnabled = Boolean(
|
||||
collectionConfig?.versions?.drafts && collectionConfig?.versions?.drafts?.autosave,
|
||||
)
|
||||
preventLeaveWithoutSaving = !autosaveEnabled
|
||||
} else if (globalConfig) {
|
||||
autosaveEnabled = Boolean(
|
||||
globalConfig?.versions?.drafts && globalConfig?.versions?.drafts?.autosave,
|
||||
)
|
||||
preventLeaveWithoutSaving = !autosaveEnabled
|
||||
} else if (typeof disableLeaveWithoutSaving !== 'undefined') {
|
||||
preventLeaveWithoutSaving = !disableLeaveWithoutSaving
|
||||
}
|
||||
const preventLeaveWithoutSaving =
|
||||
typeof disableLeaveWithoutSaving !== 'undefined' ? !disableLeaveWithoutSaving : !autosaveEnabled
|
||||
|
||||
const [isReadOnlyForIncomingUser, setIsReadOnlyForIncomingUser] = useState(false)
|
||||
const [showTakeOverModal, setShowTakeOverModal] = useState(false)
|
||||
@@ -238,6 +229,8 @@ export const DefaultEditView: React.FC<ClientSideEditViewProps> = ({
|
||||
|
||||
const onSave = useCallback(
|
||||
async (json): Promise<FormState> => {
|
||||
const controller = handleAbortRef(abortOnSaveRef)
|
||||
|
||||
reportUpdate({
|
||||
id,
|
||||
entitySlug,
|
||||
@@ -277,10 +270,6 @@ export const DefaultEditView: React.FC<ClientSideEditViewProps> = ({
|
||||
await getDocPermissions(json)
|
||||
|
||||
if ((id || globalSlug) && !autosaveEnabled) {
|
||||
abortAndIgnore(onSaveAbortControllerRef.current)
|
||||
const controller = new AbortController()
|
||||
onSaveAbortControllerRef.current = controller
|
||||
|
||||
const docPreferences = await getDocPreferences()
|
||||
|
||||
const { state } = await getFormState({
|
||||
@@ -302,6 +291,8 @@ export const DefaultEditView: React.FC<ClientSideEditViewProps> = ({
|
||||
setDocumentIsLocked(false)
|
||||
}
|
||||
|
||||
abortOnSaveRef.current = null
|
||||
|
||||
return state
|
||||
}
|
||||
},
|
||||
@@ -337,10 +328,7 @@ export const DefaultEditView: React.FC<ClientSideEditViewProps> = ({
|
||||
|
||||
const onChange: FormProps['onChange'][0] = useCallback(
|
||||
async ({ formState: prevFormState }) => {
|
||||
abortAndIgnore(onChangeAbortControllerRef.current)
|
||||
|
||||
const controller = new AbortController()
|
||||
onChangeAbortControllerRef.current = controller
|
||||
const controller = handleAbortRef(abortOnChangeRef)
|
||||
|
||||
const currentTime = Date.now()
|
||||
const timeSinceLastUpdate = currentTime - editSessionStartTime
|
||||
@@ -374,6 +362,8 @@ export const DefaultEditView: React.FC<ClientSideEditViewProps> = ({
|
||||
handleDocumentLocking(lockedState)
|
||||
}
|
||||
|
||||
abortOnChangeRef.current = null
|
||||
|
||||
return state
|
||||
},
|
||||
[
|
||||
@@ -428,9 +418,12 @@ export const DefaultEditView: React.FC<ClientSideEditViewProps> = ({
|
||||
])
|
||||
|
||||
useEffect(() => {
|
||||
const abortOnChange = abortOnChangeRef.current
|
||||
const abortOnSave = abortOnSaveRef.current
|
||||
|
||||
return () => {
|
||||
abortAndIgnore(onChangeAbortControllerRef.current)
|
||||
abortAndIgnore(onSaveAbortControllerRef.current)
|
||||
abortAndIgnore(abortOnChange)
|
||||
abortAndIgnore(abortOnSave)
|
||||
}
|
||||
}, [])
|
||||
|
||||
|
||||
@@ -820,9 +820,31 @@ export interface PagesSelect<T extends boolean = true> {
|
||||
layout?:
|
||||
| T
|
||||
| {
|
||||
cta?:
|
||||
cta?: T | CallToActionBlockSelect<T>;
|
||||
content?: T | ContentBlockSelect<T>;
|
||||
mediaBlock?: T | MediaBlockSelect<T>;
|
||||
archive?: T | ArchiveBlockSelect<T>;
|
||||
formBlock?: T | FormBlockSelect<T>;
|
||||
};
|
||||
meta?:
|
||||
| T
|
||||
| {
|
||||
title?: T;
|
||||
image?: T;
|
||||
description?: T;
|
||||
};
|
||||
publishedAt?: T;
|
||||
slug?: T;
|
||||
slugLock?: T;
|
||||
updatedAt?: T;
|
||||
createdAt?: T;
|
||||
_status?: T;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "CallToActionBlock_select".
|
||||
*/
|
||||
export interface CallToActionBlockSelect<T extends boolean = true> {
|
||||
richText?: T;
|
||||
links?:
|
||||
| T
|
||||
@@ -841,10 +863,12 @@ export interface PagesSelect<T extends boolean = true> {
|
||||
};
|
||||
id?: T;
|
||||
blockName?: T;
|
||||
};
|
||||
content?:
|
||||
| T
|
||||
| {
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "ContentBlock_select".
|
||||
*/
|
||||
export interface ContentBlockSelect<T extends boolean = true> {
|
||||
columns?:
|
||||
| T
|
||||
| {
|
||||
@@ -865,17 +889,21 @@ export interface PagesSelect<T extends boolean = true> {
|
||||
};
|
||||
id?: T;
|
||||
blockName?: T;
|
||||
};
|
||||
mediaBlock?:
|
||||
| T
|
||||
| {
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "MediaBlock_select".
|
||||
*/
|
||||
export interface MediaBlockSelect<T extends boolean = true> {
|
||||
media?: T;
|
||||
id?: T;
|
||||
blockName?: T;
|
||||
};
|
||||
archive?:
|
||||
| T
|
||||
| {
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "ArchiveBlock_select".
|
||||
*/
|
||||
export interface ArchiveBlockSelect<T extends boolean = true> {
|
||||
introContent?: T;
|
||||
populateBy?: T;
|
||||
relationTo?: T;
|
||||
@@ -884,30 +912,17 @@ export interface PagesSelect<T extends boolean = true> {
|
||||
selectedDocs?: T;
|
||||
id?: T;
|
||||
blockName?: T;
|
||||
};
|
||||
formBlock?:
|
||||
| T
|
||||
| {
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "FormBlock_select".
|
||||
*/
|
||||
export interface FormBlockSelect<T extends boolean = true> {
|
||||
form?: T;
|
||||
enableIntro?: T;
|
||||
introContent?: T;
|
||||
id?: T;
|
||||
blockName?: T;
|
||||
};
|
||||
};
|
||||
meta?:
|
||||
| T
|
||||
| {
|
||||
title?: T;
|
||||
image?: T;
|
||||
description?: T;
|
||||
};
|
||||
publishedAt?: T;
|
||||
slug?: T;
|
||||
slugLock?: T;
|
||||
updatedAt?: T;
|
||||
createdAt?: T;
|
||||
_status?: T;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
|
||||
@@ -310,9 +310,6 @@ export interface LexicalMigrateField {
|
||||
export interface LexicalLocalizedField {
|
||||
id: string;
|
||||
title: string;
|
||||
/**
|
||||
* Non-localized field with localized block subfields
|
||||
*/
|
||||
lexicalBlocksSubLocalized?: {
|
||||
root: {
|
||||
type: string;
|
||||
@@ -328,9 +325,6 @@ export interface LexicalLocalizedField {
|
||||
};
|
||||
[k: string]: unknown;
|
||||
} | null;
|
||||
/**
|
||||
* Localized field with localized block subfields
|
||||
*/
|
||||
lexicalBlocksLocalized?: {
|
||||
root: {
|
||||
type: string;
|
||||
@@ -481,9 +475,6 @@ export interface ArrayField {
|
||||
id?: string | null;
|
||||
}[]
|
||||
| null;
|
||||
/**
|
||||
* Row labels rendered as react components.
|
||||
*/
|
||||
rowLabelAsComponent?:
|
||||
| {
|
||||
title?: string | null;
|
||||
@@ -598,9 +589,6 @@ export interface BlockField {
|
||||
}
|
||||
)[]
|
||||
| null;
|
||||
/**
|
||||
* The purpose of this field is to test validateExistingBlockIsIdentical works with similar blocks with group fields
|
||||
*/
|
||||
blocksWithSimilarGroup?:
|
||||
| (
|
||||
| {
|
||||
@@ -789,18 +777,9 @@ export interface TextField {
|
||||
id: string;
|
||||
text: string;
|
||||
hiddenTextField?: string | null;
|
||||
/**
|
||||
* This field should be hidden
|
||||
*/
|
||||
adminHiddenTextField?: string | null;
|
||||
/**
|
||||
* This field should be disabled
|
||||
*/
|
||||
disabledTextField?: string | null;
|
||||
localizedText?: string | null;
|
||||
/**
|
||||
* en description
|
||||
*/
|
||||
i18nText?: string | null;
|
||||
defaultString?: string | null;
|
||||
defaultEmptyString?: string | null;
|
||||
@@ -921,14 +900,8 @@ export interface ConditionalLogic {
|
||||
userConditional?: string | null;
|
||||
parentGroup?: {
|
||||
enableParentGroupFields?: boolean | null;
|
||||
/**
|
||||
* Ensures we can rely on nested fields within `data`.
|
||||
*/
|
||||
siblingField?: string | null;
|
||||
};
|
||||
/**
|
||||
* Ensures we can rely on nested fields within `siblingsData`.
|
||||
*/
|
||||
reliesOnParentGroup?: string | null;
|
||||
groupSelection?: ('group1' | 'group2') | null;
|
||||
group1?: {
|
||||
@@ -1008,9 +981,6 @@ export interface EmailField {
|
||||
email: string;
|
||||
localizedEmail?: string | null;
|
||||
emailWithAutocomplete?: string | null;
|
||||
/**
|
||||
* en description
|
||||
*/
|
||||
i18nEmail?: string | null;
|
||||
defaultEmail?: string | null;
|
||||
defaultEmptyString?: string | null;
|
||||
@@ -1040,9 +1010,6 @@ export interface RadioField {
|
||||
*/
|
||||
export interface GroupField {
|
||||
id: string;
|
||||
/**
|
||||
* This is a group.
|
||||
*/
|
||||
group: {
|
||||
text: string;
|
||||
defaultParent?: string | null;
|
||||
@@ -1435,9 +1402,6 @@ export interface RichTextField {
|
||||
[k: string]: unknown;
|
||||
};
|
||||
lexicalCustomFields_html?: string | null;
|
||||
/**
|
||||
* This rich text field uses the lexical editor.
|
||||
*/
|
||||
lexical?: {
|
||||
root: {
|
||||
type: string;
|
||||
@@ -1453,9 +1417,6 @@ export interface RichTextField {
|
||||
};
|
||||
[k: string]: unknown;
|
||||
} | null;
|
||||
/**
|
||||
* This select field is rendered here to ensure its options dropdown renders above the rich text toolbar.
|
||||
*/
|
||||
selectHasMany?: ('one' | 'two' | 'three' | 'four' | 'five' | 'six')[] | null;
|
||||
richText: {
|
||||
[k: string]: unknown;
|
||||
@@ -1544,9 +1505,6 @@ export interface TabsFields2 {
|
||||
*/
|
||||
export interface TabsField {
|
||||
id: string;
|
||||
/**
|
||||
* This should not collapse despite there being many tabs pushing the main fields open.
|
||||
*/
|
||||
sidebarField?: string | null;
|
||||
array: {
|
||||
text: string;
|
||||
@@ -2157,42 +2115,257 @@ export interface BlockFieldsSelect<T extends boolean = true> {
|
||||
blocks?:
|
||||
| T
|
||||
| {
|
||||
content?: T | ContentBlockSelect<T>;
|
||||
number?: T | NumberBlockSelect<T>;
|
||||
subBlocks?: T | SubBlocksBlockSelect<T>;
|
||||
tabs?: T | TabsBlockSelect<T>;
|
||||
content?:
|
||||
| T
|
||||
| {
|
||||
text?: T;
|
||||
richText?: T;
|
||||
id?: T;
|
||||
blockName?: T;
|
||||
};
|
||||
number?:
|
||||
| T
|
||||
| {
|
||||
number?: T;
|
||||
id?: T;
|
||||
blockName?: T;
|
||||
};
|
||||
subBlocks?:
|
||||
| T
|
||||
| {
|
||||
subBlocks?:
|
||||
| T
|
||||
| {
|
||||
text?:
|
||||
| T
|
||||
| {
|
||||
text?: T;
|
||||
id?: T;
|
||||
blockName?: T;
|
||||
};
|
||||
number?:
|
||||
| T
|
||||
| {
|
||||
number?: T;
|
||||
id?: T;
|
||||
blockName?: T;
|
||||
};
|
||||
};
|
||||
id?: T;
|
||||
blockName?: T;
|
||||
};
|
||||
tabs?:
|
||||
| T
|
||||
| {
|
||||
textInCollapsible?: T;
|
||||
textInRow?: T;
|
||||
id?: T;
|
||||
blockName?: T;
|
||||
};
|
||||
};
|
||||
duplicate?:
|
||||
| T
|
||||
| {
|
||||
content?: T | ContentBlockSelect<T>;
|
||||
number?: T | NumberBlockSelect<T>;
|
||||
subBlocks?: T | SubBlocksBlockSelect<T>;
|
||||
tabs?: T | TabsBlockSelect<T>;
|
||||
content?:
|
||||
| T
|
||||
| {
|
||||
text?: T;
|
||||
richText?: T;
|
||||
id?: T;
|
||||
blockName?: T;
|
||||
};
|
||||
number?:
|
||||
| T
|
||||
| {
|
||||
number?: T;
|
||||
id?: T;
|
||||
blockName?: T;
|
||||
};
|
||||
subBlocks?:
|
||||
| T
|
||||
| {
|
||||
subBlocks?:
|
||||
| T
|
||||
| {
|
||||
text?:
|
||||
| T
|
||||
| {
|
||||
text?: T;
|
||||
id?: T;
|
||||
blockName?: T;
|
||||
};
|
||||
number?:
|
||||
| T
|
||||
| {
|
||||
number?: T;
|
||||
id?: T;
|
||||
blockName?: T;
|
||||
};
|
||||
};
|
||||
id?: T;
|
||||
blockName?: T;
|
||||
};
|
||||
tabs?:
|
||||
| T
|
||||
| {
|
||||
textInCollapsible?: T;
|
||||
textInRow?: T;
|
||||
id?: T;
|
||||
blockName?: T;
|
||||
};
|
||||
};
|
||||
collapsedByDefaultBlocks?:
|
||||
| T
|
||||
| {
|
||||
localizedContent?: T | LocalizedContentBlockSelect<T>;
|
||||
localizedNumber?: T | LocalizedNumberBlockSelect<T>;
|
||||
localizedSubBlocks?: T | LocalizedSubBlocksBlockSelect<T>;
|
||||
localizedTabs?: T | LocalizedTabsBlockSelect<T>;
|
||||
localizedContent?:
|
||||
| T
|
||||
| {
|
||||
text?: T;
|
||||
richText?: T;
|
||||
id?: T;
|
||||
blockName?: T;
|
||||
};
|
||||
localizedNumber?:
|
||||
| T
|
||||
| {
|
||||
number?: T;
|
||||
id?: T;
|
||||
blockName?: T;
|
||||
};
|
||||
localizedSubBlocks?:
|
||||
| T
|
||||
| {
|
||||
subBlocks?:
|
||||
| T
|
||||
| {
|
||||
text?:
|
||||
| T
|
||||
| {
|
||||
text?: T;
|
||||
id?: T;
|
||||
blockName?: T;
|
||||
};
|
||||
number?:
|
||||
| T
|
||||
| {
|
||||
number?: T;
|
||||
id?: T;
|
||||
blockName?: T;
|
||||
};
|
||||
};
|
||||
id?: T;
|
||||
blockName?: T;
|
||||
};
|
||||
localizedTabs?:
|
||||
| T
|
||||
| {
|
||||
textInCollapsible?: T;
|
||||
textInRow?: T;
|
||||
id?: T;
|
||||
blockName?: T;
|
||||
};
|
||||
};
|
||||
disableSort?:
|
||||
| T
|
||||
| {
|
||||
localizedContent?: T | LocalizedContentBlockSelect<T>;
|
||||
localizedNumber?: T | LocalizedNumberBlockSelect<T>;
|
||||
localizedSubBlocks?: T | LocalizedSubBlocksBlockSelect<T>;
|
||||
localizedTabs?: T | LocalizedTabsBlockSelect<T>;
|
||||
localizedContent?:
|
||||
| T
|
||||
| {
|
||||
text?: T;
|
||||
richText?: T;
|
||||
id?: T;
|
||||
blockName?: T;
|
||||
};
|
||||
localizedNumber?:
|
||||
| T
|
||||
| {
|
||||
number?: T;
|
||||
id?: T;
|
||||
blockName?: T;
|
||||
};
|
||||
localizedSubBlocks?:
|
||||
| T
|
||||
| {
|
||||
subBlocks?:
|
||||
| T
|
||||
| {
|
||||
text?:
|
||||
| T
|
||||
| {
|
||||
text?: T;
|
||||
id?: T;
|
||||
blockName?: T;
|
||||
};
|
||||
number?:
|
||||
| T
|
||||
| {
|
||||
number?: T;
|
||||
id?: T;
|
||||
blockName?: T;
|
||||
};
|
||||
};
|
||||
id?: T;
|
||||
blockName?: T;
|
||||
};
|
||||
localizedTabs?:
|
||||
| T
|
||||
| {
|
||||
textInCollapsible?: T;
|
||||
textInRow?: T;
|
||||
id?: T;
|
||||
blockName?: T;
|
||||
};
|
||||
};
|
||||
localizedBlocks?:
|
||||
| T
|
||||
| {
|
||||
localizedContent?: T | LocalizedContentBlockSelect<T>;
|
||||
localizedNumber?: T | LocalizedNumberBlockSelect<T>;
|
||||
localizedSubBlocks?: T | LocalizedSubBlocksBlockSelect<T>;
|
||||
localizedTabs?: T | LocalizedTabsBlockSelect<T>;
|
||||
localizedContent?:
|
||||
| T
|
||||
| {
|
||||
text?: T;
|
||||
richText?: T;
|
||||
id?: T;
|
||||
blockName?: T;
|
||||
};
|
||||
localizedNumber?:
|
||||
| T
|
||||
| {
|
||||
number?: T;
|
||||
id?: T;
|
||||
blockName?: T;
|
||||
};
|
||||
localizedSubBlocks?:
|
||||
| T
|
||||
| {
|
||||
subBlocks?:
|
||||
| T
|
||||
| {
|
||||
text?:
|
||||
| T
|
||||
| {
|
||||
text?: T;
|
||||
id?: T;
|
||||
blockName?: T;
|
||||
};
|
||||
number?:
|
||||
| T
|
||||
| {
|
||||
number?: T;
|
||||
id?: T;
|
||||
blockName?: T;
|
||||
};
|
||||
};
|
||||
id?: T;
|
||||
blockName?: T;
|
||||
};
|
||||
localizedTabs?:
|
||||
| T
|
||||
| {
|
||||
textInCollapsible?: T;
|
||||
textInRow?: T;
|
||||
id?: T;
|
||||
blockName?: T;
|
||||
};
|
||||
};
|
||||
i18nBlocks?:
|
||||
| T
|
||||
@@ -2330,116 +2503,6 @@ export interface BlockFieldsSelect<T extends boolean = true> {
|
||||
updatedAt?: T;
|
||||
createdAt?: T;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "ContentBlock_select".
|
||||
*/
|
||||
export interface ContentBlockSelect<T extends boolean = true> {
|
||||
text?: T;
|
||||
richText?: T;
|
||||
id?: T;
|
||||
blockName?: T;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "NumberBlock_select".
|
||||
*/
|
||||
export interface NumberBlockSelect<T extends boolean = true> {
|
||||
number?: T;
|
||||
id?: T;
|
||||
blockName?: T;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "SubBlocksBlock_select".
|
||||
*/
|
||||
export interface SubBlocksBlockSelect<T extends boolean = true> {
|
||||
subBlocks?:
|
||||
| T
|
||||
| {
|
||||
text?:
|
||||
| T
|
||||
| {
|
||||
text?: T;
|
||||
id?: T;
|
||||
blockName?: T;
|
||||
};
|
||||
number?:
|
||||
| T
|
||||
| {
|
||||
number?: T;
|
||||
id?: T;
|
||||
blockName?: T;
|
||||
};
|
||||
};
|
||||
id?: T;
|
||||
blockName?: T;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "TabsBlock_select".
|
||||
*/
|
||||
export interface TabsBlockSelect<T extends boolean = true> {
|
||||
textInCollapsible?: T;
|
||||
textInRow?: T;
|
||||
id?: T;
|
||||
blockName?: T;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "localizedContentBlock_select".
|
||||
*/
|
||||
export interface LocalizedContentBlockSelect<T extends boolean = true> {
|
||||
text?: T;
|
||||
richText?: T;
|
||||
id?: T;
|
||||
blockName?: T;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "localizedNumberBlock_select".
|
||||
*/
|
||||
export interface LocalizedNumberBlockSelect<T extends boolean = true> {
|
||||
number?: T;
|
||||
id?: T;
|
||||
blockName?: T;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "localizedSubBlocksBlock_select".
|
||||
*/
|
||||
export interface LocalizedSubBlocksBlockSelect<T extends boolean = true> {
|
||||
subBlocks?:
|
||||
| T
|
||||
| {
|
||||
text?:
|
||||
| T
|
||||
| {
|
||||
text?: T;
|
||||
id?: T;
|
||||
blockName?: T;
|
||||
};
|
||||
number?:
|
||||
| T
|
||||
| {
|
||||
number?: T;
|
||||
id?: T;
|
||||
blockName?: T;
|
||||
};
|
||||
};
|
||||
id?: T;
|
||||
blockName?: T;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "localizedTabsBlock_select".
|
||||
*/
|
||||
export interface LocalizedTabsBlockSelect<T extends boolean = true> {
|
||||
textInCollapsible?: T;
|
||||
textInRow?: T;
|
||||
id?: T;
|
||||
blockName?: T;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "checkbox-fields_select".
|
||||
@@ -3022,10 +3085,53 @@ export interface TabsFieldsSelect<T extends boolean = true> {
|
||||
blocks?:
|
||||
| T
|
||||
| {
|
||||
content?: T | ContentBlockSelect<T>;
|
||||
number?: T | NumberBlockSelect<T>;
|
||||
subBlocks?: T | SubBlocksBlockSelect<T>;
|
||||
tabs?: T | TabsBlockSelect<T>;
|
||||
content?:
|
||||
| T
|
||||
| {
|
||||
text?: T;
|
||||
richText?: T;
|
||||
id?: T;
|
||||
blockName?: T;
|
||||
};
|
||||
number?:
|
||||
| T
|
||||
| {
|
||||
number?: T;
|
||||
id?: T;
|
||||
blockName?: T;
|
||||
};
|
||||
subBlocks?:
|
||||
| T
|
||||
| {
|
||||
subBlocks?:
|
||||
| T
|
||||
| {
|
||||
text?:
|
||||
| T
|
||||
| {
|
||||
text?: T;
|
||||
id?: T;
|
||||
blockName?: T;
|
||||
};
|
||||
number?:
|
||||
| T
|
||||
| {
|
||||
number?: T;
|
||||
id?: T;
|
||||
blockName?: T;
|
||||
};
|
||||
};
|
||||
id?: T;
|
||||
blockName?: T;
|
||||
};
|
||||
tabs?:
|
||||
| T
|
||||
| {
|
||||
textInCollapsible?: T;
|
||||
textInRow?: T;
|
||||
id?: T;
|
||||
blockName?: T;
|
||||
};
|
||||
};
|
||||
group?:
|
||||
| T
|
||||
@@ -3035,7 +3141,24 @@ export interface TabsFieldsSelect<T extends boolean = true> {
|
||||
textInRow?: T;
|
||||
numberInRow?: T;
|
||||
json?: T;
|
||||
tab?: T | TabWithNameSelect<T>;
|
||||
tab?:
|
||||
| T
|
||||
| {
|
||||
array?:
|
||||
| T
|
||||
| {
|
||||
text?: T;
|
||||
id?: T;
|
||||
};
|
||||
text?: T;
|
||||
defaultValue?: T;
|
||||
arrayInRow?:
|
||||
| T
|
||||
| {
|
||||
textInArrayInRow?: T;
|
||||
id?: T;
|
||||
};
|
||||
};
|
||||
namedTabWithDefaultValue?:
|
||||
| T
|
||||
| {
|
||||
@@ -3085,26 +3208,6 @@ export interface TabsFieldsSelect<T extends boolean = true> {
|
||||
updatedAt?: T;
|
||||
createdAt?: T;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "TabWithName_select".
|
||||
*/
|
||||
export interface TabWithNameSelect<T extends boolean = true> {
|
||||
array?:
|
||||
| T
|
||||
| {
|
||||
text?: T;
|
||||
id?: T;
|
||||
};
|
||||
text?: T;
|
||||
defaultValue?: T;
|
||||
arrayInRow?:
|
||||
| T
|
||||
| {
|
||||
textInArrayInRow?: T;
|
||||
id?: T;
|
||||
};
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "text-fields_select".
|
||||
|
||||
Reference in New Issue
Block a user