@plone/volto 18.30.1 → 18.32.0

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 (160) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/README.md +18 -12
  3. package/locales/af/LC_MESSAGES/volto.po +33 -0
  4. package/locales/af.json +1 -1
  5. package/locales/ar/LC_MESSAGES/volto.po +33 -0
  6. package/locales/ar.json +1 -1
  7. package/locales/bg/LC_MESSAGES/volto.po +33 -0
  8. package/locales/bg.json +1 -1
  9. package/locales/bn/LC_MESSAGES/volto.po +33 -0
  10. package/locales/bn.json +1 -1
  11. package/locales/ca/LC_MESSAGES/volto.po +33 -0
  12. package/locales/ca.json +1 -1
  13. package/locales/cs/LC_MESSAGES/volto.po +33 -0
  14. package/locales/cs.json +1 -1
  15. package/locales/cy/LC_MESSAGES/volto.po +33 -0
  16. package/locales/cy.json +1 -1
  17. package/locales/da/LC_MESSAGES/volto.po +33 -0
  18. package/locales/da.json +1 -1
  19. package/locales/de/LC_MESSAGES/volto.po +33 -0
  20. package/locales/de.json +1 -1
  21. package/locales/el/LC_MESSAGES/volto.po +33 -0
  22. package/locales/el.json +1 -1
  23. package/locales/en/LC_MESSAGES/volto.po +33 -0
  24. package/locales/en.json +1 -1
  25. package/locales/en_AU/LC_MESSAGES/volto.po +33 -0
  26. package/locales/en_AU.json +1 -1
  27. package/locales/en_GB/LC_MESSAGES/volto.po +33 -0
  28. package/locales/en_GB.json +1 -1
  29. package/locales/eo/LC_MESSAGES/volto.po +33 -0
  30. package/locales/eo.json +1 -1
  31. package/locales/es/LC_MESSAGES/volto.po +33 -0
  32. package/locales/es.json +1 -1
  33. package/locales/et/LC_MESSAGES/volto.po +33 -0
  34. package/locales/et.json +1 -1
  35. package/locales/eu/LC_MESSAGES/volto.po +33 -0
  36. package/locales/eu.json +1 -1
  37. package/locales/fa/LC_MESSAGES/volto.po +33 -0
  38. package/locales/fa.json +1 -1
  39. package/locales/fi/LC_MESSAGES/volto.po +33 -0
  40. package/locales/fi.json +1 -1
  41. package/locales/fr/LC_MESSAGES/volto.po +33 -0
  42. package/locales/fr.json +1 -1
  43. package/locales/fu/LC_MESSAGES/volto.po +33 -0
  44. package/locales/fu.json +1 -1
  45. package/locales/gl/LC_MESSAGES/volto.po +33 -0
  46. package/locales/gl.json +1 -1
  47. package/locales/he/LC_MESSAGES/volto.po +33 -0
  48. package/locales/he.json +1 -1
  49. package/locales/hi/LC_MESSAGES/volto.po +33 -0
  50. package/locales/hi.json +1 -1
  51. package/locales/hr/LC_MESSAGES/volto.po +33 -0
  52. package/locales/hr.json +1 -1
  53. package/locales/hu/LC_MESSAGES/volto.po +33 -0
  54. package/locales/hu.json +1 -1
  55. package/locales/hy/LC_MESSAGES/volto.po +33 -0
  56. package/locales/hy.json +1 -1
  57. package/locales/id/LC_MESSAGES/volto.po +33 -0
  58. package/locales/id.json +1 -1
  59. package/locales/it/LC_MESSAGES/volto.po +40 -6
  60. package/locales/it.json +1 -1
  61. package/locales/ja/LC_MESSAGES/volto.po +33 -0
  62. package/locales/ja.json +1 -1
  63. package/locales/ka/LC_MESSAGES/volto.po +33 -0
  64. package/locales/ka.json +1 -1
  65. package/locales/kn/LC_MESSAGES/volto.po +33 -0
  66. package/locales/kn.json +1 -1
  67. package/locales/ko/LC_MESSAGES/volto.po +33 -0
  68. package/locales/ko.json +1 -1
  69. package/locales/lt/LC_MESSAGES/volto.po +33 -0
  70. package/locales/lt.json +1 -1
  71. package/locales/lv/LC_MESSAGES/volto.po +33 -0
  72. package/locales/lv.json +1 -1
  73. package/locales/mi/LC_MESSAGES/volto.po +33 -0
  74. package/locales/mi.json +1 -1
  75. package/locales/mk/LC_MESSAGES/volto.po +33 -0
  76. package/locales/mk.json +1 -1
  77. package/locales/my/LC_MESSAGES/volto.po +33 -0
  78. package/locales/my.json +1 -1
  79. package/locales/nb_NO/LC_MESSAGES/volto.po +33 -0
  80. package/locales/nb_NO.json +1 -1
  81. package/locales/nl/LC_MESSAGES/volto.po +33 -0
  82. package/locales/nl.json +1 -1
  83. package/locales/nn/LC_MESSAGES/volto.po +33 -0
  84. package/locales/nn.json +1 -1
  85. package/locales/pl/LC_MESSAGES/volto.po +33 -0
  86. package/locales/pl.json +1 -1
  87. package/locales/pt/LC_MESSAGES/volto.po +33 -0
  88. package/locales/pt.json +1 -1
  89. package/locales/pt_BR/LC_MESSAGES/volto.po +33 -0
  90. package/locales/pt_BR.json +1 -1
  91. package/locales/rm/LC_MESSAGES/volto.po +33 -0
  92. package/locales/rm.json +1 -1
  93. package/locales/ro/LC_MESSAGES/volto.po +33 -0
  94. package/locales/ro.json +1 -1
  95. package/locales/ru/LC_MESSAGES/volto.po +33 -0
  96. package/locales/ru.json +1 -1
  97. package/locales/sk/LC_MESSAGES/volto.po +33 -0
  98. package/locales/sk.json +1 -1
  99. package/locales/sl/LC_MESSAGES/volto.po +33 -0
  100. package/locales/sl.json +1 -1
  101. package/locales/sm/LC_MESSAGES/volto.po +33 -0
  102. package/locales/sm.json +1 -1
  103. package/locales/sq/LC_MESSAGES/volto.po +33 -0
  104. package/locales/sq.json +1 -1
  105. package/locales/sr/LC_MESSAGES/volto.po +33 -0
  106. package/locales/sr.json +1 -1
  107. package/locales/sr@cyrl/LC_MESSAGES/volto.po +33 -0
  108. package/locales/sr@cyrl.json +1 -1
  109. package/locales/sr@latn/LC_MESSAGES/volto.po +33 -0
  110. package/locales/sr@latn.json +1 -1
  111. package/locales/sv/LC_MESSAGES/volto.po +33 -0
  112. package/locales/sv.json +1 -1
  113. package/locales/ta/LC_MESSAGES/volto.po +36 -3
  114. package/locales/ta.json +1 -1
  115. package/locales/te/LC_MESSAGES/volto.po +33 -0
  116. package/locales/te.json +1 -1
  117. package/locales/th/LC_MESSAGES/volto.po +33 -0
  118. package/locales/th.json +1 -1
  119. package/locales/to/LC_MESSAGES/volto.po +33 -0
  120. package/locales/to.json +1 -1
  121. package/locales/tr/LC_MESSAGES/volto.po +33 -0
  122. package/locales/tr.json +1 -1
  123. package/locales/uk/LC_MESSAGES/volto.po +33 -0
  124. package/locales/uk.json +1 -1
  125. package/locales/vi/LC_MESSAGES/volto.po +33 -0
  126. package/locales/vi.json +1 -1
  127. package/locales/volto.pot +34 -1
  128. package/locales/zh_CN/LC_MESSAGES/volto.po +33 -0
  129. package/locales/zh_CN.json +1 -1
  130. package/locales/zh_Hant/LC_MESSAGES/volto.po +33 -0
  131. package/locales/zh_Hant.json +1 -1
  132. package/locales/zh_Hant_HK/LC_MESSAGES/volto.po +33 -0
  133. package/locales/zh_Hant_HK.json +1 -1
  134. package/package.json +3 -3
  135. package/src/components/manage/Contents/Contents.jsx +685 -665
  136. package/src/components/manage/Contents/DropZoneContent.jsx +338 -0
  137. package/src/components/manage/Form/ModalForm.jsx +12 -10
  138. package/src/components/manage/Form/ModalForm.test.jsx +26 -1
  139. package/src/components/manage/Sidebar/ObjectBrowser.jsx +3 -0
  140. package/src/components/manage/Sidebar/ObjectBrowserBody.jsx +13 -1
  141. package/src/components/manage/Widgets/DatetimeWidget.jsx +11 -1
  142. package/src/components/manage/Widgets/FormFieldWrapper.jsx +146 -168
  143. package/src/components/manage/Widgets/ObjectBrowserWidget.jsx +5 -0
  144. package/src/components/manage/Widgets/QueryWidget.jsx +137 -9
  145. package/src/components/theme/MultilingualRedirector/MultilingualRedirector.jsx +2 -2
  146. package/src/middleware/api.js +7 -2
  147. package/src/reducers/querystring/querystring.js +8 -1
  148. package/theme/themes/pastanaga/extras/contents.less +63 -0
  149. package/theme/themes/pastanaga/extras/widgets.less +34 -0
  150. package/types/components/manage/Contents/DropZoneContent.d.ts +2 -0
  151. package/types/components/manage/Controlpanels/Relations/RelationsMatrix.d.ts +1 -1
  152. package/types/components/manage/Multilingual/ManageTranslations.d.ts +1 -1
  153. package/types/components/manage/Sidebar/ObjectBrowser.d.ts +1 -1
  154. package/types/components/manage/Widgets/FormFieldWrapper.d.ts +28 -5
  155. package/types/components/manage/Widgets/ImageWidget.d.ts +1 -1
  156. package/types/components/manage/Widgets/InternalUrlWidget.d.ts +1 -1
  157. package/types/components/manage/Widgets/ObjectBrowserWidget.d.ts +2 -0
  158. package/types/components/manage/Widgets/QueryWidget.d.ts +5 -2
  159. package/types/components/manage/Widgets/UrlWidget.d.ts +1 -1
  160. package/types/components/manage/Widgets/index.d.ts +4 -4
