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

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 (95) hide show
  1. package/.yarn/install-state.gz +0 -0
  2. package/CHANGELOG.md +52 -5
  3. package/README.md +8 -7
  4. package/cypress/support/commands.js +12 -9
  5. package/cypress.config.js +1 -0
  6. package/locales/ca/LC_MESSAGES/volto.po +36 -15
  7. package/locales/ca.json +1 -1
  8. package/locales/de/LC_MESSAGES/volto.po +36 -15
  9. package/locales/de.json +1 -1
  10. package/locales/en/LC_MESSAGES/volto.po +35 -14
  11. package/locales/en.json +1 -1
  12. package/locales/es/LC_MESSAGES/volto.po +65 -44
  13. package/locales/es.json +1 -1
  14. package/locales/eu/LC_MESSAGES/volto.po +35 -14
  15. package/locales/eu.json +1 -1
  16. package/locales/fi/LC_MESSAGES/volto.po +35 -14
  17. package/locales/fi.json +1 -1
  18. package/locales/fr/LC_MESSAGES/volto.po +36 -15
  19. package/locales/fr.json +1 -1
  20. package/locales/it/LC_MESSAGES/volto.po +35 -14
  21. package/locales/it.json +1 -1
  22. package/locales/ja/LC_MESSAGES/volto.po +35 -14
  23. package/locales/ja.json +1 -1
  24. package/locales/nl/LC_MESSAGES/volto.po +36 -15
  25. package/locales/nl.json +1 -1
  26. package/locales/pt/LC_MESSAGES/volto.po +36 -15
  27. package/locales/pt.json +1 -1
  28. package/locales/pt_BR/LC_MESSAGES/volto.po +35 -14
  29. package/locales/pt_BR.json +1 -1
  30. package/locales/ro/LC_MESSAGES/volto.po +36 -15
  31. package/locales/ro.json +1 -1
  32. package/locales/volto.pot +35 -14
  33. package/locales/zh_CN/LC_MESSAGES/volto.po +36 -15
  34. package/locales/zh_CN.json +1 -1
  35. package/package.json +4 -4
  36. package/packages/volto-slate/package.json +1 -1
  37. package/packages/volto-slate/src/editor/render.jsx +2 -3
  38. package/src/actions/index.js +3 -0
  39. package/src/actions/navroot/navroot.js +16 -0
  40. package/src/actions/navroot/navroot.test.js +15 -0
  41. package/src/actions/site/site.js +16 -0
  42. package/src/actions/site/site.test.js +15 -0
  43. package/src/actions/userSession/userSession.js +17 -1
  44. package/src/components/manage/Blocks/Block/Settings.jsx +2 -0
  45. package/src/components/manage/Blocks/Block/Settings.test.jsx +90 -0
  46. package/src/components/manage/Blocks/Listing/withQuerystringResults.jsx +1 -1
  47. package/src/components/manage/Blocks/Search/hocs/withSearch.jsx +42 -25
  48. package/src/components/manage/Blocks/ToC/View.jsx +75 -13
  49. package/src/components/manage/Blocks/ToC/variations/DefaultTocRenderer.jsx +2 -12
  50. package/src/components/manage/Controlpanels/Groups/GroupsControlpanel.jsx +65 -38
  51. package/src/components/manage/Controlpanels/Rules/AddRule.jsx +1 -1
  52. package/src/components/manage/Controlpanels/Rules/EditRule.jsx +1 -1
  53. package/src/components/manage/Controlpanels/Users/RenderUsers.jsx +95 -5
  54. package/src/components/manage/Controlpanels/Users/UsersControlpanel.jsx +116 -103
  55. package/src/components/manage/Form/BlockDataForm.jsx +3 -2
  56. package/src/components/manage/Form/BlockDataForm.test.jsx +34 -2
  57. package/src/components/manage/LinksToItem/LinksToItem.test.jsx +4 -1
  58. package/src/components/manage/Messages/Messages.jsx +32 -99
  59. package/src/components/manage/Messages/Messages.test.jsx +0 -1
  60. package/src/components/manage/Sharing/Sharing.jsx +39 -16
  61. package/src/components/manage/UniversalLink/UniversalLink.jsx +4 -6
  62. package/src/components/manage/Widgets/ArrayWidget.jsx +3 -1
  63. package/src/components/manage/Widgets/ArrayWidget.test.jsx +45 -1
  64. package/src/components/manage/Widgets/RegistryImageWidget.jsx +210 -0
  65. package/src/components/manage/Widgets/RegistryImageWidget.test.jsx +91 -0
  66. package/src/components/manage/Widgets/SelectWidget.jsx +15 -1
  67. package/src/components/manage/Widgets/SelectWidget.test.jsx +45 -1
  68. package/src/components/theme/ContentMetadataTags/ContentMetadataTags.jsx +37 -3
  69. package/src/components/theme/Login/Login.jsx +159 -241
  70. package/src/components/theme/Logo/Logo.Multilingual.test.jsx +131 -1
  71. package/src/components/theme/Logo/Logo.jsx +35 -29
  72. package/src/components/theme/Logo/Logo.test.jsx +135 -1
  73. package/src/components/theme/Logout/Logout.jsx +1 -1
  74. package/src/components/theme/Navigation/Navigation.jsx +86 -171
  75. package/src/components/theme/SearchWidget/SearchWidget.jsx +15 -3
  76. package/src/components/theme/SearchWidget/SearchWidget.test.jsx +8 -0
  77. package/src/components/theme/View/View.jsx +2 -0
  78. package/src/config/ControlPanels.js +0 -1
  79. package/src/config/Widgets.jsx +2 -0
  80. package/src/config/index.js +15 -3
  81. package/src/constants/ActionTypes.js +3 -0
  82. package/src/express-middleware/images.js +1 -0
  83. package/src/helpers/MessageLabels/MessageLabels.js +26 -4
  84. package/src/helpers/Site/index.js +21 -0
  85. package/src/helpers/index.js +1 -0
  86. package/src/reducers/index.js +4 -0
  87. package/src/reducers/navroot/navroot.js +79 -0
  88. package/src/reducers/navroot/navroot.test.js +110 -0
  89. package/src/reducers/site/site.js +51 -0
  90. package/src/reducers/site/site.test.js +67 -0
  91. package/src/reducers/userSession/userSession.js +15 -1
  92. package/test-setup-config.js +1 -0
  93. package/theme/themes/pastanaga/elements/input.overrides +5 -1
  94. package/theme/themes/pastanaga/extras/login.less +3 -0
  95. package/webpack-plugins/webpack-less-plugin.js +19 -0
