decidim-comments 0.0.6 → 0.0.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (68) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +33 -0
  3. data/app/assets/javascripts/decidim/comments/bundle.js +44 -44
  4. data/app/assets/javascripts/decidim/comments/bundle.js.map +1 -1
  5. data/app/assets/javascripts/decidim/comments/comments.js.erb +2 -0
  6. data/app/frontend/application/{apollo_client.js → apollo_client.ts} +5 -5
  7. data/app/frontend/application/application.component.test.tsx +36 -0
  8. data/app/frontend/application/application.component.tsx +37 -0
  9. data/app/frontend/application/icon.component.test.tsx +49 -0
  10. data/app/frontend/application/icon.component.tsx +35 -0
  11. data/app/frontend/comments/{add_comment_form.component.test.jsx → add_comment_form.component.test.tsx} +98 -92
  12. data/app/frontend/comments/{add_comment_form.component.jsx → add_comment_form.component.tsx} +152 -153
  13. data/app/frontend/comments/{comment.component.test.jsx → comment.component.test.tsx} +59 -71
  14. data/app/frontend/comments/{comment.component.jsx → comment.component.tsx} +114 -116
  15. data/app/frontend/comments/comment_order_selector.component.test.tsx +21 -0
  16. data/app/frontend/comments/comment_order_selector.component.tsx +88 -0
  17. data/app/frontend/comments/comment_thread.component.test.tsx +65 -0
  18. data/app/frontend/comments/comment_thread.component.tsx +70 -0
  19. data/app/frontend/comments/{comments.component.test.jsx → comments.component.test.tsx} +38 -81
  20. data/app/frontend/comments/{comments.component.jsx → comments.component.tsx} +49 -63
  21. data/app/frontend/comments/down_vote_button.component.test.tsx +39 -0
  22. data/app/frontend/comments/down_vote_button.component.tsx +89 -0
  23. data/app/frontend/comments/up_vote_button.component.test.tsx +39 -0
  24. data/app/frontend/comments/up_vote_button.component.tsx +89 -0
  25. data/app/frontend/comments/vote_button.component.tsx +36 -0
  26. data/app/frontend/comments/{vote_button_component.test.jsx → vote_button_component.test.tsx} +16 -20
  27. data/app/frontend/entry.ts +19 -0
  28. data/app/frontend/{comments → fragments}/add_comment_form_commentable.fragment.graphql +1 -1
  29. data/app/frontend/{comments → fragments}/add_comment_form_session.fragment.graphql +1 -1
  30. data/app/frontend/{comments → fragments}/comment.fragment.graphql +3 -1
  31. data/app/frontend/{comments → fragments}/comment_data.fragment.graphql +6 -3
  32. data/app/frontend/{comments → fragments}/comment_thread.fragment.graphql +3 -1
  33. data/app/frontend/{comments/down_vote.fragment.graphql → fragments/down_vote_button.fragment.graphql} +2 -2
  34. data/app/frontend/{comments/up_vote.fragment.graphql → fragments/up_vote_button.fragment.graphql} +2 -2
  35. data/app/frontend/{comments/add_comment_form.mutation.graphql → mutations/add_comment.mutation.graphql} +3 -1
  36. data/app/frontend/{comments → mutations}/down_vote.mutation.graphql +3 -1
  37. data/app/frontend/{comments → mutations}/up_vote.mutation.graphql +3 -1
  38. data/app/frontend/{comments → queries}/comments.query.graphql +4 -1
  39. data/app/frontend/support/{asset_url.js → asset_url.ts} +1 -1
  40. data/app/frontend/support/{generate_comments_data.js → generate_comments_data.ts} +11 -6
  41. data/app/frontend/support/{generate_user_data.js → generate_user_data.ts} +2 -2
  42. data/app/frontend/support/{generate_user_group_data.js → generate_user_group_data.ts} +2 -2
  43. data/app/frontend/support/graphql_transformer.js +32 -0
  44. data/app/frontend/support/load_translations.ts +44 -0
  45. data/app/frontend/support/{require_all.js → require_all.ts} +1 -1
  46. data/app/frontend/support/{resolve_graphql_query.js → resolve_graphql_query.ts} +7 -7
  47. data/app/frontend/support/schema.ts +119 -0
  48. data/config/locales/eu.yml +29 -5
  49. metadata +49 -51
  50. data/app/frontend/application/application.component.jsx +0 -37
  51. data/app/frontend/application/application.component.test.jsx +0 -33
  52. data/app/frontend/application/icon.component.jsx +0 -26
  53. data/app/frontend/application/icon.component.test.jsx +0 -53
  54. data/app/frontend/comments/comment_order_selector.component.jsx +0 -72
  55. data/app/frontend/comments/comment_order_selector.component.test.jsx +0 -20
  56. data/app/frontend/comments/comment_thread.component.jsx +0 -75
  57. data/app/frontend/comments/comment_thread.component.test.jsx +0 -83
  58. data/app/frontend/comments/down_vote_button.component.jsx +0 -98
  59. data/app/frontend/comments/down_vote_button.component.test.jsx +0 -48
  60. data/app/frontend/comments/featured_comment.component.jsx +0 -23
  61. data/app/frontend/comments/featured_comment.component.test.jsx +0 -15
  62. data/app/frontend/comments/up_vote_button.component.jsx +0 -98
  63. data/app/frontend/comments/up_vote_button.component.test.jsx +0 -48
  64. data/app/frontend/comments/vote_button.component.jsx +0 -32
  65. data/app/frontend/entry.js +0 -17
  66. data/app/frontend/entry.test.js +0 -31
  67. data/app/frontend/support/load_translations.js +0 -23
  68. data/app/frontend/support/stub_component.js +0 -29
