aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--assets/style.css2
-rw-r--r--css/main.scss8
-rw-r--r--gopherproxy.go78
-rw-r--r--template.go14
4 files changed, 64 insertions, 38 deletions
diff --git a/assets/style.css b/assets/style.css
index 0f4583f..919746c 100644
--- a/assets/style.css
+++ b/assets/style.css
@@ -1 +1 @@
@font-face{font-family:'Iosevka Term SS03';font-style:normal;font-weight:normal;src:url("/iosevka-term-ss03-regular.woff2") format("woff2"),url("/iosevka-term-ss03-regular.woff") format("woff")}body{margin:0;padding:0;background-color:#14171a;color:#cad1d8;font-family:"Iosevka Term SS03","IBM Plex Mono","Fira Code","Fira Mono","Roboto Mono","Droid Sans Mono",Monaco,Consolas,Courier,monospace;font-size:1.0625em;line-height:1.5}h1,h2,h3,h4,h5,h6{font:inherit;color:#fff;margin:0}button{background:none;border:0;padding:0;color:#fff;font:inherit;text-decoration:underline;cursor:pointer}button:focus{outline:1px dotted currentColor}img{display:inline-block;vertical-align:top;max-width:8em;margin:.1em 0}img::selection{background-color:rgba(239,198,138,0.35)}img.expanded{max-width:40em;max-width:80ch}img.faded{opacity:.5}strong{font-weight:normal}::selection{color:#000;background-color:rgba(239,198,138,0.996)}:link{color:#fff}:visited{color:#cad1d8}:link:hover,:visited:hover{color:#fff}.header-base{display:flex;flex-direction:row;align-items:center;justify-content:space-between;border-bottom:1px solid #353a3f}.header{padding:.9em 1em;color:#5d646a}.location{flex:0 1 auto;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;text-overflow:ellipsis '|';margin-right:.5em;color:#5d646a}.location__prefix{margin-right:.3em;color:#5d646a}@media (hover: hover){.location__prefix:hover{color:#fff}}.location__prefix--mobile{display:none}.location__slash{margin:0 .3em}.location__uripart{color:#fff}.location__uripart+.location__slash+.location__uripart{color:#cad1d8}.location__uripart:link:hover,.location__uripart:visited:hover{color:#fff}.actions{flex:0 0 auto}.actions :visited{color:#fff}.action{display:inline}.action+.action::before{content:' | '}.wrap{padding:2em 1em;text-align:center}.content{box-sizing:border-box;display:inline-block;min-width:0;max-width:100%;margin:0;padding:0;text-align:left;font:inherit}.content--wrap{white-space:pre-wrap;word-wrap:break-word}.content--has-type-annotations{padding-left:3em;padding-left:5ch}.type-annotation{margin-left:-3em;margin-left:-5ch;color:#929ba3;white-space:pre}.modal{position:fixed;top:0;left:0;z-index:100;display:none;width:100%;height:100%;box-sizing:border-box;padding:2em;background-color:rgba(0,0,0,0.75)}.modal--visible{display:block}.modal__content{max-width:30em;padding:1.5em 1.8em;margin:0 auto;background-color:#14171a;box-shadow:0 .3em 2em #000;text-align:left}.modal__head{padding-bottom:.75em;margin-bottom:1.5em}.modal__title{padding-right:1em;white-space:nowrap;text-overflow:ellipsis;overflow:hidden;text-transform:uppercase}.setting{display:flex;flex-direction:row;align-items:baseline;justify-content:space-between}.setting::after{order:2;flex:1 1 auto;display:block;height:0;margin:0 .5em;border-bottom:2px dotted #353a3f;content:''}.setting__label{order:1}.setting__value{order:3}@media screen and (max-width: 800px){body{font-size:1em}.modal{padding:1em}.modal__content{padding:1em 1.3em}}@media screen and (max-width: 500px){.location__prefix{display:none}.location__prefix--mobile{display:inline}.action{display:block}.action+.action::before{content:''}}@media screen and (max-width: 280px){.location__prefix--mobile{display:none}} body{margin:0;padding:0;background-color:#14171a;color:#cad1d8;font-family:"Iosevka Term SS03","IBM Plex Mono","Fira Code","Fira Mono","Roboto Mono","Droid Sans Mono",Monaco,Consolas,Courier,monospace;font-size:1.0625em;line-height:1.5}h1,h2,h3,h4,h5,h6{font:inherit;color:#fff;margin:0}button{background:none;border:0;padding:0;color:#fff;font:inherit;text-decoration:underline;cursor:pointer}button:focus{outline:1px dotted currentColor}img{display:inline-block;vertical-align:top;max-width:8em;margin:.1em 0}img::selection{background-color:rgba(239,198,138,0.35)}img.expanded{max-width:40em;max-width:80ch}img.faded{opacity:.5}strong{font-weight:normal}::selection{color:#000;background-color:rgba(239,198,138,0.996)}:link{color:#fff}:visited{color:#cad1d8}:link:hover,:visited:hover{color:#fff}.header-base{display:flex;flex-direction:row;align-items:center;justify-content:space-between;border-bottom:1px solid #353a3f}.header{padding:.9em 1em;color:#5d646a}.location{flex:0 1 auto;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;text-overflow:ellipsis '|';margin-right:.5em;color:#5d646a}.location__prefix{margin-right:.3em;color:#5d646a}@media (hover: hover){.location__prefix:hover{color:#fff}}.location__prefix--mobile{display:none}.location__slash{margin:0 .3em}.location__uripart{color:#fff}.location__uripart+.location__slash+.location__uripart{color:#cad1d8}.location__uripart:link:hover,.location__uripart:visited:hover{color:#fff}.actions{flex:0 0 auto}.actions :visited{color:#fff}.action{display:inline}.action+.action::before{content:' | '}.wrap{padding:2em 1em;text-align:center}.content{box-sizing:border-box;display:inline-block;min-width:0;max-width:100%;margin:0;padding:0;text-align:left;font:inherit}.content--wrap{white-space:pre-wrap;word-wrap:break-word}.content--has-type-annotations{padding-left:3em;padding-left:5ch}.type-annotation{margin-left:-3em;margin-left:-5ch;color:#929ba3;white-space:pre}.modal{position:fixed;top:0;left:0;z-index:100;display:none;width:100%;height:100%;box-sizing:border-box;padding:2em;background-color:rgba(0,0,0,0.75)}.modal--visible{display:block}.modal__content{max-width:30em;padding:1.5em 1.8em;margin:0 auto;background-color:#14171a;box-shadow:0 .3em 2em #000;text-align:left}.modal__head{padding-bottom:.75em;margin-bottom:1.5em}.modal__title{padding-right:1em;white-space:nowrap;text-overflow:ellipsis;overflow:hidden;text-transform:uppercase}.setting{display:flex;flex-direction:row;align-items:baseline;justify-content:space-between}.setting::after{order:2;flex:1 1 auto;display:block;height:0;margin:0 .5em;border-bottom:2px dotted #353a3f;content:''}.setting__label{order:1}.setting__value{order:3}@media screen and (max-width: 800px){body{font-size:1em}.modal{padding:1em}.modal__content{padding:1em 1.3em}}@media screen and (max-width: 500px){.location__prefix{display:none}.location__prefix--mobile{display:inline}.action{display:block}.action+.action::before{content:''}}@media screen and (max-width: 280px){.location__prefix--mobile{display:none}}
diff --git a/css/main.scss b/css/main.scss
index be44d5f..f746fa1 100644
--- a/css/main.scss
+++ b/css/main.scss
@@ -12,14 +12,6 @@ $sel-text: #000;
12 12
13$font-monospace: 'Iosevka Term SS03', 'IBM Plex Mono', 'Fira Code', 'Fira Mono', 'Roboto Mono', 'Droid Sans Mono', Monaco, Consolas, Courier, monospace; 13$font-monospace: 'Iosevka Term SS03', 'IBM Plex Mono', 'Fira Code', 'Fira Mono', 'Roboto Mono', 'Droid Sans Mono', Monaco, Consolas, Courier, monospace;
14 14
15@font-face {
16 font-family: 'Iosevka Term SS03';
17 font-style: normal;
18 font-weight: normal;
19 src: url('/iosevka-term-ss03-regular.woff2') format('woff2'),
20 url('/iosevka-term-ss03-regular.woff') format('woff');
21}
22
23// 15//
24// Basic element styles 16// Basic element styles
25 17
diff --git a/gopherproxy.go b/gopherproxy.go
index 62b7446..1fcd29f 100644
--- a/gopherproxy.go
+++ b/gopherproxy.go
@@ -2,6 +2,7 @@ package gopherproxy
2 2
3import ( 3import (
4 "bytes" 4 "bytes"
5 "crypto/md5"
5 "fmt" 6 "fmt"
6 "html" 7 "html"
7 "html/template" 8 "html/template"
@@ -28,7 +29,14 @@ type Item struct {
28 Text string 29 Text string
29} 30}
30 31
31func renderDirectory(w http.ResponseWriter, tpl *template.Template, styletext string, jstext string, uri string, hostport string, d gopher.Directory) error { 32type AssetHashList struct {
33 Style string
34 JS string
35 FontW string
36 FontW2 string
37}
38
39func renderDirectory(w http.ResponseWriter, tpl *template.Template, assetHashList AssetHashList, uri string, hostport string, d gopher.Directory) error {
32 var title string 40 var title string
33 41
34 out := make([]Item, len(d.Items)) 42 out := make([]Item, len(d.Items))
@@ -78,13 +86,12 @@ func renderDirectory(w http.ResponseWriter, tpl *template.Template, styletext st
78 } 86 }
79 87
80 return tpl.Execute(w, struct { 88 return tpl.Execute(w, struct {
81 Title string 89 Title string
82 URI string 90 URI string
83 Style string 91 AssetHashList AssetHashList
84 Script string 92 Lines []Item
85 Lines []Item 93 RawText string
86 RawText string 94 }{title, fmt.Sprintf("%s/%s", hostport, uri), assetHashList, out, ""})
87 }{title, fmt.Sprintf("%s/%s", hostport, uri), styletext, jstext, out, ""})
88} 95}
89 96
90// GopherHandler returns a Handler that proxies requests 97// GopherHandler returns a Handler that proxies requests
@@ -92,7 +99,7 @@ func renderDirectory(w http.ResponseWriter, tpl *template.Template, styletext st
92// to the request path and renders the content using the provided template. 99// to the request path and renders the content using the provided template.
93// The optional robots parameters points to a robotstxt.RobotsData struct 100// The optional robots parameters points to a robotstxt.RobotsData struct
94// to test user agents against a configurable robotst.txt file. 101// to test user agents against a configurable robotst.txt file.
95func GopherHandler(tpl *template.Template, robotsdata *robotstxt.RobotsData, robotsdebug bool, styletext string, jstext string, uri string) http.HandlerFunc { 102func GopherHandler(tpl *template.Template, robotsdata *robotstxt.RobotsData, assetHashList AssetHashList, robotsdebug bool, uri string) http.HandlerFunc {
96 return func(w http.ResponseWriter, req *http.Request) { 103 return func(w http.ResponseWriter, req *http.Request) {
97 agent := req.UserAgent() 104 agent := req.UserAgent()
98 path := strings.TrimPrefix(req.URL.Path, "/") 105 path := strings.TrimPrefix(req.URL.Path, "/")
@@ -141,13 +148,12 @@ func GopherHandler(tpl *template.Template, robotsdata *robotstxt.RobotsData, rob
141 buf := new(bytes.Buffer) 148 buf := new(bytes.Buffer)
142 buf.ReadFrom(res.Body) 149 buf.ReadFrom(res.Body)
143 tpl.Execute(w, struct { 150 tpl.Execute(w, struct {
144 Title string 151 Title string
145 URI string 152 URI string
146 Style string 153 AssetHashList AssetHashList
147 Script string 154 RawText string
148 RawText string 155 Lines []Item
149 Lines []Item 156 }{uri, fmt.Sprintf("%s/%s", hostport, uri), assetHashList, buf.String(), nil})
150 }{uri, fmt.Sprintf("%s/%s", hostport, uri), styletext, jstext, buf.String(), nil})
151 } else if parts[1] == "T" { 157 } else if parts[1] == "T" {
152 _, _, err = vips.NewTransform(). 158 _, _, err = vips.NewTransform().
153 Load(res.Body). 159 Load(res.Body).
@@ -160,7 +166,7 @@ func GopherHandler(tpl *template.Template, robotsdata *robotstxt.RobotsData, rob
160 io.Copy(w, res.Body) 166 io.Copy(w, res.Body)
161 } 167 }
162 } else { 168 } else {
163 if err := renderDirectory(w, tpl, styletext, jstext, uri, hostport, res.Dir); err != nil { 169 if err := renderDirectory(w, tpl, assetHashList, uri, hostport, res.Dir); err != nil {
164 io.WriteString(w, fmt.Sprintf("<b>Error:</b><pre>%s</pre>", err)) 170 io.WriteString(w, fmt.Sprintf("<b>Error:</b><pre>%s</pre>", err))
165 return 171 return
166 } 172 }
@@ -190,10 +196,27 @@ func FaviconHandler(favicondata []byte) http.HandlerFunc {
190 } 196 }
191 197
192 w.Header().Set("Content-Type", "image/vnd.microsoft.icon") 198 w.Header().Set("Content-Type", "image/vnd.microsoft.icon")
199 w.Header().Set("Cache-Control", "max-age=2592000")
193 w.Write(favicondata) 200 w.Write(favicondata)
194 } 201 }
195} 202}
196 203
204func StyleHandler(styledata []byte) http.HandlerFunc {
205 return func(w http.ResponseWriter, req *http.Request) {
206 w.Header().Set("Content-Type", "text/css")
207 w.Header().Set("Cache-Control", "max-age=2592000")
208 w.Write(styledata)
209 }
210}
211
212func JavaScriptHandler(jsdata []byte) http.HandlerFunc {
213 return func(w http.ResponseWriter, req *http.Request) {
214 w.Header().Set("Content-Type", "text/javascript")
215 w.Header().Set("Cache-Control", "max-age=2592000")
216 w.Write(jsdata)
217 }
218}
219
197func FontHandler(woff2 bool, fontdata []byte) http.HandlerFunc { 220func FontHandler(woff2 bool, fontdata []byte) http.HandlerFunc {
198 return func(w http.ResponseWriter, req *http.Request) { 221 return func(w http.ResponseWriter, req *http.Request) {
199 if fontdata == nil { 222 if fontdata == nil {
@@ -206,6 +229,7 @@ func FontHandler(woff2 bool, fontdata []byte) http.HandlerFunc {
206 } else { 229 } else {
207 w.Header().Set("Content-Type", "font/woff") 230 w.Header().Set("Content-Type", "font/woff")
208 } 231 }
232 w.Header().Set("Cache-Control", "max-age=2592000")
209 233
210 w.Write(fontdata) 234 w.Write(fontdata)
211 } 235 }
@@ -242,21 +266,25 @@ func ListenAndServe(bind, robotsfile string, robotsdebug bool, uri string) error
242 if err != nil { 266 if err != nil {
243 fontdataw = []byte{} 267 fontdataw = []byte{}
244 } 268 }
269 fontwhash := fmt.Sprintf("%x", md5.Sum(fontdataw))
245 270
246 fontdataw2, err := box.Find("iosevka-term-ss03-regular.woff2") 271 fontdataw2, err := box.Find("iosevka-term-ss03-regular.woff2")
247 if err != nil { 272 if err != nil {
248 fontdataw2 = []byte{} 273 fontdataw2 = []byte{}
249 } 274 }
275 fontw2hash := fmt.Sprintf("%x", md5.Sum(fontdataw2))
250 276
251 styletext, err := box.FindString("style.css") 277 styledata, err := box.Find("style.css")
252 if err != nil { 278 if err != nil {
253 styletext = "" 279 styledata = []byte{}
254 } 280 }
281 stylehash := fmt.Sprintf("%x", md5.Sum(styledata))
255 282
256 jstext, err := box.FindString("main.js") 283 jsdata, err := box.Find("main.js")
257 if err != nil { 284 if err != nil {
258 jstext = "" 285 jsdata = []byte{}
259 } 286 }
287 jshash := fmt.Sprintf("%x", md5.Sum(jsdata))
260 288
261 favicondata, err := box.Find("favicon.ico") 289 favicondata, err := box.Find("favicon.ico")
262 if err != nil { 290 if err != nil {
@@ -304,11 +332,13 @@ func ListenAndServe(bind, robotsfile string, robotsdebug bool, uri string) error
304 ConcurrencyLevel: 2, 332 ConcurrencyLevel: 2,
305 }) 333 })
306 334
307 http.HandleFunc("/", GopherHandler(tpl, robotsdata, robotsdebug, styletext, jstext, uri)) 335 http.HandleFunc("/", GopherHandler(tpl, robotsdata, AssetHashList{stylehash, jshash, fontwhash, fontw2hash}, robotsdebug, uri))
308 http.HandleFunc("/robots.txt", RobotsTxtHandler(robotstxtdata)) 336 http.HandleFunc("/robots.txt", RobotsTxtHandler(robotstxtdata))
309 http.HandleFunc("/favicon.ico", FaviconHandler(favicondata)) 337 http.HandleFunc("/favicon.ico", FaviconHandler(favicondata))
310 http.HandleFunc("/iosevka-term-ss03-regular.woff", FontHandler(false, fontdataw)) 338 http.HandleFunc("/style-"+stylehash+".css", StyleHandler(styledata))
311 http.HandleFunc("/iosevka-term-ss03-regular.woff2", FontHandler(true, fontdataw2)) 339 http.HandleFunc("/main-"+jshash+".js", JavaScriptHandler(jsdata))
340 http.HandleFunc("/iosevka-term-ss03-regular-"+fontwhash+".woff", FontHandler(false, fontdataw))
341 http.HandleFunc("/iosevka-term-ss03-regular-"+fontw2hash+".woff2", FontHandler(true, fontdataw2))
312 //http.Handle("/assets/", http.StripPrefix("/assets/", http.FileServer(http.Dir("assets/")))) 342 //http.Handle("/assets/", http.StripPrefix("/assets/", http.FileServer(http.Dir("assets/"))))
313 343
314 return http.ListenAndServe(bind, nil) 344 return http.ListenAndServe(bind, nil)
diff --git a/template.go b/template.go
index ef02ce0..cb37aff 100644
--- a/template.go
+++ b/template.go
@@ -6,9 +6,15 @@ var tpltext = `<!doctype html>
6 <meta charset="utf-8"> 6 <meta charset="utf-8">
7 <meta name="viewport" content="width=device-width, initial-scale=1" /> 7 <meta name="viewport" content="width=device-width, initial-scale=1" />
8 <title>{{ .Title }}</title> 8 <title>{{ .Title }}</title>
9 <link rel="preload" href="/iosevka-term-ss03-regular.woff2" as="font" type="font/woff2" crossorigin="anonymous" /> 9 <link rel="stylesheet" href="/style-{{ .AssetHashList.Style }}.css" />
10 <style> 10 <style>
11 {{ .Style | safeCss }} 11 @font-face {
12 font-family: 'Iosevka Term SS03';
13 font-style: normal;
14 font-weight: normal;
15 src: url('/iosevka-term-ss03-regular-{{ .AssetHashList.FontW2 }}.woff2') format('woff2'),
16 url('/iosevka-term-ss03-regular-{{ .AssetHashList.FontW }}.woff') format('woff');
17 }
12 </style> 18 </style>
13 </head> 19 </head>
14 <body class="{{ if not .Lines }}is-plain{{ end }}"> 20 <body class="{{ if not .Lines }}is-plain{{ end }}">
@@ -91,8 +97,6 @@ var tpltext = `<!doctype html>
91 </div> 97 </div>
92 </div> 98 </div>
93 </aside> 99 </aside>
94 <script type="text/javascript"> 100 <script src="/main-{{ .AssetHashList.JS }}.js"></script>
95 {{ .Script | safeJs }}
96 </script>
97 </body> 101 </body>
98</html>` 102</html>`