@plone/volto 17.0.0-alpha.26 → 17.0.0-alpha.28

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 (154) hide show
  1. package/.eslintrc +26 -3
  2. package/.yarn/install-state.gz +0 -0
  3. package/.yarn/plugins/@yarnpkg/plugin-interactive-tools.cjs +541 -0
  4. package/.yarn/releases/yarn-3.6.3.cjs +874 -0
  5. package/CHANGELOG.md +60 -0
  6. package/addon-registry.js +10 -1
  7. package/create-addons-loader.js +1 -1
  8. package/locales/ca/LC_MESSAGES/volto.po +57 -31
  9. package/locales/ca.json +1 -1
  10. package/locales/de/LC_MESSAGES/volto.po +58 -32
  11. package/locales/de.json +1 -1
  12. package/locales/en/LC_MESSAGES/volto.po +57 -31
  13. package/locales/en.json +1 -1
  14. package/locales/es/LC_MESSAGES/volto.po +65 -39
  15. package/locales/es.json +1 -1
  16. package/locales/eu/LC_MESSAGES/volto.po +57 -31
  17. package/locales/eu.json +1 -1
  18. package/locales/fi/LC_MESSAGES/volto.po +57 -31
  19. package/locales/fi.json +1 -1
  20. package/locales/fr/LC_MESSAGES/volto.po +57 -31
  21. package/locales/fr.json +1 -1
  22. package/locales/it/LC_MESSAGES/volto.po +57 -31
  23. package/locales/it.json +1 -1
  24. package/locales/ja/LC_MESSAGES/volto.po +57 -31
  25. package/locales/ja.json +1 -1
  26. package/locales/nl/LC_MESSAGES/volto.po +57 -31
  27. package/locales/nl.json +1 -1
  28. package/locales/pt/LC_MESSAGES/volto.po +57 -31
  29. package/locales/pt.json +1 -1
  30. package/locales/pt_BR/LC_MESSAGES/volto.po +57 -31
  31. package/locales/pt_BR.json +1 -1
  32. package/locales/ro/LC_MESSAGES/volto.po +57 -31
  33. package/locales/ro.json +1 -1
  34. package/locales/volto.pot +62 -32
  35. package/locales/zh_CN/LC_MESSAGES/volto.po +57 -31
  36. package/locales/zh_CN.json +1 -1
  37. package/package.json +35 -26
  38. package/packages/volto-slate/package.json +1 -1
  39. package/packages/volto-slate/src/blocks/Text/TextBlockView.jsx +2 -1
  40. package/packages/volto-slate/src/blocks/Text/index.js +0 -5
  41. package/packages/volto-slate/src/editor/plugins/Link/render.jsx +5 -6
  42. package/packages/volto-slate/src/editor/render.jsx +11 -1
  43. package/razzle.config.js +4 -6
  44. package/src/components/index.js +194 -194
  45. package/src/components/manage/Add/Add.jsx +7 -8
  46. package/src/components/manage/Blocks/Block/Settings.test.jsx +17 -15
  47. package/src/components/manage/Blocks/HTML/Edit.jsx +8 -8
  48. package/src/components/manage/Blocks/HeroImageLeft/Edit.jsx +30 -25
  49. package/src/components/manage/Blocks/Listing/ListingBody.jsx +6 -4
  50. package/src/components/manage/Blocks/Maps/Edit.test.jsx +1 -2
  51. package/src/components/manage/Blocks/Maps/View.test.jsx +1 -2
  52. package/src/components/manage/Blocks/Search/components/Facets.jsx +2 -3
  53. package/src/components/manage/Blocks/Search/components/FilterList.jsx +4 -6
  54. package/src/components/manage/Blocks/Search/components/SelectFacet.jsx +2 -9
  55. package/src/components/manage/Blocks/Search/hocs/withQueryString.jsx +3 -0
  56. package/src/components/manage/Blocks/Search/hocs/withSearch.jsx +8 -6
  57. package/src/components/manage/Blocks/Search/schema.js +13 -13
  58. package/src/components/manage/Blocks/Table/Cell.jsx +2 -3
  59. package/src/components/manage/Blocks/Text/Edit.jsx +2 -3
  60. package/src/components/manage/Blocks/Title/View.jsx +4 -37
  61. package/src/components/manage/Blocks/ToC/View.jsx +1 -0
  62. package/src/components/manage/Blocks/ToC/variations/DefaultTocRenderer.jsx +3 -2
  63. package/src/components/manage/Blocks/ToC/variations/HorizontalMenu.jsx +3 -2
  64. package/src/components/manage/Contents/Contents.jsx +252 -114
  65. package/src/components/manage/Contents/ContentsPropertiesModal.jsx +90 -154
  66. package/src/components/manage/Contents/ContentsRenameModal.jsx +88 -139
  67. package/src/components/manage/Contents/ContentsRenameModal.stories.jsx +61 -0
  68. package/src/components/manage/Contents/ContentsTagsModal.jsx +83 -130
  69. package/src/components/manage/Contents/ContentsTagsModal.stories.jsx +68 -0
  70. package/src/components/manage/Contents/ContentsUploadModal.jsx +1 -2
  71. package/src/components/manage/Contents/ContentsWorkflowModal.jsx +87 -154
  72. package/src/components/manage/Controlpanels/Aliases.jsx +4 -12
  73. package/src/components/manage/Controlpanels/Rules/AddRule.jsx +2 -9
  74. package/src/components/manage/Controlpanels/UndoControlpanel.jsx +6 -9
  75. package/src/components/manage/Form/BlockDataForm.test.jsx +17 -15
  76. package/src/components/manage/Form/Form.jsx +2 -3
  77. package/src/components/manage/Form/InlineForm.test.jsx +16 -14
  78. package/src/components/manage/LockingToastsFactory/LockingToastsFactory.jsx +1 -2
  79. package/src/components/manage/Sharing/Sharing.jsx +7 -0
  80. package/src/components/manage/Sidebar/Sidebar.jsx +139 -220
  81. package/src/components/manage/Toolbar/More.jsx +12 -12
  82. package/src/components/manage/Toolbar/PersonalTools.jsx +97 -155
  83. package/src/components/manage/Toolbar/Toolbar.jsx +2 -2
  84. package/src/components/manage/UniversalLink/UniversalLink.test.jsx +2 -1
  85. package/src/components/manage/Widgets/AlignWidget.jsx +2 -4
  86. package/src/components/manage/Widgets/ColorPickerWidget.test.jsx +9 -7
  87. package/src/components/manage/Widgets/DatetimeWidget.jsx +2 -8
  88. package/src/components/manage/Widgets/IdWidget.jsx +1 -2
  89. package/src/components/manage/Widgets/ObjectBrowserWidget.jsx +2 -9
  90. package/src/components/manage/Widgets/RecurrenceWidget/RecurrenceWidget.jsx +2 -9
  91. package/src/components/manage/Widgets/RecurrenceWidget/WeekdayOfTheMonthIndexField.jsx +4 -4
  92. package/src/components/manage/Widgets/SchemaWidget.jsx +6 -9
  93. package/src/components/manage/Widgets/WysiwygWidget.jsx +2 -9
  94. package/src/components/theme/Comments/Comments.jsx +3 -10
  95. package/src/components/theme/ContentMetadataTags/ContentMetadataTags.jsx +4 -0
  96. package/src/components/theme/Login/Login.jsx +1 -2
  97. package/src/components/theme/PasswordReset/PasswordReset.jsx +1 -2
  98. package/src/components/theme/PreviewImage/PreviewImage.jsx +10 -5
  99. package/src/components/theme/PreviewImage/PreviewImage.test.js +17 -0
  100. package/src/components/theme/Register/Register.jsx +2 -4
  101. package/src/components/theme/TsTest/TsTest.test.tsx +11 -0
  102. package/src/components/theme/TsTest/TsTest.tsx +15 -0
  103. package/src/components/theme/View/AlbumView.jsx +3 -2
  104. package/src/components/theme/Widgets/ImageWidget.stories.jsx +1 -2
  105. package/src/config/Loadables.jsx +1 -1
  106. package/src/config/RichTextEditor/Blocks.jsx +2 -3
  107. package/src/config/RichTextEditor/Plugins.jsx +2 -3
  108. package/src/config/RichTextEditor/ToHTML.jsx +12 -10
  109. package/src/config/RichTextEditor/index.js +2 -3
  110. package/src/config/Views.jsx +5 -5
  111. package/src/express-middleware/ok.js +1 -1
  112. package/src/helpers/Blocks/Blocks.js +4 -6
  113. package/src/helpers/Blocks/Blocks.test.js +35 -35
  114. package/src/helpers/Extensions/withBlockSchemaEnhancer.js +48 -50
  115. package/src/helpers/FormValidation/FormValidation.js +7 -6
  116. package/src/helpers/Html/Html.jsx +2 -8
  117. package/src/helpers/Loadable/__mocks__/Loadable.js +18 -18
  118. package/src/helpers/MessageLabels/MessageLabels.js +2 -3
  119. package/src/helpers/Utils/UseDetectClickOutside.stories.jsx +2 -3
  120. package/src/helpers/Utils/Utils.js +10 -0
  121. package/src/helpers/Utils/Utils.test.js +13 -0
  122. package/src/helpers/index.js +1 -0
  123. package/src/hooks/index.js +1 -1
  124. package/src/middleware/api.js +194 -190
  125. package/src/middleware/blacklistRoutes.js +25 -22
  126. package/src/middleware/storeProtectLoadUtils.js +61 -62
  127. package/src/middleware/storeProtectLoadUtils.test.js +47 -43
  128. package/src/reducers/content/content.test.js +4 -4
  129. package/src/reducers/navigation/navigation.js +5 -5
  130. package/src/reducers/navigation/navigation.test.js +30 -0
  131. package/src/registry.js +2 -2
  132. package/src/storybook.jsx +24 -38
  133. package/theme/themes/pastanaga/collections/menu.overrides +3 -2
  134. package/theme/themes/pastanaga/elements/container.overrides +5 -2
  135. package/theme/themes/pastanaga/elements/input.overrides +1 -1
  136. package/theme/themes/pastanaga/elements/step.overrides +2 -1
  137. package/theme/themes/pastanaga/extras/blocks.less +20 -14
  138. package/theme/themes/pastanaga/extras/color-picker-widget.less +1 -1
  139. package/theme/themes/pastanaga/extras/contents.less +5 -1
  140. package/theme/themes/pastanaga/extras/draftjs.less +4 -4
  141. package/theme/themes/pastanaga/extras/grid.less +5 -4
  142. package/theme/themes/pastanaga/extras/main.less +6 -6
  143. package/theme/themes/pastanaga/extras/react-dates-overrides.less +4 -2
  144. package/theme/themes/pastanaga/extras/search.less +2 -2
  145. package/theme/themes/pastanaga/extras/sidebar.less +5 -4
  146. package/theme/themes/pastanaga/extras/time-picker-overrides.less +5 -3
  147. package/theme/themes/pastanaga/extras/toolbar.less +6 -2
  148. package/theme/themes/pastanaga/extras/userscontrolpanel.less +17 -9
  149. package/theme/themes/pastanaga/extras/widgets.less +1 -1
  150. package/theme/themes/pastanaga/modules/rating.overrides +2 -1
  151. package/theme/themes/pastanaga-cms-ui/elements/container.overrides +2 -1
  152. package/theme/themes/pastanaga-cms-ui/extras/cms-ui.elements.container.less +6 -2
  153. package/theme/themes/pastanaga-cms-ui/extras/cms-ui.site.less +2 -2
  154. package/tsconfig.json +33 -0
