feat(plugin-import-export): adds limit and page fields to export options (#13380)
### What: This PR adds `limit` and `page` fields to the export options, allowing users to control the number of documents exported and the page from which to start the export. It also enforces that limit must be a positive multiple of 100. ### Why: This feature is needed to provide pagination support for large exports, enabling users to export manageable chunks of data rather than the entire dataset at once. Enforcing multiples-of-100 for `limit` ensures consistent chunking behavior and prevents unexpected export issues. ### How: - The `limit` field determines the maximum number of documents to export and **must be a positive multiple of 100**. - The `page` field defines the starting page of the export and is displayed only when a `limit` is specified. - If `limit` is cleared, the `page` resets to 1 to maintain consistency. - Export logic was adjusted to respect the `limit` and `page` values when fetching documents. --------- Co-authored-by: Patrik Kozak <35232443+PatrikKozak@users.noreply.github.com>
This commit is contained in:
@@ -73,7 +73,17 @@ export const ExportSaveButton: React.FC = () => {
|
||||
}
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to download file')
|
||||
// Try to parse the error message from the JSON response
|
||||
let errorMsg = 'Failed to download file'
|
||||
try {
|
||||
const errorJson = await response.json()
|
||||
if (errorJson?.errors?.[0]?.message) {
|
||||
errorMsg = errorJson.errors[0].message
|
||||
}
|
||||
} catch {
|
||||
// Ignore JSON parse errors, fallback to generic message
|
||||
}
|
||||
throw new Error(errorMsg)
|
||||
}
|
||||
|
||||
const fileStream = response.body
|
||||
@@ -98,9 +108,8 @@ export const ExportSaveButton: React.FC = () => {
|
||||
a.click()
|
||||
document.body.removeChild(a)
|
||||
URL.revokeObjectURL(url)
|
||||
} catch (error) {
|
||||
console.error('Error downloading file:', error)
|
||||
toast.error('Error downloading file')
|
||||
} catch (error: any) {
|
||||
toast.error(error.message || 'Error downloading file')
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
.page-field {
|
||||
--field-width: 33.3333%;
|
||||
}
|
||||
41
packages/plugin-import-export/src/components/Page/index.tsx
Normal file
41
packages/plugin-import-export/src/components/Page/index.tsx
Normal file
@@ -0,0 +1,41 @@
|
||||
'use client'
|
||||
|
||||
import type { NumberFieldClientComponent } from 'payload'
|
||||
|
||||
import { NumberField, useField } from '@payloadcms/ui'
|
||||
import React, { useEffect } from 'react'
|
||||
|
||||
import './index.scss'
|
||||
|
||||
const baseClass = 'page-field'
|
||||
|
||||
export const Page: NumberFieldClientComponent = (props) => {
|
||||
const { setValue } = useField<number>()
|
||||
const { value: limitValue } = useField<number>({ path: 'limit' })
|
||||
|
||||
// Effect to reset page to 1 if limit is removed
|
||||
useEffect(() => {
|
||||
if (!limitValue) {
|
||||
setValue(1) // Reset page to 1
|
||||
}
|
||||
}, [limitValue, setValue])
|
||||
|
||||
return (
|
||||
<div className={baseClass}>
|
||||
<NumberField
|
||||
field={{
|
||||
name: props.field.name,
|
||||
admin: {
|
||||
autoComplete: undefined,
|
||||
placeholder: undefined,
|
||||
step: 1,
|
||||
},
|
||||
label: props.field.label,
|
||||
min: 1,
|
||||
}}
|
||||
onChange={(value) => setValue(value ?? 1)} // Update the page value on change
|
||||
path={props.path}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -28,6 +28,7 @@ export const Preview = () => {
|
||||
const { collection } = useImportExport()
|
||||
const { config } = useConfig()
|
||||
const { value: where } = useField({ path: 'where' })
|
||||
const { value: page } = useField({ path: 'page' })
|
||||
const { value: limit } = useField<number>({ path: 'limit' })
|
||||
const { value: fields } = useField<string[]>({ path: 'fields' })
|
||||
const { value: sort } = useField({ path: 'sort' })
|
||||
@@ -71,6 +72,7 @@ export const Preview = () => {
|
||||
format,
|
||||
limit,
|
||||
locale,
|
||||
page,
|
||||
sort,
|
||||
where,
|
||||
}),
|
||||
@@ -168,6 +170,7 @@ export const Preview = () => {
|
||||
i18n,
|
||||
limit,
|
||||
locale,
|
||||
page,
|
||||
sort,
|
||||
where,
|
||||
])
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
.sort-by-fields {
|
||||
display: block;
|
||||
width: 33%;
|
||||
--field-width: 25%;
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ import React, { useEffect, useState } from 'react'
|
||||
|
||||
import { reduceFields } from '../FieldsToExport/reduceFields.js'
|
||||
import { useImportExport } from '../ImportExportProvider/index.js'
|
||||
import './index.scss'
|
||||
|
||||
const baseClass = 'sort-by-fields'
|
||||
|
||||
@@ -71,7 +72,7 @@ export const SortBy: SelectFieldClientComponent = (props) => {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={baseClass} style={{ '--field-width': '33%' } as React.CSSProperties}>
|
||||
<div className={baseClass}>
|
||||
<FieldLabel label={props.field.label} path={props.path} />
|
||||
<ReactSelect
|
||||
className={baseClass}
|
||||
|
||||
@@ -6,6 +6,7 @@ import { APIError } from 'payload'
|
||||
import { Readable } from 'stream'
|
||||
|
||||
import { buildDisabledFieldRegex } from '../utilities/buildDisabledFieldRegex.js'
|
||||
import { validateLimitValue } from '../utilities/validateLimitValue.js'
|
||||
import { flattenObject } from './flattenObject.js'
|
||||
import { getCustomFieldFunctions } from './getCustomFieldFunctions.js'
|
||||
import { getFilename } from './getFilename.js'
|
||||
@@ -23,8 +24,10 @@ export type Export = {
|
||||
format: 'csv' | 'json'
|
||||
globals?: string[]
|
||||
id: number | string
|
||||
limit?: number
|
||||
locale?: string
|
||||
name: string
|
||||
page?: number
|
||||
slug: string
|
||||
sort: Sort
|
||||
user: string
|
||||
@@ -57,6 +60,8 @@ export const createExport = async (args: CreateExportArgs) => {
|
||||
locale: localeInput,
|
||||
sort,
|
||||
user,
|
||||
page,
|
||||
limit: incomingLimit,
|
||||
where,
|
||||
},
|
||||
req: { locale: localeArg, payload },
|
||||
@@ -87,14 +92,30 @@ export const createExport = async (args: CreateExportArgs) => {
|
||||
req.payload.logger.debug({ message: 'Export configuration:', name, isCSV, locale })
|
||||
}
|
||||
|
||||
const batchSize = 100 // fixed per request
|
||||
|
||||
const hardLimit =
|
||||
typeof incomingLimit === 'number' && incomingLimit > 0 ? incomingLimit : undefined
|
||||
|
||||
const { totalDocs } = await payload.count({
|
||||
collection: collectionSlug,
|
||||
user,
|
||||
locale,
|
||||
overrideAccess: false,
|
||||
})
|
||||
|
||||
const totalPages = Math.max(1, Math.ceil(totalDocs / batchSize))
|
||||
const requestedPage = page || 1
|
||||
const adjustedPage = requestedPage > totalPages ? 1 : requestedPage
|
||||
|
||||
const findArgs = {
|
||||
collection: collectionSlug,
|
||||
depth: 1,
|
||||
draft: drafts === 'yes',
|
||||
limit: 100,
|
||||
limit: batchSize,
|
||||
locale,
|
||||
overrideAccess: false,
|
||||
page: 0,
|
||||
page: 0, // The page will be incremented manually in the loop
|
||||
select,
|
||||
sort,
|
||||
user,
|
||||
@@ -156,15 +177,37 @@ export const createExport = async (args: CreateExportArgs) => {
|
||||
req.payload.logger.debug('Pre-scanning all columns before streaming')
|
||||
}
|
||||
|
||||
const limitErrorMsg = validateLimitValue(
|
||||
incomingLimit,
|
||||
req.t,
|
||||
batchSize, // step i.e. 100
|
||||
)
|
||||
if (limitErrorMsg) {
|
||||
throw new APIError(limitErrorMsg)
|
||||
}
|
||||
|
||||
const allColumns: string[] = []
|
||||
|
||||
if (isCSV) {
|
||||
const allColumnsSet = new Set<string>()
|
||||
let scanPage = 1
|
||||
|
||||
// Use the incoming page value here, defaulting to 1 if undefined
|
||||
let scanPage = adjustedPage
|
||||
let hasMore = true
|
||||
let fetched = 0
|
||||
const maxDocs = typeof hardLimit === 'number' ? hardLimit : Number.POSITIVE_INFINITY
|
||||
|
||||
while (hasMore) {
|
||||
const result = await payload.find({ ...findArgs, page: scanPage })
|
||||
const remaining = Math.max(0, maxDocs - fetched)
|
||||
if (remaining === 0) {
|
||||
break
|
||||
}
|
||||
|
||||
const result = await payload.find({
|
||||
...findArgs,
|
||||
page: scanPage,
|
||||
limit: Math.min(batchSize, remaining),
|
||||
})
|
||||
|
||||
result.docs.forEach((doc) => {
|
||||
const flat = filterDisabledCSV(flattenObject({ doc, fields, toCSVFunctions }))
|
||||
@@ -176,8 +219,9 @@ export const createExport = async (args: CreateExportArgs) => {
|
||||
})
|
||||
})
|
||||
|
||||
hasMore = result.hasNextPage
|
||||
scanPage += 1
|
||||
fetched += result.docs.length
|
||||
scanPage += 1 // Increment page for next batch
|
||||
hasMore = result.hasNextPage && fetched < maxDocs
|
||||
}
|
||||
|
||||
if (debug) {
|
||||
@@ -187,11 +231,27 @@ export const createExport = async (args: CreateExportArgs) => {
|
||||
|
||||
const encoder = new TextEncoder()
|
||||
let isFirstBatch = true
|
||||
let streamPage = 1
|
||||
let streamPage = adjustedPage
|
||||
let fetched = 0
|
||||
const maxDocs = typeof hardLimit === 'number' ? hardLimit : Number.POSITIVE_INFINITY
|
||||
|
||||
const stream = new Readable({
|
||||
async read() {
|
||||
const result = await payload.find({ ...findArgs, page: streamPage })
|
||||
const remaining = Math.max(0, maxDocs - fetched)
|
||||
|
||||
if (remaining === 0) {
|
||||
if (!isCSV) {
|
||||
this.push(encoder.encode(']'))
|
||||
}
|
||||
this.push(null)
|
||||
return
|
||||
}
|
||||
|
||||
const result = await payload.find({
|
||||
...findArgs,
|
||||
page: streamPage,
|
||||
limit: Math.min(batchSize, remaining),
|
||||
})
|
||||
|
||||
if (debug) {
|
||||
req.payload.logger.debug(`Streaming batch ${streamPage} with ${result.docs.length} docs`)
|
||||
@@ -240,10 +300,11 @@ export const createExport = async (args: CreateExportArgs) => {
|
||||
}
|
||||
}
|
||||
|
||||
fetched += result.docs.length
|
||||
isFirstBatch = false
|
||||
streamPage += 1
|
||||
streamPage += 1 // Increment stream page for the next batch
|
||||
|
||||
if (!result.hasNextPage) {
|
||||
if (!result.hasNextPage || fetched >= maxDocs) {
|
||||
if (debug) {
|
||||
req.payload.logger.debug('Stream complete - no more pages')
|
||||
}
|
||||
@@ -272,18 +333,29 @@ export const createExport = async (args: CreateExportArgs) => {
|
||||
const rows: Record<string, unknown>[] = []
|
||||
const columnsSet = new Set<string>()
|
||||
const columns: string[] = []
|
||||
let page = 1
|
||||
|
||||
// Start from the incoming page value, defaulting to 1 if undefined
|
||||
let currentPage = adjustedPage
|
||||
let fetched = 0
|
||||
let hasNextPage = true
|
||||
const maxDocs = typeof hardLimit === 'number' ? hardLimit : Number.POSITIVE_INFINITY
|
||||
|
||||
while (hasNextPage) {
|
||||
const remaining = Math.max(0, maxDocs - fetched)
|
||||
|
||||
if (remaining === 0) {
|
||||
break
|
||||
}
|
||||
|
||||
const result = await payload.find({
|
||||
...findArgs,
|
||||
page,
|
||||
page: currentPage,
|
||||
limit: Math.min(batchSize, remaining),
|
||||
})
|
||||
|
||||
if (debug) {
|
||||
req.payload.logger.debug(
|
||||
`Processing batch ${findArgs.page} with ${result.docs.length} documents`,
|
||||
`Processing batch ${currentPage} with ${result.docs.length} documents`,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -308,10 +380,12 @@ export const createExport = async (args: CreateExportArgs) => {
|
||||
outputData.push(batchRows.map((doc) => JSON.stringify(doc)).join(',\n'))
|
||||
}
|
||||
|
||||
hasNextPage = result.hasNextPage
|
||||
page += 1
|
||||
fetched += result.docs.length
|
||||
hasNextPage = result.hasNextPage && fetched < maxDocs
|
||||
currentPage += 1 // Increment page for next batch
|
||||
}
|
||||
|
||||
// Prepare final output
|
||||
if (isCSV) {
|
||||
const paddedRows = rows.map((row) => {
|
||||
const fullRow: Record<string, unknown> = {}
|
||||
|
||||
@@ -5,22 +5,33 @@ import { APIError } from 'payload'
|
||||
import { createExport } from './createExport.js'
|
||||
|
||||
export const download = async (req: PayloadRequest, debug = false) => {
|
||||
let body
|
||||
if (typeof req?.json === 'function') {
|
||||
body = await req.json()
|
||||
try {
|
||||
let body
|
||||
if (typeof req?.json === 'function') {
|
||||
body = await req.json()
|
||||
}
|
||||
|
||||
if (!body || !body.data) {
|
||||
throw new APIError('Request data is required.')
|
||||
}
|
||||
|
||||
const { collectionSlug } = body.data || {}
|
||||
|
||||
req.payload.logger.info(`Download request received ${collectionSlug}`)
|
||||
body.data.user = req.user
|
||||
|
||||
const res = await createExport({
|
||||
download: true,
|
||||
input: { ...body.data, debug },
|
||||
req,
|
||||
})
|
||||
|
||||
return res as Response
|
||||
} catch (err) {
|
||||
// Return JSON for front-end toast
|
||||
return new Response(
|
||||
JSON.stringify({ errors: [{ message: (err as Error).message || 'Something went wrong' }] }),
|
||||
{ headers: { 'Content-Type': 'application/json' }, status: 400 },
|
||||
)
|
||||
}
|
||||
|
||||
if (!body || !body.data) {
|
||||
throw new APIError('Request data is required.')
|
||||
}
|
||||
|
||||
req.payload.logger.info(`Download request received ${body.data.collectionSlug}`)
|
||||
|
||||
body.data.user = req.user
|
||||
|
||||
return createExport({
|
||||
download: true,
|
||||
input: { ...body.data, debug },
|
||||
req,
|
||||
}) as Promise<Response>
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import type { TFunction } from '@payloadcms/translations'
|
||||
import type { Config, Field, SelectField } from 'payload'
|
||||
|
||||
import type { ImportExportPluginConfig } from '../types.js'
|
||||
|
||||
import { validateLimitValue } from '../utilities/validateLimitValue.js'
|
||||
import { getFilename } from './getFilename.js'
|
||||
|
||||
export const getFields = (config: Config, pluginConfig?: ImportExportPluginConfig): Field[] => {
|
||||
@@ -11,7 +13,7 @@ export const getFields = (config: Config, pluginConfig?: ImportExportPluginConfi
|
||||
name: 'locale',
|
||||
type: 'select',
|
||||
admin: {
|
||||
width: '33%',
|
||||
width: '25%',
|
||||
},
|
||||
defaultValue: 'all',
|
||||
// @ts-expect-error - this is not correctly typed in plugins right now
|
||||
@@ -49,7 +51,7 @@ export const getFields = (config: Config, pluginConfig?: ImportExportPluginConfi
|
||||
admin: {
|
||||
// Hide if a forced format is set via plugin config
|
||||
condition: () => !pluginConfig?.format,
|
||||
width: '33%',
|
||||
width: '33.3333%',
|
||||
},
|
||||
defaultValue: (() => {
|
||||
// Default to plugin-defined format, otherwise 'csv'
|
||||
@@ -74,11 +76,37 @@ export const getFields = (config: Config, pluginConfig?: ImportExportPluginConfi
|
||||
type: 'number',
|
||||
admin: {
|
||||
placeholder: 'No limit',
|
||||
width: '33%',
|
||||
step: 100,
|
||||
width: '33.3333%',
|
||||
},
|
||||
validate: (value: null | number | undefined, { req }: { req: { t: TFunction } }) => {
|
||||
return validateLimitValue(value, req.t) ?? true
|
||||
},
|
||||
// @ts-expect-error - this is not correctly typed in plugins right now
|
||||
label: ({ t }) => t('plugin-import-export:field-limit-label'),
|
||||
},
|
||||
{
|
||||
name: 'page',
|
||||
type: 'number',
|
||||
admin: {
|
||||
components: {
|
||||
Field: '@payloadcms/plugin-import-export/rsc#Page',
|
||||
},
|
||||
condition: ({ limit }) => {
|
||||
// Show the page field only if limit is set
|
||||
return typeof limit === 'number' && limit !== 0
|
||||
},
|
||||
width: '33.3333%',
|
||||
},
|
||||
defaultValue: 1,
|
||||
// @ts-expect-error - this is not correctly typed in plugins right now
|
||||
label: ({ t }) => t('plugin-import-export:field-page-label'),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'row',
|
||||
fields: [
|
||||
{
|
||||
name: 'sort',
|
||||
type: 'text',
|
||||
@@ -90,11 +118,6 @@ export const getFields = (config: Config, pluginConfig?: ImportExportPluginConfi
|
||||
// @ts-expect-error - this is not correctly typed in plugins right now
|
||||
label: ({ t }) => t('plugin-import-export:field-sort-label'),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'row',
|
||||
fields: [
|
||||
...(localeField ? [localeField] : []),
|
||||
{
|
||||
name: 'drafts',
|
||||
@@ -109,7 +132,7 @@ export const getFields = (config: Config, pluginConfig?: ImportExportPluginConfi
|
||||
collectionConfig?.versions?.drafts,
|
||||
)
|
||||
},
|
||||
width: '33%',
|
||||
width: '25%',
|
||||
},
|
||||
defaultValue: 'yes',
|
||||
// @ts-expect-error - this is not correctly typed in plugins right now
|
||||
@@ -163,6 +186,7 @@ export const getFields = (config: Config, pluginConfig?: ImportExportPluginConfi
|
||||
value: 'all',
|
||||
},
|
||||
],
|
||||
virtual: true,
|
||||
},
|
||||
{
|
||||
name: 'fields',
|
||||
|
||||
@@ -3,6 +3,7 @@ export { ExportListMenuItem } from '../components/ExportListMenuItem/index.js'
|
||||
export { ExportSaveButton } from '../components/ExportSaveButton/index.js'
|
||||
export { FieldsToExport } from '../components/FieldsToExport/index.js'
|
||||
export { ImportExportProvider } from '../components/ImportExportProvider/index.js'
|
||||
export { Page } from '../components/Page/index.js'
|
||||
export { Preview } from '../components/Preview/index.js'
|
||||
export { SelectionToUseField } from '../components/SelectionToUseField/index.js'
|
||||
export { SortBy } from '../components/SortBy/index.js'
|
||||
|
||||
@@ -63,7 +63,7 @@ export const importExportPlugin =
|
||||
path: '@payloadcms/plugin-import-export/rsc#ExportListMenuItem',
|
||||
})
|
||||
|
||||
// // Find fields explicitly marked as disabled for import/export
|
||||
// Find fields explicitly marked as disabled for import/export
|
||||
const disabledFieldAccessors = collectDisabledFieldPaths(collection.fields)
|
||||
|
||||
// Store disabled field accessors in the admin config for use in the UI
|
||||
@@ -90,13 +90,14 @@ export const importExportPlugin =
|
||||
handler: async (req) => {
|
||||
await addDataAndFileToRequest(req)
|
||||
|
||||
const { collectionSlug, draft, fields, limit, locale, sort, where } = req.data as {
|
||||
const { collectionSlug, draft, fields, limit, locale, page, sort, where } = req.data as {
|
||||
collectionSlug: string
|
||||
draft?: 'no' | 'yes'
|
||||
fields?: string[]
|
||||
format?: 'csv' | 'json'
|
||||
limit?: number
|
||||
locale?: string
|
||||
page?: number
|
||||
sort?: any
|
||||
where?: any
|
||||
}
|
||||
@@ -118,6 +119,7 @@ export const importExportPlugin =
|
||||
limit: limit && limit > 10 ? 10 : limit,
|
||||
locale,
|
||||
overrideAccess: false,
|
||||
page,
|
||||
req,
|
||||
select,
|
||||
sort,
|
||||
|
||||
@@ -12,6 +12,7 @@ export const arTranslations: PluginDefaultTranslationsObject = {
|
||||
'field-limit-label': 'حد',
|
||||
'field-locale-label': 'موقع',
|
||||
'field-name-label': 'اسم الملف',
|
||||
'field-page-label': 'صفحة',
|
||||
'field-selectionToUse-label': 'اختيار للاستخدام',
|
||||
'field-sort-label': 'ترتيب حسب',
|
||||
'selectionToUse-allDocuments': 'استخدم جميع الوثائق',
|
||||
|
||||
@@ -12,6 +12,7 @@ export const azTranslations: PluginDefaultTranslationsObject = {
|
||||
'field-limit-label': 'Hədd',
|
||||
'field-locale-label': 'Yerli',
|
||||
'field-name-label': 'Fayl adı',
|
||||
'field-page-label': 'Səhifə',
|
||||
'field-selectionToUse-label': 'İstifadə etmək üçün seçim',
|
||||
'field-sort-label': 'Sırala',
|
||||
'selectionToUse-allDocuments': 'Bütün sənədlərdən istifadə edin',
|
||||
|
||||
@@ -12,6 +12,7 @@ export const bgTranslations: PluginDefaultTranslationsObject = {
|
||||
'field-limit-label': 'Лимит',
|
||||
'field-locale-label': 'Регион',
|
||||
'field-name-label': 'Име на файла',
|
||||
'field-page-label': 'Страница',
|
||||
'field-selectionToUse-label': 'Избор за използване',
|
||||
'field-sort-label': 'Сортирай по',
|
||||
'selectionToUse-allDocuments': 'Използвайте всички документи',
|
||||
|
||||
@@ -12,6 +12,7 @@ export const caTranslations: PluginDefaultTranslationsObject = {
|
||||
'field-limit-label': 'Límit',
|
||||
'field-locale-label': 'Local',
|
||||
'field-name-label': 'Nom del fitxer',
|
||||
'field-page-label': 'Pàgina',
|
||||
'field-selectionToUse-label': 'Selecció per utilitzar',
|
||||
'field-sort-label': 'Ordena per',
|
||||
'selectionToUse-allDocuments': 'Utilitzeu tots els documents',
|
||||
|
||||
@@ -12,6 +12,7 @@ export const csTranslations: PluginDefaultTranslationsObject = {
|
||||
'field-limit-label': 'Limita',
|
||||
'field-locale-label': 'Místní',
|
||||
'field-name-label': 'Název souboru',
|
||||
'field-page-label': 'Stránka',
|
||||
'field-selectionToUse-label': 'Výběr k použití',
|
||||
'field-sort-label': 'Seřadit podle',
|
||||
'selectionToUse-allDocuments': 'Použijte všechny dokumenty',
|
||||
|
||||
@@ -12,6 +12,7 @@ export const daTranslations: PluginDefaultTranslationsObject = {
|
||||
'field-limit-label': 'Begrænsning',
|
||||
'field-locale-label': 'Lokale',
|
||||
'field-name-label': 'Filnavn',
|
||||
'field-page-label': 'Side',
|
||||
'field-selectionToUse-label': 'Valg til brug',
|
||||
'field-sort-label': 'Sorter efter',
|
||||
'selectionToUse-allDocuments': 'Brug alle dokumenter',
|
||||
|
||||
@@ -12,6 +12,7 @@ export const deTranslations: PluginDefaultTranslationsObject = {
|
||||
'field-limit-label': 'Grenze',
|
||||
'field-locale-label': 'Ort',
|
||||
'field-name-label': 'Dateiname',
|
||||
'field-page-label': 'Seite',
|
||||
'field-selectionToUse-label': 'Auswahl zur Verwendung',
|
||||
'field-sort-label': 'Sortieren nach',
|
||||
'selectionToUse-allDocuments': 'Verwenden Sie alle Dokumente.',
|
||||
|
||||
@@ -11,6 +11,7 @@ export const enTranslations = {
|
||||
'field-limit-label': 'Limit',
|
||||
'field-locale-label': 'Locale',
|
||||
'field-name-label': 'File name',
|
||||
'field-page-label': 'Page',
|
||||
'field-selectionToUse-label': 'Selection to use',
|
||||
'field-sort-label': 'Sort by',
|
||||
'selectionToUse-allDocuments': 'Use all documents',
|
||||
|
||||
@@ -12,6 +12,7 @@ export const esTranslations: PluginDefaultTranslationsObject = {
|
||||
'field-limit-label': 'Límite',
|
||||
'field-locale-label': 'Localidad',
|
||||
'field-name-label': 'Nombre del archivo',
|
||||
'field-page-label': 'Página',
|
||||
'field-selectionToUse-label': 'Selección para usar',
|
||||
'field-sort-label': 'Ordenar por',
|
||||
'selectionToUse-allDocuments': 'Utilice todos los documentos',
|
||||
|
||||
@@ -12,6 +12,7 @@ export const etTranslations: PluginDefaultTranslationsObject = {
|
||||
'field-limit-label': 'Piirang',
|
||||
'field-locale-label': 'Lokaal',
|
||||
'field-name-label': 'Faili nimi',
|
||||
'field-page-label': 'Leht',
|
||||
'field-selectionToUse-label': 'Valiku kasutamine',
|
||||
'field-sort-label': 'Sorteeri järgi',
|
||||
'selectionToUse-allDocuments': 'Kasutage kõiki dokumente',
|
||||
|
||||
@@ -12,6 +12,7 @@ export const faTranslations: PluginDefaultTranslationsObject = {
|
||||
'field-limit-label': 'محدودیت',
|
||||
'field-locale-label': 'محلی',
|
||||
'field-name-label': 'نام فایل',
|
||||
'field-page-label': 'صفحه',
|
||||
'field-selectionToUse-label': 'انتخاب برای استفاده',
|
||||
'field-sort-label': 'مرتب سازی بر اساس',
|
||||
'selectionToUse-allDocuments': 'از تمام مستندات استفاده کنید',
|
||||
|
||||
@@ -12,6 +12,7 @@ export const frTranslations: PluginDefaultTranslationsObject = {
|
||||
'field-limit-label': 'Limite',
|
||||
'field-locale-label': 'Localisation',
|
||||
'field-name-label': 'Nom de fichier',
|
||||
'field-page-label': 'Page',
|
||||
'field-selectionToUse-label': 'Sélection à utiliser',
|
||||
'field-sort-label': 'Trier par',
|
||||
'selectionToUse-allDocuments': 'Utilisez tous les documents',
|
||||
|
||||
@@ -12,6 +12,7 @@ export const heTranslations: PluginDefaultTranslationsObject = {
|
||||
'field-limit-label': 'הגבלה',
|
||||
'field-locale-label': 'מקום',
|
||||
'field-name-label': 'שם הקובץ',
|
||||
'field-page-label': 'עמוד',
|
||||
'field-selectionToUse-label': 'בחירה לשימוש',
|
||||
'field-sort-label': 'מיין לפי',
|
||||
'selectionToUse-allDocuments': 'השתמש בכל המסמכים',
|
||||
|
||||
@@ -12,6 +12,7 @@ export const hrTranslations: PluginDefaultTranslationsObject = {
|
||||
'field-limit-label': 'Ograničenje',
|
||||
'field-locale-label': 'Lokalitet',
|
||||
'field-name-label': 'Naziv datoteke',
|
||||
'field-page-label': 'Stranica',
|
||||
'field-selectionToUse-label': 'Odabir za upotrebu',
|
||||
'field-sort-label': 'Sortiraj po',
|
||||
'selectionToUse-allDocuments': 'Koristite sve dokumente',
|
||||
|
||||
@@ -12,6 +12,7 @@ export const huTranslations: PluginDefaultTranslationsObject = {
|
||||
'field-limit-label': 'Korlát',
|
||||
'field-locale-label': 'Helyszín',
|
||||
'field-name-label': 'Fájlnév',
|
||||
'field-page-label': 'Oldal',
|
||||
'field-selectionToUse-label': 'Használatra kiválasztva',
|
||||
'field-sort-label': 'Rendezés szerint',
|
||||
'selectionToUse-allDocuments': 'Használjon minden dokumentumot',
|
||||
|
||||
@@ -12,6 +12,7 @@ export const hyTranslations: PluginDefaultTranslationsObject = {
|
||||
'field-limit-label': 'Սահմանափակում',
|
||||
'field-locale-label': 'Լոկալ',
|
||||
'field-name-label': 'Ֆայլի անվանումը',
|
||||
'field-page-label': 'Էջ',
|
||||
'field-selectionToUse-label': 'Օգտագործման ընտրություն',
|
||||
'field-sort-label': 'Դասավորել ըստ',
|
||||
'selectionToUse-allDocuments': 'Օգտագործեք բոլոր փաստաթղթերը',
|
||||
|
||||
@@ -12,6 +12,7 @@ export const itTranslations: PluginDefaultTranslationsObject = {
|
||||
'field-limit-label': 'Limite',
|
||||
'field-locale-label': 'Locale',
|
||||
'field-name-label': 'Nome del file',
|
||||
'field-page-label': 'Pagina',
|
||||
'field-selectionToUse-label': 'Selezione da utilizzare',
|
||||
'field-sort-label': 'Ordina per',
|
||||
'selectionToUse-allDocuments': 'Utilizza tutti i documenti',
|
||||
|
||||
@@ -12,6 +12,7 @@ export const jaTranslations: PluginDefaultTranslationsObject = {
|
||||
'field-limit-label': '制限',
|
||||
'field-locale-label': 'ロケール',
|
||||
'field-name-label': 'ファイル名',
|
||||
'field-page-label': 'ページ',
|
||||
'field-selectionToUse-label': '使用する選択',
|
||||
'field-sort-label': '並び替える',
|
||||
'selectionToUse-allDocuments': 'すべての文書を使用してください。',
|
||||
|
||||
@@ -12,6 +12,7 @@ export const koTranslations: PluginDefaultTranslationsObject = {
|
||||
'field-limit-label': '한계',
|
||||
'field-locale-label': '지역',
|
||||
'field-name-label': '파일 이름',
|
||||
'field-page-label': '페이지',
|
||||
'field-selectionToUse-label': '사용할 선택',
|
||||
'field-sort-label': '정렬 방식',
|
||||
'selectionToUse-allDocuments': '모든 문서를 사용하십시오.',
|
||||
|
||||
@@ -12,6 +12,7 @@ export const ltTranslations: PluginDefaultTranslationsObject = {
|
||||
'field-limit-label': 'Ribos',
|
||||
'field-locale-label': 'Lokalė',
|
||||
'field-name-label': 'Failo pavadinimas',
|
||||
'field-page-label': 'Puslapis',
|
||||
'field-selectionToUse-label': 'Naudojimo pasirinkimas',
|
||||
'field-sort-label': 'Rūšiuoti pagal',
|
||||
'selectionToUse-allDocuments': 'Naudokite visus dokumentus.',
|
||||
|
||||
@@ -12,6 +12,7 @@ export const lvTranslations: PluginDefaultTranslationsObject = {
|
||||
'field-limit-label': 'Limits',
|
||||
'field-locale-label': 'Lokalizācija',
|
||||
'field-name-label': 'Faila nosaukums',
|
||||
'field-page-label': 'Lapa',
|
||||
'field-selectionToUse-label': 'Izvēles lietošana',
|
||||
'field-sort-label': 'Kārtot pēc',
|
||||
'selectionToUse-allDocuments': 'Izmantojiet visus dokumentus',
|
||||
|
||||
@@ -12,6 +12,7 @@ export const myTranslations: PluginDefaultTranslationsObject = {
|
||||
'field-limit-label': 'ကန့်သတ်ချက်',
|
||||
'field-locale-label': 'Tempatan',
|
||||
'field-name-label': 'ဖိုင်နာမည်',
|
||||
'field-page-label': 'စာမျက်နှာ',
|
||||
'field-selectionToUse-label': 'Pilihan untuk digunakan',
|
||||
'field-sort-label': 'စီမံအလိုက်',
|
||||
'selectionToUse-allDocuments': 'Gunakan semua dokumen',
|
||||
|
||||
@@ -12,6 +12,7 @@ export const nbTranslations: PluginDefaultTranslationsObject = {
|
||||
'field-limit-label': 'Begrensning',
|
||||
'field-locale-label': 'Lokal',
|
||||
'field-name-label': 'Filnavn',
|
||||
'field-page-label': 'Side',
|
||||
'field-selectionToUse-label': 'Valg til bruk',
|
||||
'field-sort-label': 'Sorter etter',
|
||||
'selectionToUse-allDocuments': 'Bruk alle dokumentene',
|
||||
|
||||
@@ -12,6 +12,7 @@ export const nlTranslations: PluginDefaultTranslationsObject = {
|
||||
'field-limit-label': 'Limiet',
|
||||
'field-locale-label': 'Lokale',
|
||||
'field-name-label': 'Bestandsnaam',
|
||||
'field-page-label': 'Pagina',
|
||||
'field-selectionToUse-label': 'Selectie om te gebruiken',
|
||||
'field-sort-label': 'Sorteer op',
|
||||
'selectionToUse-allDocuments': 'Gebruik alle documenten',
|
||||
|
||||
@@ -12,6 +12,7 @@ export const plTranslations: PluginDefaultTranslationsObject = {
|
||||
'field-limit-label': 'Limit',
|
||||
'field-locale-label': 'Lokalizacja',
|
||||
'field-name-label': 'Nazwa pliku',
|
||||
'field-page-label': 'Strona',
|
||||
'field-selectionToUse-label': 'Wybór do użycia',
|
||||
'field-sort-label': 'Sortuj według',
|
||||
'selectionToUse-allDocuments': 'Użyj wszystkich dokumentów.',
|
||||
|
||||
@@ -12,6 +12,7 @@ export const ptTranslations: PluginDefaultTranslationsObject = {
|
||||
'field-limit-label': 'Limite',
|
||||
'field-locale-label': 'Localização',
|
||||
'field-name-label': 'Nome do arquivo',
|
||||
'field-page-label': 'Página',
|
||||
'field-selectionToUse-label': 'Seleção para usar',
|
||||
'field-sort-label': 'Ordenar por',
|
||||
'selectionToUse-allDocuments': 'Use todos os documentos',
|
||||
|
||||
@@ -12,6 +12,7 @@ export const roTranslations: PluginDefaultTranslationsObject = {
|
||||
'field-limit-label': 'Limită',
|
||||
'field-locale-label': 'Localizare',
|
||||
'field-name-label': 'Numele fișierului',
|
||||
'field-page-label': 'Pagina',
|
||||
'field-selectionToUse-label': 'Selectarea pentru utilizare',
|
||||
'field-sort-label': 'Sortează după',
|
||||
'selectionToUse-allDocuments': 'Utilizați toate documentele.',
|
||||
|
||||
@@ -12,6 +12,7 @@ export const rsTranslations: PluginDefaultTranslationsObject = {
|
||||
'field-limit-label': 'Ograničenje',
|
||||
'field-locale-label': 'Локалитет',
|
||||
'field-name-label': 'Ime datoteke',
|
||||
'field-page-label': 'Strana',
|
||||
'field-selectionToUse-label': 'Izbor za upotrebu',
|
||||
'field-sort-label': 'Sortiraj po',
|
||||
'selectionToUse-allDocuments': 'Koristite sve dokumente',
|
||||
|
||||
@@ -12,6 +12,7 @@ export const rsLatinTranslations: PluginDefaultTranslationsObject = {
|
||||
'field-limit-label': 'Ograničenje',
|
||||
'field-locale-label': 'Lokalitet',
|
||||
'field-name-label': 'Ime datoteke',
|
||||
'field-page-label': 'Strana',
|
||||
'field-selectionToUse-label': 'Izbor za upotrebu',
|
||||
'field-sort-label': 'Sortiraj po',
|
||||
'selectionToUse-allDocuments': 'Koristite sve dokumente',
|
||||
|
||||
@@ -12,6 +12,7 @@ export const ruTranslations: PluginDefaultTranslationsObject = {
|
||||
'field-limit-label': 'Лимит',
|
||||
'field-locale-label': 'Локаль',
|
||||
'field-name-label': 'Имя файла',
|
||||
'field-page-label': 'Страница',
|
||||
'field-selectionToUse-label': 'Выбор использования',
|
||||
'field-sort-label': 'Сортировать по',
|
||||
'selectionToUse-allDocuments': 'Используйте все документы',
|
||||
|
||||
@@ -12,6 +12,7 @@ export const skTranslations: PluginDefaultTranslationsObject = {
|
||||
'field-limit-label': 'Limit',
|
||||
'field-locale-label': 'Lokalita',
|
||||
'field-name-label': 'Názov súboru',
|
||||
'field-page-label': 'Stránka',
|
||||
'field-selectionToUse-label': 'Výber na použitie',
|
||||
'field-sort-label': 'Triediť podľa',
|
||||
'selectionToUse-allDocuments': 'Použite všetky dokumenty',
|
||||
|
||||
@@ -12,6 +12,7 @@ export const slTranslations: PluginDefaultTranslationsObject = {
|
||||
'field-limit-label': 'Omejitev',
|
||||
'field-locale-label': 'Lokalno',
|
||||
'field-name-label': 'Ime datoteke',
|
||||
'field-page-label': 'Stran',
|
||||
'field-selectionToUse-label': 'Izbor za uporabo',
|
||||
'field-sort-label': 'Razvrsti po',
|
||||
'selectionToUse-allDocuments': 'Uporabite vse dokumente',
|
||||
|
||||
@@ -12,6 +12,7 @@ export const svTranslations: PluginDefaultTranslationsObject = {
|
||||
'field-limit-label': 'Begränsning',
|
||||
'field-locale-label': 'Lokal',
|
||||
'field-name-label': 'Filnamn',
|
||||
'field-page-label': 'Sida',
|
||||
'field-selectionToUse-label': 'Val att använda',
|
||||
'field-sort-label': 'Sortera efter',
|
||||
'selectionToUse-allDocuments': 'Använd alla dokument',
|
||||
|
||||
@@ -12,6 +12,7 @@ export const thTranslations: PluginDefaultTranslationsObject = {
|
||||
'field-limit-label': 'จำกัด',
|
||||
'field-locale-label': 'ที่ตั้ง',
|
||||
'field-name-label': 'ชื่อไฟล์',
|
||||
'field-page-label': 'หน้า',
|
||||
'field-selectionToUse-label': 'การเลือกใช้',
|
||||
'field-sort-label': 'เรียงตาม',
|
||||
'selectionToUse-allDocuments': 'ใช้เอกสารทั้งหมด',
|
||||
|
||||
@@ -12,6 +12,7 @@ export const trTranslations: PluginDefaultTranslationsObject = {
|
||||
'field-limit-label': 'Sınır',
|
||||
'field-locale-label': 'Yerel',
|
||||
'field-name-label': 'Dosya adı',
|
||||
'field-page-label': 'Sayfa',
|
||||
'field-selectionToUse-label': 'Kullanılacak seçim',
|
||||
'field-sort-label': 'Sırala',
|
||||
'selectionToUse-allDocuments': 'Tüm belgeleri kullanın',
|
||||
|
||||
@@ -49,6 +49,9 @@
|
||||
"field-name-label": {
|
||||
"type": "string"
|
||||
},
|
||||
"field-page-label": {
|
||||
"type": "string"
|
||||
},
|
||||
"field-selectionToUse-label": {
|
||||
"type": "string"
|
||||
},
|
||||
|
||||
@@ -12,6 +12,7 @@ export const ukTranslations: PluginDefaultTranslationsObject = {
|
||||
'field-limit-label': 'Обмеження',
|
||||
'field-locale-label': 'Локалізація',
|
||||
'field-name-label': 'Назва файлу',
|
||||
'field-page-label': 'Сторінка',
|
||||
'field-selectionToUse-label': 'Вибір для використання',
|
||||
'field-sort-label': 'Сортувати за',
|
||||
'selectionToUse-allDocuments': 'Використовуйте всі документи',
|
||||
|
||||
@@ -12,6 +12,7 @@ export const viTranslations: PluginDefaultTranslationsObject = {
|
||||
'field-limit-label': 'Giới hạn',
|
||||
'field-locale-label': 'Địa phương',
|
||||
'field-name-label': 'Tên tệp',
|
||||
'field-page-label': 'Trang',
|
||||
'field-selectionToUse-label': 'Lựa chọn để sử dụng',
|
||||
'field-sort-label': 'Sắp xếp theo',
|
||||
'selectionToUse-allDocuments': 'Sử dụng tất cả các tài liệu',
|
||||
|
||||
@@ -12,6 +12,7 @@ export const zhTranslations: PluginDefaultTranslationsObject = {
|
||||
'field-limit-label': '限制',
|
||||
'field-locale-label': '语言环境',
|
||||
'field-name-label': '文件名',
|
||||
'field-page-label': '页面',
|
||||
'field-selectionToUse-label': '选择范围',
|
||||
'field-sort-label': '排序方式',
|
||||
'selectionToUse-allDocuments': '使用所有文档',
|
||||
|
||||
@@ -12,6 +12,7 @@ export const zhTwTranslations: PluginDefaultTranslationsObject = {
|
||||
'field-limit-label': '筆數上限',
|
||||
'field-locale-label': '語言地區',
|
||||
'field-name-label': '檔案名稱',
|
||||
'field-page-label': '頁面',
|
||||
'field-selectionToUse-label': '使用的選取範圍',
|
||||
'field-sort-label': '排序方式',
|
||||
'selectionToUse-allDocuments': '使用所有文件',
|
||||
|
||||
@@ -13,6 +13,7 @@ export type PluginLanguage = Language<{
|
||||
'field-limit-label': string
|
||||
'field-locale-label': string
|
||||
'field-name-label': string
|
||||
'field-page-label': string
|
||||
'field-selectionToUse-label': string
|
||||
'field-sort-label': string
|
||||
'selectionToUse-allDocuments': string
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
import type { TFunction } from '@payloadcms/translations'
|
||||
|
||||
export const validateLimitValue = (
|
||||
value: null | number | undefined,
|
||||
t: TFunction,
|
||||
step = 100,
|
||||
): string | undefined => {
|
||||
if (value && value < 0) {
|
||||
return t('validation:lessThanMin', { label: t('general:value'), min: 0, value })
|
||||
}
|
||||
|
||||
if (value && value % step !== 0) {
|
||||
return `Limit must be a multiple of ${step}`
|
||||
}
|
||||
|
||||
return undefined
|
||||
}
|
||||
Reference in New Issue
Block a user