Compare commits
81 Commits
plugin-nes
...
db-postgre
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8f5867e876 | ||
|
|
45a3e31c95 | ||
|
|
176550d26b | ||
|
|
53958d4662 | ||
|
|
a0859114eb | ||
|
|
4adc30b034 | ||
|
|
ff61d5a099 | ||
|
|
9cc88bb474 | ||
|
|
57fc211674 | ||
|
|
9da9b1fc50 | ||
|
|
30d050ef86 | ||
|
|
9beb9c8627 | ||
|
|
224cddd045 | ||
|
|
3502ce720b | ||
|
|
b8fa61942e | ||
|
|
d49bb4351f | ||
|
|
542096361f | ||
|
|
66679fbdd6 | ||
|
|
d4f28b16b4 | ||
|
|
cd07873fc5 | ||
|
|
6d28fc46bd | ||
|
|
8e903053e2 | ||
|
|
7e1052fd98 | ||
|
|
b4af95f894 | ||
|
|
381c158b03 | ||
|
|
3514bfbdae | ||
|
|
4cfe473627 | ||
|
|
c1eb9d1727 | ||
|
|
37b765cce8 | ||
|
|
5bf64933b4 | ||
|
|
094d02ce1d | ||
|
|
1fe4f4c5f4 | ||
|
|
35ce0ebc83 | ||
|
|
530c825f80 | ||
|
|
308979f31d | ||
|
|
2122242192 | ||
|
|
40c8909ee0 | ||
|
|
babe3dba6a | ||
|
|
9bb7a88526 | ||
|
|
098e389147 | ||
|
|
b56f1f4f2a | ||
|
|
443847ec71 | ||
|
|
26f6b37a20 | ||
|
|
1dcd3a2782 | ||
|
|
6f59257574 | ||
|
|
65575d3573 | ||
|
|
cbeb0a8bc7 | ||
|
|
ad62db01e7 | ||
|
|
42cba2e3a1 | ||
|
|
1401718b3b | ||
|
|
712647d741 | ||
|
|
c8d2b2b60e | ||
|
|
aab2407112 | ||
|
|
d439bf3011 | ||
|
|
62ca71fbc4 | ||
|
|
e50fa9ca8f | ||
|
|
ed7aca6525 | ||
|
|
98ccd05dd6 | ||
|
|
051bced3b5 | ||
|
|
79f08baf2f | ||
|
|
d6b63da617 | ||
|
|
d512e9382d | ||
|
|
b17cafc7be | ||
|
|
24dacd6712 | ||
|
|
6ea29094ba | ||
|
|
f27407ce7c | ||
|
|
9ae65fa791 | ||
|
|
3d2b62b210 | ||
|
|
6364afb1dd | ||
|
|
56a4692662 | ||
|
|
ef6b8e4235 | ||
|
|
5f5290341a | ||
|
|
62403584ad | ||
|
|
19fcfc27af | ||
|
|
dcf14f5f71 | ||
|
|
3a784a06cc | ||
|
|
6eeae9d53b | ||
|
|
6044f810bd | ||
|
|
e68ca9363f | ||
|
|
8a7b41721a | ||
|
|
0672e864f3 |
4
.github/PULL_REQUEST_TEMPLATE.md
vendored
4
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -12,8 +12,8 @@
|
||||
- [ ] Bug fix (non-breaking change which fixes an issue)
|
||||
- [ ] New feature (non-breaking change which adds functionality)
|
||||
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
|
||||
- [ ] Change to the [templates](../templates/) directory (does not affect core functionality)
|
||||
- [ ] Change to the [examples](../examples/) directory (does not affect core functionality)
|
||||
- [ ] Change to the [templates](https://github.com/payloadcms/payload/tree/main/templates) directory (does not affect core functionality)
|
||||
- [ ] Change to the [examples](https://github.com/payloadcms/payload/tree/main/examples) directory (does not affect core functionality)
|
||||
- [ ] This change requires a documentation update
|
||||
|
||||
## Checklist:
|
||||
|
||||
71
CHANGELOG.md
71
CHANGELOG.md
@@ -1,3 +1,74 @@
|
||||
## [2.3.0](https://github.com/payloadcms/payload/compare/v2.2.2...v2.3.0) (2023-11-30)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add serbian (latin and cyrillic) translations ([#4268](https://github.com/payloadcms/payload/issues/4268)) ([40c8909](https://github.com/payloadcms/payload/commit/40c8909ee0003d45a1afa4524ade557268d01067))
|
||||
* **db-mongodb:** search for migrations dir intelligently ([530c825](https://github.com/payloadcms/payload/commit/530c825f806708df8672e66c8e9c559c5e625e5e))
|
||||
* **db-postgres:** search for migrations dir intelligently ([308979f](https://github.com/payloadcms/payload/commit/308979f31d27979955a52f32be4ea33849b48f30))
|
||||
* **live-preview:** batches api requests ([#4315](https://github.com/payloadcms/payload/issues/4315)) ([d49bb43](https://github.com/payloadcms/payload/commit/d49bb4351f22f17f68477c3145594abbb60fab05))
|
||||
* relationship sortOptions property ([#4301](https://github.com/payloadcms/payload/issues/4301)) ([224cddd](https://github.com/payloadcms/payload/commit/224cddd04573eff578ea3fa9ea5419f28b66c613))
|
||||
* support migrations with js files ([2122242](https://github.com/payloadcms/payload/commit/21222421929ae19728c31bdccc84995ce3951224))
|
||||
* support OAuth 2.0 format Authorization: Bearer tokens in headers ([c1eb9d1](https://github.com/payloadcms/payload/commit/c1eb9d1727daf96375e73943882621127b78e593))
|
||||
* useDocumentEvents ([#4284](https://github.com/payloadcms/payload/issues/4284)) ([9bb7a88](https://github.com/payloadcms/payload/commit/9bb7a88526569a726de468de6b2010d52169ea77))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **db-postgres:** allow for nested block fields to be queried ([#4237](https://github.com/payloadcms/payload/issues/4237)) ([cd07873](https://github.com/payloadcms/payload/commit/cd07873fc544766b4aeeff873dfb8d6e3e97e9dc))
|
||||
* **db-postgres:** error saving nested arrays with versions ([#4302](https://github.com/payloadcms/payload/issues/4302)) ([3514bfb](https://github.com/payloadcms/payload/commit/3514bfbdaee99341ae739d03591cb63bd9415fe3))
|
||||
* duplicate documents with required localized fields ([#4236](https://github.com/payloadcms/payload/issues/4236)) ([9da9b1f](https://github.com/payloadcms/payload/commit/9da9b1fc5050d4f29bcf6dce2f22027834aaf698))
|
||||
* incorrect key property in Tabs field component ([#4311](https://github.com/payloadcms/payload/issues/4311)) ([3502ce7](https://github.com/payloadcms/payload/commit/3502ce720b3020eed5fc733884b525303faa4c15)), closes [#4282](https://github.com/payloadcms/payload/issues/4282)
|
||||
* **live-preview-react:** removes payload from peer deps ([7e1052f](https://github.com/payloadcms/payload/commit/7e1052fd98c88a4d68af08f98ccc8936edb8ebf6))
|
||||
* **live-preview:** compounds merge results ([#4306](https://github.com/payloadcms/payload/issues/4306)) ([381c158](https://github.com/payloadcms/payload/commit/381c158b0303b515164ae487b0ce7e555ae1a08d))
|
||||
* **live-preview:** property resets rte nodes ([#4313](https://github.com/payloadcms/payload/issues/4313)) ([66679fb](https://github.com/payloadcms/payload/commit/66679fbdd6f804bff8a58d9504c226c9fb8a57a0))
|
||||
* **live-preview:** re-populates externally updated relationships ([#4287](https://github.com/payloadcms/payload/issues/4287)) ([57fc211](https://github.com/payloadcms/payload/commit/57fc2116749059bc55161897cf139031926035ec))
|
||||
* **live-preview:** removes payload from peer deps ([b4af95f](https://github.com/payloadcms/payload/commit/b4af95f894b5f6614bace38ef79e7148e084bd3b))
|
||||
* properly exports useDocumentsEvents hook ([#4314](https://github.com/payloadcms/payload/issues/4314)) ([5420963](https://github.com/payloadcms/payload/commit/542096361f0c13aed9c6a7d971e2c47047d8e2d2))
|
||||
* properly sets tabs key in fieldSchemaToJSON ([#4317](https://github.com/payloadcms/payload/issues/4317)) ([9cc88bb](https://github.com/payloadcms/payload/commit/9cc88bb47443ecdf525f4c99d9f13d81c141c471))
|
||||
* **richtext-lexical:** HTML Converter field not working inside of tabs ([#4293](https://github.com/payloadcms/payload/issues/4293)) ([5bf6493](https://github.com/payloadcms/payload/commit/5bf64933b4b99a0ac8ef7d1d91d0165a16636a9f))
|
||||
* **richtext-lexical:** re-use payload population logic to fix population-related issues ([#4291](https://github.com/payloadcms/payload/issues/4291)) ([094d02c](https://github.com/payloadcms/payload/commit/094d02ce1d85106470a1a8c6ffe9050873f2e57a))
|
||||
* **richtext-slate:** add use client to top of tsx files importing from payload core ([#4312](https://github.com/payloadcms/payload/issues/4312)) ([d4f28b1](https://github.com/payloadcms/payload/commit/d4f28b16b4d42f224e9c5e4254f9ec55107a2f97))
|
||||
|
||||
|
||||
### BREAKING CHANGES
|
||||
|
||||
### ⚠️ @payloadcms/richtext-lexical
|
||||
|
||||
The `SlashMenuGroup` and `SlashMenuOption` classes have changed. If you have any custom lexical Features which are adding new slash menu entries, this will be a breaking change for you. If not, no action is required from your side.
|
||||
|
||||
Here are the breaking changes and how to migrate:
|
||||
|
||||
1. The `SlashMenuOption`'s first argument is now used as a `key` and not as a display name. Additionally, a new, optional `displayName` property is added which will serve as the display name. Make sure your `key` does not contain any spaces or special characters.
|
||||
2. The `title` property of `SlashMenuGroup` has been replaced by a new, mandatory `key` and an optional `displayName` property. To migrate, you will have to remove the `title` property and add a `key` property instead - make sure you do not use spaces or special characters in the `key`.
|
||||
3. Additionally, if you have custom styles targeting elements inside of slash or floating-select-toolbar menus, you will have to adjust those too, as the CSS classes changed
|
||||
|
||||
[This is an example of performing these updates](
|
||||
https://github.com/payloadcms/payload/pull/4257/files#diff-dc2e7f503dd7076dff1d810da7ec77b8fc6a9e41127df4a417dece1b6e1587a0L61)
|
||||
|
||||
## [2.2.2](https://github.com/payloadcms/payload/compare/v2.2.1...v2.2.2) (2023-11-27)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **i18n:** adds Korean translation ([#4258](https://github.com/payloadcms/payload/issues/4258)) ([1401718](https://github.com/payloadcms/payload/commit/1401718b3b549ce1454389a982474dbe159eb61f))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* number field validation ([#4233](https://github.com/payloadcms/payload/issues/4233)) ([19fcfc2](https://github.com/payloadcms/payload/commit/19fcfc27af2ecb68ff989dcaed19b7b7d041a322))
|
||||
* passes date options to the react-datepicker in filter UI, removes duplicate options from operators select ([#4225](https://github.com/payloadcms/payload/issues/4225)) ([3d2b62b](https://github.com/payloadcms/payload/commit/3d2b62b2100e36a54adc6a675257a4d671fdd469))
|
||||
* prevent json data getting reset when switching tabs ([#4123](https://github.com/payloadcms/payload/issues/4123)) ([1dcd3a2](https://github.com/payloadcms/payload/commit/1dcd3a27825ed9d276b997a66f84bb2c05e87955))
|
||||
* transactions broken within doc access ([443847e](https://github.com/payloadcms/payload/commit/443847ec716a3b87032d9d1904b6c90aadd47197))
|
||||
* typo in polish translations ([#4234](https://github.com/payloadcms/payload/issues/4234)) ([56a4692](https://github.com/payloadcms/payload/commit/56a469266207ef83053b0c9176d1be4fc26087e6))
|
||||
|
||||
## [2.2.1](https://github.com/payloadcms/payload/compare/v2.2.0...v2.2.1) (2023-11-21)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* make outputSchema optional on richtext config ([#4230](https://github.com/payloadcms/payload/issues/4230)) ([3a784a0](https://github.com/payloadcms/payload/commit/3a784a06cc6c42c96b8d6cf023d942e6661be7b5))
|
||||
|
||||
## [2.2.0](https://github.com/payloadcms/payload/compare/v2.1.1...v2.2.0) (2023-11-20)
|
||||
|
||||
|
||||
|
||||
@@ -172,20 +172,34 @@ export default buildConfig({
|
||||
collections: [Subscriptions],
|
||||
admin: {
|
||||
bundler: viteBundler(),
|
||||
vite: (config) => {
|
||||
return {
|
||||
...config,
|
||||
resolve: {
|
||||
...config.resolve,
|
||||
// highlight-start
|
||||
alias: {
|
||||
...config.resolve.alias,
|
||||
// remember, vite aliases are exact-match only
|
||||
'./hooks/createStripeSubscription': mockModulePath,
|
||||
},
|
||||
// highlight-end
|
||||
},
|
||||
vite: (incomingViteConfig) => {
|
||||
const existingAliases = incomingViteConfig?.resolve?.alias || {};
|
||||
let aliasArray: { find: string | RegExp; replacement: string; }[] = [];
|
||||
|
||||
// Pass the existing Vite aliases
|
||||
if (Array.isArray(existingAliases)) {
|
||||
aliasArray = existingAliases;
|
||||
} else {
|
||||
aliasArray = Object.values(existingAliases);
|
||||
}
|
||||
|
||||
|
||||
// highlight-start
|
||||
// Add your own aliases using the find and replacement keys
|
||||
// remember, vite aliases are exact-match only
|
||||
aliasArray.push({
|
||||
find: '../server-only-module',
|
||||
replacement: path.resolve(__dirname, './path/to/browser-safe-module.js')
|
||||
});
|
||||
// highlight-end
|
||||
|
||||
return {
|
||||
...incomingViteConfig,
|
||||
resolve: {
|
||||
...(incomingViteConfig?.resolve || {}),
|
||||
alias: aliasArray,
|
||||
}
|
||||
};
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
@@ -784,3 +784,33 @@ const MyComponent: React.FC = () => {
|
||||
</button>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### useDocumentEvents
|
||||
|
||||
The `useDocumentEvents` hook provides a way of subscribing to cross-document events, such as updates made to nested documents within a drawer. This hook will report document events that are outside the scope of the document currently being edited. This hook provides the following:
|
||||
|
||||
| Property | Description |
|
||||
|---------------------------|-------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| **`mostRecentUpdate`** | An object containing the most recently updated document. It contains the `entitySlug`, `id` (if collection), and `updatedAt` properties |
|
||||
| **`reportUpdate`** | A method used to report updates to documents. It accepts the same arguments as the `mostRecentUpdate` property. |
|
||||
|
||||
**Example:**
|
||||
|
||||
```tsx
|
||||
import { useDocumentEvents } from 'payload/components/hooks'
|
||||
|
||||
const ListenForUpdates: React.FC = () => {
|
||||
const { mostRecentUpdate } = useDocumentEvents()
|
||||
|
||||
return (
|
||||
<span>
|
||||
{JSON.stringify(mostRecentUpdate)}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
<Banner type="info">
|
||||
Right now the `useDocumentEvents` hook only tracks recently updated documents, but in the future it will track more document-related events as needed, such as document creation, deletion, etc.
|
||||
</Banner>
|
||||
|
||||
@@ -60,17 +60,33 @@ export const buildConfig({
|
||||
collections: [],
|
||||
admin: {
|
||||
bundler: viteBundler(),
|
||||
vite: (incomingViteConfig) => ({
|
||||
...incomingViteConfig,
|
||||
resolve: {
|
||||
...(incomingViteConfig?.resolve || {}),
|
||||
alias: {
|
||||
...(incomingViteConfig?.resolve?.alias || {}),
|
||||
'../server-only-module': path.resolve(__dirname, './path/to/browser-safe-module.js'),
|
||||
'../../server-only-module': path.resolve(__dirname, './path/to/browser-safe-module.js'),
|
||||
}
|
||||
vite: (incomingViteConfig) => {
|
||||
const existingAliases = incomingViteConfig?.resolve?.alias || {};
|
||||
let aliasArray: { find: string | RegExp; replacement: string; }[] = [];
|
||||
|
||||
// Pass the existing Vite aliases
|
||||
if (Array.isArray(existingAliases)) {
|
||||
aliasArray = existingAliases;
|
||||
} else {
|
||||
aliasArray = Object.values(existingAliases);
|
||||
}
|
||||
})
|
||||
|
||||
// Add your own aliases using the find and replacement keys
|
||||
aliasArray.push({
|
||||
find: '../server-only-module',
|
||||
replacement: path.resolve(__dirname, './path/to/browser-safe-module.js')
|
||||
find: '../../server-only-module',
|
||||
replacement: path.resolve(__dirname, './path/to/browser-safe-module.js')
|
||||
});
|
||||
|
||||
return {
|
||||
...incomingViteConfig,
|
||||
resolve: {
|
||||
...(incomingViteConfig?.resolve || {}),
|
||||
alias: aliasArray,
|
||||
}
|
||||
};
|
||||
},
|
||||
}
|
||||
})
|
||||
```
|
||||
@@ -90,7 +106,8 @@ That plugin should create an alias to support Vite as follows:
|
||||
```ts
|
||||
{
|
||||
// aliases go here
|
||||
'payload-plugin-cool': path.resolve(__dirname, './my-admin-plugin.js')
|
||||
find: 'payload-plugin-cool',
|
||||
replacement: path.resolve(__dirname, './my-admin-plugin.js')
|
||||
}
|
||||
|
||||
```
|
||||
@@ -108,22 +125,36 @@ export const buildConfig({
|
||||
collections: [],
|
||||
admin: {
|
||||
bundler: viteBundler(),
|
||||
vite: (incomingViteConfig) => ({
|
||||
...incomingViteConfig,
|
||||
resolve: {
|
||||
...(incomingViteConfig?.resolve || {}),
|
||||
alias: {
|
||||
...(incomingViteConfig?.resolve?.alias || {}),
|
||||
// custom aliases go here
|
||||
'../server-only-module': path.resolve(__dirname, './path/to/browser-safe-module.js'),
|
||||
}
|
||||
vite: (incomingViteConfig) => {
|
||||
const existingAliases = incomingViteConfig?.resolve?.alias || {};
|
||||
let aliasArray: { find: string | RegExp; replacement: string; }[] = [];
|
||||
|
||||
// Pass the existing Vite aliases
|
||||
if (Array.isArray(existingAliases)) {
|
||||
aliasArray = existingAliases;
|
||||
} else {
|
||||
aliasArray = Object.values(existingAliases);
|
||||
}
|
||||
})
|
||||
|
||||
// Add your own aliases using the find and replacement keys
|
||||
aliasArray.push({
|
||||
find: '../server-only-module',
|
||||
replacement: path.resolve(__dirname, './path/to/browser-safe-module.js')
|
||||
});
|
||||
|
||||
return {
|
||||
...incomingViteConfig,
|
||||
resolve: {
|
||||
...(incomingViteConfig?.resolve || {}),
|
||||
alias: aliasArray,
|
||||
}
|
||||
};
|
||||
},
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
Learn more about [aliasing server-only modules](http://localhost:3000/docs/admin/excluding-server-code#aliasing-server-only-modules).
|
||||
Learn more about [aliasing server-only modules](https://payloadcms.com/docs/admin/excluding-server-code#aliasing-server-only-modules).
|
||||
|
||||
Even though there is a new property for Vite configs specifically, we have implemented some "compatibility" between Webpack and Vite out-of-the-box.
|
||||
|
||||
|
||||
@@ -82,7 +82,7 @@ Once enabled, each document that is created within the Collection can be thought
|
||||
|
||||
### Token-based auth
|
||||
|
||||
Successfully logging in returns a `JWT` (JSON web token) which is how a user will identify themselves to Payload. By providing this JWT via either an HTTP-only cookie or an `Authorization` header, Payload will automatically identify the user and add its user JWT data to the Express `req`, which is available throughout Payload including within access control, hooks, and more.
|
||||
Successfully logging in returns a `JWT` (JSON web token) which is how a user will identify themselves to Payload. By providing this JWT via either an HTTP-only cookie or an `Authorization: JWT` or `Authorization: Bearer` header, Payload will automatically identify the user and add its user JWT data to the Express `req`, which is available throughout Payload including within access control, hooks, and more.
|
||||
|
||||
You can specify what data gets encoded to the JWT token by setting `saveToJWT` to true in your auth collection fields. If you wish to use a different key other than the field `name`, you can provide it to `saveToJWT` as a string. It is also possible to use `saveToJWT` on fields that are nested in inside groups and tabs. If a group has a `saveToJWT` set it will include the object with all sub-fields in the token. You can set `saveToJWT: false` for any fields you wish to omit. If a field inside a group has `saveToJWT` set, but the group does not, the field will be included at the top level of the token.
|
||||
|
||||
|
||||
@@ -59,7 +59,7 @@ export const Orders: CollectionConfig = {
|
||||
#### More collection config examples
|
||||
|
||||
You can find an assortment
|
||||
of [example collection configs](https://github.com/payloadcms/public-demo/tree/master/src/collections) in the Public
|
||||
of [example collection configs](https://github.com/payloadcms/public-demo/tree/master/src/payload/collections) in the Public
|
||||
Demo source code on GitHub.
|
||||
|
||||
### Admin options
|
||||
|
||||
@@ -59,7 +59,7 @@ export default Nav
|
||||
|
||||
#### Global config example
|
||||
|
||||
You can find an [example Global config](https://github.com/payloadcms/public-demo/blob/master/src/globals/MainMenu.ts) in the Public Demo source code on GitHub.
|
||||
You can find an [example Global config](https://github.com/payloadcms/public-demo/blob/master/src/payload/globals/MainMenu.ts) in the Public Demo source code on GitHub.
|
||||
|
||||
### Admin options
|
||||
|
||||
|
||||
@@ -46,8 +46,14 @@ export async function down({ payload }: MigrateDownArgs): Promise<void> {
|
||||
};
|
||||
```
|
||||
|
||||
### Migrations Directory
|
||||
|
||||
Each DB adapter has an optional property `migrationDir` where you can override where you want your migrations to be stored/read. If this is not specified, Payload will check the default and possibly make a best effort to find your migrations directory by searching in common locations ie. `./src/migrations`, `./dist/igrations`, `./migrations`, etc.
|
||||
|
||||
All database adapters should implement similar migration patterns, but there will be small differences based on the adapter and its specific needs. Below is a list of all migration commands that should be supported by your database adapter.
|
||||
|
||||
## Commands
|
||||
|
||||
### Migrate
|
||||
|
||||
The `migrate` command will run any migrations that have not yet been run.
|
||||
|
||||
@@ -70,6 +70,43 @@ Set to `true` if you'd like this field to be sortable within the Admin UI using
|
||||
|
||||
Set to `false` if you'd like to disable the ability to create new documents from within the relationship field (hides the "Add new" button in the admin UI).
|
||||
|
||||
**`sortOptions`**
|
||||
|
||||
The `sortOptions` property allows you to define a default sorting order for the options within a Relationship field's dropdown. This can be particularly useful for ensuring that the most relevant options are presented first to the user.
|
||||
|
||||
You can specify `sortOptions` in two ways:
|
||||
|
||||
**As a string:**
|
||||
|
||||
Provide a string to define a global default sort field for all relationship field dropdowns across different collections. You can prefix the field name with a minus symbol ("-") to sort in descending order.
|
||||
|
||||
Example:
|
||||
|
||||
```ts
|
||||
sortOptions: 'fieldName',
|
||||
```
|
||||
This configuration will sort all relationship field dropdowns by `"fieldName"` in ascending order.
|
||||
|
||||
**As an object :**
|
||||
|
||||
Specify an object where keys are collection slugs and values are strings representing the field names to sort by. This allows for different sorting fields for each collection's relationship dropdown.
|
||||
|
||||
Example:
|
||||
|
||||
```ts
|
||||
sortOptions: {
|
||||
"pages": "fieldName1",
|
||||
"posts": "-fieldName2",
|
||||
"categories": "fieldName3"
|
||||
}
|
||||
```
|
||||
In this configuration:
|
||||
- Dropdowns related to `pages` will be sorted by `"fieldName1"` in ascending order.
|
||||
- Dropdowns for `posts` will use `"fieldName2"` for sorting in descending order (noted by the "-" prefix).
|
||||
- Dropdowns associated with `categories` will sort based on `"fieldName3"` in ascending order.
|
||||
|
||||
Note: If `sortOptions` is not defined, the default sorting behavior of the Relationship field dropdown will be used.
|
||||
|
||||
### Filtering relationship options
|
||||
|
||||
Options can be dynamically limited by supplying a [query constraint](/docs/queries/overview), which will be used both for validating input and filtering available relationships in the UI.
|
||||
|
||||
@@ -187,3 +187,55 @@ For a working demonstration of this, check out the official [Live Preview Exampl
|
||||
- [Next.js App Router](https://github.com/payloadcms/payload/tree/main/examples/live-preview/next-app)
|
||||
- [Next.js Pages Router](https://github.com/payloadcms/payload/tree/main/examples/live-preview/next-pages)
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Relationships and/or uploads are not populating
|
||||
|
||||
If you are using relationships or uploads in your front-end application, and your front-end application runs on a different domain than your Payload server, you may need to configure [CORS](../configuration/overview) to allow requests to be made between the two domains. This includes sites that are running on a different port or subdomain. Similarly, if you are protecting resources behind user authentication, you may also need to configure [CSRF](../authentication/overview#csrf-protection) to allow cookies to be sent between the two domains. For example:
|
||||
|
||||
```ts
|
||||
// payload.config.ts
|
||||
{
|
||||
// ...
|
||||
// If your site is running on a different domain than your Payload server,
|
||||
// This will allows requests to be made between the two domains
|
||||
cors: {
|
||||
[
|
||||
'http://localhost:3001' // Your front-end application
|
||||
],
|
||||
},
|
||||
// If you are protecting resources behind user authentication,
|
||||
// This will allow cookies to be sent between the two domains
|
||||
csrf: {
|
||||
[
|
||||
'http://localhost:3001' // Your front-end application
|
||||
],
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
### Relationships and/or uploads disappear after editing a document
|
||||
|
||||
It is possible that either you are setting an improper [`depth`](../getting-started/concepts#depth) in your initial request and/or your `useLivePreview` hook, or they're mismatched. Ensure that the `depth` parameter is set to the correct value, and that it matches exactly in both places. For example:
|
||||
|
||||
```tsx
|
||||
// Your initial request
|
||||
const { docs } = await payload.find({
|
||||
collection: 'pages',
|
||||
depth: 1, // Ensure this is set to the proper depth for your application
|
||||
where: {
|
||||
slug: {
|
||||
equals: 'home',
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
```tsx
|
||||
// Your hook
|
||||
const { data } = useLivePreview<PageType>({
|
||||
initialData: initialPage,
|
||||
serverURL: PAYLOAD_SERVER_URL,
|
||||
depth: 1, // Ensure this matches the depth of your initial request
|
||||
})
|
||||
```
|
||||
|
||||
@@ -12,6 +12,10 @@ Live Preview works by rendering an iframe on the page that loads your front-end
|
||||
|
||||
{/* IMAGE OF LIVE PREVIEW HERE */}
|
||||
|
||||
<Banner type="warning">
|
||||
Live Preview is currently in beta. You may use this feature in production, but please be aware that it is subject to change and may not be fully stable for all use cases. If you encounter any issues, please [report them](https://github.com/payloadcms/payload/issues/new?assignees=jacobsfletch&labels=possible-bug&projects=&title=Live%20Preview&template=1.bug_report.yml) with as much detail as possible.
|
||||
</Banner>
|
||||
|
||||
## Setup
|
||||
|
||||
Setting up Live Preview is easy. You first need to enable it through the `admin.livePreview` property of your Payload config. It takes the following options:
|
||||
|
||||
@@ -6,7 +6,9 @@ desc: The Payload Local API allows you to interact with your database and execut
|
||||
keywords: local api, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, express
|
||||
---
|
||||
|
||||
The Payload Local API gives you the ability to execute the same operations that are available through REST and GraphQL within Node, directly on your server. Here, you don't need to deal with server latency or network speed whatsoever and can interact directly with your database.
|
||||
The Payload Local API gives you the ability to execute the same operations that are available through REST and GraphQL
|
||||
within Node, directly on your server. Here, you don't need to deal with server latency or network speed whatsoever and
|
||||
can interact directly with your database.
|
||||
|
||||
<Banner type="success">
|
||||
<strong>Tip:</strong>
|
||||
@@ -30,7 +32,9 @@ You can gain access to the currently running `payload` object via two ways:
|
||||
|
||||
##### Importing it
|
||||
|
||||
You can import or require `payload` into your own files after it's been initialized, but you need to make sure that your `import` / `require` statements come **after** you call `payload.init()`—otherwise Payload won't have been initialized yet. That might be obvious. To us, it's usually not.
|
||||
You can import or require `payload` into your own files after it's been initialized, but you need to make sure that
|
||||
your `import` / `require` statements come **after** you call `payload.init()`—otherwise Payload won't have been
|
||||
initialized yet. That might be obvious. To us, it's usually not.
|
||||
|
||||
Example:
|
||||
|
||||
@@ -47,7 +51,8 @@ const afterChangeHook: CollectionAfterChangeHook = async () => {
|
||||
|
||||
##### Accessing from the `req`
|
||||
|
||||
Payload is available anywhere you have access to the Express `req` - including within your access control and hook functions.
|
||||
Payload is available anywhere you have access to the Express `req` - including within your access control and hook
|
||||
functions.
|
||||
|
||||
Example:
|
||||
|
||||
@@ -61,10 +66,11 @@ const afterChangeHook: CollectionAfterChangeHook = async ({ req: { payload } })
|
||||
|
||||
### Local options available
|
||||
|
||||
You can specify more options within the Local API vs. REST or GraphQL due to the server-only context that they are executed in.
|
||||
You can specify more options within the Local API vs. REST or GraphQL due to the server-only context that they are
|
||||
executed in.
|
||||
|
||||
| Local Option | Description |
|
||||
| ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
|--------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `collection` | Required for Collection operations. Specifies the Collection slug to operate against. |
|
||||
| `data` | The data to use within the operation. Required for `create`, `update`. |
|
||||
| `depth` | [Control auto-population](/docs/getting-started/concepts#depth) of nested relationship and upload fields. |
|
||||
@@ -268,7 +274,8 @@ const result = await payload.delete({
|
||||
|
||||
## Auth Operations
|
||||
|
||||
If a collection has [`Authentication`](/docs/authentication/overview) enabled, additional Local API operations will be available:
|
||||
If a collection has [`Authentication`](/docs/authentication/overview) enabled, additional Local API operations will be
|
||||
available:
|
||||
|
||||
#### Login
|
||||
|
||||
@@ -319,10 +326,11 @@ const token = await payload.forgotPassword({
|
||||
// token: 'o38jf0q34jfij43f3f...', // JWT used for auth
|
||||
// user: { ... } // the user document that just logged in
|
||||
// }
|
||||
const result = await payload.forgotPassword({
|
||||
const result = await payload.resetPassword({
|
||||
collection: 'users', // required
|
||||
data: {
|
||||
// required
|
||||
password: req.body.password, // the new password to set
|
||||
token: 'afh3o2jf2p3f...', // the token generated from the forgotPassword operation
|
||||
},
|
||||
req: req, // pass an Express `req` which will be provided to all hooks
|
||||
@@ -402,7 +410,9 @@ const result = await payload.updateGlobal({
|
||||
|
||||
## Next.js Conflict with Local API
|
||||
|
||||
There is a known issue when using the Local API with Next.js version `13.4.13` and higher. Next.js executes within a separate child process, and Payload has not been initalized yet in these instances. That means that unless you explicitly initialize Payload within your operation, it will not be running and return no data / an empty object.
|
||||
There is a known issue when using the Local API with Next.js version `13.4.13` and higher. Next.js executes within a
|
||||
separate child process, and Payload has not been initalized yet in these instances. That means that unless you
|
||||
explicitly initialize Payload within your operation, it will not be running and return no data / an empty object.
|
||||
|
||||
As a workaround, we recommend leveraging the following pattern to determine and ensure Payload is initalized:
|
||||
|
||||
@@ -462,7 +472,8 @@ export const getPayloadClient = async ({ initOptions, seed }: Args = {}): Promis
|
||||
}
|
||||
```
|
||||
|
||||
To checkout how this works in a project, take a look at our [custom server example](https://github.com/payloadcms/payload/blob/master/examples/custom-server/src/getPayload.ts).
|
||||
To checkout how this works in a project, take a look at
|
||||
our [custom server example](https://github.com/payloadcms/payload/blob/master/examples/custom-server/src/getPayload.ts).
|
||||
|
||||
## Example Script using Local API
|
||||
|
||||
|
||||
@@ -153,7 +153,7 @@ Here's an overview of all the included features:
|
||||
| **`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 |
|
||||
| **`IndentFeature`** | Yes | Allows you to indent text with the tab key |
|
||||
| **`UnoderedListFeature`** | Yes | Adds unordered lists (ol) |
|
||||
| **`UnorderedListFeature`** | Yes | Adds unordered lists (ol) |
|
||||
| **`OrderedListFeature`** | Yes | Adds ordered lists (ul) |
|
||||
| **`CheckListFeature`** | Yes | Adds checklists |
|
||||
| **`LinkFeature`** | Yes | Allows you to create internal and external links |
|
||||
@@ -346,7 +346,7 @@ Once you have your headless editor instance, you can use it to convert HTML to L
|
||||
```ts
|
||||
import { $generateNodesFromDOM } from '@lexical/html'
|
||||
import { $getRoot,$getSelection } from 'lexical'
|
||||
import JSDOM from 'jsdom'
|
||||
import { JSDOM } from 'jsdom';
|
||||
|
||||
headlessEditor.update(() => {
|
||||
// In a headless environment you can use a package such as JSDom to parse the HTML string.
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
module.exports = {
|
||||
config: () => null,
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/bundler-vite",
|
||||
"version": "0.1.4",
|
||||
"version": "0.1.5",
|
||||
"description": "The officially supported Vite bundler adapter for Payload",
|
||||
"repository": "https://github.com/payloadcms/payload",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -10,7 +10,7 @@ CLI for easily starting new Payload project
|
||||
|
||||
$ npx create-payload-app
|
||||
$ npx create-payload-app my-project
|
||||
$ npx create-payload-app -n my-project -t blog
|
||||
$ npx create-payload-app -n my-project -t website
|
||||
|
||||
OPTIONS
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/db-mongodb",
|
||||
"version": "1.0.8",
|
||||
"version": "1.1.0",
|
||||
"description": "The officially supported MongoDB database adapter for Payload",
|
||||
"repository": "https://github.com/payloadcms/payload",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -2,6 +2,7 @@ import type { ClientSession, ConnectOptions, Connection } from 'mongoose'
|
||||
import type { Payload } from 'payload'
|
||||
import type { BaseDatabaseAdapter } from 'payload/database'
|
||||
|
||||
import fs from 'fs'
|
||||
import mongoose from 'mongoose'
|
||||
import path from 'path'
|
||||
import { createDatabaseAdapter } from 'payload/database'
|
||||
@@ -94,7 +95,7 @@ export function mongooseAdapter({
|
||||
url,
|
||||
}: Args): MongooseAdapterResult {
|
||||
function adapter({ payload }: { payload: Payload }) {
|
||||
const migrationDir = migrationDirArg || path.resolve(process.cwd(), 'src/migrations')
|
||||
const migrationDir = findMigrationDir(migrationDirArg)
|
||||
mongoose.set('strictQuery', false)
|
||||
|
||||
extendWebpackConfig(payload.config)
|
||||
@@ -149,3 +150,42 @@ export function mongooseAdapter({
|
||||
|
||||
return adapter
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to find migrations directory.
|
||||
*
|
||||
* Checks for the following directories in order:
|
||||
* - `migrationDir` argument from Payload config
|
||||
* - `src/migrations`
|
||||
* - `dist/migrations`
|
||||
* - `migrations`
|
||||
*
|
||||
* Defaults to `src/migrations`
|
||||
*
|
||||
* @param migrationDir
|
||||
* @returns
|
||||
*/
|
||||
function findMigrationDir(migrationDir?: string): string {
|
||||
const cwd = process.cwd()
|
||||
const srcDir = path.resolve(cwd, 'src/migrations')
|
||||
const distDir = path.resolve(cwd, 'dist/migrations')
|
||||
const relativeMigrations = path.resolve(cwd, 'migrations')
|
||||
|
||||
// Use arg if provided
|
||||
if (migrationDir) return migrationDir
|
||||
|
||||
// Check other common locations
|
||||
if (fs.existsSync(srcDir)) {
|
||||
return srcDir
|
||||
}
|
||||
|
||||
if (fs.existsSync(distDir)) {
|
||||
return distDir
|
||||
}
|
||||
|
||||
if (fs.existsSync(relativeMigrations)) {
|
||||
return relativeMigrations
|
||||
}
|
||||
|
||||
return srcDir
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/db-postgres",
|
||||
"version": "0.1.13",
|
||||
"version": "0.2.0",
|
||||
"description": "The officially supported Postgres database adapter for Payload",
|
||||
"repository": "https://github.com/payloadcms/payload",
|
||||
"license": "MIT",
|
||||
@@ -26,7 +26,8 @@
|
||||
"drizzle-orm": "0.28.5",
|
||||
"pg": "8.11.3",
|
||||
"prompts": "2.4.2",
|
||||
"to-snake-case": "1.0.0"
|
||||
"to-snake-case": "1.0.0",
|
||||
"uuid": "9.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@payloadcms/eslint-config": "workspace:*",
|
||||
|
||||
@@ -33,7 +33,7 @@ export const findMany = async function find({
|
||||
const db = adapter.sessions[req.transactionID]?.db || adapter.drizzle
|
||||
const table = adapter.tables[tableName]
|
||||
|
||||
let limit = limitArg ?? 10
|
||||
const limit = limitArg ?? 10
|
||||
let totalDocs: number
|
||||
let totalPages: number
|
||||
let hasPrevPage: boolean
|
||||
@@ -51,6 +51,7 @@ export const findMany = async function find({
|
||||
})
|
||||
|
||||
const orderedIDMap: Record<number | string, number> = {}
|
||||
let orderedIDs: (number | string)[]
|
||||
|
||||
const selectDistinctMethods: ChainedMethods = []
|
||||
|
||||
@@ -116,7 +117,8 @@ export const findMany = async function find({
|
||||
selectDistinctResult.forEach(({ id }, i) => {
|
||||
orderedIDMap[id as number | string] = i
|
||||
})
|
||||
findManyArgs.where = inArray(adapter.tables[tableName].id, Object.keys(orderedIDMap))
|
||||
orderedIDs = Object.keys(orderedIDMap)
|
||||
findManyArgs.where = inArray(adapter.tables[tableName].id, orderedIDs)
|
||||
} else {
|
||||
findManyArgs.limit = limitArg === 0 ? undefined : limitArg
|
||||
|
||||
@@ -132,7 +134,7 @@ export const findMany = async function find({
|
||||
|
||||
const findPromise = db.query[tableName].findMany(findManyArgs)
|
||||
|
||||
if (pagination !== false || selectDistinctResult?.length > limit) {
|
||||
if (pagination !== false && (orderedIDs ? orderedIDs?.length >= limit : true)) {
|
||||
const selectCountMethods: ChainedMethods = []
|
||||
|
||||
joinAliases.forEach(({ condition, table }) => {
|
||||
@@ -174,9 +176,8 @@ export const findMany = async function find({
|
||||
rawDocs.sort((a, b) => orderedIDMap[a.id] - orderedIDMap[b.id])
|
||||
}
|
||||
|
||||
if (pagination === false) {
|
||||
if (pagination === false || !totalDocs) {
|
||||
totalDocs = rawDocs.length
|
||||
limit = totalDocs
|
||||
totalPages = 1
|
||||
pagingCounter = 1
|
||||
hasPrevPage = false
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { Payload } from 'payload'
|
||||
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
import { createDatabaseAdapter } from 'payload/database'
|
||||
|
||||
@@ -42,7 +43,7 @@ export type { MigrateDownArgs, MigrateUpArgs } from './types'
|
||||
|
||||
export function postgresAdapter(args: Args): PostgresAdapterResult {
|
||||
function adapter({ payload }: { payload: Payload }) {
|
||||
const migrationDir = args.migrationDir || path.resolve(process.cwd(), 'src/migrations')
|
||||
const migrationDir = findMigrationDir(args.migrationDir)
|
||||
|
||||
extendWebpackConfig(payload.config)
|
||||
extendViteConfig(payload.config)
|
||||
@@ -53,6 +54,7 @@ export function postgresAdapter(args: Args): PostgresAdapterResult {
|
||||
// Postgres-specific
|
||||
drizzle: undefined,
|
||||
enums: {},
|
||||
fieldConstraints: {},
|
||||
pool: undefined,
|
||||
poolOptions: args.pool,
|
||||
push: args.push,
|
||||
@@ -60,7 +62,6 @@ export function postgresAdapter(args: Args): PostgresAdapterResult {
|
||||
schema: {},
|
||||
sessions: {},
|
||||
tables: {},
|
||||
fieldConstraints: {},
|
||||
|
||||
// DatabaseAdapter
|
||||
beginTransaction,
|
||||
@@ -101,3 +102,42 @@ export function postgresAdapter(args: Args): PostgresAdapterResult {
|
||||
|
||||
return adapter
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to find migrations directory.
|
||||
*
|
||||
* Checks for the following directories in order:
|
||||
* - `migrationDir` argument from Payload config
|
||||
* - `src/migrations`
|
||||
* - `dist/migrations`
|
||||
* - `migrations`
|
||||
*
|
||||
* Defaults to `src/migrations`
|
||||
*
|
||||
* @param migrationDir
|
||||
* @returns
|
||||
*/
|
||||
function findMigrationDir(migrationDir?: string): string {
|
||||
const cwd = process.cwd()
|
||||
const srcDir = path.resolve(cwd, 'src/migrations')
|
||||
const distDir = path.resolve(cwd, 'dist/migrations')
|
||||
const relativeMigrations = path.resolve(cwd, 'migrations')
|
||||
|
||||
// Use arg if provided
|
||||
if (migrationDir) return migrationDir
|
||||
|
||||
// Check other common locations
|
||||
if (fs.existsSync(srcDir)) {
|
||||
return srcDir
|
||||
}
|
||||
|
||||
if (fs.existsSync(distDir)) {
|
||||
return distDir
|
||||
}
|
||||
|
||||
if (fs.existsSync(relativeMigrations)) {
|
||||
return relativeMigrations
|
||||
}
|
||||
|
||||
return srcDir
|
||||
}
|
||||
|
||||
@@ -297,6 +297,9 @@ export const getTableColumnFromPath = ({
|
||||
table: adapter.tables[newTableName],
|
||||
}
|
||||
}
|
||||
if (pathSegments[1] === 'blockType') {
|
||||
throw new APIError('Querying on blockType is not supported')
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ export const insertArrays = async ({ adapter, arrays, db, parentRows }: Args): P
|
||||
}
|
||||
}
|
||||
|
||||
const parentID = parentRows[parentRowIndex].id
|
||||
const parentID = parentRows[parentRowIndex].id || parentRows[parentRowIndex]._parentID
|
||||
|
||||
// Add any sub arrays that need to be created
|
||||
// We will call this recursively below
|
||||
|
||||
@@ -25,7 +25,6 @@
|
||||
"payload": "workspace:*"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"payload": "^2.0.0",
|
||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0"
|
||||
},
|
||||
"exports": {
|
||||
|
||||
@@ -20,9 +20,6 @@
|
||||
"@payloadcms/eslint-config": "workspace:*",
|
||||
"payload": "workspace:*"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"payload": "^2.0.0"
|
||||
},
|
||||
"exports": {
|
||||
".": {
|
||||
"default": "./src/index.ts",
|
||||
|
||||
@@ -6,6 +6,10 @@ import { mergeData } from '.'
|
||||
// Send this cached value to `mergeData`, instead of `eventData.fieldSchemaJSON` directly
|
||||
let payloadLivePreviewFieldSchema = undefined // TODO: type this from `fieldSchemaToJSON` return type
|
||||
|
||||
// Each time the data is merged, cache the result as a `previousData` variable
|
||||
// This will ensure changes compound overtop of each other
|
||||
let payloadLivePreviewPreviousData = undefined
|
||||
|
||||
export const handleMessage = async <T>(args: {
|
||||
apiRoute?: string
|
||||
depth?: number
|
||||
@@ -35,12 +39,15 @@ export const handleMessage = async <T>(args: {
|
||||
const mergedData = await mergeData<T>({
|
||||
apiRoute,
|
||||
depth,
|
||||
externallyUpdatedRelationship: eventData.externallyUpdatedRelationship,
|
||||
fieldSchema: payloadLivePreviewFieldSchema,
|
||||
incomingData: eventData.data,
|
||||
initialData,
|
||||
initialData: payloadLivePreviewPreviousData || initialData,
|
||||
serverURL,
|
||||
})
|
||||
|
||||
payloadLivePreviewPreviousData = mergedData
|
||||
|
||||
return mergedData
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
import type { PaginatedDocs } from 'payload/database'
|
||||
import type { fieldSchemaToJSON } from 'payload/utilities'
|
||||
|
||||
import type { PopulationsByCollection, UpdatedDocument } from './types'
|
||||
|
||||
import { traverseFields } from './traverseFields'
|
||||
|
||||
export const mergeData = async <T>(args: {
|
||||
apiRoute?: string
|
||||
depth?: number
|
||||
externallyUpdatedRelationship?: UpdatedDocument
|
||||
fieldSchema: ReturnType<typeof fieldSchemaToJSON>
|
||||
incomingData: Partial<T>
|
||||
initialData: T
|
||||
@@ -18,6 +22,7 @@ export const mergeData = async <T>(args: {
|
||||
const {
|
||||
apiRoute,
|
||||
depth,
|
||||
externallyUpdatedRelationship,
|
||||
fieldSchema,
|
||||
incomingData,
|
||||
initialData,
|
||||
@@ -27,22 +32,52 @@ export const mergeData = async <T>(args: {
|
||||
|
||||
const result = { ...initialData }
|
||||
|
||||
const populationPromises: Promise<void>[] = []
|
||||
const populationsByCollection: PopulationsByCollection = {}
|
||||
|
||||
traverseFields({
|
||||
apiRoute,
|
||||
depth,
|
||||
externallyUpdatedRelationship,
|
||||
fieldSchema,
|
||||
incomingData,
|
||||
populationPromises,
|
||||
populationsByCollection,
|
||||
result,
|
||||
serverURL,
|
||||
})
|
||||
|
||||
await Promise.all(populationPromises)
|
||||
await Promise.all(
|
||||
Object.entries(populationsByCollection).map(async ([collection, populations]) => {
|
||||
const ids = new Set(populations.map(({ id }) => id))
|
||||
const url = `${serverURL}${
|
||||
apiRoute || '/api'
|
||||
}/${collection}?depth=${depth}&where[id][in]=${Array.from(ids).join(',')}`
|
||||
|
||||
let res: PaginatedDocs
|
||||
|
||||
try {
|
||||
res = await fetch(url, {
|
||||
credentials: 'include',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
}).then((res) => res.json())
|
||||
|
||||
if (res?.docs?.length > 0) {
|
||||
res.docs.forEach((doc) => {
|
||||
populationsByCollection[collection].forEach((population) => {
|
||||
if (population.id === doc.id) {
|
||||
population.ref[population.accessor] = doc
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err) // eslint-disable-line no-console
|
||||
}
|
||||
}),
|
||||
)
|
||||
|
||||
return {
|
||||
...result,
|
||||
...(returnNumberOfRequests ? { _numberOfRequests: populationPromises.length } : {}),
|
||||
...(returnNumberOfRequests
|
||||
? { _numberOfRequests: Object.keys(populationsByCollection).length }
|
||||
: {}),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
export const promise = async (args: {
|
||||
accessor: number | string
|
||||
apiRoute?: string
|
||||
collection: string
|
||||
depth: number
|
||||
id: number | string
|
||||
ref: Record<string, unknown>
|
||||
serverURL: string
|
||||
}): Promise<void> => {
|
||||
const { id, accessor, apiRoute, collection, depth, ref, serverURL } = args
|
||||
|
||||
const url = `${serverURL}${apiRoute || '/api'}/${collection}/${id}?depth=${depth}`
|
||||
|
||||
let res: Record<string, unknown> | null | undefined = null
|
||||
|
||||
try {
|
||||
res = await fetch(url, {
|
||||
credentials: 'include',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
}).then((res) => res.json())
|
||||
} catch (err) {
|
||||
console.error(err) // eslint-disable-line no-console
|
||||
}
|
||||
|
||||
ref[accessor] = res
|
||||
}
|
||||
@@ -1,24 +1,22 @@
|
||||
import type { fieldSchemaToJSON } from 'payload/utilities'
|
||||
|
||||
import { promise } from './promise'
|
||||
import type { PopulationsByCollection, UpdatedDocument } from './types'
|
||||
|
||||
import { traverseRichText } from './traverseRichText'
|
||||
|
||||
export const traverseFields = <T>(args: {
|
||||
apiRoute?: string
|
||||
depth?: number
|
||||
externallyUpdatedRelationship?: UpdatedDocument
|
||||
fieldSchema: ReturnType<typeof fieldSchemaToJSON>
|
||||
incomingData: T
|
||||
populationPromises: Promise<void>[]
|
||||
populationsByCollection: PopulationsByCollection
|
||||
result: T
|
||||
serverURL: string
|
||||
}): void => {
|
||||
const {
|
||||
apiRoute,
|
||||
depth,
|
||||
externallyUpdatedRelationship,
|
||||
fieldSchema: fieldSchemas,
|
||||
incomingData,
|
||||
populationPromises,
|
||||
populationsByCollection,
|
||||
result,
|
||||
serverURL,
|
||||
} = args
|
||||
|
||||
fieldSchemas.forEach((fieldSchema) => {
|
||||
@@ -26,6 +24,16 @@ export const traverseFields = <T>(args: {
|
||||
const fieldName = fieldSchema.name
|
||||
|
||||
switch (fieldSchema.type) {
|
||||
case 'richText':
|
||||
result[fieldName] = traverseRichText({
|
||||
externallyUpdatedRelationship,
|
||||
incomingData: incomingData[fieldName],
|
||||
populationsByCollection,
|
||||
result: result[fieldName],
|
||||
})
|
||||
|
||||
break
|
||||
|
||||
case 'array':
|
||||
if (Array.isArray(incomingData[fieldName])) {
|
||||
result[fieldName] = incomingData[fieldName].map((incomingRow, i) => {
|
||||
@@ -38,18 +46,17 @@ export const traverseFields = <T>(args: {
|
||||
}
|
||||
|
||||
traverseFields({
|
||||
apiRoute,
|
||||
depth,
|
||||
externallyUpdatedRelationship,
|
||||
fieldSchema: fieldSchema.fields,
|
||||
incomingData: incomingRow,
|
||||
populationPromises,
|
||||
populationsByCollection,
|
||||
result: result[fieldName][i],
|
||||
serverURL,
|
||||
})
|
||||
|
||||
return result[fieldName][i]
|
||||
})
|
||||
}
|
||||
|
||||
break
|
||||
|
||||
case 'blocks':
|
||||
@@ -72,13 +79,11 @@ export const traverseFields = <T>(args: {
|
||||
}
|
||||
|
||||
traverseFields({
|
||||
apiRoute,
|
||||
depth,
|
||||
externallyUpdatedRelationship,
|
||||
fieldSchema: incomingBlockJSON.fields,
|
||||
incomingData: incomingBlock,
|
||||
populationPromises,
|
||||
populationsByCollection,
|
||||
result: result[fieldName][i],
|
||||
serverURL,
|
||||
})
|
||||
|
||||
return result[fieldName][i]
|
||||
@@ -96,13 +101,11 @@ export const traverseFields = <T>(args: {
|
||||
}
|
||||
|
||||
traverseFields({
|
||||
apiRoute,
|
||||
depth,
|
||||
externallyUpdatedRelationship,
|
||||
fieldSchema: fieldSchema.fields,
|
||||
incomingData: incomingData[fieldName] || {},
|
||||
populationPromises,
|
||||
populationsByCollection,
|
||||
result: result[fieldName],
|
||||
serverURL,
|
||||
})
|
||||
|
||||
break
|
||||
@@ -111,7 +114,7 @@ export const traverseFields = <T>(args: {
|
||||
case 'relationship':
|
||||
// Handle `hasMany` relationships
|
||||
if (fieldSchema.hasMany && Array.isArray(incomingData[fieldName])) {
|
||||
if (!result[fieldName]) {
|
||||
if (!result[fieldName] || !incomingData[fieldName].length) {
|
||||
result[fieldName] = []
|
||||
}
|
||||
|
||||
@@ -131,33 +134,39 @@ export const traverseFields = <T>(args: {
|
||||
const newID = incomingRelation.value
|
||||
const newRelation = incomingRelation.relationTo
|
||||
|
||||
if (oldID !== newID || oldRelation !== newRelation) {
|
||||
populationPromises.push(
|
||||
promise({
|
||||
id: incomingRelation.value,
|
||||
accessor: 'value',
|
||||
apiRoute,
|
||||
collection: newRelation,
|
||||
depth,
|
||||
ref: result[fieldName][i],
|
||||
serverURL,
|
||||
}),
|
||||
)
|
||||
const hasChanged = newID !== oldID || newRelation !== oldRelation
|
||||
const hasUpdated =
|
||||
newRelation === externallyUpdatedRelationship?.entitySlug &&
|
||||
newID === externallyUpdatedRelationship?.id
|
||||
|
||||
if (hasChanged || hasUpdated) {
|
||||
if (!populationsByCollection[newRelation]) {
|
||||
populationsByCollection[newRelation] = []
|
||||
}
|
||||
|
||||
populationsByCollection[newRelation].push({
|
||||
id: incomingRelation.value,
|
||||
accessor: 'value',
|
||||
ref: result[fieldName][i],
|
||||
})
|
||||
}
|
||||
} else {
|
||||
// Handle `hasMany` monomorphic
|
||||
if (result[fieldName][i]?.id !== incomingRelation) {
|
||||
populationPromises.push(
|
||||
promise({
|
||||
id: incomingRelation,
|
||||
accessor: i,
|
||||
apiRoute,
|
||||
collection: String(fieldSchema.relationTo),
|
||||
depth,
|
||||
ref: result[fieldName],
|
||||
serverURL,
|
||||
}),
|
||||
)
|
||||
const hasChanged = incomingRelation !== result[fieldName][i]?.id
|
||||
const hasUpdated =
|
||||
fieldSchema.relationTo === externallyUpdatedRelationship?.entitySlug &&
|
||||
incomingRelation === externallyUpdatedRelationship?.id
|
||||
|
||||
if (hasChanged || hasUpdated) {
|
||||
if (!populationsByCollection[fieldSchema.relationTo]) {
|
||||
populationsByCollection[fieldSchema.relationTo] = []
|
||||
}
|
||||
|
||||
populationsByCollection[fieldSchema.relationTo].push({
|
||||
id: incomingRelation,
|
||||
accessor: i,
|
||||
ref: result[fieldName],
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -197,23 +206,26 @@ export const traverseFields = <T>(args: {
|
||||
const newRelation = hasNewValue ? incomingData[fieldName].relationTo : ''
|
||||
const oldRelation = hasOldValue ? result[fieldName].relationTo : ''
|
||||
|
||||
const hasChanged = newID !== oldID || newRelation !== oldRelation
|
||||
const hasUpdated =
|
||||
newRelation === externallyUpdatedRelationship?.entitySlug &&
|
||||
newID === externallyUpdatedRelationship?.id
|
||||
|
||||
// if the new value/relation is different from the old value/relation
|
||||
// populate the new value, otherwise leave it alone
|
||||
if (newID !== oldID || newRelation !== oldRelation) {
|
||||
if (hasChanged || hasUpdated) {
|
||||
// if the new value is not empty, populate it
|
||||
// otherwise set the value to null
|
||||
if (newID) {
|
||||
populationPromises.push(
|
||||
promise({
|
||||
id: newID,
|
||||
accessor: 'value',
|
||||
apiRoute,
|
||||
collection: newRelation,
|
||||
depth,
|
||||
ref: result[fieldName],
|
||||
serverURL,
|
||||
}),
|
||||
)
|
||||
if (!populationsByCollection[newRelation]) {
|
||||
populationsByCollection[newRelation] = []
|
||||
}
|
||||
|
||||
populationsByCollection[newRelation].push({
|
||||
id: newID,
|
||||
accessor: 'value',
|
||||
ref: result[fieldName],
|
||||
})
|
||||
} else {
|
||||
result[fieldName] = null
|
||||
}
|
||||
@@ -232,23 +244,26 @@ export const traverseFields = <T>(args: {
|
||||
result[fieldName].id) ||
|
||||
result[fieldName]
|
||||
|
||||
const hasChanged = newID !== oldID
|
||||
const hasUpdated =
|
||||
fieldSchema.relationTo === externallyUpdatedRelationship?.entitySlug &&
|
||||
newID === externallyUpdatedRelationship?.id
|
||||
|
||||
// if the new value is different from the old value
|
||||
// populate the new value, otherwise leave it alone
|
||||
if (newID !== oldID) {
|
||||
if (hasChanged || hasUpdated) {
|
||||
// if the new value is not empty, populate it
|
||||
// otherwise set the value to null
|
||||
if (newID) {
|
||||
populationPromises.push(
|
||||
promise({
|
||||
id: newID,
|
||||
accessor: fieldName,
|
||||
apiRoute,
|
||||
collection: String(fieldSchema.relationTo),
|
||||
depth,
|
||||
ref: result as Record<string, unknown>,
|
||||
serverURL,
|
||||
}),
|
||||
)
|
||||
if (!populationsByCollection[fieldSchema.relationTo]) {
|
||||
populationsByCollection[fieldSchema.relationTo] = []
|
||||
}
|
||||
|
||||
populationsByCollection[fieldSchema.relationTo].push({
|
||||
id: newID,
|
||||
accessor: fieldName,
|
||||
ref: result as Record<string, unknown>,
|
||||
})
|
||||
} else {
|
||||
result[fieldName] = null
|
||||
}
|
||||
|
||||
93
packages/live-preview/src/traverseRichText.ts
Normal file
93
packages/live-preview/src/traverseRichText.ts
Normal file
@@ -0,0 +1,93 @@
|
||||
import type { PopulationsByCollection, UpdatedDocument } from './types'
|
||||
|
||||
export const traverseRichText = ({
|
||||
externallyUpdatedRelationship,
|
||||
incomingData,
|
||||
populationsByCollection,
|
||||
result,
|
||||
}: {
|
||||
externallyUpdatedRelationship?: UpdatedDocument
|
||||
incomingData: any
|
||||
populationsByCollection: PopulationsByCollection
|
||||
result: any
|
||||
}): any => {
|
||||
if (Array.isArray(incomingData)) {
|
||||
if (!result) {
|
||||
result = []
|
||||
}
|
||||
|
||||
result = incomingData.map((item, index) => {
|
||||
if (!result[index]) {
|
||||
result[index] = item
|
||||
}
|
||||
|
||||
return traverseRichText({
|
||||
externallyUpdatedRelationship,
|
||||
incomingData: item,
|
||||
populationsByCollection,
|
||||
result: result[index],
|
||||
})
|
||||
})
|
||||
} else if (incomingData && typeof incomingData === 'object') {
|
||||
if (!result) {
|
||||
result = {}
|
||||
}
|
||||
|
||||
// Remove keys from `result` that do not appear in `incomingData`
|
||||
// There's likely another way to do this,
|
||||
// But recursion and references make this very difficult
|
||||
Object.keys(result).forEach((key) => {
|
||||
if (!(key in incomingData)) {
|
||||
delete result[key]
|
||||
}
|
||||
})
|
||||
|
||||
// Iterate over the keys of `incomingData` and populate `result`
|
||||
Object.keys(incomingData).forEach((key) => {
|
||||
if (!result[key]) {
|
||||
// Instantiate the key in `result` if it doesn't exist
|
||||
// Ensure its type matches the type of the `incomingData`
|
||||
// We don't have a schema to check against here
|
||||
result[key] =
|
||||
incomingData[key] && typeof incomingData[key] === 'object'
|
||||
? Array.isArray(incomingData[key])
|
||||
? []
|
||||
: {}
|
||||
: incomingData[key]
|
||||
}
|
||||
|
||||
const isRelationship = key === 'value' && 'relationTo' in incomingData
|
||||
|
||||
if (isRelationship) {
|
||||
const needsPopulation = !result.value || typeof result.value !== 'object'
|
||||
const hasChanged =
|
||||
result &&
|
||||
typeof result === 'object' &&
|
||||
result.value.id === externallyUpdatedRelationship?.id
|
||||
|
||||
if (needsPopulation || hasChanged) {
|
||||
if (!populationsByCollection[incomingData.relationTo]) {
|
||||
populationsByCollection[incomingData.relationTo] = []
|
||||
}
|
||||
|
||||
populationsByCollection[incomingData.relationTo].push({
|
||||
id: incomingData[key],
|
||||
accessor: 'value',
|
||||
ref: result,
|
||||
})
|
||||
}
|
||||
} else {
|
||||
result[key] = traverseRichText({
|
||||
externallyUpdatedRelationship,
|
||||
incomingData: incomingData[key],
|
||||
populationsByCollection,
|
||||
result: result[key],
|
||||
})
|
||||
}
|
||||
})
|
||||
} else {
|
||||
result = incomingData
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
@@ -1,3 +1,18 @@
|
||||
export type LivePreviewArgs = {}
|
||||
|
||||
export type LivePreview = void
|
||||
|
||||
export type PopulationsByCollection = {
|
||||
[slug: string]: Array<{
|
||||
accessor: number | string
|
||||
id: number | string
|
||||
ref: Record<string, unknown>
|
||||
}>
|
||||
}
|
||||
|
||||
// TODO: import this from `payload/admin/components/utilities/DocumentEvents/types.ts`
|
||||
export type UpdatedDocument = {
|
||||
entitySlug: string
|
||||
id?: number | string
|
||||
updatedAt: string
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "payload",
|
||||
"version": "2.2.0",
|
||||
"version": "2.3.0",
|
||||
"description": "Node, React and MongoDB Headless CMS and Application Framework",
|
||||
"license": "MIT",
|
||||
"main": "./dist/index.js",
|
||||
|
||||
@@ -17,6 +17,7 @@ import { StepNavProvider } from './components/elements/StepNav'
|
||||
import { AuthProvider } from './components/utilities/Auth'
|
||||
import { ConfigProvider } from './components/utilities/Config'
|
||||
import { CustomProvider } from './components/utilities/CustomProvider'
|
||||
import { DocumentEventsProvider } from './components/utilities/DocumentEvents'
|
||||
import { I18n } from './components/utilities/I18n'
|
||||
import { LoadingOverlayProvider } from './components/utilities/LoadingOverlay'
|
||||
import { LocaleProvider } from './components/utilities/Locale'
|
||||
@@ -49,11 +50,13 @@ const Root = ({ config: incomingConfig }: { config?: SanitizedConfig }) => {
|
||||
<LocaleProvider>
|
||||
<StepNavProvider>
|
||||
<LoadingOverlayProvider>
|
||||
<NavProvider>
|
||||
<CustomProvider>
|
||||
<Routes />
|
||||
</CustomProvider>
|
||||
</NavProvider>
|
||||
<DocumentEventsProvider>
|
||||
<NavProvider>
|
||||
<CustomProvider>
|
||||
<Routes />
|
||||
</CustomProvider>
|
||||
</NavProvider>
|
||||
</DocumentEventsProvider>
|
||||
</LoadingOverlayProvider>
|
||||
</StepNavProvider>
|
||||
</LocaleProvider>
|
||||
|
||||
@@ -90,36 +90,41 @@ const Duplicate: React.FC<Props> = ({ id, collection, slug }) => {
|
||||
if (result.status === 201 || result.status === 200) {
|
||||
return json.doc.id
|
||||
}
|
||||
json.errors.forEach((error) => toast.error(error.message))
|
||||
|
||||
// only show the error if this is the initial request failing
|
||||
if (!duplicateID) {
|
||||
json.errors.forEach((error) => toast.error(error.message))
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
let duplicateID
|
||||
let duplicateID: string
|
||||
let abort = false
|
||||
const localeErrors = []
|
||||
|
||||
if (localization) {
|
||||
await localization.localeCodes.reduce(async (priorLocalePatch, locale) => {
|
||||
await priorLocalePatch
|
||||
if (abort) return
|
||||
duplicateID = await saveDocument({ id, duplicateID, locale })
|
||||
const localeResult = await saveDocument({
|
||||
id,
|
||||
duplicateID,
|
||||
locale,
|
||||
})
|
||||
duplicateID = localeResult || duplicateID
|
||||
if (duplicateID && !localeResult) {
|
||||
localeErrors.push(locale)
|
||||
}
|
||||
if (!duplicateID) {
|
||||
abort = true
|
||||
}
|
||||
}, Promise.resolve())
|
||||
|
||||
if (abort && duplicateID) {
|
||||
// delete the duplicate doc to prevent incomplete
|
||||
await requests.delete(`${serverURL}${api}/${slug}/${duplicateID}`, {
|
||||
headers: {
|
||||
'Accept-Language': i18n.language,
|
||||
},
|
||||
})
|
||||
}
|
||||
} else {
|
||||
duplicateID = await saveDocument({ id })
|
||||
}
|
||||
|
||||
if (!duplicateID) {
|
||||
// document was not saved, error toast was displayed
|
||||
return
|
||||
}
|
||||
|
||||
@@ -128,6 +133,16 @@ const Duplicate: React.FC<Props> = ({ id, collection, slug }) => {
|
||||
{ autoClose: 3000 },
|
||||
)
|
||||
|
||||
if (localeErrors.length > 0) {
|
||||
toast.error(
|
||||
`
|
||||
${t('error:localesNotSaved', { count: localeErrors.length })}
|
||||
${localeErrors.join(', ')}
|
||||
`,
|
||||
{ autoClose: 5000 },
|
||||
)
|
||||
}
|
||||
|
||||
setModified(false)
|
||||
|
||||
setTimeout(() => {
|
||||
|
||||
@@ -6,10 +6,14 @@ import DatePicker from '../../../DatePicker'
|
||||
|
||||
const baseClass = 'condition-value-date'
|
||||
|
||||
const DateField: React.FC<Props> = ({ disabled, onChange, value }) => (
|
||||
<div className={baseClass}>
|
||||
<DatePicker onChange={onChange} readOnly={disabled} value={value} />
|
||||
</div>
|
||||
)
|
||||
const DateField: React.FC<Props> = ({ admin, disabled, onChange, value }) => {
|
||||
const { date } = admin || {}
|
||||
|
||||
return (
|
||||
<div className={baseClass}>
|
||||
<DatePicker {...date} onChange={onChange} readOnly={disabled} value={value} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default DateField
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
import type { Props as DateType } from '../../../../../components/elements/DatePicker/types'
|
||||
export type Props = {
|
||||
admin?: {
|
||||
date?: DateType
|
||||
}
|
||||
disabled?: boolean
|
||||
onChange: () => void
|
||||
value: Date
|
||||
|
||||
@@ -23,14 +23,26 @@ const baseClass = 'where-builder'
|
||||
const reduceFields = (fields, i18n) =>
|
||||
flattenTopLevelFields(fields).reduce((reduced, field) => {
|
||||
if (typeof fieldTypes[field.type] === 'object') {
|
||||
const operatorKeys = new Set()
|
||||
const operators = fieldTypes[field.type].operators.reduce((acc, operator) => {
|
||||
if (!operatorKeys.has(operator.value)) {
|
||||
operatorKeys.add(operator.value)
|
||||
return [
|
||||
...acc,
|
||||
{
|
||||
...operator,
|
||||
label: i18n.t(`operators:${operator.label}`),
|
||||
},
|
||||
]
|
||||
}
|
||||
return acc
|
||||
}, [])
|
||||
|
||||
const formattedField = {
|
||||
label: getTranslation(field.label || field.name, i18n),
|
||||
value: field.name,
|
||||
...fieldTypes[field.type],
|
||||
operators: fieldTypes[field.type].operators.map((operator) => ({
|
||||
...operator,
|
||||
label: i18n.t(`operators:${operator.label}`),
|
||||
})),
|
||||
operators,
|
||||
props: {
|
||||
...field,
|
||||
},
|
||||
|
||||
@@ -9,8 +9,8 @@ import FieldDescription from '../../FieldDescription'
|
||||
import DefaultLabel from '../../Label'
|
||||
import useField from '../../useField'
|
||||
import withCondition from '../../withCondition'
|
||||
import './index.scss'
|
||||
import { fieldBaseClass } from '../shared'
|
||||
import './index.scss'
|
||||
|
||||
const baseClass = 'json-field'
|
||||
|
||||
@@ -19,13 +19,13 @@ const JSONField: React.FC<Props> = (props) => {
|
||||
name,
|
||||
admin: {
|
||||
className,
|
||||
components: { Error, Label } = {},
|
||||
condition,
|
||||
description,
|
||||
editorOptions,
|
||||
readOnly,
|
||||
style,
|
||||
width,
|
||||
components: { Error, Label } = {},
|
||||
} = {},
|
||||
label,
|
||||
path: pathFromProps,
|
||||
@@ -69,8 +69,8 @@ const JSONField: React.FC<Props> = (props) => {
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
setStringValue(JSON.stringify(initialValue, null, 2))
|
||||
}, [initialValue])
|
||||
setStringValue(JSON.stringify(value ? value : initialValue, null, 2))
|
||||
}, [initialValue, value])
|
||||
|
||||
return (
|
||||
<div
|
||||
|
||||
@@ -40,13 +40,14 @@ const Relationship: React.FC<Props> = (props) => {
|
||||
admin: {
|
||||
allowCreate = true,
|
||||
className,
|
||||
components: { Error, Label } = {},
|
||||
condition,
|
||||
description,
|
||||
isSortable = true,
|
||||
readOnly,
|
||||
sortOptions,
|
||||
style,
|
||||
width,
|
||||
components: { Error, Label } = {},
|
||||
} = {},
|
||||
filterOptions,
|
||||
hasMany,
|
||||
@@ -139,7 +140,14 @@ const Relationship: React.FC<Props> = (props) => {
|
||||
|
||||
if (resultsFetched < 10) {
|
||||
const collection = collections.find((coll) => coll.slug === relation)
|
||||
const fieldToSearch = collection?.admin?.useAsTitle || 'id'
|
||||
let fieldToSearch = collection?.defaultSort || collection?.admin?.useAsTitle || 'id'
|
||||
if (!searchArg) {
|
||||
if (typeof sortOptions === 'string') {
|
||||
fieldToSearch = sortOptions
|
||||
} else if (sortOptions?.[relation]) {
|
||||
fieldToSearch = sortOptions[relation]
|
||||
}
|
||||
}
|
||||
|
||||
const query: {
|
||||
[key: string]: unknown
|
||||
@@ -236,6 +244,7 @@ const Relationship: React.FC<Props> = (props) => {
|
||||
locale,
|
||||
filterOptionsResult,
|
||||
serverURL,
|
||||
sortOptions,
|
||||
api,
|
||||
i18n,
|
||||
config,
|
||||
@@ -252,7 +261,7 @@ const Relationship: React.FC<Props> = (props) => {
|
||||
(searchArg: string, valueArg: Value | Value[]) => {
|
||||
if (search !== searchArg) {
|
||||
setLastLoadedPage({})
|
||||
updateSearch(searchArg, valueArg)
|
||||
updateSearch(searchArg, valueArg, searchArg !== '')
|
||||
}
|
||||
},
|
||||
[search, updateSearch],
|
||||
|
||||
@@ -2,11 +2,12 @@ import React from 'react'
|
||||
|
||||
import type { RichTextField } from '../../../../../fields/config/types'
|
||||
import type { RichTextAdapter } from './types'
|
||||
|
||||
const RichText: React.FC<RichTextField> = (props) => {
|
||||
const RichText: React.FC<RichTextField> = (fieldprops) => {
|
||||
// eslint-disable-next-line react/destructuring-assignment
|
||||
const editor: RichTextAdapter = props.editor
|
||||
return <editor.FieldComponent {...props} />
|
||||
const editor: RichTextAdapter = fieldprops.editor
|
||||
const { FieldComponent } = editor
|
||||
|
||||
return <FieldComponent {...fieldprops} />
|
||||
}
|
||||
|
||||
export default RichText
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { JSONSchema4 } from 'json-schema'
|
||||
|
||||
import type { PayloadRequest } from '../../../../../express/types'
|
||||
import type { RequestContext } from '../../../../../express/types'
|
||||
import type { RichTextField, Validate } from '../../../../../fields/config/types'
|
||||
import type { CellComponentProps } from '../../../views/collections/List/Cell/types'
|
||||
|
||||
@@ -31,7 +32,7 @@ export type RichTextAdapter<
|
||||
siblingDoc: Record<string, unknown>
|
||||
}) => Promise<void> | null
|
||||
|
||||
outputSchema: ({
|
||||
outputSchema?: ({
|
||||
field,
|
||||
isRequired,
|
||||
}: {
|
||||
@@ -39,10 +40,14 @@ export type RichTextAdapter<
|
||||
isRequired: boolean
|
||||
}) => JSONSchema4
|
||||
populationPromise?: (data: {
|
||||
context: RequestContext
|
||||
currentDepth?: number
|
||||
depth: number
|
||||
field: RichTextField<Value, AdapterProps, ExtraFieldProperties>
|
||||
findMany: boolean
|
||||
flattenLocales: boolean
|
||||
overrideAccess?: boolean
|
||||
populationPromises: Promise<void>[]
|
||||
req: PayloadRequest
|
||||
showHiddenFields: boolean
|
||||
siblingDoc: Record<string, unknown>
|
||||
|
||||
@@ -95,7 +95,7 @@ const TabsField: React.FC<Props> = (props) => {
|
||||
: existingPreferences?.fields?.[tabsPrefKey]?.tabIndex
|
||||
setActiveTabIndex(initialIndex || 0)
|
||||
}
|
||||
getInitialPref()
|
||||
void getInitialPref()
|
||||
}, [path, indexPath, getPreference, preferencesKey, tabsPrefKey])
|
||||
|
||||
const handleTabChange = useCallback(
|
||||
@@ -193,7 +193,11 @@ const TabsField: React.FC<Props> = (props) => {
|
||||
fieldTypes={fieldTypes}
|
||||
forceRender={forceRender}
|
||||
indexPath={indexPath}
|
||||
key={String(activeTabConfig.label)}
|
||||
key={
|
||||
activeTabConfig.label
|
||||
? getTranslation(activeTabConfig.label, i18n)
|
||||
: activeTabConfig['name']
|
||||
}
|
||||
margins="small"
|
||||
permissions={
|
||||
tabHasName(activeTabConfig) && permissions?.[activeTabConfig.name]
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
import React, { createContext, useContext, useState } from 'react'
|
||||
|
||||
import type { UpdatedDocument } from './types'
|
||||
|
||||
const Context = createContext({
|
||||
mostRecentUpdate: null,
|
||||
reportUpdate: (doc: UpdatedDocument) => null, // eslint-disable-line @typescript-eslint/no-unused-vars
|
||||
})
|
||||
|
||||
export const DocumentEventsProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
||||
const [mostRecentUpdate, reportUpdate] = useState<UpdatedDocument>(null)
|
||||
|
||||
return <Context.Provider value={{ mostRecentUpdate, reportUpdate }}>{children}</Context.Provider>
|
||||
}
|
||||
|
||||
export const useDocumentEvents = () => useContext(Context)
|
||||
@@ -0,0 +1,10 @@
|
||||
export type UpdatedDocument = {
|
||||
entitySlug: string
|
||||
id?: string
|
||||
updatedAt: string
|
||||
}
|
||||
|
||||
export type DocumentEventsContext = {
|
||||
mostRecentUpdate: UpdatedDocument
|
||||
reportUpdate: (updatedDocument: Array<UpdatedDocument>) => void
|
||||
}
|
||||
@@ -11,6 +11,7 @@ import buildStateFromSchema from '../../forms/Form/buildStateFromSchema'
|
||||
import { fieldTypes } from '../../forms/field-types'
|
||||
import { useAuth } from '../../utilities/Auth'
|
||||
import { useConfig } from '../../utilities/Config'
|
||||
import { useDocumentEvents } from '../../utilities/DocumentEvents'
|
||||
import { useDocumentInfo } from '../../utilities/DocumentInfo'
|
||||
import { EditDepthContext } from '../../utilities/EditDepth'
|
||||
import { useLocale } from '../../utilities/Locale'
|
||||
@@ -37,10 +38,17 @@ const GlobalView: React.FC<IndexProps> = (props) => {
|
||||
serverURL,
|
||||
} = useConfig()
|
||||
|
||||
const { reportUpdate } = useDocumentEvents()
|
||||
|
||||
const { admin: { components: { views: { Edit: Edit } = {} } = {} } = {}, fields, slug } = global
|
||||
|
||||
const onSave = useCallback(
|
||||
async (json) => {
|
||||
reportUpdate({
|
||||
entitySlug: global.slug,
|
||||
updatedAt: json?.result?.updatedAt || new Date().toISOString(),
|
||||
})
|
||||
|
||||
getVersions()
|
||||
getDocPermissions()
|
||||
setUpdatedAt(json?.result?.updatedAt)
|
||||
@@ -59,7 +67,18 @@ const GlobalView: React.FC<IndexProps> = (props) => {
|
||||
})
|
||||
setInitialState(state)
|
||||
},
|
||||
[getVersions, fields, user, locale, t, getDocPermissions, getDocPreferences, config],
|
||||
[
|
||||
getVersions,
|
||||
fields,
|
||||
user,
|
||||
locale,
|
||||
t,
|
||||
getDocPermissions,
|
||||
getDocPreferences,
|
||||
config,
|
||||
global,
|
||||
reportUpdate,
|
||||
],
|
||||
)
|
||||
|
||||
const [{ data, isLoading: isLoadingData }] = usePayloadAPI(`${serverURL}${api}/globals/${slug}`, {
|
||||
|
||||
@@ -4,6 +4,7 @@ import type { EditViewProps } from '../../types'
|
||||
|
||||
import { useAllFormFields } from '../../../forms/Form/context'
|
||||
import reduceFieldsToValues from '../../../forms/Form/reduceFieldsToValues'
|
||||
import { useDocumentEvents } from '../../../utilities/DocumentEvents'
|
||||
import { useLivePreviewContext } from '../Context/context'
|
||||
import { DeviceContainer } from '../Device'
|
||||
import { IFrame } from '../IFrame'
|
||||
@@ -23,6 +24,8 @@ export const LivePreview: React.FC<EditViewProps> = (props) => {
|
||||
url,
|
||||
} = useLivePreviewContext()
|
||||
|
||||
const { mostRecentUpdate } = useDocumentEvents()
|
||||
|
||||
const { breakpoint, fieldSchemaJSON } = useLivePreviewContext()
|
||||
|
||||
const prevWindowType =
|
||||
@@ -49,6 +52,7 @@ export const LivePreview: React.FC<EditViewProps> = (props) => {
|
||||
|
||||
const message = JSON.stringify({
|
||||
data: values,
|
||||
externallyUpdatedRelationship: mostRecentUpdate,
|
||||
fieldSchemaJSON: shouldSendSchema ? fieldSchemaJSON : undefined,
|
||||
type: 'payload-live-preview',
|
||||
})
|
||||
@@ -73,6 +77,7 @@ export const LivePreview: React.FC<EditViewProps> = (props) => {
|
||||
iframeRef,
|
||||
setIframeHasLoaded,
|
||||
fieldSchemaJSON,
|
||||
mostRecentUpdate,
|
||||
])
|
||||
|
||||
if (previewWindowType === 'iframe') {
|
||||
|
||||
@@ -9,6 +9,7 @@ import { DocumentHeader } from '../../../elements/DocumentHeader'
|
||||
import { FormLoadingOverlayToggle } from '../../../elements/Loading'
|
||||
import Form from '../../../forms/Form'
|
||||
import { useAuth } from '../../../utilities/Auth'
|
||||
import { useDocumentEvents } from '../../../utilities/DocumentEvents'
|
||||
import { OperationContext } from '../../../utilities/OperationProvider'
|
||||
import { CollectionRoutes } from './Routes'
|
||||
import { CustomCollectionComponent } from './Routes/CustomComponent'
|
||||
@@ -42,12 +43,19 @@ const DefaultEditView: React.FC<DefaultEditViewProps> = (props) => {
|
||||
onSave: onSaveFromProps,
|
||||
} = props
|
||||
|
||||
const { reportUpdate } = useDocumentEvents()
|
||||
|
||||
const { auth } = collection
|
||||
|
||||
const classes = [baseClass, isEditing && `${baseClass}--is-editing`].filter(Boolean).join(' ')
|
||||
|
||||
const onSave = useCallback(
|
||||
async (json) => {
|
||||
reportUpdate({
|
||||
id,
|
||||
entitySlug: collection.slug,
|
||||
updatedAt: json?.result?.updatedAt || new Date().toISOString(),
|
||||
})
|
||||
if (auth && id === user.id) {
|
||||
await refreshCookieAsync()
|
||||
}
|
||||
@@ -59,7 +67,7 @@ const DefaultEditView: React.FC<DefaultEditViewProps> = (props) => {
|
||||
})
|
||||
}
|
||||
},
|
||||
[id, onSaveFromProps, auth, user, refreshCookieAsync],
|
||||
[id, onSaveFromProps, auth, user, refreshCookieAsync, collection, reportUpdate],
|
||||
)
|
||||
|
||||
const operation = isEditing ? 'update' : 'create'
|
||||
|
||||
@@ -7,8 +7,9 @@ import type { CellComponentProps } from '../../types'
|
||||
const RichTextCell: React.FC<CellComponentProps<RichTextField>> = (props) => {
|
||||
// eslint-disable-next-line react/destructuring-assignment
|
||||
const editor: RichTextAdapter = props.field.editor
|
||||
const { CellComponent } = editor
|
||||
|
||||
return <editor.CellComponent {...props} />
|
||||
return <CellComponent {...props} />
|
||||
}
|
||||
|
||||
export default RichTextCell
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
/* eslint-disable react/destructuring-assignment */
|
||||
import type { Request } from 'express'
|
||||
|
||||
import type { SanitizedConfig } from '../config/types'
|
||||
@@ -8,22 +7,31 @@ import parseCookies from '../utilities/parseCookies'
|
||||
const getExtractJWT =
|
||||
(config: SanitizedConfig) =>
|
||||
(req: Request): null | string => {
|
||||
if (req && req.get) {
|
||||
const jwtFromHeader = req.get('Authorization')
|
||||
const origin = req.get('Origin')
|
||||
if (!req?.get) {
|
||||
return null
|
||||
}
|
||||
|
||||
if (jwtFromHeader && jwtFromHeader.indexOf('JWT ') === 0) {
|
||||
return jwtFromHeader.replace('JWT ', '')
|
||||
}
|
||||
const jwtFromHeader = req.get('Authorization')
|
||||
const origin = req.get('Origin')
|
||||
|
||||
const cookies = parseCookies(req)
|
||||
const tokenCookieName = `${config.cookiePrefix}-token`
|
||||
if (jwtFromHeader?.indexOf('JWT ') === 0) {
|
||||
return jwtFromHeader.replace('JWT ', '')
|
||||
}
|
||||
// allow RFC6750 OAuth 2.0 compliant Bearer tokens
|
||||
// in addition to the payload default JWT format
|
||||
if (jwtFromHeader?.indexOf('Bearer ') === 0) {
|
||||
return jwtFromHeader.replace('Bearer ', '')
|
||||
}
|
||||
|
||||
if (cookies && cookies[tokenCookieName]) {
|
||||
if (!origin || config.csrf.length === 0 || config.csrf.indexOf(origin) > -1) {
|
||||
return cookies[tokenCookieName]
|
||||
}
|
||||
}
|
||||
const cookies = parseCookies(req)
|
||||
const tokenCookieName = `${config.cookiePrefix}-token`
|
||||
|
||||
if (!cookies?.[tokenCookieName]) {
|
||||
return null
|
||||
}
|
||||
|
||||
if (!origin || config.csrf.length === 0 || config.csrf.indexOf(origin) > -1) {
|
||||
return cookies[tokenCookieName]
|
||||
}
|
||||
|
||||
return null
|
||||
|
||||
@@ -97,7 +97,7 @@ export default joi.object({
|
||||
CellComponent: component.required(),
|
||||
FieldComponent: component.required(),
|
||||
afterReadPromise: joi.func().optional(),
|
||||
outputSchema: joi.func().required(),
|
||||
outputSchema: joi.func().optional(),
|
||||
populationPromise: joi.func().optional(),
|
||||
validate: joi.func().required(),
|
||||
})
|
||||
|
||||
@@ -13,22 +13,28 @@ export const readMigrationFiles = async ({
|
||||
payload: Payload
|
||||
}): Promise<Migration[]> => {
|
||||
if (!fs.existsSync(payload.db.migrationDir)) {
|
||||
payload.logger.debug({
|
||||
payload.logger.error({
|
||||
msg: `No migration directory found at ${payload.db.migrationDir}`,
|
||||
})
|
||||
return []
|
||||
}
|
||||
|
||||
payload.logger.info({
|
||||
msg: `Reading migration files from ${payload.db.migrationDir}`,
|
||||
})
|
||||
|
||||
const files = fs
|
||||
.readdirSync(payload.db.migrationDir)
|
||||
.sort()
|
||||
.filter((f) => f.endsWith('.ts'))
|
||||
.filter((f) => {
|
||||
return f.endsWith('.ts') || f.endsWith('.js')
|
||||
})
|
||||
.map((file) => {
|
||||
return path.resolve(payload.db.migrationDir, file)
|
||||
})
|
||||
|
||||
return files.map((filePath) => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires,import/no-dynamic-require
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const migration = require(filePath) as Migration
|
||||
migration.name = path.basename(filePath).split('.')?.[0]
|
||||
return migration
|
||||
|
||||
@@ -33,7 +33,11 @@ type Args = {
|
||||
const flattenWhere = (query: Where): WhereField[] =>
|
||||
Object.entries(query).reduce((flattenedConstraints, [key, val]) => {
|
||||
if ((key === 'and' || key === 'or') && Array.isArray(val)) {
|
||||
return [...flattenedConstraints, ...val.map((subVal) => flattenWhere(subVal))]
|
||||
const subWhereConstraints: Where[] = val.reduce((acc, subVal) => {
|
||||
const subWhere = flattenWhere(subVal)
|
||||
return [...acc, ...subWhere]
|
||||
}, [])
|
||||
return [...flattenedConstraints, ...subWhereConstraints]
|
||||
}
|
||||
|
||||
return [...flattenedConstraints, { [key]: val }]
|
||||
|
||||
@@ -116,12 +116,24 @@ export async function validateSearchParam({
|
||||
const segments = fieldPath.split('.')
|
||||
|
||||
if (versionFields) {
|
||||
if (fieldPath === 'parent' || fieldPath === 'version') {
|
||||
fieldAccess = policies[entityType][entitySlug].read.permission
|
||||
} else if (segments[0] === 'parent' || segments[0] === 'version') {
|
||||
fieldAccess = policies[entityType][entitySlug].read.permission
|
||||
fieldAccess = policies[entityType][entitySlug]
|
||||
if (segments[0] === 'parent' || segments[0] === 'version') {
|
||||
segments.shift()
|
||||
} else {
|
||||
segments.forEach((segment, pathIndex) => {
|
||||
if (fieldAccess[segment]) {
|
||||
if (pathIndex === segments.length - 1) {
|
||||
fieldAccess = fieldAccess[segment]
|
||||
} else if ('fields' in fieldAccess[segment]) {
|
||||
fieldAccess = fieldAccess[segment].fields
|
||||
} else if ('blocks' in fieldAccess[segment]) {
|
||||
fieldAccess = fieldAccess[segment]
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fieldAccess = fieldAccess.read.permission
|
||||
} else {
|
||||
fieldAccess = policies[entityType][entitySlug].fields
|
||||
|
||||
@@ -129,10 +141,14 @@ export async function validateSearchParam({
|
||||
fieldAccess = fieldAccess[field.name]
|
||||
} else {
|
||||
segments.forEach((segment, pathIndex) => {
|
||||
if (pathIndex === segments.length - 1) {
|
||||
fieldAccess = fieldAccess[segment]
|
||||
} else {
|
||||
fieldAccess = fieldAccess[segment].fields
|
||||
if (fieldAccess[segment]) {
|
||||
if (pathIndex === segments.length - 1) {
|
||||
fieldAccess = fieldAccess[segment]
|
||||
} else if ('fields' in fieldAccess[segment]) {
|
||||
fieldAccess = fieldAccess[segment].fields
|
||||
} else if ('blocks' in fieldAccess[segment]) {
|
||||
fieldAccess = fieldAccess[segment]
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
export { useStepNav } from '../../admin/components/elements/StepNav'
|
||||
export { useTableColumns } from '../../admin/components/elements/TableColumns'
|
||||
export { useDocumentEvents } from '../../admin/components/utilities/DocumentEvents'
|
||||
export { default as useDebounce } from '../../admin/hooks/useDebounce'
|
||||
export { useDebouncedCallback } from '../../admin/hooks/useDebouncedCallback'
|
||||
export { useDelay } from '../../admin/hooks/useDelay'
|
||||
|
||||
@@ -80,6 +80,7 @@ export type {
|
||||
Option,
|
||||
OptionObject,
|
||||
PointField,
|
||||
PolymorphicRelationshipField,
|
||||
RadioField,
|
||||
RelationshipField,
|
||||
RelationshipValue,
|
||||
@@ -87,6 +88,7 @@ export type {
|
||||
RowAdmin,
|
||||
RowField,
|
||||
SelectField,
|
||||
SingleRelationshipField,
|
||||
Tab,
|
||||
TabAsField,
|
||||
TabsAdmin,
|
||||
|
||||
@@ -1,21 +1,24 @@
|
||||
export { withMergedProps } from '../admin/components/utilities/WithMergedProps'
|
||||
export { extractTranslations } from '../translations/extractTranslations'
|
||||
export { promise as afterReadPromise } from '../fields/hooks/afterRead/promise'
|
||||
export { traverseFields as afterReadTraverseFields } from '../fields/hooks/afterRead/traverseFields'
|
||||
|
||||
export { extractTranslations } from '../translations/extractTranslations'
|
||||
export { i18nInit } from '../translations/init'
|
||||
export { combineMerge } from '../utilities/combineMerge'
|
||||
|
||||
export {
|
||||
configToJSONSchema,
|
||||
entityToJSONSchema,
|
||||
withNullableJSONSchemaType,
|
||||
} from '../utilities/configToJSONSchema'
|
||||
|
||||
export { createArrayFromCommaDelineated } from '../utilities/createArrayFromCommaDelineated'
|
||||
export { deepCopyObject } from '../utilities/deepCopyObject'
|
||||
|
||||
export { deepCopyObject } from '../utilities/deepCopyObject'
|
||||
export { deepMerge } from '../utilities/deepMerge'
|
||||
export { fieldSchemaToJSON } from '../utilities/fieldSchemaToJSON'
|
||||
export { default as flattenTopLevelFields } from '../utilities/flattenTopLevelFields'
|
||||
export { formatLabels, formatNames, toWords } from '../utilities/formatLabels'
|
||||
export { getIDType } from '../utilities/getIDType'
|
||||
export { getTranslation } from '../utilities/getTranslation'
|
||||
|
||||
export { isValidID } from '../utilities/isValidID'
|
||||
|
||||
@@ -366,6 +366,11 @@ export const relationship = baseField.keys({
|
||||
Label: componentSchema,
|
||||
}),
|
||||
isSortable: joi.boolean().default(false),
|
||||
sortOptions: joi.alternatives().conditional(joi.ref('...relationTo'), {
|
||||
is: joi.string(),
|
||||
otherwise: joi.object().pattern(joi.string(), joi.string()),
|
||||
then: joi.string(),
|
||||
}),
|
||||
}),
|
||||
defaultValue: joi.alternatives().try(joi.func()),
|
||||
filterOptions: joi.alternatives().try(joi.object(), joi.func()),
|
||||
@@ -434,7 +439,7 @@ export const richText = baseField.keys({
|
||||
CellComponent: componentSchema.required(),
|
||||
FieldComponent: componentSchema.required(),
|
||||
afterReadPromise: joi.func().optional(),
|
||||
outputSchema: joi.func().required(),
|
||||
outputSchema: joi.func().optional(),
|
||||
populationPromise: joi.func().optional(),
|
||||
validate: joi.func().required(),
|
||||
})
|
||||
|
||||
@@ -430,19 +430,10 @@ export type SelectField = FieldBase & {
|
||||
type: 'select'
|
||||
}
|
||||
|
||||
export type RelationshipField = FieldBase & {
|
||||
admin?: Admin & {
|
||||
allowCreate?: boolean
|
||||
components?: {
|
||||
Error?: React.ComponentType<ErrorProps>
|
||||
Label?: React.ComponentType<LabelProps>
|
||||
}
|
||||
isSortable?: boolean
|
||||
}
|
||||
type SharedRelationshipProperties = FieldBase & {
|
||||
filterOptions?: FilterOptions
|
||||
hasMany?: boolean
|
||||
maxDepth?: number
|
||||
relationTo: string | string[]
|
||||
type: 'relationship'
|
||||
} & (
|
||||
| {
|
||||
@@ -473,6 +464,28 @@ export type RelationshipField = FieldBase & {
|
||||
}
|
||||
)
|
||||
|
||||
type RelationshipAdmin = Admin & {
|
||||
allowCreate?: boolean
|
||||
components?: {
|
||||
Error?: React.ComponentType<ErrorProps>
|
||||
Label?: React.ComponentType<LabelProps>
|
||||
}
|
||||
isSortable?: boolean
|
||||
}
|
||||
export type PolymorphicRelationshipField = SharedRelationshipProperties & {
|
||||
admin?: RelationshipAdmin & {
|
||||
sortOptions?: { [collectionSlug: string]: string }
|
||||
}
|
||||
relationTo: string[]
|
||||
}
|
||||
export type SingleRelationshipField = SharedRelationshipProperties & {
|
||||
admin?: RelationshipAdmin & {
|
||||
sortOptions?: string
|
||||
}
|
||||
relationTo: string
|
||||
}
|
||||
export type RelationshipField = PolymorphicRelationshipField | SingleRelationshipField
|
||||
|
||||
export type ValueWithRelation = {
|
||||
relationTo: string
|
||||
value: number | string
|
||||
|
||||
@@ -25,12 +25,14 @@ type Args = {
|
||||
req: PayloadRequest
|
||||
showHiddenFields: boolean
|
||||
siblingDoc: Record<string, unknown>
|
||||
triggerAccessControl?: boolean
|
||||
triggerHooks?: boolean
|
||||
}
|
||||
|
||||
// This function is responsible for the following actions, in order:
|
||||
// - Remove hidden fields from response
|
||||
// - Flatten locales into requested locale
|
||||
// - Sanitize outgoing data (point field, etc)
|
||||
// - Sanitize outgoing data (point field, etc.)
|
||||
// - Execute field hooks
|
||||
// - Execute read access control
|
||||
// - Populate relationships
|
||||
@@ -51,6 +53,8 @@ export const promise = async ({
|
||||
req,
|
||||
showHiddenFields,
|
||||
siblingDoc,
|
||||
triggerAccessControl = true,
|
||||
triggerHooks = true,
|
||||
}: Args): Promise<void> => {
|
||||
if (
|
||||
fieldAffectsData(field) &&
|
||||
@@ -138,10 +142,14 @@ export const promise = async ({
|
||||
// This is run here AND in the GraphQL Resolver
|
||||
if (editor?.populationPromise) {
|
||||
const populationPromise = editor.populationPromise({
|
||||
context,
|
||||
currentDepth,
|
||||
depth,
|
||||
field,
|
||||
findMany,
|
||||
flattenLocales,
|
||||
overrideAccess,
|
||||
populationPromises,
|
||||
req,
|
||||
showHiddenFields,
|
||||
siblingDoc,
|
||||
@@ -186,7 +194,7 @@ export const promise = async ({
|
||||
|
||||
if (fieldAffectsData(field)) {
|
||||
// Execute hooks
|
||||
if (field.hooks?.afterRead) {
|
||||
if (triggerHooks && field.hooks?.afterRead) {
|
||||
await field.hooks.afterRead.reduce(async (priorHook, currentHook) => {
|
||||
await priorHook
|
||||
|
||||
@@ -241,7 +249,7 @@ export const promise = async ({
|
||||
}
|
||||
|
||||
// Execute access control
|
||||
if (field.access && field.access.read) {
|
||||
if (triggerAccessControl && field.access && field.access.read) {
|
||||
const result = overrideAccess
|
||||
? true
|
||||
: await field.access.read({
|
||||
@@ -293,6 +301,8 @@ export const promise = async ({
|
||||
req,
|
||||
showHiddenFields,
|
||||
siblingDoc: groupDoc,
|
||||
triggerAccessControl,
|
||||
triggerHooks,
|
||||
})
|
||||
|
||||
break
|
||||
@@ -319,6 +329,8 @@ export const promise = async ({
|
||||
req,
|
||||
showHiddenFields,
|
||||
siblingDoc: row || {},
|
||||
triggerAccessControl,
|
||||
triggerHooks,
|
||||
})
|
||||
})
|
||||
} else if (!shouldHoistLocalizedValue && typeof rows === 'object' && rows !== null) {
|
||||
@@ -341,6 +353,8 @@ export const promise = async ({
|
||||
req,
|
||||
showHiddenFields,
|
||||
siblingDoc: row || {},
|
||||
triggerAccessControl,
|
||||
triggerHooks,
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -375,6 +389,8 @@ export const promise = async ({
|
||||
req,
|
||||
showHiddenFields,
|
||||
siblingDoc: row || {},
|
||||
triggerAccessControl,
|
||||
triggerHooks,
|
||||
})
|
||||
}
|
||||
})
|
||||
@@ -401,6 +417,8 @@ export const promise = async ({
|
||||
req,
|
||||
showHiddenFields,
|
||||
siblingDoc: row || {},
|
||||
triggerAccessControl,
|
||||
triggerHooks,
|
||||
})
|
||||
}
|
||||
})
|
||||
@@ -431,6 +449,8 @@ export const promise = async ({
|
||||
req,
|
||||
showHiddenFields,
|
||||
siblingDoc,
|
||||
triggerAccessControl,
|
||||
triggerHooks,
|
||||
})
|
||||
|
||||
break
|
||||
@@ -443,7 +463,7 @@ export const promise = async ({
|
||||
if (typeof siblingDoc[field.name] !== 'object') tabDoc = {}
|
||||
}
|
||||
|
||||
await traverseFields({
|
||||
traverseFields({
|
||||
collection,
|
||||
context,
|
||||
currentDepth,
|
||||
@@ -459,6 +479,8 @@ export const promise = async ({
|
||||
req,
|
||||
showHiddenFields,
|
||||
siblingDoc: tabDoc,
|
||||
triggerAccessControl,
|
||||
triggerHooks,
|
||||
})
|
||||
|
||||
break
|
||||
@@ -481,6 +503,8 @@ export const promise = async ({
|
||||
req,
|
||||
showHiddenFields,
|
||||
siblingDoc,
|
||||
triggerAccessControl,
|
||||
triggerHooks,
|
||||
})
|
||||
break
|
||||
}
|
||||
|
||||
@@ -21,6 +21,8 @@ type Args = {
|
||||
req: PayloadRequest
|
||||
showHiddenFields: boolean
|
||||
siblingDoc: Record<string, unknown>
|
||||
triggerAccessControl?: boolean
|
||||
triggerHooks?: boolean
|
||||
}
|
||||
|
||||
export const traverseFields = ({
|
||||
@@ -39,6 +41,8 @@ export const traverseFields = ({
|
||||
req,
|
||||
showHiddenFields,
|
||||
siblingDoc,
|
||||
triggerAccessControl = true,
|
||||
triggerHooks = true,
|
||||
}: Args): void => {
|
||||
fields.forEach((field) => {
|
||||
fieldPromises.push(
|
||||
@@ -58,6 +62,8 @@ export const traverseFields = ({
|
||||
req,
|
||||
showHiddenFields,
|
||||
siblingDoc,
|
||||
triggerAccessControl,
|
||||
triggerHooks,
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
@@ -413,6 +413,11 @@ describe('Field Validations', () => {
|
||||
const result = number(val, numberOptions)
|
||||
expect(result).toBe(true)
|
||||
})
|
||||
it('should validate 0', () => {
|
||||
const val = 0
|
||||
const result = number(val, { ...numberOptions, required: true })
|
||||
expect(result).toBe(true)
|
||||
})
|
||||
it('should validate 2', () => {
|
||||
const val = 1.5
|
||||
const result = number(val, numberOptions)
|
||||
|
||||
@@ -205,8 +205,11 @@ export const number: Validate<unknown, unknown, NumberField> = (
|
||||
if (typeof lengthValidationResult === 'string') return lengthValidationResult
|
||||
}
|
||||
|
||||
if (!value && required) return t('validation:required')
|
||||
if (!value && !required) return true
|
||||
if (!value && !isNumber(value)) {
|
||||
// if no value is present, validate based on required
|
||||
if (required) return t('validation:required')
|
||||
if (!required) return true
|
||||
}
|
||||
|
||||
const numbersToValidate: number[] = Array.isArray(value) ? value : [value]
|
||||
|
||||
|
||||
@@ -436,8 +436,13 @@ function buildObjectType({
|
||||
// Effectively, this means that the populationPromise for GraphQL is only run here, and not in the find.ts resolver / normal population promise.
|
||||
if (editor?.populationPromise) {
|
||||
await editor?.populationPromise({
|
||||
context,
|
||||
depth,
|
||||
field,
|
||||
findMany: false,
|
||||
flattenLocales: false,
|
||||
overrideAccess: false,
|
||||
populationPromises: [],
|
||||
req: context.req,
|
||||
showHiddenFields: false,
|
||||
siblingDoc: parent,
|
||||
|
||||
@@ -69,6 +69,8 @@
|
||||
"invalidFileType": "Invalid file type",
|
||||
"invalidFileTypeValue": "Invalid file type: {{value}}",
|
||||
"loadingDocument": "There was a problem loading the document with ID of {{id}}.",
|
||||
"localesNotSaved_one": "The following locale could not be saved:",
|
||||
"localesNotSaved_other": "The following locales could not be saved:",
|
||||
"missingEmail": "Missing email.",
|
||||
"missingIDOfDocument": "Missing ID of document to update.",
|
||||
"missingIDOfVersion": "Missing ID of version.",
|
||||
|
||||
@@ -11,12 +11,15 @@ import hr from './hr.json'
|
||||
import hu from './hu.json'
|
||||
import it from './it.json'
|
||||
import ja from './ja.json'
|
||||
import ko from './ko.json'
|
||||
import my from './my.json'
|
||||
import nb from './nb.json'
|
||||
import nl from './nl.json'
|
||||
import pl from './pl.json'
|
||||
import pt from './pt.json'
|
||||
import ro from './ro.json'
|
||||
import rs from './rs.json'
|
||||
import rsLatin from './rs-latin.json'
|
||||
import ru from './ru.json'
|
||||
import sv from './sv.json'
|
||||
import th from './th.json'
|
||||
@@ -39,12 +42,15 @@ export default {
|
||||
hu,
|
||||
it,
|
||||
ja,
|
||||
ko,
|
||||
my,
|
||||
nb,
|
||||
nl,
|
||||
pl,
|
||||
pt,
|
||||
ro,
|
||||
rs,
|
||||
rsLatin,
|
||||
ru,
|
||||
sv,
|
||||
th,
|
||||
|
||||
371
packages/payload/src/translations/ko.json
Normal file
371
packages/payload/src/translations/ko.json
Normal file
@@ -0,0 +1,371 @@
|
||||
{
|
||||
"authentication": {
|
||||
"account": "계정",
|
||||
"accountOfCurrentUser": "현재 사용자의 계정",
|
||||
"alreadyActivated": "이미 활성화됨",
|
||||
"alreadyLoggedIn": "이미 로그인됨",
|
||||
"apiKey": "API 키",
|
||||
"backToLogin": "로그인 화면으로 돌아가기",
|
||||
"beginCreateFirstUser": "시작하려면 첫 번째 사용자를 생성하세요.",
|
||||
"changePassword": "비밀번호 변경",
|
||||
"checkYourEmailForPasswordReset": "비밀번호 재설정을 안전하게 수행할 수 있는 링크가 포함된 이메일을 확인하세요.",
|
||||
"confirmGeneration": "생성 확인",
|
||||
"confirmPassword": "비밀번호 확인",
|
||||
"createFirstUser": "첫 번째 사용자 생성",
|
||||
"emailNotValid": "입력한 이메일은 유효하지 않습니다.",
|
||||
"emailSent": "이메일 전송됨",
|
||||
"enableAPIKey": "API 키 활성화",
|
||||
"failedToUnlock": "잠금 해제 실패",
|
||||
"forceUnlock": "강제 잠금 해제",
|
||||
"forgotPassword": "비밀번호를 잊으셨나요?",
|
||||
"forgotPasswordEmailInstructions": "아래에 이메일을 입력하세요. 비밀번호를 재설정하는 방법에 대한 안내가 포함된 이메일 메시지를 받게 될 것입니다.",
|
||||
"forgotPasswordQuestion": "비밀번호를 잊으셨나요?",
|
||||
"generate": "생성",
|
||||
"generateNewAPIKey": "새로운 API 키 생성",
|
||||
"generatingNewAPIKeyWillInvalidate": "새로운 API 키를 생성하면 이전 키가 무효화됩니다. 계속하시겠습니까?",
|
||||
"lockUntil": "잠금 시간",
|
||||
"logBackIn": "다시 로그인",
|
||||
"logOut": "로그아웃",
|
||||
"loggedIn": "다른 사용자로 로그인하려면 먼저 <0>로그아웃</0>해야 합니다.",
|
||||
"loggedInChangePassword": "비밀번호를 변경하려면 <0>계정 화면</0>으로 이동하여 비밀번호를 편집하세요.",
|
||||
"loggedOutInactivity": "보안을 위해 일정 시간 동안 활동하지 않아 로그아웃되었습니다.",
|
||||
"loggedOutSuccessfully": "로그아웃되었습니다.",
|
||||
"login": "로그인",
|
||||
"loginAttempts": "로그인 시도",
|
||||
"loginUser": "현재 사용자 로그인",
|
||||
"loginWithAnotherUser": "다른 사용자로 로그인하려면 먼저 <0>로그아웃</0>해야 합니다.",
|
||||
"logout": "로그아웃",
|
||||
"logoutUser": "현재 사용자 로그아웃",
|
||||
"newAPIKeyGenerated": "새로운 API 키가 생성되었습니다.",
|
||||
"newAccountCreated": "<a href=\"{{serverURL}}\">{{serverURL}}</a>에 접근할 수 있는 새로운 계정이 생성되었습니다. 다음 링크를 클릭하거나 브라우저에 URL을 붙여넣으세요: <a href=\"{{verificationURL}}\">{{verificationURL}}</a><br> 이메일을 확인한 후에 로그인할 수 있습니다.",
|
||||
"newPassword": "새로운 비밀번호",
|
||||
"resetPassword": "비밀번호 재설정",
|
||||
"resetPasswordExpiration": "비밀번호 재설정 만료",
|
||||
"resetPasswordToken": "비밀번호 재설정 토큰",
|
||||
"resetYourPassword": "비밀번호 재설정",
|
||||
"stayLoggedIn": "로그인 상태 유지",
|
||||
"successfullyUnlocked": "잠금 해제 성공",
|
||||
"unableToVerify": "확인할 수 없음",
|
||||
"verified": "확인됨",
|
||||
"verifiedSuccessfully": "성공적으로 확인됨",
|
||||
"verify": "확인",
|
||||
"verifyUser": "현재 사용자 확인",
|
||||
"verifyYourEmail": "이메일을 확인해주세요",
|
||||
"youAreInactive": "잠시 활동하지 않았으며 보안을 위해 곧 자동 로그아웃됩니다. 로그인 상태를 유지하시겠습니까?",
|
||||
"youAreReceivingResetPassword": "당신(혹은 다른 사람)이 계정의 비밀번호 초기화를 요청했기 때문에 이 이메일을 받았습니다. 다음 링크를 클릭하거나 브라우저에 붙여넣어 비밀번호를 초기화하세요:",
|
||||
"youDidNotRequestPassword": "비밀번호 초기화를 요청하지 않았다면 이 이메일을 무시하시고 비밀번호를 변경하지 마세요."
|
||||
},
|
||||
"error": {
|
||||
"accountAlreadyActivated": "이 계정은 이미 활성화되었습니다.",
|
||||
"autosaving": "이 문서를 자동 저장하는 중에 문제가 발생했습니다.",
|
||||
"correctInvalidFields": "입력하신 내용을 확인해주세요.",
|
||||
"deletingFile": "파일을 삭제하는 중에 오류가 발생했습니다.",
|
||||
"deletingTitle": "{{title}} 삭제하는 중에 오류가 발생했습니다. 인터넷 연결을 확인하고 다시 시도하세요.",
|
||||
"emailOrPasswordIncorrect": "입력한 이메일 또는 비밀번호가 올바르지 않습니다.",
|
||||
"followingFieldsInvalid_one": "다음 입력란이 유효하지 않습니다:",
|
||||
"followingFieldsInvalid_other": "다음 입력란이 유효하지 않습니다:",
|
||||
"incorrectCollection": "잘못된 컬렉션",
|
||||
"invalidFileType": "잘못된 파일 형식",
|
||||
"invalidFileTypeValue": "잘못된 파일 형식: {{value}}",
|
||||
"loadingDocument": "ID가 {{id}}인 문서를 불러오는 중에 문제가 발생했습니다.",
|
||||
"missingEmail": "이메일이 누락되었습니다.",
|
||||
"missingIDOfDocument": "업데이트할 문서의 ID가 누락되었습니다.",
|
||||
"missingIDOfVersion": "버전의 ID가 누락되었습니다.",
|
||||
"missingRequiredData": "필수 데이터가 누락되었습니다.",
|
||||
"noFilesUploaded": "파일이 업로드되지 않았습니다.",
|
||||
"noMatchedField": "\"{{label}}\"에 대한 일치하는 입력란이 없습니다.",
|
||||
"noUser": "사용자가 없습니다.",
|
||||
"notAllowedToAccessPage": "이 페이지에 접근할 권한이 없습니다.",
|
||||
"notAllowedToPerformAction": "이 작업을 수행할 권한이 없습니다.",
|
||||
"notFound": "요청한 리소스를 찾을 수 없습니다.",
|
||||
"previewing": "이 문서를 미리보는 중에 문제가 발생했습니다.",
|
||||
"problemUploadingFile": "파일 업로드 중에 문제가 발생했습니다.",
|
||||
"tokenInvalidOrExpired": "토큰이 유효하지 않거나 만료되었습니다.",
|
||||
"unPublishingDocument": "이 문서의 게시 취소 중에 문제가 발생했습니다.",
|
||||
"unableToDeleteCount": "총 {{total}}개 중 {{count}}개의 {{label}}을(를) 삭제할 수 없습니다.",
|
||||
"unableToUpdateCount": "총 {{total}}개 중 {{count}}개의 {{label}}을(를) 업데이트할 수 없습니다.",
|
||||
"unauthorized": "권한 없음, 이 요청을 수행하려면 로그인해야 합니다.",
|
||||
"unknown": "알 수 없는 오류가 발생했습니다.",
|
||||
"unspecific": "오류가 발생했습니다.",
|
||||
"userLocked": "이 사용자는 로그인 실패 횟수가 너무 많아 잠겼습니다.",
|
||||
"valueMustBeUnique": "값은 고유해야 합니다.",
|
||||
"verificationTokenInvalid": "확인 토큰이 유효하지 않습니다."
|
||||
},
|
||||
"fields": {
|
||||
"addLabel": "{{label}} 추가",
|
||||
"addLink": "링크 추가",
|
||||
"addNew": "새로 추가",
|
||||
"addNewLabel": "새로운 {{label}} 추가",
|
||||
"addRelationship": "관계 추가",
|
||||
"addUpload": "업로드 추가",
|
||||
"block": "블록",
|
||||
"blockType": "블록 유형",
|
||||
"blocks": "블록",
|
||||
"chooseBetweenCustomTextOrDocument": "사용자 지정 텍스트 URL 또는 다른 문서에 링크 중 선택하세요.",
|
||||
"chooseDocumentToLink": "연결할 문서 선택",
|
||||
"chooseFromExisting": "기존 항목 중 선택",
|
||||
"chooseLabel": "{{label}} 선택",
|
||||
"collapseAll": "모두 접기",
|
||||
"customURL": "사용자 지정 URL",
|
||||
"editLabelData": "{{label}} 데이터 수정",
|
||||
"editLink": "링크 수정",
|
||||
"editRelationship": "관계 수정",
|
||||
"enterURL": "URL 입력",
|
||||
"internalLink": "내부 링크",
|
||||
"itemsAndMore": "{{items}} 및 {{count}}개 더",
|
||||
"labelRelationship": "{{label}} 관계",
|
||||
"latitude": "위도",
|
||||
"linkType": "링크 유형",
|
||||
"linkedTo": "<0>{{label}}</0>에 연결됨",
|
||||
"longitude": "경도",
|
||||
"newLabel": "새로운 {{label}}",
|
||||
"openInNewTab": "새 탭에서 열기",
|
||||
"passwordsDoNotMatch": "비밀번호가 일치하지 않습니다.",
|
||||
"relatedDocument": "관련 문서",
|
||||
"relationTo": "관계",
|
||||
"removeRelationship": "관계 제거",
|
||||
"removeUpload": "제거",
|
||||
"saveChanges": "변경 사항 저장",
|
||||
"searchForBlock": "블록 검색",
|
||||
"selectExistingLabel": "기존 {{label}} 선택",
|
||||
"selectFieldsToEdit": "수정할 입력란 선택",
|
||||
"showAll": "모두 표시",
|
||||
"swapRelationship": "관계 교체",
|
||||
"swapUpload": "업로드 교체",
|
||||
"textToDisplay": "표시할 텍스트",
|
||||
"toggleBlock": "블록 토글",
|
||||
"uploadNewLabel": "새로운 {{label}} 업로드"
|
||||
},
|
||||
"general": {
|
||||
"aboutToDelete": "{{label}} <1>{{title}}</1>를 삭제하려고 합니다. 계속하시겠습니까?",
|
||||
"aboutToDeleteCount_many": "{{label}}를 {{count}}개 삭제하려고 합니다.",
|
||||
"aboutToDeleteCount_one": "{{label}}를 {{count}}개 삭제하려고 합니다.",
|
||||
"aboutToDeleteCount_other": "{{label}}를 {{count}}개 삭제하려고 합니다.",
|
||||
"addBelow": "아래에 추가",
|
||||
"addFilter": "필터 추가",
|
||||
"adminTheme": "관리자 테마",
|
||||
"and": "및",
|
||||
"applyChanges": "변경 사항 적용",
|
||||
"ascending": "오름차순",
|
||||
"automatic": "자동 설정",
|
||||
"backToDashboard": "대시보드로 돌아가기",
|
||||
"cancel": "취소",
|
||||
"changesNotSaved": "변경 사항이 저장되지 않았습니다. 지금 떠나면 변경 사항을 잃게 됩니다.",
|
||||
"close": "닫기",
|
||||
"collapse": "접기",
|
||||
"collections": "컬렉션",
|
||||
"columnToSort": "정렬할 열",
|
||||
"columns": "열",
|
||||
"confirm": "확인",
|
||||
"confirmDeletion": "삭제하시겠습니까?",
|
||||
"confirmDuplication": "복제하시겠습니까?",
|
||||
"copied": "복사됨",
|
||||
"copy": "복사",
|
||||
"create": "생성",
|
||||
"createNew": "새로 생성",
|
||||
"createNewLabel": "새로운 {{label}} 생성",
|
||||
"creatingNewLabel": "{{label}} 생성 중",
|
||||
"created": "생성됨",
|
||||
"createdAt": "생성 일시",
|
||||
"creating": "생성 중",
|
||||
"dark": "다크",
|
||||
"dashboard": "대시보드",
|
||||
"delete": "삭제",
|
||||
"deletedCountSuccessfully": "{{count}}개의 {{label}}를 삭제했습니다.",
|
||||
"deletedSuccessfully": "삭제되었습니다.",
|
||||
"deleting": "삭제 중...",
|
||||
"descending": "내림차순",
|
||||
"deselectAllRows": "모든 행 선택 해제",
|
||||
"duplicate": "복제",
|
||||
"duplicateWithoutSaving": "변경 사항 저장 없이 복제",
|
||||
"edit": "수정",
|
||||
"editLabel": "{{label}} 수정",
|
||||
"editing": "수정 중",
|
||||
"editingLabel_many": "{{count}}개의 {{label}} 수정 중",
|
||||
"editingLabel_one": "{{count}}개의 {{label}} 수정 중",
|
||||
"editingLabel_other": "{{count}}개의 {{label}} 수정 중",
|
||||
"email": "이메일",
|
||||
"emailAddress": "이메일 주소",
|
||||
"enterAValue": "값을 입력하세요",
|
||||
"error": "오류",
|
||||
"errors": "오류",
|
||||
"fallbackToDefaultLocale": "기본 locale로 대체",
|
||||
"filter": "필터",
|
||||
"filterWhere": "{{label}} 필터링 조건",
|
||||
"filters": "필터",
|
||||
"globals": "글로벌",
|
||||
"language": "언어",
|
||||
"lastModified": "마지막 수정 일시",
|
||||
"leaveAnyway": "그래도 나가시겠습니까?",
|
||||
"leaveWithoutSaving": "저장하지 않고 나가기",
|
||||
"light": "라이트",
|
||||
"livePreview": "실시간 미리보기",
|
||||
"loading": "불러오는 중",
|
||||
"locale": "locale",
|
||||
"locales": "locale",
|
||||
"menu": "메뉴",
|
||||
"moveDown": "아래로 이동",
|
||||
"moveUp": "위로 이동",
|
||||
"newPassword": "새 비밀번호",
|
||||
"noFiltersSet": "설정된 필터 없음",
|
||||
"noLabel": "<{{label}} 없음>",
|
||||
"noOptions": "옵션 없음",
|
||||
"noResults": "{{label}}를 찾을 수 없습니다. 아직 {{label}}이 없거나 설정한 필터와 일치하는 것이 없습니다.",
|
||||
"noValue": "값 없음",
|
||||
"none": "없음",
|
||||
"notFound": "찾을 수 없음",
|
||||
"nothingFound": "찾을 수 없습니다",
|
||||
"of": "의",
|
||||
"or": "또는",
|
||||
"open": "열기",
|
||||
"order": "순서",
|
||||
"pageNotFound": "페이지를 찾을 수 없음",
|
||||
"password": "비밀번호",
|
||||
"payloadSettings": "Payload 설정",
|
||||
"perPage": "페이지당 개수: {{limit}}",
|
||||
"remove": "제거",
|
||||
"reset": "초기화",
|
||||
"row": "행",
|
||||
"rows": "행",
|
||||
"save": "저장",
|
||||
"saving": "저장 중...",
|
||||
"searchBy": "{{label}}로 검색",
|
||||
"selectAll": "{{count}}개 {{label}} 모두 선택",
|
||||
"selectAllRows": "모든 행 선택",
|
||||
"selectValue": "값 선택",
|
||||
"selectedCount": "{{count}}개의 {{label}} 선택됨",
|
||||
"showAllLabel": "{{label}} 모두 표시",
|
||||
"sorryNotFound": "죄송합니다. 요청과 일치하는 항목이 없습니다.",
|
||||
"sort": "정렬",
|
||||
"sortByLabelDirection": "{{label}} {{direction}}으로 정렬",
|
||||
"stayOnThisPage": "이 페이지에 머무르기",
|
||||
"submissionSuccessful": "제출이 완료되었습니다.",
|
||||
"submit": "제출",
|
||||
"successfullyCreated": "{{label}}이(가) 생성되었습니다.",
|
||||
"successfullyDuplicated": "{{label}}이(가) 복제되었습니다.",
|
||||
"thisLanguage": "한국어",
|
||||
"titleDeleted": "{{label}} \"{{title}}\"을(를) 삭제했습니다.",
|
||||
"unauthorized": "권한 없음",
|
||||
"unsavedChangesDuplicate": "저장되지 않은 변경 사항이 있습니다. 복제를 계속하시겠습니까?",
|
||||
"untitled": "제목 없음",
|
||||
"updatedAt": "업데이트 일시",
|
||||
"updatedCountSuccessfully": "{{count}}개의 {{label}}을(를) 업데이트했습니다.",
|
||||
"updatedSuccessfully": "성공적으로 업데이트되었습니다.",
|
||||
"updating": "업데이트 중",
|
||||
"uploading": "업로드 중",
|
||||
"user": "사용자",
|
||||
"users": "사용자",
|
||||
"value": "값",
|
||||
"welcome": "환영합니다"
|
||||
},
|
||||
"operators": {
|
||||
"contains": "포함",
|
||||
"equals": "같음",
|
||||
"exists": "존재",
|
||||
"isGreaterThan": "보다 큼",
|
||||
"isGreaterThanOrEqualTo": "보다 크거나 같음",
|
||||
"isIn": "포함됨",
|
||||
"isLessThan": "보다 작음",
|
||||
"isLessThanOrEqualTo": "보다 작거나 같음",
|
||||
"isLike": "유사",
|
||||
"isNotEqualTo": "같지 않음",
|
||||
"isNotIn": "포함되지 않음",
|
||||
"near": "근처"
|
||||
},
|
||||
"upload": {
|
||||
"crop": "자르기",
|
||||
"cropToolDescription": "선택한 영역의 모퉁이를 드래그하거나 새로운 영역을 그리거나 아래의 값을 조정하세요.",
|
||||
"dragAndDrop": "파일을 끌어다 놓으세요",
|
||||
"dragAndDropHere": "또는 여기로 파일을 끌어다 놓으세요",
|
||||
"editImage": "이미지 수정",
|
||||
"focalPoint": "초점",
|
||||
"focalPointDescription": "미리보기에서 초점을 직접 드래그하거나 아래의 값을 조정하세요.",
|
||||
"fileName": "파일 이름",
|
||||
"fileSize": "파일 크기",
|
||||
"height": "높이",
|
||||
"lessInfo": "정보 숨기기",
|
||||
"moreInfo": "정보 더보기",
|
||||
"previewSizes": "미리보기 크기",
|
||||
"selectCollectionToBrowse": "찾을 컬렉션 선택",
|
||||
"selectFile": "파일 선택",
|
||||
"setCropArea": "자르기 영역 설정",
|
||||
"setFocalPoint": "초점 설정",
|
||||
"sizes": "크기",
|
||||
"sizesFor": "{{label}} 크기",
|
||||
"width": "너비"
|
||||
},
|
||||
"validation": {
|
||||
"emailAddress": "유효한 이메일 주소를 입력하세요.",
|
||||
"enterNumber": "유효한 숫자를 입력하세요.",
|
||||
"fieldHasNo": "이 입력란에는 {{label}}이(가) 없습니다.",
|
||||
"greaterThanMax": "{{value}}은(는) 최대 허용된 {{label}}인 {{max}}개보다 큽니다.",
|
||||
"invalidInput": "이 입력란에는 유효하지 않은 입력이 있습니다.",
|
||||
"invalidSelection": "이 입력란에는 유효하지 않은 선택이 있습니다.",
|
||||
"invalidSelections": "이 입력란에는 다음과 같은 유효하지 않은 선택 사항이 있습니다:",
|
||||
"lessThanMin": "{{value}}은(는) 최소 허용된 {{label}}인 {{min}}개보다 작습니다.",
|
||||
"limitReached": "제한에 도달했습니다. {{max}}개 항목만 추가할 수 있습니다.",
|
||||
"longerThanMin": "이 값은 최소 길이인 {{minLength}}자보다 길어야 합니다.",
|
||||
"notValidDate": "\"{{value}}\"은(는) 유효한 날짜가 아닙니다.",
|
||||
"required": "이 입력란은 필수입니다.",
|
||||
"requiresAtLeast": "이 입력란운 최소한 {{count}} {{label}}이 필요합니다.",
|
||||
"requiresNoMoreThan": "이 입력란은 최대 {{count}} {{label}} 이하이어야 합니다.",
|
||||
"requiresTwoNumbers": "이 입력란은 두 개의 숫자가 필요합니다.",
|
||||
"shorterThanMax": "이 값은 최대 길이인 {{maxLength}}자보다 짧아야 합니다.",
|
||||
"trueOrFalse": "이 입력란은 true 또는 false만 가능합니다.",
|
||||
"validUploadID": "이 입력란은 유효한 업로드 ID가 아닙니다."
|
||||
},
|
||||
"version": {
|
||||
"aboutToPublishSelection": "선택한 {{label}}을(를) 게시하려고 합니다. 계속하시겠습니까?",
|
||||
"aboutToRestore": "이 {{label}} 문서를 {{versionDate}}기준 버전으로 복원하려고 합니다.",
|
||||
"aboutToRestoreGlobal": "글로벌 {{label}}을(를) {{versionDate}}기준 버전으로 복원하려고 합니다.",
|
||||
"aboutToRevertToPublished": "이 문서의 변경 사항을 게시된 상태로 되돌리려고 합니다. 계속하시겠습니까?",
|
||||
"aboutToUnpublish": "이 문서를 게시 해제하려고 합니다. 계속하시겠습니까?",
|
||||
"aboutToUnpublishSelection": "선택한 {{label}}을(를) 게시 해제하려고 합니다. 계속하시겠습니까?",
|
||||
"autosave": "자동 저장",
|
||||
"autosavedSuccessfully": "자동 저장이 완료되었습니다.",
|
||||
"autosavedVersion": "자동 저장된 버전",
|
||||
"changed": "변경됨",
|
||||
"compareVersion": "비교할 버전 선택:",
|
||||
"confirmPublish": "게시하기",
|
||||
"confirmRevertToSaved": "저장된 상태로 되돌리기",
|
||||
"confirmUnpublish": "게시 해제하기",
|
||||
"confirmVersionRestoration": "버전 복원하기",
|
||||
"currentDocumentStatus": "현재 {{docStatus}} 문서",
|
||||
"draft": "초안",
|
||||
"draftSavedSuccessfully": "초안이 저장되었습니다.",
|
||||
"lastSavedAgo": "마지막으로 저장한지 {{distance}} 전",
|
||||
"noFurtherVersionsFound": "더 이상의 버전을 찾을 수 없습니다.",
|
||||
"noRowsFound": "{{label}}을(를) 찾을 수 없음",
|
||||
"preview": "미리보기",
|
||||
"problemRestoringVersion": "이 버전을 복원하는 중 문제가 발생했습니다.",
|
||||
"publish": "게시",
|
||||
"publishChanges": "변경 사항 게시",
|
||||
"published": "게시됨",
|
||||
"restoreThisVersion": "이 버전 복원",
|
||||
"restoredSuccessfully": "복원이 완료되었습니다.",
|
||||
"restoring": "복원 중...",
|
||||
"revertToPublished": "게시된 상태로 되돌리기",
|
||||
"reverting": "되돌리는 중...",
|
||||
"saveDraft": "초안 저장",
|
||||
"selectLocales": "표시할 locale 선택",
|
||||
"selectVersionToCompare": "비교할 버전 선택",
|
||||
"showLocales": "locale 표시:",
|
||||
"showingVersionsFor": "다음 버전 표시 중:",
|
||||
"status": "상태",
|
||||
"type": "유형",
|
||||
"unpublish": "게시 해제",
|
||||
"unpublishing": "게시 해제 중...",
|
||||
"version": "버전",
|
||||
"versionCount_many": "{{count}}개의 버전을 찾았습니다",
|
||||
"versionCount_none": "버전을 찾을 수 없습니다",
|
||||
"versionCount_one": "{{count}}개의 버전을 찾았습니다",
|
||||
"versionCount_other": "{{count}}개의 버전을 찾았습니다",
|
||||
"versionCreatedOn": "{{version}} 생성 날짜:",
|
||||
"versionID": "버전 ID",
|
||||
"versions": "버전",
|
||||
"viewingVersion": "{{entityLabel}} {{documentTitle}}의 버전 보기",
|
||||
"viewingVersionGlobal": "글로벌 {{entityLabel}}의 버전 보기",
|
||||
"viewingVersions": "{{entityLabel}} {{documentTitle}}에 대한 버전 보기",
|
||||
"viewingVersionsGlobal": "글로벌 {{entityLabel}}에 대한 버전 보기"
|
||||
}
|
||||
}
|
||||
@@ -164,7 +164,7 @@
|
||||
"copied": "Skopiowano",
|
||||
"copy": "Skopiuj",
|
||||
"create": "Stwórz",
|
||||
"createNew": "Stwórzy nowy",
|
||||
"createNew": "Stwórz nowy",
|
||||
"createNewLabel": "Stwórz nowy {{label}}",
|
||||
"created": "Utworzono",
|
||||
"createdAt": "Data utworzenia",
|
||||
|
||||
372
packages/payload/src/translations/rs-latin.json
Normal file
372
packages/payload/src/translations/rs-latin.json
Normal file
@@ -0,0 +1,372 @@
|
||||
{
|
||||
"$schema": "./translation-schema.json",
|
||||
"authentication": {
|
||||
"account": "Nalog",
|
||||
"accountOfCurrentUser": "Nalog trenutnog korisnika",
|
||||
"alreadyActivated": "Već aktivirano",
|
||||
"alreadyLoggedIn": "Već prijavljen",
|
||||
"apiKey": "API ključ",
|
||||
"backToLogin": "Nazad na prijavu",
|
||||
"beginCreateFirstUser": "Na samom početku kreiraj svog prvog korisnika",
|
||||
"changePassword": "Promeni lozinku",
|
||||
"checkYourEmailForPasswordReset": "Proverite e-poštu i poruku sa linkom za promenu lozinke.",
|
||||
"confirmGeneration": "Potvrdi kreiranje",
|
||||
"confirmPassword": "Potvrdi lozinku",
|
||||
"createFirstUser": "Kreiraj prvog korisnika",
|
||||
"emailNotValid": "Adresa e-pošte nije validna",
|
||||
"emailSent": "Poruka e-pošte prosleđena",
|
||||
"enableAPIKey": "Omogući API ključ",
|
||||
"failedToUnlock": "Neuspešno otključavanje.",
|
||||
"forceUnlock": "Prinudno otključaj",
|
||||
"forgotPassword": "Zaboravljena lozinka",
|
||||
"forgotPasswordEmailInstructions": "Molimo Vas da unesete svoj adresu e-pošte. Primićete poruku sa uputstvom za ponovno postavljanje lozinke.",
|
||||
"forgotPasswordQuestion": "Zaboravljena lozinka?",
|
||||
"generate": "Generiši",
|
||||
"generateNewAPIKey": "Generiši novi API ključ",
|
||||
"generatingNewAPIKeyWillInvalidate": "Generisanje novog API ključa će <1>poništiti</1> prethodni ključ. Da li ste sigurni da želite nastaviti?",
|
||||
"lockUntil": "Zaključaj dok",
|
||||
"logBackIn": "Ponovna prijava",
|
||||
"logOut": "Odjava",
|
||||
"loggedIn": "Za prijavu sa drugim korisničkim nalogom potrebno je prvo <0>odjaviti se</0>",
|
||||
"loggedInChangePassword": "Da biste promenili lozinku, otvorite svoj <0>nalog</0> i promenite lozinku.",
|
||||
"loggedOutInactivity": "Odjavljeni se zbog neaktivnosti.",
|
||||
"loggedOutSuccessfully": "Uspešno ste odjavljeni",
|
||||
"login": "Prijava",
|
||||
"loginAttempts": "Pokušaji prijave",
|
||||
"loginUser": "Prijava korisnika",
|
||||
"loginWithAnotherUser": "Za prijavu sa drugim korisničkim nalogom potrebno je prvo <0>odjaviti se</0>",
|
||||
"logout": "Odjava",
|
||||
"logoutUser": "Odjava korisnika",
|
||||
"newAPIKeyGenerated": "Novi API ključ generisan.",
|
||||
"newAccountCreated": "Novi nalog je kreiran. Pristupite nalogu klikom na <a href=\"{{serverURL}}\">{{serverURL}}</a>. Molimo Vas kliknite na sledeći link ili zalepite URL koji se nalazi ispod u pretraživač da biste potvrdili adresu e-pošte: <a href=\"{{verificationURL}}\">{{verificationURL}}</a><br> Nakon što potvrdite adresu e-pošte možete se ulogovati.",
|
||||
"newPassword": "Nova lozinka",
|
||||
"resetPassword": "Promena lozinke",
|
||||
"resetPasswordExpiration": "Promena roka trajanja lozinke",
|
||||
"resetPasswordToken": "Promena lozinke tokena",
|
||||
"resetYourPassword": "Promeni svoju lozinku",
|
||||
"stayLoggedIn": "Ostani prijavljen",
|
||||
"successfullyUnlocked": "Uspešno otključano",
|
||||
"unableToVerify": "Nije moguće potvrditi",
|
||||
"verified": "Potvrđeno",
|
||||
"verifiedSuccessfully": "Uspešno potvrđeno",
|
||||
"verify": "Potvrdi",
|
||||
"verifyUser": "Potvrdi korisnika",
|
||||
"verifyYourEmail": "Potvrdi svoju adresu e-pošte",
|
||||
"youAreInactive": "Neaktivni ste već neko vreme i uskoro ćete biti automatski odjavljeni zbog sigurnosti. Da li želite ostati prijavljeni?",
|
||||
"youAreReceivingResetPassword": "Primili ste ovo pošto ste Vi (ili neko u vaše ime) zatražili promenu lozinke naloga. Molimo Vas kliknite na link ili zalepite URL u svoj pretraživač da biste završili proces:",
|
||||
"youDidNotRequestPassword": "Ako niste zatražili promenu lozinke ignorišite ovu poruku i lozinka će ostati nepromenjena."
|
||||
},
|
||||
"error": {
|
||||
"accountAlreadyActivated": "Ovaj nalog je već aktiviran.",
|
||||
"autosaving": "Nastao je problem pri automatskom čuvanju ovog dokumenta.",
|
||||
"correctInvalidFields": "Molimo ispravite nevalidna polja.",
|
||||
"deletingFile": "Dogodila se greška pri brisanju datoteke.",
|
||||
"deletingTitle": "Dogodila se greška pri brisanju {{title}}. Proverite internet konekciju i pokušajte ponovo.",
|
||||
"emailOrPasswordIncorrect": "Adresa e-pošte ili lozinka su neispravni.",
|
||||
"followingFieldsInvalid_one": "Ovo polje je nevalidno:",
|
||||
"followingFieldsInvalid_other": "Ova polja su nevalidna:",
|
||||
"incorrectCollection": "Nevalidna kolekcija",
|
||||
"invalidFileType": "Nevalidan tip datoteke",
|
||||
"invalidFileTypeValue": "Nevalidan tip datoteke: {{value}}",
|
||||
"loadingDocument": "Postoji problem pri učitavanju dokumenta čiji je ID {{id}}.",
|
||||
"missingEmail": "Nedostaje adresa e-pošte.",
|
||||
"missingIDOfDocument": "Nedostaje ID dokumenta da bi se ažurirao.",
|
||||
"missingIDOfVersion": "Nedostaje ID verzije.",
|
||||
"missingRequiredData": "Nedostaju obavezni podaci.",
|
||||
"noFilesUploaded": "Nijedna datoteka nije učitana.",
|
||||
"noMatchedField": "Nema podudarajućih polja za \"{{label}}\"",
|
||||
"noUser": "Nema korisnika",
|
||||
"notAllowedToAccessPage": "Nemate dozvolu za pristup ovoj stranici.",
|
||||
"notAllowedToPerformAction": "Nemate dozvolu za izvršenje ove radnje.",
|
||||
"notFound": "Traženi resurs nije pronađen.",
|
||||
"previewing": "Postoji problem pri pregledu ovog dokumenta.",
|
||||
"problemUploadingFile": "Postoji problem pri učitavanju datoteke.",
|
||||
"tokenInvalidOrExpired": "Token je nevalidan ili je istekao.",
|
||||
"unPublishingDocument": "Postoji problem pri poništavanju objave ovog dokumenta.",
|
||||
"unableToDeleteCount": "Nije moguće izbrisati {{count}} od {{total}} {{label}}.",
|
||||
"unableToUpdateCount": "Nije moguće ažurirati {{count}} od {{total}} {{label}}.",
|
||||
"unauthorized": "Niste autorizovani da biste uputili ovaj zahtev.",
|
||||
"unknown": "Došlo je do nepoznate greške.",
|
||||
"unspecific": "Došlo je do greške.",
|
||||
"userLocked": "Ovaj korisnik je zaključan zbog prevelikog broja neuspešnih pokušaja prijave.",
|
||||
"valueMustBeUnique": "Vrednost mora biti jedinstvena.",
|
||||
"verificationTokenInvalid": "Verifikacioni token je nevalidan."
|
||||
},
|
||||
"fields": {
|
||||
"addLabel": "Dodaj {{label}}",
|
||||
"addLink": "Dodaj link",
|
||||
"addNew": "Dodaj novi",
|
||||
"addNewLabel": "Dodaj novi {{label}}",
|
||||
"addRelationship": "Dodaj relaciju",
|
||||
"addUpload": "Dodaj učitavanje",
|
||||
"block": "blokiranje",
|
||||
"blockType": "Vrsta blokiranja",
|
||||
"blocks": "blokiranja",
|
||||
"chooseBetweenCustomTextOrDocument": "Izaberite između unosa prilagođenog teksta URL ili linka na drugi dokument.",
|
||||
"chooseDocumentToLink": "Odaberite dokument koji želite linkovati.",
|
||||
"chooseFromExisting": "Odaberite iz postojećih.",
|
||||
"chooseLabel": "Odaberite {{label}}",
|
||||
"collapseAll": "Skupi sve",
|
||||
"customURL": "Prilagođeni URL",
|
||||
"editLabelData": "Izmeni {{label}} podatke",
|
||||
"editLink": "Izmeni link",
|
||||
"editRelationship": "Izmeni odnos",
|
||||
"enterURL": "Unesi URL",
|
||||
"internalLink": "Interni link",
|
||||
"itemsAndMore": "{{items}} i {{count}} više",
|
||||
"labelRelationship": "{{label}} veza",
|
||||
"latitude": "Geografska širina",
|
||||
"linkType": "Tip linka",
|
||||
"linkedTo": "Povezani sa <0>{{label}}</0>",
|
||||
"longitude": "Geografska dužina",
|
||||
"newLabel": "Novo {{label}}",
|
||||
"openInNewTab": "Otvori u novoj kartici.",
|
||||
"passwordsDoNotMatch": "Lozinke nisu iste.",
|
||||
"relatedDocument": "Povezani dokument",
|
||||
"relationTo": "Veza sa",
|
||||
"removeRelationship": "Ukloni vezu",
|
||||
"removeUpload": "Ukloni prenos",
|
||||
"saveChanges": "Sačuvaj promene",
|
||||
"searchForBlock": "Pretraži blok",
|
||||
"selectExistingLabel": "Odaberi postojeću {{label}}",
|
||||
"selectFieldsToEdit": "Odaberite polja za promenu",
|
||||
"showAll": "Pokaži sve",
|
||||
"swapRelationship": "Zameni vezu",
|
||||
"swapUpload": "Zameni prenos",
|
||||
"textToDisplay": "Tekst za prikaz",
|
||||
"toggleBlock": "Prebaci blok",
|
||||
"uploadNewLabel": "Učitaj novi {{label}}"
|
||||
},
|
||||
"general": {
|
||||
"aboutToDelete": "Izbrisaćete {{label}} <1>{{title}}</1>. Da li ste sigurni?",
|
||||
"aboutToDeleteCount_many": "Izbrisaćete {{count}} {{label}}",
|
||||
"aboutToDeleteCount_one": "Izbrisaćete {{count}} {{label}}",
|
||||
"aboutToDeleteCount_other": "Izbrisaćete {{count}} {{label}}",
|
||||
"addBelow": "Dodaj ispod",
|
||||
"addFilter": "Dodaj filter",
|
||||
"adminTheme": "Administratorska tema",
|
||||
"and": "I",
|
||||
"applyChanges": "Primeni promene",
|
||||
"ascending": "Uzlazno",
|
||||
"automatic": "Automatsko",
|
||||
"backToDashboard": "Nazad na kontrolni panel",
|
||||
"cancel": "Otkaži",
|
||||
"changesNotSaved": "Vaše promene nisu sačuvane. Ako izađete sada, izgubićete promene.",
|
||||
"close": "Zatvori",
|
||||
"collapse": "Skupi",
|
||||
"collections": "Kolekcije",
|
||||
"columnToSort": "Kolona za sortiranje",
|
||||
"columns": "Kolone",
|
||||
"confirm": "Potvrdi",
|
||||
"confirmDeletion": "Potvrdi brisanje",
|
||||
"confirmDuplication": "Potvrdi duplikaciju",
|
||||
"copied": "Kopirano",
|
||||
"copy": "Kopiraj",
|
||||
"create": "Kreiraj",
|
||||
"createNew": "Kreiraj novo",
|
||||
"createNewLabel": "Kreiraj novo {{label}}",
|
||||
"created": "Kreirano",
|
||||
"createdAt": "Kreirano u",
|
||||
"creating": "Kreira se",
|
||||
"creatingNewLabel": "Kreiranje novog {{label}}",
|
||||
"dark": "Tamno",
|
||||
"dashboard": "Kontrolni panel",
|
||||
"delete": "Obriši",
|
||||
"deletedCountSuccessfully": "Uspešno izbrisano {{count}} {{label}}.",
|
||||
"deletedSuccessfully": "Uspešno izbrisano.",
|
||||
"deleting": "Brisanje...",
|
||||
"descending": "Opadajuće",
|
||||
"deselectAllRows": "Deselektujte sve redove",
|
||||
"duplicate": "Duplikat",
|
||||
"duplicateWithoutSaving": "Ponovi bez čuvanja promena",
|
||||
"edit": "Uredi",
|
||||
"editLabel": "Uredi {{label}}",
|
||||
"editing": "Uređivanje",
|
||||
"editingLabel_many": "Uređivanje {{count}} {{label}}",
|
||||
"editingLabel_one": "Uređivanje {{count}} {{label}}",
|
||||
"editingLabel_other": "Uređivanje {{count}} {{label}}",
|
||||
"email": "E-pošta",
|
||||
"emailAddress": "Аdresa e-pošte",
|
||||
"enterAValue": "Unesi vrednost",
|
||||
"error": "Greška",
|
||||
"errors": "Greške",
|
||||
"fallbackToDefaultLocale": "Vraćanje na zadati jezik",
|
||||
"filter": "Filter",
|
||||
"filterWhere": "Filter {{label}} gde",
|
||||
"filters": "Filteri",
|
||||
"globals": "Globali",
|
||||
"language": "Jezik",
|
||||
"lastModified": "Zadnja promena",
|
||||
"leaveAnyway": "Svejedno napusti",
|
||||
"leaveWithoutSaving": "Napusti bez čuvanja",
|
||||
"light": "Svetlo",
|
||||
"livePreview": "Pregled",
|
||||
"loading": "Učitavanje",
|
||||
"locale": "Jezik",
|
||||
"locales": "Prevodi",
|
||||
"menu": "Meni",
|
||||
"moveDown": "Pomeri dole",
|
||||
"moveUp": "Pomeri gore",
|
||||
"newPassword": "Nova lozinka",
|
||||
"noFiltersSet": "Nema postavljenih filtera",
|
||||
"noLabel": "<Nema {{label}}>",
|
||||
"noOptions": "Nema opcija",
|
||||
"noResults": "Nema pronađenih {{label}}. Moguće da {{label}} još uvek ne postoji ili nema rezultata u skladu sa postavljenim filterima.",
|
||||
"noValue": "Bez vrednosti",
|
||||
"none": "Nijedan",
|
||||
"notFound": "Nije pronađeno",
|
||||
"nothingFound": "Ništa nije pronađeno",
|
||||
"of": "Od",
|
||||
"open": "Otvori",
|
||||
"or": "Ili",
|
||||
"order": "Redosled",
|
||||
"pageNotFound": "Stranica nije pronađena",
|
||||
"password": "Lozinka",
|
||||
"payloadSettings": "Payload postavke",
|
||||
"perPage": "Po stranici: {{limit}}",
|
||||
"remove": "Ukloni",
|
||||
"reset": "Ponovo postavi",
|
||||
"row": "Red",
|
||||
"rows": "Redovi",
|
||||
"save": "Sačuvaj",
|
||||
"saving": "Čuvanje u toku...",
|
||||
"searchBy": "Traži po {{label}}",
|
||||
"selectAll": "Odaberite sve {{count}} {{label}}",
|
||||
"selectAllRows": "Odaberite sve redove",
|
||||
"selectValue": "Odaberi vrednost",
|
||||
"selectedCount": "{{count}} {{label}} odabrano",
|
||||
"showAllLabel": "Prikaži sve {{label}}",
|
||||
"sorryNotFound": "Nažalost, ne postoji ništa što odgovara vašem zahtevu.",
|
||||
"sort": "Sortiraj",
|
||||
"sortByLabelDirection": "Sortiraj prema {{label}} {{direction}}",
|
||||
"stayOnThisPage": "Ostani na ovoj stranici",
|
||||
"submissionSuccessful": "Uspešno slanje",
|
||||
"submit": "Potvrdi",
|
||||
"successfullyCreated": "{{label}} uspešno kreirano.",
|
||||
"successfullyDuplicated": "{{label}} uspešno duplicirano.",
|
||||
"thisLanguage": "Srpski (latinica)",
|
||||
"titleDeleted": "{{label}} \"{{title}}\" uspešno obrisano.",
|
||||
"unauthorized": "Niste autorizovani",
|
||||
"unsavedChangesDuplicate": "Imate nesačuvane promene. Da li želite nastaviti sa dupliciranjem?",
|
||||
"untitled": "Bez naslova",
|
||||
"updatedAt": "Ažurirano u",
|
||||
"updatedCountSuccessfully": "Uspešno ažurirano {{count}} {{label}}.",
|
||||
"updatedSuccessfully": "Uspešno ažurirano.",
|
||||
"updating": "Ažuriranje",
|
||||
"uploading": "Prenos",
|
||||
"user": "Korisnik",
|
||||
"users": "Korisnici",
|
||||
"value": "Vrednost",
|
||||
"welcome": "Dobrodošli"
|
||||
},
|
||||
"operators": {
|
||||
"contains": "sadrži",
|
||||
"equals": "jednako",
|
||||
"exists": "postoji",
|
||||
"isGreaterThan": "je veće od",
|
||||
"isGreaterThanOrEqualTo": "je veće od ili jednako",
|
||||
"isIn": "je u",
|
||||
"isLessThan": "manje je od",
|
||||
"isLessThanOrEqualTo": "manje je ili jednako",
|
||||
"isLike": "je kao",
|
||||
"isNotEqualTo": "nije jednako",
|
||||
"isNotIn": "nije unutra",
|
||||
"near": "blizu"
|
||||
},
|
||||
"upload": {
|
||||
"crop": "Isecite sliku",
|
||||
"cropToolDescription": "Prevucite uglove izabranog područja, nacrtajte novo područje ili prilagodite vrednosti ispod.",
|
||||
"dragAndDrop": "Prevucite i ispustite datoteku",
|
||||
"dragAndDropHere": "ili povucite i ispustite datoteku ovde",
|
||||
"editImage": "Uredi sliku",
|
||||
"fileName": "Ime datoteke",
|
||||
"fileSize": "Veličina datoteke",
|
||||
"focalPoint": "Centralna tačka",
|
||||
"focalPointDescription": "Prevucite središnju tačku direktno na pregled ili prilagodite vrednosti ispod.",
|
||||
"height": "Visina",
|
||||
"lessInfo": "Manje informacija",
|
||||
"moreInfo": "Više informacija",
|
||||
"previewSizes": "Veličine pregleda",
|
||||
"selectCollectionToBrowse": "Odaberite kolekciju za pregled",
|
||||
"selectFile": "Odaberite datoteku",
|
||||
"setCropArea": "Postavite područje za isečenu sliku",
|
||||
"setFocalPoint": "Postavite centralnu tačku",
|
||||
"sizes": "Veličine",
|
||||
"sizesFor": "Veličine za {{label}}",
|
||||
"width": "Širina"
|
||||
},
|
||||
"validation": {
|
||||
"emailAddress": "Molimo Vas unesite validnu email adresu.",
|
||||
"enterNumber": "Molimo Vas unesite validan broj.",
|
||||
"fieldHasNo": "Ovo polje nema {{label}}",
|
||||
"greaterThanMax": "{{value}} prekoračuje maksimalan dozvoljeni {{label}} limit od {{max}}.",
|
||||
"invalidInput": "Ovo polje sadrži nevalidan unos.",
|
||||
"invalidSelection": "Ovo polje sadrži nevalidan odabir.",
|
||||
"invalidSelections": "Ovo polje ima sledeće nevalidne odabire:",
|
||||
"lessThanMin": "{{value}} je ispod dozvoljenog minimuma za {{label}} (donji limit je {{min}}).",
|
||||
"limitReached": "Dosegnut je limit, može se dodati samo {{max}} stavki.",
|
||||
"longerThanMin": "Ova vrednost mora biti duža od minimalne dužine od {{minLength}} karaktera",
|
||||
"notValidDate": "\"{{value}}\" nije validan datum.",
|
||||
"required": "Ovo polje je obavezno.",
|
||||
"requiresAtLeast": "Ovo polje zahteva minimalno {{count}} {{label}}.",
|
||||
"requiresNoMoreThan": "Ovo polje zahteva ne više od {{count}} {{label}}.",
|
||||
"requiresTwoNumbers": "Ovo polje zahteva dva broja.",
|
||||
"shorterThanMax": "Ova vrednost mora biti kraća od maksimalne dužine od {{maxLength}} karaktera",
|
||||
"trueOrFalse": "Ovo polje može biti samo tačno ili netačno",
|
||||
"validUploadID": "Ovo polje ne sadrži validan ID prenosa."
|
||||
},
|
||||
"version": {
|
||||
"aboutToPublishSelection": "Upravo ćete objaviti sve {{label}} u izboru. Da li ste sigurni?",
|
||||
"aboutToRestore": "Vratićete {{label}} dokument u stanje u kojem je bio {{versionDate}}",
|
||||
"aboutToRestoreGlobal": "Vratićete globalni {{label}} u stanje u kojem je bio {{versionDate}}.",
|
||||
"aboutToRevertToPublished": "Vratićete promene u dokumentu u objavljeno stanje. Da li ste sigurni?",
|
||||
"aboutToUnpublish": "Poništićete objavu ovog dokumenta. Da li ste sigurni?",
|
||||
"aboutToUnpublishSelection": "Upravo ćete poništiti objavu svih {{label}} u odabiru. Da li ste sigurni?",
|
||||
"autosave": "Automatsko čuvanje",
|
||||
"autosavedSuccessfully": "Automatsko čuvanje uspešno.",
|
||||
"autosavedVersion": "Verzija automatski sačuvanog dokumenta",
|
||||
"changed": "Promenjeno",
|
||||
"compareVersion": "Uporedi verziju sa:",
|
||||
"confirmPublish": "Potvrdi objavu",
|
||||
"confirmRevertToSaved": "Potvrdite vraćanje na sačuvano",
|
||||
"confirmUnpublish": "Potvrdite poništavanje objave",
|
||||
"confirmVersionRestoration": "Potvrdite vraćanje verzije",
|
||||
"currentDocumentStatus": "Trenutni {{docStatus}} dokumenta",
|
||||
"draft": "Nacrt",
|
||||
"draftSavedSuccessfully": "Nacrt uspešno sačuvan.",
|
||||
"lastSavedAgo": "Zadnji put sačuvano pre {{distance}",
|
||||
"noFurtherVersionsFound": "Nisu pronađene naredne verzije",
|
||||
"noRowsFound": "{{label}} nije pronađeno",
|
||||
"preview": "Pregled",
|
||||
"problemRestoringVersion": "Nastao je problem pri vraćanju ove verzije",
|
||||
"publish": "Objaviti",
|
||||
"publishChanges": "Objavi promene",
|
||||
"published": "Objavljeno",
|
||||
"restoreThisVersion": "Vrati ovu verziju",
|
||||
"restoredSuccessfully": "Uspešno vraćeno.",
|
||||
"restoring": "Vraćanje...",
|
||||
"revertToPublished": "Vrati na objavljeno",
|
||||
"reverting": "Vraćanje...",
|
||||
"saveDraft": "Sačuvaj nacrt",
|
||||
"selectLocales": "Odaberite jezike",
|
||||
"selectVersionToCompare": "Odaberite verziju za upoređivanje",
|
||||
"showLocales": "Prikaži jezike:",
|
||||
"showingVersionsFor": "Pokazujem verzije za:",
|
||||
"status": "Status",
|
||||
"type": "Tip",
|
||||
"unpublish": "Poništi objavu",
|
||||
"unpublishing": "Poništavanje objave...",
|
||||
"version": "Verzija",
|
||||
"versionCount_many": "{{count}} pronađenih verzija",
|
||||
"versionCount_none": "Nema pronađenih verzija",
|
||||
"versionCount_one": "{{count}} pronađena verzija",
|
||||
"versionCount_other": "{{count}} pronađenih verzija",
|
||||
"versionCreatedOn": "{{version}} kreiranih:",
|
||||
"versionID": "ID verzije",
|
||||
"versions": "Verzije",
|
||||
"viewingVersion": "Pregled verzije za {{entityLabel}} {{documentTitle}}",
|
||||
"viewingVersionGlobal": "Pregled verzije za globalni {{entityLabel}}",
|
||||
"viewingVersions": "Pregled verzija za {{entityLabel}} {{documentTitle}}",
|
||||
"viewingVersionsGlobal": "Pregled verzije za globalni {{entityLabel}}"
|
||||
}
|
||||
}
|
||||
372
packages/payload/src/translations/rs.json
Normal file
372
packages/payload/src/translations/rs.json
Normal file
@@ -0,0 +1,372 @@
|
||||
{
|
||||
"$schema": "./translation-schema.json",
|
||||
"authentication": {
|
||||
"account": "Налог",
|
||||
"accountOfCurrentUser": "Налог тренутног корисника",
|
||||
"alreadyActivated": "Већ активирано",
|
||||
"alreadyLoggedIn": "Већ пријављен",
|
||||
"apiKey": "АПИ кључ",
|
||||
"backToLogin": "Назад на пријаву",
|
||||
"beginCreateFirstUser": "На самом почетку креирај свог првог корисника",
|
||||
"changePassword": "Промени лозинку",
|
||||
"checkYourEmailForPasswordReset": "Проверите е-пошту и поруку са линком за промену лозинке.",
|
||||
"confirmGeneration": "Потврди креирање",
|
||||
"confirmPassword": "Потврди лозинку",
|
||||
"createFirstUser": "Креирај првог корисника",
|
||||
"emailNotValid": "Адреса е-поште није валидна",
|
||||
"emailSent": "Порука е-поште прослеђена",
|
||||
"enableAPIKey": "Омогући API кључ",
|
||||
"failedToUnlock": "Неуспешно откључавање.",
|
||||
"forceUnlock": "Принудно откључај",
|
||||
"forgotPassword": "Заборављена лозинка",
|
||||
"forgotPasswordEmailInstructions": "Молимо Вас да унесете својy адресy е-поште. Примићете поруку са упутством за поновно постављање лозинке.",
|
||||
"forgotPasswordQuestion": "Заборављена лозинка?",
|
||||
"generate": "Генериши",
|
||||
"generateNewAPIKey": "Генериши нови АПИ кључ",
|
||||
"generatingNewAPIKeyWillInvalidate": "Генерисање новог АПИ кључа ће <1>поништити</1> претходни кључ. Да ли сте сигурни да желите наставити?",
|
||||
"lockUntil": "Закључај док",
|
||||
"logBackIn": "Поновна пријава",
|
||||
"logOut": "Одјава",
|
||||
"loggedIn": "За пријаву са другим корисничким налогом потребно је прво <0>одјавити се</0>",
|
||||
"loggedInChangePassword": "Да бисте променили лозинку, отворите свој <0>налог</0> и промените лозинку.",
|
||||
"loggedOutInactivity": "Одјављени се због неактивности.",
|
||||
"loggedOutSuccessfully": "Успешно сте одјављени",
|
||||
"login": "Пријава",
|
||||
"loginAttempts": "Покушаји пријаве",
|
||||
"loginUser": "Пријава корисника",
|
||||
"loginWithAnotherUser": "За пријаву са другим корисничким налогом потребно је прво <0>одјавити се</0>",
|
||||
"logout": "Одјава",
|
||||
"logoutUser": "Одјава корисника",
|
||||
"newAPIKeyGenerated": "Нови АПИ кључ генерисан.",
|
||||
"newAccountCreated": "Нови налог је креиран. Приступите налогу кликом на <a href=\"{{serverURL}}\">{{serverURL}}</a>. Молимо Вас кликните на следећи линк или залепите адресу која се налази испод у претраживач да бисте потврдили адресу е-поште: <a href=\"{{verificationURL}}\">{{verificationURL}}</a><br> Након што потврдите адресу е-поште можете се улоговати.",
|
||||
"newPassword": "Нова лозинка",
|
||||
"resetPassword": "Промена лозинке",
|
||||
"resetPasswordExpiration": "Промена рока трајања лозинке",
|
||||
"resetPasswordToken": "Промена лозинке токена",
|
||||
"resetYourPassword": "Промени своју лозинку",
|
||||
"stayLoggedIn": "Остани пријављен",
|
||||
"successfullyUnlocked": "Успешно откључано",
|
||||
"unableToVerify": "Није могуће потврдити",
|
||||
"verified": "Потврђено",
|
||||
"verifiedSuccessfully": "Успешно потврђено",
|
||||
"verify": "Потврди",
|
||||
"verifyUser": "Потврди корисника",
|
||||
"verifyYourEmail": "Потврди своју адресу е-поште",
|
||||
"youAreInactive": "Неактивни сте већ неко време и ускоро ћете бити аутоматски одјављени због сигурности. Да ли желите остати пријављени?",
|
||||
"youAreReceivingResetPassword": "Примили сте поруку пошто сте Ви (или неко у ваше име) затражили промену лозинке налога. Молимо Вас кликните на линк или залепите адресу у свој претраживач да бисте завршили процес:",
|
||||
"youDidNotRequestPassword": "Ако нисте затражили промену лозинке игноришите ову поруку и лозинка ће остати непромењена."
|
||||
},
|
||||
"error": {
|
||||
"accountAlreadyActivated": "Овај налог је већ активиран.",
|
||||
"autosaving": "Настао је проблем при аутоматском чувању овог документа.",
|
||||
"correctInvalidFields": "Молимо исправите невалидна поља.",
|
||||
"deletingFile": "Догодила се грешка при брисању датотеке.",
|
||||
"deletingTitle": "Догодила се грешка при брисању {{title}}. Проверите интернет конекцију и покушајте поново.",
|
||||
"emailOrPasswordIncorrect": "Емаил или лозинка су неисправни.",
|
||||
"followingFieldsInvalid_one": "Ово поље је невалидно:",
|
||||
"followingFieldsInvalid_other": "Ова поља су невалидна:",
|
||||
"incorrectCollection": "Невалидна колекција",
|
||||
"invalidFileType": "Невалидан тип датотеке",
|
||||
"invalidFileTypeValue": "Невалидан тип датотеке: {{value}}",
|
||||
"loadingDocument": "Постоји проблем при учитавању документа чији је ИД {{id}}.",
|
||||
"missingEmail": "Недостаје емаил.",
|
||||
"missingIDOfDocument": "Недостаје ИД документа да би се ажурирао.",
|
||||
"missingIDOfVersion": "Недостаје ИД верзије.",
|
||||
"missingRequiredData": "Недостају обавезни подаци.",
|
||||
"noFilesUploaded": "Ниједна датотека није учитана.",
|
||||
"noMatchedField": "Нема подударајућих поља за \"{{label}}\"",
|
||||
"noUser": "Нема корисника",
|
||||
"notAllowedToAccessPage": "Немате дозволу за приступ овој страници.",
|
||||
"notAllowedToPerformAction": "Немате дозволу за извршење ове радње.",
|
||||
"notFound": "Тражени ресурс није пронађен.",
|
||||
"previewing": "Постоји проблем при прегледу овог документа.",
|
||||
"problemUploadingFile": "Постоји проблем при учитавању датотеке.",
|
||||
"tokenInvalidOrExpired": "Токен је невалидан или је истекао.",
|
||||
"unPublishingDocument": "Постоји проблем при поништавању објаве овог документа.",
|
||||
"unableToDeleteCount": "Није могуће избрисати {{count}} од {{total}} {{label}}.",
|
||||
"unableToUpdateCount": "Није могуће ажурирати {{count}} од {{total}} {{label}}.",
|
||||
"unauthorized": "Нисте ауторизовани да бисте упутили овај захтев.",
|
||||
"unknown": "Дошло је до непознате грешке.",
|
||||
"unspecific": "Дошло је до грешке.",
|
||||
"userLocked": "Овај корисник је закључан због превеликог броја неуспешних покушаја пријаве.",
|
||||
"valueMustBeUnique": "Вредност мора бити јединствена.",
|
||||
"verificationTokenInvalid": "Верификациони токен је невалидан."
|
||||
},
|
||||
"fields": {
|
||||
"addLabel": "Додај {{label}}",
|
||||
"addLink": "Додај линк",
|
||||
"addNew": "Додај нови",
|
||||
"addNewLabel": "Додај нови {{label}}",
|
||||
"addRelationship": "Додај релацију",
|
||||
"addUpload": "Додај учитавање",
|
||||
"block": "блокирање",
|
||||
"blockType": "Врста блокирања",
|
||||
"blocks": "блокирања",
|
||||
"chooseBetweenCustomTextOrDocument": "Изаберите између уноса прилагођеног текста адресе или линка на други документ.",
|
||||
"chooseDocumentToLink": "Одаберите документ који желите линковати.",
|
||||
"chooseFromExisting": "Одаберите из постојећих.",
|
||||
"chooseLabel": "Одаберите {{label}}",
|
||||
"collapseAll": "Скупи све",
|
||||
"customURL": "Прилагођени линк",
|
||||
"editLabelData": "Уреди {{label}} податке",
|
||||
"editLink": "Измени линк",
|
||||
"editRelationship": "Измени однос",
|
||||
"enterURL": "Унеси адресу",
|
||||
"internalLink": "Интерни линк",
|
||||
"itemsAndMore": "{{items}} и {{count}} више",
|
||||
"labelRelationship": "{{label}} веза",
|
||||
"latitude": "Географска ширина",
|
||||
"linkType": "Тип линка",
|
||||
"linkedTo": "Повезани са <0>{{label}}</0>",
|
||||
"longitude": "Географска дужина",
|
||||
"newLabel": "Ново {{label}}",
|
||||
"openInNewTab": "Отвори у новој картици.",
|
||||
"passwordsDoNotMatch": "Лозинке нису исте.",
|
||||
"relatedDocument": "Повезани документ",
|
||||
"relationTo": "Веза са",
|
||||
"removeRelationship": "Уклони везу",
|
||||
"removeUpload": "Уклони пренос",
|
||||
"saveChanges": "Сачувај промене",
|
||||
"searchForBlock": "Претражи блок",
|
||||
"selectExistingLabel": "Одабери постојећу {{label}}",
|
||||
"selectFieldsToEdit": "Одаберите поља за промену",
|
||||
"showAll": "Покажи све",
|
||||
"swapRelationship": "Замени везу",
|
||||
"swapUpload": "Замени пренос",
|
||||
"textToDisplay": "Текст за приказ",
|
||||
"toggleBlock": "Пребаци блок",
|
||||
"uploadNewLabel": "Учитај нови {{label}}"
|
||||
},
|
||||
"general": {
|
||||
"aboutToDelete": "Избрисаћете {{label}} <1>{{title}}</1>. Да ли сте сигурни?",
|
||||
"aboutToDeleteCount_many": "Избрисаћете {{count}} {{label}}",
|
||||
"aboutToDeleteCount_one": "Избрисаћете {{count}} {{label}}",
|
||||
"aboutToDeleteCount_other": "Избрисаћете {{count}} {{label}}",
|
||||
"addBelow": "Додај испод",
|
||||
"addFilter": "Додај филтер",
|
||||
"adminTheme": "Администраторска тема",
|
||||
"and": "И",
|
||||
"applyChanges": "Примени промене",
|
||||
"ascending": "Узлазно",
|
||||
"automatic": "Аутоматско",
|
||||
"backToDashboard": "Назад на контролни панел",
|
||||
"cancel": "Откажи",
|
||||
"changesNotSaved": "Ваше промене нису сачуване. Ако изађете сада, изгубићете промене.",
|
||||
"close": "Затвори",
|
||||
"collapse": "Скупи",
|
||||
"collections": "Колекције",
|
||||
"columnToSort": "Колона за сортирање",
|
||||
"columns": "Колоне",
|
||||
"confirm": "Потврди",
|
||||
"confirmDeletion": "Потврди брисање",
|
||||
"confirmDuplication": "Потврди дупликацију",
|
||||
"copied": "Копирано",
|
||||
"copy": "Копирај",
|
||||
"create": "Креирај",
|
||||
"createNew": "Креирај ново",
|
||||
"createNewLabel": "Креирај ново {{label}}",
|
||||
"created": "Креирано",
|
||||
"createdAt": "Креирано у",
|
||||
"creating": "Креира се",
|
||||
"creatingNewLabel": "Креирање новог {{label}}",
|
||||
"dark": "Тамно",
|
||||
"dashboard": "Контролни панел",
|
||||
"delete": "Обриши",
|
||||
"deletedCountSuccessfully": "Успешно избрисано {{count}} {{label}}.",
|
||||
"deletedSuccessfully": "Успешно избрисано.",
|
||||
"deleting": "Брисање...",
|
||||
"descending": "Опадајуће",
|
||||
"deselectAllRows": "Деселектујте све редове",
|
||||
"duplicate": "Дупликат",
|
||||
"duplicateWithoutSaving": "Понови без чувања промена",
|
||||
"edit": "Уреди",
|
||||
"editLabel": "Уреди {{label}}",
|
||||
"editing": "Уређивање",
|
||||
"editingLabel_many": "Уређивање {{count}} {{label}}",
|
||||
"editingLabel_one": "Уређивање {{count}} {{label}}",
|
||||
"editingLabel_other": "Уређивање {{count}} {{label}}",
|
||||
"email": "Е-пошта",
|
||||
"emailAddress": "Адреса е-поште",
|
||||
"enterAValue": "Унеси вредност",
|
||||
"error": "Грешка",
|
||||
"errors": "Грешке",
|
||||
"fallbackToDefaultLocale": "Враћање на задати језик",
|
||||
"filter": "Филтер",
|
||||
"filterWhere": "Филтер {{label}} где",
|
||||
"filters": "Филтери",
|
||||
"globals": "Глобали",
|
||||
"language": "Језик",
|
||||
"lastModified": "Задња промена",
|
||||
"leaveAnyway": "Свеједно напусти",
|
||||
"leaveWithoutSaving": "Напусти без чувања",
|
||||
"light": "Светло",
|
||||
"livePreview": "Преглед",
|
||||
"loading": "Учитавање",
|
||||
"locale": "Језик",
|
||||
"locales": "Преводи",
|
||||
"menu": "Мени",
|
||||
"moveDown": "Помери доле",
|
||||
"moveUp": "Помери горе",
|
||||
"newPassword": "Нова лозинка",
|
||||
"noFiltersSet": "Нема постављених филтера",
|
||||
"noLabel": "<Нема {{label}}>",
|
||||
"noOptions": "Нема опција",
|
||||
"noResults": "Нема пронађених {{label}}. Могуће да {{label}} још увек не постоји или нема резултата у складу са постављеним филтерима.",
|
||||
"noValue": "Без вредности",
|
||||
"none": "Ниједан",
|
||||
"notFound": "Није пронађено",
|
||||
"nothingFound": "Ништа није пронађено",
|
||||
"of": "Од",
|
||||
"open": "Отвори",
|
||||
"or": "Или",
|
||||
"order": "Редослед",
|
||||
"pageNotFound": "Страница није пронађена",
|
||||
"password": "Лозинка",
|
||||
"payloadSettings": "Payload поставке",
|
||||
"perPage": "По страници: {{limit}}",
|
||||
"remove": "Уклони",
|
||||
"reset": "Поново постави",
|
||||
"row": "Ред",
|
||||
"rows": "Редови",
|
||||
"save": "Сачувај",
|
||||
"saving": "Чување у току...",
|
||||
"searchBy": "Тражи по {{label}}",
|
||||
"selectAll": "Одаберите све {{count}} {{label}}",
|
||||
"selectAllRows": "Одаберите све редове",
|
||||
"selectValue": "Одабери вредност",
|
||||
"selectedCount": "{{count}} {{label}} одабрано",
|
||||
"showAllLabel": "Прикажи све {{label}}",
|
||||
"sorryNotFound": "Нажалост, не постоји ништа што одговара вашем захтеву.",
|
||||
"sort": "Сортирај",
|
||||
"sortByLabelDirection": "Сортирај према {{label}} {{дирецтион}}",
|
||||
"stayOnThisPage": "Остани на овој страници",
|
||||
"submissionSuccessful": "Успешно слање",
|
||||
"submit": "Потврди",
|
||||
"successfullyCreated": "{{label}} успешно креирано.",
|
||||
"successfullyDuplicated": "{{label}} успешно дуплицирано.",
|
||||
"thisLanguage": "Српски (ћирилица)",
|
||||
"titleDeleted": "{{label}} \"{{title}}\" успешно обрисано.",
|
||||
"unauthorized": "Нисте ауторизовани",
|
||||
"unsavedChangesDuplicate": "Имате несачуване промене. Да ли желите наставити са дуплицирањем?",
|
||||
"untitled": "Без наслова",
|
||||
"updatedAt": "Ажурирано у",
|
||||
"updatedCountSuccessfully": "Успешно ажурирано {{count}} {{label}}.",
|
||||
"updatedSuccessfully": "Успешно ажурирано.",
|
||||
"updating": "Ажурирање",
|
||||
"uploading": "Пренос",
|
||||
"user": "Корисник",
|
||||
"users": "Корисници",
|
||||
"value": "Вредност",
|
||||
"welcome": "Добродошли"
|
||||
},
|
||||
"operators": {
|
||||
"contains": "садржи",
|
||||
"equals": "једнако",
|
||||
"exists": "постоји",
|
||||
"isGreaterThan": "је веће од",
|
||||
"isGreaterThanOrEqualTo": "је веће од или једнако",
|
||||
"isIn": "је у",
|
||||
"isLessThan": "мање је од",
|
||||
"isLessThanOrEqualTo": "мање је или једнако",
|
||||
"isLike": "је као",
|
||||
"isNotEqualTo": "није једнако",
|
||||
"isNotIn": "није у",
|
||||
"near": "близу"
|
||||
},
|
||||
"upload": {
|
||||
"crop": "Исеците слику",
|
||||
"cropToolDescription": "Превуците углове изабраног подручја, нацртајте ново подручје или прилагодите вредности испод.",
|
||||
"dragAndDrop": "Превуците и испустите датотеку",
|
||||
"dragAndDropHere": "или превуците и испустите датотеку овде",
|
||||
"editImage": "Уреди слику",
|
||||
"fileName": "Име датотеке",
|
||||
"fileSize": "Величина датотеке",
|
||||
"focalPoint": "Централна тачка",
|
||||
"focalPointDescription": "Превуците средишњу тачку директно на преглед или прилагодите вредности испод.",
|
||||
"height": "Висина",
|
||||
"lessInfo": "Мање информација",
|
||||
"moreInfo": "Више информација",
|
||||
"previewSizes": "Величине прегледа",
|
||||
"selectCollectionToBrowse": "Одаберите колекцију за преглед",
|
||||
"selectFile": "Одаберите датотеку",
|
||||
"setCropArea": "Поставите подручје за исечену слику",
|
||||
"setFocalPoint": "Поставите централну тачку",
|
||||
"sizes": "Величине",
|
||||
"sizesFor": "Величине за {{label}}",
|
||||
"width": "Ширина"
|
||||
},
|
||||
"validation": {
|
||||
"emailAddress": "Молимо Вас унесите валидну емаил адресу.",
|
||||
"enterNumber": "Молимо Вас унесите валидан број.",
|
||||
"fieldHasNo": "Ово поље нема {{label}}",
|
||||
"greaterThanMax": "{{value}} прекорачује максималан дозвољени {{label}} лимит од {{max}}.",
|
||||
"invalidInput": "Ово поље садржи невалидан унос.",
|
||||
"invalidSelection": "Ово поље садржи невалидан одабир.",
|
||||
"invalidSelections": "Ово поље има следеће невалидне одабире:",
|
||||
"lessThanMin": "{{value}} је испод дозвољеног минимума за {{label}} (доњи лимит је {{min}}).",
|
||||
"limitReached": "Досегнут је лимит, може се додати само {{max}} ставки.",
|
||||
"longerThanMin": "Ова вредност мора бити дужа од минималне дужине од {{минЛенгтх}} карактера",
|
||||
"notValidDate": "\"{{value}}\" није валидан датум.",
|
||||
"required": "Ово поље је обавезно.",
|
||||
"requiresAtLeast": "Ово поље захтева минимално {{count}} {{label}}.",
|
||||
"requiresNoMoreThan": "Ово поље захтева не више од {{count}} {{label}}.",
|
||||
"requiresTwoNumbers": "Ово поље захтева два броја.",
|
||||
"shorterThanMax": "Ова вредност мора бити краћа од максималне дужине од {{maxLength}} карактера",
|
||||
"trueOrFalse": "Ово поље може бити само тачно или нетачно",
|
||||
"validUploadID": "Ово поље не садржи валидан ИД преноса."
|
||||
},
|
||||
"version": {
|
||||
"aboutToPublishSelection": "Управо ћете објавити све {{label}} у избору. Да ли сте сигурни?",
|
||||
"aboutToRestore": "Вратићете {{label}} документ у стање у којем је био {{versionDate}}",
|
||||
"aboutToRestoreGlobal": "Вратићете глобални {{label}} у стање у којем је био {{versionDate}}.",
|
||||
"aboutToRevertToPublished": "Вратићете промене у документу у објављено стање. Да ли сте сигурни?",
|
||||
"aboutToUnpublish": "Поништићете објаву овог документа. Да ли сте сигурни?",
|
||||
"aboutToUnpublishSelection": "Управо ћете поништити објаву свих {{label}} у одабиру. Да ли сте сигурни?",
|
||||
"autosave": "Аутоматско чување",
|
||||
"autosavedSuccessfully": "Аутоматско чување успешно.",
|
||||
"autosavedVersion": "Верзија аутоматски сачуваног документа",
|
||||
"changed": "Промењено",
|
||||
"compareVersion": "Упореди верзију са:",
|
||||
"confirmPublish": "Потврди објаву",
|
||||
"confirmRevertToSaved": "Потврдите враћање на сачувано",
|
||||
"confirmUnpublish": "Потврдите поништавање објаве",
|
||||
"confirmVersionRestoration": "Потврдите враћање верзије",
|
||||
"currentDocumentStatus": "Тренутни {{docStatus}} документа",
|
||||
"draft": "Нацрт",
|
||||
"draftSavedSuccessfully": "Нацрт успешно сачуван.",
|
||||
"lastSavedAgo": "Задњи пут сачувано пре {{distance}",
|
||||
"noFurtherVersionsFound": "Нису пронађене наредне верзије",
|
||||
"noRowsFound": "{{label}} није пронађено",
|
||||
"preview": "Преглед",
|
||||
"problemRestoringVersion": "Настао је проблем при враћању ове верзије",
|
||||
"publish": "Објавити",
|
||||
"publishChanges": "Објави промене",
|
||||
"published": "Објављено",
|
||||
"restoreThisVersion": "Врати ову верзију",
|
||||
"restoredSuccessfully": "Успешно враћено.",
|
||||
"restoring": "Враћање...",
|
||||
"revertToPublished": "Врати на објављено",
|
||||
"reverting": "Враћање...",
|
||||
"saveDraft": "Сачувај нацрт",
|
||||
"selectLocales": "Одаберите језике",
|
||||
"selectVersionToCompare": "Одаберите верзију за упоређивање",
|
||||
"showLocales": "Прикажи језике:",
|
||||
"showingVersionsFor": "Показујем верзије за:",
|
||||
"status": "Статус",
|
||||
"type": "Тип",
|
||||
"unpublish": "Поништи објаву",
|
||||
"unpublishing": "Поништавање објаве...",
|
||||
"version": "Верзија",
|
||||
"versionCount_many": "{{count}} пронађених верзија",
|
||||
"versionCount_none": "Нема пронађених верзија",
|
||||
"versionCount_one": "{{count}} пронађена верзија",
|
||||
"versionCount_other": "{{count}} пронађених верзија",
|
||||
"versionCreatedOn": "{{version}} креираних:",
|
||||
"versionID": "Идентификатор верзије",
|
||||
"versions": "Верзије",
|
||||
"viewingVersion": "Преглед верзије за {{entityLabel}} {{documentTitle}}",
|
||||
"viewingVersionGlobal": "Преглед верзије за глобални {{entityLabel}}",
|
||||
"viewingVersions": "Преглед верзија за {{entityLabel}} {{documentTitle}}",
|
||||
"viewingVersionsGlobal": "Преглед верзије за глобални {{entityLabel}}"
|
||||
}
|
||||
}
|
||||
@@ -268,6 +268,12 @@
|
||||
"loadingDocument": {
|
||||
"type": "string"
|
||||
},
|
||||
"localesNotSaved_one": {
|
||||
"type": "string"
|
||||
},
|
||||
"localesNotSaved_other": {
|
||||
"type": "string"
|
||||
},
|
||||
"missingEmail": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -348,6 +354,8 @@
|
||||
"invalidFileType",
|
||||
"invalidFileTypeValue",
|
||||
"loadingDocument",
|
||||
"localesNotSaved_one",
|
||||
"localesNotSaved_other",
|
||||
"missingEmail",
|
||||
"missingIDOfDocument",
|
||||
"missingIDOfVersion",
|
||||
|
||||
@@ -132,10 +132,20 @@ function fieldsToJSONSchema(
|
||||
}
|
||||
|
||||
case 'richText': {
|
||||
fieldSchema = field.editor.outputSchema({
|
||||
field,
|
||||
isRequired,
|
||||
})
|
||||
if (field.editor.outputSchema) {
|
||||
fieldSchema = field.editor.outputSchema({
|
||||
field,
|
||||
isRequired,
|
||||
})
|
||||
} else {
|
||||
// Maintain backwards compatibility with existing rich text editors
|
||||
fieldSchema = {
|
||||
items: {
|
||||
type: 'object',
|
||||
},
|
||||
type: withNullableJSONSchemaType('array', isRequired),
|
||||
}
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
@@ -74,7 +74,7 @@ export const fieldSchemaToJSON = (fields: Field[]): FieldSchemaJSON => {
|
||||
tabFields.push({
|
||||
name: tab.name,
|
||||
fields: fieldSchemaToJSON(tab.fields),
|
||||
type: 'tab',
|
||||
type: field.type,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
@@ -216,33 +216,33 @@ export async function getEntityPolicies<T extends Args>(args: T): Promise<Return
|
||||
)
|
||||
}
|
||||
|
||||
await Promise.all(
|
||||
operations.map(async (operation) => {
|
||||
let entityAccessPromise: Promise<void>
|
||||
await operations.reduce(async (priorOperation, operation) => {
|
||||
await priorOperation
|
||||
|
||||
if (typeof entity.access[operation] === 'function') {
|
||||
entityAccessPromise = createAccessPromise({
|
||||
access: entity.access[operation],
|
||||
accessLevel: 'entity',
|
||||
operation,
|
||||
policiesObj: policies,
|
||||
})
|
||||
} else {
|
||||
policies[operation] = {
|
||||
permission: isLoggedIn,
|
||||
}
|
||||
}
|
||||
let entityAccessPromise: Promise<void>
|
||||
|
||||
await entityAccessPromise
|
||||
|
||||
await executeFieldPolicies({
|
||||
entityPermission: policies[operation].permission,
|
||||
fields: entity.fields,
|
||||
if (typeof entity.access[operation] === 'function') {
|
||||
entityAccessPromise = createAccessPromise({
|
||||
access: entity.access[operation],
|
||||
accessLevel: 'entity',
|
||||
operation,
|
||||
policiesObj: policies,
|
||||
})
|
||||
}),
|
||||
)
|
||||
} else {
|
||||
policies[operation] = {
|
||||
permission: isLoggedIn,
|
||||
}
|
||||
}
|
||||
|
||||
await entityAccessPromise
|
||||
|
||||
await executeFieldPolicies({
|
||||
entityPermission: policies[operation].permission,
|
||||
fields: entity.fields,
|
||||
operation,
|
||||
policiesObj: policies,
|
||||
})
|
||||
}, Promise.resolve())
|
||||
|
||||
return policies
|
||||
}
|
||||
|
||||
@@ -20,8 +20,8 @@ export const adminInit = (req: PayloadRequest): void => {
|
||||
domainID = oneWayHash(host, payload.secret)
|
||||
}
|
||||
|
||||
if (user && typeof user?.id === 'string') {
|
||||
userID = oneWayHash(user.id, payload.secret)
|
||||
if (user?.id) {
|
||||
userID = oneWayHash(String(user.id), payload.secret)
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
|
||||
@@ -10,14 +10,14 @@
|
||||
"build:swc": "swc ./src -d ./dist --config-file .swcrc",
|
||||
"build:types": "tsc --emitDeclarationOnly --outDir dist",
|
||||
"clean": "rimraf {dist,*.tsbuildinfo}",
|
||||
"prepublishOnly": "yarn clean && yarn build",
|
||||
"prepublishOnly": "pnpm clean && pnpm build",
|
||||
"test": "echo \"No tests available.\""
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@aws-sdk/client-s3": "^3.142.0",
|
||||
"@aws-sdk/lib-storage": "^3.267.0",
|
||||
"@azure/storage-blob": "^12.11.0",
|
||||
"@azure/abort-controller": "^1.0.0",
|
||||
"@azure/storage-blob": "^12.11.0",
|
||||
"@google-cloud/storage": "^6.4.1",
|
||||
"payload": "^1.7.2 || ^2.0.0"
|
||||
},
|
||||
@@ -49,6 +49,7 @@
|
||||
"@azure/storage-blob": "^12.11.0",
|
||||
"@google-cloud/storage": "^6.4.1",
|
||||
"@types/express": "^4.17.9",
|
||||
"@types/find-node-modules": "^2.1.2",
|
||||
"cross-env": "^7.0.3",
|
||||
"dotenv": "^8.2.0",
|
||||
"nodemon": "^2.0.6",
|
||||
@@ -58,6 +59,7 @@
|
||||
"webpack": "^5.78.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"find-node-modules": "^2.1.3",
|
||||
"range-parser": "^1.2.1"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,37 @@
|
||||
import type { Configuration as WebpackConfig } from 'webpack'
|
||||
|
||||
import findNodeModules from 'find-node-modules'
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
|
||||
const packageName = '@payloadcms/plugin-cloud-storage'
|
||||
|
||||
const nodeModulesPaths = findNodeModules({ cwd: __dirname, relative: false })
|
||||
|
||||
export const extendWebpackConfig = (existingWebpackConfig: WebpackConfig): WebpackConfig => {
|
||||
let nodeModulesPath = nodeModulesPaths.find((p) => {
|
||||
const guess = path.resolve(p, `${packageName}/dist`)
|
||||
|
||||
if (fs.existsSync(guess)) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
})
|
||||
|
||||
if (!nodeModulesPath) {
|
||||
nodeModulesPath = process.cwd()
|
||||
}
|
||||
|
||||
const newConfig: WebpackConfig = {
|
||||
...existingWebpackConfig,
|
||||
resolve: {
|
||||
...(existingWebpackConfig.resolve || {}),
|
||||
alias: {
|
||||
...(existingWebpackConfig.resolve?.alias ? existingWebpackConfig.resolve.alias : {}),
|
||||
'@payloadcms/plugin-cloud-storage/s3': path.resolve(__dirname, './mock.js'),
|
||||
'@payloadcms/plugin-cloud-storage/s3$': path.resolve(
|
||||
nodeModulesPath,
|
||||
`./${packageName}/dist/adapters/s3/mock.js`,
|
||||
),
|
||||
},
|
||||
fallback: {
|
||||
...(existingWebpackConfig.resolve?.fallback ? existingWebpackConfig.resolve.fallback : {}),
|
||||
|
||||
@@ -29,7 +29,7 @@ export const extendWebpackConfig =
|
||||
},
|
||||
}
|
||||
|
||||
return Object.entries(options.collections).reduce(
|
||||
const modifiedConfig = Object.entries(options.collections).reduce(
|
||||
(resultingWebpackConfig, [slug, collectionOptions]) => {
|
||||
const matchedCollection = config.collections?.find((coll) => coll.slug === slug)
|
||||
|
||||
@@ -47,4 +47,6 @@ export const extendWebpackConfig =
|
||||
},
|
||||
newConfig,
|
||||
)
|
||||
|
||||
return modifiedConfig
|
||||
}
|
||||
|
||||
@@ -62,9 +62,7 @@ The `parent` relationship field is automatically added to every document which a
|
||||
|
||||
#### Breadcrumbs
|
||||
|
||||
The `breadcrumbs` field is an array which dynamically populates all parent relationships of a document up to the top level. It does not store any data in the database, and instead, acts as a `virtual` field which is dynamically populated each time the document is loaded.
|
||||
|
||||
The `breadcrumbs` array stores the following fields:
|
||||
The `breadcrumbs` field is an array which dynamically populates all parent relationships of a document up to the top level and stores the following fields.
|
||||
|
||||
- `label`
|
||||
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import type { RelationshipField } from 'payload/types'
|
||||
import type { SingleRelationshipField } from 'payload/types'
|
||||
|
||||
const createParentField = (
|
||||
relationTo: string,
|
||||
overrides?: Partial<
|
||||
RelationshipField & {
|
||||
SingleRelationshipField & {
|
||||
hasMany: false
|
||||
}
|
||||
>,
|
||||
): RelationshipField => ({
|
||||
): SingleRelationshipField => ({
|
||||
name: 'parent',
|
||||
relationTo,
|
||||
type: 'relationship',
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
PAYLOAD_PUBLIC_CMS_URL=http://localhost:3000
|
||||
MONGODB_URI=mongodb://localhost/payload-plugin-stripe
|
||||
DATABASE_URI=mongodb://localhost/payload-plugin-stripe
|
||||
PAYLOAD_SECRET=
|
||||
STRIPE_SECRET_KEY=
|
||||
STRIPE_WEBHOOKS_ENDPOINT_SECRET=
|
||||
|
||||
@@ -9,13 +9,18 @@
|
||||
"build:payload": "cross-env PAYLOAD_CONFIG_PATH=src/payload.config.ts payload build",
|
||||
"build:server": "tsc",
|
||||
"build": "yarn build:payload && yarn build:server",
|
||||
"serve": "cross-env PAYLOAD_CONFIG_PATH=dist/payload.config.js NODE_ENV=production node dist/server.js",
|
||||
"generate:types": "cross-env PAYLOAD_CONFIG_PATH=src/payload.config.ts payload generate:types"
|
||||
"serve": "cross-env PAYLOAD_CONFIG_PATH=dist/demo/src/payload.config.js NODE_ENV=production node dist/demo/src/server.js",
|
||||
"generate:types": "cross-env PAYLOAD_CONFIG_PATH=src/payload.config.ts payload generate:types",
|
||||
"clean": "rm -rf node_modules/@payloadcms/bundler-vite/node_modules/.vite build dist"
|
||||
},
|
||||
"dependencies": {
|
||||
"@payloadcms/bundler-vite": "^0.1.4",
|
||||
"@payloadcms/bundler-webpack": "^1.0.5",
|
||||
"@payloadcms/db-mongodb": "^1.0.6",
|
||||
"@payloadcms/richtext-lexical": "^0.1.16",
|
||||
"dotenv": "^8.2.0",
|
||||
"express": "^4.17.1",
|
||||
"payload": "^1.8.2",
|
||||
"payload": "^2.2.1",
|
||||
"stripe": "^10.10.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -32,6 +32,7 @@ const Customers: CollectionConfig = {
|
||||
type: 'ui',
|
||||
admin: {
|
||||
components: {
|
||||
// @ts-expect-error
|
||||
Field: (args) =>
|
||||
LinkToDoc({
|
||||
...args,
|
||||
|
||||
4
packages/plugin-stripe/demo/src/emptyModuleMock.js
Normal file
4
packages/plugin-stripe/demo/src/emptyModuleMock.js
Normal file
@@ -0,0 +1,4 @@
|
||||
module.exports = {
|
||||
raw: () => {},
|
||||
url: () => {},
|
||||
}
|
||||
@@ -1,7 +1,11 @@
|
||||
import path from 'path'
|
||||
import { buildConfig } from 'payload/config'
|
||||
import { viteBundler } from '@payloadcms/bundler-vite'
|
||||
// import { webpackBundler } from '@payloadcms/bundler-webpack'
|
||||
import { mongooseAdapter } from '@payloadcms/db-mongodb'
|
||||
import { lexicalEditor } from '@payloadcms/richtext-lexical'
|
||||
|
||||
import { stripePlugin } from '../../src'
|
||||
import stripePlugin from '../../src'
|
||||
import Customers from './collections/Customers'
|
||||
import Products from './collections/Products'
|
||||
import Users from './collections/Users'
|
||||
@@ -13,28 +17,11 @@ export default buildConfig({
|
||||
serverURL: process.env.PAYLOAD_PUBLIC_CMS_URL,
|
||||
admin: {
|
||||
user: Users.slug,
|
||||
webpack: (config) => {
|
||||
const newConfig = {
|
||||
...config,
|
||||
resolve: {
|
||||
...config.resolve,
|
||||
alias: {
|
||||
...config.resolve.alias,
|
||||
payload: path.join(__dirname, '../node_modules/payload'),
|
||||
react: path.join(__dirname, '../node_modules/react'),
|
||||
'react-dom': path.join(__dirname, '../node_modules/react-dom'),
|
||||
[path.resolve(__dirname, '../../src/index')]: path.resolve(
|
||||
__dirname,
|
||||
'../../src/admin.ts',
|
||||
),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return newConfig
|
||||
},
|
||||
bundler: viteBundler(),
|
||||
// bundler: webpackBundler(),
|
||||
},
|
||||
collections: [Users, Customers, Products],
|
||||
editor: lexicalEditor({}),
|
||||
localization: {
|
||||
locales: ['en', 'es', 'de'],
|
||||
defaultLocale: 'en',
|
||||
@@ -98,4 +85,7 @@ export default buildConfig({
|
||||
typescript: {
|
||||
outputFile: path.resolve(__dirname, 'payload-types.ts'),
|
||||
},
|
||||
db: mongooseAdapter({
|
||||
url: process.env.DATABASE_URI,
|
||||
}),
|
||||
})
|
||||
|
||||
@@ -14,7 +14,6 @@ app.get('/', (_, res) => {
|
||||
const start = async (): Promise<void> => {
|
||||
await payload.init({
|
||||
secret: process.env.PAYLOAD_SECRET,
|
||||
mongoURL: process.env.MONGODB_URI,
|
||||
express: app,
|
||||
onInit: () => {
|
||||
payload.logger.info(`Payload Admin URL: ${payload.getAdminURL()}`)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -41,7 +41,7 @@
|
||||
"@payloadcms/eslint-config": "workspace:*",
|
||||
"@types/express": "^4.17.9",
|
||||
"@types/react": "18.0.21",
|
||||
"payload": "^1.8.2",
|
||||
"payload": "workspace:*",
|
||||
"prettier": "^2.7.1",
|
||||
"react": "^18.0.0",
|
||||
"webpack": "^5.78.0"
|
||||
|
||||
@@ -4,7 +4,7 @@ import type { SanitizedStripeConfig, StripeConfig } from './types'
|
||||
|
||||
import { getFields } from './fields/getFields'
|
||||
|
||||
export const stripePlugin =
|
||||
const stripePlugin =
|
||||
(incomingStripeConfig: StripeConfig) =>
|
||||
(config: Config): Config => {
|
||||
const { collections } = config
|
||||
@@ -42,3 +42,5 @@ export const stripePlugin =
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
export default stripePlugin
|
||||
|
||||
@@ -11,6 +11,8 @@ export const extendWebpackConfig =
|
||||
? config.admin.webpack(webpackConfig)
|
||||
: webpackConfig
|
||||
|
||||
const mockModulePath = path.resolve(__dirname, './mocks/mockFile.js')
|
||||
|
||||
return {
|
||||
...existingWebpackConfig,
|
||||
resolve: {
|
||||
@@ -18,6 +20,12 @@ export const extendWebpackConfig =
|
||||
alias: {
|
||||
...(existingWebpackConfig.resolve?.alias ? existingWebpackConfig.resolve.alias : {}),
|
||||
'@payloadcms/plugin-stripe': path.resolve(__dirname, './admin.js'),
|
||||
express: mockModulePath,
|
||||
[path.resolve(__dirname, './hooks/createNewInStripe')]: mockModulePath,
|
||||
[path.resolve(__dirname, './hooks/deleteFromStripe')]: mockModulePath,
|
||||
[path.resolve(__dirname, './hooks/syncExistingWithStripe')]: mockModulePath,
|
||||
[path.resolve(__dirname, './routes/rest')]: mockModulePath,
|
||||
[path.resolve(__dirname, './routes/webhooks')]: mockModulePath,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -10,8 +10,15 @@ import { deepen } from '../utilities/deepen'
|
||||
const stripeSecretKey = process.env.STRIPE_SECRET_KEY
|
||||
const stripe = new Stripe(stripeSecretKey || '', { apiVersion: '2022-08-01' })
|
||||
|
||||
type HookArgsWithCustomCollection = Omit<
|
||||
Parameters<CollectionBeforeValidateHook>[0],
|
||||
'collection'
|
||||
> & {
|
||||
collection: CollectionConfig
|
||||
}
|
||||
|
||||
export type CollectionBeforeValidateHookWithArgs = (
|
||||
args: Parameters<CollectionBeforeValidateHook>[0] & {
|
||||
args: HookArgsWithCustomCollection & {
|
||||
collection?: CollectionConfig
|
||||
stripeConfig?: StripeConfig
|
||||
},
|
||||
|
||||
@@ -8,8 +8,12 @@ import type { StripeConfig } from '../types'
|
||||
const stripeSecretKey = process.env.STRIPE_SECRET_KEY
|
||||
const stripe = new Stripe(stripeSecretKey || '', { apiVersion: '2022-08-01' })
|
||||
|
||||
type HookArgsWithCustomCollection = Omit<Parameters<CollectionAfterDeleteHook>[0], 'collection'> & {
|
||||
collection: CollectionConfig
|
||||
}
|
||||
|
||||
export type CollectionAfterDeleteHookWithArgs = (
|
||||
args: Parameters<CollectionAfterDeleteHook>[0] & {
|
||||
args: HookArgsWithCustomCollection & {
|
||||
collection?: CollectionConfig
|
||||
stripeConfig?: StripeConfig
|
||||
},
|
||||
|
||||
@@ -10,8 +10,15 @@ import { deepen } from '../utilities/deepen'
|
||||
const stripeSecretKey = process.env.STRIPE_SECRET_KEY
|
||||
const stripe = new Stripe(stripeSecretKey || '', { apiVersion: '2022-08-01' })
|
||||
|
||||
type HookArgsWithCustomCollection = Omit<
|
||||
Parameters<CollectionBeforeChangeHook>[0],
|
||||
'collection'
|
||||
> & {
|
||||
collection: CollectionConfig
|
||||
}
|
||||
|
||||
export type CollectionBeforeChangeHookWithArgs = (
|
||||
args: Parameters<CollectionBeforeChangeHook>[0] & {
|
||||
args: HookArgsWithCustomCollection & {
|
||||
collection?: CollectionConfig
|
||||
stripeConfig?: StripeConfig
|
||||
},
|
||||
|
||||
@@ -14,7 +14,7 @@ import { syncExistingWithStripe } from './hooks/syncExistingWithStripe'
|
||||
import { stripeREST } from './routes/rest'
|
||||
import { stripeWebhooks } from './routes/webhooks'
|
||||
|
||||
export const stripePlugin =
|
||||
const stripePlugin =
|
||||
(incomingStripeConfig: StripeConfig) =>
|
||||
(config: Config): Config => {
|
||||
const { collections } = config
|
||||
@@ -55,7 +55,7 @@ export const stripePlugin =
|
||||
...collection.hooks,
|
||||
afterDelete: [
|
||||
...(existingHooks?.afterDelete || []),
|
||||
async (args) =>
|
||||
(args) =>
|
||||
deleteFromStripe({
|
||||
...args,
|
||||
collection,
|
||||
@@ -64,7 +64,7 @@ export const stripePlugin =
|
||||
],
|
||||
beforeChange: [
|
||||
...(existingHooks?.beforeChange || []),
|
||||
async (args) =>
|
||||
(args) =>
|
||||
syncExistingWithStripe({
|
||||
...args,
|
||||
collection,
|
||||
@@ -73,7 +73,7 @@ export const stripePlugin =
|
||||
],
|
||||
beforeValidate: [
|
||||
...(existingHooks?.beforeValidate || []),
|
||||
async (args) =>
|
||||
(args) =>
|
||||
createNewInStripe({
|
||||
...args,
|
||||
collection,
|
||||
@@ -91,8 +91,8 @@ export const stripePlugin =
|
||||
{
|
||||
handler: [
|
||||
express.raw({ type: 'application/json' }),
|
||||
(req, res, next) => {
|
||||
stripeWebhooks({
|
||||
async (req, res, next) => {
|
||||
await stripeWebhooks({
|
||||
config,
|
||||
next,
|
||||
req,
|
||||
@@ -108,8 +108,8 @@ export const stripePlugin =
|
||||
...(incomingStripeConfig?.rest
|
||||
? [
|
||||
{
|
||||
handler: (req: PayloadRequest, res: Response, next: NextFunction) => {
|
||||
stripeREST({
|
||||
handler: async (req: PayloadRequest, res: Response, next: NextFunction) => {
|
||||
await stripeREST({
|
||||
next,
|
||||
req,
|
||||
res,
|
||||
@@ -124,3 +124,5 @@ export const stripePlugin =
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
export default stripePlugin
|
||||
|
||||
9
packages/plugin-stripe/src/mocks/mockFile.js
Normal file
9
packages/plugin-stripe/src/mocks/mockFile.js
Normal file
@@ -0,0 +1,9 @@
|
||||
export const createNewInStripe = () => null
|
||||
export const deleteFromStripe = () => null
|
||||
export const stripeREST = () => null
|
||||
export const stripeWebhooks = () => null
|
||||
export const syncExistingWithStripe = () => null
|
||||
|
||||
export default {
|
||||
raw: () => {}, // mock express fn
|
||||
}
|
||||
@@ -46,7 +46,7 @@ export const stripeWebhooks = async (args: {
|
||||
}
|
||||
|
||||
if (event) {
|
||||
await handleWebhooks({
|
||||
handleWebhooks({
|
||||
config,
|
||||
event,
|
||||
payload: req.payload,
|
||||
|
||||
@@ -31,6 +31,7 @@ export const LinkToDoc: React.FC<
|
||||
>
|
||||
View in Stripe
|
||||
</span>
|
||||
{/* @ts-ignore */}
|
||||
<CopyToClipboard value={href} />
|
||||
</div>
|
||||
<div
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/richtext-lexical",
|
||||
"version": "0.2.0",
|
||||
"version": "0.3.0",
|
||||
"description": "The officially supported Lexical richtext adapter for Payload",
|
||||
"repository": "https://github.com/payloadcms/payload",
|
||||
"license": "MIT",
|
||||
@@ -47,7 +47,7 @@
|
||||
"payload": "workspace:*"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"payload": "^2.2.0"
|
||||
"payload": "^2.3.0"
|
||||
},
|
||||
"exports": {
|
||||
".": {
|
||||
|
||||
@@ -64,6 +64,7 @@ const RichText: React.FC<FieldProps> = (props) => {
|
||||
return (
|
||||
<div
|
||||
className={classes}
|
||||
key={path}
|
||||
style={{
|
||||
...style,
|
||||
width,
|
||||
@@ -88,6 +89,7 @@ const RichText: React.FC<FieldProps> = (props) => {
|
||||
|
||||
setValue(serializedEditorState)
|
||||
}}
|
||||
path={path}
|
||||
readOnly={readOnly}
|
||||
value={value}
|
||||
/>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user