decidim-comments 0.0.6 → 0.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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,36 @@
1
+ import * as React from "react";
2
+ import Icon from "../application/icon.component";
3
+
4
+ interface VoteButtonProps {
5
+ buttonClassName: string;
6
+ iconName: string;
7
+ votes: number;
8
+ voteAction?: () => void;
9
+ disabled?: boolean;
10
+ selectedClass?: string;
11
+ }
12
+
13
+ const VoteButton: React.SFC<VoteButtonProps> = ({
14
+ buttonClassName,
15
+ iconName,
16
+ votes,
17
+ voteAction,
18
+ disabled,
19
+ selectedClass,
20
+ }) => (
21
+ <button
22
+ className={`${buttonClassName} ${selectedClass}`}
23
+ onClick={voteAction}
24
+ disabled={disabled}
25
+ >
26
+ <Icon name={iconName} iconExtraClassName="icon--small" />
27
+ {` ${votes}`}
28
+ </button>
29
+ );
30
+
31
+ VoteButton.defaultProps = {
32
+ selectedClass: "selected",
33
+ disabled: false,
34
+ };
35
+
36
+ export default VoteButton;
@@ -1,43 +1,39 @@
1
- /* eslint-disable no-unused-expressions */
2
- import { shallow } from 'enzyme';
3
-
4
- import VoteButton from './vote_button.component';
5
- import Icon from '../application/icon.component';
6
-
7
- import stubComponent from '../support/stub_component';
1
+ import { shallow } from "enzyme";
2
+ import * as React from "react";
3
+ import Icon from "../application/icon.component";
4
+ import VoteButton from "./vote_button.component";
8
5
 
9
6
  describe("<VoteButton />", () => {
10
- const voteAction = sinon.spy();
11
- stubComponent(Icon);
7
+ const voteAction: jasmine.Spy = jasmine.createSpy("voteAction");
12
8
 
13
9
  it("should render the number of votes passed as a prop", () => {
14
10
  const wrapper = shallow(<VoteButton votes={10} buttonClassName="vote-button" iconName="vote-icon" voteAction={voteAction} />);
15
- expect(wrapper.find('button').text()).to.match(/10/);
11
+ expect(wrapper.find("button").text()).toMatch(/10/);
16
12
  });
17
13
 
18
14
  it("should render a button with the given buttonClassName", () => {
19
15
  const wrapper = shallow(<VoteButton votes={10} buttonClassName="vote-button" iconName="vote-icon" voteAction={voteAction} />);
20
- expect(wrapper.find('button.vote-button')).to.be.present();
16
+ expect(wrapper.find("button.vote-button").exists()).toBeTruthy();
21
17
  });
22
18
 
23
19
  it("should render a Icon component with the correct name prop", () => {
24
20
  const wrapper = shallow(<VoteButton votes={10} buttonClassName="vote-button" iconName="vote-icon" voteAction={voteAction} />);
25
- expect(wrapper.find(Icon)).to.have.prop("name").equal('vote-icon');
21
+ expect(wrapper.find(Icon).prop("name")).toEqual("vote-icon");
26
22
  });
27
23
 
28
24
  it("should call the voteAction prop on click", () => {
29
25
  const wrapper = shallow(<VoteButton votes={10} buttonClassName="vote-button" iconName="vote-icon" voteAction={voteAction} />);
30
- wrapper.find('button').simulate('click');
31
- expect(voteAction).to.have.been.called;
26
+ wrapper.find("button").simulate("click");
27
+ expect(voteAction).toHaveBeenCalled();
32
28
  });
33
29
 
34
30
  it("should disable the button based on the disabled prop", () => {
35
- const wrapper = shallow(<VoteButton votes={10} buttonClassName="vote-button" iconName="vote-icon" voteAction={voteAction} disabled />);
36
- expect(wrapper.find('button')).to.be.disabled();
37
- })
31
+ const wrapper = shallow(<VoteButton votes={10} buttonClassName="vote-button" iconName="vote-icon" voteAction={voteAction} disabled={true} />);
32
+ expect(wrapper.find("button").props()).toHaveProperty("disabled");
33
+ });
38
34
 
