@plone/volto 18.31.0 → 18.32.1
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.
- package/CHANGELOG.md +23 -0
- package/locales/af/LC_MESSAGES/volto.po +33 -0
- package/locales/af.json +1 -1
- package/locales/ar/LC_MESSAGES/volto.po +33 -0
- package/locales/ar.json +1 -1
- package/locales/bg/LC_MESSAGES/volto.po +33 -0
- package/locales/bg.json +1 -1
- package/locales/bn/LC_MESSAGES/volto.po +33 -0
- package/locales/bn.json +1 -1
- package/locales/ca/LC_MESSAGES/volto.po +33 -0
- package/locales/ca.json +1 -1
- package/locales/cs/LC_MESSAGES/volto.po +33 -0
- package/locales/cs.json +1 -1
- package/locales/cy/LC_MESSAGES/volto.po +33 -0
- package/locales/cy.json +1 -1
- package/locales/da/LC_MESSAGES/volto.po +33 -0
- package/locales/da.json +1 -1
- package/locales/de/LC_MESSAGES/volto.po +33 -0
- package/locales/de.json +1 -1
- package/locales/el/LC_MESSAGES/volto.po +33 -0
- package/locales/el.json +1 -1
- package/locales/en/LC_MESSAGES/volto.po +33 -0
- package/locales/en.json +1 -1
- package/locales/en_AU/LC_MESSAGES/volto.po +33 -0
- package/locales/en_AU.json +1 -1
- package/locales/en_GB/LC_MESSAGES/volto.po +33 -0
- package/locales/en_GB.json +1 -1
- package/locales/eo/LC_MESSAGES/volto.po +33 -0
- package/locales/eo.json +1 -1
- package/locales/es/LC_MESSAGES/volto.po +33 -0
- package/locales/es.json +1 -1
- package/locales/et/LC_MESSAGES/volto.po +33 -0
- package/locales/et.json +1 -1
- package/locales/eu/LC_MESSAGES/volto.po +33 -0
- package/locales/eu.json +1 -1
- package/locales/fa/LC_MESSAGES/volto.po +33 -0
- package/locales/fa.json +1 -1
- package/locales/fi/LC_MESSAGES/volto.po +33 -0
- package/locales/fi.json +1 -1
- package/locales/fr/LC_MESSAGES/volto.po +33 -0
- package/locales/fr.json +1 -1
- package/locales/fu/LC_MESSAGES/volto.po +33 -0
- package/locales/fu.json +1 -1
- package/locales/gl/LC_MESSAGES/volto.po +33 -0
- package/locales/gl.json +1 -1
- package/locales/he/LC_MESSAGES/volto.po +33 -0
- package/locales/he.json +1 -1
- package/locales/hi/LC_MESSAGES/volto.po +33 -0
- package/locales/hi.json +1 -1
- package/locales/hr/LC_MESSAGES/volto.po +33 -0
- package/locales/hr.json +1 -1
- package/locales/hu/LC_MESSAGES/volto.po +33 -0
- package/locales/hu.json +1 -1
- package/locales/hy/LC_MESSAGES/volto.po +33 -0
- package/locales/hy.json +1 -1
- package/locales/id/LC_MESSAGES/volto.po +33 -0
- package/locales/id.json +1 -1
- package/locales/it/LC_MESSAGES/volto.po +33 -0
- package/locales/it.json +1 -1
- package/locales/ja/LC_MESSAGES/volto.po +33 -0
- package/locales/ja.json +1 -1
- package/locales/ka/LC_MESSAGES/volto.po +33 -0
- package/locales/ka.json +1 -1
- package/locales/kn/LC_MESSAGES/volto.po +33 -0
- package/locales/kn.json +1 -1
- package/locales/ko/LC_MESSAGES/volto.po +33 -0
- package/locales/ko.json +1 -1
- package/locales/lt/LC_MESSAGES/volto.po +33 -0
- package/locales/lt.json +1 -1
- package/locales/lv/LC_MESSAGES/volto.po +33 -0
- package/locales/lv.json +1 -1
- package/locales/mi/LC_MESSAGES/volto.po +33 -0
- package/locales/mi.json +1 -1
- package/locales/mk/LC_MESSAGES/volto.po +33 -0
- package/locales/mk.json +1 -1
- package/locales/my/LC_MESSAGES/volto.po +33 -0
- package/locales/my.json +1 -1
- package/locales/nb_NO/LC_MESSAGES/volto.po +33 -0
- package/locales/nb_NO.json +1 -1
- package/locales/nl/LC_MESSAGES/volto.po +33 -0
- package/locales/nl.json +1 -1
- package/locales/nn/LC_MESSAGES/volto.po +33 -0
- package/locales/nn.json +1 -1
- package/locales/pl/LC_MESSAGES/volto.po +33 -0
- package/locales/pl.json +1 -1
- package/locales/pt/LC_MESSAGES/volto.po +33 -0
- package/locales/pt.json +1 -1
- package/locales/pt_BR/LC_MESSAGES/volto.po +33 -0
- package/locales/pt_BR.json +1 -1
- package/locales/rm/LC_MESSAGES/volto.po +33 -0
- package/locales/rm.json +1 -1
- package/locales/ro/LC_MESSAGES/volto.po +33 -0
- package/locales/ro.json +1 -1
- package/locales/ru/LC_MESSAGES/volto.po +33 -0
- package/locales/ru.json +1 -1
- package/locales/sk/LC_MESSAGES/volto.po +33 -0
- package/locales/sk.json +1 -1
- package/locales/sl/LC_MESSAGES/volto.po +33 -0
- package/locales/sl.json +1 -1
- package/locales/sm/LC_MESSAGES/volto.po +33 -0
- package/locales/sm.json +1 -1
- package/locales/sq/LC_MESSAGES/volto.po +33 -0
- package/locales/sq.json +1 -1
- package/locales/sr/LC_MESSAGES/volto.po +33 -0
- package/locales/sr.json +1 -1
- package/locales/sr@cyrl/LC_MESSAGES/volto.po +33 -0
- package/locales/sr@cyrl.json +1 -1
- package/locales/sr@latn/LC_MESSAGES/volto.po +33 -0
- package/locales/sr@latn.json +1 -1
- package/locales/sv/LC_MESSAGES/volto.po +33 -0
- package/locales/sv.json +1 -1
- package/locales/ta/LC_MESSAGES/volto.po +33 -0
- package/locales/ta.json +1 -1
- package/locales/te/LC_MESSAGES/volto.po +33 -0
- package/locales/te.json +1 -1
- package/locales/th/LC_MESSAGES/volto.po +33 -0
- package/locales/th.json +1 -1
- package/locales/to/LC_MESSAGES/volto.po +33 -0
- package/locales/to.json +1 -1
- package/locales/tr/LC_MESSAGES/volto.po +33 -0
- package/locales/tr.json +1 -1
- package/locales/uk/LC_MESSAGES/volto.po +33 -0
- package/locales/uk.json +1 -1
- package/locales/vi/LC_MESSAGES/volto.po +33 -0
- package/locales/vi.json +1 -1
- package/locales/volto.pot +34 -1
- package/locales/zh_CN/LC_MESSAGES/volto.po +33 -0
- package/locales/zh_CN.json +1 -1
- package/locales/zh_Hant/LC_MESSAGES/volto.po +33 -0
- package/locales/zh_Hant.json +1 -1
- package/locales/zh_Hant_HK/LC_MESSAGES/volto.po +33 -0
- package/locales/zh_Hant_HK.json +1 -1
- package/package.json +3 -3
- package/src/components/manage/Contents/Contents.jsx +685 -665
- package/src/components/manage/Contents/DropZoneContent.jsx +338 -0
- package/src/components/manage/Sidebar/ObjectBrowser.jsx +3 -0
- package/src/components/manage/Sidebar/ObjectBrowserBody.jsx +13 -1
- package/src/components/manage/Widgets/ObjectBrowserWidget.jsx +5 -0
- package/src/components/manage/Widgets/QueryWidget.jsx +137 -9
- package/src/components/theme/MultilingualRedirector/MultilingualRedirector.jsx +2 -2
- package/src/helpers/AuthToken/AuthToken.js +1 -6
- package/src/reducers/querystring/querystring.js +8 -1
- package/theme/themes/pastanaga/extras/contents.less +63 -0
- package/theme/themes/pastanaga/extras/widgets.less +34 -0
- package/types/components/manage/Contents/DropZoneContent.d.ts +2 -0
- package/types/components/manage/Controlpanels/Relations/RelationsMatrix.d.ts +1 -1
- package/types/components/manage/Multilingual/ManageTranslations.d.ts +1 -1
- package/types/components/manage/Sidebar/ObjectBrowser.d.ts +1 -1
- package/types/components/manage/Widgets/ImageWidget.d.ts +1 -1
- package/types/components/manage/Widgets/InternalUrlWidget.d.ts +1 -1
- package/types/components/manage/Widgets/ObjectBrowserWidget.d.ts +2 -0
- package/types/components/manage/Widgets/QueryWidget.d.ts +5 -2
- package/types/components/manage/Widgets/UrlWidget.d.ts +1 -1
- package/types/components/manage/Widgets/index.d.ts +3 -3
|
@@ -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;
|
|
@@ -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 {
|
|
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 &&
|
|
@@ -82,6 +82,7 @@ export class ObjectBrowserWidgetComponent extends Component {
|
|
|
82
82
|
openObjectBrowser: PropTypes.func.isRequired,
|
|
83
83
|
allowExternals: PropTypes.bool,
|
|
84
84
|
placeholder: PropTypes.string,
|
|
85
|
+
onlyFolderishSelectable: PropTypes.bool,
|
|
85
86
|
};
|
|
86
87
|
|
|
87
88
|
/**
|
|
@@ -98,6 +99,7 @@ export class ObjectBrowserWidgetComponent extends Component {
|
|
|
98
99
|
return: 'multiple',
|
|
99
100
|
initialPath: '',
|
|
100
101
|
allowExternals: false,
|
|
102
|
+
onlyFolderishSelectable: false,
|
|
101
103
|
};
|
|
102
104
|
|
|
103
105
|
state = {
|
|
@@ -315,6 +317,9 @@ export class ObjectBrowserWidgetComponent extends Component {
|
|
|
315
317
|
maximumSelectionSize:
|
|
316
318
|
this.props.widgetOptions?.pattern_options?.maximumSelectionSize ||
|
|
317
319
|
this.props.maximumSelectionSize,
|
|
320
|
+
onlyFolderishSelectable:
|
|
321
|
+
this.props.widgetOptions?.pattern_options?.onlyFolderishSelectable ||
|
|
322
|
+
this.props.onlyFolderishSelectable,
|
|
318
323
|
});
|
|
319
324
|
};
|
|
320
325
|
|
|
@@ -15,8 +15,12 @@ import groupBy from 'lodash/groupBy';
|
|
|
15
15
|
import isEmpty from 'lodash/isEmpty';
|
|
16
16
|
import map from 'lodash/map';
|
|
17
17
|
import { defineMessages, injectIntl } from 'react-intl';
|
|
18
|
+
import { withRouter } from 'react-router';
|
|
18
19
|
import { getQuerystring } from '@plone/volto/actions/querystring/querystring';
|
|
20
|
+
import { getQueryStringResults } from '@plone/volto/actions/querystringsearch/querystringsearch';
|
|
19
21
|
import Icon from '@plone/volto/components/theme/Icon/Icon';
|
|
22
|
+
import ObjectBrowserWidget from '@plone/volto/components/manage/Widgets/ObjectBrowserWidget';
|
|
23
|
+
import NumberWidget from '@plone/volto/components/manage/Widgets/NumberWidget';
|
|
20
24
|
import { injectLazyLibs } from '@plone/volto/helpers/Loadable/Loadable';
|
|
21
25
|
import cx from 'classnames';
|
|
22
26
|
import config from '@plone/volto/registry';
|
|
@@ -43,8 +47,27 @@ const messages = defineMessages({
|
|
|
43
47
|
id: 'querystring-widget-select',
|
|
44
48
|
defaultMessage: 'Select…',
|
|
45
49
|
},
|
|
50
|
+
currentPath: {
|
|
51
|
+
id: 'query-widget-currentPath',
|
|
52
|
+
defaultMessage: 'Current path (./)',
|
|
53
|
+
},
|
|
54
|
+
parentPath: {
|
|
55
|
+
id: 'query-widget-parentPath',
|
|
56
|
+
defaultMessage: 'Parent path (../)',
|
|
57
|
+
},
|
|
46
58
|
});
|
|
47
59
|
|
|
60
|
+
const parseUidDepth = (val) => {
|
|
61
|
+
if (typeof val !== 'string') return { uid: '', depth: 1 };
|
|
62
|
+
const lastSep = String(val).lastIndexOf('::');
|
|
63
|
+
if (lastSep !== -1) {
|
|
64
|
+
const uid = val.substring(0, lastSep);
|
|
65
|
+
const parsed = parseInt(val.substring(lastSep + 2), 10);
|
|
66
|
+
return { uid, depth: Number.isNaN(parsed) ? 1 : parsed };
|
|
67
|
+
}
|
|
68
|
+
return { uid: val, depth: 1 };
|
|
69
|
+
};
|
|
70
|
+
|
|
48
71
|
/**
|
|
49
72
|
* Widget for a querystring value, to define a catalog search criteria.
|
|
50
73
|
*/
|
|
@@ -97,6 +120,7 @@ export class QuerystringWidgetComponent extends Component {
|
|
|
97
120
|
};
|
|
98
121
|
this.onChangeValue = this.onChangeValue.bind(this);
|
|
99
122
|
this.getWidget = this.getWidget.bind(this);
|
|
123
|
+
this.loadReferenceWidgetItem = this.loadReferenceWidgetItem.bind(this);
|
|
100
124
|
}
|
|
101
125
|
|
|
102
126
|
/**
|
|
@@ -111,6 +135,27 @@ export class QuerystringWidgetComponent extends Component {
|
|
|
111
135
|
this.props.getQuerystring();
|
|
112
136
|
}
|
|
113
137
|
|
|
138
|
+
loadReferenceWidgetItem(v) {
|
|
139
|
+
const loading =
|
|
140
|
+
this.props.reference[`${v}_query_reference`]?.loading ?? false;
|
|
141
|
+
if (!loading && v?.length > 0) {
|
|
142
|
+
this.props.getQueryStringResults(
|
|
143
|
+
'/',
|
|
144
|
+
{
|
|
145
|
+
b_size: 1,
|
|
146
|
+
query: [
|
|
147
|
+
{
|
|
148
|
+
i: 'path',
|
|
149
|
+
o: 'plone.app.querystring.operation.string.absolutePath',
|
|
150
|
+
v: v + '::0',
|
|
151
|
+
},
|
|
152
|
+
],
|
|
153
|
+
},
|
|
154
|
+
v + '_query_reference',
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
114
159
|
/**
|
|
115
160
|
* Get correct widget
|
|
116
161
|
* @method getWidget
|
|
@@ -118,15 +163,16 @@ export class QuerystringWidgetComponent extends Component {
|
|
|
118
163
|
* @param {number} index Row index.
|
|
119
164
|
* @returns {Object} Widget.
|
|
120
165
|
*/
|
|
121
|
-
getWidget(row, index, Select) {
|
|
166
|
+
getWidget(row, index, Select, intl) {
|
|
122
167
|
const props = {
|
|
123
168
|
fluid: true,
|
|
124
169
|
value: row.v,
|
|
125
170
|
onChange: (data) => this.onChangeValue(index, data.target.value),
|
|
126
171
|
};
|
|
127
172
|
const values = this.props.indexes[row.i].values;
|
|
173
|
+
const operator = this.props.indexes[row.i].operators[row.o];
|
|
128
174
|
|
|
129
|
-
switch (
|
|
175
|
+
switch (operator.widget) {
|
|
130
176
|
case null:
|
|
131
177
|
return <span />;
|
|
132
178
|
case 'DateWidget':
|
|
@@ -217,13 +263,93 @@ export class QuerystringWidgetComponent extends Component {
|
|
|
217
263
|
</Form.Field>
|
|
218
264
|
);
|
|
219
265
|
case 'ReferenceWidget':
|
|
266
|
+
const { uid: uidValue, depth: depthValue } = parseUidDepth(props.value);
|
|
267
|
+
if (!this.props.reference[`${uidValue}_query_reference`]) {
|
|
268
|
+
this.loadReferenceWidgetItem(uidValue);
|
|
269
|
+
}
|
|
270
|
+
const referenceItem = this.props.reference[
|
|
271
|
+
`${uidValue}_query_reference`
|
|
272
|
+
]
|
|
273
|
+
? this.props.reference[`${uidValue}_query_reference`].items[0]
|
|
274
|
+
: null;
|
|
275
|
+
return (
|
|
276
|
+
<div className="location-object-browser">
|
|
277
|
+
<Form.Field className="object-browser-field">
|
|
278
|
+
<ObjectBrowserWidget
|
|
279
|
+
id={`query-reference-widget-${index}`}
|
|
280
|
+
mode="link"
|
|
281
|
+
onChange={(id, data) => {
|
|
282
|
+
const itemSelected = data.length > 0 ? data[0] : {};
|
|
283
|
+
const uid = itemSelected.UID ?? '';
|
|
284
|
+
this.onChangeValue(index, uid ? `${uid}::${depthValue}` : '');
|
|
285
|
+
this.loadReferenceWidgetItem(uid);
|
|
286
|
+
}}
|
|
287
|
+
value={uidValue && this.props.reference ? [referenceItem] : []}
|
|
288
|
+
wrapped={false}
|
|
289
|
+
onlyFolderishSelectable={true}
|
|
290
|
+
allowExternals={true}
|
|
291
|
+
/>
|
|
292
|
+
</Form.Field>
|
|
293
|
+
|
|
294
|
+
{uidValue && (
|
|
295
|
+
<Form.Field className="reference-widget-depth">
|
|
296
|
+
<NumberWidget
|
|
297
|
+
title={intl.formatMessage({
|
|
298
|
+
id: 'Depth',
|
|
299
|
+
defaultMessage: 'Depth',
|
|
300
|
+
})}
|
|
301
|
+
min={1}
|
|
302
|
+
step={1}
|
|
303
|
+
value={depthValue}
|
|
304
|
+
onChange={(id, value) => {
|
|
305
|
+
const newDepth = parseInt(value, 10) || 1;
|
|
306
|
+
const curUid = uidValue || '';
|
|
307
|
+
this.onChangeValue(index, `${curUid}::${newDepth}`);
|
|
308
|
+
}}
|
|
309
|
+
/>
|
|
310
|
+
</Form.Field>
|
|
311
|
+
)}
|
|
312
|
+
</div>
|
|
313
|
+
);
|
|
314
|
+
case 'RelativePathWidget':
|
|
315
|
+
const relativePathOptions = [
|
|
316
|
+
{
|
|
317
|
+
label: intl.formatMessage(messages.currentPath),
|
|
318
|
+
value: './',
|
|
319
|
+
},
|
|
320
|
+
{
|
|
321
|
+
label: intl.formatMessage(messages.parentPath),
|
|
322
|
+
value: '../',
|
|
323
|
+
},
|
|
324
|
+
];
|
|
325
|
+
return (
|
|
326
|
+
<Form.Field style={{ flex: '1 0 auto', maxWidth: '92%' }}>
|
|
327
|
+
<Select
|
|
328
|
+
{...props}
|
|
329
|
+
className="react-select-container"
|
|
330
|
+
classNamePrefix="react-select"
|
|
331
|
+
options={relativePathOptions}
|
|
332
|
+
styles={customSelectStyles}
|
|
333
|
+
placeholder={this.props.intl.formatMessage(messages.select)}
|
|
334
|
+
theme={selectTheme}
|
|
335
|
+
components={{ DropdownIndicator, Option }}
|
|
336
|
+
onChange={(data) => {
|
|
337
|
+
this.onChangeValue(index, data.value);
|
|
338
|
+
}}
|
|
339
|
+
isMulti={false}
|
|
340
|
+
value={
|
|
341
|
+
relativePathOptions.filter((p) => p.value === props.value)?.[0]
|
|
342
|
+
}
|
|
343
|
+
/>
|
|
344
|
+
</Form.Field>
|
|
345
|
+
);
|
|
220
346
|
default:
|
|
221
347
|
// if (row.o === 'plone.app.querystring.operation.string.relativePath') {
|
|
222
348
|
// props.onChange = data => this.onChangeValue(index, data.target.value);
|
|
223
349
|
// }
|
|
224
350
|
return (
|
|
225
351
|
<Form.Field style={{ flex: '1 0 auto' }}>
|
|
226
|
-
<Input {...props} />
|
|
352
|
+
<Input {...props} description={operator.description} />
|
|
227
353
|
</Form.Field>
|
|
228
354
|
);
|
|
229
355
|
}
|
|
@@ -334,7 +460,7 @@ export class QuerystringWidgetComponent extends Component {
|
|
|
334
460
|
value: row.i,
|
|
335
461
|
label: indexes[row.i]?.title,
|
|
336
462
|
}}
|
|
337
|
-
onChange={(data) =>
|
|
463
|
+
onChange={(data) => {
|
|
338
464
|
onChange(
|
|
339
465
|
id,
|
|
340
466
|
map(value, (curRow, curIndex) =>
|
|
@@ -346,8 +472,8 @@ export class QuerystringWidgetComponent extends Component {
|
|
|
346
472
|
}
|
|
347
473
|
: curRow,
|
|
348
474
|
),
|
|
349
|
-
)
|
|
350
|
-
}
|
|
475
|
+
);
|
|
476
|
+
}}
|
|
351
477
|
/>
|
|
352
478
|
</Form.Field>
|
|
353
479
|
<Form.Field style={{ flex: '1 0 auto' }}>
|
|
@@ -408,7 +534,7 @@ export class QuerystringWidgetComponent extends Component {
|
|
|
408
534
|
</Button>
|
|
409
535
|
)}
|
|
410
536
|
</div>
|
|
411
|
-
{this.getWidget(row, index, Select)}
|
|
537
|
+
{this.getWidget(row, index, Select, intl)}
|
|
412
538
|
{this.props.indexes[row.i].operators[row.o].widget && (
|
|
413
539
|
<Button
|
|
414
540
|
onClick={(event) => {
|
|
@@ -500,10 +626,12 @@ export class QuerystringWidgetComponent extends Component {
|
|
|
500
626
|
export default compose(
|
|
501
627
|
injectIntl,
|
|
502
628
|
injectLazyLibs(['reactSelect']),
|
|
629
|
+
withRouter,
|
|
503
630
|
connect(
|
|
504
|
-
(state) => ({
|
|
631
|
+
(state, props) => ({
|
|
505
632
|
indexes: state.querystring.indexes,
|
|
633
|
+
reference: state.querystringsearch.subrequests,
|
|
506
634
|
}),
|
|
507
|
-
{ getQuerystring },
|
|
635
|
+
{ getQuerystring, getQueryStringResults },
|
|
508
636
|
),
|
|
509
637
|
)(QuerystringWidgetComponent);
|
|
@@ -4,7 +4,7 @@ import { useDispatch } from 'react-redux';
|
|
|
4
4
|
import { useCookies } from 'react-cookie';
|
|
5
5
|
import config from '@plone/volto/registry';
|
|
6
6
|
import { changeLanguage } from '@plone/volto/actions/language/language';
|
|
7
|
-
import { toGettextLang } from '@plone/volto/helpers/Utils/Utils';
|
|
7
|
+
import { toGettextLang, toBackendLang } from '@plone/volto/helpers/Utils/Utils';
|
|
8
8
|
|
|
9
9
|
const MultilingualRedirector = (props) => {
|
|
10
10
|
const { settings } = config;
|
|
@@ -38,7 +38,7 @@ const MultilingualRedirector = (props) => {
|
|
|
38
38
|
}, [pathname, dispatch, redirectToLanguage, settings.isMultilingual]);
|
|
39
39
|
|
|
40
40
|
return pathname === '/' && settings.isMultilingual ? (
|
|
41
|
-
<Redirect to={`/${redirectToLanguage}`} />
|
|
41
|
+
<Redirect to={`/${toBackendLang(redirectToLanguage)}`} />
|
|
42
42
|
) : (
|
|
43
43
|
<>{children}</>
|
|
44
44
|
);
|