@plone/volto 18.32.0 → 18.32.2

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 (163) hide show
  1. package/.eslintignore +1 -0
  2. package/CHANGELOG.md +40 -0
  3. package/README.md +0 -4
  4. package/locales/af/LC_MESSAGES/volto.po +5 -0
  5. package/locales/af.json +1 -1
  6. package/locales/ar/LC_MESSAGES/volto.po +5 -0
  7. package/locales/ar.json +1 -1
  8. package/locales/bg/LC_MESSAGES/volto.po +5 -0
  9. package/locales/bg.json +1 -1
  10. package/locales/bn/LC_MESSAGES/volto.po +5 -0
  11. package/locales/bn.json +1 -1
  12. package/locales/ca/LC_MESSAGES/volto.po +5 -0
  13. package/locales/ca.json +1 -1
  14. package/locales/cs/LC_MESSAGES/volto.po +5 -0
  15. package/locales/cs.json +1 -1
  16. package/locales/cy/LC_MESSAGES/volto.po +5 -0
  17. package/locales/cy.json +1 -1
  18. package/locales/da/LC_MESSAGES/volto.po +5 -0
  19. package/locales/da.json +1 -1
  20. package/locales/de/LC_MESSAGES/volto.po +5 -0
  21. package/locales/de.json +1 -1
  22. package/locales/el/LC_MESSAGES/volto.po +5 -0
  23. package/locales/el.json +1 -1
  24. package/locales/en/LC_MESSAGES/volto.po +5 -0
  25. package/locales/en.json +1 -1
  26. package/locales/en_AU/LC_MESSAGES/volto.po +5 -0
  27. package/locales/en_AU.json +1 -1
  28. package/locales/en_GB/LC_MESSAGES/volto.po +5 -0
  29. package/locales/en_GB.json +1 -1
  30. package/locales/eo/LC_MESSAGES/volto.po +5 -0
  31. package/locales/eo.json +1 -1
  32. package/locales/es/LC_MESSAGES/volto.po +5 -0
  33. package/locales/es.json +1 -1
  34. package/locales/et/LC_MESSAGES/volto.po +5 -0
  35. package/locales/et.json +1 -1
  36. package/locales/eu/LC_MESSAGES/volto.po +5 -0
  37. package/locales/eu.json +1 -1
  38. package/locales/fa/LC_MESSAGES/volto.po +5 -0
  39. package/locales/fa.json +1 -1
  40. package/locales/fi/LC_MESSAGES/volto.po +5 -0
  41. package/locales/fi.json +1 -1
  42. package/locales/fr/LC_MESSAGES/volto.po +5 -0
  43. package/locales/fr.json +1 -1
  44. package/locales/fu/LC_MESSAGES/volto.po +5 -0
  45. package/locales/fu.json +1 -1
  46. package/locales/gl/LC_MESSAGES/volto.po +5 -0
  47. package/locales/gl.json +1 -1
  48. package/locales/he/LC_MESSAGES/volto.po +5 -0
  49. package/locales/he.json +1 -1
  50. package/locales/hi/LC_MESSAGES/volto.po +5 -0
  51. package/locales/hi.json +1 -1
  52. package/locales/hr/LC_MESSAGES/volto.po +5 -0
  53. package/locales/hr.json +1 -1
  54. package/locales/hu/LC_MESSAGES/volto.po +5 -0
  55. package/locales/hu.json +1 -1
  56. package/locales/hy/LC_MESSAGES/volto.po +5 -0
  57. package/locales/hy.json +1 -1
  58. package/locales/id/LC_MESSAGES/volto.po +5 -0
  59. package/locales/id.json +1 -1
  60. package/locales/it/LC_MESSAGES/volto.po +5 -0
  61. package/locales/it.json +1 -1
  62. package/locales/ja/LC_MESSAGES/volto.po +5 -0
  63. package/locales/ja.json +1 -1
  64. package/locales/ka/LC_MESSAGES/volto.po +5 -0
  65. package/locales/ka.json +1 -1
  66. package/locales/kn/LC_MESSAGES/volto.po +5 -0
  67. package/locales/kn.json +1 -1
  68. package/locales/ko/LC_MESSAGES/volto.po +5 -0
  69. package/locales/ko.json +1 -1
  70. package/locales/lt/LC_MESSAGES/volto.po +5 -0
  71. package/locales/lt.json +1 -1
  72. package/locales/lv/LC_MESSAGES/volto.po +5 -0
  73. package/locales/lv.json +1 -1
  74. package/locales/mi/LC_MESSAGES/volto.po +5 -0
  75. package/locales/mi.json +1 -1
  76. package/locales/mk/LC_MESSAGES/volto.po +5 -0
  77. package/locales/mk.json +1 -1
  78. package/locales/my/LC_MESSAGES/volto.po +5 -0
  79. package/locales/my.json +1 -1
  80. package/locales/nb_NO/LC_MESSAGES/volto.po +5 -0
  81. package/locales/nb_NO.json +1 -1
  82. package/locales/nl/LC_MESSAGES/volto.po +5 -0
  83. package/locales/nl.json +1 -1
  84. package/locales/nn/LC_MESSAGES/volto.po +5 -0
  85. package/locales/nn.json +1 -1
  86. package/locales/pl/LC_MESSAGES/volto.po +5 -0
  87. package/locales/pl.json +1 -1
  88. package/locales/pt/LC_MESSAGES/volto.po +5 -0
  89. package/locales/pt.json +1 -1
  90. package/locales/pt_BR/LC_MESSAGES/volto.po +5 -0
  91. package/locales/pt_BR.json +1 -1
  92. package/locales/rm/LC_MESSAGES/volto.po +5 -0
  93. package/locales/rm.json +1 -1
  94. package/locales/ro/LC_MESSAGES/volto.po +5 -0
  95. package/locales/ro.json +1 -1
  96. package/locales/ru/LC_MESSAGES/volto.po +5 -0
  97. package/locales/ru.json +1 -1
  98. package/locales/sk/LC_MESSAGES/volto.po +5 -0
  99. package/locales/sk.json +1 -1
  100. package/locales/sl/LC_MESSAGES/volto.po +5 -0
  101. package/locales/sl.json +1 -1
  102. package/locales/sm/LC_MESSAGES/volto.po +5 -0
  103. package/locales/sm.json +1 -1
  104. package/locales/sq/LC_MESSAGES/volto.po +5 -0
  105. package/locales/sq.json +1 -1
  106. package/locales/sr/LC_MESSAGES/volto.po +5 -0
  107. package/locales/sr.json +1 -1
  108. package/locales/sr@cyrl/LC_MESSAGES/volto.po +5 -0
  109. package/locales/sr@cyrl.json +1 -1
  110. package/locales/sr@latn/LC_MESSAGES/volto.po +5 -0
  111. package/locales/sr@latn.json +1 -1
  112. package/locales/sv/LC_MESSAGES/volto.po +5 -0
  113. package/locales/sv.json +1 -1
  114. package/locales/ta/LC_MESSAGES/volto.po +5 -0
  115. package/locales/ta.json +1 -1
  116. package/locales/te/LC_MESSAGES/volto.po +5 -0
  117. package/locales/te.json +1 -1
  118. package/locales/th/LC_MESSAGES/volto.po +5 -0
  119. package/locales/th.json +1 -1
  120. package/locales/to/LC_MESSAGES/volto.po +5 -0
  121. package/locales/to.json +1 -1
  122. package/locales/tr/LC_MESSAGES/volto.po +5 -0
  123. package/locales/tr.json +1 -1
  124. package/locales/uk/LC_MESSAGES/volto.po +5 -0
  125. package/locales/uk.json +1 -1
  126. package/locales/vi/LC_MESSAGES/volto.po +5 -0
  127. package/locales/vi.json +1 -1
  128. package/locales/volto.pot +5 -0
  129. package/locales/zh_CN/LC_MESSAGES/volto.po +5 -0
  130. package/locales/zh_CN.json +1 -1
  131. package/locales/zh_Hant/LC_MESSAGES/volto.po +5 -0
  132. package/locales/zh_Hant.json +1 -1
  133. package/locales/zh_Hant_HK/LC_MESSAGES/volto.po +5 -0
  134. package/locales/zh_Hant_HK.json +1 -1
  135. package/package.json +6 -5
  136. package/src/components/manage/Blocks/Block/BlocksForm.jsx +10 -7
  137. package/src/components/manage/Blocks/Block/BlocksForm.test.jsx +2 -9
  138. package/src/components/manage/Blocks/Block/Order/Item.jsx +9 -3
  139. package/src/components/manage/Blocks/Block/Order/Order.jsx +116 -67
  140. package/src/components/manage/Blocks/Block/Order/utilities.js +28 -11
  141. package/src/components/manage/Blocks/Listing/Edit.jsx +1 -0
  142. package/src/components/manage/Controlpanels/ContentTypeSchema.jsx +1 -1
  143. package/src/components/manage/Sharing/Sharing.jsx +10 -12
  144. package/src/components/manage/Sidebar/ObjectBrowserBody.jsx +5 -1
  145. package/src/components/manage/UniversalLink/UniversalLink.test.jsx +16 -0
  146. package/src/components/manage/UniversalLink/UniversalLink.tsx +1 -0
  147. package/src/components/theme/App/App.jsx +3 -1
  148. package/src/components/theme/ConnectionRefused/ConnectionRefused.jsx +3 -2
  149. package/src/components/theme/PasswordReset/PasswordReset.jsx +108 -191
  150. package/src/components/theme/View/RenderBlocks.jsx +8 -10
  151. package/src/components/theme/View/RenderBlocks.test.jsx +14 -4
  152. package/src/config/index.js +1 -1
  153. package/src/helpers/AuthToken/AuthToken.js +1 -6
  154. package/src/helpers/Blocks/Blocks.js +109 -24
  155. package/src/helpers/Blocks/Blocks.test.js +100 -0
  156. package/src/helpers/FormValidation/validators.ts +16 -4
  157. package/types/components/manage/Blocks/Block/Order/utilities.d.ts +2 -1
  158. package/types/components/theme/ConnectionRefused/ConnectionRefused.d.ts +2 -2
  159. package/types/components/theme/PasswordReset/PasswordReset.d.ts +6 -2
  160. package/types/config/Views.d.ts +1 -1
  161. package/types/helpers/Blocks/Blocks.d.ts +4 -0
  162. package/types/helpers/FormValidation/validators.d.ts +11 -1
  163. package/types/routes.d.ts +7 -5
