decidim-comments 0.0.3 → 0.0.5

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 (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: