lato_cms 3.4.0 → 3.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +137 -9
  3. data/app/assets/javascripts/lato_cms/controllers/lato_cms_advanced_editor_controller.js +58 -6
  4. data/app/assets/javascripts/lato_cms/controllers/lato_cms_color_field_controller.js +11 -1
  5. data/app/assets/javascripts/lato_cms/controllers/lato_cms_component_form_controller.js +48 -5
  6. data/app/assets/javascripts/lato_cms/controllers/lato_cms_component_toggle_controller.js +6 -0
  7. data/app/assets/javascripts/lato_cms/controllers/lato_cms_json_field_controller.js +3 -2
  8. data/app/assets/javascripts/lato_cms/controllers/lato_cms_media_field_controller.js +40 -2
  9. data/app/assets/javascripts/lato_cms/controllers/lato_cms_media_picker_controller.js +6 -5
  10. data/app/assets/javascripts/lato_cms/controllers/lato_cms_media_reorder_controller.js +25 -0
  11. data/app/assets/javascripts/lato_cms/controllers/lato_cms_page_preview_controller.js +6 -1
  12. data/app/assets/javascripts/lato_cms/controllers/lato_cms_repeater_controller.js +8 -0
  13. data/app/assets/javascripts/lato_cms/controllers/lato_cms_text_field_controller.js +2 -0
  14. data/app/assets/javascripts/lato_cms/controllers/lato_cms_upload_controller.js +4 -1
  15. data/app/assets/stylesheets/lato_cms/application.scss +57 -0
  16. data/app/controllers/lato_cms/application_controller.rb +23 -1
  17. data/app/controllers/lato_cms/media_controller.rb +4 -2
  18. data/app/controllers/lato_cms/pages_controller.rb +51 -22
  19. data/app/helpers/lato_cms/media_helper.rb +1 -1
  20. data/app/helpers/lato_cms/pages_helper.rb +15 -12
  21. data/app/jobs/lato_cms/export_sitemap_csv_job.rb +8 -1
  22. data/app/models/lato_cms/llm_client.rb +11 -0
  23. data/app/models/lato_cms/media.rb +41 -11
  24. data/app/models/lato_cms/page.rb +45 -7
  25. data/app/models/lato_cms/page_field.rb +64 -4
  26. data/app/models/lato_cms/template_manager.rb +49 -3
  27. data/app/views/lato_cms/media/_form_update.html.erb +9 -7
  28. data/app/views/lato_cms/media/_translations_field.html.erb +8 -5
  29. data/app/views/lato_cms/media/_upload_progress.html.erb +4 -2
  30. data/app/views/lato_cms/media/picker_action.html.erb +21 -14
  31. data/app/views/lato_cms/media/show.html.erb +2 -1
  32. data/app/views/lato_cms/pages/_component_accordion.html.erb +14 -8
  33. data/app/views/lato_cms/pages/_form_update.html.erb +5 -0
  34. data/app/views/lato_cms/pages/_repeater_item.html.erb +6 -4
  35. data/app/views/lato_cms/pages/_translations.html.erb +2 -1
  36. data/app/views/lato_cms/pages/fields/_color.html.erb +14 -6
  37. data/app/views/lato_cms/pages/fields/_json.html.erb +5 -2
  38. data/app/views/lato_cms/pages/fields/_media_field.html.erb +12 -5
  39. data/app/views/lato_cms/pages/fields/_media_item.html.erb +14 -3
  40. data/app/views/lato_cms/pages/fields/_text.html.erb +31 -22
  41. data/app/views/lato_cms/pages/index.html.erb +3 -2
  42. data/app/views/lato_cms/pages/show.html.erb +26 -9
  43. data/config/locales/en.yml +27 -1
  44. data/config/locales/it.yml +27 -1
  45. data/lib/lato_cms/config.rb +26 -1
  46. data/lib/lato_cms/version.rb +1 -1
  47. data/lib/tasks/lato_cms_tasks.rake +18 -11
  48. metadata +1 -15
@@ -4,7 +4,8 @@
4
4
  <% input_name = lato_cms_field_input_name(field_id, input_name_prefix: local_assigns[:input_name_prefix]) %>
5
5
  <% input_id = lato_cms_field_dom_id(field_id, dom_id_prefix: local_assigns[:dom_id_prefix]) %>
