bumble 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (125) hide show
  1. data/.gitignore +16 -0
  2. data/.rvmrc +1 -0
  3. data/Gemfile +4 -0
  4. data/Rakefile +1 -0
  5. data/Readme.mdown +8 -0
  6. data/app/controllers/bumble_controller.rb +45 -0
  7. data/app/controllers/comments_controller.rb +99 -0
  8. data/app/controllers/password_resets_controller.rb +34 -0
  9. data/app/controllers/posts_controller.rb +103 -0
  10. data/app/controllers/user_sessions_controller.rb +22 -0
  11. data/app/controllers/users_controller.rb +60 -0
  12. data/app/helpers/bumble_helper.rb +50 -0
  13. data/app/models/asset.rb +57 -0
  14. data/app/models/comment.rb +78 -0
  15. data/app/models/notifier.rb +35 -0
  16. data/app/models/post.rb +57 -0
  17. data/app/models/posts/blog.rb +3 -0
  18. data/app/models/posts/code.rb +3 -0
  19. data/app/models/posts/image.rb +3 -0
  20. data/app/models/posts/link.rb +7 -0
  21. data/app/models/posts/quote.rb +3 -0
  22. data/app/models/posts/video.rb +4 -0
  23. data/app/models/user.rb +35 -0
  24. data/app/models/user_session.rb +2 -0
  25. data/app/views/comments/_comment.html.haml +14 -0
  26. data/app/views/comments/_form.html.haml +9 -0
  27. data/app/views/comments/edit.html.haml +7 -0
  28. data/app/views/comments/index.atom.builder +15 -0
  29. data/app/views/comments/new.html.haml +7 -0
  30. data/app/views/layouts/_sidebar.html.haml +20 -0
  31. data/app/views/layouts/bumble.html.haml +35 -0
  32. data/app/views/notifier/activation_confirmation.erb +5 -0
  33. data/app/views/notifier/activation_instructions.erb +5 -0
  34. data/app/views/notifier/new_comment_alert.erb +8 -0
  35. data/app/views/notifier/password_reset_instructions.erb +8 -0
  36. data/app/views/password_resets/edit.html.haml +10 -0
  37. data/app/views/password_resets/new.html.haml +7 -0
  38. data/app/views/posts/_form.html.haml +10 -0
  39. data/app/views/posts/_post.html.haml +16 -0
  40. data/app/views/posts/_preview.html.haml +8 -0
  41. data/app/views/posts/edit.html.haml +10 -0
  42. data/app/views/posts/forms/_blog.html.haml +4 -0
  43. data/app/views/posts/forms/_code.html.haml +6 -0
  44. data/app/views/posts/forms/_image.html.haml +9 -0
  45. data/app/views/posts/forms/_link.html.haml +6 -0
  46. data/app/views/posts/forms/_quote.html.haml +4 -0
  47. data/app/views/posts/forms/_video.html.haml +6 -0
  48. data/app/views/posts/index.atom.builder +15 -0
  49. data/app/views/posts/index.html.haml +21 -0
  50. data/app/views/posts/new.html.haml +8 -0
  51. data/app/views/posts/search.html.haml +27 -0
  52. data/app/views/posts/show.html.haml +22 -0
  53. data/app/views/posts/types/_blog.html.haml +4 -0
  54. data/app/views/posts/types/_code.html.haml +7 -0
  55. data/app/views/posts/types/_image.html.haml +3 -0
  56. data/app/views/posts/types/_link.html.haml +3 -0
  57. data/app/views/posts/types/_quote.html.haml +2 -0
  58. data/app/views/posts/types/_video.html.haml +5 -0
  59. data/app/views/user_sessions/new.html.haml +10 -0
  60. data/app/views/users/_form.html.haml +12 -0
  61. data/app/views/users/_user.html.haml +3 -0
  62. data/app/views/users/delete.html.haml +5 -0
  63. data/app/views/users/edit.html.haml +8 -0
  64. data/app/views/users/index.html.haml +3 -0
  65. data/app/views/users/new.html.haml +7 -0
  66. data/app/views/users/show.html.haml +6 -0
  67. data/bumble.gemspec +31 -0
  68. data/config/initializers/bumble.rb +9 -0
  69. data/config/initializers/haml.rb +2 -0
  70. data/config/initializers/html5_forms.rb +163 -0
  71. data/config/initializers/inflections.rb +10 -0
  72. data/config/initializers/mime_types.rb +5 -0
  73. data/config/initializers/will_paginate.rb +3 -0
  74. data/config/routes.rb +29 -0
  75. data/lib/bumble.rb +18 -0
  76. data/lib/bumble/version.rb +3 -0
  77. data/lib/generators/bumble/assets_generator.rb +10 -0
  78. data/lib/generators/bumble/migrations_generator.rb +40 -0
  79. data/lib/generators/bumble/templates/assets/images/.gitignore +0 -0
  80. data/lib/generators/bumble/templates/assets/images/close.png +0 -0
  81. data/lib/generators/bumble/templates/assets/images/email.png +0 -0
  82. data/lib/generators/bumble/templates/assets/images/feed.png +0 -0
  83. data/lib/generators/bumble/templates/assets/images/image.png +0 -0
  84. data/lib/generators/bumble/templates/assets/images/loading.gif +0 -0
  85. data/lib/generators/bumble/templates/assets/images/preview.png +0 -0
  86. data/lib/generators/bumble/templates/assets/images/shortcut.png +0 -0
  87. data/lib/generators/bumble/templates/assets/images/youtube.png +0 -0
  88. data/lib/generators/bumble/templates/assets/javascripts/admin.js +95 -0
  89. data/lib/generators/bumble/templates/assets/javascripts/bumble.js +76 -0
  90. data/lib/generators/bumble/templates/assets/javascripts/iphone.js +7 -0
  91. data/lib/generators/bumble/templates/assets/javascripts/jquery.autogrow.js +37 -0
  92. data/lib/generators/bumble/templates/assets/javascripts/jquery.cookie.js +96 -0
  93. data/lib/generators/bumble/templates/assets/javascripts/jquery.form.js +665 -0
  94. data/lib/generators/bumble/templates/assets/javascripts/jquery.hint.js +44 -0
  95. data/lib/generators/bumble/templates/assets/javascripts/jquery.jgrow.js +85 -0
  96. data/lib/generators/bumble/templates/assets/javascripts/jquery.js +154 -0
  97. data/lib/generators/bumble/templates/assets/javascripts/jquery.ui.core.js +18 -0
  98. data/lib/generators/bumble/templates/assets/javascripts/jquery.ui.tabs.js +13 -0
  99. data/lib/generators/bumble/templates/assets/javascripts/jquery.ui.widget.js +18 -0
  100. data/lib/generators/bumble/templates/assets/javascripts/jquery.validate.js +16 -0
  101. data/lib/generators/bumble/templates/assets/javascripts/modernizr.js +13 -0
  102. data/lib/generators/bumble/templates/assets/javascripts/ui.core.js +289 -0
  103. data/lib/generators/bumble/templates/assets/javascripts/ui.tabs.js +593 -0
  104. data/lib/generators/bumble/templates/migrations/add_approved_to_comments.rb +15 -0
  105. data/lib/generators/bumble/templates/migrations/add_delta_to_posts.rb +9 -0
  106. data/lib/generators/bumble/templates/migrations/add_missing_indexes.rb +24 -0
  107. data/lib/generators/bumble/templates/migrations/add_url_to_users.rb +9 -0
  108. data/lib/generators/bumble/templates/migrations/create_assets.rb +18 -0
  109. data/lib/generators/bumble/templates/migrations/create_comments.rb +19 -0
  110. data/lib/generators/bumble/templates/migrations/create_posts.rb +25 -0
  111. data/lib/generators/bumble/templates/migrations/create_users.rb +28 -0
  112. data/lib/generators/bumble/templates/migrations/full_text_search.rb +13 -0
  113. data/public/stylesheets/sass/bumble.sass +423 -0
  114. data/public/stylesheets/sass/iphone.sass +79 -0
  115. data/public/stylesheets/sass/print.sass +2 -0
  116. data/public/stylesheets/sass/reset.sass +49 -0
  117. data/test/factories.rb +28 -0
  118. data/test/functional/comments_controller_test.rb +177 -0
  119. data/test/functional/password_resets_controller_test.rb +61 -0
  120. data/test/functional/posts_controller_test.rb +181 -0
  121. data/test/test_helper.rb +113 -0
  122. data/test/unit/comment_test.rb +18 -0
  123. data/test/unit/post_test.rb +18 -0
  124. data/test/unit/user_test.rb +37 -0
  125. metadata +403 -0
