constructor-pages 1.0.0beta1 → 1.0.0beta2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/app/assets/javascripts/constructor_pages/urlify.js +1 -0
- data/app/controllers/constructor_pages/fields_controller.rb +22 -28
- data/app/controllers/constructor_pages/pages_controller.rb +19 -48
- data/app/controllers/constructor_pages/templates_controller.rb +8 -20
- data/app/helpers/constructor_pages/fields_helper.rb +0 -2
- data/app/helpers/constructor_pages/pages_helper.rb +2 -9
- data/app/models/constructor_pages/field.rb +24 -10
- data/app/models/constructor_pages/page.rb +10 -13
- data/app/models/constructor_pages/template.rb +5 -4
- data/app/models/constructor_pages/types/boolean_type.rb +0 -2
- data/app/models/constructor_pages/types/date_type.rb +0 -2
- data/app/models/constructor_pages/types/float_type.rb +0 -2
- data/app/models/constructor_pages/types/html_type.rb +0 -2
- data/app/models/constructor_pages/types/image_type.rb +0 -2
- data/app/models/constructor_pages/types/integer_type.rb +0 -2
- data/app/models/constructor_pages/types/string_type.rb +0 -2
- data/app/models/constructor_pages/types/text_type.rb +0 -2
- data/app/views/constructor_pages/fields/_form.html.slim +4 -4
- data/app/views/constructor_pages/fields/types/_boolean.html.slim +1 -0
- data/app/views/constructor_pages/fields/types/_html.html.slim +1 -1
- data/app/views/constructor_pages/fields/types/_string.html.slim +1 -1
- data/app/views/constructor_pages/fields/types/_text.html.slim +1 -1
- data/app/views/constructor_pages/pages/_form.html.slim +5 -8
- data/app/views/constructor_pages/pages/index.html.slim +2 -3
- data/app/views/constructor_pages/templates/_form.html.slim +0 -4
- data/config/locales/en.yml +2 -0
- data/config/locales/fr.yml +2 -0
- data/config/locales/ru.yml +2 -0
- data/config/routes.rb +1 -1
- data/constructor-pages.gemspec +1 -2
- data/db/migrate/10_create_html_types.rb +1 -5
- data/db/migrate/11_create_image_types.rb +1 -5
- data/db/migrate/12_add_default_template.rb +3 -3
- data/db/migrate/14_remove_child_id_from_templates.rb +2 -6
- data/db/migrate/15_rename_link_to_redirect.rb +1 -5
- data/db/migrate/16_add_indexes.rb +13 -0
- data/db/migrate/1_create_pages.rb +12 -16
- data/db/migrate/2_create_templates.rb +1 -5
- data/db/migrate/3_create_fields.rb +1 -5
- data/db/migrate/4_create_string_types.rb +2 -6
- data/db/migrate/5_create_float_types.rb +2 -6
- data/db/migrate/6_create_boolean_types.rb +2 -6
- data/db/migrate/7_create_integer_types.rb +2 -6
- data/db/migrate/8_create_text_types.rb +1 -5
- data/db/migrate/9_create_date_types.rb +2 -6
- data/lib/constructor-pages.rb +1 -0
- data/spec/features/constructor_pages/fields_spec.rb +5 -6
- data/spec/features/constructor_pages/pages_spec.rb +26 -63
- data/spec/features/constructor_pages/templates_spec.rb +15 -28
- data/spec/models/constructor_pages/field_spec.rb +0 -2
- data/spec/models/constructor_pages/page_spec.rb +15 -31
- data/spec/models/constructor_pages/template_spec.rb +0 -2
- data/spec/models/constructor_pages/types/boolean_type_spec.rb +0 -2
- data/spec/models/constructor_pages/types/date_type_spec.rb +0 -2
- data/spec/models/constructor_pages/types/float_type_spec.rb +0 -5
- data/spec/models/constructor_pages/types/html_type_spec.rb +0 -2
- data/spec/models/constructor_pages/types/integer_type_spec.rb +0 -2
- data/spec/models/constructor_pages/types/string_type_spec.rb +0 -2
- data/spec/models/constructor_pages/types/text_type_spec.rb +0 -2
- metadata +19 -7
- data/app/helpers/constructor_pages/code_name_uniq.rb +0 -7
- data/app/helpers/constructor_pages/for_select.rb +0 -13
- data/app/helpers/constructor_pages/templates_helper.rb +0 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f675af9c0fbb73b082bfa366e4f96428ef9c9b0e
|
4
|
+
data.tar.gz: 6c2a949553d8d3aca605e8b7b5e9c19fcb045139
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c012caaa5894b41848bed34798e3a9dbdbe98690321eb5eb2e2b3546a83b4545ed04b83f4b1f1070198dd77f8c6a38b7efd46c840618161a4dbdaf91241a946a
|
7
|
+
data.tar.gz: 17091449e7387cf48d44d91005c6391be092434e92715bd4d3696c6f6d531a6dda1cdd33efb88aee8b95feefeebb31830b0cee9c9687cd2bb352ad9420189ae3
|
@@ -69,6 +69,7 @@ function URLify(s, num_chars) {
|
|
69
69
|
s = s.replace(/[^-\w\s]/g, '-'); // remove unneeded chars
|
70
70
|
s = s.replace(/^\s+|\s+$/g, ''); // trim leading/trailing spaces
|
71
71
|
s = s.replace(/[-\s]+/g, '-'); // convert spaces to hyphens
|
72
|
+
s = s.replace(/^-+|-+$/g, ''); // trim leading/trailing hyphens
|
72
73
|
s = s.toLowerCase(); // convert to lowercase
|
73
74
|
return s.substring(0, num_chars);// trim to first num_chars chars
|
74
75
|
}
|
@@ -1,68 +1,62 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
|
3
1
|
module ConstructorPages
|
4
2
|
class FieldsController < ConstructorCore::ApplicationController
|
5
3
|
include TheSortableTreeController::Rebuild
|
6
4
|
include TheSortableTreeController::ExpandNode
|
7
5
|
|
6
|
+
before_action :set_field, only: [:edit, :update, :destroy]
|
7
|
+
|
8
8
|
def new
|
9
|
-
@field = Field.new
|
9
|
+
@field = Field.new template_id: params[:template_id]
|
10
10
|
end
|
11
11
|
|
12
12
|
def edit
|
13
|
-
@field = Field.find(params[:id]).tap {|f| @template = f.template = Template.find(params[:template_id])}
|
14
13
|
end
|
15
14
|
|
16
15
|
def create
|
17
16
|
@field = Field.new field_params
|
18
|
-
@template = @field.template
|
19
17
|
|
20
18
|
if @field.save
|
21
|
-
redirect_to edit_template_path(@template), notice: t(:field_success_added, name: @field.name)
|
19
|
+
redirect_to edit_template_path(@field.template), notice: t(:field_success_added, name: @field.name)
|
22
20
|
else
|
23
|
-
render
|
21
|
+
render :new
|
24
22
|
end
|
25
23
|
end
|
26
24
|
|
27
25
|
def update
|
28
|
-
@field = Field.find params[:id]
|
29
|
-
@template = @field.template
|
30
|
-
|
31
26
|
unless @field.type_value == params[:field][:type_value]
|
32
27
|
@field.type_class.where(field_id: @field.id).each do |field|
|
33
|
-
"constructor_pages/types/#{params[:field][:type_value]}_type".classify.constantize.new(
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
28
|
+
_field = "constructor_pages/types/#{params[:field][:type_value]}_type".classify.constantize.new(field_id: @field.id, page_id: field.page_id)
|
29
|
+
|
30
|
+
unless [@field.type_value, params[:field][:type_value]].include?('image') && (@field.type_value == 'string' && field.value.strip == '')
|
31
|
+
_field.value = field.value
|
32
|
+
end
|
33
|
+
|
34
|
+
_field.save
|
35
|
+
field.destroy
|
39
36
|
end
|
40
37
|
end
|
41
38
|
|
42
39
|
if @field.update field_params
|
43
|
-
redirect_to edit_template_path(@template.id), notice: t(:field_success_updated, name: @field.name)
|
40
|
+
redirect_to edit_template_path(@field.template.id), notice: t(:field_success_updated, name: @field.name)
|
44
41
|
else
|
45
|
-
render
|
42
|
+
render :edit
|
46
43
|
end
|
47
44
|
end
|
48
45
|
|
49
46
|
def destroy
|
50
|
-
@field = Field.find(params[:id])
|
51
|
-
name, template = @field.name, @field.template.id
|
52
47
|
@field.destroy
|
53
|
-
redirect_to edit_template_url(template), notice: t(:field_success_removed, name: name)
|
48
|
+
redirect_to edit_template_url(@field.template), notice: t(:field_success_removed, name: @field.name)
|
54
49
|
end
|
55
50
|
|
56
|
-
def sortable_model
|
57
|
-
|
58
|
-
end
|
59
|
-
|
60
|
-
def sortable_collection
|
61
|
-
ConstructorPages::Field
|
62
|
-
end
|
51
|
+
def sortable_model; Field end
|
52
|
+
def sortable_collection; ConstructorPages::Field end
|
63
53
|
|
64
54
|
private
|
65
55
|
|
56
|
+
def set_field
|
57
|
+
@field = Field.find params[:id]
|
58
|
+
end
|
59
|
+
|
66
60
|
def field_params
|
67
61
|
params.require(:field).permit(
|
68
62
|
:name,
|
@@ -1,95 +1,71 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
|
3
1
|
module ConstructorPages
|
4
2
|
class PagesController < ConstructorCore::ApplicationController
|
5
3
|
include TheSortableTreeController::Rebuild
|
6
4
|
include TheSortableTreeController::ExpandNode
|
7
5
|
|
8
|
-
|
6
|
+
skip_before_filter :authenticate_user!, only: [:show]
|
9
7
|
|
10
|
-
|
8
|
+
before_action :set_page, only: [:edit, :update, :destroy]
|
11
9
|
|
12
10
|
def index
|
13
|
-
@pages = Page.
|
14
|
-
@pages_cache = Digest::MD5.hexdigest(@pages.map{|p| [p.id, p.name, p.full_url, p.in_url, p.template.lft, p.lft, p.template_id]}.join)
|
15
|
-
@template_exists = Template.count != 0
|
16
|
-
flash[:notice] = 'Create at least one template' unless @template_exists
|
11
|
+
@pages = Page.roots
|
17
12
|
end
|
18
13
|
|
19
14
|
def new
|
20
|
-
@page = Page.new
|
15
|
+
@page, @templates = Page.new, Template.all
|
16
|
+
|
17
|
+
if @templates.blank?
|
18
|
+
redirect_to pages_path, notice: t(:create_template_first)
|
19
|
+
end
|
21
20
|
end
|
22
21
|
|
23
22
|
def show
|
24
|
-
|
25
|
-
|
26
|
-
error_404 and return if @page.nil? or (!@page.published? and _request != '/')
|
23
|
+
@page = Page.find_by_path request.path
|
24
|
+
|
27
25
|
redirect_to(@page.redirect) && return if @page.redirect?
|
26
|
+
|
28
27
|
_code_name = @page.template.code_name
|
29
28
|
instance_variable_set('@'+_code_name, @page)
|
30
29
|
|
31
|
-
|
32
|
-
format.html { render "#{_code_name.pluralize}/show" rescue render "templates/#{_code_name}"}
|
33
|
-
format.json { render "#{_code_name.pluralize}/show.json", layout: false rescue render json: @page }
|
34
|
-
format.xml { render "#{_code_name.pluralize}/show.xml", layout: false rescue render xml: @page }
|
35
|
-
end
|
30
|
+
render "templates/#{_code_name}", layout: 'application'
|
36
31
|
end
|
37
32
|
|
38
33
|
def edit
|
39
|
-
@page = Page.find(params[:id])
|
40
|
-
@parent_id, @template_id = @page.parent.try(:id), @page.template.id
|
41
|
-
_code_name = @page.template.code_name.pluralize
|
42
|
-
render "#{_code_name}/edit" rescue render :edit
|
43
34
|
end
|
44
35
|
|
45
36
|
def create
|
46
37
|
@page = Page.new page_params
|
47
38
|
|
48
39
|
if @page.save
|
49
|
-
@page.touch_branch
|
50
40
|
redirect_to pages_path, notice: t(:page_success_added, name: @page.name)
|
51
41
|
else
|
52
|
-
|
53
|
-
_template = Template.find(@page.template_id)
|
54
|
-
_code_name = _template.code_name.pluralize if _template
|
55
|
-
render "#{_code_name}/new" rescue render :new
|
56
|
-
else
|
57
|
-
render :new
|
58
|
-
end
|
42
|
+
render :new
|
59
43
|
end
|
60
44
|
end
|
61
45
|
|
62
46
|
def update
|
63
|
-
@page = Page.find params[:id]
|
64
|
-
|
65
|
-
_template_changed = @page.template.id != params[:page][:template_id].to_i
|
66
|
-
|
67
|
-
@page.remove_fields_values if _template_changed
|
68
|
-
|
69
47
|
if @page.update page_params
|
70
|
-
@page.create_fields_values if _template_changed
|
71
48
|
@page.update_fields_values params[:fields]
|
72
|
-
@page.touch_branch
|
73
49
|
|
74
50
|
redirect_to pages_path, notice: t(:page_success_updated, name: @page.name)
|
75
51
|
else
|
76
|
-
render
|
52
|
+
render :edit
|
77
53
|
end
|
78
54
|
end
|
79
55
|
|
80
56
|
def destroy
|
81
|
-
@page = Page.find(params[:id])
|
82
|
-
@page.touch_branch
|
83
57
|
@page.destroy
|
84
58
|
redirect_to pages_path, notice: t(:page_success_removed, name: @page.name)
|
85
59
|
end
|
86
60
|
|
87
|
-
def sortable_model
|
88
|
-
Page
|
89
|
-
end
|
61
|
+
def sortable_model; Page end
|
90
62
|
|
91
63
|
private
|
92
64
|
|
65
|
+
def set_page
|
66
|
+
@page = Page.find params[:id]
|
67
|
+
end
|
68
|
+
|
93
69
|
def page_params
|
94
70
|
params.require(:page).permit(
|
95
71
|
:active,
|
@@ -99,7 +75,6 @@ module ConstructorPages
|
|
99
75
|
:keywords,
|
100
76
|
:description,
|
101
77
|
:auto_url,
|
102
|
-
:parent_id,
|
103
78
|
:template_id,
|
104
79
|
:in_nav,
|
105
80
|
:in_map,
|
@@ -108,9 +83,5 @@ module ConstructorPages
|
|
108
83
|
:redirect
|
109
84
|
)
|
110
85
|
end
|
111
|
-
|
112
|
-
def error_404
|
113
|
-
render file: "#{Rails.root}/public/404", layout: false, status: :not_found
|
114
|
-
end
|
115
86
|
end
|
116
87
|
end
|
@@ -1,15 +1,12 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
|
3
1
|
module ConstructorPages
|
4
2
|
class TemplatesController < ConstructorCore::ApplicationController
|
5
3
|
include TheSortableTreeController::Rebuild
|
6
4
|
include TheSortableTreeController::ExpandNode
|
7
5
|
|
8
|
-
|
6
|
+
before_action :set_template, only: [:edit, :update, :destroy]
|
9
7
|
|
10
8
|
def index
|
11
9
|
@templates = Template.roots
|
12
|
-
@templates_cache = Digest::MD5.hexdigest(@templates.map{|t| [t.id, t.name, t.lft]}.join)
|
13
10
|
end
|
14
11
|
|
15
12
|
def new
|
@@ -17,7 +14,6 @@ module ConstructorPages
|
|
17
14
|
end
|
18
15
|
|
19
16
|
def edit
|
20
|
-
@template = Template.find(params[:id])
|
21
17
|
end
|
22
18
|
|
23
19
|
def create
|
@@ -31,8 +27,6 @@ module ConstructorPages
|
|
31
27
|
end
|
32
28
|
|
33
29
|
def update
|
34
|
-
@template = Template.find params[:id]
|
35
|
-
|
36
30
|
if @template.update template_params
|
37
31
|
redirect_to templates_url, notice: t(:template_success_updated, name: @template.name)
|
38
32
|
else
|
@@ -41,28 +35,22 @@ module ConstructorPages
|
|
41
35
|
end
|
42
36
|
|
43
37
|
def destroy
|
44
|
-
@template
|
45
|
-
|
46
|
-
if @template.pages.count == 0
|
47
|
-
name = @template.name
|
48
|
-
@template.destroy
|
49
|
-
redirect_to templates_url, notice: t(:template_success_removed, name: name)
|
50
|
-
else
|
51
|
-
redirect_to :back, alert: t(:template_error_delete_pages)
|
52
|
-
end
|
38
|
+
@template.destroy
|
39
|
+
redirect_to templates_url, notice: t(:template_success_removed, name: @template.name)
|
53
40
|
end
|
54
41
|
|
55
|
-
def sortable_model
|
56
|
-
Template
|
57
|
-
end
|
42
|
+
def sortable_model; Template end
|
58
43
|
|
59
44
|
private
|
60
45
|
|
46
|
+
def set_template
|
47
|
+
@template = Template.find params[:id]
|
48
|
+
end
|
49
|
+
|
61
50
|
def template_params
|
62
51
|
params.require(:template).permit(
|
63
52
|
:name,
|
64
53
|
:code_name,
|
65
|
-
:parent_id,
|
66
54
|
:child_id
|
67
55
|
)
|
68
56
|
end
|
@@ -1,14 +1,7 @@
|
|
1
1
|
module ConstructorPages
|
2
2
|
module PagesHelper
|
3
|
-
|
4
|
-
|
5
|
-
def templates
|
6
|
-
Template.all.map{|t| ["#{'--'*t.level} #{t.name}", t.id]}
|
7
|
-
end
|
8
|
-
|
9
|
-
def image_tag_with_at2x(name_at_1x, options={})
|
10
|
-
name_at_2x = name_at_1x.gsub(%r{\.\w+$}, '@2x\0')
|
11
|
-
image_tag(name_at_1x, options.merge("data-at2x" => asset_path(name_at_2x)))
|
3
|
+
def templates_tree(templates)
|
4
|
+
templates.map{|t| ["#{'--'*t.level} #{t.name}", t.id]}
|
12
5
|
end
|
13
6
|
end
|
14
7
|
end
|
@@ -1,15 +1,11 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
|
3
1
|
module ConstructorPages
|
4
2
|
# Field model. Fields allows to add custom fields for template.
|
5
3
|
# Each field has type of value such as float, integer, string...
|
6
4
|
class Field < ActiveRecord::Base
|
7
|
-
# Adding code_name_uniqueness method
|
8
|
-
include CodeNameUniq
|
9
|
-
|
10
5
|
# Array of available field types
|
11
|
-
TYPES = %w{string integer float boolean text date html image}
|
12
|
-
|
6
|
+
TYPES = %w{string integer float boolean text date html image}
|
7
|
+
|
8
|
+
TYPES.each {|t| class_eval %{has_many :#{t}_types, class_name: 'Types::#{t.titleize}Type'} }
|
13
9
|
|
14
10
|
validates_presence_of :name
|
15
11
|
validates_uniqueness_of :code_name, scope: :template_id
|
@@ -47,6 +43,11 @@ module ConstructorPages
|
|
47
43
|
|
48
44
|
private
|
49
45
|
|
46
|
+
# Check if code_name is not available
|
47
|
+
def code_name_uniqueness
|
48
|
+
errors.add(:base, :code_name_already_in_use) unless Page.check_code_name(code_name) and check_code_name(code_name)
|
49
|
+
end
|
50
|
+
|
50
51
|
# Check if there is code_name in template branch
|
51
52
|
def check_code_name(code_name)
|
52
53
|
[code_name.pluralize, code_name.singularize].each {|name|
|
@@ -55,8 +56,21 @@ module ConstructorPages
|
|
55
56
|
true
|
56
57
|
end
|
57
58
|
|
58
|
-
|
59
|
-
|
60
|
-
|
59
|
+
def create_page_fields
|
60
|
+
template.page_ids.each_slice(500) do |batch|
|
61
|
+
_items = []
|
62
|
+
batch.each do |_id|
|
63
|
+
_items << type_class.new({page_id: _id, field_id: id})
|
64
|
+
end
|
65
|
+
type_class.import _items
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def destroy_all_page_fields
|
70
|
+
template.page_ids.each_slice(1000) do |batch|
|
71
|
+
type_class.where(page_id: batch, field_id: id).delete_all
|
72
|
+
Page.update_all({updated_at: Time.now}, {id: batch})
|
73
|
+
end
|
74
|
+
end
|
61
75
|
end
|
62
76
|
end
|
@@ -1,5 +1,3 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
|
3
1
|
module ConstructorPages
|
4
2
|
# Page model. Pages are core for company websites, blogs etc.
|
5
3
|
class Page < ActiveRecord::Base
|
@@ -14,9 +12,12 @@ module ConstructorPages
|
|
14
12
|
has_many :fields, through: :template
|
15
13
|
belongs_to :template
|
16
14
|
|
15
|
+
scope :published, -> { where(active: true) }
|
16
|
+
|
17
17
|
default_scope -> { order :lft }
|
18
18
|
|
19
19
|
validate :templates_existing_check
|
20
|
+
validate :check_template_changing, on: :update
|
20
21
|
|
21
22
|
before_save :friendly_url, :assign_template, :full_url_update
|
22
23
|
after_update :descendants_update
|
@@ -25,10 +26,10 @@ module ConstructorPages
|
|
25
26
|
acts_as_nested_set
|
26
27
|
|
27
28
|
class << self
|
28
|
-
# Used for find page by request. It return first page if no request given or request is home page
|
29
|
+
# Used for find page by request path. It return first page if no request given or request is home page
|
29
30
|
# @param request for example <tt>'/conditioners/split-systems/zanussi'</tt>
|
30
|
-
def
|
31
|
-
|
31
|
+
def find_by_path(path)
|
32
|
+
path == '/' ? Page.published.first! : Page.published.find_by!(full_url: path)
|
32
33
|
end
|
33
34
|
|
34
35
|
# Generate full_url from parent id and url
|
@@ -129,12 +130,10 @@ module ConstructorPages
|
|
129
130
|
|
130
131
|
# Update all fields values with given params.
|
131
132
|
# @param params should looks like <tt>{price: 500, content: 'Hello'}</tt>
|
132
|
-
|
133
|
-
def update_fields_values(params, reset_booleans = true)
|
133
|
+
def update_fields_values(params)
|
134
134
|
params || return
|
135
135
|
|
136
136
|
fields.each {|f| f.find_or_create_type_object(self).tap {|t| t || next
|
137
|
-
t.value = 0 if f.type_value == 'boolean' && reset_booleans
|
138
137
|
params[f.code_name.to_sym].tap {|v| v && t.value = v}
|
139
138
|
t.save }}
|
140
139
|
end
|
@@ -184,11 +183,6 @@ module ConstructorPages
|
|
184
183
|
# Check if link specified
|
185
184
|
def redirect?; url != redirect && !redirect.empty? end
|
186
185
|
|
187
|
-
# Touch all pages in same branch
|
188
|
-
def touch_branch
|
189
|
-
[ancestors, descendants].each {|p| p.map(&:touch)}
|
190
|
-
end
|
191
|
-
|
192
186
|
# When method missing it get/set field value or get page in branch
|
193
187
|
#
|
194
188
|
# Examples:
|
@@ -211,6 +205,9 @@ module ConstructorPages
|
|
211
205
|
# Page is not valid if there is no template
|
212
206
|
def templates_existing_check; errors.add_on_empty(:template_id) if Template.count == 0 end
|
213
207
|
|
208
|
+
# Page should not change template
|
209
|
+
def check_template_changing; errors.add(:base, :can_not_change_template) if template_id_changed? end
|
210
|
+
|
214
211
|
# If template_id is nil then get first template
|
215
212
|
def assign_template; self.template_id = Template.first.id unless template_id end
|
216
213
|
|