@plone/volto 17.0.0-alpha.0 → 17.0.0-alpha.10

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 (190) hide show
  1. package/.yarn/install-state.gz +0 -0
  2. package/CHANGELOG.md +451 -19
  3. package/CONTRIBUTING.md +1 -1
  4. package/README.md +12 -15
  5. package/addon-registry.js +34 -0
  6. package/create-theme-addons-loader.js +79 -0
  7. package/cypress/support/commands.js +25 -0
  8. package/locales/ca/LC_MESSAGES/volto.po +187 -6
  9. package/locales/ca.json +1 -1
  10. package/locales/de/LC_MESSAGES/volto.po +206 -25
  11. package/locales/de.json +1 -1
  12. package/locales/en/LC_MESSAGES/volto.po +186 -5
  13. package/locales/en.json +1 -1
  14. package/locales/es/LC_MESSAGES/volto.po +187 -6
  15. package/locales/es.json +1 -1
  16. package/locales/eu/LC_MESSAGES/volto.po +187 -6
  17. package/locales/eu.json +1 -1
  18. package/locales/fi/LC_MESSAGES/volto.po +4792 -0
  19. package/locales/fi.json +1 -1
  20. package/locales/fr/LC_MESSAGES/volto.po +187 -6
  21. package/locales/fr.json +1 -1
  22. package/locales/it/LC_MESSAGES/volto.po +187 -6
  23. package/locales/it.json +1 -1
  24. package/locales/ja/LC_MESSAGES/volto.po +187 -6
  25. package/locales/ja.json +1 -1
  26. package/locales/nl/LC_MESSAGES/volto.po +842 -649
  27. package/locales/nl.json +1 -1
  28. package/locales/pt/LC_MESSAGES/volto.po +187 -6
  29. package/locales/pt.json +1 -1
  30. package/locales/pt_BR/LC_MESSAGES/volto.po +195 -14
  31. package/locales/pt_BR.json +1 -1
  32. package/locales/ro/LC_MESSAGES/volto.po +187 -6
  33. package/locales/ro.json +1 -1
  34. package/locales/volto.pot +187 -6
  35. package/locales/zh_CN/LC_MESSAGES/volto.po +187 -6
  36. package/locales/zh_CN.json +1 -1
  37. package/package-why.json +0 -1
  38. package/package.json +9 -8
  39. package/packages/volto-slate/build/messages/src/blocks/Table/TableBlockEdit.json +1 -1
  40. package/packages/volto-slate/build/messages/src/blocks/Text/DefaultTextBlockEditor.json +1 -1
  41. package/packages/volto-slate/build/messages/src/blocks/Text/DetachedTextBlockEditor.json +1 -1
  42. package/packages/volto-slate/build/messages/src/blocks/Text/SlashMenu.json +1 -1
  43. package/packages/volto-slate/build/messages/src/editor/plugins/AdvancedLink/index.json +1 -1
  44. package/packages/volto-slate/build/messages/src/editor/plugins/Link/index.json +1 -1
  45. package/packages/volto-slate/build/messages/src/editor/plugins/Table/index.json +1 -1
  46. package/packages/volto-slate/build/messages/src/elementEditor/messages.json +1 -1
  47. package/packages/volto-slate/build/messages/src/widgets/HtmlSlateWidget.json +1 -1
  48. package/packages/volto-slate/build/messages/src/widgets/RichTextWidgetView.json +1 -1
  49. package/packages/volto-slate/package.json +1 -1
  50. package/packages/volto-slate/src/blocks/Table/TableBlockView.jsx +4 -4
  51. package/packages/volto-slate/src/blocks/Table/index.js +2 -0
  52. package/packages/volto-slate/src/blocks/Text/SlashMenu.jsx +4 -3
  53. package/packages/volto-slate/src/editor/deserialize.js +0 -1
  54. package/packages/volto-slate/src/editor/plugins/StyleMenu/StyleMenu.jsx +14 -4
  55. package/razzle.config.js +28 -0
  56. package/src/actions/index.js +6 -0
  57. package/src/actions/language/language.js +9 -8
  58. package/src/actions/querystringsearch/querystringsearch.js +20 -14
  59. package/src/actions/relations/rebuild.js +25 -0
  60. package/src/actions/relations/relations.js +86 -0
  61. package/src/actions/relations/relations.test.js +15 -0
  62. package/src/components/index.js +1 -0
  63. package/src/components/manage/Add/Add.jsx +2 -2
  64. package/src/components/manage/BlockChooser/BlockChooser.jsx +14 -5
  65. package/src/components/manage/BlockChooser/BlockChooser.test.jsx +5 -0
  66. package/src/components/manage/BlockChooser/BlockChooserButton.jsx +63 -29
  67. package/src/components/manage/BlockChooser/BlockChooserSearch.jsx +0 -1
  68. package/src/components/manage/Blocks/Listing/Edit.jsx +0 -19
  69. package/src/components/manage/Blocks/Listing/ListingBody.jsx +77 -61
  70. package/src/components/manage/Blocks/Listing/View.jsx +0 -4
  71. package/src/components/manage/Blocks/Listing/getAsyncData.js +10 -2
  72. package/src/components/manage/Blocks/Listing/withQuerystringResults.jsx +18 -13
  73. package/src/components/manage/Blocks/Search/SearchBlockEdit.jsx +5 -4
  74. package/src/components/manage/Blocks/Search/SearchBlockView.jsx +2 -1
  75. package/src/components/manage/Blocks/Search/components/DateRangeFacet.jsx +4 -1
  76. package/src/components/manage/Blocks/Search/components/Facets.jsx +58 -2
  77. package/src/components/manage/Blocks/Search/hocs/withSearch.jsx +24 -11
  78. package/src/components/manage/Blocks/Search/layout/LeftColumnFacets.jsx +17 -5
  79. package/src/components/manage/Blocks/Search/layout/RightColumnFacets.jsx +17 -5
  80. package/src/components/manage/Blocks/Search/layout/TopSideFacets.jsx +21 -5
  81. package/src/components/manage/Blocks/Search/schema.js +16 -1
  82. package/src/components/manage/Blocks/ToC/Edit.jsx +1 -0
  83. package/src/components/manage/Contents/Contents.jsx +69 -33
  84. package/src/components/manage/Contents/ContentsItem.jsx +6 -0
  85. package/src/components/manage/Controlpanels/AddonsControlpanel.jsx +3 -3
  86. package/src/components/manage/Controlpanels/Controlpanels.jsx +199 -224
  87. package/src/components/manage/Controlpanels/Controlpanels.test.jsx +46 -7
  88. package/src/components/manage/Controlpanels/Relations/BrokenRelations.jsx +66 -0
  89. package/src/components/manage/Controlpanels/Relations/Relations.jsx +114 -0
  90. package/src/components/manage/Controlpanels/Relations/RelationsListing.jsx +479 -0
  91. package/src/components/manage/Controlpanels/Relations/RelationsMatrix.jsx +531 -0
  92. package/src/components/manage/Controlpanels/Users/UserGroupMembershipControlPanel.jsx +3 -3
  93. package/src/components/manage/Controlpanels/Users/UserGroupMembershipListing.jsx +51 -82
  94. package/src/components/manage/Controlpanels/Users/UserGroupMembershipMatrix.jsx +79 -75
  95. package/src/components/manage/DragDropList/DragDropList.jsx +63 -42
  96. package/src/components/manage/Form/BlocksToolbar.jsx +5 -1
  97. package/src/components/manage/Form/Form.jsx +11 -5
  98. package/src/components/manage/Form/InlineForm.jsx +39 -9
  99. package/src/components/manage/Form/InlineFormState.js +8 -0
  100. package/src/components/manage/History/History.jsx +35 -18
  101. package/src/components/manage/Multilingual/CreateTranslation.jsx +2 -2
  102. package/src/components/manage/Multilingual/TranslationObject.jsx +4 -3
  103. package/src/components/manage/Preferences/ChangePassword.jsx +2 -2
  104. package/src/components/manage/Preferences/PersonalPreferences.jsx +2 -2
  105. package/src/components/manage/Toast/Toast.jsx +1 -1
  106. package/src/components/manage/Toolbar/Types.jsx +2 -2
  107. package/src/components/manage/Widgets/DatetimeWidget.jsx +9 -5
  108. package/src/components/manage/Widgets/ObjectListWidget.jsx +3 -8
  109. package/src/components/manage/Widgets/RecurrenceWidget/ByDayField.jsx +2 -1
  110. package/src/components/manage/Widgets/RecurrenceWidget/MonthOfTheYearField.jsx +2 -1
  111. package/src/components/manage/Widgets/RecurrenceWidget/Occurences.jsx +2 -1
  112. package/src/components/manage/Widgets/RecurrenceWidget/RecurrenceWidget.jsx +7 -2
  113. package/src/components/manage/Widgets/RecurrenceWidget/WeekdayOfTheMonthField.jsx +2 -1
  114. package/src/components/manage/Widgets/SelectUtils.js +1 -1
  115. package/src/components/manage/Widgets/SelectWidget.jsx +1 -1
  116. package/src/components/theme/Footer/Footer.jsx +2 -13
  117. package/src/components/theme/Header/Header.jsx +37 -63
  118. package/src/components/theme/Header/Header.test.jsx +18 -0
  119. package/src/components/theme/Icon/Icon.jsx +2 -2
  120. package/src/components/theme/LanguageSelector/LanguageSelector.js +8 -3
  121. package/src/components/theme/Login/Login.jsx +1 -0
  122. package/src/components/theme/Logo/Logo.jsx +2 -1
  123. package/src/components/theme/MultilingualRedirector/MultilingualRedirector.jsx +2 -2
  124. package/src/components/theme/Navigation/NavItem.jsx +4 -2
  125. package/src/components/theme/NotFound/NotFound.jsx +55 -41
  126. package/src/components/theme/PasswordReset/PasswordReset.jsx +7 -4
  127. package/src/components/theme/PasswordReset/RequestPasswordReset.jsx +1 -1
  128. package/src/components/theme/Sitemap/Sitemap.jsx +5 -3
  129. package/src/components/theme/View/DefaultView.jsx +1 -1
  130. package/src/components/theme/View/EventDatesInfo.jsx +2 -1
  131. package/src/components/theme/View/EventView.jsx +1 -1
  132. package/src/components/theme/View/NewsItemView.jsx +1 -1
  133. package/src/components/theme/View/RenderBlocks.jsx +7 -1
  134. package/src/components/theme/Widgets/DateWidget.jsx +2 -1
  135. package/src/components/theme/Widgets/DatetimeWidget.jsx +2 -1
  136. package/src/components/theme/Widgets/RelationsWidget.jsx +13 -11
  137. package/src/config/ControlPanels.js +2 -0
  138. package/src/config/Widgets.jsx +1 -0
  139. package/src/config/index.js +3 -0
  140. package/src/config/server.js +19 -0
  141. package/src/constants/ActionTypes.js +4 -0
  142. package/src/constants/Languages.js +8 -4
  143. package/src/express-middleware/devproxy.js +4 -2
  144. package/src/express-middleware/sitemap.js +36 -4
  145. package/src/express-middleware/static.js +32 -0
  146. package/src/helpers/Api/Api.js +1 -1
  147. package/src/helpers/FormValidation/FormValidation.js +11 -2
  148. package/src/helpers/FormValidation/FormValidation.test.js +73 -0
  149. package/src/helpers/Html/Html.jsx +3 -1
  150. package/src/helpers/Html/Html.test.jsx +5 -0
  151. package/src/helpers/MessageLabels/MessageLabels.js +72 -0
  152. package/src/helpers/Robots/Robots.js +24 -6
  153. package/src/helpers/Sitemap/Sitemap.js +44 -2
  154. package/src/helpers/Url/Url.js +27 -6
  155. package/src/helpers/Url/Url.test.js +26 -0
  156. package/src/helpers/Utils/Utils.js +38 -13
  157. package/src/helpers/Utils/Utils.test.js +4 -4
  158. package/src/helpers/index.js +7 -2
  159. package/src/hooks/userSession/useToken.js +5 -0
  160. package/src/middleware/Api.test.js +54 -0
  161. package/src/middleware/api.js +8 -4
  162. package/src/reducers/actions/actions.js +1 -1
  163. package/src/reducers/breadcrumbs/breadcrumbs.js +1 -1
  164. package/src/reducers/index.js +2 -0
  165. package/src/reducers/navigation/navigation.js +1 -1
  166. package/src/reducers/relations/relations.js +173 -0
  167. package/src/reducers/types/types.js +1 -1
  168. package/src/routes.js +5 -0
  169. package/src/server.jsx +29 -30
  170. package/src/start-server.js +4 -2
  171. package/test-setup-config.js +1 -0
  172. package/theme/themes/pastanaga/extras/blocks.less +0 -9
  173. package/theme/themes/pastanaga/extras/contents.less +1 -0
  174. package/theme/themes/pastanaga/extras/main.less +80 -1
  175. package/theme/themes/pastanaga/extras/search.less +6 -0
  176. package/theme/themes/pastanaga/extras/sidebar.less +4 -0
  177. package/theme/themes/pastanaga/extras/userscontrolpanel.less +99 -76
  178. package/.changelog.draft +0 -22
  179. package/.editorconfig +0 -36
  180. package/.storybook/main.js +0 -127
  181. package/.storybook/manager.js +0 -15
  182. package/.storybook/preview.js +0 -21
  183. package/.storybook/static/previewImage.svg +0 -48
  184. package/.yarnrc.yml +0 -5
  185. package/jsdoc.json +0 -16
  186. package/netlify.toml +0 -5
  187. package/pyvenv.cfg +0 -3
  188. package/share/man/man1/ttx.1 +0 -225
  189. package/src/components/theme/Header/Header.md +0 -27
  190. package/towncrier.toml +0 -33
