decidim-comments 0.0.3 → 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/app/assets/javascripts/decidim/comments/bundle.js +28 -23
  4. data/app/assets/javascripts/decidim/comments/bundle.js.map +1 -1
  5. data/app/commands/decidim/comments/create_comment.rb +10 -0
  6. data/app/forms/decidim/comments/comment_form.rb +1 -1
  7. data/app/frontend/comments/add_comment_form.component.jsx +171 -87
  8. data/app/frontend/comments/add_comment_form.component.test.jsx +41 -22
  9. data/app/frontend/comments/add_comment_form.mutation.graphql +5 -3
  10. data/app/frontend/comments/add_comment_form_commentable.fragment.graphql +4 -0
  11. data/app/frontend/comments/add_comment_form_session.fragment.graphql +6 -0
  12. data/app/frontend/comments/comment.component.jsx +18 -18
  13. data/app/frontend/comments/comment.component.test.jsx +22 -21
  14. data/app/frontend/comments/comment.fragment.graphql +4 -4
  15. data/app/frontend/comments/comment_data.fragment.graphql +4 -3
  16. data/app/frontend/comments/comment_order_selector.component.jsx +3 -3
  17. data/app/frontend/comments/comment_thread.component.jsx +8 -3
  18. data/app/frontend/comments/comment_thread.component.test.jsx +3 -3
  19. data/app/frontend/comments/comment_thread.fragment.graphql +1 -1
  20. data/app/frontend/comments/comments.component.jsx +52 -33
  21. data/app/frontend/comments/comments.component.test.jsx +51 -38
  22. data/app/frontend/comments/comments.query.graphql +10 -4
  23. data/app/frontend/comments/down_vote_button.component.jsx +6 -3
  24. data/app/frontend/comments/up_vote_button.component.jsx +7 -4
  25. data/app/frontend/comments/vote_button.component.jsx +5 -0
  26. data/app/frontend/comments/vote_button_component.test.jsx +1 -1
  27. data/app/frontend/entry.test.js +2 -0
  28. data/app/frontend/support/generate_comments_data.js +4 -4
  29. data/app/mailers/decidim/comments/comment_notification_mailer.rb +31 -0
  30. data/app/models/decidim/comments/comment.rb +13 -9
  31. data/app/queries/decidim/comments/{comments_with_replies.rb → sorted_comments.rb} +3 -8
  32. data/app/types/decidim/comments/commentable_interface.rb +44 -0
  33. data/app/types/decidim/comments/commentable_mutation_type.rb +29 -0
  34. data/app/types/decidim/comments/commentable_type.rb +14 -0
  35. data/app/views/decidim/comments/comment_notification_mailer/comment_created.html.erb +18 -0
  36. data/app/views/decidim/comments/comment_notification_mailer/reply_created.html.erb +18 -0
  37. data/config/locales/ca.yml +20 -4
  38. data/config/locales/en.yml +22 -5
  39. data/config/locales/es.yml +20 -4
  40. data/config/locales/eu.yml +5 -0
  41. data/lib/decidim/comments.rb +5 -0
  42. data/{app/types/decidim/comments → lib/decidim/comments/api}/add_comment_type.rb +0 -0
  43. data/{app/types/decidim/comments → lib/decidim/comments/api}/comment_mutation_type.rb +0 -0
  44. data/{app/types/decidim/comments → lib/decidim/comments/api}/comment_type.rb +11 -17
  45. data/lib/decidim/comments/commentable.rb +45 -0
  46. data/{app/helpers → lib}/decidim/comments/comments_helper.rb +15 -10
  47. data/lib/decidim/comments/mutation_extensions.rb +8 -16
  48. data/lib/decidim/comments/query_extensions.rb +5 -8
  49. data/lib/decidim/comments/test/factories.rb +3 -3
  50. metadata +21 -12
  51. data/app/frontend/comments/add_comment_form.fragment.graphql +0 -6
@@ -24,4 +24,9 @@ VoteButton.propTypes = {
24
24
  disabled: PropTypes.bool
25
25
  };
26
26
 
27
+ VoteButton.defaultProps = {
28
+ selectedClass: "selected",
29
+ disabled: false
30
+ };
31
+
27
32
  export default VoteButton;
