activeadmin 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of activeadmin might be problematic. Click here for more details.
- data/.document +5 -0
- data/.gitignore +25 -0
- data/Gemfile +16 -0
- data/LICENSE +20 -0
- data/README.rdoc +201 -0
- data/Rakefile +71 -0
- data/active_admin.gemspec +22 -0
- data/lib/active_admin.rb +229 -0
- data/lib/active_admin/action_builder.rb +60 -0
- data/lib/active_admin/action_items.rb +48 -0
- data/lib/active_admin/asset_registration.rb +34 -0
- data/lib/active_admin/breadcrumbs.rb +26 -0
- data/lib/active_admin/dashboards.rb +50 -0
- data/lib/active_admin/dashboards/dashboard_controller.rb +40 -0
- data/lib/active_admin/dashboards/renderer.rb +45 -0
- data/lib/active_admin/dashboards/section.rb +43 -0
- data/lib/active_admin/dashboards/section_renderer.rb +28 -0
- data/lib/active_admin/filters.rb +189 -0
- data/lib/active_admin/form_builder.rb +91 -0
- data/lib/active_admin/helpers/optional_display.rb +34 -0
- data/lib/active_admin/menu.rb +42 -0
- data/lib/active_admin/menu_item.rb +67 -0
- data/lib/active_admin/namespace.rb +111 -0
- data/lib/active_admin/page_config.rb +15 -0
- data/lib/active_admin/pages.rb +11 -0
- data/lib/active_admin/pages/base.rb +92 -0
- data/lib/active_admin/pages/edit.rb +21 -0
- data/lib/active_admin/pages/index.rb +58 -0
- data/lib/active_admin/pages/index/blog.rb +65 -0
- data/lib/active_admin/pages/index/table.rb +48 -0
- data/lib/active_admin/pages/index/thumbnails.rb +40 -0
- data/lib/active_admin/pages/new.rb +21 -0
- data/lib/active_admin/pages/show.rb +54 -0
- data/lib/active_admin/renderer.rb +72 -0
- data/lib/active_admin/resource.rb +96 -0
- data/lib/active_admin/resource_controller.rb +325 -0
- data/lib/active_admin/sidebar.rb +78 -0
- data/lib/active_admin/table_builder.rb +162 -0
- data/lib/active_admin/tabs_renderer.rb +39 -0
- data/lib/active_admin/version.rb +3 -0
- data/lib/active_admin/view_helpers.rb +106 -0
- data/lib/active_admin/views/active_admin_dashboard/index.html.erb +1 -0
- data/lib/active_admin/views/active_admin_default/edit.html.erb +1 -0
- data/lib/active_admin/views/active_admin_default/index.csv.erb +2 -0
- data/lib/active_admin/views/active_admin_default/index.html.erb +1 -0
- data/lib/active_admin/views/active_admin_default/new.html.erb +1 -0
- data/lib/active_admin/views/active_admin_default/show.html.erb +1 -0
- data/lib/active_admin/views/layouts/active_admin.html.erb +40 -0
- data/lib/activeadmin.rb +1 -0
- data/lib/generators/active_admin/install/install_generator.rb +31 -0
- data/lib/generators/active_admin/install/templates/active_admin.css +325 -0
- data/lib/generators/active_admin/install/templates/active_admin.js +10 -0
- data/lib/generators/active_admin/install/templates/active_admin.rb +47 -0
- data/lib/generators/active_admin/install/templates/active_admin_vendor.js +382 -0
- data/lib/generators/active_admin/install/templates/dashboards.rb +36 -0
- data/lib/generators/active_admin/install/templates/images/orderable.gif +0 -0
- data/lib/generators/active_admin/resource/resource_generator.rb +16 -0
- data/lib/generators/active_admin/resource/templates/admin.rb +3 -0
- data/spec/integration/dashboard_spec.rb +44 -0
- data/spec/integration/index_as_blog_spec.rb +65 -0
- data/spec/integration/index_as_csv_spec.rb +40 -0
- data/spec/integration/index_as_table_spec.rb +160 -0
- data/spec/integration/index_as_thumbnails_spec.rb +43 -0
- data/spec/integration/layout_spec.rb +82 -0
- data/spec/integration/new_view_spec.rb +52 -0
- data/spec/integration/show_view_spec.rb +91 -0
- data/spec/spec_helper.rb +104 -0
- data/spec/support/rails_template.rb +19 -0
- data/spec/unit/action_builder_spec.rb +76 -0
- data/spec/unit/action_items_spec.rb +41 -0
- data/spec/unit/active_admin_spec.rb +52 -0
- data/spec/unit/asset_registration_spec.rb +37 -0
- data/spec/unit/controller_filters_spec.rb +26 -0
- data/spec/unit/dashboard_section_spec.rb +63 -0
- data/spec/unit/dashboards_spec.rb +59 -0
- data/spec/unit/filter_form_builder_spec.rb +157 -0
- data/spec/unit/form_builder_spec.rb +238 -0
- data/spec/unit/menu_item_spec.rb +137 -0
- data/spec/unit/menu_spec.rb +53 -0
- data/spec/unit/namespace_spec.rb +107 -0
- data/spec/unit/registration_spec.rb +46 -0
- data/spec/unit/renderer_spec.rb +100 -0
- data/spec/unit/resource_controller_spec.rb +48 -0
- data/spec/unit/resource_spec.rb +197 -0
- data/spec/unit/routing_spec.rb +12 -0
- data/spec/unit/sidebar_spec.rb +96 -0
- data/spec/unit/table_builder_spec.rb +162 -0
- data/spec/unit/tabs_renderer_spec.rb +34 -0
- metadata +247 -0
@@ -0,0 +1,21 @@
|
|
1
|
+
module ActiveAdmin
|
2
|
+
module Pages
|
3
|
+
class Edit < Base
|
4
|
+
|
5
|
+
def title
|
6
|
+
"Edit #{active_admin_config.resource_name}"
|
7
|
+
end
|
8
|
+
|
9
|
+
def main_content
|
10
|
+
config = self.form_config.dup
|
11
|
+
config.delete(:block)
|
12
|
+
config.reverse_merge!({
|
13
|
+
:url => resource_path(resource)
|
14
|
+
})
|
15
|
+
|
16
|
+
active_admin_form_for resource, config, &form_config[:block]
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
module ActiveAdmin
|
2
|
+
module Pages
|
3
|
+
|
4
|
+
class Index < Base
|
5
|
+
|
6
|
+
# Default Index config styles. Each of these are what
|
7
|
+
# actually gets rendered in the main content and configured
|
8
|
+
# through the :as option when configuring your index
|
9
|
+
autoload :Table, 'active_admin/pages/index/table'
|
10
|
+
autoload :Blog, 'active_admin/pages/index/blog'
|
11
|
+
autoload :Thumbnails, 'active_admin/pages/index/thumbnails'
|
12
|
+
|
13
|
+
def title
|
14
|
+
active_admin_config.plural_resource_name
|
15
|
+
end
|
16
|
+
|
17
|
+
def config
|
18
|
+
index_config || default_index_config
|
19
|
+
end
|
20
|
+
|
21
|
+
# Render's the index configuration that was set in the
|
22
|
+
# controller. Defaults to rendering the ActiveAdmin::Pages::Index::Table
|
23
|
+
def main_content
|
24
|
+
renderer_class = find_index_renderer_class(config[:as])
|
25
|
+
renderer_class.new(self).to_html(config, collection)
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
# Creates a default configuration for the resource class. This is a table
|
31
|
+
# with each column displayed as well as all the default actions
|
32
|
+
def default_index_config
|
33
|
+
@default_index_config ||= ::ActiveAdmin::PageConfig.new(:as => :table) do |display|
|
34
|
+
display.id
|
35
|
+
resource_class.content_columns.each do |column|
|
36
|
+
display.column column.name.to_sym
|
37
|
+
end
|
38
|
+
display.default_actions
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# Returns the actual class for renderering the main content on the index
|
43
|
+
# page. To set this, use the :as option in the page_config block.
|
44
|
+
def find_index_renderer_class(symbol_or_class)
|
45
|
+
case symbol_or_class
|
46
|
+
when Symbol
|
47
|
+
::ActiveAdmin::Pages::Index.const_get(symbol_or_class.to_s.camelcase)
|
48
|
+
when Class
|
49
|
+
symbol_or_class
|
50
|
+
else
|
51
|
+
raise ArgumentError, "'as' requires a class or a symbol"
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
@@ -0,0 +1,65 @@
|
|
1
|
+
module ActiveAdmin
|
2
|
+
module Pages
|
3
|
+
class Index
|
4
|
+
|
5
|
+
class Blog < Renderer
|
6
|
+
|
7
|
+
def to_html(page_config, collection)
|
8
|
+
@page_config = page_config
|
9
|
+
@config = Builder.new
|
10
|
+
@page_config.block.call(@config) if @page_config.block
|
11
|
+
|
12
|
+
wrap_with_pagination(collection, :entry_name => active_admin_config.resource_name) do
|
13
|
+
content_tag :div do
|
14
|
+
collection.collect{|item| render_post(item) }.join
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def render_post(post)
|
22
|
+
content_tag_for :div, post do
|
23
|
+
title = content_tag :h3, link_to(post_title_content(post), resource_path(post))
|
24
|
+
main_content = content_tag(:div, post_content(post), :class => 'content')
|
25
|
+
title + main_content
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def post_title_content(post)
|
30
|
+
call_method_or_proc_on(post, @config.title) || "#{active_admin_config.resource_name} #{post.id}"
|
31
|
+
end
|
32
|
+
|
33
|
+
def post_content(post)
|
34
|
+
call_method_or_proc_on(post, @config.content) || ""
|
35
|
+
end
|
36
|
+
|
37
|
+
|
38
|
+
# A small builder class which gets passed into the block when defining
|
39
|
+
# the options to display as posts.
|
40
|
+
#
|
41
|
+
# ActiveAdmin.register Post
|
42
|
+
# index :as => :posts do |i|
|
43
|
+
# # i is an instance of Builder
|
44
|
+
# end
|
45
|
+
# end
|
46
|
+
class Builder
|
47
|
+
def title(method = nil, &block)
|
48
|
+
if block_given? || method
|
49
|
+
@title = block_given? ? block : method
|
50
|
+
end
|
51
|
+
@title
|
52
|
+
end
|
53
|
+
|
54
|
+
def content(method = nil, &block)
|
55
|
+
if block_given? || method
|
56
|
+
@content = block_given? ? block : method
|
57
|
+
end
|
58
|
+
@content
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
end # Posts
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module ActiveAdmin
|
2
|
+
module Pages
|
3
|
+
class Index
|
4
|
+
|
5
|
+
class Table < Renderer
|
6
|
+
|
7
|
+
def to_html(page_config, collection)
|
8
|
+
wrap_with_pagination(collection, :entry_name => active_admin_config.resource_name) do
|
9
|
+
table_options = {
|
10
|
+
:id => active_admin_config.plural_resource_name.underscore,
|
11
|
+
:sortable => true,
|
12
|
+
:class => "index_table"
|
13
|
+
}
|
14
|
+
TableBuilder.new(&page_config.block).to_html(self, collection, table_options)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
#
|
19
|
+
# Extend the default ActiveAdmin::TableBuilder with some
|
20
|
+
# methods for quickly displaying items on the index page
|
21
|
+
#
|
22
|
+
class TableBuilder < ::ActiveAdmin::TableBuilder
|
23
|
+
|
24
|
+
# Display a column for the id
|
25
|
+
def id
|
26
|
+
column 'ID', :id
|
27
|
+
end
|
28
|
+
|
29
|
+
# Adds links to View, Edit and Delete
|
30
|
+
def default_actions(options = {})
|
31
|
+
options = {
|
32
|
+
:name => ""
|
33
|
+
}.merge(options)
|
34
|
+
column options[:name] do |resource|
|
35
|
+
links = link_to "View", resource_path(resource)
|
36
|
+
links += " | "
|
37
|
+
links += link_to "Edit", edit_resource_path(resource)
|
38
|
+
links += " | "
|
39
|
+
links += link_to "Delete", resource_path(resource), :method => :delete, :confirm => "Are you sure you want to delete this?"
|
40
|
+
links
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end # TableBuilder
|
44
|
+
|
45
|
+
end # Table
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module ActiveAdmin
|
2
|
+
module Pages
|
3
|
+
class Index
|
4
|
+
|
5
|
+
class Thumbnails < Renderer
|
6
|
+
|
7
|
+
def to_html(page_config, collection)
|
8
|
+
@page_config = page_config
|
9
|
+
@config = Builder.new
|
10
|
+
@page_config.block.call(@config) if @page_config.block
|
11
|
+
|
12
|
+
wrap_with_pagination(collection, :entry_name => active_admin_config.resource_name) do
|
13
|
+
content_tag :div, :style => "clear:both;" do
|
14
|
+
collection.collect{|item| render_image(item) }.join
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def render_image(item)
|
20
|
+
link_to tag("img", :src => thumbnail_url(item), :width => 200, :height => 200),
|
21
|
+
resource_path(item)
|
22
|
+
end
|
23
|
+
|
24
|
+
def thumbnail_url(item)
|
25
|
+
call_method_or_proc_on(item, @config.image)
|
26
|
+
end
|
27
|
+
|
28
|
+
class Builder
|
29
|
+
def image(method = nil, &block)
|
30
|
+
if block_given? || method
|
31
|
+
@image = block_given? ? block : method
|
32
|
+
end
|
33
|
+
@image
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module ActiveAdmin
|
2
|
+
module Pages
|
3
|
+
class New < Base
|
4
|
+
|
5
|
+
def title
|
6
|
+
"New #{active_admin_config.resource_name}"
|
7
|
+
end
|
8
|
+
|
9
|
+
def main_content
|
10
|
+
config = self.form_config.dup
|
11
|
+
config.delete(:block)
|
12
|
+
config.reverse_merge!({
|
13
|
+
:url => collection_path
|
14
|
+
})
|
15
|
+
|
16
|
+
active_admin_form_for resource, config, &form_config[:block]
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module ActiveAdmin
|
2
|
+
module Pages
|
3
|
+
class Show < Base
|
4
|
+
|
5
|
+
def config
|
6
|
+
active_admin_config.page_configs[:show] || ::ActiveAdmin::PageConfig.new
|
7
|
+
end
|
8
|
+
|
9
|
+
def title
|
10
|
+
case config[:title]
|
11
|
+
when Symbol, Proc
|
12
|
+
call_method_or_proc_on(resource, config[:title])
|
13
|
+
when String
|
14
|
+
config[:title]
|
15
|
+
else
|
16
|
+
default_title
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def main_content
|
21
|
+
if config.block
|
22
|
+
# Eval the show config from the controller
|
23
|
+
instance_eval &config.block
|
24
|
+
else
|
25
|
+
default_main_content
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
protected
|
30
|
+
|
31
|
+
def default_title
|
32
|
+
"#{active_admin_config.resource_name} ##{resource.id}"
|
33
|
+
end
|
34
|
+
|
35
|
+
def default_main_content
|
36
|
+
table_options = {
|
37
|
+
:border => 0,
|
38
|
+
:cellpadding => 0,
|
39
|
+
:cellspacing => 0,
|
40
|
+
:id => "#{resource_class.name.underscore}_attributes",
|
41
|
+
:class => "resource_attributes"
|
42
|
+
}
|
43
|
+
content_tag :table, table_options do
|
44
|
+
show_view_columns.collect do |attr|
|
45
|
+
content_tag :tr do
|
46
|
+
content_tag(:th, attr.to_s.titlecase) + content_tag(:td, resource.send(attr))
|
47
|
+
end
|
48
|
+
end.join
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
module ActiveAdmin
|
2
|
+
class Renderer
|
3
|
+
|
4
|
+
attr_accessor :view, :assigns
|
5
|
+
|
6
|
+
def initialize(view_or_renderer)
|
7
|
+
@view = view_or_renderer.is_a?(Renderer) ? view_or_renderer.view : view_or_renderer
|
8
|
+
@assigns = view.assigns.each { |key, value| instance_variable_set("@#{key}", value) }
|
9
|
+
end
|
10
|
+
|
11
|
+
def method_missing(*args, &block)
|
12
|
+
view.send(*args, &block)
|
13
|
+
end
|
14
|
+
|
15
|
+
def to_html(*args)
|
16
|
+
end
|
17
|
+
|
18
|
+
def to_s(*args)
|
19
|
+
to_html(*args)
|
20
|
+
end
|
21
|
+
|
22
|
+
def haml(template)
|
23
|
+
begin
|
24
|
+
require 'haml' unless defined?(Haml)
|
25
|
+
rescue LoadError
|
26
|
+
raise LoadError, "Please install the HAML gem to use the HAML method with ActiveAdmin"
|
27
|
+
end
|
28
|
+
|
29
|
+
# Find the first non whitespace character in the template
|
30
|
+
indent = template.index(/\S/)
|
31
|
+
|
32
|
+
# Remove the indent if its greater than 0
|
33
|
+
if indent > 0
|
34
|
+
template = template.split("\n").collect do |line|
|
35
|
+
line[indent..-1]
|
36
|
+
end.join("\n")
|
37
|
+
end
|
38
|
+
|
39
|
+
# Render it baby
|
40
|
+
Haml::Engine.new(template).render(self)
|
41
|
+
end
|
42
|
+
|
43
|
+
protected
|
44
|
+
|
45
|
+
# Although we make a copy of all the instance variables on the way in, it
|
46
|
+
# doesn't mean that we can set new instance variables that are stored in
|
47
|
+
# the context of the view. This method allows you to do that. It can be useful
|
48
|
+
# when trying to share variables with a layout.
|
49
|
+
def set_ivar_on_view(name, value)
|
50
|
+
view.instance_variable_set(name, value)
|
51
|
+
end
|
52
|
+
|
53
|
+
# Many times throughout the views we want to either call a method on an object
|
54
|
+
# or instance_exec a proc passing in the object as the first parameter. This
|
55
|
+
# method takes care of this functionality.
|
56
|
+
#
|
57
|
+
# call_method_or_proc_on(@my_obj, :size) same as @my_obj.size
|
58
|
+
# OR
|
59
|
+
# proc = Proc.new{|s| s.size }
|
60
|
+
# call_method_or_proc_on(@my_obj, proc)
|
61
|
+
#
|
62
|
+
def call_method_or_proc_on(obj, symbol_or_proc)
|
63
|
+
case symbol_or_proc
|
64
|
+
when Symbol, String
|
65
|
+
obj.send(symbol_or_proc.to_sym)
|
66
|
+
when Proc
|
67
|
+
instance_exec(obj, &symbol_or_proc)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
module ActiveAdmin
|
2
|
+
class Resource
|
3
|
+
|
4
|
+
attr_reader :namespace, :resource, :page_configs, :member_actions, :collection_actions,
|
5
|
+
:parent_menu_item_name
|
6
|
+
attr_accessor :resource_name, :sort_order, :scope_to, :scope_to_association_method
|
7
|
+
|
8
|
+
def initialize(namespace, resource, options = {})
|
9
|
+
@namespace = namespace
|
10
|
+
@resource = resource
|
11
|
+
@options = default_options.merge(options)
|
12
|
+
@sort_order = @options[:sort_order]
|
13
|
+
@page_configs = {}
|
14
|
+
@member_actions, @collection_actions = [], []
|
15
|
+
end
|
16
|
+
|
17
|
+
# An underscored safe representation internally for this resource
|
18
|
+
def underscored_resource_name
|
19
|
+
@underscored_resource_name ||= if @options[:as]
|
20
|
+
@options[:as].gsub(' ', '').underscore.singularize
|
21
|
+
else
|
22
|
+
resource.name.gsub('::','').underscore
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# A camelized safe representation for this resource
|
27
|
+
def camelized_resource_name
|
28
|
+
underscored_resource_name.camelize
|
29
|
+
end
|
30
|
+
|
31
|
+
# Returns the name to call this resource
|
32
|
+
def resource_name
|
33
|
+
@resource_name ||= underscored_resource_name.titleize
|
34
|
+
end
|
35
|
+
|
36
|
+
# Returns the plural version of this resource
|
37
|
+
def plural_resource_name
|
38
|
+
@plural_resource_name ||= resource_name.pluralize
|
39
|
+
end
|
40
|
+
|
41
|
+
# Returns a properly formatted controller name for this
|
42
|
+
# resource within its namespace
|
43
|
+
def controller_name
|
44
|
+
[namespace.module_name, camelized_resource_name.pluralize + "Controller"].compact.join('::')
|
45
|
+
end
|
46
|
+
|
47
|
+
# Returns the controller for this resource
|
48
|
+
def controller
|
49
|
+
@controller ||= controller_name.constantize
|
50
|
+
end
|
51
|
+
|
52
|
+
# Returns the routes prefix for this resource
|
53
|
+
def route_prefix
|
54
|
+
controller.resources_configuration[:self][:route_prefix]
|
55
|
+
end
|
56
|
+
|
57
|
+
# Returns a symbol for the route to use to get to the
|
58
|
+
# collection of this resource
|
59
|
+
def route_collection_path
|
60
|
+
[route_prefix, controller.resources_configuration[:self][:route_collection_name], 'path'].compact.join('_').to_sym
|
61
|
+
end
|
62
|
+
|
63
|
+
# Set the menu options
|
64
|
+
def menu(options = {})
|
65
|
+
@parent_menu_item_name = options[:parent]
|
66
|
+
end
|
67
|
+
|
68
|
+
# Returns the name to be displayed in the menu for this resource
|
69
|
+
def menu_item_name
|
70
|
+
@menu_item_name ||= plural_resource_name
|
71
|
+
end
|
72
|
+
|
73
|
+
def clear_member_actions!
|
74
|
+
@member_actions = []
|
75
|
+
end
|
76
|
+
|
77
|
+
def clear_collection_actions!
|
78
|
+
@collection_actions = []
|
79
|
+
end
|
80
|
+
|
81
|
+
# Returns the name of the controller class for this resource
|
82
|
+
def dashboard_controller_name
|
83
|
+
[namespace.module_name, "DashboardController"].compact.join("::")
|
84
|
+
end
|
85
|
+
|
86
|
+
private
|
87
|
+
|
88
|
+
def default_options
|
89
|
+
{
|
90
|
+
:namespace => ActiveAdmin.default_namespace,
|
91
|
+
:sort_order => ActiveAdmin.default_sort_order,
|
92
|
+
}
|
93
|
+
end
|
94
|
+
|
95
|
+
end
|
96
|
+
end
|