Init attribute form

This commit is contained in:
soupette 2019-11-18 19:03:24 +01:00 committed by Alexandre Bodin
parent af8b17da1b
commit 41f09d410f
12 changed files with 435 additions and 273 deletions

View File

@ -19,10 +19,19 @@ const Button = styled.button`
&:hover,
&:active,
&:focus {
background: #f7f7f7;
outline: 0;
> div:after {
color: #0097f6;
background: #e6f0fb;
border-color: #aed4fb;
.attributeIcon {
background-color: #007eff;
> svg {
g {
path {
fill: #007eff;
}
}
}
}
}

View File

@ -6,33 +6,10 @@ const Card = styled.div`
align-items: center;
max-width: calc(100% - 18px);
&:after {
content: '\f05d';
position: absolute;
top: 7px;
right: 26px;
color: #e3e9f3;
font-size: 1.4rem;
font-family: 'FontAwesome';
-webkit-font-smoothing: antialiased;
}
&:hover {
background: none;
}
> img {
display: inline-block;
height: 20px;
width: 35px;
margin-right: 10px;
}
> span {
white-space: nowrap;
color: #9ea7b8;
font-size: 1.2rem;
font-style: italic;
font-weight: 400;
-webkit-font-smoothing: antialiased;
margin-top: auto;

View File

@ -4,81 +4,76 @@
*
*/
import React from 'react';
import React, {
forwardRef,
useEffect,
useImperativeHandle,
useRef,
} from 'react';
import { AttributeIcon } from '@buffetjs/core';
import PropTypes from 'prop-types';
import { FormattedMessage } from 'react-intl';
import attributeIcons from '../../utils/attributeIcons';
import pluginId from '../../pluginId';
import { useHistory } from 'react-router-dom';
import getTrad from '../../utils/getTrad';
import useQuery from '../../hooks/useQuery';
import Button from './Button';
import Card from './Card';
class AttributeOption extends React.Component {
componentDidUpdate(prevProps) {
const { isDisplayed, nodeToFocus, tabIndex } = this.props;
const AttributeOption = forwardRef(({ tabIndex, type }, ref) => {
const buttonRef = useRef();
const tabRef = useRef();
const query = useQuery();
const { push } = useHistory();
tabRef.current = tabIndex;
if (
prevProps.isDisplayed !== isDisplayed &&
isDisplayed &&
nodeToFocus === tabIndex
) {
this.focusNode();
useImperativeHandle(ref, () => ({
focus: () => {
buttonRef.current.focus();
},
}));
useEffect(() => {
if (tabRef.current === 0) {
buttonRef.current.focus();
}
}, []);
if (prevProps.nodeToFocus !== nodeToFocus && nodeToFocus === tabIndex) {
this.focusNode();
}
}
const handleClick = () => {
const forTarget = query.get('for');
const target = query.get('target');
button = React.createRef();
focusNode = () => {
const { current } = this.button;
current.focus();
push({
search: `modalType=attribute&actionType=create&settingType=base&for=${forTarget}&target=${target}&attributeType=${type}`,
});
};
render() {
const { description, onClick, tabIndex, type } = this.props;
return (
<div className="col-6">
<Button ref={buttonRef} type="button" onClick={handleClick}>
<Card>
<AttributeIcon
type={type}
style={{ marginRight: 10 }}
className="attributeIcon"
/>
<FormattedMessage id={getTrad(`attribute.${type}`)}>
{message => <span className="attributeType">{message}</span>}
</FormattedMessage>
<FormattedMessage id={getTrad(`attribute.${type}.description`)} />
</Card>
</Button>
</div>
);
});
return (
<div className="col-md-6">
<Button
id={`attrCard${type}`}
onClick={() => onClick(type)}
type="button"
tabIndex={tabIndex + 1}
ref={this.button}
>
<Card>
<img src={attributeIcons[type]} alt="ico" />
<FormattedMessage
id={`${pluginId}.popUpForm.attributes.${type}.name`}
>
{message => <span className="attributeType">{message}</span>}
</FormattedMessage>
<FormattedMessage id={description} />
</Card>
</Button>
</div>
);
}
}
AttributeOption.displayName = 'AttributeOption';
AttributeOption.defaultProps = {
description: 'app.utils.defaultMessage',
isDisplayed: false,
nodeToFocus: -1,
onClick: () => {},
tabIndex: 0,
type: 'string',
type: 'text',
};
AttributeOption.propTypes = {
description: PropTypes.string,
isDisplayed: PropTypes.bool,
nodeToFocus: PropTypes.number,
onClick: PropTypes.func,
tabIndex: PropTypes.number,
type: PropTypes.string,
};

View File

@ -2,15 +2,20 @@ import React from 'react';
import PropTypes from 'prop-types';
import { HeaderModalTitle } from 'strapi-helper-plugin';
import { AttributeIcon } from '@buffetjs/core';
import pluginId from '../../pluginId';
import { FormattedMessage } from 'react-intl';
import { upperFirst } from 'lodash';
import pluginId from '../../pluginId';
const ModalHeader = ({ headerId, name, type }) => {
const ModalHeader = ({ headerId, iconType, name }) => {
console.log({ iconType });
return (
<section>
<HeaderModalTitle style={{ textTransform: 'none' }}>
<AttributeIcon type={type} style={{ margin: 'auto 20px auto 0' }} />
<FormattedMessage id={`${pluginId}.${headerId}`} values={{ name }} />
<AttributeIcon type={iconType} style={{ margin: 'auto 20px auto 0' }} />
{headerId && (
<FormattedMessage id={`${pluginId}.${headerId}`} values={{ name }} />
)}
{!headerId && <span>{upperFirst(name)}</span>}
</HeaderModalTitle>
</section>
);
@ -18,14 +23,14 @@ const ModalHeader = ({ headerId, name, type }) => {
ModalHeader.defaultProps = {
headerId: '',
iconType: 'contentType',
name: '',
type: 'contentType',
};
ModalHeader.propTypes = {
headerId: PropTypes.string,
iconType: PropTypes.string,
name: PropTypes.string,
type: PropTypes.string,
};
export default ModalHeader;

View File

@ -18,7 +18,6 @@ const DataManagerProvider = ({ children }) => {
isLoading,
initialData,
modifiedData,
newSchema,
} = reducerState.toJS();
const contentTypeMatch = useRouteMatch(
@ -90,7 +89,6 @@ const DataManagerProvider = ({ children }) => {
createSchema,
initialData,
modifiedData,
newSchema,
setModifiedData,
}}
>

View File

@ -6,16 +6,6 @@ const initialState = fromJS({
initialData: {},
modifiedData: {},
isLoading: true,
newSchema: {
schemaType: '',
schema: {},
uid: '',
},
newSchemaClone: {
schemaType: '',
schema: {},
uid: '',
},
});
const reducer = (state, action) => {
@ -25,12 +15,21 @@ const reducer = (state, action) => {
.update('components', () => fromJS(action.components))
.update('contentTypes', () => fromJS(action.contentTypes))
.update('isLoading', () => false);
case 'CREATE_SCHEMA':
console.log({ action });
return state
.updateIn(['newSchema', 'schema'], () => fromJS(action.data))
.updateIn(['newSchema', 'uid'], () => fromJS(action.uid))
.updateIn(['newSchema', 'schemaType'], () => fromJS(action.schemaType));
case 'CREATE_SCHEMA': {
const newSchema = {
uid: action.uid,
isTemporary: true,
schema: {
...action.data,
attributes: {},
},
};
const key =
action.schemaType === 'contentType' ? 'contentTypes' : 'components';
return state.updateIn([key, action.uid], () => fromJS(newSchema));
}
case 'SET_MODIFIED_DATA':
return state
.update('initialData', () => OrderedMap(action.schemaToSet))

View File

@ -1,4 +1,4 @@
import React, { useEffect, useReducer, useState } from 'react';
import React, { useEffect, useReducer, useRef, useState } from 'react';
// import PropTypes from 'prop-types';
import {
ButtonModal,
@ -18,15 +18,17 @@ import { get, isEmpty, upperFirst } from 'lodash';
import pluginId from '../../pluginId';
import useQuery from '../../hooks/useQuery';
import useDataManager from '../../hooks/useDataManager';
import AttributeOption from '../../components/AttributeOption';
import ModalHeader from '../../components/ModalHeader';
import HeaderModalNavContainer from '../../components/HeaderModalNavContainer';
import HeaderNavLink from '../../components/HeaderNavLink';
import getTrad from '../../utils/getTrad';
import getAttributes from './utils/attributes';
import forms from './utils/forms';
import { createUid } from './utils/createUid';
import init from './init';
import reducer, { initialState } from './reducer';
const getTrad = id => `${pluginId}.${id}`;
const NAVLINKS = [{ id: 'base' }, { id: 'advanced' }];
const FormModal = () => {
@ -34,40 +36,77 @@ const FormModal = () => {
actionType: null,
modalType: null,
settingType: null,
// uid: null,
for: null,
target: null,
attributeType: null,
};
const [state, setState] = useState(initialStateData);
const [reducerState, dispatch] = useReducer(reducer, initialState, init);
const { push } = useHistory();
const { search } = useLocation();
const { formatMessage } = useGlobalContext();
const isOpen = !isEmpty(search);
const query = useQuery();
const attributeOptionRef = useRef();
const { contentTypes, createSchema, initialData } = useDataManager();
const { formErrors, modifiedData } = reducerState.toJS();
useEffect(() => {
if (isOpen) {
if (!isEmpty(search)) {
setState({
actionType: query.get('actionType'),
modalType: query.get('modalType'),
settingType: query.get('settingType'),
// uid: query.get('uid'),
for: query.get('for'),
target: query.get('target'),
attributeType: query.get('attributeType'),
});
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isOpen]);
}, [search]);
const displayedAttributes = getAttributes(state.for);
const form = get(forms, [state.modalType, 'form', state.settingType], () => ({
items: [],
}));
const iconType = ['components', 'contentType'].includes(state.modalType)
? state.modalType
: state.for;
const isCreatingCT = state.modalType === 'contentType';
const isCreating = state.actionType === 'create';
const headerId = isCreating
const isOpen = !isEmpty(search);
const isPickingAttribute = state.modalType === 'chooseAttribute';
const name = get(initialData, ['schema', 'name'], '');
const uid = createUid(modifiedData.name || '');
let headerId = isCreating
? `modalForm.${state.modalType}.header-create`
: 'modalForm.header-edit';
const name = get(initialData, ['schema', 'name'], '');
if (!['contentType', 'component'].includes(state.modalType)) {
headerId = null;
}
const modalBodyStyle = isPickingAttribute
? { paddingTop: '0.5rem', paddingBottom: '3rem' }
: {};
const getModalTitleSubHeader = () => {
switch (state.modalType) {
case 'chooseAttribute':
return getTrad(`modalForm.sub-header.chooseAttribute.${state.for}`);
default:
return getTrad('configurations');
}
};
const getNextSearch = nextTab => {
const newSearch = Object.keys(state).reduce((acc, current) => {
const newSearch = Object.keys(state).reduce((acc, current, index) => {
if (current !== 'settingType') {
acc = `${acc}&${current}=${state[current]}`;
acc = `${acc}${index === 0 ? '' : '&'}${current}=${state[current]}`;
} else {
acc = `${acc}&${current}=${nextTab}`;
acc = `${acc}${index === 0 ? '' : '&'}${current}=${nextTab}`;
}
return acc;
@ -87,16 +126,21 @@ const FormModal = () => {
e.preventDefault();
try {
const schema = forms.contentType.schema(Object.keys(contentTypes));
const schema = forms[state.modalType].schema(Object.keys(contentTypes));
await schema.validate(modifiedData, { abortEarly: false });
createSchema(modifiedData, state.modalType, createUid(modifiedData.name));
handleToggle();
// push({ p})
createSchema(modifiedData, state.modalType, uid);
const nextSlug = isCreatingCT ? 'content-types' : 'component-categories';
push({
pathname: `/plugins/${pluginId}/${nextSlug}/${uid}`,
search: `modalType=chooseAttribute&for=${state.modalType}&target=${modifiedData.name}`,
});
dispatch({
type: 'RESET_PROPS',
});
} catch (err) {
const errors = getYupInnerErrors(err);
// TODO
console.log({ errors });
dispatch({
type: 'SET_ERRORS',
errors,
@ -112,104 +156,161 @@ const FormModal = () => {
type: 'RESET_PROPS',
});
};
const form = get(forms, [state.modalType, 'form', state.settingType], () => ({
items: [],
}));
const onOpened = () => {
if (state.modalType === 'chooseAttribute') {
attributeOptionRef.current.focus();
}
};
return (
<Modal isOpen={isOpen} onClosed={onClosed} onToggle={handleToggle}>
<Modal
isOpen={isOpen}
onOpened={onOpened}
onClosed={onClosed}
onToggle={handleToggle}
>
<HeaderModal>
<ModalHeader
name={name}
name={state.target || name}
headerId={headerId}
type={state.modalType || 'contentType'}
iconType={iconType || 'contentType'}
/>
<section>
<HeaderModalTitle>
<FormattedMessage id={getTrad('configurations')}>
<FormattedMessage id={getModalTitleSubHeader()}>
{msg => <span>{upperFirst(msg)}</span>}
</FormattedMessage>
<div className="settings-tabs">
<HeaderModalNavContainer>
{NAVLINKS.map((link, index) => {
return (
<HeaderNavLink
isActive={state.settingType === link.id}
key={link.id}
{...link}
onClick={() => {
setState(prev => ({ ...prev, settingType: link.id }));
push({ search: getNextSearch(link.id) });
}}
nextTab={index === NAVLINKS.length - 1 ? 0 : index + 1}
/>
);
})}
</HeaderModalNavContainer>
</div>
<hr />
{!isPickingAttribute && (
<>
<div className="settings-tabs">
<HeaderModalNavContainer>
{NAVLINKS.map((link, index) => {
return (
<HeaderNavLink
isActive={state.settingType === link.id}
key={link.id}
{...link}
onClick={() => {
setState(prev => ({
...prev,
settingType: link.id,
}));
push({ search: getNextSearch(link.id) });
}}
nextTab={
index === NAVLINKS.length - 1 ? 0 : index + 1
}
/>
);
})}
</HeaderModalNavContainer>
</div>
<hr />
</>
)}
</HeaderModalTitle>
</section>
</HeaderModal>
<form onSubmit={handleSubmit}>
<ModalForm>
<ModalBody>
<ModalBody style={modalBodyStyle}>
<div className="container-fluid">
{form(modifiedData).items.map((row, index) => {
return (
<div className="row" key={index}>
{row.map(input => {
const errorId = get(
formErrors,
[...input.name.split('.'), 'id'],
null
);
return (
<div
className={`col-${input.size || 6}`}
key={input.name}
>
<Inputs
value={get(modifiedData, input.name, '')}
{...input}
error={
isEmpty(errorId)
? null
: formatMessage({ id: errorId })
}
onChange={handleChange}
onBlur={() => {}}
description={
get(input, 'description.id', null)
? formatMessage(input.description)
: input.description
}
label={
get(input, 'label.id', null)
? formatMessage(input.label)
: input.label
}
{isPickingAttribute
? displayedAttributes.map((row, i) => {
return (
<div key={i} className="row">
{i === 1 && (
<hr
style={{
width: 'calc(100% - 30px)',
marginBottom: 7,
}}
/>
)}
{row.map((attr, index) => {
const tabIndex =
i === 0
? index
: displayedAttributes[0].length + index;
return (
<AttributeOption
key={attr}
tabIndex={tabIndex}
isDisplayed
onClick={() => {}}
ref={
i === 0 && index === 0
? attributeOptionRef
: null
}
type={attr}
/>
);
})}
</div>
);
})
: form(modifiedData, state.attributeType).items.map(
(row, index) => {
return (
<div className="row" key={index}>
{row.map(input => {
const errorId = get(
formErrors,
[...input.name.split('.'), 'id'],
null
);
return (
<div
className={`col-${input.size || 6}`}
key={input.name}
>
<Inputs
value={get(modifiedData, input.name, '')}
{...input}
error={
isEmpty(errorId)
? null
: formatMessage({ id: errorId })
}
onChange={handleChange}
onBlur={() => {}}
description={
get(input, 'description.id', null)
? formatMessage(input.description)
: input.description
}
label={
get(input, 'label.id', null)
? formatMessage(input.label)
: input.label
}
/>
</div>
);
})}
</div>
);
})}
</div>
);
})}
}
)}
</div>
</ModalBody>
</ModalForm>
<ModalFooter>
<section>
<ButtonModal
message="components.popUpWarning.button.cancel"
onClick={handleToggle}
isSecondary
/>
<ButtonModal message="form.button.done" type="submit" />
</section>
</ModalFooter>
{!isPickingAttribute && (
<ModalFooter>
<section>
<ButtonModal
message="components.popUpWarning.button.cancel"
onClick={handleToggle}
isSecondary
/>
<ButtonModal message="form.button.done" type="submit" />
</section>
</ModalFooter>
)}
</form>
</Modal>
);

View File

@ -0,0 +1,23 @@
const getAttributes = () => {
const defaultAttributes = [
[
'text',
'email',
'richtext',
'password',
'number',
'enumeration',
'date',
'media',
'boolean',
'json',
// 'uid',
'relation',
],
['component', 'dynamiczone'],
];
return defaultAttributes;
};
export default getAttributes;

View File

@ -1,6 +1,7 @@
import * as yup from 'yup';
import { translatedErrors as errorsTrads } from 'strapi-helper-plugin';
import pluginId from '../../../pluginId';
import getTrad from '../../../utils/getTrad';
import { createUid, nameToSlug } from './createUid';
yup.addMethod(yup.mixed, 'defined', function() {
@ -22,6 +23,57 @@ yup.addMethod(yup.string, 'unique', function(message, allReadyTakenValues) {
});
const forms = {
attribute: {
schema() {
return yup.object();
},
form: {
advanced() {
return {
items: [[]],
};
},
base(data, type) {
const items = [
[
{
autoFocus: true,
name: 'name',
type: 'text',
label: {
id: getTrad('modalForm.attribute.form.base.name'),
},
description: {
id: getTrad('modalForm.attribute.form.base.name.description'),
},
validations: {
required: true,
},
},
],
];
if (type === 'text') {
items[0].push({
label: {
id: 'content-type-builder.form.attribute.item.number.type',
},
name: 'type',
type: 'select',
value: 'short text',
options: ['short text', 'long text'],
validations: {
required: true,
},
});
}
return {
items,
};
},
},
},
contentType: {
schema(allReadyTakenValues) {
return yup.object().shape({

View File

@ -13,14 +13,19 @@ import CustomLink from '../../components/CustomLink';
import useDataManager from '../../hooks/useDataManager';
import Wrapper from './Wrapper';
// const displayNotificationCTNotSaved = () => {
// strapi.notification.info(
// `${pluginId}.notification.info.contentType.creating.notSaved`
// );
// };
const displayNotificationCTNotSaved = () => {
strapi.notification.info(
`${pluginId}.notification.info.contentType.creating.notSaved`
);
};
function LeftMenu() {
const { components, contentTypes, newSchema } = useDataManager();
const {
components,
contentTypes,
// initialData,
// modifiedData,
} = useDataManager();
const { currentEnvironment } = useGlobalContext();
const { push } = useHistory();
const isProduction = currentEnvironment === 'production';
@ -40,14 +45,24 @@ function LeftMenu() {
})),
obj => obj.title
);
const tempSchemaCT =
newSchema.schemaType === 'contentType'
? {
name: newSchema.uid,
title: newSchema.schema.name,
to: `/plugins/${pluginId}/content-types/${newSchema.uid}`,
}
: null;
const canOpenModalCreateCTorComponent = () => {
return (
!Object.keys(contentTypes).some(
ct => contentTypes[ct].isTemporary === true
) &&
!Object.keys(components).some(
component => components[component].isTemporary === true
)
);
};
const handleClickOpenModal = type => {
if (canOpenModalCreateCTorComponent()) {
push({ search: `modalType=${type}&actionType=create&settingType=base` });
} else {
displayNotificationCTNotSaved();
}
};
const data = [
{
name: 'models',
@ -61,10 +76,7 @@ function LeftMenu() {
disabled: isProduction,
id: `${pluginId}.button.model.create`,
onClick: () => {
push({
search:
'modalType=contentType&actionType=create&settingType=base',
});
handleClickOpenModal('contentType');
},
},
},
@ -75,7 +87,6 @@ function LeftMenu() {
title: contentTypes[uid].schema.name,
to: `/plugins/${pluginId}/content-types/${uid}`,
}))
.concat(tempSchemaCT)
.filter(obj => obj !== null),
obj => obj.title
),
@ -92,9 +103,7 @@ function LeftMenu() {
disabled: isProduction,
id: `${pluginId}.button.component.create`,
onClick: () => {
push({
search: 'modalType=component&actionType=create&settingType=base',
});
handleClickOpenModal('component');
},
},
},

View File

@ -2,23 +2,7 @@
"model": "Content Type",
"group": "Group",
"attribute.WYSIWYG": "Text (WYSIWYG)",
"attribute.boolean": "Boolean",
"attribute.date": "Date",
"attribute.decimal": "Decimal",
"attribute.email": "Email",
"attribute.enumeration": "Enumeration",
"attribute.float": "Float",
"attribute.group": "Group",
"attribute.integer": "Integer",
"attribute.biginteger": "Big Integer",
"attribute.json": "JSON",
"attribute.media": "Media",
"attribute.password": "Password",
"attribute.relation": "Relation",
"attribute.richtext": "Rich text",
"attribute.string": "String",
"attribute.text": "Text",
"attribute.uuid": "Uuid",
"button.attributes.add": "Add New Field",
"button.attributes.add.another": "Add Another Field",
"button.contentType.add": "Add a Content Type",
@ -146,34 +130,7 @@
"notification.success.message.contentType.edit": "Your Content Type has been updated",
"plugin.description.long": "Modelize the data structure of your API. Create new fields and relations in just a minute. The files are automatically created and updated in your project.",
"plugin.description.short": "Modelize the data structure of your API.",
"popUpForm.attributes.boolean.description": "Yes or no, 1 or 0, true or false",
"popUpForm.attributes.boolean.name": "Boolean",
"popUpForm.attributes.date.description": "Event date, opening hours",
"popUpForm.attributes.date.name": "Date",
"popUpForm.attributes.email.description": "User's email...",
"popUpForm.attributes.email.name": "Email",
"popUpForm.attributes.enumeration.description": "List of choices",
"popUpForm.attributes.enumeration.name": "Enumeration",
"popUpForm.attributes.group.description": "Refers to a Group",
"popUpForm.attributes.group.name": "Group",
"popUpForm.attributes.json.description": "Data in JSON format",
"popUpForm.attributes.json.name": "JSON",
"popUpForm.attributes.media.description": "Images, videos, PDFs and other files",
"popUpForm.attributes.media.name": "Media",
"popUpForm.attributes.number.description": "Everything that is number",
"popUpForm.attributes.number.name": "Number",
"popUpForm.attributes.password.description": "User password...",
"popUpForm.attributes.password.name": "Password",
"popUpForm.attributes.relation.description": "Refers to a Content Type",
"popUpForm.attributes.relation.name": "Relation",
"popUpForm.attributes.richtext.description": "Formatting and creating text",
"popUpForm.attributes.richtext.name": "Rich text",
"popUpForm.attributes.string.description": "Titles, names, paragraphs, list of names",
"popUpForm.attributes.string.name": "String",
"popUpForm.attributes.text.description": "Descriptions, text paragraphs, articles",
"popUpForm.attributes.text.name": "Text",
"popUpForm.attributes.uuid.description": "Unique identifier",
"popUpForm.attributes.uuid.name": "Uuid",
"popUpForm.choose.attributes.header.title": "Add New Field",
"popUpForm.choose.attributes.header.subtitle.model": "Select a field for your content type",
"popUpForm.choose.attributes.header.subtitle.group": "Select a field for your group",
@ -221,13 +178,45 @@
"table.relations.title.singular": "including {number} relationship",
"prompt.content.unsaved": "Are you sure you want to leave this content type? All your modifications will be lost.",
"modalForm.contentType.header-create": "Create a content type",
"modalForm.header-edit": "Edit {name}",
"modalForm.component.header-create": "Create a component",
"attribute.boolean": "Boolean",
"attribute.boolean.description": "Yes or no, 1 or 0, true or false",
"attribute.component": "Component",
"attribute.component.description": "Set of fields that you can repeat or reuse",
"attribute.date": "Date",
"attribute.date.description": "Event date, opening hours",
"attribute.dynamiczone": "Dynamic zone",
"attribute.dynamiczone.description": "Dynamically pick component when editing content",
"attribute.email": "Email",
"attribute.email.description": "User's email...",
"attribute.enumeration": "Enumeration",
"attribute.enumeration.description": "List of choices",
"attribute.json": "JSON",
"attribute.json.description": "Data in JSON format",
"attribute.media": "Media",
"attribute.media.description": "Images, videos, PDFs and other files",
"attribute.number": "Number",
"attribute.number.description": "Everything that is number",
"attribute.password": "Password",
"attribute.password.description": "User password...",
"attribute.relation": "Relation",
"attribute.relation.description": "Refers to a Content Type",
"attribute.richtext": "Rich text",
"attribute.richtext.description": "Formatting and creating text",
"attribute.text": "Text",
"attribute.text.description": "Small or long text like title or description",
"attribute.uid": "Uuid",
"attribute.uid.description": "Unique identifier",
"configurations": "configurations",
"contentType.displayName.label": "Display name",
"contentType.collectionName.description": "Useful when the name of your Content Type and your table name differ",
"contentType.collectionName.label": "Collection name",
"contentType.UID.description": "The UID is used to generate the API routes and databases tables/collections"
"contentType.UID.description": "The UID is used to generate the API routes and databases tables/collections",
"modalForm.component.header-create": "Create a component",
"modalForm.contentType.header-create": "Create a content type",
"modalForm.attribute.form.base.name": "Name",
"modalForm.attribute.form.base.name.description": "No space is allowed for the name of the attribute",
"modalForm.header-edit": "Edit {name}",
"modalForm.sub-header.chooseAttribute.component": "Select a field for your component",
"modalForm.sub-header.chooseAttribute.contentType": "Select a field for your content type"
}

View File

@ -0,0 +1,5 @@
import pluginId from '../pluginId';
const getTrad = id => `${pluginId}.${id}`;
export default getTrad;