mirror of
https://github.com/strapi/strapi.git
synced 2025-11-10 23:29:33 +00:00
Merge branch 'custom-fields/list-custom-fields' of https://github.com/strapi/strapi into list-custom-fields/add-howto-btn
This commit is contained in:
commit
08ae9f65f0
@ -130,6 +130,10 @@
|
|||||||
"morph_to_many": {
|
"morph_to_many": {
|
||||||
"type": "relation",
|
"type": "relation",
|
||||||
"relation": "morphToMany"
|
"relation": "morphToMany"
|
||||||
|
},
|
||||||
|
"custom_field": {
|
||||||
|
"type": "customField",
|
||||||
|
"customField": "plugin::mycustomfields.color"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,4 @@
|
|||||||
|
<svg width="32" height="24" viewBox="0 0 32 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<rect x="0.5" y="0.5" width="31" height="23" rx="2.5" fill="#F0F0FF" stroke="#D9D8FF"/>
|
||||||
|
<path d="M14.5861 5.13768C11.8681 5.66816 9.6778 7.85296 9.14186 10.5628C8.13012 15.6761 12.7431 19.4879 16.2185 18.9492C17.3451 18.7742 17.8975 17.4562 17.3806 16.4418C16.749 15.2003 17.6514 13.7511 19.0459 13.7511H21.2252C22.2042 13.7511 22.9971 12.9417 22.9999 11.9655C22.9862 7.65608 19.065 4.2654 14.5861 5.13768ZM11.6247 13.7511C11.1407 13.7511 10.7497 13.3601 10.7497 12.8761C10.7497 12.3921 11.1407 12.0011 11.6247 12.0011C12.1087 12.0011 12.4997 12.3921 12.4997 12.8761C12.4997 13.3601 12.1087 13.7511 11.6247 13.7511ZM12.4997 10.251C12.0157 10.251 11.6247 9.86002 11.6247 9.37603C11.6247 8.89204 12.0157 8.50101 12.4997 8.50101C12.9837 8.50101 13.3747 8.89204 13.3747 9.37603C13.3747 9.86002 12.9837 10.251 12.4997 10.251ZM15.9998 8.50101C15.5158 8.50101 15.1248 8.10999 15.1248 7.626C15.1248 7.14201 15.5158 6.75099 15.9998 6.75099C16.4838 6.75099 16.8748 7.14201 16.8748 7.626C16.8748 8.10999 16.4838 8.50101 15.9998 8.50101ZM19.4998 10.251C19.0158 10.251 18.6248 9.86002 18.6248 9.37603C18.6248 8.89204 19.0158 8.50101 19.4998 8.50101C19.9838 8.50101 20.3748 8.89204 20.3748 9.37603C20.3748 9.86002 19.9838 10.251 19.4998 10.251Z" fill="#4945FF"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.3 KiB |
@ -1,6 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import Puzzle from '@strapi/icons/Puzzle';
|
import icon from './icon.svg';
|
||||||
|
|
||||||
const ColorPickerIcon = () => <Puzzle />;
|
const ColorPickerIcon = () => <img src={icon} />;
|
||||||
|
|
||||||
export default ColorPickerIcon;
|
export default ColorPickerIcon;
|
||||||
|
|||||||
@ -4,8 +4,7 @@ import ColorPickerIcon from './components/ColorPicker/ColorPickerIcon';
|
|||||||
|
|
||||||
export default {
|
export default {
|
||||||
register(app) {
|
register(app) {
|
||||||
app.customFields.register([
|
app.customFields.register({
|
||||||
{
|
|
||||||
name: 'color',
|
name: 'color',
|
||||||
pluginId: 'mycustomfields',
|
pluginId: 'mycustomfields',
|
||||||
type: 'text',
|
type: 'text',
|
||||||
@ -24,27 +23,7 @@ export default {
|
|||||||
/* webpackChunkName: "input-component" */ './components/ColorPicker/ColorPickerInput'
|
/* webpackChunkName: "input-component" */ './components/ColorPicker/ColorPickerInput'
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
},
|
});
|
||||||
{
|
|
||||||
name: 'aMap',
|
|
||||||
pluginId: 'mycustomfields',
|
|
||||||
type: 'json',
|
|
||||||
intlLabel: {
|
|
||||||
id: 'mycustomfields.map.label',
|
|
||||||
defaultMessage: 'aMap',
|
|
||||||
},
|
|
||||||
intlDescription: {
|
|
||||||
id: 'mycustomfields.map.description',
|
|
||||||
defaultMessage: 'Select any location',
|
|
||||||
},
|
|
||||||
components: {
|
|
||||||
Input: async () =>
|
|
||||||
import(
|
|
||||||
/* webpackChunkName: "input-component" */ './components/ColorPicker/ColorPickerInput'
|
|
||||||
),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
},
|
},
|
||||||
bootstrap(app) {},
|
bootstrap(app) {},
|
||||||
async registerTrads({ locales }) {
|
async registerTrads({ locales }) {
|
||||||
|
|||||||
@ -18,7 +18,7 @@ import SingleType from '@strapi/icons/SingleType';
|
|||||||
import Text from '@strapi/icons/Text';
|
import Text from '@strapi/icons/Text';
|
||||||
import Uid from '@strapi/icons/Uid';
|
import Uid from '@strapi/icons/Uid';
|
||||||
import Numbers from '@strapi/icons/Number';
|
import Numbers from '@strapi/icons/Number';
|
||||||
import { pxToRem } from '@strapi/helper-plugin';
|
import { pxToRem, useCustomFields } from '@strapi/helper-plugin';
|
||||||
|
|
||||||
const iconByTypes = {
|
const iconByTypes = {
|
||||||
biginteger: Numbers,
|
biginteger: Numbers,
|
||||||
@ -52,14 +52,24 @@ const iconByTypes = {
|
|||||||
uid: Uid,
|
uid: Uid,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const IconBox = styled(Box)`
|
const IconBox = styled(Box)`
|
||||||
width: ${pxToRem(32)};
|
width: ${pxToRem(32)};
|
||||||
height: ${pxToRem(24)};
|
height: ${pxToRem(24)};
|
||||||
box-sizing: content-box;
|
box-sizing: content-box;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const AttributeIcon = ({ type, ...rest }) => {
|
const AttributeIcon = ({ type, customField, ...rest }) => {
|
||||||
const Compo = iconByTypes[type];
|
const customFieldsRegistry = useCustomFields();
|
||||||
|
|
||||||
|
let Compo = iconByTypes[type];
|
||||||
|
|
||||||
|
if (customField) {
|
||||||
|
const { icon } = customFieldsRegistry.get(customField);
|
||||||
|
|
||||||
|
if (icon) {
|
||||||
|
Compo = icon;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!iconByTypes[type]) {
|
if (!iconByTypes[type]) {
|
||||||
return null;
|
return null;
|
||||||
@ -68,8 +78,13 @@ const AttributeIcon = ({ type, ...rest }) => {
|
|||||||
return <IconBox as={Compo} {...rest} />;
|
return <IconBox as={Compo} {...rest} />;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
AttributeIcon.defaultProps = {
|
||||||
|
customField: null,
|
||||||
|
};
|
||||||
|
|
||||||
AttributeIcon.propTypes = {
|
AttributeIcon.propTypes = {
|
||||||
type: PropTypes.string.isRequired,
|
type: PropTypes.string.isRequired,
|
||||||
|
customField: PropTypes.string,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default AttributeIcon;
|
export default AttributeIcon;
|
||||||
|
|||||||
@ -11,11 +11,11 @@ import { Box } from '@strapi/design-system/Box';
|
|||||||
import { Flex } from '@strapi/design-system/Flex';
|
import { Flex } from '@strapi/design-system/Flex';
|
||||||
import { Typography } from '@strapi/design-system/Typography';
|
import { Typography } from '@strapi/design-system/Typography';
|
||||||
import OptionBoxWrapper from '../OptionBoxWrapper';
|
import OptionBoxWrapper from '../OptionBoxWrapper';
|
||||||
import AttributeIcon, { IconBox } from '../../AttributeIcon';
|
import AttributeIcon from '../../AttributeIcon';
|
||||||
import useFormModalNavigation from '../../../hooks/useFormModalNavigation';
|
import useFormModalNavigation from '../../../hooks/useFormModalNavigation';
|
||||||
|
|
||||||
const CustomFieldOption = ({ uid, customField }) => {
|
const CustomFieldOption = ({ customFieldUid, customField }) => {
|
||||||
const { type, icon, intlLabel, intlDescription } = customField;
|
const { type, intlLabel, intlDescription } = customField;
|
||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
|
|
||||||
const { onClickSelectCustomField } = useFormModalNavigation();
|
const { onClickSelectCustomField } = useFormModalNavigation();
|
||||||
@ -23,14 +23,14 @@ const CustomFieldOption = ({ uid, customField }) => {
|
|||||||
const handleClick = () => {
|
const handleClick = () => {
|
||||||
onClickSelectCustomField({
|
onClickSelectCustomField({
|
||||||
attributeType: type,
|
attributeType: type,
|
||||||
customFieldUid: uid,
|
customFieldUid,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<OptionBoxWrapper padding={4} as="button" hasRadius type="button" onClick={handleClick}>
|
<OptionBoxWrapper padding={4} as="button" hasRadius type="button" onClick={handleClick}>
|
||||||
<Flex>
|
<Flex>
|
||||||
{icon ? <IconBox as={icon} /> : <AttributeIcon type={type} />}
|
<AttributeIcon type={type} customField={customFieldUid} />
|
||||||
<Box paddingLeft={4}>
|
<Box paddingLeft={4}>
|
||||||
<Flex>
|
<Flex>
|
||||||
<Typography fontWeight="bold">{formatMessage(intlLabel)}</Typography>
|
<Typography fontWeight="bold">{formatMessage(intlLabel)}</Typography>
|
||||||
@ -47,7 +47,7 @@ const CustomFieldOption = ({ uid, customField }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
CustomFieldOption.propTypes = {
|
CustomFieldOption.propTypes = {
|
||||||
uid: PropTypes.string.isRequired,
|
customFieldUid: PropTypes.string.isRequired,
|
||||||
customField: PropTypes.shape({
|
customField: PropTypes.shape({
|
||||||
type: PropTypes.string.isRequired,
|
type: PropTypes.string.isRequired,
|
||||||
icon: PropTypes.func,
|
icon: PropTypes.func,
|
||||||
|
|||||||
@ -41,7 +41,7 @@ const CustomFieldsList = () => {
|
|||||||
paddingBottom={1}
|
paddingBottom={1}
|
||||||
style={{ height: '100%' }}
|
style={{ height: '100%' }}
|
||||||
>
|
>
|
||||||
<CustomFieldOption key={uid} uid={uid} customField={customField} />
|
<CustomFieldOption key={uid} customFieldUid={uid} customField={customField} />
|
||||||
</Box>
|
</Box>
|
||||||
</GridItem>
|
</GridItem>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -18,10 +18,8 @@ const EmptyCard = styled(Box)`
|
|||||||
|
|
||||||
export const EmptyCardGrid = () => {
|
export const EmptyCardGrid = () => {
|
||||||
return (
|
return (
|
||||||
<Flex wrap="wrap" gap="16px">
|
<Flex wrap="wrap" gap={4}>
|
||||||
{Array(4)
|
{[...Array(4)].map((_, idx) => {
|
||||||
.fill(null)
|
|
||||||
.map((_, idx) => {
|
|
||||||
return (
|
return (
|
||||||
<EmptyCard
|
<EmptyCard
|
||||||
// eslint-disable-next-line react/no-array-index-key
|
// eslint-disable-next-line react/no-array-index-key
|
||||||
@ -54,7 +52,7 @@ const EmptyAttributes = () => {
|
|||||||
})}
|
})}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Box paddingTop={4}>
|
<Box paddingTop={4}>
|
||||||
<Typography variant="delta" as="p" textColor="neutral600" lineHeight="2.25">
|
<Typography variant="delta" as="p" textColor="neutral600">
|
||||||
{formatMessage({
|
{formatMessage({
|
||||||
id: getTrad('modalForm.empty.sub-heading'),
|
id: getTrad('modalForm.empty.sub-heading'),
|
||||||
defaultMessage:
|
defaultMessage:
|
||||||
|
|||||||
@ -3,16 +3,37 @@ import { Router } from 'react-router-dom';
|
|||||||
import { createMemoryHistory } from 'history';
|
import { createMemoryHistory } from 'history';
|
||||||
import { render, screen, getByText, fireEvent } from '@testing-library/react';
|
import { render, screen, getByText, fireEvent } from '@testing-library/react';
|
||||||
import { lightTheme, ThemeProvider } from '@strapi/design-system';
|
import { lightTheme, ThemeProvider } from '@strapi/design-system';
|
||||||
import { useCustomFields } from '@strapi/helper-plugin';
|
|
||||||
import { IntlProvider } from 'react-intl';
|
import { IntlProvider } from 'react-intl';
|
||||||
import FormModalNavigationProvider from '../../FormModalNavigationProvider';
|
import FormModalNavigationProvider from '../../FormModalNavigationProvider';
|
||||||
import AttributeOptions from '../index';
|
import AttributeOptions from '../index';
|
||||||
|
|
||||||
|
const mockCustomField = {
|
||||||
|
'plugin::mycustomfields.test': {
|
||||||
|
name: 'color',
|
||||||
|
pluginId: 'mycustomfields',
|
||||||
|
type: 'text',
|
||||||
|
icon: jest.fn(),
|
||||||
|
intlLabel: {
|
||||||
|
id: 'mycustomfields.color.label',
|
||||||
|
defaultMessage: 'Color',
|
||||||
|
},
|
||||||
|
intlDescription: {
|
||||||
|
id: 'mycustomfields.color.description',
|
||||||
|
defaultMessage: 'Select any color',
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
Input: jest.fn(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const getAll = jest.fn().mockReturnValue({});
|
||||||
jest.mock('@strapi/helper-plugin', () => ({
|
jest.mock('@strapi/helper-plugin', () => ({
|
||||||
...jest.requireActual('@strapi/helper-plugin'),
|
...jest.requireActual('@strapi/helper-plugin'),
|
||||||
useCustomFields: jest.fn(() => ({
|
useCustomFields: () => ({
|
||||||
getAll: jest.fn(() => ({})),
|
get: jest.fn().mockReturnValue(mockCustomField),
|
||||||
})),
|
getAll,
|
||||||
|
}),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const mockAttributes = [
|
const mockAttributes = [
|
||||||
@ -85,6 +106,8 @@ describe('<AttributeOptions />', () => {
|
|||||||
const App = makeApp();
|
const App = makeApp();
|
||||||
render(App);
|
render(App);
|
||||||
|
|
||||||
|
getAll.mockReturnValueOnce({});
|
||||||
|
|
||||||
const customTab = screen.getByRole('tab', { selected: false });
|
const customTab = screen.getByRole('tab', { selected: false });
|
||||||
fireEvent.click(customTab);
|
fireEvent.click(customTab);
|
||||||
const customTabSelected = screen.getByRole('tab', { selected: true });
|
const customTabSelected = screen.getByRole('tab', { selected: true });
|
||||||
@ -96,29 +119,7 @@ describe('<AttributeOptions />', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('switches to the custom tab with custom fields', () => {
|
it('switches to the custom tab with custom fields', () => {
|
||||||
useCustomFields.mockImplementationOnce(
|
getAll.mockReturnValue(mockCustomField);
|
||||||
jest.fn(() => ({
|
|
||||||
getAll: jest.fn(() => ({
|
|
||||||
'plugin::mycustomfields.test': {
|
|
||||||
name: 'color',
|
|
||||||
pluginId: 'mycustomfields',
|
|
||||||
type: 'text',
|
|
||||||
intlLabel: {
|
|
||||||
id: 'mycustomfields.color.label',
|
|
||||||
defaultMessage: 'Color',
|
|
||||||
},
|
|
||||||
intlDescription: {
|
|
||||||
id: 'mycustomfields.color.description',
|
|
||||||
defaultMessage: 'Select any color',
|
|
||||||
},
|
|
||||||
components: {
|
|
||||||
Input: jest.fn(),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})),
|
|
||||||
}))
|
|
||||||
);
|
|
||||||
|
|
||||||
const App = makeApp();
|
const App = makeApp();
|
||||||
render(App);
|
render(App);
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1,56 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { useIntl } from 'react-intl';
|
||||||
|
import { Typography } from '@strapi/design-system/Typography';
|
||||||
|
import getTrad from '../../utils/getTrad';
|
||||||
|
|
||||||
|
const DisplayedType = ({ type, customField, repeatable }) => {
|
||||||
|
const { formatMessage } = useIntl();
|
||||||
|
|
||||||
|
let readableType = type;
|
||||||
|
|
||||||
|
if (['integer', 'biginteger', 'float', 'decimal'].includes(type)) {
|
||||||
|
readableType = 'number';
|
||||||
|
} else if (['string'].includes(type)) {
|
||||||
|
readableType = 'text';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (customField) {
|
||||||
|
return (
|
||||||
|
<Typography>
|
||||||
|
{formatMessage({
|
||||||
|
id: getTrad('attribute.customField'),
|
||||||
|
defaultMessage: 'Custom field',
|
||||||
|
})}
|
||||||
|
</Typography>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Typography>
|
||||||
|
{formatMessage({
|
||||||
|
id: getTrad(`attribute.${readableType}`),
|
||||||
|
defaultMessage: type,
|
||||||
|
})}
|
||||||
|
|
||||||
|
{repeatable &&
|
||||||
|
formatMessage({
|
||||||
|
id: getTrad('component.repeatable'),
|
||||||
|
defaultMessage: '(repeatable)',
|
||||||
|
})}
|
||||||
|
</Typography>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
DisplayedType.defaultProps = {
|
||||||
|
customField: null,
|
||||||
|
repeatable: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
DisplayedType.propTypes = {
|
||||||
|
type: PropTypes.string.isRequired,
|
||||||
|
customField: PropTypes.bool,
|
||||||
|
repeatable: PropTypes.bool,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DisplayedType;
|
||||||
@ -17,9 +17,11 @@ import Curve from '../../icons/Curve';
|
|||||||
import UpperFist from '../UpperFirst';
|
import UpperFist from '../UpperFirst';
|
||||||
import BoxWrapper from './BoxWrapper';
|
import BoxWrapper from './BoxWrapper';
|
||||||
import AttributeIcon from '../AttributeIcon';
|
import AttributeIcon from '../AttributeIcon';
|
||||||
|
import DisplayedType from './DisplayedType';
|
||||||
|
|
||||||
function ListRow({
|
function ListRow({
|
||||||
configurable,
|
configurable,
|
||||||
|
customField,
|
||||||
editTarget,
|
editTarget,
|
||||||
firstLoopComponentUid,
|
firstLoopComponentUid,
|
||||||
isFromDynamicZone,
|
isFromDynamicZone,
|
||||||
@ -38,14 +40,6 @@ function ListRow({
|
|||||||
const isMorph = type === 'relation' && relation.includes('morph');
|
const isMorph = type === 'relation' && relation.includes('morph');
|
||||||
const ico = ['integer', 'biginteger', 'float', 'decimal'].includes(type) ? 'number' : type;
|
const ico = ['integer', 'biginteger', 'float', 'decimal'].includes(type) ? 'number' : type;
|
||||||
|
|
||||||
let readableType = type;
|
|
||||||
|
|
||||||
if (['integer', 'biginteger', 'float', 'decimal'].includes(type)) {
|
|
||||||
readableType = 'number';
|
|
||||||
} else if (['string'].includes(type)) {
|
|
||||||
readableType = 'text';
|
|
||||||
}
|
|
||||||
|
|
||||||
const contentType = get(contentTypes, [target], {});
|
const contentType = get(contentTypes, [target], {});
|
||||||
const contentTypeFriendlyName = get(contentType, ['schema', 'displayName'], '');
|
const contentTypeFriendlyName = get(contentType, ['schema', 'displayName'], '');
|
||||||
const isPluginContentType = get(contentType, 'plugin');
|
const isPluginContentType = get(contentType, 'plugin');
|
||||||
@ -93,7 +87,7 @@ function ListRow({
|
|||||||
<td style={{ position: 'relative' }}>
|
<td style={{ position: 'relative' }}>
|
||||||
{loopNumber !== 0 && <Curve color={isFromDynamicZone ? 'primary200' : 'neutral150'} />}
|
{loopNumber !== 0 && <Curve color={isFromDynamicZone ? 'primary200' : 'neutral150'} />}
|
||||||
<Stack paddingLeft={2} spacing={4} horizontal>
|
<Stack paddingLeft={2} spacing={4} horizontal>
|
||||||
<AttributeIcon key={src} type={src} />
|
<AttributeIcon type={src} customField={customField} />
|
||||||
<Typography fontWeight="bold">{name}</Typography>
|
<Typography fontWeight="bold">{name}</Typography>
|
||||||
</Stack>
|
</Stack>
|
||||||
</td>
|
</td>
|
||||||
@ -118,18 +112,7 @@ function ListRow({
|
|||||||
</span>
|
</span>
|
||||||
</Typography>
|
</Typography>
|
||||||
) : (
|
) : (
|
||||||
<Typography>
|
<DisplayedType type={type} customField={customField} repeatable={repeatable} />
|
||||||
{formatMessage({
|
|
||||||
id: getTrad(`attribute.${readableType}`),
|
|
||||||
defaultMessage: type,
|
|
||||||
})}
|
|
||||||
|
|
||||||
{repeatable &&
|
|
||||||
formatMessage({
|
|
||||||
id: getTrad('component.repeatable'),
|
|
||||||
defaultMessage: '(repeatable)',
|
|
||||||
})}
|
|
||||||
</Typography>
|
|
||||||
)}
|
)}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
@ -184,6 +167,7 @@ function ListRow({
|
|||||||
|
|
||||||
ListRow.defaultProps = {
|
ListRow.defaultProps = {
|
||||||
configurable: true,
|
configurable: true,
|
||||||
|
customField: null,
|
||||||
firstLoopComponentUid: null,
|
firstLoopComponentUid: null,
|
||||||
isFromDynamicZone: false,
|
isFromDynamicZone: false,
|
||||||
onClick: () => {},
|
onClick: () => {},
|
||||||
@ -197,6 +181,7 @@ ListRow.defaultProps = {
|
|||||||
|
|
||||||
ListRow.propTypes = {
|
ListRow.propTypes = {
|
||||||
configurable: PropTypes.bool,
|
configurable: PropTypes.bool,
|
||||||
|
customField: PropTypes.string,
|
||||||
editTarget: PropTypes.string.isRequired,
|
editTarget: PropTypes.string.isRequired,
|
||||||
firstLoopComponentUid: PropTypes.string,
|
firstLoopComponentUid: PropTypes.string,
|
||||||
isFromDynamicZone: PropTypes.bool,
|
isFromDynamicZone: PropTypes.bool,
|
||||||
|
|||||||
@ -4,6 +4,7 @@
|
|||||||
"attribute.boolean.description": "Yes or no, 1 or 0, true or false",
|
"attribute.boolean.description": "Yes or no, 1 or 0, true or false",
|
||||||
"attribute.component": "Component",
|
"attribute.component": "Component",
|
||||||
"attribute.component.description": "Group of fields that you can repeat or reuse",
|
"attribute.component.description": "Group of fields that you can repeat or reuse",
|
||||||
|
"attribute.customField": "Custom field",
|
||||||
"attribute.date": "Date",
|
"attribute.date": "Date",
|
||||||
"attribute.date.description": "A date picker with hours, minutes and seconds",
|
"attribute.date.description": "A date picker with hours, minutes and seconds",
|
||||||
"attribute.datetime": "Datetime",
|
"attribute.datetime": "Datetime",
|
||||||
|
|||||||
@ -0,0 +1,34 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const { formatAttribute } = require('../attributes');
|
||||||
|
|
||||||
|
describe('format attributes', () => {
|
||||||
|
it('replaces type customField with the underlying data type', () => {
|
||||||
|
const mockAttribute = {
|
||||||
|
type: 'customField',
|
||||||
|
customField: 'plugin::mycustomfields.color',
|
||||||
|
};
|
||||||
|
|
||||||
|
global.strapi = {
|
||||||
|
container: {
|
||||||
|
// mock container.get('custom-fields')
|
||||||
|
get: jest.fn(() => ({
|
||||||
|
// mock container.get('custom-fields').get(uid)
|
||||||
|
get: jest.fn(() => ({
|
||||||
|
name: 'color',
|
||||||
|
plugin: 'mycustomfields',
|
||||||
|
type: 'text',
|
||||||
|
})),
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const formattedAttribute = formatAttribute('key', mockAttribute);
|
||||||
|
|
||||||
|
const expected = {
|
||||||
|
type: 'text',
|
||||||
|
customField: 'plugin::mycustomfields.color',
|
||||||
|
};
|
||||||
|
expect(formattedAttribute).toEqual(expected);
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -67,6 +67,15 @@ const formatAttribute = (key, attribute) => {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (attribute.type === 'customField') {
|
||||||
|
const customField = strapi.container.get('custom-fields').get(attribute.customField);
|
||||||
|
|
||||||
|
return {
|
||||||
|
...attribute,
|
||||||
|
type: customField.type,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
return attribute;
|
return attribute;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -8,6 +8,7 @@ const strapi = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
describe('Custom fields registry', () => {
|
describe('Custom fields registry', () => {
|
||||||
|
describe('add', () => {
|
||||||
it('adds a custom field registered in a plugin', () => {
|
it('adds a custom field registered in a plugin', () => {
|
||||||
const mockCF = {
|
const mockCF = {
|
||||||
name: 'test',
|
name: 'test',
|
||||||
@ -103,4 +104,27 @@ describe('Custom fields registry', () => {
|
|||||||
`Custom field: 'plugin::plugintest.test' has already been registered`
|
`Custom field: 'plugin::plugintest.test' has already been registered`
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
describe('get', () => {
|
||||||
|
it('gets a registered custom field', () => {
|
||||||
|
const mockCF = {
|
||||||
|
name: 'test',
|
||||||
|
plugin: 'plugintest',
|
||||||
|
type: 'text',
|
||||||
|
};
|
||||||
|
|
||||||
|
const customFields = customFieldsRegistry(strapi);
|
||||||
|
customFields.add(mockCF);
|
||||||
|
|
||||||
|
expect(customFields.get('plugin::plugintest.test')).toEqual(mockCF);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('throws when a custom field is not registered', () => {
|
||||||
|
const customFields = customFieldsRegistry(strapi);
|
||||||
|
|
||||||
|
expect(() => customFields.get('plugin::plugintest.test')).toThrowError(
|
||||||
|
`Could not find Custom Field: plugin::plugintest.test`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -10,6 +10,14 @@ const customFieldsRegistry = strapi => {
|
|||||||
getAll() {
|
getAll() {
|
||||||
return customFields;
|
return customFields;
|
||||||
},
|
},
|
||||||
|
get(customField) {
|
||||||
|
const registeredCustomField = customFields[customField];
|
||||||
|
if (!registeredCustomField) {
|
||||||
|
throw new Error(`Could not find Custom Field: ${customField}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return registeredCustomField;
|
||||||
|
},
|
||||||
add(customField) {
|
add(customField) {
|
||||||
const customFieldList = Array.isArray(customField) ? customField : [customField];
|
const customFieldList = Array.isArray(customField) ? customField : [customField];
|
||||||
|
|
||||||
|
|||||||
@ -19,6 +19,11 @@ const cleanSchemaAttributes = (attributes, { typeMap = new Map(), isRequest = fa
|
|||||||
delete attributesCopy[prop].default;
|
delete attributesCopy[prop].default;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (attribute.type === 'customField') {
|
||||||
|
const customField = strapi.container.get('custom-fields').get(attribute.customField);
|
||||||
|
attribute.type = customField.type;
|
||||||
|
}
|
||||||
|
|
||||||
switch (attribute.type) {
|
switch (attribute.type) {
|
||||||
case 'password': {
|
case 'password': {
|
||||||
if (!isRequest) {
|
if (!isRequest) {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user