the_comments 0.9.0
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.
- 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') }
|