diff --git a/Makefile b/Makefile index 8ea6729..7c74da0 100755 --- a/Makefile +++ b/Makefile @@ -1,29 +1,28 @@ -build: +run: make embed + go vet && go fmt CGO_ENABLED=0 go build gossa.go rm gossa.go + ./gossa fixture + +watch: + ls src/* | entr -rc make run embed: echo "embedding css and js into binary" cp src/main.go gossa.go + perl -pe 's/template_will_be_here/`cat src\/template.go`/ge' -i gossa.go perl -pe 's/css_will_be_here/`cat src\/style.css`/ge' -i gossa.go + perl -pe 's/theme_will_be_here/`cat src\/theme.css`/ge' -i gossa.go perl -pe 's/js_will_be_here/`cat src\/script.js`/ge' -i gossa.go perl -pe 's/favicon_will_be_here/`base64 -w0 src\/favicon.png`/ge' -i gossa.go -run: - make build - ./gossa fixture - ci: - cd src && go vet && go fmt timeout 10 make run & - cd src && sleep 5 && go test + cp src/gossa_test.go . && sleep 5 && go test; rm gossa gossa_test.go ci-watch: - ls src/* | entr -rc make ci - -watch: - ls src/* | entr -rc make run + ls src/* | entr -rc make ci build-all: make embed diff --git a/readme.md b/readme.md index 3abd8ab..5e8c14b 100644 --- a/readme.md +++ b/readme.md @@ -5,13 +5,14 @@ gossa [![Build Status](https://travis-ci.org/pldubouilh/gossa.svg?branch=master)](https://travis-ci.org/pldubouilh/gossa) -🎢 A fast and simple webserver for your files, that's dependency-free and with under 240 lines for the server code, easily code-reviewable. +🎢 A fast and simple webserver for your files, that's dependency-free and with under 210 lines for the server code, easily code-reviewable. ### features * browse through files/directories * upload with drag-and-drop * move/rename/delete files * browse through pictures with a full-screen carousel + * stream videos directly from the browser * simple keyboard navigation/shortcuts * fast ; fills my 80MB/s AC wifi link @@ -32,12 +33,13 @@ make |-------------|-------------| |Arrows/Enter | browse through files/directories and pictures| |Ctrl/Meta + C | copy URL to clipboard| +|Ctrl/Meta + B | toggle theme (dark/clear)| +|\ | search| |Ctrl/Meta + E | rename file/folder| |Ctrl/Meta + Del | delete file/folder| |Ctrl/Meta + D | create a new directory| |Ctrl/Meta + X | cut selected path| |Ctrl/Meta + V | paste previously selected paths to directory| -|\ | search| ### ui shortcuts |shortcut | action| diff --git a/src/main_test.go b/src/gossa_test.go similarity index 89% rename from src/main_test.go rename to src/gossa_test.go index 60bffa2..80652a6 100644 --- a/src/main_test.go +++ b/src/gossa_test.go @@ -77,7 +77,7 @@ func testDefaults(t *testing.T, url string) string { t.Fatal("error δΈ­ζ–‡ folder") } - if !strings.Contains(bodyStr, ` 0.2k custom_mime_type.types `) { + if !strings.Contains(bodyStr, ` 211.0B custom_mime_type.types `) { t.Fatal("error row custom_mime_type") } @@ -116,7 +116,7 @@ func TestGetFolder(t *testing.T) { } bodyStr = testDefaults(t, "http://127.0.0.1:8001/") - if !strings.Contains(bodyStr, ` 0 AAA/ `) { + if !strings.Contains(bodyStr, ` AAA/ `) { t.Fatal("error new folder created") } @@ -140,7 +140,7 @@ func TestGetFolder(t *testing.T) { } bodyStr = testDefaults(t, "http://127.0.0.1:8001/") - if strings.Contains(bodyStr, ` 0 AAA/ `) { + if strings.Contains(bodyStr, ` AAA/ `) { t.Fatal("error folder moved") } @@ -157,7 +157,7 @@ func TestGetFolder(t *testing.T) { } bodyStr = testDefaults(t, "http://127.0.0.1:8001/") - if !strings.Contains(bodyStr, ` 0.0k α„’α…‘ α„’α…‘ `) { + if !strings.Contains(bodyStr, ` 9.0B α„’α…‘ α„’α…‘ `) { t.Fatal("error checking new file row") } diff --git a/src/main.go b/src/main.go index 4d7dd08..2799388 100755 --- a/src/main.go +++ b/src/main.go @@ -6,6 +6,7 @@ import ( "flag" "fmt" "html" + "html/template" "io" "io/ioutil" "log" @@ -21,14 +22,23 @@ var host = flag.String("h", "127.0.0.1", "host to listen to") var port = flag.String("p", "8001", "port to listen to") var verb = flag.Bool("verb", true, "verbosity") var skipHidden = flag.Bool("k", true, "skip hidden files") - var initPath = "" -var css = `css_will_be_here` // js will be embedded here -var js = `js_will_be_here` // id. css -var favicon = "data:image/png;base64,favicon_will_be_here" // id. b64 favicon -var units = [8]string{"k", "M", "G", "T", "P", "E", "Z", "Y"} var fs http.Handler +var page, _ = template.New("pageTemplate").Parse(`template_will_be_here`) + +type rowTemplate struct { + Name string + Href template.HTML + Size string + Ext string +} + +type pageTemplate struct { + Title template.HTML + RowsFiles []rowTemplate + RowsFolders []rowTemplate +} type rpcCall struct { Call string `json:"call"` @@ -47,84 +57,52 @@ func logVerb(s ...interface{}) { } } -func sizeToString(bytes float64) string { - if bytes == 0 { - return "0" - } - var u = -1 +func sizeToString(bytes int64) string { + units := [9]string{"B", "k", "M", "G", "T", "P", "E", "Z", "Y"} + b := float64(bytes) + u := 0 for { - bytes = bytes / 1024 - u++ - if bytes < 1024 { - return strconv.FormatFloat(bytes, 'f', 1, 64) + units[u] + if b < 1024 { + return strconv.FormatFloat(b, 'f', 1, 64) + units[u] } + b = b / 1024 + u++ } } -func row(name string, href string, size float64, ext string) string { - if strings.HasPrefix(href, "/") { - href = strings.Replace(href, "/", "", 1) - } - - return ` - - ` + sizeToString(size) + ` - - ` + name + ` - ` -} - func replyList(w http.ResponseWriter, path string) { if !strings.HasSuffix(path, "/") { path += "/" } - var head = ` - - - - ` + html.EscapeString(path) + ` - - - - - -
Drop here to upload
-

.` + html.EscapeString(path) + `

-
-
- - ` - _files, err := ioutil.ReadDir(initPath + path) check(err) + p := pageTemplate{} if path != "/" { - head += row("../", "../", 0, "folder") + p.RowsFolders = append(p.RowsFolders, rowTemplate{"../", "../", "", "folder"}) } - var dirs = "" - var files = "" - for _, el := range _files { - var name = el.Name() + name := el.Name() + href := url.PathEscape(name) if *skipHidden && strings.HasPrefix(name, ".") { continue } - + if el.IsDir() && strings.HasPrefix(href, "/") { + href = strings.Replace(href, "/", "", 1) + } if el.IsDir() { - dirs += row(name+"/", name, 0, "folder") + p.RowsFolders = append(p.RowsFolders, rowTemplate{name + "/", template.HTML(href), "", "folder"}) } else { - var sl = strings.Split(name, ".") - var ext = sl[len(sl)-1] - files += row(name, name, float64(el.Size()), ext) + sl := strings.Split(name, ".") + ext := strings.ToLower(sl[len(sl)-1]) + p.RowsFiles = append(p.RowsFiles, rowTemplate{name, template.HTML(href), sizeToString(el.Size()), ext}) } } - w.Write([]byte(head + dirs + files + `
-
Gossa 🎢
- - `)) + p.Title = template.HTML(html.EscapeString(path)) + page.Execute(w, p) } func doContent(w http.ResponseWriter, r *http.Request) { @@ -206,7 +184,6 @@ func checkPath(p string) (string, error) { func main() { flag.Parse() - if len(flag.Args()) == 0 { initPath = "." } else { @@ -217,11 +194,11 @@ func main() { initPath, err = filepath.Abs(initPath) check(err) - var hostString = *host + ":" + *port + hostString := *host + ":" + *port fmt.Println("Gossa startig on directory " + initPath) fmt.Println("Listening on http://" + hostString) - var root = http.Dir(initPath) + root := http.Dir(initPath) fs = http.StripPrefix("/", http.FileServer(root)) http.HandleFunc("/rpc", rpc) diff --git a/src/script.js b/src/script.js index 7ef4b80..564fd34 100755 --- a/src/script.js +++ b/src/script.js @@ -163,6 +163,11 @@ const setBackgroundLinks = t => { t.style.backgroundColor = 'rgba(123, 123, 123, const getLink = e => e.target.parentElement.querySelectorAll('a.list-links')[0] +upGrid.ondragleave = e => { + cancelDefault(e) + upGrid.style.display = 'none' +} + document.ondragenter = e => { if (isPicMode()) { return } cancelDefault(e) @@ -181,10 +186,7 @@ document.ondragenter = e => { } } -upGrid.ondragleave = e => { - cancelDefault(e) - upGrid.style.display = 'none' -} +document.ondragend = e => resetBackgroundLinks() document.ondragover = e => { cancelDefault(e) @@ -402,7 +404,7 @@ document.body.addEventListener('keydown', e => { return prevent(e) || picsNav(false) || prevPage() case 'Escape': - return prevent(e) || picsOff() + return prevent(e) || resetBackgroundLinks() || picsOff() } // Ctrl keys @@ -426,11 +428,14 @@ document.body.addEventListener('keydown', e => { case 'KeyD': return prevent(e) || isPicMode() || window.mkdirBtn() + + case 'KeyB': + return prevent(e) || toggleTheme() } } // text search - if (e.code.includes('Key')) { + if (e.code.includes('Key') && !e.ctrlKey && !e.metaKey) { typedPath += e.code.replace('Key', '').toLocaleLowerCase() clearTimeout(typedToken) typedToken = setTimeout(() => { typedPath = '' }, 1000) diff --git a/src/style.css b/src/style.css index 5dbaa44..319ed0f 100755 --- a/src/style.css +++ b/src/style.css @@ -1,3 +1,9 @@ +a { + text-decoration: none; + background-color: transparent !important; + /* border-bottom: .01em solid #dfe6e9; */ +} + .icon { display: block; height: 16px; @@ -75,7 +81,6 @@ h1 { } #progress { - background-color: white; width: 99%; left: 0.5%; right: 0.5%; @@ -83,7 +88,6 @@ h1 { bottom: 0px; padding-bottom: 10px; max-height: 50%; - overflow-y: scroll; overflow-x: hidden; } @@ -118,10 +122,9 @@ h1 { text-align: center; font-size: 4em; font-family: Helvetica; - background-color: white; color: green; opacity: 0.8; - backdrop-filter: grayscale(100%); + background-color: rgba(123, 123, 123, 0.2) } #pics { diff --git a/src/template.go b/src/template.go new file mode 100644 index 0000000..df2c0b2 --- /dev/null +++ b/src/template.go @@ -0,0 +1,59 @@ + + + + + + {{.Title}} + + + + + + + + +
Drop here to upload
+ +

.{{.Title}}

+ +
+ +
+
+ + + + + {{range .RowsFolders}} + + + + + + + {{end}} + {{range .RowsFiles}} + + + + + + + {{end}} +
{{.Size}}{{.Name}}
{{.Size}}{{.Name}}
+ + + + \ No newline at end of file diff --git a/src/theme.css b/src/theme.css new file mode 100644 index 0000000..b67272f --- /dev/null +++ b/src/theme.css @@ -0,0 +1,7 @@ +html, a { + background-color: #2d3436; color: #dfe6e9; +} + +.arrow, .icon-large-images { + filter: invert(100%) !important; +}