@plone/volto 18.0.0-alpha.35 → 18.0.0-alpha.37

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 (75) hide show
  1. package/.release-it.json +1 -1
  2. package/CHANGELOG.md +49 -0
  3. package/locales/ca/LC_MESSAGES/volto.po +58 -4
  4. package/locales/ca.json +1 -1
  5. package/locales/de/LC_MESSAGES/volto.po +61 -7
  6. package/locales/de.json +1 -1
  7. package/locales/en/LC_MESSAGES/volto.po +58 -4
  8. package/locales/en.json +1 -1
  9. package/locales/es/LC_MESSAGES/volto.po +58 -4
  10. package/locales/es.json +1 -1
  11. package/locales/eu/LC_MESSAGES/volto.po +58 -4
  12. package/locales/eu.json +1 -1
  13. package/locales/fi/LC_MESSAGES/volto.po +61 -7
  14. package/locales/fi.json +1 -1
  15. package/locales/fr/LC_MESSAGES/volto.po +58 -4
  16. package/locales/fr.json +1 -1
  17. package/locales/hi/LC_MESSAGES/volto.po +58 -4
  18. package/locales/hi.json +1 -1
  19. package/locales/it/LC_MESSAGES/volto.po +58 -4
  20. package/locales/it.json +1 -1
  21. package/locales/ja/LC_MESSAGES/volto.po +58 -4
  22. package/locales/ja.json +1 -1
  23. package/locales/nl/LC_MESSAGES/volto.po +58 -4
  24. package/locales/nl.json +1 -1
  25. package/locales/pt/LC_MESSAGES/volto.po +58 -4
  26. package/locales/pt.json +1 -1
  27. package/locales/pt_BR/LC_MESSAGES/volto.po +58 -4
  28. package/locales/pt_BR.json +1 -1
  29. package/locales/ro/LC_MESSAGES/volto.po +58 -4
  30. package/locales/ro.json +1 -1
  31. package/locales/volto.pot +59 -5
  32. package/locales/zh_CN/LC_MESSAGES/volto.po +58 -4
  33. package/locales/zh_CN.json +1 -1
  34. package/package.json +5 -5
  35. package/src/components/manage/AnchorPlugin/components/LinkButton/AddLinkForm.jsx +78 -72
  36. package/src/components/manage/AnchorPlugin/useLinkEditor.jsx +79 -0
  37. package/src/components/manage/Blocks/Image/Edit.jsx +45 -347
  38. package/src/components/manage/Blocks/Teaser/Data.jsx +95 -2
  39. package/src/components/manage/Blocks/Teaser/schema.js +24 -3
  40. package/src/components/manage/Controlpanels/Groups/GroupsControlpanel.jsx +34 -4
  41. package/src/components/manage/Controlpanels/Users/UsersControlpanel.jsx +30 -4
  42. package/src/components/manage/Form/BlocksToolbar.jsx +8 -7
  43. package/src/components/manage/Form/Form.jsx +8 -1
  44. package/src/components/manage/Form/InlineForm.jsx +4 -0
  45. package/src/components/manage/Widgets/ImageWidget.jsx +311 -0
  46. package/src/components/manage/Widgets/ImageWidget.stories.jsx +46 -0
  47. package/src/config/NonContentRoutes.jsx +2 -1
  48. package/src/config/Widgets.jsx +2 -0
  49. package/src/helpers/Blocks/cloneBlocks.test.js +54 -0
  50. package/src/helpers/Blocks/cloneBlocks.ts +43 -0
  51. package/src/helpers/MessageLabels/MessageLabels.js +8 -0
  52. package/src/icons/external-link.svg +4 -0
  53. package/theme/themes/pastanaga/collections/form.overrides +4 -0
  54. package/theme/themes/pastanaga/elements/button.overrides +2 -1
  55. package/theme/themes/pastanaga/extras/blocks.less +40 -4
  56. package/theme/themes/pastanaga/extras/grid.less +3 -3
  57. package/theme/themes/pastanaga/extras/teaser.less +53 -0
  58. package/theme/themes/pastanaga/extras/widgets.less +108 -0
  59. package/types/components/manage/AnchorPlugin/useLinkEditor.d.ts +7 -0
  60. package/types/components/manage/Blocks/Teaser/schema.d.ts +9 -2
  61. package/types/components/manage/Widgets/ImageWidget.d.ts +10 -0
  62. package/types/components/manage/Widgets/ImageWidget.stories.d.ts +11 -0
  63. package/types/config/Widgets.d.ts +2 -0
  64. package/types/helpers/Blocks/cloneBlocks.d.ts +1 -0
  65. package/types/helpers/Blocks/cloneBlocks.test.d.ts +1 -0
  66. package/types/helpers/MessageLabels/MessageLabels.d.ts +552 -1
  67. /package/src/components/manage/Blocks/Image/{schema.js → schema.jsx} +0 -0
  68. /package/src/components/theme/LanguageSelector/{LanguageSelector.js → LanguageSelector.jsx} +0 -0
  69. /package/src/helpers/Extensions/{withBlockExtensions.js → withBlockExtensions.jsx} +0 -0
  70. /package/src/helpers/Extensions/{withBlockSchemaEnhancer.js → withBlockSchemaEnhancer.jsx} +0 -0
  71. /package/src/helpers/FormValidation/{FormValidation.js → FormValidation.jsx} +0 -0
  72. /package/src/helpers/Helmet/{Helmet.js → Helmet.jsx} +0 -0
  73. /package/src/helpers/Loadable/{Loadable.js → Loadable.jsx} +0 -0
  74. /package/src/helpers/Loadable/__mocks__/{Loadable.js → Loadable.jsx} +0 -0
  75. /package/src/helpers/Utils/{Utils.js → Utils.jsx} +0 -0
