diff --git a/src/components/theme/CustomCSS/CustomCSS.jsx b/src/components/theme/CustomCSS/CustomCSS.jsx index 5bd1c52e..1beb9458 100644 --- a/src/components/theme/CustomCSS/CustomCSS.jsx +++ b/src/components/theme/CustomCSS/CustomCSS.jsx @@ -2,11 +2,11 @@ import React from 'react'; import config from '@plone/volto/registry'; const CustomCSS = (props) => { + const href = `${config.settings.apiPath}/voltoCustom.css`; return ( - + <> + + ); }; export default CustomCSS; diff --git a/src/components/theme/Widgets/TopicsWidget.test.jsx b/src/components/theme/Widgets/TopicsWidget.test.jsx index 478de267..aee36b02 100644 --- a/src/components/theme/Widgets/TopicsWidget.test.jsx +++ b/src/components/theme/Widgets/TopicsWidget.test.jsx @@ -18,12 +18,17 @@ describe('TopicsWidget Component', () => { }, }); + const tags = [ + { title: 'Environment', token: '1' }, + { title: 'Climate', token: '2' }, + ]; + const { container } = render( {tagTitle}} className={'test'} /> diff --git a/src/customizations/volto/components/manage/Blocks/Image/Edit.jsx b/src/customizations/volto/components/manage/Blocks/Image/Edit.jsx index 55f2f505..5113d56e 100644 --- a/src/customizations/volto/components/manage/Blocks/Image/Edit.jsx +++ b/src/customizations/volto/components/manage/Blocks/Image/Edit.jsx @@ -313,6 +313,7 @@ class Edit extends Component { '@id': data.url, image_field: data.image_field, image_scales: data.image_scales, + data: data, } : undefined } @@ -323,7 +324,9 @@ class Edit extends Component { ? // Backwards compat in the case that the block is storing the full server URL (() => { if (data.size === 'l') - return `${flattenToAppURL(data.url)}/@@images/image`; + return `${flattenToAppURL( + data.url, + )}/@@images/image/large`; if (data.size === 'm') return `${flattenToAppURL( data.url, @@ -332,7 +335,9 @@ class Edit extends Component { return `${flattenToAppURL( data.url, )}/@@images/image/mini`; - return `${flattenToAppURL(data.url)}/@@images/image`; + return `${flattenToAppURL( + data.url, + )}/@@images/image/large`; })() : data.url } diff --git a/src/customizations/volto/components/manage/Blocks/Image/View.jsx b/src/customizations/volto/components/manage/Blocks/Image/View.jsx index d42fed1f..9e501266 100644 --- a/src/customizations/volto/components/manage/Blocks/Image/View.jsx +++ b/src/customizations/volto/components/manage/Blocks/Image/View.jsx @@ -76,6 +76,7 @@ export const View = (props) => { '@id': data.url, image_field: data.image_field, image_scales: data.image_scales, + data: data, } : undefined } @@ -88,7 +89,7 @@ export const View = (props) => { if (data.size === 'l') return `${flattenToAppURL( data.url, - )}/@@images/image`; + )}/@@images/image/large`; if (data.size === 'm') return `${flattenToAppURL( data.url, @@ -99,7 +100,7 @@ export const View = (props) => { )}/@@images/image/mini`; return `${flattenToAppURL( data.url, - )}/@@images/image`; + )}/@@images/image/large`; })() : data.url } diff --git a/src/customizations/volto/components/manage/Blocks/Image/schema.js b/src/customizations/volto/components/manage/Blocks/Image/schema.js index 63bc6d84..864130ba 100644 --- a/src/customizations/volto/components/manage/Blocks/Image/schema.js +++ b/src/customizations/volto/components/manage/Blocks/Image/schema.js @@ -95,10 +95,12 @@ export function ImageSchema({ formData, intl }) { align: { title: intl.formatMessage(messages.Align), widget: 'align', + default: 'center', }, size: { title: intl.formatMessage(messages.size), widget: 'image_size', + default: 'l', }, href: { title: intl.formatMessage(messages.LinkTo), diff --git a/src/customizations/volto/components/theme/Header/Header.jsx b/src/customizations/volto/components/theme/Header/Header.jsx index 5f439935..3b9d4c44 100644 --- a/src/customizations/volto/components/theme/Header/Header.jsx +++ b/src/customizations/volto/components/theme/Header/Header.jsx @@ -9,16 +9,10 @@ import { connect, useDispatch, useSelector } from 'react-redux'; import { withRouter } from 'react-router-dom'; import { UniversalLink } from '@plone/volto/components'; -import { - getBaseUrl, - hasApiExpander, - flattenToAppURL, -} from '@plone/volto/helpers'; +import { getBaseUrl, hasApiExpander } from '@plone/volto/helpers'; import { getNavigation } from '@plone/volto/actions'; import { Header, Logo } from '@eeacms/volto-eea-design-system/ui'; import { usePrevious } from '@eeacms/volto-eea-design-system/helpers'; -import { find } from 'lodash'; -import globeIcon from '@eeacms/volto-eea-design-system/../theme/themes/eea/assets/images/Header/global-line.svg'; import eeaFlag from '@eeacms/volto-eea-design-system/../theme/themes/eea/assets/images/Header/eea.png'; import config from '@plone/volto/registry'; @@ -26,6 +20,9 @@ import { compose } from 'recompose'; import { BodyClass } from '@plone/volto/helpers'; import cx from 'classnames'; +import loadable from '@loadable/component'; + +const LazyLanguageSwitcher = loadable(() => import('./LanguageSwitcher')); function removeTrailingSlash(path) { return path.replace(/\/+$/, ''); @@ -35,11 +32,6 @@ function removeTrailingSlash(path) { * EEA Specific Header component. */ const EEAHeader = ({ pathname, token, items, history, subsite }) => { - const currentLang = useSelector((state) => state.intl.locale); - const translations = useSelector( - (state) => state.content.data?.['@components']?.translations?.items, - ); - const router_pathname = useSelector((state) => { return removeTrailingSlash(state.router?.location?.pathname) || ''; }); @@ -61,31 +53,25 @@ const EEAHeader = ({ pathname, token, items, history, subsite }) => { const { eea } = config.settings; const headerOpts = eea.headerOpts || {}; const headerSearchBox = eea.headerSearchBox || []; - const { logo, logoWhite } = headerOpts || {}; + const { logo, logoWhite } = headerOpts; const width = useSelector((state) => state.screen?.width); const dispatch = useDispatch(); const previousToken = usePrevious(token); - const [language, setLanguage] = React.useState( - currentLang || eea.defaultLanguage, - ); React.useEffect(() => { - const { settings } = config; const base_url = getBaseUrl(pathname); + const { settings } = config; + + // Check if navigation data needs to be fetched based on the API expander availability if (!hasApiExpander('navigation', base_url)) { dispatch(getNavigation(base_url, settings.navDepth)); } - }, [pathname, dispatch]); - React.useEffect(() => { + // Additional check for token changes if (token !== previousToken) { - const { settings } = config; - const base = getBaseUrl(pathname); - if (!hasApiExpander('navigation', base)) { - dispatch(getNavigation(base, settings.navDepth)); - } + dispatch(getNavigation(base_url, settings.navDepth)); } - }, [token, dispatch, pathname, previousToken]); + }, [pathname, token, dispatch, previousToken]); return (
@@ -155,50 +141,7 @@ const EEAHeader = ({ pathname, token, items, history, subsite }) => { {config.settings.isMultilingual && config.settings.supportedLanguages.length > 1 && config.settings.hasLanguageDropdown && ( - - } - viewportWidth={width} - > -
    - {eea.languages.map((item, index) => ( - - {item.name} - - {item.code.toUpperCase()} - - - } - onClick={() => { - const translation = find(translations, { - language: item.code, - }); - const to = translation - ? flattenToAppURL(translation['@id']) - : `/${item.code}`; - setLanguage(item.code); - history.push(to); - }} - > - ))} -
-
+ )} + await require('@plone/volto/helpers/Loadable/Loadable').__setLoadables(), +); + describe('Header', () => { - it('renders a header component', () => { + it('renders a header component with homepage_inverse_view layout', () => { const store = mockStore({ userSession: { token: null }, intl: { @@ -21,7 +35,7 @@ describe('Header', () => { messages: {}, }, navigation: { - items: ['en'], + items: [item], }, content: { data: { @@ -38,22 +52,23 @@ describe('Header', () => { config.settings = { ...config.settings, eea: { + ...config.settings.eea, headerOpts: undefined, + logoTargetUrl: '/', }, }; - const component = renderer.create( + const { container } = render(
, ); - const json = component.toJSON(); - expect(json).toMatchSnapshot(); + expect(container).toMatchSnapshot(); }); - it('renders a header component', () => { + it('renders a header component with homepage_view layout and translations', async () => { const store = mockStore({ userSession: { token: null }, intl: { @@ -61,47 +76,7 @@ describe('Header', () => { messages: {}, }, navigation: { - items: ['en'], - }, - content: { - data: { - layout: 'homepage_inverse_view', - }, - }, - router: { - location: { - pathname: '/home/', - }, - }, - }); - - config.settings = { - ...config.settings, - eea: { - headerOpts: {}, - }, - }; - - const component = renderer.create( - - -
- - , - ); - const json = component.toJSON(); - expect(json).toMatchSnapshot(); - }); - - it('renders a header component', () => { - const store = mockStore({ - userSession: { token: null }, - intl: { - locale: undefined, - messages: {}, - }, - navigation: { - items: ['en'], + items: [item], }, content: { data: { @@ -123,6 +98,7 @@ describe('Header', () => { config.settings = { ...config.settings, eea: { + ...config.settings.eea, headerOpts: { partnerLinks: { links: [{ href: '/link1', title: 'link 1' }], @@ -145,6 +121,9 @@ describe('Header', () => { ); fireEvent.click(container.querySelector('.content')); + await waitFor(() => { + expect(container.querySelector('.country-code')).not.toBeNull(); + }); fireEvent.keyDown(container.querySelector('.content'), { keyCode: 37 }); fireEvent.keyDown(container.querySelector('.content a'), { keyCode: 37 }); fireEvent.keyDown(container.querySelector('a[href="/link1"]'), { @@ -152,7 +131,7 @@ describe('Header', () => { }); fireEvent.click(container.querySelector('.country-code')); - // expect(getByText('da')).toBeInTheDocument(); + expect(getByText(container, 'RO')).toBeInTheDocument(); rerender( @@ -163,15 +142,15 @@ describe('Header', () => { ); }); - it('renders a header component', () => { + it('renders a header component with a subsite', async () => { const store = mockStore({ userSession: { token: null }, intl: { - locale: undefined, + locale: 'en', messages: {}, }, navigation: { - items: ['en'], + items: [item], }, content: { data: { @@ -179,6 +158,7 @@ describe('Header', () => { '@components': { subsite: { '@type': 'Subsite', + '@id': 'http://localhost:8080/Plone/subsite', title: 'Home Page', subsite_logo: { scales: { @@ -205,6 +185,7 @@ describe('Header', () => { config.settings = { ...config.settings, eea: { + ...config.settings.eea, headerOpts: { partnerLinks: { links: [{ href: '/link1', title: 'link 1' }], @@ -227,6 +208,9 @@ describe('Header', () => { ); fireEvent.click(container.querySelector('.content')); + await waitFor(() => { + expect(container.querySelector('.country-code')).not.toBeNull(); + }); fireEvent.keyDown(container.querySelector('.content'), { keyCode: 37 }); fireEvent.keyDown(container.querySelector('.content a'), { keyCode: 37 }); fireEvent.keyDown(container.querySelector('a[href="/link1"]'), { @@ -234,7 +218,7 @@ describe('Header', () => { }); fireEvent.click(container.querySelector('.country-code')); - // expect(getByText('da')).toBeInTheDocument(); + expect(getByText(container, 'RO')).toBeInTheDocument(); rerender( @@ -245,17 +229,17 @@ describe('Header', () => { ); }); - it('renders a header component', () => { + it('renders a header component with a subsite and two children', async () => { const store = mockStore({ userSession: { token: null }, intl: { - locale: undefined, + locale: 'en', messages: {}, }, navigation: { items: [ { url: '/test1', title: 'test 1', nav_title: 'Test 1', items: [] }, - { url: undefined, title: 'test 2', items: [] }, + { url: '/test2', title: 'test 2', items: [] }, ], }, content: { @@ -264,6 +248,7 @@ describe('Header', () => { '@components': { subsite: { '@type': 'Subsite', + '@id': 'http://localhost:8080/Plone/subsite', title: 'Home Page', subsite_logo: undefined, }, @@ -283,6 +268,7 @@ describe('Header', () => { config.settings = { ...config.settings, eea: { + ...config.settings.eea, headerOpts: { partnerLinks: { links: [{ href: '/link1', title: 'link 1' }], @@ -305,6 +291,9 @@ describe('Header', () => { ); fireEvent.click(container.querySelector('.content')); + await waitFor(() => { + expect(container.querySelector('.country-code')).not.toBeNull(); + }); fireEvent.keyDown(container.querySelector('.content'), { keyCode: 37 }); fireEvent.keyDown(container.querySelector('.content a'), { keyCode: 37 }); fireEvent.keyDown(container.querySelector('a[href="/link1"]'), { @@ -313,7 +302,7 @@ describe('Header', () => { fireEvent.click(container.querySelector('.country-code')); fireEvent.click(container.querySelector('a[href="/test1"]')); - // expect(getByText('da')).toBeInTheDocument(); + expect(getByText(container, 'RO')).toBeInTheDocument(); rerender( diff --git a/src/customizations/volto/components/theme/Header/LanguageSwitcher.jsx b/src/customizations/volto/components/theme/Header/LanguageSwitcher.jsx new file mode 100644 index 00000000..347e4dba --- /dev/null +++ b/src/customizations/volto/components/theme/Header/LanguageSwitcher.jsx @@ -0,0 +1,71 @@ +import React from 'react'; +import { useSelector } from 'react-redux'; +import { Dropdown, Image } from 'semantic-ui-react'; +import { flattenToAppURL } from '@plone/volto/helpers'; +import { find } from 'lodash'; +import globeIcon from '@eeacms/volto-eea-design-system/../theme/themes/eea/assets/images/Header/global-line.svg'; +import config from '@plone/volto/registry'; +import { Header } from '@eeacms/volto-eea-design-system/ui'; + +/** + * LanguageSwitcher component. + * Provides a dropdown menu for language selection, changing the application's + * language and navigating to the corresponding translated URL. + * + * @param {Object} props - The component props. + * @param {number} props.width - The viewport width to adjust the dropdown display. + * @param {Object} props.history - The history object from React Router for navigation. + */ +const LanguageSwitcher = ({ width, history }) => { + const currentLang = useSelector((state) => state.intl.locale); + const translations = useSelector( + (state) => state.content.data?.['@components']?.translations?.items, + ); + const { eea } = config.settings; + + const [language, setLanguage] = React.useState( + currentLang || eea.defaultLanguage, + ); + + return ( + } + viewportWidth={width} + > +
    + {eea.languages.map((item, index) => ( + + {item.name} + {item.code.toUpperCase()} + + } + onClick={() => { + const translation = find(translations, { + language: item.code, + }); + const to = translation + ? flattenToAppURL(translation['@id']) + : `/${item.code}`; + setLanguage(item.code); + history.push(to); + }} + > + ))} +
+
+ ); +}; + +export default LanguageSwitcher; diff --git a/src/customizations/volto/components/theme/Header/__snapshots__/Header.test.jsx.snap b/src/customizations/volto/components/theme/Header/__snapshots__/Header.test.jsx.snap index 8faead67..05d532b5 100644 --- a/src/customizations/volto/components/theme/Header/__snapshots__/Header.test.jsx.snap +++ b/src/customizations/volto/components/theme/Header/__snapshots__/Header.test.jsx.snap @@ -1,309 +1,145 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Header renders a header component 1`] = ` -
-
+
- European Union flag
-
- An official website of the European Union | How do you know? -
-