lato_cms 3.0.0 → 3.0.2

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 (33) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/javascripts/lato_cms/controllers/lato_cms_component_form_controller.js +4 -0
  3. data/app/assets/javascripts/lato_cms/controllers/lato_cms_file_field_controller.js +203 -0
  4. data/app/assets/javascripts/lato_cms/controllers/lato_cms_gallery_field_controller.js +36 -4
  5. data/app/assets/javascripts/lato_cms/controllers/lato_cms_image_field_controller.js +135 -6
  6. data/app/assets/javascripts/lato_cms/controllers/lato_cms_repeater_controller.js +33 -0
  7. data/app/assets/stylesheets/lato_cms/application.scss +107 -3
  8. data/app/controllers/lato_cms/pages_controller.rb +120 -49
  9. data/app/helpers/lato_cms/pages_helper.rb +15 -2
  10. data/app/models/lato_cms/page.rb +35 -4
  11. data/app/models/lato_cms/page_field.rb +27 -3
  12. data/app/models/lato_cms/template_manager.rb +2 -0
  13. data/app/views/lato_cms/pages/_component_accordion.html.erb +13 -9
  14. data/app/views/lato_cms/pages/_repeater.html.erb +22 -0
  15. data/app/views/lato_cms/pages/_repeater_item.html.erb +28 -0
  16. data/app/views/lato_cms/pages/fields/_boolean.html.erb +6 -4
  17. data/app/views/lato_cms/pages/fields/_color.html.erb +5 -3
  18. data/app/views/lato_cms/pages/fields/_date.html.erb +5 -3
  19. data/app/views/lato_cms/pages/fields/_datetime.html.erb +5 -3
  20. data/app/views/lato_cms/pages/fields/_file.html.erb +40 -15
  21. data/app/views/lato_cms/pages/fields/_gallery.html.erb +3 -2
  22. data/app/views/lato_cms/pages/fields/_image.html.erb +42 -14
  23. data/app/views/lato_cms/pages/fields/_json.html.erb +5 -3
  24. data/app/views/lato_cms/pages/fields/_multiselect.html.erb +5 -3
  25. data/app/views/lato_cms/pages/fields/_number.html.erb +5 -3
  26. data/app/views/lato_cms/pages/fields/_select.html.erb +5 -3
  27. data/app/views/lato_cms/pages/fields/_string.html.erb +5 -3
  28. data/app/views/lato_cms/pages/fields/_text.html.erb +5 -3
  29. data/app/views/lato_cms/pages/fields/_textarea.html.erb +5 -3
  30. data/config/locales/en.yml +6 -0
  31. data/config/locales/it.yml +6 -0
  32. data/lib/lato_cms/version.rb +1 -1
  33. metadata +5 -1
@@ -77,62 +77,20 @@ module LatoCms
77
77
  return
78
78
  end
79
79
 
80
- fields_data.each do |field_id, field_data|
81
- field = @page.fields.find_or_initialize_by(
82
- template_id: @page.template_id,
83
- template_component_id: template_component_id,
84
- component_id: component_id,
85
- field_id: field_id
86
- )
87
-
88
- field_config = component&.dig('fields', field_id)
89
- field_type = field_config&.dig('type') || 'string'
90
-
91
- case field_type
92
- when 'file', 'image'
93
- field.save if field.new_record?
94
- if field_data[:files].present?
95
- Array(field_data[:files]).compact.each { |f| field.files.attach(f) }
96
- end
97
- if field_data[:remove_file_ids].present?
98
- Array(field_data[:remove_file_ids]).reject(&:blank?).each do |file_id_to_remove|
99
- field.files.find { |f| f.id == file_id_to_remove.to_i }&.purge
100
- end
101
- end
102
- when 'gallery'
103
- field.save if field.new_record?
104
- if field_data[:files].present?
105
- Array(field_data[:files]).compact.each { |f| field.files.attach(f) }
106
- end
107
- if field_data[:remove_file_ids].present?
108
- Array(field_data[:remove_file_ids]).reject(&:blank?).each do |file_id_to_remove|
109
- field.files.find { |f| f.id == file_id_to_remove.to_i }&.purge
110
- end
111
- end
112
- # Persist order: existing IDs in dragged order + any new file IDs appended at end
113
- order = Array(field_data[:order]).reject(&:blank?).map(&:to_s)
114
- all_ids = field.files.reload.map { |f| f.id.to_s }
115
- sorted = order.select { |id| all_ids.include?(id) }
116
- new_ids = all_ids - sorted
117
- field.value = (sorted + new_ids).to_json
118
- when 'multiselect'
119
- value = field_data[:value]
120
- value = Array(value).reject(&:blank?)
121
- field.value = value.to_json
122
- else
123
- raw_value = field_data.is_a?(ActionController::Parameters) ? field_data[:value] : field_data
124
- field.value = raw_value.to_s.presence
125
- end
80
+ template_component = @page.template_components.find { |tc| tc[:template_component_id] == template_component_id.to_s }
126
81
 
