@plone/volto 17.20.1 → 17.20.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.
Files changed (44) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/locales/ca/LC_MESSAGES/volto.po +47 -11
  3. package/locales/ca.json +1 -1
  4. package/locales/de/LC_MESSAGES/volto.po +47 -11
  5. package/locales/de.json +1 -1
  6. package/locales/en/LC_MESSAGES/volto.po +47 -11
  7. package/locales/en.json +1 -1
  8. package/locales/es/LC_MESSAGES/volto.po +47 -11
  9. package/locales/es.json +1 -1
  10. package/locales/eu/LC_MESSAGES/volto.po +47 -11
  11. package/locales/eu.json +1 -1
  12. package/locales/fi/LC_MESSAGES/volto.po +47 -11
  13. package/locales/fi.json +1 -1
  14. package/locales/fr/LC_MESSAGES/volto.po +47 -11
  15. package/locales/fr.json +1 -1
  16. package/locales/it/LC_MESSAGES/volto.po +77 -41
  17. package/locales/it.json +1 -1
  18. package/locales/ja/LC_MESSAGES/volto.po +47 -11
  19. package/locales/ja.json +1 -1
  20. package/locales/nl/LC_MESSAGES/volto.po +47 -11
  21. package/locales/nl.json +1 -1
  22. package/locales/pt/LC_MESSAGES/volto.po +47 -11
  23. package/locales/pt.json +1 -1
  24. package/locales/pt_BR/LC_MESSAGES/volto.po +47 -11
  25. package/locales/pt_BR.json +1 -1
  26. package/locales/ro/LC_MESSAGES/volto.po +47 -11
  27. package/locales/ro.json +1 -1
  28. package/locales/volto.pot +48 -12
  29. package/locales/zh_CN/LC_MESSAGES/volto.po +47 -11
  30. package/locales/zh_CN.json +1 -1
  31. package/package.json +1 -1
  32. package/packages/volto-slate/package.json +1 -1
  33. package/src/components/manage/Contents/Contents.jsx +5 -352
  34. package/src/components/manage/Contents/ContentsDeleteModal.jsx +379 -0
  35. package/src/components/manage/Rules/Rules.jsx +7 -1
  36. package/src/components/theme/EventDetails/EventDetails.jsx +2 -2
  37. package/src/express-middleware/files.js +4 -1
  38. package/src/reducers/index.js +2 -0
  39. package/src/reducers/linkIntegrity/linkIntegrity.js +51 -0
  40. package/src/reducers/linkIntegrity/linkIntegrity.test.js +54 -0
  41. package/types/components/manage/Contents/ContentsDeleteModal.d.ts +13 -0
  42. package/types/reducers/index.d.ts +2 -0
  43. package/types/reducers/linkIntegrity/linkIntegrity.d.ts +8 -0
  44. package/types/reducers/linkIntegrity/linkIntegrity.test.d.ts +1 -0