@@ -0,0 +1,35 @@
1
+ import * as React from "react";
2
+ import assetUrl from "../support/asset_url";
3
+
4
+ interface IconProps {
5
+ name: string;
6
+ userAgent: string;
7
+ iconExtraClassName?: string;
8
+ }
9
+
10
+ export const Icon: React.SFC<IconProps> = ({ name, userAgent, iconExtraClassName }) => {
11
+ if (userAgent.match(/PhantomJS/) || userAgent.match(/Node/)) {
12
+ return <span className={`icon ${iconExtraClassName} ${name}`}>{name}</span>;
13
+ }
14
+
15
+ return (
16
+ <svg className={`icon ${iconExtraClassName} ${name}`}>
17
+ <use xmlnsXlink="http://www.w3.org/1999/xlink" xlinkHref={`${assetUrl("icons.svg")}#${name}`} />
18
+ </svg>
19
+ );
20
+ };
21
+
22
+ Icon.defaultProps = {
23
+ iconExtraClassName: "icon--before",
24
+ };
25
+
26
+ interface IconWithoutUserAgentProps {
27
+ name: string;
28
+ iconExtraClassName?: string;
29
+ }
30
+
31
+ const IconWithoutUserAgent: React.SFC<IconWithoutUserAgentProps> = ({ name, iconExtraClassName }) => (
32
+ <Icon name={name} userAgent={navigator.userAgent} iconExtraClassName={iconExtraClassName} />
33
+ );
34
+
35
+ export default IconWithoutUserAgent;
@@ -1,184 +1,190 @@
1
- /* eslint-disable no-unused-expressions */
2
- import { shallow, mount } from 'enzyme';
1
+ import { mount, ReactWrapper, shallow } from "enzyme";
2
+ import * as React from "react";
3
3
 
4
- import { AddCommentForm } from './add_comment_form.component';
4
+ import { AddCommentForm } from "./add_comment_form.component";
5
5
 
6
- import generateUserData from '../support/generate_user_data';
7
- import generateUserGroupData from '../support/generate_user_group_data';
6
+ import generateUserData from "../support/generate_user_data";
7
+ import generateUserGroupData from "../support/generate_user_group_data";
8
+ import { loadLocaleTranslations } from "../support/load_translations";
8
9
 
