diff --git a/packages/core/admin/admin/src/StrapiApp.js b/packages/core/admin/admin/src/StrapiApp.js index a7177d0a14..59def16361 100644 --- a/packages/core/admin/admin/src/StrapiApp.js +++ b/packages/core/admin/admin/src/StrapiApp.js @@ -153,21 +153,6 @@ class StrapiApp { }); }; - async initialize() { - Object.keys(this.appPlugins).forEach(plugin => { - this.appPlugins[plugin].register({ - addComponents: this.addComponents, - addCorePluginMenuLink: this.addCorePluginMenuLink, - addFields: this.addFields, - addMenuLink: this.addMenuLink, - addMiddlewares: this.addMiddlewares, - addReducers: this.addReducers, - createSettingSection: this.createSettingSection, - registerPlugin: this.registerPlugin, - }); - }); - } - async boot() { Object.keys(this.appPlugins).forEach(plugin => { const boot = this.appPlugins[plugin].boot; @@ -177,11 +162,16 @@ class StrapiApp { addSettingsLink: this.addSettingsLink, addSettingsLinks: this.addSettingsLinks, getPlugin: this.getPlugin, + registerHook: this.registerHook, }); } }); } + createHook = name => { + this.hooksDict[name] = createHook(); + }; + createSettingSection = (section, links) => { invariant(section.id, 'section.id should be defined'); invariant( @@ -209,6 +199,22 @@ class StrapiApp { return this.plugins[pluginId]; }; + async initialize() { + Object.keys(this.appPlugins).forEach(plugin => { + this.appPlugins[plugin].register({ + addComponents: this.addComponents, + addCorePluginMenuLink: this.addCorePluginMenuLink, + addFields: this.addFields, + addMenuLink: this.addMenuLink, + addMiddlewares: this.addMiddlewares, + addReducers: this.addReducers, + createHook: this.createHook, + createSettingSection: this.createSettingSection, + registerPlugin: this.registerPlugin, + }); + }); + } + async loadAdminTrads() { const arrayOfPromises = this.appLocales.map(locale => { return import(/* webpackChunkName: "[request]" */ `./translations/${locale}.json`) @@ -270,20 +276,16 @@ class StrapiApp { return Promise.resolve(); } + registerHook = (name, fn) => { + this.hooksDict[name].register(fn); + }; + registerPlugin = pluginConf => { const plugin = Plugin(pluginConf); this.plugins[plugin.pluginId] = plugin; }; - createHook = name => { - this.hooksDict[name] = createHook(); - }; - - registerHook = (name, fn) => { - this.hooksDict[name].register(fn); - }; - runHookSeries = (name, asynchronous = false) => asynchronous ? this.hooksDict[name].runSeriesAsync() : this.hooksDict[name].runSeries(); diff --git a/packages/core/content-manager/admin/src/index.js b/packages/core/content-manager/admin/src/index.js index e56705bd19..51be4181ff 100644 --- a/packages/core/content-manager/admin/src/index.js +++ b/packages/core/content-manager/admin/src/index.js @@ -17,7 +17,6 @@ const name = pluginPkg.strapi.name; export default { register(app) { - app.addReducers(reducers); app.addCorePluginMenuLink({ to: `/plugins/${pluginId}`, icon: 'book-open', @@ -28,6 +27,11 @@ export default { permissions: pluginPermissions.main, }); + app.addReducers(reducers); + + // Hook that allows to mutate the displayed headers of the list view table + app.createHook('cm/inject-column-in-table'); + app.registerPlugin({ description: pluginDescription, icon, diff --git a/packages/core/content-manager/admin/src/pages/ListView/index.js b/packages/core/content-manager/admin/src/pages/ListView/index.js index 3d7e128de2..8943475365 100644 --- a/packages/core/content-manager/admin/src/pages/ListView/index.js +++ b/packages/core/content-manager/admin/src/pages/ListView/index.js @@ -18,6 +18,7 @@ import { useNotification, useQueryParams, useRBACProvider, + useStrapiApp, request, } from '@strapi/helper-plugin'; import pluginId from '../../pluginId'; @@ -93,6 +94,14 @@ function ListView({ const trackUsageRef = useRef(trackUsage); const fetchPermissionsRef = useRef(refetchPermissions); + const { runHookWaterfall } = useStrapiApp(); + + const tableHeaders = useMemo(() => { + const headers = runHookWaterfall('cm/inject-column-in-table', { displayedHeaders, layout }); + + return headers; + }, [runHookWaterfall, displayedHeaders, layout]); + const [{ query }, setQuery] = useQueryParams(); const params = buildQueryString(query); @@ -466,7 +475,7 @@ function ListView({ canCreate={canCreate} canDelete={canDelete} canUpdate={canUpdate} - displayedHeaders={displayedHeaders} + displayedHeaders={tableHeaders} hasDraftAndPublish={hasDraftAndPublish} isBulkable={isBulkable} setQuery={setQuery} diff --git a/packages/plugins/i18n/admin/src/components/LocaleListCell/LocaleListCell.js b/packages/plugins/i18n/admin/src/components/LocaleListCell/LocaleListCell.js index 2414cb90c6..3f9871ccb1 100644 --- a/packages/plugins/i18n/admin/src/components/LocaleListCell/LocaleListCell.js +++ b/packages/plugins/i18n/admin/src/components/LocaleListCell/LocaleListCell.js @@ -1,9 +1,11 @@ import React from 'react'; import PropTypes from 'prop-types'; +import { useSelector } from 'react-redux'; import { Padded, Text } from '@buffetjs/core'; import { Tooltip } from '@buffetjs/styles'; import get from 'lodash/get'; import styled from 'styled-components'; +import selectI18NLocales from '../../selectors/selectI18nLocales'; const mapToLocaleName = (locales, localeCode) => get( @@ -18,7 +20,8 @@ const LocaleName = styled.div` white-space: nowrap; `; -const LocaleListCell = ({ locales, localizations, locale: currentLocaleCode, id }) => { +const LocaleListCell = ({ localizations, locale: currentLocaleCode, id }) => { + const locales = useSelector(selectI18NLocales); const allLocalizations = [{ locale: currentLocaleCode }, ...localizations]; const localizationNames = allLocalizations.map(locale => locale.locale); const defaultLocale = locales.find(locale => locale.isDefault); @@ -77,13 +80,6 @@ LocaleListCell.propTypes = { locale: PropTypes.string.isRequired, }) ).isRequired, - locales: PropTypes.arrayOf( - PropTypes.shape({ - name: PropTypes.string.isRequired, - code: PropTypes.string.isRequired, - isDefault: PropTypes.bool, - }) - ).isRequired, locale: PropTypes.string.isRequired, }; diff --git a/packages/plugins/i18n/admin/src/components/LocaleListCell/tests/LocaleListCell.test.js b/packages/plugins/i18n/admin/src/components/LocaleListCell/tests/LocaleListCell.test.js index 0717eb46fb..29d7b00dbb 100644 --- a/packages/plugins/i18n/admin/src/components/LocaleListCell/tests/LocaleListCell.test.js +++ b/packages/plugins/i18n/admin/src/components/LocaleListCell/tests/LocaleListCell.test.js @@ -1,5 +1,6 @@ import React from 'react'; import { render, screen } from '@testing-library/react'; +import { useSelector } from 'react-redux'; import LocaleListCell from '../LocaleListCell'; jest.mock('@buffetjs/styles', () => ({ @@ -11,6 +12,10 @@ jest.mock('@buffetjs/core', () => ({ Text: props =>
, })); +jest.mock('react-redux', () => ({ + useSelector: jest.fn(() => []), +})); + describe('LocaleListCell', () => { it('returns the default locale first, then the others sorted alphabetically', () => { const locales = [ @@ -40,6 +45,8 @@ describe('LocaleListCell', () => { }, ]; + useSelector.mockImplementation(() => locales); + const locale = 'en'; const localizations = [{ locale: 'fr-FR' }, { locale: 'ar' }]; @@ -78,6 +85,8 @@ describe('LocaleListCell', () => { }, ]; + useSelector.mockImplementation(() => locales); + const locale = 'en'; const localizations = [{ locale: 'ar' }]; @@ -115,6 +124,7 @@ describe('LocaleListCell', () => { isDefault: false, }, ]; + useSelector.mockImplementation(() => locales); const locale = 'fr-FR'; const localizations = [{ locale: 'en' }, { locale: 'ar' }]; diff --git a/packages/plugins/i18n/admin/src/contentManagerHooks/addColumnToTable.js b/packages/plugins/i18n/admin/src/contentManagerHooks/addColumnToTable.js new file mode 100644 index 0000000000..0a38339a62 --- /dev/null +++ b/packages/plugins/i18n/admin/src/contentManagerHooks/addColumnToTable.js @@ -0,0 +1,24 @@ +import React from 'react'; +import get from 'lodash/get'; +import LocaleListCell from '../components/LocaleListCell/LocaleListCell'; + +const addColumnToTableHook = ({ displayedHeaders, layout }) => { + const isFieldLocalized = get(layout, 'contentType.pluginOptions.i18n.localized', false); + + if (!isFieldLocalized) { + return displayedHeaders; + } + + return [ + ...displayedHeaders, + { + key: '__locale_key__', + fieldSchema: { type: 'string' }, + metadatas: { label: 'Content available in', searchable: false, sortable: false }, + name: 'locales', + cellFormatter: props =>