feat: lock documents while being edited (#7970)
## Description
Adds a new property to `collection` / `global` configs called
`lockDocuments`.
Set to `true` by default - the lock is automatically triggered when a
user begins editing a document within the Admin Panel and remains in
place until the user exits the editing view or the lock expires due to
inactivity.
Set to `false` to disable document locking entirely - i.e.
`lockDocuments: false`
You can pass an object to this property to configure the `duration` in
seconds, which defines how long the document remains locked without user
interaction. If no edits are made within the specified time (default:
300 seconds), the lock expires, allowing other users to edit / update or
delete the document.
```
lockDocuments: {
duration: 180, // 180 seconds or 3 minutes
}
```
- [x] I have read and understand the
[CONTRIBUTING.md](https://github.com/payloadcms/payload/blob/main/CONTRIBUTING.md)
document in this repository.
## Type of change
- [x] New feature (non-breaking change which adds functionality)
## Checklist:
- [x] I have added tests that prove my fix is effective or that my
feature works
- [x] Existing test suite passes locally with my changes
- [x] I have made corresponding changes to the documentation
This commit is contained in:
1
.github/workflows/main.yml
vendored
1
.github/workflows/main.yml
vendored
@@ -310,6 +310,7 @@ jobs:
|
||||
- fields__collections__Upload
|
||||
- live-preview
|
||||
- localization
|
||||
- locked-documents
|
||||
- i18n
|
||||
- plugin-cloud-storage
|
||||
- plugin-form-builder
|
||||
|
||||
7
.vscode/launch.json
vendored
7
.vscode/launch.json
vendored
@@ -118,6 +118,13 @@
|
||||
"request": "launch",
|
||||
"type": "node-terminal"
|
||||
},
|
||||
{
|
||||
"command": "node --no-deprecation test/dev.js locked-documents",
|
||||
"cwd": "${workspaceFolder}",
|
||||
"name": "Run Dev Locked Documents",
|
||||
"request": "launch",
|
||||
"type": "node-terminal"
|
||||
},
|
||||
{
|
||||
"command": "node --no-deprecation test/dev.js uploads",
|
||||
"cwd": "${workspaceFolder}",
|
||||
|
||||
60
docs/admin/locked-documents.mdx
Normal file
60
docs/admin/locked-documents.mdx
Normal file
@@ -0,0 +1,60 @@
|
||||
---
|
||||
title: Document Locking
|
||||
label: Document Locking
|
||||
order: 90
|
||||
desc: Ensure your documents are locked while being edited, preventing concurrent edits from multiple users and preserving data integrity.
|
||||
keywords: locking, document locking, edit locking, document, concurrency, Payload, headless, Content Management System, cms, javascript, react, node, nextjs
|
||||
---
|
||||
|
||||
Document locking in Payload ensures that only one user at a time can edit a document, preventing data conflicts and accidental overwrites. When a document is locked, other users are prevented from making changes until the lock is released, ensuring data integrity in collaborative environments.
|
||||
|
||||
The lock is automatically triggered when a user begins editing a document within the Admin Panel and remains in place until the user exits the editing view or the lock expires due to inactivity.
|
||||
|
||||
## How it works
|
||||
|
||||
When a user starts editing a document, Payload locks the document for that user. If another user tries to access the same document, they will be notified that it is currently being edited and can choose one of the following options:
|
||||
|
||||
- View in Read-Only Mode: View the document without making any changes.
|
||||
- Take Over Editing: Take over editing from the current user, which locks the document for the new editor and notifies the original user.
|
||||
- Return to Dashboard: Navigate away from the locked document and continue with other tasks.
|
||||
|
||||
The lock will automatically expire after a set period of inactivity, configurable using the duration property in the lockDocuments configuration, after which others can resume editing.
|
||||
|
||||
<Banner type="info"> <strong>Note:</strong> If your application does not require document locking, you can disable this feature for any collection by setting the <code>lockDocuments</code> property to <code>false</code>. </Banner>
|
||||
|
||||
### Config Options
|
||||
|
||||
The lockDocuments property exists on both the Collection Config and the Global Config. By default, document locking is enabled for all collections and globals, but you can customize the lock duration or disable the feature entirely.
|
||||
|
||||
Here’s an example configuration for document locking:
|
||||
|
||||
```ts
|
||||
import { CollectionConfig } from 'payload'
|
||||
|
||||
export const Posts: CollectionConfig = {
|
||||
slug: 'posts',
|
||||
fields: [
|
||||
{
|
||||
name: 'title',
|
||||
type: 'text',
|
||||
},
|
||||
// other fields...
|
||||
],
|
||||
lockDocuments: {
|
||||
duration: 600, // Duration in seconds
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
#### Locking Options
|
||||
|
||||
| Option | Description |
|
||||
| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| **`lockDocuments`** | Enables or disables document locking for the collection or global. By default, document locking is enabled. Set to an object to configure, or set to false to disable locking. |
|
||||
| **`duration`** | Specifies the duration (in seconds) for how long a document remains locked without user interaction. The default is 300 seconds (5 minutes). |
|
||||
|
||||
### Impact on APIs
|
||||
|
||||
Document locking affects both the Local API and the REST API, ensuring that if a document is locked, concurrent users will not be able to perform updates or deletes on that document (including globals). If a user attempts to update or delete a locked document, they will receive an error.
|
||||
|
||||
Once the document is unlocked or the lock duration has expired, other users can proceed with updates or deletes as normal.
|
||||
@@ -57,25 +57,26 @@ export const Posts: CollectionConfig = {
|
||||
|
||||
The following options are available:
|
||||
|
||||
| Option | Description |
|
||||
|------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| **`admin`** | The configuration options for the Admin Panel. [More details](../admin/collections). |
|
||||
| **`access`** | Provide Access Control functions to define exactly who should be able to do what with Documents in this Collection. [More details](../access-control/collections). |
|
||||
| **`auth`** | Specify options if you would like this Collection to feature authentication. [More details](../authentication/overview). |
|
||||
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
|
||||
| **`disableDuplicate`** | When true, do not show the "Duplicate" button while editing documents within this Collection and prevent `duplicate` from all APIs. |
|
||||
| **`defaultSort`** | Pass a top-level field to sort by default in the Collection List View. Prefix the name of the field with a minus symbol ("-") to sort in descending order. |
|
||||
| **`dbName`** | Custom table or Collection name depending on the Database Adapter. Auto-generated from slug if not defined. |
|
||||
| **`endpoints`** | Add custom routes to the REST API. Set to `false` to disable routes. [More details](../rest-api/overview#custom-endpoints). |
|
||||
| **`fields`** \* | Array of field types that will determine the structure and functionality of the data stored within this Collection. [More details](../fields/overview). |
|
||||
| **`graphQL`** | An object with `singularName` and `pluralName` strings used in schema generation. Auto-generated from slug if not defined. Set to `false` to disable GraphQL. |
|
||||
| **`hooks`** | Entry point for Hooks. [More details](../hooks/overview#collection-hooks). |
|
||||
| **`labels`** | Singular and plural labels for use in identifying this Collection throughout Payload. Auto-generated from slug if not defined. |
|
||||
| **`slug`** \* | Unique, URL-friendly string that will act as an identifier for this Collection. |
|
||||
| **`timestamps`** | Set to false to disable documents' automatically generated `createdAt` and `updatedAt` timestamps. |
|
||||
| **`typescript`** | An object with property `interface` as the text used in schema generation. Auto-generated from slug if not defined. |
|
||||
| **`upload`** | Specify options if you would like this Collection to support file uploads. For more, consult the [Uploads](../upload/overview) documentation. |
|
||||
| **`versions`** | Set to true to enable default options, or configure with object properties. [More details](../versions/overview#collection-config). |
|
||||
| Option | Description |
|
||||
|------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **`admin`** | The configuration options for the Admin Panel. [More details](../admin/collections). |
|
||||
| **`access`** | Provide Access Control functions to define exactly who should be able to do what with Documents in this Collection. [More details](../access-control/collections). |
|
||||
| **`auth`** | Specify options if you would like this Collection to feature authentication. [More details](../authentication/overview). |
|
||||
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
|
||||
| **`disableDuplicate`** | When true, do not show the "Duplicate" button while editing documents within this Collection and prevent `duplicate` from all APIs. |
|
||||
| **`defaultSort`** | Pass a top-level field to sort by default in the Collection List View. Prefix the name of the field with a minus symbol ("-") to sort in descending order. |
|
||||
| **`dbName`** | Custom table or Collection name depending on the Database Adapter. Auto-generated from slug if not defined. |
|
||||
| **`endpoints`** | Add custom routes to the REST API. Set to `false` to disable routes. [More details](../rest-api/overview#custom-endpoints). |
|
||||
| **`fields`** \* | Array of field types that will determine the structure and functionality of the data stored within this Collection. [More details](../fields/overview). |
|
||||
| **`graphQL`** | An object with `singularName` and `pluralName` strings used in schema generation. Auto-generated from slug if not defined. Set to `false` to disable GraphQL. |
|
||||
| **`hooks`** | Entry point for Hooks. [More details](../hooks/overview#collection-hooks). |
|
||||
| **`labels`** | Singular and plural labels for use in identifying this Collection throughout Payload. Auto-generated from slug if not defined. |
|
||||
| **`lockDocuments`** | Enables or disables document locking. By default, document locking is enabled. Set to an object to configure, or set to `false` to disable locking. [More details](../admin/locked-documents). |
|
||||
| **`slug`** \* | Unique, URL-friendly string that will act as an identifier for this Collection. |
|
||||
| **`timestamps`** | Set to false to disable documents' automatically generated `createdAt` and `updatedAt` timestamps. |
|
||||
| **`typescript`** | An object with property `interface` as the text used in schema generation. Auto-generated from slug if not defined. |
|
||||
| **`upload`** | Specify options if you would like this Collection to support file uploads. For more, consult the [Uploads](../upload/overview) documentation. |
|
||||
| **`versions`** | Set to true to enable default options, or configure with object properties. [More details](../versions/overview#collection-config). |
|
||||
|
||||
_\* An asterisk denotes that a property is required._
|
||||
|
||||
|
||||
@@ -65,21 +65,22 @@ export const Nav: GlobalConfig = {
|
||||
|
||||
The following options are available:
|
||||
|
||||
| Option | Description |
|
||||
| ------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **`access`** | Provide Access Control functions to define exactly who should be able to do what with this Global. [More details](../access-control/globals). |
|
||||
| **`admin`** | The configuration options for the Admin Panel. [More details](../admin/globals). |
|
||||
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
|
||||
| **`dbName`** | Custom table or collection name for this Global depending on the Database Adapter. Auto-generated from slug if not defined. |
|
||||
| **`description`** | Text or React component to display below the Global header to give editors more information. |
|
||||
| **`endpoints`** | Add custom routes to the REST API. [More details](../rest-api/overview#custom-endpoints). |
|
||||
| **`fields`** \* | Array of field types that will determine the structure and functionality of the data stored within this Global. [More details](../fields/overview). |
|
||||
| **`graphQL.name`** | Text used in schema generation. Auto-generated from slug if not defined. |
|
||||
| **`hooks`** | Entry point for Hooks. [More details](../hooks/overview#global-hooks). |
|
||||
| **`label`** | Text for the name in the Admin Panel or an object with keys for each language. Auto-generated from slug if not defined. |
|
||||
| **`slug`** \* | Unique, URL-friendly string that will act as an identifier for this Global. |
|
||||
| **`typescript`** | An object with property `interface` as the text used in schema generation. Auto-generated from slug if not defined. |
|
||||
| **`versions`** | Set to true to enable default options, or configure with object properties. [More details](../versions/overview#globals-config). |
|
||||
| Option | Description |
|
||||
| ------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **`access`** | Provide Access Control functions to define exactly who should be able to do what with this Global. [More details](../access-control/globals). |
|
||||
| **`admin`** | The configuration options for the Admin Panel. [More details](../admin/globals). |
|
||||
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
|
||||
| **`dbName`** | Custom table or collection name for this Global depending on the Database Adapter. Auto-generated from slug if not defined. |
|
||||
| **`description`** | Text or React component to display below the Global header to give editors more information. |
|
||||
| **`endpoints`** | Add custom routes to the REST API. [More details](../rest-api/overview#custom-endpoints). |
|
||||
| **`fields`** \* | Array of field types that will determine the structure and functionality of the data stored within this Global. [More details](../fields/overview). |
|
||||
| **`graphQL.name`** | Text used in schema generation. Auto-generated from slug if not defined. |
|
||||
| **`hooks`** | Entry point for Hooks. [More details](../hooks/overview#global-hooks). |
|
||||
| **`label`** | Text for the name in the Admin Panel or an object with keys for each language. Auto-generated from slug if not defined. |
|
||||
| **`lockDocuments`** | Enables or disables document locking. By default, document locking is enabled. Set to an object to configure, or set to `false` to disable locking. [More details](../admin/locked-documents). |
|
||||
| **`slug`** \* | Unique, URL-friendly string that will act as an identifier for this Global. |
|
||||
| **`typescript`** | An object with property `interface` as the text used in schema generation. Auto-generated from slug if not defined. |
|
||||
| **`versions`** | Set to true to enable default options, or configure with object properties. [More details](../versions/overview#globals-config). |
|
||||
|
||||
_\* An asterisk denotes that a property is required._
|
||||
|
||||
|
||||
@@ -55,59 +55,108 @@ import { CustomDefaultRootView as CustomDefaultRootView_53 } from '@/components/
|
||||
import { CustomMinimalRootView as CustomMinimalRootView_54 } from '@/components/views/CustomMinimalRootView'
|
||||
|
||||
export const importMap = {
|
||||
"@/collections/Fields/array/components/server/Label#CustomArrayFieldLabelServer": CustomArrayFieldLabelServer_0,
|
||||
"@/collections/Fields/array/components/server/Field#CustomArrayFieldServer": CustomArrayFieldServer_1,
|
||||
"@/collections/Fields/array/components/client/Label#CustomArrayFieldLabelClient": CustomArrayFieldLabelClient_2,
|
||||
"@/collections/Fields/array/components/client/Field#CustomArrayFieldClient": CustomArrayFieldClient_3,
|
||||
"@/collections/Fields/blocks/components/server/Field#CustomBlocksFieldServer": CustomBlocksFieldServer_4,
|
||||
"@/collections/Fields/blocks/components/client/Field#CustomBlocksFieldClient": CustomBlocksFieldClient_5,
|
||||
"@/collections/Fields/checkbox/components/server/Label#CustomCheckboxFieldLabelServer": CustomCheckboxFieldLabelServer_6,
|
||||
"@/collections/Fields/checkbox/components/server/Field#CustomCheckboxFieldServer": CustomCheckboxFieldServer_7,
|
||||
"@/collections/Fields/checkbox/components/client/Label#CustomCheckboxFieldLabelClient": CustomCheckboxFieldLabelClient_8,
|
||||
"@/collections/Fields/checkbox/components/client/Field#CustomCheckboxFieldClient": CustomCheckboxFieldClient_9,
|
||||
"@/collections/Fields/date/components/server/Label#CustomDateFieldLabelServer": CustomDateFieldLabelServer_10,
|
||||
"@/collections/Fields/date/components/server/Field#CustomDateFieldServer": CustomDateFieldServer_11,
|
||||
"@/collections/Fields/date/components/client/Label#CustomDateFieldLabelClient": CustomDateFieldLabelClient_12,
|
||||
"@/collections/Fields/date/components/client/Field#CustomDateFieldClient": CustomDateFieldClient_13,
|
||||
"@/collections/Fields/email/components/server/Label#CustomEmailFieldLabelServer": CustomEmailFieldLabelServer_14,
|
||||
"@/collections/Fields/email/components/server/Field#CustomEmailFieldServer": CustomEmailFieldServer_15,
|
||||
"@/collections/Fields/email/components/client/Label#CustomEmailFieldLabelClient": CustomEmailFieldLabelClient_16,
|
||||
"@/collections/Fields/email/components/client/Field#CustomEmailFieldClient": CustomEmailFieldClient_17,
|
||||
"@/collections/Fields/number/components/server/Label#CustomNumberFieldLabelServer": CustomNumberFieldLabelServer_18,
|
||||
"@/collections/Fields/number/components/server/Field#CustomNumberFieldServer": CustomNumberFieldServer_19,
|
||||
"@/collections/Fields/number/components/client/Label#CustomNumberFieldLabelClient": CustomNumberFieldLabelClient_20,
|
||||
"@/collections/Fields/number/components/client/Field#CustomNumberFieldClient": CustomNumberFieldClient_21,
|
||||
"@/collections/Fields/point/components/server/Label#CustomPointFieldLabelServer": CustomPointFieldLabelServer_22,
|
||||
"@/collections/Fields/point/components/server/Field#CustomPointFieldServer": CustomPointFieldServer_23,
|
||||
"@/collections/Fields/point/components/client/Label#CustomPointFieldLabelClient": CustomPointFieldLabelClient_24,
|
||||
"@/collections/Fields/point/components/client/Field#CustomPointFieldClient": CustomPointFieldClient_25,
|
||||
"@/collections/Fields/radio/components/server/Label#CustomRadioFieldLabelServer": CustomRadioFieldLabelServer_26,
|
||||
"@/collections/Fields/radio/components/server/Field#CustomRadioFieldServer": CustomRadioFieldServer_27,
|
||||
"@/collections/Fields/radio/components/client/Label#CustomRadioFieldLabelClient": CustomRadioFieldLabelClient_28,
|
||||
"@/collections/Fields/radio/components/client/Field#CustomRadioFieldClient": CustomRadioFieldClient_29,
|
||||
"@/collections/Fields/relationship/components/server/Label#CustomRelationshipFieldLabelServer": CustomRelationshipFieldLabelServer_30,
|
||||
"@/collections/Fields/relationship/components/server/Field#CustomRelationshipFieldServer": CustomRelationshipFieldServer_31,
|
||||
"@/collections/Fields/relationship/components/client/Label#CustomRelationshipFieldLabelClient": CustomRelationshipFieldLabelClient_32,
|
||||
"@/collections/Fields/relationship/components/client/Field#CustomRelationshipFieldClient": CustomRelationshipFieldClient_33,
|
||||
"@/collections/Fields/select/components/server/Label#CustomSelectFieldLabelServer": CustomSelectFieldLabelServer_34,
|
||||
"@/collections/Fields/select/components/server/Field#CustomSelectFieldServer": CustomSelectFieldServer_35,
|
||||
"@/collections/Fields/select/components/client/Label#CustomSelectFieldLabelClient": CustomSelectFieldLabelClient_36,
|
||||
"@/collections/Fields/select/components/client/Field#CustomSelectFieldClient": CustomSelectFieldClient_37,
|
||||
"@/collections/Fields/text/components/server/Label#CustomTextFieldLabelServer": CustomTextFieldLabelServer_38,
|
||||
"@/collections/Fields/text/components/server/Field#CustomTextFieldServer": CustomTextFieldServer_39,
|
||||
"@/collections/Fields/text/components/client/Label#CustomTextFieldLabelClient": CustomTextFieldLabelClient_40,
|
||||
"@/collections/Fields/text/components/client/Field#CustomTextFieldClient": CustomTextFieldClient_41,
|
||||
"@/collections/Fields/textarea/components/server/Label#CustomTextareaFieldLabelServer": CustomTextareaFieldLabelServer_42,
|
||||
"@/collections/Fields/textarea/components/server/Field#CustomTextareaFieldServer": CustomTextareaFieldServer_43,
|
||||
"@/collections/Fields/textarea/components/client/Label#CustomTextareaFieldLabelClient": CustomTextareaFieldLabelClient_44,
|
||||
"@/collections/Fields/textarea/components/client/Field#CustomTextareaFieldClient": CustomTextareaFieldClient_45,
|
||||
"@/collections/Views/components/CustomTabEditView#CustomTabEditView": CustomTabEditView_46,
|
||||
"@/collections/Views/components/CustomDefaultEditView#CustomDefaultEditView": CustomDefaultEditView_47,
|
||||
"@/collections/RootViews/components/CustomRootEditView#CustomRootEditView": CustomRootEditView_48,
|
||||
"@/components/afterNavLinks/LinkToCustomView#LinkToCustomView": LinkToCustomView_49,
|
||||
"@/components/afterNavLinks/LinkToCustomMinimalView#LinkToCustomMinimalView": LinkToCustomMinimalView_50,
|
||||
"@/components/afterNavLinks/LinkToCustomDefaultView#LinkToCustomDefaultView": LinkToCustomDefaultView_51,
|
||||
"@/components/views/CustomRootView#CustomRootView": CustomRootView_52,
|
||||
"@/components/views/CustomDefaultRootView#CustomDefaultRootView": CustomDefaultRootView_53,
|
||||
"@/components/views/CustomMinimalRootView#CustomMinimalRootView": CustomMinimalRootView_54
|
||||
'@/collections/Fields/array/components/server/Label#CustomArrayFieldLabelServer':
|
||||
CustomArrayFieldLabelServer_0,
|
||||
'@/collections/Fields/array/components/server/Field#CustomArrayFieldServer':
|
||||
CustomArrayFieldServer_1,
|
||||
'@/collections/Fields/array/components/client/Label#CustomArrayFieldLabelClient':
|
||||
CustomArrayFieldLabelClient_2,
|
||||
'@/collections/Fields/array/components/client/Field#CustomArrayFieldClient':
|
||||
CustomArrayFieldClient_3,
|
||||
'@/collections/Fields/blocks/components/server/Field#CustomBlocksFieldServer':
|
||||
CustomBlocksFieldServer_4,
|
||||
'@/collections/Fields/blocks/components/client/Field#CustomBlocksFieldClient':
|
||||
CustomBlocksFieldClient_5,
|
||||
'@/collections/Fields/checkbox/components/server/Label#CustomCheckboxFieldLabelServer':
|
||||
CustomCheckboxFieldLabelServer_6,
|
||||
'@/collections/Fields/checkbox/components/server/Field#CustomCheckboxFieldServer':
|
||||
CustomCheckboxFieldServer_7,
|
||||
'@/collections/Fields/checkbox/components/client/Label#CustomCheckboxFieldLabelClient':
|
||||
CustomCheckboxFieldLabelClient_8,
|
||||
'@/collections/Fields/checkbox/components/client/Field#CustomCheckboxFieldClient':
|
||||
CustomCheckboxFieldClient_9,
|
||||
'@/collections/Fields/date/components/server/Label#CustomDateFieldLabelServer':
|
||||
CustomDateFieldLabelServer_10,
|
||||
'@/collections/Fields/date/components/server/Field#CustomDateFieldServer':
|
||||
CustomDateFieldServer_11,
|
||||
'@/collections/Fields/date/components/client/Label#CustomDateFieldLabelClient':
|
||||
CustomDateFieldLabelClient_12,
|
||||
'@/collections/Fields/date/components/client/Field#CustomDateFieldClient':
|
||||
CustomDateFieldClient_13,
|
||||
'@/collections/Fields/email/components/server/Label#CustomEmailFieldLabelServer':
|
||||
CustomEmailFieldLabelServer_14,
|
||||
'@/collections/Fields/email/components/server/Field#CustomEmailFieldServer':
|
||||
CustomEmailFieldServer_15,
|
||||
'@/collections/Fields/email/components/client/Label#CustomEmailFieldLabelClient':
|
||||
CustomEmailFieldLabelClient_16,
|
||||
'@/collections/Fields/email/components/client/Field#CustomEmailFieldClient':
|
||||
CustomEmailFieldClient_17,
|
||||
'@/collections/Fields/number/components/server/Label#CustomNumberFieldLabelServer':
|
||||
CustomNumberFieldLabelServer_18,
|
||||
'@/collections/Fields/number/components/server/Field#CustomNumberFieldServer':
|
||||
CustomNumberFieldServer_19,
|
||||
'@/collections/Fields/number/components/client/Label#CustomNumberFieldLabelClient':
|
||||
CustomNumberFieldLabelClient_20,
|
||||
'@/collections/Fields/number/components/client/Field#CustomNumberFieldClient':
|
||||
CustomNumberFieldClient_21,
|
||||
'@/collections/Fields/point/components/server/Label#CustomPointFieldLabelServer':
|
||||
CustomPointFieldLabelServer_22,
|
||||
'@/collections/Fields/point/components/server/Field#CustomPointFieldServer':
|
||||
CustomPointFieldServer_23,
|
||||
'@/collections/Fields/point/components/client/Label#CustomPointFieldLabelClient':
|
||||
CustomPointFieldLabelClient_24,
|
||||
'@/collections/Fields/point/components/client/Field#CustomPointFieldClient':
|
||||
CustomPointFieldClient_25,
|
||||
'@/collections/Fields/radio/components/server/Label#CustomRadioFieldLabelServer':
|
||||
CustomRadioFieldLabelServer_26,
|
||||
'@/collections/Fields/radio/components/server/Field#CustomRadioFieldServer':
|
||||
CustomRadioFieldServer_27,
|
||||
'@/collections/Fields/radio/components/client/Label#CustomRadioFieldLabelClient':
|
||||
CustomRadioFieldLabelClient_28,
|
||||
'@/collections/Fields/radio/components/client/Field#CustomRadioFieldClient':
|
||||
CustomRadioFieldClient_29,
|
||||
'@/collections/Fields/relationship/components/server/Label#CustomRelationshipFieldLabelServer':
|
||||
CustomRelationshipFieldLabelServer_30,
|
||||
'@/collections/Fields/relationship/components/server/Field#CustomRelationshipFieldServer':
|
||||
CustomRelationshipFieldServer_31,
|
||||
'@/collections/Fields/relationship/components/client/Label#CustomRelationshipFieldLabelClient':
|
||||
CustomRelationshipFieldLabelClient_32,
|
||||
'@/collections/Fields/relationship/components/client/Field#CustomRelationshipFieldClient':
|
||||
CustomRelationshipFieldClient_33,
|
||||
'@/collections/Fields/select/components/server/Label#CustomSelectFieldLabelServer':
|
||||
CustomSelectFieldLabelServer_34,
|
||||
'@/collections/Fields/select/components/server/Field#CustomSelectFieldServer':
|
||||
CustomSelectFieldServer_35,
|
||||
'@/collections/Fields/select/components/client/Label#CustomSelectFieldLabelClient':
|
||||
CustomSelectFieldLabelClient_36,
|
||||
'@/collections/Fields/select/components/client/Field#CustomSelectFieldClient':
|
||||
CustomSelectFieldClient_37,
|
||||
'@/collections/Fields/text/components/server/Label#CustomTextFieldLabelServer':
|
||||
CustomTextFieldLabelServer_38,
|
||||
'@/collections/Fields/text/components/server/Field#CustomTextFieldServer':
|
||||
CustomTextFieldServer_39,
|
||||
'@/collections/Fields/text/components/client/Label#CustomTextFieldLabelClient':
|
||||
CustomTextFieldLabelClient_40,
|
||||
'@/collections/Fields/text/components/client/Field#CustomTextFieldClient':
|
||||
CustomTextFieldClient_41,
|
||||
'@/collections/Fields/textarea/components/server/Label#CustomTextareaFieldLabelServer':
|
||||
CustomTextareaFieldLabelServer_42,
|
||||
'@/collections/Fields/textarea/components/server/Field#CustomTextareaFieldServer':
|
||||
CustomTextareaFieldServer_43,
|
||||
'@/collections/Fields/textarea/components/client/Label#CustomTextareaFieldLabelClient':
|
||||
CustomTextareaFieldLabelClient_44,
|
||||
'@/collections/Fields/textarea/components/client/Field#CustomTextareaFieldClient':
|
||||
CustomTextareaFieldClient_45,
|
||||
'@/collections/Views/components/CustomTabEditView#CustomTabEditView': CustomTabEditView_46,
|
||||
'@/collections/Views/components/CustomDefaultEditView#CustomDefaultEditView':
|
||||
CustomDefaultEditView_47,
|
||||
'@/collections/RootViews/components/CustomRootEditView#CustomRootEditView': CustomRootEditView_48,
|
||||
'@/components/afterNavLinks/LinkToCustomView#LinkToCustomView': LinkToCustomView_49,
|
||||
'@/components/afterNavLinks/LinkToCustomMinimalView#LinkToCustomMinimalView':
|
||||
LinkToCustomMinimalView_50,
|
||||
'@/components/afterNavLinks/LinkToCustomDefaultView#LinkToCustomDefaultView':
|
||||
LinkToCustomDefaultView_51,
|
||||
'@/components/views/CustomRootView#CustomRootView': CustomRootView_52,
|
||||
'@/components/views/CustomDefaultRootView#CustomDefaultRootView': CustomDefaultRootView_53,
|
||||
'@/components/views/CustomMinimalRootView#CustomMinimalRootView': CustomMinimalRootView_54,
|
||||
}
|
||||
|
||||
@@ -261,21 +261,34 @@ export function buildObjectType({
|
||||
let type
|
||||
let relationToType = null
|
||||
|
||||
const graphQLCollections = config.collections.filter(
|
||||
(collectionConfig) => collectionConfig.graphQL !== false,
|
||||
)
|
||||
|
||||
if (Array.isArray(relationTo)) {
|
||||
relationToType = new GraphQLEnumType({
|
||||
name: `${relationshipName}_RelationTo`,
|
||||
values: relationTo.reduce(
|
||||
(relations, relation) => ({
|
||||
...relations,
|
||||
[formatName(relation)]: {
|
||||
value: relation,
|
||||
},
|
||||
}),
|
||||
{},
|
||||
),
|
||||
values: relationTo
|
||||
.filter((relation) =>
|
||||
graphQLCollections.some((collection) => collection.slug === relation),
|
||||
)
|
||||
.reduce(
|
||||
(relations, relation) => ({
|
||||
...relations,
|
||||
[formatName(relation)]: {
|
||||
value: relation,
|
||||
},
|
||||
}),
|
||||
{},
|
||||
),
|
||||
})
|
||||
|
||||
const types = relationTo.map((relation) => graphqlResult.collections[relation].graphQL.type)
|
||||
// Only pass collections that are GraphQL enabled
|
||||
const types = relationTo
|
||||
.filter((relation) =>
|
||||
graphQLCollections.some((collection) => collection.slug === relation),
|
||||
)
|
||||
.map((relation) => graphqlResult.collections[relation]?.graphQL.type)
|
||||
|
||||
type = new GraphQLObjectType({
|
||||
name: `${relationshipName}_Relationship`,
|
||||
@@ -314,9 +327,9 @@ export function buildObjectType({
|
||||
where?: unknown
|
||||
} = {}
|
||||
|
||||
const relationsUseDrafts = (Array.isArray(relationTo) ? relationTo : [relationTo]).some(
|
||||
(relation) => graphqlResult.collections[relation].config.versions?.drafts,
|
||||
)
|
||||
const relationsUseDrafts = (Array.isArray(relationTo) ? relationTo : [relationTo])
|
||||
.filter((relation) => graphQLCollections.some((collection) => collection.slug === relation))
|
||||
.some((relation) => graphqlResult.collections[relation].config.versions?.drafts)
|
||||
|
||||
if (relationsUseDrafts) {
|
||||
relationshipArgs.draft = {
|
||||
@@ -357,37 +370,39 @@ export function buildObjectType({
|
||||
let id = relatedDoc
|
||||
let collectionSlug = field.relationTo
|
||||
|
||||
if (isRelatedToManyCollections) {
|
||||
collectionSlug = relatedDoc.relationTo
|
||||
id = relatedDoc.value
|
||||
}
|
||||
|
||||
const result = await context.req.payloadDataLoader.load(
|
||||
createDataloaderCacheKey({
|
||||
collectionSlug: collectionSlug as string,
|
||||
currentDepth: 0,
|
||||
depth: 0,
|
||||
docID: id,
|
||||
draft,
|
||||
fallbackLocale,
|
||||
locale,
|
||||
overrideAccess: false,
|
||||
showHiddenFields: false,
|
||||
transactionID: context.req.transactionID,
|
||||
}),
|
||||
)
|
||||
|
||||
if (result) {
|
||||
if (graphQLCollections.some((collection) => collection.slug === collectionSlug)) {
|
||||
if (isRelatedToManyCollections) {
|
||||
results[i] = {
|
||||
relationTo: collectionSlug,
|
||||
value: {
|
||||
...result,
|
||||
collection: collectionSlug,
|
||||
},
|
||||
collectionSlug = relatedDoc.relationTo
|
||||
id = relatedDoc.value
|
||||
}
|
||||
|
||||
const result = await context.req.payloadDataLoader.load(
|
||||
createDataloaderCacheKey({
|
||||
collectionSlug: collectionSlug as string,
|
||||
currentDepth: 0,
|
||||
depth: 0,
|
||||
docID: id,
|
||||
draft,
|
||||
fallbackLocale,
|
||||
locale,
|
||||
overrideAccess: false,
|
||||
showHiddenFields: false,
|
||||
transactionID: context.req.transactionID,
|
||||
}),
|
||||
)
|
||||
|
||||
if (result) {
|
||||
if (isRelatedToManyCollections) {
|
||||
results[i] = {
|
||||
relationTo: collectionSlug,
|
||||
value: {
|
||||
...result,
|
||||
collection: collectionSlug,
|
||||
},
|
||||
}
|
||||
} else {
|
||||
results[i] = result
|
||||
}
|
||||
} else {
|
||||
results[i] = result
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -409,33 +424,37 @@ export function buildObjectType({
|
||||
}
|
||||
|
||||
if (id) {
|
||||
const relatedDocument = await context.req.payloadDataLoader.load(
|
||||
createDataloaderCacheKey({
|
||||
collectionSlug: relatedCollectionSlug as string,
|
||||
currentDepth: 0,
|
||||
depth: 0,
|
||||
docID: id,
|
||||
draft,
|
||||
fallbackLocale,
|
||||
locale,
|
||||
overrideAccess: false,
|
||||
showHiddenFields: false,
|
||||
transactionID: context.req.transactionID,
|
||||
}),
|
||||
)
|
||||
if (
|
||||
graphQLCollections.some((collection) => collection.slug === relatedCollectionSlug)
|
||||
) {
|
||||
const relatedDocument = await context.req.payloadDataLoader.load(
|
||||
createDataloaderCacheKey({
|
||||
collectionSlug: relatedCollectionSlug as string,
|
||||
currentDepth: 0,
|
||||
depth: 0,
|
||||
docID: id,
|
||||
draft,
|
||||
fallbackLocale,
|
||||
locale,
|
||||
overrideAccess: false,
|
||||
showHiddenFields: false,
|
||||
transactionID: context.req.transactionID,
|
||||
}),
|
||||
)
|
||||
|
||||
if (relatedDocument) {
|
||||
if (isRelatedToManyCollections) {
|
||||
return {
|
||||
relationTo: relatedCollectionSlug,
|
||||
value: {
|
||||
...relatedDocument,
|
||||
collection: relatedCollectionSlug,
|
||||
},
|
||||
if (relatedDocument) {
|
||||
if (isRelatedToManyCollections) {
|
||||
return {
|
||||
relationTo: relatedCollectionSlug,
|
||||
value: {
|
||||
...relatedDocument,
|
||||
collection: relatedCollectionSlug,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return relatedDocument
|
||||
return relatedDocument
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
|
||||
37
packages/next/src/elements/DocumentLocked/index.scss
Normal file
37
packages/next/src/elements/DocumentLocked/index.scss
Normal file
@@ -0,0 +1,37 @@
|
||||
@import '../../scss/styles.scss';
|
||||
|
||||
.document-locked {
|
||||
@include blur-bg;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
|
||||
&__wrapper {
|
||||
z-index: 1;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--base);
|
||||
padding: base(2);
|
||||
}
|
||||
|
||||
&__content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--base);
|
||||
|
||||
> * {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&__controls {
|
||||
display: flex;
|
||||
gap: var(--base);
|
||||
|
||||
.btn {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
93
packages/next/src/elements/DocumentLocked/index.tsx
Normal file
93
packages/next/src/elements/DocumentLocked/index.tsx
Normal file
@@ -0,0 +1,93 @@
|
||||
'use client'
|
||||
import type { ClientUser } from 'payload'
|
||||
|
||||
import { Button, Modal, useModal, useTranslation } from '@payloadcms/ui'
|
||||
import React, { useEffect } from 'react'
|
||||
|
||||
import './index.scss'
|
||||
|
||||
const modalSlug = 'document-locked'
|
||||
|
||||
const baseClass = 'document-locked'
|
||||
|
||||
const formatDate = (date) => {
|
||||
if (!date) {
|
||||
return ''
|
||||
}
|
||||
return new Intl.DateTimeFormat('en-US', {
|
||||
day: 'numeric',
|
||||
hour: 'numeric',
|
||||
hour12: true,
|
||||
minute: 'numeric',
|
||||
month: 'short',
|
||||
year: 'numeric',
|
||||
}).format(date)
|
||||
}
|
||||
|
||||
export const DocumentLocked: React.FC<{
|
||||
editedAt?: null | number
|
||||
handleGoBack: () => void
|
||||
isActive: boolean
|
||||
onReadOnly: () => void
|
||||
onTakeOver: () => void
|
||||
user?: ClientUser
|
||||
}> = ({ editedAt, handleGoBack, isActive, onReadOnly, onTakeOver, user }) => {
|
||||
const { closeModal, openModal } = useModal()
|
||||
const { t } = useTranslation()
|
||||
|
||||
useEffect(() => {
|
||||
if (isActive) {
|
||||
openModal(modalSlug)
|
||||
} else {
|
||||
closeModal(modalSlug)
|
||||
}
|
||||
}, [isActive, openModal, closeModal])
|
||||
|
||||
return (
|
||||
<Modal className={baseClass} onClose={handleGoBack} slug={modalSlug}>
|
||||
<div className={`${baseClass}__wrapper`}>
|
||||
<div className={`${baseClass}__content`}>
|
||||
<h1>{t('general:documentLocked')}</h1>
|
||||
<p>
|
||||
<strong>{user?.email ?? user?.id}</strong> {t('general:currentlyEditing')}
|
||||
</p>
|
||||
<p>
|
||||
{t('general:editedSince')} <strong>{formatDate(editedAt)}</strong>
|
||||
</p>
|
||||
</div>
|
||||
<div className={`${baseClass}__controls`}>
|
||||
<Button
|
||||
buttonStyle="secondary"
|
||||
id={`${modalSlug}-go-back`}
|
||||
onClick={handleGoBack}
|
||||
size="large"
|
||||
>
|
||||
{t('general:goBack')}
|
||||
</Button>
|
||||
<Button
|
||||
buttonStyle="secondary"
|
||||
id={`${modalSlug}-view-read-only`}
|
||||
onClick={() => {
|
||||
onReadOnly()
|
||||
closeModal(modalSlug)
|
||||
}}
|
||||
size="large"
|
||||
>
|
||||
{t('general:viewReadOnly')}
|
||||
</Button>
|
||||
<Button
|
||||
buttonStyle="primary"
|
||||
id={`${modalSlug}-take-over`}
|
||||
onClick={() => {
|
||||
void onTakeOver()
|
||||
closeModal(modalSlug)
|
||||
}}
|
||||
size="large"
|
||||
>
|
||||
{t('general:takeOver')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
37
packages/next/src/elements/DocumentTakeOver/index.scss
Normal file
37
packages/next/src/elements/DocumentTakeOver/index.scss
Normal file
@@ -0,0 +1,37 @@
|
||||
@import '../../scss/styles.scss';
|
||||
|
||||
.document-take-over {
|
||||
@include blur-bg;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
|
||||
&__wrapper {
|
||||
z-index: 1;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--base);
|
||||
padding: base(2);
|
||||
}
|
||||
|
||||
&__content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--base);
|
||||
|
||||
> * {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&__controls {
|
||||
display: flex;
|
||||
gap: var(--base);
|
||||
|
||||
.btn {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
58
packages/next/src/elements/DocumentTakeOver/index.tsx
Normal file
58
packages/next/src/elements/DocumentTakeOver/index.tsx
Normal file
@@ -0,0 +1,58 @@
|
||||
'use client'
|
||||
import { Button, Modal, useModal, useTranslation } from '@payloadcms/ui'
|
||||
import React, { useEffect } from 'react'
|
||||
|
||||
import './index.scss'
|
||||
|
||||
const modalSlug = 'document-take-over'
|
||||
|
||||
const baseClass = 'document-take-over'
|
||||
|
||||
export const DocumentTakeOver: React.FC<{
|
||||
handleBackToDashboard: () => void
|
||||
isActive: boolean
|
||||
onReadOnly: () => void
|
||||
}> = ({ handleBackToDashboard, isActive, onReadOnly }) => {
|
||||
const { closeModal, openModal } = useModal()
|
||||
const { t } = useTranslation()
|
||||
|
||||
useEffect(() => {
|
||||
if (isActive) {
|
||||
openModal(modalSlug)
|
||||
} else {
|
||||
closeModal(modalSlug)
|
||||
}
|
||||
}, [isActive, openModal, closeModal])
|
||||
|
||||
return (
|
||||
<Modal className={baseClass} slug={modalSlug}>
|
||||
<div className={`${baseClass}__wrapper`}>
|
||||
<div className={`${baseClass}__content`}>
|
||||
<h1>{t('general:editingTakenOver')}</h1>
|
||||
<p>{t('general:anotherUserTakenOver')}</p>
|
||||
</div>
|
||||
<div className={`${baseClass}__controls`}>
|
||||
<Button
|
||||
buttonStyle="primary"
|
||||
id={`${modalSlug}-back-to-dashboard`}
|
||||
onClick={handleBackToDashboard}
|
||||
size="large"
|
||||
>
|
||||
{t('general:backToDashboard')}
|
||||
</Button>
|
||||
<Button
|
||||
buttonStyle="secondary"
|
||||
id={`${modalSlug}-view-read-only`}
|
||||
onClick={() => {
|
||||
onReadOnly()
|
||||
closeModal(modalSlug)
|
||||
}}
|
||||
size="large"
|
||||
>
|
||||
{t('general:viewReadOnly')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
@@ -34,8 +34,8 @@ export const CreateFirstUserClient: React.FC<{
|
||||
const collectionConfig = getEntityConfig({ collectionSlug: userSlug }) as ClientCollectionConfig
|
||||
|
||||
const onChange: FormProps['onChange'][0] = React.useCallback(
|
||||
async ({ formState: prevFormState }) =>
|
||||
getFormState({
|
||||
async ({ formState: prevFormState }) => {
|
||||
const { state } = await getFormState({
|
||||
apiRoute,
|
||||
body: {
|
||||
collectionSlug: userSlug,
|
||||
@@ -44,7 +44,9 @@ export const CreateFirstUserClient: React.FC<{
|
||||
schemaPath: `_${userSlug}.auth`,
|
||||
},
|
||||
serverURL,
|
||||
}),
|
||||
})
|
||||
return state
|
||||
},
|
||||
[apiRoute, userSlug, serverURL],
|
||||
)
|
||||
|
||||
|
||||
@@ -35,6 +35,11 @@
|
||||
}
|
||||
}
|
||||
|
||||
&__locked.locked {
|
||||
align-items: unset;
|
||||
justify-content: unset;
|
||||
}
|
||||
|
||||
@include large-break {
|
||||
--cols: 4;
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import type { groupNavItems } from '@payloadcms/ui/shared'
|
||||
import type { Permissions, ServerProps, VisibleEntities } from 'payload'
|
||||
import type { ClientUser, Permissions, ServerProps, VisibleEntities } from 'payload'
|
||||
|
||||
import { getTranslation } from '@payloadcms/translations'
|
||||
import { Button, Card, Gutter, SetStepNav, SetViewActions } from '@payloadcms/ui'
|
||||
import { Button, Card, Gutter, Locked, SetStepNav, SetViewActions } from '@payloadcms/ui'
|
||||
import {
|
||||
EntityType,
|
||||
formatAdminURL,
|
||||
@@ -16,6 +16,7 @@ import './index.scss'
|
||||
const baseClass = 'dashboard'
|
||||
|
||||
export type DashboardProps = {
|
||||
globalData: Array<{ data: { isLocked: boolean; userEditing: ClientUser | null }; slug: string }>
|
||||
Link: React.ComponentType<any>
|
||||
navGroups?: ReturnType<typeof groupNavItems>
|
||||
permissions: Permissions
|
||||
@@ -24,6 +25,7 @@ export type DashboardProps = {
|
||||
|
||||
export const DefaultDashboard: React.FC<DashboardProps> = (props) => {
|
||||
const {
|
||||
globalData,
|
||||
i18n,
|
||||
i18n: { t },
|
||||
Link,
|
||||
@@ -93,6 +95,8 @@ export const DefaultDashboard: React.FC<DashboardProps> = (props) => {
|
||||
let createHREF: string
|
||||
let href: string
|
||||
let hasCreatePermission: boolean
|
||||
let lockStatus = null
|
||||
let userEditing = null
|
||||
|
||||
if (type === EntityType.collection) {
|
||||
title = getTranslation(entity.labels.plural, i18n)
|
||||
@@ -121,13 +125,24 @@ export const DefaultDashboard: React.FC<DashboardProps> = (props) => {
|
||||
adminRoute,
|
||||
path: `/globals/${entity.slug}`,
|
||||
})
|
||||
|
||||
// Find the lock status for the global
|
||||
const globalLockData = globalData.find(
|
||||
(global) => global.slug === entity.slug,
|
||||
)
|
||||
if (globalLockData) {
|
||||
lockStatus = globalLockData.data.isLocked
|
||||
userEditing = globalLockData.data.userEditing
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<li key={entityIndex}>
|
||||
<Card
|
||||
actions={
|
||||
hasCreatePermission && type === EntityType.collection ? (
|
||||
lockStatus ? (
|
||||
<Locked className={`${baseClass}__locked`} user={userEditing} />
|
||||
) : hasCreatePermission && type === EntityType.collection ? (
|
||||
<Button
|
||||
aria-label={t('general:createNewLabel', {
|
||||
label: getTranslation(entity.labels.singular, i18n),
|
||||
|
||||
@@ -17,7 +17,11 @@ export { generateDashboardMetadata } from './meta.js'
|
||||
|
||||
const Link = (LinkImport.default || LinkImport) as unknown as typeof LinkImport.default
|
||||
|
||||
export const Dashboard: React.FC<AdminViewProps> = ({ initPageResult, params, searchParams }) => {
|
||||
export const Dashboard: React.FC<AdminViewProps> = async ({
|
||||
initPageResult,
|
||||
params,
|
||||
searchParams,
|
||||
}) => {
|
||||
const {
|
||||
locale,
|
||||
permissions,
|
||||
@@ -44,6 +48,29 @@ export const Dashboard: React.FC<AdminViewProps> = ({ initPageResult, params, se
|
||||
visibleEntities.globals.includes(global.slug),
|
||||
)
|
||||
|
||||
const globalSlugs = config.globals.map((global) => global.slug)
|
||||
|
||||
// Filter the slugs based on permissions and visibility
|
||||
const filteredGlobalSlugs = globalSlugs.filter(
|
||||
(slug) =>
|
||||
permissions?.globals?.[slug]?.read?.permission && visibleEntities.globals.includes(slug),
|
||||
)
|
||||
|
||||
const globalData = await Promise.all(
|
||||
filteredGlobalSlugs.map(async (slug) => {
|
||||
const data = await payload.findGlobal({
|
||||
slug,
|
||||
depth: 0,
|
||||
includeLockStatus: true,
|
||||
})
|
||||
|
||||
return {
|
||||
slug,
|
||||
data,
|
||||
}
|
||||
}),
|
||||
)
|
||||
|
||||
const navGroups = groupNavItems(
|
||||
[
|
||||
...(collections.map((collection) => {
|
||||
@@ -70,6 +97,7 @@ export const Dashboard: React.FC<AdminViewProps> = ({ initPageResult, params, se
|
||||
const createMappedComponent = getCreateMappedComponent({
|
||||
importMap: payload.importMap,
|
||||
serverProps: {
|
||||
globalData,
|
||||
i18n,
|
||||
Link,
|
||||
locale,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import type {
|
||||
Data,
|
||||
FormState,
|
||||
Locale,
|
||||
PayloadRequest,
|
||||
SanitizedCollectionConfig,
|
||||
@@ -16,13 +17,16 @@ export const getDocumentData = async (args: {
|
||||
locale: Locale
|
||||
req: PayloadRequest
|
||||
schemaPath?: string
|
||||
}): Promise<Data> => {
|
||||
}): Promise<{
|
||||
data: Data
|
||||
formState: FormState
|
||||
}> => {
|
||||
const { id, collectionConfig, globalConfig, locale, req, schemaPath: schemaPathFromProps } = args
|
||||
|
||||
const schemaPath = schemaPathFromProps || collectionConfig?.slug || globalConfig?.slug
|
||||
|
||||
try {
|
||||
const formState = await buildFormState({
|
||||
const { state: formState } = await buildFormState({
|
||||
req: {
|
||||
...req,
|
||||
data: {
|
||||
@@ -44,6 +48,15 @@ export const getDocumentData = async (args: {
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error getting document data', error) // eslint-disable-line no-console
|
||||
return {}
|
||||
return {
|
||||
data: {},
|
||||
formState: {
|
||||
fields: {
|
||||
initialValue: undefined,
|
||||
valid: false,
|
||||
value: undefined,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use client'
|
||||
|
||||
import type { ClientCollectionConfig, ClientGlobalConfig } from 'payload'
|
||||
import type { ClientCollectionConfig, ClientGlobalConfig, ClientUser } from 'payload'
|
||||
|
||||
import {
|
||||
DocumentControls,
|
||||
@@ -19,8 +19,10 @@ import {
|
||||
} from '@payloadcms/ui'
|
||||
import { formatAdminURL, getFormState } from '@payloadcms/ui/shared'
|
||||
import { useRouter, useSearchParams } from 'next/navigation.js'
|
||||
import React, { Fragment, useCallback, useState } from 'react'
|
||||
import React, { Fragment, useCallback, useEffect, useRef, useState } from 'react'
|
||||
|
||||
import { DocumentLocked } from '../../../elements/DocumentLocked/index.js'
|
||||
import { DocumentTakeOver } from '../../../elements/DocumentTakeOver/index.js'
|
||||
import { LeaveWithoutSaving } from '../../../elements/LeaveWithoutSaving/index.js'
|
||||
import { Auth } from './Auth/index.js'
|
||||
import './index.scss'
|
||||
@@ -42,10 +44,12 @@ export const DefaultEditView: React.FC = () => {
|
||||
BeforeDocument,
|
||||
BeforeFields,
|
||||
collectionSlug,
|
||||
currentEditor,
|
||||
disableActions,
|
||||
disableCreate,
|
||||
disableLeaveWithoutSaving,
|
||||
docPermissions,
|
||||
documentIsLocked,
|
||||
getDocPreferences,
|
||||
getVersions,
|
||||
globalSlug,
|
||||
@@ -61,10 +65,13 @@ export const DefaultEditView: React.FC = () => {
|
||||
onSave: onSaveFromContext,
|
||||
redirectAfterDelete,
|
||||
redirectAfterDuplicate,
|
||||
setCurrentEditor,
|
||||
setDocumentIsLocked,
|
||||
unlockDocument,
|
||||
updateDocumentEditor,
|
||||
} = useDocumentInfo()
|
||||
|
||||
const { refreshCookieAsync, user } = useAuth()
|
||||
|
||||
const {
|
||||
config,
|
||||
config: {
|
||||
@@ -94,12 +101,33 @@ export const DefaultEditView: React.FC = () => {
|
||||
const auth = collectionConfig ? collectionConfig.auth : undefined
|
||||
const upload = collectionConfig ? collectionConfig.upload : undefined
|
||||
|
||||
const docConfig = collectionConfig || globalConfig
|
||||
|
||||
const lockDocumentsProp = docConfig?.lockDocuments !== undefined ? docConfig?.lockDocuments : true
|
||||
|
||||
const isLockingEnabled = lockDocumentsProp !== false
|
||||
|
||||
const preventLeaveWithoutSaving =
|
||||
(!(collectionConfig?.versions?.drafts && collectionConfig?.versions?.drafts?.autosave) ||
|
||||
!(globalConfig?.versions?.drafts && globalConfig?.versions?.drafts?.autosave)) &&
|
||||
!disableLeaveWithoutSaving
|
||||
|
||||
const classes = [baseClass, id && `${baseClass}--is-editing`]
|
||||
const [isReadOnlyForIncomingUser, setIsReadOnlyForIncomingUser] = useState(false)
|
||||
const [showTakeOverModal, setShowTakeOverModal] = useState(false)
|
||||
|
||||
const documentLockStateRef = useRef<{
|
||||
hasShownLockedModal: boolean
|
||||
isLocked: boolean
|
||||
user: ClientUser
|
||||
} | null>({
|
||||
hasShownLockedModal: false,
|
||||
isLocked: false,
|
||||
user: null,
|
||||
})
|
||||
|
||||
const [lastUpdateTime, setLastUpdateTime] = useState(Date.now())
|
||||
|
||||
const classes = [baseClass, (id || globalSlug) && `${baseClass}--is-editing`]
|
||||
|
||||
if (globalSlug) {
|
||||
classes.push(`global-edit--${globalSlug}`)
|
||||
@@ -108,7 +136,7 @@ export const DefaultEditView: React.FC = () => {
|
||||
classes.push(`collection-edit--${collectionSlug}`)
|
||||
}
|
||||
|
||||
const [schemaPath, setSchemaPath] = React.useState(() => {
|
||||
const [schemaPath, setSchemaPath] = useState(() => {
|
||||
if (operation === 'create' && auth && !auth.disableLocalStrategy) {
|
||||
return `_${entitySlug}.auth`
|
||||
}
|
||||
@@ -123,6 +151,89 @@ export const DefaultEditView: React.FC = () => {
|
||||
return false
|
||||
})
|
||||
|
||||
const handleTakeOver = useCallback(() => {
|
||||
if (!isLockingEnabled) {
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
// Call updateDocumentEditor to update the document's owner to the current user
|
||||
void updateDocumentEditor(id, collectionSlug ?? globalSlug, user)
|
||||
|
||||
documentLockStateRef.current.hasShownLockedModal = true
|
||||
|
||||
// Update the locked state to reflect the current user as the owner
|
||||
documentLockStateRef.current = {
|
||||
hasShownLockedModal: documentLockStateRef.current?.hasShownLockedModal,
|
||||
isLocked: true,
|
||||
user,
|
||||
}
|
||||
setCurrentEditor(user)
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error('Error during document takeover:', error)
|
||||
}
|
||||
}, [
|
||||
updateDocumentEditor,
|
||||
id,
|
||||
collectionSlug,
|
||||
globalSlug,
|
||||
user,
|
||||
setCurrentEditor,
|
||||
isLockingEnabled,
|
||||
])
|
||||
|
||||
const handleTakeOverWithinDoc = useCallback(() => {
|
||||
if (!isLockingEnabled) {
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
// Call updateDocumentEditor to update the document's owner to the current user
|
||||
void updateDocumentEditor(id, collectionSlug ?? globalSlug, user)
|
||||
|
||||
// Update the locked state to reflect the current user as the owner
|
||||
documentLockStateRef.current = {
|
||||
hasShownLockedModal: documentLockStateRef.current?.hasShownLockedModal,
|
||||
isLocked: true,
|
||||
user,
|
||||
}
|
||||
setCurrentEditor(user)
|
||||
|
||||
// Ensure the document is editable for the incoming user
|
||||
setIsReadOnlyForIncomingUser(false)
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error('Error during document takeover:', error)
|
||||
}
|
||||
}, [
|
||||
updateDocumentEditor,
|
||||
id,
|
||||
collectionSlug,
|
||||
globalSlug,
|
||||
user,
|
||||
setCurrentEditor,
|
||||
isLockingEnabled,
|
||||
])
|
||||
|
||||
const handleGoBack = useCallback(() => {
|
||||
const redirectRoute = formatAdminURL({
|
||||
adminRoute,
|
||||
path: collectionSlug ? `/collections/${collectionSlug}` : '/',
|
||||
})
|
||||
router.push(redirectRoute)
|
||||
}, [adminRoute, collectionSlug, router])
|
||||
|
||||
const handleBackToDashboard = useCallback(() => {
|
||||
setShowTakeOverModal(false)
|
||||
const redirectRoute = formatAdminURL({
|
||||
adminRoute,
|
||||
path: '/',
|
||||
})
|
||||
|
||||
router.push(redirectRoute)
|
||||
}, [adminRoute, router])
|
||||
|
||||
const onSave = useCallback(
|
||||
(json) => {
|
||||
reportUpdate({
|
||||
@@ -146,6 +257,11 @@ export const DefaultEditView: React.FC = () => {
|
||||
})
|
||||
}
|
||||
|
||||
// Unlock the document after save
|
||||
if ((id || globalSlug) && isLockingEnabled) {
|
||||
setDocumentIsLocked(false)
|
||||
}
|
||||
|
||||
if (!isEditing && depth < 2) {
|
||||
// Redirect to the same locale if it's been set
|
||||
const redirectRoute = formatAdminURL({
|
||||
@@ -173,13 +289,26 @@ export const DefaultEditView: React.FC = () => {
|
||||
router,
|
||||
locale,
|
||||
resetUploadEdits,
|
||||
globalSlug,
|
||||
isLockingEnabled,
|
||||
setDocumentIsLocked,
|
||||
],
|
||||
)
|
||||
|
||||
const onChange: FormProps['onChange'][0] = useCallback(
|
||||
async ({ formState: prevFormState }) => {
|
||||
const currentTime = Date.now()
|
||||
const timeSinceLastUpdate = currentTime - lastUpdateTime
|
||||
|
||||
const updateLastEdited = isLockingEnabled && timeSinceLastUpdate >= 10000 // 10 seconds
|
||||
|
||||
if (updateLastEdited) {
|
||||
setLastUpdateTime(currentTime)
|
||||
}
|
||||
|
||||
const docPreferences = await getDocPreferences()
|
||||
return getFormState({
|
||||
|
||||
const { lockedState, state } = await getFormState({
|
||||
apiRoute,
|
||||
body: {
|
||||
id,
|
||||
@@ -188,21 +317,100 @@ export const DefaultEditView: React.FC = () => {
|
||||
formState: prevFormState,
|
||||
globalSlug,
|
||||
operation,
|
||||
returnLockStatus: isLockingEnabled ? true : false,
|
||||
schemaPath,
|
||||
updateLastEdited,
|
||||
},
|
||||
serverURL,
|
||||
})
|
||||
|
||||
setDocumentIsLocked(true)
|
||||
|
||||
if (isLockingEnabled) {
|
||||
const previousOwnerId = documentLockStateRef.current?.user?.id
|
||||
|
||||
if (lockedState) {
|
||||
if (!documentLockStateRef.current || lockedState.user.id !== previousOwnerId) {
|
||||
if (previousOwnerId === user.id && lockedState.user.id !== user.id) {
|
||||
setShowTakeOverModal(true)
|
||||
documentLockStateRef.current.hasShownLockedModal = true
|
||||
}
|
||||
|
||||
documentLockStateRef.current = documentLockStateRef.current = {
|
||||
hasShownLockedModal: documentLockStateRef.current?.hasShownLockedModal || false,
|
||||
isLocked: true,
|
||||
user: lockedState.user,
|
||||
}
|
||||
setCurrentEditor(lockedState.user)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return state
|
||||
},
|
||||
[apiRoute, collectionSlug, schemaPath, getDocPreferences, globalSlug, id, operation, serverURL],
|
||||
[
|
||||
apiRoute,
|
||||
collectionSlug,
|
||||
schemaPath,
|
||||
getDocPreferences,
|
||||
globalSlug,
|
||||
id,
|
||||
operation,
|
||||
serverURL,
|
||||
user,
|
||||
documentLockStateRef,
|
||||
setCurrentEditor,
|
||||
isLockingEnabled,
|
||||
setDocumentIsLocked,
|
||||
lastUpdateTime,
|
||||
],
|
||||
)
|
||||
|
||||
// Clean up when the component unmounts or when the document is unlocked
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
if (!isLockingEnabled) {
|
||||
return
|
||||
}
|
||||
|
||||
if ((id || globalSlug) && documentIsLocked) {
|
||||
// Check if this user is still the current editor
|
||||
if (documentLockStateRef.current?.user?.id === user.id) {
|
||||
void unlockDocument(id, collectionSlug ?? globalSlug)
|
||||
setDocumentIsLocked(false)
|
||||
setCurrentEditor(null)
|
||||
}
|
||||
}
|
||||
|
||||
setShowTakeOverModal(false)
|
||||
}
|
||||
}, [
|
||||
collectionSlug,
|
||||
globalSlug,
|
||||
id,
|
||||
unlockDocument,
|
||||
user.id,
|
||||
setCurrentEditor,
|
||||
isLockingEnabled,
|
||||
documentIsLocked,
|
||||
setDocumentIsLocked,
|
||||
])
|
||||
|
||||
const shouldShowDocumentLockedModal =
|
||||
documentIsLocked &&
|
||||
currentEditor &&
|
||||
currentEditor.id !== user.id &&
|
||||
!isReadOnlyForIncomingUser &&
|
||||
!showTakeOverModal &&
|
||||
!documentLockStateRef.current?.hasShownLockedModal
|
||||
|
||||
return (
|
||||
<main className={classes.filter(Boolean).join(' ')}>
|
||||
<OperationProvider operation={operation}>
|
||||
<Form
|
||||
action={action}
|
||||
className={`${baseClass}__form`}
|
||||
disabled={isInitializing || !hasSavePermission}
|
||||
disabled={isReadOnlyForIncomingUser || isInitializing || !hasSavePermission}
|
||||
disableValidationOnSubmit={!validateBeforeSubmit}
|
||||
initialState={!isInitializing && initialState}
|
||||
isInitializing={isInitializing}
|
||||
@@ -211,7 +419,30 @@ export const DefaultEditView: React.FC = () => {
|
||||
onSuccess={onSave}
|
||||
>
|
||||
{BeforeDocument}
|
||||
{preventLeaveWithoutSaving && <LeaveWithoutSaving />}
|
||||
{isLockingEnabled && shouldShowDocumentLockedModal && !isReadOnlyForIncomingUser && (
|
||||
<DocumentLocked
|
||||
editedAt={lastUpdateTime}
|
||||
handleGoBack={handleGoBack}
|
||||
isActive={shouldShowDocumentLockedModal}
|
||||
onReadOnly={() => {
|
||||
setIsReadOnlyForIncomingUser(true)
|
||||
setShowTakeOverModal(false)
|
||||
}}
|
||||
onTakeOver={handleTakeOver}
|
||||
user={currentEditor}
|
||||
/>
|
||||
)}
|
||||
{isLockingEnabled && showTakeOverModal && (
|
||||
<DocumentTakeOver
|
||||
handleBackToDashboard={handleBackToDashboard}
|
||||
isActive={showTakeOverModal}
|
||||
onReadOnly={() => {
|
||||
setIsReadOnlyForIncomingUser(true)
|
||||
setShowTakeOverModal(false)
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{!isReadOnlyForIncomingUser && preventLeaveWithoutSaving && <LeaveWithoutSaving />}
|
||||
<SetDocumentStepNav
|
||||
collectionSlug={collectionConfig?.slug}
|
||||
globalSlug={globalConfig?.slug}
|
||||
@@ -238,10 +469,13 @@ export const DefaultEditView: React.FC = () => {
|
||||
onDrawerCreate={onDrawerCreate}
|
||||
onDuplicate={onDuplicate}
|
||||
onSave={onSave}
|
||||
onTakeOver={handleTakeOverWithinDoc}
|
||||
permissions={docPermissions}
|
||||
readOnlyForIncomingUser={isReadOnlyForIncomingUser}
|
||||
redirectAfterDelete={redirectAfterDelete}
|
||||
redirectAfterDuplicate={redirectAfterDuplicate}
|
||||
slug={collectionConfig?.slug || globalConfig?.slug}
|
||||
user={currentEditor}
|
||||
/>
|
||||
<DocumentFields
|
||||
AfterFields={AfterFields}
|
||||
@@ -285,7 +519,7 @@ export const DefaultEditView: React.FC = () => {
|
||||
}
|
||||
docPermissions={docPermissions}
|
||||
fields={(collectionConfig || globalConfig)?.fields}
|
||||
readOnly={!hasSavePermission}
|
||||
readOnly={isReadOnlyForIncomingUser || !hasSavePermission}
|
||||
schemaPath={schemaPath}
|
||||
/>
|
||||
{AfterDocument}
|
||||
|
||||
@@ -45,6 +45,7 @@
|
||||
|
||||
#heading-_select,
|
||||
.cell-_select {
|
||||
display: flex;
|
||||
min-width: unset;
|
||||
width: base(1);
|
||||
}
|
||||
|
||||
@@ -111,6 +111,7 @@ export const ListView: React.FC<AdminViewProps> = async ({
|
||||
depth: 0,
|
||||
draft: true,
|
||||
fallbackLocale: null,
|
||||
includeLockStatus: true,
|
||||
limit,
|
||||
locale,
|
||||
overrideAccess: false,
|
||||
|
||||
@@ -117,7 +117,7 @@ const PreviewView: React.FC<Props> = ({
|
||||
async ({ formState: prevFormState }) => {
|
||||
const docPreferences = await getDocPreferences()
|
||||
|
||||
return getFormState({
|
||||
const { state } = await getFormState({
|
||||
apiRoute,
|
||||
body: {
|
||||
id,
|
||||
@@ -128,6 +128,8 @@ const PreviewView: React.FC<Props> = ({
|
||||
},
|
||||
serverURL,
|
||||
})
|
||||
|
||||
return state
|
||||
},
|
||||
[serverURL, apiRoute, id, operation, schemaPath, getDocPreferences],
|
||||
)
|
||||
|
||||
@@ -437,6 +437,15 @@ export type CollectionConfig<TSlug extends CollectionSlug = any> = {
|
||||
plural?: LabelFunction | StaticLabel
|
||||
singular?: LabelFunction | StaticLabel
|
||||
}
|
||||
/**
|
||||
* Enables / Disables the ability to lock documents while editing
|
||||
* @default true
|
||||
*/
|
||||
lockDocuments?:
|
||||
| {
|
||||
duration: number
|
||||
}
|
||||
| false
|
||||
slug: string
|
||||
/**
|
||||
* Add `createdAt` and `updatedAt` fields
|
||||
@@ -509,6 +518,7 @@ export type AuthCollection = {
|
||||
}
|
||||
|
||||
export type TypeWithID = {
|
||||
docId?: any
|
||||
id: number | string
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ import { APIError } from '../../errors/index.js'
|
||||
import { afterRead } from '../../fields/hooks/afterRead/index.js'
|
||||
import { deleteUserPreferences } from '../../preferences/deleteUserPreferences.js'
|
||||
import { deleteAssociatedFiles } from '../../uploads/deleteAssociatedFiles.js'
|
||||
import { checkDocumentLockStatus } from '../../utilities/checkDocumentLockStatus.js'
|
||||
import { commitTransaction } from '../../utilities/commitTransaction.js'
|
||||
import { initTransaction } from '../../utilities/initTransaction.js'
|
||||
import { killTransaction } from '../../utilities/killTransaction.js'
|
||||
@@ -117,6 +118,17 @@ export const deleteOperation = async <TSlug extends CollectionSlug>(
|
||||
const { id } = doc
|
||||
|
||||
try {
|
||||
// /////////////////////////////////////
|
||||
// Handle potentially locked documents
|
||||
// /////////////////////////////////////
|
||||
|
||||
const { lockedDocument, shouldUnlockDocument } = await checkDocumentLockStatus({
|
||||
id,
|
||||
collectionSlug: collectionConfig.slug,
|
||||
lockErrorMessage: `Document with ID ${id} is currently locked and cannot be deleted.`,
|
||||
req,
|
||||
})
|
||||
|
||||
// /////////////////////////////////////
|
||||
// beforeDelete - Collection
|
||||
// /////////////////////////////////////
|
||||
@@ -140,6 +152,20 @@ export const deleteOperation = async <TSlug extends CollectionSlug>(
|
||||
req,
|
||||
})
|
||||
|
||||
// /////////////////////////////////////
|
||||
// Unlock the document if necessary
|
||||
// /////////////////////////////////////
|
||||
|
||||
if (shouldUnlockDocument && lockedDocument) {
|
||||
await payload.db.deleteOne({
|
||||
collection: 'payload-locked-documents',
|
||||
req,
|
||||
where: {
|
||||
id: { equals: lockedDocument.id },
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// /////////////////////////////////////
|
||||
// Delete versions
|
||||
// /////////////////////////////////////
|
||||
|
||||
@@ -9,6 +9,7 @@ import { Forbidden, NotFound } from '../../errors/index.js'
|
||||
import { afterRead } from '../../fields/hooks/afterRead/index.js'
|
||||
import { deleteUserPreferences } from '../../preferences/deleteUserPreferences.js'
|
||||
import { deleteAssociatedFiles } from '../../uploads/deleteAssociatedFiles.js'
|
||||
import { checkDocumentLockStatus } from '../../utilities/checkDocumentLockStatus.js'
|
||||
import { commitTransaction } from '../../utilities/commitTransaction.js'
|
||||
import { initTransaction } from '../../utilities/initTransaction.js'
|
||||
import { killTransaction } from '../../utilities/killTransaction.js'
|
||||
@@ -109,6 +110,17 @@ export const deleteByIDOperation = async <TSlug extends CollectionSlug>(
|
||||
throw new Forbidden(req.t)
|
||||
}
|
||||
|
||||
// /////////////////////////////////////
|
||||
// Handle potentially locked documents
|
||||
// /////////////////////////////////////
|
||||
|
||||
const { lockedDocument, shouldUnlockDocument } = await checkDocumentLockStatus({
|
||||
id,
|
||||
collectionSlug: collectionConfig.slug,
|
||||
lockErrorMessage: `Document with ID ${id} is currently locked and cannot be deleted.`,
|
||||
req,
|
||||
})
|
||||
|
||||
await deleteAssociatedFiles({
|
||||
collectionConfig,
|
||||
config,
|
||||
@@ -117,6 +129,20 @@ export const deleteByIDOperation = async <TSlug extends CollectionSlug>(
|
||||
req,
|
||||
})
|
||||
|
||||
// /////////////////////////////////////
|
||||
// Unlock the document if necessary
|
||||
// /////////////////////////////////////
|
||||
|
||||
if (shouldUnlockDocument && lockedDocument) {
|
||||
await payload.db.deleteOne({
|
||||
collection: 'payload-locked-documents',
|
||||
req,
|
||||
where: {
|
||||
id: { equals: lockedDocument.id },
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// /////////////////////////////////////
|
||||
// Delete versions
|
||||
// /////////////////////////////////////
|
||||
|
||||
@@ -20,6 +20,7 @@ export type Arguments = {
|
||||
depth?: number
|
||||
disableErrors?: boolean
|
||||
draft?: boolean
|
||||
includeLockStatus?: boolean
|
||||
limit?: number
|
||||
overrideAccess?: boolean
|
||||
page?: number
|
||||
@@ -60,6 +61,7 @@ export const findOperation = async <TSlug extends CollectionSlug>(
|
||||
depth,
|
||||
disableErrors,
|
||||
draft: draftsEnabled,
|
||||
includeLockStatus,
|
||||
limit,
|
||||
overrideAccess,
|
||||
page,
|
||||
@@ -150,6 +152,49 @@ export const findOperation = async <TSlug extends CollectionSlug>(
|
||||
})
|
||||
}
|
||||
|
||||
if (includeLockStatus) {
|
||||
try {
|
||||
const lockedDocuments = await payload.find({
|
||||
collection: 'payload-locked-documents',
|
||||
depth: 1,
|
||||
limit: sanitizedLimit,
|
||||
pagination: false,
|
||||
req,
|
||||
where: {
|
||||
and: [
|
||||
{
|
||||
'document.relationTo': {
|
||||
equals: collectionConfig.slug,
|
||||
},
|
||||
},
|
||||
{
|
||||
'document.value': {
|
||||
in: result.docs.map((doc) => doc.id),
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
|
||||
const lockedDocs = Array.isArray(lockedDocuments?.docs) ? lockedDocuments.docs : []
|
||||
|
||||
result.docs = result.docs.map((doc) => {
|
||||
const lockedDoc = lockedDocs.find((lock) => lock?.document?.value === doc.id)
|
||||
return {
|
||||
...doc,
|
||||
isLocked: !!lockedDoc,
|
||||
userEditing: lockedDoc ? lockedDoc._lastEdited?.user?.value : null,
|
||||
}
|
||||
})
|
||||
} catch (error) {
|
||||
result.docs = result.docs.map((doc) => ({
|
||||
...doc,
|
||||
isLocked: false,
|
||||
userEditing: null,
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
// /////////////////////////////////////
|
||||
// beforeRead - Collection
|
||||
// /////////////////////////////////////
|
||||
|
||||
@@ -18,6 +18,7 @@ export type Arguments = {
|
||||
disableErrors?: boolean
|
||||
draft?: boolean
|
||||
id: number | string
|
||||
includeLockStatus?: boolean
|
||||
overrideAccess?: boolean
|
||||
req: PayloadRequest
|
||||
showHiddenFields?: boolean
|
||||
@@ -53,6 +54,7 @@ export const findByIDOperation = async <TSlug extends CollectionSlug>(
|
||||
depth,
|
||||
disableErrors,
|
||||
draft: draftEnabled = false,
|
||||
includeLockStatus,
|
||||
overrideAccess = false,
|
||||
req: { fallbackLocale, locale, t },
|
||||
req,
|
||||
@@ -99,6 +101,47 @@ export const findByIDOperation = async <TSlug extends CollectionSlug>(
|
||||
return null
|
||||
}
|
||||
|
||||
// /////////////////////////////////////
|
||||
// Include Lock Status if required
|
||||
// /////////////////////////////////////
|
||||
|
||||
if (includeLockStatus && id) {
|
||||
let lockStatus = null
|
||||
|
||||
try {
|
||||
const lockedDocument = await req.payload.find({
|
||||
collection: 'payload-locked-documents',
|
||||
depth: 1,
|
||||
limit: 1,
|
||||
pagination: false,
|
||||
req,
|
||||
where: {
|
||||
and: [
|
||||
{
|
||||
'document.relationTo': {
|
||||
equals: collectionConfig.slug,
|
||||
},
|
||||
},
|
||||
{
|
||||
'document.value': {
|
||||
equals: id,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
|
||||
if (lockedDocument && lockedDocument.docs.length > 0) {
|
||||
lockStatus = lockedDocument.docs[0]
|
||||
}
|
||||
} catch {
|
||||
// swallow error
|
||||
}
|
||||
|
||||
result.isLocked = !!lockStatus
|
||||
result.userEditing = lockStatus?._lastEdited?.user?.value ?? null
|
||||
}
|
||||
|
||||
// /////////////////////////////////////
|
||||
// Replace document with draft if available
|
||||
// /////////////////////////////////////
|
||||
|
||||
@@ -18,6 +18,7 @@ export type Options<TSlug extends CollectionSlug> = {
|
||||
disableErrors?: boolean
|
||||
draft?: boolean
|
||||
fallbackLocale?: TypedLocale
|
||||
includeLockStatus?: boolean
|
||||
limit?: number
|
||||
locale?: 'all' | TypedLocale
|
||||
overrideAccess?: boolean
|
||||
@@ -40,6 +41,7 @@ export async function findLocal<TSlug extends CollectionSlug>(
|
||||
depth,
|
||||
disableErrors,
|
||||
draft = false,
|
||||
includeLockStatus,
|
||||
limit,
|
||||
overrideAccess = true,
|
||||
page,
|
||||
@@ -63,6 +65,7 @@ export async function findLocal<TSlug extends CollectionSlug>(
|
||||
depth,
|
||||
disableErrors,
|
||||
draft,
|
||||
includeLockStatus,
|
||||
limit,
|
||||
overrideAccess,
|
||||
page,
|
||||
|
||||
@@ -18,6 +18,7 @@ export type Options<TSlug extends CollectionSlug> = {
|
||||
draft?: boolean
|
||||
fallbackLocale?: TypedLocale
|
||||
id: number | string
|
||||
includeLockStatus?: boolean
|
||||
locale?: 'all' | TypedLocale
|
||||
overrideAccess?: boolean
|
||||
req?: PayloadRequest
|
||||
@@ -36,6 +37,7 @@ export default async function findByIDLocal<TSlug extends CollectionSlug>(
|
||||
depth,
|
||||
disableErrors = false,
|
||||
draft = false,
|
||||
includeLockStatus,
|
||||
overrideAccess = true,
|
||||
showHiddenFields,
|
||||
} = options
|
||||
@@ -55,6 +57,7 @@ export default async function findByIDLocal<TSlug extends CollectionSlug>(
|
||||
depth,
|
||||
disableErrors,
|
||||
draft,
|
||||
includeLockStatus,
|
||||
overrideAccess,
|
||||
req: await createLocalReq(options, payload),
|
||||
showHiddenFields,
|
||||
|
||||
@@ -25,6 +25,7 @@ import { deleteAssociatedFiles } from '../../uploads/deleteAssociatedFiles.js'
|
||||
import { generateFileData } from '../../uploads/generateFileData.js'
|
||||
import { unlinkTempFiles } from '../../uploads/unlinkTempFiles.js'
|
||||
import { uploadFiles } from '../../uploads/uploadFiles.js'
|
||||
import { checkDocumentLockStatus } from '../../utilities/checkDocumentLockStatus.js'
|
||||
import { commitTransaction } from '../../utilities/commitTransaction.js'
|
||||
import { initTransaction } from '../../utilities/initTransaction.js'
|
||||
import { killTransaction } from '../../utilities/killTransaction.js'
|
||||
@@ -177,6 +178,17 @@ export const updateOperation = async <TSlug extends CollectionSlug>(
|
||||
}
|
||||
|
||||
try {
|
||||
// /////////////////////////////////////
|
||||
// Handle potentially locked documents
|
||||
// /////////////////////////////////////
|
||||
|
||||
const { lockedDocument, shouldUnlockDocument } = await checkDocumentLockStatus({
|
||||
id,
|
||||
collectionSlug: collectionConfig.slug,
|
||||
lockErrorMessage: `Document with ID ${id} is currently locked by another user and cannot be updated.`,
|
||||
req,
|
||||
})
|
||||
|
||||
const originalDoc = await afterRead({
|
||||
collection: collectionConfig,
|
||||
context: req.context,
|
||||
@@ -323,6 +335,20 @@ export const updateOperation = async <TSlug extends CollectionSlug>(
|
||||
})
|
||||
}
|
||||
|
||||
// /////////////////////////////////////
|
||||
// Unlock the document if necessary
|
||||
// /////////////////////////////////////
|
||||
|
||||
if (shouldUnlockDocument && lockedDocument) {
|
||||
await payload.db.deleteOne({
|
||||
collection: 'payload-locked-documents',
|
||||
req,
|
||||
where: {
|
||||
id: { equals: lockedDocument.id },
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// /////////////////////////////////////
|
||||
// afterRead - Fields
|
||||
// /////////////////////////////////////
|
||||
|
||||
@@ -26,6 +26,7 @@ import { deleteAssociatedFiles } from '../../uploads/deleteAssociatedFiles.js'
|
||||
import { generateFileData } from '../../uploads/generateFileData.js'
|
||||
import { unlinkTempFiles } from '../../uploads/unlinkTempFiles.js'
|
||||
import { uploadFiles } from '../../uploads/uploadFiles.js'
|
||||
import { checkDocumentLockStatus } from '../../utilities/checkDocumentLockStatus.js'
|
||||
import { commitTransaction } from '../../utilities/commitTransaction.js'
|
||||
import { initTransaction } from '../../utilities/initTransaction.js'
|
||||
import { killTransaction } from '../../utilities/killTransaction.js'
|
||||
@@ -141,6 +142,17 @@ export const updateByIDOperation = async <TSlug extends CollectionSlug>(
|
||||
throw new Forbidden(req.t)
|
||||
}
|
||||
|
||||
// /////////////////////////////////////
|
||||
// Handle potentially locked documents
|
||||
// /////////////////////////////////////
|
||||
|
||||
const { lockedDocument, shouldUnlockDocument } = await checkDocumentLockStatus({
|
||||
id,
|
||||
collectionSlug: collectionConfig.slug,
|
||||
lockErrorMessage: `Document with ID ${id} is currently locked by another user and cannot be updated.`,
|
||||
req,
|
||||
})
|
||||
|
||||
const originalDoc = await afterRead({
|
||||
collection: collectionConfig,
|
||||
context: req.context,
|
||||
@@ -353,6 +365,20 @@ export const updateByIDOperation = async <TSlug extends CollectionSlug>(
|
||||
})
|
||||
}
|
||||
|
||||
// /////////////////////////////////////
|
||||
// Unlock the document if necessary
|
||||
// /////////////////////////////////////
|
||||
|
||||
if (shouldUnlockDocument && lockedDocument) {
|
||||
await payload.db.deleteOne({
|
||||
collection: 'payload-locked-documents',
|
||||
req,
|
||||
where: {
|
||||
id: { equals: lockedDocument.id },
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// /////////////////////////////////////
|
||||
// afterRead - Fields
|
||||
// /////////////////////////////////////
|
||||
|
||||
@@ -15,6 +15,7 @@ import { sanitizeCollection } from '../collections/config/sanitize.js'
|
||||
import { migrationsCollection } from '../database/migrations/migrationsCollection.js'
|
||||
import { InvalidConfiguration } from '../errors/index.js'
|
||||
import { sanitizeGlobals } from '../globals/config/sanitize.js'
|
||||
import { getLockedDocumentsCollection } from '../lockedDocuments/lockedDocumentsCollection.js'
|
||||
import getPreferencesCollection from '../preferences/preferencesCollection.js'
|
||||
import checkDuplicateCollections from '../utilities/checkDuplicateCollections.js'
|
||||
import { defaults } from './defaults.js'
|
||||
@@ -146,6 +147,7 @@ export const sanitizeConfig = async (incomingConfig: Config): Promise<SanitizedC
|
||||
|
||||
config.i18n = i18nConfig
|
||||
|
||||
configWithDefaults.collections.push(getLockedDocumentsCollection(config as unknown as Config))
|
||||
configWithDefaults.collections.push(getPreferencesCollection(config as unknown as Config))
|
||||
configWithDefaults.collections.push(migrationsCollection)
|
||||
|
||||
|
||||
@@ -164,6 +164,15 @@ export type GlobalConfig = {
|
||||
beforeValidate?: BeforeValidateHook[]
|
||||
}
|
||||
label?: Record<string, string> | string
|
||||
/**
|
||||
* Enables / Disables the ability to lock documents while editing
|
||||
* @default true
|
||||
*/
|
||||
lockDocuments?:
|
||||
| {
|
||||
duration: number
|
||||
}
|
||||
| false
|
||||
slug: string
|
||||
/**
|
||||
* Options used in typescript generation
|
||||
|
||||
@@ -11,6 +11,7 @@ type Args = {
|
||||
depth?: number
|
||||
draft?: boolean
|
||||
globalConfig: SanitizedGlobalConfig
|
||||
includeLockStatus?: boolean
|
||||
overrideAccess?: boolean
|
||||
req: PayloadRequest
|
||||
showHiddenFields?: boolean
|
||||
@@ -25,6 +26,7 @@ export const findOneOperation = async <T extends Record<string, unknown>>(
|
||||
depth,
|
||||
draft: draftEnabled = false,
|
||||
globalConfig,
|
||||
includeLockStatus,
|
||||
overrideAccess = false,
|
||||
req: { fallbackLocale, locale },
|
||||
req,
|
||||
@@ -56,6 +58,38 @@ export const findOneOperation = async <T extends Record<string, unknown>>(
|
||||
doc = {}
|
||||
}
|
||||
|
||||
// /////////////////////////////////////
|
||||
// Include Lock Status if required
|
||||
// /////////////////////////////////////
|
||||
|
||||
if (includeLockStatus && slug) {
|
||||
let lockStatus = null
|
||||
|
||||
try {
|
||||
const lockedDocument = await req.payload.find({
|
||||
collection: 'payload-locked-documents',
|
||||
depth: 1,
|
||||
limit: 1,
|
||||
pagination: false,
|
||||
req,
|
||||
where: {
|
||||
globalSlug: {
|
||||
equals: slug,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
if (lockedDocument && lockedDocument.docs.length > 0) {
|
||||
lockStatus = lockedDocument.docs[0]
|
||||
}
|
||||
} catch {
|
||||
// swallow error
|
||||
}
|
||||
|
||||
doc.isLocked = !!lockStatus
|
||||
doc.userEditing = lockStatus?._lastEdited?.user?.value ?? null
|
||||
}
|
||||
|
||||
// /////////////////////////////////////
|
||||
// Replace document with draft if available
|
||||
// /////////////////////////////////////
|
||||
|
||||
@@ -11,6 +11,7 @@ export type Options<TSlug extends GlobalSlug> = {
|
||||
depth?: number
|
||||
draft?: boolean
|
||||
fallbackLocale?: TypedLocale
|
||||
includeLockStatus?: boolean
|
||||
locale?: 'all' | TypedLocale
|
||||
overrideAccess?: boolean
|
||||
req?: PayloadRequest
|
||||
@@ -27,6 +28,7 @@ export default async function findOneLocal<TSlug extends GlobalSlug>(
|
||||
slug: globalSlug,
|
||||
depth,
|
||||
draft = false,
|
||||
includeLockStatus,
|
||||
overrideAccess = true,
|
||||
showHiddenFields,
|
||||
} = options
|
||||
@@ -42,6 +44,7 @@ export default async function findOneLocal<TSlug extends GlobalSlug>(
|
||||
depth,
|
||||
draft,
|
||||
globalConfig,
|
||||
includeLockStatus,
|
||||
overrideAccess,
|
||||
req: await createLocalReq(options, payload),
|
||||
showHiddenFields,
|
||||
|
||||
@@ -5,11 +5,13 @@ import type { Operation, PayloadRequest, Where } from '../../types/index.js'
|
||||
import type { DataFromGlobalSlug, SanitizedGlobalConfig } from '../config/types.js'
|
||||
|
||||
import executeAccess from '../../auth/executeAccess.js'
|
||||
import { APIError } from '../../errors/index.js'
|
||||
import { afterChange } from '../../fields/hooks/afterChange/index.js'
|
||||
import { afterRead } from '../../fields/hooks/afterRead/index.js'
|
||||
import { beforeChange } from '../../fields/hooks/beforeChange/index.js'
|
||||
import { beforeValidate } from '../../fields/hooks/beforeValidate/index.js'
|
||||
import { deepCopyObjectSimple } from '../../index.js'
|
||||
import { checkDocumentLockStatus } from '../../utilities/checkDocumentLockStatus.js'
|
||||
import { commitTransaction } from '../../utilities/commitTransaction.js'
|
||||
import { initTransaction } from '../../utilities/initTransaction.js'
|
||||
import { killTransaction } from '../../utilities/killTransaction.js'
|
||||
@@ -113,6 +115,16 @@ export const updateOperation = async <TSlug extends GlobalSlug>(
|
||||
showHiddenFields,
|
||||
})
|
||||
|
||||
// ///////////////////////////////////////////
|
||||
// Handle potentially locked global documents
|
||||
// ///////////////////////////////////////////
|
||||
|
||||
const { lockedDocument, shouldUnlockDocument } = await checkDocumentLockStatus({
|
||||
globalSlug: slug,
|
||||
lockErrorMessage: `Global with slug "${slug}" is currently locked by another user and cannot be updated.`,
|
||||
req,
|
||||
})
|
||||
|
||||
// /////////////////////////////////////
|
||||
// beforeValidate - Fields
|
||||
// /////////////////////////////////////
|
||||
@@ -250,6 +262,22 @@ export const updateOperation = async <TSlug extends GlobalSlug>(
|
||||
}
|
||||
}
|
||||
|
||||
// /////////////////////////////////////
|
||||
// Unlock the global if necessary
|
||||
// /////////////////////////////////////
|
||||
|
||||
if (shouldUnlockDocument && lockedDocument) {
|
||||
await payload.db.deleteOne({
|
||||
collection: 'payload-locked-documents',
|
||||
req,
|
||||
where: {
|
||||
globalSlug: {
|
||||
equals: slug,
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// /////////////////////////////////////
|
||||
// afterRead - Fields
|
||||
// /////////////////////////////////////
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
import type { CollectionConfig } from '../collections/config/types.js'
|
||||
import type { Config } from '../config/types.js'
|
||||
|
||||
export const getLockedDocumentsCollection = (config: Config): CollectionConfig => ({
|
||||
slug: 'payload-locked-documents',
|
||||
admin: {
|
||||
hidden: true,
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'document',
|
||||
type: 'relationship',
|
||||
index: true,
|
||||
maxDepth: 0,
|
||||
relationTo: [...config.collections.map((collectionConfig) => collectionConfig.slug)],
|
||||
},
|
||||
{
|
||||
name: 'globalSlug',
|
||||
type: 'text',
|
||||
},
|
||||
{
|
||||
name: '_lastEdited',
|
||||
type: 'group',
|
||||
fields: [
|
||||
{
|
||||
name: 'user',
|
||||
type: 'relationship',
|
||||
relationTo: config.collections
|
||||
.filter((collectionConfig) => collectionConfig.auth)
|
||||
.map((collectionConfig) => collectionConfig.slug),
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'editedAt',
|
||||
type: 'date',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'isLocked',
|
||||
type: 'checkbox',
|
||||
defaultValue: false,
|
||||
},
|
||||
],
|
||||
})
|
||||
102
packages/payload/src/utilities/checkDocumentLockStatus.ts
Normal file
102
packages/payload/src/utilities/checkDocumentLockStatus.ts
Normal file
@@ -0,0 +1,102 @@
|
||||
import type { TypeWithID } from '../collections/config/types.js'
|
||||
import type { PaginatedDocs } from '../database/types.js'
|
||||
import type { JsonObject, PayloadRequest } from '../types/index.js'
|
||||
|
||||
import { APIError } from '../errors/index.js'
|
||||
|
||||
type CheckDocumentLockStatusArgs = {
|
||||
collectionSlug?: string
|
||||
globalSlug?: string
|
||||
id?: number | string
|
||||
lockDurationDefault?: number
|
||||
lockErrorMessage?: string
|
||||
req: PayloadRequest
|
||||
}
|
||||
|
||||
type CheckDocumentLockResult = {
|
||||
lockedDocument?: JsonObject & TypeWithID
|
||||
shouldUnlockDocument: boolean
|
||||
}
|
||||
|
||||
export const checkDocumentLockStatus = async ({
|
||||
id,
|
||||
collectionSlug,
|
||||
globalSlug,
|
||||
lockDurationDefault = 300, // Default 5 minutes in seconds
|
||||
lockErrorMessage,
|
||||
req,
|
||||
}: CheckDocumentLockStatusArgs): Promise<CheckDocumentLockResult> => {
|
||||
const { payload } = req
|
||||
|
||||
// Retrieve the lockDocuments property for either collection or global
|
||||
const lockDocumentsProp = collectionSlug
|
||||
? payload.config?.collections?.find((c) => c.slug === collectionSlug)?.lockDocuments
|
||||
: payload.config?.globals?.find((g) => g.slug === globalSlug)?.lockDocuments
|
||||
|
||||
const isLockingEnabled = lockDocumentsProp !== false
|
||||
|
||||
// If lockDocuments is explicitly set to false, skip the lock logic and return early
|
||||
if (isLockingEnabled === false) {
|
||||
return { lockedDocument: undefined, shouldUnlockDocument: false }
|
||||
}
|
||||
|
||||
let lockedDocumentQuery = {}
|
||||
|
||||
if (collectionSlug) {
|
||||
lockedDocumentQuery = {
|
||||
and: [
|
||||
{ 'document.relationTo': { equals: collectionSlug } },
|
||||
{ 'document.value': { equals: id } },
|
||||
],
|
||||
}
|
||||
} else if (globalSlug) {
|
||||
lockedDocumentQuery = { globalSlug: { equals: globalSlug } }
|
||||
} else {
|
||||
throw new Error('Either collectionSlug or globalSlug must be provided.')
|
||||
}
|
||||
|
||||
const defaultLockErrorMessage = collectionSlug
|
||||
? `Document with ID ${id} is currently locked by another user and cannot be modified.`
|
||||
: `Global document with slug "${globalSlug}" is currently locked by another user and cannot be modified.`
|
||||
|
||||
const finalLockErrorMessage = lockErrorMessage || defaultLockErrorMessage
|
||||
|
||||
const lockedDocumentResult: PaginatedDocs<JsonObject & TypeWithID> = await payload.find({
|
||||
collection: 'payload-locked-documents',
|
||||
depth: 1,
|
||||
limit: 1,
|
||||
pagination: false,
|
||||
req,
|
||||
where: lockedDocumentQuery,
|
||||
})
|
||||
|
||||
let shouldUnlockDocument = false
|
||||
|
||||
// If there's a locked document, check lock conditions
|
||||
if (lockedDocumentResult.docs.length > 0) {
|
||||
const lockedDoc = lockedDocumentResult.docs[0]
|
||||
const lastEditedAt = new Date(lockedDoc?._lastEdited?.editedAt)
|
||||
const now = new Date()
|
||||
|
||||
const lockDuration =
|
||||
typeof lockDocumentsProp === 'object' ? lockDocumentsProp.duration : lockDurationDefault
|
||||
|
||||
const lockDurationInMilliseconds = lockDuration * 1000
|
||||
const currentUserId = req.user?.id
|
||||
|
||||
// If document is locked by another user and the lock hasn't expired
|
||||
if (lockedDoc._lastEdited?.user?.value?.id !== currentUserId) {
|
||||
if (now.getTime() - lastEditedAt.getTime() <= lockDurationInMilliseconds) {
|
||||
throw new APIError(finalLockErrorMessage)
|
||||
} else {
|
||||
// If lock has expired, allow unlocking
|
||||
shouldUnlockDocument = true
|
||||
}
|
||||
} else {
|
||||
// If document is locked by the current user, allow unlocking
|
||||
shouldUnlockDocument = true
|
||||
}
|
||||
}
|
||||
|
||||
return { lockedDocument: lockedDocumentResult.docs[0], shouldUnlockDocument }
|
||||
}
|
||||
@@ -20,7 +20,7 @@ export type CollectionBeforeValidateHookWithArgs = (
|
||||
collection?: CollectionConfig
|
||||
pluginConfig?: StripePluginConfig
|
||||
} & HookArgsWithCustomCollection,
|
||||
) => void
|
||||
) => Promise<Partial<any>>
|
||||
|
||||
export const createNewInStripe: CollectionBeforeValidateHookWithArgs = async (args) => {
|
||||
const { collection, data, operation, pluginConfig, req } = args
|
||||
|
||||
@@ -18,7 +18,7 @@ export type CollectionAfterDeleteHookWithArgs = (
|
||||
collection?: CollectionConfig
|
||||
pluginConfig?: StripePluginConfig
|
||||
} & HookArgsWithCustomCollection,
|
||||
) => void
|
||||
) => Promise<void>
|
||||
|
||||
export const deleteFromStripe: CollectionAfterDeleteHookWithArgs = async (args) => {
|
||||
const { collection, doc, pluginConfig, req } = args
|
||||
|
||||
@@ -20,7 +20,7 @@ export type CollectionBeforeChangeHookWithArgs = (
|
||||
collection?: CollectionConfig
|
||||
pluginConfig?: StripePluginConfig
|
||||
} & HookArgsWithCustomCollection,
|
||||
) => void
|
||||
) => Promise<Partial<any>>
|
||||
|
||||
export const syncExistingWithStripe: CollectionBeforeChangeHookWithArgs = async (args) => {
|
||||
const { collection, data, operation, originalDoc, pluginConfig, req } = args
|
||||
|
||||
@@ -9,7 +9,7 @@ type HandleCreatedOrUpdated = (
|
||||
resourceType: string
|
||||
syncConfig: SanitizedStripePluginConfig['sync'][0]
|
||||
} & Parameters<StripeWebhookHandler>[0],
|
||||
) => void
|
||||
) => Promise<void>
|
||||
|
||||
export const handleCreatedOrUpdated: HandleCreatedOrUpdated = async (args) => {
|
||||
const { config: payloadConfig, event, payload, pluginConfig, resourceType, syncConfig } = args
|
||||
|
||||
@@ -5,7 +5,7 @@ type HandleDeleted = (
|
||||
resourceType: string
|
||||
syncConfig: SanitizedStripePluginConfig['sync'][0]
|
||||
} & Parameters<StripeWebhookHandler>[0],
|
||||
) => void
|
||||
) => Promise<void>
|
||||
|
||||
export const handleDeleted: HandleDeleted = async (args) => {
|
||||
const { event, payload, pluginConfig, resourceType, syncConfig } = args
|
||||
|
||||
@@ -59,7 +59,7 @@ export const BlockComponent: React.FC<Props> = (props) => {
|
||||
// Field Schema
|
||||
useEffect(() => {
|
||||
const awaitInitialState = async () => {
|
||||
const state = await getFormState({
|
||||
const { state } = await getFormState({
|
||||
apiRoute: config.routes.api,
|
||||
body: {
|
||||
id,
|
||||
@@ -89,7 +89,7 @@ export const BlockComponent: React.FC<Props> = (props) => {
|
||||
|
||||
const onChange: FormProps['onChange'][0] = useCallback(
|
||||
async ({ formState: prevFormState }) => {
|
||||
const formState = await getFormState({
|
||||
const { state: formState } = await getFormState({
|
||||
apiRoute: config.routes.api,
|
||||
body: {
|
||||
id,
|
||||
|
||||
@@ -386,6 +386,7 @@ function $getAncestor(
|
||||
predicate: (ancestor: LexicalNode) => boolean,
|
||||
): LexicalNode | null {
|
||||
let parent: LexicalNode | null = node
|
||||
// eslint-disable-next-line no-empty
|
||||
while (parent !== null && (parent = parent.getParent()) !== null && !predicate(parent)) {}
|
||||
return parent
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ export const DrawerContent: React.FC<Omit<FieldsDrawerProps, 'drawerSlug' | 'dra
|
||||
|
||||
useEffect(() => {
|
||||
const awaitInitialState = async () => {
|
||||
const state = await getFormState({
|
||||
const { state } = await getFormState({
|
||||
apiRoute: config.routes.api,
|
||||
body: {
|
||||
id,
|
||||
@@ -65,7 +65,7 @@ export const DrawerContent: React.FC<Omit<FieldsDrawerProps, 'drawerSlug' | 'dra
|
||||
|
||||
const onChange: FormProps['onChange'][0] = useCallback(
|
||||
async ({ formState: prevFormState }) => {
|
||||
return await getFormState({
|
||||
const { state } = await getFormState({
|
||||
apiRoute: config.routes.api,
|
||||
body: {
|
||||
id,
|
||||
@@ -75,6 +75,8 @@ export const DrawerContent: React.FC<Omit<FieldsDrawerProps, 'drawerSlug' | 'dra
|
||||
},
|
||||
serverURL: config.serverURL,
|
||||
})
|
||||
|
||||
return state
|
||||
},
|
||||
|
||||
[config.routes.api, config.serverURL, schemaFieldsPath, id],
|
||||
|
||||
@@ -90,7 +90,7 @@ export const LinkButton: React.FC = () => {
|
||||
text: editor.selection ? Editor.string(editor, editor.selection) : '',
|
||||
}
|
||||
|
||||
const state = await getFormState({
|
||||
const { state } = await getFormState({
|
||||
apiRoute: config.routes.api,
|
||||
body: {
|
||||
data,
|
||||
|
||||
@@ -100,7 +100,7 @@ export const LinkElement = () => {
|
||||
url: element.url,
|
||||
}
|
||||
|
||||
const state = await getFormState({
|
||||
const { state } = await getFormState({
|
||||
apiRoute: config.routes.api,
|
||||
body: {
|
||||
data,
|
||||
|
||||
@@ -38,7 +38,7 @@ export const LinkDrawer: React.FC<Props> = ({
|
||||
|
||||
const onChange: FormProps['onChange'][0] = useCallback(
|
||||
async ({ formState: prevFormState }) => {
|
||||
return await getFormState({
|
||||
const { state } = await getFormState({
|
||||
apiRoute: config.routes.api,
|
||||
body: {
|
||||
id,
|
||||
@@ -48,6 +48,8 @@ export const LinkDrawer: React.FC<Props> = ({
|
||||
},
|
||||
serverURL: config.serverURL,
|
||||
})
|
||||
|
||||
return state
|
||||
},
|
||||
|
||||
[config.routes.api, config.serverURL, fieldMapPath, id],
|
||||
|
||||
@@ -71,7 +71,7 @@ export const UploadDrawer: React.FC<{
|
||||
const data = deepCopyObject(element?.fields || {})
|
||||
|
||||
const awaitInitialState = async () => {
|
||||
const state = await getFormState({
|
||||
const { state } = await getFormState({
|
||||
apiRoute: config.routes.api,
|
||||
body: {
|
||||
id,
|
||||
@@ -101,7 +101,7 @@ export const UploadDrawer: React.FC<{
|
||||
|
||||
const onChange: FormProps['onChange'][0] = useCallback(
|
||||
async ({ formState: prevFormState }) => {
|
||||
return await getFormState({
|
||||
const { state } = await getFormState({
|
||||
apiRoute: config.routes.api,
|
||||
body: {
|
||||
id,
|
||||
@@ -111,6 +111,8 @@ export const UploadDrawer: React.FC<{
|
||||
},
|
||||
serverURL: config.serverURL,
|
||||
})
|
||||
|
||||
return state
|
||||
},
|
||||
|
||||
[config.routes.api, config.serverURL, relatedCollection.slug, schemaPath, id],
|
||||
|
||||
@@ -126,6 +126,7 @@ export const clientTranslationKeys = createClientTranslationKeys([
|
||||
'general:addFilter',
|
||||
'general:adminTheme',
|
||||
'general:and',
|
||||
'general:anotherUserTakenOver',
|
||||
'general:applyChanges',
|
||||
'general:ascending',
|
||||
'general:automatic',
|
||||
@@ -150,6 +151,7 @@ export const clientTranslationKeys = createClientTranslationKeys([
|
||||
'general:createNewLabel',
|
||||
'general:creating',
|
||||
'general:creatingNewLabel',
|
||||
'general:currentlyEditing',
|
||||
'general:custom',
|
||||
'general:dark',
|
||||
'general:dashboard',
|
||||
@@ -161,13 +163,16 @@ export const clientTranslationKeys = createClientTranslationKeys([
|
||||
'general:depth',
|
||||
'general:deselectAllRows',
|
||||
'general:document',
|
||||
'general:documentLocked',
|
||||
'general:documents',
|
||||
'general:duplicate',
|
||||
'general:duplicateWithoutSaving',
|
||||
'general:edit',
|
||||
'general:editing',
|
||||
'general:editingLabel',
|
||||
'general:editingTakenOver',
|
||||
'general:editLabel',
|
||||
'general:editedSince',
|
||||
'general:email',
|
||||
'general:emailAddress',
|
||||
'general:enterAValue',
|
||||
@@ -178,6 +183,8 @@ export const clientTranslationKeys = createClientTranslationKeys([
|
||||
'general:filters',
|
||||
'general:filterWhere',
|
||||
'general:globals',
|
||||
'general:goBack',
|
||||
'general:isEditing',
|
||||
'general:language',
|
||||
'general:lastModified',
|
||||
'general:leaveAnyway',
|
||||
@@ -230,6 +237,7 @@ export const clientTranslationKeys = createClientTranslationKeys([
|
||||
'general:success',
|
||||
'general:successfullyCreated',
|
||||
'general:successfullyDuplicated',
|
||||
'general:takeOver',
|
||||
'general:thisLanguage',
|
||||
'general:titleDeleted',
|
||||
'general:true',
|
||||
@@ -243,6 +251,7 @@ export const clientTranslationKeys = createClientTranslationKeys([
|
||||
'general:updatedCountSuccessfully',
|
||||
'general:updatedSuccessfully',
|
||||
'general:updating',
|
||||
'general:viewReadOnly',
|
||||
'general:uploading',
|
||||
'general:welcome',
|
||||
|
||||
|
||||
@@ -177,6 +177,7 @@ export const arTranslations: DefaultTranslationsObject = {
|
||||
addFilter: 'أضف فلتر',
|
||||
adminTheme: 'شكل واجهة المستخدم',
|
||||
and: 'و',
|
||||
anotherUserTakenOver: 'قام مستخدم آخر بالاستيلاء على تحرير هذا المستند.',
|
||||
applyChanges: 'طبق التغييرات',
|
||||
ascending: 'تصاعدي',
|
||||
automatic: 'تلقائي',
|
||||
@@ -201,6 +202,8 @@ export const arTranslations: DefaultTranslationsObject = {
|
||||
createNewLabel: 'إنشاء {{label}} جديد',
|
||||
creating: 'يتمّ الإنشاء',
|
||||
creatingNewLabel: 'جاري إنشاء {{label}} جديد',
|
||||
currentlyEditing:
|
||||
'يقوم حاليًا بتحرير هذا المستند. إذا توليت، سيتم منعه من الاستمرار في التحرير وقد يفقد التغييرات غير المحفوظة.',
|
||||
custom: 'مخصص',
|
||||
dark: 'غامق',
|
||||
dashboard: 'لوحة التّحكّم',
|
||||
@@ -212,14 +215,17 @@ export const arTranslations: DefaultTranslationsObject = {
|
||||
descending: 'تنازلي',
|
||||
deselectAllRows: 'إلغاء تحديد جميع الصفوف',
|
||||
document: 'وثيقة',
|
||||
documentLocked: 'تم قفل المستند',
|
||||
documents: 'وثائق',
|
||||
duplicate: 'استنساخ',
|
||||
duplicateWithoutSaving: 'استنساخ بدون حفظ التغييرات',
|
||||
edit: 'تعديل',
|
||||
editedSince: 'تم التحرير منذ',
|
||||
editing: 'جاري التعديل',
|
||||
editingLabel_many: 'تعديل {{count}} {{label}}',
|
||||
editingLabel_one: 'تعديل {{count}} {{label}}',
|
||||
editingLabel_other: 'تعديل {{count}} {{label}}',
|
||||
editingTakenOver: 'تم الاستيلاء على التحرير',
|
||||
editLabel: 'تعديل {{label}}',
|
||||
email: 'البريد الإلكتروني',
|
||||
emailAddress: 'عنوان البريد الإلكتروني',
|
||||
@@ -232,6 +238,8 @@ export const arTranslations: DefaultTranslationsObject = {
|
||||
filters: 'عوامل التصفية',
|
||||
filterWhere: 'تصفية {{label}} حيث',
|
||||
globals: 'عامة',
|
||||
goBack: 'العودة',
|
||||
isEditing: 'يحرر',
|
||||
language: 'اللغة',
|
||||
lastModified: 'آخر تعديل',
|
||||
leaveAnyway: 'المغادرة على أي حال',
|
||||
@@ -287,6 +295,7 @@ export const arTranslations: DefaultTranslationsObject = {
|
||||
success: 'النجاح',
|
||||
successfullyCreated: '{{label}} تم إنشاؤها بنجاح.',
|
||||
successfullyDuplicated: '{{label}} تم استنساخها بنجاح.',
|
||||
takeOver: 'تولي',
|
||||
thisLanguage: 'العربية',
|
||||
titleDeleted: 'تم حذف {{label}} "{{title}}" بنجاح.',
|
||||
true: 'صحيح',
|
||||
@@ -302,6 +311,7 @@ export const arTranslations: DefaultTranslationsObject = {
|
||||
username: 'اسم المستخدم',
|
||||
users: 'المستخدمين',
|
||||
value: 'القيمة',
|
||||
viewReadOnly: 'عرض للقراءة فقط',
|
||||
welcome: 'مرحبًا',
|
||||
},
|
||||
operators: {
|
||||
|
||||
@@ -178,6 +178,7 @@ export const azTranslations: DefaultTranslationsObject = {
|
||||
addFilter: 'Filter əlavə et',
|
||||
adminTheme: 'Admin Mövzusu',
|
||||
and: 'Və',
|
||||
anotherUserTakenOver: 'Başqa bir istifadəçi bu sənədin redaktəsini ələ keçirdi.',
|
||||
applyChanges: 'Dəyişiklikləri Tətbiq Edin',
|
||||
ascending: 'Artan',
|
||||
automatic: 'Avtomatik',
|
||||
@@ -203,6 +204,8 @@ export const azTranslations: DefaultTranslationsObject = {
|
||||
createNewLabel: 'Yeni {{label}} yarat',
|
||||
creating: 'Yaradılır',
|
||||
creatingNewLabel: 'Yeni {{label}} yaradılır',
|
||||
currentlyEditing:
|
||||
'hazırda bu sənədi redaktə edir. Siz öhdəliyi götürsəniz, redaktəni davam etdirməkdən bloklanacaqlar və qeydə alınmamış dəyişiklikləri itirə bilərlər.',
|
||||
custom: 'Xüsusi',
|
||||
dark: 'Tünd',
|
||||
dashboard: 'Panel',
|
||||
@@ -214,14 +217,17 @@ export const azTranslations: DefaultTranslationsObject = {
|
||||
descending: 'Azalan',
|
||||
deselectAllRows: 'Bütün sıraları seçimi ləğv edin',
|
||||
document: 'Sənəd',
|
||||
documentLocked: 'Sənəd kilidləndi',
|
||||
documents: 'Sənədlər',
|
||||
duplicate: 'Dublikat',
|
||||
duplicateWithoutSaving: 'Dəyişiklikləri saxlamadan dublikatla',
|
||||
edit: 'Redaktə et',
|
||||
editedSince: 'Redaktə edilib',
|
||||
editing: 'Redaktə olunur',
|
||||
editingLabel_many: '{{count}} {{label}} redaktə olunur',
|
||||
editingLabel_one: '{{count}} {{label}} redaktə olunur',
|
||||
editingLabel_other: '{{count}} {{label}} redaktə olunur',
|
||||
editingTakenOver: 'Redaktə ələ keçirildi',
|
||||
editLabel: '{{label}} redaktə et',
|
||||
email: 'Elektron poçt',
|
||||
emailAddress: 'Elektron poçt ünvanı',
|
||||
@@ -234,6 +240,8 @@ export const azTranslations: DefaultTranslationsObject = {
|
||||
filters: 'Filtərlər',
|
||||
filterWhere: '{{label}} filtrlə',
|
||||
globals: 'Qloballar',
|
||||
goBack: 'Geri qayıt',
|
||||
isEditing: 'redaktə edir',
|
||||
language: 'Dil',
|
||||
lastModified: 'Son dəyişdirildi',
|
||||
leaveAnyway: 'Heç olmasa çıx',
|
||||
@@ -289,6 +297,7 @@ export const azTranslations: DefaultTranslationsObject = {
|
||||
success: 'Uğur',
|
||||
successfullyCreated: '{{label}} uğurla yaradıldı.',
|
||||
successfullyDuplicated: '{{label}} uğurla dublikatlandı.',
|
||||
takeOver: 'Əvvəl',
|
||||
thisLanguage: 'Azərbaycan dili',
|
||||
titleDeleted: '{{label}} "{{title}}" uğurla silindi.',
|
||||
true: 'Doğru',
|
||||
@@ -305,6 +314,7 @@ export const azTranslations: DefaultTranslationsObject = {
|
||||
username: 'İstifadəçi adı',
|
||||
users: 'İstifadəçilər',
|
||||
value: 'Dəyər',
|
||||
viewReadOnly: 'Yalnız oxu rejimində bax',
|
||||
welcome: 'Xoş gəldiniz',
|
||||
},
|
||||
operators: {
|
||||
|
||||
@@ -178,6 +178,7 @@ export const bgTranslations: DefaultTranslationsObject = {
|
||||
addFilter: 'Добави филтър',
|
||||
adminTheme: 'Цветова тема',
|
||||
and: 'И',
|
||||
anotherUserTakenOver: 'Друг потребител пое редактирането на този документ.',
|
||||
applyChanges: 'Приложи промените',
|
||||
ascending: 'Възходящ',
|
||||
automatic: 'Автоматична',
|
||||
@@ -202,6 +203,8 @@ export const bgTranslations: DefaultTranslationsObject = {
|
||||
createNewLabel: 'Създай нов {{label}}',
|
||||
creating: 'Създава се',
|
||||
creatingNewLabel: 'Създаване на нов {{label}}',
|
||||
currentlyEditing:
|
||||
'в момента редактира този документ. Ако поемете управлението, те ще бъдат блокирани от продължаване на редактирането и може да загубят незаписаните промени.',
|
||||
custom: 'Персонализиран',
|
||||
dark: 'Тъмна',
|
||||
dashboard: 'Табло',
|
||||
@@ -213,14 +216,17 @@ export const bgTranslations: DefaultTranslationsObject = {
|
||||
descending: 'Низходящо',
|
||||
deselectAllRows: 'Деселектирай всички редове',
|
||||
document: 'Документ',
|
||||
documentLocked: 'Документът е заключен',
|
||||
documents: 'Документи',
|
||||
duplicate: 'Дупликирай',
|
||||
duplicateWithoutSaving: 'Дупликирай без да запазваш промените',
|
||||
edit: 'Редактирай',
|
||||
editedSince: 'Редактирано от',
|
||||
editing: 'Редактиране',
|
||||
editingLabel_many: 'Редактиране на {{count}} {{label}}',
|
||||
editingLabel_one: 'Редактиране на {{count}} {{label}}',
|
||||
editingLabel_other: 'Редактиране на {{count}} {{label}}',
|
||||
editingTakenOver: 'Редактирането е поето',
|
||||
editLabel: 'Редактирай {{label}}',
|
||||
email: 'Имейл',
|
||||
emailAddress: 'Имейл адрес',
|
||||
@@ -233,6 +239,8 @@ export const bgTranslations: DefaultTranslationsObject = {
|
||||
filters: 'Филтри',
|
||||
filterWhere: 'Филтрирай {{label}} където',
|
||||
globals: 'Глобални',
|
||||
goBack: 'Върни се',
|
||||
isEditing: 'редактира',
|
||||
language: 'Език',
|
||||
lastModified: 'Последно променено',
|
||||
leaveAnyway: 'Напусни въпреки това',
|
||||
@@ -288,6 +296,7 @@ export const bgTranslations: DefaultTranslationsObject = {
|
||||
success: 'Успех',
|
||||
successfullyCreated: '{{label}} успешно създаден.',
|
||||
successfullyDuplicated: '{{label}} успешно дупликиран.',
|
||||
takeOver: 'Поемане',
|
||||
thisLanguage: 'Български',
|
||||
titleDeleted: '{{label}} "{{title}}" успешно изтрит.',
|
||||
true: 'Истина',
|
||||
@@ -303,6 +312,7 @@ export const bgTranslations: DefaultTranslationsObject = {
|
||||
username: 'Потребителско име',
|
||||
users: 'Потребители',
|
||||
value: 'Стойност',
|
||||
viewReadOnly: 'Преглед само за четене',
|
||||
welcome: 'Добре дошъл',
|
||||
},
|
||||
operators: {
|
||||
|
||||
@@ -178,6 +178,7 @@ export const csTranslations: DefaultTranslationsObject = {
|
||||
addFilter: 'Přidat filtr',
|
||||
adminTheme: 'Motiv administračního rozhraní',
|
||||
and: 'a',
|
||||
anotherUserTakenOver: 'Jiný uživatel převzal úpravy tohoto dokumentu.',
|
||||
applyChanges: 'Použít změny',
|
||||
ascending: 'Vzestupně',
|
||||
automatic: 'Automatický',
|
||||
@@ -202,6 +203,8 @@ export const csTranslations: DefaultTranslationsObject = {
|
||||
createNewLabel: 'Vytvořit nový {{label}}',
|
||||
creating: 'Vytváření',
|
||||
creatingNewLabel: 'Vytváření nového {{label}}',
|
||||
currentlyEditing:
|
||||
'právě upravuje tento dokument. Pokud převezmete kontrolu, budou zablokováni v pokračování úprav a mohou také přijít o neuložené změny.',
|
||||
custom: 'Vlastní',
|
||||
dark: 'Tmavý',
|
||||
dashboard: 'Nástěnka',
|
||||
@@ -213,14 +216,17 @@ export const csTranslations: DefaultTranslationsObject = {
|
||||
descending: 'Sestupně',
|
||||
deselectAllRows: 'Zrušte výběr všech řádků',
|
||||
document: 'Dokument',
|
||||
documentLocked: 'Dokument je uzamčen',
|
||||
documents: 'Dokumenty',
|
||||
duplicate: 'Duplikovat',
|
||||
duplicateWithoutSaving: 'Duplikovat bez uložení změn',
|
||||
edit: 'Upravit',
|
||||
editedSince: 'Upraveno od',
|
||||
editing: 'Úprava',
|
||||
editingLabel_many: 'Úprava {{count}} {{label}}',
|
||||
editingLabel_one: 'Úprava {{count}} {{label}}',
|
||||
editingLabel_other: 'Úprava {{count}} {{label}}',
|
||||
editingTakenOver: 'Úpravy byly převzaty',
|
||||
editLabel: 'Upravit {{label}}',
|
||||
email: 'E-mail',
|
||||
emailAddress: 'E-mailová adresa',
|
||||
@@ -233,6 +239,8 @@ export const csTranslations: DefaultTranslationsObject = {
|
||||
filters: 'Filtry',
|
||||
filterWhere: 'Filtrovat {{label}} kde',
|
||||
globals: 'Globální',
|
||||
goBack: 'Vrátit se',
|
||||
isEditing: 'upravuje',
|
||||
language: 'Jazyk',
|
||||
lastModified: 'Naposledy změněno',
|
||||
leaveAnyway: 'Přesto odejít',
|
||||
@@ -288,6 +296,7 @@ export const csTranslations: DefaultTranslationsObject = {
|
||||
success: 'Úspěch',
|
||||
successfullyCreated: '{{label}} úspěšně vytvořeno.',
|
||||
successfullyDuplicated: '{{label}} úspěšně duplikováno.',
|
||||
takeOver: 'Převzít',
|
||||
thisLanguage: 'Čeština',
|
||||
titleDeleted: '{{label}} "{{title}}" úspěšně smazáno.',
|
||||
true: 'Pravda',
|
||||
@@ -303,6 +312,7 @@ export const csTranslations: DefaultTranslationsObject = {
|
||||
username: 'Uživatelské jméno',
|
||||
users: 'Uživatelé',
|
||||
value: 'Hodnota',
|
||||
viewReadOnly: 'Zobrazit pouze pro čtení',
|
||||
welcome: 'Vítejte',
|
||||
},
|
||||
operators: {
|
||||
|
||||
@@ -182,6 +182,7 @@ export const deTranslations: DefaultTranslationsObject = {
|
||||
addFilter: 'Filter hinzufügen',
|
||||
adminTheme: 'Admin-Farbthema',
|
||||
and: 'Und',
|
||||
anotherUserTakenOver: 'Ein anderer Benutzer hat die Bearbeitung dieses Dokuments übernommen.',
|
||||
applyChanges: 'Änderungen anwenden',
|
||||
ascending: 'Aufsteigend',
|
||||
automatic: 'Automatisch',
|
||||
@@ -207,6 +208,8 @@ export const deTranslations: DefaultTranslationsObject = {
|
||||
createNewLabel: '{{label}} neu erstellen',
|
||||
creating: 'Erstelle',
|
||||
creatingNewLabel: 'Erstelle {{label}}',
|
||||
currentlyEditing:
|
||||
'bearbeitet gerade dieses Dokument. Wenn Sie übernehmen, wird die Bearbeitung blockiert und nicht gespeicherte Änderungen können verloren gehen.',
|
||||
custom: 'Benutzerdefiniert',
|
||||
dark: 'Dunkel',
|
||||
dashboard: 'Übersicht',
|
||||
@@ -218,14 +221,17 @@ export const deTranslations: DefaultTranslationsObject = {
|
||||
descending: 'Absteigend',
|
||||
deselectAllRows: 'Alle Zeilen abwählen',
|
||||
document: 'Dokument',
|
||||
documentLocked: 'Dokument gesperrt',
|
||||
documents: 'Dokumente',
|
||||
duplicate: 'Duplizieren',
|
||||
duplicateWithoutSaving: 'Dupliziere ohne Änderungen zu speichern',
|
||||
edit: 'Bearbeiten',
|
||||
editedSince: 'Bearbeitet seit',
|
||||
editing: 'Bearbeite',
|
||||
editingLabel_many: 'Bearbeiten von {{count}} {{label}}',
|
||||
editingLabel_one: 'Bearbeiten von {{count}} {{label}}',
|
||||
editingLabel_other: 'Bearbeiten von {{count}} {{label}}',
|
||||
editingTakenOver: 'Bearbeitung übernommen',
|
||||
editLabel: '{{label}} bearbeiten',
|
||||
email: 'E-Mail',
|
||||
emailAddress: 'E-Mail-Adresse',
|
||||
@@ -238,6 +244,8 @@ export const deTranslations: DefaultTranslationsObject = {
|
||||
filters: 'Filter',
|
||||
filterWhere: 'Filter {{label}} wo',
|
||||
globals: 'Globale Dokumente',
|
||||
goBack: 'Zurück',
|
||||
isEditing: 'bearbeitet',
|
||||
language: 'Sprache',
|
||||
lastModified: 'Zuletzt geändert',
|
||||
leaveAnyway: 'Trotzdem verlassen',
|
||||
@@ -293,6 +301,7 @@ export const deTranslations: DefaultTranslationsObject = {
|
||||
success: 'Erfolg',
|
||||
successfullyCreated: '{{label}} erfolgreich erstellt.',
|
||||
successfullyDuplicated: '{{label}} wurde erfolgreich dupliziert.',
|
||||
takeOver: 'Übernehmen',
|
||||
thisLanguage: 'Deutsch',
|
||||
titleDeleted: '{{label}} {{title}} wurde erfolgreich gelöscht.',
|
||||
true: 'Wahr',
|
||||
@@ -309,6 +318,7 @@ export const deTranslations: DefaultTranslationsObject = {
|
||||
username: 'Benutzername',
|
||||
users: 'Benutzer',
|
||||
value: 'Wert',
|
||||
viewReadOnly: 'Nur-Lese-Ansicht',
|
||||
welcome: 'Willkommen',
|
||||
},
|
||||
operators: {
|
||||
|
||||
@@ -180,6 +180,7 @@ export const enTranslations = {
|
||||
addFilter: 'Add Filter',
|
||||
adminTheme: 'Admin Theme',
|
||||
and: 'And',
|
||||
anotherUserTakenOver: 'Another user has taken over editing this document.',
|
||||
applyChanges: 'Apply Changes',
|
||||
ascending: 'Ascending',
|
||||
automatic: 'Automatic',
|
||||
@@ -205,6 +206,8 @@ export const enTranslations = {
|
||||
createNewLabel: 'Create new {{label}}',
|
||||
creating: 'Creating',
|
||||
creatingNewLabel: 'Creating new {{label}}',
|
||||
currentlyEditing:
|
||||
'is currently editing this document. If you take over, they will be blocked from continuing to edit, and may also lose unsaved changes.',
|
||||
custom: 'Custom',
|
||||
dark: 'Dark',
|
||||
dashboard: 'Dashboard',
|
||||
@@ -216,14 +219,17 @@ export const enTranslations = {
|
||||
descending: 'Descending',
|
||||
deselectAllRows: 'Deselect all rows',
|
||||
document: 'Document',
|
||||
documentLocked: 'Document locked',
|
||||
documents: 'Documents',
|
||||
duplicate: 'Duplicate',
|
||||
duplicateWithoutSaving: 'Duplicate without saving changes',
|
||||
edit: 'Edit',
|
||||
editedSince: 'Edited since',
|
||||
editing: 'Editing',
|
||||
editingLabel_many: 'Editing {{count}} {{label}}',
|
||||
editingLabel_one: 'Editing {{count}} {{label}}',
|
||||
editingLabel_other: 'Editing {{count}} {{label}}',
|
||||
editingTakenOver: 'Editing taken over',
|
||||
editLabel: 'Edit {{label}}',
|
||||
email: 'Email',
|
||||
emailAddress: 'Email Address',
|
||||
@@ -236,6 +242,8 @@ export const enTranslations = {
|
||||
filters: 'Filters',
|
||||
filterWhere: 'Filter {{label}} where',
|
||||
globals: 'Globals',
|
||||
goBack: 'Go back',
|
||||
isEditing: 'is editing',
|
||||
language: 'Language',
|
||||
lastModified: 'Last Modified',
|
||||
leaveAnyway: 'Leave anyway',
|
||||
@@ -291,6 +299,7 @@ export const enTranslations = {
|
||||
success: 'Success',
|
||||
successfullyCreated: '{{label}} successfully created.',
|
||||
successfullyDuplicated: '{{label}} successfully duplicated.',
|
||||
takeOver: 'Take over',
|
||||
thisLanguage: 'English',
|
||||
titleDeleted: '{{label}} "{{title}}" successfully deleted.',
|
||||
true: 'True',
|
||||
@@ -306,6 +315,7 @@ export const enTranslations = {
|
||||
username: 'Username',
|
||||
users: 'Users',
|
||||
value: 'Value',
|
||||
viewReadOnly: 'View read-only',
|
||||
welcome: 'Welcome',
|
||||
},
|
||||
operators: {
|
||||
|
||||
@@ -182,6 +182,7 @@ export const esTranslations: DefaultTranslationsObject = {
|
||||
addFilter: 'Añadir filtro',
|
||||
adminTheme: 'Tema del admin',
|
||||
and: 'Y',
|
||||
anotherUserTakenOver: 'Otro usuario ha tomado el control de la edición de este documento.',
|
||||
applyChanges: 'Aplicar Cambios',
|
||||
ascending: 'Ascendente',
|
||||
automatic: 'Automático',
|
||||
@@ -207,6 +208,8 @@ export const esTranslations: DefaultTranslationsObject = {
|
||||
createNewLabel: 'Crear nuevo {{label}}',
|
||||
creating: 'Creando',
|
||||
creatingNewLabel: 'Creando nuevo {{label}}',
|
||||
currentlyEditing:
|
||||
'está editando este documento. Si tomas el control, se les bloqueará para que no continúen editando y podrían perder los cambios no guardados.',
|
||||
custom: 'Personalizado',
|
||||
dark: 'Oscuro',
|
||||
dashboard: 'Tablero',
|
||||
@@ -218,14 +221,17 @@ export const esTranslations: DefaultTranslationsObject = {
|
||||
descending: 'Descendente',
|
||||
deselectAllRows: 'Deselecciona todas las filas',
|
||||
document: 'Documento',
|
||||
documentLocked: 'Documento bloqueado',
|
||||
documents: 'Documentos',
|
||||
duplicate: 'Duplicar',
|
||||
duplicateWithoutSaving: 'Duplicar sin guardar cambios',
|
||||
edit: 'Editar',
|
||||
editedSince: 'Editado desde',
|
||||
editing: 'Editando',
|
||||
editingLabel_many: 'Edición de {{count}} {{label}}',
|
||||
editingLabel_one: 'Editando {{count}} {{label}}',
|
||||
editingLabel_other: 'Edición de {{count}} {{label}}',
|
||||
editingTakenOver: 'Edición tomada',
|
||||
editLabel: 'Editar {{label}}',
|
||||
email: 'Correo electrónico',
|
||||
emailAddress: 'Dirección de Correo Electrónico',
|
||||
@@ -238,6 +244,8 @@ export const esTranslations: DefaultTranslationsObject = {
|
||||
filters: 'Filtros',
|
||||
filterWhere: 'Filtrar {{label}} donde',
|
||||
globals: 'Globales',
|
||||
goBack: 'Volver',
|
||||
isEditing: 'está editando',
|
||||
language: 'Idioma',
|
||||
lastModified: 'Última modificación',
|
||||
leaveAnyway: 'Salir de todos modos',
|
||||
@@ -293,6 +301,7 @@ export const esTranslations: DefaultTranslationsObject = {
|
||||
success: 'Éxito',
|
||||
successfullyCreated: '{{label}} creado correctamente.',
|
||||
successfullyDuplicated: '{{label}} duplicado correctamente.',
|
||||
takeOver: 'Tomar el control',
|
||||
thisLanguage: 'Español',
|
||||
titleDeleted: '{{label}} {{title}} eliminado correctamente.',
|
||||
true: 'Verdadero',
|
||||
@@ -308,6 +317,7 @@ export const esTranslations: DefaultTranslationsObject = {
|
||||
username: 'Nombre de usuario',
|
||||
users: 'Usuarios',
|
||||
value: 'Valor',
|
||||
viewReadOnly: 'Ver solo lectura',
|
||||
welcome: 'Bienvenido',
|
||||
},
|
||||
operators: {
|
||||
|
||||
@@ -177,6 +177,7 @@ export const faTranslations: DefaultTranslationsObject = {
|
||||
addFilter: 'افزودن علامت',
|
||||
adminTheme: 'پوسته پیشخوان',
|
||||
and: 'و',
|
||||
anotherUserTakenOver: 'کاربر دیگری ویرایش این سند را به دست گرفته است.',
|
||||
applyChanges: 'اعمال تغییرات',
|
||||
ascending: 'صعودی',
|
||||
automatic: 'خودکار',
|
||||
@@ -202,6 +203,8 @@ export const faTranslations: DefaultTranslationsObject = {
|
||||
createNewLabel: 'ساختن {{label}} تازه',
|
||||
creating: 'در حال ساخت',
|
||||
creatingNewLabel: 'در حال ساختن {{label}} تازه',
|
||||
currentlyEditing:
|
||||
'در حال حاضر در حال ویرایش این سند است. اگر شما مسئولیت را به عهده بگیرید، از ادامه ویرایش مسدود خواهد شد و ممکن است تغییرات ذخیره نشده را از دست بدهند.',
|
||||
custom: 'سفارشی',
|
||||
dark: 'تاریک',
|
||||
dashboard: 'پیشخوان',
|
||||
@@ -213,14 +216,17 @@ export const faTranslations: DefaultTranslationsObject = {
|
||||
descending: 'رو به پایین',
|
||||
deselectAllRows: 'تمام سطرها را از انتخاب خارج کنید',
|
||||
document: 'سند',
|
||||
documentLocked: 'سند قفل شده است',
|
||||
documents: 'اسناد',
|
||||
duplicate: 'تکراری',
|
||||
duplicateWithoutSaving: 'رونوشت بدون ذخیره کردن تغییرات',
|
||||
edit: 'نگارش',
|
||||
editedSince: 'ویرایش شده از',
|
||||
editing: 'در حال نگارش',
|
||||
editingLabel_many: 'در حال نگارش {{count}} از {{label}}',
|
||||
editingLabel_one: 'در حال نگارش {{count}} از {{label}}',
|
||||
editingLabel_other: 'در حال نگارش {{count}} از {{label}}',
|
||||
editingTakenOver: 'ویرایش به دست گرفته شد',
|
||||
editLabel: 'نگارش {{label}}',
|
||||
email: 'رایانامه',
|
||||
emailAddress: 'نشانی رایانامه',
|
||||
@@ -233,6 +239,8 @@ export const faTranslations: DefaultTranslationsObject = {
|
||||
filters: 'علامتگذاریها',
|
||||
filterWhere: 'علامت گذاری کردن {{label}} جایی که',
|
||||
globals: 'سراسری',
|
||||
goBack: 'برگشت',
|
||||
isEditing: 'در حال ویرایش است',
|
||||
language: 'زبان',
|
||||
lastModified: 'آخرین نگارش',
|
||||
leaveAnyway: 'به هر حال ترک کن',
|
||||
@@ -288,6 +296,7 @@ export const faTranslations: DefaultTranslationsObject = {
|
||||
success: 'موفقیت',
|
||||
successfullyCreated: '{{label}} با موفقیت ساخته شد.',
|
||||
successfullyDuplicated: '{{label}} با موفقیت رونوشت شد.',
|
||||
takeOver: 'تحویل گرفتن',
|
||||
thisLanguage: 'فارسی',
|
||||
titleDeleted: '{{label}} "{{title}}" با موفقیت پاک شد.',
|
||||
true: 'درست',
|
||||
@@ -303,6 +312,7 @@ export const faTranslations: DefaultTranslationsObject = {
|
||||
username: 'نام کاربری',
|
||||
users: 'کاربران',
|
||||
value: 'مقدار',
|
||||
viewReadOnly: 'فقط برای خواندن مشاهده کنید',
|
||||
welcome: 'خوشآمدید',
|
||||
},
|
||||
operators: {
|
||||
|
||||
@@ -185,6 +185,7 @@ export const frTranslations: DefaultTranslationsObject = {
|
||||
addFilter: 'Ajouter un filtre',
|
||||
adminTheme: 'Thème d’administration',
|
||||
and: 'Et',
|
||||
anotherUserTakenOver: 'Un autre utilisateur a pris en charge la modification de ce document.',
|
||||
applyChanges: 'Appliquer les modifications',
|
||||
ascending: 'Ascendant',
|
||||
automatic: 'Automatique',
|
||||
@@ -210,6 +211,8 @@ export const frTranslations: DefaultTranslationsObject = {
|
||||
createNewLabel: 'Créer un(e) nouveau ou nouvelle {{label}}',
|
||||
creating: 'création en cours',
|
||||
creatingNewLabel: 'Création d’un(e) nouveau ou nouvelle {{label}}',
|
||||
currentlyEditing:
|
||||
'est en train de modifier ce document. Si vous prenez le contrôle, ils seront bloqués pour continuer à modifier et pourraient également perdre les modifications non enregistrées.',
|
||||
custom: 'Personnalisé',
|
||||
dark: 'Sombre',
|
||||
dashboard: 'Tableau de bord',
|
||||
@@ -221,14 +224,17 @@ export const frTranslations: DefaultTranslationsObject = {
|
||||
descending: 'Descendant(e)',
|
||||
deselectAllRows: 'Désélectionner toutes les lignes',
|
||||
document: 'Document',
|
||||
documentLocked: 'Document verrouillé',
|
||||
documents: 'Documents',
|
||||
duplicate: 'Dupliquer',
|
||||
duplicateWithoutSaving: 'Dupliquer sans enregistrer les modifications',
|
||||
edit: 'Éditer',
|
||||
editedSince: 'Modifié depuis',
|
||||
editing: 'Modification en cours',
|
||||
editingLabel_many: 'Modification des {{count}} {{label}}',
|
||||
editingLabel_one: 'Modification de {{count}} {{label}}',
|
||||
editingLabel_other: 'Modification des {{count}} {{label}}',
|
||||
editingTakenOver: 'Modification prise en charge',
|
||||
editLabel: 'Modifier {{label}}',
|
||||
email: 'E-mail',
|
||||
emailAddress: 'Adresse e-mail',
|
||||
@@ -241,6 +247,8 @@ export const frTranslations: DefaultTranslationsObject = {
|
||||
filters: 'Filtres',
|
||||
filterWhere: 'Filtrer {{label}} où',
|
||||
globals: 'Globals(es)',
|
||||
goBack: 'Retourner',
|
||||
isEditing: 'est en train de modifier',
|
||||
language: 'Langue',
|
||||
lastModified: 'Dernière modification',
|
||||
leaveAnyway: 'Quitter quand même',
|
||||
@@ -296,6 +304,7 @@ export const frTranslations: DefaultTranslationsObject = {
|
||||
success: 'Succès',
|
||||
successfullyCreated: '{{label}} créé(e) avec succès.',
|
||||
successfullyDuplicated: '{{label}} dupliqué(e) avec succès.',
|
||||
takeOver: 'Prendre en charge',
|
||||
thisLanguage: 'Français',
|
||||
titleDeleted: '{{label}} "{{title}}" supprimé(e) avec succès.',
|
||||
true: 'Vrai',
|
||||
@@ -312,6 +321,7 @@ export const frTranslations: DefaultTranslationsObject = {
|
||||
username: "Nom d'utilisateur",
|
||||
users: 'Utilisateurs',
|
||||
value: 'Valeur',
|
||||
viewReadOnly: 'Afficher en lecture seule',
|
||||
welcome: 'Bienvenue',
|
||||
},
|
||||
operators: {
|
||||
|
||||
@@ -174,6 +174,7 @@ export const heTranslations: DefaultTranslationsObject = {
|
||||
addFilter: 'הוסף מסנן',
|
||||
adminTheme: 'ערכת נושא ממשק הניהול',
|
||||
and: 'וגם',
|
||||
anotherUserTakenOver: 'משתמש אחר השתלט על עריכת מסמך זה.',
|
||||
applyChanges: 'החל שינויים',
|
||||
ascending: 'בסדר עולה',
|
||||
automatic: 'אוטומטי',
|
||||
@@ -198,6 +199,8 @@ export const heTranslations: DefaultTranslationsObject = {
|
||||
createNewLabel: 'יצירת {{label}} חדש',
|
||||
creating: 'יצירה',
|
||||
creatingNewLabel: 'יצירת {{label}} חדש',
|
||||
currentlyEditing:
|
||||
'עורך כעת את המסמך הזה. אם תשתלט, הם ייחסמו מהמשך העריכה וייתכן שגם יאבדו שינויים שלא נשמרו.',
|
||||
custom: 'מותאם אישית',
|
||||
dark: 'כהה',
|
||||
dashboard: 'לוח מחוונים',
|
||||
@@ -209,14 +212,17 @@ export const heTranslations: DefaultTranslationsObject = {
|
||||
descending: 'בסדר יורד',
|
||||
deselectAllRows: 'בטל בחירת כל השורות',
|
||||
document: 'מסמך',
|
||||
documentLocked: 'המסמך ננעל',
|
||||
documents: 'מסמכים',
|
||||
duplicate: 'שכפול',
|
||||
duplicateWithoutSaving: 'שכפול ללא שמירת שינויים',
|
||||
edit: 'עריכה',
|
||||
editedSince: 'נערך מאז',
|
||||
editing: 'עריכה',
|
||||
editingLabel_many: 'עריכת {{count}} {{label}}',
|
||||
editingLabel_one: 'עריכת {{label}} אחד',
|
||||
editingLabel_other: 'עריכת {{count}} {{label}}',
|
||||
editingTakenOver: 'העריכה נלקחה על ידי',
|
||||
editLabel: 'עריכת {{label}}',
|
||||
email: 'דוא"ל',
|
||||
emailAddress: 'כתובת דוא"ל',
|
||||
@@ -229,6 +235,8 @@ export const heTranslations: DefaultTranslationsObject = {
|
||||
filters: 'מסננים',
|
||||
filterWhere: 'סנן {{label}} בהם',
|
||||
globals: 'גלובלים',
|
||||
goBack: 'חזור',
|
||||
isEditing: 'עורך',
|
||||
language: 'שפה',
|
||||
lastModified: 'נערך לאחרונה',
|
||||
leaveAnyway: 'צא בכל זאת',
|
||||
@@ -283,6 +291,7 @@ export const heTranslations: DefaultTranslationsObject = {
|
||||
success: 'הצלחה',
|
||||
successfullyCreated: '{{label}} נוצר בהצלחה.',
|
||||
successfullyDuplicated: '{{label}} שוכפל בהצלחה.',
|
||||
takeOver: 'קח פיקוד',
|
||||
thisLanguage: 'עברית',
|
||||
titleDeleted: '{{label}} "{{title}}" נמחק בהצלחה.',
|
||||
true: 'True',
|
||||
@@ -298,6 +307,7 @@ export const heTranslations: DefaultTranslationsObject = {
|
||||
username: 'שם משתמש',
|
||||
users: 'משתמשים',
|
||||
value: 'ערך',
|
||||
viewReadOnly: 'הצג קריאה בלבד',
|
||||
welcome: 'ברוך הבא',
|
||||
},
|
||||
operators: {
|
||||
|
||||
@@ -179,6 +179,7 @@ export const hrTranslations: DefaultTranslationsObject = {
|
||||
addFilter: 'Dodaj filter',
|
||||
adminTheme: 'Administratorska tema',
|
||||
and: 'I',
|
||||
anotherUserTakenOver: 'Drugi korisnik je preuzeo uređivanje ovog dokumenta.',
|
||||
applyChanges: 'Primijeni promjene',
|
||||
ascending: 'Uzlazno',
|
||||
automatic: 'Automatsko',
|
||||
@@ -203,6 +204,8 @@ export const hrTranslations: DefaultTranslationsObject = {
|
||||
createNewLabel: 'Kreiraj novo {{label}}',
|
||||
creating: 'Kreira se',
|
||||
creatingNewLabel: 'Kreiranje novog {{label}}',
|
||||
currentlyEditing:
|
||||
'trenutno uređuje ovaj dokument. Ako preuzmete, bit će im onemogućeno daljnje uređivanje i mogu izgubiti nespremljene promjene.',
|
||||
custom: 'Prilagođen',
|
||||
dark: 'Tamno',
|
||||
dashboard: 'Nadzorna ploča',
|
||||
@@ -214,14 +217,17 @@ export const hrTranslations: DefaultTranslationsObject = {
|
||||
descending: 'Silazno',
|
||||
deselectAllRows: 'Odznači sve redove',
|
||||
document: 'Dokument',
|
||||
documentLocked: 'Dokument je zaključan',
|
||||
documents: 'Dokumenti',
|
||||
duplicate: 'Duplikat',
|
||||
duplicateWithoutSaving: 'Dupliciraj bez spremanja promjena',
|
||||
edit: 'Uredi',
|
||||
editedSince: 'Uređeno od',
|
||||
editing: 'Uređivanje',
|
||||
editingLabel_many: 'Uređivanje {{count}} {{label}}',
|
||||
editingLabel_one: 'Uređivanje {{count}} {{label}}',
|
||||
editingLabel_other: 'Uređivanje {{count}} {{label}}',
|
||||
editingTakenOver: 'Uređivanje preuzeto',
|
||||
editLabel: 'Uredi {{label}}',
|
||||
email: 'Email',
|
||||
emailAddress: 'Email adresa',
|
||||
@@ -234,6 +240,8 @@ export const hrTranslations: DefaultTranslationsObject = {
|
||||
filters: 'Filteri',
|
||||
filterWhere: 'Filter {{label}} gdje',
|
||||
globals: 'Globali',
|
||||
goBack: 'Vrati se',
|
||||
isEditing: 'uređuje',
|
||||
language: 'Jezik',
|
||||
lastModified: 'Zadnja promjena',
|
||||
leaveAnyway: 'Svejedno napusti',
|
||||
@@ -289,6 +297,7 @@ export const hrTranslations: DefaultTranslationsObject = {
|
||||
success: 'Uspjeh',
|
||||
successfullyCreated: '{{label}} uspješno kreirano.',
|
||||
successfullyDuplicated: '{{label}} uspješno duplicirano.',
|
||||
takeOver: 'Preuzmi',
|
||||
thisLanguage: 'Hrvatski',
|
||||
titleDeleted: '{{label}} "{{title}}" uspješno obrisano.',
|
||||
true: 'Istinito',
|
||||
@@ -304,6 +313,7 @@ export const hrTranslations: DefaultTranslationsObject = {
|
||||
username: 'Korisničko ime',
|
||||
users: 'Korisnici',
|
||||
value: 'Attribute',
|
||||
viewReadOnly: 'Pogledaj samo za čitanje',
|
||||
welcome: 'Dobrodošli',
|
||||
},
|
||||
operators: {
|
||||
|
||||
@@ -180,6 +180,7 @@ export const huTranslations: DefaultTranslationsObject = {
|
||||
addFilter: 'Szűrő hozzáadása',
|
||||
adminTheme: 'Admin téma',
|
||||
and: 'És',
|
||||
anotherUserTakenOver: 'Egy másik felhasználó átvette ennek a dokumentumnak a szerkesztését.',
|
||||
applyChanges: 'Változtatások alkalmazása',
|
||||
ascending: 'Növekvő',
|
||||
automatic: 'Automatikus',
|
||||
@@ -205,6 +206,8 @@ export const huTranslations: DefaultTranslationsObject = {
|
||||
createNewLabel: 'Új {{label}} létrehozása',
|
||||
creating: 'Létrehozás',
|
||||
creatingNewLabel: 'Új {{label}} létrehozása',
|
||||
currentlyEditing:
|
||||
'jelenleg szerkeszti ezt a dokumentumot. Ha átveszed, nem tudja folytatni a szerkesztést, és elveszítheti a mentetlen módosításokat.',
|
||||
custom: 'Egyéni',
|
||||
dark: 'Sötét',
|
||||
dashboard: 'Irányítópult',
|
||||
@@ -216,14 +219,17 @@ export const huTranslations: DefaultTranslationsObject = {
|
||||
descending: 'Csökkenő',
|
||||
deselectAllRows: 'Jelölje ki az összes sort',
|
||||
document: 'Dokumentum',
|
||||
documentLocked: 'A dokumentum zárolva van',
|
||||
documents: 'Dokumentumok',
|
||||
duplicate: 'Duplikálás',
|
||||
duplicateWithoutSaving: 'Duplikálás a módosítások mentése nélkül',
|
||||
edit: 'Szerkesztés',
|
||||
editedSince: 'Szerkesztve',
|
||||
editing: 'Szerkesztés',
|
||||
editingLabel_many: '{{count}} {{label}} szerkesztése',
|
||||
editingLabel_one: '{{count}} {{label}} szerkesztése',
|
||||
editingLabel_other: '{{count}} {{label}} szerkesztése',
|
||||
editingTakenOver: 'A szerkesztést átvették',
|
||||
editLabel: '{{label}} szerkesztése',
|
||||
email: 'E-mail',
|
||||
emailAddress: 'E-mail cím',
|
||||
@@ -236,6 +242,8 @@ export const huTranslations: DefaultTranslationsObject = {
|
||||
filters: 'Szűrők',
|
||||
filterWhere: 'Szűrő {{label}} ahol',
|
||||
globals: 'Globálisok',
|
||||
goBack: 'Vissza',
|
||||
isEditing: 'szerkeszt',
|
||||
language: 'Nyelv',
|
||||
lastModified: 'Utoljára módosítva',
|
||||
leaveAnyway: 'Távozás mindenképp',
|
||||
@@ -291,6 +299,7 @@ export const huTranslations: DefaultTranslationsObject = {
|
||||
success: 'Siker',
|
||||
successfullyCreated: '{{label}} sikeresen létrehozva.',
|
||||
successfullyDuplicated: '{{label}} sikeresen duplikálódott.',
|
||||
takeOver: 'Átvétel',
|
||||
thisLanguage: 'Magyar',
|
||||
titleDeleted: '{{label}} "{{title}}" sikeresen törölve.',
|
||||
true: 'Igaz',
|
||||
@@ -306,6 +315,7 @@ export const huTranslations: DefaultTranslationsObject = {
|
||||
username: 'Felhasználónév',
|
||||
users: 'Felhasználók',
|
||||
value: 'Érték',
|
||||
viewReadOnly: 'Csak olvasható nézet',
|
||||
welcome: 'Üdvözöljük',
|
||||
},
|
||||
operators: {
|
||||
|
||||
@@ -181,6 +181,8 @@ export const itTranslations: DefaultTranslationsObject = {
|
||||
addFilter: 'Aggiungi Filtro',
|
||||
adminTheme: 'Tema Admin',
|
||||
and: 'E',
|
||||
anotherUserTakenOver:
|
||||
'Un altro utente ha preso il controllo della modifica di questo documento.',
|
||||
applyChanges: 'Applica modifiche',
|
||||
ascending: 'Ascendente',
|
||||
automatic: 'Automatico',
|
||||
@@ -205,6 +207,8 @@ export const itTranslations: DefaultTranslationsObject = {
|
||||
createNewLabel: 'Crea nuovo {{label}}',
|
||||
creating: 'Crea nuovo',
|
||||
creatingNewLabel: 'Creazione di un nuovo {{label}}',
|
||||
currentlyEditing:
|
||||
'sta attualmente modificando questo documento. Se prendi il controllo, verranno bloccati dal continuare a modificare e potrebbero anche perdere le modifiche non salvate.',
|
||||
custom: 'Personalizzato',
|
||||
dark: 'Scuro',
|
||||
dashboard: 'Dashboard',
|
||||
@@ -216,14 +220,17 @@ export const itTranslations: DefaultTranslationsObject = {
|
||||
descending: 'Decrescente',
|
||||
deselectAllRows: 'Deseleziona tutte le righe',
|
||||
document: 'Documento',
|
||||
documentLocked: 'Documento bloccato',
|
||||
documents: 'Documenti',
|
||||
duplicate: 'Duplica',
|
||||
duplicateWithoutSaving: 'Duplica senza salvare le modifiche',
|
||||
edit: 'Modificare',
|
||||
editedSince: 'Modificato da',
|
||||
editing: 'Modifica',
|
||||
editingLabel_many: 'Modificare {{count}} {{label}}',
|
||||
editingLabel_one: 'Modifica {{count}} {{label}}',
|
||||
editingLabel_other: 'Modificare {{count}} {{label}}',
|
||||
editingTakenOver: 'Modifica presa in carico',
|
||||
editLabel: 'Modifica {{label}}',
|
||||
email: 'Email',
|
||||
emailAddress: 'Indirizzo Email',
|
||||
@@ -236,6 +243,8 @@ export const itTranslations: DefaultTranslationsObject = {
|
||||
filters: 'Filtri',
|
||||
filterWhere: 'Filtra {{label}} se',
|
||||
globals: 'Globali',
|
||||
goBack: 'Torna indietro',
|
||||
isEditing: 'sta modificando',
|
||||
language: 'Lingua',
|
||||
lastModified: 'Ultima modifica',
|
||||
leaveAnyway: 'Esci comunque',
|
||||
@@ -291,6 +300,7 @@ export const itTranslations: DefaultTranslationsObject = {
|
||||
success: 'Successo',
|
||||
successfullyCreated: '{{label}} creato con successo.',
|
||||
successfullyDuplicated: '{{label}} duplicato con successo.',
|
||||
takeOver: 'Prendi il controllo',
|
||||
thisLanguage: 'Italiano',
|
||||
titleDeleted: '{{label}} {{title}} eliminato con successo.',
|
||||
true: 'Vero',
|
||||
@@ -306,6 +316,7 @@ export const itTranslations: DefaultTranslationsObject = {
|
||||
username: 'Nome utente',
|
||||
users: 'Utenti',
|
||||
value: 'Valore',
|
||||
viewReadOnly: 'Visualizza solo lettura',
|
||||
welcome: 'Benvenuto',
|
||||
},
|
||||
operators: {
|
||||
|
||||
@@ -179,6 +179,7 @@ export const jaTranslations: DefaultTranslationsObject = {
|
||||
addFilter: '絞り込みを追加',
|
||||
adminTheme: '管理画面のテーマ',
|
||||
and: 'かつ',
|
||||
anotherUserTakenOver: '別のユーザーがこのドキュメントの編集を引き継ぎました。',
|
||||
applyChanges: '変更を適用する',
|
||||
ascending: '昇順',
|
||||
automatic: '自動設定',
|
||||
@@ -203,6 +204,8 @@ export const jaTranslations: DefaultTranslationsObject = {
|
||||
createNewLabel: '{{label}} を新規作成',
|
||||
creating: '作成中',
|
||||
creatingNewLabel: '{{label}} を新規作成しています',
|
||||
currentlyEditing:
|
||||
'このドキュメントを編集中です。あなたが引き継ぐと、編集を続けることができなくなり、未保存の変更が失われる可能性があります。',
|
||||
custom: 'カスタム',
|
||||
dark: 'ダークモード',
|
||||
dashboard: 'ダッシュボード',
|
||||
@@ -214,14 +217,17 @@ export const jaTranslations: DefaultTranslationsObject = {
|
||||
descending: '降順',
|
||||
deselectAllRows: 'すべての行の選択を解除します',
|
||||
document: 'ドキュメント',
|
||||
documentLocked: 'ドキュメントがロックされました',
|
||||
documents: 'ドキュメント',
|
||||
duplicate: '複製',
|
||||
duplicateWithoutSaving: '変更を保存せずに複製',
|
||||
edit: '編集',
|
||||
editedSince: 'から編集',
|
||||
editing: '編集',
|
||||
editingLabel_many: '{{count}}つの{{label}}を編集しています',
|
||||
editingLabel_one: '{{count}}つの{{label}}を編集しています',
|
||||
editingLabel_other: '{{count}}つの{{label}}を編集しています',
|
||||
editingTakenOver: '編集が引き継がれました',
|
||||
editLabel: '{{label}} を編集',
|
||||
email: 'メールアドレス',
|
||||
emailAddress: 'メールアドレス',
|
||||
@@ -234,6 +240,8 @@ export const jaTranslations: DefaultTranslationsObject = {
|
||||
filters: '絞り込み',
|
||||
filterWhere: '{{label}} の絞り込み',
|
||||
globals: 'グローバル',
|
||||
goBack: '戻る',
|
||||
isEditing: '編集中',
|
||||
language: '言語',
|
||||
lastModified: '最終更新',
|
||||
leaveAnyway: 'すぐに画面を離れる',
|
||||
@@ -289,6 +297,7 @@ export const jaTranslations: DefaultTranslationsObject = {
|
||||
success: '成功',
|
||||
successfullyCreated: '{{label}} が作成されました。',
|
||||
successfullyDuplicated: '{{label}} が複製されました。',
|
||||
takeOver: '引き継ぐ',
|
||||
thisLanguage: 'Japanese',
|
||||
titleDeleted: '{{label}} "{{title}}" が削除されました。',
|
||||
true: '真実',
|
||||
@@ -304,6 +313,7 @@ export const jaTranslations: DefaultTranslationsObject = {
|
||||
username: 'ユーザーネーム',
|
||||
users: 'ユーザー',
|
||||
value: '値',
|
||||
viewReadOnly: '読み取り専用で表示',
|
||||
welcome: 'ようこそ',
|
||||
},
|
||||
operators: {
|
||||
|
||||
@@ -178,6 +178,7 @@ export const koTranslations: DefaultTranslationsObject = {
|
||||
addFilter: '필터 추가',
|
||||
adminTheme: '관리자 테마',
|
||||
and: '및',
|
||||
anotherUserTakenOver: '다른 사용자가 이 문서의 편집을 인수했습니다.',
|
||||
applyChanges: '변경 사항 적용',
|
||||
ascending: '오름차순',
|
||||
automatic: '자동 설정',
|
||||
@@ -202,6 +203,8 @@ export const koTranslations: DefaultTranslationsObject = {
|
||||
createNewLabel: '새로운 {{label}} 생성',
|
||||
creating: '생성 중',
|
||||
creatingNewLabel: '{{label}} 생성 중',
|
||||
currentlyEditing:
|
||||
'현재 이 문서를 편집 중입니다. 당신이 인수하면, 편집을 계속할 수 없게 되고, 저장되지 않은 변경 사항이 손실될 수 있습니다.',
|
||||
custom: '사용자 정의',
|
||||
dark: '다크',
|
||||
dashboard: '대시보드',
|
||||
@@ -213,14 +216,17 @@ export const koTranslations: DefaultTranslationsObject = {
|
||||
descending: '내림차순',
|
||||
deselectAllRows: '모든 행 선택 해제',
|
||||
document: '문서',
|
||||
documentLocked: '문서가 잠겼습니다',
|
||||
documents: '문서들',
|
||||
duplicate: '복제',
|
||||
duplicateWithoutSaving: '변경 사항 저장 없이 복제',
|
||||
edit: '수정',
|
||||
editedSince: '편집됨',
|
||||
editing: '수정 중',
|
||||
editingLabel_many: '{{count}}개의 {{label}} 수정 중',
|
||||
editingLabel_one: '{{count}}개의 {{label}} 수정 중',
|
||||
editingLabel_other: '{{count}}개의 {{label}} 수정 중',
|
||||
editingTakenOver: '편집이 인수되었습니다',
|
||||
editLabel: '{{label}} 수정',
|
||||
email: '이메일',
|
||||
emailAddress: '이메일 주소',
|
||||
@@ -233,6 +239,8 @@ export const koTranslations: DefaultTranslationsObject = {
|
||||
filters: '필터',
|
||||
filterWhere: '{{label}} 필터링 조건',
|
||||
globals: '글로벌',
|
||||
goBack: '돌아가기',
|
||||
isEditing: '편집 중',
|
||||
language: '언어',
|
||||
lastModified: '마지막 수정 일시',
|
||||
leaveAnyway: '그래도 나가시겠습니까?',
|
||||
@@ -288,6 +296,7 @@ export const koTranslations: DefaultTranslationsObject = {
|
||||
success: '성공',
|
||||
successfullyCreated: '{{label}}이(가) 생성되었습니다.',
|
||||
successfullyDuplicated: '{{label}}이(가) 복제되었습니다.',
|
||||
takeOver: '인수하기',
|
||||
thisLanguage: '한국어',
|
||||
titleDeleted: '{{label}} "{{title}}"을(를) 삭제했습니다.',
|
||||
true: '참',
|
||||
@@ -303,6 +312,7 @@ export const koTranslations: DefaultTranslationsObject = {
|
||||
username: '사용자 이름',
|
||||
users: '사용자',
|
||||
value: '값',
|
||||
viewReadOnly: '읽기 전용으로 보기',
|
||||
welcome: '환영합니다',
|
||||
},
|
||||
operators: {
|
||||
|
||||
@@ -180,6 +180,7 @@ export const myTranslations: DefaultTranslationsObject = {
|
||||
addFilter: 'ဇကာထည့်ပါ။',
|
||||
adminTheme: 'အက်ပ်ဒိုင်များစပ်စွာ',
|
||||
and: 'နှင့်',
|
||||
anotherUserTakenOver: 'တစ်ခြားအသုံးပြုသူသည်ဤစာရွက်စာတမ်းကိုပြင်ဆင်မှုကိုရယူလိုက်သည်။',
|
||||
applyChanges: 'ပြောင်းလဲမှုများ အသုံးပြုပါ',
|
||||
ascending: 'တက်နေသည်',
|
||||
automatic: 'အော်တို',
|
||||
@@ -205,6 +206,8 @@ export const myTranslations: DefaultTranslationsObject = {
|
||||
createNewLabel: '{{label}} အသစ် ဖန်တီးမည်။',
|
||||
creating: 'ဖန်တီးနေသည်။',
|
||||
creatingNewLabel: '{{label}} အသစ် ဖန်တီးနေသည်။',
|
||||
currentlyEditing:
|
||||
'ဒီစာရွက်စာတမ်းကိုလက်ရှိပြင်ဆင်နေသည်။ သင်ဤတာဝန်ကိုယူပါက၊ သူတို့သည်ဆက်လက်ပြင်ဆင်ခွင့်မရအောင်ပိတ်ထားမည်ဖြစ်ပြီး၊ မသိမ်းဆည်းရသေးသောပြင်ဆင်မှုများကိုလည်းဆုံးရှုံးနိုင်သည်။',
|
||||
custom: 'ထုတ်ကုန် စိတ်ကြိုက်',
|
||||
dark: 'အမှောင်',
|
||||
dashboard: 'ပင်မစာမျက်နှာ',
|
||||
@@ -216,14 +219,17 @@ export const myTranslations: DefaultTranslationsObject = {
|
||||
descending: 'ဆင်းသက်လာသည်။',
|
||||
deselectAllRows: 'အားလုံးကို မရွေးနိုင်ပါ',
|
||||
document: 'စာရွက်စာတမ်း',
|
||||
documentLocked: 'စာရွက်စာတမ်းကိုပိတ်ထားသည်',
|
||||
documents: 'စာရွက်စာတမ်းများ',
|
||||
duplicate: 'ပုံတူပွားမည်။',
|
||||
duplicateWithoutSaving: 'သေချာပါပြီ။',
|
||||
edit: 'တည်းဖြတ်ပါ။',
|
||||
editedSince: 'ကစပြီးတည်းဖြတ်ခဲ့သည်',
|
||||
editing: 'ပြင်ဆင်နေသည်။',
|
||||
editingLabel_many: 'တည်းဖြတ်ခြင်း {{count}} {{label}}',
|
||||
editingLabel_one: 'တည်းဖြတ်ခြင်း {{count}} {{label}}',
|
||||
editingLabel_other: 'တည်းဖြတ်ခြင်း {{count}} {{label}}',
|
||||
editingTakenOver: 'တည်းဖြတ်ခြင်းကိုရယူခဲ့သည်',
|
||||
editLabel: '{{label}} ပြင်ဆင်မည်။',
|
||||
email: 'အီးမေးလ်',
|
||||
emailAddress: 'အီးမေးလ် လိပ်စာ',
|
||||
@@ -236,6 +242,8 @@ export const myTranslations: DefaultTranslationsObject = {
|
||||
filters: 'စစ်ထုတ်မှုများ',
|
||||
filterWhere: 'နေရာတွင် စစ်ထုတ်ပါ။',
|
||||
globals: 'Globals',
|
||||
goBack: 'နောက်သို့',
|
||||
isEditing: 'ပြင်ဆင်နေသည်',
|
||||
language: 'ဘာသာစကား',
|
||||
lastModified: 'နောက်ဆုံးပြင်ဆင်ထားသည်။',
|
||||
leaveAnyway: 'ဘာဖြစ်ဖြစ် ထွက်မည်။',
|
||||
@@ -291,6 +299,7 @@ export const myTranslations: DefaultTranslationsObject = {
|
||||
success: 'အောင်မြင်မှု',
|
||||
successfullyCreated: '{{label}} အောင်မြင်စွာဖန်တီးခဲ့သည်။',
|
||||
successfullyDuplicated: '{{label}} အောင်မြင်စွာ ပုံတူပွားခဲ့သည်။',
|
||||
takeOver: 'တာဝန်ယူပါ',
|
||||
thisLanguage: 'မြန်မာစာ',
|
||||
titleDeleted: '{{label}} {{title}} အောင်မြင်စွာ ဖျက်သိမ်းခဲ့သည်။',
|
||||
true: 'အမှန်',
|
||||
@@ -307,6 +316,7 @@ export const myTranslations: DefaultTranslationsObject = {
|
||||
username: 'Nama pengguna',
|
||||
users: 'အသုံးပြုသူများ',
|
||||
value: 'တန်ဖိုး',
|
||||
viewReadOnly: 'ဖတ်ရှုရန်သာကြည့်ပါ',
|
||||
welcome: 'ကြိုဆိုပါတယ်။',
|
||||
},
|
||||
operators: {
|
||||
|
||||
@@ -178,6 +178,7 @@ export const nbTranslations: DefaultTranslationsObject = {
|
||||
addFilter: 'Legg til filter',
|
||||
adminTheme: 'Admin-tema',
|
||||
and: 'Og',
|
||||
anotherUserTakenOver: 'En annen bruker har tatt over redigeringen av dette dokumentet.',
|
||||
applyChanges: 'Bruk endringer',
|
||||
ascending: 'Stigende',
|
||||
automatic: 'Automatisk',
|
||||
@@ -203,6 +204,8 @@ export const nbTranslations: DefaultTranslationsObject = {
|
||||
createNewLabel: 'Opprett ny {{label}}',
|
||||
creating: 'Oppretter',
|
||||
creatingNewLabel: 'Oppretter ny {{label}}',
|
||||
currentlyEditing:
|
||||
'redigerer for øyeblikket dette dokumentet. Hvis du tar over, blir de blokkert fra å fortsette å redigere, og de kan også miste ulagrede endringer.',
|
||||
custom: 'Tilpasset',
|
||||
dark: 'Mørk',
|
||||
dashboard: 'Kontrollpanel',
|
||||
@@ -214,14 +217,17 @@ export const nbTranslations: DefaultTranslationsObject = {
|
||||
descending: 'Synkende',
|
||||
deselectAllRows: 'Fjern markeringen fra alle rader',
|
||||
document: 'Dokument',
|
||||
documentLocked: 'Dokument låst',
|
||||
documents: 'Dokumenter',
|
||||
duplicate: 'Dupliser',
|
||||
duplicateWithoutSaving: 'Dupliser uten å lagre endringer',
|
||||
edit: 'Redigere',
|
||||
editedSince: 'Redigert siden',
|
||||
editing: 'Redigerer',
|
||||
editingLabel_many: 'Redigerer {{count}} {{label}}',
|
||||
editingLabel_one: 'Redigerer {{count}} {{label}}',
|
||||
editingLabel_other: 'Redigerer {{count}} {{label}}',
|
||||
editingTakenOver: 'Redigering overtatt',
|
||||
editLabel: 'Rediger {{label}}',
|
||||
email: 'E-post',
|
||||
emailAddress: 'E-postadresse',
|
||||
@@ -234,6 +240,8 @@ export const nbTranslations: DefaultTranslationsObject = {
|
||||
filters: 'Filter',
|
||||
filterWhere: 'Filtrer {{label}} der',
|
||||
globals: 'Globale variabler',
|
||||
goBack: 'Gå tilbake',
|
||||
isEditing: 'redigerer',
|
||||
language: 'Språk',
|
||||
lastModified: 'Sist endret',
|
||||
leaveAnyway: 'Forlat likevel',
|
||||
@@ -289,6 +297,7 @@ export const nbTranslations: DefaultTranslationsObject = {
|
||||
success: 'Suksess',
|
||||
successfullyCreated: '{{label}} ble opprettet.',
|
||||
successfullyDuplicated: '{{label}} ble duplisert.',
|
||||
takeOver: 'Ta over',
|
||||
thisLanguage: 'Norsk',
|
||||
titleDeleted: '{{label}} "{{title}}" ble slettet.',
|
||||
true: 'Sann',
|
||||
@@ -304,6 +313,7 @@ export const nbTranslations: DefaultTranslationsObject = {
|
||||
username: 'Brukernavn',
|
||||
users: 'Brukere',
|
||||
value: 'Verdi',
|
||||
viewReadOnly: 'Vis skrivebeskyttet',
|
||||
welcome: 'Velkommen',
|
||||
},
|
||||
operators: {
|
||||
|
||||
@@ -180,6 +180,7 @@ export const nlTranslations: DefaultTranslationsObject = {
|
||||
addFilter: 'Filter toevoegen',
|
||||
adminTheme: 'Adminthema',
|
||||
and: 'En',
|
||||
anotherUserTakenOver: 'Een andere gebruiker heeft de bewerking van dit document overgenomen.',
|
||||
applyChanges: 'Breng wijzigingen aan',
|
||||
ascending: 'Oplopend',
|
||||
automatic: 'Automatisch',
|
||||
@@ -205,6 +206,8 @@ export const nlTranslations: DefaultTranslationsObject = {
|
||||
createNewLabel: 'Nieuw(e) {{label}} aanmaken',
|
||||
creating: 'Aanmaken',
|
||||
creatingNewLabel: 'Nieuw(e) {{label}} aanmaken',
|
||||
currentlyEditing:
|
||||
'is momenteel dit document aan het bewerken. Als je het overneemt, wordt voorkomen dat ze doorgaan met bewerken en kunnen ze ook niet-opgeslagen wijzigingen verliezen.',
|
||||
custom: 'Aangepast',
|
||||
dark: 'Donker',
|
||||
dashboard: 'Dashboard',
|
||||
@@ -216,14 +219,17 @@ export const nlTranslations: DefaultTranslationsObject = {
|
||||
descending: 'Aflopend',
|
||||
deselectAllRows: 'Deselecteer alle rijen',
|
||||
document: 'Document',
|
||||
documentLocked: 'Document vergrendeld',
|
||||
documents: 'Documenten',
|
||||
duplicate: 'Dupliceren',
|
||||
duplicateWithoutSaving: 'Dupliceren zonder wijzigingen te bewaren',
|
||||
edit: 'Bewerk',
|
||||
editedSince: 'Bewerkt sinds',
|
||||
editing: 'Bewerken',
|
||||
editingLabel_many: 'Bewerken {{count}} {{label}}',
|
||||
editingLabel_one: 'Bewerken {{count}} {{label}}',
|
||||
editingLabel_other: 'Bewerken {{count}} {{label}}',
|
||||
editingTakenOver: 'Bewerking overgenomen',
|
||||
editLabel: 'Bewerk {{label}}',
|
||||
email: 'E-mail',
|
||||
emailAddress: 'E-maildres',
|
||||
@@ -236,6 +242,8 @@ export const nlTranslations: DefaultTranslationsObject = {
|
||||
filters: 'Filters',
|
||||
filterWhere: 'Filter {{label}} waar',
|
||||
globals: 'Globalen',
|
||||
goBack: 'Ga terug',
|
||||
isEditing: 'is aan het bewerken',
|
||||
language: 'Taal',
|
||||
lastModified: 'Laatst gewijzigd',
|
||||
leaveAnyway: 'Toch weggaan',
|
||||
@@ -291,6 +299,7 @@ export const nlTranslations: DefaultTranslationsObject = {
|
||||
success: 'Succes',
|
||||
successfullyCreated: '{{label}} succesvol aangemaakt.',
|
||||
successfullyDuplicated: '{{label}} succesvol gedupliceerd.',
|
||||
takeOver: 'Overnemen',
|
||||
thisLanguage: 'Nederlands',
|
||||
titleDeleted: '{{label}} "{{title}}" succesvol verwijderd.',
|
||||
true: 'Waar',
|
||||
@@ -306,6 +315,7 @@ export const nlTranslations: DefaultTranslationsObject = {
|
||||
username: 'Gebruikersnaam',
|
||||
users: 'Gebruikers',
|
||||
value: 'Waarde',
|
||||
viewReadOnly: 'Alleen-lezen weergave',
|
||||
welcome: 'Welkom',
|
||||
},
|
||||
operators: {
|
||||
|
||||
@@ -178,6 +178,7 @@ export const plTranslations: DefaultTranslationsObject = {
|
||||
addFilter: 'Dodaj filtr',
|
||||
adminTheme: 'Motyw administratora',
|
||||
and: 'i',
|
||||
anotherUserTakenOver: 'Inny użytkownik przejął edycję tego dokumentu.',
|
||||
applyChanges: 'Zastosuj zmiany',
|
||||
ascending: 'Rosnąco',
|
||||
automatic: 'Automatyczny',
|
||||
@@ -203,6 +204,8 @@ export const plTranslations: DefaultTranslationsObject = {
|
||||
createNewLabel: 'Stwórz nowy {{label}}',
|
||||
creating: 'Tworzenie',
|
||||
creatingNewLabel: 'Tworzenie nowego {{label}}',
|
||||
currentlyEditing:
|
||||
'obecnie edytuje ten dokument. Jeśli przejmiesz kontrolę, zostaną zablokowani przed dalszą edycją i mogą również utracić niezapisane zmiany.',
|
||||
custom: 'Niestandardowy',
|
||||
dark: 'Ciemny',
|
||||
dashboard: 'Panel',
|
||||
@@ -214,14 +217,17 @@ export const plTranslations: DefaultTranslationsObject = {
|
||||
descending: 'Malejąco',
|
||||
deselectAllRows: 'Odznacz wszystkie wiersze',
|
||||
document: 'Dokument',
|
||||
documentLocked: 'Dokument zablokowany',
|
||||
documents: 'Dokumenty',
|
||||
duplicate: 'Zduplikuj',
|
||||
duplicateWithoutSaving: 'Zduplikuj bez zapisywania zmian',
|
||||
edit: 'Edytuj',
|
||||
editedSince: 'Edytowano od',
|
||||
editing: 'Edycja',
|
||||
editingLabel_many: 'Edytowanie {{count}} {{label}}',
|
||||
editingLabel_one: 'Edytowanie {{count}} {{label}}',
|
||||
editingLabel_other: 'Edytowanie {{count}} {{label}}',
|
||||
editingTakenOver: 'Edycja przejęta',
|
||||
editLabel: 'Edytuj {{label}}',
|
||||
email: 'Email',
|
||||
emailAddress: 'Adres email',
|
||||
@@ -234,6 +240,8 @@ export const plTranslations: DefaultTranslationsObject = {
|
||||
filters: 'Filtry',
|
||||
filterWhere: 'Filtruj gdzie',
|
||||
globals: 'Globalne',
|
||||
goBack: 'Wróć',
|
||||
isEditing: 'edytuje',
|
||||
language: 'Język',
|
||||
lastModified: 'Ostatnio zmodyfikowany',
|
||||
leaveAnyway: 'Wyjdź mimo to',
|
||||
@@ -289,6 +297,7 @@ export const plTranslations: DefaultTranslationsObject = {
|
||||
success: 'Sukces',
|
||||
successfullyCreated: 'Pomyślnie utworzono {{label}}.',
|
||||
successfullyDuplicated: 'Pomyślnie zduplikowano {{label}}',
|
||||
takeOver: 'Przejąć',
|
||||
thisLanguage: 'Polski',
|
||||
titleDeleted: 'Pomyślnie usunięto {{label}} {{title}}',
|
||||
true: 'Prawda',
|
||||
@@ -304,6 +313,7 @@ export const plTranslations: DefaultTranslationsObject = {
|
||||
username: 'Nazwa użytkownika',
|
||||
users: 'użytkownicy',
|
||||
value: 'Wartość',
|
||||
viewReadOnly: 'Widok tylko do odczytu',
|
||||
welcome: 'Witaj',
|
||||
},
|
||||
operators: {
|
||||
|
||||
@@ -179,6 +179,7 @@ export const ptTranslations: DefaultTranslationsObject = {
|
||||
addFilter: 'Adicionar Filtro',
|
||||
adminTheme: 'Tema do Admin',
|
||||
and: 'E',
|
||||
anotherUserTakenOver: 'Outro usuário assumiu a edição deste documento.',
|
||||
applyChanges: 'Aplicar alterações',
|
||||
ascending: 'Ascendente',
|
||||
automatic: 'Automático',
|
||||
@@ -204,6 +205,8 @@ export const ptTranslations: DefaultTranslationsObject = {
|
||||
createNewLabel: 'Criar novo(a) {{label}}',
|
||||
creating: 'Criando',
|
||||
creatingNewLabel: 'Criando novo(a) {{label}}',
|
||||
currentlyEditing:
|
||||
'está editando este documento no momento. Se você assumir, eles serão impedidos de continuar editando e poderão perder alterações não salvas.',
|
||||
custom: 'Personalizado',
|
||||
dark: 'Escuro',
|
||||
dashboard: 'Painel de Controle',
|
||||
@@ -215,14 +218,17 @@ export const ptTranslations: DefaultTranslationsObject = {
|
||||
descending: 'Decrescente',
|
||||
deselectAllRows: 'Desmarcar todas as linhas',
|
||||
document: 'Documento',
|
||||
documentLocked: 'Documento bloqueado',
|
||||
documents: 'Documentos',
|
||||
duplicate: 'Duplicar',
|
||||
duplicateWithoutSaving: 'Duplicar sem salvar alterações',
|
||||
edit: 'Editar',
|
||||
editedSince: 'Editado desde',
|
||||
editing: 'Editando',
|
||||
editingLabel_many: 'Editando {{count}} {{label}}',
|
||||
editingLabel_one: 'Editando {{count}} {{label}}',
|
||||
editingLabel_other: 'Editando {{count}} {{label}}',
|
||||
editingTakenOver: 'Edição assumida',
|
||||
editLabel: 'Editar {{label}}',
|
||||
email: 'Email',
|
||||
emailAddress: 'Endereço de Email',
|
||||
@@ -235,6 +241,8 @@ export const ptTranslations: DefaultTranslationsObject = {
|
||||
filters: 'Filtros',
|
||||
filterWhere: 'Filtrar {{label}} em que',
|
||||
globals: 'Globais',
|
||||
goBack: 'Voltar',
|
||||
isEditing: 'está editando',
|
||||
language: 'Idioma',
|
||||
lastModified: 'Última modificação',
|
||||
leaveAnyway: 'Sair mesmo assim',
|
||||
@@ -290,6 +298,7 @@ export const ptTranslations: DefaultTranslationsObject = {
|
||||
success: 'Sucesso',
|
||||
successfullyCreated: '{{label}} criado com sucesso.',
|
||||
successfullyDuplicated: '{{label}} duplicado com sucesso.',
|
||||
takeOver: 'Assumir',
|
||||
thisLanguage: 'Português',
|
||||
titleDeleted: '{{label}} {{title}} excluído com sucesso.',
|
||||
true: 'Verdadeiro',
|
||||
@@ -305,6 +314,7 @@ export const ptTranslations: DefaultTranslationsObject = {
|
||||
username: 'Nome de usuário',
|
||||
users: 'usuários',
|
||||
value: 'Valor',
|
||||
viewReadOnly: 'Visualizar somente leitura',
|
||||
welcome: 'Boas vindas',
|
||||
},
|
||||
operators: {
|
||||
|
||||
@@ -182,6 +182,7 @@ export const roTranslations: DefaultTranslationsObject = {
|
||||
addFilter: 'Adaugă filtru',
|
||||
adminTheme: 'Tema Admin',
|
||||
and: 'Şi',
|
||||
anotherUserTakenOver: 'Un alt utilizator a preluat editarea acestui document.',
|
||||
applyChanges: 'Aplicați modificările',
|
||||
ascending: 'Ascendant',
|
||||
automatic: 'Automat',
|
||||
@@ -207,6 +208,8 @@ export const roTranslations: DefaultTranslationsObject = {
|
||||
createNewLabel: 'Creați un nou {{label}}',
|
||||
creating: 'Creare',
|
||||
creatingNewLabel: 'Crearea unui nou {{label}}',
|
||||
currentlyEditing:
|
||||
'editează în prezent acest document. Dacă preiei controlul, vor fi blocați să continue editarea și ar putea pierde modificările nesalvate.',
|
||||
custom: 'Personalizat',
|
||||
dark: 'Dark',
|
||||
dashboard: 'Panoul de bord',
|
||||
@@ -218,14 +221,17 @@ export const roTranslations: DefaultTranslationsObject = {
|
||||
descending: 'Descendentă',
|
||||
deselectAllRows: 'Deselectează toate rândurile',
|
||||
document: 'Document',
|
||||
documentLocked: 'Document blocat',
|
||||
documents: 'Documente',
|
||||
duplicate: 'Duplicați',
|
||||
duplicateWithoutSaving: 'Duplicați fără salvarea modificărilor',
|
||||
edit: 'Editează',
|
||||
editedSince: 'Editat din',
|
||||
editing: 'Editare',
|
||||
editingLabel_many: 'Editare {{count}} {{label}}',
|
||||
editingLabel_one: 'Editare {{count}} {{label}}',
|
||||
editingLabel_other: 'Editare {{count}} {{label}}',
|
||||
editingTakenOver: 'Editarea preluată',
|
||||
editLabel: 'Editați {{label}}',
|
||||
email: 'Email',
|
||||
emailAddress: 'Adresa de email',
|
||||
@@ -238,6 +244,8 @@ export const roTranslations: DefaultTranslationsObject = {
|
||||
filters: 'Filtre',
|
||||
filterWhere: 'Filtrează {{label}} unde',
|
||||
globals: 'Globale',
|
||||
goBack: 'Înapoi',
|
||||
isEditing: 'editează',
|
||||
language: 'Limba',
|
||||
lastModified: 'Ultima modificare',
|
||||
leaveAnyway: 'Pleacă oricum',
|
||||
@@ -293,6 +301,7 @@ export const roTranslations: DefaultTranslationsObject = {
|
||||
success: 'Succes',
|
||||
successfullyCreated: '{{label}} creat(ă) cu succes.',
|
||||
successfullyDuplicated: '{{label}} duplicat(ă) cu succes.',
|
||||
takeOver: 'Preia controlul',
|
||||
thisLanguage: 'Română',
|
||||
titleDeleted: '{{label}} "{{title}}" șters cu succes.',
|
||||
true: 'Adevărat',
|
||||
@@ -308,6 +317,7 @@ export const roTranslations: DefaultTranslationsObject = {
|
||||
username: 'Nume de utilizator',
|
||||
users: 'Utilizatori',
|
||||
value: 'Valoare',
|
||||
viewReadOnly: 'Vizualizare doar pentru citire',
|
||||
welcome: 'Bine ați venit',
|
||||
},
|
||||
operators: {
|
||||
|
||||
@@ -178,6 +178,7 @@ export const rsTranslations: DefaultTranslationsObject = {
|
||||
addFilter: 'Додај филтер',
|
||||
adminTheme: 'Администраторска тема',
|
||||
and: 'И',
|
||||
anotherUserTakenOver: 'Други корисник је преузео уређивање овог документа.',
|
||||
applyChanges: 'Примени промене',
|
||||
ascending: 'Узлазно',
|
||||
automatic: 'Аутоматско',
|
||||
@@ -202,6 +203,8 @@ export const rsTranslations: DefaultTranslationsObject = {
|
||||
createNewLabel: 'Креирај ново {{label}}',
|
||||
creating: 'Креира се',
|
||||
creatingNewLabel: 'Креирање новог {{label}}',
|
||||
currentlyEditing:
|
||||
'тренутно уређује овај документ. Ако преузмете контролу, биће блокирани да наставе са уређивањем и могу изгубити несачуване измене.',
|
||||
custom: 'Prilagođeno',
|
||||
dark: 'Тамно',
|
||||
dashboard: 'Контролни панел',
|
||||
@@ -213,14 +216,17 @@ export const rsTranslations: DefaultTranslationsObject = {
|
||||
descending: 'Опадајуће',
|
||||
deselectAllRows: 'Деселектујте све редове',
|
||||
document: 'Dokument',
|
||||
documentLocked: 'Документ је закључан',
|
||||
documents: 'Dokumenti',
|
||||
duplicate: 'Дупликат',
|
||||
duplicateWithoutSaving: 'Понови без чувања промена',
|
||||
edit: 'Уреди',
|
||||
editedSince: 'Измењено од',
|
||||
editing: 'Уређивање',
|
||||
editingLabel_many: 'Уређивање {{count}} {{label}}',
|
||||
editingLabel_one: 'Уређивање {{count}} {{label}}',
|
||||
editingLabel_other: 'Уређивање {{count}} {{label}}',
|
||||
editingTakenOver: 'Уређивање преузето',
|
||||
editLabel: 'Уреди {{label}}',
|
||||
email: 'Е-пошта',
|
||||
emailAddress: 'Адреса е-поште',
|
||||
@@ -233,6 +239,8 @@ export const rsTranslations: DefaultTranslationsObject = {
|
||||
filters: 'Филтери',
|
||||
filterWhere: 'Филтер {{label}} где',
|
||||
globals: 'Глобали',
|
||||
goBack: 'Врати се',
|
||||
isEditing: 'уређује',
|
||||
language: 'Језик',
|
||||
lastModified: 'Задња промена',
|
||||
leaveAnyway: 'Свеједно напусти',
|
||||
@@ -288,6 +296,7 @@ export const rsTranslations: DefaultTranslationsObject = {
|
||||
success: 'Uspeh',
|
||||
successfullyCreated: '{{label}} успешно креирано.',
|
||||
successfullyDuplicated: '{{label}} успешно дуплицирано.',
|
||||
takeOver: 'Превузети',
|
||||
thisLanguage: 'Српски (ћирилица)',
|
||||
titleDeleted: '{{label}} "{{title}}" успешно обрисано.',
|
||||
true: 'Istinito',
|
||||
@@ -303,6 +312,7 @@ export const rsTranslations: DefaultTranslationsObject = {
|
||||
username: 'Korisničko ime',
|
||||
users: 'Корисници',
|
||||
value: 'Вредност',
|
||||
viewReadOnly: 'Прегледај само за читање',
|
||||
welcome: 'Добродошли',
|
||||
},
|
||||
operators: {
|
||||
|
||||
@@ -178,6 +178,7 @@ export const rsLatinTranslations: DefaultTranslationsObject = {
|
||||
addFilter: 'Dodaj filter',
|
||||
adminTheme: 'Administratorska tema',
|
||||
and: 'I',
|
||||
anotherUserTakenOver: 'Drugi korisnik je preuzeo uređivanje ovog dokumenta.',
|
||||
applyChanges: 'Primeni promene',
|
||||
ascending: 'Uzlazno',
|
||||
automatic: 'Automatsko',
|
||||
@@ -202,6 +203,8 @@ export const rsLatinTranslations: DefaultTranslationsObject = {
|
||||
createNewLabel: 'Kreiraj novo {{label}}',
|
||||
creating: 'Kreira se',
|
||||
creatingNewLabel: 'Kreiranje novog {{label}}',
|
||||
currentlyEditing:
|
||||
'trenutno uređuje ovaj dokument. Ako preuzmete kontrolu, biće blokirani da nastave sa uređivanjem i mogu izgubiti nesačuvane izmene.',
|
||||
custom: 'Prilagođen',
|
||||
dark: 'Tamno',
|
||||
dashboard: 'Kontrolni panel',
|
||||
@@ -213,14 +216,17 @@ export const rsLatinTranslations: DefaultTranslationsObject = {
|
||||
descending: 'Opadajuće',
|
||||
deselectAllRows: 'Deselektujte sve redove',
|
||||
document: 'Dokument',
|
||||
documentLocked: 'Dokument je zaključan',
|
||||
documents: 'Dokumenti',
|
||||
duplicate: 'Duplikat',
|
||||
duplicateWithoutSaving: 'Ponovi bez čuvanja promena',
|
||||
edit: 'Uredi',
|
||||
editedSince: 'Izmenjeno od',
|
||||
editing: 'Uređivanje',
|
||||
editingLabel_many: 'Uređivanje {{count}} {{label}}',
|
||||
editingLabel_one: 'Uređivanje {{count}} {{label}}',
|
||||
editingLabel_other: 'Uređivanje {{count}} {{label}}',
|
||||
editingTakenOver: 'Uređivanje preuzeto',
|
||||
editLabel: 'Uredi {{label}}',
|
||||
email: 'E-pošta',
|
||||
emailAddress: 'Аdresa e-pošte',
|
||||
@@ -233,6 +239,8 @@ export const rsLatinTranslations: DefaultTranslationsObject = {
|
||||
filters: 'Filteri',
|
||||
filterWhere: 'Filter {{label}} gde',
|
||||
globals: 'Globali',
|
||||
goBack: 'Vrati se',
|
||||
isEditing: 'uređuje',
|
||||
language: 'Jezik',
|
||||
lastModified: 'Zadnja promena',
|
||||
leaveAnyway: 'Svejedno napusti',
|
||||
@@ -288,6 +296,7 @@ export const rsLatinTranslations: DefaultTranslationsObject = {
|
||||
success: 'Uspeh',
|
||||
successfullyCreated: '{{label}} uspešno kreirano.',
|
||||
successfullyDuplicated: '{{label}} uspešno duplicirano.',
|
||||
takeOver: 'Preuzeti',
|
||||
thisLanguage: 'Srpski (latinica)',
|
||||
titleDeleted: '{{label}} "{{title}}" uspešno obrisano.',
|
||||
true: 'Istinito',
|
||||
@@ -303,6 +312,7 @@ export const rsLatinTranslations: DefaultTranslationsObject = {
|
||||
username: 'Korisničko ime',
|
||||
users: 'Korisnici',
|
||||
value: 'Vrednost',
|
||||
viewReadOnly: 'Pregledaj samo za čitanje',
|
||||
welcome: 'Dobrodošli',
|
||||
},
|
||||
operators: {
|
||||
|
||||
@@ -180,6 +180,7 @@ export const ruTranslations: DefaultTranslationsObject = {
|
||||
addFilter: 'Добавить фильтр',
|
||||
adminTheme: 'Тема Панели',
|
||||
and: 'А также',
|
||||
anotherUserTakenOver: 'Другой пользователь взял на себя редактирование этого документа.',
|
||||
applyChanges: 'Применить изменения',
|
||||
ascending: 'Восходящий',
|
||||
automatic: 'Автоматически',
|
||||
@@ -205,6 +206,8 @@ export const ruTranslations: DefaultTranslationsObject = {
|
||||
createNewLabel: 'Создать новый {{label}}',
|
||||
creating: 'Создание',
|
||||
creatingNewLabel: 'Создание нового {{label}}',
|
||||
currentlyEditing:
|
||||
'в настоящее время редактирует этот документ. Если вы возьмете на себя, они будут заблокированы от продолжения редактирования и могут потерять несохраненные изменения.',
|
||||
custom: 'Обычай',
|
||||
dark: 'Тёмная',
|
||||
dashboard: 'Панель',
|
||||
@@ -216,14 +219,17 @@ export const ruTranslations: DefaultTranslationsObject = {
|
||||
descending: 'Уменьшение',
|
||||
deselectAllRows: 'Снять выделение со всех строк',
|
||||
document: 'Документ',
|
||||
documentLocked: 'Документ заблокирован',
|
||||
documents: 'Документы',
|
||||
duplicate: 'Дублировать',
|
||||
duplicateWithoutSaving: 'Дублирование без сохранения изменений',
|
||||
edit: 'Редактировать',
|
||||
editedSince: 'Отредактировано с',
|
||||
editing: 'Редактирование',
|
||||
editingLabel_many: 'Редактирование {{count}} {{label}}',
|
||||
editingLabel_one: 'Редактирование {{count}} {{label}}',
|
||||
editingLabel_other: 'Редактирование {{count}} {{label}}',
|
||||
editingTakenOver: 'Редактирование взято под контроль',
|
||||
editLabel: 'Редактировать {{label}}',
|
||||
email: 'Email',
|
||||
emailAddress: 'Email',
|
||||
@@ -236,6 +242,8 @@ export const ruTranslations: DefaultTranslationsObject = {
|
||||
filters: 'Фильтры',
|
||||
filterWhere: 'Где фильтровать',
|
||||
globals: 'Глобальные',
|
||||
goBack: 'Назад',
|
||||
isEditing: 'редактирует',
|
||||
language: 'Язык',
|
||||
lastModified: 'Последнее изменение',
|
||||
leaveAnyway: 'Все равно уйти',
|
||||
@@ -291,6 +299,7 @@ export const ruTranslations: DefaultTranslationsObject = {
|
||||
success: 'Успех',
|
||||
successfullyCreated: '{{label}} успешно создан.',
|
||||
successfullyDuplicated: '{{label}} успешно продублирован.',
|
||||
takeOver: 'Взять на себя',
|
||||
thisLanguage: 'Русский',
|
||||
titleDeleted: '{{label}} {{title}} успешно удалено.',
|
||||
true: 'Правда',
|
||||
@@ -307,6 +316,7 @@ export const ruTranslations: DefaultTranslationsObject = {
|
||||
username: 'Имя пользователя',
|
||||
users: 'пользователи',
|
||||
value: 'Значение',
|
||||
viewReadOnly: 'Просмотр только для чтения',
|
||||
welcome: 'Добро пожаловать',
|
||||
},
|
||||
operators: {
|
||||
|
||||
@@ -180,6 +180,7 @@ export const skTranslations: DefaultTranslationsObject = {
|
||||
addFilter: 'Pridať filter',
|
||||
adminTheme: 'Motív administračného rozhrania',
|
||||
and: 'a',
|
||||
anotherUserTakenOver: 'Iný používateľ prevzal úpravy tohto dokumentu.',
|
||||
applyChanges: 'Použiť zmeny',
|
||||
ascending: 'Vzostupne',
|
||||
automatic: 'Automatický',
|
||||
@@ -204,6 +205,8 @@ export const skTranslations: DefaultTranslationsObject = {
|
||||
createNewLabel: 'Vytvoriť nový {{label}}',
|
||||
creating: 'Vytváranie',
|
||||
creatingNewLabel: 'Vytváranie nového {{label}}',
|
||||
currentlyEditing:
|
||||
'práve upravuje tento dokument. Ak prevezmete kontrolu, budú zablokovaní z pokračovania v úpravách a môžu tiež stratiť neuložené zmeny.',
|
||||
custom: 'Vlastný',
|
||||
dark: 'Tmavý',
|
||||
dashboard: 'Nástenka',
|
||||
@@ -215,14 +218,17 @@ export const skTranslations: DefaultTranslationsObject = {
|
||||
descending: 'Zostupne',
|
||||
deselectAllRows: 'Zrušiť výber všetkých riadkov',
|
||||
document: 'Dokument',
|
||||
documentLocked: 'Dokument je zamknutý',
|
||||
documents: 'Dokumenty',
|
||||
duplicate: 'Duplikovať',
|
||||
duplicateWithoutSaving: 'Duplikovať bez uloženia zmien',
|
||||
edit: 'Upraviť',
|
||||
editedSince: 'Upravené od',
|
||||
editing: 'Úpravy',
|
||||
editingLabel_many: 'Úprava {{count}} {{label}}',
|
||||
editingLabel_one: 'Úprava {{count}} {{label}}',
|
||||
editingLabel_other: 'Úprava {{count}} {{label}}',
|
||||
editingTakenOver: 'Úpravy prevzaté',
|
||||
editLabel: 'Upraviť {{label}}',
|
||||
email: 'E-mail',
|
||||
emailAddress: 'E-mailová adresa',
|
||||
@@ -235,6 +241,8 @@ export const skTranslations: DefaultTranslationsObject = {
|
||||
filters: 'Filtry',
|
||||
filterWhere: 'Filtrovat kde je {{label}}',
|
||||
globals: 'Globalné',
|
||||
goBack: 'Vrátiť sa',
|
||||
isEditing: 'upravuje',
|
||||
language: 'Jazyk',
|
||||
lastModified: 'Naposledy zmenené',
|
||||
leaveAnyway: 'Presto odísť',
|
||||
@@ -290,6 +298,7 @@ export const skTranslations: DefaultTranslationsObject = {
|
||||
success: 'Úspech',
|
||||
successfullyCreated: '{{label}} úspešne vytvorené.',
|
||||
successfullyDuplicated: '{{label}} úspešne duplikované.',
|
||||
takeOver: 'Prevziať',
|
||||
thisLanguage: 'Slovenčina',
|
||||
titleDeleted: '{{label}} "{{title}}" úspešne zmazané.',
|
||||
true: 'Pravda',
|
||||
@@ -305,6 +314,7 @@ export const skTranslations: DefaultTranslationsObject = {
|
||||
username: 'Používateľské meno',
|
||||
users: 'Používatelia',
|
||||
value: 'Hodnota',
|
||||
viewReadOnly: 'Zobraziť iba na čítanie',
|
||||
welcome: 'Vitajte',
|
||||
},
|
||||
operators: {
|
||||
|
||||
@@ -178,6 +178,7 @@ export const svTranslations: DefaultTranslationsObject = {
|
||||
addFilter: 'Lägg Till Filter',
|
||||
adminTheme: 'Admin Tema',
|
||||
and: 'Och',
|
||||
anotherUserTakenOver: 'En annan användare har tagit över redigeringen av detta dokument.',
|
||||
applyChanges: 'Verkställ ändringar',
|
||||
ascending: 'Stigande',
|
||||
automatic: 'Automatisk',
|
||||
@@ -203,6 +204,8 @@ export const svTranslations: DefaultTranslationsObject = {
|
||||
createNewLabel: 'Skapa ny {{label}}',
|
||||
creating: 'Skapar',
|
||||
creatingNewLabel: 'Skapar ny {{label}}',
|
||||
currentlyEditing:
|
||||
'redigerar för närvarande detta dokument. Om du tar över kommer de att blockeras från att fortsätta redigera och kan också förlora osparade ändringar.',
|
||||
custom: 'Anpassad',
|
||||
dark: 'Mörk',
|
||||
dashboard: 'Manöverpanel',
|
||||
@@ -214,14 +217,17 @@ export const svTranslations: DefaultTranslationsObject = {
|
||||
descending: 'Fallande',
|
||||
deselectAllRows: 'Avmarkera alla rader',
|
||||
document: 'Dokument',
|
||||
documentLocked: 'Dokument låst',
|
||||
documents: 'Dokument',
|
||||
duplicate: 'Duplicera',
|
||||
duplicateWithoutSaving: 'Duplicera utan att spara ändringar',
|
||||
edit: 'Redigera',
|
||||
editedSince: 'Redigerad sedan',
|
||||
editing: 'Redigerar',
|
||||
editingLabel_many: 'Redigerar {{count}} {{label}}',
|
||||
editingLabel_one: 'Redigerar {{count}} {{label}}',
|
||||
editingLabel_other: 'Redigerar {{count}} {{label}}',
|
||||
editingTakenOver: 'Redigering övertagen',
|
||||
editLabel: 'Redigera {{label}}',
|
||||
email: 'E-post',
|
||||
emailAddress: 'E-postadress',
|
||||
@@ -234,6 +240,8 @@ export const svTranslations: DefaultTranslationsObject = {
|
||||
filters: 'Filter',
|
||||
filterWhere: 'Filtrera {{label}} där',
|
||||
globals: 'Globala',
|
||||
goBack: 'Gå tillbaka',
|
||||
isEditing: 'redigerar',
|
||||
language: 'Språk',
|
||||
lastModified: 'Senast Ändrad',
|
||||
leaveAnyway: 'Lämna ändå',
|
||||
@@ -289,6 +297,7 @@ export const svTranslations: DefaultTranslationsObject = {
|
||||
success: 'Framgång',
|
||||
successfullyCreated: '{{label}} skapades framgångsrikt.',
|
||||
successfullyDuplicated: '{{label}} duplicerades framgångsrikt.',
|
||||
takeOver: 'Ta över',
|
||||
thisLanguage: 'Svenska',
|
||||
titleDeleted: '{{label}} "{{title}}" togs bort framgångsrikt.',
|
||||
true: 'Sann',
|
||||
@@ -304,6 +313,7 @@ export const svTranslations: DefaultTranslationsObject = {
|
||||
username: 'Användarnamn',
|
||||
users: 'Användare',
|
||||
value: 'Värde',
|
||||
viewReadOnly: 'Visa endast läsning',
|
||||
welcome: 'Välkommen',
|
||||
},
|
||||
operators: {
|
||||
|
||||
@@ -175,6 +175,7 @@ export const thTranslations: DefaultTranslationsObject = {
|
||||
addFilter: 'เพิ่มการกรอง',
|
||||
adminTheme: 'ธีมผู้ดูแลระบบ',
|
||||
and: 'และ',
|
||||
anotherUserTakenOver: 'ผู้ใช้อื่นเข้าครอบครองการแก้ไขเอกสารนี้แล้ว',
|
||||
applyChanges: 'ใช้การเปลี่ยนแปลง',
|
||||
ascending: 'น้อยไปมาก',
|
||||
automatic: 'อัตโนมัติ',
|
||||
@@ -199,6 +200,8 @@ export const thTranslations: DefaultTranslationsObject = {
|
||||
createNewLabel: 'สร้าง {{label}} ใหม่',
|
||||
creating: 'กำลังสร้าง',
|
||||
creatingNewLabel: 'กำลังสร้าง {{label}} ใหม่',
|
||||
currentlyEditing:
|
||||
'กำลังแก้ไขเอกสารนี้อยู่ในขณะนี้ หากคุณเข้าครอบครอง พวกเขาจะถูกบล็อกจากการแก้ไขต่อไป และอาจสูญเสียการเปลี่ยนแปลงที่ไม่ได้บันทึก',
|
||||
custom: 'ที่ทำขึ้นเฉพาะ',
|
||||
dark: 'มืด',
|
||||
dashboard: 'แดชบอร์ด',
|
||||
@@ -210,14 +213,17 @@ export const thTranslations: DefaultTranslationsObject = {
|
||||
descending: 'มากไปน้อย',
|
||||
deselectAllRows: 'ยกเลิกการเลือกทุกแถว',
|
||||
document: 'เอกสาร',
|
||||
documentLocked: 'เอกสารถูกล็อค',
|
||||
documents: 'เอกสาร',
|
||||
duplicate: 'สำเนา',
|
||||
duplicateWithoutSaving: 'สำเนาโดยไม่บันทึกการแก้ไข',
|
||||
edit: 'แก้ไข',
|
||||
editedSince: 'แก้ไขตั้งแต่',
|
||||
editing: 'แก้ไข',
|
||||
editingLabel_many: 'กำลังแก้ไข {{count}} {{label}}',
|
||||
editingLabel_one: 'กำลังแก้ไข {{count}} {{label}}',
|
||||
editingLabel_other: 'กำลังแก้ไข {{count}} {{label}}',
|
||||
editingTakenOver: 'การแก้ไขถูกครอบครอง',
|
||||
editLabel: 'แก้ไข {{label}}',
|
||||
email: 'อีเมล',
|
||||
emailAddress: 'อีเมล',
|
||||
@@ -230,6 +236,8 @@ export const thTranslations: DefaultTranslationsObject = {
|
||||
filters: 'กรอง',
|
||||
filterWhere: 'กรอง {{label}} เฉพาะ',
|
||||
globals: 'Globals',
|
||||
goBack: 'กลับไป',
|
||||
isEditing: 'กำลังแก้ไข',
|
||||
language: 'ภาษา',
|
||||
lastModified: 'แก้ไขล่าสุดเมื่อ',
|
||||
leaveAnyway: 'ออกจากหน้านี้',
|
||||
@@ -285,6 +293,7 @@ export const thTranslations: DefaultTranslationsObject = {
|
||||
success: 'ความสำเร็จ',
|
||||
successfullyCreated: 'สร้าง {{label}} สำเร็จ',
|
||||
successfullyDuplicated: 'สำเนา {{label}} สำเร็จ',
|
||||
takeOver: 'เข้ายึด',
|
||||
thisLanguage: 'ไทย',
|
||||
titleDeleted: 'ลบ {{label}} "{{title}}" สำเร็จ',
|
||||
true: 'จริง',
|
||||
@@ -300,6 +309,7 @@ export const thTranslations: DefaultTranslationsObject = {
|
||||
username: 'ชื่อผู้ใช้',
|
||||
users: 'ผู้ใช้',
|
||||
value: 'ค่า',
|
||||
viewReadOnly: 'ดูในโหมดอ่านอย่างเดียว',
|
||||
welcome: 'ยินดีต้อนรับ',
|
||||
},
|
||||
operators: {
|
||||
|
||||
@@ -181,6 +181,7 @@ export const trTranslations: DefaultTranslationsObject = {
|
||||
addFilter: 'Filtre ekle',
|
||||
adminTheme: 'Admin arayüzü',
|
||||
and: 've',
|
||||
anotherUserTakenOver: 'Başka bir kullanıcı bu belgenin düzenlemesini devraldı.',
|
||||
applyChanges: 'Değişiklikleri Uygula',
|
||||
ascending: 'artan',
|
||||
automatic: 'Otomatik',
|
||||
@@ -206,6 +207,8 @@ export const trTranslations: DefaultTranslationsObject = {
|
||||
createNewLabel: 'Yeni bir {{label}} oluştur',
|
||||
creating: 'Oluşturuluyor',
|
||||
creatingNewLabel: 'Yeni bir {{label}} oluşturuluyor',
|
||||
currentlyEditing:
|
||||
'şu anda bu belgeyi düzenliyor. Devralırsanız, düzenlemeye devam etmeleri engellenecek ve kaydedilmemiş değişiklikleri de kaybedebilirler.',
|
||||
custom: 'Özel',
|
||||
dark: 'Karanlık',
|
||||
dashboard: 'Anasayfa',
|
||||
@@ -217,14 +220,17 @@ export const trTranslations: DefaultTranslationsObject = {
|
||||
descending: 'Azalan',
|
||||
deselectAllRows: 'Tüm satırların seçimini kaldır',
|
||||
document: 'Belge',
|
||||
documentLocked: 'Belge kilitlendi',
|
||||
documents: 'Belgeler',
|
||||
duplicate: 'Çoğalt',
|
||||
duplicateWithoutSaving: 'Ayarları kaydetmeden çoğalt',
|
||||
edit: 'Düzenle',
|
||||
editedSince: 'O tarihten itibaren düzenlendi',
|
||||
editing: 'Düzenleniyor',
|
||||
editingLabel_many: '{{count}} {{label}} düzenleniyor',
|
||||
editingLabel_one: '{{count}} {{label}} düzenleniyor',
|
||||
editingLabel_other: '{{count}} {{label}} düzenleniyor',
|
||||
editingTakenOver: 'Düzenleme devralındı',
|
||||
editLabel: '{{label}} düzenle',
|
||||
email: 'E-posta',
|
||||
emailAddress: 'E-posta adresi',
|
||||
@@ -237,6 +243,8 @@ export const trTranslations: DefaultTranslationsObject = {
|
||||
filters: 'Filtreler',
|
||||
filterWhere: '{{label}} filtrele:',
|
||||
globals: 'Globaller',
|
||||
goBack: 'Geri dön',
|
||||
isEditing: 'düzenliyor',
|
||||
language: 'Dil',
|
||||
lastModified: 'Son değiştirme',
|
||||
leaveAnyway: 'Yine de ayrıl',
|
||||
@@ -292,6 +300,7 @@ export const trTranslations: DefaultTranslationsObject = {
|
||||
success: 'Başarı',
|
||||
successfullyCreated: '{{label}} başarıyla oluşturuldu.',
|
||||
successfullyDuplicated: '{{label}} başarıyla kopyalandı.',
|
||||
takeOver: 'Devralmak',
|
||||
thisLanguage: 'Türkçe',
|
||||
titleDeleted: '{{label}} {{title}} başarıyla silindi.',
|
||||
true: 'Doğru',
|
||||
@@ -308,6 +317,7 @@ export const trTranslations: DefaultTranslationsObject = {
|
||||
username: 'Kullanıcı Adı',
|
||||
users: 'kullanıcı',
|
||||
value: 'Değer',
|
||||
viewReadOnly: 'Salt okunur olarak görüntüle',
|
||||
welcome: 'Hoşgeldiniz',
|
||||
},
|
||||
operators: {
|
||||
|
||||
@@ -179,6 +179,7 @@ export const ukTranslations: DefaultTranslationsObject = {
|
||||
addFilter: 'Додати фільтр',
|
||||
adminTheme: 'Тема адмін панелі',
|
||||
and: 'і',
|
||||
anotherUserTakenOver: 'Інший користувач взяв на себе редагування цього документа.',
|
||||
applyChanges: 'Застосувати зміни',
|
||||
ascending: 'В порядку зростання',
|
||||
automatic: 'Автоматично',
|
||||
@@ -203,6 +204,8 @@ export const ukTranslations: DefaultTranslationsObject = {
|
||||
createNewLabel: 'Створити новий {{label}}',
|
||||
creating: 'Створення',
|
||||
creatingNewLabel: 'Створення нового {{label}}',
|
||||
currentlyEditing:
|
||||
'зараз редагує цей документ. Якщо ви переберете контроль, їм буде заблоковано продовження редагування, і вони також можуть втратити незбережені зміни.',
|
||||
custom: 'Спеціальне замовлення',
|
||||
dark: 'Темна',
|
||||
dashboard: 'Головна',
|
||||
@@ -214,14 +217,17 @@ export const ukTranslations: DefaultTranslationsObject = {
|
||||
descending: 'В порядку спадання',
|
||||
deselectAllRows: 'Скасувати вибір всіх рядків',
|
||||
document: 'Документ',
|
||||
documentLocked: 'Документ заблоковано',
|
||||
documents: 'Документи',
|
||||
duplicate: 'Дублювати',
|
||||
duplicateWithoutSaving: 'Дублювання без збереження змін',
|
||||
edit: 'Редагувати',
|
||||
editedSince: 'Відредаговано з',
|
||||
editing: 'Редагування',
|
||||
editingLabel_many: 'Редагування {{count}} {{label}}',
|
||||
editingLabel_one: 'Редагування {{count}} {{label}}',
|
||||
editingLabel_other: 'Редагування {{count}} {{label}}',
|
||||
editingTakenOver: 'Редагування взято на себе',
|
||||
editLabel: 'Редагувати {{label}}',
|
||||
email: 'Електронна пошта',
|
||||
emailAddress: 'Адреса електронної пошти',
|
||||
@@ -234,6 +240,8 @@ export const ukTranslations: DefaultTranslationsObject = {
|
||||
filters: 'Фільтри',
|
||||
filterWhere: 'Де фільтрувати {{label}}',
|
||||
globals: 'Глобальні',
|
||||
goBack: 'Повернутися',
|
||||
isEditing: 'редагує',
|
||||
language: 'Мова',
|
||||
lastModified: 'Останні зміни',
|
||||
leaveAnyway: 'Все одно вийти',
|
||||
@@ -289,6 +297,7 @@ export const ukTranslations: DefaultTranslationsObject = {
|
||||
success: 'Успіх',
|
||||
successfullyCreated: '{{label}} успішно створено.',
|
||||
successfullyDuplicated: '{{label}} успішно продубльовано.',
|
||||
takeOver: 'Перейняти',
|
||||
thisLanguage: 'Українська',
|
||||
titleDeleted: '{{label}} "{{title}}" успішно видалено.',
|
||||
true: 'Правда',
|
||||
@@ -304,6 +313,7 @@ export const ukTranslations: DefaultTranslationsObject = {
|
||||
username: "Ім'я користувача",
|
||||
users: 'Користувачі',
|
||||
value: 'Значення',
|
||||
viewReadOnly: 'Перегляд тільки для читання',
|
||||
welcome: 'Вітаю',
|
||||
},
|
||||
operators: {
|
||||
|
||||
@@ -177,6 +177,7 @@ export const viTranslations: DefaultTranslationsObject = {
|
||||
addFilter: 'Thêm bộ lọc',
|
||||
adminTheme: 'Giao diện bảng điều khiển',
|
||||
and: 'Và',
|
||||
anotherUserTakenOver: 'Người dùng khác đã tiếp quản việc chỉnh sửa tài liệu này.',
|
||||
applyChanges: 'Áp dụng Thay đổi',
|
||||
ascending: 'Sắp xếp theo thứ tự tăng dần',
|
||||
automatic: 'Tự động',
|
||||
@@ -201,6 +202,8 @@ export const viTranslations: DefaultTranslationsObject = {
|
||||
createNewLabel: 'Tạo mới {{label}}',
|
||||
creating: 'Đang tạo',
|
||||
creatingNewLabel: 'Đang tạo mới {{label}}',
|
||||
currentlyEditing:
|
||||
'hiện đang chỉnh sửa tài liệu này. Nếu bạn tiếp quản, họ sẽ bị chặn tiếp tục chỉnh sửa và cũng có thể mất các thay đổi chưa lưu.',
|
||||
custom: 'Tùy chỉnh',
|
||||
dark: 'Nền tối',
|
||||
dashboard: 'Bảng điều khiển',
|
||||
@@ -212,14 +215,17 @@ export const viTranslations: DefaultTranslationsObject = {
|
||||
descending: 'Xếp theo thứ tự giảm dần',
|
||||
deselectAllRows: 'Bỏ chọn tất cả các hàng',
|
||||
document: 'Tài liệu',
|
||||
documentLocked: 'Tài liệu bị khóa',
|
||||
documents: 'Tài liệu',
|
||||
duplicate: 'Tạo bản sao',
|
||||
duplicateWithoutSaving: 'Không lưu dữ liệu và tạo bản sao',
|
||||
edit: 'Chỉnh sửa',
|
||||
editedSince: 'Được chỉnh sửa từ',
|
||||
editing: 'Đang chỉnh sửa',
|
||||
editingLabel_many: 'Đang chỉnh sửa {{count}} {{label}}',
|
||||
editingLabel_one: 'Đang chỉnh sửa {{count}} {{label}}',
|
||||
editingLabel_other: 'Đang chỉnh sửa {{count}} {{label}}',
|
||||
editingTakenOver: 'Chỉnh sửa đã được tiếp quản',
|
||||
editLabel: 'Chỉnh sửa: {{label}}',
|
||||
email: 'Email',
|
||||
emailAddress: 'Địa chỉ Email',
|
||||
@@ -232,6 +238,8 @@ export const viTranslations: DefaultTranslationsObject = {
|
||||
filters: 'Bộ lọc',
|
||||
filterWhere: 'Lọc {{label}} với điều kiện:',
|
||||
globals: 'Toàn thể (globals)',
|
||||
goBack: 'Quay lại',
|
||||
isEditing: 'đang chỉnh sửa',
|
||||
language: 'Ngôn ngữ',
|
||||
lastModified: 'Chỉnh sửa lần cuối vào lúc',
|
||||
leaveAnyway: 'Tiếp tục thoát',
|
||||
@@ -287,6 +295,7 @@ export const viTranslations: DefaultTranslationsObject = {
|
||||
success: 'Thành công',
|
||||
successfullyCreated: '{{label}} đã được tạo thành công.',
|
||||
successfullyDuplicated: '{{label}} đã được sao chép thành công.',
|
||||
takeOver: 'Tiếp quản',
|
||||
thisLanguage: 'Vietnamese (Tiếng Việt)',
|
||||
titleDeleted: '{{label}} {{title}} đã được xóa thành công.',
|
||||
true: 'Thật',
|
||||
@@ -302,6 +311,7 @@ export const viTranslations: DefaultTranslationsObject = {
|
||||
username: 'Tên đăng nhập',
|
||||
users: 'Người dùng',
|
||||
value: 'Giá trị',
|
||||
viewReadOnly: 'Xem chỉ đọc',
|
||||
welcome: 'Xin chào',
|
||||
},
|
||||
operators: {
|
||||
|
||||
@@ -172,6 +172,7 @@ export const zhTranslations: DefaultTranslationsObject = {
|
||||
addFilter: '添加过滤器',
|
||||
adminTheme: '管理页面主题',
|
||||
and: '和',
|
||||
anotherUserTakenOver: '另一位用户接管了此文档的编辑。',
|
||||
applyChanges: '应用更改',
|
||||
ascending: '升序',
|
||||
automatic: '自动',
|
||||
@@ -196,6 +197,8 @@ export const zhTranslations: DefaultTranslationsObject = {
|
||||
createNewLabel: '创建新的{{label}}',
|
||||
creating: '创建中',
|
||||
creatingNewLabel: '正在创建新的{{label}}',
|
||||
currentlyEditing:
|
||||
'当前正在编辑此文档。如果您接管,他们将无法继续编辑,并且可能会丢失未保存的更改。',
|
||||
custom: '定制',
|
||||
dark: '深色',
|
||||
dashboard: '仪表板',
|
||||
@@ -207,14 +210,17 @@ export const zhTranslations: DefaultTranslationsObject = {
|
||||
descending: '降序',
|
||||
deselectAllRows: '取消选择所有行',
|
||||
document: '文件',
|
||||
documentLocked: '文档已锁定',
|
||||
documents: '文件',
|
||||
duplicate: '重复',
|
||||
duplicateWithoutSaving: '重复而不保存更改。',
|
||||
edit: '编辑',
|
||||
editedSince: '自...以来编辑',
|
||||
editing: '编辑中',
|
||||
editingLabel_many: '编辑 {{count}} {{label}}',
|
||||
editingLabel_one: '编辑 {{count}} {{label}}',
|
||||
editingLabel_other: '编辑 {{count}} {{label}}',
|
||||
editingTakenOver: '编辑已被接管',
|
||||
editLabel: '编辑{{label}}',
|
||||
email: '电子邮件',
|
||||
emailAddress: '电子邮件地址',
|
||||
@@ -227,6 +233,8 @@ export const zhTranslations: DefaultTranslationsObject = {
|
||||
filters: '过滤器',
|
||||
filterWhere: '过滤{{label}}',
|
||||
globals: '全局',
|
||||
goBack: '返回',
|
||||
isEditing: '正在编辑',
|
||||
language: '语言',
|
||||
lastModified: '最后修改',
|
||||
leaveAnyway: '无论如何都要离开',
|
||||
@@ -281,6 +289,7 @@ export const zhTranslations: DefaultTranslationsObject = {
|
||||
success: '成功',
|
||||
successfullyCreated: '成功创建{{label}}',
|
||||
successfullyDuplicated: '成功复制{{label}}',
|
||||
takeOver: '接管',
|
||||
thisLanguage: '中文 (简体)',
|
||||
titleDeleted: '{{label}} "{{title}}"已被成功删除。',
|
||||
true: '真实',
|
||||
@@ -296,6 +305,7 @@ export const zhTranslations: DefaultTranslationsObject = {
|
||||
username: '用户名',
|
||||
users: '用户',
|
||||
value: '值',
|
||||
viewReadOnly: '只读查看',
|
||||
welcome: '欢迎',
|
||||
},
|
||||
operators: {
|
||||
|
||||
@@ -172,6 +172,7 @@ export const zhTwTranslations: DefaultTranslationsObject = {
|
||||
addFilter: '新增過濾器',
|
||||
adminTheme: '管理頁面主題',
|
||||
and: '和',
|
||||
anotherUserTakenOver: '另一位使用者接管了此文件的編輯。',
|
||||
applyChanges: '套用更改',
|
||||
ascending: '升冪',
|
||||
automatic: '自動',
|
||||
@@ -196,6 +197,8 @@ export const zhTwTranslations: DefaultTranslationsObject = {
|
||||
createNewLabel: '建立新的{{label}}',
|
||||
creating: '建立中',
|
||||
creatingNewLabel: '正在建立新的{{label}}',
|
||||
currentlyEditing:
|
||||
'目前正在編輯此文件。如果您接管,他們將無法繼續編輯,並且可能會丟失未保存的更改。',
|
||||
custom: '自訂',
|
||||
dark: '深色',
|
||||
dashboard: '控制面板',
|
||||
@@ -207,14 +210,17 @@ export const zhTwTranslations: DefaultTranslationsObject = {
|
||||
descending: '降冪',
|
||||
deselectAllRows: '取消選擇全部',
|
||||
document: '文件',
|
||||
documentLocked: '文件已鎖定',
|
||||
documents: '文件',
|
||||
duplicate: '複製',
|
||||
duplicateWithoutSaving: '複製而不儲存變更。',
|
||||
edit: '編輯',
|
||||
editedSince: '自...以來編輯',
|
||||
editing: '編輯中',
|
||||
editingLabel_many: '編輯 {{count}} 個 {{label}}',
|
||||
editingLabel_one: '編輯 {{count}} 個 {{label}}',
|
||||
editingLabel_other: '編輯 {{count}} 個 {{label}}',
|
||||
editingTakenOver: '編輯已被接管',
|
||||
editLabel: '編輯{{label}}',
|
||||
email: '電子郵件',
|
||||
emailAddress: '電子郵件地址',
|
||||
@@ -227,6 +233,8 @@ export const zhTwTranslations: DefaultTranslationsObject = {
|
||||
filters: '過濾器',
|
||||
filterWhere: '過濾{{label}}',
|
||||
globals: '全域',
|
||||
goBack: '返回',
|
||||
isEditing: '正在編輯',
|
||||
language: '語言',
|
||||
lastModified: '最後修改',
|
||||
leaveAnyway: '無論如何都要離開',
|
||||
@@ -281,6 +289,7 @@ export const zhTwTranslations: DefaultTranslationsObject = {
|
||||
success: '成功',
|
||||
successfullyCreated: '成功建立{{label}}',
|
||||
successfullyDuplicated: '成功複製{{label}}',
|
||||
takeOver: '接管',
|
||||
thisLanguage: '中文 (繁體)',
|
||||
titleDeleted: '{{label}} "{{title}}"已被成功刪除。',
|
||||
true: '真實',
|
||||
@@ -296,6 +305,7 @@ export const zhTwTranslations: DefaultTranslationsObject = {
|
||||
username: '使用者名稱',
|
||||
users: '使用者',
|
||||
value: '值',
|
||||
viewReadOnly: '僅檢視',
|
||||
welcome: '歡迎',
|
||||
},
|
||||
operators: {
|
||||
|
||||
@@ -114,7 +114,7 @@ export function EditForm({ submitted }: EditFormProps) {
|
||||
const onChange: NonNullable<FormProps['onChange']>[0] = useCallback(
|
||||
async ({ formState: prevFormState }) => {
|
||||
const docPreferences = await getDocPreferences()
|
||||
const newFormState = await getFormState({
|
||||
const { state: newFormState } = await getFormState({
|
||||
apiRoute,
|
||||
body: {
|
||||
collectionSlug,
|
||||
|
||||
@@ -152,7 +152,7 @@ export function FormsManagerProvider({ children }: FormsManagerProps) {
|
||||
}
|
||||
|
||||
try {
|
||||
const formStateWithoutFiles = await getFormState({
|
||||
const { state: formStateWithoutFiles } = await getFormState({
|
||||
apiRoute: config.routes.api,
|
||||
body: {
|
||||
collectionSlug,
|
||||
|
||||
@@ -53,6 +53,14 @@
|
||||
}
|
||||
}
|
||||
|
||||
&__locked-controls.locked {
|
||||
position: unset;
|
||||
|
||||
.tooltip {
|
||||
top: calc(var(--base) * -0.5);
|
||||
}
|
||||
}
|
||||
|
||||
&__list-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import type {
|
||||
ClientCollectionConfig,
|
||||
ClientGlobalConfig,
|
||||
ClientUser,
|
||||
CollectionPermission,
|
||||
GlobalPermission,
|
||||
SanitizedCollectionConfig,
|
||||
@@ -18,9 +19,11 @@ import { useTranslation } from '../../providers/Translation/index.js'
|
||||
import { formatAdminURL } from '../../utilities/formatAdminURL.js'
|
||||
import { formatDate } from '../../utilities/formatDate.js'
|
||||
import { Autosave } from '../Autosave/index.js'
|
||||
import { Button } from '../Button/index.js'
|
||||
import { DeleteDocument } from '../DeleteDocument/index.js'
|
||||
import { DuplicateDocument } from '../DuplicateDocument/index.js'
|
||||
import { Gutter } from '../Gutter/index.js'
|
||||
import { Locked } from '../Locked/index.js'
|
||||
import { Popup, PopupList } from '../Popup/index.js'
|
||||
import { PreviewButton } from '../PreviewButton/index.js'
|
||||
import { PublishButton } from '../PublishButton/index.js'
|
||||
@@ -46,10 +49,13 @@ export const DocumentControls: React.FC<{
|
||||
/* Only available if `redirectAfterDuplicate` is `false` */
|
||||
readonly onDuplicate?: DocumentInfoContext['onDuplicate']
|
||||
readonly onSave?: DocumentInfoContext['onSave']
|
||||
readonly onTakeOver?: () => void
|
||||
readonly permissions: CollectionPermission | GlobalPermission | null
|
||||
readonly readOnlyForIncomingUser?: boolean
|
||||
readonly redirectAfterDelete?: boolean
|
||||
readonly redirectAfterDuplicate?: boolean
|
||||
readonly slug: SanitizedCollectionConfig['slug']
|
||||
readonly user?: ClientUser
|
||||
}> = (props) => {
|
||||
const {
|
||||
id,
|
||||
@@ -63,12 +69,15 @@ export const DocumentControls: React.FC<{
|
||||
onDelete,
|
||||
onDrawerCreate,
|
||||
onDuplicate,
|
||||
onTakeOver,
|
||||
permissions,
|
||||
readOnlyForIncomingUser,
|
||||
redirectAfterDelete,
|
||||
redirectAfterDuplicate,
|
||||
user,
|
||||
} = props
|
||||
|
||||
const { i18n } = useTranslation()
|
||||
const { i18n, t } = useTranslation()
|
||||
|
||||
const editDepth = useEditDepth()
|
||||
|
||||
@@ -125,6 +134,9 @@ export const DocumentControls: React.FC<{
|
||||
</p>
|
||||
</li>
|
||||
)}
|
||||
{user && readOnlyForIncomingUser && (
|
||||
<Locked className={`${baseClass}__locked-controls`} user={user} />
|
||||
)}
|
||||
{(collectionConfig?.versions?.drafts || globalConfig?.versions?.drafts) && (
|
||||
<Fragment>
|
||||
{(globalConfig || (collectionConfig && isEditing)) && (
|
||||
@@ -219,6 +231,17 @@ export const DocumentControls: React.FC<{
|
||||
)}
|
||||
</React.Fragment>
|
||||
)}
|
||||
{user && readOnlyForIncomingUser && (
|
||||
<Button
|
||||
buttonStyle="secondary"
|
||||
id="take-over"
|
||||
onClick={() => void onTakeOver()}
|
||||
size="medium"
|
||||
type="button"
|
||||
>
|
||||
{t('general:takeOver')}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
{showDotMenu && (
|
||||
<Popup
|
||||
|
||||
@@ -126,7 +126,7 @@ export const EditMany: React.FC<EditManyProps> = (props) => {
|
||||
React.useEffect(() => {
|
||||
if (!hasInitializedState.current) {
|
||||
const getInitialState = async () => {
|
||||
const result = await getFormState({
|
||||
const { state: result } = await getFormState({
|
||||
apiRoute,
|
||||
body: {
|
||||
collectionSlug: slug,
|
||||
@@ -146,8 +146,8 @@ export const EditMany: React.FC<EditManyProps> = (props) => {
|
||||
}, [apiRoute, hasInitializedState, serverURL, slug])
|
||||
|
||||
const onChange: FormProps['onChange'][0] = useCallback(
|
||||
({ formState: prevFormState }) =>
|
||||
getFormState({
|
||||
async ({ formState: prevFormState }) => {
|
||||
const { state } = await getFormState({
|
||||
apiRoute,
|
||||
body: {
|
||||
collectionSlug: slug,
|
||||
@@ -156,7 +156,10 @@ export const EditMany: React.FC<EditManyProps> = (props) => {
|
||||
schemaPath: slug,
|
||||
},
|
||||
serverURL,
|
||||
}),
|
||||
})
|
||||
|
||||
return state
|
||||
},
|
||||
[serverURL, apiRoute, slug],
|
||||
)
|
||||
|
||||
|
||||
14
packages/ui/src/elements/Locked/index.scss
Normal file
14
packages/ui/src/elements/Locked/index.scss
Normal file
@@ -0,0 +1,14 @@
|
||||
@import '../../scss/styles.scss';
|
||||
|
||||
.locked {
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
pointer-events: all;
|
||||
|
||||
&__tooltip {
|
||||
left: 0;
|
||||
transform: translate3d(-0%, calc(var(--caret-size) * -1), 0);
|
||||
}
|
||||
}
|
||||
38
packages/ui/src/elements/Locked/index.tsx
Normal file
38
packages/ui/src/elements/Locked/index.tsx
Normal file
@@ -0,0 +1,38 @@
|
||||
'use client'
|
||||
import type { ClientUser } from 'payload'
|
||||
|
||||
import React, { useState } from 'react'
|
||||
|
||||
import { useTableCell } from '../../elements/Table/TableCellProvider/index.js'
|
||||
import { LockIcon } from '../../icons/Lock/index.js'
|
||||
import { useTranslation } from '../../providers/Translation/index.js'
|
||||
import { Tooltip } from '../Tooltip/index.js'
|
||||
import './index.scss'
|
||||
|
||||
const baseClass = 'locked'
|
||||
|
||||
export const Locked: React.FC<{ className?: string; user: ClientUser }> = ({ className, user }) => {
|
||||
const { rowData } = useTableCell()
|
||||
const [hovered, setHovered] = useState(false)
|
||||
const { t } = useTranslation()
|
||||
|
||||
const userToUse = user ? (user?.email ?? user?.id) : rowData?.id
|
||||
|
||||
return (
|
||||
<div
|
||||
className={[baseClass, className].filter(Boolean).join(' ')}
|
||||
onMouseEnter={() => setHovered(true)}
|
||||
onMouseLeave={() => setHovered(false)}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
>
|
||||
<Tooltip
|
||||
alignCaret="left"
|
||||
className={`${baseClass}__tooltip`}
|
||||
position="top"
|
||||
show={hovered}
|
||||
>{`${userToUse} ${t('general:isEditing')}`}</Tooltip>
|
||||
<LockIcon />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import React from 'react'
|
||||
import { useTableCell } from '../../elements/Table/TableCellProvider/index.js'
|
||||
import { CheckboxInput } from '../../fields/Checkbox/Input.js'
|
||||
import { useSelection } from '../../providers/Selection/index.js'
|
||||
import { Locked } from '../Locked/index.js'
|
||||
import './index.scss'
|
||||
|
||||
const baseClass = 'select-row'
|
||||
@@ -11,6 +12,13 @@ const baseClass = 'select-row'
|
||||
export const SelectRow: React.FC = () => {
|
||||
const { selected, setSelection } = useSelection()
|
||||
const { rowData } = useTableCell()
|
||||
const { isLocked, userEditing } = rowData || {}
|
||||
|
||||
const documentIsLocked = isLocked && userEditing
|
||||
|
||||
if (documentIsLocked) {
|
||||
return <Locked user={userEditing} />
|
||||
}
|
||||
|
||||
return (
|
||||
<CheckboxInput
|
||||
|
||||
@@ -53,7 +53,7 @@
|
||||
}
|
||||
|
||||
&--position-top {
|
||||
top: calc(var(--base) * -0.6 - 13px);
|
||||
top: calc(var(--base) * -1.25);
|
||||
transform: translate3d(-50%, calc(var(--caret-size) * -1), 0);
|
||||
|
||||
&::after {
|
||||
@@ -63,7 +63,7 @@
|
||||
}
|
||||
|
||||
&--position-bottom {
|
||||
bottom: calc(var(--base) * -0.6 - 13px);
|
||||
bottom: calc(var(--base) * -1.25);
|
||||
transform: translate3d(-50%, var(--caret-size), 0);
|
||||
|
||||
&::after {
|
||||
|
||||
@@ -10,6 +10,7 @@ export type Props = {
|
||||
children: React.ReactNode
|
||||
className?: string
|
||||
delay?: number
|
||||
position?: 'bottom' | 'top'
|
||||
show?: boolean
|
||||
/**
|
||||
* If the tooltip position should not change depending on if the toolbar is outside the boundingRef. @default false
|
||||
@@ -24,6 +25,7 @@ export const Tooltip: React.FC<Props> = (props) => {
|
||||
children,
|
||||
className,
|
||||
delay = 350,
|
||||
position: positionFromProps,
|
||||
show: showFromProps = true,
|
||||
staticPositioning = false,
|
||||
} = props
|
||||
@@ -89,7 +91,7 @@ export const Tooltip: React.FC<Props> = (props) => {
|
||||
className,
|
||||
show && 'tooltip--show',
|
||||
`tooltip--caret-${alignCaret}`,
|
||||
`tooltip--position-${position}`,
|
||||
`tooltip--position-${positionFromProps || position}`,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(' ')}
|
||||
|
||||
@@ -52,6 +52,7 @@ export { GenerateConfirmation } from '../../elements/GenerateConfirmation/index.
|
||||
export { Gutter } from '../../elements/Gutter/index.js'
|
||||
export { Hamburger } from '../../elements/Hamburger/index.js'
|
||||
export { HydrateAuthProvider } from '../../elements/HydrateAuthProvider/index.js'
|
||||
export { Locked } from '../../elements/Locked/index.js'
|
||||
export { ListControls } from '../../elements/ListControls/index.js'
|
||||
export { useListDrawer } from '../../elements/ListDrawer/index.js'
|
||||
export { ListSelection } from '../../elements/ListSelection/index.js'
|
||||
|
||||
@@ -451,7 +451,7 @@ export const Form: React.FC<FormProps> = (props) => {
|
||||
|
||||
const reset = useCallback(
|
||||
async (data: unknown) => {
|
||||
const newState = await getFormState({
|
||||
const { state: newState } = await getFormState({
|
||||
apiRoute,
|
||||
body: {
|
||||
id,
|
||||
@@ -482,7 +482,7 @@ export const Form: React.FC<FormProps> = (props) => {
|
||||
|
||||
const getFieldStateBySchemaPath = useCallback(
|
||||
async ({ data, schemaPath }) => {
|
||||
const fieldSchema = await getFormState({
|
||||
const { state: fieldSchema } = await getFormState({
|
||||
apiRoute,
|
||||
body: {
|
||||
collectionSlug,
|
||||
|
||||
@@ -53,11 +53,14 @@ export const RenderField: React.FC<Props> = ({
|
||||
return null
|
||||
}
|
||||
|
||||
// Combine readOnlyFromContext with the readOnly prop passed down from RenderFields
|
||||
const isReadOnly = fieldComponentProps.readOnly ?? readOnlyFromContext
|
||||
|
||||
// `admin.readOnly` displays the value but prevents the field from being edited
|
||||
fieldComponentProps.readOnly = fieldComponentProps?.field?.admin?.readOnly
|
||||
|
||||
// if parent field is `readOnly: true`, but this field is `readOnly: false`, the field should still be editable
|
||||
if (readOnlyFromContext && fieldComponentProps.readOnly !== false) {
|
||||
if (isReadOnly && fieldComponentProps.readOnly !== false) {
|
||||
fieldComponentProps.readOnly = true
|
||||
}
|
||||
|
||||
|
||||
@@ -13,8 +13,17 @@ const baseClass = 'render-fields'
|
||||
export { Props }
|
||||
|
||||
export const RenderFields: React.FC<Props> = (props) => {
|
||||
const { className, fields, forceRender, indexPath, margins, path, permissions, schemaPath } =
|
||||
props
|
||||
const {
|
||||
className,
|
||||
fields,
|
||||
forceRender,
|
||||
indexPath,
|
||||
margins,
|
||||
path,
|
||||
permissions,
|
||||
readOnly,
|
||||
schemaPath,
|
||||
} = props
|
||||
|
||||
const { i18n } = useTranslation()
|
||||
const [hasRendered, setHasRendered] = React.useState(Boolean(forceRender))
|
||||
@@ -65,7 +74,7 @@ export const RenderFields: React.FC<Props> = (props) => {
|
||||
|
||||
return (
|
||||
<RenderField
|
||||
fieldComponentProps={{ field, forceRender: forceRenderChildren }}
|
||||
fieldComponentProps={{ field, forceRender: forceRenderChildren, readOnly }}
|
||||
indexPath={indexPath !== undefined ? `${indexPath}.${fieldIndex}` : `${fieldIndex}`}
|
||||
key={fieldIndex}
|
||||
name={name}
|
||||
|
||||
@@ -29,7 +29,9 @@ export type BuildFormStateArgs = {
|
||||
id?: number | string
|
||||
locale?: string
|
||||
operation?: 'create' | 'update'
|
||||
returnLockStatus?: boolean
|
||||
schemaPath: string
|
||||
updateLastEdited?: boolean
|
||||
}
|
||||
|
||||
export const buildStateFromSchema = async (args: Args): Promise<FormState> => {
|
||||
|
||||
8
packages/ui/src/icons/Lock/index.scss
Normal file
8
packages/ui/src/icons/Lock/index.scss
Normal file
@@ -0,0 +1,8 @@
|
||||
@import '../../scss/styles';
|
||||
|
||||
.icon--lock {
|
||||
.stroke {
|
||||
stroke: currentColor;
|
||||
stroke-width: $style-stroke-width;
|
||||
}
|
||||
}
|
||||
29
packages/ui/src/icons/Lock/index.tsx
Normal file
29
packages/ui/src/icons/Lock/index.tsx
Normal file
@@ -0,0 +1,29 @@
|
||||
import React from 'react'
|
||||
|
||||
import './index.scss'
|
||||
|
||||
export const LockIcon: React.FC<{ className?: string }> = ({ className }) => (
|
||||
<svg
|
||||
className={['icon icon--lock', className].filter(Boolean).join(' ')}
|
||||
fill="none"
|
||||
height="20"
|
||||
viewBox="0 0 20 20"
|
||||
width="20"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
className="stroke"
|
||||
d="M7.5 9.5V7.5C7.5 6.83696 7.76339 6.20107 8.23223 5.73223C8.70107 5.26339 9.33696 5 10 5C10.663 5 11.2989 5.26339 11.7678 5.73223C12.2366 6.20107 12.5 6.83696 12.5 7.5V9.5"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeOpacity="1"
|
||||
/>
|
||||
<path
|
||||
className="stroke"
|
||||
d="M13.5 9.5H6.5C5.94772 9.5 5.5 9.94772 5.5 10.5V14C5.5 14.5523 5.94772 15 6.5 15H13.5C14.0523 15 14.5 14.5523 14.5 14V10.5C14.5 9.94772 14.0523 9.5 13.5 9.5Z"
|
||||
stopOpacity="1"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
@@ -1,5 +1,6 @@
|
||||
'use client'
|
||||
import type {
|
||||
ClientUser,
|
||||
Data,
|
||||
DocumentPermissions,
|
||||
DocumentPreferences,
|
||||
@@ -18,6 +19,7 @@ import React, { createContext, useCallback, useContext, useEffect, useRef, useSt
|
||||
|
||||
import type { DocumentInfoContext, DocumentInfoProps } from './types.js'
|
||||
|
||||
import { requests } from '../../utilities/api.js'
|
||||
import { formatDocTitle } from '../../utilities/formatDocTitle.js'
|
||||
import { getFormState } from '../../utilities/getFormState.js'
|
||||
import { hasSavePermission as getHasSavePermission } from '../../utilities/hasSavePermission.js'
|
||||
@@ -67,6 +69,10 @@ const DocumentInfo: React.FC<
|
||||
const globalConfig = globals.find((g) => g.slug === globalSlug)
|
||||
const docConfig = collectionConfig || globalConfig
|
||||
|
||||
const lockDocumentsProp = docConfig?.lockDocuments !== undefined ? docConfig?.lockDocuments : true
|
||||
|
||||
const isLockingEnabled = lockDocumentsProp !== false
|
||||
|
||||
const { i18n } = useTranslation()
|
||||
|
||||
const { uploadEdits } = useUploadEdits()
|
||||
@@ -97,6 +103,10 @@ const DocumentInfo: React.FC<
|
||||
const [hasPublishPermission, setHasPublishPermission] = useState<boolean>(
|
||||
hasPublishPermissionFromProps,
|
||||
)
|
||||
|
||||
const [documentIsLocked, setDocumentIsLocked] = useState<boolean | undefined>(false)
|
||||
const [currentEditor, setCurrentEditor] = useState<ClientUser | null>(null)
|
||||
|
||||
const isInitializing = initialState === undefined || data === undefined
|
||||
const [unpublishedVersions, setUnpublishedVersions] =
|
||||
useState<PaginatedDocs<TypeWithVersion<any>>>(null)
|
||||
@@ -106,8 +116,6 @@ const DocumentInfo: React.FC<
|
||||
const { code: locale } = useLocale()
|
||||
const prevLocale = useRef(locale)
|
||||
const hasInitializedDocPermissions = useRef(false)
|
||||
// Separate locale cache used for handling permissions
|
||||
const prevLocalePermissions = useRef(locale)
|
||||
|
||||
const versionsConfig = docConfig?.versions
|
||||
|
||||
@@ -135,6 +143,109 @@ const DocumentInfo: React.FC<
|
||||
const operation = isEditing ? 'update' : 'create'
|
||||
const shouldFetchVersions = Boolean(versionsConfig && docPermissions?.readVersions?.permission)
|
||||
|
||||
const unlockDocument = useCallback(
|
||||
async (docId: number | string, slug: string) => {
|
||||
try {
|
||||
const isGlobal = slug === globalSlug
|
||||
|
||||
const query = isGlobal
|
||||
? `where[globalSlug][equals]=${slug}`
|
||||
: `where[document.value][equals]=${docId}&where[document.relationTo][equals]=${slug}`
|
||||
|
||||
const request = await requests.get(`${serverURL}${api}/payload-locked-documents?${query}`)
|
||||
|
||||
const { docs } = await request.json()
|
||||
|
||||
if (docs.length > 0) {
|
||||
const lockId = docs[0].id
|
||||
await requests.delete(`${serverURL}${api}/payload-locked-documents/${lockId}`, {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
})
|
||||
setDocumentIsLocked(false)
|
||||
}
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error('Failed to unlock the document', error)
|
||||
}
|
||||
},
|
||||
[serverURL, api, globalSlug],
|
||||
)
|
||||
|
||||
const updateDocumentEditor = useCallback(
|
||||
async (docId: number | string, slug: string, user: ClientUser) => {
|
||||
try {
|
||||
const isGlobal = slug === globalSlug
|
||||
|
||||
const query = isGlobal
|
||||
? `where[globalSlug][equals]=${slug}`
|
||||
: `where[document.value][equals]=${docId}&where[document.relationTo][equals]=${slug}`
|
||||
|
||||
// Check if the document is already locked
|
||||
const request = await requests.get(`${serverURL}${api}/payload-locked-documents?${query}`)
|
||||
|
||||
const { docs } = await request.json()
|
||||
|
||||
if (docs.length > 0) {
|
||||
const lockId = docs[0].id
|
||||
|
||||
// Send a patch request to update the _lastEdited info
|
||||
await requests.patch(`${serverURL}${api}/payload-locked-documents/${lockId}`, {
|
||||
body: JSON.stringify({
|
||||
_lastEdited: {
|
||||
editedAt: new Date(),
|
||||
user: { relationTo: user?.collection, value: user?.id },
|
||||
},
|
||||
}),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error('Failed to update the document editor', error)
|
||||
}
|
||||
},
|
||||
[serverURL, api, globalSlug],
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
if (!isLockingEnabled || (!id && !globalSlug)) {
|
||||
return
|
||||
}
|
||||
|
||||
const fetchDocumentLockState = async () => {
|
||||
if (id || globalSlug) {
|
||||
try {
|
||||
const slug = collectionSlug ?? globalSlug
|
||||
const isGlobal = slug === globalSlug
|
||||
|
||||
const query = isGlobal
|
||||
? `where[globalSlug][equals]=${slug}`
|
||||
: `where[document.value][equals]=${id}&where[document.relationTo][equals]=${slug}`
|
||||
|
||||
const request = await requests.get(`${serverURL}${api}/payload-locked-documents?${query}`)
|
||||
const { docs } = await request.json()
|
||||
|
||||
if (docs.length > 0) {
|
||||
const newEditor = docs[0]._lastEdited?.user?.value
|
||||
if (newEditor && newEditor.id !== currentEditor?.id) {
|
||||
setCurrentEditor(newEditor)
|
||||
setDocumentIsLocked(true)
|
||||
}
|
||||
} else {
|
||||
setDocumentIsLocked(false)
|
||||
}
|
||||
} catch (error) {
|
||||
// swallow error
|
||||
}
|
||||
}
|
||||
}
|
||||
void fetchDocumentLockState()
|
||||
}, [id, serverURL, api, collectionSlug, globalSlug, currentEditor, isLockingEnabled])
|
||||
|
||||
const getVersions = useCallback(async () => {
|
||||
let versionFetchURL
|
||||
let publishedFetchURL
|
||||
@@ -389,7 +500,7 @@ const DocumentInfo: React.FC<
|
||||
|
||||
const newData = collectionSlug ? json.doc : json.result
|
||||
|
||||
const newState = await getFormState({
|
||||
const { state: newState } = await getFormState({
|
||||
apiRoute: api,
|
||||
body: {
|
||||
id,
|
||||
@@ -406,6 +517,7 @@ const DocumentInfo: React.FC<
|
||||
|
||||
setInitialState(newState)
|
||||
setData(newData)
|
||||
|
||||
await getDocPermissions(newData)
|
||||
},
|
||||
[
|
||||
@@ -440,7 +552,7 @@ const DocumentInfo: React.FC<
|
||||
setIsLoading(true)
|
||||
|
||||
try {
|
||||
const result = await getFormState({
|
||||
const { state: result } = await getFormState({
|
||||
apiRoute: api,
|
||||
body: {
|
||||
id,
|
||||
@@ -564,8 +676,10 @@ const DocumentInfo: React.FC<
|
||||
const value: DocumentInfoContext = {
|
||||
...props,
|
||||
action,
|
||||
currentEditor,
|
||||
docConfig,
|
||||
docPermissions,
|
||||
documentIsLocked,
|
||||
getDocPermissions,
|
||||
getDocPreferences,
|
||||
getVersions,
|
||||
@@ -578,10 +692,14 @@ const DocumentInfo: React.FC<
|
||||
onSave,
|
||||
preferencesKey,
|
||||
publishedDoc,
|
||||
setCurrentEditor,
|
||||
setDocFieldPreferences,
|
||||
setDocumentIsLocked,
|
||||
setDocumentTitle,
|
||||
title: documentTitle,
|
||||
unlockDocument,
|
||||
unpublishedVersions,
|
||||
updateDocumentEditor,
|
||||
versions,
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type {
|
||||
ClientCollectionConfig,
|
||||
ClientGlobalConfig,
|
||||
ClientUser,
|
||||
Data,
|
||||
DocumentPermissions,
|
||||
DocumentPreferences,
|
||||
@@ -56,7 +57,9 @@ export type DocumentInfoProps = {
|
||||
}
|
||||
|
||||
export type DocumentInfoContext = {
|
||||
currentEditor?: ClientUser
|
||||
docConfig?: ClientCollectionConfig | ClientGlobalConfig
|
||||
documentIsLocked?: boolean
|
||||
getDocPermissions: (data?: Data) => Promise<void>
|
||||
getDocPreferences: () => Promise<DocumentPreferences>
|
||||
getVersions: () => Promise<void>
|
||||
@@ -66,13 +69,17 @@ export type DocumentInfoContext = {
|
||||
isLoading: boolean
|
||||
preferencesKey?: string
|
||||
publishedDoc?: { _status?: string } & TypeWithID & TypeWithTimestamps
|
||||
setCurrentEditor?: React.Dispatch<React.SetStateAction<ClientUser>>
|
||||
setDocFieldPreferences: (
|
||||
field: string,
|
||||
fieldPreferences: { [key: string]: unknown } & Partial<InsideFieldsPreferences>,
|
||||
) => void
|
||||
setDocumentIsLocked?: React.Dispatch<React.SetStateAction<boolean>>
|
||||
setDocumentTitle: (title: string) => void
|
||||
title: string
|
||||
unlockDocument: (docId: number | string, slug: string) => Promise<void>
|
||||
unpublishedVersions?: PaginatedDocs<TypeWithVersion<any>>
|
||||
updateDocumentEditor: (docId: number | string, slug: string, user: ClientUser) => Promise<void>
|
||||
versions?: PaginatedDocs<TypeWithVersion<any>>
|
||||
versionsCount?: PaginatedDocs<TypeWithVersion<any>>
|
||||
} & DocumentInfoProps
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user