@eeacms/volto-eea-website-theme 4.3.1 → 4.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file. Dates are d
|
|
|
4
4
|
|
|
5
5
|
Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
|
|
6
6
|
|
|
7
|
+
### [4.3.2](https://github.com/eea/volto-eea-website-theme/compare/4.3.1...4.3.2) - 15 May 2026
|
|
8
|
+
|
|
9
|
+
#### :bug: Bug Fixes
|
|
10
|
+
|
|
11
|
+
- fix(Header): use navroot language to fetch navigation, fix subsite case - refs #303244 [Miu Razvan - [`c25711e`](https://github.com/eea/volto-eea-website-theme/commit/c25711ec3f8492d442c9a130e5e4cb9feb1e80ad)]
|
|
12
|
+
|
|
7
13
|
### [4.3.1](https://github.com/eea/volto-eea-website-theme/compare/4.3.0...4.3.1) - 14 May 2026
|
|
8
14
|
|
|
9
15
|
#### :bug: Bug Fixes
|
package/package.json
CHANGED
|
@@ -3,26 +3,24 @@
|
|
|
3
3
|
* @module components/theme/Header/Header
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import
|
|
7
|
-
import
|
|
6
|
+
import loadable from '@loadable/component';
|
|
7
|
+
import cx from 'classnames';
|
|
8
|
+
import { useEffect, useMemo, useRef } from 'react';
|
|
8
9
|
import { connect, useDispatch, useSelector } from 'react-redux';
|
|
9
|
-
|
|
10
10
|
import { withRouter } from 'react-router-dom';
|
|
11
|
+
import { compose } from 'redux';
|
|
12
|
+
import { Dropdown, Image } from 'semantic-ui-react';
|
|
13
|
+
|
|
14
|
+
import { getNavigation } from '@plone/volto/actions/navigation/navigation';
|
|
11
15
|
import UniversalLink from '@plone/volto/components/manage/UniversalLink/UniversalLink';
|
|
12
16
|
import { getBaseUrl } from '@plone/volto/helpers/Url/Url';
|
|
13
17
|
import { hasApiExpander } from '@plone/volto/helpers/Utils/Utils';
|
|
14
|
-
import { getNavigation } from '@plone/volto/actions/navigation/navigation';
|
|
15
|
-
import { getNavigationSettings } from '@eeacms/volto-eea-website-theme/actions';
|
|
16
|
-
import Header from '@eeacms/volto-eea-design-system/ui/Header/Header';
|
|
17
|
-
import EEALogo from '@eeacms/volto-eea-website-theme/components/theme/Logo';
|
|
18
|
-
import { usePrevious } from '@eeacms/volto-eea-design-system/helpers';
|
|
19
|
-
import eeaFlag from '@eeacms/volto-eea-design-system/../theme/themes/eea/assets/images/Header/eea.png';
|
|
20
|
-
|
|
21
18
|
import config from '@plone/volto/registry';
|
|
22
|
-
import { compose } from 'redux';
|
|
23
19
|
|
|
24
|
-
import
|
|
25
|
-
import
|
|
20
|
+
import eeaFlag from '@eeacms/volto-eea-design-system/../theme/themes/eea/assets/images/Header/eea.png';
|
|
21
|
+
import Header from '@eeacms/volto-eea-design-system/ui/Header/Header';
|
|
22
|
+
import { getNavigationSettings } from '@eeacms/volto-eea-website-theme/actions';
|
|
23
|
+
import EEALogo from '@eeacms/volto-eea-website-theme/components/theme/Logo';
|
|
26
24
|
|
|
27
25
|
const LazyLanguageSwitcher = loadable(() => import('./LanguageSwitcher'));
|
|
28
26
|
const EMPTY_NAVIGATION_SETTINGS = {};
|
|
@@ -32,15 +30,79 @@ function removeTrailingSlash(path) {
|
|
|
32
30
|
}
|
|
33
31
|
|
|
34
32
|
/**
|
|
35
|
-
*
|
|
33
|
+
* Merge backend navigation settings into the config-level menu layouts.
|
|
36
34
|
*/
|
|
37
|
-
|
|
38
|
-
const
|
|
39
|
-
|
|
35
|
+
function buildEnhancedLayouts(items, navigationSettings) {
|
|
36
|
+
const configLayouts = config.settings?.menuItemsLayouts || {};
|
|
37
|
+
const enhancedLayouts = { ...configLayouts };
|
|
38
|
+
|
|
39
|
+
if (!items) return enhancedLayouts;
|
|
40
|
+
|
|
41
|
+
items.forEach(() => {
|
|
42
|
+
Object.keys(navigationSettings).forEach((routeId) => {
|
|
43
|
+
const route = navigationSettings[routeId];
|
|
44
|
+
const backendSettings = {};
|
|
45
|
+
|
|
46
|
+
if (route.hideChildrenFromNavigation !== undefined) {
|
|
47
|
+
backendSettings.hideChildrenFromNavigation =
|
|
48
|
+
route.hideChildrenFromNavigation;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (route.menuItemChildrenListColumns !== undefined) {
|
|
52
|
+
backendSettings.menuItemChildrenListColumns = Array.isArray(
|
|
53
|
+
route.menuItemChildrenListColumns,
|
|
54
|
+
)
|
|
55
|
+
? route.menuItemChildrenListColumns
|
|
56
|
+
.map((val) => (typeof val === 'string' ? parseInt(val, 10) : val))
|
|
57
|
+
.filter((val) => !isNaN(val))
|
|
58
|
+
: route.menuItemChildrenListColumns;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (route.menuItemColumns !== undefined) {
|
|
62
|
+
backendSettings.menuItemColumns = route.menuItemColumns;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (Object.keys(backendSettings).length > 0) {
|
|
66
|
+
enhancedLayouts[routeId] = {
|
|
67
|
+
...enhancedLayouts[routeId],
|
|
68
|
+
...backendSettings,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
});
|
|
40
72
|
});
|
|
41
73
|
|
|
74
|
+
return enhancedLayouts;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* EEA Specific Header component.
|
|
79
|
+
*/
|
|
80
|
+
const EEAHeader = ({ pathname, token, items, history, navroot, subsite }) => {
|
|
81
|
+
// Config / static derived values
|
|
82
|
+
const { eea } = config.settings;
|
|
83
|
+
const headerOpts = eea.headerOpts || {};
|
|
84
|
+
const { logo, logoWhite } = headerOpts;
|
|
85
|
+
|
|
42
86
|
const isSubsite = subsite?.['@type'] === 'Subsite';
|
|
43
87
|
|
|
88
|
+
// Redux state
|
|
89
|
+
const dispatch = useDispatch();
|
|
90
|
+
const width = useSelector((state) => state.screen?.width);
|
|
91
|
+
|
|
92
|
+
const router_pathname = useSelector(
|
|
93
|
+
(state) => removeTrailingSlash(state.router?.location?.pathname) || '',
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
const headerSettings = useSelector(
|
|
97
|
+
(state) => state.reduxAsyncConnect?.headerSettings,
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
const navigationSettings =
|
|
101
|
+
useSelector((state) => state.navigationSettings?.settings) ||
|
|
102
|
+
EMPTY_NAVIGATION_SETTINGS;
|
|
103
|
+
|
|
104
|
+
const updateRequest = useSelector((state) => state.content.update);
|
|
105
|
+
|
|
44
106
|
const isHomePageInverse = useSelector((state) => {
|
|
45
107
|
const layout = state.content?.data?.layout;
|
|
46
108
|
const has_home_layout =
|
|
@@ -55,85 +117,69 @@ const EEAHeader = ({ pathname, token, items, history, subsite }) => {
|
|
|
55
117
|
);
|
|
56
118
|
});
|
|
57
119
|
|
|
58
|
-
const
|
|
59
|
-
const headerOpts = eea.headerOpts || {};
|
|
60
|
-
const { logo, logoWhite } = headerOpts;
|
|
61
|
-
const width = useSelector((state) => state.screen?.width);
|
|
62
|
-
const dispatch = useDispatch();
|
|
63
|
-
|
|
64
|
-
const headerSettings = useSelector(
|
|
65
|
-
(state) => state.reduxAsyncConnect?.headerSettings,
|
|
66
|
-
);
|
|
120
|
+
const prevTokenRef = useRef(undefined);
|
|
67
121
|
|
|
122
|
+
// Derived / memoized values
|
|
68
123
|
const headerSearchBox =
|
|
69
124
|
headerSettings?.searchBox || eea.headerSearchBox || [];
|
|
70
|
-
const previousToken = usePrevious(token);
|
|
71
|
-
const navigationSettings =
|
|
72
|
-
useSelector((state) => state.navigationSettings?.settings) ||
|
|
73
|
-
EMPTY_NAVIGATION_SETTINGS;
|
|
74
|
-
const updateRequest = useSelector((state) => state.content.update);
|
|
75
|
-
|
|
76
|
-
// Combine navigation settings from backend with config fallback
|
|
77
|
-
const configLayouts = config.settings?.menuItemsLayouts || {};
|
|
78
|
-
const enhancedLayouts = { ...configLayouts };
|
|
79
125
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
126
|
+
const enhancedLayouts = buildEnhancedLayouts(items, navigationSettings);
|
|
127
|
+
|
|
128
|
+
// Prefer navroot.language; fall back to extracting language from pathname
|
|
129
|
+
// (validated against supportedLanguages) when navroot is not yet loaded.
|
|
130
|
+
const navrootLang = useMemo(() => {
|
|
131
|
+
const { supportedLanguages, navigationLanguage } = config.settings;
|
|
132
|
+
if (navroot?.language?.token) return navroot.language.token;
|
|
133
|
+
const supported = supportedLanguages || [];
|
|
134
|
+
const first = pathname.split('/').filter(Boolean)[0];
|
|
135
|
+
if (first === undefined) return navigationLanguage || null;
|
|
136
|
+
return supported.includes(first) ? first : null;
|
|
137
|
+
}, [navroot, pathname]);
|
|
138
|
+
|
|
139
|
+
// Normalize pathname for menu active-item matching when using
|
|
140
|
+
// navigationLanguage. Menu items come from the configured language; rewrite
|
|
141
|
+
// the current language prefix to match. E.g. navLang='en' on /fr/topics ->
|
|
142
|
+
// /en/topics. Uses navroot.language as source of truth instead of parsing
|
|
143
|
+
// the first path segment.
|
|
144
|
+
const normalizedPathname = useMemo(() => {
|
|
145
|
+
const navLang = config.settings.navigationLanguage;
|
|
146
|
+
if (!navLang || !navrootLang || navrootLang === navLang) return pathname;
|
|
92
147
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
typeof val === 'string' ? parseInt(val, 10) : val,
|
|
101
|
-
)
|
|
102
|
-
.filter((val) => !isNaN(val))
|
|
103
|
-
: route.menuItemChildrenListColumns;
|
|
104
|
-
}
|
|
148
|
+
const prefix = `/${navrootLang}`;
|
|
149
|
+
if (pathname === prefix) return `/${navLang}`;
|
|
150
|
+
if (pathname.startsWith(`${prefix}/`)) {
|
|
151
|
+
return `/${navLang}${pathname.slice(prefix.length)}`;
|
|
152
|
+
}
|
|
153
|
+
return pathname;
|
|
154
|
+
}, [pathname, navrootLang]);
|
|
105
155
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
156
|
+
const baseUrl = useMemo(() => {
|
|
157
|
+
const { settings } = config;
|
|
158
|
+
const navLang = settings.navigationLanguage;
|
|
159
|
+
let url = getBaseUrl(pathname);
|
|
110
160
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
...enhancedLayouts[routeId],
|
|
115
|
-
...backendSettings,
|
|
116
|
-
};
|
|
117
|
-
}
|
|
118
|
-
});
|
|
119
|
-
});
|
|
120
|
-
}
|
|
161
|
+
if (isSubsite || !navLang || !navrootLang) {
|
|
162
|
+
return url;
|
|
163
|
+
}
|
|
121
164
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
165
|
+
// When the current navroot's language differs from the configured
|
|
166
|
+
// navigationLanguage, override the base url so navigation is fetched from
|
|
167
|
+
// the configured language root instead of the current navroot.
|
|
168
|
+
if (navLang !== navrootLang) {
|
|
169
|
+
url = `/${settings.navigationLanguage}`;
|
|
170
|
+
} else if (!url && navLang === navrootLang) {
|
|
171
|
+
url = `/${navLang}`;
|
|
172
|
+
}
|
|
173
|
+
return url;
|
|
174
|
+
}, [pathname, navrootLang, isSubsite]);
|
|
130
175
|
|
|
131
|
-
|
|
176
|
+
// Fetch navigation settings on pathname change.
|
|
177
|
+
useEffect(() => {
|
|
132
178
|
dispatch(getNavigationSettings(pathname));
|
|
133
179
|
}, [dispatch, pathname]);
|
|
134
180
|
|
|
135
|
-
//
|
|
136
|
-
|
|
181
|
+
// Re-fetch navigation settings after a content update for the current page.
|
|
182
|
+
useEffect(() => {
|
|
137
183
|
if (
|
|
138
184
|
updateRequest?.loaded &&
|
|
139
185
|
removeTrailingSlash(updateRequest?.content?.['@id'] || '') ===
|
|
@@ -143,46 +189,36 @@ const EEAHeader = ({ pathname, token, items, history, subsite }) => {
|
|
|
143
189
|
}
|
|
144
190
|
}, [updateRequest, dispatch, pathname]);
|
|
145
191
|
|
|
146
|
-
|
|
192
|
+
// Fetch the main navigation tree.
|
|
193
|
+
// Cases that force a fetch:
|
|
194
|
+
// 1. Language mismatch — the current navroot's language differs from the
|
|
195
|
+
// configured navigationLanguage, so the API expander's navigation (if
|
|
196
|
+
// any) is in the wrong language and must be replaced.
|
|
197
|
+
// 2. Token change — auth state affects which nav items are visible, so
|
|
198
|
+
// expander data loaded under the previous token may be stale.
|
|
199
|
+
// 3. No expander available — backend did not pre-supply navigation for
|
|
200
|
+
// this base url, so we fetch it explicitly.
|
|
201
|
+
// Otherwise the expander already supplied correct navigation; no fetch.
|
|
202
|
+
useEffect(() => {
|
|
147
203
|
const { settings } = config;
|
|
204
|
+
const navLang = settings.navigationLanguage;
|
|
205
|
+
const langMismatch = navLang && navrootLang && navrootLang !== navLang;
|
|
206
|
+
const tokenChanged = prevTokenRef.current !== token;
|
|
148
207
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
// When navigationLanguage is not configured, fetch navigation for current page language
|
|
156
|
-
// Check if navigation data needs to be fetched based on the API expander availability
|
|
157
|
-
if (!hasApiExpander('navigation', navigationBaseUrl)) {
|
|
158
|
-
dispatch(getNavigation(navigationBaseUrl, settings.navDepth));
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
// Additional check for token changes
|
|
162
|
-
if (token !== previousToken) {
|
|
163
|
-
dispatch(getNavigation(navigationBaseUrl, settings.navDepth));
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
}, [navigationBaseUrl, token, dispatch, previousToken]);
|
|
167
|
-
|
|
168
|
-
// Normalize pathname for menu matching when using navigationLanguage
|
|
169
|
-
// This ensures menu items from the configured language match correctly even when on other language pages
|
|
170
|
-
const normalizedPathname = React.useMemo(() => {
|
|
171
|
-
const navLang = config.settings.navigationLanguage;
|
|
172
|
-
if (!navLang) {
|
|
173
|
-
return pathname;
|
|
208
|
+
if (
|
|
209
|
+
langMismatch ||
|
|
210
|
+
tokenChanged ||
|
|
211
|
+
!hasApiExpander('navigation', baseUrl)
|
|
212
|
+
) {
|
|
213
|
+
dispatch(getNavigation(baseUrl, settings.navDepth));
|
|
174
214
|
}
|
|
215
|
+
}, [dispatch, baseUrl, navrootLang, token]);
|
|
175
216
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
const rest = pathParts.slice(1).join('/');
|
|
182
|
-
return rest ? `/${navLang}/${rest}` : `/${navLang}`;
|
|
183
|
-
}
|
|
184
|
-
return pathname;
|
|
185
|
-
}, [pathname]);
|
|
217
|
+
// Track the previous token value. Runs after the fetch effect so the
|
|
218
|
+
// comparison above sees the value from the prior render.
|
|
219
|
+
useEffect(() => {
|
|
220
|
+
prevTokenRef.current = token;
|
|
221
|
+
}, [token]);
|
|
186
222
|
|
|
187
223
|
return (
|
|
188
224
|
<Header menuItems={items}>
|
|
@@ -327,6 +363,7 @@ export default compose(
|
|
|
327
363
|
(state) => ({
|
|
328
364
|
token: state.userSession.token,
|
|
329
365
|
items: state.navigation.items,
|
|
366
|
+
navroot: state.content.data?.['@components']?.navroot?.navroot,
|
|
330
367
|
subsite: state.content.data?.['@components']?.subsite,
|
|
331
368
|
}),
|
|
332
369
|
{ getNavigation },
|