radiant-comments-extension 0.0.6
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 +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>
|