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

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 (79) hide show
  1. package/.yarn/install-state.gz +0 -0
  2. package/CHANGELOG.md +51 -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/Widgets/RelationsWidget.jsx +13 -11
  66. package/src/config/ControlPanels.js +2 -0
  67. package/src/constants/ActionTypes.js +4 -0
  68. package/src/constants/Languages.js +8 -4
  69. package/src/helpers/Html/Html.jsx +3 -1
  70. package/src/helpers/Html/Html.test.jsx +5 -0
  71. package/src/helpers/MessageLabels/MessageLabels.js +72 -0
  72. package/src/reducers/actions/actions.js +1 -1
  73. package/src/reducers/breadcrumbs/breadcrumbs.js +1 -1
  74. package/src/reducers/index.js +2 -0
  75. package/src/reducers/navigation/navigation.js +1 -1
  76. package/src/reducers/relations/relations.js +173 -0
  77. package/src/reducers/types/types.js +1 -1
  78. package/src/routes.js +5 -0
  79. package/theme/themes/pastanaga/extras/userscontrolpanel.less +99 -76
@@ -0,0 +1,531 @@
1
+ import React, { useEffect, useState } from 'react';
2
+ import { capitalize, find } from 'lodash';
3
+ import { compose } from 'redux';
4
+ import { useSelector, useDispatch } from 'react-redux';
5
+ import { FormattedMessage, useIntl } from 'react-intl';
6
+ import { toast } from 'react-toastify';
7
+ import {
8
+ Button,
9
+ Divider,
10
+ Dropdown,
11
+ Form,
12
+ Header,
13
+ Input,
14
+ Popup,
15
+ Tab,
16
+ } from 'semantic-ui-react';
17
+ import withObjectBrowser from '@plone/volto/components/manage/Sidebar/ObjectBrowser';
18
+ import { messages } from '@plone/volto/helpers';
19
+ import { Icon, Toast } from '@plone/volto/components';
20
+ import { rebuildRelations, queryRelations } from '@plone/volto/actions';
21
+ import RelationsListing from './RelationsListing';
22
+ import BrokenRelations from './BrokenRelations';
23
+ import helpSVG from '@plone/volto/icons/help.svg';
24
+ import clearSVG from '@plone/volto/icons/clear.svg';
25
+ import navTreeSVG from '@plone/volto/icons/nav.svg';
26
+
27
+ const RelationsMatrix = (props) => {
28
+ const intl = useIntl();
29
+ const dispatch = useDispatch();
30
+
31
+ const [query_source, setQuery_source] = useState('');
32
+ const [query_target, setQuery_target] = useState('');
33
+ const [potential_targets_path, setPotential_targets_path] = useState('');
34
+ const [potential_sources_path, setPotential_sources_path] = useState('');
35
+ const [relationtype, setRelationtype] = useState(undefined);
36
+
37
+ const actions = useSelector((state) => state.actions?.actions ?? {});
38
+ const can_fix_relations = find(actions.user, {
39
+ id: 'plone_setup',
40
+ });
41
+
42
+ const relationtypes = useSelector((state) => state.relations?.stats?.stats);
43
+ const relationsListError = useSelector(
44
+ (state) => state.relations?.list?.error?.response?.body?.error,
45
+ );
46
+ const brokenRelations = useSelector(
47
+ (state) => state.relations?.stats?.broken,
48
+ );
49
+
50
+ let filter_options = useSelector((state) => state.groups.filter_groups);
51
+ if (filter_options) {
52
+ filter_options = filter_options.map((group) => ({
53
+ value: group.id,
54
+ label: group.title || group.id,
55
+ }));
56
+ filter_options.sort(function (a, b) {
57
+ var labelA = a.label.toUpperCase();
58
+ var labelB = b.label.toUpperCase();
59
+ if (labelA < labelB) {
60
+ return -1;
61
+ }
62
+ if (labelA > labelB) {
63
+ return 1;
64
+ }
65
+ return 0;
66
+ });
67
+ }
68
+
69
+ useEffect(() => {
70
+ dispatch(queryRelations());
71
+ }, [dispatch]);
72
+
73
+ const onReset = (event) => {
74
+ let element = event.target.querySelector('input');
75
+ element.value = '';
76
+ element.focus();
77
+ let searchtype = element.name;
78
+ switch (searchtype) {
79
+ case 'SearchY':
80
+ setQuery_source('');
81
+ break;
82
+ case 'SearchX':
83
+ setQuery_target('');
84
+ break;
85
+ case 'showPotentialTargets':
86
+ setPotential_targets_path('/');
87
+ break;
88
+ case 'showPotentialSources':
89
+ setPotential_sources_path('/');
90
+ break;
91
+ default:
92
+ break;
93
+ }
94
+ };
95
+
96
+ // search for sources
97
+ const onChangeSearchYs = (event) => {
98
+ if (event.target.value.length > 1) {
99
+ setQuery_source(event.target.value);
100
+ } else {
101
+ setQuery_source('');
102
+ }
103
+ };
104
+
105
+ // search for targets
106
+ const onChangeSearchXs = (event) => {
107
+ if (event.target.value.length > 1) {
108
+ setQuery_target(event.target.value);
109
+ } else {
110
+ setQuery_source('');
111
+ }
112
+ };
113
+
114
+ const onChangeRelation = (event, { value }) => {
115
+ setRelationtype(value);
116
+ };
117
+
118
+ const onChangeShowPotentialSources = (_value) => {
119
+ let newValue = _value;
120
+ setPotential_sources_path(newValue);
121
+ };
122
+
123
+ const onChangeShowPotentialTargets = (_value) => {
124
+ let newValue = _value;
125
+ setPotential_targets_path(newValue);
126
+ };
127
+
128
+ const rebuildRelationsHandler = (flush = false) => {
129
+ dispatch(rebuildRelations(flush))
130
+ .then(() => {
131
+ dispatch(queryRelations());
132
+ })
133
+ .then(() => {
134
+ dispatch(queryRelations(null, true, 'broken'));
135
+ })
136
+ .then(() => {
137
+ toast.success(
138
+ <Toast
139
+ success
140
+ title={intl.formatMessage(messages.success)}
141
+ content="Relations updated"
142
+ />,
143
+ );
144
+ })
145
+ .catch((error) => {
146
+ // TODO: The true error sent by the API is shadowed by the superagent one
147
+ // Update this when this issue is fixed.
148
+ const shadowedError = JSON.parse(error.response.text);
149
+ toast.error(
150
+ <Toast
151
+ error
152
+ title={shadowedError.error.type}
153
+ content={shadowedError.error.message}
154
+ />,
155
+ );
156
+ });
157
+ };
158
+
159
+ const clear_potential_sources_path = () => {
160
+ setPotential_sources_path('');
161
+ // onChange(id, undefined);
162
+ };
163
+
164
+ const clear_potential_targets_path = () => {
165
+ setPotential_targets_path('');
166
+ // onChange(id, undefined);
167
+ };
168
+
169
+ const panes = [
170
+ {
171
+ menuItem: intl.formatMessage(messages.inspectRelations),
172
+ pane: (
173
+ <Tab.Pane attached={true} key="fix">
174
+ {relationtypes ? (
175
+ <div className="controlpanel_matrix">
176
+ <div className="controlpanel_select_relation">
177
+ <Divider hidden />
178
+ <Form className="select_relation">
179
+ <Form.Field>
180
+ <Header as="h3">
181
+ <Header.Content>
182
+ <FormattedMessage
183
+ id="Relation name"
184
+ defaultMessage="Relation"
185
+ />{' '}
186
+ <Dropdown
187
+ placeholder={
188
+ relationtype ||
189
+ intl.formatMessage(messages.selectRelation)
190
+ }
191
+ >
192
+ <Dropdown.Menu>
193
+ {Object.keys(relationtypes).map((relationtype) => (
194
+ <Dropdown.Item
195
+ onClick={onChangeRelation}
196
+ value={relationtype}
197
+ className={`select-relation-${relationtype}`}
198
+ key={relationtype}
199
+ >
200
+ {`${relationtype} (${relationtypes[relationtype]})`}
201
+ </Dropdown.Item>
202
+ ))}
203
+ </Dropdown.Menu>
204
+ </Dropdown>
205
+ </Header.Content>
206
+ </Header>
207
+ </Form.Field>
208
+ </Form>
209
+ </div>
210
+ {relationtype ? (
211
+ <>
212
+ <div className="controlpanel_search_wrapper">
213
+ <div className="controlpanel_search_y">
214
+ <Header as="h4">
215
+ <Header.Content>
216
+ <FormattedMessage
217
+ id="Source"
218
+ defaultMessage="Source"
219
+ />
220
+ </Header.Content>
221
+ </Header>
222
+ <Form className="search_y" onSubmit={onReset}>
223
+ <Form.Field>
224
+ <Input
225
+ name="SearchY"
226
+ placeholder={intl.formatMessage(
227
+ messages.searchRelationSource,
228
+ )}
229
+ onChange={onChangeSearchYs}
230
+ id="y-search-input"
231
+ />
232
+ <Button.Group>
233
+ <Button
234
+ basic
235
+ className="cancel"
236
+ aria-label="cancel"
237
+ onClick={(e) => {
238
+ e.preventDefault();
239
+ e.stopPropagation();
240
+ document.querySelector(
241
+ 'input[name="SearchY"]',
242
+ ).value = '';
243
+ setQuery_source('');
244
+ }}
245
+ >
246
+ <Icon name={clearSVG} size="24px" />
247
+ </Button>
248
+ </Button.Group>
249
+ </Form.Field>
250
+ </Form>
251
+ <Form
252
+ className="add_potential_sources"
253
+ onSubmit={onReset}
254
+ >
255
+ <Form.Field>
256
+ <Input
257
+ name="showPotentialSources"
258
+ type="url"
259
+ value={potential_sources_path}
260
+ placeholder={intl.formatMessage(
261
+ messages.addPotentialSourcesPath,
262
+ )}
263
+ onChange={({ target }) =>
264
+ onChangeShowPotentialSources(target.value)
265
+ }
266
+ id="potential-sources-path-input"
267
+ />
268
+ {potential_sources_path?.length > 0 ? (
269
+ <Button.Group>
270
+ <Button
271
+ basic
272
+ className="cancel"
273
+ aria-label="clearUrlBrowser"
274
+ onClick={(e) => {
275
+ e.preventDefault();
276
+ e.stopPropagation();
277
+ clear_potential_sources_path();
278
+ }}
279
+ >
280
+ <Icon name={clearSVG} size="24px" />
281
+ </Button>
282
+ </Button.Group>
283
+ ) : (
284
+ <Button.Group>
285
+ <Button
286
+ basic
287
+ icon
288
+ aria-label="openUrlBrowser"
289
+ onClick={(e) => {
290
+ e.preventDefault();
291
+ e.stopPropagation();
292
+ props.openObjectBrowser({
293
+ mode: 'link',
294
+ overlay: true,
295
+ onSelectItem: (url) => {
296
+ onChangeShowPotentialSources(url);
297
+ },
298
+ });
299
+ }}
300
+ >
301
+ <Icon name={navTreeSVG} size="24px" />
302
+ </Button>
303
+ </Button.Group>
304
+ )}
305
+ </Form.Field>
306
+ <FormattedMessage
307
+ id="Show potential sources. Not only objects that are source of some relation."
308
+ defaultMessage="Show potential sources. Not only objects that are source of some relation."
309
+ />
310
+ </Form>
311
+ </div>
312
+ <div className="controlpanel_search_x">
313
+ <Form className="search_x" onSubmit={onReset}>
314
+ <Header as="h4">
315
+ <Header.Content>
316
+ <FormattedMessage
317
+ id="Target"
318
+ defaultMessage="Target"
319
+ />
320
+ </Header.Content>
321
+ </Header>
322
+ <Form.Field>
323
+ <Input
324
+ name="SearchX"
325
+ placeholder={intl.formatMessage(
326
+ messages.searchRelationTarget,
327
+ )}
328
+ onChange={onChangeSearchXs}
329
+ id="x-search-input"
330
+ />
331
+ <Button.Group>
332
+ <Button
333
+ basic
334
+ className="cancel"
335
+ aria-label="cancel"
336
+ onClick={(e) => {
337
+ e.preventDefault();
338
+ e.stopPropagation();
339
+ document.querySelector(
340
+ 'input[name="SearchX"]',
341
+ ).value = '';
342
+ setQuery_target('');
343
+ }}
344
+ >
345
+ <Icon name={clearSVG} size="24px" />
346
+ </Button>
347
+ </Button.Group>
348
+ </Form.Field>
349
+ </Form>
350
+ <Form
351
+ className="add_potential_targets"
352
+ onSubmit={onReset}
353
+ >
354
+ <Form.Field>
355
+ <Input
356
+ name="showPotentialTargets"
357
+ type="url"
358
+ value={potential_targets_path}
359
+ placeholder={intl.formatMessage(
360
+ messages.addPotentialTargetsPath,
361
+ )}
362
+ onChange={({ target }) =>
363
+ onChangeShowPotentialTargets(target.value)
364
+ }
365
+ id="potential-targets-path-input"
366
+ />
367
+ {potential_targets_path?.length > 0 ? (
368
+ <Button.Group>
369
+ <Button
370
+ basic
371
+ className="cancel"
372
+ aria-label="clearUrlBrowser"
373
+ onClick={(e) => {
374
+ e.preventDefault();
375
+ e.stopPropagation();
376
+ clear_potential_targets_path();
377
+ }}
378
+ >
379
+ <Icon name={clearSVG} size="24px" />
380
+ </Button>
381
+ </Button.Group>
382
+ ) : (
383
+ <Button.Group>
384
+ <Button
385
+ basic
386
+ icon
387
+ aria-label="openUrlBrowser"
388
+ onClick={(e) => {
389
+ e.preventDefault();
390
+ e.stopPropagation();
391
+ props.openObjectBrowser({
392
+ mode: 'link',
393
+ overlay: true,
394
+ onSelectItem: (url) => {
395
+ onChangeShowPotentialTargets(url);
396
+ },
397
+ });
398
+ }}
399
+ >
400
+ <Icon name={navTreeSVG} size="24px" />
401
+ </Button>
402
+ </Button.Group>
403
+ )}
404
+ </Form.Field>
405
+ <div className="foo">
406
+ <FormattedMessage
407
+ id="Show potential targets. Not only objects that are target of some relation."
408
+ defaultMessage="Show potential targets. Not only objects that are target of some relation."
409
+ />{' '}
410
+ <Popup
411
+ trigger={
412
+ <a
413
+ href="https://6.docs.plone.org/volto/recipes/widget.html#restricting-potential-targets"
414
+ target="_blank"
415
+ rel="noopener noreferrer"
416
+ >
417
+ <Icon name={helpSVG} size="16px" />
418
+ </a>
419
+ }
420
+ >
421
+ <Popup.Header>Respect constraints</Popup.Header>
422
+ <Popup.Content>
423
+ <div>
424
+ See docs.plone.org on how to respect
425
+ constraints.
426
+ </div>
427
+ </Popup.Content>
428
+ </Popup>
429
+ </div>
430
+ </Form>
431
+ </div>
432
+ </div>
433
+ <div className="controlpanel_listing_wrapper">
434
+ <RelationsListing
435
+ relationtype={relationtype}
436
+ query_source={query_source}
437
+ query_target={query_target}
438
+ potential_targets_path={potential_targets_path}
439
+ potential_sources_path={potential_sources_path}
440
+ />
441
+ </div>
442
+ </>
443
+ ) : null}
444
+ </div>
445
+ ) : (
446
+ <p>{relationsListError?.message}</p>
447
+ )}
448
+ </Tab.Pane>
449
+ ),
450
+ },
451
+ {
452
+ menuItem: intl.formatMessage(messages.fixRelations),
453
+ pane: (
454
+ <Tab.Pane attached={true} key="rebuild">
455
+ {brokenRelations && Object.keys(brokenRelations).length > 0 ? (
456
+ <div>
457
+ {can_fix_relations ? (
458
+ <React.Fragment>
459
+ <Divider hidden />
460
+ <h2>
461
+ {capitalize(intl.formatMessage(messages.rebuildRelations))}
462
+ </h2>
463
+
464
+ <Button.Group>
465
+ <Button
466
+ primary
467
+ onClick={() => rebuildRelationsHandler(false)}
468
+ content={intl.formatMessage(messages.rebuildRelations)}
469
+ aria-label={intl.formatMessage(messages.rebuildRelations)}
470
+ />
471
+ </Button.Group>
472
+
473
+ <Divider hidden />
474
+ <h2>
475
+ {capitalize(
476
+ intl.formatMessage(messages.flushAndRebuildRelations),
477
+ )}
478
+ </h2>
479
+ <ul>
480
+ <li>
481
+ Regenerate intIds (tokens of relations in relation
482
+ catalog)
483
+ </li>
484
+ <li>Rebuild relations</li>
485
+ </ul>
486
+ <p>Check the log for details!</p>
487
+ <p>
488
+ <b>Warning</b>: If you have add-ons relying on intIds, you
489
+ should not flush them.
490
+ </p>
491
+ <Divider hidden />
492
+ <Button.Group>
493
+ <Button
494
+ secondary
495
+ color="red"
496
+ onClick={() => rebuildRelationsHandler(true)}
497
+ content={intl.formatMessage(
498
+ messages.flushAndRebuildRelations,
499
+ )}
500
+ aria-label={intl.formatMessage(
501
+ messages.flushAndRebuildRelations,
502
+ )}
503
+ />
504
+ </Button.Group>
505
+ </React.Fragment>
506
+ ) : null}
507
+ <BrokenRelations />
508
+ </div>
509
+ ) : (
510
+ <div>
511
+ <FormattedMessage
512
+ id="No broken relations found."
513
+ defaultMessage="No broken relations found."
514
+ />
515
+ </div>
516
+ )}
517
+ </Tab.Pane>
518
+ ),
519
+ },
520
+ ];
521
+
522
+ return (
523
+ <Tab
524
+ panes={panes}
525
+ renderActiveOnly={false}
526
+ menu={{ secondary: true, pointing: true, attached: true, tabular: true }}
527
+ />
528
+ );
529
+ };
530
+
531
+ export default compose(withObjectBrowser)(RelationsMatrix);
@@ -9,7 +9,7 @@ import { useHistory } from 'react-router';
9
9
  import { Link, useLocation } from 'react-router-dom';
10
10
  import { FormattedMessage, useIntl } from 'react-intl';
11
11
  import { useDispatch, useSelector } from 'react-redux';
12
- import { Container, Segment } from 'semantic-ui-react';
12
+ import { Segment } from 'semantic-ui-react';
13
13
  import { Helmet, messages } from '@plone/volto/helpers';
14
14
  import {
15
15
  getControlpanel,
@@ -63,7 +63,7 @@ const UserGroupMembershipPanel = () => {
63
63
 
64
64
  return (
65
65
  <>
66
- <Container className="users-control-panel">
66
+ <div className="users-control-panel">
67
67
  <Helmet title={intl.formatMessage(messages.usergroupmemberbership)} />
68
68
  <Segment.Group raised>
69
69
  <Segment className="primary">
@@ -106,7 +106,7 @@ const UserGroupMembershipPanel = () => {
106
106
  </Segment>
107
107
  )}
108
108
  </Segment.Group>
109
- </Container>
109
+ </div>
110
110
 
111
111
  {__CLIENT__ && (
112
112
  <Portal node={document.getElementById('toolbar')}>