strapi-plugin-comments 2.0.0-beta.3 → 2.0.0-beta.7

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 (73) hide show
  1. package/README.md +335 -11
  2. package/admin/src/components/DiscussionThread/index.js +9 -2
  3. package/admin/src/components/DiscussionThreadItem/index.js +27 -12
  4. package/admin/src/components/DiscussionThreadItemActions/index.js +61 -50
  5. package/admin/src/components/DiscussionThreadItemActions/styles.js +0 -4
  6. package/admin/src/components/DiscussionThreadItemApprovalFlowActions/index.js +81 -0
  7. package/admin/src/components/DiscussionThreadItemApprovalFlowActions/styles.js +6 -0
  8. package/admin/src/components/DiscussionThreadItemMeta/index.js +16 -5
  9. package/admin/src/components/DiscussionThreadItemWarnings/index.js +16 -10
  10. package/admin/src/components/Nav/index.js +5 -1
  11. package/admin/src/components/ReportsReviewModal/index.js +5 -0
  12. package/admin/src/components/ReportsReviewTable/index.js +60 -52
  13. package/admin/src/components/icons/review.js +2 -2
  14. package/admin/src/index.js +1 -1
  15. package/admin/src/pages/Details/components/DetailsEntity/index.js +66 -0
  16. package/admin/src/pages/Details/components/DetailsFilters/index.js +37 -0
  17. package/admin/src/pages/Details/components/DetailsFilters/styles.js +8 -0
  18. package/admin/src/pages/Details/index.js +75 -42
  19. package/admin/src/pages/Discover/components/DiscoverTableRow/index.js +69 -48
  20. package/admin/src/pages/Discover/components/DiscoverTableRow/styles.js +8 -0
  21. package/admin/src/pages/Discover/index.js +72 -56
  22. package/admin/src/pages/{Details/utils → utils}/api.js +15 -3
  23. package/admin/src/permissions.js +10 -8
  24. package/admin/src/translations/en.json +17 -4
  25. package/admin/src/utils/constants.js +3 -6
  26. package/admin/src/utils/getMessage.js +1 -1
  27. package/admin/src/utils/getRelatedGroups.js +1 -3
  28. package/admin/src/utils/index.js +6 -2
  29. package/admin/src/utils/renderEntryTitle.js +14 -0
  30. package/admin/src/utils/renderInitials.js +5 -0
  31. package/admin/src/utils/resolveCommentStatus.js +30 -0
  32. package/admin/src/utils/resolveCommentStatusColor.js +22 -0
  33. package/package.json +8 -3
  34. package/permissions.js +19 -0
  35. package/server/bootstrap.js +38 -2
  36. package/server/config/index.js +9 -3
  37. package/server/controllers/admin.js +3 -3
  38. package/server/controllers/client.js +4 -18
  39. package/server/controllers/utils/parsers.js +17 -0
  40. package/server/graphql/index.js +26 -0
  41. package/server/graphql/mutations/createAbuseReport.js +20 -0
  42. package/server/graphql/mutations/createComment.js +20 -0
  43. package/server/graphql/mutations/index.js +21 -0
  44. package/server/graphql/mutations/removeComment.js +20 -0
  45. package/server/graphql/mutations/updateComment.js +20 -0
  46. package/server/graphql/queries/findAllFlat.js +29 -0
  47. package/server/graphql/queries/findAllInHierarchy.js +31 -0
  48. package/server/graphql/queries/index.js +19 -0
  49. package/server/graphql/resolvers-config.js +10 -0
  50. package/server/graphql/types/comment-author-user.js +9 -0
  51. package/server/graphql/types/comment-author.js +9 -0
  52. package/server/graphql/types/comment-nested.js +16 -0
  53. package/server/graphql/types/comment.js +16 -0
  54. package/server/graphql/types/create-comment-author.js +9 -0
  55. package/server/graphql/types/create-comment.js +9 -0
  56. package/server/graphql/types/create-report.js +9 -0
  57. package/server/graphql/types/id.js +24 -0
  58. package/server/graphql/types/identify-comment-author.js +6 -0
  59. package/server/graphql/types/index.js +17 -0
  60. package/server/graphql/types/remove-comment.js +8 -0
  61. package/server/graphql/types/report-reason.js +10 -0
  62. package/server/graphql/types/report.js +11 -0
  63. package/server/graphql/types/update-comment.js +9 -0
  64. package/server/register.js +3 -1
  65. package/server/services/admin.js +42 -15
  66. package/server/services/client.js +54 -38
  67. package/server/services/common.js +38 -67
  68. package/server/services/graphql.js +190 -0
  69. package/server/services/index.js +2 -0
  70. package/server/services/utils/functions.js +24 -17
  71. package/server/utils/constants.js +3 -3
  72. package/strapi-server.js +33 -0
  73. package/admin/src/components/EntityDetails/index.js +0 -53