@@ -0,0 +1,338 @@
1
+ import { useState, useEffect } from 'react';
2
+ import { useDispatch, useSelector, shallowEqual } from 'react-redux';
3
+ import { defineMessages, useIntl, FormattedMessage } from 'react-intl';
4
+ import {
5
+ Button,
6
+ Modal,
7
+ Table,
8
+ Input,
9
+ Dimmer,
10
+ Progress,
11
+ } from 'semantic-ui-react';
12
+ import cx from 'classnames';
13
+ import filesize from 'filesize';
14
+ import { readAsDataURL } from 'promise-file-reader';
15
+
16
+ import { createContent } from '@plone/volto/actions/content/content';
17
+ import { usePrevious } from '@plone/volto/helpers/Utils/usePrevious';
18
+ import { validateFileUploadSize } from '@plone/volto/helpers/FormValidation/FormValidation';
19
+ import Icon from '@plone/volto/components/theme/Icon/Icon';
20
+ import uploadSVG from '@plone/volto/icons/upload.svg';
21
+ import clearSVG from '@plone/volto/icons/clear.svg';
22
+ import FormattedRelativeDate from '@plone/volto/components/theme/FormattedDate/FormattedRelativeDate';
23
+ import Image from '@plone/volto/components/theme/Image/Image';
24
+
25
+ const SUBREQUEST = 'batch-upload';
26
+
27
+ const messages = defineMessages({
28
+ cancel: {
29
+ id: 'Cancel',
30
+ defaultMessage: 'Cancel',
31
+ },
32
+ upload: {
33
+ id: '{count, plural, one {Upload {count} file} other {Upload {count} files}}',
34
+ defaultMessage:
35
+ '{count, plural, one {Upload {count} file} other {Upload {count} files}}',
36
+ },
37
+ filesUploaded: {
38
+ id: 'Files uploaded: {uploadedFiles}',
39
+ defaultMessage: 'Files uploaded: {uploadedFiles}',
40
+ },
41
+ dropFiles: {
42
+ id: 'Drop files here to upload',
43
+ defaultMessage: 'Drop files here to upload',
44
+ },
45
+ releaseToAdd: {
46
+ id: 'Release to add file(s) to this folder',
47
+ defaultMessage: 'Release to add file(s) to this folder',
48
+ },
49
+ totalFilesToUpload: {
50
+ id: 'Total files to upload: {totalFiles}',
51
+ defaultMessage: 'Total files to upload: {totalFiles}',
52
+ },
53
+ uploadFiles: {
54
+ id: 'Upload Files ({count})',
55
+ defaultMessage: 'Upload Files ({count})',
56
+ },
57
+ });
58
+ const hasFiles = (e) => {
59
+ return e.dataTransfer.types && e.dataTransfer.types.includes('Files');
60
+ };
61
+
62
+ const DropZoneContent = (props) => {
63
+ const { onOk, onCancel, pathname, children } = props;
64
+ const [isDragOver, setIsDragOver] = useState(false);
65
+ const [showModal, setShowModal] = useState(false);
66
+ const [droppedFiles, setDroppedFiles] = useState([]);
67
+ const [totalFiles, setTotalFiles] = useState(0);
68
+
69
+ const intl = useIntl();
70
+ const dispatch = useDispatch();
71
+
72
+ const request = useSelector(
73
+ (state) => state.content.subrequests?.[SUBREQUEST] || {},
74
+ shallowEqual,
75
+ );
76
+
77
+ const uploadedFiles = useSelector((state) => state.content.uploadedFiles);
78
+ const prevrequestloading = usePrevious(request.loading);
79
+
80
+ useEffect(() => {
81
+ if (prevrequestloading && request.loaded) {
82
+ onOk();
83
+ setDroppedFiles([]);
84
+ }
85
+ }, [prevrequestloading, request.loaded, onOk]);
86
+
87
+ const handleDragEnter = (e) => {
88
+ if (!hasFiles(e)) {
89
+ return;
90
+ }
91
+ e.preventDefault();
92
+ e.stopPropagation();
93
+ setIsDragOver(true);
94
+ };
95
+
96
+ const handleDragLeave = (e) => {
97
+ if (!hasFiles(e)) {
98
+ return;
99
+ }
100
+ e.preventDefault();
101
+ e.stopPropagation();
102
+ if (!e.currentTarget.contains(e.relatedTarget)) {
103
+ setIsDragOver(false);
104
+ }
105
+ };
106
+
107
+ const handleDragOver = (e) => {
108
+ if (!hasFiles(e)) {
109
+ return;
110
+ }
111
+ e.preventDefault();
112
+ e.stopPropagation();
113
+ };
114
+
115
+ const onDrop = async (e) => {
116
+ if (!hasFiles(e)) {
117
+ return;
118
+ }
119
+ setIsDragOver(false);
120
+ const newFiles = Array.from(e.dataTransfer.files);
121
+ const validFiles = [];
122
+ for (let i = 0; i < newFiles.length; i++) {
123
+ if (validateFileUploadSize(newFiles[i], intl.formatMessage)) {
124
+ await readAsDataURL(newFiles[i]).then((data) => {
125
+ const fields = data.match(/^data:(.*);(.*),(.*)$/);
126
+ newFiles[i].preview = fields[0];
127
+ });
128
+ validFiles.push(newFiles[i]);
129
+ }
130
+ }
131
+ setDroppedFiles((prev) => prev.concat(validFiles));
132
+ setTotalFiles((prev) => prev + validFiles.length);
133
+ setShowModal(true);
134
+ };
135
+
136
+ const handleCloseModal = () => {
137
+ setShowModal(false);
138
+ onCancel();
139
+ setDroppedFiles([]);
140
+ setTotalFiles(0);
141
+ };
142
+
143
+ const onSubmit = () => {
144
+ Promise.all(droppedFiles.map((file) => readAsDataURL(file))).then(
145
+ (dataUrls) => {
146
+ dispatch(
147
+ createContent(
148
+ pathname,
149
+ droppedFiles.map((file, index) => {
150
+ const fields = dataUrls[index].match(/^data:(.*);(.*),(.*)$/);
151
+ const image = fields[1].split('/')[0] === 'image';
152
+ return {
153
+ '@type': image ? 'Image' : 'File',
154
+ title: file.name,
155
+ [image ? 'image' : 'file']: {
156
+ data: fields[3],
157
+ encoding: fields[2],
158
+ 'content-type': fields[1],
159
+ filename: file.name,
160
+ },
161
+ };
162
+ }),
163
+ SUBREQUEST,
164
+ ),
165
+ );
166
+ },
167
+ );
168
+ handleCloseModal();
169
+ };
170
+ const onRemoveFile = (index) => {
171
+ const updatedFiles = droppedFiles.filter((file, i) => i !== index);
172
+ setDroppedFiles(updatedFiles);
173
+ setTotalFiles(updatedFiles.length);
174
+ };
175
+
176
+ const onChangeFileName = (e, index) => {
177
+ let copyOfFiles = [...droppedFiles];
178
+ let originalFile = droppedFiles[index];
179
+ let newFile = new File([originalFile], e.target.value, {
180
+ type: originalFile.type,
181
+ });
182
+
183
+ newFile.preview = originalFile.preview;
184
+ newFile.path = e.target.value;
185
+ copyOfFiles[index] = newFile;
186
+ setDroppedFiles(copyOfFiles);
187
+ };
188
+
189
+ return (
190
+ <>
191
+ <div
192
+ className={cx('contents-dropzone', {
193
+ 'drag-over': isDragOver,
194
+ 'drag-inactive': !isDragOver,
195
+ })}
196
+ onDragEnter={handleDragEnter}
197
+ onDragLeave={handleDragLeave}
198
+ onDragOver={handleDragOver}
199
+ onDrop={onDrop}
200
+ >
201
+ {children}
202
+ {isDragOver && (
203
+ <div className="dropzone-overlay">
204
+ <div className="dropzone-content">
205
+ <Icon name={uploadSVG} size="48px" />
206
+ <h3>{intl.formatMessage(messages.dropFiles)}</h3>
207
+ <p>{intl.formatMessage(messages.releaseToAdd)}</p>
208
+ </div>
209
+ </div>
210
+ )}
211
+ </div>
212
+ <Modal
213
+ open={totalFiles > 0 && showModal}
214
+ onClose={handleCloseModal}
215
+ className="contents-upload-modal"
216
+ >
217
+ <Modal.Header>
218
+ {intl.formatMessage(messages.uploadFiles, {
219
+ count: droppedFiles.length,
220
+ })}
221
+ </Modal.Header>
222
+ <Dimmer active={request.loading}>
223
+ <div className="progress-container">
224
+ <Progress
225
+ className="progress-bar"
226
+ value={uploadedFiles}
227
+ total={totalFiles}
228
+ >
229
+ {intl.formatMessage(messages.filesUploaded, {
230
+ uploadedFiles,
231
+ })}
232
+ <br />
233
+ {intl.formatMessage(messages.totalFilesToUpload, {
234
+ totalFiles,
235
+ })}
236
+ </Progress>
237
+ </div>
238
+ </Dimmer>
239
+ <Modal.Content>
240
+ {droppedFiles.length > 0 && (
241
+ <Table compact singleLine>
242
+ <Table.Header>
243
+ <Table.Row>
244
+ <Table.HeaderCell width={8}>
245
+ <FormattedMessage id="Filename" defaultMessage="Filename" />
246
+ </Table.HeaderCell>
247
+ <Table.HeaderCell width={4}>
248
+ <FormattedMessage
249
+ id="Last modified"
250
+ defaultMessage="Last modified"
251
+ />
252
+ </Table.HeaderCell>
253
+ <Table.HeaderCell width={4}>
254
+ <FormattedMessage
255
+ id="File size"
256
+ defaultMessage="File size"
257
+ />
258
+ </Table.HeaderCell>
259
+ <Table.HeaderCell width={4}>
260
+ <FormattedMessage id="Preview" defaultMessage="Preview" />
261
+ </Table.HeaderCell>
262
+ <Table.HeaderCell />
263
+ </Table.Row>
264
+ </Table.Header>
265
+ <Table.Body>
266
+ {droppedFiles.map((file, index) => (
267
+ <Table.Row className="upload-row" key={index}>
268
+ <Table.Cell>
269
+ <Input
270
+ className="file-name"
271
+ value={file.name}
272
+ onChange={(e) => onChangeFileName(e, index)}
273
+ />
274
+ </Table.Cell>
275
+ <Table.Cell>
276
+ {file.lastModifiedDate && (
277
+ <FormattedRelativeDate date={file.lastModifiedDate} />
278
+ )}
279
+ </Table.Cell>
280
+ <Table.Cell>{filesize(file.size, { round: 0 })}</Table.Cell>
281
+ <Table.Cell>
282
+ {file.type.split('/')[0] === 'image' && (
283
+ <Image
284
+ src={file.preview}
285
+ height={60}
286
+ className="ui image"
287
+ />
288
+ )}
289
+ </Table.Cell>
290
+ <Table.Cell>
291
+ <Icon
292
+ name={clearSVG}
293
+ size="24px"
294
+ onClick={() => onRemoveFile(index)}
295
+ />
296
+ </Table.Cell>
297
+ </Table.Row>
298
+ ))}
299
+ </Table.Body>
300
+ </Table>
301
+ )}
302
+ </Modal.Content>
303
+ <Modal.Actions>
304
+ {droppedFiles.length > 0 && (
305
+ <Button
306
+ basic
307
+ circular
308
+ primary
309
+ floated="right"
310
+ icon="arrow right"
311
+ aria-label={intl.formatMessage(messages.upload, {
312
+ count: droppedFiles.length,
313
+ })}
314
+ onClick={onSubmit}
315
+ title={intl.formatMessage(messages.upload, {
316
+ count: droppedFiles.length,
317
+ })}
318
+ size="big"
319
+ />
320
+ )}
321
+ <Button
322
+ basic
323
+ circular
324
+ secondary
325
+ icon="remove"
326
+ aria-label={intl.formatMessage(messages.cancel)}
327
+ title={intl.formatMessage(messages.cancel)}
328
+ floated="right"
329
+ size="big"
330
+ onClick={handleCloseModal}
331
+ />
332
+ </Modal.Actions>
333
+ </Modal>
334
+ </>
335
+ );
336
+ };
337
+
338
+ export default DropZoneContent;
@@ -245,15 +245,17 @@ class ModalForm extends Component {
245
245
  const { schema, onCancel, description } = this.props;
246
246
  const currentFieldset = schema.fieldsets[this.state.currentTab];
247
247
 
248
- const fields = map(currentFieldset.fields, (field) => ({
249
- ...schema.properties[field],
250
- id: field,
251
- value: this.state.formData[field],
252
- required: schema.required.indexOf(field) !== -1,
253
- onChange: this.onChangeField,
254
- onBlur: this.onBlurField,
255
- onClick: this.onClickInput,
256
- }));
248
+ const fields = currentFieldset
249
+ ? map(currentFieldset.fields, (field) => ({
250
+ ...schema.properties[field],
251
+ id: field,
252
+ value: this.state.formData[field],
253
+ required: schema.required.indexOf(field) !== -1,
254
+ onChange: this.onChangeField,
255
+ onBlur: this.onBlurField,
256
+ onClick: this.onClickInput,
257
+ }))
258
+ : [];
257
259
 
258
260
  const state_errors = keys(this.state.errors).length > 0;
259
261
  return (
@@ -288,7 +290,7 @@ class ModalForm extends Component {
288
290
  )}
289
291
  <div>{this.props.submitError}</div>
290
292
  </Message>
291
- {schema.fieldsets.length > 1 && (
293
+ {schema.fieldsets?.length > 1 && (
292
294
  <Menu tabular stackable>
293
295
  {map(schema.fieldsets, (item, index) => (
294
296
  <Menu.Item
@@ -87,10 +87,35 @@ describe('ModalForm', () => {
87
87
  </Provider>
88
88
  );
89
89
  const { getByText } = render(jsx, {
90
- contaner: document.body,
90
+ container: document.body,
91
91
  });
92
92
 
93
93
  const loadingMessage = getByText(/renaming items.../i);
94
94
  expect(loadingMessage).toBeInTheDocument();
95
95
  });
96
+ it('renders with empty fieldsets array', () => {
97
+ const store = mockStore({
98
+ intl: {
99
+ locale: 'en',
100
+ messages: {},
101
+ },
102
+ });
103
+ const component = renderer.create(
104
+ <Provider store={store}>
105
+ <ModalForm
106
+ schema={{
107
+ fieldsets: [],
108
+ properties: {},
109
+ required: [],
110
+ }}
111
+ onSubmit={() => {}}
112
+ onCancel={() => {}}
113
+ open={false}
114
+ title="Action without form"
115
+ />
116
+ </Provider>,
117
+ );
118
+ const json = component.toJSON();
119
+ expect(json).toMatchSnapshot();
120
+ });
96
121
  });
@@ -58,6 +58,7 @@ const withObjectBrowser = (WrappedComponent) =>
58
58
  selectableTypes,
59
59
  maximumSelectionSize,
60
60
  currentPath,
61
+ onlyFolderishSelectable,
61
62
  } = {}) =>
62
63
  this.setState(() => ({
63
64
  isObjectBrowserOpen: true,
@@ -70,6 +71,7 @@ const withObjectBrowser = (WrappedComponent) =>
70
71
  selectableTypes,
71
72
  maximumSelectionSize,
72
73
  currentPath,
74
+ onlyFolderishSelectable,
73
75
  }));
74
76
 
75
77
  closeObjectBrowser = () => this.setState({ isObjectBrowserOpen: false });
@@ -110,6 +112,7 @@ const withObjectBrowser = (WrappedComponent) =>
110
112
  searchableTypes={this.state.searchableTypes}
111
113
  selectableTypes={this.state.selectableTypes}
112
114
  maximumSelectionSize={this.state.maximumSelectionSize}
115
+ onlyFolderishSelectable={this.state.onlyFolderishSelectable}
113
116
  />
114
117
  </SidebarPopup>
115
118
  </>
@@ -86,6 +86,7 @@ class ObjectBrowserBody extends Component {
86
86
  maximumSelectionSize: PropTypes.number,
87
87
  contextURL: PropTypes.string,
88
88
  searchableTypes: PropTypes.arrayOf(PropTypes.string),
89
+ onlyFolderishSelectable: PropTypes.bool,
89
90
  };
90
91
 
91
92
  /**
@@ -101,6 +102,7 @@ class ObjectBrowserBody extends Component {
101
102
  selectableTypes: [],
102
103
  searchableTypes: null,
103
104
  maximumSelectionSize: null,
105
+ onlyFolderishSelectable: false,
104
106
  };
105
107
 
106
108
  /**
@@ -329,7 +331,17 @@ class ObjectBrowserBody extends Component {
329
331
  };
330
332
 
331
333
  isSelectable = (item) => {
332
- const { maximumSelectionSize, data, mode, selectableTypes } = this.props;
334
+ const {
335
+ maximumSelectionSize,
336
+ data,
337
+ mode,
338
+ selectableTypes,
339
+ onlyFolderishSelectable,
340
+ } = this.props;
341
+
342
+ if (onlyFolderishSelectable && !item.is_folderish) {
343
+ return false;
344
+ }
333
345
  if (
334
346
  maximumSelectionSize &&
335
347
  data &&
@@ -84,6 +84,7 @@ const DatetimeWidgetComponent = (props) => {
84
84
  widget,
85
85
  noPastDates: propNoPastDates,
86
86
  isDisabled,
87
+ formData,
87
88
  } = props;
88
89
 
89
90
  const intl = useIntl();
@@ -106,12 +107,21 @@ const DatetimeWidgetComponent = (props) => {
106
107
  );
107
108
  }, [value, lang, moment]);
108
109
 
110
+ // If open_end is checked and this is the end field, don't render
111
+ if (id === 'end' && formData?.open_end) {
112
+ return null;
113
+ }
114
+
109
115
  const getInternalValue = () => {
110
116
  return parseDateTime(toBackendLang(lang), value, undefined, moment.default);
111
117
  };
112
118
 
113
119
  const getDateOnly = () => {
114
- return dateOnly || widget === 'date';
120
+ return (
121
+ dateOnly ||
122
+ widget === 'date' ||
123
+ ((id === 'start' || id === 'end') && formData?.whole_day)
124
+ );
115
125
  };
116
126
 
117
127
  const onDateChange = (date) => {