lato_cms 3.0.1 → 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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ce57b3c62b4c5ad59c91a5ec2f01e0eb958bfd5244097e2df0b7210f625e1a0e
4
- data.tar.gz: b6304761ec5116b821b75660140fdab9002a6a8e13b68923e0df77ace2fb53d0
3
+ metadata.gz: 527556e5bf27156bab801e7f0c23c53f9ac9e0565d051da5a0fa1c9500954357
4
+ data.tar.gz: dfcbcca5925d763f06d2043e497e128d84d3fcd200059990f9cb8fc1718699cc
5
5
  SHA512:
6
- metadata.gz: 997c72993680123c414c978e5291ffb511f7c7b19f728c23aa8535e84c226002ee7a2a950e07513f48b7840a79db55ee0c7ef9f0319e1dd2f62f44a70c957e51
7
- data.tar.gz: a11ae95caf80c3d4ceacb798f5515da2ca6f0448d1f1c859d8ed5819112adde51b5035e829f03d6791c7308b229a3880d83d09bc30fc74df216035f58f102422
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
+ }
@@ -8,6 +8,15 @@ export default class extends Controller {
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() {
@@ -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
  }
@@ -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;
@@ -90,7 +90,7 @@ module LatoCms
90
90
  respond_to do |format|
91
91
  if errors.empty?
92
92
  format.html { redirect_to lato_cms.pages_show_path(@page), notice: t('lato_cms.fields_saved') }
93
- 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) } }
94
94
  else
95
95
  error_messages = errors.map { |e| "#{e[:field_id]}: #{e[:errors].join(', ')}" }.join('; ')
96
96
  format.html { redirect_to lato_cms.pages_show_path(@page), alert: error_messages }
@@ -193,10 +193,12 @@ module LatoCms
193
193
 
194
194
  def assign_field_value(field, field_type, field_data)
195
195
  case field_type
196
- when 'file', 'image'
196
+ when 'file'
197
197
  field.save if field.new_record?
198
- attach_field_files(field, field_data)
199
- remove_field_files(field, field_data)
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)
200
202
  when 'gallery'
201
203
  field.save if field.new_record?
202
204
  attach_field_files(field, field_data)
@@ -219,6 +221,19 @@ module LatoCms
219
221
  Array(field_data[:files]).compact.each { |file| field.files.attach(file) }
220
222
  end
221
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
+
222
237
  def remove_field_files(field, field_data)
223
238
  return unless field_data[:remove_file_ids].present?
224
239
 
@@ -227,6 +242,18 @@ module LatoCms
227
242
  end
228
243
  end
229
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
+
230
257
  def create_params
231
258
  params.require(:page).permit(:title, :locale)
232
259
  end
@@ -78,6 +78,7 @@ module LatoCms
78
78
  def as_json(_options = {})
79
79
  result = {
80
80
  id: id,
81
+ persisted_field_id: field_id,
81
82
  field_id: base_field_id,
82
83
  field_type: field_type,
83
84
  field_name: field_name,
@@ -87,7 +88,9 @@ module LatoCms
87
88
  }
88
89
 
89
90
  case field_type
90
- when 'file', 'image'
91
+ when 'file'
92
+ result[:attachments] = files.map { |f| attachment_as_json(f) }
93
+ when 'image'
91
94
  attached = files.first
92
95
  result[:attachments] = attached ? [attachment_as_json(attached)] : []
93
96
  when 'gallery'
@@ -109,6 +112,7 @@ module LatoCms
109
112
  id: attachment.id,
110
113
  filename: attachment.filename.to_s,
111
114
  content_type: attachment.content_type,
115
+ byte_size: attachment.byte_size,
112
116
  url: Rails.application.routes.url_helpers.rails_blob_path(attachment, only_path: true)
113
117
  }
114
118
  end
@@ -1,7 +1,7 @@
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
6
  <% files_input_name = lato_cms_field_input_name(field_id, 'files', input_name_prefix: local_assigns[:input_name_prefix], multiple: true) %>
