@plone/volto 17.0.0-alpha.7 → 17.0.0-alpha.9

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 (82) hide show
  1. package/.yarn/install-state.gz +0 -0
  2. package/CHANGELOG.md +82 -0
  3. package/locales/ca/LC_MESSAGES/volto.po +146 -0
  4. package/locales/ca.json +1 -1
  5. package/locales/de/LC_MESSAGES/volto.po +146 -0
  6. package/locales/de.json +1 -1
  7. package/locales/en/LC_MESSAGES/volto.po +146 -0
  8. package/locales/en.json +1 -1
  9. package/locales/es/LC_MESSAGES/volto.po +146 -0
  10. package/locales/es.json +1 -1
  11. package/locales/eu/LC_MESSAGES/volto.po +146 -0
  12. package/locales/eu.json +1 -1
  13. package/locales/fi/LC_MESSAGES/volto.po +4762 -0
  14. package/locales/fi.json +1 -1
  15. package/locales/fr/LC_MESSAGES/volto.po +146 -0
  16. package/locales/fr.json +1 -1
  17. package/locales/it/LC_MESSAGES/volto.po +146 -0
  18. package/locales/it.json +1 -1
  19. package/locales/ja/LC_MESSAGES/volto.po +146 -0
  20. package/locales/ja.json +1 -1
  21. package/locales/nl/LC_MESSAGES/volto.po +801 -643
  22. package/locales/nl.json +1 -1
  23. package/locales/pt/LC_MESSAGES/volto.po +146 -0
  24. package/locales/pt.json +1 -1
  25. package/locales/pt_BR/LC_MESSAGES/volto.po +146 -0
  26. package/locales/pt_BR.json +1 -1
  27. package/locales/ro/LC_MESSAGES/volto.po +146 -0
  28. package/locales/ro.json +1 -1
  29. package/locales/volto.pot +147 -1
  30. package/locales/zh_CN/LC_MESSAGES/volto.po +146 -0
  31. package/locales/zh_CN.json +1 -1
  32. package/package.json +1 -1
  33. package/packages/volto-slate/build/messages/src/blocks/Table/TableBlockEdit.json +1 -1
  34. package/packages/volto-slate/build/messages/src/blocks/Text/DefaultTextBlockEditor.json +1 -1
  35. package/packages/volto-slate/build/messages/src/blocks/Text/DetachedTextBlockEditor.json +1 -1
  36. package/packages/volto-slate/build/messages/src/blocks/Text/SlashMenu.json +1 -1
  37. package/packages/volto-slate/build/messages/src/editor/plugins/AdvancedLink/index.json +1 -1
  38. package/packages/volto-slate/build/messages/src/editor/plugins/Link/index.json +1 -1
  39. package/packages/volto-slate/build/messages/src/editor/plugins/Table/index.json +1 -1
  40. package/packages/volto-slate/build/messages/src/elementEditor/messages.json +1 -1
  41. package/packages/volto-slate/build/messages/src/widgets/HtmlSlateWidget.json +1 -1
  42. package/packages/volto-slate/build/messages/src/widgets/RichTextWidgetView.json +1 -1
  43. package/packages/volto-slate/package.json +1 -1
  44. package/packages/volto-slate/src/blocks/Text/SlashMenu.jsx +4 -3
  45. package/packages/volto-slate/src/editor/deserialize.js +0 -1
  46. package/razzle.config.js +5 -0
  47. package/src/actions/index.js +6 -0
  48. package/src/actions/relations/rebuild.js +25 -0
  49. package/src/actions/relations/relations.js +86 -0
  50. package/src/actions/relations/relations.test.js +15 -0
  51. package/src/components/index.js +1 -0
  52. package/src/components/manage/BlockChooser/BlockChooser.jsx +8 -3
  53. package/src/components/manage/BlockChooser/BlockChooser.test.jsx +5 -0
  54. package/src/components/manage/Contents/Contents.jsx +5 -1
  55. package/src/components/manage/Contents/ContentsItem.jsx +6 -0
  56. package/src/components/manage/Controlpanels/Controlpanels.jsx +9 -0
  57. package/src/components/manage/Controlpanels/Relations/BrokenRelations.jsx +66 -0
  58. package/src/components/manage/Controlpanels/Relations/Relations.jsx +114 -0
  59. package/src/components/manage/Controlpanels/Relations/RelationsListing.jsx +479 -0
  60. package/src/components/manage/Controlpanels/Relations/RelationsMatrix.jsx +531 -0
  61. package/src/components/manage/Controlpanels/Users/UserGroupMembershipControlPanel.jsx +3 -3
  62. package/src/components/manage/Controlpanels/Users/UserGroupMembershipListing.jsx +51 -82
  63. package/src/components/manage/Controlpanels/Users/UserGroupMembershipMatrix.jsx +79 -75
  64. package/src/components/manage/Toast/Toast.jsx +1 -1
  65. package/src/components/theme/NotFound/NotFound.jsx +55 -41
  66. package/src/components/theme/View/RenderBlocks.jsx +7 -1
  67. package/src/components/theme/Widgets/RelationsWidget.jsx +13 -11
  68. package/src/config/ControlPanels.js +2 -0
  69. package/src/constants/ActionTypes.js +4 -0
  70. package/src/constants/Languages.js +8 -4
  71. package/src/helpers/Api/Api.js +1 -1
  72. package/src/helpers/Html/Html.jsx +3 -1
  73. package/src/helpers/Html/Html.test.jsx +5 -0
  74. package/src/helpers/MessageLabels/MessageLabels.js +72 -0
  75. package/src/reducers/actions/actions.js +1 -1
  76. package/src/reducers/breadcrumbs/breadcrumbs.js +1 -1
  77. package/src/reducers/index.js +2 -0
  78. package/src/reducers/navigation/navigation.js +1 -1
  79. package/src/reducers/relations/relations.js +173 -0
  80. package/src/reducers/types/types.js +1 -1
  81. package/src/routes.js +5 -0
  82. package/theme/themes/pastanaga/extras/userscontrolpanel.less +99 -76
