@eeacms/volto-eea-website-theme 0.5.2 → 0.6.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.
- package/CHANGELOG.md +31 -0
- package/package.json +1 -1
- package/src/components/manage/Blocks/Title/Edit.jsx +2 -3
- package/src/components/manage/Blocks/Title/View.jsx +1 -1
- package/src/components/manage/Blocks/Title/schema.js +6 -6
- package/src/components/theme/Banner/Banner.jsx +17 -8
- package/src/components/theme/Banner/View.jsx +26 -31
- 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
- package/src/customizations/volto/components/theme/Comments/Comments.jsx +7 -6
- package/src/index.js +1 -0
- package/theme/theme.config +2 -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);
|
@@ -18,8 +18,8 @@ import { Portal } from 'react-portal';
|
|
18
18
|
import { connect } from 'react-redux';
|
19
19
|
import { compose } from 'redux';
|
20
20
|
import { Button, Comment, Container, Icon } from 'semantic-ui-react';
|
21
|
-
import {
|
22
|
-
|
21
|
+
import { formatRelativeDate } from '@plone/volto/helpers/Utils/Date';
|
22
|
+
import config from '@plone/volto/registry';
|
23
23
|
|
24
24
|
const messages = defineMessages({
|
25
25
|
comment: {
|
@@ -297,7 +297,6 @@ class Comments extends Component {
|
|
297
297
|
*/
|
298
298
|
render() {
|
299
299
|
const { items } = this.props;
|
300
|
-
const moment = this.props.moment.default;
|
301
300
|
const { collapsedComments } = this.state;
|
302
301
|
// object with comment ids, to easily verify if any comment has children
|
303
302
|
const allCommentsWithCildren = this.addRepliesAsChildrenToComments(items);
|
@@ -318,8 +317,11 @@ class Comments extends Component {
|
|
318
317
|
<Comment.Metadata>
|
319
318
|
<span>
|
320
319
|
{' '}
|
321
|
-
<span title={
|
322
|
-
{
|
320
|
+
<span title={comment.creation_date}>
|
321
|
+
{formatRelativeDate({
|
322
|
+
date: comment.creation_date,
|
323
|
+
locale: config.settings.dateLocale || 'en-gb',
|
324
|
+
})}
|
323
325
|
</span>
|
324
326
|
</span>
|
325
327
|
</Comment.Metadata>
|
@@ -472,7 +474,6 @@ class Comments extends Component {
|
|
472
474
|
|
473
475
|
export default compose(
|
474
476
|
injectIntl,
|
475
|
-
injectLazyLibs(['moment']),
|
476
477
|
connect(
|
477
478
|
(state) => ({
|
478
479
|
items: state.comments.items,
|
package/src/index.js
CHANGED
@@ -14,6 +14,7 @@ const applyConfig = (config) => {
|
|
14
14
|
if (config.blocks.blocksConfig.accordion) {
|
15
15
|
config.blocks.blocksConfig.accordion.semanticIcon = 'ri-arrow-down-s-line';
|
16
16
|
}
|
17
|
+
|
17
18
|
// apply inPage navigation
|
18
19
|
config.settings.appExtras = [
|
19
20
|
...(config.settings.appExtras || []),
|