mirror of
https://github.com/pldubouilh/gossa.git
synced 2026-08-31 06:16:54 -04:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
52188c1ae1 | ||
|
|
64c69716b9 | ||
|
|
83e610bbf4 | ||
|
|
1d0d7c3b8c | ||
|
|
989449f3c3 | ||
|
|
e192cf373a | ||
|
|
13cf1db89d | ||
|
|
cafc46f3c1 | ||
|
|
1457c3927f | ||
|
|
f065d66d74 | ||
|
|
1519350be5 | ||
|
|
55678337b9 | ||
|
|
bb44f2d852 | ||
|
|
e195bfad2c | ||
|
|
2e7b452466 | ||
|
|
ba252e5f8d | ||
|
|
c78d70c438 | ||
|
|
300232df77 | ||
|
|
aaf1eb8ddd |
+3
-1
@@ -1,12 +1,14 @@
|
|||||||
debug
|
debug
|
||||||
test
|
test
|
||||||
|
gossa.go
|
||||||
gossa
|
gossa
|
||||||
gossa-linux
|
gossa-linux64
|
||||||
gossa-linux-arm
|
gossa-linux-arm
|
||||||
gossa-linux-arm64
|
gossa-linux-arm64
|
||||||
gossa-mac
|
gossa-mac
|
||||||
gossa-windows.exe
|
gossa-windows.exe
|
||||||
|
|
||||||
|
.vscode
|
||||||
fixture/*
|
fixture/*
|
||||||
fixture/*/*
|
fixture/*/*
|
||||||
!fixture/compress
|
!fixture/compress
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
FROM alpine
|
||||||
|
EXPOSE 8001
|
||||||
|
COPY gossa /gossa
|
||||||
|
ENTRYPOINT [ "/gossa", "-h", "0.0.0.0", "/shared" ]
|
||||||
@@ -1,8 +1,30 @@
|
|||||||
build:
|
build:
|
||||||
make embed
|
make embed
|
||||||
go build gossa.go
|
CGO_ENABLED=0 go build gossa.go
|
||||||
rm gossa.go
|
rm gossa.go
|
||||||
|
|
||||||
|
embed:
|
||||||
|
echo "embedding css and js into binary"
|
||||||
|
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
|
||||||
|
./gossa fixture
|
||||||
|
|
||||||
|
ci:
|
||||||
|
cd src && go vet && go fmt
|
||||||
|
timeout 10 make run &
|
||||||
|
cd src && sleep 5 && go test
|
||||||
|
|
||||||
|
ci-watch:
|
||||||
|
ls src/* | entr -rc make ci
|
||||||
|
|
||||||
|
watch:
|
||||||
|
ls src/* | entr -rc make run
|
||||||
|
|
||||||
build-all:
|
build-all:
|
||||||
make embed
|
make embed
|
||||||
env GOOS=linux GOARCH=amd64 go build gossa.go
|
env GOOS=linux GOARCH=amd64 go build gossa.go
|
||||||
@@ -25,25 +47,3 @@ clean:
|
|||||||
-rm gossa-linux-arm64
|
-rm gossa-linux-arm64
|
||||||
-rm gossa-mac
|
-rm gossa-mac
|
||||||
-rm gossa-windows.exe
|
-rm gossa-windows.exe
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
debug-watch:
|
|
||||||
ls main.go script.js | entr -rc go run main.go fixture
|
|
||||||
|
|
||||||
run:
|
|
||||||
go run main.go fixture
|
|
||||||
|
|||||||
@@ -5,33 +5,58 @@ gossa
|
|||||||
|
|
||||||
[](https://travis-ci.org/pldubouilh/gossa)
|
[](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
|
||||||
* upload files and folders with drag-and-drop
|
* browse through files/directories
|
||||||
* browse throughout files/directories
|
* upload with drag-and-drop
|
||||||
* create new folders
|
* move/rename/delete files
|
||||||
* browse throughout 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
|
||||||
# run
|
# build
|
||||||
go run main.go fixture
|
|
||||||
|
|
||||||
# build embedding the js/css in the binary
|
|
||||||
make
|
make
|
||||||
./gossa --help
|
|
||||||
|
|
||||||
# run CI tests
|
# run
|
||||||
make ci
|
./gossa -h 192.168.100.33 ~/storage
|
||||||
```
|
```
|
||||||
|
|
||||||
### keyboard shortcuts
|
### keyboard shortcuts
|
||||||
* Arrows browse throughout the files/directories and pictures
|
|shortcut | action|
|
||||||
* Ctrl/Meta + C copy selected path to clipboard
|
|-------------|-------------|
|
||||||
* Ctrl/Meta + D create a new directory
|
|Arrows/Enter | browse through files/directories and pictures|
|
||||||
|
|Ctrl/Meta + C | copy URL to clipboard|
|
||||||
|
|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|
|
||||||
|
|\<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
|
||||||
|
```
|
||||||
@@ -1,399 +0,0 @@
|
|||||||
function cancelDefault (e) {
|
|
||||||
e.preventDefault()
|
|
||||||
e.stopPropagation()
|
|
||||||
}
|
|
||||||
|
|
||||||
const checkDupes = test => allA.find(a => a.innerText.replace('/', '') === test)
|
|
||||||
|
|
||||||
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))
|
|
||||||
|
|
||||||
const xhr = new window.XMLHttpRequest()
|
|
||||||
xhr.open('POST', window.location.origin + '/rpc')
|
|
||||||
xhr.setRequestHeader('Content-Type', 'application/json;charset=UTF-8')
|
|
||||||
xhr.send(JSON.stringify({call, args}))
|
|
||||||
xhr.onload = cb
|
|
||||||
}
|
|
||||||
|
|
||||||
const mkdirCall = (path, cb) => rpcFs('mkdirp', [path], cb)
|
|
||||||
|
|
||||||
function mkdir () {
|
|
||||||
const folder = window.prompt('New folder name', '')
|
|
||||||
|
|
||||||
if (!folder) {
|
|
||||||
return
|
|
||||||
} else if (checkDupes(folder)) {
|
|
||||||
return window.alert('Name already already exists')
|
|
||||||
}
|
|
||||||
|
|
||||||
mkdirCall(folder, () => browseTo(location.href))
|
|
||||||
}
|
|
||||||
|
|
||||||
function warning (e) {
|
|
||||||
return 'Leaving will interrupt transfer\nAre you sure you want to leave?'
|
|
||||||
}
|
|
||||||
|
|
||||||
function newBar (name) {
|
|
||||||
const id = Math.random().toString(36).substring(7)
|
|
||||||
|
|
||||||
document.getElementById('progressBars').innerHTML += '\
|
|
||||||
<div id="' + id + '" class="barBackground">\
|
|
||||||
<span> ' + name.split('/').pop() + ' <span>\
|
|
||||||
<div class="barForeground">1%</div>\
|
|
||||||
</div>'
|
|
||||||
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) {
|
|
||||||
window.onbeforeunload = null
|
|
||||||
console.log('Done uploading ' + totalDone + ' files')
|
|
||||||
totalDone = 0
|
|
||||||
totalUploads = 0
|
|
||||||
document.getElementById('progressBars').innerHTML = ''
|
|
||||||
browseTo(location.href)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function postFile (file, path) {
|
|
||||||
totalUploads += 1
|
|
||||||
window.onbeforeunload = warning
|
|
||||||
|
|
||||||
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.upload.id = newBar(path)
|
|
||||||
|
|
||||||
const formData = new window.FormData()
|
|
||||||
formData.append(file.name, file)
|
|
||||||
|
|
||||||
xhr.upload.addEventListener('progress', a => {
|
|
||||||
console.log("YA")
|
|
||||||
updatePercent(a.target.id, a.loaded / a.total)
|
|
||||||
})
|
|
||||||
|
|
||||||
xhr.upload.addEventListener('load', shouldRefresh)
|
|
||||||
|
|
||||||
xhr.send(formData)
|
|
||||||
}
|
|
||||||
|
|
||||||
const parseDomFolder = f => {
|
|
||||||
f.createReader().readEntries(e => e.forEach(i => parseDomItem(i)))
|
|
||||||
}
|
|
||||||
|
|
||||||
function parseDomItem (domFile, shoudCheckDupes) {
|
|
||||||
if (shoudCheckDupes && checkDupes(domFile.name)) {
|
|
||||||
return window.alert(domFile.name + ' already exists !')
|
|
||||||
}
|
|
||||||
|
|
||||||
if (domFile.isFile) {
|
|
||||||
domFile.file(f => postFile(f, domFile.fullPath))
|
|
||||||
} else {
|
|
||||||
mkdirCall(domFile.fullPath, () => parseDomFolder(domFile))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function pushEntry (entry) {
|
|
||||||
if (!entry.webkitGetAsEntry && !entry.getAsEntry) {
|
|
||||||
return window.alert('Unsupported browser ! Please update to chrome/firefox.')
|
|
||||||
} else {
|
|
||||||
entry = entry.webkitGetAsEntry() || entry.getAsEntry()
|
|
||||||
}
|
|
||||||
|
|
||||||
parseDomItem(entry, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
const upGrid = document.getElementById('drop-grid')
|
|
||||||
|
|
||||||
document.ondragenter = (e) => {
|
|
||||||
if (isPicMode()) { return }
|
|
||||||
cancelDefault(e)
|
|
||||||
e.dataTransfer.dropEffect = 'copy'
|
|
||||||
upGrid.style.display = 'flex'
|
|
||||||
}
|
|
||||||
|
|
||||||
upGrid.ondragleave = (e) => {
|
|
||||||
cancelDefault(e)
|
|
||||||
upGrid.style.display = 'none'
|
|
||||||
}
|
|
||||||
|
|
||||||
document.ondragover = (e) => {
|
|
||||||
cancelDefault(e)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
document.ondrop = (e) => {
|
|
||||||
cancelDefault(e)
|
|
||||||
upGrid.style.display = 'none'
|
|
||||||
|
|
||||||
Array.from(e.dataTransfer.items).forEach(pushEntry)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
let totalUploads = 0
|
|
||||||
let totalDone = 0
|
|
||||||
|
|
||||||
const getArrowSelected = () => document.querySelectorAll('i.arrow-selected')[0]
|
|
||||||
|
|
||||||
function getASelected () {
|
|
||||||
const dest = getArrowSelected()
|
|
||||||
return !dest ? false : dest.parentElement.parentElement.querySelectorAll('a')[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
function scrollToArrow () {
|
|
||||||
const pos = getArrowSelected().getBoundingClientRect()
|
|
||||||
window.scrollTo(0, pos.y)
|
|
||||||
}
|
|
||||||
|
|
||||||
function clearArrowSelected () {
|
|
||||||
const arr = getArrowSelected()
|
|
||||||
if (!arr) { return }
|
|
||||||
arr.classList.remove('arrow-selected')
|
|
||||||
}
|
|
||||||
|
|
||||||
function restoreCursorPos () {
|
|
||||||
clearArrowSelected()
|
|
||||||
const hrefSelected = window.localStorage.getItem('last-selected' + location.href)
|
|
||||||
let a = allA.find(el => el.href === hrefSelected)
|
|
||||||
|
|
||||||
if (!a) {
|
|
||||||
if (allA[0].innerText === '../') {
|
|
||||||
a = allA[1]
|
|
||||||
} else {
|
|
||||||
a = allA[0]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const icon = a.parentElement.parentElement.querySelectorAll('.arrow-icon')[0]
|
|
||||||
icon.classList.add('arrow-selected')
|
|
||||||
scrollToArrow()
|
|
||||||
}
|
|
||||||
|
|
||||||
const storeLastArrowSrc = src => window.localStorage.setItem('last-selected' + location.href, src)
|
|
||||||
|
|
||||||
function moveArrow (down) {
|
|
||||||
const all = Array.from(document.querySelectorAll('i.arrow-icon'))
|
|
||||||
let i = all.findIndex(el => el.classList.contains('arrow-selected'))
|
|
||||||
|
|
||||||
clearArrowSelected()
|
|
||||||
|
|
||||||
if (down) {
|
|
||||||
i = all[i + 1] ? i + 1 : 0
|
|
||||||
} else {
|
|
||||||
i = all[i - 1] ? i - 1 : all.length - 1
|
|
||||||
}
|
|
||||||
|
|
||||||
all[i].classList.add('arrow-selected')
|
|
||||||
storeLastArrowSrc(getASelected().href)
|
|
||||||
|
|
||||||
const itemPos = all[i].getBoundingClientRect()
|
|
||||||
|
|
||||||
if (i === 0) {
|
|
||||||
window.scrollTo(0, 0)
|
|
||||||
} else if (i === all.length - 1) {
|
|
||||||
window.scrollTo(0, document.documentElement.scrollHeight)
|
|
||||||
} else if (itemPos.top < 0) {
|
|
||||||
window.scrollBy(0, -200)
|
|
||||||
} else if (itemPos.bottom > window.innerHeight) {
|
|
||||||
window.scrollBy(0, 200)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function setCursorToClosest () {
|
|
||||||
const a = allA.find(el => el.innerText.toLocaleLowerCase().startsWith(path))
|
|
||||||
if (!a) { return }
|
|
||||||
storeLastArrowSrc(a.href)
|
|
||||||
restoreCursorPos()
|
|
||||||
}
|
|
||||||
|
|
||||||
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 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
|
|
||||||
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 isPic = src => src && picTypes.find(type => src.toLocaleLowerCase().includes(type))
|
|
||||||
|
|
||||||
const isPicMode = () => pics.style.display === 'flex'
|
|
||||||
|
|
||||||
let imgsIndex
|
|
||||||
let allImgs
|
|
||||||
|
|
||||||
function setImage (src) {
|
|
||||||
src = src || allImgs[imgsIndex]
|
|
||||||
picsLabel.innerText = src.split('/').pop()
|
|
||||||
picsHolder.src = src
|
|
||||||
storeLastArrowSrc(src)
|
|
||||||
}
|
|
||||||
|
|
||||||
function picsOn (ifImgSelected) {
|
|
||||||
const href = getASelected().href
|
|
||||||
|
|
||||||
if (isPicMode()) {
|
|
||||||
return false
|
|
||||||
} else if (ifImgSelected && !isPic(href)) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isPic(href)) {
|
|
||||||
imgsIndex = allImgs.findIndex(el => el.includes(href))
|
|
||||||
setImage()
|
|
||||||
} else {
|
|
||||||
setImage(picsHolder.src)
|
|
||||||
}
|
|
||||||
|
|
||||||
pics.style.display = 'flex'
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
function picsToggle () {
|
|
||||||
if (!isPicMode()) {
|
|
||||||
picsOn()
|
|
||||||
} else {
|
|
||||||
pics.style.display = 'none'
|
|
||||||
restoreCursorPos()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function picsNav (down) {
|
|
||||||
if (!isPicMode()) { return false }
|
|
||||||
|
|
||||||
if (down) {
|
|
||||||
imgsIndex = allImgs[imgsIndex + 1] ? imgsIndex + 1 : 0
|
|
||||||
} else {
|
|
||||||
imgsIndex = allImgs[imgsIndex - 1] ? imgsIndex - 1 : allImgs.length - 1
|
|
||||||
}
|
|
||||||
|
|
||||||
setImage()
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
let path = ''
|
|
||||||
let clearPathToken = null
|
|
||||||
|
|
||||||
// Kb handler
|
|
||||||
document.body.addEventListener('keydown', e => {
|
|
||||||
switch (e.code) {
|
|
||||||
case 'Tab':
|
|
||||||
case 'ArrowDown':
|
|
||||||
e.preventDefault()
|
|
||||||
return picsNav(true) || moveArrow(true)
|
|
||||||
|
|
||||||
case 'ArrowUp':
|
|
||||||
e.preventDefault()
|
|
||||||
return picsNav(false) || moveArrow(false)
|
|
||||||
|
|
||||||
case 'ArrowRight':
|
|
||||||
e.preventDefault()
|
|
||||||
return picsOn(true) || picsNav(true) || nextPage()
|
|
||||||
|
|
||||||
case 'ArrowLeft':
|
|
||||||
e.preventDefault()
|
|
||||||
return picsNav(false) || prevPage()
|
|
||||||
|
|
||||||
case 'Escape':
|
|
||||||
if (isPicMode()) {
|
|
||||||
e.preventDefault()
|
|
||||||
return picsToggle()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ctrl keys
|
|
||||||
if (e.ctrlKey || e.metaKey) {
|
|
||||||
switch (e.code) {
|
|
||||||
case 'KeyD':
|
|
||||||
e.preventDefault()
|
|
||||||
return isPicMode() || mkdir()
|
|
||||||
|
|
||||||
case 'KeyC':
|
|
||||||
e.preventDefault()
|
|
||||||
return isPicMode() || cpPath()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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()
|
|
||||||
}
|
|
||||||
}, 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)
|
|
||||||
}
|
|
||||||
|
|
||||||
function init () {
|
|
||||||
allA = Array.from(document.querySelectorAll('a'))
|
|
||||||
allImgs = allA.map(el => el.href).filter(isPic)
|
|
||||||
document.getElementById('picsToggle').style.display = allImgs.length > 0 ? 'flex' : 'none'
|
|
||||||
|
|
||||||
imgsIndex = 0
|
|
||||||
partialBrowseOnClickFolders()
|
|
||||||
restoreCursorPos()
|
|
||||||
console.log('Browsed to ' + location.href)
|
|
||||||
}
|
|
||||||
|
|
||||||
init()
|
|
||||||
|
|
||||||
window.picsToggle = picsToggle
|
|
||||||
window.picsNav = () => picsNav(true)
|
|
||||||
window.mkdir = mkdir
|
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 470 B |
+44
-58
@@ -17,32 +17,33 @@ import (
|
|||||||
"strings"
|
"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 host = flag.String("h", "127.0.0.1", "host to listen to")
|
||||||
var port = flag.String("p", "8001", "port to listen to")
|
var port = flag.String("p", "8001", "port to listen to")
|
||||||
var verb = flag.Bool("verb", true, "verbosity")
|
var verb = flag.Bool("verb", true, "verbosity")
|
||||||
var skipHidden = flag.Bool("k", true, "skip hidden files")
|
var skipHidden = flag.Bool("k", true, "skip hidden files")
|
||||||
|
|
||||||
var initPath = ""
|
var initPath = ""
|
||||||
var css = `some_css`
|
var css = `css_will_be_here` // js will be embedded here
|
||||||
var jsTag = `some_js`
|
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 units = [8]string{"k", "M", "G", "T", "P", "E", "Z", "Y"}
|
||||||
|
|
||||||
|
var fs http.Handler
|
||||||
|
|
||||||
type rpcCall struct {
|
type rpcCall struct {
|
||||||
Call string `json:"call"`
|
Call string `json:"call"`
|
||||||
Args []string `json:"args"`
|
Args []string `json:"args"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func check(e error) {
|
||||||
|
if e != nil {
|
||||||
|
panic(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func logVerb(s ...interface{}) {
|
func logVerb(s ...interface{}) {
|
||||||
if *verb {
|
if *verb {
|
||||||
log.Println(s)
|
log.Println(s...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55,11 +56,9 @@ func sizeToString(bytes float64) string {
|
|||||||
bytes = bytes / 1024
|
bytes = bytes / 1024
|
||||||
u++
|
u++
|
||||||
if bytes < 1024 {
|
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 {
|
func row(name string, href string, size float64, ext string) string {
|
||||||
@@ -68,10 +67,10 @@ 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 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>
|
||||||
</tr>`
|
</tr>`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -84,17 +83,17 @@ func replyList(w http.ResponseWriter, path string) {
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width">
|
<meta name="viewport" content="width=device-width">
|
||||||
<title>` + html.EscapeString(path) + `</title>
|
<title>` + html.EscapeString(path) + `</title>
|
||||||
<script>window.onload = function(){` + jsTag + `}</script>
|
<link href="` + favicon + `" rel="icon" type="image/png"/>
|
||||||
|
<script>window.onload = function(){` + js + `}</script>
|
||||||
<style type="text/css">` + css + `</style>
|
<style type="text/css">` + css + `</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div onclick="window.mkdir()" id="newFolder"></div>
|
|
||||||
<div onclick="window.picsToggle()" id="picsToggle"></div>
|
|
||||||
<div id="pics" style="display:none;"> <div onclick="window.picsToggle()" id="picsToggleCinema"></div> <img onclick="window.picsNav()" id="picsHolder"/> <span id="picsLabel"></span> </div>
|
|
||||||
<div id="drop-grid"> Drop here to upload </div>
|
<div id="drop-grid"> Drop here to upload </div>
|
||||||
<div id="progressBars"></div>
|
|
||||||
<h1>.` + html.EscapeString(path) + `</h1>
|
<h1>.` + html.EscapeString(path) + `</h1>
|
||||||
|
<div class="icHolder"><div style="display:none;" class="ic icon-large-images" onclick="window.picsToggle()"></div>
|
||||||
|
<div class="ic icon-large-folder" onclick="window.mkdirBtn()"></div></div>
|
||||||
|
<div id="pics" style="display:none;"> <div onclick="window.picsToggle()" id="picsToggleCinema"></div> <img onclick="window.picsNav()" id="picsHolder"/> <span id="picsLabel"></span> </div>
|
||||||
<table>`
|
<table>`
|
||||||
|
|
||||||
_files, err := ioutil.ReadDir(initPath + path)
|
_files, err := ioutil.ReadDir(initPath + path)
|
||||||
@@ -122,21 +121,14 @@ func replyList(w http.ResponseWriter, path string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var resp = head + dirs + files + `</table>
|
w.Write([]byte(head + dirs + files + `</table>
|
||||||
<br><address><a href="https://github.com/pldubouilh/gossa">Gossa 🎶</a></address>
|
<br><address><a href="https://github.com/pldubouilh/gossa">Gossa 🎶</a></address>
|
||||||
</body></html>`
|
<div id="progress" style="display:none;"><span id="dlBarName"></span><div id="dlBarPc">1%</div></div>
|
||||||
|
</body></html>`))
|
||||||
w.Write([]byte(resp))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func doContent(w http.ResponseWriter, r *http.Request) {
|
func doContent(w http.ResponseWriter, r *http.Request) {
|
||||||
path := html.UnescapeString(r.URL.Path)
|
path := html.UnescapeString(r.URL.Path)
|
||||||
|
|
||||||
if strings.Contains(path, "/favicon.ico") {
|
|
||||||
w.Write([]byte(" "))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
fullPath, errPath := checkPath(path)
|
fullPath, errPath := checkPath(path)
|
||||||
stat, errStat := os.Stat(fullPath)
|
stat, errStat := os.Stat(fullPath)
|
||||||
|
|
||||||
@@ -174,22 +166,30 @@ func upload(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func rpc(w http.ResponseWriter, r *http.Request) {
|
func rpc(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var err error
|
||||||
bodyBytes, _ := ioutil.ReadAll(r.Body)
|
bodyBytes, _ := ioutil.ReadAll(r.Body)
|
||||||
bodyString := string(bodyBytes)
|
bodyString := string(bodyBytes)
|
||||||
var payload rpcCall
|
var payload rpcCall
|
||||||
json.Unmarshal([]byte(bodyString), &payload)
|
json.Unmarshal([]byte(bodyString), &payload)
|
||||||
|
|
||||||
unparsed, _ := url.PathUnescape(payload.Args[0])
|
for i := range payload.Args {
|
||||||
p, err := checkPath(unparsed)
|
payload.Args[i], err = checkPath(payload.Args[i])
|
||||||
logVerb("RPC", err, unparsed)
|
if err != nil {
|
||||||
|
logVerb("Cant read path", err, payload)
|
||||||
if err != nil {
|
w.Write([]byte("error"))
|
||||||
w.Write([]byte("error"))
|
return
|
||||||
return
|
}
|
||||||
} else if payload.Call == "mkdirp" {
|
|
||||||
os.MkdirAll(p, os.ModePerm)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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])
|
||||||
|
} else if payload.Call == "rm" {
|
||||||
|
err = os.Remove(payload.Args[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
logVerb("RPC", err, payload)
|
||||||
w.Write([]byte("ok"))
|
w.Write([]byte("ok"))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -198,7 +198,7 @@ func checkPath(p string) (string, error) {
|
|||||||
fp, err := filepath.Abs(p)
|
fp, err := filepath.Abs(p)
|
||||||
|
|
||||||
if err != nil || !strings.HasPrefix(fp, initPath) {
|
if err != nil || !strings.HasPrefix(fp, initPath) {
|
||||||
return fp, errors.New("error")
|
return "", errors.New("error")
|
||||||
}
|
}
|
||||||
|
|
||||||
return fp, nil
|
return fp, nil
|
||||||
@@ -217,20 +217,6 @@ func main() {
|
|||||||
initPath, err = filepath.Abs(initPath)
|
initPath, err = filepath.Abs(initPath)
|
||||||
check(err)
|
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
|
var hostString = *host + ":" + *port
|
||||||
fmt.Println("Gossa startig on directory " + initPath)
|
fmt.Println("Gossa startig on directory " + initPath)
|
||||||
fmt.Println("Listening on http://" + hostString)
|
fmt.Println("Listening on http://" + hostString)
|
||||||
@@ -65,19 +65,19 @@ func testDefaults(t *testing.T, url string) string {
|
|||||||
t.Fatal("error header")
|
t.Fatal("error header")
|
||||||
}
|
}
|
||||||
|
|
||||||
if !strings.Contains(bodyStr, `<a href="hols">hols/</a>`) {
|
if !strings.Contains(bodyStr, `href="hols">hols/</a>`) {
|
||||||
t.Fatal("error hols folder")
|
t.Fatal("error hols folder")
|
||||||
}
|
}
|
||||||
|
|
||||||
if !strings.Contains(bodyStr, `<a href="curimit@gmail.com%20%2840%25%29">curimit@gmail.com (40%)/</a>`) {
|
if !strings.Contains(bodyStr, `href="curimit@gmail.com%20%2840%25%29">curimit@gmail.com (40%)/</a>`) {
|
||||||
t.Fatal("error curimit@gmail.com (40%) folder")
|
t.Fatal("error curimit@gmail.com (40%) folder")
|
||||||
}
|
}
|
||||||
|
|
||||||
if !strings.Contains(bodyStr, `<a href="%E4%B8%AD%E6%96%87">中文/</a>`) {
|
if !strings.Contains(bodyStr, `href="%E4%B8%AD%E6%96%87">中文/</a>`) {
|
||||||
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 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")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -110,28 +110,40 @@ func TestGetFolder(t *testing.T) {
|
|||||||
|
|
||||||
// ~~~~~~~~~~~~~~~~~
|
// ~~~~~~~~~~~~~~~~~
|
||||||
fmt.Println("\r\n~~~~~~~~~~ test mkdir rpc")
|
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`) {
|
if !strings.Contains(bodyStr, `ok`) {
|
||||||
t.Fatal("error returned value")
|
t.Fatal("error returned value")
|
||||||
}
|
}
|
||||||
|
|
||||||
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 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")
|
||||||
}
|
}
|
||||||
|
|
||||||
// ~~~~~~~~~~~~~~~~~
|
// ~~~~~~~~~~~~~~~~~
|
||||||
fmt.Println("\r\n~~~~~~~~~~ test invalid mkdir rpc")
|
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`) {
|
if !strings.Contains(bodyStr, `error`) {
|
||||||
t.Fatal("error not returned")
|
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`) {
|
if !strings.Contains(bodyStr, `error`) {
|
||||||
t.Fatal("error not returned")
|
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, `<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")
|
||||||
|
}
|
||||||
|
|
||||||
// ~~~~~~~~~~~~~~~~~
|
// ~~~~~~~~~~~~~~~~~
|
||||||
fmt.Println("\r\n~~~~~~~~~~ test post file")
|
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")
|
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/")
|
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 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")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -155,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")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Executable
+450
@@ -0,0 +1,450 @@
|
|||||||
|
/* eslint-env browser */
|
||||||
|
/* eslint-disable no-multi-str */
|
||||||
|
|
||||||
|
function cancelDefault (e) {
|
||||||
|
e.preventDefault()
|
||||||
|
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
|
||||||
|
function rpcFs (call, args, cb) {
|
||||||
|
console.log('RPC', call, args)
|
||||||
|
const xhr = new XMLHttpRequest()
|
||||||
|
xhr.open('POST', location.origin + '/rpc')
|
||||||
|
xhr.setRequestHeader('Content-Type', 'application/json;charset=UTF-8')
|
||||||
|
xhr.send(JSON.stringify({ call, args }))
|
||||||
|
xhr.onload = 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)
|
||||||
|
|
||||||
|
// File upload
|
||||||
|
let totalDone = 0
|
||||||
|
let totalUploads = 0
|
||||||
|
let totalUploadsSize = 0
|
||||||
|
let totalUploadedSize = []
|
||||||
|
|
||||||
|
const dupe = test => allA.find(a => a.innerText.replace('/', '') === test)
|
||||||
|
const isDupe = t => dupe(t) ? alert(t + ' already already exists') || true : false
|
||||||
|
|
||||||
|
function shouldRefresh () {
|
||||||
|
totalDone += 1
|
||||||
|
if (totalUploads === totalDone) {
|
||||||
|
window.onbeforeunload = null
|
||||||
|
console.log('Done uploading ' + totalDone + ' files')
|
||||||
|
totalDone = 0
|
||||||
|
totalUploads = 0
|
||||||
|
totalUploadsSize = 0
|
||||||
|
totalUploadedSize = []
|
||||||
|
barDiv.style.display = 'none'
|
||||||
|
refresh()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
path = decodeURI(location.pathname).slice(0, -1) + path
|
||||||
|
window.onbeforeunload = warningMsg
|
||||||
|
|
||||||
|
barDiv.style.display = 'block'
|
||||||
|
totalUploads += 1
|
||||||
|
totalUploadsSize += file.size
|
||||||
|
barName.innerText = totalUploads > 1 ? totalUploads + ' files' : file.name
|
||||||
|
|
||||||
|
const formData = new FormData()
|
||||||
|
formData.append(file.name, file)
|
||||||
|
|
||||||
|
const xhr = new 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
const parseDomFolder = f => f.createReader().readEntries(e => e.forEach(i => parseDomItem(i)))
|
||||||
|
|
||||||
|
function parseDomItem (domFile, shoudCheckDupes) {
|
||||||
|
if (shoudCheckDupes && isDupe(domFile.name)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (domFile.isFile) {
|
||||||
|
domFile.file(f => postFile(f, domFile.fullPath))
|
||||||
|
} else {
|
||||||
|
// remove absolute path
|
||||||
|
const f = domFile.fullPath.startsWith('/') ? domFile.fullPath.slice(1) : domFile.fullPath
|
||||||
|
mkdirCall(f, () => parseDomFolder(domFile))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function pushEntry (entry) {
|
||||||
|
if (!entry.webkitGetAsEntry && !entry.getAsEntry) {
|
||||||
|
return alert('Unsupported browser ! Please update to chrome/firefox.')
|
||||||
|
} else {
|
||||||
|
entry = entry.webkitGetAsEntry() || entry.getAsEntry()
|
||||||
|
}
|
||||||
|
|
||||||
|
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]
|
||||||
|
|
||||||
|
document.ondragenter = e => {
|
||||||
|
if (isPicMode()) { return }
|
||||||
|
cancelDefault(e)
|
||||||
|
|
||||||
|
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 => {
|
||||||
|
cancelDefault(e)
|
||||||
|
upGrid.style.display = 'none'
|
||||||
|
}
|
||||||
|
|
||||||
|
document.ondragover = e => {
|
||||||
|
cancelDefault(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 = decodeURIComponent(s.replace(location.href, ''))
|
||||||
|
const dest = t.innerText + root
|
||||||
|
mvCall(prependPath(root), prependPath(dest), refresh)
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Array.from(e.dataTransfer.items).forEach(pushEntry)
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mkdir icon
|
||||||
|
window.mkdirBtn = function () {
|
||||||
|
const folder = prompt('new folder name', '')
|
||||||
|
if (folder && !isDupe(folder)) {
|
||||||
|
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 () {
|
||||||
|
const pos = getArrowSelected().getBoundingClientRect()
|
||||||
|
if (pos.top < 0 || pos.bottom > window.innerHeight) {
|
||||||
|
setTimeout(scrollTo, 50, 0, pos.y)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearArrowSelected () {
|
||||||
|
const arr = getArrowSelected()
|
||||||
|
if (!arr) { return }
|
||||||
|
arr.classList.remove('arrow-selected')
|
||||||
|
}
|
||||||
|
|
||||||
|
function restoreCursorPos () {
|
||||||
|
clearArrowSelected()
|
||||||
|
const hrefSelected = localStorage.getItem('last-selected' + location.href)
|
||||||
|
let a = allA.find(el => el.href === hrefSelected)
|
||||||
|
|
||||||
|
if (!a) {
|
||||||
|
if (allA[0].innerText === '../') {
|
||||||
|
a = allA[1] || allA[0]
|
||||||
|
} else {
|
||||||
|
a = allA[0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const icon = a.parentElement.parentElement.querySelectorAll('.arrow-icon')[0]
|
||||||
|
icon.classList.add('arrow-selected')
|
||||||
|
scrollToArrow()
|
||||||
|
}
|
||||||
|
|
||||||
|
function moveArrow (down) {
|
||||||
|
const all = Array.from(document.querySelectorAll('i.arrow-icon'))
|
||||||
|
let i = all.findIndex(el => el.classList.contains('arrow-selected'))
|
||||||
|
|
||||||
|
clearArrowSelected()
|
||||||
|
|
||||||
|
if (down) {
|
||||||
|
i = all[i + 1] ? i + 1 : 0
|
||||||
|
} else {
|
||||||
|
i = all[i - 1] ? i - 1 : all.length - 1
|
||||||
|
}
|
||||||
|
|
||||||
|
all[i].classList.add('arrow-selected')
|
||||||
|
storeLastArrowSrc(getASelected().href)
|
||||||
|
|
||||||
|
const itemPos = all[i].getBoundingClientRect()
|
||||||
|
|
||||||
|
if (i === 0) {
|
||||||
|
scrollTo(0, 0)
|
||||||
|
} else if (i === all.length - 1) {
|
||||||
|
scrollTo(0, document.documentElement.scrollHeight)
|
||||||
|
} else if (itemPos.top < 0) {
|
||||||
|
scrollBy(0, -200)
|
||||||
|
} else if (itemPos.bottom > window.innerHeight) {
|
||||||
|
scrollBy(0, 200)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pictures carousel
|
||||||
|
const picTypes = ['.jpg', '.jpeg', '.png', '.gif']
|
||||||
|
const isPic = src => src && picTypes.find(type => src.toLocaleLowerCase().includes(type))
|
||||||
|
const isPicMode = () => pics.style.display === 'flex'
|
||||||
|
window.picsNav = () => picsNav(true)
|
||||||
|
|
||||||
|
function setImage () {
|
||||||
|
const src = allImgs[imgsIndex]
|
||||||
|
picsHolder.src = src
|
||||||
|
picsLabel.innerText = src.split('/').pop()
|
||||||
|
storeLastArrowSrc(src)
|
||||||
|
restoreCursorPos()
|
||||||
|
}
|
||||||
|
|
||||||
|
function picsOn (ifImgSelected, href) {
|
||||||
|
href = href || getASelected().href
|
||||||
|
|
||||||
|
if (isPicMode() || (ifImgSelected && !isPic(href))) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isPic(href)) {
|
||||||
|
imgsIndex = allImgs.findIndex(el => el.includes(href))
|
||||||
|
}
|
||||||
|
|
||||||
|
setImage()
|
||||||
|
pics.style.display = 'flex'
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
const picsOff = () => { pics.style.display = 'none' }
|
||||||
|
|
||||||
|
window.picsToggle = () => isPicMode() ? picsOff() : picsOn()
|
||||||
|
|
||||||
|
function picsNav (down) {
|
||||||
|
if (!isPicMode()) { return false }
|
||||||
|
|
||||||
|
if (down) {
|
||||||
|
imgsIndex = allImgs[imgsIndex + 1] ? imgsIndex + 1 : 0
|
||||||
|
} else {
|
||||||
|
imgsIndex = allImgs[imgsIndex - 1] ? imgsIndex - 1 : allImgs.length - 1
|
||||||
|
}
|
||||||
|
|
||||||
|
setImage()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Paste handler
|
||||||
|
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
|
||||||
|
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 => {
|
||||||
|
switch (e.code) {
|
||||||
|
case 'Tab':
|
||||||
|
case 'ArrowDown':
|
||||||
|
return prevent(e) || picsNav(true) || moveArrow(true)
|
||||||
|
|
||||||
|
case 'ArrowUp':
|
||||||
|
return prevent(e) || picsNav(false) || moveArrow(false)
|
||||||
|
|
||||||
|
case 'Enter':
|
||||||
|
case 'ArrowRight':
|
||||||
|
return prevent(e) || picsOn(true) || picsNav(true) || getASelected().click()
|
||||||
|
|
||||||
|
case 'ArrowLeft':
|
||||||
|
return prevent(e) || picsNav(false) || prevPage()
|
||||||
|
|
||||||
|
case 'Escape':
|
||||||
|
return prevent(e) || picsOff()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ctrl keys
|
||||||
|
if (e.ctrlKey || e.metaKey) {
|
||||||
|
switch (e.code) {
|
||||||
|
case 'KeyC':
|
||||||
|
return prevent(e) || isPicMode() || cpPath()
|
||||||
|
|
||||||
|
case 'KeyX':
|
||||||
|
cuts.push(prependPath(decode(getASelected().href)))
|
||||||
|
return prevent(e) || false
|
||||||
|
|
||||||
|
case 'KeyV':
|
||||||
|
return prevent(e) || 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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// text search
|
||||||
|
if (e.code.includes('Key')) {
|
||||||
|
typedPath += e.code.replace('Key', '').toLocaleLowerCase()
|
||||||
|
clearTimeout(typedToken)
|
||||||
|
typedToken = setTimeout(() => { typedPath = '' }, 1000)
|
||||||
|
setCursorToClosestTyped()
|
||||||
|
}
|
||||||
|
}, false)
|
||||||
|
|
||||||
|
function init () {
|
||||||
|
allA = Array.from(document.querySelectorAll('a.list-links'))
|
||||||
|
allImgs = allA.map(el => el.href).filter(isPic)
|
||||||
|
document.getElementsByClassName('icon-large-images')[0].style.display = allImgs.length > 0 ? 'inline-block' : 'none'
|
||||||
|
|
||||||
|
imgsIndex = 0
|
||||||
|
restoreCursorPos()
|
||||||
|
console.log('Browsed to ' + location.href)
|
||||||
|
}
|
||||||
|
init()
|
||||||
+29
-21
@@ -4,6 +4,7 @@
|
|||||||
width: 16px;
|
width: 16px;
|
||||||
zoom: 1.2;
|
zoom: 1.2;
|
||||||
margin: 1px;
|
margin: 1px;
|
||||||
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.arrow-icon {
|
.arrow-icon {
|
||||||
@@ -31,27 +32,30 @@ td.file-size {
|
|||||||
|
|
||||||
td.display-name {
|
td.display-name {
|
||||||
padding-left: 0.2em;
|
padding-left: 0.2em;
|
||||||
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
#newFolder {
|
h1 {
|
||||||
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=");
|
display: inline-block;
|
||||||
position: fixed;
|
margin-top: 10px;
|
||||||
right: 20px;
|
}
|
||||||
top: 20px;
|
|
||||||
|
.icHolder {
|
||||||
|
display: inline-block;
|
||||||
|
right: 15px;
|
||||||
|
top: 25px;
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ic {
|
||||||
width: 32px;
|
width: 32px;
|
||||||
height: 32px;
|
height: 32px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
display: inline-block;
|
||||||
|
overflow: hidden;
|
||||||
|
color: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
#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;
|
|
||||||
width: 32px;
|
|
||||||
height: 32px;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
#picsToggleCinema {
|
#picsToggleCinema {
|
||||||
background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAACXBIWXMAAAsTAAALEwEAmpwYAAAAoElEQVRYhe2Wuw3AIAxEbZRRMwFlJiBLMCBDcClCCwq2gRRc7/cO8RFEOzt/DAAPIABwBixXWL5nKOBN1JQo8lhYQTooKqFmaAAWCxCDzOQSoLm8BzxM/kUwXN4STZM3SsyTV0qo5HMaW2bpFiw9hEuv4dKHqAdsXkICNCuhAalLWKxCxcDEL9lRmU1EdBPRxcxZWoCZM4Cz8JKUs7MzNA81r4TL7qAyXAAAAABJRU5ErkJggg==");
|
background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAACXBIWXMAAAsTAAALEwEAmpwYAAAAoElEQVRYhe2Wuw3AIAxEbZRRMwFlJiBLMCBDcClCCwq2gRRc7/cO8RFEOzt/DAAPIABwBixXWL5nKOBN1JQo8lhYQTooKqFmaAAWCxCDzOQSoLm8BzxM/kUwXN4STZM3SsyTV0qo5HMaW2bpFiw9hEuv4dKHqAdsXkICNCuhAalLWKxCxcDEL9lRmU1EdBPRxcxZWoCZM4Cz8JKUs7MzNA81r4TL7qAyXAAAAABJRU5ErkJggg==");
|
||||||
@@ -70,7 +74,7 @@ td.display-name {
|
|||||||
zoom: 1 !important;
|
zoom: 1 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
#progressBars {
|
#progress {
|
||||||
background-color: white;
|
background-color: white;
|
||||||
width: 99%;
|
width: 99%;
|
||||||
left: 0.5%;
|
left: 0.5%;
|
||||||
@@ -81,18 +85,14 @@ td.display-name {
|
|||||||
max-height: 50%;
|
max-height: 50%;
|
||||||
overflow-y: scroll;
|
overflow-y: scroll;
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
}
|
|
||||||
|
|
||||||
#progressBars::-webkit-scrollbar {
|
|
||||||
display: none;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.barBackground {
|
#dlBarName {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin-top: 15px;
|
margin-top: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.barForeground {
|
#dlBarPc {
|
||||||
width: 1%;
|
width: 1%;
|
||||||
height: 30px;
|
height: 30px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
@@ -156,6 +156,14 @@ td.display-name {
|
|||||||
image-orientation: from-image;
|
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 {
|
.icon-blank {
|
||||||
background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAWBJREFUeNqEUj1LxEAQnd1MVA4lyIEWx6UIKEGUExGsbC3tLfwJ/hT/g7VlCnubqxXBwg/Q4hQP/LhKL5nZuBsvuGfW5MGyuzM7jzdvVuR5DgYnZ+f99ai7Vt5t9K9unu4HLweI3qWYxI6PDosdy0fhcntxO44CcOBzPA7mfEyuHwf7ntQk4jcnywOxIlfxOCNYaLVgb6cXbkTdhJXq2SIlNMC0xIqhHczDbi8OVzpLSUa0WebRfmigLHqj1EcPZnwf7gbDIrYVRyEinurj6jTBHyI7pqVrFQqEbt6TEmZ9v1NRAJNC1xTYxIQh/MmRUlmFQE3qWOW1nqB2TWk1/3tgJV0waVvkFIEeZbHq4ElyKzAmEXOx6gnEVJuWBzmkRJBRPYGZBDsVaOlpSgVJE2yVaAe/0kx/3azBRO0VsbMFZE3CDSZKweZfYIVg+DZ6v7h9GDVOwZPw/PoxKu/fAgwALbDAXf7DdQkAAAAASUVORK5CYII=");
|
background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAWBJREFUeNqEUj1LxEAQnd1MVA4lyIEWx6UIKEGUExGsbC3tLfwJ/hT/g7VlCnubqxXBwg/Q4hQP/LhKL5nZuBsvuGfW5MGyuzM7jzdvVuR5DgYnZ+f99ai7Vt5t9K9unu4HLweI3qWYxI6PDosdy0fhcntxO44CcOBzPA7mfEyuHwf7ntQk4jcnywOxIlfxOCNYaLVgb6cXbkTdhJXq2SIlNMC0xIqhHczDbi8OVzpLSUa0WebRfmigLHqj1EcPZnwf7gbDIrYVRyEinurj6jTBHyI7pqVrFQqEbt6TEmZ9v1NRAJNC1xTYxIQh/MmRUlmFQE3qWOW1nqB2TWk1/3tgJV0waVvkFIEeZbHq4ElyKzAmEXOx6gnEVJuWBzmkRJBRPYGZBDsVaOlpSgVJE2yVaAe/0kx/3azBRO0VsbMFZE3CDSZKweZfYIVg+DZ6v7h9GDVOwZPw/PoxKu/fAgwALbDAXf7DdQkAAAAASUVORK5CYII=");
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user