decidim-comments 0.20.1 → 0.23.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (122) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/javascripts/decidim/comments/bundle.js +65 -65
  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 +28 -8
  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 +31 -29
  16. data/app/frontend/comments/add_comment_form.component.tsx +34 -18
  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.yml +122 -0
  54. data/config/locales/en.yml +24 -1
  55. data/config/locales/eo.yml +1 -0
  56. data/config/locales/es-MX.yml +24 -1
  57. data/config/locales/es-PY.yml +24 -1
  58. data/config/locales/es.yml +24 -1
  59. data/config/locales/et-EE.yml +1 -0
  60. data/config/locales/et.yml +1 -0
  61. data/config/locales/eu.yml +4 -1
  62. data/config/locales/fi-plain.yml +24 -1
  63. data/config/locales/fi.yml +31 -8
  64. data/config/locales/fr-CA.yml +123 -0
  65. data/config/locales/fr.yml +25 -2
  66. data/config/locales/ga-IE.yml +1 -0
  67. data/config/locales/gl.yml +4 -1
  68. data/config/locales/hr-HR.yml +1 -0
  69. data/config/locales/hr.yml +1 -0
  70. data/config/locales/hu.yml +18 -2
  71. data/config/locales/id-ID.yml +4 -1
  72. data/config/locales/is-IS.yml +3 -3
  73. data/config/locales/is.yml +76 -0
  74. data/config/locales/it.yml +23 -1
  75. data/config/locales/ja-JP.yml +120 -0
  76. data/config/locales/ja.yml +121 -0
  77. data/config/locales/ko-KR.yml +1 -0
  78. data/config/locales/ko.yml +1 -0
  79. data/config/locales/lt-LT.yml +1 -0
  80. data/config/locales/lt.yml +1 -0
  81. data/config/locales/lv.yml +118 -0
  82. data/config/locales/mt-MT.yml +1 -0
  83. data/config/locales/mt.yml +1 -0
  84. data/config/locales/nl.yml +26 -3
  85. data/config/locales/no.yml +24 -2
  86. data/config/locales/om-ET.yml +1 -0
  87. data/config/locales/pl.yml +62 -40
  88. data/config/locales/pt-BR.yml +5 -2
  89. data/config/locales/pt.yml +47 -25
  90. data/config/locales/ro-RO.yml +124 -0
  91. data/config/locales/ru.yml +4 -1
  92. data/config/locales/sk-SK.yml +116 -0
  93. data/config/locales/sk.yml +120 -0
  94. data/config/locales/sl.yml +4 -0
  95. data/config/locales/so-SO.yml +1 -0
  96. data/config/locales/sr-CS.yml +20 -0
  97. data/config/locales/sv.yml +26 -3
  98. data/config/locales/ti-ER.yml +1 -0
  99. data/config/locales/tr-TR.yml +4 -1
  100. data/config/locales/uk.yml +4 -2
  101. data/config/locales/vi-VN.yml +1 -0
  102. data/config/locales/vi.yml +1 -0
  103. data/config/locales/zh-CN.yml +121 -0
  104. data/config/locales/zh-TW.yml +1 -0
  105. data/db/migrate/20200320105911_index_foreign_keys_in_decidim_comments_comments.rb +7 -0
  106. data/db/migrate/20200706123136_make_comments_handle_i18n.rb +41 -0
  107. data/db/migrate/20200828101910_add_commentable_counter_cache_to_comments.rb +9 -0
  108. data/lib/decidim/comments.rb +1 -0
  109. data/lib/decidim/comments/api/comment_type.rb +5 -1
  110. data/lib/decidim/comments/comment_serializer.rb +7 -2
  111. data/lib/decidim/comments/comment_vote_serializer.rb +5 -1
  112. data/lib/decidim/comments/commentable.rb +11 -0
  113. data/lib/decidim/comments/comments_helper.rb +28 -4
  114. data/lib/decidim/comments/engine.rb +13 -0
  115. data/lib/decidim/comments/markdown.rb +55 -0
  116. data/lib/decidim/comments/mutation_extensions.rb +8 -0
  117. data/lib/decidim/comments/query_extensions.rb +4 -0
  118. data/lib/decidim/comments/test/factories.rb +10 -1
  119. data/lib/decidim/comments/test/shared_examples/comment_event.rb +12 -2
  120. data/lib/decidim/comments/test/shared_examples/create_comment_context.rb +3 -2
  121. data/lib/decidim/comments/version.rb +1 -1
  122. metadata +71 -10
