design-comuni-plone-theme 8.3.2 → 8.4.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.
Files changed (37) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/locales/de/LC_MESSAGES/volto.po +31 -16
  3. package/locales/en/LC_MESSAGES/volto.po +36 -21
  4. package/locales/es/LC_MESSAGES/volto.po +31 -16
  5. package/locales/fr/LC_MESSAGES/volto.po +31 -16
  6. package/locales/it/LC_MESSAGES/volto.po +32 -17
  7. package/locales/volto.pot +32 -17
  8. package/package.json +1 -1
  9. package/publiccode.yml +2 -2
  10. package/src/components/ItaliaTheme/Blocks/Listing/Commons/utils.js +63 -0
  11. package/src/components/ItaliaTheme/Blocks/Listing/SliderTemplate.jsx +235 -65
  12. package/src/components/ItaliaTheme/Breadcrumbs/Breadcrumbs.jsx +2 -2
  13. package/src/components/ItaliaTheme/CustomerSatisfaction/FeedbackForm.jsx +11 -7
  14. package/src/components/ItaliaTheme/Footer/FooterPNRRLogo.jsx +2 -2
  15. package/src/components/ItaliaTheme/Footer/logo-eu-inverted.svg +1 -17
  16. package/src/components/ItaliaTheme/Header/HeaderSearch/SearchModal.jsx +38 -11
  17. package/src/components/ItaliaTheme/Search/Search.jsx +7 -3
  18. package/src/components/ItaliaTheme/Search/utils.js +5 -3
  19. package/src/components/ItaliaTheme/Unauthorized/Unauthorized.jsx +3 -1
  20. package/src/components/ItaliaTheme/View/PersonaView/PersonaRuolo.jsx +108 -8
  21. package/src/components/ItaliaTheme/manage/Widgets/SubFooterConfigurationForm.jsx +1 -0
  22. package/src/customizations/volto/components/manage/Blocks/ToC/View.jsx +2 -0
  23. package/src/customizations/volto/components/manage/Blocks/ToC/variations/DefaultTocRenderer.jsx +99 -0
  24. package/src/customizations/volto/components/theme/Breadcrumbs/Breadcrumbs.jsx +3 -3
  25. package/src/customizations/volto/components/theme/View/View.jsx +308 -0
  26. package/src/customizations/volto/helpers/Api/Api.jsx +131 -0
  27. package/src/customizations/volto/middleware/api.js +362 -0
  28. package/src/customizations/volto/middleware/blacklistRoutes.js +47 -0
  29. package/src/theme/ItaliaTheme/Blocks/_imageBlock.scss +4 -0
  30. package/src/theme/ItaliaTheme/Blocks/_sliderTemplate.scss +18 -4
  31. package/src/theme/ItaliaTheme/Components/_search.scss +6 -0
  32. package/src/theme/ItaliaTheme/_common.scss +15 -0
  33. package/src/theme/ItaliaTheme/_css_variables.scss +3 -0
  34. package/src/theme/_cms-ui.scss +9 -0
  35. package/src/theme/bootstrap-override/bootstrap-italia/_footer.scss +3 -3
  36. package/src/theme/extras/_search.scss +31 -0
  37. package/src/theme/site.scss +1 -0