127
- unless field.save
128
- errors << { field_id: field_id, errors: field.errors.full_messages }
82
+ if template_component&.dig(:repeater)
83
+ save_repeater_fields(template_component, component, params[:repeater_items] || {}, params[:repeater_order] || [], errors)
84
+ else
85
+ fields_data.each do |field_id, field_data|
86
+ save_field(component, template_component_id, component_id, field_id, field_id, field_data, errors)
129
87
  end
130
88
  end
131
89
 
132
90
  respond_to do |format|
133
91
  if errors.empty?
134
92
  format.html { redirect_to lato_cms.pages_show_path(@page), notice: t('lato_cms.fields_saved') }
135
- format.json { render json: { message: t('lato_cms.fields_saved') } }
93
+ format.json { render json: { message: t('lato_cms.fields_saved'), fields: @page.fields.reload.map(&:as_json) } }
136
94
  else
137
95
  error_messages = errors.map { |e| "#{e[:field_id]}: #{e[:errors].join(', ')}" }.join('; ')
138
96
  format.html { redirect_to lato_cms.pages_show_path(@page), alert: error_messages }
@@ -183,6 +141,119 @@ module LatoCms
183
141
 
184
142
  private
185
143
 
144
+ def save_repeater_fields(template_component, component, repeater_items, repeater_order, errors)
145
+ template_component_id = template_component[:template_component_id]
146
+ component_id = template_component[:component_id]
147
+ item_ids = Array(repeater_order).reject(&:blank?).map(&:to_s)
148
+
149
+ order_field = @page.fields.find_or_initialize_by(
150
+ template_id: @page.template_id,
151
+ template_component_id: template_component_id,
152
+ component_id: component_id,
153
+ field_id: LatoCms::PageField::REPEATER_ORDER_FIELD_ID
154
+ )
155
+ order_field.value = item_ids.to_json
156
+ errors << { field_id: order_field.field_id, errors: order_field.errors.full_messages } unless order_field.save
157
+
158
+ existing_item_ids = @page.fields.where(template_component_id: template_component_id).reject(&:repeater_order?).filter_map(&:repeater_item_id).uniq
159
+ removed_item_ids = existing_item_ids - item_ids
160
+ removed_item_ids.each do |item_id|
161
+ @page.fields
162
+ .where(template_component_id: template_component_id)
163
+ .where("field_id LIKE ?", "#{ActiveRecord::Base.sanitize_sql_like(item_id)}.%")
164
+ .destroy_all
165
+ end
166
+
167
+ item_ids.each do |item_id|
168
+ item_fields = repeater_items[item_id] || repeater_items[item_id.to_sym] || {}
169
+ (template_component[:fields] || {}).each_key do |field_id|
170
+ field_data = item_fields[field_id] || item_fields[field_id.to_sym] || {}
171
+ save_field(component, template_component_id, component_id, field_id, "#{item_id}.#{field_id}", field_data, errors)
172
+ end
173
+ end
174
+ end
175
+
176
+ def save_field(component, template_component_id, component_id, config_field_id, persisted_field_id, field_data, errors)
177
+ field = @page.fields.find_or_initialize_by(
178
+ template_id: @page.template_id,
179
+ template_component_id: template_component_id,
180
+ component_id: component_id,
181
+ field_id: persisted_field_id
182
+ )
183
+
184
+ field_config = component&.dig('fields', config_field_id)
185
+ field_type = field_config&.dig('type') || 'string'
186
+
187
+ assign_field_value(field, field_type, field_data)
188
+
189
+ unless field.save
190
+ errors << { field_id: persisted_field_id, errors: field.errors.full_messages }
191
+ end
192
+ end
193
+
194
+ def assign_field_value(field, field_type, field_data)
195
+ case field_type
196
+ when 'file'
197
+ field.save if field.new_record?
198
+ assign_file_field_files(field, field_data)
199
+ when 'image'
200
+ field.save if field.new_record?
201
+ replace_field_image(field, field_data)
202
+ when 'gallery'
203
+ field.save if field.new_record?
204
+ attach_field_files(field, field_data)
205
+ remove_field_files(field, field_data)
206
+ order = Array(field_data[:order]).reject(&:blank?).map(&:to_s)
207
+ all_ids = field.files.reload.map { |f| f.id.to_s }
208
+ sorted = order.select { |id| all_ids.include?(id) }
209
+ field.value = (sorted + (all_ids - sorted)).to_json
210
+ when 'multiselect'
211
+ field.value = Array(field_data[:value]).reject(&:blank?).to_json
212
+ else
213
+ raw_value = field_data.is_a?(ActionController::Parameters) ? field_data[:value] : field_data
214
+ field.value = raw_value.to_s.presence
215
+ end
216
+ end
217
+
218
+ def attach_field_files(field, field_data)
219
+ return unless field_data[:files].present?
220
+
221
+ Array(field_data[:files]).compact.each { |file| field.files.attach(file) }
222
+ end
223
+
224
+ def assign_file_field_files(field, field_data)
225
+ if field.field_settings['multiple'] != true && field_data[:files].present?
226
+ new_file = Array(field_data[:files]).compact.first
227
+ return unless new_file
228
+
229
+ field.files.each(&:purge)
230
+ field.files.attach(new_file)
231
+ else
232
+ attach_field_files(field, field_data)
233
+ remove_field_files(field, field_data)
234
+ end
235
+ end
236
+
237
+ def remove_field_files(field, field_data)
238
+ return unless field_data[:remove_file_ids].present?
239
+
240
+ Array(field_data[:remove_file_ids]).reject(&:blank?).each do |file_id_to_remove|
241
+ field.files.find { |file| file.id == file_id_to_remove.to_i }&.purge
242
+ end
243
+ end
244
+
245
+ def replace_field_image(field, field_data)
246
+ if field_data[:files].present?
247
+ new_file = Array(field_data[:files]).compact.first
248
+ return unless new_file
249
+
250
+ field.files.each(&:purge)
251
+ field.files.attach(new_file)
252
+ else
253
+ remove_field_files(field, field_data)
254
+ end
255
+ end
256
+
186
257
  def create_params