39
35
  it("should render a button with the given selectedClass", () => {
40
- const wrapper = shallow(<VoteButton votes={10} buttonClassName="vote-button" iconName="vote-icon" voteAction={voteAction} disabled selectedClass="is-vote-selected" />);
41
- expect(wrapper.find('.is-vote-selected')).to.be.present();
42
- })
36
+ const wrapper = shallow(<VoteButton votes={10} buttonClassName="vote-button" iconName="vote-icon" voteAction={voteAction} disabled={true} selectedClass="is-vote-selected" />);
37
+ expect(wrapper.find(".is-vote-selected").exists()).toBeTruthy();
38
+ });
43
39
  });
@@ -0,0 +1,19 @@
1
+ import * as React from "react";
2
+ import * as ReactDOM from "react-dom";
3
+
4
+ import Comments, { CommentsApplicationProps } from "./comments/comments.component";
5
+ import loadTranslations from "./support/load_translations";
6
+
7
+ window.DecidimComments = window.DecidimComments || {};
8
+
9
+ window.DecidimComments.renderCommentsComponent = (nodeId: string, props: CommentsApplicationProps) => {
10
+ let node = window.$(`#${nodeId}`)[0];
11
+
12
+ ReactDOM.render(
13
+ React.createElement(Comments, props),
14
+ node,
15
+ );
16
+ };
17
+
18
+ // Load component locales from yaml files
19
+ loadTranslations();
@@ -1,4 +1,4 @@
1
1
  fragment AddCommentFormCommentable on Commentable {
2
2
  id
3
3
  type
4
- }
4
+ }
@@ -3,4 +3,4 @@ fragment AddCommentFormSession on Session {
3
3
  id
4
4
  name
5
5
  }
6
- }
6
+ }
@@ -1,3 +1,5 @@
1
+ #import "../fragments/comment_data.fragment.graphql"
2
+
1
3
  fragment Comment on Comment {
2
4
  ...CommentData
3
5
  comments {
@@ -9,4 +11,4 @@ fragment Comment on Comment {
9
11
  }
10
12
  }
11
13
  }
