@plone/volto 17.0.0-alpha.23 → 17.0.0-alpha.25

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 (81) hide show
  1. package/.yarn/install-state.gz +0 -0
  2. package/CHANGELOG.md +58 -1
  3. package/CONTRIBUTING.md +5 -1
  4. package/README.md +2 -1
  5. package/locales/ca/LC_MESSAGES/volto.po +37 -0
  6. package/locales/ca.json +1 -1
  7. package/locales/de/LC_MESSAGES/volto.po +37 -0
  8. package/locales/de.json +1 -1
  9. package/locales/en/LC_MESSAGES/volto.po +37 -0
  10. package/locales/en.json +1 -1
  11. package/locales/es/LC_MESSAGES/volto.po +37 -0
  12. package/locales/es.json +1 -1
  13. package/locales/eu/LC_MESSAGES/volto.po +37 -0
  14. package/locales/eu.json +1 -1
  15. package/locales/fi/LC_MESSAGES/volto.po +37 -0
  16. package/locales/fi.json +1 -1
  17. package/locales/fr/LC_MESSAGES/volto.po +37 -0
  18. package/locales/fr.json +1 -1
  19. package/locales/it/LC_MESSAGES/volto.po +37 -0
  20. package/locales/it.json +1 -1
  21. package/locales/ja/LC_MESSAGES/volto.po +37 -0
  22. package/locales/ja.json +1 -1
  23. package/locales/nl/LC_MESSAGES/volto.po +37 -0
  24. package/locales/nl.json +1 -1
  25. package/locales/pt/LC_MESSAGES/volto.po +37 -0
  26. package/locales/pt.json +1 -1
  27. package/locales/pt_BR/LC_MESSAGES/volto.po +37 -0
  28. package/locales/pt_BR.json +1 -1
  29. package/locales/ro/LC_MESSAGES/volto.po +37 -0
  30. package/locales/ro.json +1 -1
  31. package/locales/volto.pot +38 -1
  32. package/locales/zh_CN/LC_MESSAGES/volto.po +37 -0
  33. package/locales/zh_CN.json +1 -1
  34. package/package.json +2 -2
  35. package/packages/volto-slate/build/messages/src/blocks/Table/TableBlockEdit.json +90 -0
  36. package/packages/volto-slate/build/messages/src/blocks/Text/DefaultTextBlockEditor.json +6 -0
  37. package/packages/volto-slate/build/messages/src/blocks/Text/DetachedTextBlockEditor.json +6 -0
  38. package/packages/volto-slate/build/messages/src/blocks/Text/SlashMenu.json +6 -0
  39. package/packages/volto-slate/build/messages/src/editor/plugins/AdvancedLink/index.json +10 -0
  40. package/packages/volto-slate/build/messages/src/editor/plugins/Link/index.json +10 -0
  41. package/packages/volto-slate/build/messages/src/editor/plugins/Table/index.json +30 -0
  42. package/packages/volto-slate/build/messages/src/elementEditor/messages.json +10 -0
  43. package/packages/volto-slate/build/messages/src/widgets/HtmlSlateWidget.json +6 -0
  44. package/packages/volto-slate/build/messages/src/widgets/RichTextWidgetView.json +6 -0
  45. package/packages/volto-slate/package.json +1 -1
  46. package/src/actions/index.js +1 -0
  47. package/src/actions/relations/relations.js +17 -0
  48. package/src/components/manage/Blocks/Image/schema.js +5 -1
  49. package/src/components/manage/Blocks/Listing/withQuerystringResults.jsx +18 -11
  50. package/src/components/manage/Blocks/ToC/Schema.jsx +36 -7
  51. package/src/components/manage/Blocks/ToC/variations/DefaultTocRenderer.jsx +4 -3
  52. package/src/components/manage/Blocks/ToC/variations/DefaultTocRenderer.test.jsx +44 -0
  53. package/src/components/manage/Contents/Contents.jsx +27 -0
  54. package/src/components/manage/Controlpanels/Relations/BrokenRelations.jsx +11 -9
  55. package/src/components/manage/Controlpanels/Relations/Relations.jsx +3 -3
  56. package/src/components/manage/Controlpanels/Relations/RelationsListing.jsx +8 -7
  57. package/src/components/manage/Controlpanels/Relations/RelationsMatrix.jsx +15 -9
  58. package/src/components/manage/Controlpanels/Users/UsersControlpanel.jsx +25 -10
  59. package/src/components/manage/Diff/DiffField.jsx +25 -1
  60. package/src/components/manage/LinksToItem/LinksToItem.jsx +1 -1
  61. package/src/components/manage/LinksToItem/LinksToItem.test.jsx +1 -1
  62. package/src/components/manage/Sharing/Sharing.jsx +11 -5
  63. package/src/components/manage/Widgets/FormFieldWrapper.jsx +1 -1
  64. package/src/components/theme/Comments/Comment.stories.jsx +84 -0
  65. package/src/components/theme/Comments/Comments.jsx +273 -378
  66. package/src/components/theme/Error/ServerError.jsx +29 -0
  67. package/src/components/theme/Logout/Logout.jsx +36 -83
  68. package/src/components/theme/Search/SearchTags.jsx +30 -60
  69. package/src/components/theme/Sitemap/Sitemap.jsx +24 -13
  70. package/src/components/theme/Sitemap/Sitemap.test.jsx +23 -2
  71. package/src/config/Views.jsx +2 -0
  72. package/src/constants/ActionTypes.js +1 -0
  73. package/src/middleware/api.js +14 -2
  74. package/src/reducers/relations/relations.js +74 -46
  75. package/src/server.jsx +9 -0
  76. package/theme/themes/pastanaga/collections/form.overrides +46 -0
  77. package/theme/themes/pastanaga/elements/input.overrides +6 -0
  78. package/theme/themes/pastanaga/elements/label.overrides +10 -0
  79. package/.gitignore~ +0 -71
  80. package/news/4547.breaking~ +0 -1
  81. package/src/config/index.js~ +0 -223
