Merge pull request #1168 from payloadcms/fix/read-only

Fix: read only field styles
This commit is contained in:
James Mikrut
2022-10-06 13:20:16 -04:00
committed by GitHub
7 changed files with 184 additions and 57 deletions

View File

@@ -70,6 +70,7 @@ const RichText: React.FC<Props> = (props) => {
const [enabledLeaves, setEnabledLeaves] = useState({});
const [initialValueKey, setInitialValueKey] = useState('');
const editorRef = useRef(null);
const toolbarRef = useRef(null);
const renderElement = useCallback(({ attributes, children, element }) => {
const matchedElement = enabledElements[element?.type];
@@ -176,6 +177,31 @@ const RichText: React.FC<Props> = (props) => {
setInitialValueKey(JSON.stringify(initialValue || ''));
}, [initialValue]);
useEffect(() => {
function setClickableState(clickState: 'disabled' | 'enabled') {
const selectors = 'button, a, [role="button"]';
const toolbarButtons: (HTMLButtonElement | HTMLAnchorElement)[] = toolbarRef.current.querySelectorAll(selectors);
const editorButtons: (HTMLButtonElement | HTMLAnchorElement)[] = editorRef.current.querySelectorAll(selectors);
[...(toolbarButtons || []), ...(editorButtons || [])].forEach((child) => {
const isButton = child.tagName === 'BUTTON';
const isDisabling = clickState === 'disabled';
child.setAttribute('tabIndex', isDisabling ? '-1' : '0');
if (isButton) child.setAttribute('disabled', isDisabling ? 'disabled' : null);
});
}
if (loaded && readOnly) {
setClickableState('disabled');
}
return () => {
if (loaded && readOnly) {
setClickableState('enabled');
}
};
}, [loaded, readOnly]);
if (!loaded) {
return null;
}
@@ -215,13 +241,16 @@ const RichText: React.FC<Props> = (props) => {
editor={editor}
value={valueToRender as any[]}
onChange={(val) => {
if (val !== defaultValue && val !== value) {
if (!readOnly && val !== defaultValue && val !== value) {
setValue(val);
}
}}
>
<div className={`${baseClass}__wrapper`}>
<div className={`${baseClass}__toolbar`}>
<div
className={`${baseClass}__toolbar`}
ref={toolbarRef}
>
<div className={`${baseClass}__toolbar-wrap`}>
{elements.map((element, i) => {
let elementName: string;

View File

@@ -3,6 +3,7 @@
.rich-text {
margin-bottom: base(2);
display: flex;
isolation: isolate;
&__toolbar {
@include blur-bg(var(--theme-elevation-0));
@@ -103,6 +104,29 @@
.rich-text__editor {
background-color: var(--theme-elevation-150);
padding: base(.5);
.popup button {
display: none;
}
}
.rich-text__toolbar {
pointer-events: none;
position: relative;
top: 0;
&::after {
content: ' ';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: var(--theme-elevation-150);
opacity: .85;
z-index: 2;
backdrop-filter: unset;
}
}
}

View File

@@ -95,6 +95,7 @@ const Select: React.FC<Props> = (props) => {
showError={showError}
errorMessage={errorMessage}
required={required}
readOnly={readOnly}
description={description}
style={style}
className={className}

View File

@@ -73,12 +73,46 @@ const RichTextFields: CollectionConfig = {
},
},
},
{
name: 'richTextReadOnly',
type: 'richText',
admin: {
readOnly: true,
link: {
fields: [
{
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',
},
],
},
},
},
},
},
],
};
export const richTextDoc = {
selectHasMany: ['one', 'five'],
richText: [
function generateRichText() {
return [
{
children: [
{
@@ -220,7 +254,13 @@ export const richTextDoc = {
],
};
}),
],
];
}
export const richTextDoc = {
selectHasMany: ['one', 'five'],
richText: generateRichText(),
richTextReadOnly: generateRichText(),
};
export default RichTextFields;

View File

@@ -24,6 +24,27 @@ const SelectFields: CollectionConfig = {
},
],
},
{
name: 'selectReadOnly',
type: 'select',
admin: {
readOnly: true,
},
options: [
{
label: 'Value One',
value: 'one',
},
{
label: 'Value Two',
value: 'two',
},
{
label: 'Value Three',
value: 'three',
},
],
},
{
name: 'selectHasMany',
hasMany: true,

View File

@@ -92,6 +92,7 @@ export default buildConfig({
const richTextUploadIndex = richTextDocWithRelationship.richText.findIndex(({ type }) => type === 'upload');
richTextDocWithRelationship.richText[richTextUploadIndex].value = { id: createdUploadDoc.id };
richTextDocWithRelationship.richTextReadOnly[richTextUploadIndex].value = { id: createdUploadDoc.id };
await payload.create({ collection: 'rich-text-fields', data: richTextDocWithRelationship });

View File

@@ -143,73 +143,84 @@ describe('fields', () => {
});
describe('fields - richText', () => {
test('should create new url link', async () => {
async function navigateToRichTextFields() {
const url: AdminUrlUtil = new AdminUrlUtil(serverURL, 'rich-text-fields');
await page.goto(url.list);
await page.locator('.row-1 .cell-id').click();
}
// Open link popup
await page.locator('.rich-text__toolbar .link').click();
describe('toolbar', () => {
test('should create new url link', async () => {
await navigateToRichTextFields();
const editLinkModal = page.locator('.rich-text-link-edit-modal__template');
await expect(editLinkModal).toBeVisible();
// Open link popup
await page.locator('.rich-text__toolbar button:not([disabled]) .link').click();
// Fill values and click Confirm
await editLinkModal.locator('#field-text').fill('link text');
await editLinkModal.locator('label[for="field-linkType-custom"]').click();
await editLinkModal.locator('#field-url').fill('https://payloadcms.com');
await wait(200);
await editLinkModal.locator('button[type="submit"]').click();
const editLinkModal = page.locator('.rich-text-link-edit-modal__template');
await expect(editLinkModal).toBeVisible();
// Remove link
await page.locator('span >> text="link text"').click();
const popup = page.locator('.popup--active .rich-text-link__popup');
await expect(popup.locator('.rich-text-link__link-label')).toBeVisible();
await popup.locator('.rich-text-link__link-close').click();
await expect(page.locator('span >> text="link text"')).toHaveCount(0);
// Fill values and click Confirm
await editLinkModal.locator('#field-text').fill('link text');
await editLinkModal.locator('label[for="field-linkType-custom"]').click();
await editLinkModal.locator('#field-url').fill('https://payloadcms.com');
await wait(200);
await editLinkModal.locator('button[type="submit"]').click();
// Remove link from editor body
await page.locator('span >> text="link text"').click();
const popup = page.locator('.popup--active .rich-text-link__popup');
await expect(popup.locator('.rich-text-link__link-label')).toBeVisible();
await popup.locator('.rich-text-link__link-close').click();
await expect(page.locator('span >> text="link text"')).toHaveCount(0);
});
test('should not create new url link when read only', async () => {
await navigateToRichTextFields();
// Attempt to open link popup
const modalTrigger = page.locator('.rich-text--read-only .rich-text__toolbar button .link');
await expect(modalTrigger).toBeDisabled();
});
});
test('should populate url link', async () => {
const url: AdminUrlUtil = new AdminUrlUtil(serverURL, 'rich-text-fields');
await page.goto(url.list);
await page.locator('.row-1 .cell-id').click();
describe('editor', () => {
test('should populate url link', async () => {
navigateToRichTextFields();
// Open link popup
await page.locator('span >> text="render links"').click();
const popup = page.locator('.popup--active .rich-text-link__popup');
await expect(popup).toBeVisible();
await expect(popup.locator('a')).toHaveAttribute('href', 'https://payloadcms.com');
// Open link popup
await page.locator('#field-richText span >> text="render links"').click();
const popup = page.locator('.popup--active .rich-text-link__popup');
await expect(popup).toBeVisible();
await expect(popup.locator('a')).toHaveAttribute('href', 'https://payloadcms.com');
// Open link edit modal
await popup.locator('.rich-text-link__link-edit').click();
const editLinkModal = page.locator('.rich-text-link-edit-modal__template');
await expect(editLinkModal).toBeVisible();
// Open link edit modal
await popup.locator('.rich-text-link__link-edit').click();
const editLinkModal = page.locator('.rich-text-link-edit-modal__template');
await expect(editLinkModal).toBeVisible();
// Close link edit modal
await editLinkModal.locator('button[type="submit"]').click();
await expect(editLinkModal).not.toBeVisible();
});
// Close link edit modal
await editLinkModal.locator('button[type="submit"]').click();
await expect(editLinkModal).not.toBeVisible();
});
test('should populate relationship link', async () => {
const url: AdminUrlUtil = new AdminUrlUtil(serverURL, 'rich-text-fields');
await page.goto(url.list);
await page.locator('.row-1 .cell-id').click();
test('should populate relationship link', async () => {
navigateToRichTextFields();
// Open link popup
await page.locator('span >> text="link to relationships"').click();
const popup = page.locator('.popup--active .rich-text-link__popup');
await expect(popup).toBeVisible();
await expect(popup.locator('a')).toHaveAttribute('href', /\/admin\/collections\/array-fields\/.*/);
// Open link popup
await page.locator('#field-richText span >> text="link to relationships"').click();
const popup = page.locator('.popup--active .rich-text-link__popup');
await expect(popup).toBeVisible();
await expect(popup.locator('a')).toHaveAttribute('href', /\/admin\/collections\/array-fields\/.*/);
// Open link edit modal
await popup.locator('.rich-text-link__link-edit').click();
const editLinkModal = page.locator('.rich-text-link-edit-modal__template');
await expect(editLinkModal).toBeVisible();
// Open link edit modal
await popup.locator('.rich-text-link__link-edit').click();
const editLinkModal = page.locator('.rich-text-link-edit-modal__template');
await expect(editLinkModal).toBeVisible();
// Close link edit modal
await editLinkModal.locator('button[type="submit"]').click();
await expect(editLinkModal).not.toBeVisible();
// await page.locator('span >> text="render links"').click();
// Close link edit modal
await editLinkModal.locator('button[type="submit"]').click();
await expect(editLinkModal).not.toBeVisible();
});
});
});
});