6
6
 
7
- <label class="form-label" for="<%= input_id %>">
7
+ <%# Names the editable area through aria-labelledby: `for` cannot target a contenteditable, and the hidden input is not focusable. %>
8
+ <label class="form-label" id="<%= input_id %>_label">
8
9
  <%= label %><%= ' *' if required %>
9
10
  </label>
10
11
 
@@ -13,59 +14,66 @@
13
14
  data-lato-cms-text-field-attributes-value="<%= LatoCms::PageField::RICH_TEXT_ATTRIBUTES.join(' ') %>">
14
15
 
15
16
  <%# ── Toolbar ──────────────────────────────────────────────────────────── %>
16
- <div class="lato-cms-wysiwyg-toolbar border border-bottom-0 rounded-top bg-light px-2 py-1 d-flex flex-wrap gap-1 align-items-center"
17
+ <div role="toolbar" aria-label="<%= t('lato_cms.field_text_toolbar_label') %>" aria-controls="<%= input_id %>_editor"
18
+ class="lato-cms-wysiwyg-toolbar border border-bottom-0 rounded-top bg-light px-2 py-1 d-flex flex-wrap gap-1 align-items-center"
17
19
  data-lato-cms-text-field-target="toolbar">
18
- <button type="button" class="btn btn-sm btn-outline-secondary py-0 px-1" title="<%= t('lato_cms.field_text_toolbar_bold') %>"
20
+ <button type="button" class="btn btn-sm btn-outline-secondary py-0 px-1" title="<%= t('lato_cms.field_text_toolbar_bold') %>" aria-label="<%= t('lato_cms.field_text_toolbar_bold') %>"
19
21
  data-action="click->lato-cms-text-field#bold">
20
- <i class="bi bi-type-bold"></i>
22
+ <i class="bi bi-type-bold" aria-hidden="true"></i>
21
23
  </button>
22
- <button type="button" class="btn btn-sm btn-outline-secondary py-0 px-1" title="<%= t('lato_cms.field_text_toolbar_italic') %>"
24
+ <button type="button" class="btn btn-sm btn-outline-secondary py-0 px-1" title="<%= t('lato_cms.field_text_toolbar_italic') %>" aria-label="<%= t('lato_cms.field_text_toolbar_italic') %>"
23
25
  data-action="click->lato-cms-text-field#italic">
24
- <i class="bi bi-type-italic"></i>
26
+ <i class="bi bi-type-italic" aria-hidden="true"></i>
25
27
  </button>
26
- <button type="button" class="btn btn-sm btn-outline-secondary py-0 px-1" title="<%= t('lato_cms.field_text_toolbar_underline') %>"
28
+ <button type="button" class="btn btn-sm btn-outline-secondary py-0 px-1" title="<%= t('lato_cms.field_text_toolbar_underline') %>" aria-label="<%= t('lato_cms.field_text_toolbar_underline') %>"
27
29
  data-action="click->lato-cms-text-field#underline">
28
- <i class="bi bi-type-underline"></i>
30
+ <i class="bi bi-type-underline" aria-hidden="true"></i>
29
31
  </button>
30
- <button type="button" class="btn btn-sm btn-outline-secondary py-0 px-1" title="<%= t('lato_cms.field_text_toolbar_strikethrough') %>"
32
+ <button type="button" class="btn btn-sm btn-outline-secondary py-0 px-1" title="<%= t('lato_cms.field_text_toolbar_strikethrough') %>" aria-label="<%= t('lato_cms.field_text_toolbar_strikethrough') %>"
31
33
  data-action="click->lato-cms-text-field#strikethrough">
32
- <i class="bi bi-type-strikethrough"></i>
34
+ <i class="bi bi-type-strikethrough" aria-hidden="true"></i>
33
35
  </button>
34
36
  <span class="vr mx-1"></span>
35
- <button type="button" class="btn btn-sm btn-outline-secondary py-0 px-1" title="<%= t('lato_cms.field_text_toolbar_align_left') %>"
37
+ <button type="button" class="btn btn-sm btn-outline-secondary py-0 px-1" title="<%= t('lato_cms.field_text_toolbar_align_left') %>" aria-label="<%= t('lato_cms.field_text_toolbar_align_left') %>"
36
38
  data-action="click->lato-cms-text-field#alignLeft">