package/README.md CHANGED
@@ -50,9 +50,16 @@ Enjoy 🎉
50
50
 
51
51
  Complete installation requirements are exact same as for Strapi itself and can be found in the documentation under [Installation Requirements](https://docs.strapi.io/developer-docs/latest/getting-started/introduction.html).
52
52
 
53
+ **Minimum environment requirements**
54
+ - Node.js `>=14.x.x`
55
+ - NPM `>=7.x.x`
56
+
57
+ In our minimum support we're following [official Node.js releases timelines](https://nodejs.org/en/about/releases/).
58
+
53
59
  **Supported Strapi versions**:
54
60
 
55
- - Strapi v4.0.2 (recently tested)
61
+ - Strapi v4.0.5 (recently tested)
62
+ - Strapi v4.x
56
63
 
57
64
  (This plugin is not working with v3.x and not may work with the older Strapi v4 versions, but these are not tested nor officially supported at this time.)
58
65
 
@@ -76,10 +83,14 @@ To setup amend default plugin configuration we recommend to put following snippe
76
83
  comments: {
77
84
  badWords: false,
78
85
  moderatorRoles: ["Authenticated"],
86
+ approvalFlow: ['api::page.page'],
79
87
  entryLabel: {
80
88
  '*': ['Title', 'title', 'Name', 'name', 'Subject', 'subject'],
81
89
  'api::page.page': ['MyField'],
82
90
  },
91
+ reportReasons: {
92
+ 'MY_CUSTOM_REASON': 'MY_CUSTOM_REASON',
93
+ },
83
94
  },
84
95
  ...
85
96
  ```
@@ -87,11 +98,28 @@ To setup amend default plugin configuration we recommend to put following snippe
87
98
  ### Properties
88
99
  - `badWords` - Enabled support for [bad words filtering](https://www.npmjs.com/package/bad-words). Can be turned off or overwritten using [options reference](https://www.npmjs.com/package/bad-words#constructor). Default value: `true`.
89
100
  - `moderatorRoles` - Optional list of names of roles. Users with those roles will be notified by email when a new abuse report is created. This feature requires a built-in [Strapi email plugin](https://docs.strapi.io/developer-docs/latest/plugins/email.html) configured.
90
- - `entryLabel` - ordered list of property names per Content Type to generate related entity label. Default set as `*`.
101
+ - `approvalFlow` - list of Content Types which are supporting approval flow. Values must be in format like `'api::<collection name>.<content type name>'`. For not included, posted comments are going to be immediately visible.
102
+ - `entryLabel` - ordered list of property names per Content Type to generate related entity label. Keys must be in format like `'api::<collection name>.<content type name>'`. Default formatting set as `*`.
103
+ - `reportReasons` - set of enums you would like to use for issuing abuse reports. Provided by default `'BAD_LANGUAGE'`, `'DISCRIMINATION'` and `'OTHER'`.
91
104
 
92
105
  ## Additional GQL Configuration
106
+ All you need to do is to install and enable `@strapi/plugin-graphql` for you instance based on the **[official Strapi v4 docs](https://docs.strapi.io/developer-docs/latest/plugins/graphql.html#configurations)**.
107
+
108
+ See [available GQL specification section](#public-graphql-specification).
109
+
110
+ ## RBAC
111
+ Plugin provides granular permissions based on Strapi RBAC functionality.
112
+
113
+ ### Mandatory permissions
114
+ For any role different than **Super Admin**, to access the **Comments panel** you must set following permissions:
115
+ - _Plugins_ -> _Content-type-builder_ -> _Read_ - gives you ability to fetch Content Type schema
116
+ - _Plugins_ -> _Comments_ -> _Comments: Read_ - gives you the basic read access to **Comments Panel**
93
117
 
94
- > To be done
118
+ ### Optional permissions
119
+ Feature / Capability focused permissions:
120
+ - _Plugins_ -> _Comments_ -> _Comments: Moderate_ - allows you to block, unblock, approve &amp; reject comments
121
+ - _Plugins_ -> _Comments_ -> _Reports: Read_ - allows you to see the list of issued abuse reports against comments
122
+ - _Plugins_ -> _Comments_ -> _Reports: Moderate_ - allows you to review (resolve) issued abuse reports against comments
95
123
 
96
124
  ## Public API Comment model
97
125
 
@@ -104,10 +132,14 @@ To setup amend default plugin configuration we recommend to put following snippe
104
132
  "blockedThread": true,
105
133
  "blockReason": null,
106
134
  "authorUser": null,
107
- "authorId": "207ccfdc-94ba-45eb-979c-790f6f49c392",
108
- "authorName": "Joe Doe",
109
- "authorEmail": "jdoe@sample.com",
110
- "authorAvatar": null,
135
+ "removed": null,
136
+ "approvalStatus": "APPROVED", // Only in case of enabled approval flow. Default: null
137
+ "author": {
138
+ "id": "207ccfdc-94ba-45eb-979c-790f6f49c392",
139
+ "name": "Joe Doe",
140
+ "email": "jdoe@sample.com",
141
+ "avatar": null,
142
+ },
111
143
  "createdAt": "2020-07-14T20:13:01.649Z",
112
144
  "updatedAt": "2020-07-14T20:13:01.670Z",
113
145
  "related": {}, // Related content type entity
@@ -122,6 +154,8 @@ To setup amend default plugin configuration we recommend to put following snippe
122
154
  "blocked": true,
123
155
  "blockedThread": null,
124
156
  "blockReason": null,
157
+ "removed": null,
158
+ "approvalStatus": "REJECTED", // Only in case of enabled approval flow. Default: null
125
159
  "authorUser": {
126
160
  "id": 1,
127
161
  "username": "Sample User",
@@ -220,7 +254,6 @@ Posts a Comment related to specified instance of Content Type like for example `
220
254
  },
