V0.2.6 (#150)
This commit is contained in:
parent
62f3953aea
commit
b4bba3391d
|
@ -9,37 +9,40 @@ jobs:
|
|||
test-backend:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-go@v4
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: 1.22.0
|
||||
- run: cd backend && go test -race -v ./...
|
||||
go-version: 'stable'
|
||||
- working-directory: backend
|
||||
run: go test -race -v ./...
|
||||
lint-backend:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-go@v4
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
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
|
||||
go-version: 'stable'
|
||||
- uses: golangci/golangci-lint-action@v6
|
||||
with:
|
||||
version: v1.59
|
||||
working-directory: backend
|
||||
format-backend:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-go@v4
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: 1.22.0
|
||||
- run: cd backend && go fmt ./...
|
||||
go-version: 'stable'
|
||||
- working-directory: backend
|
||||
run: go fmt ./...
|
||||
lint-frontend:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: '20'
|
||||
- run: cd frontend && npm i eslint
|
||||
- run: cd frontend && npm run lint
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
- working-directory: frontend
|
||||
run: npm i eslint && npm run lint
|
||||
|
||||
push_dev_to_registry:
|
||||
name: Push dev image
|
||||
runs-on: ubuntu-latest
|
||||
|
@ -63,7 +66,7 @@ jobs:
|
|||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
- name: Extract metadata (tags, labels) for Docker
|
||||
id: meta
|
||||
uses: docker/metadata-action@9ec57ed1fcdbf14dcef7dfbe97b2010124a938b7
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: gtstef/filebrowser
|
||||
- name: Build and push
|
||||
|
|
|
@ -9,37 +9,39 @@ jobs:
|
|||
test-backend:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-go@v4
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: 1.22.0
|
||||
- run: cd backend && go test -race -v ./...
|
||||
go-version: 'stable'
|
||||
- working-directory: backend
|
||||
run: go test -race -v ./...
|
||||
lint-backend:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-go@v4
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
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
|
||||
go-version: 'stable'
|
||||
- uses: golangci/golangci-lint-action@v6
|
||||
with:
|
||||
version: v1.59
|
||||
working-directory: backend
|
||||
format-backend:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-go@v4
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: 1.22.0
|
||||
- run: cd backend && go fmt ./...
|
||||
go-version: 'stable'
|
||||
- working-directory: backend
|
||||
run: go fmt ./...
|
||||
lint-frontend:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: '20'
|
||||
- run: cd frontend && npm i eslint
|
||||
- run: cd frontend && npm run lint
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
- working-directory: frontend
|
||||
run: npm i eslint && npm run lint
|
||||
|
||||
push_latest_to_registry:
|
||||
needs: [lint-frontend, lint-backend, test-backend, format-backend]
|
||||
|
@ -65,7 +67,7 @@ jobs:
|
|||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
- name: Extract metadata (tags, labels) for Docker
|
||||
id: meta
|
||||
uses: docker/metadata-action@9ec57ed1fcdbf14dcef7dfbe97b2010124a938b7
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: gtstef/filebrowser
|
||||
- name: Build and push
|
||||
|
|
|
@ -11,37 +11,39 @@ jobs:
|
|||
test-backend:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-go@v4
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: 1.22.0
|
||||
- run: cd backend && go test -race -v ./...
|
||||
go-version: 'stable'
|
||||
- working-directory: backend
|
||||
run: go test -race -v ./...
|
||||
lint-backend:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-go@v4
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
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
|
||||
go-version: 'stable'
|
||||
- uses: golangci/golangci-lint-action@v6
|
||||
with:
|
||||
version: v1.59
|
||||
working-directory: backend
|
||||
format-backend:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-go@v4
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: 1.22.0
|
||||
- run: cd backend && go fmt ./...
|
||||
go-version: 'stable'
|
||||
- working-directory: backend
|
||||
run: go fmt ./...
|
||||
lint-frontend:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: '20'
|
||||
- run: cd frontend && npm i eslint
|
||||
- run: cd frontend && npm run lint
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
- working-directory: frontend
|
||||
run: npm i eslint && npm run lint
|
||||
|
||||
push_pr_to_registry:
|
||||
name: Push PR
|
||||
|
@ -66,7 +68,7 @@ jobs:
|
|||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
- name: Extract metadata (tags, labels) for Docker
|
||||
id: meta
|
||||
uses: docker/metadata-action@9ec57ed1fcdbf14dcef7dfbe97b2010124a938b7
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: gtstef/filebrowser
|
||||
- name: Build and push
|
||||
|
|
|
@ -35,37 +35,40 @@ jobs:
|
|||
test-backend:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-go@v4
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: 1.22.0
|
||||
- run: cd backend && go test -race -v ./...
|
||||
go-version: 'stable'
|
||||
- working-directory: backend
|
||||
run: go test -race -v ./...
|
||||
lint-backend:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-go@v4
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
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
|
||||
go-version: 'stable'
|
||||
- uses: golangci/golangci-lint-action@v6
|
||||
with:
|
||||
version: v1.59
|
||||
working-directory: backend
|
||||
format-backend:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-go@v4
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: 1.22.0
|
||||
- run: cd backend && go fmt ./...
|
||||
go-version: 'stable'
|
||||
- working-directory: backend
|
||||
run: go fmt ./...
|
||||
lint-frontend:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: '20'
|
||||
- run: cd frontend && npm i eslint
|
||||
- run: cd frontend && npm run lint
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
- working-directory: frontend
|
||||
run: npm i eslint && npm run lint
|
||||
|
||||
push_release_to_registry:
|
||||
needs: [lint-frontend, lint-backend, test-backend, format-backend]
|
||||
name: Push release
|
||||
|
@ -91,7 +94,7 @@ jobs:
|
|||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
- name: Extract metadata (tags, labels) for Docker
|
||||
id: meta
|
||||
uses: docker/metadata-action@9ec57ed1fcdbf14dcef7dfbe97b2010124a938b7
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: gtstef/filebrowser
|
||||
- name: Strip v from version number
|
||||
|
|
|
@ -6,6 +6,8 @@ rice-box.go
|
|||
/filebrowser
|
||||
/filebrowser.exe
|
||||
/frontend/dist
|
||||
/frontend/pkg
|
||||
/frontend/package-lock.json
|
||||
/backend/vendor
|
||||
/backend/*.cov
|
||||
|
||||
|
|
22
CHANGELOG.md
22
CHANGELOG.md
|
@ -2,16 +2,34 @@
|
|||
|
||||
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
|
||||
|
||||
- Faature: [create-folder-feature](https://github.com/gtsteffaniak/filebrowser/pull/105)
|
||||
- Feature: [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
|
||||
|
|
|
@ -1,22 +1,18 @@
|
|||
FROM node:slim as nbuild
|
||||
WORKDIR /app
|
||||
COPY ./frontend/package*.json ./
|
||||
RUN npm ci --maxsockets 1
|
||||
RUN npm i --maxsockets 1
|
||||
COPY ./frontend/ ./
|
||||
RUN npm run build
|
||||
|
||||
FROM golang:1.22-alpine as base
|
||||
WORKDIR /app
|
||||
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
|
||||
WORKDIR /
|
||||
RUN apk --no-cache add ca-certificates mailcap
|
||||
COPY --from=base /app/filebrowser* ./
|
||||
COPY --from=nbuild /app/dist/ ./frontend/dist/
|
||||
ENTRYPOINT [ "./filebrowser" ]
|
||||
|
|
38
README.md
38
README.md
|
@ -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 file manager for the web</h3>
|
||||
<h3 align="center">Filebrowser - A modern web-based file manager</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,41 +15,59 @@
|
|||
|
||||
This fork makes the following significant changes to filebrowser for origin:
|
||||
|
||||
1. [x] Better search.
|
||||
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
|
||||
- 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">
|
||||
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. It can be used as a standalone app.
|
||||
directory.
|
||||
|
||||
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.
|
||||
|
||||
## 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:
|
||||
|
@ -101,7 +119,7 @@ volumes:
|
|||
|
||||
```
|
||||
|
||||
Not using docker (not recommended)
|
||||
Not using docker (not recommended) (Must donwload asset with frontend directory next to filebrowser binary)
|
||||
|
||||
```
|
||||
./filebrowser -f <filebrowser.yml or other /path/to/config.yaml>
|
||||
|
|
|
@ -3,6 +3,7 @@ package cmd
|
|||
import (
|
||||
"crypto/tls"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"log"
|
||||
"net"
|
||||
|
@ -64,49 +65,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, true)
|
||||
go files.InitializeIndex(serverConfig.IndexingInterval, serverConfig.Indexing)
|
||||
_, err := os.Stat(serverConfig.Root)
|
||||
checkErr(err)
|
||||
checkErr(fmt.Sprint("cmd os.Stat ", serverConfig.Root), err)
|
||||
var listener net.Listener
|
||||
address := serverConfig.Address + ":" + strconv.Itoa(serverConfig.Port)
|
||||
switch {
|
||||
case serverConfig.Socket != "":
|
||||
listener, err = net.Listen("unix", serverConfig.Socket)
|
||||
checkErr(err)
|
||||
checkErr("net.Listen", err)
|
||||
socketPerm, err := cmd.Flags().GetUint32("socket-perm") //nolint:govet
|
||||
checkErr(err)
|
||||
checkErr("cmd.Flags().GetUint32", err)
|
||||
err = os.Chmod(serverConfig.Socket, os.FileMode(socketPerm))
|
||||
checkErr(err)
|
||||
checkErr("os.Chmod", err)
|
||||
case serverConfig.TLSKey != "" && serverConfig.TLSCert != "":
|
||||
cer, err := tls.LoadX509KeyPair(serverConfig.TLSCert, serverConfig.TLSKey) //nolint:govet
|
||||
checkErr(err)
|
||||
checkErr("tls.LoadX509KeyPair", err)
|
||||
listener, err = tls.Listen("tcp", address, &tls.Config{
|
||||
MinVersion: tls.VersionTLS12,
|
||||
Certificates: []tls.Certificate{cer}},
|
||||
)
|
||||
checkErr(err)
|
||||
checkErr("tls.Listen", err)
|
||||
default:
|
||||
listener, err = net.Listen("tcp", address)
|
||||
checkErr(err)
|
||||
checkErr("net.Listen", 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(err)
|
||||
checkErr("fbhttp.NewHandler", err)
|
||||
defer listener.Close()
|
||||
log.Println("Listening on", listener.Addr().String())
|
||||
//nolint: gosec
|
||||
if err := http.Serve(listener, handler); err != nil {
|
||||
log.Fatal(err)
|
||||
log.Fatalf("Could not start server on port %d: %v", serverConfig.Port, err)
|
||||
}
|
||||
}, pythonConfig{allowNoDB: true}),
|
||||
}
|
||||
|
||||
func StartFilebrowser() {
|
||||
if err := rootCmd.Execute(); err != nil {
|
||||
log.Fatal(err)
|
||||
log.Fatal("Error starting filebrowser:", err)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -121,16 +122,16 @@ func quickSetup(d pythonData) {
|
|||
settings.Config.Auth.Key = generateKey()
|
||||
if settings.Config.Auth.Method == "noauth" {
|
||||
err := d.store.Auth.Save(&auth.NoAuth{})
|
||||
checkErr(err)
|
||||
checkErr("d.store.Auth.Save", err)
|
||||
} else {
|
||||
settings.Config.Auth.Method = "password"
|
||||
err := d.store.Auth.Save(&auth.JSONAuth{})
|
||||
checkErr(err)
|
||||
checkErr("d.store.Auth.Save", err)
|
||||
}
|
||||
err := d.store.Settings.Save(&settings.Config)
|
||||
checkErr(err)
|
||||
checkErr("d.store.Settings.Save", err)
|
||||
err = d.store.Settings.SaveServer(&settings.Config.Server)
|
||||
checkErr(err)
|
||||
checkErr("d.store.Settings.SaveServer", err)
|
||||
user := &users.User{}
|
||||
settings.Config.UserDefaults.Apply(user)
|
||||
user.Username = settings.Config.Auth.AdminUsername
|
||||
|
@ -150,5 +151,5 @@ func quickSetup(d pythonData) {
|
|||
Admin: true,
|
||||
}
|
||||
err = d.store.Users.Save(user)
|
||||
checkErr(err)
|
||||
checkErr("d.store.Users.Save", err)
|
||||
}
|
||||
|
|
|
@ -42,23 +42,23 @@ including 'index_end'.`,
|
|||
},
|
||||
Run: python(func(cmd *cobra.Command, args []string, d pythonData) {
|
||||
i, err := strconv.Atoi(args[0])
|
||||
checkErr(err)
|
||||
checkErr("strconv.Atoi", err)
|
||||
f := i
|
||||
if len(args) == 2 { //nolint:gomnd
|
||||
f, err = strconv.Atoi(args[1])
|
||||
checkErr(err)
|
||||
checkErr("strconv.Atoi", err)
|
||||
}
|
||||
|
||||
user := func(u *users.User) {
|
||||
u.Rules = append(u.Rules[:i], u.Rules[f+1:]...)
|
||||
err := d.store.Users.Save(u)
|
||||
checkErr(err)
|
||||
checkErr("d.store.Users.Save", err)
|
||||
}
|
||||
|
||||
global := func(s *settings.Settings) {
|
||||
s.Rules = append(s.Rules[:i], s.Rules[f+1:]...)
|
||||
err := d.store.Settings.Save(s)
|
||||
checkErr(err)
|
||||
checkErr("d.store.Settings.Save", err)
|
||||
}
|
||||
|
||||
runRules(d.store, cmd, user, global)
|
||||
|
|
|
@ -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(err)
|
||||
checkErr("st.Users.Get", 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(err)
|
||||
checkErr("st.Settings.Get", err)
|
||||
|
||||
if globalFn != nil {
|
||||
globalFn(s)
|
||||
|
|
|
@ -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(err)
|
||||
checkErr("d.store.Users.Save", err)
|
||||
}
|
||||
|
||||
global := func(s *settings.Settings) {
|
||||
s.Rules = append(s.Rules, rule)
|
||||
err := d.store.Settings.Save(s)
|
||||
checkErr(err)
|
||||
checkErr("d.store.Settings.Save", err)
|
||||
}
|
||||
|
||||
runRules(d.store, cmd, user, global)
|
||||
|
|
|
@ -22,19 +22,19 @@ var usersAddCmd = &cobra.Command{
|
|||
LockPassword: mustGetBool(cmd.Flags(), "lockPassword"),
|
||||
}
|
||||
servSettings, err := d.store.Settings.GetServer()
|
||||
checkErr(err)
|
||||
checkErr("d.store.Settings.GetServer()", 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(err)
|
||||
checkErr("d.store.Settings.Get()", err)
|
||||
|
||||
userHome, err := s2.MakeUserDir(user.Username, user.Scope, servSettings.Root)
|
||||
checkErr(err)
|
||||
checkErr("s2.MakeUserDir", err)
|
||||
user.Scope = userHome
|
||||
|
||||
err = d.store.Users.Save(user)
|
||||
checkErr(err)
|
||||
checkErr("d.store.Users.Save", err)
|
||||
printUsers([]*users.User{user})
|
||||
}, pythonConfig{}),
|
||||
}
|
||||
|
|
|
@ -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(err)
|
||||
checkErr("d.store.Users.Gets", err)
|
||||
|
||||
err = marshal(args[0], list)
|
||||
checkErr(err)
|
||||
checkErr("marshal", err)
|
||||
}, pythonConfig{}),
|
||||
}
|
||||
|
|
|
@ -46,6 +46,6 @@ var findUsers = python(func(cmd *cobra.Command, args []string, d pythonData) {
|
|||
list, err = d.store.Users.Gets("")
|
||||
}
|
||||
|
||||
checkErr(err)
|
||||
checkErr("findUsers", err)
|
||||
printUsers(list)
|
||||
}, pythonConfig{})
|
||||
|
|
|
@ -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(err)
|
||||
checkErr("os.Open", err)
|
||||
defer fd.Close()
|
||||
|
||||
list := []*users.User{}
|
||||
err = unmarshal(args[0], &list)
|
||||
checkErr(err)
|
||||
checkErr("unmarshal", err)
|
||||
|
||||
for _, user := range list {
|
||||
err = user.Clean("")
|
||||
checkErr(err)
|
||||
checkErr("Clean", err)
|
||||
}
|
||||
|
||||
if mustGetBool(cmd.Flags(), "replace") {
|
||||
oldUsers, err := d.store.Users.Gets("")
|
||||
checkErr(err)
|
||||
checkErr("d.store.Users.Gets", err)
|
||||
|
||||
err = marshal("users.backup.json", list)
|
||||
checkErr(err)
|
||||
checkErr("marshal users.backup.json", err)
|
||||
|
||||
for _, user := range oldUsers {
|
||||
err = d.store.Users.Delete(user.ID)
|
||||
checkErr(err)
|
||||
checkErr("d.store.Users.Delete", err)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -60,7 +60,8 @@ list or set it to 0.`,
|
|||
// User exists in DB.
|
||||
if err == nil {
|
||||
if !overwrite {
|
||||
checkErr(errors.New("user " + strconv.Itoa(int(user.ID)) + " is already registred"))
|
||||
newErr := errors.New("user " + strconv.Itoa(int(user.ID)) + " is already registered")
|
||||
checkErr("", newErr)
|
||||
}
|
||||
|
||||
// If the usernames mismatch, check if there is another one in the DB
|
||||
|
@ -68,7 +69,8 @@ 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
|
||||
checkErr(usernameConflictError(user.Username, conflictuous.ID, user.ID))
|
||||
newErr := usernameConflictError(user.Username, conflictuous.ID, user.ID)
|
||||
checkErr("usernameConflictError", newErr)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
@ -78,7 +80,7 @@ list or set it to 0.`,
|
|||
}
|
||||
|
||||
err = d.store.Users.Save(user)
|
||||
checkErr(err)
|
||||
checkErr("d.store.Users.Save", err)
|
||||
}
|
||||
}, pythonConfig{}),
|
||||
}
|
||||
|
|
|
@ -25,7 +25,7 @@ var usersRmCmd = &cobra.Command{
|
|||
err = d.store.Users.Delete(id)
|
||||
}
|
||||
|
||||
checkErr(err)
|
||||
checkErr("usersRmCmd", err)
|
||||
fmt.Println("user deleted successfully")
|
||||
}, pythonConfig{}),
|
||||
}
|
||||
|
|
|
@ -29,10 +29,10 @@ options you want to change.`,
|
|||
} else {
|
||||
user, err = d.store.Users.Get("", username)
|
||||
}
|
||||
checkErr(err)
|
||||
checkErr("d.store.Users.Get", err)
|
||||
|
||||
err = d.store.Users.Update(user)
|
||||
checkErr(err)
|
||||
checkErr("d.store.Users.Update", err)
|
||||
printUsers([]*users.User{user})
|
||||
}, pythonConfig{}),
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ package cmd
|
|||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
@ -17,33 +18,33 @@ import (
|
|||
"github.com/gtsteffaniak/filebrowser/storage/bolt"
|
||||
)
|
||||
|
||||
func checkErr(err error) {
|
||||
func checkErr(source string, err error) {
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
log.Fatalf("%s: %v", source, err)
|
||||
}
|
||||
}
|
||||
|
||||
func mustGetString(flags *pflag.FlagSet, flag string) string {
|
||||
s, err := flags.GetString(flag)
|
||||
checkErr(err)
|
||||
checkErr("mustGetString", err)
|
||||
return s
|
||||
}
|
||||
|
||||
func mustGetBool(flags *pflag.FlagSet, flag string) bool {
|
||||
b, err := flags.GetBool(flag)
|
||||
checkErr(err)
|
||||
checkErr("mustGetBool", err)
|
||||
return b
|
||||
}
|
||||
|
||||
func mustGetUint(flags *pflag.FlagSet, flag string) uint {
|
||||
b, err := flags.GetUint(flag)
|
||||
checkErr(err)
|
||||
checkErr("mustGetUint", err)
|
||||
return b
|
||||
}
|
||||
|
||||
func generateKey() []byte {
|
||||
k, err := settings.GenerateKey()
|
||||
checkErr(err)
|
||||
checkErr("generateKey", err)
|
||||
return k
|
||||
}
|
||||
|
||||
|
@ -96,18 +97,19 @@ func python(fn pythonFunc, cfg pythonConfig) cobraFunc {
|
|||
|
||||
data.hadDB = exists
|
||||
db, err := storm.Open(path)
|
||||
checkErr(err)
|
||||
checkErr(fmt.Sprintf("storm.Open path %v", path), err)
|
||||
|
||||
defer db.Close()
|
||||
data.store, err = bolt.NewStorage(db)
|
||||
checkErr(err)
|
||||
checkErr("bolt.NewStorage", err)
|
||||
fn(cmd, args, data)
|
||||
}
|
||||
}
|
||||
|
||||
func marshal(filename string, data interface{}) error {
|
||||
fd, err := os.Create(filename)
|
||||
checkErr(err)
|
||||
|
||||
checkErr("os.Create", err)
|
||||
defer fd.Close()
|
||||
|
||||
switch ext := filepath.Ext(filename); ext {
|
||||
|
@ -125,7 +127,7 @@ func marshal(filename string, data interface{}) error {
|
|||
|
||||
func unmarshal(filename string, data interface{}) error {
|
||||
fd, err := os.Open(filename)
|
||||
checkErr(err)
|
||||
checkErr("os.Open", err)
|
||||
defer fd.Close()
|
||||
|
||||
switch ext := filepath.Ext(filename); ext {
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
server:
|
||||
port: 80
|
||||
port: 8080
|
||||
baseURL: "/"
|
||||
root: "/srv"
|
||||
root: "/Users/steffag/"
|
||||
indexing: false
|
||||
auth:
|
||||
method: password
|
||||
signup: false
|
||||
|
|
|
@ -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,11 +286,15 @@ func (i *FileInfo) addContent(path string) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c := string(string(content))
|
||||
if !utf8.ValidString(c) {
|
||||
stringContent := string(content)
|
||||
if !utf8.ValidString(stringContent) {
|
||||
return nil
|
||||
}
|
||||
i.Content = string(c)
|
||||
if stringContent == "" {
|
||||
i.Content = "empty-file-x6OlSil"
|
||||
return nil
|
||||
}
|
||||
i.Content = stringContent
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"name": "frontend",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {}
|
||||
}
|
|
@ -167,7 +167,6 @@ 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
|
||||
})
|
||||
|
||||
|
@ -272,6 +271,10 @@ 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
|
||||
}
|
||||
|
||||
|
|
|
@ -64,9 +64,7 @@ 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
|
||||
|
@ -104,7 +102,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, "index.html", "text/html; charset=utf-8")
|
||||
return handleWithStaticData(w, r, d, assetsFs, "public/index.html", "text/html; charset=utf-8")
|
||||
}, "", store, server)
|
||||
|
||||
static = handle(func(w http.ResponseWriter, r *http.Request, d *data) (int, error) {
|
||||
|
|
|
@ -58,6 +58,7 @@ func setDefaults() Settings {
|
|||
Database: "database.db",
|
||||
Log: "stdout",
|
||||
Root: "/srv",
|
||||
Indexing: true,
|
||||
},
|
||||
Auth: Auth{
|
||||
TokenExpirationTime: "2h",
|
||||
|
|
|
@ -54,6 +54,7 @@ type Server struct {
|
|||
Root string `json:"root"`
|
||||
UserHomeBasePath string `json:"userHomeBasePath"`
|
||||
CreateUserDir bool `json:"createUserDir"`
|
||||
Indexing bool `json:"indexing"`
|
||||
}
|
||||
|
||||
type Frontend struct {
|
||||
|
|
|
@ -2,7 +2,7 @@ package version
|
|||
|
||||
var (
|
||||
// Version is the current File Browser version.
|
||||
Version = "(0.2.5)"
|
||||
Version = "(0.2.6)"
|
||||
// CommitSHA is the commmit sha.
|
||||
CommitSHA = "(unknown)"
|
||||
)
|
||||
|
|
|
@ -11,6 +11,7 @@ server:
|
|||
CreateUserDir: false
|
||||
UserHomeBasePath: ""
|
||||
indexingInterval: 5
|
||||
indexing: true
|
||||
numImageProcessors: 4
|
||||
socket: ""
|
||||
tlsKey: ""
|
||||
|
@ -113,6 +114,8 @@ 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.
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
{
|
||||
"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"
|
||||
}
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
module.exports = {
|
||||
presets: ["@vue/app"],
|
||||
};
|
|
@ -1,47 +0,0 @@
|
|||
{
|
||||
"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
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -1,15 +1,21 @@
|
|||
{
|
||||
"name": "filebrowser-frontend",
|
||||
"version": "2.0.0",
|
||||
"version": "3.0.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"engines": {
|
||||
"npm": ">=7.0.0",
|
||||
"node": ">=18.0.0"
|
||||
},
|
||||
"scripts": {
|
||||
"serve": "vue-cli-service serve",
|
||||
"build": "vue-cli-service build",
|
||||
"fix": "npx vue-cli-service lint",
|
||||
"watch": "vue-cli-service build --watch",
|
||||
"lint": "eslint src/",
|
||||
"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 ."
|
||||
"format": "prettier --write .",
|
||||
"test": "playwright test"
|
||||
},
|
||||
"dependencies": {
|
||||
"ace-builds": "^1.24.2",
|
||||
|
@ -17,25 +23,22 @@
|
|||
"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",
|
||||
"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"
|
||||
"qrcode.vue": "^3.4.1",
|
||||
"vue": "^3.4.21",
|
||||
"vue-i18n": "^9.10.2",
|
||||
"vue-lazyload": "^3.0.0",
|
||||
"vue-router": "^4.3.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vue/cli-service": "^5.0.8",
|
||||
"compression-webpack-plugin": "^10.0.0",
|
||||
"eslint": "^9.4.0",
|
||||
"eslint-plugin-vue": "^9.26.0",
|
||||
"vue-template-compiler": "^2.6.17"
|
||||
"@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"
|
||||
}
|
||||
}
|
|
@ -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"></script>
|
||||
<script src="[{[ .ReCaptchaHost ]}]/recaptcha/api.js?render=explicit" data-vite-ignore></script>
|
||||
[{[ end ]}]
|
||||
|
||||
<title>[{[ if .Name -]}][{[ .Name ]}][{[ else ]}]File Browser[{[ end ]}]</title>
|
||||
|
@ -121,7 +121,7 @@
|
|||
<div id="app"></div>
|
||||
|
||||
[{[ if .darkMode -]}]
|
||||
<div id="loading dark-mode">
|
||||
<div id="loading" class="dark-mode">
|
||||
<div class="spinner">
|
||||
<div class="bounce1"></div>
|
||||
<div class="bounce2"></div>
|
||||
|
@ -136,6 +136,8 @@
|
|||
<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 ]}]
|
||||
|
|
|
@ -3,13 +3,22 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
// eslint-disable-next-line no-undef
|
||||
__webpack_public_path__ = window.FileBrowser.StaticURL + "/";
|
||||
import { onMounted } from 'vue';
|
||||
import { mutations } from "@/store"; // Import your store's mutations
|
||||
|
||||
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>
|
||||
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
import { removePrefix } from "./utils";
|
||||
import { baseURL } from "@/utils/constants";
|
||||
import store from "@/store";
|
||||
import { state } 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=${store.state.jwt}`;
|
||||
url = `${protocol}//${window.location.host}${baseURL}/api/command${url}?auth=${state.jwt}`;
|
||||
|
||||
let conn = new window.WebSocket(url);
|
||||
conn.onopen = () => conn.send(command);
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { createURL, fetchURL, removePrefix } from "./utils";
|
||||
import { baseURL } from "@/utils/constants";
|
||||
import store from "@/store";
|
||||
import { state } 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 (store.state.jwt) {
|
||||
url += `auth=${store.state.jwt}&`;
|
||||
if (state.jwt) {
|
||||
url += `auth=${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", store.state.jwt);
|
||||
request.setRequestHeader("X-Auth", state.jwt);
|
||||
|
||||
if (typeof onupload === "function") {
|
||||
request.upload.onprogress = onupload;
|
||||
|
|
|
@ -18,6 +18,7 @@ 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}`;
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { fetchURL, fetchJSON } from "./utils";
|
||||
import { fetchURL, fetchJSON } from "@/api/utils";
|
||||
|
||||
export async function getAll() {
|
||||
export async function getAllUsers() {
|
||||
return await fetchJSON(`/api/users`, {});
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
import store from "@/store";
|
||||
import { state } 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 || {};
|
||||
|
@ -13,21 +14,22 @@ export async function fetchURL(url, opts, auth = true) {
|
|||
try {
|
||||
res = await fetch(`${baseURL}${url}`, {
|
||||
headers: {
|
||||
"X-Auth": store.state.jwt,
|
||||
"sessionId": store.state.sessionId,
|
||||
"userScope": store.state.user.scope,
|
||||
"X-Auth": state.jwt,
|
||||
"sessionId": state.sessionId,
|
||||
"userScope": state.user.scope,
|
||||
...headers,
|
||||
},
|
||||
...rest,
|
||||
});
|
||||
} catch {
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
const error = new Error("000 No connection");
|
||||
error.status = res.status;
|
||||
throw error;
|
||||
}
|
||||
|
||||
if (auth && res.headers.get("X-Renew-Token") === "true") {
|
||||
await renew(store.state.jwt);
|
||||
await renew(state.jwt);
|
||||
}
|
||||
|
||||
if (res.status < 200 || res.status > 299) {
|
||||
|
@ -46,17 +48,16 @@ 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;
|
||||
|
@ -70,7 +71,7 @@ export function createURL(endpoint, params = {}, auth = true) {
|
|||
const url = new URL(prefix + encodePath(endpoint), origin);
|
||||
|
||||
const searchParams = {
|
||||
...(auth && { auth: store.state.jwt }),
|
||||
...(auth && { auth: state.jwt }),
|
||||
...params,
|
||||
};
|
||||
|
||||
|
|
|
@ -18,8 +18,8 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState } from "vuex";
|
||||
import Action from "@/components/header/Action";
|
||||
import { state, mutations } from "@/store"; // Import mutations as well
|
||||
import Action from "@/components/header/Action.vue";
|
||||
|
||||
export default {
|
||||
name: "breadcrumbs",
|
||||
|
@ -28,10 +28,8 @@ export default {
|
|||
},
|
||||
props: ["base", "noLink"],
|
||||
computed: {
|
||||
...mapState(["req", "user"]),
|
||||
items() {
|
||||
|
||||
const relativePath = this.$route.path.replace(this.base, "");
|
||||
const relativePath = state.route.path.replace(this.base, "");
|
||||
let parts = relativePath.split("/");
|
||||
|
||||
if (parts[0] === "") {
|
||||
|
@ -76,13 +74,18 @@ export default {
|
|||
return "router-link";
|
||||
},
|
||||
showShare() {
|
||||
if (this.$route.path.startsWith("/share")) {
|
||||
return;
|
||||
// Ensure user properties are accessed safely
|
||||
if (state.route.path.startsWith("/share")) {
|
||||
return false;
|
||||
}
|
||||
return this.user.perm.share;
|
||||
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 })
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
|
|
|
@ -36,15 +36,12 @@ 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
|
||||
|
|
|
@ -0,0 +1,221 @@
|
|||
<!-- 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>
|
|
@ -1,10 +1,8 @@
|
|||
<template>
|
||||
<div
|
||||
id="search"
|
||||
@click="open"
|
||||
v-bind:class="{ active, ongoing, 'dark-mode': isDarkMode }"
|
||||
>
|
||||
<div id="search" @click="open" :class="{ active, ongoing, 'dark-mode': isDarkMode }">
|
||||
<!-- Search input section -->
|
||||
<div id="input">
|
||||
<!-- Close button visible when search is active -->
|
||||
<button
|
||||
v-if="active"
|
||||
class="action"
|
||||
|
@ -14,7 +12,9 @@
|
|||
>
|
||||
<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,9 +27,12 @@
|
|||
: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"
|
||||
|
@ -50,15 +53,18 @@
|
|||
</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>
|
||||
<template v-if="isEmpty">
|
||||
<div v-if="isEmpty">
|
||||
<!-- Reset filters button -->
|
||||
<button
|
||||
class="mobile-boxes"
|
||||
v-if="value.length === 0 && !showBoxes"
|
||||
|
@ -66,7 +72,8 @@
|
|||
>
|
||||
Reset filters
|
||||
</button>
|
||||
<template v-if="value.length === 0 && showBoxes">
|
||||
<!-- Box types when no search input is present -->
|
||||
<div v-if="value.length === 0 && showBoxes">
|
||||
<div class="boxes">
|
||||
<h3>{{ $t("search.types") }}</h3>
|
||||
<div>
|
||||
|
@ -84,21 +91,26 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</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">
|
||||
<template>
|
||||
<div>
|
||||
<!-- 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 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
|
||||
|
@ -122,7 +134,8 @@
|
|||
search times.
|
||||
</p>
|
||||
</div>
|
||||
<template>
|
||||
<div>
|
||||
<!-- Button groups for filtering search results -->
|
||||
<ButtonGroup
|
||||
:buttons="folderSelect"
|
||||
@button-clicked="addToTypes"
|
||||
|
@ -136,6 +149,7 @@
|
|||
@remove-button-clicked="removeFromTypes"
|
||||
:isDisabled="isTypeSelectDisabled"
|
||||
/>
|
||||
<!-- Inputs for filtering by file size -->
|
||||
<div class="sizeConstraints">
|
||||
<div class="sizeInputWrapper">
|
||||
<p>Smaller Than:</p>
|
||||
|
@ -159,8 +173,9 @@
|
|||
<p>MB</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<!-- List of search results -->
|
||||
<ul v-show="results.length > 0">
|
||||
<li
|
||||
v-for="(s, k) in results"
|
||||
|
@ -185,6 +200,246 @@
|
|||
</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 {
|
||||
|
@ -198,6 +453,7 @@
|
|||
color: white;
|
||||
border-left: 1px solid gray;
|
||||
border-right: 1px solid gray;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
#result-desktop > #result-list {
|
||||
|
@ -262,7 +518,7 @@
|
|||
|
||||
/* Search */
|
||||
#search {
|
||||
background-color: unset;
|
||||
background-color: unset !important;
|
||||
z-index: 3;
|
||||
position: fixed;
|
||||
top: 0.5em;
|
||||
|
@ -509,244 +765,3 @@ 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>
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
<template>
|
||||
<nav :class="{ active, 'dark-mode': isDarkMode }">
|
||||
<template v-if="isLogged">
|
||||
<!-- Section for logged-in users -->
|
||||
<template v-if="isLoggedIn">
|
||||
<!-- My Files button -->
|
||||
<button
|
||||
class="action"
|
||||
@click="toRoot"
|
||||
|
@ -10,9 +12,12 @@
|
|||
<i class="material-icons">folder</i>
|
||||
<span>{{ $t("sidebar.myFiles") }}</span>
|
||||
</button>
|
||||
<div v-if="user.perm.create">
|
||||
|
||||
<!-- Buttons visible if user has create permission -->
|
||||
<div v-if="user.perm?.create">
|
||||
<!-- New Folder button -->
|
||||
<button
|
||||
@click="$store.commit('showHover', 'newDir')"
|
||||
@click="showHover('newDir')"
|
||||
class="action"
|
||||
:aria-label="$t('sidebar.newFolder')"
|
||||
:title="$t('sidebar.newFolder')"
|
||||
|
@ -20,8 +25,9 @@
|
|||
<i class="material-icons">create_new_folder</i>
|
||||
<span>{{ $t("sidebar.newFolder") }}</span>
|
||||
</button>
|
||||
<!-- New File button -->
|
||||
<button
|
||||
@click="$store.commit('showHover', 'newFile')"
|
||||
@click="showHover('newFile')"
|
||||
class="action"
|
||||
:aria-label="$t('sidebar.newFile')"
|
||||
:title="$t('sidebar.newFile')"
|
||||
|
@ -29,12 +35,16 @@
|
|||
<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"
|
||||
|
@ -44,7 +54,7 @@
|
|||
<i class="material-icons">settings_applications</i>
|
||||
<span>{{ $t("sidebar.settings") }}</span>
|
||||
</button>
|
||||
|
||||
<!-- Logout button -->
|
||||
<button
|
||||
v-if="canLogout"
|
||||
@click="logout"
|
||||
|
@ -58,7 +68,10 @@
|
|||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- Section for non-logged-in users -->
|
||||
<template v-else>
|
||||
<!-- Login button -->
|
||||
<router-link
|
||||
class="action"
|
||||
to="/login"
|
||||
|
@ -68,6 +81,7 @@
|
|||
<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"
|
||||
|
@ -79,10 +93,9 @@
|
|||
<span>{{ $t("sidebar.signup") }}</span>
|
||||
</router-link>
|
||||
</template>
|
||||
<div
|
||||
class="credits"
|
||||
v-if="$router.currentRoute.path.includes('/files/') && !disableUsedPercentage"
|
||||
>
|
||||
|
||||
<!-- Credits and usage information section -->
|
||||
<div class="credits" v-if="isFiles && !disableUsedPercentage && usage">
|
||||
<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>
|
||||
|
@ -104,8 +117,8 @@
|
|||
</div>
|
||||
</nav>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState, mapGetters } from "vuex";
|
||||
import * as upload from "@/utils/upload";
|
||||
import * as auth from "@/utils/auth";
|
||||
import {
|
||||
|
@ -117,39 +130,51 @@ import {
|
|||
loginPage,
|
||||
} from "@/utils/constants";
|
||||
import { files as api } from "@/api";
|
||||
import ProgressBar from "vue-simple-progress";
|
||||
import ProgressBar from "@/components/ProgressBar.vue";
|
||||
import { getHumanReadableFilesize } from "@/utils/filesizes";
|
||||
import { darkMode } from "@/utils/constants";
|
||||
import { state, getters, mutations } from "@/store"; // Import your custom store
|
||||
import { showError } from "@/notify";
|
||||
|
||||
export default {
|
||||
name: "sidebar",
|
||||
components: {
|
||||
ProgressBar,
|
||||
},
|
||||
computed: {
|
||||
...mapState(["user"]),
|
||||
isDarkMode() {
|
||||
return this.user && Object.prototype.hasOwnProperty.call(this.user, "darkMode")
|
||||
? this.user.darkMode
|
||||
: darkMode;
|
||||
mounted() {
|
||||
this.updateUsage();
|
||||
},
|
||||
computed: {
|
||||
isFiles() {
|
||||
return getters.isFiles();
|
||||
},
|
||||
user() {
|
||||
return state.user;
|
||||
},
|
||||
isDarkMode() {
|
||||
return getters.isDarkMode();
|
||||
},
|
||||
isLoggedIn() {
|
||||
return getters.isLoggedIn();
|
||||
},
|
||||
currentPrompt() {
|
||||
return getters.currentPrompt();
|
||||
},
|
||||
...mapGetters(["isLogged", "currentPrompt"]),
|
||||
active() {
|
||||
return this.currentPrompt?.prompt === "sidebar";
|
||||
return getters.currentPromptName() === "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 };
|
||||
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;
|
||||
}
|
||||
|
@ -161,40 +186,35 @@ export default {
|
|||
usedPercentage: Math.round((usage.used / usage.total) * 100),
|
||||
};
|
||||
} catch (error) {
|
||||
this.$showError(error);
|
||||
showError("Error fetching usage:", error);
|
||||
}
|
||||
return usageStats;
|
||||
console.log(usageStats);
|
||||
mutations.setUsage(usageStats);
|
||||
},
|
||||
default: { used: "0 B", total: "0 B", usedPercentage: 0 },
|
||||
shouldUpdate() {
|
||||
return this.$router.currentRoute.path.includes("/files/");
|
||||
showHover(value) {
|
||||
return mutations.showHover(value);
|
||||
},
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
// Navigate to the root files directory
|
||||
toRoot() {
|
||||
this.$router.push({ path: "/files/" }, () => {});
|
||||
this.$store.commit("closeHovers");
|
||||
mutations.closeHovers();
|
||||
},
|
||||
// Navigate to the settings page
|
||||
toSettings() {
|
||||
this.$router.push({ path: "/settings" }, () => {});
|
||||
this.$store.commit("closeHovers");
|
||||
mutations.closeHovers();
|
||||
},
|
||||
// Show the help overlay
|
||||
help() {
|
||||
this.$store.commit("showHover", "help");
|
||||
mutations.showHover("help");
|
||||
},
|
||||
upload: function () {
|
||||
if (
|
||||
typeof window.DataTransferItem !== "undefined" &&
|
||||
typeof DataTransferItem.prototype.webkitGetAsEntry !== "undefined"
|
||||
) {
|
||||
this.$store.commit("showHover", "upload");
|
||||
} else {
|
||||
document.getElementById("upload-input").click();
|
||||
}
|
||||
// Handle file upload
|
||||
upload(event) {
|
||||
return this.$upload(event);
|
||||
},
|
||||
// Handle files selected for upload
|
||||
uploadInput(event) {
|
||||
this.$store.commit("closeHovers");
|
||||
mutations.closeHovers();
|
||||
|
||||
let files = event.currentTarget.files;
|
||||
let folder_upload =
|
||||
|
@ -207,17 +227,15 @@ export default {
|
|||
}
|
||||
}
|
||||
|
||||
let path = this.$route.path.endsWith("/")
|
||||
? this.$route.path
|
||||
: this.$route.path + "/";
|
||||
let conflict = upload.checkConflict(files, this.req.items);
|
||||
let path = getters.getRoutePath();
|
||||
let conflict = upload.checkConflict(files, state.req.items);
|
||||
|
||||
if (conflict) {
|
||||
this.$store.commit("showHover", {
|
||||
prompt: "replace",
|
||||
mutations.showHover({
|
||||
name: "replace",
|
||||
confirm: (event) => {
|
||||
event.preventDefault();
|
||||
this.$store.commit("closeHovers");
|
||||
mutations.closeHovers();
|
||||
upload.handleFiles(files, path, true);
|
||||
},
|
||||
});
|
||||
|
@ -227,6 +245,7 @@ export default {
|
|||
|
||||
upload.handleFiles(files, path);
|
||||
},
|
||||
// Logout the user
|
||||
logout: auth.logout,
|
||||
},
|
||||
};
|
||||
|
|
|
@ -11,17 +11,19 @@
|
|||
@wheel="wheelMove"
|
||||
>
|
||||
<img
|
||||
src=""
|
||||
v-if="!isTiff"
|
||||
: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 UTIF from "utif";
|
||||
|
||||
import { showError } from "@/notify";
|
||||
export default {
|
||||
props: {
|
||||
src: String,
|
||||
|
@ -55,15 +57,18 @@ export default {
|
|||
},
|
||||
maxScale: 4,
|
||||
minScale: 0.25,
|
||||
isTiff: false, // Determine if the image is a TIFF
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
if (!this.decodeUTIF()) {
|
||||
this.isTiff = this.checkIfTiff(this.src);
|
||||
if (this.isTiff) {
|
||||
this.decodeTiff(this.src);
|
||||
} else {
|
||||
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%";
|
||||
}
|
||||
|
@ -79,7 +84,10 @@ export default {
|
|||
},
|
||||
watch: {
|
||||
src: function () {
|
||||
if (!this.decodeUTIF()) {
|
||||
this.isTiff = this.checkIfTiff(this.src);
|
||||
if (this.isTiff) {
|
||||
this.decodeTiff(this.src);
|
||||
} else {
|
||||
this.$refs.imgex.src = this.src;
|
||||
}
|
||||
|
||||
|
@ -89,19 +97,29 @@ export default {
|
|||
},
|
||||
},
|
||||
methods: {
|
||||
// Modified from UTIF.replaceIMG
|
||||
decodeUTIF() {
|
||||
checkIfTiff(src) {
|
||||
const sufs = ["tif", "tiff", "dng", "cr2", "nef"];
|
||||
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;
|
||||
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);
|
||||
}
|
||||
},
|
||||
onLoad() {
|
||||
let img = this.$refs.imgex;
|
||||
|
@ -146,9 +164,7 @@ 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
|
||||
);
|
||||
|
@ -275,6 +291,7 @@ export default {
|
|||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.image-ex-container {
|
||||
margin: auto;
|
||||
|
|
|
@ -24,7 +24,11 @@
|
|||
:class="{ activeimg: this.isMaximized && this.isSelected }"
|
||||
ref="thumbnail"
|
||||
/>
|
||||
<i :class="{ iconActive: this.isMaximized && this.isSelected }" v-else class="material-icons"></i>
|
||||
<i
|
||||
:class="{ iconActive: this.isMaximized && this.isSelected }"
|
||||
v-else
|
||||
class="material-icons"
|
||||
></i>
|
||||
</div>
|
||||
|
||||
<div :class="{ activecontent: this.isMaximized && this.isSelected }">
|
||||
|
@ -64,10 +68,10 @@
|
|||
<script>
|
||||
import { enableThumbs } from "@/utils/constants";
|
||||
import { getHumanReadableFilesize } from "@/utils/filesizes";
|
||||
import { mapMutations, mapGetters, mapState } from "vuex";
|
||||
import moment from "moment";
|
||||
import { fromNow } from "@/utils/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",
|
||||
|
@ -90,32 +94,44 @@ export default {
|
|||
"path",
|
||||
],
|
||||
computed: {
|
||||
...mapState(["user", "selected", "req", "jwt"]),
|
||||
...mapGetters(["selectedCount"]),
|
||||
user() {
|
||||
return state.user;
|
||||
},
|
||||
selected() {
|
||||
return state.selected;
|
||||
},
|
||||
req() {
|
||||
return state.req;
|
||||
},
|
||||
jwt() {
|
||||
return state.jwt;
|
||||
},
|
||||
selectedCount() {
|
||||
return getters.selectedCount();
|
||||
},
|
||||
isClicked() {
|
||||
if (this.user.singleClick || !this.allowedView ) {
|
||||
if (state.user.singleClick || !this.allowedView) {
|
||||
return false;
|
||||
}
|
||||
// Assuming toggleClick returns a boolean value
|
||||
return !this.isMaximized;
|
||||
},
|
||||
allowedView() {
|
||||
return this.user.viewMode != "gallery" && this.user.viewMode != "normal"
|
||||
return state.user.viewMode != "gallery" && state.user.viewMode != "normal";
|
||||
},
|
||||
singleClick() {
|
||||
return this.readOnly == undefined && this.user.singleClick;
|
||||
return this.readOnly == undefined && state.user.singleClick;
|
||||
},
|
||||
isSelected() {
|
||||
return this.selected.indexOf(this.index) !== -1;
|
||||
},
|
||||
isDraggable() {
|
||||
return this.readOnly == undefined && this.user.perm.rename;
|
||||
return this.readOnly == undefined && state.user.perm?.rename;
|
||||
},
|
||||
canDrop() {
|
||||
if (!this.isDir || this.readOnly !== undefined) return false;
|
||||
|
||||
for (let i of this.selected) {
|
||||
if (this.req.items[i].url === this.url) {
|
||||
if (state.req.items[i].url === this.url) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -123,9 +139,9 @@ export default {
|
|||
return true;
|
||||
},
|
||||
thumbnailUrl() {
|
||||
let path = this.req.path
|
||||
if (this.req.path == "/") {
|
||||
path = ""
|
||||
let path = state.req.path;
|
||||
if (state.req.path == "/") {
|
||||
path = "";
|
||||
}
|
||||
const file = {
|
||||
path: path + "/" + this.name,
|
||||
|
@ -144,7 +160,7 @@ export default {
|
|||
mounted() {
|
||||
const observer = new IntersectionObserver(this.handleIntersect, {
|
||||
root: null,
|
||||
rootMargin: '0px',
|
||||
rootMargin: "0px",
|
||||
threshold: 0.5, // Adjust threshold as needed
|
||||
});
|
||||
|
||||
|
@ -156,7 +172,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
|
||||
|
@ -167,27 +183,26 @@ 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 && this.user.dateFormat) {
|
||||
return moment(this.modified).format("L LT");
|
||||
if (this.readOnly == undefined && state.user.dateFormat) {
|
||||
return fromNow(this.modified, state.user.locale).format("L LT");
|
||||
}
|
||||
return moment(this.modified).fromNow();
|
||||
return fromNow(this.modified, state.user.locale);
|
||||
},
|
||||
dragStart: function () {
|
||||
if (this.selectedCount === 0) {
|
||||
this.addSelected(this.index);
|
||||
if (getters.selectedCount() === 0) {
|
||||
mutations.addSelected(this.index);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.isSelected) {
|
||||
this.resetSelected();
|
||||
this.addSelected(this.index);
|
||||
mutations.resetSelected();
|
||||
mutations.addSelected(this.index);
|
||||
}
|
||||
},
|
||||
dragOver: function (event) {
|
||||
|
@ -208,7 +223,7 @@ export default {
|
|||
if (!this.canDrop) return;
|
||||
event.preventDefault();
|
||||
|
||||
if (this.selectedCount === 0) return;
|
||||
if (getters.selectedCount() === 0) return;
|
||||
|
||||
let el = event.target;
|
||||
for (let i = 0; i < 5; i++) {
|
||||
|
@ -221,9 +236,9 @@ export default {
|
|||
|
||||
for (let i of this.selected) {
|
||||
items.push({
|
||||
from: this.req.items[i].url,
|
||||
to: this.url + encodeURIComponent(this.req.items[i].name),
|
||||
name: this.req.items[i].name,
|
||||
from: state.req.items[i].url,
|
||||
to: this.url + encodeURIComponent(state.req.items[i].name),
|
||||
name: state.req.items[i].name,
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -235,9 +250,9 @@ export default {
|
|||
api
|
||||
.move(items, overwrite, rename)
|
||||
.then(() => {
|
||||
this.$store.commit("setReload", true);
|
||||
mutations.setReload(true);
|
||||
})
|
||||
.catch(this.$showError);
|
||||
.catch(showError);
|
||||
};
|
||||
|
||||
let conflict = upload.checkConflict(items, baseItems);
|
||||
|
@ -246,14 +261,14 @@ export default {
|
|||
let rename = false;
|
||||
|
||||
if (conflict) {
|
||||
this.$store.commit("showHover", {
|
||||
prompt: "replace-rename",
|
||||
mutations.showHover({
|
||||
name: "replace-rename",
|
||||
confirm: (event, option) => {
|
||||
overwrite = option == "overwrite";
|
||||
rename = option == "rename";
|
||||
|
||||
event.preventDefault();
|
||||
this.$store.commit("closeHovers");
|
||||
mutations.closeHovers();
|
||||
action(overwrite, rename);
|
||||
},
|
||||
});
|
||||
|
@ -264,11 +279,11 @@ export default {
|
|||
action(overwrite, rename);
|
||||
},
|
||||
itemClick: function (event) {
|
||||
if (this.singleClick && !this.$store.state.multiple) this.open();
|
||||
if (this.singleClick && !state.multiple) this.open();
|
||||
else this.click(event);
|
||||
},
|
||||
click: function (event) {
|
||||
if (!this.singleClick && this.selectedCount !== 0) event.preventDefault();
|
||||
if (!this.singleClick && getters.selectedCount() !== 0) event.preventDefault();
|
||||
|
||||
setTimeout(() => {
|
||||
this.touches = 0;
|
||||
|
@ -279,8 +294,8 @@ export default {
|
|||
this.open();
|
||||
}
|
||||
|
||||
if (this.$store.state.selected.indexOf(this.index) !== -1) {
|
||||
this.removeSelected(this.index);
|
||||
if (state.selected.indexOf(this.index) !== -1) {
|
||||
mutations.removeSelected(this.index);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -297,21 +312,16 @@ export default {
|
|||
}
|
||||
|
||||
for (; fi <= la; fi++) {
|
||||
if (this.$store.state.selected.indexOf(fi) == -1) {
|
||||
this.addSelected(fi);
|
||||
if (state.selected.indexOf(fi) == -1) {
|
||||
mutations.addSelected(fi);
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
if (
|
||||
!this.singleClick &&
|
||||
!event.ctrlKey &&
|
||||
!event.metaKey &&
|
||||
!this.$store.state.multiple
|
||||
)
|
||||
this.resetSelected();
|
||||
this.addSelected(this.index);
|
||||
if (!this.singleClick && !event.ctrlKey && !event.metaKey && !state.multiple)
|
||||
mutations.resetSelected();
|
||||
mutations.addSelected(this.index);
|
||||
},
|
||||
open: function () {
|
||||
this.$router.push({ path: this.url });
|
||||
|
|
|
@ -7,13 +7,15 @@
|
|||
</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) {
|
||||
this.$store.commit("showHover", this.show);
|
||||
mutations.showHover(this.show);
|
||||
}
|
||||
this.$emit("action");
|
||||
},
|
||||
|
|
|
@ -27,7 +27,7 @@
|
|||
<div>
|
||||
<button
|
||||
class="button button--flat button--grey"
|
||||
@click="$store.commit('closeHovers')"
|
||||
@click="closeHovers"
|
||||
:aria-label="$t('buttons.cancel')"
|
||||
:title="$t('buttons.cancel')"
|
||||
>
|
||||
|
@ -47,11 +47,12 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState } from "vuex";
|
||||
import { mutations, state } from "@/store";
|
||||
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",
|
||||
|
@ -62,7 +63,14 @@ export default {
|
|||
dest: null,
|
||||
};
|
||||
},
|
||||
computed: mapState(["req", "selected", "user"]),
|
||||
computed: {
|
||||
user() {
|
||||
return state.user;
|
||||
},
|
||||
closeHovers() {
|
||||
return mutations.closeHovers();
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
copy: async function (event) {
|
||||
event.preventDefault();
|
||||
|
@ -71,9 +79,9 @@ export default {
|
|||
// Create a new promise for each file.
|
||||
for (let item of this.selected) {
|
||||
items.push({
|
||||
from: this.req.items[item].url,
|
||||
to: this.dest + encodeURIComponent(this.req.items[item].name),
|
||||
name: this.req.items[item].name,
|
||||
from: store.req.items[item].url,
|
||||
to: this.dest + encodeURIComponent(store.req.items[item].name),
|
||||
name: store.req.items[item].name,
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -85,9 +93,8 @@ export default {
|
|||
.then(() => {
|
||||
buttons.success("copy");
|
||||
|
||||
if (this.$route.path === this.dest) {
|
||||
this.$store.commit("setReload", true);
|
||||
|
||||
if (state.route.path === this.dest) {
|
||||
mutations.setReload(true);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -95,12 +102,12 @@ export default {
|
|||
})
|
||||
.catch((e) => {
|
||||
buttons.done("copy");
|
||||
this.$showError(e);
|
||||
showError(e);
|
||||
});
|
||||
};
|
||||
|
||||
if (this.$route.path === this.dest) {
|
||||
this.$store.commit("closeHovers");
|
||||
if (state.route.path === this.dest) {
|
||||
mutations.closeHovers();
|
||||
action(false, true);
|
||||
|
||||
return;
|
||||
|
@ -113,14 +120,14 @@ export default {
|
|||
let rename = false;
|
||||
|
||||
if (conflict) {
|
||||
this.$store.commit("showHover", {
|
||||
prompt: "replace-rename",
|
||||
mutations.showHover({
|
||||
name: "replace-rename",
|
||||
confirm: (event, option) => {
|
||||
overwrite = option == "overwrite";
|
||||
rename = option == "rename";
|
||||
|
||||
event.preventDefault();
|
||||
this.$store.commit("closeHovers");
|
||||
mutations.closeHovers();
|
||||
action(overwrite, rename);
|
||||
},
|
||||
});
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
</div>
|
||||
<div class="card-action">
|
||||
<button
|
||||
@click="$store.commit('closeHovers')"
|
||||
@click="closeHovers"
|
||||
class="button button--flat button--grey"
|
||||
:aria-label="$t('buttons.cancel')"
|
||||
:title="$t('buttons.cancel')"
|
||||
|
@ -30,24 +30,34 @@
|
|||
</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: {
|
||||
...mapGetters(["isListing", "selectedCount", "currentPrompt"]),
|
||||
...mapState(["req", "selected"]),
|
||||
isListing() {
|
||||
return getters.isListing();
|
||||
},
|
||||
selectedCount() {
|
||||
return getters.selectedCount();
|
||||
},
|
||||
currentPrompt() {
|
||||
return getters.currentPrompt();
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
...mapMutations(["closeHovers"]),
|
||||
submit: async function () {
|
||||
closeHovers() {
|
||||
mutations.closeHovers();
|
||||
},
|
||||
async submit() {
|
||||
buttons.loading("delete");
|
||||
|
||||
try {
|
||||
if (!this.isListing) {
|
||||
await api.remove(this.$route.path);
|
||||
await api.remove(state.route.path);
|
||||
buttons.success("delete");
|
||||
|
||||
this.currentPrompt?.confirm();
|
||||
|
@ -57,22 +67,22 @@ export default {
|
|||
|
||||
this.closeHovers();
|
||||
|
||||
if (this.selectedCount === 0) {
|
||||
if (getters.selectedCount() === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
let promises = [];
|
||||
for (let index of this.selected) {
|
||||
promises.push(api.remove(this.req.items[index].url));
|
||||
for (let index of state.selected) {
|
||||
promises.push(api.remove(state.req.items[index].url));
|
||||
}
|
||||
|
||||
await Promise.all(promises);
|
||||
buttons.success("delete");
|
||||
this.$store.commit("setReload", true);
|
||||
mutations.setReload(true); // Handle reload as needed
|
||||
} catch (e) {
|
||||
buttons.done("delete");
|
||||
this.$showError(e);
|
||||
if (this.isListing) this.$store.commit("setReload", true);
|
||||
showError(e);
|
||||
if (this.isListing) mutations.setReload(true); // Handle reload as needed
|
||||
}
|
||||
},
|
||||
},
|
||||
|
|
|
@ -19,22 +19,21 @@
|
|||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters, mapMutations, mapState } from "vuex";
|
||||
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: {
|
||||
...mapState(["prompts"]),
|
||||
currentPrompt() {
|
||||
return this.prompts.length ? this.prompts[this.prompts.length - 1] : null;
|
||||
return getters.currentPrompt();
|
||||
},
|
||||
user() {
|
||||
return this.currentPrompt?.props?.user;
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
async deleteUser(event) {
|
||||
|
@ -42,14 +41,16 @@ export default {
|
|||
try {
|
||||
await api.remove(this.user.id);
|
||||
this.$router.push({ path: "/settings/users" });
|
||||
this.$showSuccess(this.$t("settings.userDeleted"));
|
||||
showSuccess(this.$t("settings.userDeleted"));
|
||||
} catch (e) {
|
||||
e.message === "403"
|
||||
? this.$showError(this.$t("errors.forbidden"), false)
|
||||
: this.$showError(e);
|
||||
? showError(this.$t("errors.forbidden"), false)
|
||||
: showError(e);
|
||||
}
|
||||
},
|
||||
...mapMutations(["closeHovers"]),
|
||||
closeHovers() {
|
||||
mutations.closeHovers();
|
||||
},
|
||||
submit: async function () {
|
||||
buttons.loading("delete");
|
||||
|
||||
|
@ -65,22 +66,22 @@ export default {
|
|||
|
||||
this.closeHovers();
|
||||
|
||||
if (this.selectedCount === 0) {
|
||||
if (getters.selectedCount() === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
let promises = [];
|
||||
for (let index of this.selected) {
|
||||
promises.push(api.remove(this.req.items[index].url));
|
||||
promises.push(api.remove(state.req.items[index].url));
|
||||
}
|
||||
|
||||
await Promise.all(promises);
|
||||
buttons.success("delete");
|
||||
this.$store.commit("setReload", true);
|
||||
mutations.setReload(true); // Handle reload as needed
|
||||
} catch (e) {
|
||||
buttons.done("delete");
|
||||
this.$showError(e);
|
||||
if (this.isListing) this.$store.commit("setReload", true);
|
||||
showError(e);
|
||||
if (this.isListing) mutations.setReload(true); // Handle reload as needed
|
||||
}
|
||||
},
|
||||
},
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
<div class="card-title">
|
||||
<h2>{{ $t("prompts.download") }}</h2>
|
||||
</div>
|
||||
|
||||
<div class="card-content">
|
||||
<p>{{ $t("prompts.downloadMessage") }}</p>
|
||||
|
||||
|
@ -21,7 +20,7 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from "vuex";
|
||||
import { getters } from "@/store"; // Import your custom store
|
||||
|
||||
export default {
|
||||
name: "download",
|
||||
|
@ -39,6 +38,9 @@ export default {
|
|||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(["currentPrompt"]),
|
||||
},};
|
||||
currentPrompt() {
|
||||
return getters.currentPrompt();
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -21,9 +21,10 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState } from "vuex";
|
||||
import { state, mutations } from "@/store";
|
||||
import url from "@/utils/url";
|
||||
import { files } from "@/api";
|
||||
import { showError } from "@/notify";
|
||||
|
||||
export default {
|
||||
name: "file-list",
|
||||
|
@ -39,13 +40,12 @@ export default {
|
|||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState(["req", "user"]),
|
||||
nav() {
|
||||
return decodeURIComponent(this.current);
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.fillOptions(this.req);
|
||||
this.fillOptions(state.req);
|
||||
},
|
||||
methods: {
|
||||
fillOptions(req) {
|
||||
|
@ -86,7 +86,7 @@ export default {
|
|||
// content.
|
||||
let uri = event.currentTarget.dataset.url;
|
||||
|
||||
files.fetch(uri).then(this.fillOptions).catch(this.$showError);
|
||||
files.fetch(uri).then(this.fillOptions).catch(showError);
|
||||
},
|
||||
touchstart(event) {
|
||||
let url = event.currentTarget.dataset.url;
|
||||
|
@ -114,7 +114,7 @@ export default {
|
|||
}
|
||||
},
|
||||
itemClick: function (event) {
|
||||
if (this.user.singleClick) this.next(event);
|
||||
if (state.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 () {
|
||||
this.$store.commit("showHover", {
|
||||
prompt: "newDir",
|
||||
mutations.showHover({
|
||||
name: "newDir",
|
||||
action: null,
|
||||
confirm: null,
|
||||
props: {
|
||||
redirect: false,
|
||||
base: this.current === this.$route.path ? null : this.current,
|
||||
base: this.current === state.route.path ? null : this.current,
|
||||
},
|
||||
});
|
||||
},
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
<div class="card-action">
|
||||
<button
|
||||
type="submit"
|
||||
@click="$store.commit('closeHovers')"
|
||||
@click="closeHovers"
|
||||
class="button button--flat"
|
||||
:aria-label="$t('buttons.ok')"
|
||||
:title="$t('buttons.ok')"
|
||||
|
@ -33,5 +33,14 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
export default { name: "help" };
|
||||
import { mutations } from "@/store"; // Import the mutations
|
||||
|
||||
export default {
|
||||
name: "help",
|
||||
computed: {
|
||||
closeHovers() {
|
||||
return mutations.closeHovers; // Return the closeHovers mutation
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -33,33 +33,25 @@
|
|||
<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>
|
||||
|
@ -68,7 +60,7 @@
|
|||
<div class="card-action">
|
||||
<button
|
||||
type="submit"
|
||||
@click="$store.commit('closeHovers')"
|
||||
@click="closeHovers"
|
||||
class="button button--flat"
|
||||
:aria-label="$t('buttons.ok')"
|
||||
:title="$t('buttons.ok')"
|
||||
|
@ -78,73 +70,87 @@
|
|||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getHumanReadableFilesize } from "@/utils/filesizes";
|
||||
import { mapState, mapGetters } from "vuex";
|
||||
import moment from "moment";
|
||||
import { formatTimestamp } from "@/utils/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: {
|
||||
...mapState(["req", "selected"]),
|
||||
...mapGetters(["selectedCount", "isListing"]),
|
||||
humanSize: function () {
|
||||
if (this.selectedCount === 0 || !this.isListing) {
|
||||
return getHumanReadableFilesize(this.req.size);
|
||||
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);
|
||||
}
|
||||
|
||||
let sum = 0;
|
||||
|
||||
for (let selected of this.selected) {
|
||||
sum += this.req.items[selected].size;
|
||||
sum += state.req.items[selected].size;
|
||||
}
|
||||
|
||||
return getHumanReadableFilesize(sum);
|
||||
},
|
||||
humanTime: function () {
|
||||
if (this.selectedCount === 0) {
|
||||
return moment(this.req.modified).fromNow();
|
||||
humanTime() {
|
||||
if (getters.selectedCount() === 0) {
|
||||
return formatTimestamp(state.req.modified, state.user.locale);
|
||||
}
|
||||
|
||||
return moment(this.req.items[this.selected[0]].modified).fromNow();
|
||||
return formatTimestamp(
|
||||
state.req.items[this.selected[0]].modified,
|
||||
state.user.locale
|
||||
);
|
||||
},
|
||||
modTime: function () {
|
||||
return new Date(Date.parse(this.req.modified)).toLocaleString();
|
||||
modTime() {
|
||||
return new Date(Date.parse(state.req.modified)).toLocaleString();
|
||||
},
|
||||
name: function () {
|
||||
return this.selectedCount === 0
|
||||
? this.req.name
|
||||
: this.req.items[this.selected[0]].name;
|
||||
name() {
|
||||
return getters.selectedCount() === 0
|
||||
? state.req.name
|
||||
: state.req.items[this.selected[0]].name;
|
||||
},
|
||||
dir: function () {
|
||||
dir() {
|
||||
return (
|
||||
this.selectedCount > 1 ||
|
||||
(this.selectedCount === 0
|
||||
? this.req.isDir
|
||||
: this.req.items[this.selected[0]].isDir)
|
||||
getters.selectedCount() > 1 ||
|
||||
(getters.selectedCount() === 0
|
||||
? state.req.isDir
|
||||
: state.req.items[this.selected[0]].isDir)
|
||||
);
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
checksum: async function (event, algo) {
|
||||
async checksum(event, algo) {
|
||||
event.preventDefault();
|
||||
|
||||
let link;
|
||||
|
||||
if (this.selectedCount) {
|
||||
link = this.req.items[this.selected[0]].url;
|
||||
if (getters.selectedCount()) {
|
||||
link = state.req.items[this.selected[0]].url;
|
||||
} else {
|
||||
link = this.$route.path;
|
||||
link = state.route.path;
|
||||
}
|
||||
|
||||
try {
|
||||
const hash = await api.checksum(link, algo);
|
||||
// eslint-disable-next-line
|
||||
event.target.innerHTML = hash
|
||||
event.target.innerHTML = hash;
|
||||
} catch (e) {
|
||||
this.$showError(e);
|
||||
showError(e);
|
||||
}
|
||||
},
|
||||
},
|
||||
|
|
|
@ -26,7 +26,7 @@
|
|||
<div>
|
||||
<button
|
||||
class="button button--flat button--grey"
|
||||
@click="$store.commit('closeHovers')"
|
||||
@click="closeHovers"
|
||||
:aria-label="$t('buttons.cancel')"
|
||||
:title="$t('buttons.cancel')"
|
||||
>
|
||||
|
@ -47,11 +47,12 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState } from "vuex";
|
||||
import { mutations, state } from "@/store";
|
||||
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",
|
||||
|
@ -62,32 +63,39 @@ export default {
|
|||
dest: null,
|
||||
};
|
||||
},
|
||||
computed: mapState(["req", "selected", "user"]),
|
||||
computed: {
|
||||
user() {
|
||||
return state.user;
|
||||
},
|
||||
closeHovers() {
|
||||
return mutations.closeHovers()
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
move: async function (event) {
|
||||
event.preventDefault();
|
||||
let items = [];
|
||||
|
||||
for (let item of this.selected) {
|
||||
for (let item of state.selected) {
|
||||
items.push({
|
||||
from: this.req.items[item].url,
|
||||
to: this.dest + encodeURIComponent(this.req.items[item].name),
|
||||
name: this.req.items[item].name,
|
||||
from: state.req.items[item].url,
|
||||
to: this.dest + encodeURIComponent(state.req.items[item].name),
|
||||
name: state.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");
|
||||
this.$showError(e);
|
||||
showError(e);
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -98,15 +106,16 @@ export default {
|
|||
let rename = false;
|
||||
|
||||
if (conflict) {
|
||||
this.$store.commit("showHover", {
|
||||
prompt: "replace-rename",
|
||||
mutations.showHover({
|
||||
name: "replace-rename",
|
||||
confirm: (event, option) => {
|
||||
overwrite = option == "overwrite";
|
||||
rename = option == "rename";
|
||||
|
||||
event.preventDefault();
|
||||
this.$store.commit("closeHovers");
|
||||
mutations.closeHovers();
|
||||
action(overwrite, rename);
|
||||
mutations.setReload(true)
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
<div class="card-action">
|
||||
<button
|
||||
class="button button--flat button--grey"
|
||||
@click="$store.commit('closeHovers')"
|
||||
@click="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,23 +53,31 @@ export default {
|
|||
default: null,
|
||||
},
|
||||
},
|
||||
data: function () {
|
||||
data() {
|
||||
return {
|
||||
name: "",
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(["isFiles", "isListing"]),
|
||||
isFiles() {
|
||||
return getters.isFiles();
|
||||
},
|
||||
isListing() {
|
||||
return getters.isListing();
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
submit: async function (event) {
|
||||
closeHovers() {
|
||||
return mutations.closeHovers();
|
||||
},
|
||||
async submit(event) {
|
||||
event.preventDefault();
|
||||
if (this.new === "") return;
|
||||
if (this.name === "") return;
|
||||
|
||||
// Build the path of the new directory.
|
||||
let uri;
|
||||
if (this.base) uri = this.base;
|
||||
else if (this.isFiles) uri = this.$route.path + "/";
|
||||
else if (getters.isFiles()) uri = state.route.path + "/";
|
||||
else uri = "/";
|
||||
|
||||
if (!this.isListing) {
|
||||
|
@ -85,13 +93,13 @@ export default {
|
|||
this.$router.push({ path: uri });
|
||||
} else if (!this.base) {
|
||||
const res = await api.fetch(url.removeLastDir(uri) + "/");
|
||||
this.$store.commit("updateRequest", res);
|
||||
mutations.updateRequest(res);
|
||||
}
|
||||
} catch (e) {
|
||||
this.$showError(e);
|
||||
showError(e);
|
||||
}
|
||||
|
||||
this.$store.commit("closeHovers");
|
||||
mutations.closeHovers();
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
<div class="card-action">
|
||||
<button
|
||||
class="button button--flat button--grey"
|
||||
@click="$store.commit('closeHovers')"
|
||||
@click="closeHovers"
|
||||
:aria-label="$t('buttons.cancel')"
|
||||
:title="$t('buttons.cancel')"
|
||||
>
|
||||
|
@ -35,29 +35,36 @@
|
|||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from "vuex";
|
||||
import { state } from "@/store";
|
||||
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: function () {
|
||||
data() {
|
||||
return {
|
||||
name: "",
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(["isFiles", "isListing"]),
|
||||
isFiles() {
|
||||
return getters.isFiles();
|
||||
},
|
||||
isListing() {
|
||||
return getters.isListing();
|
||||
},
|
||||
closeHovers() {
|
||||
return mutations.closeHovers;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
submit: async function (event) {
|
||||
async submit(event) {
|
||||
event.preventDefault();
|
||||
if (this.new === "") return;
|
||||
|
||||
// Build the path of the new directory.
|
||||
let uri = this.isFiles ? this.$route.path + "/" : "/";
|
||||
if (this.name === "") return;
|
||||
// Build the path of the new file.
|
||||
let uri = getters.isFiles() ? state.route.path + "/" : "/";
|
||||
|
||||
if (!this.isListing) {
|
||||
uri = url.removeLastDir(uri) + "/";
|
||||
|
@ -70,10 +77,10 @@ export default {
|
|||
await api.post(uri);
|
||||
this.$router.push({ path: uri });
|
||||
} catch (e) {
|
||||
this.$showError(e);
|
||||
showError(e);
|
||||
}
|
||||
|
||||
this.$store.commit("closeHovers");
|
||||
mutations.closeHovers();
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
@ -5,8 +5,7 @@
|
|||
:ref="currentPromptName"
|
||||
:is="currentPromptName"
|
||||
v-bind="currentPrompt.props"
|
||||
>
|
||||
</component>
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -27,8 +26,8 @@ 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",
|
||||
|
@ -50,30 +49,31 @@ export default {
|
|||
Sidebar,
|
||||
DeleteUser,
|
||||
},
|
||||
data: function () {
|
||||
data() {
|
||||
return {
|
||||
pluginData: {
|
||||
buttons,
|
||||
store: this.$store,
|
||||
store: state, // Directly use state
|
||||
router: this.$router,
|
||||
},
|
||||
};
|
||||
},
|
||||
created() {
|
||||
window.addEventListener("keydown", (event) => {
|
||||
if (this.currentPrompt == null) return;
|
||||
let currentPrompt = getters.currentPrompt();
|
||||
if (!currentPrompt) return;
|
||||
|
||||
let prompt = this.$refs.currentComponent;
|
||||
let prompt = this.$refs[currentPrompt.name];
|
||||
|
||||
// Esc!
|
||||
if (event.keyCode === 27) {
|
||||
event.stopImmediatePropagation();
|
||||
this.$store.commit("closeHovers");
|
||||
mutations.closeHovers();
|
||||
}
|
||||
|
||||
// Enter
|
||||
if (event.keyCode == 13) {
|
||||
switch (this.currentPrompt.prompt) {
|
||||
if (event.keyCode === 13) {
|
||||
switch (currentPrompt.name) {
|
||||
case "delete":
|
||||
prompt.submit();
|
||||
break;
|
||||
|
@ -91,10 +91,25 @@ export default {
|
|||
});
|
||||
},
|
||||
computed: {
|
||||
...mapState(["plugins"]),
|
||||
...mapGetters(["currentPrompt", "currentPromptName"]),
|
||||
showOverlay: function () {
|
||||
return this.currentPrompt !== null && this.currentPrompt.prompt !== "more";
|
||||
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";
|
||||
},
|
||||
},
|
||||
methods: {},
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
<div class="card-action">
|
||||
<button
|
||||
class="button button--flat button--grey"
|
||||
@click="$store.commit('closeHovers')"
|
||||
@click="closeHovers"
|
||||
:aria-label="$t('buttons.cancel')"
|
||||
:title="$t('buttons.cancel')"
|
||||
>
|
||||
|
@ -39,15 +39,14 @@
|
|||
</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: function () {
|
||||
data() {
|
||||
return {
|
||||
name: "",
|
||||
};
|
||||
|
@ -56,37 +55,48 @@ export default {
|
|||
this.name = this.oldName();
|
||||
},
|
||||
computed: {
|
||||
...mapState(["req", "selected", "selectedCount"]),
|
||||
...mapGetters(["isListing"]),
|
||||
req() {
|
||||
return state.req;
|
||||
},
|
||||
selected() {
|
||||
return state.selected;
|
||||
},
|
||||
selectedCount() {
|
||||
return state.selectedCount;
|
||||
},
|
||||
isListing() {
|
||||
return getters.isListing();
|
||||
},
|
||||
closeHovers() {
|
||||
return mutations.closeHovers;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
cancel: function () {
|
||||
this.$store.commit("closeHovers");
|
||||
cancel() {
|
||||
mutations.closeHovers();
|
||||
},
|
||||
oldName: function () {
|
||||
oldName() {
|
||||
if (!this.isListing) {
|
||||
return this.req.name;
|
||||
return state.req.name;
|
||||
}
|
||||
|
||||
if (this.selectedCount === 0 || this.selectedCount > 1) {
|
||||
// This shouldn't happen.
|
||||
if (getters.selectedCount() === 0 || getters.selectedCount() > 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
return this.req.items[this.selected[0]].name;
|
||||
return state.req.items[this.selected[0]].name;
|
||||
},
|
||||
submit: async function () {
|
||||
async submit() {
|
||||
let oldLink = "";
|
||||
let newLink = "";
|
||||
|
||||
if (!this.isListing) {
|
||||
oldLink = this.req.url;
|
||||
oldLink = state.req.url;
|
||||
} else {
|
||||
oldLink = this.req.items[this.selected[0]].url;
|
||||
oldLink = state.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 }]);
|
||||
|
@ -95,12 +105,12 @@ export default {
|
|||
return;
|
||||
}
|
||||
|
||||
this.$store.commit("setReload", true);
|
||||
mutations.setReload(true);
|
||||
} catch (e) {
|
||||
this.$showError(e);
|
||||
showError(e);
|
||||
}
|
||||
|
||||
this.$store.commit("closeHovers");
|
||||
mutations.closeHovers();
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
@ -28,12 +28,15 @@
|
|||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from "vuex";
|
||||
import { getters } from "@/store"; // Import your custom store
|
||||
|
||||
export default {
|
||||
name: "replace",
|
||||
computed: mapGetters(["currentPrompt"]),
|
||||
computed: {
|
||||
currentPrompt() {
|
||||
return getters.currentPrompt(); // Access the getter directly from the store
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -38,9 +38,14 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from "vuex";
|
||||
import { getters } from "@/store"; // Import your custom store
|
||||
|
||||
export default {
|
||||
name: "replace-rename",
|
||||
computed: mapGetters(["currentPrompt"]),
|
||||
computed: {
|
||||
currentPrompt() {
|
||||
return getters.currentPrompt(); // Access the getter directly from the store
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -58,7 +58,7 @@
|
|||
<div class="card-action">
|
||||
<button
|
||||
class="button button--flat button--grey"
|
||||
@click="$store.commit('closeHovers')"
|
||||
@click="closeHovers"
|
||||
:aria-label="$t('buttons.close')"
|
||||
:title="$t('buttons.close')"
|
||||
>
|
||||
|
@ -119,16 +119,16 @@
|
|||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState, mapGetters } from "vuex";
|
||||
import { showSuccess, showError } from "@/notify";
|
||||
import { state, getters, mutations } from "@/store";
|
||||
import { share as api, pub as pub_api } from "@/api";
|
||||
import moment from "moment";
|
||||
import { fromNow } from "@/utils/moment";
|
||||
import Clipboard from "clipboard";
|
||||
|
||||
export default {
|
||||
name: "share",
|
||||
data: function () {
|
||||
data() {
|
||||
return {
|
||||
time: "",
|
||||
unit: "hours",
|
||||
|
@ -139,22 +139,35 @@ export default {
|
|||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState(["req", "selected", "selectedCount"]),
|
||||
...mapGetters(["isListing", "selectedCount"]),
|
||||
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
|
||||
},
|
||||
url() {
|
||||
if (!this.isListing) {
|
||||
return this.$route.path;
|
||||
return state.route.path;
|
||||
}
|
||||
if (this.selectedCount != 1) {
|
||||
if (getters.selectedCount() !== 1) {
|
||||
// selecting current view image
|
||||
return this.$route.path;
|
||||
return state.route.path;
|
||||
}
|
||||
return this.req.items[this.selected[0]].url;
|
||||
return state.req.items[this.selected[0]].url;
|
||||
},
|
||||
getContext() {
|
||||
let path = this.$route.path.replace("/files/", "./");
|
||||
if (this.selectedCount == 1) {
|
||||
path = path + this.req.items[this.selected[0]].name;
|
||||
let path = state.route.path.replace("/files/", "./");
|
||||
if (getters.selectedCount() === 1) {
|
||||
path = path + state.req.items[this.selected[0]].name;
|
||||
}
|
||||
return path;
|
||||
},
|
||||
|
@ -165,25 +178,25 @@ export default {
|
|||
this.links = links;
|
||||
this.sort();
|
||||
|
||||
if (this.links.length == 0) {
|
||||
if (this.links.length === 0) {
|
||||
this.listing = false;
|
||||
}
|
||||
} catch (e) {
|
||||
this.$showError(e);
|
||||
showError(e);
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.clip = new Clipboard(".copy-clipboard");
|
||||
this.clip.on("success", () => {
|
||||
this.$showSuccess(this.$t("success.linkCopied"));
|
||||
showSuccess(this.$t("success.linkCopied"));
|
||||
});
|
||||
},
|
||||
beforeUnmount() {
|
||||
this.clip.destroy();
|
||||
},
|
||||
methods: {
|
||||
submit: async function () {
|
||||
let isPermanent = !this.time || this.time == 0;
|
||||
async submit() {
|
||||
let isPermanent = !this.time || this.time === 0;
|
||||
|
||||
try {
|
||||
let res = null;
|
||||
|
@ -203,30 +216,30 @@ export default {
|
|||
|
||||
this.listing = true;
|
||||
} catch (e) {
|
||||
this.$showError(e);
|
||||
showError(e);
|
||||
}
|
||||
},
|
||||
deleteLink: async function (event, link) {
|
||||
async deleteLink(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) {
|
||||
this.$showError(e);
|
||||
showError(e);
|
||||
}
|
||||
},
|
||||
humanTime(time) {
|
||||
return moment(time * 1000).fromNow();
|
||||
return fromNow(time, state.user.locale);
|
||||
},
|
||||
buildLink(share) {
|
||||
return api.getShareURL(share);
|
||||
},
|
||||
hasDownloadLink() {
|
||||
return this.selected.length === 1 && !this.req.items[this.selected[0]].isDir;
|
||||
return this.selected.length === 1 && !state.req.items[this.selected[0]].isDir;
|
||||
},
|
||||
buildDownloadLink(share) {
|
||||
return pub_api.getDownloadURL(share);
|
||||
|
@ -239,8 +252,9 @@ export default {
|
|||
});
|
||||
},
|
||||
switchListing() {
|
||||
if (this.links.length == 0 && !this.listing) {
|
||||
this.$store.commit("closeHovers");
|
||||
if (this.links.length === 0 && !this.listing) {
|
||||
// Access the store directly if needed
|
||||
mutations.closeHovers();
|
||||
}
|
||||
|
||||
this.listing = !this.listing;
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
</div>
|
||||
<div class="card-action">
|
||||
<button
|
||||
@click="$store.commit('closeHovers')"
|
||||
@click="closeHovers"
|
||||
class="button button--flat button--grey"
|
||||
:aria-label="$t('buttons.cancel')"
|
||||
:title="$t('buttons.cancel')"
|
||||
|
@ -23,16 +23,21 @@
|
|||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from "vuex";
|
||||
import { getters } from "@/store"; // Import your custom store
|
||||
|
||||
export default {
|
||||
name: "share-delete",
|
||||
computed: {
|
||||
...mapGetters(["currentPrompt"]),
|
||||
closeHovers() {
|
||||
return mutations.closeHovers();
|
||||
},
|
||||
currentPrompt() {
|
||||
return getters.currentPrompt();
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
submit: function () {
|
||||
submit() {
|
||||
this.currentPrompt?.confirm();
|
||||
},
|
||||
},
|
||||
|
|
|
@ -29,9 +29,7 @@
|
|||
: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>
|
||||
|
@ -40,22 +38,26 @@
|
|||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from "vuex";
|
||||
import { getters } from "@/store"; // Import your custom store
|
||||
|
||||
export default {
|
||||
name: "uploadFiles",
|
||||
data: function () {
|
||||
data() {
|
||||
return {
|
||||
open: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(["filesInUpload", "filesInUploadCount"]),
|
||||
filesInUpload() {
|
||||
return getters.filesInUpload(); // Access the getter directly from the store
|
||||
},
|
||||
filesInUploadCount() {
|
||||
return getters.filesInUploadCount(); // Access the getter directly from the store
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
toggle: function () {
|
||||
toggle() {
|
||||
this.open = !this.open;
|
||||
},
|
||||
},
|
||||
|
|
|
@ -1,17 +1,24 @@
|
|||
<template>
|
||||
<select v-on:change="change" :value="locale">
|
||||
<option v-for="(language, value) in locales" :key="value" :value="value">
|
||||
{{ $t("languages." + language) }}
|
||||
<option v-for="(value, label) in locales" :key="label" :value="label">
|
||||
{{ $t("languages." + label) }}
|
||||
</option>
|
||||
</select>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "languages",
|
||||
props: ["locale"],
|
||||
<script lang="ts">
|
||||
import { defineComponent } from "vue";
|
||||
|
||||
export default defineComponent({
|
||||
name: "Languages",
|
||||
props: {
|
||||
locale: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
let dataObj = {
|
||||
return {
|
||||
locales: {
|
||||
he: "he",
|
||||
hu: "hu",
|
||||
|
@ -38,18 +45,12 @@ export default {
|
|||
"zh-tw": "zhTW",
|
||||
},
|
||||
};
|
||||
|
||||
Object.defineProperty(dataObj, "locales", {
|
||||
configurable: false,
|
||||
writable: false,
|
||||
});
|
||||
|
||||
return dataObj;
|
||||
},
|
||||
methods: {
|
||||
change(event) {
|
||||
this.$emit("update:locale", event.target.value);
|
||||
change(event: Event) {
|
||||
const target = event.target as HTMLSelectElement;
|
||||
this.$emit("update:locale", target.value);
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
|
|
@ -67,10 +67,10 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import Languages from "./Languages";
|
||||
import Rules from "./Rules";
|
||||
import Permissions from "./Permissions";
|
||||
import Commands from "./Commands";
|
||||
import Languages from "./Languages.vue";
|
||||
import Rules from "./Rules.vue";
|
||||
import Permissions from "./Permissions.vue";
|
||||
import Commands from "./Commands.vue";
|
||||
import { enableExec } from "@/utils/constants";
|
||||
|
||||
export default {
|
||||
|
|
|
@ -195,14 +195,13 @@ 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 {
|
||||
|
@ -238,3 +237,34 @@ 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;
|
||||
}
|
|
@ -98,7 +98,7 @@
|
|||
|
||||
/* Listing items */
|
||||
.dark-mode #listingView .item {
|
||||
background: var(--surfacePrimary);
|
||||
background: var(--surfacePrimary) !important;
|
||||
color: var(--textPrimary);
|
||||
border-color: var(--divider) !important;
|
||||
}
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
@import 'material-icons/iconfont/filled.css';
|
||||
|
||||
@font-face {
|
||||
font-family: 'Roboto';
|
||||
font-style: normal;
|
||||
|
@ -166,8 +168,6 @@
|
|||
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;
|
||||
}
|
|
@ -103,9 +103,7 @@ 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%;
|
||||
|
@ -116,16 +114,12 @@ 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: calc(100% - 5vw);
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#listingView.gallery .item div:first-of-type {
|
||||
|
@ -177,6 +171,7 @@ body.rtl #listingView {
|
|||
border: 1px solid rgba(0, 0, 0, 0.1);
|
||||
padding: 0;
|
||||
border-top: 0;
|
||||
padding-left: .5em;
|
||||
}
|
||||
|
||||
#listingView.compact h2 {
|
||||
|
@ -197,15 +192,30 @@ 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%;
|
||||
}
|
||||
|
@ -217,22 +227,23 @@ 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;
|
||||
}
|
||||
|
@ -245,10 +256,6 @@ body.rtl #listingView {
|
|||
font-weight: normal;
|
||||
}
|
||||
|
||||
#listingView.compact .header .name {
|
||||
margin-right: 3em;
|
||||
}
|
||||
|
||||
#listingView.compact .header span {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
@ -302,22 +309,14 @@ 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 !important;
|
||||
background-color: #ccc;
|
||||
display: none;
|
||||
background: var(--surfacePrimary);
|
||||
border-radius: 1em;
|
||||
}
|
||||
|
||||
#listingView.list .header i {
|
||||
|
@ -329,14 +328,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%;
|
||||
border: 0;
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
#listingView.compact .header {
|
||||
|
||||
}
|
||||
#listingView.list .item:first-child {
|
||||
margin-top: .5em;
|
||||
|
@ -344,35 +343,36 @@ body.rtl #listingView {
|
|||
border-top-right-radius: 1em;
|
||||
}
|
||||
|
||||
#listingView.list .item:last-child {
|
||||
margin-bottom: .5em;
|
||||
#listingView.compact .lastGroup > div:last-child {
|
||||
border-bottom-left-radius: 1em;
|
||||
border-bottom-right-radius: 1em;
|
||||
}
|
||||
#listingView.list .header>div:first-child {
|
||||
width: 0;
|
||||
|
||||
#listingView.list .item:last-child {
|
||||
border-bottom-left-radius: 1em;
|
||||
border-bottom-right-radius: 1em;
|
||||
}
|
||||
|
||||
#listingView.list .header .name {
|
||||
margin-right: 3em;
|
||||
#listingView.list .item:last-child {
|
||||
margin-bottom: .5em;
|
||||
}
|
||||
|
||||
#listingView > * > .header > div {
|
||||
display:flex;
|
||||
width: 100%;
|
||||
}
|
||||
#listingView.list .header {
|
||||
margin-bottom: .5em;
|
||||
}
|
||||
|
||||
#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;
|
||||
}
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
@import "~normalize.css/normalize.css";
|
||||
@import "~noty/lib/noty.css";
|
||||
@import "~noty/lib/themes/mint.css";
|
||||
@import "normalize.css/normalize.css";
|
||||
@import "./_variables.css";
|
||||
@import "./_buttons.css";
|
||||
@import "./_inputs.css";
|
||||
|
@ -13,6 +11,7 @@
|
|||
@import "./upload-files.css";
|
||||
@import "./dashboard.css";
|
||||
@import "./login.css";
|
||||
@import './mobile.css';
|
||||
|
||||
.link {
|
||||
color: var(--blue);
|
||||
|
@ -168,7 +167,9 @@ main .spinner .bounce2 {
|
|||
|
||||
#previewer .preview {
|
||||
text-align: center;
|
||||
height: calc(100vh - 4em);
|
||||
max-height: 100%;
|
||||
max-width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#previewer .preview pre {
|
||||
|
@ -385,8 +386,6 @@ body.rtl .breadcrumbs .chevron {
|
|||
margin-left: .5rem;
|
||||
}
|
||||
|
||||
@import './mobile.css';
|
||||
|
||||
/* * * * * * * * * * * * * * * *
|
||||
* RTL overrides *
|
||||
* * * * * * * * * * * * * * * */
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
// src/global.d.ts
|
||||
interface Window {
|
||||
grecaptcha?: any; // or use a more specific type if available
|
||||
}
|
||||
|
|
@ -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": "",
|
||||
"hideDotfiles": "Ocultar archivos empezados por punto",
|
||||
"insertPath": "Introduce la ruta",
|
||||
"insertRegex": "Introducir expresión regular",
|
||||
"instanceName": "Nombre de la instancia",
|
||||
|
|
|
@ -1,143 +0,0 @@
|
|||
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;
|
|
@ -0,0 +1,120 @@
|
|||
// 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;
|
|
@ -1,52 +0,0 @@
|
|||
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();
|
|
@ -0,0 +1,43 @@
|
|||
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"));
|
|
@ -0,0 +1,7 @@
|
|||
|
||||
import { showSuccess, showError, closePopUp } from "./message.js";
|
||||
export {
|
||||
showSuccess,
|
||||
showError,
|
||||
closePopUp,
|
||||
};
|
|
@ -0,0 +1,45 @@
|
|||
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)
|
||||
}
|
|
@ -1,194 +0,0 @@
|
|||
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;
|
|
@ -0,0 +1,217 @@
|
|||
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 };
|
|
@ -0,0 +1,16 @@
|
|||
// 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');
|
||||
}
|
|
@ -1,32 +1,97 @@
|
|||
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) {
|
||||
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)) {
|
||||
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);
|
||||
|
||||
let sum = state.upload.progress.reduce((acc, val) => acc + val);
|
||||
// Calculate sum of progress
|
||||
let sum = state.upload.progress.reduce((acc, val) => acc + val, 0);
|
||||
|
||||
// Return progress as a percentage
|
||||
return Math.ceil((sum / totalSize) * 100);
|
||||
},
|
||||
filesInUploadCount: (state) => {
|
||||
let total =
|
||||
Object.keys(state.upload.uploads).length + state.upload.queue.length;
|
||||
return total;
|
||||
|
||||
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;
|
||||
},
|
||||
currentPrompt: (state) => {
|
||||
return state.prompts.length > 0
|
||||
? state.prompts[state.prompts.length - 1]
|
||||
: null;
|
||||
|
||||
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]
|
||||
},
|
||||
currentPromptName: (_, getters) => {
|
||||
return getters.currentPrompt?.prompt;
|
||||
|
||||
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;
|
||||
},
|
||||
filesInUpload: (state) => {
|
||||
|
||||
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 [];
|
||||
}
|
||||
|
||||
let files = [];
|
||||
|
||||
for (let index in state.upload.uploads) {
|
||||
|
@ -34,11 +99,11 @@ const getters = {
|
|||
let id = upload.id;
|
||||
let type = upload.type;
|
||||
let name = upload.file.name;
|
||||
let size = state.upload.sizes[id];
|
||||
let size = state.upload.sizes[id] || 0; // Default to 0 if size is undefined
|
||||
let isDir = upload.file.isDir;
|
||||
let progress = isDir
|
||||
? 100
|
||||
: Math.ceil((state.upload.progress[id] / size) * 100);
|
||||
: Math.ceil((state.upload.progress[id] || 0 / size) * 100); // Default to 0 if progress is undefined
|
||||
|
||||
files.push({
|
||||
id,
|
||||
|
@ -52,5 +117,3 @@ const getters = {
|
|||
return files.sort((a, b) => a.progress - b.progress);
|
||||
},
|
||||
};
|
||||
|
||||
export default getters;
|
||||
|
|
|
@ -1,43 +0,0 @@
|
|||
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: {
|
||||
rules: [],
|
||||
},
|
||||
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 },
|
||||
});
|
|
@ -0,0 +1,10 @@
|
|||
// store/index.js
|
||||
import { state } from "./state.js";
|
||||
import { getters } from "./getters.js";
|
||||
import { mutations } from "./mutations.js";
|
||||
|
||||
export {
|
||||
state,
|
||||
getters,
|
||||
mutations
|
||||
};
|
|
@ -1,85 +1,107 @@
|
|||
import * as i18n from "@/i18n";
|
||||
import moment from "moment";
|
||||
import { state } from "./state.js";
|
||||
import { emitStateChanged } from './eventBus'; // Import the function from eventBus.js
|
||||
|
||||
const mutations = {
|
||||
closeHovers: (state) => {
|
||||
export const mutations = {
|
||||
setUsage: (value) => {
|
||||
state.usage = value;
|
||||
emitStateChanged();
|
||||
},
|
||||
closeHovers: () => {
|
||||
state.prompts = [];
|
||||
emitStateChanged();
|
||||
},
|
||||
toggleShell: (state) => {
|
||||
toggleShell: () => {
|
||||
state.showShell = !state.showShell;
|
||||
emitStateChanged();
|
||||
},
|
||||
showHover: (state, value) => {
|
||||
if (typeof value !== "object") {
|
||||
showHover: (value) => {
|
||||
if (typeof value === "object") {
|
||||
state.prompts.push({
|
||||
prompt: value,
|
||||
confirm: null,
|
||||
action: null,
|
||||
props: null,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
state.prompts.push({
|
||||
prompt: value.prompt, // Should not be null
|
||||
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,
|
||||
});
|
||||
}
|
||||
emitStateChanged();
|
||||
},
|
||||
showError: (state) => {
|
||||
showError: () => {
|
||||
state.prompts.push("error");
|
||||
emitStateChanged();
|
||||
},
|
||||
showSuccess: (state) => {
|
||||
state.prompts.push("success");
|
||||
},
|
||||
setLoading: (state, value) => {
|
||||
setLoading: (value) => {
|
||||
state.loading = value;
|
||||
emitStateChanged();
|
||||
},
|
||||
setReload: (state, value) => {
|
||||
setReload: (value) => {
|
||||
state.reload = value;
|
||||
emitStateChanged();
|
||||
},
|
||||
setUser: (state, value) => {
|
||||
setUser: (value) => {
|
||||
if (value === null) {
|
||||
state.user = null;
|
||||
emitStateChanged();
|
||||
return;
|
||||
}
|
||||
|
||||
let locale = value.locale;
|
||||
|
||||
if (locale === "") {
|
||||
locale = i18n.detectLocale();
|
||||
}
|
||||
|
||||
moment.locale(locale);
|
||||
i18n.setLocale(locale);
|
||||
i18n.default.locale = locale;
|
||||
state.user = value;
|
||||
emitStateChanged();
|
||||
},
|
||||
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) => {
|
||||
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) => {
|
||||
let i = state.selected.indexOf(value);
|
||||
if (i === -1) return;
|
||||
state.selected.splice(i, 1);
|
||||
emitStateChanged();
|
||||
},
|
||||
resetSelected: (state) => {
|
||||
resetSelected: () => {
|
||||
state.selected = [];
|
||||
mutations.setMultiple(false);
|
||||
emitStateChanged();
|
||||
},
|
||||
updateUser: (state, value) => {
|
||||
updateUser: (value) => {
|
||||
if (typeof value !== "object") return;
|
||||
if (state.user === null) {
|
||||
state.user = {};
|
||||
}
|
||||
for (let field in value) {
|
||||
if (field === "locale") {
|
||||
moment.locale(value[field]);
|
||||
i18n.default.locale = value[field];
|
||||
i18n.setLocale(value[field]);
|
||||
}
|
||||
state.user[field] = value[field];
|
||||
}
|
||||
emitStateChanged();
|
||||
},
|
||||
updateRequest: (state, value) => {
|
||||
updateRequest: (value) => {
|
||||
const selectedItems = state.selected.map((i) => state.req.items[i]);
|
||||
state.oldReq = state.req;
|
||||
state.req = value;
|
||||
|
@ -90,14 +112,20 @@ const mutations = {
|
|||
.filter((item) => selectedItems.some((rItem) => rItem.url === item.url))
|
||||
.map((item) => item.index);
|
||||
},
|
||||
// Inside your mutations object
|
||||
updateListingSortConfig(state, { field, asc }) {
|
||||
replaceRequest: (value) => {
|
||||
state.req = value;
|
||||
emitStateChanged();
|
||||
},
|
||||
setRoute: (value) => {
|
||||
state.route = value;
|
||||
emitStateChanged();
|
||||
},
|
||||
updateListingSortConfig: ({ field, asc }) => {
|
||||
state.req.sorting.by = field;
|
||||
state.req.sorting.asc = asc;
|
||||
emitStateChanged();
|
||||
},
|
||||
|
||||
updateListingItems(state) {
|
||||
// Sort the items array based on the sorting settings
|
||||
updateListingItems: () => {
|
||||
state.req.items.sort((a, b) => {
|
||||
const valueA = a[state.req.sorting.by];
|
||||
const valueB = b[state.req.sorting.by];
|
||||
|
@ -107,17 +135,18 @@ const mutations = {
|
|||
return valueA < valueB ? 1 : -1;
|
||||
}
|
||||
});
|
||||
emitStateChanged();
|
||||
},
|
||||
|
||||
updateClipboard: (state, value) => {
|
||||
updateClipboard: (value) => {
|
||||
state.clipboard.key = value.key;
|
||||
state.clipboard.items = value.items;
|
||||
state.clipboard.path = value.path;
|
||||
emitStateChanged();
|
||||
},
|
||||
resetClipboard: (state) => {
|
||||
resetClipboard: () => {
|
||||
state.clipboard.key = "";
|
||||
state.clipboard.items = [];
|
||||
emitStateChanged();
|
||||
},
|
||||
};
|
||||
|
||||
export default mutations;
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
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: {},
|
||||
});
|
|
@ -1,4 +1,4 @@
|
|||
import store from "@/store";
|
||||
import { mutations } from "@/store";
|
||||
import router from "@/router";
|
||||
import { baseURL } from "@/utils/constants";
|
||||
|
||||
|
@ -8,13 +8,12 @@ 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);
|
||||
store.commit("setJWT", token);
|
||||
store.commit("setSession", generateRandomCode(8));
|
||||
store.commit("setUser", data.user);
|
||||
mutations.setJWT(token);
|
||||
mutations.setSession(generateRandomCode(8));
|
||||
mutations.setUser(data.user);
|
||||
}
|
||||
|
||||
export async function validateLogin() {
|
||||
|
@ -29,7 +28,6 @@ export async function validateLogin() {
|
|||
|
||||
export async function login(username, password, recaptcha) {
|
||||
const data = { username, password, recaptcha };
|
||||
|
||||
const res = await fetch(`${baseURL}/api/login`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
|
@ -53,11 +51,9 @@ export async function renew(jwt) {
|
|||
"X-Auth": jwt,
|
||||
},
|
||||
});
|
||||
|
||||
const body = await res.text();
|
||||
|
||||
if (res.status === 200) {
|
||||
store.commit("setSession", generateRandomCode(8));
|
||||
mutations.setSession(generateRandomCode(8));
|
||||
parseToken(body);
|
||||
} else {
|
||||
throw new Error(body);
|
||||
|
@ -67,7 +63,6 @@ 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];
|
||||
|
@ -76,9 +71,8 @@ function generateRandomCode(length) {
|
|||
return code;
|
||||
}
|
||||
|
||||
export async function signup(username, password) {
|
||||
export async function signupLogin(username, password) {
|
||||
const data = { username, password };
|
||||
|
||||
const res = await fetch(`${baseURL}/api/signup`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
|
@ -94,9 +88,8 @@ export async function signup(username, password) {
|
|||
|
||||
export function logout() {
|
||||
document.cookie = "auth=; expires=Thu, 01 Jan 1970 00:00:01 GMT; path=/";
|
||||
|
||||
store.commit("setJWT", "");
|
||||
store.commit("setUser", null);
|
||||
mutations.setJWT("");
|
||||
mutations.setUser(null);
|
||||
localStorage.setItem("jwt", null);
|
||||
router.push({ path: "/login" });
|
||||
}
|
||||
|
|
|
@ -2,7 +2,6 @@ function loading(button) {
|
|||
let el = document.querySelector(`#${button}-button > i`);
|
||||
|
||||
if (el === undefined || el === null) {
|
||||
console.log('Error getting button ' + button)
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -24,7 +23,6 @@ function done(button) {
|
|||
let el = document.querySelector(`#${button}-button > i`);
|
||||
|
||||
if (el === undefined || el === null) {
|
||||
console.log('Error getting button ' + button)
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -41,7 +39,6 @@ function success(button) {
|
|||
let el = document.querySelector(`#${button}-button > i`);
|
||||
|
||||
if (el === undefined || el === null) {
|
||||
console.log('Error getting button ' + button)
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -17,7 +17,6 @@ const resizePreview = window.FileBrowser.ResizePreview;
|
|||
const enableExec = window.FileBrowser.EnableExec;
|
||||
const origin = window.location.origin;
|
||||
|
||||
console.log(window.FileBrowser)
|
||||
export {
|
||||
name,
|
||||
disableExternal,
|
||||
|
|
|
@ -1,12 +0,0 @@
|
|||
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;
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
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;
|
||||
}
|
|
@ -0,0 +1,97 @@
|
|||
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,
|
||||
};
|
|
@ -1,4 +1,4 @@
|
|||
import store from "@/store";
|
||||
import { state } 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 = store.state.upload.id;
|
||||
let id = 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 }),
|
||||
};
|
||||
|
||||
store.dispatch("upload/upload", item);
|
||||
state.dispatch("upload/upload", item);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,7 +29,18 @@ 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,
|
||||
|
|
|
@ -1,68 +0,0 @@
|
|||
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;
|
|
@ -2,7 +2,7 @@
|
|||
<div>
|
||||
<breadcrumbs base="/files" />
|
||||
<errors v-if="error" :errorCode="error.status" />
|
||||
<component v-else-if="currentView" :is="currentView"></component>
|
||||
<component v-else-if="currentViewLoaded" :is="currentView"></component>
|
||||
<div v-else>
|
||||
<h2 class="message delayed">
|
||||
<div class="spinner">
|
||||
|
@ -15,20 +15,16 @@
|
|||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { files as api } from "@/api";
|
||||
import { mapState, mapMutations } from "vuex";
|
||||
|
||||
import Breadcrumbs from "@/components/Breadcrumbs";
|
||||
import Errors from "@/views/Errors";
|
||||
import Breadcrumbs from "@/components/Breadcrumbs.vue";
|
||||
import Errors from "@/views/Errors.vue";
|
||||
import Preview from "@/views/files/Preview.vue";
|
||||
import ListingView from "@/views/files/ListingView.vue";
|
||||
import Editor from "@/views/files/Editor.vue";
|
||||
|
||||
function clean(path) {
|
||||
return path.endsWith("/") ? path.slice(0, -1) : path;
|
||||
}
|
||||
import { state, mutations, getters } from "@/store";
|
||||
import { pathsMatch } from "@/utils/url";
|
||||
|
||||
export default {
|
||||
name: "files",
|
||||
|
@ -39,25 +35,21 @@ export default {
|
|||
ListingView,
|
||||
Editor,
|
||||
},
|
||||
data: function () {
|
||||
data() {
|
||||
return {
|
||||
error: null,
|
||||
width: window.innerWidth,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState(["req", "reload", "loading"]),
|
||||
currentView() {
|
||||
if (this.req.type == undefined) {
|
||||
return null;
|
||||
}
|
||||
if (this.req.isDir) {
|
||||
return "listingView";
|
||||
} else if (Object.prototype.hasOwnProperty.call(this.req, 'content')) {
|
||||
return "editor";
|
||||
} else {
|
||||
return "preview";
|
||||
}
|
||||
return getters.currentView();
|
||||
},
|
||||
currentViewLoaded() {
|
||||
return getters.currentView() !== null;
|
||||
},
|
||||
reload() {
|
||||
return state.reload; // Access reload from state
|
||||
},
|
||||
},
|
||||
created() {
|
||||
|
@ -65,8 +57,9 @@ export default {
|
|||
},
|
||||
watch: {
|
||||
$route: "fetchData",
|
||||
reload: function (value) {
|
||||
reload(value) {
|
||||
if (value === true) {
|
||||
console.log("reloading")
|
||||
this.fetchData();
|
||||
}
|
||||
},
|
||||
|
@ -78,56 +71,50 @@ export default {
|
|||
window.removeEventListener("keydown", this.keyEvent);
|
||||
},
|
||||
unmounted() {
|
||||
if (this.$store.state.showShell) {
|
||||
this.$store.commit("toggleShell");
|
||||
if (state.showShell) {
|
||||
mutations.toggleShell(); // Use mutation
|
||||
}
|
||||
this.$store.commit("updateRequest", {});
|
||||
},
|
||||
currentView(newView) {
|
||||
// Commit the new value to the store
|
||||
this.setCurrentValue(newView);
|
||||
mutations.replaceRequest({}); // Use mutation
|
||||
},
|
||||
methods: {
|
||||
...mapMutations(["setLoading", "setCurrentView"]),
|
||||
async fetchData() {
|
||||
// Reset view information.
|
||||
this.$store.commit("setReload", false);
|
||||
this.$store.commit("resetSelected");
|
||||
this.$store.commit("multiple", false);
|
||||
this.$store.commit("closeHovers");
|
||||
// Reset view information using mutations
|
||||
mutations.setReload(false);
|
||||
mutations.resetSelected();
|
||||
mutations.setMultiple(false);
|
||||
mutations.closeHovers();
|
||||
|
||||
// Set loading to true and reset the error.
|
||||
this.setLoading(true);
|
||||
mutations.setLoading(true);
|
||||
this.error = null;
|
||||
|
||||
let url = this.$route.path;
|
||||
let url = state.route.path;
|
||||
if (url === "") url = "/";
|
||||
if (url[0] !== "/") url = "/" + url;
|
||||
|
||||
let data = {};
|
||||
try {
|
||||
// Fetch initial data
|
||||
let res = await api.fetch(url);
|
||||
// If not a directory, fetch content
|
||||
if (!res.isDir) {
|
||||
// get content of file if possible
|
||||
res = await api.fetch(url, true);
|
||||
}
|
||||
|
||||
if (clean(res.path) !== clean(`/${this.$route.params.pathMatch}`)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.$store.commit("updateRequest", res);
|
||||
data = res;
|
||||
// Verify if the fetched path matches the current route
|
||||
if (pathsMatch(res.path, `/${state.route.params.path}`)) {
|
||||
document.title = `${res.name} - ${document.title}`;
|
||||
}
|
||||
} catch (e) {
|
||||
this.error = e;
|
||||
} finally {
|
||||
this.setLoading(false);
|
||||
}
|
||||
mutations.setLoading(false);
|
||||
mutations.replaceRequest(data);
|
||||
},
|
||||
keyEvent(event) {
|
||||
// F1!
|
||||
if (event.keyCode === 112) {
|
||||
event.preventDefault();
|
||||
this.$store.commit("showHover", "help");
|
||||
mutations.showHover("help"); // Use mutation
|
||||
}
|
||||
},
|
||||
},
|
||||
|
|
|
@ -20,18 +20,21 @@
|
|||
<prompts :class="{ 'dark-mode': isDarkMode }"></prompts>
|
||||
<upload-files></upload-files>
|
||||
</div>
|
||||
<div class="card" id="popup-notification">
|
||||
<i v-on:click="closePopUp" class="material-icons">close</i>
|
||||
<div id="popup-notification-content">no info</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import editorBar from "./bars/EditorBar.vue";
|
||||
import defaultBar from "./bars/Default.vue";
|
||||
import listingBar from "./bars/ListingBar.vue";
|
||||
import Prompts from "@/components/prompts/Prompts";
|
||||
import { mapState, mapGetters } from "vuex";
|
||||
import Prompts from "@/components/prompts/Prompts.vue";
|
||||
import Sidebar from "@/components/Sidebar.vue";
|
||||
import UploadFiles from "../components/prompts/UploadFiles";
|
||||
import UploadFiles from "../components/prompts/UploadFiles.vue";
|
||||
import { closePopUp } from "@/notify";
|
||||
import { enableExec } from "@/utils/constants";
|
||||
import { darkMode } from "@/utils/constants";
|
||||
import { state, getters, mutations } from "@/store";
|
||||
|
||||
export default {
|
||||
name: "layout",
|
||||
|
@ -43,7 +46,7 @@ export default {
|
|||
Prompts,
|
||||
UploadFiles,
|
||||
},
|
||||
data: function () {
|
||||
data() {
|
||||
return {
|
||||
showContexts: true,
|
||||
dragCounter: 0,
|
||||
|
@ -52,50 +55,56 @@ export default {
|
|||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters([
|
||||
"isLogged",
|
||||
"progress",
|
||||
"isListing",
|
||||
"currentPrompt",
|
||||
"currentPromptName",
|
||||
]),
|
||||
...mapState(["req", "user", "state"]),
|
||||
showOverlay: function () {
|
||||
return this.currentPrompt !== null && this.currentPrompt.prompt !== "more";
|
||||
closePopUp() {
|
||||
return closePopUp;
|
||||
},
|
||||
progress() {
|
||||
return getters.progress(); // Access getter directly from the store
|
||||
},
|
||||
isListing() {
|
||||
return getters.isListing(); // Access getter directly from the store
|
||||
},
|
||||
currentPrompt() {
|
||||
return getters.currentPrompt(); // Access getter directly from the store
|
||||
},
|
||||
currentPromptName() {
|
||||
return getters.currentPromptName(); // Access getter directly from the store
|
||||
},
|
||||
req() {
|
||||
return state.req; // Access state directly from the store
|
||||
},
|
||||
user() {
|
||||
return state.user; // Access state directly from the store
|
||||
},
|
||||
showOverlay() {
|
||||
return getters.currentPrompt() !== null && getters.currentPromptName() !== "more";
|
||||
},
|
||||
isDarkMode() {
|
||||
return this.user && Object.prototype.hasOwnProperty.call(this.user, "darkMode")
|
||||
? this.user.darkMode
|
||||
: darkMode;
|
||||
return getters.isDarkMode();
|
||||
},
|
||||
isExecEnabled() {
|
||||
return enableExec;
|
||||
},
|
||||
isExecEnabled: () => enableExec,
|
||||
currentView() {
|
||||
if (this.req.type == undefined) {
|
||||
return null;
|
||||
}
|
||||
if (this.req.isDir) {
|
||||
return "listingView";
|
||||
} else if (Object.prototype.hasOwnProperty.call(this.req, 'content')) {
|
||||
return "editor";
|
||||
} else {
|
||||
return "preview";
|
||||
}
|
||||
return getters.currentView();
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
$route: function () {
|
||||
this.$store.commit("resetSelected");
|
||||
this.$store.commit("multiple", false);
|
||||
if (this.currentPrompt?.prompt !== "success") this.$store.commit("closeHovers");
|
||||
$route() {
|
||||
mutations.resetSelected();
|
||||
mutations.setMultiple(false);
|
||||
if (getters.currentPromptName() !== "success") {
|
||||
mutations.closeHovers();
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
resetPrompts() {
|
||||
this.$store.commit("closeHovers");
|
||||
mutations.closeHovers();
|
||||
},
|
||||
getTitle() {
|
||||
let title = "Title";
|
||||
if (this.$route.path.startsWith("/settings/")) {
|
||||
if (state.route.path.startsWith("/settings/")) {
|
||||
title = "Settings";
|
||||
}
|
||||
return title;
|
||||
|
@ -114,7 +123,7 @@ main::-webkit-scrollbar {
|
|||
}
|
||||
/* Use the class .dark-mode to apply styles conditionally */
|
||||
.dark-mode {
|
||||
background: var(--background);
|
||||
background: var(--background) !important;
|
||||
color: var(--textPrimary);
|
||||
}
|
||||
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue