design-comuni-plone-theme 11.12.0 → 11.12.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.
@@ -6,6 +6,9 @@ on:
6
6
  jobs:
7
7
  release_to_npm:
8
8
  runs-on: ubuntu-latest
9
+ permissions:
10
+ contents: read
11
+ id-token: write
9
12
  steps:
10
13
  - uses: actions/checkout@v4
11
14
 
@@ -14,6 +17,8 @@ jobs:
14
17
  node-version: '18.x'
15
18
  registry-url: 'https://registry.npmjs.org'
16
19
 
20
+ - run: npm install -g npm
21
+
17
22
  - run: yarn
18
23
 
19
24
  - name: Release to npm
Binary file
package/CHANGELOG.md CHANGED
@@ -1,5 +1,35 @@
1
1
 
2
2
 
3
+ ## [11.12.2](https://github.com/RedTurtle/design-comuni-plone-theme/compare/v11.12.1...v11.12.2) (2024-05-27)
4
+
5
+
6
+ ### Maintenance
7
+
8
+ * added more logging for some errors ([73a6d13](https://github.com/RedTurtle/design-comuni-plone-theme/commit/73a6d13969775a0d2e52a8794211154a6f8c4da0))
9
+ * updated repository info in package.json ([ac3ae90](https://github.com/RedTurtle/design-comuni-plone-theme/commit/ac3ae90b55b8e293bf7dc46394c4a5b81c8db035))
10
+
11
+
12
+ ### Documentation
13
+
14
+ * updated publiccode ([2a28968](https://github.com/RedTurtle/design-comuni-plone-theme/commit/2a28968d62a3800df080842dfc399f7433050b75))
15
+
16
+ ## [11.12.1](https://github.com/redturtle/design-comuni-plone-theme/compare/v11.12.0...v11.12.1) (2024-05-21)
17
+
18
+
19
+ ### Bug Fixes
20
+
21
+ * use UniversalLink in PageHeaderTassonomiaArgomenti ([690da33](https://github.com/redturtle/design-comuni-plone-theme/commit/690da33a5f568b177a9ac748af5a85da7520e314))
22
+
23
+
24
+ ### Maintenance
25
+
26
+ * fix npm release script ([bb652d7](https://github.com/redturtle/design-comuni-plone-theme/commit/bb652d717558f1541ad9c4d5250effe3adcd7f0a))
27
+
28
+
29
+ ### Documentation
30
+
31
+ * updated publiccode and release log ([1b3b3a9](https://github.com/redturtle/design-comuni-plone-theme/commit/1b3b3a9a28fb7ec31124fab06fec984000ad67ce))
32
+
3
33
  ## [11.12.0](https://github.com/redturtle/design-comuni-plone-theme/compare/v11.11.2...v11.12.0) (2024-05-21)
4
34
 
5
35
 
package/RELEASE.md CHANGED
@@ -41,6 +41,12 @@
41
41
  - ...
42
42
  -->
43
43
 
44
+ ## Versione 11.12.1 (21/05/2024)
45
+
46
+ ### Migliorie
47
+
48
+ - Migliorate le performance di navigazione per alcuni link agli argomenti nella testata delle pagine.
49
+
44
50
  ## Versione 11.12.0 (21/05/2024)
45
51
 
46
52
  ### Migliorie
package/package.json CHANGED
@@ -2,8 +2,12 @@
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.0",
5
+ "version": "11.12.2",
6
6
  "main": "src/index.js",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/RedTurtle/design-comuni-plone-theme"
10
+ },
7
11
  "keywords": [
8
12
  "volto-addon",
9
13
  "volto",
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-27'
231
231
  softwareType: standalone/web
232
- softwareVersion: 11.12.0
232
+ softwareVersion: 11.12.2
233
233
  url: 'https://github.com/italia/design-comuni-plone-theme'
234
234
  usedBy:
235
235
  - ASP Comuni Modenesi Area Nord
@@ -3,7 +3,7 @@ import { defineMessages, useIntl } from 'react-intl';
3
3
  import PropTypes from 'prop-types';
4
4
  import { Chip, ChipLabel } from 'design-react-kit';
5
5
 
6
- import { flattenToAppURL } from '@plone/volto/helpers';
6
+ import { UniversalLink } from '@plone/volto/components';
7
7
 
8
8
  /**
9
9
  * PageHeaderTassonomiaArgomenti view component class.
@@ -28,17 +28,16 @@ const PageHeaderTassonomiaArgomenti = ({ content }) => {
28
28
  <small>{intl.formatMessage(messages.topics)}</small>
29
29
  </h5>
30
30
  {content.tassonomia_argomenti.map((item, i) => (
31
- <a
32
- href={flattenToAppURL(item['@id'])}
31
+ <UniversalLink
32
+ item={item}
33
33
  key={item['@id']}
34
- title={item.title}
35
34
  className="text-decoration-none me-2 d-inline-block"
36
35
  data-element="service-topic"
37
36
  >
38
37
  <Chip color="primary" disabled={false} large={false} simple tag="div">
39
38
  <ChipLabel tag="span">{item.title}</ChipLabel>
40
39
  </Chip>
41
- </a>
40
+ </UniversalLink>
42
41
  ))}
43
42
  </div>
44
43
  ) : null;
@@ -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);