@@ -0,0 +1,9 @@
1
+ # frozen-string_literal: true
2
+
3
+ module Decidim
4
+ module Comments
5
+ class CommentByFollowedUserGroupEvent < Decidim::Events::SimpleEvent
6
+ include Decidim::Comments::CommentEvent
7
+ end
8
+ end
9
+ end
@@ -9,18 +9,38 @@ module Decidim
9
9
  include Decidim::Events::AuthorEvent
10
10
 
11
11
  included do
12
- delegate :author, to: :comment
12
+ def resource_text
13
+ comment.formatted_body
14
+ end
13
15
 
14
- def resource_path
15
- resource_locator.path(url_params)
16
+ def author
17
+ comment.normalized_author
16
18
  end
17
19
 
18
- def resource_url
19
- resource_locator.url(url_params)
20
+ def author_presenter
21
+ return unless author
22
+
23
+ @author_presenter ||= case author
24
+ when Decidim::User
25
+ Decidim::UserPresenter.new(author)
26
+ when Decidim::UserGroup
27
+ Decidim::UserGroupPresenter.new(author)
28
+ end
20
29
  end
21
30
 
22
- def resource_text
23
- comment.formatted_body
31
+ def author
32
+ comment.normalized_author
33
+ end
34
+
35
+ def author_presenter
36
+ return unless author
37
+
38
+ @author_presenter ||= case author
39
+ when Decidim::User
40
+ Decidim::UserPresenter.new(author)
41
+ when Decidim::UserGroup
42
+ Decidim::UserGroupPresenter.new(author)
43
+ end
24
44
  end
25
45
 
26
46
  private
@@ -29,7 +49,7 @@ module Decidim
29
49
  @comment ||= Decidim::Comments::Comment.find(extra[:comment_id])
30
50
  end
31
51
 
32
- def url_params
52
+ def resource_url_params
33
53
  { anchor: "comment_#{comment.id}" }
34
54
  end
35
55
  end
@@ -0,0 +1,10 @@
1
+ # frozen-string_literal: true
2
+
3
+ module Decidim
4
+ module Comments
5
+ class UserGroupMentionedEvent < Decidim::Events::SimpleEvent
6
+ include Decidim::Comments::CommentEvent
7
+ include Decidim::Events::UserGroupEvent
8
+ end
9
+ end
10
+ end
@@ -8,11 +8,27 @@ module Decidim
8
8
  attribute :body, String
9
9
  attribute :alignment, Integer
10
10
  attribute :user_group_id, Integer
11
+ attribute :commentable
11
12
 
12
13
  mimic :comment
13
14
 
14
- validates :body, presence: true, length: { maximum: 1000 }
15
+ validates :body, presence: true, length: { maximum: ->(form) { form.max_length } }
15
16
  validates :alignment, inclusion: { in: [0, 1, -1] }, if: ->(form) { form.alignment.present? }
17
+
18
+ validate :max_depth
19
+
20
+ def max_length
21
+ return current_component.settings.comments_max_length if current_component.try { settings.comments_max_length.positive? }
22
+ return current_organization.comments_max_length if current_organization.comments_max_length.positive?
23
+
24
+ 1000
25
+ end
26
+
27
+ def max_depth
28
+ return unless commentable.respond_to?(:depth)
29
+
30
+ errors.add(:base, :invalid) if commentable.depth >= Comment::MAX_DEPTH
31
+ end
16
32
  end
17
33
  end
18
34
  end
@@ -3,12 +3,20 @@ import assetUrl from "../support/asset_url";
3
3
 
4
4
  interface IconProps {
5
5
  name: string;
6
+ title?: string;
6
7
  iconExtraClassName?: string;
8
+ role?: string;
7
9
  }
8
10
 
