decidim-comments 0.19.1 → 0.23.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (123) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/javascripts/decidim/comments/bundle.js +66 -66
  3. data/app/assets/javascripts/decidim/comments/bundle.js.map +1 -1
  4. data/app/cells/decidim/comments/comment_activity_cell.rb +2 -22
  5. data/app/cells/decidim/comments/comment_cell.rb +22 -0
  6. data/app/cells/decidim/comments/comment_m/footer.erb +5 -0
  7. data/app/cells/decidim/comments/comment_m/top.erb +7 -0
  8. data/app/cells/decidim/comments/comment_m_cell.rb +29 -0
  9. data/app/commands/decidim/comments/create_comment.rb +8 -8
  10. data/app/events/decidim/comments/comment_by_followed_user_group_event.rb +9 -0
  11. data/app/events/decidim/comments/comment_event.rb +15 -2
  12. data/app/events/decidim/comments/user_group_mentioned_event.rb +10 -0
  13. data/app/forms/decidim/comments/comment_form.rb +17 -1
  14. data/app/frontend/application/icon.component.tsx +16 -4
  15. data/app/frontend/comments/add_comment_form.component.test.tsx +34 -29
  16. data/app/frontend/comments/add_comment_form.component.tsx +48 -19
  17. data/app/frontend/comments/comment.component.test.tsx +36 -5
  18. data/app/frontend/comments/comment.component.tsx +311 -89
  19. data/app/frontend/comments/comment_order_selector.component.tsx +26 -7
  20. data/app/frontend/comments/comment_thread.component.test.tsx +9 -8
  21. data/app/frontend/comments/comment_thread.component.tsx +3 -1
  22. data/app/frontend/comments/comments.component.test.tsx +17 -14
  23. data/app/frontend/comments/comments.component.tsx +90 -9
  24. data/app/frontend/comments/down_vote_button.component.tsx +27 -9
  25. data/app/frontend/comments/up_vote_button.component.tsx +27 -9
  26. data/app/frontend/comments/vote_button.component.tsx +4 -0
  27. data/app/frontend/comments/vote_button_component.test.tsx +14 -8
  28. data/app/frontend/entry.ts +19 -0
  29. data/app/frontend/entry_test.ts +2 -0
  30. data/app/frontend/mutations/add_comment.mutation.graphql +2 -2
  31. data/app/frontend/mutations/down_vote.mutation.graphql +2 -2
  32. data/app/frontend/mutations/up_vote.mutation.graphql +2 -2
  33. data/app/frontend/queries/comments.query.graphql +3 -3
  34. data/app/frontend/support/schema.ts +326 -0
  35. data/app/helpers/decidim/comments/comment_cells_helper.rb +33 -0
  36. data/app/models/decidim/comments/comment.rb +96 -18
  37. data/app/models/decidim/comments/seed.rb +1 -1
  38. data/app/queries/decidim/comments/metrics/comments_metric_manage.rb +1 -6
  39. data/app/queries/decidim/comments/sorted_comments.rb +8 -2
  40. data/app/scrubbers/decidim/comments/user_input_scrubber.rb +20 -0
  41. data/app/services/decidim/comments/new_comment_notification_creator.rb +28 -3
  42. data/app/types/decidim/comments/commentable_interface.rb +3 -2
  43. data/app/types/decidim/comments/commentable_mutation_type.rb +6 -3
  44. data/config/locales/am-ET.yml +1 -0
  45. data/config/locales/ar.yml +10 -1
  46. data/config/locales/bg-BG.yml +6 -0
  47. data/config/locales/bg.yml +6 -0
  48. data/config/locales/ca.yml +24 -1
  49. data/config/locales/cs.yml +36 -13
  50. data/config/locales/da-DK.yml +1 -0
  51. data/config/locales/da.yml +1 -0
  52. data/config/locales/de.yml +23 -1
  53. data/config/locales/el-GR.yml +1 -0
  54. data/config/locales/el.yml +122 -0
  55. data/config/locales/en.yml +24 -1
  56. data/config/locales/eo.yml +1 -0
  57. data/config/locales/es-MX.yml +24 -1
  58. data/config/locales/es-PY.yml +24 -1
  59. data/config/locales/es.yml +24 -1
  60. data/config/locales/et-EE.yml +1 -0
  61. data/config/locales/et.yml +1 -0
  62. data/config/locales/eu.yml +4 -1
  63. data/config/locales/fi-plain.yml +24 -1
  64. data/config/locales/fi.yml +31 -8
  65. data/config/locales/fr-CA.yml +123 -0
  66. data/config/locales/fr.yml +25 -2
  67. data/config/locales/ga-IE.yml +1 -0
  68. data/config/locales/gl.yml +4 -1
  69. data/config/locales/hr-HR.yml +1 -0
  70. data/config/locales/hr.yml +1 -0
  71. data/config/locales/hu.yml +18 -2
  72. data/config/locales/id-ID.yml +4 -1
  73. data/config/locales/is-IS.yml +74 -0
  74. data/config/locales/is.yml +76 -0
  75. data/config/locales/it.yml +23 -1
  76. data/config/locales/ja-JP.yml +120 -0
  77. data/config/locales/ja.yml +121 -0
  78. data/config/locales/ko-KR.yml +1 -0
  79. data/config/locales/ko.yml +1 -0
  80. data/config/locales/lt-LT.yml +1 -0
  81. data/config/locales/lt.yml +1 -0
  82. data/config/locales/lv.yml +118 -0
  83. data/config/locales/mt-MT.yml +1 -0
  84. data/config/locales/mt.yml +1 -0
  85. data/config/locales/nl.yml +26 -3
  86. data/config/locales/no.yml +88 -1
  87. data/config/locales/om-ET.yml +1 -0
  88. data/config/locales/pl.yml +62 -40
  89. data/config/locales/pt-BR.yml +5 -2
  90. data/config/locales/pt.yml +47 -25
  91. data/config/locales/ro-RO.yml +124 -0
  92. data/config/locales/ru.yml +4 -1
  93. data/config/locales/sk-SK.yml +116 -0
  94. data/config/locales/sk.yml +120 -0
  95. data/config/locales/sl.yml +4 -0
  96. data/config/locales/so-SO.yml +1 -0
  97. data/config/locales/sr-CS.yml +20 -0
  98. data/config/locales/sv.yml +26 -3
  99. data/config/locales/ti-ER.yml +1 -0
  100. data/config/locales/tr-TR.yml +4 -1
  101. data/config/locales/uk.yml +4 -2
  102. data/config/locales/vi-VN.yml +1 -0
  103. data/config/locales/vi.yml +1 -0
  104. data/config/locales/zh-CN.yml +121 -0
  105. data/config/locales/zh-TW.yml +1 -0
  106. data/db/migrate/20200320105911_index_foreign_keys_in_decidim_comments_comments.rb +7 -0
  107. data/db/migrate/20200706123136_make_comments_handle_i18n.rb +41 -0
  108. data/db/migrate/20200828101910_add_commentable_counter_cache_to_comments.rb +9 -0
  109. data/lib/decidim/comments.rb +1 -0
  110. data/lib/decidim/comments/api/comment_type.rb +5 -1
  111. data/lib/decidim/comments/comment_serializer.rb +7 -2
  112. data/lib/decidim/comments/comment_vote_serializer.rb +5 -1
  113. data/lib/decidim/comments/commentable.rb +11 -0
  114. data/lib/decidim/comments/comments_helper.rb +28 -4
  115. data/lib/decidim/comments/engine.rb +13 -0
  116. data/lib/decidim/comments/markdown.rb +55 -0
  117. data/lib/decidim/comments/mutation_extensions.rb +8 -0
  118. data/lib/decidim/comments/query_extensions.rb +4 -0
  119. data/lib/decidim/comments/test/factories.rb +10 -1
  120. data/lib/decidim/comments/test/shared_examples/comment_event.rb +12 -2
  121. data/lib/decidim/comments/test/shared_examples/create_comment_context.rb +3 -2
  122. data/lib/decidim/comments/version.rb +1 -1
  123. metadata +72 -9
