Static asset template helper

This commit is contained in:
Jonathon Leight
2026-07-11 22:07:42 -04:00
parent 8b17900165
commit 19bf9372c2
15 changed files with 100 additions and 69 deletions
+3 -3
View File
@@ -169,7 +169,7 @@
</template>
</form>
<script nonce="{{.Nonce}}">window.MESHTENDER_LINK_PLATFORMS = {{.PlatformsJS}};</script>
<script src="/static/link-editor.js"></script>
<script src="{{ asset "/static/link-editor.js" }}"></script>
</div>
</div>
@@ -290,6 +290,6 @@
document.getElementById("rename-name").value = btn.getAttribute("data-name") || "";
});
</script>
<script src="/static/timezone-picker.js"></script>
<script src="/static/webauthn.js"></script>
<script src="{{ asset "/static/timezone-picker.js" }}"></script>
<script src="{{ asset "/static/webauthn.js" }}"></script>
{{end}}
+1 -1
View File
@@ -35,6 +35,6 @@
</div>
</div>
<div class="text-center text-secondary mt-3">Don't have an account yet? <a href="/signup{{if .Next}}?next={{.Next}}{{end}}" tabindex="-1">Create one</a></div>
<script src="/static/webauthn.js"></script>
<script src="{{ asset "/static/webauthn.js" }}"></script>
<script nonce="{{.Nonce}}">document.addEventListener("DOMContentLoaded", initSigninPasskey);</script>
{{end}}
+1 -1
View File
@@ -40,5 +40,5 @@
</div>
</div>
<div class="text-center text-secondary mt-3">Already have an account? <a href="/login{{if .Next}}?next={{.Next}}{{end}}" tabindex="-1">Sign in</a></div>
<script src="/static/webauthn.js"></script>
<script src="{{ asset "/static/webauthn.js" }}"></script>
{{end}}
+4 -4
View File
@@ -277,10 +277,10 @@
}
document.getElementById("region").addEventListener("change", applyRegion);
</script>
<link rel="stylesheet" href="/static/leaflet.css">
<script src="/static/leaflet.js"></script>
<script src="/static/regionmap.js"></script>
<script src="/static/serial-setup.js"></script>
<link rel="stylesheet" href="{{ asset "/static/leaflet.css" }}">
<script src="{{ asset "/static/leaflet.js" }}"></script>
<script src="{{ asset "/static/regionmap.js" }}"></script>
<script src="{{ asset "/static/serial-setup.js" }}"></script>
{{else}}
{{/* ---- Step 3 (KISS): details for a repeater that's already on the network ---- */}}
@@ -20,8 +20,8 @@
</div>
{{end}}
<link rel="stylesheet" href="/static/leaflet.css">
<link rel="stylesheet" href="/static/leaflet-geoman.css">
<link rel="stylesheet" href="{{ asset "/static/leaflet.css" }}">
<link rel="stylesheet" href="{{ asset "/static/leaflet-geoman.css" }}">
<style>
@media (min-width: 992px) { .region-map-sticky { position: sticky; top: 1rem; } }
</style>
@@ -114,9 +114,9 @@ function addBlock(kind) {
}
})();
</script>
<script src="/static/leaflet.js"></script>
<script src="/static/leaflet-geoman.js"></script>
<script src="/static/regionmap.js"></script>
<script src="{{ asset "/static/leaflet.js" }}"></script>
<script src="{{ asset "/static/leaflet-geoman.js" }}"></script>
<script src="{{ asset "/static/regionmap.js" }}"></script>
<script nonce="{{.Nonce}}">initRegionEditor('region-map', 'regions');</script>
{{end}}
+5 -5
View File
@@ -135,14 +135,14 @@ swap needs no new markup. The flex row is nested so the [hidden] attribute wins
</div>
{{if .ShowConfig}}
<link rel="stylesheet" href="/static/leaflet.css">
<script src="/static/leaflet.js"></script>
<script src="/static/regionmap.js"></script>
<script src="/static/console-config.js"></script>
<link rel="stylesheet" href="{{ asset "/static/leaflet.css" }}">
<script src="{{ asset "/static/leaflet.js" }}"></script>
<script src="{{ asset "/static/regionmap.js" }}"></script>
<script src="{{ asset "/static/console-config.js" }}"></script>
{{end}}
<script nonce="{{.Nonce}}">
window.MESHTENDER_WS = (location.protocol === "https:" ? "wss://" : "ws://") + location.host + "/repeaters/{{.Repeater.PublicID}}/console/ws{{if .Debug}}?debug=1{{end}}";
</script>
<script src="/static/console.js"></script>
<script src="{{ asset "/static/console.js" }}"></script>
{{end}}
+3 -3
View File
@@ -126,10 +126,10 @@
<div class="card-header"><h3 class="card-title">Repeater map</h3></div>
{{if .Mapped}}
<div class="card-body p-2">
<link rel="stylesheet" href="/static/leaflet.css">
<link rel="stylesheet" href="{{ asset "/static/leaflet.css" }}">
<div id="map" style="height:260px" role="region" aria-label="Map of your repeater locations"></div>
<script src="/static/leaflet.js"></script>
<script src="/static/meshmap.js"></script>
<script src="{{ asset "/static/leaflet.js" }}"></script>
<script src="{{ asset "/static/meshmap.js" }}"></script>
<script nonce="{{.Nonce}}">
meshMap('map', [
{{range .Mapped}}{name: {{.Name}}, lat: {{.Latitude}}, lon: {{.Longitude}}},{{end}}
+4 -4
View File
@@ -171,10 +171,10 @@
<div class="card">
<div class="card-header"><h3 class="card-title">Repeaters</h3></div>
<div class="card-body p-2">
<link rel="stylesheet" href="/static/leaflet.css">
<link rel="stylesheet" href="{{ asset "/static/leaflet.css" }}">
<div id="map" style="height:260px" role="region" aria-label="Map of organization repeater locations"></div>
<script src="/static/leaflet.js"></script>
<script src="/static/meshmap.js"></script>
<script src="{{ asset "/static/leaflet.js" }}"></script>
<script src="{{ asset "/static/meshmap.js" }}"></script>
<script nonce="{{.Nonce}}">
meshMap('map', [
{{range .Repeaters}}{{if .HasLocation}}{name: {{.Name}}, lat: {{.Lat}}, lon: {{.Lon}}},{{end}}{{end}}
@@ -201,7 +201,7 @@
<!-- Per-platform link config for the links editor loaded into the modal above;
link-editor.js re-initializes on htmx:afterSwap. -->
<script nonce="{{.Nonce}}">window.MESHTENDER_LINK_PLATFORMS = {{.PlatformsJS}};</script>
<script src="/static/link-editor.js"></script>
<script src="{{ asset "/static/link-editor.js" }}"></script>
{{end}}
<a class="back-link mt-3" href="/orgs">{{template "icon-arrow-left" "me-1"}}All organizations</a>
+3 -3
View File
@@ -135,10 +135,10 @@
<div class="card mt-3">
<div class="card-header"><h3 class="card-title">Location</h3></div>
<div class="card-body p-2">
<link rel="stylesheet" href="/static/leaflet.css">
<link rel="stylesheet" href="{{ asset "/static/leaflet.css" }}">
<div id="map" style="height:240px" role="region" aria-label="Map of this repeater's location"></div>
<script src="/static/leaflet.js"></script>
<script src="/static/meshmap.js"></script>
<script src="{{ asset "/static/leaflet.js" }}"></script>
<script src="{{ asset "/static/meshmap.js" }}"></script>
<script nonce="{{.Nonce}}">
meshMap('map', [{name: {{.Repeater.Name}}, lat: {{.Repeater.Latitude}}, lon: {{.Repeater.Longitude}}}]);
</script>
@@ -64,10 +64,10 @@
<div class="card mt-3">
<div class="card-header"><h3 class="card-title">Location</h3></div>
<div class="card-body p-2">
<link rel="stylesheet" href="/static/leaflet.css">
<link rel="stylesheet" href="{{ asset "/static/leaflet.css" }}">
<div id="map" style="height:240px" role="region" aria-label="Map of this repeater's location"></div>
<script src="/static/leaflet.js"></script>
<script src="/static/meshmap.js"></script>
<script src="{{ asset "/static/leaflet.js" }}"></script>
<script src="{{ asset "/static/meshmap.js" }}"></script>
<script nonce="{{.Nonce}}">
meshMap('map', [{name: {{.Repeater.Name}}, lat: {{.Repeater.Latitude}}, lon: {{.Repeater.Longitude}}}]);
</script>
+31
View File
@@ -104,6 +104,37 @@ func TestTemplatesHaveNoInlineJS(t *testing.T) {
}
}
// TestTemplatesUseAssetHelper enforces that every static reference goes through
// the {{ asset }} helper (href="{{ asset "/static/x" }}"), never a bare
// href="/static/x". The helper rewrites the URL to a content-hashed,
// immutably-cached path; a bare reference silently loses that caching.
func TestTemplatesUseAssetHelper(t *testing.T) {
t.Parallel()
root := moduleRoot(t)
bare := regexp.MustCompile(`(?i)(href|src)="/static/`)
err := filepath.WalkDir(filepath.Join(root, "internal"), func(path string, d os.DirEntry, err error) error {
if err != nil {
return err
}
if d.IsDir() || !strings.HasSuffix(path, ".html") {
return nil
}
b, err := os.ReadFile(path)
if err != nil {
return err
}
rel, _ := filepath.Rel(root, path)
if loc := bare.FindIndex(b); loc != nil {
t.Errorf("%s: bare static reference — use {{ asset \"/static/...\" }}: %q", rel, snippet(b, loc[0]))
}
return nil
})
if err != nil {
t.Fatalf("walk templates: %v", err)
}
}
func snippet(b []byte, at int) string {
end := at + 40
if end > len(b) {
+24 -24
View File
@@ -3,14 +3,14 @@
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
<link rel="icon" href="/static/favicon.svg" type="image/svg+xml">
<link rel="icon" href="{{ asset "/static/favicon.svg" }}" type="image/svg+xml">
<title>{{block "title" .}}MeshTender{{end}}</title>
<link rel="stylesheet" href="/static/tabler.min.css">
<link rel="stylesheet" href="/static/app.css">
<script src="/static/tabler.min.js" defer></script>
<script src="/static/htmx.min.js" defer></script>
<script src="/static/ui.js" defer></script>
<script src="/static/listfilter.js" defer></script>
<link rel="stylesheet" href="{{ asset "/static/tabler.min.css" }}">
<link rel="stylesheet" href="{{ asset "/static/app.css" }}">
<script src="{{ asset "/static/tabler.min.js" }}" defer></script>
<script src="{{ asset "/static/htmx.min.js" }}" defer></script>
<script src="{{ asset "/static/ui.js" }}" defer></script>
<script src="{{ asset "/static/listfilter.js" }}" defer></script>
</head>
<body>
<a href="#main-content" class="visually-hidden-focusable btn btn-primary position-absolute top-0 start-0 m-2" style="z-index:1080">Skip to main content</a>
@@ -164,14 +164,14 @@
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
<link rel="icon" href="/static/favicon.svg" type="image/svg+xml">
<link rel="icon" href="{{ asset "/static/favicon.svg" }}" type="image/svg+xml">
<title>{{template "title" .}}</title>
<link rel="stylesheet" href="/static/tabler.min.css">
<link rel="stylesheet" href="/static/app.css">
<script src="/static/tabler.min.js" defer></script>
<script src="/static/htmx.min.js" defer></script>
<script src="/static/ui.js" defer></script>
<script src="/static/listfilter.js" defer></script>
<link rel="stylesheet" href="{{ asset "/static/tabler.min.css" }}">
<link rel="stylesheet" href="{{ asset "/static/app.css" }}">
<script src="{{ asset "/static/tabler.min.js" }}" defer></script>
<script src="{{ asset "/static/htmx.min.js" }}" defer></script>
<script src="{{ asset "/static/ui.js" }}" defer></script>
<script src="{{ asset "/static/listfilter.js" }}" defer></script>
</head>
<body>
<a href="#main-content" class="visually-hidden-focusable btn btn-primary position-absolute top-0 start-0 m-2" style="z-index:1080">Skip to main content</a>
@@ -210,12 +210,12 @@
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
<link rel="icon" href="/static/favicon.svg" type="image/svg+xml">
<link rel="icon" href="{{ asset "/static/favicon.svg" }}" type="image/svg+xml">
<title>{{template "title" .}}</title>
<link rel="stylesheet" href="/static/tabler.min.css">
<link rel="stylesheet" href="/static/app.css">
<script src="/static/tabler.min.js" defer></script>
<script src="/static/ui.js" defer></script>
<link rel="stylesheet" href="{{ asset "/static/tabler.min.css" }}">
<link rel="stylesheet" href="{{ asset "/static/app.css" }}">
<script src="{{ asset "/static/tabler.min.js" }}" defer></script>
<script src="{{ asset "/static/ui.js" }}" defer></script>
</head>
<body class="d-flex flex-column">
<div class="page page-center">
@@ -239,12 +239,12 @@
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
<link rel="icon" href="/static/favicon.svg" type="image/svg+xml">
<link rel="icon" href="{{ asset "/static/favicon.svg" }}" type="image/svg+xml">
<title>{{template "title" .}}</title>
<link rel="stylesheet" href="/static/tabler.min.css">
<link rel="stylesheet" href="/static/app.css">
<script src="/static/tabler.min.js" defer></script>
<script src="/static/ui.js" defer></script>
<link rel="stylesheet" href="{{ asset "/static/tabler.min.css" }}">
<link rel="stylesheet" href="{{ asset "/static/app.css" }}">
<script src="{{ asset "/static/tabler.min.js" }}" defer></script>
<script src="{{ asset "/static/ui.js" }}" defer></script>
</head>
<body>
{{template "rootnav" .}}
+3 -3
View File
@@ -55,10 +55,10 @@
<div class="card{{if .Config.Profiles}} mt-3{{end}}">
<div class="card-header"><h3 class="card-title">Preview a location</h3></div>
<div class="card-body p-2">
<link rel="stylesheet" href="/static/leaflet.css">
<link rel="stylesheet" href="{{ asset "/static/leaflet.css" }}">
<div id="region-map" style="height:240px" role="region" aria-label="Click the map to preview a repeater's region config"></div>
<script src="/static/leaflet.js"></script>
<script src="/static/regionmap.js"></script>
<script src="{{ asset "/static/leaflet.js" }}"></script>
<script src="{{ asset "/static/regionmap.js" }}"></script>
<script nonce="{{.Nonce}}">
regionMapView('region-map', {
pickURL: '/orgs/{{.Org.Slug}}/config?{{if .Config.Profiles}}profile={{.Config.Selected | urlquery}}&{{end}}',
+5 -5
View File
@@ -66,12 +66,12 @@
<div class="card-header"><h3 class="card-title">Repeaters</h3></div>
{{if .HasMap}}
<div class="card-body p-2">
<link rel="stylesheet" href="/static/leaflet.css">
<link rel="stylesheet" href="/static/leaflet.markercluster.css">
<link rel="stylesheet" href="{{ asset "/static/leaflet.css" }}">
<link rel="stylesheet" href="{{ asset "/static/leaflet.markercluster.css" }}">
<div id="map" style="height:260px" role="region" aria-label="Map of organization repeater locations"></div>
<script src="/static/leaflet.js"></script>
<script src="/static/leaflet.markercluster.js"></script>
<script src="/static/meshmap.js"></script>
<script src="{{ asset "/static/leaflet.js" }}"></script>
<script src="{{ asset "/static/leaflet.markercluster.js" }}"></script>
<script src="{{ asset "/static/meshmap.js" }}"></script>
<script nonce="{{.Nonce}}">
{{if .MapURL}}meshMapFromURL('map', {{.MapURL}});{{else}}meshMap('map', [
{{range .Repeaters}}{{if .HasLocation}}{name: {{.Name}}, lat: {{.Lat}}, lon: {{.Lon}}},{{end}}{{end}}
+5 -5
View File
@@ -64,12 +64,12 @@
<div class="card-header"><h3 class="card-title">Map</h3></div>
{{if .Reps.HasMap}}
<div class="card-body p-2">
<link rel="stylesheet" href="/static/leaflet.css">
<link rel="stylesheet" href="/static/leaflet.markercluster.css">
<link rel="stylesheet" href="{{ asset "/static/leaflet.css" }}">
<link rel="stylesheet" href="{{ asset "/static/leaflet.markercluster.css" }}">
<div id="map" style="height:300px" role="region" aria-label="Map of organization repeater locations"></div>
<script src="/static/leaflet.js"></script>
<script src="/static/leaflet.markercluster.js"></script>
<script src="/static/meshmap.js"></script>
<script src="{{ asset "/static/leaflet.js" }}"></script>
<script src="{{ asset "/static/leaflet.markercluster.js" }}"></script>
<script src="{{ asset "/static/meshmap.js" }}"></script>
<script nonce="{{.Nonce}}">
{{if .MapURL}}meshMapFromURL('map', {{.MapURL}});{{else}}meshMap('map', [
{{range .Reps.MapPoints}}{name: {{.Name}}, lat: {{.Lat}}, lon: {{.Lon}}},{{end}}