187
258
  params.require(:page).permit(:title, :locale)
188
259
  end
@@ -42,17 +42,30 @@ module LatoCms
42
42
  end
43
43
  end
44
44
 
45
- def lato_cms_render_field(field_id:, field_config:, page_field:)
45
+ def lato_cms_render_field(field_id:, field_config:, page_field:, input_name_prefix: nil, dom_id_prefix: nil)
46
46
  render resolve_lato_cms_field_partial(field_config),
47
47
  field_id: field_id,
48
48
  field_config: field_config,
49
- page_field: page_field
49
+ page_field: page_field,
50
+ input_name_prefix: input_name_prefix,
51
+ dom_id_prefix: dom_id_prefix
50
52
  rescue ActionView::MissingTemplate => e
51
53
  content_tag(:div, class: 'alert alert-danger mb-0') do
52
54
  "Field '#{field_id}' render error: #{e.message}"
53
55
  end
54
56
  end
55
57
 
58
+ def lato_cms_field_input_name(field_id, suffix = 'value', input_name_prefix: nil, multiple: false)
59
+ base = input_name_prefix.presence || "fields[#{field_id}]"
60
+ name = "#{base}[#{suffix}]"
61
+ multiple ? "#{name}[]" : name
62
+ end
63
+
64
+ def lato_cms_field_dom_id(field_id, suffix = 'value', dom_id_prefix: nil)
65
+ base = dom_id_prefix.presence || "fields_#{field_id}"
66
+ "#{base}_#{suffix}".parameterize(separator: '_')
67
+ end
68
+
56
69
  private