@@ -0,0 +1,362 @@
1
+ /**
2
+ * backport parziale/temporaneo di https://github.com/plone/volto/pull/5069
3
+ * rimuovere dopo l'aggiornamento a Volto >= 17.0.0-alpha.24
4
+ *
5
+ * Api middleware.
6
+ * @module middleware/api
7
+ */
8
+
9
+ import Cookies from 'universal-cookie';
10
+ import jwtDecode from 'jwt-decode';
11
+ import { compact, flatten, union } from 'lodash';
12
+ import { matchPath } from 'react-router';
13
+ import qs from 'query-string';
14
+
15
+ import config from '@plone/volto/registry';
16
+
17
+ import {
18
+ GET_CONTENT,
19
+ LOGIN,
20
+ RESET_APIERROR,
21
+ SET_APIERROR,
22
+ } from '@plone/volto/constants/ActionTypes';
23
+ import { changeLanguage } from '@plone/volto/actions';
24
+ import {
25
+ toGettextLang,
26
+ toReactIntlLang,
27
+ getCookieOptions,
28
+ } from '@plone/volto/helpers';
29
+ let socket = null;
30
+
31
+ /**
32
+ *
33
+ * Add configured expanders to an api call for an action
34
+ * Requirements:
35
+ *
36
+ * - It should add the expanders set in the config settings
37
+ * - It should preserve any query if present
38
+ * - It should preserve (and add) any expand parameter (if present) e.g. translations
39
+ * - It should take use the correct codification for arrays in querystring (repeated parameter for each member of the array)
40
+ *
41
+ * @function addExpandersToPath
42
+ * @param {string} path The url/path including the querystring
43
+ * @param {*} type The action type
44
+ * @returns {string} The url/path with the configured expanders added to the query string
45
+ */
46
+ export function addExpandersToPath(path, type, isAnonymous) {
47
+ const { settings } = config;
48
+ const { apiExpanders = [] } = settings;
49
+
50
+ const {
51
+ url,
52
+ query: { expand, ...query },
53
+ } = qs.parseUrl(path, { decode: false });
54
+
55
+ const expandersFromConfig = apiExpanders
56
+ .filter((expand) => matchPath(url, expand.match) && expand[type])
57
+ .map((expand) => expand[type]);
58
+
59
+ const expandMerge = compact(
60
+ union([expand, ...flatten(expandersFromConfig)]),
61
+ ).filter((item) => !(item === 'types' && isAnonymous)); // Remove types expander if isAnonymous
62
+
63
+ const stringifiedExpand = qs.stringify(
64
+ { expand: expandMerge },
65
+ {
66
+ arrayFormat: 'comma',
67
+ encode: false,
68
+ },
69
+ );
70
+
71
+ const querystringFromConfig = apiExpanders
72
+ .filter((expand) => matchPath(url, expand.match) && expand[type])
73
+ .reduce((acc, expand) => {
74
+ let querystring = expand?.['querystring'];
75
+ // The querystring accepts being a function to be able to take other
76
+ // config parameters
77
+ if (typeof querystring === 'function') {
78
+ querystring = querystring(config);
79
+ }
80
+ return { ...acc, ...querystring };
81
+ }, {});
82
+
83
+ const queryMerge = { ...query, ...querystringFromConfig };
84
+
85
+ const stringifiedQuery = qs.stringify(queryMerge, {
86
+ encode: false,
87
+ });
88
+
89
+ if (stringifiedQuery && stringifiedExpand) {
90
+ return `${url}?${stringifiedExpand}&${stringifiedQuery}`;
91
+ } else if (!stringifiedQuery && stringifiedExpand) {
92
+ return `${url}?${stringifiedExpand}`;
93
+ } else if (stringifiedQuery && !stringifiedExpand) {
94
+ return `${url}?${stringifiedQuery}`;
95
+ } else {
96
+ return url;
97
+ }
98
+ }
99
+
100
+ /**
101
+ * Send a message on a websocket.
102
+ * @function sendOnSocket
103
+ * @param {Object} request Request object.
104
+ * @returns {Promise} message is send
105
+ */
106
+ function sendOnSocket(request) {
107
+ return new Promise((resolve, reject) => {
108
+ switch (socket.readyState) {
109
+ case socket.CONNECTING:
110
+ socket.addEventListener('open', () => resolve(socket));
111
+ socket.addEventListener('error', reject);
112
+ break;
113
+ case socket.OPEN:
114
+ resolve(socket);
115
+ break;
116
+ default:
117
+ reject();
118
+ break;
119
+ }
120
+ }).then(() => {
121
+ socket.send(JSON.stringify(request));
122
+ });
123
+ }
124
+
125
+ /**
126
+ * Api middleware.
127
+ * @function
128
+ * @param {Object} api Api object.
129
+ * @returns {Promise} Action promise.
130
+ */
131
+ const apiMiddlewareFactory = (api) => ({ dispatch, getState }) => (next) => (
132
+ action,
133
+ ) => {
134
+ const { settings } = config;
135
+
136
+ const token = getState().userSession.token;
137
+ let isAnonymous = true;
138
+ if (token) {
139
+ const tokenExpiration = jwtDecode(token).exp;
140
+ const currentTime = new Date().getTime() / 1000;
141
+ isAnonymous = !token || currentTime > tokenExpiration;
142
+ }
143
+
144
+ if (typeof action === 'function') {
145
+ return action(dispatch, getState);
146
+ }
147
+
148
+ const { request, type, mode = 'parallel', ...rest } = action;
149
+ const { subrequest } = action; // We want subrequest remains in `...rest` above
150
+
151
+ let actionPromise;
152
+
153
+ if (!request) {
154
+ return next(action);
155
+ }
156
+
157
+ next({ ...rest, type: `${type}_PENDING` });
158
+
159
+ if (socket) {
160
+ actionPromise = Array.isArray(request)
161
+ ? Promise.all(
162
+ request.map((item) =>
163
+ sendOnSocket({
164
+ ...item,
165
+ path: addExpandersToPath(item.path, type, isAnonymous),
166
+ id: type,
167
+ }),
168
+ ),
169
+ )
170
+ : sendOnSocket({
171
+ ...request,
172
+ path: addExpandersToPath(request.path, type, isAnonymous),
173
+ id: type,
174
+ });
175
+ } else {
176
+ actionPromise = Array.isArray(request)
177
+ ? mode === 'serial'
178
+ ? request.reduce((prevPromise, item) => {
179
+ return prevPromise.then((acc) => {
180
+ return api[item.op](
181
+ addExpandersToPath(item.path, type, isAnonymous),
182
+ {
183
+ data: item.data,
184
+ type: item.type,
185
+ headers: item.headers,
186
+ params: request.params,
187
+ checkUrl: settings.actions_raising_api_errors.includes(
188
+ action.type,
189
+ ),
190
+ },
191
+ ).then((reqres) => {
192
+ return [...acc, reqres];
193
+ });
194
+ });
195
+ }, Promise.resolve([]))
196
+ : Promise.all(
197
+ request.map((item) =>
198
+ api[item.op](addExpandersToPath(item.path, type, isAnonymous), {
199
+ data: item.data,
200
+ type: item.type,
201
+ headers: item.headers,
202
+ params: request.params,
203
+ checkUrl: settings.actions_raising_api_errors.includes(
204
+ action.type,
205
+ ),
206
+ }),
207
+ ),
208
+ )
209
+ : api[request.op](addExpandersToPath(request.path, type, isAnonymous), {
210
+ data: request.data,
211
+ type: request.type,
212
+ headers: request.headers,
213
+ params: request.params,
214
+ checkUrl: settings.actions_raising_api_errors.includes(action.type),
215
+ });
216
+ actionPromise.then(
217
+ (result) => {
218
+ const { settings } = config;
219
+ if (getState().apierror.connectionRefused) {
220
+ next({
221
+ ...rest,
222
+ type: RESET_APIERROR,
223
+ });
224
+ }
225
+ if (type === GET_CONTENT) {
226
+ const lang = result?.language?.token;
227
+ if (
228
+ lang &&
229
+ getState().intl.locale !== toReactIntlLang(lang) &&
230
+ !subrequest &&
231
+ config.settings.supportedLanguages.includes(lang)
232
+ ) {
233
+ const langFileName = toGettextLang(lang);
234
+ import('~/../locales/' + langFileName + '.json').then((locale) => {
235
+ dispatch(changeLanguage(lang, locale.default));
236
+ });
237
+ }
238
+ }
239
+ if (type === LOGIN && settings.websockets) {
240
+ const cookies = new Cookies();
241
+ cookies.set(
242
+ 'auth_token',
243
+ result.token,
244
+ getCookieOptions({
245
+ expires: new Date(jwtDecode(result.token).exp * 1000),
246
+ }),
247
+ );
248
+ api.get('/@wstoken').then((res) => {
249
+ socket = new WebSocket(
250
+ `${settings.apiPath.replace('http', 'ws')}/@ws?ws_token=${
251
+ res.token
252
+ }`,
253
+ );
254
+ socket.onmessage = (message) => {
255
+ const packet = JSON.parse(message.data);
256
+ if (packet.error) {
257
+ dispatch({
258
+ type: `${packet.id}_FAIL`,
259
+ error: packet.error,
260
+ });
261
+ } else {
262
+ dispatch({
263
+ type: `${packet.id}_SUCCESS`,
264
+ result: JSON.parse(packet.data),
265
+ });
266
+ }
267
+ };
268
+ });
269
+ }
270
+ try {
271
+ return next({ ...rest, result, type: `${type}_SUCCESS` });
272
+ } catch (error) {
273
+ // There was an exception while processing reducers or downstream middleware.
274
+ next({
275
+ ...rest,
276
+ error: { status: 500, error },
277
+ type: `${type}_FAIL`,
278
+ });
279
+ // Rethrow the original exception on the client side only,
280
+ // so it doesn't fall through to express on the server.
281
+ if (__CLIENT__) throw error;
282
+ }
283
+ },
284
+ (error) => {
285
+ // Only SSR can set ECONNREFUSED
286
+ if (error.code === 'ECONNREFUSED') {
287
+ next({
288
+ ...rest,
289
+ error,
290
+ statusCode: error.code,
291
+ connectionRefused: true,
292
+ type: SET_APIERROR,
293
+ });
294
+ }
295
+
296
+ // Response error is marked crossDomain if CORS error happen
297
+ else if (error.crossDomain) {
298
+ next({
299
+ ...rest,
300
+ error,
301
+ statusCode: 'CORSERROR',
302
+ connectionRefused: false,
303
+ type: SET_APIERROR,
304
+ });
305
+ }
306
+
307
+ // Check for actions who can raise api errors
308
+ if (settings.actions_raising_api_errors.includes(action.type)) {
309
+ // Gateway timeout
310
+ if (error?.response?.statusCode === 504) {
311
+ next({
312
+ ...rest,
313
+ error,
314
+ statusCode: error.code,
315
+ connectionRefused: true,
316
+ type: SET_APIERROR,
317
+ });
318
+ }
319
+
320
+ // Redirect
321
+ else if (error?.code === 301) {
322
+ next({
323
+ ...rest,
324
+ error,
325
+ statusCode: error.code,
326
+ connectionRefused: false,
327
+ type: SET_APIERROR,
328
+ });
329
+ }
330
+
331
+ // Redirect
332
+ else if (error?.code === 408) {
333
+ next({
334
+ ...rest,
335
+ error,
336
+ statusCode: error.code,
337
+ connectionRefused: false,
338
+ type: SET_APIERROR,
339
+ });
340
+ }
341
+
342
+ // Unauthorized
343
+ else if (error?.response?.statusCode === 401) {
344
+ next({
345
+ ...rest,
346
+ error,
347
+ statusCode: error.response,
348
+ message: error.response.body.message,
349
+ connectionRefused: false,
350
+ type: SET_APIERROR,
351
+ });
352
+ }
353
+ }
354
+ return next({ ...rest, error, type: `${type}_FAIL` });
355
+ },
356
+ );
357
+ }
358
+
359
+ return actionPromise;
360
+ };
361
+
362
+ export default apiMiddlewareFactory;
@@ -0,0 +1,47 @@
1
+ /**
2
+ * backport https://github.com/plone/volto/pull/4854
3
+ */
4
+
5
+ import config from '@plone/volto/registry';
6
+ import { matchPath } from 'react-router';
7
+
8
+ const blacklistRoutes = ({ dispatch, getState }) => (next) => (action) => {
9
+ if (typeof action === 'function') {
10
+ return next(action);
11
+ }
12
+
13
+ switch (action.type) {
14
+ case '@@router/LOCATION_CHANGE':
15
+ let { pathname } = action.payload.location;
16
+ const { externalRoutes = [] } = config.settings;
17
+
18
+ const route = externalRoutes.find((route) =>
19
+ matchPath(pathname, route.match),
20
+ );
21
+
22
+ let actionToSend = action;
23
+ if (pathname.startsWith('/++api++')) {
24
+ actionToSend.payload.location.pathname = actionToSend.payload.location.pathname.substring(
25
+ 8,
26
+ );
27
+ // To handle the `window.location.replace`
28
+ pathname = actionToSend.payload.location.pathname;
29
+ if (window.history) {
30
+ window.history.replaceState(window.history.state, '', pathname);
31
+ }
32
+ }
33
+
34
+ if (!route) {
35
+ return next(actionToSend);
36
+ } else {
37
+ window.location.replace(
38
+ route.url ? route.url(actionToSend.payload) : pathname,
39
+ );
40
+ }
41
+ break;
42
+ default:
43
+ return next(action);
44
+ }
45
+ };
46
+
47
+ export default blacklistRoutes;
@@ -12,4 +12,8 @@
12
12
  }
