git-pr

created pr with 3.1 on 2024-07-19T14:53:23Z · by c8ef7d19
added 3.2 on 2024-07-19T14:55:48Z · by c8ef7d19
1: 3b99dc0 < -: ------- feat: static assets
2: 8919af5 ! 1: 66cafc6 feat: static assets folder
3: 7346122 ! 2: 5e76ed3 fix(cli): access control for removing patchsets
4: d8792d5 < -: ------- feat: static folder
pr_reviewed on 2024-07-19T15:42:29Z · by 964fa508
changed status to reviewed on 2024-07-19T15:42:29Z · by 964fa508
added 3.4 on 2024-07-19T17:43:47Z · by c8ef7d19
1: cc56ea1 = 1: 0467f9e feat: static assets folder
2: ef749a4 = 2: da1730f review: typo and future enhancement comment
-: ------- > 3: c038404 refactor: per-file override for static folder
changed status to accepted on 2024-07-19T18:27:53Z · by 964fa508
cmds
checkout latest patchset:
ssh pr.pico.sh print 3 | git am -3
checkout any patchset in a patch request:
ssh pr.pico.sh print 3.[rev] | git am -3
add changes to patch request:
git format-patch main --stdout | ssh pr.pico.sh pr add 3
set PR to open (enables RSS notifications):
ssh pr.pico.sh pr open 3
set PR to draft (stops RSS notifications):
ssh pr.pico.sh pr draft 3

Patchset 3.2 on 2024-07-19T14:55:48Z · commit 66cafc6

feat: static assets folder
Eric Bower 2024-07-19T04:27:17Z
Create a static folder that will be served as-is with the ability for
users to bring-their-own static folder.