9
- export const Icon: React.SFC<IconProps> = ({ name, iconExtraClassName }) => {
11
+ export const Icon: React.SFC<IconProps> = ({ name, title, iconExtraClassName, role = "none presentation" }) => {
12
+ let titleElement = null;
13
+ if (title) {
14
+ titleElement = <title>{title}</title>;
15
+ }
16
+
10
17
  return (
11
- <svg className={`icon ${iconExtraClassName} ${name}`}>
18
+ <svg className={`icon ${iconExtraClassName} ${name}`} role={role}>
19
+ {titleElement}
12
20
  <use
13
21
  xmlnsXlink="http://www.w3.org/1999/xlink"
14
22
  xlinkHref={`${assetUrl("icons.svg")}#${name}`}
@@ -23,12 +31,16 @@ Icon.defaultProps = {
23
31
 
24
32
  interface IconWithoutUserAgentProps {
25
33
  name: string;
34
+ title?: string;
26
35
  iconExtraClassName?: string;
36
+ role?: string;
27
37
  }
28
38
 
29
39
  const IconWithoutUserAgent: React.SFC<IconWithoutUserAgentProps> = ({
30
40
  name,
31
- iconExtraClassName
32
- }) => <Icon name={name} iconExtraClassName={iconExtraClassName} />;
41
+ title,
42
+ iconExtraClassName,
43
+ role = "none presentation"
44
+ }) => <Icon name={name} title={title} iconExtraClassName={iconExtraClassName} role={role} />;
33
45
 
34
46
  export default IconWithoutUserAgent;
@@ -2,14 +2,15 @@ import { mount, ReactWrapper, shallow } from "enzyme";
2
2
  import * as $ from "jquery";
3
3
  import * as React from "react";
4
4
 
5
- import { AddCommentForm, MAX_LENGTH } from "./add_comment_form.component";
5
+ import { AddCommentForm } from "./add_comment_form.component";
6
6
 
7
7
  import generateUserData from "../support/generate_user_data";
8
8
  import generateUserGroupData from "../support/generate_user_group_data";
9
9
  import { loadLocaleTranslations } from "../support/load_translations";
10
10
 
11
- describe("<AddCommentForm />", () => {
11
+ describe("<AddCommentForm commentsMaxLength={commentsMaxLength} />", () => {
12
12
  let session: any = null;
13
+ const commentsMaxLength: number = 1000;
13
14
  const commentable = {
14
15
  id: "1",
15
16
  type: "Decidim::DummyResources::DummyResource"
@@ -18,6 +19,7 @@ describe("<AddCommentForm />", () => {
18
19
  const addCommentStub = (): any => {
19
20
  return null;
20
21
  };
22
+ const context = {locale: undefined, toggleTranslations: undefined};
21
23
 
22
24
  beforeEach(() => {
23
25
  loadLocaleTranslations("en");
@@ -35,44 +37,44 @@ describe("<AddCommentForm />", () => {
35
37
  });
36
38
 
37
39
  it("should render a div with class add-comment", () => {
38
- const wrapper = shallow(<AddCommentForm addComment={addCommentStub} session={session} commentable={commentable} rootCommentable={commentable} orderBy={orderBy} />);
40
+ const wrapper = shallow(<AddCommentForm commentsMaxLength={commentsMaxLength} addComment={addCommentStub} session={session} commentable={commentable} rootCommentable={commentable} orderBy={orderBy} />);
39
41
  expect(wrapper.find("div.add-comment")).toBeDefined();
40
42
  });
41
43
 
42
44
  it("should have a reference to body textarea", () => {
43
- const wrapper = mount(<AddCommentForm addComment={addCommentStub} session={session} commentable={commentable} rootCommentable={commentable} orderBy={orderBy} />);
45
+ const wrapper = mount(<AddCommentForm commentsMaxLength={commentsMaxLength} addComment={addCommentStub} session={session} commentable={commentable} rootCommentable={commentable} orderBy={orderBy} />);
44
46
  expect((wrapper.instance() as AddCommentForm).bodyTextArea).toBeDefined();
45
47
  });
46
48
 
47
49
  it("should initialize with a state property disabled as true", () => {
48
- const wrapper = mount(<AddCommentForm addComment={addCommentStub} session={session} commentable={commentable} rootCommentable={commentable} orderBy={orderBy} />);
50
+ const wrapper = mount(<AddCommentForm commentsMaxLength={commentsMaxLength} addComment={addCommentStub} session={session} commentable={commentable} rootCommentable={commentable} orderBy={orderBy} />);
49
51
  expect(wrapper.state()).toHaveProperty("disabled", true);
50
52
  });
51
53
 
52
54
  it("should have a default prop showTitle as true", () => {
53
- const wrapper = mount(<AddCommentForm addComment={addCommentStub} session={session} commentable={commentable} rootCommentable={commentable} orderBy={orderBy} />);
55
+ const wrapper = mount(<AddCommentForm commentsMaxLength={commentsMaxLength} addComment={addCommentStub} session={session} commentable={commentable} rootCommentable={commentable} orderBy={orderBy} />);
54
56
  expect(wrapper.props()).toHaveProperty("showTitle", true);
55
57
  });
56
58
 
57
59
  it("should not render the title if prop showTitle is false", () => {
58
- const wrapper = shallow(<AddCommentForm addComment={addCommentStub} session={session} commentable={commentable} showTitle={false} rootCommentable={commentable} orderBy={orderBy} />);
59
- expect(wrapper.find("h5.section-heading").exists()).toBeFalsy();
60
+ const wrapper = shallow(<AddCommentForm commentsMaxLength={commentsMaxLength} addComment={addCommentStub} session={session} commentable={commentable} showTitle={false} rootCommentable={commentable} orderBy={orderBy} />);
61
+ expect(wrapper.find("h4.section-heading").exists()).toBeFalsy();
60
62
  });
61
63
 
62
64
  it("should have a default prop submitButtonClassName as 'button button--sc'", () => {
63
- const wrapper = mount(<AddCommentForm addComment={addCommentStub} session={session} commentable={commentable} rootCommentable={commentable} orderBy={orderBy} />);
65
+ const wrapper = mount(<AddCommentForm commentsMaxLength={commentsMaxLength} addComment={addCommentStub} session={session} commentable={commentable} rootCommentable={commentable} orderBy={orderBy} />);
64
66
  expect(wrapper.props()).toHaveProperty("submitButtonClassName", "button button--sc");
65
67
  });
66
68
 
67
69
  it("should use prop submitButtonClassName as a className prop for submit button", () => {
68
- const wrapper = shallow(<AddCommentForm addComment={addCommentStub} session={session} commentable={commentable} submitButtonClassName="button small hollow" rootCommentable={commentable} orderBy={orderBy} />);
70
+ const wrapper = shallow(<AddCommentForm commentsMaxLength={commentsMaxLength} addComment={addCommentStub} session={session} commentable={commentable} submitButtonClassName="button small hollow" rootCommentable={commentable} orderBy={orderBy} />);
69
71
  expect(wrapper.find('button[type="submit"]').hasClass("button")).toBeTruthy();
70
72
  expect(wrapper.find('button[type="submit"]').hasClass("small")).toBeTruthy();
71
73
  expect(wrapper.find('button[type="submit"]').hasClass("hollow")).toBeTruthy();
72
74
  });
73
75
 
74
76
  it("should enable the submit button if textarea is not blank", () => {
75
- const wrapper = mount(<AddCommentForm addComment={addCommentStub} session={session} commentable={commentable} rootCommentable={commentable} orderBy={orderBy} />);
77
+ const wrapper = mount(<AddCommentForm commentsMaxLength={commentsMaxLength} addComment={addCommentStub} session={session} commentable={commentable} rootCommentable={commentable} orderBy={orderBy} />);
76
78
  wrapper.find("textarea").simulate("change", {
77
79
  target: {
78
80
  value: "This is a comment"
@@ -82,7 +84,7 @@ describe("<AddCommentForm />", () => {
82
84
  });
83
85
 
84
86
  it("should disable the submit button if textarea is blank", () => {
85
- const wrapper = mount(<AddCommentForm addComment={addCommentStub} session={session} commentable={commentable} rootCommentable={commentable} orderBy={orderBy} />);
87
+ const wrapper = mount(<AddCommentForm commentsMaxLength={commentsMaxLength} addComment={addCommentStub} session={session} commentable={commentable} rootCommentable={commentable} orderBy={orderBy} />);
86
88
  wrapper.find("textarea").simulate("change", {
87
89
  target: {
88
90
  value: "This will be deleted"
@@ -97,19 +99,19 @@ describe("<AddCommentForm />", () => {
97
99
  });
98
100
 
99
101
  it("should not render a div with class 'opinion-toggle'", () => {
100
- const wrapper = shallow(<AddCommentForm addComment={addCommentStub} session={session} commentable={commentable} rootCommentable={commentable} orderBy={orderBy} />);
102
+ const wrapper = shallow(<AddCommentForm commentsMaxLength={commentsMaxLength} addComment={addCommentStub} session={session} commentable={commentable} rootCommentable={commentable} orderBy={orderBy} />);
101
103
  expect(wrapper.find(".opinion-toggle").exists()).toBeFalsy();
102
104
  });
103
105
 
104
106
  it("should render the remaining character count", () => {
105
- const wrapper = shallow(<AddCommentForm addComment={addCommentStub} session={session} commentable={commentable} rootCommentable={commentable} orderBy={orderBy} />);
107
+ const wrapper = shallow(<AddCommentForm commentsMaxLength={commentsMaxLength} addComment={addCommentStub} session={session} commentable={commentable} rootCommentable={commentable} orderBy={orderBy} />);
106
108
  const commentBody = "This is a new comment!";
107
109
  wrapper.find("textarea").simulate("change", {
108
110
  target: {
109
111
  value: commentBody
110
112
  }
111
113
  });
112
- expect(wrapper.find(".remaining-character-count").text()).toContain(MAX_LENGTH - commentBody.length);
114
+ expect(wrapper.find(".remaining-character-count").text()).toContain(commentsMaxLength - commentBody.length);
113
115
  });
114
116
 
115
117
  describe("submitting the form", () => {
@@ -121,14 +123,14 @@ describe("<AddCommentForm />", () => {
121
123
  beforeEach(() => {
122
124
  addComment = jasmine.createSpy("addComment");
123
125
  onCommentAdded = jasmine.createSpy("onCommentAdded");
124
- wrapper = mount(<AddCommentForm addComment={addComment} session={session} commentable={commentable} onCommentAdded={onCommentAdded} rootCommentable={commentable} orderBy={orderBy} />);
126
+ wrapper = mount(<AddCommentForm commentsMaxLength={commentsMaxLength} addComment={addComment} session={session} commentable={commentable} onCommentAdded={onCommentAdded} rootCommentable={commentable} orderBy={orderBy} />);
125
127
  message = "This will be submitted";
126
128
  (wrapper.instance() as AddCommentForm).bodyTextArea.value = message;
127
129
  });
128
130
 
129
131
  it("should call addComment prop with the textarea value and state property alignment", () => {
130
132
  wrapper.find("form").simulate("submit");
131
- expect(addComment).toHaveBeenCalledWith({ body: message, alignment: 0 });
133
+ expect(addComment).toHaveBeenCalledWith({ body: message, alignment: 0 }, context);
132
134
  });
133
135
 
134
136
  it("should reset textarea", () => {
@@ -149,25 +151,25 @@ describe("<AddCommentForm />", () => {
149
151
  });
150
152
 
151
153
  it("should initialize state with a property alignment and value 0", () => {
152
- const wrapper = shallow(<AddCommentForm addComment={addCommentStub} session={session} commentable={commentable} arguable={true} rootCommentable={commentable} orderBy={orderBy} />);
154
+ const wrapper = shallow(<AddCommentForm commentsMaxLength={commentsMaxLength} addComment={addCommentStub} session={session} commentable={commentable} arguable={true} rootCommentable={commentable} orderBy={orderBy} />);
153
155
  expect(wrapper.state()).toHaveProperty("alignment", 0);
154
156
  });
155
157
 
156
158
  describe("when receiving an optional prop arguable with value true", () => {
157
159
  it("should render a div with class 'opinion-toggle'", () => {
158
- const wrapper = shallow(<AddCommentForm addComment={addCommentStub} session={session} commentable={commentable} arguable={true} rootCommentable={commentable} orderBy={orderBy} />);
160
+ const wrapper = shallow(<AddCommentForm commentsMaxLength={commentsMaxLength} addComment={addCommentStub} session={session} commentable={commentable} arguable={true} rootCommentable={commentable} orderBy={orderBy} />);
159
161
  expect(wrapper.find(".opinion-toggle")).toBeDefined();
160
162
  });
161
163
 
162
164
  it("should set state alignment to 1 if user clicks ok button and change its class", () => {
163
- const wrapper = shallow(<AddCommentForm addComment={addCommentStub} session={session} commentable={commentable} arguable={true} rootCommentable={commentable} orderBy={orderBy} />);
165
+ const wrapper = shallow(<AddCommentForm commentsMaxLength={commentsMaxLength} addComment={addCommentStub} session={session} commentable={commentable} arguable={true} rootCommentable={commentable} orderBy={orderBy} />);
164
166
  wrapper.find(".opinion-toggle--ok").simulate("click");
165
167
  expect(wrapper.find(".opinion-toggle--ok").hasClass("is-active")).toBeTruthy();
166
168
  expect(wrapper.state()).toHaveProperty("alignment", 1);
167
169
  });
168
170
 
169
171
  it("should set state alignment to -11 if user clicks ko button and change its class", () => {
170
- const wrapper = shallow(<AddCommentForm addComment={addCommentStub} session={session} commentable={commentable} arguable={true} rootCommentable={commentable} orderBy={orderBy} />);
172
+ const wrapper = shallow(<AddCommentForm commentsMaxLength={commentsMaxLength} addComment={addCommentStub} session={session} commentable={commentable} arguable={true} rootCommentable={commentable} orderBy={orderBy} />);
171
173
  wrapper.find(".opinion-toggle--ko").simulate("click");
172
174
  expect(wrapper.find(".opinion-toggle--ko").hasClass("is-active")).toBeTruthy();
173
175
  expect(wrapper.state()).toHaveProperty("alignment", -1);
@@ -180,7 +182,7 @@ describe("<AddCommentForm />", () => {
180
182
 
181
183
  beforeEach(() => {
182
184
  addComment = jasmine.createSpy("addComment");
183
- wrapper = mount(<AddCommentForm addComment={addComment} session={session} commentable={commentable} arguable={true} rootCommentable={commentable} orderBy={orderBy} />);
185
+ wrapper = mount(<AddCommentForm commentsMaxLength={commentsMaxLength} addComment={addComment} session={session} commentable={commentable} arguable={true} rootCommentable={commentable} orderBy={orderBy} />);
184
186
  message = "This will be submitted";
185
187
  (wrapper.instance() as AddCommentForm).bodyTextArea.value = message;
186
188
  });
@@ -188,7 +190,7 @@ describe("<AddCommentForm />", () => {
188
190
  it("should call addComment prop with the state's property alignment", () => {
189
191
  wrapper.find("button.opinion-toggle--ko").simulate("click");
190
192
  wrapper.find("form").simulate("submit");
191
- expect(addComment).toHaveBeenCalledWith({ body: message, alignment: -1 });
193
+ expect(addComment).toHaveBeenCalledWith({ body: message, alignment: -1 }, context);
192
194
  });
193
195
 
194
196
  it("should reset the state to its initial state", () => {
@@ -208,12 +210,12 @@ describe("<AddCommentForm />", () => {
208
210
  });
209
211
 
210
212
  it("should have a reference to user_group_id select", () => {
211
- const wrapper = mount(<AddCommentForm addComment={addCommentStub} session={session} commentable={commentable} rootCommentable={commentable} orderBy={orderBy} />);
213
+ const wrapper = mount(<AddCommentForm commentsMaxLength={commentsMaxLength} addComment={addCommentStub} session={session} commentable={commentable} rootCommentable={commentable} orderBy={orderBy} />);
212
214
  expect((wrapper.instance() as AddCommentForm).userGroupIdSelect).toBeDefined();
213
215
  });
214
216
 
215
217
  it("should render a select with option tags for each verified user group", () => {
216
- const wrapper = mount(<AddCommentForm addComment={addCommentStub} session={session} commentable={commentable} rootCommentable={commentable} orderBy={orderBy} />);
218
+ const wrapper = mount(<AddCommentForm commentsMaxLength={commentsMaxLength} addComment={addCommentStub} session={session} commentable={commentable} rootCommentable={commentable} orderBy={orderBy} />);
217
219
  expect(wrapper.find("select").children("option").length).toBe(3);
218
220
  });
219
221
 
@@ -225,7 +227,7 @@ describe("<AddCommentForm />", () => {
225
227
 
226
228
  beforeEach(() => {
227
229
  addComment = jasmine.createSpy("addComment");
228
- wrapper = mount(<AddCommentForm addComment={addComment} session={session} commentable={commentable} rootCommentable={commentable} orderBy={orderBy} />);
230
+ wrapper = mount(<AddCommentForm commentsMaxLength={commentsMaxLength} addComment={addComment} session={session} commentable={commentable} rootCommentable={commentable} orderBy={orderBy} />);
229
231
  message = "This will be submitted";
230
232
  userGroupId = session.verifiedUserGroups[1].id;
231
233
  (wrapper.instance() as AddCommentForm).bodyTextArea.value = message;
@@ -234,7 +236,7 @@ describe("<AddCommentForm />", () => {
234
236
 
235
237
  it("should call addComment prop with the body textarea, alignment and user_group_id select values", () => {
236
238
  wrapper.find("form").simulate("submit");
237
- expect(addComment).toHaveBeenCalledWith({ body: message, alignment: 0, userGroupId });
239
+ expect(addComment).toHaveBeenCalledWith({ body: message, alignment: 0, userGroupId }, context);
238
240
  });
239
241
 
240
242
  describe("when user_group_id is blank", () => {
@@ -244,7 +246,7 @@ describe("<AddCommentForm />", () => {
244
246
 
245
247
  it("should call addComment prop with the body textarea and alignment", () => {
246
248
  wrapper.find("form").simulate("submit");
247
- expect(addComment).toHaveBeenCalledWith({ body: message, alignment: 0 });
249
+ expect(addComment).toHaveBeenCalledWith({ body: message, alignment: 0 }, context);
248
250
  });
249
251
  });
250
252
  });
@@ -256,7 +258,7 @@ describe("<AddCommentForm />", () => {
256
258
  });
257
259
 
258
260
  it("display a message to sign in or sign up", () => {
259
- const wrapper = mount(<AddCommentForm addComment={addCommentStub} session={session} commentable={commentable} rootCommentable={commentable} orderBy={orderBy} />);
261
+ const wrapper = mount(<AddCommentForm commentsMaxLength={commentsMaxLength} addComment={addCommentStub} session={session} commentable={commentable} rootCommentable={commentable} orderBy={orderBy} />);
260
262
  expect(wrapper.find("span").text()).toContain("sign up");
261
263
  });
262
264
  });
@@ -4,6 +4,8 @@ import * as React from "react";
4
4
  import { graphql } from "react-apollo";
5
5
  import * as uuid from "uuid";
6
6
 
7
+ const PropTypes = require("prop-types");
8
+
7
9
  import Icon from "../application/icon.component";
8
10
 
9
11
  const { I18n, Translate } = require("react-i18nify");
@@ -27,9 +29,10 @@ interface AddCommentFormProps {
27
29
  autoFocus?: boolean;
28
30
  arguable?: boolean;
29
31
  userAllowedToComment?: boolean;
30
- addComment?: (data: { body: string, alignment: number, userGroupId?: string }) => void;
32
+ addComment?: (data: { body: string, alignment: number, userGroupId?: string }, context: any) => void;
31
33
  onCommentAdded?: () => void;
32
34
  orderBy: string;
35
+ commentsMaxLength: number;
33
36
  }
34
37
 
35
38
  interface AddCommentFormState {
@@ -39,8 +42,6 @@ interface AddCommentFormState {
39
42
  remainingCharacterCount: number;
40
43
  }
41
44
 
42
- export const MAX_LENGTH = 1000;
43
-
44
45
  /**
45
46
  * Renders a form to create new comments.
46
47
  * @class
@@ -54,6 +55,11 @@ export class AddCommentForm extends React.Component<AddCommentFormProps, AddComm
54
55
  autoFocus: false
55
56
  };
56
57
 
58
+ public static contextTypes: any = {
59
+ locale: PropTypes.string,
60
+ toggleTranslations: PropTypes.bool
61
+ };
62
+
57
63
  public bodyTextArea: HTMLTextAreaElement;
58
64
  public userGroupIdSelect: HTMLSelectElement;
59
65
 
@@ -64,7 +70,7 @@ export class AddCommentForm extends React.Component<AddCommentFormProps, AddComm
64
70
  disabled: true,
65
71
  error: false,
66
72
  alignment: 0,
67
- remainingCharacterCount: MAX_LENGTH
73
+ remainingCharacterCount: props.commentsMaxLength
68
74
  };
69
75
  }
70
76
 
@@ -100,9 +106,9 @@ export class AddCommentForm extends React.Component<AddCommentFormProps, AddComm
100
106
 
101
107
  if (showTitle) {
102
108
  return (
103
- <h5 className="section-heading">
109
+ <h4 className="section-heading">
104
110
  {I18n.t("components.add_comment_form.title")}
105
- </h5>
111
+ </h4>
106
112
  );
107
113
  }
108
114
 
@@ -176,7 +182,7 @@ export class AddCommentForm extends React.Component<AddCommentFormProps, AddComm
176
182
  * @returns {Void|DOMElement} - The heading or an empty element
177
183
  */
178
184
  private _renderTextArea() {
179
- const { commentable: { id, type }, autoFocus } = this.props;
185
+ const { commentable: { id, type }, autoFocus, commentsMaxLength } = this.props;
180
186
  const { error } = this.state;
181
187
  const className = classnames({ "is-invalid-input": error });
182
188
 
@@ -185,11 +191,11 @@ export class AddCommentForm extends React.Component<AddCommentFormProps, AddComm
185
191
  id: `add-comment-${type}-${id}`,
186
192
  className,
187
193
  rows: "4",
188
- maxLength: MAX_LENGTH,
194
+ maxLength: commentsMaxLength,
189
195
  required: "required",
190
- pattern: `^(.){0,${MAX_LENGTH}}$`,
196
+ pattern: `^(.){0,${commentsMaxLength}}$`,
191
197
  placeholder: I18n.t("components.add_comment_form.form.body.placeholder"),
192
- onChange: (evt: React.ChangeEvent<HTMLTextAreaElement>) => this._checkCommentBody(evt.target.value)
198
+ onChange: (evt: React.ChangeEvent<HTMLTextAreaElement>) => this._checkCommentBody(evt.target.value, commentsMaxLength as number)
193
199
  };
194
200
 
195
201
  if (autoFocus) {
@@ -207,12 +213,13 @@ export class AddCommentForm extends React.Component<AddCommentFormProps, AddComm
207
213
  * @returns {Void|DOMElement} - The error or an empty element
208
214
  */
209
215
  private _renderTextAreaError() {
216
+ const { commentsMaxLength } = this.props;
210
217
  const { error } = this.state;
211
218
 
212
219
  if (error) {
213
220
  return (
214
221
  <span className="form-error is-visible">
215
- {I18n.t("components.add_comment_form.form.form_error", { length: MAX_LENGTH })}
222
+ {I18n.t("components.add_comment_form.form.form_error", { length: commentsMaxLength })}
216
223
  </span>
217
224
  );
218
225
  }
@@ -317,10 +324,10 @@ export class AddCommentForm extends React.Component<AddCommentFormProps, AddComm
317
324
  * @param {string} body - The comment's body
318
325
  * @returns {Void} - Returns nothing
319
326
  */
320
- private _checkCommentBody(body: string) {
327
+ private _checkCommentBody(body: string, commentsMaxLength: number) {
321
328
  this.setState({
322
- disabled: body === "", error: body === "" || body.length > MAX_LENGTH,
323
- remainingCharacterCount: MAX_LENGTH - body.length
329
+ disabled: body === "", error: body === "" || body.length > commentsMaxLength,
330
+ remainingCharacterCount: commentsMaxLength - body.length
324
331
  });
325
332
  }
326
333
 
@@ -343,7 +350,7 @@ export class AddCommentForm extends React.Component<AddCommentFormProps, AddComm
343
350
  }
344
351
 
345
352
  if (addComment) {
346
- addComment(addCommentParams);
353
+ addComment(addCommentParams, this.context);
347
354
  }
348
355
 
349
356
  this.bodyTextArea.value = "";
@@ -360,10 +367,12 @@ const getCommentsQuery = require("../queries/comments.query.graphql");
360
367
 
361
368
  const AddCommentFormWithMutation = graphql<addCommentMutation, AddCommentFormProps>(addCommentMutation, {
362
369
  props: ({ ownProps, mutate }) => ({
363
- addComment: ({ body, alignment, userGroupId }: { body: string, alignment: number, userGroupId: string }) => {
370
+ addComment: ({ body, alignment, userGroupId }: { body: string, alignment: number, userGroupId: string }, { locale, toggleTranslations }: any) => {
364
371
  if (mutate) {
365
372
  mutate({
366
373
  variables: {
374
+ locale,
375
+ toggleTranslations,
367
376
  commentableId: ownProps.commentable.id,
368
377
  commentableType: ownProps.commentable.type,
369
378
  body,
@@ -381,10 +390,14 @@ const AddCommentFormWithMutation = graphql<addCommentMutation, AddCommentFormPro
381
390
  createdAt: new Date().toISOString(),
382
391
  body,
383
392
  formattedBody: body,
393
+ formattedCreatedAt: new Date().toISOString(),
384
394
  alignment,
385
395
  author: {
386
396
  __typename: "User",
387
397
  name: ownProps.session && ownProps.session.user.name,
398
+ nickname: ownProps.session && ownProps.session.user.name,
399
+ profilePath: null,
400
+ badge: null,
388
401
  avatarUrl: ownProps.session && ownProps.session.user.avatarUrl,
389
402
  deleted: false
390
403
  },
@@ -402,14 +415,17 @@ const AddCommentFormWithMutation = graphql<addCommentMutation, AddCommentFormPro
402
415
  },
403
416
  update: (store, { data }: { data: addCommentMutation }) => {
404
417
  const variables = {
418
+ locale,
419
+ toggleTranslations,
405
420
  commentableId: ownProps.rootCommentable.id,
406
421
  commentableType: ownProps.rootCommentable.type,
407
- orderBy: ownProps.orderBy
422
+ orderBy: ownProps.orderBy,
423
+ singleCommentId: null
408
424
  };
409
425
  const prev = store.readQuery<GetCommentsQuery>({
410
426
  query: getCommentsQuery,
411
427
  variables
412
- });
428
+ });
413
429
  const { id, type } = ownProps.commentable;
414
430
  const newComment = data.commentable && data.commentable.addComment;
415
431
  let comments = [];