13
13
  }
14
14
  }
15
+ &.left,
16
+ &.right {
17
+ margin-bottom: 0;
18
+ }
15
19
  }
@@ -46,12 +46,12 @@
46
46
  .icon {
47
47
  width: 25px;
48
48
  height: 40px;
49
- color: lighten($primary, 40%);
50
- fill: lighten($primary, 40%);
49
+ color: $primary;
50
+ fill: $primary;
51
51
  }
52
52
  &:hover .icon {
53
53
  color: $primary;
54
- fill: $primary;
54
+ fill: darken($primary, 15%);
55
55
  }
56
56
 
57
57
  &.slick-prev {
@@ -77,6 +77,18 @@
77
77
  position: unset;
78
78
  bottom: unset;
79
79
  margin-top: 0.5rem !important;
80
+ .slick-dot button:before {
81
+ background-image: none;
82
+ background-color: #3f4142e0;
83
+ opacity: 1;
84
+ border-radius: 50%;
85
+ }
86
+ .slick-dot.slick-active button:before {
87
+ background-image: none;
88
+ background-color: $primary;
89
+ opacity: 1;
90
+ border-radius: 50%;
91
+ }
80
92
  }
81
93
 
82
94
  .slick-track {
@@ -132,7 +144,9 @@
132
144
 
133
145
  background-color: #3f4142e0;
134
146
 
135
- color: $white;
147
+ .slide-link {
148
+ color: $white;
149
+ }
136
150
 
137
151
  font-size: 1.8rem;
138
152
  font-weight: bold;
@@ -2,4 +2,10 @@
2
2
  label.has-prepend {
3
3
  z-index: unset;
4
4
  }
5
+
6
+ .react-select__option--is-focused {
7
+ border-color: var(--focus-outline-color) !important;
8
+ box-shadow: 0 0 0 2px var(--focus-outline-color) !important;
9
+ outline: none !important;
10
+ }
5
11
  }