@@ -1,14 +1,11 @@
1
- /**
2
- * PersonalTools container.
3
- * @module components/manage/Toolbar/PersonalTools
4
- */
5
- import React, { Component } from 'react';
1
+ import React, { useState, useEffect } from 'react';
6
2
  import PropTypes from 'prop-types';
7
- import { connect } from 'react-redux';
8
- import { Link } from 'react-router-dom';
3
+ import { useDispatch, useSelector, shallowEqual } from 'react-redux';
4
+ import { Link, useLocation } from 'react-router-dom';
9
5
  import jwtDecode from 'jwt-decode';
10
6
  import cx from 'classnames';
11
- import { FormattedMessage, injectIntl, defineMessages } from 'react-intl';
7
+ import { FormattedMessage, useIntl, defineMessages } from 'react-intl';
8
+
12
9
  import { Icon } from '@plone/volto/components';
13
10
  import { getUser } from '@plone/volto/actions';
14
11
  import { Pluggable } from '@plone/volto/components/manage/Pluggable';
@@ -17,12 +14,8 @@ import {
17
14
  getBaseUrl,
18
15
  userHasRoles,
19
16
  } from '@plone/volto/helpers';
20
- import { withRouter } from 'react-router-dom';
21
- import { compose } from 'redux';
22
-
23
17
  import logoutSVG from '@plone/volto/icons/log-out.svg';