12
- }
14
+ }
@@ -1,3 +1,6 @@
1
+ #import "../fragments/up_vote_button.fragment.graphql"
2
+ #import "../fragments/down_vote_button.fragment.graphql"
3
+
1
4
  fragment CommentData on Comment {
2
5
  id
3
6
  sgid
@@ -12,6 +15,6 @@ fragment CommentData on Comment {
12
15
  acceptsNewComments
13
16
  alignment
14
17
  alreadyReported
15
- ...UpVote
16
- ...DownVote
17
- }
18
+ ...UpVoteButton
19
+ ...DownVoteButton
20
+ }
@@ -1,7 +1,9 @@
1
+ #import "../fragments/comment.fragment.graphql"
2
+
1
3
  fragment CommentThread on Comment {
2
4
  author {
3
5
  name
4
6
  }
5
7
  hasComments
6
8
  ...Comment
7
- }
9
+ }
@@ -1,6 +1,6 @@
1
- fragment DownVote on Comment {
1
+ fragment DownVoteButton on Comment {
2
2
  id
3
3
  downVotes
4
4
  downVoted
5
5
  upVoted
6
- }
6
+ }
@@ -1,6 +1,6 @@
1
- fragment UpVote on Comment {
1
+ fragment UpVoteButton on Comment {
2
2
  id
3
3
  upVotes
4
4
  upVoted
5
5
  downVoted
6
- }
6
+ }
@@ -1,7 +1,9 @@
1
+ #import "../fragments/comment_thread.fragment.graphql"
2
+
1
3
  mutation addComment($commentableId: String!, $commentableType: String!, $body: String!, $alignment: Int, $userGroupId: ID) {
2
4
  commentable(id: $commentableId, type: $commentableType) {
3
5
  addComment(body: $body, alignment: $alignment, userGroupId: $userGroupId) {
4
6
  ...CommentThread
5
7
  }
6
8
  }
7
- }
9
+ }
@@ -1,7 +1,9 @@
1
+ #import "../fragments/comment.fragment.graphql"
2
+
1
3
  mutation DownVote($id: ID!) {
2
4
  comment(id: $id) {
3
5
  downVote {
4
6
  ...Comment
5
7
  }
6
8
  }
7
- }
9
+ }
@@ -1,7 +1,9 @@
1
+ #import "../fragments/comment.fragment.graphql"
2
+
1
3
  mutation UpVote($id: ID!) {
2
4
  comment(id: $id) {
3
5
  upVote {
4
6
  ...Comment
5
7
  }
6
8
  }
7
- }
9
+ }
@@ -1,3 +1,6 @@
1
+ #import "../fragments/add_comment_form_session.fragment.graphql"
2
+ #import "../fragments/comment_thread.fragment.graphql"
3
+ #import "../fragments/add_comment_form_commentable.fragment.graphql"
1
4
  query GetComments($commentableId: String!, $commentableType: String!, $orderBy: String) {
2
5
  session {
3
6
  user {
@@ -17,4 +20,4 @@ query GetComments($commentableId: String!, $commentableType: String!, $orderBy:
17
20
  }
18
21
  ...AddCommentFormCommentable
19
22
  }
20
- }
23
+ }
@@ -1,4 +1,4 @@
1
- const assetUrl = (name) => {
1
+ const assetUrl = (name: string): string => {
2
2
  const url = window.DecidimComments.assets[name];
3
3
 
4
4
  if (!url) {
@@ -1,4 +1,6 @@
1
- import { random, name, date, image } from 'faker/locale/en';
1
+ import { date, image, lorem, name, random } from "faker/locale/en";
2
+
3
+ import { CommentFragment } from "../support/schema";
2
4
 
3
5
  /**
4
6
  * Generate random comment data to emulate a database real content
@@ -6,16 +8,17 @@ import { random, name, date, image } from 'faker/locale/en';
6
8
  * @returns {Object[]} - An array of objects representing comments data
7
9
  */
8
10
  const generateCommentsData = (num = 1) => {
9
- let commentsData = [];
11
+ let commentsData: CommentFragment[] = [];
10
12
 
11
13
  for (let idx = 0; idx < num; idx += 1) {
12
14
  commentsData.push({
13
15
  id: random.uuid(),
14
- body: random.words(),
16
+ type: "Decidim::Comments::Comment",
17
+ body: lorem.words(),
15
18
  createdAt: date.past().toISOString(),
16
19
  author: {
17
20
  name: name.findName(),
18
- avatarUrl: image.imageUrl()
21
+ avatarUrl: image.imageUrl(),
19
22
  },
20
23
  hasComments: false,
21
24
  comments: [],
@@ -24,8 +27,10 @@ const generateCommentsData = (num = 1) => {
24
27
  upVotes: random.number(),
25
28
  upVoted: false,
26
29
  downVotes: random.number(),
27
- downVoted: false
28
- })
30
+ downVoted: false,
31
+ sgid: random.uuid(),
32
+ alreadyReported: false,
33
+ });
29
34
  }
30
35
 
31
36
  return commentsData;
@@ -1,4 +1,4 @@
1
- import { name } from 'faker/locale/en';
1
+ import { name } from "faker/locale/en";
2
2
 
3
3
  /**
4
4
  * Generate random user data to emulate a database real content
@@ -6,7 +6,7 @@ import { name } from 'faker/locale/en';
6
6
  */
7
7
  const generateUserData = () => {
8
8
  return {
9
- name: name.findName()
9
+ name: name.findName(),
10
10
  };
11
11
  };
12
12
 
@@ -1,4 +1,4 @@
1
- import { random, company } from 'faker/locale/en';
1
+ import { company, random } from "faker/locale/en";
2
2
 
3
3
  /**
4
4
  * Generate random user group data to emulate a database real content
@@ -7,7 +7,7 @@ import { random, company } from 'faker/locale/en';
7
7
  const generateUserGrouprData = () => {
8
8
  return {
9
9
  id: random.uuid(),
10
- name: company.companyName()
10
+ name: company.companyName(),
11
11
  };
12
12
  };
13
13
 
@@ -0,0 +1,32 @@
1
+ /* eslint-env node */
2
+ const gql = require('graphql-tag');
3
+
4
+ // Takes `source` (the source GraphQL query string)
5
+ // and `doc` (the parsed GraphQL document) and tacks on
6
+ // the imported definitions.
7
+ const expandImports = (source) => {
8
+ const lines = source.split('\n');
9
+ let outputCode = "";
10
+
11
+ lines.some((line) => {
12
+ if (line[0] === '#' && line.slice(1).split(' ')[0] === 'import') {
13
+ const importFile = line.slice(1).split(' ')[1];
14
+ const parseDocument = `require(${importFile})`;
15
+ const appendDef = `doc.definitions = doc.definitions.concat(${parseDocument}.definitions);`;
16
+ outputCode += `${appendDef}\n`;
17
+ }
18
+ return (line.length !== 0 && line[0] !== '#');
19
+ });
20
+
21
+ return outputCode;
22
+ }
23
+
24
+ module.exports = {
25
+ process(src) {
26
+ const doc = gql`${src}`;
27
+ const outputCode = `var doc = ${JSON.stringify(doc)};`;
28
+ const importOutputCode = expandImports(src, doc);
29
+
30
+ return `${outputCode}\n${importOutputCode}\nmodule.exports = doc;`;
31
+ }
32
+ };
@@ -0,0 +1,44 @@
1
+ /* eslint-disable no-param-reassign */
2
+ import requireAll from "./require_all";
3
+
4
+ const { I18n } = require("react-i18nify");
5
+
6
+ /**
7
+ * Load components translations from yaml files and import them into
8
+ * react-i18ify system so they can be used via `I18n.t` method.
9
+ * @returns {Void} - Nothing
10
+ */
11
+ const loadTranslations = () => {
12
+ const translationsContext = (<any> require).context("../../../config/locales/", true, /\.yml$/);
13
+ const translationFiles = requireAll(translationsContext);
14
+
15
+ const translations = translationsContext.keys().reduce((acc: any, key: string, index: number) => {
16
+ const match = key.match(/\.\/(.*)\.yml/);
17
+
18
+ if (match) {
19
+ let locale = match[1];
20
+ acc[locale] = translationFiles[index][locale].decidim;
21
+ }
22
+
23
+ return acc;
24
+ }, {});
25
+
26
+ I18n.setTranslations(translations);
27
+ };
28
+
29
+ /**
30
+ * Load components translations from a locale files and import them into
31
+ * react-i18ify system so they can be used via `I18n.t` method.
32
+ * @returns {Void} - Nothing
33
+ */
34
+ export const loadLocaleTranslations = (locale: string) => {
35
+ const translationFile = require(`./../../../config/locales/${locale}.yml`);
36
+ const translations = Object.keys(translationFile).reduce((acc: any, key: string) => {
37
+ acc[locale] = translationFile[locale].decidim;
38
+ return acc;
39
+ }, {});
40
+
41
+ I18n.setTranslations(translations);
42
+ };
43
+
44
+ export default loadTranslations;
@@ -3,7 +3,7 @@
3
3
  * @param {Object} requireContext - A webpack require context
4
4
  * @returns {Object[]} - An array of webpack modules
5
5
  */
6
- const requireAll = (requireContext) => {
6
+ const requireAll = (requireContext: any) => {
7
7
  return requireContext.keys().map(requireContext);
8
8
  };
9
9
 
@@ -1,5 +1,5 @@
1
- import graphql, { filter } from 'graphql-anywhere';
2
-
1
+ import graphql, { filter } from "graphql-anywhere";
2
+
3
3
  /**
4
4
  * A simple resolver which returns object properties to easily
5
5
  * traverse a graphql response
@@ -7,9 +7,9 @@ import graphql, { filter } from 'graphql-anywhere';
7
7
  * @param {Object} root - An object
8
8
  * @returns {any} - An object's property value
9
9
  */
10
- const resolver = (fieldName, root) => root[fieldName];
10
+ const resolver = (fieldName: string, root: any) => root[fieldName];
11
11
 
12
- /**
12
+ /**
13
13
  * A helper function to mock a graphql api request and return its
14
14
  * result. The result can be filtered by the same query so it just
15
15
  * returns a data subset.
@@ -17,7 +17,7 @@ const resolver = (fieldName, root) => root[fieldName];
17
17
  * @param {options} options - An object with optional options
18
18
  * @returns {Object} - The result of the query filtered or not
19
19
  */
20
- const resolveGraphQLQuery = (document, options = {}) => {
20
+ const resolveGraphQLQuery = (document: any, options: any = {}) => {
21
21
  const { filterResult, rootValue, context, variables } = options;
22
22
 
23
23
  let result = graphql(
@@ -25,13 +25,13 @@ const resolveGraphQLQuery = (document, options = {}) => {
25
25
  document,
26
26
  rootValue,
27
27
  context,
28
- variables
28
+ variables,
29
29
  );
30
30
 
31
31
  if (filterResult) {
32
32
  return filter(document, result);
33
33
  }
34
34
  return result;
35
- }
35
+ };
36
36
 
37
37
  export default resolveGraphQLQuery;