@@ -12,7 +12,7 @@ describe("<VoteButton />", () => {
12
12
 
13
13
  it("should render the number of votes passed as a prop", () => {
14
14
  const wrapper = shallow(<VoteButton votes={10} buttonClassName="vote-button" iconName="vote-icon" voteAction={voteAction} />);
15
- expect(wrapper.find('button')).to.include.text(10);
15
+ expect(wrapper.find('button').text()).to.match(/10/);
16
16
  });
17
17
 
18
18
  it("should render a button with the given buttonClassName", () => {
@@ -9,6 +9,8 @@ import chaiEnzyme from 'chai-enzyme';
9
9
  import loadTranslations from './support/load_translations';
10
10
  import requireAll from './support/require_all';
11
11
 
12
+ require('jquery');
13
+
12
14
  //
13
15
  chai.use(sinonChai)
14
16
  chai.use(chaiAsPromised)
@@ -1,6 +1,6 @@
1
1
  import { random, name, date, image } from 'faker/locale/en';
2
2
 
3
- /**
3
+ /**
4
4
  * Generate random comment data to emulate a database real content
5
5
  * @param {number} num - The number of comments to generate random data
6
6
  * @returns {Object[]} - An array of objects representing comments data
@@ -17,9 +17,9 @@ const generateCommentsData = (num = 1) => {
17
17
  name: name.findName(),
18
18
  avatarUrl: image.imageUrl()
19
19
  },
20
- hasReplies: false,
21
- replies: [],
22
- canHaveReplies: true,
20
+ hasComments: false,
21
+ comments: [],
22
+ acceptsNewComments: true,
23
23
  alignment: 0,
24
24
  upVotes: random.number(),
25
25
  upVoted: false,
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+ module Decidim
3
+ module Comments
4
+ # A custom mailer for sending notifications to users when
5
+ # a comment is created.
6
+ class CommentNotificationMailer < Decidim::ApplicationMailer
7
+ helper Decidim::ResourceHelper
8
+
9
+ def comment_created(user, comment, commentable)
10
+ with_user(user) do
11
+ @comment = comment
12
+ @commentable = commentable
13
+ @organization = commentable.organization
14
+ subject = I18n.t("comment_created.subject", scope: "decidim.comments.mailer.comment_notification")
15
+ mail(to: commentable.author.email, subject: subject)
16
+ end
17
+ end
18
+
19
+ def reply_created(user, reply, comment, commentable)
20
+ with_user(user) do
21
+ @reply = reply
22
+ @comment = comment
23
+ @commentable = commentable
24
+ @organization = commentable.organization
25
+ subject = I18n.t("reply_created.subject", scope: "decidim.comments.mailer.comment_notification")
26
+ mail(to: comment.author.email, subject: subject)
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -6,6 +6,7 @@ module Decidim
6
6
  # to discuss or share their thoughts about the resource.
7
7
  class Comment < ApplicationRecord
8
8
  include Decidim::Authorable
9
+ include Decidim::Comments::Commentable
9
10
 
10
11
  # Limit the max depth of a comment tree. If C is a comment and R is a reply:
11
12
  # C (depth 0)
@@ -16,7 +17,6 @@ module Decidim
16
17
  MAX_DEPTH = 3
17
18
 
18
19
  belongs_to :commentable, foreign_key: "decidim_commentable_id", foreign_type: "decidim_commentable_type", polymorphic: true
19
- has_many :replies, as: :commentable, foreign_key: "decidim_commentable_id", foreign_type: "decidim_commentable_type", class_name: Comment
20
20
  has_many :up_votes, -> { where(weight: 1) }, foreign_key: "decidim_comment_id", class_name: CommentVote, dependent: :destroy
21
21
  has_many :down_votes, -> { where(weight: -1) }, foreign_key: "decidim_comment_id", class_name: CommentVote, dependent: :destroy
22
22
 
@@ -24,16 +24,14 @@ module Decidim
24
24
  validates :depth, numericality: { greater_than_or_equal_to: 0 }
25
25
  validates :alignment, inclusion: { in: [0, 1, -1] }
26
26
 
27
- validate :commentable_can_have_replies
27
+ validate :commentable_can_have_comments
28
28
 
29
29
  before_save :compute_depth
30
30
 
31
31
  delegate :organization, to: :commentable
32
32
 
33
- # Public: Define if a comment can have replies or not
34
- #
35
- # Returns a bool value to indicate if comment can have replies
36
- def can_have_replies?
33
+ # Public: Override Commentable concern method `accepts_new_comments?`
34
+ def accepts_new_comments?
37
35
  depth < MAX_DEPTH
38
36
  end
39
37
 
@@ -51,12 +49,18 @@ module Decidim
51
49
  down_votes.any? { |vote| vote.author == user }
52
50
  end
53
51
 
52
+ # Public: Returns the commentable object of the parent comment
53
+ def root_commentable
54
+ return commentable if depth == 0
55
+ commentable.root_commentable
56
+ end
57
+
54
58
  private
55
59
 
56
- # Private: Check if commentable can have replies and if not adds
60
+ # Private: Check if commentable can have comments and if not adds
57
61
  # a validation error to the model
58
- def commentable_can_have_replies
59
- errors.add(:commentable, :cannot_have_replies) if commentable.respond_to?(:can_have_replies?) && !commentable.can_have_replies?
62
+ def commentable_can_have_comments
63
+ errors.add(:commentable, :cannot_have_comments) unless commentable.accepts_new_comments?
60
64
  end
61
65
 
62
66
  # Private: Compute comment depth inside the current comment tree
@@ -2,7 +2,7 @@
2
2
  module Decidim
3
3
  module Comments
4
4
  # A class used to find comments for a commentable resource
5
- class CommentsWithReplies < Rectify::Query
5
+ class SortedComments < Rectify::Query
6
6
  attr_reader :commentable
7
7
 
8
8
  # Syntactic sugar to initialize the class and return the queried objects.
@@ -32,11 +32,6 @@ module Decidim
32
32
  scope = Comment
33
33
  .where(commentable: commentable)
34
34
  .includes(:author, :up_votes, :down_votes)
35
- .includes(
36
- replies: [:author, :up_votes, :down_votes,
37
- replies: [:author, :up_votes, :down_votes,
38
- replies: [:author, :up_votes, :down_votes]]]
39
- )
40
35
 
41
36
  scope = case @options[:order_by]
42
37
  when "older"
@@ -77,8 +72,8 @@ module Decidim
77
72
  end
78
73
 
79
74
  def count_replies(comment)
80
- if comment.replies.size.positive?
81
- comment.replies.size + comment.replies.map { |reply| count_replies(reply) }.sum
75
+ if comment.comments.size.positive?
76
+ comment.comments.size + comment.comments.sum { |reply| count_replies(reply) }
82
77
  else
83
78
  0
84
79
  end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+ module Decidim
3
+ module Comments
4
+ # This interface represents a commentable object.
5
+ CommentableInterface = GraphQL::InterfaceType.define do
6
+ name "CommentableInterface"
7
+ description "A commentable interface"
8
+
9
+ field :id, !types.ID, "The commentable's ID"
10
+
11
+ field :type, !types.String, "The commentable's class name. i.e. `Decidim::ParticipatoryProcess`" do
12
+ property :commentable_type
13
+ end
14
+
15
+ field :acceptsNewComments, !types.Boolean, "Wether the object can have new comments or not" do
16
+ property :accepts_new_comments?
17
+ end
18
+
19
+ field :commentsHaveAlignment, !types.Boolean, "Wether the object comments have alignment or not" do
20
+ property :comments_have_alignment?
21
+ end
22
+
23
+ field :commentsHaveVotes, !types.Boolean, "Wether the object comments have votes or not" do
24
+ property :comments_have_votes?
25
+ end
26
+
27
+ field :comments do
28
+ type !types[CommentType]
29
+
30
+ argument :orderBy, types.String, "Order the comments"
31
+
32
+ resolve lambda { |obj, args, _ctx|
33
+ SortedComments.for(obj, order_by: args[:orderBy])
34
+ }
35
+ end
36
+
37
+ field :hasComments, !types.Boolean, "Check if the commentable has comments" do
38
+ resolve lambda { |obj, _args, _ctx|
39
+ obj.comments.size.positive?
40
+ }
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+ module Decidim
3
+ module Comments
4
+ CommentableMutationType = GraphQL::ObjectType.define do
5
+ name "CommentableMutation"
6
+ description "A commentable which includes its available mutations"
7
+
8
+ field :id, !types.ID, "The Commentable's unique ID"
9
+
10
+ field :addComment, Decidim::Comments::CommentType do
11
+ description "Add a new comment to a commentable"
12
+
13
+ argument :body, !types.String, "The comments's body"
14
+ argument :alignment, types.Int, "The comment's alignment. Can be 0 (neutral), 1 (in favor) or -1 (against)'", default_value: 0
15
+ argument :userGroupId, types.ID, "The comment's user group id. Replaces the author."
16
+
17
+ resolve lambda { |obj, args, ctx|
18
+ params = { "comment" => { "body" => args[:body], "alignment" => args[:alignment], "user_group_id" => args[:userGroupId] } }
19
+ form = Decidim::Comments::CommentForm.from_params(params)
20
+ Decidim::Comments::CreateComment.call(form, ctx[:current_user], obj) do
21
+ on(:ok) do |comment|
22
+ return comment
23
+ end
24
+ end
25
+ }
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+ module Decidim
3
+ module Comments
4
+ # This type represents a commentable object.
5
+ CommentableType = GraphQL::ObjectType.define do
6
+ name "Commentable"
7
+ description "A commentable object"
8
+
9
+ interfaces [
10
+ Decidim::Comments::CommentableInterface
11
+ ]
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,18 @@
1
+ <p><%= t("decidim.comments.comment_notification_mailer.hello", name: @commentable.author.name) %></p>
2
+
3
+ <p>
4
+ <%=
5
+ t(".new_comment_html", {
6
+ commenter: @comment.author.name,
7
+ commentable_link: link_to(@commentable.title, decidim_resource_url(@commentable))
8
+ })
9
+ %>
10
+ </p>
11
+
12
+ <blockquote>
13
+ <%= @comment.body %>
14
+ </blockquote>
15
+
16
+ <p>
17
+ <%= t("decidim.comments.comment_notification_mailer.manage_email_subscriptions_html", link: link_to(t("decidim.comments.comment_notification_mailer.notifications_settings_link"), decidim.notifications_settings_url(host: @organization.host))) %>
18
+ </p>
@@ -0,0 +1,18 @@
1
+ <p><%= t("decidim.comments.comment_notification_mailer.hello", name: @comment.author.name) %></p>
2
+
3
+ <p>
4
+ <%=
5
+ t(".new_reply_html", {
6
+ commenter: @reply.author.name,
7
+ commentable_link: link_to(@commentable.title, decidim_resource_url(@commentable))
8
+ })
9
+ %>
10
+ </p>
11
+
12
+ <blockquote>
13
+ <%= @reply.body %>
14
+ </blockquote>
15
+
16
+ <p>
17
+ <%= t("decidim.comments.comment_notification_mailer.manage_email_subscriptions_html", link: link_to(t("decidim.comments.comment_notification_mailer.notifications_settings_link"), decidim.notifications_settings_url(host: @organization.host))) %>
18
+ </p>
@@ -2,21 +2,36 @@ ca:
2
2
  activerecord:
3
3
  errors:
4
4
  messages:
5
- cannot_have_replies: no pot tenir respostes
5
+ cannot_have_comments: no pot tenir comentaris
6
6
  decidim:
7
+ comments:
8
+ comment_notification_mailer:
9
+ comment_created:
10
+ new_comment_html: Hi ha un nou comentari d'<b>%{commenter}</b> a <b>%{commentable_link}</b>
11
+ hello: Hola %{name},
12
+ manage_email_subscriptions_html: Pots deixar de rebre aquests correus electrònics canviant la configuració a %{link}.
13
+ notifications_settings_link: la pàgina de configuració de les notificacions
14
+ reply_created:
15
+ new_reply_html: Hi ha una nova resposta del teu comentari de l'<b>%{commenter}</b> a <b>%{commentable_link}</b>
16
+ mailer:
17
+ comment_notification:
18
+ comment_created:
19
+ subject: Tens un nou comentari
20
+ reply_created:
21
+ subject: Tens una nova resposta del teu comentari
7
22
  components:
8
23
  add_comment_form:
24
+ account_message: "<a href=\"%{sign_in_url}\">Entra amb el teu compte</a> o <a href=\"%{sign_up_url}\">registra't</a> per a deixar un comentari.\n"
9
25
  form:
10
26
  body:
11
27
  label: Comentari
12
28
  placeholder: Què en penses d'això?
29
+ form_error: El text és necessari i no pot ser més llarg de %{length}
13
30
  submit: Envia
14
31
  user_group_id:
15
32
  label: Comentar com a
16
33
  opinion:
17
- against: Hi estic en contra
18
- in_favor: Hi estic a favor
19
- neutral: Sóc neutral
34
+ neutral: Neutral
20
35
  title: Deixa el teu comentari
21
36
  comment:
22
37
  alignment:
@@ -33,6 +48,7 @@ ca:
33
48
  comment_thread:
34
49
  title: Conversa amb %{authorName}
35
50
  comments:
51
+ blocked_comments_warning: Els comentaris estan desactivats a la fase actual però pots llegir els comentaris de les fases anteriors.
36
52
  loading: Carregant els comentaris ...
37
53
  title: "%{count} comentaris"
38
54
  featured_comment:
@@ -3,22 +3,38 @@ en:
3
3
  activerecord:
4
4
  errors:
5
5
  messages:
6
- cannot_have_replies: can't have replies
6
+ cannot_have_comments: can't have comments
7
7
  decidim:
8
+ comments:
9
+ comment_notification_mailer:
10
+ comment_created:
11
+ new_comment_html: There is a new comment from <b>%{commenter}</b> in <b>%{commentable_link}</b>
12
+ hello: Hello %{name},
13
+ manage_email_subscriptions_html: You can stop receiving these emails by changing your settings in %{link}.
14
+ notifications_settings_link: the notifications settings page
15
+ reply_created:
16
+ new_reply_html: There is a new reply of your comment from <b>%{commenter}</b> in <b>%{commentable_link}</b>
17
+ mailer:
18
+ comment_notification:
19
+ comment_created:
20
+ subject: You have a new comment
21
+ reply_created:
22
+ subject: You have a new reply of your comment
8
23
  components:
9
24
  add_comment_form:
25
+ account_message: "<a href=\"%{sign_in_url}\">Sign in with your account</a> or <a href=\"%{sign_up_url}\">sign up</a> to add your comment.
26
+ "
10
27
  form:
11
28
  body:
12
29
  label: Comment
13
30
  placeholder: What do you think about this?
31
+ form_error: The text is required and it can't be longer than %{length} characters.
14
32
  submit: Send
15
33
  user_group_id:
16
34
  label: Comment as
17
35
  opinion:
18
- against: I am against
19
- in_favor: I am in favor
20
- neutral: I am neutral
21
- title: Leave your comment
36
+ neutral: Neutral
37
+ title: Add your comment
22
38
  comment:
23
39
  alignment:
24
40
  against: Against
@@ -34,6 +50,7 @@ en:
34
50
  comment_thread:
35
51
  title: Conversation with %{authorName}
36
52
  comments:
53
+ blocked_comments_warning: Comments are disabled in the current step, but you can read the comments from previous steps.
37
54
  loading: Loading comments ...
38
55
  title: "%{count} comments"
39
56
  featured_comment:
@@ -2,21 +2,36 @@ es:
2
2
  activerecord:
3
3
  errors:
4
4
  messages:
5
- cannot_have_replies: no pueden tener respuestas
5
+ cannot_have_comments: no puede tener comentarios
6
6
  decidim:
7
+ comments:
8
+ comment_notification_mailer:
9
+ comment_created:
10
+ new_comment_html: Hay un nuevo comentario de <b>%{commenter}</b> en <b>%{commentable_link}</b>
11
+ hello: Hola %{name},
12
+ manage_email_subscriptions_html: Puedes dejar de recibir estos correos electrónicos cambiando la configuración en %{link}.
13
+ notifications_settings_link: la página de configuración de las notificaciones
14
+ reply_created:
15
+ new_reply_html: Hay una nueva respuesta de tu comentario de <b>%{commenter}</b> en <b>%{commentable_link}</b>
16
+ mailer:
17
+ comment_notification:
18
+ comment_created:
19
+ subject: Tienes un nuevo comentario
20
+ reply_created:
21
+ subject: Uno de tus comentarios ha recibido respuesta
7
22
  components:
8
23
  add_comment_form:
24
+ account_message: "<a href=\"%{sign_in_url}\">Entra con tu cuenta</a> o <a href=\"%{sign_up_url}\">regístrate</a> para dejar tu comentario.\n"
9
25
  form:
10
26
  body:
11
27
  label: Comentario
12
28
  placeholder: '¿Qué piensas sobre esto?'
29
+ form_error: El texto es necesario y no puede ser más de caracteres %{length}.
13
30
  submit: Enviar
14
31
  user_group_id:
15
32
  label: Comentar como
16
33
  opinion:
17
- against: Estoy en contra
18
- in_favor: Estoy a favor
19
- neutral: Soy neutral
34
+ neutral: Neutral
20
35
  title: Deje su comentario
21
36
  comment:
22
37
  alignment:
@@ -33,6 +48,7 @@ es:
33
48
  comment_thread:
34
49
  title: Conversación con %{authorName}
35
50
  comments:
51
+ blocked_comments_warning: Los comentarios estan desactivados en la fase actual pero puedes leer los comentarios de las fases anteriores.
36
52
  loading: Cargando los comentarios ...
37
53
  title: "%{count} comentarios"
38
54
  featured_comment: