@eeacms/volto-cca-policy 0.1.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/.coverage.babel.config.js +9 -0
- package/.i18n.babel.config.js +1 -0
- package/.project.eslintrc.js +48 -0
- package/.release-it.json +17 -0
- package/CHANGELOG.md +30 -0
- package/DEVELOP.md +52 -0
- package/LICENSE.md +9 -0
- package/README.md +85 -0
- package/RELEASE.md +74 -0
- package/babel.config.js +17 -0
- package/bootstrap +41 -0
- package/cypress.config.js +26 -0
- package/jest-addon.config.js +36 -0
- package/locales/volto.pot +0 -0
- package/package.json +51 -0
- package/src/components/index.js +1 -0
- package/src/components/manage/Blocks/ContextNavigation/ContextNavigationEdit.jsx +31 -0
- package/src/components/manage/Blocks/ContextNavigation/ContextNavigationView.jsx +17 -0
- package/src/components/manage/Blocks/ContextNavigation/index.js +26 -0
- package/src/components/manage/Blocks/ContextNavigation/schema.js +81 -0
- package/src/components/manage/Blocks/LayoutSettings/LayoutSettingsEdit.jsx +32 -0
- package/src/components/manage/Blocks/LayoutSettings/LayoutSettingsView.jsx +15 -0
- package/src/components/manage/Blocks/LayoutSettings/edit.less +4 -0
- package/src/components/manage/Blocks/LayoutSettings/index.js +24 -0
- package/src/components/manage/Blocks/LayoutSettings/schema.js +32 -0
- package/src/components/manage/Blocks/Title/Edit.jsx +226 -0
- package/src/components/manage/Blocks/Title/View.jsx +35 -0
- package/src/components/manage/Blocks/Title/index.js +13 -0
- package/src/components/manage/Blocks/Title/schema.js +80 -0
- package/src/components/manage/Blocks/schema-utils.js +16 -0
- package/src/components/manage/Blocks/schema.js +52 -0
- package/src/components/theme/Banner/Banner.jsx +99 -0
- package/src/components/theme/Banner/View.jsx +241 -0
- package/src/components/theme/Banner/styles.less +20 -0
- package/src/components/theme/CustomCSS/CustomCSS.jsx +12 -0
- package/src/components/theme/DraftBackground/DraftBackground.jsx +16 -0
- package/src/components/theme/DraftBackground/draft.css +3 -0
- package/src/components/theme/DraftBackground/draft.png +0 -0
- package/src/components/theme/Homepage/HomePageInverseView.jsx +60 -0
- package/src/components/theme/Homepage/HomePageView.jsx +60 -0
- package/src/components/theme/Logo.jsx +34 -0
- package/src/components/theme/SubsiteClass.jsx +23 -0
- package/src/components/theme/Widgets/TokenWidget.jsx +16 -0
- package/src/config.js +307 -0
- package/src/customizations/@eeacms/volto-block-style/StyleWrapper/schema.js +44 -0
- package/src/customizations/@eeacms/volto-eea-design-system/ui/Header/HeaderSearchPopUp.js +80 -0
- package/src/customizations/@eeacms/volto-tabs-block/components/templates/default/schema.js +109 -0
- package/src/customizations/@eeacms/volto-tabs-block/components/templates/horizontal-responsive/schema.js +109 -0
- package/src/customizations/volto/components/manage/Form/Form.jsx +784 -0
- package/src/customizations/volto/components/manage/Form/ModalForm.jsx +326 -0
- package/src/customizations/volto/components/manage/Sharing/Sharing.jsx +495 -0
- package/src/customizations/volto/components/manage/Widgets/ObjectBrowserWidget.jsx +436 -0
- package/src/customizations/volto/components/theme/Breadcrumbs/Breadcrumbs.jsx +62 -0
- package/src/customizations/volto/components/theme/Comments/Comments.jsx +487 -0
- package/src/customizations/volto/components/theme/Footer/Footer.jsx +90 -0
- package/src/customizations/volto/components/theme/Header/Header.jsx +258 -0
- package/src/customizations/volto/components/theme/Tags/Tags.jsx +53 -0
- package/src/customizations/volto/components/theme/Unauthorized/Unauthorized.jsx +91 -0
- package/src/customizations/volto/components/theme/View/EventView.jsx +90 -0
- package/src/helpers/index.js +44 -0
- package/src/icons/content-box.svg +5 -0
- package/src/icons/image-narrow.svg +5 -0
- package/src/index.js +13 -0
- package/src/middleware/voltoCustom.js +37 -0
- package/src/policy.js +136 -0
|
@@ -0,0 +1,487 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Comments components.
|
|
3
|
+
* @module components/theme/Comments/Comments
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
addComment,
|
|
8
|
+
deleteComment,
|
|
9
|
+
listComments,
|
|
10
|
+
listMoreComments,
|
|
11
|
+
} from '@plone/volto/actions';
|
|
12
|
+
import { Avatar, CommentEditModal, Form } from '@plone/volto/components';
|
|
13
|
+
import { flattenToAppURL, getBaseUrl, getColor } from '@plone/volto/helpers';
|
|
14
|
+
import PropTypes from 'prop-types';
|
|
15
|
+
import React, { Component } from 'react';
|
|
16
|
+
import { defineMessages, FormattedMessage, injectIntl } from 'react-intl';
|
|
17
|
+
import { Portal } from 'react-portal';
|
|
18
|
+
import { connect } from 'react-redux';
|
|
19
|
+
import { compose } from 'redux';
|
|
20
|
+
import { Button, Comment, Container, Icon } from 'semantic-ui-react';
|
|
21
|
+
import { formatRelativeDate } from '@plone/volto/helpers/Utils/Date';
|
|
22
|
+
import config from '@plone/volto/registry';
|
|
23
|
+
|
|
24
|
+
const messages = defineMessages({
|
|
25
|
+
comment: {
|
|
26
|
+
id: 'Comment',
|
|
27
|
+
defaultMessage: 'Comment',
|
|
28
|
+
},
|
|
29
|
+
comments: {
|
|
30
|
+
id: 'Comments',
|
|
31
|
+
defaultMessage: 'Comments',
|
|
32
|
+
},
|
|
33
|
+
commentDescription: {
|
|
34
|
+
id:
|
|
35
|
+
'You can add a comment by filling out the form below. Plain text formatting.',
|
|
36
|
+
defaultMessage:
|
|
37
|
+
'You can add a comment by filling out the form below. Plain text formatting.',
|
|
38
|
+
},
|
|
39
|
+
default: {
|
|
40
|
+
id: 'Default',
|
|
41
|
+
defaultMessage: 'Default',
|
|
42
|
+
},
|
|
43
|
+
delete: {
|
|
44
|
+
id: 'Delete',
|
|
45
|
+
defaultMessage: 'Delete',
|
|
46
|
+
},
|
|
47
|
+
edit: {
|
|
48
|
+
id: 'Edit',
|
|
49
|
+
defaultMessage: 'Edit',
|
|
50
|
+
},
|
|
51
|
+
reply: {
|
|
52
|
+
id: 'Reply',
|
|
53
|
+
defaultMessage: 'Reply',
|
|
54
|
+
},
|
|
55
|
+
hideReplies: {
|
|
56
|
+
id: 'Hide Replies',
|
|
57
|
+
defaultMessage: 'Hide Replies',
|
|
58
|
+
},
|
|
59
|
+
showReplies: {
|
|
60
|
+
id: 'Show Replies',
|
|
61
|
+
defaultMessage: 'Show Replies',
|
|
62
|
+
},
|
|
63
|
+
loadMoreComments: {
|
|
64
|
+
id: 'Load more',
|
|
65
|
+
defaultMessage: 'Load more...',
|
|
66
|
+
},
|
|
67
|
+
});
|
|
68
|
+
/**
|
|
69
|
+
* Schema for the Form components to show an input field with it's label
|
|
70
|
+
* @param {Object} intl
|
|
71
|
+
*/
|
|
72
|
+
const makeFormSchema = (intl) => ({
|
|
73
|
+
fieldsets: [
|
|
74
|
+
{
|
|
75
|
+
fields: ['comment'],
|
|
76
|
+
id: 'default',
|
|
77
|
+
title: intl.formatMessage(messages.default),
|
|
78
|
+
},
|
|
79
|
+
],
|
|
80
|
+
properties: {
|
|
81
|
+
comment: {
|
|
82
|
+
title: intl.formatMessage(messages.comment),
|
|
83
|
+
type: 'string',
|
|
84
|
+
widget: 'textarea',
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
required: ['comment1'],
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Comments container class.
|
|
92
|
+
* @class Comments
|
|
93
|
+
* @extends Component
|
|
94
|
+
*/
|
|
95
|
+
class Comments extends Component {
|
|
96
|
+
/**
|
|
97
|
+
* Property types.
|
|
98
|
+
* @property {Object} propTypes Property types.
|
|
99
|
+
* @static
|
|
100
|
+
*/
|
|
101
|
+
static propTypes = {
|
|
102
|
+
addComment: PropTypes.func.isRequired,
|
|
103
|
+
deleteComment: PropTypes.func.isRequired,
|
|
104
|
+
listComments: PropTypes.func.isRequired,
|
|
105
|
+
listMoreComments: PropTypes.func.isRequired,
|
|
106
|
+
pathname: PropTypes.string.isRequired,
|
|
107
|
+
items: PropTypes.arrayOf(
|
|
108
|
+
PropTypes.shape({
|
|
109
|
+
author_name: PropTypes.string,
|
|
110
|
+
creation_date: PropTypes.string,
|
|
111
|
+
text: PropTypes.shape({
|
|
112
|
+
data: PropTypes.string,
|
|
113
|
+
'mime-type': PropTypes.string,
|
|
114
|
+
}),
|
|
115
|
+
is_deletable: PropTypes.bool,
|
|
116
|
+
is_editable: PropTypes.bool,
|
|
117
|
+
}),
|
|
118
|
+
).isRequired,
|
|
119
|
+
addRequest: PropTypes.shape({
|
|
120
|
+
loading: PropTypes.bool,
|
|
121
|
+
loaded: PropTypes.bool,
|
|
122
|
+
}).isRequired,
|
|
123
|
+
deleteRequest: PropTypes.shape({
|
|
124
|
+
loading: PropTypes.bool,
|
|
125
|
+
loaded: PropTypes.bool,
|
|
126
|
+
}).isRequired,
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Constructor
|
|
131
|
+
* @method constructor
|
|
132
|
+
* @param {Object} props Component properties
|
|
133
|
+
* @constructs Comments
|
|
134
|
+
*/
|
|
135
|
+
constructor(props) {
|
|
136
|
+
super(props);
|
|
137
|
+
this.onSubmit = this.onSubmit.bind(this);
|
|
138
|
+
this.onDelete = this.onDelete.bind(this);
|
|
139
|
+
this.onEdit = this.onEdit.bind(this);
|
|
140
|
+
this.onEditOk = this.onEditOk.bind(this);
|
|
141
|
+
this.onEditCancel = this.onEditCancel.bind(this);
|
|
142
|
+
this.setReplyTo = this.setReplyTo.bind(this);
|
|
143
|
+
this.loadMoreComments = this.loadMoreComments.bind(this);
|
|
144
|
+
this.state = {
|
|
145
|
+
showEdit: false,
|
|
146
|
+
editId: null,
|
|
147
|
+
editText: null,
|
|
148
|
+
replyTo: null,
|
|
149
|
+
collapsedComments: {},
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
componentDidMount() {
|
|
154
|
+
this.props.listComments(getBaseUrl(this.props.pathname));
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Component will receive props
|
|
159
|
+
* @method componentWillReceiveProps
|
|
160
|
+
* @param {Object} nextProps Next properties
|
|
161
|
+
* @returns {undefined}
|
|
162
|
+
*/
|
|
163
|
+
UNSAFE_componentWillReceiveProps(nextProps) {
|
|
164
|
+
if (
|
|
165
|
+
nextProps.pathname !== this.props.pathname ||
|
|
166
|
+
(this.props.addRequest.loading && nextProps.addRequest.loaded) ||
|
|
167
|
+
(this.props.deleteRequest.loading && nextProps.deleteRequest.loaded)
|
|
168
|
+
) {
|
|
169
|
+
this.props.listComments(getBaseUrl(nextProps.pathname));
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Submit handler
|
|
175
|
+
* @method onSubmit
|
|
176
|
+
* @param {Object} formData Form data.
|
|
177
|
+
* @returns {undefined}
|
|
178
|
+
*/
|
|
179
|
+
onSubmit(formData) {
|
|
180
|
+
this.props.addComment(
|
|
181
|
+
getBaseUrl(this.props.pathname),
|
|
182
|
+
formData.comment,
|
|
183
|
+
this.state.replyTo,
|
|
184
|
+
);
|
|
185
|
+
this.setState({ replyTo: null });
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* The id of the comment that will receive a reply
|
|
190
|
+
* @param {string} commentId
|
|
191
|
+
*/
|
|
192
|
+
setReplyTo(commentId) {
|
|
193
|
+
this.setState({ replyTo: commentId });
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Calls the action listMoreComments passing the received url for next array of comments
|
|
198
|
+
*/
|
|
199
|
+
loadMoreComments() {
|
|
200
|
+
this.props.listMoreComments(this.props.next);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Delete handler
|
|
205
|
+
* @method onDelete
|
|
206
|
+
* @param {Object} event Event object.
|
|
207
|
+
* @param {string} value Delete value.
|
|
208
|
+
* @returns {undefined}
|
|
209
|
+
*/
|
|
210
|
+
onDelete(value) {
|
|
211
|
+
this.props.deleteComment(value);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Will hide all replies to the specific comment
|
|
216
|
+
* including replies to any of the replies
|
|
217
|
+
* @param {string} commentId
|
|
218
|
+
*/
|
|
219
|
+
hideReply(commentId) {
|
|
220
|
+
this.setState((prevState) => {
|
|
221
|
+
const hasComment = prevState.collapsedComments[commentId];
|
|
222
|
+
const { collapsedComments } = prevState;
|
|
223
|
+
|
|
224
|
+
return {
|
|
225
|
+
collapsedComments: {
|
|
226
|
+
...collapsedComments,
|
|
227
|
+
[commentId]: !hasComment,
|
|
228
|
+
},
|
|
229
|
+
};
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Edit handler
|
|
235
|
+
* @method onEdit
|
|
236
|
+
* @param {Object} event Event object.
|
|
237
|
+
* @param {string} value Delete value.
|
|
238
|
+
* @returns {undefined}
|
|
239
|
+
*/
|
|
240
|
+
onEdit(value) {
|
|
241
|
+
this.setState({
|
|
242
|
+
showEdit: true,
|
|
243
|
+
editId: value.id,
|
|
244
|
+
editText: value.text,
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* On edit ok
|
|
250
|
+
* @method onEditOk
|
|
251
|
+
* @returns {undefined}
|
|
252
|
+
*/
|
|
253
|
+
onEditOk() {
|
|
254
|
+
this.setState({
|
|
255
|
+
showEdit: false,
|
|
256
|
+
editId: null,
|
|
257
|
+
editText: null,
|
|
258
|
+
});
|
|
259
|
+
this.props.listComments(getBaseUrl(this.props.pathname));
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* On edit cancel
|
|
264
|
+
* @method onEditCancel
|
|
265
|
+
* @returns {undefined}
|
|
266
|
+
*/
|
|
267
|
+
onEditCancel(ev) {
|
|
268
|
+
this.setState({
|
|
269
|
+
showEdit: false,
|
|
270
|
+
editId: null,
|
|
271
|
+
editText: null,
|
|
272
|
+
replyTo: null,
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
addRepliesAsChildrenToComments(items) {
|
|
277
|
+
let initialValue = {};
|
|
278
|
+
const allCommentsWithCildren = items.reduce((accumulator, item) => {
|
|
279
|
+
return {
|
|
280
|
+
[item.comment_id]: { comment: item, children: [] },
|
|
281
|
+
...accumulator,
|
|
282
|
+
};
|
|
283
|
+
}, initialValue);
|
|
284
|
+
|
|
285
|
+
items.forEach((comment) => {
|
|
286
|
+
if (comment.in_reply_to) {
|
|
287
|
+
allCommentsWithCildren[comment.in_reply_to].children.push(comment);
|
|
288
|
+
}
|
|
289
|
+
});
|
|
290
|
+
return allCommentsWithCildren;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Render method.
|
|
295
|
+
* @method render
|
|
296
|
+
* @returns {string} Markup for the component.
|
|
297
|
+
*/
|
|
298
|
+
render() {
|
|
299
|
+
const { items } = this.props;
|
|
300
|
+
const { collapsedComments } = this.state;
|
|
301
|
+
// object with comment ids, to easily verify if any comment has children
|
|
302
|
+
const allCommentsWithCildren = this.addRepliesAsChildrenToComments(items);
|
|
303
|
+
// all comments that are not a reply will be shown in the first iteration
|
|
304
|
+
const allPrimaryComments = items.filter((comment) => !comment.in_reply_to);
|
|
305
|
+
|
|
306
|
+
// recursively makes comments with their replies nested
|
|
307
|
+
// each iteration will show replies to the specific comment using allCommentsWithCildren
|
|
308
|
+
const commentElement = (comment) => (
|
|
309
|
+
<Comment key={comment.comment_id}>
|
|
310
|
+
<Avatar
|
|
311
|
+
src={comment.author_image}
|
|
312
|
+
title={comment.author_name || 'Anonymous'}
|
|
313
|
+
color={getColor(comment.author_username)}
|
|
314
|
+
/>
|
|
315
|
+
<Comment.Content>
|
|
316
|
+
<Comment.Author as="span">{comment.author_name}</Comment.Author>
|
|
317
|
+
<Comment.Metadata>
|
|
318
|
+
<span>
|
|
319
|
+
{' '}
|
|
320
|
+
<span title={comment.creation_date}>
|
|
321
|
+
{formatRelativeDate({
|
|
322
|
+
date: comment.creation_date,
|
|
323
|
+
locale: config.settings.dateLocale || 'en-gb',
|
|
324
|
+
})}
|
|
325
|
+
</span>
|
|
326
|
+
</span>
|
|
327
|
+
</Comment.Metadata>
|
|
328
|
+
<Comment.Text>
|
|
329
|
+
{' '}
|
|
330
|
+
{comment.text['mime-type'] === 'text/html' ? (
|
|
331
|
+
<div
|
|
332
|
+
dangerouslySetInnerHTML={{
|
|
333
|
+
__html: comment.text.data,
|
|
334
|
+
}}
|
|
335
|
+
/>
|
|
336
|
+
) : (
|
|
337
|
+
comment.text.data
|
|
338
|
+
)}
|
|
339
|
+
</Comment.Text>
|
|
340
|
+
<Comment.Actions>
|
|
341
|
+
<Comment.Action
|
|
342
|
+
as="a"
|
|
343
|
+
aria-label={this.props.intl.formatMessage(messages.reply)}
|
|
344
|
+
onClick={() => this.setReplyTo(comment.comment_id)}
|
|
345
|
+
>
|
|
346
|
+
<FormattedMessage id="Reply" defaultMessage="Reply" />
|
|
347
|
+
</Comment.Action>
|
|
348
|
+
{comment.is_editable && (
|
|
349
|
+
<Comment.Action
|
|
350
|
+
onClick={() =>
|
|
351
|
+
this.onEdit({
|
|
352
|
+
id: flattenToAppURL(comment['@id']),
|
|
353
|
+
text: comment.text.data,
|
|
354
|
+
})
|
|
355
|
+
}
|
|
356
|
+
aria-label={this.props.intl.formatMessage(messages.edit)}
|
|
357
|
+
value={{
|
|
358
|
+
id: flattenToAppURL(comment['@id']),
|
|
359
|
+
text: comment.text.data,
|
|
360
|
+
}}
|
|
361
|
+
>
|
|
362
|
+
<FormattedMessage id="Edit" defaultMessage="Edit" />
|
|
363
|
+
</Comment.Action>
|
|
364
|
+
)}
|
|
365
|
+
{comment.is_deletable && (
|
|
366
|
+
<Comment.Action
|
|
367
|
+
aria-label={this.props.intl.formatMessage(messages.delete)}
|
|
368
|
+
onClick={() => this.onDelete(flattenToAppURL(comment['@id']))}
|
|
369
|
+
color="red"
|
|
370
|
+
>
|
|
371
|
+
<Icon name="delete" color="red" />
|
|
372
|
+
<FormattedMessage
|
|
373
|
+
id="Delete"
|
|
374
|
+
defaultMessage="Delete"
|
|
375
|
+
color="red"
|
|
376
|
+
/>
|
|
377
|
+
</Comment.Action>
|
|
378
|
+
)}
|
|
379
|
+
<Comment.Action
|
|
380
|
+
as="a"
|
|
381
|
+
onClick={() => this.hideReply(comment.comment_id)}
|
|
382
|
+
>
|
|
383
|
+
{allCommentsWithCildren[comment.comment_id].children.length >
|
|
384
|
+
0 ? (
|
|
385
|
+
this.state.collapsedComments[comment.comment_id] ? (
|
|
386
|
+
<>
|
|
387
|
+
<Icon name="eye" color="blue" />
|
|
388
|
+
<FormattedMessage
|
|
389
|
+
id="Show Replies"
|
|
390
|
+
defaultMessage="Show Replies"
|
|
391
|
+
/>
|
|
392
|
+
</>
|
|
393
|
+
) : (
|
|
394
|
+
<>
|
|
395
|
+
<Icon name="minus" color="blue" />
|
|
396
|
+
<FormattedMessage
|
|
397
|
+
id="Hide Replies"
|
|
398
|
+
defaultMessage="Hide Replies"
|
|
399
|
+
/>
|
|
400
|
+
</>
|
|
401
|
+
)
|
|
402
|
+
) : null}
|
|
403
|
+
</Comment.Action>
|
|
404
|
+
</Comment.Actions>
|
|
405
|
+
<div id={`reply-place-${comment.comment_id}`}></div>
|
|
406
|
+
</Comment.Content>
|
|
407
|
+
|
|
408
|
+
{allCommentsWithCildren[comment.comment_id].children.length > 0
|
|
409
|
+
? allCommentsWithCildren[comment.comment_id].children.map(
|
|
410
|
+
(child, index) => (
|
|
411
|
+
<Comment.Group
|
|
412
|
+
collapsed={collapsedComments[comment.comment_id]}
|
|
413
|
+
key={`group-${index}-${comment.comment_id}`}
|
|
414
|
+
>
|
|
415
|
+
{commentElement(child)}
|
|
416
|
+
</Comment.Group>
|
|
417
|
+
),
|
|
418
|
+
)
|
|
419
|
+
: null}
|
|
420
|
+
</Comment>
|
|
421
|
+
);
|
|
422
|
+
|
|
423
|
+
return (
|
|
424
|
+
<Container className="comments">
|
|
425
|
+
<CommentEditModal
|
|
426
|
+
open={this.state.showEdit}
|
|
427
|
+
onCancel={this.onEditCancel}
|
|
428
|
+
onOk={this.onEditOk}
|
|
429
|
+
id={this.state.editId}
|
|
430
|
+
text={this.state.editText}
|
|
431
|
+
/>
|
|
432
|
+
<div id="comment-add-id">
|
|
433
|
+
<Form
|
|
434
|
+
onSubmit={this.onSubmit}
|
|
435
|
+
onCancel={this.onEditCancel}
|
|
436
|
+
submitLabel={this.props.intl.formatMessage(messages.comment)}
|
|
437
|
+
resetAfterSubmit
|
|
438
|
+
schema={makeFormSchema(this.props.intl)}
|
|
439
|
+
/>
|
|
440
|
+
</div>
|
|
441
|
+
|
|
442
|
+
{/* all comments */}
|
|
443
|
+
<Comment.Group threaded={false}>
|
|
444
|
+
{allPrimaryComments.map((item) => commentElement(item))}
|
|
445
|
+
</Comment.Group>
|
|
446
|
+
|
|
447
|
+
{/* load more button */}
|
|
448
|
+
{this.props.items_total > this.props.items.length && (
|
|
449
|
+
<Button fluid basic color="blue" onClick={this.loadMoreComments}>
|
|
450
|
+
<FormattedMessage id="Load more" defaultMessage="Load more..." />
|
|
451
|
+
</Button>
|
|
452
|
+
)}
|
|
453
|
+
|
|
454
|
+
{this.state.replyTo && (
|
|
455
|
+
<Portal
|
|
456
|
+
node={
|
|
457
|
+
document &&
|
|
458
|
+
document.getElementById(`reply-place-${this.state.replyTo}`)
|
|
459
|
+
}
|
|
460
|
+
>
|
|
461
|
+
<Form
|
|
462
|
+
onSubmit={this.onSubmit}
|
|
463
|
+
onCancel={this.onEditCancel}
|
|
464
|
+
submitLabel={this.props.intl.formatMessage(messages.comment)}
|
|
465
|
+
resetAfterSubmit
|
|
466
|
+
schema={makeFormSchema(this.props.intl)}
|
|
467
|
+
/>
|
|
468
|
+
</Portal>
|
|
469
|
+
)}
|
|
470
|
+
</Container>
|
|
471
|
+
);
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
export default compose(
|
|
476
|
+
injectIntl,
|
|
477
|
+
connect(
|
|
478
|
+
(state) => ({
|
|
479
|
+
items: state.comments.items,
|
|
480
|
+
next: state.comments.next,
|
|
481
|
+
items_total: state.comments.items_total,
|
|
482
|
+
addRequest: state.comments.add,
|
|
483
|
+
deleteRequest: state.comments.delete,
|
|
484
|
+
}),
|
|
485
|
+
{ addComment, deleteComment, listComments, listMoreComments },
|
|
486
|
+
),
|
|
487
|
+
)(Comments);
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Footer component.
|
|
3
|
+
* @module components/theme/Footer/Footer
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import React from 'react';
|
|
7
|
+
import { useSelector, shallowEqual } from 'react-redux';
|
|
8
|
+
import { flattenToAppURL } from '@plone/volto/helpers';
|
|
9
|
+
import EEAFooter from '@eeacms/volto-eea-design-system/ui/Footer/Footer';
|
|
10
|
+
import config from '@plone/volto/registry';
|
|
11
|
+
|
|
12
|
+
const Footer = () => {
|
|
13
|
+
const { eea } = config.settings;
|
|
14
|
+
const {
|
|
15
|
+
footerActions = [],
|
|
16
|
+
copyrightActions = [],
|
|
17
|
+
socialActions = [],
|
|
18
|
+
contactActions = [],
|
|
19
|
+
contactExtraActions = [],
|
|
20
|
+
} = useSelector(
|
|
21
|
+
(state) => ({
|
|
22
|
+
footerActions: state.actions?.actions?.footer_actions,
|
|
23
|
+
copyrightActions: state.actions?.actions?.copyright_actions,
|
|
24
|
+
socialActions: state.actions?.actions?.social_actions,
|
|
25
|
+
contactActions: state.actions?.actions?.contact_actions,
|
|
26
|
+
contactExtraActions: state.actions?.actions?.contact_extra_actions,
|
|
27
|
+
}),
|
|
28
|
+
shallowEqual,
|
|
29
|
+
);
|
|
30
|
+
// ZMI > portal_actions > footer_actions
|
|
31
|
+
const actions = footerActions.length
|
|
32
|
+
? footerActions.map((action) => ({
|
|
33
|
+
title: action.title,
|
|
34
|
+
link: flattenToAppURL(action.url),
|
|
35
|
+
}))
|
|
36
|
+
: eea.footerOpts.actions;
|
|
37
|
+
|
|
38
|
+
// ZMI > portal_actions > copyright_actions
|
|
39
|
+
const copyright = copyrightActions.length
|
|
40
|
+
? copyrightActions.map((action) => ({
|
|
41
|
+
title: action.title,
|
|
42
|
+
site: action.title,
|
|
43
|
+
link: flattenToAppURL(action.url),
|
|
44
|
+
}))
|
|
45
|
+
: eea.footerOpts.copyright;
|
|
46
|
+
|
|
47
|
+
// ZMI > portal_actions > social_actions
|
|
48
|
+
const social = socialActions.length
|
|
49
|
+
? socialActions.map((action) => ({
|
|
50
|
+
name: action.id,
|
|
51
|
+
icon: action.icon,
|
|
52
|
+
link: action.url,
|
|
53
|
+
}))
|
|
54
|
+
: eea.footerOpts.social;
|
|
55
|
+
|
|
56
|
+
// ZMI > portal_actions > contact_actions
|
|
57
|
+
const contacts = contactActions.length
|
|
58
|
+
? contactActions.map((action, idx) => ({
|
|
59
|
+
text: action.title,
|
|
60
|
+
icon: action.icon,
|
|
61
|
+
link: flattenToAppURL(action.url),
|
|
62
|
+
children:
|
|
63
|
+
idx === 0
|
|
64
|
+
? contactExtraActions.map((child) => ({
|
|
65
|
+
text: child.title,
|
|
66
|
+
icon: child.icon,
|
|
67
|
+
link: flattenToAppURL(child.url),
|
|
68
|
+
}))
|
|
69
|
+
: [],
|
|
70
|
+
}))
|
|
71
|
+
: eea.footerOpts.contacts;
|
|
72
|
+
|
|
73
|
+
// Update options with actions from backend
|
|
74
|
+
const options = {
|
|
75
|
+
...eea.footerOpts,
|
|
76
|
+
social,
|
|
77
|
+
contacts,
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
return (
|
|
81
|
+
<EEAFooter>
|
|
82
|
+
<EEAFooter.SubFooter {...options} />
|
|
83
|
+
<EEAFooter.Header>{eea.footerOpts.header}</EEAFooter.Header>
|
|
84
|
+
<EEAFooter.Sites sites={eea.footerOpts.sites} />
|
|
85
|
+
<EEAFooter.Actions actions={actions} copyright={copyright} />
|
|
86
|
+
</EEAFooter>
|
|
87
|
+
);
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
export default Footer;
|