Skip to content

Commit

Permalink
feat: enhances rich text upload with custom field API
Browse files Browse the repository at this point in the history
* feat: adds admin.upload.collections[collection-name].fields to the RTE to save specific data on upload elements

* chore: renames flatten to unflatten in reduceFieldsToValues, disables automatic arrow function return in eslint

* docs: adds documentation for upload.collections[collection-name].fields feature

* feat: adds recursion to richText field to populate relationship and upload nested fields

* chore: removes unused css

* fix: import path for createRichTextRelationshipPromise

* docs: updates docs to include images for the RTE upload docs
  • Loading branch information
JarrodMFlesch authored Jan 21, 2022
1 parent d07bb93 commit 0e4eb90
Show file tree
Hide file tree
Showing 23 changed files with 881 additions and 255 deletions.
1 change: 1 addition & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ module.exports = {
'react/no-unused-prop-types': 'off',
'no-underscore-dangle': 'off',
'no-use-before-define': 'off',
'arrow-body-style': 0,
'@typescript-eslint/no-use-before-define': ['error'],
'import/extensions': [
'error',
Expand Down
50 changes: 50 additions & 0 deletions demo/collections/RichText.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,56 @@ const RichText: CollectionConfig = {
type: 'richText',
label: 'Default Rich Text',
required: true,
admin: {
upload: {
collections: {
media: {
fields: [
{
type: 'textarea',
name: 'caption',
label: 'Caption',
},
{
type: 'row',
fields: [
{
type: 'relationship',
relationTo: 'admins',
name: 'linkToAdmin',
label: 'Link to Admin',
},
{
type: 'select',
name: 'imageAlignment',
label: 'Image Alignment',
options: [
{
label: 'Left',
value: 'left',
},
{
label: 'Center',
value: 'center',
},
{
label: 'Right',
value: 'right',
},
],
},
],
},
{
type: 'checkbox',
name: 'wrapText',
label: 'Wrap Text',
},
],
},
},
},
},
},
{
name: 'customRichText',
Expand Down
24 changes: 22 additions & 2 deletions docs/fields/rich-text.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,16 @@ The default `leaves` available in Payload are:

Set this property to `true` to hide this field's gutter within the admin panel. The field gutter is rendered as a vertical line and padding, but often if this field is nested within a Group, Block, or Array, you may want to hide the gutter.

**`upload.collections[collection-name].fields`**

This allows [fields](/docs/fields/overview) to be saved as meta data on an upload field inside the Rich Text Editor. When this is present, the fields will render inside a modal that can be opened by clicking the "edit" button on the upload element.

![RichText upload element](https://payloadcms.com/images/fields/richText/rte-upload-element.jpg)
*RichText field using the upload element*

![RichText upload element modal](https://payloadcms.com/images/fields/richText/rte-upload-fields-modal.jpg)
*RichText upload element modal displaying fields from the config*

### Relationship element

The built-in `relationship` element is a powerful way to reference other Documents directly within your Rich Text editor.
Expand Down Expand Up @@ -143,7 +153,7 @@ Custom `Leaf` objects follow a similar pattern but require you to define the `Le
]
}
],
elements: [
leaves: [
'bold',
'italic',
{
Expand All @@ -154,7 +164,17 @@ Custom `Leaf` objects follow a similar pattern but require you to define the `Le
// any plugins that are required by this leaf go here
]
}
]
],
upload: {
collections: {
media: {
fields: [
// any fields that you would like to save
// on an upload element in the `media` collection
],
},
},
},
}
}
]
Expand Down
20 changes: 20 additions & 0 deletions src/admin/components/elements/Button/index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,26 @@
@include color-svg(currentColor);
}

&--has-tooltip {
position: relative;

}

.btn__tooltip {
opacity: 0;
visibility: hidden;
transform: translate(-50%, -10px);
}

.btn__content {
&:hover {
.btn__tooltip {
opacity: 1;
visibility: visible;
}
}
}