221
255
  "content": "My sample response",
222
256
  "threadOf": 2, // id of comment we would like to start / continue the thread (Optional)
223
- "related": "api::page.page:1"
224
257
  }
225
258
  ```
226
259
 
@@ -230,7 +263,6 @@ Posts a Comment related to specified instance of Content Type like for example `
230
263
  "authorUser": 1, // id of a author user. Optional in case of 'enableUsers: true' in the plugin configuration
231
264
  "content": "My sample response",
232
265
  "threadOf": 2, // id of comment we would like to start / continue the thread (Optional)
233
- "related": "api::page.page:1"
234
266
  }
235
267
  ```
236
268
 
@@ -308,7 +340,7 @@ Deletes a specified Comment based on it `commentId` and related to specified ins
308
340
  - `409` - Conflict. Occurs when trying to delete a non existing comment.
309
341
 
310
342
 
311
- ### Report abuse in the Comment
343
+ ### Issue Abuse Report against specified Comment
312
344
 
313
345
  `POST <host>/comments/api::<collection name>.<content type name>:<entity id>/comment/<commentId>/report-abuse`
314
346
 
@@ -325,7 +357,7 @@ Reports abuse in specified Comment content based on it `commentId` and related t
325
357
  }
326
358
  ```
327
359
 
328
- *Available reason enums:* `OTHER`, `BAD_WORDS`, `DISCRIMINATION`
360
+ *Available reason enums:* `BAD_WORDS`, `OTHER`, `DISCRIMINATION` (want more? See [configuration section](#configuration).)
329
361
 
330
362
  **Example response body**
331
363
 
@@ -339,6 +371,298 @@ Reports abuse in specified Comment content based on it `commentId` and related t
339
371
  - `200` - Successful. Response with reported abuse.
340
372
  - `409` - Conflict. Occurs when trying to report an abuse to a non existing comment.
341
373
 
374
+ ## Public GraphQL specification
375
+
376
+ > *To test all queries and understand the schemas use GraphQL Playground exposed by `@strapi/plugin-graphql` on `http://localhost:1337/graphql`*
377
+
378
+ ### Get Comments
379
+
380
+ *REST API equivalent: [Public API -> Get Comments](#get-comments)*
381
+
382
+ **Example request**
383
+
384
+ ```graphql
385
+ query {
386
+ findAllFlat(
387
+ relation: "api::page.page:1"
388
+ filters: { content: { contains: "Test" } }
389
+ ) {
390
+ id
391
+ content
392
+ blocked
393
+ threadOf {
394
+ id
395
+ }
396
+ author {
397
+ id
398
+ name
399
+ }
400
+ }
401
+ }
402
+ ```
403
+
404
+ **Example response**
405
+
406
+ ```json
407
+ {
408
+ "data": {
409
+ "findAllFlat": [
410
+ {
411
+ "id": 3,
412
+ "content": "Test",
413
+ "blocked": false,
414
+ "threadOf": null,
415
+ "author": {
416
+ "id": "123456",
417
+ "name": "Joe Doe"
418
+ }
419
+ },
420
+ //...
421
+ ]
422
+ }
423
+ ```
424
+
425
+ ### Get Comments (flat structure)
426
+
427
+ *REST API equivalent: [Public API -> Get Comments (flat structure)](#get-comments-flat-structure)*
428
+
429
+ **Example request**
430
+
431
+ ```graphql
432
+ query {
433
+ findAllInHierarchy(relation: "api::page.page:1") {
434
+ id
435
+ content
436
+ blocked
437
+ children {
438
+ id
439
+ content
440
+ }
441
+ threadOf {
442
+ id
443
+ }
444
+ author {
445
+ id
446
+ name
447
+ }
448
+ }
449
+ }
450
+ ```
451
+
452
+ **Example response**
453
+
454
+ ```json
455
+ {
456
+ "data": {
457
+ "findAllInHierarchy": [
458
+ {
459
+ "id": 1,
460
+ "content": "Test",
461
+ "blocked": false,
462
+ "children": [
463
+ {
464
+ "id": 6,
465
+ "content": "Text to search for"
466
+ },
467
+ //...
468
+ ],
469
+ "threadOf": null,
470
+ "author": {
471
+ "id": "123456",
472
+ "name": "Joe Doe"
473
+ }
474
+ },
475
+ //...
476
+ ]
477
+ }
478
+ }
479
+ ```
480
+
481
+ ### Post a Comment
482
+
483
+ *REST API equivalent: [Public API -> Post a Comment](#post-a-comment)*
484
+
485
+ **Example request**
486
+
487
+ ```graphql
488
+ mutation createComment {
489
+ createComment(
490
+ input: {
491
+ relation: "api::page.page:1"
492
+ content: "Hello World!"
493
+ threadOf: 3
494
+ author: { id: "12345678", name: "John Wick", email: "test@test.pl" }
495
+ }
496
+ ) {
497
+ id
498
+ content
499
+ threadOf {
500
+ id
501
+ }
502
+ author {
503
+ id
504
+ name
505
+ }
506
+ }
507
+ }
508
+ ```
509
+
510
+ **Example response**
511
+
512
+ ```json
513
+ {
514
+ "data": {
515
+ "createComment": {
516
+ "id": 34,
517
+ "content": "Hello World!",
518
+ "threadOf": {
519
+ "id": 3
520
+ },
521
+ "author": {
522
+ "id": "12345678",
523
+ "name": "John Wick"
524
+ }
525
+ }
526
+ }
527
+ }
528
+ ```
529
+
530
+ ### Update Comment
531
+
532
+ *REST API equivalent: [Public API -> Update Comment](#update-comment)*
533
+
534
+ **Example request**
535
+
536
+ ```graphql
537
+ mutation updateComment {
538
+ updateComment(
539
+ input: {
540
+ id: 34
541
+ relation: "api::page.page:1"
542
+ content: "I've changed it!"
543
+ author: { id: "12345678" }
544
+ }
545
+ ) {
546
+ id
547
+ content
548
+ threadOf {
549
+ id
550
+ }
551
+ author {
552
+ id
553
+ name
554
+ }
555
+ createdAt
556
+ updatedAt
557
+ }
558
+ }
559
+ ```
560
+
561
+ **Example response**
562
+
563
+ ```json
564
+ {
565
+ "data": {
566
+ "updateComment": {
567
+ "id": 34,
568
+ "content": "I've changed it!",
569
+ "threadOf": {
570
+ "id": 3
571
+ },
572
+ "author": {
573
+ "id": "12345678",
574
+ "name": "John Wick"
575
+ },
576
+ "createdAt": "2022-01-26T07:45:35.978Z",
577
+ "updatedAt": "2022-01-26T07:47:44.659Z"
578
+ }
579
+ }
580
+ }
581
+ ```
582
+
583
+ ### Delete Comment
584
+
585
+ *REST API equivalent: [Public API -> Delete Comment](#delete-comment)*
586
+
587
+ **Example request**
588
+
589
+ ```graphql
590
+ mutation removeComment {
591
+ removeComment(
592
+ input: { id: 33, relation: "api::page.page:1", author: { id: "12345678" } }
593
+ ) {
594
+ id
595
+ removed
596
+ }
597
+ }
598
+ ```
599
+
600
+ **Example response**
601
+
602
+ ```json
603
+ {
604
+ "data": {
605
+ "removeComment": {
606
+ "id": 33,
607
+ "removed": true
608
+ }
609
+ }
610
+ }
611
+ ```
612
+
613
+ ### Issue Abuse Report against specified Comment
614
+
615
+ *REST API equivalent: [Public API -> Issue Abuse Report against specified Comment](#issue-abuse-report-against-specified-comment)*
616
+
617
+ **Example request body**
618
+
619
+ ```graphql
620
+ mutation createAbuseReport {
621
+ createAbuseReport(
622
+ input: {
623
+ commentId: 34
624
+ relation: "api::page.page:1"
625
+ reason: BAD_LANGUAGE
626
+ content: "Rude language"
627
+ }
628
+ ) {
629
+ id
630
+ reason
631
+ content
632
+ related {
633
+ id
634
+ author {
635
+ id
636
+ name
637
+ }
638
+ }
639
+ }
640
+ }
641
+ ```
642
+
643
+ *Available reason enums:* `BAD_WORDS`, `OTHER`, `DISCRIMINATION` (want more? See [configuration section](#configuration).)
644
+
645
+ **Example response**
646
+
647
+ ```json
648
+ {
649
+ "data": {
650
+ "createAbuseReport": {
651
+ "id": 28,
652
+ "content": "Rude language",
653
+ "reason": "DISCRIMINATION",
654
+ "related": {
655
+ "id": 34,
656
+ "author": {
657
+ "id": "12345678",
658
+ "name": "John Wick"
659
+ }
660
+ }
661
+ }
662
+ }
663
+ }
664
+ ```
665
+
342
666
  ## Examples
343
667
 
344
668
  Live example of plugin usage can be found in the [VirtusLab Strapi Examples](https://github.com/VirtusLab/strapi-examples/tree/master/strapi-plugin-comments) repository.
@@ -18,7 +18,7 @@ import { getMessage, getUrl } from '../../utils';
18
18
  import LoadingIndicatorOverlay from '../LoadingIndicatorOverlay';
19
19
  import { LoadingIndicatorContainer } from '../LoadingIndicatorOverlay/styles';
20
20
 
21
- const DiscussionThread = ({ level = [], selected = {}, isReloading }) => {
21
+ const DiscussionThread = ({ level = [], selected = {}, allowedActions, isReloading }) => {
22
22
  const rootThread = selected?.threadOf;
23
23
  return (<LoadingIndicatorContainer as={Box} padding={4}>
24
24
  { isReloading && <LoadingIndicatorOverlay />}
@@ -34,12 +34,14 @@ const DiscussionThread = ({ level = [], selected = {}, isReloading }) => {
34
34
  <Divider />
35
35
  </Box>
36
36
  <Flex as="ul" direction="column" alignItems="flex-start">
37
- { rootThread && (<DiscussionThreadItem {...rootThread} isThreadAuthor root pinned />) }
37
+ { rootThread && (<DiscussionThreadItem {...rootThread} allowedActions={allowedActions} isThreadAuthor root pinned />) }
38
38
  { level.map(item => {
39
39
  const isSelected = selected.id === item.id;
40
40
  const isThreadAuthor = !isNil(selected?.threadOf?.authorId) && (selected?.threadOf?.authorId === item.authorId);
41
41
  return (<DiscussionThreadItem
42
+ key={`comment-${item.id}`}
42
43
  {...item}
44
+ allowedActions={allowedActions}
43
45
  root={isNil(rootThread)}
44
46
  blockedThread={rootThread?.blockedThread || item.blockedThread}
45
47
  isSelected={isSelected}
@@ -53,6 +55,11 @@ const DiscussionThread = ({ level = [], selected = {}, isReloading }) => {
53
55
  DiscussionThread.propTypes = {
54
56
  level: PropTypes.array.isRequired,
55
57
  selected: PropTypes.object.isRequired,
58
+ allowedActions: PropTypes.shape({
59
+ canModerate: PropTypes.bool,
60
+ canAccessReports: PropTypes.bool,
61
+ canReviewReports: PropTypes.bool,
62
+ }),
56
63
  isReloading: PropTypes.bool,
57
64
  };
58
65
 
@@ -6,7 +6,6 @@
6
6
 
7
7
  import React from 'react';
8
8
  import PropTypes from 'prop-types';
9
- import { first } from 'lodash';
10
9
  import { Avatar, Initials } from '@strapi/design-system/Avatar';
11
10
  import { Box } from '@strapi/design-system/Box';
12
11
  import { Divider } from '@strapi/design-system/Divider';
@@ -17,11 +16,24 @@ import { ArrowRight } from '@strapi/icons';
17
16
  import DiscussionThreadItemMeta from '../DiscussionThreadItemMeta';
18
17
  import { AvatarWrapper, DiscussionThreadItemBox, DiscussionThreadItemBoxContent, DiscussionThreadItemContent, DiscussionThreadItemLeft, DiscussionThreadItemLinkBox, DiscussionThreadItemRight, DiscussionThreadItemRoot } from './styles';
19
18
  import DiscussionThreadItemActions from '../DiscussionThreadItemActions';
20
- import { getMessage, getUrl } from '../../utils';
19
+ import { getMessage, getUrl, renderInitials } from '../../utils';
21
20
  import DiscussionThreadItemWarnings from '../DiscussionThreadItemWarnings';
22
21
 
23
- const DiscussionThreadItem = (item) => {
24
- const { id, isSelected, isThreadAuthor, content, root, preview, pinned, blocked, blockedThread, gotThread, threadFirstItemId, authorUser, authorAvatar, authorName } = item;
22
+ const DiscussionThreadItem = (props) => {
23
+ const {
24
+ isSelected,
25
+ isThreadAuthor,
26
+ content,
27
+ root,
28
+ preview,
29
+ pinned,
30
+ blocked,
31
+ removed,
32
+ blockedThread,
33
+ gotThread,
34
+ threadFirstItemId,
35
+ authorUser,
36
+ author } = props;
25
37
 
26
38
  let background = 'neutral100';
27
39
  let borderColor = 'neutral200';
@@ -45,11 +57,9 @@ const DiscussionThreadItem = (item) => {
45
57
  StyledComponent = DiscussionThreadItemRoot;
46
58
  }
47
59
 
48
- const avatar = authorAvatar || authorUser?.avatar;
49
- const name = authorName || `${authorUser?.firstName} ${authorUser?.lastName}`;
60
+ const avatar = author?.avatar || authorUser?.avatar;
61
+ const name = author?.name || `${authorUser?.firstName} ${authorUser?.lastName}`;
50
62
  const isBlocked = blocked || blockedThread;
51
-
52
- const renderInitials = value => value.split(' ').map(_ => first(_)).join('').toUpperCase();
53
63
 
54
64
  return (<StyledComponent as="li">
55
65
  <DiscussionThreadItemContent as={Flex} direction="row">
@@ -62,16 +72,16 @@ const DiscussionThreadItem = (item) => {
62
72
  <Box as={Flex} grow={1} alignItems="center">
63
73
  <Typography variant="omega">{ content }</Typography>
64
74
  </Box>
65
- { !preview && (<DiscussionThreadItemActions { ...item } root={root || pinned} />) }
75
+ { !preview && (<DiscussionThreadItemActions { ...props } root={root || pinned} />) }
66
76
  </DiscussionThreadItemBoxContent>
67
- { !(preview || isBlocked) && (<DiscussionThreadItemWarnings { ...item } gotThread={gotThread || root} />) }
77
+ { !(preview || isBlocked || removed) && (<DiscussionThreadItemWarnings { ...props } gotThread={gotThread || root} />) }
68
78
  </Flex>
69
79
  <Divider background={dividerColor} />
70
- <DiscussionThreadItemMeta { ...item } />
80
+ <DiscussionThreadItemMeta { ...props } />
71
81
  </DiscussionThreadItemBox>
72
82
  </DiscussionThreadItemContent>
73
83
 
74
- { (gotThread && !(preview || pinned)) && (<DiscussionThreadItemLinkBox direction="column" alignItems="flex-end">
84
+ { (gotThread && !(removed || preview || pinned)) && (<DiscussionThreadItemLinkBox direction="column" alignItems="flex-end">
75
85
  <Link to={getUrl(`discover/${threadFirstItemId}`)} endIcon={<ArrowRight />}>
76
86
  { getMessage('page.details.panel.discussion.nav.drilldown') }
77
87
  </Link>
@@ -81,6 +91,11 @@ const DiscussionThreadItem = (item) => {
81
91
 
82
92
  DiscussionThreadItem.propTypes = {
83
93
  content: PropTypes.string.isRequired,
94
+ allowedActions: PropTypes.shape({
95
+ canModerate: PropTypes.bool,
96
+ canAccessReports: PropTypes.bool,
97
+ canReviewReports: PropTypes.bool,
98
+ }),
84
99
  };
85
100
 
86
101
  export default DiscussionThreadItem;