57
70
 
58
71
  def resolve_lato_cms_field_partial(field_config)
@@ -87,14 +87,45 @@ module LatoCms
87
87
  hash[tc[:template_component_id]] = {
88
88
  component_id: tc[:component_id],
89
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
90
+ fields: tc[:repeater] ?
91
+ build_repeater_fields_json(tc, fields_by_component[tc[:template_component_id]] || []) :
92
+ build_component_fields_json(tc, component_fields)
94
93
  }
95
94
  end
96
95
  end
97
96
 
97
+ def build_component_fields_json(tc, component_fields)
98
+ (tc[:fields] || {}).each_with_object({}) do |(fid, fconfig), fhash|
99
+ field = component_fields[fid.to_s]
100
+ fhash[fid] = field ? field.as_json : empty_field_json(fid.to_s, fconfig)
101
+ end
102
+ end
103
+
104
+ def build_repeater_fields_json(tc, component_fields)
105
+ order = repeater_order(component_fields)
106
+ fields_by_item = component_fields.reject(&:repeater_order?).group_by(&:repeater_item_id)
107
+ item_ids = order.presence || fields_by_item.keys.compact
108
+
109
+ item_ids.filter_map do |item_id|
110
+ item_fields = (fields_by_item[item_id] || []).index_by(&:base_field_id)
111
+ next if item_fields.blank?
112
+
113
+ {
114
+ id: item_id,
115
+ fields: build_component_fields_json(tc, item_fields)
116
+ }
117
+ end
118
+ end
119
+
120
+ def repeater_order(component_fields)
121
+ order_field = component_fields.find(&:repeater_order?)
122
+ return [] unless order_field&.value.present?
123
+
124
+ JSON.parse(order_field.value)
125
+ rescue JSON::ParserError
126
+ []
127
+ end
128
+
98
129
  def empty_field_json(fid, fconfig)
99
130
  {
100
131
  id: nil,
@@ -10,6 +10,8 @@ module LatoCms
10
10
  validates :field_id, presence: true
11
11
  validates :field_id, uniqueness: { scope: [:page_id, :template_component_id] }
12
12
 
13
+ REPEATER_ORDER_FIELD_ID = "__repeater_order".freeze
14
+
13
15
  before_save :parse_value
14
16
 
15
17
  def parsed_value
@@ -34,9 +36,27 @@ module LatoCms
34
36
  end
35
37
 
36
38
  def field_config
39
+ return nil if repeater_order?
40
+
37
41
  component = LatoCms::TemplateManager.find_component(component_id)
38
42
  return nil unless component
39
- component.dig('fields', field_id)
43
+ component.dig('fields', base_field_id)
44
+ end
45
+
46
+ def repeater_order?
47
+ field_id == REPEATER_ORDER_FIELD_ID
48
+ end
49
+
50
+ def repeater_item_id
51
+ return nil unless field_id.to_s.include?('.')
52
+
53
+ field_id.to_s.split('.', 2).first
54
+ end
55
+
56
+ def base_field_id
57
+ return field_id unless repeater_item_id
58
+
59
+ field_id.to_s.split('.', 2).last
40
60
  end
41
61
 
42
62
  def field_type
@@ -58,7 +78,8 @@ module LatoCms
58
78
  def as_json(_options = {})
59
79
  result = {
60
80
  id: id,
61
- field_id: field_id,
81
+ persisted_field_id: field_id,
82
+ field_id: base_field_id,
62
83
  field_type: field_type,
63
84
  field_name: field_name,
64
85
  required: field_required?,
@@ -67,7 +88,9 @@ module LatoCms
67
88
  }
68
89
 
69
90
  case field_type
70
- when 'file', 'image'
91
+ when 'file'
92
+ result[:attachments] = files.map { |f| attachment_as_json(f) }
93
+ when 'image'
71
94
  attached = files.first
72
95
  result[:attachments] = attached ? [attachment_as_json(attached)] : []
73
96
  when 'gallery'
@@ -89,6 +112,7 @@ module LatoCms
89
112
  id: attachment.id,
90
113
  filename: attachment.filename.to_s,
91
114
  content_type: attachment.content_type,
115
+ byte_size: attachment.byte_size,
92
116
  url: Rails.application.routes.url_helpers.rails_blob_path(attachment, only_path: true)
93
117
  }
