Compare commits

..
Author SHA1 Message Date
Pierre Dubouilh 18d44184c5 readme + ci 2023-07-08 15:43:08 +02:00
17 changed files with 177 additions and 292 deletions
+10 -5
View File
@@ -6,15 +6,20 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v5
uses: actions/setup-go@v1
with:
go-version: 1.23.0
go-version: 1.19.1
id: go
- name: deps
run: npm i -g standard
- name: Check out code into the Go module directory
uses: actions/checkout@v1
with:
submodules: true
- name: Run
run: make ci
+17 -11
View File
@@ -10,25 +10,33 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v1
with:
submodules: true
- name: Set up Go
uses: actions/setup-go@v1
with:
go-version: 1.19.1
id: go
- name: Set env
run: echo "GIT_TAG=`echo $(git describe --tags --abbrev=0)`" >> $GITHUB_ENV
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
uses: docker/setup-qemu-action@v1.2.0
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1.6.0
- name: Login to DockerHub
uses: docker/login-action@v3
uses: docker/login-action@v1.10.0
with:
username: ${{ secrets.DOCKER_USER }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Build and release on dockerhub
uses: docker/build-push-action@v6
uses: docker/build-push-action@v2.7.0
with:
file: support/build.Dockerfile
push: true
@@ -39,19 +47,17 @@ jobs:
run: make build-all
- name: "Release gh release versioned"
uses: ncipollo/release-action@v1
uses: ncipollo/release-action@58ae73b360456532aafd58ee170c045abbeaee37
with:
allowUpdates: true
artifacts: "builds/*"
bodyFile: "builds/buildout"
token: ${{ secrets.GITHUB_TOKEN }}
- name: "Release gh release latest"
uses: ncipollo/release-action@v1
uses: ncipollo/release-action@58ae73b360456532aafd58ee170c045abbeaee37
with:
tag: latest
name: Latest
allowUpdates: true
artifacts: "builds/*"
bodyFile: "builds/buildout"
token: ${{ secrets.GITHUB_TOKEN }}
+39
View File
@@ -0,0 +1,39 @@
name: rc
on:
push:
branches:
- 'rc/**'
jobs:
build:
name: Build
runs-on: ubuntu-latest
steps:
- name: Set up Go
uses: actions/setup-go@v1
with:
go-version: 1.19.1
id: go
- name: Check out code into the Go module directory
uses: actions/checkout@v1
with:
submodules: true
- name: Run
run: make ci
- name: Build all artifacts
run: make build-all
- name: "Release gh release prerelease"
uses: ncipollo/release-action@58ae73b360456532aafd58ee170c045abbeaee37
with:
allowUpdates: true
prerelease: true
tag: "rc"
artifacts: "builds/*"
token: ${{ secrets.GITHUB_TOKEN }}
Regular → Executable
+12 -10
View File
@@ -1,4 +1,4 @@
FLAGS := -trimpath
FLAGS := -ldflags "-s -w" -trimpath
NOCGO := CGO_ENABLED=0
build::
@@ -17,7 +17,10 @@ run-ro::
run-extra::
./gossa -verb=true -prefix="/fancy-path/" -k=false -symlinks=true test-fixture
ci:: build-all test
lint-js::
standard
ci:: build-all test lint-js
echo "done"
test::
@@ -56,14 +59,13 @@ watch-test::
ls gossa.go gossa_test.go ui/* | entr -rc make test
build-all:: build
go version
${NOCGO} GOOS=linux GOARCH=amd64 go build ${FLAGS} -o builds/gossa-linux-x64
${NOCGO} GOOS=linux GOARCH=arm go build ${FLAGS} -o builds/gossa-linux-arm
${NOCGO} GOOS=linux GOARCH=arm64 go build ${FLAGS} -o builds/gossa-linux-arm64
${NOCGO} GOOS=darwin GOARCH=amd64 go build ${FLAGS} -o builds/gossa-mac-x64
${NOCGO} GOOS=darwin GOARCH=arm64 go build ${FLAGS} -o builds/gossa-mac-arm64
${NOCGO} GOOS=windows GOARCH=amd64 go build ${FLAGS} -o builds/gossa-windows.exe
sha256sum builds/* | tee builds/buildout
${NOCGO} GOOS=linux GOARCH=amd64 go build ${FLAGS} -o builds/gossa-linux-x64
${NOCGO} GOOS=linux GOARCH=arm go build ${FLAGS} -o builds/gossa-linux-arm
${NOCGO} GOOS=linux GOARCH=arm64 go build ${FLAGS} -o builds/gossa-linux-arm64
${NOCGO} GOOS=darwin GOARCH=amd64 go build ${FLAGS} -o builds/gossa-mac-x64
${NOCGO} GOOS=darwin GOARCH=arm64 go build ${FLAGS} -o builds/gossa-mac-arm64
${NOCGO} GOOS=windows GOARCH=amd64 go build ${FLAGS} -o builds/gossa-windows.exe
sha256sum builds/*
clean::
rm -f gossa
+1 -1
View File
@@ -1,3 +1,3 @@
module github.com/pldubouilh/gossa
go 1.23.0
go 1.16
Regular → Executable
+38 -51
View File
@@ -12,6 +12,7 @@ import (
"html/template"
"io"
"io/fs"
"io/ioutil"
"log"
"net/http"
"net/url"
@@ -24,7 +25,7 @@ import (
type rowTemplate struct {
Name string
Href template.URL
Href template.HTML
Size string
Ext string
}
@@ -45,6 +46,11 @@ var verb = flag.Bool("verb", false, "verbosity")
var skipHidden = flag.Bool("k", true, "\nskip hidden files")
var ro = flag.Bool("ro", false, "read only mode (no upload, rename, move, etc...)")
type rpcCall struct {
Call string `json:"call"`
Args []string `json:"args"`
}
var rootPath = ""
var handler http.Handler
@@ -77,9 +83,9 @@ func humanize(bytes int64) string {
}
func replyList(w http.ResponseWriter, r *http.Request, fullPath string, path string) {
files, err := os.ReadDir(fullPath)
_files, err := ioutil.ReadDir(fullPath)
check(err)
sort.Slice(files, func(i, j int) bool { return strings.ToLower(files[i].Name()) < strings.ToLower(files[j].Name()) })
sort.Slice(_files, func(i, j int) bool { return strings.ToLower(_files[i].Name()) < strings.ToLower(_files[j].Name()) })
if !strings.HasSuffix(path, "/") {
path += "/"
@@ -94,18 +100,18 @@ func replyList(w http.ResponseWriter, r *http.Request, fullPath string, path str
p.Ro = *ro
p.Title = template.HTML(html.EscapeString(title))
for _, el := range files {
info, err := el.Info()
if err != nil {
log.Println("error - cant stat a file", err)
continue
}
for _, el := range _files {
if *skipHidden && strings.HasPrefix(el.Name(), ".") {
continue // dont print hidden files if we're not allowed
}
if *symlinks && info.Mode()&os.ModeSymlink != 0 {
continue // dont follow symlinks if we're not allowed
if !*symlinks && el.Mode()&os.ModeSymlink != 0 {
continue // dont print symlinks if were not allowed
}
el, err := os.Stat(fullPath + "/" + el.Name())
if err != nil {
log.Println("error - cant stat a file", err)
continue
}
href := url.PathEscape(el.Name())
@@ -116,25 +122,23 @@ func replyList(w http.ResponseWriter, r *http.Request, fullPath string, path str
}
if el.IsDir() {
row := rowTemplate{name + "/", template.URL(href), "", "folder"}
p.RowsFolders = append(p.RowsFolders, row)
p.RowsFolders = append(p.RowsFolders, rowTemplate{name + "/", template.HTML(href), "", "folder"})
} else {
sl := strings.Split(name, ".")
ext := strings.ToLower(sl[len(sl)-1])
row := rowTemplate{name, template.URL(href), humanize(info.Size()), ext}
p.RowsFiles = append(p.RowsFiles, row)
p.RowsFiles = append(p.RowsFiles, rowTemplate{name, template.HTML(href), humanize(el.Size()), ext})
}
}
if strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") {
w.Header().Set("Content-Type", "text/html")
w.Header().Add("Content-Encoding", "gzip")
gz, err := gzip.NewWriterLevel(w, gzip.BestSpeed) // BestSpeed is Much Faster than default - base on a very unscientific local test
gz, err := gzip.NewWriterLevel(w, gzip.BestSpeed) // BestSpeed is Much Faster than default - base on a very unscientific local test, and only ~30% larger (compression remains still very effective, ~6x)
check(err)
defer gz.Close()
tmpl.Execute(gz, p)
templateParsed.Execute(gz, p)
} else {
tmpl.Execute(w, p)
templateParsed.Execute(w, p)
}
}
@@ -146,8 +150,7 @@ func doContent(w http.ResponseWriter, r *http.Request) {
path := html.UnescapeString(r.URL.Path)
defer exitPath(w, "get content", path)
fullPath, err := enforcePath(path)
check(err)
fullPath := enforcePath(path)
stat, errStat := os.Stat(fullPath)
check(errStat)
@@ -170,9 +173,7 @@ func upload(w http.ResponseWriter, r *http.Request) {
if err != nil && err != io.EOF { // errs EOF when no more parts to process
check(err)
}
path, err = enforcePath(path)
check(err)
dst, err := os.Create(path)
dst, err := os.Create(enforcePath(path))
check(err)
io.Copy(dst, part)
w.Write([]byte("ok"))
@@ -182,9 +183,8 @@ func zipRPC(w http.ResponseWriter, r *http.Request) {
zipPath := r.URL.Query().Get("zipPath")
zipName := r.URL.Query().Get("zipName")
defer exitPath(w, "zip", zipPath)
zipFullPath, err := enforcePath(zipPath)
check(err)
_, err = os.Lstat(zipFullPath)
zipFullPath := enforcePath(zipPath)
_, err := os.Lstat(zipFullPath)
check(err)
w.Header().Add("Content-Disposition", "attachment; filename=\""+zipName+".zip\"")
zipWriter := zip.NewWriter(w)
@@ -202,7 +202,7 @@ func zipRPC(w http.ResponseWriter, r *http.Request) {
return nil // hidden files not allowed
}
if f.Mode()&os.ModeSymlink != 0 {
check(errors.New("symlink not allowed in zip downloads")) // filepath.Walk doesnt support symlinks
panic(errors.New("symlink not allowed in zip downloads")) // filepath.Walk doesnt support symlinks
}
header, err := zip.FileInfoHeader(f)
@@ -223,52 +223,39 @@ func zipRPC(w http.ResponseWriter, r *http.Request) {
}
func rpc(w http.ResponseWriter, r *http.Request) {
type rpcCall struct {
Call string `json:"call"`
Args []string `json:"args"`
}
var err error
var rpc rpcCall
defer exitPath(w, "rpc", rpc)
bodyBytes, err := io.ReadAll(r.Body)
bodyBytes, err := ioutil.ReadAll(r.Body)
check(err)
json.Unmarshal(bodyBytes, &rpc)
path0, err := enforcePath(rpc.Args[0])
path1 := ""
check(err)
if len(rpc.Args) > 1 {
path1, err = enforcePath(rpc.Args[1])
check(err)
}
if rpc.Call == "mkdirp" {
err = os.MkdirAll(path0, os.ModePerm)
} else if rpc.Call == "mv" && len(rpc.Args) == 2 {
err = os.Rename(path0, path1)
err = os.MkdirAll(enforcePath(rpc.Args[0]), os.ModePerm)
} else if rpc.Call == "mv" {
err = os.Rename(enforcePath(rpc.Args[0]), enforcePath(rpc.Args[1]))
} else if rpc.Call == "rm" {
err = os.RemoveAll(path0)
} else {
err = errors.New("invalid rpc call")
err = os.RemoveAll(enforcePath(rpc.Args[0]))
}
check(err)
w.Write([]byte("ok"))
}
func enforcePath(p string) (string, error) {
func enforcePath(p string) string {
joined := filepath.Join(rootPath, strings.TrimPrefix(p, *extraPath))
fp, err := filepath.Abs(joined)
sl, _ := filepath.EvalSymlinks(fp) // err skipped as it would error for inexistent files (RPC check). The actual behaviour is tested below
sl, _ := filepath.EvalSymlinks(fp) // err skipped as it would error for unexistent files (RPC check). The actual behaviour is tested below
// panic if we had a error getting absolute path,
// ... or if path doesnt contain the prefix path we expect,
// ... or if we're skipping hidden folders, and one is requested,
// ... or if we're skipping symlinks, path exists, and a symlink out of bound requested
if err != nil || !strings.HasPrefix(fp, rootPath) || *skipHidden && strings.Contains(p, "/.") || !*symlinks && len(sl) > 0 && !strings.HasPrefix(sl, rootPath) {
return "", errors.New("invalid path")
panic(errors.New("invalid path"))
}
return fp, nil
return fp
}
func main() {
Regular → Executable
+5 -13
View File
@@ -17,18 +17,10 @@ var styleCss string
var faviconSvg []byte
//go:embed ui/ui.tmpl
var uiTmpl string
var tmpl *template.Template
var template0 string
// fill in template
func init() {
var err error
t := strings.Replace(uiTmpl, "css_will_be_here", styleCss, 1)
t = strings.Replace(t, "js_will_be_here", scriptJs, 1)
t = strings.Replace(t, "favicon_will_be_here", base64.StdEncoding.EncodeToString(faviconSvg), 2)
tmpl, err = template.New("").Parse(t)
if err != nil {
panic(err)
}
}
var template1 = strings.Replace(template0, "css_will_be_here", styleCss, 1)
var template2 = strings.Replace(template1, "js_will_be_here", scriptJs, 1)
var template3 = strings.Replace(template2, "favicon_will_be_here", base64.StdEncoding.EncodeToString(faviconSvg), 2)
var templateParsed, _ = template.New("").Parse(template3)
-11
View File
@@ -204,17 +204,6 @@ func doTestRegular(t *testing.T, url string, testExtra bool) {
t.Fatal("post file incorrect path didnt errored")
}
// ~~~~~~~~~~~~~~~~~
fmt.Println("\r\n~~~~~~~~~~ test post file")
path = "2024-01-02-10:36:58.png"
payload = "123123123123123123123123"
body0 = postDummyFile(t, url, path, payload)
body1 = get(t, url+path)
body2 = fetchAndTestDefault(t, url)
if body0 != `ok` || body1 != payload || !strings.Contains(body2, `href="2024-01-02-10:36:58.png"`) {
t.Fatal("post file errored")
}
// ~~~~~~~~~~~~~~~~~
fmt.Println("\r\n~~~~~~~~~~ test mv rpc")
body0 = postJSON(t, url+"rpc", `{"call":"mv","args":["/AAA", "/hols/AAA"]}`)
+1 -3
View File
@@ -29,9 +29,7 @@ a simple UI comes as default, featuring :
[nix](https://search.nixos.org/packages?channel=unstable&show=gossa&from=0&size=50&sort=relevance&type=packages&query=gossa) - e.g. `nix-shell -p gossa`
[mpr](https://mpr.makedeb.org/packages/gossa)
binaries are available on the [release page](https://github.com/pldubouilh/gossa/releases)
binaries are available on the [release page](https://github.com/pldubouilh/gossa/releases) - or simply `make build` this repo.
all builds are reproducible, checkout the hashes on the release page.
+6 -12
View File
@@ -1,16 +1,10 @@
FROM docker.io/library/golang:1.23.0-alpine AS builder
RUN apk add --no-cache make
WORKDIR /gossaSrc
FROM golang:1.18 as builder
COPY . /gossaSrc
RUN make
RUN cd /gossaSrc && make
FROM docker.io/library/alpine:3.20
FROM alpine:3.15
ENV UID="1000" GID="1000" HOST="0.0.0.0" PORT="8001" PREFIX="/" FOLLOW_SYMLINKS="false" SKIP_HIDDEN_FILES="true" DATADIR="/shared" READONLY="false" VERB="false"
RUN apk add --no-cache su-exec
COPY ./support/entrypoint.sh /entrypoint.sh
COPY --from=builder /gossaSrc/gossa /gossa
RUN addgroup -g ${GID} user \
&& adduser -D -u ${UID} -G user user
WORKDIR ${DATADIR}
RUN chown ${UID}:${GID} ${DATADIR}
USER ${UID}:${GID}
ENTRYPOINT /gossa -h ${HOST} -p ${PORT} -k=${SKIP_HIDDEN_FILES} -ro=${READONLY} --symlinks=${FOLLOW_SYMLINKS} --prefix=${PREFIX} --verb=${VERB} ${DATADIR}
HEALTHCHECK --timeout=5s --start-period=5s --retries=3 CMD wget --no-verbose --tries=1 --spider 127.0.0.1:8001 || exit 1
ENTRYPOINT "/entrypoint.sh"
+1 -24
View File
@@ -2,29 +2,9 @@ version: '2'
services:
gossa-server:
image: docker.io/pldubouilh/gossa:latest
image: pldubouilh/gossa
container_name: gossa
restart: always
read_only: true
# uncomment to set the user
# user: "1000:1000"
# userns_mode: "keep-id" # uncomment if using rootless podman as well as the x-podman directive at the bottom
# environment:
#- READONLY=true # uncomment to set gossa as read only
# - UID=1000 # this should match the user set above
# - GID=1000 # this should match the user's group
cap_drop:
- ALL
cap_add:
- SETGID
- SETUID
# uncomment to set resource usage limits
# deploy:
# resources:
# limits:
# cpus: "2"
# memory: 250m
# pids: 1024
ports:
- 8001:8001
volumes:
@@ -34,6 +14,3 @@ services:
# - "traefik.port=8001"
# - "traefik.backend=gossa"
# - "traefik.frontend.rule=Host:${GOSSA}.${DOMAIN}"
# x-podman: # uncomment if using rootless podman as well as the userns_mode directive at the top
# in_pod: false
+2
View File
@@ -0,0 +1,2 @@
#!/bin/sh
exec su-exec ${UID}:${GID} /gossa -h ${HOST} -p ${PORT} -k=${SKIP_HIDDEN_FILES} -ro=${READONLY} --symlinks=${FOLLOW_SYMLINKS} --prefix=${PREFIX} --verb=${VERB} ${DATADIR}
+31 -100
View File
@@ -38,67 +38,36 @@ if you prefer building the image yourself :
a docker-compose example image is also provided. running docker compose should be straightforward : `docker-compose up .` have a look in `docker-compose.yml` for further configuration.
## multi-account setup with Caddy
## multi-account setup
authentication / user routing has been left out of the design of gossa, as simple tools are already available for this purpose. [caddy](https://caddyserver.com) is used here as an example, but other proxy can be used in a similar fashion.
authentication / user routing has been left out of the design of gossa, as simple tools are already available for this purpose. [caddy](https://caddyserver.com/v1/) is used here as an example, but other proxy can be used in a similar fashion.
### example 1 root, multiple read-only users
This sample Caddyfile will
+ enable https on the domain myserver.com (http will be automatically redirected to https)
this sample caddy config will
+ enable https on the domain myserver.com
+ password protect the access
+ route the root user requests to 1 gossa instance
+ route user1 and user2 requests to a readonly gossa instance
<details>
<summary>Legacy Caddy v1 Caddyfile</summary>
```sh
myserver.com
# proxy regular and read only instance
proxy / 127.0.0.1:8001
proxy /ro 127.0.0.1:8002 { without /ro }
# reroute non-root user to read-only
# cm9... is the output of `printf "root:password" | base64`
rewrite {
if {>Authorization} not "Basic cm9vdDpwYXNzd29yZA=="
to /ro/{path}
}
# gate access
basicauth / root password
basicauth / ro_user1 passworduser1
basicauth / ro_user2 passworduser2
```
</details>
Caddy v2 Caddyfile
```sh
myserver.com
# gate access
basic_auth {
root $2a$14$Zkx19XLiW6VYouLHR5NmfOFU0z2GTNmpkT/5qqR7hx4IjWJPDhjvG # password is "hiccup"
ro_user1 $2a$14$Zkx19XLiW6VYouLHR5NmfOFU0z2GTNmpkT/5qqR7hx4IjWJPDhjvG # password is "hiccup"
ro_user2 $2a$14$Zkx19XLiW6VYouLHR5NmfOFU0z2GTNmpkT/5qqR7hx4IjWJPDhjvG # password is "hiccup"
}
# named matcher for root user
@isroot {
vars {http.auth.user.id} root
}
# proxy regular and read only instance
handle @isroot {
reverse_proxy 127.0.0.1:8001
}
# route non-root user to read only instance
handle {
reverse_proxy 127.0.0.1:8002
proxy / 127.0.0.1:8001
proxy /ro 127.0.0.1:8002 { without /ro }
# reroute non-root user to read-only
# cm9... is the output of `printf "root:password" | base64`
rewrite {
if {>Authorization} not "Basic cm9vdDpwYXNzd29yZA=="
to /ro/{path}
}
# gate access
basicauth / root password
basicauth / ro_user1 passworduser1
basicauth / ro_user2 passworduser2
```
then simply start the 2 gossa instances, and caddy
@@ -116,59 +85,30 @@ then simply start the 2 gossa instances, and caddy
### example 2 users on 2 different folders
This sample Caddyfile will
+ enable https on the domain myserver.com (http will be automatically redirected to https)
this sample caddy config will
+ enable https on the domain myserver.com
+ password protect the access
+ route user1 to own folder
+ route user2 to own folder
+ share a folder between 2 users with a symlink
<details>
<summary>Legacy Caddy v1 Caddyfile</summary>
```sh
myserver.com
proxy /user1 127.0.0.1:8001 { without /user1 }
proxy /user2 127.0.0.1:8002 { without /user2 }
basicauth / user1 passworduser1
basicauth / user2 passworduser2
rewrite {
if {>Authorization} is "Basic dXNlcjE6cGFzc3dvcmR1c2VyMQ=="
to /user1/{path}
}
rewrite {
if {>Authorization} is "Basic dXNlcjI6cGFzc3dvcmR1c2VyMg=="
to /user2/{path}
}
```
</details>
Caddy v2 Caddyfile
```sh
myserver.com
basic_auth {
user1 $2a$14$Zkx19XLiW6VYouLHR5NmfOFU0z2GTNmpkT/5qqR7hx4IjWJPDhjvG # password is "hiccup"
user2 $2a$14$Zkx19XLiW6VYouLHR5NmfOFU0z2GTNmpkT/5qqR7hx4IjWJPDhjvG # password is "hiccup"
proxy /user1 127.0.0.1:8001 { without /user1 }
proxy /user2 127.0.0.1:8002 { without /user2 }
basicauth / user1 passworduser1
basicauth / user2 passworduser2
rewrite {
if {>Authorization} is "Basic dXNlcjE6cGFzc3dvcmR1c2VyMQ=="
to /user1/{path}
}
@user1auth {
vars {http.auth.user.id} user1
}
handle @user1auth {
reverse_proxy 127.0.0.1:8001
}
@user2auth {
vars {http.auth.user.id} user2
}
handle @user2auth {
reverse_proxy 127.0.0.1:8002
rewrite {
if {>Authorization} is "Basic dXNlcjI6cGFzc3dvcmR1c2VyMg=="
to /user2/{path}
}
```
@@ -184,12 +124,3 @@ start 2 gossa instances, and caddy
% ./gossa -p 8002 -symlinks=true test/user2 &
% ./caddy
```
## nginx setup
In order to allow for larger uploads, it's recommended to increase the maximum body size on your nginx config :
```
# increase maximum request size
client_max_body_size 100M;
```
+1 -2
View File
@@ -1,2 +1 @@
B!!!
test
B!!!
+12 -33
View File
@@ -11,7 +11,6 @@ const rmMsg = () => !confirm('Remove file?\n')
const ensureMove = () => !confirm('move items?')
const isRo = () => window.ro
// DOM elements
const upBarName = document.getElementById('upBarName')
const upBarPc = document.getElementById('upBarPc')
const upGrid = document.getElementById('drop-grid')
@@ -30,8 +29,6 @@ const editor = document.getElementById('text-editor')
const crossIcon = document.getElementById('quitAll')
const toast = document.getElementById('toast')
const table = document.getElementById('linkTable')
const helpMsg = document.getElementById('help_message')
const transparentPixel = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII='
// helpers
@@ -153,11 +150,8 @@ function rpc (call, args, cb) {
xhr.open('POST', location.origin + window.extraPath + '/rpc')
xhr.setRequestHeader('Content-Type', 'application/json;charset=UTF-8')
xhr.send(JSON.stringify({ call, args }))
xhr.onload = () => cb(false)
xhr.onerror = () => {
flicker(sadBadge)
cb(true)
}
xhr.onload = cb
xhr.onerror = () => flicker(sadBadge)
}
const mkdirCall = (path, cb) => rpc('mkdirp', [prependPath(path)], cb)
@@ -318,36 +312,28 @@ const textTypes = ['.txt', '.rtf', '.md', '.markdown', '.log', '.yaml', '.yml']
const isTextFile = src => src && textTypes.find(type => src.toLocaleLowerCase().includes(type))
let fileEdited
function saveText (cb) {
function saveText (quitting) {
const formData = new FormData()
formData.append(fileEdited, editor.value)
const fname = fileEdited + ".swp"
const path = encodeURIComponent(decodeURI(location.pathname) + fname)
const path = encodeURIComponent(decodeURI(location.pathname) + fileEdited)
upload(0, formData, path, () => {
toast.style.display = 'none'
cb()
if (!quitting) return
clearInterval(window.padTimer)
window.onbeforeunload = null
resetView()
softPrev()
refresh()
}, () => {
toast.style.display = 'block'
if (!quitting) return
alert('cant save!\r\nleave window open to resume saving\r\nwhen connection back up')
})
}
function padOff () {
if (!isEditorMode()) { return }
const swapfile = fileEdited + ".swp"
saveText(() => {
mvCall(prependPath(swapfile), prependPath(fileEdited), err => {
if (err) {
alert('cant save!\r\nleave window open to resume saving\r\nwhen connection back up')
return
}
clearInterval(window.padTimer)
window.onbeforeunload = null
resetView()
softPrev()
refresh()
})
})
saveText(true)
return true
}
@@ -861,12 +847,5 @@ function init () {
const matchingA = allA.find(a => a.href === cleanURL)
padOn(matchingA)
}
// check if we're at root path
if (location.pathname === window.extraPath + '/') {
helpMsg.style.display = 'block'
} else {
helpMsg.style.display = 'none'
}
}
init()
+1 -15
View File
@@ -24,9 +24,6 @@
.file-size {
display: none !important;
}
#help_message {
display: none !important;
}
}
/* hello standards 👋🏻 */
@@ -391,18 +388,6 @@ h1 > span:hover {
z-index: 101;
}
#help_message {
font-family: monospace;
font-size: 14px;
position: fixed;
bottom: 5px;
right: 13px;
opacity: 50%;
user-select: none;
display: none;
margin: unset;
}
.icon-large-folder {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgBAMAAACBVGfHAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAAJFBMVEUAAACZmQAICAj4+Pj//5nMzGb/+/AAAAD/zJkzMwAAAP////+gebovAAAAAXRSTlMAQObYZgAAAAFiS0dECx/XxMAAAAAHdElNRQfiBhgXAy5jJ3EAAAAA1klEQVQoz42RMQ7CMBAEQ0mZIIp0/AGecJFO7kC2FKXmATxikaxUtPS48Ss5XxwnoWI7j3f3fHJV/aFa1KzOuwsRdacFHCjpthjuChbL0U261ipJnDd65IokJhCaGTCzEOZ25SAwaAUgASoOyFFzxWEsEkQGz2BskFJfHMMwWB07AVAXg/QwZscYPzHqXAVicDE4eJ47QOOoHexzhINxQRxIEVkC6I217OGLg9DLIn7qkHHEWiAvEwAdJyQlNKK3gKfcITuxZjzrO+qt2mr/3uj1z1//6AsSG3mwAQQN9wAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxOC0wNi0yNFQyMzowMzo0Ni0wNDowMFfZ8nYAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTgtMDYtMjRUMjM6MDM6NDYtMDQ6MDAmhErKAAAAAElFTkSuQmCC");
}
@@ -481,3 +466,4 @@ h1 > span:hover {
.icon-dl {
background-image: url("data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz48IS0tIFVwbG9hZGVkIHRvOiBTVkcgUmVwbywgd3d3LnN2Z3JlcG8uY29tLCBHZW5lcmF0b3I6IFNWRyBSZXBvIE1peGVyIFRvb2xzIC0tPg0KPHN2ZyB3aWR0aD0iODAwcHgiIGhlaWdodD0iODAwcHgiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4NCjxwYXRoIGQ9Ik0yMCAxNVYxOEMyMCAxOS4xMDQ2IDE5LjEwNDYgMjAgMTggMjBINkM0Ljg5NTQzIDIwIDQgMTkuMTA0NiA0IDE4TDQgMTVNOCAxMUwxMiAxNU0xMiAxNUwxNiAxMU0xMiAxNVYzIiBzdHJva2U9IiNmZmYiIHN0cm9rZS13aWR0aD0iMS41IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiLz4NCjwvc3ZnPg==");
}
Vendored
-1
View File
@@ -78,7 +78,6 @@
</tr>
{{end}}
</table>
<p id="help_message">Help: Ctrl/Cmd + h<p>
</body>
<div id="upBar" class="bar">
<span style="display: none;" class="barName" id="upBarName"></span>