Compare commits

..

14 Commits

Author SHA1 Message Date
Jacob Fletcher
dd9763d162 regenerate types 2025-05-20 14:06:44 -04:00
Jacob Fletcher
5400703c98 fix docs 2025-05-20 14:05:19 -04:00
Jacob Fletcher
890c03a2e0 adds additional test 2025-05-20 14:03:22 -04:00
Jacob Fletcher
64f6011ba4 prevents non-admins from changing admin role 2025-05-20 13:55:02 -04:00
Jacob Fletcher
8c39eeb821 negligence 2025-05-20 13:19:26 -04:00
Jacob Fletcher
6665a016ec fix onlyAdmins role 2025-05-20 12:44:00 -04:00
Jacob Fletcher
e234cad0d5 cleanup 2025-05-20 12:33:38 -04:00
Jacob Fletcher
8e642f5f6a feat: custom query preset hooks 2025-05-20 12:29:42 -04:00
Patrik
1235a183ff fix: prevent resizing of original file with withoutEnlargement on update (#12291)
This PR updates `generateFileData` to skip applying `resizeOptions`
after updating an image if `resizeOptions.withoutEnlargement` is `true`
and the original image size is smaller than the dimensions defined in
`resizeOptions`.

This prevents unintended re-resizing of already resized images when
updating or modifying metadata without uploading a new file.

This change ensures that:

- Resizing is skipped if withoutEnlargement: true

- Resizing still occurs if withoutEnlargement: false or unset

This resolves an issue where images were being resized again
unnecessarily when updating an upload.

Fixes #12280
2025-05-20 06:43:53 -07:00
Sasha
81d333f4b0 test: add test for sorting by a virtual field with a reference (#12351) 2025-05-20 13:07:48 +00:00
Sasha
4fe3423e54 fix(plugin-multi-tenant): multi-locale tenant select label (#12444)
fixes https://github.com/payloadcms/payload/issues/12443
2025-05-20 05:02:47 -07:00
Paul
e8c2b15e2b fix(plugin-multi-tenant): add missing translation for Assigned Tenant field (#12448)
Previously the "Assigned Tenant" field didn't have a translated label
2025-05-19 13:20:12 -07:00
Paul
3127d6ad6d fix(plugin-import-export): add translations for all UI elements and fields (#12449)
Converts all text and field labels into variables that can be
translated. Also generated the translations for them

So now the UI here is internationalised


![image](https://github.com/user-attachments/assets/40d7c010-ac58-4cd7-8786-01b3de3cabb7)

I've also moved some of the generic labels into the core package since
those could be re-used elsewhere
2025-05-19 13:19:55 -07:00
Paul
72ab319d37 fix(db-*): ensure consistent sorting even when sorting on non-unique fields or no sort parameters at all (#12447)
The databases do not keep track of document order internally so when
sorting by non-unique fields such as shared `order` number values, the
returned order will be random and not consistent.

While this issue is far more noticeable on mongo it could also occur in
postgres on certain environments.

This combined with pagination can lead to the perception of duplicated
or inconsistent data.

This PR adds a second sort parameter to queries so that we always have a
fallback, `-createdAt` will be used by default or `id` if timestamps are
disabled.
2025-05-19 12:59:12 -07:00
407 changed files with 4446 additions and 12907 deletions

7
.vscode/launch.json vendored
View File

@@ -118,13 +118,6 @@
"request": "launch",
"type": "node-terminal"
},
{
"command": "pnpm tsx --no-deprecation test/dev.ts folder-view",
"cwd": "${workspaceFolder}",
"name": "Run Dev Folder View",
"request": "launch",
"type": "node-terminal"
},
{
"command": "pnpm tsx --no-deprecation test/dev.ts localization",
"cwd": "${workspaceFolder}",

View File

@@ -81,7 +81,7 @@ To install a Database Adapter, you can run **one** of the following commands:
#### 2. Copy Payload files into your Next.js app folder
Payload installs directly in your Next.js `/app` folder, and you'll need to place some files into that folder for Payload to run. You can copy these files from the [Blank Template](<https://github.com/payloadcms/payload/tree/main/templates/blank/src/app/(payload)>) on GitHub. Once you have the required Payload files in place in your `/app` folder, you should have something like this:
Payload installs directly in your Next.js `/app` folder, and you'll need to place some files into that folder for Payload to run. You can copy these files from the [Blank Template](https://github.com/payloadcms/payload/tree/main/templates/blank/src/app/(payload)) on GitHub. Once you have the required Payload files in place in your `/app` folder, you should have something like this:
```plaintext
app/

View File

@@ -50,6 +50,7 @@ The following options are available for Query Presets:
| ------------- | ------------------------------------------------------------------------------------------------------------------------------- |
| `access` | Used to define custom collection-level access control that applies to all presets. [More details](#access-control). |
| `constraints` | Used to define custom document-level access control that apply to individual presets. [More details](#document-access-control). |
| `hooks` | Used to add custom hooks to the query presets collection. [More details](#hooks). |
| `labels` | Custom labels to use for the Query Presets collection. |
## Access Control
@@ -59,7 +60,7 @@ Query Presets are subject to the same [Access Control](../access-control/overvie
Access Control for Query Presets can be customized in two ways:
1. [Collection Access Control](#collection-access-control): Applies to all presets. These rules are not controllable by the user and are statically defined in the config.
2. [Document Access Control](#document-access-control): Applies to each individual preset. These rules are controllable by the user and are saved to the document.
2. [Document Access Control](#document-access-control): Applies to each individual preset. These rules are controllable by the user and are dynamically defined on each record in the database.
### Collection Access Control
@@ -97,7 +98,7 @@ This example restricts all Query Presets to users with the role of `admin`.
### Document Access Control
You can also define access control rules that apply to each specific preset. Users have the ability to define and modify these rules on the fly as they manage presets. These are saved dynamically in the database on each document.
You can also define access control rules that apply to each specific preset. Users have the ability to define and modify these rules on the fly as they manage presets. These are saved dynamically in the database on each record.
When a user manages a preset, document-level access control options will be available to them in the Admin Panel for each operation.
@@ -150,8 +151,8 @@ const config = buildConfig({
}),
},
],
// highlight-end
},
// highlight-end
},
})
```
@@ -171,3 +172,65 @@ The following options are available for each constraint:
| `value` | The value to store in the database when this constraint is selected. |
| `fields` | An array of fields to render when this constraint is selected. |
| `access` | A function that determines the access control rules for this constraint. |
## Hooks
You can attach your own [Hooks](../hooks/overview) to the Query Presets collection. This can be useful for defining custom behavior when a preset is created, updated, or deleted.
For example, you may want to add additional access control rules that prevent certain users from creating or updating presets in a certain way.
To do this, you can use the `hooks` property in your [Payload Config](../configuration/overview):
```ts
import { buildConfig } from 'payload'
const config = buildConfig({
// ...
queryPresets: {
// ...
// highlight-start
hooks: {
beforeValidate: [
// this is a custom `beforeValidate` hook that runs before the preset is validated
// it ensures that only admins can add or remove the "admin" role from a preset
({ data, req, originalDoc }) => {
const adminRoleChanged = (current, original) => {
const currentHasAdmin = current?.roles?.includes('admin') ?? false
const originalHasAdmin = original?.roles?.includes('admin') ?? false
return currentHasAdmin !== originalHasAdmin
}
const readChanged =
data?.access?.read?.constraint === 'specificRoles' &&
adminRoleChanged(
data?.access?.read,
originalDoc?.access?.read || {},
)
const updateChanged =
data?.access?.update?.constraint === 'specificRoles' &&
adminRoleChanged(
data?.access?.update,
originalDoc?.access?.update || {},
)
if (
(readChanged || updateChanged) &&
!req.user?.roles?.includes('admin')
) {
throw new APIError(
'You must be an admin to add or remove the admin role from a preset',
403,
{},
true,
)
}
return data
},
],
},
// highlight-end
},
})
```

View File

@@ -150,6 +150,18 @@ export const buildSortParam = ({
sort = [sort]
}
// In the case of Mongo, when sorting by a field that is not unique, the results are not guaranteed to be in the same order each time.
// So we add a fallback sort to ensure that the results are always in the same order.
let fallbackSort = '-id'
if (timestamps) {
fallbackSort = '-createdAt'
}
if (!(sort.includes(fallbackSort) || sort.includes(fallbackSort.replace('-', '')))) {
sort.push(fallbackSort)
}
const sorting = sort.reduce<Record<string, string>>((acc, item) => {
let sortProperty: string
let sortDirection: SortDirection

View File

@@ -39,8 +39,9 @@ export const buildOrderBy = ({
}: Args): BuildQueryResult['orderBy'] => {
const orderBy: BuildQueryResult['orderBy'] = []
const createdAt = adapter.tables[tableName]?.createdAt
if (!sort) {
const createdAt = adapter.tables[tableName]?.createdAt
if (createdAt) {
sort = '-createdAt'
} else {
@@ -52,6 +53,18 @@ export const buildOrderBy = ({
sort = [sort]
}
// In the case of Mongo, when sorting by a field that is not unique, the results are not guaranteed to be in the same order each time.
// So we add a fallback sort to ensure that the results are always in the same order.
let fallbackSort = '-id'
if (createdAt) {
fallbackSort = '-createdAt'
}
if (!(sort.includes(fallbackSort) || sort.includes(fallbackSort.replace('-', '')))) {
sort.push(fallbackSort)
}
for (const sortItem of sort) {
let sortProperty: string
let sortDirection: 'asc' | 'desc'

View File

@@ -1,7 +1,7 @@
'use client'
import type { SanitizedConfig } from 'payload'
import { Button } from '@payloadcms/ui'
import { Link } from '@payloadcms/ui'
import { useParams, usePathname, useSearchParams } from 'next/navigation.js'
import { formatAdminURL } from 'payload/shared'
import React from 'react'
@@ -13,6 +13,7 @@ export const DocumentTabLink: React.FC<{
children?: React.ReactNode
href: string
isActive?: boolean
isCollection?: boolean
newTab?: boolean
}> = ({
adminRoute,
@@ -53,17 +54,19 @@ export const DocumentTabLink: React.FC<{
isActiveFromProps
return (
<Button
<li
aria-label={ariaLabel}
buttonStyle="tab"
className={[baseClass, isActive && `${baseClass}--active`].filter(Boolean).join(' ')}
disabled={isActive}
el={!isActive || href !== pathname ? 'link' : 'div'}
newTab={newTab}
size="medium"
to={!isActive || href !== pathname ? hrefWithLocale : undefined}
>
{children}
</Button>
<Link
className={`${baseClass}__link`}
href={!isActive || href !== pathname ? hrefWithLocale : ''}
prefetch={false}
{...(newTab && { rel: 'noopener noreferrer', target: '_blank' })}
tabIndex={isActive ? -1 : 0}
>
{children}
</Link>
</li>
)
}

View File

@@ -1,24 +1,74 @@
@import '../../../../scss/styles.scss';
@layer payload-default {
.doc-tab {
display: flex;
justify-content: center;
align-items: center;
white-space: nowrap;
@extend %h5;
position: relative;
&__link {
text-decoration: none;
display: flex;
justify-content: center;
align-items: center;
white-space: nowrap;
// Use a pseudo element for the accessability so that it doesn't take up DOM space
// Also because the parent element has `overflow: hidden` which would clip an outline
&:focus-visible::after {
content: '';
border: var(--accessibility-outline);
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
pointer-events: none;
}
}
&:focus:not(:focus-visible) {
opacity: 1;
}
&::before {
content: '';
display: block;
position: absolute;
width: 100%;
height: 100%;
border-radius: var(--style-radius-s);
background-color: var(--theme-elevation-50);
opacity: 0;
}
&:hover {
.pill-version-count {
&::before {
opacity: 1;
}
.doc-tab__count {
background-color: var(--theme-elevation-150);
}
}
&--active {
.pill-version-count {
background-color: var(--theme-elevation-250);
font-weight: 600;
&::before {
opacity: 1;
background-color: var(--theme-elevation-100);
}
.doc-tab {
&__count {
background-color: var(--theme-elevation-250);
}
}
&:hover {
.pill-version-count {
background-color: var(--theme-elevation-250);
.doc-tab {
&__count {
background-color: var(--theme-elevation-250);
}
}
}
}
@@ -30,7 +80,16 @@
gap: 4px;
width: 100%;
height: 100%;
line-height: calc(var(--base) * 1.2);
line-height: base(1.2);
padding: base(0.2) base(0.6);
}
&__count {
line-height: base(0.8);
min-width: base(0.8);
text-align: center;
background-color: var(--theme-elevation-100);
border-radius: var(--style-radius-s);
}
}
}

View File

@@ -68,6 +68,7 @@ export const DocumentTab: React.FC<
baseClass={baseClass}
href={href}
isActive={isActive}
isCollection={!!collectionConfig && !globalConfig}
newTab={newTab}
>
<span className={`${baseClass}__label`}>

View File

@@ -1,9 +0,0 @@
@layer payload-default {
.pill-version-count {
line-height: calc(var(--base) * 0.8);
min-width: calc(var(--base) * 0.8);
text-align: center;
background-color: var(--theme-elevation-100);
border-radius: var(--style-radius-s);
}
}

View File

@@ -2,9 +2,7 @@
import { useDocumentInfo } from '@payloadcms/ui'
import React from 'react'
import './index.scss'
const baseClass = 'pill-version-count'
import { baseClass } from '../../Tab/index.js'
export const VersionsPill: React.FC = () => {
const { versionCount } = useDocumentInfo()
@@ -13,5 +11,5 @@ export const VersionsPill: React.FC = () => {
return null
}
return <span className={baseClass}>{versionCount}</span>
return <span className={`${baseClass}__count`}>{versionCount}</span>
}

View File

@@ -4,7 +4,7 @@ import type { groupNavItems } from '@payloadcms/ui/shared'
import type { NavPreferences } from 'payload'
import { getTranslation } from '@payloadcms/translations'
import { BrowseByFolderButton, Link, NavGroup, useConfig, useTranslation } from '@payloadcms/ui'
import { Link, NavGroup, useConfig, useTranslation } from '@payloadcms/ui'
import { EntityType } from '@payloadcms/ui/shared'
import { usePathname } from 'next/navigation.js'
import { formatAdminURL } from 'payload/shared'
@@ -20,28 +20,14 @@ export const DefaultNavClient: React.FC<{
const {
config: {
admin: {
routes: { folders: foldersRoute },
},
folders: { collections: folderCollections = {}, enabled } = {},
routes: { admin: adminRoute },
},
} = useConfig()
const { i18n } = useTranslation()
const folderURL = formatAdminURL({
adminRoute,
path: foldersRoute,
})
const viewingRootFolderView = pathname.startsWith(folderURL)
return (
<Fragment>
{enabled && Object.keys(folderCollections).length > 0 && (
<BrowseByFolderButton active={viewingRootFolderView} />
)}
{groups.map(({ entities, label }, key) => {
return (
<NavGroup isOpen={navPreferences?.groups?.[label]?.open} key={key} label={label}>

View File

@@ -1,202 +0,0 @@
import type {
AdminViewServerProps,
BuildCollectionFolderViewResult,
FolderListViewServerPropsOnly,
ListQuery,
Where,
} from 'payload'
import { DefaultCollectionFolderView, FolderProvider, HydrateAuthProvider } from '@payloadcms/ui'
import { RenderServerComponent } from '@payloadcms/ui/elements/RenderServerComponent'
import { formatAdminURL, mergeListSearchAndWhere } from '@payloadcms/ui/shared'
import { redirect } from 'next/navigation.js'
import { getFolderData, parseDocumentID } from 'payload'
import React from 'react'
// import { renderFolderViewSlots } from './renderFolderViewSlots.js'
export type BuildCollectionFolderViewStateArgs = {
disableBulkDelete?: boolean
disableBulkEdit?: boolean
enableRowSelections: boolean
folderID?: number | string
isInDrawer?: boolean
overrideEntityVisibility?: boolean
query: ListQuery
} & AdminViewServerProps
/**
* Builds the entire view for collection-folder views on the server
*/
export const buildCollectionFolderView = async (
args: BuildCollectionFolderViewStateArgs,
): Promise<BuildCollectionFolderViewResult> => {
const {
disableBulkDelete,
disableBulkEdit,
enableRowSelections,
folderID,
initPageResult,
isInDrawer,
overrideEntityVisibility,
params,
query: queryFromArgs,
searchParams,
} = args
const {
collectionConfig,
collectionConfig: { slug: collectionSlug },
locale: fullLocale,
permissions,
req: {
i18n,
payload,
payload: { config },
query: queryFromReq,
user,
},
visibleEntities,
} = initPageResult
if (!permissions?.collections?.[collectionSlug]?.read) {
throw new Error('not-found')
}
if (collectionConfig) {
const query = queryFromArgs || queryFromReq
// const collectionFolderPreferences = await upsertPreferences<ListPreferences>({
// key: `${collectionSlug}-collection-folder`,
// req,
// value: {
// },
// })
const {
routes: { admin: adminRoute },
} = config
if (
(!visibleEntities.collections.includes(collectionSlug) && !overrideEntityVisibility) ||
!Object.keys(config.folders.collections).includes(collectionSlug)
) {
throw new Error('not-found')
}
const whereConstraints = [
mergeListSearchAndWhere({
collectionConfig,
search: typeof query?.search === 'string' ? query.search : undefined,
where: (query?.where as Where) || undefined,
}),
]
if (folderID) {
whereConstraints.push({
_folder: {
equals: parseDocumentID({ id: folderID, collectionSlug, payload }),
},
})
} else {
whereConstraints.push({
_folder: {
exists: false,
},
})
}
const { breadcrumbs, documents, subfolders } = await getFolderData({
collectionSlug,
folderID,
payload: initPageResult.req.payload,
search: query?.search as string,
user: initPageResult.req.user,
})
const resolvedFolderID = breadcrumbs[breadcrumbs.length - 1]?.id
if (
!isInDrawer &&
((resolvedFolderID && folderID && folderID !== String(resolvedFolderID)) ||
(folderID && !resolvedFolderID))
) {
return redirect(
formatAdminURL({
adminRoute,
path: `/collections/${collectionSlug}/folders`,
serverURL: config.serverURL,
}),
)
}
const newDocumentURL = formatAdminURL({
adminRoute,
path: `/collections/${collectionSlug}/create`,
})
const hasCreatePermission = permissions?.collections?.[collectionSlug]?.create
const serverProps: FolderListViewServerPropsOnly = {
collectionConfig,
documents,
i18n,
locale: fullLocale,
params,
payload,
permissions,
searchParams,
subfolders,
user,
}
// We could support slots in the folder view in the future
// const folderViewSlots = renderFolderViewSlots({
// clientProps: {
// collectionSlug,
// hasCreatePermission,
// newDocumentURL,
// },
// collectionConfig,
// description: typeof collectionConfig.admin.description === 'function'
// ? collectionConfig.admin.description({ t: i18n.t })
// : collectionConfig.admin.description,
// payload,
// serverProps,
// })
const search = query?.search as string
return {
View: (
<FolderProvider
breadcrumbs={breadcrumbs}
collectionSlug={collectionSlug}
documents={documents}
folderID={folderID}
search={search}
subfolders={subfolders}
>
<HydrateAuthProvider permissions={permissions} />
{RenderServerComponent({
clientProps: {
// ...folderViewSlots,
collectionSlug,
disableBulkDelete,
disableBulkEdit,
enableRowSelections,
hasCreatePermission,
newDocumentURL,
},
Component: collectionConfig?.admin?.components?.views?.list?.Component,
Fallback: DefaultCollectionFolderView,
importMap: payload.importMap,
serverProps,
})}
</FolderProvider>
),
}
}
throw new Error('not-found')
}

View File

@@ -1,20 +0,0 @@
import type React from 'react'
import { notFound } from 'next/navigation.js'
import type { BuildCollectionFolderViewStateArgs } from './buildView.js'
import { buildCollectionFolderView } from './buildView.js'
export const CollectionFolderView: React.FC<BuildCollectionFolderViewStateArgs> = async (args) => {
try {
const { View } = await buildCollectionFolderView(args)
return View
} catch (error) {
if (error.message === 'not-found') {
notFound()
} else {
console.error(error) // eslint-disable-line no-console
}
}
}

View File

@@ -1,35 +0,0 @@
import type { Metadata } from 'next'
import type { SanitizedCollectionConfig } from 'payload'
import { getTranslation } from '@payloadcms/translations'
import type { GenerateViewMetadata } from '../Root/index.js'
import { generateMetadata } from '../../utilities/meta.js'
export const generateCollectionFolderMetadata = async (
args: {
collectionConfig: SanitizedCollectionConfig
} & Parameters<GenerateViewMetadata>[0],
): Promise<Metadata> => {
const { collectionConfig, config, i18n } = args
let title: string = ''
const description: string = ''
const keywords: string = ''
if (collectionConfig) {
title = getTranslation(collectionConfig.labels.singular, i18n)
}
title = `${title ? `${title} ` : title}${i18n.t('folder:folders')}`
return generateMetadata({
...(config.admin.meta || {}),
description,
keywords,
serverURL: config.serverURL,
title,
...(collectionConfig?.admin?.meta || {}),
})
}

View File

@@ -1,99 +0,0 @@
import type {
AfterFolderListClientProps,
AfterFolderListTableClientProps,
AfterFolderListTableServerPropsOnly,
BeforeFolderListClientProps,
BeforeFolderListServerPropsOnly,
BeforeFolderListTableClientProps,
BeforeFolderListTableServerPropsOnly,
FolderListViewServerPropsOnly,
FolderListViewSlots,
ListViewSlotSharedClientProps,
Payload,
SanitizedCollectionConfig,
StaticDescription,
ViewDescriptionClientProps,
ViewDescriptionServerPropsOnly,
} from 'payload'
import { RenderServerComponent } from '@payloadcms/ui/elements/RenderServerComponent'
type Args = {
clientProps: ListViewSlotSharedClientProps
collectionConfig: SanitizedCollectionConfig
description?: StaticDescription
payload: Payload
serverProps: FolderListViewServerPropsOnly
}
export const renderFolderViewSlots = ({
clientProps,
collectionConfig,
description,
payload,
serverProps,
}: Args): FolderListViewSlots => {
const result: FolderListViewSlots = {} as FolderListViewSlots
if (collectionConfig.admin.components?.afterList) {
result.AfterFolderList = RenderServerComponent({
clientProps: clientProps satisfies AfterFolderListClientProps,
Component: collectionConfig.admin.components.afterList,
importMap: payload.importMap,
serverProps: serverProps satisfies AfterFolderListTableServerPropsOnly,
})
}
const listMenuItems = collectionConfig.admin.components?.listMenuItems
if (Array.isArray(listMenuItems)) {
result.listMenuItems = [
RenderServerComponent({
clientProps,
Component: listMenuItems,
importMap: payload.importMap,
serverProps,
}),
]
}
if (collectionConfig.admin.components?.afterListTable) {
result.AfterFolderListTable = RenderServerComponent({
clientProps: clientProps satisfies AfterFolderListTableClientProps,
Component: collectionConfig.admin.components.afterListTable,
importMap: payload.importMap,
serverProps: serverProps satisfies AfterFolderListTableServerPropsOnly,
})
}
if (collectionConfig.admin.components?.beforeList) {
result.BeforeFolderList = RenderServerComponent({
clientProps: clientProps satisfies BeforeFolderListClientProps,
Component: collectionConfig.admin.components.beforeList,
importMap: payload.importMap,
serverProps: serverProps satisfies BeforeFolderListServerPropsOnly,
})
}
if (collectionConfig.admin.components?.beforeListTable) {
result.BeforeFolderListTable = RenderServerComponent({
clientProps: clientProps satisfies BeforeFolderListTableClientProps,
Component: collectionConfig.admin.components.beforeListTable,
importMap: payload.importMap,
serverProps: serverProps satisfies BeforeFolderListTableServerPropsOnly,
})
}
if (collectionConfig.admin.components?.Description) {
result.Description = RenderServerComponent({
clientProps: {
collectionSlug: collectionConfig.slug,
description,
} satisfies ViewDescriptionClientProps,
Component: collectionConfig.admin.components.Description,
importMap: payload.importMap,
serverProps: serverProps satisfies ViewDescriptionServerPropsOnly,
})
}
return result
}

View File

@@ -32,12 +32,9 @@ import { renderDocumentSlots } from './renderDocumentSlots.js'
export const generateMetadata: GenerateEditViewMetadata = async (args) => getMetaBySegment(args)
/**
* This function is responsible for rendering
* an Edit Document view on the server for both:
* - default document edit views
* - on-demand edit views within drawers
*/
// This function will be responsible for rendering an Edit Document view
// it will be called on the server for Edit page views as well as
// called on-demand from document drawers
export const renderDocument = async ({
disableActions,
documentSubViewType,

View File

@@ -1,152 +0,0 @@
import type {
AdminViewServerProps,
BuildCollectionFolderViewResult,
FolderListViewServerPropsOnly,
ListQuery,
} from 'payload'
import { DefaultFolderView, FolderProvider, HydrateAuthProvider } from '@payloadcms/ui'
import { RenderServerComponent } from '@payloadcms/ui/elements/RenderServerComponent'
import { formatAdminURL } from '@payloadcms/ui/shared'
import { redirect } from 'next/navigation.js'
import { getFolderData } from 'payload'
import React from 'react'
export type BuildFolderViewArgs = {
customCellProps?: Record<string, any>
disableBulkDelete?: boolean
disableBulkEdit?: boolean
enableRowSelections: boolean
folderID?: number | string
isInDrawer?: boolean
overrideEntityVisibility?: boolean
query: ListQuery
} & AdminViewServerProps
export const buildFolderView = async (
args: BuildFolderViewArgs,
): Promise<BuildCollectionFolderViewResult> => {
const {
disableBulkDelete,
disableBulkEdit,
enableRowSelections,
folderID,
initPageResult,
isInDrawer,
params,
query: queryFromArgs,
searchParams,
} = args
const {
locale: fullLocale,
permissions,
req: {
i18n,
payload,
payload: { config },
query: queryFromReq,
user,
},
visibleEntities,
} = initPageResult
const allFolderCollectionSlugs = Object.keys(config?.folders?.collections || {})
const collections = allFolderCollectionSlugs.filter(
(collectionSlug) =>
permissions?.collections?.[collectionSlug]?.read &&
visibleEntities.collections.includes(collectionSlug),
)
if (!collections.length) {
throw new Error('not-found')
}
const query = queryFromArgs || queryFromReq
// get relationTo filter from query params
const selectedCollectionSlugs: string[] =
Array.isArray(query?.relationTo) && query.relationTo.length
? query.relationTo
: [...allFolderCollectionSlugs, config.folders.slug]
const {
routes: { admin: adminRoute },
} = config
const { breadcrumbs, documents, subfolders } = await getFolderData({
folderID,
payload: initPageResult.req.payload,
search: query?.search as string,
user: initPageResult.req.user,
})
const resolvedFolderID = breadcrumbs[breadcrumbs.length - 1]?.id
if (
!isInDrawer &&
((resolvedFolderID && folderID && folderID !== String(resolvedFolderID)) ||
(folderID && !resolvedFolderID))
) {
return redirect(
formatAdminURL({
adminRoute,
path: config.admin.routes.folders,
serverURL: config.serverURL,
}),
)
}
const serverProps: Omit<FolderListViewServerPropsOnly, 'collectionConfig' | 'listPreferences'> = {
documents,
i18n,
locale: fullLocale,
params,
payload,
permissions,
searchParams,
subfolders,
user,
}
// const folderViewSlots = renderFolderViewSlots({
// clientProps: {
// },
// description: staticDescription,
// payload,
// serverProps,
// })
// documents cannot be created without a parent folder in this view
const hasCreatePermissionCollectionSlugs = folderID
? [config.folders.slug, ...allFolderCollectionSlugs]
: [config.folders.slug]
return {
View: (
<FolderProvider
breadcrumbs={breadcrumbs}
documents={documents}
filteredCollectionSlugs={selectedCollectionSlugs}
folderID={folderID}
subfolders={subfolders}
>
<HydrateAuthProvider permissions={permissions} />
{RenderServerComponent({
clientProps: {
// ...folderViewSlots,
disableBulkDelete,
disableBulkEdit,
enableRowSelections,
hasCreatePermissionCollectionSlugs,
selectedCollectionSlugs,
},
// Component:config.folders?.components?.views?.list?.Component,
Fallback: DefaultFolderView,
importMap: payload.importMap,
serverProps,
})}
</FolderProvider>
),
}
}

View File

@@ -1,20 +0,0 @@
import type React from 'react'
import { notFound } from 'next/navigation.js'
import type { BuildFolderViewArgs } from './buildView.js'
import { buildFolderView } from './buildView.js'
export const FolderView: React.FC<BuildFolderViewArgs> = async (args) => {
try {
const { View } = await buildFolderView(args)
return View
} catch (error) {
if (error.message === 'not-found') {
notFound()
} else {
console.error(error) // eslint-disable-line no-console
}
}
}

View File

@@ -1,23 +0,0 @@
import type { Metadata } from 'next'
import type { GenerateViewMetadata } from '../Root/index.js'
import { generateMetadata } from '../../utilities/meta.js'
export const generateBrowseByFolderMetadata = async (
args: Parameters<GenerateViewMetadata>[0],
): Promise<Metadata> => {
const { config, i18n } = args
const title: string = i18n.t('folder:browseByFolder')
const description: string = ''
const keywords: string = ''
return generateMetadata({
...(config.admin.meta || {}),
description,
keywords,
serverURL: config.serverURL,
title,
})
}

View File

@@ -40,12 +40,6 @@ type RenderListViewArgs = {
redirectAfterDuplicate?: boolean
} & AdminViewServerProps
/**
* This function is responsible for rendering
* the list view on the server for both:
* - default list view
* - list view within drawers
*/
export const renderListView = async (
args: RenderListViewArgs,
): Promise<{

View File

@@ -1,6 +1,6 @@
import type { AdminViewConfig, SanitizedConfig } from 'payload'
import type { ViewFromConfig } from './getRouteData.js'
import type { ViewFromConfig } from './getViewFromConfig.js'
import { isPathMatchingRoute } from './isPathMatchingRoute.js'

View File

@@ -14,11 +14,9 @@ import { formatAdminURL } from 'payload/shared'
import type { initPage } from '../../utilities/initPage/index.js'
import { Account } from '../Account/index.js'
import { CollectionFolderView } from '../CollectionFolders/index.js'
import { CreateFirstUserView } from '../CreateFirstUser/index.js'
import { Dashboard } from '../Dashboard/index.js'
import { Document as DocumentView } from '../Document/index.js'
import { FolderView } from '../Folders/index.js'
import { forgotPasswordBaseClass, ForgotPasswordView } from '../ForgotPassword/index.js'
import { ListView } from '../List/index.js'
import { loginBaseClass, LoginView } from '../Login/index.js'
@@ -33,7 +31,6 @@ import { isPathMatchingRoute } from './isPathMatchingRoute.js'
const baseClasses = {
account: 'account',
folders: 'folders',
forgot: forgotPasswordBaseClass,
login: loginBaseClass,
reset: resetPasswordBaseClass,
@@ -52,7 +49,6 @@ export type ViewFromConfig = {
const oneSegmentViews: OneSegmentViews = {
account: Account,
createFirstUser: CreateFirstUserView,
folders: FolderView,
forgot: ForgotPasswordView,
inactivity: LogoutInactivity,
login: LoginView,
@@ -60,7 +56,7 @@ const oneSegmentViews: OneSegmentViews = {
unauthorized: UnauthorizedView,
}
type GetRouteDataArgs = {
type GetViewFromConfigArgs = {
adminRoute: string
config: SanitizedConfig
currentRoute: string
@@ -71,10 +67,9 @@ type GetRouteDataArgs = {
segments: string[]
}
type GetRouteDataResult = {
type GetViewFromConfigResult = {
DefaultView: ViewFromConfig
documentSubViewType?: DocumentSubViewTypes
folderID?: string
initPageOptions: Parameters<typeof initPage>[0]
serverProps: ServerPropsFromView
templateClassName: string
@@ -82,20 +77,19 @@ type GetRouteDataResult = {
viewType?: ViewTypes
}
export const getRouteData = ({
export const getViewFromConfig = ({
adminRoute,
config,
currentRoute,
importMap,
searchParams,
segments,
}: GetRouteDataArgs): GetRouteDataResult => {
}: GetViewFromConfigArgs): GetViewFromConfigResult => {
let ViewToRender: ViewFromConfig = null
let templateClassName: string
let templateType: 'default' | 'minimal' | undefined
let documentSubViewType: DocumentSubViewTypes
let viewType: ViewTypes
let folderID: string
const initPageOptions: Parameters<typeof initPage>[0] = {
config,
@@ -110,7 +104,6 @@ export const getRouteData = ({
const isCollection = segmentOne === 'collections'
let matchedCollection: SanitizedConfig['collections'][number] = undefined
let matchedGlobal: SanitizedConfig['globals'][number] = undefined
const isFolderViewEnabled = config.folders?.enabled
const serverProps: ServerPropsFromView = {
viewActions: config?.admin?.components?.actions || [],
@@ -160,7 +153,6 @@ export const getRouteData = ({
if (oneSegmentViews[viewKey]) {
// --> /account
// --> /create-first-user
// --> /folders
// --> /forgot
// --> /login
// --> /logout
@@ -174,11 +166,6 @@ export const getRouteData = ({
templateClassName = baseClasses[viewKey]
templateType = 'minimal'
if (isFolderViewEnabled && viewKey === 'folders') {
templateType = 'default'
viewType = 'folders'
}
if (viewKey === 'account') {
templateType = 'default'
viewType = 'account'
@@ -195,18 +182,9 @@ export const getRouteData = ({
templateClassName = baseClasses[segmentTwo]
templateType = 'minimal'
viewType = 'reset'
} else if (`/${segmentOne}` === config.admin.routes.folders) {
if (isFolderViewEnabled) {
// --> /folders/:folderID
ViewToRender = {
Component: oneSegmentViews.folders,
}
templateClassName = baseClasses.folders
templateType = 'default'
viewType = 'folders'
folderID = segmentTwo
}
} else if (isCollection && matchedCollection) {
}
if (isCollection && matchedCollection) {
// --> /collections/:collectionSlug
ViewToRender = {
@@ -251,49 +229,31 @@ export const getRouteData = ({
templateType = 'minimal'
viewType = 'verify'
} else if (isCollection && matchedCollection) {
if (segmentThree === 'folders') {
if (
isFolderViewEnabled &&
Object.keys(config.folders.collections).includes(matchedCollection.slug)
) {
// Collection Folder Views
// --> /collections/:collectionSlug/folders
// --> /collections/:collectionSlug/folders/:folderID
ViewToRender = {
Component: CollectionFolderView,
}
// Custom Views
// --> /collections/:collectionSlug/:id
// --> /collections/:collectionSlug/:id/api
// --> /collections/:collectionSlug/:id/preview
// --> /collections/:collectionSlug/:id/versions
// --> /collections/:collectionSlug/:id/versions/:versionID
templateClassName = `collection-folders`
templateType = 'default'
viewType = 'collection-folders'
folderID = segmentFour
}
} else {
// Collection Edit Views
// --> /collections/:collectionSlug/:id
// --> /collections/:collectionSlug/:id/api
// --> /collections/:collectionSlug/:id/preview
// --> /collections/:collectionSlug/:id/versions
// --> /collections/:collectionSlug/:id/versions/:versionID
ViewToRender = {
Component: DocumentView,
}
templateClassName = `collection-default-edit`
templateType = 'default'
const viewInfo = getDocumentViewInfo([segmentFour, segmentFive])
viewType = viewInfo.viewType
documentSubViewType = viewInfo.documentSubViewType
attachViewActions({
collectionOrGlobal: matchedCollection,
serverProps,
viewKeyArg: documentSubViewType,
})
ViewToRender = {
Component: DocumentView,
}
templateClassName = `collection-default-edit`
templateType = 'default'
const viewInfo = getDocumentViewInfo([segmentFour, segmentFive])
viewType = viewInfo.viewType
documentSubViewType = viewInfo.documentSubViewType
attachViewActions({
collectionOrGlobal: matchedCollection,
serverProps,
viewKeyArg: documentSubViewType,
})
} else if (isGlobal && matchedGlobal) {
// Global Edit Views
// Custom Views
// --> /globals/:globalSlug/versions
// --> /globals/:globalSlug/preview
// --> /globals/:globalSlug/versions/:versionID
@@ -328,7 +288,6 @@ export const getRouteData = ({
return {
DefaultView: ViewToRender,
documentSubViewType,
folderID,
initPageOptions,
serverProps,
templateClassName,

View File

@@ -11,12 +11,12 @@ import { RenderServerComponent } from '@payloadcms/ui/elements/RenderServerCompo
import { getClientConfig } from '@payloadcms/ui/utilities/getClientConfig'
import { notFound, redirect } from 'next/navigation.js'
import { formatAdminURL } from 'payload/shared'
import React from 'react'
import React, { Fragment } from 'react'
import { DefaultTemplate } from '../../templates/Default/index.js'
import { MinimalTemplate } from '../../templates/Minimal/index.js'
import { initPage } from '../../utilities/initPage/index.js'
import { getRouteData } from './getRouteData.js'
import { getViewFromConfig } from './getViewFromConfig.js'
export type GenerateViewMetadata = (args: {
config: SanitizedConfig
@@ -64,13 +64,12 @@ export const RootPage = async ({
const {
DefaultView,
documentSubViewType,
folderID,
initPageOptions,
serverProps,
templateClassName,
templateType,
viewType,
} = getRouteData({
} = getViewFromConfig({
adminRoute,
config,
currentRoute,
@@ -90,10 +89,6 @@ export const RootPage = async ({
})
?.then((doc) => !!doc))
/**
* This function is responsible for handling the case where the view is not found.
* The current route did not match any default views or custom route views.
*/
if (!DefaultView?.Component && !DefaultView?.payloadComponent) {
if (initPageResult?.req?.user) {
notFound()
@@ -146,7 +141,6 @@ export const RootPage = async ({
...serverProps,
clientConfig,
docID: initPageResult?.docID,
folderID,
i18n: initPageResult?.req.i18n,
importMap,
initPageResult,
@@ -157,8 +151,8 @@ export const RootPage = async ({
})
return (
<React.Fragment>
{!templateType && <React.Fragment>{RenderedView}</React.Fragment>}
<Fragment>
{!templateType && <Fragment>{RenderedView}</Fragment>}
{templateType === 'minimal' && (
<MinimalTemplate className={templateClassName}>{RenderedView}</MinimalTemplate>
)}
@@ -188,6 +182,6 @@ export const RootPage = async ({
{RenderedView}
</DefaultTemplate>
)}
</React.Fragment>
</Fragment>
)
}

View File

@@ -3,11 +3,9 @@ import type { SanitizedConfig } from 'payload'
import { getNextRequestI18n } from '../../utilities/getNextRequestI18n.js'
import { generateAccountViewMetadata } from '../Account/metadata.js'
import { generateCollectionFolderMetadata } from '../CollectionFolders/metadata.js'
import { generateCreateFirstUserViewMetadata } from '../CreateFirstUser/metadata.js'
import { generateDashboardViewMetadata } from '../Dashboard/metadata.js'
import { generateDocumentViewMetadata } from '../Document/metadata.js'
import { generateBrowseByFolderMetadata } from '../Folders/metadata.js'
import { generateForgotPasswordViewMetadata } from '../ForgotPassword/metadata.js'
import { generateListViewMetadata } from '../List/metadata.js'
import { generateLoginViewMetadata } from '../Login/metadata.js'
@@ -20,7 +18,6 @@ import { getCustomViewByRoute } from './getCustomViewByRoute.js'
const oneSegmentMeta = {
'create-first-user': generateCreateFirstUserViewMetadata,
folders: generateBrowseByFolderMetadata,
forgot: generateForgotPasswordViewMetadata,
login: generateLoginViewMetadata,
logout: generateUnauthorizedViewMetadata,
@@ -48,7 +45,7 @@ export const generatePageMetadata = async ({
const segments = Array.isArray(params.segments) ? params.segments : []
const currentRoute = `/${segments.join('/')}`
const [segmentOne, segmentTwo, segmentThree] = segments
const [segmentOne, segmentTwo] = segments
const isGlobal = segmentOne === 'globals'
const isCollection = segmentOne === 'collections'
@@ -78,7 +75,6 @@ export const generatePageMetadata = async ({
if (oneSegmentMeta[segmentOne] && segmentOne !== 'account') {
// --> /create-first-user
// --> /forgot
// --> /folders
// --> /login
// --> /logout
// --> /logout-inactivity
@@ -96,10 +92,8 @@ export const generatePageMetadata = async ({
if (`/${segmentOne}` === config.admin.routes.reset) {
// --> /reset/:token
meta = await generateResetPasswordViewMetadata({ config, i18n })
} else if (`/${segmentOne}` === config.admin.routes.folders) {
// --> /folders/:folderID
meta = await generateBrowseByFolderMetadata({ config, i18n })
} else if (isCollection) {
}
if (isCollection) {
// --> /collections/:collectionSlug
meta = await generateListViewMetadata({ collectionConfig, config, i18n })
} else if (isGlobal) {
@@ -118,29 +112,15 @@ export const generatePageMetadata = async ({
// --> /:collectionSlug/verify/:token
meta = await generateVerifyViewMetadata({ config, i18n })
} else if (isCollection) {
if (segmentThree === 'folders') {
if (Object.keys(config.folders.collections).includes(collectionConfig.slug)) {
// Collection Folder Views
// --> /collections/:collectionSlug/folders
// --> /collections/:collectionSlug/folders/:id
meta = await generateCollectionFolderMetadata({
collectionConfig,
config,
i18n,
params,
})
}
} else {
// Collection Document Views
// --> /collections/:collectionSlug/:id
// --> /collections/:collectionSlug/:id/preview
// --> /collections/:collectionSlug/:id/versions
// --> /collections/:collectionSlug/:id/versions/:version
// --> /collections/:collectionSlug/:id/api
meta = await generateDocumentViewMetadata({ collectionConfig, config, i18n, params })
}
// Custom Views
// --> /collections/:collectionSlug/:id
// --> /collections/:collectionSlug/:id/preview
// --> /collections/:collectionSlug/:id/versions
// --> /collections/:collectionSlug/:id/versions/:version
// --> /collections/:collectionSlug/:id/api
meta = await generateDocumentViewMetadata({ collectionConfig, config, i18n, params })
} else if (isGlobal) {
// Global Document Views
// Custom Views
// --> /globals/:globalSlug/versions
// --> /globals/:globalSlug/versions/:version
// --> /globals/:globalSlug/preview

View File

@@ -18,11 +18,9 @@ export const renderPill = (data, latestVersion, currentLabel, previousLabel, pil
return (
<React.Fragment>
{data?.id === latestVersion ? (
<Pill pillStyle={pillStyle} size="small">
{currentLabel}
</Pill>
<Pill pillStyle={pillStyle}>{currentLabel}</Pill>
) : (
<Pill size="small">{previousLabel}</Pill>
<Pill>{previousLabel}</Pill>
)}
&nbsp;&nbsp;
</React.Fragment>

View File

@@ -1,7 +1,7 @@
/**
* @param {import('next').NextConfig} nextConfig
* @param {Object} [sortOnOptions] - Optional configuration options
* @param {boolean} [sortOnOptions.devBundleServerPackages] - Whether to bundle server packages in development mode. @default true
* @param {Object} [options] - Optional configuration options
* @param {boolean} [options.devBundleServerPackages] - Whether to bundle server packages in development mode. @default true
*
* @returns {import('next').NextConfig}
* */

View File

@@ -53,7 +53,7 @@ export type ListQuery = {
search?: string
sort?: Sort
where?: Where
} & Record<string, unknown>
}
export type BuildTableStateArgs = {
collectionSlug: string | string[]
@@ -71,7 +71,3 @@ export type BuildTableStateArgs = {
req: PayloadRequest
tableAppearance?: 'condensed' | 'default'
}
export type BuildCollectionFolderViewResult = {
View: React.ReactNode
}

View File

@@ -563,7 +563,6 @@ export type DocumentSlots = {
}
export type {
BuildCollectionFolderViewResult,
BuildTableStateArgs,
DefaultServerFunctionArgs,
ListQuery,
@@ -619,26 +618,6 @@ export type {
EditViewProps,
} from './views/document.js'
export type {
AfterFolderListClientProps,
AfterFolderListServerProps,
AfterFolderListServerPropsOnly,
AfterFolderListTableClientProps,
AfterFolderListTableServerProps,
AfterFolderListTableServerPropsOnly,
BeforeFolderListClientProps,
BeforeFolderListServerProps,
BeforeFolderListServerPropsOnly,
BeforeFolderListTableClientProps,
BeforeFolderListTableServerProps,
BeforeFolderListTableServerPropsOnly,
FolderListViewClientProps,
FolderListViewServerProps,
FolderListViewServerPropsOnly,
FolderListViewSlots,
FolderListViewSlotSharedClientProps,
} from './views/folderList.js'
export type {
AdminViewClientProps,
/**

View File

@@ -1,59 +0,0 @@
import type { ServerProps } from '../../config/types.js'
import type { FolderOrDocument } from '../../folders/types.js'
import type { SanitizedCollectionConfig } from '../../index.js'
export type FolderListViewSlots = {
AfterFolderList?: React.ReactNode
AfterFolderListTable?: React.ReactNode
BeforeFolderList?: React.ReactNode
BeforeFolderListTable?: React.ReactNode
Description?: React.ReactNode
listMenuItems?: React.ReactNode[]
Table: React.ReactNode
}
export type FolderListViewServerPropsOnly = {
collectionConfig: SanitizedCollectionConfig
documents: FolderOrDocument[]
subfolders: FolderOrDocument[]
} & ServerProps
export type FolderListViewServerProps = FolderListViewClientProps & FolderListViewServerPropsOnly
export type FolderListViewClientProps = {
beforeActions?: React.ReactNode[]
collectionSlug: SanitizedCollectionConfig['slug']
disableBulkDelete?: boolean
disableBulkEdit?: boolean
enableRowSelections?: boolean
hasCreatePermission: boolean
newDocumentURL: string
} & FolderListViewSlots
export type FolderListViewSlotSharedClientProps = {
collectionSlug: SanitizedCollectionConfig['slug']
hasCreatePermission: boolean
newDocumentURL: string
}
// BeforeFolderList
export type BeforeFolderListClientProps = FolderListViewSlotSharedClientProps
export type BeforeFolderListServerPropsOnly = {} & FolderListViewServerPropsOnly
export type BeforeFolderListServerProps = BeforeFolderListClientProps &
BeforeFolderListServerPropsOnly
// BeforeFolderListTable
export type BeforeFolderListTableClientProps = FolderListViewSlotSharedClientProps
export type BeforeFolderListTableServerPropsOnly = {} & FolderListViewServerPropsOnly
export type BeforeFolderListTableServerProps = BeforeFolderListTableClientProps &
BeforeFolderListTableServerPropsOnly
// AfterFolderList
export type AfterFolderListClientProps = FolderListViewSlotSharedClientProps
export type AfterFolderListServerPropsOnly = {} & FolderListViewServerPropsOnly
export type AfterFolderListServerProps = AfterFolderListClientProps & AfterFolderListServerPropsOnly
// AfterFolderListTable
export type AfterFolderListTableClientProps = FolderListViewSlotSharedClientProps
export type AfterFolderListTableServerPropsOnly = {} & FolderListViewServerPropsOnly
export type AfterFolderListTableServerProps = AfterFolderListTableClientProps &
AfterFolderListTableServerPropsOnly

View File

@@ -1,4 +1,4 @@
import type { ClientTranslationsObject } from '@payloadcms/translations'
import type { ClientTranslationsObject, I18n } from '@payloadcms/translations'
import type { SanitizedPermissions } from '../../auth/index.js'
import type { ImportMap } from '../../bin/generateImportMap/index.js'
@@ -8,12 +8,13 @@ import type {
CustomComponent,
Locale,
MetaConfig,
Params,
PayloadComponent,
SanitizedConfig,
ServerProps,
} from '../../config/types.js'
import type { SanitizedGlobalConfig } from '../../globals/config/types.js'
import type { PayloadRequest } from '../../types/index.js'
import type { Payload, PayloadRequest } from '../../types/index.js'
import type { LanguageOptions } from '../LanguageOptions.js'
import type { Data, StaticDescription } from '../types.js'
import type { DocumentSubViewTypes } from './document.js'
@@ -41,14 +42,9 @@ export type AdminViewServerPropsOnly = {
* @todo remove `docID` here as it is already contained in `initPageResult`
*/
readonly docID?: number | string
readonly folderID?: string
readonly importMap: ImportMap
readonly initialData?: Data
readonly initPageResult: InitPageResult
readonly params?: { [key: string]: string | string[] | undefined }
readonly redirectAfterCreate?: boolean
readonly redirectAfterDelete?: boolean
readonly redirectAfterDuplicate?: boolean
} & ServerProps
export type AdminViewServerProps = AdminViewClientProps & AdminViewServerPropsOnly
@@ -82,10 +78,8 @@ export type InitPageResult = {
*/
export type ViewTypes =
| 'account'
| 'collection-folders'
| 'dashboard'
| 'document'
| 'folders'
| 'list'
| 'reset'
| 'verify'

View File

@@ -38,7 +38,7 @@ export type ImportMap = {
[path: UserImportPath]: any
}
export type AddToImportMap = (payloadComponent?: PayloadComponent | PayloadComponent[]) => void
export type AddToImportMap = (payloadComponent: PayloadComponent | PayloadComponent[]) => void
export async function generateImportMap(
config: SanitizedConfig,

View File

@@ -256,11 +256,6 @@ export type AfterForgotPasswordHook = (args: {
context: RequestContext
}) => any
export type EnableFoldersOptions = {
// Displays the folder collection and parentFolder field in the document view
debug?: boolean
}
export type BaseListFilter = (args: {
limit: number
locale?: TypedLocale
@@ -588,9 +583,8 @@ export type SanitizedJoins = {
export interface SanitizedCollectionConfig
extends Omit<
DeepRequired<CollectionConfig>,
'admin' | 'auth' | 'endpoints' | 'fields' | 'slug' | 'upload' | 'versions'
'auth' | 'endpoints' | 'fields' | 'slug' | 'upload' | 'versions'
> {
admin: CollectionAdminOptions
auth: Auth
endpoints: Endpoint[] | false
fields: Field[]

View File

@@ -24,7 +24,6 @@ export const defaults: Omit<Config, 'db' | 'editor' | 'secret'> = {
routes: {
account: '/account',
createFirstUser: '/create-first-user',
folders: '/folders',
forgot: '/forgot',
inactivity: '/logout-inactivity',
login: '/login',
@@ -100,7 +99,6 @@ export const addDefaultsToConfig = (config: Config): Config => {
routes: {
account: '/account',
createFirstUser: '/create-first-user',
folders: '/folders',
forgot: '/forgot',
inactivity: '/logout-inactivity',
login: '/login',
@@ -111,13 +109,6 @@ export const addDefaultsToConfig = (config: Config): Config => {
},
}
config.folders = {
collections: {},
debug: false,
enabled: false,
...(config.folders || {}),
}
config.bin = config.bin ?? []
config.collections = config.collections ?? []
config.cookiePrefix = config.cookiePrefix ?? 'payload'

View File

@@ -18,7 +18,6 @@ import { sanitizeCollection } from '../collections/config/sanitize.js'
import { migrationsCollection } from '../database/migrations/migrationsCollection.js'
import { DuplicateCollection, InvalidConfiguration } from '../errors/index.js'
import { defaultTimezones } from '../fields/baseFields/timezone/defaultTimezones.js'
import { addFolderCollections } from '../folders/addFolderCollections.js'
import { sanitizeGlobal } from '../globals/config/sanitize.js'
import {
baseBlockFields,
@@ -191,8 +190,6 @@ export const sanitizeConfig = async (incomingConfig: Config): Promise<SanitizedC
const collectionSlugs = new Set<CollectionSlug>()
await addFolderCollections(config as unknown as Config)
const validRelationships = [
...(config.collections.map((c) => c.slug) ?? []),
jobsCollectionSlug,
@@ -316,7 +313,6 @@ export const sanitizeConfig = async (incomingConfig: Config): Promise<SanitizedC
for (const hook of Object.keys(hooks)) {
const defaultAmount = hook === 'afterRead' || hook === 'beforeChange' ? 1 : 0
if (hooks[hook]?.length > defaultAmount) {
// eslint-disable-next-line no-console
console.warn(
`The jobsCollectionOverrides function is returning a collection with an additional ${hook} hook defined. These hooks will not run unless the jobs.runHooks option is set to true. Setting this option to true will negatively impact performance.`,
)
@@ -378,15 +374,7 @@ export const sanitizeConfig = async (incomingConfig: Config): Promise<SanitizedC
config.csrf.push(config.serverURL)
}
const uploadAdapters = new Set<string>()
// interact with all collections
for (const collection of config.collections) {
// deduped upload adapters
if (collection.upload?.adapter) {
uploadAdapters.add(collection.upload.adapter)
}
}
// Get deduped list of upload adapters
if (!config.upload) {
config.upload = { adapters: [] }
}

View File

@@ -43,7 +43,6 @@ import type { ErrorName } from '../errors/types.js'
import type { GlobalConfig, Globals, SanitizedGlobalConfig } from '../globals/config/types.js'
import type {
Block,
CollectionSlug,
FlattenedBlock,
JobsConfig,
Payload,
@@ -852,11 +851,6 @@ export type Config = {
* @default '/create-first-user'
*/
createFirstUser?: `/${string}`
/** The route for folder view.
*
* @default '/folders'
*/
folders: `/${string}`
/** The route for the forgot password page.
*
* @default '/forgot'
@@ -983,44 +977,6 @@ export type Config = {
email?: EmailAdapter | Promise<EmailAdapter>
/** Custom REST endpoints */
endpoints?: Endpoint[]
/**
* Options for folder view within the admin panel
*/
folders?: {
/**
* An array of functions to be ran when the folder collection is initialized
* This allows plugins to modify the collection configuration
*/
collectionOverrides?: (({
collection,
}: {
collection: CollectionConfig
}) => CollectionConfig | Promise<CollectionConfig>)[]
/**
* Collections that you would like organize within folders
*/
collections: {
[key: CollectionSlug]: any
}
/**
* Ability to view hidden fields and collections related to folders
*
* @default false
*/
debug?: boolean
/**
* Enable folders in the admin panel
*
* @default false
*/
enabled?: boolean
/**
* Slug for the folder collection
*
* @default "_folders"
*/
slug?: string
}
/**
* @see https://payloadcms.com/docs/configuration/globals#global-configs
*/
@@ -1164,6 +1120,7 @@ export type Config = {
read?: QueryPresetConstraints
update?: QueryPresetConstraints
}
hooks: CollectionConfig['hooks']
labels?: CollectionConfig['labels']
}
/** Control the routing structure that Payload binds itself to. */

View File

@@ -40,24 +40,11 @@ export { getFieldPaths } from '../fields/getFieldPaths.js'
export * from '../fields/validations.js'
export type {
FolderBreadcrumb,
FolderDocumentItemKey,
FolderEnabledColection,
FolderInterface,
FolderOrDocument,
GetFolderDataResult,
Subfolder,
} from '../folders/types.js'
export { formatFolderOrDocumentItem } from '../folders/utils/formatFolderOrDocumentItem.js'
export { validOperators, validOperatorSet } from '../types/constants.js'
export { formatFilesize } from '../uploads/formatFilesize.js'
export { isImage } from '../uploads/isImage.js'
export { combineWhereConstraints } from '../utilities/combineWhereConstraints.js'
export {
deepCopyObject,
deepCopyObjectComplex,
@@ -72,7 +59,6 @@ export {
deepMergeWithSourceArrays,
} from '../utilities/deepMerge.js'
export { extractID } from '../utilities/extractID.js'
export { fieldSchemaToJSON } from '../utilities/fieldSchemaToJSON.js'
export { flattenAllFields } from '../utilities/flattenAllFields.js'
@@ -125,5 +111,4 @@ export { validateWhereQuery } from '../utilities/validateWhereQuery.js'
export { wait } from '../utilities/wait.js'
export { default as wordBoundariesRegex } from '../utilities/wordBoundariesRegex.js'
export { versionDefaults } from '../versions/defaults.js'
export { deepMergeSimple } from '@payloadcms/translations/utilities'

View File

@@ -1,59 +0,0 @@
import type { Config } from '../config/types.js'
import type { CollectionSlug } from '../index.js'
import { foldersSlug, parentFolderFieldName } from './constants.js'
import { createFolderCollection } from './createFolderCollection.js'
export async function addFolderCollections(config: NonNullable<Config>): Promise<void> {
if (!config.collections) {
return
}
if (config.folders?.enabled) {
const enabledCollectionSlugs: CollectionSlug[] = []
const debug = Boolean(config.folders?.debug)
config.folders.slug = config.folders.slug || foldersSlug
for (let i = 0; i < config.collections.length; i++) {
const collection = config.collections[i]
if (config.folders.collections[collection.slug]) {
if (collection) {
collection.fields.push({
name: parentFolderFieldName,
type: 'relationship',
admin: {
allowCreate: false,
allowEdit: false,
components: {
Cell: '@payloadcms/ui/rsc#FolderTableCell',
Field: '@payloadcms/ui#FolderEditField',
},
},
index: true,
label: 'Folder',
relationTo: config.folders.slug,
})
enabledCollectionSlugs.push(collection.slug)
}
}
}
if (enabledCollectionSlugs.length) {
let folderCollection = createFolderCollection({
slug: config.folders.slug,
collectionSlugs: enabledCollectionSlugs,
debug,
})
if (
Array.isArray(config.folders.collectionOverrides) &&
config.folders.collectionOverrides.length
) {
for (const override of config.folders.collectionOverrides) {
folderCollection = await override({ collection: folderCollection })
}
}
config.collections.push(folderCollection)
}
}
}

View File

@@ -1,2 +0,0 @@
export const foldersSlug = '_folders'
export const parentFolderFieldName = '_folder'

View File

@@ -1,73 +0,0 @@
import type { CollectionConfig } from '../collections/config/types.js'
import { parentFolderFieldName } from './constants.js'
import { populateFolderDataEndpoint } from './endpoints/populateFolderData.js'
import { deleteSubfoldersAfterDelete } from './hooks/deleteSubfoldersAfterDelete.js'
import { dissasociateAfterDelete } from './hooks/dissasociateAfterDelete.js'
import { reparentChildFolder } from './hooks/reparentChildFolder.js'
type CreateFolderCollectionArgs = {
collectionSlugs: string[]
debug?: boolean
slug: string
}
export const createFolderCollection = ({
slug,
collectionSlugs,
debug,
}: CreateFolderCollectionArgs): CollectionConfig => ({
slug,
admin: {
hidden: !debug,
useAsTitle: 'name',
},
endpoints: [populateFolderDataEndpoint],
fields: [
{
name: 'name',
type: 'text',
index: true,
required: true,
},
{
name: parentFolderFieldName,
type: 'relationship',
admin: {
hidden: !debug,
},
index: true,
relationTo: slug,
},
{
name: 'documentsAndFolders',
type: 'join',
admin: {
hidden: !debug,
},
collection: [slug, ...collectionSlugs],
hasMany: true,
on: parentFolderFieldName,
},
],
hooks: {
afterChange: [
reparentChildFolder({
parentFolderFieldName,
}),
],
afterDelete: [
dissasociateAfterDelete({
collectionSlugs,
parentFolderFieldName,
}),
deleteSubfoldersAfterDelete({ folderSlug: slug, parentFolderFieldName }),
],
},
labels: {
plural: 'Folders',
singular: 'Folder',
},
typescript: {
interface: 'FolderInterface',
},
})

View File

@@ -1,45 +0,0 @@
import httpStatus from 'http-status'
import type { Endpoint } from '../../index.js'
import { getFolderData } from '../utils/getFolderData.js'
export const populateFolderDataEndpoint: Endpoint = {
handler: async (req) => {
if (!req?.user) {
return Response.json(
{
message: 'Unauthorized request.',
},
{
status: httpStatus.UNAUTHORIZED,
},
)
}
const folderCollection = Boolean(req.payload.collections?.[req.payload.config.folders.slug])
if (!folderCollection) {
return Response.json(
{
message: 'Folders are not configured',
},
{
status: httpStatus.NOT_FOUND,
},
)
}
const data = await getFolderData({
collectionSlug: req.searchParams?.get('collectionSlug') || undefined,
folderID: req.searchParams?.get('folderID') || undefined,
payload: req.payload,
search: req.searchParams?.get('search') || undefined,
user: req.user,
})
return Response.json(data)
},
method: 'get',
path: '/populate-folder-data',
}

View File

@@ -1,25 +0,0 @@
import type { CollectionAfterDeleteHook } from '../../index.js'
type Args = {
folderSlug: string
parentFolderFieldName: string
}
export const deleteSubfoldersAfterDelete = ({
folderSlug,
parentFolderFieldName,
}: Args): CollectionAfterDeleteHook => {
return async ({ id, req }) => {
await req.payload.delete({
collection: folderSlug,
depth: 0,
overrideAccess: false,
req,
select: { id: true },
where: {
[parentFolderFieldName]: {
equals: id,
},
},
})
}
}

View File

@@ -1,32 +0,0 @@
import type { CollectionAfterDeleteHook } from '../../index.js'
type Args = {
collectionSlugs: string[]
parentFolderFieldName: string
}
export const dissasociateAfterDelete = ({
collectionSlugs,
parentFolderFieldName,
}: Args): CollectionAfterDeleteHook => {
return async ({ id, req }) => {
for (const collectionSlug of collectionSlugs) {
await req.payload.update({
collection: collectionSlug,
data: {
[parentFolderFieldName]: null,
},
depth: 0,
overrideAccess: false,
req,
select: {
id: true,
},
where: {
[parentFolderFieldName]: {
equals: id,
},
},
})
}
}
}

View File

@@ -1,110 +0,0 @@
import type { CollectionAfterChangeHook, JsonObject, Payload, TypeWithID } from '../../index.js'
import { extractID } from '../../utilities/extractID.js'
type Args = {
folderID: number | string
parentFolderFieldName: string
parentIDToFind: number | string
payload: Payload
}
/**
* Determines if a child folder belongs to a parent folder by
* recursively checking upwards through the folder hierarchy.
*/
async function isChildOfFolder({
folderID,
parentFolderFieldName,
parentIDToFind,
payload,
}: Args): Promise<boolean> {
const parentFolder = (await payload.findByID({
id: folderID,
collection: payload.config.folders.slug,
overrideAccess: false,
select: {
parentFolderFieldName: true,
},
})) as JsonObject & TypeWithID
const parentFolderID = parentFolder[parentFolderFieldName]
? extractID(parentFolder[parentFolderFieldName])
: undefined
if (!parentFolderID) {
// made it to the root
return false
}
if (parentFolderID === parentIDToFind) {
// found match, would be cyclic
return true
}
return isChildOfFolder({
folderID: parentFolderID,
parentFolderFieldName,
parentIDToFind,
payload,
})
}
/**
* If a parent is moved into a child folder, we need to re-parent the child
*
* @example
*
* ```ts
→ F1
→ F2
→ F2A
→ F3
Moving F1 → F2A becomes:
→ F2A
→ F1
→ F2
→ F3
```
*/
export const reparentChildFolder = ({
parentFolderFieldName,
}: {
parentFolderFieldName: string
}): CollectionAfterChangeHook => {
return async ({ doc, previousDoc, req }) => {
if (
previousDoc[parentFolderFieldName] !== doc[parentFolderFieldName] &&
doc[parentFolderFieldName]
) {
const newParentFolderID = extractID(doc[parentFolderFieldName])
const isMovingToChild = newParentFolderID
? await isChildOfFolder({
folderID: newParentFolderID,
parentFolderFieldName,
parentIDToFind: doc.id,
payload: req.payload,
})
: false
if (isMovingToChild) {
// if the folder was moved into a child folder, the child folder needs
// to be re-parented with the parent of the folder that was moved
await req.payload.update({
id: newParentFolderID,
collection: req.payload.config.folders.slug,
data: {
[parentFolderFieldName]: previousDoc[parentFolderFieldName]
? extractID(previousDoc[parentFolderFieldName])
: null,
},
depth: 0,
overrideAccess: false,
req,
})
}
}
}
}

View File

@@ -1,70 +0,0 @@
import type { TypeWithID } from '../collections/config/types.js'
import type { CollectionSlug, SanitizedCollectionConfig } from '../index.js'
import type { Document } from '../types/index.js'
export type FolderInterface = {
_folder?: FolderInterface | (number | string | undefined)
documentsAndFolders?: {
docs: {
relationTo: CollectionSlug
value: Document
}[]
}
name: string
} & TypeWithID
export type FolderBreadcrumb = {
id: null | number | string
name: string
}
export type Subfolder = {
fileCount: number
hasSubfolders: boolean
id: number | string
name: string
subfolderCount: number
}
export type FolderEnabledColection = {
admin: {
custom: {
folderCollectionSlug: CollectionSlug
}
}
slug: CollectionSlug
} & SanitizedCollectionConfig
/**
* `${relationTo}-${id}` is used as a key for the item
*/
export type FolderDocumentItemKey = `${string}-${number | string}`
/**
* Needed for document card view for upload enabled collections
*/
type DocumentMediaData = {
filename?: string
mimeType?: string
url?: string
}
/**
* A generic structure for a folder or document item.
*/
export type FolderOrDocument = {
itemKey: FolderDocumentItemKey
relationTo: CollectionSlug
value: {
_folder?: number | string
_folderOrDocumentTitle: string
createdAt?: string
id: number | string
updatedAt?: string
} & DocumentMediaData
}
export type GetFolderDataResult = {
breadcrumbs: FolderBreadcrumb[] | null
documents: FolderOrDocument[]
subfolders: FolderOrDocument[]
}

View File

@@ -1,35 +0,0 @@
import type { CollectionSlug, Document } from '../../index.js'
import type { FolderOrDocument } from '../types.js'
type Args = {
isUpload: boolean
relationTo: CollectionSlug
useAsTitle?: string
value: Document
}
export function formatFolderOrDocumentItem({
isUpload,
relationTo,
useAsTitle,
value,
}: Args): FolderOrDocument {
const itemValue: FolderOrDocument['value'] = {
id: value?.id,
_folder: value?._folder,
_folderOrDocumentTitle: value[useAsTitle || 'id'],
createdAt: value?.createdAt,
updatedAt: value?.updatedAt,
}
if (isUpload) {
itemValue.filename = value.filename
itemValue.mimeType = value.mimeType
itemValue.url = value.url
}
return {
itemKey: `${relationTo}-${value.id}`,
relationTo,
value: itemValue,
}
}

View File

@@ -1,64 +0,0 @@
import type { PaginatedDocs } from '../../database/types.js'
import type { User } from '../../index.js'
import type { Payload } from '../../types/index.js'
import type { FolderBreadcrumb, FolderInterface } from '../types.js'
import { parentFolderFieldName } from '../constants.js'
type GetFolderBreadcrumbsArgs = {
breadcrumbs?: FolderBreadcrumb[]
folderID?: number | string
payload: Payload
user?: User
}
/**
* Builds breadcrumbs up from child folder
* all the way up to root folder
*/
export const getFolderBreadcrumbs = async ({
breadcrumbs = [],
folderID,
payload,
user,
}: GetFolderBreadcrumbsArgs): Promise<FolderBreadcrumb[] | null> => {
if (folderID) {
const folderQuery = (await payload.find({
collection: payload.config.folders.slug,
depth: 0,
limit: 1,
overrideAccess: false,
select: {
name: true,
[parentFolderFieldName]: true,
},
user,
where: {
id: {
equals: folderID,
},
},
})) as PaginatedDocs<FolderInterface>
const folder = folderQuery.docs[0]
if (folder) {
breadcrumbs.push({
id: folder.id,
name: folder.name,
})
if (folder[parentFolderFieldName]) {
return getFolderBreadcrumbs({
breadcrumbs,
folderID:
typeof folder[parentFolderFieldName] === 'number' ||
typeof folder[parentFolderFieldName] === 'string'
? folder[parentFolderFieldName]
: folder[parentFolderFieldName].id,
payload,
user,
})
}
}
}
return breadcrumbs.reverse()
}

View File

@@ -1,104 +0,0 @@
import type { CollectionSlug, User } from '../../index.js'
import type { Payload } from '../../types/index.js'
import type { GetFolderDataResult } from '../types.js'
import { parseDocumentID } from '../../index.js'
import { getFolderBreadcrumbs } from './getFolderBreadcrumbs.js'
import { queryDocumentsAndFoldersFromJoin } from './getFoldersAndDocumentsFromJoin.js'
import { getOrphanedDocs } from './getOrphanedDocs.js'
type Args = {
/**
* Specify to query documents from a specific collection
* @default undefined
* @example 'posts'
*/
collectionSlug?: CollectionSlug
/**
* The ID of the folder to query documents from
* @default undefined
*/
folderID?: number | string
/**
* The locale to use for the document query
* @default undefined
*/
payload: Payload
/**
* Search term to filter documents by - only applicable IF `collectionSlug` exists and NO `folderID` is provided
*/
search?: string
/**
* The user making the request
* @default undefined
*/
user?: User
}
/**
* Query for documents, subfolders and breadcrumbs for a given folder
*/
export const getFolderData = async ({
collectionSlug,
folderID: _folderID,
payload,
search,
user,
}: Args): Promise<GetFolderDataResult> => {
const parentFolderID = parseDocumentID({
id: _folderID,
collectionSlug: payload.config.folders.slug,
payload,
})
const breadcrumbsPromise = getFolderBreadcrumbs({
folderID: parentFolderID,
payload,
user,
})
if (parentFolderID) {
// subfolders and documents are queried together
const documentAndSubfolderPromise = queryDocumentsAndFoldersFromJoin({
collectionSlug,
parentFolderID,
payload,
user,
})
const [breadcrumbs, documentsAndSubfolders] = await Promise.all([
breadcrumbsPromise,
documentAndSubfolderPromise,
])
return {
breadcrumbs,
documents: documentsAndSubfolders.documents,
subfolders: documentsAndSubfolders.subfolders,
}
} else {
// subfolders and documents are queried separately
const subfoldersPromise = getOrphanedDocs({
collectionSlug: payload.config.folders.slug,
payload,
search,
user,
})
const documentsPromise = collectionSlug
? getOrphanedDocs({
collectionSlug,
payload,
search,
user,
})
: Promise.resolve([])
const [breadcrumbs, subfolders, documents] = await Promise.all([
breadcrumbsPromise,
subfoldersPromise,
documentsPromise,
])
return {
breadcrumbs,
documents,
subfolders,
}
}
}

View File

@@ -1,80 +0,0 @@
import type { User } from '../../auth/types.js'
import type { PaginatedDocs } from '../../database/types.js'
import type { CollectionSlug } from '../../index.js'
import type { Document, Payload } from '../../types/index.js'
import type { FolderOrDocument } from '../types.js'
import { formatFolderOrDocumentItem } from './formatFolderOrDocumentItem.js'
type QueryDocumentsAndFoldersResults = {
documents: FolderOrDocument[]
subfolders: FolderOrDocument[]
}
type QueryDocumentsAndFoldersArgs = {
collectionSlug?: CollectionSlug
parentFolderID: number | string
payload: Payload
user?: User
}
export async function queryDocumentsAndFoldersFromJoin({
collectionSlug,
parentFolderID,
payload,
user,
}: QueryDocumentsAndFoldersArgs): Promise<QueryDocumentsAndFoldersResults> {
const subfolderDoc = (await payload.find({
collection: payload.config.folders.slug,
joins: {
documentsAndFolders: {
limit: 100_000,
sort: 'name',
where: {
relationTo: {
in: [
payload.config.folders.slug,
...(collectionSlug
? [collectionSlug]
: Object.keys(payload.config.folders.collections)),
],
},
},
},
},
limit: 1,
// overrideAccess: false, // @todo: bug in core, throws "QueryError: The following paths cannot be queried: relationTo"
user,
where: {
id: {
equals: parentFolderID,
},
},
})) as PaginatedDocs<Document>
const childrenDocs = subfolderDoc?.docs[0]?.documentsAndFolders?.docs || []
const results: QueryDocumentsAndFoldersResults = childrenDocs.reduce(
(acc: QueryDocumentsAndFoldersResults, doc: Document) => {
const { relationTo, value } = doc
const item = formatFolderOrDocumentItem({
isUpload: Boolean(payload.collections[relationTo].config.upload),
relationTo,
useAsTitle: payload.collections[relationTo].config.admin?.useAsTitle,
value,
})
if (relationTo === payload.config.folders.slug) {
acc.subfolders.push(item)
} else {
acc.documents.push(item)
}
return acc
},
{
documents: [],
subfolders: [],
},
)
return results
}

View File

@@ -1,60 +0,0 @@
import type { CollectionSlug, Payload, User, Where } from '../../index.js'
import type { FolderOrDocument } from '../types.js'
import { formatFolderOrDocumentItem } from './formatFolderOrDocumentItem.js'
type Args = {
collectionSlug: CollectionSlug
payload: Payload
search?: string
user?: User
}
export async function getOrphanedDocs({
collectionSlug,
payload,
search,
user,
}: Args): Promise<FolderOrDocument[]> {
let whereConstraints: Where = {
or: [
{
_folder: {
exists: false,
},
},
{
_folder: {
equals: null,
},
},
],
}
if (collectionSlug && search && payload.collections[collectionSlug].config.admin?.useAsTitle) {
whereConstraints = {
[payload.collections[collectionSlug].config.admin.useAsTitle]: {
like: search,
},
}
}
const orphanedFolders = await payload.find({
collection: collectionSlug,
limit: 0,
// overrideAccess: false, // @todo: bug in core, throws "QueryError: The following paths cannot be queried: _folder"
sort: payload.collections[collectionSlug].config.admin.useAsTitle,
user,
where: whereConstraints,
})
return (
orphanedFolders?.docs.map((doc) =>
formatFolderOrDocumentItem({
isUpload: Boolean(payload.collections[collectionSlug].config.upload),
relationTo: collectionSlug,
useAsTitle: payload.collections[collectionSlug].config.admin.useAsTitle,
value: doc,
}),
) || []
)
}

View File

@@ -1371,7 +1371,6 @@ export type {
UploadFieldValidation,
UsernameFieldValidation,
} from './fields/validations.js'
export { getFolderData } from './folders/utils/getFolderData.js'
export {
type ClientGlobalConfig,
createClientGlobalConfig,
@@ -1379,6 +1378,7 @@ export {
type ServerOnlyGlobalAdminProperties,
type ServerOnlyGlobalProperties,
} from './globals/config/client.js'
export type {
AfterChangeHook as GlobalAfterChangeHook,
AfterReadHook as GlobalAfterReadHook,
@@ -1390,6 +1390,7 @@ export type {
GlobalConfig,
SanitizedGlobalConfig,
} from './globals/config/types.js'
export { docAccessOperation as docAccessOperationGlobal } from './globals/operations/docAccess.js'
export { findOneOperation } from './globals/operations/findOne.js'
@@ -1436,11 +1437,11 @@ export type {
WorkflowTypes,
} from './queues/config/types/workflowTypes.js'
export { importHandlerPath } from './queues/operations/runJobs/runJob/importHandlerPath.js'
export { getLocalI18n } from './translations/getLocalI18n.js'
export * from './types/index.js'
export { getFileByPath } from './uploads/getFileByPath.js'
export type * from './uploads/types.js'
export { addDataAndFileToRequest } from './utilities/addDataAndFileToRequest.js'
export { addLocalesToRequestFromData, sanitizeLocales } from './utilities/addLocalesToRequest.js'
@@ -1498,7 +1499,6 @@ export { logError } from './utilities/logError.js'
export { defaultLoggerOptions } from './utilities/logger.js'
export { mapAsync } from './utilities/mapAsync.js'
export { mergeHeaders } from './utilities/mergeHeaders.js'
export { parseDocumentID } from './utilities/parseDocumentID.js'
export { sanitizeFallbackLocale } from './utilities/sanitizeFallbackLocale.js'
export { sanitizeJoinParams } from './utilities/sanitizeJoinParams.js'
export { sanitizePopulateParam } from './utilities/sanitizePopulateParam.js'
@@ -1515,7 +1515,6 @@ export { appendVersionToQueryKey } from './versions/drafts/appendVersionToQueryK
export { getQueryDraftsSort } from './versions/drafts/getQueryDraftsSort.js'
export { enforceMaxVersions } from './versions/enforceMaxVersions.js'
export { getLatestCollectionVersion } from './versions/getLatestCollectionVersion.js'
export { getLatestGlobalVersion } from './versions/getLatestGlobalVersion.js'
export { saveVersion } from './versions/saveVersion.js'
export type { SchedulePublishTaskInput } from './versions/schedule/types.js'

View File

@@ -125,7 +125,9 @@ export const getQueryPresetsConfig = (config: Config): CollectionConfig => ({
},
],
hooks: {
...(config.queryPresets?.hooks || {}),
beforeValidate: [
...(config.queryPresets?.hooks?.beforeValidate || []),
({ data, operation, req }) => {
// TODO: type this
const typedData = data as any

View File

@@ -43,6 +43,33 @@ export async function cropImage({
sharpOptions.animated = true
}
const { height: originalHeight, width: originalWidth } = dimensions
const newWidth = Number(widthInPixels)
const newHeight = Number(heightInPixels)
const dimensionsChanged = originalWidth !== newWidth || originalHeight !== newHeight
if (!dimensionsChanged) {
let adjustedHeight = originalHeight
if (fileIsAnimatedType) {
const animatedMetadata = await sharp(
file.tempFilePath || file.data,
sharpOptions,
).metadata()
adjustedHeight = animatedMetadata.pages ? animatedMetadata.height : originalHeight
}
return {
data: file.data,
info: {
height: adjustedHeight,
size: file.size,
width: originalWidth,
},
}
}
const formattedCropData = {
height: Number(heightInPixels),
left: percentToPixel(x, dimensions.width),

View File

@@ -241,7 +241,7 @@ export const generateFileData = async <T>({
})
// Apply resize after cropping to ensure it conforms to resizeOptions
if (resizeOptions) {
if (resizeOptions && !resizeOptions.withoutEnlargement) {
const resizedAfterCrop = await sharp(croppedImage)
.resize({
fit: resizeOptions?.fit || 'cover',

View File

@@ -1,19 +0,0 @@
import type { Where } from '../types/index.js'
export function combineWhereConstraints(
constraints: Array<undefined | Where>,
as: 'and' | 'or' = 'and',
): Where {
if (constraints.length === 0) {
return {}
}
return {
[as]: constraints.filter((constraint): constraint is Where => {
if (constraint && typeof constraint === 'object' && Object.keys(constraint).length > 0) {
return true
}
return false
}),
}
}

View File

@@ -1,9 +0,0 @@
export const extractID = <IDType extends number | string>(
objectOrID: { id: IDType } | IDType,
): IDType => {
if (typeof objectOrID === 'string' || typeof objectOrID === 'number') {
return objectOrID
}
return objectOrID.id
}

View File

@@ -1,15 +0,0 @@
import type { CollectionSlug, Payload } from '../index.js'
import { isNumber } from './isNumber.js'
type ParseDocumentIDArgs = {
collectionSlug: CollectionSlug
id?: number | string
payload: Payload
}
export function parseDocumentID({ id, collectionSlug, payload }: ParseDocumentIDArgs) {
const idType = payload.collections[collectionSlug]?.customIDType ?? payload.db.defaultIDType
return id ? (idType === 'number' && isNumber(id) ? parseFloat(String(id)) : id) : undefined
}

View File

@@ -42,6 +42,16 @@
"import": "./src/exports/rsc.ts",
"types": "./src/exports/rsc.ts",
"default": "./src/exports/rsc.ts"
},
"./translations/languages/all": {
"import": "./src/translations/index.ts",
"types": "./src/translations/index.ts",
"default": "./src/translations/index.ts"
},
"./translations/languages/*": {
"import": "./src/translations/languages/*.ts",
"types": "./src/translations/languages/*.ts",
"default": "./src/translations/languages/*.ts"
}
},
"main": "./src/index.ts",
@@ -92,6 +102,16 @@
"import": "./dist/exports/rsc.js",
"types": "./dist/exports/rsc.d.ts",
"default": "./dist/exports/rsc.js"
},
"./translations/languages/all": {
"import": "./dist/translations/index.js",
"types": "./dist/translations/index.d.ts",
"default": "./dist/translations/index.js"
},
"./translations/languages/*": {
"import": "./dist/translations/languages/*.js",
"types": "./dist/translations/languages/*.d.ts",
"default": "./dist/translations/languages/*.js"
}
},
"main": "./dist/index.js",

View File

@@ -1,9 +1,20 @@
'use client'
import { getTranslation } from '@payloadcms/translations'
import { PopupList, useConfig, useDocumentDrawer, useTranslation } from '@payloadcms/ui'
import {
PopupList,
Translation,
useConfig,
useDocumentDrawer,
useTranslation,
} from '@payloadcms/ui'
import React, { useEffect } from 'react'
import type {
PluginImportExportTranslationKeys,
PluginImportExportTranslations,
} from '../../translations/index.js'
import { useImportExport } from '../ImportExportProvider/index.js'
import './index.scss'
@@ -14,7 +25,10 @@ export const ExportListMenuItem: React.FC<{
exportCollectionSlug: string
}> = ({ collectionSlug, exportCollectionSlug }) => {
const { getEntityConfig } = useConfig()
const { i18n } = useTranslation()
const { i18n, t } = useTranslation<
PluginImportExportTranslations,
PluginImportExportTranslationKeys
>()
const currentCollectionConfig = getEntityConfig({ collectionSlug })
const [DocumentDrawer, DocumentDrawerToggler] = useDocumentDrawer({
@@ -30,7 +44,15 @@ export const ExportListMenuItem: React.FC<{
return (
<PopupList.Button className={baseClass}>
<DocumentDrawerToggler>
Export {getTranslation(currentCollectionConfig.labels.plural, i18n)}
<Translation
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-expect-error
i18nKey="plugin-import-export:exportDocumentLabel"
t={t}
variables={{
label: getTranslation(currentCollectionConfig.labels.plural, i18n),
}}
/>
</DocumentDrawerToggler>
<DocumentDrawer />
</PopupList.Button>

View File

@@ -1,10 +1,15 @@
'use client'
import { Button, SaveButton, useConfig, useForm, useTranslation } from '@payloadcms/ui'
import { Button, SaveButton, Translation, useConfig, useForm, useTranslation } from '@payloadcms/ui'
import React from 'react'
import type {
PluginImportExportTranslationKeys,
PluginImportExportTranslations,
} from '../../translations/index.js'
export const ExportSaveButton: React.FC = () => {
const { t } = useTranslation()
const { t } = useTranslation<PluginImportExportTranslations, PluginImportExportTranslationKeys>()
const {
config: {
routes: { api },
@@ -65,7 +70,7 @@ export const ExportSaveButton: React.FC = () => {
<React.Fragment>
<SaveButton label={label}></SaveButton>
<Button onClick={handleDownload} size="medium" type="button">
Download
<Translation i18nKey="upload:download" t={t} />
</Button>
</React.Fragment>
)

View File

@@ -83,11 +83,12 @@ export const FieldsToExport: SelectFieldClientComponent = (props) => {
return (
<div className={baseClass}>
<FieldLabel label="Columns to Export" />
<FieldLabel label={props.field.label} path={props.path} />
<ReactSelect
className={baseClass}
disabled={props.readOnly}
getOptionValue={(option) => String(option.value)}
inputId={`field-${props.path.replace(/\./g, '__')}`}
isClearable={true}
isMulti={true}
isSortable={true}

View File

@@ -3,13 +3,18 @@ import type { Column } from '@payloadcms/ui'
import type { ClientField, FieldAffectingDataClient } from 'payload'
import { getTranslation } from '@payloadcms/translations'
import { Table, useConfig, useField, useTranslation } from '@payloadcms/ui'
import { Table, Translation, useConfig, useField, useTranslation } from '@payloadcms/ui'
import { fieldAffectsData } from 'payload/shared'
import * as qs from 'qs-esm'
import React from 'react'
import { useImportExport } from '../ImportExportProvider/index.js'
import type {
PluginImportExportTranslationKeys,
PluginImportExportTranslations,
} from '../../translations/index.js'
import './index.scss'
import { useImportExport } from '../ImportExportProvider/index.js'
const baseClass = 'preview'
@@ -24,7 +29,10 @@ export const Preview = () => {
const [dataToRender, setDataToRender] = React.useState<any[]>([])
const [resultCount, setResultCount] = React.useState<any>('')
const [columns, setColumns] = React.useState<Column[]>([])
const { i18n } = useTranslation()
const { i18n, t } = useTranslation<
PluginImportExportTranslations,
PluginImportExportTranslationKeys
>()
const collectionSlug = typeof collection === 'string' && collection
const collectionConfig = config.collections.find(
@@ -102,8 +110,20 @@ export const Preview = () => {
return (
<div className={baseClass}>
<div className={`${baseClass}__header`}>
<h3>Preview</h3>
{resultCount && <span>{resultCount} total documents</span>}
<h3>
<Translation i18nKey="version:preview" t={t} />
</h3>
{resultCount && (
<Translation
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-expect-error
i18nKey="plugin-import-export:totalDocumentsCount"
t={t}
variables={{
count: resultCount,
}}
/>
)}
</div>
{dataToRender && <Table columns={columns} data={dataToRender} />}
</div>

View File

@@ -72,11 +72,12 @@ export const SortBy: SelectFieldClientComponent = (props) => {
return (
<div className={baseClass} style={{ '--field-width': '33%' } as React.CSSProperties}>
<FieldLabel label="Sort By" />
<FieldLabel label={props.field.label} path={props.path} />
<ReactSelect
className={baseClass}
disabled={props.readOnly}
getOptionValue={(option) => String(option.value)}
inputId={`field-${props.path.replace(/\./g, '__')}`}
isClearable={true}
isSortable={true}
// @ts-expect-error react select option

View File

@@ -12,10 +12,11 @@ export const getFields = (config: Config): Field[] => {
width: '33%',
},
defaultValue: 'all',
label: 'Locale',
// @ts-expect-error - this is not correctly typed in plugins right now
label: ({ t }) => t('plugin-import-export:field-locale-label'),
options: [
{
label: 'All Locales',
label: ({ t }) => t('general:allLocales'),
value: 'all',
},
...config.localization.locales.map((locale) => ({
@@ -34,7 +35,8 @@ export const getFields = (config: Config): Field[] => {
name: 'name',
type: 'text',
defaultValue: () => getFilename(),
label: 'File Name',
// @ts-expect-error - this is not correctly typed in plugins right now
label: ({ t }) => t('plugin-import-export:field-name-label'),
},
{
type: 'row',
@@ -46,7 +48,8 @@ export const getFields = (config: Config): Field[] => {
width: '33%',
},
defaultValue: 'csv',
label: 'Export Format',
// @ts-expect-error - this is not correctly typed in plugins right now
label: ({ t }) => t('plugin-import-export:field-format-label'),
options: [
{
label: 'CSV',
@@ -66,6 +69,8 @@ export const getFields = (config: Config): Field[] => {
placeholder: 'No limit',
width: '33%',
},
// @ts-expect-error - this is not correctly typed in plugins right now
label: ({ t }) => t('plugin-import-export:field-limit-label'),
},
{
name: 'sort',
@@ -75,6 +80,8 @@ export const getFields = (config: Config): Field[] => {
Field: '@payloadcms/plugin-import-export/rsc#SortBy',
},
},
// @ts-expect-error - this is not correctly typed in plugins right now
label: ({ t }) => t('plugin-import-export:field-sort-label'),
},
],
},
@@ -98,14 +105,15 @@ export const getFields = (config: Config): Field[] => {
width: '33%',
},
defaultValue: 'yes',
label: 'Drafts',
// @ts-expect-error - this is not correctly typed in plugins right now
label: ({ t }) => t('plugin-import-export:field-drafts-label'),
options: [
{
label: 'Yes',
label: ({ t }) => t('general:yes'),
value: 'yes',
},
{
label: 'No',
label: ({ t }) => t('general:no'),
value: 'no',
},
],
@@ -113,6 +121,8 @@ export const getFields = (config: Config): Field[] => {
// {
// name: 'depth',
// type: 'number',
// // @ts-expect-error - this is not correctly typed in plugins right now
// label: ({ t }) => t('plugin-import-export:field-depth-label'),
// admin: {
// width: '33%',
// },
@@ -126,17 +136,22 @@ export const getFields = (config: Config): Field[] => {
name: 'selectionToUse',
type: 'radio',
defaultValue: 'all',
// @ts-expect-error - this is not correctly typed in plugins right now
label: ({ t }) => t('plugin-import-export:field-selectionToUse-label'),
options: [
{
label: 'Use current selection',
// @ts-expect-error - this is not correctly typed in plugins right now
label: ({ t }) => t('plugin-import-export:selectionToUse-currentSelection'),
value: 'currentSelection',
},
{
label: 'Use current filters',
// @ts-expect-error - this is not correctly typed in plugins right now
label: ({ t }) => t('plugin-import-export:selectionToUse-currentFilters'),
value: 'currentFilters',
},
{
label: 'Use all documents',
// @ts-expect-error - this is not correctly typed in plugins right now
label: ({ t }) => t('plugin-import-export:selectionToUse-allDocuments'),
value: 'all',
},
],
@@ -151,6 +166,8 @@ export const getFields = (config: Config): Field[] => {
},
},
hasMany: true,
// @ts-expect-error - this is not correctly typed in plugins right now
label: ({ t }) => t('plugin-import-export:field-fields-label'),
},
{
name: 'collectionSlug',
@@ -174,7 +191,8 @@ export const getFields = (config: Config): Field[] => {
defaultValue: {},
},
],
label: 'Export Options',
// @ts-expect-error - this is not correctly typed in plugins right now
label: ({ t }) => t('plugin-import-export:exportOptions'),
},
{
name: 'preview',

View File

@@ -2,6 +2,7 @@ import type { Config, JobsConfig } from 'payload'
import { deepMergeSimple } from 'payload'
import type { PluginDefaultTranslationsObject } from './translations/types.js'
import type { ImportExportPluginConfig } from './types.js'
import { getCreateCollectionExportTask } from './export/getCreateExportCollectionTask.js'
@@ -70,7 +71,23 @@ export const importExportPlugin =
config.i18n = {}
}
config.i18n.translations = deepMergeSimple(translations, config.i18n?.translations ?? {})
// config.i18n.translations = deepMergeSimple(translations, config.i18n?.translations ?? {})
/**
* Merge plugin translations
*/
const simplifiedTranslations = Object.entries(translations).reduce(
(acc, [key, value]) => {
acc[key] = value.translations
return acc
},
{} as Record<string, PluginDefaultTranslationsObject>,
)
config.i18n = {
...config.i18n,
translations: deepMergeSimple(simplifiedTranslations, config.i18n?.translations ?? {}),
}
return config
}

View File

@@ -1,9 +0,0 @@
import type { GenericTranslationsObject } from '@payloadcms/translations'
export const en: GenericTranslationsObject = {
$schema: './translation-schema.json',
'plugin-seo': {
export: 'Export',
import: 'Import',
},
}

View File

@@ -1,10 +1,90 @@
import type { GenericTranslationsObject, NestedKeysStripped } from '@payloadcms/translations'
import type {
GenericTranslationsObject,
NestedKeysStripped,
SupportedLanguages,
} from '@payloadcms/translations'
import { en } from './en.js'
import type { PluginDefaultTranslationsObject } from './types.js'
import { ar } from './languages/ar.js'
import { az } from './languages/az.js'
import { bg } from './languages/bg.js'
import { ca } from './languages/ca.js'
import { cs } from './languages/cs.js'
import { da } from './languages/da.js'
import { de } from './languages/de.js'
import { en } from './languages/en.js'
import { es } from './languages/es.js'
import { et } from './languages/et.js'
import { fa } from './languages/fa.js'
import { fr } from './languages/fr.js'
import { he } from './languages/he.js'
import { hr } from './languages/hr.js'
import { hu } from './languages/hu.js'
import { hy } from './languages/hy.js'
import { it } from './languages/it.js'
import { ja } from './languages/ja.js'
import { ko } from './languages/ko.js'
import { lt } from './languages/lt.js'
import { my } from './languages/my.js'
import { nb } from './languages/nb.js'
import { nl } from './languages/nl.js'
import { pl } from './languages/pl.js'
import { pt } from './languages/pt.js'
import { ro } from './languages/ro.js'
import { rs } from './languages/rs.js'
import { rsLatin } from './languages/rsLatin.js'
import { ru } from './languages/ru.js'
import { sk } from './languages/sk.js'
import { sl } from './languages/sl.js'
import { sv } from './languages/sv.js'
import { th } from './languages/th.js'
import { tr } from './languages/tr.js'
import { uk } from './languages/uk.js'
import { vi } from './languages/vi.js'
import { zh } from './languages/zh.js'
import { zhTw } from './languages/zhTw.js'
export const translations = {
ar,
az,
bg,
ca,
cs,
da,
de,
en,
}
es,
et,
fa,
fr,
he,
hr,
hu,
hy,
it,
ja,
ko,
lt,
my,
nb,
nl,
pl,
pt,
ro,
rs,
'rs-latin': rsLatin,
ru,
sk,
sl,
sv,
th,
tr,
uk,
vi,
zh,
'zh-TW': zhTw,
} as SupportedLanguages<PluginDefaultTranslationsObject>
export type PluginImportExportTranslations = GenericTranslationsObject

View File

@@ -0,0 +1,27 @@
import type { PluginDefaultTranslationsObject, PluginLanguage } from '../types.js'
export const arTranslations: PluginDefaultTranslationsObject = {
'plugin-import-export': {
allLocales: 'جميع المواقع',
exportDocumentLabel: 'تصدير {{label}}',
exportOptions: 'خيارات التصدير',
'field-depth-label': 'عمق',
'field-drafts-label': 'تضمن المسودات',
'field-fields-label': 'حقول',
'field-format-label': 'تنسيق التصدير',
'field-limit-label': 'حد',
'field-locale-label': 'موقع',
'field-name-label': 'اسم الملف',
'field-selectionToUse-label': 'اختيار للاستخدام',
'field-sort-label': 'ترتيب حسب',
'selectionToUse-allDocuments': 'استخدم جميع الوثائق',
'selectionToUse-currentFilters': 'استخدم الفلاتر الحالية',
'selectionToUse-currentSelection': 'استخدم الاختيار الحالي',
totalDocumentsCount: '{{count}} مستنداً إجمالياً',
},
}
export const ar: PluginLanguage = {
dateFNSKey: 'ar',
translations: arTranslations,
}

View File

@@ -0,0 +1,27 @@
import type { PluginDefaultTranslationsObject, PluginLanguage } from '../types.js'
export const azTranslations: PluginDefaultTranslationsObject = {
'plugin-import-export': {
allLocales: 'Bütün yerlər',
exportDocumentLabel: '{{label}} ixrac edin',
exportOptions: 'İxrac Variantları',
'field-depth-label': 'Dərinlik',
'field-drafts-label': 'Qaralamaları daxil etin',
'field-fields-label': 'Sahələr',
'field-format-label': 'İxrac Formatı',
'field-limit-label': 'Hədd',
'field-locale-label': 'Yerli',
'field-name-label': 'Fayl adı',
'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',
'selectionToUse-currentFilters': 'Cari filtrlərdən istifadə edin',
'selectionToUse-currentSelection': 'Cari seçimi istifadə edin',
totalDocumentsCount: '{{count}} ümumi sənəd',
},
}
export const az: PluginLanguage = {
dateFNSKey: 'az',
translations: azTranslations,
}

View File

@@ -0,0 +1,27 @@
import type { PluginDefaultTranslationsObject, PluginLanguage } from '../types.js'
export const bgTranslations: PluginDefaultTranslationsObject = {
'plugin-import-export': {
allLocales: 'Всички локации',
exportDocumentLabel: 'Експортиране {{label}}',
exportOptions: 'Опции за експортиране',
'field-depth-label': 'Дълбочина',
'field-drafts-label': 'Включете чернови',
'field-fields-label': 'Полета',
'field-format-label': 'Формат за експортиране',
'field-limit-label': 'Лимит',
'field-locale-label': 'Регион',
'field-name-label': 'Име на файла',
'field-selectionToUse-label': 'Избор за използване',
'field-sort-label': 'Сортирай по',
'selectionToUse-allDocuments': 'Използвайте всички документи',
'selectionToUse-currentFilters': 'Използвайте текущите филтри',
'selectionToUse-currentSelection': 'Използвайте текущия избор',
totalDocumentsCount: '{{count}} общо документа',
},
}
export const bg: PluginLanguage = {
dateFNSKey: 'bg',
translations: bgTranslations,
}

View File

@@ -0,0 +1,27 @@
import type { PluginDefaultTranslationsObject, PluginLanguage } from '../types.js'
export const caTranslations: PluginDefaultTranslationsObject = {
'plugin-import-export': {
allLocales: 'Totes les localitzacions',
exportDocumentLabel: 'Exporta {{label}}',
exportOptions: "Opcions d'exportació",
'field-depth-label': 'Profunditat',
'field-drafts-label': 'Inclou esborranys',
'field-fields-label': 'Camps',
'field-format-label': "Format d'exportació",
'field-limit-label': 'Límit',
'field-locale-label': 'Local',
'field-name-label': 'Nom del fitxer',
'field-selectionToUse-label': 'Selecció per utilitzar',
'field-sort-label': 'Ordena per',
'selectionToUse-allDocuments': 'Utilitzeu tots els documents',
'selectionToUse-currentFilters': 'Utilitza els filtres actuals',
'selectionToUse-currentSelection': 'Utilitza la selecció actual',
totalDocumentsCount: '{{count}} documents totals',
},
}
export const ca: PluginLanguage = {
dateFNSKey: 'ca',
translations: caTranslations,
}

View File

@@ -0,0 +1,27 @@
import type { PluginDefaultTranslationsObject, PluginLanguage } from '../types.js'
export const csTranslations: PluginDefaultTranslationsObject = {
'plugin-import-export': {
allLocales: 'Všechny lokalizace',
exportDocumentLabel: 'Export {{label}}',
exportOptions: 'Možnosti exportu',
'field-depth-label': 'Hloubka',
'field-drafts-label': 'Zahrnout návrhy',
'field-fields-label': 'Pole',
'field-format-label': 'Formát exportu',
'field-limit-label': 'Limita',
'field-locale-label': 'Místní',
'field-name-label': 'Název souboru',
'field-selectionToUse-label': 'Výběr k použití',
'field-sort-label': 'Seřadit podle',
'selectionToUse-allDocuments': 'Použijte všechny dokumenty',
'selectionToUse-currentFilters': 'Použijte aktuální filtry',
'selectionToUse-currentSelection': 'Použijte aktuální výběr',
totalDocumentsCount: '{{count}} celkem dokumentů',
},
}
export const cs: PluginLanguage = {
dateFNSKey: 'cs',
translations: csTranslations,
}

View File

@@ -0,0 +1,27 @@
import type { PluginDefaultTranslationsObject, PluginLanguage } from '../types.js'
export const daTranslations: PluginDefaultTranslationsObject = {
'plugin-import-export': {
allLocales: 'Alle lokaliteter',
exportDocumentLabel: 'Eksport {{label}}',
exportOptions: 'Eksportmuligheder',
'field-depth-label': 'Dybde',
'field-drafts-label': 'Inkluder udkast',
'field-fields-label': 'Felter',
'field-format-label': 'Eksportformat',
'field-limit-label': 'Begrænsning',
'field-locale-label': 'Lokale',
'field-name-label': 'Filnavn',
'field-selectionToUse-label': 'Valg til brug',
'field-sort-label': 'Sorter efter',
'selectionToUse-allDocuments': 'Brug alle dokumenter',
'selectionToUse-currentFilters': 'Brug nuværende filtre',
'selectionToUse-currentSelection': 'Brug nuværende valg',
totalDocumentsCount: '{{count}} samlede dokumenter',
},
}
export const da: PluginLanguage = {
dateFNSKey: 'da',
translations: daTranslations,
}

View File

@@ -0,0 +1,27 @@
import type { PluginDefaultTranslationsObject, PluginLanguage } from '../types.js'
export const deTranslations: PluginDefaultTranslationsObject = {
'plugin-import-export': {
allLocales: 'Alle Gebietsschemata',
exportDocumentLabel: 'Export {{label}}',
exportOptions: 'Exportoptionen',
'field-depth-label': 'Tiefe',
'field-drafts-label': 'Fügen Sie Entwürfe hinzu',
'field-fields-label': 'Felder',
'field-format-label': 'Exportformat',
'field-limit-label': 'Grenze',
'field-locale-label': 'Ort',
'field-name-label': 'Dateiname',
'field-selectionToUse-label': 'Auswahl zur Verwendung',
'field-sort-label': 'Sortieren nach',
'selectionToUse-allDocuments': 'Verwenden Sie alle Dokumente.',
'selectionToUse-currentFilters': 'Verwenden Sie aktuelle Filter',
'selectionToUse-currentSelection': 'Verwenden Sie die aktuelle Auswahl',
totalDocumentsCount: '{{count}} gesamte Dokumente',
},
}
export const de: PluginLanguage = {
dateFNSKey: 'de',
translations: deTranslations,
}

View File

@@ -0,0 +1,26 @@
import type { PluginLanguage } from '../types.js'
export const enTranslations = {
'plugin-import-export': {
allLocales: 'All locales',
exportDocumentLabel: 'Export {{label}}',
exportOptions: 'Export Options',
'field-depth-label': 'Depth',
'field-drafts-label': 'Include drafts',
'field-fields-label': 'Fields',
'field-format-label': 'Export Format',
'field-limit-label': 'Limit',
'field-locale-label': 'Locale',
'field-name-label': 'File name',
'field-selectionToUse-label': 'Selection to use',
'field-sort-label': 'Sort by',
'selectionToUse-allDocuments': 'Use all documents',
'selectionToUse-currentFilters': 'Use current filters',
'selectionToUse-currentSelection': 'Use current selection',
totalDocumentsCount: '{{count}} total documents',
},
}
export const en: PluginLanguage = {
dateFNSKey: 'en-US',
translations: enTranslations,
}

View File

@@ -0,0 +1,27 @@
import type { PluginDefaultTranslationsObject, PluginLanguage } from '../types.js'
export const esTranslations: PluginDefaultTranslationsObject = {
'plugin-import-export': {
allLocales: 'Todas las ubicaciones',
exportDocumentLabel: 'Exportar {{label}}',
exportOptions: 'Opciones de Exportación',
'field-depth-label': 'Profundidad',
'field-drafts-label': 'Incluir borradores',
'field-fields-label': 'Campos',
'field-format-label': 'Formato de Exportación',
'field-limit-label': 'Límite',
'field-locale-label': 'Localidad',
'field-name-label': 'Nombre del archivo',
'field-selectionToUse-label': 'Selección para usar',
'field-sort-label': 'Ordenar por',
'selectionToUse-allDocuments': 'Utilice todos los documentos',
'selectionToUse-currentFilters': 'Utilice los filtros actuales',
'selectionToUse-currentSelection': 'Usar selección actual',
totalDocumentsCount: '{{count}} documentos totales',
},
}
export const es: PluginLanguage = {
dateFNSKey: 'es',
translations: esTranslations,
}

View File

@@ -0,0 +1,27 @@
import type { PluginDefaultTranslationsObject, PluginLanguage } from '../types.js'
export const etTranslations: PluginDefaultTranslationsObject = {
'plugin-import-export': {
allLocales: 'Kõik kohalikud seaded',
exportDocumentLabel: 'Ekspordi {{label}}',
exportOptions: 'Ekspordi valikud',
'field-depth-label': 'Sügavus',
'field-drafts-label': 'Kaasa arvatud mustandid',
'field-fields-label': 'Väljad',
'field-format-label': 'Ekspordi formaat',
'field-limit-label': 'Piirang',
'field-locale-label': 'Lokaal',
'field-name-label': 'Faili nimi',
'field-selectionToUse-label': 'Valiku kasutamine',
'field-sort-label': 'Sorteeri järgi',
'selectionToUse-allDocuments': 'Kasutage kõiki dokumente',
'selectionToUse-currentFilters': 'Kasuta praeguseid filtreid',
'selectionToUse-currentSelection': 'Kasuta praegust valikut',
totalDocumentsCount: '{{count}} dokumendi koguarv',
},
}
export const et: PluginLanguage = {
dateFNSKey: 'et',
translations: etTranslations,
}

View File

@@ -0,0 +1,27 @@
import type { PluginDefaultTranslationsObject, PluginLanguage } from '../types.js'
export const faTranslations: PluginDefaultTranslationsObject = {
'plugin-import-export': {
allLocales: 'تمام مکان ها',
exportDocumentLabel: 'صادر کردن {{label}}',
exportOptions: 'گزینه های صادرات',
'field-depth-label': 'عمق',
'field-drafts-label': 'شامل پیش نویس ها',
'field-fields-label': 'مزارع',
'field-format-label': 'فرمت صادرات',
'field-limit-label': 'محدودیت',
'field-locale-label': 'محلی',
'field-name-label': 'نام فایل',
'field-selectionToUse-label': 'انتخاب برای استفاده',
'field-sort-label': 'مرتب سازی بر اساس',
'selectionToUse-allDocuments': 'از تمام مستندات استفاده کنید',
'selectionToUse-currentFilters': 'از فیلترهای فعلی استفاده کنید',
'selectionToUse-currentSelection': 'از انتخاب فعلی استفاده کنید',
totalDocumentsCount: '{{count}} سند کل',
},
}
export const fa: PluginLanguage = {
dateFNSKey: 'fa-IR',
translations: faTranslations,
}

View File

@@ -0,0 +1,27 @@
import type { PluginDefaultTranslationsObject, PluginLanguage } from '../types.js'
export const frTranslations: PluginDefaultTranslationsObject = {
'plugin-import-export': {
allLocales: 'Tous les paramètres régionaux',
exportDocumentLabel: 'Exporter {{label}}',
exportOptions: "Options d'exportation",
'field-depth-label': 'Profondeur',
'field-drafts-label': 'Inclure les ébauches',
'field-fields-label': 'Champs',
'field-format-label': "Format d'exportation",
'field-limit-label': 'Limite',
'field-locale-label': 'Localisation',
'field-name-label': 'Nom de fichier',
'field-selectionToUse-label': 'Sélection à utiliser',
'field-sort-label': 'Trier par',
'selectionToUse-allDocuments': 'Utilisez tous les documents',
'selectionToUse-currentFilters': 'Utilisez les filtres actuels',
'selectionToUse-currentSelection': 'Utilisez la sélection actuelle',
totalDocumentsCount: '{{count}} documents au total',
},
}
export const fr: PluginLanguage = {
dateFNSKey: 'fr',
translations: frTranslations,
}

View File

@@ -0,0 +1,27 @@
import type { PluginDefaultTranslationsObject, PluginLanguage } from '../types.js'
export const heTranslations: PluginDefaultTranslationsObject = {
'plugin-import-export': {
allLocales: 'כל המיקומים',
exportDocumentLabel: 'ייצוא {{label}}',
exportOptions: 'אפשרויות ייצוא',
'field-depth-label': 'עומק',
'field-drafts-label': 'כלול טיוטות',
'field-fields-label': 'שדות',
'field-format-label': 'פורמט יצוא',
'field-limit-label': 'הגבלה',
'field-locale-label': 'מקום',
'field-name-label': 'שם הקובץ',
'field-selectionToUse-label': 'בחירה לשימוש',
'field-sort-label': 'מיין לפי',
'selectionToUse-allDocuments': 'השתמש בכל המסמכים',
'selectionToUse-currentFilters': 'השתמש במסננים הנוכחיים',
'selectionToUse-currentSelection': 'השתמש בבחירה הנוכחית',
totalDocumentsCount: '{{count}} מסמכים כולל',
},
}
export const he: PluginLanguage = {
dateFNSKey: 'he',
translations: heTranslations,
}

View File

@@ -0,0 +1,27 @@
import type { PluginDefaultTranslationsObject, PluginLanguage } from '../types.js'
export const hrTranslations: PluginDefaultTranslationsObject = {
'plugin-import-export': {
allLocales: 'Sve lokalne postavke',
exportDocumentLabel: 'Izvoz {{label}}',
exportOptions: 'Opcije izvoza',
'field-depth-label': 'Dubina',
'field-drafts-label': 'Uključite nacrte',
'field-fields-label': 'Polja',
'field-format-label': 'Format izvoza',
'field-limit-label': 'Ograničenje',
'field-locale-label': 'Lokalitet',
'field-name-label': 'Naziv datoteke',
'field-selectionToUse-label': 'Odabir za upotrebu',
'field-sort-label': 'Sortiraj po',
'selectionToUse-allDocuments': 'Koristite sve dokumente',
'selectionToUse-currentFilters': 'Koristite trenutne filtre',
'selectionToUse-currentSelection': 'Koristite trenutni odabir',
totalDocumentsCount: '{{count}} ukupno dokumenata',
},
}
export const hr: PluginLanguage = {
dateFNSKey: 'hr',
translations: hrTranslations,
}

View File

@@ -0,0 +1,27 @@
import type { PluginDefaultTranslationsObject, PluginLanguage } from '../types.js'
export const huTranslations: PluginDefaultTranslationsObject = {
'plugin-import-export': {
allLocales: 'Minden helyszín',
exportDocumentLabel: '{{label}} exportálása',
exportOptions: 'Exportálási lehetőségek',
'field-depth-label': 'Mélység',
'field-drafts-label': 'Tartalmazza a vázlatokat',
'field-fields-label': 'Mezők',
'field-format-label': 'Export formátum',
'field-limit-label': 'Korlát',
'field-locale-label': 'Helyszín',
'field-name-label': 'Fájlnév',
'field-selectionToUse-label': 'Használatra kiválasztva',
'field-sort-label': 'Rendezés szerint',
'selectionToUse-allDocuments': 'Használjon minden dokumentumot',
'selectionToUse-currentFilters': 'Használja az aktuális szűrőket',
'selectionToUse-currentSelection': 'Használja a jelenlegi kiválasztást',
totalDocumentsCount: '{{count}} összes dokumentum',
},
}
export const hu: PluginLanguage = {
dateFNSKey: 'hu',
translations: huTranslations,
}

View File

@@ -0,0 +1,27 @@
import type { PluginDefaultTranslationsObject, PluginLanguage } from '../types.js'
export const hyTranslations: PluginDefaultTranslationsObject = {
'plugin-import-export': {
allLocales: 'Բոլոր տեղականությունները',
exportDocumentLabel: 'Փոխարտադրել {{label}}',
exportOptions: 'Արտահանման տարբերակներ',
'field-depth-label': 'Խորություն',
'field-drafts-label': 'Ներառեք սևագրեր',
'field-fields-label': 'Դաշտեր',
'field-format-label': 'Արտահանման ձևաչափ',
'field-limit-label': 'Սահմանափակում',
'field-locale-label': 'Լոկալ',
'field-name-label': 'Ֆայլի անվանումը',
'field-selectionToUse-label': 'Օգտագործման ընտրություն',
'field-sort-label': 'Դասավորել ըստ',
'selectionToUse-allDocuments': 'Օգտագործեք բոլոր փաստաթղթերը',
'selectionToUse-currentFilters': 'Օգտագործեք ընթացիկ ֆիլտրերը',
'selectionToUse-currentSelection': 'Օգտագործել ընթացիկ ընտրությունը',
totalDocumentsCount: '{{count}} ընդհանուր փաստաթուղթեր',
},
}
export const hy: PluginLanguage = {
dateFNSKey: 'hy-AM',
translations: hyTranslations,
}

View File

@@ -0,0 +1,27 @@
import type { PluginDefaultTranslationsObject, PluginLanguage } from '../types.js'
export const itTranslations: PluginDefaultTranslationsObject = {
'plugin-import-export': {
allLocales: 'Tutte le località',
exportDocumentLabel: 'Esporta {{label}}',
exportOptions: 'Opzioni di Esportazione',
'field-depth-label': 'Profondità',
'field-drafts-label': 'Includi bozze',
'field-fields-label': 'Campi',
'field-format-label': 'Formato di Esportazione',
'field-limit-label': 'Limite',
'field-locale-label': 'Locale',
'field-name-label': 'Nome del file',
'field-selectionToUse-label': 'Selezione da utilizzare',
'field-sort-label': 'Ordina per',
'selectionToUse-allDocuments': 'Utilizza tutti i documenti',
'selectionToUse-currentFilters': 'Utilizza i filtri correnti',
'selectionToUse-currentSelection': 'Utilizza la selezione corrente',
totalDocumentsCount: '{{count}} documenti totali',
},
}
export const it: PluginLanguage = {
dateFNSKey: 'it',
translations: itTranslations,
}

View File

@@ -0,0 +1,27 @@
import type { PluginDefaultTranslationsObject, PluginLanguage } from '../types.js'
export const jaTranslations: PluginDefaultTranslationsObject = {
'plugin-import-export': {
allLocales: 'すべてのロケール',
exportDocumentLabel: '{{label}}をエクスポートする',
exportOptions: 'エクスポートオプション',
'field-depth-label': '深さ',
'field-drafts-label': 'ドラフトを含めます',
'field-fields-label': 'フィールド',
'field-format-label': 'エクスポート形式',
'field-limit-label': '制限',
'field-locale-label': 'ロケール',
'field-name-label': 'ファイル名',
'field-selectionToUse-label': '使用する選択',
'field-sort-label': '並び替える',
'selectionToUse-allDocuments': 'すべての文書を使用してください。',
'selectionToUse-currentFilters': '現在のフィルターを使用してください',
'selectionToUse-currentSelection': '現在の選択を使用する',
totalDocumentsCount: '{{count}}合計の文書',
},
}
export const ja: PluginLanguage = {
dateFNSKey: 'ja',
translations: jaTranslations,
}

View File

@@ -0,0 +1,27 @@
import type { PluginDefaultTranslationsObject, PluginLanguage } from '../types.js'
export const koTranslations: PluginDefaultTranslationsObject = {
'plugin-import-export': {
allLocales: '모든 지역 설정',
exportDocumentLabel: '{{label}} 내보내기',
exportOptions: '수출 옵션',
'field-depth-label': '깊이',
'field-drafts-label': '초안을 포함하십시오.',
'field-fields-label': '필드',
'field-format-label': '수출 형식',
'field-limit-label': '한계',
'field-locale-label': '지역',
'field-name-label': '파일 이름',
'field-selectionToUse-label': '사용할 선택',
'field-sort-label': '정렬 방식',
'selectionToUse-allDocuments': '모든 문서를 사용하십시오.',
'selectionToUse-currentFilters': '현재 필터를 사용하십시오.',
'selectionToUse-currentSelection': '현재 선택 항목을 사용하십시오.',
totalDocumentsCount: '{{count}}개의 총 문서',
},
}
export const ko: PluginLanguage = {
dateFNSKey: 'ko',
translations: koTranslations,
}

View File

@@ -0,0 +1,27 @@
import type { PluginDefaultTranslationsObject, PluginLanguage } from '../types.js'
export const ltTranslations: PluginDefaultTranslationsObject = {
'plugin-import-export': {
allLocales: 'Visos vietovės',
exportDocumentLabel: 'Eksportuoti {{label}}',
exportOptions: 'Eksporto parinktys',
'field-depth-label': 'Gylis',
'field-drafts-label': 'Įtraukite juodraščius',
'field-fields-label': 'Laukai',
'field-format-label': 'Eksporto formatas',
'field-limit-label': 'Ribos',
'field-locale-label': 'Lokalė',
'field-name-label': 'Failo pavadinimas',
'field-selectionToUse-label': 'Naudojimo pasirinkimas',
'field-sort-label': 'Rūšiuoti pagal',
'selectionToUse-allDocuments': 'Naudokite visus dokumentus.',
'selectionToUse-currentFilters': 'Naudoti esamus filtrus',
'selectionToUse-currentSelection': 'Naudoti dabartinį pasirinkimą',
totalDocumentsCount: '{{count}} viso dokumentų',
},
}
export const lt: PluginLanguage = {
dateFNSKey: 'lt',
translations: ltTranslations,
}

View File

@@ -0,0 +1,27 @@
import type { PluginDefaultTranslationsObject, PluginLanguage } from '../types.js'
export const lvTranslations: PluginDefaultTranslationsObject = {
'plugin-import-export': {
allLocales: 'Visas lokalitātes',
exportDocumentLabel: 'Eksportēt {{label}}',
exportOptions: 'Eksportēšanas opcijas',
'field-depth-label': 'Dziļums',
'field-drafts-label': 'Iekļaut melnrakstus',
'field-fields-label': 'Lauki',
'field-format-label': 'Eksporta formāts',
'field-limit-label': 'Limits',
'field-locale-label': 'Lokalizācija',
'field-name-label': 'Faila nosaukums',
'field-selectionToUse-label': 'Izvēles lietošana',
'field-sort-label': 'Kārtot pēc',
'selectionToUse-allDocuments': 'Izmantojiet visus dokumentus',
'selectionToUse-currentFilters': 'Izmantot pašreizējos filtrus',
'selectionToUse-currentSelection': 'Izmantot pašreizējo izvēli',
totalDocumentsCount: '{{count}} kopā dokumenti',
},
}
export const lv: PluginLanguage = {
dateFNSKey: 'lv',
translations: lvTranslations,
}

View File

@@ -0,0 +1,27 @@
import type { PluginDefaultTranslationsObject, PluginLanguage } from '../types.js'
export const myTranslations: PluginDefaultTranslationsObject = {
'plugin-import-export': {
allLocales: 'အားလုံးနေရာတွင်',
exportDocumentLabel: 'Eksport {{label}}',
exportOptions: 'Pilihan Eksport',
'field-depth-label': 'အန္တိုင်း',
'field-drafts-label': 'မူကြမ်းများပါဝင်ပါ',
'field-fields-label': 'ကွင်းပျိုးရန်ကွက်များ',
'field-format-label': 'တင်ပို့နည်းအစီအစဉ်',
'field-limit-label': 'ကန့်သတ်ချက်',
'field-locale-label': 'Tempatan',
'field-name-label': 'ဖိုင်နာမည်',
'field-selectionToUse-label': 'Pilihan untuk digunakan',
'field-sort-label': 'စီမံအလိုက်',
'selectionToUse-allDocuments': 'Gunakan semua dokumen',
'selectionToUse-currentFilters': 'Gunakan penapis semasa',
'selectionToUse-currentSelection': 'Gunakan pilihan semasa',
totalDocumentsCount: '{{count}} keseluruhan dokumen',
},
}
export const my: PluginLanguage = {
dateFNSKey: 'en-US',
translations: myTranslations,
}

View File

@@ -0,0 +1,27 @@
import type { PluginDefaultTranslationsObject, PluginLanguage } from '../types.js'
export const nbTranslations: PluginDefaultTranslationsObject = {
'plugin-import-export': {
allLocales: 'Alle steder',
exportDocumentLabel: 'Eksporter {{label}}',
exportOptions: 'Eksportalternativer',
'field-depth-label': 'Dybde',
'field-drafts-label': 'Inkluder utkast',
'field-fields-label': 'Felt',
'field-format-label': 'Eksportformat',
'field-limit-label': 'Begrensning',
'field-locale-label': 'Lokal',
'field-name-label': 'Filnavn',
'field-selectionToUse-label': 'Valg til bruk',
'field-sort-label': 'Sorter etter',
'selectionToUse-allDocuments': 'Bruk alle dokumentene',
'selectionToUse-currentFilters': 'Bruk gjeldende filtre',
'selectionToUse-currentSelection': 'Bruk gjeldende utvalg',
totalDocumentsCount: '{{count}} totalt dokumenter',
},
}
export const nb: PluginLanguage = {
dateFNSKey: 'nb',
translations: nbTranslations,
}

View File

@@ -0,0 +1,27 @@
import type { PluginDefaultTranslationsObject, PluginLanguage } from '../types.js'
export const nlTranslations: PluginDefaultTranslationsObject = {
'plugin-import-export': {
allLocales: 'Alle locaties',
exportDocumentLabel: 'Exporteer {{label}}',
exportOptions: 'Exportmogelijkheden',
'field-depth-label': 'Diepte',
'field-drafts-label': 'Voeg ontwerpen toe',
'field-fields-label': 'Velden',
'field-format-label': 'Exportformaat',
'field-limit-label': 'Limiet',
'field-locale-label': 'Lokale',
'field-name-label': 'Bestandsnaam',
'field-selectionToUse-label': 'Selectie om te gebruiken',
'field-sort-label': 'Sorteer op',
'selectionToUse-allDocuments': 'Gebruik alle documenten',
'selectionToUse-currentFilters': 'Gebruik huidige filters',
'selectionToUse-currentSelection': 'Gebruik huidige selectie',
totalDocumentsCount: '{{count}} totaal aantal documenten',
},
}
export const nl: PluginLanguage = {
dateFNSKey: 'nl',
translations: nlTranslations,
}

View File

@@ -0,0 +1,27 @@
import type { PluginDefaultTranslationsObject, PluginLanguage } from '../types.js'
export const plTranslations: PluginDefaultTranslationsObject = {
'plugin-import-export': {
allLocales: 'Wszystkie lokalizacje',
exportDocumentLabel: 'Eksportuj {{label}}',
exportOptions: 'Opcje eksportu',
'field-depth-label': 'Głębokość',
'field-drafts-label': 'Dołącz szkice',
'field-fields-label': 'Pola',
'field-format-label': 'Format eksportu',
'field-limit-label': 'Limit',
'field-locale-label': 'Lokalizacja',
'field-name-label': 'Nazwa pliku',
'field-selectionToUse-label': 'Wybór do użycia',
'field-sort-label': 'Sortuj według',
'selectionToUse-allDocuments': 'Użyj wszystkich dokumentów.',
'selectionToUse-currentFilters': 'Użyj aktualnych filtrów',
'selectionToUse-currentSelection': 'Użyj aktualnego wyboru',
totalDocumentsCount: '{{count}} łączna liczba dokumentów',
},
}
export const pl: PluginLanguage = {
dateFNSKey: 'pl',
translations: plTranslations,
}

View File

@@ -0,0 +1,27 @@
import type { PluginDefaultTranslationsObject, PluginLanguage } from '../types.js'
export const ptTranslations: PluginDefaultTranslationsObject = {
'plugin-import-export': {
allLocales: 'Todos os locais',
exportDocumentLabel: 'Exportar {{label}}',
exportOptions: 'Opções de Exportação',
'field-depth-label': 'Profundidade',
'field-drafts-label': 'Incluir rascunhos',
'field-fields-label': 'Campos',
'field-format-label': 'Formato de Exportação',
'field-limit-label': 'Limite',
'field-locale-label': 'Localização',
'field-name-label': 'Nome do arquivo',
'field-selectionToUse-label': 'Seleção para usar',
'field-sort-label': 'Ordenar por',
'selectionToUse-allDocuments': 'Use todos os documentos',
'selectionToUse-currentFilters': 'Use os filtros atuais',
'selectionToUse-currentSelection': 'Use a seleção atual',
totalDocumentsCount: '{{count}} documentos totais',
},
}
export const pt: PluginLanguage = {
dateFNSKey: 'pt',
translations: ptTranslations,
}

View File

@@ -0,0 +1,3 @@
for file in *.js; do
mv -- "$file" "${file%.js}.ts"
done

View File

@@ -0,0 +1,27 @@
import type { PluginDefaultTranslationsObject, PluginLanguage } from '../types.js'
export const roTranslations: PluginDefaultTranslationsObject = {
'plugin-import-export': {
allLocales: 'Toate locațiile',
exportDocumentLabel: 'Export {{label}}',
exportOptions: 'Opțiuni de export',
'field-depth-label': 'Adâncime',
'field-drafts-label': 'Includează schițe',
'field-fields-label': 'Campuri',
'field-format-label': 'Format de export',
'field-limit-label': 'Limită',
'field-locale-label': 'Localizare',
'field-name-label': 'Numele fișierului',
'field-selectionToUse-label': 'Selectarea pentru utilizare',
'field-sort-label': 'Sortează după',
'selectionToUse-allDocuments': 'Utilizați toate documentele.',
'selectionToUse-currentFilters': 'Utilizați filtrele curente',
'selectionToUse-currentSelection': 'Utilizați selecția curentă',
totalDocumentsCount: '{{count}} documente totale',
},
}
export const ro: PluginLanguage = {
dateFNSKey: 'ro',
translations: roTranslations,
}

View File

@@ -0,0 +1,27 @@
import type { PluginDefaultTranslationsObject, PluginLanguage } from '../types.js'
export const rsTranslations: PluginDefaultTranslationsObject = {
'plugin-import-export': {
allLocales: 'Sve lokacije',
exportDocumentLabel: 'Извоз {{label}}',
exportOptions: 'Опције извоза',
'field-depth-label': 'Dubina',
'field-drafts-label': 'Uključite nacrte',
'field-fields-label': 'Polja',
'field-format-label': 'Format izvoza',
'field-limit-label': 'Ograničenje',
'field-locale-label': 'Локалитет',
'field-name-label': 'Ime datoteke',
'field-selectionToUse-label': 'Izbor za upotrebu',
'field-sort-label': 'Sortiraj po',
'selectionToUse-allDocuments': 'Koristite sve dokumente',
'selectionToUse-currentFilters': 'Koristite trenutne filtere',
'selectionToUse-currentSelection': 'Koristite trenutni izbor',
totalDocumentsCount: '{{count}} ukupno dokumenata',
},
}
export const rs: PluginLanguage = {
dateFNSKey: 'rs',
translations: rsTranslations,
}

View File

@@ -0,0 +1,27 @@
import type { PluginDefaultTranslationsObject, PluginLanguage } from '../types.js'
export const rsLatinTranslations: PluginDefaultTranslationsObject = {
'plugin-import-export': {
allLocales: 'Sve lokalne postavke',
exportDocumentLabel: 'Izvoz {{label}}',
exportOptions: 'Opcije izvoza',
'field-depth-label': 'Dubina',
'field-drafts-label': 'Uključite nacrte',
'field-fields-label': 'Polja',
'field-format-label': 'Format izvoza',
'field-limit-label': 'Ograničenje',
'field-locale-label': 'Lokalitet',
'field-name-label': 'Ime datoteke',
'field-selectionToUse-label': 'Izbor za upotrebu',
'field-sort-label': 'Sortiraj po',
'selectionToUse-allDocuments': 'Koristite sve dokumente',
'selectionToUse-currentFilters': 'Koristite trenutne filtere',
'selectionToUse-currentSelection': 'Koristi trenutni izbor',
totalDocumentsCount: '{{count}} ukupno dokumenata',
},
}
export const rsLatin: PluginLanguage = {
dateFNSKey: 'rs-Latin',
translations: rsLatinTranslations,
}

View File

@@ -0,0 +1,27 @@
import type { PluginDefaultTranslationsObject, PluginLanguage } from '../types.js'
export const ruTranslations: PluginDefaultTranslationsObject = {
'plugin-import-export': {
allLocales: 'Все локали',
exportDocumentLabel: 'Экспорт {{label}}',
exportOptions: 'Опции экспорта',
'field-depth-label': 'Глубина',
'field-drafts-label': 'Включить черновики',
'field-fields-label': 'Поля',
'field-format-label': 'Формат экспорта',
'field-limit-label': 'Лимит',
'field-locale-label': 'Локаль',
'field-name-label': 'Имя файла',
'field-selectionToUse-label': 'Выбор использования',
'field-sort-label': 'Сортировать по',
'selectionToUse-allDocuments': 'Используйте все документы',
'selectionToUse-currentFilters': 'Использовать текущие фильтры',
'selectionToUse-currentSelection': 'Использовать текущий выбор',
totalDocumentsCount: '{{count}} общее количество документов',
},
}
export const ru: PluginLanguage = {
dateFNSKey: 'ru',
translations: ruTranslations,
}

View File

@@ -0,0 +1,27 @@
import type { PluginDefaultTranslationsObject, PluginLanguage } from '../types.js'
export const skTranslations: PluginDefaultTranslationsObject = {
'plugin-import-export': {
allLocales: 'Všetky miestne nastavenia',
exportDocumentLabel: 'Export {{label}}',
exportOptions: 'Možnosti exportu',
'field-depth-label': 'Hĺbka',
'field-drafts-label': 'Zahrnúť návrhy',
'field-fields-label': 'Polia',
'field-format-label': 'Formát exportu',
'field-limit-label': 'Limit',
'field-locale-label': 'Lokalita',
'field-name-label': 'Názov súboru',
'field-selectionToUse-label': 'Výber na použitie',
'field-sort-label': 'Triediť podľa',
'selectionToUse-allDocuments': 'Použite všetky dokumenty',
'selectionToUse-currentFilters': 'Použiť aktuálne filtre',
'selectionToUse-currentSelection': 'Použiť aktuálny výber',
totalDocumentsCount: '{{count}} celkový počet dokumentov',
},
}
export const sk: PluginLanguage = {
dateFNSKey: 'sk',
translations: skTranslations,
}

View File

@@ -0,0 +1,27 @@
import type { PluginDefaultTranslationsObject, PluginLanguage } from '../types.js'
export const slTranslations: PluginDefaultTranslationsObject = {
'plugin-import-export': {
allLocales: 'Vse lokacije',
exportDocumentLabel: 'Izvozi {{label}}',
exportOptions: 'Možnosti izvoza',
'field-depth-label': 'Globina',
'field-drafts-label': 'Vključi osnutke',
'field-fields-label': 'Polja',
'field-format-label': 'Format izvoza',
'field-limit-label': 'Omejitev',
'field-locale-label': 'Lokalno',
'field-name-label': 'Ime datoteke',
'field-selectionToUse-label': 'Izbor za uporabo',
'field-sort-label': 'Razvrsti po',
'selectionToUse-allDocuments': 'Uporabite vse dokumente',
'selectionToUse-currentFilters': 'Uporabite trenutne filtre.',
'selectionToUse-currentSelection': 'Uporabi trenutno izbiro',
totalDocumentsCount: '{{count}} skupno dokumentov',
},
}
export const sl: PluginLanguage = {
dateFNSKey: 'sl-SI',
translations: slTranslations,
}

View File

@@ -0,0 +1,27 @@
import type { PluginDefaultTranslationsObject, PluginLanguage } from '../types.js'
export const svTranslations: PluginDefaultTranslationsObject = {
'plugin-import-export': {
allLocales: 'Alla platser',
exportDocumentLabel: 'Exportera {{label}}',
exportOptions: 'Exportalternativ',
'field-depth-label': 'Djup',
'field-drafts-label': 'Inkludera utkast',
'field-fields-label': 'Fält',
'field-format-label': 'Exportformat',
'field-limit-label': 'Begränsning',
'field-locale-label': 'Lokal',
'field-name-label': 'Filnamn',
'field-selectionToUse-label': 'Val att använda',
'field-sort-label': 'Sortera efter',
'selectionToUse-allDocuments': 'Använd alla dokument',
'selectionToUse-currentFilters': 'Använd aktuella filter',
'selectionToUse-currentSelection': 'Använd nuvarande urval',
totalDocumentsCount: '{{count}} totala dokument',
},
}
export const sv: PluginLanguage = {
dateFNSKey: 'sv',
translations: svTranslations,
}

Some files were not shown because too many files have changed in this diff Show More