decidim-comments 0.19.0 → 0.22.0
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 +41 -41
- data/app/assets/javascripts/decidim/comments/bundle.js.map +1 -1
- data/app/cells/decidim/comments/comment_activity_cell.rb +1 -1
- data/app/commands/decidim/comments/create_comment.rb +7 -7
- data/app/events/decidim/comments/comment_by_followed_user_group_event.rb +9 -0
- data/app/events/decidim/comments/comment_event.rb +15 -2
- data/app/events/decidim/comments/user_group_mentioned_event.rb +10 -0
- data/app/forms/decidim/comments/comment_form.rb +9 -0
- data/app/frontend/application/icon.component.tsx +16 -4
- data/app/frontend/comments/add_comment_form.component.test.tsx +4 -1
- data/app/frontend/comments/add_comment_form.component.tsx +16 -3
- data/app/frontend/comments/comment.component.test.tsx +1 -1
- data/app/frontend/comments/comment.component.tsx +287 -74
- data/app/frontend/comments/comment_order_selector.component.tsx +26 -7
- data/app/frontend/comments/comments.component.tsx +65 -8
- data/app/frontend/comments/down_vote_button.component.tsx +3 -0
- data/app/frontend/comments/up_vote_button.component.tsx +3 -0
- data/app/frontend/comments/vote_button.component.tsx +4 -0
- data/app/frontend/comments/vote_button_component.test.tsx +14 -8
- data/app/frontend/entry.ts +19 -0
- data/app/frontend/entry_test.ts +2 -0
- data/app/frontend/queries/comments.query.graphql +2 -2
- data/app/frontend/support/schema.ts +745 -744
- data/app/models/decidim/comments/comment.rb +30 -5
- data/app/queries/decidim/comments/metrics/comments_metric_manage.rb +1 -6
- data/app/queries/decidim/comments/sorted_comments.rb +8 -2
- data/app/scrubbers/decidim/comments/user_input_scrubber.rb +20 -0
- data/app/services/decidim/comments/new_comment_notification_creator.rb +28 -3
- data/app/types/decidim/comments/commentable_interface.rb +2 -1
- data/app/types/decidim/comments/commentable_mutation_type.rb +2 -2
- data/config/locales/ar.yml +10 -1
- data/config/locales/bg-BG.yml +6 -0
- data/config/locales/ca.yml +23 -1
- data/config/locales/cs.yml +35 -13
- data/config/locales/da-DK.yml +1 -0
- data/config/locales/de.yml +23 -1
- data/config/locales/el-GR.yml +1 -0
- data/config/locales/el.yml +122 -0
- data/config/locales/en.yml +23 -1
- data/config/locales/es-MX.yml +23 -1
- data/config/locales/es-PY.yml +23 -1
- data/config/locales/es.yml +23 -1
- data/config/locales/et-EE.yml +1 -0
- data/config/locales/eu.yml +4 -1
- data/config/locales/fi-plain.yml +23 -1
- data/config/locales/fi.yml +29 -7
- data/config/locales/fr-CA.yml +122 -0
- data/config/locales/fr.yml +23 -1
- data/config/locales/ga-IE.yml +1 -0
- data/config/locales/gl.yml +4 -1
- data/config/locales/hr-HR.yml +1 -0
- data/config/locales/hu.yml +17 -1
- data/config/locales/id-ID.yml +4 -1
- data/config/locales/is-IS.yml +76 -0
- data/config/locales/it.yml +23 -1
- data/config/locales/ja-JP.yml +120 -0
- data/config/locales/lt-LT.yml +1 -0
- data/config/locales/lv-LV.yml +118 -0
- data/config/locales/mt-MT.yml +1 -0
- data/config/locales/nl.yml +35 -13
- data/config/locales/no.yml +118 -0
- data/config/locales/pl.yml +57 -35
- 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 +12 -1
- 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/sr-CS.yml +20 -0
- data/config/locales/sv.yml +25 -3
- data/config/locales/tr-TR.yml +4 -1
- data/config/locales/uk.yml +4 -1
- data/db/migrate/20200320105911_index_foreign_keys_in_decidim_comments_comments.rb +7 -0
- data/lib/decidim/comments.rb +1 -0
- data/lib/decidim/comments/markdown.rb +55 -0
- data/lib/decidim/comments/test/shared_examples/comment_event.rb +11 -1
- data/lib/decidim/comments/test/shared_examples/create_comment_context.rb +3 -2
- data/lib/decidim/comments/version.rb +1 -1
- metadata +46 -9
@@ -50,7 +50,7 @@ describe("<Comment />", () => {
|
|
50
50
|
orderBy={orderBy}
|
51
51
|
/>
|
52
52
|
);
|
53
|
-
expect(wrapper.find("
|
53
|
+
expect(wrapper.find(".comment").exists()).toBeTruthy();
|
54
54
|
});
|
55
55
|
|
56
56
|
it("should render a time tag with comment's created at", () => {
|
@@ -13,13 +13,17 @@ import {
|
|
13
13
|
CommentFragment
|
14
14
|
} from "../support/schema";
|
15
15
|
|
16
|
+
import { NetworkStatus } from "apollo-client";
|
17
|
+
|
16
18
|
const { I18n } = require("react-i18nify");
|
17
19
|
|
18
20
|
interface CommentProps {
|
19
21
|
comment: CommentFragment;
|
20
|
-
session:
|
21
|
-
|
22
|
-
|
22
|
+
session:
|
23
|
+
| AddCommentFormSessionFragment & {
|
24
|
+
user: any;
|
25
|
+
}
|
26
|
+
| null;
|
23
27
|
articleClassName?: string;
|
24
28
|
isRootComment?: boolean;
|
25
29
|
votable?: boolean;
|
@@ -28,9 +32,14 @@ interface CommentProps {
|
|
28
32
|
}
|
29
33
|
|
30
34
|
interface CommentState {
|
35
|
+
showReplies: boolean;
|
31
36
|
showReplyForm: boolean;
|
32
37
|
}
|
33
38
|
|
39
|
+
interface Dict {
|
40
|
+
[key: string]: boolean | undefined;
|
41
|
+
}
|
42
|
+
|
34
43
|
/**
|
35
44
|
* A single comment component with the author info and the comment's body
|
36
45
|
* @class
|
@@ -44,18 +53,26 @@ class Comment extends React.Component<CommentProps, CommentState> {
|
|
44
53
|
votable: false
|
45
54
|
};
|
46
55
|
|
47
|
-
public commentNode:
|
56
|
+
public commentNode: HTMLDivElement;
|
48
57
|
|
49
58
|
constructor(props: CommentProps) {
|
50
59
|
super(props);
|
51
60
|
|
61
|
+
const {
|
62
|
+
comment: { id }
|
63
|
+
} = props;
|
64
|
+
const isThreadHidden = !!this.getThreadsStorage()[id];
|
65
|
+
|
52
66
|
this.state = {
|
67
|
+
showReplies: !isThreadHidden,
|
53
68
|
showReplyForm: false
|
54
69
|
};
|
55
70
|
}
|
56
71
|
|
57
72
|
public componentDidMount() {
|
58
|
-
const {
|
73
|
+
const {
|
74
|
+
comment: { id }
|
75
|
+
} = this.props;
|
59
76
|
const hash = document.location.hash;
|
60
77
|
const regex = new RegExp(`#comment_${id}`);
|
61
78
|
|
@@ -64,14 +81,14 @@ class Comment extends React.Component<CommentProps, CommentState> {
|
|
64
81
|
return;
|
65
82
|
}
|
66
83
|
const difference = to - element.scrollTop;
|
67
|
-
const perTick = difference / duration * 10;
|
84
|
+
const perTick = (difference / duration) * 10;
|
68
85
|
|
69
86
|
setTimeout(() => {
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
87
|
+
element.scrollTop = element.scrollTop + perTick;
|
88
|
+
if (element.scrollTop === to) {
|
89
|
+
return;
|
90
|
+
}
|
91
|
+
scrollTo(element, to, duration - 10);
|
75
92
|
}, 10);
|
76
93
|
}
|
77
94
|
|
@@ -84,46 +101,91 @@ class Comment extends React.Component<CommentProps, CommentState> {
|
|
84
101
|
}
|
85
102
|
}
|
86
103
|
|
87
|
-
public getNodeReference = (commentNode:
|
104
|
+
public getNodeReference = (commentNode: HTMLDivElement) =>
|
105
|
+
(this.commentNode = commentNode)
|
88
106
|
|
89
107
|
public render(): JSX.Element {
|
90
|
-
const {
|
108
|
+
const {
|
109
|
+
session,
|
110
|
+
comment: { id, author, formattedBody, createdAt, formattedCreatedAt },
|
111
|
+
articleClassName
|
112
|
+
} = this.props;
|
91
113
|
let modalName = "loginModal";
|
92
114
|
|
93
115
|
if (session && session.user) {
|
94
116
|
modalName = `flagModalComment${id}`;
|
95
117
|
}
|
96
118
|
|
119
|
+
let singleCommentUrl = `${window.location.pathname}?commentId=${id}`;
|
120
|
+
|
121
|
+
if (window.location.search && window.location.search !== "") {
|
122
|
+
singleCommentUrl = `${
|
123
|
+
window.location.pathname
|
124
|
+
}${window.location.search.replace(/commentId=\d*/gi, `commentId=${id}`)}`;
|
125
|
+
}
|
126
|
+
|
97
127
|
return (
|
98
|
-
<
|
128
|
+
<div
|
129
|
+
id={`comment_${id}`}
|
130
|
+
className={articleClassName}
|
131
|
+
ref={this.getNodeReference}
|
132
|
+
>
|
99
133
|
<div className="comment__header">
|
100
134
|
<div className="author-data">
|
101
135
|
<div className="author-data__main">
|
102
136
|
{this._renderAuthorReference()}
|
103
|
-
<span
|
137
|
+
<span>
|
138
|
+
<time dateTime={createdAt} title={createdAt}>
|
139
|
+
{formattedCreatedAt}
|
140
|
+
</time>
|
141
|
+
</span>
|
104
142
|
</div>
|
105
143
|
<div className="author-data__extra">
|
106
|
-
<button
|
107
|
-
|
144
|
+
<button
|
145
|
+
type="button"
|
146
|
+
className="link-alt"
|
147
|
+
title={I18n.t("components.comment.report.title")}
|
148
|
+
data-open={modalName}
|
149
|
+
>
|
150
|
+
<Icon
|
151
|
+
name="icon-flag"
|
152
|
+
iconExtraClassName="icon--small"
|
153
|
+
title={I18n.t("components.comment.report.title")}
|
154
|
+
role="img"
|
155
|
+
/>
|
108
156
|
</button>
|
109
157
|
{this._renderFlagModal()}
|
158
|
+
<a
|
159
|
+
href={singleCommentUrl}
|
160
|
+
title={I18n.t("components.comment.single_comment_link_title")}
|
161
|
+
>
|
162
|
+
<Icon
|
163
|
+
name="icon-link-intact"
|
164
|
+
iconExtraClassName="icon--small"
|
165
|
+
title={I18n.t("components.comment.single_comment_link_title")}
|
166
|
+
role="img"
|
167
|
+
/>
|
168
|
+
</a>
|
110
169
|
</div>
|
111
170
|
</div>
|
112
171
|
</div>
|
113
172
|
<div className="comment__content">
|
114
173
|
<div>
|
115
174
|
{this._renderAlignmentBadge()}
|
116
|
-
<div dangerouslySetInnerHTML={{__html: formattedBody}} />
|
175
|
+
<div dangerouslySetInnerHTML={{ __html: formattedBody }} />
|
117
176
|
</div>
|
118
177
|
</div>
|
119
178
|
<div className="comment__footer">
|
120
|
-
|
179
|
+
<div className="comment__actions">
|
180
|
+
{this._renderShowHideThreadButton()}
|
181
|
+
{this._renderReplyButton()}
|
182
|
+
</div>
|
121
183
|
{this._renderVoteButtons()}
|
122
184
|
</div>
|
123
185
|
{this._renderReplies()}
|
124
186
|
{this._renderAdditionalReplyButton()}
|
125
187
|
{this._renderReplyForm()}
|
126
|
-
</
|
188
|
+
</div>
|
127
189
|
);
|
128
190
|
}
|
129
191
|
|
@@ -132,13 +194,52 @@ class Comment extends React.Component<CommentProps, CommentState> {
|
|
132
194
|
this.setState({ showReplyForm: !showReplyForm });
|
133
195
|
}
|
134
196
|
|
197
|
+
private getThreadsStorage = (): Dict => {
|
198
|
+
const storage: Dict =
|
199
|
+
JSON.parse(localStorage.hiddenCommentThreads || null) || {};
|
200
|
+
|
201
|
+
return storage;
|
202
|
+
}
|
203
|
+
|
204
|
+
private saveThreadsStorage = (id: string, state: boolean) => {
|
205
|
+
const storage = this.getThreadsStorage();
|
206
|
+
storage[parseInt(id, 10)] = state;
|
207
|
+
localStorage.hiddenCommentThreads = JSON.stringify(storage);
|
208
|
+
}
|
209
|
+
|
210
|
+
private toggleReplies = () => {
|
211
|
+
const {
|
212
|
+
comment: { id }
|
213
|
+
} = this.props;
|
214
|
+
const { showReplies } = this.state;
|
215
|
+
const newState = !showReplies;
|
216
|
+
|
217
|
+
this.saveThreadsStorage(id, !newState);
|
218
|
+
this.setState({ showReplies: newState });
|
219
|
+
}
|
220
|
+
|
221
|
+
private countReplies = (comment: CommentFragment): number => {
|
222
|
+
const { comments } = comment;
|
223
|
+
|
224
|
+
if (!comments) {
|
225
|
+
return 0;
|
226
|
+
}
|
227
|
+
|
228
|
+
return (
|
229
|
+
comments.length +
|
230
|
+
comments.map(this.countReplies).reduce((a: number, b: number) => a + b, 0)
|
231
|
+
);
|
232
|
+
}
|
233
|
+
|
135
234
|
/**
|
136
235
|
* Render author information as a link to author's profile
|
137
236
|
* @private
|
138
237
|
* @returns {DOMElement} - Render a link with the author information
|
139
238
|
*/
|
140
239
|
private _renderAuthorReference() {
|
141
|
-
const {
|
240
|
+
const {
|
241
|
+
comment: { author }
|
242
|
+
} = this.props;
|
142
243
|
|
143
244
|
if (author.profilePath === "") {
|
144
245
|
return this._renderAuthor();
|
@@ -153,7 +254,9 @@ class Comment extends React.Component<CommentProps, CommentState> {
|
|
153
254
|
* @returns {DOMElement} - Render all the author information
|
154
255
|
*/
|
155
256
|
private _renderAuthor() {
|
156
|
-
const {
|
257
|
+
const {
|
258
|
+
comment: { author }
|
259
|
+
} = this.props;
|
157
260
|
|
158
261
|
if (author.deleted) {
|
159
262
|
return this._renderDeletedAuthor();
|
@@ -168,7 +271,9 @@ class Comment extends React.Component<CommentProps, CommentState> {
|
|
168
271
|
* @returns {DOMElement} - Render all the author information
|
169
272
|
*/
|
170
273
|
private _renderDeletedAuthor() {
|
171
|
-
const {
|
274
|
+
const {
|
275
|
+
comment: { author }
|
276
|
+
} = this.props;
|
172
277
|
|
173
278
|
return (
|
174
279
|
<div className="author author--inline">
|
@@ -190,7 +295,9 @@ class Comment extends React.Component<CommentProps, CommentState> {
|
|
190
295
|
* @returns {DOMElement} - Render all the author information
|
191
296
|
*/
|
192
297
|
private _renderActiveAuthor() {
|
193
|
-
const {
|
298
|
+
const {
|
299
|
+
comment: { author }
|
300
|
+
} = this.props;
|
194
301
|
|
195
302
|
return (
|
196
303
|
<div className="author author--inline">
|
@@ -198,11 +305,11 @@ class Comment extends React.Component<CommentProps, CommentState> {
|
|
198
305
|
<img src={author.avatarUrl} alt="author-avatar" />
|
199
306
|
</span>
|
200
307
|
<span className="author__name">{author.name}</span>
|
201
|
-
{
|
308
|
+
{author.badge === "" || (
|
202
309
|
<span className="author__badge">
|
203
310
|
<Icon name={`icon-${author.badge}`} />
|
204
311
|
</span>
|
205
|
-
}
|
312
|
+
)}
|
206
313
|
<span className="author__nickname">{author.nickname}</span>
|
207
314
|
</div>
|
208
315
|
);
|
@@ -214,15 +321,21 @@ class Comment extends React.Component<CommentProps, CommentState> {
|
|
214
321
|
* @returns {Void|DOMElement} - Render the reply button or not if user can reply
|
215
322
|
*/
|
216
323
|
private _renderReplyButton() {
|
217
|
-
const {
|
324
|
+
const {
|
325
|
+
comment: { id, acceptsNewComments, userAllowedToComment },
|
326
|
+
session
|
327
|
+
} = this.props;
|
218
328
|
|
219
329
|
if (session && acceptsNewComments && userAllowedToComment) {
|
220
330
|
return (
|
221
331
|
<button
|
222
332
|
className="comment__reply muted-link"
|
223
|
-
aria-controls=
|
333
|
+
aria-controls={`comment${id}-reply`}
|
334
|
+
data-toggle={`comment${id}-reply`}
|
224
335
|
onClick={this.toggleReplyForm}
|
225
336
|
>
|
337
|
+
<Icon name="icon-pencil" iconExtraClassName="icon--small" />
|
338
|
+
|
226
339
|
{I18n.t("components.comment.reply")}
|
227
340
|
</button>
|
228
341
|
);
|
@@ -237,17 +350,25 @@ class Comment extends React.Component<CommentProps, CommentState> {
|
|
237
350
|
* @returns {Void|DOMElement} - Render the reply button or not if user can reply
|
238
351
|
*/
|
239
352
|
private _renderAdditionalReplyButton() {
|
240
|
-
const {
|
353
|
+
const {
|
354
|
+
comment: { id, acceptsNewComments, hasComments, userAllowedToComment },
|
355
|
+
session,
|
356
|
+
isRootComment
|
357
|
+
} = this.props;
|
358
|
+
const { showReplies } = this.state;
|
241
359
|
|
242
360
|
if (session && acceptsNewComments && userAllowedToComment) {
|
243
|
-
if (hasComments && isRootComment) {
|
361
|
+
if (hasComments && isRootComment && showReplies) {
|
244
362
|
return (
|
245
363
|
<div className="comment__additionalreply">
|
246
364
|
<button
|
247
365
|
className="comment__reply muted-link"
|
248
|
-
aria-controls=
|
366
|
+
aria-controls={`comment${id}-reply`}
|
367
|
+
data-toggle={`comment${id}-reply`}
|
249
368
|
onClick={this.toggleReplyForm}
|
250
369
|
>
|
370
|
+
<Icon name="icon-pencil" iconExtraClassName="icon--small" />
|
371
|
+
|
251
372
|
{I18n.t("components.comment.reply")}
|
252
373
|
</button>
|
253
374
|
</div>
|
@@ -257,6 +378,40 @@ class Comment extends React.Component<CommentProps, CommentState> {
|
|
257
378
|
return null;
|
258
379
|
}
|
259
380
|
|
381
|
+
/**
|
382
|
+
* Render show/hide thread button if comment is top-level and has children.
|
383
|
+
* @private
|
384
|
+
* @returns {Void|DOMElement} - Render the reply button or not
|
385
|
+
*/
|
386
|
+
private _renderShowHideThreadButton() {
|
387
|
+
const { comment, isRootComment } = this.props;
|
388
|
+
const { id, hasComments } = comment;
|
389
|
+
const { showReplies } = this.state;
|
390
|
+
|
391
|
+
if (hasComments && isRootComment) {
|
392
|
+
return (
|
393
|
+
<button
|
394
|
+
className={`comment__reply muted-link ${
|
395
|
+
showReplies ? "comment__is-open" : ""
|
396
|
+
}`}
|
397
|
+
onClick={this.toggleReplies}
|
398
|
+
>
|
399
|
+
<Icon name="icon-comment-square" iconExtraClassName="icon--small" />
|
400
|
+
|
401
|
+
<span className="comment__text-is-closed">
|
402
|
+
{I18n.t("components.comment.show_replies", {
|
403
|
+
replies_count: this.countReplies(comment)
|
404
|
+
})}
|
405
|
+
</span>
|
406
|
+
<span className="comment__text-is-open">
|
407
|
+
{I18n.t("components.comment.hide_replies")}
|
408
|
+
</span>
|
409
|
+
</button>
|
410
|
+
);
|
411
|
+
}
|
412
|
+
return null;
|
413
|
+
}
|
414
|
+
|
260
415
|
/**
|
261
416
|
* Render upVote and downVote buttons when the comment is votable
|
262
417
|
* @private
|
@@ -264,13 +419,25 @@ class Comment extends React.Component<CommentProps, CommentState> {
|
|
264
419
|
*/
|
265
420
|
private _renderVoteButtons() {
|
266
421
|
const { session, comment, votable, rootCommentable, orderBy } = this.props;
|
267
|
-
const {
|
422
|
+
const {
|
423
|
+
comment: { userAllowedToComment }
|
424
|
+
} = this.props;
|
268
425
|
|
269
426
|
if (votable && userAllowedToComment) {
|
270
427
|
return (
|
271
428
|
<div className="comment__votes">
|
272
|
-
<UpVoteButton
|
273
|
-
|
429
|
+
<UpVoteButton
|
430
|
+
session={session}
|
431
|
+
comment={comment}
|
432
|
+
rootCommentable={rootCommentable}
|
433
|
+
orderBy={orderBy}
|
434
|
+
/>
|
435
|
+
<DownVoteButton
|
436
|
+
session={session}
|
437
|
+
comment={comment}
|
438
|
+
rootCommentable={rootCommentable}
|
439
|
+
orderBy={orderBy}
|
440
|
+
/>
|
274
441
|
</div>
|
275
442
|
);
|
276
443
|
}
|
@@ -285,6 +452,7 @@ class Comment extends React.Component<CommentProps, CommentState> {
|
|
285
452
|
*/
|
286
453
|
private _renderReplies() {
|
287
454
|
const { comment: { id, hasComments, comments }, session, votable, articleClassName, rootCommentable, orderBy } = this.props;
|
455
|
+
const { showReplies } = this.state;
|
288
456
|
let replyArticleClassName = "comment comment--nested";
|
289
457
|
|
290
458
|
if (articleClassName === "comment comment--nested") {
|
@@ -293,7 +461,7 @@ class Comment extends React.Component<CommentProps, CommentState> {
|
|
293
461
|
|
294
462
|
if (hasComments) {
|
295
463
|
return (
|
296
|
-
<div>
|
464
|
+
<div id={`comment-${id}-replies`} className={showReplies ? "" : "hide"}>
|
297
465
|
{
|
298
466
|
comments.map((reply: CommentFragment) => (
|
299
467
|
<Comment
|
@@ -322,7 +490,9 @@ class Comment extends React.Component<CommentProps, CommentState> {
|
|
322
490
|
private _renderReplyForm() {
|
323
491
|
const { session, comment, rootCommentable, orderBy } = this.props;
|
324
492
|
const { showReplyForm } = this.state;
|
325
|
-
const {
|
493
|
+
const {
|
494
|
+
comment: { userAllowedToComment }
|
495
|
+
} = this.props;
|
326
496
|
|
327
497
|
if (session && showReplyForm && userAllowedToComment) {
|
328
498
|
return (
|
@@ -348,7 +518,9 @@ class Comment extends React.Component<CommentProps, CommentState> {
|
|
348
518
|
* @returns {Void|DOMElement} - The alignment's badge or not
|
349
519
|
*/
|
350
520
|
private _renderAlignmentBadge() {
|
351
|
-
const {
|
521
|
+
const {
|
522
|
+
comment: { alignment }
|
523
|
+
} = this.props;
|
352
524
|
const spanClassName = classnames("label alignment", {
|
353
525
|
success: alignment === 1,
|
354
526
|
alert: alignment === -1
|
@@ -380,7 +552,10 @@ class Comment extends React.Component<CommentProps, CommentState> {
|
|
380
552
|
* @return {Void|DOMElement} - The comment's report modal or not.
|
381
553
|
*/
|
382
554
|
private _renderFlagModal() {
|
383
|
-
const {
|
555
|
+
const {
|
556
|
+
session,
|
557
|
+
comment: { id, sgid, alreadyReported, userAllowedToComment }
|
558
|
+
} = this.props;
|
384
559
|
const authenticityToken = this._getAuthenticityToken();
|
385
560
|
|
386
561
|
const closeModal = () => {
|
@@ -389,9 +564,15 @@ class Comment extends React.Component<CommentProps, CommentState> {
|
|
389
564
|
|
390
565
|
if (session && session.user && userAllowedToComment) {
|
391
566
|
return (
|
392
|
-
<div
|
567
|
+
<div
|
568
|
+
className="reveal flag-modal"
|
569
|
+
id={`flagModalComment${id}`}
|
570
|
+
data-reveal={true}
|
571
|
+
>
|
393
572
|
<div className="reveal__header">
|
394
|
-
<h3 className="reveal__title">
|
573
|
+
<h3 className="reveal__title">
|
574
|
+
{I18n.t("components.comment.report.title")}
|
575
|
+
</h3>
|
395
576
|
<button
|
396
577
|
className="close-button"
|
397
578
|
aria-label={I18n.t("components.comment.report.close")}
|
@@ -401,40 +582,72 @@ class Comment extends React.Component<CommentProps, CommentState> {
|
|
401
582
|
<span aria-hidden="true">×</span>
|
402
583
|
</button>
|
403
584
|
</div>
|
404
|
-
{
|
405
|
-
(
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
|
435
|
-
|
436
|
-
|
437
|
-
|
585
|
+
{(() => {
|
586
|
+
if (alreadyReported) {
|
587
|
+
return (
|
588
|
+
<p key={`already-reported-comment-${id}`}>
|
589
|
+
{I18n.t("components.comment.report.already_reported")}
|
590
|
+
</p>
|
591
|
+
);
|
592
|
+
}
|
593
|
+
return [
|
594
|
+
<p key={`report-description-comment-${id}`}>
|
595
|
+
{I18n.t("components.comment.report.description")}
|
596
|
+
</p>,
|
597
|
+
<form
|
598
|
+
key={`report-form-comment-${id}`}
|
599
|
+
method="post"
|
600
|
+
action={`/report?sgid=${sgid}`}
|
601
|
+
>
|
602
|
+
<input
|
603
|
+
type="hidden"
|
604
|
+
name="authenticity_token"
|
605
|
+
value={authenticityToken}
|
606
|
+
/>
|
607
|
+
<label htmlFor={`report_comment_${id}_reason_spam`}>
|
608
|
+
<input
|
609
|
+
type="radio"
|
610
|
+
value="spam"
|
611
|
+
name="report[reason]"
|
612
|
+
id={`report_comment_${id}_reason_spam`}
|
613
|
+
defaultChecked={true}
|
614
|
+
/>
|
615
|
+
{I18n.t("components.comment.report.reasons.spam")}
|
616
|
+
</label>
|
617
|
+
<label htmlFor={`report_comment_${id}_reason_offensive`}>
|
618
|
+
<input
|
619
|
+
type="radio"
|
620
|
+
value="offensive"
|
621
|
+
name="report[reason]"
|
622
|
+
id={`report_comment_${id}_reason_offensive`}
|
623
|
+
/>
|
624
|
+
{I18n.t("components.comment.report.reasons.offensive")}
|
625
|
+
</label>
|
626
|
+
<label htmlFor={`report_comment_${id}_reason_does_not_belong`}>
|
627
|
+
<input
|
628
|
+
type="radio"
|
629
|
+
value="does_not_belong"
|
630
|
+
name="report[reason]"
|
631
|
+
id={`report_comment_${id}_reason_does_not_belong`}
|
632
|
+
/>
|
633
|
+
{I18n.t("components.comment.report.reasons.does_not_belong", {
|
634
|
+
organization_name: session.user.organizationName
|
635
|
+
})}
|
636
|
+
</label>
|
637
|
+
<label htmlFor={`report_comment_${id}_details`}>
|
638
|
+
{I18n.t("components.comment.report.details")}
|
639
|
+
<textarea
|
640
|
+
rows={4}
|
641
|
+
name="report[details]"
|
642
|
+
id={`report_comment_${id}_details`}
|
643
|
+
/>
|
644
|
+
</label>
|
645
|
+
<button type="submit" name="commit" className="button">
|
646
|
+
{I18n.t("components.comment.report.action")}
|
647
|
+
</button>
|
648
|
+
</form>
|
649
|
+
];
|
650
|
+
})()}
|
438
651
|
</div>
|
439
652
|
);
|
440
653
|
}
|