If we detect `data_dir/static/` we will serve that instead of the
embedded one we provide by default.
Semantic diff summary
2 added, 4 modified, 0 signature changed, 0 removed across 2 analyzed files (5 files skipped: unsupported file type)
+5 -0 Makefile #
......@@ -26,3 +26,8 @@ bp: bp-setup
2626 $(DOCKER_BUILDX_BUILD) -t "ghcr.io/picosh/pico/git-ssh:$(DOCKER_TAG)" --target release-ssh .
2727 $(DOCKER_BUILDX_BUILD) -t "ghcr.io/picosh/pico/git-web:$(DOCKER_TAG)" --target release-web .
2828 .PHONY: bp
29+
30+smol:
31+ curl https://pico.sh/smol.css -o ./static/smol.css
32+ cat patches/smol.diff | git apply
33+.PHONY: smol
+53 -0 patches/smol.diff #
......@@ -0,0 +1,53 @@
1+diff --git a/static/smol.css b/static/smol.css
2+index e9b59ec..9e9d925 100644
3+--- a/static/smol.css
4++++ b/static/smol.css
5+@@ -15,48 +15,6 @@
6+ box-shadow: none;
7+ }
8+
9+-@media (prefers-color-scheme: light) {
10+- :root {
11+- --main-hue: 250;
12+- --white: #2e3f53;
13+- --white-light: #cfe0f4;
14+- --white-dark: #6c6a6a;
15+- --code: #52576f;
16+- --pre: #e1e7ee;
17+- --bg-color: #f4f4f4;
18+- --text-color: #24292f;
19+- --link-color: #005cc5;
20+- --visited: #6f42c1;
21+- --blockquote: #005cc5;
22+- --blockquote-bg: #cfe0f4;
23+- --hover: #c11e7a;
24+- --grey: #ccc;
25+- --grey-light: #6a708e;
26+- --shadow: #e8e8e8;
27+- }
28+-}
29+-
30+-@media (prefers-color-scheme: dark) {
31+- :root {
32+- --main-hue: 250;
33+- --white: #f2f2f2;
34+- --white-light: #f2f2f2;
35+- --white-dark: #e8e8e8;
36+- --code: #414558;
37+- --pre: #252525;
38+- --bg-color: #282a36;
39+- --text-color: #f2f2f2;
40+- --link-color: #8be9fd;
41+- --visited: #bd93f9;
42+- --blockquote: #bd93f9;
43+- --blockquote-bg: #353548;
44+- --hover: #ff80bf;
45+- --grey: #414558;
46+- --grey-light: #6a708e;
47+- --shadow: #252525;
48+- }
49+-}
50+-
51+ html {
52+ background-color: var(--bg-color);
53+ color: var(--text-color);
+1 -1 ssh.go #
......@@ -35,7 +35,7 @@ func GitSshServer(cfg *GitCfg) {
3535 dbpath := filepath.Join(cfg.DataDir, "pr.db")
3636 dbh, err := Open(dbpath, cfg.Logger)
3737 if err != nil {
38- panic(fmt.Sprintf("cannot find database file, check folder and perms: %s", dbpath))
38+ panic(fmt.Sprintf("cannot find database file, check folder and perms: %s: %s", dbpath, err))
3939 }
4040
4141 be := &Backend{
+726 -0 static/smol.css #
......@@ -0,0 +1,726 @@
1+*,
2+::before,
3+::after {
4+ box-sizing: border-box;
5+}
6+
7+::-moz-focus-inner {
8+ border-style: none;
9+ padding: 0;
10+}
11+:-moz-focusring {
12+ outline: 1px dotted ButtonText;
13+}
14+:-moz-ui-invalid {
15+ box-shadow: none;
16+}
17+
18+html {
19+ background-color: var(--bg-color);
20+ color: var(--text-color);
21+ font-size: 18px;
22+ line-height: 1.5;
23+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen,
24+ Ubuntu, Cantarell, "Fira Sans", "Droid Sans", "Helvetica Neue", Arial,
25+ sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
26+ -webkit-text-size-adjust: 100%;
27+ -moz-tab-size: 4;
28+ -o-tab-size: 4;
29+ tab-size: 4;
30+}
31+
32+body {
33+ margin: 0 auto;
34+}
35+
36+img {
37+ max-width: 100%;
38+ height: auto;
39+}
40+
41+b,
42+strong {
43+ font-weight: bold;
44+}
45+
46+code,
47+kbd,
48+samp,
49+pre {
50+ font-family: ui-monospace, SFMono-Regular, Consolas, "Liberation Mono", Menlo,
51+ monospace;
52+}
53+
54+code,
55+kbd,
56+samp {
57+ border: 2px solid var(--code);
58+}
59+
60+pre > code {
61+ background-color: inherit;
62+ padding: 0;
63+ border: none;
64+}
65+
66+code {
67+ font-size: 90%;
68+ border-radius: 0.3rem;
69+ padding: 0.1rem 0.3rem;
70+}
71+
72+pre {
73+ font-size: 14px;
74+ border-radius: 5px;
75+ padding: 1rem;
76+ margin: 1rem 0;
77+ overflow-x: auto;
78+ background-color: var(--pre) !important;
79+}
80+
81+small {
82+ font-size: 0.8rem;
83+}
84+
85+summary {
86+ display: list-item;
87+ cursor: pointer;
88+}
89+
90+h1,
91+h2,
92+h3,
93+h4 {
94+ margin: 0;
95+ padding: 0.5rem 0 0 0;
96+ border: 0;
97+ font-style: normal;
98+ font-weight: inherit;
99+ font-size: inherit;
100+}
101+
102+path {
103+ fill: var(--text-color);
104+ stroke: var(--text-color);
105+}
106+
107+hr {
108+ color: inherit;
109+ border: 0;
110+ margin: 0;
111+ height: 2px;
112+ background: var(--grey);
113+ margin: 1rem auto;
114+ text-align: center;
115+}
116+
117+a {
118+ text-decoration: none;
119+ color: var(--link-color);
120+}
121+
122+a:hover,
123+a:visited:hover {
124+ text-decoration: underline;
125+ color: var(--hover);
126+}
127+
128+a:visited {
129+ color: var(--visited);
130+}
131+
132+a.link-grey {
133+ text-decoration: underline;
134+ color: var(--white);
135+}
136+
137+a.link-grey:visited {
138+ color: var(--white);
139+}
140+
141+section {
142+ margin-bottom: 1.4rem;
143+}
144+
145+section:last-child {
146+ margin-bottom: 0;
147+}
148+
149+header {
150+ margin: 1rem auto;
151+}
152+
153+p {
154+ margin: 0.5rem 0;
155+}
156+
157+article {
158+ overflow-wrap: break-word;
159+}
160+
161+blockquote {
162+ border-left: 5px solid var(--blockquote);
163+ background-color: var(--blockquote-bg);
164+ padding: 0.5rem 0.75rem;
165+ margin: 0.5rem 0;
166+}
167+
168+blockquote > p {
169+ margin: 0;
170+}
171+
172+blockquote code {
173+ border: 1px solid var(--blockquote);
174+}
175+
176+ul,
177+ol {
178+ padding: 0 0 0 1rem;
179+ list-style-position: outside;
180+}
181+
182+ul[style*="list-style-type: none;"] {
183+ padding: 0;
184+}
185+
186+li {
187+ margin: 0.5rem 0;
188+}
189+
190+li > pre {
191+ padding: 0;
192+}
193+
194+footer {
195+ text-align: center;
196+ margin-bottom: 4rem;
197+}
198+
199+dt {
200+ font-weight: bold;
201+}
202+
203+dd {
204+ margin-left: 0;
205+}
206+
207+dd:not(:last-child) {
208+ margin-bottom: 0.5rem;
209+}
210+
211+figure {
212+ margin: 0;
213+}
214+
215+.container {
216+ max-width: 50em;
217+ width: 100%;
218+}
219+
220+.container-sm {
221+ max-width: 40em;
222+ width: 100%;
223+}
224+
225+.container-center {
226+ width: 100%;
227+ height: 100%;
228+ display: flex;
229+ justify-content: center;
230+}
231+
232+.mono {
233+ font-family: ui-monospace, SFMono-Regular, Consolas, "Liberation Mono", Menlo,
234+ monospace;
235+}
236+
237+.link-alt-adj,
238+.link-alt-adj:visited,
239+.link-alt-adj:visited:hover,
240+.link-alt-adj:hover {
241+ color: var(--link-color);
242+ text-decoration: none;
243+}
244+
245+.link-alt-adj:visited:hover,
246+.link-alt-adj:hover {
247+ text-decoration: underline;
248+}
249+
250+.link-alt-hover,
251+.link-alt-hover:visited,
252+.link-alt-hover:visited:hover,
253+.link-alt-hover:hover {
254+ color: var(--hover);
255+ text-decoration: none;
256+}
257+
258+.link-alt-hover:visited:hover,
259+.link-alt-hover:hover {
260+ text-decoration: underline;
261+}
262+
263+.link-alt,
264+.link-alt:visited,
265+.link-alt:visited:hover,
266+.link-alt:hover {
267+ color: var(--white);
268+ text-decoration: none;
269+}
270+
271+.link-alt:visited:hover,
272+.link-alt:hover {
273+ text-decoration: underline;
274+}
275+
276+.text-3xl {
277+ font-size: 2.5rem;
278+}
279+
280+.text-2xl {
281+ font-size: 1.9rem;
282+ line-height: 1.15;
283+}
284+
285+.text-xl {
286+ font-size: 1.55rem;
287+ line-height: 1.15;
288+}
289+
290+.text-lg {
291+ font-size: 1.35rem;
292+ line-height: 1.15;
293+}
294+
295+.text-md {
296+ font-size: 1.15rem;
297+ line-height: 1.15;
298+}
299+
300+.text-sm {
301+ font-size: 0.875rem;
302+}
303+
304+.text-xs {
305+ font-size: 0.775rem;
306+}
307+
308+.cursor-pointer {
309+ cursor: pointer;
310+}
311+
312+.w-full {
313+ width: 100%;
314+}
315+
316+.h-full {
317+ height: 100%;
318+}
319+
320+.border {
321+ border: 2px solid var(--grey-light);
322+}
323+
324+.text-left {
325+ text-align: left;
326+}
327+
328+.text-center {
329+ text-align: center;
330+}
331+
332+.text-underline {
333+ border-bottom: 3px solid var(--text-color);
334+ padding-bottom: 3px;
335+}
336+
337+.text-hdr {
338+ color: var(--hover);
339+}
340+
341+.text-underline-hdr {
342+ border-bottom: 3px solid var(--hover);
343+ padding-bottom: 3px;
344+}
345+
346+.font-bold {
347+ font-weight: bold;
348+}
349+
350+.font-italic {
351+ font-style: italic;
352+}
353+
354+.inline {
355+ display: inline;
356+}
357+
358+.inline-block {
359+ display: inline-block;
360+}
361+
362+.max-w-half {
363+ max-width: 50%;
364+}
365+
366+.h-screen {
367+ height: 100vh;
368+}
369+
370+.w-screen {
371+ width: 100vw;
372+}
373+
374+.flex {
375+ display: flex;
376+}
377+
378+.flex-col {
379+ flex-direction: column;
380+}
381+
382+.items-center {
383+ align-items: center;
384+}
385+
386+.m-0 {
387+ margin: 0;
388+}
389+
390+.mt {
391+ margin-top: 0.5rem;
392+}
393+
394+.mt-2 {
395+ margin-top: 1rem;
396+}
397+
398+.mt-4 {
399+ margin-top: 2rem;
400+}
401+
402+.mt-8 {
403+ margin-top: 4rem;
404+}
405+
406+.mb {
407+ margin-bottom: 0.5rem;
408+}
409+
410+.mb-2 {
411+ margin-bottom: 1rem;
412+}
413+
414+.mb-4 {
415+ margin-bottom: 2rem;
416+}
417+
418+.mb-8 {
419+ margin-bottom: 4rem;
420+}
421+
422+.mb-16 {
423+ margin-bottom: 8rem;
424+}
425+
426+.mr {
427+ margin-right: 0.5rem;
428+}
429+
430+.ml-sm {
431+ margin-left: 0.25rem;
432+}
433+
434+.ml {
435+ margin-left: 0.5rem;
436+}
437+
438+.pt-0 {
439+ padding-top: 0;
440+}
441+
442+.my {
443+ margin-top: 0.5rem;
444+ margin-bottom: 0.5rem;
445+}
446+
447+.my-2 {
448+ margin-top: 1rem;
449+ margin-bottom: 1rem;
450+}
451+
452+.my-4 {
453+ margin-top: 2rem;
454+ margin-bottom: 2rem;
455+}
456+
457+.my-8 {
458+ margin-top: 4rem;
459+ margin-bottom: 4rem;
460+}
461+
462+.mx {
463+ margin-left: 0.5rem;
464+ margin-right: 0.5rem;
465+}
466+
467+.mx-2 {
468+ margin-left: 1rem;
469+ margin-right: 1rem;
470+}
471+
472+.m-1 {
473+ margin: 0.5rem;
474+}
475+
476+.p-1 {
477+ padding: 0.5rem;
478+}
479+
480+.p-0 {
481+ padding: 0;
482+}
483+
484+.px-2 {
485+ padding-left: 1rem;
486+ padding-right: 1rem;
487+}
488+
489+.px-4 {
490+ padding-left: 2rem;
491+ padding-right: 2rem;
492+}
493+
494+.py {
495+ padding-top: 0.5rem;
496+ padding-bottom: 0.5rem;
497+}
498+
499+.py-2 {
500+ padding-top: 1rem;
501+ padding-bottom: 1rem;
502+}
503+
504+.py-4 {
505+ padding-top: 2rem;
506+ padding-bottom: 2rem;
507+}
508+
509+.py-8 {
510+ padding-top: 4rem;
511+ padding-bottom: 4rem;
512+}
513+
514+.justify-between {
515+ justify-content: space-between;
516+}
517+
518+.justify-center {
519+ justify-content: center;
520+}
521+
522+.gap {
523+ gap: 0.5rem;
524+}
525+
526+.gap-2 {
527+ gap: 1rem;
528+}
529+
530+.group {
531+ display: flex;
532+ flex-direction: column;
533+ gap: 0.5rem;
534+}
535+
536+.group-2 {
537+ display: flex;
538+ flex-direction: column;
539+ gap: 1rem;
540+}
541+
542+.group-h {
543+ display: flex;
544+ gap: 0.5rem;
545+ align-items: center;
546+}
547+
548+.flex-1 {
549+ flex: 1;
550+}
551+
552+.items-end {
553+ align-items: end;
554+}
555+
556+.items-start {
557+ align-items: start;
558+}
559+
560+.justify-end {
561+ justify-content: end;
562+}
563+
564+.font-grey-light {
565+ color: var(--grey-light);
566+}
567+
568+.hidden {
569+ display: none;
570+}
571+
572+.align-right {
573+ text-align: right;
574+}
575+
576+/* ==== MARKDOWN ==== */
577+
578+.md h1,
579+.md h2,
580+.md h3,
581+.md h4 {
582+ padding: 0;
583+ margin: 1.5rem 0 0.9rem 0;
584+ font-weight: bold;
585+}
586+
587+.md h1 a,
588+.md h2 a,
589+.md h3 a,
590+.md h4 a {
591+ color: var(--grey-light);
592+ text-decoration: none;
593+}
594+
595+.md h1 {
596+ font-size: 1.6rem;
597+ line-height: 1.15;
598+ border-bottom: 2px solid var(--grey);
599+ padding-bottom: 0.7rem;
600+}
601+
602+.md h2 {
603+ font-size: 1.3rem;
604+ line-height: 1.15;
605+ color: var(--white-dark);
606+}
607+
608+.md h3 {
609+ font-size: 1.2rem;
610+ color: var(--white-dark);
611+}
612+
613+.md h4 {
614+ font-size: 1rem;
615+ color: var(--white-dark);
616+}
617+
618+/* ==== HELPERS ==== */
619+
620+.logo-header {
621+ line-height: 1;
622+ display: inline-block;
623+ background-color: #FF79C6;
624+ background-image: linear-gradient(to right, #FF5555, #FF79C6, #F8F859);
625+ color: transparent;
626+ background-clip: text;
627+ border: 3px solid #FF79C6;
628+ padding: 8px 10px 10px 10px;
629+ border-radius: 10px;
630+ box-shadow: 0px 5px 0px 0px var(--shadow);
631+ background-size: 100%;
632+ -webkit-background-clip: text;
633+ -moz-background-clip: text;
634+ -webkit-text-fill-color: transparent;
635+ -moz-text-fill-color: transparent;
636+}
637+
638+.btn {
639+ border: 2px solid var(--link-color);
640+ color: var(--link-color);
641+ padding: 0.4rem 1rem;
642+ font-weight: bold;
643+ display: inline-block;
644+}
645+
646+.btn-link,
647+.btn-link:visited {
648+ border: 2px solid var(--link-color);
649+ color: var(--link-color);
650+ padding: 0.4rem 1rem;
651+ text-decoration: none;
652+ font-weight: bold;
653+ display: inline-block;
654+}
655+
656+.btn-link:visited:hover,
657+.btn-link:hover {
658+ border: 2px solid var(--hover);
659+}
660+
661+.btn-link-alt,
662+.btn-link-alt:visited {
663+ border: 2px solid var(--white);
664+ color: var(--white);
665+}
666+
667+.box {
668+ border: 2px solid var(--grey-light);
669+ padding: 0.5rem 0.75rem;
670+}
671+
672+.box-sm {
673+ border: 2px solid var(--grey-light);
674+ padding: 0.15rem 0.35rem;
675+}
676+
677+.box-alert {
678+ border: 2px solid var(--hover);
679+ padding: 0.5rem 0.75rem;
680+}
681+
682+.box-sm-alert {
683+ border: 2px solid var(--hover);
684+ padding: 0.15rem 0.35rem;
685+}
686+
687+.list-none {
688+ list-style-type: none;
689+}
690+
691+.list-disc {
692+ list-style-type: disc;
693+}
694+
695+.list-decimal {
696+ list-style-type: decimal;
697+}
698+
699+.pill {
700+ border: 1px solid var(--link-color);
701+ color: var(--link-color);
702+}
703+
704+.pill-alert {
705+ border: 1px solid var(--hover);
706+ color: var(--hover);
707+}
708+
709+.pill-info {
710+ border: 1px solid var(--visited);
711+ color: var(--visited);
712+}
713+
714+@media only screen and (max-width: 40em) {
715+ body {
716+ padding: 0 1rem;
717+ }
718+
719+ header {
720+ margin: 0;
721+ }
722+
723+ .flex-collapse {
724+ flex-direction: column;
725+ }
726+}
+18 -0 static/vars.css #
......@@ -0,0 +1,18 @@
1+:root {
2+ --main-hue: 250;
3+ --white: #f2f2f2;
4+ --white-light: #f2f2f2;
5+ --white-dark: #e8e8e8;
6+ --code: #414558;
7+ --pre: #252525;
8+ --bg-color: #282a36;
9+ --text-color: #f2f2f2;
10+ --link-color: #8be9fd;
11+ --visited: #bd93f9;
12+ --blockquote: #bd93f9;
13+ --blockquote-bg: #353548;
14+ --hover: #ff80bf;
15+ --grey: #414558;
16+ --grey-light: #6a708e;
17+ --shadow: #252525;
18+}
+3 -2 tmpl/base.html #
......@@ -9,8 +9,9 @@
99 <meta name="keywords" content="git, collaboration, patch, requests" />
1010 {{template "meta" .}}
1111
12- <link rel="stylesheet" href="https://pico.sh/smol.css" />
13- <link rel="stylesheet" href="https://pico.sh/syntax.css" />
12+ <link rel="stylesheet" href="/static/smol.css" />
13+ <link rel="stylesheet" href="/static/vars.css" />
14+ <link rel="stylesheet" href="/syntax.css" />
1415 </head>
1516 <body class="container">{{template "body" .}}</body>
1617 </html>
+68 -1 web.go #
......@@ -6,8 +6,12 @@ import (
66 "embed"
77 "fmt"
88 "html/template"
9+ "io"
10+ "io/fs"
911 "log/slog"
12+ "mime"
1013 "net/http"
14+ "os"
1115 "path/filepath"
1216 "slices"
1317 "strconv"
......@@ -23,6 +27,9 @@ import (
2327 //go:embed tmpl/*
2428 var tmplFS embed.FS
2529
30+//go:embed static/*
31+var staticFS embed.FS
32+
2633 type WebCtx struct {
2734 Pr *PrCmd
2835 Backend *Backend
......@@ -618,13 +625,68 @@ func chromaStyleHandler(w http.ResponseWriter, r *http.Request) {
618625 }
619626 }
620627
628+func serveFile(staticfs fs.FS) func(w http.ResponseWriter, r *http.Request) {
629+ return func(w http.ResponseWriter, r *http.Request) {
630+ web, err := getWebCtx(r)
631+ if err != nil {
632+ w.WriteHeader(http.StatusUnprocessableEntity)
633+ return
634+ }
635+ logger := web.Logger
636+
637+ file := r.PathValue("file")
638+ logger.Info("serving file", "file", file, "fs", staticfs)
639+ reader, err := staticfs.Open(file)
640+ if err != nil {
641+ logger.Error(err.Error())
642+ http.Error(w, "file not found", 404)
643+ return
644+ }
645+ contents, err := io.ReadAll(reader)
646+ if err != nil {
647+ logger.Error(err.Error())
648+ http.Error(w, "file not found", 404)
649+ return
650+ }
651+ contentType := mime.TypeByExtension(filepath.Ext(file))
652+ if contentType == "" {
653+ contentType = http.DetectContentType(contents)
654+ }
655+ w.Header().Add("Content-Type", contentType)
656+
657+ _, err = w.Write(contents)
658+ if err != nil {
659+ logger.Error(err.Error())
660+ http.Error(w, "server error", 500)
661+ return
662+ }
663+ }
664+}
665+
666+func getFileSystem(logger *slog.Logger, ffs embed.FS, datadir string, dirName string) (fs.FS, error) {
667+ dir := filepath.Join(datadir, dirName)
668+ _, err := os.Stat(dir)
669+ if err == nil {
670+ logger.Info("found folder in data_dir", "dir", dir)
671+ return os.DirFS(dir), nil
672+ }
673+
674+ logger.Info("using embeded folder", "dir", dir)
675+ fsys, err := fs.Sub(ffs, dirName)
676+ if err != nil {
677+ return nil, err
678+ }
679+
680+ return fsys, nil
681+}
682+
621683 func StartWebServer(cfg *GitCfg) {
622684 addr := fmt.Sprintf("%s:%s", cfg.Host, cfg.WebPort)
623685
624686 dbpath := filepath.Join(cfg.DataDir, "pr.db")
625687 dbh, err := Open(dbpath, cfg.Logger)
626688 if err != nil {
627- panic(fmt.Sprintf("cannot find database file, check folder and perms: %s", dbpath))
689+ panic(fmt.Sprintf("cannot find database file, check folder and perms: %s: %s", dbpath, err))
628690 }
629691
630692 be := &Backend{
......@@ -659,6 +721,11 @@ func StartWebServer(cfg *GitCfg) {
659721 http.HandleFunc("GET /", ctxMdw(ctx, repoListHandler))
660722 http.HandleFunc("GET /syntax.css", ctxMdw(ctx, chromaStyleHandler))
661723 http.HandleFunc("GET /rss", ctxMdw(ctx, rssHandler))
724+ filesys, err := getFileSystem(cfg.Logger, staticFS, cfg.DataDir, "static")
725+ if err != nil {
726+ panic(err)
727+ }
728+ http.HandleFunc("GET /static/{file}", ctxMdw(ctx, serveFile(filesys)))
662729
663730 cfg.Logger.Info("starting web server", "addr", addr)
664731 err = http.ListenAndServe(addr, nil)
Back to top