@@ -0,0 +1,379 @@
1
+ import React, { useState, useEffect } from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import { useDispatch, useSelector } from 'react-redux';
4
+ import { Link } from 'react-router-dom';
5
+ import { map, find } from 'lodash';
6
+ import { defineMessages, useIntl, FormattedMessage } from 'react-intl';
7
+ import { linkIntegrityCheck } from '@plone/volto/actions';
8
+ import { flattenToAppURL } from '@plone/volto/helpers';
9
+
10
+ import { Confirm, Dimmer, Loader, Table } from 'semantic-ui-react';
11
+
12
+ const messages = defineMessages({
13
+ deleteConfirmSingleItem: {
14
+ id: 'Delete this item?',
15
+ defaultMessage: 'Delete this item?',
16
+ },
17
+ deleteConfirmMultipleItems: {
18
+ id: 'Delete selected items?',
19
+ defaultMessage: 'Delete selected items?',
20
+ },
21
+ navigate_to_this_item: {
22
+ id: 'Navigate to this item',
23
+ defaultMessage: 'Navigate to this item',
24
+ },
25
+ loading: {
26
+ id: 'link-integrity: loading references',
27
+ defaultMessage: 'Checking references...',
28
+ },
29
+ delete: {
30
+ id: 'link-integrity: Delete',
31
+ defaultMessage: 'Delete',
32
+ },
33
+ delete_and_broken_links: {
34
+ id: 'link-integrity: Delete item and break links',
35
+ defaultMessage: 'Delete item and break links',
36
+ },
37
+ cancel: {
38
+ id: 'Cancel',
39
+ defaultMessage: 'Cancel',
40
+ },
41
+ });
42
+
43
+ const ContentsDeleteModal = (props) => {
44
+ const { itemsToDelete = [], open, onCancel, onOk, items } = props;
45
+ const intl = useIntl();
46
+ const dispatch = useDispatch();
47
+ const linkintegrityInfo = useSelector((state) => state.linkIntegrity?.result);
48
+ const loading = useSelector((state) => state.linkIntegrity?.loading);
49
+
50
+ const [brokenReferences, setBrokenReferences] = useState(0);
51
+ const [containedItemsToDelete, setContainedItemsToDelete] = useState([]);
52
+ const [breaches, setBreaches] = useState([]);
53
+
54
+ const [linksAndReferencesViewLink, setLinkAndReferencesViewLink] =
55
+ useState(null);
56
+
57
+ useEffect(() => {
58
+ const getFieldById = (id, field) => {
59
+ const item = find(items, { '@id': id });
60
+ return item ? item[field] : '';
61
+ };
62
+
63
+ if (itemsToDelete.length > 0 && open) {
64
+ dispatch(
65
+ linkIntegrityCheck(
66
+ map(itemsToDelete, (item) => getFieldById(item, 'UID')),
67
+ ),
68
+ );
69
+ }
70
+ }, [itemsToDelete, items, open, dispatch]);
71
+
72
+ useEffect(() => {
73
+ if (linkintegrityInfo) {
74
+ const containedItems = linkintegrityInfo
75
+ .map((result) => result.items_total ?? 0)
76
+ .reduce((acc, value) => acc + value, 0);
77
+ const breaches = linkintegrityInfo.flatMap((result) =>
78
+ result.breaches.map((source) => ({
79
+ source: source,
80
+ target: result,
81
+ })),
82
+ );
83
+ const source_by_uid = breaches.reduce(
84
+ (acc, value) => acc.set(value.source.uid, value.source),
85
+ new Map(),
86
+ );
87
+ const by_source = breaches.reduce((acc, value) => {
88
+ if (acc.get(value.source.uid) === undefined) {
89
+ acc.set(value.source.uid, new Set());
90
+ }
91
+ acc.get(value.source.uid).add(value.target);
92
+ return acc;
93
+ }, new Map());
94
+
95
+ setContainedItemsToDelete(containedItems);
96
+ setBrokenReferences(by_source.size);
97
+ setLinkAndReferencesViewLink(
98
+ linkintegrityInfo.length
99
+ ? linkintegrityInfo[0]['@id'] + '/links-to-item'
100
+ : null,
101
+ );
102
+ setBreaches(
103
+ Array.from(by_source, (entry) => ({
104
+ source: source_by_uid.get(entry[0]),
105
+ targets: Array.from(entry[1]),
106
+ })),
107
+ );
108
+ } else {
109
+ setContainedItemsToDelete([]);
110
+ setBrokenReferences(0);
111
+ setLinkAndReferencesViewLink(null);
112
+ setBreaches([]);
113
+ }
114
+ }, [linkintegrityInfo]);
115
+
116
+ return (
117
+ open && (
118
+ <Confirm
119
+ open={open}
120
+ confirmButton={
121
+ brokenReferences === 0
122
+ ? intl.formatMessage(messages.delete)
123
+ : intl.formatMessage(messages.delete_and_broken_links)
124
+ }
125
+ cancelButton={intl.formatMessage(messages.cancel)}
126
+ header={
127
+ itemsToDelete.length === 1
128
+ ? intl.formatMessage(messages.deleteConfirmSingleItem)
129
+ : intl.formatMessage(messages.deleteConfirmMultipleItems)
130
+ }
131
+ content={
132
+ <div className="content">
133
+ <Dimmer active={loading} inverted>
134
+ <Loader indeterminate size="massive">
135
+ {intl.formatMessage(messages.loading)}
136
+ </Loader>
137
+ </Dimmer>
138
+
139
+ {itemsToDelete.length > 1 ? (
140
+ containedItemsToDelete > 0 ? (
141
+ <>
142
+ <FormattedMessage
143
+ id="Some items are also a folder. By deleting them you will delete {containedItemsToDelete} {variation} inside the folders."
144
+ defaultMessage="Some items are also a folder. By deleting them you will delete {containedItemsToDelete} {variation} inside the folders."
145
+ values={{
146
+ containedItemsToDelete: (
147
+ <span>{containedItemsToDelete}</span>
148
+ ),
149
+ variation: (
150
+ <span>
151
+ {containedItemsToDelete === 1 ? (
152
+ <FormattedMessage id="item" defaultMessage="item" />
153
+ ) : (
154
+ <FormattedMessage
155
+ id="items"
156
+ defaultMessage="items"
157
+ />
158
+ )}
159
+ </span>
160
+ ),
161
+ }}
162
+ />
163
+ {brokenReferences > 0 && (
164
+ <>
165
+ <br />
166
+ <FormattedMessage
167
+ id="Some items are referenced by other contents. By deleting them {brokenReferences} {variation} will be broken."
168
+ defaultMessage="Some items are referenced by other contents. By deleting them {brokenReferences} {variation} will be broken."
169
+ values={{
170
+ brokenReferences: <span>{brokenReferences}</span>,
171
+ variation: (
172
+ <span>
173
+ {brokenReferences === 1 ? (
174
+ <FormattedMessage
175
+ id="reference"
176
+ defaultMessage="reference"
177
+ />
178
+ ) : (
179
+ <FormattedMessage
180
+ id="references"
181
+ defaultMessage="references"
182
+ />
183
+ )}
184
+ </span>
185
+ ),
186
+ }}
187
+ />
188
+ </>
189
+ )}
190
+ </>
191
+ ) : (
192
+ <>
193
+ {brokenReferences > 0 && (
194
+ <>
195
+ <FormattedMessage
196
+ id="Some items are referenced by other contents. By deleting them {brokenReferences} {variation} will be broken."
197
+ defaultMessage="Some items are referenced by other contents. By deleting them {brokenReferences} {variation} will be broken."
198
+ values={{
199
+ brokenReferences: <span>{brokenReferences}</span>,
200
+ variation: (
201
+ <span>
202
+ {brokenReferences === 1 ? (
203
+ <FormattedMessage
204
+ id="reference"
205
+ defaultMessage="reference"
206
+ />
207
+ ) : (
208
+ <FormattedMessage
209
+ id="references"
210
+ defaultMessage="references"
211
+ />
212
+ )}
213
+ </span>
214
+ ),
215
+ }}
216
+ />
217
+ </>
218
+ )}
219
+ </>
220
+ )
221
+ ) : containedItemsToDelete > 0 ? (
222
+ <>
223
+ <FormattedMessage
224
+ id="This item is also a folder. By deleting it you will delete {containedItemsToDelete} {variation} inside the folder."
225
+ defaultMessage="This item is also a folder. By deleting it you will delete {containedItemsToDelete} {variation} inside the folder."
226
+ values={{
227
+ containedItemsToDelete: (
228
+ <span>{containedItemsToDelete}</span>
229
+ ),
230
+ variation: (
231
+ <span>
232
+ {containedItemsToDelete === 1 ? (
233
+ <FormattedMessage id="item" defaultMessage="item" />
234
+ ) : (
235
+ <FormattedMessage id="items" defaultMessage="items" />
236
+ )}
237
+ </span>
238
+ ),
239
+ }}
240
+ />
241
+ {brokenReferences > 0 && (
242
+ <>
243
+ <br />
244
+ <FormattedMessage
245
+ id="Deleting this item breaks {brokenReferences} {variation}."
246
+ defaultMessage="Deleting this item breaks {brokenReferences} {variation}."
247
+ values={{
248
+ brokenReferences: <span>{brokenReferences}</span>,
249
+ variation: (
250
+ <span>
251
+ {brokenReferences === 1 ? (
252
+ <FormattedMessage
253
+ id="reference"
254
+ defaultMessage="reference"
255
+ />
256
+ ) : (
257
+ <FormattedMessage
258
+ id="references"
259
+ defaultMessage="references"
260
+ />
261
+ )}
262
+ </span>
263
+ ),
264
+ }}
265
+ />
266
+ <BrokenLinksList
267
+ intl={intl}
268
+ breaches={breaches}
269
+ linksAndReferencesViewLink={linksAndReferencesViewLink}
270
+ />
271
+ </>
272
+ )}
273
+ </>
274
+ ) : brokenReferences > 0 ? (
275
+ <>
276
+ <FormattedMessage
277
+ id="Deleting this item breaks {brokenReferences} {variation}."
278
+ defaultMessage="Deleting this item breaks {brokenReferences} {variation}."
279
+ values={{
280
+ brokenReferences: <span>{brokenReferences}</span>,
281
+ variation: (
282
+ <span>
283
+ {brokenReferences === 1 ? (
284
+ <FormattedMessage
285
+ id="reference"
286
+ defaultMessage="reference"
287
+ />
288
+ ) : (
289
+ <FormattedMessage
290
+ id="references"
291
+ defaultMessage="references"
292
+ />
293
+ )}
294
+ </span>
295
+ ),
296
+ }}
297
+ />
298
+ <BrokenLinksList
299
+ intl={intl}
300
+ breaches={breaches}
301
+ linksAndReferencesViewLink={linksAndReferencesViewLink}
302
+ />
303
+ </>
304
+ ) : null}
305
+ </div>
306
+ }
307
+ onCancel={onCancel}
308
+ onConfirm={onOk}
309
+ size="medium"
310
+ />
311
+ )
312
+ );
313
+ };
314
+
315
+ const BrokenLinksList = ({ intl, breaches, linksAndReferencesViewLink }) => {
316
+ return (
317
+ <div className="broken-links-list">
318
+ <FormattedMessage
319
+ id="These items will have broken links"
320
+ defaultMessage="These items will have broken links"
321
+ />
322
+ :
323
+ <Table compact>
324
+ <Table.Body>
325
+ {breaches.map((breach) => (
326
+ <Table.Row key={breach.source['@id']} verticalAlign="top">
327
+ <Table.Cell>
328
+ <Link
329
+ to={flattenToAppURL(breach.source['@id'])}
330
+ title={intl.formatMessage(messages.navigate_to_this_item)}
331
+ >
332
+ {breach.source.title}
333
+ </Link>
334
+ </Table.Cell>
335
+ <Table.Cell style={{ minWidth: '140px' }}>
336
+ <FormattedMessage id="refers to" defaultMessage="refers to" />:
337
+ </Table.Cell>
338
+ <Table.Cell>
339
+ <ul style={{ margin: 0 }}>
340
+ {breach.targets.map((target) => (
341
+ <li key={target['@id']}>
342
+ <Link
343
+ to={flattenToAppURL(target['@id'])}
344
+ title={intl.formatMessage(
345
+ messages.navigate_to_this_item,
346
+ )}
347
+ >
348
+ {target.title}
349
+ </Link>
350
+ </li>
351
+ ))}
352
+ </ul>
353
+ </Table.Cell>
354
+ </Table.Row>
355
+ ))}
356
+ </Table.Body>
357
+ </Table>
358
+ {linksAndReferencesViewLink && (
359
+ <Link to={flattenToAppURL(linksAndReferencesViewLink)}>
360
+ <FormattedMessage
361
+ id="View links and references to this item"
362
+ defaultMessage="View links and references to this item"
363
+ />
364
+ </Link>
365
+ )}
366
+ </div>
367
+ );
368
+ };
369
+ ContentsDeleteModal.propTypes = {
370
+ itemsToDelete: PropTypes.arrayOf(
371
+ PropTypes.shape({
372
+ UID: PropTypes.string,
373
+ }),
374
+ ).isRequired,
375
+ open: PropTypes.bool.isRequired,
376
+ onOk: PropTypes.func.isRequired,
377
+ onCancel: PropTypes.func.isRequired,
378
+ };
379
+ export default ContentsDeleteModal;
@@ -75,6 +75,10 @@ const messages = defineMessages({
75
75
  id: 'Unassigned',
76
76
  defaultMessage: 'Unassigned',
77
77
  },
78
+ select_rule: {
79
+ id: 'Select rule',
80
+ defaultMessage: 'Select rule',
81
+ },
78
82
  });
