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.
Files changed (62) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +67 -0
  4. data/Rakefile +6 -0
  5. data/app/assets/config/lato_cms_manifest.js +2 -0
  6. data/app/assets/javascripts/lato_cms/application.js +1 -0
  7. data/app/assets/javascripts/lato_cms/controllers/lato_cms_advanced_editor_controller.js +57 -0
  8. data/app/assets/javascripts/lato_cms/controllers/lato_cms_color_field_controller.js +9 -0
  9. data/app/assets/javascripts/lato_cms/controllers/lato_cms_component_accordion_controller.js +60 -0
  10. data/app/assets/javascripts/lato_cms/controllers/lato_cms_component_form_controller.js +96 -0
  11. data/app/assets/javascripts/lato_cms/controllers/lato_cms_component_toggle_controller.js +89 -0
  12. data/app/assets/javascripts/lato_cms/controllers/lato_cms_gallery_field_controller.js +136 -0
  13. data/app/assets/javascripts/lato_cms/controllers/lato_cms_image_field_controller.js +24 -0
  14. data/app/assets/javascripts/lato_cms/controllers/lato_cms_json_field_controller.js +27 -0
  15. data/app/assets/javascripts/lato_cms/controllers/lato_cms_page_preview_controller.js +86 -0
  16. data/app/assets/javascripts/lato_cms/controllers/lato_cms_text_field_controller.js +74 -0
  17. data/app/assets/stylesheets/lato_cms/application.scss +252 -0
  18. data/app/controllers/lato_cms/api/pages_controller.rb +38 -0
  19. data/app/controllers/lato_cms/application_controller.rb +26 -0
  20. data/app/controllers/lato_cms/pages_controller.rb +194 -0
  21. data/app/helpers/lato_cms/application_helper.rb +4 -0
  22. data/app/helpers/lato_cms/pages_helper.rb +69 -0
  23. data/app/jobs/lato_cms/application_job.rb +4 -0
  24. data/app/mailers/lato_cms/application_mailer.rb +6 -0
  25. data/app/models/lato_cms/page.rb +133 -0
  26. data/app/models/lato_cms/page_field.rb +113 -0
  27. data/app/models/lato_cms/template_manager.rb +64 -0
  28. data/app/views/lato_cms/pages/_component_accordion.html.erb +97 -0
  29. data/app/views/lato_cms/pages/_fields_editor.html.erb +12 -0
  30. data/app/views/lato_cms/pages/_form_create.html.erb +26 -0
  31. data/app/views/lato_cms/pages/_form_update.html.erb +39 -0
  32. data/app/views/lato_cms/pages/create.html.erb +13 -0
  33. data/app/views/lato_cms/pages/fields/_boolean.html.erb +16 -0
  34. data/app/views/lato_cms/pages/fields/_color.html.erb +18 -0
  35. data/app/views/lato_cms/pages/fields/_date.html.erb +16 -0
  36. data/app/views/lato_cms/pages/fields/_datetime.html.erb +16 -0
  37. data/app/views/lato_cms/pages/fields/_file.html.erb +29 -0
  38. data/app/views/lato_cms/pages/fields/_gallery.html.erb +55 -0
  39. data/app/views/lato_cms/pages/fields/_image.html.erb +39 -0
  40. data/app/views/lato_cms/pages/fields/_json.html.erb +23 -0
  41. data/app/views/lato_cms/pages/fields/_multiselect.html.erb +25 -0
  42. data/app/views/lato_cms/pages/fields/_number.html.erb +18 -0
  43. data/app/views/lato_cms/pages/fields/_select.html.erb +23 -0
  44. data/app/views/lato_cms/pages/fields/_string.html.erb +18 -0
  45. data/app/views/lato_cms/pages/fields/_text.html.erb +80 -0
  46. data/app/views/lato_cms/pages/fields/_textarea.html.erb +16 -0
  47. data/app/views/lato_cms/pages/index.html.erb +32 -0
  48. data/app/views/lato_cms/pages/show.html.erb +105 -0
  49. data/app/views/lato_cms/pages/update.html.erb +13 -0
  50. data/config/importmap.rb +2 -0
  51. data/config/locales/en.yml +70 -0
  52. data/config/locales/it.yml +70 -0
  53. data/config/routes.rb +22 -0
  54. data/db/migrate/20250328072343_add_lato_cms_admin_to_lato_user.rb +5 -0
  55. data/db/migrate/20260320070819_create_lato_cms_pages.rb +13 -0
  56. data/db/migrate/20260323171241_create_lato_cms_page_fields.rb +13 -0
  57. data/lib/lato_cms/config.rb +13 -0
  58. data/lib/lato_cms/engine.rb +13 -0
  59. data/lib/lato_cms/version.rb +3 -0
  60. data/lib/lato_cms.rb +15 -0
  61. data/lib/tasks/lato_cms_tasks.rake +183 -0
  62. 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,4 @@
1
+ module LatoCms
2
+ class ApplicationJob < ActiveJob::Base
3
+ end
4
+ end
@@ -0,0 +1,6 @@
1
+ module LatoCms
2
+ class ApplicationMailer < ActionMailer::Base
3
+ default from: "from@example.com"
4
+ layout "mailer"
5
+ end
6
+ 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 %>>