From 300232df775f991c91cb868f3f323220ef3d4b2c Mon Sep 17 00:00:00 2001 From: Pierre Dubouilh Date: Sat, 15 Sep 2018 11:04:35 +0200 Subject: [PATCH 01/11] implemented mv rpc --- .gitignore | 2 + Makefile | 13 ++--- main.go | 67 ++++++++++++----------- script.js | 156 ++++++++++++++++++++++++++++++++--------------------- style.css | 27 ++++++---- 5 files changed, 157 insertions(+), 108 deletions(-) diff --git a/.gitignore b/.gitignore index c3bd858..1104fc1 100755 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ debug test +gossa.go gossa gossa-linux gossa-linux-arm @@ -7,6 +8,7 @@ gossa-linux-arm64 gossa-mac gossa-windows.exe +.vscode fixture/* fixture/*/* !fixture/compress diff --git a/Makefile b/Makefile index 1500c9e..c4326d4 100755 --- a/Makefile +++ b/Makefile @@ -29,9 +29,8 @@ clean: embed: echo "embedding css and js into binary" cp main.go gossa.go - perl -pe 's/some_css/`cat style.css`/ge' -i gossa.go - perl -pe 's/some_js/`cat script.js`/ge' -i gossa.go - go build gossa.go + perl -pe 's/css_will_be_here/`cat style.css`/ge' -i gossa.go + perl -pe 's/js_will_be_here/`cat script.js`/ge' -i gossa.go ci: go fmt @@ -42,8 +41,10 @@ ci: ci-watch: ls main.go script.js main_test.go | entr -rc make ci -debug-watch: - ls main.go script.js | entr -rc go run main.go fixture +watch: + ls main.go script.js | entr -rc make run run: - go run main.go fixture + make embed + go run gossa.go fixture + rm gossa.go diff --git a/main.go b/main.go index 7439e2f..c6aee2f 100755 --- a/main.go +++ b/main.go @@ -31,8 +31,8 @@ var verb = flag.Bool("verb", true, "verbosity") var skipHidden = flag.Bool("k", true, "skip hidden files") var initPath = "" -var css = `some_css` -var jsTag = `some_js` +var css = `css_will_be_here` // js will be embedded here +var jsTag = `js_will_be_here` // id. var units = [8]string{"k", "M", "G", "T", "P", "E", "Z", "Y"} type rpcCall struct { @@ -71,10 +71,20 @@ func row(name string, href string, size float64, ext string) string { ` + sizeToString(size) + ` - ` + name + ` + ` + name + ` ` } +func extraFolder(loc string) string { + if !strings.HasSuffix(loc, "/") { + loc = loc + "/" + } + if !strings.HasPrefix(loc, "/") { + loc = "/" + loc + } + return `` + loc + `` +} + func replyList(w http.ResponseWriter, path string) { if !strings.HasSuffix(path, "/") { path += "/" @@ -88,12 +98,11 @@ func replyList(w http.ResponseWriter, path string) { - -
-
+ +
+
` + extraFolder("/hols/aaa") + `
Drop here to upload
-

.` + html.EscapeString(path) + `

