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 +4 -4
- data/app/assets/javascripts/lato_cms/controllers/lato_cms_component_form_controller.js +4 -0
- data/app/assets/javascripts/lato_cms/controllers/lato_cms_file_field_controller.js +203 -0
- data/app/assets/javascripts/lato_cms/controllers/lato_cms_gallery_field_controller.js +33 -1
- data/app/assets/javascripts/lato_cms/controllers/lato_cms_image_field_controller.js +135 -6
- data/app/assets/stylesheets/lato_cms/application.scss +107 -3
- data/app/controllers/lato_cms/pages_controller.rb +31 -4
- data/app/models/lato_cms/page_field.rb +5 -1
- data/app/views/lato_cms/pages/fields/_file.html.erb +37 -15
- data/app/views/lato_cms/pages/fields/_image.html.erb +37 -12
- data/config/locales/en.yml +4 -0
- data/config/locales/it.yml +4 -0
- data/lib/lato_cms/version.rb +1 -1
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 527556e5bf27156bab801e7f0c23c53f9ac9e0565d051da5a0fa1c9500954357
|
|
4
|
+
data.tar.gz: dfcbcca5925d763f06d2043e497e128d84d3fcd200059990f9cb8fc1718699cc
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
+
'&': '&',
|
|
177
|
+
'<': '<',
|
|
178
|
+
'>': '>',
|
|
179
|
+
'"': '"',
|
|
180
|
+
"'": '''
|
|
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", "
|
|
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
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
this.
|
|
22
|
-
this.
|
|
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
|
+
'&': '&',
|
|
131
|
+
'<': '<',
|
|
132
|
+
'>': '>',
|
|
133
|
+
'"': '"',
|
|
134
|
+
"'": '''
|
|
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
|
-
|
|
148
|
-
|
|
233
|
+
width: 100%;
|
|
234
|
+
height: 100%;
|
|
149
235
|
object-fit: cover;
|
|
150
236
|
display: block;
|
|
151
|
-
transition: opacity 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'
|
|
196
|
+
when 'file'
|
|
197
197
|
field.save if field.new_record?
|
|
198
|
-
|
|
199
|
-
|
|
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'
|
|
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']
|
|
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
|
-
|
|
15
|
-
|
|
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="
|
|
18
|
-
<
|
|
19
|
-
|
|
20
|
-
<
|
|
21
|
-
|
|
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
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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="
|
|
17
|
-
<div class="
|
|
18
|
-
<img src="<%= lato_cms_attachment_path(current_file) %>" class="
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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="
|
|
28
|
-
<div class="
|
|
29
|
-
<img src="" class="
|
|
30
|
-
|
|
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"
|
data/config/locales/en.yml
CHANGED
|
@@ -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 --"
|
data/config/locales/it.yml
CHANGED
|
@@ -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 --"
|
data/lib/lato_cms/version.rb
CHANGED
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.
|
|
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
|