Skip to content

Commit 3e015ea

Browse files
authored
Merge pull request #46 from gethinode/develop
feat(preview): add build-time embedding detection and fallback rendering
2 parents b4b91bc + a70039e commit 3e015ea

9 files changed

Lines changed: 107 additions & 6 deletions

File tree

component-library/components/preview/preview.bookshop.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ blueprint:
2222
subtle:
2323
url:
2424
device:
25+
image:
26+
show-fallback:
2527
controls-placement:
2628
desktop-responsive:
2729
width:

component-library/components/preview/preview.hugo.html

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
"controls-placement" (or .controls_placement (index . "controls-placement"))
1717
"desktop-responsive" (or .desktop_responsive (index . "desktop-responsive"))
1818
"heading" .heading
19+
"image" .image
20+
"show-fallback" (or .show_fallback (index . "show-fallback"))
1921
) }}
2022

2123
{{ if $raw }}

component-library/components/preview/preview.scss

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,13 +175,48 @@ section.preview {
175175
margin: 2rem auto;
176176
padding: 0 1rem;
177177

178+
// When a fallback image is present, fill the full preview area (flex column)
179+
&:has(.preview-fallback-image) {
180+
width: 100%;
181+
height: 100%;
182+
max-width: 100%;
183+
margin: 0;
184+
padding: 0;
185+
display: flex;
186+
flex-direction: column;
187+
188+
.preview-fallback-image {
189+
flex: 1;
190+
width: 100%;
191+
object-fit: cover;
192+
min-height: 0;
193+
}
194+
195+
.alert {
196+
flex-shrink: 0;
197+
margin: 1rem;
198+
}
199+
}
200+
178201
.alert {
179202
display: flex;
180203
align-items: center;
181204
gap: 0.5rem;
182205
}
183206
}
184207