@@ -3,256 +3,57 @@
3
3
  * @module components/manage/Blocks/Image/Edit
4
4
  */
5
5
 
6
- import React, { Component } from 'react';
7
- import PropTypes from 'prop-types';
6
+ import React from 'react';
8
7
  import { connect } from 'react-redux';
9
8
  import { compose } from 'redux';
10
- import { readAsDataURL } from 'promise-file-reader';
11
- import { Button, Dimmer, Input, Loader, Message } from 'semantic-ui-react';
12
- import { defineMessages, injectIntl } from 'react-intl';
13
- import loadable from '@loadable/component';
14
- import cx from 'classnames';
15
- import { isEqual } from 'lodash';
16
9
 
17
- import { Icon, ImageSidebar, SidebarPortal } from '@plone/volto/components';
10
+ import { injectIntl } from 'react-intl';
11
+ import cx from 'classnames';
12
+ import { ImageSidebar, SidebarPortal } from '@plone/volto/components';
18
13
  import { createContent } from '@plone/volto/actions';
14
+
19
15
  import {
20
16
  flattenToAppURL,
21
- getBaseUrl,
22
17
  isInternalURL,
23
18
  withBlockExtensions,
24
- validateFileUploadSize,
25
19
  } from '@plone/volto/helpers';
26
20
  import config from '@plone/volto/registry';
27
21
 
28
- import imageBlockSVG from '@plone/volto/components/manage/Blocks/Image/block-image.svg';
29
- import clearSVG from '@plone/volto/icons/clear.svg';
30
- import navTreeSVG from '@plone/volto/icons/nav.svg';
31
- import aheadSVG from '@plone/volto/icons/ahead.svg';
32
- import uploadSVG from '@plone/volto/icons/upload.svg';
33
-
34
- const Dropzone = loadable(() => import('react-dropzone'));
35
-
36
- const messages = defineMessages({
37
- ImageBlockInputPlaceholder: {
38
- id: 'Browse the site, drop an image, or type an URL',
39
- defaultMessage: 'Browse the site, drop an image, or type an URL',
40
- },
41
- uploadingImage: {
42
- id: 'Uploading image',
43
- defaultMessage: 'Uploading image',
44
- },
45
- });
22
+ import { ImageInput } from '@plone/volto/components/manage/Widgets/ImageWidget';
46
23
 
47
24
  /**
48
- * Edit image block class.
49
- * @class Edit
50
- * @extends Component
25
+ * Edit image block function.
26
+ * @function Edit
51
27
  */
