Compare commits
28 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
17f1e08a58 | ||
|
|
ffc850454e | ||
|
|
13814e1119 | ||
|
|
4e9e312984 | ||
|
|
ce3b407c51 | ||
|
|
fb5d099f85 | ||
|
|
1ace579a55 | ||
|
|
ac7b49c148 | ||
|
|
9d44932dba | ||
|
|
0d973d3aad | ||
|
|
cacc0999e9 | ||
|
|
42d1b6f3ae | ||
|
|
bb10c3dfa9 | ||
|
|
ce76aa23a6 | ||
|
|
94b635daf8 | ||
|
|
31871aaa4b | ||
|
|
9d465663db | ||
|
|
70081f2647 | ||
|
|
fa9d2f266f | ||
|
|
8fcfb502ca | ||
|
|
0bab2aba9e | ||
|
|
38951d950f | ||
|
|
dda8fdbcb2 | ||
|
|
bf3ba65782 | ||
|
|
f35b7c9d9d | ||
|
|
e9506c3eae | ||
|
|
bb4465548b | ||
|
|
cf8b5ca768 |
@@ -1,4 +1,4 @@
|
|||||||
name: main
|
name: Continuous Integration
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
@@ -9,8 +9,8 @@ on:
|
|||||||
pull_request:
|
pull_request:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
# linters
|
|
||||||
lint-frontend:
|
lint-frontend:
|
||||||
|
name: Lint Frontend
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v5
|
- uses: actions/checkout@v5
|
||||||
@@ -22,26 +22,43 @@ jobs:
|
|||||||
node-version: "24.x"
|
node-version: "24.x"
|
||||||
cache: "pnpm"
|
cache: "pnpm"
|
||||||
cache-dependency-path: "frontend/pnpm-lock.yaml"
|
cache-dependency-path: "frontend/pnpm-lock.yaml"
|
||||||
- run: make lint-frontend
|
- working-directory: frontend
|
||||||
|
run: |
|
||||||
|
pnpm install --frozen-lockfile
|
||||||
|
pnpm run lint
|
||||||
|
|
||||||
lint-backend:
|
lint-backend:
|
||||||
|
name: Lint Backend
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v5
|
- uses: actions/checkout@v5
|
||||||
- uses: actions/setup-go@v6
|
- uses: actions/setup-go@v6
|
||||||
with:
|
with:
|
||||||
go-version: '1.25'
|
go-version: "1.25.x"
|
||||||
- run: make lint-backend
|
- uses: golangci/golangci-lint-action@v9
|
||||||
lint:
|
with:
|
||||||
runs-on: ubuntu-latest
|
version: "latest"
|
||||||
needs: [lint-frontend, lint-backend]
|
|
||||||
steps:
|
|
||||||
- run: echo "done"
|
|
||||||
|
|
||||||
# tests
|
test:
|
||||||
test-frontend:
|
name: Test
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v5
|
- uses: actions/checkout@v5
|
||||||
|
- uses: actions/setup-go@v6
|
||||||
|
with:
|
||||||
|
go-version: "1.25.x"
|
||||||
|
- run: go test --race ./...
|
||||||
|
|
||||||
|
build:
|
||||||
|
name: Build
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v5
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
- uses: actions/setup-go@v6
|
||||||
|
with:
|
||||||
|
go-version: '1.25'
|
||||||
- uses: pnpm/action-setup@v4
|
- uses: pnpm/action-setup@v4
|
||||||
with:
|
with:
|
||||||
package_json_file: "frontend/package.json"
|
package_json_file: "frontend/package.json"
|
||||||
@@ -50,26 +67,15 @@ jobs:
|
|||||||
node-version: "24.x"
|
node-version: "24.x"
|
||||||
cache: "pnpm"
|
cache: "pnpm"
|
||||||
cache-dependency-path: "frontend/pnpm-lock.yaml"
|
cache-dependency-path: "frontend/pnpm-lock.yaml"
|
||||||
- run: make test-frontend
|
- name: Install Task
|
||||||
test-backend:
|
uses: go-task/setup-task@v1
|
||||||
runs-on: ubuntu-latest
|
- run: task build
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v5
|
|
||||||
- uses: actions/setup-go@v6
|
|
||||||
with:
|
|
||||||
go-version: '1.25'
|
|
||||||
- run: make test-backend
|
|
||||||
test:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
needs: [test-frontend, test-backend]
|
|
||||||
steps:
|
|
||||||
- run: echo "done"
|
|
||||||
|
|
||||||
# release
|
|
||||||
release:
|
release:
|
||||||
|
name: Release
|
||||||
|
needs: ["lint-frontend", "lint-backend", "test", "build"]
|
||||||
|
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: [lint, test]
|
|
||||||
if: startsWith(github.event.ref, 'refs/tags/v')
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v5
|
- uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
@@ -89,8 +95,9 @@ jobs:
|
|||||||
uses: docker/setup-qemu-action@v3
|
uses: docker/setup-qemu-action@v3
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v3
|
uses: docker/setup-buildx-action@v3
|
||||||
- name: Build frontend
|
- name: Install Task
|
||||||
run: make build-frontend
|
uses: go-task/setup-task@v1
|
||||||
|
- run: task build-frontend
|
||||||
- name: Login to Docker Hub
|
- name: Login to Docker Hub
|
||||||
uses: docker/login-action@v3
|
uses: docker/login-action@v3
|
||||||
with:
|
with:
|
||||||
@@ -103,3 +110,6 @@ jobs:
|
|||||||
args: release --clean
|
args: release --clean
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GH_PAT }}
|
GITHUB_TOKEN: ${{ secrets.GH_PAT }}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,12 +1,32 @@
|
|||||||
name: Build and Deploy Site
|
name: Docs
|
||||||
|
|
||||||
on:
|
on:
|
||||||
|
pull_request:
|
||||||
|
paths:
|
||||||
|
- 'www'
|
||||||
|
- '*.md'
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
deploy:
|
build:
|
||||||
|
name: Build Docs
|
||||||
|
if: github.event_name == 'pull_request'
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v5
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
- name: Install Task
|
||||||
|
uses: go-task/setup-task@v1
|
||||||
|
- name: Build site
|
||||||
|
run: task docs
|
||||||
|
|
||||||
|
build-and-release:
|
||||||
|
if: github.event_name == 'push' && github.ref == 'refs/heads/master'
|
||||||
|
name: Build and Release Docs
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
deployments: write
|
deployments: write
|
||||||
@@ -16,13 +36,12 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v5
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v3
|
uses: docker/setup-buildx-action@v3
|
||||||
|
- name: Install Task
|
||||||
|
uses: go-task/setup-task@v1
|
||||||
- name: Build site
|
- name: Build site
|
||||||
run: make site
|
run: task docs
|
||||||
|
|
||||||
- name: Deploy to Cloudflare Pages
|
- name: Deploy to Cloudflare Pages
|
||||||
uses: cloudflare/wrangler-action@v3
|
uses: cloudflare/wrangler-action@v3
|
||||||
with:
|
with:
|
||||||
@@ -30,3 +49,4 @@ jobs:
|
|||||||
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||||
command: pages deploy www/public --project-name=${{ secrets.CLOUDFLARE_PROJECT_NAME }}
|
command: pages deploy www/public --project-name=${{ secrets.CLOUDFLARE_PROJECT_NAME }}
|
||||||
gitHubToken: ${{ secrets.GITHUB_TOKEN }}
|
gitHubToken: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
@@ -13,7 +13,7 @@ permissions:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
main:
|
main:
|
||||||
name: Validate PR title
|
name: Validate Title
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: amannn/action-semantic-pull-request@v6
|
- uses: amannn/action-semantic-pull-request@v6
|
||||||
@@ -43,4 +43,4 @@ jobs:
|
|||||||
uses: marocchino/sticky-pull-request-comment@v2
|
uses: marocchino/sticky-pull-request-comment@v2
|
||||||
with:
|
with:
|
||||||
header: pr-title-lint-error
|
header: pr-title-lint-error
|
||||||
delete: true
|
delete: true
|
||||||
20
.github/workflows/site-pr.yml
vendored
20
.github/workflows/site-pr.yml
vendored
@@ -1,20 +0,0 @@
|
|||||||
name: Build Site
|
|
||||||
|
|
||||||
on:
|
|
||||||
pull_request:
|
|
||||||
paths:
|
|
||||||
- 'www'
|
|
||||||
- '*.md'
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v5
|
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
|
||||||
uses: docker/setup-buildx-action@v3
|
|
||||||
|
|
||||||
- name: Build site
|
|
||||||
run: make site
|
|
||||||
5
.gitignore
vendored
5
.gitignore
vendored
@@ -35,10 +35,5 @@ build/
|
|||||||
/frontend/dist/*
|
/frontend/dist/*
|
||||||
!/frontend/dist/.gitkeep
|
!/frontend/dist/.gitkeep
|
||||||
|
|
||||||
# Playwright files
|
|
||||||
/frontend/test-results/
|
|
||||||
/frontend/playwright-report/
|
|
||||||
/frontend/playwright/.cache/
|
|
||||||
|
|
||||||
default.nix
|
default.nix
|
||||||
Dockerfile.dev
|
Dockerfile.dev
|
||||||
|
|||||||
125
.golangci.yml
125
.golangci.yml
@@ -1,132 +1,9 @@
|
|||||||
version: "2"
|
version: "2"
|
||||||
|
|
||||||
linters:
|
linters:
|
||||||
# inverted configuration with `default: all` and `disable` is not scalable during updates of golangci-lint
|
default: standard
|
||||||
default: none
|
|
||||||
enable:
|
|
||||||
- bodyclose
|
|
||||||
- dogsled
|
|
||||||
- dupl
|
|
||||||
- errcheck
|
|
||||||
- errorlint
|
|
||||||
- exhaustive
|
|
||||||
- funlen
|
|
||||||
- gocheckcompilerdirectives
|
|
||||||
- gochecknoinits
|
|
||||||
- gocritic
|
|
||||||
- gocyclo
|
|
||||||
- godox
|
|
||||||
- goprintffuncname
|
|
||||||
- gosec
|
|
||||||
- govet
|
|
||||||
- ineffassign
|
|
||||||
- lll
|
|
||||||
- misspell
|
|
||||||
- mnd
|
|
||||||
- nakedret
|
|
||||||
- nolintlint
|
|
||||||
- prealloc
|
|
||||||
- revive
|
|
||||||
- rowserrcheck
|
|
||||||
- staticcheck
|
|
||||||
- testifylint
|
|
||||||
- unconvert
|
|
||||||
- unparam
|
|
||||||
- unused
|
|
||||||
- whitespace
|
|
||||||
settings:
|
|
||||||
dupl:
|
|
||||||
threshold: 100
|
|
||||||
exhaustive:
|
|
||||||
default-signifies-exhaustive: false
|
|
||||||
funlen:
|
|
||||||
lines: 100
|
|
||||||
statements: 50
|
|
||||||
gocritic:
|
|
||||||
disabled-checks:
|
|
||||||
- dupImport # https://github.com/go-critic/go-critic/issues/845
|
|
||||||
- ifElseChain
|
|
||||||
- octalLiteral
|
|
||||||
- whyNoLint
|
|
||||||
- wrapperFunc
|
|
||||||
enabled-tags:
|
|
||||||
- diagnostic
|
|
||||||
- experimental
|
|
||||||
- opinionated
|
|
||||||
- performance
|
|
||||||
- style
|
|
||||||
gocyclo:
|
|
||||||
min-complexity: 15
|
|
||||||
govet:
|
|
||||||
enable:
|
|
||||||
- nilness
|
|
||||||
- shadow
|
|
||||||
lll:
|
|
||||||
line-length: 140
|
|
||||||
misspell:
|
|
||||||
locale: US
|
|
||||||
mnd:
|
|
||||||
# don't include the "operation" and "assign"
|
|
||||||
checks:
|
|
||||||
- argument
|
|
||||||
- case
|
|
||||||
- condition
|
|
||||||
- return
|
|
||||||
ignored-numbers:
|
|
||||||
- "0"
|
|
||||||
- "1"
|
|
||||||
- "2"
|
|
||||||
- "3"
|
|
||||||
- "0666"
|
|
||||||
- "0700"
|
|
||||||
- "0700"
|
|
||||||
ignored-functions:
|
|
||||||
- strings.SplitN
|
|
||||||
- make
|
|
||||||
nolintlint:
|
|
||||||
allow-unused: false # report any unused nolint directives
|
|
||||||
require-explanation: false # require an explanation for nolint directives
|
|
||||||
require-specific: true # require nolint directives to be specific about which linter is being skipped
|
|
||||||
staticcheck:
|
|
||||||
checks:
|
|
||||||
- "all"
|
|
||||||
- "-QF*"
|
|
||||||
exclusions:
|
exclusions:
|
||||||
generated: lax
|
|
||||||
presets:
|
presets:
|
||||||
- comments
|
|
||||||
- common-false-positives
|
|
||||||
- legacy
|
|
||||||
- std-error-handling
|
- std-error-handling
|
||||||
rules:
|
|
||||||
- linters:
|
|
||||||
- gochecknoinits
|
|
||||||
path: cmd/.*.go
|
|
||||||
- linters:
|
|
||||||
- dupl
|
|
||||||
- funlen
|
|
||||||
- gochecknoinits
|
|
||||||
- gocyclo
|
|
||||||
- lll
|
|
||||||
- scopelint
|
|
||||||
path: .*_test.go
|
|
||||||
- linters:
|
|
||||||
- misspell
|
|
||||||
text: "[aA]uther"
|
|
||||||
- linters:
|
|
||||||
- mnd
|
|
||||||
text: strconv.Parse
|
|
||||||
paths:
|
|
||||||
- frontend/
|
|
||||||
|
|
||||||
formatters:
|
|
||||||
enable:
|
|
||||||
- goimports
|
|
||||||
settings:
|
|
||||||
goimports:
|
|
||||||
local-prefixes:
|
|
||||||
- github.com/filebrowser/filebrowser
|
|
||||||
exclusions:
|
|
||||||
generated: lax
|
|
||||||
paths:
|
paths:
|
||||||
- frontend/
|
- frontend/
|
||||||
|
|||||||
20
CHANGELOG.md
20
CHANGELOG.md
@@ -2,13 +2,27 @@
|
|||||||
|
|
||||||
All notable changes to this project will be documented in this file. See [commit-and-tag-version](https://github.com/absolute-version/commit-and-tag-version) for commit guidelines.
|
All notable changes to this project will be documented in this file. See [commit-and-tag-version](https://github.com/absolute-version/commit-and-tag-version) for commit guidelines.
|
||||||
|
|
||||||
## [2.45.2](https://github.com/filebrowser/filebrowser/compare/v2.45.1...v2.45.2) (2025-11-13)
|
## [2.46.1](https://github.com/filebrowser/filebrowser/compare/v2.46.0...v2.46.1) (2025-11-15)
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
|
|
||||||
* **deps:** update module github.com/shirou/gopsutil/v3 to v4 ([#5536](https://github.com/filebrowser/filebrowser/issues/5536)) ([fdff7a3](https://github.com/filebrowser/filebrowser/commit/fdff7a38f4711f2b58dfdd60bebbb057bd3a478d))
|
* env key replacer and remove unused function ([#5547](https://github.com/filebrowser/filebrowser/issues/5547)) ([13814e1](https://github.com/filebrowser/filebrowser/commit/13814e11197ebd9101940883e3ca85998f86d442))
|
||||||
* **deps:** update module gopkg.in/yaml.v2 to v3 ([#5537](https://github.com/filebrowser/filebrowser/issues/5537)) ([f26a685](https://github.com/filebrowser/filebrowser/commit/f26a68587d8432b536453093f42dc255d19d10fa))
|
* remove duplicated 'hide-defaults' flag (is 'hideDefaults') ([#5548](https://github.com/filebrowser/filebrowser/issues/5548)) ([ffc8504](https://github.com/filebrowser/filebrowser/commit/ffc850454e4cb8f10b970511681d6c627340afc7))
|
||||||
|
|
||||||
|
## [2.46.0](https://github.com/filebrowser/filebrowser/compare/v2.45.3...v2.46.0) (2025-11-14)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* add 'hide-dotfiles' as command line parameter ([#3802](https://github.com/filebrowser/filebrowser/issues/3802)) ([0d973d3](https://github.com/filebrowser/filebrowser/commit/0d973d3aad70ceb88950f2cd9c297fc76e7955b1))
|
||||||
|
* add context menu ([#3343](https://github.com/filebrowser/filebrowser/issues/3343)) ([1ace579](https://github.com/filebrowser/filebrowser/commit/1ace579a553486bb15af2d11f537414156606434))
|
||||||
|
* add option to hide the login button from public-facing pages ([#3922](https://github.com/filebrowser/filebrowser/issues/3922)) ([ac7b49c](https://github.com/filebrowser/filebrowser/commit/ac7b49c1484b4e27a1149310542ccd1e90659ee2))
|
||||||
|
* Updates for project File Browser ([#5544](https://github.com/filebrowser/filebrowser/issues/5544)) ([fb5d099](https://github.com/filebrowser/filebrowser/commit/fb5d099f8514516216f407be012d2e3f25de2441))
|
||||||
|
|
||||||
|
## [2.45.3](https://github.com/filebrowser/filebrowser/compare/v2.45.2...v2.45.3) (2025-11-13)
|
||||||
|
|
||||||
|
This is a test release to ensure the updated workflow works.
|
||||||
|
|
||||||
## [2.45.2](https://github.com/filebrowser/filebrowser/compare/v2.45.1...v2.45.2) (2025-11-13)
|
## [2.45.2](https://github.com/filebrowser/filebrowser/compare/v2.45.1...v2.45.2) (2025-11-13)
|
||||||
|
|
||||||
|
|||||||
@@ -15,11 +15,23 @@ We encourage you to use git to manage your fork. To clone the main repository, j
|
|||||||
git clone https://github.com/filebrowser/filebrowser
|
git clone https://github.com/filebrowser/filebrowser
|
||||||
```
|
```
|
||||||
|
|
||||||
|
We use [Taskfile](https://taskfile.dev/) to manage the different processes (building, releasing, etc) automatically.
|
||||||
|
|
||||||
## Build
|
## Build
|
||||||
|
|
||||||
|
You can fully build the project in order to produce a binary by running:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
task build
|
||||||
|
```
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
For development, there are a few things to have in mind.
|
||||||
|
|
||||||
### Frontend
|
### Frontend
|
||||||
|
|
||||||
We are using [Node.js](https://nodejs.org/en/) on the frontend to manage the build process. The steps to build it are:
|
We use [Node.js](https://nodejs.org/en/) on the frontend to manage the build process. Prepare the frontend environment:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# From the root of the repo, go to frontend/
|
# From the root of the repo, go to frontend/
|
||||||
@@ -27,37 +39,62 @@ cd frontend
|
|||||||
|
|
||||||
# Install the dependencies
|
# Install the dependencies
|
||||||
pnpm install
|
pnpm install
|
||||||
|
```
|
||||||
|
|
||||||
# Build the frontend
|
If you just want to develop the backend, you can create a static build of the frontend:
|
||||||
|
|
||||||
|
```bash
|
||||||
pnpm run build
|
pnpm run build
|
||||||
```
|
```
|
||||||
|
|
||||||
This will install the dependencies and build the frontend so you can then embed it into the Go app. Although, if you want to play with it, you'll get bored of building it after every change you do. So, you can run the command below to watch for changes:
|
If you want to develop the frontend, start a development server which watches for changes:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pnpm run dev
|
pnpm run dev
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Please note that you need to access File Browser's interface through the development server of the frontend.
|
||||||
|
|
||||||
### Backend
|
### Backend
|
||||||
|
|
||||||
First of all, you need to download the required dependencies. We are using the built-in `go mod` tool for dependency management. To get the modules, run:
|
First prepare the backend environment by downloading all required dependencies:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
go mod download
|
go mod download
|
||||||
```
|
```
|
||||||
|
|
||||||
The magic of File Browser is that the static assets are bundled into the final binary. For that, we use [Go embed.FS](https://golang.org/pkg/embed/). The files from `frontend/dist` will be embedded during the build process.
|
You can now build or run File Browser as any other Go project:
|
||||||
|
|
||||||
To build File Browser is just like any other Go program:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
# Build
|
||||||
go build
|
go build
|
||||||
|
|
||||||
|
# Run
|
||||||
|
go run .
|
||||||
```
|
```
|
||||||
|
|
||||||
To create a development build use the "dev" tag, this way the content inside the frontend folder will not be embedded in the binary but will be reloaded at every change:
|
## Documentation
|
||||||
|
|
||||||
|
We rely on Docker to abstract all the dependencies required for building the documentation.
|
||||||
|
|
||||||
|
To build the documentation to `www/public`:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
go build -tags dev
|
task docs
|
||||||
|
```
|
||||||
|
|
||||||
|
To start a local server on port `8000` to view the built documentation:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
task docs-serve
|
||||||
|
```
|
||||||
|
|
||||||
|
## Release
|
||||||
|
|
||||||
|
To make a release, just run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
task release
|
||||||
```
|
```
|
||||||
|
|
||||||
## Translations
|
## Translations
|
||||||
|
|||||||
88
Makefile
88
Makefile
@@ -1,88 +0,0 @@
|
|||||||
include common.mk
|
|
||||||
include tools.mk
|
|
||||||
|
|
||||||
LDFLAGS += -X "$(MODULE)/version.Version=$(VERSION)" -X "$(MODULE)/version.CommitSHA=$(VERSION_HASH)"
|
|
||||||
|
|
||||||
SITE_DOCKER_FLAGS = \
|
|
||||||
-v $(CURDIR)/www:/docs \
|
|
||||||
-v $(CURDIR)/LICENSE:/docs/docs/LICENSE \
|
|
||||||
-v $(CURDIR)/SECURITY.md:/docs/docs/security.md \
|
|
||||||
-v $(CURDIR)/CHANGELOG.md:/docs/docs/changelog.md \
|
|
||||||
-v $(CURDIR)/CODE-OF-CONDUCT.md:/docs/docs/code-of-conduct.md \
|
|
||||||
-v $(CURDIR)/CONTRIBUTING.md:/docs/docs/contributing.md
|
|
||||||
|
|
||||||
## Build:
|
|
||||||
|
|
||||||
.PHONY: build
|
|
||||||
build: | build-frontend build-backend ## Build binary
|
|
||||||
|
|
||||||
.PHONY: build-frontend
|
|
||||||
build-frontend: ## Build frontend
|
|
||||||
$Q cd frontend && pnpm install --frozen-lockfile && pnpm run build
|
|
||||||
|
|
||||||
.PHONY: build-backend
|
|
||||||
build-backend: ## Build backend
|
|
||||||
$Q $(go) build -ldflags '$(LDFLAGS)' -o .
|
|
||||||
|
|
||||||
.PHONY: test
|
|
||||||
test: | test-frontend test-backend ## Run all tests
|
|
||||||
|
|
||||||
.PHONY: test-frontend
|
|
||||||
test-frontend: ## Run frontend tests
|
|
||||||
$Q cd frontend && pnpm install --frozen-lockfile && pnpm run typecheck
|
|
||||||
|
|
||||||
.PHONY: test-backend
|
|
||||||
test-backend: ## Run backend tests
|
|
||||||
$Q $(go) test -v ./...
|
|
||||||
|
|
||||||
.PHONY: lint
|
|
||||||
lint: lint-frontend lint-backend ## Run all linters
|
|
||||||
|
|
||||||
.PHONY: lint-frontend
|
|
||||||
lint-frontend: ## Run frontend linters
|
|
||||||
$Q cd frontend && pnpm install --frozen-lockfile && pnpm run lint
|
|
||||||
|
|
||||||
.PHONY: lint-backend
|
|
||||||
lint-backend: | $(golangci-lint) ## Run backend linters
|
|
||||||
$Q $(golangci-lint) run -v
|
|
||||||
|
|
||||||
fmt: $(goimports) ## Format source files
|
|
||||||
$Q $(goimports) -local $(MODULE) -w $$(find . -type f -name '*.go' -not -path "./vendor/*")
|
|
||||||
|
|
||||||
clean: clean-tools ## Clean
|
|
||||||
|
|
||||||
## Release:
|
|
||||||
|
|
||||||
.PHONY: release-dry-run
|
|
||||||
release-dry-run:
|
|
||||||
pnpm dlx commit-and-tag-version --dry-run --skip
|
|
||||||
|
|
||||||
.PHONY: release
|
|
||||||
release:
|
|
||||||
pnpm dlx commit-and-tag-version -s
|
|
||||||
|
|
||||||
.PHONY: site
|
|
||||||
site: ## Build site
|
|
||||||
@rm -rf www/public
|
|
||||||
docker build -f www/Dockerfile --progress=plain -t filebrowser.site www
|
|
||||||
docker run --rm $(SITE_DOCKER_FLAGS) filebrowser.site build -d "public"
|
|
||||||
|
|
||||||
.PHONY: site-serve
|
|
||||||
site-serve: ## Serve site for development
|
|
||||||
docker build -f www/Dockerfile --progress=plain -t filebrowser.site www
|
|
||||||
docker run --rm -it -p 8000:8000 $(SITE_DOCKER_FLAGS) filebrowser.site
|
|
||||||
|
|
||||||
## Help:
|
|
||||||
help: ## Show this help
|
|
||||||
@echo ''
|
|
||||||
@echo 'Usage:'
|
|
||||||
@echo ' ${YELLOW}make${RESET} ${GREEN}<target> [options]${RESET}'
|
|
||||||
@echo ''
|
|
||||||
@echo 'Options:'
|
|
||||||
@$(call global_option, "V [0|1]", "enable verbose mode (default:0)")
|
|
||||||
@echo ''
|
|
||||||
@echo 'Targets:'
|
|
||||||
@awk 'BEGIN {FS = ":.*?## "} { \
|
|
||||||
if (/^[a-zA-Z_-]+:.*?##.*$$/) {printf " ${YELLOW}%-20s${GREEN}%s${RESET}\n", $$1, $$2} \
|
|
||||||
else if (/^## .*$$/) {printf " ${CYAN}%s${RESET}\n", substr($$1,4)} \
|
|
||||||
}' $(MAKEFILE_LIST)
|
|
||||||
18
README.md
18
README.md
@@ -2,7 +2,7 @@
|
|||||||
<img src="https://raw.githubusercontent.com/filebrowser/filebrowser/master/branding/banner.png" width="550"/>
|
<img src="https://raw.githubusercontent.com/filebrowser/filebrowser/master/branding/banner.png" width="550"/>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
[](https://github.com/filebrowser/filebrowser/actions/workflows/main.yaml)
|
[](https://github.com/filebrowser/filebrowser/actions/workflows/ci.yaml)
|
||||||
[](https://goreportcard.com/report/github.com/filebrowser/filebrowser/v2)
|
[](https://goreportcard.com/report/github.com/filebrowser/filebrowser/v2)
|
||||||
[](https://github.com/filebrowser/filebrowser/releases/latest)
|
[](https://github.com/filebrowser/filebrowser/releases/latest)
|
||||||
|
|
||||||
@@ -14,18 +14,12 @@ Documentation on how to install, configure, and contribute to this project is ho
|
|||||||
|
|
||||||
## Project Status
|
## Project Status
|
||||||
|
|
||||||
> [!WARNING]
|
This project is a finished product which fulfills its goal: be a single binary web File Browser which can be run by anyone anywhere. That means that File Browser is currently on **maintenance-only** mode. Therefore, please note the following:
|
||||||
>
|
|
||||||
> This project is currently on **maintenance-only** mode, and is looking for new maintainers. For more information, please read the [discussion #4906](https://github.com/filebrowser/filebrowser/discussions/4906). Therefore, please note the following:
|
|
||||||
>
|
|
||||||
> - It can take a while until someone gets back to you. Please be patient.
|
|
||||||
> - [Issues][issues] are only being used to track bugs. Any unrelated issues will be converted into a [discussion][discussions].
|
|
||||||
> - No new features will be implemented until further notice. The priority is on triaging issues and merge bug fixes.
|
|
||||||
>
|
|
||||||
> If you're interested in maintaining this project, please reach out via the discussion above.
|
|
||||||
|
|
||||||
[issues]: https://github.com/filebrowser/filebrowser/issues
|
- It can take a while until someone gets back to you. Please be patient.
|
||||||
[discussions]: https://github.com/filebrowser/filebrowser/discussions
|
- [Issues](https://github.com/filebrowser/filebrowser/issues) are meant to track bugs. Unrelated issues will be converted into [discussions](https://github.com/filebrowser/filebrowser/discussions).
|
||||||
|
- No new features will be implemented by maintainers. Pull requests for new features will be reviewed on a case by case basis.
|
||||||
|
- The priority is triaging issues, addressing security issues and reviewing pull requests meant to solve bugs.
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
|
|||||||
69
Taskfile.yml
Normal file
69
Taskfile.yml
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
version: '3'
|
||||||
|
|
||||||
|
vars:
|
||||||
|
SITE_DOCKER_FLAGS: >-
|
||||||
|
-v ./www:/docs
|
||||||
|
-v ./LICENSE:/docs/docs/LICENSE
|
||||||
|
-v ./SECURITY.md:/docs/docs/security.md
|
||||||
|
-v ./CHANGELOG.md:/docs/docs/changelog.md
|
||||||
|
-v ./CODE-OF-CONDUCT.md:/docs/docs/code-of-conduct.md
|
||||||
|
-v ./CONTRIBUTING.md:/docs/docs/contributing.md
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
build-frontend:
|
||||||
|
desc: Build frontend assets
|
||||||
|
dir: frontend
|
||||||
|
cmds:
|
||||||
|
- pnpm install --frozen-lockfile
|
||||||
|
- pnpm run build
|
||||||
|
|
||||||
|
build-backend:
|
||||||
|
desc: Build backend binary
|
||||||
|
cmds:
|
||||||
|
- go build -ldflags='-s -w -X "github.com/filebrowser/filebrowser/v2/version.Version={{.VERSION}}" -X "github.com/filebrowser/filebrowser/v2/version.CommitSHA={{.GIT_COMMIT}}"' -o filebrowser .
|
||||||
|
vars:
|
||||||
|
GIT_COMMIT:
|
||||||
|
sh: git log -n 1 --format=%h
|
||||||
|
VERSION:
|
||||||
|
sh: git describe --tags --abbrev=0 --match=v* | cut -c 2-
|
||||||
|
|
||||||
|
build:
|
||||||
|
desc: Build both frontend and backend
|
||||||
|
cmds:
|
||||||
|
- task: build-frontend
|
||||||
|
- task: build-backend
|
||||||
|
|
||||||
|
release-make:
|
||||||
|
internal: true
|
||||||
|
prompt: Do you wish to proceed?
|
||||||
|
cmds:
|
||||||
|
- pnpm dlx commit-and-tag-version -s
|
||||||
|
|
||||||
|
release-dry-run:
|
||||||
|
internal: true
|
||||||
|
cmds:
|
||||||
|
- pnpm dlx commit-and-tag-version --dry-run --skip
|
||||||
|
|
||||||
|
release:
|
||||||
|
desc: Create a new release
|
||||||
|
cmds:
|
||||||
|
- task: release-dry-run
|
||||||
|
- task: release-make
|
||||||
|
|
||||||
|
docs-image-make:
|
||||||
|
internal: true
|
||||||
|
cmds:
|
||||||
|
- docker build -f www/Dockerfile --progress=plain -t filebrowser.site www
|
||||||
|
|
||||||
|
docs:
|
||||||
|
desc: Generate documentation
|
||||||
|
cmds:
|
||||||
|
- rm -rf www/public
|
||||||
|
- task: docs-image-make
|
||||||
|
- docker run --rm {{.SITE_DOCKER_FLAGS}} filebrowser.site build -d "public"
|
||||||
|
|
||||||
|
docs-serve:
|
||||||
|
desc: Serve documentation
|
||||||
|
cmds:
|
||||||
|
- task: docs-image-make
|
||||||
|
- docker run --rm -it -p 8000:8000 {{.SITE_DOCKER_FLAGS}} filebrowser.site
|
||||||
@@ -31,6 +31,7 @@ func addConfigFlags(flags *pflag.FlagSet) {
|
|||||||
addServerFlags(flags)
|
addServerFlags(flags)
|
||||||
addUserFlags(flags)
|
addUserFlags(flags)
|
||||||
flags.BoolP("signup", "s", false, "allow users to signup")
|
flags.BoolP("signup", "s", false, "allow users to signup")
|
||||||
|
flags.Bool("hide-login-button", false, "hide login button from public pages")
|
||||||
flags.Bool("create-user-dir", false, "generate user's home directory automatically")
|
flags.Bool("create-user-dir", false, "generate user's home directory automatically")
|
||||||
flags.Uint("minimum-password-length", settings.DefaultMinimumPasswordLength, "minimum password length for new users")
|
flags.Uint("minimum-password-length", settings.DefaultMinimumPasswordLength, "minimum password length for new users")
|
||||||
flags.String("shell", "", "shell command to which other commands should be appended")
|
flags.String("shell", "", "shell command to which other commands should be appended")
|
||||||
@@ -192,9 +193,10 @@ func printSettings(ser *settings.Server, set *settings.Settings, auther auth.Aut
|
|||||||
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
|
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
|
||||||
|
|
||||||
fmt.Fprintf(w, "Sign up:\t%t\n", set.Signup)
|
fmt.Fprintf(w, "Sign up:\t%t\n", set.Signup)
|
||||||
|
fmt.Fprintf(w, "Hide Login Button:\t%t\n", set.HideLoginButton)
|
||||||
fmt.Fprintf(w, "Create User Dir:\t%t\n", set.CreateUserDir)
|
fmt.Fprintf(w, "Create User Dir:\t%t\n", set.CreateUserDir)
|
||||||
fmt.Fprintf(w, "Minimum Password Length:\t%d\n", set.MinimumPasswordLength)
|
fmt.Fprintf(w, "Minimum Password Length:\t%d\n", set.MinimumPasswordLength)
|
||||||
fmt.Fprintf(w, "Auth method:\t%s\n", set.AuthMethod)
|
fmt.Fprintf(w, "Auth Method:\t%s\n", set.AuthMethod)
|
||||||
fmt.Fprintf(w, "Shell:\t%s\t\n", strings.Join(set.Shell, " "))
|
fmt.Fprintf(w, "Shell:\t%s\t\n", strings.Join(set.Shell, " "))
|
||||||
fmt.Fprintln(w, "\nBranding:")
|
fmt.Fprintln(w, "\nBranding:")
|
||||||
fmt.Fprintf(w, "\tName:\t%s\n", set.Branding.Name)
|
fmt.Fprintf(w, "\tName:\t%s\n", set.Branding.Name)
|
||||||
@@ -215,6 +217,7 @@ func printSettings(ser *settings.Server, set *settings.Settings, auther auth.Aut
|
|||||||
fmt.Fprintf(w, "\tExec Enabled:\t%t\n", ser.EnableExec)
|
fmt.Fprintf(w, "\tExec Enabled:\t%t\n", ser.EnableExec)
|
||||||
fmt.Fprintln(w, "\nDefaults:")
|
fmt.Fprintln(w, "\nDefaults:")
|
||||||
fmt.Fprintf(w, "\tScope:\t%s\n", set.Defaults.Scope)
|
fmt.Fprintf(w, "\tScope:\t%s\n", set.Defaults.Scope)
|
||||||
|
fmt.Fprintf(w, "\tHideDotfiles:\t%t\n", set.Defaults.HideDotfiles)
|
||||||
fmt.Fprintf(w, "\tLocale:\t%s\n", set.Defaults.Locale)
|
fmt.Fprintf(w, "\tLocale:\t%s\n", set.Defaults.Locale)
|
||||||
fmt.Fprintf(w, "\tView mode:\t%s\n", set.Defaults.ViewMode)
|
fmt.Fprintf(w, "\tView mode:\t%s\n", set.Defaults.ViewMode)
|
||||||
fmt.Fprintf(w, "\tSingle Click:\t%t\n", set.Defaults.SingleClick)
|
fmt.Fprintf(w, "\tSingle Click:\t%t\n", set.Defaults.SingleClick)
|
||||||
|
|||||||
@@ -41,6 +41,11 @@ override the options.`,
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
hideLoginButton, err := getBool(flags, "hide-login-button")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
createUserDir, err := getBool(flags, "create-user-dir")
|
createUserDir, err := getBool(flags, "create-user-dir")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -84,6 +89,7 @@ override the options.`,
|
|||||||
s := &settings.Settings{
|
s := &settings.Settings{
|
||||||
Key: key,
|
Key: key,
|
||||||
Signup: signup,
|
Signup: signup,
|
||||||
|
HideLoginButton: hideLoginButton,
|
||||||
CreateUserDir: createUserDir,
|
CreateUserDir: createUserDir,
|
||||||
MinimumPasswordLength: minLength,
|
MinimumPasswordLength: minLength,
|
||||||
Shell: convertCmdStrToCmdArray(shell),
|
Shell: convertCmdStrToCmdArray(shell),
|
||||||
|
|||||||
@@ -50,6 +50,8 @@ you want to change. Other options will remain unchanged.`,
|
|||||||
ser.Port, err = getString(flags, flag.Name)
|
ser.Port, err = getString(flags, flag.Name)
|
||||||
case "log":
|
case "log":
|
||||||
ser.Log, err = getString(flags, flag.Name)
|
ser.Log, err = getString(flags, flag.Name)
|
||||||
|
case "hide-login-button":
|
||||||
|
set.HideLoginButton, err = getBool(flags, flag.Name)
|
||||||
case "signup":
|
case "signup":
|
||||||
set.Signup, err = getBool(flags, flag.Name)
|
set.Signup, err = getBool(flags, flag.Name)
|
||||||
case "auth.method":
|
case "auth.method":
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ func init() {
|
|||||||
func printToc(names []string) {
|
func printToc(names []string) {
|
||||||
for i, name := range names {
|
for i, name := range names {
|
||||||
name = strings.TrimSuffix(name, filepath.Ext(name))
|
name = strings.TrimSuffix(name, filepath.Ext(name))
|
||||||
name = strings.Replace(name, "-", " ", -1)
|
name = strings.ReplaceAll(name, "-", " ")
|
||||||
names[i] = name
|
names[i] = name
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -29,7 +29,7 @@ func printToc(names []string) {
|
|||||||
|
|
||||||
toc := ""
|
toc := ""
|
||||||
for _, name := range names {
|
for _, name := range names {
|
||||||
toc += "* [" + name + "](cli/" + strings.Replace(name, " ", "-", -1) + ".md)\n"
|
toc += "* [" + name + "](cli/" + strings.ReplaceAll(name, " ", "-") + ".md)\n"
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println(toc)
|
fmt.Println(toc)
|
||||||
@@ -84,7 +84,7 @@ func generateDocs(cmd *cobra.Command, dir string) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
basename := strings.Replace(cmd.CommandPath(), " ", "-", -1) + ".md"
|
basename := strings.ReplaceAll(cmd.CommandPath(), " ", "-") + ".md"
|
||||||
filename := filepath.Join(dir, basename)
|
filename := filepath.Join(dir, basename)
|
||||||
f, err := os.Create(filename)
|
f, err := os.Create(filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -420,6 +420,7 @@ func quickSetup(flags *pflag.FlagSet, d pythonData) error {
|
|||||||
set := &settings.Settings{
|
set := &settings.Settings{
|
||||||
Key: generateKey(),
|
Key: generateKey(),
|
||||||
Signup: false,
|
Signup: false,
|
||||||
|
HideLoginButton: true,
|
||||||
CreateUserDir: false,
|
CreateUserDir: false,
|
||||||
MinimumPasswordLength: settings.DefaultMinimumPasswordLength,
|
MinimumPasswordLength: settings.DefaultMinimumPasswordLength,
|
||||||
UserHomeBasePath: settings.DefaultUsersHomeBasePath,
|
UserHomeBasePath: settings.DefaultUsersHomeBasePath,
|
||||||
@@ -533,8 +534,7 @@ func initConfig() {
|
|||||||
|
|
||||||
v.SetEnvPrefix("FB")
|
v.SetEnvPrefix("FB")
|
||||||
v.AutomaticEnv()
|
v.AutomaticEnv()
|
||||||
v.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
|
v.SetEnvKeyReplacer(strings.NewReplacer(".", "_", "-", "_"))
|
||||||
v.SetEnvKeyReplacer(strings.NewReplacer("-", "_"))
|
|
||||||
|
|
||||||
if err := v.ReadInConfig(); err != nil {
|
if err := v.ReadInConfig(); err != nil {
|
||||||
var configParseError v.ConfigParseError
|
var configParseError v.ConfigParseError
|
||||||
|
|||||||
@@ -135,6 +135,8 @@ func getUserDefaults(flags *pflag.FlagSet, defaults *settings.UserDefaults, all
|
|||||||
defaults.Sorting.By, err = getString(flags, flag.Name)
|
defaults.Sorting.By, err = getString(flags, flag.Name)
|
||||||
case "sorting.asc":
|
case "sorting.asc":
|
||||||
defaults.Sorting.Asc, err = getBool(flags, flag.Name)
|
defaults.Sorting.Asc, err = getBool(flags, flag.Name)
|
||||||
|
case "hideDotfiles":
|
||||||
|
defaults.HideDotfiles, err = getBool(flags, flag.Name)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
visitErr = err
|
visitErr = err
|
||||||
|
|||||||
16
cmd/utils.go
16
cmd/utils.go
@@ -23,16 +23,8 @@ import (
|
|||||||
|
|
||||||
const dbPerms = 0640
|
const dbPerms = 0640
|
||||||
|
|
||||||
func returnErr(err error) error {
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getString(flags *pflag.FlagSet, flag string) (string, error) {
|
func getString(flags *pflag.FlagSet, flag string) (string, error) {
|
||||||
s, err := flags.GetString(flag)
|
return flags.GetString(flag)
|
||||||
return s, returnErr(err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func getMode(flags *pflag.FlagSet, flag string) (fs.FileMode, error) {
|
func getMode(flags *pflag.FlagSet, flag string) (fs.FileMode, error) {
|
||||||
@@ -48,13 +40,11 @@ func getMode(flags *pflag.FlagSet, flag string) (fs.FileMode, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func getBool(flags *pflag.FlagSet, flag string) (bool, error) {
|
func getBool(flags *pflag.FlagSet, flag string) (bool, error) {
|
||||||
b, err := flags.GetBool(flag)
|
return flags.GetBool(flag)
|
||||||
return b, returnErr(err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func getUint(flags *pflag.FlagSet, flag string) (uint, error) {
|
func getUint(flags *pflag.FlagSet, flag string) (uint, error) {
|
||||||
b, err := flags.GetUint(flag)
|
return flags.GetUint(flag)
|
||||||
return b, returnErr(err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func generateKey() []byte {
|
func generateKey() []byte {
|
||||||
|
|||||||
@@ -1,34 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
rules: {
|
|
||||||
'body-leading-blank': [1, 'always'],
|
|
||||||
'body-max-line-length': [2, 'always', 100],
|
|
||||||
'footer-leading-blank': [1, 'always'],
|
|
||||||
'footer-max-line-length': [2, 'always', 100],
|
|
||||||
'header-max-length': [2, 'always', 100],
|
|
||||||
'scope-case': [2, 'always', 'lower-case'],
|
|
||||||
'subject-case': [
|
|
||||||
2,
|
|
||||||
'never',
|
|
||||||
['sentence-case', 'start-case', 'pascal-case', 'upper-case'],
|
|
||||||
],
|
|
||||||
'subject-full-stop': [2, 'never', '.'],
|
|
||||||
'type-case': [2, 'always', 'lower-case'],
|
|
||||||
'type-empty': [2, 'never'],
|
|
||||||
'type-enum': [
|
|
||||||
2,
|
|
||||||
'always',
|
|
||||||
[
|
|
||||||
'feat',
|
|
||||||
'fix',
|
|
||||||
'perf',
|
|
||||||
'revert',
|
|
||||||
'refactor',
|
|
||||||
'build',
|
|
||||||
'ci',
|
|
||||||
'test',
|
|
||||||
'chore',
|
|
||||||
'docs',
|
|
||||||
],
|
|
||||||
],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
28
common.mk
28
common.mk
@@ -1,28 +0,0 @@
|
|||||||
SHELL := /usr/bin/env bash
|
|
||||||
DATE ?= $(shell date +%FT%T%z)
|
|
||||||
BASE_PATH := $(shell dirname $(realpath $(lastword $(MAKEFILE_LIST))))
|
|
||||||
VERSION ?= $(shell git describe --tags --always --match=v* 2> /dev/null || \
|
|
||||||
cat $(CURDIR)/.version 2> /dev/null || echo v0)
|
|
||||||
VERSION_HASH = $(shell git rev-parse HEAD)
|
|
||||||
BRANCH = $(shell git rev-parse --abbrev-ref HEAD)
|
|
||||||
|
|
||||||
go = GOGC=off go
|
|
||||||
MODULE = $(shell env GO111MODULE=on go list -m)
|
|
||||||
|
|
||||||
# printing
|
|
||||||
# $Q (quiet) is used in the targets as a replacer for @.
|
|
||||||
# This macro helps to print the command for debugging by setting V to 1. Example `make test-unit V=1`
|
|
||||||
V = 0
|
|
||||||
Q = $(if $(filter 1,$V),,@)
|
|
||||||
# $M is a macro to print a colored ▶ character. Example `$(info $(M) running coverage tests…)` will print "▶ running coverage tests…"
|
|
||||||
M = $(shell printf "\033[34;1m▶\033[0m")
|
|
||||||
|
|
||||||
GREEN := $(shell tput -Txterm setaf 2)
|
|
||||||
YELLOW := $(shell tput -Txterm setaf 3)
|
|
||||||
WHITE := $(shell tput -Txterm setaf 7)
|
|
||||||
CYAN := $(shell tput -Txterm setaf 6)
|
|
||||||
RESET := $(shell tput -Txterm sgr0)
|
|
||||||
|
|
||||||
define global_option
|
|
||||||
printf " ${YELLOW}%-20s${GREEN}%s${RESET}\n" $(1) $(2)
|
|
||||||
endef
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
//go:build dev
|
|
||||||
|
|
||||||
package frontend
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io/fs"
|
|
||||||
"os"
|
|
||||||
)
|
|
||||||
|
|
||||||
var assets fs.FS = os.DirFS("frontend")
|
|
||||||
|
|
||||||
func Assets() fs.FS {
|
|
||||||
return assets
|
|
||||||
}
|
|
||||||
@@ -12,7 +12,11 @@
|
|||||||
|
|
||||||
<link rel="icon" type="image/svg+xml" href="/img/icons/favicon.svg" />
|
<link rel="icon" type="image/svg+xml" href="/img/icons/favicon.svg" />
|
||||||
<link rel="shortcut icon" href="/img/icons/favicon.ico" />
|
<link rel="shortcut icon" href="/img/icons/favicon.ico" />
|
||||||
<link rel="apple-touch-icon" sizes="180x180" href="/img/icons/apple-touch-icon.png" />
|
<link
|
||||||
|
rel="apple-touch-icon"
|
||||||
|
sizes="180x180"
|
||||||
|
href="/img/icons/apple-touch-icon.png"
|
||||||
|
/>
|
||||||
<meta name="apple-mobile-web-app-title" content="File Browser" />
|
<meta name="apple-mobile-web-app-title" content="File Browser" />
|
||||||
|
|
||||||
<!-- Add to home screen for Android and modern mobile browsers -->
|
<!-- Add to home screen for Android and modern mobile browsers -->
|
||||||
|
|||||||
@@ -11,18 +11,16 @@
|
|||||||
"dev": "vite dev",
|
"dev": "vite dev",
|
||||||
"build": "pnpm run typecheck && vite build",
|
"build": "pnpm run typecheck && vite build",
|
||||||
"clean": "find ./dist -maxdepth 1 -mindepth 1 ! -name '.gitkeep' -exec rm -r {} +",
|
"clean": "find ./dist -maxdepth 1 -mindepth 1 ! -name '.gitkeep' -exec rm -r {} +",
|
||||||
"typecheck": "vue-tsc -p ./tsconfig.tsc.json --noEmit",
|
"typecheck": "vue-tsc -p ./tsconfig.app.json --noEmit",
|
||||||
"lint": "eslint src/",
|
"lint": "eslint src/",
|
||||||
"lint:fix": "eslint --fix src/",
|
"lint:fix": "eslint --fix src/",
|
||||||
"format": "prettier --write .",
|
"format": "prettier --write ."
|
||||||
"test": "playwright test"
|
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@chenfengyuan/vue-number-input": "^2.0.1",
|
"@chenfengyuan/vue-number-input": "^2.0.1",
|
||||||
"@vueuse/core": "^14.0.0",
|
"@vueuse/core": "^14.0.0",
|
||||||
"@vueuse/integrations": "^14.0.0",
|
"@vueuse/integrations": "^14.0.0",
|
||||||
"ace-builds": "^1.43.2",
|
"ace-builds": "^1.43.2",
|
||||||
"core-js": "^3.44.0",
|
|
||||||
"dayjs": "^1.11.13",
|
"dayjs": "^1.11.13",
|
||||||
"dompurify": "^3.2.6",
|
"dompurify": "^3.2.6",
|
||||||
"epubjs": "^0.3.93",
|
"epubjs": "^0.3.93",
|
||||||
@@ -51,7 +49,6 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@intlify/unplugin-vue-i18n": "^11.0.1",
|
"@intlify/unplugin-vue-i18n": "^11.0.1",
|
||||||
"@playwright/test": "^1.54.1",
|
|
||||||
"@tsconfig/node24": "^24.0.2",
|
"@tsconfig/node24": "^24.0.2",
|
||||||
"@types/lodash-es": "^4.17.12",
|
"@types/lodash-es": "^4.17.12",
|
||||||
"@types/node": "^24.10.1",
|
"@types/node": "^24.10.1",
|
||||||
@@ -60,17 +57,16 @@
|
|||||||
"@vitejs/plugin-vue": "^6.0.1",
|
"@vitejs/plugin-vue": "^6.0.1",
|
||||||
"@vue/eslint-config-prettier": "^10.2.0",
|
"@vue/eslint-config-prettier": "^10.2.0",
|
||||||
"@vue/eslint-config-typescript": "^14.6.0",
|
"@vue/eslint-config-typescript": "^14.6.0",
|
||||||
"@vue/tsconfig": "^0.7.0",
|
"@vue/tsconfig": "^0.8.1",
|
||||||
"autoprefixer": "^10.4.21",
|
"autoprefixer": "^10.4.21",
|
||||||
"concurrently": "^9.2.0",
|
|
||||||
"eslint": "^9.31.0",
|
"eslint": "^9.31.0",
|
||||||
"eslint-config-prettier": "^10.1.5",
|
"eslint-config-prettier": "^10.1.5",
|
||||||
"eslint-plugin-prettier": "^5.5.1",
|
"eslint-plugin-prettier": "^5.5.1",
|
||||||
"eslint-plugin-vue": "^10.5.1",
|
"eslint-plugin-vue": "^10.5.1",
|
||||||
"jsdom": "^27.2.0",
|
|
||||||
"postcss": "^8.5.6",
|
"postcss": "^8.5.6",
|
||||||
"prettier": "^3.6.2",
|
"prettier": "^3.6.2",
|
||||||
"terser": "^5.43.1",
|
"terser": "^5.43.1",
|
||||||
|
"typescript": "^5.9.3",
|
||||||
"vite": "^7.2.2",
|
"vite": "^7.2.2",
|
||||||
"vite-plugin-compression2": "^2.3.1",
|
"vite-plugin-compression2": "^2.3.1",
|
||||||
"vue-tsc": "^3.1.3"
|
"vue-tsc": "^3.1.3"
|
||||||
|
|||||||
@@ -1,80 +0,0 @@
|
|||||||
import { defineConfig, devices } from "@playwright/test";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Read environment variables from file.
|
|
||||||
* https://github.com/motdotla/dotenv
|
|
||||||
*/
|
|
||||||
// require('dotenv').config();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* See https://playwright.dev/docs/test-configuration.
|
|
||||||
*/
|
|
||||||
export default defineConfig({
|
|
||||||
testDir: "./tests",
|
|
||||||
/* Run tests in files in parallel */
|
|
||||||
fullyParallel: true,
|
|
||||||
/* Fail the build on CI if you accidentally left test.only in the source code. */
|
|
||||||
forbidOnly: !!process.env.CI,
|
|
||||||
/* Retry on CI only */
|
|
||||||
retries: process.env.CI ? 2 : 0,
|
|
||||||
/* Opt out of parallel tests on CI. */
|
|
||||||
workers: process.env.CI ? 1 : undefined,
|
|
||||||
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
|
|
||||||
reporter: "html",
|
|
||||||
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
|
|
||||||
use: {
|
|
||||||
/* Base URL to use in actions like `await page.goto('/')`. */
|
|
||||||
baseURL: "http://127.0.0.1:5173",
|
|
||||||
|
|
||||||
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
|
|
||||||
trace: "on-first-retry",
|
|
||||||
|
|
||||||
/* Set default locale to English (US) */
|
|
||||||
locale: "en-US",
|
|
||||||
},
|
|
||||||
|
|
||||||
/* Configure projects for major browsers */
|
|
||||||
projects: [
|
|
||||||
{
|
|
||||||
name: "chromium",
|
|
||||||
use: { ...devices["Desktop Chrome"] },
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
name: "firefox",
|
|
||||||
use: { ...devices["Desktop Firefox"] },
|
|
||||||
},
|
|
||||||
|
|
||||||
// {
|
|
||||||
// name: "webkit",
|
|
||||||
// use: { ...devices["Desktop Safari"] },
|
|
||||||
// },
|
|
||||||
|
|
||||||
/* Test against mobile viewports. */
|
|
||||||
// {
|
|
||||||
// name: 'Mobile Chrome',
|
|
||||||
// use: { ...devices['Pixel 5'] },
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// name: 'Mobile Safari',
|
|
||||||
// use: { ...devices['iPhone 12'] },
|
|
||||||
// },
|
|
||||||
|
|
||||||
/* Test against branded browsers. */
|
|
||||||
// {
|
|
||||||
// name: 'Microsoft Edge',
|
|
||||||
// use: { ...devices['Desktop Edge'], channel: 'msedge' },
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// name: 'Google Chrome',
|
|
||||||
// use: { ...devices['Desktop Chrome'], channel: 'chrome' },
|
|
||||||
// },
|
|
||||||
],
|
|
||||||
|
|
||||||
/* Run your local dev server before starting the tests */
|
|
||||||
webServer: {
|
|
||||||
command: "npm run dev",
|
|
||||||
url: "http://127.0.0.1:5173",
|
|
||||||
reuseExistingServer: !process.env.CI,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
821
frontend/pnpm-lock.yaml
generated
821
frontend/pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -18,9 +18,17 @@
|
|||||||
|
|
||||||
<meta name="robots" content="noindex,nofollow" />
|
<meta name="robots" content="noindex,nofollow" />
|
||||||
|
|
||||||
<link rel="icon" type="image/svg+xml" href="[{[ .StaticURL ]}]/img/icons/favicon.svg" />
|
<link
|
||||||
|
rel="icon"
|
||||||
|
type="image/svg+xml"
|
||||||
|
href="[{[ .StaticURL ]}]/img/icons/favicon.svg"
|
||||||
|
/>
|
||||||
<link rel="shortcut icon" href="[{[ .StaticURL ]}]/img/icons/favicon.ico" />
|
<link rel="shortcut icon" href="[{[ .StaticURL ]}]/img/icons/favicon.ico" />
|
||||||
<link rel="apple-touch-icon" sizes="180x180" href="[{[ .StaticURL ]}]/img/icons/apple-touch-icon.png" />
|
<link
|
||||||
|
rel="apple-touch-icon"
|
||||||
|
sizes="180x180"
|
||||||
|
href="[{[ .StaticURL ]}]/img/icons/apple-touch-icon.png"
|
||||||
|
/>
|
||||||
<meta name="apple-mobile-web-app-title" content="File Browser" />
|
<meta name="apple-mobile-web-app-title" content="File Browser" />
|
||||||
|
|
||||||
<!-- Add to home screen for Android and modern mobile browsers -->
|
<!-- Add to home screen for Android and modern mobile browsers -->
|
||||||
|
|||||||
47
frontend/src/components/ContextMenu.vue
Normal file
47
frontend/src/components/ContextMenu.vue
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
<template>
|
||||||
|
<div
|
||||||
|
class="context-menu"
|
||||||
|
ref="contextMenu"
|
||||||
|
v-show="show"
|
||||||
|
:style="{
|
||||||
|
top: `${props.pos.y}px`,
|
||||||
|
left: `${left}px`,
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, watch, computed, onUnmounted } from "vue";
|
||||||
|
|
||||||
|
const emit = defineEmits(["hide"]);
|
||||||
|
const props = defineProps<{ show: boolean; pos: { x: number; y: number } }>();
|
||||||
|
const contextMenu = ref<HTMLElement | null>(null);
|
||||||
|
|
||||||
|
const left = computed(() => {
|
||||||
|
return Math.min(
|
||||||
|
props.pos.x,
|
||||||
|
window.innerWidth - (contextMenu.value?.clientWidth ?? 0)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
const hideContextMenu = () => {
|
||||||
|
emit("hide");
|
||||||
|
};
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.show,
|
||||||
|
(val) => {
|
||||||
|
if (val) {
|
||||||
|
document.addEventListener("click", hideContextMenu);
|
||||||
|
} else {
|
||||||
|
document.removeEventListener("click", hideContextMenu);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
document.removeEventListener("click", hideContextMenu);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
@@ -63,6 +63,7 @@
|
|||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<router-link
|
<router-link
|
||||||
|
v-if="!hideLoginButton"
|
||||||
class="action"
|
class="action"
|
||||||
to="/login"
|
to="/login"
|
||||||
:aria-label="$t('sidebar.login')"
|
:aria-label="$t('sidebar.login')"
|
||||||
@@ -124,6 +125,7 @@ import * as auth from "@/utils/auth";
|
|||||||
import {
|
import {
|
||||||
version,
|
version,
|
||||||
signup,
|
signup,
|
||||||
|
hideLoginButton,
|
||||||
disableExternal,
|
disableExternal,
|
||||||
disableUsedPercentage,
|
disableUsedPercentage,
|
||||||
noAuth,
|
noAuth,
|
||||||
@@ -153,6 +155,7 @@ export default {
|
|||||||
return this.currentPromptName === "sidebar";
|
return this.currentPromptName === "sidebar";
|
||||||
},
|
},
|
||||||
signup: () => signup,
|
signup: () => signup,
|
||||||
|
hideLoginButton: () => hideLoginButton,
|
||||||
version: () => version,
|
version: () => version,
|
||||||
disableExternal: () => disableExternal,
|
disableExternal: () => disableExternal,
|
||||||
disableUsedPercentage: () => disableUsedPercentage,
|
disableUsedPercentage: () => disableUsedPercentage,
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
:aria-label="name"
|
:aria-label="name"
|
||||||
:aria-selected="isSelected"
|
:aria-selected="isSelected"
|
||||||
:data-ext="getExtension(name).toLowerCase()"
|
:data-ext="getExtension(name).toLowerCase()"
|
||||||
|
@contextmenu="contextMenu"
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<img
|
<img
|
||||||
@@ -239,6 +240,17 @@ const itemClick = (event: Event | KeyboardEvent) => {
|
|||||||
else click(event);
|
else click(event);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const contextMenu = (event: MouseEvent) => {
|
||||||
|
event.preventDefault();
|
||||||
|
if (
|
||||||
|
fileStore.selected.length === 0 ||
|
||||||
|
event.ctrlKey ||
|
||||||
|
fileStore.selected.indexOf(props.index) === -1
|
||||||
|
) {
|
||||||
|
click(event);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const click = (event: Event | KeyboardEvent) => {
|
const click = (event: Event | KeyboardEvent) => {
|
||||||
if (!singleClick.value && fileStore.selectedCount !== 0)
|
if (!singleClick.value && fileStore.selectedCount !== 0)
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card-content">
|
<div class="card-content">
|
||||||
|
<p>{{ $t("prompts.moveMessage") }}</p>
|
||||||
<file-list
|
<file-list
|
||||||
ref="fileList"
|
ref="fileList"
|
||||||
@update:selected="(val) => (dest = val)"
|
@update:selected="(val) => (dest = val)"
|
||||||
|
|||||||
21
frontend/src/css/context-menu.css
Normal file
21
frontend/src/css/context-menu.css
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
.context-menu {
|
||||||
|
position: absolute;
|
||||||
|
background: var(--surfacePrimary);
|
||||||
|
min-width: 180px;
|
||||||
|
max-width: 220px;
|
||||||
|
border: 1px solid var(--borderSecondary);
|
||||||
|
box-shadow: 0 2px 4px var(--borderPrimary);
|
||||||
|
z-index: 999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.context-menu .action {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
border-radius: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.context-menu .action .counter {
|
||||||
|
left: 1.75em;
|
||||||
|
}
|
||||||
@@ -63,8 +63,8 @@
|
|||||||
local("Roboto"),
|
local("Roboto"),
|
||||||
local("Roboto-Regular"),
|
local("Roboto-Regular"),
|
||||||
url(../assets/fonts/roboto/normal-latin-ext.woff2) format("woff2");
|
url(../assets/fonts/roboto/normal-latin-ext.woff2) format("woff2");
|
||||||
unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF,
|
unicode-range:
|
||||||
U+2C60-2C7F, U+A720-A7FF;
|
U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;
|
||||||
}
|
}
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
@@ -75,8 +75,9 @@
|
|||||||
local("Roboto"),
|
local("Roboto"),
|
||||||
local("Roboto-Regular"),
|
local("Roboto-Regular"),
|
||||||
url(../assets/fonts/roboto/normal-latin.woff2) format("woff2");
|
url(../assets/fonts/roboto/normal-latin.woff2) format("woff2");
|
||||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC,
|
unicode-range:
|
||||||
U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
@@ -142,8 +143,8 @@
|
|||||||
local("Roboto Medium"),
|
local("Roboto Medium"),
|
||||||
local("Roboto-Medium"),
|
local("Roboto-Medium"),
|
||||||
url(../assets/fonts/roboto/medium-latin-ext.woff2) format("woff2");
|
url(../assets/fonts/roboto/medium-latin-ext.woff2) format("woff2");
|
||||||
unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF,
|
unicode-range:
|
||||||
U+2C60-2C7F, U+A720-A7FF;
|
U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;
|
||||||
}
|
}
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
@@ -154,8 +155,9 @@
|
|||||||
local("Roboto Medium"),
|
local("Roboto Medium"),
|
||||||
local("Roboto-Medium"),
|
local("Roboto-Medium"),
|
||||||
url(../assets/fonts/roboto/medium-latin.woff2) format("woff2");
|
url(../assets/fonts/roboto/medium-latin.woff2) format("woff2");
|
||||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC,
|
unicode-range:
|
||||||
U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
@@ -221,8 +223,8 @@
|
|||||||
local("Roboto Bold"),
|
local("Roboto Bold"),
|
||||||
local("Roboto-Bold"),
|
local("Roboto-Bold"),
|
||||||
url(../assets/fonts/roboto/bold-latin-ext.woff2) format("woff2");
|
url(../assets/fonts/roboto/bold-latin-ext.woff2) format("woff2");
|
||||||
unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF,
|
unicode-range:
|
||||||
U+2C60-2C7F, U+A720-A7FF;
|
U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;
|
||||||
}
|
}
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
@@ -233,8 +235,9 @@
|
|||||||
local("Roboto Bold"),
|
local("Roboto Bold"),
|
||||||
local("Roboto-Bold"),
|
local("Roboto-Bold"),
|
||||||
url(../assets/fonts/roboto/bold-latin.woff2) format("woff2");
|
url(../assets/fonts/roboto/bold-latin.woff2) format("woff2");
|
||||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC,
|
unicode-range:
|
||||||
U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
.material-icons {
|
.material-icons {
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
@import "./mobile.css";
|
@import "./mobile.css";
|
||||||
@import "./epubReader.css";
|
@import "./epubReader.css";
|
||||||
@import "./mdPreview.css";
|
@import "./mdPreview.css";
|
||||||
|
@import "./context-menu.css";
|
||||||
|
|
||||||
/* For testing only
|
/* For testing only
|
||||||
:focus {
|
:focus {
|
||||||
@@ -459,4 +460,4 @@ html[dir="rtl"] .card-content .small + input {
|
|||||||
html[dir="rtl"] .card.floating .card-content .file-list {
|
html[dir="rtl"] .card.floating .card-content .file-list {
|
||||||
direction: ltr;
|
direction: ltr;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
@@ -166,6 +166,7 @@
|
|||||||
"allowNew": "إنشاء ملفات و مجلدات جديدة",
|
"allowNew": "إنشاء ملفات و مجلدات جديدة",
|
||||||
"allowPublish": "نشر مقالات و صفحات جديدة",
|
"allowPublish": "نشر مقالات و صفحات جديدة",
|
||||||
"allowSignup": "اسمح للمستخدمين بالاشتراك",
|
"allowSignup": "اسمح للمستخدمين بالاشتراك",
|
||||||
|
"hideLoginButton": "Hide the login button from public pages",
|
||||||
"avoidChanges": "(أتركه فارغاً إن لم ترد تغييره)",
|
"avoidChanges": "(أتركه فارغاً إن لم ترد تغييره)",
|
||||||
"branding": "الشعار",
|
"branding": "الشعار",
|
||||||
"brandingDirectoryPath": "مسار مجلد الشعار",
|
"brandingDirectoryPath": "مسار مجلد الشعار",
|
||||||
|
|||||||
@@ -166,6 +166,7 @@
|
|||||||
"allowNew": "Crear nous fitxers i carpetes",
|
"allowNew": "Crear nous fitxers i carpetes",
|
||||||
"allowPublish": "Publicar nous posts i pàgines",
|
"allowPublish": "Publicar nous posts i pàgines",
|
||||||
"allowSignup": "Permetre registre d'usuaris",
|
"allowSignup": "Permetre registre d'usuaris",
|
||||||
|
"hideLoginButton": "Hide the login button from public pages",
|
||||||
"avoidChanges": "(deixar en blanc per evitar canvis)",
|
"avoidChanges": "(deixar en blanc per evitar canvis)",
|
||||||
"branding": "Marca",
|
"branding": "Marca",
|
||||||
"brandingDirectoryPath": "Ruta de la carpeta de personalització de marca",
|
"brandingDirectoryPath": "Ruta de la carpeta de personalització de marca",
|
||||||
|
|||||||
@@ -166,6 +166,7 @@
|
|||||||
"allowNew": "Vytvářet nové soubory a adresáře",
|
"allowNew": "Vytvářet nové soubory a adresáře",
|
||||||
"allowPublish": "Publikovat nové příspěvky a stránky",
|
"allowPublish": "Publikovat nové příspěvky a stránky",
|
||||||
"allowSignup": "Povolit uživatelům registraci",
|
"allowSignup": "Povolit uživatelům registraci",
|
||||||
|
"hideLoginButton": "Hide the login button from public pages",
|
||||||
"avoidChanges": "(ponechte prázdné pro zabránění změnám)",
|
"avoidChanges": "(ponechte prázdné pro zabránění změnám)",
|
||||||
"branding": "Branding",
|
"branding": "Branding",
|
||||||
"brandingDirectoryPath": "Cesta ke složce s brandingem",
|
"brandingDirectoryPath": "Cesta ke složce s brandingem",
|
||||||
|
|||||||
@@ -166,6 +166,7 @@
|
|||||||
"allowNew": "Erstellen neuer Dateien und Ordner",
|
"allowNew": "Erstellen neuer Dateien und Ordner",
|
||||||
"allowPublish": "Veröffentlichen von neuen Beiträgen und Seiten",
|
"allowPublish": "Veröffentlichen von neuen Beiträgen und Seiten",
|
||||||
"allowSignup": "Erlaube Benutzern sich zu registrieren",
|
"allowSignup": "Erlaube Benutzern sich zu registrieren",
|
||||||
|
"hideLoginButton": "Hide the login button from public pages",
|
||||||
"avoidChanges": "(leer lassen, um Änderungen zu vermeiden)",
|
"avoidChanges": "(leer lassen, um Änderungen zu vermeiden)",
|
||||||
"branding": "Design",
|
"branding": "Design",
|
||||||
"brandingDirectoryPath": "Designverzeichnispfad",
|
"brandingDirectoryPath": "Designverzeichnispfad",
|
||||||
|
|||||||
@@ -166,6 +166,7 @@
|
|||||||
"allowNew": "Δημιουργία νέων αρχείων και φακέλων",
|
"allowNew": "Δημιουργία νέων αρχείων και φακέλων",
|
||||||
"allowPublish": "Δημοσίευση νέων αναρτήσεων και σελίδων",
|
"allowPublish": "Δημοσίευση νέων αναρτήσεων και σελίδων",
|
||||||
"allowSignup": "Να επιτρέπεται η εγγραφή νέων χρηστών",
|
"allowSignup": "Να επιτρέπεται η εγγραφή νέων χρηστών",
|
||||||
|
"hideLoginButton": "Hide the login button from public pages",
|
||||||
"avoidChanges": "(αφήστε το κενό για αποφυγή αλλαγών)",
|
"avoidChanges": "(αφήστε το κενό για αποφυγή αλλαγών)",
|
||||||
"branding": "Εξατομίκευση",
|
"branding": "Εξατομίκευση",
|
||||||
"brandingDirectoryPath": "Διαδρομή φακέλου εξατομίκευσης",
|
"brandingDirectoryPath": "Διαδρομή φακέλου εξατομίκευσης",
|
||||||
|
|||||||
@@ -166,6 +166,7 @@
|
|||||||
"allowNew": "Create new files and directories",
|
"allowNew": "Create new files and directories",
|
||||||
"allowPublish": "Publish new posts and pages",
|
"allowPublish": "Publish new posts and pages",
|
||||||
"allowSignup": "Allow users to signup",
|
"allowSignup": "Allow users to signup",
|
||||||
|
"hideLoginButton": "Hide the login button from public pages",
|
||||||
"avoidChanges": "(leave blank to avoid changes)",
|
"avoidChanges": "(leave blank to avoid changes)",
|
||||||
"branding": "Branding",
|
"branding": "Branding",
|
||||||
"brandingDirectoryPath": "Branding directory path",
|
"brandingDirectoryPath": "Branding directory path",
|
||||||
|
|||||||
@@ -166,6 +166,7 @@
|
|||||||
"allowNew": "Crear nuevos archivos y carpetas",
|
"allowNew": "Crear nuevos archivos y carpetas",
|
||||||
"allowPublish": "Publicar nuevos posts y páginas",
|
"allowPublish": "Publicar nuevos posts y páginas",
|
||||||
"allowSignup": "Permitir registro de usuarios",
|
"allowSignup": "Permitir registro de usuarios",
|
||||||
|
"hideLoginButton": "Hide the login button from public pages",
|
||||||
"avoidChanges": "(dejar en blanco para evitar cambios)",
|
"avoidChanges": "(dejar en blanco para evitar cambios)",
|
||||||
"branding": "Marca",
|
"branding": "Marca",
|
||||||
"brandingDirectoryPath": "Ruta de la carpeta de personalizacion de marca",
|
"brandingDirectoryPath": "Ruta de la carpeta de personalizacion de marca",
|
||||||
|
|||||||
@@ -166,6 +166,7 @@
|
|||||||
"allowNew": "ایجاد فایلها و پوشه های جدید",
|
"allowNew": "ایجاد فایلها و پوشه های جدید",
|
||||||
"allowPublish": "انتشار پست ها و صفحات جدید",
|
"allowPublish": "انتشار پست ها و صفحات جدید",
|
||||||
"allowSignup": "اجاره دادن به کاربران برای ثبت نام",
|
"allowSignup": "اجاره دادن به کاربران برای ثبت نام",
|
||||||
|
"hideLoginButton": "Hide the login button from public pages",
|
||||||
"avoidChanges": "(خالی بگذارید تا تغییر ایجاد نشود)",
|
"avoidChanges": "(خالی بگذارید تا تغییر ایجاد نشود)",
|
||||||
"branding": "برندسازی",
|
"branding": "برندسازی",
|
||||||
"brandingDirectoryPath": "مسیر پوشه برند",
|
"brandingDirectoryPath": "مسیر پوشه برند",
|
||||||
|
|||||||
@@ -166,6 +166,7 @@
|
|||||||
"allowNew": "Créer de nouveaux fichiers et dossiers",
|
"allowNew": "Créer de nouveaux fichiers et dossiers",
|
||||||
"allowPublish": "Publier de nouveaux posts et pages",
|
"allowPublish": "Publier de nouveaux posts et pages",
|
||||||
"allowSignup": "Autoriser les utilisateur·ices à s'inscrire",
|
"allowSignup": "Autoriser les utilisateur·ices à s'inscrire",
|
||||||
|
"hideLoginButton": "Hide the login button from public pages",
|
||||||
"avoidChanges": "(Laisser vide pour conserver l'actuel)",
|
"avoidChanges": "(Laisser vide pour conserver l'actuel)",
|
||||||
"branding": "Image de marque",
|
"branding": "Image de marque",
|
||||||
"brandingDirectoryPath": "Chemin du dossier d'image de marque",
|
"brandingDirectoryPath": "Chemin du dossier d'image de marque",
|
||||||
|
|||||||
@@ -166,6 +166,7 @@
|
|||||||
"allowNew": "יצירת קבצים ותיקיות חדשות",
|
"allowNew": "יצירת קבצים ותיקיות חדשות",
|
||||||
"allowPublish": "פרסום פוסטים ודפים חדשים",
|
"allowPublish": "פרסום פוסטים ודפים חדשים",
|
||||||
"allowSignup": "אפשר למשתמשים חדשים להירשם",
|
"allowSignup": "אפשר למשתמשים חדשים להירשם",
|
||||||
|
"hideLoginButton": "Hide the login button from public pages",
|
||||||
"avoidChanges": "(השאר ריק כדי למנוע שינויים)",
|
"avoidChanges": "(השאר ריק כדי למנוע שינויים)",
|
||||||
"branding": "מיתוג",
|
"branding": "מיתוג",
|
||||||
"brandingDirectoryPath": "נתיב תיקיית מיתוג",
|
"brandingDirectoryPath": "נתיב תיקיית מיתוג",
|
||||||
|
|||||||
@@ -166,6 +166,7 @@
|
|||||||
"allowNew": "Stvori nove datoteke i mape",
|
"allowNew": "Stvori nove datoteke i mape",
|
||||||
"allowPublish": "Objavi nove objave i stranice",
|
"allowPublish": "Objavi nove objave i stranice",
|
||||||
"allowSignup": "Dopusti registraciju korisnicima",
|
"allowSignup": "Dopusti registraciju korisnicima",
|
||||||
|
"hideLoginButton": "Hide the login button from public pages",
|
||||||
"avoidChanges": "(ostavite prazno kako biste izbjegli promjene)",
|
"avoidChanges": "(ostavite prazno kako biste izbjegli promjene)",
|
||||||
"branding": "Brendiranje",
|
"branding": "Brendiranje",
|
||||||
"brandingDirectoryPath": "Put brendiranja",
|
"brandingDirectoryPath": "Put brendiranja",
|
||||||
|
|||||||
@@ -166,6 +166,7 @@
|
|||||||
"allowNew": "Új fájlok és mappák létrehozása",
|
"allowNew": "Új fájlok és mappák létrehozása",
|
||||||
"allowPublish": "Új bejegyzések és oldalak létrehozása",
|
"allowPublish": "Új bejegyzések és oldalak létrehozása",
|
||||||
"allowSignup": "Felhasználók regisztrációjának engedélyezése",
|
"allowSignup": "Felhasználók regisztrációjának engedélyezése",
|
||||||
|
"hideLoginButton": "Hide the login button from public pages",
|
||||||
"avoidChanges": "(üresen hagyva nincs változás)",
|
"avoidChanges": "(üresen hagyva nincs változás)",
|
||||||
"branding": "Márkázás",
|
"branding": "Márkázás",
|
||||||
"brandingDirectoryPath": "Márkázás mappaútvonala",
|
"brandingDirectoryPath": "Márkázás mappaútvonala",
|
||||||
|
|||||||
@@ -166,6 +166,7 @@
|
|||||||
"allowNew": "Búa til ný skjöl og möppur",
|
"allowNew": "Búa til ný skjöl og möppur",
|
||||||
"allowPublish": "Gefa út nýjar færslur og síður",
|
"allowPublish": "Gefa út nýjar færslur og síður",
|
||||||
"allowSignup": "Leyfa nýjum notendum að skrá sig",
|
"allowSignup": "Leyfa nýjum notendum að skrá sig",
|
||||||
|
"hideLoginButton": "Hide the login button from public pages",
|
||||||
"avoidChanges": "(engar breytingar ef ekkert er skrifað)",
|
"avoidChanges": "(engar breytingar ef ekkert er skrifað)",
|
||||||
"branding": "Útlit",
|
"branding": "Útlit",
|
||||||
"brandingDirectoryPath": "Mappa fyrir branding-skjöl",
|
"brandingDirectoryPath": "Mappa fyrir branding-skjöl",
|
||||||
|
|||||||
@@ -166,6 +166,7 @@
|
|||||||
"allowNew": "Crea nuovi files o cartelle",
|
"allowNew": "Crea nuovi files o cartelle",
|
||||||
"allowPublish": "Pubblica nuovi post e pagine",
|
"allowPublish": "Pubblica nuovi post e pagine",
|
||||||
"allowSignup": "Permetti agli utenti di registrarsi",
|
"allowSignup": "Permetti agli utenti di registrarsi",
|
||||||
|
"hideLoginButton": "Hide the login button from public pages",
|
||||||
"avoidChanges": "(lascia vuoto per evitare cambiamenti)",
|
"avoidChanges": "(lascia vuoto per evitare cambiamenti)",
|
||||||
"branding": "Branding",
|
"branding": "Branding",
|
||||||
"brandingDirectoryPath": "Directory del branding",
|
"brandingDirectoryPath": "Directory del branding",
|
||||||
|
|||||||
@@ -166,6 +166,7 @@
|
|||||||
"allowNew": "ファイルやフォルダーの新規作成",
|
"allowNew": "ファイルやフォルダーの新規作成",
|
||||||
"allowPublish": "新しい投稿やページの公開",
|
"allowPublish": "新しい投稿やページの公開",
|
||||||
"allowSignup": "ユーザーの新規登録を許可",
|
"allowSignup": "ユーザーの新規登録を許可",
|
||||||
|
"hideLoginButton": "Hide the login button from public pages",
|
||||||
"avoidChanges": "(変更しない場合は空白のままにしてください)",
|
"avoidChanges": "(変更しない場合は空白のままにしてください)",
|
||||||
"branding": "ブランディング",
|
"branding": "ブランディング",
|
||||||
"brandingDirectoryPath": "ブランディングのディレクトリへのパス",
|
"brandingDirectoryPath": "ブランディングのディレクトリへのパス",
|
||||||
|
|||||||
@@ -166,6 +166,7 @@
|
|||||||
"allowNew": "파일/디렉토리 생성 허용",
|
"allowNew": "파일/디렉토리 생성 허용",
|
||||||
"allowPublish": "새 포스트/페이지 생성 허용",
|
"allowPublish": "새 포스트/페이지 생성 허용",
|
||||||
"allowSignup": "사용자 가입 허용",
|
"allowSignup": "사용자 가입 허용",
|
||||||
|
"hideLoginButton": "Hide the login button from public pages",
|
||||||
"avoidChanges": "(수정하지 않으면 비워두세요)",
|
"avoidChanges": "(수정하지 않으면 비워두세요)",
|
||||||
"branding": "브랜딩",
|
"branding": "브랜딩",
|
||||||
"brandingDirectoryPath": "브랜드 디렉토리 경로",
|
"brandingDirectoryPath": "브랜드 디렉토리 경로",
|
||||||
|
|||||||
@@ -166,6 +166,7 @@
|
|||||||
"allowNew": "Nieuwe bestanden of mappen aanmaken",
|
"allowNew": "Nieuwe bestanden of mappen aanmaken",
|
||||||
"allowPublish": "Publiceer nieuwe berichten en pagina's",
|
"allowPublish": "Publiceer nieuwe berichten en pagina's",
|
||||||
"allowSignup": "Sta gebruikers toe om zich te registreren",
|
"allowSignup": "Sta gebruikers toe om zich te registreren",
|
||||||
|
"hideLoginButton": "Hide the login button from public pages",
|
||||||
"avoidChanges": "(laat leeg om wijzigingen te voorkomen)",
|
"avoidChanges": "(laat leeg om wijzigingen te voorkomen)",
|
||||||
"branding": "Branding",
|
"branding": "Branding",
|
||||||
"brandingDirectoryPath": "Branding directory path",
|
"brandingDirectoryPath": "Branding directory path",
|
||||||
|
|||||||
@@ -166,6 +166,7 @@
|
|||||||
"allowNew": "Opprett nye filer og direktorater",
|
"allowNew": "Opprett nye filer og direktorater",
|
||||||
"allowPublish": "Publiser nye innlegg og sider",
|
"allowPublish": "Publiser nye innlegg og sider",
|
||||||
"allowSignup": "Tilat brukere å registrere seg",
|
"allowSignup": "Tilat brukere å registrere seg",
|
||||||
|
"hideLoginButton": "Hide the login button from public pages",
|
||||||
"avoidChanges": "(la stå tomt for å unngå endringer)",
|
"avoidChanges": "(la stå tomt for å unngå endringer)",
|
||||||
"branding": "Merkevarebygging",
|
"branding": "Merkevarebygging",
|
||||||
"brandingDirectoryPath": "Bane for merkevarekatalog",
|
"brandingDirectoryPath": "Bane for merkevarekatalog",
|
||||||
|
|||||||
@@ -166,6 +166,7 @@
|
|||||||
"allowNew": "Tworzenie nowych plików lub folderów",
|
"allowNew": "Tworzenie nowych plików lub folderów",
|
||||||
"allowPublish": "Tworzenie nowych wpisów i stron",
|
"allowPublish": "Tworzenie nowych wpisów i stron",
|
||||||
"allowSignup": "Pozwól użytkownikom na rejestrację",
|
"allowSignup": "Pozwól użytkownikom na rejestrację",
|
||||||
|
"hideLoginButton": "Ukryj przycisk logowania na stronach publicznych",
|
||||||
"avoidChanges": "(pozostaw puste, aby uniknąć zmian)",
|
"avoidChanges": "(pozostaw puste, aby uniknąć zmian)",
|
||||||
"branding": "Personalizacja",
|
"branding": "Personalizacja",
|
||||||
"brandingDirectoryPath": "Ścieżka do folderu personalizacji",
|
"brandingDirectoryPath": "Ścieżka do folderu personalizacji",
|
||||||
|
|||||||
@@ -166,6 +166,7 @@
|
|||||||
"allowNew": "Criar novos arquivos e pastas",
|
"allowNew": "Criar novos arquivos e pastas",
|
||||||
"allowPublish": "Publicar novas páginas e conteúdos",
|
"allowPublish": "Publicar novas páginas e conteúdos",
|
||||||
"allowSignup": "Permitir cadastro de usuários",
|
"allowSignup": "Permitir cadastro de usuários",
|
||||||
|
"hideLoginButton": "Hide the login button from public pages",
|
||||||
"avoidChanges": "(deixe em branco para manter)",
|
"avoidChanges": "(deixe em branco para manter)",
|
||||||
"branding": "Customização",
|
"branding": "Customização",
|
||||||
"brandingDirectoryPath": "Diretório de customização",
|
"brandingDirectoryPath": "Diretório de customização",
|
||||||
|
|||||||
@@ -166,6 +166,7 @@
|
|||||||
"allowNew": "Criar novos ficheiros e pastas",
|
"allowNew": "Criar novos ficheiros e pastas",
|
||||||
"allowPublish": "Publicar novas páginas e conteúdos",
|
"allowPublish": "Publicar novas páginas e conteúdos",
|
||||||
"allowSignup": "Permitir que os utilizadores criem contas",
|
"allowSignup": "Permitir que os utilizadores criem contas",
|
||||||
|
"hideLoginButton": "Hide the login button from public pages",
|
||||||
"avoidChanges": "(deixe em branco para manter)",
|
"avoidChanges": "(deixe em branco para manter)",
|
||||||
"branding": "Marca",
|
"branding": "Marca",
|
||||||
"brandingDirectoryPath": "Caminho da pasta de marca",
|
"brandingDirectoryPath": "Caminho da pasta de marca",
|
||||||
|
|||||||
@@ -166,6 +166,7 @@
|
|||||||
"allowNew": "Crează noi fișiere sau directoare",
|
"allowNew": "Crează noi fișiere sau directoare",
|
||||||
"allowPublish": "Publică noi pagini și postări",
|
"allowPublish": "Publică noi pagini și postări",
|
||||||
"allowSignup": "Permite utilizatorilor să se înregistreze",
|
"allowSignup": "Permite utilizatorilor să se înregistreze",
|
||||||
|
"hideLoginButton": "Hide the login button from public pages",
|
||||||
"avoidChanges": "(lasă gol pentru a nu schimba)",
|
"avoidChanges": "(lasă gol pentru a nu schimba)",
|
||||||
"branding": "Branding",
|
"branding": "Branding",
|
||||||
"brandingDirectoryPath": "Calea către directorul de branding",
|
"brandingDirectoryPath": "Calea către directorul de branding",
|
||||||
|
|||||||
@@ -166,6 +166,7 @@
|
|||||||
"allowNew": "Создание новых файлов или каталогов",
|
"allowNew": "Создание новых файлов или каталогов",
|
||||||
"allowPublish": "Публикация новых записей и страниц",
|
"allowPublish": "Публикация новых записей и страниц",
|
||||||
"allowSignup": "Разрешить пользователям регистрироваться",
|
"allowSignup": "Разрешить пользователям регистрироваться",
|
||||||
|
"hideLoginButton": "Hide the login button from public pages",
|
||||||
"avoidChanges": "(оставьте поле пустым, чтобы избежать изменений)",
|
"avoidChanges": "(оставьте поле пустым, чтобы избежать изменений)",
|
||||||
"branding": "Брендинг",
|
"branding": "Брендинг",
|
||||||
"brandingDirectoryPath": "Путь к каталогу брендов",
|
"brandingDirectoryPath": "Путь к каталогу брендов",
|
||||||
|
|||||||
@@ -166,6 +166,7 @@
|
|||||||
"allowNew": "Vytvárať nové súbory a priečinky",
|
"allowNew": "Vytvárať nové súbory a priečinky",
|
||||||
"allowPublish": "Zverejňovať nové príspevky a stránky",
|
"allowPublish": "Zverejňovať nové príspevky a stránky",
|
||||||
"allowSignup": "Povoliť registráciu používateľov",
|
"allowSignup": "Povoliť registráciu používateľov",
|
||||||
|
"hideLoginButton": "Hide the login button from public pages",
|
||||||
"avoidChanges": "(nechajte prázdne, aby sa nezmenilo)",
|
"avoidChanges": "(nechajte prázdne, aby sa nezmenilo)",
|
||||||
"branding": "Vlastný vzhľad",
|
"branding": "Vlastný vzhľad",
|
||||||
"brandingDirectoryPath": "Cesta k priečinku s vlastným vzhľadom",
|
"brandingDirectoryPath": "Cesta k priečinku s vlastným vzhľadom",
|
||||||
|
|||||||
@@ -166,6 +166,7 @@
|
|||||||
"allowNew": "Skapa nya filer eller mappar",
|
"allowNew": "Skapa nya filer eller mappar",
|
||||||
"allowPublish": "Publicera nya inlägg och sidor",
|
"allowPublish": "Publicera nya inlägg och sidor",
|
||||||
"allowSignup": "Tillåt användare att registrera sig",
|
"allowSignup": "Tillåt användare att registrera sig",
|
||||||
|
"hideLoginButton": "Hide the login button from public pages",
|
||||||
"avoidChanges": "(lämna blankt för att undvika ändringar)",
|
"avoidChanges": "(lämna blankt för att undvika ändringar)",
|
||||||
"branding": "Varumärke",
|
"branding": "Varumärke",
|
||||||
"brandingDirectoryPath": "Sökväg till varumärkes katalog",
|
"brandingDirectoryPath": "Sökväg till varumärkes katalog",
|
||||||
|
|||||||
@@ -166,6 +166,7 @@
|
|||||||
"allowNew": "Yeni dosyalar ve dizinler oluşturun",
|
"allowNew": "Yeni dosyalar ve dizinler oluşturun",
|
||||||
"allowPublish": "Yeni linkler ve sayfaları yayınlayın",
|
"allowPublish": "Yeni linkler ve sayfaları yayınlayın",
|
||||||
"allowSignup": "Kullanıcıların kaydolmasına izin ver",
|
"allowSignup": "Kullanıcıların kaydolmasına izin ver",
|
||||||
|
"hideLoginButton": "Hide the login button from public pages",
|
||||||
"avoidChanges": "(değişiklikleri önlemek için boş bırakın)",
|
"avoidChanges": "(değişiklikleri önlemek için boş bırakın)",
|
||||||
"branding": "Marka",
|
"branding": "Marka",
|
||||||
"brandingDirectoryPath": "Marka dizin yolu",
|
"brandingDirectoryPath": "Marka dizin yolu",
|
||||||
|
|||||||
@@ -166,6 +166,7 @@
|
|||||||
"allowNew": "Створення нових файлів або каталогів",
|
"allowNew": "Створення нових файлів або каталогів",
|
||||||
"allowPublish": "Публікація нових записів та сторінок",
|
"allowPublish": "Публікація нових записів та сторінок",
|
||||||
"allowSignup": "Дозволити користувачам реєструватися",
|
"allowSignup": "Дозволити користувачам реєструватися",
|
||||||
|
"hideLoginButton": "Hide the login button from public pages",
|
||||||
"avoidChanges": "(залишіть поле порожнім, щоб уникнути змін)",
|
"avoidChanges": "(залишіть поле порожнім, щоб уникнути змін)",
|
||||||
"branding": "Брендинг",
|
"branding": "Брендинг",
|
||||||
"brandingDirectoryPath": "Шлях до каталогу брендів",
|
"brandingDirectoryPath": "Шлях до каталогу брендів",
|
||||||
|
|||||||
@@ -166,6 +166,7 @@
|
|||||||
"allowNew": "Tạo tệp và thư mục mới",
|
"allowNew": "Tạo tệp và thư mục mới",
|
||||||
"allowPublish": "Xuất bản bài viết và trang mới",
|
"allowPublish": "Xuất bản bài viết và trang mới",
|
||||||
"allowSignup": "Cho phép người dùng đăng ký",
|
"allowSignup": "Cho phép người dùng đăng ký",
|
||||||
|
"hideLoginButton": "Hide the login button from public pages",
|
||||||
"avoidChanges": "(để trống để tránh thay đổi)",
|
"avoidChanges": "(để trống để tránh thay đổi)",
|
||||||
"branding": "Thương hiệu",
|
"branding": "Thương hiệu",
|
||||||
"brandingDirectoryPath": "Đường dẫn thư mục thương hiệu",
|
"brandingDirectoryPath": "Đường dẫn thư mục thương hiệu",
|
||||||
|
|||||||
@@ -166,6 +166,7 @@
|
|||||||
"allowNew": "创建新文件和文件夹",
|
"allowNew": "创建新文件和文件夹",
|
||||||
"allowPublish": "发布新的帖子与页面",
|
"allowPublish": "发布新的帖子与页面",
|
||||||
"allowSignup": "允许用户注册",
|
"allowSignup": "允许用户注册",
|
||||||
|
"hideLoginButton": "Hide the login button from public pages",
|
||||||
"avoidChanges": "(留空以避免更改)",
|
"avoidChanges": "(留空以避免更改)",
|
||||||
"branding": "品牌",
|
"branding": "品牌",
|
||||||
"brandingDirectoryPath": "品牌信息文件夹路径",
|
"brandingDirectoryPath": "品牌信息文件夹路径",
|
||||||
|
|||||||
@@ -166,6 +166,7 @@
|
|||||||
"allowNew": "建立新檔案和目錄",
|
"allowNew": "建立新檔案和目錄",
|
||||||
"allowPublish": "發佈新的貼文與頁面",
|
"allowPublish": "發佈新的貼文與頁面",
|
||||||
"allowSignup": "允許使用者註冊",
|
"allowSignup": "允許使用者註冊",
|
||||||
|
"hideLoginButton": "Hide the login button from public pages",
|
||||||
"avoidChanges": "(留空以避免更改)",
|
"avoidChanges": "(留空以避免更改)",
|
||||||
"branding": "品牌",
|
"branding": "品牌",
|
||||||
"brandingDirectoryPath": "品牌資訊資料夾路徑",
|
"brandingDirectoryPath": "品牌資訊資料夾路徑",
|
||||||
|
|||||||
1
frontend/src/types/settings.d.ts
vendored
1
frontend/src/types/settings.d.ts
vendored
@@ -1,6 +1,7 @@
|
|||||||
interface ISettings {
|
interface ISettings {
|
||||||
signup: boolean;
|
signup: boolean;
|
||||||
createUserDir: boolean;
|
createUserDir: boolean;
|
||||||
|
hideLoginButton: boolean;
|
||||||
minimumPasswordLength: number;
|
minimumPasswordLength: number;
|
||||||
userHomeBasePath: string;
|
userHomeBasePath: string;
|
||||||
defaults: SettingsDefaults;
|
defaults: SettingsDefaults;
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ const enableExec: boolean = window.FileBrowser.EnableExec;
|
|||||||
const tusSettings = window.FileBrowser.TusSettings;
|
const tusSettings = window.FileBrowser.TusSettings;
|
||||||
const origin = window.location.origin;
|
const origin = window.location.origin;
|
||||||
const tusEndpoint = `/api/tus`;
|
const tusEndpoint = `/api/tus`;
|
||||||
|
const hideLoginButton = window.FileBrowser.HideLoginButton;
|
||||||
|
|
||||||
export {
|
export {
|
||||||
name,
|
name,
|
||||||
@@ -39,4 +40,5 @@ export {
|
|||||||
tusSettings,
|
tusSettings,
|
||||||
origin,
|
origin,
|
||||||
tusEndpoint,
|
tusEndpoint,
|
||||||
|
hideLoginButton,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -207,7 +207,10 @@
|
|||||||
<h2 v-if="fileStore.req?.numDirs ?? false">
|
<h2 v-if="fileStore.req?.numDirs ?? false">
|
||||||
{{ t("files.folders") }}
|
{{ t("files.folders") }}
|
||||||
</h2>
|
</h2>
|
||||||
<div v-if="fileStore.req?.numDirs ?? false">
|
<div
|
||||||
|
v-if="fileStore.req?.numDirs ?? false"
|
||||||
|
@contextmenu="showContextMenu"
|
||||||
|
>
|
||||||
<item
|
<item
|
||||||
v-for="item in dirs"
|
v-for="item in dirs"
|
||||||
:key="base64(item.name)"
|
:key="base64(item.name)"
|
||||||
@@ -223,8 +226,13 @@
|
|||||||
</item>
|
</item>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h2 v-if="fileStore.req?.numFiles ?? false">{{ t("files.files") }}</h2>
|
<h2 v-if="fileStore.req?.numFiles ?? false">
|
||||||
<div v-if="fileStore.req?.numFiles ?? false">
|
{{ t("files.files") }}
|
||||||
|
</h2>
|
||||||
|
<div
|
||||||
|
v-if="fileStore.req?.numFiles ?? false"
|
||||||
|
@contextmenu="showContextMenu"
|
||||||
|
>
|
||||||
<item
|
<item
|
||||||
v-for="item in files"
|
v-for="item in files"
|
||||||
:key="base64(item.name)"
|
:key="base64(item.name)"
|
||||||
@@ -239,6 +247,53 @@
|
|||||||
>
|
>
|
||||||
</item>
|
</item>
|
||||||
</div>
|
</div>
|
||||||
|
<context-menu
|
||||||
|
:show="isContextMenuVisible"
|
||||||
|
:pos="contextMenuPos"
|
||||||
|
@hide="hideContextMenu"
|
||||||
|
>
|
||||||
|
<action
|
||||||
|
v-if="headerButtons.share"
|
||||||
|
icon="share"
|
||||||
|
:label="t('buttons.share')"
|
||||||
|
show="share"
|
||||||
|
/>
|
||||||
|
<action
|
||||||
|
v-if="headerButtons.rename"
|
||||||
|
icon="mode_edit"
|
||||||
|
:label="t('buttons.rename')"
|
||||||
|
show="rename"
|
||||||
|
/>
|
||||||
|
<action
|
||||||
|
v-if="headerButtons.copy"
|
||||||
|
id="copy-button"
|
||||||
|
icon="content_copy"
|
||||||
|
:label="t('buttons.copyFile')"
|
||||||
|
show="copy"
|
||||||
|
/>
|
||||||
|
<action
|
||||||
|
v-if="headerButtons.move"
|
||||||
|
id="move-button"
|
||||||
|
icon="forward"
|
||||||
|
:label="t('buttons.moveFile')"
|
||||||
|
show="move"
|
||||||
|
/>
|
||||||
|
<action
|
||||||
|
v-if="headerButtons.delete"
|
||||||
|
id="delete-button"
|
||||||
|
icon="delete"
|
||||||
|
:label="t('buttons.delete')"
|
||||||
|
show="delete"
|
||||||
|
/>
|
||||||
|
<action
|
||||||
|
v-if="headerButtons.download"
|
||||||
|
icon="file_download"
|
||||||
|
:label="t('buttons.download')"
|
||||||
|
@action="download"
|
||||||
|
:counter="fileStore.selectedCount"
|
||||||
|
/>
|
||||||
|
<action icon="info" :label="t('buttons.info')" show="info" />
|
||||||
|
</context-menu>
|
||||||
|
|
||||||
<input
|
<input
|
||||||
style="display: none"
|
style="display: none"
|
||||||
@@ -291,6 +346,7 @@ import HeaderBar from "@/components/header/HeaderBar.vue";
|
|||||||
import Action from "@/components/header/Action.vue";
|
import Action from "@/components/header/Action.vue";
|
||||||
import Search from "@/components/Search.vue";
|
import Search from "@/components/Search.vue";
|
||||||
import Item from "@/components/files/ListingItem.vue";
|
import Item from "@/components/files/ListingItem.vue";
|
||||||
|
import ContextMenu from "@/components/ContextMenu.vue";
|
||||||
import {
|
import {
|
||||||
computed,
|
computed,
|
||||||
inject,
|
inject,
|
||||||
@@ -310,6 +366,8 @@ const columnWidth = ref<number>(280);
|
|||||||
const dragCounter = ref<number>(0);
|
const dragCounter = ref<number>(0);
|
||||||
const width = ref<number>(window.innerWidth);
|
const width = ref<number>(window.innerWidth);
|
||||||
const itemWeight = ref<number>(0);
|
const itemWeight = ref<number>(0);
|
||||||
|
const isContextMenuVisible = ref<boolean>(false);
|
||||||
|
const contextMenuPos = ref<{ x: number; y: number }>({ x: 0, y: 0 });
|
||||||
|
|
||||||
const $showError = inject<IToastError>("$showError")!;
|
const $showError = inject<IToastError>("$showError")!;
|
||||||
|
|
||||||
@@ -438,7 +496,7 @@ watch(req, () => {
|
|||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
// Check the columns size for the first time.
|
// Check the columns size for the first time.
|
||||||
colunmsResize();
|
columnsResize();
|
||||||
|
|
||||||
// How much every listing item affects the window height
|
// How much every listing item affects the window height
|
||||||
setItemWeight();
|
setItemWeight();
|
||||||
@@ -642,7 +700,7 @@ const paste = (event: Event) => {
|
|||||||
action(overwrite, rename);
|
action(overwrite, rename);
|
||||||
};
|
};
|
||||||
|
|
||||||
const colunmsResize = () => {
|
const columnsResize = () => {
|
||||||
// Update the columns size based on the window width.
|
// Update the columns size based on the window width.
|
||||||
const items_ = css(["#listing.mosaic .item", ".mosaic#listing .item"]);
|
const items_ = css(["#listing.mosaic .item", ".mosaic#listing .item"]);
|
||||||
if (items_ === null) return;
|
if (items_ === null) return;
|
||||||
@@ -850,7 +908,7 @@ const toggleMultipleSelection = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const windowsResize = throttle(() => {
|
const windowsResize = throttle(() => {
|
||||||
colunmsResize();
|
columnsResize();
|
||||||
width.value = window.innerWidth;
|
width.value = window.innerWidth;
|
||||||
|
|
||||||
// Listing element is not displayed
|
// Listing element is not displayed
|
||||||
@@ -977,4 +1035,17 @@ const revealPreviousItem = () => {
|
|||||||
|
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const showContextMenu = (event: MouseEvent) => {
|
||||||
|
event.preventDefault();
|
||||||
|
isContextMenuVisible.value = true;
|
||||||
|
contextMenuPos.value = {
|
||||||
|
x: event.clientX + 8,
|
||||||
|
y: event.clientY + Math.floor(window.scrollY),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const hideContextMenu = () => {
|
||||||
|
isContextMenuVisible.value = false;
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -18,6 +18,11 @@
|
|||||||
{{ t("settings.createUserDir") }}
|
{{ t("settings.createUserDir") }}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<input type="checkbox" v-model="settings.hideLoginButton" />
|
||||||
|
{{ t("settings.hideLoginButton") }}
|
||||||
|
</p>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
<label class="small">{{ t("settings.userHomeBasePath") }}</label>
|
<label class="small">{{ t("settings.userHomeBasePath") }}</label>
|
||||||
<input
|
<input
|
||||||
|
|||||||
4
frontend/test-results/.last-run.json
Normal file
4
frontend/test-results/.last-run.json
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"status": "failed",
|
||||||
|
"failedTests": []
|
||||||
|
}
|
||||||
@@ -1,489 +0,0 @@
|
|||||||
import { test, expect, type Page } from "@playwright/test";
|
|
||||||
|
|
||||||
test.beforeEach(async ({ page }) => {
|
|
||||||
await page.goto("https://demo.playwright.dev/todomvc");
|
|
||||||
});
|
|
||||||
|
|
||||||
const TODO_ITEMS = [
|
|
||||||
"buy some cheese",
|
|
||||||
"feed the cat",
|
|
||||||
"book a doctors appointment",
|
|
||||||
];
|
|
||||||
|
|
||||||
test.describe("New Todo", () => {
|
|
||||||
test("should allow me to add todo items", async ({ page }) => {
|
|
||||||
// create a new todo locator
|
|
||||||
const newTodo = page.getByPlaceholder("What needs to be done?");
|
|
||||||
|
|
||||||
// Create 1st todo.
|
|
||||||
await newTodo.fill(TODO_ITEMS[0]);
|
|
||||||
await newTodo.press("Enter");
|
|
||||||
|
|
||||||
// Make sure the list only has one todo item.
|
|
||||||
await expect(page.getByTestId("todo-title")).toHaveText([TODO_ITEMS[0]]);
|
|
||||||
|
|
||||||
// Create 2nd todo.
|
|
||||||
await newTodo.fill(TODO_ITEMS[1]);
|
|
||||||
await newTodo.press("Enter");
|
|
||||||
|
|
||||||
// Make sure the list now has two todo items.
|
|
||||||
await expect(page.getByTestId("todo-title")).toHaveText([
|
|
||||||
TODO_ITEMS[0],
|
|
||||||
TODO_ITEMS[1],
|
|
||||||
]);
|
|
||||||
|
|
||||||
await checkNumberOfTodosInLocalStorage(page, 2);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("should clear text input field when an item is added", async ({
|
|
||||||
page,
|
|
||||||
}) => {
|
|
||||||
// create a new todo locator
|
|
||||||
const newTodo = page.getByPlaceholder("What needs to be done?");
|
|
||||||
|
|
||||||
// Create one todo item.
|
|
||||||
await newTodo.fill(TODO_ITEMS[0]);
|
|
||||||
await newTodo.press("Enter");
|
|
||||||
|
|
||||||
// Check that input is empty.
|
|
||||||
await expect(newTodo).toBeEmpty();
|
|
||||||
await checkNumberOfTodosInLocalStorage(page, 1);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("should append new items to the bottom of the list", async ({
|
|
||||||
page,
|
|
||||||
}) => {
|
|
||||||
// Create 3 items.
|
|
||||||
await createDefaultTodos(page);
|
|
||||||
|
|
||||||
// create a todo count locator
|
|
||||||
const todoCount = page.getByTestId("todo-count");
|
|
||||||
|
|
||||||
// Check test using different methods.
|
|
||||||
await expect(page.getByText("3 items left")).toBeVisible();
|
|
||||||
await expect(todoCount).toHaveText("3 items left");
|
|
||||||
await expect(todoCount).toContainText("3");
|
|
||||||
await expect(todoCount).toHaveText(/3/);
|
|
||||||
|
|
||||||
// Check all items in one call.
|
|
||||||
await expect(page.getByTestId("todo-title")).toHaveText(TODO_ITEMS);
|
|
||||||
await checkNumberOfTodosInLocalStorage(page, 3);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test.describe("Mark all as completed", () => {
|
|
||||||
test.beforeEach(async ({ page }) => {
|
|
||||||
await createDefaultTodos(page);
|
|
||||||
await checkNumberOfTodosInLocalStorage(page, 3);
|
|
||||||
});
|
|
||||||
|
|
||||||
test.afterEach(async ({ page }) => {
|
|
||||||
await checkNumberOfTodosInLocalStorage(page, 3);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("should allow me to mark all items as completed", async ({ page }) => {
|
|
||||||
// Complete all todos.
|
|
||||||
await page.getByLabel("Mark all as complete").check();
|
|
||||||
|
|
||||||
// Ensure all todos have 'completed' class.
|
|
||||||
await expect(page.getByTestId("todo-item")).toHaveClass([
|
|
||||||
"completed",
|
|
||||||
"completed",
|
|
||||||
"completed",
|
|
||||||
]);
|
|
||||||
await checkNumberOfCompletedTodosInLocalStorage(page, 3);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("should allow me to clear the complete state of all items", async ({
|
|
||||||
page,
|
|
||||||
}) => {
|
|
||||||
const toggleAll = page.getByLabel("Mark all as complete");
|
|
||||||
// Check and then immediately uncheck.
|
|
||||||
await toggleAll.check();
|
|
||||||
await toggleAll.uncheck();
|
|
||||||
|
|
||||||
// Should be no completed classes.
|
|
||||||
await expect(page.getByTestId("todo-item")).toHaveClass(["", "", ""]);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("complete all checkbox should update state when items are completed / cleared", async ({
|
|
||||||
page,
|
|
||||||
}) => {
|
|
||||||
const toggleAll = page.getByLabel("Mark all as complete");
|
|
||||||
await toggleAll.check();
|
|
||||||
await expect(toggleAll).toBeChecked();
|
|
||||||
await checkNumberOfCompletedTodosInLocalStorage(page, 3);
|
|
||||||
|
|
||||||
// Uncheck first todo.
|
|
||||||
const firstTodo = page.getByTestId("todo-item").nth(0);
|
|
||||||
await firstTodo.getByRole("checkbox").uncheck();
|
|
||||||
|
|
||||||
// Reuse toggleAll locator and make sure its not checked.
|
|
||||||
await expect(toggleAll).not.toBeChecked();
|
|
||||||
|
|
||||||
await firstTodo.getByRole("checkbox").check();
|
|
||||||
await checkNumberOfCompletedTodosInLocalStorage(page, 3);
|
|
||||||
|
|
||||||
// Assert the toggle all is checked again.
|
|
||||||
await expect(toggleAll).toBeChecked();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test.describe("Item", () => {
|
|
||||||
test("should allow me to mark items as complete", async ({ page }) => {
|
|
||||||
// create a new todo locator
|
|
||||||
const newTodo = page.getByPlaceholder("What needs to be done?");
|
|
||||||
|
|
||||||
// Create two items.
|
|
||||||
for (const item of TODO_ITEMS.slice(0, 2)) {
|
|
||||||
await newTodo.fill(item);
|
|
||||||
await newTodo.press("Enter");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check first item.
|
|
||||||
const firstTodo = page.getByTestId("todo-item").nth(0);
|
|
||||||
await firstTodo.getByRole("checkbox").check();
|
|
||||||
await expect(firstTodo).toHaveClass("completed");
|
|
||||||
|
|
||||||
// Check second item.
|
|
||||||
const secondTodo = page.getByTestId("todo-item").nth(1);
|
|
||||||
await expect(secondTodo).not.toHaveClass("completed");
|
|
||||||
await secondTodo.getByRole("checkbox").check();
|
|
||||||
|
|
||||||
// Assert completed class.
|
|
||||||
await expect(firstTodo).toHaveClass("completed");
|
|
||||||
await expect(secondTodo).toHaveClass("completed");
|
|
||||||
});
|
|
||||||
|
|
||||||
test("should allow me to un-mark items as complete", async ({ page }) => {
|
|
||||||
// create a new todo locator
|
|
||||||
const newTodo = page.getByPlaceholder("What needs to be done?");
|
|
||||||
|
|
||||||
// Create two items.
|
|
||||||
for (const item of TODO_ITEMS.slice(0, 2)) {
|
|
||||||
await newTodo.fill(item);
|
|
||||||
await newTodo.press("Enter");
|
|
||||||
}
|
|
||||||
|
|
||||||
const firstTodo = page.getByTestId("todo-item").nth(0);
|
|
||||||
const secondTodo = page.getByTestId("todo-item").nth(1);
|
|
||||||
const firstTodoCheckbox = firstTodo.getByRole("checkbox");
|
|
||||||
|
|
||||||
await firstTodoCheckbox.check();
|
|
||||||
await expect(firstTodo).toHaveClass("completed");
|
|
||||||
await expect(secondTodo).not.toHaveClass("completed");
|
|
||||||
await checkNumberOfCompletedTodosInLocalStorage(page, 1);
|
|
||||||
|
|
||||||
await firstTodoCheckbox.uncheck();
|
|
||||||
await expect(firstTodo).not.toHaveClass("completed");
|
|
||||||
await expect(secondTodo).not.toHaveClass("completed");
|
|
||||||
await checkNumberOfCompletedTodosInLocalStorage(page, 0);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("should allow me to edit an item", async ({ page }) => {
|
|
||||||
await createDefaultTodos(page);
|
|
||||||
|
|
||||||
const todoItems = page.getByTestId("todo-item");
|
|
||||||
const secondTodo = todoItems.nth(1);
|
|
||||||
await secondTodo.dblclick();
|
|
||||||
await expect(secondTodo.getByRole("textbox", { name: "Edit" })).toHaveValue(
|
|
||||||
TODO_ITEMS[1]
|
|
||||||
);
|
|
||||||
await secondTodo
|
|
||||||
.getByRole("textbox", { name: "Edit" })
|
|
||||||
.fill("buy some sausages");
|
|
||||||
await secondTodo.getByRole("textbox", { name: "Edit" }).press("Enter");
|
|
||||||
|
|
||||||
// Explicitly assert the new text value.
|
|
||||||
await expect(todoItems).toHaveText([
|
|
||||||
TODO_ITEMS[0],
|
|
||||||
"buy some sausages",
|
|
||||||
TODO_ITEMS[2],
|
|
||||||
]);
|
|
||||||
await checkTodosInLocalStorage(page, "buy some sausages");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test.describe("Editing", () => {
|
|
||||||
test.beforeEach(async ({ page }) => {
|
|
||||||
await createDefaultTodos(page);
|
|
||||||
await checkNumberOfTodosInLocalStorage(page, 3);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("should hide other controls when editing", async ({ page }) => {
|
|
||||||
const todoItem = page.getByTestId("todo-item").nth(1);
|
|
||||||
await todoItem.dblclick();
|
|
||||||
await expect(todoItem.getByRole("checkbox")).not.toBeVisible();
|
|
||||||
await expect(
|
|
||||||
todoItem.locator("label", {
|
|
||||||
hasText: TODO_ITEMS[1],
|
|
||||||
})
|
|
||||||
).not.toBeVisible();
|
|
||||||
await checkNumberOfTodosInLocalStorage(page, 3);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("should save edits on blur", async ({ page }) => {
|
|
||||||
const todoItems = page.getByTestId("todo-item");
|
|
||||||
await todoItems.nth(1).dblclick();
|
|
||||||
await todoItems
|
|
||||||
.nth(1)
|
|
||||||
.getByRole("textbox", { name: "Edit" })
|
|
||||||
.fill("buy some sausages");
|
|
||||||
await todoItems
|
|
||||||
.nth(1)
|
|
||||||
.getByRole("textbox", { name: "Edit" })
|
|
||||||
.dispatchEvent("blur");
|
|
||||||
|
|
||||||
await expect(todoItems).toHaveText([
|
|
||||||
TODO_ITEMS[0],
|
|
||||||
"buy some sausages",
|
|
||||||
TODO_ITEMS[2],
|
|
||||||
]);
|
|
||||||
await checkTodosInLocalStorage(page, "buy some sausages");
|
|
||||||
});
|
|
||||||
|
|
||||||
test("should trim entered text", async ({ page }) => {
|
|
||||||
const todoItems = page.getByTestId("todo-item");
|
|
||||||
await todoItems.nth(1).dblclick();
|
|
||||||
await todoItems
|
|
||||||
.nth(1)
|
|
||||||
.getByRole("textbox", { name: "Edit" })
|
|
||||||
.fill(" buy some sausages ");
|
|
||||||
await todoItems
|
|
||||||
.nth(1)
|
|
||||||
.getByRole("textbox", { name: "Edit" })
|
|
||||||
.press("Enter");
|
|
||||||
|
|
||||||
await expect(todoItems).toHaveText([
|
|
||||||
TODO_ITEMS[0],
|
|
||||||
"buy some sausages",
|
|
||||||
TODO_ITEMS[2],
|
|
||||||
]);
|
|
||||||
await checkTodosInLocalStorage(page, "buy some sausages");
|
|
||||||
});
|
|
||||||
|
|
||||||
test("should remove the item if an empty text string was entered", async ({
|
|
||||||
page,
|
|
||||||
}) => {
|
|
||||||
const todoItems = page.getByTestId("todo-item");
|
|
||||||
await todoItems.nth(1).dblclick();
|
|
||||||
await todoItems.nth(1).getByRole("textbox", { name: "Edit" }).fill("");
|
|
||||||
await todoItems
|
|
||||||
.nth(1)
|
|
||||||
.getByRole("textbox", { name: "Edit" })
|
|
||||||
.press("Enter");
|
|
||||||
|
|
||||||
await expect(todoItems).toHaveText([TODO_ITEMS[0], TODO_ITEMS[2]]);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("should cancel edits on escape", async ({ page }) => {
|
|
||||||
const todoItems = page.getByTestId("todo-item");
|
|
||||||
await todoItems.nth(1).dblclick();
|
|
||||||
await todoItems
|
|
||||||
.nth(1)
|
|
||||||
.getByRole("textbox", { name: "Edit" })
|
|
||||||
.fill("buy some sausages");
|
|
||||||
await todoItems
|
|
||||||
.nth(1)
|
|
||||||
.getByRole("textbox", { name: "Edit" })
|
|
||||||
.press("Escape");
|
|
||||||
await expect(todoItems).toHaveText(TODO_ITEMS);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test.describe("Counter", () => {
|
|
||||||
test("should display the current number of todo items", async ({ page }) => {
|
|
||||||
// create a new todo locator
|
|
||||||
const newTodo = page.getByPlaceholder("What needs to be done?");
|
|
||||||
|
|
||||||
// create a todo count locator
|
|
||||||
const todoCount = page.getByTestId("todo-count");
|
|
||||||
|
|
||||||
await newTodo.fill(TODO_ITEMS[0]);
|
|
||||||
await newTodo.press("Enter");
|
|
||||||
|
|
||||||
await expect(todoCount).toContainText("1");
|
|
||||||
|
|
||||||
await newTodo.fill(TODO_ITEMS[1]);
|
|
||||||
await newTodo.press("Enter");
|
|
||||||
await expect(todoCount).toContainText("2");
|
|
||||||
|
|
||||||
await checkNumberOfTodosInLocalStorage(page, 2);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test.describe("Clear completed button", () => {
|
|
||||||
test.beforeEach(async ({ page }) => {
|
|
||||||
await createDefaultTodos(page);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("should display the correct text", async ({ page }) => {
|
|
||||||
await page.locator(".todo-list li .toggle").first().check();
|
|
||||||
await expect(
|
|
||||||
page.getByRole("button", { name: "Clear completed" })
|
|
||||||
).toBeVisible();
|
|
||||||
});
|
|
||||||
|
|
||||||
test("should remove completed items when clicked", async ({ page }) => {
|
|
||||||
const todoItems = page.getByTestId("todo-item");
|
|
||||||
await todoItems.nth(1).getByRole("checkbox").check();
|
|
||||||
await page.getByRole("button", { name: "Clear completed" }).click();
|
|
||||||
await expect(todoItems).toHaveCount(2);
|
|
||||||
await expect(todoItems).toHaveText([TODO_ITEMS[0], TODO_ITEMS[2]]);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("should be hidden when there are no items that are completed", async ({
|
|
||||||
page,
|
|
||||||
}) => {
|
|
||||||
await page.locator(".todo-list li .toggle").first().check();
|
|
||||||
await page.getByRole("button", { name: "Clear completed" }).click();
|
|
||||||
await expect(
|
|
||||||
page.getByRole("button", { name: "Clear completed" })
|
|
||||||
).toBeHidden();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test.describe("Persistence", () => {
|
|
||||||
test("should persist its data", async ({ page }) => {
|
|
||||||
// create a new todo locator
|
|
||||||
const newTodo = page.getByPlaceholder("What needs to be done?");
|
|
||||||
|
|
||||||
for (const item of TODO_ITEMS.slice(0, 2)) {
|
|
||||||
await newTodo.fill(item);
|
|
||||||
await newTodo.press("Enter");
|
|
||||||
}
|
|
||||||
|
|
||||||
const todoItems = page.getByTestId("todo-item");
|
|
||||||
const firstTodoCheck = todoItems.nth(0).getByRole("checkbox");
|
|
||||||
await firstTodoCheck.check();
|
|
||||||
await expect(todoItems).toHaveText([TODO_ITEMS[0], TODO_ITEMS[1]]);
|
|
||||||
await expect(firstTodoCheck).toBeChecked();
|
|
||||||
await expect(todoItems).toHaveClass(["completed", ""]);
|
|
||||||
|
|
||||||
// Ensure there is 1 completed item.
|
|
||||||
await checkNumberOfCompletedTodosInLocalStorage(page, 1);
|
|
||||||
|
|
||||||
// Now reload.
|
|
||||||
await page.reload();
|
|
||||||
await expect(todoItems).toHaveText([TODO_ITEMS[0], TODO_ITEMS[1]]);
|
|
||||||
await expect(firstTodoCheck).toBeChecked();
|
|
||||||
await expect(todoItems).toHaveClass(["completed", ""]);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test.describe("Routing", () => {
|
|
||||||
test.beforeEach(async ({ page }) => {
|
|
||||||
await createDefaultTodos(page);
|
|
||||||
// make sure the app had a chance to save updated todos in storage
|
|
||||||
// before navigating to a new view, otherwise the items can get lost :(
|
|
||||||
// in some frameworks like Durandal
|
|
||||||
await checkTodosInLocalStorage(page, TODO_ITEMS[0]);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("should allow me to display active items", async ({ page }) => {
|
|
||||||
const todoItem = page.getByTestId("todo-item");
|
|
||||||
await page.getByTestId("todo-item").nth(1).getByRole("checkbox").check();
|
|
||||||
|
|
||||||
await checkNumberOfCompletedTodosInLocalStorage(page, 1);
|
|
||||||
await page.getByRole("link", { name: "Active" }).click();
|
|
||||||
await expect(todoItem).toHaveCount(2);
|
|
||||||
await expect(todoItem).toHaveText([TODO_ITEMS[0], TODO_ITEMS[2]]);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("should respect the back button", async ({ page }) => {
|
|
||||||
const todoItem = page.getByTestId("todo-item");
|
|
||||||
await page.getByTestId("todo-item").nth(1).getByRole("checkbox").check();
|
|
||||||
|
|
||||||
await checkNumberOfCompletedTodosInLocalStorage(page, 1);
|
|
||||||
|
|
||||||
await test.step("Showing all items", async () => {
|
|
||||||
await page.getByRole("link", { name: "All" }).click();
|
|
||||||
await expect(todoItem).toHaveCount(3);
|
|
||||||
});
|
|
||||||
|
|
||||||
await test.step("Showing active items", async () => {
|
|
||||||
await page.getByRole("link", { name: "Active" }).click();
|
|
||||||
});
|
|
||||||
|
|
||||||
await test.step("Showing completed items", async () => {
|
|
||||||
await page.getByRole("link", { name: "Completed" }).click();
|
|
||||||
});
|
|
||||||
|
|
||||||
await expect(todoItem).toHaveCount(1);
|
|
||||||
await page.goBack();
|
|
||||||
await expect(todoItem).toHaveCount(2);
|
|
||||||
await page.goBack();
|
|
||||||
await expect(todoItem).toHaveCount(3);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("should allow me to display completed items", async ({ page }) => {
|
|
||||||
await page.getByTestId("todo-item").nth(1).getByRole("checkbox").check();
|
|
||||||
await checkNumberOfCompletedTodosInLocalStorage(page, 1);
|
|
||||||
await page.getByRole("link", { name: "Completed" }).click();
|
|
||||||
await expect(page.getByTestId("todo-item")).toHaveCount(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("should allow me to display all items", async ({ page }) => {
|
|
||||||
await page.getByTestId("todo-item").nth(1).getByRole("checkbox").check();
|
|
||||||
await checkNumberOfCompletedTodosInLocalStorage(page, 1);
|
|
||||||
await page.getByRole("link", { name: "Active" }).click();
|
|
||||||
await page.getByRole("link", { name: "Completed" }).click();
|
|
||||||
await page.getByRole("link", { name: "All" }).click();
|
|
||||||
await expect(page.getByTestId("todo-item")).toHaveCount(3);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("should highlight the currently applied filter", async ({ page }) => {
|
|
||||||
await expect(page.getByRole("link", { name: "All" })).toHaveClass(
|
|
||||||
"selected"
|
|
||||||
);
|
|
||||||
|
|
||||||
//create locators for active and completed links
|
|
||||||
const activeLink = page.getByRole("link", { name: "Active" });
|
|
||||||
const completedLink = page.getByRole("link", { name: "Completed" });
|
|
||||||
await activeLink.click();
|
|
||||||
|
|
||||||
// Page change - active items.
|
|
||||||
await expect(activeLink).toHaveClass("selected");
|
|
||||||
await completedLink.click();
|
|
||||||
|
|
||||||
// Page change - completed items.
|
|
||||||
await expect(completedLink).toHaveClass("selected");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
async function createDefaultTodos(page: Page) {
|
|
||||||
// create a new todo locator
|
|
||||||
const newTodo = page.getByPlaceholder("What needs to be done?");
|
|
||||||
|
|
||||||
for (const item of TODO_ITEMS) {
|
|
||||||
await newTodo.fill(item);
|
|
||||||
await newTodo.press("Enter");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function checkNumberOfTodosInLocalStorage(page: Page, expected: number) {
|
|
||||||
return await page.waitForFunction((e) => {
|
|
||||||
return JSON.parse(localStorage["react-todos"]).length === e;
|
|
||||||
}, expected);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function checkNumberOfCompletedTodosInLocalStorage(
|
|
||||||
page: Page,
|
|
||||||
expected: number
|
|
||||||
) {
|
|
||||||
return await page.waitForFunction((e) => {
|
|
||||||
return (
|
|
||||||
JSON.parse(localStorage["react-todos"]).filter(
|
|
||||||
(todo: any) => todo.completed
|
|
||||||
).length === e
|
|
||||||
);
|
|
||||||
}, expected);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function checkTodosInLocalStorage(page: Page, title: string) {
|
|
||||||
return await page.waitForFunction((t) => {
|
|
||||||
return JSON.parse(localStorage["react-todos"])
|
|
||||||
.map((todo: any) => todo.title)
|
|
||||||
.includes(t);
|
|
||||||
}, title);
|
|
||||||
}
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
import { test, expect } from "./fixtures/auth";
|
|
||||||
|
|
||||||
test("redirect to login", async ({ page }) => {
|
|
||||||
await page.goto("/");
|
|
||||||
await expect(page).toHaveURL(/\/login/);
|
|
||||||
|
|
||||||
await page.goto("/files/");
|
|
||||||
await expect(page).toHaveURL(/\/login\?redirect=\/files\//);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("login and logout", async ({ authPage, page, context }) => {
|
|
||||||
await authPage.goto();
|
|
||||||
await expect(page).toHaveTitle(/Login - File Browser$/);
|
|
||||||
|
|
||||||
await authPage.loginAs("fake", "fake");
|
|
||||||
await expect(authPage.wrongCredentials).toBeVisible();
|
|
||||||
|
|
||||||
await authPage.loginAs();
|
|
||||||
await expect(authPage.wrongCredentials).toBeHidden();
|
|
||||||
// await page.waitForURL("**/files/", { timeout: 5000 });
|
|
||||||
await expect(page).toHaveTitle(/.*Files - File Browser$/);
|
|
||||||
|
|
||||||
let cookies = await context.cookies();
|
|
||||||
expect(cookies.find((c) => c.name == "auth")?.value).toBeDefined();
|
|
||||||
|
|
||||||
await authPage.logout();
|
|
||||||
// await page.waitForURL("**/login", { timeout: 5000 });
|
|
||||||
await expect(page).toHaveTitle(/Login - File Browser$/);
|
|
||||||
|
|
||||||
cookies = await context.cookies();
|
|
||||||
expect(cookies.find((c) => c.name == "auth")?.value).toBeUndefined();
|
|
||||||
});
|
|
||||||
40
frontend/tests/fixtures/auth.ts
vendored
40
frontend/tests/fixtures/auth.ts
vendored
@@ -1,40 +0,0 @@
|
|||||||
import {
|
|
||||||
type Page,
|
|
||||||
type Locator,
|
|
||||||
test as base,
|
|
||||||
expect,
|
|
||||||
} from "@playwright/test";
|
|
||||||
|
|
||||||
export class AuthPage {
|
|
||||||
public readonly wrongCredentials: Locator;
|
|
||||||
|
|
||||||
constructor(public readonly page: Page) {
|
|
||||||
this.wrongCredentials = this.page.locator("div.wrong");
|
|
||||||
}
|
|
||||||
|
|
||||||
async goto() {
|
|
||||||
await this.page.goto("/login");
|
|
||||||
}
|
|
||||||
|
|
||||||
async loginAs(username = "admin", password = "admin") {
|
|
||||||
await this.page.getByPlaceholder("Username").fill(username);
|
|
||||||
await this.page.getByPlaceholder("Password").fill(password);
|
|
||||||
await this.page.getByRole("button", { name: "Login" }).click();
|
|
||||||
}
|
|
||||||
|
|
||||||
async logout() {
|
|
||||||
await this.page.getByRole("button", { name: "Logout" }).click();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const test = base.extend<{ authPage: AuthPage }>({
|
|
||||||
authPage: async ({ page }, use) => {
|
|
||||||
const authPage = new AuthPage(page);
|
|
||||||
await authPage.goto();
|
|
||||||
await authPage.loginAs();
|
|
||||||
await use(authPage);
|
|
||||||
// await authPage.logout();
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export { test, expect };
|
|
||||||
61
frontend/tests/fixtures/settings.ts
vendored
61
frontend/tests/fixtures/settings.ts
vendored
@@ -1,61 +0,0 @@
|
|||||||
import {
|
|
||||||
type Locator,
|
|
||||||
type Page,
|
|
||||||
test as base,
|
|
||||||
expect,
|
|
||||||
} from "@playwright/test";
|
|
||||||
import { AuthPage } from "./auth";
|
|
||||||
|
|
||||||
type SettingsType = "profile" | "shares" | "global" | "users";
|
|
||||||
|
|
||||||
export class SettingsPage {
|
|
||||||
public readonly hideDotfiles: Locator; // checkbox
|
|
||||||
public readonly singleClick: Locator; // checkbox
|
|
||||||
public readonly dateFormat: Locator; // checkbox
|
|
||||||
private readonly languages: Locator; // selection
|
|
||||||
private readonly submitProfile: Locator; // submit
|
|
||||||
private readonly submitPassword: Locator; // submit
|
|
||||||
|
|
||||||
constructor(public readonly page: Page) {
|
|
||||||
this.hideDotfiles = this.page.locator('input[name="hideDotfiles"]');
|
|
||||||
this.singleClick = this.page.locator('input[name="singleClick"]');
|
|
||||||
this.dateFormat = this.page.locator('input[name="dateFormat"]');
|
|
||||||
this.languages = this.page.locator('select[name="selectLanguage"]');
|
|
||||||
this.submitProfile = this.page.locator('input[name="submitProfile"]');
|
|
||||||
this.submitPassword = this.page.locator('input[name="submitPassword"]');
|
|
||||||
}
|
|
||||||
|
|
||||||
async goto(type: SettingsType = "profile") {
|
|
||||||
await this.page.goto(`/settings/${type}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
async setLanguage(locale: string = "en") {
|
|
||||||
await this.languages.selectOption(locale);
|
|
||||||
}
|
|
||||||
|
|
||||||
async saveProfile() {
|
|
||||||
await this.submitProfile.click();
|
|
||||||
}
|
|
||||||
|
|
||||||
async savePassword() {
|
|
||||||
await this.submitPassword.click();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const test = base.extend<{ settingsPage: SettingsPage }>({
|
|
||||||
page: async ({ page }, use) => {
|
|
||||||
// Sign in with our account.
|
|
||||||
const authPage = new AuthPage(page);
|
|
||||||
await authPage.goto();
|
|
||||||
await authPage.loginAs();
|
|
||||||
await expect(page).toHaveTitle(/.*Files - File Browser$/);
|
|
||||||
// Use signed-in page in the test.
|
|
||||||
await use(page);
|
|
||||||
},
|
|
||||||
settingsPage: async ({ page }, use) => {
|
|
||||||
const settingsPage = new SettingsPage(page);
|
|
||||||
await use(settingsPage);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export { test, expect };
|
|
||||||
20
frontend/tests/fixtures/toast.ts
vendored
20
frontend/tests/fixtures/toast.ts
vendored
@@ -1,20 +0,0 @@
|
|||||||
//classes: Vue-Toastification__toast Vue-Toastification__toast--success bottom-center
|
|
||||||
import { type Page, type Locator, expect } from "@playwright/test";
|
|
||||||
|
|
||||||
export class Toast {
|
|
||||||
private readonly success: Locator;
|
|
||||||
private readonly error: Locator;
|
|
||||||
|
|
||||||
constructor(public readonly page: Page) {
|
|
||||||
this.success = this.page.locator("div.Vue-Toastification__toast--success");
|
|
||||||
this.error = this.page.locator("div.Vue-Toastification__toast--error");
|
|
||||||
}
|
|
||||||
|
|
||||||
async isSuccess() {
|
|
||||||
await expect(this.success).toBeVisible();
|
|
||||||
}
|
|
||||||
|
|
||||||
async isError() {
|
|
||||||
await expect(this.error).toBeVisible();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
import { test, expect } from "./fixtures/settings";
|
|
||||||
import { Toast } from "./fixtures/toast";
|
|
||||||
|
|
||||||
// test.describe("profile settings", () => {
|
|
||||||
test("settings button", async ({ page }) => {
|
|
||||||
const button = page.getByLabel("Settings", { exact: true });
|
|
||||||
await expect(button).toBeVisible();
|
|
||||||
await button.click();
|
|
||||||
await expect(page).toHaveTitle(/^Profile Settings/);
|
|
||||||
await expect(
|
|
||||||
page.getByRole("heading", { name: "Profile Settings" })
|
|
||||||
).toBeVisible();
|
|
||||||
});
|
|
||||||
|
|
||||||
test("set locale", async ({ settingsPage, page }) => {
|
|
||||||
const toast = new Toast(page);
|
|
||||||
|
|
||||||
await settingsPage.goto("profile");
|
|
||||||
await expect(page).toHaveTitle(/^Profile Settings/);
|
|
||||||
// await settingsPage.saveProfile();
|
|
||||||
// await toast.isSuccess();
|
|
||||||
// await expect(
|
|
||||||
// page.getByText("Settings updated!", { exact: true })
|
|
||||||
// ).toBeVisible();
|
|
||||||
|
|
||||||
await settingsPage.setLanguage("hu");
|
|
||||||
await settingsPage.saveProfile();
|
|
||||||
await toast.isSuccess();
|
|
||||||
await expect(
|
|
||||||
page.getByText("Beállítások frissítve!", { exact: true })
|
|
||||||
).toBeVisible();
|
|
||||||
await expect(
|
|
||||||
page.getByRole("heading", { name: "Profilbeállítások" })
|
|
||||||
).toBeVisible();
|
|
||||||
|
|
||||||
await settingsPage.setLanguage("en");
|
|
||||||
await settingsPage.saveProfile();
|
|
||||||
await toast.isSuccess();
|
|
||||||
await expect(
|
|
||||||
page.getByText("Settings updated!", { exact: true })
|
|
||||||
).toBeVisible();
|
|
||||||
await expect(
|
|
||||||
page.getByRole("heading", { name: "Profile Settings" })
|
|
||||||
).toBeVisible();
|
|
||||||
});
|
|
||||||
// });
|
|
||||||
@@ -1,13 +1,29 @@
|
|||||||
{
|
{
|
||||||
"extends": "@vue/tsconfig/tsconfig.dom.json",
|
"extends": "@vue/tsconfig/tsconfig.dom.json",
|
||||||
"include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
|
"include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
|
||||||
"exclude": ["src/**/__tests__/*"],
|
"exclude": [
|
||||||
|
"src/**/__tests__/*",
|
||||||
|
// Excluding non-TS Vue files which use the old Options API.
|
||||||
|
// This can be removed once those files are properly migrated to
|
||||||
|
// the new Composition API with TS support.
|
||||||
|
"src/components/Shell.vue",
|
||||||
|
"src/components/prompts/Copy.vue",
|
||||||
|
"src/components/prompts/Move.vue",
|
||||||
|
"src/components/prompts/Delete.vue",
|
||||||
|
"src/components/prompts/FileList.vue",
|
||||||
|
"src/components/prompts/Rename.vue",
|
||||||
|
"src/components/prompts/Share.vue"
|
||||||
|
],
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"composite": true,
|
"composite": true,
|
||||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
||||||
"types": ["vite/client", "@intlify/unplugin-vue-i18n/messages"],
|
"types": ["vite/client", "@intlify/unplugin-vue-i18n/messages"],
|
||||||
"paths": {
|
"paths": {
|
||||||
"@/*": ["./src/*"]
|
"@/*": ["./src/*"]
|
||||||
}
|
},
|
||||||
|
// Version 0.8.0 of @vue/tsconfig enabled this automatically.
|
||||||
|
// Disabling for now since it's causing quite a lot of errors.
|
||||||
|
// Should be revisited.
|
||||||
|
"noUncheckedIndexedAccess": false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,8 +4,7 @@
|
|||||||
"vite.config.*",
|
"vite.config.*",
|
||||||
"vitest.config.*",
|
"vitest.config.*",
|
||||||
"cypress.config.*",
|
"cypress.config.*",
|
||||||
"nightwatch.conf.*",
|
"nightwatch.conf.*"
|
||||||
"playwright.config.*"
|
|
||||||
],
|
],
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"composite": true,
|
"composite": true,
|
||||||
@@ -13,8 +12,6 @@
|
|||||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
||||||
"module": "ESNext",
|
"module": "ESNext",
|
||||||
"moduleResolution": "Bundler",
|
"moduleResolution": "Bundler",
|
||||||
"types": [
|
"types": ["node"]
|
||||||
"node"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +0,0 @@
|
|||||||
{
|
|
||||||
"extends": "./tsconfig.app.json",
|
|
||||||
// vue-tsc wont shut up about error TS9005
|
|
||||||
// in non-TS vue files so exclude them
|
|
||||||
"exclude": [
|
|
||||||
"src/components/Shell.vue",
|
|
||||||
"src/components/prompts/Copy.vue",
|
|
||||||
"src/components/prompts/Move.vue",
|
|
||||||
"src/components/prompts/Delete.vue",
|
|
||||||
"src/components/prompts/FileList.vue",
|
|
||||||
"src/components/prompts/Rename.vue",
|
|
||||||
"src/components/prompts/Share.vue"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
2
go.mod
2
go.mod
@@ -8,7 +8,7 @@ require (
|
|||||||
github.com/disintegration/imaging v1.6.2
|
github.com/disintegration/imaging v1.6.2
|
||||||
github.com/dsoprea/go-exif/v3 v3.0.1
|
github.com/dsoprea/go-exif/v3 v3.0.1
|
||||||
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568
|
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568
|
||||||
github.com/golang-jwt/jwt/v4 v4.5.2
|
github.com/golang-jwt/jwt/v5 v5.3.0
|
||||||
github.com/gorilla/mux v1.8.1
|
github.com/gorilla/mux v1.8.1
|
||||||
github.com/gorilla/websocket v1.5.3
|
github.com/gorilla/websocket v1.5.3
|
||||||
github.com/jellydator/ttlcache/v3 v3.4.0
|
github.com/jellydator/ttlcache/v3 v3.4.0
|
||||||
|
|||||||
4
go.sum
4
go.sum
@@ -97,8 +97,8 @@ github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
|
|||||||
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
|
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
|
||||||
github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs=
|
github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs=
|
||||||
github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
||||||
github.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI=
|
github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo=
|
||||||
github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
|
||||||
github.com/golang/geo v0.0.0-20190916061304-5b978397cfec/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI=
|
github.com/golang/geo v0.0.0-20190916061304-5b978397cfec/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI=
|
||||||
github.com/golang/geo v0.0.0-20200319012246-673a6f80352d/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI=
|
github.com/golang/geo v0.0.0-20200319012246-673a6f80352d/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI=
|
||||||
github.com/golang/geo v0.0.0-20210211234256-740aa86cb551/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI=
|
github.com/golang/geo v0.0.0-20210211234256-740aa86cb551/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI=
|
||||||
|
|||||||
14
http/auth.go
14
http/auth.go
@@ -9,8 +9,8 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/golang-jwt/jwt/v4"
|
"github.com/golang-jwt/jwt/v5"
|
||||||
"github.com/golang-jwt/jwt/v4/request"
|
"github.com/golang-jwt/jwt/v5/request"
|
||||||
|
|
||||||
fbErrors "github.com/filebrowser/filebrowser/v2/errors"
|
fbErrors "github.com/filebrowser/filebrowser/v2/errors"
|
||||||
"github.com/filebrowser/filebrowser/v2/users"
|
"github.com/filebrowser/filebrowser/v2/users"
|
||||||
@@ -69,15 +69,19 @@ func withUser(fn handleFunc) handleFunc {
|
|||||||
|
|
||||||
var tk authToken
|
var tk authToken
|
||||||
token, err := request.ParseFromRequest(r, &extractor{}, keyFunc, request.WithClaims(&tk))
|
token, err := request.ParseFromRequest(r, &extractor{}, keyFunc, request.WithClaims(&tk))
|
||||||
|
|
||||||
if err != nil || !token.Valid {
|
if err != nil || !token.Valid {
|
||||||
return http.StatusUnauthorized, nil
|
return http.StatusUnauthorized, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
expired := !tk.VerifyExpiresAt(time.Now().Add(time.Hour), true)
|
err = jwt.NewValidator(jwt.WithExpirationRequired()).Validate(tk)
|
||||||
|
if err != nil {
|
||||||
|
return http.StatusUnauthorized, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
expiresSoon := tk.ExpiresAt != nil && time.Until(tk.ExpiresAt.Time) < time.Hour
|
||||||
updated := tk.IssuedAt != nil && tk.IssuedAt.Unix() < d.store.Users.LastUpdate(tk.User.ID)
|
updated := tk.IssuedAt != nil && tk.IssuedAt.Unix() < d.store.Users.LastUpdate(tk.User.ID)
|
||||||
|
|
||||||
if expired || updated {
|
if expiresSoon || updated {
|
||||||
w.Header().Add("X-Renew-Token", "true")
|
w.Header().Add("X-Renew-Token", "true")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,14 +0,0 @@
|
|||||||
//go:build dev
|
|
||||||
|
|
||||||
package http
|
|
||||||
|
|
||||||
// global headers to append to every response
|
|
||||||
// cross-origin headers are necessary to be able to
|
|
||||||
// access them from a different URL during development
|
|
||||||
var globalHeaders = map[string]string{
|
|
||||||
"Cache-Control": "no-cache, no-store, must-revalidate",
|
|
||||||
"Access-Control-Allow-Origin": "*",
|
|
||||||
"Access-Control-Allow-Headers": "*",
|
|
||||||
"Access-Control-Allow-Methods": "*",
|
|
||||||
"Access-Control-Allow-Credentials": "true",
|
|
||||||
}
|
|
||||||
@@ -124,12 +124,12 @@ func createPreview(imgSvc ImgService, fileCache FileCache,
|
|||||||
options []img.Option
|
options []img.Option
|
||||||
)
|
)
|
||||||
|
|
||||||
switch {
|
switch previewSize {
|
||||||
case previewSize == PreviewSizeBig:
|
case PreviewSizeBig:
|
||||||
width = 1080
|
width = 1080
|
||||||
height = 1080
|
height = 1080
|
||||||
options = append(options, img.WithMode(img.ResizeModeFit), img.WithQuality(img.QualityMedium))
|
options = append(options, img.WithMode(img.ResizeModeFit), img.WithQuality(img.QualityMedium))
|
||||||
case previewSize == PreviewSizeThumb:
|
case PreviewSizeThumb:
|
||||||
width = 256
|
width = 256
|
||||||
height = 256
|
height = 256
|
||||||
options = append(options, img.WithMode(img.ResizeModeFill), img.WithQuality(img.QualityLow), img.WithFormat(img.FormatJpeg))
|
options = append(options, img.WithMode(img.ResizeModeFill), img.WithQuality(img.QualityLow), img.WithFormat(img.FormatJpeg))
|
||||||
|
|||||||
@@ -98,8 +98,8 @@ var publicShareHandler = withHashFile(func(w http.ResponseWriter, r *http.Reques
|
|||||||
file := d.raw.(*files.FileInfo)
|
file := d.raw.(*files.FileInfo)
|
||||||
|
|
||||||
if file.IsDir {
|
if file.IsDir {
|
||||||
file.Listing.Sorting = files.Sorting{By: "name", Asc: false}
|
file.Sorting = files.Sorting{By: "name", Asc: false}
|
||||||
file.Listing.ApplySort()
|
file.ApplySort()
|
||||||
return renderJSON(w, r, file)
|
return renderJSON(w, r, file)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ func parseQueryFiles(r *http.Request, f *files.FileInfo, _ *users.User) ([]strin
|
|||||||
fileSlice = append(fileSlice, f.Path)
|
fileSlice = append(fileSlice, f.Path)
|
||||||
} else {
|
} else {
|
||||||
for _, name := range names {
|
for _, name := range names {
|
||||||
name, err := url.QueryUnescape(strings.Replace(name, "+", "%2B", -1)) //nolint:govet
|
name, err := url.QueryUnescape(strings.ReplaceAll(name, "+", "%2B"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,8 +37,8 @@ var resourceGetHandler = withUser(func(w http.ResponseWriter, r *http.Request, d
|
|||||||
}
|
}
|
||||||
|
|
||||||
if file.IsDir {
|
if file.IsDir {
|
||||||
file.Listing.Sorting = d.user.Sorting
|
file.Sorting = d.user.Sorting
|
||||||
file.Listing.ApplySort()
|
file.ApplySort()
|
||||||
return renderJSON(w, r, file)
|
return renderJSON(w, r, file)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import (
|
|||||||
|
|
||||||
type settingsData struct {
|
type settingsData struct {
|
||||||
Signup bool `json:"signup"`
|
Signup bool `json:"signup"`
|
||||||
|
HideLoginButton bool `json:"hideLoginButton"`
|
||||||
CreateUserDir bool `json:"createUserDir"`
|
CreateUserDir bool `json:"createUserDir"`
|
||||||
MinimumPasswordLength uint `json:"minimumPasswordLength"`
|
MinimumPasswordLength uint `json:"minimumPasswordLength"`
|
||||||
UserHomeBasePath string `json:"userHomeBasePath"`
|
UserHomeBasePath string `json:"userHomeBasePath"`
|
||||||
@@ -24,6 +25,7 @@ type settingsData struct {
|
|||||||
var settingsGetHandler = withAdmin(func(w http.ResponseWriter, r *http.Request, d *data) (int, error) {
|
var settingsGetHandler = withAdmin(func(w http.ResponseWriter, r *http.Request, d *data) (int, error) {
|
||||||
data := &settingsData{
|
data := &settingsData{
|
||||||
Signup: d.settings.Signup,
|
Signup: d.settings.Signup,
|
||||||
|
HideLoginButton: d.settings.HideLoginButton,
|
||||||
CreateUserDir: d.settings.CreateUserDir,
|
CreateUserDir: d.settings.CreateUserDir,
|
||||||
MinimumPasswordLength: d.settings.MinimumPasswordLength,
|
MinimumPasswordLength: d.settings.MinimumPasswordLength,
|
||||||
UserHomeBasePath: d.settings.UserHomeBasePath,
|
UserHomeBasePath: d.settings.UserHomeBasePath,
|
||||||
@@ -55,6 +57,7 @@ var settingsPutHandler = withAdmin(func(_ http.ResponseWriter, r *http.Request,
|
|||||||
d.settings.Tus = req.Tus
|
d.settings.Tus = req.Tus
|
||||||
d.settings.Shell = req.Shell
|
d.settings.Shell = req.Shell
|
||||||
d.settings.Commands = req.Commands
|
d.settings.Commands = req.Commands
|
||||||
|
d.settings.HideLoginButton = req.HideLoginButton
|
||||||
|
|
||||||
err = d.store.Settings.Save(d.settings)
|
err = d.store.Settings.Save(d.settings)
|
||||||
return errToStatus(err), err
|
return errToStatus(err), err
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ func handleWithStaticData(w http.ResponseWriter, _ *http.Request, d *data, fSys
|
|||||||
"ResizePreview": d.server.ResizePreview,
|
"ResizePreview": d.server.ResizePreview,
|
||||||
"EnableExec": d.server.EnableExec,
|
"EnableExec": d.server.EnableExec,
|
||||||
"TusSettings": d.settings.Tus,
|
"TusSettings": d.settings.Tus,
|
||||||
|
"HideLoginButton": d.settings.HideLoginButton,
|
||||||
}
|
}
|
||||||
|
|
||||||
if d.settings.Branding.Files != "" {
|
if d.settings.Branding.Files != "" {
|
||||||
|
|||||||
@@ -4,7 +4,8 @@
|
|||||||
"config:recommended",
|
"config:recommended",
|
||||||
"group:allNonMajor",
|
"group:allNonMajor",
|
||||||
"group:allDigest",
|
"group:allDigest",
|
||||||
":disableDependencyDashboard"
|
":disableDependencyDashboard",
|
||||||
|
":semanticCommitTypeAll(chore)"
|
||||||
],
|
],
|
||||||
"postUpdateOptions": [
|
"postUpdateOptions": [
|
||||||
"gomodUpdateImportPaths",
|
"gomodUpdateImportPaths",
|
||||||
|
|||||||
@@ -48,8 +48,8 @@ func parseSearch(value string) *searchOptions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// removes the options from the value
|
// removes the options from the value
|
||||||
value = strings.Replace(value, "case:insensitive", "", -1)
|
value = strings.ReplaceAll(value, "case:insensitive", "")
|
||||||
value = strings.Replace(value, "case:sensitive", "", -1)
|
value = strings.ReplaceAll(value, "case:sensitive", "")
|
||||||
value = strings.TrimSpace(value)
|
value = strings.TrimSpace(value)
|
||||||
|
|
||||||
types := typeRegexp.FindAllStringSubmatch(value, -1)
|
types := typeRegexp.FindAllStringSubmatch(value, -1)
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ func (s *Settings) MakeUserDir(username, userScope, serverRoot string) (string,
|
|||||||
func cleanUsername(s string) string {
|
func cleanUsername(s string) string {
|
||||||
// Remove any trailing space to avoid ending on -
|
// Remove any trailing space to avoid ending on -
|
||||||
s = strings.Trim(s, " ")
|
s = strings.Trim(s, " ")
|
||||||
s = strings.Replace(s, "..", "", -1)
|
s = strings.ReplaceAll(s, "..", "")
|
||||||
|
|
||||||
// Replace all characters which not in the list `0-9A-Za-z@_\-.` with a dash
|
// Replace all characters which not in the list `0-9A-Za-z@_\-.` with a dash
|
||||||
s = invalidFilenameChars.ReplaceAllString(s, "-")
|
s = invalidFilenameChars.ReplaceAllString(s, "-")
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ type AuthMethod string
|
|||||||
type Settings struct {
|
type Settings struct {
|
||||||
Key []byte `json:"key"`
|
Key []byte `json:"key"`
|
||||||
Signup bool `json:"signup"`
|
Signup bool `json:"signup"`
|
||||||
|
HideLoginButton bool `json:"hideLoginButton"`
|
||||||
CreateUserDir bool `json:"createUserDir"`
|
CreateUserDir bool `json:"createUserDir"`
|
||||||
UserHomeBasePath string `json:"userHomeBasePath"`
|
UserHomeBasePath string `json:"userHomeBasePath"`
|
||||||
Defaults UserDefaults `json:"defaults"`
|
Defaults UserDefaults `json:"defaults"`
|
||||||
@@ -34,6 +35,7 @@ type Settings struct {
|
|||||||
MinimumPasswordLength uint `json:"minimumPasswordLength"`
|
MinimumPasswordLength uint `json:"minimumPasswordLength"`
|
||||||
FileMode fs.FileMode `json:"fileMode"`
|
FileMode fs.FileMode `json:"fileMode"`
|
||||||
DirMode fs.FileMode `json:"dirMode"`
|
DirMode fs.FileMode `json:"dirMode"`
|
||||||
|
HideDotfiles bool `json:"hideDotfiles"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetRules implements rules.Provider.
|
// GetRules implements rules.Provider.
|
||||||
|
|||||||
27
tools.mk
27
tools.mk
@@ -1,27 +0,0 @@
|
|||||||
include common.mk
|
|
||||||
|
|
||||||
# tools
|
|
||||||
TOOLS_DIR := $(BASE_PATH)/tools
|
|
||||||
TOOLS_GO_DEPS := $(TOOLS_DIR)/go.mod $(TOOLS_DIR)/go.sum
|
|
||||||
TOOLS_BIN := $(TOOLS_DIR)/bin
|
|
||||||
$(eval $(shell mkdir -p $(TOOLS_BIN)))
|
|
||||||
PATH := $(TOOLS_BIN):$(PATH)
|
|
||||||
export PATH
|
|
||||||
|
|
||||||
.PHONY: clean-tools
|
|
||||||
clean-tools:
|
|
||||||
$Q rm -rf $(TOOLS_BIN)
|
|
||||||
|
|
||||||
goimports=$(TOOLS_BIN)/goimports
|
|
||||||
$(goimports): $(TOOLS_GO_DEPS)
|
|
||||||
$Q cd $(TOOLS_DIR) && $(go) build -o $@ golang.org/x/tools/cmd/goimports
|
|
||||||
|
|
||||||
golangci-lint=$(TOOLS_BIN)/golangci-lint
|
|
||||||
$(golangci-lint): $(TOOLS_GO_DEPS)
|
|
||||||
$Q cd $(TOOLS_DIR) && $(go) build -o $@ github.com/golangci/golangci-lint/v2/cmd/golangci-lint
|
|
||||||
|
|
||||||
# js tools
|
|
||||||
TOOLS_JS_DEPS=$(TOOLS_DIR)/node_modules/.modified
|
|
||||||
$(TOOLS_JS_DEPS): $(TOOLS_DIR)/package.json $(TOOLS_DIR)/yarn.lock
|
|
||||||
$Q cd ${TOOLS_DIR} && yarn install
|
|
||||||
$Q touch -am $@
|
|
||||||
216
tools/go.mod
216
tools/go.mod
@@ -1,216 +0,0 @@
|
|||||||
module github.com/filebrowser/filebrowser/v2/tools
|
|
||||||
|
|
||||||
go 1.25
|
|
||||||
|
|
||||||
require (
|
|
||||||
github.com/golangci/golangci-lint/v2 v2.6.1
|
|
||||||
golang.org/x/tools v0.38.0
|
|
||||||
)
|
|
||||||
|
|
||||||
require (
|
|
||||||
4d63.com/gocheckcompilerdirectives v1.3.0 // indirect
|
|
||||||
4d63.com/gochecknoglobals v0.2.2 // indirect
|
|
||||||
codeberg.org/chavacava/garif v0.2.0 // indirect
|
|
||||||
dev.gaijin.team/go/exhaustruct/v4 v4.0.0 // indirect
|
|
||||||
dev.gaijin.team/go/golib v0.6.0 // indirect
|
|
||||||
github.com/4meepo/tagalign v1.4.3 // indirect
|
|
||||||
github.com/Abirdcfly/dupword v0.1.7 // indirect
|
|
||||||
github.com/AdminBenni/iota-mixing v1.0.0 // indirect
|
|
||||||
github.com/AlwxSin/noinlineerr v1.0.5 // indirect
|
|
||||||
github.com/Antonboom/errname v1.1.1 // indirect
|
|
||||||
github.com/Antonboom/nilnil v1.1.1 // indirect
|
|
||||||
github.com/Antonboom/testifylint v1.6.4 // indirect
|
|
||||||
github.com/BurntSushi/toml v1.5.0 // indirect
|
|
||||||
github.com/Djarvur/go-err113 v0.1.1 // indirect
|
|
||||||
github.com/Masterminds/semver/v3 v3.4.0 // indirect
|
|
||||||
github.com/MirrexOne/unqueryvet v1.2.1 // indirect
|
|
||||||
github.com/OpenPeeDeeP/depguard/v2 v2.2.1 // indirect
|
|
||||||
github.com/alecthomas/chroma/v2 v2.20.0 // indirect
|
|
||||||
github.com/alecthomas/go-check-sumtype v0.3.1 // indirect
|
|
||||||
github.com/alexkohler/nakedret/v2 v2.0.6 // indirect
|
|
||||||
github.com/alexkohler/prealloc v1.0.0 // indirect
|
|
||||||
github.com/alfatraining/structtag v1.0.0 // indirect
|
|
||||||
github.com/alingse/asasalint v0.0.11 // indirect
|
|
||||||
github.com/alingse/nilnesserr v0.2.0 // indirect
|
|
||||||
github.com/ashanbrown/forbidigo/v2 v2.3.0 // indirect
|
|
||||||
github.com/ashanbrown/makezero/v2 v2.1.0 // indirect
|
|
||||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
|
|
||||||
github.com/beorn7/perks v1.0.1 // indirect
|
|
||||||
github.com/bkielbasa/cyclop v1.2.3 // indirect
|
|
||||||
github.com/blizzy78/varnamelen v0.8.0 // indirect
|
|
||||||
github.com/bombsimon/wsl/v4 v4.7.0 // indirect
|
|
||||||
github.com/bombsimon/wsl/v5 v5.3.0 // indirect
|
|
||||||
github.com/breml/bidichk v0.3.3 // indirect
|
|
||||||
github.com/breml/errchkjson v0.4.1 // indirect
|
|
||||||
github.com/butuzov/ireturn v0.4.0 // indirect
|
|
||||||
github.com/butuzov/mirror v1.3.0 // indirect
|
|
||||||
github.com/catenacyber/perfsprint v0.10.0 // indirect
|
|
||||||
github.com/ccojocar/zxcvbn-go v1.0.4 // indirect
|
|
||||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
|
||||||
github.com/charithe/durationcheck v0.0.11 // indirect
|
|
||||||
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc // indirect
|
|
||||||
github.com/charmbracelet/lipgloss v1.1.0 // indirect
|
|
||||||
github.com/charmbracelet/x/ansi v0.8.0 // indirect
|
|
||||||
github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd // indirect
|
|
||||||
github.com/charmbracelet/x/term v0.2.1 // indirect
|
|
||||||
github.com/ckaznocha/intrange v0.3.1 // indirect
|
|
||||||
github.com/curioswitch/go-reassign v0.3.0 // indirect
|
|
||||||
github.com/daixiang0/gci v0.13.7 // indirect
|
|
||||||
github.com/dave/dst v0.27.3 // indirect
|
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
|
||||||
github.com/denis-tingaikin/go-header v0.5.0 // indirect
|
|
||||||
github.com/dlclark/regexp2 v1.11.5 // indirect
|
|
||||||
github.com/ettle/strcase v0.2.0 // indirect
|
|
||||||
github.com/fatih/color v1.18.0 // indirect
|
|
||||||
github.com/fatih/structtag v1.2.0 // indirect
|
|
||||||
github.com/firefart/nonamedreturns v1.0.6 // indirect
|
|
||||||
github.com/fsnotify/fsnotify v1.5.4 // indirect
|
|
||||||
github.com/fzipp/gocyclo v0.6.0 // indirect
|
|
||||||
github.com/ghostiam/protogetter v0.3.17 // indirect
|
|
||||||
github.com/go-critic/go-critic v0.14.2 // indirect
|
|
||||||
github.com/go-toolsmith/astcast v1.1.0 // indirect
|
|
||||||
github.com/go-toolsmith/astcopy v1.1.0 // indirect
|
|
||||||
github.com/go-toolsmith/astequal v1.2.0 // indirect
|
|
||||||
github.com/go-toolsmith/astfmt v1.1.0 // indirect
|
|
||||||
github.com/go-toolsmith/astp v1.1.0 // indirect
|
|
||||||
github.com/go-toolsmith/strparse v1.1.0 // indirect
|
|
||||||
github.com/go-toolsmith/typep v1.1.0 // indirect
|
|
||||||
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
|
|
||||||
github.com/go-xmlfmt/xmlfmt v1.1.3 // indirect
|
|
||||||
github.com/gobwas/glob v0.2.3 // indirect
|
|
||||||
github.com/godoc-lint/godoc-lint v0.10.1 // indirect
|
|
||||||
github.com/gofrs/flock v0.13.0 // indirect
|
|
||||||
github.com/golang/protobuf v1.5.3 // indirect
|
|
||||||
github.com/golangci/asciicheck v0.5.0 // indirect
|
|
||||||
github.com/golangci/dupl v0.0.0-20250308024227-f665c8d69b32 // indirect
|
|
||||||
github.com/golangci/go-printf-func-name v0.1.1 // indirect
|
|
||||||
github.com/golangci/gofmt v0.0.0-20250106114630-d62b90e6713d // indirect
|
|
||||||
github.com/golangci/golines v0.0.0-20250217134842-442fd0091d95 // indirect
|
|
||||||
github.com/golangci/misspell v0.7.0 // indirect
|
|
||||||
github.com/golangci/plugin-module-register v0.1.2 // indirect
|
|
||||||
github.com/golangci/revgrep v0.8.0 // indirect
|
|
||||||
github.com/golangci/swaggoswag v0.0.0-20250504205917-77f2aca3143e // indirect
|
|
||||||
github.com/golangci/unconvert v0.0.0-20250410112200-a129a6e6413e // indirect
|
|
||||||
github.com/google/go-cmp v0.7.0 // indirect
|
|
||||||
github.com/gordonklaus/ineffassign v0.2.0 // indirect
|
|
||||||
github.com/gostaticanalysis/analysisutil v0.7.1 // indirect
|
|
||||||
github.com/gostaticanalysis/comment v1.5.0 // indirect
|
|
||||||
github.com/gostaticanalysis/forcetypeassert v0.2.0 // indirect
|
|
||||||
github.com/gostaticanalysis/nilerr v0.1.2 // indirect
|
|
||||||
github.com/hashicorp/go-immutable-radix/v2 v2.1.0 // indirect
|
|
||||||
github.com/hashicorp/go-version v1.7.0 // indirect
|
|
||||||
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
|
|
||||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
|
||||||
github.com/hexops/gotextdiff v1.0.3 // indirect
|
|
||||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
|
||||||
github.com/jgautheron/goconst v1.8.2 // indirect
|
|
||||||
github.com/jingyugao/rowserrcheck v1.1.1 // indirect
|
|
||||||
github.com/jjti/go-spancheck v0.6.5 // indirect
|
|
||||||
github.com/julz/importas v0.2.0 // indirect
|
|
||||||
github.com/karamaru-alpha/copyloopvar v1.2.2 // indirect
|
|
||||||
github.com/kisielk/errcheck v1.9.0 // indirect
|
|
||||||
github.com/kkHAIKE/contextcheck v1.1.6 // indirect
|
|
||||||
github.com/kulti/thelper v0.7.1 // indirect
|
|
||||||
github.com/kunwardeep/paralleltest v1.0.15 // indirect
|
|
||||||
github.com/lasiar/canonicalheader v1.1.2 // indirect
|
|
||||||
github.com/ldez/exptostd v0.4.5 // indirect
|
|
||||||
github.com/ldez/gomoddirectives v0.7.1 // indirect
|
|
||||||
github.com/ldez/grignotin v0.10.1 // indirect
|
|
||||||
github.com/ldez/tagliatelle v0.7.2 // indirect
|
|
||||||
github.com/ldez/usetesting v0.5.0 // indirect
|
|
||||||
github.com/leonklingele/grouper v1.1.2 // indirect
|
|
||||||
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
|
||||||
github.com/macabu/inamedparam v0.2.0 // indirect
|
|
||||||
github.com/magiconair/properties v1.8.6 // indirect
|
|
||||||
github.com/manuelarte/embeddedstructfieldcheck v0.4.0 // indirect
|
|
||||||
github.com/manuelarte/funcorder v0.5.0 // indirect
|
|
||||||
github.com/maratori/testableexamples v1.0.0 // indirect
|
|
||||||
github.com/maratori/testpackage v1.1.1 // indirect
|
|
||||||
github.com/matoous/godox v1.1.0 // indirect
|
|
||||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
|
||||||
github.com/mattn/go-runewidth v0.0.16 // indirect
|
|
||||||
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
|
|
||||||
github.com/mgechev/revive v1.12.0 // indirect
|
|
||||||
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
|
||||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
|
||||||
github.com/moricho/tparallel v0.3.2 // indirect
|
|
||||||
github.com/muesli/termenv v0.16.0 // indirect
|
|
||||||
github.com/nakabonne/nestif v0.3.1 // indirect
|
|
||||||
github.com/nishanths/exhaustive v0.12.0 // indirect
|
|
||||||
github.com/nishanths/predeclared v0.2.2 // indirect
|
|
||||||
github.com/nunnatsa/ginkgolinter v0.21.2 // indirect
|
|
||||||
github.com/pelletier/go-toml v1.9.5 // indirect
|
|
||||||
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
|
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
|
||||||
github.com/polyfloyd/go-errorlint v1.8.0 // indirect
|
|
||||||
github.com/prometheus/client_golang v1.12.1 // indirect
|
|
||||||
github.com/prometheus/client_model v0.2.0 // indirect
|
|
||||||
github.com/prometheus/common v0.32.1 // indirect
|
|
||||||
github.com/prometheus/procfs v0.7.3 // indirect
|
|
||||||
github.com/quasilyte/go-ruleguard v0.4.5 // indirect
|
|
||||||
github.com/quasilyte/go-ruleguard/dsl v0.3.23 // indirect
|
|
||||||
github.com/quasilyte/gogrep v0.5.0 // indirect
|
|
||||||
github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727 // indirect
|
|
||||||
github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567 // indirect
|
|
||||||
github.com/raeperd/recvcheck v0.2.0 // indirect
|
|
||||||
github.com/rivo/uniseg v0.4.7 // indirect
|
|
||||||
github.com/rogpeppe/go-internal v1.14.1 // indirect
|
|
||||||
github.com/ryancurrah/gomodguard v1.4.1 // indirect
|
|
||||||
github.com/ryanrolds/sqlclosecheck v0.5.1 // indirect
|
|
||||||
github.com/sanposhiho/wastedassign/v2 v2.1.0 // indirect
|
|
||||||
github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 // indirect
|
|
||||||
github.com/sashamelentyev/interfacebloat v1.1.0 // indirect
|
|
||||||
github.com/sashamelentyev/usestdlibvars v1.29.0 // indirect
|
|
||||||
github.com/securego/gosec/v2 v2.22.10 // indirect
|
|
||||||
github.com/sirupsen/logrus v1.9.3 // indirect
|
|
||||||
github.com/sivchari/containedctx v1.0.3 // indirect
|
|
||||||
github.com/sonatard/noctx v0.4.0 // indirect
|
|
||||||
github.com/sourcegraph/go-diff v0.7.0 // indirect
|
|
||||||
github.com/spf13/afero v1.14.0 // indirect
|
|
||||||
github.com/spf13/cast v1.5.0 // indirect
|
|
||||||
github.com/spf13/cobra v1.10.1 // indirect
|
|
||||||
github.com/spf13/jwalterweatherman v1.1.0 // indirect
|
|
||||||
github.com/spf13/pflag v1.0.10 // indirect
|
|
||||||
github.com/spf13/viper v1.12.0 // indirect
|
|
||||||
github.com/ssgreg/nlreturn/v2 v2.2.1 // indirect
|
|
||||||
github.com/stbenjam/no-sprintf-host-port v0.2.0 // indirect
|
|
||||||
github.com/stretchr/objx v0.5.2 // indirect
|
|
||||||
github.com/stretchr/testify v1.11.1 // indirect
|
|
||||||
github.com/subosito/gotenv v1.4.1 // indirect
|
|
||||||
github.com/tetafro/godot v1.5.4 // indirect
|
|
||||||
github.com/timakin/bodyclose v0.0.0-20241222091800-1db5c5ca4d67 // indirect
|
|
||||||
github.com/timonwong/loggercheck v0.11.0 // indirect
|
|
||||||
github.com/tomarrell/wrapcheck/v2 v2.11.0 // indirect
|
|
||||||
github.com/tommy-muehle/go-mnd/v2 v2.5.1 // indirect
|
|
||||||
github.com/ultraware/funlen v0.2.0 // indirect
|
|
||||||
github.com/ultraware/whitespace v0.2.0 // indirect
|
|
||||||
github.com/uudashr/gocognit v1.2.0 // indirect
|
|
||||||
github.com/uudashr/iface v1.4.1 // indirect
|
|
||||||
github.com/xen0n/gosmopolitan v1.3.0 // indirect
|
|
||||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
|
||||||
github.com/yagipy/maintidx v1.0.0 // indirect
|
|
||||||
github.com/yeya24/promlinter v0.3.0 // indirect
|
|
||||||
github.com/ykadowak/zerologlint v0.1.5 // indirect
|
|
||||||
gitlab.com/bosi/decorder v0.4.2 // indirect
|
|
||||||
go-simpler.org/musttag v0.14.0 // indirect
|
|
||||||
go-simpler.org/sloglint v0.11.1 // indirect
|
|
||||||
go.augendre.info/arangolint v0.3.1 // indirect
|
|
||||||
go.augendre.info/fatcontext v0.9.0 // indirect
|
|
||||||
go.uber.org/automaxprocs v1.6.0 // indirect
|
|
||||||
go.uber.org/multierr v1.10.0 // indirect
|
|
||||||
go.uber.org/zap v1.27.0 // indirect
|
|
||||||
golang.org/x/exp/typeparams v0.0.0-20251023183803-a4bb9ffd2546 // indirect
|
|
||||||
golang.org/x/mod v0.29.0 // indirect
|
|
||||||
golang.org/x/sync v0.17.0 // indirect
|
|
||||||
golang.org/x/sys v0.37.0 // indirect
|
|
||||||
golang.org/x/telemetry v0.0.0-20251008203120-078029d740a8 // indirect
|
|
||||||
golang.org/x/text v0.30.0 // indirect
|
|
||||||
google.golang.org/protobuf v1.36.8 // indirect
|
|
||||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
|
||||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
|
||||||
honnef.co/go/tools v0.6.1 // indirect
|
|
||||||
mvdan.cc/gofumpt v0.9.2 // indirect
|
|
||||||
mvdan.cc/unparam v0.0.0-20251027182757-5beb8c8f8f15 // indirect
|
|
||||||
)
|
|
||||||
1001
tools/go.sum
1001
tools/go.sum
File diff suppressed because it is too large
Load Diff
@@ -1,15 +0,0 @@
|
|||||||
//go:build tools
|
|
||||||
// +build tools
|
|
||||||
|
|
||||||
package tools
|
|
||||||
|
|
||||||
// Manage tool dependencies via go.mod.
|
|
||||||
//
|
|
||||||
// https://github.com/golang/go/wiki/Modules#how-can-i-track-tool-dependencies-for-a-module
|
|
||||||
// https://github.com/golang/go/issues/25922
|
|
||||||
//
|
|
||||||
// nolint
|
|
||||||
import (
|
|
||||||
_ "github.com/golangci/golangci-lint/v2/cmd/golangci-lint"
|
|
||||||
_ "golang.org/x/tools/cmd/goimports"
|
|
||||||
)
|
|
||||||
@@ -2,6 +2,12 @@
|
|||||||
|
|
||||||
Most of the configuration can be understood through the command line interface documentation. To access it, you need to install File Browser and run `filebrowser --help`. In this page, we cover some specific, more complex, topics.
|
Most of the configuration can be understood through the command line interface documentation. To access it, you need to install File Browser and run `filebrowser --help`. In this page, we cover some specific, more complex, topics.
|
||||||
|
|
||||||
|
## Flags as Environment Variables
|
||||||
|
|
||||||
|
In some situations, it is easier to use environment variables instead of flags. For example, if you're using our provided Docker image, it's easier for you to use environment variables to customize the settings instead of flags.
|
||||||
|
|
||||||
|
All flags should be available as environment variables prefixed with `FB_`. For example, the flag `--disable-thumbnails` is available as `FB_DISABLE_THUMBNAILS`.
|
||||||
|
|
||||||
## Custom Branding
|
## Custom Branding
|
||||||
|
|
||||||
You can customize File Browser to use your own branding. This includes the following:
|
You can customize File Browser to use your own branding. This includes the following:
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
|
|
||||||
> [!WARNING]
|
> [!WARNING]
|
||||||
>
|
>
|
||||||
> This project is currently on **maintenance-only** mode. For more information, read the information on [GitHub](https://github.com/filebrowser/filebrowser#project-status).
|
> This project is on **maintenance-only** mode. For more information, read the information on [GitHub](https://github.com/filebrowser/filebrowser#project-status).
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user