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.
- checksums.yaml +4 -4
- data/app/assets/javascripts/decidim/comments/bundle.js +67 -67
- data/app/assets/javascripts/decidim/comments/bundle.js.map +1 -1
- data/app/cells/decidim/comments/comment_activity_cell.rb +2 -22
- data/app/cells/decidim/comments/comment_cell.rb +22 -0
- data/app/cells/decidim/comments/comment_m/footer.erb +5 -0
- data/app/cells/decidim/comments/comment_m/top.erb +7 -0
- data/app/cells/decidim/comments/comment_m_cell.rb +29 -0
- data/app/commands/decidim/comments/create_comment.rb +8 -8
- data/app/events/decidim/comments/comment_by_followed_user_group_event.rb +9 -0
- data/app/events/decidim/comments/comment_event.rb +14 -9
- data/app/events/decidim/comments/user_group_mentioned_event.rb +10 -0
- data/app/forms/decidim/comments/comment_form.rb +17 -1
- data/app/frontend/application/icon.component.tsx +16 -4
- data/app/frontend/comments/add_comment_form.component.test.tsx +31 -29
- data/app/frontend/comments/add_comment_form.component.tsx +34 -18
- data/app/frontend/comments/comment.component.test.tsx +36 -5
- data/app/frontend/comments/comment.component.tsx +218 -88
- data/app/frontend/comments/comment_order_selector.component.tsx +26 -7
- data/app/frontend/comments/comment_thread.component.test.tsx +9 -8
- data/app/frontend/comments/comment_thread.component.tsx +3 -1
- data/app/frontend/comments/comments.component.test.tsx +17 -14
- data/app/frontend/comments/comments.component.tsx +28 -4
- data/app/frontend/comments/down_vote_button.component.tsx +27 -9
- data/app/frontend/comments/up_vote_button.component.tsx +27 -9
- data/app/frontend/comments/vote_button.component.tsx +4 -0
- data/app/frontend/comments/vote_button_component.test.tsx +14 -8
- data/app/frontend/mutations/add_comment.mutation.graphql +2 -2
- data/app/frontend/mutations/down_vote.mutation.graphql +2 -2
- data/app/frontend/mutations/up_vote.mutation.graphql +2 -2
- data/app/frontend/queries/comments.query.graphql +2 -2
- data/app/frontend/support/schema.ts +1060 -735
- data/app/helpers/decidim/comments/comment_cells_helper.rb +33 -0
- data/app/models/decidim/comments/comment.rb +81 -22
- data/app/models/decidim/comments/seed.rb +1 -1
- data/app/queries/decidim/comments/metrics/comments_metric_manage.rb +1 -6
- data/app/services/decidim/comments/new_comment_notification_creator.rb +28 -3
- data/app/types/decidim/comments/commentable_interface.rb +1 -1
- data/app/types/decidim/comments/commentable_mutation_type.rb +6 -3
- data/config/locales/am-ET.yml +1 -0
- data/config/locales/ar.yml +4 -1
- data/config/locales/bg-BG.yml +6 -0
- data/config/locales/bg.yml +6 -0
- data/config/locales/ca.yml +19 -2
- data/config/locales/cs.yml +31 -14
- data/config/locales/da-DK.yml +1 -0
- data/config/locales/da.yml +1 -0
- data/config/locales/de.yml +47 -24
- data/config/locales/el.yml +121 -0
- data/config/locales/en.yml +18 -1
- data/config/locales/eo.yml +1 -0
- data/config/locales/es-MX.yml +18 -1
- data/config/locales/es-PY.yml +18 -1
- data/config/locales/es.yml +18 -1
- data/config/locales/et-EE.yml +1 -0
- data/config/locales/et.yml +1 -0
- data/config/locales/eu.yml +4 -1
- data/config/locales/fi-plain.yml +18 -1
- data/config/locales/fi.yml +25 -8
- data/config/locales/fr-CA.yml +123 -0
- data/config/locales/fr.yml +25 -2
- data/config/locales/ga-IE.yml +1 -0
- data/config/locales/gl.yml +17 -1
- data/config/locales/hr-HR.yml +1 -0
- data/config/locales/hr.yml +1 -0
- data/config/locales/hu.yml +12 -2
- data/config/locales/id-ID.yml +4 -1
- data/config/locales/is-IS.yml +3 -3
- data/config/locales/is.yml +76 -0
- data/config/locales/it.yml +21 -1
- data/config/locales/ja-JP.yml +120 -0
- data/config/locales/ja.yml +121 -0
- data/config/locales/ko-KR.yml +1 -0
- data/config/locales/ko.yml +1 -0
- data/config/locales/lt-LT.yml +1 -0
- data/config/locales/lt.yml +1 -0
- data/config/locales/lv.yml +118 -0
- data/config/locales/mt-MT.yml +1 -0
- data/config/locales/mt.yml +1 -0
- data/config/locales/nl.yml +27 -8
- data/config/locales/no.yml +18 -2
- data/config/locales/om-ET.yml +1 -0
- data/config/locales/pl.yml +63 -40
- data/config/locales/pt-BR.yml +5 -2
- data/config/locales/pt.yml +47 -25
- data/config/locales/ro-RO.yml +124 -0
- data/config/locales/ru.yml +4 -1
- data/config/locales/si-LK.yml +1 -0
- data/config/locales/sk-SK.yml +116 -0
- data/config/locales/sk.yml +120 -0
- data/config/locales/sl.yml +4 -0
- data/config/locales/so-SO.yml +1 -0
- data/config/locales/sr-CS.yml +20 -0
- data/config/locales/sv.yml +26 -3
- data/config/locales/sw-KE.yml +1 -0
- data/config/locales/ti-ER.yml +1 -0
- data/config/locales/tr-TR.yml +54 -31
- data/config/locales/uk.yml +4 -2
- data/config/locales/vi-VN.yml +1 -0
- data/config/locales/vi.yml +1 -0
- data/config/locales/zh-CN.yml +121 -0
- data/config/locales/zh-TW.yml +1 -0
- data/db/migrate/20200320105911_index_foreign_keys_in_decidim_comments_comments.rb +7 -0
- data/db/migrate/20200706123136_make_comments_handle_i18n.rb +41 -0
- data/db/migrate/20200828101910_add_commentable_counter_cache_to_comments.rb +9 -0
- data/lib/decidim/comments/api/comment_type.rb +5 -1
- data/lib/decidim/comments/comment_serializer.rb +7 -2
- data/lib/decidim/comments/comment_vote_serializer.rb +5 -1
- data/lib/decidim/comments/commentable.rb +11 -0
- data/lib/decidim/comments/comments_helper.rb +28 -4
- data/lib/decidim/comments/engine.rb +13 -0
- data/lib/decidim/comments/mutation_extensions.rb +8 -0
- data/lib/decidim/comments/query_extensions.rb +4 -0
- data/lib/decidim/comments/test/factories.rb +10 -1
- data/lib/decidim/comments/test/shared_examples/comment_event.rb +12 -2
- data/lib/decidim/comments/test/shared_examples/create_comment_context.rb +3 -2
- data/lib/decidim/comments/version.rb +1 -1
- metadata +59 -13
@@ -1,8 +1,8 @@
|
|
1
|
-
import {
|
1
|
+
import {mount, shallow} from "enzyme";
|
2
2
|
import * as $ from "jquery";
|
3
3
|
import * as React from "react";
|
4
4
|
|
5
|
-
import {
|
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 {
|
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("
|
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:
|
23
|
-
|
24
|
-
|
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:
|
57
|
+
public commentNode: HTMLDivElement;
|
55
58
|
|
56
59
|
constructor(props: CommentProps) {
|
57
60
|
super(props);
|
58
61
|
|
59
|
-
const {
|
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 {
|
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:
|
105
|
+
public getNodeReference = (commentNode: HTMLDivElement) =>
|
106
|
+
(this.commentNode = commentNode)
|
99
107
|
|
100
108
|
public render(): JSX.Element {
|
101
|
-
const {
|
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 = `${
|
123
|
+
singleCommentUrl = `${
|
124
|
+
window.location.pathname
|
125
|
+
}${window.location.search.replace(/commentId=\d*/gi, `commentId=${id}`)}`;
|
112
126
|
}
|
113
127
|
|
114
128
|
return (
|
115
|
-
<
|
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
|
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
|
124
|
-
|
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
|
128
|
-
|
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
|
-
</
|
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 =
|
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 {
|
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
|
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 {
|
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 {
|
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 {
|
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 {
|
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 {
|
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
|
|
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 {
|
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 ${
|
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
|
|
339
402
|
<span className="comment__text-is-closed">
|
340
|
-
{I18n.t("components.comment.show_replies", {
|
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 {
|
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
|
364
|
-
|
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 {
|
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
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
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 {
|
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 {
|
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 {
|
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
|
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">
|
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">×</span>
|
494
592
|
</button>
|
495
593
|
</div>
|
496
|
-
{
|
497
|
-
(
|
498
|
-
|
499
|
-
|
500
|
-
|
501
|
-
|
502
|
-
|
503
|
-
|
504
|
-
|
505
|
-
|
506
|
-
|
507
|
-
|
508
|
-
|
509
|
-
|
510
|
-
|
511
|
-
|
512
|
-
|
513
|
-
|
514
|
-
|
515
|
-
|
516
|
-
|
517
|
-
|
518
|
-
|
519
|
-
|
520
|
-
|
521
|
-
|
522
|
-
|
523
|
-
|
524
|
-
|
525
|
-
|
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
|
}
|