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.
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
+ }