94
118
  end
@@ -41,6 +41,8 @@ module LatoCms
41
41
  component_id: tc_config['component_id'].to_s,
42
42
  name: tc_config['name'].presence || component&.dig('name') || tc_config['component_id'].to_s.humanize,
43
43
  required: tc_config['required'] == true,
44
+ repeater: tc_config['repeater'] == true || tc_config['repeatable'] == true,
45
+ settings: tc_config['settings'] || {},
44
46
  component: component,
45
47
  fields: component ? (component['fields'] || {}) : {}
46
48
  }
@@ -54,16 +54,20 @@
54
54
  <%= hidden_field_tag :template_component_id, tc[:template_component_id] %>
55
55
  <%= hidden_field_tag :component_id, tc[:component_id] %>
56
56
 
57
- <% tc[:fields].each do |field_id, field_config| %>
58
- <% page_field = component_fields.find { |f| f.field_id == field_id } %>
57
+ <% if tc[:repeater] %>
58
+ <%= render 'lato_cms/pages/repeater', tc: tc, component_fields: component_fields %>
59
+ <% else %>
60
+ <% tc[:fields].each do |field_id, field_config| %>
61
+ <% page_field = component_fields.find { |f| f.field_id == field_id } %>
59
62
 
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>
63
+ <div class="mb-3">
64
+ <%= lato_cms_render_field(
65
+ field_id: field_id,
66
+ field_config: field_config,
67
+ page_field: page_field
68
+ ) %>
69
+ </div>
70
+ <% end %>
67
71
  <% end %>
68
72
 
69
73
  <div class="d-flex justify-content-end align-items-center gap-3 mt-1">
@@ -0,0 +1,22 @@
1
+ <% order_field = component_fields.find(&:repeater_order?) %>
2
+ <% saved_order = begin; order_field&.value.present? ? JSON.parse(order_field.value) : []; rescue JSON::ParserError; []; end %>
3
+ <% fields_by_item = component_fields.reject(&:repeater_order?).group_by(&:repeater_item_id) %>
4
+ <% item_ids = (saved_order.presence || fields_by_item.keys.compact) %>
5
+ <% item_ids = [SecureRandom.uuid] if item_ids.blank? && tc[:settings]['min'].to_i.positive? %>
6
+
7
+ <div data-controller="lato-cms-repeater">
8
+ <div data-lato-cms-repeater-target="items">
9
+ <% item_ids.each_with_index do |item_id, item_index| %>
10
+ <% item_fields = (fields_by_item[item_id] || []).index_by(&:base_field_id) %>
11
+ <%= render 'lato_cms/pages/repeater_item', tc: tc, item_id: item_id, item_index: item_index, item_fields: item_fields %>
12
+ <% end %>
13
+ </div>
14
+
15
+ <template data-lato-cms-repeater-target="template">
16
+ <%= render 'lato_cms/pages/repeater_item', tc: tc, item_id: 'NEW_RECORD', item_index: 'NEW_INDEX', item_fields: {} %>
17
+ </template>
18
+
19
+ <button type="button" class="btn btn-sm btn-outline-primary" data-action="lato-cms-repeater#add">
20
+ <i class="bi bi-plus-lg me-1"></i><%= t('lato_cms.repeater_add_item') %>
21
+ </button>
22
+ </div>
@@ -0,0 +1,28 @@
1
+ <div class="card mb-3" data-lato-cms-repeater-target="item" data-repeater-item-id="<%= item_id %>">
2
+ <input type="hidden" name="repeater_order[]" value="<%= item_id %>" data-lato-cms-repeater-target="orderInput">
3
+
4
+ <div class="card-header d-flex justify-content-between align-items-center py-2">
5
+ <strong class="small"><%= tc[:name] %> #<span data-lato-cms-repeater-target="position"><%= item_index.to_i + 1 %></span></strong>
6
+ <button type="button" class="btn btn-sm btn-outline-danger" data-action="lato-cms-repeater#remove">
7
+ <i class="bi bi-trash me-1"></i><%= t('lato_cms.repeater_remove_item') %>
8
+ </button>
9
+ </div>
10
+
11
+ <div class="card-body pb-2">
12
+ <% tc[:fields].each do |field_id, field_config| %>
13
+ <% page_field = item_fields[field_id.to_s] %>
14
+ <% input_name_prefix = "repeater_items[#{item_id}][#{field_id}]" %>
15
+ <% dom_id_prefix = "repeater_items_#{item_id}_#{field_id}" %>
16
+
17
+ <div class="mb-3">
18
+ <%= lato_cms_render_field(
19
+ field_id: field_id,
20
+ field_config: field_config,
21
+ page_field: page_field,
22
+ input_name_prefix: input_name_prefix,
23
+ dom_id_prefix: dom_id_prefix
24
+ ) %>
25
+ </div>
26
+ <% end %>
27
+ </div>
28
+ </div>
@@ -1,16 +1,18 @@
1
1
  <% label = field_config['name'] || field_id.humanize %>
