Commit ad03cef

HPCesia <me@hpcesia.com>
2026-07-02 17:00:51
Fix auto theme missing in markdown and blob
1 parent 6678887
pkg/templates/list.gohtml
@@ -1,6 +1,11 @@
 {{- /*gotype: github.com/antonmedv/gitmal/pkg/templates.ListParams*/ -}}
 {{ define "head" }}
-    {{ if and .Readme (not .InlineStyles) }}<link rel="stylesheet" href="{{ .RootHref }}markdown.css">{{ end }}
+    {{ if .Readme }}
+      {{ if not .InlineStyles }}
+        <link rel="stylesheet" href="{{ .RootHref }}markdown.css">
+        <link rel="stylesheet" href="{{ .RootHref }}chroma.css">
+      {{ end }}
+    {{ end }}
     <style>
       .files {
         border: 1px solid var(--c-border);
@@ -84,7 +89,10 @@
         }
       }
 
-      {{ if $.InlineStyles }}{{$.CSSMarkdown}}{{ end }}
+      {{ if $.InlineStyles }}
+        {{$.CSSMarkdown}}
+        {{if $.CSSChroma}}{{$.CSSChroma}}{{end}}
+      {{ end }}
       {{ end }}
     </style>
 {{ end }}
pkg/templates/markdown.gohtml
@@ -4,8 +4,12 @@
     <style>
       {{.CSSMarkdown}}
     </style>
+    {{ if .CSSChroma }}
+    <style>{{.CSSChroma}}</style>
+    {{ end }}
     {{ else }}
     <link rel="stylesheet" href="{{ .RootHref }}markdown.css">
+    <link rel="stylesheet" href="{{ .RootHref }}chroma.css">
     {{ end }}
     <style>
       [id] {
pkg/templates/templates.go
@@ -75,6 +75,7 @@ type LayoutParams struct {
 	SiteName         string
 	Dark             bool
 	CSSMarkdown      CSS
+	CSSChroma        CSS
 	RootHref         string
 	RepoHref         string
 	CurrentRefDir    string
blob.go
@@ -36,12 +36,16 @@ func generateBlobs(files []git.Blob, params Params) error {
 		html.WithCSSComments(false),
 	}
 
-	// Use a temporary formatter to render CSS once
-	var rawCSS strings.Builder
-	if err := html.New(formatterOptions...).WriteCSS(&rawCSS, style); err != nil {
+	// Generate chroma CSS for both light and dark themes
+	chromaLight, err := renderChromaCSS(params.StyleLight)
+	if err != nil {
 		return err
 	}
-	css.WriteString(stripChromaBase(rawCSS.String()))
+	chromaDark, err := renderChromaCSS(params.StyleDark)
+	if err != nil {
+		return err
+	}
+	css.WriteString(combineThemeCSS(chromaLight, chromaDark))
 
 	dirsSet := links.BuildDirSet(files)
 	filesSet := links.BuildFileSet(files)
@@ -139,6 +143,7 @@ func generateBlobs(files []git.Blob, params Params) error {
 								Title:         fmt.Sprintf("%s/%s at %s", params.Name, blob.Path, params.Ref),
 								Dark:          params.Dark,
 								CSSMarkdown:   cssMarkdown(params.Dark),
+								CSSChroma:     template.CSS(css.String()),
 								Name:          params.Name,
 								SiteName:      params.SiteName,
 								RootHref:      rootHref + params.RootPrefix,
list.go
@@ -177,8 +177,18 @@ func generateLists(files []git.Blob, params Params) error {
 
 					readmeHTML := readme(di.files, dirsSet, filesSet, params, rootHref)
 					var CSSMarkdown template.CSS
+					var CSSChroma template.CSS
 					if readmeHTML != "" {
 						CSSMarkdown = cssMarkdown(params.Dark)
+						chromaLight, err := renderChromaCSS(params.StyleLight)
+						if check(err) {
+							return
+						}
+						chromaDark, err := renderChromaCSS(params.StyleDark)
+						if check(err) {
+							return
+						}
+						CSSChroma = template.CSS(combineThemeCSS(chromaLight, chromaDark))
 					}
 
 					err = templates.ListTemplate.ExecuteTemplate(f, "layout.gohtml", templates.ListParams{
@@ -188,6 +198,7 @@ func generateLists(files []git.Blob, params Params) error {
 							SiteName:      params.SiteName,
 							Dark:          params.Dark,
 							CSSMarkdown:   CSSMarkdown,
+							CSSChroma:     CSSChroma,
 							RootHref:      rootHref + params.RootPrefix,
 							RepoHref:      rootHref,
 							CurrentRefDir: params.Ref.DirName(),
styles_gen.go
@@ -5,6 +5,7 @@ import (
 	"os"
 	"path/filepath"
 	"regexp"
+	"sort"
 	"strings"
 
 	"github.com/alecthomas/chroma/v2"
@@ -102,6 +103,31 @@ func stripChromaBase(css string) string {
 	})
 }
 
+var reChromaSelector = regexp.MustCompile(`\.chroma \.\w+`)
+
+func chromaSelectors(css string) map[string]struct{} {
+	sels := make(map[string]struct{})
+	for _, m := range reChromaSelector.FindAllString(css, -1) {
+		sels[m] = struct{}{}
+	}
+	return sels
+}
+
+// lightOnlySelectors returns selectors present in light but missing in dark.
+// These need to be reset in dark mode to prevent light colors from leaking.
+func lightOnlySelectors(light, dark string) []string {
+	lightSels := chromaSelectors(light)
+	darkSels := chromaSelectors(dark)
+	var only []string
+	for sel := range lightSels {
+		if _, ok := darkSels[sel]; !ok {
+			only = append(only, sel)
+		}
+	}
+	sort.Strings(only)
+	return only
+}
+
 func combineThemeCSS(light, dark string) string {
 	if light == "" && dark == "" {
 		return ""
@@ -109,9 +135,15 @@ func combineThemeCSS(light, dark string) string {
 	if dark == "" || light == dark {
 		return light
 	}
+
+	var darkReset strings.Builder
+	for _, sel := range lightOnlySelectors(light, dark) {
+		darkReset.WriteString(sel + " { color: inherit; }\n")
+	}
+
 	return light +
-		"\n@media (prefers-color-scheme: dark) {\n" + dark + "\n}\n" +
-		"\n[data-theme=\"dark\"] {\n" + dark + "\n}\n"
+		"\n@media (prefers-color-scheme: dark) {\n" + dark + darkReset.String() + "\n}\n" +
+		"\n[data-theme=\"dark\"] {\n" + dark + darkReset.String() + "\n}\n"
 }
 
 func renderDiffCSS(theme string) (string, error) {