decidim-comments 0.24.0 → 0.25.0.rc1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +3 -20
- 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/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 +23 -1
- data/app/packs/src/decidim/comments/comments.component.js +299 -0
- data/app/{assets/javascripts → packs/src}/decidim/comments/comments.component.test.js +49 -24
- 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/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 +2 -0
- 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 +30 -6
- 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 +29 -1
- data/config/locales/sk.yml +0 -1
- data/config/locales/sr-CS.yml +0 -1
- data/config/locales/sv.yml +35 -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/20210529095942_add_deleted_at_column_to_comments.rb +7 -0
- data/lib/decidim/comments.rb +1 -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/version.rb +1 -1
- metadata +31 -31
- data/app/assets/config/decidim_comments_manifest.js +0 -1
- data/app/assets/javascripts/decidim/comments/bundle.js.map +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
|
@@ -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
|
@@ -54,6 +54,8 @@ module Decidim
|
|
54
54
|
|
55
55
|
delegate :organization, to: :commentable
|
56
56
|
|
57
|
+
scope :not_deleted, -> { where(deleted_at: nil) }
|
58
|
+
|
57
59
|
translatable_fields :body
|
58
60
|
searchable_fields({
|
59
61
|
participatory_space: :itself,
|
@@ -91,6 +93,8 @@ module Decidim
|
|
91
93
|
|
92
94
|
# Public: Override Commentable concern method `accepts_new_comments?`
|
93
95
|
def accepts_new_comments?
|
96
|
+
return if deleted?
|
97
|
+
|
94
98
|
root_commentable.accepts_new_comments? && depth < MAX_DEPTH
|
95
99
|
end
|
96
100
|
|
@@ -156,10 +160,12 @@ module Decidim
|
|
156
160
|
def self.user_commentators_ids_in(resources)
|
157
161
|
if resources.first&.kind_of?(Decidim::Comments::Commentable)
|
158
162
|
commentable_type = resources.first.class.name
|
159
|
-
Decidim::Comments::Comment.select("DISTINCT decidim_author_id").not_hidden
|
163
|
+
Decidim::Comments::Comment.select("DISTINCT decidim_author_id").not_hidden.not_deleted
|
160
164
|
.where(decidim_commentable_id: resources.pluck(:id))
|
161
165
|
.where(decidim_commentable_type: commentable_type)
|
162
166
|
.where("decidim_author_type" => "Decidim::UserBaseEntity").pluck(:decidim_author_id)
|
167
|
+
else
|
168
|
+
[]
|
163
169
|
end
|
164
170
|
end
|
165
171
|
|
@@ -177,6 +183,22 @@ module Decidim
|
|
177
183
|
@translated_body ||= translated_attribute(body, organization)
|
178
184
|
end
|
179
185
|
|
186
|
+
def delete!
|
187
|
+
return if deleted?
|
188
|
+
|
189
|
+
update(deleted_at: Time.current)
|
190
|
+
|
191
|
+
update_counter
|
192
|
+
end
|
193
|
+
|
194
|
+
def deleted?
|
195
|
+
deleted_at.present?
|
196
|
+
end
|
197
|
+
|
198
|
+
def edited?
|
199
|
+
Decidim::ActionLog.where(resource: self).exists?(["extra @> ?", Arel.sql("{\"edit\":true}")])
|
200
|
+
end
|
201
|
+
|
180
202
|
private
|
181
203
|
|
182
204
|
def body_length
|
@@ -0,0 +1,299 @@
|
|
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(
|
41
|
+
"click.decidim-comments",
|
42
|
+
() => {
|
43
|
+
this._onInitOrder();
|
44
|
+
}
|
45
|
+
);
|
46
|
+
}
|
47
|
+
}
|
48
|
+
|
49
|
+
/**
|
50
|
+
* Handles the logic for unmounting the component
|
51
|
+
* @public
|
52
|
+
* @returns {Void} - Returns nothing
|
53
|
+
*/
|
54
|
+
unmountComponent() {
|
55
|
+
if (this.mounted) {
|
56
|
+
this.mounted = false;
|
57
|
+
this._stopPolling();
|
58
|
+
|
59
|
+
$(".add-comment .opinion-toggle .button", this.$element).off("click.decidim-comments");
|
60
|
+
$(".add-comment textarea", this.$element).off("input.decidim-comments");
|
61
|
+
$(".order-by__dropdown .is-submenu-item a", this.$element).off("click.decidim-comments");
|
62
|
+
$(".add-comment form", this.$element).off("submit.decidim-comments");
|
63
|
+
$(".add-comment textarea", this.$element).each((_i, el) => el.removeEventListener("emoji.added", this._onTextInput));
|
64
|
+
}
|
65
|
+
}
|
66
|
+
|
67
|
+
/**
|
68
|
+
* Adds a new thread to the comments section.
|
69
|
+
* @public
|
70
|
+
* @param {String} threadHtml - The HTML content for the thread.
|
71
|
+
* @returns {Void} - Returns nothing
|
72
|
+
*/
|
73
|
+
addThread(threadHtml) {
|
74
|
+
const $parent = $(".comments:first", this.$element);
|
75
|
+
const $comment = $(threadHtml);
|
76
|
+
const $threads = $(".comment-threads", this.$element);
|
77
|
+
this._addComment($threads, $comment);
|
78
|
+
this._finalizeCommentCreation($parent);
|
79
|
+
}
|
80
|
+
|
81
|
+
/**
|
82
|
+
* Adds a new reply to an existing comment.
|
83
|
+
* @public
|
84
|
+
* @param {Number} commentId - The ID of the comment for which to add the
|
85
|
+
* reply to.
|
86
|
+
* @param {String} replyHtml - The HTML content for the reply.
|
87
|
+
* @returns {Void} - Returns nothing
|
88
|
+
*/
|
89
|
+
addReply(commentId, replyHtml) {
|
90
|
+
const $parent = $(`#comment_${commentId}`);
|
91
|
+
const $comment = $(replyHtml);
|
92
|
+
const $replies = $(`#comment-${commentId}-replies`);
|
93
|
+
this._addComment($replies, $comment);
|
94
|
+
$replies.siblings(".comment__additionalreply").removeClass("hide");
|
95
|
+
this._finalizeCommentCreation($parent);
|
96
|
+
}
|
97
|
+
|
98
|
+
/**
|
99
|
+
* Generates a unique identifier for the form.
|
100
|
+
* @private
|
101
|
+
* @returns {String} - Returns a unique identifier
|
102
|
+
*/
|
103
|
+
_getUID() {
|
104
|
+
return `comments-${new Date().setUTCMilliseconds()}-${Math.floor(Math.random() * 10000000)}`;
|
105
|
+
}
|
106
|
+
|
107
|
+
/**
|
108
|
+
* Initializes the comments for the given parent element.
|
109
|
+
* @private
|
110
|
+
* @param {jQuery} $parent The parent element to initialize.
|
111
|
+
* @returns {Void} - Returns nothing
|
112
|
+
*/
|
113
|
+
_initializeComments($parent) {
|
114
|
+
$(".add-comment", $parent).each((_i, el) => {
|
115
|
+
const $add = $(el);
|
116
|
+
const $form = $("form", $add);
|
117
|
+
const $opinionButtons = $(".opinion-toggle .button", $add);
|
118
|
+
const $text = $("textarea", $form);
|
119
|
+
|
120
|
+
$opinionButtons.on("click.decidim-comments", this._onToggleOpinion);
|
121
|
+
$text.on("input.decidim-comments", this._onTextInput);
|
122
|
+
|
123
|
+
$(document).trigger("attach-mentions-element", [$text.get(0)]);
|
124
|
+
|
125
|
+
$form.on("submit.decidim-comments", () => {
|
126
|
+
const $submit = $("button[type='submit']", $form);
|
127
|
+
|
128
|
+
$submit.attr("disabled", "disabled");
|
129
|
+
this._stopPolling();
|
130
|
+
});
|
131
|
+
|
132
|
+
if ($text.length && $text.get(0) !== null) {
|
133
|
+
// Attach event to the DOM node, instead of the jQuery object
|
134
|
+
$text.get(0).addEventListener("emoji.added", this._onTextInput);
|
135
|
+
}
|
136
|
+
});
|
137
|
+
|
138
|
+
this._pollComments();
|
139
|
+
}
|
140
|
+
|
141
|
+
/**
|
142
|
+
* Adds the given comment element to the given target element and
|
143
|
+
* initializes it.
|
144
|
+
* @private
|
145
|
+
* @param {jQuery} $target - The target element to add the comment to.
|
146
|
+
* @param {jQuery} $container - The comment container element to add.
|
147
|
+
* @returns {Void} - Returns nothing
|
148
|
+
*/
|
149
|
+
_addComment($target, $container) {
|
150
|
+
let $comment = $(".comment", $container);
|
151
|
+
if ($comment.length < 1) {
|
152
|
+
// In case of a reply
|
153
|
+
$comment = $container;
|
154
|
+
}
|
155
|
+
this.lastCommentId = parseInt($comment.data("comment-id"), 10);
|
156
|
+
|
157
|
+
$target.append($container);
|
158
|
+
$container.foundation();
|
159
|
+
this._initializeComments($container);
|
160
|
+
createCharacterCounter($(".add-comment textarea", $container));
|
161
|
+
$container.find('a[target="_blank"]').each((_i, elem) => {
|
162
|
+
const $link = $(elem);
|
163
|
+
$link.data("external-link", new ExternalLink($link));
|
164
|
+
});
|
165
|
+
updateExternalDomainLinks($container)
|
166
|
+
}
|
167
|
+
|
168
|
+
/**
|
169
|
+
* Finalizes the new comment creation after the comment adding finishes
|
170
|
+
* successfully.
|
171
|
+
* @private
|
172
|
+
* @param {jQuery} $parent - The parent comment element to finalize.
|
173
|
+
* @returns {Void} - Returns nothing
|
174
|
+
*/
|
175
|
+
_finalizeCommentCreation($parent) {
|
176
|
+
const $add = $("> .add-comment", $parent);
|
177
|
+
const $text = $("textarea", $add);
|
178
|
+
const characterCounter = $text.data("remaining-characters-counter");
|
179
|
+
$text.val("");
|
180
|
+
if (characterCounter) {
|
181
|
+
characterCounter.updateStatus();
|
182
|
+
}
|
183
|
+
if (!$add.parent().is(".comments")) {
|
184
|
+
$add.addClass("hide");
|
185
|
+
}
|
186
|
+
|
187
|
+
// Restart the polling
|
188
|
+
this._pollComments();
|
189
|
+
}
|
190
|
+
|
191
|
+
/**
|
192
|
+
* Sets a timeout to poll new comments.
|
193
|
+
* @private
|
194
|
+
* @returns {Void} - Returns nothing
|
195
|
+
*/
|
196
|
+
_pollComments() {
|
197
|
+
this._stopPolling();
|
198
|
+
|
199
|
+
this.pollTimeout = setTimeout(() => {
|
200
|
+
$.ajax({
|
201
|
+
url: this.commentsUrl,
|
202
|
+
method: "GET",
|
203
|
+
contentType: "application/javascript",
|
204
|
+
data: {
|
205
|
+
"commentable_gid": this.commentableGid,
|
206
|
+
"root_depth": this.rootDepth,
|
207
|
+
order: this.order,
|
208
|
+
after: this.lastCommentId
|
209
|
+
}
|
210
|
+
}).done(() => {
|
211
|
+
this._pollComments();
|
212
|
+
});
|
213
|
+
}, this.pollingInterval);
|
214
|
+
}
|
215
|
+
|
216
|
+
/**
|
217
|
+
* Stops polling for new comments.
|
218
|
+
* @private
|
219
|
+
* @returns {Void} - Returns nothing
|
220
|
+
*/
|
221
|
+
_stopPolling() {
|
222
|
+
if (this.pollTimeout) {
|
223
|
+
clearTimeout(this.pollTimeout);
|
224
|
+
}
|
225
|
+
}
|
226
|
+
|
227
|
+
/**
|
228
|
+
* Sets the loading comments element visible in the view.
|
229
|
+
* @private
|
230
|
+
* @returns {Void} - Returns nothing
|
231
|
+
*/
|
232
|
+
_setLoading() {
|
233
|
+
const $container = $("> .comments-container", this.$element);
|
234
|
+
$("> .comments", $container).addClass("hide");
|
235
|
+
$("> .loading-comments", $container).removeClass("hide");
|
236
|
+
}
|
237
|
+
|
238
|
+
/**
|
239
|
+
* Event listener for the ordering links.
|
240
|
+
* @private
|
241
|
+
* @returns {Void} - Returns nothing
|
242
|
+
*/
|
243
|
+
_onInitOrder() {
|
244
|
+
this._stopPolling();
|
245
|
+
this._setLoading();
|
246
|
+
}
|
247
|
+
|
248
|
+
/**
|
249
|
+
* Event listener for the opinion toggle buttons.
|
250
|
+
* @private
|
251
|
+
* @param {Event} ev - The event object.
|
252
|
+
* @returns {Void} - Returns nothing
|
253
|
+
*/
|
254
|
+
_onToggleOpinion(ev) {
|
255
|
+
let $btn = $(ev.target);
|
256
|
+
if (!$btn.is(".button")) {
|
257
|
+
$btn = $btn.parents(".button");
|
258
|
+
}
|
259
|
+
|
260
|
+
const $add = $btn.closest(".add-comment");
|
261
|
+
const $form = $("form", $add);
|
262
|
+
const $opinionButtons = $(".opinion-toggle .button", $add);
|
263
|
+
const $selectedState = $(".opinion-toggle .selected-state", $add);
|
264
|
+
const $alignment = $(".alignment-input", $form);
|
265
|
+
|
266
|
+
$opinionButtons.removeClass("is-active").attr("aria-pressed", "false");
|
267
|
+
$btn.addClass("is-active").attr("aria-pressed", "true");
|
268
|
+
|
269
|
+
if ($btn.is(".opinion-toggle--ok")) {
|
270
|
+
$alignment.val(1);
|
271
|
+
} else if ($btn.is(".opinion-toggle--meh")) {
|
272
|
+
$alignment.val(0);
|
273
|
+
} else if ($btn.is(".opinion-toggle--ko")) {
|
274
|
+
$alignment.val(-1);
|
275
|
+
}
|
276
|
+
|
277
|
+
// Announce the selected state for the screen reader
|
278
|
+
$selectedState.text($btn.data("selected-label"));
|
279
|
+
}
|
280
|
+
|
281
|
+
/**
|
282
|
+
* Event listener for the comment field text input.
|
283
|
+
* @private
|
284
|
+
* @param {Event} ev - The event object.
|
285
|
+
* @returns {Void} - Returns nothing
|
286
|
+
*/
|
287
|
+
_onTextInput(ev) {
|
288
|
+
const $text = $(ev.target);
|
289
|
+
const $add = $text.closest(".add-comment");
|
290
|
+
const $form = $("form", $add);
|
291
|
+
const $submit = $("button[type='submit']", $form);
|
292
|
+
|
293
|
+
if ($text.val().length > 0) {
|
294
|
+
$submit.removeAttr("disabled");
|
295
|
+
} else {
|
296
|
+
$submit.attr("disabled", "disabled");
|
297
|
+
}
|
298
|
+
}
|
299
|
+
}
|