Compare commits

..
5 Commits
Author SHA1 Message Date
Graham Steffaniak 34b1c49054 updated workflow 2024-05-04 20:46:43 -05:00
Graham Steffaniak d79b095bbc update workflow permission 2024-05-04 20:43:36 -05:00
Graham SteffaniakandGraham Steffaniak 3cc90fab28 update release (#121)
Co-authored-by: Graham Steffaniak <gtsteff@gamil.com>
2024-05-04 20:40:34 -05:00
Graham SteffaniakandGraham Steffaniak 9f20b21ac9 Add release (#120)
Co-authored-by: Graham Steffaniak <gtsteff@gamil.com>
2024-05-04 20:26:14 -05:00
Graham Steffaniak d0532f1c83 v0.2.4 release (#116) 2024-02-09 18:11:00 -06:00
121 changed files with 11766 additions and 3286 deletions

No files matched your search

+5
View File
@@ -0,0 +1,5 @@
# These owners will be the default owners for everything in the repo.
# Unless a later match takes precedence, @o1egl will be requested for
# review when someone opens a pull request.
* @o1egl
+21 -24
View File
@@ -9,40 +9,37 @@ jobs:
test-backend:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
- uses: actions/checkout@v3
- uses: actions/setup-go@v4
with:
go-version: 'stable'
- working-directory: backend
run: go test -race -v ./...
go-version: 1.22.0
- run: cd backend && go test -race -v ./...
lint-backend:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
- uses: actions/checkout@v3
- uses: actions/setup-go@v4
with:
go-version: 'stable'
- uses: golangci/golangci-lint-action@v6
with:
version: v1.59
working-directory: backend
go-version: 1.22.0
- run: go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.54.2
- run: cd backend && golangci-lint run
format-backend:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
- uses: actions/checkout@v3
- uses: actions/setup-go@v4
with:
go-version: 'stable'
- working-directory: backend
run: go fmt ./...
go-version: 1.22.0
- run: cd backend && go fmt ./...
lint-frontend:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- working-directory: frontend
run: npm i eslint && npm run lint
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: '21'
- run: cd frontend && npm i eslint
- run: cd frontend && npm run lint
push_dev_to_registry:
name: Push dev image
runs-on: ubuntu-latest
@@ -66,7 +63,7 @@ jobs:
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@v5
uses: docker/metadata-action@9ec57ed1fcdbf14dcef7dfbe97b2010124a938b7
with:
images: gtstef/filebrowser
- name: Build and push
@@ -76,4 +73,4 @@ jobs:
file: ./Dockerfile
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
labels: ${{ steps.meta.outputs.labels }}
+20 -22
View File
@@ -9,39 +9,37 @@ jobs:
test-backend:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
- uses: actions/checkout@v3
- uses: actions/setup-go@v4
with:
go-version: 'stable'
- working-directory: backend
run: go test -race -v ./...
go-version: 1.22.0
- run: cd backend && go test -race -v ./...
lint-backend:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
- uses: actions/checkout@v3
- uses: actions/setup-go@v4
with:
go-version: 'stable'
- uses: golangci/golangci-lint-action@v6
with:
version: v1.59
working-directory: backend
go-version: 1.22.0
- run: go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.54.2
- run: cd backend && golangci-lint run
format-backend:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
- uses: actions/checkout@v3
- uses: actions/setup-go@v4
with:
go-version: 'stable'
- working-directory: backend
run: go fmt ./...
go-version: 1.22.0
- run: cd backend && go fmt ./...
lint-frontend:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- working-directory: frontend
run: npm i eslint && npm run lint
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: '21'
- run: cd frontend && npm i eslint
- run: cd frontend && npm run lint
push_latest_to_registry:
needs: [lint-frontend, lint-backend, test-backend, format-backend]
@@ -67,7 +65,7 @@ jobs:
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@v5
uses: docker/metadata-action@9ec57ed1fcdbf14dcef7dfbe97b2010124a938b7
with:
images: gtstef/filebrowser
- name: Build and push
+21 -23
View File
@@ -11,39 +11,37 @@ jobs:
test-backend:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
- uses: actions/checkout@v3
- uses: actions/setup-go@v4
with:
go-version: 'stable'
- working-directory: backend
run: go test -race -v ./...
go-version: 1.22.0
- run: cd backend && go test -race -v ./...
lint-backend:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
- uses: actions/checkout@v3
- uses: actions/setup-go@v4
with:
go-version: 'stable'
- uses: golangci/golangci-lint-action@v6
with:
version: v1.59
working-directory: backend
go-version: 1.22.0
- run: go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.54.2
- run: cd backend && golangci-lint run
format-backend:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
- uses: actions/checkout@v3
- uses: actions/setup-go@v4
with:
go-version: 'stable'
- working-directory: backend
run: go fmt ./...
go-version: 1.22.0
- run: cd backend && go fmt ./...
lint-frontend:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- working-directory: frontend
run: npm i eslint && npm run lint
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: '21'
- run: cd frontend && npm i eslint
- run: cd frontend && npm run lint
push_pr_to_registry:
name: Push PR
@@ -68,7 +66,7 @@ jobs:
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@v5
uses: docker/metadata-action@9ec57ed1fcdbf14dcef7dfbe97b2010124a938b7
with:
images: gtstef/filebrowser
- name: Build and push
@@ -78,4 +76,4 @@ jobs:
file: ./Dockerfile
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
labels: ${{ steps.meta.outputs.labels }}
+21 -24
View File
@@ -26,7 +26,7 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ steps.extract_branch.outputs.branch }}
release_name: ${{ steps.extract_branch.outputs.branch }}
release_name: Release ${{ steps.extract_branch.outputs.branch }}
body: |
Please see usage on [readme](https://github.com/gtsteffaniak/filebrowser).
Docker repo: https://hub.docker.com/r/gtstef/filebrowser/tags
@@ -35,40 +35,37 @@ jobs:
test-backend:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
- uses: actions/checkout@v3
- uses: actions/setup-go@v4
with:
go-version: 'stable'
- working-directory: backend
run: go test -race -v ./...
go-version: 1.22.0
- run: cd backend && go test -race -v ./...
lint-backend:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
- uses: actions/checkout@v3
- uses: actions/setup-go@v4
with:
go-version: 'stable'
- uses: golangci/golangci-lint-action@v6
with:
version: v1.59
working-directory: backend
go-version: 1.22.0
- run: go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.54.2
- run: cd backend && golangci-lint run
format-backend:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
- uses: actions/checkout@v3
- uses: actions/setup-go@v4
with:
go-version: 'stable'
- working-directory: backend
run: go fmt ./...
go-version: 1.22.0
- run: cd backend && go fmt ./...
lint-frontend:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- working-directory: frontend
run: npm i eslint && npm run lint
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: '21'
- run: cd frontend && npm i eslint
- run: cd frontend && npm run lint
push_release_to_registry:
needs: [lint-frontend, lint-backend, test-backend, format-backend]
name: Push release
@@ -94,7 +91,7 @@ jobs:
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@v5
uses: docker/metadata-action@9ec57ed1fcdbf14dcef7dfbe97b2010124a938b7
with:
images: gtstef/filebrowser
- name: Strip v from version number
-2
View File
@@ -6,8 +6,6 @@ rice-box.go
/filebrowser
/filebrowser.exe
/frontend/dist
/frontend/pkg
/frontend/package-lock.json
/backend/vendor
/backend/*.cov
+2 -24
View File
@@ -2,34 +2,12 @@
All notable changes to this project will be documented in this file. For commit guidelines, please refer to [Standard Version](https://github.com/conventional-changelog/standard-version).
## v0.2.6
This change focuses on minimizing and simplifying build process.
- **Change**: Migrated to Vite / Vue 3
- **Change**: removed npm modules
- replaced vuex with custom state management via src/store
- replaced noty with simple card popup notifications
- replaced moment with simple date formatter where needed
- replaced vue-simple-progress with vue component
- **Feature**: improved error logging
- backend errors show the root function that called them during the error
- frontend errors print errors to console that fail try/catch
- all frontend errors via popup notification & print to console as well
- **Fix**: Allow editing blank text based files in editor
- tweaked listing styles
- Feature: Allow disabling the index via configuration yaml
## v0.2.5
- Fix: delete user prompt works using native hovers.
## v0.2.4
- Feature: [create-folder-feature](https://github.com/gtsteffaniak/filebrowser/pull/105)
- Faature: [create-folder-feature](https://github.com/gtsteffaniak/filebrowser/pull/105)
- Feature: [playable shared video](https://github.com/filebrowser/filebrowser/issues/2537)
- Feature: photos, videos, and audio get embedded preview on share instead of icon
- Fix: sharable link bug, now uses special publicUser
- FIX: sharable link bug, now uses special publicUser
- Bump go version to 1.22
- In prep for vue3 migration, npm modules removed:
- js-base64
+8 -4
View File
@@ -1,18 +1,22 @@
FROM node:slim as nbuild
WORKDIR /app
COPY ./frontend/package*.json ./
RUN npm i --maxsockets 1
COPY ./frontend/package*.json ./
RUN npm ci --maxsockets 1
COPY ./frontend/ ./
RUN npm run build
FROM golang:1.22-alpine as base
WORKDIR /app
COPY ./backend ./
COPY ./backend ./
RUN go get -u golang.org/x/net
RUN go build -ldflags="-w -s" -o filebrowser .
FROM alpine:latest
ARG app="/app/filebrowser"
RUN apk --no-cache add ca-certificates mailcap
RUN apk --no-cache add \
ca-certificates \
mailcap
WORKDIR /
COPY --from=base /app/filebrowser* ./
COPY --from=nbuild /app/dist/ ./frontend/dist/
ENTRYPOINT [ "./filebrowser" ]
+19 -37
View File
@@ -4,7 +4,7 @@
<p align="center">
<img src="frontend/public/img/icons/favicon-256x256.png" width="100" title="Login With Custom URL">
</p>
<h3 align="center">Filebrowser - A modern web-based file manager</h3>
<h3 align="center">Filebrowser - A modern file manager for the web</h3>
<p align="center">
<img width="800" src="https://github.com/gtsteffaniak/filebrowser/assets/42989099/899152cf-3e69-4179-aa82-752af2df3fc6" title="Main Screenshot">
</p>
@@ -15,59 +15,41 @@
This fork makes the following significant changes to filebrowser for origin:
1. [x] Better search
- Lightning fast
- realtime results as you type
- Works with more type filters
- interactive results page.
2. [x] Revamped and simplified GUI navbar and sidebar menu.
- Additional compact view mode as well as refreshed view mode styles.
3. [x] Revamped configuration via `filebrowser.yml` config file.
- More configurations possible at a per-user level
- <img width="450" alt="image" src="https://github.com/gtsteffaniak/filebrowser/assets/42989099/625bd7c4-5ee9-4011-aaae-2a388ab0813b">
1. [x] Better search.
- Lightning fast
- realtime results as you type
- Works with more type filters
- interactive results page.
2. [x] Revamped and simplified GUI navbar and sidebar menu.
3. [x] **IMPORTANT** Revamped configuration via `filebrowser.yml` config file.
4. [x] More configurations possible at a per-user level
- <img width="450" alt="image" src="https://github.com/gtsteffaniak/filebrowser/assets/42989099/625bd7c4-5ee9-4011-aaae-2a388ab0813b">
5. [x] Additional compact view mode as well as refreshed view mode styles.
## About
Filebrowser provides a file managing interface within a specified directory
and it can be used to upload, delete, preview, rename and edit your files.
It allows the creation of multiple users and each user can have its own
directory.
directory. It can be used as a standalone app.
This repository is a fork, a collection of changes that make this program
work better in terms of asthetics and performance. Improved search,
simplified ui (without removing features) and more secure and up-to-date
build are just a few examples.
This Implementation of filebrowser differs significantly to the original.
There are hundereds of thousands of lines changed and they are generally
no longer compatible with eachother. This has been intentional -- the
focus of this fork is on a few key principles:
- Simplicity and improved user experience
- Efficiency of operations and performance
- Minimizing external dependancies and usage of standard libraries.
- Of course -- adding much needed features.
work better in terms of asthetics and performance. Improved search,
simplified ui (without removing features) and more secure and up-to-date
build are just a few examples.
## Look
One way you can observe the improved user experience is how I changed the UI.
The Navbar is simplified to a three component system :
1. (Left) The slide-out action panel button
2. (Middle) The powerful search bar.
3. (Right) The view change toggle.
All other functions are moved either into the action menu or popup menus.
If the action is does not depend on context, it will exist in the slide-out action panel.
If the action is available based on context, it will showup as a popup menu.
<p align="center">
<img width="500" src="https://github.com/gtsteffaniak/filebrowser/assets/42989099/35cdeb3b-ab79-4b04-8001-8f51f6ea06bb" title="Dark mode">
<img width="500" alt="image" src="https://github.com/gtsteffaniak/filebrowser/assets/42989099/55fa4f5c-440e-4a97-b711-96139208a163">
<img width="500" alt="image" src="https://github.com/gtsteffaniak/filebrowser/assets/42989099/c76f4100-949b-4e17-a3e6-e410fb8ec08f">
<img width="500" alt="image" src="https://github.com/gtsteffaniak/filebrowser/assets/42989099/0bde26f3-fa90-411e-bd0b-abaa47506d62">
<img width="560" alt="image" src="https://github.com/gtsteffaniak/filebrowser/assets/42989099/71d8f2b8-6fe6-4fdc-8aac-503d08c28d86">
</p>
## Install
Using docker:
@@ -119,7 +101,7 @@ volumes:
```
Not using docker (not recommended) (Must donwload asset with frontend directory next to filebrowser binary)
Not using docker (not recommended)
```
./filebrowser -f <filebrowser.yml or other /path/to/config.yaml>
+16 -17
View File
@@ -3,7 +3,6 @@ package cmd
import (
"crypto/tls"
"flag"
"fmt"
"io/fs"
"log"
"net"
@@ -65,49 +64,49 @@ var rootCmd = &cobra.Command{
fileCache = diskcache.New(afero.NewOsFs(), cacheDir)
}
// initialize indexing and schedule indexing ever n minutes (default 5)
go files.InitializeIndex(serverConfig.IndexingInterval, serverConfig.Indexing)
go files.InitializeIndex(serverConfig.IndexingInterval, true)
_, err := os.Stat(serverConfig.Root)
checkErr(fmt.Sprint("cmd os.Stat ", serverConfig.Root), err)
checkErr(err)
var listener net.Listener
address := serverConfig.Address + ":" + strconv.Itoa(serverConfig.Port)
switch {
case serverConfig.Socket != "":
listener, err = net.Listen("unix", serverConfig.Socket)
checkErr("net.Listen", err)
checkErr(err)
socketPerm, err := cmd.Flags().GetUint32("socket-perm") //nolint:govet
checkErr("cmd.Flags().GetUint32", err)
checkErr(err)
err = os.Chmod(serverConfig.Socket, os.FileMode(socketPerm))
checkErr("os.Chmod", err)
checkErr(err)
case serverConfig.TLSKey != "" && serverConfig.TLSCert != "":
cer, err := tls.LoadX509KeyPair(serverConfig.TLSCert, serverConfig.TLSKey) //nolint:govet
checkErr("tls.LoadX509KeyPair", err)
checkErr(err)
listener, err = tls.Listen("tcp", address, &tls.Config{
MinVersion: tls.VersionTLS12,
Certificates: []tls.Certificate{cer}},
)
checkErr("tls.Listen", err)
checkErr(err)
default:
listener, err = net.Listen("tcp", address)
checkErr("net.Listen", err)
checkErr(err)
}
sigc := make(chan os.Signal, 1)
signal.Notify(sigc, os.Interrupt, syscall.SIGTERM)
go cleanupHandler(listener, sigc)
assetsFs := dirFS{Dir: http.Dir("frontend/dist")}
handler, err := fbhttp.NewHandler(imgSvc, fileCache, d.store, &serverConfig, assetsFs)
checkErr("fbhttp.NewHandler", err)
checkErr(err)
defer listener.Close()
log.Println("Listening on", listener.Addr().String())
//nolint: gosec
if err := http.Serve(listener, handler); err != nil {
log.Fatalf("Could not start server on port %d: %v", serverConfig.Port, err)
log.Fatal(err)
}
}, pythonConfig{allowNoDB: true}),
}
func StartFilebrowser() {
if err := rootCmd.Execute(); err != nil {
log.Fatal("Error starting filebrowser:", err)
log.Fatal(err)
}
}
@@ -122,16 +121,16 @@ func quickSetup(d pythonData) {
settings.Config.Auth.Key = generateKey()
if settings.Config.Auth.Method == "noauth" {
err := d.store.Auth.Save(&auth.NoAuth{})
checkErr("d.store.Auth.Save", err)
checkErr(err)
} else {
settings.Config.Auth.Method = "password"
err := d.store.Auth.Save(&auth.JSONAuth{})
checkErr("d.store.Auth.Save", err)
checkErr(err)
}
err := d.store.Settings.Save(&settings.Config)
checkErr("d.store.Settings.Save", err)
checkErr(err)
err = d.store.Settings.SaveServer(&settings.Config.Server)
checkErr("d.store.Settings.SaveServer", err)
checkErr(err)
user := &users.User{}
settings.Config.UserDefaults.Apply(user)
user.Username = settings.Config.Auth.AdminUsername
@@ -151,5 +150,5 @@ func quickSetup(d pythonData) {
Admin: true,
}
err = d.store.Users.Save(user)
checkErr("d.store.Users.Save", err)
checkErr(err)
}
+4 -4
View File
@@ -42,23 +42,23 @@ including 'index_end'.`,
},
Run: python(func(cmd *cobra.Command, args []string, d pythonData) {
i, err := strconv.Atoi(args[0])
checkErr("strconv.Atoi", err)
checkErr(err)
f := i
if len(args) == 2 { //nolint:gomnd
f, err = strconv.Atoi(args[1])
checkErr("strconv.Atoi", err)
checkErr(err)
}
user := func(u *users.User) {
u.Rules = append(u.Rules[:i], u.Rules[f+1:]...)
err := d.store.Users.Save(u)
checkErr("d.store.Users.Save", err)
checkErr(err)
}
global := func(s *settings.Settings) {
s.Rules = append(s.Rules[:i], s.Rules[f+1:]...)
err := d.store.Settings.Save(s)
checkErr("d.store.Settings.Save", err)
checkErr(err)
}
runRules(d.store, cmd, user, global)
+2 -2
View File
@@ -33,7 +33,7 @@ func runRules(st *storage.Storage, cmd *cobra.Command, usersFn func(*users.User)
id := getUserIdentifier(cmd.Flags())
if id != nil {
user, err := st.Users.Get("", id)
checkErr("st.Users.Get", err)
checkErr(err)
if usersFn != nil {
usersFn(user)
@@ -44,7 +44,7 @@ func runRules(st *storage.Storage, cmd *cobra.Command, usersFn func(*users.User)
}
s, err := st.Settings.Get()
checkErr("st.Settings.Get", err)
checkErr(err)
if globalFn != nil {
globalFn(s)
+2 -2
View File
@@ -44,13 +44,13 @@ var rulesAddCmd = &cobra.Command{
user := func(u *users.User) {
u.Rules = append(u.Rules, rule)
err := d.store.Users.Save(u)
checkErr("d.store.Users.Save", err)
checkErr(err)
}
global := func(s *settings.Settings) {
s.Rules = append(s.Rules, rule)
err := d.store.Settings.Save(s)
checkErr("d.store.Settings.Save", err)
checkErr(err)
}
runRules(d.store, cmd, user, global)
+4 -4
View File
@@ -22,19 +22,19 @@ var usersAddCmd = &cobra.Command{
LockPassword: mustGetBool(cmd.Flags(), "lockPassword"),
}
servSettings, err := d.store.Settings.GetServer()
checkErr("d.store.Settings.GetServer()", err)
checkErr(err)
// since getUserDefaults() polluted s.Defaults.Scope
// which makes the Scope not the one saved in the db
// we need the right s.Defaults.Scope here
s2, err := d.store.Settings.Get()
checkErr("d.store.Settings.Get()", err)
checkErr(err)
userHome, err := s2.MakeUserDir(user.Username, user.Scope, servSettings.Root)
checkErr("s2.MakeUserDir", err)
checkErr(err)
user.Scope = userHome
err = d.store.Users.Save(user)
checkErr("d.store.Users.Save", err)
checkErr(err)
printUsers([]*users.User{user})
}, pythonConfig{}),
}
+2 -2
View File
@@ -16,9 +16,9 @@ path to the file where you want to write the users.`,
Args: jsonYamlArg,
Run: python(func(cmd *cobra.Command, args []string, d pythonData) {
list, err := d.store.Users.Gets("")
checkErr("d.store.Users.Gets", err)
checkErr(err)
err = marshal(args[0], list)
checkErr("marshal", err)
checkErr(err)
}, pythonConfig{}),
}
+1 -1
View File
@@ -46,6 +46,6 @@ var findUsers = python(func(cmd *cobra.Command, args []string, d pythonData) {
list, err = d.store.Users.Gets("")
}
checkErr("findUsers", err)
checkErr(err)
printUsers(list)
}, pythonConfig{})
+9 -11
View File
@@ -27,28 +27,28 @@ list or set it to 0.`,
Args: jsonYamlArg,
Run: python(func(cmd *cobra.Command, args []string, d pythonData) {
fd, err := os.Open(args[0])
checkErr("os.Open", err)
checkErr(err)
defer fd.Close()
list := []*users.User{}
err = unmarshal(args[0], &list)
checkErr("unmarshal", err)
checkErr(err)
for _, user := range list {
err = user.Clean("")
checkErr("Clean", err)
checkErr(err)
}
if mustGetBool(cmd.Flags(), "replace") {
oldUsers, err := d.store.Users.Gets("")
checkErr("d.store.Users.Gets", err)
checkErr(err)
err = marshal("users.backup.json", list)
checkErr("marshal users.backup.json", err)
checkErr(err)
for _, user := range oldUsers {
err = d.store.Users.Delete(user.ID)
checkErr("d.store.Users.Delete", err)
checkErr(err)
}
}
@@ -60,8 +60,7 @@ list or set it to 0.`,
// User exists in DB.
if err == nil {
if !overwrite {
newErr := errors.New("user " + strconv.Itoa(int(user.ID)) + " is already registered")
checkErr("", newErr)
checkErr(errors.New("user " + strconv.Itoa(int(user.ID)) + " is already registred"))
}
// If the usernames mismatch, check if there is another one in the DB
@@ -69,8 +68,7 @@ list or set it to 0.`,
// operation
if user.Username != onDB.Username {
if conflictuous, err := d.store.Users.Get("", user.Username); err == nil { //nolint:govet
newErr := usernameConflictError(user.Username, conflictuous.ID, user.ID)
checkErr("usernameConflictError", newErr)
checkErr(usernameConflictError(user.Username, conflictuous.ID, user.ID))
}
}
} else {
@@ -80,7 +78,7 @@ list or set it to 0.`,
}
err = d.store.Users.Save(user)
checkErr("d.store.Users.Save", err)
checkErr(err)
}
}, pythonConfig{}),
}
+1 -1
View File
@@ -25,7 +25,7 @@ var usersRmCmd = &cobra.Command{
err = d.store.Users.Delete(id)
}
checkErr("usersRmCmd", err)
checkErr(err)
fmt.Println("user deleted successfully")
}, pythonConfig{}),
}
+2 -2
View File
@@ -29,10 +29,10 @@ options you want to change.`,
} else {
user, err = d.store.Users.Get("", username)
}
checkErr("d.store.Users.Get", err)
checkErr(err)
err = d.store.Users.Update(user)
checkErr("d.store.Users.Update", err)
checkErr(err)
printUsers([]*users.User{user})
}, pythonConfig{}),
}
+10 -12
View File
@@ -3,7 +3,6 @@ package cmd
import (
"encoding/json"
"errors"
"fmt"
"log"
"os"
"path/filepath"
@@ -18,33 +17,33 @@ import (
"github.com/gtsteffaniak/filebrowser/storage/bolt"
)
func checkErr(source string, err error) {
func checkErr(err error) {
if err != nil {
log.Fatalf("%s: %v", source, err)
log.Fatal(err)
}
}
func mustGetString(flags *pflag.FlagSet, flag string) string {
s, err := flags.GetString(flag)
checkErr("mustGetString", err)
checkErr(err)
return s
}
func mustGetBool(flags *pflag.FlagSet, flag string) bool {
b, err := flags.GetBool(flag)
checkErr("mustGetBool", err)
checkErr(err)
return b
}
func mustGetUint(flags *pflag.FlagSet, flag string) uint {
b, err := flags.GetUint(flag)
checkErr("mustGetUint", err)
checkErr(err)
return b
}
func generateKey() []byte {
k, err := settings.GenerateKey()
checkErr("generateKey", err)
checkErr(err)
return k
}
@@ -97,19 +96,18 @@ func python(fn pythonFunc, cfg pythonConfig) cobraFunc {
data.hadDB = exists
db, err := storm.Open(path)
checkErr(fmt.Sprintf("storm.Open path %v", path), err)
checkErr(err)
defer db.Close()
data.store, err = bolt.NewStorage(db)
checkErr("bolt.NewStorage", err)
checkErr(err)
fn(cmd, args, data)
}
}
func marshal(filename string, data interface{}) error {
fd, err := os.Create(filename)
checkErr("os.Create", err)
checkErr(err)
defer fd.Close()
switch ext := filepath.Ext(filename); ext {
@@ -127,7 +125,7 @@ func marshal(filename string, data interface{}) error {
func unmarshal(filename string, data interface{}) error {
fd, err := os.Open(filename)
checkErr("os.Open", err)
checkErr(err)
defer fd.Close()
switch ext := filepath.Ext(filename); ext {
+6 -10
View File
@@ -123,7 +123,7 @@ func FileInfoFaster(opts FileOptions) (*FileInfo, error) {
if exists && !opts.Content {
// Check if the cache time is less than 1 second
if time.Since(info.CacheTime) > time.Second {
go RefreshFileInfo(opts)
go refreshFileInfo(opts)
}
// refresh cache after
return &info, nil
@@ -133,7 +133,7 @@ func FileInfoFaster(opts FileOptions) (*FileInfo, error) {
file, err := NewFileInfo(opts)
return file, err
}
updated := RefreshFileInfo(opts)
updated := refreshFileInfo(opts)
if !updated {
file, err := NewFileInfo(opts)
return file, err
@@ -146,7 +146,7 @@ func FileInfoFaster(opts FileOptions) (*FileInfo, error) {
}
}
func RefreshFileInfo(opts FileOptions) bool {
func refreshFileInfo(opts FileOptions) bool {
if !opts.Checker.Check(opts.Path) {
return false
}
@@ -286,15 +286,11 @@ func (i *FileInfo) addContent(path string) error {
if err != nil {
return err
}
stringContent := string(content)
if !utf8.ValidString(stringContent) {
c := string(string(content))
if !utf8.ValidString(c) {
return nil
}
if stringContent == "" {
i.Content = "empty-file-x6OlSil"
return nil
}
i.Content = stringContent
i.Content = string(c)
}
return nil
}
-6
View File
@@ -1,6 +0,0 @@
{
"name": "frontend",
"lockfileVersion": 3,
"requires": true,
"packages": {}
}
+9 -9
View File
@@ -13,15 +13,15 @@ require (
github.com/gorilla/mux v1.8.1
github.com/marusama/semaphore/v2 v2.5.0
github.com/mholt/archiver/v3 v3.5.1
github.com/shirou/gopsutil/v3 v3.24.5
github.com/shirou/gopsutil/v3 v3.24.4
github.com/spf13/afero v1.11.0
github.com/spf13/cobra v1.8.0
github.com/spf13/pflag v1.0.5
github.com/stretchr/testify v1.9.0
github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce
golang.org/x/crypto v0.24.0
golang.org/x/image v0.17.0
golang.org/x/text v0.16.0
golang.org/x/crypto v0.22.0
golang.org/x/image v0.15.0
golang.org/x/text v0.15.0
)
require (
@@ -30,13 +30,13 @@ require (
github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 // indirect
github.com/dsoprea/go-logging v0.0.0-20200710184922-b02d349568dd // indirect
github.com/dsoprea/go-utility/v2 v2.0.0-20221003172846-a3e1774ef349 // indirect
github.com/fatih/color v1.17.0 // indirect
github.com/fatih/color v1.16.0 // indirect
github.com/go-errors/errors v1.5.1 // indirect
github.com/go-ole/go-ole v1.3.0 // indirect
github.com/golang/geo v0.0.0-20230421003525-6adc56603217 // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/klauspost/compress v1.17.9 // indirect
github.com/klauspost/compress v1.17.8 // indirect
github.com/klauspost/pgzip v1.2.6 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
@@ -47,9 +47,9 @@ require (
github.com/ulikunitz/xz v0.5.12 // indirect
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect
github.com/yusufpapurcu/wmi v1.2.4 // indirect
go.etcd.io/bbolt v1.3.10 // indirect
golang.org/x/net v0.26.0 // indirect
golang.org/x/sys v0.21.0 // indirect
go.etcd.io/bbolt v1.3.9 // indirect
golang.org/x/net v0.24.0 // indirect
golang.org/x/sys v0.20.0 // indirect
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
+83 -20
View File
@@ -2,12 +2,14 @@ github.com/DataDog/zstd v1.4.1 h1:3oxKN3wbHibqx897utPC2LTQU4J+IHWWJO+glkAkpFM=
github.com/DataDog/zstd v1.4.1/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo=
github.com/Sereal/Sereal v0.0.0-20190618215532-0b8ac451a863 h1:BRrxwOZBolJN4gIwvZMJY1tzqBvQgpaZiQRuIDD40jM=
github.com/Sereal/Sereal v0.0.0-20190618215532-0b8ac451a863/go.mod h1:D0JMgToj/WdxCgd30Kc1UcA9E+WdZoJqeVOuYW7iTBM=
github.com/andybalholm/brotli v1.0.1 h1:KqhlKozYbRtJvsPrrEeXcO+N2l6NYT5A2QAFmSULpEc=
github.com/andybalholm/brotli v1.0.1/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y=
github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M=
github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
github.com/asdine/storm/v3 v3.2.1 h1:I5AqhkPK6nBZ/qJXySdI7ot5BlXSZ7qvDY1zAn5ZJac=
github.com/asdine/storm/v3 v3.2.1/go.mod h1:LEpXwGt4pIqrE/XcTvCnZHT5MgZCV6Ub9q7yQzOFWr0=
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c=
@@ -32,16 +34,20 @@ github.com/dsoprea/go-utility/v2 v2.0.0-20221003142440-7a1927d49d9d/go.mod h1:LV
github.com/dsoprea/go-utility/v2 v2.0.0-20221003160719-7bc88537c05e/go.mod h1:VZ7cB0pTjm1ADBWhJUOHESu4ZYy9JN+ZPqjfiW09EPU=
github.com/dsoprea/go-utility/v2 v2.0.0-20221003172846-a3e1774ef349 h1:DilThiXje0z+3UQ5YjYiSRRzVdtamFpvBQXKwMglWqw=
github.com/dsoprea/go-utility/v2 v2.0.0-20221003172846-a3e1774ef349/go.mod h1:4GC5sXji84i/p+irqghpPFZBF8tRN/Q7+700G0/DLe8=
github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4=
github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI=
github.com/fatih/color v1.10.0 h1:s36xzo75JdqLaaWoiEHk767eHiwo0598uUxyfiPkDsg=
github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 h1:BHsljHzVlRcyQhjrss6TZTdY2VfCqZPbv5k3iBFa2ZQ=
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
github.com/go-errors/errors v1.0.2/go.mod h1:psDX2osz5VnTOnFWbDeWwS7yejl+uV3FEWEp4lssFEs=
github.com/go-errors/errors v1.1.1/go.mod h1:psDX2osz5VnTOnFWbDeWwS7yejl+uV3FEWEp4lssFEs=
github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA=
github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=
github.com/go-errors/errors v1.5.1 h1:ZwEMSLRCapFLflTpT7NKaAc7ukJ8ZPEjzlxt8rPN8bk=
github.com/go-errors/errors v1.5.1/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
@@ -57,6 +63,7 @@ github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOW
github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
github.com/golang/geo v0.0.0-20190916061304-5b978397cfec/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI=
github.com/golang/geo v0.0.0-20200319012246-673a6f80352d/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI=
github.com/golang/geo v0.0.0-20210211234256-740aa86cb551 h1:gtexQ/VGyN+VVFRXSFiguSNcXmS6rkKT+X7FdIrTtfo=
github.com/golang/geo v0.0.0-20210211234256-740aa86cb551/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI=
github.com/golang/geo v0.0.0-20230421003525-6adc56603217 h1:HKlyj6in2JV6wVkmQ4XmG/EIm+SCYlPZ+V4GWit7Z+I=
github.com/golang/geo v0.0.0-20230421003525-6adc56603217/go.mod h1:8wI0hitZ3a1IxZfeH3/5I97CI8i5cLGsYe7xNhQGs9U=
@@ -65,10 +72,13 @@ github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5y
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golang/snappy v0.0.2 h1:aeE13tS0IiQgFjYdoL8qN3K1N2bXXtI6Vi51/y7BpMw=
github.com/golang/snappy v0.0.2/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
@@ -78,10 +88,14 @@ github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLf
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4=
github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
github.com/klauspost/compress v1.11.4 h1:kz40R/YWls3iqT9zX9AHN3WoVsrAWVyui5sxuLqiXqU=
github.com/klauspost/compress v1.11.4/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
github.com/klauspost/compress v1.17.6 h1:60eq2E/jlfwQXtvZEeBUYADs+BwKBWURIY+Gj2eRGjI=
github.com/klauspost/compress v1.17.6/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM=
github.com/klauspost/compress v1.17.8 h1:YcnTYrq7MikUT7k0Yb5eceMmALQPYBW/Xltxn0NAMnU=
github.com/klauspost/compress v1.17.8/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
github.com/klauspost/pgzip v1.2.5 h1:qnWYvvKqedOF2ulHpMG72XQol4ILEJ8k2wwRl/Km8oE=
github.com/klauspost/pgzip v1.2.5/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU=
github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
@@ -92,58 +106,93 @@ github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
github.com/marusama/semaphore/v2 v2.5.0 h1:o/1QJD9DBYOWRnDhPwDVAXQn6mQYD0gZaS1Tpx6DJGM=
github.com/marusama/semaphore/v2 v2.5.0/go.mod h1:z9nMiNUekt/LTpTUQdpp+4sJeYqUGpwMHfW0Z8V8fnQ=
github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8=
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mholt/archiver/v3 v3.5.1 h1:rDjOBX9JSF5BvoJGvjqK479aL70qh9DIpZCl+k7Clwo=
github.com/mholt/archiver/v3 v3.5.1/go.mod h1:e3dqJ7H78uzsRSEACH1joayhuSyhnonssnDhppzS1L4=
github.com/nwaples/rardecode v1.1.0 h1:vSxaY8vQhOcVr4mm5e8XllHWTiM4JF507A0Katqw7MQ=
github.com/nwaples/rardecode v1.1.0/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0=
github.com/nwaples/rardecode v1.1.3 h1:cWCaZwfM5H7nAD6PyEdcVnczzV8i/JtotnyW/dD9lEc=
github.com/nwaples/rardecode v1.1.3/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0=
github.com/pierrec/lz4/v4 v4.1.2 h1:qvY3YFXRQE/XB8MlLzJH7mSzBs74eA2gg52YTk6jUPM=
github.com/pierrec/lz4/v4 v4.1.2/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ=
github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b h1:0LFwY6Q3gMACTjAbMZBjXAqTOzOwFaj2Ld6cjeQ7Rig=
github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU=
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/shirou/gopsutil/v3 v3.24.5 h1:i0t8kL+kQTvpAYToeuiVk3TgDeKOFioZO3Ztz/iZ9pI=
github.com/shirou/gopsutil/v3 v3.24.5/go.mod h1:bsoOS1aStSs9ErQ1WWfxllSeS1K5D+U30r2NfcubMVk=
github.com/shirou/gopsutil/v3 v3.24.1 h1:R3t6ondCEvmARp3wxODhXMTLC/klMa87h2PHUw5m7QI=
github.com/shirou/gopsutil/v3 v3.24.1/go.mod h1:UU7a2MSBQa+kW1uuDq8DeEBS8kmrnQwsv2b5O513rwU=
github.com/shirou/gopsutil/v3 v3.24.4 h1:dEHgzZXt4LMNm+oYELpzl9YCqV65Yr/6SfrvgRBtXeU=
github.com/shirou/gopsutil/v3 v3.24.4/go.mod h1:lTd2mdiOspcqLgAnr9/nGi71NkeMpWKdmhuxm9GusH8=
github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ=
github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k=
github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY=
github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0=
github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY=
github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce h1:fb190+cK2Xz/dvi9Hv8eCYJYvIGUTN2/KLq1pT6CjEc=
github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce/go.mod h1:o8v6yHRoik09Xen7gje4m9ERNah1d1PPsVq1VEx9vE4=
github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
github.com/ulikunitz/xz v0.5.9 h1:RsKRIA2MO8x56wkkcd3LbtcE/uMszhb6DpRf+3uwa3I=
github.com/ulikunitz/xz v0.5.9/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
github.com/ulikunitz/xz v0.5.11 h1:kpFauv27b6ynzBNT/Xy+1k+fK4WswhN/6PN5WhFAGw8=
github.com/ulikunitz/xz v0.5.11/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
github.com/ulikunitz/xz v0.5.12 h1:37Nm15o69RwBkXM0J6A5OlE67RZTfzUxTj8fB3dfcsc=
github.com/ulikunitz/xz v0.5.12/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
github.com/vmihailenco/msgpack v4.0.4+incompatible h1:dSLoQfGFAo3F6OoNhwUmLwVgaUXK79GlxNBwueZn0xI=
github.com/vmihailenco/msgpack v4.0.4+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk=
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo=
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos=
github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw=
github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
go.etcd.io/bbolt v1.3.4 h1:hi1bXHMVrlQh6WwxAy+qZCV/SYIlqo+Ushwdpa4tAKg=
go.etcd.io/bbolt v1.3.4/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
go.etcd.io/bbolt v1.3.10 h1:+BqfJTcCzTItrop8mq/lbzL8wSGtj94UO/3U31shqG0=
go.etcd.io/bbolt v1.3.10/go.mod h1:bK3UQLPJZly7IlNmV7uVHJDxfe5aK9Ll93e/74Y9oEQ=
go.etcd.io/bbolt v1.3.8 h1:xs88BrvEv273UsB79e0hcVrlUWmS0a8upikMFhSyAtA=
go.etcd.io/bbolt v1.3.8/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw=
go.etcd.io/bbolt v1.3.9 h1:8x7aARPEXiXbHmtUwAIv7eV2fQFHrLLavdiJ3uzJXoI=
go.etcd.io/bbolt v1.3.9/go.mod h1:zaO32+Ti0PK1ivdPtgMESzuzL2VPoIG1PCQNvOdo/dE=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI=
golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM=
golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc=
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30=
golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.17.0 h1:nTRVVdajgB8zCMZVsViyzhnMKPwYeroEERRC64JuLco=
golang.org/x/image v0.17.0/go.mod h1:4yyo5vMFQjVjUcVk4jEQcU9MGy/rulF5WvUILseCM2E=
golang.org/x/image v0.15.0 h1:kOELfmgrmJlw4Cdb7g/QGuB3CvDrXbqEIww/pNtNBm8=
golang.org/x/image v0.15.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20191105084925-a882066a44e0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
@@ -151,13 +200,17 @@ golang.org/x/net v0.0.0-20200320220750-118fecf932d8/go.mod h1:z5CRVTTTmAJ677TzLL
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20221002022538-bcab6841153b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ=
golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c=
golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U=
golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo=
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -167,16 +220,25 @@ golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk=
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU=
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90=
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
@@ -191,5 +253,6 @@ gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+1 -4
View File
@@ -167,6 +167,7 @@ var resourcePutHandler = withUser(func(w http.ResponseWriter, r *http.Request, d
w.Header().Set("ETag", etag)
return nil
}, "save", r.URL.Path, "", d.user)
return errToStatus(err), err
})
@@ -271,10 +272,6 @@ func writeFile(fs afero.Fs, dst string, in io.Reader) (os.FileInfo, error) {
return nil, err
}
//files.RefreshFileInfo(files.FileOptions{
// Fs: info,
//})
return info, nil
}
+3 -1
View File
@@ -64,7 +64,9 @@ func handleWithStaticData(w http.ResponseWriter, _ *http.Request, d *data, fSys
if err != nil {
return http.StatusInternalServerError, err
}
auther := raw.(*auth.JSONAuth)
if auther.ReCaptcha != nil {
data["ReCaptcha"] = auther.ReCaptcha.Key != "" && auther.ReCaptcha.Secret != ""
data["ReCaptchaHost"] = auther.ReCaptcha.Host
@@ -102,7 +104,7 @@ func getStaticHandlers(store *storage.Storage, server *settings.Server, assetsFs
}
w.Header().Set("x-xss-protection", "1; mode=block")
return handleWithStaticData(w, r, d, assetsFs, "public/index.html", "text/html; charset=utf-8")
return handleWithStaticData(w, r, d, assetsFs, "index.html", "text/html; charset=utf-8")
}, "", store, server)
static = handle(func(w http.ResponseWriter, r *http.Request, d *data) (int, error) {
-1
View File
@@ -58,7 +58,6 @@ func setDefaults() Settings {
Database: "database.db",
Log: "stdout",
Root: "/srv",
Indexing: true,
},
Auth: Auth{
TokenExpirationTime: "2h",
-1
View File
@@ -54,7 +54,6 @@ type Server struct {
Root string `json:"root"`
UserHomeBasePath string `json:"userHomeBasePath"`
CreateUserDir bool `json:"createUserDir"`
Indexing bool `json:"indexing"`
}
type Frontend struct {
+1 -1
View File
@@ -2,7 +2,7 @@ package version
var (
// Version is the current File Browser version.
Version = "(0.2.6)"
Version = "(0.2.4)"
// CommitSHA is the commmit sha.
CommitSHA = "(unknown)"
)
-3
View File
@@ -11,7 +11,6 @@ server:
CreateUserDir: false
UserHomeBasePath: ""
indexingInterval: 5
indexing: true
numImageProcessors: 4
socket: ""
tlsKey: ""
@@ -114,8 +113,6 @@ userDefaults:
- `indexingInterval`: This is the time in minutes the system waits before checking for filesystem changes. Default: `5`
- `indexing`: This enables or disables indexing. (Note: search will not work without indexing) Default: `true`
- `numImageProcessors`: This is the number of image processors available. Default: `4`
- `socket`: This is the socket configuration.
-26
View File
@@ -1,26 +0,0 @@
{
"root": true,
"env": {
"node": true
},
"extends": [
"plugin:vue/vue3-essential",
"eslint:recommended",
"@vue/eslint-config-typescript"
],
"rules": {
"vue/multi-word-component-names": "off",
"vue/no-mutating-props": [
"error",
{
"shallowOnly": true
}
]
// no-undef is already included in
// @vue/eslint-config-typescript
},
"parserOptions": {
"ecmaVersion": "latest",
"sourceType": "module"
}
}
+16
View File
@@ -0,0 +1,16 @@
env:
browser: true
es2021: true
extends:
- eslint:recommended
- plugin:vue/vue3-essential
parserOptions:
ecmaVersion: latest
sourceType: module
plugins:
- vue
rules:
vue/multi-word-component-names: off
vue/no-reserved-component-names: warn
vue/no-mutating-props: off
vue/no-deprecated-v-bind-sync: warn
+3
View File
@@ -0,0 +1,3 @@
module.exports = {
presets: ["@vue/app"],
};
+47
View File
@@ -0,0 +1,47 @@
{
"name": "filebrowser-frontend",
"version": "2.0.0",
"private": true,
"scripts": {
// vue 3 changes needed
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"fix": "npx vue-cli-service lint",
"watch": "vue-cli-service build --watch",
"lint": "eslint --ext .vue,.js src/",
"lint:fix": "eslint --ext .vue,.js --fix src/",
"format": "prettier --write ."
},
"dependencies": {
"ace-builds": "^1.24.2",
"clipboard": "^2.0.4",
"css-vars-ponyfill": "^2.4.3",
"file-loader": "^6.2.0", // UNNECESSARY IN VITE
X"js-base64": "^2.5.1", // REPLACE WITH EQUIVALENT JS
"lodash.clonedeep": "^4.5.0", // TOO OLD - REPLACE WITH JS
"lodash.throttle": "^4.1.1", // TOO OLD - REPLACE WITH JS
"material-icons": "^1.10.5",
"moment": "^2.29.4", // REPLACE WITH EQUIVALENT JS
"normalize.css": "^8.0.1", // REPLACE WITH EQUIVALENT JS
"noty": "^3.2.0-beta", // REPLACE WITH EQUIVALENT JS
X"pretty-bytes": "^6.0.0", // REPLACE WITH EQUIVALENT JS
"qrcode.vue": "^1.7.0", // UPDATE TO LATEST for VUE3
"utif": "^3.1.0", // SPIKE investigate replacement
"vue": "^2.6.10", // UPDATE to vue 3
"vue-async-computed": "^3.9.0", // REPLACE WITH EQUIVALENT JS
"vue-i18n": "^8.15.3", // REMOVE
"vue-lazyload": "^1.3.3", // REMOVE
"vue-router": "^3.1.3", // UPDATE to vue 3 @vue4 https://www.npmjs.com/package/vue-router
"vue-simple-progress": "^1.1.1", // REPLACE WITH EQUIVALENT JS
"vuex": "^3.1.2", // SPIKE: HOW TO REMOVE
"vuex-router-sync": "^5.0.0", // SPIKE: HOW TO REMOVE
X"whatwg-fetch": "^3.6.2"
},
"devDependencies": {
"@vue/cli-service": "^5.0.8", // REMOVE for VUE3
"compression-webpack-plugin": "^10.0.0", // REPLACE VUE3
"eslint": "^8.51.0",
"eslint-plugin-vue": "^9.17.0",
"vue-template-compiler": "^2.6.10" // REPLACE VUE3
}
}
+8451
View File
File diff suppressed because it is too large. Load diff
+25 -28
View File
@@ -1,21 +1,15 @@
{
"name": "filebrowser-frontend",
"version": "3.0.0",
"version": "2.0.0",
"private": true,
"type": "module",
"engines": {
"npm": ">=7.0.0",
"node": ">=18.0.0"
},
"scripts": {
"dev": "vite dev",
"build": "vite build",
"watch": "vite build --watch",
"typecheck": "vue-tsc -p ./tsconfig.json --noEmit",
"lint": "npm run typecheck && eslint src/",
"lint:fix": "eslint --fix src/",
"format": "prettier --write .",
"test": "playwright test"
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"fix": "npx vue-cli-service lint",
"watch": "vue-cli-service build --watch",
"lint": "eslint --ext .vue,.js src/",
"lint:fix": "eslint --ext .vue,.js --fix src/",
"format": "prettier --write ."
},
"dependencies": {
"ace-builds": "^1.24.2",
@@ -23,22 +17,25 @@
"css-vars-ponyfill": "^2.4.3",
"file-loader": "^6.2.0",
"material-icons": "^1.10.5",
"moment": "^2.29.4",
"normalize.css": "^8.0.1",
"qrcode.vue": "^3.4.1",
"vue": "^3.4.21",
"vue-i18n": "^9.10.2",
"vue-lazyload": "^3.0.0",
"vue-router": "^4.3.0"
"noty": "^3.2.0-beta",
"qrcode.vue": "^1.7.0",
"utif": "^3.1.0",
"vue": "^2.6.10",
"vue-async-computed": "^3.9.0",
"vue-i18n": "^8.15.3",
"vue-lazyload": "^1.3.3",
"vue-router": "^3.1.3",
"vue-simple-progress": "^1.1.1",
"vuex": "^3.1.2",
"vuex-router-sync": "^5.0.0"
},
"devDependencies": {
"@intlify/unplugin-vue-i18n": "^4.0.0",
"@vitejs/plugin-vue": "^5.0.4",
"@vue/eslint-config-typescript": "^13.0.0",
"eslint": "^8.57.0",
"eslint-plugin-prettier": "^5.1.3",
"eslint-plugin-vue": "^9.24.0",
"vite": "^5.2.7",
"vite-plugin-compression2": "^1.0.0",
"vue-tsc": "^2.0.7"
"@vue/cli-service": "^5.0.8",
"compression-webpack-plugin": "^10.0.0",
"eslint": "^8.51.0",
"eslint-plugin-vue": "^9.17.0",
"vue-template-compiler": "^2.6.10"
}
}
+2 -4
View File
@@ -6,7 +6,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
[{[ if .ReCaptcha -]}]
<script src="[{[ .ReCaptchaHost ]}]/recaptcha/api.js?render=explicit" data-vite-ignore></script>
<script src="[{[ .ReCaptchaHost ]}]/recaptcha/api.js?render=explicit"></script>
[{[ end ]}]
<title>[{[ if .Name -]}][{[ .Name ]}][{[ else ]}]File Browser[{[ end ]}]</title>
@@ -121,7 +121,7 @@
<div id="app"></div>
[{[ if .darkMode -]}]
<div id="loading" class="dark-mode">
<div id="loading dark-mode">
<div class="spinner">
<div class="bounce1"></div>
<div class="bounce2"></div>
@@ -136,8 +136,6 @@
<div class="bounce3"></div>
</div>
</div> [{[ end ]}]
<script type="module" src="/src/main.ts"></script>
[{[ if .CSS -]}]
<link rel="stylesheet" href="[{[ .StaticURL ]}]/custom.css" />
[{[ end ]}]
+4 -13
View File
@@ -1,24 +1,15 @@
<template>
<router-view></router-view>
<router-view></router-view>
</template>
<script>
import { onMounted } from 'vue';
import { mutations } from "@/store"; // Import your store's mutations
// eslint-disable-next-line no-undef
__webpack_public_path__ = window.FileBrowser.StaticURL + "/";
export default {
name: "app",
computed: {},
setup() {
onMounted(() => {
mutations.setLoading(false); // Call your mutation or method to set loading to false
// Query the loading element and remove it from the DOM
const loadingDiv = document.getElementById('loading');
if (loadingDiv) {
loadingDiv.remove();
}
});
},
};
</script>
+2 -2
View File
@@ -1,13 +1,13 @@
import { removePrefix } from "./utils";
import { baseURL } from "@/utils/constants";
import { state } from "@/store";
import store from "@/store";
const ssl = window.location.protocol === "https:";
const protocol = ssl ? "wss:" : "ws:";
export default function command(url, command, onmessage, onclose) {
url = removePrefix(url);
url = `${protocol}//${window.location.host}${baseURL}/api/command${url}?auth=${state.jwt}`;
url = `${protocol}//${window.location.host}${baseURL}/api/command${url}?auth=${store.state.jwt}`;
let conn = new window.WebSocket(url);
conn.onopen = () => conn.send(command);
+4 -4
View File
@@ -1,6 +1,6 @@
import { createURL, fetchURL, removePrefix } from "./utils";
import { baseURL } from "@/utils/constants";
import { state } from "@/store";
import store from "@/store";
export async function fetch(url,content=false) {
url = removePrefix(url);
@@ -70,8 +70,8 @@ export function download(format, ...files) {
url += `algo=${format}&`;
}
if (state.jwt) {
url += `auth=${state.jwt}&`;
if (store.state.jwt) {
url += `auth=${store.state.jwt}&`;
}
window.open(url);
@@ -95,7 +95,7 @@ export async function post(url, content = "", overwrite = false, onupload) {
`${baseURL}/api/resources${url}?override=${overwrite}`,
true
);
request.setRequestHeader("X-Auth", state.jwt);
request.setRequestHeader("X-Auth", store.state.jwt);
if (typeof onupload === "function") {
request.upload.onprogress = onupload;
-1
View File
@@ -18,7 +18,6 @@ export async function remove(hash) {
export async function create(url, password = "", expires = "", unit = "hours") {
url = removePrefix(url);
url = `/api/share${url}`;
expires = String(expires);
if (expires !== "") {
url += `?expires=${expires}&unit=${unit}`;
}
+2 -2
View File
@@ -1,6 +1,6 @@
import { fetchURL, fetchJSON } from "@/api/utils";
import { fetchURL, fetchJSON } from "./utils";
export async function getAllUsers() {
export async function getAll() {
return await fetchJSON(`/api/users`, {});
}
+9 -10
View File
@@ -1,8 +1,7 @@
import { state } from "@/store";
import store from "@/store";
import { renew, logout } from "@/utils/auth";
import { baseURL } from "@/utils/constants";
import { encodePath } from "@/utils/url";
import { showError } from "@/notify";
export async function fetchURL(url, opts, auth = true) {
opts = opts || {};
@@ -14,22 +13,21 @@ export async function fetchURL(url, opts, auth = true) {
try {
res = await fetch(`${baseURL}${url}`, {
headers: {
"X-Auth": state.jwt,
"sessionId": state.sessionId,
"userScope": state.user.scope,
"X-Auth": store.state.jwt,
"sessionId": store.state.sessionId,
"userScope": store.state.user.scope,
...headers,
},
...rest,
});
} catch (e) {
console.error(e)
} catch {
const error = new Error("000 No connection");
error.status = res.status;
throw error;
}
if (auth && res.headers.get("X-Renew-Token") === "true") {
await renew(state.jwt);
await renew(store.state.jwt);
}
if (res.status < 200 || res.status > 299) {
@@ -48,16 +46,17 @@ export async function fetchURL(url, opts, auth = true) {
export async function fetchJSON(url, opts) {
const res = await fetchURL(url, opts);
if (res.status === 200) {
return res.json();
} else {
showError("unable to fetch : " + url + "status" + res.status);
throw new Error(res.status);
}
}
export function removePrefix(url) {
url = url.split("/").splice(2).join("/");
if (url === "") url = "/";
if (url[0] !== "/") url = "/" + url;
return url;
@@ -71,7 +70,7 @@ export function createURL(endpoint, params = {}, auth = true) {
const url = new URL(prefix + encodePath(endpoint), origin);
const searchParams = {
...(auth && { auth: state.jwt }),
...(auth && { auth: store.state.jwt }),
...params,
};
+10 -13
View File
@@ -18,8 +18,8 @@
</template>
<script>
import { state, mutations } from "@/store"; // Import mutations as well
import Action from "@/components/header/Action.vue";
import { mapState } from "vuex";
import Action from "@/components/header/Action";
export default {
name: "breadcrumbs",
@@ -28,8 +28,10 @@ export default {
},
props: ["base", "noLink"],
computed: {
...mapState(["req", "user"]),
items() {
const relativePath = state.route.path.replace(this.base, "");
const relativePath = this.$route.path.replace(this.base, "");
let parts = relativePath.split("/");
if (parts[0] === "") {
@@ -74,18 +76,13 @@ export default {
return "router-link";
},
showShare() {
// Ensure user properties are accessed safely
if (state.route.path.startsWith("/share")) {
return false;
if (this.$route.path.startsWith("/share")) {
return;
}
return state.user?.perm && state.user?.perm.share; // Access from state directly
},
},
methods: {
// Example of a method using mutations
updateUserPermissions(newPerms) {
mutations.updateUser({ perm: newPerms })
return this.user.perm.share;
},
},
};
</script>
<style></style>
+3
View File
@@ -36,12 +36,15 @@ export default {
methods: {
setActiveButton(index, label) {
if (label == "Only Folders" && this.activeButton != index) {
console.log("Only Folders && this.activeButton != index");
this.$emit("disableAll");
}
if (label == "Only Folders" && this.activeButton == index) {
console.log("Only Folders && this.activeButton == index");
this.$emit("enableAll");
}
if (label == "Only Files" && this.activeButton != index) {
console.log("Only Files && this.activeButton != index");
this.$emit("enableAll");
}
// If the clicked button is already active, de-select it
-221
View File
@@ -1,221 +0,0 @@
<!-- This component taken directly from vue-simple-progress
since it didnt support Vue 3 but the component itself does
https://raw.githubusercontent.com/dzwillia/vue-simple-progress/master/src/components/Progress.vue -->
<template>
<div>
<div
class="vue-simple-progress-text"
:style="text_style"
v-if="text.length > 0 && textPosition == 'top'"
>
{{ text }}
</div>
<div class="vue-simple-progress" :style="progress_style">
<div
class="vue-simple-progress-text"
:style="text_style"
v-if="text.length > 0 && textPosition == 'middle'"
>
{{ text }}
</div>
<div
style="position: relative; left: -9999px"
:style="text_style"
v-if="text.length > 0 && textPosition == 'inside'"
>
{{ text }}
</div>
<div class="vue-simple-progress-bar" :style="bar_style">
<div :style="text_style" v-if="text.length > 0 && textPosition == 'inside'">
{{ text }}
</div>
</div>
</div>
<div
class="vue-simple-progress-text"
:style="text_style"
v-if="text.length > 0 && textPosition == 'bottom'"
>
{{ text }}
</div>
</div>
</template>
<script>
// We're leaving this untouched as you can read in the beginning
var isNumber = function (n) {
return !isNaN(parseFloat(n)) && isFinite(n);
};
export default {
name: "progress-bar",
props: {
val: {
default: 0,
},
max: {
default: 100,
},
size: {
// either a number (pixel width/height) or 'tiny', 'small',
// 'medium', 'large', 'huge', 'massive' for common sizes
default: 3,
},
"bg-color": {
type: String,
default: "#eee",
},
"bar-color": {
type: String,
default: "#2196f3", // match .blue color to Material Design's 'Blue 500' color
},
"bar-transition": {
type: String,
default: "all 0.5s ease",
},
"bar-border-radius": {
type: Number,
default: 0,
},
spacing: {
type: Number,
default: 4,
},
text: {
type: String,
default: "",
},
"text-align": {
type: String,
default: "center", // 'left', 'right'
},
"text-position": {
type: String,
default: "bottom", // 'bottom', 'top', 'middle', 'inside'
},
"font-size": {
type: Number,
default: 13,
},
"text-fg-color": {
type: String,
default: "#222",
},
},
computed: {
pct() {
var pct = (this.val / this.max) * 100;
pct = pct.toFixed(2);
return Math.min(pct, this.max);
},
size_px() {
switch (this.size) {
case "tiny":
return 2;
case "small":
return 4;
case "medium":
return 8;
case "large":
return 12;
case "big":
return 16;
case "huge":
return 32;
case "massive":
return 64;
}
return isNumber(this.size) ? this.size : 32;
},
text_padding() {
switch (this.size) {
case "tiny":
case "small":
case "medium":
case "large":
case "big":
case "huge":
case "massive":
return Math.min(Math.max(Math.ceil(this.size_px / 8), 3), 12);
}
return isNumber(this.spacing) ? this.spacing : 4;
},
text_font_size() {
switch (this.size) {
case "tiny":
case "small":
case "medium":
case "large":
case "big":
case "huge":
case "massive":
return Math.min(Math.max(Math.ceil(this.size_px * 1.4), 11), 32);
}
return isNumber(this.fontSize) ? this.fontSize : 13;
},
progress_style() {
var style = {
background: this.bgColor,
};
if (this.textPosition == "middle" || this.textPosition == "inside") {
style["position"] = "relative";
style["min-height"] = this.size_px + "px";
style["z-index"] = "-2";
}
if (this.barBorderRadius > 0) {
style["border-radius"] = this.barBorderRadius + "px";
}
return style;
},
bar_style() {
var style = {
background: this.barColor,
width: this.pct + "%",
height: this.size_px + "px",
transition: this.barTransition,
};
if (this.barBorderRadius > 0) {
style["border-radius"] = this.barBorderRadius + "px";
}
if (this.textPosition == "middle" || this.textPosition == "inside") {
style["position"] = "absolute";
style["top"] = "0";
style["height"] = "100%";
(style["min-height"] = this.size_px + "px"), (style["z-index"] = "-1");
}
return style;
},
text_style() {
var style = {
color: this.textFgColor,
"font-size": this.text_font_size + "px",
"text-align": this.textAlign,
};
if (
this.textPosition == "top" ||
this.textPosition == "middle" ||
this.textPosition == "inside"
)
style["padding-bottom"] = this.text_padding + "px";
if (
this.textPosition == "bottom" ||
this.textPosition == "middle" ||
this.textPosition == "inside"
)
style["padding-top"] = this.text_padding + "px";
return style;
},
},
};
</script>
+255 -270
View File
@@ -1,8 +1,10 @@
<template>
<div id="search" @click="open" :class="{ active, ongoing, 'dark-mode': isDarkMode }">
<!-- Search input section -->
<div
id="search"
@click="open"
v-bind:class="{ active, ongoing, 'dark-mode': isDarkMode }"
>
<div id="input">
<!-- Close button visible when search is active -->
<button
v-if="active"
class="action"
@@ -12,9 +14,7 @@
>
<i class="material-icons">close</i>
</button>
<!-- Search icon when search is not active -->
<i v-else class="material-icons">search</i>
<!-- Input field for search -->
<input
class="main-input"
type="text"
@@ -27,12 +27,9 @@
:placeholder="$t('search.search')"
/>
</div>
<!-- Search results for mobile -->
<div v-if="isMobile && active" id="result" :class="{ hidden: !active }" ref="result">
<div id="result-list">
<div class="button" style="width: 100%">Search Context: {{ getContext }}</div>
<!-- List of search results -->
<ul v-show="results.length > 0">
<li
v-for="(s, k) in results"
@@ -53,18 +50,15 @@
</router-link>
</li>
</ul>
<!-- Loading icon when search is ongoing -->
<p v-show="isEmpty && isRunning" id="renew">
<i class="material-icons spin">autorenew</i>
</p>
<!-- Message when no results are found -->
<div v-show="isEmpty && !isRunning">
<div class="searchPrompt" v-show="isEmpty && !isRunning">
<p>{{ noneMessage }}</p>
</div>
</div>
<div v-if="isEmpty">
<!-- Reset filters button -->
<template v-if="isEmpty">
<button
class="mobile-boxes"
v-if="value.length === 0 && !showBoxes"
@@ -72,8 +66,7 @@
>
Reset filters
</button>
<!-- Box types when no search input is present -->
<div v-if="value.length === 0 && showBoxes">
<template v-if="value.length === 0 && showBoxes">
<div class="boxes">
<h3>{{ $t("search.types") }}</h3>
<div>
@@ -91,26 +84,21 @@
</div>
</div>
</div>
</div>
</div>
</template>
</template>
</div>
</div>
<!-- Search results for desktop -->
<div v-show="!isMobile && active" id="result-desktop" ref="result">
<div class="searchContext">Search Context: {{ getContext }}</div>
<div id="result-list">
<div>
<!-- Loading icon when search is ongoing -->
<template>
<p v-show="isEmpty && isRunning" id="renew">
<i class="material-icons spin">autorenew</i>
</p>
<!-- Message when no results are found -->
<div class="searchPrompt" v-show="isEmpty && !isRunning">
<p>{{ noneMessage }}</p>
<div class="helpButton" @click="toggleHelp()">Help</div>
</div>
<!-- Help text section -->
<div class="helpText" v-if="showHelp">
<p>
Search occurs on each character you type (3 character minimum for search
@@ -134,8 +122,7 @@
search times.
</p>
</div>
<div>
<!-- Button groups for filtering search results -->
<template>
<ButtonGroup
:buttons="folderSelect"
@button-clicked="addToTypes"
@@ -149,7 +136,6 @@
@remove-button-clicked="removeFromTypes"
:isDisabled="isTypeSelectDisabled"
/>
<!-- Inputs for filtering by file size -->
<div class="sizeConstraints">
<div class="sizeInputWrapper">
<p>Smaller Than:</p>
@@ -173,9 +159,8 @@
<p>MB</p>
</div>
</div>
</div>
</div>
<!-- List of search results -->
</template>
</template>
<ul v-show="results.length > 0">
<li
v-for="(s, k) in results"
@@ -200,246 +185,6 @@
</div>
</div>
</template>
<script>
import ButtonGroup from "./ButtonGroup.vue";
import { search } from "@/api";
import { getters, mutations, state } from "@/store";
import { showError } from "@/notify";
var boxes = {
folder: { label: "folders", icon: "folder" },
file: { label: "files", icon: "insert_drive_file" },
archive: { label: "archives", icon: "archive" },
image: { label: "images", icon: "photo" },
audio: { label: "audio files", icon: "volume_up" },
video: { label: "videos", icon: "movie" },
doc: { label: "documents", icon: "picture_as_pdf" },
};
export default {
components: {
ButtonGroup,
},
name: "search",
data: function () {
return {
largerThan: "",
smallerThan: "",
noneMessage: "Start typing 3 or more characters to begin searching.",
searchTypes: "",
isTypeSelectDisabled: false,
showHelp: false,
folderSelect: [
{ label: "Only Folders", value: "type:folder" },
{ label: "Only Files", value: "type:file" },
],
typeSelect: [
{ label: "Photos", value: "type:image" },
{ label: "Audio", value: "type:audio" },
{ label: "Videos", value: "type:video" },
{ label: "Documents", value: "type:doc" },
{ label: "Archives", value: "type:archive" },
],
value: "",
width: window.innerWidth,
ongoing: false,
results: [],
reload: false,
scrollable: null,
};
},
watch: {
active(active) {
const resultList = document.getElementById("result-list");
if (!active) {
resultList.classList.remove("active");
return;
}
setTimeout(() => {
resultList.classList.add("active");
}, 100);
},
currentPrompt(val, old) {
this.active = val?.prompt === "search";
if (old?.prompt === "search" && !this.active) {
if (this.reload) {
this.setReload(true);
}
document.body.style.overflow = "auto";
this.ongoing = false;
this.results = [];
this.value = "";
this.active = false;
this.$refs.input.blur();
} else if (this.active) {
this.reload = false;
this.$refs.input.focus();
document.body.style.overflow = "hidden";
}
},
value() {
if (this.results.length) {
this.ongoing = false;
this.results = [];
}
},
},
computed: {
active() {
return getters.currentPromptName() === "search";
},
showOverlay() {
return getters.currentPrompt() !== null && getters.currentPromptName() !== "more";
},
isDarkMode() {
return getters.isDarkMode();
},
showBoxes() {
return this.searchTypes == "";
},
boxes() {
return boxes;
},
isEmpty() {
return this.results.length === 0;
},
text() {
if (this.ongoing) {
return "";
}
return this.value === ""
? this.$t("search.typeToSearch")
: this.$t("search.pressToSearch");
},
isMobile() {
return this.width <= 800;
},
isRunning() {
return this.ongoing;
},
searchHelp() {
return this.showHelp;
},
getContext() {
let path = state.route.path;
path = path.slice(1);
path = "./" + path.substring(path.indexOf("/") + 1);
path = path.replace(/\/+$/, "") + "/";
return path;
},
},
methods: {
handleResize() {
this.width = window.innerWidth;
},
async navigateTo(url) {
mutations.closeHovers();
await this.$nextTick();
setTimeout(() => this.$router.push(url), 10);
},
basePath(str, isDir) {
let parts = str.replace(/(\/$|^\/)/, "").split("/");
if (parts.length <= 1) {
if (isDir) {
return "/";
}
return "";
}
parts.pop();
parts = parts.join("/") + "/";
if (isDir) {
parts = "/" + parts; // fix weird rtl thing
}
return parts;
},
baseName(str) {
let parts = str.replace(/(\/$|^\/)/, "").split("/");
return parts.pop();
},
open() {
mutations.showHover("search");
},
close(event) {
event.stopPropagation();
mutations.closeHovers();
},
keyup(event) {
if (event.keyCode === 27) {
this.close(event);
return;
}
this.results.length === 0;
},
addToTypes(string) {
if (this.searchTypes.includes(string)) {
return true;
}
if (string == null || string == "") {
return false;
}
this.searchTypes = this.searchTypes + string + " ";
},
resetSearchFilters() {
this.searchTypes = "";
},
removeFromTypes(string) {
if (string == null || string == "") {
return false;
}
this.searchTypes = this.searchTypes.replace(string + " ", "");
if (this.isMobile) {
this.$refs.input.focus();
}
},
folderSelectClicked() {
this.isTypeSelectDisabled = true; // Disable the other ButtonGroup
},
resetButtonGroups() {
this.isTypeSelectDisabled = false;
},
async submit(event) {
this.showHelp = false;
event.preventDefault();
if (this.value === "" || this.value.length < 3) {
this.ongoing = false;
this.results = [];
this.noneMessage = "Not enough characters to search (min 3)";
return;
}
let searchTypesFull = this.searchTypes;
if (this.largerThan != "") {
searchTypesFull = searchTypesFull + "type:largerThan=" + this.largerThan + " ";
}
if (this.smallerThan != "") {
searchTypesFull = searchTypesFull + "type:smallerThan=" + this.smallerThan + " ";
}
let path = state.route.path;
this.ongoing = true;
try {
this.results = await search(path, searchTypesFull + this.value);
} catch (error) {
showError(error);
}
this.ongoing = false;
if (this.results.length == 0) {
this.noneMessage = "No results found in indexed search.";
}
},
toggleHelp() {
this.showHelp = !this.showHelp;
},
},
mounted() {
window.addEventListener("resize", this.handleResize);
},
beforeUnmount() {
window.removeEventListener("resize", this.handleResize);
},
};
</script>
<style>
.main-input {
@@ -453,7 +198,6 @@ export default {
color: white;
border-left: 1px solid gray;
border-right: 1px solid gray;
word-wrap: break-word;
}
#result-desktop > #result-list {
@@ -518,7 +262,7 @@ export default {
/* Search */
#search {
background-color: unset !important;
background-color: unset;
z-index: 3;
position: fixed;
top: 0.5em;
@@ -765,3 +509,244 @@ body.rtl #search .boxes h3 {
align-items: center;
}
</style>
<script>
import ButtonGroup from "./ButtonGroup.vue";
import { mapState, mapGetters, mapMutations } from "vuex";
import { search } from "@/api";
import { darkMode } from "@/utils/constants";
var boxes = {
folder: { label: "folders", icon: "folder" },
file: { label: "files", icon: "insert_drive_file" },
archive: { label: "archives", icon: "archive" },
image: { label: "images", icon: "photo" },
audio: { label: "audio files", icon: "volume_up" },
video: { label: "videos", icon: "movie" },
doc: { label: "documents", icon: "picture_as_pdf" },
};
export default {
components: {
ButtonGroup,
},
name: "search",
data: function () {
return {
largerThan: "",
smallerThan: "",
noneMessage: "Start typing 3 or more characters to begin searching.",
searchTypes: "",
isTypeSelectDisabled: false,
showHelp: false,
folderSelect: [
{ label: "Only Folders", value: "type:folder" },
{ label: "Only Files", value: "type:file" },
],
typeSelect: [
{ label: "Photos", value: "type:image" },
{ label: "Audio", value: "type:audio" },
{ label: "Videos", value: "type:video" },
{ label: "Documents", value: "type:doc" },
{ label: "Archives", value: "type:archive" },
],
value: "",
width: window.innerWidth,
active: false,
ongoing: false,
results: [],
reload: false,
scrollable: null,
};
},
watch: {
active(active) {
const resultList = document.getElementById("result-list");
if (!active) {
resultList.classList.remove("active");
return;
}
setTimeout(() => {
resultList.classList.add("active");
}, 100);
},
currentPrompt(val, old) {
this.active = val?.prompt === "search";
if (old?.prompt === "search" && !this.active) {
if (this.reload) {
this.setReload(true);
}
document.body.style.overflow = "auto";
this.ongoing = false;
this.results = [];
this.value = "";
this.active = false;
this.$refs.input.blur();
} else if (this.active) {
this.reload = false;
this.$refs.input.focus();
document.body.style.overflow = "hidden";
}
},
value() {
if (this.results.length) {
this.ongoing = false;
this.results = [];
}
},
},
computed: {
...mapState(["user"]),
...mapGetters(["isListing", "currentPrompt", "currentPromptName"]),
showOverlay: function () {
return this.currentPrompt !== null && this.currentPrompt.prompt !== "more";
},
isDarkMode() {
return this.user && Object.prototype.hasOwnProperty.call(this.user, "darkMode")
? this.user.darkMode
: darkMode;
},
showBoxes() {
return this.searchTypes == "";
},
boxes() {
return boxes;
},
isEmpty() {
return this.results.length === 0;
},
text() {
if (this.ongoing) {
return "";
}
return this.value === ""
? this.$t("search.typeToSearch")
: this.$t("search.pressToSearch");
},
isMobile() {
return this.width <= 800;
},
isRunning() {
return this.ongoing;
},
searchHelp() {
return this.showHelp;
},
getContext() {
let path = this.$route.path;
path = path.slice(1);
path = "./" + path.substring(path.indexOf("/") + 1);
path = path.replace(/\/+$/, "") + "/";
return path;
},
},
mounted() {
window.addEventListener("resize", this.handleResize);
this.handleResize(); // Call this once to set the initial width
},
methods: {
...mapMutations(["showHover", "closeHovers", "setReload"]),
handleResize() {
this.width = window.innerWidth;
},
async navigateTo(url) {
this.closeHovers();
await this.$nextTick();
setTimeout(() => this.$router.push(url), 0);
},
basePath(str, isDir) {
let parts = str.replace(/(\/$|^\/)/, "").split("/");
if (parts.length <= 1) {
if (isDir) {
return "/";
}
return "";
}
parts.pop();
parts = parts.join("/") + "/";
if (isDir) {
parts = "/" + parts; // fix weird rtl thing
}
return parts;
},
baseName(str) {
let parts = str.replace(/(\/$|^\/)/, "").split("/");
return parts.pop();
},
open() {
this.$store.commit("showHover", "search");
},
close(event) {
event.stopPropagation();
this.closeHovers();
},
keyup(event) {
if (event.keyCode === 27) {
this.close(event);
return;
}
this.results.length === 0;
},
addToTypes(string) {
if (this.searchTypes.includes(string)) {
return true;
}
if (string == null || string == "") {
return false;
}
this.searchTypes = this.searchTypes + string + " ";
},
resetSearchFilters() {
this.searchTypes = "";
},
removeFromTypes(string) {
if (string == null || string == "") {
return false;
}
this.searchTypes = this.searchTypes.replace(string + " ", "");
if (this.isMobile) {
this.$refs.input.focus();
}
},
folderSelectClicked() {
this.isTypeSelectDisabled = true; // Disable the other ButtonGroup
},
resetButtonGroups() {
this.isTypeSelectDisabled = false;
},
async submit(event) {
this.showHelp = false;
event.preventDefault();
if (this.value === "" || this.value.length < 3) {
this.ongoing = false;
this.results = [];
this.noneMessage = "Not enough characters to search (min 3)";
return;
}
let searchTypesFull = this.searchTypes;
if (this.largerThan != "") {
searchTypesFull = searchTypesFull + "type:largerThan=" + this.largerThan + " ";
}
if (this.smallerThan != "") {
searchTypesFull = searchTypesFull + "type:smallerThan=" + this.smallerThan + " ";
}
let path = this.$route.path;
this.ongoing = true;
try {
this.results = await search(path, searchTypesFull + this.value);
} catch (error) {
this.$showError(error);
}
this.ongoing = false;
if (this.results.length == 0) {
this.noneMessage = "No results found in indexed search.";
}
},
toggleHelp() {
this.showHelp = !this.showHelp;
},
},
};
</script>
+66 -85
View File
@@ -1,8 +1,6 @@
<template>
<nav :class="{ active, 'dark-mode': isDarkMode }">
<!-- Section for logged-in users -->
<template v-if="isLoggedIn">
<!-- My Files button -->
<template v-if="isLogged">
<button
class="action"
@click="toRoot"
@@ -12,12 +10,9 @@
<i class="material-icons">folder</i>
<span>{{ $t("sidebar.myFiles") }}</span>
</button>
<!-- Buttons visible if user has create permission -->
<div v-if="user.perm?.create">
<!-- New Folder button -->
<div v-if="user.perm.create">
<button
@click="showHover('newDir')"
@click="$store.commit('showHover', 'newDir')"
class="action"
:aria-label="$t('sidebar.newFolder')"
:title="$t('sidebar.newFolder')"
@@ -25,9 +20,8 @@
<i class="material-icons">create_new_folder</i>
<span>{{ $t("sidebar.newFolder") }}</span>
</button>
<!-- New File button -->
<button
@click="showHover('newFile')"
@click="$store.commit('showHover', 'newFile')"
class="action"
:aria-label="$t('sidebar.newFile')"
:title="$t('sidebar.newFile')"
@@ -35,16 +29,12 @@
<i class="material-icons">note_add</i>
<span>{{ $t("sidebar.newFile") }}</span>
</button>
<!-- Upload button -->
<button id="upload-button" @click="upload($event)" class="action">
<i class="material-icons">file_upload</i>
<span>Upload file</span>
</button>
</div>
<!-- Settings and Logout buttons -->
<div>
<!-- Settings button -->
<button
class="action"
@click="toSettings"
@@ -54,7 +44,7 @@
<i class="material-icons">settings_applications</i>
<span>{{ $t("sidebar.settings") }}</span>
</button>
<!-- Logout button -->
<button
v-if="canLogout"
@click="logout"
@@ -68,10 +58,7 @@
</button>
</div>
</template>
<!-- Section for non-logged-in users -->
<template v-else>
<!-- Login button -->
<router-link
class="action"
to="/login"
@@ -81,7 +68,6 @@
<i class="material-icons">exit_to_app</i>
<span>{{ $t("sidebar.login") }}</span>
</router-link>
<!-- Signup button, if signup is enabled -->
<router-link
v-if="signup"
class="action"
@@ -93,9 +79,10 @@
<span>{{ $t("sidebar.signup") }}</span>
</router-link>
</template>
<!-- Credits and usage information section -->
<div class="credits" v-if="isFiles && !disableUsedPercentage && usage">
<div
class="credits"
v-if="$router.currentRoute.path.includes('/files/') && !disableUsedPercentage"
>
<progress-bar :val="usage.usedPercentage" size="medium"></progress-bar>
<span style="text-align: center">{{ usage.usedPercentage }}%</span>
<span>{{ usage.used }} of {{ usage.total }} used</span>
@@ -117,8 +104,8 @@
</div>
</nav>
</template>
<script>
import { mapState, mapGetters } from "vuex";
import * as upload from "@/utils/upload";
import * as auth from "@/utils/auth";
import {
@@ -130,91 +117,84 @@ import {
loginPage,
} from "@/utils/constants";
import { files as api } from "@/api";
import ProgressBar from "@/components/ProgressBar.vue";
import ProgressBar from "vue-simple-progress";
import { getHumanReadableFilesize } from "@/utils/filesizes";
import { state, getters, mutations } from "@/store"; // Import your custom store
import { showError } from "@/notify";
import { darkMode } from "@/utils/constants";
export default {
name: "sidebar",
components: {
ProgressBar,
},
mounted() {
this.updateUsage();
},
computed: {
isFiles() {
return getters.isFiles();
},
user() {
return state.user;
},
...mapState(["user"]),
isDarkMode() {
return getters.isDarkMode();
},
isLoggedIn() {
return getters.isLoggedIn();
},
currentPrompt() {
return getters.currentPrompt();
return this.user && Object.prototype.hasOwnProperty.call(this.user, "darkMode")
? this.user.darkMode
: darkMode;
},
...mapGetters(["isLogged", "currentPrompt"]),
active() {
return getters.currentPromptName() === "sidebar";
return this.currentPrompt?.prompt === "sidebar";
},
signup: () => signup,
version: () => version,
disableExternal: () => disableExternal,
disableUsedPercentage: () => disableUsedPercentage,
canLogout: () => !noAuth && loginPage,
usage: () => state.usage,
},
asyncComputed: {
usage: {
async get() {
let path = this.$route.path.endsWith("/")
? this.$route.path
: this.$route.path + "/";
let usageStats = { used: 0, total: 0, usedPercentage: 0 };
if (this.disableUsedPercentage) {
return usageStats;
}
try {
let usage = await api.usage(path);
usageStats = {
used: getHumanReadableFilesize(usage.used / 1024),
total: getHumanReadableFilesize(usage.total / 1024),
usedPercentage: Math.round((usage.used / usage.total) * 100),
};
} catch (error) {
this.$showError(error);
}
return usageStats;
},
default: { used: "0 B", total: "0 B", usedPercentage: 0 },
shouldUpdate() {
return this.$router.currentRoute.path.includes("/files/");
},
},
},
methods: {
async updateUsage() {
console.log("updating usage");
let path = getters.getRoutePath();
let usageStats = { used: "0 B", total: "0 B", usedPercentage: 0 };
if (this.disableUsedPercentage) {
return usageStats;
}
try {
let usage = await api.usage(path);
usageStats = {
used: getHumanReadableFilesize(usage.used / 1024),
total: getHumanReadableFilesize(usage.total / 1024),
usedPercentage: Math.round((usage.used / usage.total) * 100),
};
} catch (error) {
showError("Error fetching usage:", error);
}
console.log(usageStats);
mutations.setUsage(usageStats);
},
showHover(value) {
return mutations.showHover(value);
},
// Navigate to the root files directory
toRoot() {
this.$router.push({ path: "/files/" }, () => {});
mutations.closeHovers();
this.$store.commit("closeHovers");
},
// Navigate to the settings page
toSettings() {
this.$router.push({ path: "/settings" }, () => {});
mutations.closeHovers();
this.$store.commit("closeHovers");
},
// Show the help overlay
help() {
mutations.showHover("help");
this.$store.commit("showHover", "help");
},
// Handle file upload
upload(event) {
return this.$upload(event);
upload: function () {
if (
typeof window.DataTransferItem !== "undefined" &&
typeof DataTransferItem.prototype.webkitGetAsEntry !== "undefined"
) {
this.$store.commit("showHover", "upload");
} else {
document.getElementById("upload-input").click();
}
},
// Handle files selected for upload
uploadInput(event) {
mutations.closeHovers();
this.$store.commit("closeHovers");
let files = event.currentTarget.files;
let folder_upload =
@@ -227,15 +207,17 @@ export default {
}
}
let path = getters.getRoutePath();
let conflict = upload.checkConflict(files, state.req.items);
let path = this.$route.path.endsWith("/")
? this.$route.path
: this.$route.path + "/";
let conflict = upload.checkConflict(files, this.req.items);
if (conflict) {
mutations.showHover({
name: "replace",
this.$store.commit("showHover", {
prompt: "replace",
confirm: (event) => {
event.preventDefault();
mutations.closeHovers();
this.$store.commit("closeHovers");
upload.handleFiles(files, path, true);
},
});
@@ -245,7 +227,6 @@ export default {
upload.handleFiles(files, path);
},
// Logout the user
logout: auth.logout,
},
};
+21 -38
View File
@@ -11,19 +11,17 @@
@wheel="wheelMove"
>
<img
v-if="!isTiff"
:src="src"
src=""
class="image-ex-img image-ex-img-center"
ref="imgex"
@load="onLoad"
/>
<canvas v-else ref="imgex" class="image-ex-img"></canvas>
</div>
</template>
<script>
import throttle from "@/utils/throttle";
import { showError } from "@/notify";
import UTIF from "utif";
export default {
props: {
src: String,
@@ -57,18 +55,15 @@ export default {
},
maxScale: 4,
minScale: 0.25,
isTiff: false, // Determine if the image is a TIFF
};
},
mounted() {
this.isTiff = this.checkIfTiff(this.src);
if (this.isTiff) {
this.decodeTiff(this.src);
} else {
if (!this.decodeUTIF()) {
this.$refs.imgex.src = this.src;
}
let container = this.$refs.container;
this.classList.forEach((className) => container.classList.add(className));
// set width and height if they are zero
if (getComputedStyle(container).width === "0px") {
container.style.width = "100%";
}
@@ -84,10 +79,7 @@ export default {
},
watch: {
src: function () {
this.isTiff = this.checkIfTiff(this.src);
if (this.isTiff) {
this.decodeTiff(this.src);
} else {
if (!this.decodeUTIF()) {
this.$refs.imgex.src = this.src;
}
@@ -97,29 +89,19 @@ export default {
},
},
methods: {
checkIfTiff(src) {
// Modified from UTIF.replaceIMG
decodeUTIF() {
const sufs = ["tif", "tiff", "dng", "cr2", "nef"];
const suff = src.split(".").pop().toLowerCase();
return sufs.includes(suff);
},
async decodeTiff(src) {
try {
const response = await fetch(src);
if (!response.ok) {
throw new Error("Network response was not ok");
}
const blob = await response.blob(); // Convert response to a blob
const imgex = this.$refs.imgex;
if (imgex) {
// Create a URL for the blob and set it as the image source
imgex.src = URL.createObjectURL(blob);
imgex.onload = () => URL.revokeObjectURL(imgex.src); // Clean up URL object after loading
}
} catch (error) {
showError("Error decoding TIFF");
console.error("Error decoding TIFF:", error);
}
let suff = document.location.pathname.split(".").pop().toLowerCase();
if (sufs.indexOf(suff) == -1) return false;
let xhr = new XMLHttpRequest();
UTIF._xhrs.push(xhr);
UTIF._imgs.push(this.$refs.imgex);
xhr.open("GET", this.src);
xhr.responseType = "arraybuffer";
xhr.onload = UTIF._imgLoaded;
xhr.send();
return true;
},
onLoad() {
let img = this.$refs.imgex;
@@ -164,7 +146,9 @@ export default {
let container = this.$refs.container;
let img = this.$refs.imgex;
this.position.center.x = Math.floor((container.clientWidth - img.clientWidth) / 2);
this.position.center.x = Math.floor(
(container.clientWidth - img.clientWidth) / 2
);
this.position.center.y = Math.floor(
(container.clientHeight - img.clientHeight) / 2
);
@@ -291,7 +275,6 @@ export default {
},
};
</script>
<style>
.image-ex-container {
margin: auto;
+55 -65
View File
@@ -1,6 +1,6 @@
<template>
<div
:class="{ activebutton: this.isMaximized && this.isSelected }"
:class="{ activebutton: this.isMaximized && this.isSelected}"
class="item"
role="button"
tabindex="0"
@@ -18,17 +18,13 @@
@click="toggleClick"
:class="{ activetitle: this.isMaximized && this.isSelected }"
>
<img
v-if="readOnly === undefined && type === 'image' && isThumbsEnabled && isInView"
v-lazy="thumbnailUrl"
:class="{ activeimg: this.isMaximized && this.isSelected }"
ref="thumbnail"
/>
<i
:class="{ iconActive: this.isMaximized && this.isSelected }"
v-else
class="material-icons"
></i>
<img
v-if="readOnly === undefined && type === 'image' && isThumbsEnabled && isInView"
v-lazy="thumbnailUrl"
:class="{ activeimg: this.isMaximized && this.isSelected }"
ref="thumbnail"
/>
<i :class="{ iconActive: this.isMaximized && this.isSelected }" v-else class="material-icons"></i>
</div>
<div :class="{ activecontent: this.isMaximized && this.isSelected }">
@@ -68,10 +64,10 @@
<script>
import { enableThumbs } from "@/utils/constants";
import { getHumanReadableFilesize } from "@/utils/filesizes";
import { fromNow } from "@/utils/moment";
import { mapMutations, mapGetters, mapState } from "vuex";
import moment from "moment";
import { files as api } from "@/api";
import * as upload from "@/utils/upload";
import { state, getters, mutations } from "@/store"; // Import your custom store
export default {
name: "item",
@@ -94,44 +90,32 @@ export default {
"path",
],
computed: {
user() {
return state.user;
},
selected() {
return state.selected;
},
req() {
return state.req;
},
jwt() {
return state.jwt;
},
selectedCount() {
return getters.selectedCount();
},
...mapState(["user", "selected", "req", "jwt"]),
...mapGetters(["selectedCount"]),
isClicked() {
if (state.user.singleClick || !this.allowedView) {
if (this.user.singleClick || !this.allowedView ) {
return false;
}
// Assuming toggleClick returns a boolean value
return !this.isMaximized;
},
allowedView() {
return state.user.viewMode != "gallery" && state.user.viewMode != "normal";
return this.user.viewMode != "gallery" && this.user.viewMode != "normal"
},
singleClick() {
return this.readOnly == undefined && state.user.singleClick;
return this.readOnly == undefined && this.user.singleClick;
},
isSelected() {
return this.selected.indexOf(this.index) !== -1;
},
isDraggable() {
return this.readOnly == undefined && state.user.perm?.rename;
return this.readOnly == undefined && this.user.perm.rename;
},
canDrop() {
if (!this.isDir || this.readOnly !== undefined) return false;
for (let i of this.selected) {
if (state.req.items[i].url === this.url) {
if (this.req.items[i].url === this.url) {
return false;
}
}
@@ -139,12 +123,12 @@ export default {
return true;
},
thumbnailUrl() {
let path = state.req.path;
if (state.req.path == "/") {
path = "";
let path = this.req.path
if (this.req.path == "/") {
path = ""
}
const file = {
path: path + "/" + this.name,
path: path +"/"+this.name,
modified: this.modified,
};
@@ -160,7 +144,7 @@ export default {
mounted() {
const observer = new IntersectionObserver(this.handleIntersect, {
root: null,
rootMargin: "0px",
rootMargin: '0px',
threshold: 0.5, // Adjust threshold as needed
});
@@ -172,7 +156,7 @@ export default {
},
methods: {
handleIntersect(entries, observer) {
entries.forEach((entry) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
this.isThumbnailInView = true;
// Stop observing once thumbnail is in view
@@ -183,26 +167,27 @@ export default {
toggleClick() {
this.isMaximized = this.isClicked;
},
...mapMutations(["addSelected", "removeSelected", "resetSelected"]),
humanSize: function () {
return this.type == "invalid_link"
? "invalid link"
: getHumanReadableFilesize(this.size);
},
humanTime: function () {
if (this.readOnly == undefined && state.user.dateFormat) {
return fromNow(this.modified, state.user.locale).format("L LT");
if (this.readOnly == undefined && this.user.dateFormat) {
return moment(this.modified).format("L LT");
}
return fromNow(this.modified, state.user.locale);
return moment(this.modified).fromNow();
},
dragStart: function () {
if (getters.selectedCount() === 0) {
mutations.addSelected(this.index);
if (this.selectedCount === 0) {
this.addSelected(this.index);
return;
}
if (!this.isSelected) {
mutations.resetSelected();
mutations.addSelected(this.index);
this.resetSelected();
this.addSelected(this.index);
}
},
dragOver: function (event) {
@@ -223,7 +208,7 @@ export default {
if (!this.canDrop) return;
event.preventDefault();
if (getters.selectedCount() === 0) return;
if (this.selectedCount === 0) return;
let el = event.target;
for (let i = 0; i < 5; i++) {
@@ -236,9 +221,9 @@ export default {
for (let i of this.selected) {
items.push({
from: state.req.items[i].url,
to: this.url + encodeURIComponent(state.req.items[i].name),
name: state.req.items[i].name,
from: this.req.items[i].url,
to: this.url + encodeURIComponent(this.req.items[i].name),
name: this.req.items[i].name,
});
}
@@ -250,9 +235,9 @@ export default {
api
.move(items, overwrite, rename)
.then(() => {
mutations.setReload(true);
this.$store.commit("setReload", true);
})
.catch(showError);
.catch(this.$showError);
};
let conflict = upload.checkConflict(items, baseItems);
@@ -261,14 +246,14 @@ export default {
let rename = false;
if (conflict) {
mutations.showHover({
name: "replace-rename",
this.$store.commit("showHover", {
prompt: "replace-rename",
confirm: (event, option) => {
overwrite = option == "overwrite";
rename = option == "rename";
event.preventDefault();
mutations.closeHovers();
this.$store.commit("closeHovers");
action(overwrite, rename);
},
});
@@ -279,11 +264,11 @@ export default {
action(overwrite, rename);
},
itemClick: function (event) {
if (this.singleClick && !state.multiple) this.open();
if (this.singleClick && !this.$store.state.multiple) this.open();
else this.click(event);
},
click: function (event) {
if (!this.singleClick && getters.selectedCount() !== 0) event.preventDefault();
if (!this.singleClick && this.selectedCount !== 0) event.preventDefault();
setTimeout(() => {
this.touches = 0;
@@ -294,8 +279,8 @@ export default {
this.open();
}
if (state.selected.indexOf(this.index) !== -1) {
mutations.removeSelected(this.index);
if (this.$store.state.selected.indexOf(this.index) !== -1) {
this.removeSelected(this.index);
return;
}
@@ -312,16 +297,21 @@ export default {
}
for (; fi <= la; fi++) {
if (state.selected.indexOf(fi) == -1) {
mutations.addSelected(fi);
if (this.$store.state.selected.indexOf(fi) == -1) {
this.addSelected(fi);
}
}
return;
}
if (!this.singleClick && !event.ctrlKey && !event.metaKey && !state.multiple)
mutations.resetSelected();
mutations.addSelected(this.index);
if (
!this.singleClick &&
!event.ctrlKey &&
!event.metaKey &&
!this.$store.state.multiple
)
this.resetSelected();
this.addSelected(this.index);
},
open: function () {
this.$router.push({ path: this.url });
+1 -3
View File
@@ -7,15 +7,13 @@
</template>
<script>
import { mutations } from "@/store"; // Import your custom store
export default {
name: "action",
props: ["icon", "label", "counter", "show"],
methods: {
action: function () {
if (this.show) {
mutations.showHover(this.show);
this.$store.commit("showHover", this.show);
}
this.$emit("action");
},
+15 -22
View File
@@ -27,7 +27,7 @@
<div>
<button
class="button button--flat button--grey"
@click="closeHovers"
@click="$store.commit('closeHovers')"
:aria-label="$t('buttons.cancel')"
:title="$t('buttons.cancel')"
>
@@ -47,12 +47,11 @@
</template>
<script>
import { mutations, state } from "@/store";
import { mapState } from "vuex";
import FileList from "./FileList.vue";
import { files as api } from "@/api";
import buttons from "@/utils/buttons";
import * as upload from "@/utils/upload";
import { showError } from "@/notify";
export default {
name: "copy",
@@ -63,14 +62,7 @@ export default {
dest: null,
};
},
computed: {
user() {
return state.user;
},
closeHovers() {
return mutations.closeHovers();
},
},
computed: mapState(["req", "selected", "user"]),
methods: {
copy: async function (event) {
event.preventDefault();
@@ -79,9 +71,9 @@ export default {
// Create a new promise for each file.
for (let item of this.selected) {
items.push({
from: store.req.items[item].url,
to: this.dest + encodeURIComponent(store.req.items[item].name),
name: store.req.items[item].name,
from: this.req.items[item].url,
to: this.dest + encodeURIComponent(this.req.items[item].name),
name: this.req.items[item].name,
});
}
@@ -93,8 +85,9 @@ export default {
.then(() => {
buttons.success("copy");
if (state.route.path === this.dest) {
mutations.setReload(true);
if (this.$route.path === this.dest) {
this.$store.commit("setReload", true);
return;
}
@@ -102,12 +95,12 @@ export default {
})
.catch((e) => {
buttons.done("copy");
showError(e);
this.$showError(e);
});
};
if (state.route.path === this.dest) {
mutations.closeHovers();
if (this.$route.path === this.dest) {
this.$store.commit("closeHovers");
action(false, true);
return;
@@ -120,14 +113,14 @@ export default {
let rename = false;
if (conflict) {
mutations.showHover({
name: "replace-rename",
this.$store.commit("showHover", {
prompt: "replace-rename",
confirm: (event, option) => {
overwrite = option == "overwrite";
rename = option == "rename";
event.preventDefault();
mutations.closeHovers();
this.$store.commit("closeHovers");
action(overwrite, rename);
},
});
+13 -23
View File
@@ -10,7 +10,7 @@
</div>
<div class="card-action">
<button
@click="closeHovers"
@click="$store.commit('closeHovers')"
class="button button--flat button--grey"
:aria-label="$t('buttons.cancel')"
:title="$t('buttons.cancel')"
@@ -30,34 +30,24 @@
</template>
<script>
import { mapGetters, mapMutations, mapState } from "vuex";
import { files as api } from "@/api";
import buttons from "@/utils/buttons";
import { state, getters, mutations } from "@/store";
import { showError } from "@/notify";
export default {
name: "delete",
computed: {
isListing() {
return getters.isListing();
},
selectedCount() {
return getters.selectedCount();
},
currentPrompt() {
return getters.currentPrompt();
},
...mapGetters(["isListing", "selectedCount", "currentPrompt"]),
...mapState(["req", "selected"]),
},
methods: {
closeHovers() {
mutations.closeHovers();
},
async submit() {
...mapMutations(["closeHovers"]),
submit: async function () {
buttons.loading("delete");
try {
if (!this.isListing) {
await api.remove(state.route.path);
await api.remove(this.$route.path);
buttons.success("delete");
this.currentPrompt?.confirm();
@@ -67,22 +57,22 @@ export default {
this.closeHovers();
if (getters.selectedCount() === 0) {
if (this.selectedCount === 0) {
return;
}
let promises = [];
for (let index of state.selected) {
promises.push(api.remove(state.req.items[index].url));
for (let index of this.selected) {
promises.push(api.remove(this.req.items[index].url));
}
await Promise.all(promises);
buttons.success("delete");
mutations.setReload(true); // Handle reload as needed
this.$store.commit("setReload", true);
} catch (e) {
buttons.done("delete");
showError(e);
if (this.isListing) mutations.setReload(true); // Handle reload as needed
this.$showError(e);
if (this.isListing) this.$store.commit("setReload", true);
}
},
},
@@ -1,89 +0,0 @@
<template>
<div class="card floating">
<div class="card-content">
<p>Are you sure you want to delete this user?</p>
</div>
<div class="card-action">
<button
class="button button--flat button--grey"
@click="closeHovers"
v-focus
:aria-label="$t('buttons.cancel')"
:title="$t('buttons.cancel')"
>
{{ $t("buttons.cancel") }}
</button>
<button class="button button--flat" @click="deleteUser">
{{ $t("buttons.delete") }}
</button>
</div>
</div>
</template>
<script>
import { users as api } from "@/api";
import { showSuccess,showError } from "@/notify";
import buttons from "@/utils/buttons";
import { state, mutations, getters } from "@/store";
export default {
name: "delete",
computed: {
currentPrompt() {
return getters.currentPrompt();
},
user() {
return this.currentPrompt?.props?.user;
},
},
methods: {
async deleteUser(event) {
event.preventDefault();
try {
await api.remove(this.user.id);
this.$router.push({ path: "/settings/users" });
showSuccess(this.$t("settings.userDeleted"));
} catch (e) {
e.message === "403"
? showError(this.$t("errors.forbidden"), false)
: showError(e);
}
},
closeHovers() {
mutations.closeHovers();
},
submit: async function () {
buttons.loading("delete");
try {
if (!this.isListing) {
await api.remove(this.$route.path);
buttons.success("delete");
this.currentPrompt?.confirm();
this.closeHovers();
return;
}
this.closeHovers();
if (getters.selectedCount() === 0) {
return;
}
let promises = [];
for (let index of this.selected) {
promises.push(api.remove(state.req.items[index].url));
}
await Promise.all(promises);
buttons.success("delete");
mutations.setReload(true); // Handle reload as needed
} catch (e) {
buttons.done("delete");
showError(e);
if (this.isListing) mutations.setReload(true); // Handle reload as needed
}
},
},
};
</script>
+4 -6
View File
@@ -3,6 +3,7 @@
<div class="card-title">
<h2>{{ $t("prompts.download") }}</h2>
</div>
<div class="card-content">
<p>{{ $t("prompts.downloadMessage") }}</p>
@@ -20,7 +21,7 @@
</template>
<script>
import { getters } from "@/store"; // Import your custom store
import { mapGetters } from "vuex";
export default {
name: "download",
@@ -38,9 +39,6 @@ export default {
};
},
computed: {
currentPrompt() {
return getters.currentPrompt();
},
},
};
...mapGetters(["currentPrompt"]),
},};
</script>
+8 -8
View File
@@ -21,10 +21,9 @@
</template>
<script>
import { state, mutations } from "@/store";
import { mapState } from "vuex";
import url from "@/utils/url";
import { files } from "@/api";
import { showError } from "@/notify";
export default {
name: "file-list",
@@ -40,12 +39,13 @@ export default {
};
},
computed: {
...mapState(["req", "user"]),
nav() {
return decodeURIComponent(this.current);
},
},
mounted() {
this.fillOptions(state.req);
this.fillOptions(this.req);
},
methods: {
fillOptions(req) {
@@ -86,7 +86,7 @@ export default {
// content.
let uri = event.currentTarget.dataset.url;
files.fetch(uri).then(this.fillOptions).catch(showError);
files.fetch(uri).then(this.fillOptions).catch(this.$showError);
},
touchstart(event) {
let url = event.currentTarget.dataset.url;
@@ -114,7 +114,7 @@ export default {
}
},
itemClick: function (event) {
if (state.user.singleClick) this.next(event);
if (this.user.singleClick) this.next(event);
else this.select(event);
},
select: function (event) {
@@ -130,13 +130,13 @@ export default {
this.$emit("update:selected", this.selected);
},
createDir: async function () {
mutations.showHover({
name: "newDir",
this.$store.commit("showHover", {
prompt: "newDir",
action: null,
confirm: null,
props: {
redirect: false,
base: this.current === state.route.path ? null : this.current,
base: this.current === this.$route.path ? null : this.current,
},
});
},
+2 -11
View File
@@ -21,7 +21,7 @@
<div class="card-action">
<button
type="submit"
@click="closeHovers"
@click="$store.commit('closeHovers')"
class="button button--flat"
:aria-label="$t('buttons.ok')"
:title="$t('buttons.ok')"
@@ -33,14 +33,5 @@
</template>
<script>
import { mutations } from "@/store"; // Import the mutations
export default {
name: "help",
computed: {
closeHovers() {
return mutations.closeHovers; // Return the closeHovers mutation
},
},
};
export default { name: "help" };
</script>
+45 -51
View File
@@ -33,25 +33,33 @@
<p>
<strong>MD5: </strong
><code
><a @click="checksum($event, 'md5')">{{ $t("prompts.show") }}</a></code
><a @click="checksum($event, 'md5')">{{
$t("prompts.show")
}}</a></code
>
</p>
<p>
<strong>SHA1: </strong
><code
><a @click="checksum($event, 'sha1')">{{ $t("prompts.show") }}</a></code
><a @click="checksum($event, 'sha1')">{{
$t("prompts.show")
}}</a></code
>
</p>
<p>
<strong>SHA256: </strong
><code
><a @click="checksum($event, 'sha256')">{{ $t("prompts.show") }}</a></code
><a @click="checksum($event, 'sha256')">{{
$t("prompts.show")
}}</a></code
>
</p>
<p>
<strong>SHA512: </strong
><code
><a @click="checksum($event, 'sha512')">{{ $t("prompts.show") }}</a></code
><a @click="checksum($event, 'sha512')">{{
$t("prompts.show")
}}</a></code
>
</p>
</template>
@@ -60,7 +68,7 @@
<div class="card-action">
<button
type="submit"
@click="closeHovers"
@click="$store.commit('closeHovers')"
class="button button--flat"
:aria-label="$t('buttons.ok')"
:title="$t('buttons.ok')"
@@ -70,87 +78,73 @@
</div>
</div>
</template>
<script>
import { getHumanReadableFilesize } from "@/utils/filesizes";
import { formatTimestamp } from "@/utils/moment";
import { mapState, mapGetters } from "vuex";
import moment from "moment";
import { files as api } from "@/api";
import { state, getters, mutations } from "@/store"; // Import your custom store
import { showError } from "@/notify";
export default {
name: "info",
computed: {
closeHovers() {
return mutations.closeHovers;
},
req() {
return state.req;
},
selected() {
return state.selected;
},
selectedCount() {
return getters.selectedCount();
},
isListing() {
return getters.isListing();
},
humanSize() {
if (getters.selectedCount() === 0 || !this.isListing) {
return getHumanReadableFilesize(state.req.size);
...mapState(["req", "selected"]),
...mapGetters(["selectedCount", "isListing"]),
humanSize: function () {
if (this.selectedCount === 0 || !this.isListing) {
return getHumanReadableFilesize(this.req.size);
}
let sum = 0;
for (let selected of this.selected) {
sum += state.req.items[selected].size;
sum += this.req.items[selected].size;
}
return getHumanReadableFilesize(sum);
},
humanTime() {
if (getters.selectedCount() === 0) {
return formatTimestamp(state.req.modified, state.user.locale);
humanTime: function () {
if (this.selectedCount === 0) {
return moment(this.req.modified).fromNow();
}
return formatTimestamp(
state.req.items[this.selected[0]].modified,
state.user.locale
);
return moment(this.req.items[this.selected[0]].modified).fromNow();
},
modTime() {
return new Date(Date.parse(state.req.modified)).toLocaleString();
modTime: function () {
return new Date(Date.parse(this.req.modified)).toLocaleString();
},
name() {
return getters.selectedCount() === 0
? state.req.name
: state.req.items[this.selected[0]].name;
name: function () {
return this.selectedCount === 0
? this.req.name
: this.req.items[this.selected[0]].name;
},
dir() {
dir: function () {
return (
getters.selectedCount() > 1 ||
(getters.selectedCount() === 0
? state.req.isDir
: state.req.items[this.selected[0]].isDir)
this.selectedCount > 1 ||
(this.selectedCount === 0
? this.req.isDir
: this.req.items[this.selected[0]].isDir)
);
},
},
methods: {
async checksum(event, algo) {
checksum: async function (event, algo) {
event.preventDefault();
let link;
if (getters.selectedCount()) {
link = state.req.items[this.selected[0]].url;
if (this.selectedCount) {
link = this.req.items[this.selected[0]].url;
} else {
link = state.route.path;
link = this.$route.path;
}
try {
const hash = await api.checksum(link, algo);
event.target.innerHTML = hash;
// eslint-disable-next-line
event.target.innerHTML = hash
} catch (e) {
showError(e);
this.$showError(e);
}
},
},
+12 -21
View File
@@ -26,7 +26,7 @@
<div>
<button
class="button button--flat button--grey"
@click="closeHovers"
@click="$store.commit('closeHovers')"
:aria-label="$t('buttons.cancel')"
:title="$t('buttons.cancel')"
>
@@ -47,12 +47,11 @@
</template>
<script>
import { mutations, state } from "@/store";
import { mapState } from "vuex";
import FileList from "./FileList.vue";
import { files as api } from "@/api";
import buttons from "@/utils/buttons";
import * as upload from "@/utils/upload";
import { showError } from "@/notify";
export default {
name: "move",
@@ -63,39 +62,32 @@ export default {
dest: null,
};
},
computed: {
user() {
return state.user;
},
closeHovers() {
return mutations.closeHovers()
},
},
computed: mapState(["req", "selected", "user"]),
methods: {
move: async function (event) {
event.preventDefault();
let items = [];
for (let item of state.selected) {
for (let item of this.selected) {
items.push({
from: state.req.items[item].url,
to: this.dest + encodeURIComponent(state.req.items[item].name),
name: state.req.items[item].name,
from: this.req.items[item].url,
to: this.dest + encodeURIComponent(this.req.items[item].name),
name: this.req.items[item].name,
});
}
let action = async (overwrite, rename) => {
buttons.loading("move");
await api
.move(items, overwrite, rename)
.then(() => {
buttons.success("move");
this.$router.push({ path: this.dest });
mutations.setReload(true)
})
.catch((e) => {
buttons.done("move");
showError(e);
this.$showError(e);
});
};
@@ -106,16 +98,15 @@ export default {
let rename = false;
if (conflict) {
mutations.showHover({
name: "replace-rename",
this.$store.commit("showHover", {
prompt: "replace-rename",
confirm: (event, option) => {
overwrite = option == "overwrite";
rename = option == "rename";
event.preventDefault();
mutations.closeHovers();
this.$store.commit("closeHovers");
action(overwrite, rename);
mutations.setReload(true)
},
});
+11 -19
View File
@@ -18,7 +18,7 @@
<div class="card-action">
<button
class="button button--flat button--grey"
@click="closeHovers"
@click="$store.commit('closeHovers')"
:aria-label="$t('buttons.cancel')"
:title="$t('buttons.cancel')"
>
@@ -35,11 +35,11 @@
</div>
</div>
</template>
<script>
import { mapGetters } from "vuex";
import { files as api } from "@/api";
import url from "@/utils/url";
import { getters, mutations, state } from "@/store"; // Import your custom store
import { showError } from "@/notify";
export default {
name: "new-dir",
@@ -53,31 +53,23 @@ export default {
default: null,
},
},
data() {
data: function () {
return {
name: "",
};
},
computed: {
isFiles() {
return getters.isFiles();
},
isListing() {
return getters.isListing();
},
...mapGetters(["isFiles", "isListing"]),
},
methods: {
closeHovers() {
return mutations.closeHovers();
},
async submit(event) {
submit: async function (event) {
event.preventDefault();
if (this.name === "") return;
if (this.new === "") return;
// Build the path of the new directory.
let uri;
if (this.base) uri = this.base;
else if (getters.isFiles()) uri = state.route.path + "/";
else if (this.isFiles) uri = this.$route.path + "/";
else uri = "/";
if (!this.isListing) {
@@ -93,13 +85,13 @@ export default {
this.$router.push({ path: uri });
} else if (!this.base) {
const res = await api.fetch(url.removeLastDir(uri) + "/");
mutations.updateRequest(res);
this.$store.commit("updateRequest", res);
}
} catch (e) {
showError(e);
this.$showError(e);
}
mutations.closeHovers();
this.$store.commit("closeHovers");
},
},
};
+12 -19
View File
@@ -18,7 +18,7 @@
<div class="card-action">
<button
class="button button--flat button--grey"
@click="closeHovers"
@click="$store.commit('closeHovers')"
:aria-label="$t('buttons.cancel')"
:title="$t('buttons.cancel')"
>
@@ -35,36 +35,29 @@
</div>
</div>
</template>
<script>
import { state } from "@/store";
import { mapGetters } from "vuex";
import { files as api } from "@/api";
import url from "@/utils/url";
import { getters, mutations } from "@/store"; // Import your custom store
export default {
name: "new-file",
data() {
data: function () {
return {
name: "",
};
},
computed: {
isFiles() {
return getters.isFiles();
},
isListing() {
return getters.isListing();
},
closeHovers() {
return mutations.closeHovers;
},
...mapGetters(["isFiles", "isListing"]),
},
methods: {
async submit(event) {
submit: async function (event) {
event.preventDefault();
if (this.name === "") return;
// Build the path of the new file.
let uri = getters.isFiles() ? state.route.path + "/" : "/";
if (this.new === "") return;
// Build the path of the new directory.
let uri = this.isFiles ? this.$route.path + "/" : "/";
if (!this.isListing) {
uri = url.removeLastDir(uri) + "/";
@@ -77,10 +70,10 @@ export default {
await api.post(uri);
this.$router.push({ path: uri });
} catch (e) {
showError(e);
this.$showError(e);
}
mutations.closeHovers();
this.$store.commit("closeHovers");
},
},
};
+14 -31
View File
@@ -5,7 +5,8 @@
:ref="currentPromptName"
:is="currentPromptName"
v-bind="currentPrompt.props"
/>
>
</component>
</div>
</template>
@@ -24,10 +25,9 @@ import ReplaceRename from "./ReplaceRename.vue";
import Share from "./Share.vue";
import Upload from "./Upload.vue";
import ShareDelete from "./ShareDelete.vue";
import DeleteUser from "./DeleteUser.vue";
import Sidebar from "../Sidebar.vue";
import { mapGetters, mapState } from "vuex";
import buttons from "@/utils/buttons";
import { state, getters, mutations } from "@/store"; // Import your custom store
export default {
name: "prompts",
@@ -47,33 +47,31 @@ export default {
Upload,
ShareDelete,
Sidebar,
DeleteUser,
},
data() {
data: function () {
return {
pluginData: {
buttons,
store: state, // Directly use state
store: this.$store,
router: this.$router,
},
};
},
created() {
window.addEventListener("keydown", (event) => {
let currentPrompt = getters.currentPrompt();
if (!currentPrompt) return;
if (this.currentPrompt == null) return;
let prompt = this.$refs[currentPrompt.name];
let prompt = this.$refs.currentComponent;
// Esc!
if (event.keyCode === 27) {
event.stopImmediatePropagation();
mutations.closeHovers();
this.$store.commit("closeHovers");
}
// Enter
if (event.keyCode === 13) {
switch (currentPrompt.name) {
if (event.keyCode == 13) {
switch (this.currentPrompt.prompt) {
case "delete":
prompt.submit();
break;
@@ -91,25 +89,10 @@ export default {
});
},
computed: {
currentPromptName() {
if (getters.currentPromptName() == null) {
return "";
}
return getters.currentPromptName();
},
currentPrompt() {
if (getters.currentPrompt() == null) {
return {
props: {},
};
}
return getters.currentPrompt();
},
plugins() {
return state.plugins;
},
showOverlay() {
return getters.currentPromptName() !== "more";
...mapState(["plugins"]),
...mapGetters(["currentPrompt", "currentPromptName"]),
showOverlay: function () {
return this.currentPrompt !== null && this.currentPrompt.prompt !== "more";
},
},
methods: {},
+21 -31
View File
@@ -21,7 +21,7 @@
<div class="card-action">
<button
class="button button--flat button--grey"
@click="closeHovers"
@click="$store.commit('closeHovers')"
:aria-label="$t('buttons.cancel')"
:title="$t('buttons.cancel')"
>
@@ -39,14 +39,15 @@
</div>
</div>
</template>
<script>
import { mapState, mapGetters } from "vuex";
import url from "@/utils/url";
import { files as api } from "@/api";
import { state, getters, mutations } from "@/store";
export default {
name: "rename",
data() {
data: function () {
return {
name: "",
};
@@ -55,48 +56,37 @@ export default {
this.name = this.oldName();
},
computed: {
req() {
return state.req;
},
selected() {
return state.selected;
},
selectedCount() {
return state.selectedCount;
},
isListing() {
return getters.isListing();
},
closeHovers() {
return mutations.closeHovers;
},
...mapState(["req", "selected", "selectedCount"]),
...mapGetters(["isListing"]),
},
methods: {
cancel() {
mutations.closeHovers();
cancel: function () {
this.$store.commit("closeHovers");
},
oldName() {
oldName: function () {
if (!this.isListing) {
return state.req.name;
return this.req.name;
}
if (getters.selectedCount() === 0 || getters.selectedCount() > 1) {
if (this.selectedCount === 0 || this.selectedCount > 1) {
// This shouldn't happen.
return;
}
return state.req.items[this.selected[0]].name;
return this.req.items[this.selected[0]].name;
},
async submit() {
submit: async function () {
let oldLink = "";
let newLink = "";
if (!this.isListing) {
oldLink = state.req.url;
oldLink = this.req.url;
} else {
oldLink = state.req.items[this.selected[0]].url;
oldLink = this.req.items[this.selected[0]].url;
}
newLink = url.removeLastDir(oldLink) + "/" + encodeURIComponent(this.name);
newLink =
url.removeLastDir(oldLink) + "/" + encodeURIComponent(this.name);
try {
await api.move([{ from: oldLink, to: newLink }]);
@@ -105,12 +95,12 @@ export default {
return;
}
mutations.setReload(true);
this.$store.commit("setReload", true);
} catch (e) {
showError(e);
this.$showError(e);
}
mutations.closeHovers();
this.$store.commit("closeHovers");
},
},
};
+3 -6
View File
@@ -28,15 +28,12 @@
</div>
</div>
</template>
<script>
import { getters } from "@/store"; // Import your custom store
import { mapGetters } from "vuex";
export default {
name: "replace",
computed: {
currentPrompt() {
return getters.currentPrompt(); // Access the getter directly from the store
},
},
computed: mapGetters(["currentPrompt"]),
};
</script>
@@ -38,14 +38,9 @@
</template>
<script>
import { getters } from "@/store"; // Import your custom store
import { mapGetters } from "vuex";
export default {
name: "replace-rename",
computed: {
currentPrompt() {
return getters.currentPrompt(); // Access the getter directly from the store
},
},
computed: mapGetters(["currentPrompt"]),
};
</script>
+27 -41
View File
@@ -58,7 +58,7 @@
<div class="card-action">
<button
class="button button--flat button--grey"
@click="closeHovers"
@click="$store.commit('closeHovers')"
:aria-label="$t('buttons.close')"
:title="$t('buttons.close')"
>
@@ -119,16 +119,16 @@
</template>
</div>
</template>
<script>
import { showSuccess, showError } from "@/notify";
import { state, getters, mutations } from "@/store";
import { mapState, mapGetters } from "vuex";
import { share as api, pub as pub_api } from "@/api";
import { fromNow } from "@/utils/moment";
import moment from "moment";
import Clipboard from "clipboard";
export default {
name: "share",
data() {
data: function () {
return {
time: "",
unit: "hours",
@@ -139,35 +139,22 @@ export default {
};
},
computed: {
closeHovers() {
return mutations.closeHovers;
},
req() {
return state.req; // Access state directly
},
selected() {
return state.selected; // Access state directly
},
selectedCount() {
return state.selected.length; // Compute selectedCount directly from state
},
isListing() {
return getters.isListing(); // Access getter directly from the store
},
...mapState(["req", "selected", "selectedCount"]),
...mapGetters(["isListing", "selectedCount"]),
url() {
if (!this.isListing) {
return state.route.path;
return this.$route.path;
}
if (getters.selectedCount() !== 1) {
if (this.selectedCount != 1) {
// selecting current view image
return state.route.path;
return this.$route.path;
}
return state.req.items[this.selected[0]].url;
return this.req.items[this.selected[0]].url;
},
getContext() {
let path = state.route.path.replace("/files/", "./");
if (getters.selectedCount() === 1) {
path = path + state.req.items[this.selected[0]].name;
let path = this.$route.path.replace("/files/", "./");
if (this.selectedCount == 1) {
path = path + this.req.items[this.selected[0]].name;
}
return path;
},
@@ -178,25 +165,25 @@ export default {
this.links = links;
this.sort();
if (this.links.length === 0) {
if (this.links.length == 0) {
this.listing = false;
}
} catch (e) {
showError(e);
this.$showError(e);
}
},
mounted() {
this.clip = new Clipboard(".copy-clipboard");
this.clip.on("success", () => {
showSuccess(this.$t("success.linkCopied"));
this.$showSuccess(this.$t("success.linkCopied"));
});
},
beforeUnmount() {
this.clip.destroy();
},
methods: {
async submit() {
let isPermanent = !this.time || this.time === 0;
submit: async function () {
let isPermanent = !this.time || this.time == 0;
try {
let res = null;
@@ -216,30 +203,30 @@ export default {
this.listing = true;
} catch (e) {
showError(e);
this.$showError(e);
}
},
async deleteLink(event, link) {
deleteLink: async function (event, link) {
event.preventDefault();
try {
await api.remove(link.hash);
this.links = this.links.filter((item) => item.hash !== link.hash);
if (this.links.length === 0) {
if (this.links.length == 0) {
this.listing = false;
}
} catch (e) {
showError(e);
this.$showError(e);
}
},
humanTime(time) {
return fromNow(time, state.user.locale);
return moment(time * 1000).fromNow();
},
buildLink(share) {
return api.getShareURL(share);
},
hasDownloadLink() {
return this.selected.length === 1 && !state.req.items[this.selected[0]].isDir;
return this.selected.length === 1 && !this.req.items[this.selected[0]].isDir;
},
buildDownloadLink(share) {
return pub_api.getDownloadURL(share);
@@ -252,9 +239,8 @@ export default {
});
},
switchListing() {
if (this.links.length === 0 && !this.listing) {
// Access the store directly if needed
mutations.closeHovers();
if (this.links.length == 0 && !this.listing) {
this.$store.commit("closeHovers");
}
this.listing = !this.listing;
@@ -5,7 +5,7 @@
</div>
<div class="card-action">
<button
@click="closeHovers"
@click="$store.commit('closeHovers')"
class="button button--flat button--grey"
:aria-label="$t('buttons.cancel')"
:title="$t('buttons.cancel')"
@@ -23,21 +23,16 @@
</div>
</div>
</template>
<script>
import { getters } from "@/store"; // Import your custom store
<script>
import { mapGetters } from "vuex";
export default {
name: "share-delete",
computed: {
closeHovers() {
return mutations.closeHovers();
},
currentPrompt() {
return getters.currentPrompt();
},
...mapGetters(["currentPrompt"]),
},
methods: {
submit() {
submit: function () {
this.currentPrompt?.confirm();
},
},
@@ -29,7 +29,9 @@
:data-type="file.type"
:aria-label="file.name"
>
<div class="file-name"><i class="material-icons"></i> {{ file.name }}</div>
<div class="file-name">
<i class="material-icons"></i> {{ file.name }}
</div>
<div class="file-progress">
<div v-bind:style="{ width: file.progress + '%' }"></div>
</div>
@@ -38,26 +40,22 @@
</div>
</div>
</template>
<script>
import { getters } from "@/store"; // Import your custom store
import { mapGetters } from "vuex";
export default {
name: "uploadFiles",
data() {
data: function () {
return {
open: false,
};
},
computed: {
filesInUpload() {
return getters.filesInUpload(); // Access the getter directly from the store
},
filesInUploadCount() {
return getters.filesInUploadCount(); // Access the getter directly from the store
},
...mapGetters(["filesInUpload", "filesInUploadCount"]),
},
methods: {
toggle() {
toggle: function () {
this.open = !this.open;
},
},
+17 -18
View File
@@ -1,24 +1,17 @@
<template>
<select v-on:change="change" :value="locale">
<option v-for="(value, label) in locales" :key="label" :value="label">
{{ $t("languages." + label) }}
<option v-for="(language, value) in locales" :key="value" :value="value">
{{ $t("languages." + language) }}
</option>
</select>
</template>
<script lang="ts">
import { defineComponent } from "vue";
export default defineComponent({
name: "Languages",
props: {
locale: {
type: String,
required: true,
},
},
<script>
export default {
name: "languages",
props: ["locale"],
data() {
return {
let dataObj = {
locales: {
he: "he",
hu: "hu",
@@ -45,12 +38,18 @@ export default defineComponent({
"zh-tw": "zhTW",
},
};
Object.defineProperty(dataObj, "locales", {
configurable: false,
writable: false,
});
return dataObj;
},
methods: {
change(event: Event) {
const target = event.target as HTMLSelectElement;
this.$emit("update:locale", target.value);
change(event) {
this.$emit("update:locale", event.target.value);
},
},
});
};
</script>
@@ -67,10 +67,10 @@
</template>
<script>
import Languages from "./Languages.vue";
import Rules from "./Rules.vue";
import Permissions from "./Permissions.vue";
import Commands from "./Commands.vue";
import Languages from "./Languages";
import Rules from "./Rules";
import Permissions from "./Permissions";
import Commands from "./Commands";
import { enableExec } from "@/utils/constants";
export default {
@@ -118,4 +118,4 @@ export default {
},
},
};
</script>
</script>
+3 -33
View File
@@ -195,13 +195,14 @@ body.rtl .breadcrumbs a {
bottom: 1em;
left: 50%;
transform: translateX(-50%);
display: -ms-flexbox;
-ms-flex-align: center;
align-items: center;
background: #fff;
width: 95%;
max-width: 30em;
z-index: 1;
border-radius: 1em;
display: flex;
width: 90%;
}
button {
@@ -237,34 +238,3 @@ button {
#file-selection .action span {
display: none;
}
#popup-notification {
color: white;
position: fixed;
max-width: 90vw;
height: 4em;
bottom: 0;
right: -20em; /* Start off-screen */
display: flex;
padding: 1em;
align-items: center;
transition: right 1s ease; /* Animate the 'right' property */
}
#popup-notification-content {
color: white;
padding: 0;
padding-left: .5em;
}
#popup-notification.success {
background: var(--blue);
}
#popup-notification.error {
background: var(--red);
}
#popup-notification > i {
cursor: pointer;
font-size: 1.75em;
}
+1 -1
View File
@@ -98,7 +98,7 @@
/* Listing items */
.dark-mode #listingView .item {
background: var(--surfacePrimary) !important;
background: var(--surfacePrimary);
color: var(--textPrimary);
border-color: var(--divider) !important;
}
+2 -2
View File
@@ -1,5 +1,3 @@
@import 'material-icons/iconfont/filled.css';
@font-face {
font-family: 'Roboto';
font-style: normal;
@@ -168,6 +166,8 @@
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;
}
@import '~material-icons/iconfont/filled.css';
.material-icons {
font-size: 1.5rem;
}
+56 -56
View File
@@ -103,7 +103,9 @@ body.rtl #listingView {
box-shadow: rgba(0, 0, 0, 0.06) 0px 1px 3px, rgba(0, 0, 0, 0.12) 0px 1px 2px;
}
#listingView.gallery .item {
max-width: 300px;
}
#listingView.list .item,
#listingView.compact .item {
max-width: 100%;
@@ -114,12 +116,16 @@ body.rtl #listingView {
box-shadow: 0 1px 3px rgba(0, 0, 0, .12), 0 1px 2px rgba(0, 0, 0, .24) !important;
}
#listingView .header {
display: none;
}
#listingView .item div:first-of-type {
width: 5em;
}
#listingView .item div:last-of-type {
width: 100%;
width: calc(100% - 5vw);
}
#listingView.gallery .item div:first-of-type {
@@ -141,10 +147,10 @@ body.rtl #listingView {
}
#listingView.gallery .item i {
width: 100%;
margin-right: 0;
font-size: 8em;
text-align: center;
width: 100%;
margin-right: 0;
font-size: 8em;
text-align: center;
}
#listingView.gallery .item img {
@@ -171,7 +177,6 @@ body.rtl #listingView {
border: 1px solid rgba(0, 0, 0, 0.1);
padding: 0;
border-top: 0;
padding-left: .5em;
}
#listingView.compact h2 {
@@ -192,30 +197,15 @@ body.rtl #listingView {
}
#listingView.compact .item div:last-of-type {
width: calc(100% - 3em);
display: flex;
align-items: center;
}
#listingView.compact .header .name,
#listingView.list .header .name,
#listingView.list .item .name,
#listingView.compact .item .name {
width: 50%;
}
#listingView.compact .header .name,
#listingView.list .header .name {
margin-right: 1.5em;
}
#listingView .header > p {
margin: 0;
cursor: pointer;
}
#listingView.compact .header .size,
#listingView.list .header .size,
#listingView.list .item .size,
#listingView.compact .item .size {
width: 25%;
}
@@ -227,23 +217,22 @@ body.rtl #listingView {
}
#listingView.compact .header {
display: flex !important;
background: var(--surfacePrimary);
z-index: 999;
padding: .85em;
border: 0;
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
border-bottom-left-radius: 0px;
border-bottom-right-radius: 0px;
}
#listingView.compact .header,
#listingView.list .header {
border: 1px solid rgba(0, 0, 0, .1);
border-color: var(--divider);
}
#listingView.compact .header>div:first-child {
width: 0;
}
#listingView.compact .header .name {
margin-right: 3em;
}
#listingView.compact .header a {
color: inherit;
}
@@ -256,6 +245,10 @@ body.rtl #listingView {
font-weight: normal;
}
#listingView.compact .header .name {
margin-right: 3em;
}
#listingView.compact .header span {
vertical-align: middle;
}
@@ -309,14 +302,22 @@ body.rtl #listingView {
}
#listingView.list .item div:last-of-type {
width: calc(100% - 3em);
display: flex;
align-items: center;
}
#listingView.list .item .name {
width: 50%;
}
#listingView.list .item .size {
width: 25%;
}
#listingView .header {
display: none;
background: var(--surfacePrimary);
border-radius: 1em;
display: none !important;
background-color: #ccc;
}
#listingView.list .header i {
@@ -328,14 +329,14 @@ body.rtl #listingView {
#listingView.compact .header,
#listingView.list .header {
display: flex !important;
background: white;
border-top-left-radius: 1em;
border-top-right-radius: 1em;
z-index: 999;
padding: .85em;
width: 100%;
}
#listingView.compact .header {
width:100%;
border: 0;
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
}
#listingView.list .item:first-child {
margin-top: .5em;
@@ -343,36 +344,35 @@ body.rtl #listingView {
border-top-right-radius: 1em;
}
#listingView.compact .lastGroup > div:last-child {
border-bottom-left-radius: 1em;
border-bottom-right-radius: 1em;
}
#listingView.list .item:last-child {
border-bottom-left-radius: 1em;
border-bottom-right-radius: 1em;
}
#listingView.list .item:last-child {
#listingView.list .item:last-child {
margin-bottom: .5em;
border-bottom-left-radius: 1em;
border-bottom-right-radius: 1em;
}
#listingView.list .header>div:first-child {
width: 0;
}
#listingView > * > .header > div {
display:flex;
width: 100%;
}
#listingView.list .header {
margin-bottom: .5em;
#listingView.list .header .name {
margin-right: 3em;
}
#listingView.list .header a {
color: inherit;
}
#listingView.list .header>div:first-child {
width: 0;
}
#listingView.list .name {
font-weight: normal;
}
#listingView.list .header .name {
margin-right: 3em;
}
#listingView.list .header span {
vertical-align: middle;
}
@@ -411,4 +411,4 @@ body.rtl #listingView {
#listingView #multiple-selection p,
#listingView #multiple-selection i {
color: var(--item-selected);
}
}
+6 -5
View File
@@ -1,4 +1,6 @@
@import "normalize.css/normalize.css";
@import "~normalize.css/normalize.css";
@import "~noty/lib/noty.css";
@import "~noty/lib/themes/mint.css";
@import "./_variables.css";
@import "./_buttons.css";
@import "./_inputs.css";
@@ -11,7 +13,6 @@
@import "./upload-files.css";
@import "./dashboard.css";
@import "./login.css";
@import './mobile.css';
.link {
color: var(--blue);
@@ -167,9 +168,7 @@ main .spinner .bounce2 {
#previewer .preview {
text-align: center;
max-height: 100%;
max-width: 100%;
height: 100%;
height: calc(100vh - 4em);
}
#previewer .preview pre {
@@ -386,6 +385,8 @@ body.rtl .breadcrumbs .chevron {
margin-left: .5rem;
}
@import './mobile.css';
/* * * * * * * * * * * * * * * *
* RTL overrides *
* * * * * * * * * * * * * * * */
-5
View File
@@ -1,5 +0,0 @@
// src/global.d.ts
interface Window {
grecaptcha?: any; // or use a more specific type if available
}
+1 -1
View File
@@ -197,7 +197,7 @@
"executeOnShellDescription": "Por defecto, FileBrowser ejecuta los comandos llamando directamente a sus binarios. Si quieres ejecutarlos en un shell en su lugar (como Bash o PowerShell), puedes definirlo aquí con los argumentos y banderas (flags) necesarios. Si se define, el comando que se ejecuta se añadirá como argumento. Esto se aplica tanto a los comandos de usuario como a los ganchos de eventos.",
"globalRules": "Se trata de un conjunto global de reglas de permiso y rechazo. Se aplican a todos los usuarios. Puedes definir reglas específicas en la configuración de cada usuario para anular estas.",
"globalSettings": "Ajustes globales",
"hideDotfiles": "Ocultar archivos empezados por punto",
"hideDotfiles": "",
"insertPath": "Introduce la ruta",
"insertRegex": "Introducir expresión regular",
"instanceName": "Nombre de la instancia",
+143
View File
@@ -0,0 +1,143 @@
import Vue from "vue";
import VueI18n from "vue-i18n";
import he from "./he.json";
import hu from "./hu.json";
import ar from "./ar.json";
import de from "./de.json";
import el from "./el.json";
import en from "./en.json";
import es from "./es.json";
import fr from "./fr.json";
import is from "./is.json";
import it from "./it.json";
import ja from "./ja.json";
import ko from "./ko.json";
import nlBE from "./nl-be.json";
import pl from "./pl.json";
import pt from "./pt.json";
import ptBR from "./pt-br.json";
import ro from "./ro.json";
import ru from "./ru.json";
import sk from "./sk.json";
import ua from "./ua.json";
import svSE from "./sv-se.json";
import zhCN from "./zh-cn.json";
import zhTW from "./zh-tw.json";
Vue.use(VueI18n);
export function detectLocale() {
let locale = (navigator.language || navigator.browserLangugae).toLowerCase();
switch (true) {
case /^he.*/i.test(locale):
locale = "he";
break;
case /^hu.*/i.test(locale):
locale = "hu";
break;
case /^ar.*/i.test(locale):
locale = "ar";
break;
case /^el.*/i.test(locale):
locale = "el";
break;
case /^es.*/i.test(locale):
locale = "es";
break;
case /^en.*/i.test(locale):
locale = "en";
break;
case /^it.*/i.test(locale):
locale = "it";
break;
case /^fr.*/i.test(locale):
locale = "fr";
break;
case /^pt.*/i.test(locale):
locale = "pt";
break;
case /^pt-BR.*/i.test(locale):
locale = "pt-br";
break;
case /^ja.*/i.test(locale):
locale = "ja";
break;
case /^zh-CN/i.test(locale):
locale = "zh-cn";
break;
case /^zh-TW/i.test(locale):
locale = "zh-tw";
break;
case /^zh.*/i.test(locale):
locale = "zh-cn";
break;
case /^de.*/i.test(locale):
locale = "de";
break;
case /^ru.*/i.test(locale):
locale = "ru";
break;
case /^pl.*/i.test(locale):
locale = "pl";
break;
case /^ko.*/i.test(locale):
locale = "ko";
break;
case /^sk.*/i.test(locale):
locale = "sk";
break;
case /^ua.*/i.test(locale):
locale = "ua";
break;
default:
locale = "en";
}
return locale;
}
const removeEmpty = (obj) =>
Object.keys(obj)
.filter((k) => obj[k] !== null && obj[k] !== undefined && obj[k] !== "") // Remove undef. and null and empty.string.
.reduce(
(newObj, k) =>
typeof obj[k] === "object"
? Object.assign(newObj, { [k]: removeEmpty(obj[k]) }) // Recurse.
: Object.assign(newObj, { [k]: obj[k] }), // Copy value.
{}
);
export const rtlLanguages = ["he", "ar"];
const i18n = new VueI18n({
locale: detectLocale(),
fallbackLocale: "en",
messages: {
he: removeEmpty(he),
hu: removeEmpty(hu),
ar: removeEmpty(ar),
de: removeEmpty(de),
el: removeEmpty(el),
en: en,
es: removeEmpty(es),
fr: removeEmpty(fr),
is: removeEmpty(is),
it: removeEmpty(it),
ja: removeEmpty(ja),
ko: removeEmpty(ko),
"nl-be": removeEmpty(nlBE),
pl: removeEmpty(pl),
"pt-br": removeEmpty(ptBR),
pt: removeEmpty(pt),
ru: removeEmpty(ru),
ro: removeEmpty(ro),
sk: removeEmpty(sk),
"sv-se": removeEmpty(svSE),
ua: removeEmpty(ua),
"zh-cn": removeEmpty(zhCN),
"zh-tw": removeEmpty(zhTW),
},
});
export default i18n;
-120
View File
@@ -1,120 +0,0 @@
// i18n.js
import { createI18n } from 'vue-i18n';
// Import translations
import he from './he.json';
import hu from './hu.json';
import ar from './ar.json';
import de from './de.json';
import el from './el.json';
import en from './en.json';
import es from './es.json';
import fr from './fr.json';
import is from './is.json';
import it from './it.json';
import ja from './ja.json';
import ko from './ko.json';
import nlBE from './nl-be.json';
import pl from './pl.json';
import pt from './pt.json';
import ptBR from './pt-br.json';
import ro from './ro.json';
import ru from './ru.json';
import sk from './sk.json';
import ua from './ua.json';
import svSE from './sv-se.json';
import zhCN from './zh-cn.json';
import zhTW from './zh-tw.json';
type LocaleMap = { [key: string]: string };
export function detectLocale(): string {
const locale = navigator.language.toLowerCase();
const localeMap: LocaleMap = {
'he': 'he',
'hu': 'hu',
'ar': 'ar',
'el': 'el',
'es': 'es',
'en': 'en',
'is': 'is',
'it': 'it',
'fr': 'fr',
'pt-br': 'pt-br',
'pt': 'pt',
'ja': 'ja',
'zh-tw': 'zh-tw',
'zh-cn': 'zh-cn',
'zh': 'zh-cn',
'de': 'de',
'ro': 'ro',
'ru': 'ru',
'pl': 'pl',
'ko': 'ko',
'sk': 'sk',
'tr': 'tr',
'uk': 'uk',
'sv-se': 'sv',
'sv': 'sv',
'nl-be': 'nl-be',
};
for (const key in localeMap) {
if (locale.startsWith(key)) {
return localeMap[key];
}
}
return 'en-us'; // Default fallback
}
// List of RTL languages
export const rtlLanguages = ['he', 'ar'];
// Function to check if locale is RTL
export const isRtl = (locale: string) => {
const currentLocale = locale || i18n.global.locale;
return rtlLanguages.includes(currentLocale);
};
export function setLocale(locale: string) {
// according to doc u only need .value if legacy: false but they lied
// https://vue-i18n.intlify.dev/guide/essentials/scope.html#local-scope-1
//@ts-ignore
i18n.global.locale.value = locale;
}
// Create i18n instance
const i18n = createI18n({
locale: detectLocale(),
fallbackLocale: 'en',
// expose i18n.global for outside components
legacy: true,
messages: {
he,
hu,
ar,
de,
el,
en,
es,
fr,
is,
it,
ja,
ko,
'nl-be': nlBE,
pl,
'pt-br': ptBR,
pt,
ru,
ro,
sk,
'sv-se': svSE,
ua,
'zh-cn': zhCN,
'zh-tw': zhTW,
},
});
export default i18n;
+52
View File
@@ -0,0 +1,52 @@
import cssVars from "css-vars-ponyfill";
import { sync } from "vuex-router-sync";
import store from "@/store";
import router from "@/router";
import i18n from "@/i18n";
import Vue from "@/utils/vue";
import { recaptcha, loginPage } from "@/utils/constants";
import { login, validateLogin } from "@/utils/auth";
import App from "@/App";
export const eventBus = new Vue(); // creating an event bus.
cssVars();
sync(store, router);
async function start() {
try {
if (loginPage) {
await validateLogin();
} else {
await login("publicUser", "publicUser", "");
}
} catch (e) {
console.log(e);
}
if (recaptcha) {
await new Promise((resolve) => {
const check = () => {
if (typeof window.grecaptcha === "undefined") {
setTimeout(check, 100);
} else {
resolve();
}
};
check();
});
}
new Vue({
el: "#app",
store,
router,
i18n,
template: "<App/>",
components: { App },
});
}
start();
-43
View File
@@ -1,43 +0,0 @@
import { createApp } from 'vue';
import router from './router'; // Adjust the path as per your setup
import App from './App.vue'; // Adjust the path as per your setup
import { state } from '@/store'; // Adjust the path as per your setup
import i18n from "@/i18n";
import VueLazyload from "vue-lazyload";
import './css/styles.css';
const app = createApp(App);
// provide v-focus for components
app.directive("focus", {
mounted: async (el) => {
// initiate focus for the element
el.focus();
},
});
// Install additionals
app.use(VueLazyload);
app.use(i18n);
app.use(router);
// Provide state to the entire application
app.provide('state', state);
// provide v-focus for components
app.directive("focus", {
mounted: async (el) => {
// initiate focus for the element
el.focus();
},
});
app.mixin({
mounted() {
// expose vue instance to components
this.$el.__vue__ = this;
},
});
router.isReady().then(() => app.mount("#app"));
-7
View File
@@ -1,7 +0,0 @@
import { showSuccess, showError, closePopUp } from "./message.js";
export {
showSuccess,
showError,
closePopUp,
};
-45
View File
@@ -1,45 +0,0 @@
export function showPopup(type, message) {
const [popup, popupContent] = getElements();
popup.classList.remove('success', 'error'); // Clear previous types
popup.classList.add(type);
popupContent.textContent = message;
// Start animation: bring the popup into view
popup.style.right = '1em';
// Automatically hide after 10 seconds
setTimeout(() => {
closePopUp()
}, 10000);
}
export function closePopUp() {
const [popup, popupContent] = getElements();
popup.style.right = '-50em'; // Slide out
popupContent.textContent = "no content";
}
function getElements() {
const popup = document.getElementById('popup-notification');
if (!popup) {
console.error('Popup notification element not found');
return [null, null];
}
const popupContent = popup.querySelector('#popup-notification-content');
if (!popupContent) {
console.error('Popup notification content element not found');
return [null, null];
}
return [popup, popupContent];
}
export function showSuccess(message) {
showPopup('success', message);
}
export function showError(message) {
showPopup('error', message);
console.error(message)
}
+194
View File
@@ -0,0 +1,194 @@
import Vue from "vue";
import Router from "vue-router";
import Login from "@/views/Login";
import Layout from "@/views/Layout";
import Files from "@/views/Files";
import Share from "@/views/Share";
import Users from "@/views/settings/Users";
import User from "@/views/settings/User";
import Settings from "@/views/Settings";
import GlobalSettings from "@/views/settings/Global";
import ProfileSettings from "@/views/settings/Profile";
import Shares from "@/views/settings/Shares";
import Errors from "@/views/Errors";
import store from "@/store";
import { baseURL, name } from "@/utils/constants";
import i18n, { rtlLanguages } from "@/i18n";
Vue.use(Router);
const titles = {
Login: "sidebar.login",
Share: "buttons.share",
Files: "files.files",
Settings: "sidebar.settings",
ProfileSettings: "settings.profileSettings",
Shares: "settings.shareManagement",
GlobalSettings: "settings.globalSettings",
Users: "settings.users",
User: "settings.user",
Forbidden: "errors.forbidden",
NotFound: "errors.notFound",
InternalServerError: "errors.internal",
};
const router = new Router({
base: baseURL,
mode: "history",
routes: [
{
path: "/login",
name: "Login",
component: Login,
beforeEnter: (to, from, next) => {
if (store.getters.isLogged) {
return next({ path: "/files" });
}
next();
},
},
{
path: "/*",
component: Layout,
children: [
{
path: "/share/*",
name: "Share",
component: Share,
},
{
path: "/files/*",
name: "Files",
component: Files,
meta: {
requiresAuth: true,
},
},
{
path: "/settings",
name: "Settings",
component: Settings,
redirect: {
path: "/settings/profile",
},
meta: {
requiresAuth: true,
},
children: [
{
path: "/settings/profile",
name: "ProfileSettings",
component: ProfileSettings,
},
{
path: "/settings/shares",
name: "Shares",
component: Shares,
},
{
path: "/settings/global",
name: "GlobalSettings",
component: GlobalSettings,
meta: {
requiresAdmin: true,
},
},
{
path: "/settings/users",
name: "Users",
component: Users,
meta: {
requiresAdmin: true,
},
},
{
path: "/settings/users/*",
name: "User",
component: User,
meta: {
requiresAdmin: true,
},
},
],
},
{
path: "/403",
name: "Forbidden",
component: Errors,
props: {
errorCode: 403,
showHeader: true,
},
},
{
path: "/404",
name: "NotFound",
component: Errors,
props: {
errorCode: 404,
showHeader: true,
},
},
{
path: "/500",
name: "InternalServerError",
component: Errors,
props: {
errorCode: 500,
showHeader: true,
},
},
{
path: "/files",
redirect: {
path: "/files/",
},
},
{
path: "/*",
redirect: (to) => `/files${to.path}`,
},
],
},
],
});
router.beforeEach((to, from, next) => {
const title = i18n.t(titles[to.name]);
document.title = title + " - " + name;
/*** RTL related settings per route ****/
const rtlSet = document.querySelector("body").classList.contains("rtl");
const shouldSetRtl = rtlLanguages.includes(i18n.locale);
switch (true) {
case shouldSetRtl && !rtlSet:
document.querySelector("body").classList.add("rtl");
break;
case !shouldSetRtl && rtlSet:
document.querySelector("body").classList.remove("rtl");
break;
}
if (to.matched.some((record) => record.meta.requiresAuth)) {
if (!store.getters.isLogged) {
next({
path: "/login",
query: { redirect: to.fullPath },
});
return;
}
if (to.matched.some((record) => record.meta.requiresAdmin)) {
if (!store.state.user.perm.admin) {
next({ path: "/403" });
return;
}
}
}
next();
});
export default router;
-217
View File
@@ -1,217 +0,0 @@
import { RouteLocation, createRouter, createWebHistory } from "vue-router";
import Login from "@/views/Login.vue";
import Layout from "@/views/Layout.vue";
import Files from "@/views/Files.vue";
import Share from "@/views/Share.vue";
import Users from "@/views/settings/Users.vue";
import User from "@/views/settings/User.vue";
import Settings from "@/views/Settings.vue";
import GlobalSettings from "@/views/settings/Global.vue";
import ProfileSettings from "@/views/settings/Profile.vue";
import Shares from "@/views/settings/Shares.vue";
import Errors from "@/views/Errors.vue";
import { baseURL, name } from "@/utils/constants";
import { getters, state } from "@/store";
import { recaptcha, loginPage } from "@/utils/constants";
import { login, validateLogin } from "@/utils/auth";
import { mutations } from "@/store";
import i18n from "@/i18n";
const titles = {
Login: "sidebar.login",
Share: "buttons.share",
Files: "files.files",
Settings: "sidebar.settings",
ProfileSettings: "settings.profileSettings",
Shares: "settings.shareManagement",
GlobalSettings: "settings.globalSettings",
Users: "settings.users",
User: "settings.user",
Forbidden: "errors.forbidden",
NotFound: "errors.notFound",
InternalServerError: "errors.internal",
};
const routes = [
{
path: "/login",
name: "Login",
component: Login,
},
{
path: "/share",
component: Layout,
children: [
{
path: ":path*",
name: "Share",
component: Share,
},
],
},
{
path: "/files",
component: Layout,
meta: {
requiresAuth: true,
},
children: [
{
path: ":path*",
name: "Files",
component: Files,
},
],
},
{
path: "/settings",
component: Layout,
meta: {
requiresAuth: true,
},
children: [
{
path: "",
name: "Settings",
component: Settings,
redirect: {
path: "/settings/profile",
},
children: [
{
path: "profile",
name: "ProfileSettings",
component: ProfileSettings,
},
{
path: "shares",
name: "Shares",
component: Shares,
},
{
path: "global",
name: "GlobalSettings",
component: GlobalSettings,
meta: {
requiresAdmin: true,
},
},
{
path: "users",
name: "Users",
component: Users,
meta: {
requiresAdmin: true,
},
},
{
path: "users/:id",
name: "User",
component: User,
meta: {
requiresAdmin: true,
},
},
],
},
],
},
{
path: "/403",
name: "Forbidden",
component: Errors,
props: {
errorCode: 403,
showHeader: true,
},
},
{
path: "/404",
name: "NotFound",
component: Errors,
props: {
errorCode: 404,
showHeader: true,
},
},
{
path: "/500",
name: "InternalServerError",
component: Errors,
props: {
errorCode: 500,
showHeader: true,
},
},
{
path: "/:catchAll(.*)*",
redirect: (to: RouteLocation) =>
`/files/${[...to.params.catchAll].join("/")}`,
},
];
const router = createRouter({
history: createWebHistory(baseURL),
routes,
});
async function initAuth() {
if (loginPage) {
await validateLogin();
} else {
await login("publicUser", "publicUser", "");
}
if (recaptcha) {
await new Promise<void>((resolve) => {
const check = () => {
if (typeof window.grecaptcha === "undefined") {
setTimeout(check, 100);
} else {
resolve();
}
};
check();
});
}
}
router.beforeResolve(async (to, from, next) => {
const title = i18n.global.t(titles[to.name as keyof typeof titles]);
document.title = title + " - " + name;
mutations.setRoute(to)
// this will only be null on first route
if (from.name == null) {
try {
await initAuth();
} catch (error) {
console.error(error);
}
}
if (to.path.endsWith("/login") && getters.isLoggedIn()) {
next({ path: "/files/" });
return;
}
if (to.matched.some((record) => record.meta.requiresAuth)) {
if (!getters.isLoggedIn()) {
next({
path: "/login",
query: { redirect: to.fullPath },
});
return;
}
if (to.matched.some((record) => record.meta.requiresAdmin)) {
if (state.user === null || !getters.isAdmin()) {
next({ path: "/403" });
return;
}
}
}
next();
});
export { router, router as default };
-16
View File
@@ -1,16 +0,0 @@
// eventBus.ts
class EventBus extends EventTarget {
emit(event, data) {
this.dispatchEvent(new CustomEvent(event, { detail: data }));
}
on(event, callback) {
this.addEventListener(event, (e) => callback(e.detail));
}
}
export const eventBus = new EventBus();
export function emitStateChanged() {
eventBus.emit('stateChanged');
}
+23 -86
View File
@@ -1,97 +1,32 @@
import { state } from "./state.js";
export const getters = {
isDarkMode: () => {
if (state.user == null) {
return true;
}
return state.user.darkMode === true;
},
isLoggedIn: () => state.user !== null,
isAdmin: () => state.user.perm?.admin == true,
isFiles: () => state.route.name === "Files",
isListing: () => getters.isFiles() && state.req.isDir,
selectedCount: () => Array.isArray(state.selected) ? state.selected.length : 0,
isSingleFileSelected: () => getters.selectedCount() === 1 && !state.req.items[state.selected[0]]?.isDir,
selectedDownloadUrl() {
let selectedItem = state.selected[0]
return state.req.items[selectedItem].url;
},
getRoutePath: () => {
return state.route.path.endsWith("/")
? state.route.path
: state.route.path + "/";
},
currentView: () => {
let returnVal = null;
if (state.req.type !== undefined) {
if (state.req.isDir) {
returnVal = "listingView";
} else if ("content" in state.req) {
returnVal = "editor";
} else {
returnVal = "preview";
}
}
return returnVal;
},
progress: () => {
// Check if state.upload is defined and valid
if (!state.upload || !Array.isArray(state.upload.progress) || !Array.isArray(state.upload.sizes)) {
const getters = {
isLogged: (state) => state.user !== null,
isFiles: (state) => !state.loading && state.route.name === "Files",
isListing: (state, getters) => getters.isFiles && state.req.isDir,
selectedCount: (state) => state.selected.length,
progress: (state) => {
if (state.upload.progress.length == 0) {
return 0;
}
// Handle cases where progress or sizes arrays might be empty
if (state.upload.progress.length === 0 || state.upload.sizes.length === 0) {
return 0;
}
// Calculate totalSize
let totalSize = state.upload.sizes.reduce((a, b) => a + b, 0);
// Calculate sum of progress
let sum = state.upload.progress.reduce((acc, val) => acc + val, 0);
// Return progress as a percentage
let sum = state.upload.progress.reduce((acc, val) => acc + val);
return Math.ceil((sum / totalSize) * 100);
},
filesInUploadCount: () => {
// Ensure state.upload.uploads is an object and state.upload.queue is an array
const uploadsCount = typeof state.upload.uploads === 'object' ? Object.keys(state.upload.uploads).length : 0;
const queueCount = Array.isArray(state.upload.queue) ? state.upload.queue.length : 0;
return uploadsCount + queueCount;
filesInUploadCount: (state) => {
let total =
Object.keys(state.upload.uploads).length + state.upload.queue.length;
return total;
},
currentPrompt: () => {
// Ensure state.prompts is an array
if (!Array.isArray(state.prompts)) {
return null;
}
if (state.prompts.length === 0) {
return null;
}
return state.prompts[state.prompts.length - 1]
currentPrompt: (state) => {
return state.prompts.length > 0
? state.prompts[state.prompts.length - 1]
: null;
},
currentPromptName: () => {
// Ensure state.prompts is an array
if (!Array.isArray(state.prompts)) {
return null;
}
if (state.prompts.length === 0) {
return null;
}
return state.prompts[state.prompts.length - 1].name;
currentPromptName: (_, getters) => {
return getters.currentPrompt?.prompt;
},
filesInUpload: () => {
// Ensure state.upload.uploads is an object and state.upload.sizes is an array
if (typeof state.upload.uploads !== 'object' || !Array.isArray(state.upload.sizes)) {
return [];
}
filesInUpload: (state) => {
let files = [];
for (let index in state.upload.uploads) {
@@ -99,11 +34,11 @@ export const getters = {
let id = upload.id;
let type = upload.type;
let name = upload.file.name;
let size = state.upload.sizes[id] || 0; // Default to 0 if size is undefined
let size = state.upload.sizes[id];
let isDir = upload.file.isDir;
let progress = isDir
? 100
: Math.ceil((state.upload.progress[id] || 0 / size) * 100); // Default to 0 if progress is undefined
: Math.ceil((state.upload.progress[id] / size) * 100);
files.push({
id,
@@ -117,3 +52,5 @@ export const getters = {
return files.sort((a, b) => a.progress - b.progress);
},
};
export default getters;
+41
View File
@@ -0,0 +1,41 @@
import Vue from "vue";
import Vuex from "vuex";
import mutations from "./mutations";
import getters from "./getters";
import upload from "./modules/upload";
Vue.use(Vuex);
const state = {
editor: null,
user: null,
req: {
sorting: {
by: 'name', // Initial sorting field
asc: true, // Initial sorting order
},
},
oldReq: {},
clipboard: {
key: "",
items: [],
},
jwt: "",
progress: 0,
loading: false,
reload: false,
selected: [],
multiple: false,
prompts: [],
show: null,
showShell: false,
showConfirm: null,
};
export default new Vuex.Store({
strict: true,
state,
getters,
mutations,
modules: { upload },
});
-10
View File
@@ -1,10 +0,0 @@
// store/index.js
import { state } from "./state.js";
import { getters } from "./getters.js";
import { mutations } from "./mutations.js";
export {
state,
getters,
mutations
};
+47 -76
View File
@@ -1,107 +1,85 @@
import * as i18n from "@/i18n";
import { state } from "./state.js";
import { emitStateChanged } from './eventBus'; // Import the function from eventBus.js
import moment from "moment";
export const mutations = {
setUsage: (value) => {
state.usage = value;
emitStateChanged();
},
closeHovers: () => {
const mutations = {
closeHovers: (state) => {
state.prompts = [];
emitStateChanged();
},
toggleShell: () => {
toggleShell: (state) => {
state.showShell = !state.showShell;
emitStateChanged();
},
showHover: (value) => {
if (typeof value === "object") {
showHover: (state, value) => {
if (typeof value !== "object") {
state.prompts.push({
name: value?.name,
confirm: value?.confirm,
action: value?.action,
props: value?.props,
});
} else {
state.prompts.push({
name: value,
confirm: value?.confirm,
action: value?.action,
props: value?.props,
prompt: value,
confirm: null,
action: null,
props: null,
});
return;
}
emitStateChanged();
state.prompts.push({
prompt: value.prompt, // Should not be null
confirm: value?.confirm,
action: value?.action,
props: value?.props,
});
},
showError: () => {
showError: (state) => {
state.prompts.push("error");
emitStateChanged();
},
setLoading: (value) => {
showSuccess: (state) => {
state.prompts.push("success");
},
setLoading: (state, value) => {
state.loading = value;
emitStateChanged();
},
setReload: (value) => {
setReload: (state, value) => {
state.reload = value;
emitStateChanged();
},
setUser: (value) => {
setUser: (state, value) => {
if (value === null) {
state.user = null;
emitStateChanged();
return;
}
let locale = value.locale;
if (locale === "") {
locale = i18n.detectLocale();
}
i18n.setLocale(locale);
moment.locale(locale);
i18n.default.locale = locale;
state.user = value;
emitStateChanged();
},
setJWT: (value) => {
state.jwt = value;
emitStateChanged();
},
setSession: (value) => {
state.sessionId = value;
emitStateChanged();
},
setMultiple: (value) => {
state.multiple = value;
emitStateChanged();
},
addSelected: (value) => {
state.selected.push(value);
emitStateChanged();
},
removeSelected: (value) => {
setJWT: (state, value) => (state.jwt = value),
setSession: (state, value) => (state.sessionId = value),
multiple: (state, value) => (state.multiple = value),
addSelected: (state, value) => state.selected.push(value),
removeSelected: (state, value) => {
let i = state.selected.indexOf(value);
if (i === -1) return;
state.selected.splice(i, 1);
emitStateChanged();
},
resetSelected: () => {
resetSelected: (state) => {
state.selected = [];
mutations.setMultiple(false);
emitStateChanged();
},
updateUser: (value) => {
updateUser: (state, value) => {
if (typeof value !== "object") return;
if (state.user === null) {
state.user = {};
}
for (let field in value) {
if (field === "locale") {
i18n.setLocale(value[field]);
moment.locale(value[field]);
i18n.default.locale = value[field];
}
state.user[field] = value[field];
}
emitStateChanged();
},
updateRequest: (value) => {
updateRequest: (state, value) => {
const selectedItems = state.selected.map((i) => state.req.items[i]);
state.oldReq = state.req;
state.req = value;
@@ -112,20 +90,14 @@ export const mutations = {
.filter((item) => selectedItems.some((rItem) => rItem.url === item.url))
.map((item) => item.index);
},
replaceRequest: (value) => {
state.req = value;
emitStateChanged();
},
setRoute: (value) => {
state.route = value;
emitStateChanged();
},
updateListingSortConfig: ({ field, asc }) => {
// Inside your mutations object
updateListingSortConfig(state, { field, asc }) {
state.req.sorting.by = field;
state.req.sorting.asc = asc;
emitStateChanged();
},
updateListingItems: () => {
updateListingItems(state) {
// Sort the items array based on the sorting settings
state.req.items.sort((a, b) => {
const valueA = a[state.req.sorting.by];
const valueB = b[state.req.sorting.by];
@@ -135,18 +107,17 @@ export const mutations = {
return valueA < valueB ? 1 : -1;
}
});
emitStateChanged();
},
updateClipboard: (value) => {
updateClipboard: (state, value) => {
state.clipboard.key = value.key;
state.clipboard.items = value.items;
state.clipboard.path = value.path;
emitStateChanged();
},
resetClipboard: () => {
resetClipboard: (state) => {
state.clipboard.key = "";
state.clipboard.items = [];
emitStateChanged();
},
};
export default mutations;
-54
View File
@@ -1,54 +0,0 @@
import { reactive } from 'vue';
import { detectLocale } from "@/i18n";
export const state = reactive({
usage: {
used: "0 B",
total: "0 B",
usedPercentage: 0
},
editor: null,
user: {
locale: detectLocale(), // Default to the locale from moment
viewMode: 'mosaic', // Default to mosaic view
hideDotfiles: false, // Default to false, assuming this is a boolean
perm: {},
rules: [], // Default to an empty array
permissions: {}, // Default to an empty object for permissions
darkMode: false, // Default to false, assuming this is a boolean
profile: { // Example of additional user properties
username: '', // Default to an empty string
email: '', // Default to an empty string
avatarUrl: '' // Default to an empty string
}
},
req: {
sorting: {
by: 'name', // Initial sorting field
asc: true, // Initial sorting order
},
items: [],
numDirs: 0,
numFiles: 0,
},
oldReq: {},
clipboard: {
key: "",
items: [],
},
jwt: "",
progress: 0,
loading: false,
reload: false,
selected: [],
multiple: false,
upload: {
progress: [], // Array of progress values
sizes: [], // Array of sizes
},
prompts: [],
show: null,
showShell: false,
showConfirm: null,
route: {},
});
+16 -9
View File
@@ -1,4 +1,4 @@
import { mutations } from "@/store";
import store from "@/store";
import router from "@/router";
import { baseURL } from "@/utils/constants";
@@ -8,12 +8,13 @@ export function parseToken(token) {
if (parts.length !== 3) {
throw new Error("token malformed");
}
const data = JSON.parse(atob(parts[1]));
document.cookie = `auth=${token}; path=/`;
localStorage.setItem("jwt", token);
mutations.setJWT(token);
mutations.setSession(generateRandomCode(8));
mutations.setUser(data.user);
store.commit("setJWT", token);
store.commit("setSession", generateRandomCode(8));
store.commit("setUser", data.user);
}
export async function validateLogin() {
@@ -22,12 +23,13 @@ export async function validateLogin() {
await renew(localStorage.getItem("jwt"));
}
} catch (_) {
console.warn('Invalid JWT token in storage')
console.warn('Invalid JWT token in storage') // eslint-disable-line
}
}
export async function login(username, password, recaptcha) {
const data = { username, password, recaptcha };
const res = await fetch(`${baseURL}/api/login`, {
method: "POST",
headers: {
@@ -51,9 +53,11 @@ export async function renew(jwt) {
"X-Auth": jwt,
},
});
const body = await res.text();
if (res.status === 200) {
mutations.setSession(generateRandomCode(8));
store.commit("setSession", generateRandomCode(8));
parseToken(body);
} else {
throw new Error(body);
@@ -63,6 +67,7 @@ export async function renew(jwt) {
function generateRandomCode(length) {
const charset = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
let code = '';
for (let i = 0; i < length; i++) {
const randomIndex = Math.floor(Math.random() * charset.length);
code += charset[randomIndex];
@@ -71,8 +76,9 @@ function generateRandomCode(length) {
return code;
}
export async function signupLogin(username, password) {
export async function signup(username, password) {
const data = { username, password };
const res = await fetch(`${baseURL}/api/signup`, {
method: "POST",
headers: {
@@ -88,8 +94,9 @@ export async function signupLogin(username, password) {
export function logout() {
document.cookie = "auth=; expires=Thu, 01 Jan 1970 00:00:01 GMT; path=/";
mutations.setJWT("");
mutations.setUser(null);
store.commit("setJWT", "");
store.commit("setUser", null);
localStorage.setItem("jwt", null);
router.push({ path: "/login" });
}
+3
View File
@@ -2,6 +2,7 @@ function loading(button) {
let el = document.querySelector(`#${button}-button > i`);
if (el === undefined || el === null) {
console.log('Error getting button ' + button) // eslint-disable-line
return;
}
@@ -23,6 +24,7 @@ function done(button) {
let el = document.querySelector(`#${button}-button > i`);
if (el === undefined || el === null) {
console.log('Error getting button ' + button) // eslint-disable-line
return;
}
@@ -39,6 +41,7 @@ function success(button) {
let el = document.querySelector(`#${button}-button > i`);
if (el === undefined || el === null) {
console.log('Error getting button ' + button) // eslint-disable-line
return;
}
+1
View File
@@ -17,6 +17,7 @@ const resizePreview = window.FileBrowser.ResizePreview;
const enableExec = window.FileBrowser.EnableExec;
const origin = window.location.origin;
console.log(window.FileBrowser)
export {
name,
disableExternal,
+12
View File
@@ -0,0 +1,12 @@
export default function deepClone(obj) {
if (obj === null || typeof obj !== 'object') {
return obj;
}
let clone = Array.isArray(obj) ? [] : {};
for (let key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
clone[key] = deepClone(obj[key]);
}
}
return clone;
}
-17
View File
@@ -1,17 +0,0 @@
type DeepCloneable = object | Array<any>;
export default function deepClone<T extends DeepCloneable>(obj: T): T {
if (obj === null || typeof obj !== 'object') {
return obj;
}
if (Array.isArray(obj)) {
return obj.map(deepClone) as T;
}
const clone = {} as T;
for (const key in obj) {
clone[key] = deepClone(obj[key] as any);
}
return clone;
}
-97
View File
@@ -1,97 +0,0 @@
export function fromNow(date, locale) {
date = normalizeDate(date);
const now = new Date();
const diffInSeconds = Math.floor((now - date) / 1000);
const intervals = [
{ label: 'year', seconds: 31536000 },
{ label: 'month', seconds: 2592000 },
{ label: 'week', seconds: 604800 },
{ label: 'day', seconds: 86400 },
{ label: 'hour', seconds: 3600 },
{ label: 'minute', seconds: 60 },
{ label: 'second', seconds: 1 },
];
const formatter = new Intl.RelativeTimeFormat(locale, { numeric: 'auto' });
for (let interval of intervals) {
const count = Math.floor(diffInSeconds / interval.seconds);
if (count > 0) {
return formatter.format(-count, interval.label);
}
}
return 'just now';
}
export function formatTimestamp(date, locale = 'en-us') {
// Ensure `normalizeDate` returns a valid Date object
date = normalizeDate(date);
if (!(date instanceof Date) || isNaN(date)) {
console.error('Invalid date object:', date);
return 'Invalid Date';
}
// Define options for formatting
const dateOptions = {
day: '2-digit',
month: '2-digit',
year: 'numeric'
};
const timeOptions = {
hour: '2-digit',
minute: '2-digit',
second: '2-digit'
};
// Format date and time using locale
const dateFormatter = new Intl.DateTimeFormat(locale, dateOptions);
const timeFormatter = new Intl.DateTimeFormat(locale, timeOptions);
try {
// Extract date and time components
const dateParts = dateFormatter.formatToParts(date);
const timeParts = timeFormatter.formatToParts(date);
// Construct formatted timestamp
const dateMap = new Map(dateParts.map(part => [part.type, part.value]));
const timeMap = new Map(timeParts.map(part => [part.type, part.value]));
const formattedDate = locale.includes('en')
? `${dateMap.get('month')}/${dateMap.get('day')}/${dateMap.get('year')}`
: `${dateMap.get('day')}/${dateMap.get('month')}/${dateMap.get('year')}`;
// Time formatting: hh:mm:ss
const formattedTime = `${timeMap.get('hour')}:${timeMap.get('minute')}:${timeMap.get('second')}`;
// Combine date and time
return `${formattedDate} ${formattedTime}`;
} catch (error) {
console.error('Error formatting date:', error);
return 'Invalid Date';
}
}
function normalizeDate(date) {
let normalizedDate;
if (typeof date === 'string') {
// Parse the date string
normalizedDate = new Date(date);
} else if (typeof date === 'number') {
// Convert seconds to milliseconds if necessary
normalizedDate = new Date(date * (date < 1e12 ? 1000 : 1));
} else if (date instanceof Date && !isNaN(date.getTime())) {
// It's already a valid Date object
normalizedDate = date;
} else {
throw new Error("Invalid date provided");
}
return normalizedDate;
}
export default {
formatTimestamp,
fromNow,
};
+3 -3
View File
@@ -1,4 +1,4 @@
import { state } from "@/store";
import store from "@/store";
import url from "@/utils/url";
export function checkConflict(files, items) {
@@ -102,7 +102,7 @@ export function scanFiles(dt) {
export function handleFiles(files, base, overwrite = false) {
for (let i = 0; i < files.length; i++) {
let id = state.upload.id;
let id = store.state.upload.id;
let path = base;
let file = files[i];
@@ -124,6 +124,6 @@ export function handleFiles(files, base, overwrite = false) {
...(!file.isDir && { type: file.type }),
};
state.dispatch("upload/upload", item);
store.dispatch("upload/upload", item);
}
}
-11
View File
@@ -29,18 +29,7 @@ export function encodePath(str) {
.join("/");
}
// Function to remove trailing slash
export function removeTrailingSlash(url) {
return url.endsWith("/") ? url.slice(0, -1) : url;
}
export function pathsMatch(url1, url2) {
return removeTrailingSlash(url1) == removeTrailingSlash(url2);
}
export default {
pathsMatch,
removeTrailingSlash,
encodeRFC5987ValueChars,
removeLastDir,
encodePath,
+68
View File
@@ -0,0 +1,68 @@
import Vue from "vue";
import Noty from "noty";
import VueLazyload from "vue-lazyload";
import i18n from "@/i18n";
import { disableExternal } from "@/utils/constants";
import AsyncComputed from "vue-async-computed";
Vue.use(VueLazyload);
Vue.use(AsyncComputed);
Vue.config.productionTip = true;
const notyDefault = {
type: "info",
layout: "bottomRight",
timeout: 1000,
progressBar: true,
};
Vue.prototype.$noty = (opts) => {
new Noty(Object.assign({}, notyDefault, opts)).show();
};
Vue.prototype.$showSuccess = (message) => {
new Noty(
Object.assign({}, notyDefault, {
text: message,
type: "success",
})
).show();
};
Vue.prototype.$showError = (error, displayReport = true) => {
let btns = [
Noty.button(i18n.t("buttons.close"), "", function () {
n.close();
}),
];
if (!disableExternal && displayReport) {
btns.unshift(
Noty.button(i18n.t("buttons.reportIssue"), "", function () {
window.open(
"https://github.com/filebrowser/filebrowser/issues/new/choose"
);
})
);
}
let n = new Noty(
Object.assign({}, notyDefault, {
text: error.message || error,
type: "error",
timeout: null,
buttons: btns,
})
);
n.show();
};
Vue.directive("focus", {
inserted: function (el) {
el.focus();
},
});
export default Vue;
Loaded 100 of 121 files, more files were not shown because too many files have changed in this diff. Show more