@@ -2,7 +2,7 @@ import React from 'react';
2
2
  import renderer from 'react-test-renderer';
3
3
  import configureStore from 'redux-mock-store';
4
4
  import { Provider } from 'react-intl-redux';
5
- import { waitFor } from '@testing-library/react';
5
+ import { waitFor, render, fireEvent } from '@testing-library/react';
6
6
 
7
7
  import ArrayWidget from './ArrayWidget';
8
8
 
@@ -41,3 +41,47 @@ test('renders an array widget component', async () => {
41
41
  await waitFor(() => {});
42
42
  expect(component.toJSON()).toMatchSnapshot();
43
43
  });
44
+
45
+ test("No 'No value' option when default value is 0", async () => {
46
+ const store = mockStore({
47
+ intl: {
48
+ locale: 'en',
49
+ messages: {},
50
+ },
51
+ });
52
+
53
+ const choices = [
54
+ ['0', 'None'],
55
+ ['1', 'One'],
56
+ ];
57
+
58
+ const value = {
59
+ value: '0',
60
+ label: 'None',
61
+ };
62
+
63
+ const _default = 0;
64
+
65
+ const { container } = render(
66
+ <Provider store={store}>
67
+ <ArrayWidget
68
+ id="my-field"
69
+ title="My field"
70
+ fieldSet="default"
71
+ choices={choices}
72
+ default={_default}
73
+ value={value}
74
+ noValueOption={true}
75
+ onChange={() => {}}
76
+ onBlur={() => {}}
77
+ onClick={() => {}}
78
+ />
79
+ </Provider>,
80
+ );
81
+
82
+ fireEvent.mouseDown(
83
+ container.querySelector('.react-select__dropdown-indicator'),
84
+ { button: 0 },
85
+ );
86
+ expect(container).toMatchSnapshot();
87
+ });
@@ -0,0 +1,210 @@
1
+ /**
2
+ * RegistryImageWidget component.
3
+ * @module components/manage/Widgets/RegistryImageWidget
4
+ */
5
+
6
+ import React from 'react';
7
+ import PropTypes from 'prop-types';
8
+ import { Button, Image, Dimmer } from 'semantic-ui-react';
9
+ import { readAsDataURL } from 'promise-file-reader';
10
+ import { injectIntl } from 'react-intl';
11
+ import deleteSVG from '@plone/volto/icons/delete.svg';
12
+ import { Icon, FormFieldWrapper } from '@plone/volto/components';
13
+ import loadable from '@loadable/component';
14
+ import { defineMessages, useIntl } from 'react-intl';
15
+ import { toPublicURL, validateFileUploadSize } from '@plone/volto/helpers';
16
+
17
+ const imageMimetypes = [
18
+ 'image/png',
19
+ 'image/jpeg',
20
+ 'image/webp',
21
+ 'image/jpg',
22
+ 'image/gif',
23
+ 'image/svg+xml',
24
+ ];
25
+ const Dropzone = loadable(() => import('react-dropzone'));
26
+
27
+ const messages = defineMessages({
28
+ releaseDrag: {
29
+ id: 'Drop files here ...',
30
+ defaultMessage: 'Drop files here ...',
31
+ },
32
+ editFile: {
33
+ id: 'Drop file here to replace the existing file',
34
+ defaultMessage: 'Drop file here to replace the existing file',
35
+ },
36
+ fileDrag: {
37
+ id: 'Drop file here to upload a new file',
38
+ defaultMessage: 'Drop file here to upload a new file',
39
+ },
40
+ replaceFile: {
41
+ id: 'Replace existing file',
42
+ defaultMessage: 'Replace existing file',
43
+ },
44
+ addNewFile: {
45
+ id: 'Choose a file',
46
+ defaultMessage: 'Choose a file',
47
+ },
48
+ });
49
+
50
+ /**
51
+ * RegistryImageWidget component class.
52
+ * @function RegistryImageWidget
53
+ * @returns {string} Markup of the component.
54
+ *
55
+ * To use it, in schema properties, declare a field like:
56
+ *
57
+ * ```jsx
58
+ * {
59
+ * title: "File",
60
+ * widget: 'file',
61
+ * }
62
+ * ```
63
+ * or:
64
+ *
65
+ * ```jsx
66
+ * {
67
+ * title: "File",
68
+ * type: 'object',
69
+ * }
70
+ * ```
71
+ *
72
+ */
73
+ const RegistryImageWidget = (props) => {
74
+ const { id, value, onChange, isDisabled } = props;
75
+ const intl = useIntl();
76
+
77
+ const fileName = value?.split(';')[0];
78
+ const imgsrc = fileName
79
+ ? `${toPublicURL('/')}@@site-logo/${atob(
80
+ fileName.replace('filenameb64:', ''),
81
+ )}`
82
+ : '';
83
+
84
+ /**
85
+ * Drop handler
86
+ * @method onDrop
87
+ * @param {array} files File objects
88
+ * @returns {undefined}
89
+ */
90
+ const onDrop = (files) => {
91
+ const file = files[0];
92
+ if (!validateFileUploadSize(file, intl.formatMessage)) return;
93
+
94
+ readAsDataURL(file).then((data) => {
95
+ const fields = data.match(/^data:(.*);(.*),(.*)$/);
96
+ onChange(id, `filenameb64:${btoa(file.name)};datab64:${fields[3]}}`);
97
+ });
98
+
99
+ let reader = new FileReader();
100
+ reader.onload = function () {
101
+ const fields = reader.result.match(/^data:(.*);(.*),(.*)$/);
102
+ if (imageMimetypes.includes(fields[1])) {
103
+ let imagePreview = document.getElementById(`field-${id}-image`);
104
+ imagePreview.src = reader.result;
105
+ }
106
+ };
107
+ reader.readAsDataURL(files[0]);
108
+ };
109
+
110
+ return (
111
+ <FormFieldWrapper {...props}>
112
+ <Dropzone onDrop={onDrop}>
113
+ {({ getRootProps, getInputProps, isDragActive }) => (
114
+ <div className="file-widget-dropzone" {...getRootProps()}>
115
+ {isDragActive && <Dimmer active></Dimmer>}
116
+ {imgsrc ? (
117
+ <Image
118
+ className="image-preview"
119
+ id={`field-${id}-image`}
120
+ size="small"
121
+ src={imgsrc}
122
+ />
123
+ ) : (
124
+ <div className="dropzone-placeholder">
125
+ {isDragActive ? (
126
+ <p className="dropzone-text">
127
+ {intl.formatMessage(messages.releaseDrag)}
128
+ </p>
129
+ ) : value ? (
130
+ <p className="dropzone-text">
131
+ {intl.formatMessage(messages.editFile)}
132
+ </p>
133
+ ) : (
134
+ <p className="dropzone-text">
135
+ {intl.formatMessage(messages.fileDrag)}
136
+ </p>
137
+ )}
138
+ </div>
139
+ )}
140
+
141
+ <label className="label-file-widget-input">
142
+ {value
143
+ ? intl.formatMessage(messages.replaceFile)
144
+ : intl.formatMessage(messages.addNewFile)}
145
+ </label>
146
+ <input
147
+ {...getInputProps({
148
+ type: 'file',
149
+ style: { display: 'none' },
150
+ })}
151
+ id={`field-${id}`}
152
+ name={id}
153
+ type="file"
154
+ disabled={isDisabled}
155
+ />
156
+ </div>
157
+ )}
158
+ </Dropzone>
159
+ <div className="field-file-name">
160
+ {value && (
161
+ <Button
162
+ icon
163
+ basic
164
+ className="delete-button"
165
+ aria-label="delete file"
166
+ disabled={isDisabled}
167
+ onClick={() => {
168
+ onChange(id, '');
169
+ }}
170
+ >
171
+ <Icon name={deleteSVG} size="20px" />
172
+ </Button>
173
+ )}
174
+ </div>
175
+ </FormFieldWrapper>
176
+ );
177
+ };
178
+
179
+ /**
180
+ * Property types.
181
+ * @property {Object} propTypes Property types.
182
+ * @static
183
+ */
184
+ RegistryImageWidget.propTypes = {
185
+ id: PropTypes.string.isRequired,
186
+ title: PropTypes.string.isRequired,
187
+ description: PropTypes.string,
188
+ required: PropTypes.bool,
189
+ error: PropTypes.arrayOf(PropTypes.string),
190
+ value: PropTypes.shape({
191
+ '@type': PropTypes.string,
192
+ title: PropTypes.string,
193
+ }),
194
+ onChange: PropTypes.func.isRequired,
195
+ wrapped: PropTypes.bool,
196
+ };
197
+
198
+ /**
199
+ * Default properties.
200
+ * @property {Object} defaultProps Default properties.
201
+ * @static
202
+ */
203
+ RegistryImageWidget.defaultProps = {
204
+ description: null,
205
+ required: false,
206
+ error: [],
207
+ value: null,
208
+ };
209
+
210
+ export default injectIntl(RegistryImageWidget);
@@ -0,0 +1,91 @@
1
+ import React from 'react';
2
+ import { Provider } from 'react-intl-redux';
3
+ import { render, waitFor } from '@testing-library/react';
4
+ import configureStore from 'redux-mock-store';
5
+
6
+ import RegistryImageWidget from './RegistryImageWidget';
7
+
8
+ import config from '@plone/volto/registry';
9
+
10
+ jest.spyOn(global.Date, 'now').mockImplementation(() => '0');
11
+
12
+ const mockStore = configureStore();
13
+
14
+ beforeAll(() => {
15
+ config.settings.publicURL = 'http://localhost:3000';
16
+ });
17
+
18
+ describe('RegistryImageWidget', () => {
19
+ test('renders an empty file widget component', async () => {
20
+ const store = mockStore({
21
+ intl: {
22
+ locale: 'en',
23
+ messages: {},
24
+ },
25
+ });
26
+
27
+ const { container } = render(
28
+ <Provider store={store}>
29
+ <RegistryImageWidget
30
+ id="my-field"
31
+ title="My field"
32
+ fieldSet="default"
33
+ onChange={() => {}}
34
+ />
35
+ </Provider>,
36
+ );
37
+
38
+ await waitFor(() => {});
39
+ expect(container).toMatchSnapshot();
40
+ });
41
+ test('renders a file widget component with value', async () => {
42
+ const store = mockStore({
43
+ intl: {
44
+ locale: 'en',
45
+ messages: {},
46
+ },
47
+ });
48
+
49
+ const { container } = render(
50
+ <Provider store={store}>
51
+ <RegistryImageWidget
52
+ id="my-field"
53
+ title="My field"
54
+ fieldSet="default"
55
+ onChange={() => {}}
56
+ value={
57
+ 'filenameb64:bG9nby5jYWI5NDVkOC5zdmc=;datab64:PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2ZXJzaW9uPSIxLjEiIGlkPSJMYXllcl8xIiB4PSIwcHgiIHk9IjBweCIgd2lkdGg9IjE1OC4yNTNweCIgaGVpZ2h0PSI0MC42ODZweCIgdmlld0JveD0iMCAwIDE1OC4yNTMgNDAuNjg2IiBlbmFibGUtYmFja2dyb3VuZD0ibmV3IDAgMCAxNTguMjUzIDQwLjY4NiIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+CiAgICAgICAgICAgICAgICAgICAgPGc+PHBhdGggZmlsbD0iIzAwODZDMyIgZD0iTTY1LjMyNywyMy4yMDhoLTYuNTg5djExLjM4OGgtNC4zOTNWNS42MzhoMTAuOTgxYzUuNjUzLDAsOS4yNzEsMy43NDIsOS4yNzEsOC43ODUgICAgICAgICAgICAgICAgIFM3MC45NzksMjMuMjA4LDY1LjMyNywyMy4yMDh6IE02NS4wODIsOS41ODNoLTYuMzQ1djkuNjM5aDYuMzQ1YzMuMDUsMCw1LjEyNC0xLjc0OSw1LjEyNC00Ljc5OSAgICAgICAgICAgICAgICAgQzcwLjIwNiwxMS4zNzIsNjguMTMyLDkuNTgzLDY1LjA4Miw5LjU4M3oiLz48cGF0aCBmaWxsPSIjMDA4NkMzIiBkPSJNODMuOTY5LDM0LjU5NmMtMy45MDQsMC01LjY1Mi0yLjY0NC01LjY1Mi01LjY5M1Y1LjYzOGg0LjE0OHYyMy4wMjFjMCwxLjU4NywwLjU2NywyLjM5OSwyLjIzNSwyLjM5OWgxLjgzICAgICAgICAgICAgICAgICB2My41MzhIODMuOTY5eiIvPjxwYXRoIGZpbGw9IiMwMDg2QzMiIGQ9Ik0xMDQuNzYyLDMyLjM5OWMtMS4zNDQsMS4zODQtMy4zNzcsMi40NC02LjE4NCwyLjQ0Yy0yLjgwNSwwLTQuNzk5LTEuMDU4LTYuMTQxLTIuNDQgICAgICAgICAgICAgICAgIGMtMS45NTEtMi4wMzItMi40MzktNC42MzctMi40MzktOC4xMzRjMC0zLjQ1NywwLjQ4OC02LjA2MSwyLjQzOS04LjA5NGMxLjM0Mi0xLjM4MywzLjMzNi0yLjQ0LDYuMTQxLTIuNDQgICAgICAgICAgICAgICAgIGMyLjgwNywwLDQuODQsMS4wNTksNi4xODQsMi40NGMxLjk1MSwyLjAzMywyLjQzOSw0LjYzNywyLjQzOSw4LjA5NEMxMDcuMjAzLDI3Ljc2MywxMDYuNzEzLDMwLjM2NiwxMDQuNzYyLDMyLjM5OXogICAgICAgICAgICAgICAgICBNMTAxLjYyOSwxOC42MTNjLTAuNzczLTAuNzczLTEuODMtMS4xODEtMy4wNTEtMS4xODFjLTEuMjE5LDAtMi4yMzYsMC40MDYtMy4wMSwxLjE4MWMtMS4yNiwxLjI2MS0xLjQyMiwzLjQxNi0xLjQyMiw1LjY1MiAgICAgICAgICAgICAgICAgczAuMTYyLDQuMzkzLDEuNDIyLDUuNjUzYzAuNzczLDAuNzcxLDEuNzkxLDEuMjIsMy4wMSwxLjIyYzEuMjIxLDAsMi4yNzctMC40NDcsMy4wNTEtMS4yMmMxLjI2Mi0xLjI2MiwxLjQyNC0zLjQxNywxLjQyNC01LjY1MyAgICAgICAgICAgICAgICAgUzEwMi44OTEsMTkuODczLDEwMS42MjksMTguNjEzeiIvPjxwYXRoIGZpbGw9IiMwMDg2QzMiIGQ9Ik0xMjMuNjQzLDM0LjU5NlYyMi4wMjljMC0zLjIxNC0xLjgzLTQuNTk3LTQuMTQ3LTQuNTk3cy00LjI3MSwxLjQyMy00LjI3MSw0LjU5N3YxMi41NjZoLTQuMTQ3di0yMC42MiAgICAgICAgICAgICAgICAgaDQuMDY1djIuMDc0YzEuNDI1LTEuNTQ2LDMuNDE2LTIuMzE4LDUuNDktMi4zMThjMi4xMTUsMCwzLjg2NSwwLjY5MSw1LjA4NCwxLjg3MWMxLjU4NiwxLjU0NSwyLjA3NCwzLjQ5NywyLjA3NCw1LjgxNXYxMy4xNzggICAgICAgICAgICAgICAgIEwxMjMuNjQzLDM0LjU5NkwxMjMuNjQzLDM0LjU5NnoiLz48cGF0aCBmaWxsPSIjMDA4NkMzIiBkPSJNMTM1Ljc3MiwyNS40ODZjMCwzLjUzNywxLjg3MSw1Ljc3NCw1LjI0Niw1Ljc3NGMyLjMxNywwLDMuNTM5LTAuNjQ5LDUuMDA0LTIuMTE1bDIuNjQzLDIuNDgxICAgICAgICAgICAgICAgICBjLTIuMTE1LDIuMTE0LTQuMTA3LDMuMjEzLTcuNzI3LDMuMjEzYy01LjE2NiwwLTkuMjczLTIuNzI1LTkuMjczLTEwLjU3NGMwLTYuNjcxLDMuNDU3LTEwLjUzNCw4Ljc0NC0xMC41MzQgICAgICAgICAgICAgICAgIGM1LjUzMSwwLDguNzQ0LDQuMDY3LDguNzQ0LDkuOTI1djEuODNIMTM1Ljc3MnogTTE0NC40NzUsMTkuNzkxYy0wLjY1LTEuNTQ1LTIuMTEzLTIuNjA0LTQuMDY2LTIuNjA0ICAgICAgICAgICAgICAgICBjLTEuOTUxLDAtMy40NTcsMS4wNTktNC4xMDcsMi42MDRjLTAuNDA2LDAuOTM2LTAuNDg4LDEuNTQ2LTAuNTI5LDIuODA3aDkuMjczQzE0NS4wMDMsMjEuMzM3LDE0NC44ODMsMjAuNzI2LDE0NC40NzUsMTkuNzkxeiIvPjxjaXJjbGUgZmlsbD0iIzAwODZDMyIgY3g9IjE3LjgxNSIgY3k9IjExLjUxNiIgcj0iNC40MDIiLz48cGF0aCBmaWxsPSIjMDA4NkMzIiBkPSJNMzEuMTY3LDIwLjMxMWMwLDIuNDMzLTEuOTY5LDQuNDAxLTQuNDAzLDQuNDAxYy0yLjQyNywwLTQuNDAxLTEuOTctNC40MDEtNC40MDEgICAgICAgICAgICAgICAgIGMwLTIuNDMzLDEuOTc1LTQuNDAxLDQuNDAxLTQuNDAxQzI5LjIsMTUuOTA5LDMxLjE2NywxNy44NzksMzEuMTY3LDIwLjMxMXoiLz48Y2lyY2xlIGZpbGw9IiMwMDg2QzMiIGN4PSIxNy44MDEiIGN5PSIyOS4xMzEiIHI9IjQuNDAyIi8+PGc+PHBhdGggZmlsbD0iIzAwODZDMyIgZD0iTTIwLjQ0MS0wLjA0NUM5LjIwNy0wLjA0NCwwLjEsOS4wNjMsMC4wOTksMjAuMjk4QzAuMSwzMS41MzIsOS4yMDcsNDAuNjM5LDIwLjQ0MSw0MC42NDEgICAgICAgICAgICAgICAgICAgICBjMTEuMjM1LTAuMDAyLDIwLjM0MS05LjEwNywyMC4zNDMtMjAuMzQzQzQwLjc4Myw5LjA2MywzMS42NzctMC4wNDQsMjAuNDQxLTAuMDQ1eiBNMzEuODkxLDMxLjc0NyAgICAgICAgICAgICAgICAgICAgIGMtMi45MzcsMi45MzQtNi45NzIsNC43NDItMTEuNDUsNC43NDNjLTQuNDc4LTAuMDAxLTguNTEzLTEuODExLTExLjQ1LTQuNzQzQzYuMDU4LDI4LjgxLDQuMjUsMjQuNzc1LDQuMjQ5LDIwLjI5OCAgICAgICAgICAgICAgICAgICAgIGMwLjAwMS00LjQ3OCwxLjgwOS04LjUxMyw0Ljc0My0xMS40NWMyLjkzNy0yLjkzNCw2Ljk3Mi00Ljc0MiwxMS40NS00Ljc0M2M0LjQ3OCwwLjAwMSw4LjUxMywxLjgxLDExLjQ1LDQuNzQzICAgICAgICAgICAgICAgICAgICAgYzIuOTM0LDIuOTM4LDQuNzQyLDYuOTczLDQuNzQzLDExLjQ1QzM2LjYzMywyNC43NzUsMzQuODI1LDI4LjgxLDMxLjg5MSwzMS43NDd6Ii8+PC9nPjxnPjxwYXRoIGZpbGw9IiMwMDg2QzMiIGQ9Ik0xNTMuOTg1LDkuOTVjLTEuMTk1LDAtMi4xNjQsMC45NzEtMi4xNjQsMi4xNjhjMC4wMDIsMS4xOTcsMC45NjksMi4xNjgsMi4xNjQsMi4xNjggICAgICAgICAgICAgICAgICAgICBjMS4xOTksMCwyLjE3Mi0wLjk3MSwyLjE3Mi0yLjE2OFMxNTUuMTg0LDkuOTUsMTUzLjk4NSw5Ljk1eiBNMTUzLjk4NSwxMy45NjhjLTEuMDIxLTAuMDAyLTEuODQ2LTAuODI3LTEuODQ2LTEuODUgICAgICAgICAgICAgICAgICAgICBjMC4wMDItMS4wMjEsMC44MjUtMS44NDksMS44NDYtMS44NTFjMS4wMjMsMC4wMDIsMS44NTIsMC44MjgsMS44NTQsMS44NTFDMTU1LjgzNiwxMy4xNDEsMTU1LjAwOCwxMy45NjYsMTUzLjk4NSwxMy45Njh6Ii8+PC9nPjxnPjxwYXRoIGZpbGw9IiMwMDg2QzMiIGQ9Ik0xNTQuNTA3LDEzLjQwOWwtMC41NC0xLjA4aC0wLjQ4NnYxLjA4aC0wLjM4OXYtMi41NjRoMC45OTRjMC40ODQsMCwwLjc5NiwwLjMxMywwLjc5NiwwLjc1ICAgICAgICAgICAgICAgICAgICAgYzAsMC4zNjctMC4yMjQsMC42MDItMC41MTMsMC42OGwwLjU5MiwxLjEzNkwxNTQuNTA3LDEzLjQwOUwxNTQuNTA3LDEzLjQwOXogTTE1NC4wNTYsMTEuMTk1aC0wLjU3NXYwLjgwM2gwLjU3NSBjMC4yNjEsMCwwLjQzNy0wLjE0NywwLjQzNy0wLjM5OVMxNTQuMzE3LDExLjE5NSwxNTQuMDU2LDExLjE5NXoiLz48L2c+PC9nPgogICAgICAgICAgICAgICAgICA8L3N2Zz4=}'
58
+ }
59
+ />
60
+ </Provider>,
61
+ );
62
+
63
+ await waitFor(() => {});
64
+ expect(container).toMatchSnapshot();
65
+ });
66
+ // test('renders a file widget component with value in raw data', async () => {
67
+ // const store = mockStore({
68
+ // intl: {
69
+ // locale: 'en',
70
+ // messages: {},
71
+ // },
72
+ // });
73
+
74
+ // const { container } = render(
75
+ // <Provider store={store}>
76
+ // <RegistryImageWidget
77
+ // id="my-field"
78
+ // title="My field"
79
+ // fieldSet="default"
80
+ // onChange={() => {}}
81
+ // value={
82
+ // 'filenameb64:bG9nby5jYWI5NDVkOC5zdmc=;datab64:PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2ZXJzaW9uPSIxLjEiIGlkPSJMYXllcl8xIiB4PSIwcHgiIHk9IjBweCIgd2lkdGg9IjE1OC4yNTNweCIgaGVpZ2h0PSI0MC42ODZweCIgdmlld0JveD0iMCAwIDE1OC4yNTMgNDAuNjg2IiBlbmFibGUtYmFja2dyb3VuZD0ibmV3IDAgMCAxNTguMjUzIDQwLjY4NiIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+CiAgICAgICAgICAgICAgICAgICAgPGc+PHBhdGggZmlsbD0iIzAwODZDMyIgZD0iTTY1LjMyNywyMy4yMDhoLTYuNTg5djExLjM4OGgtNC4zOTNWNS42MzhoMTAuOTgxYzUuNjUzLDAsOS4yNzEsMy43NDIsOS4yNzEsOC43ODUgICAgICAgICAgICAgICAgIFM3MC45NzksMjMuMjA4LDY1LjMyNywyMy4yMDh6IE02NS4wODIsOS41ODNoLTYuMzQ1djkuNjM5aDYuMzQ1YzMuMDUsMCw1LjEyNC0xLjc0OSw1LjEyNC00Ljc5OSAgICAgICAgICAgICAgICAgQzcwLjIwNiwxMS4zNzIsNjguMTMyLDkuNTgzLDY1LjA4Miw5LjU4M3oiLz48cGF0aCBmaWxsPSIjMDA4NkMzIiBkPSJNODMuOTY5LDM0LjU5NmMtMy45MDQsMC01LjY1Mi0yLjY0NC01LjY1Mi01LjY5M1Y1LjYzOGg0LjE0OHYyMy4wMjFjMCwxLjU4NywwLjU2NywyLjM5OSwyLjIzNSwyLjM5OWgxLjgzICAgICAgICAgICAgICAgICB2My41MzhIODMuOTY5eiIvPjxwYXRoIGZpbGw9IiMwMDg2QzMiIGQ9Ik0xMDQuNzYyLDMyLjM5OWMtMS4zNDQsMS4zODQtMy4zNzcsMi40NC02LjE4NCwyLjQ0Yy0yLjgwNSwwLTQuNzk5LTEuMDU4LTYuMTQxLTIuNDQgICAgICAgICAgICAgICAgIGMtMS45NTEtMi4wMzItMi40MzktNC42MzctMi40MzktOC4xMzRjMC0zLjQ1NywwLjQ4OC02LjA2MSwyLjQzOS04LjA5NGMxLjM0Mi0xLjM4MywzLjMzNi0yLjQ0LDYuMTQxLTIuNDQgICAgICAgICAgICAgICAgIGMyLjgwNywwLDQuODQsMS4wNTksNi4xODQsMi40NGMxLjk1MSwyLjAzMywyLjQzOSw0LjYzNywyLjQzOSw4LjA5NEMxMDcuMjAzLDI3Ljc2MywxMDYuNzEzLDMwLjM2NiwxMDQuNzYyLDMyLjM5OXogICAgICAgICAgICAgICAgICBNMTAxLjYyOSwxOC42MTNjLTAuNzczLTAuNzczLTEuODMtMS4xODEtMy4wNTEtMS4xODFjLTEuMjE5LDAtMi4yMzYsMC40MDYtMy4wMSwxLjE4MWMtMS4yNiwxLjI2MS0xLjQyMiwzLjQxNi0xLjQyMiw1LjY1MiAgICAgICAgICAgICAgICAgczAuMTYyLDQuMzkzLDEuNDIyLDUuNjUzYzAuNzczLDAuNzcxLDEuNzkxLDEuMjIsMy4wMSwxLjIyYzEuMjIxLDAsMi4yNzctMC40NDcsMy4wNTEtMS4yMmMxLjI2Mi0xLjI2MiwxLjQyNC0zLjQxNywxLjQyNC01LjY1MyAgICAgICAgICAgICAgICAgUzEwMi44OTEsMTkuODczLDEwMS42MjksMTguNjEzeiIvPjxwYXRoIGZpbGw9IiMwMDg2QzMiIGQ9Ik0xMjMuNjQzLDM0LjU5NlYyMi4wMjljMC0zLjIxNC0xLjgzLTQuNTk3LTQuMTQ3LTQuNTk3cy00LjI3MSwxLjQyMy00LjI3MSw0LjU5N3YxMi41NjZoLTQuMTQ3di0yMC42MiAgICAgICAgICAgICAgICAgaDQuMDY1djIuMDc0YzEuNDI1LTEuNTQ2LDMuNDE2LTIuMzE4LDUuNDktMi4zMThjMi4xMTUsMCwzLjg2NSwwLjY5MSw1LjA4NCwxLjg3MWMxLjU4NiwxLjU0NSwyLjA3NCwzLjQ5NywyLjA3NCw1LjgxNXYxMy4xNzggICAgICAgICAgICAgICAgIEwxMjMuNjQzLDM0LjU5NkwxMjMuNjQzLDM0LjU5NnoiLz48cGF0aCBmaWxsPSIjMDA4NkMzIiBkPSJNMTM1Ljc3MiwyNS40ODZjMCwzLjUzNywxLjg3MSw1Ljc3NCw1LjI0Niw1Ljc3NGMyLjMxNywwLDMuNTM5LTAuNjQ5LDUuMDA0LTIuMTE1bDIuNjQzLDIuNDgxICAgICAgICAgICAgICAgICBjLTIuMTE1LDIuMTE0LTQuMTA3LDMuMjEzLTcuNzI3LDMuMjEzYy01LjE2NiwwLTkuMjczLTIuNzI1LTkuMjczLTEwLjU3NGMwLTYuNjcxLDMuNDU3LTEwLjUzNCw4Ljc0NC0xMC41MzQgICAgICAgICAgICAgICAgIGM1LjUzMSwwLDguNzQ0LDQuMDY3LDguNzQ0LDkuOTI1djEuODNIMTM1Ljc3MnogTTE0NC40NzUsMTkuNzkxYy0wLjY1LTEuNTQ1LTIuMTEzLTIuNjA0LTQuMDY2LTIuNjA0ICAgICAgICAgICAgICAgICBjLTEuOTUxLDAtMy40NTcsMS4wNTktNC4xMDcsMi42MDRjLTAuNDA2LDAuOTM2LTAuNDg4LDEuNTQ2LTAuNTI5LDIuODA3aDkuMjczQzE0NS4wMDMsMjEuMzM3LDE0NC44ODMsMjAuNzI2LDE0NC40NzUsMTkuNzkxeiIvPjxjaXJjbGUgZmlsbD0iIzAwODZDMyIgY3g9IjE3LjgxNSIgY3k9IjExLjUxNiIgcj0iNC40MDIiLz48cGF0aCBmaWxsPSIjMDA4NkMzIiBkPSJNMzEuMTY3LDIwLjMxMWMwLDIuNDMzLTEuOTY5LDQuNDAxLTQuNDAzLDQuNDAxYy0yLjQyNywwLTQuNDAxLTEuOTctNC40MDEtNC40MDEgICAgICAgICAgICAgICAgIGMwLTIuNDMzLDEuOTc1LTQuNDAxLDQuNDAxLTQuNDAxQzI5LjIsMTUuOTA5LDMxLjE2NywxNy44NzksMzEuMTY3LDIwLjMxMXoiLz48Y2lyY2xlIGZpbGw9IiMwMDg2QzMiIGN4PSIxNy44MDEiIGN5PSIyOS4xMzEiIHI9IjQuNDAyIi8+PGc+PHBhdGggZmlsbD0iIzAwODZDMyIgZD0iTTIwLjQ0MS0wLjA0NUM5LjIwNy0wLjA0NCwwLjEsOS4wNjMsMC4wOTksMjAuMjk4QzAuMSwzMS41MzIsOS4yMDcsNDAuNjM5LDIwLjQ0MSw0MC42NDEgICAgICAgICAgICAgICAgICAgICBjMTEuMjM1LTAuMDAyLDIwLjM0MS05LjEwNywyMC4zNDMtMjAuMzQzQzQwLjc4Myw5LjA2MywzMS42NzctMC4wNDQsMjAuNDQxLTAuMDQ1eiBNMzEuODkxLDMxLjc0NyAgICAgICAgICAgICAgICAgICAgIGMtMi45MzcsMi45MzQtNi45NzIsNC43NDItMTEuNDUsNC43NDNjLTQuNDc4LTAuMDAxLTguNTEzLTEuODExLTExLjQ1LTQuNzQzQzYuMDU4LDI4LjgxLDQuMjUsMjQuNzc1LDQuMjQ5LDIwLjI5OCAgICAgICAgICAgICAgICAgICAgIGMwLjAwMS00LjQ3OCwxLjgwOS04LjUxMyw0Ljc0My0xMS40NWMyLjkzNy0yLjkzNCw2Ljk3Mi00Ljc0MiwxMS40NS00Ljc0M2M0LjQ3OCwwLjAwMSw4LjUxMywxLjgxLDExLjQ1LDQuNzQzICAgICAgICAgICAgICAgICAgICAgYzIuOTM0LDIuOTM4LDQuNzQyLDYuOTczLDQuNzQzLDExLjQ1QzM2LjYzMywyNC43NzUsMzQuODI1LDI4LjgxLDMxLjg5MSwzMS43NDd6Ii8+PC9nPjxnPjxwYXRoIGZpbGw9IiMwMDg2QzMiIGQ9Ik0xNTMuOTg1LDkuOTVjLTEuMTk1LDAtMi4xNjQsMC45NzEtMi4xNjQsMi4xNjhjMC4wMDIsMS4xOTcsMC45NjksMi4xNjgsMi4xNjQsMi4xNjggICAgICAgICAgICAgICAgICAgICBjMS4xOTksMCwyLjE3Mi0wLjk3MSwyLjE3Mi0yLjE2OFMxNTUuMTg0LDkuOTUsMTUzLjk4NSw5Ljk1eiBNMTUzLjk4NSwxMy45NjhjLTEuMDIxLTAuMDAyLTEuODQ2LTAuODI3LTEuODQ2LTEuODUgICAgICAgICAgICAgICAgICAgICBjMC4wMDItMS4wMjEsMC44MjUtMS44NDksMS44NDYtMS44NTFjMS4wMjMsMC4wMDIsMS44NTIsMC44MjgsMS44NTQsMS44NTFDMTU1LjgzNiwxMy4xNDEsMTU1LjAwOCwxMy45NjYsMTUzLjk4NSwxMy45Njh6Ii8+PC9nPjxnPjxwYXRoIGZpbGw9IiMwMDg2QzMiIGQ9Ik0xNTQuNTA3LDEzLjQwOWwtMC41NC0xLjA4aC0wLjQ4NnYxLjA4aC0wLjM4OXYtMi41NjRoMC45OTRjMC40ODQsMCwwLjc5NiwwLjMxMywwLjc5NiwwLjc1ICAgICAgICAgICAgICAgICAgICAgYzAsMC4zNjctMC4yMjQsMC42MDItMC41MTMsMC42OGwwLjU5MiwxLjEzNkwxNTQuNTA3LDEzLjQwOUwxNTQuNTA3LDEzLjQwOXogTTE1NC4wNTYsMTEuMTk1aC0wLjU3NXYwLjgwM2gwLjU3NSBjMC4yNjEsMCwwLjQzNy0wLjE0NywwLjQzNy0wLjM5OVMxNTQuMzE3LDExLjE5NSwxNTQuMDU2LDExLjE5NXoiLz48L2c+PC9nPgogICAgICAgICAgICAgICAgICA8L3N2Zz4=}'
83
+ // }
84
+ // />
85
+ // </Provider>,
86
+ // );
87
+
88
+ // await waitFor(() => {});
89
+ // expect(container).toMatchSnapshot();
90
+ // });
91
+ });
@@ -167,6 +167,19 @@ class SelectWidget extends Component {
167
167
  }
