@plone/volto 14.0.0-alpha.33 → 14.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.
- package/CHANGELOG.md +44 -0
- package/README.md +4 -1
- package/package.json +1 -1
- package/src/actions/schema/schema.js +4 -2
- package/src/actions/schema/schema.test.js +10 -0
- package/src/actions/vocabularies/vocabularies.js +3 -10
- package/src/actions/vocabularies/vocabularies.test.js +26 -0
- package/src/components/manage/Add/Add.jsx +1 -1
- package/src/components/manage/Blocks/Block/Edit.jsx +6 -1
- package/src/components/manage/Blocks/HeroImageLeft/schema.js +1 -1
- package/src/components/manage/Blocks/Listing/schema.js +2 -0
- package/src/components/manage/Edit/Edit.jsx +4 -1
- package/src/components/manage/Sidebar/ObjectBrowserBody.jsx +1 -1
- package/src/components/manage/Sidebar/SidebarPopup.stories.jsx +42 -0
- package/src/components/manage/Widgets/AlignWidget.jsx +14 -6
- package/src/components/manage/Widgets/AlignWidget.stories.jsx +38 -0
- package/src/components/manage/Widgets/ArrayWidget.stories.jsx +2 -2
- package/src/components/manage/Widgets/CheckboxWidget.jsx +9 -0
- package/src/components/manage/Widgets/CheckboxWidget.stories.jsx +38 -0
- package/src/components/manage/Widgets/DatetimeWidget.jsx +9 -0
- package/src/components/manage/Widgets/DatetimeWidget.stories.jsx +38 -0
- package/src/components/manage/Widgets/EmailWidget.jsx +10 -3
- package/src/components/manage/Widgets/EmailWidget.stories.jsx +38 -0
- package/src/components/manage/Widgets/FileWidget.jsx +18 -0
- package/src/components/manage/Widgets/FileWidget.stories.jsx +38 -0
- package/src/components/manage/Widgets/NumberWidget.jsx +9 -2
- package/src/components/manage/Widgets/NumberWidget.stories.jsx +39 -0
- package/src/components/manage/Widgets/ObjectBrowserWidget.stories.js +1 -1
- package/src/components/manage/Widgets/ObjectListWidget.jsx +26 -0
- package/src/components/manage/Widgets/ObjectListWidget.stories.js +166 -44
- package/src/components/manage/Widgets/ObjectWidget.jsx +4 -10
- package/src/components/manage/Widgets/ObjectWidget.stories.jsx +157 -0
- package/src/components/manage/Widgets/PasswordWidget.jsx +9 -2
- package/src/components/manage/Widgets/PasswordWidget.stories.jsx +38 -0
- package/src/components/manage/Widgets/QueryWidget.jsx +1 -3
- package/src/components/manage/Widgets/QueryWidget.stories.jsx +38 -0
- package/src/components/manage/Widgets/SelectWidget.stories.jsx +1 -1
- package/src/components/manage/Widgets/TextWidget.jsx +4 -3
- package/src/components/manage/Widgets/TextWidget.stories.jsx +38 -0
- package/src/components/manage/Widgets/TextareaWidget.jsx +10 -3
- package/src/components/manage/Widgets/TextareaWidget.stories.jsx +38 -0
- package/src/components/manage/Widgets/UrlWidget.jsx +12 -4
- package/src/components/manage/Widgets/UrlWidget.stories.jsx +38 -0
- package/src/components/manage/Widgets/WysiwygWidget.jsx +11 -3
- package/src/components/manage/Widgets/WysiwygWidget.stories.jsx +41 -0
- package/src/components/theme/Anontools/Anontools.jsx +0 -2
- package/src/components/theme/Anontools/Anontools.stories.jsx +24 -0
- package/src/components/theme/Breadcrumbs/Breadcrumbs.jsx +2 -5
- package/src/components/theme/Breadcrumbs/Breadcrumbs.stories.jsx +30 -0
- package/src/components/theme/Navigation/ContextNavigation.stories.js +27 -29
- package/src/components/theme/Navigation/NavItem.jsx +36 -0
- package/src/components/theme/Navigation/NavItems.jsx +2 -15
- package/src/components/theme/Navigation/Navigation.test.jsx +26 -0
- package/src/components/theme/View/RenderBlocks.jsx +10 -2
- package/src/config/Blocks.jsx +8 -0
- package/src/helpers/Blocks/Blocks.js +45 -3
- package/src/helpers/Blocks/Blocks.test.js +212 -0
- package/src/helpers/Extensions/withBlockExtensions.js +42 -2
- package/src/helpers/Extensions/withBlockSchemaEnhancer.js +77 -3
- package/src/helpers/Url/Url.js +2 -1
- package/src/helpers/Url/Url.test.js +7 -0
- package/src/helpers/Vocabularies/Vocabularies.js +12 -0
- package/src/helpers/index.js +2 -0
- package/src/storybook.jsx +50 -0
- package/src/components/manage/Sidebar/SidebarPopup.stories.mdx +0 -41
- package/src/components/theme/Anontools/Anontools.stories.mdx +0 -18
- package/src/components/theme/Breadcrumbs/Breadcrumbs.stories.mdx +0 -30
|
@@ -11,9 +11,16 @@ import { injectIntl } from 'react-intl';
|
|
|
11
11
|
import { FormFieldWrapper } from '@plone/volto/components';
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
|
-
* TextareaWidget
|
|
15
|
-
*
|
|
16
|
-
*
|
|
14
|
+
* TextareaWidget, a widget for multiple lines text
|
|
15
|
+
*
|
|
16
|
+
* To use it, in schema properties, declare a field like:
|
|
17
|
+
*
|
|
18
|
+
* ```jsx
|
|
19
|
+
* {
|
|
20
|
+
* title: "Text",
|
|
21
|
+
* widget: 'textarea',
|
|
22
|
+
* }
|
|
23
|
+
* ```
|
|
17
24
|
*/
|
|
18
25
|
const TextareaWidget = (props) => {
|
|
19
26
|
const { id, maxLength, value, onChange, placeholder } = props;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import TextareaWidget from './TextareaWidget';
|
|
3
|
+
import Wrapper from '@plone/volto/storybook';
|
|
4
|
+
|
|
5
|
+
const TextareaWidgetComponent = ({ children, ...args }) => {
|
|
6
|
+
const [value, setValue] = React.useState('');
|
|
7
|
+
const onChange = (block, value) => setValue(value);
|
|
8
|
+
return (
|
|
9
|
+
<Wrapper location={{ pathname: '/folder2/folder21/doc212' }}>
|
|
10
|
+
<div className="ui segment form attached" style={{ width: '400px' }}>
|
|
11
|
+
<TextareaWidget
|
|
12
|
+
{...args}
|
|
13
|
+
id="field"
|
|
14
|
+
title="Textarea"
|
|
15
|
+
block="testBlock"
|
|
16
|
+
value={value}
|
|
17
|
+
onChange={onChange}
|
|
18
|
+
/>
|
|
19
|
+
</div>
|
|
20
|
+
<pre>Value: {JSON.stringify(value, null, 4)}</pre>
|
|
21
|
+
</Wrapper>
|
|
22
|
+
);
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export const Textarea = TextareaWidgetComponent.bind({});
|
|
26
|
+
|
|
27
|
+
export default {
|
|
28
|
+
title: 'Widgets/Textarea',
|
|
29
|
+
component: TextareaWidget,
|
|
30
|
+
decorators: [
|
|
31
|
+
(Story) => (
|
|
32
|
+
<div className="ui segment form attached" style={{ width: '400px' }}>
|
|
33
|
+
<Story />
|
|
34
|
+
</div>
|
|
35
|
+
),
|
|
36
|
+
],
|
|
37
|
+
argTypes: {},
|
|
38
|
+
};
|
|
@@ -18,11 +18,19 @@ import clearSVG from '@plone/volto/icons/clear.svg';
|
|
|
18
18
|
import navTreeSVG from '@plone/volto/icons/nav.svg';
|
|
19
19
|
import URLUtils from '@plone/volto/components/manage/AnchorPlugin/utils/URLUtils';
|
|
20
20
|
|
|
21
|
-
/**
|
|
22
|
-
*
|
|
23
|
-
*
|
|
21
|
+
/** Widget to edit urls
|
|
22
|
+
*
|
|
23
|
+
* This is the default widget used for the `remoteUrl` field. You can also use
|
|
24
|
+
* it by declaring a field like:
|
|
25
|
+
*
|
|
26
|
+
* ```jsx
|
|
27
|
+
* {
|
|
28
|
+
* title: "URL",
|
|
29
|
+
* widget: 'url',
|
|
30
|
+
* }
|
|
31
|
+
* ```
|
|
24
32
|
*/
|
|
25
|
-
const UrlWidget = (props) => {
|
|
33
|
+
export const UrlWidget = (props) => {
|
|
26
34
|
const { id, onChange, onBlur, onClick, minLength, maxLength } = props;
|
|
27
35
|
const inputId = `field-${id}`;
|
|
28
36
|
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import UrlWidgetDefault, { UrlWidget } from './UrlWidget';
|
|
3
|
+
import Wrapper from '@plone/volto/storybook';
|
|
4
|
+
|
|
5
|
+
const UrlWidgetComponent = ({ children, ...args }) => {
|
|
6
|
+
const [value, setValue] = React.useState('');
|
|
7
|
+
const onChange = (block, value) => setValue(value);
|
|
8
|
+
return (
|
|
9
|
+
<Wrapper location={{ pathname: '/folder2/folder21/doc212' }}>
|
|
10
|
+
<div className="ui segment form attached" style={{ width: '400px' }}>
|
|
11
|
+
<UrlWidgetDefault
|
|
12
|
+
{...args}
|
|
13
|
+
id="field"
|
|
14
|
+
title="Url"
|
|
15
|
+
block="testBlock"
|
|
16
|
+
value={value}
|
|
17
|
+
onChange={onChange}
|
|
18
|
+
/>
|
|
19
|
+
</div>
|
|
20
|
+
<pre>Value: {JSON.stringify(value, null, 4)}</pre>
|
|
21
|
+
</Wrapper>
|
|
22
|
+
);
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export const Url = UrlWidgetComponent.bind({});
|
|
26
|
+
|
|
27
|
+
export default {
|
|
28
|
+
title: 'Widgets/Url',
|
|
29
|
+
component: UrlWidget,
|
|
30
|
+
decorators: [
|
|
31
|
+
(Story) => (
|
|
32
|
+
<div className="ui segment form attached" style={{ width: '400px' }}>
|
|
33
|
+
<Story />
|
|
34
|
+
</div>
|
|
35
|
+
),
|
|
36
|
+
],
|
|
37
|
+
argTypes: {},
|
|
38
|
+
};
|
|
@@ -55,9 +55,17 @@ const messages = defineMessages({
|
|
|
55
55
|
});
|
|
56
56
|
|
|
57
57
|
/**
|
|
58
|
-
* WysiwygWidget
|
|
59
|
-
*
|
|
60
|
-
*
|
|
58
|
+
* WysiwygWidget HTML richtext editing widget
|
|
59
|
+
*
|
|
60
|
+
* To use it, in schema properties, declare a field like:
|
|
61
|
+
*
|
|
62
|
+
* ```jsx
|
|
63
|
+
* {
|
|
64
|
+
* title: "Rich text",
|
|
65
|
+
* widget: 'richtext',
|
|
66
|
+
* }
|
|
67
|
+
* ```
|
|
68
|
+
*
|
|
61
69
|
*/
|
|
62
70
|
class WysiwygWidget extends Component {
|
|
63
71
|
/**
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import WysiwygWidget from './WysiwygWidget';
|
|
3
|
+
import Wrapper from '@plone/volto/storybook';
|
|
4
|
+
|
|
5
|
+
const WysiwygWidgetComponent = ({ children, ...args }) => {
|
|
6
|
+
const [value, setValue] = React.useState('');
|
|
7
|
+
const onChange = (block, value) => setValue(value);
|
|
8
|
+
return (
|
|
9
|
+
<Wrapper location={{ pathname: '/folder2/folder21/doc212' }}>
|
|
10
|
+
<div className="ui segment form attached" style={{ width: '400px' }}>
|
|
11
|
+
<WysiwygWidget
|
|
12
|
+
{...args}
|
|
13
|
+
id="field"
|
|
14
|
+
title="Wysiwyg"
|
|
15
|
+
block="testBlock"
|
|
16
|
+
value={value}
|
|
17
|
+
onChange={onChange}
|
|
18
|
+
/>
|
|
19
|
+
</div>
|
|
20
|
+
<pre>Value: {JSON.stringify(value, null, 4)}</pre>
|
|
21
|
+
</Wrapper>
|
|
22
|
+
);
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export const Wysiwyg = WysiwygWidgetComponent.bind({});
|
|
26
|
+
|
|
27
|
+
export default {
|
|
28
|
+
title: 'Widgets/Wysiwyg',
|
|
29
|
+
component: WysiwygWidget,
|
|
30
|
+
decorators: [
|
|
31
|
+
(Story) => (
|
|
32
|
+
<div
|
|
33
|
+
className="ui segment form attached"
|
|
34
|
+
style={{ width: '400px', marginTop: '40px' }}
|
|
35
|
+
>
|
|
36
|
+
<Story />
|
|
37
|
+
</div>
|
|
38
|
+
),
|
|
39
|
+
],
|
|
40
|
+
argTypes: {},
|
|
41
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Anontools as AnontoolsDefault } from './Anontools';
|
|
3
|
+
import Wrapper from '@plone/volto/storybook';
|
|
4
|
+
|
|
5
|
+
const AnontoolsComponent = ({ children, ...args }) => {
|
|
6
|
+
return (
|
|
7
|
+
<Wrapper location={{ pathname: '/folder2/folder21/doc212' }}>
|
|
8
|
+
<div className="ui segment form attached" style={{ width: '400px' }}>
|
|
9
|
+
<AnontoolsDefault
|
|
10
|
+
userSession={{ token: null }}
|
|
11
|
+
content={{ '@id': 'myid' }}
|
|
12
|
+
/>
|
|
13
|
+
</div>
|
|
14
|
+
</Wrapper>
|
|
15
|
+
);
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export const Anontools = AnontoolsComponent.bind({});
|
|
19
|
+
|
|
20
|
+
export default {
|
|
21
|
+
title: 'Public components/Anontools',
|
|
22
|
+
component: AnontoolsDefault,
|
|
23
|
+
argTypes: {},
|
|
24
|
+
};
|
|
@@ -30,10 +30,8 @@ const messages = defineMessages({
|
|
|
30
30
|
|
|
31
31
|
/**
|
|
32
32
|
* Breadcrumbs container class.
|
|
33
|
-
* @class Breadcrumbs
|
|
34
|
-
* @extends Component
|
|
35
33
|
*/
|
|
36
|
-
class
|
|
34
|
+
export class BreadcrumbsComponent extends Component {
|
|
37
35
|
/**
|
|
38
36
|
* Property types.
|
|
39
37
|
* @property {Object} propTypes Property types.
|
|
@@ -113,7 +111,6 @@ class Breadcrumbs extends Component {
|
|
|
113
111
|
}
|
|
114
112
|
}
|
|
115
113
|
|
|
116
|
-
export const BreadcrumbsComponent = Breadcrumbs;
|
|
117
114
|
export default compose(
|
|
118
115
|
injectIntl,
|
|
119
116
|
connect(
|
|
@@ -123,4 +120,4 @@ export default compose(
|
|
|
123
120
|
}),
|
|
124
121
|
{ getBreadcrumbs },
|
|
125
122
|
),
|
|
126
|
-
)(
|
|
123
|
+
)(BreadcrumbsComponent);
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { injectIntl } from 'react-intl';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { BreadcrumbsComponent } from './Breadcrumbs';
|
|
4
|
+
import Wrapper from '@plone/volto/storybook';
|
|
5
|
+
|
|
6
|
+
export const Breadcrumb = injectIntl(({ children, ...args }) => {
|
|
7
|
+
return (
|
|
8
|
+
<Wrapper location={{ pathname: '/folder2/folder21/doc212' }}>
|
|
9
|
+
<div className="ui segment form attached" style={{ width: '400px' }}>
|
|
10
|
+
<BreadcrumbsComponent
|
|
11
|
+
pathname=""
|
|
12
|
+
items={[
|
|
13
|
+
{
|
|
14
|
+
'@id': 'https://volto.kitconcept.com/api/Members',
|
|
15
|
+
title: 'Users',
|
|
16
|
+
},
|
|
17
|
+
]}
|
|
18
|
+
getBreadcrumbs={() => {}}
|
|
19
|
+
{...args}
|
|
20
|
+
/>
|
|
21
|
+
</div>
|
|
22
|
+
</Wrapper>
|
|
23
|
+
);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
export default {
|
|
27
|
+
title: 'Public components/Breadcrumb',
|
|
28
|
+
component: BreadcrumbsComponent,
|
|
29
|
+
argTypes: {},
|
|
30
|
+
};
|
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
import ContextNavigationDefault,
|
|
2
|
-
ContextNavigationComponent as CNC,
|
|
3
|
-
} from './ContextNavigation';
|
|
1
|
+
import { ContextNavigationComponent as CNC } from './ContextNavigation'; // ContextNavigationDefault,
|
|
4
2
|
import Wrapper from '@plone/volto/storybook';
|
|
5
3
|
import React from 'react';
|
|
6
4
|
|
|
@@ -75,18 +73,18 @@ const navigation = {
|
|
|
75
73
|
url: 'http://localhost:3000/api/folder2/sitemap',
|
|
76
74
|
};
|
|
77
75
|
|
|
78
|
-
const customStore = {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
};
|
|
76
|
+
// const customStore = {
|
|
77
|
+
// contextNavigation: {
|
|
78
|
+
// '/folder2/folder21/doc212/@contextnavigation': {
|
|
79
|
+
// data: navigation,
|
|
80
|
+
// },
|
|
81
|
+
// },
|
|
82
|
+
// userSession: { token: '1234' },
|
|
83
|
+
// intl: {
|
|
84
|
+
// locale: 'en',
|
|
85
|
+
// messages: {},
|
|
86
|
+
// },
|
|
87
|
+
// };
|
|
90
88
|
|
|
91
89
|
const ContextNavigationComponent = (args) => {
|
|
92
90
|
return (
|
|
@@ -97,20 +95,20 @@ const ContextNavigationComponent = (args) => {
|
|
|
97
95
|
);
|
|
98
96
|
};
|
|
99
97
|
|
|
100
|
-
const
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
};
|
|
98
|
+
// const ContextNavigationStory = (args) => {
|
|
99
|
+
// return (
|
|
100
|
+
// <Wrapper
|
|
101
|
+
// location={{ pathname: '/folder2/folder21/doc212' }}
|
|
102
|
+
// customStore={customStore}
|
|
103
|
+
// >
|
|
104
|
+
// <style dangerouslySetInnerHTML={{ __html: style }}></style>
|
|
105
|
+
// <ContextNavigationDefault />
|
|
106
|
+
// </Wrapper>
|
|
107
|
+
// );
|
|
108
|
+
// };
|
|
111
109
|
|
|
112
110
|
export default {
|
|
113
|
-
title: '
|
|
111
|
+
title: 'Public components/Context Navigation',
|
|
114
112
|
component: CNC,
|
|
115
113
|
decorators: [
|
|
116
114
|
(Story) => (
|
|
@@ -122,11 +120,11 @@ export default {
|
|
|
122
120
|
// subcomponents: { ArgsTable },
|
|
123
121
|
};
|
|
124
122
|
|
|
125
|
-
export const
|
|
123
|
+
export const ContextNavigation = () => (
|
|
126
124
|
<ContextNavigationComponent navigation={navigation} />
|
|
127
125
|
);
|
|
128
126
|
|
|
129
|
-
export const Connected = () => <ContextNavigation />;
|
|
127
|
+
// export const Connected = () => <ContextNavigation />;
|
|
130
128
|
|
|
131
129
|
const style = `
|
|
132
130
|
.list .content .list a {
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { NavLink } from 'react-router-dom';
|
|
3
|
+
import { isInternalURL } from '@plone/volto/helpers';
|
|
4
|
+
import config from '@plone/volto/registry';
|
|
5
|
+
|
|
6
|
+
const NavItem = ({ item, lang }) => {
|
|
7
|
+
const { settings } = config;
|
|
8
|
+
if (isInternalURL(item.url)) {
|
|
9
|
+
return (
|
|
10
|
+
<NavLink
|
|
11
|
+
to={item.url === '' ? '/' : item.url}
|
|
12
|
+
key={item.url}
|
|
13
|
+
className="item"
|
|
14
|
+
activeClassName="active"
|
|
15
|
+
exact={
|
|
16
|
+
settings.isMultilingual ? item.url === `/${lang}` : item.url === ''
|
|
17
|
+
}
|
|
18
|
+
>
|
|
19
|
+
{item.title}
|
|
20
|
+
</NavLink>
|
|
21
|
+
);
|
|
22
|
+
} else {
|
|
23
|
+
return (
|
|
24
|
+
<a
|
|
25
|
+
href={item.url}
|
|
26
|
+
key={item.url}
|
|
27
|
+
className="item"
|
|
28
|
+
rel="noopener noreferrer"
|
|
29
|
+
>
|
|
30
|
+
{item.title}
|
|
31
|
+
</a>
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export default NavItem;
|
|
@@ -1,24 +1,11 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import
|
|
3
|
-
import config from '@plone/volto/registry';
|
|
2
|
+
import NavItem from './NavItem';
|
|
4
3
|
|
|
5
4
|
const NavItems = ({ items, lang }) => {
|
|
6
|
-
const { settings } = config;
|
|
7
|
-
|
|
8
5
|
return (
|
|
9
6
|
<>
|
|
10
7
|
{items.map((item) => (
|
|
11
|
-
<
|
|
12
|
-
to={item.url === '' ? '/' : item.url}
|
|
13
|
-
key={item.url}
|
|
14
|
-
className="item"
|
|
15
|
-
activeClassName="active"
|
|
16
|
-
exact={
|
|
17
|
-
settings.isMultilingual ? item.url === `/${lang}` : item.url === ''
|
|
18
|
-
}
|
|
19
|
-
>
|
|
20
|
-
{item.title}
|
|
21
|
-
</NavLink>
|
|
8
|
+
<NavItem item={item} lang={lang} key={item.url} />
|
|
22
9
|
))}
|
|
23
10
|
</>
|
|
24
11
|
);
|
|
@@ -115,4 +115,30 @@ describe('Navigation', () => {
|
|
|
115
115
|
const json = component.toJSON();
|
|
116
116
|
expect(json).toMatchSnapshot();
|
|
117
117
|
});
|
|
118
|
+
|
|
119
|
+
it('renders a navigation component including external links', () => {
|
|
120
|
+
const store = mockStore({
|
|
121
|
+
navigation: {
|
|
122
|
+
items: [
|
|
123
|
+
{ title: 'Blog', url: '/blog' },
|
|
124
|
+
{ title: 'Users', url: '/users' },
|
|
125
|
+
{ title: 'Store', url: 'https://store.plone.org' },
|
|
126
|
+
],
|
|
127
|
+
},
|
|
128
|
+
userSession: { token: '1234' },
|
|
129
|
+
intl: {
|
|
130
|
+
locale: 'en',
|
|
131
|
+
messages: {},
|
|
132
|
+
},
|
|
133
|
+
});
|
|
134
|
+
const component = renderer.create(
|
|
135
|
+
<Provider store={store}>
|
|
136
|
+
<MemoryRouter>
|
|
137
|
+
<Navigation pathname="/" />
|
|
138
|
+
</MemoryRouter>
|
|
139
|
+
</Provider>,
|
|
140
|
+
);
|
|
141
|
+
const json = component.toJSON();
|
|
142
|
+
expect(json).toMatchSnapshot();
|
|
143
|
+
});
|
|
118
144
|
});
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import { getBaseUrl } from '@plone/volto/helpers';
|
|
2
|
+
import { getBaseUrl, applyBlockDefaults } from '@plone/volto/helpers';
|
|
3
3
|
import { defineMessages, injectIntl } from 'react-intl';
|
|
4
4
|
import { map } from 'lodash';
|
|
5
5
|
import {
|
|
@@ -28,13 +28,21 @@ const RenderBlocks = (props) => {
|
|
|
28
28
|
{map(content[blocksLayoutFieldname].items, (block) => {
|
|
29
29
|
const Block =
|
|
30
30
|
blocksConfig[content[blocksFieldname]?.[block]?.['@type']]?.view;
|
|
31
|
+
|
|
32
|
+
const blockData = applyBlockDefaults({
|
|
33
|
+
data: content[blocksFieldname][block],
|
|
34
|
+
intl,
|
|
35
|
+
metadata,
|
|
36
|
+
properties: content,
|
|
37
|
+
});
|
|
38
|
+
|
|
31
39
|
return Block ? (
|
|
32
40
|
<Block
|
|
33
41
|
key={block}
|
|
34
42
|
id={block}
|
|
35
43
|
metadata={metadata}
|
|
36
44
|
properties={content}
|
|
37
|
-
data={
|
|
45
|
+
data={blockData}
|
|
38
46
|
path={getBaseUrl(path || '')}
|
|
39
47
|
blocksConfig={blocksConfig}
|
|
40
48
|
/>
|
package/src/config/Blocks.jsx
CHANGED
|
@@ -59,6 +59,11 @@ import {
|
|
|
59
59
|
} from '@plone/volto/components/manage/Blocks/Search/components';
|
|
60
60
|
import getListingBlockAsyncData from '@plone/volto/components/manage/Blocks/Listing/getAsyncData';
|
|
61
61
|
|
|
62
|
+
// block sidebar schemas (not the Dexterity Layout block settings schemas)
|
|
63
|
+
import HeroImageLeftBlockSchema from '@plone/volto/components/manage/Blocks/HeroImageLeft/schema';
|
|
64
|
+
import ListingBlockSchema from '@plone/volto/components/manage/Blocks/Listing/schema';
|
|
65
|
+
import SearchBlockSchema from '@plone/volto/components/manage/Blocks/Search/schema';
|
|
66
|
+
|
|
62
67
|
defineMessages({
|
|
63
68
|
title: {
|
|
64
69
|
id: 'title',
|
|
@@ -253,6 +258,7 @@ const blocksConfig = {
|
|
|
253
258
|
view: ViewListingBlock,
|
|
254
259
|
edit: EditListingBlock,
|
|
255
260
|
schema: BlockSettingsSchema,
|
|
261
|
+
blockSchema: ListingBlockSchema,
|
|
256
262
|
restricted: false,
|
|
257
263
|
mostUsed: true,
|
|
258
264
|
sidebarTab: 1,
|
|
@@ -321,6 +327,7 @@ const blocksConfig = {
|
|
|
321
327
|
view: ViewHeroImageLeftBlock,
|
|
322
328
|
edit: EditHeroImageLeftBlock,
|
|
323
329
|
schema: BlockSettingsSchema,
|
|
330
|
+
blockSchema: HeroImageLeftBlockSchema,
|
|
324
331
|
restricted: false,
|
|
325
332
|
mostUsed: false,
|
|
326
333
|
blockHasOwnFocusManagement: true,
|
|
@@ -387,6 +394,7 @@ const blocksConfig = {
|
|
|
387
394
|
group: 'common',
|
|
388
395
|
view: SearchBlockView,
|
|
389
396
|
edit: SearchBlockEdit,
|
|
397
|
+
blockSchema: SearchBlockSchema,
|
|
390
398
|
restricted: false,
|
|
391
399
|
mostUsed: false,
|
|
392
400
|
sidebarTab: 1,
|
|
@@ -7,6 +7,7 @@ import { omit, without, endsWith, find, keys } from 'lodash';
|
|
|
7
7
|
import move from 'lodash-move';
|
|
8
8
|
import { v4 as uuid } from 'uuid';
|
|
9
9
|
import config from '@plone/volto/registry';
|
|
10
|
+
import { applySchemaEnhancer } from '@plone/volto/helpers';
|
|
10
11
|
|
|
11
12
|
/**
|
|
12
13
|
* Get blocks field.
|
|
@@ -323,7 +324,7 @@ export function previousBlockId(formData, currentBlock) {
|
|
|
323
324
|
* Generate empty block form
|
|
324
325
|
* @function emptyBlocksForm
|
|
325
326
|
* @param {Object} formData Form data
|
|
326
|
-
* @return {Object}
|
|
327
|
+
* @return {Object} Empty blocks form with one defaultBlockType block
|
|
327
328
|
*/
|
|
328
329
|
export function emptyBlocksForm() {
|
|
329
330
|
const { settings } = config;
|
|
@@ -340,6 +341,9 @@ export function emptyBlocksForm() {
|
|
|
340
341
|
|
|
341
342
|
/**
|
|
342
343
|
* Recursively discover blocks in data and call the provided callback
|
|
344
|
+
* @function visitBlocks
|
|
345
|
+
* @param {Object} content A content data structure (an object with blocks and blocks_layout)
|
|
346
|
+
* @param {Function} callback A function to call on each discovered block
|
|
343
347
|
*/
|
|
344
348
|
export function visitBlocks(content, callback) {
|
|
345
349
|
const queue = getBlocks(content);
|
|
@@ -351,11 +355,49 @@ export function visitBlocks(content, callback) {
|
|
|
351
355
|
// { data: {blocks, blocks_layout}}
|
|
352
356
|
if (Object.keys(blockdata || {}).indexOf('blocks') > -1) {
|
|
353
357
|
queue.push(...getBlocks(blockdata));
|
|
354
|
-
// getBlocks(blockdata).forEach((tuple) => queue.push(tuple));
|
|
355
358
|
}
|
|
356
359
|
if (Object.keys(blockdata?.data || {}).indexOf('blocks') > -1) {
|
|
357
360
|
queue.push(...getBlocks(blockdata.data));
|
|
358
|
-
// getBlocks(blockdata.data).forEach((tuple) => queue.push(tuple));
|
|
359
361
|
}
|
|
360
362
|
}
|
|
361
363
|
}
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* Initializes data with the default values coming from schema
|
|
367
|
+
*/
|
|
368
|
+
export function applySchemaDefaults({ data = {}, schema }) {
|
|
369
|
+
const derivedData = {
|
|
370
|
+
...Object.keys(schema.properties).reduce((accumulator, currentField) => {
|
|
371
|
+
return schema.properties[currentField].default
|
|
372
|
+
? {
|
|
373
|
+
...accumulator,
|
|
374
|
+
[currentField]: schema.properties[currentField].default,
|
|
375
|
+
}
|
|
376
|
+
: accumulator;
|
|
377
|
+
}, {}),
|
|
378
|
+
...data,
|
|
379
|
+
};
|
|
380
|
+
return derivedData;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
/**
|
|
384
|
+
* Apply the block's default (as defined in schema) to the block data.
|
|
385
|
+
*
|
|
386
|
+
* @function applyBlockDefaults
|
|
387
|
+
* @param {Object} params An object with data, intl and anything else
|
|
388
|
+
* @return {Object} Derived data, with the defaults extracted from the schema
|
|
389
|
+
*/
|
|
390
|
+
export function applyBlockDefaults({ data, intl, ...rest }, blocksConfig) {
|
|
391
|
+
const block_type = data['@type'];
|
|
392
|
+
const { blockSchema } =
|
|
393
|
+
(blocksConfig || config.blocks.blocksConfig)[block_type] || {};
|
|
394
|
+
if (!blockSchema) return data;
|
|
395
|
+
|
|
396
|
+
let schema =
|
|
397
|
+
typeof blockSchema === 'function'
|
|
398
|
+
? blockSchema({ data, intl, ...rest })
|
|
399
|
+
: blockSchema;
|
|
400
|
+
schema = applySchemaEnhancer({ schema, formData: data, intl });
|
|
401
|
+
|
|
402
|
+
return applySchemaDefaults({ data, schema });
|
|
403
|
+
}
|