208+
// Fallback state: controls are irrelevant and the image should not be height-constrained
209+
&:has(.preview-error:not(.d-none)) {
210+
.preview-controls {
211+
display: none;
212+
}
213+
214+
.preview-content {
215+
height: auto;
216+
min-height: unset;
217+
}
218+
}
219+
185220
// Responsive behavior for small screens
186221
@media (max-width: 639px) {
187222
.preview-controls {

data/structures/preview.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,7 @@ arguments:
1111
optional: true
1212
heading:
1313
optional: true
14+
image:
15+
optional: true
16+
show-fallback:
17+
optional: true

exampleSite/go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,5 @@ go 1.19
55
require (
66
github.com/cloudcannon/bookshop/hugo/v3 v3.17.1 // indirect
77
github.com/gethinode/mod-blocks v1.7.0 // indirect
8-
github.com/gethinode/mod-utils/v5 v5.13.0 // indirect
8+
github.com/gethinode/mod-utils/v5 v5.14.0 // indirect
99
)

exampleSite/go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,3 +34,5 @@ github.com/gethinode/mod-utils/v5 v5.12.0 h1:XH6/MsvgPfYVD8Ci3t20vRH3Zz1JpaqqG6X
3434
github.com/gethinode/mod-utils/v5 v5.12.0/go.mod h1:PwQN4oOjA6k/vet11JueJ9asZMgL0DBa3jyS9tPkBWU=
3535
github.com/gethinode/mod-utils/v5 v5.13.0 h1:ztkE1REey94x36UdlZ7yeitpIQid/BcZQh+wtxBTSQ8=
3636
github.com/gethinode/mod-utils/v5 v5.13.0/go.mod h1:PwQN4oOjA6k/vet11JueJ9asZMgL0DBa3jyS9tPkBWU=
37+
github.com/gethinode/mod-utils/v5 v5.14.0 h1:Nntb2ei7yTRsUmGMUHo/fgCfacIk+GD/ZbSW64gu6HM=
38+
github.com/gethinode/mod-utils/v5 v5.14.0/go.mod h1:PwQN4oOjA6k/vet11JueJ9asZMgL0DBa3jyS9tPkBWU=

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,5 @@ go 1.19
44

55
require (
66
github.com/cloudcannon/bookshop/hugo/v3 v3.17.1 // indirect
7-
github.com/gethinode/mod-utils/v5 v5.13.0 // indirect
7+
github.com/gethinode/mod-utils/v5 v5.14.0 // indirect
88
)

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,5 @@ github.com/gethinode/mod-utils/v5 v5.12.0 h1:XH6/MsvgPfYVD8Ci3t20vRH3Zz1JpaqqG6X
1010
github.com/gethinode/mod-utils/v5 v5.12.0/go.mod h1:PwQN4oOjA6k/vet11JueJ9asZMgL0DBa3jyS9tPkBWU=
1111
github.com/gethinode/mod-utils/v5 v5.13.0 h1:ztkE1REey94x36UdlZ7yeitpIQid/BcZQh+wtxBTSQ8=
1212
github.com/gethinode/mod-utils/v5 v5.13.0/go.mod h1:PwQN4oOjA6k/vet11JueJ9asZMgL0DBa3jyS9tPkBWU=
13+
github.com/gethinode/mod-utils/v5 v5.14.0 h1:Nntb2ei7yTRsUmGMUHo/fgCfacIk+GD/ZbSW64gu6HM=
14+
github.com/gethinode/mod-utils/v5 v5.14.0/go.mod h1:PwQN4oOjA6k/vet11JueJ9asZMgL0DBa3jyS9tPkBWU=

layouts/partials/assets/preview.html

Lines changed: 58 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,51 @@
3434
{{- $controlsBelow := eq $args.controlsPlacement "bottom" -}}
3535

3636
{{ if not $error }}
37+
{{/* Build-time detection of iframe embedding restrictions */}}
38+
{{- $showFallback := $args.showFallback -}}
39+
{{- if and $args.url (not $showFallback) -}}
40+
{{- with try (resources.GetRemote $args.url (dict
41+
"method" "HEAD"
42+
"responseHeaders" (slice "X-Frame-Options" "Content-Security-Policy")
43+
)) -}}
44+
{{- if not .Err -}}
45+
{{- $headers := .Value.Data.Headers -}}
46+
{{- with index $headers "X-Frame-Options" -}}
47+
{{- $showFallback = true -}}
48+
{{- partial "utilities/LogWarn.html" (dict
49+
"partial" "assets/preview.html"
50+
"warnid" "warn-preview-restricted"
51+
"msg" "iframe embedding blocked"
52+
"details" (slice (printf "URL '%s' sets X-Frame-Options; showing fallback" $args.url))
53+
"file" page.File
54+
) -}}
55+
{{- end -}}
56+
{{- range index $headers "Content-Security-Policy" -}}
57+
{{- if findRE `frame-ancestors\s+'none'` . -}}
58+
{{- $showFallback = true -}}
59+
{{- partial "utilities/LogWarn.html" (dict
60+
"partial" "assets/preview.html"
61+
"warnid" "warn-preview-restricted"
62+
"msg" "iframe embedding blocked"
63+
"details" (slice (printf "URL '%s' sets CSP frame-ancestors 'none'; showing fallback" $args.url))
64+
"file" page.File
65+
) -}}
66+
{{- end -}}
67+
{{- end -}}
68+
{{- else -}}
69+
{{- if $showFallback -}}
70+
{{- partial "utilities/LogWarn.html" (dict
71+
"partial" "assets/preview.html"
72+
"warnid" "warn-preview-restricted"
73+
"msg" "HEAD request failed"
74+
"details" (slice (printf "Could not check '%s' for embedding restrictions; fallback shown due to show-fallback setting" $args.url))
75+
"file" page.File
76+
) -}}
77+
{{- end -}}
78+
{{- end -}}
79+
{{- end -}}
80+
{{- end -}}
81+
3782
{{/* Optional section heading */}}
3883
{{ if $args.heading.title }}
3984
{{ partial "assets/section-title.html" (dict
@@ -42,7 +87,7 @@
4287
{{ end }}
4388

4489
{{/* Device selection button group — rendered above or below the preview */}}
45-
{{- if not $controlsBelow }}
90+
{{- if and (not $showFallback) (not $controlsBelow) }}
4691
<div class="container-xxl">
4792
<div class="preview-controls d-flex justify-content-center mb-3">
4893
<div class="btn-group" role="tablist" aria-label="Device selection">
@@ -75,14 +120,23 @@
75120
role="tabpanel"
76121
aria-labelledby="{{ $id }}-{{ $device }}-tab">
77122
<iframe src="{{ $args.url }}"
78-
class="preview-iframe preview-{{ $device }}"
123+
class="preview-iframe preview-{{ $device }}{{ if $showFallback }} d-none{{ end }}"
79124
title="{{ or $args.heading.title (printf "Preview of %s" $args.url) }}"
80125
loading="lazy"
81126
allow="storage-access"
82127
sandbox="allow-scripts allow-same-origin allow-forms allow-popups allow-storage-access-by-user-activation"
83128
referrerpolicy="no-referrer-when-downgrade">
84129
</iframe>
85-
<div class="preview-error" style="display: none;">
130+
<div class="preview-error{{ if not $showFallback }} d-none{{ end }}">
131+
{{- if $args.image }}
132+
{{ partial "assets/image.html" (dict
133+
"src" $args.image
134+
"page" page
135+
"class" "preview-fallback-image"
136+
"title" (or $args.heading.title (printf "Preview of %s" $args.url))
137+
"loading" "lazy"
138+
) }}
139+
{{- end }}
86140
<div class="alert alert-warning" role="alert">
87141
{{ partial "assets/icon.html" (dict "icon" $previewError "spacing" false) }}
88142
{{ T "previewError" }} <code>{{ $args.url }}</code>.
@@ -95,7 +149,7 @@
95149
{{- end -}}
96150
</div>
97151

98-
{{- if $controlsBelow }}
152+
{{- if and (not $showFallback) $controlsBelow }}
99153
<div class="container-xxl">
100154
<div class="preview-controls controls-bottom d-flex justify-content-center mt-3">
101155
<div class="btn-group" role="tablist" aria-label="Device selection">

0 commit comments

Comments
 (0)