aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--cmd/gopherproxy/main.go3
-rw-r--r--gopherproxy.go97
-rw-r--r--startpage.txt1
-rw-r--r--template.go229
4 files changed, 187 insertions, 143 deletions
diff --git a/cmd/gopherproxy/main.go b/cmd/gopherproxy/main.go
index 66248ac..e84e978 100644
--- a/cmd/gopherproxy/main.go
+++ b/cmd/gopherproxy/main.go
@@ -13,7 +13,6 @@ var (
13 bind = flag.String("bind", "0.0.0.0:8000", "[int]:port to bind to") 13 bind = flag.String("bind", "0.0.0.0:8000", "[int]:port to bind to")
14 robotsfile = flag.String("robots-file", "robots.txt", "robots.txt file") 14 robotsfile = flag.String("robots-file", "robots.txt", "robots.txt file")
15 robotsdebug = flag.Bool("robots-debug", false, "print output about ignored robots.txt") 15 robotsdebug = flag.Bool("robots-debug", false, "print output about ignored robots.txt")
16 uri = flag.String("uri", "gopher/floodgap.com", "<gopher|gemini>/<host>:[port] to proxy to")
17 vipsconcurrency = flag.Int("vips-concurrency", 1, "Concurrency level of libvips") 16 vipsconcurrency = flag.Int("vips-concurrency", 1, "Concurrency level of libvips")
18) 17)
19 18
@@ -21,5 +20,5 @@ func main() {
21 flag.Parse() 20 flag.Parse()
22 21
23 // Use a config struct 22 // Use a config struct
24 log.Fatal(gopherproxy.ListenAndServe(*bind, *robotsfile, *robotsdebug, *vipsconcurrency, *uri)) 23 log.Fatal(gopherproxy.ListenAndServe(*bind, *robotsfile, *robotsdebug, *vipsconcurrency))
25} 24}
diff --git a/gopherproxy.go b/gopherproxy.go
index 751cebf..2a6358c 100644
--- a/gopherproxy.go
+++ b/gopherproxy.go
@@ -177,9 +177,19 @@ func parseGeminiDocument(body *bytes.Buffer, uri string, hostport string) (items
177 return 177 return
178} 178}
179 179
180func DefaultHandler(tpl *template.Template, uri string) http.HandlerFunc { 180func DefaultHandler(tpl *template.Template, startpagetext string, assetList AssetList) http.HandlerFunc {
181 return func(w http.ResponseWriter, req *http.Request) { 181 return func(w http.ResponseWriter, req *http.Request) {
182 http.Redirect(w, req, "/"+uri, http.StatusFound) 182 if err := tpl.Execute(w, struct {
183 Title string
184 URI string
185 Assets AssetList
186 RawText string
187 Lines []Item
188 Error bool
189 Protocol string
190 }{"Gopher/Gemini proxy", "", assetList, startpagetext, nil, false, "startpage"}); err != nil {
191 log.Println("Template error: " + err.Error())
192 }
183 } 193 }
184} 194}
185 195
@@ -188,7 +198,7 @@ func DefaultHandler(tpl *template.Template, uri string) http.HandlerFunc {
188// to the request path and renders the content using the provided template. 198// to the request path and renders the content using the provided template.
189// The optional robots parameters points to a robotstxt.RobotsData struct 199// The optional robots parameters points to a robotstxt.RobotsData struct
190// to test user agents against a configurable robotst.txt file. 200// to test user agents against a configurable robotst.txt file.
191func GopherHandler(tpl *template.Template, robotsdata *robotstxt.RobotsData, assetList AssetList, robotsdebug bool, uri string) http.HandlerFunc { 201func GopherHandler(tpl *template.Template, robotsdata *robotstxt.RobotsData, assetList AssetList, robotsdebug bool) http.HandlerFunc {
192 return func(w http.ResponseWriter, req *http.Request) { 202 return func(w http.ResponseWriter, req *http.Request) {
193 agent := req.UserAgent() 203 agent := req.UserAgent()
194 path := strings.TrimPrefix(req.URL.Path, "/gopher/") 204 path := strings.TrimPrefix(req.URL.Path, "/gopher/")
@@ -201,7 +211,7 @@ func GopherHandler(tpl *template.Template, robotsdata *robotstxt.RobotsData, ass
201 hostport := parts[0] 211 hostport := parts[0]
202 212
203 if len(hostport) == 0 { 213 if len(hostport) == 0 {
204 http.Redirect(w, req, "/"+uri, http.StatusFound) 214 http.Redirect(w, req, "/", http.StatusFound)
205 return 215 return
206 } 216 }
207 217
@@ -215,7 +225,7 @@ func GopherHandler(tpl *template.Template, robotsdata *robotstxt.RobotsData, ass
215 225
216 uri, err := url.QueryUnescape(strings.Join(parts[1:], "/")) 226 uri, err := url.QueryUnescape(strings.Join(parts[1:], "/"))
217 if err != nil { 227 if err != nil {
218 tpl.Execute(w, struct { 228 if e := tpl.Execute(w, struct {
219 Title string 229 Title string
220 URI string 230 URI string
221 Assets AssetList 231 Assets AssetList
@@ -223,7 +233,10 @@ func GopherHandler(tpl *template.Template, robotsdata *robotstxt.RobotsData, ass
223 Lines []Item 233 Lines []Item
224 Error bool 234 Error bool
225 Protocol string 235 Protocol string
226 }{title, hostport, assetList, fmt.Sprintf("Error: %s", err), nil, true, "gopher"}) 236 }{title, hostport, assetList, fmt.Sprintf("Error: %s", err), nil, true, "gopher"}); e != nil {
237 log.Println("Template error: " + e.Error())
238 log.Println(err.Error())
239 }
227 return 240 return
228 } 241 }
229 242
@@ -241,7 +254,7 @@ func GopherHandler(tpl *template.Template, robotsdata *robotstxt.RobotsData, ass
241 ) 254 )
242 255
243 if err != nil { 256 if err != nil {
244 tpl.Execute(w, struct { 257 if e := tpl.Execute(w, struct {
245 Title string 258 Title string
246 URI string 259 URI string
247 Assets AssetList 260 Assets AssetList
@@ -249,7 +262,9 @@ func GopherHandler(tpl *template.Template, robotsdata *robotstxt.RobotsData, ass
249 Lines []Item 262 Lines []Item
250 Error bool 263 Error bool
251 Protocol string 264 Protocol string
252 }{title, fmt.Sprintf("%s/%s", hostport, uri), assetList, fmt.Sprintf("Error: %s", err), nil, true, "gopher"}) 265 }{title, fmt.Sprintf("%s/%s", hostport, uri), assetList, fmt.Sprintf("Error: %s", err), nil, true, "gopher"}); e != nil {
266 log.Println("Template error: " + e.Error())
267 }
253 return 268 return
254 } 269 }
255 270
@@ -259,7 +274,7 @@ func GopherHandler(tpl *template.Template, robotsdata *robotstxt.RobotsData, ass
259 } else if strings.HasPrefix(parts[1], "0") && !strings.HasSuffix(uri, ".xml") && !strings.HasSuffix(uri, ".asc") { 274 } else if strings.HasPrefix(parts[1], "0") && !strings.HasSuffix(uri, ".xml") && !strings.HasSuffix(uri, ".asc") {
260 buf := new(bytes.Buffer) 275 buf := new(bytes.Buffer)
261 buf.ReadFrom(res.Body) 276 buf.ReadFrom(res.Body)
262 tpl.Execute(w, struct { 277 if err := tpl.Execute(w, struct {
263 Title string 278 Title string
264 URI string 279 URI string
265 Assets AssetList 280 Assets AssetList
@@ -267,7 +282,9 @@ func GopherHandler(tpl *template.Template, robotsdata *robotstxt.RobotsData, ass
267 Lines []Item 282 Lines []Item
268 Error bool 283 Error bool
269 Protocol string 284 Protocol string
270 }{title, fmt.Sprintf("%s/%s", hostport, uri), assetList, buf.String(), nil, false, "gopher"}) 285 }{title, fmt.Sprintf("%s/%s", hostport, uri), assetList, buf.String(), nil, false, "gopher"}); err != nil {
286 log.Println("Template error: " + err.Error())
287 }
271 } else if strings.HasPrefix(parts[1], "T") { 288 } else if strings.HasPrefix(parts[1], "T") {
272 _, _, err = vips.NewTransform(). 289 _, _, err = vips.NewTransform().
273 Load(res.Body). 290 Load(res.Body).
@@ -281,7 +298,7 @@ func GopherHandler(tpl *template.Template, robotsdata *robotstxt.RobotsData, ass
281 } 298 }
282 } else { 299 } else {
283 if err := renderGopherDirectory(w, tpl, assetList, uri, hostport, res.Dir); err != nil { 300 if err := renderGopherDirectory(w, tpl, assetList, uri, hostport, res.Dir); err != nil {
284 tpl.Execute(w, struct { 301 if e := tpl.Execute(w, struct {
285 Title string 302 Title string
286 URI string 303 URI string
287 Assets AssetList 304 Assets AssetList
@@ -289,13 +306,16 @@ func GopherHandler(tpl *template.Template, robotsdata *robotstxt.RobotsData, ass
289 Lines []Item 306 Lines []Item
290 Error bool 307 Error bool
291 Protocol string 308 Protocol string
292 }{title, fmt.Sprintf("%s/%s", hostport, uri), assetList, fmt.Sprintf("Error: %s", err), nil, true, "gopher"}) 309 }{title, fmt.Sprintf("%s/%s", hostport, uri), assetList, fmt.Sprintf("Error: %s", err), nil, true, "gopher"}); e != nil {
310 log.Println("Template error: " + e.Error())
311 log.Println(e.Error())
312 }
293 } 313 }
294 } 314 }
295 } 315 }
296} 316}
297 317
298func GeminiHandler(tpl *template.Template, robotsdata *robotstxt.RobotsData, assetList AssetList, robotsdebug bool, uri string) http.HandlerFunc { 318func GeminiHandler(tpl *template.Template, robotsdata *robotstxt.RobotsData, assetList AssetList, robotsdebug bool) http.HandlerFunc {
299 return func(w http.ResponseWriter, req *http.Request) { 319 return func(w http.ResponseWriter, req *http.Request) {
300 agent := req.UserAgent() 320 agent := req.UserAgent()
301 path := strings.TrimPrefix(req.URL.Path, "/gemini/") 321 path := strings.TrimPrefix(req.URL.Path, "/gemini/")
@@ -308,7 +328,7 @@ func GeminiHandler(tpl *template.Template, robotsdata *robotstxt.RobotsData, ass
308 hostport := parts[0] 328 hostport := parts[0]
309 329
310 if len(hostport) == 0 { 330 if len(hostport) == 0 {
311 http.Redirect(w, req, "/"+uri, http.StatusFound) 331 http.Redirect(w, req, "/", http.StatusFound)
312 return 332 return
313 } 333 }
314 334
@@ -322,7 +342,7 @@ func GeminiHandler(tpl *template.Template, robotsdata *robotstxt.RobotsData, ass
322 342
323 uri, err := url.QueryUnescape(strings.Join(parts[1:], "/")) 343 uri, err := url.QueryUnescape(strings.Join(parts[1:], "/"))
324 if err != nil { 344 if err != nil {
325 tpl.Execute(w, struct { 345 if e := tpl.Execute(w, struct {
326 Title string 346 Title string
327 URI string 347 URI string
328 Assets AssetList 348 Assets AssetList
@@ -330,7 +350,10 @@ func GeminiHandler(tpl *template.Template, robotsdata *robotstxt.RobotsData, ass
330 Lines []Item 350 Lines []Item
331 Error bool 351 Error bool
332 Protocol string 352 Protocol string
333 }{title, hostport, assetList, fmt.Sprintf("Error: %s", err), nil, true, "gemini"}) 353 }{title, hostport, assetList, fmt.Sprintf("Error: %s", err), nil, true, "gemini"}); e != nil {
354 log.Println("Template error: " + e.Error())
355 log.Println(err.Error())
356 }
334 return 357 return
335 } 358 }
336 359
@@ -348,7 +371,7 @@ func GeminiHandler(tpl *template.Template, robotsdata *robotstxt.RobotsData, ass
348 ) 371 )
349 372
350 if err != nil { 373 if err != nil {
351 tpl.Execute(w, struct { 374 if e := tpl.Execute(w, struct {
352 Title string 375 Title string
353 URI string 376 URI string
354 Assets AssetList 377 Assets AssetList
@@ -356,7 +379,10 @@ func GeminiHandler(tpl *template.Template, robotsdata *robotstxt.RobotsData, ass
356 Lines []Item 379 Lines []Item
357 Error bool 380 Error bool
358 Protocol string 381 Protocol string
359 }{title, fmt.Sprintf("%s/%s", hostport, uri), assetList, fmt.Sprintf("Error: %s", err), nil, true, "gemini"}) 382 }{title, fmt.Sprintf("%s/%s", hostport, uri), assetList, fmt.Sprintf("Error: %s", err), nil, true, "gemini"}); e != nil {
383 log.Println("Template error: " + e.Error())
384 log.Println(err.Error())
385 }
360 return 386 return
361 } 387 }
362 388
@@ -367,7 +393,7 @@ func GeminiHandler(tpl *template.Template, robotsdata *robotstxt.RobotsData, ass
367 uri, 393 uri,
368 )) 394 ))
369 if err != nil { 395 if err != nil {
370 tpl.Execute(w, struct { 396 if e := tpl.Execute(w, struct {
371 Title string 397 Title string
372 URI string 398 URI string
373 Assets AssetList 399 Assets AssetList
@@ -375,7 +401,10 @@ func GeminiHandler(tpl *template.Template, robotsdata *robotstxt.RobotsData, ass
375 Lines []Item 401 Lines []Item
376 Error bool 402 Error bool
377 Protocol string 403 Protocol string
378 }{title, fmt.Sprintf("%s/%s", hostport, uri), assetList, fmt.Sprintf("Error: %s", err), nil, true, "gemini"}) 404 }{title, fmt.Sprintf("%s/%s", hostport, uri), assetList, fmt.Sprintf("Error: %s", err), nil, true, "gemini"}); e != nil {
405 log.Println("Template error: " + e.Error())
406 log.Println(err.Error())
407 }
379 return 408 return
380 } 409 }
381 410
@@ -384,7 +413,7 @@ func GeminiHandler(tpl *template.Template, robotsdata *robotstxt.RobotsData, ass
384 } 413 }
385 414
386 if int(res.Header.Status/10) != 2 { 415 if int(res.Header.Status/10) != 2 {
387 tpl.Execute(w, struct { 416 if err := tpl.Execute(w, struct {
388 Title string 417 Title string
389 URI string 418 URI string
390 Assets AssetList 419 Assets AssetList
@@ -392,7 +421,9 @@ func GeminiHandler(tpl *template.Template, robotsdata *robotstxt.RobotsData, ass
392 Lines []Item 421 Lines []Item
393 Error bool 422 Error bool
394 Protocol string 423 Protocol string
395 }{title, fmt.Sprintf("%s/%s", hostport, uri), assetList, fmt.Sprintf("Error %d: %s", res.Header.Status, res.Header.Meta), nil, true, "gemini"}) 424 }{title, fmt.Sprintf("%s/%s", hostport, uri), assetList, fmt.Sprintf("Error %d: %s", res.Header.Status, res.Header.Meta), nil, true, "gemini"}); err != nil {
425 log.Println("Template error: " + err.Error())
426 }
396 return 427 return
397 } 428 }
398 429
@@ -423,7 +454,7 @@ func GeminiHandler(tpl *template.Template, robotsdata *robotstxt.RobotsData, ass
423 rawText = buf.String() 454 rawText = buf.String()
424 } 455 }
425 456
426 tpl.Execute(w, struct { 457 if err := tpl.Execute(w, struct {
427 Title string 458 Title string
428 URI string 459 URI string
429 Assets AssetList 460 Assets AssetList
@@ -431,7 +462,9 @@ func GeminiHandler(tpl *template.Template, robotsdata *robotstxt.RobotsData, ass
431 Lines []Item 462 Lines []Item
432 Error bool 463 Error bool
433 Protocol string 464 Protocol string
434 }{title, fmt.Sprintf("%s/%s", hostport, uri), assetList, rawText, items, false, "gemini"}) 465 }{title, fmt.Sprintf("%s/%s", hostport, uri), assetList, rawText, items, false, "gemini"}); err != nil {
466 log.Println("Template error: " + err.Error())
467 }
435 } else { 468 } else {
436 io.Copy(w, res.Body) 469 io.Copy(w, res.Body)
437 } 470 }
@@ -506,7 +539,7 @@ func FontHandler(woff2 bool, fontdata []byte) http.HandlerFunc {
506// specified by the request. The robots argument is a pointer to 539// specified by the request. The robots argument is a pointer to
507// a robotstxt.RobotsData struct for testing user agents against 540// a robotstxt.RobotsData struct for testing user agents against
508// a configurable robots.txt file. 541// a configurable robots.txt file.
509func ListenAndServe(bind, robotsfile string, robotsdebug bool, vipsconcurrency int, uri string) error { 542func ListenAndServe(bind, robotsfile string, robotsdebug bool, vipsconcurrency int) error {
510 var ( 543 var (
511 tpl *template.Template 544 tpl *template.Template
512 robotsdata *robotstxt.RobotsData 545 robotsdata *robotstxt.RobotsData
@@ -567,6 +600,12 @@ func ListenAndServe(bind, robotsfile string, robotsdebug bool, vipsconcurrency i
567 favicondata = []byte{} 600 favicondata = []byte{}
568 } 601 }
569 602
603 startpagedata, err := ioutil.ReadFile("startpage.txt")
604 if err != nil {
605 startpagedata = []byte{}
606 }
607 startpagetext := string(startpagedata)
608
570 tpldata, err := ioutil.ReadFile(".template") 609 tpldata, err := ioutil.ReadFile(".template")
571 if err == nil { 610 if err == nil {
572 tpltext = string(tpldata) 611 tpltext = string(tpldata)
@@ -622,9 +661,11 @@ func ListenAndServe(bind, robotsfile string, robotsdebug bool, vipsconcurrency i
622 ConcurrencyLevel: vipsconcurrency, 661 ConcurrencyLevel: vipsconcurrency,
623 }) 662 })
624 663
625 http.Handle("/", gziphandler.GzipHandler(DefaultHandler(tpl, uri))) 664 assets := AssetList{styleAsset, jsAsset, fontwAsset, fontw2Asset, propfontwAsset, propfontw2Asset}
626 http.Handle("/gopher/", gziphandler.GzipHandler(GopherHandler(tpl, robotsdata, AssetList{styleAsset, jsAsset, fontwAsset, fontw2Asset, propfontwAsset, propfontw2Asset}, robotsdebug, uri))) 665
627 http.Handle("/gemini/", gziphandler.GzipHandler(GeminiHandler(tpl, robotsdata, AssetList{styleAsset, jsAsset, fontwAsset, fontw2Asset, propfontwAsset, propfontw2Asset}, robotsdebug, uri))) 666 http.Handle("/", gziphandler.GzipHandler(DefaultHandler(tpl, startpagetext, assets)))
667 http.Handle("/gopher/", gziphandler.GzipHandler(GopherHandler(tpl, robotsdata, assets, robotsdebug)))
668 http.Handle("/gemini/", gziphandler.GzipHandler(GeminiHandler(tpl, robotsdata, assets, robotsdebug)))
628 http.Handle("/robots.txt", gziphandler.GzipHandler(RobotsTxtHandler(robotstxtdata))) 669 http.Handle("/robots.txt", gziphandler.GzipHandler(RobotsTxtHandler(robotstxtdata)))
629 http.Handle("/favicon.ico", gziphandler.GzipHandler(FaviconHandler(favicondata))) 670 http.Handle("/favicon.ico", gziphandler.GzipHandler(FaviconHandler(favicondata)))
630 http.Handle(styleAsset, gziphandler.GzipHandler(StyleHandler(styledata))) 671 http.Handle(styleAsset, gziphandler.GzipHandler(StyleHandler(styledata)))
diff --git a/startpage.txt b/startpage.txt
new file mode 100644
index 0000000..b411de6
--- /dev/null
+++ b/startpage.txt
@@ -0,0 +1 @@
>>>> Gopher/Gemini proxy <<<<
diff --git a/template.go b/template.go
index 3bd8950..7ddc460 100644
--- a/template.go
+++ b/template.go
@@ -2,118 +2,121 @@ package gopherproxy
2 2
3var tpltext = `<!doctype html> 3var tpltext = `<!doctype html>
4<html> 4<html>
5 <head> 5 <head>
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 }} - {{ .Protocol | title }} proxy</title> 8 <title>{{ .Title }} - {{ .Protocol | title }} proxy</title>
9 <link rel="stylesheet" href="{{ .Assets.Style }}" /> 9 <link rel="stylesheet" href="{{ .Assets.Style }}" />
10 <style> 10 <style>
11 @font-face { 11 @font-face {
12 font-family: 'Iosevka Term SS03'; 12 font-family: 'Iosevka Term SS03';
13 font-style: normal; 13 font-style: normal;
14 font-weight: normal; 14 font-weight: normal;
15 src: url('{{ .Assets.FontW2 }}') format('woff2'), 15 src: url('{{ .Assets.FontW2 }}') format('woff2'),
16 url('{{ .Assets.FontW }}') format('woff'); 16 url('{{ .Assets.FontW }}') format('woff');
17 } 17 }
18 @font-face { 18 @font-face {
19 font-family: 'Iosevka Aile'; 19 font-family: 'Iosevka Aile';
20 font-style: normal; 20 font-style: normal;
21 font-weight: normal; 21 font-weight: normal;
22 src: url('{{ .Assets.PropFontW2 }}') format('woff2'), 22 src: url('{{ .Assets.PropFontW2 }}') format('woff2'),
23 url('{{ .Assets.PropFontW }}') format('woff'); 23 url('{{ .Assets.PropFontW }}') format('woff');
24 } 24 }
25 </style> 25 </style>
26 </head> 26 </head>
27 <body class="{{ if not .Lines }}is-plain{{ end }}"> 27 <body class="{{ if not .Lines }}is-plain{{ end }}">
28 <header class="header header-base"> 28 <header class="header header-base">
29 <div class="location"> 29 <div class="location">
30 {{- $page := . -}} 30 <a class="location__prefix">{{ .Protocol }}://</a><a class="location__prefix location__prefix--mobile">://</a>
31 {{- $href := "" -}} 31
32 {{- $uriParts := split .URI "/" -}} 32 {{- if .URI -}}
33 {{- $uriLast := $uriParts | last -}} 33 {{- $page := . -}}
34 {{- $uriParts = $uriParts | pop -}} 34 {{- $href := printf "/%s" .Protocol -}}
35 {{- if eq $uriLast "" -}} 35 {{- $uriParts := split .URI "/" -}}
36 {{- $uriLast = $uriParts | last -}} 36
37 {{- $uriParts = $uriParts | pop -}} 37 {{- $uriLast := $uriParts | last -}}
38 {{- end -}} 38 {{- $uriParts = $uriParts | pop -}}
39 {{- if eq $uriLast "" -}}
40 {{- $uriLast = $uriParts | last -}}
41 {{- $uriParts = $uriParts | pop -}}
42 {{- end -}}
39 43
40 <a class="location__prefix">{{ .Protocol }}://</a><a class="location__prefix location__prefix--mobile">://</a> 44 {{- range $i, $part := $uriParts -}}
41 {{- $href = printf "%s/%s" $href .Protocol -}} 45 {{- if and (eq $page.Protocol "gopher") (eq $i 1) -}}
42 {{- range $i, $part := $uriParts -}} 46 {{- $href = printf "%s/1" $href -}}
43 {{- if and (eq $page.Protocol "gopher") (eq $i 1) -}} 47 {{- $part = $part | trimLeftChar -}}
44 {{- $href = printf "%s/1" $href -}} 48 {{- if not (eq $part "") -}}
45 {{- $part = $part | trimLeftChar -}} 49 {{- $href = printf "%s/%s" $href $part -}}
46 {{- if not (eq $part "") -}} 50 <span class="location__slash">/</span><a href="{{ $href }}/" class="location__uripart">{{ $part }}</a>
47 {{- $href = printf "%s/%s" $href $part -}} 51 {{- end -}}
48 <span class="location__slash">/</span><a href="{{ $href }}/" class="location__uripart">{{ $part }}</a> 52 {{- else -}}
49 {{- end -}} 53 {{- $href = printf "%s/%s" $href . -}}
50 {{- else -}} 54 {{- if ne $i 0 -}}
51 {{- $href = printf "%s/%s" $href . -}} 55 <span class="location__slash">/</span>
52 {{- if ne $i 0 -}} 56 {{- end -}}
53 <span class="location__slash">/</span> 57 <a href="{{ $href }}/" class="location__uripart">{{ . }}</a>
54 {{- end -}} 58 {{- end -}}
55 <a href="{{ $href }}/" class="location__uripart">{{ . }}</a> 59 {{- end -}}
56 {{- end -}} 60 {{- if ne (len $uriParts) 0 -}}
57 {{- end -}} 61 <span class="location__slash">/</span>
58 {{- if ne (len $uriParts) 0 -}} 62 {{- end -}}
59 <span class="location__slash">/</span> 63 {{- if and (eq $page.Protocol "gopher") (eq (len $uriParts) 1) -}}
60 {{- end -}} 64 {{- $uriLast = $uriLast | trimLeftChar -}}
61 {{- if and (eq $page.Protocol "gopher") (eq (len $uriParts) 1) -}} 65 {{- end -}}
62 {{- $uriLast = $uriLast | trimLeftChar -}} 66 <span class="location__uripart">{{ $uriLast }}</span>
63 {{- end -}} 67 {{- end -}}
64 <span class="location__uripart">{{ $uriLast }}</span> 68 </div>
65 </div> 69 <div class="actions">
66 <div class="actions"> 70 {{- if and (not .Lines) (not .Error) (eq .Protocol "gopher") -}}
67 {{- if and (not .Lines) (not .Error) (eq .Protocol "gopher") -}} 71 <div class="action"><a href="/{{ .URI | replace "^(.*?)/0" "$1/9" }}">View raw</a></div>
68 <div class="action"><a href="/{{ .URI | replace "^(.*?)/0" "$1/9" }}">View raw</a></div> 72 {{- end -}}
69 {{- end -}} 73 <div class="action"><button class="settings-btn">Settings</button></div>
70 <div class="action"><button class="settings-btn">Settings</button></div> 74 </div>
71 </div> 75 </header>
72 </header> 76 <main class="wrap">
73 <main class="wrap"> 77 <pre class="content content--has-monospace-font{{ if .Lines }} content--has-type-annotations{{ end }}">
74 <pre class="content content--has-monospace-font{{ if .Lines }} content--has-type-annotations{{ end }}"> 78 {{- if .Lines -}}
75 {{- if .Lines -}} 79 {{- $content := "" -}}
76 {{- $content := "" -}} 80 {{- range .Lines -}}
77 {{- range .Lines -}} 81 {{- if ne $content "" -}}
78 {{- if ne $content "" -}} 82 {{- $content = printf "%s\n" $content -}}
79 {{- $content = printf "%s\n" $content -}} 83 {{- end -}}
80 {{- end -}} 84 {{- if .Link -}}
81 {{- if .Link -}} 85 {{- $content = printf "%s%s" $content (printf "<span class=\"type-annotation\">%s </span><a class=\"link link--%s\" href=\"%s\">%s</a>" .Type .Type .Link (.Text | HTMLEscape)) -}}
82 {{- $content = printf "%s%s" $content (printf "<span class=\"type-annotation\">%s </span><a class=\"link link--%s\" href=\"%s\">%s</a>" .Type .Type .Link (.Text | HTMLEscape)) -}} 86 {{- else -}}
83 {{- else -}} 87 {{- $content = printf "%s%s" $content (printf "<span class=\"type-annotation\"> </span>%s" (.Text | HTMLEscape)) -}}
84 {{- $content = printf "%s%s" $content (printf "<span class=\"type-annotation\"> </span>%s" (.Text | HTMLEscape)) -}} 88 {{- end -}}
85 {{- end -}} 89 {{- end -}}
86 {{- end -}} 90 {{- $content | safeHtml -}}
87 {{- $content | safeHtml -}} 91 {{- else -}}
88 {{- else -}} 92 {{- .RawText -}}
89 {{- .RawText -}} 93 {{- end -}}
90 {{- end -}} 94 </pre>
91 </pre> 95 </main>
92 </main> 96 <aside class="modal modal--settings">
93 <aside class="modal modal--settings"> 97 <div class="modal__content">
94 <div class="modal__content"> 98 <header class="modal__head header-base">
95 <header class="modal__head header-base"> 99 <h1 class="modal__title">Settings</h1>
96 <h1 class="modal__title">Settings</h1> 100 <button class="modal__close-btn">Close</button>
97 <button class="modal__close-btn">Close</button> 101 </header>
98 </header> 102 <div class="setting setting--word-wrap">
99 <div class="setting setting--word-wrap"> 103 <strong class="setting__label">Wrap wide content</strong>
100 <strong class="setting__label">Wrap wide content</strong> 104 <button class="setting__value">[N/A]</button>
101 <button class="setting__value">[N/A]</button> 105 </div>
102 </div> 106 <div class="setting setting--monospace-font">
103 <div class="setting setting--monospace-font"> 107 <strong class="setting__label">Monospace font</strong>
104 <strong class="setting__label">Monospace font</strong> 108 <button class="setting__value">[N/A]</button>
105 <button class="setting__value">[N/A]</button> 109 </div>
106 </div> 110 <div class="setting setting--image-previews">
107 <div class="setting setting--image-previews"> 111 <strong class="setting__label">Image thumbnails</strong>
108 <strong class="setting__label">Image thumbnails</strong> 112 <button class="setting__value">[N/A]</button>
109 <button class="setting__value">[N/A]</button> 113 </div>
110 </div> 114 <div class="setting setting--clickable-plain-links">
111 <div class="setting setting--clickable-plain-links"> 115 <strong class="setting__label">Clickable links in text files</strong>
112 <strong class="setting__label">Clickable links in text files</strong> 116 <button class="setting__value">[N/A]</button>
113 <button class="setting__value">[N/A]</button> 117 </div>
114 </div> 118 </div>
115 </div> 119 </aside>
116 </aside> 120 <script src="{{ .Assets.JS }}"></script>
117 <script src="{{ .Assets.JS }}"></script> 121 </body>
118 </body>
119</html>` 122</html>`