lato_cms 3.0.0 → 3.0.1

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 (29) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/javascripts/lato_cms/controllers/lato_cms_gallery_field_controller.js +3 -3
  3. data/app/assets/javascripts/lato_cms/controllers/lato_cms_repeater_controller.js +33 -0
  4. data/app/controllers/lato_cms/pages_controller.rb +92 -48
  5. data/app/helpers/lato_cms/pages_helper.rb +15 -2
  6. data/app/models/lato_cms/page.rb +35 -4
  7. data/app/models/lato_cms/page_field.rb +22 -2
  8. data/app/models/lato_cms/template_manager.rb +2 -0
  9. data/app/views/lato_cms/pages/_component_accordion.html.erb +13 -9
  10. data/app/views/lato_cms/pages/_repeater.html.erb +22 -0
  11. data/app/views/lato_cms/pages/_repeater_item.html.erb +28 -0
  12. data/app/views/lato_cms/pages/fields/_boolean.html.erb +6 -4
  13. data/app/views/lato_cms/pages/fields/_color.html.erb +5 -3
  14. data/app/views/lato_cms/pages/fields/_date.html.erb +5 -3
  15. data/app/views/lato_cms/pages/fields/_datetime.html.erb +5 -3
  16. data/app/views/lato_cms/pages/fields/_file.html.erb +6 -3
  17. data/app/views/lato_cms/pages/fields/_gallery.html.erb +3 -2
  18. data/app/views/lato_cms/pages/fields/_image.html.erb +6 -3
  19. data/app/views/lato_cms/pages/fields/_json.html.erb +5 -3
  20. data/app/views/lato_cms/pages/fields/_multiselect.html.erb +5 -3
  21. data/app/views/lato_cms/pages/fields/_number.html.erb +5 -3
  22. data/app/views/lato_cms/pages/fields/_select.html.erb +5 -3
  23. data/app/views/lato_cms/pages/fields/_string.html.erb +5 -3
  24. data/app/views/lato_cms/pages/fields/_text.html.erb +5 -3
  25. data/app/views/lato_cms/pages/fields/_textarea.html.erb +5 -3
  26. data/config/locales/en.yml +2 -0
  27. data/config/locales/it.yml +2 -0
  28. data/lib/lato_cms/version.rb +1 -1
  29. metadata +4 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 508aae768bf2679d04559b46cf39e353f3279e959e527045586f042c80dfb0ed
4
- data.tar.gz: 8e1e8b0e8a45f226ba09d5c32bc183ace111b5e2029c7c62ea5562fd4ac43688
3
+ metadata.gz: ce57b3c62b4c5ad59c91a5ec2f01e0eb958bfd5244097e2df0b7210f625e1a0e
4
+ data.tar.gz: b6304761ec5116b821b75660140fdab9002a6a8e13b68923e0df77ace2fb53d0
5
5
  SHA512:
6
- metadata.gz: e7d17de34c1292366c754ff952e4197f40a4107aa7137cae4ae6f587a3411c78be834bba87133102872955539036e2cefd90261728fcbc874755395abe9d6a27
7
- data.tar.gz: 8cc1b2d80143483f2ef2f2204b18d63f0f96434a4fc24a0f762aa1e8adc7693137346273aac1b27e6c3306f790a51da90803c437a55cc6c4961457c20bf1ab36
6
+ metadata.gz: 997c72993680123c414c978e5291ffb511f7c7b19f728c23aa8535e84c226002ee7a2a950e07513f48b7840a79db55ee0c7ef9f0319e1dd2f62f44a70c957e51
7
+ data.tar.gz: a11ae95caf80c3d4ceacb798f5515da2ca6f0448d1f1c859d8ed5819112adde51b5035e829f03d6791c7308b229a3880d83d09bc30fc74df216035f58f102422
@@ -2,7 +2,7 @@ import { Controller } from "@hotwired/stimulus"
2
2
 
