mirror of
				https://github.com/strapi/strapi.git
				synced 2025-11-03 19:36:20 +00:00 
			
		
		
		
	Merge pull request #5286 from strapi/single-types/uid
add Uid attributes in CTB
This commit is contained in:
		
						commit
						da7ebced8d
					
				@ -7,12 +7,7 @@ import {
 | 
			
		||||
  useGlobalContext,
 | 
			
		||||
  PopUpWarning,
 | 
			
		||||
} from 'strapi-helper-plugin';
 | 
			
		||||
import {
 | 
			
		||||
  useHistory,
 | 
			
		||||
  useLocation,
 | 
			
		||||
  useRouteMatch,
 | 
			
		||||
  Redirect,
 | 
			
		||||
} from 'react-router-dom';
 | 
			
		||||
import { useHistory, useLocation, useRouteMatch, Redirect } from 'react-router-dom';
 | 
			
		||||
import DataManagerContext from '../../contexts/DataManagerContext';
 | 
			
		||||
import getTrad from '../../utils/getTrad';
 | 
			
		||||
import makeUnique from '../../utils/makeUnique';
 | 
			
		||||
@ -55,21 +50,17 @@ const DataManagerProvider = ({ allIcons, children }) => {
 | 
			
		||||
  } = reducerState.toJS();
 | 
			
		||||
  const { pathname } = useLocation();
 | 
			
		||||
  const { push } = useHistory();
 | 
			
		||||
  const contentTypeMatch = useRouteMatch(
 | 
			
		||||
    `/plugins/${pluginId}/content-types/:uid`
 | 
			
		||||
  );
 | 
			
		||||
  const contentTypeMatch = useRouteMatch(`/plugins/${pluginId}/content-types/:uid`);
 | 
			
		||||
  const componentMatch = useRouteMatch(
 | 
			
		||||
    `/plugins/${pluginId}/component-categories/:categoryUid/:componentUid`
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  const formatMessageRef = useRef();
 | 
			
		||||
  formatMessageRef.current = formatMessage;
 | 
			
		||||
  const isInDevelopmentMode =
 | 
			
		||||
    currentEnvironment === 'development' && autoReload;
 | 
			
		||||
  const isInDevelopmentMode = currentEnvironment === 'development' && autoReload;
 | 
			
		||||
 | 
			
		||||
  const isInContentTypeView = contentTypeMatch !== null;
 | 
			
		||||
  const firstKeyToMainSchema = isInContentTypeView
 | 
			
		||||
    ? 'contentType'
 | 
			
		||||
    : 'component';
 | 
			
		||||
  const firstKeyToMainSchema = isInContentTypeView ? 'contentType' : 'component';
 | 
			
		||||
  const currentUid = isInContentTypeView
 | 
			
		||||
    ? get(contentTypeMatch, 'params.uid', null)
 | 
			
		||||
    : get(componentMatch, 'params.componentUid', null);
 | 
			
		||||
@ -80,10 +71,7 @@ const DataManagerProvider = ({ allIcons, children }) => {
 | 
			
		||||
 | 
			
		||||
  getDataRef.current = async () => {
 | 
			
		||||
    try {
 | 
			
		||||
      const [
 | 
			
		||||
        { data: componentsArray },
 | 
			
		||||
        { data: contentTypesArray },
 | 
			
		||||
      ] = await Promise.all(
 | 
			
		||||
      const [{ data: componentsArray }, { data: contentTypesArray }] = await Promise.all(
 | 
			
		||||
        ['components', 'content-types'].map(endPoint => {
 | 
			
		||||
          return request(`/${pluginId}/${endPoint}`, {
 | 
			
		||||
            method: 'GET',
 | 
			
		||||
@ -118,11 +106,11 @@ const DataManagerProvider = ({ allIcons, children }) => {
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    // We need to set the modifiedData after the data has been retrieved
 | 
			
		||||
    // and also on pathname change
 | 
			
		||||
    if (!isLoading) {
 | 
			
		||||
    if (!isLoading && currentUid) {
 | 
			
		||||
      setModifiedData();
 | 
			
		||||
    }
 | 
			
		||||
    // eslint-disable-next-line react-hooks/exhaustive-deps
 | 
			
		||||
  }, [isLoading, pathname]);
 | 
			
		||||
  }, [isLoading, pathname, currentUid]);
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    if (currentEnvironment === 'development' && !autoReload) {
 | 
			
		||||
@ -135,8 +123,7 @@ const DataManagerProvider = ({ allIcons, children }) => {
 | 
			
		||||
  }, [autoReload, currentEnvironment]);
 | 
			
		||||
 | 
			
		||||
  const didModifiedComponents =
 | 
			
		||||
    getCreatedAndModifiedComponents(modifiedData.components || {}, components)
 | 
			
		||||
      .length > 0;
 | 
			
		||||
    getCreatedAndModifiedComponents(modifiedData.components || {}, components).length > 0;
 | 
			
		||||
 | 
			
		||||
  const addAttribute = (
 | 
			
		||||
    attributeToSet,
 | 
			
		||||
@ -158,10 +145,7 @@ const DataManagerProvider = ({ allIcons, children }) => {
 | 
			
		||||
    });
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const addCreatedComponentToDynamicZone = (
 | 
			
		||||
    dynamicZoneTarget,
 | 
			
		||||
    componentsToAdd
 | 
			
		||||
  ) => {
 | 
			
		||||
  const addCreatedComponentToDynamicZone = (dynamicZoneTarget, componentsToAdd) => {
 | 
			
		||||
    dispatch({
 | 
			
		||||
      type: 'ADD_CREATED_COMPONENT_TO_DYNAMIC_ZONE',
 | 
			
		||||
      dynamicZoneTarget,
 | 
			
		||||
@ -181,10 +165,7 @@ const DataManagerProvider = ({ allIcons, children }) => {
 | 
			
		||||
    componentCategory,
 | 
			
		||||
    shouldAddComponentToData = false
 | 
			
		||||
  ) => {
 | 
			
		||||
    const type =
 | 
			
		||||
      schemaType === 'contentType'
 | 
			
		||||
        ? 'CREATE_SCHEMA'
 | 
			
		||||
        : 'CREATE_COMPONENT_SCHEMA';
 | 
			
		||||
    const type = schemaType === 'contentType' ? 'CREATE_SCHEMA' : 'CREATE_COMPONENT_SCHEMA';
 | 
			
		||||
 | 
			
		||||
    dispatch({
 | 
			
		||||
      type,
 | 
			
		||||
@ -204,15 +185,9 @@ const DataManagerProvider = ({ allIcons, children }) => {
 | 
			
		||||
    });
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const removeAttribute = (
 | 
			
		||||
    mainDataKey,
 | 
			
		||||
    attributeToRemoveName,
 | 
			
		||||
    componentUid = ''
 | 
			
		||||
  ) => {
 | 
			
		||||
  const removeAttribute = (mainDataKey, attributeToRemoveName, componentUid = '') => {
 | 
			
		||||
    const type =
 | 
			
		||||
      mainDataKey === 'components'
 | 
			
		||||
        ? 'REMOVE_FIELD_FROM_DISPLAYED_COMPONENT'
 | 
			
		||||
        : 'REMOVE_FIELD';
 | 
			
		||||
      mainDataKey === 'components' ? 'REMOVE_FIELD_FROM_DISPLAYED_COMPONENT' : 'REMOVE_FIELD';
 | 
			
		||||
 | 
			
		||||
    if (mainDataKey === 'contentType') {
 | 
			
		||||
      emitEvent('willDeleteFieldOfContentType');
 | 
			
		||||
@ -253,17 +228,11 @@ const DataManagerProvider = ({ allIcons, children }) => {
 | 
			
		||||
  const deleteData = async () => {
 | 
			
		||||
    try {
 | 
			
		||||
      const requestURL = `/${pluginId}/${endPoint}/${currentUid}`;
 | 
			
		||||
      const isTemporary = get(
 | 
			
		||||
        modifiedData,
 | 
			
		||||
        [firstKeyToMainSchema, 'isTemporary'],
 | 
			
		||||
        false
 | 
			
		||||
      );
 | 
			
		||||
      const isTemporary = get(modifiedData, [firstKeyToMainSchema, 'isTemporary'], false);
 | 
			
		||||
      const userConfirm = window.confirm(
 | 
			
		||||
        formatMessage({
 | 
			
		||||
          id: getTrad(
 | 
			
		||||
            `popUpWarning.bodyMessage.${
 | 
			
		||||
              isInContentTypeView ? 'contentType' : 'component'
 | 
			
		||||
            }.delete`
 | 
			
		||||
            `popUpWarning.bodyMessage.${isInContentTypeView ? 'contentType' : 'component'}.delete`
 | 
			
		||||
          ),
 | 
			
		||||
        })
 | 
			
		||||
      );
 | 
			
		||||
@ -338,9 +307,7 @@ const DataManagerProvider = ({ allIcons, children }) => {
 | 
			
		||||
 | 
			
		||||
  const getAllNestedComponents = () => {
 | 
			
		||||
    const appNestedCompo = retrieveNestedComponents(components);
 | 
			
		||||
    const editingDataNestedCompos = retrieveNestedComponents(
 | 
			
		||||
      modifiedData.components || {}
 | 
			
		||||
    );
 | 
			
		||||
    const editingDataNestedCompos = retrieveNestedComponents(modifiedData.components || {});
 | 
			
		||||
 | 
			
		||||
    return makeUnique([...editingDataNestedCompos, ...appNestedCompo]);
 | 
			
		||||
  };
 | 
			
		||||
@ -370,10 +337,7 @@ const DataManagerProvider = ({ allIcons, children }) => {
 | 
			
		||||
      isInContentTypeView
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    const dataShape = orderAllDataAttributesWithImmutable(
 | 
			
		||||
      newSchemaToSet,
 | 
			
		||||
      isInContentTypeView
 | 
			
		||||
    );
 | 
			
		||||
    const dataShape = orderAllDataAttributesWithImmutable(newSchemaToSet, isInContentTypeView);
 | 
			
		||||
 | 
			
		||||
    // This prevents from losing the created content type or component when clicking on the link from the left menu
 | 
			
		||||
    const hasJustCreatedSchema =
 | 
			
		||||
@ -401,11 +365,7 @@ const DataManagerProvider = ({ allIcons, children }) => {
 | 
			
		||||
 | 
			
		||||
  const submitData = async additionalContentTypeData => {
 | 
			
		||||
    try {
 | 
			
		||||
      const isCreating = get(
 | 
			
		||||
        modifiedData,
 | 
			
		||||
        [firstKeyToMainSchema, 'isTemporary'],
 | 
			
		||||
        false
 | 
			
		||||
      );
 | 
			
		||||
      const isCreating = get(modifiedData, [firstKeyToMainSchema, 'isTemporary'], false);
 | 
			
		||||
      const body = {
 | 
			
		||||
        components: getComponentsToPost(
 | 
			
		||||
          modifiedData.components,
 | 
			
		||||
@ -505,14 +465,11 @@ const DataManagerProvider = ({ allIcons, children }) => {
 | 
			
		||||
      value={{
 | 
			
		||||
        addAttribute,
 | 
			
		||||
        addCreatedComponentToDynamicZone,
 | 
			
		||||
        allComponentsCategories: retrieveSpecificInfoFromComponents(
 | 
			
		||||
          components,
 | 
			
		||||
          ['category']
 | 
			
		||||
        ),
 | 
			
		||||
        allComponentsIconAlreadyTaken: retrieveSpecificInfoFromComponents(
 | 
			
		||||
          components,
 | 
			
		||||
          ['schema', 'icon']
 | 
			
		||||
        ),
 | 
			
		||||
        allComponentsCategories: retrieveSpecificInfoFromComponents(components, ['category']),
 | 
			
		||||
        allComponentsIconAlreadyTaken: retrieveSpecificInfoFromComponents(components, [
 | 
			
		||||
          'schema',
 | 
			
		||||
          'icon',
 | 
			
		||||
        ]),
 | 
			
		||||
        allIcons,
 | 
			
		||||
        changeDynamicZoneComponents,
 | 
			
		||||
        components,
 | 
			
		||||
 | 
			
		||||
@ -34,8 +34,7 @@ const addComponentsToState = (state, componentToAddUid, objToUpdate) => {
 | 
			
		||||
  const isTemporaryComponent = componentToAdd.get('isTemporary');
 | 
			
		||||
  const componentToAddSchema = componentToAdd.getIn(['schema', 'attributes']);
 | 
			
		||||
  const hasComponentAlreadyBeenAdded =
 | 
			
		||||
    state.getIn(['modifiedData', 'components', componentToAddUid]) !==
 | 
			
		||||
    undefined;
 | 
			
		||||
    state.getIn(['modifiedData', 'components', componentToAddUid]) !== undefined;
 | 
			
		||||
 | 
			
		||||
  // created components are already in the modifiedData.components
 | 
			
		||||
  // We don't add them because all modifications will be lost
 | 
			
		||||
@ -52,17 +51,13 @@ const addComponentsToState = (state, componentToAddUid, objToUpdate) => {
 | 
			
		||||
 | 
			
		||||
  // We need to add the nested components to the modifiedData.components as well
 | 
			
		||||
  nestedComponents.forEach(componentUid => {
 | 
			
		||||
    const isTemporary =
 | 
			
		||||
      state.getIn(['components', componentUid, 'isTemporary']) || false;
 | 
			
		||||
    const isTemporary = state.getIn(['components', componentUid, 'isTemporary']) || false;
 | 
			
		||||
    const hasNestedComponentAlreadyBeenAdded =
 | 
			
		||||
      state.getIn(['modifiedData', 'components', componentUid]) !== undefined;
 | 
			
		||||
 | 
			
		||||
    // Same logic here otherwise we will lose the modifications added to the components
 | 
			
		||||
    if (!isTemporary && !hasNestedComponentAlreadyBeenAdded) {
 | 
			
		||||
      newObj = newObj.set(
 | 
			
		||||
        componentUid,
 | 
			
		||||
        state.getIn(['components', componentUid])
 | 
			
		||||
      );
 | 
			
		||||
      newObj = newObj.set(componentUid, state.getIn(['components', componentUid]));
 | 
			
		||||
    }
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
@ -84,52 +79,42 @@ const reducer = (state, action) => {
 | 
			
		||||
        : [forTarget, targetUid];
 | 
			
		||||
 | 
			
		||||
      return state
 | 
			
		||||
        .updateIn(
 | 
			
		||||
          ['modifiedData', ...pathToDataToEdit, 'schema', 'attributes', name],
 | 
			
		||||
          () => {
 | 
			
		||||
            return fromJS(rest);
 | 
			
		||||
        .updateIn(['modifiedData', ...pathToDataToEdit, 'schema', 'attributes', name], () => {
 | 
			
		||||
          return fromJS(rest);
 | 
			
		||||
        })
 | 
			
		||||
        .updateIn(['modifiedData', ...pathToDataToEdit, 'schema', 'attributes'], obj => {
 | 
			
		||||
          const type = get(rest, 'type', 'relation');
 | 
			
		||||
          const target = get(rest, 'target', null);
 | 
			
		||||
          const nature = get(rest, 'nature', null);
 | 
			
		||||
          const currentUid = state.getIn(['modifiedData', ...pathToDataToEdit, 'uid']);
 | 
			
		||||
 | 
			
		||||
          // When the user in creating a relation with the same content type we need to create another attribute
 | 
			
		||||
          // that is the opposite of the created one
 | 
			
		||||
          if (
 | 
			
		||||
            type === 'relation' &&
 | 
			
		||||
            nature !== 'oneWay' &&
 | 
			
		||||
            nature !== 'manyWay' &&
 | 
			
		||||
            target === currentUid
 | 
			
		||||
          ) {
 | 
			
		||||
            const oppositeAttribute = {
 | 
			
		||||
              nature: getOppositeNature(nature),
 | 
			
		||||
              target,
 | 
			
		||||
              unique: rest.unique,
 | 
			
		||||
              // Leave this if we allow the required on the relation
 | 
			
		||||
              // required: rest.required,
 | 
			
		||||
              dominant: nature === 'manyToMany' ? !rest.dominant : null,
 | 
			
		||||
              targetAttribute: name,
 | 
			
		||||
              columnName: rest.targetColumnName,
 | 
			
		||||
              targetColumnName: rest.columnName,
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            return obj.update(rest.targetAttribute, () => {
 | 
			
		||||
              return fromJS(oppositeAttribute);
 | 
			
		||||
            });
 | 
			
		||||
          }
 | 
			
		||||
        )
 | 
			
		||||
        .updateIn(
 | 
			
		||||
          ['modifiedData', ...pathToDataToEdit, 'schema', 'attributes'],
 | 
			
		||||
          obj => {
 | 
			
		||||
            const type = get(rest, 'type', 'relation');
 | 
			
		||||
            const target = get(rest, 'target', null);
 | 
			
		||||
            const nature = get(rest, 'nature', null);
 | 
			
		||||
            const currentUid = state.getIn([
 | 
			
		||||
              'modifiedData',
 | 
			
		||||
              ...pathToDataToEdit,
 | 
			
		||||
              'uid',
 | 
			
		||||
            ]);
 | 
			
		||||
 | 
			
		||||
            // When the user in creating a relation with the same content type we need to create another attribute
 | 
			
		||||
            // that is the opposite of the created one
 | 
			
		||||
            if (
 | 
			
		||||
              type === 'relation' &&
 | 
			
		||||
              nature !== 'oneWay' &&
 | 
			
		||||
              nature !== 'manyWay' &&
 | 
			
		||||
              target === currentUid
 | 
			
		||||
            ) {
 | 
			
		||||
              const oppositeAttribute = {
 | 
			
		||||
                nature: getOppositeNature(nature),
 | 
			
		||||
                target,
 | 
			
		||||
                unique: rest.unique,
 | 
			
		||||
                // Leave this if we allow the required on the relation
 | 
			
		||||
                // required: rest.required,
 | 
			
		||||
                dominant: nature === 'manyToMany' ? !rest.dominant : null,
 | 
			
		||||
                targetAttribute: name,
 | 
			
		||||
                columnName: rest.targetColumnName,
 | 
			
		||||
                targetColumnName: rest.columnName,
 | 
			
		||||
              };
 | 
			
		||||
 | 
			
		||||
              return obj.update(rest.targetAttribute, () => {
 | 
			
		||||
                return fromJS(oppositeAttribute);
 | 
			
		||||
              });
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return obj;
 | 
			
		||||
          }
 | 
			
		||||
        )
 | 
			
		||||
          return obj;
 | 
			
		||||
        })
 | 
			
		||||
        .updateIn(['modifiedData', 'components'], existingCompos => {
 | 
			
		||||
          if (action.shouldAddComponentToData) {
 | 
			
		||||
            return addComponentsToState(state, rest.component, existingCompos);
 | 
			
		||||
@ -142,14 +127,7 @@ const reducer = (state, action) => {
 | 
			
		||||
      const { dynamicZoneTarget, componentsToAdd } = action;
 | 
			
		||||
 | 
			
		||||
      return state.updateIn(
 | 
			
		||||
        [
 | 
			
		||||
          'modifiedData',
 | 
			
		||||
          'contentType',
 | 
			
		||||
          'schema',
 | 
			
		||||
          'attributes',
 | 
			
		||||
          dynamicZoneTarget,
 | 
			
		||||
          'components',
 | 
			
		||||
        ],
 | 
			
		||||
        ['modifiedData', 'contentType', 'schema', 'attributes', dynamicZoneTarget, 'components'],
 | 
			
		||||
        list => {
 | 
			
		||||
          return list.concat(componentsToAdd);
 | 
			
		||||
        }
 | 
			
		||||
@ -165,14 +143,7 @@ const reducer = (state, action) => {
 | 
			
		||||
 | 
			
		||||
      return state
 | 
			
		||||
        .updateIn(
 | 
			
		||||
          [
 | 
			
		||||
            'modifiedData',
 | 
			
		||||
            'contentType',
 | 
			
		||||
            'schema',
 | 
			
		||||
            'attributes',
 | 
			
		||||
            dynamicZoneTarget,
 | 
			
		||||
            'components',
 | 
			
		||||
          ],
 | 
			
		||||
          ['modifiedData', 'contentType', 'schema', 'attributes', dynamicZoneTarget, 'components'],
 | 
			
		||||
          list => {
 | 
			
		||||
            return fromJS(makeUnique([...list.toJS(), ...newComponents]));
 | 
			
		||||
          }
 | 
			
		||||
@ -196,9 +167,7 @@ const reducer = (state, action) => {
 | 
			
		||||
        },
 | 
			
		||||
      };
 | 
			
		||||
 | 
			
		||||
      return state.updateIn(['contentTypes', action.uid], () =>
 | 
			
		||||
        fromJS(newSchema)
 | 
			
		||||
      );
 | 
			
		||||
      return state.updateIn(['contentTypes', action.uid], () => fromJS(newSchema));
 | 
			
		||||
    }
 | 
			
		||||
    case 'CREATE_COMPONENT_SCHEMA': {
 | 
			
		||||
      const newSchema = {
 | 
			
		||||
@ -214,14 +183,10 @@ const reducer = (state, action) => {
 | 
			
		||||
      if (action.shouldAddComponentToData) {
 | 
			
		||||
        return state
 | 
			
		||||
          .updateIn(['components', action.uid], () => fromJS(newSchema))
 | 
			
		||||
          .updateIn(['modifiedData', 'components', action.uid], () =>
 | 
			
		||||
            fromJS(newSchema)
 | 
			
		||||
          );
 | 
			
		||||
          .updateIn(['modifiedData', 'components', action.uid], () => fromJS(newSchema));
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      return state.updateIn(['components', action.uid], () =>
 | 
			
		||||
        fromJS(newSchema)
 | 
			
		||||
      );
 | 
			
		||||
      return state.updateIn(['components', action.uid], () => fromJS(newSchema));
 | 
			
		||||
    }
 | 
			
		||||
    case 'DELETE_NOT_SAVED_TYPE': {
 | 
			
		||||
      // Doing so will also reset the modified and the initial data
 | 
			
		||||
@ -243,144 +208,129 @@ const reducer = (state, action) => {
 | 
			
		||||
        ? [forTarget]
 | 
			
		||||
        : [forTarget, targetUid];
 | 
			
		||||
 | 
			
		||||
      return newState.updateIn(
 | 
			
		||||
        ['modifiedData', ...pathToDataToEdit, 'schema'],
 | 
			
		||||
        obj => {
 | 
			
		||||
          let oppositeAttributeNameToRemove = null;
 | 
			
		||||
          let oppositeAttributeNameToUpdate = null;
 | 
			
		||||
          let oppositeAttributeNameToCreateBecauseOfNatureChange = null;
 | 
			
		||||
          let oppositeAttributeToCreate = null;
 | 
			
		||||
      return newState.updateIn(['modifiedData', ...pathToDataToEdit, 'schema'], obj => {
 | 
			
		||||
        let oppositeAttributeNameToRemove = null;
 | 
			
		||||
        let oppositeAttributeNameToUpdate = null;
 | 
			
		||||
        let oppositeAttributeNameToCreateBecauseOfNatureChange = null;
 | 
			
		||||
        let oppositeAttributeToCreate = null;
 | 
			
		||||
 | 
			
		||||
          const newObj = OrderedMap(
 | 
			
		||||
            obj
 | 
			
		||||
              .get('attributes')
 | 
			
		||||
              .keySeq()
 | 
			
		||||
              .reduce((acc, current) => {
 | 
			
		||||
                const isEditingCurrentAttribute =
 | 
			
		||||
                  current === initialAttributeName;
 | 
			
		||||
        const newObj = OrderedMap(
 | 
			
		||||
          obj
 | 
			
		||||
            .get('attributes')
 | 
			
		||||
            .keySeq()
 | 
			
		||||
            .reduce((acc, current) => {
 | 
			
		||||
              const isEditingCurrentAttribute = current === initialAttributeName;
 | 
			
		||||
 | 
			
		||||
                if (isEditingCurrentAttribute) {
 | 
			
		||||
                  const currentUid = state.getIn([
 | 
			
		||||
                    'modifiedData',
 | 
			
		||||
                    ...pathToDataToEdit,
 | 
			
		||||
                    'uid',
 | 
			
		||||
                  ]);
 | 
			
		||||
                  const isEditingRelation = has(initialAttribute, 'nature');
 | 
			
		||||
                  const didChangeTargetRelation =
 | 
			
		||||
                    initialAttribute.target !== rest.target;
 | 
			
		||||
                  const didCreateInternalRelation = rest.target === currentUid;
 | 
			
		||||
                  const nature = rest.nature;
 | 
			
		||||
                  const initialNature = initialAttribute.nature;
 | 
			
		||||
                  const hadInternalRelation =
 | 
			
		||||
                    initialAttribute.target === currentUid;
 | 
			
		||||
                  const didChangeRelationNature =
 | 
			
		||||
                    initialAttribute.nature !== nature;
 | 
			
		||||
                  const shouldRemoveOppositeAttributeBecauseOfTargetChange =
 | 
			
		||||
                    didChangeTargetRelation &&
 | 
			
		||||
                    !didCreateInternalRelation &&
 | 
			
		||||
                    hadInternalRelation &&
 | 
			
		||||
                    isEditingRelation;
 | 
			
		||||
                  const shouldRemoveOppositeAttributeBecauseOfNatureChange =
 | 
			
		||||
                    didChangeRelationNature &&
 | 
			
		||||
                    hadInternalRelation &&
 | 
			
		||||
                    ['oneWay', 'manyWay'].includes(nature) &&
 | 
			
		||||
                    isEditingRelation;
 | 
			
		||||
                  const shouldUpdateOppositeAttributeBecauseOfNatureChange =
 | 
			
		||||
                    !ONE_SIDE_RELATIONS.includes(initialNature) &&
 | 
			
		||||
                    !ONE_SIDE_RELATIONS.includes(nature) &&
 | 
			
		||||
                    hadInternalRelation &&
 | 
			
		||||
                    didCreateInternalRelation &&
 | 
			
		||||
                    isEditingRelation;
 | 
			
		||||
                  const shouldCreateOppositeAttributeBecauseOfNatureChange =
 | 
			
		||||
                    ONE_SIDE_RELATIONS.includes(initialNature) &&
 | 
			
		||||
                    !ONE_SIDE_RELATIONS.includes(nature) &&
 | 
			
		||||
                    hadInternalRelation &&
 | 
			
		||||
                    didCreateInternalRelation &&
 | 
			
		||||
                    isEditingRelation;
 | 
			
		||||
                  const shouldCreateOppositeAttributeBecauseOfTargetChange =
 | 
			
		||||
                    didChangeTargetRelation &&
 | 
			
		||||
                    didCreateInternalRelation &&
 | 
			
		||||
                    !ONE_SIDE_RELATIONS.includes(nature);
 | 
			
		||||
              if (isEditingCurrentAttribute) {
 | 
			
		||||
                const currentUid = state.getIn(['modifiedData', ...pathToDataToEdit, 'uid']);
 | 
			
		||||
                const isEditingRelation = has(initialAttribute, 'nature');
 | 
			
		||||
                const didChangeTargetRelation = initialAttribute.target !== rest.target;
 | 
			
		||||
                const didCreateInternalRelation = rest.target === currentUid;
 | 
			
		||||
                const nature = rest.nature;
 | 
			
		||||
                const initialNature = initialAttribute.nature;
 | 
			
		||||
                const hadInternalRelation = initialAttribute.target === currentUid;
 | 
			
		||||
                const didChangeRelationNature = initialAttribute.nature !== nature;
 | 
			
		||||
                const shouldRemoveOppositeAttributeBecauseOfTargetChange =
 | 
			
		||||
                  didChangeTargetRelation &&
 | 
			
		||||
                  !didCreateInternalRelation &&
 | 
			
		||||
                  hadInternalRelation &&
 | 
			
		||||
                  isEditingRelation;
 | 
			
		||||
                const shouldRemoveOppositeAttributeBecauseOfNatureChange =
 | 
			
		||||
                  didChangeRelationNature &&
 | 
			
		||||
                  hadInternalRelation &&
 | 
			
		||||
                  ['oneWay', 'manyWay'].includes(nature) &&
 | 
			
		||||
                  isEditingRelation;
 | 
			
		||||
                const shouldUpdateOppositeAttributeBecauseOfNatureChange =
 | 
			
		||||
                  !ONE_SIDE_RELATIONS.includes(initialNature) &&
 | 
			
		||||
                  !ONE_SIDE_RELATIONS.includes(nature) &&
 | 
			
		||||
                  hadInternalRelation &&
 | 
			
		||||
                  didCreateInternalRelation &&
 | 
			
		||||
                  isEditingRelation;
 | 
			
		||||
                const shouldCreateOppositeAttributeBecauseOfNatureChange =
 | 
			
		||||
                  ONE_SIDE_RELATIONS.includes(initialNature) &&
 | 
			
		||||
                  !ONE_SIDE_RELATIONS.includes(nature) &&
 | 
			
		||||
                  hadInternalRelation &&
 | 
			
		||||
                  didCreateInternalRelation &&
 | 
			
		||||
                  isEditingRelation;
 | 
			
		||||
                const shouldCreateOppositeAttributeBecauseOfTargetChange =
 | 
			
		||||
                  didChangeTargetRelation &&
 | 
			
		||||
                  didCreateInternalRelation &&
 | 
			
		||||
                  !ONE_SIDE_RELATIONS.includes(nature);
 | 
			
		||||
 | 
			
		||||
                  // Update the opposite attribute name so it is removed at the end of the loop
 | 
			
		||||
                // Update the opposite attribute name so it is removed at the end of the loop
 | 
			
		||||
                if (
 | 
			
		||||
                  shouldRemoveOppositeAttributeBecauseOfTargetChange ||
 | 
			
		||||
                  shouldRemoveOppositeAttributeBecauseOfNatureChange
 | 
			
		||||
                ) {
 | 
			
		||||
                  oppositeAttributeNameToRemove = initialAttribute.targetAttribute;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                // Set the opposite attribute that will be updated when the loop attribute matches the name
 | 
			
		||||
                if (
 | 
			
		||||
                  shouldUpdateOppositeAttributeBecauseOfNatureChange ||
 | 
			
		||||
                  shouldCreateOppositeAttributeBecauseOfNatureChange ||
 | 
			
		||||
                  shouldCreateOppositeAttributeBecauseOfTargetChange
 | 
			
		||||
                ) {
 | 
			
		||||
                  oppositeAttributeNameToUpdate = initialAttribute.targetAttribute;
 | 
			
		||||
                  oppositeAttributeNameToCreateBecauseOfNatureChange = rest.targetAttribute;
 | 
			
		||||
 | 
			
		||||
                  oppositeAttributeToCreate = {
 | 
			
		||||
                    nature: getOppositeNature(rest.nature),
 | 
			
		||||
                    target: rest.target,
 | 
			
		||||
                    unique: rest.unique,
 | 
			
		||||
                    // Leave this if we allow the required on the relation
 | 
			
		||||
                    // required: rest.required,
 | 
			
		||||
                    dominant: rest.nature === 'manyToMany' ? !rest.dominant : null,
 | 
			
		||||
                    targetAttribute: name,
 | 
			
		||||
                    columnName: rest.targetColumnName,
 | 
			
		||||
                    targetColumnName: rest.columnName,
 | 
			
		||||
                  };
 | 
			
		||||
 | 
			
		||||
                  // First update the current attribute with the value
 | 
			
		||||
                  acc[name] = fromJS(rest);
 | 
			
		||||
 | 
			
		||||
                  // Then (if needed) create the opposite attribute the case is changing the relation from
 | 
			
		||||
                  // We do it here so keep the order of the attributes
 | 
			
		||||
                  // oneWay || manyWay to something another relation
 | 
			
		||||
                  if (
 | 
			
		||||
                    shouldRemoveOppositeAttributeBecauseOfTargetChange ||
 | 
			
		||||
                    shouldRemoveOppositeAttributeBecauseOfNatureChange
 | 
			
		||||
                  ) {
 | 
			
		||||
                    oppositeAttributeNameToRemove =
 | 
			
		||||
                      initialAttribute.targetAttribute;
 | 
			
		||||
                  }
 | 
			
		||||
 | 
			
		||||
                  // Set the opposite attribute that will be updated when the loop attribute matches the name
 | 
			
		||||
                  if (
 | 
			
		||||
                    shouldUpdateOppositeAttributeBecauseOfNatureChange ||
 | 
			
		||||
                    shouldCreateOppositeAttributeBecauseOfNatureChange ||
 | 
			
		||||
                    shouldCreateOppositeAttributeBecauseOfTargetChange
 | 
			
		||||
                  ) {
 | 
			
		||||
                    oppositeAttributeNameToUpdate =
 | 
			
		||||
                      initialAttribute.targetAttribute;
 | 
			
		||||
                    oppositeAttributeNameToCreateBecauseOfNatureChange =
 | 
			
		||||
                      rest.targetAttribute;
 | 
			
		||||
                    acc[oppositeAttributeNameToCreateBecauseOfNatureChange] = fromJS(
 | 
			
		||||
                      oppositeAttributeToCreate
 | 
			
		||||
                    );
 | 
			
		||||
 | 
			
		||||
                    oppositeAttributeToCreate = {
 | 
			
		||||
                      nature: getOppositeNature(rest.nature),
 | 
			
		||||
                      target: rest.target,
 | 
			
		||||
                      unique: rest.unique,
 | 
			
		||||
                      // Leave this if we allow the required on the relation
 | 
			
		||||
                      // required: rest.required,
 | 
			
		||||
                      dominant:
 | 
			
		||||
                        rest.nature === 'manyToMany' ? !rest.dominant : null,
 | 
			
		||||
                      targetAttribute: name,
 | 
			
		||||
                      columnName: rest.targetColumnName,
 | 
			
		||||
                      targetColumnName: rest.columnName,
 | 
			
		||||
                    };
 | 
			
		||||
 | 
			
		||||
                    // First update the current attribute with the value
 | 
			
		||||
                    acc[name] = fromJS(rest);
 | 
			
		||||
 | 
			
		||||
                    // Then (if needed) create the opposite attribute the case is changing the relation from
 | 
			
		||||
                    // We do it here so keep the order of the attributes
 | 
			
		||||
                    // oneWay || manyWay to something another relation
 | 
			
		||||
                    if (
 | 
			
		||||
                      shouldCreateOppositeAttributeBecauseOfNatureChange ||
 | 
			
		||||
                      shouldCreateOppositeAttributeBecauseOfTargetChange
 | 
			
		||||
                    ) {
 | 
			
		||||
                      acc[
 | 
			
		||||
                        oppositeAttributeNameToCreateBecauseOfNatureChange
 | 
			
		||||
                      ] = fromJS(oppositeAttributeToCreate);
 | 
			
		||||
 | 
			
		||||
                      oppositeAttributeToCreate = null;
 | 
			
		||||
                      oppositeAttributeNameToCreateBecauseOfNatureChange = null;
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    return acc;
 | 
			
		||||
                    oppositeAttributeToCreate = null;
 | 
			
		||||
                    oppositeAttributeNameToCreateBecauseOfNatureChange = null;
 | 
			
		||||
                  }
 | 
			
		||||
 | 
			
		||||
                  acc[name] = fromJS(rest);
 | 
			
		||||
                } else if (current === oppositeAttributeNameToUpdate) {
 | 
			
		||||
                  acc[
 | 
			
		||||
                    oppositeAttributeNameToCreateBecauseOfNatureChange
 | 
			
		||||
                  ] = fromJS(oppositeAttributeToCreate);
 | 
			
		||||
                } else {
 | 
			
		||||
                  acc[current] = obj.getIn(['attributes', current]);
 | 
			
		||||
                  return acc;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                return acc;
 | 
			
		||||
              }, {})
 | 
			
		||||
          );
 | 
			
		||||
                acc[name] = fromJS(rest);
 | 
			
		||||
              } else if (current === oppositeAttributeNameToUpdate) {
 | 
			
		||||
                acc[oppositeAttributeNameToCreateBecauseOfNatureChange] = fromJS(
 | 
			
		||||
                  oppositeAttributeToCreate
 | 
			
		||||
                );
 | 
			
		||||
              } else {
 | 
			
		||||
                acc[current] = obj.getIn(['attributes', current]);
 | 
			
		||||
              }
 | 
			
		||||
 | 
			
		||||
          let updatedObj;
 | 
			
		||||
              return acc;
 | 
			
		||||
            }, {})
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
          // Remove the opposite attribute
 | 
			
		||||
          if (oppositeAttributeNameToRemove !== null) {
 | 
			
		||||
            updatedObj = newObj.remove(oppositeAttributeNameToRemove);
 | 
			
		||||
          } else {
 | 
			
		||||
            updatedObj = newObj;
 | 
			
		||||
          }
 | 
			
		||||
        let updatedObj;
 | 
			
		||||
 | 
			
		||||
          return obj.set('attributes', updatedObj);
 | 
			
		||||
        // Remove the opposite attribute
 | 
			
		||||
        if (oppositeAttributeNameToRemove !== null) {
 | 
			
		||||
          updatedObj = newObj.remove(oppositeAttributeNameToRemove);
 | 
			
		||||
        } else {
 | 
			
		||||
          updatedObj = newObj;
 | 
			
		||||
        }
 | 
			
		||||
      );
 | 
			
		||||
 | 
			
		||||
        return obj.set('attributes', updatedObj);
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    case 'GET_DATA_SUCCEEDED': {
 | 
			
		||||
@ -418,35 +368,18 @@ const reducer = (state, action) => {
 | 
			
		||||
      ]);
 | 
			
		||||
    case 'REMOVE_FIELD': {
 | 
			
		||||
      const { mainDataKey, attributeToRemoveName } = action;
 | 
			
		||||
      const pathToAttributes = [
 | 
			
		||||
        'modifiedData',
 | 
			
		||||
        mainDataKey,
 | 
			
		||||
        'schema',
 | 
			
		||||
        'attributes',
 | 
			
		||||
      ];
 | 
			
		||||
      const pathToAttributeToRemove = [
 | 
			
		||||
        ...pathToAttributes,
 | 
			
		||||
        attributeToRemoveName,
 | 
			
		||||
      ];
 | 
			
		||||
      const pathToAttributes = ['modifiedData', mainDataKey, 'schema', 'attributes'];
 | 
			
		||||
      const pathToAttributeToRemove = [...pathToAttributes, attributeToRemoveName];
 | 
			
		||||
 | 
			
		||||
      const attributeToRemoveData = state.getIn(pathToAttributeToRemove);
 | 
			
		||||
 | 
			
		||||
      const isRemovingRelationAttribute =
 | 
			
		||||
        attributeToRemoveData.get('nature') !== undefined;
 | 
			
		||||
      const isRemovingRelationAttribute = attributeToRemoveData.get('nature') !== undefined;
 | 
			
		||||
      // Only content types can have relations with themselves since
 | 
			
		||||
      // components can only have oneWay or manyWay relations
 | 
			
		||||
      const canTheAttributeToRemoveHaveARelationWithItself =
 | 
			
		||||
        mainDataKey === 'contentType';
 | 
			
		||||
      const canTheAttributeToRemoveHaveARelationWithItself = mainDataKey === 'contentType';
 | 
			
		||||
 | 
			
		||||
      if (
 | 
			
		||||
        isRemovingRelationAttribute &&
 | 
			
		||||
        canTheAttributeToRemoveHaveARelationWithItself
 | 
			
		||||
      ) {
 | 
			
		||||
        const {
 | 
			
		||||
          target,
 | 
			
		||||
          nature,
 | 
			
		||||
          targetAttribute,
 | 
			
		||||
        } = attributeToRemoveData.toJS();
 | 
			
		||||
      if (isRemovingRelationAttribute && canTheAttributeToRemoveHaveARelationWithItself) {
 | 
			
		||||
        const { target, nature, targetAttribute } = attributeToRemoveData.toJS();
 | 
			
		||||
        const uid = state.getIn(['modifiedData', 'contentType', 'uid']);
 | 
			
		||||
        const shouldRemoveOppositeAttribute =
 | 
			
		||||
          target === uid && !ONE_SIDE_RELATIONS.includes(nature);
 | 
			
		||||
@ -458,7 +391,15 @@ const reducer = (state, action) => {
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      return state.removeIn(pathToAttributeToRemove);
 | 
			
		||||
      return state.removeIn(pathToAttributeToRemove).updateIn([...pathToAttributes], attributes => {
 | 
			
		||||
        return attributes.keySeq().reduce((acc, current) => {
 | 
			
		||||
          if (acc.getIn([current, 'targetField']) === attributeToRemoveName) {
 | 
			
		||||
            return acc.removeIn([current, 'targetField']);
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          return acc;
 | 
			
		||||
        }, attributes);
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
    case 'SET_MODIFIED_DATA': {
 | 
			
		||||
      let newState = state
 | 
			
		||||
@ -502,9 +443,7 @@ const reducer = (state, action) => {
 | 
			
		||||
 | 
			
		||||
      if (schemaType === 'component') {
 | 
			
		||||
        newState = newState.updateIn(['components'], obj => {
 | 
			
		||||
          return obj.update(uid, () =>
 | 
			
		||||
            newState.getIn(['modifiedData', 'component'])
 | 
			
		||||
          );
 | 
			
		||||
          return obj.update(uid, () => newState.getIn(['modifiedData', 'component']));
 | 
			
		||||
        });
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -417,13 +417,7 @@ const data = {
 | 
			
		||||
        collectionName: '',
 | 
			
		||||
        attributes: {
 | 
			
		||||
          price_range: {
 | 
			
		||||
            enum: [
 | 
			
		||||
              'very_cheap',
 | 
			
		||||
              'cheap',
 | 
			
		||||
              'average',
 | 
			
		||||
              'expensive',
 | 
			
		||||
              'very_expensive',
 | 
			
		||||
            ],
 | 
			
		||||
            enum: ['very_cheap', 'cheap', 'average', 'expensive', 'very_expensive'],
 | 
			
		||||
            type: 'enumeration',
 | 
			
		||||
          },
 | 
			
		||||
          closing_period: {
 | 
			
		||||
@ -479,6 +473,17 @@ const data = {
 | 
			
		||||
        },
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    'application::homepage.homepage': {
 | 
			
		||||
      uid: 'application::homepage.homepage',
 | 
			
		||||
      schema: {
 | 
			
		||||
        name: 'homepage',
 | 
			
		||||
        attributes: {
 | 
			
		||||
          title: { type: 'string' },
 | 
			
		||||
          description: { type: 'string' },
 | 
			
		||||
          homepageuidfield: { type: 'uid', targetField: 'description' },
 | 
			
		||||
        },
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -17,10 +17,7 @@ describe('CTB | containers | DataManagerProvider | reducer | REMOVE_FIELD', () =
 | 
			
		||||
      const state = initialState
 | 
			
		||||
        .set('contentTypes', fromJS(testData.contentTypes))
 | 
			
		||||
        .set('initialContentTypes', fromJS(testData.contentTypes))
 | 
			
		||||
        .setIn(
 | 
			
		||||
          ['modifiedData', 'contentType'],
 | 
			
		||||
          fromJS(testData.contentTypes[contentTypeUID])
 | 
			
		||||
        )
 | 
			
		||||
        .setIn(['modifiedData', 'contentType'], fromJS(testData.contentTypes[contentTypeUID]))
 | 
			
		||||
        .setIn(['modifiedData', 'components'], fromJS({}));
 | 
			
		||||
 | 
			
		||||
      const expected = state.removeIn([
 | 
			
		||||
@ -49,10 +46,7 @@ describe('CTB | containers | DataManagerProvider | reducer | REMOVE_FIELD', () =
 | 
			
		||||
      const state = initialState
 | 
			
		||||
        .set('contentTypes', fromJS(testData.contentTypes))
 | 
			
		||||
        .set('initialContentTypes', fromJS(testData.contentTypes))
 | 
			
		||||
        .setIn(
 | 
			
		||||
          ['modifiedData', 'contentType'],
 | 
			
		||||
          fromJS(testData.contentTypes[contentTypeUID])
 | 
			
		||||
        )
 | 
			
		||||
        .setIn(['modifiedData', 'contentType'], fromJS(testData.contentTypes[contentTypeUID]))
 | 
			
		||||
        .setIn(['modifiedData', 'components'], fromJS({}));
 | 
			
		||||
 | 
			
		||||
      const expected = state.removeIn([
 | 
			
		||||
@ -163,10 +157,7 @@ describe('CTB | containers | DataManagerProvider | reducer | REMOVE_FIELD', () =
 | 
			
		||||
        .setIn(['contentTypes', contentTypeUID], fromJS(contentType))
 | 
			
		||||
        .setIn(['modifiedData', 'contentType'], fromJS(contentType));
 | 
			
		||||
 | 
			
		||||
      const expected = state.setIn(
 | 
			
		||||
        ['modifiedData', 'contentType'],
 | 
			
		||||
        fromJS(expectedContentType)
 | 
			
		||||
      );
 | 
			
		||||
      const expected = state.setIn(['modifiedData', 'contentType'], fromJS(expectedContentType));
 | 
			
		||||
 | 
			
		||||
      expect(reducer(state, action)).toEqual(expected);
 | 
			
		||||
    });
 | 
			
		||||
@ -257,10 +248,7 @@ describe('CTB | containers | DataManagerProvider | reducer | REMOVE_FIELD', () =
 | 
			
		||||
        .setIn(['contentTypes', contentTypeUID], fromJS(contentType))
 | 
			
		||||
        .setIn(['modifiedData', 'contentType'], fromJS(contentType));
 | 
			
		||||
 | 
			
		||||
      const expected = state.setIn(
 | 
			
		||||
        ['modifiedData', 'contentType'],
 | 
			
		||||
        fromJS(expectedContentType)
 | 
			
		||||
      );
 | 
			
		||||
      const expected = state.setIn(['modifiedData', 'contentType'], fromJS(expectedContentType));
 | 
			
		||||
 | 
			
		||||
      expect(reducer(state, action)).toEqual(expected);
 | 
			
		||||
      expect(
 | 
			
		||||
@ -271,4 +259,35 @@ describe('CTB | containers | DataManagerProvider | reducer | REMOVE_FIELD', () =
 | 
			
		||||
      ).toEqual(expected);
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
  describe('Removing a field that is targeted by a UID field', () => {
 | 
			
		||||
    it('Should remove the attribute correctly and remove the targetField from the UID field', () => {
 | 
			
		||||
      const contentTypeUID = 'application::homepage.homepage';
 | 
			
		||||
      const attributeToRemoveName = 'description';
 | 
			
		||||
      const action = {
 | 
			
		||||
        type: 'REMOVE_FIELD',
 | 
			
		||||
        mainDataKey: 'contentType',
 | 
			
		||||
        attributeToRemoveName,
 | 
			
		||||
        componentUid: '',
 | 
			
		||||
      };
 | 
			
		||||
 | 
			
		||||
      const state = initialState
 | 
			
		||||
        .set('contentTypes', fromJS(testData.contentTypes))
 | 
			
		||||
        .set('initialContentTypes', fromJS(testData.contentTypes))
 | 
			
		||||
        .setIn(['modifiedData', 'contentType'], fromJS(testData.contentTypes[contentTypeUID]))
 | 
			
		||||
        .setIn(['modifiedData', 'components'], fromJS({}));
 | 
			
		||||
 | 
			
		||||
      const expected = state
 | 
			
		||||
        .removeIn(['modifiedData', 'contentType', 'schema', 'attributes', attributeToRemoveName])
 | 
			
		||||
        .removeIn([
 | 
			
		||||
          'modifiedData',
 | 
			
		||||
          'contentType',
 | 
			
		||||
          'schema',
 | 
			
		||||
          'attributes',
 | 
			
		||||
          'homepageuidfield',
 | 
			
		||||
          'targetField',
 | 
			
		||||
        ]);
 | 
			
		||||
 | 
			
		||||
      expect(reducer(state, action)).toEqual(expected);
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
@ -327,6 +327,7 @@ const FormModal = () => {
 | 
			
		||||
  const isOpen = !isEmpty(search);
 | 
			
		||||
  const isPickingAttribute = state.modalType === 'chooseAttribute';
 | 
			
		||||
  const uid = createUid(modifiedData.name || '');
 | 
			
		||||
  const attributes = get(allDataSchema, [...state.pathToSchema, 'schema', 'attributes'], null);
 | 
			
		||||
 | 
			
		||||
  const checkFormValidity = async () => {
 | 
			
		||||
    let schema;
 | 
			
		||||
@ -394,7 +395,6 @@ const FormModal = () => {
 | 
			
		||||
          }
 | 
			
		||||
        );
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      schema = forms[state.modalType].schema(
 | 
			
		||||
        get(allDataSchema, state.pathToSchema, {}),
 | 
			
		||||
        type,
 | 
			
		||||
@ -1090,180 +1090,184 @@ const FormModal = () => {
 | 
			
		||||
                      </div>
 | 
			
		||||
                    );
 | 
			
		||||
                  })
 | 
			
		||||
                : form(modifiedData, state.attributeType, state.step, state.actionType).items.map(
 | 
			
		||||
                    (row, index) => {
 | 
			
		||||
                      return (
 | 
			
		||||
                        <div className="row" key={index} style={{ marginBottom: 4 }}>
 | 
			
		||||
                          {row.map((input, i) => {
 | 
			
		||||
                            // The divider type is used mainly the advanced tab
 | 
			
		||||
                            // It is the one responsible for displaying the settings label
 | 
			
		||||
                            if (input.type === 'divider') {
 | 
			
		||||
                              return (
 | 
			
		||||
                                <div
 | 
			
		||||
                                  className="col-12"
 | 
			
		||||
                                  style={{
 | 
			
		||||
                                    marginBottom: '1.4rem',
 | 
			
		||||
                                    marginTop: -2,
 | 
			
		||||
                                    fontWeight: 500,
 | 
			
		||||
                                  }}
 | 
			
		||||
                                  key="divider"
 | 
			
		||||
                                >
 | 
			
		||||
                                  <FormattedMessage
 | 
			
		||||
                                    id={getTrad('form.attribute.item.settings.name')}
 | 
			
		||||
                                  />
 | 
			
		||||
                                </div>
 | 
			
		||||
                              );
 | 
			
		||||
                            }
 | 
			
		||||
 | 
			
		||||
                            // The spacer type is used mainly to align the icon picker...
 | 
			
		||||
                            if (input.type === 'spacer') {
 | 
			
		||||
                              return <div key="spacer" style={{ height: 11 }} />;
 | 
			
		||||
                            }
 | 
			
		||||
 | 
			
		||||
                            // The spacer type is used mainly to align the icon picker...
 | 
			
		||||
                            if (input.type === 'spacer-small') {
 | 
			
		||||
                              return <div key="spacer" style={{ height: 4 }} />;
 | 
			
		||||
                            }
 | 
			
		||||
 | 
			
		||||
                            if (input.type === 'spacer-medium') {
 | 
			
		||||
                              return <div key="spacer" style={{ height: 8 }} />;
 | 
			
		||||
                            }
 | 
			
		||||
 | 
			
		||||
                            // This type is used in the addComponentToDynamicZone modal when selecting the option add an existing component
 | 
			
		||||
                            // It pushes select the components to the right
 | 
			
		||||
                            if (input.type === 'pushRight') {
 | 
			
		||||
                              return <div key={`${index}.${i}`} className={`col-${input.size}`} />;
 | 
			
		||||
                            }
 | 
			
		||||
 | 
			
		||||
                            if (input.type === 'relation') {
 | 
			
		||||
                              return (
 | 
			
		||||
                                <RelationForm
 | 
			
		||||
                                  key="relation"
 | 
			
		||||
                                  mainBoxHeader={get(headers, [0, 'label'], '')}
 | 
			
		||||
                                  modifiedData={modifiedData}
 | 
			
		||||
                                  naturePickerType={state.forTarget}
 | 
			
		||||
                                  onChange={handleChange}
 | 
			
		||||
                                  errors={formErrors}
 | 
			
		||||
                                />
 | 
			
		||||
                              );
 | 
			
		||||
                            }
 | 
			
		||||
 | 
			
		||||
                            // Retrieve the error for a specific input
 | 
			
		||||
                            const errorId = get(
 | 
			
		||||
                              formErrors,
 | 
			
		||||
                              [
 | 
			
		||||
                                ...input.name
 | 
			
		||||
                                  .split('.')
 | 
			
		||||
                                  // The filter here is used when creating a component
 | 
			
		||||
                                  // in the component step 1 modal
 | 
			
		||||
                                  // Since the component info is stored in the
 | 
			
		||||
                                  // componentToCreate object we can access the error
 | 
			
		||||
                                  // By removing the key
 | 
			
		||||
                                  .filter(key => key !== 'componentToCreate'),
 | 
			
		||||
                                'id',
 | 
			
		||||
                              ],
 | 
			
		||||
                              null
 | 
			
		||||
                            );
 | 
			
		||||
 | 
			
		||||
                            const retrievedValue = get(modifiedData, input.name, '');
 | 
			
		||||
 | 
			
		||||
                            let value;
 | 
			
		||||
 | 
			
		||||
                            // Condition for the boolean default value
 | 
			
		||||
                            // The radio input doesn't accept false, true or null as value
 | 
			
		||||
                            // So we pass them as string
 | 
			
		||||
                            // This way the data stays accurate and we don't have to operate
 | 
			
		||||
                            // any data mutation
 | 
			
		||||
                            if (input.name === 'default' && state.attributeType === 'boolean') {
 | 
			
		||||
                              value = toString(retrievedValue);
 | 
			
		||||
                              // Same here for the enum
 | 
			
		||||
                            } else if (input.name === 'enum' && Array.isArray(retrievedValue)) {
 | 
			
		||||
                              value = retrievedValue.join('\n');
 | 
			
		||||
                            } else if (input.name === 'uid') {
 | 
			
		||||
                              value = input.value;
 | 
			
		||||
                            } else {
 | 
			
		||||
                              value = retrievedValue;
 | 
			
		||||
                            }
 | 
			
		||||
 | 
			
		||||
                            // The addon input is not present in @buffetjs so we are using the old lib
 | 
			
		||||
                            // for the moment that's why we don't want them be passed to buffet
 | 
			
		||||
                            // like the other created inputs
 | 
			
		||||
                            if (input.type === 'addon') {
 | 
			
		||||
                              return (
 | 
			
		||||
                                <InputsIndex
 | 
			
		||||
                                  key={input.name}
 | 
			
		||||
                                  {...input}
 | 
			
		||||
                                  type="string"
 | 
			
		||||
                                  onChange={handleChange}
 | 
			
		||||
                                  value={value}
 | 
			
		||||
                                  style={{ marginTop: 8, marginBottom: 11 }}
 | 
			
		||||
                                />
 | 
			
		||||
                              );
 | 
			
		||||
                            }
 | 
			
		||||
 | 
			
		||||
                : form(
 | 
			
		||||
                    modifiedData,
 | 
			
		||||
                    state.attributeType,
 | 
			
		||||
                    state.step,
 | 
			
		||||
                    state.actionType,
 | 
			
		||||
                    attributes
 | 
			
		||||
                  ).items.map((row, index) => {
 | 
			
		||||
                    return (
 | 
			
		||||
                      <div className="row" key={index} style={{ marginBottom: 4 }}>
 | 
			
		||||
                        {row.map((input, i) => {
 | 
			
		||||
                          // The divider type is used mainly the advanced tab
 | 
			
		||||
                          // It is the one responsible for displaying the settings label
 | 
			
		||||
                          if (input.type === 'divider') {
 | 
			
		||||
                            return (
 | 
			
		||||
                              <div className={`col-${input.size || 6}`} key={input.name}>
 | 
			
		||||
                                <Inputs
 | 
			
		||||
                                  {...input}
 | 
			
		||||
                                  modifiedData={modifiedData}
 | 
			
		||||
                                  addComponentsToDynamicZone={handleClickAddComponentsToDynamicZone}
 | 
			
		||||
                                  customInputs={{
 | 
			
		||||
                                    componentIconPicker: ComponentIconPicker,
 | 
			
		||||
                                    componentSelect: WrapperSelect,
 | 
			
		||||
                                    creatableSelect: WrapperSelect,
 | 
			
		||||
                                    customCheckboxWithChildren: CustomCheckbox,
 | 
			
		||||
                                    booleanBox: BooleanBox,
 | 
			
		||||
                                  }}
 | 
			
		||||
                                  isCreating={isCreating}
 | 
			
		||||
                                  // Props for the componentSelect
 | 
			
		||||
                                  isCreatingComponentWhileAddingAField={
 | 
			
		||||
                                    isCreatingComponentWhileAddingAField
 | 
			
		||||
                                  }
 | 
			
		||||
                                  // Props for the componentSelect
 | 
			
		||||
                                  // Since the component is created after adding it to a type
 | 
			
		||||
                                  // its name and category can't be retrieved from the data manager
 | 
			
		||||
                                  componentCategoryNeededForAddingAfieldWhileCreatingAComponent={get(
 | 
			
		||||
                                    componentToCreate,
 | 
			
		||||
                                    'category',
 | 
			
		||||
                                    null
 | 
			
		||||
                                  )}
 | 
			
		||||
                                  // Props for the componentSelect same explanation
 | 
			
		||||
                                  componentNameNeededForAddingAfieldWhileCreatingAComponent={get(
 | 
			
		||||
                                    componentToCreate,
 | 
			
		||||
                                    'name',
 | 
			
		||||
                                    null
 | 
			
		||||
                                  )}
 | 
			
		||||
                                  isAddingAComponentToAnotherComponent={
 | 
			
		||||
                                    state.forTarget === 'components' ||
 | 
			
		||||
                                    state.forTarget === 'component'
 | 
			
		||||
                                  }
 | 
			
		||||
                                  value={value}
 | 
			
		||||
                                  error={isEmpty(errorId) ? null : formatMessage({ id: errorId })}
 | 
			
		||||
                                  onChange={handleChange}
 | 
			
		||||
                                  onBlur={() => {}}
 | 
			
		||||
                                  description={
 | 
			
		||||
                                    get(input, 'description.id', null)
 | 
			
		||||
                                      ? formatMessage(input.description)
 | 
			
		||||
                                      : input.description
 | 
			
		||||
                                  }
 | 
			
		||||
                                  placeholder={
 | 
			
		||||
                                    get(input, 'placeholder.id', null)
 | 
			
		||||
                                      ? formatMessage(input.placeholder)
 | 
			
		||||
                                      : input.placeholder
 | 
			
		||||
                                  }
 | 
			
		||||
                                  label={
 | 
			
		||||
                                    get(input, 'label.id', null)
 | 
			
		||||
                                      ? formatMessage(input.label)
 | 
			
		||||
                                      : input.label
 | 
			
		||||
                                  }
 | 
			
		||||
                              <div
 | 
			
		||||
                                className="col-12"
 | 
			
		||||
                                style={{
 | 
			
		||||
                                  marginBottom: '1.4rem',
 | 
			
		||||
                                  marginTop: -2,
 | 
			
		||||
                                  fontWeight: 500,
 | 
			
		||||
                                }}
 | 
			
		||||
                                key="divider"
 | 
			
		||||
                              >
 | 
			
		||||
                                <FormattedMessage
 | 
			
		||||
                                  id={getTrad('form.attribute.item.settings.name')}
 | 
			
		||||
                                />
 | 
			
		||||
                              </div>
 | 
			
		||||
                            );
 | 
			
		||||
                          })}
 | 
			
		||||
                        </div>
 | 
			
		||||
                      );
 | 
			
		||||
                    }
 | 
			
		||||
                  )}
 | 
			
		||||
                          }
 | 
			
		||||
 | 
			
		||||
                          // The spacer type is used mainly to align the icon picker...
 | 
			
		||||
                          if (input.type === 'spacer') {
 | 
			
		||||
                            return <div key="spacer" style={{ height: 11 }} />;
 | 
			
		||||
                          }
 | 
			
		||||
 | 
			
		||||
                          // The spacer type is used mainly to align the icon picker...
 | 
			
		||||
                          if (input.type === 'spacer-small') {
 | 
			
		||||
                            return <div key="spacer" style={{ height: 4 }} />;
 | 
			
		||||
                          }
 | 
			
		||||
 | 
			
		||||
                          if (input.type === 'spacer-medium') {
 | 
			
		||||
                            return <div key="spacer" style={{ height: 8 }} />;
 | 
			
		||||
                          }
 | 
			
		||||
 | 
			
		||||
                          // This type is used in the addComponentToDynamicZone modal when selecting the option add an existing component
 | 
			
		||||
                          // It pushes select the components to the right
 | 
			
		||||
                          if (input.type === 'pushRight') {
 | 
			
		||||
                            return <div key={`${index}.${i}`} className={`col-${input.size}`} />;
 | 
			
		||||
                          }
 | 
			
		||||
 | 
			
		||||
                          if (input.type === 'relation') {
 | 
			
		||||
                            return (
 | 
			
		||||
                              <RelationForm
 | 
			
		||||
                                key="relation"
 | 
			
		||||
                                mainBoxHeader={get(headers, [0, 'label'], '')}
 | 
			
		||||
                                modifiedData={modifiedData}
 | 
			
		||||
                                naturePickerType={state.forTarget}
 | 
			
		||||
                                onChange={handleChange}
 | 
			
		||||
                                errors={formErrors}
 | 
			
		||||
                              />
 | 
			
		||||
                            );
 | 
			
		||||
                          }
 | 
			
		||||
 | 
			
		||||
                          // Retrieve the error for a specific input
 | 
			
		||||
                          const errorId = get(
 | 
			
		||||
                            formErrors,
 | 
			
		||||
                            [
 | 
			
		||||
                              ...input.name
 | 
			
		||||
                                .split('.')
 | 
			
		||||
                                // The filter here is used when creating a component
 | 
			
		||||
                                // in the component step 1 modal
 | 
			
		||||
                                // Since the component info is stored in the
 | 
			
		||||
                                // componentToCreate object we can access the error
 | 
			
		||||
                                // By removing the key
 | 
			
		||||
                                .filter(key => key !== 'componentToCreate'),
 | 
			
		||||
                              'id',
 | 
			
		||||
                            ],
 | 
			
		||||
                            null
 | 
			
		||||
                          );
 | 
			
		||||
 | 
			
		||||
                          const retrievedValue = get(modifiedData, input.name, '');
 | 
			
		||||
 | 
			
		||||
                          let value;
 | 
			
		||||
 | 
			
		||||
                          // Condition for the boolean default value
 | 
			
		||||
                          // The radio input doesn't accept false, true or null as value
 | 
			
		||||
                          // So we pass them as string
 | 
			
		||||
                          // This way the data stays accurate and we don't have to operate
 | 
			
		||||
                          // any data mutation
 | 
			
		||||
                          if (input.name === 'default' && state.attributeType === 'boolean') {
 | 
			
		||||
                            value = toString(retrievedValue);
 | 
			
		||||
                            // Same here for the enum
 | 
			
		||||
                          } else if (input.name === 'enum' && Array.isArray(retrievedValue)) {
 | 
			
		||||
                            value = retrievedValue.join('\n');
 | 
			
		||||
                          } else if (input.name === 'uid') {
 | 
			
		||||
                            value = input.value;
 | 
			
		||||
                          } else {
 | 
			
		||||
                            value = retrievedValue;
 | 
			
		||||
                          }
 | 
			
		||||
 | 
			
		||||
                          // The addon input is not present in @buffetjs so we are using the old lib
 | 
			
		||||
                          // for the moment that's why we don't want them be passed to buffet
 | 
			
		||||
                          // like the other created inputs
 | 
			
		||||
                          if (input.type === 'addon') {
 | 
			
		||||
                            return (
 | 
			
		||||
                              <InputsIndex
 | 
			
		||||
                                key={input.name}
 | 
			
		||||
                                {...input}
 | 
			
		||||
                                type="string"
 | 
			
		||||
                                onChange={handleChange}
 | 
			
		||||
                                value={value}
 | 
			
		||||
                                style={{ marginTop: 8, marginBottom: 11 }}
 | 
			
		||||
                              />
 | 
			
		||||
                            );
 | 
			
		||||
                          }
 | 
			
		||||
 | 
			
		||||
                          return (
 | 
			
		||||
                            <div className={`col-${input.size || 6}`} key={input.name}>
 | 
			
		||||
                              <Inputs
 | 
			
		||||
                                {...input}
 | 
			
		||||
                                modifiedData={modifiedData}
 | 
			
		||||
                                addComponentsToDynamicZone={handleClickAddComponentsToDynamicZone}
 | 
			
		||||
                                customInputs={{
 | 
			
		||||
                                  componentIconPicker: ComponentIconPicker,
 | 
			
		||||
                                  componentSelect: WrapperSelect,
 | 
			
		||||
                                  creatableSelect: WrapperSelect,
 | 
			
		||||
                                  customCheckboxWithChildren: CustomCheckbox,
 | 
			
		||||
                                  booleanBox: BooleanBox,
 | 
			
		||||
                                }}
 | 
			
		||||
                                isCreating={isCreating}
 | 
			
		||||
                                // Props for the componentSelect
 | 
			
		||||
                                isCreatingComponentWhileAddingAField={
 | 
			
		||||
                                  isCreatingComponentWhileAddingAField
 | 
			
		||||
                                }
 | 
			
		||||
                                // Props for the componentSelect
 | 
			
		||||
                                // Since the component is created after adding it to a type
 | 
			
		||||
                                // its name and category can't be retrieved from the data manager
 | 
			
		||||
                                componentCategoryNeededForAddingAfieldWhileCreatingAComponent={get(
 | 
			
		||||
                                  componentToCreate,
 | 
			
		||||
                                  'category',
 | 
			
		||||
                                  null
 | 
			
		||||
                                )}
 | 
			
		||||
                                // Props for the componentSelect same explanation
 | 
			
		||||
                                componentNameNeededForAddingAfieldWhileCreatingAComponent={get(
 | 
			
		||||
                                  componentToCreate,
 | 
			
		||||
                                  'name',
 | 
			
		||||
                                  null
 | 
			
		||||
                                )}
 | 
			
		||||
                                isAddingAComponentToAnotherComponent={
 | 
			
		||||
                                  state.forTarget === 'components' ||
 | 
			
		||||
                                  state.forTarget === 'component'
 | 
			
		||||
                                }
 | 
			
		||||
                                value={value}
 | 
			
		||||
                                error={isEmpty(errorId) ? null : formatMessage({ id: errorId })}
 | 
			
		||||
                                onChange={handleChange}
 | 
			
		||||
                                onBlur={() => {}}
 | 
			
		||||
                                description={
 | 
			
		||||
                                  get(input, 'description.id', null)
 | 
			
		||||
                                    ? formatMessage(input.description)
 | 
			
		||||
                                    : input.description
 | 
			
		||||
                                }
 | 
			
		||||
                                placeholder={
 | 
			
		||||
                                  get(input, 'placeholder.id', null)
 | 
			
		||||
                                    ? formatMessage(input.placeholder)
 | 
			
		||||
                                    : input.placeholder
 | 
			
		||||
                                }
 | 
			
		||||
                                label={
 | 
			
		||||
                                  get(input, 'label.id', null)
 | 
			
		||||
                                    ? formatMessage(input.label)
 | 
			
		||||
                                    : input.label
 | 
			
		||||
                                }
 | 
			
		||||
                              />
 | 
			
		||||
                            </div>
 | 
			
		||||
                          );
 | 
			
		||||
                        })}
 | 
			
		||||
                      </div>
 | 
			
		||||
                    );
 | 
			
		||||
                  })}
 | 
			
		||||
            </div>
 | 
			
		||||
          </ModalBody>
 | 
			
		||||
        </ModalForm>
 | 
			
		||||
 | 
			
		||||
@ -1,4 +1,4 @@
 | 
			
		||||
import React from 'react';
 | 
			
		||||
import React, { Fragment } from 'react';
 | 
			
		||||
import * as yup from 'yup';
 | 
			
		||||
import { get, isEmpty, toLower, trim, toNumber } from 'lodash';
 | 
			
		||||
import { translatedErrors as errorsTrads } from 'strapi-helper-plugin';
 | 
			
		||||
@ -8,22 +8,14 @@ import getTrad from '../../../utils/getTrad';
 | 
			
		||||
import { createComponentUid, createUid, nameToSlug } from './createUid';
 | 
			
		||||
import componentForm from './componentForm';
 | 
			
		||||
import fields from './staticFields';
 | 
			
		||||
import {
 | 
			
		||||
  NAME_REGEX,
 | 
			
		||||
  ENUM_REGEX,
 | 
			
		||||
  CATEGORY_NAME_REGEX,
 | 
			
		||||
} from './attributesRegexes';
 | 
			
		||||
import { NAME_REGEX, ENUM_REGEX, CATEGORY_NAME_REGEX } from './attributesRegexes';
 | 
			
		||||
import RESERVED_NAMES from './reservedNames';
 | 
			
		||||
 | 
			
		||||
/* eslint-disable indent */
 | 
			
		||||
/* eslint-disable prefer-arrow-callback */
 | 
			
		||||
 | 
			
		||||
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.string, 'unique', function(
 | 
			
		||||
@ -83,12 +75,7 @@ yup.addMethod(yup.array, 'matchesEnumRegex', function(message) {
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const ATTRIBUTES_THAT_DONT_HAVE_MIN_MAX_SETTINGS = [
 | 
			
		||||
  'boolean',
 | 
			
		||||
  'date',
 | 
			
		||||
  'enumeration',
 | 
			
		||||
  'media',
 | 
			
		||||
];
 | 
			
		||||
const ATTRIBUTES_THAT_DONT_HAVE_MIN_MAX_SETTINGS = ['boolean', 'date', 'enumeration', 'media'];
 | 
			
		||||
 | 
			
		||||
const forms = {
 | 
			
		||||
  attribute: {
 | 
			
		||||
@ -193,10 +180,7 @@ const forms = {
 | 
			
		||||
          .integer()
 | 
			
		||||
          .when('maxLength', (maxLength, schema) => {
 | 
			
		||||
            if (maxLength) {
 | 
			
		||||
              return schema.max(
 | 
			
		||||
                maxLength,
 | 
			
		||||
                getTrad('error.validation.minSupMax')
 | 
			
		||||
              );
 | 
			
		||||
              return schema.max(maxLength, getTrad('error.validation.minSupMax'));
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return schema;
 | 
			
		||||
@ -232,10 +216,7 @@ const forms = {
 | 
			
		||||
              .of(yup.string())
 | 
			
		||||
              .min(1, errorsTrads.min)
 | 
			
		||||
              .matchesEnumRegex(errorsTrads.regex)
 | 
			
		||||
              .hasNotEmptyValues(
 | 
			
		||||
                'Empty strings are not allowed',
 | 
			
		||||
                dataToValidate.enum
 | 
			
		||||
              ),
 | 
			
		||||
              .hasNotEmptyValues('Empty strings are not allowed', dataToValidate.enum),
 | 
			
		||||
            enumName: yup.string().nullable(),
 | 
			
		||||
          });
 | 
			
		||||
        case 'number':
 | 
			
		||||
@ -256,10 +237,7 @@ const forms = {
 | 
			
		||||
                .matches(/^\d*$/)
 | 
			
		||||
                .when('max', (max, schema) => {
 | 
			
		||||
                  if (max) {
 | 
			
		||||
                    return schema.isInferior(
 | 
			
		||||
                      getTrad('error.validation.minSupMax'),
 | 
			
		||||
                      max
 | 
			
		||||
                    );
 | 
			
		||||
                    return schema.isInferior(getTrad('error.validation.minSupMax'), max);
 | 
			
		||||
                  }
 | 
			
		||||
 | 
			
		||||
                  return schema;
 | 
			
		||||
@ -275,9 +253,7 @@ const forms = {
 | 
			
		||||
          let defaultType = yup.number();
 | 
			
		||||
 | 
			
		||||
          if (dataToValidate.type === 'integer') {
 | 
			
		||||
            defaultType = yup
 | 
			
		||||
              .number()
 | 
			
		||||
              .integer('component.Input.error.validation.integer');
 | 
			
		||||
            defaultType = yup.number().integer('component.Input.error.validation.integer');
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          return yup.object().shape({
 | 
			
		||||
@ -344,8 +320,7 @@ const forms = {
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
              autoFocus: false,
 | 
			
		||||
              disabled:
 | 
			
		||||
                targetAttributeValue === null || targetAttributeValue === '-',
 | 
			
		||||
              disabled: targetAttributeValue === null || targetAttributeValue === '-',
 | 
			
		||||
              name: 'targetColumnName',
 | 
			
		||||
              label: '',
 | 
			
		||||
              type: 'addon',
 | 
			
		||||
@ -366,12 +341,7 @@ const forms = {
 | 
			
		||||
          [fields.required],
 | 
			
		||||
          [fields.unique],
 | 
			
		||||
        ];
 | 
			
		||||
        const dynamiczoneItems = [
 | 
			
		||||
          [fields.required],
 | 
			
		||||
          [fields.divider],
 | 
			
		||||
          [fields.max],
 | 
			
		||||
          [fields.min],
 | 
			
		||||
        ];
 | 
			
		||||
        const dynamiczoneItems = [[fields.required], [fields.divider], [fields.max], [fields.min]];
 | 
			
		||||
 | 
			
		||||
        if (type === 'component') {
 | 
			
		||||
          if (step === '1') {
 | 
			
		||||
@ -386,11 +356,10 @@ const forms = {
 | 
			
		||||
          };
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const items = defaultItems.slice();
 | 
			
		||||
        let items = defaultItems.slice();
 | 
			
		||||
 | 
			
		||||
        if (type === 'number' && data.type !== 'biginteger') {
 | 
			
		||||
          const step =
 | 
			
		||||
            data.type === 'decimal' || data.type === 'float' ? 'any' : '1';
 | 
			
		||||
          const step = data.type === 'decimal' || data.type === 'float' ? 'any' : '1';
 | 
			
		||||
 | 
			
		||||
          items.splice(0, 1, [
 | 
			
		||||
            {
 | 
			
		||||
@ -441,10 +410,7 @@ const forms = {
 | 
			
		||||
              ].concat(
 | 
			
		||||
                data.enum
 | 
			
		||||
                  ? data.enum
 | 
			
		||||
                      .filter(
 | 
			
		||||
                        (val, index) =>
 | 
			
		||||
                          data.enum.indexOf(val) === index && !isEmpty(val)
 | 
			
		||||
                      )
 | 
			
		||||
                      .filter((val, index) => data.enum.indexOf(val) === index && !isEmpty(val))
 | 
			
		||||
                      .map(val => (
 | 
			
		||||
                        <option key={val} value={val}>
 | 
			
		||||
                          {val}
 | 
			
		||||
@ -463,9 +429,7 @@ const forms = {
 | 
			
		||||
              type: 'text',
 | 
			
		||||
              validations: {},
 | 
			
		||||
              description: {
 | 
			
		||||
                id: getTrad(
 | 
			
		||||
                  'form.attribute.item.enumeration.graphql.description'
 | 
			
		||||
                ),
 | 
			
		||||
                id: getTrad('form.attribute.item.enumeration.graphql.description'),
 | 
			
		||||
              },
 | 
			
		||||
            },
 | 
			
		||||
          ]);
 | 
			
		||||
@ -473,13 +437,20 @@ const forms = {
 | 
			
		||||
          items.splice(0, 1, [
 | 
			
		||||
            {
 | 
			
		||||
              ...fields.default,
 | 
			
		||||
              // type: data.type || 'date',
 | 
			
		||||
              type: 'date',
 | 
			
		||||
              value: null,
 | 
			
		||||
              withDefaultValue: false,
 | 
			
		||||
              disabled: data.type !== 'date',
 | 
			
		||||
            },
 | 
			
		||||
          ]);
 | 
			
		||||
        } else if (type === 'uid') {
 | 
			
		||||
          const uidItems = [
 | 
			
		||||
            [{ ...fields.default, disabled: Boolean(data.targetField), type: 'text' }],
 | 
			
		||||
            [fields.divider],
 | 
			
		||||
            [fields.required],
 | 
			
		||||
          ];
 | 
			
		||||
 | 
			
		||||
          items = uidItems;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (!ATTRIBUTES_THAT_DONT_HAVE_MIN_MAX_SETTINGS.includes(type)) {
 | 
			
		||||
@ -490,11 +461,7 @@ const forms = {
 | 
			
		||||
                name: type === 'number' ? 'max' : 'maxLength',
 | 
			
		||||
                type: 'customCheckboxWithChildren',
 | 
			
		||||
                label: {
 | 
			
		||||
                  id: getTrad(
 | 
			
		||||
                    `form.attribute.item.maximum${
 | 
			
		||||
                      type === 'number' ? '' : 'Length'
 | 
			
		||||
                    }`
 | 
			
		||||
                  ),
 | 
			
		||||
                  id: getTrad(`form.attribute.item.maximum${type === 'number' ? '' : 'Length'}`),
 | 
			
		||||
                },
 | 
			
		||||
 | 
			
		||||
                validations: {},
 | 
			
		||||
@ -506,11 +473,7 @@ const forms = {
 | 
			
		||||
                name: type === 'number' ? 'min' : 'minLength',
 | 
			
		||||
                type: 'customCheckboxWithChildren',
 | 
			
		||||
                label: {
 | 
			
		||||
                  id: getTrad(
 | 
			
		||||
                    `form.attribute.item.minimum${
 | 
			
		||||
                      type === 'number' ? '' : 'Length'
 | 
			
		||||
                    }`
 | 
			
		||||
                  ),
 | 
			
		||||
                  id: getTrad(`form.attribute.item.minimum${type === 'number' ? '' : 'Length'}`),
 | 
			
		||||
                },
 | 
			
		||||
                validations: {},
 | 
			
		||||
              },
 | 
			
		||||
@ -534,7 +497,7 @@ const forms = {
 | 
			
		||||
          items,
 | 
			
		||||
        };
 | 
			
		||||
      },
 | 
			
		||||
      base(data, type, step) {
 | 
			
		||||
      base(data, type, step, actionType, attributes) {
 | 
			
		||||
        if (type === 'relation') {
 | 
			
		||||
          return {
 | 
			
		||||
            items: [
 | 
			
		||||
@ -552,9 +515,7 @@ const forms = {
 | 
			
		||||
        if (type === 'component' && step === '1') {
 | 
			
		||||
          const itemsToConcat =
 | 
			
		||||
            data.createComponent === true
 | 
			
		||||
              ? [[{ type: 'spacer' }]].concat(
 | 
			
		||||
                  componentForm.base('componentToCreate.')
 | 
			
		||||
                )
 | 
			
		||||
              ? [[{ type: 'spacer' }]].concat(componentForm.base('componentToCreate.'))
 | 
			
		||||
              : [[{ type: 'spacer' }]];
 | 
			
		||||
 | 
			
		||||
          return {
 | 
			
		||||
@ -581,19 +542,13 @@ const forms = {
 | 
			
		||||
              size: 12,
 | 
			
		||||
              options: [
 | 
			
		||||
                {
 | 
			
		||||
                  headerId: getTrad(
 | 
			
		||||
                    'form.attribute.component.option.repeatable'
 | 
			
		||||
                  ),
 | 
			
		||||
                  descriptionId: getTrad(
 | 
			
		||||
                    'form.attribute.component.option.repeatable.description'
 | 
			
		||||
                  ),
 | 
			
		||||
                  headerId: getTrad('form.attribute.component.option.repeatable'),
 | 
			
		||||
                  descriptionId: getTrad('form.attribute.component.option.repeatable.description'),
 | 
			
		||||
                  value: true,
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                  headerId: getTrad('form.attribute.component.option.single'),
 | 
			
		||||
                  descriptionId: getTrad(
 | 
			
		||||
                    'form.attribute.component.option.single.description'
 | 
			
		||||
                  ),
 | 
			
		||||
                  descriptionId: getTrad('form.attribute.component.option.single.description'),
 | 
			
		||||
                  value: false,
 | 
			
		||||
                },
 | 
			
		||||
              ],
 | 
			
		||||
@ -615,9 +570,7 @@ const forms = {
 | 
			
		||||
              options: [
 | 
			
		||||
                {
 | 
			
		||||
                  headerId: getTrad(
 | 
			
		||||
                    `form.attribute.${type}.option.${
 | 
			
		||||
                      type === 'text' ? 'short-text' : 'multiple'
 | 
			
		||||
                    }`
 | 
			
		||||
                    `form.attribute.${type}.option.${type === 'text' ? 'short-text' : 'multiple'}`
 | 
			
		||||
                  ),
 | 
			
		||||
                  descriptionId: getTrad(
 | 
			
		||||
                    `form.attribute.${type}.option.${
 | 
			
		||||
@ -628,9 +581,7 @@ const forms = {
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                  headerId: getTrad(
 | 
			
		||||
                    `form.attribute.${type}.option.${
 | 
			
		||||
                      type === 'text' ? 'long-text' : 'single'
 | 
			
		||||
                    }`
 | 
			
		||||
                    `form.attribute.${type}.option.${type === 'text' ? 'long-text' : 'single'}`
 | 
			
		||||
                  ),
 | 
			
		||||
                  descriptionId: getTrad(
 | 
			
		||||
                    `form.attribute.${type}.option.${
 | 
			
		||||
@ -751,6 +702,35 @@ const forms = {
 | 
			
		||||
          ]);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (type === 'uid') {
 | 
			
		||||
          const options = Object.keys(attributes)
 | 
			
		||||
            .filter(key => attributes[key].type === 'string')
 | 
			
		||||
            .map(key => ({ id: key, value: key }));
 | 
			
		||||
 | 
			
		||||
          items[0].push({
 | 
			
		||||
            label: {
 | 
			
		||||
              id: getTrad('modalForm.attribute.target-field'),
 | 
			
		||||
            },
 | 
			
		||||
            name: 'targetField',
 | 
			
		||||
            type: 'select',
 | 
			
		||||
            options: [{ id: getTrad('none'), value: '' }, ...options].map((option, index) => (
 | 
			
		||||
              // eslint-disable-next-line react/no-array-index-key
 | 
			
		||||
              <Fragment key={index}>
 | 
			
		||||
                {index === 0 ? (
 | 
			
		||||
                  <FormattedMessage id={option.id}>
 | 
			
		||||
                    {msg => <option value={option.value}>{msg}</option>}
 | 
			
		||||
                  </FormattedMessage>
 | 
			
		||||
                ) : (
 | 
			
		||||
                  <option value={option.value}>{option.value}</option>
 | 
			
		||||
                )}
 | 
			
		||||
              </Fragment>
 | 
			
		||||
            )),
 | 
			
		||||
            validations: {
 | 
			
		||||
              required: true,
 | 
			
		||||
            },
 | 
			
		||||
          });
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return {
 | 
			
		||||
          items,
 | 
			
		||||
        };
 | 
			
		||||
@ -814,15 +794,11 @@ const forms = {
 | 
			
		||||
            type: 'booleanBox',
 | 
			
		||||
            size: 12,
 | 
			
		||||
            onChangeCallback: () =>
 | 
			
		||||
              strapi.notification.info(
 | 
			
		||||
                getTrad('contentType.kind.change.warning')
 | 
			
		||||
              ),
 | 
			
		||||
              strapi.notification.info(getTrad('contentType.kind.change.warning')),
 | 
			
		||||
            options: [
 | 
			
		||||
              {
 | 
			
		||||
                headerId: getTrad('menu.section.models.name.singular'),
 | 
			
		||||
                descriptionId: getTrad(
 | 
			
		||||
                  'form.button.collection-type.description'
 | 
			
		||||
                ),
 | 
			
		||||
                descriptionId: getTrad('form.button.collection-type.description'),
 | 
			
		||||
                value: 'collectionType',
 | 
			
		||||
              },
 | 
			
		||||
              {
 | 
			
		||||
@ -860,12 +836,7 @@ const forms = {
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
  component: {
 | 
			
		||||
    schema(
 | 
			
		||||
      alreadyTakenAttributes,
 | 
			
		||||
      componentCategory,
 | 
			
		||||
      isEditing = false,
 | 
			
		||||
      compoUid = null
 | 
			
		||||
    ) {
 | 
			
		||||
    schema(alreadyTakenAttributes, componentCategory, isEditing = false, compoUid = null) {
 | 
			
		||||
      const takenNames = isEditing
 | 
			
		||||
        ? alreadyTakenAttributes.filter(uid => uid !== compoUid)
 | 
			
		||||
        : alreadyTakenAttributes;
 | 
			
		||||
@ -873,12 +844,7 @@ const forms = {
 | 
			
		||||
      return yup.object().shape({
 | 
			
		||||
        name: yup
 | 
			
		||||
          .string()
 | 
			
		||||
          .unique(
 | 
			
		||||
            errorsTrads.unique,
 | 
			
		||||
            takenNames,
 | 
			
		||||
            createComponentUid,
 | 
			
		||||
            componentCategory
 | 
			
		||||
          )
 | 
			
		||||
          .unique(errorsTrads.unique, takenNames, createComponentUid, componentCategory)
 | 
			
		||||
          .isAllowed(getTrad('error.contentTypeName.reserved-name'))
 | 
			
		||||
          .required(errorsTrads.required),
 | 
			
		||||
        category: yup
 | 
			
		||||
@ -911,9 +877,7 @@ const forms = {
 | 
			
		||||
        const isCreatingComponent = get(data, 'createComponent', false);
 | 
			
		||||
 | 
			
		||||
        const itemsToConcat = isCreatingComponent
 | 
			
		||||
          ? [[{ type: 'spacer' }]].concat(
 | 
			
		||||
              componentForm.base('componentToCreate.')
 | 
			
		||||
            )
 | 
			
		||||
          ? [[{ type: 'spacer' }]].concat(componentForm.base('componentToCreate.'))
 | 
			
		||||
          : [
 | 
			
		||||
              [{ type: 'spacer' }],
 | 
			
		||||
              [
 | 
			
		||||
 | 
			
		||||
@ -257,8 +257,9 @@ const ListView = () => {
 | 
			
		||||
  };
 | 
			
		||||
  const goToCMSettingsPage = () => {
 | 
			
		||||
    const endPoint = isInContentTypeView
 | 
			
		||||
      ? `/plugins/content-manager/${targetUid}/ctm-configurations/edit-settings/content-types`
 | 
			
		||||
      ? `/plugins/content-manager/${contentTypeKind}/${targetUid}/ctm-configurations/edit-settings/content-types`
 | 
			
		||||
      : `/plugins/content-manager/ctm-configurations/edit-settings/components/${targetUid}/`;
 | 
			
		||||
 | 
			
		||||
    push(endPoint);
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -28,7 +28,7 @@
 | 
			
		||||
  "attribute.text": "Text",
 | 
			
		||||
  "attribute.text.description": "Krátký nebo delší text",
 | 
			
		||||
  "attribute.time": "Čas",
 | 
			
		||||
  "attribute.uid": "Uuid",
 | 
			
		||||
  "attribute.uid": "Uid",
 | 
			
		||||
  "attribute.uid.description": "Unikátní identifikátor",
 | 
			
		||||
  "button.attributes.add.another": "Přidat další pole",
 | 
			
		||||
  "button.component.add": "Přidat komponent",
 | 
			
		||||
 | 
			
		||||
@ -28,8 +28,9 @@
 | 
			
		||||
  "attribute.text.description": "Small or long text like title or description",
 | 
			
		||||
  "attribute.text": "Text",
 | 
			
		||||
  "attribute.time": "Time",
 | 
			
		||||
  "attribute.timestamp": "Timestamp",
 | 
			
		||||
  "attribute.uid.description": "Unique identifier",
 | 
			
		||||
  "attribute.uid": "Uuid",
 | 
			
		||||
  "attribute.uid": "Uid",
 | 
			
		||||
  "button.attributes.add.another": "Add another field",
 | 
			
		||||
  "button.component.add": "Add a component",
 | 
			
		||||
  "button.component.create": "Create new component",
 | 
			
		||||
@ -118,6 +119,7 @@
 | 
			
		||||
  "menu.section.models.name.singular": "Collection Type",
 | 
			
		||||
  "menu.section.single-types.name.plural": "Single Types",
 | 
			
		||||
  "menu.section.single-types.name.singular": "Single Type",
 | 
			
		||||
  "modalForm.attribute.target-field": "Attached field",
 | 
			
		||||
  "modalForm.attribute.form.base.name.description": "No space is allowed for the name of the attribute",
 | 
			
		||||
  "modalForm.attribute.form.base.name": "Name",
 | 
			
		||||
  "modalForm.attribute.text.type-selection": "Type",
 | 
			
		||||
@ -139,6 +141,7 @@
 | 
			
		||||
  "modalForm.sub-header.chooseAttribute.contentType": "Select a field for your collection type",
 | 
			
		||||
  "modelPage.attribute.relationWith": "Relation with",
 | 
			
		||||
  "modelPage.contentHeader.emptyDescription.description": "There is no description",
 | 
			
		||||
  "none": "None",
 | 
			
		||||
  "notification.info.creating.notSaved": "Please save your work before creating a new collection type or component",
 | 
			
		||||
  "notification.info.autoreaload-disable": "The autoReload feature is required to use this plugin. Start your server with `strapi develop`",
 | 
			
		||||
  "plugin.description.long": "Modelize the data structure of your API. Create new fields and relations in just a minute. The files are automatically created and updated in your project.",
 | 
			
		||||
 | 
			
		||||
@ -50,7 +50,10 @@
 | 
			
		||||
  "menu.section.models.name.singular": "Collection",
 | 
			
		||||
  "menu.section.single-types.name.plural": "Single Types",
 | 
			
		||||
  "menu.section.single-types.name.singular": "Single Type",
 | 
			
		||||
  "modalForm.attribute.target-field": "Champ associé",
 | 
			
		||||
  "modalForm.attribute.target-field.none": "Aucun",
 | 
			
		||||
  "modalForm.singleType.header-create": "Créer un single type",
 | 
			
		||||
  "none": "Aucun",
 | 
			
		||||
  "button.single-types.create": "Créer un single type",
 | 
			
		||||
  "modelPage.attribute.relationWith": "Relation avec",
 | 
			
		||||
  "modelPage.contentHeader.emptyDescription.description": "Il n'y a pas de description",
 | 
			
		||||
 | 
			
		||||
@ -68,7 +68,7 @@
 | 
			
		||||
  "attribute.richtext.description": "Edytor tekstu z możliwością formatowania",
 | 
			
		||||
  "attribute.text.description": "Krótki lub długi tekst jak tytuł lub opis",
 | 
			
		||||
  "attribute.time": "Czas",
 | 
			
		||||
  "attribute.uid": "Uuid",
 | 
			
		||||
  "attribute.uid": "Uid",
 | 
			
		||||
  "attribute.uid.description": "Unikalny identyfikator",
 | 
			
		||||
  "button.attributes.add.another": "Dodaj kolejne pole",
 | 
			
		||||
  "button.component.add": "Dodaj komponent",
 | 
			
		||||
 | 
			
		||||
@ -28,7 +28,7 @@
 | 
			
		||||
  "attribute.text": "Text",
 | 
			
		||||
  "attribute.text.description": "Простой текст для заголовка или описания",
 | 
			
		||||
  "attribute.time": "Time",
 | 
			
		||||
  "attribute.uid": "Uuid",
 | 
			
		||||
  "attribute.uid": "Uid",
 | 
			
		||||
  "attribute.uid.description": "Уникальный идентификатор",
 | 
			
		||||
  "button.attributes.add.another": "Ещё поле",
 | 
			
		||||
  "button.component.add": "Добавить компонент",
 | 
			
		||||
 | 
			
		||||
@ -28,7 +28,7 @@
 | 
			
		||||
  "attribute.text": "Text",
 | 
			
		||||
  "attribute.text.description": "Krátky alebo dlhší text",
 | 
			
		||||
  "attribute.time": "Čas",
 | 
			
		||||
  "attribute.uid": "Uuid",
 | 
			
		||||
  "attribute.uid": "Uid",
 | 
			
		||||
  "attribute.uid.description": "Unikátny identifikátor",
 | 
			
		||||
  "button.attributes.add.another": "Pridať ďalšie políčko",
 | 
			
		||||
  "button.component.add": "Pridať komponent",
 | 
			
		||||
 | 
			
		||||
@ -28,7 +28,7 @@
 | 
			
		||||
  "attribute.text": "文本",
 | 
			
		||||
  "attribute.text.description": "较短或较长的文字,例如标题或说明",
 | 
			
		||||
  "attribute.time": "时间",
 | 
			
		||||
  "attribute.uid": "Uuid",
 | 
			
		||||
  "attribute.uid": "Uid",
 | 
			
		||||
  "attribute.uid.description": "唯一标识符",
 | 
			
		||||
  "button.attributes.add.another": "添加一个新字段",
 | 
			
		||||
  "button.component.add": "添加组件",
 | 
			
		||||
 | 
			
		||||
@ -5,6 +5,7 @@ const getAttributeDisplayedType = type => {
 | 
			
		||||
    case 'date':
 | 
			
		||||
    case 'datetime':
 | 
			
		||||
    case 'time':
 | 
			
		||||
    case 'timestamp':
 | 
			
		||||
      displayedType = 'date';
 | 
			
		||||
      break;
 | 
			
		||||
    case 'integer':
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user