From c448abd99a470e1ec541027077dcdef0745270d8 Mon Sep 17 00:00:00 2001 From: Feuerfuchs Date: Sun, 23 Jun 2019 17:29:07 +0200 Subject: Show expandable thumbnails for images --- Makefile | 1 + assets/main.js | 39 +++++++++++++++++++++++++++++++++++++ assets/style.css | 2 +- css/main.scss | 58 ++++++++++++++++++++++++++++++++++++++++++++------------ go.mod | 2 ++ go.sum | 4 ++++ gopherproxy.go | 42 ++++++++++++++++++++++++++++++++++------ js/main.ts | 42 ++++++++++++++++++++++++++++++++++++++++ template.go | 16 +++++----------- 9 files changed, 176 insertions(+), 30 deletions(-) create mode 100644 assets/main.js create mode 100644 js/main.ts diff --git a/Makefile b/Makefile index f25707c..da35555 100644 --- a/Makefile +++ b/Makefile @@ -7,6 +7,7 @@ dev: build build: clean sassc -t compressed css/main.scss assets/style.css + tsc --strict --outFile assets/main.js js/main.ts pyftsubset fonts/iosevka-term-ss03-regular.ttf --name-IDs+=0,4,6 --text-file=glyphs.txt --flavor='woff' --output-file='assets/iosevka-term-ss03-regular.woff' pyftsubset fonts/iosevka-term-ss03-regular.ttf --name-IDs+=0,4,6 --text-file=glyphs.txt --flavor='woff2' --output-file='assets/iosevka-term-ss03-regular.woff2' go build -o ./gopherproxy ./cmd/gopherproxy/main.go diff --git a/assets/main.js b/assets/main.js new file mode 100644 index 0000000..46cb7ee --- /dev/null +++ b/assets/main.js @@ -0,0 +1,39 @@ +"use strict"; +var linkQryEls = document.getElementsByClassName('link--QRY'); +var i = linkQryEls.length; +while (i--) { + linkQryEls[i].addEventListener('click', function (e) { + e.preventDefault(); + var resp = prompt('Please enter required input: ', ''); + if ((resp !== null) && (resp !== "")) { + window.location.href = e.target.href + '?' + resp; + } + return false; + }); +} +var imgPreviewEls = document.getElementsByClassName('img-preview'); +i = imgPreviewEls.length; +var _loop_1 = function () { + var imgPreviewEl = imgPreviewEls[i]; + var child = imgPreviewEl.children[0]; + var thumbnailUrl = child.src; + child.addEventListener('load', function (e) { + child.classList.remove('faded'); + }); + imgPreviewEls[i].addEventListener('click', function (e) { + e.preventDefault(); + child.classList.add('faded'); + if (child.classList.contains('expanded')) { + child.classList.remove('expanded'); + child.src = thumbnailUrl; + } + else { + child.classList.add('expanded'); + child.src = imgPreviewEl.href; + } + return false; + }); +}; +while (i--) { + _loop_1(); +} diff --git a/assets/style.css b/assets/style.css index 5891d3b..46ad8cb 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}::selection{color:#000;background-color:rgba(239,198,138,0.996)}:link{color:#fff}:visited{color:#cad1d8}.header{padding:.9em 1em;border-bottom:1px solid #353a3f;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.3;color:#686f76}.header__uripart{color:#929ba3}.header__uripart--last{color:#fff}.wrap{text-align:center}.content{display:inline-block;min-width:50em;min-width:85ch;margin:0;padding:2em 1em;text-align:left;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}.link-type{color:#929ba3} +@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}button{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;background:none;border:0;padding:0;color:#fff}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}::selection{color:#000;background-color:rgba(239,198,138,0.996)}:link{color:#fff}:visited{color:#cad1d8}.header{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;padding:.9em 1em;border-bottom:1px solid #353a3f;line-height:1.3;color:#686f76}.header__uripart{color:#929ba3}.header__uripart--last{color:#fff}.wrap{text-align:center}.content{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;display:inline-block;min-width:50em;min-width:85ch;margin:0;padding:2em 1em;text-align:left}.type-annotation{color:#929ba3} diff --git a/css/main.scss b/css/main.scss index d0646d7..0d5a5d9 100644 --- a/css/main.scss +++ b/css/main.scss @@ -10,6 +10,12 @@ $border: mix(hsl(210, 100%, 95%), $background, 16%); $sel-background: rgba($accent, .996); $sel-text: #000; +@mixin monospace-font { + font-family: 'Iosevka Term SS03', 'IBM Plex Mono', 'Fira Code', 'Fira Mono', 'Roboto Mono', 'Droid Sans Mono', Monaco, Consolas, Courier, monospace; + font-size: 1 / 16 * 17em; + line-height: 1.5; +} + @font-face { font-family: 'Iosevka Term SS03'; font-style: normal; @@ -31,6 +37,35 @@ body { color: $text; } +button { + @include monospace-font; + + background: none; + border: 0; + padding: 0; + color: $text-plus; +} + +img { + display: inline-block; + vertical-align: top; + max-width: 8em; + margin: .1em 0; + + &::selection { + background-color: rgba($sel-background, .35); + } + + &.expanded { + max-width: 40em; + max-width: 80ch; + } + + &.faded { + opacity: .5; + } +} + ::selection { color: $sel-text; background-color: $sel-background; @@ -51,10 +86,10 @@ body { // } .header { + @include monospace-font; + padding: .9em 1em; border-bottom: 1px solid $border; - font-family: 'Iosevka Term SS03', 'IBM Plex Mono', 'Fira Code', 'Fira Mono', 'Roboto Mono', 'Droid Sans Mono', Monaco, Consolas, Courier, monospace; - font-size: 1 / 16 * 17em; line-height: 1.3; color: $text-faint; } @@ -72,17 +107,16 @@ body { } .content { - display: inline-block; - min-width: 50em; - min-width: 5ch + 80; - margin: 0; - padding: 2em 1em; - text-align: left; - font-family: 'Iosevka Term SS03', 'IBM Plex Mono', 'Fira Code', 'Fira Mono', 'Roboto Mono', 'Droid Sans Mono', Monaco, Consolas, Courier, monospace; - font-size: 1 / 16 * 17em; - line-height: 1.5; + @include monospace-font; + + display: inline-block; + min-width: 50em; + min-width: 5ch + 80; + margin: 0; + padding: 2em 1em; + text-align: left; } -.link-type { +.type-annotation { color: $text-minus; } diff --git a/go.mod b/go.mod index 099e4c0..b16bbe9 100644 --- a/go.mod +++ b/go.mod @@ -1,7 +1,9 @@ module git.feuerfuchs.dev/Feuerfuchs/gopherproxy require ( + github.com/davidbyttow/govips v0.0.0-20190304175058-d272f04c0fea github.com/gobuffalo/packr v1.25.0 + github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 github.com/prologic/go-gopher v0.0.0-20181230133552-0c68ed5f58b0 github.com/temoto/robotstxt v0.0.0-20180810133444-97ee4a9ee6ea ) diff --git a/go.sum b/go.sum index 89aa345..08d6ae0 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,8 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davidbyttow/govips v0.0.0-20190304175058-d272f04c0fea h1:ZtETbJTO1R3qVLdVbpjrDhD5fR8bYVhhq2RMi7rOlH4= +github.com/davidbyttow/govips v0.0.0-20190304175058-d272f04c0fea/go.mod h1:a3qO525EPfJNYa0NXBcNtXzJvyQsJAxphEDa7OOHPBk= github.com/gobuffalo/attrs v0.0.0-20190224210810-a9411de4debd/go.mod h1:4duuawTqi2wkkpB4ePgWMaai6/Kc6WEz83bhFwpHzj0= github.com/gobuffalo/depgen v0.0.0-20190329151759-d478694a28d3/go.mod h1:3STtPUQYuzV0gBVOY3vy6CfMm/ljR4pABfrTeHNLHUY= github.com/gobuffalo/envy v1.6.15/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI= @@ -33,6 +35,8 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE= github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0= +github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ= +github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= diff --git a/gopherproxy.go b/gopherproxy.go index 11c08d5..62b7446 100644 --- a/gopherproxy.go +++ b/gopherproxy.go @@ -10,6 +10,7 @@ import ( "log" "net/http" "net/url" + "regexp" "strings" "github.com/temoto/robotstxt" @@ -17,6 +18,8 @@ import ( "github.com/prologic/go-gopher" "github.com/gobuffalo/packr" + + "github.com/davidbyttow/govips/pkg/vips" ) type Item struct { @@ -25,7 +28,7 @@ type Item struct { Text string } -func renderDirectory(w http.ResponseWriter, tpl *template.Template, styletext string, uri string, hostport string, d gopher.Directory) error { +func renderDirectory(w http.ResponseWriter, tpl *template.Template, styletext string, jstext string, uri string, hostport string, d gopher.Directory) error { var title string out := make([]Item, len(d.Items)) @@ -78,9 +81,10 @@ func renderDirectory(w http.ResponseWriter, tpl *template.Template, styletext st Title string URI string Style string + Script string Lines []Item RawText string - }{title, fmt.Sprintf("%s/%s", hostport, uri), styletext, out, ""}) + }{title, fmt.Sprintf("%s/%s", hostport, uri), styletext, jstext, out, ""}) } // GopherHandler returns a Handler that proxies requests @@ -88,7 +92,7 @@ func renderDirectory(w http.ResponseWriter, tpl *template.Template, styletext st // to the request path and renders the content using the provided template. // The optional robots parameters points to a robotstxt.RobotsData struct // to test user agents against a configurable robotst.txt file. -func GopherHandler(tpl *template.Template, robotsdata *robotstxt.RobotsData, robotsdebug bool, styletext string, uri string) http.HandlerFunc { +func GopherHandler(tpl *template.Template, robotsdata *robotstxt.RobotsData, robotsdebug bool, styletext string, jstext string, uri string) http.HandlerFunc { return func(w http.ResponseWriter, req *http.Request) { agent := req.UserAgent() path := strings.TrimPrefix(req.URL.Path, "/") @@ -140,14 +144,23 @@ func GopherHandler(tpl *template.Template, robotsdata *robotstxt.RobotsData, rob Title string URI string Style string + Script string RawText string Lines []Item - }{uri, fmt.Sprintf("%s/%s", hostport, uri), styletext, buf.String(), nil}) + }{uri, fmt.Sprintf("%s/%s", hostport, uri), styletext, jstext, buf.String(), nil}) + } else if parts[1] == "T" { + _, _, err = vips.NewTransform(). + Load(res.Body). + ResizeStrategy(vips.ResizeStrategyAuto). + ResizeWidth(160). + Quality(75). + Output(w). + Apply() } else { io.Copy(w, res.Body) } } else { - if err := renderDirectory(w, tpl, styletext, uri, hostport, res.Dir); err != nil { + if err := renderDirectory(w, tpl, styletext, jstext, uri, hostport, res.Dir); err != nil { io.WriteString(w, fmt.Sprintf("Error:
%s
", err)) return } @@ -240,6 +253,11 @@ func ListenAndServe(bind, robotsfile string, robotsdebug bool, uri string) error styletext = "" } + jstext, err := box.FindString("main.js") + if err != nil { + jstext = "" + } + favicondata, err := box.Find("favicon.ico") if err != nil { favicondata = []byte{} @@ -257,6 +275,9 @@ func ListenAndServe(bind, robotsfile string, robotsdebug bool, uri string) error "safeCss": func(s string) template.CSS { return template.CSS(s) }, + "safeJs": func(s string) template.JS { + return template.JS(s) + }, "HTMLEscape": func(s string) string { return html.EscapeString(s) }, @@ -267,6 +288,11 @@ func ListenAndServe(bind, robotsfile string, robotsdebug bool, uri string) error "pop": func(s []string) []string { return s[:len(s)-1] }, + "replace": func(pattern, output string, input interface{}) string { + var re = regexp.MustCompile(pattern) + var inputStr = fmt.Sprintf("%v", input) + return re.ReplaceAllString(inputStr, output) + }, } tpl, err = template.New("gophermenu").Funcs(funcMap).Parse(tpltext) @@ -274,7 +300,11 @@ func ListenAndServe(bind, robotsfile string, robotsdebug bool, uri string) error log.Fatal(err) } - http.HandleFunc("/", GopherHandler(tpl, robotsdata, robotsdebug, styletext, uri)) + vips.Startup(&vips.Config{ + ConcurrencyLevel: 2, + }) + + http.HandleFunc("/", GopherHandler(tpl, robotsdata, robotsdebug, styletext, jstext, uri)) http.HandleFunc("/robots.txt", RobotsTxtHandler(robotstxtdata)) http.HandleFunc("/favicon.ico", FaviconHandler(favicondata)) http.HandleFunc("/iosevka-term-ss03-regular.woff", FontHandler(false, fontdataw)) diff --git a/js/main.ts b/js/main.ts new file mode 100644 index 0000000..21e589d --- /dev/null +++ b/js/main.ts @@ -0,0 +1,42 @@ +let linkQryEls = document.getElementsByClassName('link--QRY'); +let i = linkQryEls.length; +while (i--) { + linkQryEls[i].addEventListener('click', e => { + e.preventDefault(); + + const resp = prompt('Please enter required input: ', ''); + if ((resp !== null) && (resp !== "")) { + window.location.href = (e.target as HTMLAnchorElement).href + '?' + resp; + } + + return false; + }); +} + +let imgPreviewEls = document.getElementsByClassName('img-preview'); +i = imgPreviewEls.length; +while (i--) { + const imgPreviewEl = imgPreviewEls[i] as HTMLAnchorElement; + const child = imgPreviewEl.children[0] as HTMLImageElement; + const thumbnailUrl = child.src; + + child.addEventListener('load', e => { + child.classList.remove('faded'); + }); + + imgPreviewEls[i].addEventListener('click', e => { + e.preventDefault(); + + child.classList.add('faded'); + + if (child.classList.contains('expanded')) { + child.classList.remove('expanded'); + child.src = thumbnailUrl; + } else { + child.classList.add('expanded'); + child.src = imgPreviewEl.href; + } + + return false; + }); +} diff --git a/template.go b/template.go index 33486d5..f5f4f2b 100644 --- a/template.go +++ b/template.go @@ -47,7 +47,10 @@ var tpltext = ` {{- $content = printf "%s\n" $content -}} {{- end -}} {{- if .Link -}} - {{- $content = printf "%s%s" $content (printf "%s %s" .Type .Type .Link (.Text | HTMLEscape)) -}} + {{- $content = printf "%s%s" $content (printf "%s %s" .Type .Type .Link (.Text | HTMLEscape)) -}} + {{- if or (eq .Type "IMG") (eq .Type "GIF") -}} + {{- $content = printf "%s\n%s" $content (printf " -> " .Link (.Link | replace "^/(.*?)/I" "/$1/T")) -}} + {{- end -}} {{- else -}} {{- $content = printf "%s%s" $content (printf " %s" (.Text | HTMLEscape)) -}} {{- end -}} @@ -59,16 +62,7 @@ var tpltext = ` ` -- cgit v1.2.3-70-g09d2