2
2
  <% current_value = page_field&.value %>
3
3
  <% checked = current_value == 'true' %>
4
+ <% input_name = lato_cms_field_input_name(field_id, input_name_prefix: local_assigns[:input_name_prefix]) %>
5
+ <% input_id = lato_cms_field_dom_id(field_id, dom_id_prefix: local_assigns[:dom_id_prefix]) %>
4
6
 
5
7
  <div class="form-check form-switch">
6
- <input type="hidden" name="fields[<%= field_id %>][value]" value="false">
8
+ <input type="hidden" name="<%= input_name %>" value="false">
7
9
  <input type="checkbox"
8
10
  class="form-check-input"
9
- name="fields[<%= field_id %>][value]"
10
- id="fields_<%= field_id %>_value"
11
+ name="<%= input_name %>"
12
+ id="<%= input_id %>"
11
13
  value="true"
12
14
  <%= 'checked' if checked %>>
13
- <label class="form-check-label" for="fields_<%= field_id %>_value">
15
+ <label class="form-check-label" for="<%= input_id %>">
14
16
  <%= label %>
15
17
  </label>
16
18
  </div>
@@ -1,15 +1,17 @@
1
1
  <% label = field_config['name'] || field_id.humanize %>
2
2
  <% required = field_config['required'] == true %>
3
3
  <% current_value = page_field&.value.presence || '#000000' %>
4
+ <% input_name = lato_cms_field_input_name(field_id, input_name_prefix: local_assigns[:input_name_prefix]) %>
5
+ <% input_id = lato_cms_field_dom_id(field_id, dom_id_prefix: local_assigns[:dom_id_prefix]) %>
4
6
 
5
- <label class="form-label" for="fields_<%= field_id %>_value">
7
+ <label class="form-label" for="<%= input_id %>">
6
8
  <%= label %><%= ' *' if required %>
7
9
  </label>
8
10
  <div class="d-flex align-items-center gap-2" data-controller="lato-cms-color-field">
9
11
  <input type="color"
10
12
  class="form-control form-control-color"
11
- name="fields[<%= field_id %>][value]"
12
- id="fields_<%= field_id %>_value"
13
+ name="<%= input_name %>"
14
+ id="<%= input_id %>"
13
15
  value="<%= current_value %>"
14
16
  data-lato-cms-color-field-target="input"
15
17
  data-action="input->lato-cms-color-field#update"
@@ -2,14 +2,16 @@
2
2
  <% required = field_config['required'] == true %>
3
3
  <% settings = field_config['settings'] || {} %>
4
4
  <% current_value = page_field&.value || '' %>
