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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 508aae768bf2679d04559b46cf39e353f3279e959e527045586f042c80dfb0ed
4
- data.tar.gz: 8e1e8b0e8a45f226ba09d5c32bc183ace111b5e2029c7c62ea5562fd4ac43688
3
+ metadata.gz: 527556e5bf27156bab801e7f0c23c53f9ac9e0565d051da5a0fa1c9500954357
4
+ data.tar.gz: dfcbcca5925d763f06d2043e497e128d84d3fcd200059990f9cb8fc1718699cc
5
5
  SHA512:
6
- metadata.gz: e7d17de34c1292366c754ff952e4197f40a4107aa7137cae4ae6f587a3411c78be834bba87133102872955539036e2cefd90261728fcbc874755395abe9d6a27
7
- data.tar.gz: 8cc1b2d80143483f2ef2f2204b18d63f0f96434a4fc24a0f762aa1e8adc7693137346273aac1b27e6c3306f790a51da90803c437a55cc6c4961457c20bf1ab36
6
+ metadata.gz: 37384b79131f97266d82434b290a2bdd3ce4b844d1bf9c63bb6363f4877cfa8379ebe1d1fb4ffe2f6bcbc2a00cc69b8e13124b6e9fa9293025ab98606a4690e4
7
+ data.tar.gz: e9ce262f6bbfc70a07554edba5e18ffe9f1c028250da9f6ac5a5b7962977b2f408222e8fddd2eb949f4d48fb1bf2d319c420b1f76ccf3d0675d6a1e39d80c5bb
@@ -32,6 +32,10 @@ export default class extends Controller {
32
32
 
33
33
  if (response.ok) {
34
34
  this.showStatus('Fields saved successfully', 'success')
35
+ form.dispatchEvent(new CustomEvent('lato-cms:fields-save-success', {
36
+ bubbles: true,
37
+ detail: { success: true, data }
38
+ }))
35
39
  this.refreshPreview()
36
40
  } else {
37
41
  const errorMsg = data.errors?.map(e => `${e.field_id}: ${e.errors.join(', ')}`).join('; ') || 'Failed to save fields'
@@ -0,0 +1,203 @@
1
+ import { Controller } from "@hotwired/stimulus"
2
+
3
+ export default class extends Controller {
4
+ static targets = ["input", "item", "list", "removedNotice"]
5
+ static values = { multiple: Boolean }
6
+
7
+ newFileMap = new Map()
8
+ nextUid = 0
9
+
10
+ connect() {
11
+ this.afterSave = this.afterSave.bind(this)
12
+ this.element.closest('form')?.addEventListener('lato-cms:fields-save-success', this.afterSave)
13
+ }
14
+
15
+ disconnect() {
16
+ this.element.closest('form')?.removeEventListener('lato-cms:fields-save-success', this.afterSave)
17
+ }
18
+
19
+ addFiles() {
20
+ if (!this.multipleValue) this.prepareSingleReplacement()
21
+
22
+ Array.from(this.inputTarget.files).forEach(file => {
23
+ const uid = `new_${this.nextUid++}`
24
+ this.newFileMap.set(uid, file)
25
+ this.listTarget.appendChild(this.buildNewItem(file, uid))
26
+ })
27
+
28
+ this.inputTarget.value = ''
29
+ this.syncFileInput()
30
+ }
31
+
32
+ remove(event) {
33
+ const item = event.currentTarget.closest('[data-lato-cms-file-field-target="item"]')
34
+ if (!item) return
35
+
36
+ if (item.dataset.fileUid) {
37
+ this.newFileMap.delete(item.dataset.fileUid)
38
+ item.remove()
39
+ this.syncFileInput()
40
+ if (!this.multipleValue) this.restoreReplacedItems()
41
+ return
42
+ }
43
+
44
+ item.classList.add('lato-cms-file-field__item--removing')
45
+ item.querySelector('[data-lato-cms-file-field-target="removedNotice"]')?.classList.remove('d-none')
46
+ this.ensureRemoveInput(item)
47
+ }
48
+
49
+ undo(event) {
50
+ const item = event.currentTarget.closest('[data-lato-cms-file-field-target="item"]')
51
+ if (!item) return
52
+
53
+ item.classList.remove('lato-cms-file-field__item--removing')
54
+ item.querySelector('[data-lato-cms-file-field-target="removedNotice"]')?.classList.add('d-none')
55
+ item.querySelector('[data-remove-file-input]')?.remove()
56
+ }
57
+
58
+ ensureRemoveInput(item) {
59
+ if (item.querySelector('[data-remove-file-input]')) return
60
+
61
+ const input = document.createElement('input')
62
+ input.type = 'hidden'
63
+ input.name = this.inputTarget.name.replace(/\[files\]\[\]$/, '[remove_file_ids][]')
64
+ input.value = item.dataset.attachmentId
65
+ input.setAttribute('data-remove-file-input', '')
66
+ item.appendChild(input)
67
+ }
68
+
69
+ syncFileInput() {
70
+ const dt = new DataTransfer()
71
+ this.newFileMap.forEach(file => dt.items.add(file))
72
+ this.inputTarget.files = dt.files
73
+ }
74
+
75
+ prepareSingleReplacement() {
76
+ this.newFileMap.clear()
77
+ this.listTarget.querySelectorAll('[data-file-uid]').forEach(item => item.remove())
78
+ this.itemTargets
79
+ .filter(item => item.dataset.attachmentId && !item.querySelector('[data-remove-file-input]'))
80
+ .forEach(item => {
81
+ item.classList.add('d-none')
82
+ item.dataset.replacedByNewFile = 'true'
83
+ })
84
+ }
85
+
86
+ restoreReplacedItems() {
87
+ if (this.newFileMap.size > 0) return
88
+
89
+ this.itemTargets
90
+ .filter(item => item.dataset.replacedByNewFile === 'true')
91
+ .forEach(item => {
92
+ item.classList.remove('d-none')
93
+ delete item.dataset.replacedByNewFile
94
+ })
95
+ }
96
+
97
+ buildNewItem(file, uid) {
98
+ const item = document.createElement('div')
99
+ item.className = 'lato-cms-file-field__item lato-cms-file-field__item--new'
100
+ item.dataset.latoCmsFileFieldTarget = 'item'
101
+ item.dataset.fileUid = uid
102
+ item.innerHTML = `
103
+ <div class="lato-cms-file-field__info">
104
+ <i class="bi bi-paperclip"></i>
105
+ <span>${this.escapeHtml(file.name)}</span>
106
+ <span class="text-muted small">(${this.humanSize(file.size)})</span>
107
+ <span class="badge bg-success">${this.newBadgeLabel}</span>
108
+ </div>
109
+ <button type="button" class="btn btn-sm btn-outline-danger" data-action="lato-cms-file-field#remove">
110
+ <i class="bi bi-trash me-1"></i>${this.removeLabel}
111
+ </button>
112
+ `
113
+ return item
114
+ }
115
+
116
+ afterSave(event) {
117
+ if (!event.detail.success) return
118
+
119
+ this.newFileMap.clear()
120
+ this.inputTarget.value = ''
121
+
122
+ const field = this.findSavedField(event.detail.data?.fields || [])
123
+ if (!field) {
124
+ this.itemTargets
125
+ .filter(item => item.querySelector('[data-remove-file-input]'))
126
+ .forEach(item => item.remove())
127
+ return
128
+ }
129
+
130
+ this.listTarget.innerHTML = ''
131
+ field.attachments.forEach(attachment => {
132
+ this.listTarget.appendChild(this.buildExistingItem(attachment))
133
+ })
134
+ }
135
+
136
+ findSavedField(fields) {
137
+ return fields.find(field => {
138
+ return field.persisted_field_id === this.fieldId || field.field_id === this.fieldId
139
+ })
140
+ }
141
+
142
+ buildExistingItem(attachment) {
143
+ const item = document.createElement('div')
144
+ item.className = 'lato-cms-file-field__item'
145
+ item.dataset.latoCmsFileFieldTarget = 'item'
146
+ item.dataset.attachmentId = attachment.id
147
+ item.innerHTML = `
148
+ <div class="lato-cms-file-field__info">
149
+ <i class="bi bi-paperclip"></i>
150
+ <span>${this.escapeHtml(attachment.filename)}</span>
151
+ <span class="text-muted small">(${this.humanSize(attachment.byte_size || 0)})</span>
152
+ </div>
153
+ <button type="button" class="btn btn-sm btn-outline-danger" data-action="lato-cms-file-field#remove">
154
+ <i class="bi bi-trash me-1"></i>${this.removeLabel}
155
+ </button>
156
+ <div class="lato-cms-file-field__removed d-none" data-lato-cms-file-field-target="removedNotice">
157
+ <span class="text-danger small">
158
+ <i class="bi bi-trash me-1"></i>${this.removePendingLabel}
159
+ </span>
160
+ <button type="button" class="btn btn-link btn-sm p-0" data-action="lato-cms-file-field#undo">
161
+ ${this.undoLabel}
162
+ </button>
163
+ </div>
164
+ `
165
+ return item
166
+ }
167
+
168
+ humanSize(size) {
169
+ if (size < 1024) return `${size} B`
170
+ if (size < 1024 * 1024) return `${(size / 1024).toFixed(1)} KB`
171
+ return `${(size / 1024 / 1024).toFixed(1)} MB`
172
+ }
173
+
174
+ escapeHtml(value) {
175
+ return value.replace(/[&<>"']/g, char => ({
176
+ '&': '&amp;',
177
+ '<': '&lt;',
178
+ '>': '&gt;',
179
+ '"': '&quot;',
180
+ "'": '&#39;'
181
+ }[char]))
182
+ }
183
+
184
+ get newBadgeLabel() {
185
+ return this.element.dataset.newBadgeLabel || 'New'
186
+ }
187
+
188
+ get removeLabel() {
189
+ return this.element.dataset.removeLabel || 'Remove'
190
+ }
191
+
192
+ get removePendingLabel() {
193
+ return this.element.dataset.removePendingLabel || 'File will be removed on save'
194
+ }
195
+
196
+ get undoLabel() {
197
+ return this.element.dataset.undoLabel || 'Undo'
198
+ }
199
+
200
+ get fieldId() {
201
+ return this.element.dataset.fieldId
202
+ }
203
+ }
@@ -2,12 +2,21 @@ 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()
9
9
  nextUid = 0
10
10
 
11
+ connect() {
12
+ this.afterSave = this.afterSave.bind(this)
13
+ this.element.closest('form')?.addEventListener('lato-cms:fields-save-success', this.afterSave)
14
+ }
15
+
16
+ disconnect() {
17
+ this.element.closest('form')?.removeEventListener('lato-cms:fields-save-success', this.afterSave)
18
+ }
19
+
11
20
  // ─── File selection ─────────────────────────────────────────────────────────
12
21
 
13
22
  addFiles() {
@@ -42,7 +51,7 @@ export default class extends Controller {
42
51
  if (attachmentId) {
43
52
  const input = document.createElement('input')
44
53
  input.type = 'hidden'
45
- input.name = `fields[${this.fieldIdValue}][remove_file_ids][]`
54
+ input.name = `${this.inputNamePrefixValue}[remove_file_ids][]`
46
55
  input.value = attachmentId
47
56
  this.element.appendChild(input)
48
57
  } else if (fileUid) {
@@ -96,7 +105,7 @@ export default class extends Controller {
96
105
  if (!id) return
97
106
  const input = document.createElement('input')
98
107
  input.type = 'hidden'
99
- input.name = `fields[${this.fieldIdValue}][order][]`
108
+ input.name = `${this.inputNamePrefixValue}[order][]`
100
109
  input.value = id
101
110
  input.setAttribute('data-gallery-order-input', '')
102
111
  this.element.appendChild(input)
@@ -124,7 +133,7 @@ export default class extends Controller {
124
133
  div.dataset.fileUid = fileUid || ''
125
134
  div.draggable = true
126
135
  div.className = 'lato-cms-gallery-field__item'
127
- div.setAttribute('data-action', 'dragstart->gallery-field#onDragStart dragend->gallery-field#onDragEnd')
136
+ div.setAttribute('data-action', 'dragstart->lato-cms-gallery-field#onDragStart dragend->lato-cms-gallery-field#onDragEnd')
128
137
  div.innerHTML = `
129
138
  <img src="${src}" class="lato-cms-gallery-field__thumb" alt="">
130
139
  <button type="button" class="lato-cms-gallery-field__remove" data-action="click->lato-cms-gallery-field#remove">
@@ -133,4 +142,27 @@ export default class extends Controller {
133
142
  `
134
143
  return div
135
144
  }
145
+
146
+ afterSave(event) {
147
+ if (!event.detail.success) return
148
+
149
+ const field = this.findSavedField(event.detail.data?.fields || [])
150
+ if (!field) return
151
+
152
+ this.newFileMap.clear()
153
+ this.fileInputTarget.value = ''
154
+ this.element.querySelectorAll('[name$="[remove_file_ids][]"]').forEach(input => input.remove())
155
+ this.gridTarget.innerHTML = ''
156
+ field.attachments.forEach(attachment => {
157
+ this.gridTarget.appendChild(this.buildItem(attachment.url, attachment.id, null))
158
+ })
159
+ this.updateEmpty()
160
+ this.updateOrderInputs()
161
+ }
162
+
163
+ findSavedField(fields) {
164
+ return fields.find(field => {
165
+ return field.persisted_field_id === this.fieldIdValue || field.field_id === this.fieldIdValue
166
+ })
167
+ }
136
168
  }
@@ -1,7 +1,16 @@
1
1
  import { Controller } from "@hotwired/stimulus"
2
2
 
3
3
  export default class extends Controller {
4
- static targets = ["input", "newPreview", "newPreviewContainer", "currentContainer", "currentImage", "removeCheck"]
4
+ static targets = ["input", "newPreview", "newPreviewContainer", "newPreviewName", "newPreviewSize", "currentContainer", "currentImage", "removedNotice"]
5
+
6
+ connect() {
7
+ this.afterSave = this.afterSave.bind(this)
8
+ this.element.closest('form')?.addEventListener('lato-cms:fields-save-success', this.afterSave)
9
+ }
10
+
11
+ disconnect() {
12
+ this.element.closest('form')?.removeEventListener('lato-cms:fields-save-success', this.afterSave)
13
+ }
5
14
 
6
15
  preview() {
7
16
  const file = this.inputTarget.files[0]
@@ -10,15 +19,135 @@ export default class extends Controller {
10
19
  const reader = new FileReader()
11
20
  reader.onload = ({ target }) => {
12
21
  this.newPreviewTarget.src = target.result
22
+ this.newPreviewTarget.alt = file.name
23
+ this.newPreviewNameTarget.textContent = file.name
24
+ this.newPreviewSizeTarget.textContent = `(${this.humanSize(file.size)})`
13
25
  this.newPreviewContainerTarget.classList.remove('d-none')
26
+ this.undo()
27
+ if (this.hasCurrentContainerTarget) this.currentContainerTarget.classList.add('d-none')
14
28
  }
15
29
  reader.readAsDataURL(file)
16
30
  }
17
31
 
18
- toggleRemove() {
19
- if (!this.hasCurrentImageTarget) return
20
- const removing = this.removeCheckTarget.checked
21
- this.currentImageTarget.style.opacity = removing ? '0.3' : ''
22
- this.currentImageTarget.style.outline = removing ? '2px solid var(--bs-danger)' : ''
32
+ removeNew() {
33
+ this.inputTarget.value = ''
34
+ this.newPreviewTarget.src = ''
35
+ this.newPreviewTarget.alt = ''
36
+ this.newPreviewNameTarget.textContent = ''
37
+ this.newPreviewSizeTarget.textContent = ''
38
+ this.newPreviewContainerTarget.classList.add('d-none')
39
+ if (this.hasCurrentContainerTarget) this.currentContainerTarget.classList.remove('d-none')
40
+ }
41
+
42
+ remove() {
43
+ if (!this.hasCurrentContainerTarget) return
44
+
45
+ this.currentContainerTarget.classList.add('lato-cms-image-field--removing')
46
+ this.removedNoticeTarget.classList.remove('d-none')
47
+ this.ensureRemoveInput()
48
+ }
49
+
50
+ undo() {
51
+ if (!this.hasCurrentContainerTarget) return
52
+
53
+ this.currentContainerTarget.classList.remove('lato-cms-image-field--removing')
54
+ this.removedNoticeTarget.classList.add('d-none')
55
+ this.removeInput?.remove()
56
+ this.removeInput = null
57
+ }
58
+
59
+ ensureRemoveInput() {
60
+ if (this.removeInput) return
61
+
62
+ const input = document.createElement('input')
63
+ input.type = 'hidden'
64
+ input.name = this.inputTarget.name.replace(/\[files\]\[\]$/, '[remove_file_ids][]')
65
+ input.value = this.currentContainerTarget.dataset.attachmentId
66
+ this.element.appendChild(input)
67
+ this.removeInput = input
68
+ }
69
+
70
+ afterSave(event) {
71
+ if (!event.detail.success) return
72
+
73
+ this.inputTarget.value = ''
74
+ this.removeInput = null
75
+ const field = this.findSavedField(event.detail.data?.fields || [])
76
+ if (!field) return
77
+
78
+ const attachment = field.attachments[0]
79
+ this.element.querySelector('[data-lato-cms-image-field-target="currentContainer"]')?.remove()
80
+ this.newPreviewContainerTarget.classList.add('d-none')
81
+
82
+ if (attachment) {
83
+ this.element.insertBefore(this.buildCurrentContainer(attachment), this.newPreviewContainerTarget)
84
+ }
85
+ }
86
+
87
+ findSavedField(fields) {
88
+ return fields.find(field => {
89
+ return field.persisted_field_id === this.fieldId || field.field_id === this.fieldId
90
+ })
91
+ }
92
+
93
+ buildCurrentContainer(attachment) {
94
+ const container = document.createElement('div')
95
+ container.className = 'lato-cms-file-field__item lato-cms-image-field__item'
96
+ container.dataset.latoCmsImageFieldTarget = 'currentContainer'
97
+ container.dataset.attachmentId = attachment.id
98
+ container.innerHTML = `
99
+ <div class="lato-cms-image-field__preview">
100
+ <img src="${attachment.url}" class="lato-cms-image-field__thumb" data-lato-cms-image-field-target="currentImage" alt="${this.escapeHtml(attachment.filename)}">
101
+ </div>
102
+ <div class="lato-cms-file-field__info">
103
+ <i class="bi bi-image"></i>
104
+ <span>${this.escapeHtml(attachment.filename)}</span>
105
+ <span class="text-muted small">(${this.humanSize(attachment.byte_size || 0)})</span>
106
+ </div>
107
+ <button type="button" class="btn btn-sm btn-outline-danger" data-action="lato-cms-image-field#remove">
108
+ <i class="bi bi-trash me-1"></i>${this.removeLabel}
109
+ </button>
110
+ <div class="lato-cms-image-field__removed d-none" data-lato-cms-image-field-target="removedNotice">
111
+ <span class="text-danger small">
112
+ <i class="bi bi-trash me-1"></i>${this.removePendingLabel}
113
+ </span>
114
+ <button type="button" class="btn btn-link btn-sm p-0" data-action="lato-cms-image-field#undo">
115
+ ${this.undoLabel}
116
+ </button>
117
+ </div>
118
+ `
119
+ return container
120
+ }
121
+
122
+ humanSize(size) {
123
+ if (size < 1024) return `${size} B`
124
+ if (size < 1024 * 1024) return `${(size / 1024).toFixed(1)} KB`
125
+ return `${(size / 1024 / 1024).toFixed(1)} MB`
126
+ }
127
+
128
+ escapeHtml(value) {
129
+ return value.replace(/[&<>"']/g, char => ({
130
+ '&': '&amp;',
131
+ '<': '&lt;',
132
+ '>': '&gt;',
133
+ '"': '&quot;',
134
+ "'": '&#39;'
135
+ }[char]))
136
+ }
137
+
138
+ get fieldId() {
139
+ return this.element.dataset.fieldId
140
+ }
141
+
142
+ get removeLabel() {
143
+ return this.element.dataset.removeLabel || 'Remove'
144
+ }
145
+
146
+ get removePendingLabel() {
147
+ return this.element.dataset.removePendingLabel || 'Image will be removed on save'
148
+ }
149
+
150
+ get undoLabel() {
151
+ return this.element.dataset.undoLabel || 'Undo'
23
152
  }
24
153
  }
@@ -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
+ }
@@ -142,13 +142,99 @@ body.lato-cms-advanced-editor--open {
142
142
  }
143
143
  }
144
144
 
145
+ // ── File field ─────────────────────────────────────────────────────────────────
146
+ .lato-cms-file-field__item {
147
+ align-items: center;
148
+ background: #fff;
149
+ border: 1px solid rgba(33, 37, 41, 0.12);
150
+ border-radius: 8px;
151
+ box-shadow: 0 1px 2px rgba(33, 37, 41, 0.04);
152
+ display: flex;
153
+ gap: 0.75rem;
154
+ justify-content: space-between;
155
+ margin-bottom: 0.6rem;
156
+ min-height: 56px;
157
+ padding: 0.65rem 0.75rem;
158
+ transition: background 0.15s, border-color 0.15s, box-shadow 0.15s;
159
+
160
+ &:hover {
161
+ border-color: rgba(13, 110, 253, 0.25);
162
+ box-shadow: 0 3px 10px rgba(33, 37, 41, 0.07);
163
+ }
164
+ }
165
+
166
+ .lato-cms-file-field__info {
167
+ align-items: center;
168
+ display: flex;
169
+ flex-wrap: wrap;
170
+ gap: 0.35rem;
171
+ min-width: 0;
172
+ flex: 1;
173
+
174
+ > span:first-of-type {
175
+ overflow-wrap: anywhere;
176
+ }
177
+
178
+ > .bi {
179
+ color: var(--bs-secondary);
180
+ font-size: 1rem;
181
+ }
182
+ }
183
+
184
+ .lato-cms-file-field__removed {
185
+ align-items: center;
186
+ display: flex;
187
+ gap: 0.75rem;
188
+ justify-content: space-between;
189
+ width: 100%;
190
+ }
191
+
192
+ .lato-cms-file-field__item--removing {
193
+ background: rgba(220, 53, 69, 0.06);
194
+ border-color: rgba(220, 53, 69, 0.35);
195
+ box-shadow: none;
196
+
197
+ .lato-cms-file-field__info,
198
+ .lato-cms-image-field__preview,
199
+ > .btn {
200
+ display: none;
201
+ }
202
+ }
203
+
204
+ .lato-cms-file-field__item--new {
205
+ background: rgba(25, 135, 84, 0.04);
206
+ border-color: rgba(25, 135, 84, 0.45);
207
+
208
+ .badge {
209
+ font-weight: 500;
210
+ }
211
+ }
212
+
145
213
  // ── Image field ────────────────────────────────────────────────────────────────
214
+ .lato-cms-image-field__item {
215
+ align-items: stretch;
216
+ }
217
+
218
+ .lato-cms-image-field__preview {
219
+ align-items: center;
220
+ align-self: center;
221
+ background: var(--bs-light);
222
+ border: 1px solid rgba(33, 37, 41, 0.08);
223
+ border-radius: 6px;
224
+ display: flex;
225
+ flex: 0 0 auto;
226
+ height: 64px;
227
+ justify-content: center;
228
+ overflow: hidden;
229
+ width: 64px;
230
+ }
231
+
146
232
  .lato-cms-image-field__thumb {
147
- max-width: 200px;
148
- max-height: 200px;
233
+ width: 100%;
234
+ height: 100%;
149
235
  object-fit: cover;
150
236
  display: block;
151
- transition: opacity 0.15s, outline 0.15s;
237
+ transition: opacity 0.15s, filter 0.15s;
152
238
  }
153
239
 
154
240
  .lato-cms-image-field__remove-btn {
@@ -158,6 +244,7 @@ body.lato-cms-advanced-editor--open {
158
244
  width: 22px;
159
245
  height: 22px;
160
246
  border-radius: 50%;
247
+ border: 0;
161
248
  background: rgba(220, 53, 69, 0.85);
162
249
  color: #fff;
163
250
  display: flex;
@@ -172,6 +259,23 @@ body.lato-cms-advanced-editor--open {
172
259
  }
173
260
  }
174
261
 
262
+ .lato-cms-image-field__removed {
263
+ align-items: center;
264
+ display: flex;
265
+ gap: 0.75rem;
266
+ justify-content: space-between;
267
+ width: 100%;
268
+ }
269
+
270
+ .lato-cms-image-field--removing {
271
+ .lato-cms-image-field__preview,
272
+ .lato-cms-file-field__info,
273
+ .lato-cms-image-field__remove-btn,
274
+ > .btn {
275
+ display: none;
276
+ }
277
+ }
278
+
175
279
  .lato-cms-image-field__new-badge {
176
280
  position: absolute;
177
281
  bottom: 4px;