decidim-comments 0.19.0 → 0.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|
}
|