37
- <i class="bi bi-text-left"></i>
39
+ <i class="bi bi-text-left" aria-hidden="true"></i>
38
40
  </button>
39
- <button type="button" class="btn btn-sm btn-outline-secondary py-0 px-1" title="<%= t('lato_cms.field_text_toolbar_align_center') %>"
41
+ <button type="button" class="btn btn-sm btn-outline-secondary py-0 px-1" title="<%= t('lato_cms.field_text_toolbar_align_center') %>" aria-label="<%= t('lato_cms.field_text_toolbar_align_center') %>"
40
42
  data-action="click->lato-cms-text-field#alignCenter">
41
- <i class="bi bi-text-center"></i>
43
+ <i class="bi bi-text-center" aria-hidden="true"></i>
42
44
  </button>
43
- <button type="button" class="btn btn-sm btn-outline-secondary py-0 px-1" title="<%= t('lato_cms.field_text_toolbar_align_right') %>"
45
+ <button type="button" class="btn btn-sm btn-outline-secondary py-0 px-1" title="<%= t('lato_cms.field_text_toolbar_align_right') %>" aria-label="<%= t('lato_cms.field_text_toolbar_align_right') %>"
44
46
  data-action="click->lato-cms-text-field#alignRight">
45
- <i class="bi bi-text-right"></i>
47
+ <i class="bi bi-text-right" aria-hidden="true"></i>
46
48
  </button>
47
49
  <span class="vr mx-1"></span>
48
- <button type="button" class="btn btn-sm btn-outline-secondary py-0 px-1" title="<%= t('lato_cms.field_text_toolbar_unordered_list') %>"
50
+ <button type="button" class="btn btn-sm btn-outline-secondary py-0 px-1" title="<%= t('lato_cms.field_text_toolbar_unordered_list') %>" aria-label="<%= t('lato_cms.field_text_toolbar_unordered_list') %>"
49
51
  data-action="click->lato-cms-text-field#insertUnorderedList">
50
- <i class="bi bi-list-ul"></i>
52
+ <i class="bi bi-list-ul" aria-hidden="true"></i>
51
53
  </button>
52
- <button type="button" class="btn btn-sm btn-outline-secondary py-0 px-1" title="<%= t('lato_cms.field_text_toolbar_ordered_list') %>"
54
+ <button type="button" class="btn btn-sm btn-outline-secondary py-0 px-1" title="<%= t('lato_cms.field_text_toolbar_ordered_list') %>" aria-label="<%= t('lato_cms.field_text_toolbar_ordered_list') %>"
53
55
  data-action="click->lato-cms-text-field#insertOrderedList">
54
- <i class="bi bi-list-ol"></i>
56
+ <i class="bi bi-list-ol" aria-hidden="true"></i>
55
57
  </button>
56
58
 
57
59
  <%# ── Source toggle ────────────────────────────────────────────────── %>
58
60
  <span class="vr mx-1 ms-auto"></span>
59
- <button type="button" class="btn btn-sm btn-outline-secondary py-0 px-1" title="<%= t('lato_cms.field_text_toolbar_toggle_html_source') %>"
61
+ <button type="button" class="btn btn-sm btn-outline-secondary py-0 px-1" title="<%= t('lato_cms.field_text_toolbar_toggle_html_source') %>" aria-label="<%= t('lato_cms.field_text_toolbar_toggle_html_source') %>"
60
62
  data-action="click->lato-cms-text-field#toggleSource"
63
+ aria-pressed="false"
61
64
  data-lato-cms-text-field-target="sourceBtn">
62
- <i class="bi bi-code-slash"></i>
65
+ <i class="bi bi-code-slash" aria-hidden="true"></i>
63
66
  </button>
64
67
  </div>
65
68
 
66
69
  <%# ── Editable area (WYSIWYG) ─────────────────────────────────────────── %>
67
70
  <div class="form-control lato-cms-wysiwyg-editor rounded-0 rounded-bottom"
68
71
  contenteditable="true"
72
+ id="<%= input_id %>_editor"
73
+ role="textbox"
74
+ aria-multiline="true"
75
+ aria-labelledby="<%= input_id %>_label"
76
+ <%= 'aria-required="true"'.html_safe if required %>
69
77
  style="min-height: 150px; overflow-y: auto; line-height: 1.6;"