@@ -3,16 +3,16 @@
3
3
  * @module components/manage/Controlpanels/Controlpanels
4
4
  */
5
5
 
6
- import React, { Component } from 'react';
7
6
  import PropTypes from 'prop-types';
7
+ import { useState, useEffect } from 'react';
8
8
  import { connect } from 'react-redux';
9
9
  import { compose } from 'redux';
10
10
  import { Link } from 'react-router-dom';
11
11
  import { concat, filter, last, map, uniqBy } from 'lodash';
12
12
  import { Portal } from 'react-portal';
13
- import { Helmet } from '@plone/volto/helpers';
13
+ import { asyncConnect, Helmet } from '@plone/volto/helpers';
14
14
  import { Container, Grid, Header, Message, Segment } from 'semantic-ui-react';
15
- import { FormattedMessage, defineMessages, injectIntl } from 'react-intl';
15
+ import { FormattedMessage, defineMessages, useIntl } from 'react-intl';
16
16
 
17
17
  import { listControlpanels, getSystemInformation } from '@plone/volto/actions';
18
18
  import { Error, Icon, Toolbar, VersionOverview } from '@plone/volto/components';
@@ -90,189 +90,139 @@ const messages = defineMessages({
90
90
  id: 'Content Rules',
91
91
  defaultMessage: 'Content Rules',
92
92
  },
93
+ relations: {
94
+ id: 'Relations',
95
+ defaultMessage: 'Relations',
96
+ },
93
97
  });
