design-comuni-plone-theme 11.1.0 → 11.1.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 CHANGED
@@ -1,5 +1,23 @@
1
1
 
2
2
 
3
+ ## [11.1.1](https://github.com/redturtle/design-comuni-plone-theme/compare/v11.1.0...v11.1.1) (2023-12-22)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * image sizes too small ([b83e3da](https://github.com/redturtle/design-comuni-plone-theme/commit/b83e3da3dffdac76ec8b67f9b9333d840e380762))
9
+ * recurrence widget ([#445](https://github.com/redturtle/design-comuni-plone-theme/issues/445)) ([201a3ac](https://github.com/redturtle/design-comuni-plone-theme/commit/201a3acf5934ff727bcef9053e46c478e339b8f1))
10
+
11
+
12
+ ### Maintenance
13
+
14
+ * update customizations ([bf8c37f](https://github.com/redturtle/design-comuni-plone-theme/commit/bf8c37fec68811eab25c7777f04320be1bee046a))
15
+
16
+
17
+ ### Documentation
18
+
19
+ * updated publiccode ([5f7a4eb](https://github.com/redturtle/design-comuni-plone-theme/commit/5f7a4eb48a26f96a6653f7cd755ac486782277f8))
20
+
3
21
  ## [11.1.0](https://github.com/redturtle/design-comuni-plone-theme/compare/v11.0.2...v11.1.0) (2023-12-20)
4
22
 
5
23
 
package/RELEASE.md CHANGED
@@ -41,6 +41,12 @@
41
41
  - ...
42
42
  -->
43
43
 
44
+ ## Versione 11.1.1 (22/12/2023)
45
+
46
+ ### Fix
47
+
48
+ - Risolto un problema con la dimensione delle immagini, che risultava in alcuni casi troppo piccola causando immagini sgranate
49
+
44
50
  ## Versione 11.0.1 (19/12/2023)
45
51
 
46
52
  ### Fix
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.1.0",
5
+ "version": "11.1.1",
6
6
  "main": "src/index.js",
7
7
  "keywords": [
8
8
  "volto-addon",
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: '2023-12-20'
230
+ releaseDate: '2023-12-22'
231
231
  softwareType: standalone/web
232
- softwareVersion: 11.1.0
232
+ softwareVersion: 11.1.1
233
233
  url: 'https://github.com/italia/design-comuni-plone-theme'
234
234
  usedBy:
235
235
  - ASP Comuni Modenesi Area Nord
@@ -8,25 +8,27 @@ const ListingImage = ({
8
8
  showDefault = false,
9
9
  className = 'listing-image',
10
10
  responsive = true,
11
- sizes = '(max-width:320px) 200px, (max-width:425px) 300px, (max-width:768px) 400px, 300px',
11
+ sizes = '(max-width:320px) 200px, (max-width:425px) 300px, (max-width:767px) 500px, 410px',
12
12
  ...imageProps
13
13
  }) => {
14
14
  const Image = config.getComponent({ name: 'Image' }).component;
15
15
  // photogallery needs to check for null image
16
16
  // https://stackoverflow.com/questions/33136399/is-there-a-way-to-tell-if-reactelement-renders-null
17
- const image = Image({
18
- item,
19
- 'aria-hidden': true,
20
- alt: '',
21
- role: 'presentation',
22
- className,
23
- loading,
24
- title: item.title,
25
- responsive,
26
- sizes,
27
- ...imageProps });
17
+ const image = Image({
18
+ item,
19
+ 'aria-hidden': true,
20
+ alt: '',
21
+ role: 'presentation',
22
+ className,
23
+ loading,
24
+ title: item.title,
25
+ responsive,
26
+ sizes,
27
+ ...imageProps,
28
+ });
28
29
 
29
- if (image === null) return showDefault ? <img src={DefaultImageSVG} alt="" /> : null;
30
+ if (image === null)
31
+ return showDefault ? <img src={DefaultImageSVG} alt="" /> : null;
30
32
 
31
33
  return image;
32
34
  };
@@ -65,7 +65,12 @@ const ContentInEvidenceTemplate = (props) => {
65
65
  const date = getCalendarDate(item, rrule.rrulestr);
66
66
  const eventRecurrenceMore = getEventRecurrenceMore(item, isEditMode);
67
67
  const listingText = <ListingText item={item} />;
68
- const image = ListingImage({ item, className: 'item-image' });
68
+ const image = ListingImage({
69
+ item,
70
+ className: 'item-image',
71
+ loading: 'eager',
72
+ sizes: '(max-width:425px) 400px, (max-width:767px) 520px, 650px',
73
+ });
69
74
  const icon = getItemIcon(item);
70
75
  const BlockExtraTags = getComponentWithFallback({
71
76
  name: 'BlockExtraTags',
@@ -165,7 +165,18 @@ const PhotogalleryTemplate = ({
165
165
  <div className="it-carousel-all it-card-bg">
166
166
  <Slider {...settings} ref={slider}>
167
167
  {items.map((item, i) => {
168
- const image = ListingImage({ item });
168
+ const image = ListingImage({
169
+ item,
170
+ sizes: `(max-width:600px) 450px, (max-width:1024px) ${
171
+ items.length < 2 ? '1000' : '500'
172
+ }px, ${
173
+ items.length === 1
174
+ ? '1300'
175
+ : items.length === 2
176
+ ? '650'
177
+ : '450'
178
+ }px`,
179
+ });
169
180
  return (
170
181
  <div
171
182
  className={cx('it-single-slide-wrapper', {
@@ -353,7 +353,7 @@ const SliderTemplate = ({
353
353
  const image = ListingImage({
354
354
  item,
355
355
  loading: index === 0 ? 'eager' : 'lazy',
356
- sizes: `max-width(980px) 600px, ${1200 / nSlidesToShow}px`,
356
+ sizes: `max-width(991px) 620px, ${1300 / nSlidesToShow}px`,
357
357
  critical: true,
358
358
  });
359
359
  return (
@@ -38,7 +38,11 @@ const SmallBlockLinksTemplate = ({
38
38
  )}
39
39
  <Row className="items">
40
40
  {items.map((item, index) => {
41
- const image = ListingImage({ item, sizes: '200px', style: {} });
41
+ const image = ListingImage({
42
+ item,
43
+ sizes: '(max-width:575px) 520px, 200px',
44
+ style: {},
45
+ });
42
46
 
43
47
  return (
44
48
  <Col
@@ -22,8 +22,7 @@ export const CardPersona = ({
22
22
  }) => {
23
23
  const image = ListingImage({
24
24
  item,
25
- maxSize: 300,
26
- useOriginal: false,
25
+ sizes: '130px',
27
26
  });
28
27
 
29
28
  const hasImage = image !== null && showImage;
@@ -0,0 +1,133 @@
1
+ /**
2
+ * EndField component.
3
+ * @module components/manage/Widgets/RecurrenceWidget/EndField
4
+ *
5
+ *
6
+ * * CUSTOMIZATIONS:
7
+ * - added customization to have this changes https://github.com/plone/volto/pull/5555/files
8
+ */
9
+
10
+ import React from 'react';
11
+ import PropTypes from 'prop-types';
12
+ import { defineMessages, injectIntl } from 'react-intl';
13
+ import { Form, Grid, Input, Radio } from 'semantic-ui-react';
14
+ import DatetimeWidget from '../DatetimeWidget';
15
+
16
+ const messages = defineMessages({
17
+ recurrenceEnds: { id: 'Recurrence ends', defaultMessage: 'Ends' },
18
+ recurrenceEndsCount: { id: 'Recurrence ends after', defaultMessage: 'after' },
19
+ recurrenceEndsUntil: { id: 'Recurrence ends on', defaultMessage: 'on' },
20
+ occurrences: { id: 'Occurences', defaultMessage: 'occurrence(s)' },
21
+ });
22
+ /**
23
+ * EndField component class.
24
+ * @function EndField
25
+ * @returns {string} Markup of the component.
26
+ */
27
+ const EndField = ({ value, count, until, onChange, intl }) => {
28
+ return (
29
+ <Form.Field inline className="text">
30
+ <Grid>
31
+ <Grid.Row stretched>
32
+ <Grid.Column width="4">
33
+ <div className="wrapper">
34
+ <label htmlFor="recurrenceEnds">
35
+ {intl.formatMessage(messages.recurrenceEnds)}
36
+ </label>
37
+ </div>
38
+ </Grid.Column>
39
+ <Grid.Column width="8">
40
+ <Form.Group inline>
41
+ <Form.Field>
42
+ <Radio
43
+ label=""
44
+ name="recurrenceEnds"
45
+ id="recurrenceEndsCount"
46
+ value="count"
47
+ checked={value === 'count'}
48
+ onChange={(e, { value }) => onChange('recurrenceEnds', value)}
49
+ />
50
+ </Form.Field>
51
+ <Form.Field disabled={value !== 'count'}>
52
+ {intl.formatMessage(messages.recurrenceEndsCount)}
53
+ </Form.Field>
54
+ <Form.Field disabled={value !== 'count'}>
55
+ <Input
56
+ id="count"
57
+ name="count"
58
+ value={count || ''}
59
+ onChange={({ target }) => {
60
+ onChange(
61
+ target.id,
62
+ target.value === '' ? undefined : target.value,
63
+ );
64
+ }}
65
+ />
66
+ </Form.Field>
67
+ <Form.Field disabled={value !== 'count'}>
68
+ {intl.formatMessage(messages.occurrences)}
69
+ </Form.Field>
70
+ </Form.Group>
71
+ <Form.Group inline>
72
+ <Form.Field>
73
+ <Radio
74
+ id="recurrenceEndsUntil"
75
+ label=""
76
+ name="recurrenceEnds"
77
+ value="until"
78
+ checked={value === 'until'}
79
+ onChange={(e, { value }) => onChange('recurrenceEnds', value)}
80
+ />
81
+ </Form.Field>
82
+
83
+ <Form.Field disabled={value !== 'until'}>
84
+ <DatetimeWidget
85
+ id="until"
86
+ title={intl.formatMessage(messages.recurrenceEndsUntil)}
87
+ dateOnly={true}
88
+ value={
89
+ until
90
+ ? typeof until === 'string'
91
+ ? until
92
+ : until?.toISOString()
93
+ : ''
94
+ }
95
+ resettable={false}
96
+ onChange={(id, value) => {
97
+ onChange(id, value === '' ? undefined : value);
98
+ }}
99
+ />
100
+ </Form.Field>
101
+ </Form.Group>
102
+ </Grid.Column>
103
+ </Grid.Row>
104
+ </Grid>
105
+ </Form.Field>
106
+ );
107
+ };
108
+
109
+ /**
110
+ * Property types.
111
+ * @property {Object} propTypes Property types.
112
+ * @static
113
+ */
114
+ EndField.propTypes = {
115
+ value: PropTypes.string,
116
+ count: PropTypes.any,
117
+ until: PropTypes.any,
118
+ onChange: PropTypes.func,
119
+ };
120
+
121
+ /**
122
+ * Default properties.
123
+ * @property {Object} defaultProps Default properties.
124
+ * @static
125
+ */
126
+ EndField.defaultProps = {
127
+ value: null,
128
+ count: null,
129
+ until: null,
130
+ onChange: null,
131
+ };
132
+
133
+ export default injectIntl(EndField);
@@ -4,6 +4,7 @@
4
4
  * CUSTOMIZATIONS:
5
5
  * - add date field open calendar on top
6
6
  * - fix all imports and rrulei18n use
7
+ * - added customization to have this changes https://github.com/plone/volto/pull/5555/files
7
8
  */
8
9
 
9
10
  import React, { Component } from 'react';
@@ -216,26 +217,41 @@ class RecurrenceWidget extends Component {
216
217
 
217
218
  componentDidUpdate(prevProps) {
218
219
  if (this.props.value) {
219
- if (prevProps.formData?.start !== this.props.formData?.start) {
220
- let start = this.getUTCDate(this.props.formData?.start)
221
- .startOf('day')
222
- .toDate();
223
-
224
- this.setState((prevState) => {
225
- let rruleSet = prevState.rruleSet;
226
-
227
- rruleSet = this.updateRruleSet(
228
- rruleSet,
229
- prevState.formValues,
230
- 'dtstart',
231
- start,
232
- );
233
-
234
- return {
235
- ...prevState,
236
- rruleSet,
237
- };
238
- });
220
+ const changedStart =
221
+ prevProps.formData?.start !== this.props.formData?.start;
222
+ const changedEnd = prevProps.formData?.end !== this.props.formData?.end;
223
+
224
+ if (changedStart || changedEnd) {
225
+ let start = this.getUTCDate(this.props.formData?.start).toDate();
226
+ // let end = this.getUTCDate(this.props.formData?.end).toDate();
227
+
228
+ let changeFormValues = {};
229
+ if (changedEnd) {
230
+ changeFormValues.until = this.getUTCDate(
231
+ this.props.formData?.end,
232
+ ).toDate();
233
+ }
234
+ this.setState(
235
+ (prevState) => {
236
+ let rruleSet = prevState.rruleSet;
237
+
238
+ rruleSet = this.updateRruleSet(
239
+ rruleSet,
240
+ { ...prevState.formValues, ...changeFormValues },
241
+ 'dtstart',
242
+ start,
243
+ );
244
+
245
+ return {
246
+ ...prevState,
247
+ rruleSet,
248
+ };
249
+ },
250
+ () => {
251
+ //then, after set state, set recurrence rrule value
252
+ this.saveRrule();
253
+ },
254
+ );
239
255
  }
240
256
  }
241
257
  }
@@ -247,7 +263,7 @@ class RecurrenceWidget extends Component {
247
263
  setRecurrenceStartEnd = () => {
248
264
  const start = this.props.formData?.start;
249
265
 
250
- let _start = this.getUTCDate(start).startOf('day').toDate();
266
+ const _start = new Date(start); //The date is already in utc from plone, so this is not necessary: this.getUTCDate(start).startOf('day').toDate();
251
267
 
252
268
  this.setState((prevState) => {
253
269
  let rruleSet = prevState.rruleSet;
@@ -336,7 +352,7 @@ class RecurrenceWidget extends Component {
336
352
  case 'until':
337
353
  if (value != null) {
338
354
  formValues['recurrenceEnds'] = option;
339
- formValues[option] = toISOString(value);
355
+ formValues[option] = value;
340
356
  }
341
357
  break;
342
358
  case 'byweekday':
@@ -419,7 +435,24 @@ class RecurrenceWidget extends Component {
419
435
  }
420
436
  break;
421
437
  case 'until':
422
- value = value ? moment(new Date(value)).utc().toDate() : null;
438
+ let mDate = null;
439
+ if (value) {
440
+ mDate = this.moment(new Date(value));
441
+ if (typeof value === 'string') {
442
+ mDate = this.moment(new Date(value));
443
+ } else {
444
+ //object-->Date()
445
+ mDate = this.moment(value);
446
+ }
447
+
448
+ if (this.props.formData.end) {
449
+ //set time from formData.end
450
+ const mEnd = this.moment(new Date(this.props.formData.end));
451
+ mDate.set('hour', mEnd.get('hour'));
452
+ mDate.set('minute', mEnd.get('minute'));
453
+ }
454
+ }
455
+ value = value ? mDate.toDate() : null;
423
456
  break;
424
457
  default:
425
458
  break;
@@ -443,8 +476,8 @@ class RecurrenceWidget extends Component {
443
476
  field === 'dtstart'
444
477
  ? value
445
478
  : rruleSet.dtstart()
446
- ? rruleSet.dtstart()
447
- : moment().utc().toDate();
479
+ ? rruleSet.dtstart()
480
+ : new Date();
448
481
  var exdates =
449
482
  field === 'exdates' ? value : Object.assign([], rruleSet.exdates());
450
483
 
@@ -465,12 +498,12 @@ class RecurrenceWidget extends Component {
465
498
 
466
499
  getDefaultUntil = (freq) => {
467
500
  var end = this.props.formData?.end
468
- ? toISOString(this.getUTCDate(this.props.formData.end).toDate())
501
+ ? moment(new Date(this.props.formData.end))
469
502
  : null;
470
- var tomorrow = toISOString(moment().add(1, 'days').utc().toDate());
471
- var nextWeek = toISOString(moment().add(7, 'days').utc().toDate());
472
- var nextMonth = toISOString(moment().add(1, 'months').utc().toDate());
473
- var nextYear = toISOString(moment().add(1, 'years').utc().toDate());
503
+ var tomorrow = moment().add(1, 'days');
504
+ var nextWeek = moment().add(7, 'days');
505
+ var nextMonth = moment().add(1, 'months');
506
+ var nextYear = moment().add(1, 'years');
474
507
 
475
508
  var until = end;
476
509
  switch (freq) {
@@ -495,6 +528,19 @@ class RecurrenceWidget extends Component {
495
528
  default:
496
529
  break;
497
530
  }
531
+ if (this.props.formData.end) {
532
+ //set default end time
533
+ until.set('hour', end.get('hour'));
534
+ until.set('minute', end.get('minute'));
535
+ }
536
+
537
+ until = new Date(
538
+ until.get('year'),
539
+ until.get('month'),
540
+ until.get('date'),
541
+ until.get('hour'),
542
+ until.get('minute'),
543
+ );
498
544
 
499
545
  return until;
500
546
  };
@@ -708,9 +754,13 @@ class RecurrenceWidget extends Component {
708
754
  }
709
755
  };
710
756
 
711
- save = () => {
757
+ saveRrule = () => {
712
758
  var value = this.state.rruleSet.toString();
713
759
  this.props.onChange(this.props.id, value);
760
+ };
761
+
762
+ save = () => {
763
+ this.saveRrule();
714
764
  this.close();
715
765
  };
716
766
 
@@ -9,9 +9,16 @@
9
9
  */
10
10
 
11
11
  import { map } from 'lodash';
12
- import { flattenToAppURL } from '@plone/volto/helpers';
12
+ import {
13
+ flattenToAppURL,
14
+ getBaseUrl,
15
+ hasApiExpander,
16
+ } from '@plone/volto/helpers';
13
17
 
14
- import { GET_NAVIGATION } from '@plone/volto/constants/ActionTypes';
18
+ import {
19
+ GET_CONTENT,
20
+ GET_NAVIGATION,
21
+ } from '@plone/volto/constants/ActionTypes';
15
22
 
16
23
  const initialState = {
17
24
  error: null,
@@ -30,6 +37,7 @@ const initialState = {
30
37
  function getRecursiveItems(items) {
31
38
  return map(items, (item) => ({
32
39
  title: item.title,
40
+ description: item.description,
33
41
  url: item.remoteUrl ?? flattenToAppURL(item['@id']),
34
42
  ...(item.items && { items: getRecursiveItems(item.items) }),
35
43
  }));
@@ -43,6 +51,7 @@ function getRecursiveItems(items) {
43
51
  * @returns {Object} New state.
44
52
  */
45
53
  export default function navigation(state = initialState, action = {}) {
54
+ let hasExpander;
46
55
  switch (action.type) {
47
56
  case `${GET_NAVIGATION}_PENDING`:
48
57
  return {
@@ -51,14 +60,38 @@ export default function navigation(state = initialState, action = {}) {
51
60
  loaded: false,
52
61
  loading: true,
53
62
  };
63
+ case `${GET_CONTENT}_SUCCESS`:
64
+ hasExpander = hasApiExpander(
65
+ 'navigation',
66
+ getBaseUrl(flattenToAppURL(action.result['@id'])),
67
+ );
68
+ if (hasExpander && !action.subrequest) {
69
+ return {
70
+ ...state,
71
+ error: null,
72
+ items: getRecursiveItems(
73
+ action.result['@components'].navigation.items,
74
+ ),
75
+ loaded: true,
76
+ loading: false,
77
+ };
78
+ }
79
+ return state;
54
80
  case `${GET_NAVIGATION}_SUCCESS`:
55
- return {
56
- ...state,
57
- error: null,
58
- items: getRecursiveItems(action.result.items),
59
- loaded: true,
60
- loading: false,
61
- };
81
+ // Even if the expander is set or not, if the GET_NAVIGATION is
82
+ // called, we want it to store the data if the actions data is
83
+ // not set in the expander data (['@components']) but in the "normal"
84
+ // action result (we look for the object property returned by the endpoint)
85
+ if (!action.result?.['@components'] && action.result?.items) {
86
+ return {
87
+ ...state,
88
+ error: null,
89
+ items: getRecursiveItems(action.result.items),
90
+ loaded: true,
91
+ loading: false,
92
+ };
93
+ }
94
+ return state;
62
95
  case `${GET_NAVIGATION}_FAIL`:
63
96
  return {
64
97
  ...state,
@@ -1,366 +0,0 @@
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 =
132
- (api) =>
133
- ({ dispatch, getState }) =>
134
- (next) =>
135
- (action) => {
136
- const { settings } = config;
137
-
138
- const token = getState().userSession.token;
139
- let isAnonymous = true;
140
- if (token) {
141
- const tokenExpiration = jwtDecode(token).exp;
142
- const currentTime = new Date().getTime() / 1000;
143
- isAnonymous = !token || currentTime > tokenExpiration;
144
- }
145
-
146
- if (typeof action === 'function') {
147
- return action(dispatch, getState);
148
- }
149
-
150
- const { request, type, mode = 'parallel', ...rest } = action;
151
- const { subrequest } = action; // We want subrequest remains in `...rest` above
152
-
153
- let actionPromise;
154
-
155
- if (!request) {
156
- return next(action);
157
- }
158
-
159
- next({ ...rest, type: `${type}_PENDING` });
160
-
161
- if (socket) {
162
- actionPromise = Array.isArray(request)
163
- ? Promise.all(
164
- request.map((item) =>
165
- sendOnSocket({
166
- ...item,
167
- path: addExpandersToPath(item.path, type, isAnonymous),
168
- id: type,
169
- }),
170
- ),
171
- )
172
- : sendOnSocket({
173
- ...request,
174
- path: addExpandersToPath(request.path, type, isAnonymous),
175
- id: type,
176
- });
177
- } else {
178
- actionPromise = Array.isArray(request)
179
- ? mode === 'serial'
180
- ? request.reduce((prevPromise, item) => {
181
- return prevPromise.then((acc) => {
182
- return api[item.op](
183
- addExpandersToPath(item.path, type, isAnonymous),
184
- {
185
- data: item.data,
186
- type: item.type,
187
- headers: item.headers,
188
- params: request.params,
189
- checkUrl: settings.actions_raising_api_errors.includes(
190
- action.type,
191
- ),
192
- },
193
- ).then((reqres) => {
194
- return [...acc, reqres];
195
- });
196
- });
197
- }, Promise.resolve([]))
198
- : Promise.all(
199
- request.map((item) =>
200
- api[item.op](addExpandersToPath(item.path, type, isAnonymous), {
201
- data: item.data,
202
- type: item.type,
203
- headers: item.headers,
204
- params: request.params,
205
- checkUrl: settings.actions_raising_api_errors.includes(
206
- action.type,
207
- ),
208
- }),
209
- ),
210
- )
211
- : api[request.op](addExpandersToPath(request.path, type, isAnonymous), {
212
- data: request.data,
213
- type: request.type,
214
- headers: request.headers,
215
- params: request.params,
216
- checkUrl: settings.actions_raising_api_errors.includes(action.type),
217
- });
218
- actionPromise.then(
219
- (result) => {
220
- const { settings } = config;
221
- if (getState().apierror.connectionRefused) {
222
- next({
223
- ...rest,
224
- type: RESET_APIERROR,
225
- });
226
- }
227
- if (type === GET_CONTENT) {
228
- const lang = result?.language?.token;
229
- if (
230
- lang &&
231
- getState().intl.locale !== toReactIntlLang(lang) &&
232
- !subrequest &&
233
- config.settings.supportedLanguages.includes(lang)
234
- ) {
235
- const langFileName = toGettextLang(lang);
236
- import('~/../locales/' + langFileName + '.json').then(
237
- (locale) => {
238
- dispatch(changeLanguage(lang, locale.default));
239
- },
240
- );
241
- }
242
- }
243
- if (type === LOGIN && settings.websockets) {
244
- const cookies = new Cookies();
245
- cookies.set(
246
- 'auth_token',
247
- result.token,
248
- getCookieOptions({
249
- expires: new Date(jwtDecode(result.token).exp * 1000),
250
- }),
251
- );
252
- api.get('/@wstoken').then((res) => {
253
- socket = new WebSocket(
254
- `${settings.apiPath.replace('http', 'ws')}/@ws?ws_token=${
255
- res.token
256
- }`,
257
- );
258
- socket.onmessage = (message) => {
259
- const packet = JSON.parse(message.data);
260
- if (packet.error) {
261
- dispatch({
262
- type: `${packet.id}_FAIL`,
263
- error: packet.error,
264
- });
265
- } else {
266
- dispatch({
267
- type: `${packet.id}_SUCCESS`,
268
- result: JSON.parse(packet.data),
269
- });
270
- }
271
- };
272
- });
273
- }
274
- try {
275
- return next({ ...rest, result, type: `${type}_SUCCESS` });
276
- } catch (error) {
277
- // There was an exception while processing reducers or downstream middleware.
278
- next({
279
- ...rest,
280
- error: { status: 500, error },
281
- type: `${type}_FAIL`,
282
- });
283
- // Rethrow the original exception on the client side only,
284
- // so it doesn't fall through to express on the server.
285
- if (__CLIENT__) throw error;
286
- }
287
- },
288
- (error) => {
289
- // Only SSR can set ECONNREFUSED
290
- if (error.code === 'ECONNREFUSED') {
291
- next({
292
- ...rest,
293
- error,
294
- statusCode: error.code,
295
- connectionRefused: true,
296
- type: SET_APIERROR,
297
- });
298
- }
299
-
300
- // Response error is marked crossDomain if CORS error happen
301
- else if (error.crossDomain) {
302
- next({
303
- ...rest,
304
- error,
305
- statusCode: 'CORSERROR',
306
- connectionRefused: false,
307
- type: SET_APIERROR,
308
- });
309
- }
310
-
311
- // Check for actions who can raise api errors
312
- if (settings.actions_raising_api_errors.includes(action.type)) {
313
- // Gateway timeout
314
- if (error?.response?.statusCode === 504) {
315
- next({
316
- ...rest,
317
- error,
318
- statusCode: error.code,
319
- connectionRefused: true,
320
- type: SET_APIERROR,
321
- });
322
- }
323
-
324
- // Redirect
325
- else if (error?.code === 301) {
326
- next({
327
- ...rest,
328
- error,
329
- statusCode: error.code,
330
- connectionRefused: false,
331
- type: SET_APIERROR,
332
- });
333
- }
334
-
335
- // Redirect
336
- else if (error?.code === 408) {
337
- next({
338
- ...rest,
339
- error,
340
- statusCode: error.code,
341
- connectionRefused: false,
342
- type: SET_APIERROR,
343
- });
344
- }
345
-
346
- // Unauthorized
347
- else if (error?.response?.statusCode === 401) {
348
- next({
349
- ...rest,
350
- error,
351
- statusCode: error.response,
352
- message: error.response.body.message,
353
- connectionRefused: false,
354
- type: SET_APIERROR,
355
- });
356
- }
357
- }
358
- return next({ ...rest, error, type: `${type}_FAIL` });
359
- },
360
- );
361
- }
362
-
363
- return actionPromise;
364
- };
365
-
366
- export default apiMiddlewareFactory;