decidim-comments 0.21.0 → 0.23.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (118) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/javascripts/decidim/comments/bundle.js +67 -67
  3. data/app/assets/javascripts/decidim/comments/bundle.js.map +1 -1
  4. data/app/cells/decidim/comments/comment_activity_cell.rb +2 -22
  5. data/app/cells/decidim/comments/comment_cell.rb +22 -0
  6. data/app/cells/decidim/comments/comment_m/footer.erb +5 -0
  7. data/app/cells/decidim/comments/comment_m/top.erb +7 -0
  8. data/app/cells/decidim/comments/comment_m_cell.rb +29 -0
  9. data/app/commands/decidim/comments/create_comment.rb +8 -8
  10. data/app/events/decidim/comments/comment_by_followed_user_group_event.rb +9 -0
  11. data/app/events/decidim/comments/comment_event.rb +14 -9
  12. data/app/events/decidim/comments/user_group_mentioned_event.rb +10 -0
  13. data/app/forms/decidim/comments/comment_form.rb +17 -1
  14. data/app/frontend/application/icon.component.tsx +16 -4
  15. data/app/frontend/comments/add_comment_form.component.test.tsx +31 -29
  16. data/app/frontend/comments/add_comment_form.component.tsx +34 -18
  17. data/app/frontend/comments/comment.component.test.tsx +36 -5
  18. data/app/frontend/comments/comment.component.tsx +218 -88
  19. data/app/frontend/comments/comment_order_selector.component.tsx +26 -7
  20. data/app/frontend/comments/comment_thread.component.test.tsx +9 -8
  21. data/app/frontend/comments/comment_thread.component.tsx +3 -1
  22. data/app/frontend/comments/comments.component.test.tsx +17 -14
  23. data/app/frontend/comments/comments.component.tsx +28 -4
  24. data/app/frontend/comments/down_vote_button.component.tsx +27 -9
  25. data/app/frontend/comments/up_vote_button.component.tsx +27 -9
  26. data/app/frontend/comments/vote_button.component.tsx +4 -0
  27. data/app/frontend/comments/vote_button_component.test.tsx +14 -8
  28. data/app/frontend/mutations/add_comment.mutation.graphql +2 -2
  29. data/app/frontend/mutations/down_vote.mutation.graphql +2 -2
  30. data/app/frontend/mutations/up_vote.mutation.graphql +2 -2
  31. data/app/frontend/queries/comments.query.graphql +2 -2
  32. data/app/frontend/support/schema.ts +1060 -735
  33. data/app/helpers/decidim/comments/comment_cells_helper.rb +33 -0
  34. data/app/models/decidim/comments/comment.rb +81 -22
  35. data/app/models/decidim/comments/seed.rb +1 -1
  36. data/app/queries/decidim/comments/metrics/comments_metric_manage.rb +1 -6
  37. data/app/services/decidim/comments/new_comment_notification_creator.rb +28 -3
  38. data/app/types/decidim/comments/commentable_interface.rb +1 -1
  39. data/app/types/decidim/comments/commentable_mutation_type.rb +6 -3
  40. data/config/locales/am-ET.yml +1 -0
  41. data/config/locales/ar.yml +4 -1
  42. data/config/locales/bg-BG.yml +6 -0
  43. data/config/locales/bg.yml +6 -0
  44. data/config/locales/ca.yml +19 -2
  45. data/config/locales/cs.yml +31 -14
  46. data/config/locales/da-DK.yml +1 -0
  47. data/config/locales/da.yml +1 -0
  48. data/config/locales/de.yml +47 -24
  49. data/config/locales/el.yml +121 -0
  50. data/config/locales/en.yml +18 -1
  51. data/config/locales/eo.yml +1 -0
  52. data/config/locales/es-MX.yml +18 -1
  53. data/config/locales/es-PY.yml +18 -1
  54. data/config/locales/es.yml +18 -1
  55. data/config/locales/et-EE.yml +1 -0
  56. data/config/locales/et.yml +1 -0
  57. data/config/locales/eu.yml +4 -1
  58. data/config/locales/fi-plain.yml +18 -1
  59. data/config/locales/fi.yml +25 -8
  60. data/config/locales/fr-CA.yml +123 -0
  61. data/config/locales/fr.yml +25 -2
  62. data/config/locales/ga-IE.yml +1 -0
  63. data/config/locales/gl.yml +17 -1
  64. data/config/locales/hr-HR.yml +1 -0
  65. data/config/locales/hr.yml +1 -0
  66. data/config/locales/hu.yml +12 -2
  67. data/config/locales/id-ID.yml +4 -1
  68. data/config/locales/is-IS.yml +3 -3
  69. data/config/locales/is.yml +76 -0
  70. data/config/locales/it.yml +21 -1
  71. data/config/locales/ja-JP.yml +120 -0
  72. data/config/locales/ja.yml +121 -0
  73. data/config/locales/ko-KR.yml +1 -0
  74. data/config/locales/ko.yml +1 -0
  75. data/config/locales/lt-LT.yml +1 -0
  76. data/config/locales/lt.yml +1 -0
  77. data/config/locales/lv.yml +118 -0
  78. data/config/locales/mt-MT.yml +1 -0
  79. data/config/locales/mt.yml +1 -0
  80. data/config/locales/nl.yml +27 -8
  81. data/config/locales/no.yml +18 -2
  82. data/config/locales/om-ET.yml +1 -0
  83. data/config/locales/pl.yml +63 -40
  84. data/config/locales/pt-BR.yml +5 -2
  85. data/config/locales/pt.yml +47 -25
  86. data/config/locales/ro-RO.yml +124 -0
  87. data/config/locales/ru.yml +4 -1
  88. data/config/locales/si-LK.yml +1 -0
  89. data/config/locales/sk-SK.yml +116 -0
  90. data/config/locales/sk.yml +120 -0
  91. data/config/locales/sl.yml +4 -0
  92. data/config/locales/so-SO.yml +1 -0
  93. data/config/locales/sr-CS.yml +20 -0
  94. data/config/locales/sv.yml +26 -3
  95. data/config/locales/sw-KE.yml +1 -0
  96. data/config/locales/ti-ER.yml +1 -0
  97. data/config/locales/tr-TR.yml +54 -31
  98. data/config/locales/uk.yml +4 -2
  99. data/config/locales/vi-VN.yml +1 -0
  100. data/config/locales/vi.yml +1 -0
  101. data/config/locales/zh-CN.yml +121 -0
  102. data/config/locales/zh-TW.yml +1 -0
  103. data/db/migrate/20200320105911_index_foreign_keys_in_decidim_comments_comments.rb +7 -0
  104. data/db/migrate/20200706123136_make_comments_handle_i18n.rb +41 -0
  105. data/db/migrate/20200828101910_add_commentable_counter_cache_to_comments.rb +9 -0
  106. data/lib/decidim/comments/api/comment_type.rb +5 -1
  107. data/lib/decidim/comments/comment_serializer.rb +7 -2
  108. data/lib/decidim/comments/comment_vote_serializer.rb +5 -1
  109. data/lib/decidim/comments/commentable.rb +11 -0
  110. data/lib/decidim/comments/comments_helper.rb +28 -4
  111. data/lib/decidim/comments/engine.rb +13 -0
  112. data/lib/decidim/comments/mutation_extensions.rb +8 -0
  113. data/lib/decidim/comments/query_extensions.rb +4 -0
  114. data/lib/decidim/comments/test/factories.rb +10 -1
  115. data/lib/decidim/comments/test/shared_examples/comment_event.rb +12 -2
  116. data/lib/decidim/comments/test/shared_examples/create_comment_context.rb +3 -2
  117. data/lib/decidim/comments/version.rb +1 -1
  118. metadata +59 -13