7
7
  <% remove_input_name = lato_cms_field_input_name(field_id, 'remove_file_ids', input_name_prefix: local_assigns[:input_name_prefix], multiple: true) %>
@@ -11,22 +11,44 @@
11
11
  <%= label %><%= ' *' if required %>
12
12
  </label>
13
13
 
14
- <% if page_field&.files&.attached? %>
15
- <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? %>
16
23
  <% page_field.files.each do |file| %>
17
- <div class="form-check mb-1 p-2 ps-4 bg-light rounded">
18
- <input type="checkbox" class="form-check-input" name="<%= remove_input_name %>" value="<%= file.id %>" id="remove_file_<%= file.id %>">
19
- <label class="form-check-label small" for="remove_file_<%= file.id %>">
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>
21
- </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>
22
41
  </div>
23
42
  <% end %>
43
+ <% end %>
24
44
  </div>
25
- <% end %>
26
45
 
27
- <input type="file"
28
- class="form-control"
29
- name="<%= files_input_name %>"
30
- id="<%= input_id %>"
31
- <%= 'multiple' if multiple %>
32
- <% 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>
@@ -11,24 +11,49 @@
11
11
  <%= label %><%= ' *' if required %>
12
12
  </label>
13
13
 
14
- <div data-controller="lato-cms-image-field">
14
+ <div data-controller="lato-cms-image-field"
15
+ data-field-id="<%= field_id %>"
16
+ data-new-badge-label="<%= t('lato_cms.field_image_new_badge') %>"
17
+ data-remove-label="<%= t('lato_cms.field_image_remove') %>"
18
+ data-remove-pending-label="<%= t('lato_cms.field_image_remove_pending') %>"
19
+ data-undo-label="<%= t('lato_cms.field_file_remove_undo') %>">
15
20
  <% if current_file %>
16
- <div class="mb-2" data-lato-cms-image-field-target="currentContainer">
17
- <div class="position-relative d-inline-block">
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 %>">
19
- <label class="lato-cms-image-field__remove-btn" title="<%= t('lato_cms.field_image_remove') %>">
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">
21
- <i class="bi bi-x-lg"></i>
22
- </label>
21
+ <div class="lato-cms-file-field__item lato-cms-image-field__item" data-lato-cms-image-field-target="currentContainer" data-attachment-id="<%= current_file.id %>">
22
+ <div class="lato-cms-image-field__preview">
23
+ <img src="<%= lato_cms_attachment_path(current_file) %>" class="lato-cms-image-field__thumb" data-lato-cms-image-field-target="currentImage" alt="<%= current_file.filename %>">
24
+ </div>
25
+ <div class="lato-cms-file-field__info">
26
+ <i class="bi bi-image"></i>
27
+ <span><%= current_file.blob.filename %></span>
28
+ <span class="text-muted small">(<%= number_to_human_size(current_file.blob.byte_size) %>)</span>
29
+ </div>
30
+ <button type="button" class="btn btn-sm btn-outline-danger" data-action="lato-cms-image-field#remove">
31
+ <i class="bi bi-trash me-1"></i><%= t('lato_cms.field_image_remove') %>
32
+ </button>
33
+ <div class="lato-cms-image-field__removed d-none" data-lato-cms-image-field-target="removedNotice">
34
+ <span class="text-danger small">
35
+ <i class="bi bi-trash me-1"></i><%= t('lato_cms.field_image_remove_pending') %>
36
+ </span>
37
+ <button type="button" class="btn btn-link btn-sm p-0" data-action="lato-cms-image-field#undo">
38
+ <%= t('lato_cms.field_file_remove_undo') %>
39
+ </button>
23
40
  </div>
24
41
  </div>
25
42
  <% end %>
26
43
 
