design-comuni-plone-theme 8.3.2 → 8.4.1
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 +44 -0
- package/locales/de/LC_MESSAGES/volto.po +31 -16
- package/locales/en/LC_MESSAGES/volto.po +36 -21
- package/locales/es/LC_MESSAGES/volto.po +31 -16
- package/locales/fr/LC_MESSAGES/volto.po +31 -16
- package/locales/it/LC_MESSAGES/volto.po +32 -17
- package/locales/volto.pot +32 -17
- package/package.json +1 -1
- package/publiccode.yml +2 -2
- package/src/components/Image/helpers.js +2 -1
- package/src/components/ItaliaTheme/Blocks/Listing/Commons/utils.js +63 -0
- package/src/components/ItaliaTheme/Blocks/Listing/SliderTemplate.jsx +235 -65
- package/src/components/ItaliaTheme/Breadcrumbs/Breadcrumbs.jsx +2 -2
- package/src/components/ItaliaTheme/CustomerSatisfaction/FeedbackForm.jsx +11 -7
- package/src/components/ItaliaTheme/Footer/FooterPNRRLogo.jsx +2 -2
- package/src/components/ItaliaTheme/Footer/logo-eu-inverted.svg +1 -17
- package/src/components/ItaliaTheme/Header/HeaderSearch/SearchModal.jsx +38 -11
- package/src/components/ItaliaTheme/Search/Search.jsx +7 -3
- package/src/components/ItaliaTheme/Search/utils.js +5 -3
- package/src/components/ItaliaTheme/Unauthorized/Unauthorized.jsx +3 -1
- package/src/components/ItaliaTheme/View/PersonaView/PersonaRuolo.jsx +108 -8
- package/src/components/ItaliaTheme/manage/Widgets/SubFooterConfigurationForm.jsx +1 -0
- package/src/customizations/volto/components/manage/Blocks/ToC/View.jsx +2 -0
- package/src/customizations/volto/components/manage/Blocks/ToC/variations/DefaultTocRenderer.jsx +99 -0
- package/src/customizations/volto/components/theme/Breadcrumbs/Breadcrumbs.jsx +3 -3
- package/src/customizations/volto/components/theme/View/View.jsx +308 -0
- package/src/customizations/volto/helpers/Api/Api.jsx +131 -0
- package/src/customizations/volto/middleware/api.js +362 -0
- package/src/customizations/volto/middleware/blacklistRoutes.js +47 -0
- package/src/theme/ItaliaTheme/Blocks/_imageBlock.scss +4 -0
- package/src/theme/ItaliaTheme/Blocks/_sliderTemplate.scss +18 -4
- package/src/theme/ItaliaTheme/Components/_search.scss +6 -0
- package/src/theme/ItaliaTheme/_common.scss +15 -0
- package/src/theme/ItaliaTheme/_css_variables.scss +3 -0
- package/src/theme/_cms-ui.scss +9 -0
- package/src/theme/bootstrap-override/bootstrap-italia/_footer.scss +3 -3
- package/src/theme/extras/_search.scss +31 -0
- package/src/theme/site.scss +1 -0
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* backport https://github.com/plone/volto/pull/4854
|
|
3
|
+
*
|
|
4
|
+
* Api helper.
|
|
5
|
+
* @module helpers/Api
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import superagent from 'superagent';
|
|
9
|
+
import Cookies from 'universal-cookie';
|
|
10
|
+
import config from '@plone/volto/registry';
|
|
11
|
+
import { addHeadersFactory } from '@plone/volto/helpers/Proxy/Proxy';
|
|
12
|
+
import { stripQuerystring } from '@plone/volto/helpers';
|
|
13
|
+
|
|
14
|
+
const methods = ['get', 'post', 'put', 'patch', 'del'];
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Format the url.
|
|
18
|
+
* @function formatUrl
|
|
19
|
+
* @param {string} path Path (or URL) to be formatted.
|
|
20
|
+
* @returns {string} Formatted path.
|
|
21
|
+
*/
|
|
22
|
+
function formatUrl(path) {
|
|
23
|
+
const { settings } = config;
|
|
24
|
+
const APISUFIX = settings.legacyTraverse ? '' : '/++api++';
|
|
25
|
+
|
|
26
|
+
if (path.startsWith('http://') || path.startsWith('https://')) return path;
|
|
27
|
+
|
|
28
|
+
const adjustedPath = path[0] !== '/' ? `/${path}` : path;
|
|
29
|
+
let apiPath = '';
|
|
30
|
+
if (settings.internalApiPath && __SERVER__) {
|
|
31
|
+
apiPath = settings.internalApiPath;
|
|
32
|
+
} else if (settings.apiPath) {
|
|
33
|
+
apiPath = settings.apiPath;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return `${apiPath}${APISUFIX}${adjustedPath}`;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Api class.
|
|
41
|
+
* @class Api
|
|
42
|
+
*/
|
|
43
|
+
class Api {
|
|
44
|
+
/**
|
|
45
|
+
* Constructor
|
|
46
|
+
* @method constructor
|
|
47
|
+
* @constructs Api
|
|
48
|
+
*/
|
|
49
|
+
constructor(req) {
|
|
50
|
+
const cookies = new Cookies();
|
|
51
|
+
|
|
52
|
+
methods.forEach((method) => {
|
|
53
|
+
this[method] = (
|
|
54
|
+
path,
|
|
55
|
+
{ params, data, type, headers = {}, checkUrl = false } = {},
|
|
56
|
+
) => {
|
|
57
|
+
let request;
|
|
58
|
+
let promise = new Promise((resolve, reject) => {
|
|
59
|
+
request = superagent[method](formatUrl(path));
|
|
60
|
+
|
|
61
|
+
if (params) {
|
|
62
|
+
request.query(params);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
let authToken;
|
|
66
|
+
if (req) {
|
|
67
|
+
// We are in SSR
|
|
68
|
+
authToken = req.universalCookies.get('auth_token');
|
|
69
|
+
request.use(addHeadersFactory(req));
|
|
70
|
+
} else {
|
|
71
|
+
authToken = cookies.get('auth_token');
|
|
72
|
+
}
|
|
73
|
+
if (authToken) {
|
|
74
|
+
request.set('Authorization', `Bearer ${authToken}`);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
request.set('Accept', 'application/json');
|
|
78
|
+
|
|
79
|
+
if (type) {
|
|
80
|
+
request.type(type);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
Object.keys(headers).forEach((key) => request.set(key, headers[key]));
|
|
84
|
+
|
|
85
|
+
if (__SERVER__ && checkUrl && ['get', 'head'].includes(method)) {
|
|
86
|
+
request.redirects(0);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (data) {
|
|
90
|
+
request.send(data);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
request.end((err, response) => {
|
|
94
|
+
if (
|
|
95
|
+
checkUrl &&
|
|
96
|
+
request.url &&
|
|
97
|
+
request.xhr &&
|
|
98
|
+
encodeURI(stripQuerystring(request.url)) !==
|
|
99
|
+
stripQuerystring(request.xhr.responseURL)
|
|
100
|
+
) {
|
|
101
|
+
if (request.xhr.responseURL?.length === 0) {
|
|
102
|
+
return reject({
|
|
103
|
+
code: 408,
|
|
104
|
+
status: 408,
|
|
105
|
+
url: request.xhr.responseURL,
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
return reject({
|
|
109
|
+
code: 301,
|
|
110
|
+
url: request.xhr.responseURL,
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (['301', '302', '307', '308'].includes(err?.status)) {
|
|
115
|
+
return reject({
|
|
116
|
+
code: err.status,
|
|
117
|
+
url: err.response.headers.location,
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return err ? reject(err) : resolve(response.body || response.text);
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
promise.request = request;
|
|
125
|
+
return promise;
|
|
126
|
+
};
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export default Api;
|
|
@@ -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;
|
|
@@ -46,12 +46,12 @@
|
|
|
46
46
|
.icon {
|
|
47
47
|
width: 25px;
|
|
48
48
|
height: 40px;
|
|
49
|
-
color:
|
|
50
|
-
fill:
|
|
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
|
-
|
|
147
|
+
.slide-link {
|
|
148
|
+
color: $white;
|
|
149
|
+
}
|
|
136
150
|
|
|
137
151
|
font-size: 1.8rem;
|
|
138
152
|
font-weight: bold;
|
|
@@ -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 {
|
package/src/theme/_cms-ui.scss
CHANGED
|
@@ -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 {
|