@@ -3,6 +3,15 @@
3
3
  .text-justify {
4
4
  text-align: justify;
5
5
  }
6
+ button.btn,
7
+ button.rounded-right {
8
+ &:focus {
9
+ border-color: $focus-outline-color !important;
10
+ box-shadow: inset 0 1px 0 $focus-outline-color,
11
+ 0 1px 1px $focus-outline-color, 0 0 0 0.2rem $focus-outline-color !important;
12
+ outline: none;
13
+ }
14
+ }
6
15
 
7
16
  .btn-tertiary {
8
17
  @include button-variant($tertiary, $tertiary);
@@ -21,6 +30,12 @@
21
30
 
22
31
  a.btn-tertiary {
23
32
  color: $tertiary-text !important;
33
+ &:focus {
34
+ border-color: $focus-outline-color;
35
+ box-shadow: inset 0 1px 0 $focus-outline-color,
36
+ 0 1px 1px $focus-outline-color, 0 0 0 0.2rem $focus-outline-color;
37
+ outline: none;
38
+ }
24
39
  }
25
40
 
26
41
  .btn-outline-tertiary {
@@ -0,0 +1,3 @@
1
+ :root {
2
+ --focus-outline-color: #{$focus-outline-color};
3
+ }
@@ -218,6 +218,7 @@ body.cms-ui {
218
218
 
219
219
  .react-select__menu {
220
220
  z-index: 11;
221
+
221
222
  /* FIX CT SELECT REACTVIRTUALIZED */
222
223
  .ReactVirtualized__Grid.ReactVirtualized__List {
223
224
  width: 100% !important;
@@ -417,6 +418,14 @@ body.cms-ui {
417
418
  z-index: 0;
418
419
  }
419
420
  }
421
+ &.image {
422
+ .block.align {
423
+ &.left,
424
+ &.right {
425
+ z-index: 0;
426
+ }
427
+ }
428
+ }
420
429
  }
421
430
 
422
431
  a {
@@ -9,9 +9,9 @@
9
9
  align-items: flex-start;
10
10
  }
11
11
  .nextGenerationEULogo {
12
- margin-top: 10px;
13
- width: 178px;
14
- height: 56px;
12
+ height: 75px;
13
+ width: auto;
14
+ aspect-ratio: 167 / 41;
15
15
  }
16
16
  a {
17
17
  h2 {
@@ -37,8 +37,39 @@ body.search-modal-opened {
37
37
 
38
38
  .nav-tabs {
39
39
  .nav-item {
40
+ &:first-of-type {
41
+ a.nav-link:not(.active):focus {
42
+ border-right: 2px solid hsl(36deg, 100%, 50%) !important;
43
+ border-left: 2px solid hsl(36deg, 100%, 50%) !important;
44
+ }
45
+ a.nav-link.active:focus {
46
+ border-right: 2px solid hsl(36deg, 100%, 50%) !important;
47
+ border-left: 2px solid hsl(36deg, 100%, 50%) !important;
48
+ }
49
+ }
50
+ &:last-of-type {
51
+ a.nav-link:focus {
52
+ border-right: 2px solid hsl(36deg, 100%, 50%) !important;
53
+ border-right: 2px solid hsl(36deg, 100%, 50%) !important;
54
+ }
55
+ }
40
56
  a.nav-link {
41
57
  width: 100%;
58
+ border-top: 2px solid transparent;
59
+ &.active {
60
+ border-left: none;
61
+ border-right: none;
62
+ }
63
+ &:focus {
64
+ border-color: hsl(36deg, 100%, 50%) !important;
65
+ box-shadow: 0 0 0 2px hsl(36deg, 100%, 50%) !important;
66
+ outline: none;
67
+ border-right: none;
68
+ border-left: none;
69
+ }
70
+ &:not(.active):focus {
71
+ border-right: 2px solid hsl(36deg, 100%, 50%) !important;
72
+ }
42
73
  }
43
74
  }
44
75
  }
@@ -18,6 +18,7 @@
18
18
  @import 'extras/tables';
19
19
 
20
20
  /*** ItaliaTheme ***/
21
+ @import 'ItaliaTheme/css_variables';
21
22
  @import 'ItaliaTheme/common';
22
23
  @import 'ItaliaTheme/main';
23
24
  @import 'ItaliaTheme/ar';