design-comuni-plone-theme 11.12.1 → 11.12.3

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.
Binary file
package/CHANGELOG.md CHANGED
@@ -1,5 +1,30 @@
1
1
 
2
2
 
3
+ ## [11.12.3](https://github.com/RedTurtle/design-comuni-plone-theme/compare/v11.12.2...v11.12.3) (2024-05-28)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * removed figure tag + added prop for link condition ([#685](https://github.com/RedTurtle/design-comuni-plone-theme/issues/685)) ([601e16c](https://github.com/RedTurtle/design-comuni-plone-theme/commit/601e16c51938aefb45524d8661a901427ff79c86))
9
+
10
+
11
+ ### Documentation
12
+
13
+ * updated publiccode ([79cae20](https://github.com/RedTurtle/design-comuni-plone-theme/commit/79cae2059c5135d4f036eb16b193f072dd176f86))
14
+
15
+ ## [11.12.2](https://github.com/RedTurtle/design-comuni-plone-theme/compare/v11.12.1...v11.12.2) (2024-05-27)
16
+
17
+
18
+ ### Maintenance
19
+
20
+ * added more logging for some errors ([73a6d13](https://github.com/RedTurtle/design-comuni-plone-theme/commit/73a6d13969775a0d2e52a8794211154a6f8c4da0))
21
+ * updated repository info in package.json ([ac3ae90](https://github.com/RedTurtle/design-comuni-plone-theme/commit/ac3ae90b55b8e293bf7dc46394c4a5b81c8db035))
22
+
23
+
24
+ ### Documentation
25
+
26
+ * updated publiccode ([2a28968](https://github.com/RedTurtle/design-comuni-plone-theme/commit/2a28968d62a3800df080842dfc399f7433050b75))
27
+
3
28
  ## [11.12.1](https://github.com/redturtle/design-comuni-plone-theme/compare/v11.12.0...v11.12.1) (2024-05-21)
4
29
 
5
30
 
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "design-comuni-plone-theme",
3
3
  "description": "Volto Theme for Italia design guidelines",
4
4
  "license": "GPL-v3",
5
- "version": "11.12.1",
5
+ "version": "11.12.3",
6
6
  "main": "src/index.js",
7
7
  "repository": {
8
8
  "type": "git",
package/publiccode.yml CHANGED
@@ -227,9 +227,9 @@ maintenance:
227
227
  name: io-Comune - Il sito AgID per Comuni ed Enti Pubblici
228
228
  platforms:
229
229
  - web
230
- releaseDate: '2024-05-21'
230
+ releaseDate: '2024-05-28'
231
231
  softwareType: standalone/web
232
- softwareVersion: 11.12.1
232
+ softwareVersion: 11.12.3
233
233
  url: 'https://github.com/italia/design-comuni-plone-theme'
234
234
  usedBy:
235
235
  - ASP Comuni Modenesi Area Nord
@@ -102,7 +102,7 @@ const CardWithImageDefault = (props) => {
102
102
  })}
103
103
  >
104
104
  <div className="img-responsive img-responsive-panoramic">
105
- <figure className="img-wrapper">{image}</figure>
105
+ {image}
106
106
  {item['@type'] === 'Event' && (
107
107
  <CardCalendar
108
108
  start={item.start}
@@ -11,6 +11,7 @@ const ListingImage = ({
11
11
  responsive = true,
12
12
  showTitleAttr = true,
13
13
  sizes = '(max-width:320px) 200px, (max-width:425px) 300px, (max-width:767px) 500px, 410px',
14
+ noWrapLink = false,
14
15
  ...imageProps
15
16
  }) => {
16
17
  const Image = config.getComponent({ name: 'Image' }).component;
@@ -30,8 +31,12 @@ const ListingImage = ({
30
31
  // photogallery needs to check for null image
31
32
  // https://stackoverflow.com/questions/33136399/is-there-a-way-to-tell-if-reactelement-renders-null
32
33
 
33
- const image = (
34
- <UniversalLink item={item}>{Image(commonImageProps)}</UniversalLink>
34
+ const image = !noWrapLink ? (
35
+ <UniversalLink item={item} className="img-wrapper">
36
+ {Image(commonImageProps)}
37
+ </UniversalLink>
38
+ ) : (
39
+ Image(commonImageProps)
35
40
  );
36
41
  if (image === null)
37
42
  return showDefault ? <img src={DefaultImageSVG} alt="" /> : null;
@@ -109,7 +109,7 @@ const InEvidenceTemplate = (props) => {
109
109
  {index === 0 && image && (
110
110
  <div className="img-responsive-wrapper">
111
111
  <div className="img-responsive">
112
- <figure className="img-wrapper">{image}</figure>
112
+ {image}
113
113
  {item['@type'] === 'Event' && (
114
114
  <CardCalendar start={item.start} end={item.end} />
115
115
  )}
@@ -160,9 +160,10 @@ const PhotogalleryTemplate = ({
160
160
  items.length === 1
161
161
  ? '1300'
162
162
  : items.length === 2
163
- ? '650'
164
- : '450'
163
+ ? '650'
164
+ : '450'
165
165
  }px`,
166
+ noWrapLink: true,
166
167
  });
167
168
  return (
168
169
  <SingleSlideWrapper
@@ -43,6 +43,7 @@ const SmallBlockLinksTemplate = ({
43
43
  sizes: '(max-width:575px) 520px, 200px',
44
44
  style: {},
45
45
  alt: item.title,
46
+ noWrapLink: true,
46
47
  });
47
48
 
48
49
  return (
@@ -0,0 +1,76 @@
1
+ /**
2
+ * @module components/theme/Error/Error
3
+ * Customization:
4
+ * - added logging of errors
5
+ */
6
+
7
+ import React from 'react';
8
+ import loadable from '@loadable/component';
9
+ import config from '@plone/volto/registry';
10
+
11
+ const sentryLibraries = {
12
+ Sentry: loadable.lib(
13
+ () => import(/* webpackChunkName: "s_entry-browser" */ '@sentry/browser'),
14
+ ),
15
+ };
16
+
17
+ /**
18
+ * Error function.
19
+ * @function Error
20
+ * @returns {string} Markup of the error page.
21
+ */
22
+ const Error = (props) => {
23
+ const { views } = config;
24
+ const { error } = props;
25
+ let FoundView;
26
+
27
+ // CUSTOMIZATION: added logging of errors
28
+ const notifySentry = (error) => {
29
+ const loaders = Object.entries(sentryLibraries).map(
30
+ ([name, Lib]) =>
31
+ new Promise((resolve) =>
32
+ Lib.load().then((mod) => resolve([name, mod])),
33
+ ),
34
+ );
35
+ Promise.all(loaders).then((libs) => {
36
+ const libraries = Object.assign(
37
+ {},
38
+ ...libs.map(([name, lib]) => ({ [name]: lib })),
39
+ );
40
+ libraries.Sentry.captureException(error);
41
+ });
42
+ };
43
+
44
+ if (error.status === undefined) {
45
+ // For some reason, while development and if CORS is in place and the
46
+ // requested resource is 404, it returns undefined as status, then the
47
+ // next statement will fail
48
+ // eslint-disable-next-line no-console
49
+ console.error(
50
+ 'DEV MODE CORS ERROR in Error component: ',
51
+ JSON.stringify(props, null, 2),
52
+ );
53
+ notifySentry(props);
54
+ FoundView = views.errorViews.corsError;
55
+ } else {
56
+ if (error.status.toString() === 'corsError') {
57
+ // eslint-disable-next-line no-console
58
+ console.error(
59
+ 'CORS ERROR in Error component: ',
60
+ JSON.stringify(props, null, 2),
61
+ );
62
+ notifySentry(props);
63
+ }
64
+ FoundView = views.errorViews[error.status.toString()];
65
+ }
66
+ if (!FoundView) {
67
+ FoundView = views.errorViews['404']; // default to 404
68
+ }
69
+ return (
70
+ <div id="view">
71
+ <FoundView {...props} />
72
+ </div>
73
+ );
74
+ };
75
+
76
+ export default Error;
@@ -0,0 +1,340 @@
1
+ /**
2
+ * View container.
3
+ * @module components/theme/View/View
4
+ * Customization:
5
+ * - added logging of errors
6
+ */
7
+
8
+ import React, { Component } from 'react';
9
+ import PropTypes from 'prop-types';
10
+ import { connect } from 'react-redux';
11
+ import { compose } from 'redux';
12
+ import { Redirect } from 'react-router-dom';
13
+ import { Portal } from 'react-portal';
14
+ import { injectIntl } from 'react-intl';
15
+ import qs from 'query-string';
16
+
17
+ import loadable from '@loadable/component';
18
+
19
+ import {
20
+ ContentMetadataTags,
21
+ Comments,
22
+ Tags,
23
+ Toolbar,
24
+ } from '@plone/volto/components';
25
+ import { listActions, getContent } from '@plone/volto/actions';
26
+ import {
27
+ BodyClass,
28
+ getBaseUrl,
29
+ flattenToAppURL,
30
+ getLayoutFieldname,
31
+ hasApiExpander,
32
+ } from '@plone/volto/helpers';
33
+
34
+ import config from '@plone/volto/registry';
35
+
36
+ const sentryLibraries = {
37
+ Sentry: loadable.lib(
38
+ () => import(/* webpackChunkName: "s_entry-browser" */ '@sentry/browser'),
39
+ ),
40
+ };
41
+
42
+ /**
43
+ * View container class.
44
+ * @class View
45
+ * @extends Component
46
+ */
47
+ class View extends Component {
48
+ /**
49
+ * Property types.
50
+ * @property {Object} propTypes Property types.
51
+ * @static
52
+ */
53
+ static propTypes = {
54
+ actions: PropTypes.shape({
55
+ object: PropTypes.arrayOf(PropTypes.object),
56
+ object_buttons: PropTypes.arrayOf(PropTypes.object),
57
+ user: PropTypes.arrayOf(PropTypes.object),
58
+ }),
59
+ listActions: PropTypes.func.isRequired,
60
+ /**
61
+ * Action to get the content
62
+ */
63
+ getContent: PropTypes.func.isRequired,
64
+ /**
65
+ * Pathname of the object
66
+ */
67
+ pathname: PropTypes.string.isRequired,
68
+ location: PropTypes.shape({
69
+ search: PropTypes.string,
70
+ pathname: PropTypes.string,
71
+ }).isRequired,
72
+ /**
73
+ * Version id of the object
74
+ */
75
+ versionId: PropTypes.string,
76
+ /**
77
+ * Content of the object
78
+ */
79
+ content: PropTypes.shape({
80
+ /**
81
+ * Layout of the object
82
+ */
83
+ layout: PropTypes.string,
84
+ /**
85
+ * Allow discussion of the object
86
+ */
87
+ allow_discussion: PropTypes.bool,
88
+ /**
89
+ * Title of the object
90
+ */
91
+ title: PropTypes.string,
92
+ /**
93
+ * Description of the object
94
+ */
95
+ description: PropTypes.string,
96
+ /**
97
+ * Type of the object
98
+ */
99
+ '@type': PropTypes.string,
100
+ /**
101
+ * Subjects of the object
102
+ */
103
+ subjects: PropTypes.arrayOf(PropTypes.string),
104
+ is_folderish: PropTypes.bool,
105
+ }),
106
+ error: PropTypes.shape({
107
+ /**
108
+ * Error type
109
+ */
110
+ status: PropTypes.number,
111
+ }),
112
+ };
113
+
114
+ /**
115
+ * Default properties.
116
+ * @property {Object} defaultProps Default properties.
117
+ * @static
118
+ */
119
+ static defaultProps = {
120
+ actions: null,
121
+ content: null,
122
+ versionId: null,
123
+ error: null,
124
+ };
125
+
126
+ state = {
127
+ hasObjectButtons: null,
128
+ isClient: false,
129
+ };
130
+
131
+ componentDidMount() {
132
+ // Do not trigger the actions action if the expander is present
133
+ if (!hasApiExpander('actions', getBaseUrl(this.props.pathname))) {
134
+ this.props.listActions(getBaseUrl(this.props.pathname));
135
+ }
136
+
137
+ this.props.getContent(
138
+ getBaseUrl(this.props.pathname),
139
+ this.props.versionId,
140
+ );
141
+ this.setState({ isClient: true });
142
+ }
143
+
144
+ /**
145
+ * Component will receive props
146
+ * @method componentWillReceiveProps
147
+ * @param {Object} nextProps Next properties
148
+ * @returns {undefined}
149
+ */
150
+ UNSAFE_componentWillReceiveProps(nextProps) {
151
+ if (nextProps.pathname !== this.props.pathname) {
152
+ // Do not trigger the actions action if the expander is present
153
+ if (!hasApiExpander('actions', getBaseUrl(nextProps.pathname))) {
154
+ this.props.listActions(getBaseUrl(nextProps.pathname));
155
+ }
156
+
157
+ this.props.getContent(
158
+ getBaseUrl(nextProps.pathname),
159
+ this.props.versionId,
160
+ );
161
+ }
162
+
163
+ if (nextProps.actions.object_buttons) {
164
+ const objectButtons = nextProps.actions.object_buttons;
165
+ this.setState({
166
+ hasObjectButtons: !!objectButtons.length,
167
+ });
168
+ }
169
+ }
170
+
171
+ /**
172
+ * Default fallback view
173
+ * @method getViewDefault
174
+ * @returns {string} Markup for component.
175
+ */
176
+ getViewDefault = () => config.views.defaultView;
177
+
178
+ /**
179
+ * Get view by content type
180
+ * @method getViewByType
181
+ * @returns {string} Markup for component.
182
+ */
183
+ getViewByType = () =>
184
+ config.views.contentTypesViews[this.props.content['@type']] || null;
185
+
186
+ /**
187
+ * Get view by content layout property
188
+ * @method getViewByLayout
189
+ * @returns {string} Markup for component.
190
+ */
191
+ getViewByLayout = () =>
192
+ config.views.layoutViews[
193
+ this.props.content[getLayoutFieldname(this.props.content)]
194
+ ] || null;
195
+
196
+ /**
197
+ * Cleans the component displayName (specially for connected components)
198
+ * which have the Connect(componentDisplayName)
199
+ * @method cleanViewName
200
+ * @param {string} dirtyDisplayName The displayName
201
+ * @returns {string} Clean displayName (no Connect(...)).
202
+ */
203
+ cleanViewName = (dirtyDisplayName) =>
204
+ dirtyDisplayName
205
+ .replace('Connect(', '')
206
+ .replace('injectIntl(', '')
207
+ .replace(')', '')
208
+ .replace('connect(', '')
209
+ .toLowerCase();
210
+
211
+ // CUSTOMIZATION: added logging of errors
212
+ notifySentry = (error) => {
213
+ const loaders = Object.entries(sentryLibraries).map(
214
+ ([name, Lib]) =>
215
+ new Promise((resolve) =>
216
+ Lib.load().then((mod) => resolve([name, mod])),
217
+ ),
218
+ );
219
+ Promise.all(loaders).then((libs) => {
220
+ const libraries = Object.assign(
221
+ {},
222
+ ...libs.map(([name, lib]) => ({ [name]: lib })),
223
+ );
224
+ libraries.Sentry.captureException(error);
225
+ });
226
+ };
227
+
228
+ /**
229
+ * Render method.
230
+ * @method render
231
+ * @returns {string} Markup for the component.
232
+ */
233
+ render() {
234
+ const { views } = config;
235
+ if (this.props.error && this.props.error.code === 301) {
236
+ const redirect = flattenToAppURL(this.props.error.url).split('?')[0];
237
+ return <Redirect to={`${redirect}${this.props.location.search}`} />;
238
+ } else if (this.props.error && !this.props.connectionRefused) {
239
+ let FoundView;
240
+ if (this.props.error.status === undefined) {
241
+ // For some reason, while development and if CORS is in place and the
242
+ // requested resource is 404, it returns undefined as status, then the
243
+ // next statement will fail
244
+ // eslint-disable-next-line no-console
245
+ console.error(
246
+ 'DEV MODE CORS ERROR in View component: ',
247
+ JSON.stringify(this.props, null, 2),
248
+ );
249
+ this.notifySentry(this.props);
250
+ FoundView = views.errorViews.corsError;
251
+ } else {
252
+ if (this.props.error.status.toString() === 'corsError') {
253
+ // eslint-disable-next-line no-console
254
+ console.error(
255
+ 'CORS ERROR in View component: ',
256
+ JSON.stringify(this.props, null, 2),
257
+ );
258
+ this.notifySentry(this.props);
259
+ }
260
+ FoundView = views.errorViews[this.props.error.status.toString()];
261
+ }
262
+ if (!FoundView) {
263
+ FoundView = views.errorViews['404']; // default to 404
264
+ }
265
+ return (
266
+ <div id="view">
267
+ <FoundView {...this.props} />
268
+ </div>
269
+ );
270
+ }
271
+ if (!this.props.content) {
272
+ return <span />;
273
+ }
274
+ const RenderedView =
275
+ this.getViewByLayout() || this.getViewByType() || this.getViewDefault();
276
+
277
+ return (
278
+ <div id="view">
279
+ <ContentMetadataTags content={this.props.content} />
280
+ {/* Body class if displayName in component is set */}
281
+ <BodyClass
282
+ className={
283
+ RenderedView.displayName
284
+ ? `view-${this.cleanViewName(RenderedView.displayName)}`
285
+ : null
286
+ }
287
+ />
288
+ <RenderedView
289
+ key={this.props.content['@id']}
290
+ content={this.props.content}
291
+ location={this.props.location}
292
+ token={this.props.token}
293
+ history={this.props.history}
294
+ />
295
+ {config.settings.showTags &&
296
+ this.props.content.subjects &&
297
+ this.props.content.subjects.length > 0 && (
298
+ <Tags tags={this.props.content.subjects} />
299
+ )}
300
+ {/* Add opt-in social sharing if required, disabled by default */}
301
+ {/* In the future this might be parameterized from the app config */}
302
+ {/* <SocialSharing
303
+ url={typeof window === 'undefined' ? '' : window.location.href}
304
+ title={this.props.content.title}
305
+ description={this.props.content.description || ''}
306
+ /> */}
307
+ {this.props.content.allow_discussion && (
308
+ <Comments pathname={this.props.pathname} />
309
+ )}
310
+ {this.state.isClient && (
311
+ <Portal node={document.getElementById('toolbar')}>
312
+ <Toolbar pathname={this.props.pathname} inner={<span />} />
313
+ </Portal>
314
+ )}
315
+ </div>
316
+ );
317
+ }
318
+ }
319
+
320
+ export default compose(
321
+ injectIntl,
322
+ connect(
323
+ (state, props) => ({
324
+ actions: state.actions.actions,
325
+ token: state.userSession.token,
326
+ content: state.content.data,
327
+ error: state.content.get.error,
328
+ apiError: state.apierror.error,
329
+ connectionRefused: state.apierror.connectionRefused,
330
+ pathname: props.location.pathname,
331
+ versionId:
332
+ qs.parse(props.location.search) &&
333
+ qs.parse(props.location.search).version,
334
+ }),
335
+ {
336
+ listActions,
337
+ getContent,
338
+ },
339
+ ),
340
+ )(View);