decidim-comments 0.23.6 → 0.24.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (148) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/javascripts/decidim/comments/bundle.js.map +1 -1
  3. data/app/assets/javascripts/decidim/comments/comments.component.js.es6 +292 -0
  4. data/app/assets/javascripts/decidim/comments/comments.component.test.js +581 -0
  5. data/app/assets/javascripts/decidim/comments/comments.js.erb +1 -1
  6. data/app/cells/decidim/comments/comment/actions.erb +7 -0
  7. data/app/cells/decidim/comments/comment/alignment_badge.erb +4 -0
  8. data/app/cells/decidim/comments/comment/author.erb +1 -0
  9. data/app/cells/decidim/comments/comment/show.erb +40 -0
  10. data/app/cells/decidim/comments/comment/utilities.erb +13 -0
  11. data/app/cells/decidim/comments/comment/votes.erb +25 -0
  12. data/app/cells/decidim/comments/comment_card_cell.rb +22 -0
  13. data/app/cells/decidim/comments/comment_cell.rb +142 -8
  14. data/app/cells/decidim/comments/comment_form/comment_as.erb +10 -0
  15. data/app/cells/decidim/comments/comment_form/show.erb +24 -0
  16. data/app/cells/decidim/comments/comment_form_cell.rb +94 -0
  17. data/app/cells/decidim/comments/comment_m/top.erb +1 -1
  18. data/app/cells/decidim/comments/comment_thread/show.erb +6 -0
  19. data/app/cells/decidim/comments/comment_thread/title.erb +3 -0
  20. data/app/cells/decidim/comments/comment_thread_cell.rb +30 -0
  21. data/app/cells/decidim/comments/comments/add_comment.erb +30 -0
  22. data/app/cells/decidim/comments/comments/blocked_comments_warning.erb +3 -0
  23. data/app/cells/decidim/comments/comments/order_control.erb +37 -0
  24. data/app/cells/decidim/comments/comments/show.erb +32 -0
  25. data/app/cells/decidim/comments/comments/single_comment_warning.erb +9 -0
  26. data/app/cells/decidim/comments/comments/user_comments_blocked_warning.erb +3 -0
  27. data/app/cells/decidim/comments/comments_cell.rb +134 -0
  28. data/app/commands/decidim/comments/vote_comment.rb +34 -10
  29. data/app/controllers/decidim/comments/application_controller.rb +16 -0
  30. data/app/controllers/decidim/comments/comments_controller.rb +122 -0
  31. data/app/controllers/decidim/comments/votes_controller.rb +41 -0
  32. data/app/events/decidim/comments/comment_created_event.rb +1 -1
  33. data/app/events/decidim/comments/comment_downvoted_event.rb +8 -0
  34. data/app/events/decidim/comments/comment_upvoted_event.rb +8 -0
  35. data/app/events/decidim/comments/comment_voted_event.rb +26 -0
  36. data/app/forms/decidim/comments/comment_form.rb +6 -2
  37. data/app/models/decidim/comments/comment.rb +22 -8
  38. data/app/models/decidim/comments/seed.rb +1 -1
  39. data/app/permissions/decidim/comments/permissions.rb +59 -0
  40. data/app/queries/decidim/comments/metrics/comment_participants_metric_measure.rb +2 -2
  41. data/app/queries/decidim/comments/metrics/comments_metric_manage.rb +5 -6
  42. data/app/queries/decidim/comments/sorted_comments.rb +18 -14
  43. data/app/views/decidim/comments/comments/_comment.html.erb +5 -0
  44. data/app/views/decidim/comments/comments/_comments.html.erb +1 -0
  45. data/app/views/decidim/comments/comments/create.js.erb +16 -0
  46. data/app/views/decidim/comments/comments/error.js.erb +1 -0
  47. data/app/views/decidim/comments/comments/index.js.erb +24 -0
  48. data/app/views/decidim/comments/comments/reload.js.erb +21 -0
  49. data/app/views/decidim/comments/votes/create.js.erb +23 -0
  50. data/app/views/decidim/comments/votes/error.js.erb +1 -0
  51. data/config/locales/ar.yml +0 -2
  52. data/config/locales/ca.yml +7 -2
  53. data/config/locales/cs.yml +24 -7
  54. data/config/locales/de.yml +17 -2
  55. data/config/locales/el.yml +0 -2
  56. data/config/locales/en.yml +17 -2
  57. data/config/locales/es-MX.yml +7 -2
  58. data/config/locales/es-PY.yml +7 -2
  59. data/config/locales/es.yml +7 -2
  60. data/config/locales/eu.yml +0 -2
  61. data/config/locales/fi-plain.yml +17 -2
  62. data/config/locales/fi.yml +17 -2
  63. data/config/locales/fr-CA.yml +17 -2
  64. data/config/locales/fr.yml +17 -2
  65. data/config/locales/gl.yml +7 -2
  66. data/config/locales/hu.yml +0 -2
  67. data/config/locales/id-ID.yml +0 -2
  68. data/config/locales/is-IS.yml +0 -1
  69. data/config/locales/it.yml +4 -2
  70. data/config/locales/ja.yml +8 -4
  71. data/config/locales/lv.yml +0 -2
  72. data/config/locales/nl.yml +0 -2
  73. data/config/locales/no.yml +0 -2
  74. data/config/locales/pl.yml +19 -2
  75. data/config/locales/pt-BR.yml +0 -2
  76. data/config/locales/pt.yml +0 -2
  77. data/config/locales/ro-RO.yml +0 -3
  78. data/config/locales/ru.yml +0 -2
  79. data/config/locales/sk.yml +0 -2
  80. data/config/locales/sv.yml +7 -2
  81. data/config/locales/tr-TR.yml +7 -2
  82. data/config/locales/uk.yml +0 -1
  83. data/config/locales/zh-CN.yml +0 -2
  84. data/lib/decidim/api/add_comment_type.rb +13 -0
  85. data/lib/decidim/api/comment_mutation_type.rb +22 -0
  86. data/lib/decidim/api/comment_type.rb +89 -0
  87. data/lib/decidim/api/commentable_interface.rb +50 -0
  88. data/lib/decidim/api/commentable_mutation_type.rb +30 -0
  89. data/{app/types/decidim/comments → lib/decidim/api}/commentable_type.rb +2 -5
  90. data/lib/decidim/comments/api.rb +12 -0
  91. data/lib/decidim/comments/comments_helper.rb +10 -52
  92. data/lib/decidim/comments/engine.rb +9 -7
  93. data/lib/decidim/comments/mutation_extensions.rb +22 -22
  94. data/lib/decidim/comments/query_extensions.rb +12 -14
  95. data/lib/decidim/comments/test/shared_examples/comment_voted_event.rb +65 -0
  96. data/lib/decidim/comments/test.rb +1 -0
  97. data/lib/decidim/comments/version.rb +1 -1
  98. data/lib/decidim/comments.rb +1 -3
  99. metadata +67 -66
  100. data/app/assets/javascripts/decidim/comments/bundle.js +0 -268
  101. data/app/frontend/application/apollo_client.ts +0 -12
  102. data/app/frontend/application/application.component.test.tsx +0 -23
  103. data/app/frontend/application/application.component.tsx +0 -35
  104. data/app/frontend/application/icon.component.test.tsx +0 -38
  105. data/app/frontend/application/icon.component.tsx +0 -46
  106. data/app/frontend/comments/add_comment_form.component.test.tsx +0 -265
  107. data/app/frontend/comments/add_comment_form.component.tsx +0 -482
  108. data/app/frontend/comments/comment.component.test.tsx +0 -490
  109. data/app/frontend/comments/comment.component.tsx +0 -677
  110. data/app/frontend/comments/comment_order_selector.component.test.tsx +0 -29
  111. data/app/frontend/comments/comment_order_selector.component.tsx +0 -106
  112. data/app/frontend/comments/comment_thread.component.test.tsx +0 -82
  113. data/app/frontend/comments/comment_thread.component.tsx +0 -81
  114. data/app/frontend/comments/comments.component.test.tsx +0 -150
  115. data/app/frontend/comments/comments.component.tsx +0 -289
  116. data/app/frontend/comments/down_vote_button.component.test.tsx +0 -59
  117. data/app/frontend/comments/down_vote_button.component.tsx +0 -133
  118. data/app/frontend/comments/up_vote_button.component.test.tsx +0 -59
  119. data/app/frontend/comments/up_vote_button.component.tsx +0 -133
  120. data/app/frontend/comments/vote_button.component.tsx +0 -50
  121. data/app/frontend/comments/vote_button_component.test.tsx +0 -64
  122. data/app/frontend/entry.ts +0 -38
  123. data/app/frontend/entry_test.ts +0 -6
  124. data/app/frontend/fragments/add_comment_form_commentable.fragment.graphql +0 -4
  125. data/app/frontend/fragments/add_comment_form_session.fragment.graphql +0 -6
  126. data/app/frontend/fragments/comment.fragment.graphql +0 -14
  127. data/app/frontend/fragments/comment_data.fragment.graphql +0 -27
  128. data/app/frontend/fragments/comment_thread.fragment.graphql +0 -6
  129. data/app/frontend/fragments/down_vote_button.fragment.graphql +0 -6
  130. data/app/frontend/fragments/up_vote_button.fragment.graphql +0 -6
  131. data/app/frontend/mutations/add_comment.mutation.graphql +0 -9
  132. data/app/frontend/mutations/down_vote.mutation.graphql +0 -9
  133. data/app/frontend/mutations/up_vote.mutation.graphql +0 -9
  134. data/app/frontend/queries/comments.query.graphql +0 -26
  135. data/app/frontend/support/asset_url.ts +0 -11
  136. data/app/frontend/support/generate_comments_data.ts +0 -49
  137. data/app/frontend/support/generate_user_data.ts +0 -14
  138. data/app/frontend/support/generate_user_group_data.ts +0 -14
  139. data/app/frontend/support/graphql_transformer.js +0 -32
  140. data/app/frontend/support/load_translations.ts +0 -48
  141. data/app/frontend/support/require_all.ts +0 -10
  142. data/app/frontend/support/resolve_graphql_query.ts +0 -37
  143. data/app/frontend/support/schema.ts +0 -2026
  144. data/app/types/decidim/comments/commentable_interface.rb +0 -61
  145. data/app/types/decidim/comments/commentable_mutation_type.rb +0 -33
  146. data/lib/decidim/comments/api/add_comment_type.rb +0 -13
  147. data/lib/decidim/comments/api/comment_mutation_type.rb +0 -20
  148. data/lib/decidim/comments/api/comment_type.rb +0 -89
