binda 0.0.3 → 0.0.5
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.
- checksums.yaml +4 -4
- data/README.md +14 -29
- data/Rakefile +16 -10
- data/app/assets/javascripts/binda/components/form_item.js +12 -1
- data/app/assets/javascripts/binda/components/form_item_asset.js +1 -0
- data/app/assets/javascripts/binda/components/form_item_choice.js +58 -0
- data/app/assets/javascripts/binda/components/form_item_editor.js +39 -0
- data/app/assets/javascripts/binda/components/form_item_repeater.js +7 -0
- data/app/assets/javascripts/binda/components/sortable.js +0 -1
- data/app/assets/javascripts/binda/dist/binda.bundle.js +175 -8
- data/app/assets/javascripts/binda/index.js +4 -0
- data/app/assets/stylesheets/binda/components/form_item.scss +64 -2
- data/app/controllers/binda/application_controller.rb +2 -10
- data/app/controllers/binda/choices_controller.rb +19 -0
- data/app/controllers/binda/components_controller.rb +38 -134
- data/app/controllers/binda/field_groups_controller.rb +18 -30
- data/app/controllers/binda/structures_controller.rb +6 -6
- data/app/controllers/concerns/binda/component_controller_helper.rb +16 -0
- data/app/models/binda/checkbox.rb +7 -0
- data/app/models/binda/choice.rb +11 -0
- data/app/models/binda/component.rb +12 -136
- data/app/models/binda/field_group.rb +1 -1
- data/app/models/binda/field_setting.rb +40 -8
- data/app/models/binda/radio.rb +5 -0
- data/app/models/binda/repeater.rb +11 -79
- data/app/models/binda/select.rb +7 -0
- data/app/models/binda/structure.rb +13 -13
- data/app/models/concerns/binda/component_model_helper.rb +170 -0
- data/app/views/binda/components/_form_item_new_repeater.html.erb +3 -5
- data/app/views/binda/components/_form_item_selectable.html.erb +72 -0
- data/app/views/binda/components/_form_section.html.erb +7 -2
- data/app/views/binda/components/_form_section_repeater.html.erb +10 -0
- data/app/views/binda/field_groups/_form_item.html.erb +54 -26
- data/app/views/binda/field_groups/_form_item_choice.erb +96 -0
- data/app/views/binda/field_groups/_form_section.html.erb +2 -2
- data/app/views/binda/field_groups/_form_section_repeater.html.erb +2 -2
- data/config/locales/en.yml +7 -0
- data/config/routes.rb +2 -1
- data/db/migrate/1_create_binda_tables.rb +17 -0
- data/lib/binda/engine.rb +6 -0
- data/lib/binda/version.rb +1 -1
- data/lib/generators/binda/install/install_generator.rb +6 -8
- data/lib/generators/binda/setup/setup_generator.rb +8 -4
- metadata +66 -42
- data/app/controllers/binda/dates_controller.rb +0 -62
- data/app/controllers/binda/texts_controller.rb +0 -62
- data/app/views/binda/dates/_form.html.erb +0 -22
- data/app/views/binda/dates/edit.html.erb +0 -6
- data/app/views/binda/dates/index.html.erb +0 -27
- data/app/views/binda/dates/new.html.erb +0 -5
- data/app/views/binda/dates/show.html.erb +0 -9
- data/app/views/binda/repeaters/_form.html.erb +0 -27
- data/app/views/binda/repeaters/edit.html.erb +0 -6
- data/app/views/binda/repeaters/index.html.erb +0 -29
- data/app/views/binda/repeaters/new.html.erb +0 -5
- data/app/views/binda/repeaters/show.html.erb +0 -14
- data/app/views/binda/structures/add_child.html.erb +0 -0
- data/app/views/binda/texts/_form.html.erb +0 -22
- data/app/views/binda/texts/edit.html.erb +0 -6
- data/app/views/binda/texts/index.html.erb +0 -27
- data/app/views/binda/texts/new.html.erb +0 -5
- data/app/views/binda/texts/show.html.erb +0 -9
@@ -0,0 +1,16 @@
|
|
1
|
+
module Binda
|
2
|
+
module ComponentControllerHelper
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
def get_components( slug, ask_for_published = true, custom_order = '' )
|
6
|
+
if ask_for_published && custom_order.blank?
|
7
|
+
Binda::Structure.friendly.find( slug ).components.published.order('position')
|
8
|
+
elsif custom_order.blank?
|
9
|
+
Binda::Structure.friendly.find( slug ).components.order('position')
|
10
|
+
else
|
11
|
+
Binda::Structure.friendly.find( slug ).components.order( custom_order )
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
end
|
@@ -1,18 +1,23 @@
|
|
1
1
|
module Binda
|
2
2
|
class Component < ApplicationRecord
|
3
3
|
|
4
|
+
include ComponentModelHelper
|
5
|
+
|
4
6
|
# Associations
|
5
7
|
belongs_to :structure, required: true
|
6
8
|
has_and_belongs_to_many :categories
|
7
|
-
has_many :texts,
|
8
|
-
has_many :dates,
|
9
|
-
has_many :galleries,
|
10
|
-
has_many :assets,
|
9
|
+
has_many :texts, as: :fieldable, dependent: :delete_all
|
10
|
+
has_many :dates, as: :fieldable, dependent: :delete_all
|
11
|
+
has_many :galleries, as: :fieldable, dependent: :delete_all
|
12
|
+
has_many :assets, as: :fieldable, dependent: :delete_all
|
13
|
+
has_many :radios, as: :fieldable, dependent: :delete_all
|
14
|
+
has_many :selects, as: :fieldable, dependent: :delete_all
|
15
|
+
has_many :checkboxes, as: :fieldable, dependent: :delete_all
|
11
16
|
# Repeaters need destroy_all, not delete_all
|
12
17
|
has_many :repeaters, as: :fieldable, dependent: :destroy
|
13
18
|
|
14
19
|
|
15
|
-
accepts_nested_attributes_for :structure, :categories, :texts, :dates, :assets, :galleries, :repeaters, allow_destroy: true
|
20
|
+
accepts_nested_attributes_for :structure, :categories, :texts, :dates, :assets, :galleries, :repeaters, :radios, :selects, :checkboxes, allow_destroy: true
|
16
21
|
|
17
22
|
cattr_accessor :field_settings_array
|
18
23
|
|
@@ -47,140 +52,11 @@ module Binda
|
|
47
52
|
|
48
53
|
|
49
54
|
# CUSTOM METHODS
|
50
|
-
#
|
51
|
-
# https://github.com/norman/friendly_id/issues/436
|
55
|
+
#
|
56
|
+
# @see https://github.com/norman/friendly_id/issues/436
|
52
57
|
def should_generate_new_friendly_id?
|
53
58
|
slug.blank?
|
54
59
|
end
|
55
|
-
|
56
|
-
def find_or_create_a_field_by field_setting_id, field_type
|
57
|
-
if Binda::FieldSetting.get_fieldables.include?( field_type.capitalize ) && field_setting_id.is_a?( Integer )
|
58
|
-
self.send( field_type.pluralize ).find_or_create_by( field_setting_id: field_setting_id )
|
59
|
-
else
|
60
|
-
raise ArgumentError, "A parameter of the method 'find_or_create_a_field_by' is not correct.", caller
|
61
|
-
end
|
62
|
-
end
|
63
|
-
|
64
|
-
def get_text( field_slug )
|
65
|
-
# Get the object related to that field setting
|
66
|
-
self.texts.find{ |t| t.field_setting_id == Binda::FieldSetting.get_id( field_slug ) }.content
|
67
|
-
end
|
68
|
-
|
69
|
-
def has_text( field_slug )
|
70
|
-
# Get the object related to that field setting
|
71
|
-
obj = self.texts.find{ |t| t.field_setting_id == Binda::FieldSetting.get_id( field_slug ) }
|
72
|
-
if obj.present?
|
73
|
-
return !obj.content.blank?
|
74
|
-
else
|
75
|
-
return false
|
76
|
-
end
|
77
|
-
end
|
78
|
-
|
79
|
-
def has_image( field_slug )
|
80
|
-
# Check if the field has an attached image
|
81
|
-
obj = self.assets.find{ |t| t.field_setting_id == Binda::FieldSetting.get_id( field_slug ) }.image
|
82
|
-
return obj.present?
|
83
|
-
end
|
84
|
-
|
85
|
-
def get_image_url( field_slug, size = '' )
|
86
|
-
get_image_info( field_slug, size, 'url' )
|
87
|
-
end
|
88
|
-
|
89
|
-
def get_image_path( field_slug, size = '' )
|
90
|
-
get_image_info( field_slug, size, 'path' )
|
91
|
-
end
|
92
|
-
|
93
|
-
def get_image_info( field_slug, size, info )
|
94
|
-
# Get the object related to that field setting
|
95
|
-
obj = self.assets.find{ |t| t.field_setting_id == Binda::FieldSetting.get_id( field_slug ) }
|
96
|
-
if obj.image.present?
|
97
|
-
if obj.image.respond_to?(size) && %w[thumb medium large].include?(size)
|
98
|
-
obj.image.send(size).send(info)
|
99
|
-
else
|
100
|
-
obj.image.send(info)
|
101
|
-
end
|
102
|
-
end
|
103
|
-
end
|
104
|
-
|
105
|
-
def has_date( field_slug )
|
106
|
-
# Check if the field has an attached date
|
107
|
-
obj = self.dates.find{ |t| t.field_setting_id == Binda::FieldSetting.get_id( field_slug ) }
|
108
|
-
if obj.present?
|
109
|
-
return !obj.date.nil?
|
110
|
-
else
|
111
|
-
return false
|
112
|
-
end
|
113
|
-
end
|
114
|
-
|
115
|
-
def get_date( field_slug )
|
116
|
-
# Get the object related to that field setting
|
117
|
-
self.dates.find{ |t| t.field_setting_id == Binda::FieldSetting.get_id( field_slug ) }.date
|
118
|
-
end
|
119
|
-
|
120
|
-
def has_repeater( field_slug )
|
121
|
-
obj = self.repeaters.find_all{ |t| t.field_setting_id == Binda::FieldSetting.get_id( field_slug ) }
|
122
|
-
return obj.present?
|
123
|
-
end
|
124
|
-
|
125
|
-
def get_repeater( field_slug )
|
126
|
-
self.repeaters.find_all{ |t| t.field_setting_id == Binda::FieldSetting.get_id( field_slug ) }.sort_by(&:position)
|
127
|
-
end
|
128
|
-
|
129
|
-
|
130
|
-
# benchmark do
|
131
|
-
# id = Binda::FieldSetting.field_settings_array.find { |fs| fs.slug == 'home-slides-project-image' }.id
|
132
|
-
# obj = slide.assets.find{ |t| t.field_setting_id == id }
|
133
|
-
# if obj.image.present?
|
134
|
-
# if obj.image.respond_to?('') && %w[thumb medium large].include?('')
|
135
|
-
# obj.image.send('').send('path')
|
136
|
-
# else
|
137
|
-
# obj.image.send('path')
|
138
|
-
# end
|
139
|
-
# end
|
140
|
-
# end
|
141
|
-
|
142
|
-
# BENCHMARK GET_IMAGE METHODS
|
143
|
-
# ---------------------------
|
144
|
-
# def get_image_benchmark( field_slug, size, info )
|
145
|
-
# # Best is to run this group of test alone (i.e. without TestA and TestB)
|
146
|
-
#
|
147
|
-
# obj = self.assets.find{ |t| t.field_setting_id == Binda::FieldSetting.get_id( field_slug ) }
|
148
|
-
#
|
149
|
-
# self.class.benchmark("Check if image is present") do
|
150
|
-
# obj.image.present?
|
151
|
-
# end
|
152
|
-
# self.class.benchmark("Check if file exists") do
|
153
|
-
# obj.image.file.exists?
|
154
|
-
# end
|
155
|
-
# self.class.benchmark("Check if image respond to size") do
|
156
|
-
# obj.image.respond_to?(size)
|
157
|
-
# end
|
158
|
-
# self.class.benchmark("Check if size is in array") do
|
159
|
-
# %w[thumb medium large].include?(size)
|
160
|
-
# end
|
161
|
-
# if %w[thumb medium large].include?(size)
|
162
|
-
# self.class.benchmark("get resized image") do
|
163
|
-
# obj.image.send(size).send(info)
|
164
|
-
# end
|
165
|
-
# else
|
166
|
-
# self.class.benchmark("get default image") do
|
167
|
-
# obj.image.send(info)
|
168
|
-
# end
|
169
|
-
# end
|
170
|
-
|
171
|
-
# # Run TestA and TestB separately not one after the other
|
172
|
-
# # as the first time carrierwave runs is slower than the second time
|
173
|
-
# self.class.benchmark("TestA: Get image info (url)") do
|
174
|
-
# get_image_info( field_slug, size, 'url' )
|
175
|
-
# end
|
176
|
-
# self.class.benchmark("TestB: Get image info (path)") do
|
177
|
-
# get_image_info( field_slug, size, 'path' )
|
178
|
-
# end
|
179
|
-
|
180
|
-
# self.class.benchmark("Get field setting id") do
|
181
|
-
# self.assets.find{ |t| t.field_setting_id == Binda::FieldSetting.get_id( field_slug ) }
|
182
|
-
# end
|
183
|
-
# end
|
184
60
|
|
185
61
|
end
|
186
62
|
end
|
@@ -12,7 +12,7 @@ module Binda
|
|
12
12
|
validates :name, presence: true
|
13
13
|
validates :slug, uniqueness: true
|
14
14
|
accepts_nested_attributes_for :field_settings, allow_destroy: true, reject_if: :is_rejected
|
15
|
-
|
15
|
+
|
16
16
|
# Slug
|
17
17
|
extend FriendlyId
|
18
18
|
friendly_id :default_slug, use: [:slugged, :finders]
|
@@ -3,11 +3,10 @@ module Binda
|
|
3
3
|
|
4
4
|
# Associations
|
5
5
|
belongs_to :field_group
|
6
|
-
# has_many :field_children, class_name: ::Binda::FieldSetting, dependent: :delete_all
|
7
6
|
has_ancestry orphan_strategy: :destroy
|
8
7
|
|
9
8
|
# Fields Associations
|
10
|
-
#
|
9
|
+
#
|
11
10
|
# If you add a new field remember to update:
|
12
11
|
# - get_fieldables (see here below)
|
13
12
|
# - get_field_types (see here below)
|
@@ -17,6 +16,10 @@ module Binda
|
|
17
16
|
has_many :galleries, as: :fieldable
|
18
17
|
has_many :assets, as: :fieldable
|
19
18
|
has_many :repeater, as: :fieldable
|
19
|
+
has_many :radio, as: :fieldable
|
20
|
+
has_many :select, as: :fieldable
|
21
|
+
has_many :checkbox, as: :fieldable
|
22
|
+
|
20
23
|
|
21
24
|
# The following direct association is used to securely delete associated fields
|
22
25
|
# Infact via `fieldable` the associated fields might not be deleted
|
@@ -25,18 +28,38 @@ module Binda
|
|
25
28
|
has_many :dates, dependent: :delete_all
|
26
29
|
has_many :galleries, dependent: :delete_all
|
27
30
|
has_many :repeater, dependent: :delete_all
|
31
|
+
has_many :radio, dependent: :delete_all
|
32
|
+
has_many :select, dependent: :delete_all
|
33
|
+
has_many :checkbox, dependent: :delete_all
|
34
|
+
|
35
|
+
has_many :choices, dependent: :delete_all
|
36
|
+
has_one :default_choice, class_name: 'Binda::Choice', dependent: :delete
|
28
37
|
|
29
|
-
|
38
|
+
accepts_nested_attributes_for :choices, allow_destroy: true, reject_if: :is_rejected
|
39
|
+
|
40
|
+
def is_rejected( attributes )
|
41
|
+
attributes['label'].blank? || attributes['content'].blank?
|
42
|
+
end
|
30
43
|
|
31
44
|
cattr_accessor :field_settings_array
|
32
45
|
|
46
|
+
after_create do
|
47
|
+
self.class.reset_field_settings_array
|
48
|
+
end
|
49
|
+
|
50
|
+
after_destroy do
|
51
|
+
self.class.reset_field_settings_array
|
52
|
+
end
|
53
|
+
|
33
54
|
def self.get_fieldables
|
34
|
-
|
55
|
+
# TODO add 'Gallery' to this list
|
56
|
+
%w( Text Date Asset Repeater Radio Select Checkbox )
|
35
57
|
end
|
36
58
|
|
37
59
|
# Field types are't fieldable! watch out! They might use the same model (eg `string` and `text`)
|
38
60
|
def get_field_types
|
39
|
-
|
61
|
+
# TODO add 'gallery' to this list
|
62
|
+
%w( string text asset repeater date radio select checkbox )
|
40
63
|
end
|
41
64
|
|
42
65
|
# Validations
|
@@ -48,10 +71,10 @@ module Binda
|
|
48
71
|
extend FriendlyId
|
49
72
|
friendly_id :default_slug, use: [:slugged, :finders]
|
50
73
|
|
51
|
-
|
52
74
|
# CUSTOM METHODS
|
53
|
-
#
|
54
|
-
# https://github.com/norman/friendly_id/issues/436
|
75
|
+
#
|
76
|
+
# @see https://github.com/norman/friendly_id/issues/436
|
77
|
+
|
55
78
|
def should_generate_new_friendly_id?
|
56
79
|
slug.blank?
|
57
80
|
end
|
@@ -77,6 +100,11 @@ module Binda
|
|
77
100
|
return possible_names
|
78
101
|
end
|
79
102
|
|
103
|
+
# Retrieve the ID if a slug is provided and update the field_settings_array
|
104
|
+
# in order to avoid calling the database (or the cached response) every time.
|
105
|
+
# This way Rails logs are much cleaner
|
106
|
+
#
|
107
|
+
# @return [integer] The ID of the field setting
|
80
108
|
def self.get_id( field_slug )
|
81
109
|
# Get field setting id from slug, without multiple calls to database
|
82
110
|
# (the query runs once and caches the result, then any further call uses the cached result)
|
@@ -84,6 +112,10 @@ module Binda
|
|
84
112
|
@@field_settings_array.find { |fs| fs.slug == field_slug }.id
|
85
113
|
end
|
86
114
|
|
115
|
+
# Reset the field_settings_array. It's called every time
|
116
|
+
# the user creates or destroyes a Binda::FieldSetting
|
117
|
+
#
|
118
|
+
# @return [null]
|
87
119
|
def self.reset_field_settings_array
|
88
120
|
# Reset the result of the query taken with the above method,
|
89
121
|
# this is needed when a user creates a new field_setting but
|
@@ -1,13 +1,14 @@
|
|
1
1
|
module Binda
|
2
2
|
class Repeater < ApplicationRecord
|
3
3
|
|
4
|
+
include ComponentModelHelper
|
5
|
+
|
4
6
|
# Associations
|
5
7
|
belongs_to :fieldable, polymorphic: true
|
6
8
|
belongs_to :field_setting
|
7
|
-
# has_many :field_settings, dependent: :delete_all
|
8
9
|
|
9
10
|
# Fields Associations
|
10
|
-
#
|
11
|
+
#
|
11
12
|
# If you add a new field remember to update:
|
12
13
|
# - get_fieldables (see here below)
|
13
14
|
# - get_field_types (see here below)
|
@@ -16,92 +17,23 @@ module Binda
|
|
16
17
|
has_many :dates, as: :fieldable, dependent: :delete_all
|
17
18
|
has_many :galleries, as: :fieldable, dependent: :delete_all
|
18
19
|
has_many :assets, as: :fieldable, dependent: :delete_all
|
20
|
+
has_many :radios, as: :fieldable, dependent: :delete_all
|
21
|
+
has_many :selects, as: :fieldable, dependent: :delete_all
|
22
|
+
has_many :checkboxes, as: :fieldable, dependent: :delete_all
|
19
23
|
# Repeaters need destroy_all, not delete_all
|
20
24
|
has_many :repeaters, as: :fieldable, dependent: :destroy
|
21
25
|
|
22
|
-
accepts_nested_attributes_for :texts, :dates, :assets, :galleries, :repeaters, allow_destroy: true
|
26
|
+
accepts_nested_attributes_for :texts, :dates, :assets, :galleries, :repeaters, :radios, :selects, :checkboxes, allow_destroy: true
|
23
27
|
|
24
28
|
# The following direct association is used to securely delete associated fields
|
25
29
|
# Infact via `fieldable` the associated fields might not be deleted
|
26
30
|
# as the fieldable_id is related to the `component` rather than the `field_setting`
|
27
31
|
|
28
|
-
|
29
|
-
# validates :name, presence: true
|
30
|
-
|
31
|
-
# # Slug
|
32
|
-
# extend FriendlyId
|
33
|
-
# friendly_id :default_slug, use: [:slugged, :finders]
|
34
|
-
|
35
|
-
def find_or_create_a_field_by field_setting_id, field_type
|
36
|
-
if Binda::FieldSetting.get_fieldables.include?( field_type.capitalize ) && field_setting_id.is_a?( Integer )
|
37
|
-
self.send( field_type.pluralize ).find_or_create_by( field_setting_id: field_setting_id )
|
38
|
-
else
|
39
|
-
raise ArgumentError, "A parameter of the method 'find_or_create_a_field_by' is not correct.", caller
|
40
|
-
end
|
41
|
-
end
|
42
|
-
|
43
|
-
def get_text( field_slug )
|
44
|
-
# Get the object related to that field setting
|
45
|
-
self.texts.find{ |t| t.field_setting_id == Binda::FieldSetting.get_id( field_slug ) }.content
|
46
|
-
end
|
47
|
-
|
48
|
-
def has_text( field_slug )
|
49
|
-
# Get the object related to that field setting
|
50
|
-
obj = self.texts.find{ |t| t.field_setting_id == Binda::FieldSetting.get_id( field_slug ) }
|
51
|
-
if obj.present?
|
52
|
-
!obj.content.blank?
|
53
|
-
else
|
54
|
-
return false
|
55
|
-
end
|
56
|
-
end
|
57
|
-
|
58
|
-
def has_image( field_slug )
|
59
|
-
# Check if the field has an attached image
|
60
|
-
self.assets.find{ |t| t.field_setting_id == Binda::FieldSetting.get_id( field_slug ) }.image.present?
|
61
|
-
end
|
62
|
-
|
63
|
-
def get_image_url( field_slug, size = '' )
|
64
|
-
get_image_info( field_slug, size, 'url' )
|
65
|
-
end
|
66
|
-
|
67
|
-
def get_image_path( field_slug, size = '' )
|
68
|
-
get_image_info( field_slug, size, 'path' )
|
69
|
-
end
|
70
|
-
|
71
|
-
def get_image_info( field_slug, size, info )
|
72
|
-
# Get the object related to that field setting
|
73
|
-
obj = self.assets.find{ |t| t.field_setting_id == Binda::FieldSetting.get_id( field_slug ) }
|
74
|
-
if obj.image.present?
|
75
|
-
if obj.image.respond_to?(size) && %w[thumb medium large].include?(size)
|
76
|
-
obj.image.send(size).send(info)
|
77
|
-
else
|
78
|
-
obj.image.send(info)
|
79
|
-
end
|
80
|
-
end
|
81
|
-
end
|
82
|
-
|
83
|
-
def has_date( field_slug )
|
84
|
-
# Check if the field has an attached date
|
85
|
-
obj = self.dates.find{ |t| t.field_setting_id == Binda::FieldSetting.get_id( field_slug ) }
|
86
|
-
if obj.present?
|
87
|
-
!obj.date.nil?
|
88
|
-
else
|
89
|
-
return false
|
90
|
-
end
|
91
|
-
end
|
92
|
-
|
93
|
-
def get_date( field_slug )
|
94
|
-
# Get the object related to that field setting
|
95
|
-
self.dates.find{ |t| t.field_setting_id == Binda::FieldSetting.get_id( field_slug ) }.date
|
96
|
-
end
|
97
|
-
|
98
|
-
def has_repeater( field_slug )
|
99
|
-
obj = self.repeaters.find_all{ |t| t.field_setting_id == Binda::FieldSetting.get_id( field_slug ) }
|
100
|
-
return obj.present?
|
101
|
-
end
|
32
|
+
after_create :set_default_position
|
102
33
|
|
103
|
-
def
|
104
|
-
|
34
|
+
def set_default_position
|
35
|
+
position = self.fieldable.repeaters.find_all{ |r| r.field_setting_id == self.field_setting.id }.length
|
36
|
+
self.update_attribute 'position', position
|
105
37
|
end
|
106
38
|
|
107
39
|
end
|