@plone/volto 18.0.0-alpha.40 → 18.0.0-alpha.42
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 +61 -0
- package/locales/ca/LC_MESSAGES/volto.po +31 -1
- package/locales/ca.json +1 -1
- package/locales/de/LC_MESSAGES/volto.po +31 -1
- package/locales/de.json +1 -1
- package/locales/en/LC_MESSAGES/volto.po +31 -1
- package/locales/en.json +1 -1
- package/locales/es/LC_MESSAGES/volto.po +31 -1
- package/locales/es.json +1 -1
- package/locales/eu/LC_MESSAGES/volto.po +31 -1
- package/locales/eu.json +1 -1
- package/locales/fi/LC_MESSAGES/volto.po +31 -1
- package/locales/fi.json +1 -1
- package/locales/fr/LC_MESSAGES/volto.po +31 -1
- package/locales/fr.json +1 -1
- package/locales/hi/LC_MESSAGES/volto.po +31 -1
- package/locales/hi.json +1 -1
- package/locales/it/LC_MESSAGES/volto.po +31 -1
- package/locales/it.json +1 -1
- package/locales/ja/LC_MESSAGES/volto.po +31 -1
- package/locales/ja.json +1 -1
- package/locales/nl/LC_MESSAGES/volto.po +31 -1
- package/locales/nl.json +1 -1
- package/locales/pt/LC_MESSAGES/volto.po +31 -1
- package/locales/pt.json +1 -1
- package/locales/pt_BR/LC_MESSAGES/volto.po +31 -1
- package/locales/pt_BR.json +1 -1
- package/locales/ro/LC_MESSAGES/volto.po +31 -1
- package/locales/ro.json +1 -1
- package/locales/volto.pot +32 -2
- package/locales/zh_CN/LC_MESSAGES/volto.po +31 -1
- package/locales/zh_CN.json +1 -1
- package/package.json +4 -5
- package/razzle.config.js +2 -2
- package/src/components/index.js +0 -1
- package/src/components/manage/AnchorPlugin/components/LinkButton/AddLinkForm.jsx +1 -1
- package/src/components/manage/AnchorPlugin/useLinkEditor.jsx +21 -21
- package/src/components/manage/Blocks/Block/BlocksForm.jsx +5 -0
- package/src/components/manage/Blocks/Block/Order/Item.jsx +6 -2
- package/src/components/manage/Blocks/Block/Order/Order.jsx +2 -0
- package/src/components/manage/Blocks/Container/Data.jsx +10 -2
- package/src/components/manage/Blocks/Image/ImageSidebar.jsx +10 -2
- package/src/components/manage/Blocks/Listing/ListingData.jsx +10 -2
- package/src/components/manage/Blocks/Maps/MapsSidebar.jsx +3 -1
- package/src/components/manage/Blocks/Search/SearchBlockEdit.jsx +2 -0
- package/src/components/manage/Blocks/Search/SearchBlockView.jsx +18 -2
- package/src/components/manage/Blocks/Search/hocs/withSearch.jsx +1 -1
- package/src/components/manage/Blocks/Teaser/Data.jsx +10 -2
- package/src/components/manage/Blocks/ToC/Edit.jsx +1 -0
- package/src/components/manage/Blocks/Video/Edit.jsx +1 -1
- package/src/components/manage/Blocks/Video/VideoSidebar.jsx +3 -1
- package/src/components/manage/Contents/Contents.jsx +1 -1
- package/src/components/manage/Controlpanels/ContentTypeSchema.jsx +1 -0
- package/src/components/manage/Controlpanels/UndoControlpanel.jsx +3 -3
- package/src/components/manage/Controlpanels/Users/UserGroupMembershipListing.jsx +28 -12
- package/src/components/manage/Controlpanels/Users/UserGroupMembershipMatrix.jsx +12 -4
- package/src/components/manage/Form/Form.jsx +85 -20
- package/src/components/manage/Form/InlineForm.jsx +4 -6
- package/src/components/manage/Form/ModalForm.jsx +1 -1
- package/src/components/manage/History/History.jsx +1 -1
- package/src/components/manage/Pluggable/Pluggable.test.js +1 -1
- package/src/components/manage/Toolbar/Toolbar.jsx +1 -1
- package/src/components/manage/Widgets/ArrayWidget.jsx +2 -2
- package/src/components/manage/Widgets/ImageWidget.jsx +34 -10
- package/src/components/manage/Widgets/RecurrenceWidget/EndField.jsx +7 -1
- package/src/components/manage/Widgets/RecurrenceWidget/RecurrenceWidget.jsx +80 -31
- package/src/components/theme/Login/Login.jsx +25 -4
- package/src/components/theme/Logout/Logout.jsx +2 -2
- package/src/components/theme/Search/Search.jsx +13 -5
- package/src/components/theme/View/View.jsx +0 -7
- package/src/components/theme/View/View.test.jsx +0 -3
- package/src/config/Widgets.jsx +1 -1
- package/src/config/index.js +7 -2
- package/src/config/validation.ts +155 -0
- package/src/helpers/Extensions/withBlockExtensions.jsx +1 -1
- package/src/helpers/FormValidation/FormValidation.jsx +109 -170
- package/src/helpers/FormValidation/FormValidation.test.js +836 -8
- package/src/helpers/FormValidation/validators.ts +203 -0
- package/src/helpers/MessageLabels/MessageLabels.js +28 -0
- package/src/helpers/Url/Url.test.js +4 -4
- package/src/helpers/User/User.js +1 -1
- package/src/hooks/client/useClient.js +1 -1
- package/test-setup-config.jsx +7 -0
- package/theme/themes/default/modules/embed.variables +1 -1
- package/theme/themes/pastanaga/collections/form.overrides +36 -2
- package/theme/themes/pastanaga/extras/blocks.less +14 -5
- package/theme/themes/pastanaga/extras/sidebar.less +4 -0
- package/theme/themes/pastanaga/extras/toolbar.less +10 -3
- package/tsconfig.declarations.json +3 -2
- package/types/components/index.d.ts +0 -1
- package/types/components/manage/Blocks/Block/Order/Order.d.ts +2 -1
- package/types/components/theme/Logout/Logout.d.ts +1 -1
- package/types/config/RichTextEditor/ToHTML.d.ts +1 -1
- package/types/config/Widgets.d.ts +2 -2
- package/types/config/validation.d.ts +3 -0
- package/types/helpers/Extensions/withBlockExtensions.d.ts +1 -1
- package/types/helpers/FormValidation/FormValidation.d.ts +1 -0
- package/types/helpers/FormValidation/validators.d.ts +29 -0
- package/types/helpers/MessageLabels/MessageLabels.d.ts +36 -0
- package/types/helpers/User/User.d.ts +1 -1
- package/src/components/theme/SocialSharing/SocialSharing.jsx +0 -48
- package/src/components/theme/SocialSharing/SocialSharing.test.jsx +0 -14
|
@@ -49,10 +49,26 @@ const applyDefaults = (data, root) => {
|
|
|
49
49
|
v: root || '/',
|
|
50
50
|
},
|
|
51
51
|
];
|
|
52
|
+
|
|
53
|
+
const searchBySearchableText = data.query.filter(
|
|
54
|
+
(item) => item['i'] === 'SearchableText',
|
|
55
|
+
).length;
|
|
56
|
+
|
|
57
|
+
const sort_on = data?.sort_on
|
|
58
|
+
? { sort_on: data.sort_on }
|
|
59
|
+
: searchBySearchableText === 0
|
|
60
|
+
? { sort_on: 'effective' }
|
|
61
|
+
: {};
|
|
62
|
+
const sort_order = data?.sort_order
|
|
63
|
+
? { sort_order: data.sort_order }
|
|
64
|
+
: searchBySearchableText === 0
|
|
65
|
+
? { sort_order: 'descending' }
|
|
66
|
+
: {};
|
|
67
|
+
|
|
52
68
|
return {
|
|
53
69
|
...data,
|
|
54
|
-
sort_on
|
|
55
|
-
sort_order
|
|
70
|
+
...sort_on,
|
|
71
|
+
...sort_order,
|
|
56
72
|
query: data?.query?.length ? data.query : defaultQuery,
|
|
57
73
|
};
|
|
58
74
|
};
|
|
@@ -321,7 +321,7 @@ const withSearch = (options) => (WrappedComponent) => {
|
|
|
321
321
|
const previousUrlQuery = usePrevious(urlQuery);
|
|
322
322
|
|
|
323
323
|
// During first render the previousUrlQuery is undefined and urlQuery
|
|
324
|
-
// is empty so it
|
|
324
|
+
// is empty so it resetting the facet when you are navigating but during reload we have urlQuery and we need
|
|
325
325
|
// to set the facet at first render.
|
|
326
326
|
const preventOverrideOfFacetState =
|
|
327
327
|
previousUrlQuery === undefined && urlQuery.length === 0;
|
|
@@ -31,8 +31,15 @@ const messages = defineMessages({
|
|
|
31
31
|
});
|
|
32
32
|
|
|
33
33
|
const TeaserData = (props) => {
|
|
34
|
-
const {
|
|
35
|
-
|
|
34
|
+
const {
|
|
35
|
+
block,
|
|
36
|
+
blocksConfig,
|
|
37
|
+
blocksErrors,
|
|
38
|
+
data,
|
|
39
|
+
onChangeBlock,
|
|
40
|
+
navRoot,
|
|
41
|
+
contentType,
|
|
42
|
+
} = props;
|
|
36
43
|
const dispatch = useDispatch();
|
|
37
44
|
const intl = useIntl();
|
|
38
45
|
|
|
@@ -161,6 +168,7 @@ const TeaserData = (props) => {
|
|
|
161
168
|
actionButton={data.overwrite && ActionButton}
|
|
162
169
|
navRoot={navRoot}
|
|
163
170
|
contentType={contentType}
|
|
171
|
+
errors={blocksErrors}
|
|
164
172
|
/>
|
|
165
173
|
);
|
|
166
174
|
};
|
|
@@ -116,7 +116,7 @@ class Edit extends Component {
|
|
|
116
116
|
/**
|
|
117
117
|
* Keydown handler on Variant Menu Form
|
|
118
118
|
* This is required since the ENTER key is already mapped to a onKeyDown
|
|
119
|
-
* event and needs to be
|
|
119
|
+
* event and needs to be overridden with a child onKeyDown.
|
|
120
120
|
* @method onKeyDownVariantMenuForm
|
|
121
121
|
* @param {Object} e Event object
|
|
122
122
|
* @returns {undefined}
|
|
@@ -18,7 +18,8 @@ const messages = defineMessages({
|
|
|
18
18
|
});
|
|
19
19
|
|
|
20
20
|
const VideoSidebar = (props) => {
|
|
21
|
-
const { data, block, onChangeBlock, navRoot, contentType } =
|
|
21
|
+
const { data, block, blocksErrors, onChangeBlock, navRoot, contentType } =
|
|
22
|
+
props;
|
|
22
23
|
const intl = useIntl();
|
|
23
24
|
const schema = VideoBlockSchema({ ...props, intl });
|
|
24
25
|
|
|
@@ -44,6 +45,7 @@ const VideoSidebar = (props) => {
|
|
|
44
45
|
block={block}
|
|
45
46
|
navRoot={navRoot}
|
|
46
47
|
contentType={contentType}
|
|
48
|
+
errors={blocksErrors}
|
|
47
49
|
/>
|
|
48
50
|
)}
|
|
49
51
|
</>
|
|
@@ -156,7 +156,7 @@ const messages = defineMessages({
|
|
|
156
156
|
defaultMessage: 'Item(s) has been updated.',
|
|
157
157
|
},
|
|
158
158
|
messageReorder: {
|
|
159
|
-
id: 'Item
|
|
159
|
+
id: 'Item successfully moved.',
|
|
160
160
|
defaultMessage: 'Item successfully moved.',
|
|
161
161
|
},
|
|
162
162
|
messagePasted: {
|
|
@@ -389,7 +389,7 @@ class UndoControlpanel extends Component {
|
|
|
389
389
|
}
|
|
390
390
|
|
|
391
391
|
/**
|
|
392
|
-
* Handle next and prev buttons
|
|
392
|
+
* Handle next and prev buttons visibility when transactions are sorted
|
|
393
393
|
* @method handleSortedNextPrevButtons
|
|
394
394
|
* @returns {undefined}
|
|
395
395
|
*/
|
|
@@ -412,7 +412,7 @@ class UndoControlpanel extends Component {
|
|
|
412
412
|
}
|
|
413
413
|
|
|
414
414
|
/**
|
|
415
|
-
* Handle next and prev buttons
|
|
415
|
+
* Handle next and prev buttons visibility when transactions are not sorted
|
|
416
416
|
* @method handleNotSortedNextPrevButtons
|
|
417
417
|
* @returns {undefined}
|
|
418
418
|
*/
|
|
@@ -435,7 +435,7 @@ class UndoControlpanel extends Component {
|
|
|
435
435
|
}
|
|
436
436
|
|
|
437
437
|
/**
|
|
438
|
-
* Handle next, prev buttons and table
|
|
438
|
+
* Handle next, prev buttons and table visibility
|
|
439
439
|
* @method handleTableVisiblity
|
|
440
440
|
* @returns {undefined}
|
|
441
441
|
*/
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import React, { useEffect, useState } from 'react';
|
|
2
|
-
import { cloneDeep, uniqBy } from 'lodash';
|
|
1
|
+
import React, { useEffect, useState, useMemo } from 'react';
|
|
2
|
+
import { cloneDeep, uniqBy, debounce } from 'lodash';
|
|
3
3
|
import { useIntl } from 'react-intl';
|
|
4
4
|
import { useSelector, useDispatch, shallowEqual } from 'react-redux';
|
|
5
5
|
import jwtDecode from 'jwt-decode';
|
|
@@ -114,25 +114,41 @@ const ListingTemplate = ({
|
|
|
114
114
|
matrix_options = [];
|
|
115
115
|
}
|
|
116
116
|
|
|
117
|
+
const debouncedListUsers = useMemo(
|
|
118
|
+
() =>
|
|
119
|
+
debounce((query_user, groups_filter, userLimit) => {
|
|
120
|
+
dispatch(
|
|
121
|
+
listUsers({
|
|
122
|
+
search: query_user,
|
|
123
|
+
groups_filter: groups_filter.map((el) => el.value),
|
|
124
|
+
limit: userLimit,
|
|
125
|
+
}),
|
|
126
|
+
);
|
|
127
|
+
}, 300),
|
|
128
|
+
[dispatch],
|
|
129
|
+
);
|
|
130
|
+
|
|
117
131
|
useEffect(() => {
|
|
118
132
|
// Get users.
|
|
119
133
|
if (show_users) {
|
|
120
|
-
|
|
121
|
-
listUsers({
|
|
122
|
-
search: query_user,
|
|
123
|
-
groups_filter: groups_filter.map((el) => el.value),
|
|
124
|
-
limit: userLimit,
|
|
125
|
-
}),
|
|
126
|
-
);
|
|
134
|
+
debouncedListUsers(query_user, groups_filter, userLimit);
|
|
127
135
|
}
|
|
128
|
-
}, [
|
|
136
|
+
}, [debouncedListUsers, query_user, groups_filter, show_users, userLimit]);
|
|
137
|
+
|
|
138
|
+
const debouncedListGroups = useMemo(
|
|
139
|
+
() =>
|
|
140
|
+
debounce((query_group) => {
|
|
141
|
+
dispatch(listGroups(query_group));
|
|
142
|
+
}, 300),
|
|
143
|
+
[dispatch],
|
|
144
|
+
);
|
|
129
145
|
|
|
130
146
|
useEffect(() => {
|
|
131
147
|
// Get matrix groups.
|
|
132
148
|
if (show_matrix_options) {
|
|
133
|
-
|
|
149
|
+
debouncedListGroups(query_group);
|
|
134
150
|
}
|
|
135
|
-
}, [
|
|
151
|
+
}, [debouncedListGroups, query_group, show_matrix_options]);
|
|
136
152
|
|
|
137
153
|
const onSelectOptionHandler = (selectedvalue, checked, singleClick) => {
|
|
138
154
|
singleClick = singleClick ?? false;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import React, { useEffect, useState } from 'react';
|
|
1
|
+
import React, { useEffect, useState, useMemo } from 'react';
|
|
2
2
|
import { useSelector, useDispatch } from 'react-redux';
|
|
3
3
|
import { useIntl } from 'react-intl';
|
|
4
4
|
import { Checkbox, Form, Input } from 'semantic-ui-react';
|
|
5
5
|
|
|
6
|
-
import { isEqual } from 'lodash';
|
|
6
|
+
import { isEqual, debounce } from 'lodash';
|
|
7
7
|
|
|
8
8
|
import { messages } from '@plone/volto/helpers';
|
|
9
9
|
import { listGroups } from '@plone/volto/actions'; // getRegistry
|
|
@@ -38,12 +38,20 @@ const UserGroupMembershipMatrix = ({ many_users, many_groups }) => {
|
|
|
38
38
|
});
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
+
const debouncedListGroups = useMemo(
|
|
42
|
+
() =>
|
|
43
|
+
debounce((query_group_filter) => {
|
|
44
|
+
dispatch(listGroups('', query_group_filter));
|
|
45
|
+
}, 300),
|
|
46
|
+
[dispatch],
|
|
47
|
+
);
|
|
48
|
+
|
|
41
49
|
useEffect(() => {
|
|
42
50
|
// TODO fetch group for at least query_group_filter.length > 1?
|
|
43
51
|
if (!many_groups || (many_groups && query_group_filter.length > 1)) {
|
|
44
|
-
|
|
52
|
+
debouncedListGroups(query_group_filter);
|
|
45
53
|
}
|
|
46
|
-
}, [
|
|
54
|
+
}, [debouncedListGroups, many_groups, query_group_filter]);
|
|
47
55
|
|
|
48
56
|
const onReset = (event) => {
|
|
49
57
|
// event.preventDefault();
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
FormValidation,
|
|
13
13
|
getBlocksFieldname,
|
|
14
14
|
getBlocksLayoutFieldname,
|
|
15
|
+
hasBlocksData,
|
|
15
16
|
messages,
|
|
16
17
|
} from '@plone/volto/helpers';
|
|
17
18
|
import aheadSVG from '@plone/volto/icons/ahead.svg';
|
|
@@ -349,7 +350,7 @@ class Form extends Component {
|
|
|
349
350
|
|
|
350
351
|
/**
|
|
351
352
|
* If user clicks on input, the form will be not considered pristine
|
|
352
|
-
* this will avoid onBlur effects without
|
|
353
|
+
* this will avoid onBlur effects without interaction with the form
|
|
353
354
|
* @param {Object} e event
|
|
354
355
|
*/
|
|
355
356
|
onClickInput(e) {
|
|
@@ -527,30 +528,92 @@ class Form extends Component {
|
|
|
527
528
|
})
|
|
528
529
|
: {};
|
|
529
530
|
|
|
530
|
-
|
|
531
|
+
let blocksErrors = {};
|
|
532
|
+
|
|
533
|
+
if (hasBlocksData(formData)) {
|
|
534
|
+
// Validate blocks
|
|
535
|
+
const blocks = this.state.formData[getBlocksFieldname(formData)];
|
|
536
|
+
const blocksLayout =
|
|
537
|
+
this.state.formData[getBlocksLayoutFieldname(formData)];
|
|
538
|
+
const defaultSchema = {
|
|
539
|
+
properties: {},
|
|
540
|
+
fieldsets: [],
|
|
541
|
+
required: [],
|
|
542
|
+
};
|
|
543
|
+
blocksLayout.items.forEach((block) => {
|
|
544
|
+
let blockSchema =
|
|
545
|
+
config.blocks.blocksConfig[blocks[block]['@type']].blockSchema ||
|
|
546
|
+
defaultSchema;
|
|
547
|
+
if (typeof blockSchema === 'function') {
|
|
548
|
+
blockSchema = blockSchema({
|
|
549
|
+
intl: this.props.intl,
|
|
550
|
+
formData: blocks[block],
|
|
551
|
+
});
|
|
552
|
+
}
|
|
553
|
+
const blockErrors = FormValidation.validateFieldsPerFieldset({
|
|
554
|
+
schema: blockSchema,
|
|
555
|
+
formData: blocks[block],
|
|
556
|
+
formatMessage: this.props.intl.formatMessage,
|
|
557
|
+
});
|
|
558
|
+
if (keys(blockErrors).length > 0) {
|
|
559
|
+
blocksErrors = {
|
|
560
|
+
...blocksErrors,
|
|
561
|
+
[block]: { ...blockErrors },
|
|
562
|
+
};
|
|
563
|
+
}
|
|
564
|
+
});
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
if (keys(errors).length > 0 || keys(blocksErrors).length > 0) {
|
|
531
568
|
const activeIndex = FormValidation.showFirstTabWithErrors({
|
|
532
569
|
errors,
|
|
533
570
|
schema: this.props.schema,
|
|
534
571
|
});
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
() => {
|
|
541
|
-
Object.keys(errors).forEach((err) =>
|
|
542
|
-
toast.error(
|
|
543
|
-
<Toast
|
|
544
|
-
error
|
|
545
|
-
title={this.props.schema.properties[err].title || err}
|
|
546
|
-
content={errors[err].join(', ')}
|
|
547
|
-
/>,
|
|
548
|
-
),
|
|
549
|
-
);
|
|
572
|
+
|
|
573
|
+
this.setState({
|
|
574
|
+
errors: {
|
|
575
|
+
...errors,
|
|
576
|
+
...(!isEmpty(blocksErrors) && { blocks: blocksErrors }),
|
|
550
577
|
},
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
578
|
+
activeIndex,
|
|
579
|
+
});
|
|
580
|
+
|
|
581
|
+
if (keys(errors).length > 0) {
|
|
582
|
+
// Changes the focus to the metadata tab in the sidebar if error
|
|
583
|
+
Object.keys(errors).forEach((err) =>
|
|
584
|
+
toast.error(
|
|
585
|
+
<Toast
|
|
586
|
+
error
|
|
587
|
+
title={this.props.schema.properties[err].title || err}
|
|
588
|
+
content={errors[err].join(', ')}
|
|
589
|
+
/>,
|
|
590
|
+
),
|
|
591
|
+
);
|
|
592
|
+
this.props.setSidebarTab(0);
|
|
593
|
+
} else if (keys(blocksErrors).length > 0) {
|
|
594
|
+
const errorField = Object.entries(
|
|
595
|
+
Object.entries(blocksErrors)[0][1],
|
|
596
|
+
)[0][0];
|
|
597
|
+
const errorMessage = Object.entries(
|
|
598
|
+
Object.entries(blocksErrors)[0][1],
|
|
599
|
+
)[0][1];
|
|
600
|
+
toast.error(
|
|
601
|
+
<Toast
|
|
602
|
+
error
|
|
603
|
+
title={this.props.intl.formatMessage(
|
|
604
|
+
messages.blocksFieldsErrorTitle,
|
|
605
|
+
{ errorField },
|
|
606
|
+
)}
|
|
607
|
+
content={errorMessage}
|
|
608
|
+
/>,
|
|
609
|
+
);
|
|
610
|
+
this.props.setSidebarTab(1);
|
|
611
|
+
this.props.setUIState({
|
|
612
|
+
selected: Object.keys(blocksErrors)[0],
|
|
613
|
+
multiSelected: [],
|
|
614
|
+
hovered: null,
|
|
615
|
+
});
|
|
616
|
+
}
|
|
554
617
|
} else {
|
|
555
618
|
// Get only the values that have been modified (Edit forms), send all in case that
|
|
556
619
|
// it's an add form
|
|
@@ -730,6 +793,8 @@ class Form extends Component {
|
|
|
730
793
|
history={this.props.history}
|
|
731
794
|
location={this.props.location}
|
|
732
795
|
token={this.props.token}
|
|
796
|
+
errors={this.state.errors}
|
|
797
|
+
blocksErrors={this.state.errors.blocks}
|
|
733
798
|
/>
|
|
734
799
|
{this.state.isClient &&
|
|
735
800
|
this.state.sidebarMetadataIsAvailable &&
|
|
@@ -142,7 +142,6 @@ const InlineForm = (props) => {
|
|
|
142
142
|
content={error.message}
|
|
143
143
|
/>
|
|
144
144
|
)}
|
|
145
|
-
|
|
146
145
|
<div id={`blockform-fieldset-${defaultFieldset.id}`}>
|
|
147
146
|
<Segment className="form attached">
|
|
148
147
|
{map(defaultFieldset.fields, (field, index) => (
|
|
@@ -153,11 +152,11 @@ const InlineForm = (props) => {
|
|
|
153
152
|
focus={index === focusIndex}
|
|
154
153
|
value={formData[field]}
|
|
155
154
|
required={schema.required.indexOf(field) !== -1}
|
|
156
|
-
onChange={(id, value) => {
|
|
157
|
-
onChangeField(id, value);
|
|
155
|
+
onChange={(id, value, itemInfo) => {
|
|
156
|
+
onChangeField(id, value, itemInfo);
|
|
158
157
|
}}
|
|
159
158
|
key={field}
|
|
160
|
-
error={errors[field]}
|
|
159
|
+
error={errors?.[block]?.[field] || {}}
|
|
161
160
|
block={block}
|
|
162
161
|
/>
|
|
163
162
|
))}
|
|
@@ -166,7 +165,6 @@ const InlineForm = (props) => {
|
|
|
166
165
|
)}
|
|
167
166
|
</Segment>
|
|
168
167
|
</div>
|
|
169
|
-
|
|
170
168
|
{other.map((fieldset, index) => (
|
|
171
169
|
<Accordion fluid styled className="form" key={fieldset.id}>
|
|
172
170
|
<div key={fieldset.id} id={`blockform-fieldset-${fieldset.id}`}>
|
|
@@ -199,7 +197,7 @@ const InlineForm = (props) => {
|
|
|
199
197
|
onChangeField(id, value);
|
|
200
198
|
}}
|
|
201
199
|
key={field}
|
|
202
|
-
error={errors[field]}
|
|
200
|
+
error={errors?.[block]?.[field] || {}}
|
|
203
201
|
block={block}
|
|
204
202
|
/>
|
|
205
203
|
))}
|
|
@@ -137,7 +137,7 @@ class ModalForm extends Component {
|
|
|
137
137
|
|
|
138
138
|
/**
|
|
139
139
|
* If user clicks on input, the form will be not considered pristine
|
|
140
|
-
* this will avoid onBlur effects without
|
|
140
|
+
* this will avoid onBlur effects without interaction with the form
|
|
141
141
|
* @param {Object} e event
|
|
142
142
|
*/
|
|
143
143
|
onClickInput(e) {
|
|
@@ -345,7 +345,7 @@ export default compose(
|
|
|
345
345
|
asyncConnect([
|
|
346
346
|
{
|
|
347
347
|
key: 'actions',
|
|
348
|
-
// Dispatch async/await to make the operation
|
|
348
|
+
// Dispatch async/await to make the operation synchronous, otherwise it returns
|
|
349
349
|
// before the promise is resolved
|
|
350
350
|
promise: async ({ location, store: { dispatch } }) =>
|
|
351
351
|
await dispatch(listActions(getBaseUrl(location.pathname))),
|
|
@@ -623,7 +623,7 @@ class Toolbar extends Component {
|
|
|
623
623
|
aria-label={this.props.intl.formatMessage(
|
|
624
624
|
messages.shrinkToolbar,
|
|
625
625
|
)}
|
|
626
|
-
className={cx({
|
|
626
|
+
className={cx('toolbar-handler-button', {
|
|
627
627
|
[this.props.content?.review_state]:
|
|
628
628
|
this.props.content?.review_state,
|
|
629
629
|
})}
|
|
@@ -129,7 +129,7 @@ const compareOption = (inputValue = '', option, accessors) => {
|
|
|
129
129
|
* @class ArrayWidget
|
|
130
130
|
* @extends Component
|
|
131
131
|
*
|
|
132
|
-
* A
|
|
132
|
+
* A creatable select array widget will be rendered if the named vocabulary is
|
|
133
133
|
* in the widget definition (hint) like:
|
|
134
134
|
*
|
|
135
135
|
* ```
|
|
@@ -280,7 +280,7 @@ class ArrayWidget extends Component {
|
|
|
280
280
|
const { SortableContainer } = this.props.reactSortableHOC;
|
|
281
281
|
const Select = this.props.reactSelect.default;
|
|
282
282
|
const SortableSelect =
|
|
283
|
-
// It will be only
|
|
283
|
+
// It will be only creatable if the named vocabulary is in the widget definition
|
|
284
284
|
// (hint) like:
|
|
285
285
|
// list_field_voc_unconstrained = schema.List(
|
|
286
286
|
// title=u"List field with values from vocabulary but not constrained to them.",
|
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
getBaseUrl,
|
|
15
15
|
isInternalURL,
|
|
16
16
|
validateFileUploadSize,
|
|
17
|
+
usePrevious,
|
|
17
18
|
} from '@plone/volto/helpers';
|
|
18
19
|
import { createContent } from '@plone/volto/actions';
|
|
19
20
|
import { readAsDataURL } from 'promise-file-reader';
|
|
@@ -77,6 +78,7 @@ const UnconnectedImageInput = (props) => {
|
|
|
77
78
|
placeholderLinkInput = '',
|
|
78
79
|
onSelectItem,
|
|
79
80
|
} = props;
|
|
81
|
+
const imageValue = value?.[0]?.['@id'] || value;
|
|
80
82
|
|
|
81
83
|
const intl = useIntl();
|
|
82
84
|
const linkEditor = useLinkEditor();
|
|
@@ -91,13 +93,14 @@ const UnconnectedImageInput = (props) => {
|
|
|
91
93
|
|
|
92
94
|
const requestId = `image-upload-${id}`;
|
|
93
95
|
|
|
94
|
-
const
|
|
96
|
+
const loaded = props.request.loaded;
|
|
95
97
|
const { content } = props;
|
|
96
98
|
const imageId = content?.['@id'];
|
|
97
99
|
const image = content?.image;
|
|
100
|
+
let loading = false;
|
|
98
101
|
|
|
99
102
|
useEffect(() => {
|
|
100
|
-
if (uploading &&
|
|
103
|
+
if (uploading && loading && loaded) {
|
|
101
104
|
setUploading(false);
|
|
102
105
|
onChange(id, imageId, {
|
|
103
106
|
image_field: 'image',
|
|
@@ -106,6 +109,8 @@ const UnconnectedImageInput = (props) => {
|
|
|
106
109
|
}
|
|
107
110
|
}, [loading, loaded, uploading, imageId, image, id, onChange]); // Explicitly list all dependencies
|
|
108
111
|
|
|
112
|
+
loading = usePrevious(props.request?.loading);
|
|
113
|
+
|
|
109
114
|
const handleUpload = React.useCallback(
|
|
110
115
|
(eventOrFile) => {
|
|
111
116
|
if (restrictFileUpload === true) return;
|
|
@@ -151,7 +156,7 @@ const UnconnectedImageInput = (props) => {
|
|
|
151
156
|
}, [restrictFileUpload]);
|
|
152
157
|
const onDragLeave = React.useCallback(() => setDragging(false), []);
|
|
153
158
|
|
|
154
|
-
return
|
|
159
|
+
return imageValue ? (
|
|
155
160
|
<div
|
|
156
161
|
className="image-upload-widget-image"
|
|
157
162
|
onClick={onFocus}
|
|
@@ -161,7 +166,11 @@ const UnconnectedImageInput = (props) => {
|
|
|
161
166
|
{selected && <ImageToolbar {...props} />}
|
|
162
167
|
<img
|
|
163
168
|
className={props.className}
|
|
164
|
-
src={
|
|
169
|
+
src={
|
|
170
|
+
isInternalURL(imageValue)
|
|
171
|
+
? `${flattenToAppURL(imageValue)}/@@images/image/${imageSize}`
|
|
172
|
+
: imageValue
|
|
173
|
+
}
|
|
165
174
|
alt=""
|
|
166
175
|
/>
|
|
167
176
|
</div>
|
|
@@ -264,7 +273,7 @@ const UnconnectedImageInput = (props) => {
|
|
|
264
273
|
</div>
|
|
265
274
|
{linkEditor.anchorNode && (
|
|
266
275
|
<linkEditor.LinkEditor
|
|
267
|
-
value={
|
|
276
|
+
value={imageValue}
|
|
268
277
|
placeholder={
|
|
269
278
|
placeholderLinkInput ||
|
|
270
279
|
intl.formatMessage(messages.linkAnImage)
|
|
@@ -302,10 +311,25 @@ export const ImageInput = compose(
|
|
|
302
311
|
),
|
|
303
312
|
)(withObjectBrowser(UnconnectedImageInput));
|
|
304
313
|
|
|
305
|
-
const ImageUploadWidget = (props) =>
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
314
|
+
const ImageUploadWidget = (props) => {
|
|
315
|
+
const { fieldSet, id, title } = props;
|
|
316
|
+
return (
|
|
317
|
+
<FormFieldWrapper
|
|
318
|
+
{...props}
|
|
319
|
+
columns={1}
|
|
320
|
+
className="block image-upload-widget"
|
|
321
|
+
>
|
|
322
|
+
<div className="wrapper">
|
|
323
|
+
<label
|
|
324
|
+
id={`fieldset-${fieldSet}-field-label-${id}`}
|
|
325
|
+
htmlFor={`field-${id}`}
|
|
326
|
+
>
|
|
327
|
+
{title}
|
|
328
|
+
</label>
|
|
329
|
+
</div>
|
|
330
|
+
<ImageInput {...props} />
|
|
331
|
+
</FormFieldWrapper>
|
|
332
|
+
);
|
|
333
|
+
};
|
|
310
334
|
|
|
311
335
|
export default ImageUploadWidget;
|
|
@@ -81,7 +81,13 @@ const EndField = ({ value, count, until, onChange, intl }) => {
|
|
|
81
81
|
id="until"
|
|
82
82
|
title={intl.formatMessage(messages.recurrenceEndsUntil)}
|
|
83
83
|
dateOnly={true}
|
|
84
|
-
value={
|
|
84
|
+
value={
|
|
85
|
+
until
|
|
86
|
+
? typeof until === 'string'
|
|
87
|
+
? until
|
|
88
|
+
: until?.toISOString()
|
|
89
|
+
: ''
|
|
90
|
+
}
|
|
85
91
|
resettable={false}
|
|
86
92
|
onChange={(id, value) => {
|
|
87
93
|
onChange(id, value === '' ? undefined : value);
|