decidim-comments 0.21.0 → 0.23.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/app/assets/javascripts/decidim/comments/bundle.js +67 -67
- data/app/assets/javascripts/decidim/comments/bundle.js.map +1 -1
- data/app/cells/decidim/comments/comment_activity_cell.rb +2 -22
- data/app/cells/decidim/comments/comment_cell.rb +22 -0
- data/app/cells/decidim/comments/comment_m/footer.erb +5 -0
- data/app/cells/decidim/comments/comment_m/top.erb +7 -0
- data/app/cells/decidim/comments/comment_m_cell.rb +29 -0
- data/app/commands/decidim/comments/create_comment.rb +8 -8
- data/app/events/decidim/comments/comment_by_followed_user_group_event.rb +9 -0
- data/app/events/decidim/comments/comment_event.rb +14 -9
- data/app/events/decidim/comments/user_group_mentioned_event.rb +10 -0
- data/app/forms/decidim/comments/comment_form.rb +17 -1
- data/app/frontend/application/icon.component.tsx +16 -4
- data/app/frontend/comments/add_comment_form.component.test.tsx +31 -29
- data/app/frontend/comments/add_comment_form.component.tsx +34 -18
- data/app/frontend/comments/comment.component.test.tsx +36 -5
- data/app/frontend/comments/comment.component.tsx +218 -88
- data/app/frontend/comments/comment_order_selector.component.tsx +26 -7
- data/app/frontend/comments/comment_thread.component.test.tsx +9 -8
- data/app/frontend/comments/comment_thread.component.tsx +3 -1
- data/app/frontend/comments/comments.component.test.tsx +17 -14
- data/app/frontend/comments/comments.component.tsx +28 -4
- data/app/frontend/comments/down_vote_button.component.tsx +27 -9
- data/app/frontend/comments/up_vote_button.component.tsx +27 -9
- data/app/frontend/comments/vote_button.component.tsx +4 -0
- data/app/frontend/comments/vote_button_component.test.tsx +14 -8
- data/app/frontend/mutations/add_comment.mutation.graphql +2 -2
- data/app/frontend/mutations/down_vote.mutation.graphql +2 -2
- data/app/frontend/mutations/up_vote.mutation.graphql +2 -2
- data/app/frontend/queries/comments.query.graphql +2 -2
- data/app/frontend/support/schema.ts +1060 -735
- data/app/helpers/decidim/comments/comment_cells_helper.rb +33 -0
- data/app/models/decidim/comments/comment.rb +81 -22
- data/app/models/decidim/comments/seed.rb +1 -1
- data/app/queries/decidim/comments/metrics/comments_metric_manage.rb +1 -6
- data/app/services/decidim/comments/new_comment_notification_creator.rb +28 -3
- data/app/types/decidim/comments/commentable_interface.rb +1 -1
- data/app/types/decidim/comments/commentable_mutation_type.rb +6 -3
- data/config/locales/am-ET.yml +1 -0
- data/config/locales/ar.yml +4 -1
- data/config/locales/bg-BG.yml +6 -0
- data/config/locales/bg.yml +6 -0
- data/config/locales/ca.yml +19 -2
- data/config/locales/cs.yml +31 -14
- data/config/locales/da-DK.yml +1 -0
- data/config/locales/da.yml +1 -0
- data/config/locales/de.yml +47 -24
- data/config/locales/el.yml +121 -0
- data/config/locales/en.yml +18 -1
- data/config/locales/eo.yml +1 -0
- data/config/locales/es-MX.yml +18 -1
- data/config/locales/es-PY.yml +18 -1
- data/config/locales/es.yml +18 -1
- data/config/locales/et-EE.yml +1 -0
- data/config/locales/et.yml +1 -0
- data/config/locales/eu.yml +4 -1
- data/config/locales/fi-plain.yml +18 -1
- data/config/locales/fi.yml +25 -8
- data/config/locales/fr-CA.yml +123 -0
- data/config/locales/fr.yml +25 -2
- data/config/locales/ga-IE.yml +1 -0
- data/config/locales/gl.yml +17 -1
- data/config/locales/hr-HR.yml +1 -0
- data/config/locales/hr.yml +1 -0
- data/config/locales/hu.yml +12 -2
- data/config/locales/id-ID.yml +4 -1
- data/config/locales/is-IS.yml +3 -3
- data/config/locales/is.yml +76 -0
- data/config/locales/it.yml +21 -1
- data/config/locales/ja-JP.yml +120 -0
- data/config/locales/ja.yml +121 -0
- data/config/locales/ko-KR.yml +1 -0
- data/config/locales/ko.yml +1 -0
- data/config/locales/lt-LT.yml +1 -0
- data/config/locales/lt.yml +1 -0
- data/config/locales/lv.yml +118 -0
- data/config/locales/mt-MT.yml +1 -0
- data/config/locales/mt.yml +1 -0
- data/config/locales/nl.yml +27 -8
- data/config/locales/no.yml +18 -2
- data/config/locales/om-ET.yml +1 -0
- data/config/locales/pl.yml +63 -40
- data/config/locales/pt-BR.yml +5 -2
- data/config/locales/pt.yml +47 -25
- data/config/locales/ro-RO.yml +124 -0
- data/config/locales/ru.yml +4 -1
- data/config/locales/si-LK.yml +1 -0
- data/config/locales/sk-SK.yml +116 -0
- data/config/locales/sk.yml +120 -0
- data/config/locales/sl.yml +4 -0
- data/config/locales/so-SO.yml +1 -0
- data/config/locales/sr-CS.yml +20 -0
- data/config/locales/sv.yml +26 -3
- data/config/locales/sw-KE.yml +1 -0
- data/config/locales/ti-ER.yml +1 -0
- data/config/locales/tr-TR.yml +54 -31
- data/config/locales/uk.yml +4 -2
- data/config/locales/vi-VN.yml +1 -0
- data/config/locales/vi.yml +1 -0
- data/config/locales/zh-CN.yml +121 -0
- data/config/locales/zh-TW.yml +1 -0
- data/db/migrate/20200320105911_index_foreign_keys_in_decidim_comments_comments.rb +7 -0
- data/db/migrate/20200706123136_make_comments_handle_i18n.rb +41 -0
- data/db/migrate/20200828101910_add_commentable_counter_cache_to_comments.rb +9 -0
- data/lib/decidim/comments/api/comment_type.rb +5 -1
- data/lib/decidim/comments/comment_serializer.rb +7 -2
- data/lib/decidim/comments/comment_vote_serializer.rb +5 -1
- data/lib/decidim/comments/commentable.rb +11 -0
- data/lib/decidim/comments/comments_helper.rb +28 -4
- data/lib/decidim/comments/engine.rb +13 -0
- data/lib/decidim/comments/mutation_extensions.rb +8 -0
- data/lib/decidim/comments/query_extensions.rb +4 -0
- data/lib/decidim/comments/test/factories.rb +10 -1
- data/lib/decidim/comments/test/shared_examples/comment_event.rb +12 -2
- data/lib/decidim/comments/test/shared_examples/create_comment_context.rb +3 -2
- data/lib/decidim/comments/version.rb +1 -1
- metadata +59 -13
@@ -9,18 +9,23 @@ module Decidim
|
|
9
9
|
include Decidim::Events::AuthorEvent
|
10
10
|
|
11
11
|
included do
|
12
|
-
|
13
|
-
|
14
|
-
def resource_path
|
15
|
-
resource_locator.path(url_params)
|
12
|
+
def resource_text
|
13
|
+
comment.formatted_body
|
16
14
|
end
|
17
15
|
|
18
|
-
def
|
19
|
-
|
16
|
+
def author
|
17
|
+
comment.normalized_author
|
20
18
|
end
|
21
19
|
|
22
|
-
def
|
23
|
-
|
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
|
24
29
|
end
|
25
30
|
|
26
31
|
private
|
@@ -29,7 +34,7 @@ module Decidim
|
|
29
34
|
@comment ||= Decidim::Comments::Comment.find(extra[:comment_id])
|
30
35
|
end
|
31
36
|
|
32
|
-
def
|
37
|
+
def resource_url_params
|
33
38
|
{ anchor: "comment_#{comment.id}" }
|
34
39
|
end
|
35
40
|
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:
|
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
|
-
|
32
|
-
|
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
|
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("
|
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(
|
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:
|
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
|
-
<
|
109
|
+
<h4 className="section-heading">
|
104
110
|
{I18n.t("components.add_comment_form.title")}
|
105
|
-
</
|
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:
|
194
|
+
maxLength: commentsMaxLength,
|
189
195
|
required: "required",
|
190
|
-
pattern: `^(.){0,${
|
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:
|
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 >
|
323
|
-
remainingCharacterCount:
|
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 = [];
|