Compare commits
44 Commits
feat/folde
...
fix/resolv
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
25fa48cd0f | ||
|
|
e8401d51a2 | ||
|
|
2a41d3fbb1 | ||
|
|
07e9444c09 | ||
|
|
c772a3207c | ||
|
|
c701dd41a9 | ||
|
|
5dd13f2873 | ||
|
|
878be913fd | ||
|
|
4dfb2d24bb | ||
|
|
230128b92e | ||
|
|
23f42040ab | ||
|
|
8596ac5694 | ||
|
|
0524f198a6 | ||
|
|
5f2e846350 | ||
|
|
d3265b9931 | ||
|
|
33261b36bf | ||
|
|
73d4201df8 | ||
|
|
324daff553 | ||
|
|
50d3da5824 | ||
|
|
41aac41df4 | ||
|
|
6a5b95af7c | ||
|
|
22b1858ee8 | ||
|
|
2e7bfcbd63 | ||
|
|
3ee9a32a38 | ||
|
|
c2d38b4109 | ||
|
|
5d9c537145 | ||
|
|
904b6a6dbe | ||
|
|
cc6de7ef42 | ||
|
|
a3ef4fbfac | ||
|
|
e9ff611879 | ||
|
|
5825d0cfc7 | ||
|
|
103b476c82 | ||
|
|
a3279b319e | ||
|
|
bbb0ab784c | ||
|
|
32eac5b0c2 | ||
|
|
86098c9140 | ||
|
|
2ab8e2e194 | ||
|
|
1235a183ff | ||
|
|
81d333f4b0 | ||
|
|
4fe3423e54 | ||
|
|
7fd2cdf04c | ||
|
|
e8c2b15e2b | ||
|
|
3127d6ad6d | ||
|
|
72ab319d37 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -3,6 +3,7 @@ package-lock.json
|
||||
dist
|
||||
/.idea/*
|
||||
!/.idea/runConfigurations
|
||||
/.idea/runConfigurations/_template*
|
||||
!/.idea/payload.iml
|
||||
|
||||
# Custom actions
|
||||
|
||||
@@ -132,6 +132,7 @@ The following options are available:
|
||||
| `hideAPIURL` | Hides the "API URL" meta field while editing documents within this Collection. |
|
||||
| `enableRichTextLink` | The [Rich Text](../fields/rich-text) field features a `Link` element which allows for users to automatically reference related documents within their rich text. Set to `true` by default. |
|
||||
| `enableRichTextRelationship` | The [Rich Text](../fields/rich-text) field features a `Relationship` element which allows for users to automatically reference related documents within their rich text. Set to `true` by default. |
|
||||
| `folders` | A boolean to enable folders for a given collection. Defaults to `false`. [More details](../folders/overview). |
|
||||
| `meta` | Page metadata overrides to apply to this Collection within the Admin Panel. [More details](../admin/metadata). |
|
||||
| `preview` | Function to generate preview URLs within the Admin Panel that can point to your app. [More details](../admin/preview). |
|
||||
| `livePreview` | Enable real-time editing for instant visual feedback of your front-end application. [More details](../live-preview/overview). |
|
||||
|
||||
@@ -84,6 +84,7 @@ The following options are available:
|
||||
| **`csrf`** | A whitelist array of URLs to allow Payload to accept cookies from. [More details](../authentication/cookies#csrf-attacks). |
|
||||
| **`defaultDepth`** | If a user does not specify `depth` while requesting a resource, this depth will be used. [More details](../queries/depth). |
|
||||
| **`defaultMaxTextLength`** | The maximum allowed string length to be permitted application-wide. Helps to prevent malicious public document creation. |
|
||||
| `folders` | An optional object to configure global folder settings. [More details](../folders/overview). |
|
||||
| `queryPresets` | An object that to configure Collection Query Presets. [More details](../query-presets/overview). |
|
||||
| **`maxDepth`** | The maximum allowed depth to be permitted application-wide. This setting helps prevent against malicious queries. Defaults to `10`. [More details](../queries/depth). |
|
||||
| **`indexSortableFields`** | Automatically index all sortable top-level fields in the database to improve sort performance and add database compatibility for Azure Cosmos and similar. |
|
||||
|
||||
@@ -100,7 +100,7 @@ Here are the available Presentational Fields:
|
||||
|
||||
### Virtual Fields
|
||||
|
||||
Virtual fields are used to display data that is not stored in the database. They are useful for displaying computed values that populate within the APi response through hooks, etc.
|
||||
Virtual fields are used to display data that is not stored in the database. They are useful for displaying computed values that populate within the API response through hooks, etc.
|
||||
|
||||
Here are the available Virtual Fields:
|
||||
|
||||
|
||||
100
docs/folders/overview.mdx
Normal file
100
docs/folders/overview.mdx
Normal file
@@ -0,0 +1,100 @@
|
||||
---
|
||||
title: Folders
|
||||
label: Folders
|
||||
order: 10
|
||||
desc: Folders allow you to group documents across collections, and are a great way to organize your content.
|
||||
keywords: folders, folder, content organization
|
||||
---
|
||||
|
||||
Folders allow you to group documents across collections, and are a great way to organize your content. Folders are built on top of relationship fields, when you enable folders on a collection, Payload adds a hidden relationship field `folders`, that relates to a folder — or no folder. Folders also have the `folder` field, allowing folders to be nested within other folders.
|
||||
|
||||
The configuration for folders is done in two places, the collection config and the Payload config. The collection config is where you enable folders, and the Payload config is where you configure the global folder settings.
|
||||
|
||||
## Folder Configuration
|
||||
|
||||
On the payload config, you can configure the following settings under the `folders` property:
|
||||
|
||||
```ts
|
||||
// Type definition
|
||||
|
||||
type RootFoldersConfiguration = {
|
||||
/**
|
||||
* 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>)[]
|
||||
/**
|
||||
* Ability to view hidden fields and collections related to folders
|
||||
*
|
||||
* @default false
|
||||
*/
|
||||
debug?: boolean
|
||||
/**
|
||||
* The Folder field name
|
||||
*
|
||||
* @default "folder"
|
||||
*/
|
||||
fieldName?: string
|
||||
/**
|
||||
* Slug for the folder collection
|
||||
*
|
||||
* @default "payload-folders"
|
||||
*/
|
||||
slug?: string
|
||||
}
|
||||
```
|
||||
|
||||
```ts
|
||||
// Example usage
|
||||
|
||||
import { buildConfig } from 'payload'
|
||||
|
||||
const config = buildConfig({
|
||||
// ...
|
||||
folders: {
|
||||
// highlight-start
|
||||
debug: true, // optional
|
||||
collectionOverrides: [
|
||||
async ({ collection }) => {
|
||||
return collection
|
||||
},
|
||||
], // optional
|
||||
fieldName: 'folder', // optional
|
||||
slug: 'payload-folders', // optional
|
||||
// highlight-end
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
## Collection Configuration
|
||||
|
||||
To enable folders on a collection, you need to set the `admin.folders` property to `true` on the collection config. This will add a hidden relationship field to the collection that relates to a folder — or no folder.
|
||||
|
||||
```ts
|
||||
// Type definition
|
||||
|
||||
type CollectionFoldersConfiguration = boolean
|
||||
```
|
||||
|
||||
```ts
|
||||
// Example usage
|
||||
|
||||
import { buildConfig } from 'payload'
|
||||
|
||||
const config = buildConfig({
|
||||
collections: [
|
||||
{
|
||||
slug: 'pages',
|
||||
// highlight-start
|
||||
admin: {
|
||||
folders: true, // defaults to false
|
||||
},
|
||||
// highlight-end
|
||||
},
|
||||
],
|
||||
})
|
||||
```
|
||||
@@ -85,6 +85,7 @@ formBuilderPlugin({
|
||||
checkbox: true,
|
||||
number: true,
|
||||
message: true,
|
||||
date: false,
|
||||
payment: false,
|
||||
},
|
||||
})
|
||||
@@ -349,6 +350,18 @@ Maps to a `checkbox` input on your front-end. Used to collect a boolean value.
|
||||
| `width` | string | The width of the field on the front-end. |
|
||||
| `required` | checkbox | Whether or not the field is required when submitted. |
|
||||
|
||||
### Date
|
||||
|
||||
Maps to a `date` input on your front-end. Used to collect a date value.
|
||||
|
||||
| Property | Type | Description |
|
||||
| -------------- | -------- | ---------------------------------------------------- |
|
||||
| `name` | string | The name of the field. |
|
||||
| `label` | string | The label of the field. |
|
||||
| `defaultValue` | date | The default value of the field. |
|
||||
| `width` | string | The width of the field on the front-end. |
|
||||
| `required` | checkbox | Whether or not the field is required when submitted. |
|
||||
|
||||
### Number
|
||||
|
||||
Maps to a `number` input on your front-end. Used to collect a number.
|
||||
@@ -421,6 +434,42 @@ formBuilderPlugin({
|
||||
})
|
||||
```
|
||||
|
||||
### Customizing the date field default value
|
||||
|
||||
You can custommise the default value of the date field and any other aspects of the date block in this way.
|
||||
Note that the end submission source will be responsible for the timezone of the date. Payload only stores the date in UTC format.
|
||||
|
||||
```ts
|
||||
import { fields as formFields } from '@payloadcms/plugin-form-builder'
|
||||
|
||||
// payload.config.ts
|
||||
formBuilderPlugin({
|
||||
fields: {
|
||||
// date: true, // just enable it without any customizations
|
||||
date: {
|
||||
...formFields.date,
|
||||
fields: [
|
||||
...(formFields.date && 'fields' in formFields.date
|
||||
? formFields.date.fields.map((field) => {
|
||||
if ('name' in field && field.name === 'defaultValue') {
|
||||
return {
|
||||
...field,
|
||||
timezone: true, // optionally enable timezone
|
||||
admin: {
|
||||
...field.admin,
|
||||
description: 'This is a date field',
|
||||
},
|
||||
}
|
||||
}
|
||||
return field
|
||||
})
|
||||
: []),
|
||||
],
|
||||
},
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
## Email
|
||||
|
||||
This plugin relies on the [email configuration](../email/overview) defined in your Payload configuration. It will read from your config and attempt to send your emails using the credentials provided.
|
||||
|
||||
@@ -6,14 +6,14 @@ desc: Converting between lexical richtext and HTML
|
||||
keywords: lexical, richtext, html
|
||||
---
|
||||
|
||||
## Converting Rich Text to HTML
|
||||
## Rich Text to HTML
|
||||
|
||||
There are two main approaches to convert your Lexical-based rich text to HTML:
|
||||
|
||||
1. **Generate HTML on-demand (Recommended)**: Convert JSON to HTML wherever you need it, on-demand.
|
||||
2. **Generate HTML within your Collection**: Create a new field that automatically converts your saved JSON content to HTML. This is not recommended because it adds overhead to the Payload API and may not work well with live preview.
|
||||
|
||||
### Generating HTML on-demand (Recommended)
|
||||
### On-demand
|
||||
|
||||
To convert JSON to HTML on-demand, use the `convertLexicalToHTML` function from `@payloadcms/richtext-lexical/html`. Here's an example of how to use it in a React component in your frontend:
|
||||
|
||||
@@ -32,61 +32,81 @@ export const MyComponent = ({ data }: { data: SerializedEditorState }) => {
|
||||
}
|
||||
```
|
||||
|
||||
### Converting Lexical Blocks
|
||||
#### Dynamic Population (Advanced)
|
||||
|
||||
If your rich text includes Lexical blocks, you need to provide a way to convert them to HTML. For example:
|
||||
By default, `convertLexicalToHTML` expects fully populated data (e.g. uploads, links, etc.). If you need to dynamically fetch and populate those nodes, use the async variant, `convertLexicalToHTMLAsync`, from `@payloadcms/richtext-lexical/html-async`. You must provide a `populate` function:
|
||||
|
||||
```tsx
|
||||
'use client'
|
||||
|
||||
import type { MyInlineBlock, MyTextBlock } from '@/payload-types'
|
||||
import type {
|
||||
DefaultNodeTypes,
|
||||
SerializedBlockNode,
|
||||
SerializedInlineBlockNode,
|
||||
} from '@payloadcms/richtext-lexical'
|
||||
import type { SerializedEditorState } from '@payloadcms/richtext-lexical/lexical'
|
||||
|
||||
import {
|
||||
convertLexicalToHTML,
|
||||
type HTMLConvertersFunction,
|
||||
} from '@payloadcms/richtext-lexical/html'
|
||||
import React from 'react'
|
||||
|
||||
type NodeTypes =
|
||||
| DefaultNodeTypes
|
||||
| SerializedBlockNode<MyTextBlock>
|
||||
| SerializedInlineBlockNode<MyInlineBlock>
|
||||
|
||||
const htmlConverters: HTMLConvertersFunction<NodeTypes> = ({
|
||||
defaultConverters,
|
||||
}) => ({
|
||||
...defaultConverters,
|
||||
blocks: {
|
||||
// Each key should match your block's slug
|
||||
myTextBlock: ({ node, providedCSSString }) =>
|
||||
`<div style="background-color: red;${providedCSSString}">${node.fields.text}</div>`,
|
||||
},
|
||||
inlineBlocks: {
|
||||
// Each key should match your inline block's slug
|
||||
myInlineBlock: ({ node, providedStyleTag }) =>
|
||||
`<span${providedStyleTag}>${node.fields.text}</span$>`,
|
||||
},
|
||||
})
|
||||
import { getRestPopulateFn } from '@payloadcms/richtext-lexical/client'
|
||||
import { convertLexicalToHTMLAsync } from '@payloadcms/richtext-lexical/html-async'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
|
||||
export const MyComponent = ({ data }: { data: SerializedEditorState }) => {
|
||||
const html = convertLexicalToHTML({
|
||||
converters: htmlConverters,
|
||||
data,
|
||||
})
|
||||
const [html, setHTML] = useState<null | string>(null)
|
||||
useEffect(() => {
|
||||
async function convert() {
|
||||
const html = await convertLexicalToHTMLAsync({
|
||||
data,
|
||||
populate: getRestPopulateFn({
|
||||
apiURL: `http://localhost:3000/api`,
|
||||
}),
|
||||
})
|
||||
setHTML(html)
|
||||
}
|
||||
|
||||
return <div dangerouslySetInnerHTML={{ __html: html }} />
|
||||
void convert()
|
||||
}, [data])
|
||||
|
||||
return html && <div dangerouslySetInnerHTML={{ __html: html }} />
|
||||
}
|
||||
```
|
||||
|
||||
### Outputting HTML from the Collection
|
||||
Using the REST populate function will send a separate request for each node. If you need to populate a large number of nodes, this may be slow. For improved performance on the server, you can use the `getPayloadPopulateFn` function:
|
||||
|
||||
To automatically generate HTML from the saved richText field in your Collection, use the `lexicalHTMLField()` helper. This approach converts the JSON to HTML using an `afterRead` hook. For instance:
|
||||
```tsx
|
||||
import type { SerializedEditorState } from '@payloadcms/richtext-lexical/lexical'
|
||||
|
||||
import { getPayloadPopulateFn } from '@payloadcms/richtext-lexical'
|
||||
import { convertLexicalToHTMLAsync } from '@payloadcms/richtext-lexical/html-async'
|
||||
import { getPayload } from 'payload'
|
||||
import React from 'react'
|
||||
|
||||
import config from '../../config.js'
|
||||
|
||||
export const MyRSCComponent = async ({
|
||||
data,
|
||||
}: {
|
||||
data: SerializedEditorState
|
||||
}) => {
|
||||
const payload = await getPayload({
|
||||
config,
|
||||
})
|
||||
|
||||
const html = await convertLexicalToHTMLAsync({
|
||||
data,
|
||||
populate: await getPayloadPopulateFn({
|
||||
currentDepth: 0,
|
||||
depth: 1,
|
||||
payload,
|
||||
}),
|
||||
})
|
||||
|
||||
return html && <div dangerouslySetInnerHTML={{ __html: html }} />
|
||||
}
|
||||
```
|
||||
|
||||
### HTML field
|
||||
|
||||
The `lexicalHTMLField()` helper converts JSON to HTML and saves it in a field that is updated every time you read it via an `afterRead` hook. It's generally not recommended for two reasons:
|
||||
|
||||
1. It creates a column with duplicate content in another format.
|
||||
2. In [client-side live preview](/docs/live-preview/client), it makes it not "live".
|
||||
|
||||
Consider using the [on-demand HTML converter above](/docs/rich-text/converting-html#on-demand-recommended) or the [JSX converter](/docs/rich-text/converting-jsx) unless you have a good reason.
|
||||
|
||||
```ts
|
||||
import type { HTMLConvertersFunction } from '@payloadcms/richtext-lexical/html'
|
||||
@@ -154,74 +174,59 @@ const Pages: CollectionConfig = {
|
||||
}
|
||||
```
|
||||
|
||||
### Generating HTML in Your Frontend with Dynamic Population (Advanced)
|
||||
## Blocks to HTML
|
||||
|
||||
By default, `convertLexicalToHTML` expects fully populated data (e.g. uploads, links, etc.). If you need to dynamically fetch and populate those nodes, use the async variant, `convertLexicalToHTMLAsync`, from `@payloadcms/richtext-lexical/html-async`. You must provide a `populate` function:
|
||||
If your rich text includes Lexical blocks, you need to provide a way to convert them to HTML. For example:
|
||||
|
||||
```tsx
|
||||
'use client'
|
||||
|
||||
import type { MyInlineBlock, MyTextBlock } from '@/payload-types'
|
||||
import type {
|
||||
DefaultNodeTypes,
|
||||
SerializedBlockNode,
|
||||
SerializedInlineBlockNode,
|
||||
} from '@payloadcms/richtext-lexical'
|
||||
import type { SerializedEditorState } from '@payloadcms/richtext-lexical/lexical'
|
||||
|
||||
import { getRestPopulateFn } from '@payloadcms/richtext-lexical/client'
|
||||
import { convertLexicalToHTMLAsync } from '@payloadcms/richtext-lexical/html-async'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
|
||||
export const MyComponent = ({ data }: { data: SerializedEditorState }) => {
|
||||
const [html, setHTML] = useState<null | string>(null)
|
||||
useEffect(() => {
|
||||
async function convert() {
|
||||
const html = await convertLexicalToHTMLAsync({
|
||||
data,
|
||||
populate: getRestPopulateFn({
|
||||
apiURL: `http://localhost:3000/api`,
|
||||
}),
|
||||
})
|
||||
setHTML(html)
|
||||
}
|
||||
|
||||
void convert()
|
||||
}, [data])
|
||||
|
||||
return html && <div dangerouslySetInnerHTML={{ __html: html }} />
|
||||
}
|
||||
```
|
||||
|
||||
Using the REST populate function will send a separate request for each node. If you need to populate a large number of nodes, this may be slow. For improved performance on the server, you can use the `getPayloadPopulateFn` function:
|
||||
|
||||
```tsx
|
||||
import type { SerializedEditorState } from '@payloadcms/richtext-lexical/lexical'
|
||||
|
||||
import { getPayloadPopulateFn } from '@payloadcms/richtext-lexical'
|
||||
import { convertLexicalToHTMLAsync } from '@payloadcms/richtext-lexical/html-async'
|
||||
import { getPayload } from 'payload'
|
||||
import {
|
||||
convertLexicalToHTML,
|
||||
type HTMLConvertersFunction,
|
||||
} from '@payloadcms/richtext-lexical/html'
|
||||
import React from 'react'
|
||||
|
||||
import config from '../../config.js'
|
||||
type NodeTypes =
|
||||
| DefaultNodeTypes
|
||||
| SerializedBlockNode<MyTextBlock>
|
||||
| SerializedInlineBlockNode<MyInlineBlock>
|
||||
|
||||
export const MyRSCComponent = async ({
|
||||
data,
|
||||
}: {
|
||||
data: SerializedEditorState
|
||||
}) => {
|
||||
const payload = await getPayload({
|
||||
config,
|
||||
})
|
||||
const htmlConverters: HTMLConvertersFunction<NodeTypes> = ({
|
||||
defaultConverters,
|
||||
}) => ({
|
||||
...defaultConverters,
|
||||
blocks: {
|
||||
// Each key should match your block's slug
|
||||
myTextBlock: ({ node, providedCSSString }) =>
|
||||
`<div style="background-color: red;${providedCSSString}">${node.fields.text}</div>`,
|
||||
},
|
||||
inlineBlocks: {
|
||||
// Each key should match your inline block's slug
|
||||
myInlineBlock: ({ node, providedStyleTag }) =>
|
||||
`<span${providedStyleTag}>${node.fields.text}</span$>`,
|
||||
},
|
||||
})
|
||||
|
||||
const html = await convertLexicalToHTMLAsync({
|
||||
export const MyComponent = ({ data }: { data: SerializedEditorState }) => {
|
||||
const html = convertLexicalToHTML({
|
||||
converters: htmlConverters,
|
||||
data,
|
||||
populate: await getPayloadPopulateFn({
|
||||
currentDepth: 0,
|
||||
depth: 1,
|
||||
payload,
|
||||
}),
|
||||
})
|
||||
|
||||
return html && <div dangerouslySetInnerHTML={{ __html: html }} />
|
||||
return <div dangerouslySetInnerHTML={{ __html: html }} />
|
||||
}
|
||||
```
|
||||
|
||||
## Converting HTML to Richtext
|
||||
## HTML to Richtext
|
||||
|
||||
If you need to convert raw HTML into a Lexical editor state, use `convertHTMLToLexical` from `@payloadcms/richtext-lexical`, along with the [editorConfigFactory to retrieve the editor config](/docs/rich-text/converters#retrieving-the-editor-config):
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ desc: Converting between lexical richtext and JSX
|
||||
keywords: lexical, richtext, jsx
|
||||
---
|
||||
|
||||
## Converting Richtext to JSX
|
||||
## Richtext to JSX
|
||||
|
||||
To convert richtext to JSX, import the `RichText` component from `@payloadcms/richtext-lexical/react` and pass the richtext content to it:
|
||||
|
||||
@@ -28,7 +28,7 @@ The `RichText` component includes built-in converters for common Lexical nodes.
|
||||
populated data to work correctly.
|
||||
</Banner>
|
||||
|
||||
### Converting Internal Links
|
||||
### Internal Links
|
||||
|
||||
By default, Payload doesn't know how to convert **internal** links to JSX, as it doesn't know what the corresponding URL of the internal link is. You'll notice that you get a "found internal link, but internalDocToHref is not provided" error in the console when you try to render content with internal links.
|
||||
|
||||
@@ -81,7 +81,7 @@ export const MyComponent: React.FC<{
|
||||
}
|
||||
```
|
||||
|
||||
### Converting Lexical Blocks
|
||||
### Lexical Blocks
|
||||
|
||||
If your rich text includes custom Blocks or Inline Blocks, you must supply custom converters that match each block's slug. This converter is not included by default, as Payload doesn't know how to render your custom blocks.
|
||||
|
||||
@@ -133,7 +133,7 @@ export const MyComponent: React.FC<{
|
||||
}
|
||||
```
|
||||
|
||||
### Overriding Default JSX Converters
|
||||
### Overriding Converters
|
||||
|
||||
You can override any of the default JSX converters by passing passing your custom converter, keyed to the node type, to the `converters` prop / the converters function.
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ desc: Converting between lexical richtext and Markdown / MDX
|
||||
keywords: lexical, richtext, markdown, md, mdx
|
||||
---
|
||||
|
||||
## Converting Richtext to Markdown
|
||||
## Richtext to Markdown
|
||||
|
||||
If you have access to the Payload Config and the [lexical editor config](/docs/rich-text/converters#retrieving-the-editor-config), you can convert the lexical editor state to Markdown with the following:
|
||||
|
||||
@@ -91,7 +91,7 @@ const Pages: CollectionConfig = {
|
||||
}
|
||||
```
|
||||
|
||||
## Converting Markdown to Richtext
|
||||
## Markdown to Richtext
|
||||
|
||||
If you have access to the Payload Config and the [lexical editor config](/docs/rich-text/converters#retrieving-the-editor-config), you can convert Markdown to the lexical editor state with the following:
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ desc: Converting between lexical richtext and plaintext
|
||||
keywords: lexical, richtext, plaintext, text
|
||||
---
|
||||
|
||||
## Converting Richtext to Plaintext
|
||||
## Richtext to Plaintext
|
||||
|
||||
Here's how you can convert richtext data to plaintext using `@payloadcms/richtext-lexical/plaintext`.
|
||||
|
||||
|
||||
@@ -20,7 +20,6 @@ type SearchParam = {
|
||||
|
||||
const subQueryOptions = {
|
||||
lean: true,
|
||||
limit: 50,
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -184,7 +183,7 @@ export async function buildSearchParam({
|
||||
select[joinPath] = true
|
||||
}
|
||||
|
||||
const result = await SubModel.find(subQuery).lean().limit(50).select(select)
|
||||
const result = await SubModel.find(subQuery).lean().select(select)
|
||||
|
||||
const $in: unknown[] = []
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -21,13 +21,22 @@ export const DefaultNavClient: React.FC<{
|
||||
const {
|
||||
config: {
|
||||
admin: {
|
||||
routes: { folders: foldersRoute },
|
||||
routes: { browseByFolder: foldersRoute },
|
||||
},
|
||||
folders: { collections: folderCollections = {}, enabled } = {},
|
||||
collections,
|
||||
routes: { admin: adminRoute },
|
||||
},
|
||||
} = useConfig()
|
||||
|
||||
const [folderCollectionSlugs] = React.useState<string[]>(() => {
|
||||
return collections.reduce<string[]>((acc, collection) => {
|
||||
if (collection.admin.folders) {
|
||||
acc.push(collection.slug)
|
||||
}
|
||||
return acc
|
||||
}, [])
|
||||
})
|
||||
|
||||
const { i18n } = useTranslation()
|
||||
|
||||
const folderURL = formatAdminURL({
|
||||
@@ -39,9 +48,7 @@ export const DefaultNavClient: React.FC<{
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
{enabled && Object.keys(folderCollections).length > 0 && (
|
||||
<BrowseByFolderButton active={viewingRootFolderView} />
|
||||
)}
|
||||
{folderCollectionSlugs.length > 0 && <BrowseByFolderButton active={viewingRootFolderView} />}
|
||||
{groups.map(({ entities, label }, key) => {
|
||||
return (
|
||||
<NavGroup isOpen={navPreferences?.groups?.[label]?.open} key={key} label={label}>
|
||||
|
||||
@@ -5,13 +5,15 @@ import type {
|
||||
ListQuery,
|
||||
} from 'payload'
|
||||
|
||||
import { DefaultFolderView, FolderProvider, HydrateAuthProvider } from '@payloadcms/ui'
|
||||
import { DefaultBrowseByFolderView, 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'
|
||||
|
||||
import { getPreferences } from '../../utilities/getPreferences.js'
|
||||
|
||||
export type BuildFolderViewArgs = {
|
||||
customCellProps?: Record<string, any>
|
||||
disableBulkDelete?: boolean
|
||||
@@ -23,13 +25,14 @@ export type BuildFolderViewArgs = {
|
||||
query: ListQuery
|
||||
} & AdminViewServerProps
|
||||
|
||||
export const buildFolderView = async (
|
||||
export const buildBrowseByFolderView = async (
|
||||
args: BuildFolderViewArgs,
|
||||
): Promise<BuildCollectionFolderViewResult> => {
|
||||
const {
|
||||
disableBulkDelete,
|
||||
disableBulkEdit,
|
||||
enableRowSelections,
|
||||
folderCollectionSlugs,
|
||||
folderID,
|
||||
initPageResult,
|
||||
isInDrawer,
|
||||
@@ -51,9 +54,7 @@ export const buildFolderView = async (
|
||||
visibleEntities,
|
||||
} = initPageResult
|
||||
|
||||
const allFolderCollectionSlugs = Object.keys(config?.folders?.collections || {})
|
||||
|
||||
const collections = allFolderCollectionSlugs.filter(
|
||||
const collections = folderCollectionSlugs.filter(
|
||||
(collectionSlug) =>
|
||||
permissions?.collections?.[collectionSlug]?.read &&
|
||||
visibleEntities.collections.includes(collectionSlug),
|
||||
@@ -64,11 +65,10 @@ export const buildFolderView = async (
|
||||
}
|
||||
|
||||
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]
|
||||
: [...folderCollectionSlugs, config.folders.slug]
|
||||
|
||||
const {
|
||||
routes: { admin: adminRoute },
|
||||
@@ -85,18 +85,25 @@ export const buildFolderView = async (
|
||||
|
||||
if (
|
||||
!isInDrawer &&
|
||||
((resolvedFolderID && folderID && folderID !== String(resolvedFolderID)) ||
|
||||
((resolvedFolderID && folderID && folderID !== resolvedFolderID) ||
|
||||
(folderID && !resolvedFolderID))
|
||||
) {
|
||||
return redirect(
|
||||
formatAdminURL({
|
||||
adminRoute,
|
||||
path: config.admin.routes.folders,
|
||||
path: config.admin.routes.browseByFolder,
|
||||
serverURL: config.serverURL,
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
const browseByFolderPreferences = await getPreferences<{ viewPreference: string }>(
|
||||
'browse-by-folder',
|
||||
payload,
|
||||
user.id,
|
||||
user.collection,
|
||||
)
|
||||
|
||||
const serverProps: Omit<FolderListViewServerPropsOnly, 'collectionConfig' | 'listPreferences'> = {
|
||||
documents,
|
||||
i18n,
|
||||
@@ -119,7 +126,7 @@ export const buildFolderView = async (
|
||||
|
||||
// documents cannot be created without a parent folder in this view
|
||||
const hasCreatePermissionCollectionSlugs = folderID
|
||||
? [config.folders.slug, ...allFolderCollectionSlugs]
|
||||
? [config.folders.slug, ...folderCollectionSlugs]
|
||||
: [config.folders.slug]
|
||||
|
||||
return {
|
||||
@@ -128,6 +135,7 @@ export const buildFolderView = async (
|
||||
breadcrumbs={breadcrumbs}
|
||||
documents={documents}
|
||||
filteredCollectionSlugs={selectedCollectionSlugs}
|
||||
folderCollectionSlugs={folderCollectionSlugs}
|
||||
folderID={folderID}
|
||||
subfolders={subfolders}
|
||||
>
|
||||
@@ -140,9 +148,10 @@ export const buildFolderView = async (
|
||||
enableRowSelections,
|
||||
hasCreatePermissionCollectionSlugs,
|
||||
selectedCollectionSlugs,
|
||||
viewPreference: browseByFolderPreferences?.value?.viewPreference,
|
||||
},
|
||||
// Component:config.folders?.components?.views?.list?.Component,
|
||||
Fallback: DefaultFolderView,
|
||||
Fallback: DefaultBrowseByFolderView,
|
||||
importMap: payload.importMap,
|
||||
serverProps,
|
||||
})}
|
||||
@@ -4,11 +4,11 @@ import { notFound } from 'next/navigation.js'
|
||||
|
||||
import type { BuildFolderViewArgs } from './buildView.js'
|
||||
|
||||
import { buildFolderView } from './buildView.js'
|
||||
import { buildBrowseByFolderView } from './buildView.js'
|
||||
|
||||
export const FolderView: React.FC<BuildFolderViewArgs> = async (args) => {
|
||||
export const BrowseByFolder: React.FC<BuildFolderViewArgs> = async (args) => {
|
||||
try {
|
||||
const { View } = await buildFolderView(args)
|
||||
const { View } = await buildBrowseByFolderView(args)
|
||||
return View
|
||||
} catch (error) {
|
||||
if (error.message === 'not-found') {
|
||||
@@ -13,6 +13,8 @@ import { redirect } from 'next/navigation.js'
|
||||
import { getFolderData, parseDocumentID } from 'payload'
|
||||
import React from 'react'
|
||||
|
||||
import { getPreferences } from '../../utilities/getPreferences.js'
|
||||
|
||||
// import { renderFolderViewSlots } from './renderFolderViewSlots.js'
|
||||
|
||||
export type BuildCollectionFolderViewStateArgs = {
|
||||
@@ -35,6 +37,7 @@ export const buildCollectionFolderView = async (
|
||||
disableBulkDelete,
|
||||
disableBulkEdit,
|
||||
enableRowSelections,
|
||||
folderCollectionSlugs,
|
||||
folderID,
|
||||
initPageResult,
|
||||
isInDrawer,
|
||||
@@ -66,12 +69,12 @@ export const buildCollectionFolderView = async (
|
||||
if (collectionConfig) {
|
||||
const query = queryFromArgs || queryFromReq
|
||||
|
||||
// const collectionFolderPreferences = await upsertPreferences<ListPreferences>({
|
||||
// key: `${collectionSlug}-collection-folder`,
|
||||
// req,
|
||||
// value: {
|
||||
// },
|
||||
// })
|
||||
const collectionFolderPreferences = await getPreferences<{ viewPreference: string }>(
|
||||
`${collectionSlug}-collection-folder`,
|
||||
payload,
|
||||
user.id,
|
||||
user.collection,
|
||||
)
|
||||
|
||||
const {
|
||||
routes: { admin: adminRoute },
|
||||
@@ -79,7 +82,7 @@ export const buildCollectionFolderView = async (
|
||||
|
||||
if (
|
||||
(!visibleEntities.collections.includes(collectionSlug) && !overrideEntityVisibility) ||
|
||||
!Object.keys(config.folders.collections).includes(collectionSlug)
|
||||
!folderCollectionSlugs.includes(collectionSlug)
|
||||
) {
|
||||
throw new Error('not-found')
|
||||
}
|
||||
@@ -94,13 +97,13 @@ export const buildCollectionFolderView = async (
|
||||
|
||||
if (folderID) {
|
||||
whereConstraints.push({
|
||||
_folder: {
|
||||
[config.folders.fieldName]: {
|
||||
equals: parseDocumentID({ id: folderID, collectionSlug, payload }),
|
||||
},
|
||||
})
|
||||
} else {
|
||||
whereConstraints.push({
|
||||
_folder: {
|
||||
[config.folders.fieldName]: {
|
||||
exists: false,
|
||||
},
|
||||
})
|
||||
@@ -118,13 +121,13 @@ export const buildCollectionFolderView = async (
|
||||
|
||||
if (
|
||||
!isInDrawer &&
|
||||
((resolvedFolderID && folderID && folderID !== String(resolvedFolderID)) ||
|
||||
((resolvedFolderID && folderID && folderID !== resolvedFolderID) ||
|
||||
(folderID && !resolvedFolderID))
|
||||
) {
|
||||
return redirect(
|
||||
formatAdminURL({
|
||||
adminRoute,
|
||||
path: `/collections/${collectionSlug}/folders`,
|
||||
path: `/collections/${collectionSlug}/${config.folders.slug}`,
|
||||
serverURL: config.serverURL,
|
||||
}),
|
||||
)
|
||||
@@ -173,6 +176,7 @@ export const buildCollectionFolderView = async (
|
||||
breadcrumbs={breadcrumbs}
|
||||
collectionSlug={collectionSlug}
|
||||
documents={documents}
|
||||
folderCollectionSlugs={folderCollectionSlugs}
|
||||
folderID={folderID}
|
||||
search={search}
|
||||
subfolders={subfolders}
|
||||
@@ -187,6 +191,7 @@ export const buildCollectionFolderView = async (
|
||||
enableRowSelections,
|
||||
hasCreatePermission,
|
||||
newDocumentURL,
|
||||
viewPreference: collectionFolderPreferences?.value?.viewPreference,
|
||||
},
|
||||
Component: collectionConfig?.admin?.components?.views?.list?.Component,
|
||||
Fallback: DefaultCollectionFolderView,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import type {
|
||||
AdminViewServerProps,
|
||||
CollectionSlug,
|
||||
DocumentSubViewTypes,
|
||||
ImportMap,
|
||||
PayloadComponent,
|
||||
@@ -14,11 +15,11 @@ import { formatAdminURL } from 'payload/shared'
|
||||
import type { initPage } from '../../utilities/initPage/index.js'
|
||||
|
||||
import { Account } from '../Account/index.js'
|
||||
import { BrowseByFolder } from '../BrowseByFolder/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'
|
||||
@@ -51,8 +52,8 @@ export type ViewFromConfig = {
|
||||
|
||||
const oneSegmentViews: OneSegmentViews = {
|
||||
account: Account,
|
||||
browseByFolder: BrowseByFolder,
|
||||
createFirstUser: CreateFirstUserView,
|
||||
folders: FolderView,
|
||||
forgot: ForgotPasswordView,
|
||||
inactivity: LogoutInactivity,
|
||||
login: LoginView,
|
||||
@@ -74,6 +75,7 @@ type GetRouteDataArgs = {
|
||||
type GetRouteDataResult = {
|
||||
DefaultView: ViewFromConfig
|
||||
documentSubViewType?: DocumentSubViewTypes
|
||||
folderCollectionSlugs: CollectionSlug[]
|
||||
folderID?: string
|
||||
initPageOptions: Parameters<typeof initPage>[0]
|
||||
serverProps: ServerPropsFromView
|
||||
@@ -110,7 +112,13 @@ 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 folderCollectionSlugs = config.collections.reduce((acc, { slug, admin }) => {
|
||||
if (admin?.folders) {
|
||||
return [...acc, slug]
|
||||
}
|
||||
return acc
|
||||
}, [])
|
||||
|
||||
const serverProps: ServerPropsFromView = {
|
||||
viewActions: config?.admin?.components?.actions || [],
|
||||
@@ -160,7 +168,7 @@ export const getRouteData = ({
|
||||
if (oneSegmentViews[viewKey]) {
|
||||
// --> /account
|
||||
// --> /create-first-user
|
||||
// --> /folders
|
||||
// --> /browse-by-folder
|
||||
// --> /forgot
|
||||
// --> /login
|
||||
// --> /logout
|
||||
@@ -174,15 +182,15 @@ export const getRouteData = ({
|
||||
templateClassName = baseClasses[viewKey]
|
||||
templateType = 'minimal'
|
||||
|
||||
if (isFolderViewEnabled && viewKey === 'folders') {
|
||||
templateType = 'default'
|
||||
viewType = 'folders'
|
||||
}
|
||||
|
||||
if (viewKey === 'account') {
|
||||
templateType = 'default'
|
||||
viewType = 'account'
|
||||
}
|
||||
|
||||
if (folderCollectionSlugs.length && viewKey === 'browseByFolder') {
|
||||
templateType = 'default'
|
||||
viewType = 'folders'
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
@@ -195,17 +203,18 @@ 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 (
|
||||
folderCollectionSlugs.length &&
|
||||
`/${segmentOne}` === config.admin.routes.browseByFolder
|
||||
) {
|
||||
// --> /browse-by-folder/:folderID
|
||||
ViewToRender = {
|
||||
Component: oneSegmentViews.browseByFolder,
|
||||
}
|
||||
templateClassName = baseClasses.folders
|
||||
templateType = 'default'
|
||||
viewType = 'folders'
|
||||
folderID = segmentTwo
|
||||
} else if (isCollection && matchedCollection) {
|
||||
// --> /collections/:collectionSlug
|
||||
|
||||
@@ -251,23 +260,21 @@ 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,
|
||||
}
|
||||
|
||||
templateClassName = `collection-folders`
|
||||
templateType = 'default'
|
||||
viewType = 'collection-folders'
|
||||
folderID = segmentFour
|
||||
if (
|
||||
segmentThree === config.folders.slug &&
|
||||
folderCollectionSlugs.includes(matchedCollection.slug)
|
||||
) {
|
||||
// Collection Folder Views
|
||||
// --> /collections/:collectionSlug/:folderCollectionSlug
|
||||
// --> /collections/:collectionSlug/:folderCollectionSlug/:folderID
|
||||
ViewToRender = {
|
||||
Component: CollectionFolderView,
|
||||
}
|
||||
|
||||
templateClassName = `collection-folders`
|
||||
templateType = 'default'
|
||||
viewType = 'collection-folders'
|
||||
folderID = segmentFour
|
||||
} else {
|
||||
// Collection Edit Views
|
||||
// --> /collections/:collectionSlug/:id
|
||||
@@ -328,6 +335,7 @@ export const getRouteData = ({
|
||||
return {
|
||||
DefaultView: ViewToRender,
|
||||
documentSubViewType,
|
||||
folderCollectionSlugs,
|
||||
folderID,
|
||||
initPageOptions,
|
||||
serverProps,
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
import type { I18nClient } from '@payloadcms/translations'
|
||||
import type { Metadata } from 'next'
|
||||
import type {
|
||||
AdminViewClientProps,
|
||||
AdminViewServerPropsOnly,
|
||||
ImportMap,
|
||||
SanitizedConfig,
|
||||
} from 'payload'
|
||||
|
||||
import { RenderServerComponent } from '@payloadcms/ui/elements/RenderServerComponent'
|
||||
import { getClientConfig } from '@payloadcms/ui/utilities/getClientConfig'
|
||||
import { notFound, redirect } from 'next/navigation.js'
|
||||
import {
|
||||
type AdminViewClientProps,
|
||||
type AdminViewServerPropsOnly,
|
||||
type ImportMap,
|
||||
parseDocumentID,
|
||||
type SanitizedConfig,
|
||||
} from 'payload'
|
||||
import { formatAdminURL } from 'payload/shared'
|
||||
import React from 'react'
|
||||
|
||||
@@ -64,7 +65,8 @@ export const RootPage = async ({
|
||||
const {
|
||||
DefaultView,
|
||||
documentSubViewType,
|
||||
folderID,
|
||||
folderCollectionSlugs,
|
||||
folderID: folderIDParam,
|
||||
initPageOptions,
|
||||
serverProps,
|
||||
templateClassName,
|
||||
@@ -137,8 +139,20 @@ export const RootPage = async ({
|
||||
importMap,
|
||||
})
|
||||
|
||||
const payload = initPageResult?.req.payload
|
||||
const folderID = parseDocumentID({
|
||||
id: folderIDParam,
|
||||
collectionSlug: payload.config.folders.slug,
|
||||
payload,
|
||||
})
|
||||
|
||||
const RenderedView = RenderServerComponent({
|
||||
clientProps: { clientConfig, documentSubViewType, viewType } satisfies AdminViewClientProps,
|
||||
clientProps: {
|
||||
clientConfig,
|
||||
documentSubViewType,
|
||||
folderCollectionSlugs,
|
||||
viewType,
|
||||
} satisfies AdminViewClientProps,
|
||||
Component: DefaultView.payloadComponent,
|
||||
Fallback: DefaultView.Component,
|
||||
importMap,
|
||||
|
||||
@@ -3,11 +3,11 @@ import type { SanitizedConfig } from 'payload'
|
||||
|
||||
import { getNextRequestI18n } from '../../utilities/getNextRequestI18n.js'
|
||||
import { generateAccountViewMetadata } from '../Account/metadata.js'
|
||||
import { generateBrowseByFolderMetadata } from '../BrowseByFolder/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'
|
||||
@@ -43,8 +43,14 @@ export const generatePageMetadata = async ({
|
||||
params: paramsPromise,
|
||||
}: Args) => {
|
||||
const config = await configPromise
|
||||
|
||||
const params = await paramsPromise
|
||||
|
||||
const folderCollectionSlugs = config.collections.reduce((acc, { slug, admin }) => {
|
||||
if (admin?.folders) {
|
||||
return [...acc, slug]
|
||||
}
|
||||
return acc
|
||||
}, [])
|
||||
const segments = Array.isArray(params.segments) ? params.segments : []
|
||||
|
||||
const currentRoute = `/${segments.join('/')}`
|
||||
@@ -75,20 +81,22 @@ export const generatePageMetadata = async ({
|
||||
break
|
||||
}
|
||||
case 1: {
|
||||
if (oneSegmentMeta[segmentOne] && segmentOne !== 'account') {
|
||||
if (folderCollectionSlugs.length && `/${segmentOne}` === config.admin.routes.browseByFolder) {
|
||||
// --> /:folderCollectionSlug
|
||||
meta = await oneSegmentMeta.folders({ config, i18n })
|
||||
} else if (segmentOne === 'account') {
|
||||
// --> /account
|
||||
meta = await generateAccountViewMetadata({ config, i18n })
|
||||
break
|
||||
} else if (oneSegmentMeta[segmentOne]) {
|
||||
// --> /create-first-user
|
||||
// --> /forgot
|
||||
// --> /folders
|
||||
// --> /login
|
||||
// --> /logout
|
||||
// --> /logout-inactivity
|
||||
// --> /unauthorized
|
||||
meta = await oneSegmentMeta[segmentOne]({ config, i18n })
|
||||
break
|
||||
} else if (segmentOne === 'account') {
|
||||
// --> /account
|
||||
meta = await generateAccountViewMetadata({ config, i18n })
|
||||
break
|
||||
}
|
||||
break
|
||||
}
|
||||
@@ -96,8 +104,11 @@ 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
|
||||
} else if (
|
||||
folderCollectionSlugs.length &&
|
||||
`/${segmentOne}` === config.admin.routes.browseByFolder
|
||||
) {
|
||||
// --> /browse-by-folder/:folderID
|
||||
meta = await generateBrowseByFolderMetadata({ config, i18n })
|
||||
} else if (isCollection) {
|
||||
// --> /collections/:collectionSlug
|
||||
@@ -118,11 +129,11 @@ 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)) {
|
||||
if (segmentThree === config.folders.slug) {
|
||||
if (folderCollectionSlugs.includes(collectionConfig.slug)) {
|
||||
// Collection Folder Views
|
||||
// --> /collections/:collectionSlug/folders
|
||||
// --> /collections/:collectionSlug/folders/:id
|
||||
// --> /collections/:collectionSlug/:folderCollectionSlug
|
||||
// --> /collections/:collectionSlug/:folderCollectionSlug/:id
|
||||
meta = await generateCollectionFolderMetadata({
|
||||
collectionConfig,
|
||||
config,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { ArrayFieldClient, BlocksFieldClient, ClientConfig, ClientField } from 'payload'
|
||||
|
||||
import { fieldShouldBeLocalized } from 'payload/shared'
|
||||
import { fieldShouldBeLocalized, groupHasName } from 'payload/shared'
|
||||
|
||||
import { fieldHasChanges } from './fieldHasChanges.js'
|
||||
import { getFieldsForRowComparison } from './getFieldsForRowComparison.js'
|
||||
@@ -114,25 +114,37 @@ export function countChangedFields({
|
||||
|
||||
// Fields that have nested fields and nest their fields' data.
|
||||
case 'group': {
|
||||
if (locales && fieldShouldBeLocalized({ field, parentIsLocalized })) {
|
||||
locales.forEach((locale) => {
|
||||
if (groupHasName(field)) {
|
||||
if (locales && fieldShouldBeLocalized({ field, parentIsLocalized })) {
|
||||
locales.forEach((locale) => {
|
||||
count += countChangedFields({
|
||||
comparison: comparison?.[field.name]?.[locale],
|
||||
config,
|
||||
fields: field.fields,
|
||||
locales,
|
||||
parentIsLocalized: parentIsLocalized || field.localized,
|
||||
version: version?.[field.name]?.[locale],
|
||||
})
|
||||
})
|
||||
} else {
|
||||
count += countChangedFields({
|
||||
comparison: comparison?.[field.name]?.[locale],
|
||||
comparison: comparison?.[field.name],
|
||||
config,
|
||||
fields: field.fields,
|
||||
locales,
|
||||
parentIsLocalized: parentIsLocalized || field.localized,
|
||||
version: version?.[field.name]?.[locale],
|
||||
version: version?.[field.name],
|
||||
})
|
||||
})
|
||||
}
|
||||
} else {
|
||||
// Unnamed group field: data is NOT nested under `field.name`
|
||||
count += countChangedFields({
|
||||
comparison: comparison?.[field.name],
|
||||
comparison,
|
||||
config,
|
||||
fields: field.fields,
|
||||
locales,
|
||||
parentIsLocalized: parentIsLocalized || field.localized,
|
||||
version: version?.[field.name],
|
||||
version,
|
||||
})
|
||||
}
|
||||
break
|
||||
|
||||
@@ -27,6 +27,7 @@ export type FolderListViewClientProps = {
|
||||
enableRowSelections?: boolean
|
||||
hasCreatePermission: boolean
|
||||
newDocumentURL: string
|
||||
viewPreference: 'grid' | 'list'
|
||||
} & FolderListViewSlots
|
||||
|
||||
export type FolderListViewSlotSharedClientProps = {
|
||||
|
||||
@@ -31,6 +31,7 @@ export type AdminViewConfig = {
|
||||
export type AdminViewClientProps = {
|
||||
clientConfig: ClientConfig
|
||||
documentSubViewType?: DocumentSubViewTypes
|
||||
folderCollectionSlugs?: SanitizedCollectionConfig['slug'][]
|
||||
viewType: ViewTypes
|
||||
}
|
||||
|
||||
@@ -41,7 +42,7 @@ export type AdminViewServerPropsOnly = {
|
||||
* @todo remove `docID` here as it is already contained in `initPageResult`
|
||||
*/
|
||||
readonly docID?: number | string
|
||||
readonly folderID?: string
|
||||
readonly folderID?: number | string
|
||||
readonly importMap: ImportMap
|
||||
readonly initialData?: Data
|
||||
readonly initPageResult: InitPageResult
|
||||
|
||||
@@ -69,6 +69,7 @@ export const addDefaultsToCollectionConfig = (collection: CollectionConfig): Col
|
||||
custom: {},
|
||||
enableRichTextLink: true,
|
||||
enableRichTextRelationship: true,
|
||||
folders: false,
|
||||
useAsTitle: 'id',
|
||||
...(collection.admin || {}),
|
||||
pagination: {
|
||||
|
||||
@@ -32,6 +32,7 @@ import type {
|
||||
RelationshipField,
|
||||
UploadField,
|
||||
} from '../../fields/config/types.js'
|
||||
import type { CollectionFoldersConfiguration } from '../../folders/types.js'
|
||||
import type {
|
||||
CollectionSlug,
|
||||
JsonObject,
|
||||
@@ -344,6 +345,10 @@ export type CollectionAdminOptions = {
|
||||
disableCopyToLocale?: boolean
|
||||
enableRichTextLink?: boolean
|
||||
enableRichTextRelationship?: boolean
|
||||
/**
|
||||
* Enables folders for this collection
|
||||
*/
|
||||
folders?: CollectionFoldersConfiguration
|
||||
/**
|
||||
* Specify a navigational group for collections in the admin sidebar.
|
||||
* - Provide a string to place the entity in a custom group.
|
||||
|
||||
@@ -2,6 +2,7 @@ import type { JobsConfig } from '../queues/config/types/index.js'
|
||||
import type { Config } from './types.js'
|
||||
|
||||
import defaultAccess from '../auth/defaultAccess.js'
|
||||
import { foldersSlug, parentFolderFieldName } from '../folders/constants.js'
|
||||
|
||||
/**
|
||||
* @deprecated - remove in 4.0. This is error-prone, as mutating this object will affect any objects that use the defaults as a base.
|
||||
@@ -23,8 +24,8 @@ export const defaults: Omit<Config, 'db' | 'editor' | 'secret'> = {
|
||||
},
|
||||
routes: {
|
||||
account: '/account',
|
||||
browseByFolder: '/browse-by-folder',
|
||||
createFirstUser: '/create-first-user',
|
||||
folders: '/folders',
|
||||
forgot: '/forgot',
|
||||
inactivity: '/logout-inactivity',
|
||||
login: '/login',
|
||||
@@ -99,8 +100,8 @@ export const addDefaultsToConfig = (config: Config): Config => {
|
||||
},
|
||||
routes: {
|
||||
account: '/account',
|
||||
browseByFolder: '/browse-by-folder',
|
||||
createFirstUser: '/create-first-user',
|
||||
folders: '/folders',
|
||||
forgot: '/forgot',
|
||||
inactivity: '/logout-inactivity',
|
||||
login: '/login',
|
||||
@@ -112,9 +113,9 @@ export const addDefaultsToConfig = (config: Config): Config => {
|
||||
}
|
||||
|
||||
config.folders = {
|
||||
collections: {},
|
||||
slug: foldersSlug,
|
||||
debug: false,
|
||||
enabled: false,
|
||||
fieldName: parentFolderFieldName,
|
||||
...(config.folders || {}),
|
||||
}
|
||||
|
||||
|
||||
@@ -40,10 +40,10 @@ import type {
|
||||
import type { DatabaseAdapterResult } from '../database/types.js'
|
||||
import type { EmailAdapter, SendEmailOptions } from '../email/types.js'
|
||||
import type { ErrorName } from '../errors/types.js'
|
||||
import type { RootFoldersConfiguration } from '../folders/types.js'
|
||||
import type { GlobalConfig, Globals, SanitizedGlobalConfig } from '../globals/config/types.js'
|
||||
import type {
|
||||
Block,
|
||||
CollectionSlug,
|
||||
FlattenedBlock,
|
||||
JobsConfig,
|
||||
Payload,
|
||||
@@ -847,16 +847,16 @@ export type Config = {
|
||||
* @default '/account'
|
||||
*/
|
||||
account?: `/${string}`
|
||||
/** The route for the browse by folder view.
|
||||
*
|
||||
* @default '/browse-by-folder'
|
||||
*/
|
||||
browseByFolder: `/${string}`
|
||||
/** The route for the create first user page.
|
||||
*
|
||||
* @default '/create-first-user'
|
||||
*/
|
||||
createFirstUser?: `/${string}`
|
||||
/** The route for folder view.
|
||||
*
|
||||
* @default '/folders'
|
||||
*/
|
||||
folders: `/${string}`
|
||||
/** The route for the forgot password page.
|
||||
*
|
||||
* @default '/forgot'
|
||||
@@ -986,41 +986,7 @@ export type Config = {
|
||||
/**
|
||||
* 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
|
||||
}
|
||||
folders?: RootFoldersConfiguration
|
||||
/**
|
||||
* @see https://payloadcms.com/docs/configuration/globals#global-configs
|
||||
*/
|
||||
|
||||
@@ -29,6 +29,7 @@ export {
|
||||
fieldIsVirtual,
|
||||
fieldShouldBeLocalized,
|
||||
fieldSupportsMany,
|
||||
groupHasName,
|
||||
optionIsObject,
|
||||
optionIsValue,
|
||||
optionsAreObjects,
|
||||
|
||||
@@ -770,7 +770,7 @@ export type NamedGroupFieldClient = {
|
||||
export type UnnamedGroupFieldClient = {
|
||||
admin?: AdminClient & Pick<UnnamedGroupField['admin'], 'hideGutter'>
|
||||
fields: ClientField[]
|
||||
} & Omit<FieldBaseClient, 'required'> &
|
||||
} & Omit<FieldBaseClient, 'name' | 'required'> &
|
||||
Pick<UnnamedGroupField, 'label' | 'type'>
|
||||
|
||||
export type GroupFieldClient = NamedGroupFieldClient | UnnamedGroupFieldClient
|
||||
@@ -1960,6 +1960,12 @@ export function tabHasName<TField extends ClientTab | Tab>(tab: TField): tab is
|
||||
return 'name' in tab
|
||||
}
|
||||
|
||||
export function groupHasName(
|
||||
group: Partial<NamedGroupFieldClient>,
|
||||
): group is NamedGroupFieldClient {
|
||||
return 'name' in group
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a field has localized: true set. This does not check if a field *should*
|
||||
* be localized. To check if a field should be localized, use `fieldShouldBeLocalized`.
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
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> {
|
||||
@@ -9,51 +8,51 @@ export async function addFolderCollections(config: NonNullable<Config>): Promise
|
||||
return
|
||||
}
|
||||
|
||||
if (config.folders?.enabled) {
|
||||
const enabledCollectionSlugs: CollectionSlug[] = []
|
||||
const debug = Boolean(config.folders?.debug)
|
||||
config.folders.slug = config.folders.slug || foldersSlug
|
||||
const enabledCollectionSlugs: CollectionSlug[] = []
|
||||
const debug = Boolean(config?.folders?.debug)
|
||||
const folderFieldName = config?.folders?.fieldName as unknown as string
|
||||
const folderSlug = config?.folders?.slug as unknown as CollectionSlug
|
||||
|
||||
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',
|
||||
},
|
||||
for (let i = 0; i < config.collections.length; i++) {
|
||||
const collection = config.collections[i]
|
||||
if (collection?.admin?.folders) {
|
||||
if (collection) {
|
||||
collection.fields.push({
|
||||
name: folderFieldName,
|
||||
type: 'relationship',
|
||||
admin: {
|
||||
allowCreate: false,
|
||||
allowEdit: false,
|
||||
components: {
|
||||
Cell: '@payloadcms/ui/rsc#FolderTableCell',
|
||||
Field: '@payloadcms/ui/rsc#FolderEditField',
|
||||
},
|
||||
index: true,
|
||||
label: 'Folder',
|
||||
relationTo: config.folders.slug,
|
||||
})
|
||||
enabledCollectionSlugs.push(collection.slug)
|
||||
}
|
||||
},
|
||||
index: true,
|
||||
label: 'Folder',
|
||||
relationTo: folderSlug,
|
||||
})
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
if (enabledCollectionSlugs.length) {
|
||||
let folderCollection = createFolderCollection({
|
||||
slug: folderSlug,
|
||||
collectionSlugs: enabledCollectionSlugs,
|
||||
debug,
|
||||
folderFieldName,
|
||||
})
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
export const foldersSlug = '_folders'
|
||||
export const parentFolderFieldName = '_folder'
|
||||
export const foldersSlug = 'payload-folders'
|
||||
export const parentFolderFieldName = 'folder'
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
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'
|
||||
@@ -9,12 +8,14 @@ import { reparentChildFolder } from './hooks/reparentChildFolder.js'
|
||||
type CreateFolderCollectionArgs = {
|
||||
collectionSlugs: string[]
|
||||
debug?: boolean
|
||||
folderFieldName: string
|
||||
slug: string
|
||||
}
|
||||
export const createFolderCollection = ({
|
||||
slug,
|
||||
collectionSlugs,
|
||||
debug,
|
||||
folderFieldName,
|
||||
}: CreateFolderCollectionArgs): CollectionConfig => ({
|
||||
slug,
|
||||
admin: {
|
||||
@@ -30,7 +31,7 @@ export const createFolderCollection = ({
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: parentFolderFieldName,
|
||||
name: folderFieldName,
|
||||
type: 'relationship',
|
||||
admin: {
|
||||
hidden: !debug,
|
||||
@@ -46,21 +47,21 @@ export const createFolderCollection = ({
|
||||
},
|
||||
collection: [slug, ...collectionSlugs],
|
||||
hasMany: true,
|
||||
on: parentFolderFieldName,
|
||||
on: folderFieldName,
|
||||
},
|
||||
],
|
||||
hooks: {
|
||||
afterChange: [
|
||||
reparentChildFolder({
|
||||
parentFolderFieldName,
|
||||
folderFieldName,
|
||||
}),
|
||||
],
|
||||
afterDelete: [
|
||||
dissasociateAfterDelete({
|
||||
collectionSlugs,
|
||||
parentFolderFieldName,
|
||||
folderFieldName,
|
||||
}),
|
||||
deleteSubfoldersAfterDelete({ folderSlug: slug, parentFolderFieldName }),
|
||||
deleteSubfoldersAfterDelete({ folderFieldName, folderSlug: slug }),
|
||||
],
|
||||
},
|
||||
labels: {
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
import type { CollectionAfterDeleteHook } from '../../index.js'
|
||||
|
||||
type Args = {
|
||||
folderFieldName: string
|
||||
folderSlug: string
|
||||
parentFolderFieldName: string
|
||||
}
|
||||
export const deleteSubfoldersAfterDelete = ({
|
||||
folderFieldName,
|
||||
folderSlug,
|
||||
parentFolderFieldName,
|
||||
}: Args): CollectionAfterDeleteHook => {
|
||||
return async ({ id, req }) => {
|
||||
await req.payload.delete({
|
||||
collection: folderSlug,
|
||||
req,
|
||||
where: {
|
||||
[parentFolderFieldName]: {
|
||||
[folderFieldName]: {
|
||||
equals: id,
|
||||
},
|
||||
},
|
||||
|
||||
@@ -2,22 +2,22 @@ import type { CollectionAfterDeleteHook } from '../../index.js'
|
||||
|
||||
type Args = {
|
||||
collectionSlugs: string[]
|
||||
parentFolderFieldName: string
|
||||
folderFieldName: string
|
||||
}
|
||||
export const dissasociateAfterDelete = ({
|
||||
collectionSlugs,
|
||||
parentFolderFieldName,
|
||||
folderFieldName,
|
||||
}: Args): CollectionAfterDeleteHook => {
|
||||
return async ({ id, req }) => {
|
||||
for (const collectionSlug of collectionSlugs) {
|
||||
await req.payload.update({
|
||||
collection: collectionSlug,
|
||||
data: {
|
||||
[parentFolderFieldName]: null,
|
||||
[folderFieldName]: null,
|
||||
},
|
||||
req,
|
||||
where: {
|
||||
[parentFolderFieldName]: {
|
||||
[folderFieldName]: {
|
||||
equals: id,
|
||||
},
|
||||
},
|
||||
|
||||
@@ -3,8 +3,8 @@ import type { CollectionAfterChangeHook, Payload } from '../../index.js'
|
||||
import { extractID } from '../../utilities/extractID.js'
|
||||
|
||||
type Args = {
|
||||
folderFieldName: string
|
||||
folderID: number | string
|
||||
parentFolderFieldName: string
|
||||
parentIDToFind: number | string
|
||||
payload: Payload
|
||||
}
|
||||
@@ -14,8 +14,8 @@ type Args = {
|
||||
* recursively checking upwards through the folder hierarchy.
|
||||
*/
|
||||
async function isChildOfFolder({
|
||||
folderFieldName,
|
||||
folderID,
|
||||
parentFolderFieldName,
|
||||
parentIDToFind,
|
||||
payload,
|
||||
}: Args): Promise<boolean> {
|
||||
@@ -24,8 +24,8 @@ async function isChildOfFolder({
|
||||
collection: payload.config.folders.slug,
|
||||
})
|
||||
|
||||
const parentFolderID = parentFolder[parentFolderFieldName]
|
||||
? extractID(parentFolder[parentFolderFieldName])
|
||||
const parentFolderID = parentFolder[folderFieldName]
|
||||
? extractID(parentFolder[folderFieldName])
|
||||
: undefined
|
||||
|
||||
if (!parentFolderID) {
|
||||
@@ -39,8 +39,8 @@ async function isChildOfFolder({
|
||||
}
|
||||
|
||||
return isChildOfFolder({
|
||||
folderFieldName,
|
||||
folderID: parentFolderID,
|
||||
parentFolderFieldName,
|
||||
parentIDToFind,
|
||||
payload,
|
||||
})
|
||||
@@ -66,20 +66,17 @@ async function isChildOfFolder({
|
||||
```
|
||||
*/
|
||||
export const reparentChildFolder = ({
|
||||
parentFolderFieldName,
|
||||
folderFieldName,
|
||||
}: {
|
||||
parentFolderFieldName: string
|
||||
folderFieldName: string
|
||||
}): CollectionAfterChangeHook => {
|
||||
return async ({ doc, previousDoc, req }) => {
|
||||
if (
|
||||
previousDoc[parentFolderFieldName] !== doc[parentFolderFieldName] &&
|
||||
doc[parentFolderFieldName]
|
||||
) {
|
||||
const newParentFolderID = extractID(doc[parentFolderFieldName])
|
||||
if (previousDoc[folderFieldName] !== doc[folderFieldName] && doc[folderFieldName]) {
|
||||
const newParentFolderID = extractID(doc[folderFieldName])
|
||||
const isMovingToChild = newParentFolderID
|
||||
? await isChildOfFolder({
|
||||
folderFieldName,
|
||||
folderID: newParentFolderID,
|
||||
parentFolderFieldName,
|
||||
parentIDToFind: doc.id,
|
||||
payload: req.payload,
|
||||
})
|
||||
@@ -92,8 +89,8 @@ export const reparentChildFolder = ({
|
||||
id: newParentFolderID,
|
||||
collection: req.payload.config.folders.slug,
|
||||
data: {
|
||||
[parentFolderFieldName]: previousDoc[parentFolderFieldName]
|
||||
? extractID(previousDoc[parentFolderFieldName])
|
||||
[folderFieldName]: previousDoc[folderFieldName]
|
||||
? extractID(previousDoc[folderFieldName])
|
||||
: null,
|
||||
},
|
||||
req,
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import type { TypeWithID } from '../collections/config/types.js'
|
||||
import type { CollectionConfig, 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
|
||||
}[]
|
||||
}
|
||||
folder?: FolderInterface | (number | string | undefined)
|
||||
name: string
|
||||
} & TypeWithID
|
||||
|
||||
@@ -55,9 +55,9 @@ export type FolderOrDocument = {
|
||||
itemKey: FolderDocumentItemKey
|
||||
relationTo: CollectionSlug
|
||||
value: {
|
||||
_folder?: number | string
|
||||
_folderOrDocumentTitle: string
|
||||
createdAt?: string
|
||||
folderID?: number | string
|
||||
id: number | string
|
||||
updatedAt?: string
|
||||
} & DocumentMediaData
|
||||
@@ -68,3 +68,35 @@ export type GetFolderDataResult = {
|
||||
documents: FolderOrDocument[]
|
||||
subfolders: FolderOrDocument[]
|
||||
}
|
||||
|
||||
export type RootFoldersConfiguration = {
|
||||
/**
|
||||
* 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>)[]
|
||||
/**
|
||||
* Ability to view hidden fields and collections related to folders
|
||||
*
|
||||
* @default false
|
||||
*/
|
||||
debug?: boolean
|
||||
/**
|
||||
* The Folder field name
|
||||
*
|
||||
* @default "folder"
|
||||
*/
|
||||
fieldName?: string
|
||||
/**
|
||||
* Slug for the folder collection
|
||||
*
|
||||
* @default "payload-folders"
|
||||
*/
|
||||
slug?: string
|
||||
}
|
||||
|
||||
export type CollectionFoldersConfiguration = boolean
|
||||
|
||||
@@ -2,12 +2,14 @@ import type { CollectionSlug, Document } from '../../index.js'
|
||||
import type { FolderOrDocument } from '../types.js'
|
||||
|
||||
type Args = {
|
||||
folderFieldName: string
|
||||
isUpload: boolean
|
||||
relationTo: CollectionSlug
|
||||
useAsTitle?: string
|
||||
value: Document
|
||||
}
|
||||
export function formatFolderOrDocumentItem({
|
||||
folderFieldName,
|
||||
isUpload,
|
||||
relationTo,
|
||||
useAsTitle,
|
||||
@@ -15,9 +17,9 @@ export function formatFolderOrDocumentItem({
|
||||
}: Args): FolderOrDocument {
|
||||
const itemValue: FolderOrDocument['value'] = {
|
||||
id: value?.id,
|
||||
_folder: value?._folder,
|
||||
_folderOrDocumentTitle: value[useAsTitle || 'id'],
|
||||
_folderOrDocumentTitle: (useAsTitle && value?.[useAsTitle]) || value['id'],
|
||||
createdAt: value?.createdAt,
|
||||
folderID: value?.[folderFieldName],
|
||||
updatedAt: value?.updatedAt,
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
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 type { Document, Payload } from '../../types/index.js'
|
||||
import type { FolderBreadcrumb } from '../types.js'
|
||||
|
||||
import { parentFolderFieldName } from '../constants.js'
|
||||
type GetFolderBreadcrumbsArgs = {
|
||||
breadcrumbs?: FolderBreadcrumb[]
|
||||
folderID?: number | string
|
||||
@@ -20,15 +18,16 @@ export const getFolderBreadcrumbs = async ({
|
||||
payload,
|
||||
user,
|
||||
}: GetFolderBreadcrumbsArgs): Promise<FolderBreadcrumb[] | null> => {
|
||||
const folderFieldName: string = payload.config.folders.fieldName
|
||||
if (folderID) {
|
||||
const folderQuery = (await payload.find({
|
||||
const folderQuery = await payload.find({
|
||||
collection: payload.config.folders.slug,
|
||||
depth: 0,
|
||||
limit: 1,
|
||||
overrideAccess: false,
|
||||
select: {
|
||||
name: true,
|
||||
[parentFolderFieldName]: true,
|
||||
[folderFieldName]: true,
|
||||
},
|
||||
user,
|
||||
where: {
|
||||
@@ -36,23 +35,23 @@ export const getFolderBreadcrumbs = async ({
|
||||
equals: folderID,
|
||||
},
|
||||
},
|
||||
})) as PaginatedDocs<FolderInterface>
|
||||
})
|
||||
|
||||
const folder = folderQuery.docs[0]
|
||||
const folder = folderQuery.docs[0] as Document
|
||||
|
||||
if (folder) {
|
||||
breadcrumbs.push({
|
||||
id: folder.id,
|
||||
name: folder.name,
|
||||
})
|
||||
if (folder[parentFolderFieldName]) {
|
||||
if (folder[folderFieldName]) {
|
||||
return getFolderBreadcrumbs({
|
||||
breadcrumbs,
|
||||
folderID:
|
||||
typeof folder[parentFolderFieldName] === 'number' ||
|
||||
typeof folder[parentFolderFieldName] === 'string'
|
||||
? folder[parentFolderFieldName]
|
||||
: folder[parentFolderFieldName].id,
|
||||
typeof folder[folderFieldName] === 'number' ||
|
||||
typeof folder[folderFieldName] === 'string'
|
||||
? folder[folderFieldName]
|
||||
: folder[folderFieldName].id,
|
||||
payload,
|
||||
user,
|
||||
})
|
||||
|
||||
@@ -22,6 +22,16 @@ export async function queryDocumentsAndFoldersFromJoin({
|
||||
payload,
|
||||
user,
|
||||
}: QueryDocumentsAndFoldersArgs): Promise<QueryDocumentsAndFoldersResults> {
|
||||
const folderCollectionSlugs: string[] = payload.config.collections.reduce<string[]>(
|
||||
(acc, collection) => {
|
||||
if (collection?.admin?.folders) {
|
||||
acc.push(collection.slug)
|
||||
}
|
||||
return acc
|
||||
},
|
||||
[],
|
||||
)
|
||||
|
||||
const subfolderDoc = (await payload.find({
|
||||
collection: payload.config.folders.slug,
|
||||
joins: {
|
||||
@@ -32,16 +42,14 @@ export async function queryDocumentsAndFoldersFromJoin({
|
||||
relationTo: {
|
||||
in: [
|
||||
payload.config.folders.slug,
|
||||
...(collectionSlug
|
||||
? [collectionSlug]
|
||||
: Object.keys(payload.config.folders.collections)),
|
||||
...(collectionSlug ? [collectionSlug] : folderCollectionSlugs),
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
limit: 1,
|
||||
// overrideAccess: false, // @todo: bug in core, throws "QueryError: The following paths cannot be queried: relationTo"
|
||||
overrideAccess: false,
|
||||
user,
|
||||
where: {
|
||||
id: {
|
||||
@@ -56,6 +64,7 @@ export async function queryDocumentsAndFoldersFromJoin({
|
||||
(acc: QueryDocumentsAndFoldersResults, doc: Document) => {
|
||||
const { relationTo, value } = doc
|
||||
const item = formatFolderOrDocumentItem({
|
||||
folderFieldName: payload.config.folders.fieldName,
|
||||
isUpload: Boolean(payload.collections[relationTo].config.upload),
|
||||
relationTo,
|
||||
useAsTitle: payload.collections[relationTo].config.admin?.useAsTitle,
|
||||
|
||||
@@ -18,12 +18,12 @@ export async function getOrphanedDocs({
|
||||
let whereConstraints: Where = {
|
||||
or: [
|
||||
{
|
||||
_folder: {
|
||||
[payload.config.folders.fieldName]: {
|
||||
exists: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
_folder: {
|
||||
[payload.config.folders.fieldName]: {
|
||||
equals: null,
|
||||
},
|
||||
},
|
||||
@@ -41,7 +41,7 @@ export async function getOrphanedDocs({
|
||||
const orphanedFolders = await payload.find({
|
||||
collection: collectionSlug,
|
||||
limit: 0,
|
||||
// overrideAccess: false, // @todo: bug in core, throws "QueryError: The following paths cannot be queried: _folder"
|
||||
overrideAccess: false,
|
||||
sort: payload.collections[collectionSlug].config.admin.useAsTitle,
|
||||
user,
|
||||
where: whereConstraints,
|
||||
@@ -50,6 +50,7 @@ export async function getOrphanedDocs({
|
||||
return (
|
||||
orphanedFolders?.docs.map((doc) =>
|
||||
formatFolderOrDocumentItem({
|
||||
folderFieldName: payload.config.folders.fieldName,
|
||||
isUpload: Boolean(payload.collections[collectionSlug].config.upload),
|
||||
relationTo: collectionSlug,
|
||||
useAsTitle: payload.collections[collectionSlug].config.admin.useAsTitle,
|
||||
|
||||
@@ -65,10 +65,12 @@ export const getConstraints = (config: Config): Field => ({
|
||||
hooks: {
|
||||
beforeChange: [
|
||||
({ data, req }) => {
|
||||
if (data?.access?.[operation]?.constraint === 'onlyMe') {
|
||||
if (req.user) {
|
||||
return [req.user.id]
|
||||
}
|
||||
if (data?.access?.[operation]?.constraint === 'onlyMe' && req.user) {
|
||||
return [req.user.id]
|
||||
}
|
||||
|
||||
if (data?.access?.[operation]?.constraint === 'specificUsers' && req.user) {
|
||||
return [...(data?.access?.[operation]?.users || []), req.user.id]
|
||||
}
|
||||
|
||||
return data?.access?.[operation]?.users
|
||||
|
||||
@@ -72,7 +72,7 @@ export const preventLockout: Validate = async (
|
||||
canUpdate = true
|
||||
} catch (_err) {
|
||||
if (!canRead || !canUpdate) {
|
||||
throw new APIError('Cannot remove yourself from this preset.', 403, {}, true)
|
||||
throw new APIError('This action will lock you out of this preset.', 403, {}, true)
|
||||
}
|
||||
} finally {
|
||||
if (transaction) {
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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',
|
||||
|
||||
510
packages/payload/src/utilities/flattenTopLevelFields.spec.ts
Normal file
510
packages/payload/src/utilities/flattenTopLevelFields.spec.ts
Normal file
@@ -0,0 +1,510 @@
|
||||
import { I18nClient } from '@payloadcms/translations'
|
||||
import { ClientField } from '../fields/config/client.js'
|
||||
import flattenFields from './flattenTopLevelFields.js'
|
||||
|
||||
describe('flattenFields', () => {
|
||||
const i18n: I18nClient = {
|
||||
t: (value: string) => value,
|
||||
language: 'en',
|
||||
dateFNS: {} as any,
|
||||
dateFNSKey: 'en-US',
|
||||
fallbackLanguage: 'en',
|
||||
translations: {},
|
||||
}
|
||||
|
||||
const baseField: ClientField = {
|
||||
type: 'text',
|
||||
name: 'title',
|
||||
label: 'Title',
|
||||
}
|
||||
|
||||
describe('basic flattening', () => {
|
||||
it('should return flat list for top-level fields', () => {
|
||||
const fields = [baseField]
|
||||
const result = flattenFields(fields)
|
||||
expect(result).toHaveLength(1)
|
||||
expect(result[0].name).toBe('title')
|
||||
})
|
||||
})
|
||||
|
||||
describe('group flattening', () => {
|
||||
it('should flatten fields inside group with accessor and labelWithPrefix with moveSubFieldsToTop', () => {
|
||||
const fields: ClientField[] = [
|
||||
{
|
||||
type: 'group',
|
||||
name: 'meta',
|
||||
label: 'Meta Info',
|
||||
fields: [
|
||||
{
|
||||
type: 'text',
|
||||
name: 'slug',
|
||||
label: 'Slug',
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
const result = flattenFields(fields, {
|
||||
moveSubFieldsToTop: true,
|
||||
i18n,
|
||||
})
|
||||
|
||||
expect(result).toHaveLength(1)
|
||||
expect(result[0].name).toBe('slug')
|
||||
expect(result[0].accessor).toBe('meta-slug')
|
||||
expect(result[0].labelWithPrefix).toBe('Meta Info > Slug')
|
||||
})
|
||||
|
||||
it('should NOT flatten fields inside group without moveSubFieldsToTop', () => {
|
||||
const fields: ClientField[] = [
|
||||
{
|
||||
type: 'group',
|
||||
name: 'meta',
|
||||
label: 'Meta Info',
|
||||
fields: [
|
||||
{
|
||||
type: 'text',
|
||||
name: 'slug',
|
||||
label: 'Slug',
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
const result = flattenFields(fields)
|
||||
|
||||
// Should return the group as a top-level item, not the inner field
|
||||
expect(result).toHaveLength(1)
|
||||
expect(result[0].name).toBe('meta')
|
||||
expect('fields' in result[0]).toBe(true)
|
||||
expect('accessor' in result[0]).toBe(false)
|
||||
expect('labelWithPrefix' in result[0]).toBe(false)
|
||||
})
|
||||
|
||||
it('should correctly handle deeply nested group fields with and without moveSubFieldsToTop', () => {
|
||||
const fields: ClientField[] = [
|
||||
{
|
||||
type: 'group',
|
||||
name: 'outer',
|
||||
label: 'Outer',
|
||||
fields: [
|
||||
{
|
||||
type: 'group',
|
||||
name: 'inner',
|
||||
label: 'Inner',
|
||||
fields: [
|
||||
{
|
||||
type: 'text',
|
||||
name: 'deep',
|
||||
label: 'Deep Field',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
const hoisted = flattenFields(fields, {
|
||||
moveSubFieldsToTop: true,
|
||||
i18n,
|
||||
})
|
||||
|
||||
expect(hoisted).toHaveLength(1)
|
||||
expect(hoisted[0].name).toBe('deep')
|
||||
expect(hoisted[0].accessor).toBe('outer-inner-deep')
|
||||
expect(hoisted[0].labelWithPrefix).toBe('Outer > Inner > Deep Field')
|
||||
|
||||
const nonHoisted = flattenFields(fields)
|
||||
|
||||
expect(nonHoisted).toHaveLength(1)
|
||||
expect(nonHoisted[0].name).toBe('outer')
|
||||
expect('fields' in nonHoisted[0]).toBe(true)
|
||||
expect('accessor' in nonHoisted[0]).toBe(false)
|
||||
expect('labelWithPrefix' in nonHoisted[0]).toBe(false)
|
||||
})
|
||||
|
||||
it('should hoist fields from unnamed group if moveSubFieldsToTop is true', () => {
|
||||
const fields: ClientField[] = [
|
||||
{
|
||||
type: 'group',
|
||||
label: 'Unnamed group',
|
||||
fields: [
|
||||
{
|
||||
type: 'text',
|
||||
name: 'insideUnnamedGroup',
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
const withExtract = flattenFields(fields, {
|
||||
moveSubFieldsToTop: true,
|
||||
i18n,
|
||||
})
|
||||
|
||||
// Should keep the group as a single top-level field
|
||||
expect(withExtract).toHaveLength(1)
|
||||
expect(withExtract[0].type).toBe('text')
|
||||
expect(withExtract[0].accessor).toBeUndefined()
|
||||
expect(withExtract[0].labelWithPrefix).toBeUndefined()
|
||||
|
||||
const withoutExtract = flattenFields(fields)
|
||||
|
||||
expect(withoutExtract).toHaveLength(1)
|
||||
expect(withoutExtract[0].type).toBe('group')
|
||||
expect(withoutExtract[0].accessor).toBeUndefined()
|
||||
expect(withoutExtract[0].labelWithPrefix).toBeUndefined()
|
||||
})
|
||||
|
||||
it('should hoist using deepest named group only if parents are unnamed', () => {
|
||||
const fields: ClientField[] = [
|
||||
{
|
||||
type: 'group',
|
||||
label: 'Outer',
|
||||
fields: [
|
||||
{
|
||||
type: 'group',
|
||||
label: 'Middle',
|
||||
fields: [
|
||||
{
|
||||
type: 'group',
|
||||
name: 'namedGroup',
|
||||
label: 'Named Group',
|
||||
fields: [
|
||||
{
|
||||
type: 'group',
|
||||
label: 'Inner',
|
||||
fields: [
|
||||
{
|
||||
type: 'text',
|
||||
name: 'nestedField',
|
||||
label: 'Nested Field',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
const hoistedResult = flattenFields(fields, {
|
||||
moveSubFieldsToTop: true,
|
||||
i18n,
|
||||
})
|
||||
|
||||
expect(hoistedResult).toHaveLength(1)
|
||||
expect(hoistedResult[0].name).toBe('nestedField')
|
||||
expect(hoistedResult[0].accessor).toBe('namedGroup-nestedField')
|
||||
expect(hoistedResult[0].labelWithPrefix).toBe('Named Group > Nested Field')
|
||||
|
||||
const nonHoistedResult = flattenFields(fields)
|
||||
|
||||
expect(nonHoistedResult).toHaveLength(1)
|
||||
expect(nonHoistedResult[0].type).toBe('group')
|
||||
expect('fields' in nonHoistedResult[0]).toBe(true)
|
||||
expect('accessor' in nonHoistedResult[0]).toBe(false)
|
||||
expect('labelWithPrefix' in nonHoistedResult[0]).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('array and block edge cases', () => {
|
||||
it('should NOT flatten fields in arrays or blocks with moveSubFieldsToTop', () => {
|
||||
const fields: ClientField[] = [
|
||||
{
|
||||
type: 'array',
|
||||
name: 'items',
|
||||
label: 'Items',
|
||||
fields: [
|
||||
{
|
||||
type: 'text',
|
||||
name: 'label',
|
||||
label: 'Label',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'blocks',
|
||||
name: 'layout',
|
||||
blocks: [
|
||||
{
|
||||
slug: 'block',
|
||||
fields: [
|
||||
{
|
||||
type: 'text',
|
||||
name: 'content',
|
||||
label: 'Content',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
const result = flattenFields(fields, { moveSubFieldsToTop: true })
|
||||
expect(result).toHaveLength(2)
|
||||
expect(result[0].name).toBe('items')
|
||||
expect(result[1].name).toBe('layout')
|
||||
})
|
||||
|
||||
it('should NOT flatten fields in arrays or blocks without moveSubFieldsToTop', () => {
|
||||
const fields: ClientField[] = [
|
||||
{
|
||||
type: 'array',
|
||||
name: 'things',
|
||||
label: 'Things',
|
||||
fields: [
|
||||
{
|
||||
type: 'text',
|
||||
name: 'thingLabel',
|
||||
label: 'Thing Label',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'blocks',
|
||||
name: 'contentBlocks',
|
||||
blocks: [
|
||||
{
|
||||
slug: 'content',
|
||||
fields: [
|
||||
{
|
||||
type: 'text',
|
||||
name: 'body',
|
||||
label: 'Body',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
const result = flattenFields(fields)
|
||||
expect(result).toHaveLength(2)
|
||||
expect(result[0].name).toBe('things')
|
||||
expect(result[1].name).toBe('contentBlocks')
|
||||
})
|
||||
|
||||
it('should not hoist group fields nested inside arrays', () => {
|
||||
const fields: ClientField[] = [
|
||||
{
|
||||
type: 'array',
|
||||
name: 'arrayField',
|
||||
label: 'Array Field',
|
||||
fields: [
|
||||
{
|
||||
type: 'group',
|
||||
name: 'groupInArray',
|
||||
label: 'Group In Array',
|
||||
fields: [
|
||||
{
|
||||
type: 'text',
|
||||
name: 'nestedInArrayGroup',
|
||||
label: 'Nested In Array Group',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
const result = flattenFields(fields, { moveSubFieldsToTop: true })
|
||||
expect(result).toHaveLength(1)
|
||||
expect(result[0].name).toBe('arrayField')
|
||||
})
|
||||
|
||||
it('should not hoist group fields nested inside blocks', () => {
|
||||
const fields: ClientField[] = [
|
||||
{
|
||||
type: 'blocks',
|
||||
name: 'blockField',
|
||||
blocks: [
|
||||
{
|
||||
slug: 'exampleBlock',
|
||||
fields: [
|
||||
{
|
||||
type: 'group',
|
||||
name: 'groupInBlock',
|
||||
label: 'Group In Block',
|
||||
fields: [
|
||||
{
|
||||
type: 'text',
|
||||
name: 'nestedInBlockGroup',
|
||||
label: 'Nested In Block Group',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
const result = flattenFields(fields, { moveSubFieldsToTop: true })
|
||||
expect(result).toHaveLength(1)
|
||||
expect(result[0].name).toBe('blockField')
|
||||
})
|
||||
})
|
||||
|
||||
describe('row and collapsible behavior', () => {
|
||||
it('should recursively flatten collapsible fields regardless of moveSubFieldsToTop', () => {
|
||||
const fields: ClientField[] = [
|
||||
{
|
||||
type: 'collapsible',
|
||||
label: 'Collapsible',
|
||||
fields: [
|
||||
{
|
||||
type: 'text',
|
||||
name: 'nickname',
|
||||
label: 'Nickname',
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
const defaultResult = flattenFields(fields)
|
||||
const hoistedResult = flattenFields(fields, { moveSubFieldsToTop: true })
|
||||
|
||||
for (const result of [defaultResult, hoistedResult]) {
|
||||
expect(result).toHaveLength(1)
|
||||
expect(result[0].name).toBe('nickname')
|
||||
expect('accessor' in result[0]).toBe(false)
|
||||
expect('labelWithPrefix' in result[0]).toBe(false)
|
||||
}
|
||||
})
|
||||
|
||||
it('should recursively flatten row fields regardless of moveSubFieldsToTop', () => {
|
||||
const fields: ClientField[] = [
|
||||
{
|
||||
type: 'row',
|
||||
fields: [
|
||||
{
|
||||
type: 'text',
|
||||
name: 'firstName',
|
||||
label: 'First Name',
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
name: 'lastName',
|
||||
label: 'Last Name',
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
const defaultResult = flattenFields(fields)
|
||||
const hoistedResult = flattenFields(fields, { moveSubFieldsToTop: true })
|
||||
|
||||
for (const result of [defaultResult, hoistedResult]) {
|
||||
expect(result).toHaveLength(2)
|
||||
expect(result[0].name).toBe('firstName')
|
||||
expect(result[1].name).toBe('lastName')
|
||||
expect('accessor' in result[0]).toBe(false)
|
||||
expect('labelWithPrefix' in result[0]).toBe(false)
|
||||
}
|
||||
})
|
||||
|
||||
it('should hoist named group fields inside rows', () => {
|
||||
const fields: ClientField[] = [
|
||||
{
|
||||
type: 'row',
|
||||
fields: [
|
||||
{
|
||||
type: 'group',
|
||||
name: 'groupInRow',
|
||||
label: 'Group In Row',
|
||||
fields: [
|
||||
{
|
||||
type: 'text',
|
||||
name: 'nestedInRowGroup',
|
||||
label: 'Nested In Row Group',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
const result = flattenFields(fields, {
|
||||
moveSubFieldsToTop: true,
|
||||
i18n,
|
||||
})
|
||||
|
||||
expect(result).toHaveLength(1)
|
||||
expect(result[0].accessor).toBe('groupInRow-nestedInRowGroup')
|
||||
expect(result[0].labelWithPrefix).toBe('Group In Row > Nested In Row Group')
|
||||
})
|
||||
|
||||
it('should hoist named group fields inside collapsibles', () => {
|
||||
const fields: ClientField[] = [
|
||||
{
|
||||
type: 'collapsible',
|
||||
label: 'Collapsible',
|
||||
fields: [
|
||||
{
|
||||
type: 'group',
|
||||
name: 'groupInCollapsible',
|
||||
label: 'Group In Collapsible',
|
||||
fields: [
|
||||
{
|
||||
type: 'text',
|
||||
name: 'nestedInCollapsibleGroup',
|
||||
label: 'Nested In Collapsible Group',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
const result = flattenFields(fields, {
|
||||
moveSubFieldsToTop: true,
|
||||
i18n,
|
||||
})
|
||||
|
||||
expect(result).toHaveLength(1)
|
||||
expect(result[0].accessor).toBe('groupInCollapsible-nestedInCollapsibleGroup')
|
||||
expect(result[0].labelWithPrefix).toBe('Group In Collapsible > Nested In Collapsible Group')
|
||||
})
|
||||
})
|
||||
|
||||
describe('tab integration', () => {
|
||||
it('should hoist named group fields inside tabs when moveSubFieldsToTop is true', () => {
|
||||
const fields: ClientField[] = [
|
||||
{
|
||||
type: 'tabs',
|
||||
tabs: [
|
||||
{
|
||||
label: 'Tab One',
|
||||
fields: [
|
||||
{
|
||||
type: 'group',
|
||||
name: 'groupInTab',
|
||||
label: 'Group In Tab',
|
||||
fields: [
|
||||
{
|
||||
type: 'text',
|
||||
name: 'nestedInTabGroup',
|
||||
label: 'Nested In Tab Group',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
const result = flattenFields(fields, {
|
||||
moveSubFieldsToTop: true,
|
||||
i18n,
|
||||
})
|
||||
|
||||
expect(result).toHaveLength(1)
|
||||
expect(result[0].accessor).toBe('groupInTab-nestedInTabGroup')
|
||||
expect(result[0].labelWithPrefix).toBe('Group In Tab > Nested In Tab Group')
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,4 +1,8 @@
|
||||
// @ts-strict-ignore
|
||||
import type { I18nClient } from '@payloadcms/translations'
|
||||
|
||||
import { getTranslation } from '@payloadcms/translations'
|
||||
|
||||
import type { ClientTab } from '../admin/fields/Tabs.js'
|
||||
import type { ClientField } from '../fields/config/client.js'
|
||||
import type {
|
||||
@@ -18,38 +22,153 @@ import {
|
||||
} from '../fields/config/types.js'
|
||||
|
||||
type FlattenedField<TField> = TField extends ClientField
|
||||
? FieldAffectingDataClient | FieldPresentationalOnlyClient
|
||||
: FieldAffectingData | FieldPresentationalOnly
|
||||
? { accessor?: string; labelWithPrefix?: string } & (
|
||||
| FieldAffectingDataClient
|
||||
| FieldPresentationalOnlyClient
|
||||
)
|
||||
: { accessor?: string; labelWithPrefix?: string } & (FieldAffectingData | FieldPresentationalOnly)
|
||||
|
||||
type TabType<TField> = TField extends ClientField ? ClientTab : Tab
|
||||
|
||||
/**
|
||||
* Flattens a collection's fields into a single array of fields, as long
|
||||
* as the fields do not affect data.
|
||||
* Options to control how fields are flattened.
|
||||
*/
|
||||
type FlattenFieldsOptions = {
|
||||
/**
|
||||
* i18n context used for translating `label` values via `getTranslation`.
|
||||
*/
|
||||
i18n?: I18nClient
|
||||
/**
|
||||
* If true, presentational-only fields (like UI fields) will be included
|
||||
* in the output. Otherwise, they will be skipped.
|
||||
* Default: false.
|
||||
*/
|
||||
keepPresentationalFields?: boolean
|
||||
/**
|
||||
* A label prefix to prepend to translated labels when building `labelWithPrefix`.
|
||||
* Used recursively when flattening nested fields.
|
||||
*/
|
||||
labelPrefix?: string
|
||||
/**
|
||||
* If true, nested fields inside `group` fields will be lifted to the top level
|
||||
* and given contextual `accessor` and `labelWithPrefix` values.
|
||||
* Default: false.
|
||||
*/
|
||||
moveSubFieldsToTop?: boolean
|
||||
/**
|
||||
* A path prefix to prepend to field names when building the `accessor`.
|
||||
* Used recursively when flattening nested fields.
|
||||
*/
|
||||
pathPrefix?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Flattens a collection's fields into a single array of fields, optionally
|
||||
* extracting nested fields in group fields.
|
||||
*
|
||||
* @param fields
|
||||
* @param keepPresentationalFields if true, will skip flattening fields that are presentational only
|
||||
* @param fields - Array of fields to flatten
|
||||
* @param options - Options to control the flattening behavior
|
||||
*/
|
||||
function flattenFields<TField extends ClientField | Field>(
|
||||
fields: TField[],
|
||||
keepPresentationalFields?: boolean,
|
||||
options?: boolean | FlattenFieldsOptions,
|
||||
): FlattenedField<TField>[] {
|
||||
const normalizedOptions: FlattenFieldsOptions =
|
||||
typeof options === 'boolean' ? { keepPresentationalFields: options } : (options ?? {})
|
||||
|
||||
const {
|
||||
i18n,
|
||||
keepPresentationalFields,
|
||||
labelPrefix,
|
||||
moveSubFieldsToTop = false,
|
||||
pathPrefix,
|
||||
} = normalizedOptions
|
||||
|
||||
return fields.reduce<FlattenedField<TField>[]>((acc, field) => {
|
||||
if (fieldAffectsData(field) || (keepPresentationalFields && fieldIsPresentationalOnly(field))) {
|
||||
acc.push(field as FlattenedField<TField>)
|
||||
} else if (fieldHasSubFields(field)) {
|
||||
acc.push(...flattenFields(field.fields as TField[], keepPresentationalFields))
|
||||
if (fieldHasSubFields(field)) {
|
||||
if (field.type === 'group') {
|
||||
if (moveSubFieldsToTop && 'fields' in field) {
|
||||
const isNamedGroup = 'name' in field && typeof field.name === 'string' && !!field.name
|
||||
|
||||
const translatedLabel =
|
||||
'label' in field && field.label && i18n
|
||||
? getTranslation(field.label as string, i18n)
|
||||
: undefined
|
||||
|
||||
const labelWithPrefix =
|
||||
isNamedGroup && labelPrefix && translatedLabel
|
||||
? `${labelPrefix} > ${translatedLabel}`
|
||||
: (labelPrefix ?? translatedLabel)
|
||||
|
||||
const nameWithPrefix =
|
||||
isNamedGroup && field.name
|
||||
? pathPrefix
|
||||
? `${pathPrefix}-${field.name as string}`
|
||||
: (field.name as string)
|
||||
: pathPrefix
|
||||
|
||||
acc.push(
|
||||
...flattenFields(field.fields as TField[], {
|
||||
i18n,
|
||||
keepPresentationalFields,
|
||||
labelPrefix: isNamedGroup ? labelWithPrefix : labelPrefix,
|
||||
moveSubFieldsToTop,
|
||||
pathPrefix: isNamedGroup ? nameWithPrefix : pathPrefix,
|
||||
}),
|
||||
)
|
||||
} else {
|
||||
// Just keep the group as-is
|
||||
acc.push(field as FlattenedField<TField>)
|
||||
}
|
||||
} else if (['collapsible', 'row'].includes(field.type)) {
|
||||
// Recurse into row and collapsible
|
||||
acc.push(...flattenFields(field.fields as TField[], options))
|
||||
} else {
|
||||
// Do not hoist fields from arrays & blocks
|
||||
acc.push(field as FlattenedField<TField>)
|
||||
}
|
||||
} else if (
|
||||
fieldAffectsData(field) ||
|
||||
(keepPresentationalFields && fieldIsPresentationalOnly(field))
|
||||
) {
|
||||
// Ignore nested `id` fields when inside nested structure
|
||||
if (field.name === 'id' && labelPrefix !== undefined) {
|
||||
return acc
|
||||
}
|
||||
|
||||
const translatedLabel =
|
||||
'label' in field && field.label && i18n ? getTranslation(field.label, i18n) : undefined
|
||||
|
||||
const name = 'name' in field ? field.name : undefined
|
||||
|
||||
const isHoistingFromGroup = pathPrefix !== undefined || labelPrefix !== undefined
|
||||
|
||||
acc.push({
|
||||
...(field as FlattenedField<TField>),
|
||||
...(moveSubFieldsToTop &&
|
||||
isHoistingFromGroup && {
|
||||
accessor: pathPrefix && name ? `${pathPrefix}-${name}` : (name ?? ''),
|
||||
labelWithPrefix:
|
||||
labelPrefix && translatedLabel
|
||||
? `${labelPrefix} > ${translatedLabel}`
|
||||
: (labelPrefix ?? translatedLabel),
|
||||
}),
|
||||
})
|
||||
} else if (field.type === 'tabs' && 'tabs' in field) {
|
||||
return [
|
||||
...acc,
|
||||
...field.tabs.reduce<FlattenedField<TField>[]>((tabFields, tab: TabType<TField>) => {
|
||||
if (tabHasName(tab)) {
|
||||
return [...tabFields, { ...tab, type: 'tab' } as unknown as FlattenedField<TField>]
|
||||
} else {
|
||||
return [
|
||||
...tabFields,
|
||||
...flattenFields(tab.fields as TField[], keepPresentationalFields),
|
||||
{
|
||||
...tab,
|
||||
type: 'tab',
|
||||
...(moveSubFieldsToTop && { labelPrefix }),
|
||||
} as unknown as FlattenedField<TField>,
|
||||
]
|
||||
} else {
|
||||
return [...tabFields, ...flattenFields<TField>(tab.fields as TField[], options)]
|
||||
}
|
||||
}, []),
|
||||
]
|
||||
|
||||
@@ -47,36 +47,36 @@ let baseEvent: BaseEvent | null = null
|
||||
|
||||
export const sendEvent = async ({ event, payload }: Args): Promise<void> => {
|
||||
try {
|
||||
const { packageJSON, packageJSONPath } = await getPackageJSON()
|
||||
|
||||
// Only generate the base event once
|
||||
if (!baseEvent) {
|
||||
const { projectID, source: projectIDSource } = getProjectID(payload, packageJSON)
|
||||
baseEvent = {
|
||||
ciName: ciInfo.isCI ? ciInfo.name : null,
|
||||
envID: getEnvID(),
|
||||
isCI: ciInfo.isCI,
|
||||
nodeEnv: process.env.NODE_ENV || 'development',
|
||||
nodeVersion: process.version,
|
||||
payloadVersion: getPayloadVersion(packageJSON),
|
||||
projectID,
|
||||
projectIDSource,
|
||||
...getLocalizationInfo(payload),
|
||||
dbAdapter: payload.db.name,
|
||||
emailAdapter: payload.email?.name || null,
|
||||
uploadAdapters: payload.config.upload.adapters,
|
||||
}
|
||||
}
|
||||
|
||||
if (process.env.PAYLOAD_TELEMETRY_DEBUG) {
|
||||
payload.logger.info({
|
||||
event: { ...baseEvent, ...event, packageJSONPath },
|
||||
msg: 'Telemetry Event',
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if (payload.config.telemetry !== false) {
|
||||
const { packageJSON, packageJSONPath } = await getPackageJSON()
|
||||
|
||||
// Only generate the base event once
|
||||
if (!baseEvent) {
|
||||
const { projectID, source: projectIDSource } = getProjectID(payload, packageJSON)
|
||||
baseEvent = {
|
||||
ciName: ciInfo.isCI ? ciInfo.name : null,
|
||||
envID: getEnvID(),
|
||||
isCI: ciInfo.isCI,
|
||||
nodeEnv: process.env.NODE_ENV || 'development',
|
||||
nodeVersion: process.version,
|
||||
payloadVersion: getPayloadVersion(packageJSON),
|
||||
projectID,
|
||||
projectIDSource,
|
||||
...getLocalizationInfo(payload),
|
||||
dbAdapter: payload.db.name,
|
||||
emailAdapter: payload.email?.name || null,
|
||||
uploadAdapters: payload.config.upload.adapters,
|
||||
}
|
||||
}
|
||||
|
||||
if (process.env.PAYLOAD_TELEMETRY_DEBUG) {
|
||||
payload.logger.info({
|
||||
event: { ...baseEvent, ...event, packageJSONPath },
|
||||
msg: 'Telemetry Event',
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
await fetch('https://telemetry.payloadcms.com/events', {
|
||||
body: JSON.stringify({ ...baseEvent, ...event }),
|
||||
headers: {
|
||||
|
||||
@@ -487,6 +487,55 @@ const Checkbox: Block = {
|
||||
},
|
||||
}
|
||||
|
||||
const Date: Block = {
|
||||
slug: 'date',
|
||||
fields: [
|
||||
{
|
||||
type: 'row',
|
||||
fields: [
|
||||
{
|
||||
...name,
|
||||
admin: {
|
||||
width: '50%',
|
||||
},
|
||||
},
|
||||
{
|
||||
...label,
|
||||
admin: {
|
||||
width: '50%',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'row',
|
||||
fields: [
|
||||
{
|
||||
...width,
|
||||
admin: {
|
||||
width: '50%',
|
||||
},
|
||||
},
|
||||
{
|
||||
...required,
|
||||
admin: {
|
||||
width: '50%',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'defaultValue',
|
||||
type: 'date',
|
||||
label: 'Default Value',
|
||||
},
|
||||
],
|
||||
labels: {
|
||||
plural: 'Date Fields',
|
||||
singular: 'Date',
|
||||
},
|
||||
}
|
||||
|
||||
const Payment = (fieldConfig: PaymentFieldConfig): Block => {
|
||||
let paymentProcessorField = null
|
||||
if (fieldConfig?.paymentProcessor) {
|
||||
@@ -669,6 +718,7 @@ const Message: Block = {
|
||||
export const fields = {
|
||||
checkbox: Checkbox,
|
||||
country: Country,
|
||||
date: Date,
|
||||
email: Email,
|
||||
message: Message,
|
||||
number: Number,
|
||||
|
||||
@@ -33,6 +33,7 @@ export interface FieldsConfig {
|
||||
[key: string]: boolean | FieldConfig | undefined
|
||||
checkbox?: boolean | FieldConfig
|
||||
country?: boolean | FieldConfig
|
||||
date?: boolean | FieldConfig
|
||||
email?: boolean | FieldConfig
|
||||
message?: boolean | FieldConfig
|
||||
number?: boolean | FieldConfig
|
||||
@@ -146,6 +147,16 @@ export interface EmailField {
|
||||
width?: number
|
||||
}
|
||||
|
||||
export interface DateField {
|
||||
blockName?: string
|
||||
blockType: 'date'
|
||||
defaultValue?: string
|
||||
label?: string
|
||||
name: string
|
||||
required?: boolean
|
||||
width?: number
|
||||
}
|
||||
|
||||
export interface StateField {
|
||||
blockName?: string
|
||||
blockType: 'state'
|
||||
@@ -185,6 +196,7 @@ export interface MessageField {
|
||||
export type FormFieldBlock =
|
||||
| CheckboxField
|
||||
| CountryField
|
||||
| DateField
|
||||
| EmailField
|
||||
| MessageField
|
||||
| PaymentField
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
import type { GenericTranslationsObject } from '@payloadcms/translations'
|
||||
|
||||
export const en: GenericTranslationsObject = {
|
||||
$schema: './translation-schema.json',
|
||||
'plugin-seo': {
|
||||
export: 'Export',
|
||||
import: 'Import',
|
||||
},
|
||||
}
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
for file in *.js; do
|
||||
mv -- "$file" "${file%.js}.ts"
|
||||
done
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import type { PluginDefaultTranslationsObject, PluginLanguage } from '../types.js'
|
||||
|
||||
export const thTranslations: 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 th: PluginLanguage = {
|
||||
dateFNSKey: 'th',
|
||||
translations: thTranslations,
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import type { PluginDefaultTranslationsObject, PluginLanguage } from '../types.js'
|
||||
|
||||
export const trTranslations: PluginDefaultTranslationsObject = {
|
||||
'plugin-import-export': {
|
||||
allLocales: 'Tüm yerler',
|
||||
exportDocumentLabel: '{{label}} dışa aktar',
|
||||
exportOptions: 'İhracat Seçenekleri',
|
||||
'field-depth-label': 'Derinlik',
|
||||
'field-drafts-label': 'Taslakları dahil et',
|
||||
'field-fields-label': 'Alanlar',
|
||||
'field-format-label': 'İhracat Formatı',
|
||||
'field-limit-label': 'Sınır',
|
||||
'field-locale-label': 'Yerel',
|
||||
'field-name-label': 'Dosya adı',
|
||||
'field-selectionToUse-label': 'Kullanılacak seçim',
|
||||
'field-sort-label': 'Sırala',
|
||||
'selectionToUse-allDocuments': 'Tüm belgeleri kullanın',
|
||||
'selectionToUse-currentFilters': 'Mevcut filtreleri kullanın',
|
||||
'selectionToUse-currentSelection': 'Mevcut seçimi kullanın',
|
||||
totalDocumentsCount: '{{count}} toplam belge',
|
||||
},
|
||||
}
|
||||
|
||||
export const tr: PluginLanguage = {
|
||||
dateFNSKey: 'tr',
|
||||
translations: trTranslations,
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
{
|
||||
"type": "object",
|
||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"$schema": {
|
||||
"type": "string"
|
||||
},
|
||||
"plugin-import-export": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"export": {
|
||||
"type": "string"
|
||||
},
|
||||
"import": {
|
||||
"type": "string"
|
||||
},
|
||||
"allLocales": {
|
||||
"type": "string"
|
||||
},
|
||||
"download": {
|
||||
"type": "string"
|
||||
},
|
||||
"exportDocumentLabel": {
|
||||
"type": "string"
|
||||
},
|
||||
"exportOptions": {
|
||||
"type": "string"
|
||||
},
|
||||
"field-depth-label": {
|
||||
"type": "string"
|
||||
},
|
||||
"field-drafts-label": {
|
||||
"type": "string"
|
||||
},
|
||||
"field-fields-label": {
|
||||
"type": "string"
|
||||
},
|
||||
"field-format-label": {
|
||||
"type": "string"
|
||||
},
|
||||
"field-limit-label": {
|
||||
"type": "string"
|
||||
},
|
||||
"field-locale-label": {
|
||||
"type": "string"
|
||||
},
|
||||
"field-name-label": {
|
||||
"type": "string"
|
||||
},
|
||||
"field-selectionToUse-label": {
|
||||
"type": "string"
|
||||
},
|
||||
"field-sort-label": {
|
||||
"type": "string"
|
||||
},
|
||||
"no": {
|
||||
"type": "string"
|
||||
},
|
||||
"preview": {
|
||||
"type": "string"
|
||||
},
|
||||
"selectionToUse-allDocuments": {
|
||||
"type": "string"
|
||||
},
|
||||
"selectionToUse-currentFilters": {
|
||||
"type": "string"
|
||||
},
|
||||
"selectionToUse-currentSelection": {
|
||||
"type": "string"
|
||||
},
|
||||
"totalDocumentsCount": {
|
||||
"type": "string"
|
||||
},
|
||||
"yes": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"export",
|
||||
"import",
|
||||
"allLocales",
|
||||
"download",
|
||||
"exportDocumentLabel",
|
||||
"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",
|
||||
"no",
|
||||
"preview",
|
||||
"selectionToUse-allDocuments",
|
||||
"selectionToUse-currentFilters",
|
||||
"selectionToUse-currentSelection",
|
||||
"totalDocumentsCount",
|
||||
"yes"
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": ["plugin-import-export"]
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import type { PluginDefaultTranslationsObject, PluginLanguage } from '../types.js'
|
||||
|
||||
export const ukTranslations: 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 uk: PluginLanguage = {
|
||||
dateFNSKey: 'uk',
|
||||
translations: ukTranslations,
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import type { PluginDefaultTranslationsObject, PluginLanguage } from '../types.js'
|
||||
|
||||
export const viTranslations: PluginDefaultTranslationsObject = {
|
||||
'plugin-import-export': {
|
||||
allLocales: 'Tất cả địa điểm',
|
||||
exportDocumentLabel: 'Xuất {{label}}',
|
||||
exportOptions: 'Tùy chọn xuất',
|
||||
'field-depth-label': 'Độ sâu',
|
||||
'field-drafts-label': 'Bao gồm bản thảo',
|
||||
'field-fields-label': 'Cánh đồng',
|
||||
'field-format-label': 'Định dạng Xuất khẩu',
|
||||
'field-limit-label': 'Giới hạn',
|
||||
'field-locale-label': 'Địa phương',
|
||||
'field-name-label': 'Tên tệp',
|
||||
'field-selectionToUse-label': 'Lựa chọn để sử dụng',
|
||||
'field-sort-label': 'Sắp xếp theo',
|
||||
'selectionToUse-allDocuments': 'Sử dụng tất cả các tài liệu',
|
||||
'selectionToUse-currentFilters': 'Sử dụng bộ lọc hiện tại',
|
||||
'selectionToUse-currentSelection': 'Sử dụng lựa chọn hiện tại',
|
||||
totalDocumentsCount: '{{count}} tổng số tài liệu',
|
||||
},
|
||||
}
|
||||
|
||||
export const vi: PluginLanguage = {
|
||||
dateFNSKey: 'vi',
|
||||
translations: viTranslations,
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import type { PluginDefaultTranslationsObject, PluginLanguage } from '../types.js'
|
||||
|
||||
export const zhTranslations: 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 zh: PluginLanguage = {
|
||||
dateFNSKey: 'zh-CN',
|
||||
translations: zhTranslations,
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user