@@ -1,8 +1,12 @@
1
- /**
2
- * Comments components.
3
- * @module components/theme/Comments/Comments
4
- */
1
+ import { useEffect, useState, useMemo, useCallback } from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
4
+ import { Portal } from 'react-portal';
5
+ import { useDispatch, useSelector, shallowEqual } from 'react-redux';
6
+ import { compose } from 'redux';
7
+ import { Button, Comment, Container, Icon } from 'semantic-ui-react';
5
8
 
9
+ import { injectLazyLibs } from '@plone/volto/helpers/Loadable/Loadable';
6
10
  import {
7
11
  addComment,
8
12
  deleteComment,
@@ -10,16 +14,12 @@ import {
10
14
  listMoreComments,
11
15
  } from '@plone/volto/actions';
12
16
  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 { injectLazyLibs } from '@plone/volto/helpers/Loadable/Loadable';
22
- // import { Button, Grid, Segment, Container } from 'semantic-ui-react';
17
+ import {
18
+ flattenToAppURL,
19
+ getBaseUrl,
20
+ getColor,
21
+ usePrevious,
22
+ } from '@plone/volto/helpers';
23
23
 
24
24
  const messages = defineMessages({
25
25
  comment: {
@@ -65,10 +65,7 @@ const messages = defineMessages({
65
65
  defaultMessage: 'Load more...',
66
66
  },
67
67
  });
68
- /**
69
- * Schema for the Form components to show an input field with it's label
70
- * @param {Object} intl
71
- */
68
+
72
69
  const makeFormSchema = (intl) => ({
73
70
  fieldsets: [
74
71
  {
@@ -87,193 +84,114 @@ const makeFormSchema = (intl) => ({
87
84
  required: ['comment1'],
88
85
  });
89
86
 
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
- };
87
+ const useComments = () => {
88
+ const items = useSelector((state) => state.comments.items, shallowEqual);
89
+ const next = useSelector((state) => state.comments.next, shallowEqual);
90
+ const items_total = useSelector(
91
+ (state) => state.comments.items_total,
92
+ shallowEqual,
93
+ );
94
+ const permissions = useSelector(
95
+ (state) => state.comments.permissions || {},
96
+ shallowEqual,
97
+ );
98
+ const addRequest = useSelector((state) => state.comments.add, shallowEqual);
99
+ const deleteRequest = useSelector(
100
+ (state) => state.comments.delete,
101
+ shallowEqual,
102
+ );
128
103
 
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
- }
104
+ return { items, next, items_total, permissions, addRequest, deleteRequest };
105
+ };
152
106
 
153
- componentDidMount() {
154
- this.props.listComments(getBaseUrl(this.props.pathname));
155
- }
107
+ const Comments = (props) => {
108
+ const intl = useIntl();
109
+ const dispatch = useDispatch();
110
+ const { pathname } = props;
111
+ const [showEdit, setshowEdit] = useState(false);
112
+ const [editId, seteditId] = useState(null);
113
+ const [editText, seteditText] = useState(null);
114
+ const [replyTo, setreplyTo] = useState(null);
115
+ const [collapsedComments, setcollapsedComments] = useState({});
116
+ const {
117
+ items,
118
+ next,
119
+ items_total,
120
+ permissions,
121
+ addRequest,
122
+ deleteRequest,
123
+ } = useComments();
156
124
 
157
- /**
158
- * Component will receive props
159
- * @method componentWillReceiveProps
160
- * @param {Object} nextProps Next properties
161
- * @returns {undefined}
162
- */
163
- UNSAFE_componentWillReceiveProps(nextProps) {
125
+ const prevpathname = usePrevious(pathname);
126
+
127
+ const prevaddRequestLoading = usePrevious(addRequest.loading);
128
+ const prevdeleteRequestLoading = usePrevious(deleteRequest.loading);
129
+
130
+ useEffect(() => {
164
131
  if (
165
- nextProps.pathname !== this.props.pathname ||
166
- (this.props.addRequest.loading && nextProps.addRequest.loaded) ||
167
- (this.props.deleteRequest.loading && nextProps.deleteRequest.loaded)
132
+ pathname !== prevpathname ||
133
+ (prevaddRequestLoading && addRequest.loaded) ||
134
+ (prevdeleteRequestLoading && deleteRequest.loaded)
168
135
  ) {
169
- this.props.listComments(getBaseUrl(nextProps.pathname));
136
+ dispatch(listComments(getBaseUrl(pathname)));
170
137
  }
171
- }
138
+ }, [
139
+ dispatch,
140
+ pathname,
141
+ prevpathname,
142
+ prevaddRequestLoading,
143
+ addRequest.loaded,
144
+ prevdeleteRequestLoading,
145
+ deleteRequest.loaded,
146
+ ]);
172
147
 
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
- }
148
+ const onSubmit = (formData) => {
149
+ dispatch(addComment(getBaseUrl(pathname), formData.comment, replyTo));
150
+ setreplyTo(null);
151
+ };
195
152
 
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
- }
153
+ const setReplyTo = (commentId) => {
154
+ setreplyTo(commentId);
155
+ };
202
156
 
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
- }
157
+ const loadMoreComments = () => {
158
+ dispatch(listMoreComments(next));
159
+ };
213
160
 
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;
161
+ const onDelete = (value) => {
162
+ dispatch(deleteComment(value));
163
+ };
164
+ const prevcollapsedComments = usePrevious(collapsedComments);
223
165
 
224
- return {
225
- collapsedComments: {
226
- ...collapsedComments,
227
- [commentId]: !hasComment,
228
- },
229
- };
230
- });
231
- }
166
+ const hideReply = (commentId) => {
167
+ const hasComment = prevcollapsedComments[commentId];
168
+ setcollapsedComments((prevState) => ({
169
+ ...prevState,
170
+ [commentId]: !hasComment,
171
+ }));
172
+ };
232
173
 
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
- }
174
+ const onEdit = useCallback((value) => {
175
+ setshowEdit(true);
176
+ seteditText(value.text);
177
+ seteditId(value.id);
178
+ }, []);
247
179
 
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
- }
180
+ const onEditOk = () => {
181
+ setshowEdit(false);
182
+ seteditId(null);
183
+ seteditText(null);
184
+ dispatch(listComments(getBaseUrl(pathname)));
185
+ };
261
186
 
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
- }
187
+ const onEditCancel = useCallback(() => {
188
+ setshowEdit(false);
189
+ seteditId(null);
190
+ seteditText(null);
191
+ setreplyTo(null);
192
+ }, []);
275
193
 