@@ -1,677 +0,0 @@
1
- import * as classnames from "classnames";
2
- import * as React from "react";
3
-
4
- import Icon from "../application/icon.component";
5
-
6
- import AddCommentForm from "./add_comment_form.component";
7
- import DownVoteButton from "./down_vote_button.component";
8
- import UpVoteButton from "./up_vote_button.component";
9
-
10
- import {
11
- AddCommentFormCommentableFragment,
12
- AddCommentFormSessionFragment,
13
- CommentFragment
14
- } from "../support/schema";
15
-
16
- import { NetworkStatus } from "apollo-client";
17
-
18
- const { I18n } = require("react-i18nify");
19
-
20
- interface CommentProps {
21
- comment: CommentFragment;
22
- session:
23
- | AddCommentFormSessionFragment & {
24
- user: any;
25
- }
26
- | null;
27
- articleClassName?: string;
28
- isRootComment?: boolean;
29
- votable?: boolean;
30
- rootCommentable: AddCommentFormCommentableFragment;
31
- orderBy: string;
32
- commentsMaxLength: number;
33
- }
34
-
35
- interface CommentState {
36
- showReplies: boolean;
37
- showReplyForm: boolean;
38
- }
39
-
40
- interface Dict {
41
- [key: string]: boolean | undefined;
42
- }
43
-
44
- /**
45
- * A single comment component with the author info and the comment's body
46
- * @class
47
- * @augments Component
48
- */
49
- class Comment extends React.Component<CommentProps, CommentState> {
50
- public static defaultProps: any = {
51
- articleClassName: "comment",
52
- isRootComment: false,
53
- session: null,
54
- votable: false
55
- };
56
-
57
- public commentNode: HTMLDivElement;
58
-
59
- constructor(props: CommentProps) {
60
- super(props);
61
-
62
- const {
63
- comment: { id }
64
- } = props;
65
- const isThreadHidden = !!this.getThreadsStorage()[id];
66
-
67
- this.state = {
68
- showReplies: !isThreadHidden,
69
- showReplyForm: false
70
- };
71
- }
72
-
73
- public componentDidMount() {
74
- const {
75
- comment: { id }
76
- } = this.props;
77
- const hash = document.location.hash;
78
- const regex = new RegExp(`#comment_${id}`);
79
-
80
- function scrollTo(element: Element, to: number, duration: number) {
81
- if (duration <= 0) {
82
- return;
83
- }
84
- const difference = to - element.scrollTop;
85
- const perTick = (difference / duration) * 10;
86
-
87
- setTimeout(() => {
88
- element.scrollTop = element.scrollTop + perTick;
89
- if (element.scrollTop === to) {
90
- return;
91
- }
92
- scrollTo(element, to, duration - 10);
93
- }, 10);
94
- }
95
-
96
- if (regex.test(hash)) {
97
- scrollTo(document.body, this.commentNode.offsetTop, 200);
98
- }
99
-
100
- if (window.$(document).foundation) {
101
- window.$(`#flagModalComment${id}`).foundation();
102
- }
103
- }
104
-
105
- public getNodeReference = (commentNode: HTMLDivElement) =>
106
- (this.commentNode = commentNode)
107
-
108
- public render(): JSX.Element {
109
- const {
110
- session,
111
- comment: { id, author, formattedBody, createdAt, formattedCreatedAt },
112
- articleClassName
113
- } = this.props;
114
- let modalName = "loginModal";
115
-
116
- if (session && session.user) {
117
- modalName = `flagModalComment${id}`;
118
- }
119
-
120
- let singleCommentUrl = `${window.location.pathname}?commentId=${id}`;
121
-
122
- if (window.location.search && window.location.search !== "") {
123
- singleCommentUrl = `${
124
- window.location.pathname
125
- }${window.location.search.replace(/commentId=\d*/gi, `commentId=${id}`)}`;
126
- }
127
-
128
- return (
129
- <div
130
- id={`comment_${id}`}
131
- className={articleClassName}
132
- ref={this.getNodeReference}
133
- >
134
- <div className="comment__header">
135
- <div className="author-data">
136
- <div className="author-data__main">
137
- {this._renderAuthorReference()}
138
- <span>
139
- <time dateTime={createdAt} title={createdAt}>
140
- {formattedCreatedAt}
141
- </time>
142
- </span>
143
- </div>
144
- <div className="author-data__extra">
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
- />
157
- </button>
158
- {this._renderFlagModal()}
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
- />
169
- </a>
170
- </div>
171
- </div>
172
- </div>
173
- <div className="comment__content">
174
- <div>
175
- {this._renderAlignmentBadge()}
176
- <div dangerouslySetInnerHTML={{ __html: formattedBody }} />
177
- </div>
178
- </div>
179
- <div className="comment__footer">
180
- <div className="comment__actions">
181
- {this._renderShowHideThreadButton()}
182
- {this._renderReplyButton()}
183
- </div>
184
- {this._renderVoteButtons()}
185
- </div>
186
- {this._renderReplies()}
187
- {this._renderAdditionalReplyButton()}
188
- {this._renderReplyForm()}
189
- </div>
190
- );
191
- }
192
-
193
- private toggleReplyForm = () => {
194
- const { showReplyForm } = this.state;
195
- this.setState({ showReplyForm: !showReplyForm });
196
- }
197
-
198
- private getThreadsStorage = (): Dict => {
199
- const storage: Dict =
200
- JSON.parse(localStorage.hiddenCommentThreads || null) || {};
201
-
202
- return storage;
203
- }
204
-
205
- private saveThreadsStorage = (id: string, state: boolean) => {
206
- const storage = this.getThreadsStorage();
207
- storage[parseInt(id, 10)] = state;
208
- localStorage.hiddenCommentThreads = JSON.stringify(storage);
209
- }
210
-
211
- private toggleReplies = () => {
212
- const {
213
- comment: { id }
214
- } = this.props;
215
- const { showReplies } = this.state;
216
- const newState = !showReplies;
217
-
218
- this.saveThreadsStorage(id, !newState);
219
- this.setState({ showReplies: newState });
220
- }
221
-
222
- private countReplies = (comment: CommentFragment): number => {
223
- const { comments } = comment;
224
-
225
- if (!comments) {
226
- return 0;
227
- }
228
-
229
- return (
230
- comments.length +
231
- comments.map(this.countReplies).reduce((a: number, b: number) => a + b, 0)
232
- );
233
- }
234
-
235
- /**
236
- * Render author information as a link to author's profile
237
- * @private
238
- * @returns {DOMElement} - Render a link with the author information
239
- */
240
- private _renderAuthorReference() {
241
- const {
242
- comment: { author }
243
- } = this.props;
244
-
245
- if (author.profilePath === "") {
246
- return this._renderAuthor();
247
- }
248
-
249
- return <a href={author.profilePath}>{this._renderAuthor()}</a>;
250
- }
251
-
252
- /**
253
- * Render author information
254
- * @private
255
- * @returns {DOMElement} - Render all the author information
256
- */
257
- private _renderAuthor() {
258
- const {
259
- comment: { author }
260
- } = this.props;
261
-
262
- if (author.deleted) {
263
- return this._renderDeletedAuthor();
264
- }
265
-
266
- return this._renderActiveAuthor();
267
- }
268
-
269
- /**
270
- * Render deleted author information
271
- * @private
272
- * @returns {DOMElement} - Render all the author information
273
- */
274
- private _renderDeletedAuthor() {
275
- const {
276
- comment: { author }
277
- } = this.props;
278
-
279
- return (
280
- <div className="author author--inline">
281
- <span className="author__avatar">
282
- <img src={author.avatarUrl} alt="author-avatar" />
283
- </span>
284
- <span className="author__name">
285
- <span className="label label--small label--basic">
286
- {I18n.t("components.comment.deleted_user")}
287
- </span>
288
- </span>
289
- </div>
290
- );
291
- }
292
-
293
- /**
294
- * Render active author information
295
- * @private
296
- * @returns {DOMElement} - Render all the author information
297
- */
298
- private _renderActiveAuthor() {
299
- const {
300
- comment: { author }
301
- } = this.props;
302
-
303
- return (
304
- <div className="author author--inline">
305
- <span className="author__avatar">
306
- <img src={author.avatarUrl} alt="author-avatar" />
307
- </span>
308
- <span className="author__name">{author.name}</span>
309
- {author.badge === "" || (
310
- <span className="author__badge">
311
- <Icon name={`icon-${author.badge}`} />
312
- </span>
313
- )}
314
- <span className="author__nickname">{author.nickname}</span>
315
- </div>
316
- );
317
- }
318
-
319
- /**
320
- * Render reply button if user can reply the comment
321
- * @private
322
- * @returns {Void|DOMElement} - Render the reply button or not if user can reply
323
- */
324
- private _renderReplyButton() {
325
- const {
326
- comment: { id, acceptsNewComments, userAllowedToComment },
327
- session
328
- } = this.props;
329
-
330
- if (session && acceptsNewComments && userAllowedToComment) {
331
- return (
332
- <button
333
- className="comment__reply muted-link"
334
- aria-controls={`comment${id}-reply`}
335
- data-toggle={`comment${id}-reply`}
336
- onClick={this.toggleReplyForm}
337
- >
338
- <Icon name="icon-pencil" iconExtraClassName="icon--small" />
339
- &nbsp;
340
- {I18n.t("components.comment.reply")}
341
- </button>
342
- );
343
- }
344
-
345
- return <span>&nbsp;</span>;
346
- }
347
-
348
- /**
349
- * Render additional reply button if user can reply the comment at the bottom of a conversation
350
- * @private
351
- * @returns {Void|DOMElement} - Render the reply button or not if user can reply
352
- */
353
- private _renderAdditionalReplyButton() {
354
- const {
355
- comment: { id, acceptsNewComments, hasComments, userAllowedToComment },
356
- session,
357
- isRootComment
358
- } = this.props;
359
- const { showReplies } = this.state;
360
-
361
- if (session && acceptsNewComments && userAllowedToComment) {
362
- if (hasComments && isRootComment && showReplies) {
363
- return (
364
- <div className="comment__additionalreply">
365
- <button
366
- className="comment__reply muted-link"
367
- aria-controls={`comment${id}-reply`}
368
- data-toggle={`comment${id}-reply`}
369
- onClick={this.toggleReplyForm}
370
- >
371
- <Icon name="icon-pencil" iconExtraClassName="icon--small" />
372
- &nbsp;
373
- {I18n.t("components.comment.reply")}
374
- </button>
375
- </div>
376
- );
377
- }
378
- }
379
- return null;
380
- }
381
-
382
- /**
383
- * Render show/hide thread button if comment is top-level and has children.
384
- * @private
385
- * @returns {Void|DOMElement} - Render the reply button or not
386
- */
387
- private _renderShowHideThreadButton() {
388
- const { comment, isRootComment } = this.props;
389
- const { id, hasComments } = comment;
390
- const { showReplies } = this.state;
391
-
392
- if (hasComments && isRootComment) {
393
- return (
394
- <button
395
- className={`comment__reply muted-link ${
396
- showReplies ? "comment__is-open" : ""
397
- }`}
398
- onClick={this.toggleReplies}
399
- >
400
- <Icon name="icon-comment-square" iconExtraClassName="icon--small" />
401
- &nbsp;
402
- <span className="comment__text-is-closed">
403
- {I18n.t("components.comment.show_replies", {
404
- replies_count: this.countReplies(comment)
405
- })}
406
- </span>
407
- <span className="comment__text-is-open">
408
- {I18n.t("components.comment.hide_replies")}
409
- </span>
410
- </button>
411
- );
412
- }
413
- return null;
414
- }
415
-
416
- /**
417
- * Render upVote and downVote buttons when the comment is votable
418
- * @private
419
- * @returns {Void|DOMElement} - Render the upVote and downVote buttons or not
420
- */
421
- private _renderVoteButtons() {
422
- const { session, comment, votable, rootCommentable, orderBy } = this.props;
423
- const {
424
- comment: { userAllowedToComment }
425
- } = this.props;
426
-
427
- if (votable && userAllowedToComment) {
428
- return (
429
- <div className="comment__votes">
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
- />
442
- </div>
443
- );
444
- }
445
-
446
- return <span>&nbsp;</span>;
447
- }
448
-
449
- /**
450
- * Render comment's comments alternating the css class
451
- * @private
452
- * @returns {Void|DomElement} - A wrapper element with comment's comments inside
453
- */
454
- private _renderReplies() {
455
- const {
456
- comment: { id, hasComments, comments },
457
- session,
458
- votable,
459
- articleClassName,
460
- rootCommentable,
461
- orderBy,
462
- commentsMaxLength
463
- } = this.props;
464
- const { showReplies } = this.state;
465
- let replyArticleClassName = "comment comment--nested";
466
-
467
- if (articleClassName === "comment comment--nested") {
468
- replyArticleClassName = `${replyArticleClassName} comment--nested--alt`;
469
- }
470
-
471
- if (hasComments) {
472
- return (
473
- <div id={`comment-${id}-replies`} className={showReplies ? "" : "hide"}>
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
- ))}
486
- </div>
487
- );
488
- }
489
-
490
- return null;
491
- }
492
-
493
- /**
494
- * Render reply form based on the current component state
495
- * @private
496
- * @returns {Void|ReactElement} - Render the AddCommentForm component or not
497
- */
498
- private _renderReplyForm() {
499
- const { session, comment, rootCommentable, orderBy, commentsMaxLength } = this.props;
500
- const { showReplyForm } = this.state;
501
- const {
502
- comment: { userAllowedToComment }
503
- } = this.props;
504
-
505
- if (session && showReplyForm && userAllowedToComment) {
506
- return (
507
- <AddCommentForm
508
- session={session}
509
- commentable={comment}
510
- showTitle={false}
511
- submitButtonClassName="button small hollow"
512
- onCommentAdded={this.toggleReplyForm}
513
- autoFocus={true}
514
- rootCommentable={rootCommentable}
515
- orderBy={orderBy}
516
- commentsMaxLength={commentsMaxLength}
517
- />
518
- );
519
- }
520
-
521
- return null;
522
- }
523
-
524
- /**
525
- * Render alignment badge if comment's alignment is 0 or -1
526
- * @private
527
- * @returns {Void|DOMElement} - The alignment's badge or not
528
- */
529
- private _renderAlignmentBadge() {
530
- const {
531
- comment: { alignment }
532
- } = this.props;
533
- const spanClassName = classnames("label alignment", {
534
- success: alignment === 1,
535
- alert: alignment === -1
536
- });
537
-
538
- let label = "";
539
-
540
- if (alignment === 1) {
541
- label = I18n.t("components.comment.alignment.in_favor");
542
- } else {
543
- label = I18n.t("components.comment.alignment.against");
544
- }
545
-
546
- if (alignment === 1 || alignment === -1) {
547
- return (
548
- <span>
549
- <span className={spanClassName}>{label}</span>
550
- &nbsp;
551
- </span>
552
- );
553
- }
554
-
555
- return null;
556
- }
557
-
558
- /**
559
- * Render a modal to report the comment.
560
- * @private
561
- * @return {Void|DOMElement} - The comment's report modal or not.
562
- */
563
- private _renderFlagModal() {
564
- const {
565
- session,
566
- comment: { id, sgid, alreadyReported, userAllowedToComment }
567
- } = this.props;
568
- const authenticityToken = this._getAuthenticityToken();
569
-
570
- const closeModal = () => {
571
- window.$(`#flagModalComment${id}`).foundation("close");
572
- };
573
-
574
- if (session && session.user && userAllowedToComment) {
575
- return (
576
- <div
577
- className="reveal flag-modal"
578
- id={`flagModalComment${id}`}
579
- data-reveal={true}
580
- >
581
- <div className="reveal__header">
582
- <h3 className="reveal__title">
583
- {I18n.t("components.comment.report.title")}
584
- </h3>
585
- <button
586
- className="close-button"
587
- aria-label={I18n.t("components.comment.report.close")}
588
- type="button"
589
- onClick={closeModal}
590
- >
591
- <span aria-hidden="true">&times;</span>
592
- </button>
593
- </div>
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
- })()}
660
- </div>
661
- );
662
- }
663
-
664
- return null;
665
- }
666
-
667
- /**
668
- * Get Rails authenticity token so we can send requests through the report forms.
669
- * @private
670
- * @return {string} - The current authenticity token.
671
- */
672
- private _getAuthenticityToken() {
673
- return window.$('meta[name="csrf-token"]').attr("content");
674
- }
675
- }
676
-
677
- export default Comment;
@@ -1,29 +0,0 @@
1
- import { shallow } from "enzyme";
2
- import * as React from "react";
3
-
4
- import CommentOrderSelector from "./comment_order_selector.component";
5
-
6
- describe("<CommentOrderSelector />", () => {
7
- const orderBy = "older";
8
- const reorderComments = jasmine.createSpy("reorderComments");
9
-
10
- beforeEach(() => {
11
- window.$ = () => {
12
- return {
13
- foundation: jasmine.createSpy("")
14
- };
15
- };
16
- });
17
-
18
- it("renders a div with classes order-by__dropdown order-by__dropdown--right", () => {
19
- const wrapper = shallow(<CommentOrderSelector reorderComments={reorderComments} defaultOrderBy={orderBy} />);
20
- expect(wrapper.find("div.order-by__dropdown.order-by__dropdown--right")).toBeDefined();
21
- });
22
-
23
- it("should set state order to best_rated if user clicks on the first element", () => {
24
- const preventDefault = jasmine.createSpy("preventDefault");
25
- const wrapper = shallow(<CommentOrderSelector reorderComments={reorderComments} defaultOrderBy={orderBy} />);
26
- wrapper.find("a.test").simulate("click", {preventDefault});
27
- expect(reorderComments).toBeCalledWith("best_rated");
28
- });
29
- });