radiant-forum-extension 0.6.1 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Rakefile +1 -3
- data/VERSION +1 -1
- data/app/controllers/forums_controller.rb +13 -5
- data/app/controllers/posts_controller.rb +9 -3
- data/app/controllers/topics_controller.rb +10 -3
- data/app/helpers/forum_helper.rb +105 -0
- data/app/models/post_attachment.rb +20 -12
- data/app/models/topic.rb +1 -1
- data/app/views/admin/forums/index.html.haml +63 -65
- data/app/views/admin/reader_configuration/_edit_forum.html.haml +6 -0
- data/app/views/admin/reader_configuration/_forum.html.haml +6 -0
- data/app/views/forums/_forum.html.haml +1 -2
- data/app/views/forums/index.html.haml +27 -25
- data/app/views/forums/show.html.haml +31 -19
- data/app/views/posts/_attachment.html.haml +5 -2
- data/app/views/posts/_form.html.haml +3 -5
- data/app/views/posts/_post.html.haml +32 -29
- data/app/views/posts/_uploader.html.haml +2 -3
- data/app/views/posts/index.html.haml +27 -11
- data/app/views/topics/_form.html.haml +3 -3
- data/app/views/topics/_topic.html.haml +10 -11
- data/app/views/topics/index.html.haml +28 -22
- data/app/views/topics/new.html.haml +32 -27
- data/app/views/topics/show.html.haml +25 -10
- data/config/locales/en.yml +58 -0
- data/config/routes.rb +5 -5
- data/forum_extension.rb +18 -11
- data/lib/forum_readers_controller.rb +1 -0
- data/lib/tasks/radiant_forum_extension_tasks.rake +54 -31
- data/public/images/{forum → furniture}/attachment.png +0 -0
- data/public/images/{forum → furniture}/attachment_link.png +0 -0
- data/public/images/{forum → furniture}/attachment_over.png +0 -0
- data/public/images/{forum → furniture}/chk_off.png +0 -0
- data/public/images/{forum → furniture}/chk_on.png +0 -0
- data/public/images/{forum → furniture}/feed_14.png +0 -0
- data/public/images/{forum → furniture}/feed_28.png +0 -0
- data/public/images/furniture/post.png +0 -0
- data/public/images/furniture/post_over.png +0 -0
- data/public/images/{forum → furniture}/rdo_off.png +0 -0
- data/public/images/{forum → furniture}/rdo_on.png +0 -0
- data/public/images/{forum → furniture}/wait_16_grey.gif +0 -0
- data/public/javascripts/forum.js +198 -0
- data/public/stylesheets/sass/forum.sass +247 -0
- data/radiant-forum-extension.gemspec +21 -28
- metadata +26 -53
- data/lib/forum_helper.rb +0 -109
- data/public/images/forum/post_14.png +0 -0
- data/public/images/forum/post_14_over.png +0 -0
- data/public/javascripts/platform/forum.js +0 -175
- data/public/javascripts/platform/remotecontent.js +0 -89
- data/public/stylesheets/admin/forum.css +0 -87
- data/public/stylesheets/platform/forum.css +0 -70
data/Rakefile
CHANGED
@@ -8,9 +8,7 @@ begin
|
|
8
8
|
gem.homepage = "http://github.com/spanner/radiant-forum-extension"
|
9
9
|
gem.authors = ["spanner"]
|
10
10
|
gem.add_dependency "radiant", ">= 0.9.0"
|
11
|
-
gem.add_dependency 'radiant-reader-extension'
|
12
|
-
gem.add_dependency 'paperclip'
|
13
|
-
gem.add_dependency 'will_paginate'
|
11
|
+
gem.add_dependency 'radiant-reader-extension', "~> 1.1.0"
|
14
12
|
end
|
15
13
|
rescue LoadError
|
16
14
|
puts "Jeweler (or a dependency) not available. This is only required if you plan to package forum as a gem."
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
|
1
|
+
1.1.0
|
@@ -1,21 +1,25 @@
|
|
1
1
|
class ForumsController < ReaderActionController
|
2
|
-
|
2
|
+
include Radiant::Pagination::Controller
|
3
|
+
helper :forum
|
4
|
+
|
5
|
+
before_filter :private_forum
|
3
6
|
before_filter :find_forum, :only => :show
|
4
7
|
before_filter :no_changes_here, :except => [:index, :show]
|
5
8
|
|
6
|
-
radiant_layout { |controller|
|
9
|
+
radiant_layout { |controller| Radiant::Config['forum.layout'] || Radiant::Config['reader.layout'] }
|
7
10
|
|
8
11
|
def index
|
9
|
-
|
12
|
+
# visible is an open scope that can be overridden in other extensions, ie group_forum
|
13
|
+
@forums = Forum.visible.paginate(:all, pagination_parameters.merge(:order => "position"))
|
10
14
|
end
|
11
15
|
|
12
16
|
def show
|
13
17
|
respond_to do |format|
|
14
18
|
format.html {
|
15
|
-
@topics = Topic.paginate_by_forum_id(params[:id], :
|
19
|
+
@topics = Topic.paginate_by_forum_id(params[:id], pagination_parameters.merge(:include => :replied_by, :order => 'sticky desc, replied_at desc'))
|
16
20
|
}
|
17
21
|
format.rss {
|
18
|
-
@topics = Topic.paginate_by_forum_id(params[:id], :
|
22
|
+
@topics = Topic.paginate_by_forum_id(params[:id], pagination_parameters.merge(:include => :replied_by, :order => 'replied_at desc'))
|
19
23
|
render :layout => 'feed'
|
20
24
|
}
|
21
25
|
end
|
@@ -27,6 +31,10 @@ class ForumsController < ReaderActionController
|
|
27
31
|
|
28
32
|
protected
|
29
33
|
|
34
|
+
def private_forum
|
35
|
+
return false unless Radiant::Config['forum.public?'] || require_reader && require_activated_reader
|
36
|
+
end
|
37
|
+
|
30
38
|
def find_forum
|
31
39
|
@forum = Forum.find(params[:id])
|
32
40
|
end
|
@@ -1,6 +1,8 @@
|
|
1
1
|
class PostsController < ReaderActionController
|
2
|
+
include Radiant::Pagination::Controller
|
3
|
+
helper :forum
|
2
4
|
|
3
|
-
before_filter :
|
5
|
+
before_filter :private_forum
|
4
6
|
before_filter :require_activated_reader, :except => [:index, :show, :search]
|
5
7
|
before_filter :find_topic_or_page, :except => [:index, :search]
|
6
8
|
before_filter :require_unlocked_topic_and_page, :only => [:new, :create]
|
@@ -8,7 +10,7 @@ class PostsController < ReaderActionController
|
|
8
10
|
before_filter :build_post, :only => [:new]
|
9
11
|
before_filter :require_authority, :only => [:edit, :update, :destroy]
|
10
12
|
|
11
|
-
radiant_layout { |controller|
|
13
|
+
radiant_layout { |controller| Radiant::Config['forum.layout'] || Radiant::Config['reader.layout'] }
|
12
14
|
|
13
15
|
@@default_query_options = {
|
14
16
|
:page => 1,
|
@@ -18,7 +20,7 @@ class PostsController < ReaderActionController
|
|
18
20
|
}
|
19
21
|
|
20
22
|
def index
|
21
|
-
@posts = Post.visible.paginate(:all, @@default_query_options.merge(
|
23
|
+
@posts = Post.visible.paginate(:all, @@default_query_options.merge(pagination_parameters))
|
22
24
|
render_page_or_feed
|
23
25
|
end
|
24
26
|
|
@@ -164,6 +166,10 @@ class PostsController < ReaderActionController
|
|
164
166
|
|
165
167
|
protected
|
166
168
|
|
169
|
+
def private_forum
|
170
|
+
return false unless Radiant::Config['forum.public?'] || require_reader && require_activated_reader
|
171
|
+
end
|
172
|
+
|
167
173
|
def require_authority
|
168
174
|
(current_user && current_user.admin?) || @post.editable_by?(current_reader) # includes an editable-interval check
|
169
175
|
end
|
@@ -1,13 +1,16 @@
|
|
1
1
|
class TopicsController < ReaderActionController
|
2
|
+
include Radiant::Pagination::Controller
|
3
|
+
helper :forum
|
2
4
|
|
5
|
+
before_filter :private_forum
|
3
6
|
before_filter :find_forum_and_topic, :except => :index
|
4
7
|
before_filter :require_activated_reader, :except => [:index, :show]
|
5
8
|
|
6
|
-
radiant_layout { |controller|
|
9
|
+
radiant_layout { |controller| Radiant::Config['forum.layout'] || Radiant::Config['reader.layout'] }
|
7
10
|
|
8
11
|
def index
|
9
12
|
params[:per_page] ||= 20
|
10
|
-
@topics = Topic.visible.paginate(:all, :order => "topics.sticky desc, topics.replied_at desc", :
|
13
|
+
@topics = Topic.visible.paginate(:all, pagination_parameters.merge(:order => "topics.sticky desc, topics.replied_at desc", :include => [:forum, :reader]))
|
11
14
|
render_page_or_feed
|
12
15
|
end
|
13
16
|
|
@@ -67,7 +70,11 @@ class TopicsController < ReaderActionController
|
|
67
70
|
end
|
68
71
|
|
69
72
|
protected
|
70
|
-
|
73
|
+
|
74
|
+
def private_forum
|
75
|
+
return false unless Radiant::Config['forum.public?'] || require_reader && require_activated_reader
|
76
|
+
end
|
77
|
+
|
71
78
|
def find_forum_and_topic
|
72
79
|
@topic = Topic.find(params[:id]) if params[:id]
|
73
80
|
@page = @topic.page if @topic
|
@@ -0,0 +1,105 @@
|
|
1
|
+
require 'sanitize'
|
2
|
+
module ForumHelper
|
3
|
+
|
4
|
+
def home_page_link(options={})
|
5
|
+
home_page = (defined? Site && Site.current) ? Site.current.home_page : Page.find_by_parent_id(nil)
|
6
|
+
link_to home_page.title, home_page.url, options
|
7
|
+
end
|
8
|
+
|
9
|
+
def feed_tag(text, url, options={})
|
10
|
+
link_to text, url, options.merge(:class => 'floating feedlink')
|
11
|
+
end
|
12
|
+
|
13
|
+
def feed_link(url)
|
14
|
+
link_to image_tag('/images/forum/feed_14.png', :class => 'feedicon', :alt => t('rss_feed'), :size => '14x20'), url
|
15
|
+
end
|
16
|
+
|
17
|
+
def clean_textilize(text) # adding smilies to the default reader method
|
18
|
+
if text.blank?
|
19
|
+
""
|
20
|
+
else
|
21
|
+
textilized = RedCloth.new(text, [ :hard_breaks ])
|
22
|
+
textilized.hard_breaks = true if textilized.respond_to?("hard_breaks=")
|
23
|
+
Sanitize.clean(textilized.to_html(:textile, :smilies), Sanitize::Config::RELAXED)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def watch_tag(topic, label='watching', formclass=nil)
|
28
|
+
if current_user
|
29
|
+
monitoring = current_user.monitoring?(topic)
|
30
|
+
%{
|
31
|
+
<form action="#{monitorship_path(topic.forum, topic)}" method="post" class="#{formclass}"><div>
|
32
|
+
<input id="monitor_checkbox_#{topic.id}" name="monitor_checkbox" class="monitor_checkbox" type="checkbox"#{ ' checked="checked"' if monitoring } />
|
33
|
+
<label class="monitor_label" for="monitor_checkbox_#{topic.id}">#{label}</label>
|
34
|
+
#{hidden_field_tag '_method', monitoring ? 'delete' : ''}
|
35
|
+
#{submit_tag :set, :class => 'monitor_submit'}
|
36
|
+
</div></form>
|
37
|
+
}
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def paged_post_url(post)
|
42
|
+
if post.first?
|
43
|
+
topic_post_url(post.topic, post, :page => post.topic_page, :anchor => post.dom_id)
|
44
|
+
else
|
45
|
+
topic_post_url(post.topic, post, :page => post.topic_page, :anchor => post.dom_id)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def edit_link(post)
|
50
|
+
link_to t('edit'), edit_topic_post_url(post.topic, post), :class => 'edit_post', :id => "edit_post_#{post.id}", :title => t("edit_post")
|
51
|
+
end
|
52
|
+
|
53
|
+
def remove_link(post)
|
54
|
+
link_to t('delete'), topic_post_url(post.topic, post), :method => 'delete', :class => 'delete_post', :id => "delete_post_#{post.id}", :title => t("remove_post"), :confirm => t('really_remove_post')
|
55
|
+
end
|
56
|
+
|
57
|
+
def friendly_date(datetime)
|
58
|
+
if datetime
|
59
|
+
date = datetime.to_date
|
60
|
+
if (date.to_datetime == Date.today)
|
61
|
+
format = t('time_today')
|
62
|
+
elsif (date.to_datetime == Date.yesterday)
|
63
|
+
format = t('time_yesterday')
|
64
|
+
elsif (date.to_datetime > 6.days.ago)
|
65
|
+
format = t('time_recently')
|
66
|
+
elsif (date.year == Date.today.year)
|
67
|
+
format = t('date_this_year')
|
68
|
+
else
|
69
|
+
format = t('standard_date')
|
70
|
+
end
|
71
|
+
datetime.strftime(format)
|
72
|
+
else
|
73
|
+
t("unknown_date")
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def pagination_and_summary_for(list, name='')
|
78
|
+
%{<div class="pagination">
|
79
|
+
#{will_paginate list, :container => false}
|
80
|
+
<span class="pagination_summary">
|
81
|
+
#{pagination_summary(list, name)}
|
82
|
+
</span>
|
83
|
+
</div>
|
84
|
+
}
|
85
|
+
end
|
86
|
+
|
87
|
+
def pagination_summary(list, name='')
|
88
|
+
total = list.total_entries
|
89
|
+
if list.empty?
|
90
|
+
%{#{t('no')} #{name.pluralize}}
|
91
|
+
else
|
92
|
+
name ||= t(list.first.class.to_s.underscore.gsub('_', ' '))
|
93
|
+
if total == 1
|
94
|
+
%{#{t('showing')} #{t('one')} #{name}}
|
95
|
+
elsif list.current_page == 1 && total < list.per_page
|
96
|
+
%{#{t('all')} #{total} #{name.pluralize}}
|
97
|
+
else
|
98
|
+
start = list.offset + 1
|
99
|
+
finish = ((list.offset + list.per_page) < list.total_entries) ? list.offset + list.per_page : list.total_entries
|
100
|
+
%{#{start} #{t('to')} #{finish} #{t('of')} #{total} #{name.pluralize}}
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
end
|
@@ -9,15 +9,11 @@ class PostAttachment < ActiveRecord::Base
|
|
9
9
|
end
|
10
10
|
|
11
11
|
def thumbnail_sizes
|
12
|
-
|
13
|
-
thumbnails = Radiant::Config["assets.additional_thumbnails"].split(', ').collect{|s| s.split('=')}.inject({}) {|ha, (k, v)| ha[k.to_sym] = v; ha}
|
14
|
-
else
|
15
|
-
thumbnails = {}
|
16
|
-
end
|
17
|
-
thumbnails.merge({
|
12
|
+
{
|
18
13
|
:icon => ['24x24#', :png],
|
19
|
-
:thumbnail => ['100x100>', :png]
|
20
|
-
|
14
|
+
:thumbnail => ['100x100>', :png],
|
15
|
+
:inline => ['640x640>']
|
16
|
+
}
|
21
17
|
end
|
22
18
|
|
23
19
|
def thumbnail_names
|
@@ -29,7 +25,13 @@ class PostAttachment < ActiveRecord::Base
|
|
29
25
|
belongs_to :reader
|
30
26
|
acts_as_list :scope => :post_id
|
31
27
|
has_attached_file :file,
|
32
|
-
:styles =>
|
28
|
+
:styles => lambda { |attachment|
|
29
|
+
if image_content_types.include? attachment.instance_read(:content_type)
|
30
|
+
thumbnail_sizes
|
31
|
+
else
|
32
|
+
{}
|
33
|
+
end
|
34
|
+
},
|
33
35
|
:whiny_thumbnails => false,
|
34
36
|
:url => "/:class/:id/:basename:no_original_style.:extension",
|
35
37
|
:path => ":rails_root/public/:class/:id/:basename:no_original_style.:extension"
|
@@ -39,6 +41,9 @@ class PostAttachment < ActiveRecord::Base
|
|
39
41
|
validates_attachment_content_type :file, :content_type => Radiant::Config["forum.attachment_content_types"].split(', ') if Radiant::Config.table_exists? && Radiant::Config["forum.attachment_content_types"]
|
40
42
|
validates_attachment_size :file, :less_than => Radiant::Config["forum.max_attachment_size"].to_i.megabytes if Radiant::Config.table_exists? && Radiant::Config["forum.max_attachment_size"]
|
41
43
|
|
44
|
+
named_scope :images, :conditions => ["file_content_type IN (#{image_content_types.map{'?'}.join(',')})", *image_content_types]
|
45
|
+
named_scope :non_images, :conditions => ["file_content_type NOT IN (#{image_content_types.map{'?'}.join(',')})", *image_content_types]
|
46
|
+
|
42
47
|
def image?
|
43
48
|
self.class.image?(file_content_type)
|
44
49
|
end
|
@@ -54,14 +59,17 @@ class PostAttachment < ActiveRecord::Base
|
|
54
59
|
def extension
|
55
60
|
filename.split('.').last.downcase if filename
|
56
61
|
end
|
57
|
-
|
62
|
+
|
63
|
+
def thumbnail
|
64
|
+
file.url(:thumbnail) if image?
|
65
|
+
end
|
66
|
+
|
58
67
|
def icon
|
59
68
|
iconpath = Radiant::Config.table_exists? && Radiant::Config['forum.icon_path'] ? Radiant::Config['forum.icon_path'] : '/images/forum/icons'
|
60
69
|
if image?
|
61
|
-
|
70
|
+
file.url(:icon)
|
62
71
|
else
|
63
72
|
icon = File.join(RAILS_ROOT, 'public', iconpath, "#{extension}.png")
|
64
|
-
logger.warn "!! looking for #{icon}"
|
65
73
|
if File.exists? icon
|
66
74
|
"#{iconpath}/#{extension}.png"
|
67
75
|
else
|
data/app/models/topic.rb
CHANGED
@@ -1,73 +1,71 @@
|
|
1
1
|
- include_stylesheet('admin/forum')
|
2
2
|
= render_region :top
|
3
3
|
|
4
|
-
|
5
|
-
%
|
4
|
+
#forums_table.outset
|
5
|
+
%table#forums.index{:cellspacing=>"0", :border=>"0", :cellpadding=>"0"}
|
6
|
+
%thead
|
7
|
+
%tr
|
8
|
+
- render_region :thead do |thead|
|
9
|
+
- thead.title_header do
|
10
|
+
%th.forum Forum
|
11
|
+
- thead.latest_header do
|
12
|
+
%th.forum_latest Latest activity
|
13
|
+
- thead.modify_header do
|
14
|
+
%th.action Action
|
15
|
+
%tbody
|
16
|
+
- @forums.each do |forum|
|
17
|
+
%tr.node.level-1
|
18
|
+
- render_region :tbody do |tbody|
|
6
19
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
- thead.modify_header do
|
16
|
-
%th.action Action
|
17
|
-
%tbody
|
18
|
-
- @forums.each do |forum|
|
19
|
-
%tr.node.level-1
|
20
|
-
- render_region :tbody do |tbody|
|
21
|
-
|
22
|
-
- tbody.title_cell do
|
23
|
-
%td.forum
|
24
|
-
%h3
|
25
|
-
= link_to forum.name, edit_admin_forum_url(:id => forum)
|
26
|
-
-if forum.for_comments
|
27
|
-
%p.context
|
28
|
-
(comments forum)
|
29
|
-
= textilize(forum.description)
|
30
|
-
%p
|
31
|
-
- if forum.respond_to?(:site)
|
32
|
-
= link_to "→ browse discussions", 'http://' + forum.site.base_domain + forum_path(forum)
|
33
|
-
- else
|
34
|
-
= link_to "→ browse discussions", forum_path(forum)
|
35
|
-
|
36
|
-
- tbody.latest_cell do
|
37
|
-
%td.forum_latest
|
38
|
-
- if forum.posts_count > 0
|
39
|
-
%ul.latest
|
40
|
-
- forum.recent_topics.latest.each do |topic|
|
41
|
-
%li.topic
|
42
|
-
- if topic.page
|
43
|
-
= link_to topic.page.title, "#{topic.page.url}", {:class => 'topic'}
|
44
|
-
- else
|
45
|
-
= link_to topic.name, forum_topic_url(topic.forum, topic, :page => 'last'), {:class => 'topic'}
|
46
|
-
%br
|
47
|
-
%span.context
|
48
|
-
from
|
49
|
-
- if topic.posts.count > 1
|
50
|
-
= link_to topic.reader.name + '.', reader_url(topic.reader)
|
51
|
-
= topic.posts.count
|
52
|
-
= pluralize(topic.posts.count, 'comment') + ','
|
53
|
-
most recently from
|
54
|
-
= link_to topic.replied_by.name, reader_url(topic.replied_by)
|
55
|
-
= friendly_date(topic.replied_at)
|
56
|
-
- else
|
57
|
-
= link_to topic.reader.name, reader_url(topic.reader)
|
58
|
-
= friendly_date(topic.created_at)
|
59
|
-
-else
|
20
|
+
- tbody.title_cell do
|
21
|
+
%td.forum
|
22
|
+
%h3
|
23
|
+
= link_to forum.name, edit_admin_forum_url(:id => forum)
|
24
|
+
-if forum.for_comments
|
25
|
+
%p.context
|
26
|
+
(comments forum)
|
27
|
+
= textilize(forum.description)
|
60
28
|
%p
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
29
|
+
- if forum.respond_to?(:site)
|
30
|
+
= link_to "→ browse discussions", 'http://' + forum.site.base_domain + forum_path(forum)
|
31
|
+
- else
|
32
|
+
= link_to "→ browse discussions", forum_path(forum)
|
33
|
+
|
34
|
+
- tbody.latest_cell do
|
35
|
+
%td.forum_latest
|
36
|
+
- if forum.posts_count > 0
|
37
|
+
%ul.latest
|
38
|
+
- forum.recent_topics.latest.each do |topic|
|
39
|
+
%li.topic
|
40
|
+
- if topic.page
|
41
|
+
= link_to topic.page.title, "#{topic.page.url}", {:class => 'topic'}
|
42
|
+
- else
|
43
|
+
= link_to topic.name, forum_topic_url(topic.forum, topic, :page => 'last'), {:class => 'topic'}
|
44
|
+
%br
|
45
|
+
%span.context
|
46
|
+
from
|
47
|
+
- if topic.posts.count > 1
|
48
|
+
= link_to topic.reader.name + '.', reader_url(topic.reader)
|
49
|
+
= topic.posts.count
|
50
|
+
= pluralize(topic.posts.count, 'comment') + ','
|
51
|
+
most recently from
|
52
|
+
= link_to topic.replied_by.name, reader_url(topic.replied_by)
|
53
|
+
= friendly_date(topic.replied_at)
|
54
|
+
- else
|
55
|
+
= link_to topic.reader.name, reader_url(topic.reader)
|
56
|
+
= friendly_date(topic.created_at)
|
57
|
+
-else
|
58
|
+
%p
|
59
|
+
No topics yet.
|
60
|
+
- tbody.modify_cell do
|
61
|
+
%td.remove
|
62
|
+
-if forum.for_comments
|
63
|
+
= image('noremove', :alt => 'Remove Forum')
|
64
|
+
-else
|
65
|
+
- if admin?
|
66
|
+
= link_to image('remove', :alt => 'Remove Forum'), remove_admin_forum_url(:id => forum)
|
67
|
+
- else
|
68
|
+
= image('noremove', :alt => 'only admin can remove forums')
|
71
69
|
|
72
70
|
- render_region :bottom do |bottom|
|
73
71
|
- bottom.new_button do
|