24
18
  import rightArrowSVG from '@plone/volto/icons/right-key.svg';
25
-
26
19
  import backSVG from '@plone/volto/icons/back.svg';
27
20
  import cameraSVG from '@plone/volto/icons/camera.svg';
28
21
 
@@ -49,158 +42,107 @@ const messages = defineMessages({
49
42
  },
50
43
  });
51
44
 
52
- /**
53
- * Toolbar container class.
54
- * @class PersonalTools
55
- * @extends Component
56
- */
57
- class PersonalTools extends Component {
58
- /**
59
- * Property types.
60
- * @property {Object} propTypes Property types.
61
- * @static
62
- */
63
- static propTypes = {
64
- user: PropTypes.shape({
65
- fullname: PropTypes.string,
66
- email: PropTypes.string,
67
- home_page: PropTypes.string,
68
- location: PropTypes.string,
69
- }).isRequired,
70
- userId: PropTypes.string.isRequired,
71
- getUser: PropTypes.func.isRequired,
72
- loadComponent: PropTypes.func.isRequired,
73
- };
45
+ const PersonalTools = (props) => {
46
+ const dispatch = useDispatch();
47
+ const intl = useIntl();
48
+ const { pathname } = useLocation();
49
+ const [, setPushed] = useState(false);
50
+ const token = useSelector((state) => state.userSession.token, shallowEqual);
51
+ const user = useSelector((state) => state.users.user);
52
+ const userId = token ? jwtDecode(token).sub : '';
74
53
 
75
- componentDidMount() {
76
- this.props.getUser(this.props.userId);
77
- }
54
+ useEffect(() => {
55
+ dispatch(getUser(userId));
56
+ }, [dispatch, userId]);
78
57
 
79
- push = (selector) => {
80
- this.setState(() => ({
81
- pushed: true,
82
- }));
83
- this.props.loadComponent(selector);
58
+ const push = (selector) => {
59
+ setPushed(true);
60
+ props.loadComponent(selector);
84
61
  };
85
62
 
86
- pull = () => {
87
- this.props.unloadComponent();
63
+ const pull = () => {
64
+ props.unloadComponent();
88
65
  };
89
66
 
90
- /**
91
- * Render method.
92
- * @method render
93
- * @returns {string} Markup for the component.
94
- */
95
- render() {
96
- return (
97
- <div
98
- className={cx('personal-tools pastanaga-menu', {
99
- 'has-inner-actions': this.props.hasActions,
100
- })}
101
- style={{
102
- flex: this.props.theToolbar.current
103
- ? `0 0 ${
104
- this.props.theToolbar.current.getBoundingClientRect().width
105
- }px`
106
- : null,
107
- }}
108
- >
109
- <header className="header">
110
- <button className="back" onClick={this.pull}>
111
- <Icon
112
- name={backSVG}
113
- size="30px"
114
- title={this.props.intl.formatMessage(messages.back)}
115
- />
116
- </button>
117
- <div className="vertical divider" />
118
- <h2>
119
- {this.props.user.fullname
120
- ? this.props.user.fullname
121
- : this.props.user.username}
122
- </h2>
123
- <Link
124
- id="toolbar-logout"
125
- to={`${getBaseUrl(this.props.pathname)}/logout`}
126
- >
127
- <Icon
128
- className="logout"
129
- name={logoutSVG}
130
- size="30px"
131
- title={this.props.intl.formatMessage(messages.logout)}
132
- />
133
- </Link>
134
- </header>
135
- <div className={cx('avatar', { default: !this.props.user.portrait })}>
136
- {this.props.user.portrait ? (
137
- <img
138
- src={flattenToAppURL(this.props.user.portrait)}
139
- alt={this.props.intl.formatMessage(messages.userAvatar)}
140
- />
141
- ) : (
142
- <Icon name={cameraSVG} size="96px" />
143
- )}
144
- </div>
145
- {/* <Stats /> Maybe we can find a good fit in the future for this visual element */}
146
- <div className="pastanaga-menu-list">
147
- {/* This (probably also) should be a Component by itself*/}
148
- <ul>
67
+ return (
68
+ <div
69
+ className={cx('personal-tools pastanaga-menu', {
70
+ 'has-inner-actions': props.hasActions,
71
+ })}
72
+ style={{
73
+ flex: props.theToolbar.current
74
+ ? `0 0 ${props.theToolbar.current.getBoundingClientRect().width}px`
75
+ : null,
76
+ }}
77
+ >
78
+ <header className="header">
79
+ <button className="back" onClick={pull}>
80
+ <Icon
81
+ name={backSVG}
82
+ size="30px"
83
+ title={intl.formatMessage(messages.back)}
84
+ />
85
+ </button>
86
+ <div className="vertical divider" />
87
+ <h2>{user.fullname ? user.fullname : user.username}</h2>
88
+ <Link id="toolbar-logout" to={`${getBaseUrl(pathname)}/logout`}>
89
+ <Icon
90
+ className="logout"
91
+ name={logoutSVG}
92
+ size="30px"
93
+ title={intl.formatMessage(messages.logout)}
94
+ />
95
+ </Link>
96
+ </header>
97
+ <div className={cx('avatar', { default: !user.portrait })}>
98
+ {user.portrait ? (
99
+ <img
100
+ src={flattenToAppURL(user.portrait)}
101
+ alt={intl.formatMessage(messages.userAvatar)}
102
+ />
103
+ ) : (
104
+ <Icon name={cameraSVG} size="96px" />
105
+ )}
106
+ </div>
107
+ {/* <Stats /> Maybe we can find a good fit in the future for this visual element */}
108
+ <div className="pastanaga-menu-list">
109
+ {/* This (probably also) should be a Component by itself*/}
110
+ <ul>
111
+ <li>
112
+ <Link
113
+ id={intl.formatMessage(messages.profile)}
114
+ to="/personal-information"
115
+ >
116
+ <FormattedMessage id="Profile" defaultMessage="Profile" />
117
+ <Icon name={rightArrowSVG} size="24px" />
118
+ </Link>
119
+ </li>
120
+ <li>
121
+ <button
122
+ aria-label={intl.formatMessage(messages.preferences)}
123
+ onClick={() => push('preferences')}
124
+ >
125
+ <FormattedMessage id="Preferences" defaultMessage="Preferences" />
126
+ <Icon name={rightArrowSVG} size="24px" />
127
+ </button>
128
+ </li>
129
+
130
+ {userHasRoles(user, ['Site Administrator', 'Manager']) && (
149
131
  <li>
150
- <Link
151
- id={this.props.intl.formatMessage(messages.profile)}
152
- to="/personal-information"
153
- >
154
- <FormattedMessage id="Profile" defaultMessage="Profile" />
132
+ <Link to="/controlpanel">
133
+ <FormattedMessage id="Site Setup" defaultMessage="Site Setup" />
155
134
  <Icon name={rightArrowSVG} size="24px" />
156
135
  </Link>
157
136
  </li>
158
- <li>
159
- <button
160
- aria-label={this.props.intl.formatMessage(messages.preferences)}
161
- onClick={() => this.push('preferences')}
162
- >
163
- <FormattedMessage
164
- id="Preferences"
165
- defaultMessage="Preferences"
166
- />
167
- <Icon name={rightArrowSVG} size="24px" />
168
- </button>
169
- </li>
170
-
171
- {userHasRoles(this.props.user, [
172
- 'Site Administrator',
173
- 'Manager',
174
- ]) && (
175
- <li>
176
- <Link to="/controlpanel">
177
- <FormattedMessage
178
- id="Site Setup"
179
- defaultMessage="Site Setup"
180
- />
181
- <Icon name={rightArrowSVG} size="24px" />
182
- </Link>
183
- </li>
184
- )}
185
- <Pluggable name="toolbar-user-menu" />
186
- </ul>
187
- </div>
137
+ )}
138
+ <Pluggable name="toolbar-user-menu" />
139
+ </ul>
188
140
  </div>
189
- );
190
- }
191
- }
141
+ </div>
142
+ );
143
+ };
192
144
 
193
- export default compose(
194
- injectIntl,
195
- withRouter,
196
- connect(
197
- (state, props) => ({
198
- pathname: props.location.pathname,
199
- user: state.users.user,
200
- userId: state.userSession.token
201
- ? jwtDecode(state.userSession.token).sub
202
- : '',
203
- }),
204
- { getUser },
205
- ),
206
- )(PersonalTools);
145
+ PersonalTools.propTypes = {
146
+ loadComponent: PropTypes.func.isRequired,
147
+ };
148
+ export default PersonalTools;
@@ -591,8 +591,8 @@ class Toolbar extends Component {
591
591
  messages.shrinkToolbar,
592
592
  )}
593
593
  className={cx({
594
- [this.props.content?.review_state]: this.props.content
595
- ?.review_state,
594
+ [this.props.content?.review_state]:
595
+ this.props.content?.review_state,
596
596
  })}
597
597
  onClick={this.handleShrink}
598
598
  />
@@ -158,7 +158,8 @@ describe('UniversalLink', () => {
158
158
  });
159
159
 
160
160
  it('UniversalLink renders external link where link is blacklisted', () => {
161
- const notInEN = /^(?!.*(#|\/en|\/static|\/controlpanel|\/cypress|\/login|\/logout|\/contact-form)).*$/;
161
+ const notInEN =
162
+ /^(?!.*(#|\/en|\/static|\/controlpanel|\/cypress|\/login|\/logout|\/contact-form)).*$/;
162
163
  config.settings.externalRoutes = [
163
164
  {
164
165
  match: {
@@ -47,10 +47,8 @@ export const defaultActionsInfo = ({ intl }) => ({
47
47
  const AlignWidget = (props) => {
48
48
  const intl = useIntl();
49
49
 
50
- const {
51
- actions = ['left', 'right', 'center', 'full'],
52
- actionsInfoMap,
53
- } = props;
50
+ const { actions = ['left', 'right', 'center', 'full'], actionsInfoMap } =
51
+ props;
54
52
 
55
53
  const actionsInfo = {
56
54
  ...defaultActionsInfo({ intl }),
@@ -7,14 +7,16 @@ import ColorPickerWidget from './ColorPickerWidget';
7
7
 
8
8
  const mockStore = configureStore();
9
9
 
10
- const withStateManagement = (Component) => ({ ...props }) => {
11
- const [value, setValue] = React.useState(props.value || null);
12
- const onChange = (id, value) => {
13
- setValue(value);
14
- };
10
+ const withStateManagement =
11
+ (Component) =>
12
+ ({ ...props }) => {
13
+ const [value, setValue] = React.useState(props.value || null);
14
+ const onChange = (id, value) => {
15
+ setValue(value);
16
+ };
15
17
 
16
- return <Component {...props} onChange={onChange} value={value} />;
17
- };
18
+ return <Component {...props} onChange={onChange} value={value} />;
19
+ };
18
20
 
19
21
  describe('ColorPickerWidget', () => {
20
22
  const COLORS = [
@@ -179,14 +179,8 @@ export class DatetimeWidgetComponent extends Component {
179
179
  onFocusChange = ({ focused }) => this.setState({ focused });
180
180
 
181
181
  render() {
182
- const {
183
- id,
184
- resettable,
185
- intl,
186
- reactDates,
187
- widgetOptions,
188
- lang,
189
- } = this.props;
182
+ const { id, resettable, intl, reactDates, widgetOptions, lang } =
183
+ this.props;
190
184
  const noPastDates =
191
185
  this.props.noPastDates || widgetOptions?.pattern_options?.noPastDates;
192
186
  const moment = this.props.moment.default;
@@ -21,8 +21,7 @@ const messages = defineMessages({
21
21
  defaultMessage: "This is a reserved name and can't be used",
22
22
  },
23
23
  invalidCharacters: {
24
- id:
25
- 'Only lowercase letters (a-z) without accents, numbers (0-9), and the characters "-", "_", and "." are allowed.',
24
+ id: 'Only lowercase letters (a-z) without accents, numbers (0-9), and the characters "-", "_", and "." are allowed.',
26
25
  defaultMessage:
27
26
  'Only lowercase letters (a-z) without accents, numbers (0-9), and the characters "-", "_", and "." are allowed.',
28
27
  },
@@ -312,15 +312,8 @@ export class ObjectBrowserWidgetComponent extends Component {
312
312
  * @returns {string} Markup for the component.
313
313
  */
314
314
  render() {
315
- const {
316
- id,
317
- description,
318
- fieldSet,
319
- value,
320
- mode,
321
- onChange,
322
- isDisabled,
323
- } = this.props;
315
+ const { id, description, fieldSet, value, mode, onChange, isDisabled } =
316
+ this.props;
324
317
 
325
318
  let items = compact(!isArray(value) && value ? [value] : value || []);
326
319
 
@@ -734,15 +734,8 @@ class RecurrenceWidget extends Component {
734
734
  render() {
735
735
  const { open, dimmer, rruleSet, formValues, RRULE_LANGUAGE } = this.state;
736
736
 
737
- const {
738
- id,
739
- title,
740
- required,
741
- description,
742
- error,
743
- fieldSet,
744
- intl,
745
- } = this.props;
737
+ const { id, title, required, description, error, fieldSet, intl } =
738
+ this.props;
746
739
 
747
740
  return (
748
741
  <Form.Field
@@ -27,10 +27,10 @@ const messages = defineMessages({
27
27
  });
28
28
 
29
29
  const ORDINAL_NUMBERS = {
30
- '1': 'first',
31
- '2': 'second',
32
- '3': 'third',
33
- '4': 'fourth',
30
+ 1: 'first',
31
+ 2: 'second',
32
+ 3: 'third',
33
+ 4: 'fourth',
34
34
  '-1': 'last',
35
35
  };
36
36
 
@@ -490,9 +490,8 @@ class SchemaWidget extends Component {
490
490
  */
491
491
  onAddField(values) {
492
492
  const fieldId = slugify(values.title);
493
- const currentFieldsetFields = this.props.value.fieldsets[
494
- this.state.currentFieldset
495
- ].fields;
493
+ const currentFieldsetFields =
494
+ this.props.value.fieldsets[this.state.currentFieldset].fields;
496
495
  const hasChangeNote = currentFieldsetFields.indexOf('changeNote') > -1;
497
496
  const newFieldsetFields = hasChangeNote
498
497
  ? [
@@ -698,9 +697,8 @@ class SchemaWidget extends Component {
698
697
  const newParentFieldsetIndex = fieldsets.findIndex(
699
698
  (field) => field.id === parentFieldSet,
700
699
  );
701
- const indexOfChangeNote = fieldsets[
702
- newParentFieldsetIndex
703
- ].fields.indexOf('changeNote');
700
+ const indexOfChangeNote =
701
+ fieldsets[newParentFieldsetIndex].fields.indexOf('changeNote');
704
702
  // remove from current fieldset
705
703
  const fieldsetsWithoutField = [
706
704
  ...slice(fieldsets, 0, currentFieldset),
@@ -1363,9 +1361,8 @@ class SchemaWidget extends Component {
1363
1361
  required:
1364
1362
  this.props.value.required.indexOf(this.state.editField.id) !==
1365
1363
  -1,
1366
- parentFieldSet: this.props.value.fieldsets[
1367
- this.state.currentFieldset
1368
- ].id,
1364
+ parentFieldSet:
1365
+ this.props.value.fieldsets[this.state.currentFieldset].id,
1369
1366
  values: formatArrayToTextarea(
1370
1367
  this.props.value.properties[this.state.editField.id],
1371
1368
  ),
@@ -261,15 +261,8 @@ class WysiwygWidgetComponent extends Component {
261
261
  * @returns {string} Markup for the component.
262
262
  */
263
263
  render() {
264
- const {
265
- id,
266
- title,
267
- description,
268
- required,
269
- value,
270
- error,
271
- fieldSet,
272
- } = this.props;
264
+ const { id, title, description, required, value, error, fieldSet } =
265
+ this.props;
273
266
 
274
267
  if (__SERVER__) {
275
268
  return (
@@ -31,8 +31,7 @@ const messages = defineMessages({
31
31
  defaultMessage: 'Comments',
32
32
  },
33
33
  commentDescription: {
34
- id:
35
- 'You can add a comment by filling out the form below. Plain text formatting.',
34
+ id: 'You can add a comment by filling out the form below. Plain text formatting.',
36
35
  defaultMessage:
37
36
  'You can add a comment by filling out the form below. Plain text formatting.',
38
37
  },
@@ -113,14 +112,8 @@ const Comments = (props) => {
113
112
  const [editText, seteditText] = useState(null);
114
113
  const [replyTo, setreplyTo] = useState(null);
115
114
  const [collapsedComments, setcollapsedComments] = useState({});
116
- const {
117
- items,
118
- next,
119
- items_total,
120
- permissions,
121
- addRequest,
122
- deleteRequest,
123
- } = useComments();
115
+ const { items, next, items_total, permissions, addRequest, deleteRequest } =
116
+ useComments();
124
117
 
125
118
  const prevpathname = usePrevious(pathname);
126
119
 
@@ -83,6 +83,10 @@ const ContentMetadataTags = (props) => {
83
83
  <>
84
84
  <Helmet>
85
85
  <title>{getTitle()?.replace(/\u00AD/g, '')}</title>
86
+ <link
87
+ rel="canonical"
88
+ href={seo_canonical_url || toPublicURL(props.content['@id'])}
89
+ />
86
90
  <meta name="description" content={seo_description || description} />
87
91
  <meta
88
92
  property="og:title"
@@ -51,8 +51,7 @@ const messages = defineMessages({
51
51
  defaultMessage: 'Login Failed',
52
52
  },
53
53
  loginFailedContent: {
54
- id:
55
- 'Both email address and password are case sensitive, check that caps lock is not enabled.',
54
+ id: 'Both email address and password are case sensitive, check that caps lock is not enabled.',
56
55
  defaultMessage:
57
56
  'Both email address and password are case sensitive, check that caps lock is not enabled.',
58
57
  },
@@ -79,8 +79,7 @@ const messages = defineMessages({
79
79
  defaultMessage: 'Account activation completed',
80
80
  },
81
81
  successRedirectToLoginBody: {
82
- id:
83
- 'Your password has been set successfully. You may now {link} with your new password.',
82
+ id: 'Your password has been set successfully. You may now {link} with your new password.',
84
83
  defaultMessage:
85
84
  'Your password has been set successfully. You may now {link} with your new password.',
86
85
  },
@@ -7,13 +7,17 @@ import DefaultImageSVG from '@plone/volto/components/manage/Blocks/Listing/defau
7
7
  /**
8
8
  * Renders a preview image for a catalog brain result item.
9
9
  */
10
- function PreviewImage({ item, alt, ...rest }) {
10
+ function PreviewImage({ item, alt, image_field, showDefault = true, ...rest }) {
11
11
  const Image = config.getComponent({ name: 'Image' }).component;
12
12
 
13
- if (item.image_field && item.image_scales?.[item.image_field]?.[0]) {
14
- return (
15
- <Image item={item} imageField={item.image_field} alt={alt} {...rest} />
16
- );
13
+ const image = (
14
+ <Image item={item} image_field={image_field} alt={alt} {...rest} />
15
+ );
16
+
17
+ if (!image && !showDefault) return null;
18
+
19
+ if (image) {
20
+ return image;
17
21
  } else {
18
22
  return (
19
23
  <img
@@ -38,6 +42,7 @@ PreviewImage.propTypes = {
38
42
  title: PropTypes.string.isRequired,
39
43
  image_field: PropTypes.string,
40
44
  image_scales: PropTypes.object,
45
+ showDefault: PropTypes.bool,
41
46
  }),
42
47
  alt: PropTypes.string.isRequired,
43
48
  };
@@ -109,4 +109,21 @@ describe('PreviewImage', () => {
109
109
  const json = component.toJSON();
110
110
  expect(json).toMatchSnapshot();
111
111
  });
112
+
113
+ it('not renders a fallback image if showDefault prop is false', () => {
114
+ const item = {
115
+ title: 'Item title',
116
+ '@id': 'http://localhost:3000/something',
117
+ };
118
+ const component = renderer.create(
119
+ <PreviewImage
120
+ item={item}
121
+ className="extra"
122
+ showDefault={false}
123
+ alt={item.title}
124
+ />,
125
+ );
126
+ const json = component.toJSON();
127
+ expect(json).toMatchSnapshot();
128
+ });
112
129
  });
@@ -37,8 +37,7 @@ const messages = defineMessages({
37
37
  defaultMessage: 'E-mail',
38
38
  },
39
39
  emailDescription: {
40
- id:
41
- 'Enter an email address. This will be your login name. We respect your privacy, and will not give the address away to any third parties or expose it anywhere.',
40
+ id: 'Enter an email address. This will be your login name. We respect your privacy, and will not give the address away to any third parties or expose it anywhere.',
42
41
  defaultMessage:
43
42
  'Enter an email address. This will be your login name. We respect your privacy, and will not give the address away to any third parties or expose it anywhere.',
44
43
  },
@@ -47,8 +46,7 @@ const messages = defineMessages({
47
46
  defaultMessage: 'Account Registration Completed',
48
47
  },
49
48
  successRegisterCompletedBody: {
50
- id:
51
- 'The registration process has been successful. Please check your e-mail inbox for information on how activate your account.',
49
+ id: 'The registration process has been successful. Please check your e-mail inbox for information on how activate your account.',
52
50
  defaultMessage:
53
51
  'The registration process has been successful. Please check your e-mail inbox for information on how activate your account.',
54
52
  },
@@ -0,0 +1,11 @@
1
+ import renderer from 'react-test-renderer';
2
+ import TsTest from './TsTest';
3
+
4
+ describe('Ts test component', () => {
5
+ test('Renders', () => {
6
+ const component = renderer.create(<TsTest text="Test a TS component" />);
7
+ const json = component.toJSON();
8
+
9
+ expect(json).toMatchSnapshot();
10
+ });
11
+ });
@@ -0,0 +1,15 @@
1
+ /**
2
+ * TEST COMPONENT: React TSX component
3
+ * Ensure successful implementation of TSX and validate the functionality with Jest tests
4
+ * Dependencies: jest (version 26.6.3), ts-jest (version ^26.4.2)
5
+ */
6
+
7
+ type TestProps = {
8
+ text: string;
9
+ };
10
+
11
+ const TsTest = ({ text }: TestProps) => {
12
+ return <div>{text}</div>;
13
+ };
14
+
15
+ export default TsTest;