@@ -0,0 +1,479 @@
1
+ import React, { useEffect } from 'react';
2
+ import useDeepCompareEffect from 'use-deep-compare-effect';
3
+ import { FormattedMessage, useIntl } from 'react-intl';
4
+ import { useSelector, useDispatch } from 'react-redux';
5
+ import { toast } from 'react-toastify';
6
+ import { uniqBy } from 'lodash';
7
+ import { Checkbox, Message } from 'semantic-ui-react';
8
+ import { messages } from '@plone/volto/helpers';
9
+ import { Toast, UniversalLink } from '@plone/volto/components';
10
+ import {
11
+ createRelations,
12
+ deleteRelations,
13
+ queryRelations,
14
+ searchContent,
15
+ } from '@plone/volto/actions';
16
+
17
+ const ListingTemplate = ({
18
+ relationtype,
19
+ query_source,
20
+ query_target,
21
+ potential_sources_path,
22
+ potential_targets_path,
23
+ // target_filter,
24
+ }) => {
25
+ const intl = useIntl();
26
+ const dispatch = useDispatch();
27
+
28
+ const MAX = 40; // Maximum of rows and columns
29
+ const MAX_RELATIONS = 1000;
30
+
31
+ const stats = useSelector((state) => state.relations?.stats || null);
32
+ let relations = useSelector(
33
+ (state) => state.relations?.relations?.[relationtype]?.items || [],
34
+ );
35
+
36
+ let potential_targets_objects = useSelector(
37
+ (state) => state.search.subrequests.potential_targets?.items || [],
38
+ );
39
+
40
+ let potential_sources_objects = useSelector(
41
+ (state) => state.search.subrequests.potential_sources?.items || [],
42
+ );
43
+
44
+ const staticCatalogVocabularyQuery = useSelector(
45
+ (state) =>
46
+ state.relations?.relations?.[relationtype]
47
+ ?.staticCatalogVocabularyQuery || {},
48
+ );
49
+
50
+ // Editable if plone.api.relations available
51
+ const editable = useSelector(
52
+ (state) => state.relations?.relations?.[relationtype]?.readonly !== true,
53
+ );
54
+
55
+ let relationMatrix = {};
56
+ relations.map((tpl) => {
57
+ if (relationMatrix[tpl.source.UID]) {
58
+ relationMatrix[tpl.source.UID].targets.push(tpl.target);
59
+ } else {
60
+ relationMatrix[tpl.source.UID] = {
61
+ source: tpl.source,
62
+ targets: [tpl.target],
63
+ };
64
+ }
65
+ return relationMatrix;
66
+ });
67
+
68
+ // x-axis: relation targets
69
+ // ************************
70
+ let matrix_options = relations.map((relation) => ({
71
+ value: relation.target.UID,
72
+ label: relation.target.title,
73
+ url: relation.target['@id'],
74
+ review_state: relation.target.review_state,
75
+ uid: relation.target.UID,
76
+ }));
77
+ matrix_options = uniqBy(matrix_options, function (el) {
78
+ return el.value;
79
+ });
80
+
81
+ // Add potential targets
82
+ const potential_targets = potential_targets_objects.map((obj) => ({
83
+ value: obj.UID,
84
+ label: obj.title,
85
+ url: obj['@id'],
86
+ review_state: obj.review_state,
87
+ uid: obj.UID,
88
+ }));
89
+ // Just show potential targets if no querying
90
+ matrix_options =
91
+ query_source === '' &&
92
+ query_target === '' &&
93
+ potential_sources_path !== '' &&
94
+ potential_targets_path !== ''
95
+ ? potential_targets
96
+ : [...matrix_options, ...potential_targets];
97
+ matrix_options = uniqBy(matrix_options, function (el) {
98
+ return el.value;
99
+ });
100
+ matrix_options.sort(function (a, b) {
101
+ var labelA = a.label.toUpperCase();
102
+ var labelB = b.label.toUpperCase();
103
+ if (labelA < labelB) {
104
+ return -1;
105
+ }
106
+ if (labelA > labelB) {
107
+ return 1;
108
+ }
109
+ return 0;
110
+ });
111
+
112
+ // y-axis: relation sources
113
+ // ************************
114
+ let items = Object.keys(relationMatrix).map((key) => ({
115
+ value: key,
116
+ label: relationMatrix[key].source.title,
117
+ targets: relationMatrix[key].targets.map((el) => el.UID),
118
+ url: relationMatrix[key].source['@id'],
119
+ review_state: relationMatrix[key].source.review_state,
120
+ }));
121
+ // Add potential sources
122
+ const potential_sources = potential_sources_objects.map((obj) => ({
123
+ value: obj.UID,
124
+ label: obj.title,
125
+ targets: relationMatrix[obj.UID]?.targets?.map((el) => el.UID) || [],
126
+ url: obj['@id'],
127
+ review_state: obj.review_state,
128
+ }));
129
+ items =
130
+ query_source === '' &&
131
+ query_target === '' &&
132
+ potential_sources_path !== '' &&
133
+ potential_targets_path !== ''
134
+ ? potential_sources
135
+ : [...items, ...potential_sources];
136
+ items = uniqBy(items, function (el) {
137
+ return el.value;
138
+ });
139
+ items.sort(function (a, b) {
140
+ var labelA = a.label.toUpperCase();
141
+ var labelB = b.label.toUpperCase();
142
+ if (labelA < labelB) {
143
+ return -1;
144
+ }
145
+ if (labelA > labelB) {
146
+ return 1;
147
+ }
148
+ return 0;
149
+ });
150
+
151
+ useEffect(() => {
152
+ // If many relations, then fetch relations only with search query on source or target
153
+ if (stats?.stats[relationtype] <= MAX_RELATIONS) {
154
+ dispatch(queryRelations(relationtype));
155
+ } else {
156
+ dispatch(
157
+ queryRelations(
158
+ relationtype,
159
+ false,
160
+ null,
161
+ null,
162
+ null,
163
+ query_source
164
+ ? query_source.startsWith('/')
165
+ ? query_source
166
+ : `${query_source}*`
167
+ : null,
168
+ query_target
169
+ ? query_target.startsWith('/')
170
+ ? query_target
171
+ : `${query_target}*`
172
+ : null,
173
+ ),
174
+ );
175
+ }
176
+ }, [dispatch, stats, relationtype, query_source, query_target]);
177
+
178
+ // Get potential source and target objects
179
+ useDeepCompareEffect(() => {
180
+ // Fetch fresh potential targets
181
+ if (potential_targets_path !== '/' && potential_targets_path !== '') {
182
+ dispatch(
183
+ searchContent(
184
+ potential_targets_path,
185
+ {
186
+ SearchableText: query_target,
187
+ metadata_fields: ['UID'],
188
+ sort_on: 'sortable_title',
189
+ ...staticCatalogVocabularyQuery,
190
+ },
191
+ 'potential_targets',
192
+ ),
193
+ );
194
+ } else {
195
+ // TODO Better just reset redux store
196
+ dispatch(searchContent('/findstenichätsch', null, 'potential_targets'));
197
+ }
198
+
199
+ // Fetch fresh potential sources
200
+ if (potential_sources_path !== '/' && potential_sources_path !== '') {
201
+ dispatch(
202
+ searchContent(
203
+ potential_sources_path,
204
+ {
205
+ SearchableText: query_source,
206
+ metadata_fields: ['UID'],
207
+ sort_on: 'sortable_title',
208
+ // No need to restrict here. ...staticCatalogVocabularyQuery,
209
+ },
210
+ 'potential_sources',
211
+ ),
212
+ );
213
+ } else {
214
+ // TODO Better just reset redux store
215
+ dispatch(searchContent('/findstenichätsch', null, 'potential_sources'));
216
+ }
217
+ }, [
218
+ dispatch,
219
+ potential_targets_path,
220
+ potential_sources_path,
221
+ staticCatalogVocabularyQuery,
222
+ query_source,
223
+ query_target,
224
+ ]);
225
+
226
+ function fetchRelations() {
227
+ dispatch(
228
+ queryRelations(
229
+ relationtype,
230
+ false,
231
+ null,
232
+ null,
233
+ null,
234
+ query_source
235
+ ? query_source.startsWith('/')
236
+ ? query_source
237
+ : `${query_source}*`
238
+ : null,
239
+ query_target
240
+ ? query_target.startsWith('/')
241
+ ? query_target
242
+ : `${query_target}*`
243
+ : null,
244
+ ),
245
+ );
246
+ }
247
+
248
+ const onSelectOptionHandler = (relation, selectedvalue, checked) => {
249
+ let source = selectedvalue.y;
250
+ let target = selectedvalue.x;
251
+ const relation_data = [
252
+ {
253
+ source: source,
254
+ target: target,
255
+ relation: relation,
256
+ },
257
+ ];
258
+ dispatch(
259
+ checked ? createRelations(relation_data) : deleteRelations(relation_data),
260
+ )
261
+ .then((resp) => {
262
+ fetchRelations();
263
+ })
264
+ .then(() => {
265
+ toast.success(
266
+ <Toast
267
+ success
268
+ title={intl.formatMessage(messages.success)}
269
+ content={intl.formatMessage(messages.relationsUpdated)}
270
+ />,
271
+ );
272
+ });
273
+ };
274
+
275
+ const onSelectAllHandler = (target, items_ids, checked) => {
276
+ let relation_data = [];
277
+ items_ids.forEach((el) => {
278
+ relation_data.push({
279
+ source: el,
280
+ target: target,
281
+ relation: relationtype,
282
+ });
283
+ });
284
+ dispatch(
285
+ checked ? createRelations(relation_data) : deleteRelations(relation_data),
286
+ )
287
+ .then((resp) => {
288
+ fetchRelations();
289
+ })
290
+ .then(() => {
291
+ toast.success(
292
+ <Toast
293
+ success
294
+ title={intl.formatMessage(messages.success)}
295
+ content={intl.formatMessage(messages.relationsUpdated)}
296
+ />,
297
+ );
298
+ });
299
+ };
300
+
301
+ return (
302
+ <>
303
+ {/* <div>
304
+ <div>
305
+ <div>{items.length} sources</div>
306
+ <div>{matrix_options.length} targets</div>
307
+ </div>
308
+ <div>
309
+ <div>query_source <b>{query_source}</b></div>
310
+ <div>query_target <b>{query_target}</b></div>
311
+ <div>potential_sources_path <b>{potential_sources_path}</b></div>
312
+ <div>potential_targets_path <b>{potential_targets_path}</b></div>
313
+ </div>
314
+ </div> */}
315
+ {matrix_options.length <= MAX &&
316
+ (items.length <= MAX) & (matrix_options.length > 0) &&
317
+ items.length > 0 ? (
318
+ <div className="administration_matrix">
319
+ <div className="label-options">
320
+ <div className="target-labels">
321
+ <div></div>
322
+ <div>
323
+ {matrix_options?.map((matrix_option) => (
324
+ <div
325
+ className="label-options-label inclined"
326
+ id={`label-options-label-${matrix_option.value}`}
327
+ key={matrix_option.value}
328
+ >
329
+ <div>
330
+ <UniversalLink
331
+ href={matrix_option.url}
332
+ className={
333
+ matrix_option.review_state !== 'published'
334
+ ? 'not-published'
335
+ : ''
336
+ }
337
+ target="_blank"
338
+ >
339
+ <span className="label" title={matrix_option.label}>
340
+ {matrix_option.label.length > 30
341
+ ? matrix_option.label.slice(0, 27) + '...'
342
+ : matrix_option.label}
343
+ </span>
344
+ </UniversalLink>
345
+ </div>
346
+ </div>
347
+ ))}
348
+ </div>
349
+ </div>
350
+ <div className="listing-row selectall" key="selectall">
351
+ <div className="listing-item">
352
+ <div />
353
+ <div className="matrix_options">
354
+ {!(
355
+ relationtype === 'isReferencing' ||
356
+ relationtype === 'iterate-working-copy' ||
357
+ !editable
358
+ ) ? (
359
+ matrix_options?.map((matrix_option) => (
360
+ <div
361
+ key={matrix_option.value}
362
+ title={
363
+ intl.formatMessage(
364
+ messages.createOrDeleteRelationsToTarget,
365
+ ) + ` '${matrix_option.label}'`
366
+ }
367
+ >
368
+ <Checkbox
369
+ className="toggle-target"
370
+ defaultChecked={false}
371
+ onChange={(event, { checked }) =>
372
+ onSelectAllHandler(
373
+ matrix_option.value,
374
+ items.map((el) => el.value),
375
+ checked,
376
+ )
377
+ }
378
+ />
379
+ </div>
380
+ ))
381
+ ) : (
382
+ <FormattedMessage
383
+ id="Read only for this type of relation."
384
+ defaultMessage="Read only for this type of relation."
385
+ />
386
+ )}
387
+ </div>
388
+ </div>
389
+ </div>
390
+ </div>
391
+
392
+ <div className="items" key="items">
393
+ <>
394
+ {!editable && (
395
+ <Message warning>
396
+ <FormattedMessage
397
+ id="Relations are editable with plone.api >= 2.0.3."
398
+ defaultMessage="Relations are editable with plone.api >= 2.0.3."
399
+ />
400
+ </Message>
401
+ )}
402
+ {items.map((item) => (
403
+ <div
404
+ className="listing-row"
405
+ key={item.id}
406
+ id={`source-row-${item.value}`}
407
+ >
408
+ <div className="listing-item" key={item['@id']}>
409
+ <div>
410
+ <span title={item.label} className="item-title">
411
+ <UniversalLink
412
+ href={item.url}
413
+ className={
414
+ item.review_state !== 'published'
415
+ ? 'not-published'
416
+ : ''
417
+ }
418
+ target="_blank"
419
+ >
420
+ {item.label.length > 25
421
+ ? item.label.slice(0, 22) + '...'
422
+ : item.label}
423
+ </UniversalLink>
424
+ {/* <span>targets: {item.targets.join(', ')}</span> */}
425
+ </span>
426
+ </div>
427
+ <div className="matrix_options">
428
+ {matrix_options?.map((matrix_option) => (
429
+ <React.Fragment key={matrix_option.value}>
430
+ <Checkbox
431
+ className={`checkbox_${matrix_option.value}`}
432
+ key={matrix_option.value}
433
+ title={matrix_option.title}
434
+ disabled={
435
+ relationtype === 'isReferencing' ||
436
+ relationtype === 'iterate-working-copy' ||
437
+ !editable
438
+ }
439
+ checked={item.targets.includes(matrix_option.value)}
440
+ onChange={(event, { checked }) => {
441
+ onSelectOptionHandler(
442
+ relationtype,
443
+ { x: matrix_option.value, y: item.value },
444
+ checked,
445
+ );
446
+ }}
447
+ />
448
+ </React.Fragment>
449
+ ))}
450
+ </div>
451
+ </div>
452
+ </div>
453
+ ))}
454
+ </>
455
+ </div>
456
+ </div>
457
+ ) : (
458
+ <div className="administration_matrix">
459
+ {matrix_options.length > MAX || items.length > MAX ? (
460
+ <FormattedMessage
461
+ id="narrowDownRelations"
462
+ defaultMessage="Found {sources} sources and {targets} targets. Narrow down to {max}!"
463
+ values={{
464
+ sources: items.length,
465
+ targets: matrix_options.length,
466
+ max: MAX,
467
+ }}
468
+ />
469
+ ) : query_source || query_target ? (
470
+ <div>{intl.formatMessage(messages.norelationfound)}</div>
471
+ ) : (
472
+ <div>{intl.formatMessage(messages.toomanyrelationsfound)}</div>
473
+ )}
474
+ </div>
475
+ )}
476
+ </>
477
+ );
478
+ };
479
+ export default ListingTemplate;