lato_cms 3.2.4 → 3.2.6
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 +5 -1
- data/app/assets/javascripts/lato_cms/controllers/lato_cms_page_preview_controller.js +13 -0
- data/app/controllers/lato_cms/media_controller.rb +1 -0
- data/app/controllers/lato_cms/pages_controller.rb +6 -1
- data/app/models/lato_cms/media.rb +20 -0
- data/app/views/lato_cms/media/_preview.html.erb +3 -1
- data/app/views/lato_cms/media/index.html.erb +15 -6
- data/app/views/lato_cms/media/show.html.erb +28 -23
- data/app/views/lato_cms/pages/_component_accordion.html.erb +11 -1
- data/app/views/lato_cms/pages/fields/_media_item.html.erb +7 -1
- data/config/locales/en.yml +2 -0
- data/config/locales/it.yml +2 -0
- data/lib/lato_cms/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: cdc7dae92de55a7a6e1e1290ee957bbf0b63fdf16404524c3f1803bd68123d0e
|
|
4
|
+
data.tar.gz: 1bc853f4846e25c41db7bddc0d53ac55f05699a06cf97a221abf6b7573458a7e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 0117c7af5986170ce154bee821263a5ac8da6759d71a8fd682624e59b84d31a4f368d15d8c12d05549cd38d08baee38aa5a09550cad85a2bfaf9e30071baa50a
|
|
7
|
+
data.tar.gz: f18ef5c8dcaf4c2a3e2cd66e031bd2853eca12e69fc9aab6afcbe93989a90de362948317a526a45c08fe0ba38a647220505f95282e7e2b1910bc32c864386ca0
|
|
@@ -123,8 +123,12 @@ export default class extends Controller {
|
|
|
123
123
|
}
|
|
124
124
|
|
|
125
125
|
previewMarkup (item) {
|
|
126
|
+
// Mirrors the server-rendered tile: with a poster nothing but that image is
|
|
127
|
+
// fetched until the admin hits play (a freshly uploaded video has none yet,
|
|
128
|
+
// its poster job runs in the background).
|
|
126
129
|
if (this.kindValue === 'video') {
|
|
127
|
-
|
|
130
|
+
const poster = item.posterUrl || ''
|
|
131
|
+
return `<video controls preload="${poster ? 'none' : 'metadata'}" poster="${poster}" class="lato-cms-media-field__player" src="${item.url}"></video>`
|
|
128
132
|
}
|
|
129
133
|
if (item.thumbnailUrl || item.url) {
|
|
130
134
|
return `<img src="${item.thumbnailUrl || item.url}" class="lato-cms-media-field__thumb">`
|
|
@@ -6,8 +6,16 @@ export default class extends Controller {
|
|
|
6
6
|
connect () {
|
|
7
7
|
this.handleComponentChange = this.handleComponentChange.bind(this)
|
|
8
8
|
this.handleIframeLoad = this.handleIframeLoad.bind(this)
|
|
9
|
+
this.handleMorph = this.handleMorph.bind(this)
|
|
9
10
|
|
|
10
11
|
document.addEventListener('lato-cms:component-change', this.handleComponentChange)
|
|
12
|
+
// Actions that re-render the editor server-side (cloning a component from
|
|
13
|
+
// another locale, editing the page settings) redirect back to this same
|
|
14
|
+
// URL, which Turbo treats as a page refresh and applies by morphing. Morph
|
|
15
|
+
// leaves an unchanged <iframe> exactly as it is — the element is never
|
|
16
|
+
// recreated, so the preview kept showing pre-clone content until a manual
|
|
17
|
+
// reload. The refresh is asked for explicitly here.
|
|
18
|
+
document.addEventListener('turbo:morph', this.handleMorph)
|
|
11
19
|
|
|
12
20
|
if (this.hasIframeTarget) {
|
|
13
21
|
this.iframeTarget.addEventListener('load', this.handleIframeLoad)
|
|
@@ -26,6 +34,7 @@ export default class extends Controller {
|
|
|
26
34
|
|
|
27
35
|
disconnect () {
|
|
28
36
|
document.removeEventListener('lato-cms:component-change', this.handleComponentChange)
|
|
37
|
+
document.removeEventListener('turbo:morph', this.handleMorph)
|
|
29
38
|
|
|
30
39
|
if (this.hasIframeTarget) {
|
|
31
40
|
this.iframeTarget.removeEventListener('load', this.handleIframeLoad)
|
|
@@ -109,6 +118,10 @@ export default class extends Controller {
|
|
|
109
118
|
this.postActiveComponent()
|
|
110
119
|
}
|
|
111
120
|
|
|
121
|
+
handleMorph () {
|
|
122
|
+
this.refresh()
|
|
123
|
+
}
|
|
124
|
+
|
|
112
125
|
syncInitiallyOpenComponent () {
|
|
113
126
|
const openCollapse = document.querySelector('#components-accordion .accordion-collapse.show')
|
|
114
127
|
if (!openCollapse) return
|
|
@@ -10,6 +10,7 @@ module LatoCms
|
|
|
10
10
|
# "Unused" = no page field references it at all: the only media that can
|
|
11
11
|
# be deleted, so it's worth being able to list just those.
|
|
12
12
|
media = media.where.missing(:page_field_media) if params[:usage] == 'unused'
|
|
13
|
+
media = media.missing_translation(params[:missing]) if LatoCms::Media::TRANSLATABLE_ATTRIBUTES.include?(params[:missing])
|
|
13
14
|
|
|
14
15
|
@media = lato_index_collection(
|
|
15
16
|
media.order(created_at: :desc),
|
|
@@ -114,7 +114,12 @@ module LatoCms
|
|
|
114
114
|
respond_to do |format|
|
|
115
115
|
if errors.empty?
|
|
116
116
|
format.html { redirect_to lato_cms.pages_show_path(@page), notice: t('lato_cms.fields_saved') }
|
|
117
|
-
|
|
117
|
+
# Only the saved component's fields: the editor repaints its media
|
|
118
|
+
# fields from this list, matching them by persisted field id, which is
|
|
119
|
+
# unique within a component but not across the page (two components
|
|
120
|
+
# can both have an "image" field). Handing it the whole page let a
|
|
121
|
+
# field adopt another component's media, and the next save stored it.
|
|
122
|
+
format.json { render json: { message: t('lato_cms.fields_saved'), fields: @page.fields.reload.select { |field| field.template_component_id == template_component_id.to_s }.map(&:as_json) } }
|
|
118
123
|
else
|
|
119
124
|
error_messages = errors.map { |e| "#{e[:field_id]}: #{e[:errors].join(', ')}" }.join('; ')
|
|
120
125
|
format.html { redirect_to lato_cms.pages_show_path(@page), alert: error_messages }
|
|
@@ -45,6 +45,26 @@ module LatoCms
|
|
|
45
45
|
|
|
46
46
|
scope :of_type, ->(type) { where(media_type: type) if type.present? }
|
|
47
47
|
|
|
48
|
+
# Media missing `attribute` (alt_text or title) in at least one configured
|
|
49
|
+
# locale — the ones worth going back to fill in. Alt text exists only for
|
|
50
|
+
# images, so that filter is scoped to them; a title is editable on every
|
|
51
|
+
# media type.
|
|
52
|
+
#
|
|
53
|
+
# Filtered in Ruby rather than SQL: the translations live as a JSON blob in
|
|
54
|
+
# a text column, and "blank in one locale" is not a predicate any adapter
|
|
55
|
+
# can express without guessing at that serialization (a stored empty string
|
|
56
|
+
# counts as missing too). Only the id and the one column are loaded.
|
|
57
|
+
scope :missing_translation, ->(attribute) {
|
|
58
|
+
attribute = attribute.to_s
|
|
59
|
+
next none unless TRANSLATABLE_ATTRIBUTES.include?(attribute)
|
|
60
|
+
|
|
61
|
+
candidates = attribute == "alt_text" ? where(media_type: "image") : all
|
|
62
|
+
locales = LatoCms.config.locales
|
|
63
|
+
incomplete = candidates.select(:id, attribute).reject { |media| locales.all? { |locale| media.public_send(attribute, locale).present? } }
|
|
64
|
+
|
|
65
|
+
candidates.where(id: incomplete.map(&:id))
|
|
66
|
+
}
|
|
67
|
+
|
|
48
68
|
# Hook picked up by lato's `lato_index_collection` in place of its generic
|
|
49
69
|
# search, whose SQL leaves column names unqualified. The Spaces association
|
|
50
70
|
# joins lato_spaces_groups, which also has a `name` column, so that generic
|
|
@@ -7,7 +7,9 @@
|
|
|
7
7
|
<%= image_tag media.preview_url, class: 'd-block mx-auto', style: 'max-width: 100%; max-height: 420px;', alt: media.alt_text %>
|
|
8
8
|
<% end %>
|
|
9
9
|
<% elsif media.video? %>
|
|
10
|
-
|
|
10
|
+
<%# Poster first, video bytes only on play (see the media field tile). %>
|
|
11
|
+
<video controls preload="<%= media.poster_url ? 'none' : 'metadata' %>" poster="<%= media.poster_url %>"
|
|
12
|
+
class="d-block w-100 rounded bg-black" style="max-height: 420px;" src="<%= media.url %>"></video>
|
|
11
13
|
<% end %>
|
|
12
14
|
|
|
13
15
|
<div class="d-flex align-items-center gap-2 mt-2">
|
|
@@ -1,12 +1,21 @@
|
|
|
1
1
|
<%= lato_page_head t('lato_cms.media_index_title'), [{ label: t('lato_cms.media_index_title') }] %>
|
|
2
2
|
|
|
3
3
|
<div class="card">
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
4
|
+
<%# Mutually exclusive shortcuts to the media worth acting on: the ones that
|
|
5
|
+
can be deleted, and the ones still missing a translation. %>
|
|
6
|
+
<% filters = [
|
|
7
|
+
{ label: t('lato_cms.media_filter_all'), icon: 'bi-images', params: {} },
|
|
8
|
+
{ label: t('lato_cms.media_filter_unused'), icon: 'bi-unlink', params: { usage: 'unused' } }
|
|
9
|
+
] + LatoCms::Media::TRANSLATABLE_ATTRIBUTES.map do |attribute|
|
|
10
|
+
{ label: t("lato_cms.media_filter_missing_#{attribute}"), icon: 'bi-translate', params: { missing: attribute } }
|
|
11
|
+
end %>
|
|
12
|
+
<% current = { usage: params[:usage].presence, missing: params[:missing].presence }.compact %>
|
|
13
|
+
|
|
14
|
+
<div class="card-header d-flex flex-wrap gap-2">
|
|
15
|
+
<% filters.each do |filter| %>
|
|
16
|
+
<%= link_to lato_cms.media_path(filter[:params]), class: "btn btn-sm #{filter[:params] == current ? 'btn-primary' : 'btn-outline-secondary'}" do %>
|
|
17
|
+
<i class="bi <%= filter[:icon] %> me-1"></i><%= filter[:label] %>
|
|
18
|
+
<% end %>
|
|
10
19
|
<% end %>
|
|
11
20
|
</div>
|
|
12
21
|
<div class="card-body">
|
|
@@ -11,34 +11,39 @@
|
|
|
11
11
|
<%# Two columns from lg up (preview left, data right), stacked below: the
|
|
12
12
|
preview is the thing you look at first, the metadata the thing you read
|
|
13
13
|
next to it. %>
|
|
14
|
-
<div class="card-body
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
14
|
+
<div class="card-body">
|
|
15
|
+
<%# The row stays inside the card body: `row` carries a negative top margin
|
|
16
|
+
for its gutters, which on the body itself ate the card's own padding
|
|
17
|
+
and pushed the preview against the header. %>
|
|
18
|
+
<div class="row g-4">
|
|
19
|
+
<div class="col-lg-5">
|
|
20
|
+
<%= render 'lato_cms/media/preview', media: @media %>
|
|
21
|
+
</div>
|
|
18
22
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
23
|
+
<dl class="col-lg-7 row mb-0">
|
|
24
|
+
<dt class="col-sm-3"><%= LatoCms::Media.human_attribute_name(:name) %></dt>
|
|
25
|
+
<dd class="col-sm-9"><%= @media.name %></dd>
|
|
22
26
|
|
|
23
|
-
|
|
24
|
-
|
|
27
|
+
<dt class="col-sm-3"><%= t('lato_cms.media_type_label') %></dt>
|
|
28
|
+
<dd class="col-sm-9"><%= lato_cms_media_media_type(@media) %></dd>
|
|
25
29
|
|
|
26
|
-
|
|
30
|
+
<%# Alt text is image-only, mirroring the edit form. Both are translatable:
|
|
27
31
|
one row per configured locale, blanks included, so a missing
|
|
28
32
|
translation is visible instead of just absent. %>
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
33
|
+
<% attributes = @media.image? ? LatoCms::Media::TRANSLATABLE_ATTRIBUTES : %w[title] %>
|
|
34
|
+
<% attributes.each do |attribute| %>
|
|
35
|
+
<dt class="col-sm-3"><%= t("lato_cms.media_#{attribute}") %></dt>
|
|
36
|
+
<dd class="col-sm-9">
|
|
37
|
+
<% LatoCms.config.locales.each do |locale| %>
|
|
38
|
+
<div class="d-flex gap-2">
|
|
39
|
+
<span class="badge bg-secondary align-self-start"><%= locale.to_s.upcase %></span>
|
|
40
|
+
<span><%= @media.public_send(attribute, locale).presence || content_tag(:span, '—', class: 'text-muted') %></span>
|
|
41
|
+
</div>
|
|
42
|
+
<% end %>
|
|
43
|
+
</dd>
|
|
44
|
+
<% end %>
|
|
45
|
+
</dl>
|
|
46
|
+
</div>
|
|
42
47
|
</div>
|
|
43
48
|
</div>
|
|
44
49
|
|
|
@@ -104,10 +104,20 @@
|
|
|
104
104
|
<% page_field = component_fields.find { |f| f.field_id == field_id } %>
|
|
105
105
|
|
|
106
106
|
<div class="mb-3">
|
|
107
|
+
<%# The dom id prefix carries the template component: every
|
|
108
|
+
component of the page is rendered at once, and two of them
|
|
109
|
+
can expose the same field id (a template mounting one
|
|
110
|
+
component twice, or two components both calling their
|
|
111
|
+
image field "image"). Without it they shared input ids —
|
|
112
|
+
and, for media fields, the picker frame id the selection
|
|
113
|
+
event is correlated by, so a media picked in one component
|
|
114
|
+
landed in the other as well. Input *names* stay
|
|
115
|
+
component-agnostic: each form posts its own component. %>
|
|
107
116
|
<%= lato_cms_render_field(
|
|
108
117
|
field_id: field_id,
|
|
109
118
|
field_config: field_config,
|
|
110
|
-
page_field: page_field
|
|
119
|
+
page_field: page_field,
|
|
120
|
+
dom_id_prefix: "fields_#{tc[:template_component_id]}_#{field_id}"
|
|
111
121
|
) %>
|
|
112
122
|
</div>
|
|
113
123
|
<% end %>
|
|
@@ -8,7 +8,13 @@
|
|
|
8
8
|
data-action="dragstart->lato-cms-media-reorder#onDragStart dragend->lato-cms-media-reorder#onDragEnd"
|
|
9
9
|
<% end %>>
|
|
10
10
|
<% if kind == 'video' %>
|
|
11
|
-
|
|
11
|
+
<%# With a poster the browser fetches nothing but that image until the admin
|
|
12
|
+
hits play: a page can hold many video fields, and preloading metadata
|
|
13
|
+
for each one meant every video file was requested just to open the
|
|
14
|
+
editor (enough of them and the players stall before they ever start).
|
|
15
|
+
Without a poster there's nothing else to show, so metadata it is. %>
|
|
16
|
+
<video controls preload="<%= media.poster_url ? 'none' : 'metadata' %>" poster="<%= media.poster_url %>"
|
|
17
|
+
class="lato-cms-media-field__player" src="<%= media.url %>"></video>
|
|
12
18
|
<% elsif media.thumbnail_url %>
|
|
13
19
|
<img src="<%= media.thumbnail_url %>" class="lato-cms-media-field__thumb" alt="<%= media.alt_text %>">
|
|
14
20
|
<% else %>
|
data/config/locales/en.yml
CHANGED
|
@@ -127,6 +127,8 @@ en:
|
|
|
127
127
|
media_usages_title: Used in
|
|
128
128
|
media_filter_all: All
|
|
129
129
|
media_filter_unused: Unused
|
|
130
|
+
media_filter_missing_alt_text: Missing alt text
|
|
131
|
+
media_filter_missing_title: Missing title
|
|
130
132
|
media_summary_title: Media details
|
|
131
133
|
media_type_label: Type
|
|
132
134
|
media_update_tab_details: Details
|
data/config/locales/it.yml
CHANGED
|
@@ -127,6 +127,8 @@ it:
|
|
|
127
127
|
media_usages_title: Utilizzato in
|
|
128
128
|
media_filter_all: Tutti
|
|
129
129
|
media_filter_unused: Senza utilizzi
|
|
130
|
+
media_filter_missing_alt_text: Senza alt text
|
|
131
|
+
media_filter_missing_title: Senza title
|
|
130
132
|
media_summary_title: Dettagli media
|
|
131
133
|
media_type_label: Tipo
|
|
132
134
|
media_update_tab_details: Dettagli
|
data/lib/lato_cms/version.rb
CHANGED