Compare commits

...

42 Commits

Author SHA1 Message Date
Elliot DeNolf
08f372e6c2 chore(release): db-postgres/0.8.0 [skip ci] 2024-04-19 11:37:56 -04:00
Elliot DeNolf
2994269f22 chore(release): db-mongodb/1.5.0 [skip ci] 2024-04-19 11:37:43 -04:00
Elliot DeNolf
8d1a706928 chore(release): payload/2.13.0 [skip ci] 2024-04-19 11:36:17 -04:00
Pan
fcb29bb1c6 feat(plugin-seo): add Chinese translation (#5429) 2024-04-19 11:13:26 -04:00
Kendell Joseph
2c402cc65c feat: json field schemas (#5726) 2024-04-19 10:45:40 -04:00
Elliot DeNolf
ad38f76011 fix: properly handle drafts in bulk update (#5872)
Co-authored-by: Dan Ribbens <dan.ribbens@gmail.com>
2024-04-19 10:08:26 -04:00
Jessica Chowdhury
b974a2c042 chore(i18n): adds document translation key (#5889) 2024-04-18 10:17:35 +01:00
Dan Ribbens
cb8d562132 fix(db-mongodb): ignore end session errors (#5904) 2024-04-17 16:44:15 -04:00
Patrik
1f0036054a fix: adds type error validations for email and password in login operation (#4852) 2024-04-17 16:31:35 -04:00
Elliot DeNolf
aaa4397351 ci: cut down on codeowners noise [skip ci] 2024-04-16 22:10:44 -04:00
Dan Ribbens
6185f8a5d8 fix(db-postgres): query hasMany fields with in (#5881) 2024-04-16 17:09:53 -04:00
Jacob Fletcher
e47e544364 fix(plugin-seo): uses correct key for ukrainian translation (#5873) 2024-04-16 11:16:33 -04:00
Bohdan Kucheriavyi
2e80350a5a chore(plugin-seo): adds Ukrainian translations (#5843)
Signed-off-by: Bohdan Kucheriavyi <bohdan.kucheriavyi@zapal.tech>
2024-04-16 09:31:18 -04:00
Ritsu
4c4f924e90 fix(db-postgres): validateExistingBlockIsIdentical localized (#5839) 2024-04-15 16:49:43 -04:00
Elliot DeNolf
170957c380 feat: allow configuration for setting headers on external file fetch (#5865) 2024-04-15 15:09:00 -04:00
Elliot DeNolf
9061ae05e7 docs: add externalFileHeaderFilter 2024-04-15 15:08:03 -04:00
Dan Ribbens
fe0028c899 fix(db-mongodb): version fields indexSortableFields (#5863) 2024-04-15 14:46:06 -04:00
Elliot DeNolf
ec1ad0b662 feat: allow configuration for setting headers on external file fetch 2024-04-15 14:40:55 -04:00
Tylan Davis
e5f32562a3 docs: adds missing tick marks (#5858) 2024-04-15 13:06:17 -04:00
Tylan Davis
ba6100cbe8 docs: adds missing tick marks 2024-04-15 13:03:44 -04:00
Alessio Gravili
c08c8b5628 ci: add weird tune linux network step which seems to reduce flakes (#5857) 2024-04-15 12:38:38 -04:00
James Mikrut
9e918831d1 fix: number ids were not sanitized to number in rest api (#5794) 2024-04-15 11:02:36 -04:00
Patrik
25c9a145be fix: passes parent id instead of incoming id to saveVersion (#5831) 2024-04-15 10:42:36 -04:00
Alessio Gravili
3b1d331316 fix(richtext-lexical): use correct nodeType on HorizontalRule feature HTML converter (#5805) 2024-04-11 16:10:55 -04:00
Alessio Gravili
5aa68b5e5d feat(richtext-lexical): add HorizontalRuleFeature, improve block handle positioning (#5804) 2024-04-11 15:54:56 -04:00
Alessio Gravili
d8e9084db2 feat(richtext-lexical): add HorizontalRuleFeature 2024-04-11 15:37:45 -04:00
Dan Ribbens
65690a675c fix(db-postgres): relationship query pagination (#5802) 2024-04-11 15:37:19 -04:00
PatrikKozak
9530d28a67 fix: updates var 2024-04-11 15:08:24 -04:00
PatrikKozak
509ec677c4 fix: uses find instead of fieldIndex for custom ID check 2024-04-11 14:19:45 -04:00
Alessio Gravili
a00439ea89 fix(richtext-lexical): limit unnecessary floating handle positioning updates 2024-04-11 14:12:59 -04:00
Alessio Gravili
0055a8eb36 feat(richtext-lexical): improve floating handle y-positioning by positioning it in the center for smaller elements. 2024-04-11 13:50:24 -04:00
Alessio Gravili
de5d6cc4bd fix(richtext-lexical): incorrect floating handle y-position calculation next to certain kinds of HTML elements like HR 2024-04-11 12:29:49 -04:00
PatrikKozak
51f84a4fcf fix: number ids were not sanitized to number in rest api 2024-04-11 12:23:57 -04:00
Paul
c0ba6cc19a fix: use isolateObjectProperty function in createLocalReq (#5748) 2024-04-11 09:27:42 -04:00
Patrik
5fa99fb060 fix(db-mongodb): failing contains query with special chars (#5774) 2024-04-11 09:23:05 -04:00
Patrik
e3c3ddac34 fix: avoids getting and setting doc preferences when creating new (#5757) 2024-04-10 11:20:02 -04:00
Alessio Gravili
6186493246 fix(richtext-lexical)!: do not allow omitting editor prop for sub-richtext fields within lexical defined in the payload config (#5766)
**BREAKING:** Modifies fields types which are allowed to be passed in to upload, link, and blocks lexical features. Can break your types even if no sub-richText editor is passed in
2024-04-10 11:14:32 -04:00
Alessio Gravili
9b44296092 fix(richtext-lexical): catch errors that may occur during HTML generation (#5754) 2024-04-09 15:58:34 -04:00
Paul
cbd03ed2f8 fix: req.collection being lost when querying a global inside a collection (#5727) 2024-04-08 15:16:37 -03:00
Alessio Gravili
cf135fd1e4 fix(richtext-lexical): pass through config for schema generation. Makes it more robust (#5700) 2024-04-05 15:55:51 -04:00
Dan Ribbens
e7608f5507 fix: block field type missing dbName (#5695) 2024-04-05 15:52:06 -04:00
Alessio Gravili
608d6d0a87 fix(db-postgres): hasMany relationship query contains operator (#4212)
Co-authored-by: Dan Ribbens <dan.ribbens@gmail.com>
2024-04-05 13:59:00 -04:00
122 changed files with 4201 additions and 403 deletions

46
.github/CODEOWNERS vendored
View File

@@ -1,50 +1,32 @@
# Order matters. The last matching pattern takes precedence. # Order matters. The last matching pattern takes precedence.
### Catch-all ###
* @denolfe @jmikrut @DanRibbens
.* @denolfe @jmikrut @DanRibbens
### Core ### ### Core ###
/packages/payload/ @denolfe @jmikrut @DanRibbens
/packages/payload/src/uploads/ @denolfe
/packages/payload/src/admin/ @jmikrut @jacobsfletch @JarrodMFlesch
### Adapters ### ### Adapters ###
/packages/bundler-*/ @denolfe @jmikrut @DanRibbens @JarrodMFlesch /packages/richtext-*/ @AlessioGr
/packages/db-*/ @denolfe @jmikrut @DanRibbens
/packages/richtext-*/ @denolfe @jmikrut @DanRibbens @AlessioGr
### Plugins ### ### Plugins ###
/packages/plugin-*/ @denolfe @jmikrut @DanRibbens @jacobsfletch @JarrodMFlesch @AlessioGr
/packages/plugin-cloud*/ @denolfe /packages/plugin-cloud*/ @denolfe
/packages/plugin-form-builder/ @jacobsfletch
/packages/plugin-live-preview*/ @jacobsfletch
/packages/plugin-nested-docs/ @jacobsfletch
/packages/plugin-password-protection/ @jmikrut
/packages/plugin-redirects/ @jacobsfletch
/packages/plugin-search/ @jacobsfletch
/packages/plugin-sentry/ @JessChowdhury
/packages/plugin-seo/ @jacobsfletch
/packages/plugin-stripe/ @jacobsfletch
/packages/plugin-zapier/ @JarrodMFlesch
### Examples ###
/examples/ @jacobsfletch
/examples/testing/ @JarrodMFlesch
/examples/email/ @JessChowdhury
/examples/whitelabel/ @JessChowdhury
### Templates ### ### Templates ###
/templates/ @jacobsfletch /templates/ @jacobsfletch @denolfe
/templates/blank/ @denolfe
### Misc ### ### Misc ###
/packages/create-payload-app/ @denolfe /packages/create-payload-app/ @denolfe
/packages/eslint-config-payload/ @denolfe /packages/eslint-*/ @denolfe
/packages/payload-admin-bar/ @jacobsfletch
### Build Files ###
/**/package.json @denolfe
/tsconfig.json @denolfe
/**/tsconfig*.json @denolfe
/jest.config.js @denolfe
/**/jest.config.js @denolfe
### Root ### ### Root ###
/package.json @denolfe /package.json @denolfe
/scripts/ @denolfe /scripts/ @denolfe
/.husky/ @denolfe
/.vscode/ @denolfe
/.github/ @denolfe /.github/ @denolfe
/.github/CODEOWNERS @denolfe

View File

@@ -15,6 +15,10 @@ jobs:
needs_build: ${{ steps.filter.outputs.needs_build }} needs_build: ${{ steps.filter.outputs.needs_build }}
templates: ${{ steps.filter.outputs.templates }} templates: ${{ steps.filter.outputs.templates }}
steps: steps:
# https://github.com/actions/virtual-environments/issues/1187
- name: tune linux network
run: sudo ethtool -K eth0 tx off rx off
- uses: actions/checkout@v4 - uses: actions/checkout@v4
with: with:
fetch-depth: 25 fetch-depth: 25
@@ -45,6 +49,10 @@ jobs:
with: with:
fetch-depth: 25 fetch-depth: 25
# https://github.com/actions/virtual-environments/issues/1187
- name: tune linux network
run: sudo ethtool -K eth0 tx off rx off
- name: Use Node.js 18 - name: Use Node.js 18
uses: actions/setup-node@v3 uses: actions/setup-node@v3
with: with:
@@ -96,6 +104,10 @@ jobs:
AWS_REGION: us-east-1 AWS_REGION: us-east-1
steps: steps:
# https://github.com/actions/virtual-environments/issues/1187
- name: tune linux network
run: sudo ethtool -K eth0 tx off rx off
- name: Use Node.js 18 - name: Use Node.js 18
uses: actions/setup-node@v3 uses: actions/setup-node@v3
with: with:
@@ -177,6 +189,10 @@ jobs:
part: [ 1/8, 2/8, 3/8, 4/8, 5/8, 6/8, 7/8, 8/8 ] part: [ 1/8, 2/8, 3/8, 4/8, 5/8, 6/8, 7/8, 8/8 ]
steps: steps:
# https://github.com/actions/virtual-environments/issues/1187
- name: tune linux network
run: sudo ethtool -K eth0 tx off rx off
- name: Use Node.js 18 - name: Use Node.js 18
uses: actions/setup-node@v3 uses: actions/setup-node@v3
with: with:
@@ -214,6 +230,10 @@ jobs:
needs: core-build needs: core-build
steps: steps:
# https://github.com/actions/virtual-environments/issues/1187
- name: tune linux network
run: sudo ethtool -K eth0 tx off rx off
- name: Use Node.js 18 - name: Use Node.js 18
uses: actions/setup-node@v3 uses: actions/setup-node@v3
with: with:
@@ -254,6 +274,10 @@ jobs:
- live-preview-react - live-preview-react
steps: steps:
# https://github.com/actions/virtual-environments/issues/1187
- name: tune linux network
run: sudo ethtool -K eth0 tx off rx off
- name: Use Node.js 18 - name: Use Node.js 18
uses: actions/setup-node@v3 uses: actions/setup-node@v3
with: with:
@@ -291,6 +315,10 @@ jobs:
- plugin-seo - plugin-seo
steps: steps:
# https://github.com/actions/virtual-environments/issues/1187
- name: tune linux network
run: sudo ethtool -K eth0 tx off rx off
- name: Use Node.js 18 - name: Use Node.js 18
uses: actions/setup-node@v3 uses: actions/setup-node@v3
with: with:
@@ -329,6 +357,10 @@ jobs:
with: with:
fetch-depth: 25 fetch-depth: 25
# https://github.com/actions/virtual-environments/issues/1187
- name: tune linux network
run: sudo ethtool -K eth0 tx off rx off
- name: Use Node.js 18 - name: Use Node.js 18
uses: actions/setup-node@v3 uses: actions/setup-node@v3
with: with:

View File

@@ -1,3 +1,51 @@
## [2.13.0](https://github.com/payloadcms/payload/compare/v2.12.1...v2.13.0) (2024-04-19)
### Features
* allow configuration for setting headers on external file fetch ([ec1ad0b](https://github.com/payloadcms/payload/commit/ec1ad0b6628d400d7435821c8a72b6746bf87577))
* **db-\*:** custom db table and enum names ([#5045](https://github.com/payloadcms/payload/issues/5045)) ([9bbacc4](https://github.com/payloadcms/payload/commit/9bbacc4fb1ad247634f394e95c42ee3adade8048))
* json field schemas ([#5726](https://github.com/payloadcms/payload/issues/5726)) ([2c402cc](https://github.com/payloadcms/payload/commit/2c402cc65c9e8f7f33e2fb0ce5e1a8ceff52af1b))
* **plugin-seo:** add Chinese translation ([#5429](https://github.com/payloadcms/payload/issues/5429)) ([fcb29bb](https://github.com/payloadcms/payload/commit/fcb29bb1c637867301bbc1070b4a84383bf0e90a))
* **richtext-lexical:** add HorizontalRuleFeature ([d8e9084](https://github.com/payloadcms/payload/commit/d8e9084db21828968046ab59775633e409ce5c2a))
* **richtext-lexical:** improve floating handle y-positioning by positioning it in the center for smaller elements. ([0055a8e](https://github.com/payloadcms/payload/commit/0055a8eb36b95722cccdc5eb3101a79d3e764f8b))
### Bug Fixes
* adds type error validations for `email` and `password` in login operation ([#4852](https://github.com/payloadcms/payload/issues/4852)) ([1f00360](https://github.com/payloadcms/payload/commit/1f0036054a9461535b0992f2449e91e4eaf97d4e))
* avoids getting and setting doc preferences when creating new ([#5757](https://github.com/payloadcms/payload/issues/5757)) ([e3c3dda](https://github.com/payloadcms/payload/commit/e3c3ddac34dff8fa085f5b702be2838d513be300))
* block field type missing dbName ([#5695](https://github.com/payloadcms/payload/issues/5695)) ([e7608f5](https://github.com/payloadcms/payload/commit/e7608f5507d3b85ea3f44b5cb1f43edf67608b1b))
* **db-mongodb:** failing `contains` query with special chars ([#5774](https://github.com/payloadcms/payload/issues/5774)) ([5fa99fb](https://github.com/payloadcms/payload/commit/5fa99fb060cabbb69b5d6688748260e562e6bea3))
* **db-mongodb:** ignore end session errors ([#5904](https://github.com/payloadcms/payload/issues/5904)) ([cb8d562](https://github.com/payloadcms/payload/commit/cb8d562132bee437798880e1d7f64dbfdee36949))
* **db-mongodb:** version fields indexSortableFields ([#5863](https://github.com/payloadcms/payload/issues/5863)) ([fe0028c](https://github.com/payloadcms/payload/commit/fe0028c89945303a431b48efdae7b6e22304c8a3))
* **db-postgres:** hasMany relationship query contains operator ([#4212](https://github.com/payloadcms/payload/issues/4212)) ([608d6d0](https://github.com/payloadcms/payload/commit/608d6d0a872af224ea42c3e6c8a3b4f21678f550))
* **db-postgres:** issue querying by localised relationship not respecting locale as constraint ([#5666](https://github.com/payloadcms/payload/issues/5666)) ([44599cb](https://github.com/payloadcms/payload/commit/44599cbc7b8f23d6d8c7a3e05466237406812a6d))
* **db-postgres:** query hasMany fields with in ([#5881](https://github.com/payloadcms/payload/issues/5881)) ([6185f8a](https://github.com/payloadcms/payload/commit/6185f8a5d845d12651f5a3ee128eb43d3b9d2449))
* **db-postgres:** relationship query pagination ([#5802](https://github.com/payloadcms/payload/issues/5802)) ([65690a6](https://github.com/payloadcms/payload/commit/65690a675c17cfacebe775a327a57741ac09416a))
* **db-postgres:** validateExistingBlockIsIdentical localized ([#5839](https://github.com/payloadcms/payload/issues/5839)) ([4c4f924](https://github.com/payloadcms/payload/commit/4c4f924e90ee23a73c9a7cc7e69bbc2caf902b92))
* duplicate document multiple times in quick succession ([#5642](https://github.com/payloadcms/payload/issues/5642)) ([373787d](https://github.com/payloadcms/payload/commit/373787de31cbbd33b587aa4be6344948f082f5bb))
* missing date locales ([#5656](https://github.com/payloadcms/payload/issues/5656)) ([c1c8600](https://github.com/payloadcms/payload/commit/c1c86009a5e9aad401a05f7c63ad37bd3f88dc84))
* number ids were not sanitized to number in rest api ([51f84a4](https://github.com/payloadcms/payload/commit/51f84a4fcfd437eb73c7d83205b66e3620085909))
* passes parent id instead of incoming id to saveVersion ([#5831](https://github.com/payloadcms/payload/issues/5831)) ([25c9a14](https://github.com/payloadcms/payload/commit/25c9a145bec9e9566d2bbcba59d5b34394e10bbd))
* **plugin-seo:** uses correct key for ukrainian translation ([#5873](https://github.com/payloadcms/payload/issues/5873)) ([e47e544](https://github.com/payloadcms/payload/commit/e47e544364031ac834565a4d86ef6ec9c04e63c0))
* properly handle drafts in bulk update ([#5872](https://github.com/payloadcms/payload/issues/5872)) ([ad38f76](https://github.com/payloadcms/payload/commit/ad38f760111abf947c6b0ee4b983ee1224a9bf1b))
* req.collection being lost when querying a global inside a collection ([#5727](https://github.com/payloadcms/payload/issues/5727)) ([cbd03ed](https://github.com/payloadcms/payload/commit/cbd03ed2f8819ee8ac20e8739cc03e88ff4caa25))
* **richtext-lexical:** catch errors that may occur during HTML generation ([#5754](https://github.com/payloadcms/payload/issues/5754)) ([9b44296](https://github.com/payloadcms/payload/commit/9b442960929d00faa07f1383b1267f71e6f44efe))
* **richtext-lexical:** do not allow omitting editor prop for sub-richtext fields within lexical defined in the payload config ([#5766](https://github.com/payloadcms/payload/issues/5766)) ([6186493](https://github.com/payloadcms/payload/commit/6186493246157b4d4b33c8c47378f08581315942))
* **richtext-lexical:** incorrect floating handle y-position calculation next to certain kinds of HTML elements like HR ([de5d6cc](https://github.com/payloadcms/payload/commit/de5d6cc4bd591745156f0b8c56795b7bd2eaad7e))
* **richtext-lexical:** limit unnecessary floating handle positioning updates ([a00439e](https://github.com/payloadcms/payload/commit/a00439ea893e074d64be83ee6af1e780178a7ee3))
* **richtext-lexical:** pass through config for schema generation. Makes it more robust ([#5700](https://github.com/payloadcms/payload/issues/5700)) ([cf135fd](https://github.com/payloadcms/payload/commit/cf135fd1e4aeb30121281399e26be901393ada6d))
* **richtext-lexical:** use correct nodeType on HorizontalRule feature HTML converter ([#5805](https://github.com/payloadcms/payload/issues/5805)) ([3b1d331](https://github.com/payloadcms/payload/commit/3b1d3313165499616673f6d363c90ef884994525))
* updates type name of `CustomPublishButtonProps` to `CustomPublishButtonType` ([#5644](https://github.com/payloadcms/payload/issues/5644)) ([7df7bf4](https://github.com/payloadcms/payload/commit/7df7bf448bd26e870a1fde8aaa47430904d68366))
* updates var ([9530d28](https://github.com/payloadcms/payload/commit/9530d28a6760a667b718027a49ea43ba1accd546))
* use isolateObjectProperty function in createLocalReq ([#5748](https://github.com/payloadcms/payload/issues/5748)) ([c0ba6cc](https://github.com/payloadcms/payload/commit/c0ba6cc19a20c043a08ca77caacd47ef7cfb48f4))
* uses find instead of fieldIndex for custom ID check ([509ec67](https://github.com/payloadcms/payload/commit/509ec677c42993d9c08facf6928a5ef1e9767508))
### ⚠ BREAKING CHANGES
* **richtext-lexical:** do not allow omitting editor prop for sub-richtext fields within lexical defined in the payload config ([#5766](https://github.com/payloadcms/payload/issues/5766))
## [2.12.1](https://github.com/payloadcms/payload/compare/v2.12.0...v2.12.1) (2024-04-03) ## [2.12.1](https://github.com/payloadcms/payload/compare/v2.12.0...v2.12.1) (2024-04-03)

View File

@@ -4,7 +4,7 @@ label: JSON
order: 50 order: 50
desc: The JSON field type will store any string in the Database. Learn how to use JSON fields, see examples and options. desc: The JSON field type will store any string in the Database. Learn how to use JSON fields, see examples and options.
keywords: json, fields, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, express keywords: json, jsonSchema, schema, validation, fields, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, express
--- ---
<Banner> <Banner>
@@ -30,6 +30,7 @@ This field uses the `monaco-react` editor syntax highlighting.
| **`unique`** | Enforce that each entry in the Collection has a unique value for this field. | | **`unique`** | Enforce that each entry in the Collection has a unique value for this field. |
| **`index`** | Build an [index](/docs/database/overview) for this field to produce faster queries. Set this field to `true` if your users will perform queries on this field's data often. | | **`index`** | Build an [index](/docs/database/overview) for this field to produce faster queries. Set this field to `true` if your users will perform queries on this field's data often. |
| **`validate`** | Provide a custom validation function that will be executed on both the Admin panel and the backend. [More](/docs/fields/overview#validation) | | **`validate`** | Provide a custom validation function that will be executed on both the Admin panel and the backend. [More](/docs/fields/overview#validation) |
| **`jsonSchema`** | Provide a JSON schema that will be used for validation. [JSON schemas](https://json-schema.org/learn/getting-started-step-by-step) |
| **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](/docs/authentication/config), include its data in the user JWT. | | **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](/docs/authentication/config), include its data in the user JWT. |
| **`hooks`** | Provide field-based hooks to control logic for this field. [More](/docs/fields/overview#field-level-hooks) | | **`hooks`** | Provide field-based hooks to control logic for this field. [More](/docs/fields/overview#field-level-hooks) |
| **`access`** | Provide field-based access control to denote what users can see and do with this field's data. [More](/docs/fields/overview#field-level-access-control) | | **`access`** | Provide field-based access control to denote what users can see and do with this field's data. [More](/docs/fields/overview#field-level-access-control) |
@@ -52,7 +53,7 @@ In addition to the default [field admin config](/docs/fields/overview#admin-conf
### Example ### Example
`collections/ExampleCollection.ts `collections/ExampleCollection.ts`
```ts ```ts
import { CollectionConfig } from 'payload/types' import { CollectionConfig } from 'payload/types'
@@ -68,3 +69,68 @@ export const ExampleCollection: CollectionConfig = {
], ],
} }
``` ```
### JSON Schema Validation
Payload JSON fields fully support the [JSON schema](https://json-schema.org/) standard. By providing a schema in your field config, the editor will be guided in the admin UI, getting typeahead for properties and their formats automatically. When the document is saved, the default validation will prevent saving any invalid data in the field according to the schema in your config.
If you only provide a URL to a schema, Payload will fetch the desired schema if it is publicly available. If not, it is recommended to add the schema directly to your config or import it from another file so that it can be implemented consistently in your project.
#### Local JSON Schema
`collections/ExampleCollection.ts`
```ts
import { CollectionConfig } from 'payload/types'
export const ExampleCollection: CollectionConfig = {
slug: 'example-collection',
fields: [
{
name: 'customerJSON', // required
type: 'json', // required
jsonSchema: {
uri: 'a://b/foo.json', // required
fileMatch: ['a://b/foo.json'], // required
schema: {
type: 'object',
properties: {
foo: {
enum: ['bar', 'foobar'],
}
},
},
},
},
],
}
// {"foo": "bar"} or {"foo": "foobar"} - ok
// Attempting to create {"foo": "not-bar"} will throw an error
```
#### Remote JSON Schema
`collections/ExampleCollection.ts`
```ts
import { CollectionConfig } from 'payload/types'
export const ExampleCollection: CollectionConfig = {
slug: 'example-collection',
fields: [
{
name: 'customerJSON', // required
type: 'json', // required
jsonSchema: {
uri: 'https://example.com/customer.schema.json', // required
fileMatch: ['https://example.com/customer.schema.json'], // required
},
},
],
}
// If 'https://example.com/customer.schema.json' has a JSON schema
// {"foo": "bar"} or {"foo": "foobar"} - ok
// Attempting to create {"foo": "not-bar"} will throw an error
```

View File

@@ -153,13 +153,14 @@ Here's an overview of all the included features:
| **`HeadingFeature`** | Yes | Adds Heading Nodes (by default, H1 - H6, but that can be customized) | | **`HeadingFeature`** | Yes | Adds Heading Nodes (by default, H1 - H6, but that can be customized) |
| **`AlignFeature`** | Yes | Allows you to align text left, centered and right | | **`AlignFeature`** | Yes | Allows you to align text left, centered and right |
| **`IndentFeature`** | Yes | Allows you to indent text with the tab key | | **`IndentFeature`** | Yes | Allows you to indent text with the tab key |
| **`UnorderedListFeature`** | Yes | Adds unordered lists (ul) | | **`UnorderedListFeature`** | Yes | Adds unordered lists (ul) |
| **`OrderedListFeature`** | Yes | Adds ordered lists (ol) | | **`OrderedListFeature`** | Yes | Adds ordered lists (ol) |
| **`CheckListFeature`** | Yes | Adds checklists | | **`CheckListFeature`** | Yes | Adds checklists |
| **`LinkFeature`** | Yes | Allows you to create internal and external links | | **`LinkFeature`** | Yes | Allows you to create internal and external links |
| **`RelationshipFeature`** | Yes | Allows you to create block-level (not inline) relationships to other documents | | **`RelationshipFeature`** | Yes | Allows you to create block-level (not inline) relationships to other documents |
| **`BlockQuoteFeature`** | Yes | Allows you to create block-level quotes | | **`BlockQuoteFeature`** | Yes | Allows you to create block-level quotes |
| **`UploadFeature`** | Yes | Allows you to create block-level upload nodes - this supports all kinds of uploads, not just images | | **`UploadFeature`** | Yes | Allows you to create block-level upload nodes - this supports all kinds of uploads, not just images |
| **`HorizontalRuleFeature`** | Yes | Horizontal rules / separators. Basically displays an `<hr>` element |
| **`BlocksFeature`** | No | Allows you to use Payload's [Blocks Field](/docs/fields/blocks) directly inside your editor. In the feature props, you can specify the allowed blocks - just like in the Blocks field. | | **`BlocksFeature`** | No | Allows you to use Payload's [Blocks Field](/docs/fields/blocks) directly inside your editor. In the feature props, you can specify the allowed blocks - just like in the Blocks field. |
| **`TreeViewFeature`** | No | Adds a debug box under the editor, which allows you to see the current editor state live, the dom, as well as time travel. Very useful for debugging | | **`TreeViewFeature`** | No | Adds a debug box under the editor, which allows you to see the current editor state live, the dom, as well as time travel. Very useful for debugging |

View File

@@ -40,21 +40,22 @@ Every Payload Collection can opt-in to supporting Uploads by specifying the `upl
### Collection Upload Options ### Collection Upload Options
| Option | Description | | Option | Description |
| ------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | ------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`staticURL`** \* | The URL path to use to access your uploads. Relative path like `/media` will be served by payload. Full path like `https://example.com/media` needs to be served by another web server. | | **`staticURL`** \* | The URL path to use to access your uploads. Relative path like `/media` will be served by payload. Full path like `https://example.com/media` needs to be served by another web server. |
| **`staticDir`** \* | The folder directory to use to store media in. Can be either an absolute path or relative to the directory that contains your config. | | **`staticDir`** \* | The folder directory to use to store media in. Can be either an absolute path or relative to the directory that contains your config. |
| **`adminThumbnail`** | Set the way that the Admin panel will display thumbnails for this Collection. [More](#admin-thumbnails) | | **`adminThumbnail`** | Set the way that the Admin panel will display thumbnails for this Collection. [More](#admin-thumbnails) |
| **`crop`** | Set to `false` to disable the cropping tool in the Admin panel. Crop is enabled by default. [More](#crop-and-focal-point-selector) | | **`crop`** | Set to `false` to disable the cropping tool in the Admin panel. Crop is enabled by default. [More](#crop-and-focal-point-selector) |
| **`disableLocalStorage`** | Completely disable uploading files to disk locally. [More](#disabling-local-upload-storage) | | **`disableLocalStorage`** | Completely disable uploading files to disk locally. [More](#disabling-local-upload-storage) |
| **`focalPoint`** | Set to `false` to disable the focal point selection tool in the Admin panel. The focal point selector is only available when `imageSizes` or `resizeOptions` are defined. [More](#crop-and-focal-point-selector) | | **`externalFileHeaderFilter`** | Accepts existing headers and can filter/modify them. |
| **`formatOptions`** | An object with `format` and `options` that are used with the Sharp image library to format the upload file. [More](https://sharp.pixelplumbing.com/api-output#toformat) | | **`focalPoint`** | Set to `false` to disable the focal point selection tool in the Admin panel. The focal point selector is only available when `imageSizes` or `resizeOptions` are defined. [More](#crop-and-focal-point-selector) |
| **`handlers`** | Array of Express request handlers to execute before the built-in Payload static middleware executes. | | **`formatOptions`** | An object with `format` and `options` that are used with the Sharp image library to format the upload file. [More](https://sharp.pixelplumbing.com/api-output#toformat) |
| **`imageSizes`** | If specified, image uploads will be automatically resized in accordance to these image sizes. [More](#image-sizes) | | **`handlers`** | Array of Express request handlers to execute before the built-in Payload static middleware executes. |
| **`mimeTypes`** | Restrict mimeTypes in the file picker. Array of valid mimetypes or mimetype wildcards [More](#mimetypes) | | **`imageSizes`** | If specified, image uploads will be automatically resized in accordance to these image sizes. [More](#image-sizes) |
| **`staticOptions`** | Set options for `express.static` to use while serving your static files. [More](http://expressjs.com/en/resources/middleware/serve-static.html) | | **`mimeTypes`** | Restrict mimeTypes in the file picker. Array of valid mimetypes or mimetype wildcards [More](#mimetypes) |
| **`resizeOptions`** | An object passed to the the Sharp image library to resize the uploaded file. [More](https://sharp.pixelplumbing.com/api-resize) | | **`staticOptions`** | Set options for `express.static` to use while serving your static files. [More](http://expressjs.com/en/resources/middleware/serve-static.html) |
| **`filesRequiredOnCreate`** | Mandate file data on creation, default is true. | | **`resizeOptions`** | An object passed to the the Sharp image library to resize the uploaded file. [More](https://sharp.pixelplumbing.com/api-resize) |
| **`filesRequiredOnCreate`** | Mandate file data on creation, default is true. |
_An asterisk denotes that a property above is required._ _An asterisk denotes that a property above is required._

View File

@@ -128,6 +128,7 @@
] ]
}, },
"dependencies": { "dependencies": {
"@sentry/react": "^7.77.0" "@sentry/react": "^7.77.0",
"ajv": "^8.12.0"
} }
} }

View File

@@ -1,6 +1,6 @@
{ {
"name": "@payloadcms/db-mongodb", "name": "@payloadcms/db-mongodb",
"version": "1.4.4", "version": "1.5.0",
"description": "The officially supported MongoDB database adapter for Payload", "description": "The officially supported MongoDB database adapter for Payload",
"repository": { "repository": {
"type": "git", "type": "git",

View File

@@ -6,11 +6,7 @@ import type { SanitizedCollectionConfig } from 'payload/types'
import mongoose from 'mongoose' import mongoose from 'mongoose'
import mongooseAggregatePaginate from 'mongoose-aggregate-paginate-v2' import mongooseAggregatePaginate from 'mongoose-aggregate-paginate-v2'
import paginate from 'mongoose-paginate-v2' import paginate from 'mongoose-paginate-v2'
import { import { buildVersionCollectionFields, buildVersionGlobalFields } from 'payload/versions'
buildVersionCollectionFields,
buildVersionGlobalFields,
getVersionsModelName,
} from 'payload/versions'
import type { MongooseAdapter } from '.' import type { MongooseAdapter } from '.'
import type { CollectionModel } from './types' import type { CollectionModel } from './types'
@@ -33,6 +29,7 @@ export const init: Init = async function init(this: MongooseAdapter) {
const versionSchema = buildSchema(this.payload.config, versionCollectionFields, { const versionSchema = buildSchema(this.payload.config, versionCollectionFields, {
disableUnique: true, disableUnique: true,
draftsEnabled: true, draftsEnabled: true,
indexSortableFields: this.payload.config.indexSortableFields,
options: { options: {
minimize: false, minimize: false,
timestamps: false, timestamps: false,

View File

@@ -142,7 +142,10 @@ export const sanitizeQueryValue = ({
if (path !== '_id' || (path === '_id' && hasCustomID && field.type === 'text')) { if (path !== '_id' || (path === '_id' && hasCustomID && field.type === 'text')) {
if (operator === 'contains') { if (operator === 'contains') {
formattedValue = { $options: 'i', $regex: formattedValue } formattedValue = {
$options: 'i',
$regex: formattedValue.replace(/[\\^$*+?.()|[\]{}]/g, '\\$&'),
}
} }
} }

View File

@@ -6,6 +6,10 @@ export const commitTransaction: CommitTransaction = async function commitTransac
} }
await this.sessions[id].commitTransaction() await this.sessions[id].commitTransaction()
await this.sessions[id].endSession() try {
await this.sessions[id].endSession()
} catch (error) {
// ending sessions is only best effort and won't impact anything if it fails since the transaction was committed
}
delete this.sessions[id] delete this.sessions[id]
} }

View File

@@ -1,6 +1,6 @@
{ {
"name": "@payloadcms/db-postgres", "name": "@payloadcms/db-postgres",
"version": "0.7.1", "version": "0.8.0",
"description": "The officially supported Postgres database adapter for Payload", "description": "The officially supported Postgres database adapter for Payload",
"repository": { "repository": {
"type": "git", "type": "git",

View File

@@ -120,7 +120,7 @@ export const findMany = async function find({
const findPromise = db.query[tableName].findMany(findManyArgs) const findPromise = db.query[tableName].findMany(findManyArgs)
if (pagination !== false && (orderedIDs ? orderedIDs?.length >= limit : true)) { if (pagination !== false && (orderedIDs ? orderedIDs?.length <= limit : true)) {
const selectCountMethods: ChainedMethods = [] const selectCountMethods: ChainedMethods = []
joinAliases.forEach(({ condition, table }) => { joinAliases.forEach(({ condition, table }) => {

View File

@@ -225,6 +225,85 @@ export const getTableColumnFromPath = ({
}) })
} }
case 'select': {
if (field.hasMany) {
newTableName = getTableName({
adapter,
config: field,
parentTableName: `${tableName}_${tableNameSuffix}`,
prefix: `${tableName}_${tableNameSuffix}`,
})
if (locale && field.localized && adapter.payload.config.localization) {
joins[newTableName] = and(
eq(adapter.tables[tableName].id, adapter.tables[newTableName].parent),
eq(adapter.tables[newTableName]._locale, locale),
)
if (locale !== 'all') {
constraints.push({
columnName: '_locale',
table: adapter.tables[newTableName],
value: locale,
})
}
} else {
joins[newTableName] = eq(
adapter.tables[tableName].id,
adapter.tables[newTableName].parent,
)
}
return {
columnName: 'value',
constraints,
field,
table: adapter.tables[newTableName],
}
}
break
}
case 'text':
case 'number': {
if (field.hasMany) {
let tableType = 'texts'
let columnName = 'text'
if (field.type === 'number') {
tableType = 'numbers'
columnName = 'number'
}
newTableName = `${tableName}_${tableType}`
const joinConstraints = [
eq(adapter.tables[tableName].id, adapter.tables[newTableName].parent),
eq(adapter.tables[newTableName].path, `${constraintPath}${field.name}`),
]
if (locale && field.localized && adapter.payload.config.localization) {
joins[newTableName] = and(
...joinConstraints,
eq(adapter.tables[newTableName]._locale, locale),
)
if (locale !== 'all') {
constraints.push({
columnName: 'locale',
table: adapter.tables[newTableName],
value: locale,
})
}
} else {
joins[newTableName] = and(...joinConstraints)
}
return {
columnName,
constraints,
field,
table: adapter.tables[newTableName],
}
}
break
}
case 'array': { case 'array': {
newTableName = getTableName({ newTableName = getTableName({
adapter, adapter,
@@ -484,43 +563,41 @@ export const getTableColumnFromPath = ({
value, value,
}) })
} }
}
default: { if (fieldAffectsData(field)) {
if (fieldAffectsData(field)) { if (field.localized && adapter.payload.config.localization) {
if (field.localized && adapter.payload.config.localization) { // If localized, we go to localized table and set aliasTable to undefined
// If localized, we go to localized table and set aliasTable to undefined // so it is not picked up below to be used as targetTable
// so it is not picked up below to be used as targetTable newTableName = `${tableName}${adapter.localesSuffix}`
newTableName = `${tableName}${adapter.localesSuffix}`
const parentTable = aliasTable || adapter.tables[tableName] const parentTable = aliasTable || adapter.tables[tableName]
joins[newTableName] = eq(parentTable.id, adapter.tables[newTableName]._parentID) joins[newTableName] = eq(parentTable.id, adapter.tables[newTableName]._parentID)
aliasTable = undefined aliasTable = undefined
if (locale !== 'all') { if (locale !== 'all') {
constraints.push({ constraints.push({
columnName: '_locale', columnName: '_locale',
table: adapter.tables[newTableName], table: adapter.tables[newTableName],
value: locale, value: locale,
}) })
}
}
const targetTable = aliasTable || adapter.tables[newTableName]
selectFields[`${newTableName}.${columnPrefix}${field.name}`] =
targetTable[`${columnPrefix}${field.name}`]
return {
columnName: `${columnPrefix}${field.name}`,
constraints,
field,
pathSegments,
table: targetTable,
}
} }
} }
const targetTable = aliasTable || adapter.tables[newTableName]
selectFields[`${newTableName}.${columnPrefix}${field.name}`] =
targetTable[`${columnPrefix}${field.name}`]
return {
columnName: `${columnPrefix}${field.name}`,
constraints,
field,
pathSegments,
table: targetTable,
}
} }
} }

View File

@@ -85,6 +85,10 @@ export const sanitizeQueryValue = ({
} }
} }
if ('hasMany' in field && field.hasMany && operator === 'contains') {
operator = 'equals'
}
if (operator === 'near' || operator === 'within' || operator === 'intersects') { if (operator === 'near' || operator === 'within' || operator === 'intersects') {
throw new APIError( throw new APIError(
`Querying with '${operator}' is not supported with the postgres database adapter.`, `Querying with '${operator}' is not supported with the postgres database adapter.`,

View File

@@ -493,6 +493,7 @@ export const traverseFields = ({
localized: field.localized, localized: field.localized,
rootTableName, rootTableName,
table: adapter.tables[blockTableName], table: adapter.tables[blockTableName],
tableLocales: adapter.tables[`${blockTableName}${adapter.localesSuffix}`],
}) })
} }
adapter.blockTableNames[`${rootTableName}.${toSnakeCase(block.slug)}`] = blockTableName adapter.blockTableNames[`${rootTableName}.${toSnakeCase(block.slug)}`] = blockTableName

View File

@@ -10,9 +10,13 @@ type Args = {
localized: boolean localized: boolean
rootTableName: string rootTableName: string
table: GenericTable table: GenericTable
tableLocales?: GenericTable
} }
const getFlattenedFieldNames = (fields: Field[], prefix: string = ''): string[] => { const getFlattenedFieldNames = (
fields: Field[],
prefix: string = '',
): { localized?: boolean; name: string }[] => {
return fields.reduce((fieldsToUse, field) => { return fields.reduce((fieldsToUse, field) => {
let fieldPrefix = prefix let fieldPrefix = prefix
@@ -44,7 +48,13 @@ const getFlattenedFieldNames = (fields: Field[], prefix: string = ''): string[]
} }
if (fieldAffectsData(field)) { if (fieldAffectsData(field)) {
return [...fieldsToUse, `${fieldPrefix?.replace('.', '_') || ''}${field.name}`] return [
...fieldsToUse,
{
name: `${fieldPrefix?.replace('.', '_') || ''}${field.name}`,
localized: field.localized,
},
]
} }
return fieldsToUse return fieldsToUse
@@ -56,22 +66,25 @@ export const validateExistingBlockIsIdentical = ({
localized, localized,
rootTableName, rootTableName,
table, table,
tableLocales,
}: Args): void => { }: Args): void => {
const fieldNames = getFlattenedFieldNames(block.fields) const fieldNames = getFlattenedFieldNames(block.fields)
const missingField = const missingField =
// ensure every field from the config is in the matching table // ensure every field from the config is in the matching table
fieldNames.find((name) => Object.keys(table).indexOf(name) === -1) || fieldNames.find(({ name, localized }) => {
const fieldTable = localized && tableLocales ? tableLocales : table
return Object.keys(fieldTable).indexOf(name) === -1
}) ||
// ensure every table column is matched for every field from the config // ensure every table column is matched for every field from the config
Object.keys(table).find((fieldName) => { Object.keys(table).find((fieldName) => {
if (!['_locale', '_order', '_parentID', '_path', '_uuid'].includes(fieldName)) { if (!['_locale', '_order', '_parentID', '_path', '_uuid'].includes(fieldName)) {
return fieldNames.indexOf(fieldName) === -1 return fieldNames.findIndex((field) => field.name) === -1
} }
}) })
if (missingField) { if (missingField) {
throw new InvalidConfiguration( throw new InvalidConfiguration(
`The table ${rootTableName} has multiple blocks with slug ${block.slug}, but the schemas do not match. One block includes the field ${missingField}, while the other block does not.`, `The table ${rootTableName} has multiple blocks with slug ${block.slug}, but the schemas do not match. One block includes the field ${typeof missingField === 'string' ? missingField : missingField.name}, while the other block does not.`,
) )
} }

View File

@@ -1,6 +1,6 @@
{ {
"name": "payload", "name": "payload",
"version": "2.12.1", "version": "2.13.0",
"description": "Node, React and MongoDB Headless CMS and Application Framework", "description": "Node, React and MongoDB Headless CMS and Application Framework",
"license": "MIT", "license": "MIT",
"main": "./dist/index.js", "main": "./dist/index.js",

View File

@@ -64,7 +64,9 @@ const DeleteDocument: React.FC<Props> = (props) => {
if (res.status < 400) { if (res.status < 400) {
setDeleting(false) setDeleting(false)
toggleModal(modalSlug) toggleModal(modalSlug)
toast.success(json.message || t('titleDeleted', { label: getTranslation(singular, i18n), title })) toast.success(
json.message || t('titleDeleted', { label: getTranslation(singular, i18n), title }),
)
return history.push(`${admin}/collections/${slug}`) return history.push(`${admin}/collections/${slug}`)
} }

View File

@@ -75,7 +75,7 @@ export const DocumentControls: React.FC<{
label: label:
typeof collection?.labels?.singular === 'string' typeof collection?.labels?.singular === 'string'
? collection.labels.singular ? collection.labels.singular
: 'document', : t('document'),
})} })}
</p> </p>
</li> </li>

View File

@@ -83,7 +83,7 @@ const SaveDraft: React.FC<{ action: string; disabled: boolean }> = ({ action, di
) )
} }
const EditMany: React.FC<Props> = (props) => { const EditMany: React.FC<Props> = (props) => {
const { collection: { fields, labels: { plural }, slug } = {}, collection, resetParams } = props const { collection: { slug, fields, labels: { plural } } = {}, collection, resetParams } = props
const { permissions } = useAuth() const { permissions } = useAuth()
const { closeModal } = useModal() const { closeModal } = useModal()
@@ -148,11 +148,11 @@ const EditMany: React.FC<Props> = (props) => {
{collection.versions ? ( {collection.versions ? (
<React.Fragment> <React.Fragment>
<Publish <Publish
action={`${serverURL}${api}/${slug}${getQueryParams()}`} action={`${serverURL}${api}/${slug}${getQueryParams()}&draft=true`}
disabled={selected.length === 0} disabled={selected.length === 0}
/> />
<SaveDraft <SaveDraft
action={`${serverURL}${api}/${slug}${getQueryParams()}`} action={`${serverURL}${api}/${slug}${getQueryParams()}&draft=true`}
disabled={selected.length === 0} disabled={selected.length === 0}
/> />
</React.Fragment> </React.Fragment>

View File

@@ -41,46 +41,53 @@ const CollapsibleField: React.FC<Props> = (props) => {
async (newCollapsedState: boolean) => { async (newCollapsedState: boolean) => {
const existingPreferences: DocumentPreferences = await getPreference(preferencesKey) const existingPreferences: DocumentPreferences = await getPreference(preferencesKey)
setPreference(preferencesKey, { if (preferencesKey) {
...existingPreferences, await setPreference(preferencesKey, {
...(path ...existingPreferences,
? { ...(path
fields: { ? {
...(existingPreferences?.fields || {}), fields: {
[path]: { ...(existingPreferences?.fields || {}),
...existingPreferences?.fields?.[path], [path]: {
collapsed: newCollapsedState, ...existingPreferences?.fields?.[path],
collapsed: newCollapsedState,
},
}, },
}, }
} : {
: { fields: {
fields: { ...(existingPreferences?.fields || {}),
...(existingPreferences?.fields || {}), [fieldPreferencesKey]: {
[fieldPreferencesKey]: { ...existingPreferences?.fields?.[fieldPreferencesKey],
...existingPreferences?.fields?.[fieldPreferencesKey], collapsed: newCollapsedState,
collapsed: newCollapsedState, },
}, },
}, }),
}), })
}) }
}, },
[preferencesKey, fieldPreferencesKey, getPreference, setPreference, path], [preferencesKey, fieldPreferencesKey, getPreference, setPreference, path],
) )
useEffect(() => { useEffect(() => {
const fetchInitialState = async () => { const fetchInitialState = async () => {
const preferences = await getPreference(preferencesKey) if (preferencesKey) {
if (preferences) { const preferences = await getPreference(preferencesKey)
const initCollapsedFromPref = path const specificPreference = path
? preferences?.fields?.[path]?.collapsed ? preferences?.fields?.[path]?.collapsed
: preferences?.fields?.[fieldPreferencesKey]?.collapsed : preferences?.fields?.[fieldPreferencesKey]?.collapsed
setCollapsedOnMount(Boolean(initCollapsedFromPref))
if (specificPreference !== undefined) {
setCollapsedOnMount(Boolean(specificPreference))
} else {
setCollapsedOnMount(typeof initCollapsed === 'boolean' ? initCollapsed : false)
}
} else { } else {
setCollapsedOnMount(typeof initCollapsed === 'boolean' ? initCollapsed : false) setCollapsedOnMount(typeof initCollapsed === 'boolean' ? initCollapsed : false)
} }
} }
fetchInitialState() void fetchInitialState()
}, [getPreference, preferencesKey, fieldPreferencesKey, initCollapsed, path]) }, [getPreference, preferencesKey, fieldPreferencesKey, initCollapsed, path])
if (typeof collapsedOnMount !== 'boolean') return null if (typeof collapsedOnMount !== 'boolean') return null

View File

@@ -27,6 +27,7 @@ const JSONField: React.FC<Props> = (props) => {
style, style,
width, width,
} = {}, } = {},
jsonSchema,
label, label,
path: pathFromProps, path: pathFromProps,
required, required,
@@ -54,6 +55,25 @@ const JSONField: React.FC<Props> = (props) => {
validate: memoizedValidate, validate: memoizedValidate,
}) })
const handleMount = useCallback(
(editor, monaco) => {
if (!jsonSchema) return
const existingSchemas = monaco.languages.json.jsonDefaults.diagnosticsOptions.schemas || []
const modelUri = monaco.Uri.parse(jsonSchema.uri)
const model = monaco.editor.createModel(JSON.stringify(value, null, 2), 'json', modelUri)
monaco.languages.json.jsonDefaults.setDiagnosticsOptions({
enableSchemaRequest: true,
schemas: [...existingSchemas, jsonSchema],
validate: true,
})
editor.setModel(model)
},
[value, jsonSchema],
)
const handleChange = useCallback( const handleChange = useCallback(
(val) => { (val) => {
try { try {
@@ -104,6 +124,7 @@ const JSONField: React.FC<Props> = (props) => {
<CodeEditor <CodeEditor
defaultLanguage="json" defaultLanguage="json"
onChange={handleChange} onChange={handleChange}
onMount={handleMount}
options={editorOptions} options={editorOptions}
readOnly={readOnly} readOnly={readOnly}
value={stringValue} value={stringValue}

View File

@@ -1,4 +1,5 @@
import type { JSONSchema4 } from 'json-schema' import type { JSONSchema4 } from 'json-schema'
import type { SanitizedConfig } from 'payload/config'
import type { PayloadRequest } from '../../../../../express/types' import type { PayloadRequest } from '../../../../../express/types'
import type { RequestContext } from '../../../../../express/types' import type { RequestContext } from '../../../../../express/types'
@@ -30,12 +31,14 @@ type RichTextAdapterBase<
}) => Promise<void> | null }) => Promise<void> | null
outputSchema?: ({ outputSchema?: ({
collectionIDFieldTypes, collectionIDFieldTypes,
config,
field, field,
interfaceNameDefinitions, interfaceNameDefinitions,
isRequired, isRequired,
payload, payload,
}: { }: {
collectionIDFieldTypes: { [key: string]: 'number' | 'string' } collectionIDFieldTypes: { [key: string]: 'number' | 'string' }
config?: SanitizedConfig
field: RichTextField<Value, AdapterProps, ExtraFieldProperties> field: RichTextField<Value, AdapterProps, ExtraFieldProperties>
/** /**
* Allows you to define new top-level interfaces that can be re-used in the output schema. * Allows you to define new top-level interfaces that can be re-used in the output schema.

View File

@@ -88,14 +88,16 @@ const TabsField: React.FC<Props> = (props) => {
const tabsPrefKey = `tabs-${indexPath}` const tabsPrefKey = `tabs-${indexPath}`
useEffect(() => { useEffect(() => {
const getInitialPref = async () => { if (preferencesKey) {
const existingPreferences: DocumentPreferences = await getPreference(preferencesKey) const getInitialPref = async () => {
const initialIndex = path const existingPreferences: DocumentPreferences = await getPreference(preferencesKey)
? existingPreferences?.fields?.[path]?.tabIndex const initialIndex = path
: existingPreferences?.fields?.[tabsPrefKey]?.tabIndex ? existingPreferences?.fields?.[path]?.tabIndex
setActiveTabIndex(initialIndex || 0) : existingPreferences?.fields?.[tabsPrefKey]?.tabIndex
setActiveTabIndex(initialIndex || 0)
}
void getInitialPref()
} }
void getInitialPref()
}, [path, indexPath, getPreference, preferencesKey, tabsPrefKey]) }, [path, indexPath, getPreference, preferencesKey, tabsPrefKey])
const handleTabChange = useCallback( const handleTabChange = useCallback(
@@ -104,28 +106,30 @@ const TabsField: React.FC<Props> = (props) => {
const existingPreferences: DocumentPreferences = await getPreference(preferencesKey) const existingPreferences: DocumentPreferences = await getPreference(preferencesKey)
setPreference(preferencesKey, { if (preferencesKey) {
...existingPreferences, await setPreference(preferencesKey, {
...(path ...existingPreferences,
? { ...(path
fields: { ? {
...(existingPreferences?.fields || {}), fields: {
[path]: { ...(existingPreferences?.fields || {}),
...existingPreferences?.fields?.[path], [path]: {
tabIndex: incomingTabIndex, ...existingPreferences?.fields?.[path],
tabIndex: incomingTabIndex,
},
}, },
}, }
} : {
: { fields: {
fields: { ...existingPreferences?.fields,
...existingPreferences?.fields, [tabsPrefKey]: {
[tabsPrefKey]: { ...existingPreferences?.fields?.[tabsPrefKey],
...existingPreferences?.fields?.[tabsPrefKey], tabIndex: incomingTabIndex,
tabIndex: incomingTabIndex, },
}, },
}, }),
}), })
}) }
}, },
[preferencesKey, getPreference, setPreference, path, tabsPrefKey], [preferencesKey, getPreference, setPreference, path, tabsPrefKey],
) )

View File

@@ -26,7 +26,7 @@ export type DefaultEditViewProps = CollectionEditViewProps & {
} }
const DefaultEditView: React.FC<DefaultEditViewProps> = (props) => { const DefaultEditView: React.FC<DefaultEditViewProps> = (props) => {
const { i18n } = useTranslation('general') const { i18n, t } = useTranslation('general')
const { refreshCookieAsync, user } = useAuth() const { refreshCookieAsync, user } = useAuth()
const { const {
@@ -115,7 +115,7 @@ const DefaultEditView: React.FC<DefaultEditViewProps> = (props) => {
name={`collection-edit--${ name={`collection-edit--${
typeof collection?.labels?.singular === 'string' typeof collection?.labels?.singular === 'string'
? collection.labels.singular ? collection.labels.singular
: 'document' : t('document')
}`} }`}
type="withoutNav" type="withoutNav"
/> />

View File

@@ -8,7 +8,7 @@ import type { PayloadRequest } from '../../express/types'
import type { User } from '../types' import type { User } from '../types'
import { buildAfterOperation } from '../../collections/operations/utils' import { buildAfterOperation } from '../../collections/operations/utils'
import { AuthenticationError, LockedAuth } from '../../errors' import { AuthenticationError, LockedAuth, ValidationError } from '../../errors'
import { afterRead } from '../../fields/hooks/afterRead' import { afterRead } from '../../fields/hooks/afterRead'
import { commitTransaction } from '../../utilities/commitTransaction' import { commitTransaction } from '../../utilities/commitTransaction'
import getCookieExpiration from '../../utilities/getCookieExpiration' import getCookieExpiration from '../../utilities/getCookieExpiration'
@@ -86,6 +86,13 @@ async function login<TSlug extends keyof GeneratedTypes['collections']>(
const { email: unsanitizedEmail, password } = data const { email: unsanitizedEmail, password } = data
if (typeof unsanitizedEmail !== 'string' || unsanitizedEmail.trim() === '') {
throw new ValidationError([{ field: 'email', message: req.i18n.t('validation:required') }])
}
if (typeof password !== 'string' || password.trim() === '') {
throw new ValidationError([{ field: 'password', message: req.i18n.t('validation:required') }])
}
const email = unsanitizedEmail ? unsanitizedEmail.toLowerCase().trim() : null const email = unsanitizedEmail ? unsanitizedEmail.toLowerCase().trim() : null
let user = await payload.db.findOne<any>({ let user = await payload.db.findOne<any>({

View File

@@ -169,6 +169,7 @@ const collectionSchema = joi.object().keys({
adminThumbnail: joi.alternatives().try(joi.string(), joi.func()), adminThumbnail: joi.alternatives().try(joi.string(), joi.func()),
crop: joi.bool(), crop: joi.bool(),
disableLocalStorage: joi.bool(), disableLocalStorage: joi.bool(),
externalFileHeaderFilter: joi.func(),
filesRequiredOnCreate: joi.bool(), filesRequiredOnCreate: joi.bool(),
focalPoint: joi.bool(), focalPoint: joi.bool(),
formatOptions: joi.object().keys({ formatOptions: joi.object().keys({

View File

@@ -341,7 +341,8 @@ export type CollectionAdminOptions = {
useAsTitle?: string useAsTitle?: string
} }
export type BaseCollectionConfig = { /** Manage all aspects of a data collection */
export type CollectionConfig = {
/** /**
* Access control * Access control
*/ */
@@ -448,9 +449,6 @@ export type BaseCollectionConfig = {
versions?: IncomingCollectionVersions | boolean versions?: IncomingCollectionVersions | boolean
} }
/** Manage all aspects of a data collection */
export type CollectionConfig = BaseCollectionConfig
export interface SanitizedCollectionConfig export interface SanitizedCollectionConfig
extends Omit< extends Omit<
DeepRequired<CollectionConfig>, DeepRequired<CollectionConfig>,

View File

@@ -268,7 +268,8 @@ async function update<TSlug extends keyof GeneratedTypes['collections']>(
global: null, global: null,
operation: 'update', operation: 'update',
req, req,
skipValidation: shouldSaveDraft || data._status === 'draft', skipValidation:
Boolean(collectionConfig.versions?.drafts) && data._status !== 'published',
}) })
// ///////////////////////////////////// // /////////////////////////////////////
@@ -297,7 +298,6 @@ async function update<TSlug extends keyof GeneratedTypes['collections']>(
...result, ...result,
createdAt: doc.createdAt, createdAt: doc.createdAt,
}, },
draft: shouldSaveDraft,
payload, payload,
req, req,
}) })

View File

@@ -241,7 +241,7 @@ async function updateByID<TSlug extends keyof GeneratedTypes['collections']>(
global: null, global: null,
operation: 'update', operation: 'update',
req, req,
skipValidation: shouldSaveDraft || data._status === 'draft', skipValidation: Boolean(collectionConfig.versions?.drafts) && data._status !== 'published',
}) })
// ///////////////////////////////////// // /////////////////////////////////////

View File

@@ -6,6 +6,7 @@ import type { PayloadRequest } from '../../express/types'
import type { Document } from '../../types' import type { Document } from '../../types'
import { NotFound } from '../../errors' import { NotFound } from '../../errors'
import { sanitizeCollectionID } from '../../utilities/sanitizeCollectionID'
import deleteByID from '../operations/deleteByID' import deleteByID from '../operations/deleteByID'
export type DeleteResult = { export type DeleteResult = {
@@ -18,9 +19,15 @@ export default async function deleteByIDHandler(
res: Response, res: Response,
next: NextFunction, next: NextFunction,
): Promise<Response<DeleteResult> | void> { ): Promise<Response<DeleteResult> | void> {
const id = sanitizeCollectionID({
id: req.params.id,
collectionSlug: req.collection.config.slug,
payload: req.payload,
})
try { try {
const doc = await deleteByID({ const doc = await deleteByID({
id: req.params.id, id,
collection: req.collection, collection: req.collection,
depth: parseInt(String(req.query.depth), 10), depth: parseInt(String(req.query.depth), 10),
req, req,

View File

@@ -3,6 +3,7 @@ import type { NextFunction, Response } from 'express'
import type { PayloadRequest } from '../../express/types' import type { PayloadRequest } from '../../express/types'
import type { Document } from '../../types' import type { Document } from '../../types'
import { sanitizeCollectionID } from '../../utilities/sanitizeCollectionID'
import findByID from '../operations/findByID' import findByID from '../operations/findByID'
export type FindByIDResult = { export type FindByIDResult = {
@@ -15,9 +16,15 @@ export default async function findByIDHandler(
res: Response, res: Response,
next: NextFunction, next: NextFunction,
): Promise<Response<FindByIDResult> | void> { ): Promise<Response<FindByIDResult> | void> {
const id = sanitizeCollectionID({
id: req.params.id,
collectionSlug: req.collection.config.slug,
payload: req.payload,
})
try { try {
const doc = await findByID({ const doc = await findByID({
id: req.params.id, id,
collection: req.collection, collection: req.collection,
depth: Number(req.query.depth), depth: Number(req.query.depth),
draft: req.query.draft === 'true', draft: req.query.draft === 'true',

View File

@@ -3,6 +3,7 @@ import type { NextFunction, Response } from 'express'
import type { PayloadRequest } from '../../express/types' import type { PayloadRequest } from '../../express/types'
import type { Document } from '../../types' import type { Document } from '../../types'
import { sanitizeCollectionID } from '../../utilities/sanitizeCollectionID'
import findVersionByID from '../operations/findVersionByID' import findVersionByID from '../operations/findVersionByID'
export type FindByIDResult = { export type FindByIDResult = {
@@ -15,8 +16,14 @@ export default async function findVersionByIDHandler(
res: Response, res: Response,
next: NextFunction, next: NextFunction,
): Promise<Response<FindByIDResult> | void> { ): Promise<Response<FindByIDResult> | void> {
const options = { const id = sanitizeCollectionID({
id: req.params.id, id: req.params.id,
collectionSlug: req.collection.config.slug,
payload: req.payload,
})
const options = {
id,
collection: req.collection, collection: req.collection,
depth: parseInt(String(req.query.depth), 10), depth: parseInt(String(req.query.depth), 10),
payload: req.payload, payload: req.payload,

View File

@@ -6,6 +6,7 @@ import type { PayloadRequest } from '../../express/types'
import type { Document } from '../../types' import type { Document } from '../../types'
import formatSuccessResponse from '../../express/responses/formatSuccess' import formatSuccessResponse from '../../express/responses/formatSuccess'
import { sanitizeCollectionID } from '../../utilities/sanitizeCollectionID'
import restoreVersion from '../operations/restoreVersion' import restoreVersion from '../operations/restoreVersion'
export type RestoreResult = { export type RestoreResult = {
@@ -18,8 +19,14 @@ export default async function restoreVersionHandler(
res: Response, res: Response,
next: NextFunction, next: NextFunction,
): Promise<Response<RestoreResult> | void> { ): Promise<Response<RestoreResult> | void> {
const options = { const id = sanitizeCollectionID({
id: req.params.id, id: req.params.id,
collectionSlug: req.collection.config.slug,
payload: req.payload,
})
const options = {
id,
collection: req.collection, collection: req.collection,
depth: Number(req.query.depth), depth: Number(req.query.depth),
payload: req.payload, payload: req.payload,

View File

@@ -5,6 +5,7 @@ import httpStatus from 'http-status'
import type { PayloadRequest } from '../../express/types' import type { PayloadRequest } from '../../express/types'
import formatSuccessResponse from '../../express/responses/formatSuccess' import formatSuccessResponse from '../../express/responses/formatSuccess'
import { sanitizeCollectionID } from '../../utilities/sanitizeCollectionID'
import updateByID from '../operations/updateByID' import updateByID from '../operations/updateByID'
export type UpdateResult = { export type UpdateResult = {
@@ -29,12 +30,18 @@ export default async function updateByIDHandler(
res: Response, res: Response,
next: NextFunction, next: NextFunction,
): Promise<Response<UpdateResult> | void> { ): Promise<Response<UpdateResult> | void> {
const id = sanitizeCollectionID({
id: req.params.id,
collectionSlug: req.collection.config.slug,
payload: req.payload,
})
try { try {
const draft = req.query.draft === 'true' const draft = req.query.draft === 'true'
const autosave = req.query.autosave === 'true' const autosave = req.query.autosave === 'true'
const doc = await updateByID({ const doc = await updateByID({
id: req.params.id, id,
autosave, autosave,
collection: req.collection, collection: req.collection,
data: req.body, data: req.body,

View File

@@ -0,0 +1,16 @@
import type { Field } from '../fields/config/types'
import { fieldAffectsData } from '../fields/config/types'
import APIError from './APIError'
class MissingEditorProp extends APIError {
constructor(field: Field) {
super(
`RichText field${
fieldAffectsData(field) ? ` "${field.name}"` : ''
} is missing the editor prop`,
)
}
}
export default MissingEditorProp

View File

@@ -68,6 +68,7 @@ export type {
FieldWithMany, FieldWithMany,
FieldWithMaxDepth, FieldWithMaxDepth,
FieldWithPath, FieldWithPath,
FieldWithRichTextRequiredEditor,
FieldWithSubFields, FieldWithSubFields,
FilterOptions, FilterOptions,
FilterOptionsProps, FilterOptionsProps,
@@ -86,6 +87,7 @@ export type {
RelationshipField, RelationshipField,
RelationshipValue, RelationshipValue,
RichTextField, RichTextField,
RichTextFieldRequiredEditor,
RowAdmin, RowAdmin,
RowField, RowField,
SelectField, SelectField,

View File

@@ -8,6 +8,7 @@ import {
InvalidFieldRelationship, InvalidFieldRelationship,
MissingFieldType, MissingFieldType,
} from '../../errors' } from '../../errors'
import MissingEditorProp from '../../errors/MissingEditorProps'
import { formatLabels, toWords } from '../../utilities/formatLabels' import { formatLabels, toWords } from '../../utilities/formatLabels'
import { baseBlockFields } from '../baseFields/baseBlockFields' import { baseBlockFields } from '../baseFields/baseBlockFields'
import { baseIDField } from '../baseFields/baseIDField' import { baseIDField } from '../baseFields/baseIDField'
@@ -18,6 +19,12 @@ type Args = {
config: Config config: Config
existingFieldNames?: Set<string> existingFieldNames?: Set<string>
fields: Field[] fields: Field[]
/**
* If true, a richText field will require an editor property to be set, as the sanitizeFields function will not add it from the payload config if not present.
*
* @default false
*/
requireFieldLevelRichTextEditor?: boolean
/** /**
* If not null, will validate that upload and relationship fields do not relate to a collection that is not in this array. * If not null, will validate that upload and relationship fields do not relate to a collection that is not in this array.
* This validation will be skipped if validRelationships is null. * This validation will be skipped if validRelationships is null.
@@ -29,6 +36,7 @@ export const sanitizeFields = ({
config, config,
existingFieldNames = new Set(), existingFieldNames = new Set(),
fields, fields,
requireFieldLevelRichTextEditor = false,
validRelationships, validRelationships,
}: Args): Field[] => { }: Args): Field[] => {
if (!fields) return [] if (!fields) return []
@@ -44,8 +52,12 @@ export const sanitizeFields = ({
} }
// Make sure that the richText field has an editor // Make sure that the richText field has an editor
if (field.type === 'richText' && !field.editor && config.editor) { if (field.type === 'richText' && !field.editor) {
field.editor = config.editor if (config.editor && !requireFieldLevelRichTextEditor) {
field.editor = config.editor
} else {
throw new MissingEditorProp(field)
}
} }
// Auto-label // Auto-label
@@ -113,7 +125,7 @@ export const sanitizeFields = ({
if (fieldAffectsData(field)) { if (fieldAffectsData(field)) {
if (existingFieldNames.has(field.name)) { if (existingFieldNames.has(field.name)) {
throw new DuplicateFieldName(field.name) throw new DuplicateFieldName(field.name)
} else if (!['id', 'blockName'].includes(field.name)) { } else if (!['blockName', 'id'].includes(field.name)) {
existingFieldNames.add(field.name) existingFieldNames.add(field.name)
} }
@@ -145,6 +157,7 @@ export const sanitizeFields = ({
config, config,
existingFieldNames: fieldAffectsData(field) ? new Set() : existingFieldNames, existingFieldNames: fieldAffectsData(field) ? new Set() : existingFieldNames,
fields: field.fields, fields: field.fields,
requireFieldLevelRichTextEditor,
validRelationships, validRelationships,
}) })
} }
@@ -160,6 +173,7 @@ export const sanitizeFields = ({
config, config,
existingFieldNames: tabHasName(tab) ? new Set() : existingFieldNames, existingFieldNames: tabHasName(tab) ? new Set() : existingFieldNames,
fields: tab.fields, fields: tab.fields,
requireFieldLevelRichTextEditor,
validRelationships, validRelationships,
}) })
@@ -176,9 +190,10 @@ export const sanitizeFields = ({
unsanitizedBlock.fields = sanitizeFields({ unsanitizedBlock.fields = sanitizeFields({
config, config,
fields: block.fields,
validRelationships,
existingFieldNames: new Set(), existingFieldNames: new Set(),
fields: block.fields,
requireFieldLevelRichTextEditor,
validRelationships,
}) })
return unsanitizedBlock return unsanitizedBlock

View File

@@ -187,6 +187,7 @@ export const json = baseField.keys({
editorOptions: joi.object().unknown(), // Editor['options'] @monaco-editor/react editorOptions: joi.object().unknown(), // Editor['options'] @monaco-editor/react
}), }),
defaultValue: joi.alternatives().try(joi.array(), joi.object()), defaultValue: joi.alternatives().try(joi.array(), joi.object()),
jsonSchema: joi.object().unknown(),
}) })
export const select = baseField.keys({ export const select = baseField.keys({

View File

@@ -436,6 +436,7 @@ type JSONAdmin = Admin & {
export type JSONField = Omit<FieldBase, 'admin'> & { export type JSONField = Omit<FieldBase, 'admin'> & {
admin?: JSONAdmin admin?: JSONAdmin
jsonSchema?: Record<string, unknown>
type: 'json' type: 'json'
} }
@@ -549,6 +550,14 @@ export type RichTextField<
type: 'richText' type: 'richText'
} & ExtraProperties } & ExtraProperties
export type RichTextFieldRequiredEditor<
Value extends object = any,
AdapterProps = any,
ExtraProperties = object,
> = Omit<RichTextField<Value, AdapterProps, ExtraProperties>, 'editor'> & {
editor: RichTextAdapter<Value, AdapterProps, ExtraProperties>
}
export type ArrayField = FieldBase & { export type ArrayField = FieldBase & {
admin?: Admin & { admin?: Admin & {
components?: { components?: {
@@ -597,6 +606,10 @@ export type RadioField = FieldBase & {
export type Block = { export type Block = {
/** Extension point to add your custom data. */ /** Extension point to add your custom data. */
custom?: Record<string, any> custom?: Record<string, any>
/**
* Customize the SQL table name
*/
dbName?: DBIdentifierName
fields: Field[] fields: Field[]
/** @deprecated - please migrate to the interfaceName property instead. */ /** @deprecated - please migrate to the interfaceName property instead. */
graphQL?: { graphQL?: {
@@ -654,6 +667,10 @@ export type Field =
| UIField | UIField
| UploadField | UploadField
export type FieldWithRichTextRequiredEditor =
| Exclude<Field, RichTextField>
| RichTextFieldRequiredEditor
export type FieldAffectingData = export type FieldAffectingData =
| ArrayField | ArrayField
| BlockField | BlockField

View File

@@ -1,3 +1,4 @@
import Ajv from 'ajv'
import type { RichTextAdapter } from '../exports/types' import type { RichTextAdapter } from '../exports/types'
import type { import type {
ArrayField, ArrayField,
@@ -130,9 +131,9 @@ export const code: Validate<unknown, unknown, CodeField> = (value: string, { req
return true return true
} }
export const json: Validate<unknown, unknown, JSONField & { jsonError?: string }> = ( export const json: Validate<unknown, unknown, JSONField & { jsonError?: string }> = async (
value: string, value: string,
{ jsonError, required, t }, { jsonError, jsonSchema, required, t },
) => { ) => {
if (required && !value) { if (required && !value) {
return t('validation:required') return t('validation:required')
@@ -142,6 +143,55 @@ export const json: Validate<unknown, unknown, JSONField & { jsonError?: string }
return t('validation:invalidInput') return t('validation:invalidInput')
} }
const isNotEmpty = (value) => {
if (value === undefined || value === null) {
return false
}
if (Array.isArray(value) && value.length === 0) {
return false
}
if (typeof value === 'object' && Object.keys(value).length === 0) {
return false
}
return true
}
const fetchSchema = ({ uri, schema }) => {
if (uri && schema) return schema
return fetch(uri)
.then((response) => {
if (!response.ok) {
throw new Error('Network response was not ok')
}
return response.json()
})
.then((json) => {
const jsonSchemaSanitizations = {
id: undefined,
$id: json.id,
$schema: 'http://json-schema.org/draft-07/schema#',
}
return Object.assign(json, jsonSchemaSanitizations)
})
}
if (!canUseDOM && jsonSchema && isNotEmpty(value)) {
try {
jsonSchema.schema = await fetchSchema(jsonSchema)
const { schema } = jsonSchema
const ajv = new Ajv()
if (!ajv.validate(schema, value)) {
return t(ajv.errorsText())
}
} catch (error) {
return t(error.message)
}
}
return true return true
} }

View File

@@ -69,6 +69,8 @@
"invalidFileType": "نوع ملف غير صالح", "invalidFileType": "نوع ملف غير صالح",
"invalidFileTypeValue": "نوع ملف غير صالح: {{value}}", "invalidFileTypeValue": "نوع ملف غير صالح: {{value}}",
"loadingDocument": "حدثت مشكلة أثناء تحميل المستند برقم التعريف {{id}}.", "loadingDocument": "حدثت مشكلة أثناء تحميل المستند برقم التعريف {{id}}.",
"localesNotSaved_one": "لم يتم حفظ اللغة التالية:",
"localesNotSaved_other": "لم يتم حفظ اللغات التالية:",
"missingEmail": "البريد الإلكتروني مفقود.", "missingEmail": "البريد الإلكتروني مفقود.",
"missingIDOfDocument": "معرّف المستند المراد تحديثه مفقود.", "missingIDOfDocument": "معرّف المستند المراد تحديثه مفقود.",
"missingIDOfVersion": "معرّف النسخة مفقود.", "missingIDOfVersion": "معرّف النسخة مفقود.",
@@ -177,6 +179,8 @@
"deleting": "يتمّ الحذف...", "deleting": "يتمّ الحذف...",
"descending": "تنازلي", "descending": "تنازلي",
"deselectAllRows": "إلغاء تحديد جميع الصفوف", "deselectAllRows": "إلغاء تحديد جميع الصفوف",
"document": "وثيقة",
"documents": "وثائق",
"duplicate": "استنساخ", "duplicate": "استنساخ",
"duplicateWithoutSaving": "استنساخ بدون حفظ التغييرات", "duplicateWithoutSaving": "استنساخ بدون حفظ التغييرات",
"edit": "تعديل", "edit": "تعديل",
@@ -369,4 +373,4 @@
"viewingVersions": "يتمّ استعراض النُّسَخ ل {{entityLabel}} {{documentTitle}}", "viewingVersions": "يتمّ استعراض النُّسَخ ل {{entityLabel}} {{documentTitle}}",
"viewingVersionsGlobal": "يتمّ استعراض النُّسَخ للاعداد العامّ {{entityLabel}}" "viewingVersionsGlobal": "يتمّ استعراض النُّسَخ للاعداد العامّ {{entityLabel}}"
} }
} }

View File

@@ -63,13 +63,14 @@
"deletingFile": "Faylın silinməsində xəta baş verdi.", "deletingFile": "Faylın silinməsində xəta baş verdi.",
"deletingTitle": "{{title}} silinərkən xəta baş verdi. Zəhmət olmasa, bağlantınızı yoxlayın və yenidən cəhd edin.", "deletingTitle": "{{title}} silinərkən xəta baş verdi. Zəhmət olmasa, bağlantınızı yoxlayın və yenidən cəhd edin.",
"emailOrPasswordIncorrect": "Təqdim olunan e-poçt və ya şifrə yanlışdır.", "emailOrPasswordIncorrect": "Təqdim olunan e-poçt və ya şifrə yanlışdır.",
"followingFieldsInvalid_many": "Aşağıdakı sahələr yanlışdır:",
"followingFieldsInvalid_one": "Aşağıdakı sahə yanlışdır:", "followingFieldsInvalid_one": "Aşağıdakı sahə yanlışdır:",
"followingFieldsInvalid_other": "Aşağıdaki sahələr yanlışdır:", "followingFieldsInvalid_other": "Aşağıdaki sahələr yanlışdır:",
"incorrectCollection": "Yanlış Kolleksiya", "incorrectCollection": "Yanlış Kolleksiya",
"invalidFileType": "Yanlış fayl növü", "invalidFileType": "Yanlış fayl növü",
"invalidFileTypeValue": "Yanlış fayl növü: {{value}}", "invalidFileTypeValue": "Yanlış fayl növü: {{value}}",
"loadingDocument": "{{id}} ID-li sənədin yüklənməsində problem baş verdi.", "loadingDocument": "{{id}} ID-li sənədin yüklənməsində problem baş verdi.",
"localesNotSaved_one": "Aşağıdakı yerləşdirmə saxlanıla bilmədi:",
"localesNotSaved_other": "Aşağıdakı yerləşdirmələr saxlanıla bilmədi:",
"missingEmail": "E-poçt adresi çatışmır.", "missingEmail": "E-poçt adresi çatışmır.",
"missingIDOfDocument": "Yeniləmək üçün sənədin ID-si çatışmır.", "missingIDOfDocument": "Yeniləmək üçün sənədin ID-si çatışmır.",
"missingIDOfVersion": "Versiyanın ID-si çatışmır.", "missingIDOfVersion": "Versiyanın ID-si çatışmır.",
@@ -178,6 +179,8 @@
"deleting": "Silinir...", "deleting": "Silinir...",
"descending": "Azalan", "descending": "Azalan",
"deselectAllRows": "Bütün sıraları seçimi ləğv edin", "deselectAllRows": "Bütün sıraları seçimi ləğv edin",
"document": "Sənəd",
"documents": "Sənədlər",
"duplicate": "Dublikat", "duplicate": "Dublikat",
"duplicateWithoutSaving": "Dəyişiklikləri saxlamadan dublikatla", "duplicateWithoutSaving": "Dəyişiklikləri saxlamadan dublikatla",
"edit": "Redaktə et", "edit": "Redaktə et",
@@ -370,4 +373,4 @@
"viewingVersions": "{{entityLabel}} {{documentTitle}} üçün versiyaları göstərir", "viewingVersions": "{{entityLabel}} {{documentTitle}} üçün versiyaları göstərir",
"viewingVersionsGlobal": "Qlobal {{entityLabel}} üçün versiyaları göstərir" "viewingVersionsGlobal": "Qlobal {{entityLabel}} üçün versiyaları göstərir"
} }
} }

View File

@@ -69,6 +69,8 @@
"invalidFileType": "Невалиден тип на файл", "invalidFileType": "Невалиден тип на файл",
"invalidFileTypeValue": "Невалиден тип на файл: {{value}}", "invalidFileTypeValue": "Невалиден тип на файл: {{value}}",
"loadingDocument": "Имаше проблем при зареждането на документа с идентификатор {{id}}.", "loadingDocument": "Имаше проблем при зареждането на документа с идентификатор {{id}}.",
"localesNotSaved_one": "Следният локал не може да бъде запазен:",
"localesNotSaved_other": "Следните локали не може да бъдат запазени:",
"missingEmail": "Липсващ имейл.", "missingEmail": "Липсващ имейл.",
"missingIDOfDocument": "Липсващ идентификатор на документа за обновяване.", "missingIDOfDocument": "Липсващ идентификатор на документа за обновяване.",
"missingIDOfVersion": "Липсващ идентификатор на версия.", "missingIDOfVersion": "Липсващ идентификатор на версия.",
@@ -177,6 +179,8 @@
"deleting": "Изтриване...", "deleting": "Изтриване...",
"descending": "Низходящо", "descending": "Низходящо",
"deselectAllRows": "Деселектирай всички редове", "deselectAllRows": "Деселектирай всички редове",
"document": "Документ",
"documents": "Документи",
"duplicate": "Дупликирай", "duplicate": "Дупликирай",
"duplicateWithoutSaving": "Дупликирай без да запазваш промените", "duplicateWithoutSaving": "Дупликирай без да запазваш промените",
"edit": "Редактирай", "edit": "Редактирай",
@@ -369,4 +373,4 @@
"viewingVersions": "Гледане на версии за {{entityLabel}} {{documentTitle}}", "viewingVersions": "Гледане на версии за {{entityLabel}} {{documentTitle}}",
"viewingVersionsGlobal": "Гледане на версии за глобалния документ {{entityLabel}}" "viewingVersionsGlobal": "Гледане на версии за глобалния документ {{entityLabel}}"
} }
} }

View File

@@ -69,6 +69,8 @@
"invalidFileType": "Neplatný typ souboru", "invalidFileType": "Neplatný typ souboru",
"invalidFileTypeValue": "Neplatný typ souboru: {{value}}", "invalidFileTypeValue": "Neplatný typ souboru: {{value}}",
"loadingDocument": "Při načítání dokumentu s ID {{id}} došlo k chybě.", "loadingDocument": "Při načítání dokumentu s ID {{id}} došlo k chybě.",
"localesNotSaved_one": "Následující lokalitu se nepodařilo uložit:",
"localesNotSaved_other": "Následující lokality se nepodařilo uložit:",
"missingEmail": "Chybějící email.", "missingEmail": "Chybějící email.",
"missingIDOfDocument": "Chybějící ID dokumentu pro aktualizaci.", "missingIDOfDocument": "Chybějící ID dokumentu pro aktualizaci.",
"missingIDOfVersion": "Chybějící ID verze.", "missingIDOfVersion": "Chybějící ID verze.",
@@ -177,6 +179,8 @@
"deleting": "Odstraňování...", "deleting": "Odstraňování...",
"descending": "Sestupně", "descending": "Sestupně",
"deselectAllRows": "Zrušte výběr všech řádků", "deselectAllRows": "Zrušte výběr všech řádků",
"document": "Dokument",
"documents": "Dokumenty",
"duplicate": "Duplikovat", "duplicate": "Duplikovat",
"duplicateWithoutSaving": "Duplikovat bez uložení změn", "duplicateWithoutSaving": "Duplikovat bez uložení změn",
"edit": "Upravit", "edit": "Upravit",
@@ -369,4 +373,4 @@
"viewingVersions": "Zobrazuji verze pro {{entityLabel}} {{documentTitle}}", "viewingVersions": "Zobrazuji verze pro {{entityLabel}} {{documentTitle}}",
"viewingVersionsGlobal": "Zobrazuji verze pro globální {{entityLabel}}" "viewingVersionsGlobal": "Zobrazuji verze pro globální {{entityLabel}}"
} }
} }

View File

@@ -69,6 +69,8 @@
"invalidFileType": "Ungültiger Datei-Typ", "invalidFileType": "Ungültiger Datei-Typ",
"invalidFileTypeValue": "Ungültiger Datei-Typ: {{value}}", "invalidFileTypeValue": "Ungültiger Datei-Typ: {{value}}",
"loadingDocument": "Es gab ein Problem, das Dokument mit der ID {{id}} zu laden.", "loadingDocument": "Es gab ein Problem, das Dokument mit der ID {{id}} zu laden.",
"localesNotSaved_one": "Das folgende Gebietsschema konnte nicht gespeichert werden:",
"localesNotSaved_other": "Die folgenden Gebietsschemata konnten nicht gespeichert werden:",
"missingEmail": "E-Mail-Adresse fehlt.", "missingEmail": "E-Mail-Adresse fehlt.",
"missingIDOfDocument": "ID des zu speichernden Dokuments fehlt.", "missingIDOfDocument": "ID des zu speichernden Dokuments fehlt.",
"missingIDOfVersion": "ID der Version fehlt.", "missingIDOfVersion": "ID der Version fehlt.",
@@ -177,6 +179,8 @@
"deleting": "Lösche...", "deleting": "Lösche...",
"descending": "Absteigend", "descending": "Absteigend",
"deselectAllRows": "Alle Zeilen abwählen", "deselectAllRows": "Alle Zeilen abwählen",
"document": "Dokument",
"documents": "Dokumente",
"duplicate": "Duplizieren", "duplicate": "Duplizieren",
"duplicateWithoutSaving": "Dupliziere ohne Änderungen zu speichern", "duplicateWithoutSaving": "Dupliziere ohne Änderungen zu speichern",
"edit": "Bearbeiten", "edit": "Bearbeiten",
@@ -369,4 +373,4 @@
"viewingVersions": "Betrachte Versionen für {{entityLabel}} {{documentTitle}}", "viewingVersions": "Betrachte Versionen für {{entityLabel}} {{documentTitle}}",
"viewingVersionsGlobal": "`Betrachte Versionen für das Globale Dokument {{entityLabel}}" "viewingVersionsGlobal": "`Betrachte Versionen für das Globale Dokument {{entityLabel}}"
} }
} }

View File

@@ -179,6 +179,8 @@
"deleting": "Deleting...", "deleting": "Deleting...",
"descending": "Descending", "descending": "Descending",
"deselectAllRows": "Deselect all rows", "deselectAllRows": "Deselect all rows",
"document": "Document",
"documents": "Documents",
"duplicate": "Duplicate", "duplicate": "Duplicate",
"duplicateWithoutSaving": "Duplicate without saving changes", "duplicateWithoutSaving": "Duplicate without saving changes",
"edit": "Edit", "edit": "Edit",

View File

@@ -69,6 +69,8 @@
"invalidFileType": "Tipo de archivo inválido", "invalidFileType": "Tipo de archivo inválido",
"invalidFileTypeValue": "Tipo de archivo inválido: {{value}}", "invalidFileTypeValue": "Tipo de archivo inválido: {{value}}",
"loadingDocument": "Ocurrió un problema al cargar el documento con la ID {{id}}.", "loadingDocument": "Ocurrió un problema al cargar el documento con la ID {{id}}.",
"localesNotSaved_one": "No se pudo guardar la siguiente configuración regional:",
"localesNotSaved_other": "No se pudieron guardar las siguientes configuraciones regionales:",
"missingEmail": "Falta el correo.", "missingEmail": "Falta el correo.",
"missingIDOfDocument": "Falta la ID del documento a actualizar.", "missingIDOfDocument": "Falta la ID del documento a actualizar.",
"missingIDOfVersion": "Falta la ID de la versión.", "missingIDOfVersion": "Falta la ID de la versión.",
@@ -177,6 +179,8 @@
"deleting": "Eliminando...", "deleting": "Eliminando...",
"descending": "Descendente", "descending": "Descendente",
"deselectAllRows": "Deselecciona todas las filas", "deselectAllRows": "Deselecciona todas las filas",
"document": "Documento",
"documents": "Documentos",
"duplicate": "Duplicar", "duplicate": "Duplicar",
"duplicateWithoutSaving": "Duplicar sin guardar cambios", "duplicateWithoutSaving": "Duplicar sin guardar cambios",
"edit": "Editar", "edit": "Editar",
@@ -369,4 +373,4 @@
"viewingVersions": "Viendo versiones para {{entityLabel}} {{documentTitle}}", "viewingVersions": "Viendo versiones para {{entityLabel}} {{documentTitle}}",
"viewingVersionsGlobal": "Viendo versiones para el global {{entityLabel}}" "viewingVersionsGlobal": "Viendo versiones para el global {{entityLabel}}"
} }
} }

View File

@@ -69,6 +69,8 @@
"invalidFileType": "نوع رسانه نامعتبر است", "invalidFileType": "نوع رسانه نامعتبر است",
"invalidFileTypeValue": "نوع رسانه نامعتبر: {{value}}", "invalidFileTypeValue": "نوع رسانه نامعتبر: {{value}}",
"loadingDocument": "مشکلی در بارگیری رسانه با شناسه {{id}} پیش آمد.", "loadingDocument": "مشکلی در بارگیری رسانه با شناسه {{id}} پیش آمد.",
"localesNotSaved_one": "امکان ذخیره‌سازی تنظیمات محلی زیر وجود ندارد:",
"localesNotSaved_other": "امکان ذخیره‌سازی تنظیمات محلی زیر وجود ندارد:",
"missingEmail": "رایانامه وارد نشده.", "missingEmail": "رایانامه وارد نشده.",
"missingIDOfDocument": "شناسه سند جهت بروزرسانی نامعتبر است.", "missingIDOfDocument": "شناسه سند جهت بروزرسانی نامعتبر است.",
"missingIDOfVersion": "شناسه نگارش وارد نشده.", "missingIDOfVersion": "شناسه نگارش وارد نشده.",
@@ -177,6 +179,8 @@
"deleting": "در حال حذف...", "deleting": "در حال حذف...",
"descending": "رو به پایین", "descending": "رو به پایین",
"deselectAllRows": "تمام سطرها را از انتخاب خارج کنید", "deselectAllRows": "تمام سطرها را از انتخاب خارج کنید",
"document": "سند",
"documents": "اسناد",
"duplicate": "تکراری", "duplicate": "تکراری",
"duplicateWithoutSaving": "رونوشت بدون ذخیره کردن تغییرات", "duplicateWithoutSaving": "رونوشت بدون ذخیره کردن تغییرات",
"edit": "نگارش", "edit": "نگارش",
@@ -369,4 +373,4 @@
"viewingVersions": "مشاهده نگارش‌ها برای {{entityLabel}} {{documentTitle}}", "viewingVersions": "مشاهده نگارش‌ها برای {{entityLabel}} {{documentTitle}}",
"viewingVersionsGlobal": "مشاهده نگارش‌های کلی {{entityLabel}}" "viewingVersionsGlobal": "مشاهده نگارش‌های کلی {{entityLabel}}"
} }
} }

View File

@@ -69,6 +69,8 @@
"invalidFileType": "Type de fichier invalide", "invalidFileType": "Type de fichier invalide",
"invalidFileTypeValue": "Type de fichier invalide : {{value}}", "invalidFileTypeValue": "Type de fichier invalide : {{value}}",
"loadingDocument": "Un problème est survenu lors du chargement du document qui a pour identifiant {{id}}.", "loadingDocument": "Un problème est survenu lors du chargement du document qui a pour identifiant {{id}}.",
"localesNotSaved_one": "Le paramètre régional suivant n'a pas pu être enregistré :",
"localesNotSaved_other": "Les paramètres régionaux suivants n'ont pas pu être enregistrés :",
"missingEmail": "E-mail manquant.", "missingEmail": "E-mail manquant.",
"missingIDOfDocument": "Il manque l'identifiant du document à mettre à jour.", "missingIDOfDocument": "Il manque l'identifiant du document à mettre à jour.",
"missingIDOfVersion": "Il manque l'identifiant de la version.", "missingIDOfVersion": "Il manque l'identifiant de la version.",
@@ -177,6 +179,8 @@
"deleting": "Suppression en cours...", "deleting": "Suppression en cours...",
"descending": "Descendant(e)", "descending": "Descendant(e)",
"deselectAllRows": "Désélectionner toutes les lignes", "deselectAllRows": "Désélectionner toutes les lignes",
"document": "Document",
"documents": "Documents",
"duplicate": "Dupliquer", "duplicate": "Dupliquer",
"duplicateWithoutSaving": "Dupliquer sans enregistrer les modifications", "duplicateWithoutSaving": "Dupliquer sans enregistrer les modifications",
"edit": "Éditer", "edit": "Éditer",
@@ -369,4 +373,4 @@
"viewingVersions": "Affichage des versions de ou du {{entityLabel}} {{documentTitle}}", "viewingVersions": "Affichage des versions de ou du {{entityLabel}} {{documentTitle}}",
"viewingVersionsGlobal": "Affichage des versions globales de ou du {{entityLabel}}" "viewingVersionsGlobal": "Affichage des versions globales de ou du {{entityLabel}}"
} }
} }

View File

@@ -69,6 +69,8 @@
"invalidFileType": "Nevaljan tip datoteke", "invalidFileType": "Nevaljan tip datoteke",
"invalidFileTypeValue": "Nevaljan tip datoteke: {{value}}", "invalidFileTypeValue": "Nevaljan tip datoteke: {{value}}",
"loadingDocument": "Pojavio se problem pri učitavanju dokumenta čiji je ID {{id}}.", "loadingDocument": "Pojavio se problem pri učitavanju dokumenta čiji je ID {{id}}.",
"localesNotSaved_one": "Sljedeću lokalnu postavku nije bilo moguće spremiti:",
"localesNotSaved_other": "Sljedeće lokalne postavke nije bilo moguće spremiti:",
"missingEmail": "Nedostaje email.", "missingEmail": "Nedostaje email.",
"missingIDOfDocument": "Nedostaje ID dokumenta da bi se ažurirao.", "missingIDOfDocument": "Nedostaje ID dokumenta da bi se ažurirao.",
"missingIDOfVersion": "Nedostaje ID verzije.", "missingIDOfVersion": "Nedostaje ID verzije.",
@@ -177,6 +179,8 @@
"deleting": "Brisanje...", "deleting": "Brisanje...",
"descending": "Silazno", "descending": "Silazno",
"deselectAllRows": "Odznači sve redove", "deselectAllRows": "Odznači sve redove",
"document": "Dokument",
"documents": "Dokumenti",
"duplicate": "Duplikat", "duplicate": "Duplikat",
"duplicateWithoutSaving": "Dupliciraj bez spremanja promjena", "duplicateWithoutSaving": "Dupliciraj bez spremanja promjena",
"edit": "Uredi", "edit": "Uredi",
@@ -369,4 +373,4 @@
"viewingVersions": "Pregled verzija za {{entityLabel}} {{documentTitle}}", "viewingVersions": "Pregled verzija za {{entityLabel}} {{documentTitle}}",
"viewingVersionsGlobal": "Pregled verzije za globalni {{entityLabel}}" "viewingVersionsGlobal": "Pregled verzije za globalni {{entityLabel}}"
} }
} }

View File

@@ -69,6 +69,8 @@
"invalidFileType": "Érvénytelen fájltípus", "invalidFileType": "Érvénytelen fájltípus",
"invalidFileTypeValue": "Érvénytelen fájltípus: {{value}}", "invalidFileTypeValue": "Érvénytelen fájltípus: {{value}}",
"loadingDocument": "Hiba történt a {{id}} azonosítójú dokumentum betöltésekor.", "loadingDocument": "Hiba történt a {{id}} azonosítójú dokumentum betöltésekor.",
"localesNotSaved_one": "Az alábbi helyi beállítást nem sikerült menteni:",
"localesNotSaved_other": "Az alábbi helyi beállításokat nem sikerült menteni:",
"missingEmail": "Hiányzó e-mail.", "missingEmail": "Hiányzó e-mail.",
"missingIDOfDocument": "Hiányzik a frissítendő dokumentum azonosítója.", "missingIDOfDocument": "Hiányzik a frissítendő dokumentum azonosítója.",
"missingIDOfVersion": "A verzió azonosítója hiányzik.", "missingIDOfVersion": "A verzió azonosítója hiányzik.",
@@ -177,6 +179,8 @@
"deleting": "Törlés...", "deleting": "Törlés...",
"descending": "Csökkenő", "descending": "Csökkenő",
"deselectAllRows": "Jelölje ki az összes sort", "deselectAllRows": "Jelölje ki az összes sort",
"document": "Dokumentum",
"documents": "Dokumentumok",
"duplicate": "Duplikálás", "duplicate": "Duplikálás",
"duplicateWithoutSaving": "Duplikálás a módosítások mentése nélkül", "duplicateWithoutSaving": "Duplikálás a módosítások mentése nélkül",
"edit": "Szerkesztés", "edit": "Szerkesztés",
@@ -369,4 +373,4 @@
"viewingVersions": "A {{entityLabel}} {{documentTitle}} verzióinak megtekintése", "viewingVersions": "A {{entityLabel}} {{documentTitle}} verzióinak megtekintése",
"viewingVersionsGlobal": "A globális {{entityLabel}} verzióinak megtekintése" "viewingVersionsGlobal": "A globális {{entityLabel}} verzióinak megtekintése"
} }
} }

View File

@@ -69,6 +69,8 @@
"invalidFileType": "Tipo di file non valido", "invalidFileType": "Tipo di file non valido",
"invalidFileTypeValue": "Tipo di file non valido: {{value}}", "invalidFileTypeValue": "Tipo di file non valido: {{value}}",
"loadingDocument": "Si è verificato un problema durante il caricamento del documento con ID {{id}}.", "loadingDocument": "Si è verificato un problema durante il caricamento del documento con ID {{id}}.",
"localesNotSaved_one": "Non è stato possibile salvare la seguente impostazione locale:",
"localesNotSaved_other": "Non è stato possibile salvare le seguenti impostazioni locali:",
"missingEmail": "Email mancante.", "missingEmail": "Email mancante.",
"missingIDOfDocument": "ID del documento da aggiornare mancante.", "missingIDOfDocument": "ID del documento da aggiornare mancante.",
"missingIDOfVersion": "ID della versione mancante.", "missingIDOfVersion": "ID della versione mancante.",
@@ -177,6 +179,8 @@
"deleting": "Sto eliminando...", "deleting": "Sto eliminando...",
"descending": "Decrescente", "descending": "Decrescente",
"deselectAllRows": "Deseleziona tutte le righe", "deselectAllRows": "Deseleziona tutte le righe",
"document": "Documento",
"documents": "Documenti",
"duplicate": "Duplica", "duplicate": "Duplica",
"duplicateWithoutSaving": "Duplica senza salvare le modifiche", "duplicateWithoutSaving": "Duplica senza salvare le modifiche",
"edit": "Modificare", "edit": "Modificare",
@@ -370,4 +374,4 @@
"viewingVersions": "Visualizzazione delle versioni per {{entityLabel}} {{documentTitle}}", "viewingVersions": "Visualizzazione delle versioni per {{entityLabel}} {{documentTitle}}",
"viewingVersionsGlobal": "`Visualizzazione delle versioni per {{entityLabel}}" "viewingVersionsGlobal": "`Visualizzazione delle versioni per {{entityLabel}}"
} }
} }

View File

@@ -69,6 +69,8 @@
"invalidFileType": "無効なファイル形式", "invalidFileType": "無効なファイル形式",
"invalidFileTypeValue": "無効なファイル形式: {{value}}", "invalidFileTypeValue": "無効なファイル形式: {{value}}",
"loadingDocument": "IDが {{id}} のデータを読み込む際に問題が発生しました。", "loadingDocument": "IDが {{id}} のデータを読み込む際に問題が発生しました。",
"localesNotSaved_one": "次のロケールを保存できませんでした:",
"localesNotSaved_other": "次のロケールを保存できませんでした:",
"missingEmail": "メールアドレスが不足しています。", "missingEmail": "メールアドレスが不足しています。",
"missingIDOfDocument": "更新するデータのIDが不足しています。", "missingIDOfDocument": "更新するデータのIDが不足しています。",
"missingIDOfVersion": "バージョンIDが不足しています。", "missingIDOfVersion": "バージョンIDが不足しています。",
@@ -177,6 +179,8 @@
"deleting": "削除しています...", "deleting": "削除しています...",
"descending": "降順", "descending": "降順",
"deselectAllRows": "すべての行の選択を解除します", "deselectAllRows": "すべての行の選択を解除します",
"document": "ドキュメント",
"documents": "ドキュメント",
"duplicate": "複製", "duplicate": "複製",
"duplicateWithoutSaving": "変更を保存せずに複製", "duplicateWithoutSaving": "変更を保存せずに複製",
"edit": "編集", "edit": "編集",
@@ -369,4 +373,4 @@
"viewingVersions": "表示バージョン: {{entityLabel}} {{documentTitle}}", "viewingVersions": "表示バージョン: {{entityLabel}} {{documentTitle}}",
"viewingVersionsGlobal": "表示バージョン: グローバルな {{entityLabel}}" "viewingVersionsGlobal": "表示バージョン: グローバルな {{entityLabel}}"
} }
} }

View File

@@ -68,6 +68,8 @@
"invalidFileType": "잘못된 파일 형식", "invalidFileType": "잘못된 파일 형식",
"invalidFileTypeValue": "잘못된 파일 형식: {{value}}", "invalidFileTypeValue": "잘못된 파일 형식: {{value}}",
"loadingDocument": "ID가 {{id}}인 문서를 불러오는 중에 문제가 발생했습니다.", "loadingDocument": "ID가 {{id}}인 문서를 불러오는 중에 문제가 발생했습니다.",
"localesNotSaved_one": "다음 로케일을 저장할 수 없습니다:",
"localesNotSaved_other": "다음 로케일들을 저장할 수 없습니다:",
"missingEmail": "이메일이 누락되었습니다.", "missingEmail": "이메일이 누락되었습니다.",
"missingIDOfDocument": "업데이트할 문서의 ID가 누락되었습니다.", "missingIDOfDocument": "업데이트할 문서의 ID가 누락되었습니다.",
"missingIDOfVersion": "버전의 ID가 누락되었습니다.", "missingIDOfVersion": "버전의 ID가 누락되었습니다.",
@@ -176,6 +178,8 @@
"deleting": "삭제 중...", "deleting": "삭제 중...",
"descending": "내림차순", "descending": "내림차순",
"deselectAllRows": "모든 행 선택 해제", "deselectAllRows": "모든 행 선택 해제",
"document": "문서",
"documents": "문서들",
"duplicate": "복제", "duplicate": "복제",
"duplicateWithoutSaving": "변경 사항 저장 없이 복제", "duplicateWithoutSaving": "변경 사항 저장 없이 복제",
"edit": "수정", "edit": "수정",

View File

@@ -69,6 +69,8 @@
"invalidFileType": "မမှန်ကန်သော ဖိုင်အမျိုးအစား", "invalidFileType": "မမှန်ကန်သော ဖိုင်အမျိုးအစား",
"invalidFileTypeValue": "မမှန်ကန်သော ဖိုင်အမျိုးအစား: {{value}}", "invalidFileTypeValue": "မမှန်ကန်သော ဖိုင်အမျိုးအစား: {{value}}",
"loadingDocument": "{{id}} ID ဖြင့် ဖိုင်ကို ဖွင့်ရာတွင် ပြဿနာရှိနေသည်။", "loadingDocument": "{{id}} ID ဖြင့် ဖိုင်ကို ဖွင့်ရာတွင် ပြဿနာရှိနေသည်။",
"localesNotSaved_one": "အောက်ပါ ဒေသသတ်မှတ်ချက်ကို သိမ်းဆည်း၍ မရပါ။",
"localesNotSaved_other": "အောက်ပါ ဒေသသတ်မှတ်ချက်များကို သိမ်းဆည်း၍ မရပါ။",
"missingEmail": "အီးမေးလ်ပျောက်ဆုံး", "missingEmail": "အီးမေးလ်ပျောက်ဆုံး",
"missingIDOfDocument": "ပြင်ဆင်ရန် ဖိုင် ID ပျောက်နေပါသည်။", "missingIDOfDocument": "ပြင်ဆင်ရန် ဖိုင် ID ပျောက်နေပါသည်။",
"missingIDOfVersion": "ပျောက်ဆုံး ဗားရှင်း ID", "missingIDOfVersion": "ပျောက်ဆုံး ဗားရှင်း ID",
@@ -177,6 +179,8 @@
"deleting": "ဖျက်နေဆဲ ...", "deleting": "ဖျက်နေဆဲ ...",
"descending": "ဆင်းသက်လာသည်။", "descending": "ဆင်းသက်လာသည်။",
"deselectAllRows": "အားလုံးကို မရွေးနိုင်ပါ", "deselectAllRows": "အားလုံးကို မရွေးနိုင်ပါ",
"document": "စာရွက်စာတမ်း",
"documents": "စာရွက်စာတမ်းများ",
"duplicate": "ပုံတူပွားမည်။", "duplicate": "ပုံတူပွားမည်။",
"duplicateWithoutSaving": "သေချာပါပြီ။", "duplicateWithoutSaving": "သေချာပါပြီ။",
"edit": "တည်းဖြတ်ပါ။", "edit": "တည်းဖြတ်ပါ။",
@@ -369,4 +373,4 @@
"viewingVersions": "{{entityLabel}} {{documentTitle}} အတွက် ဗားရှင်းများကို ကြည့်ရှုခြင်း", "viewingVersions": "{{entityLabel}} {{documentTitle}} အတွက် ဗားရှင်းများကို ကြည့်ရှုခြင်း",
"viewingVersionsGlobal": "`ဂလိုဘယ်ဆိုင်ရာ {{entityLabel}} အတွက် ဗားရှင်းများကို ကြည့်ရှုနေသည်" "viewingVersionsGlobal": "`ဂလိုဘယ်ဆိုင်ရာ {{entityLabel}} အတွက် ဗားရှင်းများကို ကြည့်ရှုနေသည်"
} }
} }

View File

@@ -69,6 +69,8 @@
"invalidFileType": "Ugyldig filtype", "invalidFileType": "Ugyldig filtype",
"invalidFileTypeValue": "Ugyldig filtype: {{value}}", "invalidFileTypeValue": "Ugyldig filtype: {{value}}",
"loadingDocument": "Det oppstod et problem under lasting av dokumentet med ID {{id}}.", "loadingDocument": "Det oppstod et problem under lasting av dokumentet med ID {{id}}.",
"localesNotSaved_one": "Følgende lokalisering kunne ikke lagres:",
"localesNotSaved_other": "Følgende lokaliseringer kunne ikke lagres:",
"missingEmail": "Mangler e-postadresse.", "missingEmail": "Mangler e-postadresse.",
"missingIDOfDocument": "Mangler ID for dokumentet som skal oppdateres.", "missingIDOfDocument": "Mangler ID for dokumentet som skal oppdateres.",
"missingIDOfVersion": "Mangler ID for versjonen.", "missingIDOfVersion": "Mangler ID for versjonen.",
@@ -177,6 +179,8 @@
"deleting": "Sletter...", "deleting": "Sletter...",
"descending": "Synkende", "descending": "Synkende",
"deselectAllRows": "Fjern markeringen fra alle rader", "deselectAllRows": "Fjern markeringen fra alle rader",
"document": "Dokument",
"documents": "Dokumenter",
"duplicate": "Dupliser", "duplicate": "Dupliser",
"duplicateWithoutSaving": "Dupliser uten å lagre endringer", "duplicateWithoutSaving": "Dupliser uten å lagre endringer",
"edit": "Redigere", "edit": "Redigere",
@@ -369,4 +373,4 @@
"viewingVersions": "Viser versjoner for {{entityLabel}} {{documentTitle}}", "viewingVersions": "Viser versjoner for {{entityLabel}} {{documentTitle}}",
"viewingVersionsGlobal": "Viser versjoner for den globale variabelen {{entityLabel}}" "viewingVersionsGlobal": "Viser versjoner for den globale variabelen {{entityLabel}}"
} }
} }

View File

@@ -69,6 +69,8 @@
"invalidFileType": "Ongeldig bestandstype", "invalidFileType": "Ongeldig bestandstype",
"invalidFileTypeValue": "Ongeldig bestandstype: {{value}}", "invalidFileTypeValue": "Ongeldig bestandstype: {{value}}",
"loadingDocument": "Er was een probleem met het laden van het document met ID {{id}}.", "loadingDocument": "Er was een probleem met het laden van het document met ID {{id}}.",
"localesNotSaved_one": "De volgende taalinstelling kon niet worden opgeslagen:",
"localesNotSaved_other": "De volgende taalinstellingen konden niet worden opgeslagen:",
"missingEmail": "E-mailadres ontbreekt.", "missingEmail": "E-mailadres ontbreekt.",
"missingIDOfDocument": "ID ontbreekt van het aan te passen document.", "missingIDOfDocument": "ID ontbreekt van het aan te passen document.",
"missingIDOfVersion": "ID van versie ontbreekt.", "missingIDOfVersion": "ID van versie ontbreekt.",
@@ -177,6 +179,8 @@
"deleting": "Verwijderen...", "deleting": "Verwijderen...",
"descending": "Aflopend", "descending": "Aflopend",
"deselectAllRows": "Deselecteer alle rijen", "deselectAllRows": "Deselecteer alle rijen",
"document": "Document",
"documents": "Documenten",
"duplicate": "Dupliceren", "duplicate": "Dupliceren",
"duplicateWithoutSaving": "Dupliceren zonder wijzigingen te bewaren", "duplicateWithoutSaving": "Dupliceren zonder wijzigingen te bewaren",
"edit": "Bewerk", "edit": "Bewerk",
@@ -369,4 +373,4 @@
"viewingVersions": "Bekijk versies voor {{entityLabel}} {{documentTitle}}", "viewingVersions": "Bekijk versies voor {{entityLabel}} {{documentTitle}}",
"viewingVersionsGlobal": "`Bekijk versies voor global {{entityLabel}}" "viewingVersionsGlobal": "`Bekijk versies voor global {{entityLabel}}"
} }
} }

View File

@@ -69,6 +69,8 @@
"invalidFileType": "Nieprawidłowy typ pliku", "invalidFileType": "Nieprawidłowy typ pliku",
"invalidFileTypeValue": "Nieprawidłowy typ pliku: {{value}}", "invalidFileTypeValue": "Nieprawidłowy typ pliku: {{value}}",
"loadingDocument": "Wystapił problem podczas ładowania dokumentu o ID {{id}}.", "loadingDocument": "Wystapił problem podczas ładowania dokumentu o ID {{id}}.",
"localesNotSaved_one": "Następującej lokalizacji nie można było zapisać:",
"localesNotSaved_other": "Następujących lokalizacji nie można było zapisać:",
"missingEmail": "Brak adresu email.", "missingEmail": "Brak adresu email.",
"missingIDOfDocument": "Brak ID dokumentu do aktualizacji.", "missingIDOfDocument": "Brak ID dokumentu do aktualizacji.",
"missingIDOfVersion": "Brak ID wersji", "missingIDOfVersion": "Brak ID wersji",
@@ -178,6 +180,8 @@
"deleting": "Usuwanie...", "deleting": "Usuwanie...",
"descending": "Malejąco", "descending": "Malejąco",
"deselectAllRows": "Odznacz wszystkie wiersze", "deselectAllRows": "Odznacz wszystkie wiersze",
"document": "Dokument",
"documents": "Dokumenty",
"duplicate": "Zduplikuj", "duplicate": "Zduplikuj",
"duplicateWithoutSaving": "Zduplikuj bez zapisywania zmian", "duplicateWithoutSaving": "Zduplikuj bez zapisywania zmian",
"edit": "Edytuj", "edit": "Edytuj",

View File

@@ -69,6 +69,8 @@
"invalidFileType": "Tipo de arquivo inválido", "invalidFileType": "Tipo de arquivo inválido",
"invalidFileTypeValue": "Tipo de arquivo inválido: {{value}}", "invalidFileTypeValue": "Tipo de arquivo inválido: {{value}}",
"loadingDocument": "Ocorreu um problema ao carregar o documento com ID {{id}}.", "loadingDocument": "Ocorreu um problema ao carregar o documento com ID {{id}}.",
"localesNotSaved_one": "A seguinte configuração regional não pôde ser salva:",
"localesNotSaved_other": "As seguintes configurações regionais não puderam ser salvas:",
"missingEmail": "Email ausente.", "missingEmail": "Email ausente.",
"missingIDOfDocument": "ID do documento a ser atualizado ausente.", "missingIDOfDocument": "ID do documento a ser atualizado ausente.",
"missingIDOfVersion": "ID da versão ausente.", "missingIDOfVersion": "ID da versão ausente.",
@@ -177,6 +179,8 @@
"deleting": "Excluindo...", "deleting": "Excluindo...",
"descending": "Decrescente", "descending": "Decrescente",
"deselectAllRows": "Desmarcar todas as linhas", "deselectAllRows": "Desmarcar todas as linhas",
"document": "Documento",
"documents": "Documentos",
"duplicate": "Duplicar", "duplicate": "Duplicar",
"duplicateWithoutSaving": "Duplicar sem salvar alterações", "duplicateWithoutSaving": "Duplicar sem salvar alterações",
"edit": "Editar", "edit": "Editar",
@@ -369,4 +373,4 @@
"viewingVersions": "Visualizando versões para o/a {{entityLabel}} {{documentTitle}}", "viewingVersions": "Visualizando versões para o/a {{entityLabel}} {{documentTitle}}",
"viewingVersionsGlobal": "`Visualizando versões para o global {{entityLabel}}" "viewingVersionsGlobal": "`Visualizando versões para o global {{entityLabel}}"
} }
} }

View File

@@ -69,6 +69,8 @@
"invalidFileType": "Tip de fișier invalid", "invalidFileType": "Tip de fișier invalid",
"invalidFileTypeValue": "Tip de fișier invalid: {{value}}", "invalidFileTypeValue": "Tip de fișier invalid: {{value}}",
"loadingDocument": "A existat o problemă la încărcarea documentului cu ID-ul de {{id}}.", "loadingDocument": "A existat o problemă la încărcarea documentului cu ID-ul de {{id}}.",
"localesNotSaved_one": "Următoarea localizare nu a putut fi salvată:",
"localesNotSaved_other": "Următoarele localizări nu au putut fi salvate:",
"missingEmail": "Lipsește emailul.", "missingEmail": "Lipsește emailul.",
"missingIDOfDocument": "Lipsește ID-ul documentului care trebuie actualizat.", "missingIDOfDocument": "Lipsește ID-ul documentului care trebuie actualizat.",
"missingIDOfVersion": "Lipsește ID-ul versiunii.", "missingIDOfVersion": "Lipsește ID-ul versiunii.",
@@ -177,6 +179,8 @@
"deleting": "Deleting...", "deleting": "Deleting...",
"descending": "Descendentă", "descending": "Descendentă",
"deselectAllRows": "Deselectează toate rândurile", "deselectAllRows": "Deselectează toate rândurile",
"document": "Document",
"documents": "Documente",
"duplicate": "Duplicați", "duplicate": "Duplicați",
"duplicateWithoutSaving": "Duplicați fără salvarea modificărilor", "duplicateWithoutSaving": "Duplicați fără salvarea modificărilor",
"edit": "Editează", "edit": "Editează",
@@ -369,4 +373,4 @@
"viewingVersions": "Vizualizarea versiunilor pentru {{entityLabel}} {{documentTitle}}", "viewingVersions": "Vizualizarea versiunilor pentru {{entityLabel}} {{documentTitle}}",
"viewingVersionsGlobal": "Vizualizarea versiunilor pentru globala {{entityLabel}}" "viewingVersionsGlobal": "Vizualizarea versiunilor pentru globala {{entityLabel}}"
} }
} }

View File

@@ -69,6 +69,8 @@
"invalidFileType": "Nevalidan tip datoteke", "invalidFileType": "Nevalidan tip datoteke",
"invalidFileTypeValue": "Nevalidan tip datoteke: {{value}}", "invalidFileTypeValue": "Nevalidan tip datoteke: {{value}}",
"loadingDocument": "Postoji problem pri učitavanju dokumenta čiji je ID {{id}}.", "loadingDocument": "Postoji problem pri učitavanju dokumenta čiji je ID {{id}}.",
"localesNotSaved_one": "Nije moglo da se sačuva sledeće lokalno podešavanje:",
"localesNotSaved_other": "Nisu mogla da se sačuvaju sledeća lokalna podešavanja:",
"missingEmail": "Nedostaje adresa e-pošte.", "missingEmail": "Nedostaje adresa e-pošte.",
"missingIDOfDocument": "Nedostaje ID dokumenta da bi se ažurirao.", "missingIDOfDocument": "Nedostaje ID dokumenta da bi se ažurirao.",
"missingIDOfVersion": "Nedostaje ID verzije.", "missingIDOfVersion": "Nedostaje ID verzije.",
@@ -177,6 +179,8 @@
"deleting": "Brisanje...", "deleting": "Brisanje...",
"descending": "Opadajuće", "descending": "Opadajuće",
"deselectAllRows": "Deselektujte sve redove", "deselectAllRows": "Deselektujte sve redove",
"document": "Dokument",
"documents": "Dokumenti",
"duplicate": "Duplikat", "duplicate": "Duplikat",
"duplicateWithoutSaving": "Ponovi bez čuvanja promena", "duplicateWithoutSaving": "Ponovi bez čuvanja promena",
"edit": "Uredi", "edit": "Uredi",

View File

@@ -69,6 +69,8 @@
"invalidFileType": "Невалидан тип датотеке", "invalidFileType": "Невалидан тип датотеке",
"invalidFileTypeValue": "Невалидан тип датотеке: {{value}}", "invalidFileTypeValue": "Невалидан тип датотеке: {{value}}",
"loadingDocument": "Постоји проблем при учитавању документа чији је ИД {{id}}.", "loadingDocument": "Постоји проблем при учитавању документа чији је ИД {{id}}.",
"localesNotSaved_one": "Следеће локалне поставке није могло бити сачувано:",
"localesNotSaved_other": "Следеће локалне поставке нису могле бити сачуване:",
"missingEmail": "Недостаје емаил.", "missingEmail": "Недостаје емаил.",
"missingIDOfDocument": "Недостаје ИД документа да би се ажурирао.", "missingIDOfDocument": "Недостаје ИД документа да би се ажурирао.",
"missingIDOfVersion": "Недостаје ИД верзије.", "missingIDOfVersion": "Недостаје ИД верзије.",
@@ -177,6 +179,8 @@
"deleting": "Брисање...", "deleting": "Брисање...",
"descending": "Опадајуће", "descending": "Опадајуће",
"deselectAllRows": "Деселектујте све редове", "deselectAllRows": "Деселектујте све редове",
"document": "Dokument",
"documents": "Dokumenti",
"duplicate": "Дупликат", "duplicate": "Дупликат",
"duplicateWithoutSaving": "Понови без чувања промена", "duplicateWithoutSaving": "Понови без чувања промена",
"edit": "Уреди", "edit": "Уреди",

View File

@@ -69,6 +69,8 @@
"invalidFileType": "Недопустимый тип файла", "invalidFileType": "Недопустимый тип файла",
"invalidFileTypeValue": "Недопустимый тип файла: {{value}}", "invalidFileTypeValue": "Недопустимый тип файла: {{value}}",
"loadingDocument": "Возникла проблема при загрузке документа с ID {{id}}.", "loadingDocument": "Возникла проблема при загрузке документа с ID {{id}}.",
"localesNotSaved_one": "Следующую локализацию не удалось сохранить:",
"localesNotSaved_other": "Следующие локализации не удалось сохранить:",
"missingEmail": "Отсутствует email.", "missingEmail": "Отсутствует email.",
"missingIDOfDocument": "Отсутствующий ID документа для обновления.", "missingIDOfDocument": "Отсутствующий ID документа для обновления.",
"missingIDOfVersion": "Отсутствует ID версии.", "missingIDOfVersion": "Отсутствует ID версии.",
@@ -177,6 +179,8 @@
"deleting": "Удаление...", "deleting": "Удаление...",
"descending": "Уменьшение", "descending": "Уменьшение",
"deselectAllRows": "Снять выделение со всех строк", "deselectAllRows": "Снять выделение со всех строк",
"document": "Документ",
"documents": "Документы",
"duplicate": "Дублировать", "duplicate": "Дублировать",
"duplicateWithoutSaving": "Дублирование без сохранения изменений", "duplicateWithoutSaving": "Дублирование без сохранения изменений",
"edit": "Редактировать", "edit": "Редактировать",
@@ -369,4 +373,4 @@
"viewingVersions": "Просмотр версий для {{entityLabel}} {{documentTitle}}", "viewingVersions": "Просмотр версий для {{entityLabel}} {{documentTitle}}",
"viewingVersionsGlobal": "`Просмотр версии для глобальной Коллекции {{entityLabel}}" "viewingVersionsGlobal": "`Просмотр версии для глобальной Коллекции {{entityLabel}}"
} }
} }

View File

@@ -69,6 +69,8 @@
"invalidFileType": "Ogiltig filtyp", "invalidFileType": "Ogiltig filtyp",
"invalidFileTypeValue": "Ogiltig filtyp: {{value}}", "invalidFileTypeValue": "Ogiltig filtyp: {{value}}",
"loadingDocument": "Det gick inte att läsa in dokumentet med ID {{id}}.", "loadingDocument": "Det gick inte att läsa in dokumentet med ID {{id}}.",
"localesNotSaved_one": "Följande lokal kunde inte sparas:",
"localesNotSaved_other": "Följande lokaler kunde inte sparas:",
"missingEmail": "E-postadress saknas.", "missingEmail": "E-postadress saknas.",
"missingIDOfDocument": "Saknar ID för dokumentet som ska uppdateras.", "missingIDOfDocument": "Saknar ID för dokumentet som ska uppdateras.",
"missingIDOfVersion": "ID för versionen saknas.", "missingIDOfVersion": "ID för versionen saknas.",
@@ -177,6 +179,8 @@
"deleting": "Tar bort...", "deleting": "Tar bort...",
"descending": "Fallande", "descending": "Fallande",
"deselectAllRows": "Avmarkera alla rader", "deselectAllRows": "Avmarkera alla rader",
"document": "Dokument",
"documents": "Dokument",
"duplicate": "Duplicera", "duplicate": "Duplicera",
"duplicateWithoutSaving": "Duplicera utan att spara ändringar", "duplicateWithoutSaving": "Duplicera utan att spara ändringar",
"edit": "Redigera", "edit": "Redigera",
@@ -369,4 +373,4 @@
"viewingVersions": "Visar versioner för {{entityLabel}} {{documentTitle}}", "viewingVersions": "Visar versioner för {{entityLabel}} {{documentTitle}}",
"viewingVersionsGlobal": "Visa versioner för den globala {{entityLabel}}" "viewingVersionsGlobal": "Visa versioner för den globala {{entityLabel}}"
} }
} }

View File

@@ -69,6 +69,8 @@
"invalidFileType": "ประเภทของไฟล์ไม่ถูกต้อง", "invalidFileType": "ประเภทของไฟล์ไม่ถูกต้อง",
"invalidFileTypeValue": "ประเภทของไฟล์ไม่ถูกต้อง: {{value}}", "invalidFileTypeValue": "ประเภทของไฟล์ไม่ถูกต้อง: {{value}}",
"loadingDocument": "เกิดปัญหาระหว่างการโหลดเอกสารที่มี ID {{id}}", "loadingDocument": "เกิดปัญหาระหว่างการโหลดเอกสารที่มี ID {{id}}",
"localesNotSaved_one": "ไม่สามารถบันทึกกำหนดสถานที่ต่อไปนี้ได้:",
"localesNotSaved_other": "ไม่สามารถบันทึกกำหนดสถานที่ต่อไปนี้ได้:",
"missingEmail": "ไม่พบอีเมล", "missingEmail": "ไม่พบอีเมล",
"missingIDOfDocument": "ไม่พบ ID ของเอกสารที่ต้องการแก้ไข", "missingIDOfDocument": "ไม่พบ ID ของเอกสารที่ต้องการแก้ไข",
"missingIDOfVersion": "ไม่พบ ID ของเวอร์ชัน", "missingIDOfVersion": "ไม่พบ ID ของเวอร์ชัน",
@@ -177,6 +179,8 @@
"deleting": "กำลังลบ...", "deleting": "กำลังลบ...",
"descending": "มากไปน้อย", "descending": "มากไปน้อย",
"deselectAllRows": "ยกเลิกการเลือกทุกแถว", "deselectAllRows": "ยกเลิกการเลือกทุกแถว",
"document": "เอกสาร",
"documents": "เอกสาร",
"duplicate": "สำเนา", "duplicate": "สำเนา",
"duplicateWithoutSaving": "สำเนาโดยไม่บันทึกการแก้ไข", "duplicateWithoutSaving": "สำเนาโดยไม่บันทึกการแก้ไข",
"edit": "แก้ไข", "edit": "แก้ไข",
@@ -369,4 +373,4 @@
"viewingVersions": "กำลังดูเวอร์ชันของ {{entityLabel}} {{documentTitle}}", "viewingVersions": "กำลังดูเวอร์ชันของ {{entityLabel}} {{documentTitle}}",
"viewingVersionsGlobal": "กำลังดูเวอร์ชันของ global {{entityLabel}}" "viewingVersionsGlobal": "กำลังดูเวอร์ชันของ global {{entityLabel}}"
} }
} }

View File

@@ -69,6 +69,8 @@
"invalidFileType": "Geçersiz dosya türü", "invalidFileType": "Geçersiz dosya türü",
"invalidFileTypeValue": "Geçersiz dosya türü: {{value}}", "invalidFileTypeValue": "Geçersiz dosya türü: {{value}}",
"loadingDocument": "{{id}} ID'ye sahip döküman yüklenirken bir sorun oluştu.", "loadingDocument": "{{id}} ID'ye sahip döküman yüklenirken bir sorun oluştu.",
"localesNotSaved_one": "Aşağıdaki yerel ayar kaydedilemedi:",
"localesNotSaved_other": "Aşağıdaki yerel ayarlar kaydedilemedi:",
"missingEmail": "E-posta adresi girilmedi.", "missingEmail": "E-posta adresi girilmedi.",
"missingIDOfDocument": "Güncellenecek döküman ID'si eksik.", "missingIDOfDocument": "Güncellenecek döküman ID'si eksik.",
"missingIDOfVersion": "Versiyon ID'si geçersiz.", "missingIDOfVersion": "Versiyon ID'si geçersiz.",
@@ -177,6 +179,8 @@
"deleting": "Siliniyor...", "deleting": "Siliniyor...",
"descending": "Azalan", "descending": "Azalan",
"deselectAllRows": "Tüm satırların seçimini kaldır", "deselectAllRows": "Tüm satırların seçimini kaldır",
"document": "Belge",
"documents": "Belgeler",
"duplicate": "Çoğalt", "duplicate": "Çoğalt",
"duplicateWithoutSaving": "Ayarları kaydetmeden çoğalt", "duplicateWithoutSaving": "Ayarları kaydetmeden çoğalt",
"edit": "Düzenle", "edit": "Düzenle",
@@ -369,4 +373,4 @@
"viewingVersions": "{{entityLabel}} {{documentTitle}} için sürümler gösteriliyor", "viewingVersions": "{{entityLabel}} {{documentTitle}} için sürümler gösteriliyor",
"viewingVersionsGlobal": "`Global {{entityLabel}} için sürümler gösteriliyor" "viewingVersionsGlobal": "`Global {{entityLabel}} için sürümler gösteriliyor"
} }
} }

View File

@@ -681,6 +681,12 @@
"deselectAllRows": { "deselectAllRows": {
"type": "string" "type": "string"
}, },
"document": {
"type": "string"
},
"documents": {
"type": "string"
},
"duplicate": { "duplicate": {
"type": "string" "type": "string"
}, },
@@ -962,6 +968,8 @@
"deletedSuccessfully", "deletedSuccessfully",
"deleting", "deleting",
"descending", "descending",
"document",
"documents",
"duplicate", "duplicate",
"duplicateWithoutSaving", "duplicateWithoutSaving",
"edit", "edit",

View File

@@ -64,11 +64,13 @@
"deletingTitle": "Виникла помилка під час видалення {{title}}, Будь ласка, перевірте ваше з'єднання та спробуйте ще раз.", "deletingTitle": "Виникла помилка під час видалення {{title}}, Будь ласка, перевірте ваше з'єднання та спробуйте ще раз.",
"emailOrPasswordIncorrect": "Вказаний email або пароль не є вірними", "emailOrPasswordIncorrect": "Вказаний email або пароль не є вірними",
"followingFieldsInvalid_one": "Наступне поле не є вірним:", "followingFieldsInvalid_one": "Наступне поле не є вірним:",
"followingFieldsInvalid_other": "Наступні поля не є вірними", "followingFieldsInvalid_other": "Наступні поля не є вірними:",
"incorrectCollection": "Неправильна колекція", "incorrectCollection": "Неправильна колекція",
"invalidFileType": "Невіртий тип файлу", "invalidFileType": "Невіртий тип файлу",
"invalidFileTypeValue": "Невірний тип файлу: {{value}}", "invalidFileTypeValue": "Невірний тип файлу: {{value}}",
"loadingDocument": "Виникла помилка під час завантаження документа з ID {{id}}.", "loadingDocument": "Виникла помилка під час завантаження документа з ID {{id}}.",
"localesNotSaved_one": "Наступне місце не вдалося зберегти:",
"localesNotSaved_other": "Наступні місця не вдалося зберегти:",
"missingEmail": "Відсутній email.", "missingEmail": "Відсутній email.",
"missingIDOfDocument": "Відсутній ID документа для оновлення.", "missingIDOfDocument": "Відсутній ID документа для оновлення.",
"missingIDOfVersion": "Відсутній ID версії.", "missingIDOfVersion": "Відсутній ID версії.",
@@ -177,6 +179,8 @@
"deleting": "Видалення...", "deleting": "Видалення...",
"descending": "В порядку спадання", "descending": "В порядку спадання",
"deselectAllRows": "Скасувати вибір всіх рядків", "deselectAllRows": "Скасувати вибір всіх рядків",
"document": "Документ",
"documents": "Документи",
"duplicate": "Дублювати", "duplicate": "Дублювати",
"duplicateWithoutSaving": "Дублювання без збереження змін", "duplicateWithoutSaving": "Дублювання без збереження змін",
"edit": "Редагувати", "edit": "Редагувати",
@@ -369,4 +373,4 @@
"viewingVersions": "Огляд версій для {{entityLabel}} {{documentTitle}}", "viewingVersions": "Огляд версій для {{entityLabel}} {{documentTitle}}",
"viewingVersionsGlobal": "Огляд версій для глобальної колекції {{entityLabel}}" "viewingVersionsGlobal": "Огляд версій для глобальної колекції {{entityLabel}}"
} }
} }

View File

@@ -69,6 +69,8 @@
"invalidFileType": "Lỗi - Định dạng tệp không hợp lệ.", "invalidFileType": "Lỗi - Định dạng tệp không hợp lệ.",
"invalidFileTypeValue": "Lỗi - Định dạng tệp không hợp lệ: {{value}}.", "invalidFileTypeValue": "Lỗi - Định dạng tệp không hợp lệ: {{value}}.",
"loadingDocument": "Lỗi - Đã xảy ra vấn để khi tải bản tài liệu với ID {{id}}.", "loadingDocument": "Lỗi - Đã xảy ra vấn để khi tải bản tài liệu với ID {{id}}.",
"localesNotSaved_one": "Không thể lưu trữ cài đặt vùng sau đây:",
"localesNotSaved_other": "Không thể lưu trữ các cài đặt vùng sau đây:",
"missingEmail": "Lỗi - Thiếu email.", "missingEmail": "Lỗi - Thiếu email.",
"missingIDOfDocument": "Lỗi - Thiếu ID của bản tài liệu cần cập nhật.", "missingIDOfDocument": "Lỗi - Thiếu ID của bản tài liệu cần cập nhật.",
"missingIDOfVersion": "Lỗi - Thiếu ID của phiên bản.", "missingIDOfVersion": "Lỗi - Thiếu ID của phiên bản.",
@@ -177,6 +179,8 @@
"deleting": "Đang xóa...", "deleting": "Đang xóa...",
"descending": "Xếp theo thứ tự giảm dần", "descending": "Xếp theo thứ tự giảm dần",
"deselectAllRows": "Bỏ chọn tất cả các hàng", "deselectAllRows": "Bỏ chọn tất cả các hàng",
"document": "Tài liệu",
"documents": "Tài liệu",
"duplicate": "Tạo bản sao", "duplicate": "Tạo bản sao",
"duplicateWithoutSaving": "Không lưu dữ liệu và tạo bản sao", "duplicateWithoutSaving": "Không lưu dữ liệu và tạo bản sao",
"edit": "Chỉnh sửa", "edit": "Chỉnh sửa",
@@ -369,4 +373,4 @@
"viewingVersions": "Xem những phiên bản của {{entityLabel}} {{documentTitle}}", "viewingVersions": "Xem những phiên bản của {{entityLabel}} {{documentTitle}}",
"viewingVersionsGlobal": "`Xem những phiên bản toàn thể (global) của {{entityLabel}}" "viewingVersionsGlobal": "`Xem những phiên bản toàn thể (global) của {{entityLabel}}"
} }
} }

View File

@@ -179,6 +179,8 @@
"deleting": "刪除中...", "deleting": "刪除中...",
"descending": "降冪", "descending": "降冪",
"deselectAllRows": "取消選擇全部", "deselectAllRows": "取消選擇全部",
"document": "文件",
"documents": "文件",
"duplicate": "複製", "duplicate": "複製",
"duplicateWithoutSaving": "複製而不儲存變更。", "duplicateWithoutSaving": "複製而不儲存變更。",
"edit": "編輯", "edit": "編輯",
@@ -317,8 +319,8 @@
"trueOrFalse": "該字串只能等於是或否。", "trueOrFalse": "該字串只能等於是或否。",
"validUploadID": "該字串不是有效的上傳ID。" "validUploadID": "該字串不是有效的上傳ID。"
}, },
"version": { "version": {
"aboutToPublishSelection": "您確定即將發佈所選的 {{label}} 嗎?", "aboutToPublishSelection": "您確定即將發佈所選的 {{label}} 嗎?",
"aboutToRestore": "您將把這個文件{{label}}回復到{{versionDate}}時的狀態", "aboutToRestore": "您將把這個文件{{label}}回復到{{versionDate}}時的狀態",
"aboutToRestoreGlobal": "您要將痊域的{{label}}回復到{{versionDate}}時的狀態", "aboutToRestoreGlobal": "您要將痊域的{{label}}回復到{{versionDate}}時的狀態",
"aboutToRevertToPublished": "您將要將這個文件的內容還原到它的發佈狀態。您確定嗎?", "aboutToRevertToPublished": "您將要將這個文件的內容還原到它的發佈狀態。您確定嗎?",
@@ -370,5 +372,5 @@
"viewingVersionGlobal": "正在查看全域{{entityLabel}}的版本", "viewingVersionGlobal": "正在查看全域{{entityLabel}}的版本",
"viewingVersions": "正在查看{{entityLabel}} {{documentTitle}}的版本", "viewingVersions": "正在查看{{entityLabel}} {{documentTitle}}的版本",
"viewingVersionsGlobal": "正在查看全域{{entityLabel}}的版本" "viewingVersionsGlobal": "正在查看全域{{entityLabel}}的版本"
} }
} }

View File

@@ -69,6 +69,8 @@
"invalidFileType": "无效的文件类型", "invalidFileType": "无效的文件类型",
"invalidFileTypeValue": "无效的文件类型: {{value}}", "invalidFileTypeValue": "无效的文件类型: {{value}}",
"loadingDocument": "加载ID为{{id}}的文件时出现了问题。", "loadingDocument": "加载ID为{{id}}的文件时出现了问题。",
"localesNotSaved_one": "无法保存以下区域设置:",
"localesNotSaved_other": "无法保存以下区域设置:",
"missingEmail": "缺少电子邮件。", "missingEmail": "缺少电子邮件。",
"missingIDOfDocument": "缺少需要更新的文档的ID。", "missingIDOfDocument": "缺少需要更新的文档的ID。",
"missingIDOfVersion": "缺少版本的ID。", "missingIDOfVersion": "缺少版本的ID。",
@@ -177,6 +179,8 @@
"deleting": "删除中...", "deleting": "删除中...",
"descending": "降序", "descending": "降序",
"deselectAllRows": "取消选择所有行", "deselectAllRows": "取消选择所有行",
"document": "文件",
"documents": "文件",
"duplicate": "重复", "duplicate": "重复",
"duplicateWithoutSaving": "重复而不保存更改。", "duplicateWithoutSaving": "重复而不保存更改。",
"edit": "编辑", "edit": "编辑",

View File

@@ -16,8 +16,8 @@ import type { FileData, FileToSave, ProbedImageSize } from './types'
import { FileUploadError, MissingFile } from '../errors' import { FileUploadError, MissingFile } from '../errors'
import canResizeImage from './canResizeImage' import canResizeImage from './canResizeImage'
import cropImage from './cropImage' import cropImage from './cropImage'
import getFileByPath from './getFileByPath'
import { getExternalFile } from './getExternalFile' import { getExternalFile } from './getExternalFile'
import getFileByPath from './getFileByPath'
import getImageSize from './getImageSize' import getImageSize from './getImageSize'
import getSafeFileName from './getSafeFilename' import getSafeFileName from './getSafeFilename'
import resizeAndTransformImageSizes from './imageResizer' import resizeAndTransformImageSizes from './imageResizer'
@@ -73,7 +73,11 @@ export const generateFileData = async <T>({
file = response as UploadedFile file = response as UploadedFile
overwriteExistingFiles = true overwriteExistingFiles = true
} else if (filename && url) { } else if (filename && url) {
file = (await getExternalFile({ req, data: data as FileData })) as UploadedFile file = (await getExternalFile({
data: data as FileData,
req,
uploadConfig: collectionConfig.upload,
})) as UploadedFile
overwriteExistingFiles = true overwriteExistingFiles = true
} }
} catch (err) { } catch (err) {

View File

@@ -1,14 +1,15 @@
import type { Request } from 'express' import type { Request } from 'express'
import type { File, FileData } from './types' import type { File, FileData, IncomingUploadType } from './types'
import { APIError } from '../errors' import { APIError } from '../errors'
type Args = { type Args = {
data: FileData data: FileData
req: Request req: Request
uploadConfig: IncomingUploadType
} }
export const getExternalFile = async ({ data, req }: Args): Promise<File> => { export const getExternalFile = async ({ data, req, uploadConfig }: Args): Promise<File> => {
const { filename, url } = data const { filename, url } = data
if (typeof url === 'string') { if (typeof url === 'string') {
@@ -20,10 +21,19 @@ export const getExternalFile = async ({ data, req }: Args): Promise<File> => {
const { default: fetch } = (await import('node-fetch')) as any const { default: fetch } = (await import('node-fetch')) as any
// Convert headers
const convertedHeaders: Record<string, string> = headersToObject(req.headers)
const headers = uploadConfig.externalFileHeaderFilter
? uploadConfig.externalFileHeaderFilter(convertedHeaders)
: {
cookie: req.headers['cookie'],
}
const res = await fetch(fileURL, { const res = await fetch(fileURL, {
credentials: 'include', credentials: 'include',
headers: { headers: {
...req.headers, headers,
}, },
method: 'GET', method: 'GET',
}) })
@@ -42,3 +52,16 @@ export const getExternalFile = async ({ data, req }: Args): Promise<File> => {
throw new APIError('Invalid file url', 400) throw new APIError('Invalid file url', 400)
} }
function headersToObject(headers) {
const headersObj = {}
headers.forEach((value, key) => {
// If the header value is an array, join its elements into a single string
if (Array.isArray(value)) {
headersObj[key] = value.join(', ')
} else {
headersObj[key] = value
}
})
return headersObj
}

View File

@@ -74,6 +74,12 @@ export type IncomingUploadType = {
adminThumbnail?: GetAdminThumbnail | string adminThumbnail?: GetAdminThumbnail | string
crop?: boolean crop?: boolean
disableLocalStorage?: boolean disableLocalStorage?: boolean
/**
* Accepts existing headers and can filter/modify them.
*
* Useful for adding custom headers to fetch from external providers.
*/
externalFileHeaderFilter?: (headers: Record<string, string>) => Record<string, string>
filesRequiredOnCreate?: boolean filesRequiredOnCreate?: boolean
focalPoint?: boolean focalPoint?: boolean
/** Options for original upload file only. For sizes, set each formatOptions individually. */ /** Options for original upload file only. For sizes, set each formatOptions individually. */

View File

@@ -93,6 +93,7 @@ export function fieldsToJSONSchema(
*/ */
interfaceNameDefinitions: Map<string, JSONSchema4>, interfaceNameDefinitions: Map<string, JSONSchema4>,
payload?: Payload, payload?: Payload,
config?: SanitizedConfig,
): { ): {
properties: { properties: {
[k: string]: JSONSchema4 [k: string]: JSONSchema4
@@ -155,6 +156,7 @@ export function fieldsToJSONSchema(
if (field.editor.outputSchema) { if (field.editor.outputSchema) {
fieldSchema = field.editor.outputSchema({ fieldSchema = field.editor.outputSchema({
collectionIDFieldTypes, collectionIDFieldTypes,
config: config || payload?.config,
field, field,
interfaceNameDefinitions, interfaceNameDefinitions,
isRequired, isRequired,
@@ -334,6 +336,7 @@ export function fieldsToJSONSchema(
block.fields, block.fields,
interfaceNameDefinitions, interfaceNameDefinitions,
payload, payload,
config,
) )
const blockSchema: JSONSchema4 = { const blockSchema: JSONSchema4 = {
@@ -374,6 +377,7 @@ export function fieldsToJSONSchema(
field.fields, field.fields,
interfaceNameDefinitions, interfaceNameDefinitions,
payload, payload,
config,
), ),
}, },
} }
@@ -395,6 +399,7 @@ export function fieldsToJSONSchema(
field.fields, field.fields,
interfaceNameDefinitions, interfaceNameDefinitions,
payload, payload,
config,
) )
Object.entries(childSchema.properties).forEach(([propName, propSchema]) => { Object.entries(childSchema.properties).forEach(([propName, propSchema]) => {
fieldSchemas.set(propName, propSchema) fieldSchemas.set(propName, propSchema)
@@ -412,6 +417,7 @@ export function fieldsToJSONSchema(
tab.fields, tab.fields,
interfaceNameDefinitions, interfaceNameDefinitions,
payload, payload,
config,
) )
if (tabHasName(tab)) { if (tabHasName(tab)) {
// could have interface // could have interface
@@ -442,6 +448,7 @@ export function fieldsToJSONSchema(
field.fields, field.fields,
interfaceNameDefinitions, interfaceNameDefinitions,
payload, payload,
config,
), ),
} }
@@ -522,7 +529,13 @@ export function entityToJSONSchema(
type: 'object', type: 'object',
additionalProperties: false, additionalProperties: false,
title, title,
...fieldsToJSONSchema(collectionIDFieldTypes, entity.fields, interfaceNameDefinitions, payload), ...fieldsToJSONSchema(
collectionIDFieldTypes,
entity.fields,
interfaceNameDefinitions,
payload,
config,
),
} }
} }

View File

@@ -3,6 +3,7 @@ import type { PayloadRequest } from '../exports/types'
import { getDataLoader } from '../collections/dataloader' import { getDataLoader } from '../collections/dataloader'
import { i18nInit } from '../translations/init' import { i18nInit } from '../translations/init'
import isolateObjectProperty from './isolateObjectProperty'
function getRequestContext( function getRequestContext(
req: PayloadRequest = { context: null } as PayloadRequest, req: PayloadRequest = { context: null } as PayloadRequest,
@@ -56,8 +57,10 @@ export const createLocalReq: CreateLocalReq = (
req.i18n = i18n req.i18n = i18n
req.t = i18n.t req.t = i18n.t
req.user = user || req?.user || null req.user = user || req?.user || null
req.collection = collection ? payload.collections?.[collection] : null
req.payloadDataLoader = req?.payloadDataLoader || getDataLoader(req) req.payloadDataLoader = req?.payloadDataLoader || getDataLoader(req)
req = isolateObjectProperty(req, 'collection')
req.collection = payload.collections[collection]
return req return req
} }

View File

@@ -0,0 +1,27 @@
import type { Payload } from '..'
import flattenFields from './flattenTopLevelFields'
type Args = {
collectionSlug: string
id: string
payload: Payload
}
export const sanitizeCollectionID = ({ id, collectionSlug, payload }: Args): number | string => {
let sanitizedID: number | string = id
const collection = payload.collections[collectionSlug]
// If default db ID type is a number, we should sanitize
let shouldSanitize = Boolean(payload.db.defaultIDType === 'number')
// UNLESS the custom ID for this collection is text.... then we leave it
const hasIdField = flattenFields(collection.config.fields).find((field) => field.name === 'id')
if (shouldSanitize && hasIdField) shouldSanitize = false
// If we still should sanitize, parse float
if (shouldSanitize) sanitizedID = parseFloat(sanitizedID)
return sanitizedID
}

View File

@@ -26,6 +26,8 @@ export const getLatestCollectionVersion = async <T extends TypeWithID = any>({
if (config.versions?.drafts) { if (config.versions?.drafts) {
const { docs } = await payload.db.findVersions<T>({ const { docs } = await payload.db.findVersions<T>({
collection: config.slug, collection: config.slug,
limit: 1,
pagination: false,
req, req,
sort: '-updatedAt', sort: '-updatedAt',
where: { parent: { equals: id } }, where: { parent: { equals: id } },

View File

@@ -153,7 +153,7 @@ export const saveVersion = async ({
createdVersion.updatedAt = result.updatedAt createdVersion.updatedAt = result.updatedAt
createdVersion = sanitizeInternalFields(createdVersion) createdVersion = sanitizeInternalFields(createdVersion)
createdVersion.id = id createdVersion.id = result.parent
return createdVersion return createdVersion
} }

View File

@@ -4,6 +4,9 @@ import fa from './fa.json'
import fr from './fr.json' import fr from './fr.json'
import nb from './nb.json' import nb from './nb.json'
import pl from './pl.json' import pl from './pl.json'
import ua from './ua.json'
import zh from './zh.json'
import zhTw from './zh-tw.json'
export default { export default {
en, en,
@@ -11,5 +14,8 @@ export default {
fa, fa,
fr, fr,
nb, nb,
pl, pl,
ua,
zh,
zhTw,
} }

View File

@@ -0,0 +1,22 @@
{
"$schema": "./translation-schema.json",
"plugin-seo": {
"almostThere": "Ще трошки",
"autoGenerate": "Згенерувати",
"bestPractices": "найкращі практики",
"characterCount": "{{current}}/{{minLength}}-{{maxLength}} символів, ",
"charactersLeftOver": "залишилось {{characters}} символів",
"charactersToGo": " на {{characters}} символів коротше",
"charactersTooMany": "на {{characters}} символів довше",
"checksPassing": "{{current}}/{{max}} перевірок пройдено",
"good": "Чудово",
"imageAutoGenerationTip": "Автоматична генерація використає зображення з головного блоку",
"lengthTipDescription": "Має бути від {{minLength}} до {{maxLength}} символів. Щоб дізнатися, як писати якісні метаописи — перегляньте ",
"lengthTipTitle": "Має бути від {{minLength}} до {{maxLength}} символів. Щоб дізнатися, як писати якісні метазаголовки — перегляньте ",
"noImage": "Немає зображення",
"preview": "Попередній перегляд",
"previewDescription": "Реальне відображення може відрізнятися в залежності від вмісту та релевантності пошуку.",
"tooLong": "Задовгий",
"tooShort": "Закороткий"
}
}

View File

@@ -0,0 +1,22 @@
{
"$schema": "./translation-schema.json",
"plugin-seo": {
"autoGenerate": "自動生成",
"imageAutoGenerationTip": "自動生成將擷取所選的主頁圖片。",
"bestPractices": "最佳實踐",
"lengthTipTitle": "網站標題應介於 {{minLength}} 到 {{maxLength}} 字之間。關於如何撰寫良好的網站標題,請參閱 ",
"lengthTipDescription": "網站描述應介於 {{minLength}} 到 {{maxLength}} 字元之間。關於如何撰寫良好的網站描述,請參閱 ",
"good": "良好",
"tooLong": "過長",
"tooShort": "過短",
"almostThere": "快將完成",
"characterCount": "{{current}}/{{minLength}}-{{maxLength}} 字,",
"charactersToGo": "還差 {{characters}} 字",
"charactersLeftOver": "剩下 {{characters}} 字",
"charactersTooMany": "超過 {{characters}} 字",
"noImage": "沒有圖片",
"checksPassing": "通過 {{current}}/{{max}} 項檢查",
"preview": "預覽",
"previewDescription": "實際結果可能因內容和搜尋相關性而不同。"
}
}

View File

@@ -0,0 +1,22 @@
{
"$schema": "./translation-schema.json",
"plugin-seo": {
"autoGenerate": "自动生成",
"imageAutoGenerationTip": "自动生成将提取所选的主页图片。",
"bestPractices": "最佳实践",
"lengthTipTitle": "网站标题应介于 {{minLength}} 到 {{maxLength}} 字之间。关于如何撰写良好的网站标题,请参阅 ",
"lengthTipDescription": "网站描述应介于 {{minLength}} 到 {{maxLength}} 字元之间。关于如何撰写良好的网站描述,请参阅 ",
"good": "良好",
"tooLong": "过长",
"tooShort": "过短",
"almostThere": "快将完成",
"characterCount": "{{current}}/{{minLength}}-{{maxLength}} 字,",
"charactersToGo": "还差 {{characters}} 字",
"charactersLeftOver": "剩下 {{characters}} 字",
"charactersTooMany": "超过 {{characters}} 字",
"noImage": "没有图片",
"checksPassing": "通过 {{current}}/{{max}} 项检查",
"preview": "预览",
"previewDescription": "实际结果可能因内容和搜索相关性而不同。"
}
}

View File

@@ -52,6 +52,7 @@ export const BlockComponent: React.FC<Props> = (props) => {
sanitizeFields({ sanitizeFields({
config: payloadConfig, config: payloadConfig,
fields: unsanitizedFormSchema, fields: unsanitizedFormSchema,
requireFieldLevelRichTextEditor: true,
validRelationships, validRelationships,
}), }),
blockFieldWrapperName, blockFieldWrapperName,

View File

@@ -1,4 +1,4 @@
import type { Block, BlockField } from 'payload/types' import type { Block, BlockField, FieldWithRichTextRequiredEditor } from 'payload/types'
import { baseBlockFields, sanitizeFields } from 'payload/config' import { baseBlockFields, sanitizeFields } from 'payload/config'
import { fieldsToJSONSchema, formatLabels, getTranslation } from 'payload/utilities' import { fieldsToJSONSchema, formatLabels, getTranslation } from 'payload/utilities'
@@ -12,8 +12,12 @@ import { INSERT_BLOCK_COMMAND } from './plugin/commands'
import { blockPopulationPromiseHOC } from './populationPromise' import { blockPopulationPromiseHOC } from './populationPromise'
import { blockValidationHOC } from './validate' import { blockValidationHOC } from './validate'
export type LexicalBlock = Omit<Block, 'fields'> & {
fields: FieldWithRichTextRequiredEditor[]
}
export type BlocksFeatureProps = { export type BlocksFeatureProps = {
blocks: Block[] blocks: LexicalBlock[]
} }
export const BlocksFeature = (props?: BlocksFeatureProps): FeatureProvider => { export const BlocksFeature = (props?: BlocksFeatureProps): FeatureProvider => {
@@ -23,7 +27,7 @@ export const BlocksFeature = (props?: BlocksFeatureProps): FeatureProvider => {
const blockCopy = cloneDeep(block) const blockCopy = cloneDeep(block)
return { return {
...blockCopy, ...blockCopy,
fields: blockCopy.fields.concat(baseBlockFields), fields: blockCopy.fields.concat(baseBlockFields as FieldWithRichTextRequiredEditor[]),
labels: !blockCopy.labels ? formatLabels(blockCopy.slug) : blockCopy.labels, labels: !blockCopy.labels ? formatLabels(blockCopy.slug) : blockCopy.labels,
} }
}) })
@@ -36,6 +40,7 @@ export const BlocksFeature = (props?: BlocksFeatureProps): FeatureProvider => {
generatedTypes: { generatedTypes: {
modifyOutputSchema: ({ modifyOutputSchema: ({
collectionIDFieldTypes, collectionIDFieldTypes,
config,
currentSchema, currentSchema,
field, field,
interfaceNameDefinitions, interfaceNameDefinitions,
@@ -46,15 +51,16 @@ export const BlocksFeature = (props?: BlocksFeatureProps): FeatureProvider => {
} }
// sanitize blocks // sanitize blocks
const validRelationships = payload.config.collections.map((c) => c.slug) || [] const validRelationships = config?.collections?.map((c) => c.slug) || []
const sanitizedBlocks = props.blocks.map((block) => { const sanitizedBlocks = props.blocks.map((block) => {
const blockCopy = cloneDeep(block) const blockCopy = cloneDeep(block)
return { return {
...blockCopy, ...blockCopy,
fields: sanitizeFields({ fields: sanitizeFields({
config: payload.config, config,
fields: blockCopy.fields, fields: blockCopy.fields,
requireFieldLevelRichTextEditor: true,
validRelationships, validRelationships,
}), }),
} }
@@ -73,6 +79,7 @@ export const BlocksFeature = (props?: BlocksFeatureProps): FeatureProvider => {
[blocksField], [blocksField],
interfaceNameDefinitions, interfaceNameDefinitions,
payload, payload,
config,
) )
return currentSchema return currentSchema

View File

@@ -37,6 +37,7 @@ export const blockPopulationPromiseHOC = (
block.fields = sanitizeFields({ block.fields = sanitizeFields({
config: payloadConfig, config: payloadConfig,
fields: block.fields, fields: block.fields,
requireFieldLevelRichTextEditor: true,
validRelationships, validRelationships,
}) })
}) })

View File

@@ -27,6 +27,7 @@ export const blockValidationHOC = (
block.fields = sanitizeFields({ block.fields = sanitizeFields({
config: payloadConfig, config: payloadConfig,
fields: block.fields, fields: block.fields,
requireFieldLevelRichTextEditor: true,
validRelationships, validRelationships,
}) })
}) })

View File

@@ -1,6 +1,6 @@
import type { i18n } from 'i18next' import type { i18n } from 'i18next'
import type { SanitizedConfig } from 'payload/config' import type { SanitizedConfig } from 'payload/config'
import type { Field } from 'payload/types' import type { FieldWithRichTextRequiredEditor } from 'payload/types'
import { $findMatchingParent } from '@lexical/utils' import { $findMatchingParent } from '@lexical/utils'
import { $getSelection, $isRangeSelection } from 'lexical' import { $getSelection, $isRangeSelection } from 'lexical'
@@ -46,8 +46,12 @@ export type LinkFeatureProps = ExclusiveLinkCollectionsProps & {
* displayed in the link editor drawer. * displayed in the link editor drawer.
*/ */
fields?: fields?:
| ((args: { config: SanitizedConfig; defaultFields: Field[]; i18n: i18n }) => Field[]) | ((args: {
| Field[] config: SanitizedConfig
defaultFields: FieldWithRichTextRequiredEditor[]
i18n: i18n
}) => FieldWithRichTextRequiredEditor[])
| FieldWithRichTextRequiredEditor[]
} }
export const LinkFeature = (props: LinkFeatureProps): FeatureProvider => { export const LinkFeature = (props: LinkFeatureProps): FeatureProvider => {
@@ -99,6 +103,7 @@ export const LinkFeature = (props: LinkFeatureProps): FeatureProvider => {
}, },
nodes: [ nodes: [
{ {
type: LinkNode.getType(),
converters: { converters: {
html: { html: {
converter: async ({ converters, node, parent }) => { converter: async ({ converters, node, parent }) => {
@@ -125,10 +130,10 @@ export const LinkFeature = (props: LinkFeatureProps): FeatureProvider => {
}, },
node: LinkNode, node: LinkNode,
populationPromises: [linkPopulationPromiseHOC(props)], populationPromises: [linkPopulationPromiseHOC(props)],
type: LinkNode.getType(),
// TODO: Add validation similar to upload for internal links and fields // TODO: Add validation similar to upload for internal links and fields
}, },
{ {
type: AutoLinkNode.getType(),
converters: { converters: {
html: { html: {
converter: async ({ converters, node, parent }) => { converter: async ({ converters, node, parent }) => {
@@ -158,7 +163,6 @@ export const LinkFeature = (props: LinkFeatureProps): FeatureProvider => {
}, },
node: AutoLinkNode, node: AutoLinkNode,
populationPromises: [linkPopulationPromiseHOC(props)], populationPromises: [linkPopulationPromiseHOC(props)],
type: AutoLinkNode.getType(),
}, },
], ],
plugins: [ plugins: [

View File

@@ -77,6 +77,7 @@ export function LinkEditor({
const fields = sanitizeFields({ const fields = sanitizeFields({
config, config,
fields: fieldsUnsanitized, fields: fieldsUnsanitized,
requireFieldLevelRichTextEditor: true,
validRelationships, validRelationships,
}) })

View File

@@ -57,8 +57,9 @@ export const ExtraFieldsUploadDrawer: React.FC<
// Sanitize custom fields here // Sanitize custom fields here
const validRelationships = config.collections.map((c) => c.slug) || [] const validRelationships = config.collections.map((c) => c.slug) || []
const fieldSchema = sanitizeFields({ const fieldSchema = sanitizeFields({
config: config, config,
fields: fieldSchemaUnsanitized, fields: fieldSchemaUnsanitized,
requireFieldLevelRichTextEditor: true,
validRelationships, validRelationships,
}) })
@@ -85,8 +86,9 @@ export const ExtraFieldsUploadDrawer: React.FC<
// Sanitize custom fields here // Sanitize custom fields here
const validRelationships = config.collections.map((c) => c.slug) || [] const validRelationships = config.collections.map((c) => c.slug) || []
const fieldSchema = sanitizeFields({ const fieldSchema = sanitizeFields({
config: config, config,
fields: fieldSchemaUnsanitized, fields: fieldSchemaUnsanitized,
requireFieldLevelRichTextEditor: true,
validRelationships, validRelationships,
}) })

View File

@@ -1,4 +1,4 @@
import type { Field } from 'payload/types' import type { FieldWithRichTextRequiredEditor } from 'payload/types'
import payload from 'payload' import payload from 'payload'
@@ -15,7 +15,7 @@ import { uploadValidation } from './validate'
export type UploadFeatureProps = { export type UploadFeatureProps = {
collections: { collections: {
[collection: string]: { [collection: string]: {
fields: Field[] fields: FieldWithRichTextRequiredEditor[]
} }
} }
} }
@@ -33,13 +33,26 @@ export const UploadFeature = (props?: UploadFeatureProps): FeatureProvider => {
return { return {
nodes: [ nodes: [
{ {
type: UploadNode.getType(),
converters: { converters: {
html: { html: {
converter: async ({ node }) => { converter: async ({ node }) => {
const uploadDocument: any = await payload.findByID({ let uploadDocument: any
id: node.value.id, try {
collection: node.relationTo, uploadDocument = await payload.findByID({
}) id: node.value.id,
collection: node.relationTo,
})
} catch (ignored) {
// eslint-disable-next-line no-console
console.error(
'Lexical upload node HTML converter: error fetching upload file',
ignored,
'Node:',
node,
)
return `<img />`
}
const url: string = getAbsoluteURL(uploadDocument?.url as string) const url: string = getAbsoluteURL(uploadDocument?.url as string)
/** /**
@@ -92,7 +105,6 @@ export const UploadFeature = (props?: UploadFeatureProps): FeatureProvider => {
}, },
node: UploadNode, node: UploadNode,
populationPromises: [uploadPopulationPromiseHOC(props)], populationPromises: [uploadPopulationPromiseHOC(props)],
type: UploadNode.getType(),
validations: [uploadValidation()], validations: [uploadValidation()],
}, },
], ],
@@ -104,7 +116,7 @@ export const UploadFeature = (props?: UploadFeatureProps): FeatureProvider => {
position: 'normal', position: 'normal',
}, },
], ],
props: props, props,
slashMenu: { slashMenu: {
options: [ options: [
{ {

View File

@@ -35,18 +35,23 @@ export async function convertLexicalNodesToHTML({
const converterForNode = converters.find((converter) => const converterForNode = converters.find((converter) =>
converter.nodeTypes.includes(node.type), converter.nodeTypes.includes(node.type),
) )
if (!converterForNode) { try {
if (unknownConverter) { if (!converterForNode) {
return unknownConverter.converter({ childIndex: i, converters, node, parent }) if (unknownConverter) {
return await unknownConverter.converter({ childIndex: i, converters, node, parent })
}
return '<span>unknown node</span>'
} }
return '<span>unknown node</span>' return converterForNode.converter({
childIndex: i,
converters,
node,
parent,
})
} catch (error) {
console.error('Error converting lexical node to HTML:', error, 'node:', node)
return ''
} }
return converterForNode.converter({
childIndex: i,
converters,
node,
parent,
})
}), }),
) )

View File

@@ -0,0 +1,75 @@
import type { NodeKey } from 'lexical'
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'
import { useLexicalNodeSelection } from '@lexical/react/useLexicalNodeSelection'
import { mergeRegister } from '@lexical/utils'
import {
$getNodeByKey,
$getSelection,
$isNodeSelection,
CLICK_COMMAND,
COMMAND_PRIORITY_LOW,
KEY_BACKSPACE_COMMAND,
KEY_DELETE_COMMAND,
} from 'lexical'
import { useCallback, useEffect } from 'react'
import { $isHorizontalRuleNode } from '../nodes/HorizontalRuleNode'
/**
* React component rendered in the lexical editor, WITHIN the hr element created by createDOM of the HorizontalRuleNode.
*
* @param nodeKey every node has a unique key (this key is not saved to the database and thus may differ between sessions). It's useful for working with the CURRENT lexical editor state
*/
export function HorizontalRuleComponent({ nodeKey }: { nodeKey: NodeKey }) {
const [editor] = useLexicalComposerContext()
const [isSelected, setSelected, clearSelection] = useLexicalNodeSelection(nodeKey)
const onDelete = useCallback(
(event: KeyboardEvent) => {
if (isSelected && $isNodeSelection($getSelection())) {
event.preventDefault()
const node = $getNodeByKey(nodeKey)
if ($isHorizontalRuleNode(node)) {
node.remove()
return true
}
}
return false
},
[isSelected, nodeKey],
)
useEffect(() => {
return mergeRegister(
editor.registerCommand(
CLICK_COMMAND,
(event: MouseEvent) => {
const hrElem = editor.getElementByKey(nodeKey)
if (event.target === hrElem) {
if (!event.shiftKey) {
clearSelection()
}
setSelected(!isSelected)
return true
}
return false
},
COMMAND_PRIORITY_LOW,
),
editor.registerCommand(KEY_DELETE_COMMAND, onDelete, COMMAND_PRIORITY_LOW),
editor.registerCommand(KEY_BACKSPACE_COMMAND, onDelete, COMMAND_PRIORITY_LOW),
)
}, [clearSelection, editor, isSelected, nodeKey, onDelete, setSelected])
useEffect(() => {
const hrElem = editor.getElementByKey(nodeKey)
if (hrElem !== null) {
hrElem.className = isSelected ? 'selected' : ''
}
}, [editor, isSelected, nodeKey])
return null
}

View File

@@ -0,0 +1,74 @@
import type { HTMLConverter } from '../converters/html/converter/types'
import type { FeatureProvider } from '../types'
import type { SerializedHorizontalRuleNode } from './nodes/HorizontalRuleNode'
import { SlashMenuOption } from '../../lexical/plugins/SlashMenu/LexicalTypeaheadMenuPlugin/types'
import { HorizontalRuleNode, INSERT_HORIZONTAL_RULE_COMMAND } from './nodes/HorizontalRuleNode'
export const HorizontalRuleFeature = (): FeatureProvider => {
return {
feature: () => {
return {
/**
* Define the nodes that this feature provides. Nodes define how the node is rendered in the lexical editor, what data is saved, and other lexical node behavior.
*/
nodes: [
{
type: HorizontalRuleNode.getType(),
converters: {
html: {
converter: () => {
return `<hr/>`
},
nodeTypes: [HorizontalRuleNode.getType()],
} as HTMLConverter<SerializedHorizontalRuleNode>,
},
node: HorizontalRuleNode,
},
],
/**
* Plugins are React components which can interact with the lexical API. This one specifically registers the INSERT_HORIZONTAL_RULE_COMMAND command and defines the behavior for when it is called.
*/
plugins: [
{
Component: () =>
// @ts-expect-error
import('./plugin').then((module) => module.HorizontalRulePlugin),
position: 'normal',
},
],
props: null,
/**
* Add a new slash menu option for inserting a horizontal rule. When running /hr in the editor, the user will be able to insert a horizontal rule. This runs the onSelect function when the option is selected.
* Inside onSelect, we run the INSERT_HORIZONTAL_RULE_COMMAND command whose behavior is defined in the plugin.
*/
slashMenu: {
options: [
{
displayName: 'Basic',
key: 'basic',
options: [
new SlashMenuOption(`horizontalrule`, {
Icon: () =>
// @ts-expect-error
import('../../lexical/ui/icons/HorizontalRule').then(
(module) => module.HorizontalRuleIcon,
),
displayName: `Horizontal Rule`,
keywords: ['hr', 'horizontal rule', 'line', 'separator'],
onSelect: ({ editor }) => {
editor.dispatchCommand(INSERT_HORIZONTAL_RULE_COMMAND, undefined)
},
}),
],
},
],
},
}
},
/**
* Every feature needs a unique key
*/
key: 'horizontalrule',
}
}

View File

@@ -0,0 +1,123 @@
import type {
DOMConversionMap,
DOMConversionOutput,
DOMExportOutput,
LexicalCommand,
LexicalNode,
SerializedLexicalNode,
} from 'lexical'
import { $applyNodeReplacement, DecoratorNode, createCommand } from 'lexical'
import * as React from 'react'
const HorizontalRuleComponent = React.lazy(() =>
// @ts-expect-error TypeScript being dumb
import('../component').then((module) => ({
default: module.HorizontalRuleComponent,
})),
)
/**
* Serialized representation of a horizontal rule node. Serialized = converted to JSON. This is what is stored in the database / in the lexical editor state.
*/
export type SerializedHorizontalRuleNode = SerializedLexicalNode
export const INSERT_HORIZONTAL_RULE_COMMAND: LexicalCommand<void> = createCommand(
'INSERT_HORIZONTAL_RULE_COMMAND',
)
/**
* This node is a DecoratorNode. DecoratorNodes allow you to render React components in the editor.
*
* They need both createDom and decorate functions. createDom => outside of the html. decorate => React Component inside of the html.
*
* If we used DecoratorBlockNode instead, we would only need a decorate method
*/
export class HorizontalRuleNode extends DecoratorNode<React.ReactElement> {
static clone(node: HorizontalRuleNode): HorizontalRuleNode {
return new HorizontalRuleNode(node.__key)
}
static getType(): string {
return 'horizontalrule'
}
/**
* Defines what happens if you copy an hr element from another page and paste it into the lexical editor
*
* This also determines the behavior of lexical's internal HTML -> Lexical converter
*/
static importDOM(): DOMConversionMap | null {
return {
hr: () => ({
conversion: convertHorizontalRuleElement,
priority: 0,
}),
}
}
/**
* The data for this node is stored serialized as JSON. This is the "load function" of that node: it takes the saved data and converts it into a node.
*/
static importJSON(serializedNode: SerializedHorizontalRuleNode): HorizontalRuleNode {
return $createHorizontalRuleNode()
}
/**
* Determines how the hr element is rendered in the lexical editor. This is only the "initial" / "outer" HTML element.
*/
createDOM(): HTMLElement {
return document.createElement('hr')
}
/**
* Allows you to render a React component within whatever createDOM returns.
*/
decorate(): React.ReactElement {
return <HorizontalRuleComponent nodeKey={this.__key} />
}
/**
* Opposite of importDOM, this function defines what happens when you copy an hr element from the lexical editor and paste it into another page.
*
* This also determines the behavior of lexical's internal Lexical -> HTML converter
*/
exportDOM(): DOMExportOutput {
return { element: document.createElement('hr') }
}
/**
* Opposite of importJSON. This determines what data is saved in the database / in the lexical editor state.
*/
exportJSON(): SerializedLexicalNode {
return {
type: 'horizontalrule',
version: 1,
}
}
getTextContent(): string {
return '\n'
}
isInline(): false {
return false
}
updateDOM(): boolean {
return false
}
}
function convertHorizontalRuleElement(): DOMConversionOutput {
return { node: $createHorizontalRuleNode() }
}
export function $createHorizontalRuleNode(): HorizontalRuleNode {
return $applyNodeReplacement(new HorizontalRuleNode())
}
export function $isHorizontalRuleNode(
node: LexicalNode | null | undefined,
): node is HorizontalRuleNode {
return node instanceof HorizontalRuleNode
}

View File

@@ -0,0 +1,20 @@
@import 'payload/scss';
hr {
padding: 2px 2px;
border: none;
margin: 1rem 0;
cursor: pointer;
}
hr:after {
content: '';
display: block;
height: 2px;
background-color: var(--theme-elevation-250);
}
hr.selected {
outline: 2px solid var(--theme-success-500);
user-select: none;
}

View File

@@ -0,0 +1,44 @@
'use client'
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'
import { $insertNodeToNearestRoot } from '@lexical/utils'
import { $getSelection, $isRangeSelection, COMMAND_PRIORITY_EDITOR } from 'lexical'
import { useEffect } from 'react'
import {
$createHorizontalRuleNode,
INSERT_HORIZONTAL_RULE_COMMAND,
} from '../nodes/HorizontalRuleNode'
import './index.scss'
/**
* Registers the INSERT_HORIZONTAL_RULE_COMMAND lexical command and defines the behavior for when it is called.
*/
export function HorizontalRulePlugin(): null {
const [editor] = useLexicalComposerContext()
useEffect(() => {
return editor.registerCommand(
INSERT_HORIZONTAL_RULE_COMMAND,
(type) => {
const selection = $getSelection()
if (!$isRangeSelection(selection)) {
return false
}
const focusNode = selection.focus.getNode()
if (focusNode !== null) {
const horizontalRuleNode = $createHorizontalRuleNode()
$insertNodeToNearestRoot(horizontalRuleNode)
}
return true
},
COMMAND_PRIORITY_EDITOR,
)
}, [editor])
return null
}

View File

@@ -69,6 +69,7 @@ export type Feature = {
generatedTypes?: { generatedTypes?: {
modifyOutputSchema: ({ modifyOutputSchema: ({
collectionIDFieldTypes, collectionIDFieldTypes,
config,
currentSchema, currentSchema,
field, field,
interfaceNameDefinitions, interfaceNameDefinitions,
@@ -76,6 +77,7 @@ export type Feature = {
payload, payload,
}: { }: {
collectionIDFieldTypes: { [key: string]: 'number' | 'string' } collectionIDFieldTypes: { [key: string]: 'number' | 'string' }
config?: SanitizedConfig
/** /**
* Current schema which will be modified by this function. * Current schema which will be modified by this function.
*/ */
@@ -228,6 +230,7 @@ export type SanitizedFeatures = Required<
modifyOutputSchemas: Array< modifyOutputSchemas: Array<
({ ({
collectionIDFieldTypes, collectionIDFieldTypes,
config,
currentSchema, currentSchema,
field, field,
interfaceNameDefinitions, interfaceNameDefinitions,
@@ -235,6 +238,7 @@ export type SanitizedFeatures = Required<
payload, payload,
}: { }: {
collectionIDFieldTypes: { [key: string]: 'number' | 'string' } collectionIDFieldTypes: { [key: string]: 'number' | 'string' }
config?: SanitizedConfig
/** /**
* Current schema which will be modified by this function. * Current schema which will be modified by this function.
*/ */

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