v0.3.3 release (#257)
This commit is contained in:
parent
266a76459d
commit
d2a3e50d37
|
@ -61,7 +61,7 @@ jobs:
|
||||||
JSON="${{ steps.meta.outputs.tags }}"
|
JSON="${{ steps.meta.outputs.tags }}"
|
||||||
# Use jq to remove 'v' from the version field
|
# Use jq to remove 'v' from the version field
|
||||||
JSON=$(echo "$JSON" | sed 's/filebrowser:v/filebrowser:/')
|
JSON=$(echo "$JSON" | sed 's/filebrowser:v/filebrowser:/')
|
||||||
echo "cleaned_tag=$JSON" >> $GITHUB_OUTPUT
|
echo "CLEANED_TAG=$JSON" >> $GITHUB_OUTPUT
|
||||||
- name: Build and push
|
- name: Build and push
|
||||||
uses: docker/build-push-action@v6
|
uses: docker/build-push-action@v6
|
||||||
with:
|
with:
|
||||||
|
@ -72,5 +72,5 @@ jobs:
|
||||||
platforms: linux/amd64,linux/arm64,linux/arm/v7
|
platforms: linux/amd64,linux/arm64,linux/arm/v7
|
||||||
file: ./Dockerfile
|
file: ./Dockerfile
|
||||||
push: true
|
push: true
|
||||||
tags: ${{ steps.modify-json.outputs.cleaned_tag }}
|
tags: ${{ steps.modify-json.outputs.CLEANED_TAG }}
|
||||||
labels: ${{ steps.meta.outputs.labels }}
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
|
|
|
@ -10,7 +10,7 @@ permissions:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
push_release_to_registry:
|
push_release_to_registry:
|
||||||
name: Push dev release
|
name: Push release
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
|
@ -35,16 +35,16 @@ jobs:
|
||||||
JSON="${{ steps.meta.outputs.tags }}"
|
JSON="${{ steps.meta.outputs.tags }}"
|
||||||
# Use jq to remove 'v' from the version field
|
# Use jq to remove 'v' from the version field
|
||||||
JSON=$(echo "$JSON" | sed 's/filebrowser:v/filebrowser:/')
|
JSON=$(echo "$JSON" | sed 's/filebrowser:v/filebrowser:/')
|
||||||
echo "CLEANED_TAG=$JSON" >> $GITHUB_ENV
|
echo "cleaned_tag=$JSON" >> $GITHUB_OUTPUT
|
||||||
- name: Build and push
|
- name: Build and push
|
||||||
uses: docker/build-push-action@v6
|
uses: docker/build-push-action@v6
|
||||||
with:
|
with:
|
||||||
|
context: .
|
||||||
build-args: |
|
build-args: |
|
||||||
VERSION=${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.version'] }}
|
VERSION=${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.version'] }}
|
||||||
REVISION=${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.revision'] }}
|
REVISION=${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.revision'] }}
|
||||||
context: .
|
|
||||||
platforms: linux/amd64
|
platforms: linux/amd64
|
||||||
file: ./Dockerfile
|
file: ./Dockerfile
|
||||||
push: true
|
push: true
|
||||||
tags: ${{ env.CLEANED_TAG }}
|
tags: ${{ steps.modify-json.outputs.cleaned_tag }}
|
||||||
labels: ${{ steps.meta.outputs.labels }}
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
|
|
21
CHANGELOG.md
21
CHANGELOG.md
|
@ -2,6 +2,25 @@
|
||||||
|
|
||||||
All notable changes to this project will be documented in this file. For commit guidelines, please refer to [Standard Version](https://github.com/conventional-changelog/standard-version).
|
All notable changes to this project will be documented in this file. For commit guidelines, please refer to [Standard Version](https://github.com/conventional-changelog/standard-version).
|
||||||
|
|
||||||
|
## v0.3.3
|
||||||
|
|
||||||
|
**New Features**
|
||||||
|
- Navigating remembers your previous scroll position when opening items and then navigating backwards.
|
||||||
|
- New Icons with larger selection of file types
|
||||||
|
- file "type" is shown on item info page.
|
||||||
|
- added optional non-root "filebrowser" user for docker image. See https://github.com/gtsteffaniak/filebrowser/issues/251
|
||||||
|
- File preview supports more file types:
|
||||||
|
- images: jpg, bmp, gif, tiff, png, svg, heic, webp
|
||||||
|
|
||||||
|
**Notes**:
|
||||||
|
- The file "type" is now either "directory" or a specific mimetype such as "text/xml".
|
||||||
|
- update safari styling
|
||||||
|
|
||||||
|
**Bugfixes**:
|
||||||
|
- Delete/move file/folders sometimes wouldn't work.
|
||||||
|
- Possible fix for context menu not showing issue. See https://github.com/gtsteffaniak/filebrowser/issues/251
|
||||||
|
- Fixed drag/drop not refreshing immediately to reflect changes.
|
||||||
|
|
||||||
## v0.3.2
|
## v0.3.2
|
||||||
|
|
||||||
**New Features**
|
**New Features**
|
||||||
|
@ -186,7 +205,7 @@ This change focuses on minimizing and simplifying build process.
|
||||||
- The shell feature has been deprecated.
|
- The shell feature has been deprecated.
|
||||||
- Custom commands can be executed within the Docker container if needed.
|
- Custom commands can be executed within the Docker container if needed.
|
||||||
- The JSON config file is no longer used.
|
- The JSON config file is no longer used.
|
||||||
- All configurations are now performed via the advanced `filebrowser.yaml`.
|
- All configurations are now performed via the advanced `config.yaml`.
|
||||||
- The only allowed flag is specifying the config file.
|
- The only allowed flag is specifying the config file.
|
||||||
- Removed old code for migrating database versions.
|
- Removed old code for migrating database versions.
|
||||||
- Eliminated all unused `cmd` code.
|
- Eliminated all unused `cmd` code.
|
||||||
|
|
|
@ -20,8 +20,12 @@ RUN npm run build-docker
|
||||||
FROM alpine:latest
|
FROM alpine:latest
|
||||||
ENV FILEBROWSER_NO_EMBEDED="true"
|
ENV FILEBROWSER_NO_EMBEDED="true"
|
||||||
RUN apk --no-cache add ca-certificates mailcap
|
RUN apk --no-cache add ca-certificates mailcap
|
||||||
COPY --from=base /app/filebrowser* ./
|
WORKDIR /home/filebrowser
|
||||||
|
RUN adduser -D -s /bin/true -u 1000 filebrowser
|
||||||
|
USER filebrowser
|
||||||
|
COPY --from=base --chown=filebrowser:1000 /app/filebrowser* ./
|
||||||
|
COPY --from=nbuild --chown=filebrowser:1000 /app/dist/ ./http/dist/
|
||||||
|
USER root
|
||||||
# exposing default port for auto discovery.
|
# exposing default port for auto discovery.
|
||||||
EXPOSE 80
|
EXPOSE 80
|
||||||
COPY --from=nbuild /app/dist/ ./http/dist/
|
|
||||||
ENTRYPOINT [ "./filebrowser" ]
|
ENTRYPOINT [ "./filebrowser" ]
|
||||||
|
|
|
@ -8,7 +8,7 @@ WORKDIR /app
|
||||||
COPY ./frontend/package.json ./
|
COPY ./frontend/package.json ./
|
||||||
RUN npm i --maxsockets 1
|
RUN npm i --maxsockets 1
|
||||||
RUN npx playwright install --with-deps firefox
|
RUN npx playwright install --with-deps firefox
|
||||||
COPY [ "backend/filebrowser.yaml", "./" ]
|
COPY [ "backend/config.yaml", "./" ]
|
||||||
COPY ./frontend/ ./frontend
|
COPY ./frontend/ ./frontend
|
||||||
WORKDIR /app/frontend
|
WORKDIR /app/frontend
|
||||||
RUN npm run build-docker
|
RUN npm run build-docker
|
||||||
|
|
38
README.md
38
README.md
|
@ -10,7 +10,7 @@
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
> [!Note]
|
> [!Note]
|
||||||
> Starting with `v0.3.0` API routes have been slightly altered for friendly usage outside of the UI. The resources API returns items in separate `files` and `folder` objects now.
|
> Starting with v0.3.3, configuration file mapping is different to support non-root user. Now, the default config file name is `config.yaml` and in docker the path is `/home/filebrowser/config.yaml` and `/home/filebrowser/<database_file>`. Please read the usage below to properly update your config to point the new config location.
|
||||||
|
|
||||||
> [!WARNING]
|
> [!WARNING]
|
||||||
> - There is no stable version yet. Always check release notes for bug fixes on functionality that may have been changed. If you notice any unexpected behavior -- please open an issue to have it fixed soon.
|
> - There is no stable version yet. Always check release notes for bug fixes on functionality that may have been changed. If you notice any unexpected behavior -- please open an issue to have it fixed soon.
|
||||||
|
@ -22,18 +22,18 @@ FileBrowser Quantum is a fork of the file browser opensource project with the fo
|
||||||
- Real-time search results as you type
|
- Real-time search results as you type
|
||||||
- Search supports file/folder sizes and many file type filters.
|
- Search supports file/folder sizes and many file type filters.
|
||||||
- Enhanced interactive results that show file/folder sizes.
|
- Enhanced interactive results that show file/folder sizes.
|
||||||
1. [x] Revamped and simplified GUI navbar and sidebar menu.
|
2. [x] Revamped and simplified GUI navbar and sidebar menu.
|
||||||
- Additional compact view mode as well as refreshed view mode
|
- Additional compact view mode as well as refreshed view mode
|
||||||
styles.
|
styles.
|
||||||
- Many graphical and user experience improvements.
|
- Many graphical and user experience improvements.
|
||||||
- right-click context menu
|
- right-click context menu
|
||||||
1. [x] Revamped and simplified configuration via `filebrowser.yml` config file.
|
3. [x] Revamped and simplified configuration via `filebrowser.yml` config file.
|
||||||
1. [x] Better listing browsing
|
4. [x] Better listing browsing
|
||||||
- Switching view modes is instant
|
- Switching view modes is instant
|
||||||
- Folder sizes are shown as well
|
- Folder sizes are shown as well
|
||||||
- Changing Sort order is instant
|
- Changing Sort order is instant
|
||||||
- The entire directory is loaded in 1/3 the time
|
- The entire directory is loaded in 1/3 the time
|
||||||
1. [x] Developer API support
|
5. [x] Developer API support
|
||||||
- Can create long-live API Tokens.
|
- Can create long-live API Tokens.
|
||||||
- Helpful Swagger page available at `/swagger` endpoint.
|
- Helpful Swagger page available at `/swagger` endpoint.
|
||||||
|
|
||||||
|
@ -94,7 +94,13 @@ Using docker:
|
||||||
1. docker run (no persistent db):
|
1. docker run (no persistent db):
|
||||||
|
|
||||||
```
|
```
|
||||||
docker run -it -v /path/to/folder:/srv -p 80:80 gtstef/filebrowser
|
docker run -it -v /path/to/folder:/srv -v $(pwd)/config.yaml:/home/filebrowser/config.yaml -p 80:80 gtstef/filebrowser
|
||||||
|
```
|
||||||
|
|
||||||
|
or optionally, as non-root filebrowser user:
|
||||||
|
|
||||||
|
```
|
||||||
|
docker run -u filebrowser -it -v $(pwd)/config.yaml:/home/filebrowser/config.yaml -v /path/to/folder:/srv -p 80:80 gtstef/filebrowser
|
||||||
```
|
```
|
||||||
|
|
||||||
1. docker compose:
|
1. docker compose:
|
||||||
|
@ -106,11 +112,14 @@ services:
|
||||||
filebrowser:
|
filebrowser:
|
||||||
volumes:
|
volumes:
|
||||||
- '/path/to/folder:/srv' # required (for now not configurable)
|
- '/path/to/folder:/srv' # required (for now not configurable)
|
||||||
- './database:/database' # optional if you want db to persist - configure a path under "database" dir in config file.
|
# optional if you want db to persist - configure a path under "database" dir in config file.
|
||||||
- './filebrowser.yaml:/filebrowser.yaml' # required
|
- './database:/home/filebrowser/database'
|
||||||
|
- './config.yaml:/home/filebrowser/config.yaml'
|
||||||
ports:
|
ports:
|
||||||
- '80:80'
|
- '80:80'
|
||||||
image: gtstef/filebrowser
|
image: gtstef/filebrowser
|
||||||
|
# optionally run as non-root filebrowser user
|
||||||
|
#user: filebrowser
|
||||||
restart: always
|
restart: always
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -121,8 +130,9 @@ services:
|
||||||
filebrowser:
|
filebrowser:
|
||||||
volumes:
|
volumes:
|
||||||
- 'storage:/srv' # required (for now not configurable)
|
- 'storage:/srv' # required (for now not configurable)
|
||||||
- './database:/database' # optional if you want db to persist - configure a path under "database" dir in config file.
|
# optional if you want db to persist - configure a path under "database" dir in config file.
|
||||||
- './filebrowser.yaml:/filebrowser.yaml' # required
|
- './database:/home/filebrowser/database'
|
||||||
|
- './config.yaml:/home/filebrowser/config.yaml'
|
||||||
ports:
|
ports:
|
||||||
- '80:80'
|
- '80:80'
|
||||||
image: gtstef/filebrowser
|
image: gtstef/filebrowser
|
||||||
|
@ -139,14 +149,14 @@ volumes:
|
||||||
Not using docker (not recommended), download your binary from releases and run with your custom config file:
|
Not using docker (not recommended), download your binary from releases and run with your custom config file:
|
||||||
|
|
||||||
```
|
```
|
||||||
./filebrowser -c <filebrowser.yml or other /path/to/config.yaml>
|
./filebrowser -c <config.yaml or other /path/to/config.yaml>
|
||||||
```
|
```
|
||||||
|
|
||||||
## Command Line Usage
|
## Command Line Usage
|
||||||
|
|
||||||
There are very few commands available. There are 3 actions done via the command line:
|
There are very few commands available. There are 3 actions done via the command line:
|
||||||
|
|
||||||
1. Running the program, as shown in the install step. The only argument used is the config file if you choose to override the default "filebrowser.yaml"
|
1. Running the program, as shown in the install step. The only argument used is the config file if you choose to override the default "config.yaml"
|
||||||
2. Checking the version info via `./filebrowser version`
|
2. Checking the version info via `./filebrowser version`
|
||||||
3. Updating the DB, which currently only supports adding users via `./filebrowser set -u username,password [-a] [-s "example/scope"]`
|
3. Updating the DB, which currently only supports adding users via `./filebrowser set -u username,password [-a] [-s "example/scope"]`
|
||||||
|
|
||||||
|
@ -172,8 +182,8 @@ Failed Request
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
All configuration is now done via a single configuration file:
|
All configuration is now done via a single configuration file:
|
||||||
`filebrowser.yaml`, here is an example of minimal [configuration
|
`config.yaml`, here is an example of minimal [configuration
|
||||||
file](./backend/filebrowser.yaml).
|
file](./backend/config.yaml).
|
||||||
|
|
||||||
View the [Configuration Help Page](./docs/configuration.md) for available
|
View the [Configuration Help Page](./docs/configuration.md) for available
|
||||||
configuration options and other help.
|
configuration options and other help.
|
||||||
|
|
|
@ -3,7 +3,7 @@ package auth
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/gtsteffaniak/filebrowser/users"
|
"github.com/gtsteffaniak/filebrowser/backend/users"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Auther is the authentication interface.
|
// Auther is the authentication interface.
|
||||||
|
|
|
@ -9,9 +9,9 @@ import (
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/gtsteffaniak/filebrowser/errors"
|
"github.com/gtsteffaniak/filebrowser/backend/errors"
|
||||||
"github.com/gtsteffaniak/filebrowser/settings"
|
"github.com/gtsteffaniak/filebrowser/backend/settings"
|
||||||
"github.com/gtsteffaniak/filebrowser/users"
|
"github.com/gtsteffaniak/filebrowser/backend/users"
|
||||||
)
|
)
|
||||||
|
|
||||||
type hookCred struct {
|
type hookCred struct {
|
||||||
|
|
|
@ -7,8 +7,8 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/gtsteffaniak/filebrowser/settings"
|
"github.com/gtsteffaniak/filebrowser/backend/settings"
|
||||||
"github.com/gtsteffaniak/filebrowser/users"
|
"github.com/gtsteffaniak/filebrowser/backend/users"
|
||||||
)
|
)
|
||||||
|
|
||||||
type jsonCred struct {
|
type jsonCred struct {
|
||||||
|
|
|
@ -3,8 +3,8 @@ package auth
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/gtsteffaniak/filebrowser/settings"
|
"github.com/gtsteffaniak/filebrowser/backend/settings"
|
||||||
"github.com/gtsteffaniak/filebrowser/users"
|
"github.com/gtsteffaniak/filebrowser/backend/users"
|
||||||
)
|
)
|
||||||
|
|
||||||
// MethodNoAuth is used to identify no auth.
|
// MethodNoAuth is used to identify no auth.
|
||||||
|
|
|
@ -4,10 +4,10 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/gtsteffaniak/filebrowser/settings"
|
"github.com/gtsteffaniak/filebrowser/backend/settings"
|
||||||
|
|
||||||
"github.com/gtsteffaniak/filebrowser/errors"
|
"github.com/gtsteffaniak/filebrowser/backend/errors"
|
||||||
"github.com/gtsteffaniak/filebrowser/users"
|
"github.com/gtsteffaniak/filebrowser/backend/users"
|
||||||
)
|
)
|
||||||
|
|
||||||
// MethodProxyAuth is used to identify no auth.
|
// MethodProxyAuth is used to identify no auth.
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
package auth
|
package auth
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/gtsteffaniak/filebrowser/users"
|
"github.com/gtsteffaniak/filebrowser/backend/users"
|
||||||
)
|
)
|
||||||
|
|
||||||
// StorageBackend is a storage backend for auth storage.
|
// StorageBackend is a storage backend for auth storage.
|
||||||
|
|
|
@ -7,17 +7,17 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/gtsteffaniak/filebrowser/diskcache"
|
"github.com/gtsteffaniak/filebrowser/backend/diskcache"
|
||||||
"github.com/gtsteffaniak/filebrowser/files"
|
"github.com/gtsteffaniak/filebrowser/backend/files"
|
||||||
fbhttp "github.com/gtsteffaniak/filebrowser/http"
|
fbhttp "github.com/gtsteffaniak/filebrowser/backend/http"
|
||||||
"github.com/gtsteffaniak/filebrowser/img"
|
"github.com/gtsteffaniak/filebrowser/backend/img"
|
||||||
"github.com/gtsteffaniak/filebrowser/settings"
|
"github.com/gtsteffaniak/filebrowser/backend/settings"
|
||||||
"github.com/gtsteffaniak/filebrowser/storage"
|
"github.com/gtsteffaniak/filebrowser/backend/storage"
|
||||||
"github.com/gtsteffaniak/filebrowser/swagger/docs"
|
"github.com/gtsteffaniak/filebrowser/backend/swagger/docs"
|
||||||
"github.com/swaggo/swag"
|
"github.com/swaggo/swag"
|
||||||
|
|
||||||
"github.com/gtsteffaniak/filebrowser/users"
|
"github.com/gtsteffaniak/filebrowser/backend/users"
|
||||||
"github.com/gtsteffaniak/filebrowser/version"
|
"github.com/gtsteffaniak/filebrowser/backend/version"
|
||||||
)
|
)
|
||||||
|
|
||||||
func getStore(config string) (*storage.Storage, bool) {
|
func getStore(config string) (*storage.Storage, bool) {
|
||||||
|
@ -47,7 +47,7 @@ func StartFilebrowser() {
|
||||||
var help bool
|
var help bool
|
||||||
// Override the default usage output to use generalUsage()
|
// Override the default usage output to use generalUsage()
|
||||||
flag.Usage = generalUsage
|
flag.Usage = generalUsage
|
||||||
flag.StringVar(&configPath, "c", "filebrowser.yaml", "Path to the config file.")
|
flag.StringVar(&configPath, "c", "config.yaml", "Path to the config file, default: config.yaml")
|
||||||
flag.BoolVar(&help, "h", false, "Get help about commands")
|
flag.BoolVar(&help, "h", false, "Get help about commands")
|
||||||
|
|
||||||
// Parse global flags (before subcommands)
|
// Parse global flags (before subcommands)
|
||||||
|
@ -67,7 +67,7 @@ func StartFilebrowser() {
|
||||||
setCmd.StringVar(&user, "u", "", "Comma-separated username and password: \"set -u <username>,<password>\"")
|
setCmd.StringVar(&user, "u", "", "Comma-separated username and password: \"set -u <username>,<password>\"")
|
||||||
setCmd.BoolVar(&asAdmin, "a", false, "Create user as admin user, used in combination with -u")
|
setCmd.BoolVar(&asAdmin, "a", false, "Create user as admin user, used in combination with -u")
|
||||||
setCmd.StringVar(&scope, "s", "", "Specify a user scope, otherwise default user config scope is used")
|
setCmd.StringVar(&scope, "s", "", "Specify a user scope, otherwise default user config scope is used")
|
||||||
setCmd.StringVar(&dbConfig, "c", "filebrowser.yaml", "Path to the config file.")
|
setCmd.StringVar(&dbConfig, "c", "config.yaml", "Path to the config file, default: config.yaml")
|
||||||
|
|
||||||
// Parse subcommand flags only if a subcommand is specified
|
// Parse subcommand flags only if a subcommand is specified
|
||||||
if len(os.Args) > 1 {
|
if len(os.Args) > 1 {
|
||||||
|
|
|
@ -5,10 +5,10 @@ import (
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
"github.com/gtsteffaniak/filebrowser/settings"
|
"github.com/gtsteffaniak/filebrowser/backend/settings"
|
||||||
"github.com/gtsteffaniak/filebrowser/storage"
|
"github.com/gtsteffaniak/filebrowser/backend/storage"
|
||||||
"github.com/gtsteffaniak/filebrowser/users"
|
"github.com/gtsteffaniak/filebrowser/backend/users"
|
||||||
"github.com/gtsteffaniak/filebrowser/utils"
|
"github.com/gtsteffaniak/filebrowser/backend/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
|
|
@ -6,10 +6,10 @@ import (
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/pflag"
|
"github.com/spf13/pflag"
|
||||||
|
|
||||||
"github.com/gtsteffaniak/filebrowser/settings"
|
"github.com/gtsteffaniak/filebrowser/backend/settings"
|
||||||
"github.com/gtsteffaniak/filebrowser/storage"
|
"github.com/gtsteffaniak/filebrowser/backend/storage"
|
||||||
"github.com/gtsteffaniak/filebrowser/users"
|
"github.com/gtsteffaniak/filebrowser/backend/users"
|
||||||
"github.com/gtsteffaniak/filebrowser/utils"
|
"github.com/gtsteffaniak/filebrowser/backend/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
|
|
@ -5,10 +5,10 @@ import (
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
"github.com/gtsteffaniak/filebrowser/settings"
|
"github.com/gtsteffaniak/filebrowser/backend/settings"
|
||||||
"github.com/gtsteffaniak/filebrowser/storage"
|
"github.com/gtsteffaniak/filebrowser/backend/storage"
|
||||||
"github.com/gtsteffaniak/filebrowser/users"
|
"github.com/gtsteffaniak/filebrowser/backend/users"
|
||||||
"github.com/gtsteffaniak/filebrowser/utils"
|
"github.com/gtsteffaniak/filebrowser/backend/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/gtsteffaniak/filebrowser/storage"
|
"github.com/gtsteffaniak/filebrowser/backend/storage"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,7 @@ import (
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
"github.com/gtsteffaniak/filebrowser/users"
|
"github.com/gtsteffaniak/filebrowser/backend/users"
|
||||||
)
|
)
|
||||||
|
|
||||||
var usersCmd = &cobra.Command{
|
var usersCmd = &cobra.Command{
|
||||||
|
|
|
@ -3,9 +3,9 @@ package cmd
|
||||||
import (
|
import (
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
"github.com/gtsteffaniak/filebrowser/storage"
|
"github.com/gtsteffaniak/filebrowser/backend/storage"
|
||||||
"github.com/gtsteffaniak/filebrowser/users"
|
"github.com/gtsteffaniak/filebrowser/backend/users"
|
||||||
"github.com/gtsteffaniak/filebrowser/utils"
|
"github.com/gtsteffaniak/filebrowser/backend/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/gtsteffaniak/filebrowser/storage"
|
"github.com/gtsteffaniak/filebrowser/backend/storage"
|
||||||
"github.com/gtsteffaniak/filebrowser/utils"
|
"github.com/gtsteffaniak/filebrowser/backend/utils"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -3,9 +3,9 @@ package cmd
|
||||||
import (
|
import (
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
"github.com/gtsteffaniak/filebrowser/storage"
|
"github.com/gtsteffaniak/filebrowser/backend/storage"
|
||||||
"github.com/gtsteffaniak/filebrowser/users"
|
"github.com/gtsteffaniak/filebrowser/backend/users"
|
||||||
"github.com/gtsteffaniak/filebrowser/utils"
|
"github.com/gtsteffaniak/filebrowser/backend/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
|
|
@ -8,9 +8,9 @@ import (
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
"github.com/gtsteffaniak/filebrowser/storage"
|
"github.com/gtsteffaniak/filebrowser/backend/storage"
|
||||||
"github.com/gtsteffaniak/filebrowser/users"
|
"github.com/gtsteffaniak/filebrowser/backend/users"
|
||||||
"github.com/gtsteffaniak/filebrowser/utils"
|
"github.com/gtsteffaniak/filebrowser/backend/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
|
|
@ -3,8 +3,8 @@ package cmd
|
||||||
import (
|
import (
|
||||||
"log"
|
"log"
|
||||||
|
|
||||||
"github.com/gtsteffaniak/filebrowser/storage"
|
"github.com/gtsteffaniak/filebrowser/backend/storage"
|
||||||
"github.com/gtsteffaniak/filebrowser/utils"
|
"github.com/gtsteffaniak/filebrowser/backend/utils"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -3,9 +3,9 @@ package cmd
|
||||||
import (
|
import (
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
"github.com/gtsteffaniak/filebrowser/storage"
|
"github.com/gtsteffaniak/filebrowser/backend/storage"
|
||||||
"github.com/gtsteffaniak/filebrowser/users"
|
"github.com/gtsteffaniak/filebrowser/backend/users"
|
||||||
"github.com/gtsteffaniak/filebrowser/utils"
|
"github.com/gtsteffaniak/filebrowser/backend/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
|
|
@ -10,8 +10,8 @@ import (
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/pflag"
|
"github.com/spf13/pflag"
|
||||||
|
|
||||||
"github.com/gtsteffaniak/filebrowser/storage"
|
"github.com/gtsteffaniak/filebrowser/backend/storage"
|
||||||
"github.com/gtsteffaniak/filebrowser/utils"
|
"github.com/gtsteffaniak/filebrowser/backend/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
func mustGetString(flags *pflag.FlagSet, flag string) string {
|
func mustGetString(flags *pflag.FlagSet, flag string) string {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
server:
|
server:
|
||||||
port: 80
|
port: 80
|
||||||
baseURL: "/"
|
baseURL: "/"
|
||||||
root: "./srv"
|
root: "/srv"
|
||||||
auth:
|
auth:
|
||||||
method: password
|
method: password
|
||||||
signup: false
|
signup: false
|
Binary file not shown.
|
@ -24,11 +24,24 @@ var documentTypes = []string{
|
||||||
".pdf", // Portable Document Format
|
".pdf", // Portable Document Format
|
||||||
".odt", // OpenDocument Text
|
".odt", // OpenDocument Text
|
||||||
".rtf", // Rich Text Format
|
".rtf", // Rich Text Format
|
||||||
|
".conf",
|
||||||
|
".bash_history",
|
||||||
|
".gitignore",
|
||||||
|
".htpasswd",
|
||||||
|
".profile",
|
||||||
|
".dockerignore",
|
||||||
|
".editorconfig",
|
||||||
|
|
||||||
// Presentation Formats
|
// Presentation Formats
|
||||||
".ppt", ".pptx", // Microsoft PowerPoint
|
".ppt", ".pptx", // Microsoft PowerPoint
|
||||||
".odp", // OpenDocument Presentation
|
".odp", // OpenDocument Presentation
|
||||||
|
|
||||||
|
// google docs
|
||||||
|
".gdoc",
|
||||||
|
|
||||||
|
// google sheet
|
||||||
|
".gsheet",
|
||||||
|
|
||||||
// Spreadsheet Formats
|
// Spreadsheet Formats
|
||||||
".xls", ".xlsx", // Microsoft Excel
|
".xls", ".xlsx", // Microsoft Excel
|
||||||
".ods", // OpenDocument Spreadsheet
|
".ods", // OpenDocument Spreadsheet
|
||||||
|
@ -102,6 +115,16 @@ type SearchOptions struct {
|
||||||
Terms []string
|
Terms []string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func extendedMimeTypeCheck(extension string) string {
|
||||||
|
if isDoc(extension) {
|
||||||
|
return "application/document"
|
||||||
|
}
|
||||||
|
if isText(extension) {
|
||||||
|
return "text/plain"
|
||||||
|
}
|
||||||
|
return "blob"
|
||||||
|
}
|
||||||
|
|
||||||
func ParseSearch(value string) SearchOptions {
|
func ParseSearch(value string) SearchOptions {
|
||||||
opts := SearchOptions{
|
opts := SearchOptions{
|
||||||
Conditions: map[string]bool{
|
Conditions: map[string]bool{
|
||||||
|
@ -199,8 +222,6 @@ func IsMatchingType(extension string, matchType string) bool {
|
||||||
switch matchType {
|
switch matchType {
|
||||||
case "doc":
|
case "doc":
|
||||||
return isDoc(extension)
|
return isDoc(extension)
|
||||||
case "pdf":
|
|
||||||
return extension == ".pdf"
|
|
||||||
case "text":
|
case "text":
|
||||||
return isText(extension)
|
return isText(extension)
|
||||||
case "archive":
|
case "archive":
|
||||||
|
|
|
@ -22,7 +22,7 @@ func TestIsMatchingType(t *testing.T) {
|
||||||
extension string
|
extension string
|
||||||
expectedType string
|
expectedType string
|
||||||
}{
|
}{
|
||||||
{".pdf", "pdf"},
|
{".pdf", "doc"},
|
||||||
{".doc", "doc"},
|
{".doc", "doc"},
|
||||||
{".docx", "doc"},
|
{".docx", "doc"},
|
||||||
{".json", "text"},
|
{".json", "text"},
|
||||||
|
|
|
@ -11,6 +11,7 @@ import (
|
||||||
"io"
|
"io"
|
||||||
"mime"
|
"mime"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"sort"
|
"sort"
|
||||||
|
@ -20,11 +21,11 @@ import (
|
||||||
"time"
|
"time"
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
|
|
||||||
"github.com/gtsteffaniak/filebrowser/errors"
|
"github.com/gtsteffaniak/filebrowser/backend/errors"
|
||||||
"github.com/gtsteffaniak/filebrowser/fileutils"
|
"github.com/gtsteffaniak/filebrowser/backend/fileutils"
|
||||||
"github.com/gtsteffaniak/filebrowser/settings"
|
"github.com/gtsteffaniak/filebrowser/backend/settings"
|
||||||
"github.com/gtsteffaniak/filebrowser/users"
|
"github.com/gtsteffaniak/filebrowser/backend/users"
|
||||||
"github.com/gtsteffaniak/filebrowser/utils"
|
"github.com/gtsteffaniak/filebrowser/backend/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -201,7 +202,8 @@ func DeleteFiles(absPath string, opts FileOptions) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
index := GetIndex(rootPath)
|
index := GetIndex(rootPath)
|
||||||
err = index.RefreshFileInfo(opts)
|
refreshConfig := FileOptions{Path: filepath.Dir(opts.Path), IsDir: true}
|
||||||
|
err = index.RefreshFileInfo(refreshConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -341,74 +343,29 @@ func getContent(path string) (string, error) {
|
||||||
return stringContent, nil
|
return stringContent, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// detectType detects the file type.
|
// DetectType detects the MIME type of a file and updates the ItemInfo struct.
|
||||||
func (i *ItemInfo) detectType(path string, modify, saveContent, readHeader bool) error {
|
func (i *ItemInfo) DetectType(path string, saveContent bool) {
|
||||||
name := i.Name
|
name := i.Name
|
||||||
var contentErr error
|
|
||||||
|
|
||||||
ext := filepath.Ext(name)
|
ext := filepath.Ext(name)
|
||||||
var buffer []byte
|
|
||||||
if readHeader {
|
|
||||||
buffer = i.readFirstBytes(path)
|
|
||||||
mimetype := mime.TypeByExtension(ext)
|
|
||||||
if mimetype == "" {
|
|
||||||
http.DetectContentType(buffer)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, fileType := range AllFiletypeOptions {
|
// Attempt MIME detection by file extension
|
||||||
if IsMatchingType(ext, fileType) {
|
i.Type = strings.Split(mime.TypeByExtension(ext), ";")[0]
|
||||||
i.Type = fileType
|
|
||||||
}
|
|
||||||
switch i.Type {
|
|
||||||
case "text":
|
|
||||||
if !modify {
|
|
||||||
i.Type = "textImmutable"
|
|
||||||
}
|
|
||||||
if saveContent {
|
|
||||||
return contentErr
|
|
||||||
}
|
|
||||||
case "video":
|
|
||||||
// TODO add back somewhere else, not during metadata fetch
|
|
||||||
//parentDir := strings.TrimRight(path, name)
|
|
||||||
//i.detectSubtitles(parentDir)
|
|
||||||
case "doc":
|
|
||||||
if ext == ".pdf" {
|
|
||||||
i.Type = "pdf"
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if saveContent {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if i.Type == "" {
|
if i.Type == "" {
|
||||||
i.Type = "blob"
|
i.Type = extendedMimeTypeCheck(ext)
|
||||||
if saveContent {
|
|
||||||
return contentErr
|
|
||||||
}
|
}
|
||||||
}
|
if i.Type == "blob" {
|
||||||
|
realpath, _, _ := GetRealPath(path)
|
||||||
return nil
|
// Read only the first 512 bytes for efficient MIME detection
|
||||||
}
|
file, err := os.Open(realpath)
|
||||||
|
|
||||||
// readFirstBytes reads the first bytes of the file.
|
|
||||||
func (i *ItemInfo) readFirstBytes(path string) []byte {
|
|
||||||
file, err := os.Open(path)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
i.Type = "blob"
|
|
||||||
return nil
|
} else {
|
||||||
}
|
|
||||||
defer file.Close()
|
defer file.Close()
|
||||||
|
buffer := make([]byte, 512)
|
||||||
buffer := make([]byte, 512) //nolint:gomnd
|
n, _ := file.Read(buffer) // Ignore errors from Read
|
||||||
n, err := file.Read(buffer)
|
i.Type = strings.Split(http.DetectContentType(buffer[:n]), ";")[0]
|
||||||
if err != nil && err != io.EOF {
|
}
|
||||||
i.Type = "blob"
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return buffer[:n]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO add subtitles back
|
// TODO add subtitles back
|
||||||
|
|
|
@ -9,8 +9,8 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gtsteffaniak/filebrowser/settings"
|
"github.com/gtsteffaniak/filebrowser/backend/settings"
|
||||||
"github.com/gtsteffaniak/filebrowser/utils"
|
"github.com/gtsteffaniak/filebrowser/backend/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Index struct {
|
type Index struct {
|
||||||
|
@ -132,7 +132,7 @@ func (si *Index) indexDirectory(adjustedPath string, quick, recursive bool) erro
|
||||||
dirInfos = append(dirInfos, *itemInfo)
|
dirInfos = append(dirInfos, *itemInfo)
|
||||||
si.NumDirs++
|
si.NumDirs++
|
||||||
} else {
|
} else {
|
||||||
_ = itemInfo.detectType(combinedPath+file.Name(), true, false, false)
|
itemInfo.DetectType(combinedPath+file.Name(), false)
|
||||||
itemInfo.Size = file.Size()
|
itemInfo.Size = file.Size()
|
||||||
fileInfos = append(fileInfos, *itemInfo)
|
fileInfos = append(fileInfos, *itemInfo)
|
||||||
totalSize += itemInfo.Size
|
totalSize += itemInfo.Size
|
||||||
|
|
|
@ -4,7 +4,7 @@ import (
|
||||||
"log"
|
"log"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gtsteffaniak/filebrowser/settings"
|
"github.com/gtsteffaniak/filebrowser/backend/settings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// schedule in minutes
|
// schedule in minutes
|
||||||
|
|
|
@ -7,7 +7,7 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gtsteffaniak/filebrowser/settings"
|
"github.com/gtsteffaniak/filebrowser/backend/settings"
|
||||||
)
|
)
|
||||||
|
|
||||||
func BenchmarkFillIndex(b *testing.B) {
|
func BenchmarkFillIndex(b *testing.B) {
|
||||||
|
|
|
@ -6,7 +6,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/gtsteffaniak/filebrowser/utils"
|
"github.com/gtsteffaniak/filebrowser/backend/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -14,19 +14,19 @@ var (
|
||||||
maxSearchResults = 100
|
maxSearchResults = 100
|
||||||
)
|
)
|
||||||
|
|
||||||
type searchResult struct {
|
type SearchResult struct {
|
||||||
Path string `json:"path"`
|
Path string `json:"path"`
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
Size int64 `json:"size"`
|
Size int64 `json:"size"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (si *Index) Search(search string, scope string, sourceSession string) []searchResult {
|
func (si *Index) Search(search string, scope string, sourceSession string) []SearchResult {
|
||||||
// Remove slashes
|
// Remove slashes
|
||||||
scope = si.makeIndexPath(scope)
|
scope = si.makeIndexPath(scope)
|
||||||
runningHash := utils.GenerateRandomHash(4)
|
runningHash := utils.GenerateRandomHash(4)
|
||||||
sessionInProgress.Store(sourceSession, runningHash) // Store the value in the sync.Map
|
sessionInProgress.Store(sourceSession, runningHash) // Store the value in the sync.Map
|
||||||
searchOptions := ParseSearch(search)
|
searchOptions := ParseSearch(search)
|
||||||
results := make(map[string]searchResult, 0)
|
results := make(map[string]SearchResult, 0)
|
||||||
count := 0
|
count := 0
|
||||||
var directories []string
|
var directories []string
|
||||||
cachedDirs, ok := utils.SearchResultsCache.Get(si.Root + scope).([]string)
|
cachedDirs, ok := utils.SearchResultsCache.Get(si.Root + scope).([]string)
|
||||||
|
@ -62,7 +62,7 @@ func (si *Index) Search(search string, scope string, sourceSession string) []sea
|
||||||
}
|
}
|
||||||
matches := reducedDir.containsSearchTerm(searchTerm, searchOptions)
|
matches := reducedDir.containsSearchTerm(searchTerm, searchOptions)
|
||||||
if matches {
|
if matches {
|
||||||
results[scopedPath] = searchResult{Path: scopedPath, Type: "directory", Size: dir.Size}
|
results[scopedPath] = SearchResult{Path: scopedPath, Type: "directory", Size: dir.Size}
|
||||||
count++
|
count++
|
||||||
}
|
}
|
||||||
// search files first
|
// search files first
|
||||||
|
@ -75,14 +75,14 @@ func (si *Index) Search(search string, scope string, sourceSession string) []sea
|
||||||
value, found := sessionInProgress.Load(sourceSession)
|
value, found := sessionInProgress.Load(sourceSession)
|
||||||
if !found || value != runningHash {
|
if !found || value != runningHash {
|
||||||
si.mu.Unlock()
|
si.mu.Unlock()
|
||||||
return []searchResult{}
|
return []SearchResult{}
|
||||||
}
|
}
|
||||||
if count > maxSearchResults {
|
if count > maxSearchResults {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
matches := item.containsSearchTerm(searchTerm, searchOptions)
|
matches := item.containsSearchTerm(searchTerm, searchOptions)
|
||||||
if matches {
|
if matches {
|
||||||
results[scopedPath] = searchResult{Path: scopedPath, Type: item.Type, Size: item.Size}
|
results[scopedPath] = SearchResult{Path: scopedPath, Type: item.Type, Size: item.Size}
|
||||||
count++
|
count++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -91,7 +91,7 @@ func (si *Index) Search(search string, scope string, sourceSession string) []sea
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sort keys based on the number of elements in the path after splitting by "/"
|
// Sort keys based on the number of elements in the path after splitting by "/"
|
||||||
sortedKeys := make([]searchResult, 0, len(results))
|
sortedKeys := make([]SearchResult, 0, len(results))
|
||||||
for _, v := range results {
|
for _, v := range results {
|
||||||
sortedKeys = append(sortedKeys, v)
|
sortedKeys = append(sortedKeys, v)
|
||||||
}
|
}
|
||||||
|
|
|
@ -119,12 +119,12 @@ func TestSearchIndexes(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
search string
|
search string
|
||||||
scope string
|
scope string
|
||||||
expectedResult []searchResult
|
expectedResult []SearchResult
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
search: "audio",
|
search: "audio",
|
||||||
scope: "/new/",
|
scope: "/new/",
|
||||||
expectedResult: []searchResult{
|
expectedResult: []SearchResult{
|
||||||
{
|
{
|
||||||
Path: "test/audio.wav",
|
Path: "test/audio.wav",
|
||||||
Type: "audio",
|
Type: "audio",
|
||||||
|
@ -135,7 +135,7 @@ func TestSearchIndexes(t *testing.T) {
|
||||||
{
|
{
|
||||||
search: "test",
|
search: "test",
|
||||||
scope: "/",
|
scope: "/",
|
||||||
expectedResult: []searchResult{
|
expectedResult: []SearchResult{
|
||||||
{
|
{
|
||||||
Path: "test/",
|
Path: "test/",
|
||||||
Type: "directory",
|
Type: "directory",
|
||||||
|
@ -151,7 +151,7 @@ func TestSearchIndexes(t *testing.T) {
|
||||||
{
|
{
|
||||||
search: "archive",
|
search: "archive",
|
||||||
scope: "/",
|
scope: "/",
|
||||||
expectedResult: []searchResult{
|
expectedResult: []SearchResult{
|
||||||
{
|
{
|
||||||
Path: "firstDir/archive.zip",
|
Path: "firstDir/archive.zip",
|
||||||
Type: "archive",
|
Type: "archive",
|
||||||
|
@ -167,7 +167,7 @@ func TestSearchIndexes(t *testing.T) {
|
||||||
{
|
{
|
||||||
search: "arch",
|
search: "arch",
|
||||||
scope: "/firstDir",
|
scope: "/firstDir",
|
||||||
expectedResult: []searchResult{
|
expectedResult: []SearchResult{
|
||||||
{
|
{
|
||||||
Path: "archive.zip",
|
Path: "archive.zip",
|
||||||
Type: "archive",
|
Type: "archive",
|
||||||
|
@ -178,7 +178,7 @@ func TestSearchIndexes(t *testing.T) {
|
||||||
{
|
{
|
||||||
search: "isdir",
|
search: "isdir",
|
||||||
scope: "/",
|
scope: "/",
|
||||||
expectedResult: []searchResult{
|
expectedResult: []SearchResult{
|
||||||
{
|
{
|
||||||
Path: "firstDir/thisIsDir/",
|
Path: "firstDir/thisIsDir/",
|
||||||
Type: "directory",
|
Type: "directory",
|
||||||
|
@ -189,7 +189,7 @@ func TestSearchIndexes(t *testing.T) {
|
||||||
{
|
{
|
||||||
search: "IsDir type:largerThan=1",
|
search: "IsDir type:largerThan=1",
|
||||||
scope: "/",
|
scope: "/",
|
||||||
expectedResult: []searchResult{
|
expectedResult: []SearchResult{
|
||||||
{
|
{
|
||||||
Path: "firstDir/thisIsDir/",
|
Path: "firstDir/thisIsDir/",
|
||||||
Type: "directory",
|
Type: "directory",
|
||||||
|
@ -200,7 +200,7 @@ func TestSearchIndexes(t *testing.T) {
|
||||||
{
|
{
|
||||||
search: "video",
|
search: "video",
|
||||||
scope: "/",
|
scope: "/",
|
||||||
expectedResult: []searchResult{
|
expectedResult: []SearchResult{
|
||||||
{
|
{
|
||||||
Path: "new/test/video.MP4",
|
Path: "new/test/video.MP4",
|
||||||
Type: "video",
|
Type: "video",
|
||||||
|
|
|
@ -3,7 +3,7 @@ package files
|
||||||
import (
|
import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
"github.com/gtsteffaniak/filebrowser/settings"
|
"github.com/gtsteffaniak/filebrowser/backend/settings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// UpdateFileMetadata updates the FileInfo for the specified directory in the index.
|
// UpdateFileMetadata updates the FileInfo for the specified directory in the index.
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
module github.com/gtsteffaniak/filebrowser
|
module github.com/gtsteffaniak/filebrowser/backend
|
||||||
|
|
||||||
go 1.22.5
|
go 1.22.5
|
||||||
|
|
||||||
|
@ -7,7 +7,8 @@ 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/goccy/go-yaml v1.15.6
|
github.com/gabriel-vasile/mimetype v1.4.7
|
||||||
|
github.com/goccy/go-yaml v1.15.7
|
||||||
github.com/golang-jwt/jwt/v4 v4.5.1
|
github.com/golang-jwt/jwt/v4 v4.5.1
|
||||||
github.com/google/go-cmp v0.6.0
|
github.com/google/go-cmp v0.6.0
|
||||||
github.com/shirou/gopsutil/v3 v3.24.5
|
github.com/shirou/gopsutil/v3 v3.24.5
|
||||||
|
@ -17,9 +18,9 @@ require (
|
||||||
github.com/stretchr/testify v1.9.0
|
github.com/stretchr/testify v1.9.0
|
||||||
github.com/swaggo/http-swagger v1.3.4
|
github.com/swaggo/http-swagger v1.3.4
|
||||||
github.com/swaggo/swag v1.16.4
|
github.com/swaggo/swag v1.16.4
|
||||||
golang.org/x/crypto v0.29.0
|
golang.org/x/crypto v0.30.0
|
||||||
golang.org/x/image v0.22.0
|
golang.org/x/image v0.23.0
|
||||||
golang.org/x/text v0.20.0
|
golang.org/x/text v0.21.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
@ -43,9 +44,9 @@ require (
|
||||||
github.com/swaggo/files v1.0.1 // indirect
|
github.com/swaggo/files v1.0.1 // indirect
|
||||||
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
||||||
go.etcd.io/bbolt v1.3.11 // indirect
|
go.etcd.io/bbolt v1.3.11 // indirect
|
||||||
golang.org/x/net v0.31.0 // indirect
|
golang.org/x/net v0.32.0 // indirect
|
||||||
golang.org/x/sys v0.27.0 // indirect
|
golang.org/x/sys v0.28.0 // indirect
|
||||||
golang.org/x/tools v0.27.0 // indirect
|
golang.org/x/tools v0.28.0 // indirect
|
||||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
)
|
)
|
||||||
|
|
|
@ -30,6 +30,8 @@ github.com/dsoprea/go-utility/v2 v2.0.0-20221003172846-a3e1774ef349 h1:DilThiXje
|
||||||
github.com/dsoprea/go-utility/v2 v2.0.0-20221003172846-a3e1774ef349/go.mod h1:4GC5sXji84i/p+irqghpPFZBF8tRN/Q7+700G0/DLe8=
|
github.com/dsoprea/go-utility/v2 v2.0.0-20221003172846-a3e1774ef349/go.mod h1:4GC5sXji84i/p+irqghpPFZBF8tRN/Q7+700G0/DLe8=
|
||||||
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 h1:BHsljHzVlRcyQhjrss6TZTdY2VfCqZPbv5k3iBFa2ZQ=
|
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 h1:BHsljHzVlRcyQhjrss6TZTdY2VfCqZPbv5k3iBFa2ZQ=
|
||||||
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
|
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
|
||||||
|
github.com/gabriel-vasile/mimetype v1.4.7 h1:SKFKl7kD0RiPdbht0s7hFtjl489WcQ1VyPW8ZzUMYCA=
|
||||||
|
github.com/gabriel-vasile/mimetype v1.4.7/go.mod h1:GDlAgAyIRT27BhFl53XNAFtfjzOkLaF35JdEG0P7LtU=
|
||||||
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
|
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
|
||||||
github.com/go-errors/errors v1.0.2/go.mod h1:psDX2osz5VnTOnFWbDeWwS7yejl+uV3FEWEp4lssFEs=
|
github.com/go-errors/errors v1.0.2/go.mod h1:psDX2osz5VnTOnFWbDeWwS7yejl+uV3FEWEp4lssFEs=
|
||||||
github.com/go-errors/errors v1.1.1/go.mod h1:psDX2osz5VnTOnFWbDeWwS7yejl+uV3FEWEp4lssFEs=
|
github.com/go-errors/errors v1.1.1/go.mod h1:psDX2osz5VnTOnFWbDeWwS7yejl+uV3FEWEp4lssFEs=
|
||||||
|
@ -47,8 +49,8 @@ github.com/go-openapi/spec v0.21.0 h1:LTVzPc3p/RzRnkQqLRndbAzjY0d0BCL72A6j3CdL9Z
|
||||||
github.com/go-openapi/spec v0.21.0/go.mod h1:78u6VdPw81XU44qEWGhtr982gJ5BWg2c0I5XwVMotYk=
|
github.com/go-openapi/spec v0.21.0/go.mod h1:78u6VdPw81XU44qEWGhtr982gJ5BWg2c0I5XwVMotYk=
|
||||||
github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=
|
github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=
|
||||||
github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
|
github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
|
||||||
github.com/goccy/go-yaml v1.15.6 h1:gy5kf1yjMia3/c3wWD+u1z3lU5XlhpT8FZGaLJU9cOA=
|
github.com/goccy/go-yaml v1.15.7 h1:L7XuKpd/A66X4w/dlk08lVfiIADdy79a1AzRoIefC98=
|
||||||
github.com/goccy/go-yaml v1.15.6/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
|
github.com/goccy/go-yaml v1.15.7/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
|
||||||
github.com/golang-jwt/jwt/v4 v4.5.1 h1:JdqV9zKUdtaa9gdPlywC3aeoEsR681PlKC+4F5gQgeo=
|
github.com/golang-jwt/jwt/v4 v4.5.1 h1:JdqV9zKUdtaa9gdPlywC3aeoEsR681PlKC+4F5gQgeo=
|
||||||
github.com/golang-jwt/jwt/v4 v4.5.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
github.com/golang-jwt/jwt/v4 v4.5.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
||||||
github.com/golang/geo v0.0.0-20190916061304-5b978397cfec/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI=
|
github.com/golang/geo v0.0.0-20190916061304-5b978397cfec/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI=
|
||||||
|
@ -114,11 +116,11 @@ go.etcd.io/bbolt v1.3.11 h1:yGEzV1wPz2yVCLsD8ZAiGHhHVlczyC9d1rP43/VCRJ0=
|
||||||
go.etcd.io/bbolt v1.3.11/go.mod h1:dksAq7YMXoljX0xu6VF5DMZGbhYYoLUalEiSySYAS4I=
|
go.etcd.io/bbolt v1.3.11/go.mod h1:dksAq7YMXoljX0xu6VF5DMZGbhYYoLUalEiSySYAS4I=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/crypto v0.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ=
|
golang.org/x/crypto v0.30.0 h1:RwoQn3GkWiMkzlX562cLB7OxWvjH1L8xutO2WoJcRoY=
|
||||||
golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg=
|
golang.org/x/crypto v0.30.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
||||||
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||||
golang.org/x/image v0.22.0 h1:UtK5yLUzilVrkjMAZAZ34DXGpASN8i8pj8g+O+yd10g=
|
golang.org/x/image v0.23.0 h1:HseQ7c2OpPKTPVzNjG5fwJsOTCiiwS4QdsYi5XU6H68=
|
||||||
golang.org/x/image v0.22.0/go.mod h1:9hPFhljd4zZ1GNSIZJ49sqbp45GKK9t6w+iXvGqZUz4=
|
golang.org/x/image v0.23.0/go.mod h1:wJJBTdLfCCf3tiHa1fNxpZmUI4mmoZvwMCPP0ddoNKY=
|
||||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||||
golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4=
|
golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4=
|
||||||
golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
|
golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
|
||||||
|
@ -133,12 +135,12 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v
|
||||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||||
golang.org/x/net v0.0.0-20221002022538-bcab6841153b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
|
golang.org/x/net v0.0.0-20221002022538-bcab6841153b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
|
||||||
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||||
golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo=
|
golang.org/x/net v0.32.0 h1:ZqPmj8Kzc+Y6e0+skZsuACbx+wzMgo5MQsJh9Qd6aYI=
|
||||||
golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM=
|
golang.org/x/net v0.32.0/go.mod h1:CwU0IoeOlnQQWJ6ioyFrfRuomB8GKF6KbYXZVyeXNfs=
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ=
|
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
|
||||||
golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
@ -153,8 +155,8 @@ golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||||
golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s=
|
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
|
||||||
golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||||
|
@ -163,13 +165,13 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug=
|
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
||||||
golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4=
|
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||||
golang.org/x/tools v0.27.0 h1:qEKojBykQkQ4EynWy4S8Weg69NumxKdn40Fce3uc/8o=
|
golang.org/x/tools v0.28.0 h1:WuB6qZ4RPCQo5aP3WdKZS7i595EdWqWR8vqJTlwTVK8=
|
||||||
golang.org/x/tools v0.27.0/go.mod h1:sUi0ZgbwW9ZPAq26Ekut+weQPR5eIM6GQLQ1Yjm1H0Q=
|
golang.org/x/tools v0.28.0/go.mod h1:dcIOrVd3mfQKTgrDVQHqCPMWy6lnhfhtX3hLXYVLfRw=
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||||
google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
|
google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
|
||||||
|
|
|
@ -7,7 +7,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gtsteffaniak/filebrowser/users"
|
"github.com/gtsteffaniak/filebrowser/backend/users"
|
||||||
)
|
)
|
||||||
|
|
||||||
func createApiKeyHandler(w http.ResponseWriter, r *http.Request, d *requestContext) (int, error) {
|
func createApiKeyHandler(w http.ResponseWriter, r *http.Request, d *requestContext) (int, error) {
|
||||||
|
|
|
@ -16,11 +16,11 @@ import (
|
||||||
"github.com/golang-jwt/jwt/v4/request"
|
"github.com/golang-jwt/jwt/v4/request"
|
||||||
"golang.org/x/crypto/bcrypt"
|
"golang.org/x/crypto/bcrypt"
|
||||||
|
|
||||||
"github.com/gtsteffaniak/filebrowser/errors"
|
"github.com/gtsteffaniak/filebrowser/backend/errors"
|
||||||
"github.com/gtsteffaniak/filebrowser/settings"
|
"github.com/gtsteffaniak/filebrowser/backend/settings"
|
||||||
"github.com/gtsteffaniak/filebrowser/share"
|
"github.com/gtsteffaniak/filebrowser/backend/share"
|
||||||
"github.com/gtsteffaniak/filebrowser/users"
|
"github.com/gtsteffaniak/filebrowser/backend/users"
|
||||||
"github.com/gtsteffaniak/filebrowser/utils"
|
"github.com/gtsteffaniak/filebrowser/backend/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|
|
@ -11,10 +11,10 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/golang-jwt/jwt/v4"
|
"github.com/golang-jwt/jwt/v4"
|
||||||
"github.com/gtsteffaniak/filebrowser/files"
|
"github.com/gtsteffaniak/filebrowser/backend/files"
|
||||||
"github.com/gtsteffaniak/filebrowser/runner"
|
"github.com/gtsteffaniak/filebrowser/backend/runner"
|
||||||
"github.com/gtsteffaniak/filebrowser/settings"
|
"github.com/gtsteffaniak/filebrowser/backend/settings"
|
||||||
"github.com/gtsteffaniak/filebrowser/users"
|
"github.com/gtsteffaniak/filebrowser/backend/users"
|
||||||
)
|
)
|
||||||
|
|
||||||
type requestContext struct {
|
type requestContext struct {
|
||||||
|
|
|
@ -8,15 +8,15 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/asdine/storm/v3"
|
"github.com/asdine/storm/v3"
|
||||||
"github.com/gtsteffaniak/filebrowser/diskcache"
|
"github.com/gtsteffaniak/filebrowser/backend/diskcache"
|
||||||
"github.com/gtsteffaniak/filebrowser/files"
|
"github.com/gtsteffaniak/filebrowser/backend/files"
|
||||||
"github.com/gtsteffaniak/filebrowser/img"
|
"github.com/gtsteffaniak/filebrowser/backend/img"
|
||||||
"github.com/gtsteffaniak/filebrowser/settings"
|
"github.com/gtsteffaniak/filebrowser/backend/settings"
|
||||||
"github.com/gtsteffaniak/filebrowser/share"
|
"github.com/gtsteffaniak/filebrowser/backend/share"
|
||||||
"github.com/gtsteffaniak/filebrowser/storage"
|
"github.com/gtsteffaniak/filebrowser/backend/storage"
|
||||||
"github.com/gtsteffaniak/filebrowser/storage/bolt"
|
"github.com/gtsteffaniak/filebrowser/backend/storage/bolt"
|
||||||
"github.com/gtsteffaniak/filebrowser/users"
|
"github.com/gtsteffaniak/filebrowser/backend/users"
|
||||||
"github.com/gtsteffaniak/filebrowser/utils"
|
"github.com/gtsteffaniak/filebrowser/backend/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
func setupTestEnv(t *testing.T) {
|
func setupTestEnv(t *testing.T) {
|
||||||
|
|
|
@ -8,9 +8,10 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/gtsteffaniak/filebrowser/files"
|
"github.com/gtsteffaniak/filebrowser/backend/files"
|
||||||
"github.com/gtsteffaniak/filebrowser/img"
|
"github.com/gtsteffaniak/filebrowser/backend/img"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ImgService interface {
|
type ImgService interface {
|
||||||
|
@ -64,7 +65,7 @@ func previewHandler(w http.ResponseWriter, r *http.Request, d *requestContext) (
|
||||||
return http.StatusBadRequest, fmt.Errorf("can't create preview for directory")
|
return http.StatusBadRequest, fmt.Errorf("can't create preview for directory")
|
||||||
}
|
}
|
||||||
setContentDisposition(w, r, fileInfo.Name)
|
setContentDisposition(w, r, fileInfo.Name)
|
||||||
if fileInfo.Type != "image" {
|
if !strings.HasPrefix(fileInfo.Type, "image") {
|
||||||
return http.StatusNotImplemented, fmt.Errorf("can't create preview for %s type", fileInfo.Type)
|
return http.StatusNotImplemented, fmt.Errorf("can't create preview for %s type", fileInfo.Type)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,11 +6,11 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/gtsteffaniak/filebrowser/files"
|
"github.com/gtsteffaniak/filebrowser/backend/files"
|
||||||
"github.com/gtsteffaniak/filebrowser/settings"
|
"github.com/gtsteffaniak/filebrowser/backend/settings"
|
||||||
"github.com/gtsteffaniak/filebrowser/users"
|
"github.com/gtsteffaniak/filebrowser/backend/users"
|
||||||
|
|
||||||
_ "github.com/gtsteffaniak/filebrowser/swagger/docs"
|
_ "github.com/gtsteffaniak/filebrowser/backend/swagger/docs"
|
||||||
)
|
)
|
||||||
|
|
||||||
func publicShareHandler(w http.ResponseWriter, r *http.Request, d *requestContext) (int, error) {
|
func publicShareHandler(w http.ResponseWriter, r *http.Request, d *requestContext) (int, error) {
|
||||||
|
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"archive/zip"
|
"archive/zip"
|
||||||
"compress/gzip"
|
"compress/gzip"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
@ -13,7 +14,7 @@ import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/gtsteffaniak/filebrowser/files"
|
"github.com/gtsteffaniak/filebrowser/backend/files"
|
||||||
)
|
)
|
||||||
|
|
||||||
func setContentDisposition(w http.ResponseWriter, r *http.Request, fileName string) {
|
func setContentDisposition(w http.ResponseWriter, r *http.Request, fileName string) {
|
||||||
|
@ -44,7 +45,12 @@ func rawHandler(w http.ResponseWriter, r *http.Request, d *requestContext) (int,
|
||||||
if !d.user.Perm.Download {
|
if !d.user.Perm.Download {
|
||||||
return http.StatusAccepted, nil
|
return http.StatusAccepted, nil
|
||||||
}
|
}
|
||||||
files := r.URL.Query().Get("files")
|
encodedFiles := r.URL.Query().Get("files")
|
||||||
|
// Decode the URL-encoded path
|
||||||
|
files, err := url.QueryUnescape(encodedFiles)
|
||||||
|
if err != nil {
|
||||||
|
return http.StatusBadRequest, fmt.Errorf("invalid path encoding: %v", err)
|
||||||
|
}
|
||||||
return rawFilesHandler(w, r, d, strings.Split(files, ","))
|
return rawFilesHandler(w, r, d, strings.Split(files, ","))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,9 +13,9 @@ import (
|
||||||
|
|
||||||
"github.com/shirou/gopsutil/v3/disk"
|
"github.com/shirou/gopsutil/v3/disk"
|
||||||
|
|
||||||
"github.com/gtsteffaniak/filebrowser/errors"
|
"github.com/gtsteffaniak/filebrowser/backend/errors"
|
||||||
"github.com/gtsteffaniak/filebrowser/files"
|
"github.com/gtsteffaniak/filebrowser/backend/files"
|
||||||
"github.com/gtsteffaniak/filebrowser/utils"
|
"github.com/gtsteffaniak/filebrowser/backend/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
// resourceGetHandler retrieves information about a resource.
|
// resourceGetHandler retrieves information about a resource.
|
||||||
|
@ -33,9 +33,13 @@ import (
|
||||||
// @Failure 500 {object} map[string]string "Internal server error"
|
// @Failure 500 {object} map[string]string "Internal server error"
|
||||||
// @Router /api/resources [get]
|
// @Router /api/resources [get]
|
||||||
func resourceGetHandler(w http.ResponseWriter, r *http.Request, d *requestContext) (int, error) {
|
func resourceGetHandler(w http.ResponseWriter, r *http.Request, d *requestContext) (int, error) {
|
||||||
|
|
||||||
// TODO source := r.URL.Query().Get("source")
|
// TODO source := r.URL.Query().Get("source")
|
||||||
path := r.URL.Query().Get("path")
|
encodedPath := r.URL.Query().Get("path")
|
||||||
|
// Decode the URL-encoded path
|
||||||
|
path, err := url.QueryUnescape(encodedPath)
|
||||||
|
if err != nil {
|
||||||
|
return http.StatusBadRequest, fmt.Errorf("invalid path encoding: %v", err)
|
||||||
|
}
|
||||||
fileInfo, err := files.FileInfoFaster(files.FileOptions{
|
fileInfo, err := files.FileInfoFaster(files.FileOptions{
|
||||||
Path: filepath.Join(d.user.Scope, path),
|
Path: filepath.Join(d.user.Scope, path),
|
||||||
Modify: d.user.Perm.Modify,
|
Modify: d.user.Perm.Modify,
|
||||||
|
@ -78,7 +82,12 @@ func resourceGetHandler(w http.ResponseWriter, r *http.Request, d *requestContex
|
||||||
// @Router /api/resources [delete]
|
// @Router /api/resources [delete]
|
||||||
func resourceDeleteHandler(w http.ResponseWriter, r *http.Request, d *requestContext) (int, error) {
|
func resourceDeleteHandler(w http.ResponseWriter, r *http.Request, d *requestContext) (int, error) {
|
||||||
// TODO source := r.URL.Query().Get("source")
|
// TODO source := r.URL.Query().Get("source")
|
||||||
path := r.URL.Query().Get("path")
|
encodedPath := r.URL.Query().Get("path")
|
||||||
|
// Decode the URL-encoded path
|
||||||
|
path, err := url.QueryUnescape(encodedPath)
|
||||||
|
if err != nil {
|
||||||
|
return http.StatusBadRequest, fmt.Errorf("invalid path encoding: %v", err)
|
||||||
|
}
|
||||||
if path == "/" || !d.user.Perm.Delete {
|
if path == "/" || !d.user.Perm.Delete {
|
||||||
return http.StatusForbidden, nil
|
return http.StatusForbidden, nil
|
||||||
}
|
}
|
||||||
|
@ -87,7 +96,7 @@ func resourceDeleteHandler(w http.ResponseWriter, r *http.Request, d *requestCon
|
||||||
return http.StatusNotFound, err
|
return http.StatusNotFound, err
|
||||||
}
|
}
|
||||||
fileOpts := files.FileOptions{
|
fileOpts := files.FileOptions{
|
||||||
Path: filepath.Join(d.user.Scope, path),
|
Path: realPath,
|
||||||
IsDir: isDir,
|
IsDir: isDir,
|
||||||
Modify: d.user.Perm.Modify,
|
Modify: d.user.Perm.Modify,
|
||||||
Expand: false,
|
Expand: false,
|
||||||
|
@ -130,7 +139,12 @@ func resourceDeleteHandler(w http.ResponseWriter, r *http.Request, d *requestCon
|
||||||
// @Router /api/resources [post]
|
// @Router /api/resources [post]
|
||||||
func resourcePostHandler(w http.ResponseWriter, r *http.Request, d *requestContext) (int, error) {
|
func resourcePostHandler(w http.ResponseWriter, r *http.Request, d *requestContext) (int, error) {
|
||||||
// TODO source := r.URL.Query().Get("source")
|
// TODO source := r.URL.Query().Get("source")
|
||||||
path := r.URL.Query().Get("path")
|
encodedPath := r.URL.Query().Get("path")
|
||||||
|
// Decode the URL-encoded path
|
||||||
|
path, err := url.QueryUnescape(encodedPath)
|
||||||
|
if err != nil {
|
||||||
|
return http.StatusBadRequest, fmt.Errorf("invalid path encoding: %v", err)
|
||||||
|
}
|
||||||
if !d.user.Perm.Create || !d.user.Check(path) {
|
if !d.user.Perm.Create || !d.user.Check(path) {
|
||||||
return http.StatusForbidden, nil
|
return http.StatusForbidden, nil
|
||||||
}
|
}
|
||||||
|
@ -142,7 +156,7 @@ func resourcePostHandler(w http.ResponseWriter, r *http.Request, d *requestConte
|
||||||
}
|
}
|
||||||
// Directories creation on POST.
|
// Directories creation on POST.
|
||||||
if strings.HasSuffix(path, "/") {
|
if strings.HasSuffix(path, "/") {
|
||||||
err := files.WriteDirectory(fileOpts)
|
err = files.WriteDirectory(fileOpts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errToStatus(err), err
|
return errToStatus(err), err
|
||||||
}
|
}
|
||||||
|
@ -188,7 +202,13 @@ func resourcePostHandler(w http.ResponseWriter, r *http.Request, d *requestConte
|
||||||
// @Router /api/resources [put]
|
// @Router /api/resources [put]
|
||||||
func resourcePutHandler(w http.ResponseWriter, r *http.Request, d *requestContext) (int, error) {
|
func resourcePutHandler(w http.ResponseWriter, r *http.Request, d *requestContext) (int, error) {
|
||||||
// TODO source := r.URL.Query().Get("source")
|
// TODO source := r.URL.Query().Get("source")
|
||||||
path := r.URL.Query().Get("path")
|
// TODO source := r.URL.Query().Get("source")
|
||||||
|
encodedPath := r.URL.Query().Get("path")
|
||||||
|
// Decode the URL-encoded path
|
||||||
|
path, err := url.QueryUnescape(encodedPath)
|
||||||
|
if err != nil {
|
||||||
|
return http.StatusBadRequest, fmt.Errorf("invalid path encoding: %v", err)
|
||||||
|
}
|
||||||
if !d.user.Perm.Modify || !d.user.Check(path) {
|
if !d.user.Perm.Modify || !d.user.Check(path) {
|
||||||
return http.StatusForbidden, nil
|
return http.StatusForbidden, nil
|
||||||
}
|
}
|
||||||
|
@ -233,16 +253,21 @@ func resourcePutHandler(w http.ResponseWriter, r *http.Request, d *requestContex
|
||||||
// @Router /api/resources [patch]
|
// @Router /api/resources [patch]
|
||||||
func resourcePatchHandler(w http.ResponseWriter, r *http.Request, d *requestContext) (int, error) {
|
func resourcePatchHandler(w http.ResponseWriter, r *http.Request, d *requestContext) (int, error) {
|
||||||
// TODO source := r.URL.Query().Get("source")
|
// TODO source := r.URL.Query().Get("source")
|
||||||
src := r.URL.Query().Get("from")
|
|
||||||
dst := r.URL.Query().Get("destination")
|
|
||||||
action := r.URL.Query().Get("action")
|
action := r.URL.Query().Get("action")
|
||||||
dst, err := url.QueryUnescape(dst)
|
encodedFrom := r.URL.Query().Get("from")
|
||||||
if !d.user.Check(src) || !d.user.Check(dst) {
|
// Decode the URL-encoded path
|
||||||
return http.StatusForbidden, nil
|
src, err := url.QueryUnescape(encodedFrom)
|
||||||
|
if err != nil {
|
||||||
|
return http.StatusBadRequest, fmt.Errorf("invalid path encoding: %v", err)
|
||||||
}
|
}
|
||||||
|
dst := r.URL.Query().Get("destination")
|
||||||
|
dst, err = url.QueryUnescape(dst)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errToStatus(err), err
|
return errToStatus(err), err
|
||||||
}
|
}
|
||||||
|
if !d.user.Check(src) || !d.user.Check(dst) {
|
||||||
|
return http.StatusForbidden, nil
|
||||||
|
}
|
||||||
if dst == "/" || src == "/" {
|
if dst == "/" || src == "/" {
|
||||||
return http.StatusForbidden, nil
|
return http.StatusForbidden, nil
|
||||||
}
|
}
|
||||||
|
@ -298,13 +323,14 @@ func delThumbs(ctx context.Context, fileCache FileCache, file *files.FileInfo) e
|
||||||
|
|
||||||
func patchAction(ctx context.Context, action, src, dst string, d *requestContext, fileCache FileCache, isSrcDir bool) error {
|
func patchAction(ctx context.Context, action, src, dst string, d *requestContext, fileCache FileCache, isSrcDir bool) error {
|
||||||
switch action {
|
switch action {
|
||||||
// TODO: use enum
|
|
||||||
case "copy":
|
case "copy":
|
||||||
if !d.user.Perm.Create {
|
if !d.user.Perm.Create {
|
||||||
return errors.ErrPermissionDenied
|
return errors.ErrPermissionDenied
|
||||||
}
|
}
|
||||||
return files.CopyResource(src, dst, isSrcDir)
|
err := files.CopyResource(src, dst, isSrcDir)
|
||||||
|
return err
|
||||||
case "rename", "move":
|
case "rename", "move":
|
||||||
|
|
||||||
if !d.user.Perm.Rename {
|
if !d.user.Perm.Rename {
|
||||||
return errors.ErrPermissionDenied
|
return errors.ErrPermissionDenied
|
||||||
}
|
}
|
||||||
|
@ -378,7 +404,9 @@ func diskUsage(w http.ResponseWriter, r *http.Request, d *requestContext) (int,
|
||||||
}
|
}
|
||||||
|
|
||||||
func inspectIndex(w http.ResponseWriter, r *http.Request) {
|
func inspectIndex(w http.ResponseWriter, r *http.Request) {
|
||||||
path := r.URL.Query().Get("path")
|
encodedPath := r.URL.Query().Get("path")
|
||||||
|
// Decode the URL-encoded path
|
||||||
|
path, _ := url.QueryUnescape(encodedPath)
|
||||||
isDir := r.URL.Query().Get("isDir") == "true"
|
isDir := r.URL.Query().Get("isDir") == "true"
|
||||||
index := files.GetIndex(config.Server.Root)
|
index := files.GetIndex(config.Server.Root)
|
||||||
info, _ := index.GetReducedMetadata(path, isDir)
|
info, _ := index.GetReducedMetadata(path, isDir)
|
||||||
|
|
|
@ -10,9 +10,9 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"text/template"
|
"text/template"
|
||||||
|
|
||||||
"github.com/gtsteffaniak/filebrowser/settings"
|
"github.com/gtsteffaniak/filebrowser/backend/settings"
|
||||||
"github.com/gtsteffaniak/filebrowser/storage"
|
"github.com/gtsteffaniak/filebrowser/backend/storage"
|
||||||
"github.com/gtsteffaniak/filebrowser/version"
|
"github.com/gtsteffaniak/filebrowser/backend/version"
|
||||||
|
|
||||||
httpSwagger "github.com/swaggo/http-swagger" // http-swagger middleware
|
httpSwagger "github.com/swaggo/http-swagger" // http-swagger middleware
|
||||||
)
|
)
|
||||||
|
|
|
@ -4,8 +4,8 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/gtsteffaniak/filebrowser/files"
|
"github.com/gtsteffaniak/filebrowser/backend/files"
|
||||||
"github.com/gtsteffaniak/filebrowser/settings"
|
"github.com/gtsteffaniak/filebrowser/backend/settings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// searchHandler handles search requests for files based on the provided query.
|
// searchHandler handles search requests for files based on the provided query.
|
||||||
|
@ -49,7 +49,7 @@ import (
|
||||||
// @Param query query string true "Search query"
|
// @Param query query string true "Search query"
|
||||||
// @Param scope query string false "path within user scope to search, for example '/first/second' to search within the second directory only"
|
// @Param scope query string false "path within user scope to search, for example '/first/second' to search within the second directory only"
|
||||||
// @Param SessionId header string false "User session ID, add unique value to prevent collisions"
|
// @Param SessionId header string false "User session ID, add unique value to prevent collisions"
|
||||||
// @Success 200 {array} files.searchResult "List of search results"
|
// @Success 200 {array} files.SearchResult "List of search results"
|
||||||
// @Failure 400 {object} map[string]string "Bad Request"
|
// @Failure 400 {object} map[string]string "Bad Request"
|
||||||
// @Router /api/search [get]
|
// @Router /api/search [get]
|
||||||
func searchHandler(w http.ResponseWriter, r *http.Request, d *requestContext) (int, error) {
|
func searchHandler(w http.ResponseWriter, r *http.Request, d *requestContext) (int, error) {
|
||||||
|
|
|
@ -4,8 +4,8 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/gtsteffaniak/filebrowser/settings"
|
"github.com/gtsteffaniak/filebrowser/backend/settings"
|
||||||
"github.com/gtsteffaniak/filebrowser/users"
|
"github.com/gtsteffaniak/filebrowser/backend/users"
|
||||||
)
|
)
|
||||||
|
|
||||||
type settingsData struct {
|
type settingsData struct {
|
||||||
|
|
|
@ -12,8 +12,8 @@ import (
|
||||||
|
|
||||||
"golang.org/x/crypto/bcrypt"
|
"golang.org/x/crypto/bcrypt"
|
||||||
|
|
||||||
"github.com/gtsteffaniak/filebrowser/errors"
|
"github.com/gtsteffaniak/filebrowser/backend/errors"
|
||||||
"github.com/gtsteffaniak/filebrowser/share"
|
"github.com/gtsteffaniak/filebrowser/backend/share"
|
||||||
)
|
)
|
||||||
|
|
||||||
// shareListHandler returns a list of all share links.
|
// shareListHandler returns a list of all share links.
|
||||||
|
|
|
@ -12,9 +12,9 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"text/template"
|
"text/template"
|
||||||
|
|
||||||
"github.com/gtsteffaniak/filebrowser/auth"
|
"github.com/gtsteffaniak/filebrowser/backend/auth"
|
||||||
"github.com/gtsteffaniak/filebrowser/settings"
|
"github.com/gtsteffaniak/filebrowser/backend/settings"
|
||||||
"github.com/gtsteffaniak/filebrowser/version"
|
"github.com/gtsteffaniak/filebrowser/backend/version"
|
||||||
)
|
)
|
||||||
|
|
||||||
var templateRenderer *TemplateRenderer
|
var templateRenderer *TemplateRenderer
|
||||||
|
|
|
@ -12,10 +12,10 @@ import (
|
||||||
"golang.org/x/text/cases"
|
"golang.org/x/text/cases"
|
||||||
"golang.org/x/text/language"
|
"golang.org/x/text/language"
|
||||||
|
|
||||||
"github.com/gtsteffaniak/filebrowser/errors"
|
"github.com/gtsteffaniak/filebrowser/backend/errors"
|
||||||
"github.com/gtsteffaniak/filebrowser/files"
|
"github.com/gtsteffaniak/filebrowser/backend/files"
|
||||||
"github.com/gtsteffaniak/filebrowser/storage"
|
"github.com/gtsteffaniak/filebrowser/backend/storage"
|
||||||
"github.com/gtsteffaniak/filebrowser/users"
|
"github.com/gtsteffaniak/filebrowser/backend/users"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|
|
@ -5,7 +5,7 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
libErrors "github.com/gtsteffaniak/filebrowser/errors"
|
libErrors "github.com/gtsteffaniak/filebrowser/backend/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
func errToStatus(err error) int {
|
func errToStatus(err error) int {
|
||||||
|
|
|
@ -261,3 +261,22 @@ func getEmbeddedThumbnail(in io.Reader) ([]byte, io.Reader, error) {
|
||||||
thm, err := ifd.Thumbnail()
|
thm, err := ifd.Thumbnail()
|
||||||
return thm, wrappedReader, err
|
return thm, wrappedReader, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CreateThumbnail takes raw image data and creates a thumbnail image.
|
||||||
|
func CreateThumbnail(rawData io.Reader, width, height int) (image.Image, error) {
|
||||||
|
// Decode the raw image to get an image.Image.
|
||||||
|
img, _, err := image.Decode(rawData)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to decode image: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resize the image to create a thumbnail using the specified dimensions.
|
||||||
|
thumb := imaging.Fit(img, width, height, imaging.Lanczos)
|
||||||
|
|
||||||
|
// Optionally, convert the thumbnail to grayscale if needed.
|
||||||
|
// Uncomment the line below if you want the result to be grayscale.
|
||||||
|
// thumb = imaging.Grayscale(thumb)
|
||||||
|
|
||||||
|
// Return the resized thumbnail image.
|
||||||
|
return thumb, nil
|
||||||
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/gtsteffaniak/filebrowser/cmd"
|
"github.com/gtsteffaniak/filebrowser/backend/cmd"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
|
|
@ -3,7 +3,7 @@ package runner
|
||||||
import (
|
import (
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
|
||||||
"github.com/gtsteffaniak/filebrowser/settings"
|
"github.com/gtsteffaniak/filebrowser/backend/settings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ParseCommand parses the command taking in account if the current
|
// ParseCommand parses the command taking in account if the current
|
||||||
|
|
|
@ -7,9 +7,9 @@ import (
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/gtsteffaniak/filebrowser/files"
|
"github.com/gtsteffaniak/filebrowser/backend/files"
|
||||||
"github.com/gtsteffaniak/filebrowser/settings"
|
"github.com/gtsteffaniak/filebrowser/backend/settings"
|
||||||
"github.com/gtsteffaniak/filebrowser/users"
|
"github.com/gtsteffaniak/filebrowser/backend/users"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Runner is a commands runner.
|
// Runner is a commands runner.
|
||||||
|
|
|
@ -7,7 +7,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/goccy/go-yaml"
|
"github.com/goccy/go-yaml"
|
||||||
"github.com/gtsteffaniak/filebrowser/users"
|
"github.com/gtsteffaniak/filebrowser/backend/users"
|
||||||
)
|
)
|
||||||
|
|
||||||
var Config Settings
|
var Config Settings
|
||||||
|
|
|
@ -3,7 +3,7 @@ package settings
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/gtsteffaniak/filebrowser/users"
|
"github.com/gtsteffaniak/filebrowser/backend/users"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestSettings_MakeUserDir(t *testing.T) {
|
func TestSettings_MakeUserDir(t *testing.T) {
|
||||||
|
|
|
@ -3,7 +3,7 @@ package settings
|
||||||
import (
|
import (
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
|
|
||||||
"github.com/gtsteffaniak/filebrowser/users"
|
"github.com/gtsteffaniak/filebrowser/backend/users"
|
||||||
)
|
)
|
||||||
|
|
||||||
const DefaultUsersHomeBasePath = "/users"
|
const DefaultUsersHomeBasePath = "/users"
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
package settings
|
package settings
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/gtsteffaniak/filebrowser/errors"
|
"github.com/gtsteffaniak/filebrowser/backend/errors"
|
||||||
"github.com/gtsteffaniak/filebrowser/users"
|
"github.com/gtsteffaniak/filebrowser/backend/users"
|
||||||
)
|
)
|
||||||
|
|
||||||
// StorageBackend is a settings storage backend.
|
// StorageBackend is a settings storage backend.
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
package settings
|
package settings
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/gtsteffaniak/filebrowser/users"
|
"github.com/gtsteffaniak/filebrowser/backend/users"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Settings struct {
|
type Settings struct {
|
||||||
|
|
|
@ -3,7 +3,7 @@ package share
|
||||||
import (
|
import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gtsteffaniak/filebrowser/errors"
|
"github.com/gtsteffaniak/filebrowser/backend/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
// StorageBackend is the interface to implement for a share storage.
|
// StorageBackend is the interface to implement for a share storage.
|
||||||
|
|
|
@ -2,8 +2,8 @@ package bolt
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/asdine/storm/v3"
|
"github.com/asdine/storm/v3"
|
||||||
"github.com/gtsteffaniak/filebrowser/auth"
|
"github.com/gtsteffaniak/filebrowser/backend/auth"
|
||||||
"github.com/gtsteffaniak/filebrowser/errors"
|
"github.com/gtsteffaniak/filebrowser/backend/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
type authBackend struct {
|
type authBackend struct {
|
||||||
|
|
|
@ -3,10 +3,10 @@ package bolt
|
||||||
import (
|
import (
|
||||||
"github.com/asdine/storm/v3"
|
"github.com/asdine/storm/v3"
|
||||||
|
|
||||||
"github.com/gtsteffaniak/filebrowser/auth"
|
"github.com/gtsteffaniak/filebrowser/backend/auth"
|
||||||
"github.com/gtsteffaniak/filebrowser/settings"
|
"github.com/gtsteffaniak/filebrowser/backend/settings"
|
||||||
"github.com/gtsteffaniak/filebrowser/share"
|
"github.com/gtsteffaniak/filebrowser/backend/share"
|
||||||
"github.com/gtsteffaniak/filebrowser/users"
|
"github.com/gtsteffaniak/filebrowser/backend/users"
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewStorage creates a storage.Storage based on Bolt DB.
|
// NewStorage creates a storage.Storage based on Bolt DB.
|
||||||
|
|
|
@ -2,7 +2,7 @@ package bolt
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/asdine/storm/v3"
|
"github.com/asdine/storm/v3"
|
||||||
"github.com/gtsteffaniak/filebrowser/settings"
|
"github.com/gtsteffaniak/filebrowser/backend/settings"
|
||||||
)
|
)
|
||||||
|
|
||||||
type settingsBackend struct {
|
type settingsBackend struct {
|
||||||
|
|
|
@ -4,8 +4,8 @@ import (
|
||||||
"github.com/asdine/storm/v3"
|
"github.com/asdine/storm/v3"
|
||||||
"github.com/asdine/storm/v3/q"
|
"github.com/asdine/storm/v3/q"
|
||||||
|
|
||||||
"github.com/gtsteffaniak/filebrowser/errors"
|
"github.com/gtsteffaniak/filebrowser/backend/errors"
|
||||||
"github.com/gtsteffaniak/filebrowser/share"
|
"github.com/gtsteffaniak/filebrowser/backend/share"
|
||||||
)
|
)
|
||||||
|
|
||||||
type shareBackend struct {
|
type shareBackend struct {
|
||||||
|
|
|
@ -6,9 +6,9 @@ import (
|
||||||
|
|
||||||
"github.com/asdine/storm/v3"
|
"github.com/asdine/storm/v3"
|
||||||
|
|
||||||
"github.com/gtsteffaniak/filebrowser/errors"
|
"github.com/gtsteffaniak/filebrowser/backend/errors"
|
||||||
"github.com/gtsteffaniak/filebrowser/users"
|
"github.com/gtsteffaniak/filebrowser/backend/users"
|
||||||
"github.com/gtsteffaniak/filebrowser/utils"
|
"github.com/gtsteffaniak/filebrowser/backend/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
type usersBackend struct {
|
type usersBackend struct {
|
||||||
|
|
|
@ -3,7 +3,7 @@ package bolt
|
||||||
import (
|
import (
|
||||||
"github.com/asdine/storm/v3"
|
"github.com/asdine/storm/v3"
|
||||||
|
|
||||||
"github.com/gtsteffaniak/filebrowser/errors"
|
"github.com/gtsteffaniak/filebrowser/backend/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
func get(db *storm.DB, name string, to interface{}) error {
|
func get(db *storm.DB, name string, to interface{}) error {
|
||||||
|
|
|
@ -7,14 +7,14 @@ import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
"github.com/asdine/storm/v3"
|
"github.com/asdine/storm/v3"
|
||||||
"github.com/gtsteffaniak/filebrowser/auth"
|
"github.com/gtsteffaniak/filebrowser/backend/auth"
|
||||||
"github.com/gtsteffaniak/filebrowser/errors"
|
"github.com/gtsteffaniak/filebrowser/backend/errors"
|
||||||
"github.com/gtsteffaniak/filebrowser/files"
|
"github.com/gtsteffaniak/filebrowser/backend/files"
|
||||||
"github.com/gtsteffaniak/filebrowser/settings"
|
"github.com/gtsteffaniak/filebrowser/backend/settings"
|
||||||
"github.com/gtsteffaniak/filebrowser/share"
|
"github.com/gtsteffaniak/filebrowser/backend/share"
|
||||||
"github.com/gtsteffaniak/filebrowser/storage/bolt"
|
"github.com/gtsteffaniak/filebrowser/backend/storage/bolt"
|
||||||
"github.com/gtsteffaniak/filebrowser/users"
|
"github.com/gtsteffaniak/filebrowser/backend/users"
|
||||||
"github.com/gtsteffaniak/filebrowser/utils"
|
"github.com/gtsteffaniak/filebrowser/backend/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Storage is a storage powered by a Backend which makes the necessary
|
// Storage is a storage powered by a Backend which makes the necessary
|
||||||
|
|
|
@ -579,7 +579,7 @@ const docTemplate = `{
|
||||||
"schema": {
|
"schema": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"items": {
|
"items": {
|
||||||
"$ref": "#/definitions/files.searchResult"
|
"$ref": "#/definitions/files.SearchResult"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -1186,7 +1186,7 @@ const docTemplate = `{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"files.searchResult": {
|
"files.SearchResult": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"path": {
|
"path": {
|
||||||
|
|
|
@ -568,7 +568,7 @@
|
||||||
"schema": {
|
"schema": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"items": {
|
"items": {
|
||||||
"$ref": "#/definitions/files.searchResult"
|
"$ref": "#/definitions/files.SearchResult"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -1175,7 +1175,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"files.searchResult": {
|
"files.SearchResult": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"path": {
|
"path": {
|
||||||
|
|
|
@ -31,7 +31,7 @@ definitions:
|
||||||
type:
|
type:
|
||||||
type: string
|
type: string
|
||||||
type: object
|
type: object
|
||||||
files.searchResult:
|
files.SearchResult:
|
||||||
properties:
|
properties:
|
||||||
path:
|
path:
|
||||||
type: string
|
type: string
|
||||||
|
@ -658,7 +658,7 @@ paths:
|
||||||
description: List of search results
|
description: List of search results
|
||||||
schema:
|
schema:
|
||||||
items:
|
items:
|
||||||
$ref: '#/definitions/files.searchResult'
|
$ref: '#/definitions/files.SearchResult'
|
||||||
type: array
|
type: array
|
||||||
"400":
|
"400":
|
||||||
description: Bad Request
|
description: Bad Request
|
||||||
|
|
|
@ -4,7 +4,7 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gtsteffaniak/filebrowser/errors"
|
"github.com/gtsteffaniak/filebrowser/backend/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
// StorageBackend is the interface to implement for a users storage.
|
// StorageBackend is the interface to implement for a users storage.
|
||||||
|
|
|
@ -1,18 +1,17 @@
|
||||||
# Planned Roadmap
|
# Planned Roadmap
|
||||||
|
|
||||||
upcoming 0.3.x releases, ordered by priority:
|
upcoming 0.3.x releases, ordered by priority:
|
||||||
|
- more indexing flexability
|
||||||
- More filetype icons and refreshed icons.
|
- option not to index hidden files/folders
|
||||||
- more filetype previews - eg. office, photoshop, vector, 3d files.
|
- options folders to include/exclude from indexing
|
||||||
- Enable mobile search with same features as desktop
|
- implement more indexing runners for more efficienct filesystem watching
|
||||||
- Enable mobile search with same features as desktop
|
- more filetype previews: eg. raw img, office, photoshop, vector, 3d files.
|
||||||
- Theme configuration from settings
|
- introduce jobs as replacement to runners.
|
||||||
- introduce jobs as replacement to runners.
|
|
||||||
- Add Job status to the sidebar
|
- Add Job status to the sidebar
|
||||||
- index status.
|
- index status.
|
||||||
- Job status from users
|
- Job status from users
|
||||||
- upload status
|
- upload status
|
||||||
- opentelemetry metrics
|
- opentelemetry metrics
|
||||||
|
|
||||||
Unplanned Future releases:
|
Unplanned Future releases:
|
||||||
- multiple sources https://github.com/filebrowser/filebrowser/issues/2514
|
- multiple sources https://github.com/filebrowser/filebrowser/issues/2514
|
||||||
|
|
|
@ -25,6 +25,7 @@
|
||||||
"css-vars-ponyfill": "^2.4.3",
|
"css-vars-ponyfill": "^2.4.3",
|
||||||
"file-loader": "^6.2.0",
|
"file-loader": "^6.2.0",
|
||||||
"material-icons": "^1.10.5",
|
"material-icons": "^1.10.5",
|
||||||
|
"material-symbols": "^0.27.2",
|
||||||
"normalize.css": "^8.0.1",
|
"normalize.css": "^8.0.1",
|
||||||
"qrcode.vue": "^3.4.1",
|
"qrcode.vue": "^3.4.1",
|
||||||
"vue": "^3.4.21",
|
"vue": "^3.4.21",
|
||||||
|
|
|
@ -6,7 +6,7 @@ import { notify } from "@/notify";
|
||||||
// Notify if errors occur
|
// Notify if errors occur
|
||||||
export async function fetchFiles(url, content = false) {
|
export async function fetchFiles(url, content = false) {
|
||||||
try {
|
try {
|
||||||
let path = removePrefix(url, "files");
|
let path = encodeURIComponent(removePrefix(url, "files"));
|
||||||
const apiPath = getApiPath("api/resources",{path: path, content: content});
|
const apiPath = getApiPath("api/resources",{path: path, content: content});
|
||||||
const res = await fetchURL(apiPath);
|
const res = await fetchURL(apiPath);
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
|
@ -24,7 +24,8 @@ async function resourceAction(url, method, content) {
|
||||||
if (content) {
|
if (content) {
|
||||||
opts.body = content;
|
opts.body = content;
|
||||||
}
|
}
|
||||||
const apiPath = getApiPath("api/resources", { path: url });
|
let path = encodeURIComponent(removePrefix(url, "files"));
|
||||||
|
const apiPath = getApiPath("api/resources", { path: path });
|
||||||
const res = await fetchURL(apiPath, opts);
|
const res = await fetchURL(apiPath, opts);
|
||||||
return res;
|
return res;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
@ -35,7 +36,8 @@ async function resourceAction(url, method, content) {
|
||||||
|
|
||||||
export async function remove(url) {
|
export async function remove(url) {
|
||||||
try {
|
try {
|
||||||
return await resourceAction(url, "DELETE");
|
let path = encodeURIComponent(removePrefix(url, "files"));
|
||||||
|
return await resourceAction(path, "DELETE");
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
notify.showError(err.message || "Error deleting resource");
|
notify.showError(err.message || "Error deleting resource");
|
||||||
throw err;
|
throw err;
|
||||||
|
@ -44,7 +46,8 @@ export async function remove(url) {
|
||||||
|
|
||||||
export async function put(url, content = "") {
|
export async function put(url, content = "") {
|
||||||
try {
|
try {
|
||||||
return await resourceAction(url, "PUT", content);
|
let path = encodeURIComponent(removePrefix(url, "files"));
|
||||||
|
return await resourceAction(path, "PUT", content);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
notify.showError(err.message || "Error putting resource");
|
notify.showError(err.message || "Error putting resource");
|
||||||
throw err;
|
throw err;
|
||||||
|
@ -58,14 +61,14 @@ export function download(format, files) {
|
||||||
try {
|
try {
|
||||||
let fileargs = "";
|
let fileargs = "";
|
||||||
if (files.length === 1) {
|
if (files.length === 1) {
|
||||||
fileargs = removePrefix(files[0], "files")
|
fileargs = decodeURI(removePrefix(files[0], "files"))
|
||||||
} else {
|
} else {
|
||||||
for (let file of files) {
|
for (let file of files) {
|
||||||
fileargs += removePrefix(file,"files") + ",";
|
fileargs += decodeURI(removePrefix(file,"files")) + ",";
|
||||||
}
|
}
|
||||||
fileargs = fileargs.substring(0, fileargs.length - 1);
|
fileargs = fileargs.substring(0, fileargs.length - 1);
|
||||||
}
|
}
|
||||||
const apiPath = getApiPath("api/raw", { files: fileargs, algo: format });
|
const apiPath = getApiPath("api/raw", { files: encodeURIComponent(fileargs), algo: format });
|
||||||
const url = window.origin+apiPath
|
const url = window.origin+apiPath
|
||||||
window.open(url);
|
window.open(url);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
@ -130,9 +133,11 @@ export async function moveCopy(items, action = "copy", overwrite = false, rename
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
for (let item of items) {
|
for (let item of items) {
|
||||||
|
let toPath = encodeURIComponent(removePrefix(decodeURI(item.to), "files"));
|
||||||
|
let fromPath = encodeURIComponent(removePrefix(decodeURI(item.from), "files"));
|
||||||
let localParams = { ...params };
|
let localParams = { ...params };
|
||||||
localParams.destination = item.to;
|
localParams.destination = toPath;
|
||||||
localParams.from = item.from;
|
localParams.from = fromPath;
|
||||||
const apiPath = getApiPath("api/resources", localParams);
|
const apiPath = getApiPath("api/resources", localParams);
|
||||||
promises.push(fetch(apiPath, { method: "PATCH" }));
|
promises.push(fetch(apiPath, { method: "PATCH" }));
|
||||||
}
|
}
|
||||||
|
@ -157,7 +162,7 @@ export async function checksum(url, algo) {
|
||||||
export function getDownloadURL(path, inline) {
|
export function getDownloadURL(path, inline) {
|
||||||
try {
|
try {
|
||||||
const params = {
|
const params = {
|
||||||
files: removePrefix(path,"files"),
|
files: encodeURIComponent(removePrefix(decodeURI(path),"files")),
|
||||||
...(inline && { inline: "true" }),
|
...(inline && { inline: "true" }),
|
||||||
};
|
};
|
||||||
const apiPath = getApiPath("api/raw", params);
|
const apiPath = getApiPath("api/raw", params);
|
||||||
|
@ -171,7 +176,7 @@ export function getDownloadURL(path, inline) {
|
||||||
export function getPreviewURL(path, size, modified) {
|
export function getPreviewURL(path, size, modified) {
|
||||||
try {
|
try {
|
||||||
const params = {
|
const params = {
|
||||||
path: path,
|
path: encodeURIComponent(removePrefix(decodeURI(path),"files")),
|
||||||
size: size,
|
size: size,
|
||||||
key: Date.parse(modified),
|
key: Date.parse(modified),
|
||||||
inline: "true",
|
inline: "true",
|
||||||
|
@ -190,7 +195,7 @@ export function getSubtitlesURL(file) {
|
||||||
for (const sub of file.subtitles) {
|
for (const sub of file.subtitles) {
|
||||||
const params = {
|
const params = {
|
||||||
inline: "true",
|
inline: "true",
|
||||||
path: sub
|
path: encodeURIComponent(removePrefix(sub,"files"))
|
||||||
};
|
};
|
||||||
const apiPath = getApiPath("api/raw", params);
|
const apiPath = getApiPath("api/raw", params);
|
||||||
return window.origin+apiPath
|
return window.origin+apiPath
|
||||||
|
|
|
@ -61,7 +61,6 @@ export async function fetchJSON(url, opts) {
|
||||||
|
|
||||||
export function adjustedData(data, url) {
|
export function adjustedData(data, url) {
|
||||||
data.url = url;
|
data.url = url;
|
||||||
|
|
||||||
if (data.type === "directory") {
|
if (data.type === "directory") {
|
||||||
if (!data.url.endsWith("/")) data.url += "/";
|
if (!data.url.endsWith("/")) data.url += "/";
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
left: `${left}px`,
|
left: `${left}px`,
|
||||||
}"
|
}"
|
||||||
class="button"
|
class="button"
|
||||||
:class="{ 'dark-mode': isDarkMode, mobile: isMobile }"
|
:class="{ 'dark-mode': isDarkMode, centered: centered }"
|
||||||
>
|
>
|
||||||
<div v-if="selectedCount > 0" class="button selected-count-header">
|
<div v-if="selectedCount > 0" class="button selected-count-header">
|
||||||
<span>{{ selectedCount }} selected</span>
|
<span>{{ selectedCount }} selected</span>
|
||||||
|
@ -108,8 +108,8 @@ export default {
|
||||||
user() {
|
user() {
|
||||||
return state.user;
|
return state.user;
|
||||||
},
|
},
|
||||||
isMobile() {
|
centered() {
|
||||||
return getters.isMobile();
|
return getters.isMobile() || ( !this.posX || !this.posY );
|
||||||
},
|
},
|
||||||
showContext() {
|
showContext() {
|
||||||
if (getters.currentPromptName() == "ContextMenu" && state.prompts != []) {
|
if (getters.currentPromptName() == "ContextMenu" && state.prompts != []) {
|
||||||
|
@ -158,7 +158,6 @@ export default {
|
||||||
return mutations.showHover(value);
|
return mutations.showHover(value);
|
||||||
},
|
},
|
||||||
setPositions() {
|
setPositions() {
|
||||||
console.log("Setting positions");
|
|
||||||
const contextProps = getters.currentPrompt().props;
|
const contextProps = getters.currentPrompt().props;
|
||||||
let tempX = contextProps.posX;
|
let tempX = contextProps.posX;
|
||||||
let tempY = contextProps.posY;
|
let tempY = contextProps.posY;
|
||||||
|
@ -204,7 +203,7 @@ export default {
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
#context-menu.mobile {
|
#context-menu.centered {
|
||||||
top: 50% !important;
|
top: 50% !important;
|
||||||
left: 50% !important;
|
left: 50% !important;
|
||||||
-webkit-transform: translate(-50%, -50%);
|
-webkit-transform: translate(-50%, -50%);
|
||||||
|
|
|
@ -0,0 +1,127 @@
|
||||||
|
<template>
|
||||||
|
<span>
|
||||||
|
<!-- Material Icon -->
|
||||||
|
<i v-if="isMaterialIcon" :class="classes" class="icon"> {{ materialIcon }} </i>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { getTypeInfo } from "@/utils/mimetype";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "Icon",
|
||||||
|
props: {
|
||||||
|
mimetype: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
materialIcon: "",
|
||||||
|
classes: "",
|
||||||
|
svgPath: "",
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
isMaterialIcon() {
|
||||||
|
return this.materialIcon !== "";
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
getIconForType(mimetype) {
|
||||||
|
return getTypeInfo(mimetype);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
const result = this.getIconForType(this.mimetype);
|
||||||
|
this.classes = result.classes || "material-icons"; // Default class
|
||||||
|
this.color = result.color || "lightgray"; // Default color
|
||||||
|
this.materialIcon = result.materialIcon || "";
|
||||||
|
this.svgPath = result.svgPath || ""; // For SVG file paths
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.file-icons [aria-label^="."] {
|
||||||
|
opacity: 0.33;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-icons [aria-label$=".bak"] {
|
||||||
|
opacity: 0.33;
|
||||||
|
}
|
||||||
|
|
||||||
|
.svg-icons {
|
||||||
|
display: flex;
|
||||||
|
max-width: 100px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
/* Default size */
|
||||||
|
fill: currentColor;
|
||||||
|
/* Uses inherited color */
|
||||||
|
}
|
||||||
|
|
||||||
|
.purple-icons {
|
||||||
|
color: purple;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Icon Colors */
|
||||||
|
.blue-icons {
|
||||||
|
color: var(--icon-blue);
|
||||||
|
}
|
||||||
|
|
||||||
|
.lightblue-icons {
|
||||||
|
color: lightskyblue;
|
||||||
|
}
|
||||||
|
|
||||||
|
.orange-icons {
|
||||||
|
color: lightcoral;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tan-icons {
|
||||||
|
color: tan;
|
||||||
|
}
|
||||||
|
|
||||||
|
.plum-icons {
|
||||||
|
color: plum;
|
||||||
|
}
|
||||||
|
|
||||||
|
.red-icons {
|
||||||
|
color: rgb(246, 70, 70);
|
||||||
|
}
|
||||||
|
|
||||||
|
.beige-icons {
|
||||||
|
color: beige;
|
||||||
|
}
|
||||||
|
|
||||||
|
.deep-blue-icons {
|
||||||
|
color: rgb(29, 95, 191);
|
||||||
|
}
|
||||||
|
|
||||||
|
.green-icons {
|
||||||
|
color: rgb(23, 128, 74);
|
||||||
|
}
|
||||||
|
|
||||||
|
.red-orange-icons {
|
||||||
|
color: rgb(255, 147, 111);
|
||||||
|
}
|
||||||
|
|
||||||
|
.gray-icons {
|
||||||
|
color: gray;
|
||||||
|
}
|
||||||
|
|
||||||
|
.skyblue-icons {
|
||||||
|
color: rgb(42, 170, 242);
|
||||||
|
}
|
||||||
|
|
||||||
|
.lightgray-icons {
|
||||||
|
color: lightgray;
|
||||||
|
}
|
||||||
|
|
||||||
|
.yellow-icons {
|
||||||
|
color: yellow;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -3,15 +3,30 @@
|
||||||
<!-- Search input section -->
|
<!-- Search input section -->
|
||||||
<div id="input" @click="open">
|
<div id="input" @click="open">
|
||||||
<!-- Close button visible when search is active -->
|
<!-- Close button visible when search is active -->
|
||||||
<button v-if="active" class="action" @click="close" :aria-label="$t('buttons.close')"
|
<button
|
||||||
:title="$t('buttons.close')">
|
v-if="active"
|
||||||
|
class="action"
|
||||||
|
@click="close"
|
||||||
|
:aria-label="$t('buttons.close')"
|
||||||
|
:title="$t('buttons.close')"
|
||||||
|
>
|
||||||
<i class="material-icons">close</i>
|
<i class="material-icons">close</i>
|
||||||
</button>
|
</button>
|
||||||
<!-- Search icon when search is not active -->
|
<!-- Search icon when search is not active -->
|
||||||
<i v-else class="material-icons">search</i>
|
<i v-else class="material-icons">search</i>
|
||||||
<!-- Input field for search -->
|
<!-- Input field for search -->
|
||||||
<input id="main-input" class="main-input" type="text" @keyup.exact="keyup" @input="submit" ref="input"
|
<input
|
||||||
:autofocus="active" v-model.trim="value" :aria-label="$t('search.search')" :placeholder="$t('search.search')" />
|
id="main-input"
|
||||||
|
class="main-input"
|
||||||
|
type="text"
|
||||||
|
@keyup.exact="keyup"
|
||||||
|
@input="submit"
|
||||||
|
ref="input"
|
||||||
|
:autofocus="active"
|
||||||
|
v-model.trim="value"
|
||||||
|
:aria-label="$t('search.search')"
|
||||||
|
:placeholder="$t('search.search')"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Search results for desktop -->
|
<!-- Search results for desktop -->
|
||||||
|
@ -21,29 +36,52 @@
|
||||||
<div>
|
<div>
|
||||||
<div v-if="active">
|
<div v-if="active">
|
||||||
<div v-if="isMobile">
|
<div v-if="isMobile">
|
||||||
<ButtonGroup :buttons="toggleOptionButton" @button-clicked="enableOptions" @remove-button-clicked="disableOptions" />
|
<ButtonGroup
|
||||||
|
:buttons="toggleOptionButton"
|
||||||
|
@button-clicked="enableOptions"
|
||||||
|
@remove-button-clicked="disableOptions"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div v-show="showOptions">
|
<div v-show="showOptions">
|
||||||
<!-- Button groups for filtering search results -->
|
<!-- Button groups for filtering search results -->
|
||||||
<ButtonGroup :buttons="folderSelect" @button-clicked="addToTypes" @remove-button-clicked="removeFromTypes"
|
<ButtonGroup
|
||||||
@disableAll="folderSelectClicked()" @enableAll="resetButtonGroups()" />
|
:buttons="folderSelect"
|
||||||
<ButtonGroup :buttons="typeSelect" @button-clicked="addToTypes" @remove-button-clicked="removeFromTypes"
|
@button-clicked="addToTypes"
|
||||||
:isDisabled="isTypeSelectDisabled" />
|
@remove-button-clicked="removeFromTypes"
|
||||||
|
@disableAll="folderSelectClicked()"
|
||||||
|
@enableAll="resetButtonGroups()"
|
||||||
|
/>
|
||||||
|
<ButtonGroup
|
||||||
|
:buttons="typeSelect"
|
||||||
|
@button-clicked="addToTypes"
|
||||||
|
@remove-button-clicked="removeFromTypes"
|
||||||
|
:isDisabled="isTypeSelectDisabled"
|
||||||
|
/>
|
||||||
<!-- Inputs for filtering by file size -->
|
<!-- Inputs for filtering by file size -->
|
||||||
<div class="sizeConstraints">
|
<div class="sizeConstraints">
|
||||||
<div class="sizeInputWrapper">
|
<div class="sizeInputWrapper">
|
||||||
<p>Smaller Than:</p>
|
<p>Smaller Than:</p>
|
||||||
<input class="sizeInput" v-model="smallerThan" type="number" min="0" placeholder="number" />
|
<input
|
||||||
|
class="sizeInput"
|
||||||
|
v-model="smallerThan"
|
||||||
|
type="number"
|
||||||
|
min="0"
|
||||||
|
placeholder="number"
|
||||||
|
/>
|
||||||
<p>MB</p>
|
<p>MB</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="sizeInputWrapper">
|
<div class="sizeInputWrapper">
|
||||||
<p>Larger Than:</p>
|
<p>Larger Than:</p>
|
||||||
<input class="sizeInput" v-model="largerThan" type="number" placeholder="number" />
|
<input
|
||||||
|
class="sizeInput"
|
||||||
|
v-model="largerThan"
|
||||||
|
type="number"
|
||||||
|
placeholder="number"
|
||||||
|
/>
|
||||||
<p>MB</p>
|
<p>MB</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- Loading icon when search is ongoing -->
|
<!-- Loading icon when search is ongoing -->
|
||||||
|
@ -78,28 +116,14 @@
|
||||||
<!-- List of search results -->
|
<!-- List of search results -->
|
||||||
<ul v-show="results.length > 0">
|
<ul v-show="results.length > 0">
|
||||||
<li v-for="(s, k) in results" :key="k" class="search-entry">
|
<li v-for="(s, k) in results" :key="k" class="search-entry">
|
||||||
<router-link :to="s.path">
|
<a :href="getRelative(s.path)">
|
||||||
<i v-if="s.type == 'directory'" class="material-icons folder-icons">
|
<Icon :mimetype="s.type" />
|
||||||
folder
|
|
||||||
</i>
|
|
||||||
<i v-else-if="s.type == 'audio'" class="material-icons audio-icons">
|
|
||||||
volume_up
|
|
||||||
</i>
|
|
||||||
<i v-else-if="s.type == 'image'" class="material-icons image-icons">
|
|
||||||
photo
|
|
||||||
</i>
|
|
||||||
<i v-else-if="s.type == 'video'" class="material-icons video-icons">
|
|
||||||
movie
|
|
||||||
</i>
|
|
||||||
<i v-else-if="s.type == 'archive'" class="material-icons archive-icons">
|
|
||||||
archive
|
|
||||||
</i>
|
|
||||||
<i v-else class="material-icons file-icons"> insert_drive_file </i>
|
|
||||||
<span class="text-container">
|
<span class="text-container">
|
||||||
{{ basePath(s.path, s.type == "directory") }}<b>{{ baseName(s.path) }}</b>
|
{{ basePath(s.path, s.type === "directory")
|
||||||
|
}}<b>{{ baseName(s.path) }}</b>
|
||||||
</span>
|
</span>
|
||||||
<div class="filesize">{{ humanSize(s.size) }}</div>
|
<div class="filesize">{{ humanSize(s.size) }}</div>
|
||||||
</router-link>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
@ -112,6 +136,7 @@ import ButtonGroup from "./ButtonGroup.vue";
|
||||||
import { search } from "@/api";
|
import { search } from "@/api";
|
||||||
import { getters, mutations, state } from "@/store";
|
import { getters, mutations, state } from "@/store";
|
||||||
import { getHumanReadableFilesize } from "@/utils/filesizes";
|
import { getHumanReadableFilesize } from "@/utils/filesizes";
|
||||||
|
import Icon from "@/components/Icon.vue";
|
||||||
|
|
||||||
var boxes = {
|
var boxes = {
|
||||||
folder: { label: "folders", icon: "folder" },
|
folder: { label: "folders", icon: "folder" },
|
||||||
|
@ -126,6 +151,7 @@ var boxes = {
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
ButtonGroup,
|
ButtonGroup,
|
||||||
|
Icon,
|
||||||
},
|
},
|
||||||
name: "search",
|
name: "search",
|
||||||
data: function () {
|
data: function () {
|
||||||
|
@ -147,9 +173,7 @@ export default {
|
||||||
{ label: "Documents", value: "type:doc" },
|
{ label: "Documents", value: "type:doc" },
|
||||||
{ label: "Archives", value: "type:archive" },
|
{ label: "Archives", value: "type:archive" },
|
||||||
],
|
],
|
||||||
toggleOptionButton: [
|
toggleOptionButton: [{ label: "Show Options" }],
|
||||||
{ label: "Show Options" },
|
|
||||||
],
|
|
||||||
value: "",
|
value: "",
|
||||||
ongoing: false,
|
ongoing: false,
|
||||||
results: [],
|
results: [],
|
||||||
|
@ -192,7 +216,7 @@ export default {
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
showOptions() {
|
showOptions() {
|
||||||
return !this.hiddenOptions || !this.isMobile
|
return !this.hiddenOptions || !this.isMobile;
|
||||||
},
|
},
|
||||||
isMobile() {
|
isMobile() {
|
||||||
return state.isMobile;
|
return state.isMobile;
|
||||||
|
@ -241,17 +265,19 @@ export default {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
getRelative(path) {
|
||||||
|
return window.location.href + "/" + path;
|
||||||
|
},
|
||||||
|
getIcon(mimetype) {
|
||||||
|
return getMaterialIconForType(mimetype);
|
||||||
|
},
|
||||||
enableOptions() {
|
enableOptions() {
|
||||||
this.hiddenOptions = false
|
this.hiddenOptions = false;
|
||||||
this.toggleOptionButton = [
|
this.toggleOptionButton = [{ label: "Hide Options" }];
|
||||||
{ label: "Hide Options" },
|
|
||||||
];
|
|
||||||
},
|
},
|
||||||
disableOptions() {
|
disableOptions() {
|
||||||
this.hiddenOptions = true
|
this.hiddenOptions = true;
|
||||||
this.toggleOptionButton = [
|
this.toggleOptionButton = [{ label: "Show Options" }];
|
||||||
{ label: "Show Options" },
|
|
||||||
];
|
|
||||||
},
|
},
|
||||||
humanSize(size) {
|
humanSize(size) {
|
||||||
return getHumanReadableFilesize(size);
|
return getHumanReadableFilesize(size);
|
||||||
|
@ -372,7 +398,7 @@ export default {
|
||||||
word-wrap: break-word;
|
word-wrap: break-word;
|
||||||
}
|
}
|
||||||
|
|
||||||
#results>#result-list {
|
#results > #result-list {
|
||||||
max-height: 80vh;
|
max-height: 80vh;
|
||||||
width: 35em;
|
width: 35em;
|
||||||
overflow: scroll;
|
overflow: scroll;
|
||||||
|
@ -518,7 +544,7 @@ body.rtl #search #result {
|
||||||
direction: ltr;
|
direction: ltr;
|
||||||
}
|
}
|
||||||
|
|
||||||
#search #result>div>*:first-child {
|
#search #result > div > *:first-child {
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -528,7 +554,7 @@ body.rtl #search #result {
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Search Results */
|
/* Search Results */
|
||||||
body.rtl #search #result ul>* {
|
body.rtl #search #result ul > * {
|
||||||
direction: ltr;
|
direction: ltr;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
<template>
|
<template>
|
||||||
<component
|
<a
|
||||||
:is="quickNav ? 'a' : 'div'"
|
:href="getUrl()"
|
||||||
:href="quickNav ? getUrl() : undefined"
|
|
||||||
:class="{
|
:class="{
|
||||||
item: true,
|
item: true,
|
||||||
activebutton: isMaximized && isSelected,
|
activebutton: isMaximized && isSelected,
|
||||||
}"
|
}"
|
||||||
|
:id="getID"
|
||||||
role="button"
|
role="button"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
:draggable="isDraggable"
|
:draggable="isDraggable"
|
||||||
|
@ -17,20 +17,21 @@
|
||||||
:aria-label="name"
|
:aria-label="name"
|
||||||
:aria-selected="isSelected"
|
:aria-selected="isSelected"
|
||||||
@contextmenu="onRightClick"
|
@contextmenu="onRightClick"
|
||||||
@click="quickNav ? toggleClick() : itemClick($event)"
|
@click="click($event)"
|
||||||
>
|
>
|
||||||
<div @click="toggleClick" :class="{ activetitle: isMaximized && isSelected }">
|
<div @click="toggleClick" :class="{ activetitle: isMaximized && isSelected }">
|
||||||
<img
|
<img
|
||||||
v-if="readOnly === undefined && type === 'image' && isThumbsEnabled && isInView"
|
v-if="
|
||||||
|
readOnly === undefined &&
|
||||||
|
type.startsWith('image') &&
|
||||||
|
isThumbsEnabled &&
|
||||||
|
isInView
|
||||||
|
"
|
||||||
v-lazy="thumbnailUrl"
|
v-lazy="thumbnailUrl"
|
||||||
:class="{ activeimg: isMaximized && isSelected }"
|
:class="{ activeimg: isMaximized && isSelected }"
|
||||||
ref="thumbnail"
|
ref="thumbnail"
|
||||||
/>
|
/>
|
||||||
<i
|
<Icon v-else :mimetype="type" />
|
||||||
:class="{ iconActive: isMaximized && isSelected }"
|
|
||||||
v-else
|
|
||||||
class="material-icons"
|
|
||||||
></i>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="text" :class="{ activecontent: isMaximized && isSelected }">
|
<div class="text" :class="{ activecontent: isMaximized && isSelected }">
|
||||||
|
@ -40,7 +41,7 @@
|
||||||
<time :datetime="modified">{{ humanTime() }}</time>
|
<time :datetime="modified">{{ humanTime() }}</time>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</component>
|
</a>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
@ -77,9 +78,14 @@ import * as upload from "@/utils/upload";
|
||||||
import { state, getters, mutations } from "@/store"; // Import your custom store
|
import { state, getters, mutations } from "@/store"; // Import your custom store
|
||||||
import { baseURL } from "@/utils/constants";
|
import { baseURL } from "@/utils/constants";
|
||||||
import { router } from "@/router";
|
import { router } from "@/router";
|
||||||
|
import { url } from "@/utils";
|
||||||
|
import Icon from "@/components/Icon.vue";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "item",
|
name: "item",
|
||||||
|
components: {
|
||||||
|
Icon,
|
||||||
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
isThumbnailInView: false,
|
isThumbnailInView: false,
|
||||||
|
@ -99,6 +105,9 @@ export default {
|
||||||
"path",
|
"path",
|
||||||
],
|
],
|
||||||
computed: {
|
computed: {
|
||||||
|
getID() {
|
||||||
|
return url.base64Encode(encodeURIComponent(this.name));
|
||||||
|
},
|
||||||
quickNav() {
|
quickNav() {
|
||||||
return state.user.singleClick && !state.multiple;
|
return state.user.singleClick && !state.multiple;
|
||||||
},
|
},
|
||||||
|
@ -146,6 +155,7 @@ export default {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
|
// Prevent default navigation for left-clicks
|
||||||
const observer = new IntersectionObserver(this.handleIntersect, {
|
const observer = new IntersectionObserver(this.handleIntersect, {
|
||||||
root: null,
|
root: null,
|
||||||
rootMargin: "0px",
|
rootMargin: "0px",
|
||||||
|
@ -159,6 +169,13 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
updateHashAndNavigate(path) {
|
||||||
|
// Update hash in the browser without full page reload
|
||||||
|
window.location.hash = path;
|
||||||
|
|
||||||
|
// Optional: Trigger native navigation
|
||||||
|
window.location.href = this.getRelative(path);
|
||||||
|
},
|
||||||
getUrl() {
|
getUrl() {
|
||||||
return baseURL.slice(0, -1) + this.url;
|
return baseURL.slice(0, -1) + this.url;
|
||||||
},
|
},
|
||||||
|
@ -166,7 +183,8 @@ export default {
|
||||||
event.preventDefault(); // Prevent default context menu
|
event.preventDefault(); // Prevent default context menu
|
||||||
|
|
||||||
// If no items are selected, select the right-clicked item
|
// If no items are selected, select the right-clicked item
|
||||||
if (getters.selectedCount() === 0) {
|
if (!state.multiple) {
|
||||||
|
mutations.resetSelected();
|
||||||
mutations.addSelected(this.index);
|
mutations.addSelected(this.index);
|
||||||
}
|
}
|
||||||
mutations.showHover({
|
mutations.showHover({
|
||||||
|
@ -278,13 +296,18 @@ export default {
|
||||||
|
|
||||||
action(overwrite, rename);
|
action(overwrite, rename);
|
||||||
},
|
},
|
||||||
itemClick(event) {
|
|
||||||
if (this.singleClick && !state.multiple) this.open();
|
|
||||||
else this.click(event);
|
|
||||||
},
|
|
||||||
click(event) {
|
click(event) {
|
||||||
if (!this.singleClick && getters.selectedCount() !== 0) event.preventDefault();
|
if (event.button === 0) {
|
||||||
|
// Left-click
|
||||||
|
event.preventDefault();
|
||||||
|
if (this.quickNav) {
|
||||||
|
this.open();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.singleClick && getters.selectedCount() !== 0 && event.button === 0) {
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.touches = 0;
|
this.touches = 0;
|
||||||
}, 500);
|
}, 500);
|
||||||
|
@ -319,12 +342,15 @@ export default {
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!this.singleClick && !event.ctrlKey && !event.metaKey && !state.multiple)
|
if (!this.singleClick && !event.ctrlKey && !event.metaKey && !state.multiple) {
|
||||||
mutations.resetSelected();
|
mutations.resetSelected();
|
||||||
|
}
|
||||||
mutations.addSelected(this.index);
|
mutations.addSelected(this.index);
|
||||||
},
|
},
|
||||||
open() {
|
open() {
|
||||||
router.push({ path: this.url });
|
location.hash = state.req.items[this.index].name;
|
||||||
|
const newurl = url.removePrefix(this.url);
|
||||||
|
router.push({ path: newurl });
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -16,6 +16,10 @@
|
||||||
<strong>{{ $t("prompts.size") }}:</strong>
|
<strong>{{ $t("prompts.size") }}:</strong>
|
||||||
<span id="content_length"></span> {{ humanSize }}
|
<span id="content_length"></span> {{ humanSize }}
|
||||||
</p>
|
</p>
|
||||||
|
<p v-if="!dir || selected.length > 1">
|
||||||
|
<strong>Type:</strong>
|
||||||
|
<span id="content_length"></span> {{ type }}
|
||||||
|
</p>
|
||||||
<p v-if="selected.length < 2" :title="modTime">
|
<p v-if="selected.length < 2" :title="modTime">
|
||||||
<strong>{{ $t("prompts.lastModified") }}:</strong> {{ humanTime }}
|
<strong>{{ $t("prompts.lastModified") }}:</strong> {{ humanTime }}
|
||||||
</p>
|
</p>
|
||||||
|
@ -124,6 +128,11 @@ export default {
|
||||||
? state.req.name
|
? state.req.name
|
||||||
: state.req.items[this.selected[0]].name;
|
: state.req.items[this.selected[0]].name;
|
||||||
},
|
},
|
||||||
|
type() {
|
||||||
|
return getters.selectedCount() === 0
|
||||||
|
? state.req.type
|
||||||
|
: state.req.items[this.selected[0]].type;
|
||||||
|
},
|
||||||
dir() {
|
dir() {
|
||||||
return (
|
return (
|
||||||
getters.selectedCount() > 1 ||
|
getters.selectedCount() > 1 ||
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
<div class="card-title">
|
<div class="card-title">
|
||||||
<h2>{{ $t("buttons.share") }}</h2>
|
<h2>{{ $t("buttons.share") }}</h2>
|
||||||
</div>
|
</div>
|
||||||
<div class="searchContext">Path: {{ getContext }}</div>
|
<div class="searchContext">Path: {{ subpath }}</div>
|
||||||
|
|
||||||
<template v-if="listing">
|
<template v-if="listing">
|
||||||
<div class="card-content">
|
<div class="card-content">
|
||||||
|
@ -167,26 +167,14 @@ export default {
|
||||||
}
|
}
|
||||||
return state.req.items[this.selected[0]].url;
|
return state.req.items[this.selected[0]].url;
|
||||||
},
|
},
|
||||||
getContext() {
|
|
||||||
const prefix = `/files/`;
|
|
||||||
let path = state.route.path.replace(prefix, "./");
|
|
||||||
if (getters.selectedCount() === 1) {
|
|
||||||
path = path + state.req.items[this.selected[0]].name;
|
|
||||||
}
|
|
||||||
return decodeURIComponent(path);
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
async beforeMount() {
|
async beforeMount() {
|
||||||
try {
|
try {
|
||||||
const prefix = `/files`;
|
let path = "." + getters.routePath("files");
|
||||||
let path = state.route.path.startsWith(prefix)
|
if (getters.selectedCount() === 1) {
|
||||||
? state.route.path.slice(prefix.length)
|
path = path + state.req.items[this.selected[0]].name;
|
||||||
: state.route.path;
|
|
||||||
path = decodeURIComponent(path);
|
|
||||||
if (path == "") {
|
|
||||||
path = "/";
|
|
||||||
}
|
}
|
||||||
this.subpath = path;
|
this.subpath = decodeURIComponent(path);
|
||||||
// get last element of the path
|
// get last element of the path
|
||||||
const links = await shareApi.get(this.subpath);
|
const links = await shareApi.get(this.subpath);
|
||||||
this.links = links;
|
this.links = links;
|
||||||
|
|
|
@ -63,6 +63,7 @@ export default {
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
margin-bottom: 0px !important;
|
margin-bottom: 0px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
#sidebar {
|
#sidebar {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
@ -78,9 +79,6 @@ export default {
|
||||||
padding-bottom: 4em;
|
padding-bottom: 4em;
|
||||||
background-color: rgb(255 255 255 / 50%) !important;
|
background-color: rgb(255 255 255 / 50%) !important;
|
||||||
}
|
}
|
||||||
#sidebar.dark-mode {
|
|
||||||
background-color: rgb(37 49 55 / 33%) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
#sidebar.sticky {
|
#sidebar.sticky {
|
||||||
z-index: 3;
|
z-index: 3;
|
||||||
|
|
|
@ -78,7 +78,7 @@ over
|
||||||
/* Main Content */
|
/* Main Content */
|
||||||
main {
|
main {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
padding: 1em;
|
padding: .5em;
|
||||||
padding-top: 4em;
|
padding-top: 4em;
|
||||||
overflow: scroll;
|
overflow: scroll;
|
||||||
top: 0;
|
top: 0;
|
||||||
|
|
|
@ -272,3 +272,27 @@
|
||||||
.dark-mode #results {
|
.dark-mode #results {
|
||||||
background-color: var(--background);
|
background-color: var(--background);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Use the class .dark-mode to apply styles conditionally */
|
||||||
|
.dark-mode {
|
||||||
|
background: #141D24 !important;
|
||||||
|
color: var(--textPrimary);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Header */
|
||||||
|
.dark-mode-header {
|
||||||
|
color: white;
|
||||||
|
background: #141D24;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Header with backdrop-filter support */
|
||||||
|
@supports (backdrop-filter: none) {
|
||||||
|
.dark-mode-header {
|
||||||
|
background-color: rgb(37 49 55 / 33%) !important;
|
||||||
|
backdrop-filter: blur(16px) invert(0.1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#sidebar.dark-mode {
|
||||||
|
background-color: #141D24 !important;
|
||||||
|
}
|
|
@ -1,4 +1,6 @@
|
||||||
@import 'material-icons/iconfont/filled.css';
|
@import 'material-icons/iconfont/filled.css';
|
||||||
|
@import 'material-icons/iconfont/outlined.css';
|
||||||
|
@import 'material-symbols/index.css';
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'Roboto';
|
font-family: 'Roboto';
|
||||||
|
@ -168,6 +170,3 @@
|
||||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;
|
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;
|
||||||
}
|
}
|
||||||
|
|
||||||
.material-icons {
|
|
||||||
font-size: 1.5rem;
|
|
||||||
}
|
|
|
@ -9,7 +9,6 @@ header {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
background-color: rgb(255 255 255 / 50%) !important;
|
|
||||||
padding: 0.5em;
|
padding: 0.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -49,25 +48,3 @@ header img {
|
||||||
header .action span {
|
header .action span {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Icon Colors */
|
|
||||||
.folder-icons {
|
|
||||||
color: var(--icon-blue);
|
|
||||||
}
|
|
||||||
|
|
||||||
.video-icons {
|
|
||||||
color: lightskyblue;
|
|
||||||
}
|
|
||||||
|
|
||||||
.image-icons {
|
|
||||||
color: lightcoral;
|
|
||||||
}
|
|
||||||
|
|
||||||
.archive-icons {
|
|
||||||
color: tan;
|
|
||||||
}
|
|
||||||
|
|
||||||
.audio-icons {
|
|
||||||
color: plum;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
|
@ -1,205 +0,0 @@
|
||||||
/* Icons */
|
|
||||||
|
|
||||||
/* General */
|
|
||||||
|
|
||||||
.file-icons [aria-label^="."] { opacity: 0.33 }
|
|
||||||
.file-icons [aria-label$=".bak"] { opacity: 0.33 }
|
|
||||||
|
|
||||||
.file-icons [data-type=audio] i::before { content: 'volume_up' }
|
|
||||||
.file-icons [data-type=blob] i::before { content: 'insert_drive_file' }
|
|
||||||
.file-icons [data-type=image] i::before { content: 'image' }
|
|
||||||
.file-icons [data-type=pdf] i::before { content: 'description' }
|
|
||||||
.file-icons [data-type=text] i::before { content: 'description' }
|
|
||||||
.file-icons [data-type=video] i::before { content: 'movie' }
|
|
||||||
.file-icons [data-type=invalid_link] i::before { content: 'link_off' }
|
|
||||||
|
|
||||||
/* #f90 - Image */
|
|
||||||
|
|
||||||
.file-icons [aria-label$=".ai"] i::before,
|
|
||||||
.file-icons [aria-label$=".odg"] i::before,
|
|
||||||
.file-icons [aria-label$=".xcf"] i::before
|
|
||||||
{ content: 'image' }
|
|
||||||
|
|
||||||
/* #f90 - Presentation */
|
|
||||||
|
|
||||||
.file-icons [aria-label$=".odp"] i::before,
|
|
||||||
.file-icons [aria-label$=".ppt"] i::before,
|
|
||||||
.file-icons [aria-label$=".pptx"] i::before
|
|
||||||
{ content: 'slideshow' }
|
|
||||||
|
|
||||||
/* #0f0 - Spreadsheet/Database */
|
|
||||||
|
|
||||||
.file-icons [aria-label$=".csv"] i::before,
|
|
||||||
.file-icons [aria-label$=".db"] i::before,
|
|
||||||
.file-icons [aria-label$=".odb"] i::before,
|
|
||||||
.file-icons [aria-label$=".ods"] i::before,
|
|
||||||
.file-icons [aria-label$=".xls"] i::before,
|
|
||||||
.file-icons [aria-label$=".xlsx"] i::before
|
|
||||||
{ content: 'border_all' }
|
|
||||||
|
|
||||||
/* #00f - Document */
|
|
||||||
|
|
||||||
.file-icons [aria-label$=".doc"] i::before,
|
|
||||||
.file-icons [aria-label$=".docx"] i::before,
|
|
||||||
.file-icons [aria-label$=".log"] i::before,
|
|
||||||
.file-icons [aria-label$=".odt"] i::before,
|
|
||||||
.file-icons [aria-label$=".rtf"] i::before
|
|
||||||
{ content: 'description' }
|
|
||||||
|
|
||||||
/* #999 - Code */
|
|
||||||
|
|
||||||
.file-icons [aria-label$=".c"] i::before,
|
|
||||||
.file-icons [aria-label$=".cpp"] i::before,
|
|
||||||
.file-icons [aria-label$=".cs"] i::before,
|
|
||||||
.file-icons [aria-label$=".css"] i::before,
|
|
||||||
.file-icons [aria-label$=".go"] i::before,
|
|
||||||
.file-icons [aria-label$=".h"] i::before,
|
|
||||||
.file-icons [aria-label$=".html"] i::before,
|
|
||||||
.file-icons [aria-label$=".java"] i::before,
|
|
||||||
.file-icons [aria-label$=".js"] i::before,
|
|
||||||
.file-icons [aria-label$=".json"] i::before,
|
|
||||||
.file-icons [aria-label$=".kt"] i::before,
|
|
||||||
.file-icons [aria-label$=".php"] i::before,
|
|
||||||
.file-icons [aria-label$=".py"] i::before,
|
|
||||||
.file-icons [aria-label$=".rb"] i::before,
|
|
||||||
.file-icons [aria-label$=".rs"] i::before,
|
|
||||||
.file-icons [aria-label$=".vue"] i::before,
|
|
||||||
.file-icons [aria-label$=".xml"] i::before,
|
|
||||||
.file-icons [aria-label$=".yml"] i::before
|
|
||||||
{ content: 'code' }
|
|
||||||
|
|
||||||
/* #999 - Executable */
|
|
||||||
|
|
||||||
.file-icons [aria-label$=".apk"] i::before,
|
|
||||||
.file-icons [aria-label$=".bat"] i::before,
|
|
||||||
.file-icons [aria-label$=".exe"] i::before,
|
|
||||||
.file-icons [aria-label$=".jar"] i::before,
|
|
||||||
.file-icons [aria-label$=".ps1"] i::before,
|
|
||||||
.file-icons [aria-label$=".sh"] i::before
|
|
||||||
{ content: 'web_asset' }
|
|
||||||
|
|
||||||
/* #999 - Installer */
|
|
||||||
|
|
||||||
.file-icons [aria-label$=".deb"] i::before,
|
|
||||||
.file-icons [aria-label$=".msi"] i::before,
|
|
||||||
.file-icons [aria-label$=".pkg"] i::before,
|
|
||||||
.file-icons [aria-label$=".rpm"] i::before
|
|
||||||
{ content: 'archive' }
|
|
||||||
|
|
||||||
/* #999 - Compressed */
|
|
||||||
|
|
||||||
.file-icons [aria-label$=".7z"] i::before,
|
|
||||||
.file-icons [aria-label$=".bz2"] i::before,
|
|
||||||
.file-icons [aria-label$=".cab"] i::before,
|
|
||||||
.file-icons [aria-label$=".gz"] i::before,
|
|
||||||
.file-icons [aria-label$=".rar"] i::before,
|
|
||||||
.file-icons [aria-label$=".tar"] i::before,
|
|
||||||
.file-icons [aria-label$=".xz"] i::before,
|
|
||||||
.file-icons [aria-label$=".zip"] i::before,
|
|
||||||
.file-icons [aria-label$=".zst"] i::before
|
|
||||||
{ content: 'folder_zip' }
|
|
||||||
|
|
||||||
/* #999 - Disk */
|
|
||||||
|
|
||||||
.file-icons [aria-label$=".ccd"] i::before,
|
|
||||||
.file-icons [aria-label$=".dmg"] i::before,
|
|
||||||
.file-icons [aria-label$=".iso"] i::before,
|
|
||||||
.file-icons [aria-label$=".mdf"] i::before,
|
|
||||||
.file-icons [aria-label$=".vdi"] i::before,
|
|
||||||
.file-icons [aria-label$=".vhd"] i::before,
|
|
||||||
.file-icons [aria-label$=".vmdk"] i::before,
|
|
||||||
.file-icons [aria-label$=".wim"] i::before
|
|
||||||
{ content: 'album' }
|
|
||||||
|
|
||||||
/* #999 - Font */
|
|
||||||
|
|
||||||
.file-icons [aria-label$=".otf"] i::before,
|
|
||||||
.file-icons [aria-label$=".ttf"] i::before,
|
|
||||||
.file-icons [aria-label$=".woff"] i::before,
|
|
||||||
.file-icons [aria-label$=".woff2"] i::before
|
|
||||||
{ content: 'font_download' }
|
|
||||||
|
|
||||||
/* Colors */
|
|
||||||
|
|
||||||
/* General */
|
|
||||||
|
|
||||||
.file-icons [data-type=audio] i { color: var(--icon-yellow) }
|
|
||||||
.file-icons [data-type=image] i { color: var(--icon-orange) }
|
|
||||||
.file-icons [data-type=video] i { color: var(--icon-violet) }
|
|
||||||
.file-icons [data-type=invalid_link] i { color: var(--icon-red) }
|
|
||||||
|
|
||||||
/* #f00 - Adobe/Oracle */
|
|
||||||
|
|
||||||
.file-icons [aria-label$=".ai"] i,
|
|
||||||
.file-icons [aria-label$=".java"] i,
|
|
||||||
.file-icons [aria-label$=".jar"] i,
|
|
||||||
.file-icons [aria-label$=".psd"] i,
|
|
||||||
.file-icons [aria-label$=".rb"] i,
|
|
||||||
.file-icons [data-type=pdf] i
|
|
||||||
{ color: var(--icon-red) }
|
|
||||||
|
|
||||||
/* #f90 - Image/Presentation */
|
|
||||||
|
|
||||||
.file-icons [aria-label$=".html"] i,
|
|
||||||
.file-icons [aria-label$=".odg"] i,
|
|
||||||
.file-icons [aria-label$=".odp"] i,
|
|
||||||
.file-icons [aria-label$=".ppt"] i,
|
|
||||||
.file-icons [aria-label$=".pptx"] i,
|
|
||||||
.file-icons [aria-label$=".vue"] i,
|
|
||||||
.file-icons [aria-label$=".xcf"] i
|
|
||||||
{ color: var(--icon-orange) }
|
|
||||||
|
|
||||||
/* #ff0 - Various */
|
|
||||||
|
|
||||||
.file-icons [aria-label$=".css"] i,
|
|
||||||
.file-icons [aria-label$=".js"] i,
|
|
||||||
.file-icons [aria-label$=".json"] i,
|
|
||||||
.file-icons [aria-label$=".zip"] i
|
|
||||||
{ color: var(--icon-yellow) }
|
|
||||||
|
|
||||||
/* #0f0 - Spreadsheet/Google */
|
|
||||||
|
|
||||||
.file-icons [aria-label$=".apk"] i,
|
|
||||||
.file-icons [aria-label$=".dex"] i,
|
|
||||||
.file-icons [aria-label$=".go"] i,
|
|
||||||
.file-icons [aria-label$=".ods"] i,
|
|
||||||
.file-icons [aria-label$=".xls"] i,
|
|
||||||
.file-icons [aria-label$=".xlsx"] i
|
|
||||||
{ color: var(--icon-green) }
|
|
||||||
|
|
||||||
/* #00f - Document/Microsoft/Apple/Closed */
|
|
||||||
|
|
||||||
.file-icons [aria-label$=".aac"] i,
|
|
||||||
.file-icons [aria-label$=".bat"] i,
|
|
||||||
.file-icons [aria-label$=".cab"] i,
|
|
||||||
.file-icons [aria-label$=".cs"] i,
|
|
||||||
.file-icons [aria-label$=".dmg"] i,
|
|
||||||
.file-icons [aria-label$=".doc"] i,
|
|
||||||
.file-icons [aria-label$=".docx"] i,
|
|
||||||
.file-icons [aria-label$=".emf"] i,
|
|
||||||
.file-icons [aria-label$=".exe"] i,
|
|
||||||
.file-icons [aria-label$=".ico"] i,
|
|
||||||
.file-icons [aria-label$=".mp2"] i,
|
|
||||||
.file-icons [aria-label$=".mp3"] i,
|
|
||||||
.file-icons [aria-label$=".mp4"] i,
|
|
||||||
.file-icons [aria-label$=".mpg"] i,
|
|
||||||
.file-icons [aria-label$=".msi"] i,
|
|
||||||
.file-icons [aria-label$=".odt"] i,
|
|
||||||
.file-icons [aria-label$=".ps1"] i,
|
|
||||||
.file-icons [aria-label$=".rtf"] i,
|
|
||||||
.file-icons [aria-label$=".vob"] i,
|
|
||||||
.file-icons [aria-label$=".wim"] i
|
|
||||||
{ color: var(--icon-blue) }
|
|
||||||
|
|
||||||
/* #60f - Various */
|
|
||||||
|
|
||||||
.file-icons [aria-label$=".iso"] i,
|
|
||||||
.file-icons [aria-label$=".php"] i,
|
|
||||||
.file-icons [aria-label$=".rar"] i
|
|
||||||
{ color: var(--icon-violet) }
|
|
||||||
|
|
||||||
/* Overrides */
|
|
||||||
|
|
||||||
.file-icons [data-dir=true] i { color: var(--icon-blue) }
|
|
||||||
.file-icons [data-dir=true] i::before { content: 'folder' }
|
|
||||||
.file-icons [aria-selected=true] i { color: var(--item-selected) }
|
|
|
@ -7,7 +7,6 @@
|
||||||
@import "./base.css";
|
@import "./base.css";
|
||||||
@import "./header.css";
|
@import "./header.css";
|
||||||
@import "./listing.css";
|
@import "./listing.css";
|
||||||
@import "./listing-icons.css";
|
|
||||||
@import "./dashboard.css";
|
@import "./dashboard.css";
|
||||||
@import "./login.css";
|
@import "./login.css";
|
||||||
@import './mobile.css';
|
@import './mobile.css';
|
||||||
|
|
|
@ -121,7 +121,7 @@ const router = createRouter({
|
||||||
|
|
||||||
// Helper function to check if a route resolves to itself
|
// Helper function to check if a route resolves to itself
|
||||||
function isSameRoute(to: RouteLocation, from: RouteLocation) {
|
function isSameRoute(to: RouteLocation, from: RouteLocation) {
|
||||||
return to.path === from.path && JSON.stringify(to.params) === JSON.stringify(from.params);
|
return to.path === from.path && JSON.stringify(to.params) === JSON.stringify(from.params) && to.hash === from.hash;
|
||||||
}
|
}
|
||||||
|
|
||||||
router.beforeResolve(async (to, from, next) => {
|
router.beforeResolve(async (to, from, next) => {
|
||||||
|
|
|
@ -105,6 +105,9 @@ export const getters = {
|
||||||
if (getters.currentView() == "settings") {
|
if (getters.currentView() == "settings") {
|
||||||
visible = !getters.isMobile();
|
visible = !getters.isMobile();
|
||||||
}
|
}
|
||||||
|
if (getters.currentView() == "share") {
|
||||||
|
visible = false
|
||||||
|
}
|
||||||
if (typeof getters.currentPromptName() === "string" && !getters.isStickySidebar()) {
|
if (typeof getters.currentPromptName() === "string" && !getters.isStickySidebar()) {
|
||||||
visible = false;
|
visible = false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
import * as url from "./url.js";
|
||||||
|
|
||||||
|
export {
|
||||||
|
url,
|
||||||
|
};
|
|
@ -0,0 +1,198 @@
|
||||||
|
|
||||||
|
export function getTypeInfo(mimeType) {
|
||||||
|
if (mimeType === "directory" || mimeType === "application/vnd.google-apps.folder") {
|
||||||
|
return {
|
||||||
|
classes: "blue-icons material-icons",
|
||||||
|
materialIcon: "folder",
|
||||||
|
simpleType: "directory",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mimeType.startsWith("image/")) {
|
||||||
|
return {
|
||||||
|
classes: "orange-icons material-icons",
|
||||||
|
materialIcon: "photo",
|
||||||
|
simpleType: "image",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
mimeType.startsWith("audio/") ||
|
||||||
|
mimeType === "application/vnd.google-apps.audio"
|
||||||
|
) {
|
||||||
|
return {
|
||||||
|
classes: "plum-icons material-icons",
|
||||||
|
materialIcon: "volume_up",
|
||||||
|
simpleType: "audio",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
mimeType.startsWith("video/") ||
|
||||||
|
mimeType === "application/vnd.google-apps.video"
|
||||||
|
) {
|
||||||
|
return {
|
||||||
|
classes: "skyblue-icons material-icons",
|
||||||
|
materialIcon: "movie",
|
||||||
|
simpleType: "video",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mimeType.startsWith("font/")) {
|
||||||
|
return {
|
||||||
|
classes: "gray-icons material-icons",
|
||||||
|
materialIcon: "font_download",
|
||||||
|
simpleType: "font",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
mimeType === "application/zip" ||
|
||||||
|
mimeType === "application/x-7z-compressed" ||
|
||||||
|
mimeType === "application/x-bzip" ||
|
||||||
|
mimeType === "application/x-rar-compressed" ||
|
||||||
|
mimeType === "application/x-tar" ||
|
||||||
|
mimeType === "application/gzip" ||
|
||||||
|
mimeType === "application/x-xz" ||
|
||||||
|
mimeType === "application/x-zip-compressed" ||
|
||||||
|
mimeType === "application/x-gzip"
|
||||||
|
) {
|
||||||
|
return {
|
||||||
|
classes: "tan-icons material-icons",
|
||||||
|
materialIcon: "archive",
|
||||||
|
simpleType: "archive",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mimeType === "application/pdf") {
|
||||||
|
return {
|
||||||
|
classes: "red-icons material-icons",
|
||||||
|
materialIcon: "picture_as_pdf",
|
||||||
|
simpleType: "pdf",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
mimeType === "application/msword" ||
|
||||||
|
mimeType ===
|
||||||
|
"application/vnd.openxmlformats-officedocument.wordprocessingml.document" ||
|
||||||
|
mimeType === "application/vnd.google-apps.document" ||
|
||||||
|
mimeType === "text/rtf" ||
|
||||||
|
mimeType === "application/rtf"
|
||||||
|
) {
|
||||||
|
return {
|
||||||
|
classes: "deep-blue-icons material-icons",
|
||||||
|
materialIcon: "description",
|
||||||
|
simpleType: "document",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
mimeType === "application/vnd.ms-excel" ||
|
||||||
|
mimeType ===
|
||||||
|
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" ||
|
||||||
|
mimeType === "application/vnd.google-apps.spreadsheet"
|
||||||
|
) {
|
||||||
|
return {
|
||||||
|
classes: "green-icons material-icons",
|
||||||
|
materialIcon: "border_all",
|
||||||
|
simpleType: "document",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (mimeType === "text/csv") {
|
||||||
|
return {
|
||||||
|
classes: "green-icons material-icons",
|
||||||
|
materialIcon: "border_all",
|
||||||
|
simpleType: "document",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
mimeType === "application/vnd.ms-powerpoint" ||
|
||||||
|
mimeType ===
|
||||||
|
"application/vnd.openxmlformats-officedocument.presentationml.presentation" ||
|
||||||
|
mimeType === "application/vnd.google-apps.presentation"
|
||||||
|
) {
|
||||||
|
return {
|
||||||
|
classes: "red-orange-icons material-icons",
|
||||||
|
materialIcon: "slideshow",
|
||||||
|
simpleType: "document",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mimeType === "text/plain" || mimeType === "text/markdown") {
|
||||||
|
return {
|
||||||
|
classes: "beige-icons material-icons",
|
||||||
|
materialIcon: "description",
|
||||||
|
simpleType: "text",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mimeType === "application/json" || mimeType === "application/xml") {
|
||||||
|
return {
|
||||||
|
classes: "yellow-icons material-icons",
|
||||||
|
materialIcon: "code",
|
||||||
|
simpleType: "text",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
mimeType === "application/octet-stream" ||
|
||||||
|
mimeType === "application/x-executable" ||
|
||||||
|
mimeType === "application/vnd.google-apps.unknown"
|
||||||
|
) {
|
||||||
|
return {
|
||||||
|
classes: "gray-icons material-icons",
|
||||||
|
materialIcon: "memory",
|
||||||
|
simpleType: "binary",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mimeType === "application/javascript" || mimeType === "text/javascript") {
|
||||||
|
return {
|
||||||
|
classes: "yellow-icons material-symbols-outlined",
|
||||||
|
materialIcon: "javascript",
|
||||||
|
simpleType: "text",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
mimeType === "application/x-python" ||
|
||||||
|
mimeType === "text/html" ||
|
||||||
|
mimeType === "text/css" ||
|
||||||
|
mimeType === "application/vnd.google-apps.sites"
|
||||||
|
) {
|
||||||
|
return {
|
||||||
|
classes: "gray-icons material-symbols-outlined",
|
||||||
|
materialIcon: "code_blocks",
|
||||||
|
simpleType: "text",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
mimeType === "application/x-disk-image" ||
|
||||||
|
mimeType === "application/x-iso-image" ||
|
||||||
|
mimeType === "application/x-apple-diskimage"
|
||||||
|
) {
|
||||||
|
return {
|
||||||
|
classes: "gray-icons material-symbols-outlined",
|
||||||
|
materialIcon: "deployed_code",
|
||||||
|
simpleType: "binary",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mimeType === "invalid_link") {
|
||||||
|
return {
|
||||||
|
classes: "lightgray-icons material-icons",
|
||||||
|
materialIcon: "link_off",
|
||||||
|
simpleType: "invalid_link",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default fallback
|
||||||
|
return {
|
||||||
|
classes: "lightgray-icons material-icons",
|
||||||
|
materialIcon: "description",
|
||||||
|
simpleType: "blob",
|
||||||
|
};
|
||||||
|
}
|
|
@ -46,7 +46,7 @@ export default {
|
||||||
getApiPath
|
getApiPath
|
||||||
};
|
};
|
||||||
|
|
||||||
export function removePrefix(path, prefix) {
|
export function removePrefix(path, prefix = "") {
|
||||||
if (path === undefined) {
|
if (path === undefined) {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
@ -54,9 +54,12 @@ export function removePrefix(path, prefix) {
|
||||||
prefix = "/" + trimSlashes(prefix)
|
prefix = "/" + trimSlashes(prefix)
|
||||||
}
|
}
|
||||||
const combined = trimSlashes(baseURL) + prefix
|
const combined = trimSlashes(baseURL) + prefix
|
||||||
|
const combined2 = "/" + combined
|
||||||
// Remove combined (baseURL + prefix) from the start of the path if present
|
// Remove combined (baseURL + prefix) from the start of the path if present
|
||||||
if (path.startsWith(combined)) {
|
if (path.startsWith(combined)) {
|
||||||
path = path.slice(combined.length);
|
path = path.slice(combined.length);
|
||||||
|
} else if (path.startsWith(combined2)) {
|
||||||
|
path = path.slice(combined2.length);
|
||||||
} else if (path.startsWith(prefix)) {
|
} else if (path.startsWith(prefix)) {
|
||||||
// Fallback: remove only the prefix if the combined string isn't present
|
// Fallback: remove only the prefix if the combined string isn't present
|
||||||
path = path.slice(prefix.length);
|
path = path.slice(prefix.length);
|
||||||
|
@ -110,3 +113,7 @@ export function removeLeadingSlash(str) {
|
||||||
export function trimSlashes(str) {
|
export function trimSlashes(str) {
|
||||||
return removeLeadingSlash(removeTrailingSlash(str))
|
return removeLeadingSlash(removeTrailingSlash(str))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function base64Encode(str) {
|
||||||
|
return btoa(unescape(encodeURIComponent(str)));
|
||||||
|
}
|
|
@ -24,7 +24,7 @@ import Preview from "@/views/files/Preview.vue";
|
||||||
import ListingView from "@/views/files/ListingView.vue";
|
import ListingView from "@/views/files/ListingView.vue";
|
||||||
import Editor from "@/views/files/Editor.vue";
|
import Editor from "@/views/files/Editor.vue";
|
||||||
import { state, mutations, getters } from "@/store";
|
import { state, mutations, getters } from "@/store";
|
||||||
import { pathsMatch } from "@/utils/url.js";
|
import { url } from "@/utils";
|
||||||
import { notify } from "@/notify";
|
import { notify } from "@/notify";
|
||||||
//import { removePrefix } from "@/utils/url.js";
|
//import { removePrefix } from "@/utils/url.js";
|
||||||
|
|
||||||
|
@ -41,6 +41,8 @@ export default {
|
||||||
return {
|
return {
|
||||||
error: null,
|
error: null,
|
||||||
width: window.innerWidth,
|
width: window.innerWidth,
|
||||||
|
lastPath: "",
|
||||||
|
lastHash: "",
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
@ -66,6 +68,7 @@ export default {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
|
window.addEventListener("hashchange", this.scrollToHash);
|
||||||
window.addEventListener("keydown", this.keyEvent);
|
window.addEventListener("keydown", this.keyEvent);
|
||||||
},
|
},
|
||||||
beforeUnmount() {
|
beforeUnmount() {
|
||||||
|
@ -75,11 +78,26 @@ export default {
|
||||||
mutations.replaceRequest({}); // Use mutation
|
mutations.replaceRequest({}); // Use mutation
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
scrollToHash() {
|
||||||
|
if (window.location.hash === this.lastHash) return;
|
||||||
|
this.lastHash = window.location.hash
|
||||||
|
if (window.location.hash) {
|
||||||
|
const id = url.base64Encode(window.location.hash.slice(1));
|
||||||
|
const element = document.getElementById(id);
|
||||||
|
if (element) {
|
||||||
|
element.scrollIntoView({
|
||||||
|
behavior: "instant",
|
||||||
|
block: "center",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
async fetchData() {
|
async fetchData() {
|
||||||
|
if (state.route.path === this.lastPath) return;
|
||||||
|
this.lastHash = ""
|
||||||
// Set loading to true and reset the error.
|
// Set loading to true and reset the error.
|
||||||
mutations.setLoading("files", true);
|
mutations.setLoading("files", true);
|
||||||
this.error = null;
|
this.error = null;
|
||||||
|
|
||||||
// Reset view information using mutations
|
// Reset view information using mutations
|
||||||
mutations.setReload(false);
|
mutations.setReload(false);
|
||||||
mutations.resetSelected();
|
mutations.resetSelected();
|
||||||
|
@ -94,14 +112,14 @@ export default {
|
||||||
if (res.type != "directory") {
|
if (res.type != "directory") {
|
||||||
let content = false;
|
let content = false;
|
||||||
// only check content for blob or text files
|
// only check content for blob or text files
|
||||||
if (res.type == "blob" || res.type == "text") {
|
if (res.type.startsWith("application") || res.type.startsWith("text")) {
|
||||||
content = true;
|
content = true;
|
||||||
}
|
}
|
||||||
res = await filesApi.fetchFiles(getters.routePath(), content);
|
res = await filesApi.fetchFiles(getters.routePath(), content);
|
||||||
}
|
}
|
||||||
data = res;
|
data = res;
|
||||||
// Verify if the fetched path matches the current route
|
// Verify if the fetched path matches the current route
|
||||||
if (pathsMatch(res.path, `/${state.route.params.path}`)) {
|
if (url.pathsMatch(res.path, `/${state.route.params.path}`)) {
|
||||||
document.title = `${res.name} - ${document.title}`;
|
document.title = `${res.name} - ${document.title}`;
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
@ -112,6 +130,10 @@ export default {
|
||||||
mutations.replaceRequest(data);
|
mutations.replaceRequest(data);
|
||||||
mutations.setLoading("files", false);
|
mutations.setLoading("files", false);
|
||||||
}
|
}
|
||||||
|
setTimeout(() => {
|
||||||
|
this.scrollToHash();
|
||||||
|
}, 25);
|
||||||
|
this.lastPath = state.route.path;
|
||||||
},
|
},
|
||||||
keyEvent(event) {
|
keyEvent(event) {
|
||||||
// F1!
|
// F1!
|
||||||
|
|
|
@ -115,7 +115,6 @@ export default {
|
||||||
if (!getters.isLoggedIn()) {
|
if (!getters.isLoggedIn()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
mutations.resetSelected();
|
|
||||||
mutations.setMultiple(false);
|
mutations.setMultiple(false);
|
||||||
if (getters.currentPromptName() !== "success") {
|
if (getters.currentPromptName() !== "success") {
|
||||||
mutations.closeHovers();
|
mutations.closeHovers();
|
||||||
|
@ -155,29 +154,10 @@ main {
|
||||||
}
|
}
|
||||||
|
|
||||||
main.moveWithSidebar {
|
main.moveWithSidebar {
|
||||||
padding-left: 21em;
|
padding-left: 20.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
main::-webkit-scrollbar {
|
main::-webkit-scrollbar {
|
||||||
display: none; /* Safari and Chrome */
|
display: none; /* Safari and Chrome */
|
||||||
}
|
}
|
||||||
/* Use the class .dark-mode to apply styles conditionally */
|
|
||||||
.dark-mode {
|
|
||||||
background: var(--background) !important;
|
|
||||||
color: var(--textPrimary);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Header */
|
|
||||||
.dark-mode-header {
|
|
||||||
color: white;
|
|
||||||
background-color: rgb(255 255 255 / 50%) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Header with backdrop-filter support */
|
|
||||||
@supports (backdrop-filter: none) {
|
|
||||||
.dark-mode-header {
|
|
||||||
background-color: rgb(37 49 55 / 33%) !important;
|
|
||||||
backdrop-filter: blur(16px) invert(0.1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -149,6 +149,8 @@ import QrcodeVue from "qrcode.vue";
|
||||||
import Item from "@/components/files/ListingItem.vue";
|
import Item from "@/components/files/ListingItem.vue";
|
||||||
import Clipboard from "clipboard";
|
import Clipboard from "clipboard";
|
||||||
import { state, getters, mutations } from "@/store";
|
import { state, getters, mutations } from "@/store";
|
||||||
|
import { url } from "@/utils";
|
||||||
|
import { getTypeInfo } from "@/utils/mimetype";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "share",
|
name: "share",
|
||||||
|
@ -209,12 +211,11 @@ export default {
|
||||||
},
|
},
|
||||||
icon() {
|
icon() {
|
||||||
if (state.req.type == "directory") return "folder";
|
if (state.req.type == "directory") return "folder";
|
||||||
if (state.req.type === "image") return "insert_photo";
|
if (getTypeInfo(state.req.type).simpleType == "image") return "insert_photo";
|
||||||
if (state.req.type === "audio") return "volume_up";
|
if (getTypeInfo(state.req.type).simpleType == "audio") return "volume_up";
|
||||||
if (state.req.type === "video") return "movie";
|
if (getTypeInfo(state.req.type).simpleType == "video") return "movie";
|
||||||
return "insert_drive_file";
|
return "insert_drive_file";
|
||||||
},
|
},
|
||||||
|
|
||||||
humanSize() {
|
humanSize() {
|
||||||
if (state.req.type == "directory") {
|
if (state.req.type == "directory") {
|
||||||
return state.req.items.length;
|
return state.req.items.length;
|
||||||
|
@ -229,10 +230,13 @@ export default {
|
||||||
return new Date(Date.parse(state.req.modified)).toLocaleString();
|
return new Date(Date.parse(state.req.modified)).toLocaleString();
|
||||||
},
|
},
|
||||||
isImage() {
|
isImage() {
|
||||||
return state.req.type === "image";
|
return getTypeInfo(state.req.type).simpleType === "image";
|
||||||
},
|
},
|
||||||
isMedia() {
|
isMedia() {
|
||||||
return state.req.type === "video" || state.req.type === "audio";
|
return (
|
||||||
|
getTypeInfo(state.req.type).simpleType === "video" ||
|
||||||
|
getTypeInfo(state.req.type).simpleType === "audio"
|
||||||
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
@ -245,7 +249,7 @@ export default {
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
base64(name) {
|
base64(name) {
|
||||||
return window.btoa(unescape(encodeURIComponent(name)));
|
return url.base64Encode(name);
|
||||||
},
|
},
|
||||||
async fetchData() {
|
async fetchData() {
|
||||||
let urlPath = getters.routePath("share");
|
let urlPath = getters.routePath("share");
|
||||||
|
@ -298,7 +302,7 @@ export default {
|
||||||
download() {
|
download() {
|
||||||
if (getters.isSingleFileSelected()) {
|
if (getters.isSingleFileSelected()) {
|
||||||
const share = {
|
const share = {
|
||||||
path: his.subPath,
|
path: this.subPath,
|
||||||
hash: this.hash,
|
hash: this.hash,
|
||||||
token: this.token,
|
token: this.token,
|
||||||
format: null,
|
format: null,
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import url from "@/utils/url.js";
|
import { url } from "@/utils";
|
||||||
import router from "@/router";
|
import router from "@/router";
|
||||||
import { state, mutations, getters } from "@/store";
|
import { state, mutations, getters } from "@/store";
|
||||||
import { filesApi } from "@/api";
|
import { filesApi } from "@/api";
|
||||||
|
@ -227,7 +227,7 @@ export default {
|
||||||
mutations.closeHovers();
|
mutations.closeHovers();
|
||||||
},
|
},
|
||||||
base64(name) {
|
base64(name) {
|
||||||
return window.btoa(unescape(encodeURIComponent(name)));
|
return url.base64Encode(name);
|
||||||
},
|
},
|
||||||
keyEvent(event) {
|
keyEvent(event) {
|
||||||
// No prompts are shown
|
// No prompts are shown
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue