aboutsummaryrefslogtreecommitdiffstats
path: root/internal
diff options
context:
space:
mode:
Diffstat (limited to 'internal')
-rw-r--r--internal/port/gemini.go176
-rw-r--r--internal/port/gopher.go138
-rw-r--r--internal/port/main.go69
-rw-r--r--internal/port/tpl/_fonts.html16
-rw-r--r--internal/port/tpl/_header.html48
-rw-r--r--internal/port/tpl/_modals.html24
-rw-r--r--internal/port/tpl/gemini.html38
-rw-r--r--internal/port/tpl/gopher.html59
-rw-r--r--internal/port/tpl/startpage.html120
9 files changed, 400 insertions, 288 deletions
diff --git a/internal/port/gemini.go b/internal/port/gemini.go
index b10da7d..0d8292c 100644
--- a/internal/port/gemini.go
+++ b/internal/port/gemini.go
@@ -1,7 +1,6 @@
1package port 1package port
2 2
3import ( 3import (
4 "bufio"
5 "bytes" 4 "bytes"
6 "fmt" 5 "fmt"
7 "html/template" 6 "html/template"
@@ -10,7 +9,6 @@ import (
10 "mime" 9 "mime"
11 "net/http" 10 "net/http"
12 "net/url" 11 "net/url"
13 "regexp"
14 "strings" 12 "strings"
15 13
16 "golang.org/x/net/html/charset" 14 "golang.org/x/net/html/charset"
@@ -21,129 +19,83 @@ import (
21 "github.com/temoto/robotstxt" 19 "github.com/temoto/robotstxt"
22) 20)
23 21
24type SectionType byte 22type GeminiTemplateVariables struct {
25
26const (
27 RAW_TEXT = SectionType(0)
28 REFLOW_TEXT = SectionType(1)
29 LINK = SectionType(2)
30)
31
32type Section struct {
33 Type SectionType
34 Text string
35 URL template.URL
36}
37
38type templateVariables struct {
39 Title string 23 Title string
40 URI string 24 URL string
41 Assets AssetList 25 Assets AssetList
42 Sections []Section 26 Sections []GeminiSection
27 Nav []GeminiNavItem
43} 28}
44 29
45var ( 30type GeminiNavItem struct {
46 TermEscapeSGRPattern = regexp.MustCompile("\\[\\d+(;\\d+)*m") 31 Label string
47) 32 URL string
33}
48 34
49func resolveURI(uri string, baseURL *url.URL) (resolvedURI string) { 35type GeminiSection struct {
36 Type libgemini.GeminiDocSectionType
37 Text string
38 URL template.URL
39 Items []string
40}
41
42func resolveURL(uri string, baseURL *url.URL) (resolvedURL string) {
50 if strings.HasPrefix(uri, "//") { 43 if strings.HasPrefix(uri, "//") {
51 resolvedURI = "/gemini/" + strings.TrimPrefix(uri, "//") 44 resolvedURL = "/gemini/" + strings.TrimPrefix(uri, "//")
52 } else if strings.HasPrefix(uri, "gemini://") { 45 } else if strings.HasPrefix(uri, "gemini://") {
53 resolvedURI = "/gemini/" + strings.TrimPrefix(uri, "gemini://") 46 resolvedURL = "/gemini/" + strings.TrimPrefix(uri, "gemini://")
54 } else if strings.HasPrefix(uri, "gopher://") { 47 } else if strings.HasPrefix(uri, "gopher://") {
55 resolvedURI = "/gopher/" + strings.TrimPrefix(uri, "gopher://") 48 resolvedURL = "/gopher/" + strings.TrimPrefix(uri, "gopher://")
56 } else { 49 } else {
57 url, err := url.Parse(uri) 50 url, err := url.Parse(uri)
58 if err != nil { 51 if err != nil {
59 return "" 52 return ""
60 } 53 }
61 adjustedURI := baseURL.ResolveReference(url) 54 adjustedURL := baseURL.ResolveReference(url)
62 path := adjustedURI.Path 55 path := adjustedURL.Path
63 if !strings.HasPrefix(path, "/") { 56 if !strings.HasPrefix(path, "/") {
64 path = "/" + path 57 path = "/" + path
65 } 58 }
66 if adjustedURI.Scheme == "gemini" { 59 if adjustedURL.Scheme == "gemini" {
67 resolvedURI = "/gemini/" + adjustedURI.Host + path 60 resolvedURL = "/gemini/" + adjustedURL.Host + path
68 } else if adjustedURI.Scheme == "gopher" { 61 } else if adjustedURL.Scheme == "gopher" {
69 resolvedURI = "/gopher/" + adjustedURI.Host + path 62 resolvedURL = "/gopher/" + adjustedURL.Host + path
70 } else { 63 } else {
71 resolvedURI = adjustedURI.String() 64 resolvedURL = adjustedURL.String()
72 } 65 }
73 } 66 }
74 67
75 return 68 return
76} 69}
77 70
78func parseGeminiDocument(body *bytes.Buffer, uri string, hostport string) (sections []Section) { 71func parseGeminiDocument(body *bytes.Buffer, uri string, hostport string) (sections []GeminiSection) {
79 baseURL, err := url.Parse(fmt.Sprintf( 72 baseURL, err := url.Parse(fmt.Sprintf(
80 "gemini://%s/%s", 73 "gemini://%s/%s",
81 hostport, 74 hostport,
82 uri, 75 uri,
83 )) 76 ))
84 if err != nil { 77 if err != nil {
85 return []Section{} 78 return
86 }
87
88 skipSection := true
89
90 section := Section{
91 Type: RAW_TEXT,
92 } 79 }
93 80
94 scanner := bufio.NewScanner(body) 81 unpreppedSections := libgemini.ParseGeminiDocument(body)
95 82
96 for scanner.Scan() { 83 for _, section := range unpreppedSections {
97 line := strings.Trim(scanner.Text(), "\r\n") 84 if section.Type != libgemini.LINK {
98 line = TermEscapeSGRPattern.ReplaceAllString(line, "") 85 sections = append(sections, GeminiSection{
99 86 Type: section.Type,
100 linkMatch := libgemini.LinkPattern.FindStringSubmatch(line) 87 Text: section.Text,
101 if len(linkMatch) != 0 && linkMatch[0] != "" { 88 URL: template.URL(section.URL),
102 curType := section.Type 89 Items: section.Items,
103
104 if !skipSection {
105 sections = append(sections, section)
106 }
107
108 label := linkMatch[2]
109 if label == "" {
110 label = linkMatch[1]
111 }
112
113 sections = append(sections, Section{
114 Type: LINK,
115 Text: label,
116 URL: template.URL(resolveURI(linkMatch[1], baseURL)),
117 }) 90 })
118
119 skipSection = false
120 section = Section{
121 Type: curType,
122 }
123 } else {
124 reflowModeMatch := libgemini.ReflowModePattern.FindStringSubmatch(line)
125 if len(reflowModeMatch) != 0 {
126 newType := RAW_TEXT
127 if section.Type == RAW_TEXT {
128 newType = REFLOW_TEXT
129 }
130
131 if !skipSection {
132 sections = append(sections, section)
133 }
134
135 skipSection = false
136 section = Section{
137 Type: newType,
138 }
139 } else {
140 section.Text = section.Text + "\n" + line
141 }
142 } 91 }
143 }
144 92
145 if !skipSection { 93 sections = append(sections, GeminiSection{
146 sections = append(sections, section) 94 Type: section.Type,
95 Text: section.Text,
96 URL: template.URL(resolveURL(section.URL, baseURL)),
97 Items: section.Items,
98 })
147 } 99 }
148 100
149 return 101 return
@@ -176,12 +128,12 @@ func GeminiHandler(tpl *template.Template, robotsdata *robotstxt.RobotsData, ass
176 128
177 uri, err := url.QueryUnescape(strings.Join(parts[1:], "/")) 129 uri, err := url.QueryUnescape(strings.Join(parts[1:], "/"))
178 if err != nil { 130 if err != nil {
179 if e := tpl.Execute(w, templateVariables{ 131 if e := tpl.Execute(w, GeminiTemplateVariables{
180 Title: title, 132 Title: title,
181 URI: hostport, 133 URL: hostport,
182 Assets: assetList, 134 Assets: assetList,
183 Sections: []Section{{ 135 Sections: []GeminiSection{{
184 Type: RAW_TEXT, 136 Type: libgemini.RAW_TEXT,
185 Text: fmt.Sprintf("Error: %s", err), 137 Text: fmt.Sprintf("Error: %s", err),
186 }}, 138 }},
187 }); e != nil { 139 }); e != nil {
@@ -205,12 +157,12 @@ func GeminiHandler(tpl *template.Template, robotsdata *robotstxt.RobotsData, ass
205 ) 157 )
206 158
207 if err != nil { 159 if err != nil {
208 if e := tpl.Execute(w, templateVariables{ 160 if e := tpl.Execute(w, GeminiTemplateVariables{
209 Title: title, 161 Title: title,
210 URI: fmt.Sprintf("%s/%s", hostport, uri), 162 URL: fmt.Sprintf("%s/%s", hostport, uri),
211 Assets: assetList, 163 Assets: assetList,
212 Sections: []Section{{ 164 Sections: []GeminiSection{{
213 Type: RAW_TEXT, 165 Type: libgemini.RAW_TEXT,
214 Text: fmt.Sprintf("Error: %s", err), 166 Text: fmt.Sprintf("Error: %s", err),
215 }}, 167 }},
216 }); e != nil { 168 }); e != nil {
@@ -227,12 +179,12 @@ func GeminiHandler(tpl *template.Template, robotsdata *robotstxt.RobotsData, ass
227 uri, 179 uri,
228 )) 180 ))
229 if err != nil { 181 if err != nil {
230 if e := tpl.Execute(w, templateVariables{ 182 if e := tpl.Execute(w, GeminiTemplateVariables{
231 Title: title, 183 Title: title,
232 URI: fmt.Sprintf("%s/%s", hostport, uri), 184 URL: fmt.Sprintf("%s/%s", hostport, uri),
233 Assets: assetList, 185 Assets: assetList,
234 Sections: []Section{{ 186 Sections: []GeminiSection{{
235 Type: RAW_TEXT, 187 Type: libgemini.RAW_TEXT,
236 Text: fmt.Sprintf("Error: %s", err), 188 Text: fmt.Sprintf("Error: %s", err),
237 }}, 189 }},
238 }); e != nil { 190 }); e != nil {
@@ -242,17 +194,17 @@ func GeminiHandler(tpl *template.Template, robotsdata *robotstxt.RobotsData, ass
242 return 194 return
243 } 195 }
244 196
245 http.Redirect(w, req, resolveURI(res.Header.Meta, baseURL), http.StatusFound) 197 http.Redirect(w, req, resolveURL(res.Header.Meta, baseURL), http.StatusFound)
246 return 198 return
247 } 199 }
248 200
249 if int(res.Header.Status/10) != 2 { 201 if int(res.Header.Status/10) != 2 {
250 if err := tpl.Execute(w, templateVariables{ 202 if err := tpl.Execute(w, GeminiTemplateVariables{
251 Title: title, 203 Title: title,
252 URI: fmt.Sprintf("%s/%s", hostport, uri), 204 URL: fmt.Sprintf("%s/%s", hostport, uri),
253 Assets: assetList, 205 Assets: assetList,
254 Sections: []Section{{ 206 Sections: []GeminiSection{{
255 Type: RAW_TEXT, 207 Type: libgemini.RAW_TEXT,
256 Text: fmt.Sprintf("Error %d: %s", res.Header.Status, res.Header.Meta), 208 Text: fmt.Sprintf("Error %d: %s", res.Header.Status, res.Header.Meta),
257 }}, 209 }},
258 }); err != nil { 210 }); err != nil {
@@ -277,20 +229,20 @@ func GeminiHandler(tpl *template.Template, robotsdata *robotstxt.RobotsData, ass
277 writer.Close() 229 writer.Close()
278 } 230 }
279 231
280 var sections []Section 232 var sections []GeminiSection
281 233
282 if strings.HasPrefix(res.Header.Meta, libgemini.MIME_GEMINI) { 234 if strings.HasPrefix(res.Header.Meta, libgemini.MIME_GEMINI) {
283 sections = parseGeminiDocument(buf, uri, hostport) 235 sections = parseGeminiDocument(buf, uri, hostport)
284 } else { 236 } else {
285 sections = append(sections, Section{ 237 sections = append(sections, GeminiSection{
286 Type: RAW_TEXT, 238 Type: libgemini.RAW_TEXT,
287 Text: buf.String(), 239 Text: buf.String(),
288 }) 240 })
289 } 241 }
290 242
291 if err := tpl.Execute(w, templateVariables{ 243 if err := tpl.Execute(w, GeminiTemplateVariables{
292 Title: title, 244 Title: title,
293 URI: fmt.Sprintf("%s/%s", hostport, uri), 245 URL: fmt.Sprintf("%s/%s", hostport, uri),
294 Assets: assetList, 246 Assets: assetList,
295 Sections: sections, 247 Sections: sections,
296 }); err != nil { 248 }); err != nil {
diff --git a/internal/port/gopher.go b/internal/port/gopher.go
index abbc4d9..d2283c6 100644
--- a/internal/port/gopher.go
+++ b/internal/port/gopher.go
@@ -17,16 +17,73 @@ import (
17 "github.com/temoto/robotstxt" 17 "github.com/temoto/robotstxt"
18) 18)
19 19
20type Item struct { 20type gopherTemplateVariables struct {
21 Title string
22 URL string
23 Assets AssetList
24 Lines []GopherItem
25 Nav []GopherNavItem
26 IsPlain bool
27}
28
29type GopherNavItem struct {
30 Label string
31 URL string
32 Current bool
33}
34
35type GopherItem struct {
21 Link template.URL 36 Link template.URL
22 Type string 37 Type string
23 Text string 38 Text string
24} 39}
25 40
41func trimLeftChars(s string, n int) string {
42 m := 0
43 for i := range s {
44 if m >= n {
45 return s[i:]
46 }
47 m++
48 }
49 return s[:0]
50}
51
52func urlToNav(url string) (items []GopherNavItem) {
53 partialURL := "/gopher"
54 parts := strings.Split(url, "/")
55
56 for i, part := range parts {
57 if i == 1 {
58 partialURL = partialURL + "/1"
59 part = trimLeftChars(part, 1)
60
61 if part == "" {
62 continue
63 }
64 } else {
65 partialURL = partialURL + "/" + part
66 }
67
68 current := false
69 if i == len(parts)-1 || (len(parts) == 2 && i == 0) {
70 current = true
71 }
72
73 items = append(items, GopherNavItem{
74 Label: part,
75 URL: partialURL,
76 Current: current,
77 })
78 }
79
80 return
81}
82
26func renderGopherDirectory(w http.ResponseWriter, tpl *template.Template, assetList AssetList, uri string, hostport string, d libgopher.Directory) error { 83func renderGopherDirectory(w http.ResponseWriter, tpl *template.Template, assetList AssetList, uri string, hostport string, d libgopher.Directory) error {
27 var title string 84 var title string
28 85
29 out := make([]Item, len(d.Items)) 86 out := make([]GopherItem, len(d.Items))
30 87
31 for i, x := range d.Items { 88 for i, x := range d.Items {
32 if x.Type == libgopher.INFO && x.Selector == "TITLE" { 89 if x.Type == libgopher.INFO && x.Selector == "TITLE" {
@@ -34,7 +91,7 @@ func renderGopherDirectory(w http.ResponseWriter, tpl *template.Template, assetL
34 continue 91 continue
35 } 92 }
36 93
37 tr := Item{ 94 tr := GopherItem{
38 Text: x.Description, 95 Text: x.Description,
39 Type: x.Type.String(), 96 Type: x.Type.String(),
40 } 97 }
@@ -89,12 +146,12 @@ func renderGopherDirectory(w http.ResponseWriter, tpl *template.Template, assetL
89 } 146 }
90 } 147 }
91 148
92 return tpl.Execute(w, TemplateVariables{ 149 return tpl.Execute(w, gopherTemplateVariables{
93 Title: title, 150 Title: title,
94 URI: fmt.Sprintf("%s/%s", hostport, uri), 151 URL: fmt.Sprintf("%s/%s", hostport, uri),
95 Assets: assetList, 152 Assets: assetList,
96 Lines: out, 153 Lines: out,
97 Protocol: "gopher", 154 Nav: urlToNav(fmt.Sprintf("%s/%s", hostport, uri)),
98 }) 155 })
99} 156}
100 157
@@ -130,13 +187,15 @@ func GopherHandler(tpl *template.Template, robotsdata *robotstxt.RobotsData, ass
130 187
131 uri, err := url.QueryUnescape(strings.Join(parts[1:], "/")) 188 uri, err := url.QueryUnescape(strings.Join(parts[1:], "/"))
132 if err != nil { 189 if err != nil {
133 if e := tpl.Execute(w, TemplateVariables{ 190 if e := tpl.Execute(w, gopherTemplateVariables{
134 Title: title, 191 Title: title,
135 URI: hostport, 192 URL: hostport,
136 Assets: assetList, 193 Assets: assetList,
137 RawText: fmt.Sprintf("Error: %s", err), 194 Lines: []GopherItem{{
138 Error: true, 195 Text: fmt.Sprintf("Error: %s", err),
139 Protocol: "gopher", 196 }},
197 Nav: urlToNav(hostport),
198 IsPlain: true,
140 }); e != nil { 199 }); e != nil {
141 log.Println("Template error: " + e.Error()) 200 log.Println("Template error: " + e.Error())
142 log.Println(err.Error()) 201 log.Println(err.Error())
@@ -158,13 +217,15 @@ func GopherHandler(tpl *template.Template, robotsdata *robotstxt.RobotsData, ass
158 ) 217 )
159 218
160 if err != nil { 219 if err != nil {
161 if e := tpl.Execute(w, TemplateVariables{ 220 if e := tpl.Execute(w, gopherTemplateVariables{
162 Title: title, 221 Title: title,
163 URI: fmt.Sprintf("%s/%s", hostport, uri), 222 URL: fmt.Sprintf("%s/%s", hostport, uri),
164 Assets: assetList, 223 Assets: assetList,
165 RawText: fmt.Sprintf("Error: %s", err), 224 Lines: []GopherItem{{
166 Error: true, 225 Text: fmt.Sprintf("Error: %s", err),
167 Protocol: "gopher", 226 }},
227 Nav: urlToNav(fmt.Sprintf("%s/%s", hostport, uri)),
228 IsPlain: true,
168 }); e != nil { 229 }); e != nil {
169 log.Println("Template error: " + e.Error()) 230 log.Println("Template error: " + e.Error())
170 } 231 }
@@ -178,12 +239,15 @@ func GopherHandler(tpl *template.Template, robotsdata *robotstxt.RobotsData, ass
178 buf := new(bytes.Buffer) 239 buf := new(bytes.Buffer)
179 buf.ReadFrom(res.Body) 240 buf.ReadFrom(res.Body)
180 241
181 if err := tpl.Execute(w, TemplateVariables{ 242 if err := tpl.Execute(w, gopherTemplateVariables{
182 Title: title, 243 Title: title,
183 URI: fmt.Sprintf("%s/%s", hostport, uri), 244 URL: fmt.Sprintf("%s/%s", hostport, uri),
184 Assets: assetList, 245 Assets: assetList,
185 RawText: buf.String(), 246 Lines: []GopherItem{{
186 Protocol: "gopher", 247 Text: buf.String(),
248 }},
249 Nav: urlToNav(fmt.Sprintf("%s/%s", hostport, uri)),
250 IsPlain: true,
187 }); err != nil { 251 }); err != nil {
188 log.Println("Template error: " + err.Error()) 252 log.Println("Template error: " + err.Error())
189 } 253 }
@@ -200,13 +264,15 @@ func GopherHandler(tpl *template.Template, robotsdata *robotstxt.RobotsData, ass
200 } 264 }
201 } else { 265 } else {
202 if err := renderGopherDirectory(w, tpl, assetList, uri, hostport, res.Dir); err != nil { 266 if err := renderGopherDirectory(w, tpl, assetList, uri, hostport, res.Dir); err != nil {
203 if e := tpl.Execute(w, TemplateVariables{ 267 if e := tpl.Execute(w, gopherTemplateVariables{
204 Title: title, 268 Title: title,
205 URI: fmt.Sprintf("%s/%s", hostport, uri), 269 URL: fmt.Sprintf("%s/%s", hostport, uri),
206 Assets: assetList, 270 Assets: assetList,
207 RawText: fmt.Sprintf("Error: %s", err), 271 Lines: []GopherItem{{
208 Error: true, 272 Text: fmt.Sprintf("Error: %s", err),
209 Protocol: "gopher", 273 }},
274 Nav: urlToNav(fmt.Sprintf("%s/%s", hostport, uri)),
275 IsPlain: false,
210 }); e != nil { 276 }); e != nil {
211 log.Println("Template error: " + e.Error()) 277 log.Println("Template error: " + e.Error())
212 log.Println(e.Error()) 278 log.Println(e.Error())
diff --git a/internal/port/main.go b/internal/port/main.go
index 9fa245e..267df44 100644
--- a/internal/port/main.go
+++ b/internal/port/main.go
@@ -26,23 +26,19 @@ type AssetList struct {
26 PropFontW2 string 26 PropFontW2 string
27} 27}
28 28
29type TemplateVariables struct { 29type startTemplateVariables struct {
30 Title string 30 Title string
31 URI string 31 URL string
32 Assets AssetList 32 Assets AssetList
33 RawText string 33 Content string
34 Lines []Item
35 Error bool
36 Protocol string
37} 34}
38 35
39func DefaultHandler(tpl *template.Template, startpagetext string, assetList AssetList) http.HandlerFunc { 36func DefaultHandler(tpl *template.Template, startpagetext string, assetList AssetList) http.HandlerFunc {
40 return func(w http.ResponseWriter, req *http.Request) { 37 return func(w http.ResponseWriter, req *http.Request) {
41 if err := tpl.Execute(w, TemplateVariables{ 38 if err := tpl.Execute(w, startTemplateVariables{
42 Title: "Gopher/Gemini proxy", 39 Title: "Gopher/Gemini proxy",
43 Assets: assetList, 40 Assets: assetList,
44 RawText: startpagetext, 41 Content: startpagetext,
45 Protocol: "startpage",
46 }); err != nil { 42 }); err != nil {
47 log.Println("Template error: " + err.Error()) 43 log.Println("Template error: " + err.Error())
48 } 44 }
@@ -118,7 +114,7 @@ func FontHandler(woff2 bool, fontdata []byte) http.HandlerFunc {
118// a robotstxt.RobotsData struct for testing user agents against 114// a robotstxt.RobotsData struct for testing user agents against
119// a configurable robots.txt file. 115// a configurable robots.txt file.
120func ListenAndServe(bind, startpagefile string, robotsfile string, robotsdebug bool, vipsconcurrency int) error { 116func ListenAndServe(bind, startpagefile string, robotsfile string, robotsdebug bool, vipsconcurrency int) error {
121 box := packr.New("assets", "../assets") 117 box := packr.New("assets", "../../assets")
122 118
123 // 119 //
124 // Robots 120 // Robots
@@ -205,24 +201,6 @@ func ListenAndServe(bind, startpagefile string, robotsfile string, robotsdebug b
205 // 201 //
206 // 202 //
207 203
208 var templates *template.Template
209
210 var allFiles []string
211 files, err := ioutil.ReadDir("./tpl")
212 if err != nil {
213 fmt.Println(err)
214 }
215 for _, file := range files {
216 filename := file.Name()
217 if strings.HasSuffix(filename, ".html") {
218 allFiles = append(allFiles, "./tpl/"+filename)
219 }
220 }
221
222 templates, err = template.ParseFiles(allFiles...)
223
224 //
225
226 funcMap := template.FuncMap{ 204 funcMap := template.FuncMap{
227 "safeHtml": func(s string) template.HTML { 205 "safeHtml": func(s string) template.HTML {
228 return template.HTML(s) 206 return template.HTML(s)
@@ -266,9 +244,30 @@ func ListenAndServe(bind, startpagefile string, robotsfile string, robotsdebug b
266 244
267 // 245 //
268 246
269 startpageTpl := templates.Lookup("startpage.html").Funcs(funcMap) 247 var templates *template.Template
270 geminiTpl := templates.Lookup("gemini.html").Funcs(funcMap) 248
271 gopherTpl := templates.Lookup("gopher.html").Funcs(funcMap) 249 var allFiles []string
250 files, err := ioutil.ReadDir("./internal/port/tpl")
251 if err != nil {
252 return err
253 }
254 for _, file := range files {
255 filename := file.Name()
256 if strings.HasSuffix(filename, ".html") {
257 allFiles = append(allFiles, "./internal/port/tpl/"+filename)
258 }
259 }
260
261 templates, err = template.New("main.html").Funcs(funcMap).ParseFiles(allFiles...)
262 if err != nil {
263 return err
264 }
265
266 //
267
268 startpageTpl := templates.Lookup("startpage.html")
269 geminiTpl := templates.Lookup("gemini.html")
270 gopherTpl := templates.Lookup("gopher.html")
272 271
273 // 272 //
274 // 273 //
diff --git a/internal/port/tpl/_fonts.html b/internal/port/tpl/_fonts.html
new file mode 100644
index 0000000..b56aa22
--- /dev/null
+++ b/internal/port/tpl/_fonts.html
@@ -0,0 +1,16 @@
1<style>
2 @font-face {
3 font-family: 'Iosevka Term SS03';
4 font-style: normal;
5 font-weight: normal;
6 src: url('{{ .Assets.FontW2 }}') format('woff2'),
7 url('{{ .Assets.FontW }}') format('woff');
8 }
9 @font-face {
10 font-family: 'Iosevka Aile';
11 font-style: normal;
12 font-weight: normal;
13 src: url('{{ .Assets.PropFontW2 }}') format('woff2'),
14 url('{{ .Assets.PropFontW }}') format('woff');
15 }
16</style>
diff --git a/internal/port/tpl/_header.html b/internal/port/tpl/_header.html
new file mode 100644
index 0000000..5bcd254
--- /dev/null
+++ b/internal/port/tpl/_header.html
@@ -0,0 +1,48 @@
1<header class="header header-base">
2 <div class="location">
3 <a class="location__prefix">{{ .Protocol }}://</a><a class="location__prefix location__prefix--mobile">://</a>
4
5 {{- if .URL -}}
6 {{- $page := . -}}
7 {{- $href := printf "/%s" .Protocol -}}
8 {{- $uriParts := split .URL "/" -}}
9
10 {{- $uriLast := $uriParts | last -}}
11 {{- $uriParts = $uriParts | pop -}}
12 {{- if eq $uriLast "" -}}
13 {{- $uriLast = $uriParts | last -}}
14 {{- $uriParts = $uriParts | pop -}}
15 {{- end -}}
16
17 {{- range $i, $part := $uriParts -}}
18 {{- if and (eq $page.Protocol "gopher") (eq $i 1) -}}
19 {{- $href = printf "%s/1" $href -}}
20 {{- $part = $part | trimLeftChar -}}
21 {{- if not (eq $part "") -}}
22 {{- $href = printf "%s/%s" $href $part -}}
23 <span class="location__slash">/</span><a href="{{ $href }}/" class="location__uripart">{{ $part }}</a>
24 {{- end -}}
25 {{- else -}}
26 {{- $href = printf "%s/%s" $href . -}}
27 {{- if ne $i 0 -}}
28 <span class="location__slash">/</span>
29 {{- end -}}
30 <a href="{{ $href }}/" class="location__uripart">{{ . }}</a>
31 {{- end -}}
32 {{- end -}}
33 {{- if ne (len $uriParts) 0 -}}
34 <span class="location__slash">/</span>
35 {{- end -}}
36 {{- if and (eq $page.Protocol "gopher") (eq (len $uriParts) 1) -}}
37 {{- $uriLast = $uriLast | trimLeftChar -}}
38 {{- end -}}
39 <span class="location__uripart">{{ $uriLast }}</span>
40 {{- end -}}
41 </div>
42 <div class="actions">
43 {{- if and (not .Lines) (not .Error) (eq .Protocol "gopher") -}}
44 <div class="action"><a href="/gopher/{{ .URL | replace "^([^/]*)/0" "$1/9" }}">View raw</a></div>
45 {{- end -}}
46 <div class="action"><button class="settings-btn">Settings</button></div>
47 </div>
48</header>
diff --git a/internal/port/tpl/_modals.html b/internal/port/tpl/_modals.html
new file mode 100644
index 0000000..3c08d9a
--- /dev/null
+++ b/internal/port/tpl/_modals.html
@@ -0,0 +1,24 @@
1<aside class="modal modal--settings">
2 <div class="modal__content">
3 <header class="modal__head header-base">
4 <h1 class="modal__title">Settings</h1>
5 <button class="modal__close-btn">Close</button>
6 </header>
7 <div class="setting setting--word-wrap">
8 <strong class="setting__label">Wrap wide content</strong>
9 <button class="setting__value">[N/A]</button>
10 </div>
11 <div class="setting setting--monospace-font">
12 <strong class="setting__label">Monospace font</strong>
13 <button class="setting__value">[N/A]</button>
14 </div>
15 <div class="setting setting--image-previews">
16 <strong class="setting__label">Image thumbnails</strong>
17 <button class="setting__value">[N/A]</button>
18 </div>
19 <div class="setting setting--clickable-plain-links">
20 <strong class="setting__label">Clickable links in text files</strong>
21 <button class="setting__value">[N/A]</button>
22 </div>
23 </div>
24</aside>
diff --git a/internal/port/tpl/gemini.html b/internal/port/tpl/gemini.html
index e69de29..08f1b8e 100644
--- a/internal/port/tpl/gemini.html
+++ b/internal/port/tpl/gemini.html
@@ -0,0 +1,38 @@
1<!doctype html>
2<html>
3 <head>
4 <meta charset="utf-8">
5 <meta name="viewport" content="width=device-width, initial-scale=1" />
6 <title>{{ .Title }} - Gemini proxy</title>
7 <link rel="stylesheet" href="{{ .Assets.Style }}" />
8 {{- template "_fonts.html" . -}}
9 </head>
10 <body class="{{ if not .Lines }}is-plain{{ end }}">
11 {{- template "_header.html" . -}}
12
13 <main class="wrap">
14 <pre class="content content--has-monospace-font{{ if .Lines }} content--has-type-annotations{{ end }}">
15 {{- if .Lines -}}
16 {{- $content := "" -}}
17 {{- range .Lines -}}
18 {{- if ne $content "" -}}
19 {{- $content = printf "%s\n" $content -}}
20 {{- end -}}
21 {{- if .Link -}}
22 {{- $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)) -}}
23 {{- else -}}
24 {{- $content = printf "%s%s" $content (printf "<span class=\"type-annotation\"> </span>%s" (.Text | HTMLEscape)) -}}
25 {{- end -}}
26 {{- end -}}
27 {{- $content | safeHtml -}}
28 {{- else -}}
29 {{- .RawText -}}
30 {{- end -}}
31 </pre>
32 </main>
33
34 {{- template "_modals.html" . -}}
35
36 <script src="{{ .Assets.JS }}"></script>
37 </body>
38</html>
diff --git a/internal/port/tpl/gopher.html b/internal/port/tpl/gopher.html
index e69de29..c971847 100644
--- a/internal/port/tpl/gopher.html
+++ b/internal/port/tpl/gopher.html
@@ -0,0 +1,59 @@
1<!doctype html>
2<html>
3 <head>
4 <meta charset="utf-8">
5 <meta name="viewport" content="width=device-width, initial-scale=1" />
6 <title>{{ .Title }} - Gopher proxy</title>
7 <link rel="stylesheet" href="{{ .Assets.Style }}" />
8 {{- template "_fonts.html" . -}}
9 </head>
10 <body class="{{ if .IsPlain }}is-plain{{ end }}">
11 <header class="header header-base">
12 <div class="location">
13 <a class="location__prefix">gopher://</a><a class="location__prefix location__prefix--mobile">://</a>
14 {{- range $i, $item := .Nav -}}
15 {{- if ne $i 0 -}}
16 <span class="location__slash">/</span>
17 {{- end -}}
18 {{- if .Current -}}
19 <span class="location__uripart">{{ .Label }}</span>
20 {{- else -}}
21 <a href="{{ .URL }}/" class="location__uripart">{{ .Label }}</a>
22 {{- end -}}
23 {{- end -}}
24 </div>
25 <div class="actions">
26 {{- if .IsPlain -}}
27 <div class="action"><a href="/gopher/{{ .URL | replace "^([^/]*)/0" "$1/9" }}">View raw</a></div>
28 {{- end -}}
29 <div class="action"><button class="settings-btn">Settings</button></div>
30 </div>
31 </header>
32
33 <main class="wrap">
34 <pre class="content content--has-monospace-font{{ if not .IsPlain }} content--has-type-annotations{{ end }}">
35 {{- $content := "" -}}
36 {{- $page := . -}}
37 {{- range .Lines -}}
38 {{- if ne $content "" -}}
39 {{- $content = printf "%s\n" $content -}}
40 {{- end -}}
41 {{- if $page.IsPlain -}}
42 {{- $content = printf "%s%s" $content (.Text | HTMLEscape) -}}
43 {{- else -}}
44 {{- if .Link -}}
45 {{- $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)) -}}
46 {{- else -}}
47 {{- $content = printf "%s%s" $content (printf "<span class=\"type-annotation\"> </span>%s" (.Text | HTMLEscape)) -}}
48 {{- end -}}
49 {{- end -}}
50 {{- end -}}
51 {{- $content | safeHtml -}}
52 </pre>
53 </main>
54
55 {{- template "_modals.html" . -}}
56
57 <script src="{{ .Assets.JS }}"></script>
58 </body>
59</html>
diff --git a/internal/port/tpl/startpage.html b/internal/port/tpl/startpage.html
index 8482a6f..cfe519d 100644
--- a/internal/port/tpl/startpage.html
+++ b/internal/port/tpl/startpage.html
@@ -3,118 +3,28 @@
3 <head> 3 <head>
4 <meta charset="utf-8"> 4 <meta charset="utf-8">
5 <meta name="viewport" content="width=device-width, initial-scale=1" /> 5 <meta name="viewport" content="width=device-width, initial-scale=1" />
6 <title>{{ .Title }}{{ if ne .Protocol "startpage" }} - {{ .Protocol | title }} proxy{{ end }}</title> 6 <title>{{ .Title }}</title>
7 <link rel="stylesheet" href="{{ .Assets.Style }}" /> 7 <link rel="stylesheet" href="{{ .Assets.Style }}" />
8 <style> 8 {{- template "_fonts.html" . -}}
9 @font-face {
10 font-family: 'Iosevka Term SS03';
11 font-style: normal;
12 font-weight: normal;
13 src: url('{{ .Assets.FontW2 }}') format('woff2'),
14 url('{{ .Assets.FontW }}') format('woff');
15 }
16 @font-face {
17 font-family: 'Iosevka Aile';
18 font-style: normal;
19 font-weight: normal;
20 src: url('{{ .Assets.PropFontW2 }}') format('woff2'),
21 url('{{ .Assets.PropFontW }}') format('woff');
22 }
23 </style>
24 </head> 9 </head>
25 <body class="{{ if not .Lines }}is-plain{{ end }}"> 10 <body class="is-plain">
26 <header class="header header-base"> 11 <header class="header header-base">
27 <div class="location"> 12 <div class="location">
28 <a class="location__prefix">{{ .Protocol }}://</a><a class="location__prefix location__prefix--mobile">://</a> 13 <a class="location__prefix">start://</a><a class="location__prefix location__prefix--mobile">://</a>
29 14 </div>
30 {{- if .URI -}} 15 <div class="actions">
31 {{- $page := . -}} 16 <div class="action"><button class="settings-btn">Settings</button></div>
32 {{- $href := printf "/%s" .Protocol -}} 17 </div>
33 {{- $uriParts := split .URI "/" -}} 18 </header>
34
35 {{- $uriLast := $uriParts | last -}}
36 {{- $uriParts = $uriParts | pop -}}
37 {{- if eq $uriLast "" -}}
38 {{- $uriLast = $uriParts | last -}}
39 {{- $uriParts = $uriParts | pop -}}
40 {{- end -}}
41 19
42 {{- range $i, $part := $uriParts -}}
43 {{- if and (eq $page.Protocol "gopher") (eq $i 1) -}}
44 {{- $href = printf "%s/1" $href -}}
45 {{- $part = $part | trimLeftChar -}}
46 {{- if not (eq $part "") -}}
47 {{- $href = printf "%s/%s" $href $part -}}
48 <span class="location__slash">/</span><a href="{{ $href }}/" class="location__uripart">{{ $part }}</a>
49 {{- end -}}
50 {{- else -}}
51 {{- $href = printf "%s/%s" $href . -}}
52 {{- if ne $i 0 -}}
53 <span class="location__slash">/</span>
54 {{- end -}}
55 <a href="{{ $href }}/" class="location__uripart">{{ . }}</a>
56 {{- end -}}
57 {{- end -}}
58 {{- if ne (len $uriParts) 0 -}}
59 <span class="location__slash">/</span>
60 {{- end -}}
61 {{- if and (eq $page.Protocol "gopher") (eq (len $uriParts) 1) -}}
62 {{- $uriLast = $uriLast | trimLeftChar -}}
63 {{- end -}}
64 <span class="location__uripart">{{ $uriLast }}</span>
65 {{- end -}}
66 </div>
67 <div class="actions">
68 {{- if and (not .Lines) (not .Error) (eq .Protocol "gopher") -}}
69 <div class="action"><a href="/gopher/{{ .URI | replace "^([^/]*)/0" "$1/9" }}">View raw</a></div>
70 {{- end -}}
71 <div class="action"><button class="settings-btn">Settings</button></div>
72 </div>
73 </header>
74 <main class="wrap"> 20 <main class="wrap">
75 <pre class="content content--has-monospace-font{{ if .Lines }} content--has-type-annotations{{ end }}"> 21 <pre class="content content--has-monospace-font">
76 {{- if .Lines -}} 22 {{- .Content -}}
77 {{- $content := "" -}}
78 {{- range .Lines -}}
79 {{- if ne $content "" -}}
80 {{- $content = printf "%s\n" $content -}}
81 {{- end -}}
82 {{- if .Link -}}
83 {{- $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)) -}}
84 {{- else -}}
85 {{- $content = printf "%s%s" $content (printf "<span class=\"type-annotation\"> </span>%s" (.Text | HTMLEscape)) -}}
86 {{- end -}}
87 {{- end -}}
88 {{- $content | safeHtml -}}
89 {{- else -}}
90 {{- .RawText -}}
91 {{- end -}}
92 </pre> 23 </pre>
93 </main> 24 </main>
94 <aside class="modal modal--settings"> 25
95 <div class="modal__content"> 26 {{- template "_modals.html" . -}}
96 <header class="modal__head header-base"> 27
97 <h1 class="modal__title">Settings</h1>
98 <button class="modal__close-btn">Close</button>
99 </header>
100 <div class="setting setting--word-wrap">
101 <strong class="setting__label">Wrap wide content</strong>
102 <button class="setting__value">[N/A]</button>
103 </div>
104 <div class="setting setting--monospace-font">
105 <strong class="setting__label">Monospace font</strong>
106 <button class="setting__value">[N/A]</button>
107 </div>
108 <div class="setting setting--image-previews">
109 <strong class="setting__label">Image thumbnails</strong>
110 <button class="setting__value">[N/A]</button>
111 </div>
112 <div class="setting setting--clickable-plain-links">
113 <strong class="setting__label">Clickable links in text files</strong>
114 <button class="setting__value">[N/A]</button>
115 </div>
116 </div>
117 </aside>
118 <script src="{{ .Assets.JS }}"></script> 28 <script src="{{ .Assets.JS }}"></script>
119 </body> 29 </body>
120</html> 30</html>