@@ -43,28 +43,47 @@ class CommentOrderSelector extends React.Component<CommentOrderSelectorProps, Co
43
43
  <ul
44
44
  className="dropdown menu"
45
45
  data-dropdown-menu="data-dropdown-menu"
46
+ data-autoclose="false"
47
+ data-disable-hover="true"
48
+ data-click-open="true"
49
+ data-close-on-click="true"
50
+ tabIndex={-1}
46
51
  ref={this.setDropdown}
47
52
  >
48
- <li>
49
- <a>{I18n.t(`components.comment_order_selector.order.${orderBy}`)}</a>
50
- <ul className="menu">
53
+ <li className="is-dropdown-submenu-parent" tabIndex={-1}>
54
+ <a
55
+ href="#"
56
+ id="comments-order-menu-control"
57
+ aria-label={I18n.t("components.comment_order_selector.title")}
58
+ aria-controls="comments-order-menu"
59
+ aria-haspopup="true"
60
+ >
61
+ {I18n.t(`components.comment_order_selector.order.${orderBy}`)}
62
+ </a>
63
+ <ul
64
+ className="menu is-dropdown-submenu"
65
+ id="language-chooser-menu"
66
+ role="menu"
67
+ aria-labelledby="comments-order-menu-control"
68
+ tabIndex={-1}
69
+ >
51
70
  <li>
