cerrado @ 5dd940eb52b40c78a2078ed0a02440e84bee0306

feat: Write css dark/light theme on standalone url

This will allow tho write both themes into the same css file.
Also mend with the css generate by chroma so it can be nested.
  1diff --git a/pkg/handler/git/handler.go b/pkg/handler/git/handler.go
  2index ffa5dfd6230013ce209b1c3f51c866254e734e2e..d046d19e47cfa20f771d86c54764253a1c8b84b3 100644
  3--- a/pkg/handler/git/handler.go
  4+++ b/pkg/handler/git/handler.go
  5@@ -349,6 +349,7 @@
  6 	formatter := html.New(
  7 		html.WithLineNumbers(true),
  8 		html.WithLinkableLineNumbers(true, "L"),
  9+		html.WithClasses(true),
 10 	)
 11 
 12 	iterator, err := lexer.Tokenise(nil, string(file))
 13@@ -440,6 +441,7 @@
 14 	formatter := html.New(
 15 		html.WithLineNumbers(true),
 16 		html.WithLinkableLineNumbers(true, "L"),
 17+		html.WithClasses(true),
 18 	)
 19 
 20 	iterator, err := lexer.Tokenise(nil, diff)
 21diff --git a/pkg/handler/router.go b/pkg/handler/router.go
 22index fea882737aac94a9f22681315783e1831bfe9663..bc81350d19a5296cae5d8da058bac29a69bff693 100644
 23--- a/pkg/handler/router.go
 24+++ b/pkg/handler/router.go
 25@@ -31,6 +31,14 @@ 	if err != nil {
 26 		return nil, err
 27 	}
 28 
 29+	cssStaticHandler, err := static.ServeStaticCSSHandler(
 30+		configRepo.GetSyntaxHighlight(),
 31+		configRepo.GetSyntaxHighlightDark(),
 32+	)
 33+	if err != nil {
 34+		return nil, err
 35+	}
 36+
 37 	mux := ext.NewRouter()
 38 	mux.AddMiddleware(ext.Compress)
 39 	mux.AddMiddleware(ext.Log)
 40@@ -45,6 +53,7 @@ 		mux.AddMiddleware(ext.DisableAuthentication)
 41 	}
 42 
 43 	mux.HandleFunc("/static/{file}", staticHandler)
 44+	mux.HandleFunc("/static/theme", cssStaticHandler)
 45 	mux.HandleFunc("/{name}/about/{$}", gitHandler.About)
 46 	mux.HandleFunc("/{name}", gitHandler.Multiplex)
 47 	mux.HandleFunc("/{name}/{rest...}", gitHandler.Multiplex)
 48diff --git a/pkg/handler/static/handler.go b/pkg/handler/static/handler.go
 49index cdb2ae6d4b81ebc5396a0e6ce9d01bd48a73e2e9..779c78660309a955ece24d643d21877b610c2cad 100644
 50--- a/pkg/handler/static/handler.go
 51+++ b/pkg/handler/static/handler.go
 52@@ -1,6 +1,8 @@
 53 package static
 54 
 55 import (
 56+	"fmt"
 57+	"io"
 58 	"io/fs"
 59 	"mime"
 60 	"net/http"
 61@@ -8,6 +10,9 @@ 	"path/filepath"
 62 
 63 	"git.gabrielgio.me/cerrado/pkg/ext"
 64 	"git.gabrielgio.me/cerrado/static"
 65+	"github.com/alecthomas/chroma/v2"
 66+	"github.com/alecthomas/chroma/v2/formatters/html"
 67+	"github.com/alecthomas/chroma/v2/styles"
 68 )
 69 
 70 func ServeStaticHandler() (ext.ErrorRequestHandler, error) {
 71@@ -28,3 +33,47 @@ 		http.ServeFileFS(w, r.Request, staticFs, f)
 72 		return nil
 73 	}, nil
 74 }
 75+
 76+func ServeStaticCSSHandler(lightTheme, darkTheme string) (ext.ErrorRequestHandler, error) {
 77+	var (
 78+		lightStyle = styles.Get(lightTheme)
 79+		darkStyle  = styles.Get(darkTheme)
 80+		formatter  = html.New(
 81+			html.WithCSSComments(false),
 82+		)
 83+	)
 84+
 85+	return func(w http.ResponseWriter, r *ext.Request) error {
 86+		ext.SetMIME(w, "text/css")
 87+
 88+		var style *chroma.Style
 89+		style = darkStyle
 90+		w.Write([]byte("[data-bs-theme=\"dark\"] {\n"))
 91+		err := formatter.WriteCSS(&ws{w}, style)
 92+		if err != nil {
 93+			return err
 94+		}
 95+		w.Write([]byte("}\n"))
 96+
 97+		style = lightStyle
 98+		w.Write([]byte("[data-bs-theme=\"light\"] {\n"))
 99+		err = formatter.WriteCSS(&ws{w}, style)
100+		if err != nil {
101+			return err
102+		}
103+		w.Write([]byte("\n}"))
104+
105+		return nil
106+	}, nil
107+}
108+
109+type ws struct {
110+	inner io.Writer
111+}
112+
113+// This is very cursed, and rely on the fact that it writes every css rule at time.
114+// it adds & to the begging so it can be nested by the ServeStaticCSSHandler.
115+// This will allow the follow bootstrap data-bs-theme.
116+func (w *ws) Write(p []byte) (n int, err error) {
117+	return fmt.Fprintf(w.inner, "& %s", string(p))
118+}
119diff --git a/templates/base.qtpl b/templates/base.qtpl
120index b3df94a5b8b51f047b1403ddea94a376122fd844..e43fb67fa5b7c74e9a0caa9057788cf46a4a614e 100644
121--- a/templates/base.qtpl
122+++ b/templates/base.qtpl
123@@ -60,6 +60,8 @@     <meta charset="utf-8">
124     <link rel="icon" href="data:,">
125     <title>{%= p.Title(ctx) %}</title> 
126     <link rel="stylesheet" href="/static/main{%s Slug %}.css">
127+    <link rel="stylesheet" href="/static/themes/dark">
128+    <link rel="stylesheet" href="/static/themes/light">
129     <html data-bs-theme="dark">
130     <meta content="text/html;charset=utf-8" http-equiv="Content-Type">
131     <meta name="viewport" content="width=device-width, initial-scale=1" />
132diff --git a/templates/base.qtpl.go b/templates/base.qtpl.go
133index dce4cbcf1c80f70254c7a2dc5bcbc3794414fc2d..783de2c10402a1a33125b1f872bc3db488ede7ab 100644
134--- a/templates/base.qtpl.go
135+++ b/templates/base.qtpl.go
136@@ -8,16 +8,14 @@ //line templates/base.qtpl:3
137 package templates
138 
139 //line templates/base.qtpl:3
140-import "context"
141-
142-//line templates/base.qtpl:4
143-import "strconv"
144+import (
145+	"context"
146+	"strconv"
147+	"time" //line templates/base.qtpl:4
148 
149-//line templates/base.qtpl:5
150-import "time"
151+	//line templates/base.qtpl:5
152+	//line templates/base.qtpl:7
153 
154-//line templates/base.qtpl:7
155-import (
156 	qtio422016 "io"
157 
158 	qt422016 "github.com/valyala/quicktemplate"
159@@ -112,59 +110,60 @@ //line templates/base.qtpl:62
160 	qw422016.E().S(Slug)
161 //line templates/base.qtpl:62
162 	qw422016.N().S(`.css">
163+    <link rel="stylesheet" href="/static/theme">
164     <html data-bs-theme="dark">
165     <meta content="text/html;charset=utf-8" http-equiv="Content-Type">
166     <meta name="viewport" content="width=device-width, initial-scale=1" />
167   </head>
168   <body>
169     `)
170-//line templates/base.qtpl:68
171+//line templates/base.qtpl:70
172 	p.StreamNavbar(qw422016, ctx)
173-//line templates/base.qtpl:68
174+//line templates/base.qtpl:70
175 	qw422016.N().S(`
176     <div class="container">
177       `)
178-//line templates/base.qtpl:70
179+//line templates/base.qtpl:72
180 	p.StreamContent(qw422016, ctx)
181-//line templates/base.qtpl:70
182+//line templates/base.qtpl:72
183 	qw422016.N().S(`
184     </div>
185   </body>
186   `)
187-//line templates/base.qtpl:73
188+//line templates/base.qtpl:75
189 	p.StreamScript(qw422016, ctx)
190-//line templates/base.qtpl:73
191+//line templates/base.qtpl:75
192 	qw422016.N().S(`
193   <script>
194   function a(){const e=window.matchMedia("(prefers-color-scheme: dark)").matches;document.documentElement.setAttribute("data-bs-theme",e?"dark":"light")}a(),window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change",a);
195   </script>
196 </html>
197 `)
198-//line templates/base.qtpl:78
199+//line templates/base.qtpl:80
200 }
201 
202-//line templates/base.qtpl:78
203+//line templates/base.qtpl:80
204 func WritePageTemplate(qq422016 qtio422016.Writer, p Page, ctx context.Context) {
205-//line templates/base.qtpl:78
206+//line templates/base.qtpl:80
207 	qw422016 := qt422016.AcquireWriter(qq422016)
208-//line templates/base.qtpl:78
209+//line templates/base.qtpl:80
210 	StreamPageTemplate(qw422016, p, ctx)
211-//line templates/base.qtpl:78
212+//line templates/base.qtpl:80
213 	qt422016.ReleaseWriter(qw422016)
214-//line templates/base.qtpl:78
215+//line templates/base.qtpl:80
216 }
217 
218-//line templates/base.qtpl:78
219+//line templates/base.qtpl:80
220 func PageTemplate(p Page, ctx context.Context) string {
221-//line templates/base.qtpl:78
222+//line templates/base.qtpl:80
223 	qb422016 := qt422016.AcquireByteBuffer()
224-//line templates/base.qtpl:78
225+//line templates/base.qtpl:80
226 	WritePageTemplate(qb422016, p, ctx)
227-//line templates/base.qtpl:78
228+//line templates/base.qtpl:80
229 	qs422016 := string(qb422016.B)
230-//line templates/base.qtpl:78
231+//line templates/base.qtpl:80
232 	qt422016.ReleaseByteBuffer(qb422016)
233-//line templates/base.qtpl:78
234+//line templates/base.qtpl:80
235 	return qs422016
236-//line templates/base.qtpl:78
237+//line templates/base.qtpl:80
238 }