@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.
Files changed (67) hide show
  1. package/CHANGELOG.md +44 -0
  2. package/README.md +4 -1
  3. package/package.json +1 -1
  4. package/src/actions/schema/schema.js +4 -2
  5. package/src/actions/schema/schema.test.js +10 -0
  6. package/src/actions/vocabularies/vocabularies.js +3 -10
  7. package/src/actions/vocabularies/vocabularies.test.js +26 -0
  8. package/src/components/manage/Add/Add.jsx +1 -1
  9. package/src/components/manage/Blocks/Block/Edit.jsx +6 -1
  10. package/src/components/manage/Blocks/HeroImageLeft/schema.js +1 -1
  11. package/src/components/manage/Blocks/Listing/schema.js +2 -0
  12. package/src/components/manage/Edit/Edit.jsx +4 -1
  13. package/src/components/manage/Sidebar/ObjectBrowserBody.jsx +1 -1
  14. package/src/components/manage/Sidebar/SidebarPopup.stories.jsx +42 -0
  15. package/src/components/manage/Widgets/AlignWidget.jsx +14 -6
  16. package/src/components/manage/Widgets/AlignWidget.stories.jsx +38 -0
  17. package/src/components/manage/Widgets/ArrayWidget.stories.jsx +2 -2
  18. package/src/components/manage/Widgets/CheckboxWidget.jsx +9 -0
  19. package/src/components/manage/Widgets/CheckboxWidget.stories.jsx +38 -0
  20. package/src/components/manage/Widgets/DatetimeWidget.jsx +9 -0
  21. package/src/components/manage/Widgets/DatetimeWidget.stories.jsx +38 -0
  22. package/src/components/manage/Widgets/EmailWidget.jsx +10 -3
  23. package/src/components/manage/Widgets/EmailWidget.stories.jsx +38 -0
  24. package/src/components/manage/Widgets/FileWidget.jsx +18 -0
  25. package/src/components/manage/Widgets/FileWidget.stories.jsx +38 -0
  26. package/src/components/manage/Widgets/NumberWidget.jsx +9 -2
  27. package/src/components/manage/Widgets/NumberWidget.stories.jsx +39 -0
  28. package/src/components/manage/Widgets/ObjectBrowserWidget.stories.js +1 -1
  29. package/src/components/manage/Widgets/ObjectListWidget.jsx +26 -0
  30. package/src/components/manage/Widgets/ObjectListWidget.stories.js +166 -44
  31. package/src/components/manage/Widgets/ObjectWidget.jsx +4 -10
  32. package/src/components/manage/Widgets/ObjectWidget.stories.jsx +157 -0
  33. package/src/components/manage/Widgets/PasswordWidget.jsx +9 -2
  34. package/src/components/manage/Widgets/PasswordWidget.stories.jsx +38 -0
  35. package/src/components/manage/Widgets/QueryWidget.jsx +1 -3
  36. package/src/components/manage/Widgets/QueryWidget.stories.jsx +38 -0
  37. package/src/components/manage/Widgets/SelectWidget.stories.jsx +1 -1
  38. package/src/components/manage/Widgets/TextWidget.jsx +4 -3
  39. package/src/components/manage/Widgets/TextWidget.stories.jsx +38 -0
  40. package/src/components/manage/Widgets/TextareaWidget.jsx +10 -3
  41. package/src/components/manage/Widgets/TextareaWidget.stories.jsx +38 -0
  42. package/src/components/manage/Widgets/UrlWidget.jsx +12 -4
  43. package/src/components/manage/Widgets/UrlWidget.stories.jsx +38 -0
  44. package/src/components/manage/Widgets/WysiwygWidget.jsx +11 -3
  45. package/src/components/manage/Widgets/WysiwygWidget.stories.jsx +41 -0
  46. package/src/components/theme/Anontools/Anontools.jsx +0 -2
  47. package/src/components/theme/Anontools/Anontools.stories.jsx +24 -0
  48. package/src/components/theme/Breadcrumbs/Breadcrumbs.jsx +2 -5
  49. package/src/components/theme/Breadcrumbs/Breadcrumbs.stories.jsx +30 -0
  50. package/src/components/theme/Navigation/ContextNavigation.stories.js +27 -29
  51. package/src/components/theme/Navigation/NavItem.jsx +36 -0
  52. package/src/components/theme/Navigation/NavItems.jsx +2 -15
  53. package/src/components/theme/Navigation/Navigation.test.jsx +26 -0
  54. package/src/components/theme/View/RenderBlocks.jsx +10 -2
  55. package/src/config/Blocks.jsx +8 -0
  56. package/src/helpers/Blocks/Blocks.js +45 -3
  57. package/src/helpers/Blocks/Blocks.test.js +212 -0
  58. package/src/helpers/Extensions/withBlockExtensions.js +42 -2
  59. package/src/helpers/Extensions/withBlockSchemaEnhancer.js +77 -3
  60. package/src/helpers/Url/Url.js +2 -1
  61. package/src/helpers/Url/Url.test.js +7 -0
  62. package/src/helpers/Vocabularies/Vocabularies.js +12 -0
  63. package/src/helpers/index.js +2 -0
  64. package/src/storybook.jsx +50 -0
  65. package/src/components/manage/Sidebar/SidebarPopup.stories.mdx +0 -41
  66. package/src/components/theme/Anontools/Anontools.stories.mdx +0 -18
  67. 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 component class.
15
- * @function TextareaWidget
16
- * @returns {string} Markup of the component.
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
- /** UrlWidget function component
22
- * @function UrlWidget
23
- * @returns {string} Markup of the component
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 container class.
59
- * @class WysiwygWidget
60
- * @extends Component
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
+ };
@@ -13,8 +13,6 @@ import config from '@plone/volto/registry';
13
13
 
14
14
  /**
15
15
  * Anontools container class.
16
- * @class Anontools
17
- * @extends Component
18
16
  */
19
17
  export class Anontools extends Component {
20
18
  /**
@@ -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 Breadcrumbs extends Component {
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
- )(Breadcrumbs);
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
- contextNavigation: {
80
- '/folder2/folder21/doc212/@contextnavigation': {
81
- data: navigation,
82
- },
83
- },
84
- userSession: { token: '1234' },
85
- intl: {
86
- locale: 'en',
87
- messages: {},
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 ContextNavigation = (args) => {
101
- return (
102
- <Wrapper
103
- location={{ pathname: '/folder2/folder21/doc212' }}
104
- customStore={customStore}
105
- >
106
- <style dangerouslySetInnerHTML={{ __html: style }}></style>
107
- <ContextNavigationDefault />
108
- </Wrapper>
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: 'Internal components/Context navigation',
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 Renderer = () => (
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 { NavLink } from 'react-router-dom';
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
- <NavLink
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={content[blocksFieldname][block]}
45
+ data={blockData}
38
46
  path={getBaseUrl(path || '')}
39
47
  blocksConfig={blocksConfig}
40
48
  />
@@ -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} Emptry blocks form with one defaultBlockType block
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
+ }