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
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Ilya N. Zykin
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,248 @@
|
|
1
|
+
# TheComments 0.9.0
|
2
|
+
|
3
|
+
TheComments - probably, best solution for comments for Ruby on Rails.
|
4
|
+
|
5
|
+
### What's wrong with other gems?
|
6
|
+
|
7
|
+
Just look at [Ruby-Toolbox](https://www.ruby-toolbox.com/categories/rails_comments). What we can see?
|
8
|
+
|
9
|
+
* [Acts as commentable with threading](https://github.com/elight/acts_as_commentable_with_threading) - so, guys, where is the render helper for the tree? There is no helper! Should I make render helper for tree by myself? Nooooo!!! I'm so sorry, but I can't use this gem.
|
10
|
+
* [acts_as_commentable](https://github.com/jackdempsey/acts_as_commentable) - so, I can see code for models. But I can't see code for controllers and views. Unfortunately, there is no threading. It's not enough for me.
|
11
|
+
* [opinio](https://github.com/Draiken/opinio) - looks better, but there is no threading. I want to have more!
|
12
|
+
* [has_threaded_comments](https://github.com/aarongough/has_threaded_comments) - Nice work! Nice gem! Models, controllers, views, view helper for tree rendering! **But**, last activity 2 years ago, I need few features, I think - I can make it better.
|
13
|
+
|
14
|
+
### In sum
|
15
|
+
|
16
|
+

|
17
|
+
|
18
|
+
### My hopes about comments system
|
19
|
+
|
20
|
+
* Open comments for everybody (by default). *I hate user registration*
|
21
|
+
* Moderation for comments and simple Admin UI
|
22
|
+
* Spam traps instead Captcha. *I hate Captcha*
|
23
|
+
* Blacklists for IP and UserAgent
|
24
|
+
* Comment counters for commentable objects and User
|
25
|
+
* Denormalization for fast and Request-free comment list building
|
26
|
+
* Ready for external content filters ( **sanitize**, **RedCloth**, **Markdown**)
|
27
|
+
* Ready for Rails4 (and Rails::Engine)
|
28
|
+
* Delete without destroy
|
29
|
+
|
30
|
+
### Requires
|
31
|
+
|
32
|
+
```ruby
|
33
|
+
gem 'awesome_nested_set'
|
34
|
+
gem 'the_sortable_tree'
|
35
|
+
gem 'state_machine'
|
36
|
+
```
|
37
|
+
|
38
|
+
### Anti Spam system
|
39
|
+
|
40
|
+
User agent must have:
|
41
|
+
|
42
|
+
* Cookies support
|
43
|
+
* JavaScript and Ajax support
|
44
|
+
|
45
|
+
_Usually spambot not support Cookies and JavaScript_
|
46
|
+
|
47
|
+
Comment form has:
|
48
|
+
|
49
|
+
* fake (hidden) fields
|
50
|
+
|
51
|
+
_Usually spam bot puts data in fake inputs_
|
52
|
+
|
53
|
+
Trap via time:
|
54
|
+
|
55
|
+
* User should be few seconds on page, before comment sending (by default 5 sec)
|
56
|
+
|
57
|
+
_Usually spam bots works faster, than human. We can try to use this feature of behavior_
|
58
|
+
|
59
|
+
## Installation
|
60
|
+
|
61
|
+
```ruby
|
62
|
+
gem 'the_comments'
|
63
|
+
```
|
64
|
+
|
65
|
+
**bundle**
|
66
|
+
|
67
|
+
```ruby
|
68
|
+
bundle exec rails g the_comment install
|
69
|
+
```
|
70
|
+
|
71
|
+
### Assets
|
72
|
+
|
73
|
+
**app/assets/javascripts/application.js**
|
74
|
+
|
75
|
+
```js
|
76
|
+
//= require the_comments
|
77
|
+
```
|
78
|
+
|
79
|
+
**app/assets/stylesheets/application.css**
|
80
|
+
|
81
|
+
```css
|
82
|
+
/*
|
83
|
+
*= require the_comments
|
84
|
+
*/
|
85
|
+
```
|
86
|
+
|
87
|
+
### User Model
|
88
|
+
|
89
|
+
```ruby
|
90
|
+
class User < ActiveRecord::Base
|
91
|
+
# Your implementation of role policy
|
92
|
+
def admin?
|
93
|
+
self == User.first
|
94
|
+
end
|
95
|
+
|
96
|
+
# include TheComments methods
|
97
|
+
include TheCommentsUser
|
98
|
+
|
99
|
+
# denormalization for commentable objects
|
100
|
+
def commentable_title
|
101
|
+
login
|
102
|
+
end
|
103
|
+
|
104
|
+
def commentable_url
|
105
|
+
[class.to_s.tableize, login].join('/')
|
106
|
+
end
|
107
|
+
|
108
|
+
# Comments moderator checking (simple example)
|
109
|
+
# Usually comment's holder should be moderator
|
110
|
+
def comment_moderator? comment
|
111
|
+
admin? || id == comment.holder_id
|
112
|
+
end
|
113
|
+
|
114
|
+
end
|
115
|
+
```
|
116
|
+
|
117
|
+
**User#coments** - comments. Set of all created comments
|
118
|
+
|
119
|
+
```ruby
|
120
|
+
User.first.comments
|
121
|
+
# => Array of comments, where User is creator (owner)
|
122
|
+
```
|
123
|
+
|
124
|
+
**User#comcoms** - commentable comments. Set of all comments of all owned commentable objects of this user.
|
125
|
+
|
126
|
+
```ruby
|
127
|
+
User.first.comcoms
|
128
|
+
# => Array of all comments of all owned commentable objects, where User is holder
|
129
|
+
# Usually comment's holder should be moderator of this comments
|
130
|
+
# because this user should maintain cleaness of his commentable objects
|
131
|
+
```
|
132
|
+
|
133
|
+
**Attention!** You should be sure that you understand who is owner, and who is holder of comments!
|
134
|
+
|
135
|
+
### Commentable Model (Page, Blog, Article, User ...)
|
136
|
+
|
137
|
+
**Attention!** User model can be commentable object also.
|
138
|
+
|
139
|
+
```ruby
|
140
|
+
class Blog < ActiveRecord::Base
|
141
|
+
# include TheComments methods
|
142
|
+
include TheCommentsCommentable
|
143
|
+
|
144
|
+
# (!) Every commentable Model must have next 2 methods
|
145
|
+
# denormalization for commentable objects
|
146
|
+
def commentable_title
|
147
|
+
# "My first blog post"
|
148
|
+
title
|
149
|
+
end
|
150
|
+
|
151
|
+
def commentable_url
|
152
|
+
# blogs/1-my-first-blog-post
|
153
|
+
[self.class.to_s.tableize, slug_id].join('/')
|
154
|
+
end
|
155
|
+
end
|
156
|
+
```
|
157
|
+
|
158
|
+
### Comment Model
|
159
|
+
|
160
|
+
```ruby
|
161
|
+
class Comment < ActiveRecord::Base
|
162
|
+
# include TheComments methods
|
163
|
+
include TheCommentsBase
|
164
|
+
|
165
|
+
# Define comment's avatar url
|
166
|
+
# Usually we use Comment#user (owner of comment) to define avatar
|
167
|
+
# @blog.comments.includes(:user) <= use includes(:user) to decrease queries count
|
168
|
+
# comment#user.avatar_url
|
169
|
+
|
170
|
+
# Simple way to define avatar url
|
171
|
+
def avatar_url
|
172
|
+
hash = Digest::MD5.hexdigest self.id.to_s
|
173
|
+
"http://www.gravatar.com/avatar/#{hash}?s=30&d=identicon"
|
174
|
+
end
|
175
|
+
|
176
|
+
# Define your filters for content
|
177
|
+
# Expample for: gem 'RedCloth', gem 'sanitize'
|
178
|
+
# your personal SmilesProcessor
|
179
|
+
def prepare_content
|
180
|
+
text = self.raw_content
|
181
|
+
text = RedCloth.new(text).to_html
|
182
|
+
text = SmilesProcessor.new(text)
|
183
|
+
text = Sanitize.clean(text, Sanitize::Config::RELAXED)
|
184
|
+
self.content = text
|
185
|
+
end
|
186
|
+
end
|
187
|
+
```
|
188
|
+
|
189
|
+
### IP, User Agent black lists
|
190
|
+
|
191
|
+
Models must looks like this:
|
192
|
+
|
193
|
+
```ruby
|
194
|
+
class IpBlackList < ActiveRecord::Base
|
195
|
+
include TheCommentsBlackUserAgent
|
196
|
+
end
|
197
|
+
|
198
|
+
class UserAgentBlackList < ActiveRecord::Base
|
199
|
+
include TheCommentsBlackIp
|
200
|
+
end
|
201
|
+
```
|
202
|
+
|
203
|
+
### Commentable controller
|
204
|
+
|
205
|
+
```ruby
|
206
|
+
class BlogsController < ApplicationController
|
207
|
+
include TheCommentsController::Cookies
|
208
|
+
include TheCommentsController::ViewToken
|
209
|
+
|
210
|
+
def show
|
211
|
+
@blog = Blog.where(id: params[:id]).with_states(:published).first
|
212
|
+
@comments = @blog.comments.with_state([:draft, :published]).nested_set
|
213
|
+
end
|
214
|
+
end
|
215
|
+
```
|
216
|
+
|
217
|
+
### View
|
218
|
+
|
219
|
+
```ruby
|
220
|
+
%h1= @blog.title
|
221
|
+
%p= @blog.content
|
222
|
+
|
223
|
+
= render partial: 'comments/tree', locals: { comments_tree: @comments, commentable: @blog }
|
224
|
+
```
|
225
|
+
|
226
|
+
## Configuration
|
227
|
+
|
228
|
+
**config/initializers/the_comments.rb**
|
229
|
+
|
230
|
+
```ruby
|
231
|
+
TheComments.configure do |config|
|
232
|
+
config.max_reply_depth = 3 # 3 by default
|
233
|
+
config.tolerance_time = 15 # 5 (sec) by default
|
234
|
+
config.empty_inputs = [:email, :message, :commentBody] # [:message] by default
|
235
|
+
end
|
236
|
+
```
|
237
|
+
|
238
|
+
* **max_reply_depth** - comments tree nesting by default
|
239
|
+
* **tolerance_time** - how many seconds user should spend on page, before comment send
|
240
|
+
* **empty_inputs** - names of hidden (via css) fields for spam detecting
|
241
|
+
|
242
|
+
## Contributing
|
243
|
+
|
244
|
+
1. Fork it
|
245
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
246
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
247
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
248
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
@@ -0,0 +1,104 @@
|
|
1
|
+
# ERROR MSG BUILDER
|
2
|
+
@error_text_builder = (errors) ->
|
3
|
+
error_msgs = ''
|
4
|
+
for error in errors
|
5
|
+
error_msgs += "<p><b>#{ error }</b></p>"
|
6
|
+
error_msgs
|
7
|
+
|
8
|
+
# FORM CLEANER
|
9
|
+
@clear_comment_form = ->
|
10
|
+
$('.error_notifier', '#new_comment, .comments_tree').hide()
|
11
|
+
$("input[name='comment[title]']").val('')
|
12
|
+
$("textarea[name='comment[raw_content]']").val('')
|
13
|
+
|
14
|
+
# NOTIFIER
|
15
|
+
@comments_error_notifier = (form, text) ->
|
16
|
+
form.children('.error_notifier').empty().hide().append(text).show()
|
17
|
+
|
18
|
+
# JUST HELPER
|
19
|
+
@unixsec = (t) -> Math.round(t.getTime() / 1000)
|
20
|
+
|
21
|
+
# HIGHTLIGHT ANCHOR
|
22
|
+
@highlight_anchor = ->
|
23
|
+
hash = document.location.hash
|
24
|
+
if hash.match('#comment_')
|
25
|
+
$(hash).addClass 'highlighted'
|
26
|
+
|
27
|
+
$ ->
|
28
|
+
window.tolerance_time_start = unixsec(new Date)
|
29
|
+
|
30
|
+
comment_forms = "#new_comment, .reply_comments_form"
|
31
|
+
tolerance_time = $('[data-comments-tolarance-time]').first().data('comments-tolarance-time')
|
32
|
+
|
33
|
+
# Button Click => AJAX Before Send
|
34
|
+
submits = '#new_comment input[type=submit], .reply_comments_form input[type=submit]'
|
35
|
+
$(document).on 'click', submits, (e) ->
|
36
|
+
button = $ e.target
|
37
|
+
form = button.parents('form').first()
|
38
|
+
time_diff = unixsec(new Date) - window.tolerance_time_start
|
39
|
+
|
40
|
+
if tolerance_time && (time_diff < tolerance_time)
|
41
|
+
delta = tolerance_time - time_diff
|
42
|
+
error_msgs = error_text_builder(["Please wait #{delta} secs"])
|
43
|
+
comments_error_notifier(form, error_msgs)
|
44
|
+
return false
|
45
|
+
|
46
|
+
$('.tolerance_time').val time_diff
|
47
|
+
button.prop 'disabled', true
|
48
|
+
true
|
49
|
+
|
50
|
+
# AJAX ERROR
|
51
|
+
$(document).on 'ajax:error', comment_forms, (request, response, status) ->
|
52
|
+
form = $ @
|
53
|
+
$('input[type=submit]', form).prop 'disabled', false
|
54
|
+
error_msgs = error_text_builder(["Server Error: #{response.status}"])
|
55
|
+
comments_error_notifier(form, error_msgs)
|
56
|
+
|
57
|
+
# COMMENT FORMS => SUCCESS
|
58
|
+
$(document).on 'ajax:success', comment_forms, (request, response, status) ->
|
59
|
+
form = $ @
|
60
|
+
$('input[type=submit]', form).prop 'disabled', false
|
61
|
+
|
62
|
+
if typeof(response) is 'string'
|
63
|
+
anchor = $(response).find('.comment').attr('id')
|
64
|
+
clear_comment_form()
|
65
|
+
form.hide()
|
66
|
+
$('.parent_id').val('')
|
67
|
+
$('#new_comment').fadeIn()
|
68
|
+
tree = form.parent().siblings('.nested_set')
|
69
|
+
tree = $('ol.comments_tree') if tree.length is 0
|
70
|
+
tree.append(response)
|
71
|
+
document.location.hash = anchor
|
72
|
+
else
|
73
|
+
error_msgs = error_text_builder(response.errors)
|
74
|
+
comments_error_notifier(form, error_msgs)
|
75
|
+
|
76
|
+
# NEW ROOT BUTTON
|
77
|
+
$(document).on 'click', '#new_root_comment', ->
|
78
|
+
$('.reply_comments_form').hide()
|
79
|
+
$('.parent_id').val('')
|
80
|
+
$('#new_comment').fadeIn()
|
81
|
+
false
|
82
|
+
|
83
|
+
# REPLY BUTTON
|
84
|
+
$(document).on 'click', '.reply_link', ->
|
85
|
+
link = $ @
|
86
|
+
comment = link.parent().parent().parent()
|
87
|
+
|
88
|
+
$(comment_forms).hide()
|
89
|
+
form = $('#new_comment').clone().removeAttr('id').addClass('reply_comments_form')
|
90
|
+
|
91
|
+
comment_id = comment.data('comment-id')
|
92
|
+
$('.parent_id', form).val comment_id
|
93
|
+
|
94
|
+
comment.siblings('.form_holder').html(form)
|
95
|
+
form.fadeIn()
|
96
|
+
false
|
97
|
+
|
98
|
+
$ ->
|
99
|
+
# ANCHOR HIGHLIGHT
|
100
|
+
highlight_anchor()
|
101
|
+
|
102
|
+
$(window).on 'hashchange', ->
|
103
|
+
$('.comment.highlighted').removeClass 'highlighted'
|
104
|
+
highlight_anchor()
|
@@ -0,0 +1,59 @@
|
|
1
|
+
$ ->
|
2
|
+
# CONTROLS
|
3
|
+
holder = $('.comments_list')
|
4
|
+
|
5
|
+
holder.on 'ajax:success', '.to_published', (request, response, status) ->
|
6
|
+
link = $ @
|
7
|
+
log link.parents('.item')
|
8
|
+
link.parents('.item').first().attr('class', 'item published')
|
9
|
+
|
10
|
+
holder.on 'ajax:success', '.to_draft', (request, response, status) ->
|
11
|
+
link = $ @
|
12
|
+
log link.parents('.item')
|
13
|
+
link.parents('.item').first().attr('class', 'item draft')
|
14
|
+
|
15
|
+
holder.on 'ajax:success', '.to_spam, .to_deleted', (request, response, status) ->
|
16
|
+
$(@).parents('li').first().hide()
|
17
|
+
|
18
|
+
$('.comments_tree').on 'ajax:success', '.delete', (request, response, status) ->
|
19
|
+
$(@).parents('li').first().hide()
|
20
|
+
|
21
|
+
# INPLACE EDIT
|
22
|
+
inplace_forms = '.comments_list .form form'
|
23
|
+
$(document).on 'ajax:success', inplace_forms, (request, response, status) ->
|
24
|
+
form = $ @
|
25
|
+
item = form.parents('.item')
|
26
|
+
item.children('.body').html(response).show()
|
27
|
+
item.children('.form').hide()
|
28
|
+
|
29
|
+
# FOR MANAGE SECTION
|
30
|
+
list = $('.comments_list')
|
31
|
+
|
32
|
+
list.on 'click', '.controls a.view', ->
|
33
|
+
form = $(@).parents('div.form')
|
34
|
+
body = form.siblings('.body')
|
35
|
+
body.show()
|
36
|
+
form.hide()
|
37
|
+
false
|
38
|
+
|
39
|
+
list.on 'click', '.controls a.edit', ->
|
40
|
+
body = $(@).parents('div.body')
|
41
|
+
form = body.siblings('.form')
|
42
|
+
body.hide()
|
43
|
+
form.show()
|
44
|
+
false
|
45
|
+
|
46
|
+
# BLACK LIST
|
47
|
+
holder = $('.black_list')
|
48
|
+
|
49
|
+
holder.on 'ajax:success', '.to_warning', (request, response, status) ->
|
50
|
+
link = $ @
|
51
|
+
li = link.parents('li').first()
|
52
|
+
li.attr 'class', 'warning'
|
53
|
+
li.find('.state').html 'warning'
|
54
|
+
|
55
|
+
holder.on 'ajax:success', '.to_banned', (request, response, status) ->
|
56
|
+
link = $ @
|
57
|
+
li = link.parents('li').first()
|
58
|
+
li.attr 'class', 'banned'
|
59
|
+
li.find('.state').html 'banned'
|
@@ -0,0 +1,251 @@
|
|
1
|
+
.comments_tree, .comments_list, .black_list{
|
2
|
+
font-family: Arial;
|
3
|
+
|
4
|
+
margin:0; padding:0;
|
5
|
+
*{ margin: 0; padding: 0; }
|
6
|
+
|
7
|
+
a{ text-decoration: none; }
|
8
|
+
a:hover{ text-decoration: underline; }
|
9
|
+
|
10
|
+
ol{
|
11
|
+
margin: 0;
|
12
|
+
padding: 0 0 0 20px;
|
13
|
+
list-style: none outside none;
|
14
|
+
}
|
15
|
+
li{
|
16
|
+
margin-bottom: 5px;
|
17
|
+
position: relative;
|
18
|
+
list-style: none outside none;
|
19
|
+
}
|
20
|
+
}
|
21
|
+
|
22
|
+
.comments, .comments_tree{
|
23
|
+
font-family: Arial;
|
24
|
+
font-size: 13px;
|
25
|
+
|
26
|
+
.error_notifier{
|
27
|
+
background-color: #F2DEDE;
|
28
|
+
border-color: #EED3D7;
|
29
|
+
color: #B94A48;
|
30
|
+
|
31
|
+
border-radius: 4px;
|
32
|
+
margin: 0 0 15px 0;
|
33
|
+
padding: 10px 10px 0 10px;
|
34
|
+
overflow: hidden;
|
35
|
+
|
36
|
+
p{ margin: 0 0 10px 0; }
|
37
|
+
}
|
38
|
+
form{
|
39
|
+
background: white;
|
40
|
+
border: 1px solid gray;
|
41
|
+
border-radius: 3px;
|
42
|
+
padding: 10px;
|
43
|
+
|
44
|
+
p{ margin: 0 0 10px 0; }
|
45
|
+
|
46
|
+
input[type=text]{
|
47
|
+
border: 1px solid gray;
|
48
|
+
padding: 4px;
|
49
|
+
width: 75%;
|
50
|
+
}
|
51
|
+
textarea{
|
52
|
+
border: 1px solid gray;
|
53
|
+
font-family: Arial;
|
54
|
+
font-size: 13px;
|
55
|
+
height: 150px;
|
56
|
+
padding: 4px;
|
57
|
+
width: 75%;
|
58
|
+
}
|
59
|
+
.trap{
|
60
|
+
margin: 0; padding: 0;
|
61
|
+
filter: alpha(opacity=0.001);
|
62
|
+
height: 0.1px;
|
63
|
+
opacity: 0.001;
|
64
|
+
overflow: hidden;
|
65
|
+
}
|
66
|
+
}
|
67
|
+
}
|
68
|
+
|
69
|
+
.comments_tree{
|
70
|
+
.nested_set{
|
71
|
+
border-left: 1px dotted lightGray;
|
72
|
+
}
|
73
|
+
|
74
|
+
li{
|
75
|
+
.comment.draft{
|
76
|
+
border: 1px solid gray;
|
77
|
+
background: lightGray;
|
78
|
+
padding: 10px;
|
79
|
+
}
|
80
|
+
}
|
81
|
+
|
82
|
+
.form_holder{ margin-left: 40px; }
|
83
|
+
|
84
|
+
.edit{
|
85
|
+
margin-bottom: 3px;
|
86
|
+
text-align:center;
|
87
|
+
line-height: 130%;
|
88
|
+
background: #336;
|
89
|
+
color: white;
|
90
|
+
padding: 1px;
|
91
|
+
}
|
92
|
+
|
93
|
+
.comment{
|
94
|
+
overflow: hidden; zoom: 1;
|
95
|
+
|
96
|
+
.userpic{
|
97
|
+
overflow: hidden; zoom: 1;
|
98
|
+
float: left;
|
99
|
+
width: 50px;
|
100
|
+
|
101
|
+
img{ margin-bottom: 10px; }
|
102
|
+
}
|
103
|
+
.userbar, .cbody{
|
104
|
+
margin: 0 0 5px 55px;
|
105
|
+
padding: 3px;
|
106
|
+
}
|
107
|
+
.userbar{
|
108
|
+
background: #ddd;
|
109
|
+
border-radius: 3px;
|
110
|
+
padding-left: 7px;
|
111
|
+
}
|
112
|
+
&.draft{
|
113
|
+
.userbar{ background: #EED3D7; }
|
114
|
+
.to_draft{ display: none; }
|
115
|
+
}
|
116
|
+
&.published{
|
117
|
+
.to_published{ display: none; }
|
118
|
+
}
|
119
|
+
.cbody{
|
120
|
+
border-bottom: 1px solid LightGray;
|
121
|
+
padding-bottom: 10px;
|
122
|
+
line-height: 135%;
|
123
|
+
margin-bottom: 0;
|
124
|
+
}
|
125
|
+
.reply{
|
126
|
+
margin: 0 0 5px 55px;
|
127
|
+
font-size: 12px;
|
128
|
+
}
|
129
|
+
}
|
130
|
+
|
131
|
+
.controls{
|
132
|
+
a{
|
133
|
+
font-size:11px;
|
134
|
+
display:block;
|
135
|
+
}
|
136
|
+
}
|
137
|
+
|
138
|
+
.comment{
|
139
|
+
margin-bottom: 10px;
|
140
|
+
|
141
|
+
&.published, &.draft{
|
142
|
+
margin-bottom: 10px;
|
143
|
+
border-radius: 3px;
|
144
|
+
padding: 5px;
|
145
|
+
}
|
146
|
+
&.highlighted{ border: 1px dashed #ff6633; }
|
147
|
+
|
148
|
+
}
|
149
|
+
|
150
|
+
.form_holder{
|
151
|
+
form{ margin: 10px 0; }
|
152
|
+
}
|
153
|
+
}
|
154
|
+
|
155
|
+
.new_comment{
|
156
|
+
.btn{
|
157
|
+
padding: 7px;
|
158
|
+
margin-top: 5px;
|
159
|
+
cursor: pointer;
|
160
|
+
}
|
161
|
+
}
|
162
|
+
|
163
|
+
.comments_list{
|
164
|
+
li{
|
165
|
+
margin-bottom: 20px;
|
166
|
+
|
167
|
+
.item{
|
168
|
+
border: 1px solid gray;
|
169
|
+
border-radius: 5px;
|
170
|
+
padding: 10px;
|
171
|
+
}
|
172
|
+
|
173
|
+
.draft{
|
174
|
+
border-left: 5px solid orange;
|
175
|
+
|
176
|
+
.controls{
|
177
|
+
a.to_draft{ display: none }
|
178
|
+
}
|
179
|
+
}
|
180
|
+
.published{
|
181
|
+
border-left: 5px solid green;
|
182
|
+
|
183
|
+
.controls{
|
184
|
+
a.to_published{ display: none; }
|
185
|
+
}
|
186
|
+
}
|
187
|
+
|
188
|
+
.deleted{
|
189
|
+
border-left: 5px solid red;
|
190
|
+
|
191
|
+
.controls{
|
192
|
+
a.to_deleted, a.to_spam{ display: none; }
|
193
|
+
}
|
194
|
+
}
|
195
|
+
|
196
|
+
.comment{
|
197
|
+
div{ margin: 0 0 10px 0; }
|
198
|
+
|
199
|
+
label{
|
200
|
+
width: 75px;
|
201
|
+
font-weight: bold;
|
202
|
+
display: inline-block;
|
203
|
+
}
|
204
|
+
input[type=text]{
|
205
|
+
width: 70%;
|
206
|
+
padding: 4px;
|
207
|
+
}
|
208
|
+
input[type=submit]{
|
209
|
+
padding: 5px;
|
210
|
+
cursor: pointer;
|
211
|
+
}
|
212
|
+
textarea{
|
213
|
+
width: 70%;
|
214
|
+
padding: 4px;
|
215
|
+
height: 150px;
|
216
|
+
}
|
217
|
+
.content{
|
218
|
+
line-height: 130%;
|
219
|
+
background: #ddd;
|
220
|
+
padding: 10px;
|
221
|
+
}
|
222
|
+
.commentable{
|
223
|
+
margin-bottom: 10px;
|
224
|
+
}
|
225
|
+
.controls{
|
226
|
+
background: lightgray;
|
227
|
+
padding: 3px;
|
228
|
+
a{ margin-right: 15px; }
|
229
|
+
}
|
230
|
+
}
|
231
|
+
}
|
232
|
+
}
|
233
|
+
|
234
|
+
.black_list{
|
235
|
+
li{
|
236
|
+
border: 1px solid gray;
|
237
|
+
border-radius: 5px;
|
238
|
+
padding: 10px;
|
239
|
+
&.warning{
|
240
|
+
border-left: 5px solid orange;
|
241
|
+
.to_warning{ display: none; }
|
242
|
+
}
|
243
|
+
&.banned{
|
244
|
+
border-left: 5px solid black;
|
245
|
+
.to_banned{ display: none; }
|
246
|
+
}
|
247
|
+
}
|
248
|
+
p{
|
249
|
+
margin-bottom: 10px;
|
250
|
+
}
|
251
|
+
}
|