biovision-comment 0.1.170914 → 0.7.190905.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/README.md +68 -14
- data/app/assets/javascripts/biovision/comment/biovision-comments.js +118 -0
- data/app/assets/stylesheets/biovision/comment/comments.scss +111 -38
- data/app/controllers/admin/comments_controller.rb +18 -5
- data/app/controllers/comments_controller.rb +45 -32
- data/app/helpers/comments_helper.rb +20 -0
- data/app/models/comment.rb +88 -19
- data/app/models/concerns/commentable_item.rb +15 -0
- data/app/services/biovision/components/comments_component.rb +39 -0
- data/app/services/comment_handler.rb +49 -0
- data/app/services/comments_manager.rb +24 -0
- data/app/views/admin/comments/entity/_in_list.html.erb +8 -5
- data/app/views/admin/comments/index.html.erb +2 -1
- data/app/views/admin/comments/show.html.erb +25 -10
- data/app/views/admin/components/links/_comments.html.erb +3 -0
- data/app/views/comment_mailer/entry_reply.text.erb +2 -6
- data/app/views/comments/_comment.html.erb +66 -16
- data/app/views/comments/_form.html.erb +92 -19
- data/app/views/comments/_list.html.erb +17 -19
- data/app/views/comments/_reply_container.html.erb +15 -0
- data/app/views/comments/_section.html.erb +34 -0
- data/app/views/comments/edit.html.erb +6 -5
- data/app/views/comments/new.html.erb +2 -2
- data/config/locales/comments-ru.yml +32 -2
- data/config/routes.rb +18 -10
- data/db/migrate/20170914000001_create_comments.rb +48 -23
- data/db/migrate/20190203090909_add_fields_to_comments.rb +14 -0
- data/db/migrate/20190428212121_add_approved_to_comments.rb +14 -0
- data/db/migrate/20190428212122_create_comments_component.rb +21 -0
- data/db/migrate/20190628101010_add_data_to_comments.rb +15 -0
- data/lib/biovision/comment/engine.rb +7 -2
- data/lib/biovision/comment/version.rb +3 -1
- metadata +19 -8
- data/app/views/comments/show.html.erb +0 -27
@@ -1,25 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Comments
|
1
4
|
class CommentsController < ApplicationController
|
2
|
-
before_action :
|
3
|
-
before_action :restrict_access, except: [
|
4
|
-
before_action :set_entity, only: [
|
5
|
+
before_action :set_handler
|
6
|
+
before_action :restrict_access, except: %i[check create]
|
7
|
+
before_action :set_entity, only: %i[edit update destroy]
|
8
|
+
|
9
|
+
layout 'admin', except: :create
|
5
10
|
|
6
|
-
|
11
|
+
# post /comments/check
|
12
|
+
def check
|
13
|
+
@entity = Comment.instance_for_check(params[:entity_id], entity_parameters)
|
14
|
+
|
15
|
+
render 'shared/forms/check'
|
16
|
+
end
|
7
17
|
|
8
18
|
# post /comments
|
9
19
|
def create
|
10
|
-
|
11
|
-
|
12
|
-
notify_participants
|
13
|
-
redirect_to(@entity.commentable || admin_comment_path(@entity.id), notice: t('comments.create.success'))
|
20
|
+
if params.key?(:agree)
|
21
|
+
emulate_creation
|
14
22
|
else
|
15
|
-
|
23
|
+
create_comment
|
16
24
|
end
|
17
25
|
end
|
18
26
|
|
19
|
-
# get /comments/:id
|
20
|
-
def show
|
21
|
-
end
|
22
|
-
|
23
27
|
# get /comments/:id/edit
|
24
28
|
def edit
|
25
29
|
end
|
@@ -27,9 +31,9 @@ class CommentsController < ApplicationController
|
|
27
31
|
# patch /comments/:id
|
28
32
|
def update
|
29
33
|
if @entity.update entity_parameters
|
30
|
-
|
34
|
+
form_processed_ok(admin_comment_path(id: @entity.id))
|
31
35
|
else
|
32
|
-
|
36
|
+
form_processed_with_error(:edit)
|
33
37
|
end
|
34
38
|
end
|
35
39
|
|
@@ -43,36 +47,45 @@ class CommentsController < ApplicationController
|
|
43
47
|
|
44
48
|
private
|
45
49
|
|
50
|
+
def component_slug
|
51
|
+
Biovision::Components::CommentsComponent::SLUG
|
52
|
+
end
|
53
|
+
|
46
54
|
def restrict_access
|
47
|
-
|
55
|
+
error = 'Managing comments is not allowed'
|
56
|
+
handle_http_401(error) unless component_handler.allow?('moderator')
|
57
|
+
end
|
58
|
+
|
59
|
+
def emulate_creation
|
60
|
+
form_processed_ok(root_path)
|
61
|
+
end
|
62
|
+
|
63
|
+
def create_comment
|
64
|
+
@entity = component_handler.create_comment(creation_parameters)
|
65
|
+
if @entity.valid?
|
66
|
+
notify_participants
|
67
|
+
next_page = param_from_request(:return_url)
|
68
|
+
form_processed_ok(next_page.match?(%r{\A/[^/]}) ? next_page : root_path)
|
69
|
+
else
|
70
|
+
form_processed_with_error(:new)
|
71
|
+
end
|
48
72
|
end
|
49
73
|
|
50
74
|
def set_entity
|
51
75
|
@entity = Comment.find_by(id: params[:id])
|
52
|
-
if @entity.nil?
|
53
|
-
handle_http_404('Cannot find comment')
|
54
|
-
end
|
76
|
+
handle_http_404('Cannot find comment') if @entity.nil?
|
55
77
|
end
|
56
78
|
|
57
79
|
def entity_parameters
|
58
|
-
|
59
|
-
params.require(:comment).permit(permitted)
|
80
|
+
params.require(:comment).permit(Comment.entity_parameters)
|
60
81
|
end
|
61
82
|
|
62
83
|
def creation_parameters
|
63
|
-
|
84
|
+
permitted = Comment.creation_parameters
|
85
|
+
params.require(:comment).permit(permitted).merge(owner_for_entity(true))
|
64
86
|
end
|
65
87
|
|
66
88
|
def notify_participants
|
67
|
-
|
68
|
-
unless commentable.owned_by?(current_user)
|
69
|
-
category = Notification.category_from_object(commentable)
|
70
|
-
Notification.notify(commentable.user, category, commentable.id)
|
71
|
-
# begin
|
72
|
-
# Comments.entry_reply(@entity).deliver_now if @entity.notify_entry_owner?
|
73
|
-
# rescue Net::SMTPAuthenticationError => error
|
74
|
-
# logger.warn error.message
|
75
|
-
# end
|
76
|
-
end
|
89
|
+
# to be implemented...
|
77
90
|
end
|
78
91
|
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Helper methods for handling comments
|
4
|
+
module CommentsHelper
|
5
|
+
# @param [Comment] entity
|
6
|
+
# @param [String] text
|
7
|
+
# @param [Hash] options
|
8
|
+
def admin_comment_link(entity, text = entity.text_for_link, options = {})
|
9
|
+
link_to(text, admin_comment_path(id: entity.id), options)
|
10
|
+
end
|
11
|
+
|
12
|
+
# @param [Comment] entity
|
13
|
+
# @param [String] text
|
14
|
+
# @param [Hash] options
|
15
|
+
def comment_link(entity, text = entity.commentable_name, options = {})
|
16
|
+
anchor = options.key?(:anchor)
|
17
|
+
options.delete(:anchor)
|
18
|
+
link_to(text, CommentsManager.commentable_path(entity, anchor), options)
|
19
|
+
end
|
20
|
+
end
|
data/app/models/comment.rb
CHANGED
@@ -1,53 +1,103 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Comment
|
4
|
+
#
|
5
|
+
# Attributes:
|
6
|
+
# agent_id [Agent], optional
|
7
|
+
# approved [Boolean]
|
8
|
+
# author_email [String], optional
|
9
|
+
# author_name [String], optional
|
10
|
+
# body [Text]
|
11
|
+
# commentable_id [Integer]
|
12
|
+
# commentable_type [String]
|
13
|
+
# created_at [DateTime]
|
14
|
+
# deleted [Boolean]
|
15
|
+
# downvote_count [Integer]
|
16
|
+
# ip [Inet], optional
|
17
|
+
# locked [Boolean]
|
18
|
+
# parent_id [Comment], optional
|
19
|
+
# spam [Boolean]
|
20
|
+
# updated_at [DateTime]
|
21
|
+
# upvote_count [Integer]
|
22
|
+
# visible [Boolean]
|
23
|
+
# vote_result [Integer]
|
1
24
|
class Comment < ApplicationRecord
|
25
|
+
include Checkable
|
2
26
|
include HasOwner
|
3
27
|
include Toggleable
|
4
|
-
include VotableItem
|
28
|
+
include VotableItem if Gem.loaded_specs.key?('biovision-vote')
|
5
29
|
|
6
|
-
|
30
|
+
AUTHOR_LIMIT = 100
|
31
|
+
BODY_LIMIT = 1_048_576
|
7
32
|
|
8
33
|
toggleable :visible
|
9
34
|
|
10
|
-
belongs_to :user, optional: true
|
35
|
+
belongs_to :user, optional: true
|
11
36
|
belongs_to :agent, optional: true
|
12
37
|
belongs_to :commentable, polymorphic: true, counter_cache: true, touch: false
|
38
|
+
belongs_to :parent, class_name: Comment.to_s, optional: true
|
39
|
+
has_many :child_comments, class_name: Comment.to_s, foreign_key: :parent_id, dependent: :destroy
|
13
40
|
|
14
41
|
validates_presence_of :body
|
42
|
+
validates_length_of :body, maximum: BODY_LIMIT
|
43
|
+
validates_length_of :author_name, maximum: AUTHOR_LIMIT
|
44
|
+
validates_length_of :author_email, maximum: AUTHOR_LIMIT
|
15
45
|
validate :commentable_is_commentable
|
16
46
|
|
47
|
+
after_create { commentable.comment_impact(self) if commentable.respond_to?(:comment_impact) }
|
48
|
+
|
17
49
|
scope :recent, -> { order 'id desc' }
|
18
|
-
scope :
|
50
|
+
scope :chronological, -> { order 'id asc' }
|
51
|
+
scope :visible, -> { where(deleted: false, visible: true, spam: false) } #, approved: true) }
|
52
|
+
scope :list_for_administration, -> { recent }
|
53
|
+
scope :list_for_visitors, -> { visible.chronological }
|
54
|
+
scope :list_for_visitors_recent, -> { visible.recent }
|
55
|
+
scope :list_for_owner, ->(v) { owned_by(v).where(deleted: false).recent }
|
19
56
|
|
20
57
|
# @param [Integer] page
|
21
|
-
def self.page_for_administration(page)
|
22
|
-
|
58
|
+
def self.page_for_administration(page = 1)
|
59
|
+
list_for_administration.page(page)
|
23
60
|
end
|
24
61
|
|
25
62
|
# @param [Integer] page
|
26
|
-
def self.page_for_visitor(page)
|
27
|
-
|
63
|
+
def self.page_for_visitor(page = 1)
|
64
|
+
list_for_visitors.page(page)
|
28
65
|
end
|
29
66
|
|
30
67
|
# @param [User] user
|
31
68
|
# @param [Integer] page
|
32
|
-
def self.page_for_owner(user, page)
|
33
|
-
|
69
|
+
def self.page_for_owner(user, page = 1)
|
70
|
+
list_for_owner(user).page(page)
|
34
71
|
end
|
35
72
|
|
36
73
|
def self.entity_parameters
|
37
|
-
%i
|
74
|
+
%i[author_name author_email body]
|
38
75
|
end
|
39
76
|
|
40
77
|
def self.creation_parameters
|
41
|
-
entity_parameters + %i
|
78
|
+
entity_parameters + %i[commentable_id commentable_type parent_id]
|
42
79
|
end
|
43
80
|
|
44
81
|
def self.administrative_parameters
|
45
|
-
entity_parameters + %i
|
82
|
+
entity_parameters + %i[deleted visible spam]
|
83
|
+
end
|
84
|
+
|
85
|
+
def self.tree(collection)
|
86
|
+
result = {}
|
87
|
+
|
88
|
+
collection.each do |entity|
|
89
|
+
result[entity.id] = {
|
90
|
+
parent_id: entity.parent_id,
|
91
|
+
comment: entity,
|
92
|
+
}
|
93
|
+
end
|
94
|
+
|
95
|
+
result
|
46
96
|
end
|
47
97
|
|
48
98
|
# @param [User] user
|
49
99
|
def visible_to?(user)
|
50
|
-
if
|
100
|
+
if commentable.respond_to? :visible_to?
|
51
101
|
if deleted?
|
52
102
|
UserPrivilege.user_has_privilege?(user, :administrator) && commentable.visible_to?(user)
|
53
103
|
else
|
@@ -67,13 +117,32 @@ class Comment < ApplicationRecord
|
|
67
117
|
end
|
68
118
|
end
|
69
119
|
|
120
|
+
def text_for_link
|
121
|
+
"#{self.class.model_name.human} #{id}"
|
122
|
+
end
|
123
|
+
|
124
|
+
def commentable_name
|
125
|
+
"#{commentable.class.model_name.human} #{commentable_id}"
|
126
|
+
end
|
127
|
+
|
128
|
+
def commentable_title
|
129
|
+
if commentable.respond_to?(:title)
|
130
|
+
commentable.title
|
131
|
+
else
|
132
|
+
commentable_name
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
def profile_name
|
137
|
+
user.nil? ? author_name : user.profile_name
|
138
|
+
end
|
139
|
+
|
70
140
|
private
|
71
141
|
|
72
142
|
def commentable_is_commentable
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
end
|
143
|
+
return unless commentable.respond_to?(:commentable_by?)
|
144
|
+
return if commentable.commentable_by?(user)
|
145
|
+
|
146
|
+
errors.add(:commentable, I18n.t('activerecord.errors.models.comment.attributes.commentable.not_commentable'))
|
78
147
|
end
|
79
148
|
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Adds "commentable" behavior to entity
|
4
|
+
module CommentableItem
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
included do
|
8
|
+
has_many :comments, as: :commentable, dependent: :destroy
|
9
|
+
end
|
10
|
+
|
11
|
+
# @param [User] user
|
12
|
+
def commentable_by?(user)
|
13
|
+
!user.nil?
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Biovision
|
4
|
+
module Components
|
5
|
+
# Handler for Biovision Comments
|
6
|
+
class CommentsComponent < BaseComponent
|
7
|
+
SLUG = 'comments'
|
8
|
+
|
9
|
+
def self.privilege_names
|
10
|
+
%w[moderator]
|
11
|
+
end
|
12
|
+
|
13
|
+
def use_parameters?
|
14
|
+
false
|
15
|
+
end
|
16
|
+
|
17
|
+
# @param [Hash] parameters
|
18
|
+
def create_comment(parameters)
|
19
|
+
@comment = ::Comment.new(parameters)
|
20
|
+
@comment.save
|
21
|
+
@comment
|
22
|
+
end
|
23
|
+
|
24
|
+
protected
|
25
|
+
|
26
|
+
# @param [Hash] data
|
27
|
+
# @return [Hash]
|
28
|
+
def normalize_settings(data)
|
29
|
+
result = {}
|
30
|
+
flags = %w[premoderation]
|
31
|
+
flags.each { |f| result[f] = data[f].to_i == 1 }
|
32
|
+
numbers = %w[auto_approve_threshold]
|
33
|
+
numbers.each { |f| result[f] = data[f].to_i }
|
34
|
+
|
35
|
+
result
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Handler for working with comments
|
4
|
+
class CommentHandler
|
5
|
+
attr_accessor :user
|
6
|
+
|
7
|
+
# @param [User] user
|
8
|
+
def initialize(user = nil)
|
9
|
+
slug = Biovision::Components::CommentsComponent::SLUG
|
10
|
+
@user = user
|
11
|
+
@handler = Biovision::Components::BaseComponent.handler(slug)
|
12
|
+
end
|
13
|
+
|
14
|
+
# Get list of comments for entity
|
15
|
+
#
|
16
|
+
# Depending on privileges, receives list for visitors (only visible
|
17
|
+
# and approved) or all comments (including deleted).
|
18
|
+
#
|
19
|
+
# @param [ApplicationRecord] entity
|
20
|
+
def list(entity)
|
21
|
+
if @handler.class.allow?(@user)
|
22
|
+
entity.comments.list_for_administration
|
23
|
+
else
|
24
|
+
entity.comments.list_for_visitors
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# Auto-approve comment from current user?
|
29
|
+
#
|
30
|
+
# If premoderation flag is not set, every comment is automatically approved.
|
31
|
+
# If premoderation flag is set, difference between approved and non-approved
|
32
|
+
# comments must be more than threshold; anonymous comments are always
|
33
|
+
# non-approved.
|
34
|
+
def approve?
|
35
|
+
return true unless @component.settings['premoderate']
|
36
|
+
return false if @user.nil?
|
37
|
+
return true if @component.class.allow?(@user)
|
38
|
+
|
39
|
+
gate = @component.settings['auto_approve_threshold'].to_i
|
40
|
+
positive = Comment.where(user: @user, approved: true).count
|
41
|
+
negative = Comment.where(user: @user, approved: false).count
|
42
|
+
positive - negative >= gate
|
43
|
+
end
|
44
|
+
|
45
|
+
# @param [ApplicationRecord] entity
|
46
|
+
def allow_reply?(entity)
|
47
|
+
entity.respond_to?(:commentable_by?) && entity.commentable_by?(@user)
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Tool for handling comments
|
4
|
+
class CommentsManager
|
5
|
+
# @param [Comment] comment
|
6
|
+
# @param [TrueClass|FalseClass] anchor
|
7
|
+
def self.commentable_path(comment, anchor = false)
|
8
|
+
method_name = "#{comment.commentable_type}_path".downcase.to_sym
|
9
|
+
if respond_to?(method_name)
|
10
|
+
result = send(method_name, comment.commentable)
|
11
|
+
anchor ? "#{result}#comment-#{comment.id}" : result
|
12
|
+
else
|
13
|
+
"##{method_name}"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
# @param [Post] entity
|
18
|
+
def self.post_path(entity)
|
19
|
+
return '#post' unless Gem.loaded_specs.key?('biovision-post')
|
20
|
+
|
21
|
+
handler = PostManager.new(entity)
|
22
|
+
handler.post_path
|
23
|
+
end
|
24
|
+
end
|
@@ -6,23 +6,26 @@
|
|
6
6
|
<%= admin_comment_link(entity) %>
|
7
7
|
|
8
8
|
<% if entity.deleted? %>
|
9
|
-
|
9
|
+
(<%= t('activerecord.attributes.comment.deleted') %>)
|
10
10
|
<% end %>
|
11
11
|
</div>
|
12
12
|
|
13
13
|
<div class="info">
|
14
|
+
<%= entity.commentable_name %>
|
15
|
+
(<%= entity.commentable_title %>)<br/>
|
14
16
|
<%= admin_user_link(entity.user) %>,
|
15
17
|
<%= time_tag entity.created_at %>
|
16
18
|
</div>
|
17
19
|
|
18
|
-
<div class="info">
|
19
|
-
<%=
|
20
|
+
<div class="secondary info">
|
21
|
+
<%= simple_format(entity.body) %>
|
20
22
|
</div>
|
21
23
|
|
22
24
|
<ul class="actions">
|
23
|
-
<li><%= edit_icon(edit_comment_path(entity.id)) %></li>
|
25
|
+
<li><%= edit_icon(edit_comment_path(id: entity.id)) %></li>
|
24
26
|
<% unless entity.deleted? %>
|
25
|
-
|
27
|
+
<li><%= world_icon(CommentsManager.commentable_path(entity, true)) %></li>
|
28
|
+
<li class="danger"><%= destroy_icon(entity) %></li>
|
26
29
|
<% end %>
|
27
30
|
</ul>
|
28
31
|
</div>
|