decidim-comments 0.24.2 → 0.25.0.rc3
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.
- checksums.yaml +4 -4
- data/README.md +3 -20
- data/app/assets/javascripts/decidim/comments/bundle.js.map +1 -1
- data/app/cells/decidim/comments/comment/actions.erb +1 -1
- data/app/cells/decidim/comments/comment/deletion_data.erb +1 -0
- data/app/cells/decidim/comments/comment/show.erb +30 -21
- data/app/cells/decidim/comments/comment/utilities.erb +40 -12
- data/app/cells/decidim/comments/comment/votes.erb +6 -6
- data/app/cells/decidim/comments/comment_cell.rb +29 -0
- data/app/cells/decidim/comments/comment_form/show.erb +1 -1
- data/app/cells/decidim/comments/comments/add_comment.erb +10 -6
- data/app/cells/decidim/comments/comments/order_control.erb +4 -5
- data/app/cells/decidim/comments/comments/show.erb +2 -4
- data/app/cells/decidim/comments/comments/user_comments_blocked_warning.erb +5 -1
- data/app/cells/decidim/comments/comments_cell.rb +24 -2
- data/app/cells/decidim/comments/edit_comment_modal_form/show.erb +29 -0
- data/app/cells/decidim/comments/edit_comment_modal_form_cell.rb +53 -0
- data/app/commands/decidim/comments/create_comment.rb +2 -1
- data/app/commands/decidim/comments/delete_comment.rb +46 -0
- data/app/commands/decidim/comments/update_comment.rb +62 -0
- data/app/controllers/decidim/comments/comments_controller.rb +63 -6
- data/app/events/decidim/comments/comment_voted_event.rb +9 -0
- data/app/forms/decidim/comments/comment_form.rb +1 -1
- data/app/models/decidim/comments/comment.rb +24 -1
- data/app/packs/src/decidim/comments/comments.component.js +300 -0
- data/app/{assets/javascripts → packs/src}/decidim/comments/comments.component.test.js +116 -26
- data/app/packs/src/decidim/comments/comments.component_for_testing.js +8 -0
- data/app/packs/src/decidim/comments/comments.js +1 -0
- data/app/permissions/decidim/comments/permissions.rb +10 -1
- data/app/queries/decidim/comments/metrics/comment_participants_metric_measure.rb +1 -1
- data/app/queries/decidim/comments/metrics/comments_metric_manage.rb +1 -1
- data/app/queries/decidim/comments/sorted_comments.rb +8 -6
- data/app/views/decidim/comments/comments/_delete.html.erb +5 -0
- data/app/views/decidim/comments/comments/_edited_comment.html.erb +1 -0
- data/app/views/decidim/comments/comments/create.js.erb +4 -2
- data/app/views/decidim/comments/comments/delete.js.erb +17 -0
- data/app/views/decidim/comments/comments/deletion_error.js.erb +1 -0
- data/app/views/decidim/comments/comments/reload.js.erb +2 -0
- data/app/views/decidim/comments/comments/update.js.erb +8 -0
- data/app/views/decidim/comments/comments/update_error.js.erb +1 -0
- data/config/assets.rb +5 -0
- data/config/locales/ar.yml +0 -1
- data/config/locales/ca.yml +7 -1
- data/config/locales/cs.yml +25 -1
- data/config/locales/de.yml +7 -1
- data/config/locales/el.yml +0 -1
- data/config/locales/en.yml +25 -1
- data/config/locales/es-MX.yml +7 -1
- data/config/locales/es-PY.yml +7 -1
- data/config/locales/es.yml +7 -1
- data/config/locales/fi-plain.yml +25 -1
- data/config/locales/fi.yml +25 -1
- data/config/locales/fr-CA.yml +25 -1
- data/config/locales/fr-LU.yml +162 -0
- data/config/locales/fr.yml +25 -1
- data/config/locales/gl.yml +25 -1
- data/config/locales/hu.yml +0 -1
- data/config/locales/it.yml +38 -1
- data/config/locales/ja.yml +35 -1
- data/config/locales/lb-LU.yml +1 -0
- data/config/locales/lv.yml +0 -1
- data/config/locales/nl.yml +27 -2
- data/config/locales/no.yml +0 -1
- data/config/locales/pl.yml +7 -1
- data/config/locales/pt-BR.yml +61 -0
- data/config/locales/pt.yml +0 -1
- data/config/locales/ro-RO.yml +25 -1
- data/config/locales/sk.yml +0 -1
- data/config/locales/sr-CS.yml +0 -1
- data/config/locales/sv.yml +25 -1
- data/config/locales/tr-TR.yml +0 -1
- data/config/locales/zh-CN.yml +0 -1
- data/db/migrate/20200706123136_make_comments_handle_i18n.rb +1 -1
- data/db/migrate/20210402124534_add_participatory_process_to_comments.rb +12 -0
- data/db/migrate/20210529095942_add_deleted_at_column_to_comments.rb +7 -0
- data/lib/decidim/comments/commentable.rb +6 -1
- data/lib/decidim/comments/commentable_with_component.rb +33 -0
- data/lib/decidim/comments/engine.rb +1 -9
- data/lib/decidim/comments/test/factories.rb +1 -0
- data/lib/decidim/comments/version.rb +1 -1
- data/lib/decidim/comments.rb +1 -0
- data/lib/tasks/decidim_comments.rake +15 -0
- metadata +32 -29
- data/app/assets/config/decidim_comments_manifest.js +0 -1
- data/app/assets/javascripts/decidim/comments/comments.component.js.es6 +0 -292
- data/app/assets/javascripts/decidim/comments/comments.js.erb +0 -10
- data/config/locales/ja-JP.yml +0 -120
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
<div class="reveal edit-comment-modal" id="<%= "editCommentModal#{model.id}" %>" data-reveal>
|
|
2
|
+
<div class="reveal__header">
|
|
3
|
+
<h3 class="reveal__title"><%= t("decidim.components.edit_comment_modal_form.title") %></h3>
|
|
4
|
+
<button class="close-button" data-close aria-label="<%= t("decidim.components.edit_comment_modal_form.close") %>" type="button">
|
|
5
|
+
<span aria-hidden="true">×</span>
|
|
6
|
+
</button>
|
|
7
|
+
</div>
|
|
8
|
+
<%= form_for(form_object, url: decidim_comments.comment_path(comment), method: :put, remote: true, html: { id: form_id }) do |form| %>
|
|
9
|
+
<div class="field">
|
|
10
|
+
<label class="show-for-sr" for="<%= form_id %>">
|
|
11
|
+
<%= t("decidim.components.edit_comment_modal_form.form.body.label") %>
|
|
12
|
+
</label>
|
|
13
|
+
<div class="hashtags__container">
|
|
14
|
+
<%= form.text_area(
|
|
15
|
+
:body,
|
|
16
|
+
id: form_id,
|
|
17
|
+
rows: 4,
|
|
18
|
+
maxlength: comments_max_length,
|
|
19
|
+
required: true,
|
|
20
|
+
placeholder: t("decidim.components.edit_comment_modal_form.form.body.placeholder"),
|
|
21
|
+
label: false,
|
|
22
|
+
data: { remaining_characters: "##{form_id}-remaining-characters" }
|
|
23
|
+
) %>
|
|
24
|
+
</div>
|
|
25
|
+
<button type="submit" data-close class="button button--sc"><%= t("decidim.components.edit_comment_modal_form.form.submit") %></button>
|
|
26
|
+
<span id="<%= form_id %>-remaining-characters" class="remaining-character-count"></span>
|
|
27
|
+
</div>
|
|
28
|
+
<% end %>
|
|
29
|
+
</div>
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Decidim
|
|
4
|
+
module Comments
|
|
5
|
+
# A cell to display a form for edditing a comment.
|
|
6
|
+
class EditCommentModalFormCell < Decidim::ViewModel
|
|
7
|
+
delegate :current_user, :user_signed_in?, to: :controller
|
|
8
|
+
alias comment model
|
|
9
|
+
|
|
10
|
+
private
|
|
11
|
+
|
|
12
|
+
def decidim_comments
|
|
13
|
+
Decidim::Comments::Engine.routes.url_helpers
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def form_id
|
|
17
|
+
"edit_comment_#{comment.id}"
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def form_object
|
|
21
|
+
Decidim::Comments::CommentForm.new(
|
|
22
|
+
body: comment.translated_body
|
|
23
|
+
)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def comments_max_length
|
|
27
|
+
return 1000 unless model.respond_to?(:component)
|
|
28
|
+
return component_comments_max_length if component_comments_max_length
|
|
29
|
+
return organization_comments_max_length if organization_comments_max_length
|
|
30
|
+
|
|
31
|
+
1000
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def component_comments_max_length
|
|
35
|
+
return unless model.component&.settings.respond_to?(:comments_max_length)
|
|
36
|
+
|
|
37
|
+
model.component.settings.comments_max_length if model.component.settings.comments_max_length.positive?
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def organization_comments_max_length
|
|
41
|
+
return unless organization
|
|
42
|
+
|
|
43
|
+
organization.comments_max_length if organization.comments_max_length.positive?
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def organization
|
|
47
|
+
return model.organization if model.respond_to?(:organization)
|
|
48
|
+
|
|
49
|
+
model.component.organization if model.component.organization.comments_max_length.positive?
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
@@ -39,7 +39,8 @@ module Decidim
|
|
|
39
39
|
root_commentable: root_commentable(form.commentable),
|
|
40
40
|
body: { I18n.locale => parsed.rewrite },
|
|
41
41
|
alignment: form.alignment,
|
|
42
|
-
decidim_user_group_id: form.user_group_id
|
|
42
|
+
decidim_user_group_id: form.user_group_id,
|
|
43
|
+
participatory_space: form.current_component.try(:participatory_space)
|
|
43
44
|
}
|
|
44
45
|
|
|
45
46
|
@comment = Decidim.traceability.create!(
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Decidim
|
|
4
|
+
module Comments
|
|
5
|
+
# A command with all the business logic to delete a comment
|
|
6
|
+
class DeleteComment < Rectify::Command
|
|
7
|
+
# Public: Initializes the command.
|
|
8
|
+
#
|
|
9
|
+
# comment - The comment to delete.
|
|
10
|
+
# current_user - The user performing the action.
|
|
11
|
+
def initialize(comment, current_user)
|
|
12
|
+
@comment = comment
|
|
13
|
+
@current_user = current_user
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# Executes the command. Broadcasts these events:
|
|
17
|
+
#
|
|
18
|
+
# - :ok when everything is valid.
|
|
19
|
+
# - :invalid if comment isn't authored by current_user.
|
|
20
|
+
#
|
|
21
|
+
# Returns nothing.
|
|
22
|
+
def call
|
|
23
|
+
return broadcast(:invalid) unless comment.authored_by?(current_user)
|
|
24
|
+
|
|
25
|
+
delete_comment
|
|
26
|
+
|
|
27
|
+
broadcast(:ok)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
private
|
|
31
|
+
|
|
32
|
+
attr_reader :comment, :current_user
|
|
33
|
+
|
|
34
|
+
def delete_comment
|
|
35
|
+
Decidim.traceability.perform_action!(
|
|
36
|
+
:delete,
|
|
37
|
+
comment,
|
|
38
|
+
current_user,
|
|
39
|
+
visibility: "public-only"
|
|
40
|
+
) do
|
|
41
|
+
comment.delete!
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Decidim
|
|
4
|
+
module Comments
|
|
5
|
+
# A command with all the business logic to update an existing comment
|
|
6
|
+
class UpdateComment < Rectify::Command
|
|
7
|
+
# Public: Initializes the command.
|
|
8
|
+
#
|
|
9
|
+
# comment - Decidim::Comments::Comment
|
|
10
|
+
# current_user - Decidim::User
|
|
11
|
+
# form - A form object with the params.
|
|
12
|
+
def initialize(comment, current_user, form)
|
|
13
|
+
@comment = comment
|
|
14
|
+
@current_user = current_user
|
|
15
|
+
@form = form
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Executes the command. Broadcasts these events:
|
|
19
|
+
#
|
|
20
|
+
# - :ok when everything is valid.
|
|
21
|
+
# - :invalid if the form wasn't valid and we couldn't proceed.
|
|
22
|
+
#
|
|
23
|
+
# Returns nothing.
|
|
24
|
+
def call
|
|
25
|
+
return broadcast(:invalid) if form.invalid? || !comment.authored_by?(current_user)
|
|
26
|
+
|
|
27
|
+
update_comment
|
|
28
|
+
|
|
29
|
+
broadcast(:ok)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
private
|
|
33
|
+
|
|
34
|
+
attr_reader :form, :comment, :current_user
|
|
35
|
+
|
|
36
|
+
def update_comment
|
|
37
|
+
parsed = Decidim::ContentProcessor.parse(form.body, current_organization: form.current_organization)
|
|
38
|
+
|
|
39
|
+
params = {
|
|
40
|
+
body: { I18n.locale => parsed.rewrite }
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
@comment = Decidim.traceability.update!(
|
|
44
|
+
comment,
|
|
45
|
+
current_user,
|
|
46
|
+
params,
|
|
47
|
+
visibility: "public-only",
|
|
48
|
+
edit: true
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
mentioned_users = parsed.metadata[:user].users
|
|
52
|
+
mentioned_groups = parsed.metadata[:user_group].groups
|
|
53
|
+
CommentCreation.publish(@comment, parsed.metadata)
|
|
54
|
+
send_notifications(mentioned_users, mentioned_groups)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def send_notifications(mentioned_users, mentioned_groups)
|
|
58
|
+
NewCommentNotificationCreator.new(comment, mentioned_users, mentioned_groups).create
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
@@ -8,8 +8,8 @@ module Decidim
|
|
|
8
8
|
include Decidim::ResourceHelper
|
|
9
9
|
|
|
10
10
|
before_action :authenticate_user!, only: [:create]
|
|
11
|
-
before_action :set_commentable
|
|
12
|
-
before_action :ensure_commentable
|
|
11
|
+
before_action :set_commentable, except: [:destroy, :update]
|
|
12
|
+
before_action :ensure_commentable!, except: [:destroy, :update]
|
|
13
13
|
|
|
14
14
|
helper_method :root_depth, :commentable, :order, :reply?, :reload?
|
|
15
15
|
|
|
@@ -21,7 +21,7 @@ module Decidim
|
|
|
21
21
|
order_by: order,
|
|
22
22
|
after: params.fetch(:after, 0).to_i
|
|
23
23
|
)
|
|
24
|
-
@comments_count = commentable.
|
|
24
|
+
@comments_count = commentable.comments_count
|
|
25
25
|
|
|
26
26
|
respond_to do |format|
|
|
27
27
|
format.js do
|
|
@@ -37,6 +37,31 @@ module Decidim
|
|
|
37
37
|
end
|
|
38
38
|
end
|
|
39
39
|
|
|
40
|
+
def update
|
|
41
|
+
set_comment
|
|
42
|
+
enforce_permission_to :update, :comment, comment: comment
|
|
43
|
+
|
|
44
|
+
form = Decidim::Comments::CommentForm.from_params(
|
|
45
|
+
params.merge(commentable: comment.commentable)
|
|
46
|
+
).with_context(
|
|
47
|
+
current_organization: current_organization
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
Decidim::Comments::UpdateComment.call(comment, current_user, form) do
|
|
51
|
+
on(:ok) do
|
|
52
|
+
respond_to do |format|
|
|
53
|
+
format.js { render :update }
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
on(:invalid) do
|
|
58
|
+
respond_to do |format|
|
|
59
|
+
format.js { render :update_error }
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
40
65
|
def create
|
|
41
66
|
enforce_permission_to :create, :comment, commentable: commentable
|
|
42
67
|
|
|
@@ -44,7 +69,7 @@ module Decidim
|
|
|
44
69
|
params.merge(commentable: commentable)
|
|
45
70
|
).with_context(
|
|
46
71
|
current_organization: current_organization,
|
|
47
|
-
current_component:
|
|
72
|
+
current_component: current_component
|
|
48
73
|
)
|
|
49
74
|
Decidim::Comments::CreateComment.call(form, current_user) do
|
|
50
75
|
on(:ok) do |comment|
|
|
@@ -63,6 +88,34 @@ module Decidim
|
|
|
63
88
|
end
|
|
64
89
|
end
|
|
65
90
|
|
|
91
|
+
def current_component
|
|
92
|
+
return commentable.component if commentable.respond_to?(:component)
|
|
93
|
+
return commentable.participatory_space if commentable.respond_to?(:participatory_space)
|
|
94
|
+
return commentable if Decidim.participatory_space_manifests.find { |manifest| manifest.model_class_name == commentable.class.name }
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def destroy
|
|
98
|
+
set_comment
|
|
99
|
+
@commentable = @comment.commentable
|
|
100
|
+
|
|
101
|
+
enforce_permission_to :destroy, :comment, comment: comment
|
|
102
|
+
|
|
103
|
+
Decidim::Comments::DeleteComment.call(comment, current_user) do
|
|
104
|
+
on(:ok) do
|
|
105
|
+
@comments_count = @comment.root_commentable.comments_count
|
|
106
|
+
respond_to do |format|
|
|
107
|
+
format.js { render :delete }
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
on(:invalid) do
|
|
112
|
+
respond_to do |format|
|
|
113
|
+
format.js { render :deletion_error }
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
66
119
|
private
|
|
67
120
|
|
|
68
121
|
attr_reader :commentable, :comment
|
|
@@ -71,6 +124,10 @@ module Decidim
|
|
|
71
124
|
@commentable = GlobalID::Locator.locate_signed(commentable_gid)
|
|
72
125
|
end
|
|
73
126
|
|
|
127
|
+
def set_comment
|
|
128
|
+
@comment = Decidim::Comments::Comment.find_by(id: params[:id])
|
|
129
|
+
end
|
|
130
|
+
|
|
74
131
|
def ensure_commentable!
|
|
75
132
|
raise ActionController::RoutingError, "Not Found" unless commentable
|
|
76
133
|
end
|
|
@@ -80,9 +137,9 @@ module Decidim
|
|
|
80
137
|
@comments_count = begin
|
|
81
138
|
case commentable
|
|
82
139
|
when Decidim::Comments::Comment
|
|
83
|
-
commentable.root_commentable.
|
|
140
|
+
commentable.root_commentable.comments_count
|
|
84
141
|
else
|
|
85
|
-
commentable.
|
|
142
|
+
commentable.comments_count
|
|
86
143
|
end
|
|
87
144
|
end
|
|
88
145
|
end
|
|
@@ -8,6 +8,11 @@ module Decidim
|
|
|
8
8
|
i18n_attributes :upvotes
|
|
9
9
|
i18n_attributes :downvotes
|
|
10
10
|
|
|
11
|
+
def initialize(resource:, event_name:, user:, user_role: nil, extra: nil)
|
|
12
|
+
resource = target_resource(resource)
|
|
13
|
+
super
|
|
14
|
+
end
|
|
15
|
+
|
|
11
16
|
def upvotes
|
|
12
17
|
extra[:upvotes]
|
|
13
18
|
end
|
|
@@ -21,6 +26,10 @@ module Decidim
|
|
|
21
26
|
def resource_url_params
|
|
22
27
|
{ anchor: "comment_#{comment.id}" }
|
|
23
28
|
end
|
|
29
|
+
|
|
30
|
+
def target_resource(t_resource)
|
|
31
|
+
t_resource.is_a?(Decidim::Comments::Comment) ? t_resource.root_commentable : t_resource
|
|
32
|
+
end
|
|
24
33
|
end
|
|
25
34
|
end
|
|
26
35
|
end
|
|
@@ -19,7 +19,7 @@ module Decidim
|
|
|
19
19
|
validate :max_depth
|
|
20
20
|
|
|
21
21
|
def max_length
|
|
22
|
-
if current_component
|
|
22
|
+
if current_component.try(:settings).respond_to?(:comments_max_length)
|
|
23
23
|
component_length = current_component.try { settings.comments_max_length.positive? }
|
|
24
24
|
return current_component.settings.comments_max_length if component_length
|
|
25
25
|
end
|
|
@@ -29,6 +29,7 @@ module Decidim
|
|
|
29
29
|
|
|
30
30
|
belongs_to :commentable, foreign_key: "decidim_commentable_id", foreign_type: "decidim_commentable_type", polymorphic: true
|
|
31
31
|
belongs_to :root_commentable, foreign_key: "decidim_root_commentable_id", foreign_type: "decidim_root_commentable_type", polymorphic: true, touch: true
|
|
32
|
+
belongs_to :participatory_space, foreign_key: "decidim_participatory_space_id", foreign_type: "decidim_participatory_space_type", polymorphic: true, optional: true
|
|
32
33
|
has_many :up_votes, -> { where(weight: 1) }, foreign_key: "decidim_comment_id", class_name: "CommentVote", dependent: :destroy
|
|
33
34
|
has_many :down_votes, -> { where(weight: -1) }, foreign_key: "decidim_comment_id", class_name: "CommentVote", dependent: :destroy
|
|
34
35
|
|
|
@@ -54,6 +55,8 @@ module Decidim
|
|
|
54
55
|
|
|
55
56
|
delegate :organization, to: :commentable
|
|
56
57
|
|
|
58
|
+
scope :not_deleted, -> { where(deleted_at: nil) }
|
|
59
|
+
|
|
57
60
|
translatable_fields :body
|
|
58
61
|
searchable_fields({
|
|
59
62
|
participatory_space: :itself,
|
|
@@ -79,7 +82,9 @@ module Decidim
|
|
|
79
82
|
participatory_space.try(:visible?) && component.try(:published?)
|
|
80
83
|
end
|
|
81
84
|
|
|
85
|
+
alias original_participatory_space participatory_space
|
|
82
86
|
def participatory_space
|
|
87
|
+
return original_participatory_space if original_participatory_space.present?
|
|
83
88
|
return root_commentable if root_commentable.is_a?(Decidim::Participable)
|
|
84
89
|
|
|
85
90
|
root_commentable.participatory_space
|
|
@@ -91,6 +96,8 @@ module Decidim
|
|
|
91
96
|
|
|
92
97
|
# Public: Override Commentable concern method `accepts_new_comments?`
|
|
93
98
|
def accepts_new_comments?
|
|
99
|
+
return if deleted?
|
|
100
|
+
|
|
94
101
|
root_commentable.accepts_new_comments? && depth < MAX_DEPTH
|
|
95
102
|
end
|
|
96
103
|
|
|
@@ -156,7 +163,7 @@ module Decidim
|
|
|
156
163
|
def self.user_commentators_ids_in(resources)
|
|
157
164
|
if resources.first&.kind_of?(Decidim::Comments::Commentable)
|
|
158
165
|
commentable_type = resources.first.class.name
|
|
159
|
-
Decidim::Comments::Comment.select("DISTINCT decidim_author_id").not_hidden
|
|
166
|
+
Decidim::Comments::Comment.select("DISTINCT decidim_author_id").not_hidden.not_deleted
|
|
160
167
|
.where(decidim_commentable_id: resources.pluck(:id))
|
|
161
168
|
.where(decidim_commentable_type: commentable_type)
|
|
162
169
|
.where("decidim_author_type" => "Decidim::UserBaseEntity").pluck(:decidim_author_id)
|
|
@@ -179,6 +186,22 @@ module Decidim
|
|
|
179
186
|
@translated_body ||= translated_attribute(body, organization)
|
|
180
187
|
end
|
|
181
188
|
|
|
189
|
+
def delete!
|
|
190
|
+
return if deleted?
|
|
191
|
+
|
|
192
|
+
update(deleted_at: Time.current)
|
|
193
|
+
|
|
194
|
+
update_counter
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
def deleted?
|
|
198
|
+
deleted_at.present?
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
def edited?
|
|
202
|
+
Decidim::ActionLog.where(resource: self).exists?(["extra @> ?", Arel.sql("{\"edit\":true}")])
|
|
203
|
+
end
|
|
204
|
+
|
|
182
205
|
private
|
|
183
206
|
|
|
184
207
|
def body_length
|
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
/* eslint id-length: ["error", { "exceptions": ["$"] }] */
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A plain Javascript component that handles the comments.
|
|
5
|
+
*
|
|
6
|
+
* @class
|
|
7
|
+
* @augments Component
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
// This is necessary for testing purposes
|
|
11
|
+
const $ = window.$;
|
|
12
|
+
|
|
13
|
+
import { createCharacterCounter } from "src/decidim/input_character_counter"
|
|
14
|
+
import ExternalLink from "src/decidim/external_link"
|
|
15
|
+
import updateExternalDomainLinks from "src/decidim/external_domain_warning"
|
|
16
|
+
|
|
17
|
+
export default class CommentsComponent {
|
|
18
|
+
constructor($element, config) {
|
|
19
|
+
this.$element = $element;
|
|
20
|
+
this.commentableGid = config.commentableGid;
|
|
21
|
+
this.commentsUrl = config.commentsUrl;
|
|
22
|
+
this.rootDepth = config.rootDepth;
|
|
23
|
+
this.order = config.order;
|
|
24
|
+
this.lastCommentId = config.lastCommentId;
|
|
25
|
+
this.pollingInterval = config.pollingInterval || 15000;
|
|
26
|
+
this.id = this.$element.attr("id") || this._getUID();
|
|
27
|
+
this.mounted = false;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Handles the logic for mounting the component
|
|
32
|
+
* @public
|
|
33
|
+
* @returns {Void} - Returns nothing
|
|
34
|
+
*/
|
|
35
|
+
mountComponent() {
|
|
36
|
+
if (this.$element.length > 0 && !this.mounted) {
|
|
37
|
+
this.mounted = true;
|
|
38
|
+
this._initializeComments(this.$element);
|
|
39
|
+
|
|
40
|
+
$(".order-by__dropdown .is-submenu-item a", this.$element).on("click.decidim-comments", () => this._onInitOrder());
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Handles the logic for unmounting the component
|
|
46
|
+
* @public
|
|
47
|
+
* @returns {Void} - Returns nothing
|
|
48
|
+
*/
|
|
49
|
+
unmountComponent() {
|
|
50
|
+
if (this.mounted) {
|
|
51
|
+
this.mounted = false;
|
|
52
|
+
this._stopPolling();
|
|
53
|
+
|
|
54
|
+
$(".add-comment .opinion-toggle .button", this.$element).off("click.decidim-comments");
|
|
55
|
+
$(".add-comment textarea", this.$element).off("input.decidim-comments");
|
|
56
|
+
$(".order-by__dropdown .is-submenu-item a", this.$element).off("click.decidim-comments");
|
|
57
|
+
$(".add-comment form", this.$element).off("submit.decidim-comments");
|
|
58
|
+
$(".add-comment textarea", this.$element).each((_i, el) => el.removeEventListener("emoji.added", this._onTextInput));
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Adds a new thread to the comments section.
|
|
64
|
+
* @public
|
|
65
|
+
* @param {String} threadHtml - The HTML content for the thread.
|
|
66
|
+
* @param {Boolean} fromCurrentUser - A boolean indicating whether the user
|
|
67
|
+
* herself was the author of the new thread. Defaults to false.
|
|
68
|
+
* @returns {Void} - Returns nothing
|
|
69
|
+
*/
|
|
70
|
+
addThread(threadHtml, fromCurrentUser = false) {
|
|
71
|
+
const $parent = $(".comments:first", this.$element);
|
|
72
|
+
const $comment = $(threadHtml);
|
|
73
|
+
const $threads = $(".comment-threads", this.$element);
|
|
74
|
+
this._addComment($threads, $comment);
|
|
75
|
+
this._finalizeCommentCreation($parent, fromCurrentUser);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Adds a new reply to an existing comment.
|
|
80
|
+
* @public
|
|
81
|
+
* @param {Number} commentId - The ID of the comment for which to add the
|
|
82
|
+
* reply to.
|
|
83
|
+
* @param {String} replyHtml - The HTML content for the reply.
|
|
84
|
+
* @param {Boolean} fromCurrentUser - A boolean indicating whether the user
|
|
85
|
+
* herself was the author of the new reply. Defaults to false.
|
|
86
|
+
* @returns {Void} - Returns nothing
|
|
87
|
+
*/
|
|
88
|
+
addReply(commentId, replyHtml, fromCurrentUser = false) {
|
|
89
|
+
const $parent = $(`#comment_${commentId}`);
|
|
90
|
+
const $comment = $(replyHtml);
|
|
91
|
+
const $replies = $(`#comment-${commentId}-replies`);
|
|
92
|
+
this._addComment($replies, $comment);
|
|
93
|
+
$replies.siblings(".comment__additionalreply").removeClass("hide");
|
|
94
|
+
this._finalizeCommentCreation($parent, fromCurrentUser);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Generates a unique identifier for the form.
|
|
99
|
+
* @private
|
|
100
|
+
* @returns {String} - Returns a unique identifier
|
|
101
|
+
*/
|
|
102
|
+
_getUID() {
|
|
103
|
+
return `comments-${new Date().setUTCMilliseconds()}-${Math.floor(Math.random() * 10000000)}`;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Initializes the comments for the given parent element.
|
|
108
|
+
* @private
|
|
109
|
+
* @param {jQuery} $parent The parent element to initialize.
|
|
110
|
+
* @returns {Void} - Returns nothing
|
|
111
|
+
*/
|
|
112
|
+
_initializeComments($parent) {
|
|
113
|
+
$(".add-comment", $parent).each((_i, el) => {
|
|
114
|
+
const $add = $(el);
|
|
115
|
+
const $form = $("form", $add);
|
|
116
|
+
const $opinionButtons = $(".opinion-toggle .button", $add);
|
|
117
|
+
const $text = $("textarea", $form);
|
|
118
|
+
|
|
119
|
+
$opinionButtons.on("click.decidim-comments", this._onToggleOpinion);
|
|
120
|
+
$text.on("input.decidim-comments", this._onTextInput);
|
|
121
|
+
|
|
122
|
+
$(document).trigger("attach-mentions-element", [$text.get(0)]);
|
|
123
|
+
|
|
124
|
+
$form.on("submit.decidim-comments", () => {
|
|
125
|
+
const $submit = $("button[type='submit']", $form);
|
|
126
|
+
|
|
127
|
+
$submit.attr("disabled", "disabled");
|
|
128
|
+
this._stopPolling();
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
if ($text.length && $text.get(0) !== null) {
|
|
132
|
+
// Attach event to the DOM node, instead of the jQuery object
|
|
133
|
+
$text.get(0).addEventListener("emoji.added", this._onTextInput);
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
this._pollComments();
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Adds the given comment element to the given target element and
|
|
142
|
+
* initializes it.
|
|
143
|
+
* @private
|
|
144
|
+
* @param {jQuery} $target - The target element to add the comment to.
|
|
145
|
+
* @param {jQuery} $container - The comment container element to add.
|
|
146
|
+
* @returns {Void} - Returns nothing
|
|
147
|
+
*/
|
|
148
|
+
_addComment($target, $container) {
|
|
149
|
+
let $comment = $(".comment", $container);
|
|
150
|
+
if ($comment.length < 1) {
|
|
151
|
+
// In case of a reply
|
|
152
|
+
$comment = $container;
|
|
153
|
+
}
|
|
154
|
+
this.lastCommentId = parseInt($comment.data("comment-id"), 10);
|
|
155
|
+
|
|
156
|
+
$target.append($container);
|
|
157
|
+
$container.foundation();
|
|
158
|
+
this._initializeComments($container);
|
|
159
|
+
createCharacterCounter($(".add-comment textarea", $container));
|
|
160
|
+
$container.find('a[target="_blank"]').each((_i, elem) => {
|
|
161
|
+
const $link = $(elem);
|
|
162
|
+
$link.data("external-link", new ExternalLink($link));
|
|
163
|
+
});
|
|
164
|
+
updateExternalDomainLinks($container)
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Finalizes the new comment creation after the comment adding finishes
|
|
169
|
+
* successfully.
|
|
170
|
+
* @private
|
|
171
|
+
* @param {jQuery} $parent - The parent comment element to finalize.
|
|
172
|
+
* @param {Boolean} fromCurrentUser - A boolean indicating whether the user
|
|
173
|
+
* herself was the author of the new comment.
|
|
174
|
+
* @returns {Void} - Returns nothing
|
|
175
|
+
*/
|
|
176
|
+
_finalizeCommentCreation($parent, fromCurrentUser) {
|
|
177
|
+
if (fromCurrentUser) {
|
|
178
|
+
const $add = $("> .add-comment", $parent);
|
|
179
|
+
const $text = $("textarea", $add);
|
|
180
|
+
const characterCounter = $text.data("remaining-characters-counter");
|
|
181
|
+
$text.val("");
|
|
182
|
+
if (characterCounter) {
|
|
183
|
+
characterCounter.updateStatus();
|
|
184
|
+
}
|
|
185
|
+
if (!$add.parent().is(".comments")) {
|
|
186
|
+
$add.addClass("hide");
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Restart the polling
|
|
191
|
+
this._pollComments();
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Sets a timeout to poll new comments.
|
|
196
|
+
* @private
|
|
197
|
+
* @returns {Void} - Returns nothing
|
|
198
|
+
*/
|
|
199
|
+
_pollComments() {
|
|
200
|
+
this._stopPolling();
|
|
201
|
+
|
|
202
|
+
this.pollTimeout = setTimeout(() => {
|
|
203
|
+
$.ajax({
|
|
204
|
+
url: this.commentsUrl,
|
|
205
|
+
method: "GET",
|
|
206
|
+
contentType: "application/javascript",
|
|
207
|
+
data: {
|
|
208
|
+
"commentable_gid": this.commentableGid,
|
|
209
|
+
"root_depth": this.rootDepth,
|
|
210
|
+
order: this.order,
|
|
211
|
+
after: this.lastCommentId
|
|
212
|
+
}
|
|
213
|
+
}).done(() => this._pollComments());
|
|
214
|
+
}, this.pollingInterval);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Stops polling for new comments.
|
|
219
|
+
* @private
|
|
220
|
+
* @returns {Void} - Returns nothing
|
|
221
|
+
*/
|
|
222
|
+
_stopPolling() {
|
|
223
|
+
if (this.pollTimeout) {
|
|
224
|
+
clearTimeout(this.pollTimeout);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Sets the loading comments element visible in the view.
|
|
230
|
+
* @private
|
|
231
|
+
* @returns {Void} - Returns nothing
|
|
232
|
+
*/
|
|
233
|
+
_setLoading() {
|
|
234
|
+
const $container = $("> .comments-container", this.$element);
|
|
235
|
+
$("> .comments", $container).addClass("hide");
|
|
236
|
+
$("> .loading-comments", $container).removeClass("hide");
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Event listener for the ordering links.
|
|
241
|
+
* @private
|
|
242
|
+
* @returns {Void} - Returns nothing
|
|
243
|
+
*/
|
|
244
|
+
_onInitOrder() {
|
|
245
|
+
this._stopPolling();
|
|
246
|
+
this._setLoading();
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Event listener for the opinion toggle buttons.
|
|
251
|
+
* @private
|
|
252
|
+
* @param {Event} ev - The event object.
|
|
253
|
+
* @returns {Void} - Returns nothing
|
|
254
|
+
*/
|
|
255
|
+
_onToggleOpinion(ev) {
|
|
256
|
+
let $btn = $(ev.target);
|
|
257
|
+
if (!$btn.is(".button")) {
|
|
258
|
+
$btn = $btn.parents(".button");
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
const $add = $btn.closest(".add-comment");
|
|
262
|
+
const $form = $("form", $add);
|
|
263
|
+
const $opinionButtons = $(".opinion-toggle .button", $add);
|
|
264
|
+
const $selectedState = $(".opinion-toggle .selected-state", $add);
|
|
265
|
+
const $alignment = $(".alignment-input", $form);
|
|
266
|
+
|
|
267
|
+
$opinionButtons.removeClass("is-active").attr("aria-pressed", "false");
|
|
268
|
+
$btn.addClass("is-active").attr("aria-pressed", "true");
|
|
269
|
+
|
|
270
|
+
if ($btn.is(".opinion-toggle--ok")) {
|
|
271
|
+
$alignment.val(1);
|
|
272
|
+
} else if ($btn.is(".opinion-toggle--meh")) {
|
|
273
|
+
$alignment.val(0);
|
|
274
|
+
} else if ($btn.is(".opinion-toggle--ko")) {
|
|
275
|
+
$alignment.val(-1);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// Announce the selected state for the screen reader
|
|
279
|
+
$selectedState.text($btn.data("selected-label"));
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Event listener for the comment field text input.
|
|
284
|
+
* @private
|
|
285
|
+
* @param {Event} ev - The event object.
|
|
286
|
+
* @returns {Void} - Returns nothing
|
|
287
|
+
*/
|
|
288
|
+
_onTextInput(ev) {
|
|
289
|
+
const $text = $(ev.target);
|
|
290
|
+
const $add = $text.closest(".add-comment");
|
|
291
|
+
const $form = $("form", $add);
|
|
292
|
+
const $submit = $("button[type='submit']", $form);
|
|
293
|
+
|
|
294
|
+
if ($text.val().length > 0) {
|
|
295
|
+
$submit.removeAttr("disabled");
|
|
296
|
+
} else {
|
|
297
|
+
$submit.attr("disabled", "disabled");
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}
|