@@ -34,27 +34,44 @@ export function getProjection(
34
34
  depth = minDepth;
35
35
  }
36
36
 
37
- return { depth, maxDepth, minDepth, parentId: getParentId() };
37
+ return { depth, maxDepth, minDepth, ...getParent() };
38
38
 
39
- function getParentId() {
39
+ function getParent() {
40
40
  if (depth === 0 || !previousItem) {
41
- return null;
41
+ return {
42
+ parentId: null,
43
+ parentType: null,
44
+ };
42
45
  }
43
46
 
44
47
  if (depth <= previousItem.depth) {
45
- return previousItem.parentId;
48
+ return {
49
+ parentId: previousItem.parentId,
50
+ parentType: previousItem.parentType,
51
+ };
46
52
  }
47
53
 
48
54
  if (depth > previousItem.depth) {
49
- return previousItem.id;
55
+ return {
56
+ parentId: previousItem.id,
57
+ parentType: previousItem.data?.['@type'] || null,
58
+ };
50
59
  }
51
60
 
52
61
  const newParent = newItems
53
62
  .slice(0, overItemIndex)
54
63
  .reverse()
55
- .find((item) => item.depth === depth)?.parentId;
56
-
57
- return newParent ?? null;
64
+ .find((item) => item.depth === depth);
65
+
66
+ return newParent
67
+ ? {
68
+ parentId: newParent.parentId,
69
+ parentType: newParent.parentType,
70
+ }
71
+ : {
72
+ parentId: null,
73
+ parentType: null,
74
+ };
58
75
  }
59
76
  }
