v0.3.3 release (#257)
This commit is contained in:
parent
266a76459d
commit
d2a3e50d37
|
@ -61,7 +61,7 @@ jobs:
|
|||
JSON="${{ steps.meta.outputs.tags }}"
|
||||
# Use jq to remove 'v' from the version field
|
||||
JSON=$(echo "$JSON" | sed 's/filebrowser:v/filebrowser:/')
|
||||
echo "cleaned_tag=$JSON" >> $GITHUB_OUTPUT
|
||||
echo "CLEANED_TAG=$JSON" >> $GITHUB_OUTPUT
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
|
@ -72,5 +72,5 @@ jobs:
|
|||
platforms: linux/amd64,linux/arm64,linux/arm/v7
|
||||
file: ./Dockerfile
|
||||
push: true
|
||||
tags: ${{ steps.modify-json.outputs.cleaned_tag }}
|
||||
tags: ${{ steps.modify-json.outputs.CLEANED_TAG }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
|
|
|
@ -10,7 +10,7 @@ permissions:
|
|||
|
||||
jobs:
|
||||
push_release_to_registry:
|
||||
name: Push dev release
|
||||
name: Push release
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
|
@ -35,16 +35,16 @@ jobs:
|
|||
JSON="${{ steps.meta.outputs.tags }}"
|
||||
# Use jq to remove 'v' from the version field
|
||||
JSON=$(echo "$JSON" | sed 's/filebrowser:v/filebrowser:/')
|
||||
echo "CLEANED_TAG=$JSON" >> $GITHUB_ENV
|
||||
echo "cleaned_tag=$JSON" >> $GITHUB_OUTPUT
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
build-args: |
|
||||
VERSION=${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.version'] }}
|
||||
REVISION=${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.revision'] }}
|
||||
context: .
|
||||
platforms: linux/amd64
|
||||
file: ./Dockerfile
|
||||
push: true
|
||||
tags: ${{ env.CLEANED_TAG }}
|
||||
tags: ${{ steps.modify-json.outputs.cleaned_tag }}
|
||||
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).
|
||||
|
||||
## 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
|
||||
|
||||
**New Features**
|
||||
|
@ -186,7 +205,7 @@ This change focuses on minimizing and simplifying build process.
|
|||
- The shell feature has been deprecated.
|
||||
- Custom commands can be executed within the Docker container if needed.
|
||||
- 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.
|
||||
- Removed old code for migrating database versions.
|
||||
- Eliminated all unused `cmd` code.
|
||||
|
|
|
@ -20,8 +20,12 @@ RUN npm run build-docker
|
|||
FROM alpine:latest
|
||||
ENV FILEBROWSER_NO_EMBEDED="true"
|
||||
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.
|
||||
EXPOSE 80
|
||||
COPY --from=nbuild /app/dist/ ./http/dist/
|
||||
ENTRYPOINT [ "./filebrowser" ]
|
||||
|
|
|
@ -8,7 +8,7 @@ WORKDIR /app
|
|||
COPY ./frontend/package.json ./
|
||||
RUN npm i --maxsockets 1
|
||||
RUN npx playwright install --with-deps firefox
|
||||
COPY [ "backend/filebrowser.yaml", "./" ]
|
||||
COPY [ "backend/config.yaml", "./" ]
|
||||
COPY ./frontend/ ./frontend
|
||||
WORKDIR /app/frontend
|
||||
RUN npm run build-docker
|
||||
|
|
38
README.md
38
README.md
|
@ -10,7 +10,7 @@
|
|||
</p>
|
||||
|
||||
> [!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]
|
||||
> - 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
|
||||
- Search supports file/folder sizes and many file type filters.
|
||||
- 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
|
||||
styles.
|
||||
- Many graphical and user experience improvements.
|
||||
- right-click context menu
|
||||
1. [x] Revamped and simplified configuration via `filebrowser.yml` config file.
|
||||
1. [x] Better listing browsing
|
||||
3. [x] Revamped and simplified configuration via `filebrowser.yml` config file.
|
||||
4. [x] Better listing browsing
|
||||
- Switching view modes is instant
|
||||
- Folder sizes are shown as well
|
||||
- Changing Sort order is instant
|
||||
- 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.
|
||||
- Helpful Swagger page available at `/swagger` endpoint.
|
||||
|
||||
|
@ -94,7 +94,13 @@ Using docker:
|
|||
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:
|
||||
|
@ -106,11 +112,14 @@ services:
|
|||
filebrowser:
|
||||
volumes:
|
||||
- '/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.
|
||||
- './filebrowser.yaml:/filebrowser.yaml' # required
|
||||
# optional if you want db to persist - configure a path under "database" dir in config file.
|
||||
- './database:/home/filebrowser/database'
|
||||
- './config.yaml:/home/filebrowser/config.yaml'
|
||||
ports:
|
||||
- '80:80'
|
||||
image: gtstef/filebrowser
|
||||
# optionally run as non-root filebrowser user
|
||||
#user: filebrowser
|
||||
restart: always
|
||||
```
|
||||
|
||||
|
@ -121,8 +130,9 @@ services:
|
|||
filebrowser:
|
||||
volumes:
|
||||
- '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.
|
||||
- './filebrowser.yaml:/filebrowser.yaml' # required
|
||||
# optional if you want db to persist - configure a path under "database" dir in config file.
|
||||
- './database:/home/filebrowser/database'
|
||||
- './config.yaml:/home/filebrowser/config.yaml'
|
||||
ports:
|
||||
- '80:80'
|
||||
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:
|
||||
|
||||
```
|
||||
./filebrowser -c <filebrowser.yml or other /path/to/config.yaml>
|
||||
./filebrowser -c <config.yaml or other /path/to/config.yaml>
|
||||
```
|
||||
|
||||
## Command Line Usage
|
||||
|
||||
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`
|
||||
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
|
||||
|
||||
All configuration is now done via a single configuration file:
|
||||
`filebrowser.yaml`, here is an example of minimal [configuration
|
||||
file](./backend/filebrowser.yaml).
|
||||
`config.yaml`, here is an example of minimal [configuration
|
||||
file](./backend/config.yaml).
|
||||
|
||||
View the [Configuration Help Page](./docs/configuration.md) for available
|
||||
configuration options and other help.
|
||||
|
|
|
@ -3,7 +3,7 @@ package auth
|
|||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gtsteffaniak/filebrowser/users"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/users"
|
||||
)
|
||||
|
||||
// Auther is the authentication interface.
|
||||
|
|
|
@ -9,9 +9,9 @@ import (
|
|||
"os/exec"
|
||||
"strings"
|
||||
|
||||
"github.com/gtsteffaniak/filebrowser/errors"
|
||||
"github.com/gtsteffaniak/filebrowser/settings"
|
||||
"github.com/gtsteffaniak/filebrowser/users"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/errors"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/settings"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/users"
|
||||
)
|
||||
|
||||
type hookCred struct {
|
||||
|
|
|
@ -7,8 +7,8 @@ import (
|
|||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/gtsteffaniak/filebrowser/settings"
|
||||
"github.com/gtsteffaniak/filebrowser/users"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/settings"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/users"
|
||||
)
|
||||
|
||||
type jsonCred struct {
|
||||
|
|
|
@ -3,8 +3,8 @@ package auth
|
|||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gtsteffaniak/filebrowser/settings"
|
||||
"github.com/gtsteffaniak/filebrowser/users"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/settings"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/users"
|
||||
)
|
||||
|
||||
// MethodNoAuth is used to identify no auth.
|
||||
|
|
|
@ -4,10 +4,10 @@ import (
|
|||
"net/http"
|
||||
"os"
|
||||
|
||||
"github.com/gtsteffaniak/filebrowser/settings"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/settings"
|
||||
|
||||
"github.com/gtsteffaniak/filebrowser/errors"
|
||||
"github.com/gtsteffaniak/filebrowser/users"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/errors"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/users"
|
||||
)
|
||||
|
||||
// MethodProxyAuth is used to identify no auth.
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
package auth
|
||||
|
||||
import (
|
||||
"github.com/gtsteffaniak/filebrowser/users"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/users"
|
||||
)
|
||||
|
||||
// StorageBackend is a storage backend for auth storage.
|
||||
|
|
|
@ -7,17 +7,17 @@ import (
|
|||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/gtsteffaniak/filebrowser/diskcache"
|
||||
"github.com/gtsteffaniak/filebrowser/files"
|
||||
fbhttp "github.com/gtsteffaniak/filebrowser/http"
|
||||
"github.com/gtsteffaniak/filebrowser/img"
|
||||
"github.com/gtsteffaniak/filebrowser/settings"
|
||||
"github.com/gtsteffaniak/filebrowser/storage"
|
||||
"github.com/gtsteffaniak/filebrowser/swagger/docs"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/diskcache"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/files"
|
||||
fbhttp "github.com/gtsteffaniak/filebrowser/backend/http"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/img"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/settings"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/storage"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/swagger/docs"
|
||||
"github.com/swaggo/swag"
|
||||
|
||||
"github.com/gtsteffaniak/filebrowser/users"
|
||||
"github.com/gtsteffaniak/filebrowser/version"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/users"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/version"
|
||||
)
|
||||
|
||||
func getStore(config string) (*storage.Storage, bool) {
|
||||
|
@ -47,7 +47,7 @@ func StartFilebrowser() {
|
|||
var help bool
|
||||
// Override the default usage output to use 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")
|
||||
|
||||
// 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.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(&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
|
||||
if len(os.Args) > 1 {
|
||||
|
|
|
@ -5,10 +5,10 @@ import (
|
|||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/gtsteffaniak/filebrowser/settings"
|
||||
"github.com/gtsteffaniak/filebrowser/storage"
|
||||
"github.com/gtsteffaniak/filebrowser/users"
|
||||
"github.com/gtsteffaniak/filebrowser/utils"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/settings"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/storage"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/users"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/utils"
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
|
|
@ -6,10 +6,10 @@ import (
|
|||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
|
||||
"github.com/gtsteffaniak/filebrowser/settings"
|
||||
"github.com/gtsteffaniak/filebrowser/storage"
|
||||
"github.com/gtsteffaniak/filebrowser/users"
|
||||
"github.com/gtsteffaniak/filebrowser/utils"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/settings"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/storage"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/users"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/utils"
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
|
|
@ -5,10 +5,10 @@ import (
|
|||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/gtsteffaniak/filebrowser/settings"
|
||||
"github.com/gtsteffaniak/filebrowser/storage"
|
||||
"github.com/gtsteffaniak/filebrowser/users"
|
||||
"github.com/gtsteffaniak/filebrowser/utils"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/settings"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/storage"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/users"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/utils"
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
package cmd
|
||||
|
||||
import (
|
||||
"github.com/gtsteffaniak/filebrowser/storage"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/storage"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ import (
|
|||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/gtsteffaniak/filebrowser/users"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/users"
|
||||
)
|
||||
|
||||
var usersCmd = &cobra.Command{
|
||||
|
|
|
@ -3,9 +3,9 @@ package cmd
|
|||
import (
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/gtsteffaniak/filebrowser/storage"
|
||||
"github.com/gtsteffaniak/filebrowser/users"
|
||||
"github.com/gtsteffaniak/filebrowser/utils"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/storage"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/users"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/utils"
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
package cmd
|
||||
|
||||
import (
|
||||
"github.com/gtsteffaniak/filebrowser/storage"
|
||||
"github.com/gtsteffaniak/filebrowser/utils"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/storage"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/utils"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
|
|
|
@ -3,9 +3,9 @@ package cmd
|
|||
import (
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/gtsteffaniak/filebrowser/storage"
|
||||
"github.com/gtsteffaniak/filebrowser/users"
|
||||
"github.com/gtsteffaniak/filebrowser/utils"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/storage"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/users"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/utils"
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
|
|
@ -8,9 +8,9 @@ import (
|
|||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/gtsteffaniak/filebrowser/storage"
|
||||
"github.com/gtsteffaniak/filebrowser/users"
|
||||
"github.com/gtsteffaniak/filebrowser/utils"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/storage"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/users"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/utils"
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
|
|
@ -3,8 +3,8 @@ package cmd
|
|||
import (
|
||||
"log"
|
||||
|
||||
"github.com/gtsteffaniak/filebrowser/storage"
|
||||
"github.com/gtsteffaniak/filebrowser/utils"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/storage"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/utils"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
|
|
|
@ -3,9 +3,9 @@ package cmd
|
|||
import (
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/gtsteffaniak/filebrowser/storage"
|
||||
"github.com/gtsteffaniak/filebrowser/users"
|
||||
"github.com/gtsteffaniak/filebrowser/utils"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/storage"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/users"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/utils"
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
|
|
@ -10,8 +10,8 @@ import (
|
|||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
|
||||
"github.com/gtsteffaniak/filebrowser/storage"
|
||||
"github.com/gtsteffaniak/filebrowser/utils"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/storage"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/utils"
|
||||
)
|
||||
|
||||
func mustGetString(flags *pflag.FlagSet, flag string) string {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
server:
|
||||
port: 80
|
||||
baseURL: "/"
|
||||
root: "./srv"
|
||||
root: "/srv"
|
||||
auth:
|
||||
method: password
|
||||
signup: false
|
Binary file not shown.
|
@ -24,11 +24,24 @@ var documentTypes = []string{
|
|||
".pdf", // Portable Document Format
|
||||
".odt", // OpenDocument Text
|
||||
".rtf", // Rich Text Format
|
||||
".conf",
|
||||
".bash_history",
|
||||
".gitignore",
|
||||
".htpasswd",
|
||||
".profile",
|
||||
".dockerignore",
|
||||
".editorconfig",
|
||||
|
||||
// Presentation Formats
|
||||
".ppt", ".pptx", // Microsoft PowerPoint
|
||||
".odp", // OpenDocument Presentation
|
||||
|
||||
// google docs
|
||||
".gdoc",
|
||||
|
||||
// google sheet
|
||||
".gsheet",
|
||||
|
||||
// Spreadsheet Formats
|
||||
".xls", ".xlsx", // Microsoft Excel
|
||||
".ods", // OpenDocument Spreadsheet
|
||||
|
@ -102,6 +115,16 @@ type SearchOptions struct {
|
|||
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 {
|
||||
opts := SearchOptions{
|
||||
Conditions: map[string]bool{
|
||||
|
@ -199,8 +222,6 @@ func IsMatchingType(extension string, matchType string) bool {
|
|||
switch matchType {
|
||||
case "doc":
|
||||
return isDoc(extension)
|
||||
case "pdf":
|
||||
return extension == ".pdf"
|
||||
case "text":
|
||||
return isText(extension)
|
||||
case "archive":
|
||||
|
|
|
@ -22,7 +22,7 @@ func TestIsMatchingType(t *testing.T) {
|
|||
extension string
|
||||
expectedType string
|
||||
}{
|
||||
{".pdf", "pdf"},
|
||||
{".pdf", "doc"},
|
||||
{".doc", "doc"},
|
||||
{".docx", "doc"},
|
||||
{".json", "text"},
|
||||
|
|
|
@ -11,6 +11,7 @@ import (
|
|||
"io"
|
||||
"mime"
|
||||
"net/http"
|
||||
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
|
@ -20,11 +21,11 @@ import (
|
|||
"time"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/gtsteffaniak/filebrowser/errors"
|
||||
"github.com/gtsteffaniak/filebrowser/fileutils"
|
||||
"github.com/gtsteffaniak/filebrowser/settings"
|
||||
"github.com/gtsteffaniak/filebrowser/users"
|
||||
"github.com/gtsteffaniak/filebrowser/utils"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/errors"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/fileutils"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/settings"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/users"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/utils"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -201,7 +202,8 @@ func DeleteFiles(absPath string, opts FileOptions) error {
|
|||
return err
|
||||
}
|
||||
index := GetIndex(rootPath)
|
||||
err = index.RefreshFileInfo(opts)
|
||||
refreshConfig := FileOptions{Path: filepath.Dir(opts.Path), IsDir: true}
|
||||
err = index.RefreshFileInfo(refreshConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -341,74 +343,29 @@ func getContent(path string) (string, error) {
|
|||
return stringContent, nil
|
||||
}
|
||||
|
||||
// detectType detects the file type.
|
||||
func (i *ItemInfo) detectType(path string, modify, saveContent, readHeader bool) error {
|
||||
// DetectType detects the MIME type of a file and updates the ItemInfo struct.
|
||||
func (i *ItemInfo) DetectType(path string, saveContent bool) {
|
||||
name := i.Name
|
||||
var contentErr error
|
||||
|
||||
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 {
|
||||
if IsMatchingType(ext, fileType) {
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
// Attempt MIME detection by file extension
|
||||
i.Type = strings.Split(mime.TypeByExtension(ext), ";")[0]
|
||||
if i.Type == "" {
|
||||
i.Type = "blob"
|
||||
if saveContent {
|
||||
return contentErr
|
||||
i.Type = extendedMimeTypeCheck(ext)
|
||||
}
|
||||
if i.Type == "blob" {
|
||||
realpath, _, _ := GetRealPath(path)
|
||||
// Read only the first 512 bytes for efficient MIME detection
|
||||
file, err := os.Open(realpath)
|
||||
if err != nil {
|
||||
|
||||
} else {
|
||||
defer file.Close()
|
||||
buffer := make([]byte, 512)
|
||||
n, _ := file.Read(buffer) // Ignore errors from Read
|
||||
i.Type = strings.Split(http.DetectContentType(buffer[:n]), ";")[0]
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// readFirstBytes reads the first bytes of the file.
|
||||
func (i *ItemInfo) readFirstBytes(path string) []byte {
|
||||
file, err := os.Open(path)
|
||||
if err != nil {
|
||||
i.Type = "blob"
|
||||
return nil
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
buffer := make([]byte, 512) //nolint:gomnd
|
||||
n, err := file.Read(buffer)
|
||||
if err != nil && err != io.EOF {
|
||||
i.Type = "blob"
|
||||
return nil
|
||||
}
|
||||
|
||||
return buffer[:n]
|
||||
}
|
||||
|
||||
// TODO add subtitles back
|
||||
|
|
|
@ -9,8 +9,8 @@ import (
|
|||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/gtsteffaniak/filebrowser/settings"
|
||||
"github.com/gtsteffaniak/filebrowser/utils"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/settings"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/utils"
|
||||
)
|
||||
|
||||
type Index struct {
|
||||
|
@ -132,7 +132,7 @@ func (si *Index) indexDirectory(adjustedPath string, quick, recursive bool) erro
|
|||
dirInfos = append(dirInfos, *itemInfo)
|
||||
si.NumDirs++
|
||||
} else {
|
||||
_ = itemInfo.detectType(combinedPath+file.Name(), true, false, false)
|
||||
itemInfo.DetectType(combinedPath+file.Name(), false)
|
||||
itemInfo.Size = file.Size()
|
||||
fileInfos = append(fileInfos, *itemInfo)
|
||||
totalSize += itemInfo.Size
|
||||
|
|
|
@ -4,7 +4,7 @@ import (
|
|||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/gtsteffaniak/filebrowser/settings"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/settings"
|
||||
)
|
||||
|
||||
// schedule in minutes
|
||||
|
|
|
@ -7,7 +7,7 @@ import (
|
|||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/gtsteffaniak/filebrowser/settings"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/settings"
|
||||
)
|
||||
|
||||
func BenchmarkFillIndex(b *testing.B) {
|
||||
|
|
|
@ -6,7 +6,7 @@ import (
|
|||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/gtsteffaniak/filebrowser/utils"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/utils"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -14,19 +14,19 @@ var (
|
|||
maxSearchResults = 100
|
||||
)
|
||||
|
||||
type searchResult struct {
|
||||
type SearchResult struct {
|
||||
Path string `json:"path"`
|
||||
Type string `json:"type"`
|
||||
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
|
||||
scope = si.makeIndexPath(scope)
|
||||
runningHash := utils.GenerateRandomHash(4)
|
||||
sessionInProgress.Store(sourceSession, runningHash) // Store the value in the sync.Map
|
||||
searchOptions := ParseSearch(search)
|
||||
results := make(map[string]searchResult, 0)
|
||||
results := make(map[string]SearchResult, 0)
|
||||
count := 0
|
||||
var directories []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)
|
||||
if matches {
|
||||
results[scopedPath] = searchResult{Path: scopedPath, Type: "directory", Size: dir.Size}
|
||||
results[scopedPath] = SearchResult{Path: scopedPath, Type: "directory", Size: dir.Size}
|
||||
count++
|
||||
}
|
||||
// search files first
|
||||
|
@ -75,14 +75,14 @@ func (si *Index) Search(search string, scope string, sourceSession string) []sea
|
|||
value, found := sessionInProgress.Load(sourceSession)
|
||||
if !found || value != runningHash {
|
||||
si.mu.Unlock()
|
||||
return []searchResult{}
|
||||
return []SearchResult{}
|
||||
}
|
||||
if count > maxSearchResults {
|
||||
break
|
||||
}
|
||||
matches := item.containsSearchTerm(searchTerm, searchOptions)
|
||||
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++
|
||||
}
|
||||
}
|
||||
|
@ -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 "/"
|
||||
sortedKeys := make([]searchResult, 0, len(results))
|
||||
sortedKeys := make([]SearchResult, 0, len(results))
|
||||
for _, v := range results {
|
||||
sortedKeys = append(sortedKeys, v)
|
||||
}
|
||||
|
|
|
@ -119,12 +119,12 @@ func TestSearchIndexes(t *testing.T) {
|
|||
tests := []struct {
|
||||
search string
|
||||
scope string
|
||||
expectedResult []searchResult
|
||||
expectedResult []SearchResult
|
||||
}{
|
||||
{
|
||||
search: "audio",
|
||||
scope: "/new/",
|
||||
expectedResult: []searchResult{
|
||||
expectedResult: []SearchResult{
|
||||
{
|
||||
Path: "test/audio.wav",
|
||||
Type: "audio",
|
||||
|
@ -135,7 +135,7 @@ func TestSearchIndexes(t *testing.T) {
|
|||
{
|
||||
search: "test",
|
||||
scope: "/",
|
||||
expectedResult: []searchResult{
|
||||
expectedResult: []SearchResult{
|
||||
{
|
||||
Path: "test/",
|
||||
Type: "directory",
|
||||
|
@ -151,7 +151,7 @@ func TestSearchIndexes(t *testing.T) {
|
|||
{
|
||||
search: "archive",
|
||||
scope: "/",
|
||||
expectedResult: []searchResult{
|
||||
expectedResult: []SearchResult{
|
||||
{
|
||||
Path: "firstDir/archive.zip",
|
||||
Type: "archive",
|
||||
|
@ -167,7 +167,7 @@ func TestSearchIndexes(t *testing.T) {
|
|||
{
|
||||
search: "arch",
|
||||
scope: "/firstDir",
|
||||
expectedResult: []searchResult{
|
||||
expectedResult: []SearchResult{
|
||||
{
|
||||
Path: "archive.zip",
|
||||
Type: "archive",
|
||||
|
@ -178,7 +178,7 @@ func TestSearchIndexes(t *testing.T) {
|
|||
{
|
||||
search: "isdir",
|
||||
scope: "/",
|
||||
expectedResult: []searchResult{
|
||||
expectedResult: []SearchResult{
|
||||
{
|
||||
Path: "firstDir/thisIsDir/",
|
||||
Type: "directory",
|
||||
|
@ -189,7 +189,7 @@ func TestSearchIndexes(t *testing.T) {
|
|||
{
|
||||
search: "IsDir type:largerThan=1",
|
||||
scope: "/",
|
||||
expectedResult: []searchResult{
|
||||
expectedResult: []SearchResult{
|
||||
{
|
||||
Path: "firstDir/thisIsDir/",
|
||||
Type: "directory",
|
||||
|
@ -200,7 +200,7 @@ func TestSearchIndexes(t *testing.T) {
|
|||
{
|
||||
search: "video",
|
||||
scope: "/",
|
||||
expectedResult: []searchResult{
|
||||
expectedResult: []SearchResult{
|
||||
{
|
||||
Path: "new/test/video.MP4",
|
||||
Type: "video",
|
||||
|
|
|
@ -3,7 +3,7 @@ package files
|
|||
import (
|
||||
"path/filepath"
|
||||
|
||||
"github.com/gtsteffaniak/filebrowser/settings"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/settings"
|
||||
)
|
||||
|
||||
// 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
|
||||
|
||||
|
@ -7,7 +7,8 @@ require (
|
|||
github.com/disintegration/imaging v1.6.2
|
||||
github.com/dsoprea/go-exif/v3 v3.0.1
|
||||
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/google/go-cmp v0.6.0
|
||||
github.com/shirou/gopsutil/v3 v3.24.5
|
||||
|
@ -17,9 +18,9 @@ require (
|
|||
github.com/stretchr/testify v1.9.0
|
||||
github.com/swaggo/http-swagger v1.3.4
|
||||
github.com/swaggo/swag v1.16.4
|
||||
golang.org/x/crypto v0.29.0
|
||||
golang.org/x/image v0.22.0
|
||||
golang.org/x/text v0.20.0
|
||||
golang.org/x/crypto v0.30.0
|
||||
golang.org/x/image v0.23.0
|
||||
golang.org/x/text v0.21.0
|
||||
)
|
||||
|
||||
require (
|
||||
|
@ -43,9 +44,9 @@ require (
|
|||
github.com/swaggo/files v1.0.1 // indirect
|
||||
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
||||
go.etcd.io/bbolt v1.3.11 // indirect
|
||||
golang.org/x/net v0.31.0 // indirect
|
||||
golang.org/x/sys v0.27.0 // indirect
|
||||
golang.org/x/tools v0.27.0 // indirect
|
||||
golang.org/x/net v0.32.0 // indirect
|
||||
golang.org/x/sys v0.28.0 // indirect
|
||||
golang.org/x/tools v0.28.0 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // 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/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/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.2/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/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=
|
||||
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.6/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
|
||||
github.com/goccy/go-yaml v1.15.7 h1:L7XuKpd/A66X4w/dlk08lVfiIADdy79a1AzRoIefC98=
|
||||
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/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
||||
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=
|
||||
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.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ=
|
||||
golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg=
|
||||
golang.org/x/crypto v0.30.0 h1:RwoQn3GkWiMkzlX562cLB7OxWvjH1L8xutO2WoJcRoY=
|
||||
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.22.0 h1:UtK5yLUzilVrkjMAZAZ34DXGpASN8i8pj8g+O+yd10g=
|
||||
golang.org/x/image v0.22.0/go.mod h1:9hPFhljd4zZ1GNSIZJ49sqbp45GKK9t6w+iXvGqZUz4=
|
||||
golang.org/x/image v0.23.0 h1:HseQ7c2OpPKTPVzNjG5fwJsOTCiiwS4QdsYi5XU6H68=
|
||||
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.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4=
|
||||
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-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.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo=
|
||||
golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM=
|
||||
golang.org/x/net v0.32.0 h1:ZqPmj8Kzc+Y6e0+skZsuACbx+wzMgo5MQsJh9Qd6aYI=
|
||||
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-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ=
|
||||
golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
|
||||
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-20190916202348-b4ddaad3f8a3/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.1.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.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
|
||||
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-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
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.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.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug=
|
||||
golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4=
|
||||
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
||||
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-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.27.0 h1:qEKojBykQkQ4EynWy4S8Weg69NumxKdn40Fce3uc/8o=
|
||||
golang.org/x/tools v0.27.0/go.mod h1:sUi0ZgbwW9ZPAq26Ekut+weQPR5eIM6GQLQ1Yjm1H0Q=
|
||||
golang.org/x/tools v0.28.0 h1:WuB6qZ4RPCQo5aP3WdKZS7i595EdWqWR8vqJTlwTVK8=
|
||||
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=
|
||||
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
|
||||
|
|
|
@ -7,7 +7,7 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gtsteffaniak/filebrowser/users"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/users"
|
||||
)
|
||||
|
||||
func createApiKeyHandler(w http.ResponseWriter, r *http.Request, d *requestContext) (int, error) {
|
||||
|
|
|
@ -16,11 +16,11 @@ import (
|
|||
"github.com/golang-jwt/jwt/v4/request"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
|
||||
"github.com/gtsteffaniak/filebrowser/errors"
|
||||
"github.com/gtsteffaniak/filebrowser/settings"
|
||||
"github.com/gtsteffaniak/filebrowser/share"
|
||||
"github.com/gtsteffaniak/filebrowser/users"
|
||||
"github.com/gtsteffaniak/filebrowser/utils"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/errors"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/settings"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/share"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/users"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/utils"
|
||||
)
|
||||
|
||||
var (
|
||||
|
|
|
@ -11,10 +11,10 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/golang-jwt/jwt/v4"
|
||||
"github.com/gtsteffaniak/filebrowser/files"
|
||||
"github.com/gtsteffaniak/filebrowser/runner"
|
||||
"github.com/gtsteffaniak/filebrowser/settings"
|
||||
"github.com/gtsteffaniak/filebrowser/users"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/files"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/runner"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/settings"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/users"
|
||||
)
|
||||
|
||||
type requestContext struct {
|
||||
|
|
|
@ -8,15 +8,15 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/asdine/storm/v3"
|
||||
"github.com/gtsteffaniak/filebrowser/diskcache"
|
||||
"github.com/gtsteffaniak/filebrowser/files"
|
||||
"github.com/gtsteffaniak/filebrowser/img"
|
||||
"github.com/gtsteffaniak/filebrowser/settings"
|
||||
"github.com/gtsteffaniak/filebrowser/share"
|
||||
"github.com/gtsteffaniak/filebrowser/storage"
|
||||
"github.com/gtsteffaniak/filebrowser/storage/bolt"
|
||||
"github.com/gtsteffaniak/filebrowser/users"
|
||||
"github.com/gtsteffaniak/filebrowser/utils"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/diskcache"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/files"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/img"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/settings"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/share"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/storage"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/storage/bolt"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/users"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/utils"
|
||||
)
|
||||
|
||||
func setupTestEnv(t *testing.T) {
|
||||
|
|
|
@ -8,9 +8,10 @@ import (
|
|||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/gtsteffaniak/filebrowser/files"
|
||||
"github.com/gtsteffaniak/filebrowser/img"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/files"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/img"
|
||||
)
|
||||
|
||||
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")
|
||||
}
|
||||
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)
|
||||
}
|
||||
|
||||
|
|
|
@ -6,11 +6,11 @@ import (
|
|||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/gtsteffaniak/filebrowser/files"
|
||||
"github.com/gtsteffaniak/filebrowser/settings"
|
||||
"github.com/gtsteffaniak/filebrowser/users"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/files"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/settings"
|
||||
"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) {
|
||||
|
|
|
@ -5,6 +5,7 @@ import (
|
|||
"archive/zip"
|
||||
"compress/gzip"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
|
@ -13,7 +14,7 @@ import (
|
|||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/gtsteffaniak/filebrowser/files"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/files"
|
||||
)
|
||||
|
||||
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 {
|
||||
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, ","))
|
||||
}
|
||||
|
||||
|
|
|
@ -13,9 +13,9 @@ import (
|
|||
|
||||
"github.com/shirou/gopsutil/v3/disk"
|
||||
|
||||
"github.com/gtsteffaniak/filebrowser/errors"
|
||||
"github.com/gtsteffaniak/filebrowser/files"
|
||||
"github.com/gtsteffaniak/filebrowser/utils"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/errors"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/files"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/utils"
|
||||
)
|
||||
|
||||
// resourceGetHandler retrieves information about a resource.
|
||||
|
@ -33,9 +33,13 @@ import (
|
|||
// @Failure 500 {object} map[string]string "Internal server error"
|
||||
// @Router /api/resources [get]
|
||||
func resourceGetHandler(w http.ResponseWriter, r *http.Request, d *requestContext) (int, error) {
|
||||
|
||||
// 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{
|
||||
Path: filepath.Join(d.user.Scope, path),
|
||||
Modify: d.user.Perm.Modify,
|
||||
|
@ -78,7 +82,12 @@ func resourceGetHandler(w http.ResponseWriter, r *http.Request, d *requestContex
|
|||
// @Router /api/resources [delete]
|
||||
func resourceDeleteHandler(w http.ResponseWriter, r *http.Request, d *requestContext) (int, error) {
|
||||
// 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 {
|
||||
return http.StatusForbidden, nil
|
||||
}
|
||||
|
@ -87,7 +96,7 @@ func resourceDeleteHandler(w http.ResponseWriter, r *http.Request, d *requestCon
|
|||
return http.StatusNotFound, err
|
||||
}
|
||||
fileOpts := files.FileOptions{
|
||||
Path: filepath.Join(d.user.Scope, path),
|
||||
Path: realPath,
|
||||
IsDir: isDir,
|
||||
Modify: d.user.Perm.Modify,
|
||||
Expand: false,
|
||||
|
@ -130,7 +139,12 @@ func resourceDeleteHandler(w http.ResponseWriter, r *http.Request, d *requestCon
|
|||
// @Router /api/resources [post]
|
||||
func resourcePostHandler(w http.ResponseWriter, r *http.Request, d *requestContext) (int, error) {
|
||||
// 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) {
|
||||
return http.StatusForbidden, nil
|
||||
}
|
||||
|
@ -142,7 +156,7 @@ func resourcePostHandler(w http.ResponseWriter, r *http.Request, d *requestConte
|
|||
}
|
||||
// Directories creation on POST.
|
||||
if strings.HasSuffix(path, "/") {
|
||||
err := files.WriteDirectory(fileOpts)
|
||||
err = files.WriteDirectory(fileOpts)
|
||||
if err != nil {
|
||||
return errToStatus(err), err
|
||||
}
|
||||
|
@ -188,7 +202,13 @@ func resourcePostHandler(w http.ResponseWriter, r *http.Request, d *requestConte
|
|||
// @Router /api/resources [put]
|
||||
func resourcePutHandler(w http.ResponseWriter, r *http.Request, d *requestContext) (int, error) {
|
||||
// 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) {
|
||||
return http.StatusForbidden, nil
|
||||
}
|
||||
|
@ -233,16 +253,21 @@ func resourcePutHandler(w http.ResponseWriter, r *http.Request, d *requestContex
|
|||
// @Router /api/resources [patch]
|
||||
func resourcePatchHandler(w http.ResponseWriter, r *http.Request, d *requestContext) (int, error) {
|
||||
// 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")
|
||||
dst, err := url.QueryUnescape(dst)
|
||||
if !d.user.Check(src) || !d.user.Check(dst) {
|
||||
return http.StatusForbidden, nil
|
||||
encodedFrom := r.URL.Query().Get("from")
|
||||
// Decode the URL-encoded path
|
||||
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 {
|
||||
return errToStatus(err), err
|
||||
}
|
||||
if !d.user.Check(src) || !d.user.Check(dst) {
|
||||
return http.StatusForbidden, nil
|
||||
}
|
||||
if dst == "/" || src == "/" {
|
||||
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 {
|
||||
switch action {
|
||||
// TODO: use enum
|
||||
case "copy":
|
||||
if !d.user.Perm.Create {
|
||||
return errors.ErrPermissionDenied
|
||||
}
|
||||
return files.CopyResource(src, dst, isSrcDir)
|
||||
err := files.CopyResource(src, dst, isSrcDir)
|
||||
return err
|
||||
case "rename", "move":
|
||||
|
||||
if !d.user.Perm.Rename {
|
||||
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) {
|
||||
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"
|
||||
index := files.GetIndex(config.Server.Root)
|
||||
info, _ := index.GetReducedMetadata(path, isDir)
|
||||
|
|
|
@ -10,9 +10,9 @@ import (
|
|||
"os"
|
||||
"text/template"
|
||||
|
||||
"github.com/gtsteffaniak/filebrowser/settings"
|
||||
"github.com/gtsteffaniak/filebrowser/storage"
|
||||
"github.com/gtsteffaniak/filebrowser/version"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/settings"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/storage"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/version"
|
||||
|
||||
httpSwagger "github.com/swaggo/http-swagger" // http-swagger middleware
|
||||
)
|
||||
|
|
|
@ -4,8 +4,8 @@ import (
|
|||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/gtsteffaniak/filebrowser/files"
|
||||
"github.com/gtsteffaniak/filebrowser/settings"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/files"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/settings"
|
||||
)
|
||||
|
||||
// searchHandler handles search requests for files based on the provided query.
|
||||
|
@ -49,7 +49,7 @@ import (
|
|||
// @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 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"
|
||||
// @Router /api/search [get]
|
||||
func searchHandler(w http.ResponseWriter, r *http.Request, d *requestContext) (int, error) {
|
||||
|
|
|
@ -4,8 +4,8 @@ import (
|
|||
"encoding/json"
|
||||
"net/http"
|
||||
|
||||
"github.com/gtsteffaniak/filebrowser/settings"
|
||||
"github.com/gtsteffaniak/filebrowser/users"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/settings"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/users"
|
||||
)
|
||||
|
||||
type settingsData struct {
|
||||
|
|
|
@ -12,8 +12,8 @@ import (
|
|||
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
|
||||
"github.com/gtsteffaniak/filebrowser/errors"
|
||||
"github.com/gtsteffaniak/filebrowser/share"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/errors"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/share"
|
||||
)
|
||||
|
||||
// shareListHandler returns a list of all share links.
|
||||
|
|
|
@ -12,9 +12,9 @@ import (
|
|||
"strings"
|
||||
"text/template"
|
||||
|
||||
"github.com/gtsteffaniak/filebrowser/auth"
|
||||
"github.com/gtsteffaniak/filebrowser/settings"
|
||||
"github.com/gtsteffaniak/filebrowser/version"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/auth"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/settings"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/version"
|
||||
)
|
||||
|
||||
var templateRenderer *TemplateRenderer
|
||||
|
|
|
@ -12,10 +12,10 @@ import (
|
|||
"golang.org/x/text/cases"
|
||||
"golang.org/x/text/language"
|
||||
|
||||
"github.com/gtsteffaniak/filebrowser/errors"
|
||||
"github.com/gtsteffaniak/filebrowser/files"
|
||||
"github.com/gtsteffaniak/filebrowser/storage"
|
||||
"github.com/gtsteffaniak/filebrowser/users"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/errors"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/files"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/storage"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/users"
|
||||
)
|
||||
|
||||
var (
|
||||
|
|
|
@ -5,7 +5,7 @@ import (
|
|||
"net/http"
|
||||
"os"
|
||||
|
||||
libErrors "github.com/gtsteffaniak/filebrowser/errors"
|
||||
libErrors "github.com/gtsteffaniak/filebrowser/backend/errors"
|
||||
)
|
||||
|
||||
func errToStatus(err error) int {
|
||||
|
|
|
@ -261,3 +261,22 @@ func getEmbeddedThumbnail(in io.Reader) ([]byte, io.Reader, error) {
|
|||
thm, err := ifd.Thumbnail()
|
||||
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
|
||||
|
||||
import (
|
||||
"github.com/gtsteffaniak/filebrowser/cmd"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/cmd"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
|
|
@ -3,7 +3,7 @@ package runner
|
|||
import (
|
||||
"os/exec"
|
||||
|
||||
"github.com/gtsteffaniak/filebrowser/settings"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/settings"
|
||||
)
|
||||
|
||||
// ParseCommand parses the command taking in account if the current
|
||||
|
|
|
@ -7,9 +7,9 @@ import (
|
|||
"os/exec"
|
||||
"strings"
|
||||
|
||||
"github.com/gtsteffaniak/filebrowser/files"
|
||||
"github.com/gtsteffaniak/filebrowser/settings"
|
||||
"github.com/gtsteffaniak/filebrowser/users"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/files"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/settings"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/users"
|
||||
)
|
||||
|
||||
// Runner is a commands runner.
|
||||
|
|
|
@ -7,7 +7,7 @@ import (
|
|||
"strings"
|
||||
|
||||
"github.com/goccy/go-yaml"
|
||||
"github.com/gtsteffaniak/filebrowser/users"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/users"
|
||||
)
|
||||
|
||||
var Config Settings
|
||||
|
|
|
@ -3,7 +3,7 @@ package settings
|
|||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/gtsteffaniak/filebrowser/users"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/users"
|
||||
)
|
||||
|
||||
func TestSettings_MakeUserDir(t *testing.T) {
|
||||
|
|
|
@ -3,7 +3,7 @@ package settings
|
|||
import (
|
||||
"crypto/rand"
|
||||
|
||||
"github.com/gtsteffaniak/filebrowser/users"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/users"
|
||||
)
|
||||
|
||||
const DefaultUsersHomeBasePath = "/users"
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
package settings
|
||||
|
||||
import (
|
||||
"github.com/gtsteffaniak/filebrowser/errors"
|
||||
"github.com/gtsteffaniak/filebrowser/users"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/errors"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/users"
|
||||
)
|
||||
|
||||
// StorageBackend is a settings storage backend.
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
package settings
|
||||
|
||||
import (
|
||||
"github.com/gtsteffaniak/filebrowser/users"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/users"
|
||||
)
|
||||
|
||||
type Settings struct {
|
||||
|
|
|
@ -3,7 +3,7 @@ package share
|
|||
import (
|
||||
"time"
|
||||
|
||||
"github.com/gtsteffaniak/filebrowser/errors"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/errors"
|
||||
)
|
||||
|
||||
// StorageBackend is the interface to implement for a share storage.
|
||||
|
|
|
@ -2,8 +2,8 @@ package bolt
|
|||
|
||||
import (
|
||||
"github.com/asdine/storm/v3"
|
||||
"github.com/gtsteffaniak/filebrowser/auth"
|
||||
"github.com/gtsteffaniak/filebrowser/errors"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/auth"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/errors"
|
||||
)
|
||||
|
||||
type authBackend struct {
|
||||
|
|
|
@ -3,10 +3,10 @@ package bolt
|
|||
import (
|
||||
"github.com/asdine/storm/v3"
|
||||
|
||||
"github.com/gtsteffaniak/filebrowser/auth"
|
||||
"github.com/gtsteffaniak/filebrowser/settings"
|
||||
"github.com/gtsteffaniak/filebrowser/share"
|
||||
"github.com/gtsteffaniak/filebrowser/users"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/auth"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/settings"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/share"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/users"
|
||||
)
|
||||
|
||||
// NewStorage creates a storage.Storage based on Bolt DB.
|
||||
|
|
|
@ -2,7 +2,7 @@ package bolt
|
|||
|
||||
import (
|
||||
"github.com/asdine/storm/v3"
|
||||
"github.com/gtsteffaniak/filebrowser/settings"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/settings"
|
||||
)
|
||||
|
||||
type settingsBackend struct {
|
||||
|
|
|
@ -4,8 +4,8 @@ import (
|
|||
"github.com/asdine/storm/v3"
|
||||
"github.com/asdine/storm/v3/q"
|
||||
|
||||
"github.com/gtsteffaniak/filebrowser/errors"
|
||||
"github.com/gtsteffaniak/filebrowser/share"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/errors"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/share"
|
||||
)
|
||||
|
||||
type shareBackend struct {
|
||||
|
|
|
@ -6,9 +6,9 @@ import (
|
|||
|
||||
"github.com/asdine/storm/v3"
|
||||
|
||||
"github.com/gtsteffaniak/filebrowser/errors"
|
||||
"github.com/gtsteffaniak/filebrowser/users"
|
||||
"github.com/gtsteffaniak/filebrowser/utils"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/errors"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/users"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/utils"
|
||||
)
|
||||
|
||||
type usersBackend struct {
|
||||
|
|
|
@ -3,7 +3,7 @@ package bolt
|
|||
import (
|
||||
"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 {
|
||||
|
|
|
@ -7,14 +7,14 @@ import (
|
|||
"path/filepath"
|
||||
|
||||
"github.com/asdine/storm/v3"
|
||||
"github.com/gtsteffaniak/filebrowser/auth"
|
||||
"github.com/gtsteffaniak/filebrowser/errors"
|
||||
"github.com/gtsteffaniak/filebrowser/files"
|
||||
"github.com/gtsteffaniak/filebrowser/settings"
|
||||
"github.com/gtsteffaniak/filebrowser/share"
|
||||
"github.com/gtsteffaniak/filebrowser/storage/bolt"
|
||||
"github.com/gtsteffaniak/filebrowser/users"
|
||||
"github.com/gtsteffaniak/filebrowser/utils"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/auth"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/errors"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/files"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/settings"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/share"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/storage/bolt"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/users"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/utils"
|
||||
)
|
||||
|
||||
// Storage is a storage powered by a Backend which makes the necessary
|
||||
|
|
|
@ -579,7 +579,7 @@ const docTemplate = `{
|
|||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/files.searchResult"
|
||||
"$ref": "#/definitions/files.SearchResult"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -1186,7 +1186,7 @@ const docTemplate = `{
|
|||
}
|
||||
}
|
||||
},
|
||||
"files.searchResult": {
|
||||
"files.SearchResult": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"path": {
|
||||
|
|
|
@ -568,7 +568,7 @@
|
|||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/files.searchResult"
|
||||
"$ref": "#/definitions/files.SearchResult"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -1175,7 +1175,7 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"files.searchResult": {
|
||||
"files.SearchResult": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"path": {
|
||||
|
|
|
@ -31,7 +31,7 @@ definitions:
|
|||
type:
|
||||
type: string
|
||||
type: object
|
||||
files.searchResult:
|
||||
files.SearchResult:
|
||||
properties:
|
||||
path:
|
||||
type: string
|
||||
|
@ -658,7 +658,7 @@ paths:
|
|||
description: List of search results
|
||||
schema:
|
||||
items:
|
||||
$ref: '#/definitions/files.searchResult'
|
||||
$ref: '#/definitions/files.SearchResult'
|
||||
type: array
|
||||
"400":
|
||||
description: Bad Request
|
||||
|
|
|
@ -4,7 +4,7 @@ import (
|
|||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/gtsteffaniak/filebrowser/errors"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/errors"
|
||||
)
|
||||
|
||||
// StorageBackend is the interface to implement for a users storage.
|
||||
|
|
|
@ -1,18 +1,17 @@
|
|||
# Planned Roadmap
|
||||
|
||||
upcoming 0.3.x releases, ordered by priority:
|
||||
|
||||
- More filetype icons and refreshed icons.
|
||||
- more filetype previews - eg. office, photoshop, vector, 3d files.
|
||||
- Enable mobile search with same features as desktop
|
||||
- Enable mobile search with same features as desktop
|
||||
- Theme configuration from settings
|
||||
- introduce jobs as replacement to runners.
|
||||
- Add Job status to the sidebar
|
||||
- index status.
|
||||
- Job status from users
|
||||
- upload status
|
||||
- opentelemetry metrics
|
||||
- more indexing flexability
|
||||
- option not to index hidden files/folders
|
||||
- options folders to include/exclude from indexing
|
||||
- implement more indexing runners for more efficienct filesystem watching
|
||||
- more filetype previews: eg. raw img, office, photoshop, vector, 3d files.
|
||||
- introduce jobs as replacement to runners.
|
||||
- Add Job status to the sidebar
|
||||
- index status.
|
||||
- Job status from users
|
||||
- upload status
|
||||
- opentelemetry metrics
|
||||
|
||||
Unplanned Future releases:
|
||||
- multiple sources https://github.com/filebrowser/filebrowser/issues/2514
|
||||
|
|
|
@ -25,6 +25,7 @@
|
|||
"css-vars-ponyfill": "^2.4.3",
|
||||
"file-loader": "^6.2.0",
|
||||
"material-icons": "^1.10.5",
|
||||
"material-symbols": "^0.27.2",
|
||||
"normalize.css": "^8.0.1",
|
||||
"qrcode.vue": "^3.4.1",
|
||||
"vue": "^3.4.21",
|
||||
|
|
|
@ -6,7 +6,7 @@ import { notify } from "@/notify";
|
|||
// Notify if errors occur
|
||||
export async function fetchFiles(url, content = false) {
|
||||
try {
|
||||
let path = removePrefix(url, "files");
|
||||
let path = encodeURIComponent(removePrefix(url, "files"));
|
||||
const apiPath = getApiPath("api/resources",{path: path, content: content});
|
||||
const res = await fetchURL(apiPath);
|
||||
const data = await res.json();
|
||||
|
@ -24,7 +24,8 @@ async function resourceAction(url, method, content) {
|
|||
if (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);
|
||||
return res;
|
||||
} catch (err) {
|
||||
|
@ -35,7 +36,8 @@ async function resourceAction(url, method, content) {
|
|||
|
||||
export async function remove(url) {
|
||||
try {
|
||||
return await resourceAction(url, "DELETE");
|
||||
let path = encodeURIComponent(removePrefix(url, "files"));
|
||||
return await resourceAction(path, "DELETE");
|
||||
} catch (err) {
|
||||
notify.showError(err.message || "Error deleting resource");
|
||||
throw err;
|
||||
|
@ -44,7 +46,8 @@ export async function remove(url) {
|
|||
|
||||
export async function put(url, content = "") {
|
||||
try {
|
||||
return await resourceAction(url, "PUT", content);
|
||||
let path = encodeURIComponent(removePrefix(url, "files"));
|
||||
return await resourceAction(path, "PUT", content);
|
||||
} catch (err) {
|
||||
notify.showError(err.message || "Error putting resource");
|
||||
throw err;
|
||||
|
@ -58,14 +61,14 @@ export function download(format, files) {
|
|||
try {
|
||||
let fileargs = "";
|
||||
if (files.length === 1) {
|
||||
fileargs = removePrefix(files[0], "files")
|
||||
fileargs = decodeURI(removePrefix(files[0], "files"))
|
||||
} else {
|
||||
for (let file of files) {
|
||||
fileargs += removePrefix(file,"files") + ",";
|
||||
fileargs += decodeURI(removePrefix(file,"files")) + ",";
|
||||
}
|
||||
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
|
||||
window.open(url);
|
||||
} catch (err) {
|
||||
|
@ -130,9 +133,11 @@ export async function moveCopy(items, action = "copy", overwrite = false, rename
|
|||
}
|
||||
try {
|
||||
for (let item of items) {
|
||||
let toPath = encodeURIComponent(removePrefix(decodeURI(item.to), "files"));
|
||||
let fromPath = encodeURIComponent(removePrefix(decodeURI(item.from), "files"));
|
||||
let localParams = { ...params };
|
||||
localParams.destination = item.to;
|
||||
localParams.from = item.from;
|
||||
localParams.destination = toPath;
|
||||
localParams.from = fromPath;
|
||||
const apiPath = getApiPath("api/resources", localParams);
|
||||
promises.push(fetch(apiPath, { method: "PATCH" }));
|
||||
}
|
||||
|
@ -157,7 +162,7 @@ export async function checksum(url, algo) {
|
|||
export function getDownloadURL(path, inline) {
|
||||
try {
|
||||
const params = {
|
||||
files: removePrefix(path,"files"),
|
||||
files: encodeURIComponent(removePrefix(decodeURI(path),"files")),
|
||||
...(inline && { inline: "true" }),
|
||||
};
|
||||
const apiPath = getApiPath("api/raw", params);
|
||||
|
@ -171,7 +176,7 @@ export function getDownloadURL(path, inline) {
|
|||
export function getPreviewURL(path, size, modified) {
|
||||
try {
|
||||
const params = {
|
||||
path: path,
|
||||
path: encodeURIComponent(removePrefix(decodeURI(path),"files")),
|
||||
size: size,
|
||||
key: Date.parse(modified),
|
||||
inline: "true",
|
||||
|
@ -190,7 +195,7 @@ export function getSubtitlesURL(file) {
|
|||
for (const sub of file.subtitles) {
|
||||
const params = {
|
||||
inline: "true",
|
||||
path: sub
|
||||
path: encodeURIComponent(removePrefix(sub,"files"))
|
||||
};
|
||||
const apiPath = getApiPath("api/raw", params);
|
||||
return window.origin+apiPath
|
||||
|
|
|
@ -61,7 +61,6 @@ export async function fetchJSON(url, opts) {
|
|||
|
||||
export function adjustedData(data, url) {
|
||||
data.url = url;
|
||||
|
||||
if (data.type === "directory") {
|
||||
if (!data.url.endsWith("/")) data.url += "/";
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
left: `${left}px`,
|
||||
}"
|
||||
class="button"
|
||||
:class="{ 'dark-mode': isDarkMode, mobile: isMobile }"
|
||||
:class="{ 'dark-mode': isDarkMode, centered: centered }"
|
||||
>
|
||||
<div v-if="selectedCount > 0" class="button selected-count-header">
|
||||
<span>{{ selectedCount }} selected</span>
|
||||
|
@ -108,8 +108,8 @@ export default {
|
|||
user() {
|
||||
return state.user;
|
||||
},
|
||||
isMobile() {
|
||||
return getters.isMobile();
|
||||
centered() {
|
||||
return getters.isMobile() || ( !this.posX || !this.posY );
|
||||
},
|
||||
showContext() {
|
||||
if (getters.currentPromptName() == "ContextMenu" && state.prompts != []) {
|
||||
|
@ -158,7 +158,6 @@ export default {
|
|||
return mutations.showHover(value);
|
||||
},
|
||||
setPositions() {
|
||||
console.log("Setting positions");
|
||||
const contextProps = getters.currentPrompt().props;
|
||||
let tempX = contextProps.posX;
|
||||
let tempY = contextProps.posY;
|
||||
|
@ -204,7 +203,7 @@ export default {
|
|||
justify-content: center;
|
||||
}
|
||||
|
||||
#context-menu.mobile {
|
||||
#context-menu.centered {
|
||||
top: 50% !important;
|
||||
left: 50% !important;
|
||||
-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 -->
|
||||
<div id="input" @click="open">
|
||||
<!-- Close button visible when search is active -->
|
||||
<button v-if="active" class="action" @click="close" :aria-label="$t('buttons.close')"
|
||||
:title="$t('buttons.close')">
|
||||
<button
|
||||
v-if="active"
|
||||
class="action"
|
||||
@click="close"
|
||||
:aria-label="$t('buttons.close')"
|
||||
:title="$t('buttons.close')"
|
||||
>
|
||||
<i class="material-icons">close</i>
|
||||
</button>
|
||||
<!-- Search icon when search is not active -->
|
||||
<i v-else class="material-icons">search</i>
|
||||
<!-- Input field for search -->
|
||||
<input 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')" />
|
||||
<input
|
||||
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>
|
||||
|
||||
<!-- Search results for desktop -->
|
||||
|
@ -21,29 +36,52 @@
|
|||
<div>
|
||||
<div v-if="active">
|
||||
<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 v-show="showOptions">
|
||||
<!-- Button groups for filtering search results -->
|
||||
<ButtonGroup :buttons="folderSelect" @button-clicked="addToTypes" @remove-button-clicked="removeFromTypes"
|
||||
@disableAll="folderSelectClicked()" @enableAll="resetButtonGroups()" />
|
||||
<ButtonGroup :buttons="typeSelect" @button-clicked="addToTypes" @remove-button-clicked="removeFromTypes"
|
||||
:isDisabled="isTypeSelectDisabled" />
|
||||
<ButtonGroup
|
||||
:buttons="folderSelect"
|
||||
@button-clicked="addToTypes"
|
||||
@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 -->
|
||||
<div class="sizeConstraints">
|
||||
<div class="sizeInputWrapper">
|
||||
<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>
|
||||
</div>
|
||||
<div class="sizeInputWrapper">
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<!-- Loading icon when search is ongoing -->
|
||||
|
@ -78,28 +116,14 @@
|
|||
<!-- List of search results -->
|
||||
<ul v-show="results.length > 0">
|
||||
<li v-for="(s, k) in results" :key="k" class="search-entry">
|
||||
<router-link :to="s.path">
|
||||
<i v-if="s.type == 'directory'" class="material-icons folder-icons">
|
||||
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>
|
||||
<a :href="getRelative(s.path)">
|
||||
<Icon :mimetype="s.type" />
|
||||
<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>
|
||||
<div class="filesize">{{ humanSize(s.size) }}</div>
|
||||
</router-link>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
@ -112,6 +136,7 @@ import ButtonGroup from "./ButtonGroup.vue";
|
|||
import { search } from "@/api";
|
||||
import { getters, mutations, state } from "@/store";
|
||||
import { getHumanReadableFilesize } from "@/utils/filesizes";
|
||||
import Icon from "@/components/Icon.vue";
|
||||
|
||||
var boxes = {
|
||||
folder: { label: "folders", icon: "folder" },
|
||||
|
@ -126,6 +151,7 @@ var boxes = {
|
|||
export default {
|
||||
components: {
|
||||
ButtonGroup,
|
||||
Icon,
|
||||
},
|
||||
name: "search",
|
||||
data: function () {
|
||||
|
@ -147,9 +173,7 @@ export default {
|
|||
{ label: "Documents", value: "type:doc" },
|
||||
{ label: "Archives", value: "type:archive" },
|
||||
],
|
||||
toggleOptionButton: [
|
||||
{ label: "Show Options" },
|
||||
],
|
||||
toggleOptionButton: [{ label: "Show Options" }],
|
||||
value: "",
|
||||
ongoing: false,
|
||||
results: [],
|
||||
|
@ -192,7 +216,7 @@ export default {
|
|||
},
|
||||
computed: {
|
||||
showOptions() {
|
||||
return !this.hiddenOptions || !this.isMobile
|
||||
return !this.hiddenOptions || !this.isMobile;
|
||||
},
|
||||
isMobile() {
|
||||
return state.isMobile;
|
||||
|
@ -241,17 +265,19 @@ export default {
|
|||
},
|
||||
},
|
||||
methods: {
|
||||
getRelative(path) {
|
||||
return window.location.href + "/" + path;
|
||||
},
|
||||
getIcon(mimetype) {
|
||||
return getMaterialIconForType(mimetype);
|
||||
},
|
||||
enableOptions() {
|
||||
this.hiddenOptions = false
|
||||
this.toggleOptionButton = [
|
||||
{ label: "Hide Options" },
|
||||
];
|
||||
this.hiddenOptions = false;
|
||||
this.toggleOptionButton = [{ label: "Hide Options" }];
|
||||
},
|
||||
disableOptions() {
|
||||
this.hiddenOptions = true
|
||||
this.toggleOptionButton = [
|
||||
{ label: "Show Options" },
|
||||
];
|
||||
this.hiddenOptions = true;
|
||||
this.toggleOptionButton = [{ label: "Show Options" }];
|
||||
},
|
||||
humanSize(size) {
|
||||
return getHumanReadableFilesize(size);
|
||||
|
@ -372,7 +398,7 @@ export default {
|
|||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
#results>#result-list {
|
||||
#results > #result-list {
|
||||
max-height: 80vh;
|
||||
width: 35em;
|
||||
overflow: scroll;
|
||||
|
@ -518,7 +544,7 @@ body.rtl #search #result {
|
|||
direction: ltr;
|
||||
}
|
||||
|
||||
#search #result>div>*:first-child {
|
||||
#search #result > div > *:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
|
@ -528,7 +554,7 @@ body.rtl #search #result {
|
|||
}
|
||||
|
||||
/* Search Results */
|
||||
body.rtl #search #result ul>* {
|
||||
body.rtl #search #result ul > * {
|
||||
direction: ltr;
|
||||
text-align: left;
|
||||
}
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
<template>
|
||||
<component
|
||||
:is="quickNav ? 'a' : 'div'"
|
||||
:href="quickNav ? getUrl() : undefined"
|
||||
<a
|
||||
:href="getUrl()"
|
||||
:class="{
|
||||
item: true,
|
||||
activebutton: isMaximized && isSelected,
|
||||
}"
|
||||
:id="getID"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
:draggable="isDraggable"
|
||||
|
@ -17,20 +17,21 @@
|
|||
:aria-label="name"
|
||||
:aria-selected="isSelected"
|
||||
@contextmenu="onRightClick"
|
||||
@click="quickNav ? toggleClick() : itemClick($event)"
|
||||
@click="click($event)"
|
||||
>
|
||||
<div @click="toggleClick" :class="{ activetitle: isMaximized && isSelected }">
|
||||
<img
|
||||
v-if="readOnly === undefined && type === 'image' && isThumbsEnabled && isInView"
|
||||
v-if="
|
||||
readOnly === undefined &&
|
||||
type.startsWith('image') &&
|
||||
isThumbsEnabled &&
|
||||
isInView
|
||||
"
|
||||
v-lazy="thumbnailUrl"
|
||||
:class="{ activeimg: isMaximized && isSelected }"
|
||||
ref="thumbnail"
|
||||
/>
|
||||
<i
|
||||
:class="{ iconActive: isMaximized && isSelected }"
|
||||
v-else
|
||||
class="material-icons"
|
||||
></i>
|
||||
<Icon v-else :mimetype="type" />
|
||||
</div>
|
||||
|
||||
<div class="text" :class="{ activecontent: isMaximized && isSelected }">
|
||||
|
@ -40,7 +41,7 @@
|
|||
<time :datetime="modified">{{ humanTime() }}</time>
|
||||
</p>
|
||||
</div>
|
||||
</component>
|
||||
</a>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
|
@ -77,9 +78,14 @@ import * as upload from "@/utils/upload";
|
|||
import { state, getters, mutations } from "@/store"; // Import your custom store
|
||||
import { baseURL } from "@/utils/constants";
|
||||
import { router } from "@/router";
|
||||
import { url } from "@/utils";
|
||||
import Icon from "@/components/Icon.vue";
|
||||
|
||||
export default {
|
||||
name: "item",
|
||||
components: {
|
||||
Icon,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isThumbnailInView: false,
|
||||
|
@ -99,6 +105,9 @@ export default {
|
|||
"path",
|
||||
],
|
||||
computed: {
|
||||
getID() {
|
||||
return url.base64Encode(encodeURIComponent(this.name));
|
||||
},
|
||||
quickNav() {
|
||||
return state.user.singleClick && !state.multiple;
|
||||
},
|
||||
|
@ -146,6 +155,7 @@ export default {
|
|||
},
|
||||
},
|
||||
mounted() {
|
||||
// Prevent default navigation for left-clicks
|
||||
const observer = new IntersectionObserver(this.handleIntersect, {
|
||||
root: null,
|
||||
rootMargin: "0px",
|
||||
|
@ -159,6 +169,13 @@ export default {
|
|||
}
|
||||
},
|
||||
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() {
|
||||
return baseURL.slice(0, -1) + this.url;
|
||||
},
|
||||
|
@ -166,7 +183,8 @@ export default {
|
|||
event.preventDefault(); // Prevent default context menu
|
||||
|
||||
// If no items are selected, select the right-clicked item
|
||||
if (getters.selectedCount() === 0) {
|
||||
if (!state.multiple) {
|
||||
mutations.resetSelected();
|
||||
mutations.addSelected(this.index);
|
||||
}
|
||||
mutations.showHover({
|
||||
|
@ -278,13 +296,18 @@ export default {
|
|||
|
||||
action(overwrite, rename);
|
||||
},
|
||||
itemClick(event) {
|
||||
if (this.singleClick && !state.multiple) this.open();
|
||||
else this.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(() => {
|
||||
this.touches = 0;
|
||||
}, 500);
|
||||
|
@ -319,12 +342,15 @@ export default {
|
|||
|
||||
return;
|
||||
}
|
||||
if (!this.singleClick && !event.ctrlKey && !event.metaKey && !state.multiple)
|
||||
if (!this.singleClick && !event.ctrlKey && !event.metaKey && !state.multiple) {
|
||||
mutations.resetSelected();
|
||||
}
|
||||
mutations.addSelected(this.index);
|
||||
},
|
||||
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>
|
||||
<span id="content_length"></span> {{ humanSize }}
|
||||
</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">
|
||||
<strong>{{ $t("prompts.lastModified") }}:</strong> {{ humanTime }}
|
||||
</p>
|
||||
|
@ -124,6 +128,11 @@ export default {
|
|||
? state.req.name
|
||||
: state.req.items[this.selected[0]].name;
|
||||
},
|
||||
type() {
|
||||
return getters.selectedCount() === 0
|
||||
? state.req.type
|
||||
: state.req.items[this.selected[0]].type;
|
||||
},
|
||||
dir() {
|
||||
return (
|
||||
getters.selectedCount() > 1 ||
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
<div class="card-title">
|
||||
<h2>{{ $t("buttons.share") }}</h2>
|
||||
</div>
|
||||
<div class="searchContext">Path: {{ getContext }}</div>
|
||||
<div class="searchContext">Path: {{ subpath }}</div>
|
||||
|
||||
<template v-if="listing">
|
||||
<div class="card-content">
|
||||
|
@ -167,26 +167,14 @@ export default {
|
|||
}
|
||||
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() {
|
||||
try {
|
||||
const prefix = `/files`;
|
||||
let path = state.route.path.startsWith(prefix)
|
||||
? state.route.path.slice(prefix.length)
|
||||
: state.route.path;
|
||||
path = decodeURIComponent(path);
|
||||
if (path == "") {
|
||||
path = "/";
|
||||
let path = "." + getters.routePath("files");
|
||||
if (getters.selectedCount() === 1) {
|
||||
path = path + state.req.items[this.selected[0]].name;
|
||||
}
|
||||
this.subpath = path;
|
||||
this.subpath = decodeURIComponent(path);
|
||||
// get last element of the path
|
||||
const links = await shareApi.get(this.subpath);
|
||||
this.links = links;
|
||||
|
|
|
@ -63,6 +63,7 @@ export default {
|
|||
overflow: auto;
|
||||
margin-bottom: 0px !important;
|
||||
}
|
||||
|
||||
#sidebar {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
@ -78,9 +79,6 @@ export default {
|
|||
padding-bottom: 4em;
|
||||
background-color: rgb(255 255 255 / 50%) !important;
|
||||
}
|
||||
#sidebar.dark-mode {
|
||||
background-color: rgb(37 49 55 / 33%) !important;
|
||||
}
|
||||
|
||||
#sidebar.sticky {
|
||||
z-index: 3;
|
||||
|
|
|
@ -78,7 +78,7 @@ over
|
|||
/* Main Content */
|
||||
main {
|
||||
position: fixed;
|
||||
padding: 1em;
|
||||
padding: .5em;
|
||||
padding-top: 4em;
|
||||
overflow: scroll;
|
||||
top: 0;
|
||||
|
|
|
@ -272,3 +272,27 @@
|
|||
.dark-mode #results {
|
||||
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/outlined.css';
|
||||
@import 'material-symbols/index.css';
|
||||
|
||||
@font-face {
|
||||
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;
|
||||
}
|
||||
|
||||
.material-icons {
|
||||
font-size: 1.5rem;
|
||||
}
|
|
@ -9,7 +9,6 @@ header {
|
|||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
background-color: rgb(255 255 255 / 50%) !important;
|
||||
padding: 0.5em;
|
||||
}
|
||||
|
||||
|
@ -49,25 +48,3 @@ header img {
|
|||
header .action span {
|
||||
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 "./header.css";
|
||||
@import "./listing.css";
|
||||
@import "./listing-icons.css";
|
||||
@import "./dashboard.css";
|
||||
@import "./login.css";
|
||||
@import './mobile.css';
|
||||
|
|
|
@ -121,7 +121,7 @@ const router = createRouter({
|
|||
|
||||
// Helper function to check if a route resolves to itself
|
||||
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) => {
|
||||
|
|
|
@ -105,6 +105,9 @@ export const getters = {
|
|||
if (getters.currentView() == "settings") {
|
||||
visible = !getters.isMobile();
|
||||
}
|
||||
if (getters.currentView() == "share") {
|
||||
visible = false
|
||||
}
|
||||
if (typeof getters.currentPromptName() === "string" && !getters.isStickySidebar()) {
|
||||
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
|
||||
};
|
||||
|
||||
export function removePrefix(path, prefix) {
|
||||
export function removePrefix(path, prefix = "") {
|
||||
if (path === undefined) {
|
||||
return ""
|
||||
}
|
||||
|
@ -54,9 +54,12 @@ export function removePrefix(path, prefix) {
|
|||
prefix = "/" + trimSlashes(prefix)
|
||||
}
|
||||
const combined = trimSlashes(baseURL) + prefix
|
||||
const combined2 = "/" + combined
|
||||
// Remove combined (baseURL + prefix) from the start of the path if present
|
||||
if (path.startsWith(combined)) {
|
||||
path = path.slice(combined.length);
|
||||
} else if (path.startsWith(combined2)) {
|
||||
path = path.slice(combined2.length);
|
||||
} else if (path.startsWith(prefix)) {
|
||||
// Fallback: remove only the prefix if the combined string isn't present
|
||||
path = path.slice(prefix.length);
|
||||
|
@ -110,3 +113,7 @@ export function removeLeadingSlash(str) {
|
|||
export function trimSlashes(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 Editor from "@/views/files/Editor.vue";
|
||||
import { state, mutations, getters } from "@/store";
|
||||
import { pathsMatch } from "@/utils/url.js";
|
||||
import { url } from "@/utils";
|
||||
import { notify } from "@/notify";
|
||||
//import { removePrefix } from "@/utils/url.js";
|
||||
|
||||
|
@ -41,6 +41,8 @@ export default {
|
|||
return {
|
||||
error: null,
|
||||
width: window.innerWidth,
|
||||
lastPath: "",
|
||||
lastHash: "",
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
|
@ -66,6 +68,7 @@ export default {
|
|||
},
|
||||
},
|
||||
mounted() {
|
||||
window.addEventListener("hashchange", this.scrollToHash);
|
||||
window.addEventListener("keydown", this.keyEvent);
|
||||
},
|
||||
beforeUnmount() {
|
||||
|
@ -75,11 +78,26 @@ export default {
|
|||
mutations.replaceRequest({}); // Use mutation
|
||||
},
|
||||
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() {
|
||||
if (state.route.path === this.lastPath) return;
|
||||
this.lastHash = ""
|
||||
// Set loading to true and reset the error.
|
||||
mutations.setLoading("files", true);
|
||||
this.error = null;
|
||||
|
||||
// Reset view information using mutations
|
||||
mutations.setReload(false);
|
||||
mutations.resetSelected();
|
||||
|
@ -94,14 +112,14 @@ export default {
|
|||
if (res.type != "directory") {
|
||||
let content = false;
|
||||
// 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;
|
||||
}
|
||||
res = await filesApi.fetchFiles(getters.routePath(), content);
|
||||
}
|
||||
data = res;
|
||||
// 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}`;
|
||||
}
|
||||
} catch (e) {
|
||||
|
@ -112,6 +130,10 @@ export default {
|
|||
mutations.replaceRequest(data);
|
||||
mutations.setLoading("files", false);
|
||||
}
|
||||
setTimeout(() => {
|
||||
this.scrollToHash();
|
||||
}, 25);
|
||||
this.lastPath = state.route.path;
|
||||
},
|
||||
keyEvent(event) {
|
||||
// F1!
|
||||
|
|
|
@ -115,7 +115,6 @@ export default {
|
|||
if (!getters.isLoggedIn()) {
|
||||
return;
|
||||
}
|
||||
mutations.resetSelected();
|
||||
mutations.setMultiple(false);
|
||||
if (getters.currentPromptName() !== "success") {
|
||||
mutations.closeHovers();
|
||||
|
@ -155,29 +154,10 @@ main {
|
|||
}
|
||||
|
||||
main.moveWithSidebar {
|
||||
padding-left: 21em;
|
||||
padding-left: 20.5em;
|
||||
}
|
||||
|
||||
main::-webkit-scrollbar {
|
||||
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>
|
||||
|
|
|
@ -149,6 +149,8 @@ import QrcodeVue from "qrcode.vue";
|
|||
import Item from "@/components/files/ListingItem.vue";
|
||||
import Clipboard from "clipboard";
|
||||
import { state, getters, mutations } from "@/store";
|
||||
import { url } from "@/utils";
|
||||
import { getTypeInfo } from "@/utils/mimetype";
|
||||
|
||||
export default {
|
||||
name: "share",
|
||||
|
@ -209,12 +211,11 @@ export default {
|
|||
},
|
||||
icon() {
|
||||
if (state.req.type == "directory") return "folder";
|
||||
if (state.req.type === "image") return "insert_photo";
|
||||
if (state.req.type === "audio") return "volume_up";
|
||||
if (state.req.type === "video") return "movie";
|
||||
if (getTypeInfo(state.req.type).simpleType == "image") return "insert_photo";
|
||||
if (getTypeInfo(state.req.type).simpleType == "audio") return "volume_up";
|
||||
if (getTypeInfo(state.req.type).simpleType == "video") return "movie";
|
||||
return "insert_drive_file";
|
||||
},
|
||||
|
||||
humanSize() {
|
||||
if (state.req.type == "directory") {
|
||||
return state.req.items.length;
|
||||
|
@ -229,10 +230,13 @@ export default {
|
|||
return new Date(Date.parse(state.req.modified)).toLocaleString();
|
||||
},
|
||||
isImage() {
|
||||
return state.req.type === "image";
|
||||
return getTypeInfo(state.req.type).simpleType === "image";
|
||||
},
|
||||
isMedia() {
|
||||
return state.req.type === "video" || state.req.type === "audio";
|
||||
return (
|
||||
getTypeInfo(state.req.type).simpleType === "video" ||
|
||||
getTypeInfo(state.req.type).simpleType === "audio"
|
||||
);
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
|
@ -245,7 +249,7 @@ export default {
|
|||
});
|
||||
},
|
||||
base64(name) {
|
||||
return window.btoa(unescape(encodeURIComponent(name)));
|
||||
return url.base64Encode(name);
|
||||
},
|
||||
async fetchData() {
|
||||
let urlPath = getters.routePath("share");
|
||||
|
@ -298,7 +302,7 @@ export default {
|
|||
download() {
|
||||
if (getters.isSingleFileSelected()) {
|
||||
const share = {
|
||||
path: his.subPath,
|
||||
path: this.subPath,
|
||||
hash: this.hash,
|
||||
token: this.token,
|
||||
format: null,
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
</style>
|
||||
|
||||
<script>
|
||||
import url from "@/utils/url.js";
|
||||
import { url } from "@/utils";
|
||||
import router from "@/router";
|
||||
import { state, mutations, getters } from "@/store";
|
||||
import { filesApi } from "@/api";
|
||||
|
@ -227,7 +227,7 @@ export default {
|
|||
mutations.closeHovers();
|
||||
},
|
||||
base64(name) {
|
||||
return window.btoa(unescape(encodeURIComponent(name)));
|
||||
return url.base64Encode(name);
|
||||
},
|
||||
keyEvent(event) {
|
||||
// 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