9
10
  describe("<AddCommentForm />", () => {
10
- let session = null;
11
+ let session: any = null;
11
12
  const commentable = {
12
13
  id: "1",
13
- type: "Decidim::DummyResource"
14
+ type: "Decidim::DummyResource",
14
15
  };
15
- const addCommentStub = () => {
16
+ const addCommentStub = (): any => {
16
17
  return null;
17
- }
18
+ };
18
19
 
19
20
  beforeEach(() => {
21
+ loadLocaleTranslations("en");
20
22
  session = {
21
23
  user: generateUserData(),
22
- verifiedUserGroups: []
24
+ verifiedUserGroups: [],
25
+ };
26
+ window.DecidimComments = {
27
+ assets: {
28
+ "icons.svg": "/assets/icons.svg",
29
+ },
23
30
  };
24
31
  });
25
32
 
26
33
  it("should render a div with class add-comment", () => {
27
34
  const wrapper = shallow(<AddCommentForm addComment={addCommentStub} session={session} commentable={commentable} />);
28
- expect(wrapper.find('div.add-comment')).to.present();
35
+ expect(wrapper.find("div.add-comment")).toBeDefined();
29
36
  });
30
37
 
31
38
  it("should have a reference to body textarea", () => {
32
39
  const wrapper = mount(<AddCommentForm addComment={addCommentStub} session={session} commentable={commentable} />);
33
- expect(wrapper.instance().bodyTextArea).to.be.ok;
40
+ expect((wrapper.instance() as AddCommentForm).bodyTextArea).toBeDefined();
34
41
  });
35
42
 
36
43
  it("should initialize with a state property disabled as true", () => {
37
44
  const wrapper = mount(<AddCommentForm addComment={addCommentStub} session={session} commentable={commentable} />);
38
- expect(wrapper).to.have.state('disabled', true);
45
+ expect(wrapper.state()).toHaveProperty("disabled", true);
39
46
  });
40
47
 
41
48
  it("should have a default prop showTitle as true", () => {
42
49
  const wrapper = mount(<AddCommentForm addComment={addCommentStub} session={session} commentable={commentable} />);
43
- expect(wrapper).to.have.prop('showTitle').equal(true);
50
+ expect(wrapper.props()).toHaveProperty("showTitle", true);
44
51
  });
45
52
 
46
53
  it("should not render the title if prop showTitle is false", () => {
47
54
  const wrapper = shallow(<AddCommentForm addComment={addCommentStub} session={session} commentable={commentable} showTitle={false} />);
48
- expect(wrapper.find('h5.section-heading')).not.to.be.present();
55
+ expect(wrapper.find("h5.section-heading").exists()).toBeFalsy();
49
56
  });
50
57
 
51
58
  it("should have a default prop submitButtonClassName as 'button button--sc'", () => {
52
59
  const wrapper = mount(<AddCommentForm addComment={addCommentStub} session={session} commentable={commentable} />);
53
- expect(wrapper).to.have.prop('submitButtonClassName').equal('button button--sc');
60
+ expect(wrapper.props()).toHaveProperty("submitButtonClassName", "button button--sc");
54
61
  });
55
62
 
56
63
  it("should have a default prop maxLength of 1000", () => {
57
64
  const wrapper = mount(<AddCommentForm addComment={addCommentStub} session={session} commentable={commentable} />);
58
- expect(wrapper).to.have.prop('maxLength').equal(1000);
65
+ expect(wrapper.props()).toHaveProperty("maxLength", 1000);
59
66
  });
60
67
 
61
-
62
68
  it("should use prop submitButtonClassName as a className prop for submit button", () => {
63
69
  const wrapper = shallow(<AddCommentForm addComment={addCommentStub} session={session} commentable={commentable} submitButtonClassName="button small hollow" />);
64
- expect(wrapper.find('button[type="submit"]')).to.have.className('button');
65
- expect(wrapper.find('button[type="submit"]')).to.have.className('small');
66
- expect(wrapper.find('button[type="submit"]')).to.have.className('hollow');
70
+ expect(wrapper.find('button[type="submit"]').hasClass("button")).toBeTruthy();
71
+ expect(wrapper.find('button[type="submit"]').hasClass("small")).toBeTruthy();
72
+ expect(wrapper.find('button[type="submit"]').hasClass("hollow")).toBeTruthy();
67
73
  });
68
74
 
69
75
  it("should enable the submit button if textarea is not blank", () => {
70
76
  const wrapper = mount(<AddCommentForm addComment={addCommentStub} session={session} commentable={commentable} />);
71
- wrapper.find('textarea').simulate('change', {
77
+ wrapper.find("textarea").simulate("change", {
72
78
  target: {
73
- value: 'This is a comment'
74
- }
79
+ value: "This is a comment",
80
+ },
75
81
  });
76
- expect(wrapper.find('button[type="submit"]')).not.to.be.disabled();
82
+ expect(wrapper.find('button[type="submit"]').props()).not.toHaveProperty("disabled", true);
77
83
  });
78
84
 
79
85
  it("should disable the submit button if textarea is blank", () => {
80
86
  const wrapper = mount(<AddCommentForm addComment={addCommentStub} session={session} commentable={commentable} />);
81
- wrapper.find('textarea').simulate('change', {
87
+ wrapper.find("textarea").simulate("change", {
82
88
  target: {
83
- value: 'This will be deleted'
84
- }
89
+ value: "This will be deleted",
90
+ },
85
91
  });
86
- wrapper.find('textarea').simulate('change', {
92
+ wrapper.find("textarea").simulate("change", {
87
93
  target: {
88
- value: ''
89
- }
94
+ value: "",
95
+ },
90
96
  });
91
- expect(wrapper.find('button[type="submit"]')).to.be.disabled();
97
+ expect(wrapper.find('button[type="submit"]').props()).toHaveProperty("disabled", true);
92
98
  });
93
99
 
94
100
  it("should not render a div with class 'opinion-toggle'", () => {
95
101
  const wrapper = shallow(<AddCommentForm addComment={addCommentStub} session={session} commentable={commentable} />);
96
- expect(wrapper.find('.opinion-toggle')).not.to.be.present();
102
+ expect(wrapper.find(".opinion-toggle").exists()).toBeFalsy();
97
103
  });
98
104
 
99
105
  describe("submitting the form", () => {
100
- let addComment = null;
101
- let onCommentAdded = null;
102
- let wrapper = null;
103
- let message = null;
106
+ let addComment: jasmine.Spy;
107
+ let onCommentAdded: jasmine.Spy ;
108
+ let wrapper: ReactWrapper<any, {}>;
109
+ let message: any = null;
104
110
 
105
111
  beforeEach(() => {
106
- addComment = sinon.spy();
107
- onCommentAdded = sinon.spy();
112
+ addComment = jasmine.createSpy("addComment");
113
+ onCommentAdded = jasmine.createSpy("onCommentAdded");
108
114
  wrapper = mount(<AddCommentForm addComment={addComment} session={session} commentable={commentable} onCommentAdded={onCommentAdded} />);
109
- message = 'This will be submitted';
110
- wrapper.instance().bodyTextArea.value = message;
115
+ message = "This will be submitted";
116
+ (wrapper.instance() as AddCommentForm).bodyTextArea.value = message;
111
117
  });
112
118
 
113
119
  it("should call addComment prop with the textarea value and state property alignment", () => {
114
- wrapper.find('form').simulate('submit');
115
- expect(addComment).to.calledWith({ body: message, alignment: 0 });
120
+ wrapper.find("form").simulate("submit");
121
+ expect(addComment).toHaveBeenCalledWith({ body: message, alignment: 0 });
116
122
  });
117
123
 
118
124
  it("should reset textarea", () => {
119
- wrapper.find('form').simulate('submit');
120
- expect(wrapper.find('textarea')).to.have.value('');
125
+ wrapper.find("form").simulate("submit");
126
+ expect((wrapper.find("textarea").get(0) as any).value).toBe("");
121
127
  });
122
128
 
123
129
  it("should prevent default form submission", () => {
124
- const preventDefault = sinon.spy();
125
- wrapper.find('form').simulate('submit', { preventDefault });
126
- expect(preventDefault).to.have.been.called;
130
+ const preventDefault = jasmine.createSpy("preventDefault");
131
+ wrapper.find("form").simulate("submit", { preventDefault });
132
+ expect(preventDefault).toHaveBeenCalled();
127
133
  });
128
134
 
129
135
  it("should call the prop onCommentAdded function", () => {
130
- wrapper.find('form').simulate('submit');
131
- expect(onCommentAdded).to.have.been.called;
136
+ wrapper.find("form").simulate("submit");
137
+ expect(onCommentAdded).toHaveBeenCalled();
132
138
  });
133
139
  });
134
140
 
135
141
  it("should initialize state with a property alignment and value 0", () => {
136
- const wrapper = shallow(<AddCommentForm addComment={addCommentStub} session={session} commentable={commentable} arguable />);
137
- expect(wrapper).to.have.state('alignment').equal(0);
142
+ const wrapper = shallow(<AddCommentForm addComment={addCommentStub} session={session} commentable={commentable} arguable={true} />);
143
+ expect(wrapper.state()).toHaveProperty("alignment", 0);
138
144
  });
139
145
 
140
146
  describe("when receiving an optional prop arguable with value true", () => {
141
147
  it("should render a div with class 'opinion-toggle'", () => {
142
- const wrapper = shallow(<AddCommentForm addComment={addCommentStub} session={session} commentable={commentable} arguable />);
143
- expect(wrapper.find('.opinion-toggle')).to.be.present();
148
+ const wrapper = shallow(<AddCommentForm addComment={addCommentStub} session={session} commentable={commentable} arguable={true} />);
149
+ expect(wrapper.find(".opinion-toggle")).toBeDefined();
144
150
  });
145
151
 
146
152
  it("should set state alignment to 1 if user clicks ok button and change its class", () => {
147
- const wrapper = shallow(<AddCommentForm addComment={addCommentStub} session={session} commentable={commentable} arguable />);
148
- wrapper.find('.opinion-toggle--ok').simulate('click');
149
- expect(wrapper.find('.opinion-toggle--ok')).to.have.className('is-active');
150
- expect(wrapper).to.have.state('alignment').equal(1);
153
+ const wrapper = shallow(<AddCommentForm addComment={addCommentStub} session={session} commentable={commentable} arguable={true} />);
154
+ wrapper.find(".opinion-toggle--ok").simulate("click");
155
+ expect(wrapper.find(".opinion-toggle--ok").hasClass("is-active")).toBeTruthy();
156
+ expect(wrapper.state()).toHaveProperty("alignment", 1);
151
157
  });
152
158
 
153
159
  it("should set state alignment to -11 if user clicks ko button and change its class", () => {
154
- const wrapper = shallow(<AddCommentForm addComment={addCommentStub} session={session} commentable={commentable} arguable />);
155
- wrapper.find('.opinion-toggle--ko').simulate('click');
156
- expect(wrapper.find('.opinion-toggle--ko')).to.have.className('is-active');
157
- expect(wrapper).to.have.state('alignment').equal(-1);
160
+ const wrapper = shallow(<AddCommentForm addComment={addCommentStub} session={session} commentable={commentable} arguable={true} />);
161
+ wrapper.find(".opinion-toggle--ko").simulate("click");
162
+ expect(wrapper.find(".opinion-toggle--ko").hasClass("is-active")).toBeTruthy();
163
+ expect(wrapper.state()).toHaveProperty("alignment", -1);
158
164
  });
159
165
 
160
166
  describe("submitting the form", () => {
161
- let addComment = null;
162
- let wrapper = null;
163
- let message = null;
167
+ let wrapper: ReactWrapper<any, {}>;
168
+ let addComment: jasmine.Spy;
169
+ let message: string;
164
170
 
165
171
  beforeEach(() => {
166
- addComment = sinon.spy();
167
- wrapper = mount(<AddCommentForm addComment={addComment} session={session} commentable={commentable} arguable />);
168
- message = 'This will be submitted';
169
- wrapper.instance().bodyTextArea.value = message;
172
+ addComment = jasmine.createSpy("addComment");
173
+ wrapper = mount(<AddCommentForm addComment={addComment} session={session} commentable={commentable} arguable={true} />);
174
+ message = "This will be submitted";
175
+ (wrapper.instance() as AddCommentForm).bodyTextArea.value = message;
170
176
  });
171
177
 
172
178
  it("should call addComment prop with the state's property alignment", () => {
173
- wrapper.find('button.opinion-toggle--ko').simulate('click');
174
- wrapper.find('form').simulate('submit');
175
- expect(addComment).to.calledWith({ body: message, alignment: -1 });
179
+ wrapper.find("button.opinion-toggle--ko").simulate("click");
180
+ wrapper.find("form").simulate("submit");
181
+ expect(addComment).toHaveBeenCalledWith({ body: message, alignment: -1 });
176
182
  });
177
183
 
178
184
  it("should reset the state to its initial state", () => {
179
- wrapper.find('button.opinion-toggle--ok').simulate('click');
180
- wrapper.find('form').simulate('submit');
181
- expect(wrapper).to.have.state('alignment').eq(0);
185
+ wrapper.find("button.opinion-toggle--ok").simulate("click");
186
+ wrapper.find("form").simulate("submit");
187
+ expect(wrapper.state()).toHaveProperty("alignment", 0);
182
188
  });
183
189
  });
184
190
  });
@@ -187,48 +193,48 @@ describe("<AddCommentForm />", () => {
187
193
  beforeEach(() => {
188
194
  session.verifiedUserGroups = [
189
195
  generateUserGroupData(),
190
- generateUserGroupData()
196
+ generateUserGroupData(),
191
197
  ];
192
198
  });
193
199
 
194
200
  it("should have a reference to user_group_id select", () => {
195
201
  const wrapper = mount(<AddCommentForm addComment={addCommentStub} session={session} commentable={commentable} />);
196
- expect(wrapper.instance().userGroupIdSelect).to.be.ok;
202
+ expect((wrapper.instance() as AddCommentForm).userGroupIdSelect).toBeDefined();
197
203
  });
198
204
 
199
205
  it("should render a select with option tags for each verified user group", () => {
200
206
  const wrapper = mount(<AddCommentForm addComment={addCommentStub} session={session} commentable={commentable} />);
201
- expect(wrapper.find('select')).to.have.exactly(3).descendants('option');
207
+ expect(wrapper.find("select").children("option").length).toBe(3);
202
208
  });
203
209
 
204
210
  describe("submitting the form", () => {
205
- let addComment = null;
206
- let wrapper = null;
207
- let message = null;
208
- let userGroupId = null;
211
+ let addComment: jasmine.Spy;
212
+ let wrapper: ReactWrapper<any, {}>;
213
+ let message: string;
214
+ let userGroupId: string;
209
215
 
210
216
  beforeEach(() => {
211
- addComment = sinon.spy();
217
+ addComment = jasmine.createSpy("addComment");
212
218
  wrapper = mount(<AddCommentForm addComment={addComment} session={session} commentable={commentable} />);
213
- message = 'This will be submitted';
219
+ message = "This will be submitted";
214
220
  userGroupId = session.verifiedUserGroups[1].id;
215
- wrapper.instance().bodyTextArea.value = message;
216
- wrapper.instance().userGroupIdSelect.value = userGroupId;
221
+ (wrapper.instance() as AddCommentForm).bodyTextArea.value = message;
222
+ (wrapper.instance() as AddCommentForm).userGroupIdSelect.value = userGroupId;
217
223
  });
218
224
 
219
225
  it("should call addComment prop with the body textarea, alignment and user_group_id select values", () => {
220
- wrapper.find('form').simulate('submit');
221
- expect(addComment).to.calledWith({ body: message, alignment: 0, userGroupId });
226
+ wrapper.find("form").simulate("submit");
227
+ expect(addComment).toHaveBeenCalledWith({ body: message, alignment: 0, userGroupId });
222
228
  });
223
229
 
224
230
  describe("when user_group_id is blank", () => {
225
231
  beforeEach(() => {
226
- wrapper.instance().userGroupIdSelect.value = '';
232
+ (wrapper.instance() as AddCommentForm).userGroupIdSelect.value = "";
227
233
  });
228
234
 
229
235
  it("should call addComment prop with the body textarea and alignment", () => {
230
- wrapper.find('form').simulate('submit');
231
- expect(addComment).to.calledWith({ body: message, alignment: 0 });
236
+ wrapper.find("form").simulate("submit");
237
+ expect(addComment).toHaveBeenCalledWith({ body: message, alignment: 0 });
232
238
  });
233
239
  });
234
240
  });
@@ -241,7 +247,7 @@ describe("<AddCommentForm />", () => {
241
247
 
242
248
  it("display a message to sign in or sign up", () => {
243
249
  const wrapper = mount(<AddCommentForm addComment={addCommentStub} session={session} commentable={commentable} />);
244
- expect(wrapper.find('span')).to.include.text("sign up");
250
+ expect(wrapper.find("span").text()).toContain("sign up");
245
251
  });
246
252
  });
247
253
  });
@@ -1,39 +1,69 @@
1
1
  /* eslint-disable no-return-assign, react/no-unused-prop-types, max-lines */
2
- import { Component, PropTypes } from 'react';
3
- import { graphql } from 'react-apollo';
4
- import gql from 'graphql-tag';
5
- import { I18n, Translate } from 'react-i18nify';
6
- import uuid from 'uuid';
7
- import classnames from 'classnames';
8
-
9
- import Icon from '../application/icon.component';
10
-
11
- import addCommentMutation from './add_comment_form.mutation.graphql';
12
- import commentThreadFragment from './comment_thread.fragment.graphql'
13
- import commentFragment from './comment.fragment.graphql';
14
- import commentDataFragment from './comment_data.fragment.graphql';
15
- import upVoteFragment from './up_vote.fragment.graphql';
16
- import downVoteFragment from './down_vote.fragment.graphql';
17
- import addCommentFormSessionFragment from './add_comment_form_session.fragment.graphql';
18
- import addCommentFormCommentableFragment from './add_comment_form_commentable.fragment.graphql';
2
+ import * as classnames from "classnames";
3
+ import * as React from "react";
4
+ import { graphql } from "react-apollo";
5
+ import * as uuid from "uuid";
6
+
7
+ import Icon from "../application/icon.component";
8
+
9
+ const { I18n, Translate } = require("react-i18nify");
10
+
11
+ import {
12
+ AddCommentFormCommentableFragment,
13
+ AddCommentFormSessionFragment,
14
+ AddCommentMutation,
15
+ CommentFragment,
16
+ GetCommentsQuery,
17
+ } from "../support/schema";
18
+
19
+ interface AddCommentFormProps {
20
+ session: AddCommentFormSessionFragment & {
21
+ user: any;
22
+ } | null;
23
+ commentable: AddCommentFormCommentableFragment;
24
+ showTitle?: boolean;
25
+ submitButtonClassName?: string;
26
+ autoFocus?: boolean;
27
+ maxLength?: number;
28
+ arguable?: boolean;
29
+ addComment?: (data: { body: string, alignment: number, userGroupId?: string }) => void;
30
+ onCommentAdded?: () => void;
31
+ }
32
+
33
+ interface AddCommentFormState {
34
+ disabled: boolean;
35
+ error: boolean;
36
+ alignment: number;
37
+ }
19
38
 
20
39
  /**
21
40
  * Renders a form to create new comments.
22
41
  * @class
23
42
  * @augments Component
24
43
  */
25
- export class AddCommentForm extends Component {
26
- constructor(props) {
44
+ export class AddCommentForm extends React.Component<AddCommentFormProps, AddCommentFormState> {
45
+ public static defaultProps = {
46
+ showTitle: true,
47
+ submitButtonClassName: "button button--sc",
48
+ arguable: false,
49
+ autoFocus: false,
50
+ maxLength: 1000,
51
+ };
52
+
53
+ public bodyTextArea: HTMLTextAreaElement;
54
+ public userGroupIdSelect: HTMLSelectElement;
55
+
56
+ constructor(props: AddCommentFormProps) {
27
57
  super(props);
28
58
 
29
59
  this.state = {
30
60
  disabled: true,
31
61
  error: false,
32
- alignment: 0
62
+ alignment: 0,
33
63
  };
34
64
  }
35
65
 
36
- render() {
66
+ public render() {
37
67
  return (
38
68
  <div className="add-comment">
39
69
  {this._renderHeading()}
@@ -49,13 +79,13 @@ export class AddCommentForm extends Component {
49
79
  * @private
50
80
  * @returns {Void|DOMElement} - The heading or an empty element
51
81
  */
52
- _renderHeading() {
82
+ private _renderHeading() {
53
83
  const { showTitle } = this.props;
54
84
 
55
85
  if (showTitle) {
56
86
  return (
57
87
  <h5 className="section-heading">
58
- { I18n.t("components.add_comment_form.title") }
88
+ {I18n.t("components.add_comment_form.title")}
59
89
  </h5>
60
90
  );
61
91
  }
@@ -68,7 +98,7 @@ export class AddCommentForm extends Component {
68
98
  * @private
69
99
  * @returns {Void|DOMElement} - The message or an empty element.
70
100
  */
71
- _renderAccountMessage() {
101
+ private _renderAccountMessage() {
72
102
  const { session } = this.props;
73
103
 
74
104
  if (!session) {
@@ -78,7 +108,7 @@ export class AddCommentForm extends Component {
78
108
  value="components.add_comment_form.account_message"
79
109
  sign_in_url="/users/sign_in"
80
110
  sign_up_url="/users/sign_up"
81
- dangerousHTML
111
+ dangerousHTML={true}
82
112
  />
83
113
  </p>
84
114
  );
@@ -92,21 +122,23 @@ export class AddCommentForm extends Component {
92
122
  * @private
93
123
  * @returns {Void|DOMElement} - The add comment form on an empty element.
94
124
  */
95
- _renderForm() {
125
+ private _renderForm() {
96
126
  const { session, submitButtonClassName, commentable: { id, type } } = this.props;
97
127
  const { disabled } = this.state;
98
128
 
99
129
  if (session) {
100
130
  return (
101
- <form onSubmit={(evt) => this._addComment(evt)}>
131
+ <form onSubmit={this.addComment}>
102
132
  {this._renderCommentAs()}
103
133
  <div className="field">
104
- <label className="show-for-sr" htmlFor={`add-comment-${type}-${id}`}>{ I18n.t("components.add_comment_form.form.body.label") }</label>
134
+ <label className="show-for-sr" htmlFor={`add-comment-${type}-${id}`}>{I18n.t("components.add_comment_form.form.body.label")}</label>
105
135
  {this._renderTextArea()}
106
136
  {this._renderTextAreaError()}
107
- <button type="submit"
108
- className={submitButtonClassName}
109
- disabled={disabled}>
137
+ <button
138
+ type="submit"
139
+ className={submitButtonClassName}
140
+ disabled={disabled}
141
+ >
110
142
  {I18n.t("components.add_comment_form.form.submit")}
111
143
  </button>
112
144
  </div>
@@ -122,13 +154,13 @@ export class AddCommentForm extends Component {
122
154
  * @private
123
155
  * @returns {Void|DOMElement} - The heading or an empty element
124
156
  */
125
- _renderTextArea() {
157
+ private _renderTextArea() {
126
158
  const { commentable: { id, type }, autoFocus, maxLength } = this.props;
127
159
  const { error } = this.state;
128
- const className = classnames({ 'is-invalid-input': error });
160
+ const className = classnames({ "is-invalid-input": error });
129
161
 
130
- let textAreaProps = {
131
- ref: (textarea) => {this.bodyTextArea = textarea},
162
+ let textAreaProps: any = {
163
+ ref: (textarea: HTMLTextAreaElement) => {this.bodyTextArea = textarea; },
132
164
  id: `add-comment-${type}-${id}`,
133
165
  className,
134
166
  rows: "4",
@@ -136,10 +168,11 @@ export class AddCommentForm extends Component {
136
168
  required: "required",
137
169
  pattern: `^(.){0,${maxLength}}$`,
138
170
  placeholder: I18n.t("components.add_comment_form.form.body.placeholder"),
139
- onChange: (evt) => this._checkCommentBody(evt.target.value)
171
+ onChange: (evt: React.ChangeEvent<HTMLTextAreaElement>) => this._checkCommentBody(evt.target.value),
140
172
  };
173
+
141
174
  if (autoFocus) {
142
- textAreaProps.autoFocus = 'autoFocus';
175
+ textAreaProps.autoFocus = "autoFocus";
143
176
  }
144
177
 
145
178
  return (
@@ -152,14 +185,14 @@ export class AddCommentForm extends Component {
152
185
  * @private
153
186
  * @returns {Void|DOMElement} - The error or an empty element
154
187
  */
155
- _renderTextAreaError() {
188
+ private _renderTextAreaError() {
156
189
  const { maxLength } = this.props;
157
190
  const { error } = this.state;
158
191
 
159
192
  if (error) {
160
193
  return (
161
194
  <span className="form-error is-visible">
162
- { I18n.t("components.add_comment_form.form.form_error", { length: maxLength }) }
195
+ {I18n.t("components.add_comment_form.form.form_error", { length: maxLength })}
163
196
  </span>
164
197
  );
165
198
  }
@@ -167,23 +200,29 @@ export class AddCommentForm extends Component {
167
200
  return null;
168
201
  }
169
202
 
203
+ private setAlignment = (alignment: number) => {
204
+ return () => {
205
+ this.setState({ alignment });
206
+ };
207
+ }
208
+
170
209
  /**
171
210
  * Render opinion buttons or not based on the arguable prop
172
211
  * @private
173
212
  * @returns {Void|DOMElement} - Returns nothing or a wrapper with buttons
174
213
  */
175
- _renderOpinionButtons() {
214
+ private _renderOpinionButtons() {
176
215
  const { session, arguable } = this.props;
177
216
  const { alignment } = this.state;
178
- const buttonClassName = classnames('button', 'tiny', 'button--muted');
179
- const okButtonClassName = classnames(buttonClassName, 'opinion-toggle--ok', {
180
- 'is-active': alignment === 1
217
+ const buttonClassName = classnames("button", "tiny", "button--muted");
218
+ const okButtonClassName = classnames(buttonClassName, "opinion-toggle--ok", {
219
+ "is-active": alignment === 1,
181
220
  });
182
- const koButtonClassName = classnames(buttonClassName, 'opinion-toggle--ko', {
183
- 'is-active': alignment === -1
221
+ const koButtonClassName = classnames(buttonClassName, "opinion-toggle--ko", {
222
+ "is-active": alignment === -1,
184
223
  });
185
- const neutralButtonClassName = classnames(buttonClassName, 'opinion-toggle--meh', {
186
- 'is-active': alignment === 0
224
+ const neutralButtonClassName = classnames(buttonClassName, "opinion-toggle--meh", {
225
+ "is-active": alignment === 0,
187
226
  });
188
227
 
189
228
  if (session && arguable) {
@@ -191,19 +230,19 @@ export class AddCommentForm extends Component {
191
230
  <div className="opinion-toggle button-group">
192
231
  <button
193
232
  className={okButtonClassName}
194
- onClick={() => this.setState({ alignment: 1 })}
233
+ onClick={this.setAlignment(1)}
195
234
  >
196
235
  <Icon iconExtraClassName="" name="icon-thumb-up" />
197
236
  </button>
198
237
  <button
199
238
  className={neutralButtonClassName}
200
- onClick={() => this.setState({ alignment: 0 })}
239
+ onClick={this.setAlignment(0)}
201
240
  >
202
- { I18n.t("components.add_comment_form.opinion.neutral") }
241
+ {I18n.t("components.add_comment_form.opinion.neutral")}
203
242
  </button>
204
243
  <button
205
244
  className={koButtonClassName}
206
- onClick={() => this.setState({ alignment: -1 })}
245
+ onClick={this.setAlignment(-1)}
207
246
  >
208
247
  <Icon iconExtraClassName="" name="icon-thumb-down" />
209
248
  </button>
@@ -214,34 +253,39 @@ export class AddCommentForm extends Component {
214
253
  return null;
215
254
  }
216
255
 
256
+ private setUserGroupIdSelect = (select: HTMLSelectElement) => {this.userGroupIdSelect = select; };
257
+
217
258
  /**
218
259
  * Render a select with an option for each user's verified group
219
260
  * @private
220
261
  * @returns {Void|DOMElement} - Returns nothing or a form field.
221
262
  */
222
- _renderCommentAs() {
263
+ private _renderCommentAs() {
223
264
  const { session, commentable: { id, type } } = this.props;
224
- const { user, verifiedUserGroups } = session;
225
265
 
226
- if (verifiedUserGroups.length > 0) {
227
- return (
228
- <div className="field">
229
- <label htmlFor={`add-comment-${type}-${id}-user-group-id`}>
230
- { I18n.t('components.add_comment_form.form.user_group_id.label') }
231
- </label>
232
- <select
233
- ref={(select) => {this.userGroupIdSelect = select}}
234
- id={`add-comment-${type}-${id}-user-group-id`}
235
- >
236
- <option value="">{ user.name }</option>
237
- {
238
- verifiedUserGroups.map((userGroup) => (
239
- <option key={userGroup.id} value={userGroup.id}>{userGroup.name}</option>
240
- ))
241
- }
242
- </select>
243
- </div>
244
- );
266
+ if (session) {
267
+ const { user, verifiedUserGroups } = session;
268
+
269
+ if (verifiedUserGroups.length > 0) {
270
+ return (
271
+ <div className="field">
272
+ <label htmlFor={`add-comment-${type}-${id}-user-group-id`}>
273
+ {I18n.t("components.add_comment_form.form.user_group_id.label")}
274
+ </label>
275
+ <select
276
+ ref={this.setUserGroupIdSelect}
277
+ id={`add-comment-${type}-${id}-user-group-id`}
278
+ >
279
+ <option value="">{user.name}</option>
280
+ {
281
+ verifiedUserGroups.map((userGroup) => (
282
+ <option key={userGroup.id} value={userGroup.id}>{userGroup.name}</option>
283
+ ))
284
+ }
285
+ </select>
286
+ </div>
287
+ );
288
+ }
245
289
  }
246
290
 
247
291
  return null;
@@ -253,9 +297,9 @@ export class AddCommentForm extends Component {
253
297
  * @param {string} body - The comment's body
254
298
  * @returns {Void} - Returns nothing
255
299
  */
256
- _checkCommentBody(body) {
300
+ private _checkCommentBody(body: string) {
257
301
  const { maxLength } = this.props;
258
- this.setState({ disabled: body === '', error: body === '' || body.length > maxLength });
302
+ this.setState({ disabled: body === "", error: body === "" || (maxLength !== undefined && body.length > maxLength) });
259
303
  }
260
304
 
261
305
  /**
@@ -265,20 +309,22 @@ export class AddCommentForm extends Component {
265
309
  * @param {DOMEvent} evt - The form's submission event
266
310
  * @returns {Void} - Returns nothing
267
311
  */
268
- _addComment(evt) {
312
+ private addComment = (evt: React.FormEvent<HTMLFormElement>) => {
269
313
  const { alignment } = this.state;
270
314
  const { addComment, onCommentAdded } = this.props;
271
- let addCommentParams = { body: this.bodyTextArea.value, alignment };
315
+ let addCommentParams: { body: string, alignment: number, userGroupId?: string } = { body: this.bodyTextArea.value, alignment };
272
316
 
273
317
  evt.preventDefault();
274
318
 
275
- if (this.userGroupIdSelect && this.userGroupIdSelect.value !== '') {
319
+ if (this.userGroupIdSelect && this.userGroupIdSelect.value !== "") {
276
320
  addCommentParams.userGroupId = this.userGroupIdSelect.value;
277
321
  }
278
322
 
279
- addComment(addCommentParams);
323
+ if (addComment) {
324
+ addComment(addCommentParams);
325
+ }
280
326
 
281
- this.bodyTextArea.value = '';
327
+ this.bodyTextArea.value = "";
282
328
  this.setState({ alignment: 0 });
283
329
 
284
330
  if (onCommentAdded) {
@@ -287,80 +333,33 @@ export class AddCommentForm extends Component {
287
333
  }
288
334
  }
289
335
 
290
- AddCommentForm.propTypes = {
291
- addComment: PropTypes.func.isRequired,
292
- session: PropTypes.shape({
293
- user: PropTypes.shape({
294
- name: PropTypes.string.isRequired
295
- }),
296
- verifiedUserGroups: PropTypes.arrayOf(
297
- PropTypes.shape({
298
- name: PropTypes.string.isRequired
299
- })
300
- ).isRequired
301
- }),
302
- commentable: PropTypes.shape({
303
- id: PropTypes.string.isRequired,
304
- type: PropTypes.string.isRequired
305
- }),
306
- showTitle: PropTypes.bool.isRequired,
307
- submitButtonClassName: PropTypes.string.isRequired,
308
- onCommentAdded: PropTypes.func,
309
- arguable: PropTypes.bool,
310
- autoFocus: PropTypes.bool,
311
- maxLength: PropTypes.number.isRequired
312
- };
313
-
314
- AddCommentForm.defaultProps = {
315
- onCommentAdded: function() {},
316
- showTitle: true,
317
- submitButtonClassName: 'button button--sc',
318
- arguable: false,
319
- autoFocus: false,
320
- maxLength: 1000
321
- };
322
-
323
- AddCommentForm.fragments = {
324
- session: gql`
325
- ${addCommentFormSessionFragment}
326
- `,
327
- commentable: gql`
328
- ${addCommentFormCommentableFragment}
329
- `
330
- };
331
-
332
- const AddCommentFormWithMutation = graphql(gql`
333
- ${addCommentMutation}
334
- ${commentThreadFragment}
335
- ${commentFragment}
336
- ${commentDataFragment}
337
- ${upVoteFragment}
338
- ${downVoteFragment}
339
- `, {
336
+ const addCommentMutation = require("../mutations/add_comment.mutation.graphql");
337
+
338
+ const AddCommentFormWithMutation = graphql(addCommentMutation, {
340
339
  props: ({ ownProps, mutate }) => ({
341
- addComment: ({ body, alignment, userGroupId }) => mutate({
340
+ addComment: ({ body, alignment, userGroupId }: { body: string, alignment: number, userGroupId: string }) => mutate({
342
341
  variables: {
343
342
  commentableId: ownProps.commentable.id,
344
343
  commentableType: ownProps.commentable.type,
345
344
  body,
346
345
  alignment,
347
- userGroupId
346
+ userGroupId,
348
347
  },
349
348
  optimisticResponse: {
350
349
  commentable: {
351
- __typename: 'CommentableMutation',
350
+ __typename: "CommentableMutation",
352
351
  addComment: {
353
- __typename: 'Comment',
352
+ __typename: "Comment",
354
353
  id: uuid(),
355
354
  sgid: uuid(),
356
355
  type: "Decidim::Comments::Comment",
357
356
  createdAt: new Date().toISOString(),
358
357
  body,
359
- alignment: alignment,
358
+ alignment,
360
359
  author: {
361
- __typename: 'User',
360
+ __typename: "User",
362
361
  name: ownProps.session.user.name,
363
- avatarUrl: ownProps.session.user.avatarUrl
362
+ avatarUrl: ownProps.session.user.avatarUrl,
364
363
  },
365
364
  comments: [],
366
365
  hasComments: false,
@@ -369,32 +368,32 @@ const AddCommentFormWithMutation = graphql(gql`
369
368
  upVoted: false,
370
369
  downVotes: 0,
371
370
  downVoted: false,
372
- alreadyReported: false
373
- }
374
- }
371
+ alreadyReported: false,
372
+ },
373
+ },
375
374
  },
376
375
  updateQueries: {
377
- GetComments: (prev, { mutationResult: { data } }) => {
376
+ GetComments: (prev: GetCommentsQuery, { mutationResult: { data } }: { mutationResult: { data: AddCommentMutation }}) => {
378
377
  const { id, type } = ownProps.commentable;
379
- const newComment = data.commentable.addComment;
378
+ const newComment = data.commentable && data.commentable.addComment;
380
379
  let comments = [];
381
380
 
382
- const commentReducer = (comment) => {
381
+ const commentReducer = (comment: CommentFragment): CommentFragment => {
383
382
  const replies = comment.comments || [];
384
383
 
385
- if (comment.id === id) {
384
+ if (newComment && comment.id === id) {
386
385
  return {
387
386
  ...comment,
388
387
  hasComments: true,
389
388
  comments: [
390
389
  ...replies,
391
- newComment
392
- ]
390
+ newComment,
391
+ ],
393
392
  };
394
393
  }
395
394
  return {
396
395
  ...comment,
397
- comments: replies.map(commentReducer)
396
+ comments: replies.map(commentReducer),
398
397
  };
399
398
  };
400
399
 
@@ -403,7 +402,7 @@ const AddCommentFormWithMutation = graphql(gql`
403
402
  } else {
404
403
  comments = [
405
404
  ...prev.commentable.comments,
406
- newComment
405
+ newComment,
407
406
  ];
408
407
  }
409
408
 
@@ -411,13 +410,13 @@ const AddCommentFormWithMutation = graphql(gql`
411
410
  ...prev,
412
411
  commentable: {
413
412
  ...prev.commentable,
414
- comments
415
- }
413
+ comments,
414
+ },
416
415
  };
417
- }
418
- }
419
- })
420
- })
416
+ },
417
+ },
418
+ }),
419
+ }),
421
420
  })(AddCommentForm);
422
421
 
423
422
  export default AddCommentFormWithMutation;