52
- <a href="" className="test" onClick={this.updateOrder("best_rated")} >
71
+ <a href="#" className="test" onClick={this.updateOrder("best_rated")} tabIndex={-1}>
53
72
  {I18n.t("components.comment_order_selector.order.best_rated")}
54
73
  </a>
55
74
  </li>
56
75
  <li>
57
- <a href="" onClick={this.updateOrder("recent")} >
76
+ <a href="#" onClick={this.updateOrder("recent")} tabIndex={-1}>
58
77
  {I18n.t("components.comment_order_selector.order.recent")}
59
78
  </a>
60
79
  </li>
61
80
  <li>
62
- <a href="" onClick={this.updateOrder("older")} >
81
+ <a href="#" onClick={this.updateOrder("older")} tabIndex={-1}>
63
82
  {I18n.t("components.comment_order_selector.order.older")}
64
83
  </a>
65
84
  </li>
66
85
  <li>
67
- <a href="" onClick={this.updateOrder("most_discussed")} >
86
+ <a href="" onClick={this.updateOrder("most_discussed")} tabIndex={-1}>
68
87
  {I18n.t("components.comment_order_selector.order.most_discussed")}
69
88
  </a>
70
89
  </li>
@@ -9,7 +9,8 @@ import generateCommentsData from "../support/generate_comments_data";
9
9
  import generateCUserData from "../support/generate_user_data";
10
10
  import { loadLocaleTranslations } from "../support/load_translations";
11
11
 