5
+ <% input_name = lato_cms_field_input_name(field_id, input_name_prefix: local_assigns[:input_name_prefix]) %>
6
+ <% input_id = lato_cms_field_dom_id(field_id, dom_id_prefix: local_assigns[:dom_id_prefix]) %>
5
7
 
6
- <label class="form-label" for="fields_<%= field_id %>_value">
8
+ <label class="form-label" for="<%= input_id %>">
7
9
  <%= label %><%= ' *' if required %>
8
10
  </label>
9
11
  <input type="date"
10
12
  class="form-control"
11
- name="fields[<%= field_id %>][value]"
12
- id="fields_<%= field_id %>_value"
13
+ name="<%= input_name %>"
14
+ id="<%= input_id %>"
13
15
  value="<%= current_value %>"
14
16
  <%= 'required' if required %>
15
17
  <% if settings['min'] %>min="<%= settings['min'] %>"<% end %>
@@ -2,14 +2,16 @@
2
2
  <% required = field_config['required'] == true %>
3
3
  <% settings = field_config['settings'] || {} %>
4
4
  <% current_value = page_field&.value || '' %>
5
+ <% input_name = lato_cms_field_input_name(field_id, input_name_prefix: local_assigns[:input_name_prefix]) %>
6
+ <% input_id = lato_cms_field_dom_id(field_id, dom_id_prefix: local_assigns[:dom_id_prefix]) %>
5
7
 
6
- <label class="form-label" for="fields_<%= field_id %>_value">
8
+ <label class="form-label" for="<%= input_id %>">
7
9
  <%= label %><%= ' *' if required %>
8
10
  </label>
9
11
  <input type="datetime-local"
10
12
  class="form-control"
11
- name="fields[<%= field_id %>][value]"
12
- id="fields_<%= field_id %>_value"
13
+ name="<%= input_name %>"
14
+ id="<%= input_id %>"
13
15
  value="<%= current_value %>"
14
16
  <%= 'required' if required %>
15
17
  <% if settings['min'] %>min="<%= settings['min'] %>"<% end %>
@@ -1,29 +1,54 @@
1
1
  <% label = field_config['name'] || field_id.humanize %>
2
2
  <% required = field_config['required'] == true %>
3
3
  <% settings = field_config['settings'] || {} %>
4
- <% multiple = settings['multiple'] != false %>
4
+ <% multiple = settings['multiple'] == true %>
5
5
  <% accept = settings['accept'] %>
6
+ <% files_input_name = lato_cms_field_input_name(field_id, 'files', input_name_prefix: local_assigns[:input_name_prefix], multiple: true) %>
7
+ <% remove_input_name = lato_cms_field_input_name(field_id, 'remove_file_ids', input_name_prefix: local_assigns[:input_name_prefix], multiple: true) %>
8
+ <% input_id = lato_cms_field_dom_id(field_id, 'files', dom_id_prefix: local_assigns[:dom_id_prefix]) %>
6
9
 
7
10
  <label class="form-label">
8
11
  <%= label %><%= ' *' if required %>
9
12
  </label>
10
13
 
11
- <% if page_field&.files&.attached? %>
12
- <div class="mb-2">
14
+ <div data-controller="lato-cms-file-field"
15
+ data-field-id="<%= field_id %>"
16
+ data-lato-cms-file-field-multiple-value="<%= multiple %>"
17
+ data-new-badge-label="<%= t('lato_cms.field_file_new_badge') %>"
18
+ data-remove-label="<%= t('lato_cms.field_file_remove') %>"
19
+ data-remove-pending-label="<%= t('lato_cms.field_file_remove_pending') %>"
20
+ data-undo-label="<%= t('lato_cms.field_file_remove_undo') %>">
21
+ <div class="mb-2" data-lato-cms-file-field-target="list">
22
+ <% if page_field&.files&.attached? %>
13
23
  <% page_field.files.each do |file| %>
