mirror of
				https://github.com/strapi/strapi.git
				synced 2025-10-31 01:47:13 +00:00 
			
		
		
		
	Merge pull request #5322 from strapi/single-types/uid-ctm
Add UID in CTM and fix Single types alignments
This commit is contained in:
		
						commit
						0bb347f79b
					
				
										
											Binary file not shown.
										
									
								
							| Before Width: | Height: | Size: 1.0 MiB After Width: | Height: | Size: 352 KiB | 
| @ -26,7 +26,7 @@ const Wrapper = styled.div` | |||||||
|     font-weight: 400; |     font-weight: 400; | ||||||
|     letter-spacing: 0.05rem; |     letter-spacing: 0.05rem; | ||||||
|     vertical-align: middle; |     vertical-align: middle; | ||||||
|     color: ${props => props.theme.main.colors.strapi['gray-light']}; |     color: ${({ theme }) => theme.main.colors.strapi.grayLight}; | ||||||
|   } |   } | ||||||
| `;
 | `;
 | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -2,7 +2,7 @@ import styled from 'styled-components'; | |||||||
| 
 | 
 | ||||||
| const A = styled.a` | const A = styled.a` | ||||||
|   position: relative; |   position: relative; | ||||||
|   padding-top: 0.8rem; |   padding-top: 0.7rem; | ||||||
|   padding-bottom: 0.2rem; |   padding-bottom: 0.2rem; | ||||||
|   padding-left: 1.6rem; |   padding-left: 1.6rem; | ||||||
|   min-height: 3.6rem; |   min-height: 3.6rem; | ||||||
|  | |||||||
| @ -5,7 +5,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; | |||||||
| 
 | 
 | ||||||
| const FaIcon = styled(({ small, ...props }) => <FontAwesomeIcon {...props} />)` | const FaIcon = styled(({ small, ...props }) => <FontAwesomeIcon {...props} />)` | ||||||
|   position: absolute; |   position: absolute; | ||||||
|   top: calc(50% - 0.9rem + 0.5rem); |   top: calc(50% - 0.9rem + 0.3rem); | ||||||
|   left: 1.6rem; |   left: 1.6rem; | ||||||
|   margin-right: 1.2rem; |   margin-right: 1.2rem; | ||||||
|   font-size: ${props => (props.small ? '1rem' : '1.4rem')}; |   font-size: ${props => (props.small ? '1rem' : '1.4rem')}; | ||||||
| @ -14,9 +14,7 @@ const FaIcon = styled(({ small, ...props }) => <FontAwesomeIcon {...props} />)` | |||||||
|   text-align: center; |   text-align: center; | ||||||
| `;
 | `;
 | ||||||
| 
 | 
 | ||||||
| const LeftMenuIcon = ({ icon }) => ( | const LeftMenuIcon = ({ icon }) => <FaIcon small={icon === 'circle'} icon={icon} />; | ||||||
|   <FaIcon small={icon === 'circle'} icon={icon} /> |  | ||||||
| ); |  | ||||||
| 
 | 
 | ||||||