12
- describe("<CommentThread />", () => {
12
+ describe("<CommentThread commentsMaxLength={commentsMaxLength} />", () => {
13
+ const commentsMaxLength: number = 1000;
13
14
  const orderBy = "older";
14
15
  const rootCommentable = {
15
16
  id: "1",
@@ -30,7 +31,7 @@ describe("<CommentThread />", () => {
30
31
 
31
32
  describe("when comment doesn't have comments", () => {
32
33
  it("should not render a title with author name", () => {
33
- const wrapper = shallow(<CommentThread comment={comment} session={session} rootCommentable={rootCommentable} orderBy={orderBy} />);
34
+ const wrapper = shallow(<CommentThread commentsMaxLength={commentsMaxLength} comment={comment} session={session} rootCommentable={rootCommentable} orderBy={orderBy} />);
34
35
  expect(wrapper.find("h6.comment-thread__title").exists()).toBeFalsy();
35
36
  });
36
37
  });
@@ -41,7 +42,7 @@ describe("<CommentThread />", () => {
41
42
  });
42
43
 
43
44
  it("should render a h6 comment-thread__title with author name", () => {
44
- const wrapper = shallow(<CommentThread comment={comment} session={session} rootCommentable={rootCommentable} orderBy={orderBy} />);
45
+ const wrapper = shallow(<CommentThread commentsMaxLength={commentsMaxLength} comment={comment} session={session} rootCommentable={rootCommentable} orderBy={orderBy} />);
45
46
  expect(wrapper.find("h6.comment-thread__title").text()).toContain(`Conversation with ${comment.author.name}`);
46
47
  });
47
48
 
@@ -51,7 +52,7 @@ describe("<CommentThread />", () => {
51
52
  });
52
53
 
53
54
  it("should render a h6 comment-thread__title with 'Deleted participant'", () => {
54
- const wrapper = shallow(<CommentThread comment={comment} session={session} rootCommentable={rootCommentable} orderBy={orderBy} />);
55
+ const wrapper = shallow(<CommentThread commentsMaxLength={commentsMaxLength} comment={comment} session={session} rootCommentable={rootCommentable} orderBy={orderBy} />);
55
56
  expect(wrapper.find("h6.comment-thread__title").text()).toContain("Conversation with Deleted participant");
56
57
  });
57
58
  });
@@ -59,22 +60,22 @@ describe("<CommentThread />", () => {
59
60
 
60
61
  describe("should render a Comment", () => {
61
62
  it("and pass the session as a prop to it", () => {
62
- const wrapper = shallow(<CommentThread comment={comment} session={session} rootCommentable={rootCommentable} orderBy={orderBy} />);
63
+ const wrapper = shallow(<CommentThread commentsMaxLength={commentsMaxLength} comment={comment} session={session} rootCommentable={rootCommentable} orderBy={orderBy} />);
63
64
  expect(wrapper.find(Comment).first().props()).toHaveProperty("session", session);
64
65
  });
65
66
 
66
67
  it("and pass comment data as a prop to it", () => {
67
- const wrapper = shallow(<CommentThread comment={comment} session={session} rootCommentable={rootCommentable} orderBy={orderBy} />);
68
+ const wrapper = shallow(<CommentThread commentsMaxLength={commentsMaxLength} comment={comment} session={session} rootCommentable={rootCommentable} orderBy={orderBy} />);
68
69
  expect(wrapper.find(Comment).first().props()).toHaveProperty("comment", comment);
69
70
  });
70
71
 
71
72
  it("and pass the votable as a prop to it", () => {
72
- const wrapper = shallow(<CommentThread comment={comment} session={session} votable={true} rootCommentable={rootCommentable} orderBy={orderBy} />);
73
+ const wrapper = shallow(<CommentThread commentsMaxLength={commentsMaxLength} comment={comment} session={session} votable={true} rootCommentable={rootCommentable} orderBy={orderBy} />);
73
74
  expect(wrapper.find(Comment).first().props()).toHaveProperty("votable", true);
74
75
  });
75
76
 
76
77
  it("and pass the isRootComment equal true", () => {
77
- const wrapper = shallow(<CommentThread comment={comment} session={session} votable={true} rootCommentable={rootCommentable} orderBy={orderBy} />);
78
+ const wrapper = shallow(<CommentThread commentsMaxLength={commentsMaxLength} comment={comment} session={session} votable={true} rootCommentable={rootCommentable} orderBy={orderBy} />);
78
79
  expect(wrapper.find(Comment).first().props()).toHaveProperty("isRootComment", true);
79
80
  });
80
81
  });
@@ -18,6 +18,7 @@ interface CommentThreadProps {
18
18
  votable?: boolean;
19
19
  rootCommentable: AddCommentFormCommentableFragment;
20
20
  orderBy: string;
21
+ commentsMaxLength: number;
21
22
  }
22
23
 
23
24
  /**
@@ -33,7 +34,7 @@ class CommentThread extends React.Component<CommentThreadProps> {
33
34
  };
34
35
 
35
36
  public render() {
36
- const { comment, session, votable, rootCommentable, orderBy } = this.props;
37
+ const { comment, session, votable, rootCommentable, orderBy, commentsMaxLength } = this.props;
37
38
 
38
39
  return (
39
40
  <div>
@@ -46,6 +47,7 @@ class CommentThread extends React.Component<CommentThreadProps> {
46
47
  isRootComment={true}
47
48
  rootCommentable={rootCommentable}
48
49
  orderBy={orderBy}
50
+ commentsMaxLength={commentsMaxLength}
49
51
  />
50
52
  </div>
51
53
  </div>
@@ -12,12 +12,15 @@ import resolveGraphQLQuery from "../support/resolve_graphql_query";
12
12
 
13
13
  import { loadLocaleTranslations } from "../support/load_translations";
14
14
 
15
- describe("<Comments />", () => {
15
+ describe("<Comments commentsMaxLength={commentsMaxLength} />", () => {
16
16
  let commentable: any = {};
17
17
  let session: any = null;
18
+ const commentsMaxLength: number = 1000;
18
19
  const commentableId = "1";
19
20
  const commentableType = "Decidim::DummyResources::DummyResource";
20
21
  const orderBy = "older";
22
+ const locale = "en";
23
+ const toggleTranslations = false;
21
24
  const reorderComments = jasmine.createSpy("reorderComments");
22
25
 
23
26
  beforeEach(() => {
@@ -52,19 +55,19 @@ describe("<Comments />", () => {
52
55
  });
53
56
 
54
57
  it("renders loading-comments class and the respective loading text", () => {
55
- const wrapper = shallow(<Comments commentable={commentable} session={session} reorderComments={reorderComments} orderBy={orderBy} loading={true} />);
58
+ const wrapper = shallow(<Comments locale={locale} toggleTranslations={toggleTranslations} commentsMaxLength={commentsMaxLength} commentable={commentable} session={session} reorderComments={reorderComments} orderBy={orderBy} loading={true} />);
56
59
  expect(wrapper.find(".loading-comments").exists()).toBeTruthy();
57
60
  expect(wrapper.find("h2").text()).toEqual("Loading comments ...");
58
61
  });
59
62
 
60
63
  it("renders a div of id comments", () => {
61
- const wrapper = shallow(<Comments commentable={commentable} session={session} reorderComments={reorderComments} orderBy={orderBy} />);
64
+ const wrapper = shallow(<Comments locale={locale} toggleTranslations={toggleTranslations} commentsMaxLength={commentsMaxLength} commentable={commentable} session={session} reorderComments={reorderComments} orderBy={orderBy} />);
62
65
  expect(wrapper.find("#comments").exists()).toBeTruthy();
63
66
  });
64
67
 
65
68
  describe("renders a CommentThread component for each comment", () => {
66
69
  it("and pass filter comment data as a prop to it", () => {
67
- const wrapper = shallow(<Comments commentable={commentable} session={session} reorderComments={reorderComments} orderBy={orderBy} />);
70
+ const wrapper = shallow(<Comments locale={locale} toggleTranslations={toggleTranslations} commentsMaxLength={commentsMaxLength} commentable={commentable} session={session} reorderComments={reorderComments} orderBy={orderBy} />);
68
71
  expect(wrapper.find(CommentThread).length).toEqual(commentable.comments.length);
69
72
  wrapper.find(CommentThread).forEach((node, idx) => {
70
73
  expect(node.prop("comment")).toEqual(commentable.comments[idx]);
@@ -72,7 +75,7 @@ describe("<Comments />", () => {
72
75
  });
73
76
 
74
77
  it("and pass the session as a prop to it", () => {
75
- const wrapper = shallow(<Comments commentable={commentable} session={session} reorderComments={reorderComments} orderBy={orderBy} />);
78
+ const wrapper = shallow(<Comments locale={locale} toggleTranslations={toggleTranslations} commentsMaxLength={commentsMaxLength} commentable={commentable} session={session} reorderComments={reorderComments} orderBy={orderBy} />);
76
79
  expect(wrapper.find(CommentThread).length).toEqual(commentable.comments.length);
77
80
  wrapper.find(CommentThread).forEach((node) => {
78
81
  expect(node.prop("session")).toEqual(session);
@@ -80,7 +83,7 @@ describe("<Comments />", () => {
80
83
  });
81
84
 
82
85
  it("and pass the commentable 'commentsHaveVotes' property as a prop to it", () => {
83
- const wrapper = shallow(<Comments commentable={commentable} session={session} reorderComments={reorderComments} orderBy={orderBy} />);
86
+ const wrapper = shallow(<Comments locale={locale} toggleTranslations={toggleTranslations} commentsMaxLength={commentsMaxLength} commentable={commentable} session={session} reorderComments={reorderComments} orderBy={orderBy} />);
84
87
  expect(wrapper.find(CommentThread).length).toEqual(commentable.comments.length);
85
88
  wrapper.find(CommentThread).forEach((node) => {
86
89
  expect(node.prop("votable")).toBeTruthy();
@@ -89,13 +92,13 @@ describe("<Comments />", () => {
89
92
  });
90
93
 
91
94
  it("renders comments count", () => {
92
- const wrapper = shallow(<Comments commentable={commentable} session={session} reorderComments={reorderComments} orderBy={orderBy} />);
95
+ const wrapper = shallow(<Comments locale={locale} toggleTranslations={toggleTranslations} commentsMaxLength={commentsMaxLength} commentable={commentable} session={session} reorderComments={reorderComments} orderBy={orderBy} />);
93
96
  const rex = new RegExp(`${commentable.comments.length} comments`);
94
97
  expect(wrapper.find("h2.section-heading").text()).toMatch(rex);
95
98
  });
96
99
 
97
100
  it("renders a AddCommentForm component and pass the commentable 'commentsHaveAlignment' as a prop", () => {
98
- const wrapper = shallow(<Comments commentable={commentable} session={session} reorderComments={reorderComments} orderBy={orderBy} />);
101
+ const wrapper = shallow(<Comments locale={locale} toggleTranslations={toggleTranslations} commentsMaxLength={commentsMaxLength} commentable={commentable} session={session} reorderComments={reorderComments} orderBy={orderBy} />);
99
102
  expect(wrapper.find(AddCommentForm).length).toEqual(1);
100
103
  expect(wrapper.find(AddCommentForm).prop("arguable")).toBeTruthy();
101
104
  });
@@ -107,12 +110,12 @@ describe("<Comments />", () => {
107
110
  });
108
111
 
109
112
  it("doesn't render an AddCommentForm component", () => {
110
- const wrapper = shallow(<Comments commentable={commentable} session={session} reorderComments={reorderComments} orderBy={orderBy} />);
113
+ const wrapper = shallow(<Comments locale={locale} toggleTranslations={toggleTranslations} commentsMaxLength={commentsMaxLength} commentable={commentable} session={session} reorderComments={reorderComments} orderBy={orderBy} />);
111
114
  expect(wrapper.find(AddCommentForm).exists()).toBeFalsy();
112
115
  });
113
116
 
114
117
  it("renders a callout message to inform the user that comments are blocked", () => {
115
- const wrapper = shallow(<Comments commentable={commentable} session={session} reorderComments={reorderComments} orderBy={orderBy} />);
118
+ const wrapper = shallow(<Comments locale={locale} toggleTranslations={toggleTranslations} commentsMaxLength={commentsMaxLength} commentable={commentable} session={session} reorderComments={reorderComments} orderBy={orderBy} />);
116
119
  expect(wrapper.find(".callout.warning").text()).toContain("disabled");
117
120
  });
118
121
  });
@@ -123,24 +126,24 @@ describe("<Comments />", () => {
123
126
  });
124
127
 
125
128
  it("doesn't render an AddCommentForm component", () => {
126
- const wrapper = shallow(<Comments commentable={commentable} session={session} reorderComments={reorderComments} orderBy={orderBy} />);
129
+ const wrapper = shallow(<Comments locale={locale} toggleTranslations={toggleTranslations} commentsMaxLength={commentsMaxLength} commentable={commentable} session={session} reorderComments={reorderComments} orderBy={orderBy} />);
127
130
  expect(wrapper.find(AddCommentForm).exists()).toBeFalsy();
128
131
  });
129
132
 
130
133
  it("renders a callout message to inform the user that comments are blocked", () => {
131
- const wrapper = shallow(<Comments commentable={commentable} session={session} reorderComments={reorderComments} orderBy={orderBy} />);
134
+ const wrapper = shallow(<Comments locale={locale} toggleTranslations={toggleTranslations} commentsMaxLength={commentsMaxLength} commentable={commentable} session={session} reorderComments={reorderComments} orderBy={orderBy} />);
132
135
  expect(wrapper.find(".callout.warning").text()).toContain("not able");
133
136
  });
134
137
  });
135
138
 
136
139
  describe("renders a CommentOrderSelector component", () => {
137
140
  it("and pass the reorderComments as a prop to it", () => {
138
- const wrapper = shallow(<Comments commentable={commentable} session={session} reorderComments={reorderComments} orderBy={orderBy} />);
141
+ const wrapper = shallow(<Comments locale={locale} toggleTranslations={toggleTranslations} commentsMaxLength={commentsMaxLength} commentable={commentable} session={session} reorderComments={reorderComments} orderBy={orderBy} />);
139
142
  expect(wrapper.find(CommentOrderSelector).prop("reorderComments")).toEqual(reorderComments);
140
143
  });
141
144
 
142
145
  it("and pass the orderBy as a prop to it", () => {
143
- const wrapper = shallow(<Comments commentable={commentable} session={session} reorderComments={reorderComments} orderBy={orderBy} />);
146
+ const wrapper = shallow(<Comments locale={locale} toggleTranslations={toggleTranslations} commentsMaxLength={commentsMaxLength} commentable={commentable} session={session} reorderComments={reorderComments} orderBy={orderBy} />);
144
147
  expect(wrapper.find(CommentOrderSelector).prop("defaultOrderBy")).toEqual("older");
145
148
  });
146
149
  });
@@ -1,6 +1,8 @@
1
1
  import * as React from "react";
2
2
  import { graphql } from "react-apollo";
3
3
 
4
+ const PropTypes = require("prop-types");
5
+
4
6
  import Application from "../application/application.component";
5
7
 
6
8
  import AddCommentForm from "./add_comment_form.component";
@@ -12,12 +14,16 @@ import {
12
14
  GetCommentsQueryVariables
13
15
  } from "../support/schema";
14
16
 
15
- const { I18n } = require("react-i18nify");
17
+ const { I18n, Translate } = require("react-i18nify");
16
18
 
17
19
  interface CommentsProps extends GetCommentsQuery {
20
+ locale: string;
21
+ toggleTranslations: boolean;
18
22
  loading?: boolean;
19
23
  orderBy: string;
24
+ singleCommentId?: string;
20
25
  reorderComments: (orderBy: string) => void;
26
+ commentsMaxLength: number;
21
27
  }
22
28
 
23
29
  /**
@@ -36,10 +42,25 @@ export class Comments extends React.Component<CommentsProps> {
36
42
  }
37
43
  };
38
44
 
45
+ public static childContextTypes: any = {
46
+ locale: PropTypes.string,
47
+ toggleTranslations: PropTypes.bool
48
+ };
49
+
50
+ public getChildContext() {
51
+ return {
52
+ locale: this.props.locale,
53
+ toggleTranslations: this.props.toggleTranslations
54
+ };
55
+ }
56
+
39
57
  public render() {
40
- const { commentable: { comments, totalCommentsCount = 0, userAllowedToComment }, reorderComments, orderBy, loading } = this.props;
58
+ const { commentable: { totalCommentsCount = 0 }, singleCommentId, loading, commentsMaxLength } = this.props;
41
59
  let commentClasses = "comments";
42
60
  let commentHeader = I18n.t("components.comments.title", { count: totalCommentsCount });
61
+ if (singleCommentId && singleCommentId !== "") {
62
+ commentHeader = I18n.t("components.comments.comment_details_title");
63
+ }
43
64
 
44
65
  if (loading) {
45
66
  commentClasses += " loading-comments";
@@ -53,11 +74,9 @@ export class Comments extends React.Component<CommentsProps> {
53
74
  <h2 className="order-by__text section-heading">
54
75
  {commentHeader}
55
76
  </h2>
56
- <CommentOrderSelector
57
- reorderComments={reorderComments}
58
- defaultOrderBy={orderBy}
59
- />
77
+ {this._renderCommentOrderSelector()}
60
78
  </div>
79
+ {this._renderSingleCommentWarning()}
61
80
  {this._renderBlockedCommentsWarning()}
62
81
  {this._renderCommentThreads()}
63
82
  {this._renderAddCommentForm()}
@@ -67,6 +86,54 @@ export class Comments extends React.Component<CommentsProps> {
67
86
  );
68
87
  }
69
88
 
89
+ /**
90
+ * Renders warning message when viewing a single comment.
91
+ * @private
92
+ * @returns {Void|DOMElement} - A warning message or nothing.
93
+ */
94
+ private _renderSingleCommentWarning() {
95
+ const { singleCommentId, reorderComments, orderBy } = this.props;
96
+
97
+ if (singleCommentId && singleCommentId !== "") {
98
+ const newUrl = `${window.location.pathname}${window.location.search.replace(`commentId=${singleCommentId}`, "")}`;
99
+
100
+ return (
101
+ <div className="callout secondary">
102
+ <h5>{I18n.t("components.comments.single_comment_warning_title")}</h5>
103
+ <p>
104
+ <Translate
105
+ value="components.comments.single_comment_warning"
106
+ url={newUrl}
107
+ dangerousHTML={true}
108
+ />
109
+ </p>
110
+ </div>
111
+ );
112
+ }
113
+
114
+ return null;
115
+ }
116
+
117
+ /**
118
+ * Renders an order selector.
119
+ * @private
120
+ * @returns {Void|DOMElement} - A warning message or nothing.
121
+ */
122
+ private _renderCommentOrderSelector() {
123
+ const { singleCommentId, reorderComments, orderBy } = this.props;
124
+
125
+ if (singleCommentId && singleCommentId !== "") {
126
+ return null;
127
+ }
128
+
129
+ return (
130
+ <CommentOrderSelector
131
+ reorderComments={reorderComments}
132
+ defaultOrderBy={orderBy}
133
+ />
134
+ );
135
+ }
136
+
70
137
  /**
71
138
  * Renders a warning message if the commentable doesn't accept new comments.
72
139
  * @private
@@ -114,7 +181,7 @@ export class Comments extends React.Component<CommentsProps> {
114
181
  * @returns {ReactComponent[]} - A collection of CommentThread components
115
182
  */
116
183
  private _renderCommentThreads() {
117
- const { session, commentable, orderBy } = this.props;
184
+ const { session, commentable, orderBy, commentsMaxLength } = this.props;
118
185
  const { comments, commentsHaveVotes } = commentable;
119
186
 
120
187
  return comments.map((comment) => (
@@ -125,6 +192,7 @@ export class Comments extends React.Component<CommentsProps> {
125
192
  votable={commentsHaveVotes}
126
193
  rootCommentable={commentable}
127
194
  orderBy={orderBy}
195
+ commentsMaxLength={commentsMaxLength}
128
196
  />
129
197
  ));
130
198
  }
@@ -135,9 +203,13 @@ export class Comments extends React.Component<CommentsProps> {
135
203
  * @returns {Void|ReactComponent} - A AddCommentForm component or nothing
136
204
  */
137
205
  private _renderAddCommentForm() {
138
- const { session, commentable, orderBy } = this.props;
206
+ const { session, commentable, orderBy, singleCommentId, commentsMaxLength } = this.props;
139
207
  const { acceptsNewComments, commentsHaveAlignment, userAllowedToComment } = commentable;
140
208
 
209
+ if (singleCommentId && singleCommentId !== "") {
210
+ return null;
211
+ }
212
+
141
213
  if (acceptsNewComments && userAllowedToComment) {
142
214
  return (
143
215
  <AddCommentForm
@@ -146,6 +218,7 @@ export class Comments extends React.Component<CommentsProps> {
146
218
  arguable={commentsHaveAlignment}
147
219
  rootCommentable={commentable}
148
220
  orderBy={orderBy}
221
+ commentsMaxLength={commentsMaxLength}
149
222
  />
150
223
  );
151
224
  }
@@ -176,6 +249,7 @@ const CommentsWithData: any = graphql<GetCommentsQuery, CommentsProps>(commentsQ
176
249
  session,
177
250
  commentable,
178
251
  orderBy: ownProps.orderBy,
252
+ singleCommentId: ownProps.singleCommentId,
179
253
  reorderComments: (orderBy: string) => {
180
254
  return refetch({
181
255
  orderBy
@@ -187,7 +261,10 @@ const CommentsWithData: any = graphql<GetCommentsQuery, CommentsProps>(commentsQ
187
261
  })(Comments);
188
262
 
189
263
  export interface CommentsApplicationProps extends GetCommentsQueryVariables {
264
+ singleCommentId: string;
190
265
  locale: string;
266
+ toggleTranslations: boolean;
267
+ commentsMaxLength: number;
191
268
  }
192
269
 
193
270
  /**
@@ -195,12 +272,16 @@ export interface CommentsApplicationProps extends GetCommentsQueryVariables {
195
272
  * connect it with Apollo client and store.
196
273
  * @returns {ReactComponent} - A component wrapped within an Application component
197
274
  */
198
- const CommentsApplication: React.SFC<CommentsApplicationProps> = ({ locale, commentableId, commentableType }) => (
275
+ const CommentsApplication: React.SFC<CommentsApplicationProps> = ({ locale, toggleTranslations, commentableId, commentableType, singleCommentId, commentsMaxLength }) => (
199
276
  <Application locale={locale}>
200
277
  <CommentsWithData
278
+ commentsMaxLength={commentsMaxLength}
201
279
  commentableId={commentableId}
202
280
  commentableType={commentableType}
281
+ locale={locale}
282
+ toggleTranslations={toggleTranslations}
203
283
  orderBy="older"
284
+ singleCommentId={singleCommentId}
204
285
  />
205
286
  </Application>
206
287
  );