@eeacms/volto-eea-website-theme 0.5.2 → 0.5.3
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 +11 -0
- package/package.json +1 -1
- package/src/components/manage/Blocks/Title/Edit.jsx +2 -3
- package/src/components/manage/Blocks/Title/schema.js +6 -6
- package/src/components/theme/Banner/Banner.jsx +3 -0
- package/src/components/theme/Banner/View.jsx +24 -28
- package/src/components/theme/DraftBackground/draft.css +1 -1
- package/src/customizations/volto/components/manage/Form/Form.jsx +775 -0
- package/src/customizations/volto/components/manage/Form/ModalForm.jsx +321 -0
- package/src/customizations/volto/components/manage/Sharing/Sharing.jsx +478 -0
- package/src/customizations/volto/components/manage/Widgets/ObjectBrowserWidget.jsx +426 -0
@@ -0,0 +1,426 @@
|
|
1
|
+
/**
|
2
|
+
* ObjectBrowserWidget component.
|
3
|
+
* @module components/manage/Widgets/ObjectBrowserWidget
|
4
|
+
*/
|
5
|
+
|
6
|
+
import React, { Component } from 'react';
|
7
|
+
import PropTypes from 'prop-types';
|
8
|
+
import { compose } from 'redux';
|
9
|
+
import { compact, isArray, isEmpty, remove } from 'lodash';
|
10
|
+
import { connect } from 'react-redux';
|
11
|
+
import { Label, Popup, Button } from 'semantic-ui-react';
|
12
|
+
import {
|
13
|
+
flattenToAppURL,
|
14
|
+
isInternalURL,
|
15
|
+
isUrl,
|
16
|
+
normalizeUrl,
|
17
|
+
removeProtocol,
|
18
|
+
} from '@plone/volto/helpers/Url/Url';
|
19
|
+
import { searchContent } from '@plone/volto/actions/search/search';
|
20
|
+
import withObjectBrowser from '@plone/volto/components/manage/Sidebar/ObjectBrowser';
|
21
|
+
import { defineMessages, injectIntl } from 'react-intl';
|
22
|
+
import Icon from '@plone/volto/components/theme/Icon/Icon';
|
23
|
+
import FormFieldWrapper from '@plone/volto/components/manage/Widgets/FormFieldWrapper';
|
24
|
+
|
25
|
+
import navTreeSVG from '@plone/volto/icons/nav.svg';
|
26
|
+
import clearSVG from '@plone/volto/icons/clear.svg';
|
27
|
+
import homeSVG from '@plone/volto/icons/home.svg';
|
28
|
+
import aheadSVG from '@plone/volto/icons/ahead.svg';
|
29
|
+
import blankSVG from '@plone/volto/icons/blank.svg';
|
30
|
+
import { withRouter } from 'react-router';
|
31
|
+
|
32
|
+
const messages = defineMessages({
|
33
|
+
placeholder: {
|
34
|
+
id: 'No items selected',
|
35
|
+
defaultMessage: 'No items selected',
|
36
|
+
},
|
37
|
+
edit: {
|
38
|
+
id: 'Edit',
|
39
|
+
defaultMessage: 'Edit',
|
40
|
+
},
|
41
|
+
delete: {
|
42
|
+
id: 'Delete',
|
43
|
+
defaultMessage: 'Delete',
|
44
|
+
},
|
45
|
+
openObjectBrowser: {
|
46
|
+
id: 'Open object browser',
|
47
|
+
defaultMessage: 'Open object browser',
|
48
|
+
},
|
49
|
+
});
|
50
|
+
|
51
|
+
/**
|
52
|
+
* ObjectBrowserWidget component class.
|
53
|
+
* @class ObjectBrowserWidget
|
54
|
+
* @extends Component
|
55
|
+
*/
|
56
|
+
export class ObjectBrowserWidgetComponent extends Component {
|
57
|
+
/**
|
58
|
+
* Property types.
|
59
|
+
* @property {Object} propTypes Property types.
|
60
|
+
* @static
|
61
|
+
*/
|
62
|
+
static propTypes = {
|
63
|
+
id: PropTypes.string.isRequired,
|
64
|
+
title: PropTypes.string.isRequired,
|
65
|
+
description: PropTypes.string,
|
66
|
+
mode: PropTypes.string, // link, image, multiple
|
67
|
+
return: PropTypes.string, // single, multiple
|
68
|
+
required: PropTypes.bool,
|
69
|
+
error: PropTypes.arrayOf(PropTypes.string),
|
70
|
+
value: PropTypes.oneOfType([
|
71
|
+
PropTypes.arrayOf(PropTypes.object),
|
72
|
+
PropTypes.object,
|
73
|
+
]),
|
74
|
+
onChange: PropTypes.func.isRequired,
|
75
|
+
openObjectBrowser: PropTypes.func.isRequired,
|
76
|
+
allowExternals: PropTypes.bool,
|
77
|
+
};
|
78
|
+
|
79
|
+
/**
|
80
|
+
* Default properties
|
81
|
+
* @property {Object} defaultProps Default properties.
|
82
|
+
* @static
|
83
|
+
*/
|
84
|
+
static defaultProps = {
|
85
|
+
description: null,
|
86
|
+
required: false,
|
87
|
+
error: [],
|
88
|
+
value: [],
|
89
|
+
mode: 'multiple',
|
90
|
+
return: 'multiple',
|
91
|
+
allowExternals: false,
|
92
|
+
};
|
93
|
+
|
94
|
+
state = {
|
95
|
+
manualLinkInput: '',
|
96
|
+
validURL: false,
|
97
|
+
};
|
98
|
+
|
99
|
+
constructor(props) {
|
100
|
+
super(props);
|
101
|
+
this.selectedItemsRef = React.createRef();
|
102
|
+
this.placeholderRef = React.createRef();
|
103
|
+
}
|
104
|
+
renderLabel(item) {
|
105
|
+
const href = item['@id'];
|
106
|
+
return (
|
107
|
+
<Popup
|
108
|
+
key={flattenToAppURL(href)}
|
109
|
+
content={
|
110
|
+
<div style={{ display: 'flex' }}>
|
111
|
+
{isInternalURL(href) ? (
|
112
|
+
<Icon name={homeSVG} size="18px" />
|
113
|
+
) : (
|
114
|
+
<Icon name={blankSVG} size="18px" />
|
115
|
+
)}
|
116
|
+
|
117
|
+
{flattenToAppURL(href)}
|
118
|
+
</div>
|
119
|
+
}
|
120
|
+
trigger={
|
121
|
+
<Label>
|
122
|
+
<div className="item-title">{item.title}</div>
|
123
|
+
<div>
|
124
|
+
{this.props.mode === 'multiple' && (
|
125
|
+
<Icon
|
126
|
+
name={clearSVG}
|
127
|
+
size="12px"
|
128
|
+
className="right"
|
129
|
+
onClick={(event) => {
|
130
|
+
event.preventDefault();
|
131
|
+
this.removeItem(item);
|
132
|
+
}}
|
133
|
+
/>
|
134
|
+
)}
|
135
|
+
</div>
|
136
|
+
</Label>
|
137
|
+
}
|
138
|
+
/>
|
139
|
+
);
|
140
|
+
}
|
141
|
+
|
142
|
+
removeItem = (item) => {
|
143
|
+
let value = [...this.props.value];
|
144
|
+
remove(value, function (_item) {
|
145
|
+
return _item['@id'] === item['@id'];
|
146
|
+
});
|
147
|
+
this.props.onChange(this.props.id, value);
|
148
|
+
};
|
149
|
+
|
150
|
+
onChange = (item) => {
|
151
|
+
let value = this.props.mode === 'multiple' ? [...this.props.value] : [];
|
152
|
+
value = value.filter((item) => item != null);
|
153
|
+
const maxSize =
|
154
|
+
this.props.widgetOptions?.pattern_options?.maximumSelectionSize || -1;
|
155
|
+
if (maxSize === 1 && value.length === 1) {
|
156
|
+
value = []; //enable replace of selected item with another value, if maxsize is 1
|
157
|
+
}
|
158
|
+
let exists = false;
|
159
|
+
let index = -1;
|
160
|
+
value.forEach((_item, _index) => {
|
161
|
+
if (flattenToAppURL(_item['@id']) === flattenToAppURL(item['@id'])) {
|
162
|
+
exists = true;
|
163
|
+
index = _index;
|
164
|
+
}
|
165
|
+
});
|
166
|
+
//find(value, {
|
167
|
+
// '@id': flattenToAppURL(item['@id']),
|
168
|
+
// });
|
169
|
+
if (!exists) {
|
170
|
+
// add item
|
171
|
+
// Check if we want to filter the attributes of the selected item
|
172
|
+
let resultantItem = item;
|
173
|
+
if (this.props.selectedItemAttrs) {
|
174
|
+
const allowedItemKeys = [
|
175
|
+
...this.props.selectedItemAttrs,
|
176
|
+
// Add the required attributes for the widget to work
|
177
|
+
'@id',
|
178
|
+
'title',
|
179
|
+
];
|
180
|
+
resultantItem = Object.keys(item)
|
181
|
+
.filter((key) => allowedItemKeys.includes(key))
|
182
|
+
.reduce((obj, key) => {
|
183
|
+
obj[key] = item[key];
|
184
|
+
return obj;
|
185
|
+
}, {});
|
186
|
+
}
|
187
|
+
// Add required @id field, just in case
|
188
|
+
resultantItem = { ...resultantItem, '@id': item['@id'] };
|
189
|
+
value.push(resultantItem);
|
190
|
+
if (this.props.return === 'single') {
|
191
|
+
this.props.onChange(this.props.id, value[0]);
|
192
|
+
} else {
|
193
|
+
this.props.onChange(this.props.id, value);
|
194
|
+
}
|
195
|
+
} else {
|
196
|
+
//remove item
|
197
|
+
value.splice(index, 1);
|
198
|
+
this.props.onChange(this.props.id, value);
|
199
|
+
}
|
200
|
+
};
|
201
|
+
|
202
|
+
onManualLinkInput = (e) => {
|
203
|
+
this.setState({ manualLinkInput: e.target.value });
|
204
|
+
if (this.validateManualLink(e.target.value)) {
|
205
|
+
this.setState({ validURL: true });
|
206
|
+
} else {
|
207
|
+
this.setState({ validURL: false });
|
208
|
+
}
|
209
|
+
};
|
210
|
+
|
211
|
+
validateManualLink = (url) => {
|
212
|
+
if (this.props.allowExternals) {
|
213
|
+
return isUrl(url);
|
214
|
+
} else {
|
215
|
+
return isInternalURL(url);
|
216
|
+
}
|
217
|
+
};
|
218
|
+
|
219
|
+
onSubmitManualLink = () => {
|
220
|
+
if (this.validateManualLink(this.state.manualLinkInput)) {
|
221
|
+
if (isInternalURL(this.state.manualLinkInput)) {
|
222
|
+
const link = this.state.manualLinkInput;
|
223
|
+
// convert it into an internal on if possible
|
224
|
+
this.props
|
225
|
+
.searchContent(
|
226
|
+
'/',
|
227
|
+
{
|
228
|
+
'path.query': flattenToAppURL(this.state.manualLinkInput),
|
229
|
+
'path.depth': '0',
|
230
|
+
sort_on: 'getObjPositionInParent',
|
231
|
+
metadata_fields: '_all',
|
232
|
+
b_size: 1000,
|
233
|
+
},
|
234
|
+
`${this.props.block}-${this.props.mode}`,
|
235
|
+
)
|
236
|
+
.then((resp) => {
|
237
|
+
if (resp.items?.length > 0) {
|
238
|
+
this.onChange(resp.items[0]);
|
239
|
+
} else {
|
240
|
+
this.props.onChange(this.props.id, [
|
241
|
+
{
|
242
|
+
'@id': normalizeUrl(link),
|
243
|
+
title: removeProtocol(link),
|
244
|
+
},
|
245
|
+
]);
|
246
|
+
}
|
247
|
+
});
|
248
|
+
} else {
|
249
|
+
this.props.onChange(this.props.id, [
|
250
|
+
{
|
251
|
+
'@id': normalizeUrl(this.state.manualLinkInput),
|
252
|
+
title: removeProtocol(this.state.manualLinkInput),
|
253
|
+
},
|
254
|
+
]);
|
255
|
+
}
|
256
|
+
this.setState({ validURL: true, manualLinkInput: '' });
|
257
|
+
}
|
258
|
+
};
|
259
|
+
|
260
|
+
onKeyDownManualLink = (e) => {
|
261
|
+
if (e.key === 'Enter') {
|
262
|
+
e.preventDefault();
|
263
|
+
e.stopPropagation();
|
264
|
+
this.onSubmitManualLink();
|
265
|
+
} else if (e.key === 'Escape') {
|
266
|
+
e.preventDefault();
|
267
|
+
e.stopPropagation();
|
268
|
+
// TODO: Do something on ESC key
|
269
|
+
}
|
270
|
+
};
|
271
|
+
|
272
|
+
showObjectBrowser = (ev) => {
|
273
|
+
ev.preventDefault();
|
274
|
+
this.props.openObjectBrowser({
|
275
|
+
mode: this.props.mode,
|
276
|
+
currentPath: this.props.location.pathname,
|
277
|
+
propDataName: 'value',
|
278
|
+
onSelectItem: (url, item) => {
|
279
|
+
this.onChange(item);
|
280
|
+
},
|
281
|
+
selectableTypes: this.props.widgetOptions?.pattern_options
|
282
|
+
?.selectableTypes,
|
283
|
+
maximumSelectionSize: this.props.widgetOptions?.pattern_options
|
284
|
+
?.maximumSelectionSize,
|
285
|
+
});
|
286
|
+
};
|
287
|
+
|
288
|
+
handleSelectedItemsRefClick = (e) => {
|
289
|
+
if (this.props.isDisabled) {
|
290
|
+
return;
|
291
|
+
}
|
292
|
+
|
293
|
+
if (
|
294
|
+
e.target.contains(this.selectedItemsRef.current) ||
|
295
|
+
e.target.contains(this.placeholderRef.current)
|
296
|
+
) {
|
297
|
+
this.showObjectBrowser(e);
|
298
|
+
}
|
299
|
+
};
|
300
|
+
|
301
|
+
/**
|
302
|
+
* Render method.
|
303
|
+
* @method render
|
304
|
+
* @returns {string} Markup for the component.
|
305
|
+
*/
|
306
|
+
render() {
|
307
|
+
const {
|
308
|
+
id,
|
309
|
+
description,
|
310
|
+
fieldSet,
|
311
|
+
value,
|
312
|
+
mode,
|
313
|
+
onChange,
|
314
|
+
isDisabled,
|
315
|
+
} = this.props;
|
316
|
+
|
317
|
+
let items = compact(!isArray(value) && value ? [value] : value || []);
|
318
|
+
|
319
|
+
let icon =
|
320
|
+
mode === 'multiple' || items.length === 0 ? navTreeSVG : clearSVG;
|
321
|
+
let iconAction =
|
322
|
+
mode === 'multiple' || items.length === 0
|
323
|
+
? this.showObjectBrowser
|
324
|
+
: (e) => {
|
325
|
+
e.preventDefault();
|
326
|
+
onChange(id, this.props.return === 'single' ? null : []);
|
327
|
+
};
|
328
|
+
|
329
|
+
return (
|
330
|
+
<FormFieldWrapper
|
331
|
+
{...this.props}
|
332
|
+
className={description ? 'help text' : 'text'}
|
333
|
+
>
|
334
|
+
<div
|
335
|
+
className="objectbrowser-field"
|
336
|
+
aria-labelledby={`fieldset-${
|
337
|
+
fieldSet || 'default'
|
338
|
+
}-field-label-${id}`}
|
339
|
+
>
|
340
|
+
<div
|
341
|
+
className="selected-values"
|
342
|
+
onClick={this.handleSelectedItemsRefClick}
|
343
|
+
onKeyDown={this.handleSelectedItemsRefClick}
|
344
|
+
role="searchbox"
|
345
|
+
tabIndex={0}
|
346
|
+
ref={this.selectedItemsRef}
|
347
|
+
>
|
348
|
+
{items.map((item) => this.renderLabel(item))}
|
349
|
+
|
350
|
+
{items.length === 0 && this.props.mode === 'multiple' && (
|
351
|
+
<div className="placeholder" ref={this.placeholderRef}>
|
352
|
+
{this.props.intl.formatMessage(messages.placeholder)}
|
353
|
+
</div>
|
354
|
+
)}
|
355
|
+
{this.props.allowExternals &&
|
356
|
+
items.length === 0 &&
|
357
|
+
this.props.mode !== 'multiple' && (
|
358
|
+
<input
|
359
|
+
onKeyDown={this.onKeyDownManualLink}
|
360
|
+
onChange={this.onManualLinkInput}
|
361
|
+
value={this.state.manualLinkInput}
|
362
|
+
placeholder={this.props.intl.formatMessage(
|
363
|
+
messages.placeholder,
|
364
|
+
)}
|
365
|
+
/>
|
366
|
+
)}
|
367
|
+
</div>
|
368
|
+
{this.state.manualLinkInput && isEmpty(items) && (
|
369
|
+
<Button.Group>
|
370
|
+
<Button
|
371
|
+
basic
|
372
|
+
icon
|
373
|
+
className="cancel"
|
374
|
+
onClick={(e) => {
|
375
|
+
e.stopPropagation();
|
376
|
+
this.setState({ manualLinkInput: '' });
|
377
|
+
}}
|
378
|
+
>
|
379
|
+
<Icon name={clearSVG} size="18px" color="#e40166" />
|
380
|
+
</Button>
|
381
|
+
<Button
|
382
|
+
basic
|
383
|
+
icon
|
384
|
+
primary
|
385
|
+
disabled={!this.state.validURL}
|
386
|
+
onClick={(e) => {
|
387
|
+
e.stopPropagation();
|
388
|
+
this.onSubmitManualLink();
|
389
|
+
}}
|
390
|
+
>
|
391
|
+
<Icon name={aheadSVG} size="18px" />
|
392
|
+
</Button>
|
393
|
+
</Button.Group>
|
394
|
+
)}
|
395
|
+
{!this.state.manualLinkInput && (
|
396
|
+
<Button
|
397
|
+
aria-label={this.props.intl.formatMessage(
|
398
|
+
messages.openObjectBrowser,
|
399
|
+
)}
|
400
|
+
onClick={iconAction}
|
401
|
+
className="action"
|
402
|
+
disabled={isDisabled}
|
403
|
+
>
|
404
|
+
<Icon name={icon} size="18px" />
|
405
|
+
</Button>
|
406
|
+
)}
|
407
|
+
</div>
|
408
|
+
</FormFieldWrapper>
|
409
|
+
);
|
410
|
+
}
|
411
|
+
}
|
412
|
+
|
413
|
+
const ObjectBrowserWidgetMode = (mode) =>
|
414
|
+
compose(
|
415
|
+
injectIntl,
|
416
|
+
withObjectBrowser,
|
417
|
+
withRouter,
|
418
|
+
connect(null, { searchContent }),
|
419
|
+
)((props) => <ObjectBrowserWidgetComponent {...props} mode={mode} />);
|
420
|
+
export { ObjectBrowserWidgetMode };
|
421
|
+
export default compose(
|
422
|
+
injectIntl,
|
423
|
+
withObjectBrowser,
|
424
|
+
withRouter,
|
425
|
+
connect(null, { searchContent }),
|
426
|
+
)(ObjectBrowserWidgetComponent);
|