79
83
 
80
84
  /**
@@ -365,7 +369,9 @@ class Rules extends Component {
365
369
  />
366
370
  <div style={{ display: 'flex' }}>
367
371
  <Select
368
- placeholder="Select rule"
372
+ placeholder={this.props.intl.formatMessage(
373
+ messages.select_rule,
374
+ )}
369
375
  value={this.state.newRule}
370
376
  onChange={(e, { value }) => this.setState({ newRule: value })}
371
377
  options={assignable_rules.map((rule, i) => {
@@ -6,7 +6,7 @@ import {
6
6
  Recurrence,
7
7
  } from '@plone/volto/components/theme/View/EventDatesInfo';
8
8
  import { Icon } from '@plone/volto/components';
9
- import { expandToBackendURL } from '@plone/volto/helpers';
9
+ import { flattenToAppURL } from '@plone/volto/helpers';
10
10
 
11
11
  import calendarSVG from '@plone/volto/icons/calendar.svg';
12
12
 
@@ -147,7 +147,7 @@ const EventDetails = ({ content, display_as = 'aside' }) => {
147
147
  className="ics-download"
148
148
  target="_blank"
149
149
  rel="noreferrer"
150
- href={`${expandToBackendURL(content['@id'])}/ics_view`}
150
+ href={`${flattenToAppURL(content['@id'])}/ics_view`}
151
151
  >
152
152
  {intl.formatMessage(messages.downloadEvent)}
153
153
  </a>
@@ -29,7 +29,10 @@ function filesMiddlewareFn(req, res, next) {
29
29
  export default function filesMiddleware() {
30
30
  const middleware = express.Router();
31
31
 
32
- middleware.all(['**/@@download/*', '**/@@display-file/*'], filesMiddlewareFn);
32
+ middleware.all(
33
+ ['**/@@download/*', '**/@@display-file/*', '**/ics_view'],
34
+ filesMiddlewareFn,
35
+ );
33
36
  middleware.id = 'filesResourcesProcessor';
