lato_cms 3.0.13 → 3.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/app/assets/javascripts/lato_cms/controllers/lato_cms_media_field_controller.js +143 -0
- data/app/assets/javascripts/lato_cms/controllers/lato_cms_media_picker_controller.js +110 -0
- data/app/assets/javascripts/lato_cms/controllers/lato_cms_media_reorder_controller.js +37 -0
- data/app/assets/javascripts/lato_cms/controllers/lato_cms_reload_on_modal_close_controller.js +44 -0
- data/app/assets/stylesheets/lato_cms/application.scss +131 -158
- data/app/controllers/lato_cms/application_controller.rb +4 -0
- data/app/controllers/lato_cms/media_controller.rb +121 -0
- data/app/controllers/lato_cms/pages_controller.rb +75 -91
- data/app/helpers/lato_cms/media_helper.rb +50 -0
- data/app/jobs/lato_cms/export_sitemap_csv_job.rb +33 -0
- data/app/jobs/lato_cms/generate_alt_text_job.rb +21 -0
- data/app/jobs/lato_cms/generate_video_poster_job.rb +7 -5
- data/app/jobs/lato_cms/translate_component_fields_job.rb +16 -0
- data/app/models/lato_cms/llm_client.rb +31 -0
- data/app/models/lato_cms/media.rb +270 -0
- data/app/models/lato_cms/page.rb +40 -0
- data/app/models/lato_cms/page_field.rb +52 -80
- data/app/models/lato_cms/page_field_media.rb +8 -0
- data/app/views/lato_cms/media/_form_create.html.erb +20 -0
- data/app/views/lato_cms/media/_form_update.html.erb +60 -0
- data/app/views/lato_cms/media/create.html.erb +13 -0
- data/app/views/lato_cms/media/index.html.erb +22 -0
- data/app/views/lato_cms/media/picker_action.html.erb +98 -0
- data/app/views/lato_cms/media/update.html.erb +13 -0
- data/app/views/lato_cms/pages/_component_accordion.html.erb +15 -0
- data/app/views/lato_cms/pages/fields/_file.html.erb +4 -55
- data/app/views/lato_cms/pages/fields/_gallery.html.erb +4 -60
- data/app/views/lato_cms/pages/fields/_image.html.erb +4 -68
- data/app/views/lato_cms/pages/fields/_media_field.html.erb +62 -0
- data/app/views/lato_cms/pages/fields/_media_item.html.erb +27 -0
- data/app/views/lato_cms/pages/fields/_video.html.erb +4 -69
- data/app/views/lato_cms/pages/index.html.erb +17 -0
- data/app/views/lato_cms/pages/show.html.erb +2 -1
- data/config/locales/en.yml +36 -12
- data/config/locales/it.yml +36 -12
- data/config/routes.rb +12 -0
- data/db/migrate/20260728175413_create_lato_cms_media.rb +11 -0
- data/db/migrate/20260728175414_create_lato_cms_page_field_media.rb +12 -0
- data/db/migrate/20260729120000_change_lato_cms_media_alt_text_to_translations.rb +25 -0
- data/lib/lato_cms/config.rb +12 -1
- data/lib/lato_cms/version.rb +1 -1
- metadata +38 -5
- data/app/assets/javascripts/lato_cms/controllers/lato_cms_file_field_controller.js +0 -225
- data/app/assets/javascripts/lato_cms/controllers/lato_cms_gallery_field_controller.js +0 -190
- data/app/assets/javascripts/lato_cms/controllers/lato_cms_image_field_controller.js +0 -169
- data/app/assets/javascripts/lato_cms/controllers/lato_cms_video_field_controller.js +0 -180
|
@@ -1,68 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
<% current_file = page_field&.files&.attached? ? page_field.files.first : nil %>
|
|
6
|
-
<% files_input_name = lato_cms_field_input_name(field_id, 'files', input_name_prefix: local_assigns[:input_name_prefix], multiple: true) %>
|
|
7
|
-
<% remove_input_name = lato_cms_field_input_name(field_id, 'remove_file_ids', input_name_prefix: local_assigns[:input_name_prefix], multiple: true) %>
|
|
8
|
-
<% input_id = lato_cms_field_dom_id(field_id, 'files', dom_id_prefix: local_assigns[:dom_id_prefix]) %>
|
|
9
|
-
|
|
10
|
-
<label class="form-label">
|
|
11
|
-
<%= label %><%= ' *' if required %>
|
|
12
|
-
</label>
|
|
13
|
-
|
|
14
|
-
<div data-controller="lato-cms-image-field"
|
|
15
|
-
data-field-id="<%= field_id %>"
|
|
16
|
-
data-field-required="<%= required %>"
|
|
17
|
-
data-new-badge-label="<%= t('lato_cms.field_image_new_badge') %>"
|
|
18
|
-
data-remove-label="<%= t('lato_cms.field_image_remove') %>"
|
|
19
|
-
data-remove-pending-label="<%= t('lato_cms.field_image_remove_pending') %>"
|
|
20
|
-
data-undo-label="<%= t('lato_cms.field_file_remove_undo') %>">
|
|
21
|
-
<% if current_file %>
|
|
22
|
-
<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 %>">
|
|
23
|
-
<div class="lato-cms-image-field__preview">
|
|
24
|
-
<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 %>">
|
|
25
|
-
</div>
|
|
26
|
-
<div class="lato-cms-file-field__info">
|
|
27
|
-
<i class="bi bi-image"></i>
|
|
28
|
-
<span><%= current_file.blob.filename %></span>
|
|
29
|
-
<span class="text-muted small">(<%= number_to_human_size(current_file.blob.byte_size) %>)</span>
|
|
30
|
-
</div>
|
|
31
|
-
<button type="button" class="lato-cms-attachment-field__remove" title="<%= t('lato_cms.field_image_remove') %>" aria-label="<%= t('lato_cms.field_image_remove') %>" data-action="lato-cms-image-field#remove">
|
|
32
|
-
<i class="bi bi-trash"></i>
|
|
33
|
-
</button>
|
|
34
|
-
<div class="lato-cms-file-field__removed d-none" data-lato-cms-image-field-target="removedNotice">
|
|
35
|
-
<span class="text-danger small">
|
|
36
|
-
<i class="bi bi-trash me-1"></i><%= t('lato_cms.field_image_remove_pending') %>
|
|
37
|
-
</span>
|
|
38
|
-
<button type="button" class="btn btn-link btn-sm p-0" data-action="lato-cms-image-field#undo">
|
|
39
|
-
<%= t('lato_cms.field_file_remove_undo') %>
|
|
40
|
-
</button>
|
|
41
|
-
</div>
|
|
42
|
-
</div>
|
|
43
|
-
<% end %>
|
|
44
|
-
|
|
45
|
-
<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">
|
|
46
|
-
<div class="lato-cms-image-field__preview">
|
|
47
|
-
<img src="" class="lato-cms-image-field__thumb" data-lato-cms-image-field-target="newPreview" alt="">
|
|
48
|
-
</div>
|
|
49
|
-
<div class="lato-cms-file-field__info">
|
|
50
|
-
<i class="bi bi-image"></i>
|
|
51
|
-
<span data-lato-cms-image-field-target="newPreviewName"></span>
|
|
52
|
-
<span class="text-muted small" data-lato-cms-image-field-target="newPreviewSize"></span>
|
|
53
|
-
<span class="badge bg-success"><%= t('lato_cms.field_image_new_badge') %></span>
|
|
54
|
-
</div>
|
|
55
|
-
<button type="button" class="lato-cms-attachment-field__remove" title="<%= t('lato_cms.field_image_remove') %>" aria-label="<%= t('lato_cms.field_image_remove') %>" data-action="lato-cms-image-field#removeNew">
|
|
56
|
-
<i class="bi bi-trash"></i>
|
|
57
|
-
</button>
|
|
58
|
-
</div>
|
|
59
|
-
|
|
60
|
-
<input type="file"
|
|
61
|
-
class="form-control"
|
|
62
|
-
name="<%= files_input_name %>"
|
|
63
|
-
id="<%= input_id %>"
|
|
64
|
-
accept="<%= accept %>"
|
|
65
|
-
data-lato-cms-image-field-target="input"
|
|
66
|
-
data-action="change->lato-cms-image-field#preview"
|
|
67
|
-
<%= 'required' if required && !current_file %>>
|
|
68
|
-
</div>
|
|
1
|
+
<%= render partial: 'lato_cms/pages/fields/media_field', locals: {
|
|
2
|
+
field_id: field_id, field_config: field_config, page_field: page_field, kind: 'image', multiple: false,
|
|
3
|
+
input_name_prefix: local_assigns[:input_name_prefix], dom_id_prefix: local_assigns[:dom_id_prefix]
|
|
4
|
+
} %>
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
<%# Shared markup for image/video/file/gallery fields. Rendered by the 4
|
|
2
|
+
type-specific partials with `kind` ('image'/'video'/'file') and `multiple`
|
|
3
|
+
locals — differences between them are otherwise just presentation. %>
|
|
4
|
+
<% label = field_config['name'] || field_id.humanize %>
|
|
5
|
+
<% required = field_config['required'] == true %>
|
|
6
|
+
<% current_media = page_field ? page_field.media.to_a : [] %>
|
|
7
|
+
<% dom_id = lato_cms_field_dom_id(field_id, 'media_picker', dom_id_prefix: local_assigns[:dom_id_prefix]) %>
|
|
8
|
+
<% frame_id = "media_picker_#{dom_id}" %>
|
|
9
|
+
<% hidden_name = multiple ?
|
|
10
|
+
lato_cms_field_input_name(field_id, 'media_ids', input_name_prefix: local_assigns[:input_name_prefix], multiple: true) :
|
|
11
|
+
lato_cms_field_input_name(field_id, 'media_id', input_name_prefix: local_assigns[:input_name_prefix]) %>
|
|
12
|
+
<% picker_type = kind == 'file' ? nil : kind %>
|
|
13
|
+
|
|
14
|
+
<label class="form-label">
|
|
15
|
+
<%= label %><%= ' *' if required %>
|
|
16
|
+
</label>
|
|
17
|
+
|
|
18
|
+
<div data-controller="lato-cms-media-field<%= ' lato-cms-media-reorder' if multiple %>"
|
|
19
|
+
data-lato-cms-media-field-kind-value="<%= kind %>"
|
|
20
|
+
data-lato-cms-media-field-multiple-value="<%= multiple %>"
|
|
21
|
+
data-lato-cms-media-field-frame-id-value="<%= frame_id %>"
|
|
22
|
+
data-lato-cms-media-field-hidden-name-value="<%= hidden_name %>"
|
|
23
|
+
data-lato-cms-media-field-field-id-value="<%= field_id %>"
|
|
24
|
+
data-field-required="<%= required %>">
|
|
25
|
+
|
|
26
|
+
<div class="lato-cms-media-field__grid mb-2"
|
|
27
|
+
data-lato-cms-media-field-target="grid"
|
|
28
|
+
data-lato-cms-media-reorder-target="grid"
|
|
29
|
+
<% if multiple %>data-action="dragover->lato-cms-media-reorder#onGridDragOver drop->lato-cms-media-reorder#onGridDrop"<% end %>>
|
|
30
|
+
<% current_media.each do |m| %>
|
|
31
|
+
<%= render partial: 'lato_cms/pages/fields/media_item', locals: { media: m, kind: kind, hidden_name: hidden_name, multiple: multiple } %>
|
|
32
|
+
<% end %>
|
|
33
|
+
|
|
34
|
+
<%# The empty state AND the "add more" affordance for multi-value fields —
|
|
35
|
+
a single dashed tile living inside the grid, hidden (not removed) for
|
|
36
|
+
single-value fields once something is selected, so the field-side JS
|
|
37
|
+
only ever has to toggle a class rather than rebuild this node. %>
|
|
38
|
+
<%# Deliberately NOT using the shared `lato-action` trigger convention here:
|
|
39
|
+
it opens frames by hoping Turbo's own click handling notices a
|
|
40
|
+
just-inserted <turbo-frame> before driving a full-page visit, which
|
|
41
|
+
loses that race for this link. `openPicker` below prevents the
|
|
42
|
+
default navigation itself and sets the frame's `src` explicitly
|
|
43
|
+
(Turbo's documented, non-racy way to trigger a frame-scoped fetch). %>
|
|
44
|
+
<%= link_to lato_cms.media_picker_action_path(frame_id: frame_id, multiple: multiple, type: picker_type),
|
|
45
|
+
class: "lato-cms-media-field__tile lato-cms-media-field__tile--add#{' d-none' if !multiple && current_media.any?}",
|
|
46
|
+
data: {
|
|
47
|
+
lato_cms_media_field_target: 'addTile',
|
|
48
|
+
action: 'click->lato-cms-media-field#openPicker',
|
|
49
|
+
action_title: t('lato_cms.media_picker_title'), action_size: 'lg'
|
|
50
|
+
} do %>
|
|
51
|
+
<i class="bi bi-images"></i>
|
|
52
|
+
<span><%= t('lato_cms.media_choose_from_library') %></span>
|
|
53
|
+
<% end %>
|
|
54
|
+
</div>
|
|
55
|
+
|
|
56
|
+
<%# Real-but-hidden input anchors the browser's native required-validation
|
|
57
|
+
bubble (a display:none required input fails silently as "not
|
|
58
|
+
focusable"). Kept in sync with selection state by the Stimulus controller. %>
|
|
59
|
+
<input type="text" class="visually-hidden" value="<%= current_media.first&.id %>"
|
|
60
|
+
data-lato-cms-media-field-target="validationAnchor"
|
|
61
|
+
<%= 'required' if required && current_media.empty? %>>
|
|
62
|
+
</div>
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
<div class="lato-cms-media-field__tile lato-cms-media-field__item"
|
|
2
|
+
data-media-id="<%= media.id %>"
|
|
3
|
+
data-lato-cms-media-field-target="item"
|
|
4
|
+
<% if multiple %>
|
|
5
|
+
data-lato-cms-media-reorder-target="item"
|
|
6
|
+
draggable="true"
|
|
7
|
+
data-action="dragstart->lato-cms-media-reorder#onDragStart dragend->lato-cms-media-reorder#onDragEnd"
|
|
8
|
+
<% end %>>
|
|
9
|
+
<% if kind == 'video' %>
|
|
10
|
+
<video muted preload="metadata" class="lato-cms-media-field__player" src="<%= media.url %>"></video>
|
|
11
|
+
<i class="bi bi-play-circle-fill lato-cms-media-field__badge"></i>
|
|
12
|
+
<% elsif media.thumbnail_url %>
|
|
13
|
+
<img src="<%= media.thumbnail_url %>" class="lato-cms-media-field__thumb" alt="<%= media.alt_text %>">
|
|
14
|
+
<% else %>
|
|
15
|
+
<div class="lato-cms-media-field__icon"><i class="bi bi-paperclip"></i></div>
|
|
16
|
+
<% end %>
|
|
17
|
+
|
|
18
|
+
<div class="lato-cms-media-field__overlay">
|
|
19
|
+
<span class="lato-cms-media-field__name"><%= media.name %></span>
|
|
20
|
+
</div>
|
|
21
|
+
|
|
22
|
+
<button type="button" class="lato-cms-media-field__remove" title="<%= t('lato_cms.field_file_remove') %>" aria-label="<%= t('lato_cms.field_file_remove') %>" data-action="lato-cms-media-field#remove">
|
|
23
|
+
<i class="bi bi-x-lg"></i>
|
|
24
|
+
</button>
|
|
25
|
+
|
|
26
|
+
<input type="hidden" name="<%= hidden_name %>" value="<%= media.id %>" data-lato-cms-media-field-target="hiddenInput">
|
|
27
|
+
</div>
|
|
@@ -1,69 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
<% current_file = page_field&.video_attachment %>
|
|
6
|
-
<% poster_file = page_field&.poster_attachment %>
|
|
7
|
-
<% files_input_name = lato_cms_field_input_name(field_id, 'files', input_name_prefix: local_assigns[:input_name_prefix], multiple: true) %>
|
|
8
|
-
<% remove_input_name = lato_cms_field_input_name(field_id, 'remove_file_ids', input_name_prefix: local_assigns[:input_name_prefix], multiple: true) %>
|
|
9
|
-
<% input_id = lato_cms_field_dom_id(field_id, 'files', dom_id_prefix: local_assigns[:dom_id_prefix]) %>
|
|
10
|
-
|
|
11
|
-
<label class="form-label">
|
|
12
|
-
<%= label %><%= ' *' if required %>
|
|
13
|
-
</label>
|
|
14
|
-
|
|
15
|
-
<div data-controller="lato-cms-video-field"
|
|
16
|
-
data-field-id="<%= field_id %>"
|
|
17
|
-
data-field-required="<%= required %>"
|
|
18
|
-
data-new-badge-label="<%= t('lato_cms.field_video_new_badge') %>"
|
|
19
|
-
data-remove-label="<%= t('lato_cms.field_video_remove') %>"
|
|
20
|
-
data-remove-pending-label="<%= t('lato_cms.field_video_remove_pending') %>"
|
|
21
|
-
data-undo-label="<%= t('lato_cms.field_file_remove_undo') %>">
|
|
22
|
-
<% if current_file %>
|
|
23
|
-
<div class="lato-cms-file-field__item lato-cms-video-field__item" data-lato-cms-video-field-target="currentContainer" data-attachment-id="<%= current_file.id %>">
|
|
24
|
-
<div class="lato-cms-video-field__preview">
|
|
25
|
-
<video controls preload="metadata" class="lato-cms-video-field__player" src="<%= lato_cms_attachment_path(current_file) %>" <% if poster_file %>poster="<%= lato_cms_attachment_path(poster_file) %>"<% end %> data-lato-cms-video-field-target="currentVideo"></video>
|
|
26
|
-
</div>
|
|
27
|
-
<div class="lato-cms-file-field__info">
|
|
28
|
-
<i class="bi bi-film"></i>
|
|
29
|
-
<span><%= current_file.blob.filename %></span>
|
|
30
|
-
<span class="text-muted small">(<%= number_to_human_size(current_file.blob.byte_size) %>)</span>
|
|
31
|
-
</div>
|
|
32
|
-
<button type="button" class="lato-cms-attachment-field__remove" title="<%= t('lato_cms.field_video_remove') %>" aria-label="<%= t('lato_cms.field_video_remove') %>" data-action="lato-cms-video-field#remove">
|
|
33
|
-
<i class="bi bi-trash"></i>
|
|
34
|
-
</button>
|
|
35
|
-
<div class="lato-cms-file-field__removed d-none" data-lato-cms-video-field-target="removedNotice">
|
|
36
|
-
<span class="text-danger small">
|
|
37
|
-
<i class="bi bi-trash me-1"></i><%= t('lato_cms.field_video_remove_pending') %>
|
|
38
|
-
</span>
|
|
39
|
-
<button type="button" class="btn btn-link btn-sm p-0" data-action="lato-cms-video-field#undo">
|
|
40
|
-
<%= t('lato_cms.field_file_remove_undo') %>
|
|
41
|
-
</button>
|
|
42
|
-
</div>
|
|
43
|
-
</div>
|
|
44
|
-
<% end %>
|
|
45
|
-
|
|
46
|
-
<div class="lato-cms-file-field__item lato-cms-file-field__item--new lato-cms-video-field__item d-none" data-lato-cms-video-field-target="newPreviewContainer">
|
|
47
|
-
<div class="lato-cms-video-field__preview">
|
|
48
|
-
<video controls preload="metadata" class="lato-cms-video-field__player" data-lato-cms-video-field-target="newPreview"></video>
|
|
49
|
-
</div>
|
|
50
|
-
<div class="lato-cms-file-field__info">
|
|
51
|
-
<i class="bi bi-film"></i>
|
|
52
|
-
<span data-lato-cms-video-field-target="newPreviewName"></span>
|
|
53
|
-
<span class="text-muted small" data-lato-cms-video-field-target="newPreviewSize"></span>
|
|
54
|
-
<span class="badge bg-success"><%= t('lato_cms.field_video_new_badge') %></span>
|
|
55
|
-
</div>
|
|
56
|
-
<button type="button" class="lato-cms-attachment-field__remove" title="<%= t('lato_cms.field_video_remove') %>" aria-label="<%= t('lato_cms.field_video_remove') %>" data-action="lato-cms-video-field#removeNew">
|
|
57
|
-
<i class="bi bi-trash"></i>
|
|
58
|
-
</button>
|
|
59
|
-
</div>
|
|
60
|
-
|
|
61
|
-
<input type="file"
|
|
62
|
-
class="form-control"
|
|
63
|
-
name="<%= files_input_name %>"
|
|
64
|
-
id="<%= input_id %>"
|
|
65
|
-
accept="<%= accept %>"
|
|
66
|
-
data-lato-cms-video-field-target="input"
|
|
67
|
-
data-action="change->lato-cms-video-field#preview"
|
|
68
|
-
<%= 'required' if required && !current_file %>>
|
|
69
|
-
</div>
|
|
1
|
+
<%= render partial: 'lato_cms/pages/fields/media_field', locals: {
|
|
2
|
+
field_id: field_id, field_config: field_config, page_field: page_field, kind: 'video', multiple: false,
|
|
3
|
+
input_name_prefix: local_assigns[:input_name_prefix], dom_id_prefix: local_assigns[:dom_id_prefix]
|
|
4
|
+
} %>
|
|
@@ -10,6 +10,23 @@
|
|
|
10
10
|
<%= locale_to_flag(locale) %> <%= locale.to_s.upcase %>
|
|
11
11
|
<% end %>
|
|
12
12
|
<% end %>
|
|
13
|
+
|
|
14
|
+
<div class="dropdown ms-auto" title="<%= t('lato_cms.cta_actions') %>" data-controller="lato-tooltip">
|
|
15
|
+
<button type="button" class="btn btn-sm btn-outline-secondary dropdown-toggle" data-bs-toggle="dropdown">
|
|
16
|
+
<i class="bi bi-three-dots"></i>
|
|
17
|
+
</button>
|
|
18
|
+
<ul class="dropdown-menu dropdown-menu-end">
|
|
19
|
+
<li>
|
|
20
|
+
<%= link_to t('lato_cms.sitemap_export_cta'), lato_cms.pages_sitemap_export_action_path, class: 'dropdown-item',
|
|
21
|
+
data: {
|
|
22
|
+
lato_action_target: 'trigger',
|
|
23
|
+
turbo_method: :post,
|
|
24
|
+
turbo_frame: 'lato_operation',
|
|
25
|
+
action_title: t('lato_cms.sitemap_export_title')
|
|
26
|
+
} %>
|
|
27
|
+
</li>
|
|
28
|
+
</ul>
|
|
29
|
+
</div>
|
|
13
30
|
</div>
|
|
14
31
|
<div class="card-body">
|
|
15
32
|
<%= lato_index @pages,
|
|
@@ -70,7 +70,8 @@
|
|
|
70
70
|
</div>
|
|
71
71
|
|
|
72
72
|
<%# ── Editor column ──────────────────────────────────────────────────── %>
|
|
73
|
-
<div class="col-12 col-lg-6 mb-4 lato-cms-advanced-editor__editor-col" data-lato-cms-advanced-editor-target="editorCol"
|
|
73
|
+
<div class="col-12 col-lg-6 mb-4 lato-cms-advanced-editor__editor-col" data-lato-cms-advanced-editor-target="editorCol"
|
|
74
|
+
data-controller="lato-cms-reload-on-modal-close">
|
|
74
75
|
<div class="card h-100">
|
|
75
76
|
<div class="card-header d-flex justify-content-between align-items-center">
|
|
76
77
|
<h2 class="fs-5 mb-0"><%= t('lato_cms.show_editor_title') %></h2>
|
data/config/locales/en.yml
CHANGED
|
@@ -33,6 +33,10 @@ en:
|
|
|
33
33
|
form_template_unavailable: "The template \"%{template_id}\" is no longer available. Please select a different template."
|
|
34
34
|
action_view_frontend: View
|
|
35
35
|
cta_actions: Actions
|
|
36
|
+
sitemap_export_cta: Export sitemap (CSV)
|
|
37
|
+
sitemap_export_title: Sitemap CSV export
|
|
38
|
+
sitemap_export_message: "%{count} pages exported"
|
|
39
|
+
sitemap_export_failed: Failed to start sitemap export
|
|
36
40
|
translations_title: Translations
|
|
37
41
|
translation_no_other_locales: No other locales are configured.
|
|
38
42
|
translation_link_cta: Link
|
|
@@ -70,8 +74,12 @@ en:
|
|
|
70
74
|
component_clone_cta: Clone
|
|
71
75
|
component_clone_from: "Clone from %{lang}"
|
|
72
76
|
component_clone_confirm: "Replace this component's fields with the ones from the %{lang} page?"
|
|
77
|
+
component_clone_translate_from: "Clone & translate from %{lang}"
|
|
78
|
+
component_clone_translate_confirm: "Replace this component's fields with translated copies from the %{lang} page?"
|
|
73
79
|
component_cloned: Component cloned successfully
|
|
74
80
|
component_clone_failed: Failed to clone component
|
|
81
|
+
component_clone_translate_failed: Fields were cloned, but failed to start translation
|
|
82
|
+
component_clone_translated: Fields translated successfully
|
|
75
83
|
component_state_saving: Updating...
|
|
76
84
|
component_state_saved: State updated
|
|
77
85
|
component_state_save_error: Failed to update state
|
|
@@ -86,19 +94,35 @@ en:
|
|
|
86
94
|
fields_component_missing_alert_html: "Component <strong>%{component_id}</strong> not found. The component configuration may have been removed."
|
|
87
95
|
fields_previously_saved_data: "Previously saved data:"
|
|
88
96
|
field_file_remove: Remove
|
|
89
|
-
field_file_remove_pending: File will be removed on save
|
|
90
|
-
field_file_remove_undo: Undo
|
|
91
|
-
field_file_new_badge: New
|
|
92
|
-
field_gallery_empty: No images yet. Add images below.
|
|
93
|
-
field_gallery_add_images: Add images
|
|
94
|
-
field_gallery_drag_to_reorder: Drag to reorder
|
|
95
|
-
field_image_remove: Remove
|
|
96
|
-
field_image_remove_pending: Image will be removed on save
|
|
97
|
-
field_image_new_badge: New
|
|
98
|
-
field_video_remove: Remove
|
|
99
|
-
field_video_remove_pending: Video will be removed on save
|
|
100
|
-
field_video_new_badge: New
|
|
101
97
|
field_multiselect_hint: Hold Ctrl/Cmd to select multiple
|
|
98
|
+
media_index_title: Media
|
|
99
|
+
media_create_title: Upload media
|
|
100
|
+
media_create_cta: Upload
|
|
101
|
+
media_update_title: Edit media
|
|
102
|
+
media_alt_text: Alt text
|
|
103
|
+
media_alt_text_regenerate: Regenerate with AI
|
|
104
|
+
media_alt_text_regenerate_confirm: "This will overwrite the current alt text in every language with a new AI-generated one. Continue?"
|
|
105
|
+
media_alt_text_regenerated: Alt text regenerated
|
|
106
|
+
media_alt_text_regenerate_unavailable: Alt text can only be regenerated for images, and only when an LLM is configured
|
|
107
|
+
media_alt_text_regenerate_failed: Failed to start alt text regeneration
|
|
108
|
+
media_created: Media uploaded successfully
|
|
109
|
+
media_updated: Media updated successfully
|
|
110
|
+
media_deleted: Media deleted successfully
|
|
111
|
+
media_delete_in_use: "Cannot delete: used in %{count} place(s)"
|
|
112
|
+
media_delete_failed: Failed to delete media
|
|
113
|
+
media_choose_from_library: Choose from library
|
|
114
|
+
media_picker_title: Media library
|
|
115
|
+
media_picker_tab_browse: Browse library
|
|
116
|
+
media_picker_tab_upload: Upload new
|
|
117
|
+
media_picker_empty: No media found.
|
|
118
|
+
media_picker_confirm: Confirm selection
|
|
119
|
+
media_picker_selected_label: selected
|
|
120
|
+
media_search_placeholder: Search media...
|
|
121
|
+
media_upload_cta: Upload
|
|
122
|
+
media_type_all: All
|
|
123
|
+
media_type_image: Images
|
|
124
|
+
media_type_video: Videos
|
|
125
|
+
media_type_file: Files
|
|
102
126
|
field_select_placeholder: "-- Select --"
|
|
103
127
|
field_text_toolbar_bold: Bold
|
|
104
128
|
field_text_toolbar_italic: Italic
|
data/config/locales/it.yml
CHANGED
|
@@ -33,6 +33,10 @@ it:
|
|
|
33
33
|
form_template_unavailable: "Il template \"%{template_id}\" non e piu disponibile. Seleziona un template diverso."
|
|
34
34
|
action_view_frontend: Anteprima
|
|
35
35
|
cta_actions: Azioni
|
|
36
|
+
sitemap_export_cta: Esporta sitemap (CSV)
|
|
37
|
+
sitemap_export_title: Esportazione sitemap CSV
|
|
38
|
+
sitemap_export_message: "%{count} pagine esportate"
|
|
39
|
+
sitemap_export_failed: Impossibile avviare l'esportazione della sitemap
|
|
36
40
|
translations_title: Traduzioni
|
|
37
41
|
translation_no_other_locales: Nessuna altra lingua configurata.
|
|
38
42
|
translation_link_cta: Collega
|
|
@@ -70,8 +74,12 @@ it:
|
|
|
70
74
|
component_clone_cta: Clona
|
|
71
75
|
component_clone_from: "Clona da %{lang}"
|
|
72
76
|
component_clone_confirm: "Sostituire i campi di questa componente con quelli della pagina in %{lang}?"
|
|
77
|
+
component_clone_translate_from: "Clona e traduci da %{lang}"
|
|
78
|
+
component_clone_translate_confirm: "Sostituire i campi di questa componente con copie tradotte dalla pagina in %{lang}?"
|
|
73
79
|
component_cloned: Componente clonata con successo
|
|
74
80
|
component_clone_failed: Impossibile clonare la componente
|
|
81
|
+
component_clone_translate_failed: I campi sono stati clonati, ma non è stato possibile avviare la traduzione
|
|
82
|
+
component_clone_translated: Campi tradotti con successo
|
|
75
83
|
component_state_saving: Aggiornamento in corso...
|
|
76
84
|
component_state_saved: Stato aggiornato
|
|
77
85
|
component_state_save_error: Errore durante aggiornamento stato
|
|
@@ -86,19 +94,35 @@ it:
|
|
|
86
94
|
fields_component_missing_alert_html: "Componente <strong>%{component_id}</strong> non trovato. La configurazione del componente potrebbe essere stata rimossa."
|
|
87
95
|
fields_previously_saved_data: "Dati salvati in precedenza:"
|
|
88
96
|
field_file_remove: Rimuovi
|
|
89
|
-
field_file_remove_pending: Il file verra rimosso al salvataggio
|
|
90
|
-
field_file_remove_undo: Annulla
|
|
91
|
-
field_file_new_badge: Nuovo
|
|
92
|
-
field_gallery_empty: Nessuna immagine presente. Aggiungi immagini qui sotto.
|
|
93
|
-
field_gallery_add_images: Aggiungi immagini
|
|
94
|
-
field_gallery_drag_to_reorder: Trascina per riordinare
|
|
95
|
-
field_image_remove: Rimuovi
|
|
96
|
-
field_image_remove_pending: L'immagine verra rimossa al salvataggio
|
|
97
|
-
field_image_new_badge: Nuovo
|
|
98
|
-
field_video_remove: Rimuovi
|
|
99
|
-
field_video_remove_pending: Il video verra rimosso al salvataggio
|
|
100
|
-
field_video_new_badge: Nuovo
|
|
101
97
|
field_multiselect_hint: Tieni premuto Ctrl/Cmd per selezionare piu opzioni
|
|
98
|
+
media_index_title: Media
|
|
99
|
+
media_create_title: Carica media
|
|
100
|
+
media_create_cta: Carica
|
|
101
|
+
media_update_title: Modifica media
|
|
102
|
+
media_alt_text: Testo alternativo
|
|
103
|
+
media_alt_text_regenerate: Rigenera con AI
|
|
104
|
+
media_alt_text_regenerate_confirm: "Il testo alternativo attuale in ogni lingua verrà sostituito con uno generato dall'AI. Continuare?"
|
|
105
|
+
media_alt_text_regenerated: Testo alternativo rigenerato
|
|
106
|
+
media_alt_text_regenerate_unavailable: Il testo alternativo può essere rigenerato solo per le immagini e solo se è configurato un LLM
|
|
107
|
+
media_alt_text_regenerate_failed: Impossibile avviare la rigenerazione del testo alternativo
|
|
108
|
+
media_created: Media caricato con successo
|
|
109
|
+
media_updated: Media aggiornato con successo
|
|
110
|
+
media_deleted: Media eliminato con successo
|
|
111
|
+
media_delete_in_use: "Impossibile eliminare: usato in %{count} punto/i"
|
|
112
|
+
media_delete_failed: Impossibile eliminare il media
|
|
113
|
+
media_choose_from_library: Scegli dalla libreria
|
|
114
|
+
media_picker_title: Libreria media
|
|
115
|
+
media_picker_tab_browse: Sfoglia libreria
|
|
116
|
+
media_picker_tab_upload: Carica nuovo
|
|
117
|
+
media_picker_empty: Nessun media trovato.
|
|
118
|
+
media_picker_confirm: Conferma selezione
|
|
119
|
+
media_picker_selected_label: selezionati
|
|
120
|
+
media_search_placeholder: Cerca media...
|
|
121
|
+
media_upload_cta: Carica
|
|
122
|
+
media_type_all: Tutti
|
|
123
|
+
media_type_image: Immagini
|
|
124
|
+
media_type_video: Video
|
|
125
|
+
media_type_file: File
|
|
102
126
|
field_select_placeholder: "-- Seleziona --"
|
|
103
127
|
field_text_toolbar_bold: Grassetto
|
|
104
128
|
field_text_toolbar_italic: Corsivo
|
data/config/routes.rb
CHANGED
|
@@ -10,6 +10,7 @@ LatoCms::Engine.routes.draw do
|
|
|
10
10
|
|
|
11
11
|
scope :pages do
|
|
12
12
|
get '', to: 'pages#index', as: :pages
|
|
13
|
+
post 'sitemap-export', to: 'pages#sitemap_export_action', as: :pages_sitemap_export_action
|
|
13
14
|
get 'create', to: 'pages#create', as: :pages_create
|
|
14
15
|
post 'create', to: 'pages#create_action', as: :pages_create_action
|
|
15
16
|
get ':id', to: 'pages#show', as: :pages_show
|
|
@@ -23,4 +24,15 @@ LatoCms::Engine.routes.draw do
|
|
|
23
24
|
delete ':id/translations', to: 'pages#unlink_translation_action', as: :pages_unlink_translation_action
|
|
24
25
|
delete ':id', to: 'pages#destroy_action', as: :pages_destroy_action
|
|
25
26
|
end
|
|
27
|
+
|
|
28
|
+
scope :media do
|
|
29
|
+
get '', to: 'media#index', as: :media
|
|
30
|
+
get 'picker', to: 'media#picker_action', as: :media_picker_action
|
|
31
|
+
get 'create', to: 'media#create', as: :media_create
|
|
32
|
+
post 'create', to: 'media#create_action', as: :media_create_action
|
|
33
|
+
get ':id/update', to: 'media#update', as: :media_update
|
|
34
|
+
patch ':id/update', to: 'media#update_action', as: :media_update_action
|
|
35
|
+
post ':id/regenerate-alt-text', to: 'media#regenerate_alt_text_action', as: :media_regenerate_alt_text_action
|
|
36
|
+
delete ':id', to: 'media#destroy_action', as: :media_destroy_action
|
|
37
|
+
end
|
|
26
38
|
end
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
class CreateLatoCmsMedia < ActiveRecord::Migration[8.1]
|
|
2
|
+
def change
|
|
3
|
+
create_table :lato_cms_media do |t|
|
|
4
|
+
t.string :name, null: false
|
|
5
|
+
t.string :alt_text
|
|
6
|
+
t.string :media_type, null: false, default: 'file'
|
|
7
|
+
t.timestamps
|
|
8
|
+
end
|
|
9
|
+
add_index :lato_cms_media, :media_type
|
|
10
|
+
end
|
|
11
|
+
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
class CreateLatoCmsPageFieldMedia < ActiveRecord::Migration[8.1]
|
|
2
|
+
def change
|
|
3
|
+
create_table :lato_cms_page_field_media do |t|
|
|
4
|
+
t.references :page_field, null: false, foreign_key: { to_table: :lato_cms_page_fields }
|
|
5
|
+
t.references :media, null: false, foreign_key: { to_table: :lato_cms_media }
|
|
6
|
+
t.integer :position, null: false, default: 0
|
|
7
|
+
t.timestamps
|
|
8
|
+
end
|
|
9
|
+
add_index :lato_cms_page_field_media, [:page_field_id, :media_id], unique: true
|
|
10
|
+
add_index :lato_cms_page_field_media, [:page_field_id, :position]
|
|
11
|
+
end
|
|
12
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
class ChangeLatoCmsMediaAltTextToTranslations < ActiveRecord::Migration[8.1]
|
|
2
|
+
class MigrationMedia < ActiveRecord::Base
|
|
3
|
+
self.table_name = 'lato_cms_media'
|
|
4
|
+
end
|
|
5
|
+
|
|
6
|
+
def up
|
|
7
|
+
change_column :lato_cms_media, :alt_text, :text
|
|
8
|
+
MigrationMedia.reset_column_information
|
|
9
|
+
|
|
10
|
+
locale = (LatoCms.config.locales.first || :en).to_s
|
|
11
|
+
MigrationMedia.where.not(alt_text: [nil, '']).find_each do |media|
|
|
12
|
+
media.update_column(:alt_text, { locale => media.alt_text }.to_json)
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def down
|
|
17
|
+
MigrationMedia.reset_column_information
|
|
18
|
+
|
|
19
|
+
MigrationMedia.find_each do |media|
|
|
20
|
+
translations = JSON.parse(media.alt_text.presence || '{}') rescue {}
|
|
21
|
+
media.update_column(:alt_text, translations.values.compact.first)
|
|
22
|
+
end
|
|
23
|
+
change_column :lato_cms_media, :alt_text, :string
|
|
24
|
+
end
|
|
25
|
+
end
|
data/lib/lato_cms/config.rb
CHANGED
|
@@ -3,7 +3,7 @@ module LatoCms
|
|
|
3
3
|
# This class contains the default configuration of the engine.
|
|
4
4
|
##
|
|
5
5
|
class Config
|
|
6
|
-
attr_accessor :locales, :templates_path, :admin_roles
|
|
6
|
+
attr_accessor :locales, :templates_path, :admin_roles, :llm_api_url, :llm_model, :llm_api_key
|
|
7
7
|
|
|
8
8
|
def initialize
|
|
9
9
|
@locales = [:en]
|
|
@@ -15,6 +15,17 @@ module LatoCms
|
|
|
15
15
|
# `operator` has read/edit access; `admin` also manages pages
|
|
16
16
|
# (create, update, delete) and translation links.
|
|
17
17
|
@admin_roles = { none: 0, operator: 1, admin: 2 }
|
|
18
|
+
|
|
19
|
+
# Optional OpenAI-compatible endpoint used to auto-generate alt text
|
|
20
|
+
# for uploaded images (see LatoCms::Media#generate_alt_text!). All
|
|
21
|
+
# three must be set for the feature to activate; unset by default.
|
|
22
|
+
@llm_api_url = nil
|
|
23
|
+
@llm_model = nil
|
|
24
|
+
@llm_api_key = nil
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def llm_configured?
|
|
28
|
+
llm_api_url.present? && llm_model.present? && llm_api_key.present?
|
|
18
29
|
end
|
|
19
30
|
end
|
|
20
31
|
end
|
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.
|
|
4
|
+
version: 3.1.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Gregorio Galante
|
|
@@ -79,6 +79,20 @@ dependencies:
|
|
|
79
79
|
- - ">="
|
|
80
80
|
- !ruby/object:Gem::Version
|
|
81
81
|
version: '1.2'
|
|
82
|
+
- !ruby/object:Gem::Dependency
|
|
83
|
+
name: ruby-vips
|
|
84
|
+
requirement: !ruby/object:Gem::Requirement
|
|
85
|
+
requirements:
|
|
86
|
+
- - "~>"
|
|
87
|
+
- !ruby/object:Gem::Version
|
|
88
|
+
version: '2.0'
|
|
89
|
+
type: :runtime
|
|
90
|
+
prerelease: false
|
|
91
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
92
|
+
requirements:
|
|
93
|
+
- - "~>"
|
|
94
|
+
- !ruby/object:Gem::Version
|
|
95
|
+
version: '2.0'
|
|
82
96
|
description: A Rails engine to manage application users on Lato projects!
|
|
83
97
|
email:
|
|
84
98
|
- me@gregoriogalante.com
|
|
@@ -96,26 +110,40 @@ files:
|
|
|
96
110
|
- app/assets/javascripts/lato_cms/controllers/lato_cms_component_accordion_controller.js
|
|
97
111
|
- app/assets/javascripts/lato_cms/controllers/lato_cms_component_form_controller.js
|
|
98
112
|
- app/assets/javascripts/lato_cms/controllers/lato_cms_component_toggle_controller.js
|
|
99
|
-
- app/assets/javascripts/lato_cms/controllers/lato_cms_file_field_controller.js
|
|
100
|
-
- app/assets/javascripts/lato_cms/controllers/lato_cms_gallery_field_controller.js
|
|
101
|
-
- app/assets/javascripts/lato_cms/controllers/lato_cms_image_field_controller.js
|
|
102
113
|
- app/assets/javascripts/lato_cms/controllers/lato_cms_json_field_controller.js
|
|
114
|
+
- app/assets/javascripts/lato_cms/controllers/lato_cms_media_field_controller.js
|
|
115
|
+
- app/assets/javascripts/lato_cms/controllers/lato_cms_media_picker_controller.js
|
|
116
|
+
- app/assets/javascripts/lato_cms/controllers/lato_cms_media_reorder_controller.js
|
|
103
117
|
- app/assets/javascripts/lato_cms/controllers/lato_cms_page_preview_controller.js
|
|
118
|
+
- app/assets/javascripts/lato_cms/controllers/lato_cms_reload_on_modal_close_controller.js
|
|
104
119
|
- app/assets/javascripts/lato_cms/controllers/lato_cms_repeater_controller.js
|
|
105
120
|
- app/assets/javascripts/lato_cms/controllers/lato_cms_text_field_controller.js
|
|
106
|
-
- app/assets/javascripts/lato_cms/controllers/lato_cms_video_field_controller.js
|
|
107
121
|
- app/assets/stylesheets/lato_cms/application.scss
|
|
108
122
|
- app/controllers/lato_cms/api/pages_controller.rb
|
|
109
123
|
- app/controllers/lato_cms/application_controller.rb
|
|
124
|
+
- app/controllers/lato_cms/media_controller.rb
|
|
110
125
|
- app/controllers/lato_cms/pages_controller.rb
|
|
111
126
|
- app/helpers/lato_cms/application_helper.rb
|
|
127
|
+
- app/helpers/lato_cms/media_helper.rb
|
|
112
128
|
- app/helpers/lato_cms/pages_helper.rb
|
|
113
129
|
- app/jobs/lato_cms/application_job.rb
|
|
130
|
+
- app/jobs/lato_cms/export_sitemap_csv_job.rb
|
|
131
|
+
- app/jobs/lato_cms/generate_alt_text_job.rb
|
|
114
132
|
- app/jobs/lato_cms/generate_video_poster_job.rb
|
|
133
|
+
- app/jobs/lato_cms/translate_component_fields_job.rb
|
|
115
134
|
- app/mailers/lato_cms/application_mailer.rb
|
|
135
|
+
- app/models/lato_cms/llm_client.rb
|
|
136
|
+
- app/models/lato_cms/media.rb
|
|
116
137
|
- app/models/lato_cms/page.rb
|
|
117
138
|
- app/models/lato_cms/page_field.rb
|
|
139
|
+
- app/models/lato_cms/page_field_media.rb
|
|
118
140
|
- app/models/lato_cms/template_manager.rb
|
|
141
|
+
- app/views/lato_cms/media/_form_create.html.erb
|
|
142
|
+
- app/views/lato_cms/media/_form_update.html.erb
|
|
143
|
+
- app/views/lato_cms/media/create.html.erb
|
|
144
|
+
- app/views/lato_cms/media/index.html.erb
|
|
145
|
+
- app/views/lato_cms/media/picker_action.html.erb
|
|
146
|
+
- app/views/lato_cms/media/update.html.erb
|
|
119
147
|
- app/views/lato_cms/pages/_component_accordion.html.erb
|
|
120
148
|
- app/views/lato_cms/pages/_fields_editor.html.erb
|
|
121
149
|
- app/views/lato_cms/pages/_form_create.html.erb
|
|
@@ -132,6 +160,8 @@ files:
|
|
|
132
160
|
- app/views/lato_cms/pages/fields/_gallery.html.erb
|
|
133
161
|
- app/views/lato_cms/pages/fields/_image.html.erb
|
|
134
162
|
- app/views/lato_cms/pages/fields/_json.html.erb
|
|
163
|
+
- app/views/lato_cms/pages/fields/_media_field.html.erb
|
|
164
|
+
- app/views/lato_cms/pages/fields/_media_item.html.erb
|
|
135
165
|
- app/views/lato_cms/pages/fields/_multiselect.html.erb
|
|
136
166
|
- app/views/lato_cms/pages/fields/_number.html.erb
|
|
137
167
|
- app/views/lato_cms/pages/fields/_select.html.erb
|
|
@@ -152,6 +182,9 @@ files:
|
|
|
152
182
|
- db/migrate/20260323171241_create_lato_cms_page_fields.rb
|
|
153
183
|
- db/migrate/20260705000000_add_translation_group_id_to_lato_cms_pages.rb
|
|
154
184
|
- db/migrate/20260712092241_change_lato_cms_admin_to_lato_cms_admin_role_on_lato_user.rb
|
|
185
|
+
- db/migrate/20260728175413_create_lato_cms_media.rb
|
|
186
|
+
- db/migrate/20260728175414_create_lato_cms_page_field_media.rb
|
|
187
|
+
- db/migrate/20260729120000_change_lato_cms_media_alt_text_to_translations.rb
|
|
155
188
|
- lib/lato_cms.rb
|
|
156
189
|
- lib/lato_cms/config.rb
|
|
157
190
|
- lib/lato_cms/engine.rb
|