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