@@ -0,0 +1,57 @@
1
+ require 'open-uri'
2
+
3
+ class Asset < ActiveRecord::Base
4
+
5
+ belongs_to :post
6
+
7
+ attr_accessible :attachment, :attachment_url, :attachment_remote_url
8
+ attr_accessor :attachment_url
9
+
10
+ has_attached_file :attachment,
11
+ :storage => :s3,
12
+ :path => ":basename:normalized_style.:extension",
13
+ :default_style => :original,
14
+ :bucket => 'bumble_production',
15
+ :s3_credentials => { :access_key_id => ENV['s3_access_key_id'], :secret_access_key => ENV['s3_secret_access_key'] },
16
+ :s3_headers => { 'Cache-Control' => 'max-age=315576000', 'Expires' => 10.years.from_now.httpdate },
17
+ :styles => { :thumb => '500x500>'}
18
+
19
+
20
+ Paperclip.interpolates :normalized_style do |attachment, style|
21
+ "_#{style}" if attachment.instance.image? && style != :original
22
+ end
23
+
24
+ before_validation :download_remote_file, :if => :attachment_url_provided?
25
+
26
+ validates_presence_of :attachment_file_name
27
+ validates_presence_of :attachment_remote_url, :if => :attachment_url_provided?, :message => 'is invalid or inaccessible'
28
+ validates_uniqueness_of :attachment_file_name
29
+
30
+ before_post_process :image? # bypass image processing if not an image
31
+
32
+ def image?
33
+ !(attachment_content_type =~ /^image.*/).nil?
34
+ end
35
+
36
+ def to_s
37
+ attachment_file_name
38
+ end
39
+
40
+ private
41
+
42
+ def attachment_url_provided?
43
+ !self.attachment_url.blank?
44
+ end
45
+
46
+ def download_remote_file
47
+ self.attachment = do_download_remote_file
48
+ self.attachment_remote_url = attachment_url
49
+ end
50
+
51
+ def do_download_remote_file
52
+ io = open(URI.parse(attachment_url))
53
+ def io.original_filename; base_uri.path.split('/').last; end
54
+ io.original_filename.blank? ? nil : io
55
+ rescue # catch errors with validations
56
+ end
57
+ end
@@ -0,0 +1,78 @@
1
+ class Comment < ActiveRecord::Base
2
+ include Gravtastic
3
+ belongs_to :post # TODO , :counter_cache => true
4
+ belongs_to :user
5
+
6
+ validates_presence_of :post, :body
7
+ validates_presence_of :author_name, :author_email, :if => :anonymous?
8
+ validates_format_of :author_email, :with => /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i, :allow_blank => :true
9
+
10
+ is_gravtastic :with => :email, :rating => 'R', :size => 30
11
+
12
+ after_save :set_parent_delta
13
+ after_create :email_post_author
14
+
15
+ scope :ham, :conditions => {:approved => true}
16
+ scope :spam, :conditions => {:approved => false}
17
+ scope :old, lambda {{:conditions => ["created_at < :now", { :now => 14.days.ago }]}}
18
+
19
+ def set_parent_delta
20
+ self.post.update_attributes(:delta => true)
21
+ end
22
+
23
+ def email_post_author
24
+ Notifier.deliver_new_comment_alert(self) unless user_id == post.user_id
25
+ end
26
+
27
+ def anonymous?
28
+ !user_id?
29
+ end
30
+
31
+ def email
32
+ user_id.blank? ? author_email : user.email
33
+ end
34
+
35
+ def author
36
+ user_id.blank? ? author_name : user.to_s
37
+ end
38
+
39
+ def author_url
40
+ anonymous? ? read_attribute(:author_url) : user.url
41
+ end
42
+
43
+ before_create :check_for_spam
44
+
45
+ def request=(request)
46
+ self.user_ip = request.remote_ip
47
+ self.user_agent = request.env['HTTP_USER_AGENT']
48
+ self.referrer = request.env['HTTP_REFERER']
49
+ end
50
+
51
+ def check_for_spam
52
+ self.approved = !Akismetor.spam?(akismet_attributes)
53
+ true # return true so it doesn't stop save
54
+ end
55
+
56
+ def akismet_attributes
57
+ {
58
+ :key => '4a8bfe691b69',
59
+ :blog => "http://#{DOMAIN}",
60
+ :user_ip => user_ip,
61
+ :user_agent => user_agent,
62
+ :comment_author => author,
63
+ :comment_author_email => email,
64
+ :comment_author_url => author_url,
65
+ :comment_content => body
66
+ }
67
+ end
68
+
69
+ def mark_as_spam!
70
+ update_attribute(:approved, false)
71
+ Akismetor.submit_spam(akismet_attributes)
72
+ end
73
+
74
+ def mark_as_ham!
75
+ update_attribute(:approved, true)
76
+ Akismetor.submit_ham(akismet_attributes)
77
+ end
78
+ end
@@ -0,0 +1,35 @@
1
+ class Notifier < ActionMailer::Base
2
+ def password_reset_instructions(user)
3
+ setup(user)
4
+ subject "Password Reset Instructions"
5
+ body :edit_password_reset_url => edit_password_reset_url(user.perishable_token)
6
+ end
7
+
8
+ def activation_instructions(user)
9
+ setup(user)
10
+ subject "Activation Instructions"
11
+ body :account_activation_url => activate_url(user.perishable_token)
12
+ end
13
+
14
+ def activation_confirmation(user)
15
+ setup(user)
16
+ subject "Welcome to #{DOMAIN}"
17
+ body :root_url => root_url
18
+ end
19
+
20
+ def new_comment_alert(comment)
21
+ setup(comment.post.user)
22
+ subject "New comment on #{comment.post.title}"
23
+ body :comment => comment
24
+ end
25
+
26
+ protected
27
+
28
+ def setup(user)
29
+ recipients user.email
30
+ from "#{DOMAIN} <noreply@#{DOMAIN}>"
31
+ subject "#{DOMAIN} Message"
32
+ sent_on Time.now
33
+ body :user => user
34
+ end
35
+ end
@@ -0,0 +1,57 @@
1
+ class Post < ActiveRecord::Base
2
+ has_many :assets, :dependent => :destroy
3
+ has_many :comments, :dependent => :destroy, :order => :created_at
4
+ belongs_to :user
5
+
6
+ accepts_nested_attributes_for :assets
7
+
8
+ # has_permalink :title, :unless => Proc.new { |post| post.read_attribute(:title).blank? }
9
+
10
+ validates_presence_of :user, :published_at
11
+
12
+ before_validation :format_published_at
13
+
14
+ def asset
15
+ assets.first
16
+ end
17
+
18
+ def to_param
19
+ permalink.blank? ? id.to_s : permalink
20
+ end
21
+
22
+ def self.types
23
+ Dir[File.expand_path('../posts',__FILE__)+'/*.rb'].each { |f| require_dependency f }
24
+ self.subclasses.collect(&:to_s).sort
25
+ end
26
+
27
+ def self.find_by_permalink_or_id(param)
28
+ Post.find_by_permalink(param) || Post.find(param)
29
+ end
30
+
31
+ cattr_reader :per_page
32
+ @@per_page = 5
33
+
34
+ scope :all_public, :conditions => {:publicly_viewable => true}
35
+ scope :all_private, :conditions => {:publicly_viewable => false}
36
+ scope :published, lambda {{:conditions => ["published_at < :now", { :now => Time.now.utc }]}}
37
+
38
+ def title
39
+ read_attribute(:title).blank? ? "Post #{id}" : read_attribute(:title)
40
+ end
41
+
42
+ index do
43
+ title
44
+ link_url
45
+ image_url
46
+ video_embed
47
+ description
48
+ quote
49
+ permalink
50
+ type
51
+ via
52
+ end
53
+
54
+ def format_published_at
55
+ self.published_at = Time.now if self.published_at.nil?
56
+ end
57
+ end
@@ -0,0 +1,3 @@
1
+ class Blog < Post
2
+ validates_presence_of :description
3
+ end
@@ -0,0 +1,3 @@
1
+ class Code < Post
2
+ validates_presence_of :description
3
+ end
@@ -0,0 +1,3 @@
1
+ class Image < Post
2
+ validates_associated :asset
3
+ end
@@ -0,0 +1,7 @@
1
+ class Link < Post
2
+ validates_presence_of :link_url
3
+
4
+ def title
5
+ self.read_attribute(:title).blank? ? self.link_url : self.read_attribute(:title)
6
+ end
7
+ end
@@ -0,0 +1,3 @@
1
+ class Quote < Post
2
+ validates_presence_of :quote
3
+ end
@@ -0,0 +1,4 @@
1
+ class Video < Post
2
+ validates_presence_of :video_embed, :if => Proc.new { |video| video.link_url.blank? }
3
+ validates_presence_of :link_url, :if => Proc.new { |video| video.video_embed.blank? }
4
+ end
@@ -0,0 +1,35 @@
1
+ class User < ActiveRecord::Base
2
+
3
+ acts_as_authentic
4
+
5
+ validates_presence_of :first_name
6
+
7
+ is_gravtastic :with => :email, :rating => 'R', :size => 30
8
+
9
+ def to_s
10
+ first_name
11
+ end
12
+
13
+ def name
14
+ [first_name, last_name].reject(&:blank?).join(' ')
15
+ end
16
+
17
+ def activate!
18
+ self.update_attributes(:activated_at => Time.now)
19
+ end
20
+
21
+ def deliver_password_reset_instructions!
22
+ reset_perishable_token!
23
+ Notifier.deliver_password_reset_instructions(self)
24
+ end
25
+
26
+ def deliver_activation_instructions!
27
+ reset_perishable_token!
28
+ Notifier.deliver_activation_instructions(self)
29
+ end
30
+
31
+ def deliver_activation_confirmation!
32
+ reset_perishable_token!
33
+ Notifier.deliver_activation_confirmation(self)
34
+ end
35
+ end
@@ -0,0 +1,2 @@
1
+ class UserSession < Authlogic::Session::Base
2
+ end
@@ -0,0 +1,14 @@
1
+ .vcard[comment]{:class => ('author' if comment.user_id == comment.post.user.id)}
2
+ = link_to_unless comment.author_url.blank?, image_tag(comment.gravatar_url, :class => 'photo'), h(comment.author_url)
3
+ = markdown comment.body
4
+ %p.meta
5
+ = format_datetime_ago(comment.created_at)
6
+ by
7
+ = link_to_unless comment.author_url.blank?, h(comment.author), h(comment.author_url), :rel => :nofollow, :class => 'url fn'
8
+ - if current_user
9
+ \-
10
+ = link_to 'Edit', edit_post_comment_path(comment.post, comment)
11
+ |
12
+ = link_to 'Delete', post_comment_path(comment.post, comment), :class => :delete
13
+ |
14
+ = spam_link_for comment
@@ -0,0 +1,9 @@
1
+ - unless current_user
2
+ = form.label :author_name, 'Name'
3
+ .field= form.text_field :author_name, :class => "required", :placeholder => 'Name..'
4
+ = form.label :author_email, 'Email'
5
+ .field= form.email_field :author_email, :class => "required email", :placeholder => 'Email..', :autocorrect => 'off'
6
+ = form.label :author_url, 'Url'
7
+ .field= form.url_field :author_url, :class => 'url', :placeholder => 'Url..'
8
+ = form.label :body, 'Your Comment'
9
+ .field= form.text_area :body, :rows => 6, :class => "required", :placeholder => 'Your comment..'
@@ -0,0 +1,7 @@
1
+ %h2 Edit comment
2
+
3
+ - form_for [@post, @comment], :url => post_comment_path do |form|
4
+ = render :partial => form
5
+ = form.submit "Update"
6
+ or
7
+ = link_to 'Cancel', post_path(@post)
@@ -0,0 +1,15 @@
1
+ atom_feed(:schema_date => 2008, :root_url => root_url, :url => comments_url(:format => :atom)) do |feed|
2
+ feed.title "Comments"
3
+ feed.updated @comments.first.updated_at
4
+
5
+ for comment in @comments
6
+ feed.entry(comment, :url => post_url(comment.post, :anchor => dom_id(comment))) do |entry|
7
+ entry.title "Comment #{comment.id}"
8
+ entry.author do |author|
9
+ author.name comment.author
10
+ author.uri comment.author_url
11
+ end
12
+ entry.content markdown(comment.body), :type => 'html'
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,7 @@
1
+ %h2 New comment
2
+
3
+ - form_for [@post, @comment], :url => post_comments_path do |form|
4
+ = render :partial => form
5
+ = form.submit "Post Comment"
6
+ or
7
+ = link_to 'Cancel', post_path(@post)
@@ -0,0 +1,20 @@
1
+ #sidebar
2
+ %h3 Search
3
+ - form_tag search_posts_path, :method => :get, :id => 'search_form' do
4
+ .field= text_field_tag :query, params[:query], :placeholder => 'Keywords..', :type => 'search'
5
+ = submit_tag 'Search'
6
+ %h3 Links
7
+ %ul#links
8
+ %li.feed= link_to "Feed", posts_path(:format => :atom)
9
+ %li.email= mail_to CONTACT_EMAIL, "Email"
10
+
11
+ - if current_user
12
+ %h3 Admin
13
+ %p
14
+ = pluralize(Post.count, 'post')
15
+ and
16
+ = pluralize(Comment.count, 'comment')
17
+ %p
18
+ = link_to "Edit account", edit_user_path(current_user)
19
+ or
20
+ = link_to "Logout", logout_path
@@ -0,0 +1,35 @@
1
+ !!!
2
+ %html.no-js
3
+ %head
4
+ %meta{'http-equiv' => 'Content-Type', :content => "text/html; charset=utf-8"}
5
+ %meta{:name => 'author', :content => 'Bumble'}
6
+ %meta{:name => 'keywords', :content => 'keywords'}
7
+ %meta{:name => 'description', :content => page_description}
8
+ = auto_discovery_link_tag :atom, posts_url(:format => :atom), :title => 'Posts feed'
9
+ = auto_discovery_link_tag :atom, comments_url(:format => :atom), :title => 'Comments feed'
10
+ - if protect_against_forgery?
11
+ :javascript
12
+ var AUTH_TOKEN=#{form_authenticity_token.inspect};
13
+ = stylesheet_link_tag('bumble', :media => 'screen, print')
14
+ = javascript_include_tag 'jquery', 'jquery.validate', 'jquery.form', 'jquery.hint', 'jquery.autogrow', 'modernizr', 'bumble'
15
+ - if current_user
16
+ = javascript_include_tag 'jquery.cookie', 'jquery.ui.core', 'jquery.ui.widget', 'jquery.ui.tabs', 'admin'
17
+ - if iphone?
18
+ %meta{:name => "viewport", :content => "width=device-width; initial-scale=1.0; maximum-scale=1.0;"}
19
+ = stylesheet_link_tag 'iphone'
20
+ = javascript_include_tag 'iphone'
21
+ %title= page_title
22
+ = yield :header
23
+ %body
24
+ #header
25
+ %h1= link_to DOMAIN, root_path, :rel => :home
26
+ #content
27
+ - flash.each do |key, msg|
28
+ .flash{:id => key}= msg
29
+ = yield
30
+ = render 'layouts/sidebar'
31
+ #footer
32
+ = link_to '#header', :class => 'up', :title => 'Back to top' do
33
+ &#x2191;
34
+ Powered by
35
+ = link_to "Bumble", 'http://github.com/andrew/bumble'
@@ -0,0 +1,5 @@
1
+ Your account has been activated. Enjoy Welcome to sparkler etc.
2
+
3
+ <%= @root_url %>
4
+
5
+ If the above URL does not work try copying and pasting it into your browser. If you continue to have problem, please feel free to contact us.
@@ -0,0 +1,5 @@
1
+ Thank you for creating an account! Click the url below to activate your account:
2
+
3
+ <%= @account_activation_url %>
4
+
5
+ If the above URL does not work try copying and pasting it into your browser. If you continue to have problem, please feel free to contact us.
@@ -0,0 +1,8 @@
1
+ A new comment has been posted on <%= @comment.post.title %> by <%= @comment.author %><%= " (#{@comment.author_url})" if @comment.author_url.present? %>:
2
+
3
+ <%= @comment.body %>
4
+
5
+ It<%= @comment.approved ? " doesn't look like" : "'s been marked as" %> spam.
6
+
7
+ -----
8
+ See it here: <%= post_url(@comment.post, :anchor => dom_id(@comment)) %>