276
- addRepliesAsChildrenToComments(items) {
194
+ const addRepliesAsChildrenToComments = (items) => {
277
195
  let initialValue = {};
278
196
  const allCommentsWithCildren = items.reduce((accumulator, item) => {
279
197
  return {
@@ -288,205 +206,182 @@ class Comments extends Component {
288
206
  }
289
207
  });
290
208
  return allCommentsWithCildren;
291
- }
209
+ };
292
210
 
293
- /**
294
- * Render method.
295
- * @method render
296
- * @returns {string} Markup for the component.
297
- */
298
- render() {
299
- const { items, permissions } = this.props;
300
- const moment = this.props.moment.default;
301
- const { collapsedComments } = this.state;
302
- // object with comment ids, to easily verify if any comment has children
303
- const allCommentsWithCildren = this.addRepliesAsChildrenToComments(items);
304
- // all comments that are not a reply will be shown in the first iteration
305
- const allPrimaryComments = items.filter((comment) => !comment.in_reply_to);
211
+ const moment = props.moment.default;
306
212
 
307
- // recursively makes comments with their replies nested
308
- // each iteration will show replies to the specific comment using allCommentsWithCildren
309
- const commentElement = (comment) => (
310
- <Comment key={comment.comment_id}>
311
- <Avatar
312
- src={flattenToAppURL(comment.author_image)}
313
- title={comment.author_name || 'Anonymous'}
314
- color={getColor(comment.author_username)}
315
- />
316
- <Comment.Content>
317
- <Comment.Author>{comment.author_name}</Comment.Author>
318
- <Comment.Metadata>
319
- <span>
320
- {' '}
321
- <span title={moment(comment.creation_date).format('LLLL')}>
322
- {moment(comment.creation_date).fromNow()}
323
- </span>
324
- </span>
325
- </Comment.Metadata>
326
- <Comment.Text>
213
+ const allCommentsWithCildren = useMemo(
214
+ () => addRepliesAsChildrenToComments(items),
215
+ [items],
216
+ );
217
+ // all comments that are not a reply will be shown in the first iteration
218
+ const allPrimaryComments = items.filter((comment) => !comment.in_reply_to);
219
+
220
+ // recursively makes comments with their replies nested
221
+ // each iteration will show replies to the specific comment using allCommentsWithCildren
222
+ const commentElement = (comment) => (
223
+ <Comment key={comment.comment_id}>
224
+ <Avatar
225
+ src={flattenToAppURL(comment.author_image)}
226
+ title={comment.author_name || 'Anonymous'}
227
+ color={getColor(comment.author_username)}
228
+ />
229
+ <Comment.Content>
230
+ <Comment.Author>{comment.author_name}</Comment.Author>
231
+ <Comment.Metadata>
232
+ <span>
327
233
  {' '}
328
- {comment.text['mime-type'] === 'text/html' ? (
329
- <div
330
- dangerouslySetInnerHTML={{
331
- __html: comment.text.data,
332
- }}
333
- />
334
- ) : (
335
- comment.text.data
336
- )}
337
- </Comment.Text>
338
- <Comment.Actions>
339
- {comment.can_reply && (
340
- <Comment.Action
341
- as="a"
342
- aria-label={this.props.intl.formatMessage(messages.reply)}
343
- onClick={() => this.setReplyTo(comment.comment_id)}
344
- >
345
- <FormattedMessage id="Reply" defaultMessage="Reply" />
346
- </Comment.Action>
347
- )}
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={{
234
+ <span title={moment(comment.creation_date).format('LLLL')}>
235
+ {moment(comment.creation_date).fromNow()}
236
+ </span>
237
+ </span>
238
+ </Comment.Metadata>
239
+ <Comment.Text>
240
+ {' '}
241
+ {comment.text['mime-type'] === 'text/html' ? (
242
+ <div
243
+ dangerouslySetInnerHTML={{
244
+ __html: comment.text.data,
245
+ }}
246
+ />
247
+ ) : (
248
+ comment.text.data
249
+ )}
250
+ </Comment.Text>
251
+ <Comment.Actions>
252
+ {comment.can_reply && (
253
+ <Comment.Action
254
+ as="a"
255
+ aria-label={intl.formatMessage(messages.reply)}
256
+ onClick={() => setReplyTo(comment.comment_id)}
257
+ >
258
+ <FormattedMessage id="Reply" defaultMessage="Reply" />
259
+ </Comment.Action>
260
+ )}
261
+ {comment.is_editable && (
262
+ <Comment.Action
263
+ onClick={() =>
264
+ onEdit({
358
265
  id: flattenToAppURL(comment['@id']),
359
266
  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
- )}
267
+ })
268
+ }
269
+ aria-label={intl.formatMessage(messages.edit)}
270
+ value={{
271
+ id: flattenToAppURL(comment['@id']),
272
+ text: comment.text.data,
273
+ }}
274
+ >
275
+ <FormattedMessage id="Edit" defaultMessage="Edit" />
276
+ </Comment.Action>
277
+ )}
278
+ {comment.is_deletable && (
379
279
  <Comment.Action
380
- as="a"
381
- onClick={() => this.hideReply(comment.comment_id)}
280
+ aria-label={intl.formatMessage(messages.delete)}
281
+ onClick={() => onDelete(flattenToAppURL(comment['@id']))}
282
+ color="red"
382
283
  >
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}
284
+ <Icon name="delete" color="red" />
285
+ <FormattedMessage
286
+ id="Delete"
287
+ defaultMessage="Delete"
288
+ color="red"
289
+ />
403
290
  </Comment.Action>
404
- </Comment.Actions>
405
- <div id={`reply-place-${comment.comment_id}`}></div>
406
- </Comment.Content>
291
+ )}
292
+ <Comment.Action as="a" onClick={() => hideReply(comment.comment_id)}>
293
+ {allCommentsWithCildren[comment.comment_id].children.length > 0 ? (
294
+ collapsedComments[comment.comment_id] ? (
295
+ <>
296
+ <Icon name="eye" color="blue" />
297
+ <FormattedMessage
298
+ id="Show Replies"
299
+ defaultMessage="Show Replies"
300
+ />
301
+ </>
302
+ ) : (
303
+ <>
304
+ <Icon name="minus" color="blue" />
305
+ <FormattedMessage
306
+ id="Hide Replies"
307
+ defaultMessage="Hide Replies"
308
+ />
309
+ </>
310
+ )
311
+ ) : null}
312
+ </Comment.Action>
313
+ </Comment.Actions>
314
+ <div id={`reply-place-${comment.comment_id}`}></div>
315
+ </Comment.Content>
316
+
317
+ {allCommentsWithCildren[comment.comment_id].children.length > 0
318
+ ? allCommentsWithCildren[comment.comment_id].children.map(
319
+ (child, index) => (
320
+ <Comment.Group
321
+ collapsed={collapsedComments[comment.comment_id]}
322
+ key={`group-${index}-${comment.comment_id}`}
323
+ >
324
+ {commentElement(child)}
325
+ </Comment.Group>
326
+ ),
327
+ )
328
+ : null}
329
+ </Comment>
330
+ );
407
331
 
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
- );
332
+ if (!permissions.view_comments) return '';
422
333
 
423
- if (!permissions.view_comments) return '';
334
+ return (
335
+ <Container className="comments">
336
+ <CommentEditModal
337
+ open={showEdit}
338
+ onCancel={onEditCancel}
339
+ onOk={onEditOk}
340
+ id={editId}
341
+ text={editText}
342
+ />
343
+ {permissions.can_reply && (
344
+ <div id="comment-add-id">
345
+ <Form
346
+ onSubmit={onSubmit}
347
+ onCancel={onEditCancel}
348
+ submitLabel={intl.formatMessage(messages.comment)}
349
+ resetAfterSubmit
350
+ schema={makeFormSchema(intl)}
351
+ />
352
+ </div>
353
+ )}
354
+ {/* all comments */}
355
+ <Comment.Group threaded>
356
+ {allPrimaryComments.map((item) => commentElement(item))}
357
+ </Comment.Group>
424
358
 
425
- return (
426
- <Container className="comments">
427
- <CommentEditModal
428
- open={this.state.showEdit}
429
- onCancel={this.onEditCancel}
430
- onOk={this.onEditOk}
431
- id={this.state.editId}
432
- text={this.state.editText}
433
- />
434
- {permissions.can_reply && (
435
- <div id="comment-add-id">
436
- <Form
437
- onSubmit={this.onSubmit}
438
- onCancel={this.onEditCancel}
439
- submitLabel={this.props.intl.formatMessage(messages.comment)}
440
- resetAfterSubmit
441
- schema={makeFormSchema(this.props.intl)}
442
- />
443
- </div>
444
- )}
445
- {/* all comments */}
446
- <Comment.Group threaded>
447
- {allPrimaryComments.map((item) => commentElement(item))}
448
- </Comment.Group>
359
+ {/* load more button */}
360
+ {items_total > items.length && (
361
+ <Button fluid basic color="blue" onClick={loadMoreComments}>
362
+ <FormattedMessage id="Load more" defaultMessage="Load more..." />
363
+ </Button>
364
+ )}
449
365
 
450
- {/* load more button */}
451
- {this.props.items_total > this.props.items.length && (
452
- <Button fluid basic color="blue" onClick={this.loadMoreComments}>
453
- <FormattedMessage id="Load more" defaultMessage="Load more..." />
454
- </Button>
455
- )}
366
+ {replyTo && (
367
+ <Portal
368
+ node={document && document.getElementById(`reply-place-${replyTo}`)}
369
+ >
370
+ <Form
371
+ onSubmit={onSubmit}
372
+ onCancel={onEditCancel}
373
+ submitLabel={intl.formatMessage(messages.comment)}
374
+ resetAfterSubmit
375
+ schema={makeFormSchema(intl)}
376
+ />
377
+ </Portal>
378
+ )}
379
+ </Container>
380
+ );
381
+ };
456
382
 
457
- {this.state.replyTo && (
458
- <Portal
459
- node={
460
- document &&
461
- document.getElementById(`reply-place-${this.state.replyTo}`)
462
- }
463
- >
464
- <Form
465
- onSubmit={this.onSubmit}
466
- onCancel={this.onEditCancel}
467
- submitLabel={this.props.intl.formatMessage(messages.comment)}
468
- resetAfterSubmit
469
- schema={makeFormSchema(this.props.intl)}
470
- />
471
- </Portal>
472
- )}
473
- </Container>
474
- );
475
- }
476
- }
383
+ Comments.propTypes = {
384
+ pathname: PropTypes.string.isRequired,
385
+ };
477
386
 
478
- export default compose(
479
- injectIntl,
480
- injectLazyLibs(['moment']),
481
- connect(
482
- (state) => ({
483
- items: state.comments.items,
484
- next: state.comments.next,
485
- items_total: state.comments.items_total,
486
- permissions: state.comments.permissions || {},
487
- addRequest: state.comments.add,
488
- deleteRequest: state.comments.delete,
489
- }),
490
- { addComment, deleteComment, listComments, listMoreComments },
491
- ),
492
- )(Comments);
387
+ export default compose(injectLazyLibs(['moment']))(Comments);