radiant-comments-extension 0.0.6
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +3 -0
- data/CHANGELOG +40 -0
- data/HELP_admin.markdown +52 -0
- data/HELP_designer.markdown +36 -0
- data/MIT-LICENSE +20 -0
- data/README.rdoc +53 -0
- data/Rakefile +133 -0
- data/TODO +6 -0
- data/VERSION +1 -0
- data/app/controllers/admin/comments_controller.rb +130 -0
- data/app/controllers/comments_controller.rb +59 -0
- data/app/helpers/admin/comments_helper.rb +7 -0
- data/app/models/akismet_spam_filter.rb +37 -0
- data/app/models/comment.rb +121 -0
- data/app/models/comment_mailer.rb +24 -0
- data/app/models/mollom_spam_filter.rb +52 -0
- data/app/models/simple_spam_filter.rb +38 -0
- data/app/models/spam_filter.rb +43 -0
- data/app/views/admin/comments/_comment.rhtml +34 -0
- data/app/views/admin/comments/_form.rhtml +36 -0
- data/app/views/admin/comments/edit.rhtml +5 -0
- data/app/views/admin/comments/index.rhtml +55 -0
- data/app/views/admin/pages/_comments.rhtml +0 -0
- data/app/views/admin/pages/_edit_comments_enabled.rhtml +8 -0
- data/app/views/admin/pages/_index_head_view_comments.rhtml +1 -0
- data/app/views/admin/pages/_index_view_comments.rhtml +11 -0
- data/app/views/comment_mailer/comment_notification.rhtml +21 -0
- data/app/views/comments/_comment.rhtml +1 -0
- data/app/views/comments/_form.rhtml +23 -0
- data/app/views/comments/_new.rhtml +5 -0
- data/autotest/discover.rb +3 -0
- data/comments_extension.rb +81 -0
- data/cucumber.yml +1 -0
- data/db/migrate/001_create_comments.rb +29 -0
- data/db/migrate/002_create_snippets.rb +115 -0
- data/db/migrate/003_change_filter_id_from_integer_to_string.rb +10 -0
- data/db/migrate/004_add_approval_columns.rb +13 -0
- data/db/migrate/005_add_mollomid_column.rb +11 -0
- data/db/migrate/006_move_config_to_migrations.rb +22 -0
- data/db/migrate/007_add_preference_for_simple_spamcheck.rb +12 -0
- data/features/support/env.rb +16 -0
- data/features/support/paths.rb +16 -0
- data/lib/akismet.rb +134 -0
- data/lib/comment_page_extensions.rb +41 -0
- data/lib/comment_tags.rb +338 -0
- data/lib/mollom.rb +246 -0
- data/lib/radiant-comments-extension.rb +0 -0
- data/lib/tasks/comments_extension_tasks.rake +68 -0
- data/public/images/admin/accept.png +0 -0
- data/public/images/admin/comment_edit.png +0 -0
- data/public/images/admin/comments.png +0 -0
- data/public/images/admin/comments_delete.png +0 -0
- data/public/images/admin/delete.png +0 -0
- data/public/images/admin/email.png +0 -0
- data/public/images/admin/error.png +0 -0
- data/public/images/admin/link.png +0 -0
- data/public/images/admin/page_white_edit.png +0 -0
- data/public/images/admin/table_save.png +0 -0
- data/public/images/admin/tick.png +0 -0
- data/public/stylesheets/admin/comments.css +41 -0
- data/radiant-comments-extension.gemspec +133 -0
- data/spec/controllers/admin/comments_controller_spec.rb +57 -0
- data/spec/controllers/admin/comments_routing_spec.rb +43 -0
- data/spec/controllers/page_postback_spec.rb +51 -0
- data/spec/datasets/comments_dataset.rb +7 -0
- data/spec/models/akismet_spam_filter_spec.rb +61 -0
- data/spec/models/comment_spec.rb +148 -0
- data/spec/models/comment_tags_spec.rb +55 -0
- data/spec/models/mollom_spam_filter_spec.rb +103 -0
- data/spec/models/simple_spam_filter_spec.rb +44 -0
- data/spec/models/spam_filter_spec.rb +38 -0
- data/spec/spec.opts +6 -0
- data/spec/spec_helper.rb +36 -0
- data/test/fixtures/users.yml +6 -0
- data/test/integration/comment_enabling_test.rb +18 -0
- data/test/test_helper.rb +24 -0
- data/test/unit/comment_test.rb +52 -0
- metadata +177 -0
@@ -0,0 +1,59 @@
|
|
1
|
+
class CommentsController < ApplicationController
|
2
|
+
|
3
|
+
no_login_required
|
4
|
+
skip_before_filter :verify_authenticity_token
|
5
|
+
before_filter :find_page
|
6
|
+
before_filter :set_host
|
7
|
+
|
8
|
+
def index
|
9
|
+
@page.selected_comment = @page.comments.find_by_id(flash[:selected_comment])
|
10
|
+
@page.request = request
|
11
|
+
render :text => @page.render
|
12
|
+
end
|
13
|
+
|
14
|
+
def create
|
15
|
+
comment = @page.comments.build(params[:comment])
|
16
|
+
comment.request = request
|
17
|
+
comment.request = @page.request = request
|
18
|
+
comment.save!
|
19
|
+
|
20
|
+
clear_single_page_cache(comment)
|
21
|
+
if Radiant::Config['comments.notification'] == "true"
|
22
|
+
if comment.approved? || Radiant::Config['comments.notify_unapproved'] == "true"
|
23
|
+
CommentMailer.deliver_comment_notification(comment)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
flash[:selected_comment] = comment.id
|
28
|
+
redirect_to "#{@page.url}comments#comment-#{comment.id}"
|
29
|
+
rescue ActiveRecord::RecordInvalid
|
30
|
+
@page.last_comment = comment
|
31
|
+
render :text => @page.render
|
32
|
+
# rescue Comments::MollomUnsure
|
33
|
+
#flash, en render :text => @page.render
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def find_page
|
39
|
+
url = params[:url]
|
40
|
+
url.shift if defined?(SiteLanguage) && SiteLanguage.count > 1
|
41
|
+
@page = Page.find_by_url(url.join("/"))
|
42
|
+
end
|
43
|
+
|
44
|
+
def set_host
|
45
|
+
CommentMailer.default_url_options[:host] = request.host_with_port
|
46
|
+
end
|
47
|
+
|
48
|
+
def clear_single_page_cache(comment)
|
49
|
+
if comment && comment.page
|
50
|
+
unless defined?(ResponseCache)
|
51
|
+
Radiant::Cache::EntityStore.new.purge(comment.page.url)
|
52
|
+
Radiant::Cache::MetaStore.new.purge(comment.page.url)
|
53
|
+
else
|
54
|
+
ResponseCache.instance.clear
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
class AkismetSpamFilter < SpamFilter
|
2
|
+
def message
|
3
|
+
'Protected by <a href="http://akismet.com/">Akismet</a>'
|
4
|
+
end
|
5
|
+
|
6
|
+
def configured?
|
7
|
+
!Radiant::Config['comments.akismet_key'].blank? &&
|
8
|
+
!Radiant::Config['comments.akismet_url'].blank?
|
9
|
+
end
|
10
|
+
|
11
|
+
def approved?(comment)
|
12
|
+
(akismet.valid? && ham?(comment)) || raise(SpamFilter::Spam)
|
13
|
+
rescue
|
14
|
+
# Spam and anything raised by Net::HTTP, e.g. Errno, Timeout stuff
|
15
|
+
false
|
16
|
+
end
|
17
|
+
|
18
|
+
def akismet
|
19
|
+
@akismet ||= Akismet.new(Radiant::Config['comments.akismet_key'], Radiant::Config['comments.akismet_url'])
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
def ham?(comment)
|
24
|
+
!akismet.commentCheck(
|
25
|
+
comment.author_ip, # remote IP
|
26
|
+
comment.user_agent, # user agent
|
27
|
+
comment.referrer, # http referer
|
28
|
+
comment.page.url, # permalink
|
29
|
+
'comment', # comment type
|
30
|
+
comment.author, # author name
|
31
|
+
comment.author_email, # author email
|
32
|
+
comment.author_url, # author url
|
33
|
+
comment.content, # comment text
|
34
|
+
{} # other
|
35
|
+
)
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,121 @@
|
|
1
|
+
require 'digest/md5'
|
2
|
+
class Comment < ActiveRecord::Base
|
3
|
+
belongs_to :page, :counter_cache => true
|
4
|
+
|
5
|
+
named_scope :unapproved, :conditions => {:approved_at => nil}
|
6
|
+
named_scope :approved, :conditions => 'approved_at IS NOT NULL'
|
7
|
+
named_scope :recent, :order => 'created_at DESC'
|
8
|
+
|
9
|
+
validate :check_for_spam
|
10
|
+
validates_presence_of :author, :author_email, :content
|
11
|
+
|
12
|
+
before_save :auto_approve
|
13
|
+
before_save :apply_filter
|
14
|
+
before_save :canonicalize_url
|
15
|
+
|
16
|
+
attr_accessor :valid_spam_answer, :spam_answer
|
17
|
+
attr_accessible :author, :author_email, :author_url, :filter_id, :content, :valid_spam_answer, :spam_answer
|
18
|
+
|
19
|
+
def self.per_page
|
20
|
+
count = Radiant::Config['comments.per_page'].to_i.abs
|
21
|
+
count > 0 ? count : 50
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.spam_filter
|
25
|
+
@spam_filter ||= SpamFilter.select
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.simple_spam_filter_enabled?
|
29
|
+
spam_filter == SimpleSpamFilter && spam_filter.required?
|
30
|
+
end
|
31
|
+
|
32
|
+
def request=(request)
|
33
|
+
self.author_ip = request.remote_ip
|
34
|
+
self.user_agent = request.env['HTTP_USER_AGENT']
|
35
|
+
self.referrer = request.env['HTTP_REFERER']
|
36
|
+
end
|
37
|
+
|
38
|
+
def auto_approve?
|
39
|
+
Radiant::Config['comments.auto_approve'] == "true" && spam_filter.approved?(self)
|
40
|
+
end
|
41
|
+
|
42
|
+
def unapproved?
|
43
|
+
!approved?
|
44
|
+
end
|
45
|
+
|
46
|
+
def approved?
|
47
|
+
!approved_at.nil?
|
48
|
+
end
|
49
|
+
|
50
|
+
def ap_status
|
51
|
+
if approved?
|
52
|
+
"approved"
|
53
|
+
else
|
54
|
+
"unapproved"
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def approve!
|
59
|
+
self.update_attribute(:approved_at, Time.now)
|
60
|
+
end
|
61
|
+
|
62
|
+
def unapprove!
|
63
|
+
self.update_attribute(:approved_at, nil)
|
64
|
+
spam_filter.spam!(self)
|
65
|
+
end
|
66
|
+
|
67
|
+
def check_for_spam
|
68
|
+
spam_filter && spam_filter.valid?(self)
|
69
|
+
end
|
70
|
+
|
71
|
+
private
|
72
|
+
def spam_filter
|
73
|
+
self.class.spam_filter
|
74
|
+
end
|
75
|
+
|
76
|
+
def auto_approve
|
77
|
+
self.approved_at = Time.now if auto_approve?
|
78
|
+
end
|
79
|
+
|
80
|
+
def apply_filter
|
81
|
+
cleaner_type = defined?(COMMENT_SANITIZE_OPTION) ? COMMENT_SANITIZE_OPTION : Sanitize::Config::RELAXED
|
82
|
+
sanitized_content = Sanitize.clean(content, cleaner_type)
|
83
|
+
self.content_html = filter.filter(sanitized_content)
|
84
|
+
end
|
85
|
+
|
86
|
+
def canonicalize_url
|
87
|
+
self.author_url = CGI.escapeHTML(author_url =~ /\Ahttps?:\/\//i ? author_url : "http://#{author_url}") unless author_url.blank?
|
88
|
+
end
|
89
|
+
|
90
|
+
def filter
|
91
|
+
if filtering_enabled? && filter_from_form
|
92
|
+
filter_from_form
|
93
|
+
else
|
94
|
+
SimpleFilter.new
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def filter_from_form
|
99
|
+
unless filter_id.blank?
|
100
|
+
TextFilter.descendants.find { |f| f.filter_name == filter_id }
|
101
|
+
else
|
102
|
+
nil
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def filtering_enabled?
|
107
|
+
Radiant::Config['comments.filters_enabled'] == "true"
|
108
|
+
end
|
109
|
+
|
110
|
+
class SimpleFilter
|
111
|
+
include ERB::Util
|
112
|
+
include ActionView::Helpers::TextHelper
|
113
|
+
include ActionView::Helpers::TagHelper
|
114
|
+
|
115
|
+
def filter(content)
|
116
|
+
simple_format(escape_once(content))
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
class AntispamWarning < StandardError; end
|
121
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
class CommentMailer < ActionMailer::Base
|
2
|
+
def comment_notification(comment, sent_at = Time.now)
|
3
|
+
notify_creator_config = Radiant::Config['comments.notify_creator']
|
4
|
+
notify_updater_config = Radiant::Config['comments.notify_updater']
|
5
|
+
notification_to_config = Radiant::Config['comments.notification_to']
|
6
|
+
|
7
|
+
receivers = []
|
8
|
+
receivers << notification_to_config unless notification_to_config.blank?
|
9
|
+
receivers << comment.page.created_by.email unless notify_creator_config == "false"
|
10
|
+
if notify_updater_config == "true" && comment.page.updated_by != comment.page.created_by
|
11
|
+
receivers << comment.page.updated_by.email
|
12
|
+
end
|
13
|
+
|
14
|
+
page_url = root_url(:host => default_url_options[:host], :port => default_url_options[:port])[0..-2] + comment.page.url
|
15
|
+
site_name = Radiant::Config['comments.notification_site_name']
|
16
|
+
|
17
|
+
subject "[#{site_name}] New #{comment.ap_status} comment posted"
|
18
|
+
recipients receivers.join(',')
|
19
|
+
from Radiant::Config['comments.notification_from']
|
20
|
+
sent_on sent_at
|
21
|
+
|
22
|
+
body :site_name => site_name, :comment => comment, :page_url => page_url
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
class MollomSpamFilter < SpamFilter
|
2
|
+
def message
|
3
|
+
'Protected by <a href="http://mollom.com">Mollom</a>'
|
4
|
+
end
|
5
|
+
|
6
|
+
def configured?
|
7
|
+
!Radiant::Config['comments.mollom_privatekey'].blank? &&
|
8
|
+
!Radiant::Config['comments.mollom_publickey'].blank?
|
9
|
+
end
|
10
|
+
|
11
|
+
def approved?(comment)
|
12
|
+
(mollom.key_ok? && ham?(comment)) || raise(SpamFilter::Spam)
|
13
|
+
rescue Mollom::Error, SpamFilter::Spam
|
14
|
+
false
|
15
|
+
end
|
16
|
+
|
17
|
+
def spam!(comment)
|
18
|
+
begin
|
19
|
+
if mollom.key_ok? and !comment.mollom_id.empty?
|
20
|
+
mollom.send_feedback :session_id => comment.mollom_id, :feedback => 'spam'
|
21
|
+
end
|
22
|
+
rescue Mollom::Error => e
|
23
|
+
raise Comment::AntispamWarning.new(e.to_s)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def mollom
|
28
|
+
@mollom ||= Mollom.new(:private_key => Radiant::Config['comments.mollom_privatekey'], :public_key => Radiant::Config['comments.mollom_publickey']).tap do |m|
|
29
|
+
unless Rails.cache.read('MOLLOM_SERVER_CACHE').blank?
|
30
|
+
m.server_list = YAML::load(Rails.cache.read('MOLLOM_SERVER_CACHE'))
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
def ham?(comment)
|
37
|
+
response = mollom.check_content(
|
38
|
+
:author_name => comment.author, # author name
|
39
|
+
:author_mail => comment.author_email, # author email
|
40
|
+
:author_url => comment.author_url, # author url
|
41
|
+
:post_body => comment.content # comment text
|
42
|
+
)
|
43
|
+
comment.mollom_id = response.session_id
|
44
|
+
save_mollom_servers
|
45
|
+
response.ham?
|
46
|
+
end
|
47
|
+
|
48
|
+
def save_mollom_servers
|
49
|
+
Rails.cache.write('MOLLOM_SERVER_CACHE', mollom.server_list.to_yaml) if mollom.key_ok?
|
50
|
+
rescue Mollom::Error #TODO: something with this error...
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# A simple challenge-response spam filter
|
2
|
+
class SimpleSpamFilter < SpamFilter
|
3
|
+
def message
|
4
|
+
if required?
|
5
|
+
'Comments are protected from spam by a simple challenge/response field. For more robust spam filtering, try <a href="http://mollom.com">Mollom</a> or <a href="http://akismet.com/">Akismet</a>.'
|
6
|
+
else
|
7
|
+
'You have 3 built-in options for spam protection although currently comments are not automatically protected. Install <a href="http://mollom.com">Mollom</a> or <a href="http://akismet.com/">Akismet</a> to protect against comment spam through an external service, or use the <r:comments:spam_answer_tag />. Instructions may be found in the README.'
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def configured?
|
12
|
+
true
|
13
|
+
end
|
14
|
+
|
15
|
+
# Instead of filtering at the approval stage, the simple spam filter requires
|
16
|
+
# the user to give the correct answer before saving the record.
|
17
|
+
def valid?(comment)
|
18
|
+
if !required? || comment.valid_spam_answer == hashed_spam_answer(comment)
|
19
|
+
true
|
20
|
+
else
|
21
|
+
comment.errors.add :spam_answer, "is not correct."
|
22
|
+
false
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def approved?(comment)
|
27
|
+
true
|
28
|
+
end
|
29
|
+
|
30
|
+
def required?
|
31
|
+
Radiant::Config['comments.simple_spam_filter_required?']
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
def hashed_spam_answer(comment)
|
36
|
+
Digest::MD5.hexdigest(comment.spam_answer.to_s.to_slug)
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
unless Array.instance_methods.include?('without')
|
2
|
+
class Array
|
3
|
+
def without(object)
|
4
|
+
self.dup.tap do |new_array|
|
5
|
+
new_array.delete(object)
|
6
|
+
end
|
7
|
+
end
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
class SpamFilter
|
12
|
+
include Simpleton
|
13
|
+
|
14
|
+
def message
|
15
|
+
raise NotImplementedError, 'spam filter subclasses should implement this method'
|
16
|
+
end
|
17
|
+
|
18
|
+
def select
|
19
|
+
# Make sure Simple filter comes last, as a fallback
|
20
|
+
filters = SpamFilter.descendants.without(SimpleSpamFilter) << SimpleSpamFilter
|
21
|
+
filters.find {|filter| filter.try(:configured?) }
|
22
|
+
end
|
23
|
+
|
24
|
+
def approved?(comment)
|
25
|
+
raise NotImplementedError, "spam filter subclasses should implement this method"
|
26
|
+
end
|
27
|
+
|
28
|
+
def spam!(comment)
|
29
|
+
# This is only implemented in filters that accept feedback like Mollom
|
30
|
+
end
|
31
|
+
|
32
|
+
def configured?
|
33
|
+
false
|
34
|
+
end
|
35
|
+
|
36
|
+
# By default, let comments save to the database. Then they can be approved
|
37
|
+
# manually or auto-approved by the filter.
|
38
|
+
def valid?(comment)
|
39
|
+
true
|
40
|
+
end
|
41
|
+
|
42
|
+
class Spam < ::StandardError; end
|
43
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
<tr id="<%= dom_id(comment) %>" class="comment<%= " approved" if comment.approved? %>">
|
2
|
+
<td class="content" <% if comment.content.size >= 70 -%>title="Click to toggle complete text"<% end-%>>
|
3
|
+
<blockquote class="short"><%= escape_once(truncate(comment.content, :length => 70)) %></blockquote>
|
4
|
+
<% if comment.content.size >= 70 %>
|
5
|
+
<blockquote class="expanded" style="display:none"><%= escape_once(comment.content) %></blockquote>
|
6
|
+
<% end %>
|
7
|
+
</td>
|
8
|
+
<td class="date">
|
9
|
+
<%= comment.created_at.strftime("%b %e, %Y at %I:%M%p") %>
|
10
|
+
</td>
|
11
|
+
<td class="author">
|
12
|
+
by <%= escape_once(comment.author) %>
|
13
|
+
<% unless comment.author_email.blank? %><%= mail_to(comment.author_email, image_tag("admin/email.png"))%><% end %>
|
14
|
+
<% unless comment.author_url.blank? %><%= link_to(image_tag("admin/link.png"), comment.author_url) %><% end %>
|
15
|
+
</td>
|
16
|
+
<% unless @page %>
|
17
|
+
<td class="page">
|
18
|
+
on
|
19
|
+
<%= link_to truncate(comment.page.title, :length => 40), comment.page.url, :class => 'view-page' %>
|
20
|
+
<%= link_to image_tag("admin/page_white_edit.png"),
|
21
|
+
edit_admin_page_path(comment.page), :title => "Edit page" %>
|
22
|
+
</td>
|
23
|
+
<% end %>
|
24
|
+
<td class="controls">
|
25
|
+
<%= link_to(image_tag('admin/accept.png'), approve_admin_comment_path(comment),
|
26
|
+
:title => "Approve comment", :method => :put) unless comment.approved? %>
|
27
|
+
<%= link_to(image_tag('admin/error.png'), unapprove_admin_comment_path(comment),
|
28
|
+
:method => :put, :title => "Unapprove comment") if comment.approved? %>
|
29
|
+
<%= link_to image_tag("admin/delete.png"), admin_comment_path(comment),
|
30
|
+
:method => :delete, :confirm => "Are you sure you want to delete this comment?", :title => "Delete comment" %>
|
31
|
+
<%= link_to image_tag("admin/comment_edit.png"), edit_admin_comment_path(comment),
|
32
|
+
:title => "Edit comment" %>
|
33
|
+
</td>
|
34
|
+
</tr>
|
@@ -0,0 +1,36 @@
|
|
1
|
+
<% if Comment.spam_filter == SimpleSpamFilter %>
|
2
|
+
<div style="display:none">
|
3
|
+
<%= f.hidden_field :spam_answer, :value => "hemidemisemiquaver" %>
|
4
|
+
<%= f.hidden_field :valid_spam_answer, :value => Digest::MD5.hexdigest("hemidemisemiquaver") %>
|
5
|
+
</div>
|
6
|
+
<% end %>
|
7
|
+
<div id="comment_form_container" class="form-area">
|
8
|
+
<div class="row">
|
9
|
+
<p>
|
10
|
+
<label for="comment_author">Author</label><br/>
|
11
|
+
<%= f.text_field :author %>
|
12
|
+
</p>
|
13
|
+
<p>
|
14
|
+
<label for="comment_author_url">URL</label><br/>
|
15
|
+
<%= f.text_field :author_url %>
|
16
|
+
</p>
|
17
|
+
</div>
|
18
|
+
<br style="clear: both" />
|
19
|
+
<p>
|
20
|
+
<label for="comment_author_email">Email</label><br/>
|
21
|
+
<%= f.text_field :author_email %>
|
22
|
+
</p>
|
23
|
+
<p>
|
24
|
+
<label for="comment_content">Comment</label><br />
|
25
|
+
<%= f.text_area :content, :rows => 10, :cols => 72, :class => "textarea"%>
|
26
|
+
</p>
|
27
|
+
<p>
|
28
|
+
<label for="comment_filter_id">Filter</label>
|
29
|
+
<%= f.select :filter_id, [['<none>', '']] + TextFilter.descendants.map { |s| s.filter_name }.sort %>
|
30
|
+
</p>
|
31
|
+
</div>
|
32
|
+
<p class="buttons">
|
33
|
+
<%= save_model_button(@comment) %>
|
34
|
+
or
|
35
|
+
<%= link_to "Cancel", :back %>
|
36
|
+
</p>
|