Merge pull request #5322 from strapi/single-types/uid-ctm

Add UID in CTM and fix Single types alignments
This commit is contained in:
cyril lopez 2020-02-26 17:40:00 +01:00 committed by GitHub
commit 0bb347f79b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
31 changed files with 576 additions and 218 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 MiB

After

Width:  |  Height:  |  Size: 352 KiB

View File

@ -26,7 +26,7 @@ const Wrapper = styled.div`
font-weight: 400;
letter-spacing: 0.05rem;
vertical-align: middle;
color: ${props => props.theme.main.colors.strapi['gray-light']};
color: ${({ theme }) => theme.main.colors.strapi.grayLight};
}
`;

View File

@ -2,7 +2,7 @@ import styled from 'styled-components';
const A = styled.a`
position: relative;
padding-top: 0.8rem;
padding-top: 0.7rem;
padding-bottom: 0.2rem;
padding-left: 1.6rem;
min-height: 3.6rem;

View File

@ -5,7 +5,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
const FaIcon = styled(({ small, ...props }) => <FontAwesomeIcon {...props} />)`
position: absolute;
top: calc(50% - 0.9rem + 0.5rem);
top: calc(50% - 0.9rem + 0.3rem);
left: 1.6rem;
margin-right: 1.2rem;
font-size: ${props => (props.small ? '1rem' : '1.4rem')};
@ -14,9 +14,7 @@ const FaIcon = styled(({ small, ...props }) => <FontAwesomeIcon {...props} />)`
text-align: center;
`;
const LeftMenuIcon = ({ icon }) => (
<FaIcon small={icon === 'circle'} icon={icon} />
);
const LeftMenuIcon = ({ icon }) => <FaIcon small={icon === 'circle'} icon={icon} />;
LeftMenuIcon.propTypes = {
icon: PropTypes.string,

View File

@ -2,9 +2,9 @@ import styled from 'styled-components';
const Search = styled.input`
width: 100%;
padding: 0 21px;
padding: 0 15px;
outline: 0;
font-size: 1.3rem;
font-size: 1.2rem;
color: ${props => props.theme.main.colors.white};
`;

View File

@ -5,13 +5,14 @@ const Title = styled.div`
justify-content: space-between;
padding-left: 2rem;
padding-right: 1.6rem;
padding-top: 0.7rem;
padding-top: 1rem;
margin-bottom: 0.8rem;
color: ${props => props.theme.main.colors.leftMenu['title-color']};
text-transform: uppercase;
font-size: 1.1rem;
font-size: 1.2rem;
letter-spacing: 0.1rem;
font-weight: 800;
max-height: 28px;
`;
Title.defaultProps = {

View File

@ -6,7 +6,7 @@ const EmptyLinksList = styled.div`
padding-right: 1.6rem;
font-weight: 300;
min-height: 3.6rem;
padding-top: 0.2rem;
padding-top: 0.6rem;
`;
EmptyLinksList.defaultProps = {

View File

@ -10,12 +10,7 @@ import { FormattedMessage } from 'react-intl';
import { withRouter } from 'react-router-dom';
import { get } from 'lodash';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import {
ButtonDropdown,
DropdownItem,
DropdownMenu,
DropdownToggle,
} from 'reactstrap';
import { ButtonDropdown, DropdownItem, DropdownMenu, DropdownToggle } from 'reactstrap';
import { auth } from 'strapi-helper-plugin';
import Wrapper from './components';
@ -26,14 +21,14 @@ const Logout = ({ history: { push } }) => {
const id = get(auth.getUserInfo(), 'id');
push({
pathname: `/plugins/content-manager/strapi::administrator/${id}`,
pathname: `/plugins/content-manager/collectionType/strapi::administrator/${id}`,
search:
'?redirectUrl=/plugins/content-manager/strapi::administrator/&_page=0&_limit=0&_sort=id',
'?redirectUrl=/plugins/content-manager/collectionType/strapi::administrator/&_page=0&_limit=0&_sort=id',
});
};
const handleGoToAdministrator = () => {
push({
pathname: '/plugins/content-manager/strapi::administrator',
pathname: '/plugins/content-manager/collectionType/strapi::administrator',
});
};
const handleLogout = () => {

View File

@ -10,8 +10,9 @@ const colors = {
pink: '#ff5b77',
purple: '#613d7c',
gray: '#464a4c',
border: '#E3E9F3',
'gray-dark': '#292b2c',
'gray-light': '#636c72',
grayLight: '#636c72',
'gray-lighter': '#eceeef',
'gray-lightest': '#f7f7f9',

View File

@ -12,9 +12,7 @@ export { default as Button } from './components/Button';
export { default as ButtonModal } from './components/ButtonModal';
export { default as CircleButton } from './components/CircleButton';
export { default as ContainerFluid } from './components/ContainerFluid';
export {
default as EmptyAttributesBlock,
} from './components/EmptyAttributesBlock';
export { default as EmptyAttributesBlock } from './components/EmptyAttributesBlock';
export { default as ErrorBoundary } from './components/ErrorBoundary';
export { default as ExtendComponent } from './components/ExtendComponent';
export { default as GlobalPagination } from './components/GlobalPagination';
@ -24,54 +22,32 @@ export { default as HeaderModalTitle } from './components/HeaderModalTitle';
export { default as IcoContainer } from './components/IcoContainer';
export { default as InputAddon } from './components/InputAddon';
export {
default as InputAddonWithErrors,
} from './components/InputAddonWithErrors';
export { default as InputAddonWithErrors } from './components/InputAddonWithErrors';
export { default as InputCheckbox } from './components/InputCheckbox';
export {
default as InputCheckboxWithErrors,
} from './components/InputCheckboxWithErrors';
export { default as InputCheckboxWithErrors } from './components/InputCheckboxWithErrors';
export { default as InputDate } from './components/InputDate';
export {
default as InputDateWithErrors,
} from './components/InputDateWithErrors';
export { default as InputDateWithErrors } from './components/InputDateWithErrors';
export { default as InputDescription } from './components/InputDescription';
export { default as InputEmail } from './components/InputEmail';
export {
default as InputEmailWithErrors,
} from './components/InputEmailWithErrors';
export { default as InputEmailWithErrors } from './components/InputEmailWithErrors';
export { default as InputErrors } from './components/InputErrors';
export { default as InputFile } from './components/InputFile';
export { default as InputNumber } from './components/InputNumber';
export {
default as InputNumberWithErrors,
} from './components/InputNumberWithErrors';
export { default as InputNumberWithErrors } from './components/InputNumberWithErrors';
export { default as InputPassword } from './components/InputPassword';
export {
default as InputPasswordWithErrors,
} from './components/InputPasswordWithErrors';
export { default as InputPasswordWithErrors } from './components/InputPasswordWithErrors';
export { default as InputSearch } from './components/InputSearch';
export {
default as InputSearchWithErrors,
} from './components/InputSearchWithErrors';
export { default as InputSearchWithErrors } from './components/InputSearchWithErrors';
export { default as InputSelect } from './components/InputSelect';
export {
default as InputSelectWithErrors,
} from './components/InputSelectWithErrors';
export { default as InputSelectWithErrors } from './components/InputSelectWithErrors';
export { default as InputsIndex } from './components/InputsIndex';
export { default as InputSpacer } from './components/InputSpacer';
export { default as InputText } from './components/InputText';
export {
default as InputTextWithErrors,
} from './components/InputTextWithErrors';
export { default as InputTextWithErrors } from './components/InputTextWithErrors';
export { default as InputTextArea } from './components/InputTextArea';
export {
default as InputTextAreaWithErrors,
} from './components/InputTextAreaWithErrors';
export { default as InputTextAreaWithErrors } from './components/InputTextAreaWithErrors';
export { default as InputToggle } from './components/InputToggle';
export {
default as InputToggleWithErrors,
} from './components/InputToggleWithErrors';
export { default as InputToggleWithErrors } from './components/InputToggleWithErrors';
export { default as Label } from './components/Label';
export { default as LeftMenu } from './components/LeftMenu';
@ -86,9 +62,7 @@ export { default as ListTitle } from './components/ListTitle';
export { default as LoadingBar } from './components/LoadingBar';
export { default as LoadingIndicator } from './components/LoadingIndicator';
export {
default as LoadingIndicatorPage,
} from './components/LoadingIndicatorPage';
export { default as LoadingIndicatorPage } from './components/LoadingIndicatorPage';
export { default as Modal } from './components/Modal';
export { default as ModalBody } from './components/BodyModal';
@ -105,11 +79,7 @@ export { default as TrashButton } from './components/TrashButton';
export { default as ViewContainer } from './components/ViewContainer';
// Contexts
export {
GlobalContext,
GlobalContextProvider,
useGlobalContext,
} from './contexts/GlobalContext';
export { GlobalContext, GlobalContextProvider, useGlobalContext } from './contexts/GlobalContext';
// Utils
export { default as auth } from './utils/auth';

View File

@ -58,12 +58,7 @@ const CustomTable = ({ data, headers, isBulkable }) => {
handleGoTo(row.id);
}}
>
<Row
isBulkable={isBulkable}
headers={headers}
row={row}
goTo={handleGoTo}
/>
<Row isBulkable={isBulkable} headers={headers} row={row} goTo={handleGoTo} />
</TableRow>
);
})
@ -73,9 +68,7 @@ const CustomTable = ({ data, headers, isBulkable }) => {
<Table className="table">
<TableHeader headers={headers} isBulkable={isBulkable} />
<tbody>
{entriesToDelete.length > 0 && (
<ActionCollapse colSpan={colSpanLength} />
)}
{entriesToDelete.length > 0 && <ActionCollapse colSpan={colSpanLength} />}
{content}
</tbody>
</Table>
@ -91,13 +84,6 @@ CustomTable.defaultProps = {
CustomTable.propTypes = {
data: PropTypes.array,
headers: PropTypes.array,
history: PropTypes.shape({
location: PropTypes.shape({
pathname: PropTypes.string,
search: PropTypes.string,
}),
push: PropTypes.func.isRequired,
}).isRequired,
isBulkable: PropTypes.bool,
};

View File

@ -0,0 +1,16 @@
import styled from 'styled-components';
import { InputText } from '@buffetjs/core';
import { colors } from '@buffetjs/styles';
const InputUID = styled(InputText)`
width: 100%;
${({ error }) =>
error &&
`
> input {
border-color: ${colors.darkOrange};
}
`}
`;
export default InputUID;

View File

@ -0,0 +1,21 @@
import styled from 'styled-components';
const Option = styled.div`
&:hover {
background-color: #e4f0fc;
.right-label {
display: block;
}
}
cursor: pointer;
line-height: 2.6rem;
font-size: 1.5rem;
padding: 5px;
display: flex;
justify-content: space-between;
.right-label {
display: none;
}
`;
export default Option;

View File

@ -0,0 +1,12 @@
import styled from 'styled-components';
const OptionsTitle = styled.div`
line-height: 2.1rem;
font-size: 1.2rem;
padding: 5px;
text-transform: uppercase;
color: ${({ theme }) => theme.main.colors.grayLight};
border-bottom: 1px solid ${({ theme }) => theme.main.colors.border};
`;
export default OptionsTitle;

View File

@ -0,0 +1,9 @@
import styled from 'styled-components';
const RightOptionLabel = styled.div`
color: ${({ theme }) => theme.main.colors.strapi.blue};
text-transform: uppercase;
font-weight: bold;
`;
export default RightOptionLabel;

View File

@ -0,0 +1,35 @@
import React from 'react';
import PropTypes from 'prop-types';
import { FormattedMessage } from 'react-intl';
import getTrad from '../../../utils/getTrad'
import OptionsWrapper from './wrapper';
import Option from './Option';
import OptionsTitle from './OptionsTitle';
import RightOptionLabel from './RightOptionLabel';
const Options = ({ options, title }) => (
<OptionsWrapper>
{title && <OptionsTitle>{title}</OptionsTitle>}
{options.map(option => (
<Option key={option.id} onClick={option.onClick}>
<div>{option.label}</div>
<FormattedMessage id={getTrad('components.uid.apply')}>
{msg => <RightOptionLabel className="right-label">{msg}</RightOptionLabel>}
</FormattedMessage>
</Option>
))}
</OptionsWrapper>
);
Options.propTypes = {
options: PropTypes.array.isRequired,
title: PropTypes.string,
};
Options.defaultProps = {
title: null,
};
export default Options;

View File

@ -0,0 +1,13 @@
import styled from 'styled-components';
const wrapper = styled.div`
position: absolute;
width: 100%;
margin-top: 3px;
border: 1px solid ${props => props.theme.main.colors.border};
border-radius: 2px;
background-color: white;
z-index: 10;
`;
export default wrapper;

View File

@ -0,0 +1,18 @@
import styled from 'styled-components';
const RegenerateButton = styled.div`
display: flex;
align-items: center;
justify-content: center;
width: 40px;
height: 32px;
background-color: #fafafb;
z-index: 10;
&:hover {
cursor: pointer;
background-color: #aed4fb;
}
`;
export default RegenerateButton;

View File

@ -0,0 +1,14 @@
import styled from 'styled-components';
const RightContent = styled.div`
display: flex;
z-index: 10;
background-color: ${({ theme }) => theme.main.colors.white};
align-items: center;
line-height: 32px;
right: 1px;
top: 1px;
position: absolute;
`;
export default RightContent;

View File

@ -0,0 +1,69 @@
import PropTypes from 'prop-types';
import React from 'react';
import { Success, Remove } from '@buffetjs/icons';
import styled from 'styled-components';
import { useGlobalContext } from 'strapi-helper-plugin';
import pluginId from '../../pluginId';
import getTrad from '../../utils/getTrad';
// Note you don't need to create a specific file for this one
// as it will soon be replaced by the Text one so you can leave it in this file.
const RightContentLabel = styled.div`
padding: 0 5px;
text-transform: capitalize;
color: ${({ theme, color }) => theme.main.colors[color]};
`;
const RightLabel = ({ label, availability }) => {
const { formatMessage } = useGlobalContext();
if (label) {
return (
<RightContentLabel color="blue">
{formatMessage({
id: getTrad('components.uid.regenerate'),
})}
</RightContentLabel>
);
}
if (availability !== null) {
// This should be more generic in the futur.
return availability.isAvailable ? (
<>
<Success fill="#27b70f" width="20px" height="20px" />
<RightContentLabel color="green">
{formatMessage({
id: `${pluginId}.components.uid.available`,
})}
</RightContentLabel>
</>
) : (
<>
<Remove fill="#ff203c" width="12px" height="12px" />
<RightContentLabel color="red">
{formatMessage({
id: getTrad('components.uid.unavailable'),
})}
</RightContentLabel>
</>
);
}
return null;
};
RightLabel.propTypes = {
label: PropTypes.string,
availability: PropTypes.shape({
isAvailable: PropTypes.bool,
}),
};
RightLabel.defaultProps = {
label: null,
availability: null,
};
export default RightLabel;

View File

@ -0,0 +1,265 @@
import React, { useEffect, useState, useRef } from 'react';
import PropTypes from 'prop-types';
import { Sync } from '@buffetjs/icons';
import { ErrorMessage as BaseErrorMessage } from '@buffetjs/styles';
import { Label, Error } from '@buffetjs/core';
import { useDebounce, useClickAwayListener } from '@buffetjs/hooks';
import styled from 'styled-components';
import { request, LoadingIndicator } from 'strapi-helper-plugin';
import { FormattedMessage } from 'react-intl';
import { isEmpty } from 'lodash';
import pluginId from '../../pluginId';
import getRequestUrl from '../../utils/getRequestUrl';
import useDataManager from '../../hooks/useDataManager';
import RightLabel from './RightLabel';
import Options from './Options';
import RegenerateButton from './RegenerateButton';
import RightContent from './RightContent';
import Input from './InputUID';
// There is no need to create additional files for those little components.
const Wrapper = styled.div`
position: relative;
padding-bottom: 23px;
`;
const InputContainer = styled.div`
position: relative;
`;
const ErrorMessage = styled(BaseErrorMessage)`
padding-top: 10px;
`;
const Name = styled(Label)`
display: block;
text-transform: capitalize;
margin-bottom: 1rem;
`;
// This component should be in buffetjs. It will be used in the media lib.
// This component will be the strapi custom dropdown component.
// TODO : Make this component generic -> InputDropdown.
// TODO : Use the Compounds components pattern
// https://blog.bitsrc.io/understanding-compound-components-in-react-23c4b84535b5
const InputUID = ({
attribute,
contentTypeUID,
error: inputError,
name,
onChange,
required,
validations,
value,
}) => {
const { modifiedData, initialData } = useDataManager();
const [isLoading, setIsLoading] = useState(false);
const [availability, setAvailability] = useState(null);
const [isSuggestionOpen, setIsSuggestionOpen] = useState(true);
const [isCustomized, setIsCustomized] = useState(false);
const [label, setLabel] = useState();
const debouncedValue = useDebounce(value, 300);
const debouncedTargetFieldValue = useDebounce(modifiedData[attribute.targetField], 300);
const wrapperRef = useRef(null);
const generateUid = useRef();
const initialValue = initialData[name];
const isCreation = isEmpty(initialData);
generateUid.current = async () => {
setIsLoading(true);
const requestURL = getRequestUrl('explorer/uid/generate');
try {
const { data } = await request(requestURL, {
method: 'POST',
body: {
contentTypeUID,
field: name,
data: modifiedData,
},
});
onChange({ target: { name, value: data, type: 'text' } });
setIsLoading(false);
} catch (err) {
console.error({ err });
setIsLoading(false);
}
};
const checkAvailability = async () => {
setIsLoading(true);
const requestURL = getRequestUrl('explorer/uid/check-availability');
try {
const data = await request(requestURL, {
method: 'POST',
body: {
contentTypeUID,
field: name,
value: value || null,
},
});
setAvailability(data);
if (data.suggestion) {
setIsSuggestionOpen(true);
}
setIsLoading(false);
} catch (err) {
console.error({ err });
setIsLoading(false);
}
};
useEffect(() => {
if (!value && required) {
generateUid.current();
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
useEffect(() => {
if (debouncedValue && debouncedValue !== initialValue) {
checkAvailability();
}
if (!debouncedValue) {
setAvailability(null);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [debouncedValue, initialValue]);
useEffect(() => {
let timer;
if (availability && availability.isAvailable) {
timer = setTimeout(() => {
setAvailability(null);
}, 4000);
}
return () => {
if (timer) {
clearTimeout(timer);
}
};
}, [availability]);
useEffect(() => {
if (!isCustomized && isCreation && debouncedTargetFieldValue !== null) {
generateUid.current();
}
}, [debouncedTargetFieldValue, isCustomized, isCreation]);
useClickAwayListener(wrapperRef, () => setIsSuggestionOpen(false));
const handleFocus = () => {
if (availability && availability.suggestion) {
setIsSuggestionOpen(true);
}
};
const handleSuggestionClick = () => {
setIsSuggestionOpen(false);
onChange({ target: { name, value: availability.suggestion, type: 'text' } });
};
const handleGenerateMouseEnter = () => {
setLabel('regenerate');
};
const handleGenerateMouseLeave = () => {
setLabel(null);
};
const handleChange = (e, canCheck, dispatch) => {
if (!canCheck) {
dispatch({
type: 'SET_CHECK',
});
}
dispatch({
type: 'SET_ERROR',
error: null,
});
if (e.target.value && isCreation) {
setIsCustomized(true);
}
onChange(e);
};
return (
<Error name={name} inputError={inputError} type="text" validations={validations}>
{({ canCheck, onBlur, error, dispatch }) => {
const hasError = error && error !== null;
return (
<Wrapper ref={wrapperRef}>
<Name htmlFor={name}>{name}</Name>
<InputContainer>
<Input
error={hasError}
onFocus={handleFocus}
name={name}
onChange={e => handleChange(e, canCheck, dispatch)}
type="text"
onBlur={onBlur}
// eslint-disable-next-line no-irregular-whitespace
value={value || ''}
/>
<RightContent>
<RightLabel availability={availability} label={label} />
<RegenerateButton
onMouseEnter={handleGenerateMouseEnter}
onMouseLeave={handleGenerateMouseLeave}
onClick={generateUid.current}
>
{isLoading ? (
<LoadingIndicator />
) : (
<Sync fill={label ? '#007EFF' : '#B5B7BB'} width="15px" height="15px" />
)}
</RegenerateButton>
</RightContent>
{availability && availability.suggestion && isSuggestionOpen && (
<FormattedMessage id={`${pluginId}.components.uid.suggested`}>
{msg => (
<Options
title={msg}
options={[
{
id: 'suggestion',
label: availability.suggestion,
onClick: handleSuggestionClick,
},
]}
/>
)}
</FormattedMessage>
)}
</InputContainer>
{hasError && <ErrorMessage>{error}</ErrorMessage>}
</Wrapper>
);
}}
</Error>
);
};
InputUID.propTypes = {
attribute: PropTypes.object.isRequired,
contentTypeUID: PropTypes.string.isRequired,
error: PropTypes.string,
name: PropTypes.string.isRequired,
onChange: PropTypes.func.isRequired,
required: PropTypes.bool,
validations: PropTypes.object,
value: PropTypes.string,
};
InputUID.defaultProps = {
error: null,
required: false,
validations: {},
value: '',
};
export default InputUID;

View File

@ -9,6 +9,7 @@ import InputJSONWithErrors from '../InputJSONWithErrors';
import InputFileWithErrors from '../InputFileWithErrors';
import SelectWrapper from '../SelectWrapper';
import WysiwygWithErrors from '../WysiwygWithErrors';
import InputUID from '../InputUID';
const getInputType = (type = '') => {
switch (toLower(type)) {
@ -42,32 +43,19 @@ const getInputType = (type = '') => {
case 'WYSIWYG':
case 'richtext':
return 'wysiwyg';
case 'uid':
return 'uid';
default:
return 'text';
}
};
function Inputs({ autoFocus, keys, layout, name, onBlur }) {
const {
didCheckErrors,
formErrors,
modifiedData,
onChange,
} = useDataManager();
const attribute = useMemo(
() => get(layout, ['schema', 'attributes', name], {}),
[layout, name]
);
const metadatas = useMemo(
() => get(layout, ['metadatas', name, 'edit'], {}),
[layout, name]
);
const disabled = useMemo(() => !get(metadatas, 'editable', true), [
metadatas,
]);
const { didCheckErrors, formErrors, modifiedData, onChange } = useDataManager();
const attribute = useMemo(() => get(layout, ['schema', 'attributes', name], {}), [layout, name]);
const metadatas = useMemo(() => get(layout, ['metadatas', name, 'edit'], {}), [layout, name]);
const disabled = useMemo(() => !get(metadatas, 'editable', true), [metadatas]);
const type = useMemo(() => get(attribute, 'type', null), [attribute]);
const validations = omit(attribute, [
'type',
'model',
@ -83,13 +71,13 @@ function Inputs({ autoFocus, keys, layout, name, onBlur }) {
if (visible === false) {
return null;
}
const temporaryErrorIdUntilBuffetjsSupportsFormattedMessage =
'app.utils.defaultMessage';
const temporaryErrorIdUntilBuffetjsSupportsFormattedMessage = 'app.utils.defaultMessage';
const errorId = get(
formErrors,
[keys, 'id'],
temporaryErrorIdUntilBuffetjsSupportsFormattedMessage
);
const isRequired = get(validations, ['required'], false);
if (type === 'relation') {
return (
@ -131,12 +119,8 @@ function Inputs({ autoFocus, keys, layout, name, onBlur }) {
);
});
const isRequired = get(validations, ['required'], false);
const enumOptions = [
<FormattedMessage
id="components.InputSelect.option.placeholder"
key="__enum_option_null"
>
<FormattedMessage id="components.InputSelect.option.placeholder" key="__enum_option_null">
{msg => (
<option disabled={isRequired} hidden={isRequired} value="">
{msg}
@ -156,19 +140,21 @@ function Inputs({ autoFocus, keys, layout, name, onBlur }) {
didCheckErrors={didCheckErrors}
disabled={disabled}
error={
isEmpty(error) ||
errorId === temporaryErrorIdUntilBuffetjsSupportsFormattedMessage
isEmpty(error) || errorId === temporaryErrorIdUntilBuffetjsSupportsFormattedMessage
? null
: error
}
inputDescription={description}
description={description}
contentTypeUID={layout.uid}
customInputs={{
media: InputFileWithErrors,
json: InputJSONWithErrors,
wysiwyg: WysiwygWithErrors,
uid: InputUID,
}}
multiple={get(attribute, 'multiple', false)}
attribute={attribute}
name={keys}
onBlur={onBlur}
onChange={onChange}

View File

@ -1,10 +1,4 @@
import React, {
useCallback,
useEffect,
useMemo,
useReducer,
useState,
} from 'react';
import React, { useCallback, useEffect, useMemo, useReducer, useState } from 'react';
import PropTypes from 'prop-types';
import { useParams } from 'react-router-dom';
import { cloneDeep, get, set } from 'lodash';
@ -74,9 +68,9 @@ const EditSettingsView = ({
}, [modifiedData]);
const getForm = () =>
Object.keys(
get(modifiedData, ['metadatas', metaToEdit, 'edit'], {})
).filter(meta => meta !== 'visible');
Object.keys(get(modifiedData, ['metadatas', metaToEdit, 'edit'], {})).filter(
meta => meta !== 'visible'
);
const getRelationsLayout = useCallback(() => {
return get(modifiedData, ['layouts', 'editRelations'], []);
@ -94,10 +88,7 @@ const EditSettingsView = ({
const getEditRemainingFields = () => {
const attributes = getAttributes;
const metadatas = get(modifiedData, ['metadatas'], {});
const displayedFields = getEditLayout().reduce(
(acc, curr) => [...acc, ...curr.rowContent],
[]
);
const displayedFields = getEditLayout().reduce((acc, curr) => [...acc, ...curr.rowContent], []);
return Object.keys(attributes)
.filter(attr => get(attributes, [attr, 'type'], '') !== 'relation')
@ -114,11 +105,7 @@ const EditSettingsView = ({
}
const targetKey = formType === 'component' ? 'component' : 'targetModel';
const key = get(
modifiedData,
['schema', 'attributes', metaToEdit, targetKey],
''
);
const key = get(modifiedData, ['schema', 'attributes', metaToEdit, targetKey], '');
return get(componentsAndModelsMainPossibleMainFields, [key], []);
},
@ -129,13 +116,10 @@ const EditSettingsView = ({
useEffect(() => {
const getData = async () => {
try {
const { data } = await request(
getRequestUrl(`${type}/${slug || componentSlug}`),
{
method: 'GET',
signal,
}
);
const { data } = await request(getRequestUrl(`${type}/${slug || componentSlug}`), {
method: 'GET',
signal,
});
dispatch({
type: 'GET_DATA_SUCCEEDED',
@ -176,6 +160,7 @@ const EditSettingsView = ({
const handleConfirm = async () => {
try {
const body = cloneDeep(modifiedData);
// We need to send the unformated edit layout
set(body, 'layouts.edit', unformatLayout(body.layouts.edit));
@ -183,6 +168,7 @@ const EditSettingsView = ({
delete body.uid;
delete body.isComponent;
delete body.category;
delete body.apiID;
await request(getRequestUrl(`${type}/${slug || componentSlug}`), {
method: 'PUT',
@ -252,24 +238,15 @@ const EditSettingsView = ({
getForm().map((meta, index) => {
const formType = get(getAttributes, [metaToEdit, 'type']);
if (
formType === 'dynamiczone' &&
!['label', 'description'].includes(meta)
) {
if (formType === 'dynamiczone' && !['label', 'description'].includes(meta)) {
return null;
}
if (
(formType === 'component' || formType === 'media') &&
meta !== 'label'
) {
if ((formType === 'component' || formType === 'media') && meta !== 'label') {
return null;
}
if (
(formType === 'json' || formType === 'boolean') &&
meta === 'placeholder'
) {
if ((formType === 'json' || formType === 'boolean') && meta === 'placeholder') {
return null;
}
@ -284,11 +261,7 @@ const EditSettingsView = ({
>
{description => (
<FormattedMessage
id={get(
getInputProps(meta),
'label.id',
'app.utils.defaultMessage'
)}
id={get(getInputProps(meta), 'label.id', 'app.utils.defaultMessage')}
>
{label => (
<Input

View File

@ -32,7 +32,7 @@ const Header = () => {
const currentContentTypeMainField = get(layout, ['settings', 'mainField'], 'id');
const currentContentTypeName = get(layout, ['schema', 'info', 'name']);
const isCreatingEntry = id === 'create';
const isCreatingEntry = id === 'create' || (isSingleType && !initialData.created_at);
/* eslint-disable indent */
const entryHeaderTitle = isCreatingEntry

View File

@ -13,11 +13,7 @@ import * as yup from 'yup';
import { translatedErrors as errorsTrads } from 'strapi-helper-plugin';
yup.addMethod(yup.mixed, 'defined', function() {
return this.test(
'defined',
errorsTrads.required,
value => value !== undefined
);
return this.test('defined', errorsTrads.required, value => value !== undefined);
});
yup.addMethod(yup.array, 'notEmptyMin', function(min) {
@ -89,12 +85,9 @@ const createYupSchema = (model, { components }) => {
}
if (attribute.type === 'component') {
const componentFieldSchema = createYupSchema(
components[attribute.component],
{
components,
}
);
const componentFieldSchema = createYupSchema(components[attribute.component], {
components,
});
if (attribute.repeatable === true) {
const { min, max, required } = attribute;
@ -129,9 +122,7 @@ const createYupSchema = (model, { components }) => {
: componentFieldSchema.nullable();
}
return attribute.required === true
? yup.object().defined()
: yup.object().nullable();
return attribute.required === true ? yup.object().defined() : yup.object().nullable();
});
acc[current] = componentSchema;
@ -178,11 +169,7 @@ const createYupSchema = (model, { components }) => {
const createYupSchemaAttribute = (type, validations) => {
let schema = yup.mixed();
if (
['string', 'text', 'richtext', 'email', 'password', 'enumeration'].includes(
type
)
) {
if (['string', 'uid', 'text', 'richtext', 'email', 'password', 'enumeration'].includes(type)) {
schema = yup.string();
}
@ -194,12 +181,7 @@ const createYupSchemaAttribute = (type, validations) => {
return true;
}
if (
isNumber(value) ||
isNull(value) ||
isObject(value) ||
isArray(value)
) {
if (isNumber(value) || isNull(value) || isObject(value) || isArray(value)) {
return true;
}
@ -238,9 +220,8 @@ const createYupSchemaAttribute = (type, validations) => {
if (
!!validationValue ||
((!isBoolean(validationValue) &&
Number.isInteger(Math.floor(validationValue))) ||
validationValue === 0)
(!isBoolean(validationValue) && Number.isInteger(Math.floor(validationValue))) ||
validationValue === 0
) {
switch (validation) {
case 'required':
@ -282,16 +263,12 @@ const createYupSchemaAttribute = (type, validations) => {
}
break;
case 'positive':
if (
['number', 'integer', 'bigint', 'float', 'decimal'].includes(type)
) {
if (['number', 'integer', 'bigint', 'float', 'decimal'].includes(type)) {
schema = schema.positive();
}
break;
case 'negative':
if (
['number', 'integer', 'bigint', 'float', 'decimal'].includes(type)
) {
if (['number', 'integer', 'bigint', 'float', 'decimal'].includes(type)) {
schema = schema.negative();
}
break;

View File

@ -50,6 +50,12 @@
"components.TableEmpty.withSearch": "There is no {contentType} corresponding to the search ({search})...",
"components.TableEmpty.withoutFilter": "There is no {contentType}...",
"components.uid.available": "available",
"components.uid.apply": "apply",
"components.uid.regenerate": "regenerate",
"components.uid.suggested": "suggested",
"components.uid.unavailable": "unavailable",
"containers.Edit.addAnItem": "Add an item...",
"containers.Edit.pluginHeader.title.new": "Create an entry",
"containers.Edit.clickToJump": "Click to jump to the entry",

View File

@ -47,6 +47,12 @@
"components.TableEmpty.withSearch": "Aucun {contentType} n'a été trouvé avec cette recherche ({search})...",
"components.TableEmpty.withoutFilter": "Aucun {contentType} n'a été trouvé...",
"components.uid.available": "disponible",
"components.uid.apply": "appliquer",
"components.uid.regenerate": "regénérer",
"components.uid.unavailable": "indisponible",
"components.uid.suggested": "suggéré",
"containers.Edit.addAnItem": "Ajouter un élément...",
"containers.Edit.pluginHeader.title.new": "Créer un document",
"containers.Edit.clickToJump": "Cliquer pour voir l'entrée",

View File

@ -704,7 +704,7 @@ const forms = {
if (type === 'uid') {
const options = Object.keys(attributes)
.filter(key => attributes[key].type === 'string')
.filter(key => ['string', 'text'].includes(attributes[key].type))
.map(key => ({ id: key, value: key }));
items[0].push({

View File

@ -99,12 +99,10 @@ module.exports = {
},
async getEnvironments(ctx) {
const environments = Object.keys(strapi.config.environments).map(
environment => ({
name: environment,
active: strapi.config.environment === environment,
})
);
const environments = Object.keys(strapi.config.environments).map(environment => ({
name: environment,
active: strapi.config.environment === environment,
}));
ctx.send({ environments });
},
@ -143,18 +141,14 @@ module.exports = {
},
async find(ctx) {
const data = await strapi.plugins['upload'].services.upload.fetchAll(
ctx.query
);
const data = await strapi.plugins['upload'].services.upload.fetchAll(ctx.query);
// Send 200 `ok`
ctx.send(data);
},
async findOne(ctx) {
const data = await strapi.plugins['upload'].services.upload.fetch(
ctx.params
);
const data = await strapi.plugins['upload'].services.upload.fetch(ctx.params);
if (!data) {
return ctx.notFound('file.notFound');
@ -164,9 +158,7 @@ module.exports = {
},
async count(ctx) {
const data = await strapi.plugins['upload'].services.upload.count(
ctx.query
);
const data = await strapi.plugins['upload'].services.upload.count(ctx.query);
ctx.send({ count: data });
},
@ -208,9 +200,9 @@ const searchQueries = {
return ({ id }) => {
return model
.query(qb => {
qb.whereRaw('LOWER(hash) LIKE ?', [
qb.whereRaw('LOWER(hash) LIKE ?', [`%${id}%`]).orWhereRaw('LOWER(name) LIKE ?', [
`%${id}%`,
]).orWhereRaw('LOWER(name) LIKE ?', [`%${id}%`]);
]);
})
.fetchAll()
.then(results => results.toJSON());

View File

@ -33,9 +33,7 @@ module.exports = {
const createBuffer = async stream => {
const parts = await toArray(fs.createReadStream(stream.path));
const buffers = parts.map(part =>
_.isBuffer(part) ? part : Buffer.from(part)
);
const buffers = parts.map(part => (_.isBuffer(part) ? part : Buffer.from(part)));
const buffer = Buffer.concat(buffers);
@ -44,10 +42,7 @@ module.exports = {
name: stream.name,
sha256: niceHash(buffer),
hash: uuid().replace(/-/g, ''),
ext:
stream.name.split('.').length > 1
? `.${_.last(stream.name.split('.'))}`
: '',
ext: stream.name.split('.').length > 1 ? `.${_.last(stream.name.split('.'))}` : '',
buffer,
mime: stream.type,
size: (stream.size / 1000).toFixed(2),

View File

@ -3322,9 +3322,9 @@ acorn-globals@^4.1.0:
acorn-walk "^6.0.1"
acorn-jsx@^5.1.0:
version "5.1.0"
resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.1.0.tgz#294adb71b57398b0680015f0a38c563ee1db5384"
integrity sha512-tMUqwBWfLFbJbizRmEcWSLw6HnFzfdJs2sOJEOwwtVPMoH/0Ay+E703oZz78VSXZiiDcZrQ5XKjPIUQixhmgVw==
version "5.2.0"
resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.2.0.tgz#4c66069173d6fdd68ed85239fc256226182b2ebe"
integrity sha512-HiUX/+K2YpkpJ+SzBffkM/AQ2YE03S0U1kjTLVpoJdhZMOWy8qvXVN9JdLqv2QsaQ6MPYQIuNmwD8zOiYUofLQ==
acorn-walk@^6.0.1:
version "6.2.0"
@ -15555,9 +15555,9 @@ rsvp@^4.8.4:
integrity sha512-nfMOlASu9OnRJo1mbEk2cz0D56a1MBNrJ7orjRZQG10XDyuvwksKbuXNp6qa+kbn839HwjwhBzhFmdsaEAfauA==
run-async@^2.2.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.3.0.tgz#0371ab4ae0bdd720d4166d7dfda64ff7a445a6c0"
integrity sha1-A3GrSuC91yDUFm19/aZP96RFpsA=
version "2.4.0"
resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.4.0.tgz#e59054a5b86876cfae07f431d18cbaddc594f1e8"
integrity sha512-xJTbh/d7Lm7SBhc1tNvTpeCHaEzoyxPrqNlvSdMfBTYwaY++UJFyXUOxAtsRUXjlqOfj8luNaR9vjCh4KeV+pg==
dependencies:
is-promise "^2.1.0"