feat(ui): allows customizing version diff components, render versions ui on the server (#10815)
This PR moves the logic for rendering diff field components in the version comparison view from the client to the server. This allows us to expose more customization options to the server-side Payload Config. For example, users can now pass their own diff components for fields - even including RSCs. This PR also cleans up the version view types Implements the following from https://github.com/payloadcms/payload/discussions/4197: - allow for customization of diff components - more control over versions screens in general TODO: - [x] Bring getFieldPaths fixes into core - [x] Cleanup and test with scrutiny. Ensure all field types display their diffs correctly - [x] Review public API for overriding field types, add docs - [x] Add e2e test for new public API
This commit is contained in:
@@ -1,9 +1,9 @@
|
||||
---
|
||||
title: Fields Overview
|
||||
description: Fields are the building blocks of Payload, find out how to add or remove a field, change field type, add hooks, define Access Control and Validation.
|
||||
keywords: overview, fields, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, nextjs
|
||||
label: Overview
|
||||
order: 10
|
||||
desc: Fields are the building blocks of Payload, find out how to add or remove a field, change field type, add hooks, define Access Control and Validation.
|
||||
keywords: overview, fields, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, nextjs
|
||||
title: Fields Overview
|
||||
---
|
||||
|
||||
Fields are the building blocks of Payload. They define the schema of the Documents that will be stored in the [Database](../database/overview), as well as automatically generate the corresponding UI within the [Admin Panel](../admin/overview).
|
||||
@@ -48,8 +48,7 @@ export const Page: CollectionConfig = {
|
||||
```
|
||||
|
||||
<Banner type="warning">
|
||||
**Reminder:**
|
||||
Each field is an object with at least the `type` property. This matches the field to its corresponding Field Type. [More details](#field-options).
|
||||
**Reminder:** Each field is an object with at least the `type` property. This matches the field to its corresponding Field Type. [More details](#field-options).
|
||||
</Banner>
|
||||
|
||||
There are three main categories of fields in Payload:
|
||||
@@ -91,10 +90,10 @@ Presentational Fields do not store data in the database. Instead, they are used
|
||||
|
||||
Here are the available Presentational Fields:
|
||||
|
||||
- [Collapsible](/docs/fields/collapsible) - nests fields within a collapsible component
|
||||
- [Row](/docs/fields/row) - aligns fields horizontally
|
||||
- [Tabs (Unnamed)](/docs/fields/tabs) - nests fields within a tabbed layout
|
||||
- [UI](/docs/fields/ui) - blank field for custom UI components
|
||||
- [Collapsible](../fields/collapsible) - nests fields within a collapsible component
|
||||
- [Row](../fields/row) - aligns fields horizontally
|
||||
- [Tabs (Unnamed)](../fields/tabs) - nests fields within a tabbed layout
|
||||
- [UI](../fields/ui) - blank field for custom UI components
|
||||
|
||||
### Virtual Fields
|
||||
|
||||
@@ -102,11 +101,10 @@ Virtual fields are used to display data that is not stored in the database. They
|
||||
|
||||
Here are the available Virtual Fields:
|
||||
|
||||
- [Join](/docs/fields/join) - achieves two-way data binding between fields
|
||||
- [Join](../fields/join) - achieves two-way data binding between fields
|
||||
|
||||
<Banner type="success">
|
||||
**Tip:**
|
||||
Don't see a built-in field type that you need? Build it! Using a combination of [Field Validations](#validation) and [Custom Components](../admin/components), you can override the entirety of how a component functions within the [Admin Panel](../admin/overview) to effectively create your own field type.
|
||||
**Tip:** Don't see a built-in field type that you need? Build it! Using a combination of [Field Validations](#validation) and [Custom Components](../admin/components), you can override the entirety of how a component functions within the [Admin Panel](../admin/overview) to effectively create your own field type.
|
||||
</Banner>
|
||||
|
||||
## Field Options
|
||||
@@ -147,10 +145,10 @@ Payload reserves various field names for internal use. Using reserved field name
|
||||
|
||||
The following field names are forbidden and cannot be used:
|
||||
|
||||
- `__v`
|
||||
- `salt`
|
||||
- `hash`
|
||||
- `file`
|
||||
- `__v`
|
||||
- `salt`
|
||||
- `hash`
|
||||
- `file`
|
||||
|
||||
### Field-level Hooks
|
||||
|
||||
@@ -241,8 +239,7 @@ export const myField: Field = {
|
||||
```
|
||||
|
||||
<Banner type="success">
|
||||
**Tip:**
|
||||
You can use async `defaultValue` functions to fill fields with data from API requests or Local API using `req.payload`.
|
||||
**Tip:** You can use async `defaultValue` functions to fill fields with data from API requests or Local API using `req.payload`.
|
||||
</Banner>
|
||||
|
||||
### Validation
|
||||
@@ -265,10 +262,10 @@ Custom validation functions should return either `true` or a `string` representi
|
||||
|
||||
The following arguments are provided to the `validate` function:
|
||||
|
||||
| Argument | Description |
|
||||
| -------- | --------------------------------------------------------------------------------------------- |
|
||||
| `value` | The value of the field being validated. |
|
||||
| `ctx` | An object with additional data and context. [More details](#validation-context) |
|
||||
| Argument | Description |
|
||||
| --- | --- |
|
||||
| `value` | The value of the field being validated. |
|
||||
| `ctx` | An object with additional data and context. [More details](#validation-context) |
|
||||
|
||||
#### Validation Context
|
||||
|
||||
@@ -289,14 +286,14 @@ export const MyField: Field = {
|
||||
|
||||
The following additional properties are provided in the `ctx` object:
|
||||
|
||||
| Property | Description |
|
||||
| ------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `data` | An object containing the full collection or global document currently being edited. |
|
||||
| `siblingData` | An object containing document data that is scoped to only fields within the same parent of this field. |
|
||||
| `operation` | Will be `create` or `update` depending on the UI action or API call. |
|
||||
| `id` | The `id` of the current document being edited. `id` is `undefined` during the `create` operation. |
|
||||
| `req` | The current HTTP request object. Contains `payload`, `user`, etc. |
|
||||
| `event` | Either `onChange` or `submit` depending on the current action. Used as a performance opt-in. [More details](#async-field-validations). |
|
||||
| Property | Description |
|
||||
| --- | --- |
|
||||
| `data` | An object containing the full collection or global document currently being edited. |
|
||||
| `siblingData` | An object containing document data that is scoped to only fields within the same parent of this field. |
|
||||
| `operation` | Will be `create` or `update` depending on the UI action or API call. |
|
||||
| `id` | The `id` of the current document being edited. `id` is `undefined` during the `create` operation. |
|
||||
| `req` | The current HTTP request object. Contains `payload`, `user`, etc. |
|
||||
| `event` | Either `onChange` or `submit` depending on the current action. Used as a performance opt-in. [More details](#async-field-validations). |
|
||||
|
||||
#### Reusing Default Field Validations
|
||||
|
||||
@@ -402,8 +399,7 @@ export const MyCollection: CollectionConfig = {
|
||||
```
|
||||
|
||||
<Banner type="warning">
|
||||
**Reminder:**
|
||||
The Custom ID Fields can only be of type [`Number`](./number) or [`Text`](./text). Custom ID fields with type `text` must not contain `/` or `.` characters.
|
||||
**Reminder:** The Custom ID Fields can only be of type [`Number`](./number) or [`Text`](./text). Custom ID fields with type `text` must not contain `/` or `.` characters.
|
||||
</Banner>
|
||||
|
||||
## Admin Options
|
||||
@@ -430,21 +426,21 @@ export const CollectionConfig: CollectionConfig = {
|
||||
|
||||
The following options are available:
|
||||
|
||||
| Option | Description |
|
||||
| ----------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **`condition`** | Programmatically show / hide fields based on other fields. [More details](#conditional-logic). |
|
||||
| **`components`** | All Field Components can be swapped out for [Custom Components](../admin/components) that you define. |
|
||||
| **`description`** | Helper text to display alongside the field to provide more information for the editor. [More details](#description). |
|
||||
| **`position`** | Specify if the field should be rendered in the sidebar by defining `position: 'sidebar'`. |
|
||||
| **`width`** | Restrict the width of a field. You can pass any string-based value here, be it pixels, percentages, etc. This property is especially useful when fields are nested within a `Row` type where they can be organized horizontally. |
|
||||
| **`style`** | [CSS Properties](https://developer.mozilla.org/en-US/docs/Web/CSS) to inject into the root element of the field. |
|
||||
| **`className`** | Attach a [CSS class attribute](https://developer.mozilla.org/en-US/docs/Web/CSS/Class_selectors) to the root DOM element of a field. |
|
||||
| **`readOnly`** | Setting a field to `readOnly` has no effect on the API whatsoever but disables the admin component's editability to prevent editors from modifying the field's value. |
|
||||
| **`disabled`** | If a field is `disabled`, it is completely omitted from the [Admin Panel](../admin/overview) entirely. |
|
||||
| **`disableBulkEdit`** | Set `disableBulkEdit` to `true` to prevent fields from appearing in the select options when making edits for multiple documents. Defaults to `true` for UI fields. |
|
||||
| **`disableListColumn`** | Set `disableListColumn` to `true` to prevent fields from appearing in the list view column selector. |
|
||||
| **`disableListFilter`** | Set `disableListFilter` to `true` to prevent fields from appearing in the list view filter options. |
|
||||
| **`hidden`** | Will transform the field into a `hidden` input type. Its value will still submit with requests in the Admin Panel, but the field itself will not be visible to editors. |
|
||||
| Option | Description |
|
||||
| --- | --- |
|
||||
| **`condition`** | Programmatically show / hide fields based on other fields. [More details](#conditional-logic). |
|
||||
| **`components`** | All Field Components can be swapped out for [Custom Components](../admin/components) that you define. |
|
||||
| **`description`** | Helper text to display alongside the field to provide more information for the editor. [More details](#description). |
|
||||
| **`position`** | Specify if the field should be rendered in the sidebar by defining `position: 'sidebar'`. |
|
||||
| **`width`** | Restrict the width of a field. You can pass any string-based value here, be it pixels, percentages, etc. This property is especially useful when fields are nested within a `Row` type where they can be organized horizontally. |
|
||||
| **`style`** | [CSS Properties](https://developer.mozilla.org/en-US/docs/Web/CSS) to inject into the root element of the field. |
|
||||
| **`className`** | Attach a [CSS class attribute](https://developer.mozilla.org/en-US/docs/Web/CSS/Class_selectors) to the root DOM element of a field. |
|
||||
| **`readOnly`** | Setting a field to `readOnly` has no effect on the API whatsoever but disables the admin component's editability to prevent editors from modifying the field's value. |
|
||||
| **`disabled`** | If a field is `disabled`, it is completely omitted from the [Admin Panel](../admin/overview) entirely. |
|
||||
| **`disableBulkEdit`** | Set `disableBulkEdit` to `true` to prevent fields from appearing in the select options when making edits for multiple documents. Defaults to `true` for UI fields. |
|
||||
| **`disableListColumn`** | Set `disableListColumn` to `true` to prevent fields from appearing in the list view column selector. |
|
||||
| **`disableListFilter`** | Set `disableListFilter` to `true` to prevent fields from appearing in the list view filter options. |
|
||||
| **`hidden`** | Will transform the field into a `hidden` input type. Its value will still submit with requests in the Admin Panel, but the field itself will not be visible to editors. |
|
||||
|
||||
### Field Descriptions
|
||||
|
||||
@@ -452,9 +448,9 @@ Field Descriptions are used to provide additional information to the editor abou
|
||||
|
||||
A description can be configured in three ways:
|
||||
|
||||
- As a string.
|
||||
- As a function which returns a string. [More details](#description-functions).
|
||||
- As a React component. [More details](#description).
|
||||
- As a string.
|
||||
- As a function which returns a string. [More details](#description-functions).
|
||||
- As a React component. [More details](#description).
|
||||
|
||||
To add a Custom Description to a field, use the `admin.description` property in your Field Config:
|
||||
|
||||
@@ -477,15 +473,14 @@ export const MyCollectionConfig: SanitizedCollectionConfig = {
|
||||
```
|
||||
|
||||
<Banner type="warning">
|
||||
**Reminder:**
|
||||
To replace the Field Description with a [Custom Component](../admin/components), use the `admin.components.Description` property. [More details](#description).
|
||||
**Reminder:** To replace the Field Description with a [Custom Component](../admin/components), use the `admin.components.Description` property. [More details](#description).
|
||||
</Banner>
|
||||
|
||||
#### Description Functions
|
||||
|
||||
Custom Descriptions can also be defined as a function. Description Functions are executed on the server and can be used to format simple descriptions based on the user's current [Locale](../configuration/localization).
|
||||
|
||||
To add a Description Function to a field, set the `admin.description` property to a _function_ in your Field Config:
|
||||
To add a Description Function to a field, set the `admin.description` property to a *function* in your Field Config:
|
||||
|
||||
```ts
|
||||
import type { SanitizedCollectionConfig } from 'payload'
|
||||
@@ -507,13 +502,12 @@ export const MyCollectionConfig: SanitizedCollectionConfig = {
|
||||
|
||||
All Description Functions receive the following arguments:
|
||||
|
||||
| Argument | Description |
|
||||
| -------------- | ---------------------------------------------------------------- |
|
||||
| **`t`** | The `t` function used to internationalize the Admin Panel. [More details](../configuration/i18n) |
|
||||
| Argument | Description |
|
||||
| --- | --- |
|
||||
| **`t`** | The `t` function used to internationalize the Admin Panel. [More details](../configuration/i18n) |
|
||||
|
||||
<Banner type="info">
|
||||
**Note:**
|
||||
If you need to subscribe to live updates within your form, use a Description Component instead. [More details](#description).
|
||||
**Note:** If you need to subscribe to live updates within your form, use a Description Component instead. [More details](#description).
|
||||
</Banner>
|
||||
|
||||
### Conditional Logic
|
||||
@@ -562,6 +556,7 @@ Within the [Admin Panel](../admin/overview), fields are represented in three dis
|
||||
- [Field](#field) - The actual form field rendered in the Edit View.
|
||||
- [Cell](#cell) - The table cell component rendered in the List View.
|
||||
- [Filter](#filter) - The filter component rendered in the List View.
|
||||
- [Diff](#diff) - The Diff component rendered in the Version Diff View
|
||||
|
||||
To swap in Field Components with your own, use the `admin.components` property in your Field Config:
|
||||
|
||||
@@ -586,16 +581,17 @@ export const CollectionConfig: CollectionConfig = {
|
||||
|
||||
The following options are available:
|
||||
|
||||
| Component | Description |
|
||||
| ---------- | --------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **`Field`** | The form field rendered of the Edit View. [More details](#field). |
|
||||
| **`Cell`** | The table cell rendered of the List View. [More details](#cell). |
|
||||
| **`Filter`** | The filter component rendered in the List View. [More details](#filter). |
|
||||
| **`Label`** | Override the default Label of the Field Component. [More details](#label). |
|
||||
| **`Error`** | Override the default Error of the Field Component. [More details](#error). |
|
||||
| **`Description`** | Override the default Description of the Field Component. [More details](#description). |
|
||||
| **`beforeInput`** | An array of elements that will be added before the input of the Field Component. [More details](#afterinput-and-beforeinput).|
|
||||
| **`afterInput`** | An array of elements that will be added after the input of the Field Component. [More details](#afterinput-and-beforeinput). |
|
||||
| Component | Description |
|
||||
| --- | --- |
|
||||
| **`Field`** | The form field rendered of the Edit View. [More details](#field). |
|
||||
| **`Cell`** | The table cell rendered of the List View. [More details](#cell). |
|
||||
| **`Filter`** | The filter component rendered in the List View. [More details](#filter). |
|
||||
| **`Label`** | Override the default Label of the Field Component. [More details](#label). |
|
||||
| **`Error`** | Override the default Error of the Field Component. [More details](#error). |
|
||||
| **`Diff`** | Override the default Diff component rendered in the Version Diff View. [More details](#diff). |
|
||||
| **`Description`** | Override the default Description of the Field Component. [More details](#description). |
|
||||
| **`beforeInput`** | An array of elements that will be added before the input of the Field Component. [More details](#afterinput-and-beforeinput). |
|
||||
| **`afterInput`** | An array of elements that will be added after the input of the Field Component. [More details](#afterinput-and-beforeinput). |
|
||||
|
||||
#### Field
|
||||
|
||||
@@ -622,7 +618,7 @@ export const CollectionConfig: CollectionConfig = {
|
||||
}
|
||||
```
|
||||
|
||||
_For details on how to build Custom Components, see [Building Custom Components](../admin/components#building-custom-components)._
|
||||
*For details on how to build Custom Components, see [Building Custom Components](../admin/components#building-custom-components).*
|
||||
|
||||
<Banner type="warning">
|
||||
Instead of replacing the entire Field Component, you can alternately replace or slot-in only specific parts by using the [`Label`](#label), [`Error`](#error), [`beforeInput`](#afterinput-and-beforinput), and [`afterInput`](#afterinput-and-beforinput) properties.
|
||||
@@ -632,31 +628,31 @@ _For details on how to build Custom Components, see [Building Custom Components]
|
||||
|
||||
All Field Components receive the following props by default:
|
||||
|
||||
| Property | Description |
|
||||
| -------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **`docPreferences`** | An object that contains the [Preferences](../admin/preferences) for the document. |
|
||||
| **`field`** | In Client Components, this is the sanitized Client Field Config. In Server Components, this is the original Field Config. Server Components will also receive the sanitized field config through the`clientField` prop (see below). |
|
||||
| **`locale`** | The locale of the field. [More details](../configuration/localization). |
|
||||
| **`readOnly`** | A boolean value that represents if the field is read-only or not. |
|
||||
| **`user`** | The currently authenticated user. [More details](../authentication/overview). |
|
||||
| **`validate`** | A function that can be used to validate the field. |
|
||||
| **`path`** | A string representing the direct, dynamic path to the field at runtime, i.e. `myGroup.myArray.0.myField`. |
|
||||
| **`schemaPath`** | A string representing the direct, static path to the Field Config, i.e. `posts.myGroup.myArray.myField`. |
|
||||
| **`indexPath`** | A hyphen-notated string representing the path to the field _within the nearest named ancestor field_, i.e. `0-0` |
|
||||
| Property | Description |
|
||||
| --- | --- |
|
||||
| **`docPreferences`** | An object that contains the [Preferences](../admin/preferences) for the document. |
|
||||
| **`field`** | In Client Components, this is the sanitized Client Field Config. In Server Components, this is the original Field Config. Server Components will also receive the sanitized field config through the`clientField` prop (see below). |
|
||||
| **`locale`** | The locale of the field. [More details](../configuration/localization). |
|
||||
| **`readOnly`** | A boolean value that represents if the field is read-only or not. |
|
||||
| **`user`** | The currently authenticated user. [More details](../authentication/overview). |
|
||||
| **`validate`** | A function that can be used to validate the field. |
|
||||
| **`path`** | A string representing the direct, dynamic path to the field at runtime, i.e. `myGroup.myArray.0.myField`. |
|
||||
| **`schemaPath`** | A string representing the direct, static path to the Field Config, i.e. `posts.myGroup.myArray.myField`. |
|
||||
| **`indexPath`** | A hyphen-notated string representing the path to the field *within the nearest named ancestor field*, i.e. `0-0` |
|
||||
|
||||
In addition to the above props, all Server Components will also receive the following props:
|
||||
|
||||
| Property | Description |
|
||||
| ----------------- | ----------------------------------------------------------------------------- |
|
||||
| **`clientField`** | The serializable Client Field Config. |
|
||||
| **`field`** | The Field Config. |
|
||||
| **`data`** | The current document being edited. |
|
||||
| **`i18n`** | The [i18n](../configuration/i18n) object. |
|
||||
| **`payload`** | The [Payload](../local-api/overview) class. |
|
||||
| **`permissions`** | The field permissions based on the currently authenticated user. |
|
||||
| **`siblingData`** | The data of the field's siblings. |
|
||||
| **`user`** | The currently authenticated user. [More details](../authentication/overview). |
|
||||
| **`value`** | The value of the field at render-time. |
|
||||
| Property | Description |
|
||||
| --- | --- |
|
||||
| **`clientField`** | The serializable Client Field Config. |
|
||||
| **`field`** | The Field Config. |
|
||||
| **`data`** | The current document being edited. |
|
||||
| **`i18n`** | The [i18n](../configuration/i18n) object. |
|
||||
| **`payload`** | The [Payload](../local-api/overview) class. |
|
||||
| **`permissions`** | The field permissions based on the currently authenticated user. |
|
||||
| **`siblingData`** | The data of the field's siblings. |
|
||||
| **`user`** | The currently authenticated user. [More details](../authentication/overview). |
|
||||
| **`value`** | The value of the field at render-time. |
|
||||
|
||||
##### Sending and receiving values from the form
|
||||
|
||||
@@ -722,10 +718,10 @@ export const myField: Field = {
|
||||
|
||||
All Cell Components receive the same [Default Field Component Props](#field), plus the following:
|
||||
|
||||
| Property | Description |
|
||||
| ---------------- | ----------------------------------------------------------------- |
|
||||
| **`link`** | A boolean representing whether this cell should be wrapped in a link. |
|
||||
| **`onClick`** | A function that is called when the cell is clicked. |
|
||||
| Property | Description |
|
||||
| --- | --- |
|
||||
| **`link`** | A boolean representing whether this cell should be wrapped in a link. |
|
||||
| **`onClick`** | A function that is called when the cell is clicked. |
|
||||
|
||||
For details on how to build Custom Components themselves, see [Building Custom Components](../admin/components#building-custom-components).
|
||||
|
||||
@@ -867,9 +863,45 @@ import type {
|
||||
} from 'payload'
|
||||
```
|
||||
|
||||
#### Diff
|
||||
|
||||
The Diff Component is rendered in the Version Diff view. It will only be visible in entities with versioning enabled,
|
||||
|
||||
To swap in your own Diff Component, use the `admin.components.Diff` property in your Field Config:
|
||||
|
||||
```ts
|
||||
import type { Field } from 'payload'
|
||||
|
||||
export const myField: Field = {
|
||||
name: 'myField',
|
||||
type: 'text',
|
||||
admin: {
|
||||
components: {
|
||||
Diff: '/path/to/MyCustomDiffComponent', // highlight-line
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
All Error Components receive the [Default Field Component Props](#field).
|
||||
|
||||
For details on how to build Custom Components themselves, see [Building Custom Components](../admin/components#building-custom-components).
|
||||
|
||||
##### TypeScript#diff-component-types
|
||||
|
||||
When building Custom Diff Components, you can import the component types to ensure type safety in your component. There is an explicit type for the Diff Component, one for every Field Type and server/client environment. The convention is to append `DiffServerComponent` or `DiffClientComponent` to the type of field, i.e. `TextFieldDiffClientComponent`.
|
||||
|
||||
```tsx
|
||||
import type {
|
||||
TextFieldDiffServerComponent,
|
||||
TextFieldDiffClientComponent,
|
||||
// And so on for each Field Type
|
||||
} from 'payload'
|
||||
```
|
||||
|
||||
#### afterInput and beforeInput
|
||||
|
||||
With these properties you can add multiple components _before_ and _after_ the input element, as their name suggests. This is useful when you need to render additional elements alongside the field without replacing the entire field component.
|
||||
With these properties you can add multiple components *before* and *after* the input element, as their name suggests. This is useful when you need to render additional elements alongside the field without replacing the entire field component.
|
||||
|
||||
To add components before and after the input element, use the `admin.components.beforeInput` and `admin.components.afterInput` properties in your Field Config:
|
||||
|
||||
@@ -906,4 +938,4 @@ You can import the Payload `Field` type as well as other common types from the `
|
||||
|
||||
```ts
|
||||
import type { Field } from 'payload'
|
||||
```
|
||||
```
|
||||
@@ -0,0 +1,13 @@
|
||||
'use client'
|
||||
|
||||
import React, { createContext } from 'react'
|
||||
|
||||
type SelectedLocalesContextType = {
|
||||
selectedLocales: string[]
|
||||
}
|
||||
|
||||
export const SelectedLocalesContext = createContext<SelectedLocalesContextType>({
|
||||
selectedLocales: [],
|
||||
})
|
||||
|
||||
export const useSelectedLocales = () => React.useContext(SelectedLocalesContext)
|
||||
@@ -1,36 +1,45 @@
|
||||
'use client'
|
||||
import type { OptionObject } from 'payload'
|
||||
|
||||
import {
|
||||
CheckboxInput,
|
||||
Gutter,
|
||||
useConfig, useDocumentInfo, usePayloadAPI, useTranslation } from '@payloadcms/ui'
|
||||
import { CheckboxInput, Gutter, useConfig, useDocumentInfo, useTranslation } from '@payloadcms/ui'
|
||||
import { formatDate } from '@payloadcms/ui/shared'
|
||||
import React, { useState } from 'react'
|
||||
import { usePathname, useRouter, useSearchParams } from 'next/navigation.js'
|
||||
import React, { useEffect, useMemo, useState } from 'react'
|
||||
|
||||
import type { CompareOption, DefaultVersionsViewProps } from './types.js'
|
||||
|
||||
import { diffComponents } from '../RenderFieldsToDiff/fields/index.js'
|
||||
import { RenderFieldsToDiff } from '../RenderFieldsToDiff/index.js'
|
||||
import Restore from '../Restore/index.js'
|
||||
import { SelectComparison } from '../SelectComparison/index.js'
|
||||
import { SelectLocales } from '../SelectLocales/index.js'
|
||||
import './index.scss'
|
||||
import { SelectLocales } from '../SelectLocales/index.js'
|
||||
import { SelectedLocalesContext } from './SelectedLocalesContext.js'
|
||||
import { SetStepNav } from './SetStepNav.js'
|
||||
|
||||
const baseClass = 'view-version'
|
||||
|
||||
export const DefaultVersionView: React.FC<DefaultVersionsViewProps> = ({
|
||||
canUpdate,
|
||||
doc,
|
||||
docPermissions,
|
||||
initialComparisonDoc,
|
||||
latestDraftVersion,
|
||||
latestPublishedVersion,
|
||||
localeOptions,
|
||||
modifiedOnly: modifiedOnlyProp,
|
||||
RenderedDiff,
|
||||
selectedLocales: selectedLocalesProp,
|
||||
versionID,
|
||||
}) => {
|
||||
const { config, getEntityConfig } = useConfig()
|
||||
|
||||
const availableLocales = useMemo(
|
||||
() =>
|
||||
config.localization
|
||||
? config.localization.locales.map((locale) => ({
|
||||
label: locale.label,
|
||||
value: locale.code,
|
||||
}))
|
||||
: [],
|
||||
[config.localization],
|
||||
)
|
||||
|
||||
const { i18n } = useTranslation()
|
||||
const { id, collectionSlug, globalSlug } = useDocumentInfo()
|
||||
|
||||
@@ -38,13 +47,44 @@ export const DefaultVersionView: React.FC<DefaultVersionsViewProps> = ({
|
||||
|
||||
const [globalConfig] = useState(() => getEntityConfig({ globalSlug }))
|
||||
|
||||
const [locales, setLocales] = useState<OptionObject[]>(localeOptions)
|
||||
const [selectedLocales, setSelectedLocales] = useState<OptionObject[]>(selectedLocalesProp)
|
||||
|
||||
const [compareValue, setCompareValue] = useState<CompareOption>()
|
||||
const [modifiedOnly, setModifiedOnly] = useState(false)
|
||||
const router = useRouter()
|
||||
const pathname = usePathname()
|
||||
const searchParams = useSearchParams()
|
||||
const [modifiedOnly, setModifiedOnly] = useState(modifiedOnlyProp)
|
||||
function onToggleModifiedOnly() {
|
||||
setModifiedOnly(!modifiedOnly)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
// If the selected comparison doc or locales change, update URL params so that version page RSC
|
||||
// can update the version comparison state
|
||||
const current = new URLSearchParams(Array.from(searchParams.entries()))
|
||||
|
||||
if (!compareValue) {
|
||||
current.delete('compareValue')
|
||||
} else {
|
||||
current.set('compareValue', compareValue?.value)
|
||||
}
|
||||
if (!selectedLocales) {
|
||||
current.delete('localeCodes')
|
||||
} else {
|
||||
current.set('localeCodes', JSON.stringify(selectedLocales.map((locale) => locale.value)))
|
||||
}
|
||||
|
||||
if (!modifiedOnly) {
|
||||
current.delete('modifiedOnly')
|
||||
} else {
|
||||
current.set('modifiedOnly', 'true')
|
||||
}
|
||||
|
||||
const search = current.toString()
|
||||
const query = search ? `?${search}` : ''
|
||||
router.push(`${pathname}${query}`)
|
||||
}, [compareValue, pathname, router, searchParams, selectedLocales, modifiedOnly])
|
||||
|
||||
const {
|
||||
admin: { dateFormat },
|
||||
localization,
|
||||
@@ -60,19 +100,6 @@ export const DefaultVersionView: React.FC<DefaultVersionsViewProps> = ({
|
||||
collectionSlug || globalSlug
|
||||
}/versions`
|
||||
|
||||
const compareFetchURL = compareValue?.value && `${compareBaseURL}/${compareValue.value}`
|
||||
|
||||
const [{ data: currentComparisonDoc }] = usePayloadAPI(compareFetchURL, {
|
||||
initialData: initialComparisonDoc,
|
||||
initialParams: { depth: 1, draft: 'true', locale: 'all' },
|
||||
})
|
||||
|
||||
const comparison = compareValue?.value && currentComparisonDoc?.version // the `version` key is only present on `versions` documents
|
||||
|
||||
const canUpdate = docPermissions?.update
|
||||
|
||||
const localeValues = locales && locales.map((locale) => locale.value)
|
||||
|
||||
const draftsEnabled = Boolean((collectionConfig || globalConfig)?.versions.drafts)
|
||||
|
||||
return (
|
||||
@@ -129,29 +156,18 @@ export const DefaultVersionView: React.FC<DefaultVersionsViewProps> = ({
|
||||
versionID={versionID}
|
||||
/>
|
||||
{localization && (
|
||||
<SelectLocales onChange={setLocales} options={localeOptions} value={locales} />
|
||||
<SelectLocales
|
||||
onChange={setSelectedLocales}
|
||||
options={availableLocales}
|
||||
value={selectedLocales}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
{doc?.version && (
|
||||
<RenderFieldsToDiff
|
||||
comparison={comparison}
|
||||
diffComponents={diffComponents}
|
||||
fieldPermissions={docPermissions?.fields}
|
||||
fields={(collectionConfig || globalConfig)?.fields}
|
||||
i18n={i18n}
|
||||
locales={localeValues}
|
||||
modifiedOnly={modifiedOnly}
|
||||
version={
|
||||
globalConfig
|
||||
? {
|
||||
...doc?.version,
|
||||
createdAt: doc?.version?.createdAt || doc.createdAt,
|
||||
updatedAt: doc?.version?.updatedAt || doc.updatedAt,
|
||||
}
|
||||
: doc?.version
|
||||
}
|
||||
/>
|
||||
)}
|
||||
<SelectedLocalesContext.Provider
|
||||
value={{ selectedLocales: selectedLocales.map((locale) => locale.value) }}
|
||||
>
|
||||
{doc?.version && RenderedDiff}
|
||||
</SelectedLocalesContext.Provider>
|
||||
</Gutter>
|
||||
</main>
|
||||
)
|
||||
|
||||
@@ -1,9 +1,4 @@
|
||||
import type {
|
||||
Document,
|
||||
OptionObject,
|
||||
SanitizedCollectionPermission,
|
||||
SanitizedGlobalPermission,
|
||||
} from 'payload'
|
||||
import type { Document, OptionObject } from 'payload'
|
||||
|
||||
export type CompareOption = {
|
||||
label: React.ReactNode | string
|
||||
@@ -13,11 +8,12 @@ export type CompareOption = {
|
||||
}
|
||||
|
||||
export type DefaultVersionsViewProps = {
|
||||
readonly canUpdate: boolean
|
||||
readonly doc: Document
|
||||
readonly docPermissions: SanitizedCollectionPermission | SanitizedGlobalPermission
|
||||
readonly initialComparisonDoc: Document
|
||||
readonly latestDraftVersion?: string
|
||||
readonly latestPublishedVersion?: string
|
||||
readonly localeOptions: OptionObject[]
|
||||
modifiedOnly: boolean
|
||||
readonly RenderedDiff: React.ReactNode
|
||||
readonly selectedLocales: OptionObject[]
|
||||
readonly versionID?: string
|
||||
}
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
'use client'
|
||||
const baseClass = 'render-field-diffs'
|
||||
import type { VersionField } from 'payload'
|
||||
|
||||
import './index.scss'
|
||||
|
||||
import { ShimmerEffect } from '@payloadcms/ui'
|
||||
import React, { Fragment, useEffect } from 'react'
|
||||
|
||||
export const RenderVersionFieldsToDiff = ({
|
||||
versionFields,
|
||||
}: {
|
||||
versionFields: VersionField[]
|
||||
}): React.ReactNode => {
|
||||
const [hasMounted, setHasMounted] = React.useState(false)
|
||||
|
||||
// defer rendering until after the first mount as the CSS is loaded with Emotion
|
||||
// this will ensure that the CSS is loaded before rendering the diffs and prevent CLS
|
||||
useEffect(() => {
|
||||
setHasMounted(true)
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div className={baseClass}>
|
||||
{!hasMounted ? (
|
||||
<Fragment>
|
||||
<ShimmerEffect height="8rem" width="100%" />
|
||||
</Fragment>
|
||||
) : (
|
||||
versionFields?.map((field, fieldIndex) => {
|
||||
if (field.fieldByLocale) {
|
||||
const LocaleComponents: React.ReactNode[] = []
|
||||
for (const [locale, baseField] of Object.entries(field.fieldByLocale)) {
|
||||
LocaleComponents.push(
|
||||
<div className={`${baseClass}__locale`} key={[locale, fieldIndex].join('-')}>
|
||||
<div className={`${baseClass}__locale-value`}>{baseField.CustomComponent}</div>
|
||||
</div>,
|
||||
)
|
||||
}
|
||||
return (
|
||||
<div className={`${baseClass}__field`} key={fieldIndex}>
|
||||
{LocaleComponents}
|
||||
</div>
|
||||
)
|
||||
} else if (field.field) {
|
||||
return (
|
||||
<div
|
||||
className={`${baseClass}__field field__${field.field.type}`}
|
||||
data-field-path={field.field.path}
|
||||
key={fieldIndex}
|
||||
>
|
||||
{field.field.CustomComponent}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return null
|
||||
})
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,417 @@
|
||||
import type { I18nClient } from '@payloadcms/translations'
|
||||
import type {
|
||||
BaseVersionField,
|
||||
ClientField,
|
||||
ClientFieldSchemaMap,
|
||||
Field,
|
||||
FieldDiffClientProps,
|
||||
FieldDiffServerProps,
|
||||
FieldTypes,
|
||||
PayloadComponent,
|
||||
PayloadRequest,
|
||||
SanitizedFieldPermissions,
|
||||
VersionField,
|
||||
} from 'payload'
|
||||
import type { DiffMethod } from 'react-diff-viewer-continued'
|
||||
|
||||
import { RenderServerComponent } from '@payloadcms/ui/elements/RenderServerComponent'
|
||||
import { dequal } from 'dequal/lite'
|
||||
import { fieldIsID, getUniqueListBy, tabHasName } from 'payload/shared'
|
||||
|
||||
import { diffMethods } from './fields/diffMethods.js'
|
||||
import { diffComponents } from './fields/index.js'
|
||||
import { getFieldPathsModified } from './utilities/getFieldPathsModified.js'
|
||||
|
||||
export type BuildVersionFieldsArgs = {
|
||||
clientSchemaMap: ClientFieldSchemaMap
|
||||
comparisonSiblingData: object
|
||||
customDiffComponents: Partial<
|
||||
Record<FieldTypes, PayloadComponent<FieldDiffServerProps, FieldDiffClientProps>>
|
||||
>
|
||||
entitySlug: string
|
||||
fieldPermissions:
|
||||
| {
|
||||
[key: string]: SanitizedFieldPermissions
|
||||
}
|
||||
| true
|
||||
fields: Field[]
|
||||
i18n: I18nClient
|
||||
modifiedOnly: boolean
|
||||
parentIndexPath: string
|
||||
parentPath: string
|
||||
parentSchemaPath: string
|
||||
req: PayloadRequest
|
||||
selectedLocales: string[]
|
||||
versionSiblingData: object
|
||||
}
|
||||
|
||||
/**
|
||||
* Build up an object that contains rendered diff components for each field.
|
||||
* This is then sent to the client to be rendered.
|
||||
*
|
||||
* Here, the server is responsible for traversing through the document data and building up this
|
||||
* version state object.
|
||||
*/
|
||||
export const buildVersionFields = ({
|
||||
clientSchemaMap,
|
||||
comparisonSiblingData,
|
||||
customDiffComponents,
|
||||
entitySlug,
|
||||
fieldPermissions,
|
||||
fields,
|
||||
i18n,
|
||||
modifiedOnly,
|
||||
parentIndexPath,
|
||||
parentPath,
|
||||
parentSchemaPath,
|
||||
req,
|
||||
selectedLocales,
|
||||
versionSiblingData,
|
||||
}: BuildVersionFieldsArgs): {
|
||||
versionFields: VersionField[]
|
||||
} => {
|
||||
const versionFields: VersionField[] = []
|
||||
let fieldIndex = -1
|
||||
for (const field of fields) {
|
||||
fieldIndex++
|
||||
if (fieldIsID(field)) {
|
||||
continue
|
||||
}
|
||||
|
||||
const { indexPath, path, schemaPath } = getFieldPathsModified({
|
||||
field,
|
||||
index: fieldIndex,
|
||||
parentIndexPath: 'name' in field ? '' : parentIndexPath,
|
||||
parentPath,
|
||||
parentSchemaPath,
|
||||
})
|
||||
|
||||
const clientField = clientSchemaMap.get(entitySlug + '.' + schemaPath)
|
||||
|
||||
if (!clientField) {
|
||||
req.payload.logger.error({
|
||||
clientFieldKey: entitySlug + '.' + schemaPath,
|
||||
clientSchemaMapKeys: Array.from(clientSchemaMap.keys()),
|
||||
msg: 'No client field found for ' + entitySlug + '.' + schemaPath,
|
||||
parentPath,
|
||||
parentSchemaPath,
|
||||
path,
|
||||
schemaPath,
|
||||
})
|
||||
throw new Error('No client field found for ' + entitySlug + '.' + schemaPath)
|
||||
}
|
||||
|
||||
const versionField: VersionField = {}
|
||||
const isLocalized = 'localized' in field && field.localized
|
||||
const fieldName: null | string = 'name' in field ? field.name : null
|
||||
|
||||
const versionValue = fieldName ? versionSiblingData?.[fieldName] : versionSiblingData
|
||||
|
||||
const comparisonValue = fieldName ? comparisonSiblingData?.[fieldName] : comparisonSiblingData
|
||||
|
||||
if (isLocalized) {
|
||||
versionField.fieldByLocale = {}
|
||||
|
||||
for (const locale of selectedLocales) {
|
||||
versionField.fieldByLocale[locale] = buildVersionField({
|
||||
clientField: clientField as ClientField,
|
||||
clientSchemaMap,
|
||||
comparisonValue: comparisonValue?.[locale],
|
||||
customDiffComponents,
|
||||
entitySlug,
|
||||
field,
|
||||
fieldPermissions,
|
||||
i18n,
|
||||
indexPath,
|
||||
locale,
|
||||
modifiedOnly,
|
||||
parentPath,
|
||||
parentSchemaPath,
|
||||
path,
|
||||
req,
|
||||
schemaPath,
|
||||
selectedLocales,
|
||||
versionValue: versionValue?.[locale],
|
||||
})
|
||||
if (!versionField.fieldByLocale[locale]) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
} else {
|
||||
versionField.field = buildVersionField({
|
||||
clientField: clientField as ClientField,
|
||||
clientSchemaMap,
|
||||
comparisonValue,
|
||||
customDiffComponents,
|
||||
entitySlug,
|
||||
field,
|
||||
fieldPermissions,
|
||||
i18n,
|
||||
indexPath,
|
||||
modifiedOnly,
|
||||
parentPath,
|
||||
parentSchemaPath,
|
||||
path,
|
||||
req,
|
||||
schemaPath,
|
||||
selectedLocales,
|
||||
versionValue,
|
||||
})
|
||||
|
||||
if (!versionField.field) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
versionFields.push(versionField)
|
||||
}
|
||||
|
||||
return {
|
||||
versionFields,
|
||||
}
|
||||
}
|
||||
|
||||
const buildVersionField = ({
|
||||
clientField,
|
||||
clientSchemaMap,
|
||||
comparisonValue,
|
||||
customDiffComponents,
|
||||
entitySlug,
|
||||
field,
|
||||
fieldPermissions,
|
||||
i18n,
|
||||
indexPath,
|
||||
locale,
|
||||
modifiedOnly,
|
||||
parentPath,
|
||||
parentSchemaPath,
|
||||
path,
|
||||
req,
|
||||
schemaPath,
|
||||
selectedLocales,
|
||||
versionValue,
|
||||
}: {
|
||||
clientField: ClientField
|
||||
comparisonValue: unknown
|
||||
field: Field
|
||||
indexPath: string
|
||||
locale?: string
|
||||
modifiedOnly?: boolean
|
||||
path: string
|
||||
schemaPath: string
|
||||
versionValue: unknown
|
||||
} & Omit<
|
||||
BuildVersionFieldsArgs,
|
||||
'comparisonSiblingData' | 'fields' | 'parentIndexPath' | 'versionSiblingData'
|
||||
>): BaseVersionField | null => {
|
||||
const fieldName: null | string = 'name' in field ? field.name : null
|
||||
|
||||
const diffMethod: DiffMethod = diffMethods[field.type] || 'CHARS'
|
||||
|
||||
const hasPermission =
|
||||
fieldPermissions === true ||
|
||||
!fieldName ||
|
||||
fieldPermissions?.[fieldName] === true ||
|
||||
fieldPermissions?.[fieldName]?.read
|
||||
|
||||
const subFieldPermissions =
|
||||
fieldPermissions === true ||
|
||||
!fieldName ||
|
||||
fieldPermissions?.[fieldName] === true ||
|
||||
fieldPermissions?.[fieldName]?.fields
|
||||
|
||||
if (!hasPermission) {
|
||||
return null
|
||||
}
|
||||
|
||||
if (modifiedOnly && dequal(versionValue, comparisonValue)) {
|
||||
return null
|
||||
}
|
||||
|
||||
const CustomComponent = field?.admin?.components?.Diff ?? customDiffComponents?.[field.type]
|
||||
const DefaultComponent = diffComponents?.[field.type]
|
||||
|
||||
const baseVersionField: BaseVersionField = {
|
||||
type: field.type,
|
||||
fields: [],
|
||||
path,
|
||||
schemaPath,
|
||||
}
|
||||
|
||||
if (field.type === 'tabs' && 'tabs' in field) {
|
||||
baseVersionField.tabs = []
|
||||
let tabIndex = -1
|
||||
for (const tab of field.tabs) {
|
||||
tabIndex++
|
||||
const isNamedTab = tabHasName(tab)
|
||||
|
||||
const {
|
||||
indexPath: tabIndexPath,
|
||||
path: tabPath,
|
||||
schemaPath: tabSchemaPath,
|
||||
} = getFieldPathsModified({
|
||||
field: {
|
||||
...tab,
|
||||
type: 'tab',
|
||||
},
|
||||
index: tabIndex,
|
||||
parentIndexPath: indexPath,
|
||||
parentPath,
|
||||
parentSchemaPath,
|
||||
})
|
||||
baseVersionField.tabs.push({
|
||||
name: 'name' in tab ? tab.name : null,
|
||||
fields: buildVersionFields({
|
||||
clientSchemaMap,
|
||||
comparisonSiblingData: 'name' in tab ? comparisonValue?.[tab.name] : comparisonValue,
|
||||
customDiffComponents,
|
||||
entitySlug,
|
||||
fieldPermissions,
|
||||
fields: tab.fields,
|
||||
i18n,
|
||||
modifiedOnly,
|
||||
parentIndexPath: isNamedTab ? '' : tabIndexPath,
|
||||
parentPath: tabPath,
|
||||
parentSchemaPath: tabSchemaPath,
|
||||
req,
|
||||
selectedLocales,
|
||||
versionSiblingData: 'name' in tab ? versionValue?.[tab.name] : versionValue,
|
||||
}).versionFields,
|
||||
label: tab.label,
|
||||
})
|
||||
}
|
||||
} // At this point, we are dealing with a `row`, etc
|
||||
else if ('fields' in field) {
|
||||
if (field.type === 'array') {
|
||||
if (!Array.isArray(versionValue)) {
|
||||
throw new Error('Expected versionValue to be an array')
|
||||
}
|
||||
baseVersionField.rows = []
|
||||
|
||||
for (let i = 0; i < versionValue.length; i++) {
|
||||
const comparisonRow = comparisonValue?.[i] || {}
|
||||
const versionRow = versionValue?.[i] || {}
|
||||
baseVersionField.rows[i] = buildVersionFields({
|
||||
clientSchemaMap,
|
||||
comparisonSiblingData: comparisonRow,
|
||||
customDiffComponents,
|
||||
entitySlug,
|
||||
fieldPermissions,
|
||||
fields: field.fields,
|
||||
i18n,
|
||||
modifiedOnly,
|
||||
parentIndexPath: 'name' in field ? '' : indexPath,
|
||||
parentPath: path + '.' + i,
|
||||
parentSchemaPath: schemaPath,
|
||||
req,
|
||||
selectedLocales,
|
||||
versionSiblingData: versionRow,
|
||||
}).versionFields
|
||||
}
|
||||
} else {
|
||||
baseVersionField.fields = buildVersionFields({
|
||||
clientSchemaMap,
|
||||
comparisonSiblingData: comparisonValue as object,
|
||||
customDiffComponents,
|
||||
entitySlug,
|
||||
fieldPermissions,
|
||||
fields: field.fields,
|
||||
i18n,
|
||||
modifiedOnly,
|
||||
parentIndexPath: 'name' in field ? '' : indexPath,
|
||||
parentPath: path,
|
||||
parentSchemaPath: schemaPath,
|
||||
req,
|
||||
selectedLocales,
|
||||
versionSiblingData: versionValue as object,
|
||||
}).versionFields
|
||||
}
|
||||
} else if (field.type === 'blocks') {
|
||||
baseVersionField.rows = []
|
||||
|
||||
if (!Array.isArray(versionValue)) {
|
||||
throw new Error('Expected versionValue to be an array')
|
||||
}
|
||||
|
||||
for (let i = 0; i < versionValue.length; i++) {
|
||||
const comparisonRow = comparisonValue?.[i] || {}
|
||||
const versionRow = versionValue[i] || {}
|
||||
const versionBlock = field.blocks.find((block) => block.slug === versionRow.blockType)
|
||||
|
||||
let fields = []
|
||||
|
||||
if (versionRow.blockType === comparisonRow.blockType) {
|
||||
fields = versionBlock.fields
|
||||
} else {
|
||||
const comparisonBlock = field.blocks.find((block) => block.slug === comparisonRow.blockType)
|
||||
if (comparisonBlock) {
|
||||
fields = getUniqueListBy<Field>(
|
||||
[...versionBlock.fields, ...comparisonBlock.fields],
|
||||
'name',
|
||||
)
|
||||
} else {
|
||||
fields = versionBlock.fields
|
||||
}
|
||||
}
|
||||
|
||||
baseVersionField.rows[i] = buildVersionFields({
|
||||
clientSchemaMap,
|
||||
comparisonSiblingData: comparisonRow,
|
||||
customDiffComponents,
|
||||
entitySlug,
|
||||
fieldPermissions,
|
||||
fields,
|
||||
i18n,
|
||||
modifiedOnly,
|
||||
parentIndexPath: 'name' in field ? '' : indexPath,
|
||||
parentPath: path + '.' + i,
|
||||
parentSchemaPath: schemaPath + '.' + versionBlock.slug,
|
||||
req,
|
||||
selectedLocales,
|
||||
versionSiblingData: versionRow,
|
||||
}).versionFields
|
||||
}
|
||||
}
|
||||
|
||||
const clientCellProps: FieldDiffClientProps = {
|
||||
baseVersionField: {
|
||||
...baseVersionField,
|
||||
CustomComponent: undefined,
|
||||
},
|
||||
comparisonValue,
|
||||
diffMethod,
|
||||
field: clientField,
|
||||
fieldPermissions: subFieldPermissions,
|
||||
versionValue,
|
||||
}
|
||||
|
||||
const serverCellProps: FieldDiffServerProps = {
|
||||
...clientCellProps,
|
||||
clientField,
|
||||
field,
|
||||
i18n,
|
||||
req,
|
||||
selectedLocales,
|
||||
}
|
||||
|
||||
baseVersionField.CustomComponent = RenderServerComponent({
|
||||
clientProps: locale
|
||||
? ({
|
||||
...clientCellProps,
|
||||
locale,
|
||||
} as FieldDiffClientProps)
|
||||
: clientCellProps,
|
||||
Component: CustomComponent,
|
||||
Fallback: DefaultComponent,
|
||||
importMap: req.payload.importMap,
|
||||
key: 'diff component',
|
||||
serverProps: locale
|
||||
? ({
|
||||
...serverCellProps,
|
||||
locale,
|
||||
} as FieldDiffServerProps)
|
||||
: serverCellProps,
|
||||
})
|
||||
|
||||
return baseVersionField
|
||||
}
|
||||
@@ -1,46 +1,43 @@
|
||||
'use client'
|
||||
import type { CollapsibleFieldDiffClientComponent } from 'payload'
|
||||
|
||||
import { getTranslation } from '@payloadcms/translations'
|
||||
import { useTranslation } from '@payloadcms/ui'
|
||||
import React from 'react'
|
||||
|
||||
import type { DiffComponentProps } from '../types.js'
|
||||
|
||||
import { useSelectedLocales } from '../../../Default/SelectedLocalesContext.js'
|
||||
import { DiffCollapser } from '../../DiffCollapser/index.js'
|
||||
import { RenderFieldsToDiff } from '../../index.js'
|
||||
import { RenderVersionFieldsToDiff } from '../../RenderVersionFieldsToDiff.js'
|
||||
|
||||
const baseClass = 'collapsible-diff'
|
||||
|
||||
export const Collapsible: React.FC<DiffComponentProps> = ({
|
||||
comparison,
|
||||
diffComponents,
|
||||
export const Collapsible: CollapsibleFieldDiffClientComponent = ({
|
||||
baseVersionField,
|
||||
comparisonValue,
|
||||
field,
|
||||
fieldPermissions,
|
||||
fields,
|
||||
i18n,
|
||||
locales,
|
||||
version,
|
||||
versionValue,
|
||||
}) => {
|
||||
const { i18n } = useTranslation()
|
||||
const { selectedLocales } = useSelectedLocales()
|
||||
|
||||
if (!baseVersionField.fields?.length) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={baseClass}>
|
||||
<DiffCollapser
|
||||
comparison={comparison}
|
||||
fields={fields}
|
||||
comparison={comparisonValue}
|
||||
fields={field.fields}
|
||||
label={
|
||||
'label' in field &&
|
||||
field.label &&
|
||||
typeof field.label !== 'function' && <span>{getTranslation(field.label, i18n)}</span>
|
||||
}
|
||||
locales={locales}
|
||||
version={version}
|
||||
locales={selectedLocales}
|
||||
version={versionValue}
|
||||
>
|
||||
<RenderFieldsToDiff
|
||||
comparison={comparison}
|
||||
diffComponents={diffComponents}
|
||||
fieldPermissions={fieldPermissions}
|
||||
fields={fields}
|
||||
i18n={i18n}
|
||||
locales={locales}
|
||||
version={version}
|
||||
/>
|
||||
<RenderVersionFieldsToDiff versionFields={baseVersionField.fields} />
|
||||
</DiffCollapser>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -1,32 +1,34 @@
|
||||
'use client'
|
||||
import type { GroupFieldDiffClientComponent } from 'payload'
|
||||
|
||||
import { getTranslation } from '@payloadcms/translations'
|
||||
import React from 'react'
|
||||
|
||||
import './index.scss'
|
||||
|
||||
import type { DiffComponentProps } from '../types.js'
|
||||
import { useTranslation } from '@payloadcms/ui'
|
||||
import React from 'react'
|
||||
|
||||
import { useSelectedLocales } from '../../../Default/SelectedLocalesContext.js'
|
||||
import { DiffCollapser } from '../../DiffCollapser/index.js'
|
||||
import { RenderFieldsToDiff } from '../../index.js'
|
||||
import { RenderVersionFieldsToDiff } from '../../RenderVersionFieldsToDiff.js'
|
||||
|
||||
const baseClass = 'group-diff'
|
||||
|
||||
export const Group: React.FC<DiffComponentProps> = ({
|
||||
comparison,
|
||||
diffComponents,
|
||||
export const Group: GroupFieldDiffClientComponent = ({
|
||||
baseVersionField,
|
||||
comparisonValue,
|
||||
field,
|
||||
fieldPermissions,
|
||||
fields,
|
||||
i18n,
|
||||
locale,
|
||||
locales,
|
||||
version,
|
||||
versionValue,
|
||||
}) => {
|
||||
const { i18n } = useTranslation()
|
||||
const { selectedLocales } = useSelectedLocales()
|
||||
|
||||
return (
|
||||
<div className={baseClass}>
|
||||
<DiffCollapser
|
||||
comparison={comparison}
|
||||
fields={fields}
|
||||
comparison={comparisonValue}
|
||||
fields={field.fields}
|
||||
label={
|
||||
'label' in field &&
|
||||
field.label &&
|
||||
@@ -37,18 +39,10 @@ export const Group: React.FC<DiffComponentProps> = ({
|
||||
</span>
|
||||
)
|
||||
}
|
||||
locales={locales}
|
||||
version={version}
|
||||
locales={selectedLocales}
|
||||
version={versionValue}
|
||||
>
|
||||
<RenderFieldsToDiff
|
||||
comparison={comparison}
|
||||
diffComponents={diffComponents}
|
||||
fieldPermissions={fieldPermissions}
|
||||
fields={fields}
|
||||
i18n={i18n}
|
||||
locales={locales}
|
||||
version={version}
|
||||
/>
|
||||
<RenderVersionFieldsToDiff versionFields={baseVersionField.fields} />
|
||||
</DiffCollapser>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -1,32 +1,34 @@
|
||||
'use client'
|
||||
import type { ClientField } from 'payload'
|
||||
|
||||
import type { FieldDiffClientProps } from 'payload'
|
||||
|
||||
import { getTranslation } from '@payloadcms/translations'
|
||||
import { useTranslation } from '@payloadcms/ui'
|
||||
|
||||
import './index.scss'
|
||||
|
||||
import { fieldIsArrayType, fieldIsBlockType } from 'payload/shared'
|
||||
import React from 'react'
|
||||
|
||||
import type { DiffComponentProps } from '../types.js'
|
||||
|
||||
import { useSelectedLocales } from '../../../Default/SelectedLocalesContext.js'
|
||||
import { DiffCollapser } from '../../DiffCollapser/index.js'
|
||||
import './index.scss'
|
||||
import { RenderFieldsToDiff } from '../../index.js'
|
||||
import { RenderVersionFieldsToDiff } from '../../RenderVersionFieldsToDiff.js'
|
||||
import { getFieldsForRowComparison } from '../../utilities/getFieldsForRowComparison.js'
|
||||
|
||||
const baseClass = 'iterable-diff'
|
||||
|
||||
export const Iterable: React.FC<DiffComponentProps> = ({
|
||||
comparison,
|
||||
diffComponents,
|
||||
export const Iterable: React.FC<FieldDiffClientProps> = ({
|
||||
baseVersionField,
|
||||
comparisonValue,
|
||||
field,
|
||||
fieldPermissions,
|
||||
i18n,
|
||||
locale,
|
||||
locales,
|
||||
modifiedOnly,
|
||||
version,
|
||||
versionValue,
|
||||
}) => {
|
||||
const versionRowCount = Array.isArray(version) ? version.length : 0
|
||||
const comparisonRowCount = Array.isArray(comparison) ? comparison.length : 0
|
||||
const { i18n } = useTranslation()
|
||||
const { selectedLocales } = useSelectedLocales()
|
||||
|
||||
const versionRowCount = Array.isArray(versionValue) ? versionValue.length : 0
|
||||
const comparisonRowCount = Array.isArray(comparisonValue) ? comparisonValue.length : 0
|
||||
const maxRows = Math.max(versionRowCount, comparisonRowCount)
|
||||
|
||||
if (!fieldIsArrayType(field) && !fieldIsBlockType(field)) {
|
||||
@@ -36,7 +38,7 @@ export const Iterable: React.FC<DiffComponentProps> = ({
|
||||
return (
|
||||
<div className={baseClass}>
|
||||
<DiffCollapser
|
||||
comparison={comparison}
|
||||
comparison={comparisonValue}
|
||||
field={field}
|
||||
isIterable
|
||||
label={
|
||||
@@ -49,18 +51,20 @@ export const Iterable: React.FC<DiffComponentProps> = ({
|
||||
</span>
|
||||
)
|
||||
}
|
||||
locales={locales}
|
||||
version={version}
|
||||
locales={selectedLocales}
|
||||
version={versionValue}
|
||||
>
|
||||
{maxRows > 0 && (
|
||||
<div className={`${baseClass}__rows`}>
|
||||
{Array.from(Array(maxRows).keys()).map((row, i) => {
|
||||
const versionRow = version?.[i] || {}
|
||||
const comparisonRow = comparison?.[i] || {}
|
||||
const versionRow = versionValue?.[i] || {}
|
||||
const comparisonRow = comparisonValue?.[i] || {}
|
||||
|
||||
const fields: ClientField[] = getFieldsForRowComparison({
|
||||
const { fields, versionFields } = getFieldsForRowComparison({
|
||||
baseVersionField,
|
||||
comparisonRow,
|
||||
field,
|
||||
row: i,
|
||||
versionRow,
|
||||
})
|
||||
|
||||
@@ -73,19 +77,11 @@ export const Iterable: React.FC<DiffComponentProps> = ({
|
||||
comparison={comparisonRow}
|
||||
fields={fields}
|
||||
label={rowLabel}
|
||||
locales={locales}
|
||||
locales={selectedLocales}
|
||||
version={versionRow}
|
||||
>
|
||||
<RenderFieldsToDiff
|
||||
comparison={comparisonRow}
|
||||
diffComponents={diffComponents}
|
||||
fieldPermissions={fieldPermissions}
|
||||
fields={fields}
|
||||
i18n={i18n}
|
||||
locales={locales}
|
||||
modifiedOnly
|
||||
version={versionRow}
|
||||
/></DiffCollapser>
|
||||
<RenderVersionFieldsToDiff versionFields={versionFields} />
|
||||
</DiffCollapser>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
|
||||
@@ -1,17 +1,19 @@
|
||||
'use client'
|
||||
import type { ClientCollectionConfig, ClientField, RelationshipFieldClient } from 'payload'
|
||||
import type {
|
||||
ClientCollectionConfig,
|
||||
ClientField,
|
||||
RelationshipFieldDiffClientComponent,
|
||||
} from 'payload'
|
||||
|
||||
import { getTranslation } from '@payloadcms/translations'
|
||||
import { useConfig } from '@payloadcms/ui'
|
||||
import { useConfig, useTranslation } from '@payloadcms/ui'
|
||||
import { fieldAffectsData, fieldIsPresentationalOnly } from 'payload/shared'
|
||||
import React from 'react'
|
||||
import ReactDiffViewer from 'react-diff-viewer-continued'
|
||||
|
||||
import type { DiffComponentProps } from '../types.js'
|
||||
|
||||
import Label from '../../Label/index.js'
|
||||
import { diffStyles } from '../styles.js'
|
||||
import './index.scss'
|
||||
import { diffStyles } from '../styles.js'
|
||||
|
||||
const baseClass = 'relationship-diff'
|
||||
|
||||
@@ -96,13 +98,14 @@ const generateLabelFromValue = (
|
||||
return valueToReturn
|
||||
}
|
||||
|
||||
export const Relationship: React.FC<DiffComponentProps<RelationshipFieldClient>> = ({
|
||||
comparison,
|
||||
export const Relationship: RelationshipFieldDiffClientComponent = ({
|
||||
comparisonValue,
|
||||
field,
|
||||
i18n,
|
||||
locale,
|
||||
version,
|
||||
versionValue,
|
||||
}) => {
|
||||
const { i18n } = useTranslation()
|
||||
|
||||
const placeholder = `[${i18n.t('general:noValue')}]`
|
||||
|
||||
const {
|
||||
@@ -112,25 +115,27 @@ export const Relationship: React.FC<DiffComponentProps<RelationshipFieldClient>>
|
||||
let versionToRender: string | undefined = placeholder
|
||||
let comparisonToRender: string | undefined = placeholder
|
||||
|
||||
if (version) {
|
||||
if ('hasMany' in field && field.hasMany && Array.isArray(version)) {
|
||||
if (versionValue) {
|
||||
if ('hasMany' in field && field.hasMany && Array.isArray(versionValue)) {
|
||||
versionToRender =
|
||||
version.map((val) => generateLabelFromValue(collections, field, locale, val)).join(', ') ||
|
||||
placeholder
|
||||
versionValue
|
||||
.map((val) => generateLabelFromValue(collections, field, locale, val))
|
||||
.join(', ') || placeholder
|
||||
} else {
|
||||
versionToRender = generateLabelFromValue(collections, field, locale, version) || placeholder
|
||||
versionToRender =
|
||||
generateLabelFromValue(collections, field, locale, versionValue) || placeholder
|
||||
}
|
||||
}
|
||||
|
||||
if (comparison) {
|
||||
if ('hasMany' in field && field.hasMany && Array.isArray(comparison)) {
|
||||
if (comparisonValue) {
|
||||
if ('hasMany' in field && field.hasMany && Array.isArray(comparisonValue)) {
|
||||
comparisonToRender =
|
||||
comparison
|
||||
comparisonValue
|
||||
.map((val) => generateLabelFromValue(collections, field, locale, val))
|
||||
.join(', ') || placeholder
|
||||
} else {
|
||||
comparisonToRender =
|
||||
generateLabelFromValue(collections, field, locale, comparison) || placeholder
|
||||
generateLabelFromValue(collections, field, locale, comparisonValue) || placeholder
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,38 +1,16 @@
|
||||
'use client'
|
||||
import { getTranslation } from '@payloadcms/translations'
|
||||
import type { RowFieldDiffClientComponent } from 'payload'
|
||||
|
||||
import React from 'react'
|
||||
|
||||
import type { DiffComponentProps } from '../types.js'
|
||||
|
||||
import { RenderFieldsToDiff } from '../../index.js'
|
||||
import Label from '../../Label/index.js'
|
||||
import { RenderVersionFieldsToDiff } from '../../RenderVersionFieldsToDiff.js'
|
||||
|
||||
const baseClass = 'row-diff'
|
||||
|
||||
export const Row: React.FC<DiffComponentProps> = ({
|
||||
comparison,
|
||||
diffComponents,
|
||||
field,
|
||||
fieldPermissions,
|
||||
fields,
|
||||
i18n,
|
||||
locales,
|
||||
version,
|
||||
}) => {
|
||||
export const Row: RowFieldDiffClientComponent = ({ baseVersionField }) => {
|
||||
return (
|
||||
<div className={baseClass}>
|
||||
{'label' in field && field.label && typeof field.label !== 'function' && (
|
||||
<Label>{getTranslation(field.label, i18n)}</Label>
|
||||
)}
|
||||
<RenderFieldsToDiff
|
||||
comparison={comparison}
|
||||
diffComponents={diffComponents}
|
||||
fieldPermissions={fieldPermissions}
|
||||
fields={fields}
|
||||
i18n={i18n}
|
||||
locales={locales}
|
||||
version={version}
|
||||
/>
|
||||
<RenderVersionFieldsToDiff versionFields={baseVersionField.fields} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
'use client'
|
||||
import type { I18nClient } from '@payloadcms/translations'
|
||||
import type { OptionObject, SelectField, SelectFieldClient } from 'payload'
|
||||
import type { OptionObject, SelectField, SelectFieldDiffClientComponent } from 'payload'
|
||||
|
||||
import { getTranslation } from '@payloadcms/translations'
|
||||
import { useTranslation } from '@payloadcms/ui'
|
||||
import React from 'react'
|
||||
|
||||
import type { DiffComponentProps } from '../types.js'
|
||||
|
||||
import Label from '../../Label/index.js'
|
||||
import './index.scss'
|
||||
import { diffStyles } from '../styles.js'
|
||||
import { DiffViewer } from './DiffViewer/index.js'
|
||||
import './index.scss'
|
||||
|
||||
const baseClass = 'select-diff'
|
||||
|
||||
@@ -45,30 +44,45 @@ const getTranslatedOptions = (
|
||||
return typeof options === 'string' ? options : getTranslation(options.label, i18n)
|
||||
}
|
||||
|
||||
export const Select: React.FC<DiffComponentProps<SelectFieldClient>> = ({
|
||||
comparison,
|
||||
export const Select: SelectFieldDiffClientComponent = ({
|
||||
comparisonValue,
|
||||
diffMethod,
|
||||
field,
|
||||
i18n,
|
||||
locale,
|
||||
version,
|
||||
versionValue,
|
||||
}) => {
|
||||
const { i18n } = useTranslation()
|
||||
|
||||
let placeholder = ''
|
||||
|
||||
if (version === comparison) {
|
||||
if (versionValue == comparisonValue) {
|
||||
placeholder = `[${i18n.t('general:noValue')}]`
|
||||
}
|
||||
|
||||
const options = 'options' in field && field.options
|
||||
|
||||
const comparisonToRender =
|
||||
typeof comparison !== 'undefined'
|
||||
? getTranslatedOptions(getOptionsToRender(comparison, options, field.hasMany), i18n)
|
||||
typeof comparisonValue !== 'undefined'
|
||||
? getTranslatedOptions(
|
||||
getOptionsToRender(
|
||||
typeof comparisonValue === 'string' ? comparisonValue : JSON.stringify(comparisonValue),
|
||||
options,
|
||||
field.hasMany,
|
||||
),
|
||||
i18n,
|
||||
)
|
||||
: placeholder
|
||||
|
||||
const versionToRender =
|
||||
typeof version !== 'undefined'
|
||||
? getTranslatedOptions(getOptionsToRender(version, options, field.hasMany), i18n)
|
||||
typeof versionValue !== 'undefined'
|
||||
? getTranslatedOptions(
|
||||
getOptionsToRender(
|
||||
typeof versionValue === 'string' ? versionValue : JSON.stringify(versionValue),
|
||||
options,
|
||||
field.hasMany,
|
||||
),
|
||||
i18n,
|
||||
)
|
||||
: placeholder
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,37 +1,55 @@
|
||||
'use client'
|
||||
import type { ClientTab, TabsFieldClient } from 'payload'
|
||||
import type {
|
||||
ClientTab,
|
||||
FieldDiffClientProps,
|
||||
TabsFieldClient,
|
||||
TabsFieldDiffClientComponent,
|
||||
VersionTab,
|
||||
} from 'payload'
|
||||
|
||||
import { getTranslation } from '@payloadcms/translations'
|
||||
import { useTranslation } from '@payloadcms/ui'
|
||||
import React from 'react'
|
||||
|
||||
import type { DiffComponentProps } from '../types.js'
|
||||
|
||||
import { DiffCollapser } from '../../DiffCollapser/index.js'
|
||||
import { RenderFieldsToDiff } from '../../index.js'
|
||||
import './index.scss'
|
||||
import { useSelectedLocales } from '../../../Default/SelectedLocalesContext.js'
|
||||
import { DiffCollapser } from '../../DiffCollapser/index.js'
|
||||
import { RenderVersionFieldsToDiff } from '../../RenderVersionFieldsToDiff.js'
|
||||
|
||||
const baseClass = 'tabs-diff'
|
||||
|
||||
export const Tabs: React.FC<DiffComponentProps<TabsFieldClient>> = (props) => {
|
||||
const { comparison, field, locales, version } = props
|
||||
export const Tabs: TabsFieldDiffClientComponent = (props) => {
|
||||
const { baseVersionField, comparisonValue, field, versionValue } = props
|
||||
const { selectedLocales } = useSelectedLocales()
|
||||
|
||||
return (
|
||||
<div className={baseClass}>
|
||||
{field.tabs.map((tab, i) => {
|
||||
{baseVersionField.tabs.map((tab, i) => {
|
||||
if (!tab?.fields?.length) {
|
||||
return null
|
||||
}
|
||||
const fieldTab = field.tabs?.[i]
|
||||
return (
|
||||
<div className={`${baseClass}__tab`} key={i}>
|
||||
{(() => {
|
||||
if ('name' in tab && locales && tab.localized) {
|
||||
if ('name' in fieldTab && selectedLocales && fieldTab.localized) {
|
||||
// Named localized tab
|
||||
return locales.map((locale, index) => {
|
||||
return selectedLocales.map((locale, index) => {
|
||||
const localizedTabProps = {
|
||||
...props,
|
||||
comparison: comparison?.[tab.name]?.[locale],
|
||||
version: version?.[tab.name]?.[locale],
|
||||
comparison: comparisonValue?.[tab.name]?.[locale],
|
||||
version: versionValue?.[tab.name]?.[locale],
|
||||
}
|
||||
return (
|
||||
<div className={`${baseClass}__tab-locale`} key={[locale, index].join('-')}>
|
||||
<div className={`${baseClass}__tab-locale-value`}>
|
||||
<Tab key={locale} {...localizedTabProps} locale={locale} tab={tab} />
|
||||
<Tab
|
||||
key={locale}
|
||||
{...localizedTabProps}
|
||||
fieldTab={fieldTab}
|
||||
locale={locale}
|
||||
tab={tab}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
@@ -40,13 +58,13 @@ export const Tabs: React.FC<DiffComponentProps<TabsFieldClient>> = (props) => {
|
||||
// Named tab
|
||||
const namedTabProps = {
|
||||
...props,
|
||||
comparison: comparison?.[tab.name],
|
||||
version: version?.[tab.name],
|
||||
comparison: comparisonValue?.[tab.name],
|
||||
version: versionValue?.[tab.name],
|
||||
}
|
||||
return <Tab key={i} {...namedTabProps} tab={tab} />
|
||||
return <Tab fieldTab={fieldTab} key={i} {...namedTabProps} tab={tab} />
|
||||
} else {
|
||||
// Unnamed tab
|
||||
return <Tab key={i} {...props} tab={tab} />
|
||||
return <Tab fieldTab={fieldTab} key={i} {...props} tab={tab} />
|
||||
}
|
||||
})()}
|
||||
</div>
|
||||
@@ -57,24 +75,22 @@ export const Tabs: React.FC<DiffComponentProps<TabsFieldClient>> = (props) => {
|
||||
}
|
||||
|
||||
type TabProps = {
|
||||
tab: ClientTab
|
||||
} & DiffComponentProps<TabsFieldClient>
|
||||
fieldTab: ClientTab
|
||||
tab: VersionTab
|
||||
} & FieldDiffClientProps<TabsFieldClient>
|
||||
|
||||
const Tab: React.FC<TabProps> = ({ comparisonValue, fieldTab, locale, tab, versionValue }) => {
|
||||
const { i18n } = useTranslation()
|
||||
const { selectedLocales } = useSelectedLocales()
|
||||
|
||||
if (!tab.fields?.length) {
|
||||
return null
|
||||
}
|
||||
|
||||
const Tab: React.FC<TabProps> = ({
|
||||
comparison,
|
||||
diffComponents,
|
||||
fieldPermissions,
|
||||
i18n,
|
||||
locale,
|
||||
locales,
|
||||
modifiedOnly,
|
||||
tab,
|
||||
version,
|
||||
}) => {
|
||||
return (
|
||||
<DiffCollapser
|
||||
comparison={comparison}
|
||||
fields={tab.fields}
|
||||
comparison={comparisonValue}
|
||||
fields={fieldTab.fields}
|
||||
label={
|
||||
'label' in tab &&
|
||||
tab.label &&
|
||||
@@ -85,19 +101,10 @@ const Tab: React.FC<TabProps> = ({
|
||||
</span>
|
||||
)
|
||||
}
|
||||
locales={locales}
|
||||
version={version}
|
||||
locales={selectedLocales}
|
||||
version={versionValue}
|
||||
>
|
||||
<RenderFieldsToDiff
|
||||
comparison={comparison}
|
||||
diffComponents={diffComponents}
|
||||
fieldPermissions={fieldPermissions}
|
||||
fields={tab.fields}
|
||||
i18n={i18n}
|
||||
locales={locales}
|
||||
modifiedOnly={modifiedOnly}
|
||||
version={version}
|
||||
/>
|
||||
<RenderVersionFieldsToDiff versionFields={tab.fields} />
|
||||
</DiffCollapser>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,44 +1,36 @@
|
||||
'use client'
|
||||
import type { TextFieldClient } from 'payload'
|
||||
import type { TextFieldDiffClientComponent } from 'payload'
|
||||
|
||||
import { getTranslation } from '@payloadcms/translations'
|
||||
import { useTranslation } from '@payloadcms/ui'
|
||||
import React from 'react'
|
||||
|
||||
import type { DiffComponentProps } from '../types.js'
|
||||
|
||||
import Label from '../../Label/index.js'
|
||||
import './index.scss'
|
||||
import { diffStyles } from '../styles.js'
|
||||
import { DiffViewer } from './DiffViewer/index.js'
|
||||
import './index.scss'
|
||||
|
||||
const baseClass = 'text-diff'
|
||||
|
||||
export const Text: React.FC<DiffComponentProps<TextFieldClient>> = ({
|
||||
comparison,
|
||||
export const Text: TextFieldDiffClientComponent = ({
|
||||
comparisonValue,
|
||||
diffMethod,
|
||||
field,
|
||||
i18n,
|
||||
isRichText = false,
|
||||
locale,
|
||||
version,
|
||||
versionValue,
|
||||
}) => {
|
||||
const { i18n } = useTranslation()
|
||||
|
||||
let placeholder = ''
|
||||
|
||||
if (version === comparison) {
|
||||
if (versionValue == comparisonValue) {
|
||||
placeholder = `[${i18n.t('general:noValue')}]`
|
||||
}
|
||||
|
||||
let versionToRender = version
|
||||
let comparisonToRender = comparison
|
||||
|
||||
if (isRichText) {
|
||||
if (typeof version === 'object') {
|
||||
versionToRender = JSON.stringify(version, null, 2)
|
||||
}
|
||||
if (typeof comparison === 'object') {
|
||||
comparisonToRender = JSON.stringify(comparison, null, 2)
|
||||
}
|
||||
}
|
||||
const versionToRender: string =
|
||||
typeof versionValue === 'string' ? versionValue : JSON.stringify(versionValue, null, 2)
|
||||
const comparisonToRender =
|
||||
typeof comparisonValue === 'string' ? comparisonValue : JSON.stringify(comparisonValue, null, 2)
|
||||
|
||||
return (
|
||||
<div className={baseClass}>
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import type { FieldDiffClientProps, FieldTypes } from 'payload'
|
||||
|
||||
import { Collapsible } from './Collapsible/index.js'
|
||||
import { Group } from './Group/index.js'
|
||||
import { Iterable } from './Iterable/index.js'
|
||||
@@ -7,7 +9,7 @@ import { Select } from './Select/index.js'
|
||||
import { Tabs } from './Tabs/index.js'
|
||||
import { Text } from './Text/index.js'
|
||||
|
||||
export const diffComponents = {
|
||||
export const diffComponents: Record<FieldTypes, React.ComponentType<FieldDiffClientProps>> = {
|
||||
array: Iterable,
|
||||
blocks: Iterable,
|
||||
checkbox: Text,
|
||||
@@ -16,6 +18,7 @@ export const diffComponents = {
|
||||
date: Text,
|
||||
email: Text,
|
||||
group: Group,
|
||||
join: null,
|
||||
json: Text,
|
||||
number: Text,
|
||||
point: Text,
|
||||
@@ -27,5 +30,6 @@ export const diffComponents = {
|
||||
tabs: Tabs,
|
||||
text: Text,
|
||||
textarea: Text,
|
||||
ui: null,
|
||||
upload: Relationship,
|
||||
}
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
import type { I18nClient } from '@payloadcms/translations'
|
||||
import type { ClientField, SanitizedFieldPermissions } from 'payload'
|
||||
import type React from 'react'
|
||||
import type { DiffMethod } from 'react-diff-viewer-continued'
|
||||
|
||||
export type DiffComponents = Record<string, React.FC<DiffComponentProps>>
|
||||
|
||||
export type DiffComponentProps<TField extends ClientField = ClientField> = {
|
||||
readonly comparison: any
|
||||
readonly diffComponents: DiffComponents
|
||||
readonly diffMethod?: DiffMethod
|
||||
readonly field: TField
|
||||
readonly fieldPermissions?:
|
||||
| {
|
||||
[key: string]: SanitizedFieldPermissions
|
||||
}
|
||||
| true
|
||||
readonly fields: ClientField[]
|
||||
readonly i18n: I18nClient
|
||||
readonly isRichText?: boolean
|
||||
readonly locale?: string
|
||||
readonly locales?: string[]
|
||||
readonly modifiedOnly?: boolean
|
||||
readonly version: any
|
||||
}
|
||||
@@ -1,177 +1,8 @@
|
||||
'use client'
|
||||
import type { DiffMethod } from 'react-diff-viewer-continued'
|
||||
import { buildVersionFields, type BuildVersionFieldsArgs } from './buildVersionFields.js'
|
||||
import { RenderVersionFieldsToDiff } from './RenderVersionFieldsToDiff.js'
|
||||
|
||||
import { ShimmerEffect } from '@payloadcms/ui'
|
||||
import { dequal } from 'dequal/lite'
|
||||
import { fieldAffectsData, fieldIsID } from 'payload/shared'
|
||||
import React, { Fragment, useEffect } from 'react'
|
||||
export const RenderDiff = (args: BuildVersionFieldsArgs): React.ReactNode => {
|
||||
const { versionFields } = buildVersionFields(args)
|
||||
|
||||
import type { diffComponents as _diffComponents } from './fields/index.js'
|
||||
import type { FieldDiffProps, Props } from './types.js'
|
||||
|
||||
import { diffMethods } from './fields/diffMethods.js'
|
||||
import './index.scss'
|
||||
|
||||
const baseClass = 'render-field-diffs'
|
||||
|
||||
export const RenderFieldsToDiff: React.FC<Props> = ({
|
||||
comparison,
|
||||
diffComponents: __diffComponents,
|
||||
fieldPermissions,
|
||||
fields,
|
||||
i18n,
|
||||
locales,
|
||||
modifiedOnly,
|
||||
version,
|
||||
}) => {
|
||||
// typing it as `as typeof _diffComponents` here ensures the TField generics of DiffComponentProps are respected.
|
||||
// Without it, you could pass a UI field to the Tabs component, without it erroring
|
||||
const diffComponents: typeof _diffComponents = __diffComponents as typeof _diffComponents
|
||||
|
||||
const [hasMounted, setHasMounted] = React.useState(false)
|
||||
|
||||
// defer rendering until after the first mount as the CSS is loaded with Emotion
|
||||
// this will ensure that the CSS is loaded before rendering the diffs and prevent CLS
|
||||
useEffect(() => {
|
||||
setHasMounted(true)
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div className={baseClass}>
|
||||
{!hasMounted ? (
|
||||
<Fragment>
|
||||
<ShimmerEffect height="8rem" width="100%" />
|
||||
</Fragment>
|
||||
) : (
|
||||
<Fragment>
|
||||
{fields?.map((field, i) => {
|
||||
if (fieldIsID(field)) {
|
||||
return null
|
||||
}
|
||||
|
||||
const Component = diffComponents[field.type]
|
||||
|
||||
const isRichText = field.type === 'richText'
|
||||
const diffMethod: DiffMethod = diffMethods[field.type] || 'CHARS'
|
||||
|
||||
if (Component) {
|
||||
if (fieldAffectsData(field)) {
|
||||
const fieldName = field.name
|
||||
const valueIsObject = field.type === 'code' || field.type === 'json'
|
||||
|
||||
const versionValue = valueIsObject
|
||||
? JSON.stringify(version?.[fieldName])
|
||||
: version?.[fieldName]
|
||||
|
||||
const comparisonValue = valueIsObject
|
||||
? JSON.stringify(comparison?.[fieldName])
|
||||
: comparison?.[fieldName]
|
||||
|
||||
if (modifiedOnly && dequal(versionValue, comparisonValue)) {
|
||||
return null
|
||||
}
|
||||
|
||||
const hasPermission =
|
||||
fieldPermissions === true ||
|
||||
fieldPermissions?.[fieldName] === true ||
|
||||
fieldPermissions?.[fieldName]?.read
|
||||
|
||||
const subFieldPermissions =
|
||||
fieldPermissions === true ||
|
||||
fieldPermissions?.[fieldName] === true ||
|
||||
fieldPermissions?.[fieldName]?.fields
|
||||
|
||||
if (!hasPermission) {
|
||||
return null
|
||||
}
|
||||
|
||||
const baseCellProps: FieldDiffProps = {
|
||||
comparison: comparisonValue,
|
||||
diffComponents,
|
||||
diffMethod,
|
||||
field,
|
||||
fieldPermissions: subFieldPermissions,
|
||||
fields: 'fields' in field ? field?.fields : fields,
|
||||
i18n,
|
||||
isRichText,
|
||||
locales,
|
||||
modifiedOnly,
|
||||
version: versionValue,
|
||||
}
|
||||
|
||||
if (field.localized) {
|
||||
return (
|
||||
<div className={`${baseClass}__field`} key={i}>
|
||||
{locales.map((locale, index) => {
|
||||
const versionLocaleValue = versionValue?.[locale]
|
||||
const comparisonLocaleValue = comparisonValue?.[locale]
|
||||
|
||||
const cellProps = {
|
||||
...baseCellProps,
|
||||
comparison: comparisonLocaleValue,
|
||||
version: versionLocaleValue,
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={`${baseClass}__locale`} key={[locale, index].join('-')}>
|
||||
<div className={`${baseClass}__locale-value`}>
|
||||
<Component {...cellProps} locale={locale} />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={`${baseClass}__field`} key={i}>
|
||||
<Component {...baseCellProps} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
if (field.type === 'tabs' && 'tabs' in field) {
|
||||
const Tabs = diffComponents.tabs
|
||||
|
||||
return (
|
||||
<Tabs
|
||||
comparison={comparison}
|
||||
diffComponents={diffComponents}
|
||||
field={field}
|
||||
fieldPermissions={fieldPermissions}
|
||||
fields={[]}
|
||||
i18n={i18n}
|
||||
key={i}
|
||||
locales={locales}
|
||||
version={version}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
// At this point, we are dealing with a field with subfields but no
|
||||
// nested data, eg. row, collapsible, etc.
|
||||
if ('fields' in field) {
|
||||
return (
|
||||
<Component
|
||||
comparison={comparison}
|
||||
diffComponents={diffComponents}
|
||||
field={field}
|
||||
fieldPermissions={fieldPermissions}
|
||||
fields={field.fields}
|
||||
i18n={i18n}
|
||||
key={i}
|
||||
locales={locales}
|
||||
version={version}
|
||||
/>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
})}
|
||||
</Fragment>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
return <RenderVersionFieldsToDiff versionFields={versionFields} />
|
||||
}
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
import type { I18nClient } from '@payloadcms/translations'
|
||||
import type { ClientField, SanitizedFieldPermissions } from 'payload'
|
||||
import type { DiffMethod } from 'react-diff-viewer-continued'
|
||||
|
||||
import type { DiffComponents } from './fields/types.js'
|
||||
|
||||
export type Props = {
|
||||
readonly comparison: Record<string, any>
|
||||
readonly diffComponents: DiffComponents
|
||||
readonly fieldPermissions:
|
||||
| {
|
||||
[key: string]: SanitizedFieldPermissions
|
||||
}
|
||||
| true
|
||||
readonly fields: ClientField[]
|
||||
readonly i18n: I18nClient
|
||||
readonly locales: string[]
|
||||
readonly modifiedOnly?: boolean
|
||||
readonly version: Record<string, any>
|
||||
}
|
||||
|
||||
export type FieldDiffProps = {
|
||||
diffMethod: DiffMethod
|
||||
field: ClientField
|
||||
isRichText: boolean
|
||||
} & Props
|
||||
@@ -178,9 +178,11 @@ export function countChangedFieldsInRows({
|
||||
const comparisonRow = comparisonRows?.[i] || {}
|
||||
const versionRow = versionRows?.[i] || {}
|
||||
|
||||
const rowFields = getFieldsForRowComparison({
|
||||
const { fields: rowFields } = getFieldsForRowComparison({
|
||||
baseVersionField: { type: 'text', fields: [], path: '', schemaPath: '' }, // Doesn't matter, as we don't need the versionFields output here
|
||||
comparisonRow,
|
||||
field,
|
||||
row: i,
|
||||
versionRow,
|
||||
})
|
||||
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
import type { ClientField, Field, Tab, TabAsFieldClient } from 'payload'
|
||||
|
||||
type Args = {
|
||||
field: ClientField | Field | Tab | TabAsFieldClient
|
||||
index: number
|
||||
parentIndexPath: string
|
||||
parentPath: string
|
||||
parentSchemaPath: string
|
||||
}
|
||||
|
||||
type FieldPaths = {
|
||||
/**
|
||||
* A string of '-' separated indexes representing where
|
||||
* to find this field in a given field schema array.
|
||||
* It will always be complete and accurate.
|
||||
*/
|
||||
indexPath: string
|
||||
/**
|
||||
* Path for this field relative to its position in the data.
|
||||
*/
|
||||
path: string
|
||||
/**
|
||||
* Path for this field relative to its position in the schema.
|
||||
*/
|
||||
schemaPath: string
|
||||
}
|
||||
|
||||
export function getFieldPathsModified({
|
||||
field,
|
||||
index,
|
||||
parentIndexPath,
|
||||
parentPath,
|
||||
parentSchemaPath,
|
||||
}: Args): FieldPaths {
|
||||
const parentPathSegments = parentPath.split('.')
|
||||
|
||||
const parentIsUnnamed = parentPathSegments[parentPathSegments.length - 1].startsWith('_index-')
|
||||
|
||||
const parentWithoutIndex = parentIsUnnamed
|
||||
? parentPathSegments.slice(0, -1).join('.')
|
||||
: parentPath
|
||||
|
||||
const parentPathToUse = parentIsUnnamed ? parentWithoutIndex : parentPath
|
||||
|
||||
const parentSchemaPathSegments = parentSchemaPath.split('.')
|
||||
const parentSchemaIsUnnamed =
|
||||
parentSchemaPathSegments[parentSchemaPathSegments.length - 1].startsWith('_index-')
|
||||
const parentSchemaWithoutIndex = parentSchemaIsUnnamed
|
||||
? parentSchemaPathSegments.slice(0, -1).join('.')
|
||||
: parentSchemaPath
|
||||
const parentSchemaPathToUse = parentSchemaIsUnnamed ? parentSchemaWithoutIndex : parentSchemaPath
|
||||
|
||||
if ('name' in field) {
|
||||
return {
|
||||
indexPath: '',
|
||||
path: `${parentPathToUse ? parentPathToUse + '.' : ''}${field.name}`,
|
||||
schemaPath: `${parentSchemaPathToUse ? parentSchemaPathToUse + '.' : ''}${field.name}`,
|
||||
}
|
||||
}
|
||||
|
||||
const indexSuffix = `_index-${`${parentIndexPath ? parentIndexPath + '-' : ''}${index}`}`
|
||||
|
||||
return {
|
||||
indexPath: `${parentIndexPath ? parentIndexPath + '-' : ''}${index}`,
|
||||
path: `${parentPathToUse ? parentPathToUse + '.' : ''}${indexSuffix}`,
|
||||
schemaPath: `${!parentIsUnnamed && parentSchemaPathToUse ? parentSchemaPathToUse + '.' : ''}${indexSuffix}`,
|
||||
}
|
||||
}
|
||||
@@ -15,13 +15,15 @@ describe('getFieldsForRowComparison', () => {
|
||||
fields: arrayFields,
|
||||
}
|
||||
|
||||
const result = getFieldsForRowComparison({
|
||||
const { fields } = getFieldsForRowComparison({
|
||||
field,
|
||||
versionRow: {},
|
||||
comparisonRow: {},
|
||||
row: 0,
|
||||
baseVersionField: { fields: [] },
|
||||
})
|
||||
|
||||
expect(result).toEqual(arrayFields)
|
||||
expect(fields).toEqual(arrayFields)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -46,13 +48,15 @@ describe('getFieldsForRowComparison', () => {
|
||||
const versionRow = { blockType: 'blockA' }
|
||||
const comparisonRow = { blockType: 'blockA' }
|
||||
|
||||
const result = getFieldsForRowComparison({
|
||||
const { fields } = getFieldsForRowComparison({
|
||||
field,
|
||||
versionRow,
|
||||
comparisonRow,
|
||||
row: 0,
|
||||
baseVersionField: { fields: [] },
|
||||
})
|
||||
|
||||
expect(result).toEqual(blockAFields)
|
||||
expect(fields).toEqual(blockAFields)
|
||||
})
|
||||
|
||||
it('should return unique combined fields when block types differ', () => {
|
||||
@@ -80,14 +84,16 @@ describe('getFieldsForRowComparison', () => {
|
||||
const versionRow = { blockType: 'blockA' }
|
||||
const comparisonRow = { blockType: 'blockB' }
|
||||
|
||||
const result = getFieldsForRowComparison({
|
||||
const { fields } = getFieldsForRowComparison({
|
||||
field,
|
||||
versionRow,
|
||||
comparisonRow,
|
||||
row: 0,
|
||||
baseVersionField: { fields: [] },
|
||||
})
|
||||
|
||||
// Should contain all unique fields from both blocks
|
||||
expect(result).toEqual([
|
||||
expect(fields).toEqual([
|
||||
{ name: 'a', type: 'text' },
|
||||
{ name: 'b', type: 'text' },
|
||||
{ name: 'c', type: 'text' },
|
||||
|
||||
@@ -1,4 +1,10 @@
|
||||
import type { ArrayFieldClient, BlocksFieldClient, ClientField } from 'payload'
|
||||
import type {
|
||||
ArrayFieldClient,
|
||||
BaseVersionField,
|
||||
BlocksFieldClient,
|
||||
ClientField,
|
||||
VersionField,
|
||||
} from 'payload'
|
||||
|
||||
import { getUniqueListBy } from 'payload/shared'
|
||||
|
||||
@@ -9,21 +15,27 @@ import { getUniqueListBy } from 'payload/shared'
|
||||
* because the fields from the version and comparison rows may differ.
|
||||
*/
|
||||
export function getFieldsForRowComparison({
|
||||
baseVersionField,
|
||||
comparisonRow,
|
||||
field,
|
||||
row,
|
||||
versionRow,
|
||||
}: {
|
||||
baseVersionField: BaseVersionField
|
||||
comparisonRow: any
|
||||
field: ArrayFieldClient | BlocksFieldClient
|
||||
row: number
|
||||
versionRow: any
|
||||
}) {
|
||||
}): { fields: ClientField[]; versionFields: VersionField[] } {
|
||||
let fields: ClientField[] = []
|
||||
let versionFields: VersionField[] = []
|
||||
|
||||
if (field.type === 'array' && 'fields' in field) {
|
||||
fields = field.fields
|
||||
}
|
||||
|
||||
if (field.type === 'blocks') {
|
||||
versionFields = baseVersionField.rows?.length
|
||||
? baseVersionField.rows[row]
|
||||
: baseVersionField.fields
|
||||
} else if (field.type === 'blocks') {
|
||||
if (versionRow?.blockType === comparisonRow?.blockType) {
|
||||
const matchedBlock = ('blocks' in field &&
|
||||
field.blocks?.find((block) => block.slug === versionRow?.blockType)) || {
|
||||
@@ -31,6 +43,9 @@ export function getFieldsForRowComparison({
|
||||
}
|
||||
|
||||
fields = matchedBlock.fields
|
||||
versionFields = baseVersionField.rows?.length
|
||||
? baseVersionField.rows[row]
|
||||
: baseVersionField.fields
|
||||
} else {
|
||||
const matchedVersionBlock = ('blocks' in field &&
|
||||
field.blocks?.find((block) => block.slug === versionRow?.blockType)) || {
|
||||
@@ -45,8 +60,13 @@ export function getFieldsForRowComparison({
|
||||
[...matchedVersionBlock.fields, ...matchedComparisonBlock.fields],
|
||||
'name',
|
||||
)
|
||||
|
||||
// buildVersionFields already merged the fields of the version and comparison rows together
|
||||
versionFields = baseVersionField.rows?.length
|
||||
? baseVersionField.rows[row]
|
||||
: baseVersionField.fields
|
||||
}
|
||||
}
|
||||
|
||||
return fields
|
||||
return { fields, versionFields }
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import type { PaginatedDocs, Where } from 'payload'
|
||||
|
||||
import { fieldBaseClass, Pill, ReactSelect, useConfig, useTranslation } from '@payloadcms/ui'
|
||||
import { formatDate } from '@payloadcms/ui/shared'
|
||||
import * as qs from 'qs-esm'
|
||||
import { stringify } from 'qs-esm'
|
||||
import React, { useCallback, useEffect, useState } from 'react'
|
||||
|
||||
import type { Props } from './types.js'
|
||||
@@ -87,7 +87,7 @@ export const SelectComparison: React.FC<Props> = (props) => {
|
||||
})
|
||||
}
|
||||
|
||||
const search = qs.stringify(query)
|
||||
const search = stringify(query)
|
||||
|
||||
const response = await fetch(`${baseURL}?${search}`, {
|
||||
credentials: 'include',
|
||||
@@ -163,8 +163,12 @@ export const SelectComparison: React.FC<Props> = (props) => {
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
if (!i18n.dateFNS) {
|
||||
// If dateFNS is not loaded, we can't format the date in getResults
|
||||
return
|
||||
}
|
||||
void getResults({ lastLoadedPage: 1 })
|
||||
}, [getResults])
|
||||
}, [getResults, i18n.dateFNS])
|
||||
|
||||
const filteredOptions = options.filter(
|
||||
(option, index, self) => self.findIndex((t) => t.value === option.value) === index,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { PaginatedDocs, SanitizedCollectionConfig, SanitizedGlobalConfig } from 'payload'
|
||||
import type { PaginatedDocs, SanitizedCollectionConfig } from 'payload'
|
||||
|
||||
import type { CompareOption } from '../Default/types.js'
|
||||
|
||||
|
||||
@@ -7,14 +7,18 @@ import type {
|
||||
SanitizedGlobalPermission,
|
||||
} from 'payload'
|
||||
|
||||
import { getClientConfig } from '@payloadcms/ui/utilities/getClientConfig'
|
||||
import { getClientSchemaMap } from '@payloadcms/ui/utilities/getClientSchemaMap'
|
||||
import { getSchemaMap } from '@payloadcms/ui/utilities/getSchemaMap'
|
||||
import { notFound } from 'next/navigation.js'
|
||||
import React from 'react'
|
||||
|
||||
import { getLatestVersion } from '../Versions/getLatestVersion.js'
|
||||
import { DefaultVersionView } from './Default/index.js'
|
||||
import { RenderDiff } from './RenderFieldsToDiff/index.js'
|
||||
|
||||
export const VersionView: PayloadServerReactComponent<EditViewComponent> = async (props) => {
|
||||
const { initPageResult, routeSegments } = props
|
||||
const { i18n, initPageResult, routeSegments, searchParams } = props
|
||||
|
||||
const {
|
||||
collectionConfig,
|
||||
@@ -30,6 +34,13 @@ export const VersionView: PayloadServerReactComponent<EditViewComponent> = async
|
||||
const collectionSlug = collectionConfig?.slug
|
||||
const globalSlug = globalConfig?.slug
|
||||
|
||||
const localeCodesFromParams = searchParams.localeCodes
|
||||
? JSON.parse(searchParams.localeCodes as string)
|
||||
: null
|
||||
const comparisonVersionIDFromParams: string = searchParams.compareValue as string
|
||||
|
||||
const modifiedOnly: boolean = searchParams.modifiedOnly === 'true'
|
||||
|
||||
const { localization } = config
|
||||
|
||||
let docPermissions: SanitizedCollectionPermission | SanitizedGlobalPermission
|
||||
@@ -48,8 +59,8 @@ export const VersionView: PayloadServerReactComponent<EditViewComponent> = async
|
||||
doc = await payload.findVersionByID({
|
||||
id: versionID,
|
||||
collection: slug,
|
||||
depth: 1,
|
||||
locale: '*',
|
||||
depth: 0,
|
||||
locale: 'all',
|
||||
overrideAccess: false,
|
||||
req,
|
||||
user,
|
||||
@@ -59,15 +70,21 @@ export const VersionView: PayloadServerReactComponent<EditViewComponent> = async
|
||||
latestDraftVersion = await getLatestVersion({
|
||||
slug,
|
||||
type: 'collection',
|
||||
locale: 'all',
|
||||
overrideAccess: false,
|
||||
parentID: id,
|
||||
payload,
|
||||
req,
|
||||
status: 'draft',
|
||||
})
|
||||
latestPublishedVersion = await getLatestVersion({
|
||||
slug,
|
||||
type: 'collection',
|
||||
locale: 'all',
|
||||
overrideAccess: false,
|
||||
parentID: id,
|
||||
payload,
|
||||
req,
|
||||
status: 'published',
|
||||
})
|
||||
}
|
||||
@@ -85,8 +102,8 @@ export const VersionView: PayloadServerReactComponent<EditViewComponent> = async
|
||||
doc = await payload.findGlobalVersionByID({
|
||||
id: versionID,
|
||||
slug,
|
||||
depth: 1,
|
||||
locale: '*',
|
||||
depth: 0,
|
||||
locale: 'all',
|
||||
overrideAccess: false,
|
||||
req,
|
||||
user,
|
||||
@@ -96,13 +113,19 @@ export const VersionView: PayloadServerReactComponent<EditViewComponent> = async
|
||||
latestDraftVersion = await getLatestVersion({
|
||||
slug,
|
||||
type: 'global',
|
||||
locale: 'all',
|
||||
overrideAccess: false,
|
||||
payload,
|
||||
req,
|
||||
status: 'draft',
|
||||
})
|
||||
latestPublishedVersion = await getLatestVersion({
|
||||
slug,
|
||||
type: 'global',
|
||||
locale: 'all',
|
||||
overrideAccess: false,
|
||||
payload,
|
||||
req,
|
||||
status: 'published',
|
||||
})
|
||||
}
|
||||
@@ -120,12 +143,27 @@ export const VersionView: PayloadServerReactComponent<EditViewComponent> = async
|
||||
}
|
||||
}
|
||||
|
||||
const localeOptions: OptionObject[] =
|
||||
localization &&
|
||||
localization.locales.map(({ code, label }) => ({
|
||||
label,
|
||||
value: code,
|
||||
}))
|
||||
const selectedLocales: OptionObject[] = []
|
||||
if (localization) {
|
||||
if (localeCodesFromParams) {
|
||||
for (const code of localeCodesFromParams) {
|
||||
const locale = localization.locales.find((locale) => locale.code === code)
|
||||
if (locale) {
|
||||
selectedLocales.push({
|
||||
label: locale.label,
|
||||
value: locale.code,
|
||||
})
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (const { code, label } of localization.locales) {
|
||||
selectedLocales.push({
|
||||
label,
|
||||
value: code,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const latestVersion =
|
||||
latestPublishedVersion?.updatedAt > latestDraftVersion?.updatedAt
|
||||
@@ -136,14 +174,83 @@ export const VersionView: PayloadServerReactComponent<EditViewComponent> = async
|
||||
return notFound()
|
||||
}
|
||||
|
||||
/**
|
||||
* The doc to compare this version to is either the latest version, or a specific version if specified in the URL.
|
||||
* This specific version is added to the URL when a user selects a version to compare to.
|
||||
*/
|
||||
let comparisonDoc = null
|
||||
if (comparisonVersionIDFromParams) {
|
||||
if (collectionSlug) {
|
||||
comparisonDoc = await payload.findVersionByID({
|
||||
id: comparisonVersionIDFromParams,
|
||||
collection: collectionSlug,
|
||||
depth: 0,
|
||||
locale: 'all',
|
||||
overrideAccess: false,
|
||||
req,
|
||||
})
|
||||
} else {
|
||||
comparisonDoc = await payload.findGlobalVersionByID({
|
||||
id: comparisonVersionIDFromParams,
|
||||
slug: globalSlug,
|
||||
depth: 0,
|
||||
locale: 'all',
|
||||
overrideAccess: false,
|
||||
req,
|
||||
})
|
||||
}
|
||||
} else {
|
||||
comparisonDoc = latestVersion
|
||||
}
|
||||
|
||||
const schemaMap = getSchemaMap({
|
||||
collectionSlug,
|
||||
config,
|
||||
globalSlug,
|
||||
i18n,
|
||||
})
|
||||
|
||||
const clientSchemaMap = getClientSchemaMap({
|
||||
collectionSlug,
|
||||
config: getClientConfig({ config: payload.config, i18n, importMap: payload.importMap }),
|
||||
globalSlug,
|
||||
i18n,
|
||||
payload,
|
||||
schemaMap,
|
||||
})
|
||||
|
||||
const RenderedDiff = RenderDiff({
|
||||
clientSchemaMap,
|
||||
comparisonSiblingData: comparisonDoc?.version,
|
||||
customDiffComponents: {},
|
||||
entitySlug: collectionSlug || globalSlug,
|
||||
fieldPermissions: docPermissions?.fields,
|
||||
fields: (collectionConfig || globalConfig)?.fields,
|
||||
i18n,
|
||||
modifiedOnly,
|
||||
parentIndexPath: '',
|
||||
parentPath: '',
|
||||
parentSchemaPath: '',
|
||||
req,
|
||||
selectedLocales: selectedLocales && selectedLocales.map((locale) => locale.value),
|
||||
versionSiblingData: globalConfig
|
||||
? {
|
||||
...doc?.version,
|
||||
createdAt: doc?.version?.createdAt || doc.createdAt,
|
||||
updatedAt: doc?.version?.updatedAt || doc.updatedAt,
|
||||
}
|
||||
: doc?.version,
|
||||
})
|
||||
|
||||
return (
|
||||
<DefaultVersionView
|
||||
canUpdate={docPermissions?.update}
|
||||
doc={doc}
|
||||
docPermissions={docPermissions}
|
||||
initialComparisonDoc={latestVersion}
|
||||
latestDraftVersion={latestDraftVersion?.id}
|
||||
latestPublishedVersion={latestPublishedVersion?.id}
|
||||
localeOptions={localeOptions}
|
||||
modifiedOnly={modifiedOnly}
|
||||
RenderedDiff={RenderedDiff}
|
||||
selectedLocales={selectedLocales}
|
||||
versionID={versionID}
|
||||
/>
|
||||
)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { Payload, Where } from 'payload'
|
||||
import type { Payload, PayloadRequest, Where } from 'payload'
|
||||
|
||||
import { logError } from 'payload'
|
||||
|
||||
@@ -8,14 +8,17 @@ type ReturnType = {
|
||||
} | null
|
||||
|
||||
type Args = {
|
||||
locale?: string
|
||||
overrideAccess?: boolean
|
||||
parentID?: number | string
|
||||
payload: Payload
|
||||
req?: PayloadRequest
|
||||
slug: string
|
||||
status: 'draft' | 'published'
|
||||
type: 'collection' | 'global'
|
||||
}
|
||||
export async function getLatestVersion(args: Args): Promise<ReturnType> {
|
||||
const { slug, type = 'collection', parentID, payload, status } = args
|
||||
const { slug, type = 'collection', locale, overrideAccess, parentID, payload, req, status } = args
|
||||
|
||||
const and: Where[] = [
|
||||
{
|
||||
@@ -37,6 +40,9 @@ export async function getLatestVersion(args: Args): Promise<ReturnType> {
|
||||
const sharedOptions = {
|
||||
depth: 0,
|
||||
limit: 1,
|
||||
locale,
|
||||
overrideAccess,
|
||||
req,
|
||||
sort: '-updatedAt',
|
||||
where: {
|
||||
and,
|
||||
|
||||
@@ -13,6 +13,8 @@ import type {
|
||||
import type {
|
||||
FieldDescriptionClientComponent,
|
||||
FieldDescriptionServerComponent,
|
||||
FieldDiffClientComponent,
|
||||
FieldDiffServerComponent,
|
||||
FieldLabelClientComponent,
|
||||
FieldLabelServerComponent,
|
||||
} from '../types.js'
|
||||
@@ -53,7 +55,6 @@ export type ArrayFieldDescriptionServerComponent = FieldDescriptionServerCompone
|
||||
ArrayField,
|
||||
ArrayFieldClientWithoutType
|
||||
>
|
||||
|
||||
export type ArrayFieldDescriptionClientComponent =
|
||||
FieldDescriptionClientComponent<ArrayFieldClientWithoutType>
|
||||
|
||||
@@ -61,5 +62,7 @@ export type ArrayFieldErrorServerComponent = FieldErrorServerComponent<
|
||||
ArrayField,
|
||||
ArrayFieldClientWithoutType
|
||||
>
|
||||
|
||||
export type ArrayFieldErrorClientComponent = FieldErrorClientComponent<ArrayFieldClientWithoutType>
|
||||
|
||||
export type ArrayFieldDiffServerComponent = FieldDiffServerComponent<ArrayField, ArrayFieldClient>
|
||||
export type ArrayFieldDiffClientComponent = FieldDiffClientComponent<ArrayFieldClient>
|
||||
|
||||
@@ -14,6 +14,8 @@ import type {
|
||||
import type {
|
||||
FieldDescriptionClientComponent,
|
||||
FieldDescriptionServerComponent,
|
||||
FieldDiffClientComponent,
|
||||
FieldDiffServerComponent,
|
||||
FieldLabelClientComponent,
|
||||
FieldLabelServerComponent,
|
||||
} from '../types.js'
|
||||
@@ -80,3 +82,10 @@ export type BlocksFieldErrorServerComponent = FieldErrorServerComponent<
|
||||
|
||||
export type BlocksFieldErrorClientComponent =
|
||||
FieldErrorClientComponent<BlocksFieldClientWithoutType>
|
||||
|
||||
export type BlocksFieldDiffServerComponent = FieldDiffServerComponent<
|
||||
BlocksField,
|
||||
BlocksFieldClient
|
||||
>
|
||||
|
||||
export type BlocksFieldDiffClientComponent = FieldDiffClientComponent<BlocksFieldClient>
|
||||
|
||||
@@ -13,6 +13,8 @@ import type {
|
||||
import type {
|
||||
FieldDescriptionClientComponent,
|
||||
FieldDescriptionServerComponent,
|
||||
FieldDiffClientComponent,
|
||||
FieldDiffServerComponent,
|
||||
FieldLabelClientComponent,
|
||||
FieldLabelServerComponent,
|
||||
} from '../types.js'
|
||||
@@ -71,3 +73,10 @@ export type CheckboxFieldErrorServerComponent = FieldErrorServerComponent<
|
||||
|
||||
export type CheckboxFieldErrorClientComponent =
|
||||
FieldErrorClientComponent<CheckboxFieldClientWithoutType>
|
||||
|
||||
export type CheckboxFieldDiffServerComponent = FieldDiffServerComponent<
|
||||
CheckboxField,
|
||||
CheckboxFieldClient
|
||||
>
|
||||
|
||||
export type CheckboxFieldDiffClientComponent = FieldDiffClientComponent<CheckboxFieldClient>
|
||||
|
||||
@@ -14,6 +14,8 @@ import type {
|
||||
import type {
|
||||
FieldDescriptionClientComponent,
|
||||
FieldDescriptionServerComponent,
|
||||
FieldDiffClientComponent,
|
||||
FieldDiffServerComponent,
|
||||
FieldLabelClientComponent,
|
||||
FieldLabelServerComponent,
|
||||
} from '../types.js'
|
||||
@@ -67,3 +69,7 @@ export type CodeFieldErrorServerComponent = FieldErrorServerComponent<
|
||||
>
|
||||
|
||||
export type CodeFieldErrorClientComponent = FieldErrorClientComponent<CodeFieldClientWithoutType>
|
||||
|
||||
export type CodeFieldDiffServerComponent = FieldDiffServerComponent<CodeField, CodeFieldClient>
|
||||
|
||||
export type CodeFieldDiffClientComponent = FieldDiffClientComponent<CodeFieldClient>
|
||||
|
||||
@@ -12,6 +12,8 @@ import type {
|
||||
import type {
|
||||
FieldDescriptionClientComponent,
|
||||
FieldDescriptionServerComponent,
|
||||
FieldDiffClientComponent,
|
||||
FieldDiffServerComponent,
|
||||
FieldLabelClientComponent,
|
||||
FieldLabelServerComponent,
|
||||
} from '../types.js'
|
||||
@@ -61,3 +63,10 @@ export type CollapsibleFieldErrorServerComponent = FieldErrorServerComponent<
|
||||
|
||||
export type CollapsibleFieldErrorClientComponent =
|
||||
FieldErrorClientComponent<CollapsibleFieldClientWithoutType>
|
||||
|
||||
export type CollapsibleFieldDiffServerComponent = FieldDiffServerComponent<
|
||||
CollapsibleField,
|
||||
CollapsibleFieldClient
|
||||
>
|
||||
|
||||
export type CollapsibleFieldDiffClientComponent = FieldDiffClientComponent<CollapsibleFieldClient>
|
||||
|
||||
@@ -13,6 +13,8 @@ import type {
|
||||
import type {
|
||||
FieldDescriptionClientComponent,
|
||||
FieldDescriptionServerComponent,
|
||||
FieldDiffClientComponent,
|
||||
FieldDiffServerComponent,
|
||||
FieldLabelClientComponent,
|
||||
FieldLabelServerComponent,
|
||||
} from '../types.js'
|
||||
@@ -64,3 +66,7 @@ export type DateFieldErrorServerComponent = FieldErrorServerComponent<
|
||||
>
|
||||
|
||||
export type DateFieldErrorClientComponent = FieldErrorClientComponent<DateFieldClientWithoutType>
|
||||
|
||||
export type DateFieldDiffServerComponent = FieldDiffServerComponent<DateField, DateFieldClient>
|
||||
|
||||
export type DateFieldDiffClientComponent = FieldDiffClientComponent<DateFieldClient>
|
||||
|
||||
@@ -13,6 +13,8 @@ import type {
|
||||
import type {
|
||||
FieldDescriptionClientComponent,
|
||||
FieldDescriptionServerComponent,
|
||||
FieldDiffClientComponent,
|
||||
FieldDiffServerComponent,
|
||||
FieldLabelClientComponent,
|
||||
FieldLabelServerComponent,
|
||||
} from '../types.js'
|
||||
@@ -64,3 +66,7 @@ export type EmailFieldErrorServerComponent = FieldErrorServerComponent<
|
||||
>
|
||||
|
||||
export type EmailFieldErrorClientComponent = FieldErrorClientComponent<EmailFieldClientWithoutType>
|
||||
|
||||
export type EmailFieldDiffServerComponent = FieldDiffServerComponent<EmailField, EmailFieldClient>
|
||||
|
||||
export type EmailFieldDiffClientComponent = FieldDiffClientComponent<EmailFieldClient>
|
||||
|
||||
@@ -12,6 +12,8 @@ import type {
|
||||
import type {
|
||||
FieldDescriptionClientComponent,
|
||||
FieldDescriptionServerComponent,
|
||||
FieldDiffClientComponent,
|
||||
FieldDiffServerComponent,
|
||||
FieldLabelClientComponent,
|
||||
FieldLabelServerComponent,
|
||||
} from '../types.js'
|
||||
@@ -60,3 +62,7 @@ export type GroupFieldErrorServerComponent = FieldErrorServerComponent<
|
||||
>
|
||||
|
||||
export type GroupFieldErrorClientComponent = FieldErrorClientComponent<GroupFieldClientWithoutType>
|
||||
|
||||
export type GroupFieldDiffServerComponent = FieldDiffServerComponent<GroupField, GroupFieldClient>
|
||||
|
||||
export type GroupFieldDiffClientComponent = FieldDiffClientComponent<GroupFieldClient>
|
||||
|
||||
@@ -13,6 +13,8 @@ import type {
|
||||
import type {
|
||||
FieldDescriptionClientComponent,
|
||||
FieldDescriptionServerComponent,
|
||||
FieldDiffClientComponent,
|
||||
FieldDiffServerComponent,
|
||||
FieldLabelClientComponent,
|
||||
FieldLabelServerComponent,
|
||||
} from '../types.js'
|
||||
@@ -64,3 +66,7 @@ export type JSONFieldErrorServerComponent = FieldErrorServerComponent<
|
||||
>
|
||||
|
||||
export type JSONFieldErrorClientComponent = FieldErrorClientComponent<JSONFieldClientWithoutType>
|
||||
|
||||
export type JSONFieldDiffServerComponent = FieldDiffServerComponent<JSONField, JSONFieldClient>
|
||||
|
||||
export type JSONFieldDiffClientComponent = FieldDiffClientComponent<JSONFieldClient>
|
||||
|
||||
@@ -12,6 +12,8 @@ import type {
|
||||
import type {
|
||||
FieldDescriptionClientComponent,
|
||||
FieldDescriptionServerComponent,
|
||||
FieldDiffClientComponent,
|
||||
FieldDiffServerComponent,
|
||||
FieldLabelClientComponent,
|
||||
FieldLabelServerComponent,
|
||||
} from '../types.js'
|
||||
@@ -44,11 +46,21 @@ export type JoinFieldLabelServerComponent = FieldLabelServerComponent<JoinField>
|
||||
|
||||
export type JoinFieldLabelClientComponent = FieldLabelClientComponent<JoinFieldClientWithoutType>
|
||||
|
||||
export type JoinFieldDescriptionServerComponent = FieldDescriptionServerComponent<JoinField>
|
||||
export type JoinFieldDescriptionServerComponent = FieldDescriptionServerComponent<
|
||||
JoinField,
|
||||
JoinFieldClientWithoutType
|
||||
>
|
||||
|
||||
export type JoinFieldDescriptionClientComponent =
|
||||
FieldDescriptionClientComponent<JoinFieldClientWithoutType>
|
||||
|
||||
export type JoinFieldErrorServerComponent = FieldErrorServerComponent<JoinField>
|
||||
export type JoinFieldErrorServerComponent = FieldErrorServerComponent<
|
||||
JoinField,
|
||||
JoinFieldClientWithoutType
|
||||
>
|
||||
|
||||
export type JoinFieldErrorClientComponent = FieldErrorClientComponent<JoinFieldClientWithoutType>
|
||||
|
||||
export type JoinFieldDiffServerComponent = FieldDiffServerComponent<JoinField, JoinFieldClient>
|
||||
|
||||
export type JoinFieldDiffClientComponent = FieldDiffClientComponent<JoinFieldClient>
|
||||
|
||||
@@ -13,6 +13,8 @@ import type {
|
||||
import type {
|
||||
FieldDescriptionClientComponent,
|
||||
FieldDescriptionServerComponent,
|
||||
FieldDiffClientComponent,
|
||||
FieldDiffServerComponent,
|
||||
FieldLabelClientComponent,
|
||||
FieldLabelServerComponent,
|
||||
} from '../types.js'
|
||||
@@ -67,3 +69,10 @@ export type NumberFieldErrorServerComponent = FieldErrorServerComponent<
|
||||
|
||||
export type NumberFieldErrorClientComponent =
|
||||
FieldErrorClientComponent<NumberFieldClientWithoutType>
|
||||
|
||||
export type NumberFieldDiffServerComponent = FieldDiffServerComponent<
|
||||
NumberField,
|
||||
NumberFieldClient
|
||||
>
|
||||
|
||||
export type NumberFieldDiffClientComponent = FieldDiffClientComponent<NumberFieldClient>
|
||||
|
||||
@@ -13,6 +13,8 @@ import type {
|
||||
import type {
|
||||
FieldDescriptionClientComponent,
|
||||
FieldDescriptionServerComponent,
|
||||
FieldDiffClientComponent,
|
||||
FieldDiffServerComponent,
|
||||
FieldLabelClientComponent,
|
||||
FieldLabelServerComponent,
|
||||
} from '../types.js'
|
||||
@@ -64,3 +66,7 @@ export type PointFieldErrorServerComponent = FieldErrorServerComponent<
|
||||
>
|
||||
|
||||
export type PointFieldErrorClientComponent = FieldErrorClientComponent<PointFieldClientWithoutType>
|
||||
|
||||
export type PointFieldDiffServerComponent = FieldDiffServerComponent<PointField, PointFieldClient>
|
||||
|
||||
export type PointFieldDiffClientComponent = FieldDiffClientComponent<PointFieldClient>
|
||||
|
||||
@@ -13,6 +13,8 @@ import type {
|
||||
import type {
|
||||
FieldDescriptionClientComponent,
|
||||
FieldDescriptionServerComponent,
|
||||
FieldDiffClientComponent,
|
||||
FieldDiffServerComponent,
|
||||
FieldLabelClientComponent,
|
||||
FieldLabelServerComponent,
|
||||
} from '../types.js'
|
||||
@@ -49,7 +51,7 @@ export type RadioFieldClientComponent = FieldClientComponent<
|
||||
RadioFieldBaseClientProps
|
||||
>
|
||||
|
||||
export type OnChange<T = string> = (value: T) => void
|
||||
type OnChange<T = string> = (value: T) => void
|
||||
|
||||
export type RadioFieldLabelServerComponent = FieldLabelServerComponent<
|
||||
RadioField,
|
||||
@@ -72,3 +74,7 @@ export type RadioFieldErrorServerComponent = FieldErrorServerComponent<
|
||||
>
|
||||
|
||||
export type RadioFieldErrorClientComponent = FieldErrorClientComponent<RadioFieldClientWithoutType>
|
||||
|
||||
export type RadioFieldDiffServerComponent = FieldDiffServerComponent<RadioField, RadioFieldClient>
|
||||
|
||||
export type RadioFieldDiffClientComponent = FieldDiffClientComponent<RadioFieldClient>
|
||||
|
||||
@@ -13,6 +13,8 @@ import type {
|
||||
import type {
|
||||
FieldDescriptionClientComponent,
|
||||
FieldDescriptionServerComponent,
|
||||
FieldDiffClientComponent,
|
||||
FieldDiffServerComponent,
|
||||
FieldLabelClientComponent,
|
||||
FieldLabelServerComponent,
|
||||
} from '../types.js'
|
||||
@@ -66,3 +68,10 @@ export type RelationshipFieldErrorServerComponent = FieldErrorServerComponent<
|
||||
|
||||
export type RelationshipFieldErrorClientComponent =
|
||||
FieldErrorClientComponent<RelationshipFieldClientWithoutType>
|
||||
|
||||
export type RelationshipFieldDiffServerComponent = FieldDiffServerComponent<
|
||||
RelationshipField,
|
||||
RelationshipFieldClient
|
||||
>
|
||||
|
||||
export type RelationshipFieldDiffClientComponent = FieldDiffClientComponent<RelationshipFieldClient>
|
||||
|
||||
@@ -13,6 +13,8 @@ import type {
|
||||
import type {
|
||||
FieldDescriptionClientComponent,
|
||||
FieldDescriptionServerComponent,
|
||||
FieldDiffClientComponent,
|
||||
FieldDiffServerComponent,
|
||||
FieldLabelClientComponent,
|
||||
FieldLabelServerComponent,
|
||||
} from '../types.js'
|
||||
@@ -78,3 +80,10 @@ export type RichTextFieldErrorServerComponent = FieldErrorServerComponent<
|
||||
|
||||
export type RichTextFieldErrorClientComponent =
|
||||
FieldErrorClientComponent<RichTextFieldClientWithoutType>
|
||||
|
||||
export type RichTextFieldDiffServerComponent = FieldDiffServerComponent<
|
||||
RichTextField,
|
||||
RichTextFieldClient
|
||||
>
|
||||
|
||||
export type RichTextFieldDiffClientComponent = FieldDiffClientComponent<RichTextFieldClient>
|
||||
|
||||
@@ -11,6 +11,8 @@ import type {
|
||||
import type {
|
||||
FieldDescriptionClientComponent,
|
||||
FieldDescriptionServerComponent,
|
||||
FieldDiffClientComponent,
|
||||
FieldDiffServerComponent,
|
||||
FieldErrorClientComponent,
|
||||
FieldErrorServerComponent,
|
||||
FieldLabelClientComponent,
|
||||
@@ -56,3 +58,7 @@ export type RowFieldErrorServerComponent = FieldErrorServerComponent<
|
||||
>
|
||||
|
||||
export type RowFieldErrorClientComponent = FieldErrorClientComponent<RowFieldClientWithoutType>
|
||||
|
||||
export type RowFieldDiffServerComponent = FieldDiffServerComponent<RowField, RowFieldClient>
|
||||
|
||||
export type RowFieldDiffClientComponent = FieldDiffClientComponent<RowFieldClient>
|
||||
|
||||
@@ -13,6 +13,8 @@ import type {
|
||||
import type {
|
||||
FieldDescriptionClientComponent,
|
||||
FieldDescriptionServerComponent,
|
||||
FieldDiffClientComponent,
|
||||
FieldDiffServerComponent,
|
||||
FieldLabelClientComponent,
|
||||
FieldLabelServerComponent,
|
||||
} from '../types.js'
|
||||
@@ -68,3 +70,10 @@ export type SelectFieldErrorServerComponent = FieldErrorServerComponent<
|
||||
|
||||
export type SelectFieldErrorClientComponent =
|
||||
FieldErrorClientComponent<SelectFieldClientWithoutType>
|
||||
|
||||
export type SelectFieldDiffServerComponent = FieldDiffServerComponent<
|
||||
SelectField,
|
||||
SelectFieldClient
|
||||
>
|
||||
|
||||
export type SelectFieldDiffClientComponent = FieldDiffClientComponent<SelectFieldClient>
|
||||
|
||||
@@ -18,6 +18,8 @@ import type {
|
||||
import type {
|
||||
FieldDescriptionClientComponent,
|
||||
FieldDescriptionServerComponent,
|
||||
FieldDiffClientComponent,
|
||||
FieldDiffServerComponent,
|
||||
FieldLabelClientComponent,
|
||||
FieldLabelServerComponent,
|
||||
} from '../types.js'
|
||||
@@ -63,3 +65,7 @@ export type TabsFieldErrorServerComponent = FieldErrorServerComponent<
|
||||
>
|
||||
|
||||
export type TabsFieldErrorClientComponent = FieldErrorClientComponent<TabsFieldClientWithoutType>
|
||||
|
||||
export type TabsFieldDiffServerComponent = FieldDiffServerComponent<TabsField, TabsFieldClient>
|
||||
|
||||
export type TabsFieldDiffClientComponent = FieldDiffClientComponent<TabsFieldClient>
|
||||
|
||||
@@ -14,6 +14,8 @@ import type {
|
||||
import type {
|
||||
FieldDescriptionClientComponent,
|
||||
FieldDescriptionServerComponent,
|
||||
FieldDiffClientComponent,
|
||||
FieldDiffServerComponent,
|
||||
FieldLabelClientComponent,
|
||||
FieldLabelServerComponent,
|
||||
} from '../types.js'
|
||||
@@ -67,3 +69,7 @@ export type TextFieldErrorServerComponent = FieldErrorServerComponent<
|
||||
>
|
||||
|
||||
export type TextFieldErrorClientComponent = FieldErrorClientComponent<TextFieldClientWithoutType>
|
||||
|
||||
export type TextFieldDiffServerComponent = FieldDiffServerComponent<TextField, TextFieldClient>
|
||||
|
||||
export type TextFieldDiffClientComponent = FieldDiffClientComponent<TextFieldClient>
|
||||
|
||||
@@ -14,6 +14,8 @@ import type {
|
||||
import type {
|
||||
FieldDescriptionClientComponent,
|
||||
FieldDescriptionServerComponent,
|
||||
FieldDiffClientComponent,
|
||||
FieldDiffServerComponent,
|
||||
FieldLabelClientComponent,
|
||||
FieldLabelServerComponent,
|
||||
} from '../types.js'
|
||||
@@ -72,3 +74,10 @@ export type TextareaFieldErrorServerComponent = FieldErrorServerComponent<
|
||||
|
||||
export type TextareaFieldErrorClientComponent =
|
||||
FieldErrorClientComponent<TextareaFieldClientWithoutType>
|
||||
|
||||
export type TextareaFieldDiffServerComponent = FieldDiffServerComponent<
|
||||
TextareaField,
|
||||
TextareaFieldClient
|
||||
>
|
||||
|
||||
export type TextareaFieldDiffClientComponent = FieldDiffClientComponent<TextareaFieldClient>
|
||||
|
||||
@@ -4,6 +4,8 @@ import type { UIField, UIFieldClient } from '../../fields/config/types.js'
|
||||
import type {
|
||||
ClientFieldBase,
|
||||
FieldClientComponent,
|
||||
FieldDiffClientComponent,
|
||||
FieldDiffServerComponent,
|
||||
FieldPaths,
|
||||
FieldServerComponent,
|
||||
ServerFieldBase,
|
||||
@@ -32,3 +34,7 @@ export type UIFieldServerComponent = FieldServerComponent<
|
||||
UIFieldClientWithoutType,
|
||||
UIFieldBaseServerProps
|
||||
>
|
||||
|
||||
export type UIFieldDiffServerComponent = FieldDiffServerComponent<UIField, UIFieldClient>
|
||||
|
||||
export type UIFieldDiffClientComponent = FieldDiffClientComponent<UIFieldClient>
|
||||
|
||||
@@ -13,6 +13,8 @@ import type {
|
||||
import type {
|
||||
FieldDescriptionClientComponent,
|
||||
FieldDescriptionServerComponent,
|
||||
FieldDiffClientComponent,
|
||||
FieldDiffServerComponent,
|
||||
FieldLabelClientComponent,
|
||||
FieldLabelServerComponent,
|
||||
} from '../types.js'
|
||||
@@ -66,3 +68,10 @@ export type UploadFieldErrorServerComponent = FieldErrorServerComponent<
|
||||
|
||||
export type UploadFieldErrorClientComponent =
|
||||
FieldErrorClientComponent<UploadFieldClientWithoutType>
|
||||
|
||||
export type UploadFieldDiffServerComponent = FieldDiffServerComponent<
|
||||
UploadField,
|
||||
UploadFieldClient
|
||||
>
|
||||
|
||||
export type UploadFieldDiffClientComponent = FieldDiffClientComponent<UploadFieldClient>
|
||||
|
||||
86
packages/payload/src/admin/forms/Diff.ts
Normal file
86
packages/payload/src/admin/forms/Diff.ts
Normal file
@@ -0,0 +1,86 @@
|
||||
import type { I18nClient } from '@payloadcms/translations'
|
||||
|
||||
import type { ClientField, Field, FieldTypes, Tab } from '../../fields/config/types.js'
|
||||
import type {
|
||||
ClientFieldWithOptionalType,
|
||||
PayloadRequest,
|
||||
SanitizedFieldPermissions,
|
||||
TypedLocale,
|
||||
} from '../../index.js'
|
||||
|
||||
export type VersionTab = {
|
||||
fields: VersionField[]
|
||||
name?: string
|
||||
} & Pick<Tab, 'label'>
|
||||
|
||||
export type BaseVersionField = {
|
||||
CustomComponent?: React.ReactNode
|
||||
fields: VersionField[]
|
||||
path: string
|
||||
rows?: VersionField[][]
|
||||
schemaPath: string
|
||||
tabs?: VersionTab[]
|
||||
type: FieldTypes
|
||||
}
|
||||
|
||||
export type VersionField = {
|
||||
field?: BaseVersionField
|
||||
fieldByLocale?: Record<TypedLocale, BaseVersionField>
|
||||
}
|
||||
|
||||
/**
|
||||
* Taken from react-diff-viewer-continued
|
||||
*/
|
||||
export declare enum DiffMethod {
|
||||
CHARS = 'diffChars',
|
||||
CSS = 'diffCss',
|
||||
JSON = 'diffJson',
|
||||
LINES = 'diffLines',
|
||||
SENTENCES = 'diffSentences',
|
||||
TRIMMED_LINES = 'diffTrimmedLines',
|
||||
WORDS = 'diffWords',
|
||||
WORDS_WITH_SPACE = 'diffWordsWithSpace',
|
||||
}
|
||||
|
||||
export type FieldDiffClientProps<TClientField extends ClientFieldWithOptionalType = ClientField> = {
|
||||
baseVersionField: BaseVersionField
|
||||
/**
|
||||
* Field value from the version being compared
|
||||
*/
|
||||
comparisonValue: unknown
|
||||
diffMethod: DiffMethod
|
||||
field: TClientField
|
||||
fieldPermissions:
|
||||
| {
|
||||
[key: string]: SanitizedFieldPermissions
|
||||
}
|
||||
| true
|
||||
/**
|
||||
* If this field is localized, this will be the locale of the field
|
||||
*/
|
||||
locale?: string
|
||||
/**
|
||||
* Field value from the current version
|
||||
*/
|
||||
versionValue: unknown
|
||||
}
|
||||
|
||||
export type FieldDiffServerProps<
|
||||
TField extends Field = Field,
|
||||
TClientField extends ClientFieldWithOptionalType = ClientField,
|
||||
> = {
|
||||
clientField: TClientField
|
||||
field: TField
|
||||
i18n: I18nClient
|
||||
req: PayloadRequest
|
||||
selectedLocales: string[]
|
||||
} & Omit<FieldDiffClientProps, 'field'>
|
||||
|
||||
export type FieldDiffClientComponent<
|
||||
TFieldClient extends ClientFieldWithOptionalType = ClientFieldWithOptionalType,
|
||||
> = React.ComponentType<FieldDiffClientProps<TFieldClient>>
|
||||
|
||||
export type FieldDiffServerComponent<
|
||||
TFieldServer extends Field = Field,
|
||||
TFieldClient extends ClientFieldWithOptionalType = ClientFieldWithOptionalType,
|
||||
> = React.ComponentType<FieldDiffServerProps<TFieldServer, TFieldClient>>
|
||||
@@ -51,6 +51,8 @@ export type {
|
||||
ArrayFieldClientProps,
|
||||
ArrayFieldDescriptionClientComponent,
|
||||
ArrayFieldDescriptionServerComponent,
|
||||
ArrayFieldDiffClientComponent,
|
||||
ArrayFieldDiffServerComponent,
|
||||
ArrayFieldErrorClientComponent,
|
||||
ArrayFieldErrorServerComponent,
|
||||
ArrayFieldLabelClientComponent,
|
||||
@@ -66,6 +68,8 @@ export type {
|
||||
BlocksFieldClientProps,
|
||||
BlocksFieldDescriptionClientComponent,
|
||||
BlocksFieldDescriptionServerComponent,
|
||||
BlocksFieldDiffClientComponent,
|
||||
BlocksFieldDiffServerComponent,
|
||||
BlocksFieldErrorClientComponent,
|
||||
BlocksFieldErrorServerComponent,
|
||||
BlocksFieldLabelClientComponent,
|
||||
@@ -79,6 +83,8 @@ export type {
|
||||
CheckboxFieldClientProps,
|
||||
CheckboxFieldDescriptionClientComponent,
|
||||
CheckboxFieldDescriptionServerComponent,
|
||||
CheckboxFieldDiffClientComponent,
|
||||
CheckboxFieldDiffServerComponent,
|
||||
CheckboxFieldErrorClientComponent,
|
||||
CheckboxFieldErrorServerComponent,
|
||||
CheckboxFieldLabelClientComponent,
|
||||
@@ -92,6 +98,8 @@ export type {
|
||||
CodeFieldClientProps,
|
||||
CodeFieldDescriptionClientComponent,
|
||||
CodeFieldDescriptionServerComponent,
|
||||
CodeFieldDiffClientComponent,
|
||||
CodeFieldDiffServerComponent,
|
||||
CodeFieldErrorClientComponent,
|
||||
CodeFieldErrorServerComponent,
|
||||
CodeFieldLabelClientComponent,
|
||||
@@ -105,6 +113,8 @@ export type {
|
||||
CollapsibleFieldClientProps,
|
||||
CollapsibleFieldDescriptionClientComponent,
|
||||
CollapsibleFieldDescriptionServerComponent,
|
||||
CollapsibleFieldDiffClientComponent,
|
||||
CollapsibleFieldDiffServerComponent,
|
||||
CollapsibleFieldErrorClientComponent,
|
||||
CollapsibleFieldErrorServerComponent,
|
||||
CollapsibleFieldLabelClientComponent,
|
||||
@@ -118,6 +128,8 @@ export type {
|
||||
DateFieldClientProps,
|
||||
DateFieldDescriptionClientComponent,
|
||||
DateFieldDescriptionServerComponent,
|
||||
DateFieldDiffClientComponent,
|
||||
DateFieldDiffServerComponent,
|
||||
DateFieldErrorClientComponent,
|
||||
DateFieldErrorServerComponent,
|
||||
DateFieldLabelClientComponent,
|
||||
@@ -131,6 +143,8 @@ export type {
|
||||
EmailFieldClientProps,
|
||||
EmailFieldDescriptionClientComponent,
|
||||
EmailFieldDescriptionServerComponent,
|
||||
EmailFieldDiffClientComponent,
|
||||
EmailFieldDiffServerComponent,
|
||||
EmailFieldErrorClientComponent,
|
||||
EmailFieldErrorServerComponent,
|
||||
EmailFieldLabelClientComponent,
|
||||
@@ -144,6 +158,8 @@ export type {
|
||||
GroupFieldClientProps,
|
||||
GroupFieldDescriptionClientComponent,
|
||||
GroupFieldDescriptionServerComponent,
|
||||
GroupFieldDiffClientComponent,
|
||||
GroupFieldDiffServerComponent,
|
||||
GroupFieldErrorClientComponent,
|
||||
GroupFieldErrorServerComponent,
|
||||
GroupFieldLabelClientComponent,
|
||||
@@ -159,6 +175,8 @@ export type {
|
||||
JoinFieldClientProps,
|
||||
JoinFieldDescriptionClientComponent,
|
||||
JoinFieldDescriptionServerComponent,
|
||||
JoinFieldDiffClientComponent,
|
||||
JoinFieldDiffServerComponent,
|
||||
JoinFieldErrorClientComponent,
|
||||
JoinFieldErrorServerComponent,
|
||||
JoinFieldLabelClientComponent,
|
||||
@@ -172,6 +190,8 @@ export type {
|
||||
JSONFieldClientProps,
|
||||
JSONFieldDescriptionClientComponent,
|
||||
JSONFieldDescriptionServerComponent,
|
||||
JSONFieldDiffClientComponent,
|
||||
JSONFieldDiffServerComponent,
|
||||
JSONFieldErrorClientComponent,
|
||||
JSONFieldErrorServerComponent,
|
||||
JSONFieldLabelClientComponent,
|
||||
@@ -185,6 +205,8 @@ export type {
|
||||
NumberFieldClientProps,
|
||||
NumberFieldDescriptionClientComponent,
|
||||
NumberFieldDescriptionServerComponent,
|
||||
NumberFieldDiffClientComponent,
|
||||
NumberFieldDiffServerComponent,
|
||||
NumberFieldErrorClientComponent,
|
||||
NumberFieldErrorServerComponent,
|
||||
NumberFieldLabelClientComponent,
|
||||
@@ -198,6 +220,8 @@ export type {
|
||||
PointFieldClientProps,
|
||||
PointFieldDescriptionClientComponent,
|
||||
PointFieldDescriptionServerComponent,
|
||||
PointFieldDiffClientComponent,
|
||||
PointFieldDiffServerComponent,
|
||||
PointFieldErrorClientComponent,
|
||||
PointFieldErrorServerComponent,
|
||||
PointFieldLabelClientComponent,
|
||||
@@ -211,6 +235,8 @@ export type {
|
||||
RadioFieldClientProps,
|
||||
RadioFieldDescriptionClientComponent,
|
||||
RadioFieldDescriptionServerComponent,
|
||||
RadioFieldDiffClientComponent,
|
||||
RadioFieldDiffServerComponent,
|
||||
RadioFieldErrorClientComponent,
|
||||
RadioFieldErrorServerComponent,
|
||||
RadioFieldLabelClientComponent,
|
||||
@@ -224,6 +250,8 @@ export type {
|
||||
RelationshipFieldClientProps,
|
||||
RelationshipFieldDescriptionClientComponent,
|
||||
RelationshipFieldDescriptionServerComponent,
|
||||
RelationshipFieldDiffClientComponent,
|
||||
RelationshipFieldDiffServerComponent,
|
||||
RelationshipFieldErrorClientComponent,
|
||||
RelationshipFieldErrorServerComponent,
|
||||
RelationshipFieldLabelClientComponent,
|
||||
@@ -237,6 +265,8 @@ export type {
|
||||
RichTextFieldClientProps,
|
||||
RichTextFieldDescriptionClientComponent,
|
||||
RichTextFieldDescriptionServerComponent,
|
||||
RichTextFieldDiffClientComponent,
|
||||
RichTextFieldDiffServerComponent,
|
||||
RichTextFieldErrorClientComponent,
|
||||
RichTextFieldErrorServerComponent,
|
||||
RichTextFieldLabelClientComponent,
|
||||
@@ -250,6 +280,8 @@ export type {
|
||||
RowFieldClientProps,
|
||||
RowFieldDescriptionClientComponent,
|
||||
RowFieldDescriptionServerComponent,
|
||||
RowFieldDiffClientComponent,
|
||||
RowFieldDiffServerComponent,
|
||||
RowFieldErrorClientComponent,
|
||||
RowFieldErrorServerComponent,
|
||||
RowFieldLabelClientComponent,
|
||||
@@ -263,6 +295,8 @@ export type {
|
||||
SelectFieldClientProps,
|
||||
SelectFieldDescriptionClientComponent,
|
||||
SelectFieldDescriptionServerComponent,
|
||||
SelectFieldDiffClientComponent,
|
||||
SelectFieldDiffServerComponent,
|
||||
SelectFieldErrorClientComponent,
|
||||
SelectFieldErrorServerComponent,
|
||||
SelectFieldLabelClientComponent,
|
||||
@@ -277,6 +311,8 @@ export type {
|
||||
TabsFieldClientProps,
|
||||
TabsFieldDescriptionClientComponent,
|
||||
TabsFieldDescriptionServerComponent,
|
||||
TabsFieldDiffClientComponent,
|
||||
TabsFieldDiffServerComponent,
|
||||
TabsFieldErrorClientComponent,
|
||||
TabsFieldErrorServerComponent,
|
||||
TabsFieldLabelClientComponent,
|
||||
@@ -290,6 +326,8 @@ export type {
|
||||
TextFieldClientProps,
|
||||
TextFieldDescriptionClientComponent,
|
||||
TextFieldDescriptionServerComponent,
|
||||
TextFieldDiffClientComponent,
|
||||
TextFieldDiffServerComponent,
|
||||
TextFieldErrorClientComponent,
|
||||
TextFieldErrorServerComponent,
|
||||
TextFieldLabelClientComponent,
|
||||
@@ -303,6 +341,8 @@ export type {
|
||||
TextareaFieldClientProps,
|
||||
TextareaFieldDescriptionClientComponent,
|
||||
TextareaFieldDescriptionServerComponent,
|
||||
TextareaFieldDiffClientComponent,
|
||||
TextareaFieldDiffServerComponent,
|
||||
TextareaFieldErrorClientComponent,
|
||||
TextareaFieldErrorServerComponent,
|
||||
TextareaFieldLabelClientComponent,
|
||||
@@ -314,6 +354,8 @@ export type {
|
||||
export type {
|
||||
UIFieldClientComponent,
|
||||
UIFieldClientProps,
|
||||
UIFieldDiffClientComponent,
|
||||
UIFieldDiffServerComponent,
|
||||
UIFieldServerComponent,
|
||||
UIFieldServerProps,
|
||||
} from './fields/UI.js'
|
||||
@@ -323,6 +365,8 @@ export type {
|
||||
UploadFieldClientProps,
|
||||
UploadFieldDescriptionClientComponent,
|
||||
UploadFieldDescriptionServerComponent,
|
||||
UploadFieldDiffClientComponent,
|
||||
UploadFieldDiffServerComponent,
|
||||
UploadFieldErrorClientComponent,
|
||||
UploadFieldErrorServerComponent,
|
||||
UploadFieldLabelClientComponent,
|
||||
@@ -342,6 +386,17 @@ export type {
|
||||
StaticDescription,
|
||||
} from './forms/Description.js'
|
||||
|
||||
export type {
|
||||
BaseVersionField,
|
||||
DiffMethod,
|
||||
FieldDiffClientComponent,
|
||||
FieldDiffClientProps,
|
||||
FieldDiffServerComponent,
|
||||
FieldDiffServerProps,
|
||||
VersionField,
|
||||
VersionTab,
|
||||
} from './forms/Diff.js'
|
||||
|
||||
export type {
|
||||
FieldErrorClientComponent,
|
||||
FieldErrorClientProps,
|
||||
|
||||
@@ -110,5 +110,7 @@ export function genImportMapIterateFields({
|
||||
|
||||
hasKey(field?.admin?.components, 'RowLabel') &&
|
||||
addToImportMap(field?.admin?.components?.RowLabel)
|
||||
|
||||
hasKey(field?.admin?.components, 'Diff') && addToImportMap(field?.admin?.components?.Diff)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,14 +45,18 @@ import type {
|
||||
DateFieldErrorServerComponent,
|
||||
DateFieldLabelClientComponent,
|
||||
DateFieldLabelServerComponent,
|
||||
DefaultCellComponentProps,
|
||||
DefaultServerCellComponentProps,
|
||||
Description,
|
||||
EmailFieldClientProps,
|
||||
EmailFieldErrorClientComponent,
|
||||
EmailFieldErrorServerComponent,
|
||||
EmailFieldLabelClientComponent,
|
||||
EmailFieldLabelServerComponent,
|
||||
FieldDescriptionClientComponent,
|
||||
FieldDescriptionServerComponent,
|
||||
FieldDescriptionClientProps,
|
||||
FieldDescriptionServerProps,
|
||||
FieldDiffClientComponent,
|
||||
FieldDiffServerProps,
|
||||
GroupFieldClientProps,
|
||||
GroupFieldLabelClientComponent,
|
||||
GroupFieldLabelServerComponent,
|
||||
@@ -269,9 +273,10 @@ export type FilterOptions<TData = any> =
|
||||
type Admin = {
|
||||
className?: string
|
||||
components?: {
|
||||
Cell?: CustomComponent
|
||||
Description?: CustomComponent<FieldDescriptionClientComponent | FieldDescriptionServerComponent>
|
||||
Field?: CustomComponent<FieldClientComponent | FieldServerComponent>
|
||||
Cell?: PayloadComponent<DefaultServerCellComponentProps, DefaultCellComponentProps>
|
||||
Description?: PayloadComponent<FieldDescriptionServerProps, FieldDescriptionClientProps>
|
||||
Diff?: PayloadComponent<FieldDiffServerProps, FieldDiffClientComponent>
|
||||
Field?: PayloadComponent<FieldClientComponent | FieldServerComponent>
|
||||
/**
|
||||
* The Filter component has to be a client component
|
||||
*/
|
||||
|
||||
@@ -53,6 +53,16 @@
|
||||
"types": "./src/utilities/buildTableState.ts",
|
||||
"default": "./src/utilities/buildTableState.ts"
|
||||
},
|
||||
"./utilities/getClientSchemaMap": {
|
||||
"import": "./src/utilities/getClientSchemaMap.ts",
|
||||
"types": "./src/utilities/getClientSchemaMap.ts",
|
||||
"default": "./src/utilities/getClientSchemaMap.ts"
|
||||
},
|
||||
"./utilities/getSchemaMap": {
|
||||
"import": "./src/utilities/getSchemaMap.ts",
|
||||
"types": "./src/utilities/getSchemaMap.ts",
|
||||
"default": "./src/utilities/getSchemaMap.ts"
|
||||
},
|
||||
"./utilities/schedulePublishHandler": {
|
||||
"import": "./src/utilities/schedulePublishHandler.ts",
|
||||
"types": "./src/utilities/schedulePublishHandler.ts",
|
||||
|
||||
11807
pnpm-lock.yaml
generated
11807
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -1,14 +1,20 @@
|
||||
import path from 'path'
|
||||
import { type Payload } from 'payload'
|
||||
import { fileURLToPath } from 'url'
|
||||
|
||||
import { seedDB } from '../helpers/seed.js'
|
||||
import { seed } from './seed.js'
|
||||
import { collectionSlugs } from './slugs.js'
|
||||
|
||||
const filename = fileURLToPath(import.meta.url)
|
||||
const dirname = path.dirname(filename)
|
||||
|
||||
export async function clearAndSeedEverything(_payload: Payload, parallel: boolean = false) {
|
||||
return await seedDB({
|
||||
snapshotKey: 'versionsTest',
|
||||
collectionSlugs,
|
||||
_payload,
|
||||
uploadsDir: path.resolve(dirname, './collections/uploads'),
|
||||
seedFunction: async (_payload) => {
|
||||
await seed(_payload, parallel)
|
||||
},
|
||||
|
||||
172
test/versions/collections/Diff.ts
Normal file
172
test/versions/collections/Diff.ts
Normal file
@@ -0,0 +1,172 @@
|
||||
import type { CollectionConfig } from 'payload'
|
||||
|
||||
import { diffCollectionSlug, draftCollectionSlug } from '../slugs.js'
|
||||
|
||||
export const Diff: CollectionConfig = {
|
||||
slug: diffCollectionSlug,
|
||||
fields: [
|
||||
{
|
||||
name: 'array',
|
||||
type: 'array',
|
||||
fields: [
|
||||
{
|
||||
name: 'textInArray',
|
||||
type: 'text',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'blocks',
|
||||
type: 'blocks',
|
||||
blocks: [
|
||||
{
|
||||
slug: 'TextBlock',
|
||||
fields: [
|
||||
{
|
||||
name: 'textInBlock',
|
||||
type: 'text',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'checkbox',
|
||||
name: 'checkbox',
|
||||
},
|
||||
{
|
||||
type: 'code',
|
||||
name: 'code',
|
||||
},
|
||||
{
|
||||
type: 'collapsible',
|
||||
label: 'Collapsible',
|
||||
fields: [
|
||||
{
|
||||
name: 'textInCollapsible',
|
||||
type: 'text',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'date',
|
||||
name: 'date',
|
||||
},
|
||||
{
|
||||
type: 'email',
|
||||
name: 'email',
|
||||
},
|
||||
{
|
||||
type: 'group',
|
||||
name: 'group',
|
||||
fields: [
|
||||
{
|
||||
name: 'textInGroup',
|
||||
type: 'text',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'number',
|
||||
name: 'number',
|
||||
},
|
||||
{
|
||||
type: 'point',
|
||||
name: 'point',
|
||||
},
|
||||
{
|
||||
type: 'radio',
|
||||
name: 'radio',
|
||||
options: [
|
||||
{
|
||||
label: 'Option 1',
|
||||
value: 'option1',
|
||||
},
|
||||
{
|
||||
label: 'Option 2',
|
||||
value: 'option2',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'relationship',
|
||||
name: 'relationship',
|
||||
relationTo: draftCollectionSlug,
|
||||
},
|
||||
{
|
||||
name: 'richtext',
|
||||
type: 'richText',
|
||||
},
|
||||
{
|
||||
name: 'richtextWithCustomDiff',
|
||||
type: 'richText',
|
||||
admin: {
|
||||
components: {
|
||||
Diff: './elements/RichTextDiffComponent/index.js#RichTextDiffComponent',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
fields: [
|
||||
{
|
||||
name: 'textInRow',
|
||||
type: 'text',
|
||||
},
|
||||
],
|
||||
type: 'row',
|
||||
},
|
||||
{
|
||||
name: 'select',
|
||||
type: 'select',
|
||||
options: [
|
||||
{
|
||||
label: 'Option 1',
|
||||
value: 'option1',
|
||||
},
|
||||
{
|
||||
label: 'Option 2',
|
||||
value: 'option2',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'tabs',
|
||||
tabs: [
|
||||
{
|
||||
name: 'namedTab1',
|
||||
fields: [
|
||||
{
|
||||
name: 'textInNamedTab1',
|
||||
type: 'text',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Unnamed Tab 2',
|
||||
fields: [
|
||||
{
|
||||
name: 'textInUnnamedTab2',
|
||||
type: 'text',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'text',
|
||||
type: 'text',
|
||||
},
|
||||
{
|
||||
name: 'textArea',
|
||||
type: 'textarea',
|
||||
},
|
||||
{
|
||||
name: 'upload',
|
||||
relationTo: 'media',
|
||||
type: 'upload',
|
||||
},
|
||||
],
|
||||
versions: {
|
||||
maxPerDoc: 35,
|
||||
},
|
||||
}
|
||||
17
test/versions/collections/Media.ts
Normal file
17
test/versions/collections/Media.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import type { CollectionConfig } from 'payload'
|
||||
|
||||
import path from 'path'
|
||||
import { fileURLToPath } from 'url'
|
||||
|
||||
import { mediaCollectionSlug } from '../slugs.js'
|
||||
|
||||
const filename = fileURLToPath(import.meta.url)
|
||||
const dirname = path.dirname(filename)
|
||||
|
||||
export const Media: CollectionConfig = {
|
||||
fields: [],
|
||||
slug: mediaCollectionSlug,
|
||||
upload: {
|
||||
staticDir: path.resolve(dirname, './uploads'),
|
||||
},
|
||||
}
|
||||
BIN
test/versions/collections/uploads/image-1.jpg
Normal file
BIN
test/versions/collections/uploads/image-1.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 84 KiB |
BIN
test/versions/collections/uploads/image-1.png
Normal file
BIN
test/versions/collections/uploads/image-1.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 88 KiB |
BIN
test/versions/collections/uploads/image.jpg
Normal file
BIN
test/versions/collections/uploads/image.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 84 KiB |
BIN
test/versions/collections/uploads/image.png
Normal file
BIN
test/versions/collections/uploads/image.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 88 KiB |
@@ -5,10 +5,12 @@ const dirname = path.dirname(filename)
|
||||
import { buildConfigWithDefaults } from '../buildConfigWithDefaults.js'
|
||||
import AutosavePosts from './collections/Autosave.js'
|
||||
import CustomIDs from './collections/CustomIDs.js'
|
||||
import { Diff } from './collections/Diff.js'
|
||||
import DisablePublish from './collections/DisablePublish.js'
|
||||
import DraftPosts from './collections/Drafts.js'
|
||||
import DraftWithMax from './collections/DraftsWithMax.js'
|
||||
import LocalizedPosts from './collections/Localized.js'
|
||||
import { Media } from './collections/Media.js'
|
||||
import Posts from './collections/Posts.js'
|
||||
import VersionPosts from './collections/Versions.js'
|
||||
import AutosaveGlobal from './globals/Autosave.js'
|
||||
@@ -35,6 +37,8 @@ export default buildConfigWithDefaults({
|
||||
LocalizedPosts,
|
||||
VersionPosts,
|
||||
CustomIDs,
|
||||
Diff,
|
||||
Media,
|
||||
],
|
||||
globals: [AutosaveGlobal, DraftGlobal, DraftWithMaxGlobal, DisablePublishGlobal, LocalizedGlobal],
|
||||
indexSortableFields: true,
|
||||
|
||||
@@ -30,7 +30,7 @@ import { wait } from 'payload/shared'
|
||||
import { fileURLToPath } from 'url'
|
||||
|
||||
import type { PayloadTestSDK } from '../helpers/sdk/index.js'
|
||||
import type { Config } from './payload-types.js'
|
||||
import type { Config, Diff } from './payload-types.js'
|
||||
|
||||
import {
|
||||
changeLocale,
|
||||
@@ -52,6 +52,7 @@ import {
|
||||
autosaveCollectionSlug,
|
||||
autoSaveGlobalSlug,
|
||||
customIDSlug,
|
||||
diffCollectionSlug,
|
||||
disablePublishGlobalSlug,
|
||||
disablePublishSlug,
|
||||
draftCollectionSlug,
|
||||
@@ -785,6 +786,8 @@ describe('Versions', () => {
|
||||
describe('Versions diff view', () => {
|
||||
let postID: string
|
||||
let versionID: string
|
||||
let diffID: string
|
||||
let versionDiffID: string
|
||||
|
||||
beforeAll(() => {
|
||||
url = new AdminUrlUtil(serverURL, draftCollectionSlug)
|
||||
@@ -826,20 +829,47 @@ describe('Versions', () => {
|
||||
})
|
||||
|
||||
versionID = versions.docs[0].id
|
||||
|
||||
const diffDoc = (
|
||||
await payload.find({
|
||||
collection: diffCollectionSlug,
|
||||
})
|
||||
).docs[0] as Diff
|
||||
|
||||
diffID = diffDoc.id
|
||||
|
||||
const versionDiff = (
|
||||
await payload.findVersions({
|
||||
collection: diffCollectionSlug,
|
||||
where: {
|
||||
parent: { equals: diffID },
|
||||
},
|
||||
})
|
||||
).docs[0] as Diff
|
||||
|
||||
versionDiffID = versionDiff.id
|
||||
})
|
||||
|
||||
test('should render diff', async () => {
|
||||
async function navigateToVersionDiff() {
|
||||
const versionURL = `${serverURL}/admin/collections/${draftCollectionSlug}/${postID}/versions/${versionID}`
|
||||
await page.goto(versionURL)
|
||||
await page.waitForURL(versionURL)
|
||||
await expect(page.locator('.render-field-diffs').first()).toBeVisible()
|
||||
}
|
||||
|
||||
async function navigateToVersionFieldsDiff() {
|
||||
const versionURL = `${serverURL}/admin/collections/${diffCollectionSlug}/${diffID}/versions/${versionDiffID}`
|
||||
await page.goto(versionURL)
|
||||
await page.waitForURL(versionURL)
|
||||
await expect(page.locator('.render-field-diffs').first()).toBeVisible()
|
||||
}
|
||||
|
||||
test('should render diff', async () => {
|
||||
await navigateToVersionDiff()
|
||||
})
|
||||
|
||||
test('should render diff for nested fields', async () => {
|
||||
const versionURL = `${serverURL}/admin/collections/${draftCollectionSlug}/${postID}/versions/${versionID}`
|
||||
await page.goto(versionURL)
|
||||
await page.waitForURL(versionURL)
|
||||
await expect(page.locator('.render-field-diffs').first()).toBeVisible()
|
||||
await navigateToVersionDiff()
|
||||
|
||||
const blocksDiffLabel = page.getByText('Blocks Field', { exact: true })
|
||||
await expect(blocksDiffLabel).toBeVisible()
|
||||
@@ -858,10 +888,7 @@ describe('Versions', () => {
|
||||
})
|
||||
|
||||
test('should render diff collapser for nested fields', async () => {
|
||||
const versionURL = `${serverURL}/admin/collections/${draftCollectionSlug}/${postID}/versions/${versionID}`
|
||||
await page.goto(versionURL)
|
||||
await page.waitForURL(versionURL)
|
||||
await expect(page.locator('.render-field-diffs').first()).toBeVisible()
|
||||
await navigateToVersionDiff()
|
||||
|
||||
const blocksDiffLabel = page.getByText('Blocks Field', { exact: true })
|
||||
await expect(blocksDiffLabel).toBeVisible()
|
||||
@@ -908,5 +935,233 @@ describe('Versions', () => {
|
||||
// Expect collapser content to be visible
|
||||
await expect(diffCollapserContent).toBeVisible()
|
||||
})
|
||||
|
||||
test('correctly renders diff for array fields', async () => {
|
||||
await navigateToVersionFieldsDiff()
|
||||
|
||||
const textInArray = page.locator('[data-field-path="array.0.textInArray"]')
|
||||
|
||||
await expect(textInArray.locator('tr').nth(1).locator('td').nth(1)).toHaveText('textInArray')
|
||||
await expect(textInArray.locator('tr').nth(1).locator('td').nth(3)).toHaveText('textInArray2')
|
||||
})
|
||||
|
||||
test('correctly renders diff for block fields', async () => {
|
||||
await navigateToVersionFieldsDiff()
|
||||
|
||||
const textInBlock = page.locator('[data-field-path="blocks.0.textInBlock"]')
|
||||
|
||||
await expect(textInBlock.locator('tr').nth(1).locator('td').nth(1)).toHaveText('textInBlock')
|
||||
await expect(textInBlock.locator('tr').nth(1).locator('td').nth(3)).toHaveText('textInBlock2')
|
||||
})
|
||||
|
||||
test('correctly renders diff for checkbox fields', async () => {
|
||||
await navigateToVersionFieldsDiff()
|
||||
|
||||
const checkbox = page.locator('[data-field-path="checkbox"]')
|
||||
|
||||
await expect(checkbox.locator('tr').nth(1).locator('td').nth(1)).toHaveText('true')
|
||||
await expect(checkbox.locator('tr').nth(1).locator('td').nth(3)).toHaveText('false')
|
||||
})
|
||||
|
||||
test('correctly renders diff for code fields', async () => {
|
||||
await navigateToVersionFieldsDiff()
|
||||
|
||||
const code = page.locator('[data-field-path="code"]')
|
||||
|
||||
await expect(code.locator('tr').nth(1).locator('td').nth(1)).toHaveText('code')
|
||||
await expect(code.locator('tr').nth(1).locator('td').nth(3)).toHaveText('code2')
|
||||
})
|
||||
|
||||
test('correctly renders diff for collapsible fields', async () => {
|
||||
await navigateToVersionFieldsDiff()
|
||||
|
||||
const collapsible = page.locator('[data-field-path="textInCollapsible"]')
|
||||
|
||||
await expect(collapsible.locator('tr').nth(1).locator('td').nth(1)).toHaveText(
|
||||
'textInCollapsible',
|
||||
)
|
||||
await expect(collapsible.locator('tr').nth(1).locator('td').nth(3)).toHaveText(
|
||||
'textInCollapsible2',
|
||||
)
|
||||
})
|
||||
|
||||
test('correctly renders diff for date fields', async () => {
|
||||
await navigateToVersionFieldsDiff()
|
||||
|
||||
const date = page.locator('[data-field-path="date"]')
|
||||
|
||||
await expect(date.locator('tr').nth(1).locator('td').nth(1)).toHaveText(
|
||||
'2021-01-01T00:00:00.000Z',
|
||||
)
|
||||
await expect(date.locator('tr').nth(1).locator('td').nth(3)).toHaveText(
|
||||
'2023-01-01T00:00:00.000Z',
|
||||
)
|
||||
})
|
||||
|
||||
test('correctly renders diff for email fields', async () => {
|
||||
await navigateToVersionFieldsDiff()
|
||||
|
||||
const email = page.locator('[data-field-path="email"]')
|
||||
|
||||
await expect(email.locator('tr').nth(1).locator('td').nth(1)).toHaveText('email@email.com')
|
||||
await expect(email.locator('tr').nth(1).locator('td').nth(3)).toHaveText('email2@email.com')
|
||||
})
|
||||
|
||||
test('correctly renders diff for group fields', async () => {
|
||||
await navigateToVersionFieldsDiff()
|
||||
|
||||
const group = page.locator('[data-field-path="group.textInGroup"]')
|
||||
|
||||
await expect(group.locator('tr').nth(1).locator('td').nth(1)).toHaveText('textInGroup')
|
||||
await expect(group.locator('tr').nth(1).locator('td').nth(3)).toHaveText('textInGroup2')
|
||||
})
|
||||
|
||||
test('correctly renders diff for number fields', async () => {
|
||||
await navigateToVersionFieldsDiff()
|
||||
|
||||
const number = page.locator('[data-field-path="number"]')
|
||||
|
||||
await expect(number.locator('tr').nth(1).locator('td').nth(1)).toHaveText('1')
|
||||
await expect(number.locator('tr').nth(1).locator('td').nth(3)).toHaveText('2')
|
||||
})
|
||||
|
||||
test('correctly renders diff for point fields', async () => {
|
||||
await navigateToVersionFieldsDiff()
|
||||
|
||||
const point = page.locator('[data-field-path="point"]')
|
||||
|
||||
await expect(point.locator('tr').nth(3).locator('td').nth(1)).toHaveText('2')
|
||||
await expect(point.locator('tr').nth(3).locator('td').nth(3)).toHaveText('3')
|
||||
})
|
||||
|
||||
test('correctly renders diff for radio fields', async () => {
|
||||
await navigateToVersionFieldsDiff()
|
||||
|
||||
const radio = page.locator('[data-field-path="radio"]')
|
||||
|
||||
await expect(radio.locator('tr').nth(1).locator('td').nth(1)).toHaveText('Option 1')
|
||||
await expect(radio.locator('tr').nth(1).locator('td').nth(3)).toHaveText('Option 2')
|
||||
})
|
||||
|
||||
test('correctly renders diff for relationship fields', async () => {
|
||||
await navigateToVersionFieldsDiff()
|
||||
|
||||
const relationship = page.locator('[data-field-path="relationship"]')
|
||||
|
||||
const draftDocs = await payload.find({
|
||||
collection: 'draft-posts',
|
||||
sort: 'createdAt',
|
||||
limit: 3,
|
||||
})
|
||||
|
||||
await expect(relationship.locator('tr').nth(1).locator('td').nth(1)).toHaveText(
|
||||
String(draftDocs?.docs?.[1]?.id),
|
||||
)
|
||||
await expect(relationship.locator('tr').nth(1).locator('td').nth(3)).toHaveText(
|
||||
String(draftDocs?.docs?.[2]?.id),
|
||||
)
|
||||
})
|
||||
|
||||
test('correctly renders diff for richtext fields', async () => {
|
||||
await navigateToVersionFieldsDiff()
|
||||
|
||||
const richtext = page.locator('[data-field-path="richtext"]')
|
||||
|
||||
await expect(richtext.locator('tr').nth(16).locator('td').nth(1)).toHaveText(
|
||||
'"text": "richtext",',
|
||||
)
|
||||
await expect(richtext.locator('tr').nth(16).locator('td').nth(3)).toHaveText(
|
||||
'"text": "richtext2",',
|
||||
)
|
||||
})
|
||||
|
||||
test('correctly renders diff for richtext fields with custom Diff component', async () => {
|
||||
await navigateToVersionFieldsDiff()
|
||||
|
||||
const richtextWithCustomDiff = page.locator('[data-field-path="richtextWithCustomDiff"]')
|
||||
|
||||
await expect(richtextWithCustomDiff.locator('p')).toHaveText('Test')
|
||||
})
|
||||
|
||||
test('correctly renders diff for row fields', async () => {
|
||||
await navigateToVersionFieldsDiff()
|
||||
|
||||
const textInRow = page.locator('[data-field-path="textInRow"]')
|
||||
|
||||
await expect(textInRow.locator('tr').nth(1).locator('td').nth(1)).toHaveText('textInRow')
|
||||
await expect(textInRow.locator('tr').nth(1).locator('td').nth(3)).toHaveText('textInRow2')
|
||||
})
|
||||
|
||||
test('correctly renders diff for select fields', async () => {
|
||||
await navigateToVersionFieldsDiff()
|
||||
|
||||
const select = page.locator('[data-field-path="select"]')
|
||||
|
||||
await expect(select.locator('tr').nth(1).locator('td').nth(1)).toHaveText('Option 1')
|
||||
await expect(select.locator('tr').nth(1).locator('td').nth(3)).toHaveText('Option 2')
|
||||
})
|
||||
|
||||
test('correctly renders diff for named tabs', async () => {
|
||||
await navigateToVersionFieldsDiff()
|
||||
|
||||
const textInNamedTab1 = page.locator('[data-field-path="namedTab1.textInNamedTab1"]')
|
||||
|
||||
await expect(textInNamedTab1.locator('tr').nth(1).locator('td').nth(1)).toHaveText(
|
||||
'textInNamedTab1',
|
||||
)
|
||||
await expect(textInNamedTab1.locator('tr').nth(1).locator('td').nth(3)).toHaveText(
|
||||
'textInNamedTab12',
|
||||
)
|
||||
})
|
||||
|
||||
test('correctly renders diff for unnamed tabs', async () => {
|
||||
await navigateToVersionFieldsDiff()
|
||||
|
||||
const textInUnamedTab2 = page.locator('[data-field-path="textInUnnamedTab2"]')
|
||||
|
||||
await expect(textInUnamedTab2.locator('tr').nth(1).locator('td').nth(1)).toHaveText(
|
||||
'textInUnnamedTab2',
|
||||
)
|
||||
await expect(textInUnamedTab2.locator('tr').nth(1).locator('td').nth(3)).toHaveText(
|
||||
'textInUnnamedTab22',
|
||||
)
|
||||
})
|
||||
|
||||
test('correctly renders diff for text fields', async () => {
|
||||
await navigateToVersionFieldsDiff()
|
||||
|
||||
const text = page.locator('[data-field-path="text"]')
|
||||
|
||||
await expect(text.locator('tr').nth(1).locator('td').nth(1)).toHaveText('text')
|
||||
await expect(text.locator('tr').nth(1).locator('td').nth(3)).toHaveText('text2')
|
||||
})
|
||||
|
||||
test('correctly renders diff for textArea fields', async () => {
|
||||
await navigateToVersionFieldsDiff()
|
||||
|
||||
const textArea = page.locator('[data-field-path="textArea"]')
|
||||
|
||||
await expect(textArea.locator('tr').nth(1).locator('td').nth(1)).toHaveText('textArea')
|
||||
await expect(textArea.locator('tr').nth(1).locator('td').nth(3)).toHaveText('textArea2')
|
||||
})
|
||||
|
||||
test('correctly renders diff for upload fields', async () => {
|
||||
await navigateToVersionFieldsDiff()
|
||||
|
||||
const upload = page.locator('[data-field-path="upload"]')
|
||||
|
||||
const uploadDocs = await payload.find({
|
||||
collection: 'media',
|
||||
sort: 'createdAt',
|
||||
limit: 2,
|
||||
})
|
||||
|
||||
await expect(upload.locator('tr').nth(1).locator('td').nth(1)).toHaveText(
|
||||
String(uploadDocs?.docs?.[0]?.id),
|
||||
)
|
||||
await expect(upload.locator('tr').nth(1).locator('td').nth(3)).toHaveText(
|
||||
String(uploadDocs?.docs?.[1]?.id),
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
5
test/versions/elements/RichTextDiffComponent/index.tsx
Normal file
5
test/versions/elements/RichTextDiffComponent/index.tsx
Normal file
@@ -0,0 +1,5 @@
|
||||
import type { RichTextFieldDiffServerComponent } from 'payload'
|
||||
|
||||
export const RichTextDiffComponent: RichTextFieldDiffServerComponent = () => {
|
||||
return <p>Test</p>
|
||||
}
|
||||
BIN
test/versions/image.jpg
Normal file
BIN
test/versions/image.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 84 KiB |
BIN
test/versions/image.png
Normal file
BIN
test/versions/image.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 88 KiB |
@@ -19,6 +19,8 @@ export interface Config {
|
||||
'localized-posts': LocalizedPost;
|
||||
'version-posts': VersionPost;
|
||||
'custom-ids': CustomId;
|
||||
diff: Diff;
|
||||
media: Media;
|
||||
users: User;
|
||||
'payload-jobs': PayloadJob;
|
||||
'payload-locked-documents': PayloadLockedDocument;
|
||||
@@ -35,6 +37,8 @@ export interface Config {
|
||||
'localized-posts': LocalizedPostsSelect<false> | LocalizedPostsSelect<true>;
|
||||
'version-posts': VersionPostsSelect<false> | VersionPostsSelect<true>;
|
||||
'custom-ids': CustomIdsSelect<false> | CustomIdsSelect<true>;
|
||||
diff: DiffSelect<false> | DiffSelect<true>;
|
||||
media: MediaSelect<false> | MediaSelect<true>;
|
||||
users: UsersSelect<false> | UsersSelect<true>;
|
||||
'payload-jobs': PayloadJobsSelect<false> | PayloadJobsSelect<true>;
|
||||
'payload-locked-documents': PayloadLockedDocumentsSelect<false> | PayloadLockedDocumentsSelect<true>;
|
||||
@@ -208,6 +212,102 @@ export interface CustomId {
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "diff".
|
||||
*/
|
||||
export interface Diff {
|
||||
id: string;
|
||||
array?:
|
||||
| {
|
||||
textInArray?: string | null;
|
||||
id?: string | null;
|
||||
}[]
|
||||
| null;
|
||||
blocks?:
|
||||
| {
|
||||
textInBlock?: string | null;
|
||||
id?: string | null;
|
||||
blockName?: string | null;
|
||||
blockType: 'TextBlock';
|
||||
}[]
|
||||
| null;
|
||||
checkbox?: boolean | null;
|
||||
code?: string | null;
|
||||
textInCollapsible?: string | null;
|
||||
date?: string | null;
|
||||
email?: string | null;
|
||||
group?: {
|
||||
textInGroup?: string | null;
|
||||
};
|
||||
number?: number | null;
|
||||
/**
|
||||
* @minItems 2
|
||||
* @maxItems 2
|
||||
*/
|
||||
point?: [number, number] | null;
|
||||
radio?: ('option1' | 'option2') | null;
|
||||
relationship?: (string | null) | DraftPost;
|
||||
richtext?: {
|
||||
root: {
|
||||
type: string;
|
||||
children: {
|
||||
type: string;
|
||||
version: number;
|
||||
[k: string]: unknown;
|
||||
}[];
|
||||
direction: ('ltr' | 'rtl') | null;
|
||||
format: 'left' | 'start' | 'center' | 'right' | 'end' | 'justify' | '';
|
||||
indent: number;
|
||||
version: number;
|
||||
};
|
||||
[k: string]: unknown;
|
||||
} | null;
|
||||
richtextWithCustomDiff?: {
|
||||
root: {
|
||||
type: string;
|
||||
children: {
|
||||
type: string;
|
||||
version: number;
|
||||
[k: string]: unknown;
|
||||
}[];
|
||||
direction: ('ltr' | 'rtl') | null;
|
||||
format: 'left' | 'start' | 'center' | 'right' | 'end' | 'justify' | '';
|
||||
indent: number;
|
||||
version: number;
|
||||
};
|
||||
[k: string]: unknown;
|
||||
} | null;
|
||||
textInRow?: string | null;
|
||||
select?: ('option1' | 'option2') | null;
|
||||
namedTab1?: {
|
||||
textInNamedTab1?: string | null;
|
||||
};
|
||||
textInUnnamedTab2?: string | null;
|
||||
text?: string | null;
|
||||
textArea?: string | null;
|
||||
upload?: (string | null) | Media;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "media".
|
||||
*/
|
||||
export interface Media {
|
||||
id: string;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
url?: string | null;
|
||||
thumbnailURL?: string | null;
|
||||
filename?: string | null;
|
||||
mimeType?: string | null;
|
||||
filesize?: number | null;
|
||||
width?: number | null;
|
||||
height?: number | null;
|
||||
focalX?: number | null;
|
||||
focalY?: number | null;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "users".
|
||||
@@ -356,6 +456,14 @@ export interface PayloadLockedDocument {
|
||||
relationTo: 'custom-ids';
|
||||
value: string | CustomId;
|
||||
} | null)
|
||||
| ({
|
||||
relationTo: 'diff';
|
||||
value: string | Diff;
|
||||
} | null)
|
||||
| ({
|
||||
relationTo: 'media';
|
||||
value: string | Media;
|
||||
} | null)
|
||||
| ({
|
||||
relationTo: 'users';
|
||||
value: string | User;
|
||||
@@ -522,6 +630,75 @@ export interface CustomIdsSelect<T extends boolean = true> {
|
||||
updatedAt?: T;
|
||||
createdAt?: T;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "diff_select".
|
||||
*/
|
||||
export interface DiffSelect<T extends boolean = true> {
|
||||
array?:
|
||||
| T
|
||||
| {
|
||||
textInArray?: T;
|
||||
id?: T;
|
||||
};
|
||||
blocks?:
|
||||
| T
|
||||
| {
|
||||
TextBlock?:
|
||||
| T
|
||||
| {
|
||||
textInBlock?: T;
|
||||
id?: T;
|
||||
blockName?: T;
|
||||
};
|
||||
};
|
||||
checkbox?: T;
|
||||
code?: T;
|
||||
textInCollapsible?: T;
|
||||
date?: T;
|
||||
email?: T;
|
||||
group?:
|
||||
| T
|
||||
| {
|
||||
textInGroup?: T;
|
||||
};
|
||||
number?: T;
|
||||
point?: T;
|
||||
radio?: T;
|
||||
relationship?: T;
|
||||
richtext?: T;
|
||||
richtextWithCustomDiff?: T;
|
||||
textInRow?: T;
|
||||
select?: T;
|
||||
namedTab1?:
|
||||
| T
|
||||
| {
|
||||
textInNamedTab1?: T;
|
||||
};
|
||||
textInUnnamedTab2?: T;
|
||||
text?: T;
|
||||
textArea?: T;
|
||||
upload?: T;
|
||||
updatedAt?: T;
|
||||
createdAt?: T;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "media_select".
|
||||
*/
|
||||
export interface MediaSelect<T extends boolean = true> {
|
||||
updatedAt?: T;
|
||||
createdAt?: T;
|
||||
url?: T;
|
||||
thumbnailURL?: T;
|
||||
filename?: T;
|
||||
mimeType?: T;
|
||||
filesize?: T;
|
||||
width?: T;
|
||||
height?: T;
|
||||
focalX?: T;
|
||||
focalY?: T;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "users_select".
|
||||
|
||||
@@ -1,11 +1,17 @@
|
||||
import { type Payload } from 'payload'
|
||||
import path from 'path'
|
||||
import { getFileByPath, type Payload } from 'payload'
|
||||
import { fileURLToPath } from 'url'
|
||||
|
||||
import type { DraftPost } from './payload-types.js'
|
||||
|
||||
import { devUser } from '../credentials.js'
|
||||
import { executePromises } from '../helpers/executePromises.js'
|
||||
import { titleToDelete } from './shared.js'
|
||||
import { draftCollectionSlug } from './slugs.js'
|
||||
import { diffCollectionSlug, draftCollectionSlug, mediaCollectionSlug } from './slugs.js'
|
||||
import { textToLexicalJSON } from './textToLexicalJSON.js'
|
||||
|
||||
const filename = fileURLToPath(import.meta.url)
|
||||
const dirname = path.dirname(filename)
|
||||
|
||||
export async function seed(_payload: Payload, parallel: boolean = false) {
|
||||
const blocksField: DraftPost['blocksField'] = [
|
||||
@@ -16,6 +22,24 @@ export async function seed(_payload: Payload, parallel: boolean = false) {
|
||||
},
|
||||
]
|
||||
|
||||
const imageFilePath = path.resolve(dirname, './image.jpg')
|
||||
const imageFile = await getFileByPath(imageFilePath)
|
||||
|
||||
const { id: uploadedImage } = await _payload.create({
|
||||
collection: mediaCollectionSlug,
|
||||
data: {},
|
||||
file: imageFile,
|
||||
})
|
||||
|
||||
const imageFilePath2 = path.resolve(dirname, './image.png')
|
||||
const imageFile2 = await getFileByPath(imageFilePath2)
|
||||
|
||||
const { id: uploadedImage2 } = await _payload.create({
|
||||
collection: mediaCollectionSlug,
|
||||
data: {},
|
||||
file: imageFile2,
|
||||
})
|
||||
|
||||
await executePromises(
|
||||
[
|
||||
() =>
|
||||
@@ -70,7 +94,7 @@ export async function seed(_payload: Payload, parallel: boolean = false) {
|
||||
})
|
||||
}
|
||||
|
||||
await _payload.create({
|
||||
const draft2 = await _payload.create({
|
||||
collection: draftCollectionSlug,
|
||||
data: {
|
||||
_status: 'published',
|
||||
@@ -95,4 +119,87 @@ export async function seed(_payload: Payload, parallel: boolean = false) {
|
||||
overrideAccess: true,
|
||||
draft: true,
|
||||
})
|
||||
|
||||
const diffDoc = await _payload.create({
|
||||
collection: diffCollectionSlug,
|
||||
data: {
|
||||
array: [
|
||||
{
|
||||
textInArray: 'textInArray',
|
||||
},
|
||||
],
|
||||
blocks: [
|
||||
{
|
||||
blockType: 'TextBlock',
|
||||
textInBlock: 'textInBlock',
|
||||
},
|
||||
],
|
||||
checkbox: true,
|
||||
code: 'code',
|
||||
date: '2021-01-01T00:00:00.000Z',
|
||||
email: 'email@email.com',
|
||||
group: {
|
||||
textInGroup: 'textInGroup',
|
||||
},
|
||||
namedTab1: {
|
||||
textInNamedTab1: 'textInNamedTab1',
|
||||
},
|
||||
number: 1,
|
||||
point: [1, 2],
|
||||
radio: 'option1',
|
||||
relationship: manyDraftsID,
|
||||
richtext: textToLexicalJSON({ text: 'richtext' }),
|
||||
richtextWithCustomDiff: textToLexicalJSON({ text: 'richtextWithCustomDiff' }),
|
||||
select: 'option1',
|
||||
text: 'text',
|
||||
textArea: 'textArea',
|
||||
textInCollapsible: 'textInCollapsible',
|
||||
textInRow: 'textInRow',
|
||||
textInUnnamedTab2: 'textInUnnamedTab2',
|
||||
upload: uploadedImage,
|
||||
},
|
||||
depth: 0,
|
||||
})
|
||||
|
||||
const updatedDiffDoc = await _payload.update({
|
||||
id: diffDoc.id,
|
||||
collection: diffCollectionSlug,
|
||||
data: {
|
||||
array: [
|
||||
{
|
||||
textInArray: 'textInArray2',
|
||||
},
|
||||
],
|
||||
blocks: [
|
||||
{
|
||||
blockType: 'TextBlock',
|
||||
textInBlock: 'textInBlock2',
|
||||
},
|
||||
],
|
||||
checkbox: false,
|
||||
code: 'code2',
|
||||
date: '2023-01-01T00:00:00.000Z',
|
||||
email: 'email2@email.com',
|
||||
group: {
|
||||
textInGroup: 'textInGroup2',
|
||||
},
|
||||
namedTab1: {
|
||||
textInNamedTab1: 'textInNamedTab12',
|
||||
},
|
||||
number: 2,
|
||||
point: [1, 3],
|
||||
radio: 'option2',
|
||||
relationship: draft2.id,
|
||||
richtext: textToLexicalJSON({ text: 'richtext2' }),
|
||||
richtextWithCustomDiff: textToLexicalJSON({ text: 'richtextWithCustomDiff2' }),
|
||||
select: 'option2',
|
||||
text: 'text2',
|
||||
textArea: 'textArea2',
|
||||
textInCollapsible: 'textInCollapsible2',
|
||||
textInRow: 'textInRow2',
|
||||
textInUnnamedTab2: 'textInUnnamedTab22',
|
||||
upload: uploadedImage2,
|
||||
},
|
||||
depth: 0,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -7,6 +7,9 @@ export const draftWithMaxCollectionSlug = 'draft-with-max-posts'
|
||||
|
||||
export const postCollectionSlug = 'posts'
|
||||
|
||||
export const diffCollectionSlug = 'diff'
|
||||
export const mediaCollectionSlug = 'media'
|
||||
|
||||
export const versionCollectionSlug = 'version-posts'
|
||||
|
||||
export const disablePublishSlug = 'disable-publish'
|
||||
@@ -17,6 +20,8 @@ export const collectionSlugs = [
|
||||
autosaveCollectionSlug,
|
||||
draftCollectionSlug,
|
||||
postCollectionSlug,
|
||||
diffCollectionSlug,
|
||||
mediaCollectionSlug,
|
||||
versionCollectionSlug,
|
||||
]
|
||||
|
||||
|
||||
41
test/versions/textToLexicalJSON.ts
Normal file
41
test/versions/textToLexicalJSON.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import type {
|
||||
SerializedEditorState,
|
||||
SerializedParagraphNode,
|
||||
SerializedTextNode,
|
||||
} from '@payloadcms/richtext-lexical/lexical'
|
||||
|
||||
export function textToLexicalJSON({ text }: { text: string }): any {
|
||||
const editorJSON: SerializedEditorState = {
|
||||
root: {
|
||||
type: 'root',
|
||||
format: '',
|
||||
indent: 0,
|
||||
version: 1,
|
||||
direction: 'ltr',
|
||||
children: [
|
||||
{
|
||||
children: [
|
||||
{
|
||||
detail: 0,
|
||||
format: 0,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text,
|
||||
type: 'text',
|
||||
version: 1,
|
||||
} as SerializedTextNode,
|
||||
],
|
||||
direction: 'ltr',
|
||||
format: '',
|
||||
indent: 0,
|
||||
textFormat: 0,
|
||||
type: 'paragraph',
|
||||
textStyle: '',
|
||||
version: 1,
|
||||
} as SerializedParagraphNode,
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
return editorJSON
|
||||
}
|
||||
Reference in New Issue
Block a user