1diff --git a/pkg/handler/router.go b/pkg/handler/router.go
2index cb5d6f54dfbf444aa0830337c9f9044067d9c16b..1fbc4e3f90bc3304bf82c26084a06ddf756df5dc 100644
3--- a/pkg/handler/router.go
4+++ b/pkg/handler/router.go
5@@ -55,7 +55,11 @@ mux.AddMiddleware(ext.DisableAuthentication)
6 }
7
8 mux.HandleFunc("/static/{file}", staticHandler)
9- mux.HandleFunc(fmt.Sprintf("/static/theme%s.css", templates.Slug), cssStaticHandler) // add slug so css file can be cached forever.
10+ // add slug and session so css file can be cached forever.
11+ // Slug follow commit id, which is update every new version
12+ // Session is update every time server restarts, this allows the css to be
13+ // cached forever but refresh if the admin updates the server configuration.
14+ mux.HandleFunc(fmt.Sprintf("/static/theme.%s%s.css", templates.Session, templates.Slug), cssStaticHandler)
15 mux.HandleFunc("/{name}/about/{$}", gitHandler.About)
16 mux.HandleFunc("/{name}", gitHandler.Multiplex)
17 mux.HandleFunc("/{name}/{rest...}", gitHandler.Multiplex)
18diff --git a/templates/base.qtpl b/templates/base.qtpl
19index 1dddb57bb0f0f60829259fc2eb60561c823a5b6f..6ff3d5340196e906267f25a52d87f3620da8ab0b 100644
20--- a/templates/base.qtpl
21+++ b/templates/base.qtpl
22@@ -1,11 +1,28 @@
23 This is a base page template. All the other template pages implement this interface.
24
25 {% import "context" %}
26+{% import "crypto/rand" %}
27+{% import "encoding/hex" %}
28 {% import "strconv" %}
29 {% import "time" %}
30
31-{% code
32+{% code
33+
34 var Slug = ""
35+ var Session = ""
36+
37+ func init() {
38+ Session = hex.EncodeToString(generateSmallTimeID())
39+}
40+
41+func generateSmallTimeID() []byte {
42+ b := make([]byte, 4)
43+ _, err := rand.Read(b)
44+ if err != nil {
45+ panic(err)
46+ }
47+ return b
48+}
49 %}
50
51 {% interface
52@@ -58,9 +75,9 @@ <html lang="en" data-bs-theme="light">
53 <head>
54 <meta charset="utf-8">
55 <link rel="icon" href="data:,">
56- <title>{%= p.Title(ctx) %}</title>
57+ <title>{%= p.Title(ctx) %}</title>
58 <link rel="stylesheet" href="/static/main{%s Slug %}.css">
59- <link rel="stylesheet" href="/static/theme{%s Slug %}.css">
60+ <link rel="stylesheet" href="/static/theme.{%s Session %}{%s Slug %}.css">
61 <html data-bs-theme="dark">
62 <meta content="text/html;charset=utf-8" http-equiv="Content-Type">
63 <meta name="viewport" content="width=device-width, initial-scale=1" />
64diff --git a/templates/base.qtpl.go b/templates/base.qtpl.go
65index 3c4d8a94cc1649c5efd739976e21c045f8582db4..db8afecb01de33a8fec5beb9d2a6796e4d6662ed 100644
66--- a/templates/base.qtpl.go
67+++ b/templates/base.qtpl.go
68@@ -11,57 +11,77 @@ //line templates/base.qtpl:3
69 import "context"
70
71 //line templates/base.qtpl:4
72-import "strconv"
73+import "crypto/rand"
74
75 //line templates/base.qtpl:5
76-import "time"
77+import "encoding/hex"
78+
79+//line templates/base.qtpl:6
80+import "strconv"
81
82 //line templates/base.qtpl:7
83+import "time"
84+
85+//line templates/base.qtpl:9
86 import (
87 qtio422016 "io"
88
89 qt422016 "github.com/valyala/quicktemplate"
90 )
91
92-//line templates/base.qtpl:7
93+//line templates/base.qtpl:9
94 var (
95 _ = qtio422016.Copy
96 _ = qt422016.AcquireByteBuffer
97 )
98
99-//line templates/base.qtpl:8
100+//line templates/base.qtpl:11
101 var Slug = ""
102+var Session = ""
103
104-//line templates/base.qtpl:12
105+func init() {
106+ Session = hex.EncodeToString(generateSmallTimeID())
107+}
108+
109+func generateSmallTimeID() []byte {
110+ b := make([]byte, 4)
111+ _, err := rand.Read(b)
112+ if err != nil {
113+ panic(err)
114+ }
115+ return b
116+}
117+
118+//line templates/base.qtpl:29
119 type Page interface {
120-//line templates/base.qtpl:12
121+//line templates/base.qtpl:29
122 Title(ctx context.Context) string
123-//line templates/base.qtpl:12
124+//line templates/base.qtpl:29
125 StreamTitle(qw422016 *qt422016.Writer, ctx context.Context)
126-//line templates/base.qtpl:12
127+//line templates/base.qtpl:29
128 WriteTitle(qq422016 qtio422016.Writer, ctx context.Context)
129-//line templates/base.qtpl:12
130+//line templates/base.qtpl:29
131 Content(ctx context.Context) string
132-//line templates/base.qtpl:12
133+//line templates/base.qtpl:29
134 StreamContent(qw422016 *qt422016.Writer, ctx context.Context)
135-//line templates/base.qtpl:12
136+//line templates/base.qtpl:29
137 WriteContent(qq422016 qtio422016.Writer, ctx context.Context)
138-//line templates/base.qtpl:12
139+//line templates/base.qtpl:29
140 Script(ctx context.Context) string
141-//line templates/base.qtpl:12
142+//line templates/base.qtpl:29
143 StreamScript(qw422016 *qt422016.Writer, ctx context.Context)
144-//line templates/base.qtpl:12
145+//line templates/base.qtpl:29
146 WriteScript(qq422016 qtio422016.Writer, ctx context.Context)
147-//line templates/base.qtpl:12
148+//line templates/base.qtpl:29
149 Navbar(ctx context.Context) string
150-//line templates/base.qtpl:12
151+//line templates/base.qtpl:29
152 StreamNavbar(qw422016 *qt422016.Writer, ctx context.Context)
153-//line templates/base.qtpl:12
154+//line templates/base.qtpl:29
155 WriteNavbar(qq422016 qtio422016.Writer, ctx context.Context)
156-//line templates/base.qtpl:12
157+//line templates/base.qtpl:29
158 }
159
160-//line templates/base.qtpl:21
161+//line templates/base.qtpl:38
162 func FromUInttoString(u *uint) string {
163 if u != nil {
164 return strconv.FormatUint(uint64(*u), 10)
165@@ -69,23 +89,23 @@ }
166 return ""
167 }
168
169-//line templates/base.qtpl:31
170+//line templates/base.qtpl:48
171 func TimeFormat(t time.Time) string {
172 return t.Format("02.01.2006")
173 }
174
175-//line templates/base.qtpl:36
176+//line templates/base.qtpl:53
177 func Ignore[T any](v T, _ error) T {
178 return v
179 }
180
181-//line templates/base.qtpl:42
182+//line templates/base.qtpl:59
183 func IsAuthenticationDisabled(ctx context.Context) bool {
184 t, ok := ctx.Value("disableAuthentication").(bool)
185 return ok && t
186 }
187
188-//line templates/base.qtpl:48
189+//line templates/base.qtpl:65
190 func IsLoggedIn(ctx context.Context) bool {
191 t, ok := ctx.Value("logged").(bool)
192 return ok && t
193@@ -93,9 +113,9 @@ }
194
195 // Page prints a page implementing Page interface.
196
197-//line templates/base.qtpl:55
198+//line templates/base.qtpl:72
199 func StreamPageTemplate(qw422016 *qt422016.Writer, p Page, ctx context.Context) {
200-//line templates/base.qtpl:55
201+//line templates/base.qtpl:72
202 qw422016.N().S(`
203 <!DOCTYPE html>
204 <html lang="en" data-bs-theme="light">
205@@ -103,19 +123,21 @@ <head>
206 <meta charset="utf-8">
207 <link rel="icon" href="data:,">
208 <title>`)
209-//line templates/base.qtpl:61
210+//line templates/base.qtpl:78
211 p.StreamTitle(qw422016, ctx)
212-//line templates/base.qtpl:61
213- qw422016.N().S(`</title>
214+//line templates/base.qtpl:78
215+ qw422016.N().S(`</title>
216 <link rel="stylesheet" href="/static/main`)
217-//line templates/base.qtpl:62
218+//line templates/base.qtpl:79
219 qw422016.E().S(Slug)
220-//line templates/base.qtpl:62
221+//line templates/base.qtpl:79
222 qw422016.N().S(`.css">
223- <link rel="stylesheet" href="/static/theme`)
224-//line templates/base.qtpl:63
225+ <link rel="stylesheet" href="/static/theme.`)
226+//line templates/base.qtpl:80
227+ qw422016.E().S(Session)
228+//line templates/base.qtpl:80
229 qw422016.E().S(Slug)
230-//line templates/base.qtpl:63
231+//line templates/base.qtpl:80
232 qw422016.N().S(`.css">
233 <html data-bs-theme="dark">
234 <meta content="text/html;charset=utf-8" http-equiv="Content-Type">
235@@ -123,53 +145,53 @@ <meta name="viewport" content="width=device-width, initial-scale=1" />
236 </head>
237 <body>
238 `)
239-//line templates/base.qtpl:69
240+//line templates/base.qtpl:86
241 p.StreamNavbar(qw422016, ctx)
242-//line templates/base.qtpl:69
243+//line templates/base.qtpl:86
244 qw422016.N().S(`
245 <div class="container">
246 `)
247-//line templates/base.qtpl:71
248+//line templates/base.qtpl:88
249 p.StreamContent(qw422016, ctx)
250-//line templates/base.qtpl:71
251+//line templates/base.qtpl:88
252 qw422016.N().S(`
253 </div>
254 </body>
255 `)
256-//line templates/base.qtpl:74
257+//line templates/base.qtpl:91
258 p.StreamScript(qw422016, ctx)
259-//line templates/base.qtpl:74
260+//line templates/base.qtpl:91
261 qw422016.N().S(`
262 <script>
263 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);
264 </script>
265 </html>
266 `)
267-//line templates/base.qtpl:79
268+//line templates/base.qtpl:96
269 }
270
271-//line templates/base.qtpl:79
272+//line templates/base.qtpl:96
273 func WritePageTemplate(qq422016 qtio422016.Writer, p Page, ctx context.Context) {
274-//line templates/base.qtpl:79
275+//line templates/base.qtpl:96
276 qw422016 := qt422016.AcquireWriter(qq422016)
277-//line templates/base.qtpl:79
278+//line templates/base.qtpl:96
279 StreamPageTemplate(qw422016, p, ctx)
280-//line templates/base.qtpl:79
281+//line templates/base.qtpl:96
282 qt422016.ReleaseWriter(qw422016)
283-//line templates/base.qtpl:79
284+//line templates/base.qtpl:96
285 }
286
287-//line templates/base.qtpl:79
288+//line templates/base.qtpl:96
289 func PageTemplate(p Page, ctx context.Context) string {
290-//line templates/base.qtpl:79
291+//line templates/base.qtpl:96
292 qb422016 := qt422016.AcquireByteBuffer()
293-//line templates/base.qtpl:79
294+//line templates/base.qtpl:96
295 WritePageTemplate(qb422016, p, ctx)
296-//line templates/base.qtpl:79
297+//line templates/base.qtpl:96
298 qs422016 := string(qb422016.B)
299-//line templates/base.qtpl:79
300+//line templates/base.qtpl:96
301 qt422016.ReleaseByteBuffer(qb422016)
302-//line templates/base.qtpl:79
303+//line templates/base.qtpl:96
304 return qs422016
305-//line templates/base.qtpl:79
306+//line templates/base.qtpl:96
307 }