&--icon-style-without-border {
.btn__icon {
border: none;
Expand Down
27 changes: 23 additions & 4 deletions src/admin/components/elements/Button/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import plus from '../../icons/Plus';
import x from '../../icons/X';
import chevron from '../../icons/Chevron';
import edit from '../../icons/Edit';
import swap from '../../icons/Swap';
import Tooltip from '../Tooltip';

import './index.scss';

Expand All @@ -14,15 +16,21 @@ const icons = {
x,
chevron,
edit,
swap,
};

const baseClass = 'btn';

const ButtonContents = ({ children, icon }) => {
const ButtonContents = ({ children, icon, tooltip }) => {
const BuiltInIcon = icons[icon];

return (
<span className={`${baseClass}__content`}>
{tooltip && (
<Tooltip className={`${baseClass}__tooltip`}>
{tooltip}
</Tooltip>
)}
{children && (
<span className={`${baseClass}__label`}>
{children}
Expand Down Expand Up @@ -55,6 +63,7 @@ const Button: React.FC<Props> = (props) => {
size = 'medium',
iconPosition = 'right',
newTab,
tooltip,
} = props;

const classes = [
Expand All @@ -68,6 +77,7 @@ const Button: React.FC<Props> = (props) => {
round && `${baseClass}--round`,
size && `${baseClass}--size-${size}`,
iconPosition && `${baseClass}--icon-position-${iconPosition}`,
tooltip && `${baseClass}--has-tooltip`,
].filter(Boolean).join(' ');

function handleClick(event) {
Expand All @@ -90,7 +100,10 @@ const Button: React.FC<Props> = (props) => {
{...buttonProps}
to={to || url}
>
<ButtonContents icon={icon}>
<ButtonContents
icon={icon}
tooltip={tooltip}
>
{children}
</ButtonContents>
</Link>
Expand All @@ -102,7 +115,10 @@ const Button: React.FC<Props> = (props) => {
{...buttonProps}
href={url}
>
<ButtonContents icon={icon}>
<ButtonContents
icon={icon}
tooltip={tooltip}
>
{children}
</ButtonContents>
</a>
Expand All @@ -114,7 +130,10 @@ const Button: React.FC<Props> = (props) => {
type="submit"
{...buttonProps}
>
<ButtonContents icon={icon}>
<ButtonContents
icon={icon}
tooltip={tooltip}
>
{children}
</ButtonContents>
</button>
Expand Down
1 change: 1 addition & 0 deletions src/admin/components/elements/Button/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,5 @@ export type Props = {
size?: 'small' | 'medium',
iconPosition?: 'left' | 'right',
newTab?: boolean
tooltip?: string
}
1 change: 1 addition & 0 deletions src/admin/components/elements/Tooltip/index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
line-height: base(.75);
font-weight: normal;
white-space: nowrap;
border-radius: 2px;

span {
position: absolute;
Expand Down
8 changes: 4 additions & 4 deletions src/admin/components/forms/Form/reduceFieldsToValues.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { unflatten } from 'flatley';
import { unflatten as flatleyUnflatten } from 'flatley';
import { Fields, Data } from './types';

const reduceFieldsToValues = (fields: Fields, flatten?: boolean): Data => {
const reduceFieldsToValues = (fields: Fields, unflatten?: boolean): Data => {
const data = {};

Object.keys(fields).forEach((key) => {
Expand All @@ -14,8 +14,8 @@ const reduceFieldsToValues = (fields: Fields, flatten?: boolean): Data => {
}
});

if (flatten) {
const unflattened = unflatten(data, { safe: true });
if (unflatten) {
const unflattened = flatleyUnflatten(data, { safe: true });
return unflattened;
}

Expand Down
6 changes: 4 additions & 2 deletions src/admin/components/forms/field-types/RichText/RichText.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -80,14 +80,15 @@ const RichText: React.FC<Props> = (props) => {
attributes={attributes}
element={element}
path={path}
fieldProps={props}
>
{children}
</Element>
);
}

return <div {...attributes}>{children}</div>;
}, [enabledElements, path]);
}, [enabledElements, path, props]);

const renderLeaf = useCallback(({ attributes, children, leaf }) => {
const matchedLeafName = Object.keys(enabledLeaves).find((leafName) => leaf[leafName]);
Expand All @@ -100,6 +101,7 @@ const RichText: React.FC<Props> = (props) => {
attributes={attributes}
leaf={leaf}
path={path}
fieldProps={props}
>
{children}
</Leaf>
Expand All @@ -109,7 +111,7 @@ const RichText: React.FC<Props> = (props) => {
return (
<span {...attributes}>{children}</span>
);
}, [enabledLeaves, path]);
}, [enabledLeaves, path, props]);

const memoizedValidate = useCallback((value) => {
const validationResult = validate(value, { required });
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
@import '../../../../../../../../scss/styles.scss';

.edit-upload-modal {
@include blur-bg;
display: flex;
align-items: center;

.template-minimal {
padding-top: base(4);
align-items: flex-start;
}

&__header {
margin-bottom: $baseline;
display: flex;

h1 {
margin: 0 auto 0 0;
}

.btn {
margin: 0 0 0 $baseline;
}
}
}
Loading

0 comments on commit 0e4eb90

Please sign in to comment.