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