52
- class Edit extends Component {
53
- /**
54
- * Property types.
55
- * @property {Object} propTypes Property types.
56
- * @static
57
- */
58
- static propTypes = {
59
- selected: PropTypes.bool.isRequired,
60
- block: PropTypes.string.isRequired,
61
- index: PropTypes.number.isRequired,
62
- data: PropTypes.objectOf(PropTypes.any).isRequired,
63
- content: PropTypes.objectOf(PropTypes.any),
64
- request: PropTypes.shape({
65
- loading: PropTypes.bool,
66
- loaded: PropTypes.bool,
67
- }).isRequired,
68
- pathname: PropTypes.string.isRequired,
69
- onChangeBlock: PropTypes.func.isRequired,
70
- onSelectBlock: PropTypes.func.isRequired,
71
- onDeleteBlock: PropTypes.func.isRequired,
72
- onFocusPreviousBlock: PropTypes.func.isRequired,
73
- onFocusNextBlock: PropTypes.func.isRequired,
74
- handleKeyDown: PropTypes.func.isRequired,
75
- createContent: PropTypes.func.isRequired,
76
- openObjectBrowser: PropTypes.func.isRequired,
77
- };
78
28
 
79
- state = {
80
- uploading: false,
81
- url: '',
82
- dragging: false,
83
- };
84
-
85
- /**
86
- * Component will receive props
87
- * @method componentWillReceiveProps
88
- * @param {Object} nextProps Next properties
89
- * @returns {undefined}
90
- */
91
- UNSAFE_componentWillReceiveProps(nextProps) {
92
- // Update block data after upload finished
93
- if (
94
- this.props.request.loading &&
95
- nextProps.request.loaded &&
96
- this.state.uploading
97
- ) {
98
- this.setState({
99
- uploading: false,
100
- });
101
- this.props.onChangeBlock(this.props.block, {
102
- ...this.props.data,
103
- url: nextProps.content['@id'],
104
- image_field: 'image',
105
- image_scales: { image: [nextProps.content.image] },
106
- alt: '',
29
+ // const messages = defineMessages({
30
+ // notImage: {
31
+ // id: 'The provided link does not lead to an image.',
32
+ // defaultMessage: 'The provided link does not lead to an image.',
33
+ // },
34
+ // });
35
+
36
+ function Edit(props) {
37
+ const { data } = props;
38
+ const Image = config.getComponent({ name: 'Image' }).component;
39
+
40
+ const handleChange = React.useCallback(
41
+ async (id, image, { title, image_field, image_scales } = {}) => {
42
+ const url = image ? image['@id'] || image : '';
43
+
44
+ props.onChangeBlock(props.block, {
45
+ ...props.data,
46
+ url: flattenToAppURL(url),
47
+ image_field,
48
+ image_scales,
49
+ alt: props.data.alt || title || '',
107
50
  });
108
- }
109
- }
110
-
111
- /**
112
- * @param {*} nextProps
113
- * @returns {boolean}
114
- * @memberof Edit
115
- */
116
- shouldComponentUpdate(nextProps) {
117
- return (
118
- this.props.selected ||
119
- nextProps.selected ||
120
- !isEqual(this.props.data, nextProps.data)
121
- );
122
- }
123
-
124
- /**
125
- * Upload image handler (not used), but useful in case that we want a button
126
- * not powered by react-dropzone
127
- * @method onUploadImage
128
- * @returns {undefined}
129
- */
130
- onUploadImage = (e) => {
131
- e.stopPropagation();
132
- const file = e.target.files[0];
133
- if (!validateFileUploadSize(file, this.props.intl.formatMessage)) return;
134
- this.setState({
135
- uploading: true,
136
- });
137
- readAsDataURL(file).then((data) => {
138
- const fields = data.match(/^data:(.*);(.*),(.*)$/);
139
- this.props.createContent(
140
- getBaseUrl(this.props.pathname),
141
- {
142
- '@type': 'Image',
143
- title: file.name,
144
- image: {
145
- data: fields[3],
146
- encoding: fields[2],
147
- 'content-type': fields[1],
148
- filename: file.name,
149
- },
150
- },
151
- this.props.block,
152
- );
153
- });
154
- };
51
+ },
52
+ [props],
53
+ );
155
54
 
156
- /**
157
- * Change url handler
158
- * @method onChangeUrl
159
- * @param {Object} target Target object
160
- * @returns {undefined}
161
- */
162
- onChangeUrl = ({ target }) => {
163
- this.setState({
164
- url: target.value,
165
- });
166
- };
167
-
168
- /**
169
- * Submit url handler
170
- * @method onSubmitUrl
171
- * @param {object} e Event
172
- * @returns {undefined}
173
- */
174
- onSubmitUrl = () => {
175
- this.props.onChangeBlock(this.props.block, {
176
- ...this.props.data,
177
- url: flattenToAppURL(this.state.url),
178
- image_field: undefined,
179
- image_scales: undefined,
180
- });
181
- };
182
-
183
- /**
184
- * Drop handler
185
- * @method onDrop
186
- * @param {array} files File objects
187
- * @returns {undefined}
188
- */
189
- onDrop = (files) => {
190
- if (!validateFileUploadSize(files[0], this.props.intl.formatMessage)) {
191
- this.setState({ dragging: false });
192
- return;
193
- }
194
- this.setState({ uploading: true });
195
-
196
- readAsDataURL(files[0]).then((data) => {
197
- const fields = data.match(/^data:(.*);(.*),(.*)$/);
198
- this.props.createContent(
199
- getBaseUrl(this.props.pathname),
200
- {
201
- '@type': 'Image',
202
- title: files[0].name,
203
- image: {
204
- data: fields[3],
205
- encoding: fields[2],
206
- 'content-type': fields[1],
207
- filename: files[0].name,
208
- },
209
- },
210
- this.props.block,
211
- );
212
- });
213
- };
214
-
215
- /**
216
- * Keydown handler on Variant Menu Form
217
- * This is required since the ENTER key is already mapped to a onKeyDown
218
- * event and needs to be overriden with a child onKeyDown.
219
- * @method onKeyDownVariantMenuForm
220
- * @param {Object} e Event object
221
- * @returns {undefined}
222
- */
223
- onKeyDownVariantMenuForm = (e) => {
224
- if (e.key === 'Enter') {
225
- e.preventDefault();
226
- e.stopPropagation();
227
- this.onSubmitUrl();
228
- } else if (e.key === 'Escape') {
229
- e.preventDefault();
230
- e.stopPropagation();
231
- // TODO: Do something on ESC key
232
- }
233
- };
234
- onDragEnter = () => {
235
- this.setState({ dragging: true });
236
- };
237
- onDragLeave = () => {
238
- this.setState({ dragging: false });
239
- };
240
-
241
- node = React.createRef();
242
-
243
- /**
244
- * Render method.
245
- * @method render
246
- * @returns {string} Markup for the component.
247
- */
248
- render() {
249
- const Image = config.getComponent({ name: 'Image' }).component;
250
- const { data } = this.props;
251
- const placeholder =
252
- this.props.data.placeholder ||
253
- this.props.intl.formatMessage(messages.ImageBlockInputPlaceholder);
254
-
255
- return (
55
+ return (
56
+ <>
256
57
  <div
257
58
  className={cx(
258
59
  'block image align',
@@ -303,123 +104,20 @@ class Edit extends Component {
303
104
  responsive={true}
304
105
  />
305
106
  ) : (
306
- <div>
307
- {this.props.editable && (
308
- <Dropzone
309
- noClick
310
- onDrop={this.onDrop}
311
- onDragEnter={this.onDragEnter}
312
- onDragLeave={this.onDragLeave}
313
- className="dropzone"
314
- >
315
- {({ getRootProps, getInputProps }) => (
316
- <div {...getRootProps()}>
317
- <Message>
318
- {this.state.dragging && <Dimmer active></Dimmer>}
319
- {this.state.uploading && (
320
- <Dimmer active>
321
- <Loader indeterminate>
322
- {this.props.intl.formatMessage(
323
- messages.uploadingImage,
324
- )}
325
- </Loader>
326
- </Dimmer>
327
- )}
328
- <div className="no-image-wrapper">
329
- <img src={imageBlockSVG} alt="" />
330
- <div className="toolbar-inner">
331
- <Button.Group>
332
- <Button
333
- basic
334
- icon
335
- onClick={(e) => {
336
- e.stopPropagation();
337
- e.preventDefault();
338
- this.props.openObjectBrowser({
339
- onSelectItem: (
340
- url,
341
- { title, image_field, image_scales },
342
- ) => {
343
- this.props.onChangeBlock(this.props.block, {
344
- ...this.props.data,
345
- url,
346
- image_field,
347
- image_scales,
348
- alt: this.props.data.alt || title || '',
349
- });
350
- },
351
- });
352
- }}
353
- >
354
- <Icon name={navTreeSVG} size="24px" />
355
- </Button>
356
- </Button.Group>
357
- <Button.Group>
358
- <label className="ui button basic icon">
359
- <Icon name={uploadSVG} size="24px" />
360
- <input
361
- {...getInputProps({
362
- type: 'file',
363
- onChange: this.onUploadImage,
364
- style: { display: 'none' },
365
- })}
366
- />
367
- </label>
368
- </Button.Group>
369
- <Input
370
- onKeyDown={this.onKeyDownVariantMenuForm}
371
- onChange={this.onChangeUrl}
372
- placeholder={placeholder}
373
- value={this.state.url}
374
- onClick={(e) => {
375
- e.target.focus();
376
- }}
377
- onFocus={(e) => {
378
- this.props.onSelectBlock(this.props.id);
379
- }}
380
- />
381
- {this.state.url && (
382
- <Button.Group>
383
- <Button
384
- basic
385
- className="cancel"
386
- onClick={(e) => {
387
- e.stopPropagation();
388
- this.setState({ url: '' });
389
- }}
390
- >
391
- <Icon name={clearSVG} size="30px" />
392
- </Button>
393
- </Button.Group>
394
- )}
395
- <Button.Group>
396
- <Button
397
- basic
398
- primary
399
- disabled={!this.state.url}
400
- onClick={(e) => {
401
- e.stopPropagation();
402
- this.onSubmitUrl();
403
- }}
404
- >
405
- <Icon name={aheadSVG} size="30px" />
406
- </Button>
407
- </Button.Group>
408
- </div>
409
- </div>
410
- </Message>
411
- </div>
412
- )}
413
- </Dropzone>
414
- )}
415
- </div>
107
+ <ImageInput
108
+ onChange={handleChange}
109
+ placeholderLinkInput={data.placeholder}
110
+ block={props.block}
111
+ id={props.block}
112
+ objectBrowserPickerType={'image'}
113
+ />
416
114
  )}
417
- <SidebarPortal selected={this.props.selected}>
418
- <ImageSidebar {...this.props} />
115
+ <SidebarPortal selected={props.selected}>
116
+ <ImageSidebar {...props} />
419
117
  </SidebarPortal>
420
118
  </div>
421
- );
422
- }
119
+ </>
120
+ );
423
121
  }
424
122
 
425
123
  export default compose(
@@ -1,10 +1,18 @@
1
1
  import React from 'react';
2
+ import { useDispatch } from 'react-redux';
2
3
  import { defineMessages, useIntl } from 'react-intl';
3
4
  import { Button } from 'semantic-ui-react';
4
- import { Icon } from '@plone/volto/components';
5
+ import { toast } from 'react-toastify';
6
+ import { Icon, Toast } from '@plone/volto/components';
5
7
  import { BlockDataForm } from '@plone/volto/components/manage/Form';
8
+ import {
9
+ flattenToAppURL,
10
+ messages as defaultMessages,
11
+ } from '@plone/volto/helpers';
12
+ import { getContent } from '@plone/volto/actions';
6
13
  import { isEmpty } from 'lodash';
7
14
 
15
+ import reloadSVG from '@plone/volto/icons/reload.svg';
8
16
  import trashSVG from '@plone/volto/icons/delete.svg';
9
17
 
10
18
  const messages = defineMessages({
@@ -12,11 +20,20 @@ const messages = defineMessages({
12
20
  id: 'Reset the block',
13
21
  defaultMessage: 'Reset the block',
14
22
  },
23
+ refreshTeaser: {
24
+ id: 'Refresh source content',
25
+ defaultMessage: 'Refresh source content',
26
+ },
27
+ invalidTeaser: {
28
+ id: 'Invalid teaser source',
29
+ defaultMessage: 'Invalid teaser source',
30
+ },
15
31
  });
16
32
 
17
33
  const TeaserData = (props) => {
18
34
  const { block, blocksConfig, data, onChangeBlock, navRoot, contentType } =
19
35
  props;
36
+ const dispatch = useDispatch();
20
37
  const intl = useIntl();
21
38
 
22
39
  const reset = () => {
@@ -29,6 +46,64 @@ const TeaserData = (props) => {
29
46
  });
30
47
  };
31
48
 
49
+ const dataTransformer = (resp, data) => {
50
+ let hrefData = {
51
+ '@id': flattenToAppURL(resp['@id']),
52
+ '@type': resp?.['@type'],
53
+ Description: resp?.description,
54
+ Title: resp.title,
55
+ hasPreviewImage: resp?.preview_image ? true : false,
56
+ head_title: resp.head_title ?? null,
57
+ image_field: resp?.preview_image
58
+ ? 'preview_image'
59
+ : resp?.image
60
+ ? 'image'
61
+ : null,
62
+ image_scales: {
63
+ preview_image: [resp?.preview_image],
64
+ image: [resp?.image],
65
+ },
66
+ title: resp.title,
67
+ };
68
+ let blockData = {
69
+ '@type': data['@type'],
70
+ description: resp?.description,
71
+ head_title: resp?.head_title,
72
+ overwrite: data.overwrite,
73
+ href: [hrefData],
74
+ styles: data.styles,
75
+ title: resp.title,
76
+ };
77
+ return blockData;
78
+ };
79
+
80
+ const refresh = () => {
81
+ if (data.href?.[0]?.['@id']) {
82
+ dispatch(
83
+ getContent(
84
+ flattenToAppURL(data.href[0]['@id']),
85
+ null,
86
+ `${block}-teaser`,
87
+ ),
88
+ )
89
+ .then((resp) => {
90
+ if (resp) {
91
+ let blockData = dataTransformer(resp, data);
92
+ onChangeBlock(block, blockData);
93
+ }
94
+ })
95
+ .catch((e) => {
96
+ toast.error(
97
+ <Toast
98
+ error
99
+ title={props.intl.formatMessage(defaultMessages.error)}
100
+ content={props.intl.formatMessage(messages.invalidTeaser)}
101
+ />,
102
+ );
103
+ });
104
+ }
105
+ };
106
+
32
107
  const isReseteable =
33
108
  isEmpty(data.href) && !data.title && !data.description && !data.head_title;
34
109
 
@@ -45,7 +120,24 @@ const TeaserData = (props) => {
45
120
  </Button.Group>
46
121
  );
47
122
 
48
- const schema = blocksConfig[data['@type']].blockSchema({ intl });
123
+ const ActionButton = (
124
+ <Button.Group className="refresh teaser">
125
+ <Button
126
+ aria-label={intl.formatMessage(messages.refreshTeaser)}
127
+ basic
128
+ onClick={() => refresh()}
129
+ disabled={isEmpty(data.href)}
130
+ >
131
+ {intl.formatMessage(messages.refreshTeaser)}
132
+ <Icon name={reloadSVG} size="20px" color="#00000099" />
133
+ </Button>
134
+ </Button.Group>
135
+ );
136
+
137
+ const schema = blocksConfig[data['@type']].blockSchema({
138
+ data,
139
+ intl,
140
+ });
49
141
  const dataAdapter = blocksConfig[data['@type']].dataAdapter;
50
142
 
51
143
  return (
@@ -66,6 +158,7 @@ const TeaserData = (props) => {
66
158
  block={block}
67
159
  blocksConfig={blocksConfig}
68
160
  headerActions={HeaderActions}
161
+ actionButton={data.overwrite && ActionButton}
69
162
  navRoot={navRoot}
70
163
  contentType={contentType}
71
164
  />
@@ -34,16 +34,31 @@ const messages = defineMessages({
34
34
  id: 'Alignment',
35
35
  defaultMessage: 'Alignment',
36
36
  },
37
+ overwrite: {
38
+ id: 'Customize teaser content',
39
+ defaultMessage: 'Customize teaser content',
40
+ },
41
+ overwriteDescription: {
42
+ id: 'Check this box to customize the title, description, or image of the target content item for this teaser. Leave it unchecked to show updates to the target content item if it is edited later.',
43
+ defaultMessage:
44
+ 'Check this box to customize the title, description, or image of the target content item for this teaser. Leave it unchecked to show updates to the target content item if it is edited later.',
45
+ },
37
46
  });
38
47
 
39
- export const TeaserSchema = ({ intl }) => {
48
+ export const TeaserSchema = ({ data, intl }) => {
40
49
  const schema = {
41
50
  title: intl.formatMessage(messages.teaser),
42
51
  fieldsets: [
43
52
  {
44
53
  id: 'default',
45
54
  title: 'Default',
46
- fields: ['href', 'title', 'head_title', 'description', 'preview_image'],
55
+ fields: [
56
+ 'href',
57
+ 'overwrite',
58
+ ...(data?.overwrite
59
+ ? ['title', 'head_title', 'description', 'preview_image']
60
+ : []),
61
+ ],
47
62
  },
48
63
  ],
49
64
 
@@ -63,6 +78,12 @@ export const TeaserSchema = ({ intl }) => {
63
78
  ],
64
79
  allowExternals: true,
65
80
  },
81
+ overwrite: {
82
+ title: intl.formatMessage(messages.overwrite),
83
+ description: intl.formatMessage(messages.overwriteDescription),
84
+ type: 'boolean',
85
+ default: false,
86
+ },
66
87
  title: {
67
88
  title: intl.formatMessage(messages.title),
68
89
  },
@@ -85,7 +106,7 @@ export const TeaserSchema = ({ intl }) => {
85
106
  type: 'boolean',
86
107
  },
87
108
  },
88
- required: [],
109
+ required: ['href'],
89
110
  };
90
111
 
91
112
  addStyling({ schema, intl });
@@ -48,8 +48,10 @@ import {
48
48
  Button,
49
49
  Form,
50
50
  Input,
51
+ Loader,
51
52
  Segment,
52
53
  Table,
54
+ Dimmer,
53
55
  } from 'semantic-ui-react';
54
56
 
55
57
  /**
@@ -161,6 +163,12 @@ class GroupsControlpanel extends Component {
161
163
  ) {
162
164
  this.props.listGroups(this.state.search);
163
165
  }
166
+ if (
167
+ this.props.deleteGroupRequest.loading &&
168
+ nextProps.deleteGroupRequest.loaded
169
+ ) {
170
+ this.onDeleteGroupSuccess();
171
+ }
164
172
  if (
165
173
  this.props.createGroupRequest.loading &&
166
174
  nextProps.createGroupRequest.loaded
@@ -246,10 +254,6 @@ class GroupsControlpanel extends Component {
246
254
  onDeleteOk() {
247
255
  if (this.state.groupToDelete) {
248
256
  this.props.deleteGroup(this.state.groupToDelete.id);
249
- this.setState({
250
- showDelete: false,
251
- groupToDelete: undefined,
252
- });
253
257
  }
254
258
  }
255
259
 
@@ -262,6 +266,7 @@ class GroupsControlpanel extends Component {
262
266
  this.setState({
263
267
  showDelete: false,
264
268
  itemsToDelete: [],
269
+ groupToDelete: undefined,
265
270
  });
266
271
  }
267
272
 
@@ -367,6 +372,25 @@ class GroupsControlpanel extends Component {
367
372
  );
368
373
  }
369
374
 
375
+ /**
376
+ * Handle Success after deleteGroup()
377
+ *
378
+ * @returns {undefined}
379
+ */
380
+ onDeleteGroupSuccess() {
381
+ this.setState({
382
+ groupToDelete: undefined,
383
+ showDelete: false,
384
+ });
385
+ toast.success(
386
+ <Toast
387
+ success
388
+ title={this.props.intl.formatMessage(messages.success)}
389
+ content={this.props.intl.formatMessage(messages.groupDeleted)}
390
+ />,
391
+ );
392
+ }
393
+
370
394
  /**
371
395
  * On change page
372
396
  * @method onChangePage
@@ -409,6 +433,12 @@ class GroupsControlpanel extends Component {
409
433
  )}
410
434
  content={
411
435
  <div className="content">
436
+ <Dimmer active={this?.props?.deleteGroupRequest?.loading}>
437
+ <Loader>
438
+ <FormattedMessage id="Loading" defaultMessage="Loading." />
439
+ </Loader>
440
+ </Dimmer>
441
+
412
442
  <ul className="content">
413
443
  <FormattedMessage
414
444
  id="Do you really want to delete the group {groupname}?"