27
- <div class="mb-2 d-none" data-lato-cms-image-field-target="newPreviewContainer">
28
- <div class="position-relative d-inline-block">
29
- <img src="" class="img-thumbnail lato-cms-image-field__thumb" data-lato-cms-image-field-target="newPreview" alt="">
30
- <span class="badge bg-success lato-cms-image-field__new-badge"><%= t('lato_cms.field_image_new_badge') %></span>
44
+ <div class="lato-cms-file-field__item lato-cms-file-field__item--new lato-cms-image-field__item d-none" data-lato-cms-image-field-target="newPreviewContainer">
45
+ <div class="lato-cms-image-field__preview">
46
+ <img src="" class="lato-cms-image-field__thumb" data-lato-cms-image-field-target="newPreview" alt="">
47
+ </div>
48
+ <div class="lato-cms-file-field__info">
49
+ <i class="bi bi-image"></i>
50
+ <span data-lato-cms-image-field-target="newPreviewName"></span>
51
+ <span class="text-muted small" data-lato-cms-image-field-target="newPreviewSize"></span>
52
+ <span class="badge bg-success"><%= t('lato_cms.field_image_new_badge') %></span>
31
53
  </div>
54
+ <button type="button" class="btn btn-sm btn-outline-danger" data-action="lato-cms-image-field#removeNew">
55
+ <i class="bi bi-trash me-1"></i><%= t('lato_cms.field_image_remove') %>
56
+ </button>
32
57
  </div>
33
58
 
34
59
  <input type="file"
@@ -53,10 +53,14 @@ en:
53
53
  fields_component_missing_alert_html: "Component <strong>%{component_id}</strong> not found. The component configuration may have been removed."
54
54
  fields_previously_saved_data: "Previously saved data:"
55
55
  field_file_remove: Remove
56
+ field_file_remove_pending: File will be removed on save
57
+ field_file_remove_undo: Undo
58
+ field_file_new_badge: New
56
59
  field_gallery_empty: No images yet. Add images below.
57
60
  field_gallery_add_images: Add images
58
61
  field_gallery_drag_to_reorder: Drag to reorder
59
62
  field_image_remove: Remove
63
+ field_image_remove_pending: Image will be removed on save
60
64
  field_image_new_badge: New
61
65
  field_multiselect_hint: Hold Ctrl/Cmd to select multiple
62
66
  field_select_placeholder: "-- Select --"
@@ -53,10 +53,14 @@ it:
53
53
  fields_component_missing_alert_html: "Componente <strong>%{component_id}</strong> non trovato. La configurazione del componente potrebbe essere stata rimossa."
54
54
  fields_previously_saved_data: "Dati salvati in precedenza:"
55
55
  field_file_remove: Rimuovi
56
+ field_file_remove_pending: Il file verra rimosso al salvataggio
57
+ field_file_remove_undo: Annulla
58
+ field_file_new_badge: Nuovo
56
59
  field_gallery_empty: Nessuna immagine presente. Aggiungi immagini qui sotto.
57
60
  field_gallery_add_images: Aggiungi immagini
58
61
  field_gallery_drag_to_reorder: Trascina per riordinare
59
62
  field_image_remove: Rimuovi
63
+ field_image_remove_pending: L'immagine verra rimossa al salvataggio
60
64
  field_image_new_badge: Nuovo
61
65
  field_multiselect_hint: Tieni premuto Ctrl/Cmd per selezionare piu opzioni
62
66
  field_select_placeholder: "-- Seleziona --"
@@ -1,3 +1,3 @@
1
1
  module LatoCms
2
- VERSION = "3.0.1"
2
+ VERSION = "3.0.2"
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.1
4
+ version: 3.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Gregorio Galante
@@ -82,6 +82,7 @@ files:
82
82
  - app/assets/javascripts/lato_cms/controllers/lato_cms_component_accordion_controller.js
83
83
  - app/assets/javascripts/lato_cms/controllers/lato_cms_component_form_controller.js
84
84
  - app/assets/javascripts/lato_cms/controllers/lato_cms_component_toggle_controller.js
85
+ - app/assets/javascripts/lato_cms/controllers/lato_cms_file_field_controller.js
85
86
  - app/assets/javascripts/lato_cms/controllers/lato_cms_gallery_field_controller.js
86
87
  - app/assets/javascripts/lato_cms/controllers/lato_cms_image_field_controller.js
87
88
  - app/assets/javascripts/lato_cms/controllers/lato_cms_json_field_controller.js