60
77
 
@@ -79,12 +96,12 @@ function getMinDepth({ nextItem }) {
79
96
  return 0;
80
97
  }
81
98
 
82
- function flatten(items = [], parentId = null, depth = 0) {
99
+ function flatten(items = [], parentId = null, parentType = null, depth = 0) {
83
100
  return items.reduce((acc, item, index) => {
84
101
  return [
85
102
  ...acc,
86
- { ...item, parentId, depth, index },
87
- ...flatten(item.children, item.id, depth + 1),
103
+ { ...item, parentId, parentType, depth, index },
104
+ ...flatten(item.children, item.id, item.data?.['@type'], depth + 1),
88
105
  ];
89
106
  }, []);
90
107
  }
@@ -50,6 +50,7 @@ const Edit = React.memo(
50
50
  function areEquals(prevProps, nextProps) {
51
51
  return !(
52
52
  nextProps.selected !== prevProps.selected ||
53
+ nextProps.index !== prevProps.index ||
53
54
  !isEqual(prevProps.data, nextProps.data)
54
55
  );
55
56
  },
@@ -169,7 +169,7 @@ class ContentTypeSchema extends Component {
169
169
  error
170
170
  title={this.props.intl.formatMessage(messages.error)}
171
171
  content={JSON.stringify(
172
- nextProps.schemaRequest.put.error.response.body ||
172
+ nextProps.schemaRequest.put.error.response.body?.message ||
173
173
  nextProps.schemaRequest.put.error.response.text,
174
174
  )}
175
175
  />,
@@ -485,11 +485,19 @@ class SharingComponent extends Component {
485
485
  />
486
486
  </p>
487
487
  </Segment>
488
- <Segment className="actions" attached clearing>
488
+ <Segment className="right aligned actions" attached clearing>
489
+ <Button
490
+ basic
491
+ secondary
492
+ aria-label={this.props.intl.formatMessage(messages.cancel)}
493
+ title={this.props.intl.formatMessage(messages.cancel)}
494
+ onClick={this.onCancel}
495
+ >
496
+ <Icon className="circled" name={clearSVG} size="30px" />
497
+ </Button>
489
498
  <Button
490
499
  basic
491
500
  primary
492
- floated="right"
493
501
  type="submit"
494
502
  aria-label={this.props.intl.formatMessage(messages.save)}
495
503
  title={this.props.intl.formatMessage(messages.save)}
@@ -498,16 +506,6 @@ class SharingComponent extends Component {
498
506
  >
499
507
  <Icon className="circled" name={aheadSVG} size="30px" />
500
508
  </Button>
501
- <Button
502
- basic
503
- secondary
504
- aria-label={this.props.intl.formatMessage(messages.cancel)}
505
- title={this.props.intl.formatMessage(messages.cancel)}
506
- floated="right"
507
- onClick={this.onCancel}
508
- >
509
- <Icon className="circled" name={clearSVG} size="30px" />
510
- </Button>
511
509
  </Segment>
512
510
  </Form>
513
511
  </Plug>
@@ -144,10 +144,14 @@ class ObjectBrowserBody extends Component {
144
144
  showSearchInput: false,
145
145
  // In image mode, the searchable types default to the image types which
146
146
  // can be overridden with the property if specified.
147
+ // If selectableTypes are passed, the searchableTypes are the selectableTypes
147
148
  searchableTypes:
148
149
  this.props.mode === 'image'
149
150
  ? this.props.searchableTypes || config.settings.imageObjects
150
- : this.props.searchableTypes,
151
+ : [
152
+ ...(this.props.searchableTypes ?? []),
153
+ ...(this.props.selectableTypes ?? []),
154
+ ],
151
155
  view: this.props.mode === 'image' ? 'icons' : 'list',
152
156
  };
153
157
  this.searchInputRef = React.createRef();
@@ -89,6 +89,22 @@ describe('UniversalLink', () => {
89
89
  );
90
90
  });
91
91
 
92
+ it('check UniversalLink append http when user has not entered the protocol', () => {
93
+ const { getByTitle } = render(
94
+ <Provider store={store}>
95
+ <MemoryRouter>
96
+ <UniversalLink href="www.github.com" title="Volto GitHub repository">
97
+ <h1>Title</h1>
98
+ </UniversalLink>
99
+ </MemoryRouter>
100
+ </Provider>,
101
+ );
102
+
103
+ expect(getByTitle('Volto GitHub repository').getAttribute('href')).toBe(
104
+ 'http://www.github.com',
105
+ );
106
+ });
107
+
92
108
  it('check UniversalLink set target attribute for ext links', () => {
93
109
  const { getByTitle } = render(
94
110
  <Provider store={store}>
@@ -122,6 +122,7 @@ const UniversalLink = React.memo(
122
122
  onClick,
123
123
  onKeyDown,
124
124
  item,
125
+ href,
125
126
  ...rest
126
127
  } = props;
127
128
  __test.renderCounter();
@@ -174,7 +174,9 @@ export class App extends Component {
174
174
  <main ref={this.mainRef}>
175
175
  <OutdatedBrowser />
176
176
  {this.props.connectionRefused ? (
177
- <ConnectionRefusedView />
177
+ <ConnectionRefusedView
178
+ staticContext={this.props.staticContext}
179
+ />
178
180
  ) : this.state.hasError ? (
179
181
  <Error
180
182
  message={this.state.error.message}
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Home container.
2
+ * Connection refused error page.
3
3
  * @module components/theme/ConnectionRefused/ConnectionRefused
4
4
  */
5
5
 
@@ -7,6 +7,7 @@ import React from 'react';
7
7
  import { FormattedMessage } from 'react-intl';
8
8
  import { Container } from 'semantic-ui-react';
9
9
  import config from '@plone/volto/registry';
10
+ import { withServerErrorCode } from '@plone/volto/helpers/Utils/Utils';
10
11
 
11
12
  const ConnectionRefused = () => (
12
13
  <Container
@@ -71,4 +72,4 @@ const ConnectionRefused = () => (
71
72
  </Container>
72
73
  );
73
74
 
74
- export default ConnectionRefused;
75
+ export default withServerErrorCode(503)(ConnectionRefused);
@@ -3,14 +3,12 @@
3
3
  * @module components/theme/PasswordReset/PasswordReset
4
4
  */
5
5
 
6
- import React, { Component } from 'react';
7
- import PropTypes from 'prop-types';
8
- import { connect } from 'react-redux';
9
- import { compose } from 'redux';
10
- import { Link, withRouter } from 'react-router-dom';
6
+ import { useState, useEffect } from 'react';
7
+ import { useSelector, useDispatch } from 'react-redux';
8
+ import { Link, useHistory, useParams } from 'react-router-dom';
11
9
  import Helmet from '@plone/volto/helpers/Helmet/Helmet';
12
10
  import { Container } from 'semantic-ui-react';
13
- import { FormattedMessage, defineMessages, injectIntl } from 'react-intl';
11
+ import { FormattedMessage, defineMessages, useIntl } from 'react-intl';
14
12
 
15
13
  import { Form } from '@plone/volto/components/manage/Form';
16
14
  import { setInitialPassword } from '@plone/volto/actions/users/users';
@@ -90,220 +88,139 @@ const messages = defineMessages({
90
88
  });
91
89
 
92
90
  /**
93
- * PasswordReset class.
94
- * @class PasswordReset
95
- * @extends Component
91
+ * @function PasswordReset
92
+ * @returns {JSX.Element}
96
93
  */
97
- class PasswordReset extends Component {
98
- /**
99
- * Property types.
100
- * @property {Object} propTypes Property types.
101
- * @static
102
- */
103
- static propTypes = {
104
- loading: PropTypes.bool.isRequired,
105
- loaded: PropTypes.bool.isRequired,
106
- error: PropTypes.string,
107
- token: PropTypes.string.isRequired,
108
- setInitialPassword: PropTypes.func.isRequired,
109
- };
94
+ function PasswordReset() {
95
+ const dispatch = useDispatch();
96
+ const history = useHistory();
97
+ const { token } = useParams();
110
98
 
111
- /**
112
- * Default properties.
113
- * @property {Object} defaultProps Default properties.
114
- * @static
115
- */
116
- static defaultProps = {
117
- error: null,
118
- };
99
+ const loading = useSelector((state) => state.users.initial.loading);
100
+ const loaded = useSelector((state) => state.users.initial.loaded);
101
+ const error = useSelector((state) => state.users.initial.error);
119
102
 
120
- /**
121
- * Constructor
122
- * @method constructor
123
- * @param {Object} props Component properties
124
- * @constructs Controlpanel
125
- */
126
- constructor(props) {
127
- super(props);
128
- this.onCancel = this.onCancel.bind(this);
129
- this.onSubmit = this.onSubmit.bind(this);
130
- this.state = {
131
- error: null,
132
- isSuccessful: false,
133
- };
103
+ const [localError, setLocalError] = useState(null);
104
+ const [isSuccessful, setIsSuccessful] = useState(false);
105
+ const intl = useIntl();
134
106
 
135
- this.identifierField = config.settings.useEmailAsLogin
136
- ? 'email'
137
- : 'username';
107
+ const identifierField = config.settings.useEmailAsLogin
108
+ ? 'email'
109
+ : 'username';
138
110
 
139
- this.identifierTitle =
140
- this.identifierField === 'email'
141
- ? this.props.intl.formatMessage(messages.emailTitle)
142
- : this.props.intl.formatMessage(messages.usernameTitle);
111
+ const identifierTitle =
112
+ identifierField === 'email'
113
+ ? intl.formatMessage(messages.emailTitle)
114
+ : intl.formatMessage(messages.usernameTitle);
143
115
 
144
- this.identifierDescription =
145
- this.identifierField === 'email'
146
- ? this.props.intl.formatMessage(messages.emailDescription)
147
- : this.props.intl.formatMessage(messages.usernameDescription);
148
- }
116
+ const identifierDescription =
117
+ identifierField === 'email'
118
+ ? intl.formatMessage(messages.emailDescription)
119
+ : intl.formatMessage(messages.usernameDescription);
149
120
 
150
- /**
151
- * Component will receive props
152
- * @method componentWillReceiveProps
153
- * @param {Object} nextProps Next properties
154
- * @returns {undefined}
155
- */
156
- UNSAFE_componentWillReceiveProps(nextProps) {
157
- if (this.props.loading && nextProps.loaded) {
158
- this.setState({ isSuccessful: true });
121
+ useEffect(() => {
122
+ if (!loading && loaded) {
123
+ setIsSuccessful(true);
159
124
  }
160
- }
125
+ }, [loading, loaded]);
161
126
 
162
127
  /**
163
128
  * Submit handler
164
129
  * @method onSubmit
165
130
  * @param {object} data Form data.
166
- * @param {object} event Form data.
167
131
  * @returns {undefined}
168
132
  */
169
- onSubmit(data) {
133
+ const onSubmit = (data) => {
170
134
  if (data.password === data.passwordRepeat) {
171
- this.props.setInitialPassword(
172
- data[this.identifierField],
173
- this.props.token,
174
- data.password,
175
- );
176
- this.setState({
177
- error: null,
178
- });
135
+ dispatch(setInitialPassword(data[identifierField], token, data.password));
136
+ setLocalError(null);
179
137
  } else {
180
- this.setState({
181
- error: {
182
- message: this.props.intl.formatMessage(messages.passwordsDoNotMatch),
183
- },
138
+ setLocalError({
139
+ message: intl.formatMessage(messages.passwordsDoNotMatch),
184
140
  });
185
141
  }
186
- }
142
+ };
187
143
 
188
144
  /**
189
145
  * Cancel handler
190
146
  * @method onCancel
191
147
  * @returns {undefined}
192
148
  */
193
- onCancel() {
194
- this.props.history.goBack();
149
+ const onCancel = () => {
150
+ history.goBack();
151
+ };
152
+
153
+ if (isSuccessful) {
154
+ return (
155
+ <Container>
156
+ <h1 className="documentFirstHeading">
157
+ <FormattedMessage {...messages.successRedirectToLoginTitle} />
158
+ </h1>
159
+ <p className="description">
160
+ <FormattedMessage
161
+ {...messages.successRedirectToLoginBody}
162
+ values={{
163
+ link: (
164
+ <Link to="/login">{intl.formatMessage({ id: 'Log In' })}</Link>
165
+ ),
166
+ }}
167
+ />
168
+ </p>
169
+ </Container>
170
+ );
195
171
  }
196
172
 
197
- /**
198
- * Render method.
199
- * @method render
200
- * @returns {string} Markup for the component.
201
- */
202
- render() {
203
- if (this.state.isSuccessful) {
204
- return (
173
+ if (token) {
174
+ const errmsg = error ? error.response?.body?.error || error : null;
175
+ return (
176
+ <div id="page-password-reset">
177
+ <Helmet title={intl.formatMessage(messages.passwordReset)} />
205
178
  <Container>
206
- <h1 className="documentFirstHeading">
207
- <FormattedMessage
208
- id="Account activation completed"
209
- defaultMessage="Account activation completed"
210
- />
211
- </h1>
212
- <p className="description">
213
- <FormattedMessage
214
- id="Your password has been set successfully. You may now {link} with your new password."
215
- defaultMessage="Your password has been set successfully. You may now {link} with your new password."
216
- values={{
217
- link: (
218
- <Link to="/login">
219
- {this.props.intl.formatMessage({ id: 'Log In' })}
220
- </Link>
221
- ),
222
- }}
223
- />
224
- </p>
225
- </Container>
226
- );
227
- }
228
- if (this.props.token) {
229
- const errmsg = this.props.error
230
- ? this.props.error.response.body.error
231
- : null;
232
- return (
233
- <div id="page-password-reset">
234
- <Helmet
235
- title={this.props.intl.formatMessage(messages.passwordReset)}
236
- />
237
- <Container>
238
- <Form
239
- title={this.props.intl.formatMessage(messages.title)}
240
- description={this.props.intl.formatMessage(messages.description)}
241
- onSubmit={this.onSubmit}
242
- onCancel={this.onCancel}
243
- error={this.state.error || errmsg}
244
- schema={{
245
- fieldsets: [
246
- {
247
- id: 'default',
248
- title: this.props.intl.formatMessage(messages.default),
249
- fields: [
250
- this.identifierField,
251
- 'password',
252
- 'passwordRepeat',
253
- ],
254
- },
255
- ],
256
- properties: {
257
- [this.identifierField]: {
258
- type: 'string',
259
- title: this.identifierTitle,
260
- description: this.identifierDescription,
261
- },
262
- password: {
263
- description: this.props.intl.formatMessage(
264
- messages.passwordDescription,
265
- ),
266
- title: this.props.intl.formatMessage(
267
- messages.passwordTitle,
268
- ),
269
- type: 'string',
270
- widget: 'password',
271
- },
272
- passwordRepeat: {
273
- description: this.props.intl.formatMessage(
274
- messages.passwordRepeatDescription,
275
- ),
276
- title: this.props.intl.formatMessage(
277
- messages.passwordRepeatTitle,
278
- ),
279
- type: 'string',
280
- widget: 'password',
281
- },
179
+ <Form
180
+ title={intl.formatMessage(messages.title)}
181
+ description={intl.formatMessage(messages.description)}
182
+ onSubmit={onSubmit}
183
+ onCancel={onCancel}
184
+ error={localError || errmsg}
185
+ schema={{
186
+ fieldsets: [
187
+ {
188
+ id: 'default',
189
+ title: intl.formatMessage(messages.default),
190
+ fields: [identifierField, 'password', 'passwordRepeat'],
282
191
  },
283
- submitLabel: this.props.intl.formatMessage(
284
- messages.setMyPassword,
285
- ),
286
- required: [this.identifierField, 'password', 'passwordRepeat'],
287
- }}
288
- />
289
- </Container>
290
- </div>
291
- );
292
- }
293
- return <div />;
192
+ ],
193
+ properties: {
194
+ [identifierField]: {
195
+ type: 'string',
196
+ title: identifierTitle,
197
+ description: identifierDescription,
198
+ },
199
+ password: {
200
+ description: intl.formatMessage(messages.passwordDescription),
201
+ title: intl.formatMessage(messages.passwordTitle),
202
+ type: 'string',
203
+ widget: 'password',
204
+ },
205
+ passwordRepeat: {
206
+ description: intl.formatMessage(
207
+ messages.passwordRepeatDescription,
208
+ ),
209
+ title: intl.formatMessage(messages.passwordRepeatTitle),
210
+ type: 'string',
211
+ widget: 'password',
212
+ },
213
+ },
214
+ submitLabel: intl.formatMessage(messages.setMyPassword),
215
+ required: [identifierField, 'password', 'passwordRepeat'],
216
+ }}
217
+ />
218
+ </Container>
219
+ </div>
220
+ );
294
221
  }
222
+
223
+ return <div />;
295
224
  }
296
225
 
297
- export default compose(
298
- withRouter,
299
- injectIntl,
300
- connect(
301
- (state, props) => ({
302
- loading: state.users.initial.loading,
303
- loaded: state.users.initial.loaded,
304
- error: state.users.initial.error,
305
- token: props.match.params.token,
306
- }),
307
- { setInitialPassword },
308
- ),
309
- )(PasswordReset);
226
+ export default PasswordReset;
@@ -5,8 +5,7 @@ import map from 'lodash/map';
5
5
  import MaybeWrap from '@plone/volto/components/manage/MaybeWrap/MaybeWrap';
6
6
  import {
7
7
  applyBlockDefaults,
8
- getBlocksFieldname,
9
- getBlocksLayoutFieldname,
8
+ getBlocks,
10
9
  hasBlocksData,
11
10
  } from '@plone/volto/helpers/Blocks/Blocks';
12
11
  import StyleWrapper from '@plone/volto/components/manage/Blocks/Block/StyleWrapper';
@@ -28,26 +27,25 @@ const messages = defineMessages({
28
27
  const RenderBlocks = (props) => {
29
28
  const { blockWrapperTag, content, location, isContainer, metadata } = props;
30
29
  const intl = useIntl();
31
- const blocksFieldname = getBlocksFieldname(content);
32
- const blocksLayoutFieldname = getBlocksLayoutFieldname(content);
33
30
  const blocksConfig = props.blocksConfig || config.blocks.blocksConfig;
34
31
  const CustomTag = props.as || React.Fragment;
35
32
 
33
+ const blockList = getBlocks(content);
34
+
36
35
  return hasBlocksData(content) ? (
37
36
  <CustomTag>
38
- {map(content[blocksLayoutFieldname].items, (block) => {
37
+ {map(blockList, ([block, rawBlockData]) => {
39
38
  const Block =
40
- blocksConfig[content[blocksFieldname]?.[block]?.['@type']]?.view ||
41
- ViewDefaultBlock;
39
+ blocksConfig[rawBlockData?.['@type']]?.view || ViewDefaultBlock;
42
40
 
43
41
  const blockData = applyBlockDefaults({
44
- data: content[blocksFieldname][block],
42
+ data: rawBlockData,
45
43
  intl,
46
44
  metadata,
47
45
  properties: content,
48
46
  });
49
47
 
50
- if (content[blocksFieldname]?.[block]?.['@type'] === 'empty') {
48
+ if (rawBlockData?.['@type'] === 'empty') {
51
49
  return (
52
50
  <MaybeWrap
53
51
  key={block}
@@ -91,7 +89,7 @@ const RenderBlocks = (props) => {
91
89
  return (
92
90
  <div key={block}>
93
91
  {intl.formatMessage(messages.unknownBlock, {
94
- block: content[blocksFieldname]?.[block]?.['@type'],
92
+ block: rawBlockData?.['@type'],
95
93
  })}
96
94
  </div>
97
95
  );
@@ -90,14 +90,14 @@ test('Provides path to blocks', () => {
90
90
  expect(container).toMatchSnapshot();
91
91
  });
92
92
 
93
- test('Renders invalid blocks', () => {
93
+ test('Filters out invalid blocks', () => {
94
94
  const store = mockStore({
95
95
  intl: {
96
96
  locale: 'en',
97
97
  messages: {},
98
98
  },
99
99
  });
100
- const { queryAllByText } = render(
100
+ const { queryAllByText, queryByText } = render(
101
101
  <Provider store={store}>
102
102
  <RenderBlocks
103
103
  blocksConfig={{
@@ -113,7 +113,14 @@ test('Renders invalid blocks', () => {
113
113
  }}
114
114
  content={{
115
115
  blocks_layout: {
116
- items: ['MISSING-YOU-1', 'a', 'MISSING-YOU-2'],
116
+ items: [
117
+ 'MISSING-YOU-1',
118
+ 'a',
119
+ 'MISSING-YOU-2',
120
+ null,
121
+ undefined,
122
+ 'undefined',
123
+ ],
117
124
  },
118
125
  blocks: {
119
126
  a: {
@@ -126,7 +133,10 @@ test('Renders invalid blocks', () => {
126
133
  />
127
134
  </Provider>,
128
135
  );
136
+ // Invalid blocks (missing from blocks object or invalid IDs) are filtered out and not rendered
129
137
  expect(
130
138
  queryAllByText('Invalid block - Will be removed on saving'),
131
- ).toHaveLength(2);
139
+ ).toHaveLength(0);
140
+ // Only valid blocks are rendered
141
+ expect(queryByText('id: a - text: bar - path: /foo')).not.toBeNull();
132
142
  });
@@ -119,7 +119,7 @@ let config = {
119
119
  defaultBlockType: 'slate',
120
120
  verticalFormTabs: false,
121
121
  useEmailAsLogin: false,
122
- persistentReducers: ['blocksClipboard'],
122
+ persistentReducers: ['blocksClipboard.cut', 'blocksClipboard.copy'],
123
123
  initialReducersBlacklist: [], // reducers in this list won't be hydrated in windows.__data
124
124
  asyncPropsExtenders: [getSiteAsyncPropExtender], // per route asyncConnect customizers
125
125
  contentIcons: contentIcons,