the_comments 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. data/.gitignore +18 -0
  2. data/Gemfile +4 -0
  3. data/LICENSE.txt +22 -0
  4. data/README.md +248 -0
  5. data/Rakefile +1 -0
  6. data/app/assets/javascripts/the_comments.js.coffee +104 -0
  7. data/app/assets/javascripts/the_comments_manage.js.coffee +59 -0
  8. data/app/assets/stylesheets/the_comments.css.scss +251 -0
  9. data/app/controllers/concerns/the_comments_controller.rb +227 -0
  10. data/app/controllers/concerns/the_comments_ip_controller.rb +17 -0
  11. data/app/controllers/concerns/the_comments_user_agent_controller.rb +17 -0
  12. data/app/helpers/render_comments_tree_helper.rb +113 -0
  13. data/app/models/concerns/the_comments_base.rb +64 -0
  14. data/app/models/concerns/the_comments_black_ip.rb +10 -0
  15. data/app/models/concerns/the_comments_black_user_agent.rb +10 -0
  16. data/app/models/concerns/the_comments_commentable.rb +50 -0
  17. data/app/models/concerns/the_comments_states.rb +64 -0
  18. data/app/models/concerns/the_comments_user.rb +25 -0
  19. data/app/views/ip_black_lists/index.html.haml +17 -0
  20. data/app/views/the_comments/_comment.html.haml +1 -0
  21. data/app/views/the_comments/_comment_body.html.haml +30 -0
  22. data/app/views/the_comments/_form.html.haml +25 -0
  23. data/app/views/the_comments/_manage_controls.html.haml +4 -0
  24. data/app/views/the_comments/_tree.html.haml +4 -0
  25. data/app/views/the_comments/index.html.haml +19 -0
  26. data/app/views/the_comments/manage.html.haml +29 -0
  27. data/app/views/user_agent_black_lists/index.html.haml +17 -0
  28. data/config/locales/en.yml +43 -0
  29. data/config/routes.rb +24 -0
  30. data/db/migrate/20130101010101_create_comments.rb +81 -0
  31. data/lib/generators/the_comments/USAGE +29 -0
  32. data/lib/generators/the_comments/templates/comments_controller.rb +25 -0
  33. data/lib/generators/the_comments/templates/ip_black_lists_controller.rb +10 -0
  34. data/lib/generators/the_comments/templates/the_comments.rb +7 -0
  35. data/lib/generators/the_comments/templates/user_agent_black_lists_controller.rb +10 -0
  36. data/lib/generators/the_comments/the_comments_generator.rb +32 -0
  37. data/lib/generators/the_comments/views_generator.rb +41 -0
  38. data/lib/the_comments/config.rb +21 -0
  39. data/lib/the_comments/version.rb +3 -0
  40. data/lib/the_comments.rb +10 -0
  41. data/the_comments.gemspec +23 -0
  42. data/the_comments.jpg +0 -0
  43. metadata +120 -0
data/.gitignore ADDED
@@ -0,0 +1,18 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ .rvmrc
7
+ Gemfile.lock
8
+ InstalledFiles
9
+ _yardoc
10
+ coverage
11
+ doc/
12
+ lib/bundler/man
13
+ pkg
14
+ rdoc
15
+ spec/reports
16
+ test/tmp
17
+ test/version_tmp
18
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in the_comments.gemspec
4
+ gemspec
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
+ ![TheComments](https://raw.github.com/open-cook/the_comments/master/the_comments.jpg)
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
+ }