@@ -1,8 +1,8 @@
1
- import { mount, shallow } from "enzyme";
1
+ import {mount, shallow} from "enzyme";
2
2
  import * as $ from "jquery";
3
3
  import * as React from "react";
4
4
 
5
- import { CommentFragment } from "../support/schema";
5
+ import {CommentFragment} from "../support/schema";
6
6
  import AddCommentForm from "./add_comment_form.component";
7
7
  import Comment from "./comment.component";
8
8
  import DownVoteButton from "./down_vote_button.component";
@@ -11,13 +11,15 @@ import UpVoteButton from "./up_vote_button.component";
11
11
  import generateCommentsData from "../support/generate_comments_data";
12
12
  import generateUserData from "../support/generate_user_data";
13
13
 
14
- import { loadLocaleTranslations } from "../support/load_translations";
14
+ import {loadLocaleTranslations} from "../support/load_translations";
15
15
 
16
16
  describe("<Comment />", () => {
17
+ const commentsMaxLength: number = 1000;
17
18
  const orderBy = "older";
18
19
  const rootCommentable = {
19
20
  id: "1",
20
- type: "Decidim::DummyResources::DummyResource"
21
+ type: "Decidim::DummyResources::DummyResource",
22
+ commentsMaxLength: 1000
21
23
  };
22
24
  let comment: CommentFragment;
23
25
  let session: any = null;
@@ -48,9 +50,10 @@ describe("<Comment />", () => {
48
50
  session={session}
49
51
  rootCommentable={rootCommentable}
50
52
  orderBy={orderBy}
53
+ commentsMaxLength={commentsMaxLength}
51
54
  />
52
55
  );
53
- expect(wrapper.find("article.comment").exists()).toBeTruthy();
56
+ expect(wrapper.find(".comment").exists()).toBeTruthy();
54
57
  });
55
58
 
56
59
  it("should render a time tag with comment's created at", () => {
@@ -60,6 +63,7 @@ describe("<Comment />", () => {
60
63
  session={session}
61
64
  rootCommentable={rootCommentable}
62
65
  orderBy={orderBy}
66
+ commentsMaxLength={commentsMaxLength}
63
67
  />
64
68
  );
65
69
  expect(wrapper.find("time").prop("dateTime")).toEqual(comment.createdAt);
@@ -72,6 +76,7 @@ describe("<Comment />", () => {
72
76
  session={session}
73
77
  rootCommentable={rootCommentable}
74
78
  orderBy={orderBy}
79
+ commentsMaxLength={commentsMaxLength}
75
80
  />
76
81
  );
77
82
  expect(wrapper.find("span.author__name").text()).toEqual(
@@ -86,6 +91,7 @@ describe("<Comment />", () => {
86
91
  session={session}
87
92
  rootCommentable={rootCommentable}
88
93
  orderBy={orderBy}
94
+ commentsMaxLength={commentsMaxLength}
89
95
  />
90
96
  );
91
97
  expect(wrapper.find("span.author__nickname").text()).toEqual(
@@ -105,6 +111,7 @@ describe("<Comment />", () => {
105
111
  session={session}
106
112
  rootCommentable={rootCommentable}
107
113
  orderBy={orderBy}
114
+ commentsMaxLength={commentsMaxLength}
108
115
  />
109
116
  );
110
117
  expect(
@@ -120,6 +127,7 @@ describe("<Comment />", () => {
120
127
  session={session}
121
128
  rootCommentable={rootCommentable}
122
129
  orderBy={orderBy}
130
+ commentsMaxLength={commentsMaxLength}
123
131
  />
124
132
  );
125
133
  expect(wrapper.find(".author__avatar img").prop("src")).toEqual(
@@ -134,6 +142,7 @@ describe("<Comment />", () => {
134
142
  session={session}
135
143
  rootCommentable={rootCommentable}
136
144
  orderBy={orderBy}
145
+ commentsMaxLength={commentsMaxLength}
137
146
  />
138
147
  );
139
148
  expect(wrapper.find("div.comment__content").html()).toContain(
@@ -148,6 +157,7 @@ describe("<Comment />", () => {
148
157
  session={session}
149
158
  rootCommentable={rootCommentable}
150
159
  orderBy={orderBy}
160
+ commentsMaxLength={commentsMaxLength}
151
161
  />
152
162
  );
153
163
  expect(wrapper.state()).toHaveProperty("showReplyForm", false);
@@ -160,6 +170,7 @@ describe("<Comment />", () => {
160
170
  session={session}
161
171
  rootCommentable={rootCommentable}
162
172
  orderBy={orderBy}
173
+ commentsMaxLength={commentsMaxLength}
163
174
  />
164
175
  );
165
176
  expect(wrapper.find(AddCommentForm).exists()).toBeFalsy();
@@ -181,6 +192,7 @@ describe("<Comment />", () => {
181
192
  isRootComment={true}
182
193
  rootCommentable={rootCommentable}
183
194
  orderBy={orderBy}
195
+ commentsMaxLength={commentsMaxLength}
184
196
  />
185
197
  );
186
198
  expect(wrapper.find("div.comment__additionalreply").exists()).toBeFalsy();
@@ -194,6 +206,7 @@ describe("<Comment />", () => {
194
206
  session={session}
195
207
  rootCommentable={rootCommentable}
196
208
  orderBy={orderBy}
209
+ commentsMaxLength={commentsMaxLength}
197
210
  />
198
211
  );
199
212
  expect(wrapper.find("div.comment__additionalreply").exists()).toBeFalsy();
@@ -208,6 +221,7 @@ describe("<Comment />", () => {
208
221
  isRootComment={true}
209
222
  rootCommentable={rootCommentable}
210
223
  orderBy={orderBy}
224
+ commentsMaxLength={commentsMaxLength}
211
225
  />
212
226
  );
213
227
  expect(wrapper.find("div.comment__additionalreply").exists()).toBeTruthy();
@@ -221,6 +235,7 @@ describe("<Comment />", () => {
221
235
  votable={true}
222
236
  rootCommentable={rootCommentable}
223
237
  orderBy={orderBy}
238
+ commentsMaxLength={commentsMaxLength}
224
239
  />
225
240
  );
226
241
  wrapper.find(Comment).forEach((node, idx) => {
@@ -239,6 +254,7 @@ describe("<Comment />", () => {
239
254
  articleClassName="comment comment--nested"
240
255
  rootCommentable={rootCommentable}
241
256
  orderBy={orderBy}
257
+ commentsMaxLength={commentsMaxLength}
242
258
  />
243
259
  );
244
260
  wrapper.find(Comment).forEach(node => {
@@ -255,6 +271,7 @@ describe("<Comment />", () => {
255
271
  session={session}
256
272
  rootCommentable={rootCommentable}
257
273
  orderBy={orderBy}
274
+ commentsMaxLength={commentsMaxLength}
258
275
  />
259
276
  );
260
277
  expect(wrapper.prop("articleClassName")).toEqual("comment");
@@ -267,6 +284,7 @@ describe("<Comment />", () => {
267
284
  session={session}
268
285
  rootCommentable={rootCommentable}
269
286
  orderBy={orderBy}
287
+ commentsMaxLength={commentsMaxLength}
270
288
  />
271
289
  );
272
290
  expect(wrapper.prop("isRootComment")).toBeFalsy();
@@ -284,6 +302,7 @@ describe("<Comment />", () => {
284
302
  session={session}
285
303
  rootCommentable={rootCommentable}
286
304
  orderBy={orderBy}
305
+ commentsMaxLength={commentsMaxLength}
287
306
  />
288
307
  );
289
308
  expect(wrapper.find("button.comment__reply").exists()).toBeFalsy();
@@ -302,6 +321,7 @@ describe("<Comment />", () => {
302
321
  session={session}
303
322
  rootCommentable={rootCommentable}
304
323
  orderBy={orderBy}
324
+ commentsMaxLength={commentsMaxLength}
305
325
  />
306
326
  );
307
327
  expect(wrapper.find("button.comment__reply").exists()).toBeFalsy();
@@ -314,6 +334,7 @@ describe("<Comment />", () => {
314
334
  session={session}
315
335
  rootCommentable={rootCommentable}
316
336
  orderBy={orderBy}
337
+ commentsMaxLength={commentsMaxLength}
317
338
  />
318
339
  );
319
340
  expect(wrapper.find(".flag-modal").exists()).toBeFalsy();
@@ -328,6 +349,7 @@ describe("<Comment />", () => {
328
349
  session={session}
329
350
  rootCommentable={rootCommentable}
330
351
  orderBy={orderBy}
352
+ commentsMaxLength={commentsMaxLength}
331
353
  />
332
354
  );
333
355
  expect(wrapper.find("span.alignment.label").text()).toEqual("In favor");
@@ -341,6 +363,7 @@ describe("<Comment />", () => {
341
363
  session={session}
342
364
  rootCommentable={rootCommentable}
343
365
  orderBy={orderBy}
366
+ commentsMaxLength={commentsMaxLength}
344
367
  />
345
368
  );
346
369
  expect(wrapper.find("span.alert.label").text()).toEqual("Against");
@@ -353,6 +376,7 @@ describe("<Comment />", () => {
353
376
  session={session}
354
377
  rootCommentable={rootCommentable}
355
378
  orderBy={orderBy}
379
+ commentsMaxLength={commentsMaxLength}
356
380
  />
357
381
  );
358
382
  expect(wrapper.find(".flag-modal").exists()).toBeTruthy();
@@ -367,6 +391,7 @@ describe("<Comment />", () => {
367
391
  session={session}
368
392
  rootCommentable={rootCommentable}
369
393
  orderBy={orderBy}
394
+ commentsMaxLength={commentsMaxLength}
370
395
  />
371
396
  );
372
397
  expect(wrapper.find(".flag-modal form").exists()).toBeFalsy();
@@ -382,6 +407,7 @@ describe("<Comment />", () => {
382
407
  votable={true}
383
408
  rootCommentable={rootCommentable}
384
409
  orderBy={orderBy}
410
+ commentsMaxLength={commentsMaxLength}
385
411
  />
386
412
  );
387
413
  expect(wrapper.find(UpVoteButton).prop("comment")).toEqual(comment);
@@ -395,6 +421,7 @@ describe("<Comment />", () => {
395
421
  votable={true}
396
422
  rootCommentable={rootCommentable}
397
423
  orderBy={orderBy}
424
+ commentsMaxLength={commentsMaxLength}
398
425
  />
399
426
  );
400
427
  expect(wrapper.find(DownVoteButton).prop("comment")).toEqual(comment);
@@ -413,6 +440,7 @@ describe("<Comment />", () => {
413
440
  session={session}
414
441
  rootCommentable={rootCommentable}
415
442
  orderBy={orderBy}
443
+ commentsMaxLength={commentsMaxLength}
416
444
  />
417
445
  );
418
446
  expect(wrapper.find("button.comment__reply").exists()).toBeFalsy();
@@ -425,6 +453,7 @@ describe("<Comment />", () => {
425
453
  session={session}
426
454
  rootCommentable={rootCommentable}
427
455
  orderBy={orderBy}
456
+ commentsMaxLength={commentsMaxLength}
428
457
  />
429
458
  );
430
459
  expect(wrapper.find(".flag-modal").exists()).toBeFalsy();
@@ -438,6 +467,7 @@ describe("<Comment />", () => {
438
467
  votable={true}
439
468
  rootCommentable={rootCommentable}
440
469
  orderBy={orderBy}
470
+ commentsMaxLength={commentsMaxLength}
441
471
  />
442
472
  );
443
473
  expect(wrapper.find(".comment__votes--up").exists()).toBeFalsy();
@@ -451,6 +481,7 @@ describe("<Comment />", () => {
451
481
  votable={true}
452
482
  rootCommentable={rootCommentable}
453
483
  orderBy={orderBy}
484
+ commentsMaxLength={commentsMaxLength}
454
485
  />
455
486
  );
456
487
  expect(wrapper.find(".comment__votes--down").exists()).toBeFalsy();
@@ -19,14 +19,17 @@ const { I18n } = require("react-i18nify");
19
19
 
20
20
  interface CommentProps {
21
21
  comment: CommentFragment;
22
- session: AddCommentFormSessionFragment & {
23
- user: any;
24
- } | null;
22
+ session:
23
+ | AddCommentFormSessionFragment & {
24
+ user: any;
25
+ }
26
+ | null;
25
27
  articleClassName?: string;
26
28
  isRootComment?: boolean;
27
29
  votable?: boolean;
28
30
  rootCommentable: AddCommentFormCommentableFragment;
29
31
  orderBy: string;
32
+ commentsMaxLength: number;
30
33
  }
31
34
 
32
35
  interface CommentState {
@@ -51,12 +54,14 @@ class Comment extends React.Component<CommentProps, CommentState> {
51
54
  votable: false
52
55
  };
53
56
 
54
- public commentNode: HTMLElement;
57
+ public commentNode: HTMLDivElement;
55
58
 
56
59
  constructor(props: CommentProps) {
57
60
  super(props);
58
61
 
59
- const { comment: { id } } = props;
62
+ const {
63
+ comment: { id }
64
+ } = props;
60
65
  const isThreadHidden = !!this.getThreadsStorage()[id];
61
66
 
62
67
  this.state = {
@@ -66,7 +71,9 @@ class Comment extends React.Component<CommentProps, CommentState> {
66
71
  }
67
72
 
68
73
  public componentDidMount() {
69
- const { comment: { id } } = this.props;
74
+ const {
75
+ comment: { id }
76
+ } = this.props;
70
77
  const hash = document.location.hash;
71
78
  const regex = new RegExp(`#comment_${id}`);
72
79
 
@@ -75,7 +82,7 @@ class Comment extends React.Component<CommentProps, CommentState> {
75
82
  return;
76
83
  }
77
84
  const difference = to - element.scrollTop;
78
- const perTick = difference / duration * 10;
85
+ const perTick = (difference / duration) * 10;
79
86
 
80
87
  setTimeout(() => {
81
88
  element.scrollTop = element.scrollTop + perTick;
@@ -95,10 +102,15 @@ class Comment extends React.Component<CommentProps, CommentState> {
95
102
  }
96
103
  }
97
104
 
98
- public getNodeReference = (commentNode: HTMLElement) => this.commentNode = commentNode;
105
+ public getNodeReference = (commentNode: HTMLDivElement) =>
106
+ (this.commentNode = commentNode)
99
107
 
100
108
  public render(): JSX.Element {
101
- const { session, comment: { id, author, formattedBody, createdAt, formattedCreatedAt }, articleClassName } = this.props;
109
+ const {
110
+ session,
111
+ comment: { id, author, formattedBody, createdAt, formattedCreatedAt },
112
+ articleClassName
113
+ } = this.props;
102
114
  let modalName = "loginModal";
103
115
 
104
116
  if (session && session.user) {
@@ -108,24 +120,52 @@ class Comment extends React.Component<CommentProps, CommentState> {
108
120
  let singleCommentUrl = `${window.location.pathname}?commentId=${id}`;
109
121
 
110
122
  if (window.location.search && window.location.search !== "") {
111
- singleCommentUrl = `${window.location.pathname}${window.location.search.replace(/commentId=\d*/gi, `commentId=${id}`)}`;
123
+ singleCommentUrl = `${
124
+ window.location.pathname
125
+ }${window.location.search.replace(/commentId=\d*/gi, `commentId=${id}`)}`;
112
126
  }
113
127
 
114
128
  return (
115
- <article id={`comment_${id}`} className={articleClassName} ref={this.getNodeReference}>
129
+ <div
130
+ id={`comment_${id}`}
131
+ className={articleClassName}
132
+ ref={this.getNodeReference}
133
+ >
116
134
  <div className="comment__header">
117
135
  <div className="author-data">
118
136
  <div className="author-data__main">
119
137
  {this._renderAuthorReference()}
120
- <span><time dateTime={createdAt} title={createdAt}>{formattedCreatedAt}</time></span>
138
+ <span>
139
+ <time dateTime={createdAt} title={createdAt}>
140
+ {formattedCreatedAt}
141
+ </time>
142
+ </span>
121
143
  </div>
122
144
  <div className="author-data__extra">
123
- <button type="button" title={I18n.t("components.comment.report.title")} data-open={modalName}>
124
- <Icon name="icon-flag" iconExtraClassName="icon--small" />
145
+ <button
146
+ type="button"
147
+ className="link-alt"
148
+ title={I18n.t("components.comment.report.title")}
149
+ data-open={modalName}
150
+ >
151
+ <Icon
152
+ name="icon-flag"
153
+ iconExtraClassName="icon--small"
154
+ title={I18n.t("components.comment.report.title")}
155
+ role="img"
156
+ />
125
157
  </button>
126
158
  {this._renderFlagModal()}
127
- <a href={singleCommentUrl} title={I18n.t("components.comment.single_comment_link_title")}>
128
- <Icon name="icon-link-intact" iconExtraClassName="icon--small" />
159
+ <a
160
+ href={singleCommentUrl}
161
+ title={I18n.t("components.comment.single_comment_link_title")}
162
+ >
163
+ <Icon
164
+ name="icon-link-intact"
165
+ iconExtraClassName="icon--small"
166
+ title={I18n.t("components.comment.single_comment_link_title")}
167
+ role="img"
168
+ />
129
169
  </a>
130
170
  </div>
131
171
  </div>
@@ -146,7 +186,7 @@ class Comment extends React.Component<CommentProps, CommentState> {
146
186
  {this._renderReplies()}
147
187
  {this._renderAdditionalReplyButton()}
148
188
  {this._renderReplyForm()}
149
- </article>
189
+ </div>
150
190
  );
151
191
  }
152
192
 
@@ -156,7 +196,8 @@ class Comment extends React.Component<CommentProps, CommentState> {
156
196
  }
157
197
 
158
198
  private getThreadsStorage = (): Dict => {
159
- const storage: Dict = JSON.parse(localStorage.hiddenCommentThreads || null) || {};
199
+ const storage: Dict =
200
+ JSON.parse(localStorage.hiddenCommentThreads || null) || {};
160
201
 
161
202
  return storage;
162
203
  }
@@ -168,7 +209,9 @@ class Comment extends React.Component<CommentProps, CommentState> {
168
209
  }
169
210
 
170
211
  private toggleReplies = () => {
171
- const { comment: { id } } = this.props;
212
+ const {
213
+ comment: { id }
214
+ } = this.props;
172
215
  const { showReplies } = this.state;
173
216
  const newState = !showReplies;
174
217
 
@@ -183,7 +226,10 @@ class Comment extends React.Component<CommentProps, CommentState> {
183
226
  return 0;
184
227
  }
185
228
 
186
- return comments.length + comments.map(this.countReplies).reduce((a: number, b: number) => a + b, 0);
229
+ return (
230
+ comments.length +
231
+ comments.map(this.countReplies).reduce((a: number, b: number) => a + b, 0)
232
+ );
187
233
  }
188
234
 
189
235
  /**
@@ -192,7 +238,9 @@ class Comment extends React.Component<CommentProps, CommentState> {
192
238
  * @returns {DOMElement} - Render a link with the author information
193
239
  */
194
240
  private _renderAuthorReference() {
195
- const { comment: { author } } = this.props;
241
+ const {
242
+ comment: { author }
243
+ } = this.props;
196
244
 
197
245
  if (author.profilePath === "") {
198
246
  return this._renderAuthor();
@@ -207,7 +255,9 @@ class Comment extends React.Component<CommentProps, CommentState> {
207
255
  * @returns {DOMElement} - Render all the author information
208
256
  */
209
257
  private _renderAuthor() {
210
- const { comment: { author } } = this.props;
258
+ const {
259
+ comment: { author }
260
+ } = this.props;
211
261
 
212
262
  if (author.deleted) {
213
263
  return this._renderDeletedAuthor();
@@ -222,7 +272,9 @@ class Comment extends React.Component<CommentProps, CommentState> {
222
272
  * @returns {DOMElement} - Render all the author information
223
273
  */
224
274
  private _renderDeletedAuthor() {
225
- const { comment: { author } } = this.props;
275
+ const {
276
+ comment: { author }
277
+ } = this.props;
226
278
 
227
279
  return (
228
280
  <div className="author author--inline">
@@ -244,7 +296,9 @@ class Comment extends React.Component<CommentProps, CommentState> {
244
296
  * @returns {DOMElement} - Render all the author information
245
297
  */
246
298
  private _renderActiveAuthor() {
247
- const { comment: { author } } = this.props;
299
+ const {
300
+ comment: { author }
301
+ } = this.props;
248
302
 
249
303
  return (
250
304
  <div className="author author--inline">
@@ -252,11 +306,11 @@ class Comment extends React.Component<CommentProps, CommentState> {
252
306
  <img src={author.avatarUrl} alt="author-avatar" />
253
307
  </span>
254
308
  <span className="author__name">{author.name}</span>
255
- {author.badge === "" ||
309
+ {author.badge === "" || (
256
310
  <span className="author__badge">
257
311
  <Icon name={`icon-${author.badge}`} />
258
312
  </span>
259
- }
313
+ )}
260
314
  <span className="author__nickname">{author.nickname}</span>
261
315
  </div>
262
316
  );
@@ -268,7 +322,10 @@ class Comment extends React.Component<CommentProps, CommentState> {
268
322
  * @returns {Void|DOMElement} - Render the reply button or not if user can reply
269
323
  */
270
324
  private _renderReplyButton() {
271
- const { comment: { id, acceptsNewComments, userAllowedToComment }, session } = this.props;
325
+ const {
326
+ comment: { id, acceptsNewComments, userAllowedToComment },
327
+ session
328
+ } = this.props;
272
329
 
273
330
  if (session && acceptsNewComments && userAllowedToComment) {
274
331
  return (
@@ -281,7 +338,7 @@ class Comment extends React.Component<CommentProps, CommentState> {
281
338
  <Icon name="icon-pencil" iconExtraClassName="icon--small" />
282
339
  &nbsp;
283
340
  {I18n.t("components.comment.reply")}
284
- </button >
341
+ </button>
285
342
  );
286
343
  }
287
344
 
@@ -294,7 +351,11 @@ class Comment extends React.Component<CommentProps, CommentState> {
294
351
  * @returns {Void|DOMElement} - Render the reply button or not if user can reply
295
352
  */
296
353
  private _renderAdditionalReplyButton() {
297
- const { comment: { id, acceptsNewComments, hasComments, userAllowedToComment }, session, isRootComment } = this.props;
354
+ const {
355
+ comment: { id, acceptsNewComments, hasComments, userAllowedToComment },
356
+ session,
357
+ isRootComment
358
+ } = this.props;
298
359
  const { showReplies } = this.state;
299
360
 
300
361
  if (session && acceptsNewComments && userAllowedToComment) {
@@ -331,13 +392,17 @@ class Comment extends React.Component<CommentProps, CommentState> {
331
392
  if (hasComments && isRootComment) {
332
393
  return (
333
394
  <button
334
- className={`comment__reply muted-link ${showReplies ? "comment__is-open" : ""}`}
395
+ className={`comment__reply muted-link ${
396
+ showReplies ? "comment__is-open" : ""
397
+ }`}
335
398
  onClick={this.toggleReplies}
336
399
  >
337
400
  <Icon name="icon-comment-square" iconExtraClassName="icon--small" />
338
401
  &nbsp;
339
402
  <span className="comment__text-is-closed">
340
- {I18n.t("components.comment.show_replies", { replies_count: this.countReplies(comment) })}
403
+ {I18n.t("components.comment.show_replies", {
404
+ replies_count: this.countReplies(comment)
405
+ })}
341
406
  </span>
342
407
  <span className="comment__text-is-open">
343
408
  {I18n.t("components.comment.hide_replies")}
@@ -355,13 +420,25 @@ class Comment extends React.Component<CommentProps, CommentState> {
355
420
  */
356
421
  private _renderVoteButtons() {
357
422
  const { session, comment, votable, rootCommentable, orderBy } = this.props;
358
- const { comment: { userAllowedToComment } } = this.props;
423
+ const {
424
+ comment: { userAllowedToComment }
425
+ } = this.props;
359
426
 
360
427
  if (votable && userAllowedToComment) {
361
428
  return (
362
429
  <div className="comment__votes">
363
- <UpVoteButton session={session} comment={comment} rootCommentable={rootCommentable} orderBy={orderBy} />
364
- <DownVoteButton session={session} comment={comment} rootCommentable={rootCommentable} orderBy={orderBy} />
430
+ <UpVoteButton
431
+ session={session}
432
+ comment={comment}
433
+ rootCommentable={rootCommentable}
434
+ orderBy={orderBy}
435
+ />
436
+ <DownVoteButton
437
+ session={session}
438
+ comment={comment}
439
+ rootCommentable={rootCommentable}
440
+ orderBy={orderBy}
441
+ />
365
442
  </div>
366
443
  );
367
444
  }
@@ -375,7 +452,15 @@ class Comment extends React.Component<CommentProps, CommentState> {
375
452
  * @returns {Void|DomElement} - A wrapper element with comment's comments inside
376
453
  */
377
454
  private _renderReplies() {
378
- const { comment: { id, hasComments, comments }, session, votable, articleClassName, rootCommentable, orderBy } = this.props;
455
+ const {
456
+ comment: { id, hasComments, comments },
457
+ session,
458
+ votable,
459
+ articleClassName,
460
+ rootCommentable,
461
+ orderBy,
462
+ commentsMaxLength
463
+ } = this.props;
379
464
  const { showReplies } = this.state;
380
465
  let replyArticleClassName = "comment comment--nested";
381
466
 
@@ -386,19 +471,18 @@ class Comment extends React.Component<CommentProps, CommentState> {
386
471
  if (hasComments) {
387
472
  return (
388
473
  <div id={`comment-${id}-replies`} className={showReplies ? "" : "hide"}>
389
- {
390
- comments.map((reply: CommentFragment) => (
391
- <Comment
392
- key={`comment_${id}_reply_${reply.id}`}
393
- comment={reply}
394
- session={session}
395
- votable={votable}
396
- articleClassName={replyArticleClassName}
397
- rootCommentable={rootCommentable}
398
- orderBy={orderBy}
399
- />
400
- ))
401
- }
474
+ {comments.map((reply: CommentFragment) => (
475
+ <Comment
476
+ key={`comment_${id}_reply_${reply.id}`}
477
+ comment={reply}
478
+ session={session}
479
+ votable={votable}
480
+ articleClassName={replyArticleClassName}
481
+ rootCommentable={rootCommentable}
482
+ orderBy={orderBy}
483
+ commentsMaxLength={commentsMaxLength}
484
+ />
485
+ ))}
402
486
  </div>
403
487
  );
404
488
  }
@@ -412,9 +496,11 @@ class Comment extends React.Component<CommentProps, CommentState> {
412
496
  * @returns {Void|ReactElement} - Render the AddCommentForm component or not
413
497
  */
414
498
  private _renderReplyForm() {
415
- const { session, comment, rootCommentable, orderBy } = this.props;
499
+ const { session, comment, rootCommentable, orderBy, commentsMaxLength } = this.props;
416
500
  const { showReplyForm } = this.state;
417
- const { comment: { userAllowedToComment } } = this.props;
501
+ const {
502
+ comment: { userAllowedToComment }
503
+ } = this.props;
418
504
 
419
505
  if (session && showReplyForm && userAllowedToComment) {
420
506
  return (
@@ -427,6 +513,7 @@ class Comment extends React.Component<CommentProps, CommentState> {
427
513
  autoFocus={true}
428
514
  rootCommentable={rootCommentable}
429
515
  orderBy={orderBy}
516
+ commentsMaxLength={commentsMaxLength}
430
517
  />
431
518
  );
432
519
  }
@@ -440,7 +527,9 @@ class Comment extends React.Component<CommentProps, CommentState> {
440
527
  * @returns {Void|DOMElement} - The alignment's badge or not
441
528
  */
442
529
  private _renderAlignmentBadge() {
443
- const { comment: { alignment } } = this.props;
530
+ const {
531
+ comment: { alignment }
532
+ } = this.props;
444
533
  const spanClassName = classnames("label alignment", {
445
534
  success: alignment === 1,
446
535
  alert: alignment === -1
@@ -472,7 +561,10 @@ class Comment extends React.Component<CommentProps, CommentState> {
472
561
  * @return {Void|DOMElement} - The comment's report modal or not.
473
562
  */
474
563
  private _renderFlagModal() {
475
- const { session, comment: { id, sgid, alreadyReported, userAllowedToComment } } = this.props;
564
+ const {
565
+ session,
566
+ comment: { id, sgid, alreadyReported, userAllowedToComment }
567
+ } = this.props;
476
568
  const authenticityToken = this._getAuthenticityToken();
477
569
 
478
570
  const closeModal = () => {
@@ -481,9 +573,15 @@ class Comment extends React.Component<CommentProps, CommentState> {
481
573
 
482
574
  if (session && session.user && userAllowedToComment) {
483
575
  return (
484
- <div className="reveal flag-modal" id={`flagModalComment${id}`} data-reveal={true}>
576
+ <div
577
+ className="reveal flag-modal"
578
+ id={`flagModalComment${id}`}
579
+ data-reveal={true}
580
+ >
485
581
  <div className="reveal__header">
486
- <h3 className="reveal__title">{I18n.t("components.comment.report.title")}</h3>
582
+ <h3 className="reveal__title">
583
+ {I18n.t("components.comment.report.title")}
584
+ </h3>
487
585
  <button
488
586
  className="close-button"
489
587
  aria-label={I18n.t("components.comment.report.close")}
@@ -493,40 +591,72 @@ class Comment extends React.Component<CommentProps, CommentState> {
493
591
  <span aria-hidden="true">&times;</span>
494
592
  </button>
495
593
  </div>
496
- {
497
- (() => {
498
- if (alreadyReported) {
499
- return (
500
- <p key={`already-reported-comment-${id}`}>{I18n.t("components.comment.report.already_reported")}</p>
501
- );
502
- }
503
- return [
504
- <p key={`report-description-comment-${id}`}>{I18n.t("components.comment.report.description")}</p>,
505
- (
506
- <form key={`report-form-comment-${id}`} method="post" action={`/report?sgid=${sgid}`}>
507
- <input type="hidden" name="authenticity_token" value={authenticityToken} />
508
- <label htmlFor={`report_comment_${id}_reason_spam`}>
509
- <input type="radio" value="spam" name="report[reason]" id={`report_comment_${id}_reason_spam`} defaultChecked={true} />
510
- {I18n.t("components.comment.report.reasons.spam")}
511
- </label>
512
- <label htmlFor={`report_comment_${id}_reason_offensive`}>
513
- <input type="radio" value="offensive" name="report[reason]" id={`report_comment_${id}_reason_offensive`} />
514
- {I18n.t("components.comment.report.reasons.offensive")}
515
- </label>
516
- <label htmlFor={`report_comment_${id}_reason_does_not_belong`}>
517
- <input type="radio" value="does_not_belong" name="report[reason]" id={`report_comment_${id}_reason_does_not_belong`} />
518
- {I18n.t("components.comment.report.reasons.does_not_belong", { organization_name: session.user.organizationName })}
519
- </label>
520
- <label htmlFor={`report_comment_${id}_details`}>
521
- {I18n.t("components.comment.report.details")}
522
- <textarea rows={4} name="report[details]" id={`report_comment_${id}_details`} />
523
- </label>
524
- <button type="submit" name="commit" className="button">{I18n.t("components.comment.report.action")}</button>
525
- </form>
526
- )
527
- ];
528
- })()
529
- }
594
+ {(() => {
595
+ if (alreadyReported) {
596
+ return (
597
+ <p key={`already-reported-comment-${id}`}>
598
+ {I18n.t("components.comment.report.already_reported")}
599
+ </p>
600
+ );
601
+ }
602
+ return [
603
+ <p key={`report-description-comment-${id}`}>
604
+ {I18n.t("components.comment.report.description")}
605
+ </p>,
606
+ <form
607
+ key={`report-form-comment-${id}`}
608
+ method="post"
609
+ action={`/report?sgid=${sgid}`}
610
+ >
611
+ <input
612
+ type="hidden"
613
+ name="authenticity_token"
614
+ value={authenticityToken}
615
+ />
616
+ <label htmlFor={`report_comment_${id}_reason_spam`}>
617
+ <input
618
+ type="radio"
619
+ value="spam"
620
+ name="report[reason]"
621
+ id={`report_comment_${id}_reason_spam`}
622
+ defaultChecked={true}
623
+ />
624
+ {I18n.t("components.comment.report.reasons.spam")}
625
+ </label>
626
+ <label htmlFor={`report_comment_${id}_reason_offensive`}>
627
+ <input
628
+ type="radio"
629
+ value="offensive"
630
+ name="report[reason]"
631
+ id={`report_comment_${id}_reason_offensive`}
632
+ />
633
+ {I18n.t("components.comment.report.reasons.offensive")}
634
+ </label>
635
+ <label htmlFor={`report_comment_${id}_reason_does_not_belong`}>
636
+ <input
637
+ type="radio"
638
+ value="does_not_belong"
639
+ name="report[reason]"
640
+ id={`report_comment_${id}_reason_does_not_belong`}
641
+ />
642
+ {I18n.t("components.comment.report.reasons.does_not_belong", {
643
+ organization_name: session.user.organizationName
644
+ })}
645
+ </label>
646
+ <label htmlFor={`report_comment_${id}_details`}>
647
+ {I18n.t("components.comment.report.details")}
648
+ <textarea
649
+ rows={4}
650
+ name="report[details]"
651
+ id={`report_comment_${id}_details`}
652
+ />
653
+ </label>
654
+ <button type="submit" name="commit" className="button">
655
+ {I18n.t("components.comment.report.action")}
656
+ </button>
657
+ </form>
658
+ ];
659
+ })()}
530
660
  </div>
531
661
  );
532
662
  }