diff --git a/packages/core/content-manager/admin/src/exposedHooks.js b/packages/core/content-manager/admin/src/exposedHooks.js index b1b4efa990..08787b0cd9 100644 --- a/packages/core/content-manager/admin/src/exposedHooks.js +++ b/packages/core/content-manager/admin/src/exposedHooks.js @@ -1,3 +1,27 @@ +/** + * Hook that allows to mutate the displayed headers of the list view table + * @constant + * @type {string} + */ export const INJECT_COLUMN_IN_TABLE = 'CM/pages/ListView/inject-column-in-table'; + +/** + * Hook that allows to mutate the CM's collection types links pre-set filters + * @constant + * @type {string} + */ export const MUTATE_COLLECTION_TYPES_LINKS = 'CM/pages/App/mutate-collection-types-links'; + +/** + * Hook that allows to mutate the CM's edit view layout + * @constant + * @type {string} + */ +export const MUTATE_EDIT_VIEW_LAYOUT = 'CM/pages/EditView/mutate-edit-view-layout'; + +/** + * Hook that allows to mutate the CM's single types links pre-set filters + * @constant + * @type {string} + */ export const MUTATE_SINGLE_TYPES_LINKS = 'CM/pages/App/mutate-single-types-links'; diff --git a/packages/core/content-manager/admin/src/index.js b/packages/core/content-manager/admin/src/index.js index ad5bca7d3c..15c73ce2ca 100644 --- a/packages/core/content-manager/admin/src/index.js +++ b/packages/core/content-manager/admin/src/index.js @@ -9,6 +9,7 @@ import pluginPkg from '../../package.json'; import { INJECT_COLUMN_IN_TABLE, MUTATE_COLLECTION_TYPES_LINKS, + MUTATE_EDIT_VIEW_LAYOUT, MUTATE_SINGLE_TYPES_LINKS, } from './exposedHooks'; import pluginId from './pluginId'; @@ -33,12 +34,10 @@ export default { }); app.addReducers(reducers); - - // Hook that allows to mutate the displayed headers of the list view table app.createHook(INJECT_COLUMN_IN_TABLE); - // Hook that allows to mutate the CM's link pre-set filters app.createHook(MUTATE_COLLECTION_TYPES_LINKS); app.createHook(MUTATE_SINGLE_TYPES_LINKS); + app.createHook(MUTATE_EDIT_VIEW_LAYOUT); app.registerPlugin({ description: pluginDescription, diff --git a/packages/core/content-manager/admin/src/pages/EditViewLayoutManager/index.js b/packages/core/content-manager/admin/src/pages/EditViewLayoutManager/index.js index ed78ca4874..b201459896 100644 --- a/packages/core/content-manager/admin/src/pages/EditViewLayoutManager/index.js +++ b/packages/core/content-manager/admin/src/pages/EditViewLayoutManager/index.js @@ -1,7 +1,8 @@ import React, { useEffect } from 'react'; import PropTypes from 'prop-types'; import { useDispatch, useSelector } from 'react-redux'; -import { LoadingIndicatorPage, useQueryParams } from '@strapi/helper-plugin'; +import { LoadingIndicatorPage, useQueryParams, useStrapiApp } from '@strapi/helper-plugin'; +import { MUTATE_EDIT_VIEW_LAYOUT } from '../../exposedHooks'; import { useSyncRbac } from '../../hooks'; import { resetProps, setLayout } from './actions'; import selectLayout from './selectors'; @@ -11,15 +12,19 @@ const EditViewLayoutManager = ({ layout, ...rest }) => { const currentLayout = useSelector(selectLayout); const dispatch = useDispatch(); const [{ query }] = useQueryParams(); + const { runHookWaterfall } = useStrapiApp(); const permissions = useSyncRbac(query, rest.slug, 'editView'); useEffect(() => { - dispatch(setLayout(layout, query)); + // Allow the plugins to extend the edit view layout + const mutatedLayout = runHookWaterfall(MUTATE_EDIT_VIEW_LAYOUT, { layout, query }); + + dispatch(setLayout(mutatedLayout.layout, query)); return () => { dispatch(resetProps()); }; - }, [layout, dispatch, query]); + }, [layout, dispatch, query, runHookWaterfall]); if (!currentLayout || !permissions) { return ; diff --git a/packages/plugins/i18n/admin/src/middlewares/extendCMEditViewLayoutMiddleware.js b/packages/plugins/i18n/admin/src/contentManagerHooks/mutateEditViewLayout.js similarity index 80% rename from packages/plugins/i18n/admin/src/middlewares/extendCMEditViewLayoutMiddleware.js rename to packages/plugins/i18n/admin/src/contentManagerHooks/mutateEditViewLayout.js index 1d0ca72a87..c29e776936 100644 --- a/packages/plugins/i18n/admin/src/middlewares/extendCMEditViewLayoutMiddleware.js +++ b/packages/plugins/i18n/admin/src/contentManagerHooks/mutateEditViewLayout.js @@ -97,60 +97,57 @@ const enhanceComponentLayoutForRelations = (layout, locale) => return enhancedRow; }); -const extendCMEditViewLayoutMiddleware = () => () => next => action => { - if (action.type !== 'ContentManager/EditViewLayoutManager/SET_LAYOUT') { - return next(action); - } +const getPathToContentType = pathArray => ['contentType', ...pathArray]; - const hasi18nEnabled = get( - action, +const mutateEditViewLayoutHook = ({ layout, query }) => { + const hasI18nEnabled = get( + layout, getPathToContentType(['pluginOptions', 'i18n', 'localized']), false ); - if (!hasi18nEnabled) { - return next(action); + if (!hasI18nEnabled) { + return { layout, query }; } - const currentLocale = get(action, ['query', 'plugins', 'i18n', 'locale'], null); + const currentLocale = get(query, ['plugins', 'i18n', 'locale'], null); // This might break the cm, has the user might be redirected to the homepage if (!currentLocale) { - return next(action); + return { layout, query }; } const editLayoutPath = getPathToContentType(['layouts', 'edit']); const editRelationsPath = getPathToContentType(['layouts', 'editRelations']); - const editLayout = get(action, editLayoutPath); - const editRelationsLayout = get(action, editRelationsPath); + const editLayout = get(layout, editLayoutPath); + const editRelationsLayout = get(layout, editRelationsPath); const nextEditRelationLayout = enhanceRelationLayout(editRelationsLayout, currentLocale); const nextEditLayout = enhanceEditLayout(editLayout); const enhancedLayouts = { - ...action.layout.contentType.layouts, + ...layout.contentType.layouts, editRelations: nextEditRelationLayout, edit: nextEditLayout, }; - const components = enhanceComponentsLayout(action.layout.components, currentLocale); - const enhancedAction = { - ...action, + const components = enhanceComponentsLayout(layout.components, currentLocale); + + const enhancedData = { + query, layout: { - ...action.layout, + ...layout, contentType: { - ...action.layout.contentType, + ...layout.contentType, layouts: enhancedLayouts, }, components, }, }; - return next(enhancedAction); + return enhancedData; }; -const getPathToContentType = pathArray => ['layout', 'contentType', ...pathArray]; - -export default extendCMEditViewLayoutMiddleware; +export default mutateEditViewLayoutHook; export { enhanceComponentLayoutForRelations, enhanceComponentsLayout, diff --git a/packages/plugins/i18n/admin/src/middlewares/tests/extendCMEditViewLayoutMiddleware.test.js b/packages/plugins/i18n/admin/src/contentManagerHooks/tests/mutateEditViewLayout.test.js similarity index 76% rename from packages/plugins/i18n/admin/src/middlewares/tests/extendCMEditViewLayoutMiddleware.test.js rename to packages/plugins/i18n/admin/src/contentManagerHooks/tests/mutateEditViewLayout.test.js index e50ba5d37f..8f89a19b7d 100644 --- a/packages/plugins/i18n/admin/src/middlewares/tests/extendCMEditViewLayoutMiddleware.test.js +++ b/packages/plugins/i18n/admin/src/contentManagerHooks/tests/mutateEditViewLayout.test.js @@ -1,102 +1,106 @@ import React from 'react'; import { Globe, GlobeCrossed } from '@buffetjs/icons'; import { getTrad } from '../../utils'; -import extendCMEditViewLayoutMiddleware, { +import mutateEditViewLayout, { enhanceComponentsLayout, enhanceEditLayout, enhanceRelationLayout, -} from '../extendCMEditViewLayoutMiddleware'; +} from '../mutateEditViewLayout'; const localizedTrad = getTrad('Field.localized'); const localizedTradDefaultMessage = 'This value is unique for the selected locale'; const notLocalizedTrad = getTrad('Field.not-localized'); const notLocalizedTradDefaultMessage = 'This value is common to all locales'; -describe('i18n | Middlewares | extendCMEditViewLayoutMiddleware', () => { - it('should forward the action if the type is undefined', () => { - const middleware = extendCMEditViewLayoutMiddleware(); - const action = { test: true, type: undefined }; - - const next = jest.fn(); - - middleware()(next)(action); - - expect(next).toBeCalledWith(action); - }); - - it('should forward if the type is not correct', () => { - const middleware = extendCMEditViewLayoutMiddleware(); - const action = { test: true, type: 'TEST' }; - - const next = jest.fn(); - - middleware()(next)(action); - - expect(next).toBeCalledWith(action); - }); - - describe('should forward when the type is ContentManager/EditViewLayoutManager/SET_LAYOUT', () => { - it('should forward when i18n is not enabled on the content type', () => { - const layout = { - components: {}, - contentType: { - uid: 'test', - pluginOptions: { i18n: { localized: false } }, - layouts: { - edit: ['test'], - }, +describe('i18n | contentManagerHooks | mutateEditViewLayout', () => { + it('should forward when i18n is not enabled on the content type', () => { + const layout = { + components: {}, + contentType: { + uid: 'test', + pluginOptions: { i18n: { localized: false } }, + layouts: { + edit: ['test'], }, - }; - const action = { - type: 'ContentManager/EditViewLayoutManager/SET_LAYOUT', - layout, - }; - const middleware = extendCMEditViewLayoutMiddleware(); - const next = jest.fn(); + }, + }; + const data = { + layout, + query: null, + }; + const results = mutateEditViewLayout(data); - middleware()(next)(action); + expect(results).toEqual(data); + }); - expect(next).toBeCalledWith(action); - }); + it('should forward the action when i18n is enabled and the query.locale is not defined', () => { + const layout = { + contentType: { + uid: 'test', + pluginOptions: { i18n: { localized: true } }, + layouts: { + edit: [], + editRelations: [ + { + fieldSchema: {}, + metadatas: {}, + name: 'addresses', + queryInfos: {}, + size: 6, + targetModelPluginOptions: {}, + }, + ], + }, + }, + }; - it('should forward the action when i18n is enabled and the query.locale is not defined', () => { - const layout = { - contentType: { - uid: 'test', - pluginOptions: { i18n: { localized: true } }, - layouts: { - edit: [], - editRelations: [ - { - fieldSchema: {}, - metadatas: {}, - name: 'addresses', - queryInfos: {}, - size: 6, - targetModelPluginOptions: {}, + const data = { + query: null, + layout, + }; + const results = mutateEditViewLayout(data); + + expect(results).toEqual(data); + }); + + it('should modify the editRelations layout when i18n is enabled and the query.locale is defined', () => { + const layout = { + contentType: { + uid: 'test', + pluginOptions: { i18n: { localized: true } }, + layouts: { + edit: [], + editRelations: [ + { + fieldSchema: {}, + metadatas: {}, + name: 'addresses', + queryInfos: { + test: true, + defaultParams: {}, + paramsToKeep: ['plugins.i18n.locale'], }, - ], - }, + size: 6, + targetModelPluginOptions: {}, + }, + ], }, - }; + }, + components: {}, + }; - const action = { - type: 'ContentManager/EditViewLayoutManager/SET_LAYOUT', - layout, - }; - const middleware = extendCMEditViewLayoutMiddleware(); + const data = { + layout, + query: { plugins: { i18n: { locale: 'en' } } }, + }; + const results = mutateEditViewLayout(data); - const next = jest.fn(); - middleware()(next)(action); - - expect(next).toBeCalledWith(action); - }); - - it('should modify the editRelations layout when i18n is enabled and the query.locale is defined', () => { - const layout = { + expect(results).toEqual({ + ...data, + layout: { + ...layout, contentType: { - uid: 'test', - pluginOptions: { i18n: { localized: true } }, + ...layout.contentType, layouts: { edit: [], editRelations: [ @@ -111,53 +115,15 @@ describe('i18n | Middlewares | extendCMEditViewLayoutMiddleware', () => { }, size: 6, targetModelPluginOptions: {}, + labelIcon: { + title: { id: localizedTrad, defaultMessage: localizedTradDefaultMessage }, + icon: , + }, }, ], }, }, - components: {}, - }; - - const action = { - type: 'ContentManager/EditViewLayoutManager/SET_LAYOUT', - layout, - query: { plugins: { i18n: { locale: 'en' } } }, - }; - const middleware = extendCMEditViewLayoutMiddleware(); - - const next = jest.fn(); - middleware()(next)(action); - - expect(next).toBeCalledWith({ - ...action, - layout: { - ...layout, - contentType: { - ...layout.contentType, - layouts: { - edit: [], - editRelations: [ - { - fieldSchema: {}, - metadatas: {}, - name: 'addresses', - queryInfos: { - test: true, - defaultParams: {}, - paramsToKeep: ['plugins.i18n.locale'], - }, - size: 6, - targetModelPluginOptions: {}, - labelIcon: { - title: { id: localizedTrad, defaultMessage: localizedTradDefaultMessage }, - icon: , - }, - }, - ], - }, - }, - }, - }); + }, }); }); diff --git a/packages/plugins/i18n/admin/src/index.js b/packages/plugins/i18n/admin/src/index.js index ee933d01e7..fafabf2738 100644 --- a/packages/plugins/i18n/admin/src/index.js +++ b/packages/plugins/i18n/admin/src/index.js @@ -19,6 +19,7 @@ import DeleteModalAdditionalInfos from './components/DeleteModalAdditionalInfos' import addLocaleToCollectionTypesLinksHook from './contentManagerHooks/addLocaleToCollectionTypesLinks'; import addLocaleToSingleTypesLinksHook from './contentManagerHooks/addLocaleToSingleTypesLinks'; import addColumnToTableHook from './contentManagerHooks/addColumnToTable'; +import mutateEditViewLayoutHook from './contentManagerHooks/mutateEditViewLayout'; const pluginDescription = pluginPkg.strapi.description || pluginPkg.description; const icon = pluginPkg.strapi.icon; @@ -50,6 +51,8 @@ export default { app.registerHook('CM/pages/App/mutate-single-types-links', addLocaleToSingleTypesLinksHook); // Hook that adds a column into the CM's LV table app.registerHook('CM/pages/ListView/inject-column-in-table', addColumnToTableHook); + // Hooks that mutates the edit view layout + app.registerHook('CM/pages/EditView/mutate-edit-view-layout', mutateEditViewLayoutHook); // Add the settings link app.addSettingsLink('global', { intlLabel: { diff --git a/packages/plugins/i18n/admin/src/middlewares/index.js b/packages/plugins/i18n/admin/src/middlewares/index.js index ca3a0220e7..64244e30b5 100644 --- a/packages/plugins/i18n/admin/src/middlewares/index.js +++ b/packages/plugins/i18n/admin/src/middlewares/index.js @@ -1,12 +1,10 @@ import addCommonFieldsToInitialDataMiddleware from './addCommonFieldsToInitialDataMiddleware'; -import extendCMEditViewLayoutMiddleware from './extendCMEditViewLayoutMiddleware'; import extendCTBInitialDataMiddleware from './extendCTBInitialDataMiddleware'; import extendCTBAttributeInitialDataMiddleware from './extendCTBAttributeInitialDataMiddleware'; import localePermissionMiddleware from './localePermissionMiddleware'; const middlewares = [ addCommonFieldsToInitialDataMiddleware, - extendCMEditViewLayoutMiddleware, extendCTBInitialDataMiddleware, extendCTBAttributeInitialDataMiddleware, localePermissionMiddleware,