3
3
  export default class extends Controller {
4
4
  static targets = ["grid", "fileInput", "emptyMsg"]
5
- static values = { fieldId: String }
5
+ static values = { fieldId: String, inputNamePrefix: String }
6
6
 
7
7
  dragging = null
8
8
  newFileMap = new Map()
@@ -42,7 +42,7 @@ export default class extends Controller {
42
42
  if (attachmentId) {
43
43
  const input = document.createElement('input')
44
44
  input.type = 'hidden'
45
- input.name = `fields[${this.fieldIdValue}][remove_file_ids][]`
45
+ input.name = `${this.inputNamePrefixValue}[remove_file_ids][]`
46
46
  input.value = attachmentId
47
47
  this.element.appendChild(input)
48
48
  } else if (fileUid) {
@@ -96,7 +96,7 @@ export default class extends Controller {
96
96
  if (!id) return
97
97
  const input = document.createElement('input')
98
98
  input.type = 'hidden'
99
- input.name = `fields[${this.fieldIdValue}][order][]`
99
+ input.name = `${this.inputNamePrefixValue}[order][]`
100
100
  input.value = id
101
101
  input.setAttribute('data-gallery-order-input', '')
102
102
  this.element.appendChild(input)
@@ -0,0 +1,33 @@
1
+ import { Controller } from '@hotwired/stimulus'
2
+
3
+ export default class extends Controller {
4
+ static targets = ['items', 'template', 'item', 'position', 'orderInput']
5
+
6
+ add () {
7
+ const itemId = window.crypto.randomUUID()
8
+ const html = this.templateTarget.innerHTML
9
+ .replaceAll('NEW_RECORD', itemId)
10
+ .replaceAll('NEW_INDEX', this.itemTargets.length.toString())
11
+
12
+ this.itemsTarget.insertAdjacentHTML('beforeend', html)
13
+ this.refresh()
14
+ }
15
+
16
+ remove (event) {
17
+ const item = event.currentTarget.closest('[data-lato-cms-repeater-target="item"]')
18
+ if (!item) return
19
+
20
+ item.remove()
21
+ this.refresh()
22
+ }
23
+
24
+ refresh () {
25
+ this.itemTargets.forEach((item, index) => {
26
+ const position = item.querySelector('[data-lato-cms-repeater-target="position"]')
27
+ if (position) position.textContent = index + 1
28
+
29
+ const orderInput = item.querySelector('[data-lato-cms-repeater-target="orderInput"]')
30
+ if (orderInput) orderInput.value = item.dataset.repeaterItemId
31
+ })
32
+ }
33
+ }
@@ -77,55 +77,13 @@ 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
 
@@ -183,6 +141,92 @@ 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', 'image'
197
+ field.save if field.new_record?
198
+ attach_field_files(field, field_data)
199
+ remove_field_files(field, field_data)
200
+ when 'gallery'
201
+ field.save if field.new_record?
202
+ attach_field_files(field, field_data)
203
+ remove_field_files(field, field_data)
204
+ order = Array(field_data[:order]).reject(&:blank?).map(&:to_s)
205
+ all_ids = field.files.reload.map { |f| f.id.to_s }
206
+ sorted = order.select { |id| all_ids.include?(id) }
207
+ field.value = (sorted + (all_ids - sorted)).to_json
208
+ when 'multiselect'
209
+ field.value = Array(field_data[:value]).reject(&:blank?).to_json
210
+ else
211
+ raw_value = field_data.is_a?(ActionController::Parameters) ? field_data[:value] : field_data
212
+ field.value = raw_value.to_s.presence
213
+ end
214
+ end
215
+
216
+ def attach_field_files(field, field_data)
217
+ return unless field_data[:files].present?
218
+
219
+ Array(field_data[:files]).compact.each { |file| field.files.attach(file) }
220
+ end
221
+
222
+ def remove_field_files(field, field_data)
223
+ return unless field_data[:remove_file_ids].present?
224
+
225
+ Array(field_data[:remove_file_ids]).reject(&:blank?).each do |file_id_to_remove|
226
+ field.files.find { |file| file.id == file_id_to_remove.to_i }&.purge
227
+ end
228
+ end
229
+
186
230
  def create_params
187
231
  params.require(:page).permit(:title, :locale)
188
232
  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,7 @@ module LatoCms
58
78
  def as_json(_options = {})
59
79
  result = {
60
80
  id: id,
61
- field_id: field_id,
81
+ field_id: base_field_id,
62
82
  field_type: field_type,
63
83
  field_name: field_name,
64
84
  required: field_required?,
@@ -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 %>
@@ -3,6 +3,9 @@
3
3
  <% settings = field_config['settings'] || {} %>
4
4
  <% multiple = settings['multiple'] != false %>
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 %>
@@ -12,7 +15,7 @@
12
15
  <div class="mb-2">
13
16
  <% page_field.files.each do |file| %>
14
17
  <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 %>">
18
+ <input type="checkbox" class="form-check-input" name="<%= remove_input_name %>" value="<%= file.id %>" id="remove_file_<%= file.id %>">
16
19
  <label class="form-check-label small" for="remove_file_<%= file.id %>">
17
20
  <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
21
  </label>
@@ -23,7 +26,7 @@
23
26
 
24
27
  <input type="file"
25
28
  class="form-control"
26
- name="fields[<%= field_id %>][files][]"
27
- id="fields_<%= field_id %>_files"
29
+ name="<%= files_input_name %>"
30
+ id="<%= input_id %>"
28
31
  <%= 'multiple' if multiple %>
29
32
  <% if accept %>accept="<%= accept %>"<% end %>>
@@ -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"
@@ -3,6 +3,9 @@
3
3
  <% settings = field_config['settings'] || {} %>
4
4
  <% accept = settings['accept'] || 'image/*' %>
5
5
  <% current_file = page_field&.files&.attached? ? page_field.files.first : nil %>
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 %>
@@ -14,7 +17,7 @@
14
17
  <div class="position-relative d-inline-block">
15
18
  <img src="<%= lato_cms_attachment_path(current_file) %>" class="img-thumbnail lato-cms-image-field__thumb" data-lato-cms-image-field-target="currentImage" alt="<%= current_file.filename %>">
16
19
  <label class="lato-cms-image-field__remove-btn" title="<%= t('lato_cms.field_image_remove') %>">
17
- <input type="checkbox" class="d-none" name="fields[<%= field_id %>][remove_file_ids][]" value="<%= current_file.id %>" data-lato-cms-image-field-target="removeCheck" data-action="change->lato-cms-image-field#toggleRemove">
20
+ <input type="checkbox" class="d-none" name="<%= remove_input_name %>" value="<%= current_file.id %>" data-lato-cms-image-field-target="removeCheck" data-action="change->lato-cms-image-field#toggleRemove">
18
21
  <i class="bi bi-x-lg"></i>
19
22
  </label>
20
23
  </div>
@@ -30,8 +33,8 @@
30
33
 
31
34
  <input type="file"
32
35
  class="form-control"
33
- name="fields[<%= field_id %>][files][]"
34
- id="fields_<%= field_id %>_files"
36
+ name="<%= files_input_name %>"
37
+ id="<%= input_id %>"
35
38
  accept="<%= accept %>"
36
39
  data-lato-cms-image-field-target="input"
37
40
  data-action="change->lato-cms-image-field#preview"
@@ -5,15 +5,17 @@
5
5
  <% if current_value.present? %>
6
6
  <% begin; current_value = JSON.pretty_generate(JSON.parse(current_value)); rescue JSON::ParserError; end %>
7
7
  <% end %>
8
+ <% input_name = lato_cms_field_input_name(field_id, input_name_prefix: local_assigns[:input_name_prefix]) %>
9
+ <% input_id = lato_cms_field_dom_id(field_id, dom_id_prefix: local_assigns[:dom_id_prefix]) %>
8
10
 
9
- <label class="form-label" for="fields_<%= field_id %>_value">
11
+ <label class="form-label" for="<%= input_id %>">
10
12
  <%= label %><%= ' *' if required %>
11
13
  </label>
12
14
  <div data-controller="lato-cms-json-field">
13
15
  <textarea
14
16
  class="form-control font-monospace"
15
- name="fields[<%= field_id %>][value]"
16
- id="fields_<%= field_id %>_value"
17
+ name="<%= input_name %>"
18
+ id="<%= input_id %>"
17
19
  rows="<%= settings['rows'] || 6 %>"
18
20
  data-lato-cms-json-field-target="input"
19
21
  data-action="input->lato-cms-json-field#validate"
@@ -3,14 +3,16 @@
3
3
  <% settings = field_config['settings'] || {} %>
4
4
  <% options = settings['options'] || [] %>
5
5
  <% current_values = begin; page_field&.value.present? ? JSON.parse(page_field.value) : []; rescue; []; end %>
6
+ <% input_name = lato_cms_field_input_name(field_id, 'value', input_name_prefix: local_assigns[:input_name_prefix], multiple: true) %>
7
+ <% input_id = lato_cms_field_dom_id(field_id, dom_id_prefix: local_assigns[:dom_id_prefix]) %>
6
8
 
7
- <label class="form-label" for="fields_<%= field_id %>_value">
9
+ <label class="form-label" for="<%= input_id %>">
8
10
  <%= label %><%= ' *' if required %>
9
11
  </label>
10
12
  <select
11
13
  class="form-select"
12
- name="fields[<%= field_id %>][value][]"
13
- id="fields_<%= field_id %>_value"
14
+ name="<%= input_name %>"
15
+ id="<%= input_id %>"
14
16
  multiple
15
17
  size="<%= [options.length, 6].min %>"
16
18
  <%= 'required' if required %>>
@@ -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="number"
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 %>
@@ -3,14 +3,16 @@
3
3
  <% settings = field_config['settings'] || {} %>
4
4
  <% options = settings['options'] || [] %>
5
5
  <% current_value = page_field&.value || '' %>
6
+ <% input_name = lato_cms_field_input_name(field_id, input_name_prefix: local_assigns[:input_name_prefix]) %>
7
+ <% input_id = lato_cms_field_dom_id(field_id, dom_id_prefix: local_assigns[:dom_id_prefix]) %>
6
8
 
7
- <label class="form-label" for="fields_<%= field_id %>_value">
9
+ <label class="form-label" for="<%= input_id %>">
8
10
  <%= label %><%= ' *' if required %>
9
11
  </label>
10
12
  <select
11
13
  class="form-select"
12
- name="fields[<%= field_id %>][value]"
13
- id="fields_<%= field_id %>_value"
14
+ name="<%= input_name %>"
15
+ id="<%= input_id %>"
14
16
  <%= 'required' if required %>>
15
17
  <option value=""><%= t('lato_cms.field_select_placeholder') %></option>
16
18
  <% options.each do |option| %>
@@ -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="text"
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['placeholder'] %>placeholder="<%= settings['placeholder'] %>"<% end %>
@@ -1,8 +1,10 @@
1
1
  <% label = field_config['name'] || field_id.humanize %>
2
2
  <% required = field_config['required'] == true %>
3
3
  <% current_value = page_field&.value || '' %>
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
 
@@ -73,8 +75,8 @@
73
75
 
74
76
  <%# ── Hidden input submitted with the form ───────────────────────────── %>
75
77
  <input type="hidden"
76
- name="fields[<%= field_id %>][value]"
77
- id="fields_<%= field_id %>_value"
78
+ name="<%= input_name %>"
79
+ id="<%= input_id %>"
78
80
  data-lato-cms-text-field-target="input"
79
81
  value="<%= CGI.escapeHTML(current_value) %>">
80
82
  </div>
@@ -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
  <textarea
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
  rows="<%= settings['rows'] || 4 %>"
14
16
  <%= 'required' if required %>
15
17
  <% if settings['placeholder'] %>placeholder="<%= settings['placeholder'] %>"<% end %>
@@ -48,6 +48,8 @@ en:
48
48
  component_required_cannot_disable: Required component cannot be disabled
49
49
  component_disabled_cannot_save: Disabled component cannot be saved
50
50
  fields_save_component: Save
51
+ repeater_add_item: Add item
52
+ repeater_remove_item: Remove
51
53
  fields_component_missing_alert_html: "Component <strong>%{component_id}</strong> not found. The component configuration may have been removed."
52
54
  fields_previously_saved_data: "Previously saved data:"
53
55
  field_file_remove: Remove
@@ -48,6 +48,8 @@ it:
48
48
  component_required_cannot_disable: Una componente obbligatoria non puo essere disattivata
49
49
  component_disabled_cannot_save: Una componente disattivata non puo essere salvata
50
50
  fields_save_component: Salva
51
+ repeater_add_item: Aggiungi elemento
52
+ repeater_remove_item: Rimuovi
51
53
  fields_component_missing_alert_html: "Componente <strong>%{component_id}</strong> non trovato. La configurazione del componente potrebbe essere stata rimossa."
52
54
  fields_previously_saved_data: "Dati salvati in precedenza:"
53
55
  field_file_remove: Rimuovi
@@ -1,3 +1,3 @@
1
1
  module LatoCms
2
- VERSION = "3.0.0"
2
+ VERSION = "3.0.1"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lato_cms
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.0.0
4
+ version: 3.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Gregorio Galante
@@ -86,6 +86,7 @@ files:
86
86
  - app/assets/javascripts/lato_cms/controllers/lato_cms_image_field_controller.js
87
87
  - app/assets/javascripts/lato_cms/controllers/lato_cms_json_field_controller.js
88
88
  - app/assets/javascripts/lato_cms/controllers/lato_cms_page_preview_controller.js
89
+ - app/assets/javascripts/lato_cms/controllers/lato_cms_repeater_controller.js
89
90
  - app/assets/javascripts/lato_cms/controllers/lato_cms_text_field_controller.js
90
91
  - app/assets/stylesheets/lato_cms/application.scss
91
92
  - app/controllers/lato_cms/api/pages_controller.rb
@@ -102,6 +103,8 @@ files:
102
103
  - app/views/lato_cms/pages/_fields_editor.html.erb
103
104
  - app/views/lato_cms/pages/_form_create.html.erb
104
105
  - app/views/lato_cms/pages/_form_update.html.erb
106
+ - app/views/lato_cms/pages/_repeater.html.erb
107
+ - app/views/lato_cms/pages/_repeater_item.html.erb
105
108
  - app/views/lato_cms/pages/create.html.erb
106
109
  - app/views/lato_cms/pages/fields/_boolean.html.erb
107
110
  - app/views/lato_cms/pages/fields/_color.html.erb