mirror of
https://github.com/gtsteffaniak/filebrowser.git
synced 2026-11-04 16:50:08 -05:00
Compare commits
10
Commits
main
...
v1.5.0-beta
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
848664005a | ||
|
|
e53eff9252 | ||
|
|
f82ea77726 | ||
|
|
cb02778bde | ||
|
|
a638d31476 | ||
|
|
119d1298c8 | ||
|
|
8f4916dee5 | ||
|
|
15ef3e4c88 | ||
|
|
4072e46e44 | ||
|
|
38d141eb97 |
No files matched your search
@@ -2,6 +2,34 @@
|
||||
|
||||
All notable changes to this project will be documented in this file. For commit guidelines, please refer to [Standard Version](https://github.com/conventional-changelog/standard-version).
|
||||
|
||||
## v1.5.0
|
||||
|
||||
**New Features**:
|
||||
- Added basic html viewer with relative reference support (#2522)
|
||||
- Shares can have pinned files, requires a user to have edit access to share (#2522)
|
||||
- Enhanced search
|
||||
- now uses "lazy" match by default. (#2509)
|
||||
- added missing `case sensitive` option in the UI
|
||||
- Progressive Web App (PWA) improvements
|
||||
- restored install prompt with sidebar install button (#2086)
|
||||
- camera and video capture buttons on upload (mobile-friendly `capture` inputs)
|
||||
- send files to other apps via the Web Share API (`Send to app` in the context menu)
|
||||
- App notifications for file operations (#2478)
|
||||
- optional browser notifications when uploads, chunked downloads, move/copy, or failures finish while the tab is in the background
|
||||
- single on/off toggle in Notifications settings, stored in browser local storage
|
||||
|
||||
**Notes**:
|
||||
- [docker] upgraded ffmpeg version 8.1 to 8.1.1
|
||||
|
||||
**BugFixes**:
|
||||
- fixed PWA manifest `scope` and `id` so install works when the app is served under a base URL
|
||||
- installing a public share as a PWA now opens the share URL instead of the site root (#2302)
|
||||
|
||||
## v1.4.4
|
||||
|
||||
**BugFixes**:
|
||||
- Pinning/Unpinning file resets users' scope permission (#2532)
|
||||
|
||||
## v1.4.3
|
||||
|
||||
**Security**:
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
FROM gtstef/ffmpeg:8.1-decode AS ffmpeg
|
||||
FROM gtstef/ffmpeg:8.1.1-decode AS ffmpeg
|
||||
FROM golang:alpine AS base
|
||||
ARG VERSION
|
||||
ARG REVISION
|
||||
|
||||
@@ -223,12 +223,12 @@ func GetDirItems(opts utils.FileOptions, access *access.Storage, user *users.Use
|
||||
// FileInfoFasterFunc is a variable that can be mocked in tests
|
||||
var FileInfoFasterFunc = fileInfoFasterImpl
|
||||
|
||||
func FileInfoFaster(opts utils.FileOptions, access *access.Storage, user *users.User, share *share.Storage) (*iteminfo.ExtendedFileInfo, error) {
|
||||
return FileInfoFasterFunc(opts, access, user, share)
|
||||
func FileInfoFaster(opts utils.FileOptions, access *access.Storage, user *users.User, shareStore *share.Storage) (*iteminfo.ExtendedFileInfo, error) {
|
||||
return FileInfoFasterFunc(opts, access, user, shareStore)
|
||||
}
|
||||
|
||||
// fileInfoFasterImpl is the actual implementation of FileInfoFaster
|
||||
func fileInfoFasterImpl(opts utils.FileOptions, access *access.Storage, user *users.User, share *share.Storage) (*iteminfo.ExtendedFileInfo, error) {
|
||||
func fileInfoFasterImpl(opts utils.FileOptions, access *access.Storage, user *users.User, shareStore *share.Storage) (*iteminfo.ExtendedFileInfo, error) {
|
||||
response := &iteminfo.ExtendedFileInfo{}
|
||||
indexPath, userScope, topLevelErr := CheckPermissions(opts, access, user)
|
||||
accessRulesErr := topLevelErr != nil && topLevelErr == errors.ErrAccessDenied && indexPath != ""
|
||||
@@ -268,19 +268,25 @@ func fileInfoFasterImpl(opts utils.FileOptions, access *access.Storage, user *us
|
||||
response.FileInfo = *info
|
||||
response.RealPath = filepath.Join(idx.Path, indexPath)
|
||||
response.Source = opts.Source
|
||||
if share != nil && user.Permissions.Share && opts.ShowSharedAttr {
|
||||
if shareStore != nil && user.Permissions.Share && opts.ShowSharedAttr {
|
||||
for i := range response.Files {
|
||||
file := &response.Files[i]
|
||||
file.IsShared = share.IsShared(response.Path+file.Name, idx.Path, user.ID)
|
||||
file.IsShared = shareStore.IsShared(response.Path+file.Name, idx.Path, user.ID)
|
||||
}
|
||||
for i := range response.Folders {
|
||||
folder := &response.Folders[i]
|
||||
folder.IsShared = share.IsShared(response.Path+folder.Name, idx.Path, user.ID)
|
||||
folder.IsShared = shareStore.IsShared(response.Path+folder.Name, idx.Path, user.ID)
|
||||
}
|
||||
response.IsShared = share.IsShared(response.Path, idx.Path, user.ID)
|
||||
response.IsShared = shareStore.IsShared(response.Path, idx.Path, user.ID)
|
||||
}
|
||||
if info.Type == "directory" && opts.ShowPinnedItems {
|
||||
if source, ok := users.ResolveSourceKey(response.Source); ok {
|
||||
if opts.ShareHash != "" && shareStore != nil {
|
||||
if link, err := shareStore.GetByHash(opts.ShareHash); err == nil {
|
||||
if shareRelDir, err := link.ShareRelativeDir(response.Path); err == nil {
|
||||
response.PinnedItems = link.PinnedItems.NamesForDirectory(shareRelDir)
|
||||
}
|
||||
}
|
||||
} else if source, ok := users.ResolveSourceKey(response.Source); ok {
|
||||
response.PinnedItems = user.PinnedNamesForDirectory(source.Path, response.Path)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,7 +57,8 @@ type FileOptions struct {
|
||||
ExtractEmbeddedSubtitles bool // whether to extract embedded subtitles from media files
|
||||
AlbumArt bool // whether to get album art from media files
|
||||
ShowHidden bool // whether to show hidden files (true = show, false = hide)
|
||||
ShowPinnedItems bool // whether to show pinned items
|
||||
ShowPinnedItems bool // whether to show pinned items
|
||||
ShareHash string // when set, pinned items are loaded from this share link
|
||||
HideFileExt string // Hide files based on extensions (eg: '.lrc' or '.srt')
|
||||
FollowSymlinks bool // whether to follow symlinks
|
||||
Only string // whether to only get files or folders
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
package share
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/gtsteffaniak/filebrowser/backend/common/utils"
|
||||
)
|
||||
|
||||
// PinnedItems maps share-relative directory paths to pinned item names.
|
||||
type PinnedItems map[string][]string
|
||||
|
||||
// Add records a pinned item name under shareRelDir.
|
||||
func (p PinnedItems) Add(shareRelDir, name string) {
|
||||
if p == nil {
|
||||
return
|
||||
}
|
||||
items := p[shareRelDir]
|
||||
if slices.Contains(items, name) {
|
||||
return
|
||||
}
|
||||
p[shareRelDir] = append(items, name)
|
||||
}
|
||||
|
||||
// Remove deletes a pinned item name and prunes empty map entries.
|
||||
func (p PinnedItems) Remove(shareRelDir, name string) {
|
||||
if p == nil {
|
||||
return
|
||||
}
|
||||
items := p[shareRelDir]
|
||||
idx := slices.Index(items, name)
|
||||
if idx < 0 {
|
||||
return
|
||||
}
|
||||
items = append(items[:idx], items[idx+1:]...)
|
||||
if len(items) == 0 {
|
||||
delete(p, shareRelDir)
|
||||
} else {
|
||||
p[shareRelDir] = items
|
||||
}
|
||||
}
|
||||
|
||||
// NamesForDirectory returns pinned item names for a share-relative directory path.
|
||||
func (p PinnedItems) NamesForDirectory(shareRelDir string) []string {
|
||||
if len(p) == 0 {
|
||||
return nil
|
||||
}
|
||||
names := p[shareRelDir]
|
||||
if len(names) == 0 {
|
||||
return nil
|
||||
}
|
||||
out := make([]string, len(names))
|
||||
copy(out, names)
|
||||
return out
|
||||
}
|
||||
|
||||
// EnsurePinnedItems returns a non-nil map for mutation.
|
||||
func (l *Link) EnsurePinnedItems() PinnedItems {
|
||||
if l.PinnedItems == nil {
|
||||
l.PinnedItems = make(PinnedItems)
|
||||
}
|
||||
return l.PinnedItems
|
||||
}
|
||||
|
||||
// ShareRelativeDir maps a source index directory path to a share-relative directory path.
|
||||
// link.Path is the index path of the share root (e.g. "/share/").
|
||||
func (l *Link) ShareRelativeDir(indexDirPath string) (string, error) {
|
||||
shareRootIndex := utils.AddTrailingSlashIfNotExists(l.Path)
|
||||
indexDirPath = utils.AddTrailingSlashIfNotExists(indexDirPath)
|
||||
if !strings.HasPrefix(indexDirPath, shareRootIndex) {
|
||||
return "", fmt.Errorf("path is outside share scope")
|
||||
}
|
||||
|
||||
rel := strings.TrimPrefix(indexDirPath, shareRootIndex)
|
||||
if rel == "" {
|
||||
return "/", nil
|
||||
}
|
||||
return utils.AddTrailingSlashIfNotExists("/" + strings.TrimPrefix(rel, "/")), nil
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package share
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestSharePinnedItemsAddRemove(t *testing.T) {
|
||||
p := make(PinnedItems)
|
||||
const dir = "/"
|
||||
const name = "vacation.jpg"
|
||||
|
||||
p.Add(dir, name)
|
||||
if len(p[dir]) != 1 || p[dir][0] != name {
|
||||
t.Fatalf("add failed: %#v", p)
|
||||
}
|
||||
|
||||
p.Add(dir, name)
|
||||
if len(p[dir]) != 1 {
|
||||
t.Fatalf("duplicate add should be ignored: %#v", p)
|
||||
}
|
||||
|
||||
p.Remove(dir, name)
|
||||
if len(p) != 0 {
|
||||
t.Fatalf("remove should prune empty entries: %#v", p)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSharePinnedNamesForDirectory(t *testing.T) {
|
||||
p := PinnedItems{
|
||||
"/photos/": {"vacation.jpg", "notes.txt"},
|
||||
}
|
||||
|
||||
names := p.NamesForDirectory("/photos/")
|
||||
if len(names) != 2 || names[0] != "vacation.jpg" || names[1] != "notes.txt" {
|
||||
t.Fatalf("unexpected names: %#v", names)
|
||||
}
|
||||
if got := p.NamesForDirectory("/missing/"); got != nil {
|
||||
t.Fatalf("expected nil for missing directory, got %#v", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestShareRelativeDir(t *testing.T) {
|
||||
link := &Link{CommonShare: CommonShare{Path: "/share/"}}
|
||||
|
||||
got, err := link.ShareRelativeDir("/share/")
|
||||
if err != nil {
|
||||
t.Fatalf("ShareRelativeDir root: %v", err)
|
||||
}
|
||||
if got != "/" {
|
||||
t.Fatalf("got %q, want /", got)
|
||||
}
|
||||
|
||||
got, err = link.ShareRelativeDir("/share/test [file2]/")
|
||||
if err != nil {
|
||||
t.Fatalf("ShareRelativeDir subdir: %v", err)
|
||||
}
|
||||
if got != "/test [file2]/" {
|
||||
t.Fatalf("got %q, want /test [file2]/", got)
|
||||
}
|
||||
}
|
||||
@@ -60,11 +60,12 @@ type CreateBody struct {
|
||||
// Link is the information needed to build a shareable link.
|
||||
type Link struct {
|
||||
CommonShare
|
||||
Downloads int `json:"downloads"`
|
||||
Hash string `json:"hash" storm:"id,index"`
|
||||
UserID uint `json:"userID"`
|
||||
Expire int64 `json:"expire"`
|
||||
PasswordHash string `json:"password_hash,omitempty"`
|
||||
Downloads int `json:"downloads"`
|
||||
Hash string `json:"hash" storm:"id,index"`
|
||||
UserID uint `json:"userID"`
|
||||
Expire int64 `json:"expire"`
|
||||
PasswordHash string `json:"password_hash,omitempty"`
|
||||
PinnedItems PinnedItems `json:"pinnedItems,omitempty"`
|
||||
// Token is a random value that will only be set when PasswordHash is set. It is
|
||||
// URL-Safe and is used to download links in password-protected shares via a
|
||||
// query arg.
|
||||
|
||||
@@ -19,13 +19,17 @@ type SearchItem struct {
|
||||
}
|
||||
|
||||
// appendNameGlobSQL adds AND (name GLOB ? OR ...) or AND (name GLOB ? AND ...) when joinAnd is true.
|
||||
func appendNameGlobSQL(query string, args []interface{}, patterns []string, joinAnd bool) (string, []interface{}) {
|
||||
func appendNameGlobSQL(query string, args []interface{}, patterns []string, joinAnd, caseExact bool) (string, []interface{}) {
|
||||
if len(patterns) == 0 {
|
||||
return query, args
|
||||
}
|
||||
nameExpr := "name"
|
||||
if !caseExact {
|
||||
nameExpr = "lower(name)"
|
||||
}
|
||||
parts := make([]string, len(patterns))
|
||||
for i := range patterns {
|
||||
parts[i] = "name GLOB ?"
|
||||
parts[i] = nameExpr + " GLOB ?"
|
||||
args = append(args, patterns[i])
|
||||
}
|
||||
join := " OR "
|
||||
@@ -38,7 +42,7 @@ func appendNameGlobSQL(query string, args []interface{}, patterns []string, join
|
||||
// SearchItems queries the database for items matching the search criteria for a single source.
|
||||
// When nameGlobPatterns is non-empty (and largest is false), rows are restricted with SQLite name GLOB ... OR ....
|
||||
// Returns rows that can be iterated to scan search results.
|
||||
func (db *IndexDB) SearchItems(source string, scope string, largest bool, nameGlobPatterns []string, nameGlobPatternsAnd bool) (*sql.Rows, error) {
|
||||
func (db *IndexDB) SearchItems(source string, scope string, largest bool, nameGlobPatterns []string, nameGlobPatternsAnd, caseExact bool) (*sql.Rows, error) {
|
||||
query := `
|
||||
SELECT path, name, size, mod_time, type, is_dir, has_preview
|
||||
FROM index_items
|
||||
@@ -54,7 +58,7 @@ func (db *IndexDB) SearchItems(source string, scope string, largest bool, nameGl
|
||||
}
|
||||
|
||||
if !largest && len(nameGlobPatterns) > 0 {
|
||||
query, args = appendNameGlobSQL(query, args, nameGlobPatterns, nameGlobPatternsAnd)
|
||||
query, args = appendNameGlobSQL(query, args, nameGlobPatterns, nameGlobPatternsAnd, caseExact)
|
||||
}
|
||||
|
||||
if largest {
|
||||
@@ -67,7 +71,7 @@ func (db *IndexDB) SearchItems(source string, scope string, largest bool, nameGl
|
||||
// SearchItemsMultiSource queries the database for items matching the search criteria across multiple sources.
|
||||
// When nameGlobPatterns is non-empty (and largest is false), restricts rows with SQLite name GLOB ... OR ....
|
||||
// Returns rows that can be iterated to scan search results.
|
||||
func (db *IndexDB) SearchItemsMultiSource(sources []string, sourceScopes map[string]string, largest bool, nameGlobPatterns []string, nameGlobPatternsAnd bool) (*sql.Rows, error) {
|
||||
func (db *IndexDB) SearchItemsMultiSource(sources []string, sourceScopes map[string]string, largest bool, nameGlobPatterns []string, nameGlobPatternsAnd, caseExact bool) (*sql.Rows, error) {
|
||||
if len(sources) == 0 {
|
||||
return nil, fmt.Errorf("at least one source is required")
|
||||
}
|
||||
@@ -119,7 +123,7 @@ func (db *IndexDB) SearchItemsMultiSource(sources []string, sourceScopes map[str
|
||||
}
|
||||
|
||||
if !largest && len(nameGlobPatterns) > 0 {
|
||||
query, args = appendNameGlobSQL(query, args, nameGlobPatterns, nameGlobPatternsAnd)
|
||||
query, args = appendNameGlobSQL(query, args, nameGlobPatterns, nameGlobPatternsAnd, caseExact)
|
||||
}
|
||||
|
||||
if largest {
|
||||
|
||||
+18
-18
@@ -24,16 +24,16 @@ require (
|
||||
github.com/stretchr/testify v1.11.1
|
||||
github.com/swaggo/http-swagger v1.3.4
|
||||
github.com/swaggo/swag v1.16.6
|
||||
golang.org/x/crypto v0.52.0
|
||||
golang.org/x/image v0.41.0
|
||||
golang.org/x/mod v0.36.0
|
||||
golang.org/x/net v0.55.0
|
||||
golang.org/x/crypto v0.53.0
|
||||
golang.org/x/image v0.42.0
|
||||
golang.org/x/mod v0.37.0
|
||||
golang.org/x/net v0.56.0
|
||||
golang.org/x/oauth2 v0.36.0
|
||||
golang.org/x/sys v0.45.0
|
||||
golang.org/x/sys v0.46.0
|
||||
golang.org/x/time v0.15.0
|
||||
golang.org/x/tools v0.45.0
|
||||
golang.org/x/tools v0.46.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
modernc.org/sqlite v1.51.0
|
||||
modernc.org/sqlite v1.52.0
|
||||
)
|
||||
|
||||
require (
|
||||
@@ -123,13 +123,13 @@ require (
|
||||
github.com/go-openapi/jsonpointer v0.23.1 // indirect
|
||||
github.com/go-openapi/jsonreference v0.21.6 // indirect
|
||||
github.com/go-openapi/spec v0.22.5 // indirect
|
||||
github.com/go-openapi/swag/conv v0.26.0 // indirect
|
||||
github.com/go-openapi/swag/jsonname v0.26.0 // indirect
|
||||
github.com/go-openapi/swag/jsonutils v0.26.0 // indirect
|
||||
github.com/go-openapi/swag/loading v0.26.0 // indirect
|
||||
github.com/go-openapi/swag/stringutils v0.26.0 // indirect
|
||||
github.com/go-openapi/swag/typeutils v0.26.0 // indirect
|
||||
github.com/go-openapi/swag/yamlutils v0.26.0 // indirect
|
||||
github.com/go-openapi/swag/conv v0.26.1 // indirect
|
||||
github.com/go-openapi/swag/jsonname v0.26.1 // indirect
|
||||
github.com/go-openapi/swag/jsonutils v0.26.1 // indirect
|
||||
github.com/go-openapi/swag/loading v0.26.1 // indirect
|
||||
github.com/go-openapi/swag/stringutils v0.26.1 // indirect
|
||||
github.com/go-openapi/swag/typeutils v0.26.1 // indirect
|
||||
github.com/go-openapi/swag/yamlutils v0.26.1 // indirect
|
||||
github.com/go-playground/locales v0.14.1 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||
github.com/go-toolsmith/astcast v1.1.0 // indirect
|
||||
@@ -147,7 +147,7 @@ require (
|
||||
github.com/gofrs/flock v0.13.0 // indirect
|
||||
github.com/gohugoio/hugo v0.149.1 // indirect
|
||||
github.com/golang-jwt/jwt/v5 v5.3.1 // indirect
|
||||
github.com/golang/geo v0.0.0-20260605061815-58f45aa500f1 // indirect
|
||||
github.com/golang/geo v0.0.0-20260612074446-f1a45663b0f3 // indirect
|
||||
github.com/golang/protobuf v1.5.3 // indirect
|
||||
github.com/golangci/asciicheck v0.5.0 // indirect
|
||||
github.com/golangci/dupl v0.0.0-20260401084720-c99c5cf5c202 // indirect
|
||||
@@ -288,13 +288,13 @@ require (
|
||||
go.yaml.in/yaml/v3 v3.0.4 // indirect
|
||||
golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa // indirect
|
||||
golang.org/x/exp/typeparams v0.0.0-20260209203927-2842357ff358 // indirect
|
||||
golang.org/x/sync v0.20.0 // indirect
|
||||
golang.org/x/text v0.37.0 // indirect
|
||||
golang.org/x/sync v0.21.0 // indirect
|
||||
golang.org/x/text v0.38.0 // indirect
|
||||
google.golang.org/protobuf v1.36.10 // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
honnef.co/go/tools v0.7.0 // indirect
|
||||
modernc.org/libc v1.72.5 // indirect
|
||||
modernc.org/libc v1.73.1 // indirect
|
||||
modernc.org/mathutil v1.7.1 // indirect
|
||||
modernc.org/memory v1.11.0 // indirect
|
||||
mvdan.cc/gofumpt v0.9.2 // indirect
|
||||
|
||||
+42
-42
@@ -323,22 +323,22 @@ github.com/go-openapi/jsonreference v0.21.6/go.mod h1:xzbgtQ3ZbWxvET3AxdzCJlJt6v
|
||||
github.com/go-openapi/spec v0.22.5 h1:KhO7RBlKQfonUWX2WzQCoLIXVA6AcNqDGZ3a1Dutdlo=
|
||||
github.com/go-openapi/spec v0.22.5/go.mod h1:vxpOtMya5TXtENXKE5bKqv5NjocVhyhxHrlZfvKnZ74=
|
||||
github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=
|
||||
github.com/go-openapi/swag/conv v0.26.0 h1:5yGGsPYI1ZCva93U0AoKi/iZrNhaJEjr324YVsiD89I=
|
||||
github.com/go-openapi/swag/conv v0.26.0/go.mod h1:tpAmIL7X58VPnHHiSO4uE3jBeRamGsFsfdDeDtb5ECE=
|
||||
github.com/go-openapi/swag/jsonname v0.26.0 h1:gV1NFX9M8avo0YSpmWogqfQISigCmpaiNci8cGECU5w=
|
||||
github.com/go-openapi/swag/jsonname v0.26.0/go.mod h1:urBBR8bZNoDYGr653ynhIx+gTeIz0ARZxHkAPktJK2M=
|
||||
github.com/go-openapi/swag/jsonutils v0.26.0 h1:FawFML2iAXsPqmERscuMPIHmFsoP1tOqWkxBaKNMsnA=
|
||||
github.com/go-openapi/swag/jsonutils v0.26.0/go.mod h1:2VmA0CJlyFqgawOaPI9psnjFDqzyivIqLYN34t9p91E=
|
||||
github.com/go-openapi/swag/jsonutils/fixtures_test v0.26.0 h1:apqeINu/ICHouqiRZbyFvuDge5jCmmLTqGQ9V95EaOM=
|
||||
github.com/go-openapi/swag/jsonutils/fixtures_test v0.26.0/go.mod h1:AyM6QT8uz5IdKxk5akv0y6u4QvcL9GWERt0Jx/F/R8Y=
|
||||
github.com/go-openapi/swag/loading v0.26.0 h1:Apg6zaKhCJurpJer0DCxq99qwmhFddBhaMX7kilDcko=
|
||||
github.com/go-openapi/swag/loading v0.26.0/go.mod h1:dBxQ/6V2uBaAQdevN18VELE6xSpJWZxLX4txe12JwDg=
|
||||
github.com/go-openapi/swag/stringutils v0.26.0 h1:qZQngLxs5s7SLijc3N2ZO+fUq2o8LjuWAASSrJuh+xg=
|
||||
github.com/go-openapi/swag/stringutils v0.26.0/go.mod h1:sWn5uY+QIIspwPhvgnqJsH8xqFT2ZbYcvbcFanRyhFE=
|
||||
github.com/go-openapi/swag/typeutils v0.26.0 h1:2kdEwdiNWy+JJdOvu5MA2IIg2SylWAFuuyQIKYybfq4=
|
||||
github.com/go-openapi/swag/typeutils v0.26.0/go.mod h1:oovDuIUvTrEHVMqWilQzKzV4YlSKgyZmFh7AlfABNVE=
|
||||
github.com/go-openapi/swag/yamlutils v0.26.0 h1:H7O8l/8NJJQ/oiReEN+oMpnGMyt8G0hl460nRZxhLMQ=
|
||||
github.com/go-openapi/swag/yamlutils v0.26.0/go.mod h1:1evKEGAtP37Pkwcc7EWMF0hedX0/x3Rkvei2wtG/TbU=
|
||||
github.com/go-openapi/swag/conv v0.26.1 h1:slr5FVkg9Wc3Y5zcwenD8Sd/PQ94b2I/QJI7N7KTBpg=
|
||||
github.com/go-openapi/swag/conv v0.26.1/go.mod h1:mvQXgPptZk9GTrFgGwWvT4q+dN+zQej9JfmGwnipz1A=
|
||||
github.com/go-openapi/swag/jsonname v0.26.1 h1:VReupaV6WxlAsCn0e4DUfgV6bPmINnPpyJDLqSfNPcE=
|
||||
github.com/go-openapi/swag/jsonname v0.26.1/go.mod h1:OvdW6BoWoj33pTfi7x9vFrgmT+fk7aw0BRwvCE0YOuc=
|
||||
github.com/go-openapi/swag/jsonutils v0.26.1 h1:2hdBfFkHg+7Wrz2VsCbeyR6hzkRDs7AztnMR2u84yOY=
|
||||
github.com/go-openapi/swag/jsonutils v0.26.1/go.mod h1:U+RMJH3wa+6BRiphuRtIyI8fW9HPFqFQ4sHk2oRx0UQ=
|
||||
github.com/go-openapi/swag/jsonutils/fixtures_test v0.26.1 h1:1CD7NiLLb/TXl3tOnFYU4b+mNfb5rtgHkaA+q7RMYYQ=
|
||||
github.com/go-openapi/swag/jsonutils/fixtures_test v0.26.1/go.mod h1:ZWafc8nMdYzTE3uYY6W86f0n46+IF0g4uUyRhJw/kXc=
|
||||
github.com/go-openapi/swag/loading v0.26.1 h1:E9K4wqXeROlhjFQ13K9zMz6ojFGXIggGe+ad1odrK9w=
|
||||
github.com/go-openapi/swag/loading v0.26.1/go.mod h1:3qvRIlWzWdq1HvmldwmuJ2ohpcAryN6xVt2OTKd0/7E=
|
||||
github.com/go-openapi/swag/stringutils v0.26.1 h1:f88uYyTso7TnHrKM/bUBsQ5e2wKf37cpgo6pvbzd9yU=
|
||||
github.com/go-openapi/swag/stringutils v0.26.1/go.mod h1:Sc6d3bU8fgk5AyZR8/8jEQ+Is/Ald+TD/IIggPN8UJk=
|
||||
github.com/go-openapi/swag/typeutils v0.26.1 h1:yg42FgMzRR6PVQ3M3qHz1s+Y6/P4HoJ3cBarXa3OVnU=
|
||||
github.com/go-openapi/swag/typeutils v0.26.1/go.mod h1:VfnV+oUtSP2vCSCn2aJgnr8OevUYemyIzzS1VOzS10o=
|
||||
github.com/go-openapi/swag/yamlutils v0.26.1 h1:0TSLK+lXs9vfIhAWzBeI/lOzEnIoot6WTCO1aAeWFTk=
|
||||
github.com/go-openapi/swag/yamlutils v0.26.1/go.mod h1:7W5b7PRX9MxwL7TjeG7H8HkyBGRsIDRObhyMWFgBI2M=
|
||||
github.com/go-openapi/testify/enable/yaml/v2 v2.5.1 h1:q9NtHwK4qHF7yZziBPvZyv7zWAIk8ok88Gh2mR6Jpc8=
|
||||
github.com/go-openapi/testify/enable/yaml/v2 v2.5.1/go.mod h1:JW0MXIotCYps/XsgJnG3a8Q7rE5xAiBwoOD5OfaIQBk=
|
||||
github.com/go-openapi/testify/v2 v2.5.1 h1:TMdhCaw8fUNraVSf3Omoob1dO/AzBfhtFAPW0an6sBo=
|
||||
@@ -417,8 +417,8 @@ github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArs
|
||||
github.com/golang/geo v0.0.0-20190916061304-5b978397cfec/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI=
|
||||
github.com/golang/geo v0.0.0-20200319012246-673a6f80352d/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI=
|
||||
github.com/golang/geo v0.0.0-20210211234256-740aa86cb551/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI=
|
||||
github.com/golang/geo v0.0.0-20260605061815-58f45aa500f1 h1:vkAT/N8DPfmQ3ZltANiX95HhM0nv/tqoq5Innoimcwo=
|
||||
github.com/golang/geo v0.0.0-20260605061815-58f45aa500f1/go.mod h1:Mymr9kRGDc64JPr03TSZmuIBODZ3KyswLzm1xL0HFA8=
|
||||
github.com/golang/geo v0.0.0-20260612074446-f1a45663b0f3 h1:UlucSQUu9SZdDRlMWv5N/T3Rig9gv615vWvHFKrXHWA=
|
||||
github.com/golang/geo v0.0.0-20260612074446-f1a45663b0f3/go.mod h1:Mymr9kRGDc64JPr03TSZmuIBODZ3KyswLzm1xL0HFA8=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
@@ -952,8 +952,8 @@ golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8U
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.52.0 h1:RMs7fP2rXdep0CftQlK8Uf+kibLm7qkCcradZWYz988=
|
||||
golang.org/x/crypto v0.52.0/go.mod h1:1QgfPxDqh0T2M/elOJtp9RvuR95kVjir0e6/BvEmGbc=
|
||||
golang.org/x/crypto v0.53.0 h1:QZ4Muo8THX6CizN2vPPd5fBGHyogrdK9fG4wLPFUsto=
|
||||
golang.org/x/crypto v0.53.0/go.mod h1:DNLU434OwVakk9PzuwV8w62mAJpRJL3vsgcfp4Qnsio=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
@@ -972,8 +972,8 @@ golang.org/x/exp/typeparams v0.0.0-20260209203927-2842357ff358 h1:qWFG1Dj7TBjOjO
|
||||
golang.org/x/exp/typeparams v0.0.0-20260209203927-2842357ff358/go.mod h1:4Mzdyp/6jzw9auFDJ3OMF5qksa7UvPnzKqTVGcb04ms=
|
||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/image v0.41.0 h1:8wS72eGJMJaBxK6okTzd4WaXumUlTVlb753MlsSvTCo=
|
||||
golang.org/x/image v0.41.0/go.mod h1:uIc348UZMSvS5Z65CVZ7iDPaNobNFEPeJ4kbqTOszmA=
|
||||
golang.org/x/image v0.42.0 h1:1gSs6ehNWXLbkHBIPcWztk3D/6aIA/8hauiAYtlodVY=
|
||||
golang.org/x/image v0.42.0/go.mod h1:rrpelvGFt+kLPAjPM4HeWPgrl0FtafueU//e5N0qk/Q=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
@@ -996,8 +996,8 @@ golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.36.0 h1:JJjpVx6myfUsUdAzZuOSTTmRE0PfZeNWzzvKrP7amb4=
|
||||
golang.org/x/mod v0.36.0/go.mod h1:moc6ELqsWcOw5Ef3xVprK5ul/MvtVvkIXLziUOICjUQ=
|
||||
golang.org/x/mod v0.37.0 h1:vF1DjpVEshcIqoEaauuHebaLk1O1forxjxBaVn884JQ=
|
||||
golang.org/x/mod v0.37.0/go.mod h1:m8S8VeM9r4dzDwjrKO0a1sZP3YjeMamRRlD+fmR2Q/0=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
@@ -1036,8 +1036,8 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug
|
||||
golang.org/x/net v0.0.0-20221002022538-bcab6841153b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8=
|
||||
golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww=
|
||||
golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o=
|
||||
golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
@@ -1059,8 +1059,8 @@ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJ
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/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.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
|
||||
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||
golang.org/x/sync v0.21.0 h1:HLII4xRRTtCRkxYp4HNFF0Js/Og6q2i++KXbg0gHCwM=
|
||||
golang.org/x/sync v0.21.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
@@ -1107,8 +1107,8 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY=
|
||||
golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw=
|
||||
golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
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=
|
||||
@@ -1120,8 +1120,8 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/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.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc=
|
||||
golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38=
|
||||
golang.org/x/text v0.38.0 h1:sXmwo9DwP3OK9EZ7PqAdaooSGozfl/3a6/xJcbzPRhE=
|
||||
golang.org/x/text v0.38.0/go.mod h1:YXZt3QhHUKYT53r2lLKFIVi6Ao1jdzrTR/KQ09qyxF4=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
@@ -1174,8 +1174,8 @@ golang.org/x/tools v0.1.1-0.20210302220138-2ac05c832e1a/go.mod h1:9bzcO0MWcOuT0t
|
||||
golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/tools v0.45.0 h1:18qN3FAooORvApf5XjCXgsuayZOEtXf6JK18I3+ONa8=
|
||||
golang.org/x/tools v0.45.0/go.mod h1:LuUGqqaXcXMEFEruIVJVm5mgDD8vww/z/SR1gQ4uE/0=
|
||||
golang.org/x/tools v0.46.0 h1:7jTurBkPZu4moS/Uy4OQT1M+QBlsj3wejyZwsT8Z7rk=
|
||||
golang.org/x/tools v0.46.0/go.mod h1:FrD85F8l+NWL+9XWBSyVSHO6Ne4jutsfIFba7AWQ5Ys=
|
||||
golang.org/x/tools/go/expect v0.1.1-deprecated h1:jpBZDwmgPhXsKZC6WhL20P4b/wmnpsEAGHaNy0n/rJM=
|
||||
golang.org/x/tools/go/expect v0.1.1-deprecated/go.mod h1:eihoPOH+FgIqa3FpoTwguz/bVUSGBlGQU67vpBeOrBY=
|
||||
golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated h1:1h2MnaIAIXISqTFKdENegdpAgUXz6NrPEsbIeWaBRvM=
|
||||
@@ -1292,10 +1292,10 @@ honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9
|
||||
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||
honnef.co/go/tools v0.7.0 h1:w6WUp1VbkqPEgLz4rkBzH/CSU6HkoqNLp6GstyTx3lU=
|
||||
honnef.co/go/tools v0.7.0/go.mod h1:pm29oPxeP3P82ISxZDgIYeOaf9ta6Pi0EWvCFoLG2vc=
|
||||
modernc.org/cc/v4 v4.28.2 h1:3tQ0lf2ADtoby2EtSP+J7IE2SHwEJdP8ioR59wx7XpY=
|
||||
modernc.org/cc/v4 v4.28.2/go.mod h1:OnovgIhbbMXMu1aISnJ0wvVD1KnW+cAUJkIrAWh+kVI=
|
||||
modernc.org/ccgo/v4 v4.34.2 h1:mxsy2FdrB6+qG3NfXefz1AmWv0ehOSDO4jxgxd7h9yo=
|
||||
modernc.org/ccgo/v4 v4.34.2/go.mod h1:1L7us56+kAKu04p25EATpmBBvhbcqqZ85ibqWVwVgog=
|
||||
modernc.org/cc/v4 v4.28.4 h1:Hd/4Es+MBj+/7hSdZaisNyu6bv3V0Dp2MdllyfqaH+c=
|
||||
modernc.org/cc/v4 v4.28.4/go.mod h1:OnovgIhbbMXMu1aISnJ0wvVD1KnW+cAUJkIrAWh+kVI=
|
||||
modernc.org/ccgo/v4 v4.34.4 h1:OVnSOWQjVKOYkFxoHYB+qQmSHK5gqMqARM+K9DpR/Ws=
|
||||
modernc.org/ccgo/v4 v4.34.4/go.mod h1:qdKqE8FNIYyysougB1RX9MxCzp5oJOcQXSobANJ4TuE=
|
||||
modernc.org/fileutil v1.4.0 h1:j6ZzNTftVS054gi281TyLjHPp6CPHr2KCxEXjEbD6SM=
|
||||
modernc.org/fileutil v1.4.0/go.mod h1:EqdKFDxiByqxLk8ozOxObDSfcVOv/54xDs/DUHdvCUU=
|
||||
modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI=
|
||||
@@ -1304,8 +1304,8 @@ modernc.org/gc/v3 v3.1.3 h1:6QAplYyVO+KdPW3pGnqmJDUxtkec8ooEWvks/hhU3lc=
|
||||
modernc.org/gc/v3 v3.1.3/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY=
|
||||
modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks=
|
||||
modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI=
|
||||
modernc.org/libc v1.72.5 h1:m2OGx9Ser1VvTS4Z9ZJlWs+CBMxutLaTiAWkNz+NB9U=
|
||||
modernc.org/libc v1.72.5/go.mod h1:np0N7KDJ7eUtMZmOqVZNldrZyG+DHLl2B5pg8Hbar3U=
|
||||
modernc.org/libc v1.73.1 h1:HMa/TQJY2Z8ZjqBRbVJMLjBhmxTOwN2aMcxNrT01Dbc=
|
||||
modernc.org/libc v1.73.1/go.mod h1:DXZ3eO8qMCNn2SnmTNCiC71nJ9Rcq3PsnpU6Vc4rWK8=
|
||||
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
|
||||
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
|
||||
modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
|
||||
@@ -1314,8 +1314,8 @@ modernc.org/opt v0.2.0 h1:tGyef5ApycA7FSEOMraay9SaTk5zmbx7Tu+cJs4QKZg=
|
||||
modernc.org/opt v0.2.0/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
|
||||
modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
|
||||
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
|
||||
modernc.org/sqlite v1.51.0 h1:aH/MMSoayAIhozZ7uJbVTT9QO/VhzBf0J9tymmmuC/U=
|
||||
modernc.org/sqlite v1.51.0/go.mod h1:tcNzv5p84E0skkmJn038y+hWJbLQXQqEnQfeh5r2JLM=
|
||||
modernc.org/sqlite v1.52.0 h1:p4dhYh2tXZCiyaqHwRVJDjIGKWyXayiQpThxgDzJaxo=
|
||||
modernc.org/sqlite v1.52.0/go.mod h1:tcNzv5p84E0skkmJn038y+hWJbLQXQqEnQfeh5r2JLM=
|
||||
modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
|
||||
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
|
||||
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
|
||||
|
||||
@@ -166,6 +166,7 @@ func StartHttp(ctx context.Context, storage *bolt.BoltStore, shutdownComplete ch
|
||||
api.HandleFunc("PATCH /share", withPermShare(sharePatchHandler))
|
||||
api.HandleFunc("DELETE /share", withPermShare(shareDeleteHandler))
|
||||
publicApi.HandleFunc("GET /share/info", withOrWithoutUser(shareInfoHandler))
|
||||
publicApi.HandleFunc("PATCH /share/pinnedItems", withPermShare(sharePatchPinnedItemsHandler))
|
||||
publicApi.HandleFunc("GET /share/image", withHashFile(getShareImage))
|
||||
|
||||
// ========================================
|
||||
|
||||
@@ -137,6 +137,8 @@ func withHashFileHelper(fn handleFunc) handleFunc {
|
||||
ShowHidden: link.ShowHidden,
|
||||
HideFileExt: link.HideFileExt,
|
||||
FollowSymlinks: true,
|
||||
ShowPinnedItems: true,
|
||||
ShareHash: hash,
|
||||
}, store.Access, data.shareUser, store.Share)
|
||||
if err != nil {
|
||||
logger.Errorf("error fetching file info for share. hash=%v path=%v error=%v", hash, path, err)
|
||||
|
||||
@@ -15,7 +15,13 @@ import (
|
||||
type pinnedItemPatchRequest struct {
|
||||
Source string `json:"source" validate:"required"`
|
||||
Path string `json:"path" validate:"required"` // scope-relative parent directory
|
||||
Name string `json:"name" validate:"required"` // item basename within path
|
||||
Name string `json:"name" validate:"required"` // item basename within path
|
||||
}
|
||||
|
||||
// sharePinnedItemPatchRequest is the JSON body for PATCH /public/api/share/pinnedItems.
|
||||
type sharePinnedItemPatchRequest struct {
|
||||
Path string `json:"path" validate:"required"` // share-relative parent directory
|
||||
Name string `json:"name" validate:"required"` // item basename within path
|
||||
}
|
||||
|
||||
// scopeRelativeDirToIndexPath maps a scope-relative directory to source and index paths.
|
||||
@@ -39,6 +45,18 @@ func scopeRelativeDirToIndexPath(sourceName, userScope, directoryPath string) (s
|
||||
return source.Path, idx.MakeIndexPath(fullDirPath, true), nil
|
||||
}
|
||||
|
||||
// normalizeShareRelativeDir returns a normalized share-relative directory path key.
|
||||
func normalizeShareRelativeDir(shareRelDir string) (string, error) {
|
||||
if shareRelDir == "" || shareRelDir == "/" {
|
||||
return "/", nil
|
||||
}
|
||||
cleanDir, err := utils.SanitizeUserPath(shareRelDir)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("invalid path: %w", err)
|
||||
}
|
||||
return utils.AddTrailingSlashIfNotExists(cleanDir), nil
|
||||
}
|
||||
|
||||
// pinnedItemAction returns the patch action query param, defaulting to "add".
|
||||
func pinnedItemAction(r *http.Request) string {
|
||||
action := strings.ToLower(strings.TrimSpace(r.URL.Query().Get("action")))
|
||||
|
||||
@@ -700,3 +700,78 @@ func redirectToShare(w http.ResponseWriter, r *http.Request, d *requestContext)
|
||||
http.Redirect(w, r, newURL, http.StatusMovedPermanently)
|
||||
return http.StatusMovedPermanently, nil
|
||||
}
|
||||
|
||||
// publicSharePatchPinnedItemsHandler adds or removes a pinned item on a share link.
|
||||
// @Summary Add or remove a pinned item on a share
|
||||
// @Description Patches one pinned item at a time for share owners. Defaults to add; pass ?action=remove to unpin.
|
||||
// @Tags Shares
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param hash query string true "Share hash"
|
||||
// @Param action query string false "add (default) or remove"
|
||||
// @Param body body sharePinnedItemPatchRequest true "Pinned item"
|
||||
// @Success 204 "No Content"
|
||||
// @Failure 400 {object} map[string]string "Bad Request"
|
||||
// @Failure 403 {object} map[string]string "Forbidden"
|
||||
// @Failure 404 {object} map[string]string "Share not found"
|
||||
// @Failure 500 {object} map[string]string "Internal Server Error"
|
||||
// @Router /public/api/share/pinnedItems [patch]
|
||||
func sharePatchPinnedItemsHandler(w http.ResponseWriter, r *http.Request, d *requestContext) (int, error) {
|
||||
hash := r.URL.Query().Get("hash")
|
||||
if hash == "" {
|
||||
return http.StatusBadRequest, fmt.Errorf("hash is required")
|
||||
}
|
||||
|
||||
link, err := store.Share.GetByHash(hash)
|
||||
if err != nil {
|
||||
return http.StatusNotFound, fmt.Errorf("share hash not found")
|
||||
}
|
||||
|
||||
if d.user.Username == "anonymous" || !link.UserCanEdit(d.user) {
|
||||
return http.StatusForbidden, fmt.Errorf("share pin editing is not allowed for this user")
|
||||
}
|
||||
|
||||
if link.ShareType == "upload" {
|
||||
return http.StatusForbidden, fmt.Errorf("pinning is disabled for upload shares")
|
||||
}
|
||||
|
||||
var body sharePinnedItemPatchRequest
|
||||
if err = json.NewDecoder(r.Body).Decode(&body); err != nil {
|
||||
return http.StatusBadRequest, fmt.Errorf("failed to decode body: %w", err)
|
||||
}
|
||||
defer r.Body.Close()
|
||||
|
||||
action := pinnedItemAction(r)
|
||||
if action != "add" && action != "remove" {
|
||||
return http.StatusBadRequest, fmt.Errorf("action must be add or remove")
|
||||
}
|
||||
|
||||
if body.Path == "" || body.Name == "" {
|
||||
return http.StatusBadRequest, fmt.Errorf("path and name are required")
|
||||
}
|
||||
|
||||
shareRelDir, err := normalizeShareRelativeDir(body.Path)
|
||||
if err != nil {
|
||||
return http.StatusBadRequest, fmt.Errorf("invalid path: %s", body.Path)
|
||||
}
|
||||
|
||||
cleanName, err := utils.SanitizeUserPath(body.Name)
|
||||
if err != nil {
|
||||
return http.StatusBadRequest, fmt.Errorf("invalid name: %s", body.Name)
|
||||
}
|
||||
body.Name = cleanName
|
||||
|
||||
pinned := link.EnsurePinnedItems()
|
||||
switch action {
|
||||
case "add":
|
||||
pinned.Add(shareRelDir, body.Name)
|
||||
case "remove":
|
||||
pinned.Remove(shareRelDir, body.Name)
|
||||
}
|
||||
|
||||
if err := store.Share.Save(link); err != nil {
|
||||
return http.StatusInternalServerError, fmt.Errorf("failed to save share: %w", err)
|
||||
}
|
||||
|
||||
return http.StatusNoContent, nil
|
||||
}
|
||||
+38
-4
@@ -6,6 +6,7 @@ import (
|
||||
"html/template"
|
||||
"io/fs"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
@@ -96,6 +97,17 @@ func handleWithStaticData(w http.ResponseWriter, r *http.Request, d *requestCont
|
||||
shareHash := ""
|
||||
data := make(map[string]interface{})
|
||||
disableNavButtons := config.Frontend.DisableNavButtons
|
||||
if d.share != nil {
|
||||
shareHash = d.share.Hash
|
||||
}
|
||||
if shareHash == "" {
|
||||
shareHash = icons.ShareHashFromHTTPPath(r.URL.Path, config.Server.BaseURL)
|
||||
}
|
||||
if shareHash != "" && d.share == nil && store != nil {
|
||||
if link, err := store.Share.GetByHash(shareHash); err == nil {
|
||||
d.share = link
|
||||
}
|
||||
}
|
||||
if d.share != nil {
|
||||
if d.share.Favicon != "" {
|
||||
if strings.HasPrefix(d.share.Favicon, "http") {
|
||||
@@ -124,7 +136,6 @@ func handleWithStaticData(w http.ResponseWriter, r *http.Request, d *requestCont
|
||||
if shareFavicon != "" {
|
||||
favicon = shareFavicon
|
||||
}
|
||||
shareHash = d.share.Hash
|
||||
}
|
||||
// Set login icon URL
|
||||
loginIcon := staticURL + "/loginIcon"
|
||||
@@ -162,6 +173,20 @@ func handleWithStaticData(w http.ResponseWriter, r *http.Request, d *requestCont
|
||||
pwaIcon256 := staticURL + "/icons/pwa-icon-256.png"
|
||||
pwaIcon512 := staticURL + "/icons/pwa-icon-512.png"
|
||||
|
||||
manifestURL := staticURL + "/site.webmanifest"
|
||||
if shareHash != "" {
|
||||
pwaStartURL := config.Server.BaseURL + "public/share/" + shareHash + "/"
|
||||
query := url.Values{}
|
||||
query.Set("start", pwaStartURL)
|
||||
if title != config.Frontend.Name {
|
||||
query.Set("name", title)
|
||||
}
|
||||
if description != config.Frontend.Description {
|
||||
query.Set("description", description)
|
||||
}
|
||||
manifestURL = staticURL + "/site.webmanifest?" + query.Encode()
|
||||
}
|
||||
|
||||
data["htmlVars"] = map[string]interface{}{
|
||||
"title": title,
|
||||
"customCSS": template.CSS(config.Frontend.Styling.CustomCSSRaw),
|
||||
@@ -183,7 +208,7 @@ func handleWithStaticData(w http.ResponseWriter, r *http.Request, d *requestCont
|
||||
"pwaIcon192": pwaIcon192,
|
||||
"pwaIcon256": pwaIcon256,
|
||||
"pwaIcon512": pwaIcon512,
|
||||
"manifestURL": staticURL + "/site.webmanifest",
|
||||
"manifestURL": manifestURL,
|
||||
}
|
||||
|
||||
data["globalVars"] = map[string]interface{}{
|
||||
@@ -267,12 +292,21 @@ func setContentType(w http.ResponseWriter, path string) {
|
||||
}
|
||||
}
|
||||
|
||||
// manifestHandler serves the cached PWA manifest
|
||||
// manifestHandler serves the cached PWA manifest, optionally scoped to a public share.
|
||||
func manifestHandler(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/manifest+json")
|
||||
w.Header().Set("Cache-Control", "public, max-age=3600") // Cache for 1 hour
|
||||
|
||||
if err := json.NewEncoder(w).Encode(icons.CachedManifest); err != nil {
|
||||
manifest := icons.CachedManifest
|
||||
if startURL := r.URL.Query().Get("start"); startURL != "" {
|
||||
name := r.URL.Query().Get("name")
|
||||
description := r.URL.Query().Get("description")
|
||||
if shareManifest, ok := icons.ManifestForShare(config.Server.BaseURL, startURL, name, description); ok {
|
||||
manifest = shareManifest
|
||||
}
|
||||
}
|
||||
|
||||
if err := json.NewEncoder(w).Encode(manifest); err != nil {
|
||||
http.Error(w, "Failed to serve manifest", http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,8 @@ import (
|
||||
type PWAManifest struct {
|
||||
Name string `json:"name"`
|
||||
ShortName string `json:"short_name"`
|
||||
ID string `json:"id"`
|
||||
Scope string `json:"scope"`
|
||||
Icons []PWAIcon `json:"icons"`
|
||||
StartURL string `json:"start_url"`
|
||||
Display string `json:"display"`
|
||||
@@ -40,6 +42,8 @@ func generatePWAManifest(name, description, baseURL, themeColor, pwaIcon192, pwa
|
||||
return PWAManifest{
|
||||
Name: name,
|
||||
ShortName: shortName,
|
||||
ID: baseURL,
|
||||
Scope: baseURL,
|
||||
StartURL: baseURL,
|
||||
Display: "standalone",
|
||||
BackgroundColor: "#ffffff",
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
package icons
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/gtsteffaniak/filebrowser/backend/common/utils"
|
||||
)
|
||||
|
||||
const maxManifestTextLen = 256
|
||||
|
||||
func isValidShareHash(hash string) bool {
|
||||
if len(hash) < 8 || len(hash) > 64 {
|
||||
return false
|
||||
}
|
||||
if strings.Contains(hash, "..") || strings.Contains(hash, "/") {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// ShareRootFromStartURL validates a share PWA start URL and returns the share root
|
||||
// used for manifest scope/id plus the normalized start URL.
|
||||
func ShareRootFromStartURL(baseURL, startURL string) (shareRoot, normalizedStart string, ok bool) {
|
||||
if startURL == "" || !strings.HasPrefix(startURL, baseURL) {
|
||||
return "", "", false
|
||||
}
|
||||
|
||||
rel := strings.TrimPrefix(startURL, baseURL)
|
||||
if !strings.HasPrefix(rel, "public/share/") {
|
||||
return "", "", false
|
||||
}
|
||||
|
||||
rest := strings.TrimPrefix(rel, "public/share/")
|
||||
hash, subPath, _ := strings.Cut(rest, "/")
|
||||
if !isValidShareHash(hash) {
|
||||
return "", "", false
|
||||
}
|
||||
if subPath != "" {
|
||||
if _, err := utils.SanitizeUserPath(subPath); err != nil {
|
||||
return "", "", false
|
||||
}
|
||||
}
|
||||
|
||||
shareRoot = baseURL + "public/share/" + hash + "/"
|
||||
normalizedStart = startURL
|
||||
if !strings.HasPrefix(normalizedStart, shareRoot) && normalizedStart != strings.TrimSuffix(shareRoot, "/") {
|
||||
return "", "", false
|
||||
}
|
||||
if normalizedStart == baseURL+"public/share/"+hash {
|
||||
normalizedStart = shareRoot
|
||||
}
|
||||
|
||||
return shareRoot, normalizedStart, true
|
||||
}
|
||||
|
||||
// ShareHashFromHTTPPath extracts a share hash from common FileBrowser share routes.
|
||||
func ShareHashFromHTTPPath(path, baseURL string) string {
|
||||
prefixes := []string{
|
||||
strings.TrimSuffix(baseURL, "/") + "/public/share/",
|
||||
"/public/share/",
|
||||
"/share/",
|
||||
}
|
||||
for _, prefix := range prefixes {
|
||||
if !strings.HasPrefix(path, prefix) {
|
||||
continue
|
||||
}
|
||||
rest := strings.TrimPrefix(path, prefix)
|
||||
hash, _, _ := strings.Cut(rest, "/")
|
||||
if isValidShareHash(hash) {
|
||||
return hash
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func sanitizeManifestText(value string) string {
|
||||
if len(value) > maxManifestTextLen {
|
||||
return value[:maxManifestTextLen]
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
// ManifestForShare returns a manifest scoped to a single public share link.
|
||||
func ManifestForShare(baseURL, startURL, name, description string) (PWAManifest, bool) {
|
||||
shareRoot, normalizedStart, ok := ShareRootFromStartURL(baseURL, startURL)
|
||||
if !ok {
|
||||
return PWAManifest{}, false
|
||||
}
|
||||
|
||||
manifest := CachedManifest
|
||||
manifest.StartURL = normalizedStart
|
||||
manifest.Scope = shareRoot
|
||||
manifest.ID = shareRoot
|
||||
|
||||
if name = sanitizeManifestText(strings.TrimSpace(name)); name != "" {
|
||||
manifest.Name = name
|
||||
shortName := name
|
||||
if len(shortName) > 12 {
|
||||
shortName = shortName[:12]
|
||||
}
|
||||
manifest.ShortName = shortName
|
||||
}
|
||||
if description = sanitizeManifestText(strings.TrimSpace(description)); description != "" {
|
||||
manifest.Description = description
|
||||
}
|
||||
|
||||
return manifest, true
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
package icons
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestShareRootFromStartURL(t *testing.T) {
|
||||
baseURL := "/testing/"
|
||||
startURL := "/testing/public/share/abc123XYZ-_/"
|
||||
shareRoot, normalizedStart, ok := ShareRootFromStartURL(baseURL, startURL)
|
||||
if !ok {
|
||||
t.Fatal("expected valid share start URL")
|
||||
}
|
||||
if shareRoot != "/testing/public/share/abc123XYZ-_/" {
|
||||
t.Fatalf("unexpected share root: %q", shareRoot)
|
||||
}
|
||||
if normalizedStart != startURL {
|
||||
t.Fatalf("unexpected normalized start: %q", normalizedStart)
|
||||
}
|
||||
}
|
||||
|
||||
func TestShareRootFromStartURLRejectsTraversal(t *testing.T) {
|
||||
baseURL := "/testing/"
|
||||
startURL := "/testing/public/share/abc123XYZ-_/../secret/"
|
||||
_, _, ok := ShareRootFromStartURL(baseURL, startURL)
|
||||
if ok {
|
||||
t.Fatal("expected traversal path to be rejected")
|
||||
}
|
||||
}
|
||||
|
||||
func TestShareHashFromHTTPPath(t *testing.T) {
|
||||
baseURL := "/testing/"
|
||||
paths := []string{
|
||||
"/testing/public/share/hash1234567890abcdef/",
|
||||
"/public/share/hash1234567890abcdef/files",
|
||||
"/share/hash1234567890abcdef/",
|
||||
}
|
||||
for _, path := range paths {
|
||||
hash := ShareHashFromHTTPPath(path, baseURL)
|
||||
if hash != "hash1234567890abcdef" {
|
||||
t.Fatalf("path %q: got hash %q", path, hash)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestShareHashFromHTTPPathRejectsEmbeddedPrefix(t *testing.T) {
|
||||
baseURL := "/testing/"
|
||||
path := "/evil/route/share/hash1234567890abcdef/"
|
||||
hash := ShareHashFromHTTPPath(path, baseURL)
|
||||
if hash != "" {
|
||||
t.Fatalf("expected empty hash for non-prefixed path, got %q", hash)
|
||||
}
|
||||
}
|
||||
|
||||
func TestManifestForShare(t *testing.T) {
|
||||
CachedManifest = PWAManifest{
|
||||
Name: "FileBrowser Quantum",
|
||||
ShortName: "FBQ",
|
||||
StartURL: "/testing/",
|
||||
Scope: "/testing/",
|
||||
ID: "/testing/",
|
||||
}
|
||||
|
||||
manifest, ok := ManifestForShare(
|
||||
"/testing/",
|
||||
"/testing/public/share/hash1234567890abcdef/",
|
||||
"My Share",
|
||||
"Shared files",
|
||||
)
|
||||
if !ok {
|
||||
t.Fatal("expected share manifest")
|
||||
}
|
||||
if manifest.StartURL != "/testing/public/share/hash1234567890abcdef/" {
|
||||
t.Fatalf("unexpected start_url: %q", manifest.StartURL)
|
||||
}
|
||||
if manifest.Scope != "/testing/public/share/hash1234567890abcdef/" {
|
||||
t.Fatalf("unexpected scope: %q", manifest.Scope)
|
||||
}
|
||||
if manifest.Name != "My Share" {
|
||||
t.Fatalf("unexpected name: %q", manifest.Name)
|
||||
}
|
||||
}
|
||||
@@ -95,12 +95,6 @@ func (s *Scanner) executeScan() {
|
||||
s.fullScanCounter = 0
|
||||
}
|
||||
})
|
||||
scanMode := "full"
|
||||
if quick {
|
||||
scanMode = "quick"
|
||||
}
|
||||
logger.Debugf("[%s] scanner [%s] starting %s scan (full/quick cadence step %d of 5)", s.idx.Name, s.scanPath, scanMode, pre+1)
|
||||
|
||||
s.runIndexing(quick)
|
||||
s.updateSchedule()
|
||||
}
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
package iteminfo
|
||||
|
||||
import "strings"
|
||||
|
||||
type namePatternMode int
|
||||
|
||||
const (
|
||||
patternLazyDefault namePatternMode = iota
|
||||
patternQuotedLiteral
|
||||
patternUserGlob
|
||||
)
|
||||
|
||||
func escapeGlobLiteral(s string) string {
|
||||
var b strings.Builder
|
||||
for _, r := range s {
|
||||
switch r {
|
||||
case '*', '?', '[', '\\':
|
||||
b.WriteByte('\\')
|
||||
b.WriteRune(r)
|
||||
default:
|
||||
b.WriteRune(r)
|
||||
}
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func wrapGlobPattern(s string) string {
|
||||
return "*" + s + "*"
|
||||
}
|
||||
|
||||
// BuildNameGlobPattern converts a search term into a SQLite GLOB pattern for indexed file names.
|
||||
func BuildNameGlobPattern(term string, quoted, useWildcard, exactCase bool) string {
|
||||
if term == "" {
|
||||
return ""
|
||||
}
|
||||
|
||||
var mode namePatternMode
|
||||
switch {
|
||||
case useWildcard:
|
||||
mode = patternUserGlob
|
||||
case quoted:
|
||||
mode = patternQuotedLiteral
|
||||
default:
|
||||
mode = patternLazyDefault
|
||||
}
|
||||
|
||||
var pattern string
|
||||
switch mode {
|
||||
case patternUserGlob:
|
||||
pattern = term
|
||||
case patternQuotedLiteral:
|
||||
pattern = wrapGlobPattern(escapeGlobLiteral(term))
|
||||
case patternLazyDefault:
|
||||
parts := strings.Fields(term)
|
||||
if len(parts) == 0 {
|
||||
return ""
|
||||
}
|
||||
escaped := make([]string, len(parts))
|
||||
for i, p := range parts {
|
||||
escaped[i] = escapeGlobLiteral(p)
|
||||
}
|
||||
pattern = wrapGlobPattern(strings.Join(escaped, "*"))
|
||||
}
|
||||
|
||||
if !exactCase {
|
||||
pattern = strings.ToLower(pattern)
|
||||
}
|
||||
return pattern
|
||||
}
|
||||
|
||||
// NameGlobPatternsForSearch builds SQLite name GLOB patterns for the given search options.
|
||||
func NameGlobPatternsForSearch(opts SearchOptions, useWildcard, largest bool) []string {
|
||||
if largest || len(opts.Terms) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
exactCase := opts.Conditions["exact"]
|
||||
var patterns []string
|
||||
for _, t := range opts.Terms {
|
||||
if t == "" {
|
||||
continue
|
||||
}
|
||||
p := BuildNameGlobPattern(t, opts.Quoted, useWildcard, exactCase)
|
||||
if p != "" {
|
||||
patterns = append(patterns, p)
|
||||
}
|
||||
}
|
||||
if len(patterns) == 0 {
|
||||
return nil
|
||||
}
|
||||
return patterns
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package iteminfo
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestBuildNameGlobPattern(t *testing.T) {
|
||||
tests := []struct {
|
||||
term string
|
||||
quoted bool
|
||||
useWildcard bool
|
||||
exactCase bool
|
||||
want string
|
||||
}{
|
||||
{"cons", false, false, false, "*cons*"},
|
||||
{"new folder", false, false, false, "*new*folder*"},
|
||||
{"new folder", true, false, false, "*new folder*"},
|
||||
{"new*folder", false, true, false, "new*folder"},
|
||||
{"Blade Runner", false, false, false, "*blade*runner*"},
|
||||
{"Blade Runner", false, false, true, "*Blade*Runner*"},
|
||||
{"test[1]", false, false, false, "*test\\[1]*"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
got := BuildNameGlobPattern(tt.term, tt.quoted, tt.useWildcard, tt.exactCase)
|
||||
if got != tt.want {
|
||||
t.Errorf("BuildNameGlobPattern(%q, quoted=%v, wildcard=%v, exact=%v) = %q, want %q",
|
||||
tt.term, tt.quoted, tt.useWildcard, tt.exactCase, got, tt.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestNameGlobPatternsForSearch(t *testing.T) {
|
||||
opts := SearchOptions{
|
||||
Terms: []string{"house", "car"},
|
||||
}
|
||||
patterns := NameGlobPatternsForSearch(opts, false, false)
|
||||
if len(patterns) != 2 || patterns[0] != "*house*" || patterns[1] != "*car*" {
|
||||
t.Fatalf("unexpected patterns: %#v", patterns)
|
||||
}
|
||||
|
||||
optsQuoted := SearchOptions{
|
||||
Terms: []string{"new folder"},
|
||||
Quoted: true,
|
||||
}
|
||||
patterns = NameGlobPatternsForSearch(optsQuoted, false, false)
|
||||
if len(patterns) != 1 || patterns[0] != "*new folder*" {
|
||||
t.Fatalf("unexpected quoted patterns: %#v", patterns)
|
||||
}
|
||||
|
||||
optsLargest := SearchOptions{Terms: []string{"test"}}
|
||||
if p := NameGlobPatternsForSearch(optsLargest, false, true); p != nil {
|
||||
t.Fatalf("largest mode should return nil patterns, got %#v", p)
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,8 @@ type SearchOptions struct {
|
||||
// MatchAllTerms, when true and non-wildcard search, an item must contain every term (AND). When false (default), any term matches (OR).
|
||||
// For name GLOB wildcard search, name must match every pattern when true; when false, patterns are OR-combined in SQL.
|
||||
MatchAllTerms bool
|
||||
// Quoted is true when the search query was a double-quoted phrase; spaces are kept literal in name matching.
|
||||
Quoted bool
|
||||
}
|
||||
|
||||
// BuildSearchOptionsFromQuery merges optional repeated literal terms (HTTP "terms" parameters) with structured filter text ("query" prefix).
|
||||
@@ -119,6 +121,7 @@ func ParseSearch(value string) SearchOptions {
|
||||
unique = strings.TrimSuffix(unique, "\"")
|
||||
|
||||
opts.Terms = []string{unique}
|
||||
opts.Quoted = true
|
||||
return opts
|
||||
}
|
||||
value = strings.TrimSpace(value)
|
||||
|
||||
+12
-27
@@ -55,10 +55,11 @@ func (idx *Index) SearchParsed(baseOpts iteminfo.SearchOptions, scope string, so
|
||||
searchOptions.Terms = []string{""}
|
||||
}
|
||||
|
||||
nameGlobPatterns := nameGlobPatternsForSearch(searchOptions, useWildcard, largest)
|
||||
globAnd := useWildcard && searchOptions.MatchAllTerms
|
||||
nameGlobPatterns := iteminfo.NameGlobPatternsForSearch(searchOptions, useWildcard, largest)
|
||||
caseExact := searchOptions.Conditions["exact"]
|
||||
globAnd := len(nameGlobPatterns) > 0 && searchOptions.MatchAllTerms
|
||||
|
||||
rows, err := idx.db.SearchItems(idx.Name, scope, largest, nameGlobPatterns, globAnd)
|
||||
rows, err := idx.db.SearchItems(idx.Name, scope, largest, nameGlobPatterns, globAnd, caseExact)
|
||||
if err != nil {
|
||||
return []*SearchResult{}
|
||||
}
|
||||
@@ -95,7 +96,7 @@ func (idx *Index) SearchParsed(baseOpts iteminfo.SearchOptions, scope string, so
|
||||
HasPreview: hasPreview,
|
||||
}
|
||||
|
||||
matches := itemMatchesSearchFilters(item, isDir, searchOptions, largest, useWildcard, nameGlobPatterns)
|
||||
matches := itemMatchesSearchFilters(item, isDir, searchOptions, largest, nameGlobPatterns)
|
||||
|
||||
if matches {
|
||||
resType := mimeType
|
||||
@@ -127,7 +128,7 @@ func (idx *Index) SearchParsed(baseOpts iteminfo.SearchOptions, scope string, so
|
||||
return sortedKeys
|
||||
}
|
||||
|
||||
func itemMatchesSearchFilters(item iteminfo.ItemInfo, isDir bool, searchOptions iteminfo.SearchOptions, largest, useWildcard bool, nameGlobPatterns []string) bool {
|
||||
func itemMatchesSearchFilters(item iteminfo.ItemInfo, isDir bool, searchOptions iteminfo.SearchOptions, largest bool, nameGlobPatterns []string) bool {
|
||||
if largest {
|
||||
largerThan := int64(searchOptions.LargerThan) * 1024 * 1024
|
||||
sizeMatches := largerThan == 0 || item.Size > largerThan
|
||||
@@ -141,7 +142,7 @@ func itemMatchesSearchFilters(item iteminfo.ItemInfo, isDir bool, searchOptions
|
||||
dateMatches := searchDateMatches(item.ModTime.Unix(), searchOptions)
|
||||
return sizeMatches && typeMatches && dateMatches
|
||||
}
|
||||
if useWildcard && len(nameGlobPatterns) > 0 {
|
||||
if len(nameGlobPatterns) > 0 {
|
||||
return item.MatchesSearchAuxiliaryFilters(searchOptions)
|
||||
}
|
||||
if searchOptions.MatchAllTerms {
|
||||
@@ -168,23 +169,6 @@ func itemMatchesSearchFilters(item iteminfo.ItemInfo, isDir bool, searchOptions
|
||||
return false
|
||||
}
|
||||
|
||||
// nameGlobPatternsForSearch builds non-empty parsed terms for SQLite name GLOB OR clauses.
|
||||
func nameGlobPatternsForSearch(opts iteminfo.SearchOptions, useWildcard, largest bool) []string {
|
||||
if largest || !useWildcard || len(opts.Terms) == 0 {
|
||||
return nil
|
||||
}
|
||||
var patterns []string
|
||||
for _, t := range opts.Terms {
|
||||
if t != "" {
|
||||
patterns = append(patterns, t)
|
||||
}
|
||||
}
|
||||
if len(patterns) == 0 {
|
||||
return nil
|
||||
}
|
||||
return patterns
|
||||
}
|
||||
|
||||
func searchDateMatches(modUnix int64, opts iteminfo.SearchOptions) bool {
|
||||
if opts.ModifiedNewerThan > 0 && modUnix < opts.ModifiedNewerThan {
|
||||
return false
|
||||
@@ -234,10 +218,11 @@ func SearchMultiSourcesParsed(baseOpts iteminfo.SearchOptions, sources []string,
|
||||
searchOptions.Terms = []string{""}
|
||||
}
|
||||
|
||||
nameGlobPatterns := nameGlobPatternsForSearch(searchOptions, useWildcard, largest)
|
||||
globAnd := useWildcard && searchOptions.MatchAllTerms
|
||||
nameGlobPatterns := iteminfo.NameGlobPatternsForSearch(searchOptions, useWildcard, largest)
|
||||
caseExact := searchOptions.Conditions["exact"]
|
||||
globAnd := len(nameGlobPatterns) > 0 && searchOptions.MatchAllTerms
|
||||
|
||||
rows, err := db.SearchItemsMultiSource(sources, normalizedScopes, largest, nameGlobPatterns, globAnd)
|
||||
rows, err := db.SearchItemsMultiSource(sources, normalizedScopes, largest, nameGlobPatterns, globAnd, caseExact)
|
||||
if err != nil {
|
||||
return []*SearchResult{}
|
||||
}
|
||||
@@ -275,7 +260,7 @@ func SearchMultiSourcesParsed(baseOpts iteminfo.SearchOptions, sources []string,
|
||||
HasPreview: hasPreview,
|
||||
}
|
||||
|
||||
matches := itemMatchesSearchFilters(item, isDir, searchOptions, largest, useWildcard, nameGlobPatterns)
|
||||
matches := itemMatchesSearchFilters(item, isDir, searchOptions, largest, nameGlobPatterns)
|
||||
|
||||
if matches {
|
||||
resType := mimeType
|
||||
|
||||
@@ -116,6 +116,14 @@ func TestParseSearch(t *testing.T) {
|
||||
Terms: []string{"thisfile"},
|
||||
},
|
||||
},
|
||||
{
|
||||
input: `"new folder"`,
|
||||
want: iteminfo.SearchOptions{
|
||||
Conditions: map[string]bool{"exact": false},
|
||||
Terms: []string{"new folder"},
|
||||
Quoted: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
input: "type:olderThan=2024-06-01 type:newerThan=2024-01-01 findme",
|
||||
want: iteminfo.SearchOptions{
|
||||
@@ -266,6 +274,13 @@ func TestSearchIndexes(t *testing.T) {
|
||||
{ItemInfo: iteminfo.ItemInfo{Name: "toggle.gif", Size: 65536, Type: "image/gif"}},
|
||||
},
|
||||
},
|
||||
"/media/": {
|
||||
ItemInfo: iteminfo.ItemInfo{Name: "media", Type: "directory"},
|
||||
Files: []iteminfo.ExtendedItemInfo{
|
||||
{ItemInfo: iteminfo.ItemInfo{Name: "Blade.Runner.mkv", Type: "video"}},
|
||||
{ItemInfo: iteminfo.ItemInfo{Name: "new.folder.txt", Type: "text"}},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Insert all directories and files into database
|
||||
@@ -462,6 +477,65 @@ func TestSearchIndexes(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
search: "Blade Runner",
|
||||
scope: "/media/",
|
||||
expectedResult: []*SearchResult{
|
||||
{
|
||||
Path: "/media/Blade.Runner.mkv",
|
||||
Type: "video",
|
||||
Size: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
search: "new folder",
|
||||
scope: "/media/",
|
||||
expectedResult: []*SearchResult{
|
||||
{
|
||||
Path: "/media/new.folder.txt",
|
||||
Type: "text",
|
||||
Size: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
search: "new folder",
|
||||
scope: "/",
|
||||
expectedResult: []*SearchResult{
|
||||
{
|
||||
Path: "/new+folder/",
|
||||
Type: "directory",
|
||||
Size: 0,
|
||||
},
|
||||
{
|
||||
Path: "/media/new.folder.txt",
|
||||
Type: "text",
|
||||
Size: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
search: `"new folder"`,
|
||||
scope: "/media/",
|
||||
expectedResult: []*SearchResult{},
|
||||
},
|
||||
{
|
||||
search: "VIDEO",
|
||||
scope: "/new/test/",
|
||||
expectedResult: []*SearchResult{
|
||||
{
|
||||
Path: "/new/test/video.MP4",
|
||||
Type: "video",
|
||||
Size: 0,
|
||||
},
|
||||
{
|
||||
Path: "/new/test/video.mp4",
|
||||
Type: "video",
|
||||
Size: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
@@ -481,6 +555,59 @@ func TestSearchIndexes(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestSearchUserGlob(t *testing.T) {
|
||||
if indexDB == nil {
|
||||
var err error
|
||||
indexDB, _, err = dbsql.NewIndexDB("test_search_glob", "OFF", 1000, 32, false)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create test database: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
index := Index{
|
||||
Source: settings.Source{
|
||||
Name: "test_search_glob",
|
||||
Path: "/mock/path",
|
||||
},
|
||||
db: indexDB,
|
||||
mock: true,
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
directories := map[string]*iteminfo.FileInfo{
|
||||
"/media/": {
|
||||
ItemInfo: iteminfo.ItemInfo{Name: "media", Type: "directory"},
|
||||
Files: []iteminfo.ExtendedItemInfo{
|
||||
{ItemInfo: iteminfo.ItemInfo{Name: "NEW-folder", Type: "text"}},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for path, dirInfo := range directories {
|
||||
dirInfo.Path = path
|
||||
if dirInfo.ModTime.IsZero() {
|
||||
dirInfo.ModTime = now
|
||||
}
|
||||
_ = index.db.InsertItem("test_search_glob", path, dirInfo)
|
||||
for _, file := range dirInfo.Files {
|
||||
filePath := strings.TrimSuffix(path, "/") + "/" + file.Name
|
||||
fileInfo := &iteminfo.FileInfo{
|
||||
Path: filePath,
|
||||
ItemInfo: file.ItemInfo,
|
||||
}
|
||||
if fileInfo.ModTime.IsZero() {
|
||||
fileInfo.ModTime = now
|
||||
}
|
||||
_ = index.db.InsertItem("test_search_glob", filePath, fileInfo)
|
||||
}
|
||||
}
|
||||
|
||||
result := index.Search("new*folder", "/media/", "", false, DefaultSearchResults, 0, 0, true)
|
||||
if len(result) != 1 || result[0].Path != "/media/NEW-folder" {
|
||||
t.Fatalf("expected new*folder to match NEW-folder, got %#v", result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSearchLargestModeExcludesRoot(t *testing.T) {
|
||||
// Initialize the database if not already done
|
||||
if indexDB == nil {
|
||||
|
||||
@@ -4630,6 +4630,86 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/public/api/share/pinnedItems": {
|
||||
"patch": {
|
||||
"description": "Patches one pinned item at a time for share owners. Defaults to add; pass ?action=remove to unpin.",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Shares"
|
||||
],
|
||||
"summary": "Add or remove a pinned item on a share",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Share hash",
|
||||
"name": "hash",
|
||||
"in": "query",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "add (default) or remove",
|
||||
"name": "action",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"description": "Pinned item",
|
||||
"name": "body",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/http.sharePinnedItemPatchRequest"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"204": {
|
||||
"description": "No Content"
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad Request",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "Forbidden",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Share not found",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"definitions": {
|
||||
@@ -4923,6 +5003,9 @@ const docTemplate = `{
|
||||
"perUserDownloadLimit": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"pinnedItems": {
|
||||
"$ref": "#/definitions/share.PinnedItems"
|
||||
},
|
||||
"quickDownload": {
|
||||
"type": "boolean"
|
||||
},
|
||||
@@ -5119,6 +5202,23 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"http.sharePinnedItemPatchRequest": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"name",
|
||||
"path"
|
||||
],
|
||||
"properties": {
|
||||
"name": {
|
||||
"description": "item basename within path",
|
||||
"type": "string"
|
||||
},
|
||||
"path": {
|
||||
"description": "share-relative parent directory",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"http.unarchiveRequest": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -7283,6 +7383,9 @@ const docTemplate = `{
|
||||
"perUserDownloadLimit": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"pinnedItems": {
|
||||
"$ref": "#/definitions/share.PinnedItems"
|
||||
},
|
||||
"quickDownload": {
|
||||
"type": "boolean"
|
||||
},
|
||||
@@ -7343,6 +7446,15 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"share.PinnedItems": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"users.AuthToken": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
||||
@@ -4619,6 +4619,86 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/public/api/share/pinnedItems": {
|
||||
"patch": {
|
||||
"description": "Patches one pinned item at a time for share owners. Defaults to add; pass ?action=remove to unpin.",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Shares"
|
||||
],
|
||||
"summary": "Add or remove a pinned item on a share",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Share hash",
|
||||
"name": "hash",
|
||||
"in": "query",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "add (default) or remove",
|
||||
"name": "action",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"description": "Pinned item",
|
||||
"name": "body",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/http.sharePinnedItemPatchRequest"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"204": {
|
||||
"description": "No Content"
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad Request",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "Forbidden",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Share not found",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"definitions": {
|
||||
@@ -4912,6 +4992,9 @@
|
||||
"perUserDownloadLimit": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"pinnedItems": {
|
||||
"$ref": "#/definitions/share.PinnedItems"
|
||||
},
|
||||
"quickDownload": {
|
||||
"type": "boolean"
|
||||
},
|
||||
@@ -5108,6 +5191,23 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"http.sharePinnedItemPatchRequest": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"name",
|
||||
"path"
|
||||
],
|
||||
"properties": {
|
||||
"name": {
|
||||
"description": "item basename within path",
|
||||
"type": "string"
|
||||
},
|
||||
"path": {
|
||||
"description": "share-relative parent directory",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"http.unarchiveRequest": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -7272,6 +7372,9 @@
|
||||
"perUserDownloadLimit": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"pinnedItems": {
|
||||
"$ref": "#/definitions/share.PinnedItems"
|
||||
},
|
||||
"quickDownload": {
|
||||
"type": "boolean"
|
||||
},
|
||||
@@ -7332,6 +7435,15 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"share.PinnedItems": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"users.AuthToken": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
||||
@@ -193,6 +193,8 @@ definitions:
|
||||
type: boolean
|
||||
perUserDownloadLimit:
|
||||
type: boolean
|
||||
pinnedItems:
|
||||
$ref: '#/definitions/share.PinnedItems'
|
||||
quickDownload:
|
||||
type: boolean
|
||||
shareTheme:
|
||||
@@ -339,6 +341,18 @@ definitions:
|
||||
- path
|
||||
- source
|
||||
type: object
|
||||
http.sharePinnedItemPatchRequest:
|
||||
properties:
|
||||
name:
|
||||
description: item basename within path
|
||||
type: string
|
||||
path:
|
||||
description: share-relative parent directory
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
- path
|
||||
type: object
|
||||
http.unarchiveRequest:
|
||||
properties:
|
||||
deleteAfter:
|
||||
@@ -1967,6 +1981,8 @@ definitions:
|
||||
type: string
|
||||
perUserDownloadLimit:
|
||||
type: boolean
|
||||
pinnedItems:
|
||||
$ref: '#/definitions/share.PinnedItems'
|
||||
quickDownload:
|
||||
type: boolean
|
||||
shareTheme:
|
||||
@@ -2013,6 +2029,12 @@ definitions:
|
||||
"gallery"'
|
||||
type: string
|
||||
type: object
|
||||
share.PinnedItems:
|
||||
additionalProperties:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
type: object
|
||||
users.AuthToken:
|
||||
properties:
|
||||
Permissions:
|
||||
@@ -5587,4 +5609,58 @@ paths:
|
||||
summary: Get share information by hash
|
||||
tags:
|
||||
- Shares
|
||||
/public/api/share/pinnedItems:
|
||||
patch:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Patches one pinned item at a time for share owners. Defaults to
|
||||
add; pass ?action=remove to unpin.
|
||||
parameters:
|
||||
- description: Share hash
|
||||
in: query
|
||||
name: hash
|
||||
required: true
|
||||
type: string
|
||||
- description: add (default) or remove
|
||||
in: query
|
||||
name: action
|
||||
type: string
|
||||
- description: Pinned item
|
||||
in: body
|
||||
name: body
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/http.sharePinnedItemPatchRequest'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"204":
|
||||
description: No Content
|
||||
"400":
|
||||
description: Bad Request
|
||||
schema:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
"403":
|
||||
description: Forbidden
|
||||
schema:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
"404":
|
||||
description: Share not found
|
||||
schema:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
"500":
|
||||
description: Internal Server Error
|
||||
schema:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
summary: Add or remove a pinned item on a share
|
||||
tags:
|
||||
- Shares
|
||||
swagger: "2.0"
|
||||
@@ -5,6 +5,7 @@ import pluginVue from "eslint-plugin-vue";
|
||||
import vueI18n from '@intlify/eslint-plugin-vue-i18n';
|
||||
import vueParser from "vue-eslint-parser";
|
||||
import globals from "globals";
|
||||
import security from "eslint-plugin-security";
|
||||
|
||||
export default defineConfig(
|
||||
{
|
||||
@@ -12,7 +13,7 @@ export default defineConfig(
|
||||
"**/dist/**",
|
||||
"**/node_modules/**",
|
||||
"**/public/**",
|
||||
"**/i18n/is.json",
|
||||
"**/*.json",
|
||||
],
|
||||
},
|
||||
|
||||
@@ -21,6 +22,7 @@ export default defineConfig(
|
||||
...tseslint.configs.recommended,
|
||||
...pluginVue.configs["flat/essential"],
|
||||
...vueI18n.configs.recommended,
|
||||
security.configs.recommended,
|
||||
|
||||
// i18n
|
||||
{
|
||||
@@ -75,8 +77,13 @@ export default defineConfig(
|
||||
"prefer-template": "warn",
|
||||
"@typescript-eslint/consistent-type-definitions": "warn",
|
||||
"@typescript-eslint/prefer-optional-chain": "warn",
|
||||
"@typescript-eslint/no-floating-promises": "error",
|
||||
"@typescript-eslint/no-unnecessary-condition": "off", // this one is useful, but is pretty noisy and found lot of false positives
|
||||
"@typescript-eslint/no-dynamic-delete": "warn",
|
||||
"@typescript-eslint/no-misused-promises": "error",
|
||||
"prefer-object-has-own": "error",
|
||||
"no-prototype-builtins": "error",
|
||||
"no-implied-eval": "error",
|
||||
"no-restricted-globals": [
|
||||
"error",
|
||||
{ name: "isNaN", message: "Use Number.isNaN instead." },
|
||||
@@ -118,6 +125,20 @@ export default defineConfig(
|
||||
"vue/require-v-for-key": "error",
|
||||
"vue/no-reserved-component-names": "off",
|
||||
"vue/no-unused-components": "warn",
|
||||
//"vue/no-v-html": "warn",
|
||||
"vue/no-v-text-v-html-on-component": "warn",
|
||||
},
|
||||
},
|
||||
// Relax the rules a bit for tests files
|
||||
{
|
||||
files: ["tests/**/*.ts", "**/*.spec.ts", "**/*.test.ts"],
|
||||
rules: {
|
||||
"preserve-caught-error": "off",
|
||||
"no-empty-pattern": "off",
|
||||
"@typescript-eslint/no-explicit-any": "off",
|
||||
"@typescript-eslint/no-unused-vars": ["error", { caughtErrors: "none" }],
|
||||
"no-unused-expressions": "off",
|
||||
"security/detect-non-literal-regexp": "off",
|
||||
},
|
||||
},
|
||||
);
|
||||
Generated
+196
-137
@@ -32,10 +32,12 @@
|
||||
"@intlify/eslint-plugin-vue-i18n": "^4.4.0",
|
||||
"@intlify/unplugin-vue-i18n": "^11.2.3",
|
||||
"@playwright/test": "^1.54.1",
|
||||
"@types/eslint-plugin-security": "^3.0.1",
|
||||
"@types/node": "^24.1.0",
|
||||
"@vitejs/plugin-vue": "^5.0.4",
|
||||
"deepl-node": "^1.16.0",
|
||||
"eslint": "^10.4.0",
|
||||
"eslint-plugin-security": "^4.0.0",
|
||||
"eslint-plugin-vue": "^10.9.1",
|
||||
"fs-extra": "^11.3.0",
|
||||
"globals": "^17.6.0",
|
||||
@@ -1798,6 +1800,27 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/eslint": {
|
||||
"version": "9.6.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz",
|
||||
"integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/estree": "*",
|
||||
"@types/json-schema": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/eslint-plugin-security": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/eslint-plugin-security/-/eslint-plugin-security-3.0.1.tgz",
|
||||
"integrity": "sha512-z+v78aGgONHyq7mMaXgzG+10HonH0HBubSLDbTXdNGyCLtM3c29MdDc9hQzHO9pDan4WYHWdULVOJyzszqDuqg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/eslint": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/esrecurse": {
|
||||
"version": "4.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/esrecurse/-/esrecurse-4.3.1.tgz",
|
||||
@@ -1830,9 +1853,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "24.13.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.13.1.tgz",
|
||||
"integrity": "sha512-RSpUJGmvsJ1ZeBehQZFhIdpsz+bIpES0nIQXko4Ybq+N+kX6XvOq3Jo+iJ82FWLdblFq85AsMikd3m35jgezYg==",
|
||||
"version": "24.13.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.13.2.tgz",
|
||||
"integrity": "sha512-fRa09kZTgu8o71KFcDjUFuc7F+dEbZYZmkI0mg5YBTRs0yMKjYHsq/c0urDKeDb+D5qVgXOdFcuu+DZPKOITwA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -1847,17 +1870,17 @@
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/@typescript-eslint/eslint-plugin": {
|
||||
"version": "8.60.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.60.1.tgz",
|
||||
"integrity": "sha512-JQ4S5GB0tfjO8BuJ4fcX+HodkzJjYBV+7OJ+wLygaX7OGQ7FudyHL4NSCA6ob+w3Yn+5MkKIozOwQhXeM7opVg==",
|
||||
"version": "8.61.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.61.0.tgz",
|
||||
"integrity": "sha512-bFNvl9ZczlVb+wR2Akszf3gHfKVj/8WanXaGJ3UstTA7brNKg0cNdk6X1Psu5V7MZ2oQtzZKOEzIUehaoxbDGw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@eslint-community/regexpp": "^4.12.2",
|
||||
"@typescript-eslint/scope-manager": "8.60.1",
|
||||
"@typescript-eslint/type-utils": "8.60.1",
|
||||
"@typescript-eslint/utils": "8.60.1",
|
||||
"@typescript-eslint/visitor-keys": "8.60.1",
|
||||
"@typescript-eslint/scope-manager": "8.61.0",
|
||||
"@typescript-eslint/type-utils": "8.61.0",
|
||||
"@typescript-eslint/utils": "8.61.0",
|
||||
"@typescript-eslint/visitor-keys": "8.61.0",
|
||||
"ignore": "^7.0.5",
|
||||
"natural-compare": "^1.4.0",
|
||||
"ts-api-utils": "^2.5.0"
|
||||
@@ -1870,22 +1893,22 @@
|
||||
"url": "https://opencollective.com/typescript-eslint"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@typescript-eslint/parser": "^8.60.1",
|
||||
"@typescript-eslint/parser": "^8.61.0",
|
||||
"eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
|
||||
"typescript": ">=4.8.4 <6.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/parser": {
|
||||
"version": "8.60.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.60.1.tgz",
|
||||
"integrity": "sha512-A0M6ua6H252bVjPvvtSgl2QA4+ET9S5Mtkb2GDyTxIhH/C4qDItT7RQNO5PhMC6NXGYXOR9dIalcDDgBKT7oFA==",
|
||||
"version": "8.61.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.61.0.tgz",
|
||||
"integrity": "sha512-5B7PfA2e1NQGCnDHd/0lW7W3gvp3d59Ryw54FYO8Uswxo9f6ikw3AZV+Xj/TvpImmpsiYyUqAfhC6kJID1jF6w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/scope-manager": "8.60.1",
|
||||
"@typescript-eslint/types": "8.60.1",
|
||||
"@typescript-eslint/typescript-estree": "8.60.1",
|
||||
"@typescript-eslint/visitor-keys": "8.60.1",
|
||||
"@typescript-eslint/scope-manager": "8.61.0",
|
||||
"@typescript-eslint/types": "8.61.0",
|
||||
"@typescript-eslint/typescript-estree": "8.61.0",
|
||||
"@typescript-eslint/visitor-keys": "8.61.0",
|
||||
"debug": "^4.4.3"
|
||||
},
|
||||
"engines": {
|
||||
@@ -1901,14 +1924,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/project-service": {
|
||||
"version": "8.60.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.60.1.tgz",
|
||||
"integrity": "sha512-eXkTH2bxmXlqD1RnOPmLZ9ZM9D3VwSx04JOwBnP9RQ+yUA5a2Mu7SfW8uaV2Aon53NJzZlZYuX7tn91Izf+xaw==",
|
||||
"version": "8.61.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.61.0.tgz",
|
||||
"integrity": "sha512-DV42F7MLJO6Rax7SK1yg43tcnEfGUrurSpSxKuVX+a3RCTzBlH3fuxprrOJXKCJGAaw82xXocikJ0uQaqwXgGA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/tsconfig-utils": "^8.60.1",
|
||||
"@typescript-eslint/types": "^8.60.1",
|
||||
"@typescript-eslint/tsconfig-utils": "^8.61.0",
|
||||
"@typescript-eslint/types": "^8.61.0",
|
||||
"debug": "^4.4.3"
|
||||
},
|
||||
"engines": {
|
||||
@@ -1923,14 +1946,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/scope-manager": {
|
||||
"version": "8.60.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.60.1.tgz",
|
||||
"integrity": "sha512-gvI5OQoptnxQnchOirukCuQ55svJSTuD/4k5+pC267xyBtYry748R9/c3tYUzb/iE6RZfllRz2lVulLCHkTm4w==",
|
||||
"version": "8.61.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.61.0.tgz",
|
||||
"integrity": "sha512-IWdXFHFSb6mlC3HPc7QsLDm5zYEbUla6trDEHf32D3/dnuUyXd87plScSNXSbm0/RxMvObpI17sv/EDTGrGZkA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.60.1",
|
||||
"@typescript-eslint/visitor-keys": "8.60.1"
|
||||
"@typescript-eslint/types": "8.61.0",
|
||||
"@typescript-eslint/visitor-keys": "8.61.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
@@ -1941,9 +1964,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/tsconfig-utils": {
|
||||
"version": "8.60.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.60.1.tgz",
|
||||
"integrity": "sha512-nh8w4qAteiKuZu3pSSzG/yGKpw0OlkrKnzFmbVRenKaD4qc+7i1GrmZaLVkr8rk4uipiPGMOW4YsM6WmKZ5CvA==",
|
||||
"version": "8.61.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.61.0.tgz",
|
||||
"integrity": "sha512-O5Amvdv9ztMpxpf+vmFULGG78IE6Qwdr3bCGvqwG4nwc9H2qXkOYJJnRbRHyMkQTjv1d03olqwwwzHLMqpFePQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -1958,15 +1981,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/type-utils": {
|
||||
"version": "8.60.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.60.1.tgz",
|
||||
"integrity": "sha512-sdwTrpjosW7ANQYJ39ZBF1ZyEMEGVB2UsikrserVM/30a/F1dTLnu9bGxEdosugyu5caigjLrR2qiD11asjI1A==",
|
||||
"version": "8.61.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.61.0.tgz",
|
||||
"integrity": "sha512-TuBiQYIkd97yBfInHCTKVYMbX4kvEmpOEuixIuzCU9p8BGT1SfyyO0d0IfDMbPIHcjn/hWnusUX5e8v5Xg+X8A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.60.1",
|
||||
"@typescript-eslint/typescript-estree": "8.60.1",
|
||||
"@typescript-eslint/utils": "8.60.1",
|
||||
"@typescript-eslint/types": "8.61.0",
|
||||
"@typescript-eslint/typescript-estree": "8.61.0",
|
||||
"@typescript-eslint/utils": "8.61.0",
|
||||
"debug": "^4.4.3",
|
||||
"ts-api-utils": "^2.5.0"
|
||||
},
|
||||
@@ -1983,9 +2006,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/types": {
|
||||
"version": "8.60.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.60.1.tgz",
|
||||
"integrity": "sha512-4h0tY8ppCkdCzcrl2YM5M3my0xsE1Tf8om3owEu5oPWmXwkKRmk0j0LGDzYBGUcAlesEbxBhazqu/K4cu3Ug7w==",
|
||||
"version": "8.61.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.61.0.tgz",
|
||||
"integrity": "sha512-9QTQpZ5Iin4CdIodfbDQFSeiSJKidgYJYug1P9CC2xWgUTvlmixViqDZNciMjwLBZyJnG4tGmPl97rVAFb1AJg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -1997,16 +2020,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/typescript-estree": {
|
||||
"version": "8.60.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.60.1.tgz",
|
||||
"integrity": "sha512-alpRkfG8hlVE5kdJW2GkfgDgXxold3e8e4l6EnmhRmRLbekgAPCCGDVD++sABy9FcgPFroq+uFcCSM1vR57Cew==",
|
||||
"version": "8.61.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.61.0.tgz",
|
||||
"integrity": "sha512-42zatd5qSvvcV1JdDBCLxYRznvP4eIHpPoZXdkPFnAmanA4FuZ5dibSnCBggY8hQnqajPpoGjXFdZ7fIJKQnlA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/project-service": "8.60.1",
|
||||
"@typescript-eslint/tsconfig-utils": "8.60.1",
|
||||
"@typescript-eslint/types": "8.60.1",
|
||||
"@typescript-eslint/visitor-keys": "8.60.1",
|
||||
"@typescript-eslint/project-service": "8.61.0",
|
||||
"@typescript-eslint/tsconfig-utils": "8.61.0",
|
||||
"@typescript-eslint/types": "8.61.0",
|
||||
"@typescript-eslint/visitor-keys": "8.61.0",
|
||||
"debug": "^4.4.3",
|
||||
"minimatch": "^10.2.2",
|
||||
"semver": "^7.7.3",
|
||||
@@ -2025,16 +2048,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/utils": {
|
||||
"version": "8.60.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.60.1.tgz",
|
||||
"integrity": "sha512-h2MPBLoNtjc3qZWfY3Tl51yPorQ2McHn8pJfcMNTcIvrrZrr90Ykffit0yjrPFWQcRcUxzH20+6OcVdW4yHtUg==",
|
||||
"version": "8.61.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.61.0.tgz",
|
||||
"integrity": "sha512-3bzFt7ImFMW/jVYwJamDoe/dMOdFLSC6pom6rRjdh4SZJEYupyMzem8e7vKZLclLfpHjlwSAXOUxtKxGXUiLqA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@eslint-community/eslint-utils": "^4.9.1",
|
||||
"@typescript-eslint/scope-manager": "8.60.1",
|
||||
"@typescript-eslint/types": "8.60.1",
|
||||
"@typescript-eslint/typescript-estree": "8.60.1"
|
||||
"@typescript-eslint/scope-manager": "8.61.0",
|
||||
"@typescript-eslint/types": "8.61.0",
|
||||
"@typescript-eslint/typescript-estree": "8.61.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
@@ -2049,13 +2072,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/visitor-keys": {
|
||||
"version": "8.60.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.60.1.tgz",
|
||||
"integrity": "sha512-EbGRQg4FhrmwLodl+t3JNAnXHWVr9Vp+Zl1QBZVPY4ByfkzIT8cX3K6QWODHtkIZqqJVEWvhHSx3v5PDHsaQag==",
|
||||
"version": "8.61.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.61.0.tgz",
|
||||
"integrity": "sha512-QVLZu3ZPQEE+HICQyAMZ2yLQhxf0meY/wx6Hx14YcTNj13JB3qHlX3lJ02L3fLGHgERRH71kvYDwiXIguT3AjQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.60.1",
|
||||
"@typescript-eslint/types": "8.61.0",
|
||||
"eslint-visitor-keys": "^5.0.0"
|
||||
},
|
||||
"engines": {
|
||||
@@ -2246,39 +2269,39 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/compiler-core": {
|
||||
"version": "3.5.35",
|
||||
"resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.35.tgz",
|
||||
"integrity": "sha512-BUmHaR1J+O+CKZ9uJucdVTEr1LHsdyvv7vG3eNRhK3CczEHeMd/LtsHAuD7PbrxvI2envCY2v7HI1vC1aBRzKw==",
|
||||
"version": "3.5.38",
|
||||
"resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.38.tgz",
|
||||
"integrity": "sha512-s99aGxWYig9ErHbct27KXEGhrBYlRI6c4MwAgXErOAbX9xiW37/uMa+XUDO69zLz83dng8UUZ70CTOJrLrYrEQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/parser": "^7.29.3",
|
||||
"@vue/shared": "3.5.35",
|
||||
"@babel/parser": "^7.29.7",
|
||||
"@vue/shared": "3.5.38",
|
||||
"entities": "^7.0.1",
|
||||
"estree-walker": "^2.0.2",
|
||||
"source-map-js": "^1.2.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/compiler-dom": {
|
||||
"version": "3.5.35",
|
||||
"resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.35.tgz",
|
||||
"integrity": "sha512-k+bprkXxuqhVajgTx5mUHuir7TwQzUKOWR40ng1ncAqQRPnrLngGGgqVEEhOnTMlc8btHYVKmrP8s5Qyg0hvYA==",
|
||||
"version": "3.5.38",
|
||||
"resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.38.tgz",
|
||||
"integrity": "sha512-JTqp25l8aFfJYF7/KmsXZjAxJz7T+SjmTJLoXVjHtc2BrSgSiW2n9Aem/cWq1OPe68A8JL06B3eVdhlP0H4TVw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vue/compiler-core": "3.5.35",
|
||||
"@vue/shared": "3.5.35"
|
||||
"@vue/compiler-core": "3.5.38",
|
||||
"@vue/shared": "3.5.38"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/compiler-sfc": {
|
||||
"version": "3.5.35",
|
||||
"resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.35.tgz",
|
||||
"integrity": "sha512-G5VPMcXTSywXBgtFOZOnHKBxKSrwXUcvY1iaF5/hRcy7t0J6CH/d8ha9F4nzi00Fax1eLV0QHM7v4mQu68jydw==",
|
||||
"version": "3.5.38",
|
||||
"resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.38.tgz",
|
||||
"integrity": "sha512-DuA2GiZawSEW442iw/9+Fkol8hTgb4Ke5KkhmSry65QA7YuyMbIdy8p0XZRMvNwJdgRz307W8g1CSzdvS4nuNg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/parser": "^7.29.3",
|
||||
"@vue/compiler-core": "3.5.35",
|
||||
"@vue/compiler-dom": "3.5.35",
|
||||
"@vue/compiler-ssr": "3.5.35",
|
||||
"@vue/shared": "3.5.35",
|
||||
"@babel/parser": "^7.29.7",
|
||||
"@vue/compiler-core": "3.5.38",
|
||||
"@vue/compiler-dom": "3.5.38",
|
||||
"@vue/compiler-ssr": "3.5.38",
|
||||
"@vue/shared": "3.5.38",
|
||||
"estree-walker": "^2.0.2",
|
||||
"magic-string": "^0.30.21",
|
||||
"postcss": "^8.5.15",
|
||||
@@ -2286,13 +2309,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/compiler-ssr": {
|
||||
"version": "3.5.35",
|
||||
"resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.35.tgz",
|
||||
"integrity": "sha512-rGhAeXgdM7/ffTJGXT69rCCdTmjDewnFuUZfBQQHTdcEBeWdT5HCGY60y2ytLJr9/Dsu7IntUi5z/w0h6Rjnzw==",
|
||||
"version": "3.5.38",
|
||||
"resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.38.tgz",
|
||||
"integrity": "sha512-7s+W5Gc42FGxZMcuwl8H5B29T8BJPMdBT7KHFE+BbAuZ/iTEdTtv7z2XiMjiaUUw4w3ZcCEdHs36RuYJ2VA7bA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vue/compiler-dom": "3.5.35",
|
||||
"@vue/shared": "3.5.35"
|
||||
"@vue/compiler-dom": "3.5.38",
|
||||
"@vue/shared": "3.5.38"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/compiler-vue2": {
|
||||
@@ -2338,53 +2361,53 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/reactivity": {
|
||||
"version": "3.5.35",
|
||||
"resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.35.tgz",
|
||||
"integrity": "sha512-tVc+SsHConvh/Lz64qq1pP3rYArBmK42xonovEcxY74SQtvctZodG/zhq54P5dr38cVuw25d27cPNRdlMidpGQ==",
|
||||
"version": "3.5.38",
|
||||
"resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.38.tgz",
|
||||
"integrity": "sha512-pG6LV/NDNRbKizcUjFFLAfjaL8mcv4DmR9avNcUw2gDHBzZneuS2TWCmp633ynzxz9YYKNeEPK2I8Wraqy2HUQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vue/shared": "3.5.35"
|
||||
"@vue/shared": "3.5.38"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/runtime-core": {
|
||||
"version": "3.5.35",
|
||||
"resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.35.tgz",
|
||||
"integrity": "sha512-A/xFNX9loIcWDygeQuNCfKuh0CoYBzxhqEMNah5TSFg9Z53DrFYEN2qi5CU9necjM1OWYegYREUTHmXTmhfXtg==",
|
||||
"version": "3.5.38",
|
||||
"resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.38.tgz",
|
||||
"integrity": "sha512-iyW8WVfF1CpCXxncZY5Ei6rSd6oZr5DgEom//fUjRBRl56AXPD+s9ATvukRt77ZFTuYlnVA1bxY+dJB94tWVYw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vue/reactivity": "3.5.35",
|
||||
"@vue/shared": "3.5.35"
|
||||
"@vue/reactivity": "3.5.38",
|
||||
"@vue/shared": "3.5.38"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/runtime-dom": {
|
||||
"version": "3.5.35",
|
||||
"resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.35.tgz",
|
||||
"integrity": "sha512-odrJ1C391dbGnyDRh8U+rnP7J2amIEzfmRk5vXy7xi3aZhEXofTvpi0T4HJb6jlNqQZTNPR5MPHSB3RHNkIORA==",
|
||||
"version": "3.5.38",
|
||||
"resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.38.tgz",
|
||||
"integrity": "sha512-apX2wt9sdfDshS+a2xueFZLVpt0GkRJZSoPmrW/SA4yzXTznhfcMVW59gr7h4YQeY0vJhdJkk2rsIDwgfFgC5A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vue/reactivity": "3.5.35",
|
||||
"@vue/runtime-core": "3.5.35",
|
||||
"@vue/shared": "3.5.35",
|
||||
"@vue/reactivity": "3.5.38",
|
||||
"@vue/runtime-core": "3.5.38",
|
||||
"@vue/shared": "3.5.38",
|
||||
"csstype": "^3.2.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/server-renderer": {
|
||||
"version": "3.5.35",
|
||||
"resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.35.tgz",
|
||||
"integrity": "sha512-NkebSOYdB97wi8OQcO3HqzZSlymJi/aWsN/7h74OSVhRTm6qGs3Jp3e0rCXynmWwSlKeRrnlIug+ilYoHBmQDA==",
|
||||
"version": "3.5.38",
|
||||
"resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.38.tgz",
|
||||
"integrity": "sha512-vue8vbf2QlV4quHqzwmJy6dWfmRhP1J8l4wtZg60CL6VoKqcPY2oe7may3+1d9qfpedjK5PRLFqd5k3Isj9mUw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vue/compiler-ssr": "3.5.35",
|
||||
"@vue/shared": "3.5.35"
|
||||
"@vue/compiler-ssr": "3.5.38",
|
||||
"@vue/shared": "3.5.38"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"vue": "3.5.35"
|
||||
"vue": "3.5.38"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/shared": {
|
||||
"version": "3.5.35",
|
||||
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.35.tgz",
|
||||
"integrity": "sha512-zSbjL7gRXwks2ZQLRGCajBtBXEOXW9Ddhn/HvSdrGkE2dqGnumzW8XtusRrxrE9LvqtiqDXQ+A60Hp6mvdYxfA==",
|
||||
"version": "3.5.38",
|
||||
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.38.tgz",
|
||||
"integrity": "sha512-FTW0AFZNaK5/mOqvGBwVfUlNLU38TiQn4+DQgIFUnrBBJQ1crMJ82yeGQLV5jyKFsO8yRukpbuP7x+nRbH6aug==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@xmldom/xmldom": {
|
||||
@@ -2403,9 +2426,9 @@
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/acorn": {
|
||||
"version": "8.16.0",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz",
|
||||
"integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==",
|
||||
"version": "8.17.0",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.17.0.tgz",
|
||||
"integrity": "sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
@@ -2896,9 +2919,9 @@
|
||||
"license": "BSD-2-Clause"
|
||||
},
|
||||
"node_modules/dompurify": {
|
||||
"version": "3.4.8",
|
||||
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.8.tgz",
|
||||
"integrity": "sha512-yb1cEmaOum7wFvOCSQxyfgVlv5D47Rc30iZWoMpbDIWTnJ6grDDQyu2KFJzB2k7u0pMuJcQ1zphH//fFnw2tjQ==",
|
||||
"version": "3.4.10",
|
||||
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.10.tgz",
|
||||
"integrity": "sha512-0xzNv0e7oYC6yyuOGZIABPM4qtg3QxLFniDNPP4ZP90wR8Yq3zgwpRbrNiT4N3IKqDbbYFEJLV+JWEs19aZ//w==",
|
||||
"license": "(MPL-2.0 OR Apache-2.0)",
|
||||
"optionalDependencies": {
|
||||
"@types/trusted-types": "^2.0.7"
|
||||
@@ -3211,6 +3234,22 @@
|
||||
"eslint": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-plugin-security": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-security/-/eslint-plugin-security-4.0.0.tgz",
|
||||
"integrity": "sha512-tfuQT8K/Li1ZxhFzyD8wPIKtlzZxqBcPr9q0jFMQ77wWAbKBVEhaMPVQRTMTvCMUDhwBe5vPVqQPwAGk/ASfxQ==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"safe-regex": "^2.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/eslint"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-plugin-vue": {
|
||||
"version": "10.9.2",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-10.9.2.tgz",
|
||||
@@ -4616,16 +4655,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/nwsapi": {
|
||||
"version": "2.2.23",
|
||||
"resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.23.tgz",
|
||||
"integrity": "sha512-7wfH4sLbt4M0gCDzGE6vzQBo0bfTKjU7Sfpqy/7gs1qBfYz2vEJH6vXcBKpO3+6Yu1telwd0t9HpyOoLEQQbIQ==",
|
||||
"version": "2.2.24",
|
||||
"resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.24.tgz",
|
||||
"integrity": "sha512-7YRhZ3jS45LwmSCT4b2sVFHt/WuovaktDU07QrtOBY2PXskss5a9jfmR9jptyumwXST+rFjrmppMY1KT/yn35A==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/obug": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/obug/-/obug-2.1.2.tgz",
|
||||
"integrity": "sha512-AWGB9WFcRXOQs48Z/udjI5ZcZMHXwX8XPByNpOydgcGsDLIzjGizhoMWJyKAWze7AVW/2W1i+/gPX4YtKe5cyg==",
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/obug/-/obug-2.1.3.tgz",
|
||||
"integrity": "sha512-9miFgM2OFba7hB+pRgvtV84pYTBaoTHohvmIgiRt6dRIzbwEOIaNaP+dIlGs2fNFoB0SeISs0Jz5WFVRid6Xyg==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
"https://github.com/sponsors/sxzz",
|
||||
@@ -4911,9 +4950,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/postcss-selector-parser": {
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz",
|
||||
"integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==",
|
||||
"version": "7.1.4",
|
||||
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.4.tgz",
|
||||
"integrity": "sha512-HeP7D2wyhkR+XaK6v4W8oRF62Dsz4flyuczALJp61GckGm42u1saSSJ/0auvcBqxs3jMRFEcPK34At/0JBKdOg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -5024,6 +5063,16 @@
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
},
|
||||
"node_modules/regexp-tree": {
|
||||
"version": "0.1.27",
|
||||
"resolved": "https://registry.npmjs.org/regexp-tree/-/regexp-tree-0.1.27.tgz",
|
||||
"integrity": "sha512-iETxpjK6YoRWJG5o6hXLwvjYAoW+FEZn9os0PD/b6AP6xQwsa/Y7lCVgIixBbUPMfhu+i2LtdeAqVTgGlQarfA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"regexp-tree": "bin/regexp-tree"
|
||||
}
|
||||
},
|
||||
"node_modules/resolve-from": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
|
||||
@@ -5127,6 +5176,16 @@
|
||||
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/safe-regex": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-2.1.1.tgz",
|
||||
"integrity": "sha512-rx+x8AMzKb5Q5lQ95Zoi6ZbJqwCLkqi3XuJXp5P3rT8OEc6sZCJG5AE5dU3lsgRr/F4Bs31jSlVN+j5KrsGu9A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"regexp-tree": "~0.1.1"
|
||||
}
|
||||
},
|
||||
"node_modules/safer-buffer": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
|
||||
@@ -5148,9 +5207,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/semver": {
|
||||
"version": "7.8.2",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.8.2.tgz",
|
||||
"integrity": "sha512-c8jsqUZm3omBOI66G90z1Dyw5z622G8oLG+omfsHBJf3CWQTlOcwOjvOG6wtiNfW6anKm/eA39LMwMtMez2TiQ==",
|
||||
"version": "7.8.4",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.8.4.tgz",
|
||||
"integrity": "sha512-rUCObTnP32Q08R2uuIrt7r9PlEonuTmtuXYcW6s5kjdlj3xbnwe+21yXptAUYcMAABLkYYTtnmzb3w3EDZfueA==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"bin": {
|
||||
@@ -5573,16 +5632,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/typescript-eslint": {
|
||||
"version": "8.60.1",
|
||||
"resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.60.1.tgz",
|
||||
"integrity": "sha512-6m5hkkRAp8lKvhVpcprAIn5KkehQEh+47oHH2VGnExEh7dhNxXlg6GPAOIu6TxbVQxhebrJDvjl3020ooiWCMA==",
|
||||
"version": "8.61.0",
|
||||
"resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.61.0.tgz",
|
||||
"integrity": "sha512-8y31Rd0eGTrDKqhy6vT0HtzhN+YLjQizwX3aA3hPXP/ynSfnrBXcQY5IzsP9/DM7+klX4IUncZZjkchP0z+rUw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/eslint-plugin": "8.60.1",
|
||||
"@typescript-eslint/parser": "8.60.1",
|
||||
"@typescript-eslint/typescript-estree": "8.60.1",
|
||||
"@typescript-eslint/utils": "8.60.1"
|
||||
"@typescript-eslint/eslint-plugin": "8.61.0",
|
||||
"@typescript-eslint/parser": "8.61.0",
|
||||
"@typescript-eslint/typescript-estree": "8.61.0",
|
||||
"@typescript-eslint/utils": "8.61.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
@@ -5934,16 +5993,16 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/vue": {
|
||||
"version": "3.5.35",
|
||||
"resolved": "https://registry.npmjs.org/vue/-/vue-3.5.35.tgz",
|
||||
"integrity": "sha512-cx89fnr+0kVGHiNFG6y6s0bdjypJRFNZn6x3WPstNdQR1bi1mbB7h4v5IBGTsPJU3nK1+0Iqj3Zf+hZWMieR4Q==",
|
||||
"version": "3.5.38",
|
||||
"resolved": "https://registry.npmjs.org/vue/-/vue-3.5.38.tgz",
|
||||
"integrity": "sha512-vAMKHfImQlYSy0C+PBue4s3ERZ2xGKfgZg5GXAsLInq1dyh2H78ILVP5sK0KPFPVW4kv+OGCIvBEondcjpZp7A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vue/compiler-dom": "3.5.35",
|
||||
"@vue/compiler-sfc": "3.5.35",
|
||||
"@vue/runtime-dom": "3.5.35",
|
||||
"@vue/server-renderer": "3.5.35",
|
||||
"@vue/shared": "3.5.35"
|
||||
"@vue/compiler-dom": "3.5.38",
|
||||
"@vue/compiler-sfc": "3.5.38",
|
||||
"@vue/runtime-dom": "3.5.38",
|
||||
"@vue/server-renderer": "3.5.38",
|
||||
"@vue/shared": "3.5.38"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": "*"
|
||||
|
||||
@@ -50,10 +50,12 @@
|
||||
"@intlify/eslint-plugin-vue-i18n": "^4.4.0",
|
||||
"@intlify/unplugin-vue-i18n": "^11.2.3",
|
||||
"@playwright/test": "^1.54.1",
|
||||
"@types/eslint-plugin-security": "^3.0.1",
|
||||
"@types/node": "^24.1.0",
|
||||
"@vitejs/plugin-vue": "^5.0.4",
|
||||
"deepl-node": "^1.16.0",
|
||||
"eslint": "^10.4.0",
|
||||
"eslint-plugin-security": "^4.0.0",
|
||||
"eslint-plugin-vue": "^10.9.1",
|
||||
"fs-extra": "^11.3.0",
|
||||
"globals": "^17.6.0",
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
<meta name="theme-color" content="{{ .htmlVars.color }}">
|
||||
|
||||
<!-- Add to home screen for Safari on iOS/iPadOS -->
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black">
|
||||
<meta name="apple-mobile-web-app-title" content="assets">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="{{ .htmlVars.appIcon }}">
|
||||
@@ -51,6 +52,12 @@
|
||||
<!-- Injected variables come from backend server -->
|
||||
<script>
|
||||
window.globalVars = {{ .globalVars }};
|
||||
window.__pwaDeferredPrompt = null;
|
||||
window.addEventListener("beforeinstallprompt", function (event) {
|
||||
event.preventDefault();
|
||||
window.__pwaDeferredPrompt = event;
|
||||
window.dispatchEvent(new Event("pwa-install-available"));
|
||||
});
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable security/detect-object-injection */
|
||||
// scripts/sync-translations.js
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
@@ -15,7 +15,7 @@ import { fetchURL } from "./utils";
|
||||
*/
|
||||
export async function createArchive(opts) {
|
||||
const { fromSource, toSource, paths, destination, format, compression, deleteAfter } = opts;
|
||||
if (!fromSource || !paths?.length || !destination) {
|
||||
if (!fromSource || !Array.isArray(paths) || paths.length === 0 || !destination) {
|
||||
throw new Error("fromSource, paths, and destination are required");
|
||||
}
|
||||
const body = {
|
||||
|
||||
@@ -175,10 +175,10 @@ export async function createApiKey(params) {
|
||||
}
|
||||
|
||||
// DELETE /api/auth/token
|
||||
export function deleteApiKey(params) {
|
||||
export async function deleteApiKey(params) {
|
||||
try {
|
||||
const apiPath = getApiPath('auth/token', params)
|
||||
fetchURL(apiPath, {
|
||||
await fetchURL(apiPath, {
|
||||
method: 'DELETE'
|
||||
})
|
||||
} catch (err) {
|
||||
@@ -201,10 +201,7 @@ function base64ToArrayBuffer(base64url) {
|
||||
let base64 = base64url.replace(/-/g, '+').replace(/_/g, '/')
|
||||
while (base64.length % 4) base64 += '='
|
||||
const binary = atob(base64)
|
||||
const bytes = new Uint8Array(binary.length)
|
||||
for (let i = 0; i < binary.length; i++) {
|
||||
bytes[i] = binary.charCodeAt(i)
|
||||
}
|
||||
const bytes = Uint8Array.from(binary, (c) => c.charCodeAt(0))
|
||||
return bytes.buffer
|
||||
}
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ export async function getConfig(req) {
|
||||
}
|
||||
|
||||
// POST /api/office/callback or /public/api/office/callback
|
||||
export async function callback(params, hash = null) {
|
||||
export async function callback(params, hash) {
|
||||
try {
|
||||
let apiPath
|
||||
if (hash) {
|
||||
|
||||
@@ -2,8 +2,13 @@ import { notify } from '@/notify'
|
||||
import { mutations, state } from '@/store'
|
||||
import { globalVars } from '@/utils/constants'
|
||||
import { downloadManager } from '@/utils/downloadManager'
|
||||
import {
|
||||
notifyDownloadComplete,
|
||||
notifyDownloadError,
|
||||
} from '@/utils/appNotifications'
|
||||
import { getApiPath, getPublicApiPath } from '@/utils/url.js'
|
||||
import { adjustedData, fetchURL } from './utils'
|
||||
import { getObjectProperty } from '@/utils/object'
|
||||
|
||||
export { fetchPreviewImage } from '@/utils/previewRequests'
|
||||
|
||||
@@ -53,7 +58,7 @@ export async function getItems(source, path, only = "") {
|
||||
* Registers a graceful pause for the active chunked upload at this path.
|
||||
* Call immediately before aborting the chunk XHR so the server keeps partial data.
|
||||
*/
|
||||
export async function signalUploadPause(source, path, shareHash = null) {
|
||||
export async function signalUploadPause(source, path, shareHash) {
|
||||
if (shareHash) {
|
||||
const apiPath = getPublicApiPath('resources/pause', {
|
||||
hash: shareHash,
|
||||
@@ -497,7 +502,7 @@ async function downloadChunkedArchive(url, format, files, filePaths, source, sha
|
||||
|
||||
downloadManager.setStatus(downloadId, 'downloading')
|
||||
|
||||
const hasDownloadPrompt = state.prompts?.some((h) => h.name === 'download')
|
||||
const hasDownloadPrompt = state.prompts.some((h) => h.name === 'download')
|
||||
if (!hasDownloadPrompt) {
|
||||
mutations.showPrompt({ name: 'download' })
|
||||
}
|
||||
@@ -530,6 +535,7 @@ async function downloadChunkedArchive(url, format, files, filePaths, source, sha
|
||||
|
||||
downloadManager.setStatus(downloadId, 'completed')
|
||||
downloadManager.updateProgress(downloadId, fileSize, fileSize)
|
||||
notifyDownloadComplete(fileName, fileSize)
|
||||
|
||||
setTimeout(() => {
|
||||
document.body.removeChild(link)
|
||||
@@ -544,6 +550,7 @@ async function downloadChunkedArchive(url, format, files, filePaths, source, sha
|
||||
const message =
|
||||
error instanceof Error ? error.message || 'Unknown error' : String(error)
|
||||
downloadManager.setError(downloadId, message)
|
||||
notifyDownloadError(fileName, message)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
@@ -585,7 +592,7 @@ async function downloadChunked(file, shareHash = "") {
|
||||
downloadManager.setStatus(downloadId, "downloading")
|
||||
|
||||
// Show download prompt if not already shown (it should already be shown by downloadFiles, but check to be safe)
|
||||
const hasDownloadPrompt = state.prompts?.some(h => h.name === 'download');
|
||||
const hasDownloadPrompt = state.prompts.some(h => h.name === 'download');
|
||||
|
||||
if (!hasDownloadPrompt) {
|
||||
mutations.showPrompt({ name: 'download' })
|
||||
@@ -620,6 +627,7 @@ async function downloadChunked(file, shareHash = "") {
|
||||
const dlDone = downloadManager.findById(downloadId)
|
||||
const finalSize = dlDone?.size ?? fileSize
|
||||
downloadManager.updateProgress(downloadId, finalSize, finalSize)
|
||||
notifyDownloadComplete(fileName, finalSize)
|
||||
|
||||
setTimeout(() => {
|
||||
document.body.removeChild(link)
|
||||
@@ -636,6 +644,7 @@ async function downloadChunked(file, shareHash = "") {
|
||||
const message =
|
||||
error instanceof Error ? error.message || "Unknown error" : String(error)
|
||||
downloadManager.setError(downloadId, message)
|
||||
notifyDownloadError(fileName, message)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
@@ -663,9 +672,9 @@ export function post(
|
||||
const request = new XMLHttpRequest();
|
||||
request.open("POST", apiPath, true);
|
||||
|
||||
for (const header in headers) {
|
||||
request.setRequestHeader(header, headers[header]);
|
||||
}
|
||||
Object.entries(headers).forEach(([header, value]) => {
|
||||
request.setRequestHeader(header, value);
|
||||
});
|
||||
|
||||
if (typeof onupload === "function") {
|
||||
request.upload.onprogress = (event) => {
|
||||
@@ -808,7 +817,7 @@ export async function checksum(source, path, algo) {
|
||||
const apiPath = getApiPath('resources', params)
|
||||
const res = await fetchURL(apiPath)
|
||||
const data = await res.json()
|
||||
return data.checksums[algo]
|
||||
return getObjectProperty(data.checksums, algo)
|
||||
} catch (err) {
|
||||
notify.showError(err.message || 'Error fetching checksum')
|
||||
throw err
|
||||
@@ -862,7 +871,7 @@ export function getPreviewURL(source, path, modified) {
|
||||
// POST /api/resources/archive - Create an archive
|
||||
export async function createArchive(opts) {
|
||||
const { fromSource, toSource, paths, destination, format, compression, deleteAfter } = opts;
|
||||
if (!fromSource || !paths?.length || !destination) {
|
||||
if (!fromSource || !Array.isArray(paths) || paths.length === 0 || !destination) {
|
||||
throw new Error("fromSource, paths, and destination are required");
|
||||
}
|
||||
const body = {
|
||||
@@ -1051,9 +1060,10 @@ export function postPublic(
|
||||
const request = new XMLHttpRequest();
|
||||
request.open("POST", apiPath, true);
|
||||
|
||||
for (const header in headers) {
|
||||
request.setRequestHeader(header, headers[header]);
|
||||
}
|
||||
Object.entries(headers).forEach(([header, value]) => {
|
||||
request.setRequestHeader(header, value);
|
||||
});
|
||||
|
||||
if (typeof onupload === "function") {
|
||||
request.upload.onprogress = (event) => {
|
||||
if (event.lengthComputable) {
|
||||
@@ -1164,7 +1174,7 @@ export async function bulkDeletePublic(items) {
|
||||
throw new Error('items array is required and must not be empty')
|
||||
}
|
||||
|
||||
const hash = state.shareInfo?.hash;
|
||||
const hash = state.shareInfo.hash;
|
||||
if (!hash) {
|
||||
throw new Error('share hash is required')
|
||||
}
|
||||
|
||||
@@ -64,7 +64,7 @@ export async function create(bodyObj = {}) {
|
||||
const apiPath = getApiPath("share");
|
||||
return await fetchJSON(apiPath, {
|
||||
method: "POST",
|
||||
body: JSON.stringify(bodyObj || {}),
|
||||
body: JSON.stringify(bodyObj),
|
||||
});
|
||||
} catch (/** @type {any} */ err) {
|
||||
notify.showError(err.message || "Error creating share");
|
||||
@@ -124,3 +124,14 @@ export async function getShareInfoPublic(hash) {
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
// PATCH /public/api/share/pinnedItems (add by default; ?action=remove to unpin)
|
||||
export async function patchPinnedItem({ hash, path, name, action = 'add' }) {
|
||||
const params = { hash, action }
|
||||
const apiPath = getPublicApiPath('share/pinnedItems', params)
|
||||
await fetchURL(apiPath, {
|
||||
method: 'PATCH',
|
||||
body: JSON.stringify({ path, name }),
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
})
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import { fetchURL, fetchJSON } from '@/api/utils'
|
||||
import { getApiPath, getPublicApiPath } from '@/utils/url.js'
|
||||
import { getObjectProperty, setObjectProperty } from '@/utils/object'
|
||||
import { notify } from '@/notify'
|
||||
import { state } from '@/store/state.js'
|
||||
import { mutations } from '@/store/mutations.js'
|
||||
@@ -107,8 +108,9 @@ export async function update(user, which = ['all'], options = {}) {
|
||||
if (which.length !== 1 || which[0] !== 'all') {
|
||||
userData = {}
|
||||
which.forEach(key => {
|
||||
if (key in user) {
|
||||
userData[key] = user[key]
|
||||
const value = getObjectProperty(user, key)
|
||||
if (value !== undefined) {
|
||||
userData = setObjectProperty(userData, key, value)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -129,4 +129,3 @@ describe('adjustedData', () => {
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -82,6 +82,12 @@
|
||||
:label="$t('general.download')"
|
||||
@action="startDownload"
|
||||
/>
|
||||
<action
|
||||
v-if="showSendToApp"
|
||||
icon="ios_share"
|
||||
:label="$t('buttons.sendToApp')"
|
||||
@action="sendToApp"
|
||||
/>
|
||||
<action
|
||||
v-if="showUnarchive"
|
||||
icon="unarchive"
|
||||
@@ -185,6 +191,7 @@
|
||||
<action v-if="isPreview && permissions.modify" icon="edit" :label="$t('general.rename')" @action="showRenamePromptForPreview" />
|
||||
<action v-if="showWatch" icon="visibility" :label="$t('buttons.watchFile')" @action="watchFile()" />
|
||||
<action v-if="hasDownload" icon="file_download" :label="$t('general.download')" @action="startDownload" />
|
||||
<action v-if="showSendToAppInPreview" icon="ios_share" :label="$t('buttons.sendToApp')" @action="sendToAppFromPreview" />
|
||||
<action v-if="showUnarchiveInOverflow" icon="folder_open" :label="$t('prompts.unarchive')" @action="showUnarchivePromptFromPreview" />
|
||||
<action v-if="showEdit" icon="edit" :label="$t('general.edit')" @action="edit()" />
|
||||
<action v-if="markdownPreview" icon="visibility" :label="$t('general.preview')" @action="switchToMarkdown" />
|
||||
@@ -195,7 +202,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { resourcesApi, usersApi } from "@/api";
|
||||
import { resourcesApi, shareApi, usersApi } from "@/api";
|
||||
import Action from "@/components/Action.vue";
|
||||
import { notify } from "@/notify";
|
||||
import { getters, mutations, state } from "@/store";
|
||||
@@ -204,6 +211,8 @@ import buttons from "@/utils/buttons";
|
||||
import { copyToClipboard } from "@/utils/clipboard";
|
||||
import { globalVars } from "@/utils/constants.js";
|
||||
import downloadFiles from "@/utils/download";
|
||||
import { canNativeShare, nativeShareFile } from "@/utils/nativeShare";
|
||||
import { isRichTextPreviewMimeType } from "@/utils/mimetype";
|
||||
|
||||
function isArchivePath(pathOrName) {
|
||||
if (!pathOrName || typeof pathOrName !== "string") return false;
|
||||
@@ -254,7 +263,9 @@ export default {
|
||||
if (state.selected.length === 0) return [];
|
||||
// Map to actual items from state.req
|
||||
if (typeof state.selected[0] === 'number') {
|
||||
return state.selected.map(index => state.req.items[index]);
|
||||
return state.selected
|
||||
.map(index => state.req.items.at(index))
|
||||
.filter(item => item !== null);
|
||||
}
|
||||
return state.selected;
|
||||
},
|
||||
@@ -328,13 +339,26 @@ export default {
|
||||
if (this.showLimitedOptions) return false;
|
||||
return this.selectedCount <= 1 && this.showShare;
|
||||
},
|
||||
showSendToApp() {
|
||||
if (!canNativeShare()) return false;
|
||||
if (this.showLimitedOptions || this.showCreate) return false;
|
||||
if (!this.permissions.download || this.selectedCount !== 1) return false;
|
||||
const item = this.firstSelected;
|
||||
return item && !item.isDir && item.type !== "directory";
|
||||
},
|
||||
showSendToAppInPreview() {
|
||||
if (!canNativeShare()) return false;
|
||||
if (!this.hasDownload || this.req?.isDir) return false;
|
||||
return this.isPreview;
|
||||
},
|
||||
showRename() {
|
||||
if (this.showLimitedOptions) return false;
|
||||
return !this.showCreate && this.selectedCount === 1 && this.permissions.modify && !this.isSearchActive;
|
||||
},
|
||||
showPinAction() {
|
||||
if (this.showLimitedOptions) return false;
|
||||
if (getters.isShare() || !getters.isLoggedIn()) return false;
|
||||
if (!getters.isLoggedIn()) return false;
|
||||
if (getters.isShare() && !state.shareInfo?.canEditShare) return false;
|
||||
if (this.showCreate || this.isSearchActive) return false;
|
||||
if (this.selectedCount !== 1) return false;
|
||||
return getters.currentView() === "listingView";
|
||||
@@ -478,7 +502,7 @@ export default {
|
||||
},
|
||||
markdownPreview() {
|
||||
if (getters.currentView() !== 'editor') return false;
|
||||
return state.req.type === 'text/markdown';
|
||||
return isRichTextPreviewMimeType(state.req.type);
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
@@ -692,6 +716,17 @@ export default {
|
||||
const items = this.providedItems;
|
||||
downloadFiles(items);
|
||||
},
|
||||
async sendToApp() {
|
||||
mutations.closeTopPrompt();
|
||||
const item = this.firstSelected;
|
||||
if (!item) return;
|
||||
await nativeShareFile(item);
|
||||
},
|
||||
async sendToAppFromPreview() {
|
||||
mutations.closeTopPrompt();
|
||||
if (!this.req) return;
|
||||
await nativeShareFile(this.req);
|
||||
},
|
||||
showDeletePrompt() {
|
||||
mutations.closeTopPrompt();
|
||||
mutations.showPrompt({
|
||||
@@ -723,25 +758,37 @@ export default {
|
||||
},
|
||||
async togglePin() {
|
||||
const item = this.firstSelected;
|
||||
if (!item?.name || getters.isShare() || !getters.isLoggedIn()) {
|
||||
if (!item?.name || !getters.isLoggedIn()) {
|
||||
return;
|
||||
}
|
||||
if (getters.isShare() && !state.shareInfo?.canEditShare) {
|
||||
return;
|
||||
}
|
||||
mutations.closeHovers();
|
||||
const source = item.source || state.req?.source || state.sources.current;
|
||||
if (!source) {
|
||||
return;
|
||||
}
|
||||
const directoryPath = state.req?.type === "directory"
|
||||
? state.req.path
|
||||
: url.getParentDir(item.path);
|
||||
const action = item.pinned ? "remove" : "add";
|
||||
try {
|
||||
await usersApi.patchPinnedItem({
|
||||
source,
|
||||
path: directoryPath,
|
||||
name: item.name,
|
||||
action,
|
||||
});
|
||||
if (getters.isShare()) {
|
||||
await shareApi.patchPinnedItem({
|
||||
hash: state.shareInfo.hash,
|
||||
path: directoryPath,
|
||||
name: item.name,
|
||||
action,
|
||||
});
|
||||
} else {
|
||||
const source = item.source || state.req?.source || state.sources.current;
|
||||
if (!source) {
|
||||
return;
|
||||
}
|
||||
await usersApi.patchPinnedItem({
|
||||
source,
|
||||
path: directoryPath,
|
||||
name: item.name,
|
||||
action,
|
||||
});
|
||||
}
|
||||
mutations.setReload(true);
|
||||
} catch (e) {
|
||||
notify.showError(e);
|
||||
|
||||
@@ -85,6 +85,12 @@
|
||||
:name="$t('search.useWildcardSearch')"
|
||||
:description="$t('search.useWildcardSearchDescription')"
|
||||
/>
|
||||
<ToggleSwitch
|
||||
class="item"
|
||||
v-model="caseExactSearch"
|
||||
:name="$t('search.caseExact')"
|
||||
:description="$t('search.caseExactDescription')"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</SettingsItem>
|
||||
@@ -173,6 +179,7 @@ export default {
|
||||
isTypeSelectDisabled: false,
|
||||
showPreviewImages: false,
|
||||
useWildcardSearch: false,
|
||||
caseExactSearch: false,
|
||||
folderSelect: [
|
||||
{ label: this.$t("search.onlyFolders"), value: "type:folder" },
|
||||
{ label: this.$t("search.onlyFiles"), value: "type:file" },
|
||||
@@ -212,6 +219,9 @@ export default {
|
||||
useWildcardSearch() {
|
||||
this.submit();
|
||||
},
|
||||
caseExactSearch() {
|
||||
this.submit();
|
||||
},
|
||||
searchTypes() {
|
||||
this.submit();
|
||||
},
|
||||
@@ -281,7 +291,7 @@ export default {
|
||||
return getters.eventTheme();
|
||||
},
|
||||
disableSearchOptions() {
|
||||
return state.user.disableSearchOptions;
|
||||
return state.user?.disableSearchOptions;
|
||||
},
|
||||
foldersOnly() {
|
||||
return this.isTypeSelectDisabled;
|
||||
@@ -403,7 +413,7 @@ export default {
|
||||
},
|
||||
updateSource(event) {
|
||||
this.selectedSource = event.target.value;
|
||||
this.submit();
|
||||
void this.submit();
|
||||
},
|
||||
getItemUrl(s) {
|
||||
// Use source from result if available, otherwise fall back to selectedSource
|
||||
@@ -507,6 +517,13 @@ export default {
|
||||
if (this.useWildcardSearch) {
|
||||
dateParams.useWildcard = true;
|
||||
}
|
||||
let searchQuery = searchTypesFull + this.value;
|
||||
if (this.caseExactSearch) {
|
||||
searchQuery =
|
||||
searchTypesFull.trim() === ""
|
||||
? `case:exact ${this.value}`
|
||||
: `case:exact ${searchTypesFull}${this.value}`;
|
||||
}
|
||||
this.ongoing++;
|
||||
|
||||
// Determine which sources to search
|
||||
@@ -525,7 +542,7 @@ export default {
|
||||
this.results = await toolsApi.search(
|
||||
scope,
|
||||
sourcesToSearch,
|
||||
searchTypesFull + this.value,
|
||||
searchQuery,
|
||||
false,
|
||||
dateParams
|
||||
);
|
||||
@@ -620,6 +637,9 @@ export default {
|
||||
if (this.useWildcardSearch) {
|
||||
query.wildcard = "1";
|
||||
}
|
||||
if (this.caseExactSearch) {
|
||||
query.caseExact = "1";
|
||||
}
|
||||
if (this.isTypeSelectDisabled) {
|
||||
query.typeLock = "1";
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@ export default {
|
||||
name: "StatusBar",
|
||||
data() {
|
||||
return {
|
||||
gallerySize: state.user.gallerySize,
|
||||
gallerySize: state.user?.gallerySize,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
@@ -91,8 +91,8 @@ export default {
|
||||
}
|
||||
let total = 0;
|
||||
state.selected.forEach(index => {
|
||||
if (index >= 0 && index < state.req.items.length) {
|
||||
const item = state.req.items[index];
|
||||
if (index >= 0 && index < state.req?.items.length) {
|
||||
const item = state.req.items.at(index);
|
||||
if (item?.size) {
|
||||
total += item.size;
|
||||
}
|
||||
@@ -206,7 +206,7 @@ export default {
|
||||
// Only update if the mode actually changed
|
||||
if (newMode !== currentMode) {
|
||||
mutations.updateDisplayPreferences({ viewMode: newMode });
|
||||
mutations.updateCurrentUser({ viewMode: newMode });
|
||||
void mutations.updateCurrentUser({ viewMode: newMode });
|
||||
}
|
||||
},
|
||||
// Ctrl + Mouse Wheel to adjust the slider sizes
|
||||
@@ -215,7 +215,7 @@ export default {
|
||||
|
||||
const delta = event.deltaY > 0 ? 1 : -1; // Scroll down increases, up decreases
|
||||
|
||||
const advSearch = (state.route?.path || "").startsWith("/tools/advancedSearch");
|
||||
const advSearch = (state.route.path || "").startsWith("/tools/advancedSearch");
|
||||
if (this.currentView === "listingView" || advSearch) {
|
||||
event.preventDefault();
|
||||
const newSize = Math.min(9, Math.max(1, this.gallerySize - delta));
|
||||
|
||||
@@ -85,7 +85,7 @@ export default {
|
||||
computed: {
|
||||
darkMode() { return getters.isDarkMode(); },
|
||||
queueCount() {
|
||||
return state.playbackQueue?.queue?.length || 0;
|
||||
return state.playbackQueue.queue.length || 0;
|
||||
},
|
||||
syncedLyrics() {
|
||||
return this.lyrics.length > 0 && !this.lyrics.every(line => line.timestamp === 0);
|
||||
|
||||
@@ -36,6 +36,8 @@ import { resourcesApi } from "@/api";
|
||||
import { notify } from "@/notify";
|
||||
import { getters, mutations, state } from "@/store";
|
||||
import { url } from "@/utils";
|
||||
import { getObjectProperty } from '@/utils/object.js';
|
||||
import { goToItemNotificationButton } from "@/utils/notificationActions";
|
||||
|
||||
export default {
|
||||
name: "breadcrumbs",
|
||||
@@ -69,7 +71,7 @@ export default {
|
||||
return state.req.hasUpdate;
|
||||
},
|
||||
isDroppable() {
|
||||
return getters.permissions()?.modify
|
||||
return getters.permissions().modify
|
||||
},
|
||||
homeLink() {
|
||||
return {
|
||||
@@ -80,14 +82,12 @@ export default {
|
||||
};
|
||||
},
|
||||
scrollRatio() {
|
||||
const ratio = state.listing?.scrollRatio || 0;
|
||||
const ratio = state.listing.scrollRatio || 0;
|
||||
return ratio;
|
||||
},
|
||||
items() {
|
||||
const req = state.req;
|
||||
if (!req.items || !req.path) {
|
||||
return [];
|
||||
}
|
||||
if (!req.path) return [];
|
||||
const encodedPathString = url.encodedPath(state.req.path);
|
||||
const originalParts = state.req.path.split("/");
|
||||
const encodedParts = encodedPathString.split("/");
|
||||
@@ -105,8 +105,8 @@ export default {
|
||||
let accumulatedPath = "";
|
||||
|
||||
for (let i = 0; i < originalParts.length; i++) {
|
||||
const origPart = originalParts[i];
|
||||
const encodedElement = encodedParts[i];
|
||||
const origPart = getObjectProperty(originalParts, i);
|
||||
const encodedElement = getObjectProperty(encodedParts, i);
|
||||
buildRef = `${buildRef + encodedElement}/`;
|
||||
accumulatedPath = accumulatedPath ? `${accumulatedPath}/${origPart}` : origPart;
|
||||
|
||||
@@ -210,7 +210,7 @@ export default {
|
||||
for (const i of state.selected) {
|
||||
if (i < 0 || i >= state.req.length) continue;
|
||||
|
||||
const selectedItem = state.req.items[i];
|
||||
const selectedItem = state.req.items.at(i);
|
||||
|
||||
let fromPath = selectedItem.path;
|
||||
|
||||
@@ -279,17 +279,16 @@ export default {
|
||||
await resourcesApi.moveCopy(itemsToMove, "move", overwrite, rename);
|
||||
}
|
||||
|
||||
const buttonAction = () => {
|
||||
url.goToItem(source, targetPath, {}, false, getters.isShare());
|
||||
};
|
||||
|
||||
notify.showSuccess(this.$t("prompts.moveSuccess"), {
|
||||
icon: "folder",
|
||||
buttons: [{
|
||||
label: this.$t("buttons.goToItem"),
|
||||
primary: true,
|
||||
action: buttonAction
|
||||
}]
|
||||
buttons: [
|
||||
goToItemNotificationButton(
|
||||
this.$t("buttons.goToItem"),
|
||||
source,
|
||||
targetPath,
|
||||
getters.isShare()
|
||||
),
|
||||
],
|
||||
});
|
||||
mutations.closeTopPrompt();
|
||||
mutations.setReload(true);
|
||||
@@ -308,7 +307,7 @@ export default {
|
||||
const rename = option === "rename";
|
||||
event.preventDefault();
|
||||
mutations.closeTopPrompt();
|
||||
moveAction(overwrite, rename);
|
||||
void moveAction(overwrite, rename);
|
||||
},
|
||||
});
|
||||
return;
|
||||
|
||||
@@ -148,7 +148,7 @@ export default {
|
||||
},
|
||||
availableSources() {
|
||||
// Get all available sources from state.sources.info
|
||||
return state.sources?.info ? Object.keys(state.sources.info) : [state.req.source];
|
||||
return state.sources.info ? Object.keys(state.sources.info) : [state.req.source];
|
||||
},
|
||||
showSourceSelector() {
|
||||
if (this.hideDestinationSource) {
|
||||
@@ -280,7 +280,7 @@ export default {
|
||||
this.selectedSource = null;
|
||||
this.selectedType = null;
|
||||
// Fetch files for the new source
|
||||
this.withLoading(() => resourcesApi.fetchFiles(newSource, newPath).then(this.fillOptions));
|
||||
void this.withLoading(() => resourcesApi.fetchFiles(newSource, newPath).then(this.fillOptions));
|
||||
},
|
||||
resetToShare(newHash) {
|
||||
// Reset to the share root
|
||||
@@ -291,7 +291,7 @@ export default {
|
||||
this.selectedSource = null;
|
||||
this.selectedType = null;
|
||||
// Fetch files for the share
|
||||
this.withLoading(() => resourcesApi.fetchFilesPublic("/", newHash).then(this.fillOptions));
|
||||
void this.withLoading(() => resourcesApi.fetchFilesPublic("/", newHash).then(this.fillOptions));
|
||||
},
|
||||
fillOptions(req) {
|
||||
// Sets the current path and resets the current items.
|
||||
@@ -373,14 +373,14 @@ export default {
|
||||
if (this.browseSource) {
|
||||
// Explicitly browsing a source - use files API
|
||||
this.source = sourceToUse;
|
||||
this.withLoading(() => resourcesApi.fetchFiles(sourceToUse, path).then(this.fillOptions));
|
||||
void this.withLoading(() => resourcesApi.fetchFiles(sourceToUse, path).then(this.fillOptions));
|
||||
} else if (this.browseShare || getters.isShare()) {
|
||||
// Browsing a share - use public API
|
||||
const hashToUse = this.browseShare || state.shareInfo?.hash;
|
||||
this.withLoading(() => resourcesApi.fetchFilesPublic(path, hashToUse).then(this.fillOptions));
|
||||
void this.withLoading(() => resourcesApi.fetchFilesPublic(path, hashToUse).then(this.fillOptions));
|
||||
} else {
|
||||
this.source = sourceToUse;
|
||||
this.withLoading(() => resourcesApi.fetchFiles(sourceToUse, path).then(this.fillOptions));
|
||||
void this.withLoading(() => resourcesApi.fetchFiles(sourceToUse, path).then(this.fillOptions));
|
||||
}
|
||||
|
||||
},
|
||||
@@ -510,13 +510,9 @@ export default {
|
||||
this.selectedType = null;
|
||||
if (this.browseShare || getters.isShare()) {
|
||||
const hashToUse = this.browseShare || state.shareInfo?.hash;
|
||||
this.withLoading(() =>
|
||||
resourcesApi.fetchFilesPublic(p, hashToUse).then(this.fillOptions)
|
||||
);
|
||||
void this.withLoading(() => resourcesApi.fetchFilesPublic(p, hashToUse).then(this.fillOptions));
|
||||
} else {
|
||||
this.withLoading(() =>
|
||||
resourcesApi.fetchFiles(source, p).then(this.fillOptions)
|
||||
);
|
||||
void this.withLoading(() => resourcesApi.fetchFiles(source, p).then(this.fillOptions));
|
||||
}
|
||||
},
|
||||
fillFromList() {
|
||||
|
||||
@@ -81,7 +81,9 @@ import LoadingSpinner from '@/components/LoadingSpinner.vue';
|
||||
import { state, getters, mutations } from '@/store';
|
||||
import { eventBus } from '@/store/eventBus';
|
||||
import { goToItem, joinPath } from '@/utils/url';
|
||||
import { goToItemNotificationButton } from '@/utils/notificationActions';
|
||||
import { notify } from '@/notify';
|
||||
import { getObjectProperty } from '@/utils/object.js';
|
||||
|
||||
export default {
|
||||
name: 'FileTree',
|
||||
@@ -136,7 +138,7 @@ export default {
|
||||
return this.isRootInstance ? this.rootNodes : this.nodes;
|
||||
},
|
||||
canModify() {
|
||||
return getters.permissions()?.modify;
|
||||
return getters.permissions().modify;
|
||||
},
|
||||
showFiles() {
|
||||
// Defaults to true
|
||||
@@ -315,7 +317,7 @@ export default {
|
||||
// don't navigate to current item if we're already viewing it
|
||||
if (this.isCurrentItem(node)) {
|
||||
if (node.isDir) {
|
||||
this.toggleExpand(node, false); // If the item is a folder, just expand/collapse instead of navigate again
|
||||
void this.toggleExpand(node, false); // If the item is a folder, just expand/collapse instead of navigate again
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -379,8 +381,7 @@ export default {
|
||||
|
||||
let currentLevel = this.isRootInstance ? this.rootNodes : this.nodes;
|
||||
|
||||
for (let i = 0; i < pathParts.length; i++) {
|
||||
const part = pathParts[i];
|
||||
for (const part of pathParts) {
|
||||
const node = currentLevel.find(n => n.name === part && n.isDir);
|
||||
if (!node) break;
|
||||
|
||||
@@ -467,7 +468,7 @@ export default {
|
||||
}
|
||||
// Set a new timeout
|
||||
const timeout = setTimeout(() => {
|
||||
this.toggleExpand(node, true); // expand without navigating
|
||||
void this.toggleExpand(node, true); // expand without navigating
|
||||
this.expandTimeouts.delete(node);
|
||||
}, 800); // 800ms delay (we need to hover that time to auto expand when dragging a item)
|
||||
this.expandTimeouts.set(node, timeout);
|
||||
@@ -529,7 +530,7 @@ export default {
|
||||
// Build list of items to move
|
||||
const items = [];
|
||||
for (const index of selectedIndices) {
|
||||
const item = state.req.items[index];
|
||||
const item = getObjectProperty(state.req?.items, index);
|
||||
if (!item) continue;
|
||||
|
||||
// Prevent dropping onto itself or into its own subdirectory
|
||||
@@ -587,20 +588,16 @@ export default {
|
||||
} else {
|
||||
await resourcesApi.moveCopy(items, 'move', overwrite, rename);
|
||||
}
|
||||
const buttonAction = () => {
|
||||
if (this.isShare) {
|
||||
goToItem(this.shareHash, node.path, {}, false, true);
|
||||
} else {
|
||||
goToItem(this.currentSource, node.path, {});
|
||||
}
|
||||
};
|
||||
const buttonProps = {
|
||||
icon: 'folder',
|
||||
buttons: [{
|
||||
label: this.$t('buttons.goToItem'),
|
||||
primary: true,
|
||||
action: buttonAction,
|
||||
}]
|
||||
buttons: [
|
||||
goToItemNotificationButton(
|
||||
this.$t('buttons.goToItem'),
|
||||
this.isShare ? this.shareHash : this.currentSource,
|
||||
node.path,
|
||||
this.isShare
|
||||
),
|
||||
],
|
||||
};
|
||||
notify.showSuccess(this.$t('prompts.moveSuccess'), buttonProps);
|
||||
mutations.closeTopPrompt();
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
import { fetchPreviewImage } from "@/api/resources";
|
||||
import { globalVars } from "@/utils/constants";
|
||||
import { getTypeInfo } from "@/utils/mimetype";
|
||||
import { getObjectProperty } from '@/utils/object.js';
|
||||
import { mutations, state, getters } from "@/store";
|
||||
import { setImageLoaded } from "@/utils/imageCache";
|
||||
import ThreeJs from "@/views/files/ThreeJs.vue";
|
||||
@@ -225,7 +226,7 @@ export default {
|
||||
},
|
||||
shouldUse3DPreview() {
|
||||
// Check if we should use 3D preview instead of regular icon
|
||||
if (!this.isFile || !this.size || !this.path) return false;
|
||||
if (this.mimetype === "directory" || !this.size || !this.path) return false;
|
||||
if (!getters.previewPerms().models) return false;
|
||||
|
||||
const MAX_SIZE = 250 * 1024; // 250KB in bytes
|
||||
@@ -240,7 +241,7 @@ export default {
|
||||
return `${state.user?.gallerySize || 1}-${getters.viewMode()}`;
|
||||
},
|
||||
routePath() {
|
||||
return state.route?.path ?? "";
|
||||
return state.route.path ?? "";
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
@@ -366,9 +367,9 @@ export default {
|
||||
}
|
||||
// Set the thumbnail or popup preview
|
||||
if (getters.previewPerms().popup) {
|
||||
mutations.setPreviewSource(sequence[index]);
|
||||
mutations.setPreviewSource(getObjectProperty(sequence, index));
|
||||
} else {
|
||||
this.currentThumbnail = sequence[index];
|
||||
this.currentThumbnail = getObjectProperty(sequence, index);
|
||||
}
|
||||
|
||||
// Preload the next frame (cancellable fetch warms HTTP cache)
|
||||
|
||||
@@ -33,7 +33,7 @@ export default {
|
||||
];
|
||||
},
|
||||
rows() {
|
||||
return buildIndexInfoRows(this.info, this.$t, state.user.locale);
|
||||
return buildIndexInfoRows(this.info, this.$t, state.user?.locale);
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -142,11 +142,13 @@
|
||||
import { globalVars } from "@/utils/constants";
|
||||
import downloadFiles from "@/utils/download";
|
||||
import { getHumanReadableFilesize } from "@/utils/filesizes";
|
||||
import { getObjectProperty } from '@/utils/object.js';
|
||||
import { resourcesApi } from "@/api";
|
||||
import * as upload from "@/utils/upload";
|
||||
import { state, getters, mutations } from "@/store"; // Import your custom store
|
||||
import { url } from "@/utils";
|
||||
import { notify } from "@/notify";
|
||||
import { goToItemNotificationButton } from "@/utils/notificationActions";
|
||||
import Icon from "@/components/files/Icon.vue";
|
||||
|
||||
export default {
|
||||
@@ -278,15 +280,13 @@ export default {
|
||||
if (this.readOnly === true) return false;
|
||||
|
||||
for (const i of this.selected) {
|
||||
if (
|
||||
state.req.items[i].path === this.path &&
|
||||
state.req.source === this.source
|
||||
) {
|
||||
const item = getObjectProperty(state.req.items, i);
|
||||
if (!item) continue;
|
||||
// Also check if we're trying to drop an item onto itself
|
||||
if (item.path === this.path && state.req.source === this.source) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Also check if we're trying to drop an item onto itself
|
||||
if (state.req.items[i].index === this.index) {
|
||||
if (item.index === this.index) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -302,7 +302,7 @@ export default {
|
||||
let previewPath;
|
||||
if (this.path) {
|
||||
previewPath = this.path;
|
||||
} else if (state?.req?.path && this.name) {
|
||||
} else if (state.req?.path && this.name) {
|
||||
previewPath = url.joinPath(state.req.path, this.name);
|
||||
} else {
|
||||
return "";
|
||||
@@ -310,13 +310,13 @@ export default {
|
||||
|
||||
// If forceFilesApi is true, always use authenticated files API
|
||||
if (this.forceFilesApi) {
|
||||
return resourcesApi.getPreviewURL(this.source || state?.req?.source, previewPath, this.modified);
|
||||
return resourcesApi.getPreviewURL(this.source || state.req?.source, previewPath, this.modified);
|
||||
}
|
||||
|
||||
if (getters.isShare()) {
|
||||
return resourcesApi.getPreviewURLPublic(previewPath);
|
||||
}
|
||||
return resourcesApi.getPreviewURL(this.source || state.req.source, previewPath, this.modified);
|
||||
return resourcesApi.getPreviewURL(this.source || state.req?.source, previewPath, this.modified);
|
||||
},
|
||||
isThumbsEnabled() {
|
||||
return globalVars.enableThumbs;
|
||||
@@ -553,12 +553,14 @@ export default {
|
||||
|
||||
let items = [];
|
||||
for (const i of state.selected) {
|
||||
const item = getObjectProperty(state.req.items, i);
|
||||
if (!item) continue;
|
||||
items.push({
|
||||
from: state.req.items[i].path,
|
||||
fromSource: state.req.items[i].source,
|
||||
to: url.joinPath(this.path, state.req.items[i].name),
|
||||
from: item.path,
|
||||
fromSource: item.source,
|
||||
to: url.joinPath(this.path, item.name),
|
||||
toSource: this.source,
|
||||
name: state.req.items[i].name,
|
||||
name: item.name,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -617,18 +619,16 @@ export default {
|
||||
await resourcesApi.moveCopy(items, "move", overwrite, rename);
|
||||
}
|
||||
// Notification to move into the folder
|
||||
const buttonAction = () => {
|
||||
this.open();
|
||||
};
|
||||
const buttonProps = {
|
||||
icon: "folder",
|
||||
buttons: [
|
||||
{
|
||||
label: this.$t("buttons.goToItem"),
|
||||
primary: true,
|
||||
action: buttonAction
|
||||
}
|
||||
]
|
||||
goToItemNotificationButton(
|
||||
this.$t("buttons.goToItem"),
|
||||
this.source,
|
||||
this.path,
|
||||
getters.isShare()
|
||||
),
|
||||
],
|
||||
};
|
||||
notify.showSuccess(this.$t("prompts.moveSuccess"), buttonProps);
|
||||
// Close the prompt after successful operation and reload items for reflect the changes
|
||||
@@ -705,7 +705,7 @@ export default {
|
||||
}
|
||||
|
||||
if (
|
||||
!state.user.singleClick &&
|
||||
!state.user?.singleClick &&
|
||||
getters.selectedCount() !== 0 &&
|
||||
event.button === 0
|
||||
) {
|
||||
@@ -767,7 +767,7 @@ export default {
|
||||
}
|
||||
|
||||
if (
|
||||
!state.user.singleClick &&
|
||||
!state.user?.singleClick &&
|
||||
!event.ctrlKey &&
|
||||
!event.metaKey &&
|
||||
!event.shiftKey &&
|
||||
@@ -802,14 +802,15 @@ export default {
|
||||
// Check if state.req.items exists and has the item at this index
|
||||
// This prevents errors when ListingItem is used outside of the main file listing (e.g., duplicate finder)
|
||||
let previousHistoryItem = null;
|
||||
if (state.req.items?.[this.index]) {
|
||||
const currentItem = getObjectProperty(state.req.items, this.index);
|
||||
if (currentItem) {
|
||||
previousHistoryItem = {
|
||||
name: state.req.items[this.index].name,
|
||||
name: currentItem.name,
|
||||
source: state.req.source,
|
||||
path: state.req.path,
|
||||
};
|
||||
}
|
||||
url.goToItem(this.source, this.path, previousHistoryItem || {}, false, getters.isShare());
|
||||
url.goToItem(this.source, this.path, previousHistoryItem, false, getters.isShare());
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -62,7 +62,7 @@ export default {
|
||||
if (!state.shareInfo?.isPasswordProtected) {
|
||||
return true
|
||||
}
|
||||
return state.shareInfo.passwordValid
|
||||
return state.shareInfo?.passwordValid
|
||||
},
|
||||
shareInfo() {
|
||||
return state.shareInfo;
|
||||
|
||||
@@ -577,13 +577,13 @@ export default {
|
||||
case "ArrowRight":
|
||||
if (this.hasNext) {
|
||||
event.preventDefault();
|
||||
this.next();
|
||||
void this.next();
|
||||
}
|
||||
break;
|
||||
case "ArrowLeft":
|
||||
if (this.hasPrevious) {
|
||||
event.preventDefault();
|
||||
this.prev();
|
||||
void this.prev();
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -786,9 +786,9 @@ export default {
|
||||
|
||||
// Navigate based on button type
|
||||
if (this.touchState.buttonType === 'previous' && this.hasPrevious) {
|
||||
this.prev();
|
||||
void this.prev();
|
||||
} else if (this.touchState.buttonType === 'next' && this.hasNext) {
|
||||
this.next();
|
||||
void this.next();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -916,7 +916,7 @@ export default {
|
||||
}
|
||||
// Only navigate if this wasn't a drag
|
||||
if (!this.dragState.triggered) {
|
||||
this.prev();
|
||||
void this.prev();
|
||||
}
|
||||
this.resetDragState();
|
||||
},
|
||||
@@ -930,7 +930,7 @@ export default {
|
||||
}
|
||||
// Only navigate if this wasn't a drag
|
||||
if (!this.dragState.triggered) {
|
||||
this.next();
|
||||
void this.next();
|
||||
}
|
||||
this.resetDragState();
|
||||
},
|
||||
|
||||
@@ -110,7 +110,7 @@ export default {
|
||||
async deleteApi() {
|
||||
// Dummy delete function, to be filled in later
|
||||
try {
|
||||
authApi.deleteApiKey({ name: this.name });
|
||||
await authApi.deleteApiKey({ name: this.name });
|
||||
// Emit event to refresh API tokens list
|
||||
setTimeout(() => {
|
||||
eventBus.emit('apiKeysChanged');
|
||||
|
||||
@@ -142,7 +142,7 @@
|
||||
import { state, mutations, getters } from "@/store";
|
||||
import { notify } from "@/notify";
|
||||
import { resourcesApi } from "@/api";
|
||||
import { goToItem } from "@/utils/url";
|
||||
import { goToItemNotificationButton } from "@/utils/notificationActions";
|
||||
import LoadingSpinner from "@/components/LoadingSpinner.vue";
|
||||
import FileList from "@/components/files/FileList.vue";
|
||||
import ToggleSwitch from "@/components/settings/ToggleSwitch.vue";
|
||||
@@ -180,7 +180,7 @@ export default {
|
||||
},
|
||||
watch: {
|
||||
deleteAfter(newVal) {
|
||||
mutations.updateCurrentUser({ deleteAfterArchive: newVal });
|
||||
void mutations.updateCurrentUser({ deleteAfterArchive: newVal });
|
||||
},
|
||||
showFileList(newVal) {
|
||||
if (!newVal) {
|
||||
@@ -251,7 +251,7 @@ export default {
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
if (this.newDirName && this.isDirNameValid) {
|
||||
this.createDirectory();
|
||||
void this.createDirectory();
|
||||
}
|
||||
},
|
||||
async createDirectory() {
|
||||
@@ -264,15 +264,15 @@ export default {
|
||||
? `${currentPath + this.newDirName}/`
|
||||
: `${currentPath}/${this.newDirName}/`;
|
||||
if (getters.isShare()) {
|
||||
await resourcesApi.postPublic(state.shareInfo?.hash, fullPath, "", false, undefined, {}, true);
|
||||
await resourcesApi.postPublic(state.shareInfo.hash, fullPath, "", false, undefined, {}, true);
|
||||
} else {
|
||||
await resourcesApi.post(currentSource, fullPath, "", false, undefined, {}, true);
|
||||
}
|
||||
if (getters.isShare()) {
|
||||
resourcesApi.fetchFilesPublic(currentPath, state.shareInfo?.hash)
|
||||
await resourcesApi.fetchFilesPublic(currentPath, state.shareInfo.hash)
|
||||
.then((req) => this.$refs.fileList.fillOptions(req, true));
|
||||
} else {
|
||||
resourcesApi.fetchFiles(currentSource, currentPath)
|
||||
await resourcesApi.fetchFiles(currentSource, currentPath)
|
||||
.then((req) => this.$refs.fileList.fillOptions(req, true));
|
||||
}
|
||||
mutations.setReload(true);
|
||||
@@ -312,14 +312,18 @@ export default {
|
||||
|
||||
const destSource = this.destSource || this.source;
|
||||
const archivePath = dest;
|
||||
const buttonAction = () => {
|
||||
if (archivePath) {
|
||||
goToItem(destSource || null, archivePath, {}, false, getters.isShare());
|
||||
}
|
||||
};
|
||||
notify.showSuccess(this.$t("prompts.archiveSuccess"), {
|
||||
icon: "folder",
|
||||
buttons: archivePath ? [{ label: this.$t("buttons.goToItem"), primary: true, action: buttonAction }] : undefined,
|
||||
buttons: archivePath
|
||||
? [
|
||||
goToItemNotificationButton(
|
||||
this.$t("buttons.goToItem"),
|
||||
destSource,
|
||||
archivePath,
|
||||
getters.isShare()
|
||||
),
|
||||
]
|
||||
: undefined,
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
|
||||
@@ -116,8 +116,9 @@ export default {
|
||||
// Only include permissions when customizing token
|
||||
if (this.customizeToken) {
|
||||
// Filter to get keys of permissions set to true and join them as a comma-separated string
|
||||
const permissionsString = Object.keys(this.localPerms)
|
||||
.filter((key) => this.localPerms[key])
|
||||
const permissionsString = Object.entries(this.localPerms)
|
||||
.filter(([, value]) => value)
|
||||
.map(([key]) => key)
|
||||
.join(",");
|
||||
params.permissions = permissionsString;
|
||||
}
|
||||
|
||||
@@ -116,7 +116,7 @@ export default {
|
||||
|
||||
if (state.isSearchActive || getters.currentView() === "preview") {
|
||||
const selected = state.selected[0];
|
||||
const item = state.req.items?.[selected] || selected;
|
||||
const item = state.req.items?.at(selected) || selected;
|
||||
const previewUrl = this.getPreviewUrl(item.source || state.req.source, item.path, item.modified, item.type);
|
||||
items.push({
|
||||
source: item.source || state.req.source,
|
||||
@@ -128,7 +128,7 @@ export default {
|
||||
hasPreview: item.hasPreview,
|
||||
});
|
||||
} else if (!this.isListing) {
|
||||
const item = state.req.items[state.selected[0]];
|
||||
const item = state.req.items.at(state.selected[0]);
|
||||
const previewUrl = this.getPreviewUrl(item.source || state.req.source, item.path, item.modified, item.type);
|
||||
items.push({
|
||||
source: item.source || state.req.source,
|
||||
@@ -141,7 +141,7 @@ export default {
|
||||
});
|
||||
} else {
|
||||
for (const index of state.selected) {
|
||||
const item = state.req.items[index];
|
||||
const item = state.req.items.at(index);
|
||||
const previewUrl = this.getPreviewUrl(item.source || state.req.source, item.path, item.modified, item.type);
|
||||
items.push({
|
||||
source: item.source || state.req.source,
|
||||
|
||||
@@ -106,13 +106,13 @@ export default {
|
||||
return getters.currentPrompt();
|
||||
},
|
||||
downloads() {
|
||||
return downloadManager?.queue || [];
|
||||
return downloadManager.queue || [];
|
||||
},
|
||||
hasDownloads() {
|
||||
return (downloadManager?.queue?.length || 0) > 0;
|
||||
return (downloadManager.queue.length || 0) > 0;
|
||||
},
|
||||
hasClearable() {
|
||||
if (!downloadManager?.queue) {
|
||||
if (!downloadManager.queue) {
|
||||
return false;
|
||||
}
|
||||
return downloadManager.queue.some((download) =>
|
||||
@@ -173,7 +173,7 @@ export default {
|
||||
},
|
||||
close() {
|
||||
// Only close if no active downloads
|
||||
if (!downloadManager?.hasActive()) {
|
||||
if (!downloadManager.hasActive()) {
|
||||
const prompt = getters.currentPrompt();
|
||||
if (prompt?.name === 'download') {
|
||||
mutations.closeTopPrompt();
|
||||
|
||||
@@ -78,7 +78,11 @@ import buttons from "@/utils/buttons";
|
||||
import * as upload from "@/utils/upload";
|
||||
import { url } from "@/utils";
|
||||
import { notify } from "@/notify";
|
||||
import { goToItem } from "@/utils/url";
|
||||
import {
|
||||
notifyMoveCopyComplete,
|
||||
notifyOperationError,
|
||||
} from "@/utils/appNotifications";
|
||||
import { goToItemNotificationButton } from "@/utils/notificationActions";
|
||||
import LoadingSpinner from "@/components/LoadingSpinner.vue";
|
||||
import PathPickerButton from "@/components/files/PathPickerButton.vue";
|
||||
import { eventBus } from '@/store/eventBus';
|
||||
@@ -160,7 +164,7 @@ export default {
|
||||
}));
|
||||
} else if (state.isSearchActive) {
|
||||
// Add null checks to prevent undefined values
|
||||
if (state.selected?.[0]?.path) {
|
||||
if (state.selected[0]?.path) {
|
||||
this.localItems = [
|
||||
{
|
||||
from: state.selected[0].path,
|
||||
@@ -172,7 +176,7 @@ export default {
|
||||
} else {
|
||||
if (state.selected && state.req?.items) {
|
||||
for (const item of state.selected) {
|
||||
const reqItem = state.req.items[item];
|
||||
const reqItem = state.req.items.at(item);
|
||||
if (reqItem?.path) {
|
||||
this.localItems.push({
|
||||
from: reqItem.path,
|
||||
@@ -221,7 +225,7 @@ export default {
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
if (this.newDirName && this.isDirNameValid) {
|
||||
this.createDirectory();
|
||||
void this.createDirectory();
|
||||
}
|
||||
},
|
||||
async createDirectory() {
|
||||
@@ -239,10 +243,10 @@ export default {
|
||||
}
|
||||
// Refresh the file list while keeping the current navigation that we did in the prompt
|
||||
if (getters.isShare()) {
|
||||
resourcesApi.fetchFilesPublic(currentPath, state.shareInfo?.hash)
|
||||
await resourcesApi.fetchFilesPublic(currentPath, state.shareInfo.hash)
|
||||
.then((req) => this.$refs.fileList.fillOptions(req, true));
|
||||
} else {
|
||||
resourcesApi.fetchFiles(currentSource, currentPath)
|
||||
await resourcesApi.fetchFiles(currentSource, currentPath)
|
||||
.then((req) => this.$refs.fileList.fillOptions(req, true));
|
||||
}
|
||||
// Clicking create will also return the buttons to their normal state
|
||||
@@ -355,6 +359,7 @@ export default {
|
||||
// All operations failed - show error but DON'T close prompt
|
||||
const errorMessage = result.failed[0]?.message || this.$t("prompts.operationFailed");
|
||||
notify.showError(errorMessage);
|
||||
notifyOperationError(errorMessage);
|
||||
return;
|
||||
} else if (hasFailures && hasSuccesses) {
|
||||
// Partial failure - show warning and continue
|
||||
@@ -382,6 +387,7 @@ export default {
|
||||
|
||||
if (!hasFailures || hasSuccesses) {
|
||||
notify.showSuccessToast(this.$t("prompts.moveSuccess"));
|
||||
notifyMoveCopyComplete("move", this.localItems.length);
|
||||
}
|
||||
|
||||
// Navigate next, previous or parent dir (like when deleting)
|
||||
@@ -408,27 +414,25 @@ export default {
|
||||
const destSource = this.destSource;
|
||||
const destPath = this.destPath;
|
||||
|
||||
// Show success notification with optional button to navigate to destination
|
||||
// For shares, destSource might be null, but goToItem handles shares via state.shareInfo.hash
|
||||
const buttonAction = () => {
|
||||
if (destPath) {
|
||||
goToItem(destSource || state.shareInfo?.hash, destPath, {}, false, getters.isShare());
|
||||
}
|
||||
};
|
||||
const buttonProps = {
|
||||
icon: "folder",
|
||||
buttons: destPath ? [
|
||||
{
|
||||
label: this.$t("buttons.goToItem"),
|
||||
primary: true,
|
||||
action: buttonAction
|
||||
}
|
||||
] : undefined
|
||||
buttons: destPath
|
||||
? [
|
||||
goToItemNotificationButton(
|
||||
this.$t("buttons.goToItem"),
|
||||
destSource || state.shareInfo?.hash,
|
||||
destPath,
|
||||
getters.isShare()
|
||||
),
|
||||
]
|
||||
: undefined,
|
||||
};
|
||||
if (this.operation === "move") {
|
||||
notify.showSuccess(this.$t("prompts.moveSuccess"), buttonProps);
|
||||
notifyMoveCopyComplete("move", this.localItems.length);
|
||||
} else {
|
||||
notify.showSuccess(this.$t("prompts.copySuccess"), buttonProps);
|
||||
notifyMoveCopyComplete("copy", this.localItems.length);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -454,6 +458,7 @@ export default {
|
||||
}
|
||||
|
||||
notify.showError(errorMessage);
|
||||
notifyOperationError(errorMessage);
|
||||
} finally {
|
||||
this.isLoading = false; // Hide loading spinner
|
||||
}
|
||||
|
||||
@@ -36,6 +36,7 @@ import { resourcesApi } from "@/api";
|
||||
import { getters, mutations, state } from "@/store"; // Import your custom store
|
||||
import { url } from "@/utils";
|
||||
import { notify } from "@/notify";
|
||||
import { goToItemNotificationButton } from "@/utils/notificationActions";
|
||||
import LoadingSpinner from "@/components/LoadingSpinner.vue";
|
||||
export default {
|
||||
name: "new-dir",
|
||||
@@ -112,16 +113,16 @@ export default {
|
||||
},
|
||||
|
||||
showNotification(source, newPath) {
|
||||
const buttonAction = () => {
|
||||
url.goToItem(source, newPath, {}, false, getters.isShare());
|
||||
};
|
||||
const buttonProps = {
|
||||
icon: "folder",
|
||||
buttons: [{
|
||||
label: this.$t("buttons.goToItem"),
|
||||
primary: true,
|
||||
action: buttonAction
|
||||
}]
|
||||
buttons: [
|
||||
goToItemNotificationButton(
|
||||
this.$t("buttons.goToItem"),
|
||||
source,
|
||||
newPath,
|
||||
getters.isShare()
|
||||
),
|
||||
],
|
||||
};
|
||||
notify.showSuccess(this.$t("prompts.newDirSuccess"), buttonProps);
|
||||
},
|
||||
|
||||
@@ -37,6 +37,7 @@ import { resourcesApi } from "@/api";
|
||||
import { getters, mutations } from "@/store"; // Import your custom store
|
||||
import { notify } from "@/notify";
|
||||
import { url } from "@/utils";
|
||||
import { goToItemNotificationButton } from "@/utils/notificationActions";
|
||||
import LoadingSpinner from "@/components/LoadingSpinner.vue";
|
||||
export default {
|
||||
name: "new-file",
|
||||
@@ -109,16 +110,16 @@ export default {
|
||||
},
|
||||
|
||||
showNotification(source, newPath) {
|
||||
const buttonAction = () => {
|
||||
url.goToItem(source, newPath, {}, false, getters.isShare());
|
||||
};
|
||||
const buttonProps = {
|
||||
icon: "insert_drive_file",
|
||||
buttons: [{
|
||||
label: this.$t("buttons.goToItem"),
|
||||
primary: true,
|
||||
action: buttonAction
|
||||
}]
|
||||
buttons: [
|
||||
goToItemNotificationButton(
|
||||
this.$t("buttons.goToItem"),
|
||||
source,
|
||||
newPath,
|
||||
getters.isShare()
|
||||
),
|
||||
],
|
||||
};
|
||||
notify.showSuccess(this.$t("prompts.newFileSuccess"), buttonProps);
|
||||
},
|
||||
|
||||
@@ -71,13 +71,13 @@ export default {
|
||||
},
|
||||
computed: {
|
||||
playbackQueue() {
|
||||
return state.playbackQueue?.queue || [];
|
||||
return state.playbackQueue.queue;
|
||||
},
|
||||
currentQueueIndex() {
|
||||
return state.playbackQueue?.currentIndex ?? -1;
|
||||
return state.playbackQueue.currentIndex;
|
||||
},
|
||||
playbackMode() {
|
||||
return state.playbackQueue?.mode || 'single';
|
||||
return state.playbackQueue.mode || 'single';
|
||||
},
|
||||
queueCount() {
|
||||
return this.playbackQueue.length;
|
||||
@@ -110,7 +110,7 @@ export default {
|
||||
}));
|
||||
},
|
||||
isPlaying() {
|
||||
return state.playbackQueue?.isPlaying || false;
|
||||
return state.playbackQueue.isPlaying || false;
|
||||
},
|
||||
isPromptVisible() {
|
||||
// Check if this PlaybackQueue prompt is the current active prompt
|
||||
@@ -188,7 +188,7 @@ export default {
|
||||
},
|
||||
navigateToIndex(index) {
|
||||
if (index >= 0 && index < this.playbackQueue.length) {
|
||||
const item = this.playbackQueue[index];
|
||||
const item = this.playbackQueue.at(index);
|
||||
// Update store with new current index
|
||||
mutations.setPlaybackQueue({
|
||||
queue: this.playbackQueue,
|
||||
|
||||
@@ -212,16 +212,16 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
triggerPromptBorderFlash(id) {
|
||||
if (this.flashBorderTimers[id]) {
|
||||
clearTimeout(this.flashBorderTimers[id]);
|
||||
const timer = getObjectProperty(this.flashBorderTimers, id);
|
||||
if (timer) {
|
||||
clearTimeout(timer);
|
||||
}
|
||||
this.flashBorderIds = { ...this.flashBorderIds, [id]: true };
|
||||
this.flashBorderTimers[id] = setTimeout(() => {
|
||||
const next = { ...this.flashBorderIds };
|
||||
delete next[id];
|
||||
this.flashBorderIds = setObjectProperty(this.flashBorderIds, id, true);
|
||||
this.flashBorderTimers = setObjectProperty(this.flashBorderTimers, id, setTimeout(() => {
|
||||
const next = omitObjectProperty(this.flashBorderIds, id);
|
||||
this.flashBorderIds = next;
|
||||
delete this.flashBorderTimers[id];
|
||||
}, 500);
|
||||
this.flashBorderTimers = omitObjectProperty(this.flashBorderTimers, id);
|
||||
}, 500));
|
||||
},
|
||||
handleWindowResize() {
|
||||
const maxWidth = window.innerWidth * 0.9;
|
||||
@@ -366,7 +366,7 @@ export default {
|
||||
const promptToClose = state.prompts.find(p => p.id === id);
|
||||
if (!promptToClose) return;
|
||||
// Check if it's the upload prompt with active uploads
|
||||
if (promptToClose?.name === "upload") {
|
||||
if (promptToClose.name === "upload") {
|
||||
const hasActiveUploads = state.upload.isUploading;
|
||||
const hasWarningPrompt = state.prompts.some(p => p.name === "CloseWithActiveUploads");
|
||||
|
||||
@@ -395,11 +395,11 @@ export default {
|
||||
this.cleanupDragState(id);
|
||||
},
|
||||
cleanupDragState(id) {
|
||||
delete this.dragOffsets[id];
|
||||
delete this.dragStarts[id];
|
||||
this.dragOffsets = omitObjectProperty(this.dragOffsets, id);
|
||||
this.dragStarts = omitObjectProperty(this.dragStarts, id);
|
||||
this.touchIds = omitObjectProperty(this.touchIds, id);
|
||||
this.draggingIds.delete(id);
|
||||
delete this.sizes[id];
|
||||
this.sizes = omitObjectProperty(this.sizes, id);
|
||||
},
|
||||
/**
|
||||
* Escape closes the top prompt. Enter clicks the last `.card-actions` button (far right in DOM).
|
||||
@@ -471,7 +471,7 @@ export default {
|
||||
const headerHeight = headerEl ? headerEl.getBoundingClientRect().height : 40;
|
||||
const rect = el.getBoundingClientRect();
|
||||
const windowHeight = rect.height;
|
||||
const offset = this.dragOffsets[id] || { x: 0, y: 0 };
|
||||
const offset = getObjectProperty(this.dragOffsets, id) || { x: 0, y: 0 };
|
||||
const centerX = viewportW / 2 + offset.x;
|
||||
const centerY = viewportH / 2 + offset.y;
|
||||
|
||||
@@ -482,10 +482,10 @@ export default {
|
||||
const clampedX = Math.max(minCenterX, Math.min(maxCenterX, centerX));
|
||||
const clampedY = Math.max(minCenterY, Math.min(maxCenterY, centerY));
|
||||
|
||||
this.dragOffsets[id] = {
|
||||
this.dragOffsets = setObjectProperty(this.dragOffsets, id, {
|
||||
x: clampedX - viewportW / 2,
|
||||
y: clampedY - viewportH / 2,
|
||||
};
|
||||
});
|
||||
},
|
||||
getPromptElement(id) {
|
||||
const windows = this.$refs.promptWindow;
|
||||
@@ -509,8 +509,17 @@ export default {
|
||||
const pos = this.getPointerPos(e, type);
|
||||
if (!pos) return;
|
||||
|
||||
if (!this.dragOffsets[id]) this.dragOffsets[id] = { x: 0, y: 0 };
|
||||
this.dragStarts[id] = { x: pos.x - this.dragOffsets[id].x, y: pos.y - this.dragOffsets[id].y };
|
||||
let dragOffsets = this.dragOffsets;
|
||||
const existingOffset = getObjectProperty(dragOffsets, id);
|
||||
if (!existingOffset) {
|
||||
dragOffsets = setObjectProperty(dragOffsets, id, { x: 0, y: 0 });
|
||||
this.dragOffsets = dragOffsets;
|
||||
}
|
||||
const offset = getObjectProperty(this.dragOffsets, id);
|
||||
this.dragStarts = setObjectProperty(this.dragStarts, id, {
|
||||
x: pos.x - offset.x,
|
||||
y: pos.y - offset.y,
|
||||
});
|
||||
this.draggingIds.add(id);
|
||||
|
||||
const move = (e) => this.onPointerMove(e, id, type);
|
||||
@@ -526,7 +535,8 @@ export default {
|
||||
}
|
||||
},
|
||||
onPointerMove(e, id, type) {
|
||||
if (!this.dragStarts[id]) return;
|
||||
const dragStart = getObjectProperty(this.dragStarts, id);
|
||||
if (!dragStart) return;
|
||||
let pos;
|
||||
if (type === "touch") {
|
||||
if (!e.touches) return;
|
||||
@@ -538,10 +548,10 @@ export default {
|
||||
pos = { x: e.clientX, y: e.clientY };
|
||||
}
|
||||
|
||||
this.dragOffsets[id] = {
|
||||
x: pos.x - this.dragStarts[id].x,
|
||||
y: pos.y - this.dragStarts[id].y,
|
||||
};
|
||||
this.dragOffsets = setObjectProperty(this.dragOffsets, id, {
|
||||
x: pos.x - dragStart.x,
|
||||
y: pos.y - dragStart.y,
|
||||
});
|
||||
|
||||
// Find the element for this prompt
|
||||
const windows = this.$refs.promptWindow;
|
||||
@@ -558,7 +568,7 @@ export default {
|
||||
window.removeEventListener("mousemove", moveHandler);
|
||||
window.removeEventListener("mouseup", endHandler);
|
||||
}
|
||||
delete this.dragStarts[id];
|
||||
this.dragStarts = omitObjectProperty(this.dragStarts, id);
|
||||
this.draggingIds.delete(id);
|
||||
},
|
||||
startResize(e, id, edge) {
|
||||
@@ -572,20 +582,25 @@ export default {
|
||||
: windows;
|
||||
if (!el) return;
|
||||
// Capture current size (if not already fixed, set it)
|
||||
if (!this.sizes[id]) {
|
||||
this.sizes[id] = {
|
||||
let sizes = this.sizes;
|
||||
const existingSize = getObjectProperty(sizes, id);
|
||||
if (!existingSize) {
|
||||
sizes = setObjectProperty(sizes, id, {
|
||||
width: el.offsetWidth,
|
||||
height: el.offsetHeight,
|
||||
};
|
||||
});
|
||||
this.sizes = sizes;
|
||||
}
|
||||
const currentSize = getObjectProperty(this.sizes, id);
|
||||
const dragOffset = getObjectProperty(this.dragOffsets, id) || { x: 0, y: 0 };
|
||||
// Initial values
|
||||
this.resizingId = id;
|
||||
this.resizingEdge = edge;
|
||||
this.resizeStart = {
|
||||
width: this.sizes[id].width,
|
||||
height: this.sizes[id].height,
|
||||
offsetX: this.dragOffsets[id]?.x || 0,
|
||||
offsetY: this.dragOffsets[id]?.y || 0,
|
||||
width: currentSize.width,
|
||||
height: currentSize.height,
|
||||
offsetX: dragOffset.x,
|
||||
offsetY: dragOffset.y,
|
||||
clientX: pos.x,
|
||||
clientY: pos.y,
|
||||
};
|
||||
@@ -604,7 +619,6 @@ export default {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
},
|
||||
|
||||
onResizeMove(e, type) {
|
||||
if (!this.resizingId) return;
|
||||
const pos = this.getPointerPos(e, type);
|
||||
@@ -667,12 +681,12 @@ export default {
|
||||
deltaOffsetY = +(newHeight - start.height) / 2;
|
||||
}
|
||||
// Update sizes
|
||||
this.sizes[id] = { width: newWidth, height: newHeight };
|
||||
this.sizes = setObjectProperty(this.sizes, id, { width: newWidth, height: newHeight });
|
||||
// Update drag
|
||||
this.dragOffsets[id] = {
|
||||
this.dragOffsets = setObjectProperty(this.dragOffsets, id, {
|
||||
x: start.offsetX + deltaOffsetX,
|
||||
y: start.offsetY + deltaOffsetY,
|
||||
};
|
||||
});
|
||||
// Clamp to viewport
|
||||
const windows = this.$refs.promptWindow;
|
||||
const el = Array.isArray(windows)
|
||||
|
||||
@@ -127,7 +127,7 @@ export default {
|
||||
event.stopPropagation();
|
||||
}
|
||||
if (event.key === 'Enter') {
|
||||
this.submit();
|
||||
void this.submit();
|
||||
}
|
||||
},
|
||||
updateFullName() {
|
||||
@@ -167,7 +167,7 @@ export default {
|
||||
}
|
||||
|
||||
// Use parentItems if we are in a preview, otherwise use state.req.items
|
||||
const items = this.parentItems.length > 0 ? this.parentItems : (state.req?.items || []);
|
||||
const items = this.parentItems.length > 0 ? this.parentItems : (state.req?.items ?? []);
|
||||
for (const item of items) {
|
||||
if (!item.name) {
|
||||
continue;
|
||||
|
||||
@@ -36,7 +36,7 @@ export default {
|
||||
},
|
||||
isSameFile() {
|
||||
// Check if the current prompt has props indicating same file
|
||||
return this.currentPrompt?.props?.isSameFile === true;
|
||||
return this.currentPrompt.props?.isSameFile === true;
|
||||
},
|
||||
},
|
||||
methods: {},
|
||||
|
||||
@@ -560,7 +560,9 @@ export default {
|
||||
return this.req.path;
|
||||
}
|
||||
const index = /** @type {number} */ (this.selected[0]);
|
||||
return buildItemUrl(this.req.items[index].source, this.req.items[index].path);
|
||||
const item = this.req.items.at(index);
|
||||
if (!item) return this.req.path;
|
||||
return buildItemUrl(item.source, item.path);
|
||||
},
|
||||
isEditMode() {
|
||||
return this.editing && this.link && Object.keys(this.link).length > 0;
|
||||
@@ -805,7 +807,7 @@ export default {
|
||||
// Update the link in the local list
|
||||
const index = this.links.findIndex(l => l.hash === this.editingLink.hash);
|
||||
if (index !== -1) {
|
||||
this.links[index] = res;
|
||||
this.links.splice(index, 1, res);
|
||||
}
|
||||
this.editingLink = null;
|
||||
// emit event to reload shares in settings view
|
||||
@@ -901,7 +903,7 @@ export default {
|
||||
* @param {number} time
|
||||
*/
|
||||
humanTime(time) {
|
||||
return fromNow(time, state.user.locale)
|
||||
return fromNow(time, state.user?.locale)
|
||||
},
|
||||
sort() {
|
||||
this.links = this.links.sort((a, b) => {
|
||||
@@ -1085,9 +1087,13 @@ export default {
|
||||
this.filePickerField = null;
|
||||
},
|
||||
validateExtensions(value) {
|
||||
if (value === "" || value === "*") return true;
|
||||
const regex = /^\.\w+(?: \.\w+)*$/;
|
||||
return regex.test(value);
|
||||
const normalized = value.trim();
|
||||
if (normalized === "" || normalized === "*") {
|
||||
return true;
|
||||
}
|
||||
const parts = normalized.split(/\s+/);
|
||||
const extensionRegex = /^\.\w+$/;
|
||||
return parts.every(part => extensionRegex.test(part));
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -52,7 +52,7 @@ export default {
|
||||
if (!state.shareInfo?.isPasswordProtected) {
|
||||
return true
|
||||
}
|
||||
return state.shareInfo.passwordValid
|
||||
return state.shareInfo?.passwordValid
|
||||
},
|
||||
getShareBanner() {
|
||||
if (state.shareInfo?.banner.startsWith("http")) {
|
||||
|
||||
@@ -295,6 +295,7 @@ import { notify } from "@/notify";
|
||||
import { usersApi, shareApi } from "@/api";
|
||||
import { tools } from "@/utils/constants";
|
||||
import { getIconClass } from "@/utils/material-symbols";
|
||||
import { getObjectProperty } from '@/utils/object.js';
|
||||
import FileList from "../files/FileList.vue";
|
||||
import ToggleSwitch from "@/components/settings/ToggleSwitch.vue";
|
||||
import { eventBus } from "@/store/eventBus";
|
||||
@@ -404,7 +405,7 @@ export default {
|
||||
// Initialize with existing sidebar links based on context
|
||||
if (this.context === 'share' && this.shareData?.sidebarLinks) {
|
||||
this.links = [...this.shareData.sidebarLinks];
|
||||
} else if (this.context === 'user' && state.user?.sidebarLinks && state.user.sidebarLinks.length > 0) {
|
||||
} else if (this.context === 'user' && state.user?.sidebarLinks && state.user?.sidebarLinks.length > 0) {
|
||||
this.links = [...state.user.sidebarLinks];
|
||||
} else if (this.context === 'user') {
|
||||
// Generate default links from sources for user context
|
||||
@@ -413,7 +414,7 @@ export default {
|
||||
|
||||
if (this.context === 'user') {
|
||||
if (typeof state.user?.showToolsInSidebar === 'boolean') {
|
||||
this.showToolsInSidebar = state.user.showToolsInSidebar;
|
||||
this.showToolsInSidebar = state.user?.showToolsInSidebar;
|
||||
} else {
|
||||
this.showToolsInSidebar = true;
|
||||
}
|
||||
@@ -549,20 +550,28 @@ export default {
|
||||
}
|
||||
},
|
||||
getCategoryLabel(category) {
|
||||
const labels = {
|
||||
source: this.$t('general.source'),
|
||||
'source-minimal': this.$t('general.source'),
|
||||
'source-alt': this.$t('general.source'),
|
||||
'source-hybrid': this.$t('general.source'),
|
||||
'source-hybrid-2': this.$t('general.source'),
|
||||
tool: this.$t('general.tool'),
|
||||
custom: this.$t('sidebar.customLink'),
|
||||
share: this.$t('general.share'),
|
||||
shareInfo: this.$t('share.shareInfo'),
|
||||
download: this.$t('general.download'),
|
||||
divider: this.$t('general.divider'),
|
||||
};
|
||||
return labels[category] || category;
|
||||
switch (category) {
|
||||
case 'source':
|
||||
case 'source-minimal':
|
||||
case 'source-alt':
|
||||
case 'source-hybrid':
|
||||
case 'source-hybrid-2':
|
||||
return this.$t('general.source');
|
||||
case 'tool':
|
||||
return this.$t('general.tool');
|
||||
case 'custom':
|
||||
return this.$t('sidebar.customLink');
|
||||
case 'share':
|
||||
return this.$t('general.share');
|
||||
case 'shareInfo':
|
||||
return this.$t('share.shareInfo');
|
||||
case 'download':
|
||||
return this.$t('general.download');
|
||||
case 'divider':
|
||||
return this.$t('general.divider');
|
||||
default:
|
||||
return category;
|
||||
}
|
||||
},
|
||||
getLinkDisplayName(link) {
|
||||
// For dividers, show the name or a default
|
||||
@@ -701,7 +710,8 @@ export default {
|
||||
this.tempSelectedSource = "";
|
||||
},
|
||||
editLink(index) {
|
||||
const link = this.links[index];
|
||||
const link = this.links.at(index);
|
||||
if (!link) return;
|
||||
this.editingIndex = index;
|
||||
this.showAddForm = true;
|
||||
|
||||
@@ -807,7 +817,7 @@ export default {
|
||||
event.dataTransfer.setData("text/html", event.target);
|
||||
|
||||
// Set the entire link item as the drag image
|
||||
const linkItem = this.linkItemRefs[index];
|
||||
const linkItem = getObjectProperty(this.linkItemRefs, index);
|
||||
if (linkItem) {
|
||||
// Create a clone for the drag image to avoid affecting the original
|
||||
const clone = linkItem.cloneNode(true);
|
||||
@@ -899,7 +909,7 @@ export default {
|
||||
|
||||
await usersApi.update(updatedUser, ['sidebarLinks', 'showToolsInSidebar']);
|
||||
|
||||
mutations.updateCurrentUser({
|
||||
void mutations.updateCurrentUser({
|
||||
sidebarLinks: [...this.links],
|
||||
showToolsInSidebar: this.showToolsInSidebar,
|
||||
});
|
||||
|
||||
@@ -131,7 +131,7 @@ export default {
|
||||
// Wait for navigation so Layout mounts and registers the toast callback before we toast
|
||||
await this.$router.push(this.redirect);
|
||||
}
|
||||
mutations.updateCurrentUser({ otpEnabled: true });
|
||||
void mutations.updateCurrentUser({ otpEnabled: true });
|
||||
this.succeeded = true;
|
||||
this.error = "";
|
||||
notify.showSuccessToast(this.$t("otp.verificationSucceed"));
|
||||
|
||||
@@ -123,7 +123,7 @@ import { state, mutations, getters } from "@/store";
|
||||
import { url } from "@/utils";
|
||||
import { notify } from "@/notify";
|
||||
import { resourcesApi } from "@/api";
|
||||
import { goToItem } from "@/utils/url";
|
||||
import { goToItemNotificationButton } from "@/utils/notificationActions";
|
||||
import FileList from "@/components/files/FileList.vue";
|
||||
import LoadingSpinner from "@/components/LoadingSpinner.vue";
|
||||
import ToggleSwitch from "@/components/settings/ToggleSwitch.vue";
|
||||
@@ -152,7 +152,7 @@ export default {
|
||||
watch: {
|
||||
deleteAfter(newVal) {
|
||||
// Update the user preference in real time
|
||||
mutations.updateCurrentUser({ deleteAfterArchive: newVal });
|
||||
void mutations.updateCurrentUser({ deleteAfterArchive: newVal });
|
||||
},
|
||||
showFileList(newVal) {
|
||||
if (!newVal) {
|
||||
@@ -230,7 +230,7 @@ export default {
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
if (this.newDirName && this.isDirNameValid) {
|
||||
this.createDirectory();
|
||||
void this.createDirectory();
|
||||
}
|
||||
},
|
||||
async createDirectory() {
|
||||
@@ -248,10 +248,10 @@ export default {
|
||||
await resourcesApi.post(currentSource, fullPath, "", false, undefined, {}, true);
|
||||
}
|
||||
if (getters.isShare()) {
|
||||
resourcesApi.fetchFilesPublic(currentPath, state.shareInfo?.hash)
|
||||
await resourcesApi.fetchFilesPublic(currentPath, state.shareInfo.hash)
|
||||
.then((req) => this.$refs.fileList.fillOptions(req, true));
|
||||
} else {
|
||||
resourcesApi.fetchFiles(currentSource, currentPath)
|
||||
await resourcesApi.fetchFiles(currentSource, currentPath)
|
||||
.then((req) => this.$refs.fileList.fillOptions(req, true));
|
||||
}
|
||||
mutations.setReload(true);
|
||||
@@ -280,10 +280,18 @@ export default {
|
||||
|
||||
const destPath = this.destPath;
|
||||
const destSource = toSource;
|
||||
const buttonAction = () => destPath && goToItem(destSource || state.shareInfo?.hash, destPath, {}, false, getters.isShare());
|
||||
notify.showSuccess(this.$t("prompts.unarchiveSuccess"), {
|
||||
icon: "folder",
|
||||
buttons: destPath ? [{ label: this.$t("buttons.goToItem"), primary: true, action: buttonAction }] : undefined,
|
||||
buttons: destPath
|
||||
? [
|
||||
goToItemNotificationButton(
|
||||
this.$t("buttons.goToItem"),
|
||||
destSource || state.shareInfo?.hash,
|
||||
destPath,
|
||||
getters.isShare()
|
||||
),
|
||||
]
|
||||
: undefined,
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
|
||||
@@ -56,6 +56,22 @@
|
||||
>
|
||||
{{ $t("general.folder") }}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
style="margin-left: 1em"
|
||||
@click="triggerCameraPicker"
|
||||
class="button button--flat"
|
||||
>
|
||||
{{ $t("general.camera") }}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
style="margin-left: 1em"
|
||||
@click="triggerVideoPicker"
|
||||
class="button button--flat"
|
||||
>
|
||||
{{ $t("general.video") }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -189,6 +205,10 @@
|
||||
<input ref="fileInput" @change="onFilePicked" type="file" multiple style="display: none" />
|
||||
<input ref="folderInput" @change="onFolderPicked" type="file" webkitdirectory directory multiple
|
||||
style="display: none" />
|
||||
<input ref="cameraInput" @change="onFilePicked" type="file" accept="image/*" capture="environment"
|
||||
style="display: none" />
|
||||
<input ref="videoInput" @change="onFilePicked" type="file" accept="video/*" capture="environment"
|
||||
style="display: none" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
@@ -246,6 +266,8 @@ export default {
|
||||
setup(props) {
|
||||
const fileInput = ref(null);
|
||||
const folderInput = ref(null);
|
||||
const cameraInput = ref(null);
|
||||
const videoInput = ref(null);
|
||||
const files = computed(() => uploadManager.queue);
|
||||
const isDragging = ref(false);
|
||||
const showConflictPrompt = ref(false);
|
||||
@@ -280,7 +302,7 @@ export default {
|
||||
clearAll: clearAll.value,
|
||||
},
|
||||
};
|
||||
mutations.updateCurrentUser(data);
|
||||
void mutations.updateCurrentUser(data);
|
||||
await usersApi.update(data, ["fileLoading"]);
|
||||
notify.showSuccessToast("Upload settings updated");
|
||||
} catch (e) {
|
||||
@@ -324,7 +346,7 @@ export default {
|
||||
mutations.showPrompt({
|
||||
name: "rename",
|
||||
confirm: (newName) => {
|
||||
renameUploadFolder(conflictingFolder, newName);
|
||||
void renameUploadFolder(conflictingFolder, newName);
|
||||
},
|
||||
props: { folderName: conflictingFolder }
|
||||
});
|
||||
@@ -378,7 +400,7 @@ export default {
|
||||
|
||||
watch(isUploading, (active) => {
|
||||
if (active) {
|
||||
acquireWakeLock();
|
||||
void acquireWakeLock();
|
||||
} else {
|
||||
releaseWakeLock();
|
||||
}
|
||||
@@ -423,7 +445,7 @@ export default {
|
||||
|
||||
const handleVisibilityChange = async () => {
|
||||
if (document.visibilityState === "visible" && isUploading.value) {
|
||||
acquireWakeLock();
|
||||
void acquireWakeLock();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -456,7 +478,7 @@ export default {
|
||||
await processDroppedItems(items, destination);
|
||||
} else if (first.file instanceof File && typeof first.relativePath === 'string') {
|
||||
// This is an array of objects {file, relativePath} for paste from clipboard OS.
|
||||
uploadManager.add(destination, items);
|
||||
void uploadManager.add(destination, items);
|
||||
}
|
||||
} else if (items) {
|
||||
// This case handles a FileList object from the upload prompt's own input fields.
|
||||
@@ -466,7 +488,7 @@ export default {
|
||||
};
|
||||
|
||||
onMounted(async () => {
|
||||
document.addEventListener("visibilitychange", handleVisibilityChange);
|
||||
document.addEventListener("visibilitychange", void handleVisibilityChange);
|
||||
window.addEventListener("beforeunload", handleBeforeUnload);
|
||||
uploadManager.setOnConflict(handleConflict);
|
||||
if (props.initialItems) {
|
||||
@@ -475,7 +497,7 @@ export default {
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
document.removeEventListener("visibilitychange", handleVisibilityChange);
|
||||
document.removeEventListener("visibilitychange", void handleVisibilityChange);
|
||||
window.removeEventListener("beforeunload", handleBeforeUnload);
|
||||
uploadManager.setOnConflict(() => {}); // cleanup
|
||||
releaseWakeLock();
|
||||
@@ -489,6 +511,14 @@ export default {
|
||||
if (folderInput.value) folderInput.value.click();
|
||||
};
|
||||
|
||||
const triggerCameraPicker = () => {
|
||||
if (cameraInput.value) cameraInput.value.click();
|
||||
};
|
||||
|
||||
const triggerVideoPicker = () => {
|
||||
if (videoInput.value) videoInput.value.click();
|
||||
};
|
||||
|
||||
const onFilePicked = (event) => {
|
||||
const pickedFiles = event.target.files;
|
||||
if (pickedFiles.length > 0) {
|
||||
@@ -570,7 +600,7 @@ export default {
|
||||
allFiles.forEach(files => { filesToUpload.push(...files); });
|
||||
|
||||
if (filesToUpload.length > 0) {
|
||||
uploadManager.add(destination, filesToUpload);
|
||||
void uploadManager.add(destination, filesToUpload);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -580,7 +610,7 @@ export default {
|
||||
relativePath: file.webkitRelativePath || file.name,
|
||||
}));
|
||||
if (filesToAdd.length > 0) {
|
||||
uploadManager.add(destination, filesToAdd);
|
||||
void uploadManager.add(destination, filesToAdd);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -637,10 +667,14 @@ export default {
|
||||
return {
|
||||
triggerFilePicker,
|
||||
triggerFolderPicker,
|
||||
triggerCameraPicker,
|
||||
triggerVideoPicker,
|
||||
onFilePicked,
|
||||
onFolderPicked,
|
||||
fileInput,
|
||||
folderInput,
|
||||
cameraInput,
|
||||
videoInput,
|
||||
files,
|
||||
isDragging,
|
||||
onDragEnter,
|
||||
|
||||
@@ -163,6 +163,7 @@ import Errors from "@/views/Errors.vue";
|
||||
import { notify } from "@/notify";
|
||||
import { globalVars } from "@/utils/constants";
|
||||
import { eventBus } from "@/store/eventBus";
|
||||
import { setObjectProperty } from '@/utils/object.js';
|
||||
|
||||
export default {
|
||||
name: "user-edit",
|
||||
@@ -513,7 +514,7 @@ export default {
|
||||
return "/";
|
||||
},
|
||||
openScopePicker(index) {
|
||||
const row = this.selectedSources[index];
|
||||
const row = this.selectedSources.at(index);
|
||||
if (!row?.name) {
|
||||
return;
|
||||
}
|
||||
@@ -584,7 +585,7 @@ export default {
|
||||
this.emitUserUpdate();
|
||||
},
|
||||
updateUserField(field, value) {
|
||||
this.user[field] = value;
|
||||
this.user = setObjectProperty(this.user, field, value);
|
||||
this.emitUserUpdate();
|
||||
},
|
||||
setDefaultLoginMethod() {
|
||||
|
||||
@@ -244,8 +244,9 @@ export default {
|
||||
|
||||
resolvedKey(row) {
|
||||
const k = this.itemKey;
|
||||
if (typeof row[k] !== "undefined" && row[k] !== null) {
|
||||
return String(row[k]);
|
||||
const value = getObjectProperty(row, k);
|
||||
if (value !== undefined && value !== null) {
|
||||
return String(value);
|
||||
}
|
||||
return JSON.stringify(row);
|
||||
},
|
||||
|
||||
@@ -155,7 +155,7 @@ export default {
|
||||
return getters.isLoggedIn() && !getters.routePath().startsWith("/share");
|
||||
},
|
||||
toggleClick() {
|
||||
mutations.updateCurrentUser({ singleClick: !state.user.singleClick });
|
||||
void mutations.updateCurrentUser({ singleClick: !state.user.singleClick });
|
||||
},
|
||||
toggleDarkMode() {
|
||||
mutations.toggleDarkMode();
|
||||
@@ -165,7 +165,7 @@ export default {
|
||||
if (!state.showSidebar && state.user.stickySidebar) {
|
||||
mutations.toggleSidebar();
|
||||
}
|
||||
mutations.updateCurrentUser({ stickySidebar: !state.user.stickySidebar });
|
||||
void mutations.updateCurrentUser({ stickySidebar: !state.user.stickySidebar });
|
||||
},
|
||||
navigateTo(path,hash) {
|
||||
mutations.setPreviousHistoryItem({
|
||||
|
||||
@@ -193,6 +193,7 @@ import { state, getters, mutations } from "@/store";
|
||||
import ProgressBar from "@/components/ProgressBar.vue";
|
||||
import { goToItem } from "@/utils/url";
|
||||
import { getIconClass } from "@/utils/material-symbols";
|
||||
import { getObjectProperty } from '@/utils/object.js';
|
||||
import IndexInfo from "@/components/files/IndexInfo.vue";
|
||||
import { globalVars } from "@/utils/constants";
|
||||
import { resourcesApi } from "@/api";
|
||||
@@ -233,7 +234,7 @@ export default {
|
||||
},
|
||||
isShare: () => getters.isShare(),
|
||||
hasLinks() {
|
||||
return this.sidebarLinksToDisplay?.length > 0;
|
||||
return this.sidebarLinksToDisplay.length > 0;
|
||||
},
|
||||
user: () => (state.user || {username: 'anonymous'}),
|
||||
sourceInfo() {
|
||||
@@ -266,7 +267,7 @@ export default {
|
||||
},
|
||||
sidebarLinksToDisplay() {
|
||||
// If viewing a share, use share's links
|
||||
if (getters.isShare() && state.shareInfo?.sidebarLinks && state.shareInfo.sidebarLinks.length > 0) {
|
||||
if (getters.isShare() && state.shareInfo?.sidebarLinks?.length > 0) {
|
||||
return state.shareInfo.sidebarLinks;
|
||||
}
|
||||
// If user has custom links, use those
|
||||
@@ -314,7 +315,7 @@ export default {
|
||||
},
|
||||
dropdownSourceItems() {
|
||||
return this.sourceNames.map(rawName => {
|
||||
const customLink = this.sourceLinkMap[rawName];
|
||||
const customLink = getObjectProperty(this.sourceLinkMap, rawName);
|
||||
return {
|
||||
rawName,
|
||||
displayName: customLink ? customLink.name : rawName,
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
|
||||
<script>
|
||||
import { state, getters, mutations } from "@/store";
|
||||
import { getObjectProperty } from '@/utils/object.js';
|
||||
import { settings } from "@/utils/constants";
|
||||
import { router } from "@/router";
|
||||
|
||||
@@ -40,14 +41,14 @@ export default {
|
||||
shouldShow(setting) {
|
||||
const perm = setting?.permissions || {};
|
||||
// Check if all keys in setting.perm exist in state.user.perm and have truthy values
|
||||
return Object.keys(perm).every((key) => state.user.permissions[key]);
|
||||
return Object.keys(perm).every((key) => getObjectProperty(state.user.permissions, key));
|
||||
},
|
||||
active: (view) => state.activeSettingsView === view,
|
||||
setView(view) {
|
||||
mutations.closeHovers();
|
||||
mutations.closeTopPrompt();
|
||||
if (state.route.path !== "/settings") {
|
||||
router.push({ path: "/settings", hash: `#${view}` }, () => {});
|
||||
void router.push({ path: "/settings", hash: `#${view}` }, () => {});
|
||||
} else {
|
||||
mutations.setActiveSettingsView(view);
|
||||
}
|
||||
|
||||
@@ -8,6 +8,10 @@
|
||||
<a :href="releaseUrl">{{ $t("sidebar.updateIsAvailable") }}</a>
|
||||
<i @click="setSeenUpdate" aria-label="close-banner" class="material-symbols">close</i>
|
||||
</div>
|
||||
<div v-if="showPwaInstall" class="button release-banner">
|
||||
<a href="#" @click.prevent="installPwa">{{ $t("pwa.install") }}</a>
|
||||
<i @click="dismissPwaInstall" aria-label="close-banner" class="material-symbols">close</i>
|
||||
</div>
|
||||
<SidebarSettings v-if="isSettings"></SidebarSettings>
|
||||
<SidebarGeneral v-if="!isSettings"></SidebarGeneral>
|
||||
<div class="buffer"></div>
|
||||
@@ -38,6 +42,7 @@
|
||||
<script>
|
||||
import { globalVars } from "@/utils/constants";
|
||||
import { getters, mutations, state } from "@/store"; // Import your custom store
|
||||
import { installAvailable, promptInstall } from "@/utils/pwaInstall";
|
||||
import SidebarGeneral from "./General.vue";
|
||||
import SidebarSettings from "./Settings.vue";
|
||||
|
||||
@@ -52,6 +57,7 @@ export default {
|
||||
resizeStartX: 0,
|
||||
resizeStartWidth: 0,
|
||||
previousSidebarSize: null, // Remember the previous width when switching from desktop to mobile.
|
||||
pwaInstallDismissed: sessionStorage.getItem("pwaInstallDismissed") === "true",
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
@@ -64,10 +70,10 @@ export default {
|
||||
const sidebarVisible = getters.isSidebarVisible();
|
||||
const isSticky = getters.isStickySidebar();
|
||||
if (!sidebarVisible) {
|
||||
mutations.updateCurrentUser({ stickySidebar: true });
|
||||
void mutations.updateCurrentUser({ stickySidebar: true });
|
||||
mutations.toggleSidebar();
|
||||
} else if (sidebarVisible && isSticky) {
|
||||
mutations.updateCurrentUser({ stickySidebar: false });
|
||||
void mutations.updateCurrentUser({ stickySidebar: false });
|
||||
} else {
|
||||
mutations.toggleSidebar();
|
||||
}
|
||||
@@ -114,6 +120,9 @@ export default {
|
||||
!state.user.disableUpdateNotifications
|
||||
);
|
||||
},
|
||||
showPwaInstall() {
|
||||
return installAvailable.value && !this.pwaInstallDismissed && !this.isSettings;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
getBaseFontSize() {
|
||||
@@ -168,6 +177,13 @@ export default {
|
||||
setSeenUpdate() {
|
||||
mutations.setSeenUpdate(globalVars.updateAvailable);
|
||||
},
|
||||
async installPwa() {
|
||||
await promptInstall();
|
||||
},
|
||||
dismissPwaInstall() {
|
||||
this.pwaInstallDismissed = true;
|
||||
sessionStorage.setItem("pwaInstallDismissed", "true");
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -29,7 +29,8 @@
|
||||
"goToItem": "انتقل إلى العنصر",
|
||||
"copyPath": "مسار النسخ",
|
||||
"pinItem": "تثبيت",
|
||||
"unpinItem": "إلغاء التثبيت"
|
||||
"unpinItem": "إلغاء التثبيت",
|
||||
"sendToApp": "إرسال إلى التطبيق"
|
||||
},
|
||||
"errors": {
|
||||
"forbidden": "You don't have permissions to access this.",
|
||||
@@ -245,7 +246,9 @@
|
||||
"helpText2": "مطابقة السلسلة الفرعية على أسماء الملفات أو المجلدات فقط (وليس المسارات الكاملة). حالة الأحرف: مطابقة تامة للمطابقة الحساسة لحالة الأحرف\n\n لـ OR بين المصطلحات استخدم حرف الأنبوب؛ علامات اقتباس مزدوجة لعبارة واحدة\n\nتمكين البحث بحرف بدل لمزيد من الخيارات المتقدمة.",
|
||||
"useWildcardSearch": "البحث بحرف بدل (حساس لحالة الأحرف)",
|
||||
"useWildcardSearchDescription": "خيارات أحرف البدل المدعومة:\n\n- تطابق أي تسلسل: *\n\n- مطابقة حرف واحد: ؟\n\n- فئة الأحرف: [a-z]، [0-9]\n\n- النفي: [^0-9]\n\n- حرفيًا * أو ؟ أو [ يجب أن تكون ملفوفة بين قوسين، على سبيل المثال [*]، [?]، [?]، []\n\n- جميع النتائج حساسة لحالة الأحرف",
|
||||
"openInAdvancedSearch": "فتح في البحث المتقدم"
|
||||
"openInAdvancedSearch": "فتح في البحث المتقدم",
|
||||
"caseExact": "مطابقة تراعي تمييز الأحرف الكبيرة والصغيرة",
|
||||
"caseExactDescription": "قم بمطابقة أسماء الملفات والمجلدات باستخدام الأحرف الكبيرة والصغيرة بالضبط، بدلاً من تجاهل الفروق بين الأحرف الكبيرة والصغيرة."
|
||||
},
|
||||
"settings": {
|
||||
"avoidChanges": "(أتركه فارغاً إن لم ترد تغييره)",
|
||||
@@ -572,7 +575,9 @@
|
||||
"no": "لا يوجد{suffix}",
|
||||
"to": "إلى{suffix}",
|
||||
"and": "و{suffix}",
|
||||
"or": "أو{suffix}"
|
||||
"or": "أو{suffix}",
|
||||
"camera": "الكاميرا{suffix}",
|
||||
"video": "فيديو{suffix}"
|
||||
},
|
||||
"access": {
|
||||
"rules": "قواعد الوصول",
|
||||
@@ -778,7 +783,6 @@
|
||||
"scopeAria": "قصر البحث على مجلد",
|
||||
"noTerms": "أدخل مصطلح بحث واحد على الأقل.",
|
||||
"termTooShort": "يجب ألا يقل حجم كل مصطلح بحث عن {minSearchLength} حرف على الأقل",
|
||||
"caseExact": "تطابق حساس لحالة الأحرف",
|
||||
"resultsTitle": "نتائج البحث",
|
||||
"invalidSources": "قم بتشغيل مصدر واحد على الأقل.",
|
||||
"noResults": "لا توجد ملفات أو مجلدات مطابقة."
|
||||
@@ -807,7 +811,18 @@
|
||||
"description": "عرض جميع التنبيهات من جلستك الحالية",
|
||||
"empty": "لا توجد إخطارات حتى الآن",
|
||||
"clearAll": "مسح الكل",
|
||||
"total": "{count} الإخطارات"
|
||||
"appEnabled": "إشعارات التطبيقات",
|
||||
"appEnabledDescription": "عرض إشعارات المتصفح عند انتهاء عمليات الملفات أثناء وجود علامة التبويب هذه في الخلفية. يتطلب إذنًا من المتصفح. يتم تخزينها في هذا المتصفح فقط.",
|
||||
"appUnsupported": "لا يدعم هذا المتصفح إشعارات التطبيقات.",
|
||||
"permissionGranted": "مسموح",
|
||||
"permissionDeniedHelp": "تم حظر الإشعارات. قم بتمكينها في إعدادات متصفحك لهذا الموقع.",
|
||||
"uploadTitle": "اكتمل التحميل",
|
||||
"downloadTitle": "تنزيل الملف بالكامل",
|
||||
"moveTitle": "اكتملت العملية",
|
||||
"copyTitle": "اكتملت النسخ",
|
||||
"uploadFailedTitle": "فشل التحميل",
|
||||
"downloadFailedTitle": "فشل التنزيل",
|
||||
"operationFailedTitle": "فشلت العملية"
|
||||
},
|
||||
"events": {
|
||||
"serverShutdown": "تم إيقاف تشغيل الخادم.",
|
||||
@@ -860,5 +875,8 @@
|
||||
"documentDownload": "تنزيل المستند - مكتمل",
|
||||
"editorSuccess": "تهيئة المحرر - نجاح! اكتملت جميع الخطوات.",
|
||||
"setupError": "خطأ في الإعداد"
|
||||
},
|
||||
"pwa": {
|
||||
"install": "تثبيت التطبيق"
|
||||
}
|
||||
}
|
||||
@@ -29,7 +29,8 @@
|
||||
"goToItem": "Přejít na položku",
|
||||
"copyPath": "Kopírovat cestu",
|
||||
"pinItem": "Připnout",
|
||||
"unpinItem": "Odepnout"
|
||||
"unpinItem": "Odepnout",
|
||||
"sendToApp": "Odeslat do aplikace"
|
||||
},
|
||||
"errors": {
|
||||
"forbidden": "Nemáte oprávnění k přístupu.",
|
||||
@@ -315,7 +316,9 @@
|
||||
"helpText2": "Shoda podřetězce pouze s názvy souborů nebo složek (ne s úplnými cestami). case:exact pro porovnávání s rozlišováním malých a velkých písmen.\n\n pro OR mezi výrazy použijte znak pipe; dvojité uvozovky pro jednu frázi.\n\nPovolte vyhledávání se zástupnými znaky pro pokročilejší možnosti.",
|
||||
"useWildcardSearch": "Vyhledávání se zástupnými znaky (rozlišování velkých a malých písmen)",
|
||||
"useWildcardSearchDescription": "Podporované možnosti zástupných znaků:\n\n- Shodují se s libovolnou sekvencí: *\n\n- Shoda s jedním znakem: ?\n\n- Třída znaků: [a-z], [0-9]\n\n- Negace: [^0-9]\n\n- Doslovné *, ? nebo [ je třeba zabalit do závorek, např. [*], [?], [[].\n\n- U všech výsledků se rozlišují velká a malá písmena",
|
||||
"openInAdvancedSearch": "Otevřít v rozšířeném vyhledávání"
|
||||
"openInAdvancedSearch": "Otevřít v rozšířeném vyhledávání",
|
||||
"caseExact": "Porovnání s rozlišováním velkých a malých písmen",
|
||||
"caseExactDescription": "Porovnávejte názvy souborů a složek s ohledem na přesné rozlišování velkých a malých písmen, místo aby se velká a malá písmena ignorovala."
|
||||
},
|
||||
"profileSettings": {
|
||||
"hideFileExt": "Skrýt určité soubory (podle přípony)",
|
||||
@@ -666,7 +669,9 @@
|
||||
"no": "Ne{suffix}",
|
||||
"to": "až{suffix}",
|
||||
"and": "a{suffix}",
|
||||
"or": "nebo{suffix}"
|
||||
"or": "nebo{suffix}",
|
||||
"camera": "Fotoaparát{suffix}",
|
||||
"video": "Video{suffix}"
|
||||
},
|
||||
"access": {
|
||||
"rules": "Pravidla přístupu",
|
||||
@@ -778,7 +783,6 @@
|
||||
"scopeAria": "Omezení vyhledávání na složku",
|
||||
"noTerms": "Zadejte alespoň jeden hledaný výraz.",
|
||||
"termTooShort": "Každý hledaný výraz musí mít alespoň {minSearchLength} znaků.",
|
||||
"caseExact": "Shoda rozlišující malá a velká písmena",
|
||||
"resultsTitle": "Výsledky vyhledávání",
|
||||
"invalidSources": "Zapněte alespoň jeden zdroj.",
|
||||
"noResults": "Žádné odpovídající soubory nebo složky."
|
||||
@@ -807,7 +811,18 @@
|
||||
"description": "Zobrazení všech oznámení z aktuální relace",
|
||||
"empty": "Zatím žádná oznámení",
|
||||
"clearAll": "Vymazat vše",
|
||||
"total": "{count} oznámení"
|
||||
"appEnabled": "Oznámení v aplikaci",
|
||||
"appEnabledDescription": "Zobrazovat oznámení prohlížeče po dokončení operací se soubory, když je tato karta na pozadí. Vyžaduje oprávnění prohlížeče. Uloženo pouze v tomto prohlížeči.",
|
||||
"appUnsupported": "Tento prohlížeč nepodporuje oznámení z aplikací.",
|
||||
"permissionGranted": "povoleno",
|
||||
"permissionDeniedHelp": "Oznámení jsou zablokována. Povolte je v nastavení prohlížeče pro tento web.",
|
||||
"uploadTitle": "Nahrávání dokončeno",
|
||||
"downloadTitle": "Stažení dokončeno",
|
||||
"moveTitle": "Přesun dokončen",
|
||||
"copyTitle": "Kopírování dokončeno",
|
||||
"uploadFailedTitle": "Nahrávání se nezdařilo",
|
||||
"downloadFailedTitle": "Stahování se nezdařilo",
|
||||
"operationFailedTitle": "Operace se nezdařila"
|
||||
},
|
||||
"events": {
|
||||
"serverShutdown": "Server byl vypnut.",
|
||||
@@ -860,5 +875,8 @@
|
||||
"documentDownload": "Dokument ke stažení - kompletní",
|
||||
"editorSuccess": "Inicializace editoru - úspěch! Všechny kroky byly dokončeny.",
|
||||
"setupError": "Chyba nastavení"
|
||||
},
|
||||
"pwa": {
|
||||
"install": "Nainstalovat aplikaci"
|
||||
}
|
||||
}
|
||||
@@ -29,7 +29,8 @@
|
||||
"goToItem": "Weiter zum Artikel",
|
||||
"copyPath": "Pfad kopieren",
|
||||
"pinItem": "Anheften",
|
||||
"unpinItem": "Lösen"
|
||||
"unpinItem": "Lösen",
|
||||
"sendToApp": "An App senden"
|
||||
},
|
||||
"errors": {
|
||||
"forbidden": "Sie haben keine Berechtigung dies abzurufen.",
|
||||
@@ -245,7 +246,9 @@
|
||||
"helpText2": "Teilstring-Übereinstimmung nur bei Datei- oder Ordnernamen (nicht bei vollständigen Pfaden). case:exact für Übereinstimmung unter Berücksichtigung der Groß-/Kleinschreibung\n\n für ODER zwischen Begriffen Pipe-Zeichen verwenden; doppelte Anführungszeichen für eine Phrase.\n\nAktivieren Sie die Platzhaltersuche für erweiterte Optionen.",
|
||||
"useWildcardSearch": "Wildcard-Suche (Groß- und Kleinschreibung beachten)",
|
||||
"useWildcardSearchDescription": "Unterstützte Platzhalteroptionen:\n\n- Entspricht einer beliebigen Sequenz: *\n\n- Übereinstimmung mit einem Zeichen: ?\n\n- Zeichenklasse: [a-z], [0-9]\n\n- Negation: [^0-9]\n\n- Buchstäbliche *, ? oder [ müssen in Klammern eingeschlossen werden, z. B. [*], [?], [[]\n\n- Bei allen Ergebnissen wird zwischen Groß- und Kleinschreibung unterschieden.",
|
||||
"openInAdvancedSearch": "In der erweiterten Suche öffnen"
|
||||
"openInAdvancedSearch": "In der erweiterten Suche öffnen",
|
||||
"caseExact": "Groß-/Kleinschreibung beachten",
|
||||
"caseExactDescription": "Datei- und Ordnernamen anhand der genauen Groß- und Kleinschreibung abgleichen, anstatt Groß- und Kleinschreibung zu ignorieren."
|
||||
},
|
||||
"settings": {
|
||||
"avoidChanges": "(leer lassen, um Änderungen zu vermeiden)",
|
||||
@@ -572,7 +575,9 @@
|
||||
"no": "Nein{suffix}",
|
||||
"to": "zu{suffix}",
|
||||
"and": "und{suffix}",
|
||||
"or": "oder{suffix}"
|
||||
"or": "oder{suffix}",
|
||||
"camera": "Kamera{suffix}",
|
||||
"video": "Video{suffix}"
|
||||
},
|
||||
"access": {
|
||||
"rules": "Zugangsregeln",
|
||||
@@ -778,7 +783,6 @@
|
||||
"scopeAria": "Suche auf einen Ordner beschränken",
|
||||
"noTerms": "Geben Sie mindestens einen Suchbegriff ein.",
|
||||
"termTooShort": "Jeder Suchbegriff muss mindestens {minSearchLength} Zeichen umfassen.",
|
||||
"caseExact": "Abgleich unter Berücksichtigung der Groß-/Kleinschreibung",
|
||||
"resultsTitle": "Suchergebnisse",
|
||||
"invalidSources": "Schalten Sie mindestens eine Quelle ein.",
|
||||
"noResults": "Keine passenden Dateien oder Ordner."
|
||||
@@ -807,7 +811,18 @@
|
||||
"description": "Alle Benachrichtigungen aus Ihrer aktuellen Sitzung anzeigen",
|
||||
"empty": "Noch keine Benachrichtigungen",
|
||||
"clearAll": "Alle löschen",
|
||||
"total": "{count} Benachrichtigungen"
|
||||
"appEnabled": "App-Benachrichtigungen",
|
||||
"appEnabledDescription": "Browser-Benachrichtigungen anzeigen, wenn Dateioperationen abgeschlossen sind, während sich diese Registerkarte im Hintergrund befindet. Erfordert eine Browser-Berechtigung. Wird nur in diesem Browser gespeichert.",
|
||||
"appUnsupported": "App-Benachrichtigungen werden in diesem Browser nicht unterstützt.",
|
||||
"permissionGranted": "zulässig",
|
||||
"permissionDeniedHelp": "Benachrichtigungen sind deaktiviert. Bitte aktivieren Sie sie in den Browsereinstellungen für diese Website.",
|
||||
"uploadTitle": "Upload abgeschlossen",
|
||||
"downloadTitle": "Download abgeschlossen",
|
||||
"moveTitle": "Verschieben abgeschlossen",
|
||||
"copyTitle": "Kopieren abgeschlossen",
|
||||
"uploadFailedTitle": "Der Upload ist fehlgeschlagen",
|
||||
"downloadFailedTitle": "Der Download ist fehlgeschlagen",
|
||||
"operationFailedTitle": "Vorgang fehlgeschlagen"
|
||||
},
|
||||
"events": {
|
||||
"serverShutdown": "Der Server wurde abgeschaltet.",
|
||||
@@ -860,5 +875,8 @@
|
||||
"documentDownload": "Dokument-Download - Vollständig",
|
||||
"editorSuccess": "Editor-Initialisierung - Erfolg! Alle Schritte abgeschlossen.",
|
||||
"setupError": "Setup-Fehler"
|
||||
},
|
||||
"pwa": {
|
||||
"install": "App installieren"
|
||||
}
|
||||
}
|
||||
@@ -29,7 +29,8 @@
|
||||
"goToItem": "Πηγαίνετε στο στοιχείο",
|
||||
"copyPath": "Αντιγραφή διαδρομής",
|
||||
"pinItem": "Καρφίτσωμα",
|
||||
"unpinItem": "Ξεκαρφίτσωμα"
|
||||
"unpinItem": "Ξεκαρφίτσωμα",
|
||||
"sendToApp": "Αποστολή στην εφαρμογή"
|
||||
},
|
||||
"errors": {
|
||||
"forbidden": "Δεν έχετε άδεια πρόσβασης σε αυτό.",
|
||||
@@ -245,7 +246,9 @@
|
||||
"helpText2": "Αντιστοίχιση υποσυνόλων μόνο σε ονόματα αρχείων ή φακέλων (όχι σε πλήρεις διαδρομές). case:exact για αντιστοίχιση ευαίσθητη στην πεζότητα\n\n για OR μεταξύ όρων χρησιμοποιήστε τον χαρακτήρα pipe- διπλά εισαγωγικά για μία φράση.\n\nΕνεργοποιήστε την αναζήτηση με μπαλαντέρ για πιο προηγμένες επιλογές.",
|
||||
"useWildcardSearch": "Αναζήτηση με μπαλαντέρ (ευαίσθητη στην πεζότητα)",
|
||||
"useWildcardSearchDescription": "Υποστηριζόμενες επιλογές μπαλαντέρ:\n\n- Ταιριάζει με οποιαδήποτε ακολουθία: *\n\n- Ταιριάζει με έναν χαρακτήρα: ?\n\n- Κατηγορία χαρακτήρων: [a-z], [0-9]\n\n- Άρνηση: [^0-9]\n\n- Τα κυριολεκτικά *, ?, ή [ πρέπει να περιτυλίγονται σε αγκύλες, π.χ. [*], [?], [[]\n\n- Όλα τα αποτελέσματα είναι ευαίσθητα στην πεζότητα",
|
||||
"openInAdvancedSearch": "Άνοιγμα σε Σύνθετη Αναζήτηση"
|
||||
"openInAdvancedSearch": "Άνοιγμα σε Σύνθετη Αναζήτηση",
|
||||
"caseExact": "Αντιστοίχιση με διάκριση κεφαλαίων-μικρών",
|
||||
"caseExactDescription": "Να γίνεται αντιστοίχιση ονομάτων αρχείων και φακέλων με βάση την ακριβή χρήση κεφαλαίων και μικρών γραμμάτων, αντί να αγνοούνται οι διαφορές μεταξύ κεφαλαίων και μικρών γραμμάτων."
|
||||
},
|
||||
"settings": {
|
||||
"avoidChanges": "(αφήστε το κενό για αποφυγή αλλαγών)",
|
||||
@@ -572,7 +575,9 @@
|
||||
"no": "Όχι{suffix}",
|
||||
"to": "προς{suffix}",
|
||||
"and": "και{suffix}",
|
||||
"or": "ή{suffix}"
|
||||
"or": "ή{suffix}",
|
||||
"camera": "Κάμερα{suffix}",
|
||||
"video": "Βίντεο{suffix}"
|
||||
},
|
||||
"access": {
|
||||
"rules": "Κανόνες πρόσβασης",
|
||||
@@ -778,7 +783,6 @@
|
||||
"scopeAria": "Περιορισμός της αναζήτησης σε έναν φάκελο",
|
||||
"noTerms": "Εισάγετε τουλάχιστον έναν όρο αναζήτησης.",
|
||||
"termTooShort": "Κάθε όρος αναζήτησης πρέπει να αποτελείται από τουλάχιστον {minSearchLength} χαρακτήρες.",
|
||||
"caseExact": "Αντιστοίχιση με ευαισθησία στην πεζότητα",
|
||||
"resultsTitle": "Αποτελέσματα αναζήτησης",
|
||||
"invalidSources": "Ενεργοποιήστε τουλάχιστον μία πηγή.",
|
||||
"noResults": "Δεν υπάρχουν ταιριαστά αρχεία ή φάκελοι."
|
||||
@@ -807,7 +811,18 @@
|
||||
"description": "Προβολή όλων των ειδοποιήσεων από την τρέχουσα συνεδρία σας",
|
||||
"empty": "Δεν υπάρχουν ακόμη ειδοποιήσεις",
|
||||
"clearAll": "Εκκαθάριση όλων",
|
||||
"total": "{count} ειδοποιήσεις"
|
||||
"appEnabled": "Ειδοποιήσεις εφαρμογών",
|
||||
"appEnabledDescription": "Εμφάνιση ειδοποιήσεων του προγράμματος περιήγησης όταν ολοκληρώνονται οι λειτουργίες αρχείων ενώ αυτή η καρτέλα βρίσκεται στο παρασκήνιο. Απαιτείται άδεια από το πρόγραμμα περιήγησης. Αποθηκεύεται μόνο σε αυτό το πρόγραμμα περιήγησης.",
|
||||
"appUnsupported": "Οι ειδοποιήσεις εφαρμογών δεν υποστηρίζονται σε αυτόν τον περιηγητή.",
|
||||
"permissionGranted": "επιτρέπεται",
|
||||
"permissionDeniedHelp": "Οι ειδοποιήσεις έχουν αποκλειστεί. Ενεργοποιήστε τις στις ρυθμίσεις του προγράμματος περιήγησής σας για αυτόν τον ιστότοπο.",
|
||||
"uploadTitle": "Η μεταφόρτωση ολοκληρώθηκε",
|
||||
"downloadTitle": "Η λήψη ολοκληρώθηκε",
|
||||
"moveTitle": "Η μετακίνηση ολοκληρώθηκε",
|
||||
"copyTitle": "Η αντιγραφή ολοκληρώθηκε",
|
||||
"uploadFailedTitle": "Η μεταφόρτωση απέτυχε",
|
||||
"downloadFailedTitle": "Η λήψη απέτυχε",
|
||||
"operationFailedTitle": "Η λειτουργία απέτυχε"
|
||||
},
|
||||
"events": {
|
||||
"serverShutdown": "Ο διακομιστής τερματίστηκε.",
|
||||
@@ -860,5 +875,8 @@
|
||||
"documentDownload": "Λήψη εγγράφων - Πλήρης",
|
||||
"editorSuccess": "Αρχικοποίηση επεξεργαστή - Επιτυχία! Όλα τα βήματα ολοκληρώθηκαν.",
|
||||
"setupError": "Σφάλμα ρύθμισης"
|
||||
},
|
||||
"pwa": {
|
||||
"install": "Εγκατάσταση εφαρμογής"
|
||||
}
|
||||
}
|
||||
@@ -87,6 +87,8 @@
|
||||
"download": "Download{suffix}",
|
||||
"file": "File{suffix}",
|
||||
"folder": "Folder{suffix}",
|
||||
"camera": "Camera{suffix}",
|
||||
"video": "Video{suffix}",
|
||||
"info": "Info{suffix}",
|
||||
"move": "Move{suffix}",
|
||||
"new": "New{suffix}",
|
||||
@@ -148,7 +150,8 @@
|
||||
"goToItem": "Go to item",
|
||||
"goToSource": "Go to Source Location",
|
||||
"pinItem": "Pin",
|
||||
"unpinItem": "Unpin"
|
||||
"unpinItem": "Unpin",
|
||||
"sendToApp": "Send to app"
|
||||
},
|
||||
"errors": {
|
||||
"forbidden": "You don't have permissions to access this.",
|
||||
@@ -432,8 +435,10 @@
|
||||
"showPreviewImagesDescription": "Display thumbnail previews for search results when available, and show larger icons for all results.",
|
||||
"useWildcardSearch": "Wildcard search",
|
||||
"useWildcardSearchDescription": "Supported wildcard options:\n\n• Match any sequence: *\n\n• Match one character: ?\n\n• Character class: [a-z], [0-9]\n\n• Negation: [^0-9]\n\n• Literal *, ?, or [ need to be wrapped in brackets, e.g. [*], [?], [[]",
|
||||
"caseExact": "Case-sensitive match",
|
||||
"caseExactDescription": "Match file and folder names using exact letter casing instead of ignoring upper and lower case.",
|
||||
"helpText1": "Search occurs on each character you type ({minSearchLength} character minimum).",
|
||||
"helpText2": "Substring match on file or folder names only (not full paths). case:exact for case-sensitive matching\n\n for OR between terms use pipe character; double quotes for one phrase.\n\nEnable wildcard search for more advanced options.",
|
||||
"helpText2": "Case-insensitive substring match on file or folder names only (not full paths). Space-separated words match across common delimiters like periods, underscores, and hyphens (e.g. \"Blade Runner\" finds Blade.Runner.mkv). Use case:exact for case-sensitive matching.\n\nFor OR between terms use the pipe character; use double quotes for an exact phrase with spaces.\n\nEnable wildcard search for advanced glob patterns (*, ?, [a-z]).",
|
||||
"openInAdvancedSearch": "Open in Advanced Search"
|
||||
},
|
||||
"profileSettings": {
|
||||
@@ -766,7 +771,6 @@
|
||||
"scopeAria": "Limit search to a folder",
|
||||
"noTerms": "Enter at least one search term.",
|
||||
"termTooShort": "Each search term must be at least {minSearchLength} characters",
|
||||
"caseExact": "Case-sensitive match",
|
||||
"resultsTitle": "Search results",
|
||||
"invalidSources": "Turn on at least one source.",
|
||||
"noResults": "No matching files or folders."
|
||||
@@ -798,7 +802,18 @@
|
||||
"description": "View all notifications from your current session",
|
||||
"empty": "No notifications yet",
|
||||
"clearAll": "Clear All",
|
||||
"total": "{count} notifications"
|
||||
"appEnabled": "App notifications",
|
||||
"appEnabledDescription": "Show browser notifications when file operations finish while this tab is in the background. Requires browser permission. Stored in this browser only.",
|
||||
"appUnsupported": "App notifications are not supported in this browser.",
|
||||
"permissionGranted": "allowed",
|
||||
"permissionDeniedHelp": "Notifications are blocked. Enable them in your browser settings for this site.",
|
||||
"uploadTitle": "Upload complete",
|
||||
"downloadTitle": "Download complete",
|
||||
"moveTitle": "Move complete",
|
||||
"copyTitle": "Copy complete",
|
||||
"uploadFailedTitle": "Upload failed",
|
||||
"downloadFailedTitle": "Download failed",
|
||||
"operationFailedTitle": "Operation failed"
|
||||
},
|
||||
"duplicateFinder": {
|
||||
"minSize": "Minimum Size (MB){suffix}",
|
||||
@@ -860,5 +875,8 @@
|
||||
"documentDownload": "Document Download - Complete",
|
||||
"editorSuccess": "Editor Initialization - Success! All steps completed.",
|
||||
"setupError": "Setup Error"
|
||||
},
|
||||
"pwa": {
|
||||
"install": "Install app"
|
||||
}
|
||||
}
|
||||
@@ -29,7 +29,8 @@
|
||||
"goToItem": "Abrir",
|
||||
"copyPath": "Copiar ruta",
|
||||
"pinItem": "Fijar",
|
||||
"unpinItem": "Desfijar"
|
||||
"unpinItem": "Desfijar",
|
||||
"sendToApp": "Enviar a la aplicación"
|
||||
},
|
||||
"errors": {
|
||||
"forbidden": "No tienes permisos para acceder.",
|
||||
@@ -245,7 +246,9 @@
|
||||
"helpText2": "Coincidencia de subcadenas sólo en nombres de archivos o carpetas (no en rutas completas). case:exact para coincidencia sensible a mayúsculas y minúsculas.\n\n para OR entre términos utilice el carácter pipa; comillas dobles para una frase.\n\nHabilite la búsqueda con comodines para opciones más avanzadas.",
|
||||
"useWildcardSearch": "Búsqueda con comodín (distingue mayúsculas de minúsculas)",
|
||||
"useWildcardSearchDescription": "Opciones de comodín admitidas:\n\n- Coinciden con cualquier secuencia: *\n\n- Coincidir con un carácter: ?\n\n- Clase de caracteres: [a-z], [0-9]\n\n- Negación: [^0-9]\n\n- Los caracteres *, ? o [ deben ir entre paréntesis, por ejemplo [*], [?], [[].\n\n- Todos los resultados distinguen entre mayúsculas y minúsculas",
|
||||
"openInAdvancedSearch": "Abrir en búsqueda avanzada"
|
||||
"openInAdvancedSearch": "Abrir en búsqueda avanzada",
|
||||
"caseExact": "Coincidencia que distingue entre mayúsculas y minúsculas",
|
||||
"caseExactDescription": "Compara los nombres de archivos y carpetas utilizando el uso exacto de mayúsculas y minúsculas, en lugar de ignorar la distinción entre mayúsculas y minúsculas."
|
||||
},
|
||||
"settings": {
|
||||
"scopes": "Fuentes a las que el usuario tendrá acceso:",
|
||||
@@ -572,7 +575,9 @@
|
||||
"no": "No{suffix}",
|
||||
"to": "a{suffix}",
|
||||
"and": "y{suffix}",
|
||||
"or": "o{suffix}"
|
||||
"or": "o{suffix}",
|
||||
"camera": "Cámara{suffix}",
|
||||
"video": "Vídeo{suffix}"
|
||||
},
|
||||
"access": {
|
||||
"rules": "Reglas de acceso",
|
||||
@@ -778,7 +783,6 @@
|
||||
"scopeAria": "Limitar la búsqueda a una carpeta",
|
||||
"noTerms": "Introduzca al menos un término de búsqueda.",
|
||||
"termTooShort": "Cada término de búsqueda debe tener al menos {minSearchLength} caracteres",
|
||||
"caseExact": "Coincidencia entre mayúsculas y minúsculas",
|
||||
"resultsTitle": "Resultados de la búsqueda",
|
||||
"invalidSources": "Enciende al menos una fuente.",
|
||||
"noResults": "No hay archivos o carpetas coincidentes."
|
||||
@@ -807,7 +811,18 @@
|
||||
"description": "Aquí verás todas las notificaciones de tu sesión actual",
|
||||
"empty": "Aún no tienes notificaciones",
|
||||
"clearAll": "Borrar todo",
|
||||
"total": "{count} notificaciones"
|
||||
"appEnabled": "Notificaciones de la aplicación",
|
||||
"appEnabledDescription": "Mostrar notificaciones del navegador cuando finalicen las operaciones con archivos mientras esta pestaña está en segundo plano. Requiere permiso del navegador. Se guarda únicamente en este navegador.",
|
||||
"appUnsupported": "Este navegador no admite notificaciones de aplicaciones.",
|
||||
"permissionGranted": "permitido",
|
||||
"permissionDeniedHelp": "Las notificaciones están bloqueadas. Actívalas en la configuración de tu navegador para este sitio web.",
|
||||
"uploadTitle": "Carga completada",
|
||||
"downloadTitle": "Descarga completada",
|
||||
"moveTitle": "Movimiento completado",
|
||||
"copyTitle": "Copia completada",
|
||||
"uploadFailedTitle": "Error al subir el archivo",
|
||||
"downloadFailedTitle": "Error al descargar",
|
||||
"operationFailedTitle": "La operación ha fallado"
|
||||
},
|
||||
"events": {
|
||||
"serverShutdown": "El servidor fúe apagado",
|
||||
@@ -860,5 +875,8 @@
|
||||
"documentDownload": "Descarga de documentos - Completa",
|
||||
"editorSuccess": "Inicialización del editor - ¡Éxito! Todos los pasos se completaron.",
|
||||
"setupError": "Error de configuración"
|
||||
},
|
||||
"pwa": {
|
||||
"install": "Instalar la aplicación"
|
||||
}
|
||||
}
|
||||
@@ -29,7 +29,8 @@
|
||||
"goToItem": "Aller à l'article",
|
||||
"copyPath": "Chemin de la copie",
|
||||
"pinItem": "Épingler",
|
||||
"unpinItem": "Détacher"
|
||||
"unpinItem": "Détacher",
|
||||
"sendToApp": "Envoyer vers l'application"
|
||||
},
|
||||
"errors": {
|
||||
"forbidden": "Vous n'avez pas la permission d'accéder à cela.",
|
||||
@@ -245,7 +246,9 @@
|
||||
"helpText2": "Correspondance de sous-chaînes sur les noms de fichiers ou de dossiers uniquement (pas sur les chemins d'accès complets). case:exact pour une correspondance sensible à la casse.\n\n pour le OU entre les termes, utiliser le caractère pipe ; les guillemets doubles pour une seule phrase.\n\nActivez la recherche par caractères génériques pour obtenir des options plus avancées.",
|
||||
"useWildcardSearch": "Recherche par caractères génériques (sensible à la casse)",
|
||||
"useWildcardSearchDescription": "Options de caractères génériques prises en charge :\n\n- Correspond à n'importe quelle séquence : *\n\n- Correspondre à un seul caractère : ?\n\n- Classe de caractères : [a-z], [0-9]\n\n- Négation : [^0-9]\n\n- Les valeurs littérales *, ? ou [ doivent être mises entre parenthèses, par exemple [*], [ ?], [[]].\n\n- Tous les résultats sont sensibles à la casse",
|
||||
"openInAdvancedSearch": "Ouvrir en recherche avancée"
|
||||
"openInAdvancedSearch": "Ouvrir en recherche avancée",
|
||||
"caseExact": "Correspondance sensible à la casse",
|
||||
"caseExactDescription": "Comparer les noms de fichiers et de dossiers en tenant compte de la casse exacte, plutôt qu'en ignorant la distinction entre majuscules et minuscules."
|
||||
},
|
||||
"settings": {
|
||||
"scopes": "Définir les sources autorisées et la portée de l'utilisateur au sein de celles-ci",
|
||||
@@ -572,7 +575,9 @@
|
||||
"no": "Non{suffix}",
|
||||
"to": "à{suffix}",
|
||||
"and": "et{suffix}",
|
||||
"or": "ou{suffix}"
|
||||
"or": "ou{suffix}",
|
||||
"camera": "Appareil photo{suffix}",
|
||||
"video": "Vidéo{suffix}"
|
||||
},
|
||||
"access": {
|
||||
"rules": "Règles d'accès",
|
||||
@@ -778,7 +783,6 @@
|
||||
"scopeAria": "Limiter la recherche à un dossier",
|
||||
"noTerms": "Saisissez au moins un terme de recherche.",
|
||||
"termTooShort": "Chaque terme de recherche doit comporter au moins {minSearchLength} caractères.",
|
||||
"caseExact": "Correspondance sensible à la casse",
|
||||
"resultsTitle": "Résultats de la recherche",
|
||||
"invalidSources": "Allumez au moins une source.",
|
||||
"noResults": "Aucun fichier ou dossier correspondant."
|
||||
@@ -807,7 +811,18 @@
|
||||
"description": "Afficher toutes les notifications de la session en cours",
|
||||
"empty": "Aucune notification pour l'instant",
|
||||
"clearAll": "Tout effacer",
|
||||
"total": "{count} notifications"
|
||||
"appEnabled": "Notifications de l'application",
|
||||
"appEnabledDescription": "Afficher les notifications du navigateur lorsque les opérations sur les fichiers sont terminées alors que cet onglet est en arrière-plan. Nécessite l'autorisation du navigateur. Enregistré uniquement dans ce navigateur.",
|
||||
"appUnsupported": "Les notifications de l'application ne sont pas prises en charge par ce navigateur.",
|
||||
"permissionGranted": "autorisé",
|
||||
"permissionDeniedHelp": "Les notifications sont désactivées. Activez-les dans les paramètres de votre navigateur pour ce site.",
|
||||
"uploadTitle": "Téléchargement terminé",
|
||||
"downloadTitle": "Téléchargement terminé",
|
||||
"moveTitle": "Déménagement terminé",
|
||||
"copyTitle": "Copie terminée",
|
||||
"uploadFailedTitle": "Échec du téléchargement",
|
||||
"downloadFailedTitle": "Échec du téléchargement",
|
||||
"operationFailedTitle": "Échec de l'opération"
|
||||
},
|
||||
"events": {
|
||||
"serverShutdown": "Le serveur a été arrêté.",
|
||||
@@ -860,5 +875,8 @@
|
||||
"documentDownload": "Téléchargement du document - Complet",
|
||||
"editorSuccess": "Initialisation de l'éditeur - Succès ! Toutes les étapes sont terminées.",
|
||||
"setupError": "Erreur de configuration"
|
||||
},
|
||||
"pwa": {
|
||||
"install": "Installer l'application"
|
||||
}
|
||||
}
|
||||
@@ -29,7 +29,8 @@
|
||||
"goToItem": "עבור לפריט",
|
||||
"copyPath": "העתק נתיב",
|
||||
"pinItem": "נעץ",
|
||||
"unpinItem": "הסר נעיצה"
|
||||
"unpinItem": "הסר נעיצה",
|
||||
"sendToApp": "שלח לאפליקציה"
|
||||
},
|
||||
"errors": {
|
||||
"forbidden": "אין לך הרשאות גישה",
|
||||
@@ -245,7 +246,9 @@
|
||||
"helpText2": "התאמת תת-מחרוזת רק לשמות קבצים או תיקיות (לא לנתיבים מלאים). case:exact להתאמה רגישה לאותיות רישיות וקטנות; השתמש בסימן הצינור (|) לחיבור בין מונחים באמצעות \"או\"; השתמש במרכאות כפולות לביטוי אחד. הפעל חיפוש עם תווים כלליים לקבלת אפשרויות מתקדמות יותר.",
|
||||
"useWildcardSearch": "חיפוש עם תווים כלליים (רגיש לאותיות רישיות)",
|
||||
"useWildcardSearchDescription": "אפשרויות תווים כלליים נתמכות: • התאמה לכל רצף: * • התאמה לתו בודד: ? • קבוצת תווים: [a-z], [0-9] • שלילה: [^0-9] • יש להקיף את התווים *, ? או [ בסוגריים, לדוגמה: [*], [?], [[] • כל התוצאות רגישות לאותיות גדולות וקטנות",
|
||||
"openInAdvancedSearch": "פתח בחיפוש מתקדם"
|
||||
"openInAdvancedSearch": "פתח בחיפוש מתקדם",
|
||||
"caseExact": "התאמה רגישה לאותיות רישיות",
|
||||
"caseExactDescription": "התאם שמות קבצים ותיקיות על פי רישיות אותיות מדויקות, במקום להתעלם מההבדל בין אותיות גדולות לקטנות."
|
||||
},
|
||||
"settings": {
|
||||
"avoidChanges": "(השאר ריק כדי למנוע שינויים)",
|
||||
@@ -572,7 +575,9 @@
|
||||
"no": "לא{suffix}",
|
||||
"to": "אל{suffix}",
|
||||
"and": "ו-{suffix}",
|
||||
"or": "או{suffix}"
|
||||
"or": "או{suffix}",
|
||||
"camera": "מצלמה{suffix}",
|
||||
"video": "סרטון{suffix}"
|
||||
},
|
||||
"access": {
|
||||
"rules": "כללי גישה",
|
||||
@@ -778,7 +783,6 @@
|
||||
"scopeAria": "הגבל את החיפוש לתיקיה",
|
||||
"noTerms": "הזן לפחות מונח חיפוש אחד.",
|
||||
"termTooShort": "כל מונח חיפוש חייב להכיל לפחות {minSearchLength} תווים",
|
||||
"caseExact": "התאמה רגישה לאותיות רישיות",
|
||||
"resultsTitle": "תוצאות החיפוש",
|
||||
"invalidSources": "הפעל לפחות מקור אחד.",
|
||||
"noResults": "אין קבצים או תיקיות תואמים."
|
||||
@@ -807,7 +811,18 @@
|
||||
"description": "הצג את כל ההודעות מהפעלה הנוכחית שלך",
|
||||
"empty": "אין עדיין הודעות",
|
||||
"clearAll": "נקה הכל",
|
||||
"total": "{count} הודעות"
|
||||
"appEnabled": "התראות באפליקציה",
|
||||
"appEnabledDescription": "הצג התראות דפדפן עם סיום פעולות הקובץ כאשר הכרטיסייה נמצאת ברקע. דורש הרשאת דפדפן. נשמר בדפדפן זה בלבד.",
|
||||
"appUnsupported": "הודעות האפליקציה אינן נתמכות בדפדפן זה.",
|
||||
"permissionGranted": "מותר",
|
||||
"permissionDeniedHelp": "ההודעות חסומות. הפעל אותן בהגדרות הדפדפן עבור אתר זה.",
|
||||
"uploadTitle": "ההעלאה הושלמה",
|
||||
"downloadTitle": "ההורדה הושלמה",
|
||||
"moveTitle": "הפעולה הושלמה",
|
||||
"copyTitle": "ההעתקה הושלמה",
|
||||
"uploadFailedTitle": "ההעלאה נכשלה",
|
||||
"downloadFailedTitle": "ההורדה נכשלה",
|
||||
"operationFailedTitle": "הפעולה נכשלה"
|
||||
},
|
||||
"events": {
|
||||
"serverShutdown": "השרת כובה.",
|
||||
@@ -860,5 +875,8 @@
|
||||
"documentDownload": "הורדת מסמך - מלא",
|
||||
"editorSuccess": "אתחול העורך - הצלחה! כל השלבים הושלמו.",
|
||||
"setupError": "שגיאת התקנה"
|
||||
},
|
||||
"pwa": {
|
||||
"install": "התקן את האפליקציה"
|
||||
}
|
||||
}
|
||||
@@ -29,7 +29,8 @@
|
||||
"goToItem": "Tovább a tételhez",
|
||||
"copyPath": "Másolás útvonal",
|
||||
"pinItem": "Rögzítés",
|
||||
"unpinItem": "Rögzítés feloldása"
|
||||
"unpinItem": "Rögzítés feloldása",
|
||||
"sendToApp": "Küldés az alkalmazásba"
|
||||
},
|
||||
"errors": {
|
||||
"forbidden": "Nincs jogosultsága a hozzáféréshez.",
|
||||
@@ -245,7 +246,9 @@
|
||||
"helpText2": "Csak fájl- vagy mappanevek (nem teljes elérési utak) részlánc-illesztése. case:exact a nagy- és kisbetű-érzékeny illesztéshez.\n\n a kifejezések közötti VAGY-hoz használja a pipe karaktert; dupla idézőjelek egy kifejezéshez.\n\nEngedélyezze a vadkártyás keresést a fejlettebb lehetőségekhez.",
|
||||
"useWildcardSearch": "Wildcard keresés (nagy- és kisbetű-érzékeny)",
|
||||
"useWildcardSearchDescription": "Támogatott helyettesítő opciók:\n\n- Bármilyen sorozatra illeszkedik: *\n\n- Egy karakterrel való egyezés: ?\n\n- Karakterosztály: [a-z], [0-9]\n\n- Negáció: [^0-9]\n\n- A szó szerinti *, ? vagy [ karaktereket zárójelbe kell tenni, pl. [*], [?], [[]\n\n- Minden eredmény nagy- és kisbetű-érzékeny",
|
||||
"openInAdvancedSearch": "Megnyitás a bővített keresőben"
|
||||
"openInAdvancedSearch": "Megnyitás a bővített keresőben",
|
||||
"caseExact": "Kis- és nagybetűk közötti különbséget figyelembe vevő keresés",
|
||||
"caseExactDescription": "A fájl- és mappanevek összehasonlításakor vegye figyelembe a betűk pontos írásmódját, ne pedig a kis- és nagybetűk közötti különbséget."
|
||||
},
|
||||
"settings": {
|
||||
"avoidChanges": "(üresen hagyva nincs változás)",
|
||||
@@ -572,7 +575,9 @@
|
||||
"no": "Nem{suffix}",
|
||||
"to": "hogy{suffix}",
|
||||
"and": "és{suffix}",
|
||||
"or": "vagy{suffix}"
|
||||
"or": "vagy{suffix}",
|
||||
"camera": "Fényképezőgép{suffix}",
|
||||
"video": "Videó{suffix}"
|
||||
},
|
||||
"access": {
|
||||
"rules": "Hozzáférési szabályok",
|
||||
@@ -778,7 +783,6 @@
|
||||
"scopeAria": "Keresés korlátozása egy mappára",
|
||||
"noTerms": "Adjon meg legalább egy keresési kifejezést.",
|
||||
"termTooShort": "Minden keresési kifejezésnek legalább {minSearchLength} karakterből kell állnia.",
|
||||
"caseExact": "Nagy- és kisbetű-érzékeny egyezés",
|
||||
"resultsTitle": "Keresési eredmények",
|
||||
"invalidSources": "Kapcsoljon be legalább egy forrást.",
|
||||
"noResults": "Nincsenek megfelelő fájlok vagy mappák."
|
||||
@@ -807,7 +811,18 @@
|
||||
"description": "Az aktuális munkamenet összes értesítésének megtekintése",
|
||||
"empty": "Még nincsenek értesítések",
|
||||
"clearAll": "Mindent törölni",
|
||||
"total": "{count} értesítések"
|
||||
"appEnabled": "Alkalmazás-értesítések",
|
||||
"appEnabledDescription": "A böngésző értesítéseket jelenít meg, ha fájlműveletek fejeződnek be, miközben ez a lap háttérben van. Ehhez böngészői engedély szükséges. Csak ebben a böngészőben kerül tárolásra.",
|
||||
"appUnsupported": "Ez a böngésző nem támogatja az alkalmazásértesítéseket.",
|
||||
"permissionGranted": "megengedett",
|
||||
"permissionDeniedHelp": "Az értesítések letiltva vannak. Kérjük, engedélyezze őket a böngésző beállításaiban ezen a webhelyen.",
|
||||
"uploadTitle": "A feltöltés befejeződött",
|
||||
"downloadTitle": "Letöltés befejezve",
|
||||
"moveTitle": "A művelet befejeződött",
|
||||
"copyTitle": "Másolás befejezve",
|
||||
"uploadFailedTitle": "A feltöltés nem sikerült",
|
||||
"downloadFailedTitle": "A letöltés nem sikerült",
|
||||
"operationFailedTitle": "A művelet sikertelen"
|
||||
},
|
||||
"events": {
|
||||
"serverShutdown": "A kiszolgálót leállították.",
|
||||
@@ -860,5 +875,8 @@
|
||||
"documentDownload": "Dokumentum letöltése - Teljes",
|
||||
"editorSuccess": "Szerkesztő inicializálása - Sikeres! Minden lépés befejeződött.",
|
||||
"setupError": "Beállítási hiba"
|
||||
},
|
||||
"pwa": {
|
||||
"install": "Alkalmazás telepítése"
|
||||
}
|
||||
}
|
||||
@@ -1022,8 +1022,7 @@
|
||||
"description": "__NOT_TRANSLATED__",
|
||||
"empty": "__NOT_TRANSLATED__",
|
||||
"clearAll": "__NOT_TRANSLATED__",
|
||||
"clearConfirm": "__NOT_TRANSLATED__",
|
||||
"total": "__NOT_TRANSLATED__"
|
||||
"clearConfirm": "__NOT_TRANSLATED__"
|
||||
},
|
||||
"duplicateFinder": {
|
||||
"title": "__NOT_TRANSLATED__",
|
||||
|
||||
@@ -116,7 +116,9 @@
|
||||
"no": "No{suffix}",
|
||||
"to": "a{suffix}",
|
||||
"and": "e{suffix}",
|
||||
"or": "oppure{suffix}"
|
||||
"or": "oppure{suffix}",
|
||||
"camera": "Fotocamera{suffix}",
|
||||
"video": "Video{suffix}"
|
||||
},
|
||||
"buttons": {
|
||||
"copySuccess": "Copiato negli appunti!",
|
||||
@@ -148,7 +150,8 @@
|
||||
"goToSource": "Vai alla posizione sorgente",
|
||||
"copyPath": "Percorso di copia",
|
||||
"pinItem": "Appunta",
|
||||
"unpinItem": "Rimuovi puntina"
|
||||
"unpinItem": "Rimuovi puntina",
|
||||
"sendToApp": "Invia all'app"
|
||||
},
|
||||
"errors": {
|
||||
"forbidden": "Non hai i permessi per accedere a questa risorsa.",
|
||||
@@ -434,7 +437,9 @@
|
||||
"helpText2": "Corrispondenza di sottostringhe solo sui nomi di file o cartelle (non sui percorsi completi). case:exact per una corrispondenza sensibile alle maiuscole e alle minuscole.\n\n per OR tra termini usare il carattere pipe; doppi apici per una frase.\n\nAbilitare la ricerca con caratteri jolly per opzioni più avanzate.",
|
||||
"useWildcardSearch": "Ricerca con caratteri jolly (sensibile alle maiuscole)",
|
||||
"useWildcardSearchDescription": "Opzioni jolly supportate:\n\n- Corrisponde a qualsiasi sequenza: *\n\n- Corrisponde a un solo carattere: ?\n\n- Classe di caratteri: [a-z], [0-9]\n\n- Negazione: [^0-9]\n\n- I caratteri letterali *, ?, o [ devono essere racchiusi tra parentesi, ad esempio [*], [?], [[]\n\n- Tutti i risultati sono sensibili alle maiuscole e alle minuscole",
|
||||
"openInAdvancedSearch": "Aprire la ricerca avanzata"
|
||||
"openInAdvancedSearch": "Aprire la ricerca avanzata",
|
||||
"caseExact": "Corrispondenza che distingue tra maiuscole e minuscole",
|
||||
"caseExactDescription": "Abbinare i nomi di file e cartelle utilizzando la distinzione esatta tra maiuscole e minuscole, anziché ignorarla."
|
||||
},
|
||||
"profileSettings": {
|
||||
"hideFileExt": "Nascondere determinati file (per estensione)",
|
||||
@@ -787,7 +792,6 @@
|
||||
"scopeAria": "Limitare la ricerca a una cartella",
|
||||
"noTerms": "Inserite almeno un termine di ricerca.",
|
||||
"termTooShort": "Ogni termine di ricerca deve essere composto da almeno {minSearchLength} caratteri.",
|
||||
"caseExact": "Corrispondenza sensibile alle maiuscole e alle minuscole",
|
||||
"resultsTitle": "Risultati della ricerca",
|
||||
"invalidSources": "Accendere almeno una sorgente.",
|
||||
"noResults": "Nessun file o cartella corrispondente."
|
||||
@@ -798,7 +802,18 @@
|
||||
"description": "Visualizza tutte le notifiche della sessione corrente",
|
||||
"empty": "Ancora nessuna notifica",
|
||||
"clearAll": "Pulisci tutto",
|
||||
"total": "{count} notifiche"
|
||||
"appEnabled": "Notifiche delle app",
|
||||
"appEnabledDescription": "Mostra le notifiche del browser al termine delle operazioni sui file quando questa scheda è in secondo piano. Richiede l'autorizzazione del browser. Memorizzato solo in questo browser.",
|
||||
"appUnsupported": "Le notifiche delle app non sono supportate in questo browser.",
|
||||
"permissionGranted": "ammesso",
|
||||
"permissionDeniedHelp": "Le notifiche sono bloccate. Abilitale nelle impostazioni del browser per questo sito.",
|
||||
"uploadTitle": "Caricamento completato",
|
||||
"downloadTitle": "Download completato",
|
||||
"moveTitle": "Operazione completata",
|
||||
"copyTitle": "Copia completata",
|
||||
"uploadFailedTitle": "Caricamento non riuscito",
|
||||
"downloadFailedTitle": "Il download non è andato a buon fine",
|
||||
"operationFailedTitle": "Operazione non riuscita"
|
||||
},
|
||||
"duplicateFinder": {
|
||||
"minSize": "Dimensione Minima (MB){suffix}",
|
||||
@@ -860,5 +875,8 @@
|
||||
"documentDownload": "Download Documento - Completato",
|
||||
"editorSuccess": "Inizializzazione Editor - Successo! Tutti i passaggi completati.",
|
||||
"setupError": "Errore Setup"
|
||||
},
|
||||
"pwa": {
|
||||
"install": "Installa l'app"
|
||||
}
|
||||
}
|
||||
@@ -29,7 +29,8 @@
|
||||
"goToItem": "アイテムへ",
|
||||
"copyPath": "コピーパス",
|
||||
"pinItem": "ピン留め",
|
||||
"unpinItem": "ピン留めを解除"
|
||||
"unpinItem": "ピン留めを解除",
|
||||
"sendToApp": "アプリに送信"
|
||||
},
|
||||
"errors": {
|
||||
"forbidden": "このリソースへのアクセス権限がありません。",
|
||||
@@ -245,7 +246,9 @@
|
||||
"helpText2": "ファイル名またはフォルダ名のみ(フルパスではない)の部分文字列マッチ case:exactで大文字小文字を区別してマッチング\n\n パイプ文字を使用し、1つのフレーズを二重引用符で囲みます。\n\nワイルドカード検索を有効にすると、より高度なオプションを使用できます。",
|
||||
"useWildcardSearch": "ワイルドカード検索(大文字と小文字を区別)",
|
||||
"useWildcardSearchDescription": "サポートされているワイルドカードオプション:\n\n- 任意のシーケンスにマッチします:*\n\n- 1文字にマッチ: ?\n\n- 文字クラス:[a-z], [0-9]\n\n- 否定[^0-9]\n\n- 例えば、[*]、[?]、[[]のように、*、?\n\n- すべての結果は大文字と小文字を区別します。",
|
||||
"openInAdvancedSearch": "詳細検索で開く"
|
||||
"openInAdvancedSearch": "詳細検索で開く",
|
||||
"caseExact": "大文字と小文字を区別する一致",
|
||||
"caseExactDescription": "大文字と小文字を区別せずに処理するのではなく、ファイル名とフォルダ名の文字の大文字・小文字を厳密に一致させる。"
|
||||
},
|
||||
"settings": {
|
||||
"avoidChanges": "(変更を避けるために空白にしてください)",
|
||||
@@ -572,7 +575,9 @@
|
||||
"no": "いいえ{suffix}",
|
||||
"to": "~へ{suffix}",
|
||||
"and": "そして{suffix}",
|
||||
"or": "または{suffix}"
|
||||
"or": "または{suffix}",
|
||||
"camera": "カメラ{suffix}",
|
||||
"video": "動画{suffix}"
|
||||
},
|
||||
"access": {
|
||||
"rules": "アクセスルール",
|
||||
@@ -778,7 +783,6 @@
|
||||
"scopeAria": "検索対象をフォルダに限定する",
|
||||
"noTerms": "少なくとも1つの検索語を入力してください。",
|
||||
"termTooShort": "各検索語は{minSearchLength} 文字以上でなければなりません。",
|
||||
"caseExact": "大文字と小文字の一致",
|
||||
"resultsTitle": "検索結果",
|
||||
"invalidSources": "少なくとも1つのソースをオンにする。",
|
||||
"noResults": "一致するファイルやフォルダがありません。"
|
||||
@@ -807,7 +811,18 @@
|
||||
"description": "現在のセッションのすべての通知を表示する",
|
||||
"empty": "まだ通知なし",
|
||||
"clearAll": "すべてクリア",
|
||||
"total": "{count} 通知"
|
||||
"appEnabled": "アプリの通知",
|
||||
"appEnabledDescription": "このタブがバックグラウンドにある間にファイル操作が完了した際、ブラウザに通知を表示します。ブラウザの権限が必要です。このブラウザにのみ保存されます。",
|
||||
"appUnsupported": "このブラウザではアプリ通知に対応していません。",
|
||||
"permissionGranted": "許可された",
|
||||
"permissionDeniedHelp": "通知がブロックされています。このサイトについて、ブラウザの設定で通知を有効にしてください。",
|
||||
"uploadTitle": "アップロードが完了しました",
|
||||
"downloadTitle": "すべてダウンロード",
|
||||
"moveTitle": "移動が完了しました",
|
||||
"copyTitle": "コピー完了",
|
||||
"uploadFailedTitle": "アップロードに失敗しました",
|
||||
"downloadFailedTitle": "ダウンロードに失敗しました",
|
||||
"operationFailedTitle": "操作に失敗しました"
|
||||
},
|
||||
"events": {
|
||||
"serverShutdown": "サーバーはシャットダウンされた。",
|
||||
@@ -860,5 +875,8 @@
|
||||
"documentDownload": "資料ダウンロード - 完",
|
||||
"editorSuccess": "エディターの初期化 - 成功!すべてのステップが完了しました。",
|
||||
"setupError": "セットアップエラー"
|
||||
},
|
||||
"pwa": {
|
||||
"install": "アプリをインストール"
|
||||
}
|
||||
}
|
||||
@@ -29,7 +29,8 @@
|
||||
"goToItem": "항목으로 이동",
|
||||
"copyPath": "경로 복사",
|
||||
"pinItem": "고정",
|
||||
"unpinItem": "고정 해제"
|
||||
"unpinItem": "고정 해제",
|
||||
"sendToApp": "앱으로 보내기"
|
||||
},
|
||||
"errors": {
|
||||
"forbidden": "접근 권한이 없습니다.",
|
||||
@@ -245,7 +246,9 @@
|
||||
"helpText2": "대소문자 구분 일치를 위해 파일 또는 폴더 이름에 대해서만 하위 문자열 일치(전체 경로가 아님).\n\n 용어 사이의 OR에는 파이프 문자를, 한 구문에는 큰따옴표를 사용합니다.\n\n고급 옵션을 사용하려면 와일드카드 검색을 사용 설정하세요.",
|
||||
"useWildcardSearch": "와일드카드 검색(대소문자 구분)",
|
||||
"useWildcardSearchDescription": "지원되는 와일드카드 옵션:\n\n- 모든 시퀀스와 일치합니다: *\n\n- 한 문자 일치: ?\n\n- 문자 클래스: [a-z], [0-9]\n\n- 부정: [^0-9]\n\n- 리터럴 *, ? 또는 [는 괄호로 묶어야 합니다(예: [*], [?], [[]).\n\n- 모든 결과는 대소문자를 구분합니다.",
|
||||
"openInAdvancedSearch": "고급 검색에서 열기"
|
||||
"openInAdvancedSearch": "고급 검색에서 열기",
|
||||
"caseExact": "대소문자를 구분하는 일치",
|
||||
"caseExactDescription": "대소문자를 구분하지 않고 무시하는 대신, 파일 및 폴더 이름을 대소문자를 정확히 일치시켜 검색합니다."
|
||||
},
|
||||
"settings": {
|
||||
"avoidChanges": "(수정하지 않으면 비워두세요)",
|
||||
@@ -572,7 +575,9 @@
|
||||
"no": "아니요{suffix}",
|
||||
"to": "~에{suffix}",
|
||||
"and": "그리고{suffix}",
|
||||
"or": "또는{suffix}"
|
||||
"or": "또는{suffix}",
|
||||
"camera": "카메라{suffix}",
|
||||
"video": "동영상{suffix}"
|
||||
},
|
||||
"access": {
|
||||
"rules": "액세스 규칙",
|
||||
@@ -778,7 +783,6 @@
|
||||
"scopeAria": "폴더로 검색 제한",
|
||||
"noTerms": "검색어를 하나 이상 입력합니다.",
|
||||
"termTooShort": "각 검색어는 {minSearchLength} 자 이상이어야 합니다.",
|
||||
"caseExact": "대소문자 구분 일치",
|
||||
"resultsTitle": "검색 결과",
|
||||
"invalidSources": "하나 이상의 소스를 켭니다.",
|
||||
"noResults": "일치하는 파일이나 폴더가 없습니다."
|
||||
@@ -807,7 +811,18 @@
|
||||
"description": "현재 세션의 모든 알림 보기",
|
||||
"empty": "아직 알림이 없습니다.",
|
||||
"clearAll": "모두 지우기",
|
||||
"total": "{count} 알림"
|
||||
"appEnabled": "앱 알림",
|
||||
"appEnabledDescription": "이 탭이 백그라운드에 있을 때 파일 작업이 완료되면 브라우저 알림을 표시합니다. 브라우저 권한이 필요합니다. 이 브라우저에만 저장됩니다.",
|
||||
"appUnsupported": "이 브라우저에서는 앱 알림이 지원되지 않습니다.",
|
||||
"permissionGranted": "허용됨",
|
||||
"permissionDeniedHelp": "알림이 차단되었습니다. 브라우저 설정에서 이 사이트에 대한 알림을 허용해 주세요.",
|
||||
"uploadTitle": "업로드 완료",
|
||||
"downloadTitle": "전체 다운로드",
|
||||
"moveTitle": "이동이 완료되었습니다",
|
||||
"copyTitle": "복사 완료",
|
||||
"uploadFailedTitle": "업로드에 실패했습니다",
|
||||
"downloadFailedTitle": "다운로드에 실패했습니다",
|
||||
"operationFailedTitle": "작업이 실패했습니다"
|
||||
},
|
||||
"events": {
|
||||
"serverShutdown": "서버가 종료되었습니다.",
|
||||
@@ -860,5 +875,8 @@
|
||||
"documentDownload": "문서 다운로드 - 완료",
|
||||
"editorSuccess": "편집기 초기화 - 성공! 모든 단계가 완료되었습니다.",
|
||||
"setupError": "설정 오류"
|
||||
},
|
||||
"pwa": {
|
||||
"install": "앱 설치"
|
||||
}
|
||||
}
|
||||
@@ -29,7 +29,8 @@
|
||||
"goToItem": "Ga naar item",
|
||||
"copyPath": "Kopieer pad",
|
||||
"pinItem": "Vastzetten",
|
||||
"unpinItem": "Losmaken"
|
||||
"unpinItem": "Losmaken",
|
||||
"sendToApp": "Naar app verzenden"
|
||||
},
|
||||
"errors": {
|
||||
"forbidden": "U hebt geen rechten om hier toegang toe te krijgen.",
|
||||
@@ -245,7 +246,9 @@
|
||||
"helpText2": "Substring match alleen op bestands- of mapnamen (geen volledige paden). case:exact voor hoofdlettergevoelig matchen\n\n voor OR tussen termen gebruik je het teken pipe; dubbele aanhalingstekens voor één zin.\n\nSchakel wildcard zoeken in voor meer geavanceerde opties.",
|
||||
"useWildcardSearch": "Zoeken met jokertekens (hoofdlettergevoelig)",
|
||||
"useWildcardSearchDescription": "Ondersteunde jokertekenopties:\n\n- Overeenstemming met een willekeurige reeks: *\n\n- Overeenstemming met één teken: ?\n\n- Tekenklasse: [a-z], [0-9]\n\n- Negatie: [^0-9]\n\n- Letterlijke *, ?, of [ moeten tussen haakjes staan, bijv. [*], [?], [[]\n\n- Alle resultaten zijn hoofdlettergevoelig",
|
||||
"openInAdvancedSearch": "Openen in geavanceerd zoeken"
|
||||
"openInAdvancedSearch": "Openen in geavanceerd zoeken",
|
||||
"caseExact": "Hoofdlettergevoelige zoekopdracht",
|
||||
"caseExactDescription": "Pas bij het vergelijken van bestands- en mapnamen de exacte hoofdlettergebruik toe in plaats van hoofdletters en kleine letters te negeren."
|
||||
},
|
||||
"settings": {
|
||||
"avoidChanges": "(laat leeg om wijzigingen te voorkomen)",
|
||||
@@ -666,7 +669,9 @@
|
||||
"no": "Geen{suffix}",
|
||||
"to": "naar{suffix}",
|
||||
"and": "en{suffix}",
|
||||
"or": "of{suffix}"
|
||||
"or": "of{suffix}",
|
||||
"camera": "Camera{suffix}",
|
||||
"video": "Video{suffix}"
|
||||
},
|
||||
"access": {
|
||||
"rules": "Toegangsregels",
|
||||
@@ -778,7 +783,6 @@
|
||||
"scopeAria": "Zoekopdracht beperken tot een map",
|
||||
"noTerms": "Voer ten minste één zoekterm in.",
|
||||
"termTooShort": "Elke zoekterm moet minimaal {minSearchLength} tekens bevatten.",
|
||||
"caseExact": "Hoofdlettergevoelige match",
|
||||
"resultsTitle": "Zoekresultaten",
|
||||
"invalidSources": "Zet minstens één bron aan.",
|
||||
"noResults": "Geen overeenkomende bestanden of mappen."
|
||||
@@ -807,7 +811,18 @@
|
||||
"description": "Alle meldingen van je huidige sessie bekijken",
|
||||
"empty": "Nog geen meldingen",
|
||||
"clearAll": "Alles wissen",
|
||||
"total": "{count} meldingen"
|
||||
"appEnabled": "App-meldingen",
|
||||
"appEnabledDescription": "Toon browsermeldingen wanneer bestandsbewerkingen zijn voltooid terwijl dit tabblad op de achtergrond staat. Hiervoor is toestemming van de browser vereist. Wordt alleen in deze browser opgeslagen.",
|
||||
"appUnsupported": "App-meldingen worden in deze browser niet ondersteund.",
|
||||
"permissionGranted": "toegestaan",
|
||||
"permissionDeniedHelp": "Meldingen zijn geblokkeerd. Schakel ze in de instellingen van je browser voor deze website in.",
|
||||
"uploadTitle": "Uploaden voltooid",
|
||||
"downloadTitle": "Download voltooid",
|
||||
"moveTitle": "Verplaatsing voltooid",
|
||||
"copyTitle": "Kopiëren voltooid",
|
||||
"uploadFailedTitle": "Het uploaden is mislukt",
|
||||
"downloadFailedTitle": "Het downloaden is mislukt",
|
||||
"operationFailedTitle": "De bewerking is mislukt"
|
||||
},
|
||||
"events": {
|
||||
"serverShutdown": "De server is uitgeschakeld.",
|
||||
@@ -860,5 +875,8 @@
|
||||
"documentDownload": "Document downloaden - Compleet",
|
||||
"editorSuccess": "Initialisatie Editor - Succes! Alle stappen voltooid.",
|
||||
"setupError": "Setup-fout"
|
||||
},
|
||||
"pwa": {
|
||||
"install": "App installeren"
|
||||
}
|
||||
}
|
||||
@@ -116,7 +116,9 @@
|
||||
"no": "Geen{suffix}",
|
||||
"to": "naar{suffix}",
|
||||
"and": "en{suffix}",
|
||||
"or": "of{suffix}"
|
||||
"or": "of{suffix}",
|
||||
"camera": "Camera{suffix}",
|
||||
"video": "Video{suffix}"
|
||||
},
|
||||
"buttons": {
|
||||
"copySuccess": "Gekopieerd naar klembord!",
|
||||
@@ -148,7 +150,8 @@
|
||||
"goToItem": "Ga naar item",
|
||||
"goToSource": "Ga naar bronlocatie",
|
||||
"pinItem": "Vastzetten",
|
||||
"unpinItem": "Losmaken"
|
||||
"unpinItem": "Losmaken",
|
||||
"sendToApp": "Naar app verzenden"
|
||||
},
|
||||
"errors": {
|
||||
"forbidden": "U hebt hier geen toegangsrechten.",
|
||||
@@ -434,7 +437,9 @@
|
||||
"useWildcardSearchDescription": "Ondersteunde jokertekenopties:\n\n- Overeenstemming met een willekeurige reeks: *\n\n- Overeenstemming met één teken: ?\n\n- Tekenklasse: [a-z], [0-9]\n\n- Negatie: [^0-9]\n\n- Letterlijke *, ?, of [ moeten tussen haakjes staan, bijv. [*], [?], [[]\n\n- Alle resultaten zijn hoofdlettergevoelig",
|
||||
"helpText1": "Er wordt gezocht op elk teken dat je typt (minimaal {minSearchLength} tekens).",
|
||||
"helpText2": "Substring match alleen op bestands- of mapnamen (geen volledige paden). case:exact voor hoofdlettergevoelig matchen\n\n voor OR tussen termen gebruik je het teken pipe; dubbele aanhalingstekens voor één zin.\n\nSchakel wildcard zoeken in voor meer geavanceerde opties.",
|
||||
"openInAdvancedSearch": "Openen in geavanceerd zoeken"
|
||||
"openInAdvancedSearch": "Openen in geavanceerd zoeken",
|
||||
"caseExact": "Hoofdlettergevoelige zoekopdracht",
|
||||
"caseExactDescription": "Pas bij het vergelijken van bestands- en mapnamen de exacte hoofdlettergebruik toe, in plaats van hoofdletters en kleine letters te negeren."
|
||||
},
|
||||
"profileSettings": {
|
||||
"hideFileExt": "Bepaalde bestanden verbergen (op extensie)",
|
||||
@@ -766,7 +771,6 @@
|
||||
"scopeAria": "Zoekopdracht beperken tot een map",
|
||||
"noTerms": "Voer ten minste één zoekterm in.",
|
||||
"termTooShort": "Elke zoekterm moet minimaal {minSearchLength} tekens bevatten.",
|
||||
"caseExact": "Hoofdlettergevoelige match",
|
||||
"resultsTitle": "Zoekresultaten",
|
||||
"invalidSources": "Zet minstens één bron aan.",
|
||||
"noResults": "Geen overeenkomende bestanden of mappen."
|
||||
@@ -798,7 +802,18 @@
|
||||
"description": "Alle meldingen van uw huidige sessie bekijken",
|
||||
"empty": "Nog geen meldingen",
|
||||
"clearAll": "Alles wissen",
|
||||
"total": "{count} meldingen"
|
||||
"appEnabled": "App-meldingen",
|
||||
"appEnabledDescription": "Toon browsermeldingen wanneer bestandsbewerkingen zijn voltooid terwijl dit tabblad op de achtergrond staat. Hiervoor is toestemming van de browser vereist. Wordt alleen in deze browser opgeslagen.",
|
||||
"appUnsupported": "App-meldingen worden in deze browser niet ondersteund.",
|
||||
"permissionGranted": "toegestaan",
|
||||
"permissionDeniedHelp": "Meldingen zijn geblokkeerd. Schakel ze in de instellingen van je browser voor deze website in.",
|
||||
"uploadTitle": "Uploaden voltooid",
|
||||
"downloadTitle": "Download voltooid",
|
||||
"moveTitle": "Verplaatsing voltooid",
|
||||
"copyTitle": "Kopiëren voltooid",
|
||||
"uploadFailedTitle": "Het uploaden is mislukt",
|
||||
"downloadFailedTitle": "Het downloaden is mislukt",
|
||||
"operationFailedTitle": "De bewerking is mislukt"
|
||||
},
|
||||
"duplicateFinder": {
|
||||
"minSize": "Minimale grootte (MB){suffix}",
|
||||
@@ -860,5 +875,8 @@
|
||||
"documentDownload": "Document downloaden - Voltooid",
|
||||
"editorSuccess": "Editor-inintialisatie - Succes! Alle stappen voltooid.",
|
||||
"setupError": "Instellingenfout"
|
||||
},
|
||||
"pwa": {
|
||||
"install": "App installeren"
|
||||
}
|
||||
}
|
||||
@@ -29,7 +29,8 @@
|
||||
"goToItem": "Przejdź do pozycji",
|
||||
"copyPath": "Ścieżka kopiowania",
|
||||
"pinItem": "Przypnij",
|
||||
"unpinItem": "Odepnij"
|
||||
"unpinItem": "Odepnij",
|
||||
"sendToApp": "Wyślij do aplikacji"
|
||||
},
|
||||
"errors": {
|
||||
"forbidden": "Nie posiadasz uprawnień potrzebnych, by uzyskać do tego dostęp.",
|
||||
@@ -245,7 +246,9 @@
|
||||
"helpText2": "Dopasowanie podciągu tylko do nazw plików lub folderów (nie pełnych ścieżek). case:exact dla dopasowania z uwzględnieniem wielkości liter.\n\n dla OR między wyrażeniami użyj znaku potoku; podwójne cudzysłowy dla jednej frazy.\n\nWłącz wyszukiwanie wieloznaczne, aby uzyskać bardziej zaawansowane opcje.",
|
||||
"useWildcardSearch": "Wyszukiwanie z użyciem symboli wieloznacznych (z uwzględnieniem wielkości liter)",
|
||||
"useWildcardSearchDescription": "Obsługiwane opcje symboli wieloznacznych:\n\n- Dopasuj dowolną sekwencję: *\n\n- Dopasowanie jednego znaku: ?\n\n- Klasa znaków: [a-z], [0-9]\n\n- Negacja: [^0-9]\n\n- Dosłowne *, ? lub [ muszą być ujęte w nawiasy, np. [*], [?], [[]\n\n- We wszystkich wynikach rozróżniana jest wielkość liter",
|
||||
"openInAdvancedSearch": "Otwórz w wyszukiwaniu zaawansowanym"
|
||||
"openInAdvancedSearch": "Otwórz w wyszukiwaniu zaawansowanym",
|
||||
"caseExact": "Wyszukiwanie z uwzględnieniem wielkości liter",
|
||||
"caseExactDescription": "Porównuj nazwy plików i folderów, uwzględniając dokładną wielkość liter, zamiast ignorować różnicę między wielkimi a małymi literami."
|
||||
},
|
||||
"settings": {
|
||||
"avoidChanges": "(pozostaw puste aby nie zosatało zmienione)",
|
||||
@@ -572,7 +575,9 @@
|
||||
"no": "Nie{suffix}",
|
||||
"to": "do{suffix}",
|
||||
"and": "oraz{suffix}",
|
||||
"or": "lub{suffix}"
|
||||
"or": "lub{suffix}",
|
||||
"camera": "Aparat{suffix}",
|
||||
"video": "Film{suffix}"
|
||||
},
|
||||
"access": {
|
||||
"rules": "Zasady dostępu",
|
||||
@@ -778,7 +783,6 @@
|
||||
"scopeAria": "Ograniczenie wyszukiwania do folderu",
|
||||
"noTerms": "Wprowadź co najmniej jedno wyszukiwane hasło.",
|
||||
"termTooShort": "Każde wyszukiwane hasło musi zawierać co najmniej {minSearchLength} znaków",
|
||||
"caseExact": "Dopasowanie z uwzględnieniem wielkości liter",
|
||||
"resultsTitle": "Wyniki wyszukiwania",
|
||||
"invalidSources": "Włącz co najmniej jedno źródło.",
|
||||
"noResults": "Brak pasujących plików lub folderów."
|
||||
@@ -807,7 +811,18 @@
|
||||
"description": "Wyświetlanie wszystkich powiadomień z bieżącej sesji",
|
||||
"empty": "Brak powiadomień",
|
||||
"clearAll": "Wyczyść wszystko",
|
||||
"total": "{count} powiadomienia"
|
||||
"appEnabled": "Powiadomienia w aplikacji",
|
||||
"appEnabledDescription": "Wyświetlaj powiadomienia przeglądarki po zakończeniu operacji na plikach, gdy ta karta jest w tle. Wymaga uprawnień przeglądarki. Dane są zapisywane wyłącznie w tej przeglądarce.",
|
||||
"appUnsupported": "W tej przeglądarce nie są obsługiwane powiadomienia z aplikacji.",
|
||||
"permissionGranted": "dozwolone",
|
||||
"permissionDeniedHelp": "Powiadomienia są zablokowane. Włącz je w ustawieniach przeglądarki dla tej witryny.",
|
||||
"uploadTitle": "Przesyłanie zakończone",
|
||||
"downloadTitle": "Pobierz całość",
|
||||
"moveTitle": "Operacja zakończona",
|
||||
"copyTitle": "Kopiowanie zakończone",
|
||||
"uploadFailedTitle": "Przesyłanie nie powiodło się",
|
||||
"downloadFailedTitle": "Pobieranie nie powiodło się",
|
||||
"operationFailedTitle": "Operacja nie powiodła się"
|
||||
},
|
||||
"events": {
|
||||
"serverShutdown": "Serwer został wyłączony.",
|
||||
@@ -860,5 +875,8 @@
|
||||
"documentDownload": "Pobieranie dokumentów - zakończone",
|
||||
"editorSuccess": "Inicjalizacja edytora - sukces! Wszystkie kroki zostały wykonane.",
|
||||
"setupError": "Błąd konfiguracji"
|
||||
},
|
||||
"pwa": {
|
||||
"install": "Zainstaluj aplikację"
|
||||
}
|
||||
}
|
||||
@@ -29,7 +29,8 @@
|
||||
"goToItem": "Ir para o item",
|
||||
"copyPath": "Copiar caminho",
|
||||
"pinItem": "Fixar",
|
||||
"unpinItem": "Desafixar"
|
||||
"unpinItem": "Desafixar",
|
||||
"sendToApp": "Enviar para o aplicativo"
|
||||
},
|
||||
"errors": {
|
||||
"forbidden": "Você não tem permissões para acessar isto.",
|
||||
@@ -245,7 +246,9 @@
|
||||
"helpText2": "Correspondência de subcadeia somente em nomes de arquivos ou pastas (não em caminhos completos). case:exact para correspondência com distinção entre maiúsculas e minúsculas\n\n para OU entre termos, use o caractere pipe; aspas duplas para uma frase.\n\nAtive a pesquisa curinga para obter opções mais avançadas.",
|
||||
"useWildcardSearch": "Pesquisa curinga (diferencia maiúsculas de minúsculas)",
|
||||
"useWildcardSearchDescription": "Opções curinga compatíveis:\n\n- Corresponde a qualquer sequência: *\n\n- Corresponde a um caractere: ?\n\n- Classe de caracteres: [a-z], [0-9]\n\n- Negação: [^0-9]\n\n- Os literais *, ? ou [ precisam ser colocados entre colchetes, por exemplo, [*], [?], [[]\n\n- Todos os resultados diferenciam maiúsculas de minúsculas",
|
||||
"openInAdvancedSearch": "Abrir na pesquisa avançada"
|
||||
"openInAdvancedSearch": "Abrir na pesquisa avançada",
|
||||
"caseExact": "Correspondência que diferencia maiúsculas de minúsculas",
|
||||
"caseExactDescription": "Compare nomes de arquivos e pastas usando a maiúscula e minúscula exatas, em vez de ignorar a diferença entre maiúsculas e minúsculas."
|
||||
},
|
||||
"settings": {
|
||||
"avoidChanges": "(deixe em branco para manter)",
|
||||
@@ -572,7 +575,9 @@
|
||||
"no": "Não{suffix}",
|
||||
"to": "para{suffix}",
|
||||
"and": "e{suffix}",
|
||||
"or": "ou{suffix}"
|
||||
"or": "ou{suffix}",
|
||||
"camera": "Câmera{suffix}",
|
||||
"video": "Vídeo{suffix}"
|
||||
},
|
||||
"access": {
|
||||
"rules": "Regras de acesso",
|
||||
@@ -778,7 +783,6 @@
|
||||
"scopeAria": "Limitar a pesquisa a uma pasta",
|
||||
"noTerms": "Digite pelo menos um termo de pesquisa.",
|
||||
"termTooShort": "Cada termo de pesquisa deve ter pelo menos {minSearchLength} caracteres",
|
||||
"caseExact": "Correspondência com distinção entre maiúsculas e minúsculas",
|
||||
"resultsTitle": "Resultados da pesquisa",
|
||||
"invalidSources": "Ligue pelo menos uma fonte.",
|
||||
"noResults": "Não há arquivos ou pastas correspondentes."
|
||||
@@ -807,7 +811,18 @@
|
||||
"description": "Exibir todas as notificações de sua sessão atual",
|
||||
"empty": "Ainda não há notificações",
|
||||
"clearAll": "Limpar tudo",
|
||||
"total": "{count} notificações"
|
||||
"appEnabled": "Notificações de aplicativos",
|
||||
"appEnabledDescription": "Mostrar notificações do navegador quando as operações com arquivos forem concluídas enquanto esta guia estiver em segundo plano. Requer permissão do navegador. Armazenado apenas neste navegador.",
|
||||
"appUnsupported": "As notificações de aplicativos não são compatíveis com este navegador.",
|
||||
"permissionGranted": "permitido",
|
||||
"permissionDeniedHelp": "As notificações estão bloqueadas. Ative-as nas configurações do seu navegador para este site.",
|
||||
"uploadTitle": "Upload concluído",
|
||||
"downloadTitle": "Baixar na íntegra",
|
||||
"moveTitle": "Movimentação concluída",
|
||||
"copyTitle": "Cópia concluída",
|
||||
"uploadFailedTitle": "Falha no upload",
|
||||
"downloadFailedTitle": "Falha no download",
|
||||
"operationFailedTitle": "A operação falhou"
|
||||
},
|
||||
"events": {
|
||||
"serverShutdown": "O servidor foi desligado.",
|
||||
@@ -860,5 +875,8 @@
|
||||
"documentDownload": "Download de documentos - Completo",
|
||||
"editorSuccess": "Inicialização do editor - Sucesso! Todas as etapas foram concluídas.",
|
||||
"setupError": "Erro de configuração"
|
||||
},
|
||||
"pwa": {
|
||||
"install": "Instalar o aplicativo"
|
||||
}
|
||||
}
|
||||
@@ -29,7 +29,8 @@
|
||||
"goToItem": "Ir para o item",
|
||||
"copyPath": "Caminho de cópia",
|
||||
"pinItem": "Fixar",
|
||||
"unpinItem": "Desafixar"
|
||||
"unpinItem": "Desafixar",
|
||||
"sendToApp": "Enviar para a aplicação"
|
||||
},
|
||||
"errors": {
|
||||
"forbidden": "Não tem permissões para aceder a isto",
|
||||
@@ -245,7 +246,9 @@
|
||||
"helpText2": "Correspondência de subcadeia de caracteres apenas em nomes de ficheiros ou pastas (não em caminhos completos). case:exact para correspondência sensível a maiúsculas e minúsculas\n\n para OU entre termos, utilizar o carácter pipe; aspas duplas para uma frase.\n\nAtivar a pesquisa de wildcards para opções mais avançadas.",
|
||||
"useWildcardSearch": "Pesquisa com caracteres curinga (sensível a maiúsculas e minúsculas)",
|
||||
"useWildcardSearchDescription": "Opções curingas suportadas:\n\n- Corresponde a qualquer sequência: *\n\n- Corresponder a um carácter: ?\n\n- Classe de caracteres: [a-z], [0-9]\n\n- Negação: [^0-9]\n\n- Os literais *, ?, ou [ devem ser colocados entre parênteses, por exemplo, [*], [?], [[]\n\n- Todos os resultados são sensíveis a maiúsculas e minúsculas",
|
||||
"openInAdvancedSearch": "Abrir na Pesquisa Avançada"
|
||||
"openInAdvancedSearch": "Abrir na Pesquisa Avançada",
|
||||
"caseExact": "Correspondência que distingue maiúsculas de minúsculas",
|
||||
"caseExactDescription": "Comparar os nomes dos ficheiros e pastas utilizando a maiúscula e minúscula exatas, em vez de ignorar as maiúsculas e minúsculas."
|
||||
},
|
||||
"settings": {
|
||||
"avoidChanges": "(deixe em branco para manter)",
|
||||
@@ -666,7 +669,9 @@
|
||||
"no": "Não{suffix}",
|
||||
"to": "para{suffix}",
|
||||
"and": "e{suffix}",
|
||||
"or": "ou{suffix}"
|
||||
"or": "ou{suffix}",
|
||||
"camera": "Câmara{suffix}",
|
||||
"video": "Vídeo{suffix}"
|
||||
},
|
||||
"access": {
|
||||
"rules": "Regras de acesso",
|
||||
@@ -778,7 +783,6 @@
|
||||
"scopeAria": "Limitar a pesquisa a uma pasta",
|
||||
"noTerms": "Introduzir pelo menos um termo de pesquisa.",
|
||||
"termTooShort": "Cada termo de pesquisa deve ter, pelo menos, {minSearchLength} caracteres",
|
||||
"caseExact": "Correspondência com distinção de maiúsculas e minúsculas",
|
||||
"resultsTitle": "Resultados da pesquisa",
|
||||
"invalidSources": "Ligar pelo menos uma fonte.",
|
||||
"noResults": "Não há ficheiros ou pastas correspondentes."
|
||||
@@ -807,7 +811,18 @@
|
||||
"description": "Ver todas as notificações da sua sessão atual",
|
||||
"empty": "Ainda não há notificações",
|
||||
"clearAll": "Limpar tudo",
|
||||
"total": "{count} notificações"
|
||||
"appEnabled": "Notificações da aplicação",
|
||||
"appEnabledDescription": "Mostrar notificações do navegador quando as operações com ficheiros forem concluídas enquanto este separador estiver em segundo plano. Requer autorização do navegador. Guardado apenas neste navegador.",
|
||||
"appUnsupported": "As notificações de aplicações não são suportadas neste navegador.",
|
||||
"permissionGranted": "permitido",
|
||||
"permissionDeniedHelp": "As notificações estão bloqueadas. Ative-as nas definições do seu navegador para este site.",
|
||||
"uploadTitle": "Carregamento concluído",
|
||||
"downloadTitle": "Descarregar na íntegra",
|
||||
"moveTitle": "Movimentação concluída",
|
||||
"copyTitle": "Cópia concluída",
|
||||
"uploadFailedTitle": "Falha no carregamento",
|
||||
"downloadFailedTitle": "O download falhou",
|
||||
"operationFailedTitle": "A operação falhou"
|
||||
},
|
||||
"events": {
|
||||
"serverShutdown": "O servidor foi desligado.",
|
||||
@@ -860,5 +875,8 @@
|
||||
"documentDownload": "Descarregamento de documentos - Completo",
|
||||
"editorSuccess": "Inicialização do editor - Sucesso! Todas as etapas foram concluídas.",
|
||||
"setupError": "Erro de configuração"
|
||||
},
|
||||
"pwa": {
|
||||
"install": "Instalar a aplicação"
|
||||
}
|
||||
}
|
||||
@@ -29,7 +29,8 @@
|
||||
"goToItem": "Du-te la articol",
|
||||
"copyPath": "Copiați calea",
|
||||
"pinItem": "Fixare",
|
||||
"unpinItem": "Anulare fixare"
|
||||
"unpinItem": "Anulare fixare",
|
||||
"sendToApp": "Trimite către aplicație"
|
||||
},
|
||||
"errors": {
|
||||
"forbidden": "Nu ai permisiuni sa accesezi asta.",
|
||||
@@ -245,7 +246,9 @@
|
||||
"helpText2": "Potrivire de substringuri numai pe nume de fișiere sau de foldere (nu pe căi complete). case:exact pentru potrivire în funcție de caz\n\n pentru OR între termeni utilizați caracterul pipă; ghilimele duble pentru o frază.\n\nActivați căutarea wildcard pentru opțiuni mai avansate.",
|
||||
"useWildcardSearch": "Căutare wildcard (sensibil la majuscule)",
|
||||
"useWildcardSearchDescription": "Opțiuni de wildcard acceptate:\n\n- Potrivește orice secvență: *\n\n- Potrivește un singur caracter: ?\n\n- Clasa de caractere: [a-z], [0-9]\n\n- Negație: [^0-9]\n\n- Literele *, ? sau [ trebuie să fie puse între paranteze, de exemplu [*], [?], [[]\n\n- Toate rezultatele sunt sensibile la majuscule",
|
||||
"openInAdvancedSearch": "Deschidere în căutarea avansată"
|
||||
"openInAdvancedSearch": "Deschidere în căutarea avansată",
|
||||
"caseExact": "Căutare cu diferențiere între majuscule și minuscule",
|
||||
"caseExactDescription": "Compară numele fișierelor și folderelor folosind exact litere mari și mici, în loc să ignore diferențele dintre ele."
|
||||
},
|
||||
"settings": {
|
||||
"avoidChanges": "(lasă gol pentru a nu schimba)",
|
||||
@@ -572,7 +575,9 @@
|
||||
"no": "Nu{suffix}",
|
||||
"to": "spre{suffix}",
|
||||
"and": "și{suffix}",
|
||||
"or": "sau{suffix}"
|
||||
"or": "sau{suffix}",
|
||||
"camera": "Cameră foto{suffix}",
|
||||
"video": "Video{suffix}"
|
||||
},
|
||||
"access": {
|
||||
"rules": "Reguli de acces",
|
||||
@@ -778,7 +783,6 @@
|
||||
"scopeAria": "Limitarea căutării la un dosar",
|
||||
"noTerms": "Introduceți cel puțin un termen de căutare.",
|
||||
"termTooShort": "Fiecare termen de căutare trebuie să aibă cel puțin {minSearchLength} caractere",
|
||||
"caseExact": "Potrivire sensibilă la majuscule",
|
||||
"resultsTitle": "Rezultatele căutării",
|
||||
"invalidSources": "Porniți cel puțin o sursă.",
|
||||
"noResults": "Nu există fișiere sau foldere corespunzătoare."
|
||||
@@ -807,7 +811,18 @@
|
||||
"description": "Vizualizați toate notificările din sesiunea curentă",
|
||||
"empty": "Nu există încă notificări",
|
||||
"clearAll": "Ștergeți tot",
|
||||
"total": "{count} notificări"
|
||||
"appEnabled": "Notificări din aplicații",
|
||||
"appEnabledDescription": "Afișează notificări în browser la finalizarea operațiunilor cu fișiere atunci când această filă se află în fundal. Necesită permisiunea browserului. Se salvează numai în acest browser.",
|
||||
"appUnsupported": "Notificările aplicațiilor nu sunt acceptate în acest browser.",
|
||||
"permissionGranted": "permis",
|
||||
"permissionDeniedHelp": "Notificările sunt blocate. Activează-le în setările browserului pentru acest site.",
|
||||
"uploadTitle": "Încărcarea s-a încheiat",
|
||||
"downloadTitle": "Descărcare completă",
|
||||
"moveTitle": "Mutarea s-a finalizat",
|
||||
"copyTitle": "Copiere finalizată",
|
||||
"uploadFailedTitle": "Încărcarea a eșuat",
|
||||
"downloadFailedTitle": "Descărcarea a eșuat",
|
||||
"operationFailedTitle": "Operațiunea a eșuat"
|
||||
},
|
||||
"events": {
|
||||
"serverShutdown": "Serverul a fost închis.",
|
||||
@@ -860,5 +875,8 @@
|
||||
"documentDownload": "Descărcare document - Complet",
|
||||
"editorSuccess": "Inițializarea editorului - Succes! Toate etapele au fost finalizate.",
|
||||
"setupError": "Eroare de configurare"
|
||||
},
|
||||
"pwa": {
|
||||
"install": "Instalează aplicația"
|
||||
}
|
||||
}
|
||||
@@ -29,7 +29,8 @@
|
||||
"goToItem": "Перейти к пункту",
|
||||
"copyPath": "Путь копирования",
|
||||
"pinItem": "Закрепить",
|
||||
"unpinItem": "Открепить"
|
||||
"unpinItem": "Открепить",
|
||||
"sendToApp": "Отправить в приложение"
|
||||
},
|
||||
"errors": {
|
||||
"forbidden": "У вас нет прав доступа к этому.",
|
||||
@@ -245,7 +246,9 @@
|
||||
"helpText2": "Подстрочное соответствие только для имен файлов или папок (не полных путей). case:exact для чувствительного к регистру соответствия\n\n для ИЛИ между терминами используйте символ трубы; двойные кавычки для одной фразы.\n\nВключите поиск по подстановочным знакам для получения более широких возможностей.",
|
||||
"useWildcardSearch": "Поиск с использованием подстановочных знаков (с учетом регистра)",
|
||||
"useWildcardSearchDescription": "Поддерживаемые параметры подстановочного знака:\n\n- Соответствие любой последовательности: *\n\n- Соответствие одному символу: ?\n\n- Класс символов: [a-z], [0-9]\n\n- Отрицание: [^0-9]\n\n- Буквальные *, ? или [ должны быть заключены в скобки, например, [*], [?], [[]\n\n- Все результаты чувствительны к регистру",
|
||||
"openInAdvancedSearch": "Открыть в расширенном поиске"
|
||||
"openInAdvancedSearch": "Открыть в расширенном поиске",
|
||||
"caseExact": "Поиск с учетом регистра",
|
||||
"caseExactDescription": "Сопоставлять имена файлов и папок, учитывая регистр букв, а не игнорируя его."
|
||||
},
|
||||
"settings": {
|
||||
"avoidChanges": "(оставьте поле пустым, чтобы избежать изменений)",
|
||||
@@ -572,7 +575,9 @@
|
||||
"no": "Нет{suffix}",
|
||||
"to": "до{suffix}",
|
||||
"and": "и{suffix}",
|
||||
"or": "или{suffix}"
|
||||
"or": "или{suffix}",
|
||||
"camera": "Камера{suffix}",
|
||||
"video": "Видео{suffix}"
|
||||
},
|
||||
"access": {
|
||||
"rules": "Правила доступа",
|
||||
@@ -778,7 +783,6 @@
|
||||
"scopeAria": "Ограничьте поиск папкой",
|
||||
"noTerms": "Введите хотя бы один поисковый запрос.",
|
||||
"termTooShort": "Каждый поисковый запрос должен содержать не менее {minSearchLength} символов.",
|
||||
"caseExact": "Соответствие с учетом регистра",
|
||||
"resultsTitle": "Результаты поиска",
|
||||
"invalidSources": "Включите хотя бы один источник.",
|
||||
"noResults": "Нет подходящих файлов или папок."
|
||||
@@ -807,7 +811,18 @@
|
||||
"description": "Просмотр всех уведомлений в текущем сеансе",
|
||||
"empty": "Уведомлений пока нет",
|
||||
"clearAll": "Очистить все",
|
||||
"total": "{count} уведомления"
|
||||
"appEnabled": "Уведомления в приложениях",
|
||||
"appEnabledDescription": "Показывать уведомления браузера по завершении операций с файлами, когда эта вкладка находится в фоновом режиме. Требуется разрешение браузера. Сохраняется только в этом браузере.",
|
||||
"appUnsupported": "В этом браузере не поддерживаются уведомления приложений.",
|
||||
"permissionGranted": "разрешено",
|
||||
"permissionDeniedHelp": "Уведомления заблокированы. Включите их в настройках браузера для этого сайта.",
|
||||
"uploadTitle": "Загрузка завершена",
|
||||
"downloadTitle": "Скачать полностью",
|
||||
"moveTitle": "Перемещение завершено",
|
||||
"copyTitle": "Копирование завершено",
|
||||
"uploadFailedTitle": "Загрузка не удалась",
|
||||
"downloadFailedTitle": "Загрузка не удалась",
|
||||
"operationFailedTitle": "Операция завершилась неудачей"
|
||||
},
|
||||
"events": {
|
||||
"serverShutdown": "Сервер был выключен.",
|
||||
@@ -860,5 +875,8 @@
|
||||
"documentDownload": "Загрузка документов - Полная",
|
||||
"editorSuccess": "Инициализация редактора - Успех! Все шаги выполнены.",
|
||||
"setupError": "Ошибка установки"
|
||||
},
|
||||
"pwa": {
|
||||
"install": "Установить приложение"
|
||||
}
|
||||
}
|
||||
@@ -29,7 +29,8 @@
|
||||
"goToItem": "Prejsť na položku",
|
||||
"copyPath": "Kopírovať cestu",
|
||||
"pinItem": "Pripnúť",
|
||||
"unpinItem": "Odpnúť"
|
||||
"unpinItem": "Odpnúť",
|
||||
"sendToApp": "Odoslať do aplikácie"
|
||||
},
|
||||
"errors": {
|
||||
"forbidden": "You don't have permissions to access this.",
|
||||
@@ -245,7 +246,9 @@
|
||||
"helpText2": "Zhoda podreťazca len na názvoch súborov alebo priečinkov (nie na celých cestách). case:exact pre porovnávanie podľa veľkosti písmen\n\n pre OR medzi výrazmi použite znak pipe; dvojité úvodzovky pre jednu frázu.\n\nPovoľte vyhľadávanie so zástupnými znakmi pre pokročilejšie možnosti.",
|
||||
"useWildcardSearch": "Vyhľadávanie so zástupnými znakmi (rozlišovanie veľkých a malých písmen)",
|
||||
"useWildcardSearchDescription": "Podporované možnosti zástupných znakov:\n\n- Zodpovedajú akejkoľvek sekvencii: *\n\n- Zodpovedá jednému znaku: ?\n\n- Trieda znakov: [a-z], [0-9]\n\n- Negácia: [^0-9]\n\n- Doslovné *, ? alebo [ musia byť uzavreté v zátvorkách, napr. [*], [?], [[]\n\n- Pri všetkých výsledkoch sa rozlišujú veľké a malé písmená",
|
||||
"openInAdvancedSearch": "Otvoriť v rozšírenom vyhľadávaní"
|
||||
"openInAdvancedSearch": "Otvoriť v rozšírenom vyhľadávaní",
|
||||
"caseExact": "Porovnanie s rozlišovaním veľkých a malých písmen",
|
||||
"caseExactDescription": "Porovnávať názvy súborov a priečinkov na základe presného rozlišovania veľkých a malých písmen namiesto ignorovania veľkých a malých písmen."
|
||||
},
|
||||
"settings": {
|
||||
"avoidChanges": "(nechajte prázdne, aby sa nezmenilo)",
|
||||
@@ -572,7 +575,9 @@
|
||||
"no": "Nie{suffix}",
|
||||
"to": "do{suffix}",
|
||||
"and": "a{suffix}",
|
||||
"or": "alebo{suffix}"
|
||||
"or": "alebo{suffix}",
|
||||
"camera": "Fotoaparát{suffix}",
|
||||
"video": "Video{suffix}"
|
||||
},
|
||||
"access": {
|
||||
"rules": "Pravidlá prístupu",
|
||||
@@ -778,7 +783,6 @@
|
||||
"scopeAria": "Obmedzenie vyhľadávania na priečinok",
|
||||
"noTerms": "Zadajte aspoň jeden vyhľadávací výraz.",
|
||||
"termTooShort": "Každý hľadaný výraz musí mať aspoň {minSearchLength} znakov.",
|
||||
"caseExact": "Zhoda s rozlišovaním veľkých a malých písmen",
|
||||
"resultsTitle": "Výsledky vyhľadávania",
|
||||
"invalidSources": "Zapnite aspoň jeden zdroj.",
|
||||
"noResults": "Žiadne zodpovedajúce súbory alebo priečinky."
|
||||
@@ -807,7 +811,18 @@
|
||||
"description": "Zobrazenie všetkých oznámení z aktuálnej relácie",
|
||||
"empty": "Zatiaľ žiadne oznámenia",
|
||||
"clearAll": "Vymazať všetko",
|
||||
"total": "{count} oznámenia"
|
||||
"appEnabled": "Oznámenia v aplikácii",
|
||||
"appEnabledDescription": "Zobrazovať oznámenia prehliadača po dokončení operácií so súbormi, keď je táto karta v pozadí. Vyžaduje povolenie prehliadača. Uložené iba v tomto prehliadači.",
|
||||
"appUnsupported": "Tento prehliadač nepodporuje oznámenia z aplikácií.",
|
||||
"permissionGranted": "povolené",
|
||||
"permissionDeniedHelp": "Oznámenia sú blokované. V nastaveniach prehliadača ich pre túto stránku povolte.",
|
||||
"uploadTitle": "Nahratie dokončené",
|
||||
"downloadTitle": "Stiahnuť celé",
|
||||
"moveTitle": "Presun dokončený",
|
||||
"copyTitle": "Kopírovanie dokončené",
|
||||
"uploadFailedTitle": "Nahratie sa nepodarilo",
|
||||
"downloadFailedTitle": "Stiahnutie sa nepodarilo",
|
||||
"operationFailedTitle": "Operácia sa nepodarila"
|
||||
},
|
||||
"events": {
|
||||
"serverShutdown": "Server bol vypnutý.",
|
||||
@@ -860,5 +875,8 @@
|
||||
"documentDownload": "Stiahnutie dokumentu - Kompletné",
|
||||
"editorSuccess": "Inicializácia editora - úspech! Všetky kroky boli dokončené.",
|
||||
"setupError": "Chyba nastavenia"
|
||||
},
|
||||
"pwa": {
|
||||
"install": "Nainštalovať aplikáciu"
|
||||
}
|
||||
}
|
||||
@@ -29,7 +29,8 @@
|
||||
"goToItem": "Gå till objektet",
|
||||
"copyPath": "Kopiera sökväg",
|
||||
"pinItem": "Fäst",
|
||||
"unpinItem": "Lossa"
|
||||
"unpinItem": "Lossa",
|
||||
"sendToApp": "Skicka till appen"
|
||||
},
|
||||
"errors": {
|
||||
"forbidden": "Du saknar rättigheter till detta",
|
||||
@@ -245,7 +246,9 @@
|
||||
"helpText2": "Delsträngsmatchning endast på fil- eller mappnamn (inte fullständiga sökvägar). case:exact för skiftlägeskänslig matchning\n\n för OR mellan termer använd pipe-tecken; dubbla citattecken för en fras.\n\nAktivera jokerteckensökning för mer avancerade alternativ.",
|
||||
"useWildcardSearch": "Sökning med jokertecken (skiftlägeskänslig)",
|
||||
"useWildcardSearchDescription": "Alternativ för jokertecken som stöds:\n\n- Matchar vilken sekvens som helst: *\n\n- Matcha ett tecken: ?\n\n- Teckenklass: [a-z], [0-9]\n\n- Negation: [^0-9]\n\n- Bokstavliga *, ? eller [ måste omslutas av parenteser, t.ex. [*], [?], [[]\n\n- Alla resultat är skiftlägeskänsliga",
|
||||
"openInAdvancedSearch": "Öppna i avancerad sökning"
|
||||
"openInAdvancedSearch": "Öppna i avancerad sökning",
|
||||
"caseExact": "Skillnad mellan stora och små bokstäver",
|
||||
"caseExactDescription": "Jämför fil- och mappnamn med exakt bokstavsstorlek istället för att bortse från skillnaden mellan stora och små bokstäver."
|
||||
},
|
||||
"settings": {
|
||||
"avoidChanges": "(lämna blankt för att undvika ändringar)",
|
||||
@@ -666,7 +669,9 @@
|
||||
"no": "Nej{suffix}",
|
||||
"to": "till{suffix}",
|
||||
"and": "och{suffix}",
|
||||
"or": "eller{suffix}"
|
||||
"or": "eller{suffix}",
|
||||
"camera": "Kamera{suffix}",
|
||||
"video": "Video{suffix}"
|
||||
},
|
||||
"access": {
|
||||
"rules": "Regler för åtkomst",
|
||||
@@ -778,7 +783,6 @@
|
||||
"scopeAria": "Begränsa sökningen till en mapp",
|
||||
"noTerms": "Ange minst en sökterm.",
|
||||
"termTooShort": "Varje sökterm måste innehålla minst {minSearchLength} tecken",
|
||||
"caseExact": "Matchning med skiftlägeskänslighet",
|
||||
"resultsTitle": "Sökresultat",
|
||||
"invalidSources": "Slå på minst en källa.",
|
||||
"noResults": "Inga matchande filer eller mappar."
|
||||
@@ -807,7 +811,18 @@
|
||||
"description": "Visa alla meddelanden från din aktuella session",
|
||||
"empty": "Inga meddelanden ännu",
|
||||
"clearAll": "Rensa alla",
|
||||
"total": "{count} meddelanden"
|
||||
"appEnabled": "Appaviseringar",
|
||||
"appEnabledDescription": "Visa webbläsaraviseringar när filåtgärder slutförs medan den här fliken är i bakgrunden. Kräver behörighet i webbläsaren. Sparas endast i den här webbläsaren.",
|
||||
"appUnsupported": "Appaviseringar stöds inte i den här webbläsaren.",
|
||||
"permissionGranted": "tillåtet",
|
||||
"permissionDeniedHelp": "Meddelanden är blockerade. Aktivera dem i webbläsarens inställningar för den här webbplatsen.",
|
||||
"uploadTitle": "Uppladdningen är klar",
|
||||
"downloadTitle": "Ladda ner hela filen",
|
||||
"moveTitle": "Flytten är klar",
|
||||
"copyTitle": "Kopieringen är klar",
|
||||
"uploadFailedTitle": "Uppladdningen misslyckades",
|
||||
"downloadFailedTitle": "Nedladdningen misslyckades",
|
||||
"operationFailedTitle": "Åtgärden misslyckades"
|
||||
},
|
||||
"events": {
|
||||
"serverShutdown": "Servern stängdes av.",
|
||||
@@ -860,5 +875,8 @@
|
||||
"documentDownload": "Dokumentnedladdning - Komplett",
|
||||
"editorSuccess": "Initialisering av Editor - Framgång! Alla steg slutförda.",
|
||||
"setupError": "Inställningsfel"
|
||||
},
|
||||
"pwa": {
|
||||
"install": "Installera appen"
|
||||
}
|
||||
}
|
||||
@@ -29,7 +29,8 @@
|
||||
"goToItem": "Öğeye git",
|
||||
"copyPath": "Kopyalama Yolu",
|
||||
"pinItem": "Sabitle",
|
||||
"unpinItem": "Sabitlemeyi Kaldır"
|
||||
"unpinItem": "Sabitlemeyi Kaldır",
|
||||
"sendToApp": "Uygulamaya gönder"
|
||||
},
|
||||
"errors": {
|
||||
"forbidden": "Buna erişim izniniz yok.",
|
||||
@@ -245,7 +246,9 @@
|
||||
"helpText2": "Yalnızca dosya veya klasör adlarında alt dize eşleşmesi (tam yollarda değil). case:exact büyük/küçük harfe duyarlı eşleştirme için\n\n terimler arasında VEYA için boru karakteri kullanın; bir ifade için çift tırnak kullanın.\n\nDaha gelişmiş seçenekler için joker karakter aramayı etkinleştirin.",
|
||||
"useWildcardSearch": "Joker karakter araması (büyük/küçük harfe duyarlı)",
|
||||
"useWildcardSearchDescription": "Desteklenen joker karakter seçenekleri:\n\n- Herhangi bir diziyi eşleştirin: *\n\n- Bir karakteri eşleştirin: ?\n\n- Karakter sınıfı: [a-z], [0-9]\n\n- Negasyon: [^0-9]\n\n- Literal *, ? veya [ parantez içine alınmalıdır, örneğin [*], [?], [[]\n\n- Tüm sonuçlar büyük/küçük harfe duyarlıdır",
|
||||
"openInAdvancedSearch": "Gelişmiş Aramada Aç"
|
||||
"openInAdvancedSearch": "Gelişmiş Aramada Aç",
|
||||
"caseExact": "Büyük/küçük harf duyarlı eşleştirme",
|
||||
"caseExactDescription": "Büyük ve küçük harfleri göz ardı etmek yerine, dosya ve klasör adlarını tam harf büyük/küçük yazımına göre eşleştirin."
|
||||
},
|
||||
"settings": {
|
||||
"avoidChanges": "(değişiklikleri önlemek için boş bırakın)",
|
||||
@@ -572,7 +575,9 @@
|
||||
"no": "Hayır{suffix}",
|
||||
"to": "...'ye{suffix}",
|
||||
"and": "ve{suffix}",
|
||||
"or": "veya{suffix}"
|
||||
"or": "veya{suffix}",
|
||||
"camera": "Kamera{suffix}",
|
||||
"video": "Video{suffix}"
|
||||
},
|
||||
"access": {
|
||||
"rules": "Erişim kuralları",
|
||||
@@ -778,7 +783,6 @@
|
||||
"scopeAria": "Aramayı bir klasörle sınırlandırın",
|
||||
"noTerms": "En az bir arama terimi girin.",
|
||||
"termTooShort": "Her bir arama terimi en az {minSearchLength} karakter olmalıdır",
|
||||
"caseExact": "Harfe duyarlı eşleşme",
|
||||
"resultsTitle": "Arama sonuçları",
|
||||
"invalidSources": "En az bir kaynağı açın.",
|
||||
"noResults": "Eşleşen dosya veya klasör yok."
|
||||
@@ -807,7 +811,18 @@
|
||||
"description": "Geçerli oturumunuzdaki tüm bildirimleri görüntüleyin",
|
||||
"empty": "Henüz bildirim yok",
|
||||
"clearAll": "Tümünü Temizle",
|
||||
"total": "{count} BİLDİRİMLER"
|
||||
"appEnabled": "Uygulama bildirimleri",
|
||||
"appEnabledDescription": "Bu sekme arka planda iken dosya işlemleri tamamlandığında tarayıcı bildirimlerini göster. Tarayıcı izni gerektirir. Yalnızca bu tarayıcıda saklanır.",
|
||||
"appUnsupported": "Bu tarayıcıda uygulama bildirimleri desteklenmemektedir.",
|
||||
"permissionGranted": "izin verilen",
|
||||
"permissionDeniedHelp": "Bildirimler engellenmiştir. Bu site için tarayıcı ayarlarınızdan bildirimleri etkinleştirin.",
|
||||
"uploadTitle": "Yükleme tamamlandı",
|
||||
"downloadTitle": "Tam sürümü indir",
|
||||
"moveTitle": "İşlem tamamlandı",
|
||||
"copyTitle": "Kopyalama tamamlandı",
|
||||
"uploadFailedTitle": "Yükleme başarısız",
|
||||
"downloadFailedTitle": "İndirme başarısız",
|
||||
"operationFailedTitle": "İşlem başarısız oldu"
|
||||
},
|
||||
"events": {
|
||||
"serverShutdown": "Sunucu kapatıldı.",
|
||||
@@ -860,5 +875,8 @@
|
||||
"documentDownload": "Belge İndirme - Tamamlandı",
|
||||
"editorSuccess": "Editör Başlatma - Başarılı! Tüm adımlar tamamlandı.",
|
||||
"setupError": "Kurulum Hatası"
|
||||
},
|
||||
"pwa": {
|
||||
"install": "Uygulamayı yükle"
|
||||
}
|
||||
}
|
||||
@@ -29,7 +29,8 @@
|
||||
"goToItem": "Перейти до пункту",
|
||||
"copyPath": "Шлях копіювання",
|
||||
"pinItem": "Закріпити",
|
||||
"unpinItem": "Відкріпити"
|
||||
"unpinItem": "Відкріпити",
|
||||
"sendToApp": "Надіслати в додаток"
|
||||
},
|
||||
"errors": {
|
||||
"forbidden": "У вас немає прав доступу до цього.",
|
||||
@@ -245,7 +246,9 @@
|
||||
"helpText2": "Підрядковий пошук лише за іменами файлів або папок (не за повними шляхами). case:exact для пошуку з урахуванням регістру\n\n для АБО між термінами використовуйте символ труби; подвійні лапки для однієї фрази.\n\nУвімкніть пошук за підстановочними символами для більш розширених можливостей.",
|
||||
"useWildcardSearch": "Пошук за підстановчими символами (з урахуванням регістру)",
|
||||
"useWildcardSearchDescription": "Підтримувані символи підстановки:\n\n- Відповідність будь-якій послідовності: *\n\n- Збіг з одним символом: ?\n\n- Клас символів: [a-z], [0-9].\n\n- Заперечення: [^0-9]\n\n- Літерали *, ? або [ слід брати в дужки, наприклад, [*], [?], [[].\n\n- Всі результати чутливі до регістру",
|
||||
"openInAdvancedSearch": "Відкрити в розширеному пошуку"
|
||||
"openInAdvancedSearch": "Відкрити в розширеному пошуку",
|
||||
"caseExact": "З урахуванням регістру",
|
||||
"caseExactDescription": "Збігати назви файлів і папок, враховуючи точний регістр літер, а не ігноруючи великі та малі літери."
|
||||
},
|
||||
"settings": {
|
||||
"avoidChanges": "(залишіть поле порожнім, щоб уникнути змін)",
|
||||
@@ -666,7 +669,9 @@
|
||||
"no": "Ні.{suffix}",
|
||||
"to": "до{suffix}",
|
||||
"and": "та{suffix}",
|
||||
"or": "або{suffix}"
|
||||
"or": "або{suffix}",
|
||||
"camera": "Камера{suffix}",
|
||||
"video": "Відео{suffix}"
|
||||
},
|
||||
"access": {
|
||||
"rules": "Правила доступу",
|
||||
@@ -778,7 +783,6 @@
|
||||
"scopeAria": "Обмежити пошук до папки",
|
||||
"noTerms": "Введіть принаймні один пошуковий запит.",
|
||||
"termTooShort": "Кожен пошуковий запит повинен містити щонайменше {minSearchLength} символів",
|
||||
"caseExact": "Збіг з урахуванням регістру",
|
||||
"resultsTitle": "Результати пошуку",
|
||||
"invalidSources": "Увімкніть хоча б одне джерело.",
|
||||
"noResults": "Немає співпадаючих файлів або папок."
|
||||
@@ -807,7 +811,18 @@
|
||||
"description": "Переглянути всі сповіщення з поточного сеансу",
|
||||
"empty": "Ще немає повідомлень",
|
||||
"clearAll": "Очистити все.",
|
||||
"total": "{count} сповіщення"
|
||||
"appEnabled": "Сповіщення в додатках",
|
||||
"appEnabledDescription": "Показувати сповіщення браузера про завершення операцій з файлами, коли ця вкладка працює у фоновому режимі. Потрібний дозвіл браузера. Зберігається лише в цьому браузері.",
|
||||
"appUnsupported": "У цьому браузері не підтримуються сповіщення додатків.",
|
||||
"permissionGranted": "дозволено",
|
||||
"permissionDeniedHelp": "Повідомлення заблоковано. Увімкніть їх у налаштуваннях браузера для цього сайту.",
|
||||
"uploadTitle": "Завантаження завершено",
|
||||
"downloadTitle": "Завантажити повністю",
|
||||
"moveTitle": "Переміщення завершено",
|
||||
"copyTitle": "Копіювання завершено",
|
||||
"uploadFailedTitle": "Завантаження не вдалося",
|
||||
"downloadFailedTitle": "Завантаження не вдалося",
|
||||
"operationFailedTitle": "Операція не вдалася"
|
||||
},
|
||||
"events": {
|
||||
"serverShutdown": "Сервер був вимкнений.",
|
||||
@@ -860,5 +875,8 @@
|
||||
"documentDownload": "Завантаження документа - Повне",
|
||||
"editorSuccess": "Ініціалізація редактора - успіх! Всі кроки виконано.",
|
||||
"setupError": "Помилка встановлення"
|
||||
},
|
||||
"pwa": {
|
||||
"install": "Встановити додаток"
|
||||
}
|
||||
}
|
||||
@@ -29,7 +29,8 @@
|
||||
"goToItem": "转到项目",
|
||||
"copyPath": "复制路径",
|
||||
"pinItem": "固定",
|
||||
"unpinItem": "取消固定"
|
||||
"unpinItem": "取消固定",
|
||||
"sendToApp": "发送至应用"
|
||||
},
|
||||
"errors": {
|
||||
"forbidden": "你无权限访问",
|
||||
@@ -245,7 +246,9 @@
|
||||
"helpText2": "仅对文件或文件夹名称(而非完整路径)进行子串匹配。 case:exact 用于大小写敏感匹配\n\n 对于术语之间的 OR,使用管道字符;一个短语使用双引号。\n\n启用通配符搜索可获得更多高级选项。",
|
||||
"useWildcardSearch": "通配符搜索(区分大小写)",
|
||||
"useWildcardSearchDescription": "支持通配符选项:\n\n- 匹配任何序列:*\n\n- 匹配一个字符:?\n\n- 字符类别:[a-z]、[0-9]\n\n- 否定:[^0-9]\n\n- 字面 *、? 或 [ 需要用括号包装,例如 [*]、[?]、[[]\n\n- 所有结果均区分大小写",
|
||||
"openInAdvancedSearch": "在高级搜索中打开"
|
||||
"openInAdvancedSearch": "在高级搜索中打开",
|
||||
"caseExact": "区分大小写的匹配",
|
||||
"caseExactDescription": "匹配文件和文件夹名称时,应严格区分大小写,而非忽略大小写。"
|
||||
},
|
||||
"settings": {
|
||||
"scopes": "定义允许的来源及其中的用户范围",
|
||||
@@ -666,7 +669,9 @@
|
||||
"no": "没有{suffix}",
|
||||
"to": "到{suffix}",
|
||||
"and": "以及{suffix}",
|
||||
"or": "或{suffix}"
|
||||
"or": "或{suffix}",
|
||||
"camera": "相机{suffix}",
|
||||
"video": "视频{suffix}"
|
||||
},
|
||||
"access": {
|
||||
"rules": "访问规则",
|
||||
@@ -778,7 +783,6 @@
|
||||
"scopeAria": "限制搜索文件夹",
|
||||
"noTerms": "至少输入一个搜索词。",
|
||||
"termTooShort": "每个搜索词必须至少包含{minSearchLength} 个字符",
|
||||
"caseExact": "区分大小写匹配",
|
||||
"resultsTitle": "搜索结果",
|
||||
"invalidSources": "至少打开一个信号源。",
|
||||
"noResults": "没有匹配的文件或文件夹。"
|
||||
@@ -807,7 +811,18 @@
|
||||
"description": "查看当前会话的所有通知",
|
||||
"empty": "尚无通知",
|
||||
"clearAll": "全部清除",
|
||||
"total": "{count} 通知"
|
||||
"appEnabled": "应用通知",
|
||||
"appEnabledDescription": "当此标签页处于后台时,在文件操作完成后显示浏览器通知。需要浏览器权限。仅存储于此浏览器中。",
|
||||
"appUnsupported": "此浏览器不支持应用通知。",
|
||||
"permissionGranted": "允许",
|
||||
"permissionDeniedHelp": "通知已被屏蔽。请在浏览器设置中为该网站启用通知。",
|
||||
"uploadTitle": "上传完成",
|
||||
"downloadTitle": "下载完成",
|
||||
"moveTitle": "移动完成",
|
||||
"copyTitle": "复制完成",
|
||||
"uploadFailedTitle": "上传失败",
|
||||
"downloadFailedTitle": "下载失败",
|
||||
"operationFailedTitle": "操作失败"
|
||||
},
|
||||
"events": {
|
||||
"serverShutdown": "服务器已关闭。",
|
||||
@@ -860,5 +875,8 @@
|
||||
"documentDownload": "文件下载 - 完成",
|
||||
"editorSuccess": "编辑器初始化 - 成功!所有步骤已完成。",
|
||||
"setupError": "设置错误"
|
||||
},
|
||||
"pwa": {
|
||||
"install": "安装应用"
|
||||
}
|
||||
}
|
||||
Loaded 100 of 165 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user