` @@ -123,8 +132,10 @@ func replyList(w http.ResponseWriter, path string) { } var resp = head + dirs + files + `
+
+
Gossa 🎢
- ` + ` w.Write([]byte(resp)) } @@ -174,22 +185,28 @@ func upload(w http.ResponseWriter, r *http.Request) { } func rpc(w http.ResponseWriter, r *http.Request) { + var err error bodyBytes, _ := ioutil.ReadAll(r.Body) bodyString := string(bodyBytes) var payload rpcCall json.Unmarshal([]byte(bodyString), &payload) - unparsed, _ := url.PathUnescape(payload.Args[0]) - p, err := checkPath(unparsed) - logVerb("RPC", err, unparsed) - - if err != nil { - w.Write([]byte("error")) - return - } else if payload.Call == "mkdirp" { - os.MkdirAll(p, os.ModePerm) + for i := range payload.Args { + payload.Args[i], err = checkPath(payload.Args[i]) + if err != nil { + logVerb("Cant read path", err, payload) + w.Write([]byte("error")) + return + } } + if payload.Call == "mkdirp" { + err = os.MkdirAll(payload.Args[0], os.ModePerm) + } else if payload.Call == "mv" { + err = os.Rename(payload.Args[0], payload.Args[1]) + } + + logVerb("RPC", err, payload) w.Write([]byte("ok")) } @@ -198,7 +215,7 @@ func checkPath(p string) (string, error) { fp, err := filepath.Abs(p) if err != nil || !strings.HasPrefix(fp, initPath) { - return fp, errors.New("error") + return "", errors.New("error") } return fp, nil @@ -217,20 +234,6 @@ func main() { initPath, err = filepath.Abs(initPath) check(err) - // Read CSS file if not embedded - if len(css) < 10 { - c, err := ioutil.ReadFile("./style.css") - check(err) - css = string(c) - } - - // Read JS file if not embedded - if len(jsTag) < 10 { - j, err := ioutil.ReadFile("./script.js") - check(err) - jsTag = string(j) - } - var hostString = *host + ":" + *port fmt.Println("Gossa startig on directory " + initPath) fmt.Println("Listening on http://" + hostString) diff --git a/script.js b/script.js index 331d7fa..a37c820 100755 --- a/script.js +++ b/script.js @@ -1,25 +1,34 @@ +/* eslint-env browser */ +/* global allA */ +/* eslint-disable no-multi-str */ + function cancelDefault (e) { e.preventDefault() e.stopPropagation() } -const checkDupes = test => allA.find(a => a.innerText.replace('/', '') === test) - +// RPC function rpcFs (call, args, cb) { - const decodedPath = decodeURI(window.location.pathname) - args = args.map(a => a.startsWith('/') ? a.slice(1) : a) - args = args.map(a => encodeURIComponent(decodedPath + a)) + // Prefix path with pwd if not absolute + const decodedPath = decodeURI(location.pathname) + args = args.map(a => a.startsWith('/') ? a : decodedPath + a) + // args = args.map(a => encodeURIComponent(a)) + console.log('RPC', call, args) const xhr = new window.XMLHttpRequest() - xhr.open('POST', window.location.origin + '/rpc') + xhr.open('POST', location.origin + '/rpc') xhr.setRequestHeader('Content-Type', 'application/json;charset=UTF-8') - xhr.send(JSON.stringify({call, args})) + xhr.send(JSON.stringify({ call, args })) xhr.onload = cb } +// RPC Handlers const mkdirCall = (path, cb) => rpcFs('mkdirp', [path], cb) -function mkdir () { +const mvCall = (path1, path2, cb) => rpcFs('mv', [path1, path2], cb) + +// Mkdir switch +window.mkdirBtn = function () { const folder = window.prompt('New folder name', '') if (!folder) { @@ -28,13 +37,14 @@ function mkdir () { return window.alert('Name already already exists') } - mkdirCall(folder, () => browseTo(location.href)) + mkdirCall(folder, refresh) } function warning (e) { return 'Leaving will interrupt transfer\nAre you sure you want to leave?' } +// File upload function newBar (name) { const id = Math.random().toString(36).substring(7) @@ -61,10 +71,12 @@ function shouldRefresh () { totalDone = 0 totalUploads = 0 document.getElementById('progressBars').innerHTML = '' - browseTo(location.href) + refresh() } } +const checkDupes = test => allA.find(a => a.innerText.replace('/', '') === test) + function postFile (file, path) { totalUploads += 1 window.onbeforeunload = warning @@ -72,8 +84,8 @@ function postFile (file, path) { const xhr = new window.XMLHttpRequest() path = decodeURI(location.pathname).slice(0, -1) + path - xhr.open('POST', window.location.origin + '/post') - xhr.setRequestHeader("gossa-path", encodeURIComponent(path)) + xhr.open('POST', location.origin + '/post') + xhr.setRequestHeader('gossa-path', encodeURIComponent(path)) xhr.upload.id = newBar(path) const formData = new window.FormData() @@ -100,7 +112,9 @@ function parseDomItem (domFile, shoudCheckDupes) { if (domFile.isFile) { domFile.file(f => postFile(f, domFile.fullPath)) } else { - mkdirCall(domFile.fullPath, () => parseDomFolder(domFile)) + // remove absolute path + const f = domFile.fullPath.startsWith('/') ? domFile.fullPath.slice(1) : domFile.fullPath + mkdirCall(f, () => parseDomFolder(domFile)) } } @@ -114,13 +128,35 @@ function pushEntry (entry) { parseDomItem(entry, true) } +// Move files and folders +const isTextEvent = e => e.dataTransfer.items[0].type === 'text/plain' + +const isFolder = e => e && e.href && e.innerText.endsWith('/') + +const resetBackgroundLinks = () => { allA.forEach(a => { a.parentElement.style.backgroundColor = 'unset' }) } + +const setBackgroundLinks = t => { t.style.backgroundColor = 'rgba(123, 123, 123, 0.2)' } + +const getLink = e => e.target.parentElement.querySelectorAll('a.list-links')[0] + const upGrid = document.getElementById('drop-grid') document.ondragenter = (e) => { if (isPicMode()) { return } cancelDefault(e) - e.dataTransfer.dropEffect = 'copy' - upGrid.style.display = 'flex' + + resetBackgroundLinks() + + if (isTextEvent(e) && (isFolder(e.target) || isFolder(e.target.firstChild))) { + const t = getLink(e) + if (!t) return + setBackgroundLinks(t.parentElement) + } + + if (!isTextEvent(e)) { + upGrid.style.display = 'flex' + e.dataTransfer.dropEffect = 'copy' + } } upGrid.ondragleave = (e) => { @@ -133,11 +169,24 @@ document.ondragover = (e) => { return false } +// Handle drop - upload or move document.ondrop = (e) => { cancelDefault(e) upGrid.style.display = 'none' + resetBackgroundLinks() + + if (isTextEvent(e)) { + const t = e.target.classList.contains('fav') ? e.target : getLink(e) + if (!t || !t.innerText.endsWith('/')) return + e.dataTransfer.items[0].getAsString(s => { + const root = decodeURI(s.replace(location.href, '')) + const dest = t.innerText + root + mvCall(root, dest, refresh) + }) + } else { + Array.from(e.dataTransfer.items).forEach(pushEntry) + } - Array.from(e.dataTransfer.items).forEach(pushEntry) return false } @@ -164,7 +213,7 @@ function clearArrowSelected () { function restoreCursorPos () { clearArrowSelected() - const hrefSelected = window.localStorage.getItem('last-selected' + location.href) + const hrefSelected = localStorage.getItem('last-selected' + location.href) let a = allA.find(el => el.href === hrefSelected) if (!a) { @@ -180,7 +229,7 @@ function restoreCursorPos () { scrollToArrow() } -const storeLastArrowSrc = src => window.localStorage.setItem('last-selected' + location.href, src) +const storeLastArrowSrc = src => localStorage.setItem('last-selected' + location.href, src) function moveArrow (down) { const all = Array.from(document.querySelectorAll('i.arrow-icon')) @@ -210,12 +259,9 @@ function moveArrow (down) { } } -function setCursorToClosest () { - const a = allA.find(el => el.innerText.toLocaleLowerCase().startsWith(path)) - if (!a) { return } - storeLastArrowSrc(a.href) - restoreCursorPos() -} +const refresh = () => browseTo(location.href) + +const prevPage = () => browseTo(location.href + '../') window.onpopstate = prevPage @@ -231,22 +277,12 @@ function browseTo (href) { document.head.querySelectorAll('title')[0].innerText = title document.body.querySelectorAll('h1')[0].innerText = '.' + title window.history.pushState({}, '', window.encodeURI(title)) - } + } init() })) } -function nextPage () { - const a = getASelected() - if (!a.href || !a.innerText.endsWith('/')) { return } - browseTo(a.href) -} - -function prevPage () { - browseTo(window.location.href + "../") -} - function cpPath () { var t = document.createElement('textarea') t.value = getASelected().href @@ -317,8 +353,16 @@ function picsNav (down) { return true } -let path = '' -let clearPathToken = null +let allA +let typedPath = '' +let typedToken = null + +function setCursorToClosestTyped () { + const a = allA.find(el => el.innerText.toLocaleLowerCase().startsWith(typedPath)) + if (!a) { return } + storeLastArrowSrc(a.href) + restoreCursorPos() +} // Kb handler document.body.addEventListener('keydown', e => { @@ -332,9 +376,11 @@ document.body.addEventListener('keydown', e => { e.preventDefault() return picsNav(false) || moveArrow(false) + case 'Enter': + case 'Space': case 'ArrowRight': e.preventDefault() - return picsOn(true) || picsNav(true) || nextPage() + return picsOn(true) || picsNav(true) || getASelected().click() case 'ArrowLeft': e.preventDefault() @@ -345,10 +391,6 @@ document.body.addEventListener('keydown', e => { e.preventDefault() return picsToggle() } - - case 'Enter': - e.preventDefault() - return picsOn(true) || picsNav(true) || getASelected().click() } // Ctrl keys @@ -356,7 +398,7 @@ document.body.addEventListener('keydown', e => { switch (e.code) { case 'KeyD': e.preventDefault() - return isPicMode() || mkdir() + return isPicMode() || window.mkdirBtn() case 'KeyC': e.preventDefault() @@ -366,31 +408,26 @@ document.body.addEventListener('keydown', e => { // Any other key, for text search if (e.code.includes('Key')) { - path += e.code.replace('Key', '').toLocaleLowerCase() - window.clearTimeout(clearPathToken) - clearPathToken = setTimeout(() => { path = '' }, 1000) - setCursorToClosest() + typedPath += e.code.replace('Key', '').toLocaleLowerCase() + window.clearTimeout(typedToken) + typedToken = setTimeout(() => { typedPath = '' }, 1000) + setCursorToClosestTyped() } }, false) -function partialBrowseOnClickFolders () { - allA.forEach(a => { - if (!a.innerText.endsWith('/')) { return } - a.addEventListener('click', e => { - e.preventDefault() - storeLastArrowSrc(e.target.href) - browseTo(e.target.href) - }) - }, false) +window.onClickLink = e => { + if (!e.target.innerText.endsWith('/')) { return true } + storeLastArrowSrc(e.target.href) + browseTo(e.target.href) + return false } function init () { - allA = Array.from(document.querySelectorAll('a')) + allA = Array.from(document.querySelectorAll('a.list-links')) allImgs = allA.map(el => el.href).filter(isPic) - document.getElementById('picsToggle').style.display = allImgs.length > 0 ? 'flex' : 'none' + document.getElementsByClassName('icon-large-images')[0].style.display = allImgs.length > 0 ? 'inline-block' : 'none' imgsIndex = 0 - partialBrowseOnClickFolders() restoreCursorPos() console.log('Browsed to ' + location.href) } @@ -399,4 +436,3 @@ init() window.picsToggle = picsToggle window.picsNav = () => picsNav(true) -window.mkdir = mkdir diff --git a/style.css b/style.css index 82437fc..353e863 100755 --- a/style.css +++ b/style.css @@ -31,28 +31,27 @@ td.file-size { td.display-name { padding-left: 0.2em; + width: 100%; } -#newFolder { - background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAFG0lEQVRYR81XfUxbVRQ/77XQrgxWZB06ZZjNOFaklfIxxhbM5owaIqLgZGUhQUOCRBLJJKCwZPEPNBoXFgPuI+G/zT/mki2Zc5uZATNBRBiDoqXA6ootbdnaUWD9bj33ta9pS8vYaMJOcnLffb33nt/5nXPPeaVgjYVaY/vw5ADo7OwsMBgMzVwu10pYcbvdEcmhKMrO4XCOtLa2TseCPYaBlpaWNJFIdDEnJyfbarWCx+Nh1Ov1MiMRdqRpGqamppQqlSoPQS+sFgQDoLm5OTkpKalXKpVmGI1GxnuiLJBgQAQUgrinVqvfam9v74sJgLq6uvWpqan9BQUF4tnZ2QCAcCDEuB+ABxnownD8uFy4cC2no6Pj8nIgGQbq6+t5QqFwKC8vT6zX68HlcjEgyEi8Z9kgxon4gbgSEhIcAoGAPR/x+HIac4SwRNSrVCq/a2tra44Ggr0FVFNTkyI3N1es0+kCxqMxwAIhhxKjrPqNMgCIkvdarbYPQ1X4MADQ2Ng4gkmYhRvA7nSD1UkFmAjOhWDjQa4zxggA1nt21OsMt245Je+wa21cWBw5UWUM7GUfGhoaBgsLC2UzMzMwcjcRzJ4NwOXQhO8A7aFe+N4jB4HXzJM/DGQkc4fTYXdRfC27yE3TNqN+rnLgdOVwyG7Mg978/PxdhIHRxS3w6is7wWJzRc2fSBWMtb10k291PJeGnr+m51R3tAcGTx26FgKgtra2B3OgiAAYd2VA0W4Z6My2qAAe5wd+HAeG/tboNFpT+Z8n5cwVDjhSU1NzDQG8ptXq4DYtgd0FUpgxM0UxJuL2eGFdPAf6x9RTeq3l7YHT8rFwAJcwBMUanRH0PAm8LM0Aw/3YMODBPLI63CBK4sNv/ZNKg9n8+uCpKk0IgOrq6vPZ2dnv/qszgU20EzK2b4O78/ZVe0+M2xxY2nF8Jnkd/HxDqZhdXCwa/b7SHAKgqqrqrEQiOTilswB/axFsSU8D84JjVQBcSLsdPWfvS9pGAVy4rhjZpHXLzp07wHS7QA7I5fIuBFA9MfMAUjL3w0bRJliwOR8LAIm3w4UVFMdgSUsRwMVfFX3d7WWBwhQAUFFR0SmTyT5SGZwgemk/JCYmgg0LUiQhdJKzfSXZt4KMOMPSjZ2TfRm2+ekNPLjcM3al+/h7b7I/BQCUl5cfy8rKapgwceF52RvAE/DBiV4QIZ6w6sHnUL9WTlJKQhz88sf4me5jZYeWACgtLW3LzMz8bGJeCC/m7QM6Lh7j5wEX812wciPLrUyMp+D6wOTxG+3lnywBUFJSckQsFn8xad0ML2TvAYc3+tdaAM8ywMJ/IlWS63VB7+Dtz3/veP/LJQCKi4s/xQ+Sb8bt6ZC2IwfcgH0gXJg4LycRwuPfQCMCh90Gw4o7H/afONgVDoDeu3fvYeyGX4+7d8Bz26VoKJQBf0t6iHmSjZGXEAbum+acY6PDHyh+OHwWVzEJxlp5CjthDVbCr1QghdRtYqa1shLVa/YGrCBFiKF7erNlZODqx+qr3/6EUxMLgFgS4Q0o2bWv5KQ2LotKfjZ9BUc+2hICQKPW6G6eP1pm0f6jxuksYYFlQIiTlD3yo5e4fGGqFz+l8LrRkS6c1+uvDdGuhr8nUxSHFATKS1NWzvrNA85FE++/oQsd0zevkCZEvA8txfgiCTURlYf6AJWAipUo8aCtqKS5zKNa2IMj3TUSEqLoQsyEGCZn+ipbkDw5f81i5usjHrTmDPwPh1d5PwAYMkwAAAAASUVORK5CYII="); + + +.icHolder { position: fixed; right: 20px; top: 20px; - width: 32px; - height: 32px; - cursor: pointer; } -#picsToggle { - background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAABxUlEQVRYR+2XyyuFQRjGz3HPNfIPiEL5D0RYyNJeVlYuycIlf4EkSRQLG1bWkrKwo7BSyg5LZUW533+PZvQ1nL7jO2echTP1NDPffO/7POed55vmxGMZbvEM88eyAoIVGGY7pkCx5225I/8EWBRPUMAN8xLP5Db9LYNSV8C7Wa2lP/MkpJG8Jyb3548PVsAK8GnMIjjvfQsohGAW9BqiNfpR8Ai09uBbgMw16GzhAnOZ/E8EXENU7gi4Yl4JCkwlvrbfhwcyLkDlHnIqMM98BOSDp1Q80ETwtjFSJ/2pQ6Sp9nkmYMJVxuOGOCUB1SQ5BDWG9Jy+GVz8ICLRozwWnqNUQMp3QIuT+Zh5K5DJbLO+sudKMCSygBWy9CX4WXs813bojK8HG+AFtINLJybXrOlx0iehzDMXUuZd1rfAGNDnpnYE2oC+Ctt+LaCLyE2gwChNwlQde/zmMH41iUIr0MCL+6AiCnMgRpXpBjJf0gKqePkA1KVIbsPXGfQAGfMtrAJyvL71jjSR2zTLDAaSEbDES/1pJrfpdOOaDKuAJ+5vaROa8P8K+MtLqbjKVOrgfUC3mGng+2asG7FOTJk9+88oW4HYB/NaXyF6NJlmAAAAAElFTkSuQmCC"); - position: fixed; - right: 60px; - top: 20px; +.ic { width: 32px; height: 32px; cursor: pointer; + display: inline-block; + overflow: hidden; + color: transparent; } + #picsToggleCinema { background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAACXBIWXMAAAsTAAALEwEAmpwYAAAAoElEQVRYhe2Wuw3AIAxEbZRRMwFlJiBLMCBDcClCCwq2gRRc7/cO8RFEOzt/DAAPIABwBixXWL5nKOBN1JQo8lhYQTooKqFmaAAWCxCDzOQSoLm8BzxM/kUwXN4STZM3SsyTV0qo5HMaW2bpFiw9hEuv4dKHqAdsXkICNCuhAalLWKxCxcDEL9lRmU1EdBPRxcxZWoCZM4Cz8JKUs7MzNA81r4TL7qAyXAAAAABJRU5ErkJggg=="); position: fixed; @@ -156,6 +155,14 @@ td.display-name { image-orientation: from-image; } +.icon-large-folder { + background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAFG0lEQVRYR81XfUxbVRQ/77XQrgxWZB06ZZjNOFaklfIxxhbM5owaIqLgZGUhQUOCRBLJJKCwZPEPNBoXFgPuI+G/zT/mki2Zc5uZATNBRBiDoqXA6ootbdnaUWD9bj33ta9pS8vYaMJOcnLffb33nt/5nXPPeaVgjYVaY/vw5ADo7OwsMBgMzVwu10pYcbvdEcmhKMrO4XCOtLa2TseCPYaBlpaWNJFIdDEnJyfbarWCx+Nh1Ov1MiMRdqRpGqamppQqlSoPQS+sFgQDoLm5OTkpKalXKpVmGI1GxnuiLJBgQAQUgrinVqvfam9v74sJgLq6uvWpqan9BQUF4tnZ2QCAcCDEuB+ABxnownD8uFy4cC2no6Pj8nIgGQbq6+t5QqFwKC8vT6zX68HlcjEgyEi8Z9kgxon4gbgSEhIcAoGAPR/x+HIac4SwRNSrVCq/a2tra44Ggr0FVFNTkyI3N1es0+kCxqMxwAIhhxKjrPqNMgCIkvdarbYPQ1X4MADQ2Ng4gkmYhRvA7nSD1UkFmAjOhWDjQa4zxggA1nt21OsMt245Je+wa21cWBw5UWUM7GUfGhoaBgsLC2UzMzMwcjcRzJ4NwOXQhO8A7aFe+N4jB4HXzJM/DGQkc4fTYXdRfC27yE3TNqN+rnLgdOVwyG7Mg978/PxdhIHRxS3w6is7wWJzRc2fSBWMtb10k291PJeGnr+m51R3tAcGTx26FgKgtra2B3OgiAAYd2VA0W4Z6My2qAAe5wd+HAeG/tboNFpT+Z8n5cwVDjhSU1NzDQG8ptXq4DYtgd0FUpgxM0UxJuL2eGFdPAf6x9RTeq3l7YHT8rFwAJcwBMUanRH0PAm8LM0Aw/3YMODBPLI63CBK4sNv/ZNKg9n8+uCpKk0IgOrq6vPZ2dnv/qszgU20EzK2b4O78/ZVe0+M2xxY2nF8Jnkd/HxDqZhdXCwa/b7SHAKgqqrqrEQiOTilswB/axFsSU8D84JjVQBcSLsdPWfvS9pGAVy4rhjZpHXLzp07wHS7QA7I5fIuBFA9MfMAUjL3w0bRJliwOR8LAIm3w4UVFMdgSUsRwMVfFX3d7WWBwhQAUFFR0SmTyT5SGZwgemk/JCYmgg0LUiQhdJKzfSXZt4KMOMPSjZ2TfRm2+ekNPLjcM3al+/h7b7I/BQCUl5cfy8rKapgwceF52RvAE/DBiV4QIZ6w6sHnUL9WTlJKQhz88sf4me5jZYeWACgtLW3LzMz8bGJeCC/m7QM6Lh7j5wEX812wciPLrUyMp+D6wOTxG+3lnywBUFJSckQsFn8xad0ML2TvAYc3+tdaAM8ywMJ/IlWS63VB7+Dtz3/veP/LJQCKi4s/xQ+Sb8bt6ZC2IwfcgH0gXJg4LycRwuPfQCMCh90Gw4o7H/afONgVDoDeu3fvYeyGX4+7d8Bz26VoKJQBf0t6iHmSjZGXEAbum+acY6PDHyh+OHwWVzEJxlp5CjthDVbCr1QghdRtYqa1shLVa/YGrCBFiKF7erNlZODqx+qr3/6EUxMLgFgS4Q0o2bWv5KQ2LotKfjZ9BUc+2hICQKPW6G6eP1pm0f6jxuksYYFlQIiTlD3yo5e4fGGqFz+l8LrRkS6c1+uvDdGuhr8nUxSHFATKS1NWzvrNA85FE++/oQsd0zevkCZEvA8txfgiCTURlYf6AJWAipUo8aCtqKS5zKNa2IMj3TUSEqLoQsyEGCZn+ipbkDw5f81i5usjHrTmDPwPh1d5PwAYMkwAAAAASUVORK5CYII="); +} + +.icon-large-images { + background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAABxUlEQVRYR+2XyyuFQRjGz3HPNfIPiEL5D0RYyNJeVlYuycIlf4EkSRQLG1bWkrKwo7BSyg5LZUW533+PZvQ1nL7jO2echTP1NDPffO/7POed55vmxGMZbvEM88eyAoIVGGY7pkCx5225I/8EWBRPUMAN8xLP5Db9LYNSV8C7Wa2lP/MkpJG8Jyb3548PVsAK8GnMIjjvfQsohGAW9BqiNfpR8Ai09uBbgMw16GzhAnOZ/E8EXENU7gi4Yl4JCkwlvrbfhwcyLkDlHnIqMM98BOSDp1Q80ETwtjFSJ/2pQ6Sp9nkmYMJVxuOGOCUB1SQ5BDWG9Jy+GVz8ICLRozwWnqNUQMp3QIuT+Zh5K5DJbLO+sudKMCSygBWy9CX4WXs813bojK8HG+AFtINLJybXrOlx0iehzDMXUuZd1rfAGNDnpnYE2oC+Ctt+LaCLyE2gwChNwlQde/zmMH41iUIr0MCL+6AiCnMgRpXpBjJf0gKqePkA1KVIbsPXGfQAGfMtrAJyvL71jjSR2zTLDAaSEbDES/1pJrfpdOOaDKuAJ+5vaROa8P8K+MtLqbjKVOrgfUC3mGng+2asG7FOTJk9+88oW4HYB/NaXyF6NJlmAAAAAElFTkSuQmCC"); +} + .icon-blank { background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAWBJREFUeNqEUj1LxEAQnd1MVA4lyIEWx6UIKEGUExGsbC3tLfwJ/hT/g7VlCnubqxXBwg/Q4hQP/LhKL5nZuBsvuGfW5MGyuzM7jzdvVuR5DgYnZ+f99ai7Vt5t9K9unu4HLweI3qWYxI6PDosdy0fhcntxO44CcOBzPA7mfEyuHwf7ntQk4jcnywOxIlfxOCNYaLVgb6cXbkTdhJXq2SIlNMC0xIqhHczDbi8OVzpLSUa0WebRfmigLHqj1EcPZnwf7gbDIrYVRyEinurj6jTBHyI7pqVrFQqEbt6TEmZ9v1NRAJNC1xTYxIQh/MmRUlmFQE3qWOW1nqB2TWk1/3tgJV0waVvkFIEeZbHq4ElyKzAmEXOx6gnEVJuWBzmkRJBRPYGZBDsVaOlpSgVJE2yVaAe/0kx/3azBRO0VsbMFZE3CDSZKweZfYIVg+DZ6v7h9GDVOwZPw/PoxKu/fAgwALbDAXf7DdQkAAAAASUVORK5CYII="); } From c78d70c438ef1b14a863689bd3cc89dc570fd5f5 Mon Sep 17 00:00:00 2001 From: Pierre Dubouilh Date: Sun, 16 Sep 2018 22:05:02 +0200 Subject: [PATCH 02/11] favicon --- main.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/main.go b/main.go index c6aee2f..a0b6bdf 100755 --- a/main.go +++ b/main.go @@ -94,7 +94,8 @@ func replyList(w http.ResponseWriter, path string) { - ` + html.EscapeString(path) + ` + ` + html.EscapeString(path) + ` + From ba252e5f8de411b824b5aa0fd5d4ff54b63b9fd4 Mon Sep 17 00:00:00 2001 From: Pierre Dubouilh Date: Fri, 21 Sep 2018 15:33:09 +0200 Subject: [PATCH 03/11] shift setup multi uploads --- main.go | 24 +++++++------------ script.js | 70 ++++++++++++++++++++++++++----------------------------- style.css | 22 ++++++++--------- 3 files changed, 52 insertions(+), 64 deletions(-) diff --git a/main.go b/main.go index a0b6bdf..f68c341 100755 --- a/main.go +++ b/main.go @@ -34,6 +34,7 @@ var initPath = "" var css = `css_will_be_here` // js will be embedded here var jsTag = `js_will_be_here` // id. var units = [8]string{"k", "M", "G", "T", "P", "E", "Z", "Y"} +var icon = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwBAMAAAClLOS0AAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAAHlBMVEUAAAAAAACAgIDAwMD///8AAIAAAP8A//+AgAD//wACp+eHAAAAAXRSTlMAQObYZgAAAAFiS0dEBI9o2VEAAAAHdElNRQfiBhoALCQijt0NAAAAuElEQVQ4y92TQRKCMAxF0w4HCC5c18oJxAMwDEfAK+g52OkN9LYqbTRJ6Uyd0Y1vU+DlT0kJAAED+oJAtYKqTAJUiqkwmUAoxiVhMoFnOS4LkwZ8YBVXR89tK9hvSGyl6EnYQeJJNDLQlgjPaLioGSMX6+ObQnH6nvh88z8RB4YQYhaKvnlGeA3Ndq3Rw/6qVNjdz8X5wcTunY0vfxlu14k6cRa6Kh7RLOi8ugrQxK5mQS0i+4FVz3dvYZO5KVb6VAAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxOC0wNi0yNlQwMDo0NDozNi0wNDowMNQdHpQAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTgtMDYtMjZUMDA6NDQ6MzYtMDQ6MDClQKYoAAAAAElFTkSuQmCC" type rpcCall struct { Call string `json:"call"` @@ -95,16 +96,16 @@ func replyList(w http.ResponseWriter, path string) { ` + html.EscapeString(path) + ` - + +
Drop here to upload
+

.` + html.EscapeString(path) + `

` + extraFolder("/hols/aaa") + `
-
Drop here to upload
-

.` + html.EscapeString(path) + `

` _files, err := ioutil.ReadDir(initPath + path) @@ -132,23 +133,14 @@ func replyList(w http.ResponseWriter, path string) { } } - var resp = head + dirs + files + `
-
- -
Gossa 🎢
- ` - - w.Write([]byte(resp)) + w.Write([]byte(head + dirs + files + ` +
Gossa 🎢
+ + `)) } func doContent(w http.ResponseWriter, r *http.Request) { path := html.UnescapeString(r.URL.Path) - - if strings.Contains(path, "/favicon.ico") { - w.Write([]byte(" ")) - return - } - fullPath, errPath := checkPath(path) stat, errStat := os.Stat(fullPath) diff --git a/script.js b/script.js index a37c820..780c0c5 100755 --- a/script.js +++ b/script.js @@ -12,7 +12,6 @@ function rpcFs (call, args, cb) { // Prefix path with pwd if not absolute const decodedPath = decodeURI(location.pathname) args = args.map(a => a.startsWith('/') ? a : decodedPath + a) - // args = args.map(a => encodeURIComponent(a)) console.log('RPC', call, args) const xhr = new window.XMLHttpRequest() @@ -44,25 +43,6 @@ function warning (e) { return 'Leaving will interrupt transfer\nAre you sure you want to leave?' } -// File upload -function newBar (name) { - const id = Math.random().toString(36).substring(7) - - document.getElementById('progressBars').innerHTML += '\ -
\ - ' + name.split('/').pop() + ' \ -
1%
\ -
' - return id -} - -function updatePercent (id, percent) { - const el = document.getElementById(id).querySelectorAll('div.barForeground')[0] - const width = Math.floor(100 * percent).toString() + '%' - el.innerText = width - el.style.width = width -} - function shouldRefresh () { totalDone += 1 if (totalUploads === totalDone) { @@ -70,33 +50,52 @@ function shouldRefresh () { console.log('Done uploading ' + totalDone + ' files') totalDone = 0 totalUploads = 0 - document.getElementById('progressBars').innerHTML = '' + totalUploadsSize = 0 + totalUploadedSize = [] + barDiv.style.display = 'none' refresh() } } const checkDupes = test => allA.find(a => a.innerText.replace('/', '') === test) +const barName = document.getElementById('dlBarName') + +const barPc = document.getElementById('dlBarPc') + +const barDiv = document.getElementById('progress') + +let totalDone = 0 +let totalUploads = 0 +let totalUploadsSize = 0 +let totalUploadedSize = [] + +function updatePercent (ev) { + totalUploadedSize[ev.target.id] = ev.loaded + const ttlDone = totalUploadedSize.reduce((s, x) => s + x) + const pc = Math.floor(100 * ttlDone / totalUploadsSize) + '%' + barPc.innerText = pc + barPc.style.width = pc +} + function postFile (file, path) { - totalUploads += 1 + path = decodeURI(location.pathname).slice(0, -1) + path window.onbeforeunload = warning - const xhr = new window.XMLHttpRequest() - path = decodeURI(location.pathname).slice(0, -1) + path - - xhr.open('POST', location.origin + '/post') - xhr.setRequestHeader('gossa-path', encodeURIComponent(path)) - xhr.upload.id = newBar(path) + barDiv.style.display = 'block' + totalUploads += 1 + totalUploadsSize += file.size + barName.innerText = totalUploads > 1 ? totalUploads + ' files' : file.name const formData = new window.FormData() formData.append(file.name, file) - xhr.upload.addEventListener('progress', a => { - updatePercent(a.target.id, a.loaded / a.total) - }) - + const xhr = new window.XMLHttpRequest() + xhr.open('POST', location.origin + '/post') + xhr.setRequestHeader('gossa-path', encodeURIComponent(path)) xhr.upload.addEventListener('load', shouldRefresh) - + xhr.upload.addEventListener('progress', updatePercent) + xhr.upload.id = totalUploads xhr.send(formData) } @@ -190,9 +189,6 @@ document.ondrop = (e) => { return false } -let totalUploads = 0 -let totalDone = 0 - const getArrowSelected = () => document.querySelectorAll('i.arrow-selected')[0] function getASelected () { @@ -218,7 +214,7 @@ function restoreCursorPos () { if (!a) { if (allA[0].innerText === '../') { - a = allA[1] + a = allA[1] || allA[0] } else { a = allA[0] } diff --git a/style.css b/style.css index 353e863..11af5eb 100755 --- a/style.css +++ b/style.css @@ -34,12 +34,16 @@ td.display-name { width: 100%; } - +h1 { + display: inline-block; + margin-top: 10px; +} .icHolder { - position: fixed; - right: 20px; - top: 20px; + display: inline-block; + right: 15px; + top: 25px; + position: absolute; } .ic { @@ -69,7 +73,7 @@ td.display-name { zoom: 1 !important; } -#progressBars { +#progress { background-color: white; width: 99%; left: 0.5%; @@ -80,18 +84,14 @@ td.display-name { max-height: 50%; overflow-y: scroll; overflow-x: hidden; - } - -#progressBars::-webkit-scrollbar { - display: none; } -.barBackground { +#dlBarName { width: 100%; margin-top: 15px; } -.barForeground { +#dlBarPc { width: 1%; height: 30px; text-align: center; From 2e7b452466af3ebe5a3b41e5b361b52d99d3a5e0 Mon Sep 17 00:00:00 2001 From: Pierre Dubouilh Date: Fri, 21 Sep 2018 22:52:31 +0200 Subject: [PATCH 04/11] favicon --- main.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/main.go b/main.go index f68c341..3819667 100755 --- a/main.go +++ b/main.go @@ -34,7 +34,7 @@ var initPath = "" var css = `css_will_be_here` // js will be embedded here var jsTag = `js_will_be_here` // id. var units = [8]string{"k", "M", "G", "T", "P", "E", "Z", "Y"} -var icon = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwBAMAAAClLOS0AAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAAHlBMVEUAAAAAAACAgIDAwMD///8AAIAAAP8A//+AgAD//wACp+eHAAAAAXRSTlMAQObYZgAAAAFiS0dEBI9o2VEAAAAHdElNRQfiBhoALCQijt0NAAAAuElEQVQ4y92TQRKCMAxF0w4HCC5c18oJxAMwDEfAK+g52OkN9LYqbTRJ6Uyd0Y1vU+DlT0kJAAED+oJAtYKqTAJUiqkwmUAoxiVhMoFnOS4LkwZ8YBVXR89tK9hvSGyl6EnYQeJJNDLQlgjPaLioGSMX6+ObQnH6nvh88z8RB4YQYhaKvnlGeA3Ndq3Rw/6qVNjdz8X5wcTunY0vfxlu14k6cRa6Kh7RLOi8ugrQxK5mQS0i+4FVz3dvYZO5KVb6VAAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxOC0wNi0yNlQwMDo0NDozNi0wNDowMNQdHpQAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTgtMDYtMjZUMDA6NDQ6MzYtMDQ6MDClQKYoAAAAAElFTkSuQmCC" +var favicon = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgBAMAAACBVGfHAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAAGFBMVEUAAACAgIDAwMAAAAD//////wAAAP+AgACyGYBKAAAAAXRSTlMAQObYZgAAAAFiS0dEBI9o2VEAAAAHdElNRQfiBhgXAzXpQrjsAAAArklEQVQoz3WQQQ7CIBBF4QbS4gEsYV8DHkAc3Rev0Buw8PoOQ9KZjPGlm76+hE+N+cdJvdughC+r+OqcAxCJB8CHE1vqBiASH6OHFycj2NEE50g8KdgxybBSMQI0oTYqKOim3EjcR9BNQGGnhSmJVh08PG45R+aScctUmS3jXFlEXdRroKHMnOgqzNxUQcNk8W7jd/CQ1IUcklSx6KImo45pRh1D4iPowiTBan74AnnnVMHA9EjhAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDE4LTA2LTI0VDIzOjAzOjUzLTA0OjAwyUvdTwAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxOC0wNi0yNFQyMzowMzo1My0wNDowMLgWZfMAAAAASUVORK5CYII=" type rpcCall struct { Call string `json:"call"` @@ -96,7 +96,7 @@ func replyList(w http.ResponseWriter, path string) { ` + html.EscapeString(path) + ` - + From e195bfad2ca3958772ac309ead9c32dd4b9ffd4d Mon Sep 17 00:00:00 2001 From: Pierre Dubouilh Date: Sat, 22 Sep 2018 10:29:24 +0200 Subject: [PATCH 05/11] pic mode onclick pictures --- script.js | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/script.js b/script.js index 780c0c5..d6e5edc 100755 --- a/script.js +++ b/script.js @@ -307,8 +307,8 @@ function setImage (src) { storeLastArrowSrc(src) } -function picsOn (ifImgSelected) { - const href = getASelected().href +function picsOn (ifImgSelected, href) { + href = href || getASelected().href if (isPicMode()) { return false @@ -412,10 +412,14 @@ document.body.addEventListener('keydown', e => { }, false) window.onClickLink = e => { - if (!e.target.innerText.endsWith('/')) { return true } - storeLastArrowSrc(e.target.href) - browseTo(e.target.href) - return false + if (e.target.innerText.endsWith('/')) { + storeLastArrowSrc(e.target.href) + browseTo(e.target.href) + return false + } else if (picsOn(true, e.target.href)) { + return false + } + return true } function init () { From bb44f2d852fa79b1f7167b535bb1c9140c27db2c Mon Sep 17 00:00:00 2001 From: Pierre Dubouilh Date: Sat, 22 Sep 2018 10:30:06 +0200 Subject: [PATCH 06/11] remove test option extra fav folder --- main.go | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/main.go b/main.go index 3819667..227c67c 100755 --- a/main.go +++ b/main.go @@ -76,16 +76,6 @@ func row(name string, href string, size float64, ext string) string { ` } -func extraFolder(loc string) string { - if !strings.HasSuffix(loc, "/") { - loc = loc + "/" - } - if !strings.HasPrefix(loc, "/") { - loc = "/" + loc - } - return `` + loc + `` -} - func replyList(w http.ResponseWriter, path string) { if !strings.HasSuffix(path, "/") { path += "/" @@ -104,7 +94,7 @@ func replyList(w http.ResponseWriter, path string) {
Drop here to upload

.` + html.EscapeString(path) + `

-
` + extraFolder("/hols/aaa") + `
+
` From 55678337b9f1df2d56bc425ad954728a84f18ead Mon Sep 17 00:00:00 2001 From: Pierre Dubouilh Date: Mon, 24 Sep 2018 18:41:51 +0200 Subject: [PATCH 07/11] keybinding move elements --- Makefile | 45 ++++++++++++++++++++++----------------------- readme.md | 11 +++++++---- script.js | 36 +++++++++++++++++++++++++++--------- 3 files changed, 56 insertions(+), 36 deletions(-) diff --git a/Makefile b/Makefile index c4326d4..5d5db15 100755 --- a/Makefile +++ b/Makefile @@ -3,6 +3,28 @@ build: go build gossa.go rm gossa.go +embed: + echo "embedding css and js into binary" + cp main.go gossa.go + perl -pe 's/css_will_be_here/`cat style.css`/ge' -i gossa.go + perl -pe 's/js_will_be_here/`cat script.js`/ge' -i gossa.go + +run: + make build + ./gossa fixture + +ci: + go fmt + go vet + timeout 5 make run & + sleep 1 && go test + +ci-watch: + ls main.go script.js main_test.go | entr -rc make ci + +watch: + ls main.go script.js | entr -rc make run + build-all: make embed env GOOS=linux GOARCH=amd64 go build gossa.go @@ -25,26 +47,3 @@ clean: -rm gossa-linux-arm64 -rm gossa-mac -rm gossa-windows.exe - -embed: - echo "embedding css and js into binary" - cp main.go gossa.go - perl -pe 's/css_will_be_here/`cat style.css`/ge' -i gossa.go - perl -pe 's/js_will_be_here/`cat script.js`/ge' -i gossa.go - -ci: - go fmt - go vet - timeout 5 go run main.go fixture & - sleep 1 && go test - -ci-watch: - ls main.go script.js main_test.go | entr -rc make ci - -watch: - ls main.go script.js | entr -rc make run - -run: - make embed - go run gossa.go fixture - rm gossa.go diff --git a/readme.md b/readme.md index ec68ba1..ab49e1b 100644 --- a/readme.md +++ b/readme.md @@ -8,19 +8,20 @@ gossa 🎢 A fast and simple webserver for your files. It's dependency-free and with under 250 lines for the server code, easily code-reviewable. ### features - * upload files and folders with drag-and-drop * browse throughout files/directories + * upload files and folders with drag-and-drop * create new folders + * move files to different directories with drag-and-drop and keyboard * browse throughout pictures with a full-screen carousel * simple keyboard navigation/shortcuts * fast ; fills my 80MB/s AC wifi link ### run ```sh -# run -go run main.go fixture +# run on test fixture folder +make run -# build embedding the js/css in the binary +# build make ./gossa --help @@ -32,6 +33,8 @@ make ci * Arrows/Enter browse throughout the files/directories and pictures * Ctrl/Meta + C copy selected path to clipboard * Ctrl/Meta + D create a new directory + * Ctrl/Meta + X cut selected path + * Ctrl/Meta + V paste paths previously selected with the above shortcut to directory * \ search on first letters in filename ### built blobs diff --git a/script.js b/script.js index d6e5edc..274be6b 100755 --- a/script.js +++ b/script.js @@ -1,5 +1,4 @@ /* eslint-env browser */ -/* global allA */ /* eslint-disable no-multi-str */ function cancelDefault (e) { @@ -9,10 +8,6 @@ function cancelDefault (e) { // RPC function rpcFs (call, args, cb) { - // Prefix path with pwd if not absolute - const decodedPath = decodeURI(location.pathname) - args = args.map(a => a.startsWith('/') ? a : decodedPath + a) - console.log('RPC', call, args) const xhr = new window.XMLHttpRequest() xhr.open('POST', location.origin + '/rpc') @@ -21,8 +16,10 @@ function rpcFs (call, args, cb) { xhr.onload = cb } +const prependPath = (a) => a.startsWith('/') ? a : decodeURI(location.pathname) + a + // RPC Handlers -const mkdirCall = (path, cb) => rpcFs('mkdirp', [path], cb) +const mkdirCall = (path, cb) => rpcFs('mkdirp', [prependPath(path)], cb) const mvCall = (path1, path2, cb) => rpcFs('mv', [path1, path2], cb) @@ -178,9 +175,9 @@ document.ondrop = (e) => { const t = e.target.classList.contains('fav') ? e.target : getLink(e) if (!t || !t.innerText.endsWith('/')) return e.dataTransfer.items[0].getAsString(s => { - const root = decodeURI(s.replace(location.href, '')) + const root = decodeURIComponent(s.replace(location.href, '')) const dest = t.innerText + root - mvCall(root, dest, refresh) + mvCall(prependPath(root), prependPath(dest), refresh) }) } else { Array.from(e.dataTransfer.items).forEach(pushEntry) @@ -360,6 +357,18 @@ function setCursorToClosestTyped () { restoreCursorPos() } +let cuts = [] + +function onPaste () { + if (!cuts.length) { return refresh() } + const root = cuts.pop() + const pwd = decodeURIComponent(location.pathname) + const isFolderDest = getASelected().innerText.endsWith('/') + const filename = root.split('/').pop() + const dest = isFolderDest ? pwd + getASelected().innerText : pwd + mvCall(root, dest + filename, onPaste) +} + // Kb handler document.body.addEventListener('keydown', e => { switch (e.code) { @@ -373,7 +382,6 @@ document.body.addEventListener('keydown', e => { return picsNav(false) || moveArrow(false) case 'Enter': - case 'Space': case 'ArrowRight': e.preventDefault() return picsOn(true) || picsNav(true) || getASelected().click() @@ -399,6 +407,16 @@ document.body.addEventListener('keydown', e => { case 'KeyC': e.preventDefault() return isPicMode() || cpPath() + + case 'KeyX': + e.preventDefault() + const x = decodeURIComponent(getASelected().href).replace(location.origin, '') + cuts.push(prependPath(x)) + return false + + case 'KeyV': + e.preventDefault() + return onPaste() } } From 1519350be508e471a0b49bcaf8d227c7e1d89960 Mon Sep 17 00:00:00 2001 From: Pierre Dubouilh Date: Sat, 29 Sep 2018 23:09:19 +0200 Subject: [PATCH 08/11] cleanup makefile --- Makefile | 7 ++++--- main.go => src/main.go | 0 main_test.go => src/main_test.go | 0 script.js => src/script.js | 0 style.css => src/style.css | 0 5 files changed, 4 insertions(+), 3 deletions(-) rename main.go => src/main.go (100%) rename main_test.go => src/main_test.go (100%) rename script.js => src/script.js (100%) rename style.css => src/style.css (100%) diff --git a/Makefile b/Makefile index 5d5db15..d8e521d 100755 --- a/Makefile +++ b/Makefile @@ -5,15 +5,16 @@ build: embed: echo "embedding css and js into binary" - cp main.go gossa.go - perl -pe 's/css_will_be_here/`cat style.css`/ge' -i gossa.go - perl -pe 's/js_will_be_here/`cat script.js`/ge' -i gossa.go + cp src/main.go gossa.go + perl -pe 's/css_will_be_here/`cat src\/style.css`/ge' -i gossa.go + perl -pe 's/js_will_be_here/`cat src\/script.js`/ge' -i gossa.go run: make build ./gossa fixture ci: + cd src go fmt go vet timeout 5 make run & diff --git a/main.go b/src/main.go similarity index 100% rename from main.go rename to src/main.go diff --git a/main_test.go b/src/main_test.go similarity index 100% rename from main_test.go rename to src/main_test.go diff --git a/script.js b/src/script.js similarity index 100% rename from script.js rename to src/script.js diff --git a/style.css b/src/style.css similarity index 100% rename from style.css rename to src/style.css From f065d66d74c4dfb2829033aa323450ee1e4bc1c3 Mon Sep 17 00:00:00 2001 From: Pierre Dubouilh Date: Sun, 30 Sep 2018 00:23:44 +0200 Subject: [PATCH 09/11] fix tests + add new mv rpc --- Makefile | 6 ++---- src/main_test.go | 30 +++++++++++++++++++++--------- 2 files changed, 23 insertions(+), 13 deletions(-) diff --git a/Makefile b/Makefile index d8e521d..8cb1818 100755 --- a/Makefile +++ b/Makefile @@ -14,11 +14,9 @@ run: ./gossa fixture ci: - cd src - go fmt - go vet + cd src && go vet && go fmt timeout 5 make run & - sleep 1 && go test + cd src && sleep 1 && go test ci-watch: ls main.go script.js main_test.go | entr -rc make ci diff --git a/src/main_test.go b/src/main_test.go index 2ce7f2f..164aece 100644 --- a/src/main_test.go +++ b/src/main_test.go @@ -65,19 +65,19 @@ func testDefaults(t *testing.T, url string) string { t.Fatal("error header") } - if !strings.Contains(bodyStr, `hols/`) { + if !strings.Contains(bodyStr, `href="hols">hols/`) { t.Fatal("error hols folder") } - if !strings.Contains(bodyStr, `curimit@gmail.com (40%)/`) { + if !strings.Contains(bodyStr, `href="curimit@gmail.com%20%2840%25%29">curimit@gmail.com (40%)/`) { t.Fatal("error curimit@gmail.com (40%) folder") } - if !strings.Contains(bodyStr, `δΈ­ζ–‡/`) { + if !strings.Contains(bodyStr, `href="%E4%B8%AD%E6%96%87">δΈ­ζ–‡/`) { t.Fatal("error δΈ­ζ–‡ folder") } - if !strings.Contains(bodyStr, ``) { + if !strings.Contains(bodyStr, ``) { t.Fatal("error row custom_mime_type") } @@ -110,28 +110,40 @@ func TestGetFolder(t *testing.T) { // ~~~~~~~~~~~~~~~~~ fmt.Println("\r\n~~~~~~~~~~ test mkdir rpc") - bodyStr = postJSON(t, "http://127.0.0.1:8001/rpc", `{"call":"mkdirp","args":["%2FAAA"]}`) + bodyStr = postJSON(t, "http://127.0.0.1:8001/rpc", `{"call":"mkdirp","args":["/AAA"]}`) if !strings.Contains(bodyStr, `ok`) { t.Fatal("error returned value") } bodyStr = testDefaults(t, "http://127.0.0.1:8001/") - if !strings.Contains(bodyStr, ``) { + if !strings.Contains(bodyStr, ``) { t.Fatal("error new folder created") } // ~~~~~~~~~~~~~~~~~ fmt.Println("\r\n~~~~~~~~~~ test invalid mkdir rpc") - bodyStr = postJSON(t, "http://127.0.0.1:8001/rpc", `{"call":"mkdirp","args":["..%2FBBB"]}`) + bodyStr = postJSON(t, "http://127.0.0.1:8001/rpc", `{"call":"mkdirp","args":["../BBB"]}`) if !strings.Contains(bodyStr, `error`) { t.Fatal("error not returned") } - bodyStr = postJSON(t, "http://127.0.0.1:8001/rpc", `{"call":"mkdirp","args":["%2F..%2FBBB"]}`) + bodyStr = postJSON(t, "http://127.0.0.1:8001/rpc", `{"call":"mkdirp","args":["/../BBB"]}`) if !strings.Contains(bodyStr, `error`) { t.Fatal("error not returned") } + // ~~~~~~~~~~~~~~~~~ + fmt.Println("\r\n~~~~~~~~~~ test mv rpc") + bodyStr = postJSON(t, "http://127.0.0.1:8001/rpc", `{"call":"mv","args":["/AAA", "/hols/AAA"]}`) + if !strings.Contains(bodyStr, `ok`) { + t.Fatal("error returned value") + } + + bodyStr = testDefaults(t, "http://127.0.0.1:8001/") + if strings.Contains(bodyStr, ``) { + t.Fatal("error folder moved") + } + // ~~~~~~~~~~~~~~~~~ fmt.Println("\r\n~~~~~~~~~~ test post file") bodyStr = postDummyFile(t, "http://127.0.0.1:8001/post", "%2F%E1%84%92%E1%85%A1%20%E1%84%92%E1%85%A1") @@ -145,7 +157,7 @@ func TestGetFolder(t *testing.T) { } bodyStr = testDefaults(t, "http://127.0.0.1:8001/") - if !strings.Contains(bodyStr, ``) { + if !strings.Contains(bodyStr, ``) { t.Fatal("error checking new file row") } From 1457c3927f5e75b18ac79ba0bae08ae896f6d506 Mon Sep 17 00:00:00 2001 From: Pierre Dubouilh Date: Thu, 8 Nov 2018 10:37:59 +0100 Subject: [PATCH 10/11] fix makefile --- Makefile | 4 ++-- src/main.go | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Makefile b/Makefile index 8cb1818..36b02c2 100755 --- a/Makefile +++ b/Makefile @@ -19,10 +19,10 @@ ci: cd src && sleep 1 && go test ci-watch: - ls main.go script.js main_test.go | entr -rc make ci + ls src/* | entr -rc make ci watch: - ls main.go script.js | entr -rc make run + ls src/* | entr -rc make run build-all: make embed diff --git a/src/main.go b/src/main.go index 227c67c..01720b1 100755 --- a/src/main.go +++ b/src/main.go @@ -32,7 +32,7 @@ var skipHidden = flag.Bool("k", true, "skip hidden files") var initPath = "" var css = `css_will_be_here` // js will be embedded here -var jsTag = `js_will_be_here` // id. +var jsTag = `js_will_be_here` // id. css var units = [8]string{"k", "M", "G", "T", "P", "E", "Z", "Y"} var favicon = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgBAMAAACBVGfHAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAAGFBMVEUAAACAgIDAwMAAAAD//////wAAAP+AgACyGYBKAAAAAXRSTlMAQObYZgAAAAFiS0dEBI9o2VEAAAAHdElNRQfiBhgXAzXpQrjsAAAArklEQVQoz3WQQQ7CIBBF4QbS4gEsYV8DHkAc3Rev0Buw8PoOQ9KZjPGlm76+hE+N+cdJvdughC+r+OqcAxCJB8CHE1vqBiASH6OHFycj2NEE50g8KdgxybBSMQI0oTYqKOim3EjcR9BNQGGnhSmJVh08PG45R+aScctUmS3jXFlEXdRroKHMnOgqzNxUQcNk8W7jd/CQ1IUcklSx6KImo45pRh1D4iPowiTBan74AnnnVMHA9EjhAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDE4LTA2LTI0VDIzOjAzOjUzLTA0OjAwyUvdTwAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxOC0wNi0yNFQyMzowMzo1My0wNDowMLgWZfMAAAAASUVORK5CYII=" @@ -43,7 +43,7 @@ type rpcCall struct { func logVerb(s ...interface{}) { if *verb { - log.Println(s) + log.Println(s...) } } From cafc46f3c19ac021d9b82b073a75d441563f8dd2 Mon Sep 17 00:00:00 2001 From: Pierre Dubouilh Date: Fri, 16 Nov 2018 23:39:57 +0100 Subject: [PATCH 11/11] small cleanup --- Makefile | 1 + readme.md | 22 +++++++++------------- src/favicon.png | Bin 0 -> 470 bytes src/main.go | 28 +++++++++++++--------------- 4 files changed, 23 insertions(+), 28 deletions(-) create mode 100644 src/favicon.png diff --git a/Makefile b/Makefile index 36b02c2..63c2521 100755 --- a/Makefile +++ b/Makefile @@ -8,6 +8,7 @@ embed: cp src/main.go gossa.go perl -pe 's/css_will_be_here/`cat src\/style.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 diff --git a/readme.md b/readme.md index ab49e1b..61735a5 100644 --- a/readme.md +++ b/readme.md @@ -8,34 +8,30 @@ gossa 🎢 A fast and simple webserver for your files. It's dependency-free and with under 250 lines for the server code, easily code-reviewable. ### features - * browse throughout files/directories - * upload files and folders with drag-and-drop + * browse through files/directories + * upload with drag-and-drop * create new folders - * move files to different directories with drag-and-drop and keyboard - * browse throughout pictures with a full-screen carousel + * move files with drag-and-drop and keyboard + * browse through pictures with a full-screen carousel * simple keyboard navigation/shortcuts * fast ; fills my 80MB/s AC wifi link ### run ```sh -# run on test fixture folder -make run - # build make -./gossa --help -# run CI tests -make ci +# run +./gossa -h 192.168.100.33 ~/storage ``` ### keyboard shortcuts * Arrows/Enter browse throughout the files/directories and pictures - * Ctrl/Meta + C copy selected path to clipboard + * Ctrl/Meta + C copy URL to clipboard * Ctrl/Meta + D create a new directory * Ctrl/Meta + X cut selected path - * Ctrl/Meta + V paste paths previously selected with the above shortcut to directory - * \ search on first letters in filename + * Ctrl/Meta + V paste previously selected paths to directory + * \ search ### built blobs built blobs are available on the [release page](https://github.com/pldubouilh/gossa/releases). diff --git a/src/favicon.png b/src/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..7e179467c61b33122011c92dfed1f8bbd063b377 GIT binary patch literal 470 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnF3?v&v(vJfvmUKs7M+SzC{oH>NSwWJ?9znhg z3{`3j3=J&|48MRv4KElNN(~qoUL`OvSj}Ky5HFasE6@fgArasc;tEpG&~V_u0U+Z) z2!QB@28KcydFKA(P2MGcAo5 z%kFsGalhc}khr{6volQA#wO3w{n@0#(;+i;AIC(x#9ql>u<*>B7g}fTgg72f`Iz^( z{KJGRtum8BHojP-wzx0TP1gC5@{2<%2eayaFjYPeIe6fU$3vjYRZCnWN>UO_QmvAU zQh^kMk%6Iwu7R1Zkx7V=v6X?bm8r3=fr*uY!AbAC{wNx9^HVa@DsgMrA(r|XsDZ)L L)z4*}Q$iB}GDxG* literal 0 HcmV?d00001 diff --git a/src/main.go b/src/main.go index 01720b1..5a68e8f 100755 --- a/src/main.go +++ b/src/main.go @@ -17,30 +17,30 @@ import ( "strings" ) -func check(e error) { - if e != nil { - panic(e) - } -} - -var fs http.Handler - 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 jsTag = `js_will_be_here` // id. css +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 favicon = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgBAMAAACBVGfHAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAAGFBMVEUAAACAgIDAwMAAAAD//////wAAAP+AgACyGYBKAAAAAXRSTlMAQObYZgAAAAFiS0dEBI9o2VEAAAAHdElNRQfiBhgXAzXpQrjsAAAArklEQVQoz3WQQQ7CIBBF4QbS4gEsYV8DHkAc3Rev0Buw8PoOQ9KZjPGlm76+hE+N+cdJvdughC+r+OqcAxCJB8CHE1vqBiASH6OHFycj2NEE50g8KdgxybBSMQI0oTYqKOim3EjcR9BNQGGnhSmJVh08PG45R+aScctUmS3jXFlEXdRroKHMnOgqzNxUQcNk8W7jd/CQ1IUcklSx6KImo45pRh1D4iPowiTBan74AnnnVMHA9EjhAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDE4LTA2LTI0VDIzOjAzOjUzLTA0OjAwyUvdTwAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxOC0wNi0yNFQyMzowMzo1My0wNDowMLgWZfMAAAAASUVORK5CYII=" + +var fs http.Handler type rpcCall struct { Call string `json:"call"` Args []string `json:"args"` } +func check(e error) { + if e != nil { + panic(e) + } +} + func logVerb(s ...interface{}) { if *verb { log.Println(s...) @@ -56,11 +56,9 @@ func sizeToString(bytes float64) string { bytes = bytes / 1024 u++ if bytes < 1024 { - break + return strconv.FormatFloat(bytes, 'f', 1, 64) + units[u] } } - - return strconv.FormatFloat(bytes, 'f', 1, 64) + units[u] } func row(name string, href string, size float64, ext string) string { @@ -87,7 +85,7 @@ func replyList(w http.ResponseWriter, path string) { ` + html.EscapeString(path) + ` - +
0.2k custom_mime_type.types
0.2k custom_mime_type.types
0 AAA/
0 AAA/
0 AAA/
0.0k α„’α…‘ α„’α…‘
0.0k α„’α…‘ α„’α…‘