168
168
  }
169
169
 
170
+ componentDidUpdate(prevProps) {
171
+ if (
172
+ this.props.vocabBaseUrl !== prevProps.vocabBaseUrl &&
173
+ (!this.props.choices || this.props.choices?.length === 0)
174
+ ) {
175
+ this.props.getVocabulary({
176
+ vocabNameOrURL: this.props.vocabBaseUrl,
177
+ size: -1,
178
+ subrequest: this.props.lang,
179
+ });
180
+ }
181
+ }
182
+
170
183
  /**
171
184
  * Render method.
172
185
  * @method render
@@ -190,7 +203,8 @@ class SelectWidget extends Component {
190
203
  })),
191
204
  // Only set "no-value" option if there's no default in the field
192
205
  // TODO: also if this.props.defaultValue?
193
- ...(this.props.noValueOption && !this.props.default
206
+ ...(this.props.noValueOption &&
207
+ (this.props.default === undefined || this.props.default === null)
194
208
  ? [
195
209
  {
196
210
  label: this.props.intl.formatMessage(messages.no_value),
@@ -1,7 +1,7 @@
1
1
  import React from 'react';
2
2
  import configureStore from 'redux-mock-store';
3
3
  import { Provider } from 'react-intl-redux';
4
- import { waitFor, render, screen } from '@testing-library/react';
4
+ import { waitFor, render, screen, fireEvent } from '@testing-library/react';
5
5
 
6
6
  import SelectWidget from './SelectWidget';
7
7
 
@@ -43,3 +43,47 @@ test('renders a select widget component', async () => {
43
43
  await waitFor(() => screen.getByText('My field'));
44
44
  expect(container).toMatchSnapshot();
45
45
  });
46
+
47
+ test("No 'No value' option when default value is 0", async () => {
48
+ const store = mockStore({
49
+ intl: {
50
+ locale: 'en',
51
+ messages: {},
52
+ },
53
+ });
54
+
55
+ const choices = [
56
+ ['0', 'None'],
57
+ ['1', 'One'],
58
+ ];
59
+
60
+ const value = {
61
+ value: '0',
62
+ label: 'None',
63
+ };
64
+
65
+ const _default = 0;
66
+
67
+ const { container } = render(
68
+ <Provider store={store}>
69
+ <SelectWidget
70
+ id="my-field"
71
+ title="My field"
72
+ fieldSet="default"
73
+ choices={choices}
74
+ default={_default}
75
+ value={value}
76
+ onChange={() => {}}
77
+ onBlur={() => {}}
78
+ onClick={() => {}}
79
+ />
80
+ </Provider>,
81
+ );
82
+
83
+ await waitFor(() => screen.getByText('None'));
84
+ fireEvent.mouseDown(
85
+ container.querySelector('.react-select__dropdown-indicator'),
86
+ { button: 0 },
87
+ );
88
+ expect(container).toMatchSnapshot();
89
+ });
@@ -1,6 +1,13 @@
1
- import React from 'react';
2
- import { toPublicURL, Helmet } from '@plone/volto/helpers';
1
+ import React, { useEffect } from 'react';
2
+ import {
3
+ toPublicURL,
4
+ Helmet,
5
+ hasApiExpander,
6
+ getBaseUrl,
7
+ } from '@plone/volto/helpers';
8
+ import { getNavroot } from '@plone/volto/actions';
3
9
  import config from '@plone/volto/registry';
10
+ import { useDispatch, useSelector } from 'react-redux';
4
11
 
5
12
  const ContentMetadataTags = (props) => {
6
13
  const {
@@ -14,6 +21,17 @@ const ContentMetadataTags = (props) => {
14
21
  description,
15
22
  } = props.content;
16
23
 
24
+ const dispatch = useDispatch();
25
+ const pathname = useSelector((state) => state.router.location.pathname);
26
+ const navroot = useSelector((state) => state.navroot?.data?.navroot);
27
+ const site = useSelector((state) => state.site?.data);
28
+
29
+ useEffect(() => {
30
+ if (pathname && !hasApiExpander('navroot', getBaseUrl(pathname))) {
31
+ dispatch(getNavroot(getBaseUrl(pathname)));
32
+ }
33
+ }, [dispatch, pathname]);
34
+
17
35
  const getContentImageInfo = () => {
18
36
  const { contentMetadataTagsImageField } = config.settings;
19
37
  const image = props.content[contentMetadataTagsImageField];
@@ -45,10 +63,26 @@ const ContentMetadataTags = (props) => {
45
63
 
46
64
  const contentImageInfo = getContentImageInfo();
47
65
 
66
+ const getTitle = () => {
67
+ const includeSiteTitle =
68
+ config?.settings?.siteTitleFormat?.includeSiteTitle || false;
69
+ const titleAndSiteTitleSeparator =
70
+ config?.settings?.titleAndSiteTitleSeparator || '-';
71
+ const navRootTitle = navroot?.title;
72
+ const siteRootTitle = site?.['plone.site_title'];
73
+ const titlePart = navRootTitle || siteRootTitle;
74
+
75
+ if (includeSiteTitle && titlePart && titlePart !== title) {
76
+ return seo_title || `${title} ${titleAndSiteTitleSeparator} ${titlePart}`;
77
+ } else {
78
+ return seo_title || title;
79
+ }
80
+ };
81
+
48
82
  return (
49
83
  <>
50
84
  <Helmet>
51
- <title>{(seo_title || title)?.replace(/\u00AD/g, '')}</title>
85
+ <title>{getTitle()?.replace(/\u00AD/g, '')}</title>
52
86
  <meta name="description" content={seo_description || description} />
53
87
  <meta
54
88
  property="og:title"