@eeacms/volto-eea-website-theme 3.7.0 → 3.9.0
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 +19 -2
- package/package.json +3 -1
- package/src/actions/index.js +1 -0
- package/src/actions/navigation.js +24 -0
- package/src/actions/print.js +9 -1
- package/src/components/manage/Blocks/ContextNavigation/variations/Accordion.jsx +42 -35
- package/src/components/manage/Blocks/LayoutSettings/LayoutSettingsEdit.test.jsx +383 -0
- package/src/components/manage/Blocks/Title/variations/WebReportPage.test.jsx +232 -0
- package/src/components/theme/Banner/View.jsx +11 -92
- package/src/components/theme/PrintLoader/PrintLoader.jsx +56 -0
- package/src/components/theme/PrintLoader/PrintLoader.test.jsx +91 -0
- package/src/components/theme/PrintLoader/style.less +12 -0
- package/src/components/theme/WebReport/WebReportSectionView.test.jsx +462 -0
- package/src/components/theme/Widgets/ImageViewWidget.test.jsx +26 -0
- package/src/components/theme/Widgets/NavigationBehaviorWidget.jsx +601 -0
- package/src/components/theme/Widgets/NavigationBehaviorWidget.test.jsx +507 -0
- package/src/components/theme/Widgets/SimpleArrayWidget.jsx +183 -0
- package/src/components/theme/Widgets/SimpleArrayWidget.test.jsx +283 -0
- package/src/constants/ActionTypes.js +2 -0
- package/src/customizations/volto/components/manage/History/History.diff +207 -0
- package/src/customizations/volto/components/manage/History/History.jsx +444 -0
- package/src/customizations/volto/components/theme/Comments/Comments.jsx +9 -2
- package/src/customizations/volto/components/theme/Comments/Comments.test.jsx +4 -4
- package/src/customizations/volto/components/theme/Comments/comments.less +16 -0
- package/src/customizations/volto/components/theme/Header/Header.jsx +60 -1
- package/src/customizations/volto/components/theme/View/DefaultView.jsx +42 -33
- package/src/customizations/volto/helpers/Html/Html.jsx +212 -0
- package/src/customizations/volto/helpers/Html/Readme.md +1 -0
- package/src/customizations/volto/server.jsx +375 -0
- package/src/helpers/loadLazyImages.js +11 -0
- package/src/helpers/loadLazyImages.test.js +22 -0
- package/src/helpers/setupPrintView.js +134 -0
- package/src/helpers/setupPrintView.test.js +49 -0
- package/src/index.js +11 -1
- package/src/index.test.js +6 -0
- package/src/middleware/voltoCustom.test.js +282 -0
- package/src/reducers/index.js +2 -1
- package/src/reducers/navigation/navigation.js +47 -0
- package/src/reducers/navigation/navigation.test.js +348 -0
- package/src/reducers/navigation.js +55 -0
- package/src/reducers/print.js +18 -8
- package/src/reducers/print.test.js +117 -0
|
@@ -0,0 +1,601 @@
|
|
|
1
|
+
import React, { useEffect, useCallback } from 'react';
|
|
2
|
+
import { useSelector, useDispatch } from 'react-redux';
|
|
3
|
+
import { v4 as uuid } from 'uuid';
|
|
4
|
+
import { Icon, FormFieldWrapper } from '@plone/volto/components';
|
|
5
|
+
import ObjectWidget from '@plone/volto/components/manage/Widgets/ObjectWidget';
|
|
6
|
+
import { Accordion, Button, Segment, Form, Dropdown } from 'semantic-ui-react';
|
|
7
|
+
import { getNavigation } from '@plone/volto/actions';
|
|
8
|
+
import { defineMessages, useIntl } from 'react-intl';
|
|
9
|
+
import config from '@plone/volto/registry';
|
|
10
|
+
|
|
11
|
+
import upSVG from '@plone/volto/icons/up-key.svg';
|
|
12
|
+
import downSVG from '@plone/volto/icons/down-key.svg';
|
|
13
|
+
|
|
14
|
+
const messages = defineMessages({
|
|
15
|
+
loadNavigationRoutes: {
|
|
16
|
+
id: 'Load Main Navigation Routes',
|
|
17
|
+
defaultMessage: 'Load Main Navigation Routes',
|
|
18
|
+
},
|
|
19
|
+
hideChildrenFromNavigation: {
|
|
20
|
+
id: 'Hide Children From Navigation',
|
|
21
|
+
defaultMessage: 'Hide Children From Navigation',
|
|
22
|
+
},
|
|
23
|
+
|
|
24
|
+
menuItemChildrenListColumns: {
|
|
25
|
+
id: 'Menu Item Children List Columns',
|
|
26
|
+
defaultMessage: 'Menu Item Children List Columns',
|
|
27
|
+
},
|
|
28
|
+
menuItemColumns: {
|
|
29
|
+
id: 'Menu Item Columns',
|
|
30
|
+
defaultMessage: 'Menu Item Columns',
|
|
31
|
+
},
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
const defaultRouteSettings = {
|
|
35
|
+
hideChildrenFromNavigation: true,
|
|
36
|
+
// Don't include empty arrays in default settings
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
// Helper functions for menuItemColumns conversion (numbers to semantic UI format)
|
|
40
|
+
const numberToColumnString = (num) => {
|
|
41
|
+
const numbers = [
|
|
42
|
+
'',
|
|
43
|
+
'one',
|
|
44
|
+
'two',
|
|
45
|
+
'three',
|
|
46
|
+
'four',
|
|
47
|
+
'five',
|
|
48
|
+
'six',
|
|
49
|
+
'seven',
|
|
50
|
+
'eight',
|
|
51
|
+
'nine',
|
|
52
|
+
];
|
|
53
|
+
return numbers[num] ? `${numbers[num]} wide column` : '';
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const numbersToMenuItemColumns = (numbers) => {
|
|
57
|
+
if (!Array.isArray(numbers)) return [];
|
|
58
|
+
return numbers
|
|
59
|
+
.map((num) => numberToColumnString(parseInt(num)))
|
|
60
|
+
.filter((col) => col !== '');
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const columnStringToNumber = (colString) => {
|
|
64
|
+
const numbers = {
|
|
65
|
+
one: 1,
|
|
66
|
+
two: 2,
|
|
67
|
+
three: 3,
|
|
68
|
+
four: 4,
|
|
69
|
+
five: 5,
|
|
70
|
+
six: 6,
|
|
71
|
+
seven: 7,
|
|
72
|
+
eight: 8,
|
|
73
|
+
nine: 9,
|
|
74
|
+
};
|
|
75
|
+
const match = colString.match(
|
|
76
|
+
/^(one|two|three|four|five|six|seven|eight|nine) wide column$/,
|
|
77
|
+
);
|
|
78
|
+
return match ? numbers[match[1]] : null;
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
const menuItemColumnsToNumbers = (columns) => {
|
|
82
|
+
if (!Array.isArray(columns)) return [];
|
|
83
|
+
return columns
|
|
84
|
+
.map((col) => columnStringToNumber(col))
|
|
85
|
+
.filter((num) => num !== null);
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
// Custom component for integer array fields
|
|
89
|
+
// eslint-disable-next-line no-unused-vars
|
|
90
|
+
const IntegerArrayField = ({
|
|
91
|
+
title,
|
|
92
|
+
description,
|
|
93
|
+
value = [],
|
|
94
|
+
onChange,
|
|
95
|
+
options = [],
|
|
96
|
+
routePath,
|
|
97
|
+
}) => {
|
|
98
|
+
const addValue = () => {
|
|
99
|
+
const newArray = [...value, options[0] || 1];
|
|
100
|
+
onChange(newArray);
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
const removeValue = (index) => {
|
|
104
|
+
const newArray = value.filter((_, i) => i !== index);
|
|
105
|
+
onChange(newArray);
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
const updateValue = (index, newValue) => {
|
|
109
|
+
const newArray = [...value];
|
|
110
|
+
newArray[index] = parseInt(newValue);
|
|
111
|
+
onChange(newArray);
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
return (
|
|
115
|
+
<Form.Field>
|
|
116
|
+
<label>{title}</label>
|
|
117
|
+
<div
|
|
118
|
+
style={{
|
|
119
|
+
marginBottom: '0.5em',
|
|
120
|
+
fontSize: '0.9em',
|
|
121
|
+
color: '#666',
|
|
122
|
+
fontStyle: 'italic',
|
|
123
|
+
}}
|
|
124
|
+
>
|
|
125
|
+
{description} • Route: <strong>{routePath}</strong>
|
|
126
|
+
</div>
|
|
127
|
+
|
|
128
|
+
{value.map((val, index) => (
|
|
129
|
+
<div
|
|
130
|
+
key={index}
|
|
131
|
+
style={{
|
|
132
|
+
display: 'flex',
|
|
133
|
+
alignItems: 'center',
|
|
134
|
+
marginBottom: '0.5em',
|
|
135
|
+
}}
|
|
136
|
+
>
|
|
137
|
+
<Dropdown
|
|
138
|
+
selection
|
|
139
|
+
value={val}
|
|
140
|
+
options={options.map((opt) => ({
|
|
141
|
+
key: opt,
|
|
142
|
+
value: opt,
|
|
143
|
+
text: opt,
|
|
144
|
+
}))}
|
|
145
|
+
onChange={(e, { value: newValue }) => updateValue(index, newValue)}
|
|
146
|
+
style={{ marginRight: '0.5em', minWidth: '80px' }}
|
|
147
|
+
/>
|
|
148
|
+
<Button
|
|
149
|
+
icon="trash"
|
|
150
|
+
size="small"
|
|
151
|
+
color="red"
|
|
152
|
+
type="button"
|
|
153
|
+
onClick={() => removeValue(index)}
|
|
154
|
+
/>
|
|
155
|
+
</div>
|
|
156
|
+
))}
|
|
157
|
+
|
|
158
|
+
<Button
|
|
159
|
+
icon="plus"
|
|
160
|
+
content="Add"
|
|
161
|
+
size="small"
|
|
162
|
+
type="button"
|
|
163
|
+
onClick={addValue}
|
|
164
|
+
/>
|
|
165
|
+
</Form.Field>
|
|
166
|
+
);
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
// Get settings from config.settings.menuItemsLayouts
|
|
170
|
+
const getConfigSettingsForRoute = (routePath) => {
|
|
171
|
+
const menuItemsLayouts = config.settings?.menuItemsLayouts || {};
|
|
172
|
+
|
|
173
|
+
// Find the most specific parent route match
|
|
174
|
+
const findMostSpecificConfig = (targetPath) => {
|
|
175
|
+
// First check for exact match
|
|
176
|
+
if (menuItemsLayouts[targetPath]) {
|
|
177
|
+
return menuItemsLayouts[targetPath];
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Find all matching parent routes
|
|
181
|
+
const matchingRoutes = Object.keys(menuItemsLayouts)
|
|
182
|
+
.filter((configPath) => {
|
|
183
|
+
// Skip wildcard for now
|
|
184
|
+
if (configPath === '*') return false;
|
|
185
|
+
// Check if targetPath starts with configPath
|
|
186
|
+
return targetPath.startsWith(configPath);
|
|
187
|
+
})
|
|
188
|
+
.sort((a, b) => b.length - a.length); // Sort by length (most specific first)
|
|
189
|
+
|
|
190
|
+
// Return the most specific parent route config
|
|
191
|
+
if (matchingRoutes.length > 0) {
|
|
192
|
+
return menuItemsLayouts[matchingRoutes[0]];
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Fall back to wildcard
|
|
196
|
+
return menuItemsLayouts['*'] || {};
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
const routeConfig = findMostSpecificConfig(routePath);
|
|
200
|
+
|
|
201
|
+
const settings = {
|
|
202
|
+
hideChildrenFromNavigation:
|
|
203
|
+
routeConfig.hideChildrenFromNavigation !== undefined
|
|
204
|
+
? routeConfig.hideChildrenFromNavigation
|
|
205
|
+
: true,
|
|
206
|
+
includeInNavigation: true,
|
|
207
|
+
expandChildren: false,
|
|
208
|
+
navigationDepth: 0,
|
|
209
|
+
showIcons: true,
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
// Only add array properties if they have values
|
|
213
|
+
if (
|
|
214
|
+
routeConfig.menuItemChildrenListColumns &&
|
|
215
|
+
routeConfig.menuItemChildrenListColumns.length > 0
|
|
216
|
+
) {
|
|
217
|
+
settings.menuItemChildrenListColumns =
|
|
218
|
+
routeConfig.menuItemChildrenListColumns;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (routeConfig.menuItemColumns && routeConfig.menuItemColumns.length > 0) {
|
|
222
|
+
// Convert from semantic UI format to numbers for widget display
|
|
223
|
+
settings.menuItemColumns = menuItemColumnsToNumbers(
|
|
224
|
+
routeConfig.menuItemColumns,
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
return settings;
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
// Schema for individual route settings - simple version
|
|
232
|
+
const getRouteSettingsSchema = (intl) => ({
|
|
233
|
+
title: 'Route Navigation Settings',
|
|
234
|
+
fieldsets: [
|
|
235
|
+
{
|
|
236
|
+
id: 'default',
|
|
237
|
+
title: 'Default',
|
|
238
|
+
fields: [
|
|
239
|
+
'hideChildrenFromNavigation',
|
|
240
|
+
'menuItemChildrenListColumns',
|
|
241
|
+
'menuItemColumns',
|
|
242
|
+
],
|
|
243
|
+
},
|
|
244
|
+
],
|
|
245
|
+
properties: {
|
|
246
|
+
hideChildrenFromNavigation: {
|
|
247
|
+
title: intl.formatMessage(messages.hideChildrenFromNavigation),
|
|
248
|
+
type: 'boolean',
|
|
249
|
+
default: true,
|
|
250
|
+
},
|
|
251
|
+
|
|
252
|
+
menuItemChildrenListColumns: {
|
|
253
|
+
title: intl.formatMessage(messages.menuItemChildrenListColumns),
|
|
254
|
+
description: 'Number of columns for each route',
|
|
255
|
+
type: 'array',
|
|
256
|
+
widget: 'simple_array',
|
|
257
|
+
items: {
|
|
258
|
+
minimum: 1,
|
|
259
|
+
maximum: 10,
|
|
260
|
+
},
|
|
261
|
+
default: [],
|
|
262
|
+
},
|
|
263
|
+
menuItemColumns: {
|
|
264
|
+
title: intl.formatMessage(messages.menuItemColumns),
|
|
265
|
+
description: 'Size of each route section',
|
|
266
|
+
type: 'array',
|
|
267
|
+
widget: 'simple_array',
|
|
268
|
+
items: {
|
|
269
|
+
minimum: 1,
|
|
270
|
+
maximum: 9,
|
|
271
|
+
},
|
|
272
|
+
default: [],
|
|
273
|
+
},
|
|
274
|
+
},
|
|
275
|
+
required: [],
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
const NavigationBehaviorWidget = (props) => {
|
|
279
|
+
const { value = '{}', id, onChange } = props;
|
|
280
|
+
const intl = useIntl();
|
|
281
|
+
const dispatch = useDispatch();
|
|
282
|
+
const navigation = useSelector((state) => state.navigation?.items || []);
|
|
283
|
+
const navigationLoaded = useSelector((state) => state.navigation?.loaded);
|
|
284
|
+
|
|
285
|
+
// Parse JSON string to object
|
|
286
|
+
const parseValue = (val) => {
|
|
287
|
+
try {
|
|
288
|
+
return typeof val === 'string' ? JSON.parse(val) : val || {};
|
|
289
|
+
} catch (e) {
|
|
290
|
+
return {};
|
|
291
|
+
}
|
|
292
|
+
};
|
|
293
|
+
|
|
294
|
+
const routeSettings = parseValue(value);
|
|
295
|
+
|
|
296
|
+
const [localActiveObject, setLocalActiveObject] = React.useState(-1);
|
|
297
|
+
let activeObject = localActiveObject;
|
|
298
|
+
let setActiveObject = setLocalActiveObject;
|
|
299
|
+
|
|
300
|
+
function handleChangeActiveObject(e, blockProps) {
|
|
301
|
+
const { index } = blockProps;
|
|
302
|
+
const newIndex = activeObject === index ? -1 : index;
|
|
303
|
+
setActiveObject(newIndex);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
const flattenNavigationToRoutes = useCallback(
|
|
307
|
+
(items, level = 0) => {
|
|
308
|
+
let routes = [];
|
|
309
|
+
|
|
310
|
+
items.forEach((item) => {
|
|
311
|
+
const itemPath = item.url || item.id;
|
|
312
|
+
const currentPath = itemPath;
|
|
313
|
+
const routeId = item['@id'] || item.url || item.id || uuid();
|
|
314
|
+
const configSettings =
|
|
315
|
+
getConfigSettingsForRoute(currentPath) || defaultRouteSettings;
|
|
316
|
+
const savedSettings = routeSettings[routeId] || {};
|
|
317
|
+
|
|
318
|
+
// Merge settings intelligently - use config values for empty/missing fields
|
|
319
|
+
let finalSettings = { ...defaultRouteSettings };
|
|
320
|
+
|
|
321
|
+
// Add config settings first (as defaults)
|
|
322
|
+
if (configSettings) {
|
|
323
|
+
Object.keys(configSettings).forEach((key) => {
|
|
324
|
+
if (
|
|
325
|
+
configSettings[key] !== undefined &&
|
|
326
|
+
configSettings[key] !== null
|
|
327
|
+
) {
|
|
328
|
+
finalSettings[key] = configSettings[key];
|
|
329
|
+
}
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// Override with saved settings, including null values (explicit deletion)
|
|
334
|
+
if (savedSettings) {
|
|
335
|
+
Object.keys(savedSettings).forEach((key) => {
|
|
336
|
+
if (savedSettings[key] !== undefined) {
|
|
337
|
+
// Handle null values as explicit deletion - don't override with config
|
|
338
|
+
if (savedSettings[key] === null) {
|
|
339
|
+
// Field was explicitly cleared - remove it from finalSettings
|
|
340
|
+
delete finalSettings[key];
|
|
341
|
+
} else if (Array.isArray(savedSettings[key])) {
|
|
342
|
+
// For arrays, always override with saved value (including empty arrays)
|
|
343
|
+
finalSettings[key] = savedSettings[key];
|
|
344
|
+
} else {
|
|
345
|
+
// For non-arrays, override with saved value
|
|
346
|
+
finalSettings[key] = savedSettings[key];
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// Convert menuItemColumns from semantic UI format to numbers for widget display
|
|
353
|
+
if (
|
|
354
|
+
finalSettings.menuItemColumns &&
|
|
355
|
+
Array.isArray(finalSettings.menuItemColumns)
|
|
356
|
+
) {
|
|
357
|
+
// Check if values are in semantic UI format
|
|
358
|
+
if (
|
|
359
|
+
finalSettings.menuItemColumns.length > 0 &&
|
|
360
|
+
typeof finalSettings.menuItemColumns[0] === 'string' &&
|
|
361
|
+
finalSettings.menuItemColumns[0].includes('wide column')
|
|
362
|
+
) {
|
|
363
|
+
finalSettings.menuItemColumns = menuItemColumnsToNumbers(
|
|
364
|
+
finalSettings.menuItemColumns,
|
|
365
|
+
);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
const route = {
|
|
370
|
+
'@id': routeId,
|
|
371
|
+
title: item.title || item.name,
|
|
372
|
+
path: currentPath,
|
|
373
|
+
url: item.url,
|
|
374
|
+
level: level,
|
|
375
|
+
hasChildren: item.items && item.items.length > 0,
|
|
376
|
+
portal_type: item.portal_type || item['@type'],
|
|
377
|
+
...finalSettings,
|
|
378
|
+
};
|
|
379
|
+
|
|
380
|
+
routes.push(route);
|
|
381
|
+
|
|
382
|
+
if (item.items && item.items.length > 0) {
|
|
383
|
+
routes = routes.concat(
|
|
384
|
+
flattenNavigationToRoutes(item.items, level + 1),
|
|
385
|
+
);
|
|
386
|
+
}
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
return routes;
|
|
390
|
+
},
|
|
391
|
+
[routeSettings],
|
|
392
|
+
);
|
|
393
|
+
|
|
394
|
+
useEffect(() => {
|
|
395
|
+
if (!navigationLoaded) {
|
|
396
|
+
dispatch(getNavigation('', 1));
|
|
397
|
+
}
|
|
398
|
+
}, [dispatch, navigationLoaded]);
|
|
399
|
+
|
|
400
|
+
// Auto-populate from config if no settings exist
|
|
401
|
+
useEffect(() => {
|
|
402
|
+
if (
|
|
403
|
+
navigationLoaded &&
|
|
404
|
+
navigation.length > 0 &&
|
|
405
|
+
Object.keys(routeSettings).length === 0
|
|
406
|
+
) {
|
|
407
|
+
const routes = flattenNavigationToRoutes(navigation);
|
|
408
|
+
const level0Routes = routes.filter((route) => route.level === 0);
|
|
409
|
+
|
|
410
|
+
if (level0Routes.length > 0) {
|
|
411
|
+
const newSettings = {};
|
|
412
|
+
level0Routes.forEach((route) => {
|
|
413
|
+
// Save the current settings (which include config values) for each route
|
|
414
|
+
const {
|
|
415
|
+
'@id': routeId,
|
|
416
|
+
title: _,
|
|
417
|
+
url: ___,
|
|
418
|
+
level: ____,
|
|
419
|
+
hasChildren: _____,
|
|
420
|
+
portal_type: ______,
|
|
421
|
+
...settings
|
|
422
|
+
} = route;
|
|
423
|
+
newSettings[routeId] = settings;
|
|
424
|
+
});
|
|
425
|
+
|
|
426
|
+
onChange(id, JSON.stringify(newSettings));
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
}, [
|
|
430
|
+
navigationLoaded,
|
|
431
|
+
navigation,
|
|
432
|
+
routeSettings,
|
|
433
|
+
onChange,
|
|
434
|
+
id,
|
|
435
|
+
flattenNavigationToRoutes,
|
|
436
|
+
]);
|
|
437
|
+
|
|
438
|
+
const allRoutes = React.useMemo(() => {
|
|
439
|
+
const routes = flattenNavigationToRoutes(navigation);
|
|
440
|
+
// Filter to show only level 0 routes (main routes that decide navigation behavior)
|
|
441
|
+
return routes.filter((route) => route.level === 0); // level 0 = main routes
|
|
442
|
+
}, [navigation, flattenNavigationToRoutes]);
|
|
443
|
+
|
|
444
|
+
return (
|
|
445
|
+
<div className="navigation-objectlist-widget">
|
|
446
|
+
<FormFieldWrapper {...props} className="navigation-behavior-widget" />
|
|
447
|
+
|
|
448
|
+
<div className="routes-area">
|
|
449
|
+
{allRoutes.map((route, index) => (
|
|
450
|
+
<Accordion key={route['@id']} fluid styled>
|
|
451
|
+
<Accordion.Title
|
|
452
|
+
active={activeObject === index}
|
|
453
|
+
index={index}
|
|
454
|
+
onClick={handleChangeActiveObject}
|
|
455
|
+
>
|
|
456
|
+
<div className="label">
|
|
457
|
+
<strong style={{ marginLeft: '0.5rem' }}>{route.title}</strong>
|
|
458
|
+
<span
|
|
459
|
+
style={{
|
|
460
|
+
color: '#666',
|
|
461
|
+
fontSize: '0.85em',
|
|
462
|
+
fontStyle: 'italic',
|
|
463
|
+
marginLeft: '0.5rem',
|
|
464
|
+
}}
|
|
465
|
+
>
|
|
466
|
+
({route.path})
|
|
467
|
+
</span>
|
|
468
|
+
</div>
|
|
469
|
+
|
|
470
|
+
<div className="accordion-tools">
|
|
471
|
+
{activeObject === index ? (
|
|
472
|
+
<Icon name={upSVG} size="20px" />
|
|
473
|
+
) : (
|
|
474
|
+
<Icon name={downSVG} size="20px" />
|
|
475
|
+
)}
|
|
476
|
+
</div>
|
|
477
|
+
</Accordion.Title>
|
|
478
|
+
<Accordion.Content active={activeObject === index}>
|
|
479
|
+
<Segment>
|
|
480
|
+
<ObjectWidget
|
|
481
|
+
id={`${id}-${index}`}
|
|
482
|
+
key={`ow-${id}-${index}`}
|
|
483
|
+
schema={getRouteSettingsSchema(intl)}
|
|
484
|
+
value={route}
|
|
485
|
+
onChange={(fieldId, fieldValue) => {
|
|
486
|
+
const routeId = route['@id'];
|
|
487
|
+
const {
|
|
488
|
+
'@id': _,
|
|
489
|
+
title: __,
|
|
490
|
+
path: ___,
|
|
491
|
+
url: ____,
|
|
492
|
+
level: _____,
|
|
493
|
+
hasChildren: ______,
|
|
494
|
+
portal_type: _______,
|
|
495
|
+
...settings
|
|
496
|
+
} = fieldValue;
|
|
497
|
+
|
|
498
|
+
// Preserve existing settings and merge with new ones
|
|
499
|
+
const existingSettings = routeSettings[routeId] || {};
|
|
500
|
+
|
|
501
|
+
// Convert existing menuItemColumns from semantic UI back to numbers for merging
|
|
502
|
+
const cleanedExistingSettings = { ...existingSettings };
|
|
503
|
+
if (
|
|
504
|
+
cleanedExistingSettings.menuItemColumns &&
|
|
505
|
+
Array.isArray(cleanedExistingSettings.menuItemColumns)
|
|
506
|
+
) {
|
|
507
|
+
// Check if existing values are in semantic UI format and convert to numbers
|
|
508
|
+
if (
|
|
509
|
+
cleanedExistingSettings.menuItemColumns.length > 0 &&
|
|
510
|
+
typeof cleanedExistingSettings.menuItemColumns[0] ===
|
|
511
|
+
'string' &&
|
|
512
|
+
cleanedExistingSettings.menuItemColumns[0].includes(
|
|
513
|
+
'wide column',
|
|
514
|
+
)
|
|
515
|
+
) {
|
|
516
|
+
cleanedExistingSettings.menuItemColumns =
|
|
517
|
+
menuItemColumnsToNumbers(
|
|
518
|
+
cleanedExistingSettings.menuItemColumns,
|
|
519
|
+
);
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
// Check if the field was explicitly set in the widget's settings
|
|
524
|
+
const explicitlySetFields = Object.keys(settings);
|
|
525
|
+
|
|
526
|
+
let mergedSettings = {
|
|
527
|
+
...cleanedExistingSettings,
|
|
528
|
+
...settings,
|
|
529
|
+
};
|
|
530
|
+
|
|
531
|
+
// Handle null values for explicitly cleared fields
|
|
532
|
+
Object.keys(settings).forEach((key) => {
|
|
533
|
+
if (settings[key] === null) {
|
|
534
|
+
// Field was explicitly cleared - store as null to preserve intent
|
|
535
|
+
mergedSettings[key] = null;
|
|
536
|
+
}
|
|
537
|
+
});
|
|
538
|
+
|
|
539
|
+
// Convert menuItemColumns from numbers back to semantic UI format for backend storage
|
|
540
|
+
if (
|
|
541
|
+
mergedSettings.menuItemColumns !== null &&
|
|
542
|
+
mergedSettings.menuItemColumns
|
|
543
|
+
) {
|
|
544
|
+
if (mergedSettings.menuItemColumns.length > 0) {
|
|
545
|
+
mergedSettings.menuItemColumns =
|
|
546
|
+
numbersToMenuItemColumns(
|
|
547
|
+
mergedSettings.menuItemColumns,
|
|
548
|
+
);
|
|
549
|
+
} else if (
|
|
550
|
+
!explicitlySetFields.includes('menuItemColumns')
|
|
551
|
+
) {
|
|
552
|
+
// Only remove empty menuItemColumns if not explicitly set by user
|
|
553
|
+
delete mergedSettings.menuItemColumns;
|
|
554
|
+
} else {
|
|
555
|
+
// Keep empty array if explicitly set by user (cleared all elements)
|
|
556
|
+
mergedSettings.menuItemColumns = [];
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
// Handle menuItemChildrenListColumns similarly
|
|
561
|
+
if (mergedSettings.menuItemChildrenListColumns !== null) {
|
|
562
|
+
if (
|
|
563
|
+
!mergedSettings.menuItemChildrenListColumns ||
|
|
564
|
+
(mergedSettings.menuItemChildrenListColumns.length ===
|
|
565
|
+
0 &&
|
|
566
|
+
!explicitlySetFields.includes(
|
|
567
|
+
'menuItemChildrenListColumns',
|
|
568
|
+
))
|
|
569
|
+
) {
|
|
570
|
+
delete mergedSettings.menuItemChildrenListColumns;
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
// Clean up other empty arrays only if not explicitly set by user
|
|
575
|
+
Object.keys(mergedSettings).forEach((key) => {
|
|
576
|
+
if (
|
|
577
|
+
Array.isArray(mergedSettings[key]) &&
|
|
578
|
+
mergedSettings[key].length === 0 &&
|
|
579
|
+
!explicitlySetFields.includes(key)
|
|
580
|
+
) {
|
|
581
|
+
delete mergedSettings[key];
|
|
582
|
+
}
|
|
583
|
+
});
|
|
584
|
+
|
|
585
|
+
const newSettings = {
|
|
586
|
+
...routeSettings,
|
|
587
|
+
[routeId]: mergedSettings,
|
|
588
|
+
};
|
|
589
|
+
onChange(id, JSON.stringify(newSettings));
|
|
590
|
+
}}
|
|
591
|
+
/>
|
|
592
|
+
</Segment>
|
|
593
|
+
</Accordion.Content>
|
|
594
|
+
</Accordion>
|
|
595
|
+
))}
|
|
596
|
+
</div>
|
|
597
|
+
</div>
|
|
598
|
+
);
|
|
599
|
+
};
|
|
600
|
+
|
|
601
|
+
export default NavigationBehaviorWidget;
|