feat: allow customizing the link fields (#2559)

This commit is contained in:
Michel v. Varendorff
2023-04-26 17:08:47 +02:00
committed by GitHub
parent ddb34c3d83
commit bf6522898d
7 changed files with 91 additions and 22 deletions

View File

@@ -83,6 +83,8 @@ Set this property to `true` to hide this field's gutter within the admin panel.
This allows [fields](/docs/fields/overview) to be saved as extra fields on a link 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 link element.
`link.fields` may either be an array of fields (in which case all fields defined in it will be appended below the default fields) or a function that accepts the default fields as only argument and returns an array defining the entirety of fields to be used (thus providing a mechanism of overriding the default fields).
![RichText link fields](https://payloadcms.com/images/docs/fields/richText/rte-link-fields-modal.jpg)
*RichText link with custom fields*

View File

@@ -63,16 +63,16 @@ export const LinkButton: React.FC<{
const locale = useLocale();
const [initialState, setInitialState] = useState<Fields>({});
const { t } = useTranslation(['upload', 'general']);
const { t, i18n } = useTranslation(['upload', 'general']);
const editor = useSlate();
const config = useConfig();
const [fieldSchema] = useState(() => {
const fields: Field[] = [
...getBaseFields(config),
];
const baseFields: Field[] = getBaseFields(config);
if (customFieldSchema) {
const fields = typeof customFieldSchema === 'function' ? customFieldSchema({ defaultFields: baseFields, config, i18n }) : baseFields;
if (Array.isArray(customFieldSchema)) {
fields.push({
name: 'fields',
type: 'group',

View File

@@ -79,11 +79,11 @@ export const LinkElement: React.FC<{
const [renderPopup, setRenderPopup] = useState(false);
const [initialState, setInitialState] = useState<Fields>({});
const [fieldSchema] = useState(() => {
const fields: Field[] = [
...getBaseFields(config),
];
const baseFields: Field[] = getBaseFields(config);
if (customFieldSchema) {
const fields = typeof customFieldSchema === 'function' ? customFieldSchema({ defaultFields: baseFields, config, i18n }) : baseFields;
if (Array.isArray(customFieldSchema)) {
fields.push({
name: 'fields',
type: 'group',

View File

@@ -413,7 +413,10 @@ export const richText = baseField.keys({
})),
}),
link: joi.object({
fields: joi.array().items(joi.link('#field')),
fields: joi.alternatives(
joi.array().items(joi.link('#field')),
joi.func(),
),
}),
}),
});

View File

@@ -1,9 +1,10 @@
/* eslint-disable no-use-before-define */
import { CSSProperties } from 'react';
import { Editor } from 'slate';
import type { TFunction } from 'i18next';
import type { TFunction, i18n as Ii18n } from 'i18next';
import type { EditorProps } from '@monaco-editor/react';
import { Operation, Where } from '../../types';
import { SanitizedConfig } from "../../config/types";
import { TypeWithID } from '../../collections/config/types';
import { PayloadRequest } from '../../express/types';
import { ConditionalDateProps } from '../../admin/components/elements/DatePicker/types';
@@ -353,7 +354,7 @@ export type RichTextField = FieldBase & {
}
}
link?: {
fields?: Field[];
fields?: Field[] | ((args: {defaultFields: Field[], config: SanitizedConfig, i18n: Ii18n}) => Field[]);
}
}
}

View File

@@ -95,6 +95,55 @@ const RichTextFields: CollectionConfig = {
},
},
},
{
name: 'richTextCustomFields',
type: 'richText',
admin: {
elements: [
'h1',
'h2',
'h3',
'h4',
'h5',
'h6',
'ul',
'ol',
'indent',
'link',
'relationship',
'upload',
],
link: {
fields: () => [
{
required: false,
name: 'rel',
label: 'Rel Attribute',
type: 'select',
hasMany: true,
options: [
'noopener', 'noreferrer', 'nofollow',
],
admin: {
description: 'The rel attribute defines the relationship between a linked resource and the current document. This is a custom link field.',
},
},
],
},
upload: {
collections: {
uploads: {
fields: [
{
name: 'caption',
type: 'richText',
},
],
},
},
},
},
},
{
name: 'richTextReadOnly',
type: 'richText',
@@ -401,6 +450,7 @@ export const richTextDoc = {
selectHasMany: ['one', 'five'],
richText: generateRichText(),
richTextReadOnly: generateRichText(),
richTextCustomFields: generateRichText(),
};
export default RichTextFields;

View File

@@ -446,7 +446,7 @@ describe('fields', () => {
await navigateToRichTextFields();
// Open link drawer
await page.locator('.rich-text__toolbar button:not([disabled]) .link').click();
await page.locator('.rich-text__toolbar button:not([disabled]) .link').first().click();
// find the drawer
const editLinkModal = await page.locator('[id^=drawer_1_rich-text-link-]');
@@ -479,7 +479,7 @@ describe('fields', () => {
await navigateToRichTextFields();
// Open link drawer
await page.locator('.rich-text__toolbar button:not([disabled]) .upload-rich-text-button').click();
await page.locator('.rich-text__toolbar button:not([disabled]) .upload-rich-text-button').first().click();
// open the list select menu
await page.locator('.list-drawer__select-collection-wrap .rs__control').click();
@@ -493,7 +493,7 @@ describe('fields', () => {
await navigateToRichTextFields();
// Open link drawer
await page.locator('.rich-text__toolbar button:not([disabled]) .relationship-rich-text-button').click();
await page.locator('.rich-text__toolbar button:not([disabled]) .relationship-rich-text-button').first().click();
// open the list select menu
await page.locator('.list-drawer__select-collection-wrap .rs__control').click();
@@ -501,11 +501,24 @@ describe('fields', () => {
const menu = page.locator('.list-drawer__select-collection-wrap .rs__menu');
await expect(menu).not.toContainText('Uploads');
});
test('should respect customizing the default fields', async () => {
await navigateToRichTextFields();
const field = page.locator('.rich-text', { has: page.locator('#field-richTextCustomFields') });
const button = await field.locator('button.rich-text__button.link');
await button.click();
const linkDrawer = await page.locator('[id^=drawer_1_rich-text-link-]');
await expect(linkDrawer).toBeVisible();
const fieldCount = await linkDrawer.locator('.render-fields > .field-type').count();
await expect(fieldCount).toEqual(1);
});
});
describe('editor', () => {
test('should populate url link', async () => {
navigateToRichTextFields();
await navigateToRichTextFields();
// Open link popup
await page.locator('#field-richText span >> text="render links"').click();
@@ -528,7 +541,7 @@ describe('fields', () => {
});
test('should populate relationship link', async () => {
navigateToRichTextFields();
await navigateToRichTextFields();
// Open link popup
await page.locator('#field-richText span >> text="link to relationships"').click();
@@ -551,7 +564,7 @@ describe('fields', () => {
});
test('should open upload drawer and render custom relationship fields', async () => {
navigateToRichTextFields();
await navigateToRichTextFields();
const field = await page.locator('#field-richText');
const button = await field.locator('button.rich-text-upload__upload-drawer-toggler');
@@ -564,7 +577,7 @@ describe('fields', () => {
});
test('should open upload document drawer from read-only field', async () => {
navigateToRichTextFields();
await navigateToRichTextFields();
const field = await page.locator('#field-richTextReadOnly');
const button = await field.locator('button.rich-text-upload__doc-drawer-toggler.doc-drawer__toggler');
@@ -575,7 +588,7 @@ describe('fields', () => {
});
test('should open relationship document drawer from read-only field', async () => {
navigateToRichTextFields();
await navigateToRichTextFields();
const field = await page.locator('#field-richTextReadOnly');
const button = await field.locator('button.rich-text-relationship__doc-drawer-toggler.doc-drawer__toggler');
@@ -586,14 +599,14 @@ describe('fields', () => {
});
test('should populate new links', async () => {
navigateToRichTextFields();
await navigateToRichTextFields();
// Highlight existing text
const headingElement = await page.locator('#field-richText h1 >> text="Hello, I\'m a rich text field."');
await headingElement.selectText();
// click the toolbar link button
await page.locator('.rich-text__toolbar button:not([disabled]) .link').click();
await page.locator('.rich-text__toolbar button:not([disabled]) .link').first().click();
// find the drawer and confirm the values
const editLinkModal = await page.locator('[id^=drawer_1_rich-text-link-]');