94
98
 
95
99
  /**
96
100
  * Controlpanels container class.
97
- * @class Controlpanels
98
- * @extends Component
99
101
  */
100
- class Controlpanels extends Component {
101
- /**
102
- * Property types.
103
- * @property {Object} propTypes Property types.
104
- * @static
105
- */
106
- static propTypes = {
107
- listControlpanels: PropTypes.func.isRequired,
108
- controlpanels: PropTypes.arrayOf(
109
- PropTypes.shape({
110
- '@id': PropTypes.string,
111
- group: PropTypes.string,
112
- title: PropTypes.string,
113
- }),
114
- ).isRequired,
115
- pathname: PropTypes.string.isRequired,
116
- };
102
+ function Controlpanels({
103
+ controlpanels,
104
+ controlpanelsRequest,
105
+ systemInformation,
106
+ pathname,
107
+ }) {
108
+ const intl = useIntl();
109
+ const [isClient, setIsClient] = useState(false);
117
110
 
118
- /**
119
- * Constructor
120
- * @method constructor
121
- * @param {Object} props Component properties
122
- * @constructs EditComponent
123
- */
124
- constructor(props) {
125
- super(props);
126
- this.state = {
127
- error: null,
128
- isClient: false,
129
- };
130
- }
111
+ useEffect(() => {
112
+ setIsClient(true);
113
+ }, []);
131
114
 
132
- /**
133
- * Component did mount
134
- * @method componentDidMount
135
- * @returns {undefined}
136
- */
137
- componentDidMount() {
138
- this.props.listControlpanels();
139
- this.props.getSystemInformation();
140
- this.setState({ isClient: true });
141
- }
115
+ const error = controlpanelsRequest?.error;
142
116
 
143
- UNSAFE_componentWillReceiveProps(nextProps) {
144
- // Error
145
- if (
146
- this.props.controlpanelsRequest.loading &&
147
- nextProps.controlpanelsRequest.error
148
- ) {
149
- this.setState({
150
- error: nextProps.controlpanelsRequest.error,
151
- });
152
- }
117
+ if (error) {
118
+ return <Error error={error} />;
153
119
  }
154
120
 
155
- /**
156
- * Render method.
157
- * @method render
158
- * @returns {string} Markup for the component.
159
- */
160
- render() {
161
- // Error
162
- if (this.state.error) {
163
- return <Error error={this.state.error} />;
164
- }
121
+ let customcontrolpanels = config.settings.controlpanels
122
+ ? config.settings.controlpanels.map((el) => {
123
+ el.group =
124
+ intl.formatMessage({
125
+ id: el.group,
126
+ defaultMessage: el.group,
127
+ }) || el.group;
128
+ return el;
129
+ })
130
+ : [];
131
+ const { filterControlPanels } = config.settings;
165
132
 
166
- let customcontrolpanels = config.settings.controlpanels
167
- ? config.settings.controlpanels.map((el) => {
168
- el.group =
169
- this.props.intl.formatMessage({
170
- id: el.group,
171
- defaultMessage: el.group,
172
- }) || el.group;
173
- return el;
174
- })
175
- : [];
176
- const { filterControlPanels } = config.settings;
177
- const controlpanels = map(
178
- concat(
179
- filterControlPanels(this.props.controlpanels),
180
- customcontrolpanels,
181
- [
182
- {
183
- '@id': '/addons',
184
- group: this.props.intl.formatMessage(messages.general),
185
- title: this.props.intl.formatMessage(messages.addons),
186
- },
187
- {
188
- '@id': '/database',
189
- group: this.props.intl.formatMessage(messages.general),
190
- title: this.props.intl.formatMessage(messages.database),
191
- },
192
- {
193
- '@id': '/rules',
194
- group: this.props.intl.formatMessage(messages.content),
195
- title: this.props.intl.formatMessage(messages.contentRules),
196
- },
197
- {
198
- '@id': '/undo',
199
- group: this.props.intl.formatMessage(messages.general),
200
- title: this.props.intl.formatMessage(messages.undo),
201
- },
202
- {
203
- '@id': '/aliases',
204
- group: this.props.intl.formatMessage(messages.general),
205
- title: this.props.intl.formatMessage(messages.urlmanagement),
206
- },
207
- {
208
- '@id': '/moderate-comments',
209
- group: this.props.intl.formatMessage(messages.content),
210
- title: this.props.intl.formatMessage(messages.moderatecomments),
211
- },
212
- {
213
- '@id': '/users',
214
- group: this.props.intl.formatMessage(
215
- messages.usersControlPanelCategory,
216
- ),
217
- title: this.props.intl.formatMessage(messages.users),
218
- },
219
- {
220
- '@id': '/usergroupmembership',
221
- group: this.props.intl.formatMessage(
222
- messages.usersControlPanelCategory,
223
- ),
224
- title: this.props.intl.formatMessage(
225
- messages.usergroupmemberbership,
226
- ),
227
- },
228
- {
229
- '@id': '/groups',
230
- group: this.props.intl.formatMessage(
231
- messages.usersControlPanelCategory,
232
- ),
233
- title: this.props.intl.formatMessage(messages.groups),
234
- },
235
- ],
236
- ),
237
- (controlpanel) => ({
238
- ...controlpanel,
239
- id: last(controlpanel['@id'].split('/')),
240
- }),
241
- );
242
- const groups = map(uniqBy(controlpanels, 'group'), 'group');
243
- const { controlPanelsIcons: icons } = config.settings;
133
+ const filteredControlPanels = map(
134
+ concat(filterControlPanels(controlpanels), customcontrolpanels, [
135
+ {
136
+ '@id': '/addons',
137
+ group: intl.formatMessage(messages.general),
138
+ title: intl.formatMessage(messages.addons),
139
+ },
140
+ {
141
+ '@id': '/database',
142
+ group: intl.formatMessage(messages.general),
143
+ title: intl.formatMessage(messages.database),
144
+ },
145
+ {
146
+ '@id': '/rules',
147
+ group: intl.formatMessage(messages.content),
148
+ title: intl.formatMessage(messages.contentRules),
149
+ },
150
+ {
151
+ '@id': '/undo',
152
+ group: intl.formatMessage(messages.general),
153
+ title: intl.formatMessage(messages.undo),
154
+ },
155
+ {
156
+ '@id': '/aliases',
157
+ group: intl.formatMessage(messages.general),
158
+ title: intl.formatMessage(messages.urlmanagement),
159
+ },
160
+ {
161
+ '@id': '/relations',
162
+ group: intl.formatMessage(messages.content),
163
+ title: intl.formatMessage(messages.relations),
164
+ },
165
+ {
166
+ '@id': '/moderate-comments',
167
+ group: intl.formatMessage(messages.content),
168
+ title: intl.formatMessage(messages.moderatecomments),
169
+ },
170
+ {
171
+ '@id': '/users',
172
+ group: intl.formatMessage(messages.usersControlPanelCategory),
173
+ title: intl.formatMessage(messages.users),
174
+ },
175
+ {
176
+ '@id': '/usergroupmembership',
177
+ group: intl.formatMessage(messages.usersControlPanelCategory),
178
+ title: intl.formatMessage(messages.usergroupmemberbership),
179
+ },
180
+ {
181
+ '@id': '/groups',
182
+ group: intl.formatMessage(messages.usersControlPanelCategory),
183
+ title: intl.formatMessage(messages.groups),
184
+ },
185
+ ]),
186
+ (controlpanel) => ({
187
+ ...controlpanel,
188
+ id: last(controlpanel['@id'].split('/')),
189
+ }),
190
+ );
191
+ const groups = map(uniqBy(filteredControlPanels, 'group'), 'group');
192
+ const { controlPanelsIcons: icons } = config.settings;
244
193
 
245
- return (
246
- <div className="view-wrapper">
247
- <Helmet title={this.props.intl.formatMessage(messages.sitesetup)} />
248
- <Container className="controlpanel">
249
- <Segment.Group raised>
250
- <Segment className="primary">
251
- <FormattedMessage id="Site Setup" defaultMessage="Site Setup" />
252
- </Segment>
253
- {this.props.systemInformation &&
254
- this.props.systemInformation.upgrade && (
255
- <Message attached warning>
256
- <FormattedMessage
257
- id="The site configuration is outdated and needs to be upgraded."
258
- defaultMessage="The site configuration is outdated and needs to be upgraded."
259
- />{' '}
260
- <Link to={`/controlpanel/plone-upgrade`}>
261
- <FormattedMessage
262
- id="Please continue with the upgrade."
263
- defaultMessage="Please continue with the upgrade."
264
- />
265
- </Link>
266
- </Message>
267
- )}
268
- {map(groups, (group) => [
269
- <Segment key={`header-${group}`} secondary>
270
- {group}
271
- </Segment>,
272
- <Segment key={`body-${group}`} attached>
273
- <Grid columns={6}>
274
- <Grid.Row>
275
- {map(filter(controlpanels, { group }), (controlpanel) => (
194
+ return (
195
+ <div className="view-wrapper">
196
+ <Helmet title={intl.formatMessage(messages.sitesetup)} />
197
+ <Container className="controlpanel">
198
+ <Segment.Group raised>
199
+ <Segment className="primary">
200
+ <FormattedMessage id="Site Setup" defaultMessage="Site Setup" />
201
+ </Segment>
202
+ {systemInformation && systemInformation.upgrade && (
203
+ <Message attached warning>
204
+ <FormattedMessage
205
+ id="The site configuration is outdated and needs to be upgraded."
206
+ defaultMessage="The site configuration is outdated and needs to be upgraded."
207
+ />{' '}
208
+ <Link to={`/controlpanel/plone-upgrade`}>
209
+ <FormattedMessage
210
+ id="Please continue with the upgrade."
211
+ defaultMessage="Please continue with the upgrade."
212
+ />
213
+ </Link>
214
+ </Message>
215
+ )}
216
+ {map(groups, (group) => [
217
+ <Segment key={`header-${group}`} secondary>
218
+ {group}
219
+ </Segment>,
220
+ <Segment key={`body-${group}`} attached>
221
+ <Grid doubling columns={6}>
222
+ <Grid.Row>
223
+ {map(
224
+ filter(filteredControlPanels, { group }),
225
+ (controlpanel) => (
276
226
  <Grid.Column key={controlpanel.id}>
277
227
  <Link to={`/controlpanel/${controlpanel.id}`}>
278
228
  <Header as="h3" icon textAlign="center">
@@ -286,58 +236,83 @@ class Controlpanels extends Component {
286
236
  </Header>
287
237
  </Link>
288
238
  </Grid.Column>
289
- ))}
290
- </Grid.Row>
291
- </Grid>
292
- </Segment>,
293
- ])}
294
- </Segment.Group>
295
- <Segment.Group raised>
296
- <Segment className="primary">
297
- <FormattedMessage
298
- id="Version Overview"
299
- defaultMessage="Version Overview"
300
- />
301
- </Segment>
302
- <Segment attached>
303
- {this.props.systemInformation ? (
304
- <VersionOverview {...this.props.systemInformation} />
305
- ) : null}
306
- </Segment>
307
- </Segment.Group>
308
- </Container>
309
- {this.state.isClient && (
310
- <Portal node={document.getElementById('toolbar')}>
311
- <Toolbar
312
- pathname={this.props.pathname}
313
- hideDefaultViewButtons
314
- inner={
315
- <Link to="/" className="item">
316
- <Icon
317
- name={backSVG}
318
- className="contents circled"
319
- size="30px"
320
- title={this.props.intl.formatMessage(messages.back)}
321
- />
322
- </Link>
323
- }
239
+ ),
240
+ )}
241
+ </Grid.Row>
242
+ </Grid>
243
+ </Segment>,
244
+ ])}
245
+ </Segment.Group>
246
+ <Segment.Group raised>
247
+ <Segment className="primary">
248
+ <FormattedMessage
249
+ id="Version Overview"
250
+ defaultMessage="Version Overview"
324
251
  />
325
- </Portal>
326
- )}
327
- </div>
328
- );
329
- }
252
+ </Segment>
253
+ <Segment attached>
254
+ {systemInformation ? (
255
+ <VersionOverview {...systemInformation} />
256
+ ) : null}
257
+ </Segment>
258
+ </Segment.Group>
259
+ </Container>
260
+ {isClient && (
261
+ <Portal node={document.getElementById('toolbar')}>
262
+ <Toolbar
263
+ pathname={pathname}
264
+ hideDefaultViewButtons
265
+ inner={
266
+ <Link to="/" className="item">
267
+ <Icon
268
+ name={backSVG}
269
+ className="contents circled"
270
+ size="30px"
271
+ title={intl.formatMessage(messages.back)}
272
+ />
273
+ </Link>
274
+ }
275
+ />
276
+ </Portal>
277
+ )}
278
+ </div>
279
+ );
330
280
  }
331
281
 
332
- export default compose(
333
- injectIntl,
334
- connect(
335
- (state, props) => ({
336
- controlpanels: state.controlpanels.controlpanels,
337
- controlpanelsRequest: state.controlpanels.list,
338
- pathname: props.location.pathname,
339
- systemInformation: state.controlpanels.systeminformation,
282
+ /**
283
+ * Property types.
284
+ * @property {Object} propTypes Property types.
285
+ * @static
286
+ */
287
+ Controlpanels.propTypes = {
288
+ controlpanels: PropTypes.arrayOf(
289
+ PropTypes.shape({
290
+ '@id': PropTypes.string,
291
+ group: PropTypes.string,
292
+ title: PropTypes.string,
340
293
  }),
341
- { listControlpanels, getSystemInformation },
342
- ),
294
+ ).isRequired,
295
+ pathname: PropTypes.string.isRequired,
296
+ };
297
+
298
+ export default compose(
299
+ connect((state, props) => ({
300
+ controlpanels: state.controlpanels.controlpanels,
301
+ controlpanelsRequest: state.controlpanels.list,
302
+ pathname: props.location.pathname,
303
+ systemInformation: state.controlpanels.systeminformation,
304
+ })),
305
+
306
+ asyncConnect([
307
+ {
308
+ key: 'controlpanels',
309
+ promise: async ({ location, store: { dispatch } }) =>
310
+ await dispatch(listControlpanels()),
311
+ },
312
+ {
313
+ key: 'systemInformation',
314
+ promise: async ({ location, store: { dispatch } }) =>
315
+ await dispatch(getSystemInformation()),
316
+ },
317
+ ]),
343
318
  )(Controlpanels);
@@ -1,11 +1,11 @@
1
1
  import React from 'react';
2
- import renderer from 'react-test-renderer';
3
- import configureStore from 'redux-mock-store';
4
2
  import { Provider } from 'react-intl-redux';
5
3
  import { MemoryRouter } from 'react-router-dom';
4
+ import renderer from 'react-test-renderer';
5
+ import configureStore from 'redux-mock-store';
6
6
 
7
- import Controlpanels from './Controlpanels';
8
7
  import config from '@plone/volto/registry';
8
+ import Controlpanels from './Controlpanels';
9
9
 
10
10
  const mockStore = configureStore();
11
11
 
@@ -20,7 +20,30 @@ jest.mock('./VersionOverview', () =>
20
20
  describe('Controlpanels', () => {
21
21
  it('renders a controlpanels component', () => {
22
22
  const store = mockStore({
23
- controlpanels: {
23
+ controlpanels: [
24
+ {
25
+ '@id': 'http://localhost:8080/Plone/@controlpanels/date-and-time',
26
+ group: 'General',
27
+ title: 'Date and Time',
28
+ },
29
+ {
30
+ '@id': 'http://localhost:8080/Plone/@controlpanels/lang',
31
+ group: 'General',
32
+ title: 'Language',
33
+ },
34
+ {
35
+ '@id': 'http://localhost:8080/Plone/@controlpanels/editing',
36
+ group: 'Content',
37
+ title: 'Editing',
38
+ },
39
+ {
40
+ '@id': 'http://localhost:8080/Plone/@controlpanels/security',
41
+ group: 'Security',
42
+ title: 'test',
43
+ },
44
+ ],
45
+ reduxAsyncConnect: {
46
+ // Mocked in redux async connect as it isn't fetch client-side.
24
47
  controlpanels: [
25
48
  {
26
49
  '@id': 'http://localhost:8080/Plone/@controlpanels/date-and-time',
@@ -40,10 +63,11 @@ describe('Controlpanels', () => {
40
63
  {
41
64
  '@id': 'http://localhost:8080/Plone/@controlpanels/security',
42
65
  group: 'Security',
43
- title: 'Security',
66
+ title: 'test',
44
67
  },
45
68
  ],
46
69
  },
70
+ router: { location: '/blog' },
47
71
  intl: {
48
72
  locale: 'en',
49
73
  messages: {},
@@ -62,9 +86,24 @@ describe('Controlpanels', () => {
62
86
 
63
87
  it('renders an additional control panel', () => {
64
88
  const store = mockStore({
65
- controlpanels: {
66
- controlpanels: [],
89
+ controlpanels: [
90
+ {
91
+ '@id': 'http://localhost:8080/Plone/@controlpanels/security',
92
+ group: 'Security',
93
+ title: 'test',
94
+ },
95
+ ],
96
+ reduxAsyncConnect: {
97
+ // Mocked in redux async connect as it isn't fetch client-side.
98
+ controlpanels: [
99
+ {
100
+ '@id': 'http://localhost:8080/Plone/@controlpanels/security',
101
+ group: 'Security',
102
+ title: 'test',
103
+ },
104
+ ],
67
105
  },
106
+ router: { location: '/blog' },
68
107
  intl: {
69
108
  locale: 'en',
70
109
  messages: {},
@@ -0,0 +1,66 @@
1
+ import React, { useEffect } from 'react';
2
+ import { uniqBy } from 'lodash';
3
+ import { FormattedMessage } from 'react-intl';
4
+ import { useSelector, useDispatch } from 'react-redux';
5
+ import { Divider, Segment, Table } from 'semantic-ui-react';
6
+ import { queryRelations } from '@plone/volto/actions';
7
+ import { flattenToAppURL } from '@plone/volto/helpers';
8
+ import { UniversalLink } from '@plone/volto/components';
9
+
10
+ const BrokenRelations = () => {
11
+ const dispatch = useDispatch();
12
+ const brokenRelationStats = useSelector(
13
+ (state) => state.relations?.stats?.broken || {},
14
+ );
15
+ const brokenRelations = useSelector(
16
+ (state) => state.relations?.subrequests?.broken?.relations,
17
+ );
18
+
19
+ useEffect(() => {
20
+ dispatch(queryRelations(null, true, 'broken'));
21
+ }, [dispatch]);
22
+
23
+ return brokenRelations && Object.keys(brokenRelations).length > 0 ? (
24
+ <>
25
+ <Divider section hidden />
26
+ <Segment>
27
+ <h3>
28
+ <FormattedMessage
29
+ id="Broken relations"
30
+ defaultMessage="Broken relations"
31
+ />
32
+ </h3>
33
+ {Object.keys(brokenRelations).map((relationname) => (
34
+ <div key={relationname}>
35
+ <Divider section hidden />
36
+ <h4>
37
+ {brokenRelationStats[relationname]} broken <i>{relationname}</i>{' '}
38
+ relations
39
+ </h4>
40
+ <Table>
41
+ <Table.Body>
42
+ {uniqBy(brokenRelations[relationname].items, function (el) {
43
+ return el[0];
44
+ }).map((el) => (
45
+ <Table.Row key={el[0]}>
46
+ <Table.Cell>
47
+ <UniversalLink
48
+ href={`${flattenToAppURL(el[0])}/edit`}
49
+ openLinkInNewTab={true}
50
+ >
51
+ {flattenToAppURL(el[0])}
52
+ </UniversalLink>
53
+ </Table.Cell>
54
+ <Table.Cell>{el[1]}</Table.Cell>
55
+ </Table.Row>
56
+ ))}
57
+ </Table.Body>
58
+ </Table>
59
+ </div>
60
+ ))}
61
+ </Segment>
62
+ </>
63
+ ) : null;
64
+ };
65
+
66
+ export default BrokenRelations;