34
37
  return middleware;
35
38
  }
@@ -20,6 +20,7 @@ import emailNotification from '@plone/volto/reducers/emailNotification/emailNoti
20
20
  import emailSend from '@plone/volto/reducers/emailSend/emailSend';
21
21
  import form from '@plone/volto/reducers/form/form';
22
22
  import history from '@plone/volto/reducers/history/history';
23
+ import linkIntegrity from '@plone/volto/reducers/linkIntegrity/linkIntegrity';
23
24
  import groups from '@plone/volto/reducers/groups/groups';
24
25
  import messages from '@plone/volto/reducers/messages/messages';
25
26
  import navigation from '@plone/volto/reducers/navigation/navigation';
@@ -79,6 +80,7 @@ const reducers = {
79
80
  form,
80
81
  groups,
81
82
  history,
83
+ linkIntegrity,
82
84
  messages,
83
85
  navigation,
84
86
  querystring,
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Linkintegrity reducer.
3
+ * @module reducers/linkIntegrity/linkIntegrity
4
+ */
5
+
6
+ import { LINK_INTEGRITY_CHECK } from '@plone/volto/constants/ActionTypes';
7
+
8
+ const initialState = {
9
+ error: null,
10
+ loaded: false,
11
+ loading: false,
12
+ result: null,
13
+ };
14
+
15
+ /**
16
+ * History reducer.
17
+ * @function linkIntegrity
18
+ * @param {Object} state Current state.
19
+ * @param {Object} action Action to be handled.
20
+ * @returns {Object} New state.
21
+ */
22
+ export default function linkIntegrity(state = initialState, action = {}) {
23
+ switch (action.type) {
24
+ case `${LINK_INTEGRITY_CHECK}_PENDING`:
25
+ return {
26
+ ...state,
27
+ error: null,
28
+ loaded: false,
29
+ loading: true,
30
+ result: null,
31
+ };
32
+ case `${LINK_INTEGRITY_CHECK}_SUCCESS`:
33
+ return {
34
+ ...state,
35
+ error: null,
36
+ loaded: true,
37
+ loading: false,
38
+ result: action.result,
39
+ };
40
+ case `${LINK_INTEGRITY_CHECK}_FAIL`:
41
+ return {
42
+ ...state,
43
+ error: action.error,
44
+ loaded: true,
45
+ loading: false,
46
+ result: null,
47
+ };
48
+ default:
49
+ return state;
50
+ }
51
+ }
@@ -0,0 +1,54 @@
1
+ import linkIntegrity from './linkIntegrity';
2
+ import { LINK_INTEGRITY_CHECK } from '@plone/volto/constants/ActionTypes';
3
+
4
+ describe('Link integrity reducer', () => {
5
+ it('should return the initial state', () => {
6
+ expect(linkIntegrity()).toEqual({
7
+ error: null,
8
+ loaded: false,
9
+ loading: false,
10
+ result: null,
11
+ });
12
+ });
13
+
14
+ it('should handle LINK_INTEGRITY_CHECK_PENDING', () => {
15
+ expect(
16
+ linkIntegrity(undefined, {
17
+ type: `${LINK_INTEGRITY_CHECK}_PENDING`,
18
+ }),
19
+ ).toEqual({
20
+ error: null,
21
+ loaded: false,
22
+ loading: true,
23
+ result: null,
24
+ });
25
+ });
26
+
27
+ it('should handle LINK_INTEGRITY_CHECK_SUCCESS', () => {
28
+ expect(
29
+ linkIntegrity(undefined, {
30
+ type: `${LINK_INTEGRITY_CHECK}_SUCCESS`,
31
+ result: 'result',
32
+ }),
33
+ ).toEqual({
34
+ error: null,
35
+ loaded: true,
36
+ loading: false,
37
+ result: 'result',
38
+ });
39
+ });
40
+
41
+ it('should handle LINK_INTEGRITY_CHECK_FAIL', () => {
42
+ expect(
43
+ linkIntegrity(undefined, {
44
+ type: `${LINK_INTEGRITY_CHECK}_FAIL`,
45
+ error: 'failed',
46
+ }),
47
+ ).toEqual({
48
+ error: 'failed',
49
+ loaded: true,
50
+ loading: false,
51
+ result: null,
52
+ });
53
+ });
54
+ });
@@ -0,0 +1,13 @@
1
+ export default ContentsDeleteModal;
2
+ declare function ContentsDeleteModal(props: any): JSX.Element;
3
+ declare namespace ContentsDeleteModal {
4
+ namespace propTypes {
5
+ let itemsToDelete: PropTypes.Validator<PropTypes.InferProps<{
6
+ UID: PropTypes.Requireable<string>;
7
+ }>[]>;
8
+ let open: PropTypes.Validator<boolean>;
9
+ let onOk: PropTypes.Validator<(...args: any[]) => any>;
10
+ let onCancel: PropTypes.Validator<(...args: any[]) => any>;
11
+ }
12
+ }
13
+ import PropTypes from 'prop-types';
@@ -18,6 +18,7 @@ declare namespace reducers {
18
18
  export { form };
19
19
  export { groups };
20
20
  export { history };
21
+ export { linkIntegrity };
21
22
  export { messages };
22
23
  export { navigation };
23
24
  export { querystring };
@@ -66,6 +67,7 @@ import emailSend from '@plone/volto/reducers/emailSend/emailSend';
66
67
  import form from '@plone/volto/reducers/form/form';
67
68
  import groups from '@plone/volto/reducers/groups/groups';
68
69
  import history from '@plone/volto/reducers/history/history';
70
+ import linkIntegrity from '@plone/volto/reducers/linkIntegrity/linkIntegrity';
69
71
  import messages from '@plone/volto/reducers/messages/messages';
70
72
  import navigation from '@plone/volto/reducers/navigation/navigation';
71
73
  import querystring from '@plone/volto/reducers/querystring/querystring';
@@ -0,0 +1,8 @@
1
+ /**
2
+ * History reducer.
3
+ * @function linkIntegrity
4
+ * @param {Object} state Current state.
5
+ * @param {Object} action Action to be handled.
6
+ * @returns {Object} New state.
7
+ */
8
+ export default function linkIntegrity(state?: any, action?: any): any;