V0.2.6 (#150)
This commit is contained in:
parent
62f3953aea
commit
b4bba3391d
|
@ -9,37 +9,40 @@ jobs:
|
||||||
test-backend:
|
test-backend:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: actions/setup-go@v4
|
- uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: 1.22.0
|
go-version: 'stable'
|
||||||
- run: cd backend && go test -race -v ./...
|
- working-directory: backend
|
||||||
|
run: go test -race -v ./...
|
||||||
lint-backend:
|
lint-backend:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: actions/setup-go@v4
|
- uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: 1.22.0
|
go-version: 'stable'
|
||||||
- run: go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.54.2
|
- uses: golangci/golangci-lint-action@v6
|
||||||
- run: cd backend && golangci-lint run
|
with:
|
||||||
|
version: v1.59
|
||||||
|
working-directory: backend
|
||||||
format-backend:
|
format-backend:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: actions/setup-go@v4
|
- uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: 1.22.0
|
go-version: 'stable'
|
||||||
- run: cd backend && go fmt ./...
|
- working-directory: backend
|
||||||
|
run: go fmt ./...
|
||||||
lint-frontend:
|
lint-frontend:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v4
|
||||||
- uses: actions/setup-node@v2
|
- uses: actions/setup-node@v4
|
||||||
with:
|
- working-directory: frontend
|
||||||
node-version: '20'
|
run: npm i eslint && npm run lint
|
||||||
- run: cd frontend && npm i eslint
|
|
||||||
- run: cd frontend && npm run lint
|
|
||||||
push_dev_to_registry:
|
push_dev_to_registry:
|
||||||
name: Push dev image
|
name: Push dev image
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
@ -63,7 +66,7 @@ jobs:
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
- name: Extract metadata (tags, labels) for Docker
|
- name: Extract metadata (tags, labels) for Docker
|
||||||
id: meta
|
id: meta
|
||||||
uses: docker/metadata-action@9ec57ed1fcdbf14dcef7dfbe97b2010124a938b7
|
uses: docker/metadata-action@v5
|
||||||
with:
|
with:
|
||||||
images: gtstef/filebrowser
|
images: gtstef/filebrowser
|
||||||
- name: Build and push
|
- name: Build and push
|
||||||
|
|
|
@ -9,37 +9,39 @@ jobs:
|
||||||
test-backend:
|
test-backend:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: actions/setup-go@v4
|
- uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: 1.22.0
|
go-version: 'stable'
|
||||||
- run: cd backend && go test -race -v ./...
|
- working-directory: backend
|
||||||
|
run: go test -race -v ./...
|
||||||
lint-backend:
|
lint-backend:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: actions/setup-go@v4
|
- uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: 1.22.0
|
go-version: 'stable'
|
||||||
- run: go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.54.2
|
- uses: golangci/golangci-lint-action@v6
|
||||||
- run: cd backend && golangci-lint run
|
with:
|
||||||
|
version: v1.59
|
||||||
|
working-directory: backend
|
||||||
format-backend:
|
format-backend:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: actions/setup-go@v4
|
- uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: 1.22.0
|
go-version: 'stable'
|
||||||
- run: cd backend && go fmt ./...
|
- working-directory: backend
|
||||||
|
run: go fmt ./...
|
||||||
lint-frontend:
|
lint-frontend:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v4
|
||||||
- uses: actions/setup-node@v2
|
- uses: actions/setup-node@v4
|
||||||
with:
|
- working-directory: frontend
|
||||||
node-version: '20'
|
run: npm i eslint && npm run lint
|
||||||
- run: cd frontend && npm i eslint
|
|
||||||
- run: cd frontend && npm run lint
|
|
||||||
|
|
||||||
push_latest_to_registry:
|
push_latest_to_registry:
|
||||||
needs: [lint-frontend, lint-backend, test-backend, format-backend]
|
needs: [lint-frontend, lint-backend, test-backend, format-backend]
|
||||||
|
@ -65,7 +67,7 @@ jobs:
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
- name: Extract metadata (tags, labels) for Docker
|
- name: Extract metadata (tags, labels) for Docker
|
||||||
id: meta
|
id: meta
|
||||||
uses: docker/metadata-action@9ec57ed1fcdbf14dcef7dfbe97b2010124a938b7
|
uses: docker/metadata-action@v5
|
||||||
with:
|
with:
|
||||||
images: gtstef/filebrowser
|
images: gtstef/filebrowser
|
||||||
- name: Build and push
|
- name: Build and push
|
||||||
|
|
|
@ -11,37 +11,39 @@ jobs:
|
||||||
test-backend:
|
test-backend:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: actions/setup-go@v4
|
- uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: 1.22.0
|
go-version: 'stable'
|
||||||
- run: cd backend && go test -race -v ./...
|
- working-directory: backend
|
||||||
|
run: go test -race -v ./...
|
||||||
lint-backend:
|
lint-backend:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: actions/setup-go@v4
|
- uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: 1.22.0
|
go-version: 'stable'
|
||||||
- run: go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.54.2
|
- uses: golangci/golangci-lint-action@v6
|
||||||
- run: cd backend && golangci-lint run
|
with:
|
||||||
|
version: v1.59
|
||||||
|
working-directory: backend
|
||||||
format-backend:
|
format-backend:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: actions/setup-go@v4
|
- uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: 1.22.0
|
go-version: 'stable'
|
||||||
- run: cd backend && go fmt ./...
|
- working-directory: backend
|
||||||
|
run: go fmt ./...
|
||||||
lint-frontend:
|
lint-frontend:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v4
|
||||||
- uses: actions/setup-node@v2
|
- uses: actions/setup-node@v4
|
||||||
with:
|
- working-directory: frontend
|
||||||
node-version: '20'
|
run: npm i eslint && npm run lint
|
||||||
- run: cd frontend && npm i eslint
|
|
||||||
- run: cd frontend && npm run lint
|
|
||||||
|
|
||||||
push_pr_to_registry:
|
push_pr_to_registry:
|
||||||
name: Push PR
|
name: Push PR
|
||||||
|
@ -66,7 +68,7 @@ jobs:
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
- name: Extract metadata (tags, labels) for Docker
|
- name: Extract metadata (tags, labels) for Docker
|
||||||
id: meta
|
id: meta
|
||||||
uses: docker/metadata-action@9ec57ed1fcdbf14dcef7dfbe97b2010124a938b7
|
uses: docker/metadata-action@v5
|
||||||
with:
|
with:
|
||||||
images: gtstef/filebrowser
|
images: gtstef/filebrowser
|
||||||
- name: Build and push
|
- name: Build and push
|
||||||
|
|
|
@ -35,37 +35,40 @@ jobs:
|
||||||
test-backend:
|
test-backend:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: actions/setup-go@v4
|
- uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: 1.22.0
|
go-version: 'stable'
|
||||||
- run: cd backend && go test -race -v ./...
|
- working-directory: backend
|
||||||
|
run: go test -race -v ./...
|
||||||
lint-backend:
|
lint-backend:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: actions/setup-go@v4
|
- uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: 1.22.0
|
go-version: 'stable'
|
||||||
- run: go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.54.2
|
- uses: golangci/golangci-lint-action@v6
|
||||||
- run: cd backend && golangci-lint run
|
with:
|
||||||
|
version: v1.59
|
||||||
|
working-directory: backend
|
||||||
format-backend:
|
format-backend:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: actions/setup-go@v4
|
- uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: 1.22.0
|
go-version: 'stable'
|
||||||
- run: cd backend && go fmt ./...
|
- working-directory: backend
|
||||||
|
run: go fmt ./...
|
||||||
lint-frontend:
|
lint-frontend:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v4
|
||||||
- uses: actions/setup-node@v2
|
- uses: actions/setup-node@v4
|
||||||
with:
|
- working-directory: frontend
|
||||||
node-version: '20'
|
run: npm i eslint && npm run lint
|
||||||
- run: cd frontend && npm i eslint
|
|
||||||
- run: cd frontend && npm run lint
|
|
||||||
push_release_to_registry:
|
push_release_to_registry:
|
||||||
needs: [lint-frontend, lint-backend, test-backend, format-backend]
|
needs: [lint-frontend, lint-backend, test-backend, format-backend]
|
||||||
name: Push release
|
name: Push release
|
||||||
|
@ -91,7 +94,7 @@ jobs:
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
- name: Extract metadata (tags, labels) for Docker
|
- name: Extract metadata (tags, labels) for Docker
|
||||||
id: meta
|
id: meta
|
||||||
uses: docker/metadata-action@9ec57ed1fcdbf14dcef7dfbe97b2010124a938b7
|
uses: docker/metadata-action@v5
|
||||||
with:
|
with:
|
||||||
images: gtstef/filebrowser
|
images: gtstef/filebrowser
|
||||||
- name: Strip v from version number
|
- name: Strip v from version number
|
||||||
|
|
|
@ -6,6 +6,8 @@ rice-box.go
|
||||||
/filebrowser
|
/filebrowser
|
||||||
/filebrowser.exe
|
/filebrowser.exe
|
||||||
/frontend/dist
|
/frontend/dist
|
||||||
|
/frontend/pkg
|
||||||
|
/frontend/package-lock.json
|
||||||
/backend/vendor
|
/backend/vendor
|
||||||
/backend/*.cov
|
/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).
|
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
|
## v0.2.5
|
||||||
|
|
||||||
- Fix: delete user prompt works using native hovers.
|
- Fix: delete user prompt works using native hovers.
|
||||||
|
|
||||||
## v0.2.4
|
## 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: [playable shared video](https://github.com/filebrowser/filebrowser/issues/2537)
|
||||||
- Feature: photos, videos, and audio get embedded preview on share instead of icon
|
- 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
|
- Bump go version to 1.22
|
||||||
- In prep for vue3 migration, npm modules removed:
|
- In prep for vue3 migration, npm modules removed:
|
||||||
- js-base64
|
- js-base64
|
||||||
|
|
|
@ -1,22 +1,18 @@
|
||||||
FROM node:slim as nbuild
|
FROM node:slim as nbuild
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
COPY ./frontend/package*.json ./
|
COPY ./frontend/package*.json ./
|
||||||
RUN npm ci --maxsockets 1
|
RUN npm i --maxsockets 1
|
||||||
COPY ./frontend/ ./
|
COPY ./frontend/ ./
|
||||||
RUN npm run build
|
RUN npm run build
|
||||||
|
|
||||||
FROM golang:1.22-alpine as base
|
FROM golang:1.22-alpine as base
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
COPY ./backend ./
|
COPY ./backend ./
|
||||||
RUN go get -u golang.org/x/net
|
|
||||||
RUN go build -ldflags="-w -s" -o filebrowser .
|
RUN go build -ldflags="-w -s" -o filebrowser .
|
||||||
|
|
||||||
FROM alpine:latest
|
FROM alpine:latest
|
||||||
ARG app="/app/filebrowser"
|
ARG app="/app/filebrowser"
|
||||||
RUN apk --no-cache add \
|
RUN apk --no-cache add ca-certificates mailcap
|
||||||
ca-certificates \
|
|
||||||
mailcap
|
|
||||||
WORKDIR /
|
|
||||||
COPY --from=base /app/filebrowser* ./
|
COPY --from=base /app/filebrowser* ./
|
||||||
COPY --from=nbuild /app/dist/ ./frontend/dist/
|
COPY --from=nbuild /app/dist/ ./frontend/dist/
|
||||||
ENTRYPOINT [ "./filebrowser" ]
|
ENTRYPOINT [ "./filebrowser" ]
|
||||||
|
|
38
README.md
38
README.md
|
@ -4,7 +4,7 @@
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<img src="frontend/public/img/icons/favicon-256x256.png" width="100" title="Login With Custom URL">
|
<img src="frontend/public/img/icons/favicon-256x256.png" width="100" title="Login With Custom URL">
|
||||||
</p>
|
</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">
|
<p align="center">
|
||||||
<img width="800" src="https://github.com/gtsteffaniak/filebrowser/assets/42989099/899152cf-3e69-4179-aa82-752af2df3fc6" title="Main Screenshot">
|
<img width="800" src="https://github.com/gtsteffaniak/filebrowser/assets/42989099/899152cf-3e69-4179-aa82-752af2df3fc6" title="Main Screenshot">
|
||||||
</p>
|
</p>
|
||||||
|
@ -15,41 +15,59 @@
|
||||||
|
|
||||||
This fork makes the following significant changes to filebrowser for origin:
|
This fork makes the following significant changes to filebrowser for origin:
|
||||||
|
|
||||||
1. [x] Better search.
|
1. [x] Better search
|
||||||
- Lightning fast
|
- Lightning fast
|
||||||
- realtime results as you type
|
- realtime results as you type
|
||||||
- Works with more type filters
|
- Works with more type filters
|
||||||
- interactive results page.
|
- interactive results page.
|
||||||
2. [x] Revamped and simplified GUI navbar and sidebar menu.
|
2. [x] Revamped and simplified GUI navbar and sidebar menu.
|
||||||
3. [x] **IMPORTANT** Revamped configuration via `filebrowser.yml` config file.
|
- Additional compact view mode as well as refreshed view mode styles.
|
||||||
4. [x] More configurations possible at a per-user level
|
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">
|
- <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
|
## About
|
||||||
|
|
||||||
Filebrowser provides a file managing interface within a specified directory
|
Filebrowser provides a file managing interface within a specified directory
|
||||||
and it can be used to upload, delete, preview, rename and edit your files.
|
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
|
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
|
This repository is a fork, a collection of changes that make this program
|
||||||
work better in terms of asthetics and performance. Improved search,
|
work better in terms of asthetics and performance. Improved search,
|
||||||
simplified ui (without removing features) and more secure and up-to-date
|
simplified ui (without removing features) and more secure and up-to-date
|
||||||
build are just a few examples.
|
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
|
## 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">
|
<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" 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/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/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="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">
|
<img width="560" alt="image" src="https://github.com/gtsteffaniak/filebrowser/assets/42989099/71d8f2b8-6fe6-4fdc-8aac-503d08c28d86">
|
||||||
|
|
||||||
|
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
|
||||||
## Install
|
## Install
|
||||||
|
|
||||||
Using docker:
|
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>
|
./filebrowser -f <filebrowser.yml or other /path/to/config.yaml>
|
||||||
|
|
|
@ -3,6 +3,7 @@ package cmd
|
||||||
import (
|
import (
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"flag"
|
"flag"
|
||||||
|
"fmt"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"log"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
|
@ -64,49 +65,49 @@ var rootCmd = &cobra.Command{
|
||||||
fileCache = diskcache.New(afero.NewOsFs(), cacheDir)
|
fileCache = diskcache.New(afero.NewOsFs(), cacheDir)
|
||||||
}
|
}
|
||||||
// initialize indexing and schedule indexing ever n minutes (default 5)
|
// 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)
|
_, err := os.Stat(serverConfig.Root)
|
||||||
checkErr(err)
|
checkErr(fmt.Sprint("cmd os.Stat ", serverConfig.Root), err)
|
||||||
var listener net.Listener
|
var listener net.Listener
|
||||||
address := serverConfig.Address + ":" + strconv.Itoa(serverConfig.Port)
|
address := serverConfig.Address + ":" + strconv.Itoa(serverConfig.Port)
|
||||||
switch {
|
switch {
|
||||||
case serverConfig.Socket != "":
|
case serverConfig.Socket != "":
|
||||||
listener, err = net.Listen("unix", serverConfig.Socket)
|
listener, err = net.Listen("unix", serverConfig.Socket)
|
||||||
checkErr(err)
|
checkErr("net.Listen", err)
|
||||||
socketPerm, err := cmd.Flags().GetUint32("socket-perm") //nolint:govet
|
socketPerm, err := cmd.Flags().GetUint32("socket-perm") //nolint:govet
|
||||||
checkErr(err)
|
checkErr("cmd.Flags().GetUint32", err)
|
||||||
err = os.Chmod(serverConfig.Socket, os.FileMode(socketPerm))
|
err = os.Chmod(serverConfig.Socket, os.FileMode(socketPerm))
|
||||||
checkErr(err)
|
checkErr("os.Chmod", err)
|
||||||
case serverConfig.TLSKey != "" && serverConfig.TLSCert != "":
|
case serverConfig.TLSKey != "" && serverConfig.TLSCert != "":
|
||||||
cer, err := tls.LoadX509KeyPair(serverConfig.TLSCert, serverConfig.TLSKey) //nolint:govet
|
cer, err := tls.LoadX509KeyPair(serverConfig.TLSCert, serverConfig.TLSKey) //nolint:govet
|
||||||
checkErr(err)
|
checkErr("tls.LoadX509KeyPair", err)
|
||||||
listener, err = tls.Listen("tcp", address, &tls.Config{
|
listener, err = tls.Listen("tcp", address, &tls.Config{
|
||||||
MinVersion: tls.VersionTLS12,
|
MinVersion: tls.VersionTLS12,
|
||||||
Certificates: []tls.Certificate{cer}},
|
Certificates: []tls.Certificate{cer}},
|
||||||
)
|
)
|
||||||
checkErr(err)
|
checkErr("tls.Listen", err)
|
||||||
default:
|
default:
|
||||||
listener, err = net.Listen("tcp", address)
|
listener, err = net.Listen("tcp", address)
|
||||||
checkErr(err)
|
checkErr("net.Listen", err)
|
||||||
}
|
}
|
||||||
sigc := make(chan os.Signal, 1)
|
sigc := make(chan os.Signal, 1)
|
||||||
signal.Notify(sigc, os.Interrupt, syscall.SIGTERM)
|
signal.Notify(sigc, os.Interrupt, syscall.SIGTERM)
|
||||||
go cleanupHandler(listener, sigc)
|
go cleanupHandler(listener, sigc)
|
||||||
assetsFs := dirFS{Dir: http.Dir("frontend/dist")}
|
assetsFs := dirFS{Dir: http.Dir("frontend/dist")}
|
||||||
handler, err := fbhttp.NewHandler(imgSvc, fileCache, d.store, &serverConfig, assetsFs)
|
handler, err := fbhttp.NewHandler(imgSvc, fileCache, d.store, &serverConfig, assetsFs)
|
||||||
checkErr(err)
|
checkErr("fbhttp.NewHandler", err)
|
||||||
defer listener.Close()
|
defer listener.Close()
|
||||||
log.Println("Listening on", listener.Addr().String())
|
log.Println("Listening on", listener.Addr().String())
|
||||||
//nolint: gosec
|
//nolint: gosec
|
||||||
if err := http.Serve(listener, handler); err != nil {
|
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}),
|
}, pythonConfig{allowNoDB: true}),
|
||||||
}
|
}
|
||||||
|
|
||||||
func StartFilebrowser() {
|
func StartFilebrowser() {
|
||||||
if err := rootCmd.Execute(); err != nil {
|
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()
|
settings.Config.Auth.Key = generateKey()
|
||||||
if settings.Config.Auth.Method == "noauth" {
|
if settings.Config.Auth.Method == "noauth" {
|
||||||
err := d.store.Auth.Save(&auth.NoAuth{})
|
err := d.store.Auth.Save(&auth.NoAuth{})
|
||||||
checkErr(err)
|
checkErr("d.store.Auth.Save", err)
|
||||||
} else {
|
} else {
|
||||||
settings.Config.Auth.Method = "password"
|
settings.Config.Auth.Method = "password"
|
||||||
err := d.store.Auth.Save(&auth.JSONAuth{})
|
err := d.store.Auth.Save(&auth.JSONAuth{})
|
||||||
checkErr(err)
|
checkErr("d.store.Auth.Save", err)
|
||||||
}
|
}
|
||||||
err := d.store.Settings.Save(&settings.Config)
|
err := d.store.Settings.Save(&settings.Config)
|
||||||
checkErr(err)
|
checkErr("d.store.Settings.Save", err)
|
||||||
err = d.store.Settings.SaveServer(&settings.Config.Server)
|
err = d.store.Settings.SaveServer(&settings.Config.Server)
|
||||||
checkErr(err)
|
checkErr("d.store.Settings.SaveServer", err)
|
||||||
user := &users.User{}
|
user := &users.User{}
|
||||||
settings.Config.UserDefaults.Apply(user)
|
settings.Config.UserDefaults.Apply(user)
|
||||||
user.Username = settings.Config.Auth.AdminUsername
|
user.Username = settings.Config.Auth.AdminUsername
|
||||||
|
@ -150,5 +151,5 @@ func quickSetup(d pythonData) {
|
||||||
Admin: true,
|
Admin: true,
|
||||||
}
|
}
|
||||||
err = d.store.Users.Save(user)
|
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) {
|
Run: python(func(cmd *cobra.Command, args []string, d pythonData) {
|
||||||
i, err := strconv.Atoi(args[0])
|
i, err := strconv.Atoi(args[0])
|
||||||
checkErr(err)
|
checkErr("strconv.Atoi", err)
|
||||||
f := i
|
f := i
|
||||||
if len(args) == 2 { //nolint:gomnd
|
if len(args) == 2 { //nolint:gomnd
|
||||||
f, err = strconv.Atoi(args[1])
|
f, err = strconv.Atoi(args[1])
|
||||||
checkErr(err)
|
checkErr("strconv.Atoi", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
user := func(u *users.User) {
|
user := func(u *users.User) {
|
||||||
u.Rules = append(u.Rules[:i], u.Rules[f+1:]...)
|
u.Rules = append(u.Rules[:i], u.Rules[f+1:]...)
|
||||||
err := d.store.Users.Save(u)
|
err := d.store.Users.Save(u)
|
||||||
checkErr(err)
|
checkErr("d.store.Users.Save", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
global := func(s *settings.Settings) {
|
global := func(s *settings.Settings) {
|
||||||
s.Rules = append(s.Rules[:i], s.Rules[f+1:]...)
|
s.Rules = append(s.Rules[:i], s.Rules[f+1:]...)
|
||||||
err := d.store.Settings.Save(s)
|
err := d.store.Settings.Save(s)
|
||||||
checkErr(err)
|
checkErr("d.store.Settings.Save", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
runRules(d.store, cmd, user, global)
|
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())
|
id := getUserIdentifier(cmd.Flags())
|
||||||
if id != nil {
|
if id != nil {
|
||||||
user, err := st.Users.Get("", id)
|
user, err := st.Users.Get("", id)
|
||||||
checkErr(err)
|
checkErr("st.Users.Get", err)
|
||||||
|
|
||||||
if usersFn != nil {
|
if usersFn != nil {
|
||||||
usersFn(user)
|
usersFn(user)
|
||||||
|
@ -44,7 +44,7 @@ func runRules(st *storage.Storage, cmd *cobra.Command, usersFn func(*users.User)
|
||||||
}
|
}
|
||||||
|
|
||||||
s, err := st.Settings.Get()
|
s, err := st.Settings.Get()
|
||||||
checkErr(err)
|
checkErr("st.Settings.Get", err)
|
||||||
|
|
||||||
if globalFn != nil {
|
if globalFn != nil {
|
||||||
globalFn(s)
|
globalFn(s)
|
||||||
|
|
|
@ -44,13 +44,13 @@ var rulesAddCmd = &cobra.Command{
|
||||||
user := func(u *users.User) {
|
user := func(u *users.User) {
|
||||||
u.Rules = append(u.Rules, rule)
|
u.Rules = append(u.Rules, rule)
|
||||||
err := d.store.Users.Save(u)
|
err := d.store.Users.Save(u)
|
||||||
checkErr(err)
|
checkErr("d.store.Users.Save", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
global := func(s *settings.Settings) {
|
global := func(s *settings.Settings) {
|
||||||
s.Rules = append(s.Rules, rule)
|
s.Rules = append(s.Rules, rule)
|
||||||
err := d.store.Settings.Save(s)
|
err := d.store.Settings.Save(s)
|
||||||
checkErr(err)
|
checkErr("d.store.Settings.Save", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
runRules(d.store, cmd, user, global)
|
runRules(d.store, cmd, user, global)
|
||||||
|
|
|
@ -22,19 +22,19 @@ var usersAddCmd = &cobra.Command{
|
||||||
LockPassword: mustGetBool(cmd.Flags(), "lockPassword"),
|
LockPassword: mustGetBool(cmd.Flags(), "lockPassword"),
|
||||||
}
|
}
|
||||||
servSettings, err := d.store.Settings.GetServer()
|
servSettings, err := d.store.Settings.GetServer()
|
||||||
checkErr(err)
|
checkErr("d.store.Settings.GetServer()", err)
|
||||||
// since getUserDefaults() polluted s.Defaults.Scope
|
// since getUserDefaults() polluted s.Defaults.Scope
|
||||||
// which makes the Scope not the one saved in the db
|
// which makes the Scope not the one saved in the db
|
||||||
// we need the right s.Defaults.Scope here
|
// we need the right s.Defaults.Scope here
|
||||||
s2, err := d.store.Settings.Get()
|
s2, err := d.store.Settings.Get()
|
||||||
checkErr(err)
|
checkErr("d.store.Settings.Get()", err)
|
||||||
|
|
||||||
userHome, err := s2.MakeUserDir(user.Username, user.Scope, servSettings.Root)
|
userHome, err := s2.MakeUserDir(user.Username, user.Scope, servSettings.Root)
|
||||||
checkErr(err)
|
checkErr("s2.MakeUserDir", err)
|
||||||
user.Scope = userHome
|
user.Scope = userHome
|
||||||
|
|
||||||
err = d.store.Users.Save(user)
|
err = d.store.Users.Save(user)
|
||||||
checkErr(err)
|
checkErr("d.store.Users.Save", err)
|
||||||
printUsers([]*users.User{user})
|
printUsers([]*users.User{user})
|
||||||
}, pythonConfig{}),
|
}, pythonConfig{}),
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,9 +16,9 @@ path to the file where you want to write the users.`,
|
||||||
Args: jsonYamlArg,
|
Args: jsonYamlArg,
|
||||||
Run: python(func(cmd *cobra.Command, args []string, d pythonData) {
|
Run: python(func(cmd *cobra.Command, args []string, d pythonData) {
|
||||||
list, err := d.store.Users.Gets("")
|
list, err := d.store.Users.Gets("")
|
||||||
checkErr(err)
|
checkErr("d.store.Users.Gets", err)
|
||||||
|
|
||||||
err = marshal(args[0], list)
|
err = marshal(args[0], list)
|
||||||
checkErr(err)
|
checkErr("marshal", err)
|
||||||
}, pythonConfig{}),
|
}, pythonConfig{}),
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,6 +46,6 @@ var findUsers = python(func(cmd *cobra.Command, args []string, d pythonData) {
|
||||||
list, err = d.store.Users.Gets("")
|
list, err = d.store.Users.Gets("")
|
||||||
}
|
}
|
||||||
|
|
||||||
checkErr(err)
|
checkErr("findUsers", err)
|
||||||
printUsers(list)
|
printUsers(list)
|
||||||
}, pythonConfig{})
|
}, pythonConfig{})
|
||||||
|
|
|
@ -27,28 +27,28 @@ list or set it to 0.`,
|
||||||
Args: jsonYamlArg,
|
Args: jsonYamlArg,
|
||||||
Run: python(func(cmd *cobra.Command, args []string, d pythonData) {
|
Run: python(func(cmd *cobra.Command, args []string, d pythonData) {
|
||||||
fd, err := os.Open(args[0])
|
fd, err := os.Open(args[0])
|
||||||
checkErr(err)
|
checkErr("os.Open", err)
|
||||||
defer fd.Close()
|
defer fd.Close()
|
||||||
|
|
||||||
list := []*users.User{}
|
list := []*users.User{}
|
||||||
err = unmarshal(args[0], &list)
|
err = unmarshal(args[0], &list)
|
||||||
checkErr(err)
|
checkErr("unmarshal", err)
|
||||||
|
|
||||||
for _, user := range list {
|
for _, user := range list {
|
||||||
err = user.Clean("")
|
err = user.Clean("")
|
||||||
checkErr(err)
|
checkErr("Clean", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if mustGetBool(cmd.Flags(), "replace") {
|
if mustGetBool(cmd.Flags(), "replace") {
|
||||||
oldUsers, err := d.store.Users.Gets("")
|
oldUsers, err := d.store.Users.Gets("")
|
||||||
checkErr(err)
|
checkErr("d.store.Users.Gets", err)
|
||||||
|
|
||||||
err = marshal("users.backup.json", list)
|
err = marshal("users.backup.json", list)
|
||||||
checkErr(err)
|
checkErr("marshal users.backup.json", err)
|
||||||
|
|
||||||
for _, user := range oldUsers {
|
for _, user := range oldUsers {
|
||||||
err = d.store.Users.Delete(user.ID)
|
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.
|
// User exists in DB.
|
||||||
if err == nil {
|
if err == nil {
|
||||||
if !overwrite {
|
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
|
// If the usernames mismatch, check if there is another one in the DB
|
||||||
|
@ -68,7 +69,8 @@ list or set it to 0.`,
|
||||||
// operation
|
// operation
|
||||||
if user.Username != onDB.Username {
|
if user.Username != onDB.Username {
|
||||||
if conflictuous, err := d.store.Users.Get("", user.Username); err == nil { //nolint:govet
|
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 {
|
} else {
|
||||||
|
@ -78,7 +80,7 @@ list or set it to 0.`,
|
||||||
}
|
}
|
||||||
|
|
||||||
err = d.store.Users.Save(user)
|
err = d.store.Users.Save(user)
|
||||||
checkErr(err)
|
checkErr("d.store.Users.Save", err)
|
||||||
}
|
}
|
||||||
}, pythonConfig{}),
|
}, pythonConfig{}),
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,7 +25,7 @@ var usersRmCmd = &cobra.Command{
|
||||||
err = d.store.Users.Delete(id)
|
err = d.store.Users.Delete(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
checkErr(err)
|
checkErr("usersRmCmd", err)
|
||||||
fmt.Println("user deleted successfully")
|
fmt.Println("user deleted successfully")
|
||||||
}, pythonConfig{}),
|
}, pythonConfig{}),
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,10 +29,10 @@ options you want to change.`,
|
||||||
} else {
|
} else {
|
||||||
user, err = d.store.Users.Get("", username)
|
user, err = d.store.Users.Get("", username)
|
||||||
}
|
}
|
||||||
checkErr(err)
|
checkErr("d.store.Users.Get", err)
|
||||||
|
|
||||||
err = d.store.Users.Update(user)
|
err = d.store.Users.Update(user)
|
||||||
checkErr(err)
|
checkErr("d.store.Users.Update", err)
|
||||||
printUsers([]*users.User{user})
|
printUsers([]*users.User{user})
|
||||||
}, pythonConfig{}),
|
}, pythonConfig{}),
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ package cmd
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
@ -17,33 +18,33 @@ import (
|
||||||
"github.com/gtsteffaniak/filebrowser/storage/bolt"
|
"github.com/gtsteffaniak/filebrowser/storage/bolt"
|
||||||
)
|
)
|
||||||
|
|
||||||
func checkErr(err error) {
|
func checkErr(source string, err error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatalf("%s: %v", source, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func mustGetString(flags *pflag.FlagSet, flag string) string {
|
func mustGetString(flags *pflag.FlagSet, flag string) string {
|
||||||
s, err := flags.GetString(flag)
|
s, err := flags.GetString(flag)
|
||||||
checkErr(err)
|
checkErr("mustGetString", err)
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
func mustGetBool(flags *pflag.FlagSet, flag string) bool {
|
func mustGetBool(flags *pflag.FlagSet, flag string) bool {
|
||||||
b, err := flags.GetBool(flag)
|
b, err := flags.GetBool(flag)
|
||||||
checkErr(err)
|
checkErr("mustGetBool", err)
|
||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
func mustGetUint(flags *pflag.FlagSet, flag string) uint {
|
func mustGetUint(flags *pflag.FlagSet, flag string) uint {
|
||||||
b, err := flags.GetUint(flag)
|
b, err := flags.GetUint(flag)
|
||||||
checkErr(err)
|
checkErr("mustGetUint", err)
|
||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
func generateKey() []byte {
|
func generateKey() []byte {
|
||||||
k, err := settings.GenerateKey()
|
k, err := settings.GenerateKey()
|
||||||
checkErr(err)
|
checkErr("generateKey", err)
|
||||||
return k
|
return k
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -96,18 +97,19 @@ func python(fn pythonFunc, cfg pythonConfig) cobraFunc {
|
||||||
|
|
||||||
data.hadDB = exists
|
data.hadDB = exists
|
||||||
db, err := storm.Open(path)
|
db, err := storm.Open(path)
|
||||||
checkErr(err)
|
checkErr(fmt.Sprintf("storm.Open path %v", path), err)
|
||||||
|
|
||||||
defer db.Close()
|
defer db.Close()
|
||||||
data.store, err = bolt.NewStorage(db)
|
data.store, err = bolt.NewStorage(db)
|
||||||
checkErr(err)
|
checkErr("bolt.NewStorage", err)
|
||||||
fn(cmd, args, data)
|
fn(cmd, args, data)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func marshal(filename string, data interface{}) error {
|
func marshal(filename string, data interface{}) error {
|
||||||
fd, err := os.Create(filename)
|
fd, err := os.Create(filename)
|
||||||
checkErr(err)
|
|
||||||
|
checkErr("os.Create", err)
|
||||||
defer fd.Close()
|
defer fd.Close()
|
||||||
|
|
||||||
switch ext := filepath.Ext(filename); ext {
|
switch ext := filepath.Ext(filename); ext {
|
||||||
|
@ -125,7 +127,7 @@ func marshal(filename string, data interface{}) error {
|
||||||
|
|
||||||
func unmarshal(filename string, data interface{}) error {
|
func unmarshal(filename string, data interface{}) error {
|
||||||
fd, err := os.Open(filename)
|
fd, err := os.Open(filename)
|
||||||
checkErr(err)
|
checkErr("os.Open", err)
|
||||||
defer fd.Close()
|
defer fd.Close()
|
||||||
|
|
||||||
switch ext := filepath.Ext(filename); ext {
|
switch ext := filepath.Ext(filename); ext {
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
server:
|
server:
|
||||||
port: 80
|
port: 8080
|
||||||
baseURL: "/"
|
baseURL: "/"
|
||||||
root: "/srv"
|
root: "/Users/steffag/"
|
||||||
|
indexing: false
|
||||||
auth:
|
auth:
|
||||||
method: password
|
method: password
|
||||||
signup: false
|
signup: false
|
||||||
|
|
|
@ -123,7 +123,7 @@ func FileInfoFaster(opts FileOptions) (*FileInfo, error) {
|
||||||
if exists && !opts.Content {
|
if exists && !opts.Content {
|
||||||
// Check if the cache time is less than 1 second
|
// Check if the cache time is less than 1 second
|
||||||
if time.Since(info.CacheTime) > time.Second {
|
if time.Since(info.CacheTime) > time.Second {
|
||||||
go refreshFileInfo(opts)
|
go RefreshFileInfo(opts)
|
||||||
}
|
}
|
||||||
// refresh cache after
|
// refresh cache after
|
||||||
return &info, nil
|
return &info, nil
|
||||||
|
@ -133,7 +133,7 @@ func FileInfoFaster(opts FileOptions) (*FileInfo, error) {
|
||||||
file, err := NewFileInfo(opts)
|
file, err := NewFileInfo(opts)
|
||||||
return file, err
|
return file, err
|
||||||
}
|
}
|
||||||
updated := refreshFileInfo(opts)
|
updated := RefreshFileInfo(opts)
|
||||||
if !updated {
|
if !updated {
|
||||||
file, err := NewFileInfo(opts)
|
file, err := NewFileInfo(opts)
|
||||||
return file, err
|
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) {
|
if !opts.Checker.Check(opts.Path) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -286,11 +286,15 @@ func (i *FileInfo) addContent(path string) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
c := string(string(content))
|
stringContent := string(content)
|
||||||
if !utf8.ValidString(c) {
|
if !utf8.ValidString(stringContent) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
i.Content = string(c)
|
if stringContent == "" {
|
||||||
|
i.Content = "empty-file-x6OlSil"
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
i.Content = stringContent
|
||||||
}
|
}
|
||||||
return nil
|
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)
|
w.Header().Set("ETag", etag)
|
||||||
return nil
|
return nil
|
||||||
}, "save", r.URL.Path, "", d.user)
|
}, "save", r.URL.Path, "", d.user)
|
||||||
|
|
||||||
return errToStatus(err), err
|
return errToStatus(err), err
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -272,6 +271,10 @@ func writeFile(fs afero.Fs, dst string, in io.Reader) (os.FileInfo, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//files.RefreshFileInfo(files.FileOptions{
|
||||||
|
// Fs: info,
|
||||||
|
//})
|
||||||
|
|
||||||
return info, nil
|
return info, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -64,9 +64,7 @@ func handleWithStaticData(w http.ResponseWriter, _ *http.Request, d *data, fSys
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return http.StatusInternalServerError, err
|
return http.StatusInternalServerError, err
|
||||||
}
|
}
|
||||||
|
|
||||||
auther := raw.(*auth.JSONAuth)
|
auther := raw.(*auth.JSONAuth)
|
||||||
|
|
||||||
if auther.ReCaptcha != nil {
|
if auther.ReCaptcha != nil {
|
||||||
data["ReCaptcha"] = auther.ReCaptcha.Key != "" && auther.ReCaptcha.Secret != ""
|
data["ReCaptcha"] = auther.ReCaptcha.Key != "" && auther.ReCaptcha.Secret != ""
|
||||||
data["ReCaptchaHost"] = auther.ReCaptcha.Host
|
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")
|
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)
|
}, "", store, server)
|
||||||
|
|
||||||
static = handle(func(w http.ResponseWriter, r *http.Request, d *data) (int, error) {
|
static = handle(func(w http.ResponseWriter, r *http.Request, d *data) (int, error) {
|
||||||
|
|
|
@ -58,6 +58,7 @@ func setDefaults() Settings {
|
||||||
Database: "database.db",
|
Database: "database.db",
|
||||||
Log: "stdout",
|
Log: "stdout",
|
||||||
Root: "/srv",
|
Root: "/srv",
|
||||||
|
Indexing: true,
|
||||||
},
|
},
|
||||||
Auth: Auth{
|
Auth: Auth{
|
||||||
TokenExpirationTime: "2h",
|
TokenExpirationTime: "2h",
|
||||||
|
|
|
@ -54,6 +54,7 @@ type Server struct {
|
||||||
Root string `json:"root"`
|
Root string `json:"root"`
|
||||||
UserHomeBasePath string `json:"userHomeBasePath"`
|
UserHomeBasePath string `json:"userHomeBasePath"`
|
||||||
CreateUserDir bool `json:"createUserDir"`
|
CreateUserDir bool `json:"createUserDir"`
|
||||||
|
Indexing bool `json:"indexing"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Frontend struct {
|
type Frontend struct {
|
||||||
|
|
|
@ -2,7 +2,7 @@ package version
|
||||||
|
|
||||||
var (
|
var (
|
||||||
// Version is the current File Browser version.
|
// Version is the current File Browser version.
|
||||||
Version = "(0.2.5)"
|
Version = "(0.2.6)"
|
||||||
// CommitSHA is the commmit sha.
|
// CommitSHA is the commmit sha.
|
||||||
CommitSHA = "(unknown)"
|
CommitSHA = "(unknown)"
|
||||||
)
|
)
|
||||||
|
|
|
@ -11,6 +11,7 @@ server:
|
||||||
CreateUserDir: false
|
CreateUserDir: false
|
||||||
UserHomeBasePath: ""
|
UserHomeBasePath: ""
|
||||||
indexingInterval: 5
|
indexingInterval: 5
|
||||||
|
indexing: true
|
||||||
numImageProcessors: 4
|
numImageProcessors: 4
|
||||||
socket: ""
|
socket: ""
|
||||||
tlsKey: ""
|
tlsKey: ""
|
||||||
|
@ -113,6 +114,8 @@ userDefaults:
|
||||||
|
|
||||||
- `indexingInterval`: This is the time in minutes the system waits before checking for filesystem changes. Default: `5`
|
- `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`
|
- `numImageProcessors`: This is the number of image processors available. Default: `4`
|
||||||
|
|
||||||
- `socket`: This is the socket configuration.
|
- `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",
|
"name": "filebrowser-frontend",
|
||||||
"version": "2.0.0",
|
"version": "3.0.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
|
"type": "module",
|
||||||
|
"engines": {
|
||||||
|
"npm": ">=7.0.0",
|
||||||
|
"node": ">=18.0.0"
|
||||||
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"serve": "vue-cli-service serve",
|
"dev": "vite dev",
|
||||||
"build": "vue-cli-service build",
|
"build": "vite build",
|
||||||
"fix": "npx vue-cli-service lint",
|
"watch": "vite build --watch",
|
||||||
"watch": "vue-cli-service build --watch",
|
"typecheck": "vue-tsc -p ./tsconfig.json --noEmit",
|
||||||
"lint": "eslint src/",
|
"lint": "npm run typecheck && eslint src/",
|
||||||
"lint:fix": "eslint --fix src/",
|
"lint:fix": "eslint --fix src/",
|
||||||
"format": "prettier --write ."
|
"format": "prettier --write .",
|
||||||
|
"test": "playwright test"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ace-builds": "^1.24.2",
|
"ace-builds": "^1.24.2",
|
||||||
|
@ -17,25 +23,22 @@
|
||||||
"css-vars-ponyfill": "^2.4.3",
|
"css-vars-ponyfill": "^2.4.3",
|
||||||
"file-loader": "^6.2.0",
|
"file-loader": "^6.2.0",
|
||||||
"material-icons": "^1.10.5",
|
"material-icons": "^1.10.5",
|
||||||
"moment": "^2.29.4",
|
|
||||||
"normalize.css": "^8.0.1",
|
"normalize.css": "^8.0.1",
|
||||||
"noty": "^3.2.0-beta",
|
"qrcode.vue": "^3.4.1",
|
||||||
"qrcode.vue": "^1.7.0",
|
"vue": "^3.4.21",
|
||||||
"utif": "^3.1.0",
|
"vue-i18n": "^9.10.2",
|
||||||
"vue": "^2.6.10",
|
"vue-lazyload": "^3.0.0",
|
||||||
"vue-async-computed": "^3.9.0",
|
"vue-router": "^4.3.0"
|
||||||
"vue-i18n": "^8.15.3",
|
|
||||||
"vue-lazyload": "^1.3.3",
|
|
||||||
"vue-router": "^3.1.3",
|
|
||||||
"vue-simple-progress": "^1.1.1",
|
|
||||||
"vuex": "^3.1.2",
|
|
||||||
"vuex-router-sync": "^5.0.0"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@vue/cli-service": "^5.0.8",
|
"@intlify/unplugin-vue-i18n": "^4.0.0",
|
||||||
"compression-webpack-plugin": "^10.0.0",
|
"@vitejs/plugin-vue": "^5.0.4",
|
||||||
"eslint": "^9.4.0",
|
"@vue/eslint-config-typescript": "^13.0.0",
|
||||||
"eslint-plugin-vue": "^9.26.0",
|
"eslint": "^8.57.0",
|
||||||
"vue-template-compiler": "^2.6.17"
|
"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">
|
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
|
||||||
|
|
||||||
[{[ if .ReCaptcha -]}]
|
[{[ if .ReCaptcha -]}]
|
||||||
<script src="[{[ .ReCaptchaHost ]}]/recaptcha/api.js?render=explicit"></script>
|
<script src="[{[ .ReCaptchaHost ]}]/recaptcha/api.js?render=explicit" data-vite-ignore></script>
|
||||||
[{[ end ]}]
|
[{[ end ]}]
|
||||||
|
|
||||||
<title>[{[ if .Name -]}][{[ .Name ]}][{[ else ]}]File Browser[{[ end ]}]</title>
|
<title>[{[ if .Name -]}][{[ .Name ]}][{[ else ]}]File Browser[{[ end ]}]</title>
|
||||||
|
@ -121,7 +121,7 @@
|
||||||
<div id="app"></div>
|
<div id="app"></div>
|
||||||
|
|
||||||
[{[ if .darkMode -]}]
|
[{[ if .darkMode -]}]
|
||||||
<div id="loading dark-mode">
|
<div id="loading" class="dark-mode">
|
||||||
<div class="spinner">
|
<div class="spinner">
|
||||||
<div class="bounce1"></div>
|
<div class="bounce1"></div>
|
||||||
<div class="bounce2"></div>
|
<div class="bounce2"></div>
|
||||||
|
@ -136,6 +136,8 @@
|
||||||
<div class="bounce3"></div>
|
<div class="bounce3"></div>
|
||||||
</div>
|
</div>
|
||||||
</div> [{[ end ]}]
|
</div> [{[ end ]}]
|
||||||
|
<script type="module" src="/src/main.ts"></script>
|
||||||
|
|
||||||
[{[ if .CSS -]}]
|
[{[ if .CSS -]}]
|
||||||
<link rel="stylesheet" href="[{[ .StaticURL ]}]/custom.css" />
|
<link rel="stylesheet" href="[{[ .StaticURL ]}]/custom.css" />
|
||||||
[{[ end ]}]
|
[{[ end ]}]
|
||||||
|
|
|
@ -3,13 +3,22 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import { onMounted } from 'vue';
|
||||||
// eslint-disable-next-line no-undef
|
import { mutations } from "@/store"; // Import your store's mutations
|
||||||
__webpack_public_path__ = window.FileBrowser.StaticURL + "/";
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "app",
|
name: "app",
|
||||||
computed: {},
|
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>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
import { removePrefix } from "./utils";
|
import { removePrefix } from "./utils";
|
||||||
import { baseURL } from "@/utils/constants";
|
import { baseURL } from "@/utils/constants";
|
||||||
import store from "@/store";
|
import { state } from "@/store";
|
||||||
|
|
||||||
const ssl = window.location.protocol === "https:";
|
const ssl = window.location.protocol === "https:";
|
||||||
const protocol = ssl ? "wss:" : "ws:";
|
const protocol = ssl ? "wss:" : "ws:";
|
||||||
|
|
||||||
export default function command(url, command, onmessage, onclose) {
|
export default function command(url, command, onmessage, onclose) {
|
||||||
url = removePrefix(url);
|
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);
|
let conn = new window.WebSocket(url);
|
||||||
conn.onopen = () => conn.send(command);
|
conn.onopen = () => conn.send(command);
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { createURL, fetchURL, removePrefix } from "./utils";
|
import { createURL, fetchURL, removePrefix } from "./utils";
|
||||||
import { baseURL } from "@/utils/constants";
|
import { baseURL } from "@/utils/constants";
|
||||||
import store from "@/store";
|
import { state } from "@/store";
|
||||||
|
|
||||||
export async function fetch(url,content=false) {
|
export async function fetch(url,content=false) {
|
||||||
url = removePrefix(url);
|
url = removePrefix(url);
|
||||||
|
@ -70,8 +70,8 @@ export function download(format, ...files) {
|
||||||
url += `algo=${format}&`;
|
url += `algo=${format}&`;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (store.state.jwt) {
|
if (state.jwt) {
|
||||||
url += `auth=${store.state.jwt}&`;
|
url += `auth=${state.jwt}&`;
|
||||||
}
|
}
|
||||||
|
|
||||||
window.open(url);
|
window.open(url);
|
||||||
|
@ -95,7 +95,7 @@ export async function post(url, content = "", overwrite = false, onupload) {
|
||||||
`${baseURL}/api/resources${url}?override=${overwrite}`,
|
`${baseURL}/api/resources${url}?override=${overwrite}`,
|
||||||
true
|
true
|
||||||
);
|
);
|
||||||
request.setRequestHeader("X-Auth", store.state.jwt);
|
request.setRequestHeader("X-Auth", state.jwt);
|
||||||
|
|
||||||
if (typeof onupload === "function") {
|
if (typeof onupload === "function") {
|
||||||
request.upload.onprogress = onupload;
|
request.upload.onprogress = onupload;
|
||||||
|
|
|
@ -18,6 +18,7 @@ export async function remove(hash) {
|
||||||
export async function create(url, password = "", expires = "", unit = "hours") {
|
export async function create(url, password = "", expires = "", unit = "hours") {
|
||||||
url = removePrefix(url);
|
url = removePrefix(url);
|
||||||
url = `/api/share${url}`;
|
url = `/api/share${url}`;
|
||||||
|
expires = String(expires);
|
||||||
if (expires !== "") {
|
if (expires !== "") {
|
||||||
url += `?expires=${expires}&unit=${unit}`;
|
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`, {});
|
return await fetchJSON(`/api/users`, {});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
import store from "@/store";
|
import { state } from "@/store";
|
||||||
import { renew, logout } from "@/utils/auth";
|
import { renew, logout } from "@/utils/auth";
|
||||||
import { baseURL } from "@/utils/constants";
|
import { baseURL } from "@/utils/constants";
|
||||||
import { encodePath } from "@/utils/url";
|
import { encodePath } from "@/utils/url";
|
||||||
|
import { showError } from "@/notify";
|
||||||
|
|
||||||
export async function fetchURL(url, opts, auth = true) {
|
export async function fetchURL(url, opts, auth = true) {
|
||||||
opts = opts || {};
|
opts = opts || {};
|
||||||
|
@ -13,21 +14,22 @@ export async function fetchURL(url, opts, auth = true) {
|
||||||
try {
|
try {
|
||||||
res = await fetch(`${baseURL}${url}`, {
|
res = await fetch(`${baseURL}${url}`, {
|
||||||
headers: {
|
headers: {
|
||||||
"X-Auth": store.state.jwt,
|
"X-Auth": state.jwt,
|
||||||
"sessionId": store.state.sessionId,
|
"sessionId": state.sessionId,
|
||||||
"userScope": store.state.user.scope,
|
"userScope": state.user.scope,
|
||||||
...headers,
|
...headers,
|
||||||
},
|
},
|
||||||
...rest,
|
...rest,
|
||||||
});
|
});
|
||||||
} catch {
|
} catch (e) {
|
||||||
|
console.error(e)
|
||||||
const error = new Error("000 No connection");
|
const error = new Error("000 No connection");
|
||||||
error.status = res.status;
|
error.status = res.status;
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (auth && res.headers.get("X-Renew-Token") === "true") {
|
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) {
|
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) {
|
export async function fetchJSON(url, opts) {
|
||||||
const res = await fetchURL(url, opts);
|
const res = await fetchURL(url, opts);
|
||||||
|
|
||||||
if (res.status === 200) {
|
if (res.status === 200) {
|
||||||
return res.json();
|
return res.json();
|
||||||
} else {
|
} else {
|
||||||
|
showError("unable to fetch : " + url + "status" + res.status);
|
||||||
throw new Error(res.status);
|
throw new Error(res.status);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function removePrefix(url) {
|
export function removePrefix(url) {
|
||||||
url = url.split("/").splice(2).join("/");
|
url = url.split("/").splice(2).join("/");
|
||||||
|
|
||||||
if (url === "") url = "/";
|
if (url === "") url = "/";
|
||||||
if (url[0] !== "/") url = "/" + url;
|
if (url[0] !== "/") url = "/" + url;
|
||||||
return url;
|
return url;
|
||||||
|
@ -70,7 +71,7 @@ export function createURL(endpoint, params = {}, auth = true) {
|
||||||
const url = new URL(prefix + encodePath(endpoint), origin);
|
const url = new URL(prefix + encodePath(endpoint), origin);
|
||||||
|
|
||||||
const searchParams = {
|
const searchParams = {
|
||||||
...(auth && { auth: store.state.jwt }),
|
...(auth && { auth: state.jwt }),
|
||||||
...params,
|
...params,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -18,8 +18,8 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { mapState } from "vuex";
|
import { state, mutations } from "@/store"; // Import mutations as well
|
||||||
import Action from "@/components/header/Action";
|
import Action from "@/components/header/Action.vue";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "breadcrumbs",
|
name: "breadcrumbs",
|
||||||
|
@ -28,10 +28,8 @@ export default {
|
||||||
},
|
},
|
||||||
props: ["base", "noLink"],
|
props: ["base", "noLink"],
|
||||||
computed: {
|
computed: {
|
||||||
...mapState(["req", "user"]),
|
|
||||||
items() {
|
items() {
|
||||||
|
const relativePath = state.route.path.replace(this.base, "");
|
||||||
const relativePath = this.$route.path.replace(this.base, "");
|
|
||||||
let parts = relativePath.split("/");
|
let parts = relativePath.split("/");
|
||||||
|
|
||||||
if (parts[0] === "") {
|
if (parts[0] === "") {
|
||||||
|
@ -76,13 +74,18 @@ export default {
|
||||||
return "router-link";
|
return "router-link";
|
||||||
},
|
},
|
||||||
showShare() {
|
showShare() {
|
||||||
if (this.$route.path.startsWith("/share")) {
|
// Ensure user properties are accessed safely
|
||||||
return;
|
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>
|
</script>
|
||||||
|
|
||||||
<style></style>
|
|
||||||
|
|
|
@ -36,15 +36,12 @@ export default {
|
||||||
methods: {
|
methods: {
|
||||||
setActiveButton(index, label) {
|
setActiveButton(index, label) {
|
||||||
if (label == "Only Folders" && this.activeButton != index) {
|
if (label == "Only Folders" && this.activeButton != index) {
|
||||||
console.log("Only Folders && this.activeButton != index");
|
|
||||||
this.$emit("disableAll");
|
this.$emit("disableAll");
|
||||||
}
|
}
|
||||||
if (label == "Only Folders" && this.activeButton == index) {
|
if (label == "Only Folders" && this.activeButton == index) {
|
||||||
console.log("Only Folders && this.activeButton == index");
|
|
||||||
this.$emit("enableAll");
|
this.$emit("enableAll");
|
||||||
}
|
}
|
||||||
if (label == "Only Files" && this.activeButton != index) {
|
if (label == "Only Files" && this.activeButton != index) {
|
||||||
console.log("Only Files && this.activeButton != index");
|
|
||||||
this.$emit("enableAll");
|
this.$emit("enableAll");
|
||||||
}
|
}
|
||||||
// If the clicked button is already active, de-select it
|
// 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>
|
<template>
|
||||||
<div
|
<div id="search" @click="open" :class="{ active, ongoing, 'dark-mode': isDarkMode }">
|
||||||
id="search"
|
<!-- Search input section -->
|
||||||
@click="open"
|
|
||||||
v-bind:class="{ active, ongoing, 'dark-mode': isDarkMode }"
|
|
||||||
>
|
|
||||||
<div id="input">
|
<div id="input">
|
||||||
|
<!-- Close button visible when search is active -->
|
||||||
<button
|
<button
|
||||||
v-if="active"
|
v-if="active"
|
||||||
class="action"
|
class="action"
|
||||||
|
@ -14,7 +12,9 @@
|
||||||
>
|
>
|
||||||
<i class="material-icons">close</i>
|
<i class="material-icons">close</i>
|
||||||
</button>
|
</button>
|
||||||
|
<!-- Search icon when search is not active -->
|
||||||
<i v-else class="material-icons">search</i>
|
<i v-else class="material-icons">search</i>
|
||||||
|
<!-- Input field for search -->
|
||||||
<input
|
<input
|
||||||
class="main-input"
|
class="main-input"
|
||||||
type="text"
|
type="text"
|
||||||
|
@ -27,9 +27,12 @@
|
||||||
:placeholder="$t('search.search')"
|
:placeholder="$t('search.search')"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Search results for mobile -->
|
||||||
<div v-if="isMobile && active" id="result" :class="{ hidden: !active }" ref="result">
|
<div v-if="isMobile && active" id="result" :class="{ hidden: !active }" ref="result">
|
||||||
<div id="result-list">
|
<div id="result-list">
|
||||||
<div class="button" style="width: 100%">Search Context: {{ getContext }}</div>
|
<div class="button" style="width: 100%">Search Context: {{ getContext }}</div>
|
||||||
|
<!-- List of search results -->
|
||||||
<ul v-show="results.length > 0">
|
<ul v-show="results.length > 0">
|
||||||
<li
|
<li
|
||||||
v-for="(s, k) in results"
|
v-for="(s, k) in results"
|
||||||
|
@ -50,15 +53,18 @@
|
||||||
</router-link>
|
</router-link>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
<!-- Loading icon when search is ongoing -->
|
||||||
<p v-show="isEmpty && isRunning" id="renew">
|
<p v-show="isEmpty && isRunning" id="renew">
|
||||||
<i class="material-icons spin">autorenew</i>
|
<i class="material-icons spin">autorenew</i>
|
||||||
</p>
|
</p>
|
||||||
|
<!-- Message when no results are found -->
|
||||||
<div v-show="isEmpty && !isRunning">
|
<div v-show="isEmpty && !isRunning">
|
||||||
<div class="searchPrompt" v-show="isEmpty && !isRunning">
|
<div class="searchPrompt" v-show="isEmpty && !isRunning">
|
||||||
<p>{{ noneMessage }}</p>
|
<p>{{ noneMessage }}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<template v-if="isEmpty">
|
<div v-if="isEmpty">
|
||||||
|
<!-- Reset filters button -->
|
||||||
<button
|
<button
|
||||||
class="mobile-boxes"
|
class="mobile-boxes"
|
||||||
v-if="value.length === 0 && !showBoxes"
|
v-if="value.length === 0 && !showBoxes"
|
||||||
|
@ -66,7 +72,8 @@
|
||||||
>
|
>
|
||||||
Reset filters
|
Reset filters
|
||||||
</button>
|
</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">
|
<div class="boxes">
|
||||||
<h3>{{ $t("search.types") }}</h3>
|
<h3>{{ $t("search.types") }}</h3>
|
||||||
<div>
|
<div>
|
||||||
|
@ -84,21 +91,26 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
|
||||||
</template>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Search results for desktop -->
|
||||||
<div v-show="!isMobile && active" id="result-desktop" ref="result">
|
<div v-show="!isMobile && active" id="result-desktop" ref="result">
|
||||||
<div class="searchContext">Search Context: {{ getContext }}</div>
|
<div class="searchContext">Search Context: {{ getContext }}</div>
|
||||||
<div id="result-list">
|
<div id="result-list">
|
||||||
<template>
|
<div>
|
||||||
|
<!-- Loading icon when search is ongoing -->
|
||||||
<p v-show="isEmpty && isRunning" id="renew">
|
<p v-show="isEmpty && isRunning" id="renew">
|
||||||
<i class="material-icons spin">autorenew</i>
|
<i class="material-icons spin">autorenew</i>
|
||||||
</p>
|
</p>
|
||||||
|
<!-- Message when no results are found -->
|
||||||
<div class="searchPrompt" v-show="isEmpty && !isRunning">
|
<div class="searchPrompt" v-show="isEmpty && !isRunning">
|
||||||
<p>{{ noneMessage }}</p>
|
<p>{{ noneMessage }}</p>
|
||||||
<div class="helpButton" @click="toggleHelp()">Help</div>
|
<div class="helpButton" @click="toggleHelp()">Help</div>
|
||||||
</div>
|
</div>
|
||||||
|
<!-- Help text section -->
|
||||||
<div class="helpText" v-if="showHelp">
|
<div class="helpText" v-if="showHelp">
|
||||||
<p>
|
<p>
|
||||||
Search occurs on each character you type (3 character minimum for search
|
Search occurs on each character you type (3 character minimum for search
|
||||||
|
@ -122,7 +134,8 @@
|
||||||
search times.
|
search times.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<template>
|
<div>
|
||||||
|
<!-- Button groups for filtering search results -->
|
||||||
<ButtonGroup
|
<ButtonGroup
|
||||||
:buttons="folderSelect"
|
:buttons="folderSelect"
|
||||||
@button-clicked="addToTypes"
|
@button-clicked="addToTypes"
|
||||||
|
@ -136,6 +149,7 @@
|
||||||
@remove-button-clicked="removeFromTypes"
|
@remove-button-clicked="removeFromTypes"
|
||||||
:isDisabled="isTypeSelectDisabled"
|
:isDisabled="isTypeSelectDisabled"
|
||||||
/>
|
/>
|
||||||
|
<!-- Inputs for filtering by file size -->
|
||||||
<div class="sizeConstraints">
|
<div class="sizeConstraints">
|
||||||
<div class="sizeInputWrapper">
|
<div class="sizeInputWrapper">
|
||||||
<p>Smaller Than:</p>
|
<p>Smaller Than:</p>
|
||||||
|
@ -159,8 +173,9 @@
|
||||||
<p>MB</p>
|
<p>MB</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</div>
|
||||||
</template>
|
</div>
|
||||||
|
<!-- List of search results -->
|
||||||
<ul v-show="results.length > 0">
|
<ul v-show="results.length > 0">
|
||||||
<li
|
<li
|
||||||
v-for="(s, k) in results"
|
v-for="(s, k) in results"
|
||||||
|
@ -185,6 +200,246 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</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>
|
<style>
|
||||||
.main-input {
|
.main-input {
|
||||||
|
@ -198,6 +453,7 @@
|
||||||
color: white;
|
color: white;
|
||||||
border-left: 1px solid gray;
|
border-left: 1px solid gray;
|
||||||
border-right: 1px solid gray;
|
border-right: 1px solid gray;
|
||||||
|
word-wrap: break-word;
|
||||||
}
|
}
|
||||||
|
|
||||||
#result-desktop > #result-list {
|
#result-desktop > #result-list {
|
||||||
|
@ -262,7 +518,7 @@
|
||||||
|
|
||||||
/* Search */
|
/* Search */
|
||||||
#search {
|
#search {
|
||||||
background-color: unset;
|
background-color: unset !important;
|
||||||
z-index: 3;
|
z-index: 3;
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 0.5em;
|
top: 0.5em;
|
||||||
|
@ -509,244 +765,3 @@ body.rtl #search .boxes h3 {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
</style>
|
</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>
|
<template>
|
||||||
<nav :class="{ active, 'dark-mode': isDarkMode }">
|
<nav :class="{ active, 'dark-mode': isDarkMode }">
|
||||||
<template v-if="isLogged">
|
<!-- Section for logged-in users -->
|
||||||
|
<template v-if="isLoggedIn">
|
||||||
|
<!-- My Files button -->
|
||||||
<button
|
<button
|
||||||
class="action"
|
class="action"
|
||||||
@click="toRoot"
|
@click="toRoot"
|
||||||
|
@ -10,9 +12,12 @@
|
||||||
<i class="material-icons">folder</i>
|
<i class="material-icons">folder</i>
|
||||||
<span>{{ $t("sidebar.myFiles") }}</span>
|
<span>{{ $t("sidebar.myFiles") }}</span>
|
||||||
</button>
|
</button>
|
||||||
<div v-if="user.perm.create">
|
|
||||||
|
<!-- Buttons visible if user has create permission -->
|
||||||
|
<div v-if="user.perm?.create">
|
||||||
|
<!-- New Folder button -->
|
||||||
<button
|
<button
|
||||||
@click="$store.commit('showHover', 'newDir')"
|
@click="showHover('newDir')"
|
||||||
class="action"
|
class="action"
|
||||||
:aria-label="$t('sidebar.newFolder')"
|
:aria-label="$t('sidebar.newFolder')"
|
||||||
:title="$t('sidebar.newFolder')"
|
:title="$t('sidebar.newFolder')"
|
||||||
|
@ -20,8 +25,9 @@
|
||||||
<i class="material-icons">create_new_folder</i>
|
<i class="material-icons">create_new_folder</i>
|
||||||
<span>{{ $t("sidebar.newFolder") }}</span>
|
<span>{{ $t("sidebar.newFolder") }}</span>
|
||||||
</button>
|
</button>
|
||||||
|
<!-- New File button -->
|
||||||
<button
|
<button
|
||||||
@click="$store.commit('showHover', 'newFile')"
|
@click="showHover('newFile')"
|
||||||
class="action"
|
class="action"
|
||||||
:aria-label="$t('sidebar.newFile')"
|
:aria-label="$t('sidebar.newFile')"
|
||||||
:title="$t('sidebar.newFile')"
|
:title="$t('sidebar.newFile')"
|
||||||
|
@ -29,12 +35,16 @@
|
||||||
<i class="material-icons">note_add</i>
|
<i class="material-icons">note_add</i>
|
||||||
<span>{{ $t("sidebar.newFile") }}</span>
|
<span>{{ $t("sidebar.newFile") }}</span>
|
||||||
</button>
|
</button>
|
||||||
|
<!-- Upload button -->
|
||||||
<button id="upload-button" @click="upload($event)" class="action">
|
<button id="upload-button" @click="upload($event)" class="action">
|
||||||
<i class="material-icons">file_upload</i>
|
<i class="material-icons">file_upload</i>
|
||||||
<span>Upload file</span>
|
<span>Upload file</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Settings and Logout buttons -->
|
||||||
<div>
|
<div>
|
||||||
|
<!-- Settings button -->
|
||||||
<button
|
<button
|
||||||
class="action"
|
class="action"
|
||||||
@click="toSettings"
|
@click="toSettings"
|
||||||
|
@ -44,7 +54,7 @@
|
||||||
<i class="material-icons">settings_applications</i>
|
<i class="material-icons">settings_applications</i>
|
||||||
<span>{{ $t("sidebar.settings") }}</span>
|
<span>{{ $t("sidebar.settings") }}</span>
|
||||||
</button>
|
</button>
|
||||||
|
<!-- Logout button -->
|
||||||
<button
|
<button
|
||||||
v-if="canLogout"
|
v-if="canLogout"
|
||||||
@click="logout"
|
@click="logout"
|
||||||
|
@ -58,7 +68,10 @@
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<!-- Section for non-logged-in users -->
|
||||||
<template v-else>
|
<template v-else>
|
||||||
|
<!-- Login button -->
|
||||||
<router-link
|
<router-link
|
||||||
class="action"
|
class="action"
|
||||||
to="/login"
|
to="/login"
|
||||||
|
@ -68,6 +81,7 @@
|
||||||
<i class="material-icons">exit_to_app</i>
|
<i class="material-icons">exit_to_app</i>
|
||||||
<span>{{ $t("sidebar.login") }}</span>
|
<span>{{ $t("sidebar.login") }}</span>
|
||||||
</router-link>
|
</router-link>
|
||||||
|
<!-- Signup button, if signup is enabled -->
|
||||||
<router-link
|
<router-link
|
||||||
v-if="signup"
|
v-if="signup"
|
||||||
class="action"
|
class="action"
|
||||||
|
@ -79,10 +93,9 @@
|
||||||
<span>{{ $t("sidebar.signup") }}</span>
|
<span>{{ $t("sidebar.signup") }}</span>
|
||||||
</router-link>
|
</router-link>
|
||||||
</template>
|
</template>
|
||||||
<div
|
|
||||||
class="credits"
|
<!-- Credits and usage information section -->
|
||||||
v-if="$router.currentRoute.path.includes('/files/') && !disableUsedPercentage"
|
<div class="credits" v-if="isFiles && !disableUsedPercentage && usage">
|
||||||
>
|
|
||||||
<progress-bar :val="usage.usedPercentage" size="medium"></progress-bar>
|
<progress-bar :val="usage.usedPercentage" size="medium"></progress-bar>
|
||||||
<span style="text-align: center">{{ usage.usedPercentage }}%</span>
|
<span style="text-align: center">{{ usage.usedPercentage }}%</span>
|
||||||
<span>{{ usage.used }} of {{ usage.total }} used</span>
|
<span>{{ usage.used }} of {{ usage.total }} used</span>
|
||||||
|
@ -104,8 +117,8 @@
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { mapState, mapGetters } from "vuex";
|
|
||||||
import * as upload from "@/utils/upload";
|
import * as upload from "@/utils/upload";
|
||||||
import * as auth from "@/utils/auth";
|
import * as auth from "@/utils/auth";
|
||||||
import {
|
import {
|
||||||
|
@ -117,39 +130,51 @@ import {
|
||||||
loginPage,
|
loginPage,
|
||||||
} from "@/utils/constants";
|
} from "@/utils/constants";
|
||||||
import { files as api } from "@/api";
|
import { files as api } from "@/api";
|
||||||
import ProgressBar from "vue-simple-progress";
|
import ProgressBar from "@/components/ProgressBar.vue";
|
||||||
import { getHumanReadableFilesize } from "@/utils/filesizes";
|
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 {
|
export default {
|
||||||
name: "sidebar",
|
name: "sidebar",
|
||||||
components: {
|
components: {
|
||||||
ProgressBar,
|
ProgressBar,
|
||||||
},
|
},
|
||||||
computed: {
|
mounted() {
|
||||||
...mapState(["user"]),
|
this.updateUsage();
|
||||||
isDarkMode() {
|
},
|
||||||
return this.user && Object.prototype.hasOwnProperty.call(this.user, "darkMode")
|
computed: {
|
||||||
? this.user.darkMode
|
isFiles() {
|
||||||
: darkMode;
|
return getters.isFiles();
|
||||||
|
},
|
||||||
|
user() {
|
||||||
|
return state.user;
|
||||||
|
},
|
||||||
|
isDarkMode() {
|
||||||
|
return getters.isDarkMode();
|
||||||
|
},
|
||||||
|
isLoggedIn() {
|
||||||
|
return getters.isLoggedIn();
|
||||||
|
},
|
||||||
|
currentPrompt() {
|
||||||
|
return getters.currentPrompt();
|
||||||
},
|
},
|
||||||
...mapGetters(["isLogged", "currentPrompt"]),
|
|
||||||
active() {
|
active() {
|
||||||
return this.currentPrompt?.prompt === "sidebar";
|
return getters.currentPromptName() === "sidebar";
|
||||||
},
|
},
|
||||||
signup: () => signup,
|
signup: () => signup,
|
||||||
version: () => version,
|
version: () => version,
|
||||||
disableExternal: () => disableExternal,
|
disableExternal: () => disableExternal,
|
||||||
disableUsedPercentage: () => disableUsedPercentage,
|
disableUsedPercentage: () => disableUsedPercentage,
|
||||||
canLogout: () => !noAuth && loginPage,
|
canLogout: () => !noAuth && loginPage,
|
||||||
|
usage: () => state.usage,
|
||||||
},
|
},
|
||||||
asyncComputed: {
|
methods: {
|
||||||
usage: {
|
async updateUsage() {
|
||||||
async get() {
|
console.log("updating usage");
|
||||||
let path = this.$route.path.endsWith("/")
|
|
||||||
? this.$route.path
|
let path = getters.getRoutePath();
|
||||||
: this.$route.path + "/";
|
let usageStats = { used: "0 B", total: "0 B", usedPercentage: 0 };
|
||||||
let usageStats = { used: 0, total: 0, usedPercentage: 0 };
|
|
||||||
if (this.disableUsedPercentage) {
|
if (this.disableUsedPercentage) {
|
||||||
return usageStats;
|
return usageStats;
|
||||||
}
|
}
|
||||||
|
@ -161,40 +186,35 @@ export default {
|
||||||
usedPercentage: Math.round((usage.used / usage.total) * 100),
|
usedPercentage: Math.round((usage.used / usage.total) * 100),
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} 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 },
|
showHover(value) {
|
||||||
shouldUpdate() {
|
return mutations.showHover(value);
|
||||||
return this.$router.currentRoute.path.includes("/files/");
|
|
||||||
},
|
},
|
||||||
},
|
// Navigate to the root files directory
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
toRoot() {
|
toRoot() {
|
||||||
this.$router.push({ path: "/files/" }, () => {});
|
this.$router.push({ path: "/files/" }, () => {});
|
||||||
this.$store.commit("closeHovers");
|
mutations.closeHovers();
|
||||||
},
|
},
|
||||||
|
// Navigate to the settings page
|
||||||
toSettings() {
|
toSettings() {
|
||||||
this.$router.push({ path: "/settings" }, () => {});
|
this.$router.push({ path: "/settings" }, () => {});
|
||||||
this.$store.commit("closeHovers");
|
mutations.closeHovers();
|
||||||
},
|
},
|
||||||
|
// Show the help overlay
|
||||||
help() {
|
help() {
|
||||||
this.$store.commit("showHover", "help");
|
mutations.showHover("help");
|
||||||
},
|
},
|
||||||
upload: function () {
|
// Handle file upload
|
||||||
if (
|
upload(event) {
|
||||||
typeof window.DataTransferItem !== "undefined" &&
|
return this.$upload(event);
|
||||||
typeof DataTransferItem.prototype.webkitGetAsEntry !== "undefined"
|
|
||||||
) {
|
|
||||||
this.$store.commit("showHover", "upload");
|
|
||||||
} else {
|
|
||||||
document.getElementById("upload-input").click();
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
// Handle files selected for upload
|
||||||
uploadInput(event) {
|
uploadInput(event) {
|
||||||
this.$store.commit("closeHovers");
|
mutations.closeHovers();
|
||||||
|
|
||||||
let files = event.currentTarget.files;
|
let files = event.currentTarget.files;
|
||||||
let folder_upload =
|
let folder_upload =
|
||||||
|
@ -207,17 +227,15 @@ export default {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let path = this.$route.path.endsWith("/")
|
let path = getters.getRoutePath();
|
||||||
? this.$route.path
|
let conflict = upload.checkConflict(files, state.req.items);
|
||||||
: this.$route.path + "/";
|
|
||||||
let conflict = upload.checkConflict(files, this.req.items);
|
|
||||||
|
|
||||||
if (conflict) {
|
if (conflict) {
|
||||||
this.$store.commit("showHover", {
|
mutations.showHover({
|
||||||
prompt: "replace",
|
name: "replace",
|
||||||
confirm: (event) => {
|
confirm: (event) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
this.$store.commit("closeHovers");
|
mutations.closeHovers();
|
||||||
upload.handleFiles(files, path, true);
|
upload.handleFiles(files, path, true);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -227,6 +245,7 @@ export default {
|
||||||
|
|
||||||
upload.handleFiles(files, path);
|
upload.handleFiles(files, path);
|
||||||
},
|
},
|
||||||
|
// Logout the user
|
||||||
logout: auth.logout,
|
logout: auth.logout,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -11,17 +11,19 @@
|
||||||
@wheel="wheelMove"
|
@wheel="wheelMove"
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
src=""
|
v-if="!isTiff"
|
||||||
|
:src="src"
|
||||||
class="image-ex-img image-ex-img-center"
|
class="image-ex-img image-ex-img-center"
|
||||||
ref="imgex"
|
ref="imgex"
|
||||||
@load="onLoad"
|
@load="onLoad"
|
||||||
/>
|
/>
|
||||||
|
<canvas v-else ref="imgex" class="image-ex-img"></canvas>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import throttle from "@/utils/throttle";
|
import throttle from "@/utils/throttle";
|
||||||
import UTIF from "utif";
|
import { showError } from "@/notify";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: {
|
props: {
|
||||||
src: String,
|
src: String,
|
||||||
|
@ -55,15 +57,18 @@ export default {
|
||||||
},
|
},
|
||||||
maxScale: 4,
|
maxScale: 4,
|
||||||
minScale: 0.25,
|
minScale: 0.25,
|
||||||
|
isTiff: false, // Determine if the image is a TIFF
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
if (!this.decodeUTIF()) {
|
this.isTiff = this.checkIfTiff(this.src);
|
||||||
|
if (this.isTiff) {
|
||||||
|
this.decodeTiff(this.src);
|
||||||
|
} else {
|
||||||
this.$refs.imgex.src = this.src;
|
this.$refs.imgex.src = this.src;
|
||||||
}
|
}
|
||||||
let container = this.$refs.container;
|
let container = this.$refs.container;
|
||||||
this.classList.forEach((className) => container.classList.add(className));
|
this.classList.forEach((className) => container.classList.add(className));
|
||||||
// set width and height if they are zero
|
|
||||||
if (getComputedStyle(container).width === "0px") {
|
if (getComputedStyle(container).width === "0px") {
|
||||||
container.style.width = "100%";
|
container.style.width = "100%";
|
||||||
}
|
}
|
||||||
|
@ -79,7 +84,10 @@ export default {
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
src: function () {
|
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;
|
this.$refs.imgex.src = this.src;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -89,19 +97,29 @@ export default {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
// Modified from UTIF.replaceIMG
|
checkIfTiff(src) {
|
||||||
decodeUTIF() {
|
|
||||||
const sufs = ["tif", "tiff", "dng", "cr2", "nef"];
|
const sufs = ["tif", "tiff", "dng", "cr2", "nef"];
|
||||||
let suff = document.location.pathname.split(".").pop().toLowerCase();
|
const suff = src.split(".").pop().toLowerCase();
|
||||||
if (sufs.indexOf(suff) == -1) return false;
|
return sufs.includes(suff);
|
||||||
let xhr = new XMLHttpRequest();
|
},
|
||||||
UTIF._xhrs.push(xhr);
|
async decodeTiff(src) {
|
||||||
UTIF._imgs.push(this.$refs.imgex);
|
try {
|
||||||
xhr.open("GET", this.src);
|
const response = await fetch(src);
|
||||||
xhr.responseType = "arraybuffer";
|
if (!response.ok) {
|
||||||
xhr.onload = UTIF._imgLoaded;
|
throw new Error("Network response was not ok");
|
||||||
xhr.send();
|
}
|
||||||
return true;
|
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() {
|
onLoad() {
|
||||||
let img = this.$refs.imgex;
|
let img = this.$refs.imgex;
|
||||||
|
@ -146,9 +164,7 @@ export default {
|
||||||
let container = this.$refs.container;
|
let container = this.$refs.container;
|
||||||
let img = this.$refs.imgex;
|
let img = this.$refs.imgex;
|
||||||
|
|
||||||
this.position.center.x = Math.floor(
|
this.position.center.x = Math.floor((container.clientWidth - img.clientWidth) / 2);
|
||||||
(container.clientWidth - img.clientWidth) / 2
|
|
||||||
);
|
|
||||||
this.position.center.y = Math.floor(
|
this.position.center.y = Math.floor(
|
||||||
(container.clientHeight - img.clientHeight) / 2
|
(container.clientHeight - img.clientHeight) / 2
|
||||||
);
|
);
|
||||||
|
@ -275,6 +291,7 @@ export default {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.image-ex-container {
|
.image-ex-container {
|
||||||
margin: auto;
|
margin: auto;
|
||||||
|
|
|
@ -24,7 +24,11 @@
|
||||||
:class="{ activeimg: this.isMaximized && this.isSelected }"
|
:class="{ activeimg: this.isMaximized && this.isSelected }"
|
||||||
ref="thumbnail"
|
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>
|
||||||
|
|
||||||
<div :class="{ activecontent: this.isMaximized && this.isSelected }">
|
<div :class="{ activecontent: this.isMaximized && this.isSelected }">
|
||||||
|
@ -64,10 +68,10 @@
|
||||||
<script>
|
<script>
|
||||||
import { enableThumbs } from "@/utils/constants";
|
import { enableThumbs } from "@/utils/constants";
|
||||||
import { getHumanReadableFilesize } from "@/utils/filesizes";
|
import { getHumanReadableFilesize } from "@/utils/filesizes";
|
||||||
import { mapMutations, mapGetters, mapState } from "vuex";
|
import { fromNow } from "@/utils/moment";
|
||||||
import moment from "moment";
|
|
||||||
import { files as api } from "@/api";
|
import { files as api } from "@/api";
|
||||||
import * as upload from "@/utils/upload";
|
import * as upload from "@/utils/upload";
|
||||||
|
import { state, getters, mutations } from "@/store"; // Import your custom store
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "item",
|
name: "item",
|
||||||
|
@ -90,32 +94,44 @@ export default {
|
||||||
"path",
|
"path",
|
||||||
],
|
],
|
||||||
computed: {
|
computed: {
|
||||||
...mapState(["user", "selected", "req", "jwt"]),
|
user() {
|
||||||
...mapGetters(["selectedCount"]),
|
return state.user;
|
||||||
|
},
|
||||||
|
selected() {
|
||||||
|
return state.selected;
|
||||||
|
},
|
||||||
|
req() {
|
||||||
|
return state.req;
|
||||||
|
},
|
||||||
|
jwt() {
|
||||||
|
return state.jwt;
|
||||||
|
},
|
||||||
|
selectedCount() {
|
||||||
|
return getters.selectedCount();
|
||||||
|
},
|
||||||
isClicked() {
|
isClicked() {
|
||||||
if (this.user.singleClick || !this.allowedView ) {
|
if (state.user.singleClick || !this.allowedView) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
// Assuming toggleClick returns a boolean value
|
|
||||||
return !this.isMaximized;
|
return !this.isMaximized;
|
||||||
},
|
},
|
||||||
allowedView() {
|
allowedView() {
|
||||||
return this.user.viewMode != "gallery" && this.user.viewMode != "normal"
|
return state.user.viewMode != "gallery" && state.user.viewMode != "normal";
|
||||||
},
|
},
|
||||||
singleClick() {
|
singleClick() {
|
||||||
return this.readOnly == undefined && this.user.singleClick;
|
return this.readOnly == undefined && state.user.singleClick;
|
||||||
},
|
},
|
||||||
isSelected() {
|
isSelected() {
|
||||||
return this.selected.indexOf(this.index) !== -1;
|
return this.selected.indexOf(this.index) !== -1;
|
||||||
},
|
},
|
||||||
isDraggable() {
|
isDraggable() {
|
||||||
return this.readOnly == undefined && this.user.perm.rename;
|
return this.readOnly == undefined && state.user.perm?.rename;
|
||||||
},
|
},
|
||||||
canDrop() {
|
canDrop() {
|
||||||
if (!this.isDir || this.readOnly !== undefined) return false;
|
if (!this.isDir || this.readOnly !== undefined) return false;
|
||||||
|
|
||||||
for (let i of this.selected) {
|
for (let i of this.selected) {
|
||||||
if (this.req.items[i].url === this.url) {
|
if (state.req.items[i].url === this.url) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -123,9 +139,9 @@ export default {
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
thumbnailUrl() {
|
thumbnailUrl() {
|
||||||
let path = this.req.path
|
let path = state.req.path;
|
||||||
if (this.req.path == "/") {
|
if (state.req.path == "/") {
|
||||||
path = ""
|
path = "";
|
||||||
}
|
}
|
||||||
const file = {
|
const file = {
|
||||||
path: path + "/" + this.name,
|
path: path + "/" + this.name,
|
||||||
|
@ -144,7 +160,7 @@ export default {
|
||||||
mounted() {
|
mounted() {
|
||||||
const observer = new IntersectionObserver(this.handleIntersect, {
|
const observer = new IntersectionObserver(this.handleIntersect, {
|
||||||
root: null,
|
root: null,
|
||||||
rootMargin: '0px',
|
rootMargin: "0px",
|
||||||
threshold: 0.5, // Adjust threshold as needed
|
threshold: 0.5, // Adjust threshold as needed
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -156,7 +172,7 @@ export default {
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
handleIntersect(entries, observer) {
|
handleIntersect(entries, observer) {
|
||||||
entries.forEach(entry => {
|
entries.forEach((entry) => {
|
||||||
if (entry.isIntersecting) {
|
if (entry.isIntersecting) {
|
||||||
this.isThumbnailInView = true;
|
this.isThumbnailInView = true;
|
||||||
// Stop observing once thumbnail is in view
|
// Stop observing once thumbnail is in view
|
||||||
|
@ -167,27 +183,26 @@ export default {
|
||||||
toggleClick() {
|
toggleClick() {
|
||||||
this.isMaximized = this.isClicked;
|
this.isMaximized = this.isClicked;
|
||||||
},
|
},
|
||||||
...mapMutations(["addSelected", "removeSelected", "resetSelected"]),
|
|
||||||
humanSize: function () {
|
humanSize: function () {
|
||||||
return this.type == "invalid_link"
|
return this.type == "invalid_link"
|
||||||
? "invalid link"
|
? "invalid link"
|
||||||
: getHumanReadableFilesize(this.size);
|
: getHumanReadableFilesize(this.size);
|
||||||
},
|
},
|
||||||
humanTime: function () {
|
humanTime: function () {
|
||||||
if (this.readOnly == undefined && this.user.dateFormat) {
|
if (this.readOnly == undefined && state.user.dateFormat) {
|
||||||
return moment(this.modified).format("L LT");
|
return fromNow(this.modified, state.user.locale).format("L LT");
|
||||||
}
|
}
|
||||||
return moment(this.modified).fromNow();
|
return fromNow(this.modified, state.user.locale);
|
||||||
},
|
},
|
||||||
dragStart: function () {
|
dragStart: function () {
|
||||||
if (this.selectedCount === 0) {
|
if (getters.selectedCount() === 0) {
|
||||||
this.addSelected(this.index);
|
mutations.addSelected(this.index);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.isSelected) {
|
if (!this.isSelected) {
|
||||||
this.resetSelected();
|
mutations.resetSelected();
|
||||||
this.addSelected(this.index);
|
mutations.addSelected(this.index);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
dragOver: function (event) {
|
dragOver: function (event) {
|
||||||
|
@ -208,7 +223,7 @@ export default {
|
||||||
if (!this.canDrop) return;
|
if (!this.canDrop) return;
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
if (this.selectedCount === 0) return;
|
if (getters.selectedCount() === 0) return;
|
||||||
|
|
||||||
let el = event.target;
|
let el = event.target;
|
||||||
for (let i = 0; i < 5; i++) {
|
for (let i = 0; i < 5; i++) {
|
||||||
|
@ -221,9 +236,9 @@ export default {
|
||||||
|
|
||||||
for (let i of this.selected) {
|
for (let i of this.selected) {
|
||||||
items.push({
|
items.push({
|
||||||
from: this.req.items[i].url,
|
from: state.req.items[i].url,
|
||||||
to: this.url + encodeURIComponent(this.req.items[i].name),
|
to: this.url + encodeURIComponent(state.req.items[i].name),
|
||||||
name: this.req.items[i].name,
|
name: state.req.items[i].name,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -235,9 +250,9 @@ export default {
|
||||||
api
|
api
|
||||||
.move(items, overwrite, rename)
|
.move(items, overwrite, rename)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.$store.commit("setReload", true);
|
mutations.setReload(true);
|
||||||
})
|
})
|
||||||
.catch(this.$showError);
|
.catch(showError);
|
||||||
};
|
};
|
||||||
|
|
||||||
let conflict = upload.checkConflict(items, baseItems);
|
let conflict = upload.checkConflict(items, baseItems);
|
||||||
|
@ -246,14 +261,14 @@ export default {
|
||||||
let rename = false;
|
let rename = false;
|
||||||
|
|
||||||
if (conflict) {
|
if (conflict) {
|
||||||
this.$store.commit("showHover", {
|
mutations.showHover({
|
||||||
prompt: "replace-rename",
|
name: "replace-rename",
|
||||||
confirm: (event, option) => {
|
confirm: (event, option) => {
|
||||||
overwrite = option == "overwrite";
|
overwrite = option == "overwrite";
|
||||||
rename = option == "rename";
|
rename = option == "rename";
|
||||||
|
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
this.$store.commit("closeHovers");
|
mutations.closeHovers();
|
||||||
action(overwrite, rename);
|
action(overwrite, rename);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -264,11 +279,11 @@ export default {
|
||||||
action(overwrite, rename);
|
action(overwrite, rename);
|
||||||
},
|
},
|
||||||
itemClick: function (event) {
|
itemClick: function (event) {
|
||||||
if (this.singleClick && !this.$store.state.multiple) this.open();
|
if (this.singleClick && !state.multiple) this.open();
|
||||||
else this.click(event);
|
else this.click(event);
|
||||||
},
|
},
|
||||||
click: function (event) {
|
click: function (event) {
|
||||||
if (!this.singleClick && this.selectedCount !== 0) event.preventDefault();
|
if (!this.singleClick && getters.selectedCount() !== 0) event.preventDefault();
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.touches = 0;
|
this.touches = 0;
|
||||||
|
@ -279,8 +294,8 @@ export default {
|
||||||
this.open();
|
this.open();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.$store.state.selected.indexOf(this.index) !== -1) {
|
if (state.selected.indexOf(this.index) !== -1) {
|
||||||
this.removeSelected(this.index);
|
mutations.removeSelected(this.index);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -297,21 +312,16 @@ export default {
|
||||||
}
|
}
|
||||||
|
|
||||||
for (; fi <= la; fi++) {
|
for (; fi <= la; fi++) {
|
||||||
if (this.$store.state.selected.indexOf(fi) == -1) {
|
if (state.selected.indexOf(fi) == -1) {
|
||||||
this.addSelected(fi);
|
mutations.addSelected(fi);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (
|
if (!this.singleClick && !event.ctrlKey && !event.metaKey && !state.multiple)
|
||||||
!this.singleClick &&
|
mutations.resetSelected();
|
||||||
!event.ctrlKey &&
|
mutations.addSelected(this.index);
|
||||||
!event.metaKey &&
|
|
||||||
!this.$store.state.multiple
|
|
||||||
)
|
|
||||||
this.resetSelected();
|
|
||||||
this.addSelected(this.index);
|
|
||||||
},
|
},
|
||||||
open: function () {
|
open: function () {
|
||||||
this.$router.push({ path: this.url });
|
this.$router.push({ path: this.url });
|
||||||
|
|
|
@ -7,13 +7,15 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import { mutations } from "@/store"; // Import your custom store
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "action",
|
name: "action",
|
||||||
props: ["icon", "label", "counter", "show"],
|
props: ["icon", "label", "counter", "show"],
|
||||||
methods: {
|
methods: {
|
||||||
action: function () {
|
action: function () {
|
||||||
if (this.show) {
|
if (this.show) {
|
||||||
this.$store.commit("showHover", this.show);
|
mutations.showHover(this.show);
|
||||||
}
|
}
|
||||||
this.$emit("action");
|
this.$emit("action");
|
||||||
},
|
},
|
||||||
|
|
|
@ -27,7 +27,7 @@
|
||||||
<div>
|
<div>
|
||||||
<button
|
<button
|
||||||
class="button button--flat button--grey"
|
class="button button--flat button--grey"
|
||||||
@click="$store.commit('closeHovers')"
|
@click="closeHovers"
|
||||||
:aria-label="$t('buttons.cancel')"
|
:aria-label="$t('buttons.cancel')"
|
||||||
:title="$t('buttons.cancel')"
|
:title="$t('buttons.cancel')"
|
||||||
>
|
>
|
||||||
|
@ -47,11 +47,12 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { mapState } from "vuex";
|
import { mutations, state } from "@/store";
|
||||||
import FileList from "./FileList.vue";
|
import FileList from "./FileList.vue";
|
||||||
import { files as api } from "@/api";
|
import { files as api } from "@/api";
|
||||||
import buttons from "@/utils/buttons";
|
import buttons from "@/utils/buttons";
|
||||||
import * as upload from "@/utils/upload";
|
import * as upload from "@/utils/upload";
|
||||||
|
import { showError } from "@/notify";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "copy",
|
name: "copy",
|
||||||
|
@ -62,7 +63,14 @@ export default {
|
||||||
dest: null,
|
dest: null,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: mapState(["req", "selected", "user"]),
|
computed: {
|
||||||
|
user() {
|
||||||
|
return state.user;
|
||||||
|
},
|
||||||
|
closeHovers() {
|
||||||
|
return mutations.closeHovers();
|
||||||
|
},
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
copy: async function (event) {
|
copy: async function (event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
@ -71,9 +79,9 @@ export default {
|
||||||
// Create a new promise for each file.
|
// Create a new promise for each file.
|
||||||
for (let item of this.selected) {
|
for (let item of this.selected) {
|
||||||
items.push({
|
items.push({
|
||||||
from: this.req.items[item].url,
|
from: store.req.items[item].url,
|
||||||
to: this.dest + encodeURIComponent(this.req.items[item].name),
|
to: this.dest + encodeURIComponent(store.req.items[item].name),
|
||||||
name: this.req.items[item].name,
|
name: store.req.items[item].name,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -85,9 +93,8 @@ export default {
|
||||||
.then(() => {
|
.then(() => {
|
||||||
buttons.success("copy");
|
buttons.success("copy");
|
||||||
|
|
||||||
if (this.$route.path === this.dest) {
|
if (state.route.path === this.dest) {
|
||||||
this.$store.commit("setReload", true);
|
mutations.setReload(true);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -95,12 +102,12 @@ export default {
|
||||||
})
|
})
|
||||||
.catch((e) => {
|
.catch((e) => {
|
||||||
buttons.done("copy");
|
buttons.done("copy");
|
||||||
this.$showError(e);
|
showError(e);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
if (this.$route.path === this.dest) {
|
if (state.route.path === this.dest) {
|
||||||
this.$store.commit("closeHovers");
|
mutations.closeHovers();
|
||||||
action(false, true);
|
action(false, true);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
|
@ -113,14 +120,14 @@ export default {
|
||||||
let rename = false;
|
let rename = false;
|
||||||
|
|
||||||
if (conflict) {
|
if (conflict) {
|
||||||
this.$store.commit("showHover", {
|
mutations.showHover({
|
||||||
prompt: "replace-rename",
|
name: "replace-rename",
|
||||||
confirm: (event, option) => {
|
confirm: (event, option) => {
|
||||||
overwrite = option == "overwrite";
|
overwrite = option == "overwrite";
|
||||||
rename = option == "rename";
|
rename = option == "rename";
|
||||||
|
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
this.$store.commit("closeHovers");
|
mutations.closeHovers();
|
||||||
action(overwrite, rename);
|
action(overwrite, rename);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="card-action">
|
<div class="card-action">
|
||||||
<button
|
<button
|
||||||
@click="$store.commit('closeHovers')"
|
@click="closeHovers"
|
||||||
class="button button--flat button--grey"
|
class="button button--flat button--grey"
|
||||||
:aria-label="$t('buttons.cancel')"
|
:aria-label="$t('buttons.cancel')"
|
||||||
:title="$t('buttons.cancel')"
|
:title="$t('buttons.cancel')"
|
||||||
|
@ -30,24 +30,34 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { mapGetters, mapMutations, mapState } from "vuex";
|
|
||||||
import { files as api } from "@/api";
|
import { files as api } from "@/api";
|
||||||
import buttons from "@/utils/buttons";
|
import buttons from "@/utils/buttons";
|
||||||
|
import { state, getters, mutations } from "@/store";
|
||||||
|
import { showError } from "@/notify";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "delete",
|
name: "delete",
|
||||||
computed: {
|
computed: {
|
||||||
...mapGetters(["isListing", "selectedCount", "currentPrompt"]),
|
isListing() {
|
||||||
...mapState(["req", "selected"]),
|
return getters.isListing();
|
||||||
|
},
|
||||||
|
selectedCount() {
|
||||||
|
return getters.selectedCount();
|
||||||
|
},
|
||||||
|
currentPrompt() {
|
||||||
|
return getters.currentPrompt();
|
||||||
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
...mapMutations(["closeHovers"]),
|
closeHovers() {
|
||||||
submit: async function () {
|
mutations.closeHovers();
|
||||||
|
},
|
||||||
|
async submit() {
|
||||||
buttons.loading("delete");
|
buttons.loading("delete");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (!this.isListing) {
|
if (!this.isListing) {
|
||||||
await api.remove(this.$route.path);
|
await api.remove(state.route.path);
|
||||||
buttons.success("delete");
|
buttons.success("delete");
|
||||||
|
|
||||||
this.currentPrompt?.confirm();
|
this.currentPrompt?.confirm();
|
||||||
|
@ -57,22 +67,22 @@ export default {
|
||||||
|
|
||||||
this.closeHovers();
|
this.closeHovers();
|
||||||
|
|
||||||
if (this.selectedCount === 0) {
|
if (getters.selectedCount() === 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let promises = [];
|
let promises = [];
|
||||||
for (let index of this.selected) {
|
for (let index of state.selected) {
|
||||||
promises.push(api.remove(this.req.items[index].url));
|
promises.push(api.remove(state.req.items[index].url));
|
||||||
}
|
}
|
||||||
|
|
||||||
await Promise.all(promises);
|
await Promise.all(promises);
|
||||||
buttons.success("delete");
|
buttons.success("delete");
|
||||||
this.$store.commit("setReload", true);
|
mutations.setReload(true); // Handle reload as needed
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
buttons.done("delete");
|
buttons.done("delete");
|
||||||
this.$showError(e);
|
showError(e);
|
||||||
if (this.isListing) this.$store.commit("setReload", true);
|
if (this.isListing) mutations.setReload(true); // Handle reload as needed
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -19,22 +19,21 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { mapGetters, mapMutations, mapState } from "vuex";
|
|
||||||
import { users as api } from "@/api";
|
import { users as api } from "@/api";
|
||||||
|
import { showSuccess,showError } from "@/notify";
|
||||||
import buttons from "@/utils/buttons";
|
import buttons from "@/utils/buttons";
|
||||||
|
import { state, mutations, getters } from "@/store";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "delete",
|
name: "delete",
|
||||||
computed: {
|
computed: {
|
||||||
...mapState(["prompts"]),
|
|
||||||
currentPrompt() {
|
currentPrompt() {
|
||||||
return this.prompts.length ? this.prompts[this.prompts.length - 1] : null;
|
return getters.currentPrompt();
|
||||||
},
|
},
|
||||||
user() {
|
user() {
|
||||||
return this.currentPrompt?.props?.user;
|
return this.currentPrompt?.props?.user;
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
async deleteUser(event) {
|
async deleteUser(event) {
|
||||||
|
@ -42,14 +41,16 @@ export default {
|
||||||
try {
|
try {
|
||||||
await api.remove(this.user.id);
|
await api.remove(this.user.id);
|
||||||
this.$router.push({ path: "/settings/users" });
|
this.$router.push({ path: "/settings/users" });
|
||||||
this.$showSuccess(this.$t("settings.userDeleted"));
|
showSuccess(this.$t("settings.userDeleted"));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
e.message === "403"
|
e.message === "403"
|
||||||
? this.$showError(this.$t("errors.forbidden"), false)
|
? showError(this.$t("errors.forbidden"), false)
|
||||||
: this.$showError(e);
|
: showError(e);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
...mapMutations(["closeHovers"]),
|
closeHovers() {
|
||||||
|
mutations.closeHovers();
|
||||||
|
},
|
||||||
submit: async function () {
|
submit: async function () {
|
||||||
buttons.loading("delete");
|
buttons.loading("delete");
|
||||||
|
|
||||||
|
@ -65,22 +66,22 @@ export default {
|
||||||
|
|
||||||
this.closeHovers();
|
this.closeHovers();
|
||||||
|
|
||||||
if (this.selectedCount === 0) {
|
if (getters.selectedCount() === 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let promises = [];
|
let promises = [];
|
||||||
for (let index of this.selected) {
|
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);
|
await Promise.all(promises);
|
||||||
buttons.success("delete");
|
buttons.success("delete");
|
||||||
this.$store.commit("setReload", true);
|
mutations.setReload(true); // Handle reload as needed
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
buttons.done("delete");
|
buttons.done("delete");
|
||||||
this.$showError(e);
|
showError(e);
|
||||||
if (this.isListing) this.$store.commit("setReload", true);
|
if (this.isListing) mutations.setReload(true); // Handle reload as needed
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -3,7 +3,6 @@
|
||||||
<div class="card-title">
|
<div class="card-title">
|
||||||
<h2>{{ $t("prompts.download") }}</h2>
|
<h2>{{ $t("prompts.download") }}</h2>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card-content">
|
<div class="card-content">
|
||||||
<p>{{ $t("prompts.downloadMessage") }}</p>
|
<p>{{ $t("prompts.downloadMessage") }}</p>
|
||||||
|
|
||||||
|
@ -21,7 +20,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { mapGetters } from "vuex";
|
import { getters } from "@/store"; // Import your custom store
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "download",
|
name: "download",
|
||||||
|
@ -39,6 +38,9 @@ export default {
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapGetters(["currentPrompt"]),
|
currentPrompt() {
|
||||||
},};
|
return getters.currentPrompt();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -21,9 +21,10 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { mapState } from "vuex";
|
import { state, mutations } from "@/store";
|
||||||
import url from "@/utils/url";
|
import url from "@/utils/url";
|
||||||
import { files } from "@/api";
|
import { files } from "@/api";
|
||||||
|
import { showError } from "@/notify";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "file-list",
|
name: "file-list",
|
||||||
|
@ -39,13 +40,12 @@ export default {
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapState(["req", "user"]),
|
|
||||||
nav() {
|
nav() {
|
||||||
return decodeURIComponent(this.current);
|
return decodeURIComponent(this.current);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.fillOptions(this.req);
|
this.fillOptions(state.req);
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
fillOptions(req) {
|
fillOptions(req) {
|
||||||
|
@ -86,7 +86,7 @@ export default {
|
||||||
// content.
|
// content.
|
||||||
let uri = event.currentTarget.dataset.url;
|
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) {
|
touchstart(event) {
|
||||||
let url = event.currentTarget.dataset.url;
|
let url = event.currentTarget.dataset.url;
|
||||||
|
@ -114,7 +114,7 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
itemClick: function (event) {
|
itemClick: function (event) {
|
||||||
if (this.user.singleClick) this.next(event);
|
if (state.user.singleClick) this.next(event);
|
||||||
else this.select(event);
|
else this.select(event);
|
||||||
},
|
},
|
||||||
select: function (event) {
|
select: function (event) {
|
||||||
|
@ -130,13 +130,13 @@ export default {
|
||||||
this.$emit("update:selected", this.selected);
|
this.$emit("update:selected", this.selected);
|
||||||
},
|
},
|
||||||
createDir: async function () {
|
createDir: async function () {
|
||||||
this.$store.commit("showHover", {
|
mutations.showHover({
|
||||||
prompt: "newDir",
|
name: "newDir",
|
||||||
action: null,
|
action: null,
|
||||||
confirm: null,
|
confirm: null,
|
||||||
props: {
|
props: {
|
||||||
redirect: false,
|
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">
|
<div class="card-action">
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
@click="$store.commit('closeHovers')"
|
@click="closeHovers"
|
||||||
class="button button--flat"
|
class="button button--flat"
|
||||||
:aria-label="$t('buttons.ok')"
|
:aria-label="$t('buttons.ok')"
|
||||||
:title="$t('buttons.ok')"
|
:title="$t('buttons.ok')"
|
||||||
|
@ -33,5 +33,14 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<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>
|
</script>
|
||||||
|
|
|
@ -33,33 +33,25 @@
|
||||||
<p>
|
<p>
|
||||||
<strong>MD5: </strong
|
<strong>MD5: </strong
|
||||||
><code
|
><code
|
||||||
><a @click="checksum($event, 'md5')">{{
|
><a @click="checksum($event, 'md5')">{{ $t("prompts.show") }}</a></code
|
||||||
$t("prompts.show")
|
|
||||||
}}</a></code
|
|
||||||
>
|
>
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<strong>SHA1: </strong
|
<strong>SHA1: </strong
|
||||||
><code
|
><code
|
||||||
><a @click="checksum($event, 'sha1')">{{
|
><a @click="checksum($event, 'sha1')">{{ $t("prompts.show") }}</a></code
|
||||||
$t("prompts.show")
|
|
||||||
}}</a></code
|
|
||||||
>
|
>
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<strong>SHA256: </strong
|
<strong>SHA256: </strong
|
||||||
><code
|
><code
|
||||||
><a @click="checksum($event, 'sha256')">{{
|
><a @click="checksum($event, 'sha256')">{{ $t("prompts.show") }}</a></code
|
||||||
$t("prompts.show")
|
|
||||||
}}</a></code
|
|
||||||
>
|
>
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<strong>SHA512: </strong
|
<strong>SHA512: </strong
|
||||||
><code
|
><code
|
||||||
><a @click="checksum($event, 'sha512')">{{
|
><a @click="checksum($event, 'sha512')">{{ $t("prompts.show") }}</a></code
|
||||||
$t("prompts.show")
|
|
||||||
}}</a></code
|
|
||||||
>
|
>
|
||||||
</p>
|
</p>
|
||||||
</template>
|
</template>
|
||||||
|
@ -68,7 +60,7 @@
|
||||||
<div class="card-action">
|
<div class="card-action">
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
@click="$store.commit('closeHovers')"
|
@click="closeHovers"
|
||||||
class="button button--flat"
|
class="button button--flat"
|
||||||
:aria-label="$t('buttons.ok')"
|
:aria-label="$t('buttons.ok')"
|
||||||
:title="$t('buttons.ok')"
|
:title="$t('buttons.ok')"
|
||||||
|
@ -78,73 +70,87 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { getHumanReadableFilesize } from "@/utils/filesizes";
|
import { getHumanReadableFilesize } from "@/utils/filesizes";
|
||||||
import { mapState, mapGetters } from "vuex";
|
import { formatTimestamp } from "@/utils/moment";
|
||||||
import moment from "moment";
|
|
||||||
import { files as api } from "@/api";
|
import { files as api } from "@/api";
|
||||||
|
import { state, getters, mutations } from "@/store"; // Import your custom store
|
||||||
|
import { showError } from "@/notify";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "info",
|
name: "info",
|
||||||
computed: {
|
computed: {
|
||||||
...mapState(["req", "selected"]),
|
closeHovers() {
|
||||||
...mapGetters(["selectedCount", "isListing"]),
|
return mutations.closeHovers;
|
||||||
humanSize: function () {
|
},
|
||||||
if (this.selectedCount === 0 || !this.isListing) {
|
req() {
|
||||||
return getHumanReadableFilesize(this.req.size);
|
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;
|
let sum = 0;
|
||||||
|
|
||||||
for (let selected of this.selected) {
|
for (let selected of this.selected) {
|
||||||
sum += this.req.items[selected].size;
|
sum += state.req.items[selected].size;
|
||||||
}
|
}
|
||||||
|
|
||||||
return getHumanReadableFilesize(sum);
|
return getHumanReadableFilesize(sum);
|
||||||
},
|
},
|
||||||
humanTime: function () {
|
humanTime() {
|
||||||
if (this.selectedCount === 0) {
|
if (getters.selectedCount() === 0) {
|
||||||
return moment(this.req.modified).fromNow();
|
return formatTimestamp(state.req.modified, state.user.locale);
|
||||||
}
|
}
|
||||||
|
return formatTimestamp(
|
||||||
return moment(this.req.items[this.selected[0]].modified).fromNow();
|
state.req.items[this.selected[0]].modified,
|
||||||
|
state.user.locale
|
||||||
|
);
|
||||||
},
|
},
|
||||||
modTime: function () {
|
modTime() {
|
||||||
return new Date(Date.parse(this.req.modified)).toLocaleString();
|
return new Date(Date.parse(state.req.modified)).toLocaleString();
|
||||||
},
|
},
|
||||||
name: function () {
|
name() {
|
||||||
return this.selectedCount === 0
|
return getters.selectedCount() === 0
|
||||||
? this.req.name
|
? state.req.name
|
||||||
: this.req.items[this.selected[0]].name;
|
: state.req.items[this.selected[0]].name;
|
||||||
},
|
},
|
||||||
dir: function () {
|
dir() {
|
||||||
return (
|
return (
|
||||||
this.selectedCount > 1 ||
|
getters.selectedCount() > 1 ||
|
||||||
(this.selectedCount === 0
|
(getters.selectedCount() === 0
|
||||||
? this.req.isDir
|
? state.req.isDir
|
||||||
: this.req.items[this.selected[0]].isDir)
|
: state.req.items[this.selected[0]].isDir)
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
checksum: async function (event, algo) {
|
async checksum(event, algo) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
let link;
|
let link;
|
||||||
|
|
||||||
if (this.selectedCount) {
|
if (getters.selectedCount()) {
|
||||||
link = this.req.items[this.selected[0]].url;
|
link = state.req.items[this.selected[0]].url;
|
||||||
} else {
|
} else {
|
||||||
link = this.$route.path;
|
link = state.route.path;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const hash = await api.checksum(link, algo);
|
const hash = await api.checksum(link, algo);
|
||||||
// eslint-disable-next-line
|
event.target.innerHTML = hash;
|
||||||
event.target.innerHTML = hash
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.$showError(e);
|
showError(e);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -26,7 +26,7 @@
|
||||||
<div>
|
<div>
|
||||||
<button
|
<button
|
||||||
class="button button--flat button--grey"
|
class="button button--flat button--grey"
|
||||||
@click="$store.commit('closeHovers')"
|
@click="closeHovers"
|
||||||
:aria-label="$t('buttons.cancel')"
|
:aria-label="$t('buttons.cancel')"
|
||||||
:title="$t('buttons.cancel')"
|
:title="$t('buttons.cancel')"
|
||||||
>
|
>
|
||||||
|
@ -47,11 +47,12 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { mapState } from "vuex";
|
import { mutations, state } from "@/store";
|
||||||
import FileList from "./FileList.vue";
|
import FileList from "./FileList.vue";
|
||||||
import { files as api } from "@/api";
|
import { files as api } from "@/api";
|
||||||
import buttons from "@/utils/buttons";
|
import buttons from "@/utils/buttons";
|
||||||
import * as upload from "@/utils/upload";
|
import * as upload from "@/utils/upload";
|
||||||
|
import { showError } from "@/notify";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "move",
|
name: "move",
|
||||||
|
@ -62,32 +63,39 @@ export default {
|
||||||
dest: null,
|
dest: null,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: mapState(["req", "selected", "user"]),
|
computed: {
|
||||||
|
user() {
|
||||||
|
return state.user;
|
||||||
|
},
|
||||||
|
closeHovers() {
|
||||||
|
return mutations.closeHovers()
|
||||||
|
},
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
move: async function (event) {
|
move: async function (event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
let items = [];
|
let items = [];
|
||||||
|
|
||||||
for (let item of this.selected) {
|
for (let item of state.selected) {
|
||||||
items.push({
|
items.push({
|
||||||
from: this.req.items[item].url,
|
from: state.req.items[item].url,
|
||||||
to: this.dest + encodeURIComponent(this.req.items[item].name),
|
to: this.dest + encodeURIComponent(state.req.items[item].name),
|
||||||
name: this.req.items[item].name,
|
name: state.req.items[item].name,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let action = async (overwrite, rename) => {
|
let action = async (overwrite, rename) => {
|
||||||
buttons.loading("move");
|
buttons.loading("move");
|
||||||
|
|
||||||
await api
|
await api
|
||||||
.move(items, overwrite, rename)
|
.move(items, overwrite, rename)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
buttons.success("move");
|
buttons.success("move");
|
||||||
this.$router.push({ path: this.dest });
|
this.$router.push({ path: this.dest });
|
||||||
|
mutations.setReload(true)
|
||||||
})
|
})
|
||||||
.catch((e) => {
|
.catch((e) => {
|
||||||
buttons.done("move");
|
buttons.done("move");
|
||||||
this.$showError(e);
|
showError(e);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -98,15 +106,16 @@ export default {
|
||||||
let rename = false;
|
let rename = false;
|
||||||
|
|
||||||
if (conflict) {
|
if (conflict) {
|
||||||
this.$store.commit("showHover", {
|
mutations.showHover({
|
||||||
prompt: "replace-rename",
|
name: "replace-rename",
|
||||||
confirm: (event, option) => {
|
confirm: (event, option) => {
|
||||||
overwrite = option == "overwrite";
|
overwrite = option == "overwrite";
|
||||||
rename = option == "rename";
|
rename = option == "rename";
|
||||||
|
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
this.$store.commit("closeHovers");
|
mutations.closeHovers();
|
||||||
action(overwrite, rename);
|
action(overwrite, rename);
|
||||||
|
mutations.setReload(true)
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -18,7 +18,7 @@
|
||||||
<div class="card-action">
|
<div class="card-action">
|
||||||
<button
|
<button
|
||||||
class="button button--flat button--grey"
|
class="button button--flat button--grey"
|
||||||
@click="$store.commit('closeHovers')"
|
@click="closeHovers"
|
||||||
:aria-label="$t('buttons.cancel')"
|
:aria-label="$t('buttons.cancel')"
|
||||||
:title="$t('buttons.cancel')"
|
:title="$t('buttons.cancel')"
|
||||||
>
|
>
|
||||||
|
@ -35,11 +35,11 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { mapGetters } from "vuex";
|
|
||||||
import { files as api } from "@/api";
|
import { files as api } from "@/api";
|
||||||
import url from "@/utils/url";
|
import url from "@/utils/url";
|
||||||
|
import { getters, mutations, state } from "@/store"; // Import your custom store
|
||||||
|
import { showError } from "@/notify";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "new-dir",
|
name: "new-dir",
|
||||||
|
@ -53,23 +53,31 @@ export default {
|
||||||
default: null,
|
default: null,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
data: function () {
|
data() {
|
||||||
return {
|
return {
|
||||||
name: "",
|
name: "",
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapGetters(["isFiles", "isListing"]),
|
isFiles() {
|
||||||
|
return getters.isFiles();
|
||||||
|
},
|
||||||
|
isListing() {
|
||||||
|
return getters.isListing();
|
||||||
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
submit: async function (event) {
|
closeHovers() {
|
||||||
|
return mutations.closeHovers();
|
||||||
|
},
|
||||||
|
async submit(event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
if (this.new === "") return;
|
if (this.name === "") return;
|
||||||
|
|
||||||
// Build the path of the new directory.
|
// Build the path of the new directory.
|
||||||
let uri;
|
let uri;
|
||||||
if (this.base) uri = this.base;
|
if (this.base) uri = this.base;
|
||||||
else if (this.isFiles) uri = this.$route.path + "/";
|
else if (getters.isFiles()) uri = state.route.path + "/";
|
||||||
else uri = "/";
|
else uri = "/";
|
||||||
|
|
||||||
if (!this.isListing) {
|
if (!this.isListing) {
|
||||||
|
@ -85,13 +93,13 @@ export default {
|
||||||
this.$router.push({ path: uri });
|
this.$router.push({ path: uri });
|
||||||
} else if (!this.base) {
|
} else if (!this.base) {
|
||||||
const res = await api.fetch(url.removeLastDir(uri) + "/");
|
const res = await api.fetch(url.removeLastDir(uri) + "/");
|
||||||
this.$store.commit("updateRequest", res);
|
mutations.updateRequest(res);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.$showError(e);
|
showError(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.$store.commit("closeHovers");
|
mutations.closeHovers();
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -18,7 +18,7 @@
|
||||||
<div class="card-action">
|
<div class="card-action">
|
||||||
<button
|
<button
|
||||||
class="button button--flat button--grey"
|
class="button button--flat button--grey"
|
||||||
@click="$store.commit('closeHovers')"
|
@click="closeHovers"
|
||||||
:aria-label="$t('buttons.cancel')"
|
:aria-label="$t('buttons.cancel')"
|
||||||
:title="$t('buttons.cancel')"
|
:title="$t('buttons.cancel')"
|
||||||
>
|
>
|
||||||
|
@ -35,29 +35,36 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { mapGetters } from "vuex";
|
import { state } from "@/store";
|
||||||
import { files as api } from "@/api";
|
import { files as api } from "@/api";
|
||||||
import url from "@/utils/url";
|
import url from "@/utils/url";
|
||||||
|
import { getters, mutations } from "@/store"; // Import your custom store
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "new-file",
|
name: "new-file",
|
||||||
data: function () {
|
data() {
|
||||||
return {
|
return {
|
||||||
name: "",
|
name: "",
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapGetters(["isFiles", "isListing"]),
|
isFiles() {
|
||||||
|
return getters.isFiles();
|
||||||
|
},
|
||||||
|
isListing() {
|
||||||
|
return getters.isListing();
|
||||||
|
},
|
||||||
|
closeHovers() {
|
||||||
|
return mutations.closeHovers;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
submit: async function (event) {
|
async submit(event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
if (this.new === "") return;
|
if (this.name === "") return;
|
||||||
|
// Build the path of the new file.
|
||||||
// Build the path of the new directory.
|
let uri = getters.isFiles() ? state.route.path + "/" : "/";
|
||||||
let uri = this.isFiles ? this.$route.path + "/" : "/";
|
|
||||||
|
|
||||||
if (!this.isListing) {
|
if (!this.isListing) {
|
||||||
uri = url.removeLastDir(uri) + "/";
|
uri = url.removeLastDir(uri) + "/";
|
||||||
|
@ -70,10 +77,10 @@ export default {
|
||||||
await api.post(uri);
|
await api.post(uri);
|
||||||
this.$router.push({ path: uri });
|
this.$router.push({ path: uri });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.$showError(e);
|
showError(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.$store.commit("closeHovers");
|
mutations.closeHovers();
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -5,8 +5,7 @@
|
||||||
:ref="currentPromptName"
|
:ref="currentPromptName"
|
||||||
:is="currentPromptName"
|
:is="currentPromptName"
|
||||||
v-bind="currentPrompt.props"
|
v-bind="currentPrompt.props"
|
||||||
>
|
/>
|
||||||
</component>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -27,8 +26,8 @@ import Upload from "./Upload.vue";
|
||||||
import ShareDelete from "./ShareDelete.vue";
|
import ShareDelete from "./ShareDelete.vue";
|
||||||
import DeleteUser from "./DeleteUser.vue";
|
import DeleteUser from "./DeleteUser.vue";
|
||||||
import Sidebar from "../Sidebar.vue";
|
import Sidebar from "../Sidebar.vue";
|
||||||
import { mapGetters, mapState } from "vuex";
|
|
||||||
import buttons from "@/utils/buttons";
|
import buttons from "@/utils/buttons";
|
||||||
|
import { state, getters, mutations } from "@/store"; // Import your custom store
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "prompts",
|
name: "prompts",
|
||||||
|
@ -50,30 +49,31 @@ export default {
|
||||||
Sidebar,
|
Sidebar,
|
||||||
DeleteUser,
|
DeleteUser,
|
||||||
},
|
},
|
||||||
data: function () {
|
data() {
|
||||||
return {
|
return {
|
||||||
pluginData: {
|
pluginData: {
|
||||||
buttons,
|
buttons,
|
||||||
store: this.$store,
|
store: state, // Directly use state
|
||||||
router: this.$router,
|
router: this.$router,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
window.addEventListener("keydown", (event) => {
|
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!
|
// Esc!
|
||||||
if (event.keyCode === 27) {
|
if (event.keyCode === 27) {
|
||||||
event.stopImmediatePropagation();
|
event.stopImmediatePropagation();
|
||||||
this.$store.commit("closeHovers");
|
mutations.closeHovers();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Enter
|
// Enter
|
||||||
if (event.keyCode == 13) {
|
if (event.keyCode === 13) {
|
||||||
switch (this.currentPrompt.prompt) {
|
switch (currentPrompt.name) {
|
||||||
case "delete":
|
case "delete":
|
||||||
prompt.submit();
|
prompt.submit();
|
||||||
break;
|
break;
|
||||||
|
@ -91,10 +91,25 @@ export default {
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapState(["plugins"]),
|
currentPromptName() {
|
||||||
...mapGetters(["currentPrompt", "currentPromptName"]),
|
if (getters.currentPromptName() == null) {
|
||||||
showOverlay: function () {
|
return "";
|
||||||
return this.currentPrompt !== null && this.currentPrompt.prompt !== "more";
|
}
|
||||||
|
return getters.currentPromptName();
|
||||||
|
},
|
||||||
|
currentPrompt() {
|
||||||
|
if (getters.currentPrompt() == null) {
|
||||||
|
return {
|
||||||
|
props: {},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return getters.currentPrompt();
|
||||||
|
},
|
||||||
|
plugins() {
|
||||||
|
return state.plugins;
|
||||||
|
},
|
||||||
|
showOverlay() {
|
||||||
|
return getters.currentPromptName() !== "more";
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {},
|
methods: {},
|
||||||
|
|
|
@ -21,7 +21,7 @@
|
||||||
<div class="card-action">
|
<div class="card-action">
|
||||||
<button
|
<button
|
||||||
class="button button--flat button--grey"
|
class="button button--flat button--grey"
|
||||||
@click="$store.commit('closeHovers')"
|
@click="closeHovers"
|
||||||
:aria-label="$t('buttons.cancel')"
|
:aria-label="$t('buttons.cancel')"
|
||||||
:title="$t('buttons.cancel')"
|
:title="$t('buttons.cancel')"
|
||||||
>
|
>
|
||||||
|
@ -39,15 +39,14 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { mapState, mapGetters } from "vuex";
|
|
||||||
import url from "@/utils/url";
|
import url from "@/utils/url";
|
||||||
import { files as api } from "@/api";
|
import { files as api } from "@/api";
|
||||||
|
import { state, getters, mutations } from "@/store";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "rename",
|
name: "rename",
|
||||||
data: function () {
|
data() {
|
||||||
return {
|
return {
|
||||||
name: "",
|
name: "",
|
||||||
};
|
};
|
||||||
|
@ -56,37 +55,48 @@ export default {
|
||||||
this.name = this.oldName();
|
this.name = this.oldName();
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapState(["req", "selected", "selectedCount"]),
|
req() {
|
||||||
...mapGetters(["isListing"]),
|
return state.req;
|
||||||
|
},
|
||||||
|
selected() {
|
||||||
|
return state.selected;
|
||||||
|
},
|
||||||
|
selectedCount() {
|
||||||
|
return state.selectedCount;
|
||||||
|
},
|
||||||
|
isListing() {
|
||||||
|
return getters.isListing();
|
||||||
|
},
|
||||||
|
closeHovers() {
|
||||||
|
return mutations.closeHovers;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
cancel: function () {
|
cancel() {
|
||||||
this.$store.commit("closeHovers");
|
mutations.closeHovers();
|
||||||
},
|
},
|
||||||
oldName: function () {
|
oldName() {
|
||||||
if (!this.isListing) {
|
if (!this.isListing) {
|
||||||
return this.req.name;
|
return state.req.name;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.selectedCount === 0 || this.selectedCount > 1) {
|
if (getters.selectedCount() === 0 || getters.selectedCount() > 1) {
|
||||||
// This shouldn't happen.
|
|
||||||
return;
|
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 oldLink = "";
|
||||||
let newLink = "";
|
let newLink = "";
|
||||||
|
|
||||||
if (!this.isListing) {
|
if (!this.isListing) {
|
||||||
oldLink = this.req.url;
|
oldLink = state.req.url;
|
||||||
} else {
|
} else {
|
||||||
oldLink = this.req.items[this.selected[0]].url;
|
oldLink = state.req.items[this.selected[0]].url;
|
||||||
}
|
}
|
||||||
|
|
||||||
newLink =
|
newLink = url.removeLastDir(oldLink) + "/" + encodeURIComponent(this.name);
|
||||||
url.removeLastDir(oldLink) + "/" + encodeURIComponent(this.name);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await api.move([{ from: oldLink, to: newLink }]);
|
await api.move([{ from: oldLink, to: newLink }]);
|
||||||
|
@ -95,12 +105,12 @@ export default {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.$store.commit("setReload", true);
|
mutations.setReload(true);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.$showError(e);
|
showError(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.$store.commit("closeHovers");
|
mutations.closeHovers();
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -28,12 +28,15 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { mapGetters } from "vuex";
|
import { getters } from "@/store"; // Import your custom store
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "replace",
|
name: "replace",
|
||||||
computed: mapGetters(["currentPrompt"]),
|
computed: {
|
||||||
|
currentPrompt() {
|
||||||
|
return getters.currentPrompt(); // Access the getter directly from the store
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -38,9 +38,14 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { mapGetters } from "vuex";
|
import { getters } from "@/store"; // Import your custom store
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "replace-rename",
|
name: "replace-rename",
|
||||||
computed: mapGetters(["currentPrompt"]),
|
computed: {
|
||||||
|
currentPrompt() {
|
||||||
|
return getters.currentPrompt(); // Access the getter directly from the store
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -58,7 +58,7 @@
|
||||||
<div class="card-action">
|
<div class="card-action">
|
||||||
<button
|
<button
|
||||||
class="button button--flat button--grey"
|
class="button button--flat button--grey"
|
||||||
@click="$store.commit('closeHovers')"
|
@click="closeHovers"
|
||||||
:aria-label="$t('buttons.close')"
|
:aria-label="$t('buttons.close')"
|
||||||
:title="$t('buttons.close')"
|
:title="$t('buttons.close')"
|
||||||
>
|
>
|
||||||
|
@ -119,16 +119,16 @@
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<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 { share as api, pub as pub_api } from "@/api";
|
||||||
import moment from "moment";
|
import { fromNow } from "@/utils/moment";
|
||||||
import Clipboard from "clipboard";
|
import Clipboard from "clipboard";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "share",
|
name: "share",
|
||||||
data: function () {
|
data() {
|
||||||
return {
|
return {
|
||||||
time: "",
|
time: "",
|
||||||
unit: "hours",
|
unit: "hours",
|
||||||
|
@ -139,22 +139,35 @@ export default {
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapState(["req", "selected", "selectedCount"]),
|
closeHovers() {
|
||||||
...mapGetters(["isListing", "selectedCount"]),
|
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() {
|
url() {
|
||||||
if (!this.isListing) {
|
if (!this.isListing) {
|
||||||
return this.$route.path;
|
return state.route.path;
|
||||||
}
|
}
|
||||||
if (this.selectedCount != 1) {
|
if (getters.selectedCount() !== 1) {
|
||||||
// selecting current view image
|
// 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() {
|
getContext() {
|
||||||
let path = this.$route.path.replace("/files/", "./");
|
let path = state.route.path.replace("/files/", "./");
|
||||||
if (this.selectedCount == 1) {
|
if (getters.selectedCount() === 1) {
|
||||||
path = path + this.req.items[this.selected[0]].name;
|
path = path + state.req.items[this.selected[0]].name;
|
||||||
}
|
}
|
||||||
return path;
|
return path;
|
||||||
},
|
},
|
||||||
|
@ -165,25 +178,25 @@ export default {
|
||||||
this.links = links;
|
this.links = links;
|
||||||
this.sort();
|
this.sort();
|
||||||
|
|
||||||
if (this.links.length == 0) {
|
if (this.links.length === 0) {
|
||||||
this.listing = false;
|
this.listing = false;
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.$showError(e);
|
showError(e);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.clip = new Clipboard(".copy-clipboard");
|
this.clip = new Clipboard(".copy-clipboard");
|
||||||
this.clip.on("success", () => {
|
this.clip.on("success", () => {
|
||||||
this.$showSuccess(this.$t("success.linkCopied"));
|
showSuccess(this.$t("success.linkCopied"));
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
beforeUnmount() {
|
beforeUnmount() {
|
||||||
this.clip.destroy();
|
this.clip.destroy();
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
submit: async function () {
|
async submit() {
|
||||||
let isPermanent = !this.time || this.time == 0;
|
let isPermanent = !this.time || this.time === 0;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
let res = null;
|
let res = null;
|
||||||
|
@ -203,30 +216,30 @@ export default {
|
||||||
|
|
||||||
this.listing = true;
|
this.listing = true;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.$showError(e);
|
showError(e);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
deleteLink: async function (event, link) {
|
async deleteLink(event, link) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
try {
|
try {
|
||||||
await api.remove(link.hash);
|
await api.remove(link.hash);
|
||||||
this.links = this.links.filter((item) => item.hash !== 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;
|
this.listing = false;
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.$showError(e);
|
showError(e);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
humanTime(time) {
|
humanTime(time) {
|
||||||
return moment(time * 1000).fromNow();
|
return fromNow(time, state.user.locale);
|
||||||
},
|
},
|
||||||
buildLink(share) {
|
buildLink(share) {
|
||||||
return api.getShareURL(share);
|
return api.getShareURL(share);
|
||||||
},
|
},
|
||||||
hasDownloadLink() {
|
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) {
|
buildDownloadLink(share) {
|
||||||
return pub_api.getDownloadURL(share);
|
return pub_api.getDownloadURL(share);
|
||||||
|
@ -239,8 +252,9 @@ export default {
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
switchListing() {
|
switchListing() {
|
||||||
if (this.links.length == 0 && !this.listing) {
|
if (this.links.length === 0 && !this.listing) {
|
||||||
this.$store.commit("closeHovers");
|
// Access the store directly if needed
|
||||||
|
mutations.closeHovers();
|
||||||
}
|
}
|
||||||
|
|
||||||
this.listing = !this.listing;
|
this.listing = !this.listing;
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="card-action">
|
<div class="card-action">
|
||||||
<button
|
<button
|
||||||
@click="$store.commit('closeHovers')"
|
@click="closeHovers"
|
||||||
class="button button--flat button--grey"
|
class="button button--flat button--grey"
|
||||||
:aria-label="$t('buttons.cancel')"
|
:aria-label="$t('buttons.cancel')"
|
||||||
:title="$t('buttons.cancel')"
|
:title="$t('buttons.cancel')"
|
||||||
|
@ -23,16 +23,21 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { mapGetters } from "vuex";
|
import { getters } from "@/store"; // Import your custom store
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "share-delete",
|
name: "share-delete",
|
||||||
computed: {
|
computed: {
|
||||||
...mapGetters(["currentPrompt"]),
|
closeHovers() {
|
||||||
|
return mutations.closeHovers();
|
||||||
|
},
|
||||||
|
currentPrompt() {
|
||||||
|
return getters.currentPrompt();
|
||||||
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
submit: function () {
|
submit() {
|
||||||
this.currentPrompt?.confirm();
|
this.currentPrompt?.confirm();
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -29,9 +29,7 @@
|
||||||
:data-type="file.type"
|
:data-type="file.type"
|
||||||
:aria-label="file.name"
|
:aria-label="file.name"
|
||||||
>
|
>
|
||||||
<div class="file-name">
|
<div class="file-name"><i class="material-icons"></i> {{ file.name }}</div>
|
||||||
<i class="material-icons"></i> {{ file.name }}
|
|
||||||
</div>
|
|
||||||
<div class="file-progress">
|
<div class="file-progress">
|
||||||
<div v-bind:style="{ width: file.progress + '%' }"></div>
|
<div v-bind:style="{ width: file.progress + '%' }"></div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -40,22 +38,26 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { mapGetters } from "vuex";
|
import { getters } from "@/store"; // Import your custom store
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "uploadFiles",
|
name: "uploadFiles",
|
||||||
data: function () {
|
data() {
|
||||||
return {
|
return {
|
||||||
open: false,
|
open: false,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
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: {
|
methods: {
|
||||||
toggle: function () {
|
toggle() {
|
||||||
this.open = !this.open;
|
this.open = !this.open;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,17 +1,24 @@
|
||||||
<template>
|
<template>
|
||||||
<select v-on:change="change" :value="locale">
|
<select v-on:change="change" :value="locale">
|
||||||
<option v-for="(language, value) in locales" :key="value" :value="value">
|
<option v-for="(value, label) in locales" :key="label" :value="label">
|
||||||
{{ $t("languages." + language) }}
|
{{ $t("languages." + label) }}
|
||||||
</option>
|
</option>
|
||||||
</select>
|
</select>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script lang="ts">
|
||||||
export default {
|
import { defineComponent } from "vue";
|
||||||
name: "languages",
|
|
||||||
props: ["locale"],
|
export default defineComponent({
|
||||||
|
name: "Languages",
|
||||||
|
props: {
|
||||||
|
locale: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
data() {
|
data() {
|
||||||
let dataObj = {
|
return {
|
||||||
locales: {
|
locales: {
|
||||||
he: "he",
|
he: "he",
|
||||||
hu: "hu",
|
hu: "hu",
|
||||||
|
@ -38,18 +45,12 @@ export default {
|
||||||
"zh-tw": "zhTW",
|
"zh-tw": "zhTW",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
Object.defineProperty(dataObj, "locales", {
|
|
||||||
configurable: false,
|
|
||||||
writable: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
return dataObj;
|
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
change(event) {
|
change(event: Event) {
|
||||||
this.$emit("update:locale", event.target.value);
|
const target = event.target as HTMLSelectElement;
|
||||||
|
this.$emit("update:locale", target.value);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -67,10 +67,10 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import Languages from "./Languages";
|
import Languages from "./Languages.vue";
|
||||||
import Rules from "./Rules";
|
import Rules from "./Rules.vue";
|
||||||
import Permissions from "./Permissions";
|
import Permissions from "./Permissions.vue";
|
||||||
import Commands from "./Commands";
|
import Commands from "./Commands.vue";
|
||||||
import { enableExec } from "@/utils/constants";
|
import { enableExec } from "@/utils/constants";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
|
|
@ -195,14 +195,13 @@ body.rtl .breadcrumbs a {
|
||||||
bottom: 1em;
|
bottom: 1em;
|
||||||
left: 50%;
|
left: 50%;
|
||||||
transform: translateX(-50%);
|
transform: translateX(-50%);
|
||||||
display: -ms-flexbox;
|
|
||||||
-ms-flex-align: center;
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
background: #fff;
|
background: #fff;
|
||||||
width: 95%;
|
|
||||||
max-width: 30em;
|
max-width: 30em;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
border-radius: 1em;
|
border-radius: 1em;
|
||||||
|
display: flex;
|
||||||
|
width: 90%;
|
||||||
}
|
}
|
||||||
|
|
||||||
button {
|
button {
|
||||||
|
@ -238,3 +237,34 @@ button {
|
||||||
#file-selection .action span {
|
#file-selection .action span {
|
||||||
display: none;
|
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 */
|
/* Listing items */
|
||||||
.dark-mode #listingView .item {
|
.dark-mode #listingView .item {
|
||||||
background: var(--surfacePrimary);
|
background: var(--surfacePrimary) !important;
|
||||||
color: var(--textPrimary);
|
color: var(--textPrimary);
|
||||||
border-color: var(--divider) !important;
|
border-color: var(--divider) !important;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
@import 'material-icons/iconfont/filled.css';
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'Roboto';
|
font-family: 'Roboto';
|
||||||
font-style: normal;
|
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;
|
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 {
|
.material-icons {
|
||||||
font-size: 1.5rem;
|
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;
|
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.list .item,
|
||||||
#listingView.compact .item {
|
#listingView.compact .item {
|
||||||
max-width: 100%;
|
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;
|
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 {
|
#listingView .item div:first-of-type {
|
||||||
width: 5em;
|
width: 5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
#listingView .item div:last-of-type {
|
#listingView .item div:last-of-type {
|
||||||
width: calc(100% - 5vw);
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
#listingView.gallery .item div:first-of-type {
|
#listingView.gallery .item div:first-of-type {
|
||||||
|
@ -177,6 +171,7 @@ body.rtl #listingView {
|
||||||
border: 1px solid rgba(0, 0, 0, 0.1);
|
border: 1px solid rgba(0, 0, 0, 0.1);
|
||||||
padding: 0;
|
padding: 0;
|
||||||
border-top: 0;
|
border-top: 0;
|
||||||
|
padding-left: .5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
#listingView.compact h2 {
|
#listingView.compact h2 {
|
||||||
|
@ -197,15 +192,30 @@ body.rtl #listingView {
|
||||||
}
|
}
|
||||||
|
|
||||||
#listingView.compact .item div:last-of-type {
|
#listingView.compact .item div:last-of-type {
|
||||||
width: calc(100% - 3em);
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#listingView.compact .header .name,
|
||||||
|
#listingView.list .header .name,
|
||||||
|
#listingView.list .item .name,
|
||||||
#listingView.compact .item .name {
|
#listingView.compact .item .name {
|
||||||
width: 50%;
|
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 {
|
#listingView.compact .item .size {
|
||||||
width: 25%;
|
width: 25%;
|
||||||
}
|
}
|
||||||
|
@ -217,22 +227,23 @@ body.rtl #listingView {
|
||||||
}
|
}
|
||||||
|
|
||||||
#listingView.compact .header {
|
#listingView.compact .header {
|
||||||
display: flex !important;
|
|
||||||
background: var(--surfacePrimary);
|
|
||||||
z-index: 999;
|
z-index: 999;
|
||||||
padding: .85em;
|
padding: .85em;
|
||||||
border: 0;
|
|
||||||
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
|
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 {
|
#listingView.compact .header>div:first-child {
|
||||||
width: 0;
|
width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
#listingView.compact .header .name {
|
|
||||||
margin-right: 3em;
|
|
||||||
}
|
|
||||||
|
|
||||||
#listingView.compact .header a {
|
#listingView.compact .header a {
|
||||||
color: inherit;
|
color: inherit;
|
||||||
}
|
}
|
||||||
|
@ -245,10 +256,6 @@ body.rtl #listingView {
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
#listingView.compact .header .name {
|
|
||||||
margin-right: 3em;
|
|
||||||
}
|
|
||||||
|
|
||||||
#listingView.compact .header span {
|
#listingView.compact .header span {
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
|
@ -302,22 +309,14 @@ body.rtl #listingView {
|
||||||
}
|
}
|
||||||
|
|
||||||
#listingView.list .item div:last-of-type {
|
#listingView.list .item div:last-of-type {
|
||||||
width: calc(100% - 3em);
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
#listingView.list .item .name {
|
|
||||||
width: 50%;
|
|
||||||
}
|
|
||||||
|
|
||||||
#listingView.list .item .size {
|
|
||||||
width: 25%;
|
|
||||||
}
|
|
||||||
|
|
||||||
#listingView .header {
|
#listingView .header {
|
||||||
display: none !important;
|
display: none;
|
||||||
background-color: #ccc;
|
background: var(--surfacePrimary);
|
||||||
|
border-radius: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
#listingView.list .header i {
|
#listingView.list .header i {
|
||||||
|
@ -329,14 +328,14 @@ body.rtl #listingView {
|
||||||
#listingView.compact .header,
|
#listingView.compact .header,
|
||||||
#listingView.list .header {
|
#listingView.list .header {
|
||||||
display: flex !important;
|
display: flex !important;
|
||||||
background: white;
|
|
||||||
border-top-left-radius: 1em;
|
border-top-left-radius: 1em;
|
||||||
border-top-right-radius: 1em;
|
border-top-right-radius: 1em;
|
||||||
z-index: 999;
|
z-index: 999;
|
||||||
padding: .85em;
|
padding: .85em;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
border: 0;
|
}
|
||||||
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
|
#listingView.compact .header {
|
||||||
|
|
||||||
}
|
}
|
||||||
#listingView.list .item:first-child {
|
#listingView.list .item:first-child {
|
||||||
margin-top: .5em;
|
margin-top: .5em;
|
||||||
|
@ -344,35 +343,36 @@ body.rtl #listingView {
|
||||||
border-top-right-radius: 1em;
|
border-top-right-radius: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
#listingView.list .item:last-child {
|
#listingView.compact .lastGroup > div:last-child {
|
||||||
margin-bottom: .5em;
|
|
||||||
border-bottom-left-radius: 1em;
|
border-bottom-left-radius: 1em;
|
||||||
border-bottom-right-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 {
|
#listingView.list .item:last-child {
|
||||||
margin-right: 3em;
|
margin-bottom: .5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
#listingView > * > .header > div {
|
||||||
|
display:flex;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
#listingView.list .header {
|
||||||
|
margin-bottom: .5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
#listingView.list .header a {
|
#listingView.list .header a {
|
||||||
color: inherit;
|
color: inherit;
|
||||||
}
|
}
|
||||||
|
|
||||||
#listingView.list .header>div:first-child {
|
|
||||||
width: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
#listingView.list .name {
|
#listingView.list .name {
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
#listingView.list .header .name {
|
|
||||||
margin-right: 3em;
|
|
||||||
}
|
|
||||||
|
|
||||||
#listingView.list .header span {
|
#listingView.list .header span {
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,4 @@
|
||||||
@import "~normalize.css/normalize.css";
|
@import "normalize.css/normalize.css";
|
||||||
@import "~noty/lib/noty.css";
|
|
||||||
@import "~noty/lib/themes/mint.css";
|
|
||||||
@import "./_variables.css";
|
@import "./_variables.css";
|
||||||
@import "./_buttons.css";
|
@import "./_buttons.css";
|
||||||
@import "./_inputs.css";
|
@import "./_inputs.css";
|
||||||
|
@ -13,6 +11,7 @@
|
||||||
@import "./upload-files.css";
|
@import "./upload-files.css";
|
||||||
@import "./dashboard.css";
|
@import "./dashboard.css";
|
||||||
@import "./login.css";
|
@import "./login.css";
|
||||||
|
@import './mobile.css';
|
||||||
|
|
||||||
.link {
|
.link {
|
||||||
color: var(--blue);
|
color: var(--blue);
|
||||||
|
@ -168,7 +167,9 @@ main .spinner .bounce2 {
|
||||||
|
|
||||||
#previewer .preview {
|
#previewer .preview {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
height: calc(100vh - 4em);
|
max-height: 100%;
|
||||||
|
max-width: 100%;
|
||||||
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
#previewer .preview pre {
|
#previewer .preview pre {
|
||||||
|
@ -385,8 +386,6 @@ body.rtl .breadcrumbs .chevron {
|
||||||
margin-left: .5rem;
|
margin-left: .5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
@import './mobile.css';
|
|
||||||
|
|
||||||
/* * * * * * * * * * * * * * * *
|
/* * * * * * * * * * * * * * * *
|
||||||
* RTL overrides *
|
* 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.",
|
"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.",
|
"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",
|
"globalSettings": "Ajustes globales",
|
||||||
"hideDotfiles": "",
|
"hideDotfiles": "Ocultar archivos empezados por punto",
|
||||||
"insertPath": "Introduce la ruta",
|
"insertPath": "Introduce la ruta",
|
||||||
"insertRegex": "Introducir expresión regular",
|
"insertRegex": "Introducir expresión regular",
|
||||||
"instanceName": "Nombre de la instancia",
|
"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 = {
|
import { state } from "./state.js";
|
||||||
isLogged: (state) => state.user !== null,
|
|
||||||
isFiles: (state) => !state.loading && state.route.name === "Files",
|
export const getters = {
|
||||||
isListing: (state, getters) => getters.isFiles && state.req.isDir,
|
isDarkMode: () => {
|
||||||
selectedCount: (state) => state.selected.length,
|
if (state.user == null) {
|
||||||
progress: (state) => {
|
return true;
|
||||||
if (state.upload.progress.length == 0) {
|
}
|
||||||
|
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;
|
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 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);
|
return Math.ceil((sum / totalSize) * 100);
|
||||||
},
|
},
|
||||||
filesInUploadCount: (state) => {
|
|
||||||
let total =
|
filesInUploadCount: () => {
|
||||||
Object.keys(state.upload.uploads).length + state.upload.queue.length;
|
// Ensure state.upload.uploads is an object and state.upload.queue is an array
|
||||||
return total;
|
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
|
currentPrompt: () => {
|
||||||
? state.prompts[state.prompts.length - 1]
|
// Ensure state.prompts is an array
|
||||||
: null;
|
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 = [];
|
let files = [];
|
||||||
|
|
||||||
for (let index in state.upload.uploads) {
|
for (let index in state.upload.uploads) {
|
||||||
|
@ -34,11 +99,11 @@ const getters = {
|
||||||
let id = upload.id;
|
let id = upload.id;
|
||||||
let type = upload.type;
|
let type = upload.type;
|
||||||
let name = upload.file.name;
|
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 isDir = upload.file.isDir;
|
||||||
let progress = isDir
|
let progress = isDir
|
||||||
? 100
|
? 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({
|
files.push({
|
||||||
id,
|
id,
|
||||||
|
@ -52,5 +117,3 @@ const getters = {
|
||||||
return files.sort((a, b) => a.progress - b.progress);
|
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 * 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 = {
|
export const mutations = {
|
||||||
closeHovers: (state) => {
|
setUsage: (value) => {
|
||||||
|
state.usage = value;
|
||||||
|
emitStateChanged();
|
||||||
|
},
|
||||||
|
closeHovers: () => {
|
||||||
state.prompts = [];
|
state.prompts = [];
|
||||||
|
emitStateChanged();
|
||||||
},
|
},
|
||||||
toggleShell: (state) => {
|
toggleShell: () => {
|
||||||
state.showShell = !state.showShell;
|
state.showShell = !state.showShell;
|
||||||
|
emitStateChanged();
|
||||||
},
|
},
|
||||||
showHover: (state, value) => {
|
showHover: (value) => {
|
||||||
if (typeof value !== "object") {
|
if (typeof value === "object") {
|
||||||
state.prompts.push({
|
state.prompts.push({
|
||||||
prompt: value,
|
name: value?.name,
|
||||||
confirm: null,
|
|
||||||
action: null,
|
|
||||||
props: null,
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
state.prompts.push({
|
|
||||||
prompt: value.prompt, // Should not be null
|
|
||||||
confirm: value?.confirm,
|
confirm: value?.confirm,
|
||||||
action: value?.action,
|
action: value?.action,
|
||||||
props: value?.props,
|
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");
|
state.prompts.push("error");
|
||||||
|
emitStateChanged();
|
||||||
},
|
},
|
||||||
showSuccess: (state) => {
|
setLoading: (value) => {
|
||||||
state.prompts.push("success");
|
|
||||||
},
|
|
||||||
setLoading: (state, value) => {
|
|
||||||
state.loading = value;
|
state.loading = value;
|
||||||
|
emitStateChanged();
|
||||||
},
|
},
|
||||||
setReload: (state, value) => {
|
setReload: (value) => {
|
||||||
state.reload = value;
|
state.reload = value;
|
||||||
|
emitStateChanged();
|
||||||
},
|
},
|
||||||
setUser: (state, value) => {
|
setUser: (value) => {
|
||||||
if (value === null) {
|
if (value === null) {
|
||||||
state.user = null;
|
state.user = null;
|
||||||
|
emitStateChanged();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let locale = value.locale;
|
let locale = value.locale;
|
||||||
|
|
||||||
if (locale === "") {
|
if (locale === "") {
|
||||||
locale = i18n.detectLocale();
|
locale = i18n.detectLocale();
|
||||||
}
|
}
|
||||||
|
i18n.setLocale(locale);
|
||||||
moment.locale(locale);
|
|
||||||
i18n.default.locale = locale;
|
i18n.default.locale = locale;
|
||||||
state.user = value;
|
state.user = value;
|
||||||
|
emitStateChanged();
|
||||||
},
|
},
|
||||||
setJWT: (state, value) => (state.jwt = value),
|
setJWT: (value) => {
|
||||||
setSession: (state, value) => (state.sessionId = value),
|
state.jwt = value;
|
||||||
multiple: (state, value) => (state.multiple = value),
|
emitStateChanged();
|
||||||
addSelected: (state, value) => state.selected.push(value),
|
},
|
||||||
removeSelected: (state, value) => {
|
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);
|
let i = state.selected.indexOf(value);
|
||||||
if (i === -1) return;
|
if (i === -1) return;
|
||||||
state.selected.splice(i, 1);
|
state.selected.splice(i, 1);
|
||||||
|
emitStateChanged();
|
||||||
},
|
},
|
||||||
resetSelected: (state) => {
|
resetSelected: () => {
|
||||||
state.selected = [];
|
state.selected = [];
|
||||||
|
mutations.setMultiple(false);
|
||||||
|
emitStateChanged();
|
||||||
},
|
},
|
||||||
updateUser: (state, value) => {
|
updateUser: (value) => {
|
||||||
if (typeof value !== "object") return;
|
if (typeof value !== "object") return;
|
||||||
if (state.user === null) {
|
if (state.user === null) {
|
||||||
state.user = {};
|
state.user = {};
|
||||||
}
|
}
|
||||||
for (let field in value) {
|
for (let field in value) {
|
||||||
if (field === "locale") {
|
if (field === "locale") {
|
||||||
moment.locale(value[field]);
|
i18n.setLocale(value[field]);
|
||||||
i18n.default.locale = value[field];
|
|
||||||
}
|
}
|
||||||
state.user[field] = value[field];
|
state.user[field] = value[field];
|
||||||
}
|
}
|
||||||
|
emitStateChanged();
|
||||||
},
|
},
|
||||||
updateRequest: (state, value) => {
|
updateRequest: (value) => {
|
||||||
const selectedItems = state.selected.map((i) => state.req.items[i]);
|
const selectedItems = state.selected.map((i) => state.req.items[i]);
|
||||||
state.oldReq = state.req;
|
state.oldReq = state.req;
|
||||||
state.req = value;
|
state.req = value;
|
||||||
|
@ -90,14 +112,20 @@ const mutations = {
|
||||||
.filter((item) => selectedItems.some((rItem) => rItem.url === item.url))
|
.filter((item) => selectedItems.some((rItem) => rItem.url === item.url))
|
||||||
.map((item) => item.index);
|
.map((item) => item.index);
|
||||||
},
|
},
|
||||||
// Inside your mutations object
|
replaceRequest: (value) => {
|
||||||
updateListingSortConfig(state, { field, asc }) {
|
state.req = value;
|
||||||
|
emitStateChanged();
|
||||||
|
},
|
||||||
|
setRoute: (value) => {
|
||||||
|
state.route = value;
|
||||||
|
emitStateChanged();
|
||||||
|
},
|
||||||
|
updateListingSortConfig: ({ field, asc }) => {
|
||||||
state.req.sorting.by = field;
|
state.req.sorting.by = field;
|
||||||
state.req.sorting.asc = asc;
|
state.req.sorting.asc = asc;
|
||||||
|
emitStateChanged();
|
||||||
},
|
},
|
||||||
|
updateListingItems: () => {
|
||||||
updateListingItems(state) {
|
|
||||||
// Sort the items array based on the sorting settings
|
|
||||||
state.req.items.sort((a, b) => {
|
state.req.items.sort((a, b) => {
|
||||||
const valueA = a[state.req.sorting.by];
|
const valueA = a[state.req.sorting.by];
|
||||||
const valueB = b[state.req.sorting.by];
|
const valueB = b[state.req.sorting.by];
|
||||||
|
@ -107,17 +135,18 @@ const mutations = {
|
||||||
return valueA < valueB ? 1 : -1;
|
return valueA < valueB ? 1 : -1;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
emitStateChanged();
|
||||||
},
|
},
|
||||||
|
updateClipboard: (value) => {
|
||||||
updateClipboard: (state, value) => {
|
|
||||||
state.clipboard.key = value.key;
|
state.clipboard.key = value.key;
|
||||||
state.clipboard.items = value.items;
|
state.clipboard.items = value.items;
|
||||||
state.clipboard.path = value.path;
|
state.clipboard.path = value.path;
|
||||||
|
emitStateChanged();
|
||||||
},
|
},
|
||||||
resetClipboard: (state) => {
|
resetClipboard: () => {
|
||||||
state.clipboard.key = "";
|
state.clipboard.key = "";
|
||||||
state.clipboard.items = [];
|
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 router from "@/router";
|
||||||
import { baseURL } from "@/utils/constants";
|
import { baseURL } from "@/utils/constants";
|
||||||
|
|
||||||
|
@ -8,13 +8,12 @@ export function parseToken(token) {
|
||||||
if (parts.length !== 3) {
|
if (parts.length !== 3) {
|
||||||
throw new Error("token malformed");
|
throw new Error("token malformed");
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = JSON.parse(atob(parts[1]));
|
const data = JSON.parse(atob(parts[1]));
|
||||||
document.cookie = `auth=${token}; path=/`;
|
document.cookie = `auth=${token}; path=/`;
|
||||||
localStorage.setItem("jwt", token);
|
localStorage.setItem("jwt", token);
|
||||||
store.commit("setJWT", token);
|
mutations.setJWT(token);
|
||||||
store.commit("setSession", generateRandomCode(8));
|
mutations.setSession(generateRandomCode(8));
|
||||||
store.commit("setUser", data.user);
|
mutations.setUser(data.user);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function validateLogin() {
|
export async function validateLogin() {
|
||||||
|
@ -29,7 +28,6 @@ export async function validateLogin() {
|
||||||
|
|
||||||
export async function login(username, password, recaptcha) {
|
export async function login(username, password, recaptcha) {
|
||||||
const data = { username, password, recaptcha };
|
const data = { username, password, recaptcha };
|
||||||
|
|
||||||
const res = await fetch(`${baseURL}/api/login`, {
|
const res = await fetch(`${baseURL}/api/login`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
|
@ -53,11 +51,9 @@ export async function renew(jwt) {
|
||||||
"X-Auth": jwt,
|
"X-Auth": jwt,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const body = await res.text();
|
const body = await res.text();
|
||||||
|
|
||||||
if (res.status === 200) {
|
if (res.status === 200) {
|
||||||
store.commit("setSession", generateRandomCode(8));
|
mutations.setSession(generateRandomCode(8));
|
||||||
parseToken(body);
|
parseToken(body);
|
||||||
} else {
|
} else {
|
||||||
throw new Error(body);
|
throw new Error(body);
|
||||||
|
@ -67,7 +63,6 @@ export async function renew(jwt) {
|
||||||
function generateRandomCode(length) {
|
function generateRandomCode(length) {
|
||||||
const charset = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
const charset = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
||||||
let code = '';
|
let code = '';
|
||||||
|
|
||||||
for (let i = 0; i < length; i++) {
|
for (let i = 0; i < length; i++) {
|
||||||
const randomIndex = Math.floor(Math.random() * charset.length);
|
const randomIndex = Math.floor(Math.random() * charset.length);
|
||||||
code += charset[randomIndex];
|
code += charset[randomIndex];
|
||||||
|
@ -76,9 +71,8 @@ function generateRandomCode(length) {
|
||||||
return code;
|
return code;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function signup(username, password) {
|
export async function signupLogin(username, password) {
|
||||||
const data = { username, password };
|
const data = { username, password };
|
||||||
|
|
||||||
const res = await fetch(`${baseURL}/api/signup`, {
|
const res = await fetch(`${baseURL}/api/signup`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
|
@ -94,9 +88,8 @@ export async function signup(username, password) {
|
||||||
|
|
||||||
export function logout() {
|
export function logout() {
|
||||||
document.cookie = "auth=; expires=Thu, 01 Jan 1970 00:00:01 GMT; path=/";
|
document.cookie = "auth=; expires=Thu, 01 Jan 1970 00:00:01 GMT; path=/";
|
||||||
|
mutations.setJWT("");
|
||||||
store.commit("setJWT", "");
|
mutations.setUser(null);
|
||||||
store.commit("setUser", null);
|
|
||||||
localStorage.setItem("jwt", null);
|
localStorage.setItem("jwt", null);
|
||||||
router.push({ path: "/login" });
|
router.push({ path: "/login" });
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,6 @@ function loading(button) {
|
||||||
let el = document.querySelector(`#${button}-button > i`);
|
let el = document.querySelector(`#${button}-button > i`);
|
||||||
|
|
||||||
if (el === undefined || el === null) {
|
if (el === undefined || el === null) {
|
||||||
console.log('Error getting button ' + button)
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -24,7 +23,6 @@ function done(button) {
|
||||||
let el = document.querySelector(`#${button}-button > i`);
|
let el = document.querySelector(`#${button}-button > i`);
|
||||||
|
|
||||||
if (el === undefined || el === null) {
|
if (el === undefined || el === null) {
|
||||||
console.log('Error getting button ' + button)
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -41,7 +39,6 @@ function success(button) {
|
||||||
let el = document.querySelector(`#${button}-button > i`);
|
let el = document.querySelector(`#${button}-button > i`);
|
||||||
|
|
||||||
if (el === undefined || el === null) {
|
if (el === undefined || el === null) {
|
||||||
console.log('Error getting button ' + button)
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,6 @@ const resizePreview = window.FileBrowser.ResizePreview;
|
||||||
const enableExec = window.FileBrowser.EnableExec;
|
const enableExec = window.FileBrowser.EnableExec;
|
||||||
const origin = window.location.origin;
|
const origin = window.location.origin;
|
||||||
|
|
||||||
console.log(window.FileBrowser)
|
|
||||||
export {
|
export {
|
||||||
name,
|
name,
|
||||||
disableExternal,
|
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";
|
import url from "@/utils/url";
|
||||||
|
|
||||||
export function checkConflict(files, items) {
|
export function checkConflict(files, items) {
|
||||||
|
@ -102,7 +102,7 @@ export function scanFiles(dt) {
|
||||||
|
|
||||||
export function handleFiles(files, base, overwrite = false) {
|
export function handleFiles(files, base, overwrite = false) {
|
||||||
for (let i = 0; i < files.length; i++) {
|
for (let i = 0; i < files.length; i++) {
|
||||||
let id = store.state.upload.id;
|
let id = state.upload.id;
|
||||||
let path = base;
|
let path = base;
|
||||||
let file = files[i];
|
let file = files[i];
|
||||||
|
|
||||||
|
@ -124,6 +124,6 @@ export function handleFiles(files, base, overwrite = false) {
|
||||||
...(!file.isDir && { type: file.type }),
|
...(!file.isDir && { type: file.type }),
|
||||||
};
|
};
|
||||||
|
|
||||||
store.dispatch("upload/upload", item);
|
state.dispatch("upload/upload", item);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,7 +29,18 @@ export function encodePath(str) {
|
||||||
.join("/");
|
.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 {
|
export default {
|
||||||
|
pathsMatch,
|
||||||
|
removeTrailingSlash,
|
||||||
encodeRFC5987ValueChars,
|
encodeRFC5987ValueChars,
|
||||||
removeLastDir,
|
removeLastDir,
|
||||||
encodePath,
|
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>
|
<div>
|
||||||
<breadcrumbs base="/files" />
|
<breadcrumbs base="/files" />
|
||||||
<errors v-if="error" :errorCode="error.status" />
|
<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>
|
<div v-else>
|
||||||
<h2 class="message delayed">
|
<h2 class="message delayed">
|
||||||
<div class="spinner">
|
<div class="spinner">
|
||||||
|
@ -15,20 +15,16 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { files as api } from "@/api";
|
import { files as api } from "@/api";
|
||||||
import { mapState, mapMutations } from "vuex";
|
|
||||||
|
|
||||||
import Breadcrumbs from "@/components/Breadcrumbs";
|
import Breadcrumbs from "@/components/Breadcrumbs.vue";
|
||||||
import Errors from "@/views/Errors";
|
import Errors from "@/views/Errors.vue";
|
||||||
import Preview from "@/views/files/Preview.vue";
|
import Preview from "@/views/files/Preview.vue";
|
||||||
import ListingView from "@/views/files/ListingView.vue";
|
import ListingView from "@/views/files/ListingView.vue";
|
||||||
import Editor from "@/views/files/Editor.vue";
|
import Editor from "@/views/files/Editor.vue";
|
||||||
|
import { state, mutations, getters } from "@/store";
|
||||||
function clean(path) {
|
import { pathsMatch } from "@/utils/url";
|
||||||
return path.endsWith("/") ? path.slice(0, -1) : path;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "files",
|
name: "files",
|
||||||
|
@ -39,25 +35,21 @@ export default {
|
||||||
ListingView,
|
ListingView,
|
||||||
Editor,
|
Editor,
|
||||||
},
|
},
|
||||||
data: function () {
|
data() {
|
||||||
return {
|
return {
|
||||||
error: null,
|
error: null,
|
||||||
width: window.innerWidth,
|
width: window.innerWidth,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapState(["req", "reload", "loading"]),
|
|
||||||
currentView() {
|
currentView() {
|
||||||
if (this.req.type == undefined) {
|
return getters.currentView();
|
||||||
return null;
|
},
|
||||||
}
|
currentViewLoaded() {
|
||||||
if (this.req.isDir) {
|
return getters.currentView() !== null;
|
||||||
return "listingView";
|
},
|
||||||
} else if (Object.prototype.hasOwnProperty.call(this.req, 'content')) {
|
reload() {
|
||||||
return "editor";
|
return state.reload; // Access reload from state
|
||||||
} else {
|
|
||||||
return "preview";
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
|
@ -65,8 +57,9 @@ export default {
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
$route: "fetchData",
|
$route: "fetchData",
|
||||||
reload: function (value) {
|
reload(value) {
|
||||||
if (value === true) {
|
if (value === true) {
|
||||||
|
console.log("reloading")
|
||||||
this.fetchData();
|
this.fetchData();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -78,56 +71,50 @@ export default {
|
||||||
window.removeEventListener("keydown", this.keyEvent);
|
window.removeEventListener("keydown", this.keyEvent);
|
||||||
},
|
},
|
||||||
unmounted() {
|
unmounted() {
|
||||||
if (this.$store.state.showShell) {
|
if (state.showShell) {
|
||||||
this.$store.commit("toggleShell");
|
mutations.toggleShell(); // Use mutation
|
||||||
}
|
}
|
||||||
this.$store.commit("updateRequest", {});
|
mutations.replaceRequest({}); // Use mutation
|
||||||
},
|
|
||||||
currentView(newView) {
|
|
||||||
// Commit the new value to the store
|
|
||||||
this.setCurrentValue(newView);
|
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
...mapMutations(["setLoading", "setCurrentView"]),
|
|
||||||
async fetchData() {
|
async fetchData() {
|
||||||
// Reset view information.
|
// Reset view information using mutations
|
||||||
this.$store.commit("setReload", false);
|
mutations.setReload(false);
|
||||||
this.$store.commit("resetSelected");
|
mutations.resetSelected();
|
||||||
this.$store.commit("multiple", false);
|
mutations.setMultiple(false);
|
||||||
this.$store.commit("closeHovers");
|
mutations.closeHovers();
|
||||||
|
|
||||||
// Set loading to true and reset the error.
|
// Set loading to true and reset the error.
|
||||||
this.setLoading(true);
|
mutations.setLoading(true);
|
||||||
this.error = null;
|
this.error = null;
|
||||||
|
|
||||||
let url = this.$route.path;
|
let url = state.route.path;
|
||||||
if (url === "") url = "/";
|
if (url === "") url = "/";
|
||||||
if (url[0] !== "/") url = "/" + url;
|
if (url[0] !== "/") url = "/" + url;
|
||||||
|
let data = {};
|
||||||
try {
|
try {
|
||||||
|
// Fetch initial data
|
||||||
let res = await api.fetch(url);
|
let res = await api.fetch(url);
|
||||||
|
// If not a directory, fetch content
|
||||||
if (!res.isDir) {
|
if (!res.isDir) {
|
||||||
// get content of file if possible
|
|
||||||
res = await api.fetch(url, true);
|
res = await api.fetch(url, true);
|
||||||
}
|
}
|
||||||
|
data = res;
|
||||||
if (clean(res.path) !== clean(`/${this.$route.params.pathMatch}`)) {
|
// Verify if the fetched path matches the current route
|
||||||
return;
|
if (pathsMatch(res.path, `/${state.route.params.path}`)) {
|
||||||
}
|
|
||||||
|
|
||||||
this.$store.commit("updateRequest", res);
|
|
||||||
document.title = `${res.name} - ${document.title}`;
|
document.title = `${res.name} - ${document.title}`;
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.error = e;
|
this.error = e;
|
||||||
} finally {
|
|
||||||
this.setLoading(false);
|
|
||||||
}
|
}
|
||||||
|
mutations.setLoading(false);
|
||||||
|
mutations.replaceRequest(data);
|
||||||
},
|
},
|
||||||
keyEvent(event) {
|
keyEvent(event) {
|
||||||
// F1!
|
// F1!
|
||||||
if (event.keyCode === 112) {
|
if (event.keyCode === 112) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
this.$store.commit("showHover", "help");
|
mutations.showHover("help"); // Use mutation
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -20,18 +20,21 @@
|
||||||
<prompts :class="{ 'dark-mode': isDarkMode }"></prompts>
|
<prompts :class="{ 'dark-mode': isDarkMode }"></prompts>
|
||||||
<upload-files></upload-files>
|
<upload-files></upload-files>
|
||||||
</div>
|
</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>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import editorBar from "./bars/EditorBar.vue";
|
import editorBar from "./bars/EditorBar.vue";
|
||||||
import defaultBar from "./bars/Default.vue";
|
import defaultBar from "./bars/Default.vue";
|
||||||
import listingBar from "./bars/ListingBar.vue";
|
import listingBar from "./bars/ListingBar.vue";
|
||||||
import Prompts from "@/components/prompts/Prompts";
|
import Prompts from "@/components/prompts/Prompts.vue";
|
||||||
import { mapState, mapGetters } from "vuex";
|
|
||||||
import Sidebar from "@/components/Sidebar.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 { enableExec } from "@/utils/constants";
|
||||||
import { darkMode } from "@/utils/constants";
|
import { state, getters, mutations } from "@/store";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "layout",
|
name: "layout",
|
||||||
|
@ -43,7 +46,7 @@ export default {
|
||||||
Prompts,
|
Prompts,
|
||||||
UploadFiles,
|
UploadFiles,
|
||||||
},
|
},
|
||||||
data: function () {
|
data() {
|
||||||
return {
|
return {
|
||||||
showContexts: true,
|
showContexts: true,
|
||||||
dragCounter: 0,
|
dragCounter: 0,
|
||||||
|
@ -52,50 +55,56 @@ export default {
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapGetters([
|
closePopUp() {
|
||||||
"isLogged",
|
return closePopUp;
|
||||||
"progress",
|
},
|
||||||
"isListing",
|
progress() {
|
||||||
"currentPrompt",
|
return getters.progress(); // Access getter directly from the store
|
||||||
"currentPromptName",
|
},
|
||||||
]),
|
isListing() {
|
||||||
...mapState(["req", "user", "state"]),
|
return getters.isListing(); // Access getter directly from the store
|
||||||
showOverlay: function () {
|
},
|
||||||
return this.currentPrompt !== null && this.currentPrompt.prompt !== "more";
|
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() {
|
isDarkMode() {
|
||||||
return this.user && Object.prototype.hasOwnProperty.call(this.user, "darkMode")
|
return getters.isDarkMode();
|
||||||
? this.user.darkMode
|
},
|
||||||
: darkMode;
|
isExecEnabled() {
|
||||||
|
return enableExec;
|
||||||
},
|
},
|
||||||
isExecEnabled: () => enableExec,
|
|
||||||
currentView() {
|
currentView() {
|
||||||
if (this.req.type == undefined) {
|
return getters.currentView();
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if (this.req.isDir) {
|
|
||||||
return "listingView";
|
|
||||||
} else if (Object.prototype.hasOwnProperty.call(this.req, 'content')) {
|
|
||||||
return "editor";
|
|
||||||
} else {
|
|
||||||
return "preview";
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
$route: function () {
|
$route() {
|
||||||
this.$store.commit("resetSelected");
|
mutations.resetSelected();
|
||||||
this.$store.commit("multiple", false);
|
mutations.setMultiple(false);
|
||||||
if (this.currentPrompt?.prompt !== "success") this.$store.commit("closeHovers");
|
if (getters.currentPromptName() !== "success") {
|
||||||
|
mutations.closeHovers();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
resetPrompts() {
|
resetPrompts() {
|
||||||
this.$store.commit("closeHovers");
|
mutations.closeHovers();
|
||||||
},
|
},
|
||||||
getTitle() {
|
getTitle() {
|
||||||
let title = "Title";
|
let title = "Title";
|
||||||
if (this.$route.path.startsWith("/settings/")) {
|
if (state.route.path.startsWith("/settings/")) {
|
||||||
title = "Settings";
|
title = "Settings";
|
||||||
}
|
}
|
||||||
return title;
|
return title;
|
||||||
|
@ -114,7 +123,7 @@ main::-webkit-scrollbar {
|
||||||
}
|
}
|
||||||
/* Use the class .dark-mode to apply styles conditionally */
|
/* Use the class .dark-mode to apply styles conditionally */
|
||||||
.dark-mode {
|
.dark-mode {
|
||||||
background: var(--background);
|
background: var(--background) !important;
|
||||||
color: var(--textPrimary);
|
color: var(--textPrimary);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue