radiant-forum-extension 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (146) hide show
  1. data/README.md +155 -0
  2. data/Rakefile +139 -0
  3. data/VERSION +1 -0
  4. data/app/controllers/admin/forums_controller.rb +11 -0
  5. data/app/controllers/forums_controller.rb +34 -0
  6. data/app/controllers/posts_controller.rb +238 -0
  7. data/app/controllers/topics_controller.rb +78 -0
  8. data/app/models/forum.rb +52 -0
  9. data/app/models/moderatorship.rb +5 -0
  10. data/app/models/post.rb +122 -0
  11. data/app/models/post_attachment.rb +73 -0
  12. data/app/models/topic.rb +162 -0
  13. data/app/views/admin/forums/_form.html.haml +25 -0
  14. data/app/views/admin/forums/edit.html.haml +5 -0
  15. data/app/views/admin/forums/index.html.haml +76 -0
  16. data/app/views/admin/forums/moderate.html.haml +2 -0
  17. data/app/views/admin/forums/new.html.haml +5 -0
  18. data/app/views/admin/forums/remove.html.haml +22 -0
  19. data/app/views/admin/pages/_edit_commentability.html.haml +9 -0
  20. data/app/views/admin/reader_settings/_forum.html.haml +4 -0
  21. data/app/views/admin/sites/_choose_forum_layout.html.haml +7 -0
  22. data/app/views/forums/_forum.html.haml +22 -0
  23. data/app/views/forums/index.html.haml +44 -0
  24. data/app/views/forums/show.html.haml +38 -0
  25. data/app/views/forums/show.rss.builder +13 -0
  26. data/app/views/layouts/feed.rss.builder +5 -0
  27. data/app/views/monitorships/create.rjs.erb +1 -0
  28. data/app/views/monitorships/destroy.rjs.erb +1 -0
  29. data/app/views/monitorships/index.html.erb +35 -0
  30. data/app/views/pages/_comment.html.haml +10 -0
  31. data/app/views/pages/_comments.html.haml +15 -0
  32. data/app/views/posts/_attachment.html.haml +2 -0
  33. data/app/views/posts/_form.html.haml +6 -0
  34. data/app/views/posts/_latest.html.haml +17 -0
  35. data/app/views/posts/_post.html.haml +67 -0
  36. data/app/views/posts/_post.rss.builder +13 -0
  37. data/app/views/posts/_reply.html.haml +36 -0
  38. data/app/views/posts/_search.html.haml +28 -0
  39. data/app/views/posts/_uploader.html.haml +16 -0
  40. data/app/views/posts/edit.html.haml +20 -0
  41. data/app/views/posts/index.html.haml +33 -0
  42. data/app/views/posts/index.rss.builder +14 -0
  43. data/app/views/posts/new.html.haml +14 -0
  44. data/app/views/posts/preview.html.haml +32 -0
  45. data/app/views/posts/search.html.haml +54 -0
  46. data/app/views/posts/search.rss.builder +14 -0
  47. data/app/views/posts/show.html.haml +10 -0
  48. data/app/views/reader_notifier/post.rhtml +14 -0
  49. data/app/views/readers/_extra_controls.html.haml +5 -0
  50. data/app/views/readers/show_with_posts.html.haml +47 -0
  51. data/app/views/topics/_form.html.haml +26 -0
  52. data/app/views/topics/_help.html.haml +8 -0
  53. data/app/views/topics/_locked.html.haml +2 -0
  54. data/app/views/topics/_topic.html.haml +35 -0
  55. data/app/views/topics/_topic.rss.builder +9 -0
  56. data/app/views/topics/comments.html.haml +5 -0
  57. data/app/views/topics/edit.html.haml +26 -0
  58. data/app/views/topics/index.html.haml +41 -0
  59. data/app/views/topics/index.rss.builder +14 -0
  60. data/app/views/topics/new.html.haml +40 -0
  61. data/app/views/topics/show.html.haml +45 -0
  62. data/app/views/topics/show.rss.builder +14 -0
  63. data/config/routes.rb +16 -0
  64. data/db/migrate/001_create_forum_tables.rb +64 -0
  65. data/db/migrate/002_pages_commentable.rb +16 -0
  66. data/db/migrate/003_extend_sites.rb +13 -0
  67. data/db/migrate/004_sample_layout.rb +41 -0
  68. data/db/migrate/005_post_attachments.rb +19 -0
  69. data/db/migrate/006_user_relations.rb +20 -0
  70. data/db/migrate/007_reader_ownership.rb +35 -0
  71. data/db/migrate/008_first_posts.rb +13 -0
  72. data/db/migrate/20090824111005_import_helpers.rb +15 -0
  73. data/forum_extension.rb +44 -0
  74. data/lib/forum_admin_ui.rb +37 -0
  75. data/lib/forum_helper.rb +109 -0
  76. data/lib/forum_page.rb +45 -0
  77. data/lib/forum_reader.rb +9 -0
  78. data/lib/forum_reader_notifier.rb +9 -0
  79. data/lib/forum_readers_controller.rb +15 -0
  80. data/lib/forum_red_cloth3.rb +10 -0
  81. data/lib/forum_red_cloth4.rb +21 -0
  82. data/lib/forum_site.rb +12 -0
  83. data/lib/forum_tags.rb +285 -0
  84. data/lib/tasks/radiant_forum_extension_tasks.rake +137 -0
  85. data/pkg/radiant-forum-extension-0.5.0.gem +0 -0
  86. data/public/images/admin/forum.png +0 -0
  87. data/public/images/admin/new-forum.png +0 -0
  88. data/public/images/admin/nominus.png +0 -0
  89. data/public/images/admin/noremove.png +0 -0
  90. data/public/images/emoticons/angry.gif +0 -0
  91. data/public/images/emoticons/bigsmile.gif +0 -0
  92. data/public/images/emoticons/confused.gif +0 -0
  93. data/public/images/emoticons/cool.gif +0 -0
  94. data/public/images/emoticons/cry.gif +0 -0
  95. data/public/images/emoticons/devil.gif +0 -0
  96. data/public/images/emoticons/neutral.gif +0 -0
  97. data/public/images/emoticons/sad.gif +0 -0
  98. data/public/images/emoticons/shamed.gif +0 -0
  99. data/public/images/emoticons/shocked.gif +0 -0
  100. data/public/images/emoticons/smile.gif +0 -0
  101. data/public/images/emoticons/surprised.gif +0 -0
  102. data/public/images/emoticons/tongue.gif +0 -0
  103. data/public/images/emoticons/wink.gif +0 -0
  104. data/public/images/forum/attachment.png +0 -0
  105. data/public/images/forum/attachment_link.png +0 -0
  106. data/public/images/forum/attachment_over.png +0 -0
  107. data/public/images/forum/chk_off.png +0 -0
  108. data/public/images/forum/chk_on.png +0 -0
  109. data/public/images/forum/feed_14.png +0 -0
  110. data/public/images/forum/feed_28.png +0 -0
  111. data/public/images/forum/post_14.png +0 -0
  112. data/public/images/forum/post_14_over.png +0 -0
  113. data/public/images/forum/rdo_off.png +0 -0
  114. data/public/images/forum/rdo_on.png +0 -0
  115. data/public/images/forum/wait_16_grey.gif +0 -0
  116. data/public/javascripts/platform/forum.js +175 -0
  117. data/public/javascripts/platform/remotecontent.js +89 -0
  118. data/public/stylesheets/admin/forum.css +87 -0
  119. data/public/stylesheets/platform/forum.css +70 -0
  120. data/radiant-forum-extension.gemspec +215 -0
  121. data/spec/controllers/admin/forums_controller_spec.rb +54 -0
  122. data/spec/controllers/forums_controller_spec.rb +42 -0
  123. data/spec/controllers/posts_controller_spec.rb +310 -0
  124. data/spec/controllers/topics_controller_spec.rb +133 -0
  125. data/spec/datasets/forum_layouts_dataset.rb +15 -0
  126. data/spec/datasets/forum_pages_dataset.rb +11 -0
  127. data/spec/datasets/forum_readers_dataset.rb +55 -0
  128. data/spec/datasets/forum_sites_dataset.rb +10 -0
  129. data/spec/datasets/forums_dataset.rb +28 -0
  130. data/spec/datasets/posts_dataset.rb +31 -0
  131. data/spec/datasets/topics_dataset.rb +37 -0
  132. data/spec/lib/forum_admin_ui_spec.rb +35 -0
  133. data/spec/lib/forum_page_spec.rb +63 -0
  134. data/spec/lib/forum_reader_notification_spec.rb +9 -0
  135. data/spec/lib/forum_reader_spec.rb +13 -0
  136. data/spec/lib/forum_site_spec.rb +39 -0
  137. data/spec/models/forum_spec.rb +48 -0
  138. data/spec/models/post_spec.rb +91 -0
  139. data/spec/models/topic_spec.rb +105 -0
  140. data/spec/spec.opts +6 -0
  141. data/spec/spec_helper.rb +44 -0
  142. data/vendor/plugins/acts_as_list/README +23 -0
  143. data/vendor/plugins/acts_as_list/init.rb +3 -0
  144. data/vendor/plugins/acts_as_list/lib/active_record/acts/list.rb +256 -0
  145. data/vendor/plugins/acts_as_list/test/list_test.rb +332 -0
  146. metadata +287 -0