14
- <div class="form-check mb-1 p-2 ps-4 bg-light rounded">
15
- <input type="checkbox" class="form-check-input" name="fields[<%= field_id %>][remove_file_ids][]" value="<%= file.id %>" id="remove_file_<%= file.id %>">
16
- <label class="form-check-label small" for="remove_file_<%= file.id %>">
17
- <i class="bi bi-paperclip me-1"></i><%= file.blob.filename %> (<%= number_to_human_size(file.blob.byte_size) %>) — <span class="text-danger"><%= t('lato_cms.field_file_remove') %></span>
18
- </label>
24
+ <div class="lato-cms-file-field__item" data-lato-cms-file-field-target="item" data-attachment-id="<%= file.id %>">
25
+ <div class="lato-cms-file-field__info">
26
+ <i class="bi bi-paperclip"></i>
27
+ <span><%= file.blob.filename %></span>
28
+ <span class="text-muted small">(<%= number_to_human_size(file.blob.byte_size) %>)</span>
29
+ </div>
30
+ <button type="button" class="btn btn-sm btn-outline-danger" data-action="lato-cms-file-field#remove">
31
+ <i class="bi bi-trash me-1"></i><%= t('lato_cms.field_file_remove') %>
32
+ </button>
33
+ <div class="lato-cms-file-field__removed d-none" data-lato-cms-file-field-target="removedNotice">
34
+ <span class="text-danger small">
35
+ <i class="bi bi-trash me-1"></i><%= t('lato_cms.field_file_remove_pending') %>
36
+ </span>
37
+ <button type="button" class="btn btn-link btn-sm p-0" data-action="lato-cms-file-field#undo">
38
+ <%= t('lato_cms.field_file_remove_undo') %>
39
+ </button>
40
+ </div>
19
41
  </div>
20
42
  <% end %>
43
+ <% end %>
21
44
  </div>
22
- <% end %>
23
45
 
24
- <input type="file"
25
- class="form-control"
26
- name="fields[<%= field_id %>][files][]"
27
- id="fields_<%= field_id %>_files"
28
- <%= 'multiple' if multiple %>
29
- <% if accept %>accept="<%= accept %>"<% end %>>
46
+ <input type="file"
47
+ class="form-control"
48
+ name="<%= files_input_name %>"
49
+ id="<%= input_id %>"
50
+ data-lato-cms-file-field-target="input"
51
+ data-action="change->lato-cms-file-field#addFiles"
52
+ <%= 'multiple' if multiple %>
53
+ <% if accept %>accept="<%= accept %>"<% end %>>
54
+ </div>
@@ -2,6 +2,7 @@
2
2
  <% required = field_config['required'] == true %>
3
3
  <% settings = field_config['settings'] || {} %>
4
4
  <% accept = settings['accept'] || 'image/*' %>
5
+ <% files_input_name = lato_cms_field_input_name(field_id, 'files', input_name_prefix: local_assigns[:input_name_prefix], multiple: true) %>
5
6
 
6
7
  <%# Build ordered file list from value JSON or default order %>
7
8
  <% ordered_files = begin
@@ -19,7 +20,7 @@ end %>
19
20
  <%= label %><%= ' *' if required %>
20
21
  </label>
21
22
 
22
- <div data-controller="lato-cms-gallery-field" data-lato-cms-gallery-field-field-id-value="<%= field_id %>">
23
+ <div data-controller="lato-cms-gallery-field" data-lato-cms-gallery-field-field-id-value="<%= field_id %>" data-lato-cms-gallery-field-input-name-prefix-value="<%= local_assigns[:input_name_prefix].presence || "fields[#{field_id}]" %>">
23
24
  <div class="lato-cms-gallery-field__grid mb-2"
24
25
  data-lato-cms-gallery-field-target="grid"
25
26
  data-action="dragover->lato-cms-gallery-field#onGridDragOver drop->lato-cms-gallery-field#onGridDrop">
@@ -45,7 +46,7 @@ end %>
45
46
  <i class="bi bi-plus-lg me-1"></i><%= t('lato_cms.field_gallery_add_images') %>
46
47
  <input type="file"
47
48
  class="d-none"
48
- name="fields[<%= field_id %>][files][]"
49
+ name="<%= files_input_name %>"
49
50
  accept="<%= accept %>"
50
51
  multiple
51
52
  data-lato-cms-gallery-field-target="fileInput"