70
78
  data-lato-cms-text-field-target="editor"><%= current_value.html_safe %></div>
71
79
 
@@ -73,6 +81,7 @@
73
81
  <textarea class="form-control lato-cms-wysiwyg-source rounded-0 rounded-bottom font-monospace d-none"
74
82
  style="min-height: 150px; font-size: 0.8rem; resize: vertical; white-space: pre;"
75
83
  spellcheck="false"
84
+ aria-label="<%= label %> — <%= t('lato_cms.field_text_source_label') %>"
76
85
  data-lato-cms-text-field-target="source"></textarea>
77
86
 
78
87
  <%# ── Hidden input submitted with the form ───────────────────────────── %>
@@ -12,8 +12,9 @@
12
12
  <% end %>
13
13
 
14
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>
15
+ <button type="button" class="btn btn-sm btn-outline-secondary dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false"
16
+ aria-label="<%= t('lato_cms.cta_actions') %>">
17
+ <i class="bi bi-three-dots" aria-hidden="true"></i>
17
18
  </button>
18
19
  <ul class="dropdown-menu dropdown-menu-end">
19
20
  <li>
@@ -1,19 +1,23 @@
1
- <%= lato_page_head @page.title, [
1
+ <%# h(): the title is user content and lato_page_head renders it raw. %>
2
+ <%= lato_page_head h(@page.title), [
2
3
  { label: t('lato_cms.pages_index_title'), path: lato_cms.pages_path },
3
4
  { label: @page.title }
4
5
  ] %>
5
6
 
6
- <div data-controller="lato-cms-advanced-editor" data-lato-cms-advanced-editor-list-path-value="<%= lato_cms.pages_path %>">
7
+ <div data-controller="lato-cms-advanced-editor" data-lato-cms-advanced-editor-list-path-value="<%= lato_cms.pages_path %>"
8
+ data-lato-cms-advanced-editor-unsaved-message-value="<%= t('lato_cms.fields_unsaved_changes_confirm') %>">
7
9
  <div class="row align-items-stretch lato-cms-advanced-editor__row lato-cms-advanced-editor__row--active">
8
10
 
9
11
  <%# ── Preview column ─────────────────────────────────────────────────── %>
10
12
  <div class="col-12 col-lg-6 mb-4 lato-cms-advanced-editor__preview-col">
11
- <% if @page.frontend_url.present? %>
13
+ <% if (preview_url = @page.safe_frontend_url) %>
12
14
  <div class="card h-100" style="overflow: hidden;" data-controller="lato-cms-page-preview">
13
15
  <div class="card-header d-flex justify-content-between align-items-center py-2 gap-2">
14
- <div class="btn-group btn-group-sm" role="group">
16
+ <div class="btn-group btn-group-sm" role="group" aria-label="<%= t('lato_cms.show_preview_devices_label') %>">
15
17
  <button type="button" class="btn btn-outline-secondary active"
16
18
  title="<%= t('lato_cms.show_preview_desktop_tooltip') %>"
19
+ aria-label="<%= t('lato_cms.show_preview_desktop_tooltip') %>"
20
+ aria-pressed="true"
17
21
  data-controller="lato-tooltip"
18
22
  data-action="click->lato-cms-page-preview#setDevice"
19
23
  data-device="desktop"
@@ -22,6 +26,8 @@
22
26
  </button>
23
27
  <button type="button" class="btn btn-outline-secondary"
24
28
  title="<%= t('lato_cms.show_preview_tablet_tooltip') %>"
29
+ aria-label="<%= t('lato_cms.show_preview_tablet_tooltip') %>"
30
+ aria-pressed="false"
25
31
  data-controller="lato-tooltip"
26
32
  data-action="click->lato-cms-page-preview#setDevice"
27
33
  data-device="tablet"
@@ -30,6 +36,8 @@
30
36
  </button>
31
37
  <button type="button" class="btn btn-outline-secondary"
32
38
  title="<%= t('lato_cms.show_preview_mobile_tooltip') %>"
39
+ aria-label="<%= t('lato_cms.show_preview_mobile_tooltip') %>"
40
+ aria-pressed="false"
33
41
  data-controller="lato-tooltip"
34
42
  data-action="click->lato-cms-page-preview#setDevice"
35
43
  data-device="mobile"
@@ -40,6 +48,7 @@
40
48
  <div class="d-flex align-items-center gap-2">
41
49
  <div class="input-group input-group-sm" style="width: 130px;">
42
50
  <input type="number" class="form-control" min="320" max="3840" step="1"
51
+ aria-label="<%= t('lato_cms.show_preview_custom_width_tooltip') %>"
43
52
  placeholder="<%= t('lato_cms.show_preview_custom_width_placeholder') %>"
44
53
  title="<%= t('lato_cms.show_preview_custom_width_tooltip') %>"
45
54
  data-controller="lato-tooltip"
@@ -51,7 +60,13 @@
51
60
  </div>
52
61
  </div>
53
62
  <div class="lato-cms-page-preview__viewport" data-lato-cms-page-preview-target="viewport">
54
- <iframe src="<%= @page.frontend_url %>" data-lato-cms-page-preview-target="iframe"></iframe>
63
+ <%# sandbox keeps the frontend from navigating the panel or opening
64
+ dialogs on it; scripts/same-origin stay on so the site renders
65
+ and the preview can postMessage the open component. %>
66
+ <iframe src="<%= preview_url %>" title="<%= t('lato_cms.show_preview_title') %>"
67
+ sandbox="allow-scripts allow-same-origin allow-forms allow-popups"
68
+ referrerpolicy="strict-origin-when-cross-origin"
69
+ data-lato-cms-page-preview-target="iframe"></iframe>
55
70
  </div>
56
71
  </div>
57
72
  <% else %>
@@ -80,14 +95,15 @@
80
95
  <span class="vr"></span>
81
96
  <%= lato_cms_page_actions_dropdown(@page, show_edit: lato_cms_admin?, show_translations: lato_cms_admin?, show_delete: lato_cms_admin?, hide_show: true) %>
82
97
  <span class="vr"></span>
83
- <button class="btn btn-sm btn-outline-primary"
98
+ <button type="button" class="btn btn-sm btn-outline-primary"
84
99
  title="<%= t('lato_cms.show_hide_panel_tooltip') %>"
100
+ aria-label="<%= t('lato_cms.show_hide_panel_tooltip') %>"
85
101
  data-controller="lato-tooltip"
86
102
  data-action="click->lato-cms-advanced-editor#toggleSidebar">
87
103
  <i class="bi bi-layout-sidebar-reverse"></i>
88
104
  </button>
89
105
  <%= link_to lato_cms.pages_path, class: 'btn btn-sm btn-outline-danger',
90
- title: t('lato_cms.show_back_to_list_tooltip'), data: { controller: 'lato-tooltip' } do %>
106
+ title: t('lato_cms.show_back_to_list_tooltip'), 'aria-label': t('lato_cms.show_back_to_list_tooltip'), data: { controller: 'lato-tooltip' } do %>
91
107
  <i class="bi bi-box-arrow-left"></i>
92
108
  <% end %>
93
109
  </div>
@@ -106,7 +122,7 @@
106
122
  <% elsif !@page.template_available? %>
107
123
  <div class="alert alert-warning">
108
124
  <i class="bi bi-exclamation-triangle me-1"></i>
109
- <%= t('lato_cms.show_template_unavailable', template_id: @page.template_id) %>
125
+ <%= t('lato_cms.show_template_unavailable_html', template_id: @page.template_id) %>
110
126
  <% if lato_cms_admin? %>
111
127
  <%= link_to t('lato_cms.show_edit_settings_cta'), lato_cms.pages_update_path(@page), class: 'btn btn-sm btn-outline-warning ms-2',
112
128
  data: { lato_action_target: 'trigger', turbo_frame: dom_id(@page, 'form'), action_title: t('lato_cms.page_update_title') } %>
@@ -137,9 +153,10 @@
137
153
  </div>
138
154
 
139
155
  <%# ── Re-open sidebar tab (visible only in advanced mode + sidebar closed) %>
140
- <button class="lato-cms-advanced-editor__reopen-btn"
156
+ <button type="button" class="lato-cms-advanced-editor__reopen-btn"
141
157
  hidden
142
158
  title="<%= t('lato_cms.show_show_editor_panel_tooltip') %>"
159
+ aria-label="<%= t('lato_cms.show_show_editor_panel_tooltip') %>"
143
160
  data-controller="lato-tooltip"
144
161
  data-action="click->lato-cms-advanced-editor#toggleSidebar"
145
162
  data-lato-cms-advanced-editor-target="reopenBtn">
@@ -63,7 +63,7 @@ en:
63
63
  show_preview_custom_width_placeholder: Width
64
64
  show_no_template_title: No template assigned
65
65
  show_no_template_description: Assign a template to start editing fields.
66
- show_template_unavailable: "The template <strong>%{template_id}</strong> is no longer available. Please assign a different template."
66
+ show_template_unavailable_html: "The template <strong>%{template_id}</strong> is no longer available. Please assign a different template."
67
67
  show_previously_saved_fields: "Previously saved fields:"
68
68
  fields_component_not_found_badge: Component not found
69
69
  component_required_badge: Required
@@ -162,3 +162,29 @@ en:
162
162
  field_text_toolbar_unordered_list: Unordered list
163
163
  field_text_toolbar_ordered_list: Ordered list
164
164
  field_text_toolbar_toggle_html_source: Toggle HTML source
165
+ field_text_toolbar_label: Text formatting
166
+ field_text_source_label: HTML source
167
+ field_color_clear: Clear color
168
+ field_color_empty: No color
169
+ field_json_valid: Valid JSON
170
+ field_json_invalid: "Invalid JSON: %{error}"
171
+ field_render_error: "Field \"%{field_id}\" cannot be rendered. Check its configuration."
172
+ fields_saving: Saving...
173
+ fields_save_failed: Failed to save fields
174
+ fields_save_error: An error occurred while saving
175
+ fields_unsaved_changes_confirm: There are unsaved changes. Leave this page and lose them?
176
+ component_not_in_template: This component or one of its fields is not part of the page template
177
+ rate_limited: Too many requests, please wait a minute and try again.
178
+ cta_page_settings: Page settings
179
+ show_preview_title: Frontend preview
180
+ show_preview_devices_label: Preview size
181
+ form_template_change_warning: Fields saved under the current template are hidden if you switch template.
182
+ media_delete_confirm: Are you sure you want to delete this media?
183
+ media_content_type_not_allowed: "type %{content_type} is not allowed"
184
+ media_file_too_large: "is larger than the maximum size (%{size})"
185
+ media_search_label: Search media
186
+ media_search_cta: Search
187
+ media_move_before: Move before
188
+ media_move_after: Move after
189
+ media_field_select: "Select %{name}"
190
+ translation_select_label: "Page to link for %{locale}"
@@ -63,7 +63,7 @@ it:
63
63
  show_preview_custom_width_placeholder: Larghezza
64
64
  show_no_template_title: Nessun template assegnato
65
65
  show_no_template_description: Assegna un template per iniziare a modificare i campi.
66
- show_template_unavailable: "Il template <strong>%{template_id}</strong> non e piu disponibile. Assegna un template diverso."
66
+ show_template_unavailable_html: "Il template <strong>%{template_id}</strong> non e piu disponibile. Assegna un template diverso."
67
67
  show_previously_saved_fields: "Campi salvati in precedenza:"
68
68
  fields_component_not_found_badge: Componente non trovato
69
69
  component_required_badge: Obbligatoria
@@ -162,3 +162,29 @@ it:
162
162
  field_text_toolbar_unordered_list: Elenco puntato
163
163
  field_text_toolbar_ordered_list: Elenco numerato
164
164
  field_text_toolbar_toggle_html_source: Mostra/nascondi sorgente HTML
165
+ field_text_toolbar_label: Formattazione testo
166
+ field_text_source_label: Sorgente HTML
167
+ field_color_clear: Rimuovi colore
168
+ field_color_empty: Nessun colore
169
+ field_json_valid: JSON valido
170
+ field_json_invalid: "JSON non valido: %{error}"
171
+ field_render_error: "Il campo \"%{field_id}\" non può essere mostrato. Controlla la sua configurazione."
172
+ fields_saving: Salvataggio...
173
+ fields_save_failed: Salvataggio dei campi non riuscito
174
+ fields_save_error: Si è verificato un errore durante il salvataggio
175
+ fields_unsaved_changes_confirm: Ci sono modifiche non salvate. Uscire dalla pagina e perderle?
176
+ component_not_in_template: Questo componente o uno dei suoi campi non fa parte del template della pagina
177
+ rate_limited: Troppe richieste, attendi un minuto e riprova.
178
+ cta_page_settings: Impostazioni pagina
179
+ show_preview_title: Anteprima del frontend
180
+ show_preview_devices_label: Dimensione anteprima
181
+ form_template_change_warning: Se cambi template, i campi salvati con quello attuale non vengono più mostrati.
182
+ media_delete_confirm: Sei sicuro di voler eliminare questo media?
183
+ media_content_type_not_allowed: "di tipo %{content_type} non è consentito"
184
+ media_file_too_large: "supera la dimensione massima (%{size})"
185
+ media_search_label: Cerca media
186
+ media_search_cta: Cerca
187
+ media_move_before: Sposta prima
188
+ media_move_after: Sposta dopo
189
+ media_field_select: "Seleziona %{name}"
190
+ translation_select_label: "Pagina da collegare per %{locale}"
@@ -5,7 +5,7 @@ module LatoCms
5
5
  class Config
6
6
  attr_accessor :locales, :templates_path, :admin_roles, :media_url_mode, :llm_api_url, :llm_model,
7
7
  :llm_api_key, :llm_generate_alt_text, :llm_generate_title, :llm_alt_text_prompt,
8
- :llm_title_prompt
8
+ :llm_title_prompt, :media_allowed_content_types, :media_max_file_size, :llm_max_image_size
9
9
 
10
10
  # How the URLs of media files are built (see LatoCms::Media#url).
11
11
  # - :redirect — Rails answers 302 with a signed URL that expires; on a
@@ -70,12 +70,37 @@ module LatoCms
70
70
  # LLM_PROMPT_VARIABLES placeholders.
71
71
  @llm_alt_text_prompt = nil
72
72
  @llm_title_prompt = nil
73
+
74
+ # Server-side upload limits for the media library (the picker `accept`
75
+ # attribute is only a hint to the browser). Set either to nil to lift it.
76
+ @media_allowed_content_types = DEFAULT_MEDIA_ALLOWED_CONTENT_TYPES.dup
77
+ @media_max_file_size = 100.megabytes
78
+
79
+ # Images larger than this are not sent to the LLM: the whole file is
80
+ # read in memory and base64-encoded into the request.
81
+ @llm_max_image_size = 20.megabytes
73
82
  end
74
83
 
84
+ # Media uploads accepted by default: images, audio, video, PDFs, plain
85
+ # text/CSV, office documents and zip archives. HTML and scripts are left
86
+ # out on purpose. A trailing `*` matches any subtype.
87
+ DEFAULT_MEDIA_ALLOWED_CONTENT_TYPES = %w[
88
+ image/* video/* audio/*
89
+ application/pdf text/plain text/csv application/zip
90
+ application/msword application/vnd.ms-excel application/vnd.ms-powerpoint
91
+ application/vnd.openxmlformats-officedocument.* application/vnd.oasis.opendocument.*
92
+ ].freeze
93
+
75
94
  def media_url_proxy?
76
95
  media_url_mode.to_sym == :proxy
77
96
  end
78
97
 
98
+ def media_content_type_allowed?(content_type)
99
+ return true if media_allowed_content_types.nil?
100
+
101
+ Array(media_allowed_content_types).any? { |pattern| File.fnmatch?(pattern.to_s, content_type.to_s) }
102
+ end
103
+
79
104
  def llm_configured?
80
105
  llm_api_url.present? && llm_model.present? && llm_api_key.present?
81
106
  end
@@ -1,3 +1,3 @@
1
1
  module LatoCms
2
- VERSION = "3.4.0"
2
+ VERSION = "3.5.0"
3
3
  end
@@ -5,7 +5,7 @@ namespace :lato_cms do
5
5
  desc 'Install Lato CMS engine and create example templates'
6
6
  # Usage: rails lato_cms:install:application
7
7
  task application: :environment do
8
- base_path = Rails.root.join('config', 'lato_cms')
8
+ base_path = Rails.root.join(LatoCms.config.templates_path)
9
9
  templates_path = base_path.join('templates')
10
10
  components_path = base_path.join('components')
11
11
 
@@ -34,10 +34,8 @@ namespace :lato_cms do
34
34
  placeholder: "Enter a subtitle"
35
35
  image:
36
36
  name: Background Image
37
- type: file
37
+ type: image
38
38
  required: false
39
- settings:
40
- accept: "image/*"
41
39
  YAML
42
40
  puts "Created #{component_file}"
43
41
  else
@@ -105,17 +103,22 @@ namespace :lato_cms do
105
103
 
106
104
  abort "Component file already exists: #{file}" if File.exist?(file)
107
105
 
108
- valid_types = %w[string textarea file number date datetime boolean select multiselect color json text]
106
+ # `custom` is left out: it needs a `render` partial path, which a
107
+ # field_id:type pair cannot carry (see the custom_field task).
108
+ valid_types = LatoCms::PagesHelper::BUILTIN_FIELD_TYPES
109
109
 
110
- # Parse field pairs from extra args
110
+ # Parse field pairs from extra args. An unknown type aborts instead of
111
+ # silently becoming a string field.
111
112
  fields = {}
112
113
  (args.extras || []).each do |arg|
113
114
  parts = arg.split(':')
114
- next unless parts.length == 2
115
+ abort "Invalid field definition \"#{arg}\": use field_id:type" unless parts.length == 2
115
116
 
116
117
  field_id = parts[0].parameterize(separator: '_')
117
118
  field_type = parts[1]
118
- field_type = 'string' unless valid_types.include?(field_type)
119
+ unless valid_types.include?(field_type)
120
+ abort "Unsupported field type \"#{field_type}\" for #{field_id}. Supported: #{valid_types.join(', ')}"
121
+ end
119
122
 
120
123
  fields[field_id] = {
121
124
  'name' => field_id.humanize,
@@ -155,20 +158,24 @@ namespace :lato_cms do
155
158
  file = views_path.join("_#{field_id}.html.erb")
156
159
  abort "Custom field partial already exists: #{file}" if File.exist?(file)
157
160
 
161
+ # Names and ids come from the engine helpers so the field also works
162
+ # inside a repeater (where both carry the item prefix).
158
163
  File.write(file, <<~ERB)
159
164
  <% label = field_config['name'] || field_id.humanize %>
160
165
  <% required = field_config['required'] == true %>
161
166
  <% settings = field_config['settings'] || {} %>
162
167
  <% current_value = page_field&.value.to_s %>
168
+ <% input_name = lato_cms_field_input_name(field_id, input_name_prefix: local_assigns[:input_name_prefix]) %>
169
+ <% input_id = lato_cms_field_dom_id(field_id, dom_id_prefix: local_assigns[:dom_id_prefix]) %>
163
170
 
164
- <label class="form-label" for="fields_<%= field_id %>_value">
171
+ <label class="form-label" for="<%= input_id %>">
165
172
  <%= label %><%= ' *' if required %>
166
173
  </label>
167
174
 
168
175
  <input type="text"
169
176
  class="form-control"
170
- name="fields[<%= field_id %>][value]"
171
- id="fields_<%= field_id %>_value"
177
+ name="<%= input_name %>"
178
+ id="<%= input_id %>"
172
179
  value="<%= current_value %>"
173
180
  <%= 'required' if required %>
174
181
  placeholder="<%= settings['placeholder'] %>">
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.0
4
+ version: 3.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Gregorio Galante
@@ -57,20 +57,6 @@ dependencies:
57
57
  - - ">="
58
58
  - !ruby/object:Gem::Version
59
59
  version: 3.2.0
60
- - !ruby/object:Gem::Dependency
61
- name: jbuilder
62
- requirement: !ruby/object:Gem::Requirement
63
- requirements:
64
- - - ">="
65
- - !ruby/object:Gem::Version
66
- version: '0'
67
- type: :runtime
68
- prerelease: false
69
- version_requirements: !ruby/object:Gem::Requirement
70
- requirements:
71
- - - ">="
72
- - !ruby/object:Gem::Version
73
- version: '0'
74
60
  - !ruby/object:Gem::Dependency
75
61
  name: image_processing
76
62
  requirement: !ruby/object:Gem::Requirement