lato_cms 3.0.0
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 +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +67 -0
- data/Rakefile +6 -0
- data/app/assets/config/lato_cms_manifest.js +2 -0
- data/app/assets/javascripts/lato_cms/application.js +1 -0
- data/app/assets/javascripts/lato_cms/controllers/lato_cms_advanced_editor_controller.js +57 -0
- data/app/assets/javascripts/lato_cms/controllers/lato_cms_color_field_controller.js +9 -0
- data/app/assets/javascripts/lato_cms/controllers/lato_cms_component_accordion_controller.js +60 -0
- data/app/assets/javascripts/lato_cms/controllers/lato_cms_component_form_controller.js +96 -0
- data/app/assets/javascripts/lato_cms/controllers/lato_cms_component_toggle_controller.js +89 -0
- data/app/assets/javascripts/lato_cms/controllers/lato_cms_gallery_field_controller.js +136 -0
- data/app/assets/javascripts/lato_cms/controllers/lato_cms_image_field_controller.js +24 -0
- data/app/assets/javascripts/lato_cms/controllers/lato_cms_json_field_controller.js +27 -0
- data/app/assets/javascripts/lato_cms/controllers/lato_cms_page_preview_controller.js +86 -0
- data/app/assets/javascripts/lato_cms/controllers/lato_cms_text_field_controller.js +74 -0
- data/app/assets/stylesheets/lato_cms/application.scss +252 -0
- data/app/controllers/lato_cms/api/pages_controller.rb +38 -0
- data/app/controllers/lato_cms/application_controller.rb +26 -0
- data/app/controllers/lato_cms/pages_controller.rb +194 -0
- data/app/helpers/lato_cms/application_helper.rb +4 -0
- data/app/helpers/lato_cms/pages_helper.rb +69 -0
- data/app/jobs/lato_cms/application_job.rb +4 -0
- data/app/mailers/lato_cms/application_mailer.rb +6 -0
- data/app/models/lato_cms/page.rb +133 -0
- data/app/models/lato_cms/page_field.rb +113 -0
- data/app/models/lato_cms/template_manager.rb +64 -0
- data/app/views/lato_cms/pages/_component_accordion.html.erb +97 -0
- data/app/views/lato_cms/pages/_fields_editor.html.erb +12 -0
- data/app/views/lato_cms/pages/_form_create.html.erb +26 -0
- data/app/views/lato_cms/pages/_form_update.html.erb +39 -0
- data/app/views/lato_cms/pages/create.html.erb +13 -0
- data/app/views/lato_cms/pages/fields/_boolean.html.erb +16 -0
- data/app/views/lato_cms/pages/fields/_color.html.erb +18 -0
- data/app/views/lato_cms/pages/fields/_date.html.erb +16 -0
- data/app/views/lato_cms/pages/fields/_datetime.html.erb +16 -0
- data/app/views/lato_cms/pages/fields/_file.html.erb +29 -0
- data/app/views/lato_cms/pages/fields/_gallery.html.erb +55 -0
- data/app/views/lato_cms/pages/fields/_image.html.erb +39 -0
- data/app/views/lato_cms/pages/fields/_json.html.erb +23 -0
- data/app/views/lato_cms/pages/fields/_multiselect.html.erb +25 -0
- data/app/views/lato_cms/pages/fields/_number.html.erb +18 -0
- data/app/views/lato_cms/pages/fields/_select.html.erb +23 -0
- data/app/views/lato_cms/pages/fields/_string.html.erb +18 -0
- data/app/views/lato_cms/pages/fields/_text.html.erb +80 -0
- data/app/views/lato_cms/pages/fields/_textarea.html.erb +16 -0
- data/app/views/lato_cms/pages/index.html.erb +32 -0
- data/app/views/lato_cms/pages/show.html.erb +105 -0
- data/app/views/lato_cms/pages/update.html.erb +13 -0
- data/config/importmap.rb +2 -0
- data/config/locales/en.yml +70 -0
- data/config/locales/it.yml +70 -0
- data/config/routes.rb +22 -0
- data/db/migrate/20250328072343_add_lato_cms_admin_to_lato_user.rb +5 -0
- data/db/migrate/20260320070819_create_lato_cms_pages.rb +13 -0
- data/db/migrate/20260323171241_create_lato_cms_page_fields.rb +13 -0
- data/lib/lato_cms/config.rb +13 -0
- data/lib/lato_cms/engine.rb +13 -0
- data/lib/lato_cms/version.rb +3 -0
- data/lib/lato_cms.rb +15 -0
- data/lib/tasks/lato_cms_tasks.rake +183 -0
- metadata +158 -0
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
module LatoCms
|
|
2
|
+
module PagesHelper
|
|
3
|
+
BUILTIN_FIELD_TYPES = %w[string textarea text number date datetime boolean select multiselect color json file image gallery].freeze
|
|
4
|
+
|
|
5
|
+
# Returns the path for an Active Storage attachment.
|
|
6
|
+
# Using main_app avoids the missing `attachment_path` error inside the engine.
|
|
7
|
+
def lato_cms_attachment_path(attachment)
|
|
8
|
+
main_app.rails_blob_path(attachment.blob, only_path: true)
|
|
9
|
+
rescue StandardError
|
|
10
|
+
'#'
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def lato_cms_page_locale(page)
|
|
14
|
+
content_tag(:span, class: 'badge bg-secondary') do
|
|
15
|
+
concat locale_to_flag(page.locale)
|
|
16
|
+
concat " #{page.locale.upcase}"
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def lato_cms_page_actions(page, show_edit: false, show_delete: false, hide_show: false)
|
|
21
|
+
btn_group = capture do
|
|
22
|
+
content_tag(:div, class: 'btn-group btn-group-sm') do
|
|
23
|
+
concat(link_to(t('lato_cms.cta_show'), lato_cms.pages_show_path(page), class: 'btn btn-primary')) unless hide_show
|
|
24
|
+
if show_edit
|
|
25
|
+
concat link_to(t('lato_cms.cta_edit'), lato_cms.pages_update_path(page), class: 'btn btn-secondary',
|
|
26
|
+
data: { lato_action_target: 'trigger', turbo_frame: dom_id(page, 'form'), action_title: t('lato_cms.page_update_title') })
|
|
27
|
+
end
|
|
28
|
+
if show_delete
|
|
29
|
+
concat link_to(t('lato_cms.cta_delete'), lato_cms.pages_destroy_action_path(page), class: 'btn btn-danger',
|
|
30
|
+
data: { turbo_method: 'DELETE', turbo_confirm: t('lato_cms.cta_delete_confirm') })
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
content_tag(:div, class: 'd-flex align-items-center gap-2') do
|
|
36
|
+
if page.frontend_url.blank?
|
|
37
|
+
concat content_tag(:span, t('lato_cms.action_view_frontend'), class: 'btn btn-sm btn-link px-0 disabled text-muted')
|
|
38
|
+
else
|
|
39
|
+
concat link_to(t('lato_cms.action_view_frontend'), page.frontend_url, class: 'btn btn-sm btn-link px-0', target: '_blank')
|
|
40
|
+
end
|
|
41
|
+
concat btn_group
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def lato_cms_render_field(field_id:, field_config:, page_field:)
|
|
46
|
+
render resolve_lato_cms_field_partial(field_config),
|
|
47
|
+
field_id: field_id,
|
|
48
|
+
field_config: field_config,
|
|
49
|
+
page_field: page_field
|
|
50
|
+
rescue ActionView::MissingTemplate => e
|
|
51
|
+
content_tag(:div, class: 'alert alert-danger mb-0') do
|
|
52
|
+
"Field '#{field_id}' render error: #{e.message}"
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
private
|
|
57
|
+
|
|
58
|
+
def resolve_lato_cms_field_partial(field_config)
|
|
59
|
+
type = (field_config['type'] || 'string').to_s
|
|
60
|
+
return "lato_cms/pages/fields/#{type}" if BUILTIN_FIELD_TYPES.include?(type)
|
|
61
|
+
return 'lato_cms/pages/fields/string' unless type == 'custom'
|
|
62
|
+
|
|
63
|
+
render_path = field_config['render'].to_s.strip
|
|
64
|
+
return 'lato_cms/pages/fields/string' if render_path.blank?
|
|
65
|
+
|
|
66
|
+
render_path
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
module LatoCms
|
|
2
|
+
class Page < ApplicationRecord
|
|
3
|
+
include LatoSpaces::Associable
|
|
4
|
+
include LatoSpaces::AssociableRequired
|
|
5
|
+
include LatoSpaces::AssociableUnique
|
|
6
|
+
|
|
7
|
+
attr_accessor :actions
|
|
8
|
+
|
|
9
|
+
validates :title, presence: true
|
|
10
|
+
validates :locale, presence: true, inclusion: { in: ->(page) { LatoCms.config.locales.map(&:to_s) } }
|
|
11
|
+
validates :permalink, presence: true, format: { with: /\A\/([a-zA-Z0-9\-_]+(\/[a-zA-Z0-9\-_]+)*)?\z/, message: 'must start with / and contain only letters, numbers, hyphens, and underscores' }
|
|
12
|
+
validate :permalink_unique_within_lato_spaces_group
|
|
13
|
+
|
|
14
|
+
before_validation :generate_permalink, on: :create
|
|
15
|
+
|
|
16
|
+
has_many :fields, class_name: 'LatoCms::PageField', dependent: :destroy
|
|
17
|
+
|
|
18
|
+
def template
|
|
19
|
+
LatoCms::TemplateManager.find_template(template_id)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def template_name
|
|
23
|
+
template&.dig('name') || template_id
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def template_available?
|
|
27
|
+
template_id.blank? || template.present?
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def template_components
|
|
31
|
+
return [] unless template
|
|
32
|
+
LatoCms::TemplateManager.resolve_template_components(template)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def component_enabled?(template_component_id, default: true)
|
|
36
|
+
value = (component_states || {})[template_component_id.to_s]
|
|
37
|
+
return default if value.nil?
|
|
38
|
+
|
|
39
|
+
value == true
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def component_required?(template_component_id)
|
|
43
|
+
tc = template_components.find { |component| component[:template_component_id] == template_component_id.to_s }
|
|
44
|
+
tc && tc[:required] == true
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def component_effectively_enabled?(template_component_id, default: true)
|
|
48
|
+
return true if component_required?(template_component_id)
|
|
49
|
+
|
|
50
|
+
component_enabled?(template_component_id, default: default)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def set_component_enabled(template_component_id, enabled)
|
|
54
|
+
key = template_component_id.to_s
|
|
55
|
+
self.component_states = (component_states || {}).merge(key => ActiveModel::Type::Boolean.new.cast(enabled))
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def as_json(options = {})
|
|
59
|
+
data = {
|
|
60
|
+
id: id,
|
|
61
|
+
title: title,
|
|
62
|
+
permalink: permalink,
|
|
63
|
+
locale: locale,
|
|
64
|
+
template_id: template_id,
|
|
65
|
+
template_name: template_name,
|
|
66
|
+
frontend_url: frontend_url,
|
|
67
|
+
created_at: created_at,
|
|
68
|
+
updated_at: updated_at
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if options[:include_fields]
|
|
72
|
+
fields.load unless fields.loaded?
|
|
73
|
+
data[:fields] = build_fields_json(template_components.select { |tc| component_effectively_enabled?(tc[:template_component_id]) })
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
data
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
private
|
|
80
|
+
|
|
81
|
+
def build_fields_json(comps)
|
|
82
|
+
fields_by_component = fields.group_by(&:template_component_id)
|
|
83
|
+
|
|
84
|
+
comps.each_with_object({}) do |tc, hash|
|
|
85
|
+
component_fields = (fields_by_component[tc[:template_component_id]] || []).index_by(&:field_id)
|
|
86
|
+
|
|
87
|
+
hash[tc[:template_component_id]] = {
|
|
88
|
+
component_id: tc[:component_id],
|
|
89
|
+
name: tc[:name],
|
|
90
|
+
fields: (tc[:fields] || {}).each_with_object({}) do |(fid, fconfig), fhash|
|
|
91
|
+
field = component_fields[fid.to_s]
|
|
92
|
+
fhash[fid] = field ? field.as_json : empty_field_json(fid.to_s, fconfig)
|
|
93
|
+
end
|
|
94
|
+
}
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def empty_field_json(fid, fconfig)
|
|
99
|
+
{
|
|
100
|
+
id: nil,
|
|
101
|
+
field_id: fid,
|
|
102
|
+
field_type: fconfig['type'] || 'string',
|
|
103
|
+
field_name: fconfig['name'] || fid.humanize,
|
|
104
|
+
required: fconfig['required'] == true,
|
|
105
|
+
value: nil,
|
|
106
|
+
attachments: []
|
|
107
|
+
}
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def generate_permalink
|
|
111
|
+
return if title.blank?
|
|
112
|
+
return if permalink.present?
|
|
113
|
+
|
|
114
|
+
base = "/#{title.parameterize}"
|
|
115
|
+
candidate = base
|
|
116
|
+
counter = 1
|
|
117
|
+
|
|
118
|
+
while LatoCms::Page.for_lato_spaces_group(self.lato_spaces_group_id).where(permalink: candidate).where.not(id: id).exists?
|
|
119
|
+
candidate = "#{base}-#{counter}"
|
|
120
|
+
counter += 1
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
self.permalink = candidate
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def permalink_unique_within_lato_spaces_group
|
|
127
|
+
other_pages_with_same_permalink = LatoCms::Page.for_lato_spaces_group(self.lato_spaces_group_id).where(permalink: permalink).where.not(id: id)
|
|
128
|
+
if other_pages_with_same_permalink.count.positive?
|
|
129
|
+
errors.add(:permalink, :taken)
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
end
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
module LatoCms
|
|
2
|
+
class PageField < ApplicationRecord
|
|
3
|
+
belongs_to :page, class_name: 'LatoCms::Page'
|
|
4
|
+
|
|
5
|
+
has_many_attached :files
|
|
6
|
+
|
|
7
|
+
validates :template_id, presence: true
|
|
8
|
+
validates :template_component_id, presence: true
|
|
9
|
+
validates :component_id, presence: true
|
|
10
|
+
validates :field_id, presence: true
|
|
11
|
+
validates :field_id, uniqueness: { scope: [:page_id, :template_component_id] }
|
|
12
|
+
|
|
13
|
+
before_save :parse_value
|
|
14
|
+
|
|
15
|
+
def parsed_value
|
|
16
|
+
return nil if value.blank?
|
|
17
|
+
|
|
18
|
+
case field_type
|
|
19
|
+
when 'number'
|
|
20
|
+
value.include?('.') ? value.to_f : value.to_i
|
|
21
|
+
when 'boolean'
|
|
22
|
+
value == 'true'
|
|
23
|
+
when 'json'
|
|
24
|
+
JSON.parse(value) rescue value
|
|
25
|
+
when 'date'
|
|
26
|
+
Date.parse(value) rescue value
|
|
27
|
+
when 'datetime'
|
|
28
|
+
DateTime.parse(value) rescue value
|
|
29
|
+
when 'multiselect'
|
|
30
|
+
JSON.parse(value) rescue [value]
|
|
31
|
+
else
|
|
32
|
+
value
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def field_config
|
|
37
|
+
component = LatoCms::TemplateManager.find_component(component_id)
|
|
38
|
+
return nil unless component
|
|
39
|
+
component.dig('fields', field_id)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def field_type
|
|
43
|
+
field_config&.dig('type') || 'string'
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def field_name
|
|
47
|
+
field_config&.dig('name') || field_id.to_s.humanize
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def field_required?
|
|
51
|
+
field_config&.dig('required') == true
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def field_settings
|
|
55
|
+
field_config&.dig('settings') || {}
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def as_json(_options = {})
|
|
59
|
+
result = {
|
|
60
|
+
id: id,
|
|
61
|
+
field_id: field_id,
|
|
62
|
+
field_type: field_type,
|
|
63
|
+
field_name: field_name,
|
|
64
|
+
required: field_required?,
|
|
65
|
+
value: nil,
|
|
66
|
+
attachments: []
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
case field_type
|
|
70
|
+
when 'file', 'image'
|
|
71
|
+
attached = files.first
|
|
72
|
+
result[:attachments] = attached ? [attachment_as_json(attached)] : []
|
|
73
|
+
when 'gallery'
|
|
74
|
+
order = value ? (JSON.parse(value) rescue []) : []
|
|
75
|
+
all_files = files.to_a
|
|
76
|
+
ordered = order.any? ? all_files.sort_by { |f| order.index(f.id.to_s) || Float::INFINITY } : all_files
|
|
77
|
+
result[:attachments] = ordered.map { |f| attachment_as_json(f) }
|
|
78
|
+
else
|
|
79
|
+
result[:value] = parsed_value
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
result
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
private
|
|
86
|
+
|
|
87
|
+
def attachment_as_json(attachment)
|
|
88
|
+
{
|
|
89
|
+
id: attachment.id,
|
|
90
|
+
filename: attachment.filename.to_s,
|
|
91
|
+
content_type: attachment.content_type,
|
|
92
|
+
url: Rails.application.routes.url_helpers.rails_blob_path(attachment, only_path: true)
|
|
93
|
+
}
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def parse_value
|
|
97
|
+
return if value.blank?
|
|
98
|
+
|
|
99
|
+
case field_type
|
|
100
|
+
when 'json'
|
|
101
|
+
begin
|
|
102
|
+
parsed = JSON.parse(value)
|
|
103
|
+
self.value = parsed.to_json
|
|
104
|
+
rescue JSON::ParserError
|
|
105
|
+
errors.add(:value, 'is not valid JSON')
|
|
106
|
+
throw :abort
|
|
107
|
+
end
|
|
108
|
+
when 'boolean'
|
|
109
|
+
self.value = ActiveModel::Type::Boolean.new.cast(value).to_s
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
module LatoCms
|
|
2
|
+
class TemplateManager
|
|
3
|
+
class << self
|
|
4
|
+
def templates_path
|
|
5
|
+
Rails.root.join(LatoCms.config.templates_path, 'templates')
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def components_path
|
|
9
|
+
Rails.root.join(LatoCms.config.templates_path, 'components')
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def templates
|
|
13
|
+
load_yaml_files(templates_path)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def components
|
|
17
|
+
load_yaml_files(components_path)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def find_template(template_id)
|
|
21
|
+
return nil if template_id.blank?
|
|
22
|
+
templates.find { |t| t['id'] == template_id.to_s }
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def find_component(component_id)
|
|
26
|
+
return nil if component_id.blank?
|
|
27
|
+
components.find { |c| c['id'] == component_id.to_s }
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def template_options
|
|
31
|
+
templates.map { |t| [t['name'], t['id']] }
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def resolve_template_components(template)
|
|
35
|
+
return [] unless template && template['components']
|
|
36
|
+
|
|
37
|
+
template['components'].map do |tc_id, tc_config|
|
|
38
|
+
component = find_component(tc_config['component_id'])
|
|
39
|
+
{
|
|
40
|
+
template_component_id: tc_id.to_s,
|
|
41
|
+
component_id: tc_config['component_id'].to_s,
|
|
42
|
+
name: tc_config['name'].presence || component&.dig('name') || tc_config['component_id'].to_s.humanize,
|
|
43
|
+
required: tc_config['required'] == true,
|
|
44
|
+
component: component,
|
|
45
|
+
fields: component ? (component['fields'] || {}) : {}
|
|
46
|
+
}
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
private
|
|
51
|
+
|
|
52
|
+
def load_yaml_files(path)
|
|
53
|
+
return [] unless Dir.exist?(path)
|
|
54
|
+
|
|
55
|
+
Dir.glob(File.join(path, '*.yml')).filter_map do |file|
|
|
56
|
+
YAML.load_file(file)
|
|
57
|
+
rescue Psych::SyntaxError => e
|
|
58
|
+
Rails.logger.error("LatoCms: Failed to parse YAML file #{file}: #{e.message}")
|
|
59
|
+
nil
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
<% accordion_id = "component-#{tc[:template_component_id]}" %>
|
|
2
|
+
<% component_fields = page.fields.select { |f| f.template_component_id == tc[:template_component_id] } %>
|
|
3
|
+
<% required_component = tc[:required] == true %>
|
|
4
|
+
<% enabled_component = page.component_effectively_enabled?(tc[:template_component_id]) %>
|
|
5
|
+
|
|
6
|
+
<div class="accordion-item">
|
|
7
|
+
<h2 class="accordion-header" id="heading-<%= accordion_id %>">
|
|
8
|
+
<button class="accordion-button <%= 'collapsed' unless expanded %>" type="button" data-bs-toggle="collapse" data-bs-target="#collapse-<%= accordion_id %>" aria-expanded="<%= expanded %>" aria-controls="collapse-<%= accordion_id %>">
|
|
9
|
+
<%= tc[:name] %>
|
|
10
|
+
<% unless tc[:component] %>
|
|
11
|
+
<span class="badge bg-warning ms-2"><%= t('lato_cms.fields_component_not_found_badge') %></span>
|
|
12
|
+
<% end %>
|
|
13
|
+
</button>
|
|
14
|
+
</h2>
|
|
15
|
+
<div id="collapse-<%= accordion_id %>" class="accordion-collapse collapse <%= 'show' if expanded %>" aria-labelledby="heading-<%= accordion_id %>" data-bs-parent="#components-accordion" data-template-component-id="<%= tc[:template_component_id] %>" data-component-id="<%= tc[:component_id] %>">
|
|
16
|
+
<div class="accordion-body">
|
|
17
|
+
<% if required_component %>
|
|
18
|
+
<div>
|
|
19
|
+
<% else %>
|
|
20
|
+
<div data-controller="lato-cms-component-toggle"
|
|
21
|
+
data-lato-cms-component-toggle-active-label-value="<%= t('lato_cms.component_active_label') %>"
|
|
22
|
+
data-lato-cms-component-toggle-inactive-label-value="<%= t('lato_cms.component_inactive_label') %>"
|
|
23
|
+
data-lato-cms-component-toggle-saving-label-value="<%= t('lato_cms.component_state_saving') %>"
|
|
24
|
+
data-lato-cms-component-toggle-saved-label-value="<%= t('lato_cms.component_state_saved') %>"
|
|
25
|
+
data-lato-cms-component-toggle-error-label-value="<%= t('lato_cms.component_state_save_error') %>">
|
|
26
|
+
|
|
27
|
+
<%= form_with url: lato_cms.pages_toggle_component_action_path(page, tc[:template_component_id]), method: :patch,
|
|
28
|
+
data: { turbo: false, action: 'submit->lato-cms-component-toggle#toggle' } do %>
|
|
29
|
+
<div class="d-flex justify-content-between align-items-center mb-3 pb-2 border-bottom">
|
|
30
|
+
<div class="d-flex align-items-center gap-2 small fw-medium <%= enabled_component ? 'text-success' : 'text-muted' %>" data-lato-cms-component-toggle-target="stateWrapper">
|
|
31
|
+
<i class="<%= enabled_component ? 'bi bi-eye' : 'bi bi-eye-slash' %>" data-lato-cms-component-toggle-target="stateIcon"></i>
|
|
32
|
+
<span data-lato-cms-component-toggle-target="stateLabel"><%= enabled_component ? t('lato_cms.component_active_label') : t('lato_cms.component_inactive_label') %></span>
|
|
33
|
+
</div>
|
|
34
|
+
<div class="d-flex align-items-center gap-2">
|
|
35
|
+
<span data-lato-cms-component-toggle-target="status"></span>
|
|
36
|
+
<div class="form-check form-switch m-0">
|
|
37
|
+
<%= hidden_field_tag :enabled, '0' %>
|
|
38
|
+
<%= check_box_tag :enabled, '1', enabled_component, class: 'form-check-input', id: "toggle-#{accordion_id}", onchange: 'this.form.requestSubmit()', data: { lato_cms_component_toggle_target: 'checkbox' } %>
|
|
39
|
+
<%= label_tag "toggle-#{accordion_id}", t('lato_cms.component_enabled_label'), class: 'form-check-label ms-1 small' %>
|
|
40
|
+
</div>
|
|
41
|
+
</div>
|
|
42
|
+
</div>
|
|
43
|
+
<% end %>
|
|
44
|
+
<% end %>
|
|
45
|
+
|
|
46
|
+
<% if tc[:component] %>
|
|
47
|
+
<% if required_component %>
|
|
48
|
+
<div>
|
|
49
|
+
<% else %>
|
|
50
|
+
<div data-lato-cms-component-toggle-target="fieldsContainer" class="<%= enabled_component ? '' : 'd-none' %>">
|
|
51
|
+
<% end %>
|
|
52
|
+
<%= form_with url: lato_cms.pages_save_fields_action_path(page), method: :post, multipart: true,
|
|
53
|
+
data: { turbo: false, controller: 'lato-cms-component-form' } do |form| %>
|
|
54
|
+
<%= hidden_field_tag :template_component_id, tc[:template_component_id] %>
|
|
55
|
+
<%= hidden_field_tag :component_id, tc[:component_id] %>
|
|
56
|
+
|
|
57
|
+
<% tc[:fields].each do |field_id, field_config| %>
|
|
58
|
+
<% page_field = component_fields.find { |f| f.field_id == field_id } %>
|
|
59
|
+
|
|
60
|
+
<div class="mb-3">
|
|
61
|
+
<%= lato_cms_render_field(
|
|
62
|
+
field_id: field_id,
|
|
63
|
+
field_config: field_config,
|
|
64
|
+
page_field: page_field
|
|
65
|
+
) %>
|
|
66
|
+
</div>
|
|
67
|
+
<% end %>
|
|
68
|
+
|
|
69
|
+
<div class="d-flex justify-content-end align-items-center gap-3 mt-1">
|
|
70
|
+
<span data-lato-cms-component-form-target="status"></span>
|
|
71
|
+
<button type="submit" class="btn btn-sm btn-success" data-lato-cms-component-form-target="submitBtn">
|
|
72
|
+
<i class="bi bi-check-lg me-1"></i><%= t('lato_cms.fields_save_component') %>
|
|
73
|
+
</button>
|
|
74
|
+
</div>
|
|
75
|
+
<% end %>
|
|
76
|
+
</div>
|
|
77
|
+
<% else %>
|
|
78
|
+
<div class="alert alert-warning mb-0">
|
|
79
|
+
<i class="bi bi-exclamation-triangle me-1"></i>
|
|
80
|
+
<%= t('lato_cms.fields_component_missing_alert_html', component_id: tc[:component_id]).html_safe %>
|
|
81
|
+
</div>
|
|
82
|
+
<% if component_fields.any? %>
|
|
83
|
+
<div class="mt-3">
|
|
84
|
+
<p class="text-muted small mb-2"><%= t('lato_cms.fields_previously_saved_data') %></p>
|
|
85
|
+
<% component_fields.each do |field| %>
|
|
86
|
+
<div class="mb-2">
|
|
87
|
+
<small class="text-muted"><%= field.field_id %>:</small>
|
|
88
|
+
<span><%= field.value.present? ? field.value.truncate(100) : '-' %></span>
|
|
89
|
+
</div>
|
|
90
|
+
<% end %>
|
|
91
|
+
</div>
|
|
92
|
+
<% end %>
|
|
93
|
+
<% end %>
|
|
94
|
+
</div>
|
|
95
|
+
</div>
|
|
96
|
+
</div>
|
|
97
|
+
</div>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<% page ||= @page %>
|
|
2
|
+
<% template_components ||= @template_components %>
|
|
3
|
+
|
|
4
|
+
<div class="accordion" id="components-accordion" data-controller="lato-cms-component-accordion">
|
|
5
|
+
<% template_components.each_with_index do |tc, index| %>
|
|
6
|
+
<%= render 'lato_cms/pages/component_accordion',
|
|
7
|
+
page: page,
|
|
8
|
+
tc: tc,
|
|
9
|
+
index: index,
|
|
10
|
+
expanded: index == 0 %>
|
|
11
|
+
<% end %>
|
|
12
|
+
</div>
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
<% page ||= LatoCms::Page.new %>
|
|
2
|
+
|
|
3
|
+
<%= turbo_frame_tag dom_id(page, 'form') do %>
|
|
4
|
+
<%= form_with model: page, url: lato_cms.pages_create_action_path, data: { turbo_frame: '_self', controller: 'lato-form' } do |form| %>
|
|
5
|
+
<%= lato_form_notices class: %w[mb-3] %>
|
|
6
|
+
<%= lato_form_errors page, class: %w[mb-3] %>
|
|
7
|
+
|
|
8
|
+
<div class="mb-3">
|
|
9
|
+
<%= lato_form_item_label form, :title %>
|
|
10
|
+
<%= lato_form_item_input_text form, :title, required: true %>
|
|
11
|
+
</div>
|
|
12
|
+
|
|
13
|
+
<% if LatoCms.config.locales.length > 1 %>
|
|
14
|
+
<div class="mb-3">
|
|
15
|
+
<%= lato_form_item_label form, :locale %>
|
|
16
|
+
<%= lato_form_item_input_select form, :locale, LatoCms.config.locales.map { |l| [l.to_s.upcase, l.to_s] }, required: true %>
|
|
17
|
+
</div>
|
|
18
|
+
<% else %>
|
|
19
|
+
<%= form.hidden_field :locale, value: LatoCms.config.locales.first.to_s %>
|
|
20
|
+
<% end %>
|
|
21
|
+
|
|
22
|
+
<div class="d-flex justify-content-end">
|
|
23
|
+
<%= lato_form_submit form, t('lato_cms.page_create_cta'), class: %w[btn-success] %>
|
|
24
|
+
</div>
|
|
25
|
+
<% end %>
|
|
26
|
+
<% end %>
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
<% page ||= LatoCms::Page.new %>
|
|
2
|
+
|
|
3
|
+
<%= turbo_frame_tag dom_id(page, 'form') do %>
|
|
4
|
+
<%= form_with model: page, url: lato_cms.pages_update_action_path(page), method: :patch, data: { turbo_frame: '_self', controller: 'lato-form' } do |form| %>
|
|
5
|
+
<%= lato_form_notices class: %w[mb-3] %>
|
|
6
|
+
<%= lato_form_errors page, class: %w[mb-3] %>
|
|
7
|
+
|
|
8
|
+
<div class="mb-3">
|
|
9
|
+
<%= lato_form_item_label form, :title %>
|
|
10
|
+
<%= lato_form_item_input_text form, :title, required: true %>
|
|
11
|
+
</div>
|
|
12
|
+
|
|
13
|
+
<div class="mb-3">
|
|
14
|
+
<%= lato_form_item_label form, :permalink %>
|
|
15
|
+
<%= lato_form_item_input_text form, :permalink, required: true %>
|
|
16
|
+
</div>
|
|
17
|
+
|
|
18
|
+
<div class="mb-3">
|
|
19
|
+
<%= lato_form_item_label form, :frontend_url, t('lato_cms.form_frontend_url') %>
|
|
20
|
+
<%= lato_form_item_input_text form, :frontend_url %>
|
|
21
|
+
</div>
|
|
22
|
+
|
|
23
|
+
<div class="mb-3">
|
|
24
|
+
<% template_options = [[t('lato_cms.form_no_template_option'), '']] + LatoCms::TemplateManager.template_options %>
|
|
25
|
+
<%= lato_form_item_label form, :template_id, t('lato_cms.form_template') %>
|
|
26
|
+
<%= lato_form_item_input_select form, :template_id, template_options %>
|
|
27
|
+
<% if page.template_id.present? && !page.template_available? %>
|
|
28
|
+
<div class="form-text text-danger">
|
|
29
|
+
<i class="bi bi-exclamation-triangle me-1"></i>
|
|
30
|
+
<%= t('lato_cms.form_template_unavailable', template_id: page.template_id) %>
|
|
31
|
+
</div>
|
|
32
|
+
<% end %>
|
|
33
|
+
</div>
|
|
34
|
+
|
|
35
|
+
<div class="d-flex justify-content-end">
|
|
36
|
+
<%= lato_form_submit form, t('lato_cms.cta_update'), class: %w[btn-success] %>
|
|
37
|
+
</div>
|
|
38
|
+
<% end %>
|
|
39
|
+
<% end %>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<%= lato_page_head t('lato_cms.page_create_title'), [
|
|
2
|
+
{ label: t('lato_cms.pages_index_title'), path: lato_cms.pages_path },
|
|
3
|
+
{ label: t('lato_cms.page_create_title') }
|
|
4
|
+
] %>
|
|
5
|
+
|
|
6
|
+
<div class="card mb-4">
|
|
7
|
+
<div class="card-header">
|
|
8
|
+
<h2 class="fs-4 mb-0"><%= t('lato_cms.page_create_title') %></h2>
|
|
9
|
+
</div>
|
|
10
|
+
<div class="card-body">
|
|
11
|
+
<%= render 'lato_cms/pages/form_create', page: @page %>
|
|
12
|
+
</div>
|
|
13
|
+
</div>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
<% label = field_config['name'] || field_id.humanize %>
|
|
2
|
+
<% current_value = page_field&.value %>
|
|
3
|
+
<% checked = current_value == 'true' %>
|
|
4
|
+
|
|
5
|
+
<div class="form-check form-switch">
|
|
6
|
+
<input type="hidden" name="fields[<%= field_id %>][value]" value="false">
|
|
7
|
+
<input type="checkbox"
|
|
8
|
+
class="form-check-input"
|
|
9
|
+
name="fields[<%= field_id %>][value]"
|
|
10
|
+
id="fields_<%= field_id %>_value"
|
|
11
|
+
value="true"
|
|
12
|
+
<%= 'checked' if checked %>>
|
|
13
|
+
<label class="form-check-label" for="fields_<%= field_id %>_value">
|
|
14
|
+
<%= label %>
|
|
15
|
+
</label>
|
|
16
|
+
</div>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
<% label = field_config['name'] || field_id.humanize %>
|
|
2
|
+
<% required = field_config['required'] == true %>
|
|
3
|
+
<% current_value = page_field&.value.presence || '#000000' %>
|
|
4
|
+
|
|
5
|
+
<label class="form-label" for="fields_<%= field_id %>_value">
|
|
6
|
+
<%= label %><%= ' *' if required %>
|
|
7
|
+
</label>
|
|
8
|
+
<div class="d-flex align-items-center gap-2" data-controller="lato-cms-color-field">
|
|
9
|
+
<input type="color"
|
|
10
|
+
class="form-control form-control-color"
|
|
11
|
+
name="fields[<%= field_id %>][value]"
|
|
12
|
+
id="fields_<%= field_id %>_value"
|
|
13
|
+
value="<%= current_value %>"
|
|
14
|
+
data-lato-cms-color-field-target="input"
|
|
15
|
+
data-action="input->lato-cms-color-field#update"
|
|
16
|
+
<%= 'required' if required %>>
|
|
17
|
+
<code class="text-muted small" data-lato-cms-color-field-target="hex"><%= current_value.upcase %></code>
|
|
18
|
+
</div>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
<% label = field_config['name'] || field_id.humanize %>
|
|
2
|
+
<% required = field_config['required'] == true %>
|
|
3
|
+
<% settings = field_config['settings'] || {} %>
|
|
4
|
+
<% current_value = page_field&.value || '' %>
|
|
5
|
+
|
|
6
|
+
<label class="form-label" for="fields_<%= field_id %>_value">
|
|
7
|
+
<%= label %><%= ' *' if required %>
|
|
8
|
+
</label>
|
|
9
|
+
<input type="date"
|
|
10
|
+
class="form-control"
|
|
11
|
+
name="fields[<%= field_id %>][value]"
|
|
12
|
+
id="fields_<%= field_id %>_value"
|
|
13
|
+
value="<%= current_value %>"
|
|
14
|
+
<%= 'required' if required %>
|
|
15
|
+
<% if settings['min'] %>min="<%= settings['min'] %>"<% end %>
|
|
16
|
+
<% if settings['max'] %>max="<%= settings['max'] %>"<% end %>>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
<% label = field_config['name'] || field_id.humanize %>
|
|
2
|
+
<% required = field_config['required'] == true %>
|
|
3
|
+
<% settings = field_config['settings'] || {} %>
|
|
4
|
+
<% current_value = page_field&.value || '' %>
|
|
5
|
+
|
|
6
|
+
<label class="form-label" for="fields_<%= field_id %>_value">
|
|
7
|
+
<%= label %><%= ' *' if required %>
|
|
8
|
+
</label>
|
|
9
|
+
<input type="datetime-local"
|
|
10
|
+
class="form-control"
|
|
11
|
+
name="fields[<%= field_id %>][value]"
|
|
12
|
+
id="fields_<%= field_id %>_value"
|
|
13
|
+
value="<%= current_value %>"
|
|
14
|
+
<%= 'required' if required %>
|
|
15
|
+
<% if settings['min'] %>min="<%= settings['min'] %>"<% end %>
|
|
16
|
+
<% if settings['max'] %>max="<%= settings['max'] %>"<% end %>>
|