the_comments 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +18 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +248 -0
- data/Rakefile +1 -0
- data/app/assets/javascripts/the_comments.js.coffee +104 -0
- data/app/assets/javascripts/the_comments_manage.js.coffee +59 -0
- data/app/assets/stylesheets/the_comments.css.scss +251 -0
- data/app/controllers/concerns/the_comments_controller.rb +227 -0
- data/app/controllers/concerns/the_comments_ip_controller.rb +17 -0
- data/app/controllers/concerns/the_comments_user_agent_controller.rb +17 -0
- data/app/helpers/render_comments_tree_helper.rb +113 -0
- data/app/models/concerns/the_comments_base.rb +64 -0
- data/app/models/concerns/the_comments_black_ip.rb +10 -0
- data/app/models/concerns/the_comments_black_user_agent.rb +10 -0
- data/app/models/concerns/the_comments_commentable.rb +50 -0
- data/app/models/concerns/the_comments_states.rb +64 -0
- data/app/models/concerns/the_comments_user.rb +25 -0
- data/app/views/ip_black_lists/index.html.haml +17 -0
- data/app/views/the_comments/_comment.html.haml +1 -0
- data/app/views/the_comments/_comment_body.html.haml +30 -0
- data/app/views/the_comments/_form.html.haml +25 -0
- data/app/views/the_comments/_manage_controls.html.haml +4 -0
- data/app/views/the_comments/_tree.html.haml +4 -0
- data/app/views/the_comments/index.html.haml +19 -0
- data/app/views/the_comments/manage.html.haml +29 -0
- data/app/views/user_agent_black_lists/index.html.haml +17 -0
- data/config/locales/en.yml +43 -0
- data/config/routes.rb +24 -0
- data/db/migrate/20130101010101_create_comments.rb +81 -0
- data/lib/generators/the_comments/USAGE +29 -0
- data/lib/generators/the_comments/templates/comments_controller.rb +25 -0
- data/lib/generators/the_comments/templates/ip_black_lists_controller.rb +10 -0
- data/lib/generators/the_comments/templates/the_comments.rb +7 -0
- data/lib/generators/the_comments/templates/user_agent_black_lists_controller.rb +10 -0
- data/lib/generators/the_comments/the_comments_generator.rb +32 -0
- data/lib/generators/the_comments/views_generator.rb +41 -0
- data/lib/the_comments/config.rb +21 -0
- data/lib/the_comments/version.rb +3 -0
- data/lib/the_comments.rb +10 -0
- data/the_comments.gemspec +23 -0
- data/the_comments.jpg +0 -0
- metadata +120 -0
@@ -0,0 +1,227 @@
|
|
1
|
+
module TheCommentsController
|
2
|
+
COMMENTS_COOKIES_TOKEN = 'JustTheCommentsCookies'
|
3
|
+
|
4
|
+
# View token for Commentable controller
|
5
|
+
#
|
6
|
+
# class PagesController < ApplicationController
|
7
|
+
# include TheCommentsController::ViewToken
|
8
|
+
# end
|
9
|
+
module ViewToken
|
10
|
+
def comments_view_token
|
11
|
+
cookies[:comments_view_token] = { :value => SecureRandom.hex, :expires => 7.days.from_now } unless cookies[:comments_view_token]
|
12
|
+
cookies[:comments_view_token]
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
# Cookies for spam protection
|
17
|
+
#
|
18
|
+
# class ApplicationController < ActionController::Base
|
19
|
+
# include TheCommentsController::Cookies
|
20
|
+
# end
|
21
|
+
module Cookies
|
22
|
+
extend ActiveSupport::Concern
|
23
|
+
included { before_action :set_the_comments_cookies }
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def set_the_comments_cookies
|
28
|
+
cookies[:the_comment_cookies] = { :value => TheCommentsController::COMMENTS_COOKIES_TOKEN, :expires => 1.year.from_now }
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# Base functionality of Comments Controller
|
33
|
+
#
|
34
|
+
# class CommentsController < ApplicationController
|
35
|
+
# include TheCommentsController::Base
|
36
|
+
# end
|
37
|
+
module Base
|
38
|
+
extend ActiveSupport::Concern
|
39
|
+
|
40
|
+
included do
|
41
|
+
include TheCommentsController::ViewToken
|
42
|
+
|
43
|
+
before_action -> { @errors = [] }
|
44
|
+
|
45
|
+
# Attention! We should not set TheComments cookie before create
|
46
|
+
skip_before_action :set_the_comments_cookies, only: [:create]
|
47
|
+
|
48
|
+
# Black lists
|
49
|
+
before_action :stop_black_ip, only: [:create]
|
50
|
+
before_action :stop_black_user_agent, only: [:create]
|
51
|
+
|
52
|
+
# Spam protection
|
53
|
+
before_action :ajax_requests_required, only: [:create]
|
54
|
+
before_action :cookies_required, only: [:create]
|
55
|
+
before_action :empty_trap_required, only: [:create]
|
56
|
+
before_action :tolerance_time_required, only: [:create]
|
57
|
+
|
58
|
+
# preparation
|
59
|
+
before_action :define_commentable, only: [:create]
|
60
|
+
end
|
61
|
+
|
62
|
+
# App side methods (overwrite it)
|
63
|
+
def index
|
64
|
+
@comments = Comment.with_state(:published).order('created_at DESC').page(params[:page])
|
65
|
+
render template: 'the_comments/index'
|
66
|
+
end
|
67
|
+
|
68
|
+
# Methods based on *current_user* helper
|
69
|
+
def edit
|
70
|
+
@comments = current_user.comcoms.where(id: params[:id]).page(params[:page])
|
71
|
+
render template: 'the_comments/manage'
|
72
|
+
end
|
73
|
+
|
74
|
+
def my
|
75
|
+
@comments = current_user.comments.with_state(:draft, :published).order('created_at DESC').page(params[:page])
|
76
|
+
render template: 'the_comments/index'
|
77
|
+
end
|
78
|
+
|
79
|
+
def incoming
|
80
|
+
@comments = current_user.comcoms.with_state(:draft, :published).order('created_at DESC').page(params[:page])
|
81
|
+
render template: 'the_comments/manage'
|
82
|
+
end
|
83
|
+
|
84
|
+
def trash
|
85
|
+
@comments = current_user.comcoms.with_state(:deleted).order('created_at DESC').page(params[:page])
|
86
|
+
render template: 'the_comments/manage'
|
87
|
+
end
|
88
|
+
|
89
|
+
# Base methods
|
90
|
+
|
91
|
+
def update
|
92
|
+
comment = Comment.where(id: params[:id]).first
|
93
|
+
comment.update_attributes!(patch_comment_params)
|
94
|
+
render(layout: false, partial: 'the_comments/comment_body', locals: { comment: comment })
|
95
|
+
end
|
96
|
+
|
97
|
+
def create
|
98
|
+
@comment = @commentable.comments.new comment_params
|
99
|
+
if @comment.valid?
|
100
|
+
@comment.save
|
101
|
+
return render layout: false, partial: 'the_comments/comment', locals: { tree: @comment }
|
102
|
+
end
|
103
|
+
render json: { errors: @comment.errors.full_messages }
|
104
|
+
end
|
105
|
+
|
106
|
+
def to_published
|
107
|
+
Comment.where(id: params[:id]).first.to_published
|
108
|
+
render nothing: :true
|
109
|
+
end
|
110
|
+
|
111
|
+
def to_draft
|
112
|
+
Comment.where(id: params[:id]).first.to_draft
|
113
|
+
render nothing: :true
|
114
|
+
end
|
115
|
+
|
116
|
+
def to_spam
|
117
|
+
comment = Comment.where(id: params[:id]).first
|
118
|
+
IpBlackList.where(ip: comment.ip).first_or_create.increment!(:count)
|
119
|
+
UserAgentBlackList.where(user_agent: comment.user_agent).first_or_create.increment!(:count)
|
120
|
+
comment.to_deleted
|
121
|
+
render nothing: :true
|
122
|
+
end
|
123
|
+
|
124
|
+
def to_trash
|
125
|
+
Comment.where(id: params[:id]).first.to_deleted
|
126
|
+
render nothing: :true
|
127
|
+
end
|
128
|
+
|
129
|
+
private
|
130
|
+
|
131
|
+
def denormalized_fields
|
132
|
+
title = @commentable.commentable_title
|
133
|
+
url = @commentable.commentable_url
|
134
|
+
@commentable ? { commentable_title: title, commentable_url: url } : {}
|
135
|
+
end
|
136
|
+
|
137
|
+
def request_data_for_black_lists
|
138
|
+
r = request
|
139
|
+
{ ip: r.ip, referer: CGI::unescape(r.referer || 'direct_visit'), user_agent: r.user_agent }
|
140
|
+
end
|
141
|
+
|
142
|
+
def define_commentable
|
143
|
+
commentable_klass = params[:comment][:commentable_type].constantize
|
144
|
+
commentable_id = params[:comment][:commentable_id]
|
145
|
+
|
146
|
+
@commentable = commentable_klass.where(id: commentable_id).first
|
147
|
+
return render(json: { errors: ['Commentable object is undefined'] }) unless @commentable
|
148
|
+
end
|
149
|
+
|
150
|
+
def comment_params
|
151
|
+
params
|
152
|
+
.require(:comment)
|
153
|
+
.permit(:title, :contacts, :raw_content, :parent_id)
|
154
|
+
.merge(user: current_user, view_token: comments_view_token)
|
155
|
+
.merge(denormalized_fields)
|
156
|
+
.merge( tolerance_time: params[:tolerance_time].to_i )
|
157
|
+
.merge(request_data_for_black_lists)
|
158
|
+
end
|
159
|
+
|
160
|
+
def patch_comment_params
|
161
|
+
params
|
162
|
+
.require(:comment)
|
163
|
+
.permit(:title, :contacts, :raw_content, :parent_id)
|
164
|
+
end
|
165
|
+
|
166
|
+
# Protection tricks
|
167
|
+
def cookies_required
|
168
|
+
unless cookies[:the_comment_cookies] == TheCommentsController::COMMENTS_COOKIES_TOKEN
|
169
|
+
@errors << [t('the_comments.cookies'), t('the_comments.cookies_required')].join(' ')
|
170
|
+
return render(json: { errors: @errors })
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
def ajax_requests_required
|
175
|
+
unless request.xhr?
|
176
|
+
IpBlackList.where(ip: request.ip).first_or_create.increment!(:count)
|
177
|
+
UserAgentBlackList.where(user_agent: request.user_agent).first_or_create.increment!(:count)
|
178
|
+
return render(text: t('the_comments.ajax_requests_required'))
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
def empty_trap_required
|
183
|
+
# TODO: 1) inject ?, 2) fields can be removed on client site
|
184
|
+
is_user = true
|
185
|
+
params.slice(*TheComments.config.empty_inputs).values.each{|v| is_user = is_user && v.blank? }
|
186
|
+
|
187
|
+
unless is_user
|
188
|
+
IpBlackList.where(ip: request.ip).first_or_create.increment!(:count)
|
189
|
+
UserAgentBlackList.where(user_agent: request.user_agent).first_or_create.increment!(:count)
|
190
|
+
@errors << [t('the_comments.trap'), t('the_comments.trap_message')].join(' ')
|
191
|
+
return render(json: { errors: @errors })
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
def tolerance_time_required
|
196
|
+
min_time = TheComments.config.tolerance_time
|
197
|
+
this_time = params[:tolerance_time].to_i
|
198
|
+
if this_time < min_time
|
199
|
+
@errors << [t('the_comments.tolerance_time'), t('the_comments.tolerance_time_message', time: min_time - this_time )].join(' ')
|
200
|
+
return render(json: { errors: @errors })
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
def stop_black_ip
|
205
|
+
if IpBlackList.where(ip: request.ip, state: :banned).first
|
206
|
+
@errors << [t('the_comments.black_ip'), t('the_comments.black_ip_message')].join(' ')
|
207
|
+
return render(json: { errors: @errors })
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
def stop_black_user_agent
|
212
|
+
if UserAgentBlackList.where(user_agent: request.user_agent, state: :banned).first
|
213
|
+
@errors << [t('the_comments.black_user_agent'), t('the_comments.black_user_agent_message')].join(' ')
|
214
|
+
return render(json: { errors: @errors })
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
def time_tolerance_required
|
219
|
+
if params[:time_tolerance].to_i < TheComments.config.tolerance_time
|
220
|
+
errors = {}
|
221
|
+
errors[t('the_comments.time_tolerance')] = [t('the_comments.time_tolerance_message')]
|
222
|
+
return render(json: { errors: errors })
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
end#Base
|
227
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module TheCommentsIpController
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
|
4
|
+
included do
|
5
|
+
def index
|
6
|
+
@ip_black_lists = if params[:ip]
|
7
|
+
IpBlackList.where(ip: params[:ip])
|
8
|
+
else
|
9
|
+
IpBlackList.order('count DESC').page(params[:page])
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def to_state
|
14
|
+
return render text: IpBlackList.where(id: params[:ip_black_list_id]).first.update!(state: params[:state])
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module TheCommentsUserAgentController
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
|
4
|
+
included do
|
5
|
+
def index
|
6
|
+
@ip_black_lists = if params[:ip]
|
7
|
+
UserAgentBlackList.where(ip: params[:ip])
|
8
|
+
else
|
9
|
+
UserAgentBlackList.order('count DESC').page(params[:page])
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def to_state
|
14
|
+
return render text: UserAgentBlackList.where(id: params[:user_agent_black_list_id]).first.update!(state: params[:state])
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,113 @@
|
|
1
|
+
# coding: UTF-8
|
2
|
+
# DOC:
|
3
|
+
# We use Helper Methods for tree building,
|
4
|
+
# because it's faster than View Templates and Partials
|
5
|
+
|
6
|
+
# SECURITY note
|
7
|
+
# Prepare your data on server side for rendering
|
8
|
+
# or use h.html_escape(node.content)
|
9
|
+
# for escape potentially dangerous content
|
10
|
+
module RenderCommentsTreeHelper
|
11
|
+
module Render
|
12
|
+
class << self
|
13
|
+
attr_accessor :h, :options
|
14
|
+
|
15
|
+
# Main Helpers
|
16
|
+
def controller
|
17
|
+
@options[:controller]
|
18
|
+
end
|
19
|
+
|
20
|
+
def t str
|
21
|
+
controller.t str
|
22
|
+
end
|
23
|
+
|
24
|
+
# Render Helpers
|
25
|
+
def visible_draft?
|
26
|
+
controller.try(:comments_view_token) == @comment.view_token
|
27
|
+
end
|
28
|
+
|
29
|
+
def moderator?
|
30
|
+
controller.try(:current_user).try(:comment_moderator?, @comment)
|
31
|
+
end
|
32
|
+
|
33
|
+
# Render Methods
|
34
|
+
def render_node(h, options)
|
35
|
+
@h, @options = h, options
|
36
|
+
@comment = options[:node]
|
37
|
+
|
38
|
+
@max_reply_depth = options[:max_reply_depth] || TheComments.config.max_reply_depth
|
39
|
+
|
40
|
+
if @comment.draft?
|
41
|
+
draft_comment
|
42
|
+
elsif @comment.published?
|
43
|
+
published_comment
|
44
|
+
else
|
45
|
+
deleted_comment
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def draft_comment
|
50
|
+
if visible_draft? || moderator?
|
51
|
+
published_comment
|
52
|
+
else
|
53
|
+
"<li class='draft'>
|
54
|
+
<div class='comment draft' id='comment_#{@comment.anchor}'>
|
55
|
+
#{ t('the_comments.waiting_for_moderation') }
|
56
|
+
#{ h.link_to '#', '#comment_' + @comment.anchor }
|
57
|
+
</div>
|
58
|
+
#{ children }
|
59
|
+
</li>"
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def published_comment
|
64
|
+
"<li>
|
65
|
+
<div id='comment_#{@comment.anchor}' class='comment #{@comment.state}' data-comment-id='#{@comment.to_param}'>
|
66
|
+
<div>
|
67
|
+
#{ avatar }
|
68
|
+
#{ userbar }
|
69
|
+
<div class='cbody'>#{ @comment.content }</div>
|
70
|
+
#{ reply }
|
71
|
+
</div>
|
72
|
+
</div>
|
73
|
+
|
74
|
+
<div class='form_holder'></div>
|
75
|
+
#{ children }
|
76
|
+
</li>"
|
77
|
+
end
|
78
|
+
|
79
|
+
def avatar
|
80
|
+
"<div class='userpic'>
|
81
|
+
<img src='#{ @comment.avatar_url }' alt='userpic' />
|
82
|
+
#{ controls }
|
83
|
+
</div>"
|
84
|
+
end
|
85
|
+
|
86
|
+
def userbar
|
87
|
+
anchor = h.link_to('#', '#comment_' + @comment.anchor)
|
88
|
+
title = @comment.title.blank? ? t('the_comments.guest_name') : @comment.title
|
89
|
+
"<div class='userbar'>#{ title } #{ anchor }</div>"
|
90
|
+
end
|
91
|
+
|
92
|
+
def moderator_controls
|
93
|
+
if moderator?
|
94
|
+
h.link_to t('the_comments.edit'), h.edit_comment_url(@comment), class: :edit
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def reply
|
99
|
+
if @comment.depth < @max_reply_depth
|
100
|
+
"<p class='reply'><a href='#' class='reply_link'>#{ t('the_comments.reply') }</a>"
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def controls
|
105
|
+
"<div class='controls'>#{ moderator_controls }</div>"
|
106
|
+
end
|
107
|
+
|
108
|
+
def children
|
109
|
+
"<ol class='nested_set'>#{ options[:children] }</ol>"
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
module TheCommentsBase
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
|
4
|
+
included do
|
5
|
+
# Nested Set
|
6
|
+
attr_accessible :parent_id
|
7
|
+
acts_as_nested_set scope: [:commentable_type, :commentable_id]
|
8
|
+
|
9
|
+
# Comments State Machine
|
10
|
+
include TheCommentsStates
|
11
|
+
|
12
|
+
# TheSortableTree
|
13
|
+
include TheSortableTree::Scopes
|
14
|
+
|
15
|
+
attr_accessible :user, :title, :contacts, :raw_content, :view_token, :state
|
16
|
+
attr_accessible :ip, :referer, :user_agent, :tolerance_time
|
17
|
+
|
18
|
+
validates :raw_content, presence: true
|
19
|
+
|
20
|
+
# relations
|
21
|
+
belongs_to :user
|
22
|
+
belongs_to :holder, class_name: :User
|
23
|
+
belongs_to :commentable, polymorphic: true
|
24
|
+
|
25
|
+
# callbacks
|
26
|
+
before_create :define_holder, :define_anchor, :denormalize_commentable
|
27
|
+
after_create :update_cache_counters
|
28
|
+
before_save :prepare_content
|
29
|
+
|
30
|
+
def avatar_url
|
31
|
+
src = id.to_s
|
32
|
+
src = title unless title.blank?
|
33
|
+
src = contacts if !contacts.blank? && /@/ =~ contacts
|
34
|
+
hash = Digest::MD5.hexdigest(src)
|
35
|
+
"http://www.gravatar.com/avatar/#{hash}?s=40&d=identicon"
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def define_anchor
|
41
|
+
self.anchor = SecureRandom.hex[0..5]
|
42
|
+
end
|
43
|
+
|
44
|
+
def define_holder
|
45
|
+
self.holder = self.commentable.user
|
46
|
+
end
|
47
|
+
|
48
|
+
def denormalize_commentable
|
49
|
+
self.commentable_title = self.commentable.try :commentable_title
|
50
|
+
self.commentable_url = self.commentable.try :commentable_url
|
51
|
+
self.commentable_state = self.commentable.try :state
|
52
|
+
end
|
53
|
+
|
54
|
+
def prepare_content
|
55
|
+
self.content = self.raw_content
|
56
|
+
end
|
57
|
+
|
58
|
+
def update_cache_counters
|
59
|
+
self.user.try :increment!, :draft_comments_count
|
60
|
+
self.holder.try :increment!, :draft_comcoms_count
|
61
|
+
self.commentable.try :increment!, :draft_comments_count
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module TheCommentsCommentable
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
|
4
|
+
included do
|
5
|
+
has_many :comments, as: :commentable
|
6
|
+
|
7
|
+
# TODO: update requests reduction
|
8
|
+
# *define_denormalize_flags* - should be placed before
|
9
|
+
# title or url builder filters
|
10
|
+
before_validation :define_denormalize_flags
|
11
|
+
after_save :denormalize_for_comments
|
12
|
+
|
13
|
+
def commentable_title
|
14
|
+
try(:title) || 'Undefined title'
|
15
|
+
end
|
16
|
+
|
17
|
+
def commentable_url
|
18
|
+
# /pages/1
|
19
|
+
['', self.class.to_s.tableize, self.to_param].join('/')
|
20
|
+
end
|
21
|
+
|
22
|
+
def comments_sum
|
23
|
+
published_comments_count + draft_comments_count
|
24
|
+
end
|
25
|
+
|
26
|
+
def recalculate_comments_counters
|
27
|
+
self.draft_comments_count = comments.with_state(:draft).count
|
28
|
+
self.published_comments_count = comments.with_state(:published).count
|
29
|
+
self.deleted_comments_count = comments.with_state(:deleted).count
|
30
|
+
save
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def define_denormalize_flags
|
36
|
+
@_commentable_title = commentable_title
|
37
|
+
@_commentable_url = commentable_url
|
38
|
+
end
|
39
|
+
|
40
|
+
def denormalize_for_comments
|
41
|
+
title_changed = @_commentable_title != commentable_title
|
42
|
+
url_changed = @_commentable_url != commentable_url
|
43
|
+
cstate = try(:state_changed?) ? { commentable_state: state } : {}
|
44
|
+
|
45
|
+
if title_changed || url_changed || !cstate.blank?
|
46
|
+
comments.update_all({ commentable_title: commentable_title, commentable_url: commentable_url}.merge(cstate))
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
module TheCommentsStates
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
|
4
|
+
included do
|
5
|
+
# :draft | :published | :deleted
|
6
|
+
state_machine :state, :initial => :draft do
|
7
|
+
|
8
|
+
# events
|
9
|
+
event :to_draft do
|
10
|
+
transition all - :draft => :draft
|
11
|
+
end
|
12
|
+
|
13
|
+
event :to_published do
|
14
|
+
transition all - :published => :published
|
15
|
+
end
|
16
|
+
|
17
|
+
event :to_deleted do
|
18
|
+
transition any - :deleted => :deleted
|
19
|
+
end
|
20
|
+
|
21
|
+
# transition callbacks
|
22
|
+
after_transition any => any do |comment|
|
23
|
+
@comment = comment
|
24
|
+
@owner = comment.user
|
25
|
+
@holder = comment.holder
|
26
|
+
@commentable = comment.commentable
|
27
|
+
end
|
28
|
+
|
29
|
+
# between draft and published
|
30
|
+
after_transition [:draft, :published] => [:draft, :published] do |comment, transition|
|
31
|
+
from = transition.from_name
|
32
|
+
to = transition.to_name
|
33
|
+
|
34
|
+
@holder.try :increment!, :"#{to}_comcoms_count"
|
35
|
+
@holder.try :decrement!, :"#{from}_comcoms_count"
|
36
|
+
|
37
|
+
[@owner, @commentable].each do |obj|
|
38
|
+
obj.try :increment!, "#{to}_comments_count"
|
39
|
+
obj.try :decrement!, "#{from}_comments_count"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# to deleted (cascade like query)
|
44
|
+
after_transition [:draft, :published] => :deleted do |comment|
|
45
|
+
ids = comment.self_and_descendants.map(&:id)
|
46
|
+
Comment.where(id: ids).update_all(state: :deleted)
|
47
|
+
[@holder, @owner, @commentable].each{|o| o.try :recalculate_comments_counters }
|
48
|
+
end
|
49
|
+
|
50
|
+
# from deleted
|
51
|
+
after_transition :deleted => [:draft, :published] do |comment, transition|
|
52
|
+
to = transition.to_name
|
53
|
+
|
54
|
+
@holder.try :decrement!, :deleted_comcoms_count
|
55
|
+
@holder.try :increment!, "#{to}_comcoms_count"
|
56
|
+
|
57
|
+
[@owner, @commentable].each do |obj|
|
58
|
+
obj.try :decrement!, :deleted_comments_count
|
59
|
+
obj.try :increment!, "#{to}_comments_count"
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module TheCommentsUser
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
|
4
|
+
included do
|
5
|
+
has_many :comments
|
6
|
+
has_many :comcoms, class_name: :Comment, foreign_key: :holder_id
|
7
|
+
|
8
|
+
def recalculate_comments_counters
|
9
|
+
[:comments, :comcoms].each do |name|
|
10
|
+
send "draft_#{name}_count=", send(name).with_state(:draft).count
|
11
|
+
send "published_#{name}_count=", send(name).with_state(:published).count
|
12
|
+
send "deleted_#{name}_count=", send(name).with_state(:deleted).count
|
13
|
+
end
|
14
|
+
save
|
15
|
+
end
|
16
|
+
|
17
|
+
def comments_sum
|
18
|
+
published_comments_count + draft_comments_count
|
19
|
+
end
|
20
|
+
|
21
|
+
def comcoms_sum
|
22
|
+
published_comcoms_count + draft_comcoms_count
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
- unless @ip_black_lists.blank?
|
2
|
+
%ol.black_list
|
3
|
+
- @ip_black_lists.each do |black|
|
4
|
+
%li{ class: black.state }
|
5
|
+
%p
|
6
|
+
%b ip:
|
7
|
+
= black.ip
|
8
|
+
%b count:
|
9
|
+
= black.count
|
10
|
+
%b state:
|
11
|
+
%span.state= black.state
|
12
|
+
%p
|
13
|
+
%b action:
|
14
|
+
= link_to :warning, ip_black_list_to_state_path(black, state: :warning), remote: true, method: :patch, class: :to_warning
|
15
|
+
= link_to :banned, ip_black_list_to_state_path(black, state: :banned), remote: true, method: :patch, class: :to_banned
|
16
|
+
- else
|
17
|
+
%p= t('the_comments.item_not_found')
|
@@ -0,0 +1 @@
|
|
1
|
+
= build_server_tree(tree, render_module: RenderCommentsTreeHelper, controller: controller)
|
@@ -0,0 +1,30 @@
|
|
1
|
+
.comment
|
2
|
+
.commentable
|
3
|
+
= comment.commentable_type
|
4
|
+
→
|
5
|
+
= link_to comment.commentable_title, comment.commentable_url
|
6
|
+
= comment.try(:commentable_state) ? "(#{comment.try(:commentable_state)})" : nil
|
7
|
+
.title
|
8
|
+
%label from:
|
9
|
+
= comment.title.blank? ? t('the_comments.guest_name') : comment.title
|
10
|
+
.contacts
|
11
|
+
%label contacts:
|
12
|
+
= comment.contacts
|
13
|
+
.content
|
14
|
+
= comment.content
|
15
|
+
.params
|
16
|
+
%b tolerance time:
|
17
|
+
= comment.tolerance_time || 'none'
|
18
|
+
\|
|
19
|
+
%b ip:
|
20
|
+
= link_to(comment.ip, ip_black_lists_url(ip: comment.ip)) || 'none'
|
21
|
+
\|
|
22
|
+
%b UA:
|
23
|
+
= link_to(comment.user_agent, user_agent_black_lists_url(agent: comment.user_agent)) || 'none'
|
24
|
+
\|
|
25
|
+
%b referer:
|
26
|
+
= comment.referer || 'none'
|
27
|
+
|
28
|
+
.controls
|
29
|
+
= link_to :Edit, '#', class: :edit
|
30
|
+
= render partial: 'the_comments/manage_controls', locals: { comment: comment }
|
@@ -0,0 +1,25 @@
|
|
1
|
+
%h3
|
2
|
+
= link_to 'New Comment', '#', id: :new_root_comment
|
3
|
+
|
4
|
+
= form_for Comment.new, remote: true, authenticity_token: true do |f|
|
5
|
+
.error_notifier{ style: "display:none" }
|
6
|
+
%label title:
|
7
|
+
%p= f.text_field :title
|
8
|
+
|
9
|
+
%label contacts:
|
10
|
+
%p= f.text_field :contacts
|
11
|
+
|
12
|
+
%label content*:
|
13
|
+
%p= f.text_area :raw_content
|
14
|
+
|
15
|
+
%p.trap
|
16
|
+
- TheComments.config.empty_inputs.each do |name|
|
17
|
+
= text_field_tag name, nil, autocomplete: :off, tabindex: -1, id: nil
|
18
|
+
|
19
|
+
= hidden_field_tag :tolerance_time, 0, id: nil, class: :tolerance_time
|
20
|
+
|
21
|
+
= f.hidden_field :commentable_type, value: commentable.class
|
22
|
+
= f.hidden_field :commentable_id, value: commentable.id
|
23
|
+
= f.hidden_field :parent_id, class: :parent_id
|
24
|
+
|
25
|
+
%p= f.submit 'Create Comment', class: :btn
|
@@ -0,0 +1,4 @@
|
|
1
|
+
= link_to t('the_comments.to_published'), to_published_comment_url(comment), remote: true, class: :to_published, method: :post
|
2
|
+
= link_to t('the_comments.to_draft'), to_draft_comment_url(comment), remote: true, class: :to_draft, method: :post
|
3
|
+
= link_to t('the_comments.to_spam'), to_spam_comment_url(comment), remote: true, class: :to_spam, method: :post
|
4
|
+
= link_to t('the_comments.to_deleted'), to_trash_comment_url(comment), remote: true, class: :to_deleted, method: :delete, data: { confirm: t('the_comments.delete_confirm') }
|