| LeftMenuIcon.propTypes = { | LeftMenuIcon.propTypes = { | ||||||
|   icon: PropTypes.string, |   icon: PropTypes.string, | ||||||
|  | |||||||
| @ -2,9 +2,9 @@ import styled from 'styled-components'; | |||||||
| 
 | 
 | ||||||
| const Search = styled.input` | const Search = styled.input` | ||||||
|   width: 100%; |   width: 100%; | ||||||
|   padding: 0 21px; |   padding: 0 15px; | ||||||
|   outline: 0; |   outline: 0; | ||||||
|   font-size: 1.3rem; |   font-size: 1.2rem; | ||||||
|   color: ${props => props.theme.main.colors.white}; |   color: ${props => props.theme.main.colors.white}; | ||||||
| `;
 | `;
 | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -5,13 +5,14 @@ const Title = styled.div` | |||||||
|   justify-content: space-between; |   justify-content: space-between; | ||||||
|   padding-left: 2rem; |   padding-left: 2rem; | ||||||
|   padding-right: 1.6rem; |   padding-right: 1.6rem; | ||||||
|   padding-top: 0.7rem; |   padding-top: 1rem; | ||||||
|   margin-bottom: 0.8rem; |   margin-bottom: 0.8rem; | ||||||
|   color: ${props => props.theme.main.colors.leftMenu['title-color']}; |   color: ${props => props.theme.main.colors.leftMenu['title-color']}; | ||||||
|   text-transform: uppercase; |   text-transform: uppercase; | ||||||
|   font-size: 1.1rem; |   font-size: 1.2rem; | ||||||
|   letter-spacing: 0.1rem; |   letter-spacing: 0.1rem; | ||||||
|   font-weight: 800; |   font-weight: 800; | ||||||
|  |   max-height: 28px; | ||||||
| `;
 | `;
 | ||||||
| 
 | 
 | ||||||
| Title.defaultProps = { | Title.defaultProps = { | ||||||
|  | |||||||
| @ -6,7 +6,7 @@ const EmptyLinksList = styled.div` | |||||||
|   padding-right: 1.6rem; |   padding-right: 1.6rem; | ||||||
|   font-weight: 300; |   font-weight: 300; | ||||||
|   min-height: 3.6rem; |   min-height: 3.6rem; | ||||||
|   padding-top: 0.2rem; |   padding-top: 0.6rem; | ||||||
| `;
 | `;
 | ||||||
| 
 | 
 | ||||||
| EmptyLinksList.defaultProps = { | EmptyLinksList.defaultProps = { | ||||||
|  | |||||||
| @ -10,12 +10,7 @@ import { FormattedMessage } from 'react-intl'; | |||||||
| import { withRouter } from 'react-router-dom'; | import { withRouter } from 'react-router-dom'; | ||||||
| import { get } from 'lodash'; | import { get } from 'lodash'; | ||||||
| import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; | import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; | ||||||
| import { | import { ButtonDropdown, DropdownItem, DropdownMenu, DropdownToggle } from 'reactstrap'; | ||||||
|   ButtonDropdown, |  | ||||||
|   DropdownItem, |  | ||||||
|   DropdownMenu, |  | ||||||
|   DropdownToggle, |  | ||||||
| } from 'reactstrap'; |  | ||||||
| import { auth } from 'strapi-helper-plugin'; | import { auth } from 'strapi-helper-plugin'; | ||||||
| import Wrapper from './components'; | import Wrapper from './components'; | ||||||
| 
 | 
 | ||||||
| @ -26,14 +21,14 @@ const Logout = ({ history: { push } }) => { | |||||||
|     const id = get(auth.getUserInfo(), 'id'); |     const id = get(auth.getUserInfo(), 'id'); | ||||||
| 
 | 
 | ||||||
|     push({ |     push({ | ||||||
|       pathname: `/plugins/content-manager/strapi::administrator/${id}`, |       pathname: `/plugins/content-manager/collectionType/strapi::administrator/${id}`, | ||||||
|       search: |       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 = () => { |   const handleGoToAdministrator = () => { | ||||||
|     push({ |     push({ | ||||||
|       pathname: '/plugins/content-manager/strapi::administrator', |       pathname: '/plugins/content-manager/collectionType/strapi::administrator', | ||||||
|     }); |     }); | ||||||
|   }; |   }; | ||||||
|   const handleLogout = () => { |   const handleLogout = () => { | ||||||
|  | |||||||
| @ -10,8 +10,9 @@ const colors = { | |||||||
|   pink: '#ff5b77', |   pink: '#ff5b77', | ||||||
|   purple: '#613d7c', |   purple: '#613d7c', | ||||||
|   gray: '#464a4c', |   gray: '#464a4c', | ||||||
|  |   border: '#E3E9F3', | ||||||
|   'gray-dark': '#292b2c', |   'gray-dark': '#292b2c', | ||||||
|   'gray-light': '#636c72', |   grayLight: '#636c72', | ||||||
|   'gray-lighter': '#eceeef', |   'gray-lighter': '#eceeef', | ||||||
|   'gray-lightest': '#f7f7f9', |   'gray-lightest': '#f7f7f9', | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -12,9 +12,7 @@ export { default as Button } from './components/Button'; | |||||||
| export { default as ButtonModal } from './components/ButtonModal'; | export { default as ButtonModal } from './components/ButtonModal'; | ||||||
| export { default as CircleButton } from './components/CircleButton'; | export { default as CircleButton } from './components/CircleButton'; | ||||||
| export { default as ContainerFluid } from './components/ContainerFluid'; | export { default as ContainerFluid } from './components/ContainerFluid'; | ||||||
| export { | export { default as EmptyAttributesBlock } from './components/EmptyAttributesBlock'; | ||||||
|   default as EmptyAttributesBlock, |  | ||||||
| } from './components/EmptyAttributesBlock'; |  | ||||||
| export { default as ErrorBoundary } from './components/ErrorBoundary'; | export { default as ErrorBoundary } from './components/ErrorBoundary'; | ||||||
| export { default as ExtendComponent } from './components/ExtendComponent'; | export { default as ExtendComponent } from './components/ExtendComponent'; | ||||||
| export { default as GlobalPagination } from './components/GlobalPagination'; | 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 IcoContainer } from './components/IcoContainer'; | ||||||
| export { default as InputAddon } from './components/InputAddon'; | export { default as InputAddon } from './components/InputAddon'; | ||||||
| 
 | 
 | ||||||
| export { | export { default as InputAddonWithErrors } from './components/InputAddonWithErrors'; | ||||||
|   default as InputAddonWithErrors, |  | ||||||
| } from './components/InputAddonWithErrors'; |  | ||||||
| export { default as InputCheckbox } from './components/InputCheckbox'; | export { default as InputCheckbox } from './components/InputCheckbox'; | ||||||
| export { | export { default as InputCheckboxWithErrors } from './components/InputCheckboxWithErrors'; | ||||||
|   default as InputCheckboxWithErrors, |  | ||||||
| } from './components/InputCheckboxWithErrors'; |  | ||||||
| export { default as InputDate } from './components/InputDate'; | export { default as InputDate } from './components/InputDate'; | ||||||
| export { | export { default as InputDateWithErrors } from './components/InputDateWithErrors'; | ||||||
|   default as InputDateWithErrors, |  | ||||||
| } from './components/InputDateWithErrors'; |  | ||||||
| export { default as InputDescription } from './components/InputDescription'; | export { default as InputDescription } from './components/InputDescription'; | ||||||
| export { default as InputEmail } from './components/InputEmail'; | export { default as InputEmail } from './components/InputEmail'; | ||||||
| export { | export { default as InputEmailWithErrors } from './components/InputEmailWithErrors'; | ||||||
|   default as InputEmailWithErrors, |  | ||||||
| } from './components/InputEmailWithErrors'; |  | ||||||
| export { default as InputErrors } from './components/InputErrors'; | export { default as InputErrors } from './components/InputErrors'; | ||||||
| export { default as InputFile } from './components/InputFile'; | export { default as InputFile } from './components/InputFile'; | ||||||
| export { default as InputNumber } from './components/InputNumber'; | export { default as InputNumber } from './components/InputNumber'; | ||||||
| export { | export { default as InputNumberWithErrors } from './components/InputNumberWithErrors'; | ||||||
|   default as InputNumberWithErrors, |  | ||||||
| } from './components/InputNumberWithErrors'; |  | ||||||
| export { default as InputPassword } from './components/InputPassword'; | export { default as InputPassword } from './components/InputPassword'; | ||||||
| export { | export { default as InputPasswordWithErrors } from './components/InputPasswordWithErrors'; | ||||||
|   default as InputPasswordWithErrors, |  | ||||||
| } from './components/InputPasswordWithErrors'; |  | ||||||
| export { default as InputSearch } from './components/InputSearch'; | export { default as InputSearch } from './components/InputSearch'; | ||||||
| export { | export { default as InputSearchWithErrors } from './components/InputSearchWithErrors'; | ||||||
|   default as InputSearchWithErrors, |  | ||||||
| } from './components/InputSearchWithErrors'; |  | ||||||
| export { default as InputSelect } from './components/InputSelect'; | export { default as InputSelect } from './components/InputSelect'; | ||||||
| export { | export { default as InputSelectWithErrors } from './components/InputSelectWithErrors'; | ||||||
|   default as InputSelectWithErrors, |  | ||||||
| } from './components/InputSelectWithErrors'; |  | ||||||
| export { default as InputsIndex } from './components/InputsIndex'; | export { default as InputsIndex } from './components/InputsIndex'; | ||||||
| export { default as InputSpacer } from './components/InputSpacer'; | export { default as InputSpacer } from './components/InputSpacer'; | ||||||
| export { default as InputText } from './components/InputText'; | export { default as InputText } from './components/InputText'; | ||||||
| export { | export { default as InputTextWithErrors } from './components/InputTextWithErrors'; | ||||||
|   default as InputTextWithErrors, |  | ||||||
| } from './components/InputTextWithErrors'; |  | ||||||
| export { default as InputTextArea } from './components/InputTextArea'; | export { default as InputTextArea } from './components/InputTextArea'; | ||||||
| export { | export { default as InputTextAreaWithErrors } from './components/InputTextAreaWithErrors'; | ||||||
|   default as InputTextAreaWithErrors, |  | ||||||
| } from './components/InputTextAreaWithErrors'; |  | ||||||
| export { default as InputToggle } from './components/InputToggle'; | export { default as InputToggle } from './components/InputToggle'; | ||||||
| export { | export { default as InputToggleWithErrors } from './components/InputToggleWithErrors'; | ||||||
|   default as InputToggleWithErrors, |  | ||||||
| } from './components/InputToggleWithErrors'; |  | ||||||
| 
 | 
 | ||||||
| export { default as Label } from './components/Label'; | export { default as Label } from './components/Label'; | ||||||
| export { default as LeftMenu } from './components/LeftMenu'; | 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 LoadingBar } from './components/LoadingBar'; | ||||||
| export { default as LoadingIndicator } from './components/LoadingIndicator'; | export { default as LoadingIndicator } from './components/LoadingIndicator'; | ||||||
| export { | export { default as LoadingIndicatorPage } from './components/LoadingIndicatorPage'; | ||||||
|   default as LoadingIndicatorPage, |  | ||||||
| } from './components/LoadingIndicatorPage'; |  | ||||||
| 
 | 
 | ||||||
| export { default as Modal } from './components/Modal'; | export { default as Modal } from './components/Modal'; | ||||||
| export { default as ModalBody } from './components/BodyModal'; | 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'; | export { default as ViewContainer } from './components/ViewContainer'; | ||||||
| 
 | 
 | ||||||
| // Contexts
 | // Contexts
 | ||||||
| export { | export { GlobalContext, GlobalContextProvider, useGlobalContext } from './contexts/GlobalContext'; | ||||||
|   GlobalContext, |  | ||||||
|   GlobalContextProvider, |  | ||||||
|   useGlobalContext, |  | ||||||
| } from './contexts/GlobalContext'; |  | ||||||
| 
 | 
 | ||||||
| // Utils
 | // Utils
 | ||||||
| export { default as auth } from './utils/auth'; | export { default as auth } from './utils/auth'; | ||||||
|  | |||||||
| @ -58,12 +58,7 @@ const CustomTable = ({ data, headers, isBulkable }) => { | |||||||
|               handleGoTo(row.id); |               handleGoTo(row.id); | ||||||
|             }} |             }} | ||||||
|           > |           > | ||||||
|             <Row |             <Row isBulkable={isBulkable} headers={headers} row={row} goTo={handleGoTo} /> | ||||||
|               isBulkable={isBulkable} |  | ||||||
|               headers={headers} |  | ||||||
|               row={row} |  | ||||||
|               goTo={handleGoTo} |  | ||||||
|             /> |  | ||||||
|           </TableRow> |           </TableRow> | ||||||
|         ); |         ); | ||||||
|       }) |       }) | ||||||
| @ -73,9 +68,7 @@ const CustomTable = ({ data, headers, isBulkable }) => { | |||||||
|     <Table className="table"> |     <Table className="table"> | ||||||
|       <TableHeader headers={headers} isBulkable={isBulkable} /> |       <TableHeader headers={headers} isBulkable={isBulkable} /> | ||||||
|       <tbody> |       <tbody> | ||||||
|         {entriesToDelete.length > 0 && ( |         {entriesToDelete.length > 0 && <ActionCollapse colSpan={colSpanLength} />} | ||||||
|           <ActionCollapse colSpan={colSpanLength} /> |  | ||||||
|         )} |  | ||||||
|         {content} |         {content} | ||||||
|       </tbody> |       </tbody> | ||||||
|     </Table> |     </Table> | ||||||
| @ -91,13 +84,6 @@ CustomTable.defaultProps = { | |||||||
| CustomTable.propTypes = { | CustomTable.propTypes = { | ||||||
|   data: PropTypes.array, |   data: PropTypes.array, | ||||||
|   headers: 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, |   isBulkable: PropTypes.bool, | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -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; | ||||||
| @ -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; | ||||||
| @ -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; | ||||||
| @ -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; | ||||||
| @ -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; | ||||||
| @ -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; | ||||||
| @ -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; | ||||||
| @ -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; | ||||||
| @ -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; | ||||||
| @ -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; | ||||||
| @ -9,6 +9,7 @@ import InputJSONWithErrors from '../InputJSONWithErrors'; | |||||||
| import InputFileWithErrors from '../InputFileWithErrors'; | import InputFileWithErrors from '../InputFileWithErrors'; | ||||||
| import SelectWrapper from '../SelectWrapper'; | import SelectWrapper from '../SelectWrapper'; | ||||||
| import WysiwygWithErrors from '../WysiwygWithErrors'; | import WysiwygWithErrors from '../WysiwygWithErrors'; | ||||||
|  | import InputUID from '../InputUID'; | ||||||
| 
 | 
 | ||||||
| const getInputType = (type = '') => { | const getInputType = (type = '') => { | ||||||
|   switch (toLower(type)) { |   switch (toLower(type)) { | ||||||
| @ -42,32 +43,19 @@ const getInputType = (type = '') => { | |||||||
|     case 'WYSIWYG': |     case 'WYSIWYG': | ||||||
|     case 'richtext': |     case 'richtext': | ||||||
|       return 'wysiwyg'; |       return 'wysiwyg'; | ||||||
|  |     case 'uid': | ||||||
|  |       return 'uid'; | ||||||
|     default: |     default: | ||||||
|       return 'text'; |       return 'text'; | ||||||
|   } |   } | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| function Inputs({ autoFocus, keys, layout, name, onBlur }) { | function Inputs({ autoFocus, keys, layout, name, onBlur }) { | ||||||
|   const { |   const { didCheckErrors, formErrors, modifiedData, onChange } = useDataManager(); | ||||||
|     didCheckErrors, |   const attribute = useMemo(() => get(layout, ['schema', 'attributes', name], {}), [layout, name]); | ||||||
|     formErrors, |   const metadatas = useMemo(() => get(layout, ['metadatas', name, 'edit'], {}), [layout, name]); | ||||||
|     modifiedData, |   const disabled = useMemo(() => !get(metadatas, 'editable', true), [metadatas]); | ||||||
|     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 type = useMemo(() => get(attribute, 'type', null), [attribute]); | ||||||
| 
 |  | ||||||
|   const validations = omit(attribute, [ |   const validations = omit(attribute, [ | ||||||
|     'type', |     'type', | ||||||
|     'model', |     'model', | ||||||
| @ -83,13 +71,13 @@ function Inputs({ autoFocus, keys, layout, name, onBlur }) { | |||||||
|   if (visible === false) { |   if (visible === false) { | ||||||
|     return null; |     return null; | ||||||
|   } |   } | ||||||
|   const temporaryErrorIdUntilBuffetjsSupportsFormattedMessage = |   const temporaryErrorIdUntilBuffetjsSupportsFormattedMessage = 'app.utils.defaultMessage'; | ||||||
|     'app.utils.defaultMessage'; |  | ||||||
|   const errorId = get( |   const errorId = get( | ||||||
|     formErrors, |     formErrors, | ||||||
|     [keys, 'id'], |     [keys, 'id'], | ||||||
|     temporaryErrorIdUntilBuffetjsSupportsFormattedMessage |     temporaryErrorIdUntilBuffetjsSupportsFormattedMessage | ||||||
|   ); |   ); | ||||||
|  |   const isRequired = get(validations, ['required'], false); | ||||||
| 
 | 
 | ||||||
|   if (type === 'relation') { |   if (type === 'relation') { | ||||||
|     return ( |     return ( | ||||||
| @ -131,12 +119,8 @@ function Inputs({ autoFocus, keys, layout, name, onBlur }) { | |||||||
|     ); |     ); | ||||||
|   }); |   }); | ||||||
| 
 | 
 | ||||||
|   const isRequired = get(validations, ['required'], false); |  | ||||||
|   const enumOptions = [ |   const enumOptions = [ | ||||||
|     <FormattedMessage |     <FormattedMessage id="components.InputSelect.option.placeholder" key="__enum_option_null"> | ||||||
|       id="components.InputSelect.option.placeholder" |  | ||||||
|       key="__enum_option_null" |  | ||||||
|     > |  | ||||||
|       {msg => ( |       {msg => ( | ||||||
|         <option disabled={isRequired} hidden={isRequired} value=""> |         <option disabled={isRequired} hidden={isRequired} value=""> | ||||||
|           {msg} |           {msg} | ||||||
| @ -156,19 +140,21 @@ function Inputs({ autoFocus, keys, layout, name, onBlur }) { | |||||||
|             didCheckErrors={didCheckErrors} |             didCheckErrors={didCheckErrors} | ||||||
|             disabled={disabled} |             disabled={disabled} | ||||||
|             error={ |             error={ | ||||||
|               isEmpty(error) || |               isEmpty(error) || errorId === temporaryErrorIdUntilBuffetjsSupportsFormattedMessage | ||||||
|               errorId === temporaryErrorIdUntilBuffetjsSupportsFormattedMessage |  | ||||||
|                 ? null |                 ? null | ||||||
|                 : error |                 : error | ||||||
|             } |             } | ||||||
|             inputDescription={description} |             inputDescription={description} | ||||||
|             description={description} |             description={description} | ||||||
|  |             contentTypeUID={layout.uid} | ||||||
|             customInputs={{ |             customInputs={{ | ||||||
|               media: InputFileWithErrors, |               media: InputFileWithErrors, | ||||||
|               json: InputJSONWithErrors, |               json: InputJSONWithErrors, | ||||||
|               wysiwyg: WysiwygWithErrors, |               wysiwyg: WysiwygWithErrors, | ||||||
|  |               uid: InputUID, | ||||||
|             }} |             }} | ||||||
|             multiple={get(attribute, 'multiple', false)} |             multiple={get(attribute, 'multiple', false)} | ||||||
|  |             attribute={attribute} | ||||||
|             name={keys} |             name={keys} | ||||||
|             onBlur={onBlur} |             onBlur={onBlur} | ||||||
|             onChange={onChange} |             onChange={onChange} | ||||||
|  | |||||||
| @ -1,10 +1,4 @@ | |||||||
| import React, { | import React, { useCallback, useEffect, useMemo, useReducer, useState } from 'react'; | ||||||
|   useCallback, |  | ||||||
|   useEffect, |  | ||||||
|   useMemo, |  | ||||||
|   useReducer, |  | ||||||
|   useState, |  | ||||||
| } from 'react'; |  | ||||||
| import PropTypes from 'prop-types'; | import PropTypes from 'prop-types'; | ||||||
| import { useParams } from 'react-router-dom'; | import { useParams } from 'react-router-dom'; | ||||||
| import { cloneDeep, get, set } from 'lodash'; | import { cloneDeep, get, set } from 'lodash'; | ||||||
| @ -74,9 +68,9 @@ const EditSettingsView = ({ | |||||||
|   }, [modifiedData]); |   }, [modifiedData]); | ||||||
| 
 | 
 | ||||||
|   const getForm = () => |   const getForm = () => | ||||||
|     Object.keys( |     Object.keys(get(modifiedData, ['metadatas', metaToEdit, 'edit'], {})).filter( | ||||||
|       get(modifiedData, ['metadatas', metaToEdit, 'edit'], {}) |       meta => meta !== 'visible' | ||||||
|     ).filter(meta => meta !== 'visible'); |     ); | ||||||
| 
 | 
 | ||||||
|   const getRelationsLayout = useCallback(() => { |   const getRelationsLayout = useCallback(() => { | ||||||
|     return get(modifiedData, ['layouts', 'editRelations'], []); |     return get(modifiedData, ['layouts', 'editRelations'], []); | ||||||
| @ -94,10 +88,7 @@ const EditSettingsView = ({ | |||||||
|   const getEditRemainingFields = () => { |   const getEditRemainingFields = () => { | ||||||
|     const attributes = getAttributes; |     const attributes = getAttributes; | ||||||
|     const metadatas = get(modifiedData, ['metadatas'], {}); |     const metadatas = get(modifiedData, ['metadatas'], {}); | ||||||
|     const displayedFields = getEditLayout().reduce( |     const displayedFields = getEditLayout().reduce((acc, curr) => [...acc, ...curr.rowContent], []); | ||||||
|       (acc, curr) => [...acc, ...curr.rowContent], |  | ||||||
|       [] |  | ||||||
|     ); |  | ||||||
| 
 | 
 | ||||||
|     return Object.keys(attributes) |     return Object.keys(attributes) | ||||||
|       .filter(attr => get(attributes, [attr, 'type'], '') !== 'relation') |       .filter(attr => get(attributes, [attr, 'type'], '') !== 'relation') | ||||||
| @ -114,11 +105,7 @@ const EditSettingsView = ({ | |||||||
|       } |       } | ||||||
| 
 | 
 | ||||||
|       const targetKey = formType === 'component' ? 'component' : 'targetModel'; |       const targetKey = formType === 'component' ? 'component' : 'targetModel'; | ||||||
|       const key = get( |       const key = get(modifiedData, ['schema', 'attributes', metaToEdit, targetKey], ''); | ||||||
|         modifiedData, |  | ||||||
|         ['schema', 'attributes', metaToEdit, targetKey], |  | ||||||
|         '' |  | ||||||
|       ); |  | ||||||
| 
 | 
 | ||||||
|       return get(componentsAndModelsMainPossibleMainFields, [key], []); |       return get(componentsAndModelsMainPossibleMainFields, [key], []); | ||||||
|     }, |     }, | ||||||
| @ -129,13 +116,10 @@ const EditSettingsView = ({ | |||||||
|   useEffect(() => { |   useEffect(() => { | ||||||
|     const getData = async () => { |     const getData = async () => { | ||||||
|       try { |       try { | ||||||
|         const { data } = await request( |         const { data } = await request(getRequestUrl(`${type}/${slug || componentSlug}`), { | ||||||
|           getRequestUrl(`${type}/${slug || componentSlug}`), |  | ||||||
|           { |  | ||||||
|           method: 'GET', |           method: 'GET', | ||||||
|           signal, |           signal, | ||||||
|           } |         }); | ||||||
|         ); |  | ||||||
| 
 | 
 | ||||||
|         dispatch({ |         dispatch({ | ||||||
|           type: 'GET_DATA_SUCCEEDED', |           type: 'GET_DATA_SUCCEEDED', | ||||||
| @ -176,6 +160,7 @@ const EditSettingsView = ({ | |||||||
|   const handleConfirm = async () => { |   const handleConfirm = async () => { | ||||||
|     try { |     try { | ||||||
|       const body = cloneDeep(modifiedData); |       const body = cloneDeep(modifiedData); | ||||||
|  | 
 | ||||||
|       // We need to send the unformated edit layout
 |       // We need to send the unformated edit layout
 | ||||||
|       set(body, 'layouts.edit', unformatLayout(body.layouts.edit)); |       set(body, 'layouts.edit', unformatLayout(body.layouts.edit)); | ||||||
| 
 | 
 | ||||||
| @ -183,6 +168,7 @@ const EditSettingsView = ({ | |||||||
|       delete body.uid; |       delete body.uid; | ||||||
|       delete body.isComponent; |       delete body.isComponent; | ||||||
|       delete body.category; |       delete body.category; | ||||||
|  |       delete body.apiID; | ||||||
| 
 | 
 | ||||||
|       await request(getRequestUrl(`${type}/${slug || componentSlug}`), { |       await request(getRequestUrl(`${type}/${slug || componentSlug}`), { | ||||||
|         method: 'PUT', |         method: 'PUT', | ||||||
| @ -252,24 +238,15 @@ const EditSettingsView = ({ | |||||||
|     getForm().map((meta, index) => { |     getForm().map((meta, index) => { | ||||||
|       const formType = get(getAttributes, [metaToEdit, 'type']); |       const formType = get(getAttributes, [metaToEdit, 'type']); | ||||||
| 
 | 
 | ||||||
|       if ( |       if (formType === 'dynamiczone' && !['label', 'description'].includes(meta)) { | ||||||
|         formType === 'dynamiczone' && |  | ||||||
|         !['label', 'description'].includes(meta) |  | ||||||
|       ) { |  | ||||||
|         return null; |         return null; | ||||||
|       } |       } | ||||||
| 
 | 
 | ||||||
|       if ( |       if ((formType === 'component' || formType === 'media') && meta !== 'label') { | ||||||
|         (formType === 'component' || formType === 'media') && |  | ||||||
|         meta !== 'label' |  | ||||||
|       ) { |  | ||||||
|         return null; |         return null; | ||||||
|       } |       } | ||||||
| 
 | 
 | ||||||
|       if ( |       if ((formType === 'json' || formType === 'boolean') && meta === 'placeholder') { | ||||||
|         (formType === 'json' || formType === 'boolean') && |  | ||||||
|         meta === 'placeholder' |  | ||||||
|       ) { |  | ||||||
|         return null; |         return null; | ||||||
|       } |       } | ||||||
| 
 | 
 | ||||||
| @ -284,11 +261,7 @@ const EditSettingsView = ({ | |||||||
|           > |           > | ||||||
|             {description => ( |             {description => ( | ||||||
|               <FormattedMessage |               <FormattedMessage | ||||||
|                 id={get( |                 id={get(getInputProps(meta), 'label.id', 'app.utils.defaultMessage')} | ||||||
|                   getInputProps(meta), |  | ||||||
|                   'label.id', |  | ||||||
|                   'app.utils.defaultMessage' |  | ||||||
|                 )} |  | ||||||
|               > |               > | ||||||
|                 {label => ( |                 {label => ( | ||||||
|                   <Input |                   <Input | ||||||
|  | |||||||
| @ -32,7 +32,7 @@ const Header = () => { | |||||||
| 
 | 
 | ||||||
|   const currentContentTypeMainField = get(layout, ['settings', 'mainField'], 'id'); |   const currentContentTypeMainField = get(layout, ['settings', 'mainField'], 'id'); | ||||||
|   const currentContentTypeName = get(layout, ['schema', 'info', 'name']); |   const currentContentTypeName = get(layout, ['schema', 'info', 'name']); | ||||||
|   const isCreatingEntry = id === 'create'; |   const isCreatingEntry = id === 'create' || (isSingleType && !initialData.created_at); | ||||||
| 
 | 
 | ||||||
|   /* eslint-disable indent */ |   /* eslint-disable indent */ | ||||||
|   const entryHeaderTitle = isCreatingEntry |   const entryHeaderTitle = isCreatingEntry | ||||||
|  | |||||||
| @ -13,11 +13,7 @@ import * as yup from 'yup'; | |||||||
| import { translatedErrors as errorsTrads } from 'strapi-helper-plugin'; | import { translatedErrors as errorsTrads } from 'strapi-helper-plugin'; | ||||||
| 
 | 
 | ||||||
| yup.addMethod(yup.mixed, 'defined', function() { | yup.addMethod(yup.mixed, 'defined', function() { | ||||||
|   return this.test( |   return this.test('defined', errorsTrads.required, value => value !== undefined); | ||||||
|     'defined', |  | ||||||
|     errorsTrads.required, |  | ||||||
|     value => value !== undefined |  | ||||||
|   ); |  | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| yup.addMethod(yup.array, 'notEmptyMin', function(min) { | yup.addMethod(yup.array, 'notEmptyMin', function(min) { | ||||||
| @ -89,12 +85,9 @@ const createYupSchema = (model, { components }) => { | |||||||
|       } |       } | ||||||
| 
 | 
 | ||||||
|       if (attribute.type === 'component') { |       if (attribute.type === 'component') { | ||||||
|         const componentFieldSchema = createYupSchema( |         const componentFieldSchema = createYupSchema(components[attribute.component], { | ||||||
|           components[attribute.component], |  | ||||||
|           { |  | ||||||
|           components, |           components, | ||||||
|           } |         }); | ||||||
|         ); |  | ||||||
| 
 | 
 | ||||||
|         if (attribute.repeatable === true) { |         if (attribute.repeatable === true) { | ||||||
|           const { min, max, required } = attribute; |           const { min, max, required } = attribute; | ||||||
| @ -129,9 +122,7 @@ const createYupSchema = (model, { components }) => { | |||||||
|               : componentFieldSchema.nullable(); |               : componentFieldSchema.nullable(); | ||||||
|           } |           } | ||||||
| 
 | 
 | ||||||
|           return attribute.required === true |           return attribute.required === true ? yup.object().defined() : yup.object().nullable(); | ||||||
|             ? yup.object().defined() |  | ||||||
|             : yup.object().nullable(); |  | ||||||
|         }); |         }); | ||||||
| 
 | 
 | ||||||
|         acc[current] = componentSchema; |         acc[current] = componentSchema; | ||||||
| @ -178,11 +169,7 @@ const createYupSchema = (model, { components }) => { | |||||||
| const createYupSchemaAttribute = (type, validations) => { | const createYupSchemaAttribute = (type, validations) => { | ||||||
|   let schema = yup.mixed(); |   let schema = yup.mixed(); | ||||||
| 
 | 
 | ||||||
|   if ( |   if (['string', 'uid', 'text', 'richtext', 'email', 'password', 'enumeration'].includes(type)) { | ||||||
|     ['string', 'text', 'richtext', 'email', 'password', 'enumeration'].includes( |  | ||||||
|       type |  | ||||||
|     ) |  | ||||||
|   ) { |  | ||||||
|     schema = yup.string(); |     schema = yup.string(); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
| @ -194,12 +181,7 @@ const createYupSchemaAttribute = (type, validations) => { | |||||||
|           return true; |           return true; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         if ( |         if (isNumber(value) || isNull(value) || isObject(value) || isArray(value)) { | ||||||
|           isNumber(value) || |  | ||||||
|           isNull(value) || |  | ||||||
|           isObject(value) || |  | ||||||
|           isArray(value) |  | ||||||
|         ) { |  | ||||||
|           return true; |           return true; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
| @ -238,9 +220,8 @@ const createYupSchemaAttribute = (type, validations) => { | |||||||
| 
 | 
 | ||||||
|     if ( |     if ( | ||||||
|       !!validationValue || |       !!validationValue || | ||||||
|       ((!isBoolean(validationValue) && |       (!isBoolean(validationValue) && Number.isInteger(Math.floor(validationValue))) || | ||||||
|         Number.isInteger(Math.floor(validationValue))) || |       validationValue === 0 | ||||||
|         validationValue === 0) |  | ||||||
|     ) { |     ) { | ||||||
|       switch (validation) { |       switch (validation) { | ||||||
|         case 'required': |         case 'required': | ||||||
| @ -282,16 +263,12 @@ const createYupSchemaAttribute = (type, validations) => { | |||||||
|           } |           } | ||||||
|           break; |           break; | ||||||
|         case 'positive': |         case 'positive': | ||||||
|           if ( |           if (['number', 'integer', 'bigint', 'float', 'decimal'].includes(type)) { | ||||||
|             ['number', 'integer', 'bigint', 'float', 'decimal'].includes(type) |  | ||||||
|           ) { |  | ||||||
|             schema = schema.positive(); |             schema = schema.positive(); | ||||||
|           } |           } | ||||||
|           break; |           break; | ||||||
|         case 'negative': |         case 'negative': | ||||||
|           if ( |           if (['number', 'integer', 'bigint', 'float', 'decimal'].includes(type)) { | ||||||
|             ['number', 'integer', 'bigint', 'float', 'decimal'].includes(type) |  | ||||||
|           ) { |  | ||||||
|             schema = schema.negative(); |             schema = schema.negative(); | ||||||
|           } |           } | ||||||
|           break; |           break; | ||||||
|  | |||||||
| @ -50,6 +50,12 @@ | |||||||
|   "components.TableEmpty.withSearch": "There is no {contentType} corresponding to the search ({search})...", |   "components.TableEmpty.withSearch": "There is no {contentType} corresponding to the search ({search})...", | ||||||
|   "components.TableEmpty.withoutFilter": "There is no {contentType}...", |   "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.addAnItem": "Add an item...", | ||||||
|   "containers.Edit.pluginHeader.title.new": "Create an entry", |   "containers.Edit.pluginHeader.title.new": "Create an entry", | ||||||
|   "containers.Edit.clickToJump": "Click to jump to the entry", |   "containers.Edit.clickToJump": "Click to jump to the entry", | ||||||
|  | |||||||
| @ -47,6 +47,12 @@ | |||||||
|   "components.TableEmpty.withSearch": "Aucun {contentType} n'a été trouvé avec cette recherche ({search})...", |   "components.TableEmpty.withSearch": "Aucun {contentType} n'a été trouvé avec cette recherche ({search})...", | ||||||
|   "components.TableEmpty.withoutFilter": "Aucun {contentType} n'a été trouvé...", |   "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.addAnItem": "Ajouter un élément...", | ||||||
|   "containers.Edit.pluginHeader.title.new": "Créer un document", |   "containers.Edit.pluginHeader.title.new": "Créer un document", | ||||||
|   "containers.Edit.clickToJump": "Cliquer pour voir l'entrée", |   "containers.Edit.clickToJump": "Cliquer pour voir l'entrée", | ||||||
|  | |||||||
| @ -704,7 +704,7 @@ const forms = { | |||||||
| 
 | 
 | ||||||
|         if (type === 'uid') { |         if (type === 'uid') { | ||||||
|           const options = Object.keys(attributes) |           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 })); |             .map(key => ({ id: key, value: key })); | ||||||
| 
 | 
 | ||||||
|           items[0].push({ |           items[0].push({ | ||||||
|  | |||||||
| @ -99,12 +99,10 @@ module.exports = { | |||||||
|   }, |   }, | ||||||
| 
 | 
 | ||||||
|   async getEnvironments(ctx) { |   async getEnvironments(ctx) { | ||||||
|     const environments = Object.keys(strapi.config.environments).map( |     const environments = Object.keys(strapi.config.environments).map(environment => ({ | ||||||
|       environment => ({ |  | ||||||
|       name: environment, |       name: environment, | ||||||
|       active: strapi.config.environment === environment, |       active: strapi.config.environment === environment, | ||||||
|       }) |     })); | ||||||
|     ); |  | ||||||
| 
 | 
 | ||||||
|     ctx.send({ environments }); |     ctx.send({ environments }); | ||||||
|   }, |   }, | ||||||
| @ -143,18 +141,14 @@ module.exports = { | |||||||
|   }, |   }, | ||||||
| 
 | 
 | ||||||
|   async find(ctx) { |   async find(ctx) { | ||||||
|     const data = await strapi.plugins['upload'].services.upload.fetchAll( |     const data = await strapi.plugins['upload'].services.upload.fetchAll(ctx.query); | ||||||
|       ctx.query |  | ||||||
|     ); |  | ||||||
| 
 | 
 | ||||||
|     // Send 200 `ok`
 |     // Send 200 `ok`
 | ||||||
|     ctx.send(data); |     ctx.send(data); | ||||||
|   }, |   }, | ||||||
| 
 | 
 | ||||||
|   async findOne(ctx) { |   async findOne(ctx) { | ||||||
|     const data = await strapi.plugins['upload'].services.upload.fetch( |     const data = await strapi.plugins['upload'].services.upload.fetch(ctx.params); | ||||||
|       ctx.params |  | ||||||
|     ); |  | ||||||
| 
 | 
 | ||||||
|     if (!data) { |     if (!data) { | ||||||
|       return ctx.notFound('file.notFound'); |       return ctx.notFound('file.notFound'); | ||||||
| @ -164,9 +158,7 @@ module.exports = { | |||||||
|   }, |   }, | ||||||
| 
 | 
 | ||||||
|   async count(ctx) { |   async count(ctx) { | ||||||
|     const data = await strapi.plugins['upload'].services.upload.count( |     const data = await strapi.plugins['upload'].services.upload.count(ctx.query); | ||||||
|       ctx.query |  | ||||||
|     ); |  | ||||||
| 
 | 
 | ||||||
|     ctx.send({ count: data }); |     ctx.send({ count: data }); | ||||||
|   }, |   }, | ||||||
| @ -208,9 +200,9 @@ const searchQueries = { | |||||||
|     return ({ id }) => { |     return ({ id }) => { | ||||||
|       return model |       return model | ||||||
|         .query(qb => { |         .query(qb => { | ||||||
|           qb.whereRaw('LOWER(hash) LIKE ?', [ |           qb.whereRaw('LOWER(hash) LIKE ?', [`%${id}%`]).orWhereRaw('LOWER(name) LIKE ?', [ | ||||||
|             `%${id}%`, |             `%${id}%`, | ||||||
|           ]).orWhereRaw('LOWER(name) LIKE ?', [`%${id}%`]); |           ]); | ||||||
|         }) |         }) | ||||||
|         .fetchAll() |         .fetchAll() | ||||||
|         .then(results => results.toJSON()); |         .then(results => results.toJSON()); | ||||||
|  | |||||||
| @ -33,9 +33,7 @@ module.exports = { | |||||||
| 
 | 
 | ||||||
|     const createBuffer = async stream => { |     const createBuffer = async stream => { | ||||||
|       const parts = await toArray(fs.createReadStream(stream.path)); |       const parts = await toArray(fs.createReadStream(stream.path)); | ||||||
|       const buffers = parts.map(part => |       const buffers = parts.map(part => (_.isBuffer(part) ? part : Buffer.from(part))); | ||||||
|         _.isBuffer(part) ? part : Buffer.from(part) |  | ||||||
|       ); |  | ||||||
| 
 | 
 | ||||||
|       const buffer = Buffer.concat(buffers); |       const buffer = Buffer.concat(buffers); | ||||||
| 
 | 
 | ||||||
| @ -44,10 +42,7 @@ module.exports = { | |||||||
|         name: stream.name, |         name: stream.name, | ||||||
|         sha256: niceHash(buffer), |         sha256: niceHash(buffer), | ||||||
|         hash: uuid().replace(/-/g, ''), |         hash: uuid().replace(/-/g, ''), | ||||||
|         ext: |         ext: stream.name.split('.').length > 1 ? `.${_.last(stream.name.split('.'))}` : '', | ||||||
|           stream.name.split('.').length > 1 |  | ||||||
|             ? `.${_.last(stream.name.split('.'))}` |  | ||||||
|             : '', |  | ||||||
|         buffer, |         buffer, | ||||||
|         mime: stream.type, |         mime: stream.type, | ||||||
|         size: (stream.size / 1000).toFixed(2), |         size: (stream.size / 1000).toFixed(2), | ||||||
|  | |||||||
							
								
								
									
										12
									
								
								yarn.lock
									
									
									
									
									
								
							
							
						
						
									
										12
									
								
								yarn.lock
									
									
									
									
									
								
							| @ -3322,9 +3322,9 @@ acorn-globals@^4.1.0: | |||||||
|     acorn-walk "^6.0.1" |     acorn-walk "^6.0.1" | ||||||
| 
 | 
 | ||||||
| acorn-jsx@^5.1.0: | acorn-jsx@^5.1.0: | ||||||
|   version "5.1.0" |   version "5.2.0" | ||||||
|   resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.1.0.tgz#294adb71b57398b0680015f0a38c563ee1db5384" |   resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.2.0.tgz#4c66069173d6fdd68ed85239fc256226182b2ebe" | ||||||
|   integrity sha512-tMUqwBWfLFbJbizRmEcWSLw6HnFzfdJs2sOJEOwwtVPMoH/0Ay+E703oZz78VSXZiiDcZrQ5XKjPIUQixhmgVw== |   integrity sha512-HiUX/+K2YpkpJ+SzBffkM/AQ2YE03S0U1kjTLVpoJdhZMOWy8qvXVN9JdLqv2QsaQ6MPYQIuNmwD8zOiYUofLQ== | ||||||
| 
 | 
 | ||||||
| acorn-walk@^6.0.1: | acorn-walk@^6.0.1: | ||||||
|   version "6.2.0" |   version "6.2.0" | ||||||
| @ -15555,9 +15555,9 @@ rsvp@^4.8.4: | |||||||
|   integrity sha512-nfMOlASu9OnRJo1mbEk2cz0D56a1MBNrJ7orjRZQG10XDyuvwksKbuXNp6qa+kbn839HwjwhBzhFmdsaEAfauA== |   integrity sha512-nfMOlASu9OnRJo1mbEk2cz0D56a1MBNrJ7orjRZQG10XDyuvwksKbuXNp6qa+kbn839HwjwhBzhFmdsaEAfauA== | ||||||
| 
 | 
 | ||||||
| run-async@^2.2.0: | run-async@^2.2.0: | ||||||
|   version "2.3.0" |   version "2.4.0" | ||||||
|   resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.3.0.tgz#0371ab4ae0bdd720d4166d7dfda64ff7a445a6c0" |   resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.4.0.tgz#e59054a5b86876cfae07f431d18cbaddc594f1e8" | ||||||
|   integrity sha1-A3GrSuC91yDUFm19/aZP96RFpsA= |   integrity sha512-xJTbh/d7Lm7SBhc1tNvTpeCHaEzoyxPrqNlvSdMfBTYwaY++UJFyXUOxAtsRUXjlqOfj8luNaR9vjCh4KeV+pg== | ||||||
|   dependencies: |   dependencies: | ||||||
|     is-promise "^2.1.0" |     is-promise "^2.1.0" | ||||||
| 
 | 
 | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 cyril lopez
						cyril lopez