Compare commits

...
6 Commits
Author SHA1 Message Date
Pierre Dubouilh 52188c1ae1 Merge pull request #4 from pldubouilh/rm
add rm/rename commands
2018-11-24 11:19:12 +01:00
Pierre Dubouilh 64c69716b9 add rm/rename commands 2018-11-24 11:18:35 +01:00
Pierre Dubouilh 83e610bbf4 Merge pull request #3 from pldubouilh/cleanup-js
cleanup js
2018-11-17 18:17:53 +01:00
Pierre Dubouilh 1d0d7c3b8c cleanup js 2018-11-17 17:01:27 +01:00
Pierre Dubouilh 989449f3c3 Merge pull request #2 from pldubouilh/docker
dockerfile
2018-11-17 01:08:12 +01:00
Pierre Dubouilh e192cf373a dockerfile 2018-11-17 01:01:58 +01:00
8 changed files with 235 additions and 192 deletions
+1 -1
View File
@@ -2,7 +2,7 @@ debug
test test
gossa.go gossa.go
gossa gossa
gossa-linux gossa-linux64
gossa-linux-arm gossa-linux-arm
gossa-linux-arm64 gossa-linux-arm64
gossa-mac gossa-mac
+4
View File
@@ -0,0 +1,4 @@
FROM alpine
EXPOSE 8001
COPY gossa /gossa
ENTRYPOINT [ "/gossa", "-h", "0.0.0.0", "/shared" ]
+3 -3
View File
@@ -1,6 +1,6 @@
build: build:
make embed make embed
go build gossa.go CGO_ENABLED=0 go build gossa.go
rm gossa.go rm gossa.go
embed: embed:
@@ -16,8 +16,8 @@ run:
ci: ci:
cd src && go vet && go fmt cd src && go vet && go fmt
timeout 5 make run & timeout 10 make run &
cd src && sleep 1 && go test cd src && sleep 5 && go test
ci-watch: ci-watch:
ls src/* | entr -rc make ci ls src/* | entr -rc make ci
+36 -11
View File
@@ -5,17 +5,19 @@ gossa
[![Build Status](https://travis-ci.org/pldubouilh/gossa.svg?branch=master)](https://travis-ci.org/pldubouilh/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. It's dependency-free and with under 250 lines for the server code, easily code-reviewable. 🎶 A fast and simple webserver for your files, that's dependency-free and with under 240 lines for the server code, easily code-reviewable.
### features ### features
* browse through files/directories * browse through files/directories
* upload with drag-and-drop * upload with drag-and-drop
* create new folders * move/rename/delete files
* move files with drag-and-drop and keyboard
* browse through pictures with a full-screen carousel * browse through pictures with a full-screen carousel
* simple keyboard navigation/shortcuts * simple keyboard navigation/shortcuts
* fast ; fills my 80MB/s AC wifi link * fast ; fills my 80MB/s AC wifi link
### built blobs
built blobs are available on the [release page](https://github.com/pldubouilh/gossa/releases).
### run ### run
```sh ```sh
# build # build
@@ -26,12 +28,35 @@ make
``` ```
### keyboard shortcuts ### keyboard shortcuts
* Arrows/Enter browse throughout the files/directories and pictures |shortcut | action|
* Ctrl/Meta + C copy URL to clipboard |-------------|-------------|
* Ctrl/Meta + D create a new directory |Arrows/Enter | browse through files/directories and pictures|
* Ctrl/Meta + X cut selected path |Ctrl/Meta + C | copy URL to clipboard|
* Ctrl/Meta + V paste previously selected paths to directory |Ctrl/Meta + E | rename file/folder|
* \<any letter\> search |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|
|\<any letter\> | search|
### built blobs ### ui shortcuts
built blobs are available on the [release page](https://github.com/pldubouilh/gossa/releases). |shortcut | action|
| ------------- |-------------|
|click new folder icon | create new folder|
|click images icon | toggle image carousel|
|click file icon | rename item|
|double click file icon | delete item|
|drag-and-drop item on UI | move item|
|drag-and-drop external item | upload file/folders|
### using with docker
a pretty short docker file is provided
```sh
# build docker image
make
docker build -t gossa .
# run
sudo docker run -v ~/ToShare:/shared -p 8001:8001 gossa
```
+3 -1
View File
@@ -67,7 +67,7 @@ func row(name string, href string, size float64, ext string) string {
} }
return `<tr> return `<tr>
<td><i class="btn icon icon-` + strings.ToLower(ext) + ` icon-blank"></i></td> <td><i ondblclick="return rm(event)" onclick="return rename(event)" class="btn icon icon-` + strings.ToLower(ext) + ` icon-blank"></i></td>
<td class="file-size"><code>` + sizeToString(size) + `</code></td> <td class="file-size"><code>` + sizeToString(size) + `</code></td>
<td class="arrow"><i class="arrow-icon"></i></td> <td class="arrow"><i class="arrow-icon"></i></td>
<td class="display-name"><a class="list-links" onclick="return onClickLink(event)" href="` + url.PathEscape(href) + `">` + name + `</a></td> <td class="display-name"><a class="list-links" onclick="return onClickLink(event)" href="` + url.PathEscape(href) + `">` + name + `</a></td>
@@ -185,6 +185,8 @@ func rpc(w http.ResponseWriter, r *http.Request) {
err = os.MkdirAll(payload.Args[0], os.ModePerm) err = os.MkdirAll(payload.Args[0], os.ModePerm)
} else if payload.Call == "mv" { } else if payload.Call == "mv" {
err = os.Rename(payload.Args[0], payload.Args[1]) err = os.Rename(payload.Args[0], payload.Args[1])
} else if payload.Call == "rm" {
err = os.Remove(payload.Args[0])
} }
logVerb("RPC", err, payload) logVerb("RPC", err, payload)
+21 -4
View File
@@ -77,7 +77,7 @@ func testDefaults(t *testing.T, url string) string {
t.Fatal("error 中文 folder") t.Fatal("error 中文 folder")
} }
if !strings.Contains(bodyStr, `<tr> <td><i class="btn icon icon-types icon-blank"></i></td> <td class="file-size"><code>0.2k</code></td> <td class="arrow"><i class="arrow-icon"></i></td> <td class="display-name"><a class="list-links" onclick="return onClickLink(event)" href="custom_mime_type.types">custom_mime_type.types</a></td> </tr>`) { if !strings.Contains(bodyStr, `<tr> <td><i ondblclick="return rm(event)" onclick="return rename(event)" class="btn icon icon-types icon-blank"></i></td> <td class="file-size"><code>0.2k</code></td> <td class="arrow"><i class="arrow-icon"></i></td> <td class="display-name"><a class="list-links" onclick="return onClickLink(event)" href="custom_mime_type.types">custom_mime_type.types</a></td> </tr>`) {
t.Fatal("error row custom_mime_type") 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/") bodyStr = testDefaults(t, "http://127.0.0.1:8001/")
if !strings.Contains(bodyStr, `<tr> <td><i class="btn icon icon-folder icon-blank"></i></td> <td class="file-size"><code>0</code></td> <td class="arrow"><i class="arrow-icon"></i></td> <td class="display-name"><a class="list-links" onclick="return onClickLink(event)" href="AAA">AAA/</a></td> </tr>`) { if !strings.Contains(bodyStr, `<tr> <td><i ondblclick="return rm(event)" onclick="return rename(event)" class="btn icon icon-folder icon-blank"></i></td> <td class="file-size"><code>0</code></td> <td class="arrow"><i class="arrow-icon"></i></td> <td class="display-name"><a class="list-links" onclick="return onClickLink(event)" href="AAA">AAA/</a></td> </tr>`) {
t.Fatal("error new folder created") t.Fatal("error new folder created")
} }
@@ -140,7 +140,7 @@ func TestGetFolder(t *testing.T) {
} }
bodyStr = testDefaults(t, "http://127.0.0.1:8001/") bodyStr = testDefaults(t, "http://127.0.0.1:8001/")
if strings.Contains(bodyStr, `<tr> <td><i class="btn icon icon-folder icon-blank"></i></td> <td class="file-size"><code>0</code></td> <td class="arrow"><i class="arrow-icon"></i></td> <td class="display-name"><a class="list-links" onclick="return onClickLink(event)" href="AAA">AAA/</a></td> </tr>`) { if strings.Contains(bodyStr, `<tr> <td><i ondblclick="return rm(event)" onclick="return rename(event)" class="btn icon icon-folder icon-blank"></i></td> <td class="file-size"><code>0</code></td> <td class="arrow"><i class="arrow-icon"></i></td> <td class="display-name"><a class="list-links" onclick="return onClickLink(event)" href="AAA">AAA/</a></td> </tr>`) {
t.Fatal("error folder moved") t.Fatal("error folder moved")
} }
@@ -157,7 +157,7 @@ func TestGetFolder(t *testing.T) {
} }
bodyStr = testDefaults(t, "http://127.0.0.1:8001/") bodyStr = testDefaults(t, "http://127.0.0.1:8001/")
if !strings.Contains(bodyStr, `<tr> <td><i class="btn icon icon-하 하 icon-blank"></i></td> <td class="file-size"><code>0.0k</code></td> <td class="arrow"><i class="arrow-icon"></i></td> <td class="display-name"><a class="list-links" onclick="return onClickLink(event)" href="%E1%84%92%E1%85%A1%20%E1%84%92%E1%85%A1">하 하</a></td> </tr>`) { if !strings.Contains(bodyStr, `<tr> <td><i ondblclick="return rm(event)" onclick="return rename(event)" class="btn icon icon-하 하 icon-blank"></i></td> <td class="file-size"><code>0.0k</code></td> <td class="arrow"><i class="arrow-icon"></i></td> <td class="display-name"><a class="list-links" onclick="return onClickLink(event)" href="%E1%84%92%E1%85%A1%20%E1%84%92%E1%85%A1">하 하</a></td> </tr>`) {
t.Fatal("error checking new file row") t.Fatal("error checking new file row")
} }
@@ -167,4 +167,21 @@ func TestGetFolder(t *testing.T) {
if !strings.Contains(bodyStr, `err`) { if !strings.Contains(bodyStr, `err`) {
t.Fatal("error not returned") t.Fatal("error not returned")
} }
// ~~~~~~~~~~~~~~~~~
fmt.Println("\r\n~~~~~~~~~~ test rm rpc & cleanup")
bodyStr = postJSON(t, "http://127.0.0.1:8001/rpc", `{"call":"rm","args":["/hols/AAA"]}`)
if !strings.Contains(bodyStr, `ok`) {
t.Fatal("error returned value")
}
bodyStr = get(t, "http://127.0.0.1:8001/hols/AAA")
if !strings.Contains(bodyStr, `error`) {
t.Fatal("error not returned")
}
bodyStr = postJSON(t, "http://127.0.0.1:8001/rpc", `{"call":"rm","args":["/하 하"]}`)
if !strings.Contains(bodyStr, `ok`) {
t.Fatal("error returned value")
}
} }
+166 -172
View File
@@ -6,39 +6,83 @@ function cancelDefault (e) {
e.stopPropagation() e.stopPropagation()
} }
const warningMsg = () => 'Leaving will interrupt transfer?\n'
const rmMsg = () => !confirm('Remove file?\n')
const barName = document.getElementById('dlBarName')
const barPc = document.getElementById('dlBarPc')
const barDiv = document.getElementById('progress')
const upGrid = document.getElementById('drop-grid')
const pics = document.getElementById('pics')
const picsHolder = document.getElementById('picsHolder')
const picsLabel = document.getElementById('picsLabel')
// helpers
let allA
let imgsIndex
let allImgs
const decode = a => decodeURIComponent(a).replace(location.origin, '')
const getArrowSelected = () => document.querySelectorAll('i.arrow-selected')[0]
const getASelected = () => !getArrowSelected() ? false : getArrowSelected().parentElement.parentElement.querySelectorAll('a')[0]
const prependPath = a => a.startsWith('/') ? a : decodeURI(location.pathname) + a
const prevent = e => e.preventDefault()
// Soft nav
function browseTo (href) {
fetch(href).then(r => r.text().then(t => {
const parsed = new DOMParser().parseFromString(t, 'text/html')
const table = parsed.querySelectorAll('table')[0].innerHTML
document.body.querySelectorAll('table')[0].innerHTML = table
const title = parsed.head.querySelectorAll('title')[0].innerText
// check if is current path - if so skip following
if (document.head.querySelectorAll('title')[0].innerText !== title) {
document.head.querySelectorAll('title')[0].innerText = title
document.body.querySelectorAll('h1')[0].innerText = '.' + title
history.pushState({}, '', encodeURI(title))
}
init()
}))
}
window.onClickLink = e => {
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
}
const refresh = () => browseTo(location.href)
const prevPage = () => browseTo(location.href + '../')
window.onpopstate = prevPage
// RPC // RPC
function rpcFs (call, args, cb) { function rpcFs (call, args, cb) {
console.log('RPC', call, args) console.log('RPC', call, args)
const xhr = new window.XMLHttpRequest() const xhr = new XMLHttpRequest()
xhr.open('POST', location.origin + '/rpc') xhr.open('POST', location.origin + '/rpc')
xhr.setRequestHeader('Content-Type', 'application/json;charset=UTF-8') xhr.setRequestHeader('Content-Type', 'application/json;charset=UTF-8')
xhr.send(JSON.stringify({ call, args })) xhr.send(JSON.stringify({ call, args }))
xhr.onload = cb xhr.onload = cb
} }
const prependPath = (a) => a.startsWith('/') ? a : decodeURI(location.pathname) + a
// RPC Handlers
const mkdirCall = (path, cb) => rpcFs('mkdirp', [prependPath(path)], cb) const mkdirCall = (path, cb) => rpcFs('mkdirp', [prependPath(path)], cb)
const rmCall = (path1, cb) => rpcFs('rm', [prependPath(path1)], cb)
const mvCall = (path1, path2, cb) => rpcFs('mv', [path1, path2], cb) const mvCall = (path1, path2, cb) => rpcFs('mv', [path1, path2], cb)
// Mkdir switch // File upload
window.mkdirBtn = function () { let totalDone = 0
const folder = window.prompt('New folder name', '') let totalUploads = 0
let totalUploadsSize = 0
let totalUploadedSize = []
if (!folder) { const dupe = test => allA.find(a => a.innerText.replace('/', '') === test)
return const isDupe = t => dupe(t) ? alert(t + ' already already exists') || true : false
} else if (checkDupes(folder)) {
return window.alert('Name already already exists')
}
mkdirCall(folder, refresh)
}
function warning (e) {
return 'Leaving will interrupt transfer\nAre you sure you want to leave?'
}
function shouldRefresh () { function shouldRefresh () {
totalDone += 1 totalDone += 1
@@ -54,19 +98,6 @@ function shouldRefresh () {
} }
} }
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) { function updatePercent (ev) {
totalUploadedSize[ev.target.id] = ev.loaded totalUploadedSize[ev.target.id] = ev.loaded
const ttlDone = totalUploadedSize.reduce((s, x) => s + x) const ttlDone = totalUploadedSize.reduce((s, x) => s + x)
@@ -77,17 +108,17 @@ function updatePercent (ev) {
function postFile (file, path) { function postFile (file, path) {
path = decodeURI(location.pathname).slice(0, -1) + path path = decodeURI(location.pathname).slice(0, -1) + path
window.onbeforeunload = warning window.onbeforeunload = warningMsg
barDiv.style.display = 'block' barDiv.style.display = 'block'
totalUploads += 1 totalUploads += 1
totalUploadsSize += file.size totalUploadsSize += file.size
barName.innerText = totalUploads > 1 ? totalUploads + ' files' : file.name barName.innerText = totalUploads > 1 ? totalUploads + ' files' : file.name
const formData = new window.FormData() const formData = new FormData()
formData.append(file.name, file) formData.append(file.name, file)
const xhr = new window.XMLHttpRequest() const xhr = new XMLHttpRequest()
xhr.open('POST', location.origin + '/post') xhr.open('POST', location.origin + '/post')
xhr.setRequestHeader('gossa-path', encodeURIComponent(path)) xhr.setRequestHeader('gossa-path', encodeURIComponent(path))
xhr.upload.addEventListener('load', shouldRefresh) xhr.upload.addEventListener('load', shouldRefresh)
@@ -96,15 +127,12 @@ function postFile (file, path) {
xhr.send(formData) xhr.send(formData)
} }
const parseDomFolder = f => { const parseDomFolder = f => f.createReader().readEntries(e => e.forEach(i => parseDomItem(i)))
f.createReader().readEntries(e => e.forEach(i => parseDomItem(i)))
}
function parseDomItem (domFile, shoudCheckDupes) { function parseDomItem (domFile, shoudCheckDupes) {
if (shoudCheckDupes && checkDupes(domFile.name)) { if (shoudCheckDupes && isDupe(domFile.name)) {
return window.alert(domFile.name + ' already exists !') return
} }
if (domFile.isFile) { if (domFile.isFile) {
domFile.file(f => postFile(f, domFile.fullPath)) domFile.file(f => postFile(f, domFile.fullPath))
} else { } else {
@@ -116,7 +144,7 @@ function parseDomItem (domFile, shoudCheckDupes) {
function pushEntry (entry) { function pushEntry (entry) {
if (!entry.webkitGetAsEntry && !entry.getAsEntry) { if (!entry.webkitGetAsEntry && !entry.getAsEntry) {
return window.alert('Unsupported browser ! Please update to chrome/firefox.') return alert('Unsupported browser ! Please update to chrome/firefox.')
} else { } else {
entry = entry.webkitGetAsEntry() || entry.getAsEntry() entry = entry.webkitGetAsEntry() || entry.getAsEntry()
} }
@@ -135,9 +163,7 @@ const setBackgroundLinks = t => { t.style.backgroundColor = 'rgba(123, 123, 123,
const getLink = e => e.target.parentElement.querySelectorAll('a.list-links')[0] const getLink = e => e.target.parentElement.querySelectorAll('a.list-links')[0]
const upGrid = document.getElementById('drop-grid') document.ondragenter = e => {
document.ondragenter = (e) => {
if (isPicMode()) { return } if (isPicMode()) { return }
cancelDefault(e) cancelDefault(e)
@@ -155,18 +181,18 @@ document.ondragenter = (e) => {
} }
} }
upGrid.ondragleave = (e) => { upGrid.ondragleave = e => {
cancelDefault(e) cancelDefault(e)
upGrid.style.display = 'none' upGrid.style.display = 'none'
} }
document.ondragover = (e) => { document.ondragover = e => {
cancelDefault(e) cancelDefault(e)
return false return false
} }
// Handle drop - upload or move // Handle drop - upload or move
document.ondrop = (e) => { document.ondrop = e => {
cancelDefault(e) cancelDefault(e)
upGrid.style.display = 'none' upGrid.style.display = 'none'
resetBackgroundLinks() resetBackgroundLinks()
@@ -186,16 +212,46 @@ document.ondrop = (e) => {
return false return false
} }
const getArrowSelected = () => document.querySelectorAll('i.arrow-selected')[0] // Mkdir icon
window.mkdirBtn = function () {
function getASelected () { const folder = prompt('new folder name', '')
const dest = getArrowSelected() if (folder && !isDupe(folder)) {
return !dest ? false : dest.parentElement.parentElement.querySelectorAll('a')[0] mkdirCall(folder, refresh)
}
} }
// Icon click handler
const getBtnA = e => e.target.parentElement.parentElement.querySelector('a')
window.rm = e => {
clearTimeout(window.clickToken)
const path = e.key ? getASelected().href : getBtnA(e).pathname
rmMsg() || rmCall(decode(path), refresh)
}
window.rename = (e, commit) => {
clearTimeout(window.clickToken)
if (!commit) {
window.clickToken = setTimeout(window.rename, 300, e, true)
return
}
const orig = e.key ? getASelected().innerText : getBtnA(e).innerText
const chg = prompt('rename to', orig)
if (chg && !isDupe(chg)) {
mvCall(prependPath(orig), prependPath(chg), refresh)
}
}
// Keyboard Arrow
const storeLastArrowSrc = src => localStorage.setItem('last-selected' + location.href, src)
function scrollToArrow () { function scrollToArrow () {
const pos = getArrowSelected().getBoundingClientRect() const pos = getArrowSelected().getBoundingClientRect()
window.scrollTo(0, pos.y) if (pos.top < 0 || pos.bottom > window.innerHeight) {
setTimeout(scrollTo, 50, 0, pos.y)
}
} }
function clearArrowSelected () { function clearArrowSelected () {
@@ -222,8 +278,6 @@ function restoreCursorPos () {
scrollToArrow() scrollToArrow()
} }
const storeLastArrowSrc = src => localStorage.setItem('last-selected' + location.href, src)
function moveArrow (down) { function moveArrow (down) {
const all = Array.from(document.querySelectorAll('i.arrow-icon')) const all = Array.from(document.querySelectorAll('i.arrow-icon'))
let i = all.findIndex(el => el.classList.contains('arrow-selected')) let i = all.findIndex(el => el.classList.contains('arrow-selected'))
@@ -242,96 +296,49 @@ function moveArrow (down) {
const itemPos = all[i].getBoundingClientRect() const itemPos = all[i].getBoundingClientRect()
if (i === 0) { if (i === 0) {
window.scrollTo(0, 0) scrollTo(0, 0)
} else if (i === all.length - 1) { } else if (i === all.length - 1) {
window.scrollTo(0, document.documentElement.scrollHeight) scrollTo(0, document.documentElement.scrollHeight)
} else if (itemPos.top < 0) { } else if (itemPos.top < 0) {
window.scrollBy(0, -200) scrollBy(0, -200)
} else if (itemPos.bottom > window.innerHeight) { } else if (itemPos.bottom > window.innerHeight) {
window.scrollBy(0, 200) scrollBy(0, 200)
} }
} }
const refresh = () => browseTo(location.href) // Pictures carousel
const prevPage = () => browseTo(location.href + '../')
window.onpopstate = prevPage
function browseTo (href) {
window.fetch(href).then(r => r.text().then(t => {
const parsed = new window.DOMParser().parseFromString(t, 'text/html')
const table = parsed.querySelectorAll('table')[0].innerHTML
document.body.querySelectorAll('table')[0].innerHTML = table
const title = parsed.head.querySelectorAll('title')[0].innerText
// check if is current path - if so skip following
if (document.head.querySelectorAll('title')[0].innerText !== title) {
document.head.querySelectorAll('title')[0].innerText = title
document.body.querySelectorAll('h1')[0].innerText = '.' + title
window.history.pushState({}, '', window.encodeURI(title))
}
init()
}))
}
function cpPath () {
var t = document.createElement('textarea')
t.value = getASelected().href
document.body.appendChild(t)
t.select()
document.execCommand('copy')
document.body.removeChild(t)
}
const pics = document.getElementById('pics')
const picsHolder = document.getElementById('picsHolder')
const picsLabel = document.getElementById('picsLabel')
const picTypes = ['.jpg', '.jpeg', '.png', '.gif'] const picTypes = ['.jpg', '.jpeg', '.png', '.gif']
const isPic = src => src && picTypes.find(type => src.toLocaleLowerCase().includes(type)) const isPic = src => src && picTypes.find(type => src.toLocaleLowerCase().includes(type))
const isPicMode = () => pics.style.display === 'flex' const isPicMode = () => pics.style.display === 'flex'
window.picsNav = () => picsNav(true)
let imgsIndex function setImage () {
let allImgs const src = allImgs[imgsIndex]
function setImage (src) {
src = src || allImgs[imgsIndex]
picsLabel.innerText = src.split('/').pop()
picsHolder.src = src picsHolder.src = src
picsLabel.innerText = src.split('/').pop()
storeLastArrowSrc(src) storeLastArrowSrc(src)
restoreCursorPos()
} }
function picsOn (ifImgSelected, href) { function picsOn (ifImgSelected, href) {
href = href || getASelected().href href = href || getASelected().href
if (isPicMode()) { if (isPicMode() || (ifImgSelected && !isPic(href))) {
return false
} else if (ifImgSelected && !isPic(href)) {
return false return false
} }
if (isPic(href)) { if (isPic(href)) {
imgsIndex = allImgs.findIndex(el => el.includes(href)) imgsIndex = allImgs.findIndex(el => el.includes(href))
setImage()
} else {
setImage(picsHolder.src)
} }
setImage()
pics.style.display = 'flex' pics.style.display = 'flex'
return true return true
} }
function picsToggle () { const picsOff = () => { pics.style.display = 'none' }
if (!isPicMode()) {
picsOn() window.picsToggle = () => isPicMode() ? picsOff() : picsOn()
} else {
pics.style.display = 'none'
restoreCursorPos()
}
}
function picsNav (down) { function picsNav (down) {
if (!isPicMode()) { return false } if (!isPicMode()) { return false }
@@ -346,19 +353,8 @@ function picsNav (down) {
return true return true
} }
let allA // Paste handler
let typedPath = ''
let typedToken = null
function setCursorToClosestTyped () {
const a = allA.find(el => el.innerText.toLocaleLowerCase().startsWith(typedPath))
if (!a) { return }
storeLastArrowSrc(a.href)
restoreCursorPos()
}
let cuts = [] let cuts = []
function onPaste () { function onPaste () {
if (!cuts.length) { return refresh() } if (!cuts.length) { return refresh() }
const root = cuts.pop() const root = cuts.pop()
@@ -370,76 +366,78 @@ function onPaste () {
} }
// Kb handler // Kb handler
let typedPath = ''
let typedToken = null
function cpPath () {
var t = document.createElement('textarea')
t.value = getASelected().href
document.body.appendChild(t)
t.select()
document.execCommand('copy')
document.body.removeChild(t)
}
function setCursorToClosestTyped () {
const a = allA.find(el => el.innerText.toLocaleLowerCase().startsWith(typedPath))
if (!a) { return }
storeLastArrowSrc(a.href)
restoreCursorPos()
}
document.body.addEventListener('keydown', e => { document.body.addEventListener('keydown', e => {
switch (e.code) { switch (e.code) {
case 'Tab': case 'Tab':
case 'ArrowDown': case 'ArrowDown':
e.preventDefault() return prevent(e) || picsNav(true) || moveArrow(true)
return picsNav(true) || moveArrow(true)
case 'ArrowUp': case 'ArrowUp':
e.preventDefault() return prevent(e) || picsNav(false) || moveArrow(false)
return picsNav(false) || moveArrow(false)
case 'Enter': case 'Enter':
case 'ArrowRight': case 'ArrowRight':
e.preventDefault() return prevent(e) || picsOn(true) || picsNav(true) || getASelected().click()
return picsOn(true) || picsNav(true) || getASelected().click()
case 'ArrowLeft': case 'ArrowLeft':
e.preventDefault() return prevent(e) || picsNav(false) || prevPage()
return picsNav(false) || prevPage()
case 'Escape': case 'Escape':
if (isPicMode()) { return prevent(e) || picsOff()
e.preventDefault()
return picsToggle()
}
} }
// Ctrl keys // Ctrl keys
if (e.ctrlKey || e.metaKey) { if (e.ctrlKey || e.metaKey) {
switch (e.code) { switch (e.code) {
case 'KeyD':
e.preventDefault()
return isPicMode() || window.mkdirBtn()
case 'KeyC': case 'KeyC':
e.preventDefault() return prevent(e) || isPicMode() || cpPath()
return isPicMode() || cpPath()
case 'KeyX': case 'KeyX':
e.preventDefault() cuts.push(prependPath(decode(getASelected().href)))
const x = decodeURIComponent(getASelected().href).replace(location.origin, '') return prevent(e) || false
cuts.push(prependPath(x))
return false
case 'KeyV': case 'KeyV':
e.preventDefault() return prevent(e) || onPaste()
return onPaste()
case 'Backspace':
return prevent(e) || isPicMode() || window.rm(e)
case 'KeyE':
return prevent(e) || isPicMode() || window.rename(e)
case 'KeyD':
return prevent(e) || isPicMode() || window.mkdirBtn()
} }
} }
// Any other key, for text search // text search
if (e.code.includes('Key')) { if (e.code.includes('Key')) {
typedPath += e.code.replace('Key', '').toLocaleLowerCase() typedPath += e.code.replace('Key', '').toLocaleLowerCase()
window.clearTimeout(typedToken) clearTimeout(typedToken)
typedToken = setTimeout(() => { typedPath = '' }, 1000) typedToken = setTimeout(() => { typedPath = '' }, 1000)
setCursorToClosestTyped() setCursorToClosestTyped()
} }
}, false) }, false)
window.onClickLink = e => {
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 () { function init () {
allA = Array.from(document.querySelectorAll('a.list-links')) allA = Array.from(document.querySelectorAll('a.list-links'))
allImgs = allA.map(el => el.href).filter(isPic) allImgs = allA.map(el => el.href).filter(isPic)
@@ -449,8 +447,4 @@ function init () {
restoreCursorPos() restoreCursorPos()
console.log('Browsed to ' + location.href) console.log('Browsed to ' + location.href)
} }
init() init()
window.picsToggle = picsToggle
window.picsNav = () => picsNav(true)
+1
View File
@@ -4,6 +4,7 @@
width: 16px; width: 16px;
zoom: 1.2; zoom: 1.2;
margin: 1px; margin: 1px;
cursor: pointer;
} }
.arrow-icon { .arrow-icon {