@@ -0,0 +1,13 @@
1
+ class FirstPosts < ActiveRecord::Migration
2
+
3
+ def self.up
4
+ Topic.find(:all).each do |topic|
5
+ topic.first_post = topic.posts.first
6
+ topic.save
7
+ end
8
+ end
9
+
10
+ def self.down
11
+
12
+ end
13
+ end
@@ -0,0 +1,15 @@
1
+ class ImportHelpers < ActiveRecord::Migration
2
+ def self.up
3
+ add_column :readers, :old_id, :integer
4
+ add_column :forums, :old_id, :integer
5
+ add_column :topics, :old_id, :integer
6
+ add_column :posts, :old_id, :integer
7
+ end
8
+
9
+ def self.down
10
+ remove_column :readers, :old_id
11
+ remove_column :forums, :old_id
12
+ remove_column :topics, :old_id
13
+ remove_column :posts, :old_id
14
+ end
15
+ end
@@ -0,0 +1,44 @@
1
+ require_dependency 'application_controller'
2
+
3
+ class ForumExtension < Radiant::Extension
4
+ version "0.4"
5
+ description "Nice clean forums and page comments for inclusion in your radiant site. Derived long ago from beast. Requires the reader extension and share_layouts."
6
+ url "http://spanner.org/radiant/forum"
7
+
8
+ def activate
9
+ Reader.send :include, ForumReader
10
+ ReaderNotifier.send :include, ForumReaderNotifier
11
+ ReadersController.send :include, ForumReadersController
12
+ Page.send :include, ForumPage
13
+ UserActionObserver.instance.send :add_observer!, Forum
14
+ UserActionObserver.instance.send :add_observer!, Topic
15
+ UserActionObserver.instance.send :add_observer!, Post
16
+ Page.send :include, ForumTags
17
+ Admin::ReaderSettingsController.make_settable 'forum.editable_period' => 15, 'forum.public?' => true, 'forum.layout' => '', 'forum.allow_page_comments?' => true
18
+
19
+ unless defined? admin.forum # UI is a singleton
20
+ Radiant::AdminUI.send :include, ForumAdminUI
21
+ admin.forum = Radiant::AdminUI.load_default_forum_regions
22
+ admin.pages.edit.add :parts_bottom, "edit_commentability", :after => "edit_layout_and_type"
23
+ admin.reader_settings.index.add :settings, "forum", :after => "sender"
24
+ if defined? Site && admin.sites
25
+ Site.send :include, ForumSite
26
+ end
27
+ end
28
+
29
+ if defined? RedCloth::DEFAULT_RULES # identifies redcloth 3
30
+ RedCloth.send :include, ForumRedCloth3
31
+ RedCloth::DEFAULT_RULES.push(:smilies)
32
+ else
33
+ RedCloth::TextileDoc.send :include, ForumRedCloth4
34
+ end
35
+
36
+ ApplicationHelper.send :include, ForumHelper
37
+
38
+ tab("Readers") do
39
+ add_item 'Forum', '/admin/readers/forums', :before => 'Settings'
40
+ end
41
+ end
42
+
43
+ end
44
+
@@ -0,0 +1,37 @@
1
+ module ForumAdminUI
2
+
3
+ def self.included(base)
4
+ base.class_eval do
5
+
6
+ attr_accessor :forum
7
+ alias_method :forums, :forum
8
+
9
+ def load_default_regions_with_forum
10
+ load_default_regions_without_forum
11
+ @forum = load_default_forum_regions
12
+ end
13
+ alias_method_chain :load_default_regions, :forum
14
+
15
+ protected
16
+
17
+ def load_default_forum_regions
18
+ returning OpenStruct.new do |forum|
19
+ forum.edit = Radiant::AdminUI::RegionSet.new do |edit|
20
+ edit.main.concat %w{edit_header edit_form}
21
+ edit.form.concat %w{edit_name edit_description}
22
+ edit.form_bottom.concat %w{edit_timestamp edit_buttons}
23
+ end
24
+ forum.index = Radiant::AdminUI::RegionSet.new do |index|
25
+ index.thead.concat %w{title_header latest_header modify_header}
26
+ index.tbody.concat %w{title_cell latest_cell modify_cell}
27
+ index.bottom.concat %w{new_button}
28
+ end
29
+ forum.remove = forum.index
30
+ forum.new = forum.edit
31
+ end
32
+ end
33
+
34
+ end
35
+ end
36
+ end
37
+
@@ -0,0 +1,109 @@
1
+ require 'sanitize'
2
+ module ForumHelper
3
+
4
+ def self.included(base)
5
+ base.module_eval {
6
+
7
+ def home_page_link(options={})
8
+ home_page = (defined? Site && Site.current) ? Site.current.home_page : Page.find_by_parent_id(nil)
9
+ link_to home_page.title, home_page.url, options
10
+ end
11
+
12
+ def feed_tag(text, url, options={})
13
+ link_to text, url, options.merge(:class => 'floating feedlink')
14
+ end
15
+
16
+ def feed_link(url)
17
+ link_to image_tag('/images/forum/feed_14.png', :class => 'feedicon', :alt => 'rss feed', :size => '14x20'), url
18
+ end
19
+
20
+ def clean_textilize(text) # adding smilies to the default reader method
21
+ if text.blank?
22
+ ""
23
+ else
24
+ textilized = RedCloth.new(text, [ :hard_breaks ])
25
+ textilized.hard_breaks = true if textilized.respond_to?("hard_breaks=")
26
+ Sanitize.clean(textilized.to_html(:textile, :smilies), Sanitize::Config::RELAXED)
27
+ end
28
+ end
29
+
30
+ def watch_tag(topic, label='watching', formclass=nil)
31
+ if current_user
32
+ monitoring = current_user.monitoring?(topic)
33
+ %{
34
+ <form action="#{monitorship_path(topic.forum, topic)}" method="post" class="#{formclass}"><div>
35
+ <input id="monitor_checkbox_#{topic.id}" name="monitor_checkbox" class="monitor_checkbox" type="checkbox"#{ ' checked="checked"' if monitoring } />
36
+ <label class="monitor_label" for="monitor_checkbox_#{topic.id}">#{label}</label>
37
+ #{hidden_field_tag '_method', monitoring ? 'delete' : ''}
38
+ #{submit_tag :set, :class => 'monitor_submit'}
39
+ </div></form>
40
+ }
41
+ end
42
+ end
43
+
44
+ def paged_post_url(post)
45
+ if post.first?
46
+ topic_post_url(post.topic, post, :page => post.topic_page, :anchor => post.dom_id)
47
+ else
48
+ topic_post_url(post.topic, post, :page => post.topic_page, :anchor => post.dom_id)
49
+ end
50
+ end
51
+
52
+ def edit_link(post)
53
+ link_to 'e', edit_topic_post_url(post.topic, post), :class => 'edit_post', :id => "edit_post_#{post.id}", :title => "edit post"
54
+ end
55
+
56
+ def remove_link(post)
57
+ link_to 'x', topic_post_url(post.topic, post), :method => 'delete', :class => 'delete_post', :id => "delete_post_#{post.id}", :title => "remove post", :confirm => "Are you sure you want to delete this message?"
58
+ end
59
+
60
+ def friendly_date(datetime)
61
+ if datetime
62
+ date = datetime.to_date
63
+ if (date == Date.today)
64
+ format = "today at %l:%M%p"
65
+ elsif (date == Date.yesterday)
66
+ format = "yesterday at %l:%M%p"
67
+ elsif (date.year == Date.today.year)
68
+ format = "on %B %e"
69
+ else
70
+ format = "on %B %e, %Y"
71
+ end
72
+ datetime.strftime(format)
73
+ else
74
+ "unknown date"
75
+ end
76
+ end
77
+
78
+ def paginate_and_summarise(list, plural='')
79
+ pagination = will_paginate list, :separator => %{<span class="separator">|</span>}, :container => false
80
+ %{<div class="pagination">
81
+ #{pagination}
82
+ <span class="pagination_summary">
83
+ showing #{pagination_summary(list, plural)}
84
+ </span>
85
+ </div>
86
+ }
87
+ end
88
+
89
+ def pagination_summary(list, plural='')
90
+ total = list.total_entries
91
+ if total == 1
92
+ if plural.blank?
93
+ "one"
94
+ else
95
+ %{one #{plural.singularize}}
96
+ end
97
+ elsif list.current_page == 1 && total < list.per_page
98
+ %{all #{total} #{plural}}
99
+ else
100
+ start = list.offset + 1
101
+ finish = ((list.offset + list.per_page) < list.total_entries) ? list.offset + list.per_page : list.total_entries
102
+ %{#{start} to #{finish} of #{total} #{plural}}
103
+ end
104
+ end
105
+
106
+ }
107
+ end
108
+
109
+ end
data/lib/forum_page.rb ADDED
@@ -0,0 +1,45 @@
1
+ module ForumPage
2
+
3
+ def self.included(base)
4
+ base.class_eval {
5
+ has_one :topic
6
+ include InstanceMethods
7
+ }
8
+ end
9
+
10
+ module InstanceMethods
11
+
12
+ def find_or_build_topic
13
+ if self.topic
14
+ self.topic
15
+ elsif self.still_commentable?
16
+ self.build_topic(:name => title, :forum => Forum.find_or_create_comments_forum, :reader => Reader.find_or_create_for_user(created_by)) # posts_controller will do the right thing with a new topic
17
+ end
18
+ end
19
+
20
+ def posts
21
+ self.topic ? self.topic.posts : []
22
+ end
23
+
24
+ def has_posts?
25
+ self.topic && self.topic.has_posts?
26
+ end
27
+
28
+ # def cache?
29
+ # !has_posts?
30
+ # end
31
+
32
+ def still_commentable?
33
+ commentable? && !comments_closed? && (!commentable_period || Time.now - self.created_at < commentable_period)
34
+ end
35
+
36
+ def commentable_period
37
+ Radiant::Config['forum.commentable_period'].to_i.days if Radiant::Config['forum.commentable_period']
38
+ end
39
+
40
+ def locked?
41
+ !still_commentable?
42
+ end
43
+
44
+ end
45
+ end
@@ -0,0 +1,9 @@
1
+ module ForumReader
2
+
3
+ def self.included(base)
4
+ base.class_eval {
5
+ has_many :topics, :dependent => :nullify
6
+ has_many :posts, :order => 'posts.created_at desc', :dependent => :nullify
7
+ }
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ module ForumReaderNotifier
2
+
3
+ def post(reader, post)
4
+ setup_email(reader)
5
+ @subject += "New post under '#{post.topic.name}'"
6
+ @body[:post] = post
7
+ @body[:post_url] = url_for(post)
8
+ end
9
+ end
@@ -0,0 +1,15 @@
1
+ module ForumReadersController
2
+
3
+ def self.included(base)
4
+ base.class_eval do
5
+ alias_method_chain :show, :forum
6
+ end
7
+ end
8
+
9
+ def show_with_forum
10
+ @reader = Reader.find(params[:id])
11
+ @posts = Post.paginate_by_reader_id(@reader.id, :page => params[:page], :include => :topic, :order => 'posts.created_at desc') if @reader
12
+ render :template => 'readers/show_with_posts'
13
+ end
14
+
15
+ end
@@ -0,0 +1,10 @@
1
+ module ForumRedCloth3
2
+
3
+ def smilies(text)
4
+ %w{angry smile bigsmile confused cool cry devil neutral sad shamed shocked surprised tongue wink }.each do |icon|
5
+ imgtag =
6
+ text.gsub!(/:#{icon}:/, %{<img src="/images/emoticons/#{icon}.gif" alt="(#{icon})" class="smiley" />})
7
+ end
8
+ end
9
+
10
+ end
@@ -0,0 +1,21 @@
1
+ module ForumRedCloth4
2
+
3
+ def self.included(base)
4
+ # base.extend ClassMethods
5
+ base.class_eval do
6
+ def to_html_with_smilies(*rules)
7
+ rules.push(:smilies) unless rules.include?(:smilies)
8
+ to_html_without_smilies(*rules)
9
+ end
10
+ alias_method_chain :to_html, :smilies unless self.instance_methods.include?("to_html_without_smilies")
11
+ end
12
+ end
13
+
14
+ def smilies(text)
15
+ %w{angry smile bigsmile confused cool cry devil neutral sad shamed shocked surprised tongue wink }.each do |icon|
16
+ imgtag =
17
+ text.gsub!(/:#{icon}:/, %{<img src="/images/emoticons/#{icon}.gif" alt="(#{icon})" title="#{icon}" class="smiley" />})
18
+ end
19
+ end
20
+
21
+ end
data/lib/forum_site.rb ADDED
@@ -0,0 +1,12 @@
1
+ module ForumSite
2
+
3
+ def self.included(base)
4
+ base.class_eval %{
5
+ has_many :forums
6
+ has_many :topics
7
+ has_many :posts
8
+ belongs_to :forum_layout, :class_name => 'Layout'
9
+ }
10
+ super
11
+ end
12
+ end
data/lib/forum_tags.rb ADDED
@@ -0,0 +1,285 @@
1
+ module ForumTags
2
+ include Radiant::Taggable
3
+ include ActionView::Helpers::TagHelper
4
+ include ActionView::Helpers::FormTagHelper
5
+ include ActionView::Helpers::FormOptionsHelper
6
+
7
+ class TagError < StandardError; end
8
+
9
+ tag 'comments' do |tag|
10
+ raise TagError, "can't have comments without a page" unless page = tag.locals.page
11
+ if page.commentable?
12
+ tag.locals.comments = page.posts
13
+ tag.expand
14
+ end
15
+ end
16
+
17
+ desc %{
18
+ Returns a string in the form "x comments" or "no comments yet".
19
+
20
+ *Usage:*
21
+ <pre><code><r:comments:summary /></code></pre>
22
+ }
23
+
24
+ tag 'comments:summary' do |tag|
25
+ if tag.locals.comments.empty?
26
+ "no comments yet"
27
+ elsif tag.locals.comments.size == 1
28
+ "one comment"
29
+ else
30
+ "#{tag.locals.comments.size} comments"
31
+ end
32
+ end
33
+
34
+ desc %{
35
+ Anything between if_comments tags is displayed only - dramatic pause - if there are comments.
36
+
37
+ *Usage:*
38
+ <pre><code><r:if_comments>...</r:if_comments></code></pre>
39
+ }
40
+ tag 'if_comments' do |tag|
41
+ raise TagError, "can't have if_comments without a page" unless page = tag.locals.page
42
+ tag.expand if page.posts.any?
43
+ end
44
+
45
+ desc %{
46
+ Anything between unless_comments tags is displayed only if there are no comments.
47
+
48
+ *Usage:*
49
+ <pre><code><r:unless_comments>...</r:unless_comments></code></pre>
50
+ }
51
+ tag 'unless_comments' do |tag|
52
+ raise TagError, "can't have unless_comments without a page" unless page = tag.locals.page
53
+ tag.expand if page.posts.any?
54
+ end
55
+
56
+ desc %{
57
+ If you want more control over the display of page comments, you can spell them out:
58
+
59
+ *Usage:*
60
+ <pre><code><r:comments:each>
61
+ <h2><r:comment:reader:name /></h2>
62
+ <p class="date"><r:comment:date /></p>
63
+ <r:comment:body_html />
64
+ </r:comments:each>
65
+ <r:comment_link />
66
+ </code></pre>
67
+ }
68
+ tag 'comments:each' do |tag|
69
+ results = []
70
+ tag.locals.comments.each do |post|
71
+ tag.locals.comment = post
72
+ results << tag.expand
73
+ end
74
+ results
75
+ end
76
+
77
+ desc %{
78
+ To enable page commenting, all you have to do is put this in your layout:
79
+
80
+ *Usage:*
81
+ <pre><code><r:comments:all /></code></pre>
82
+ }
83
+ tag 'comments:all' do |tag|
84
+ posts = tag.locals.comments
85
+ results = ""
86
+ results << %{<div class="page_comments">}
87
+ results << "<h2>Comments</h2>"
88
+ results << %{<div id="forum">
89
+ }
90
+ if posts.empty?
91
+ results << "<p>None yet.</p>"
92
+ else
93
+ posts.each do |post|
94
+ tag.locals.comment = post
95
+ results << tag.render('comment')
96
+ end
97
+ end
98
+ results << %{#{tag.render('comment_link', 'class' => 'inline inviting')}}
99
+ results << "</div></div>"
100
+ results
101
+ end
102
+
103
+ tag 'comment' do |tag|
104
+ raise TagError, "can't have r:comment without a post" unless post = tag.locals.comment
105
+ if tag.double?
106
+ tag.locals.reader = post.reader
107
+ tag.expand
108
+ else
109
+ %{<div class="post" id="#{post.dom_id}">
110
+ <div class="post_header">
111
+ <h2><img src="#{post.reader.gravatar_url(:size => 40)}" width="40" height ="40" class="gravatar" /> #{post.reader.name}</h2>
112
+ <p class="context">#{friendly_date(post.created_at)}</p>
113
+ </div>
114
+ <div class="post_body">#{post.body_html}</div>
115
+ </div>}
116
+ end
117
+ end
118
+
119
+ tag 'comment:reader' do |tag|
120
+ raise TagError, "can't have comment:reader without a comment" unless reader = tag.locals.reader
121
+ tag.expand
122
+ end
123
+
124
+ desc %{
125
+ The name of the commenter
126
+ }
127
+ tag 'comment:reader:name' do |tag|
128
+ tag.locals.reader.name
129
+ end
130
+
131
+ desc %{
132
+ A gravatar for the commenter
133
+ }
134
+ tag 'comment:reader:gravatar' do |tag|
135
+ tag.locals.reader.gravatar
136
+ end
137
+
138
+ desc %{
139
+ The date of the comment
140
+ }
141
+ tag 'comment:date' do |tag|
142
+ tag.locals.comment.created_at.to_s(:html_date)
143
+ end
144
+
145
+ desc %{
146
+ The time_ago of the comment
147
+ }
148
+ tag 'comment:ago' do |tag|
149
+ time_ago_in_words(tag.locals.comment.created_at)
150
+ end
151
+
152
+ desc %{
153
+ The body of the comment as it was entered (but html-escaped)
154
+ }
155
+ tag 'comment:body' do |tag|
156
+ h(tag.locals.comment.body)
157
+ end
158
+
159
+ desc %{
160
+ The body of the comment rendered into html (and whitelisted, so this ought to be safe)
161
+ }
162
+ tag 'comment:body_html' do |tag|
163
+ tag.locals.comment.body_html
164
+ end
165
+
166
+ desc %{
167
+ A link to the post-a-comment form. Typically you'll use a bit of remote scripting to replace this with
168
+ a comment or login form depending on whether a reader is detected, but you can just leave the link too.
169
+
170
+ If text is given, the link will be wrapped around it. The default is just "Add a comment". Any supplied
171
+ attributes are passed through, so you can specify class, id and anything else you like.
172
+
173
+ *Usage:*
174
+ <pre><code>
175
+ <r:if_comments>
176
+ <r:comment_link />
177
+ </r:if_comments>
178
+ <r:unless_comments>
179
+ <r:comment_link class="how_exciting">Be the first to add a comment!</r:comment_link>
180
+ </r:unless_comments>
181
+ </code></pre>
182
+ }
183
+ tag 'comment_link' do |tag|
184
+ raise TagError, "can't have `r:comment_link' without a page." unless tag.locals.page
185
+ options = tag.attr.dup
186
+ options['class'] ||= 'newmessage'
187
+ attributes = options.inject('') { |s, (k, v)| s << %{#{k.to_s.downcase}="#{v}" } }.strip
188
+ attributes = " #{attributes}" unless attributes.empty?
189
+ text = tag.double? ? tag.expand : "Add a comment"
190
+ %{<a href="#{tag.render('comment_url')}"#{attributes}>#{text}</a>}
191
+ end
192
+
193
+ desc %{
194
+ The address for add-a-comment links
195
+ }
196
+ tag 'comment_url' do |tag|
197
+ raise TagError, "can't have `r:comment_url' without a page." unless tag.locals.page
198
+ new_page_post_url(tag.locals.page)
199
+ end
200
+
201
+ desc %{
202
+ Shows the standard block of recent discussion activity.
203
+ }
204
+ tag 'forum_latest' do |tag|
205
+ results = []
206
+ results << %{<ul class="clean">}
207
+ Topic.visible.latest(6).each do |topic|
208
+ tag.locals.topic = topic
209
+ results << %{<li>#{tag.render('topic:summary')}</li>}
210
+ end
211
+ results << %{</ul>}
212
+ results
213
+ end
214
+
215
+ desc %{
216
+ Shows the standard forum search form in a reasonably compact and stylable way.
217
+
218
+ Takes options with_title (set to false to omit the usual heading), by_forum (set to true to show a discussion category dropdown) and by_reader (set to true to show a message-from dropdown) and label (set to the title you would like to display over the main search field).
219
+ }
220
+ tag 'forum_search' do |tag|
221
+ results = []
222
+ compact = true unless tag.attr['by_forum'] == 'true' || tag.attr['by_reader'] == 'true'
223
+ q_label = tag.attr['label']
224
+ q_label = "Look for this text" if q_label.blank? && !compact
225
+ results << %{<form class="friendly" action="#{search_posts_url}">}
226
+ results << %{<h2>Forum Search</h2>} unless tag.attr['with_title'] == 'false'
227
+ results << %{<p>}
228
+ results << %{<label for="q">#{q_label}</label><br />} unless q_label.blank?
229
+ results << %{#{text_field_tag("q", params[:q], :class => 'standard')}}
230
+ results << %{#{submit_tag "search", :class => 'button'}} if compact
231
+ results << %{</p>}
232
+ unless compact
233
+ results << %{<p><label for="reader_id">From this person</label><br /><select name="reader_id"><option value="">anyone</option>#{options_from_collection_for_select(Reader.all, "id", "name")}</select></p>} if tag.attr['by_reader'] == 'true'
234
+ results << %{<p><label for="forum_id">In this discussion category</label><br /><select name="forum_id"><option value="">anywhere</option>#{options_from_collection_for_select(Forum.visible, "id", "name")}</select></p>} if tag.attr['by_forum'] == 'true'
235
+ results << %{<p class="buttons">#{submit_tag "search", :class => 'button'}</p>}
236
+ end
237
+ results << %{</form>}
238
+ results
239
+ end
240
+
241
+ tag 'topic' do |tag|
242
+ tag.expand if tag.locals.topic
243
+ end
244
+ tag 'topic:summary' do |tag|
245
+ results = []
246
+ topic = tag.locals.topic
247
+ post = topic.last_post
248
+ results << %{<img src="#{post.reader.gravatar_url(:size => 42)}" width="42" height="42" class="gravatar"> } if tag.attr['gravatar'] == 'true'
249
+ results << %{<a href="#{forum_topic_path(topic.forum, topic)}">#{topic.name}</a> }
250
+ results << %{<span class="credit">}
251
+ if topic.page
252
+ results << " commented upon by "
253
+ elsif post.first?
254
+ results << " started by "
255
+ else
256
+ results << " replied to by "
257
+ end
258
+ results << "#{post.reader.name} #{friendly_date(post.created_at)}"
259
+ results << %{</span>}
260
+ results
261
+ end
262
+
263
+
264
+
265
+ private
266
+
267
+ # copied from forum_helper
268
+
269
+ def friendly_date(datetime)
270
+ if datetime
271
+ date = datetime.to_date
272
+ if (date == Date.today)
273
+ format = "today at %l:%M%p"
274
+ elsif (date == Date.yesterday)
275
+ format = "yesterday at %l:%M%p"
276
+ elsif (date.year == Date.today.year)
277
+ format = "on %B %e"
278
+ else
279
+ format = "on %B %e, %Y"
280
+ end
281
+ datetime.strftime(format)
282
+ end
283
+ end
284
+
285
+ end