mirror of
https://github.com/gtsteffaniak/filebrowser.git
synced 2026-11-04 16:50:08 -05:00
Compare commits
11
Commits
main
...
v1.2.3-stable
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
56cc547a3e | ||
|
|
5b8d2f71ab | ||
|
|
09713b32a5 | ||
|
|
52e3c5c142 | ||
|
|
2af75311bd | ||
|
|
82c0182c22 | ||
|
|
c8f0b5abea | ||
|
|
d3d029d1e9 | ||
|
|
f39002f375 | ||
|
|
a658f100c0 | ||
|
|
c2d255a25e |
No files matched your search
@@ -2,6 +2,24 @@
|
||||
|
||||
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.2.2-stable
|
||||
|
||||
**Security**:
|
||||
- Patched Stored XSS in public share page via unsanitized share metadata (text/template misuse)
|
||||
- Patched Incomplete Remediation of CVE-2026-27611: Password-Protected Share Bypass via /public/api/share/info GHSA-525j-95gf-766f
|
||||
|
||||
**New Features**:
|
||||
- "Divider" option in sidebar links to add a text or divider between links (#1875)
|
||||
- shares offer a "go to source Location" sidebar link and button when editing a share.
|
||||
|
||||
**Notes**:
|
||||
- Share edit/delete permissions are scoped to the user's shares rather than global (#2050)
|
||||
- OIDC group claims accepted as map (#2084)
|
||||
|
||||
**BugFixes**:
|
||||
- fixed the requirement that the database path needed to be set in the config file, now it loads `FILEBROWSER_DATABASE` value by default, fallback to config file property.
|
||||
- Error downloading zipped directory: no such file or directory (users with scope) #2015
|
||||
|
||||
## v1.2.1-stable
|
||||
|
||||
**BugFixes**:
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
FROM gtstef/ffmpeg:8.0.1-decode AS ffmpeg
|
||||
FROM gtstef/ffmpeg:8.1-decode AS ffmpeg
|
||||
FROM golang:alpine AS base
|
||||
ARG VERSION
|
||||
ARG REVISION
|
||||
|
||||
+12
-4
@@ -9,6 +9,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/gtsteffaniak/filebrowser/backend/common/errors"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/common/utils"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/database/users"
|
||||
)
|
||||
|
||||
@@ -42,14 +43,21 @@ func (auther JSONAuth) Auth(r *http.Request, userStore *users.Storage) (*users.U
|
||||
}
|
||||
}
|
||||
|
||||
user, err := userStore.Get(username)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to get user from store: %v", err)
|
||||
user, getErr := userStore.Get(username)
|
||||
var passwordHash string
|
||||
if getErr != nil {
|
||||
passwordHash = utils.InvalidPasswordHash
|
||||
} else {
|
||||
passwordHash = user.Password
|
||||
}
|
||||
err = users.CheckPwd(password, user.Password)
|
||||
// always run checkPwd to prevent timing attacks
|
||||
err = utils.CheckPwd(password, passwordHash)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if getErr != nil {
|
||||
return nil, fmt.Errorf("unable to get user from store: %v", err)
|
||||
}
|
||||
|
||||
// check for OTP for password
|
||||
if user.TOTPSecret != "" {
|
||||
|
||||
+9
-4
@@ -89,6 +89,11 @@ func StartFilebrowser() {
|
||||
settings.Env.EmbeddedFs = os.IsNotExist(err)
|
||||
}
|
||||
|
||||
err = utils.SetInvalidPasswordHash()
|
||||
if err != nil {
|
||||
logger.Fatalf("Failed to set security hash: %v", err)
|
||||
}
|
||||
|
||||
sourceList := []string{}
|
||||
for path, source := range settings.Config.Server.SourceMap {
|
||||
sourceList = append(sourceList, fmt.Sprintf("%v: %v", source.Name, path))
|
||||
@@ -182,7 +187,7 @@ func rootCMD(ctx context.Context, store *bolt.BoltStore, serverConfig *settings.
|
||||
cacheDir := settings.Config.Server.CacheDir
|
||||
numWorkers := settings.Config.Server.NumImageProcessors
|
||||
ffmpeg.SetFFmpegPaths()
|
||||
|
||||
|
||||
// Initialize asset filesystem before starting services
|
||||
if settings.Env.EmbeddedFs {
|
||||
embeddedAssets := fbhttp.GetEmbeddedAssets()
|
||||
@@ -194,7 +199,7 @@ func rootCMD(ctx context.Context, store *bolt.BoltStore, serverConfig *settings.
|
||||
} else {
|
||||
fileutils.InitAssetFS(nil, false)
|
||||
}
|
||||
|
||||
|
||||
// Start preview service
|
||||
err := preview.StartPreviewGenerator(numWorkers, cacheDir)
|
||||
if err != nil {
|
||||
@@ -202,12 +207,12 @@ func rootCMD(ctx context.Context, store *bolt.BoltStore, serverConfig *settings.
|
||||
}
|
||||
logger.Debugf("MuPDF Enabled : %v", settings.Env.MuPdfAvailable)
|
||||
logger.Debugf("Media Enabled : %v", settings.MediaEnabled())
|
||||
|
||||
|
||||
// Generate PWA icons after preview service is initialized
|
||||
if err := icons.GeneratePWAIcons(); err != nil {
|
||||
logger.Warningf("Failed to generate PWA icons: %v", err)
|
||||
}
|
||||
|
||||
|
||||
fbhttp.StartHttp(ctx, store, shutdownComplete)
|
||||
return nil
|
||||
}
|
||||
@@ -480,8 +480,8 @@ func loadConfigWithDefaults(configFile string, generate bool) error {
|
||||
|
||||
// Check if config file exists
|
||||
if _, err := os.Stat(configFile); err != nil {
|
||||
if configFile != "" {
|
||||
logger.Errorf("could not open config file '%v', using default settings.", configFile)
|
||||
if configFile != "" && configFile != "config.yaml" {
|
||||
logger.Fatalf("could not open config file '%v'", configFile)
|
||||
}
|
||||
Config.Server.Sources = []*Source{
|
||||
{
|
||||
@@ -634,14 +634,9 @@ func setDefaults(generate bool) Settings {
|
||||
database := os.Getenv("FILEBROWSER_DATABASE")
|
||||
if database == "" {
|
||||
database = "database.db"
|
||||
} else {
|
||||
// check if database file exists
|
||||
if _, err := os.Stat(database); os.IsNotExist(err) {
|
||||
database = "database.db"
|
||||
}
|
||||
}
|
||||
if _, err := os.Stat(database); os.IsNotExist(err) {
|
||||
logger.Warning("database file could not be found. If this is unexpected, the default path is `./database.db`, but it can be configured in the config file under `server.database`.")
|
||||
logger.Warning("database file could not be found. If this is unexpected, please set the FILEBROWSER_DATABASE environment variable to the correct path.")
|
||||
}
|
||||
s := Settings{
|
||||
Server: Server{
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
var BcryptCost = bcrypt.DefaultCost
|
||||
var InvalidPasswordHash = ""
|
||||
|
||||
// HashPwd hashes a password.
|
||||
func HashPwd(password string) (string, error) {
|
||||
bytes, err := bcrypt.GenerateFromPassword([]byte(password), BcryptCost)
|
||||
return string(bytes), err
|
||||
}
|
||||
|
||||
// CheckPwd checks if a password is correct.
|
||||
func CheckPwd(password, hash string) error {
|
||||
return bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
|
||||
}
|
||||
|
||||
func SetInvalidPasswordHash() error {
|
||||
passwordHash, err := HashPwd(InsecureRandomIdentifier(16))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
InvalidPasswordHash = passwordHash
|
||||
return nil
|
||||
}
|
||||
@@ -2,10 +2,13 @@ package share
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/gtsteffaniak/filebrowser/backend/common/settings"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/database/users"
|
||||
)
|
||||
|
||||
// IsSingleFileShare determines if this share is for a single file (not a directory).
|
||||
@@ -144,10 +147,69 @@ func (l *Link) HasReachedUserLimit(username string) bool {
|
||||
return count >= l.DownloadsLimit
|
||||
}
|
||||
|
||||
func (l *Link) GetSourceName() (string, error) {
|
||||
func (l *Link) GetSourceName() string {
|
||||
sourceInfo, ok := settings.Config.Server.SourceMap[l.Source]
|
||||
if !ok {
|
||||
return "", fmt.Errorf("source not found")
|
||||
return ""
|
||||
}
|
||||
return sourceInfo.Name, nil
|
||||
return sourceInfo.Name
|
||||
}
|
||||
|
||||
func (l *Link) UserCanEdit(user *users.User) bool {
|
||||
return l.UserID == user.ID || user.Permissions.Admin
|
||||
}
|
||||
|
||||
func (l *Link) SourceURL(user *users.User) string {
|
||||
sourceName := l.GetSourceName()
|
||||
// get user scope path from share
|
||||
userScope, err := user.GetScopeForSourceName(sourceName)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
if !strings.HasPrefix(l.Path, userScope) {
|
||||
return ""
|
||||
}
|
||||
scopedPath := strings.TrimPrefix(l.Path, userScope)
|
||||
return "/" + filepath.Join("files", sourceName, scopedPath)
|
||||
}
|
||||
|
||||
func (l *Link) BannerURL() string {
|
||||
_, _, err := l.GetShareImagePartsHelper(true)
|
||||
if err == nil {
|
||||
return fmt.Sprintf("%s%spublic/api/share/image?banner=true&hash=%s", settings.Config.Server.ExternalUrl, settings.Config.Server.BaseURL, l.Hash)
|
||||
}
|
||||
return l.Banner
|
||||
}
|
||||
|
||||
func (l *Link) FaviconURL() string {
|
||||
_, _, err := l.GetShareImagePartsHelper(false)
|
||||
if err == nil {
|
||||
return fmt.Sprintf("%s%spublic/api/share/image?favicon=true&hash=%s", settings.Config.Server.ExternalUrl, settings.Config.Server.BaseURL, l.Hash)
|
||||
}
|
||||
return l.Favicon
|
||||
}
|
||||
|
||||
func (l *Link) GetShareImagePartsHelper(isBanner bool) (string, string, error) {
|
||||
// Get the asset query string from share
|
||||
var assetQueryString string
|
||||
if isBanner {
|
||||
assetQueryString = l.Banner
|
||||
} else {
|
||||
assetQueryString = l.Favicon
|
||||
}
|
||||
|
||||
if assetQueryString == "" {
|
||||
return "", "", fmt.Errorf("asset not configured for this share")
|
||||
}
|
||||
|
||||
// Parse the query string to extract source and path
|
||||
assetParams, err := url.ParseQuery(assetQueryString)
|
||||
if err != nil {
|
||||
return "", "", fmt.Errorf("invalid asset configuration: %v", err)
|
||||
}
|
||||
|
||||
sourceName := assetParams.Get("source")
|
||||
assetPath := assetParams.Get("path")
|
||||
|
||||
return sourceName, assetPath, nil
|
||||
}
|
||||
@@ -45,6 +45,8 @@ type CommonShare struct {
|
||||
HasPassword bool `json:"hasPassword,omitempty"`
|
||||
ShowHidden bool `json:"showHidden,omitempty"` // show hidden files in share (true = show, false = hide)
|
||||
DisableLoginOption bool `json:"disableLoginOption"` // disable login option in share (true = hide, false = show)
|
||||
SourceURL string `json:"sourceURL,omitempty"`
|
||||
CanEdit bool `json:"canEdit,omitempty"`
|
||||
}
|
||||
type CreateBody struct {
|
||||
CommonShare
|
||||
|
||||
@@ -18,6 +18,7 @@ import (
|
||||
var userStore *users.Storage
|
||||
|
||||
func InitializeDb(path string) (*bolt.BoltStore, bool, error) {
|
||||
logger.Debugf("Initializing database: %v", path)
|
||||
exists, err := dbExists(path)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
|
||||
+26
-26
@@ -15,23 +15,23 @@ require (
|
||||
github.com/google/go-cmp v0.7.0
|
||||
github.com/gtsteffaniak/go-cache v1.1.0
|
||||
github.com/gtsteffaniak/go-logger v1.0.2
|
||||
github.com/kovidgoyal/imaging v1.8.19
|
||||
github.com/mattn/go-sqlite3 v1.14.34
|
||||
github.com/kovidgoyal/imaging v1.8.20
|
||||
github.com/mattn/go-sqlite3 v1.14.37
|
||||
github.com/pquerna/otp v1.5.0
|
||||
github.com/spf13/afero v1.15.0
|
||||
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.48.0
|
||||
golang.org/x/image v0.36.0
|
||||
golang.org/x/mod v0.33.0
|
||||
golang.org/x/net v0.50.0
|
||||
golang.org/x/oauth2 v0.35.0
|
||||
golang.org/x/sys v0.41.0
|
||||
golang.org/x/time v0.14.0
|
||||
golang.org/x/tools v0.42.0
|
||||
golang.org/x/crypto v0.49.0
|
||||
golang.org/x/image v0.37.0
|
||||
golang.org/x/mod v0.34.0
|
||||
golang.org/x/net v0.52.0
|
||||
golang.org/x/oauth2 v0.36.0
|
||||
golang.org/x/sys v0.42.0
|
||||
golang.org/x/time v0.15.0
|
||||
golang.org/x/tools v0.43.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
modernc.org/sqlite v1.46.1
|
||||
modernc.org/sqlite v1.47.0
|
||||
)
|
||||
|
||||
require (
|
||||
@@ -99,7 +99,7 @@ require (
|
||||
github.com/dsoprea/go-logging v0.0.0-20200710184922-b02d349568dd // indirect
|
||||
github.com/dsoprea/go-utility/v2 v2.0.0-20221003172846-a3e1774ef349 // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/ebitengine/purego v0.10.0-alpha.4 // indirect
|
||||
github.com/ebitengine/purego v0.10.0 // indirect
|
||||
github.com/ettle/strcase v0.2.0 // indirect
|
||||
github.com/fatih/color v1.18.0 // indirect
|
||||
github.com/fatih/structtag v1.2.0 // indirect
|
||||
@@ -111,16 +111,16 @@ require (
|
||||
github.com/go-critic/go-critic v0.14.3 // indirect
|
||||
github.com/go-errors/errors v1.5.1 // indirect
|
||||
github.com/go-jose/go-jose/v4 v4.1.3 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.22.4 // indirect
|
||||
github.com/go-openapi/jsonreference v0.21.4 // indirect
|
||||
github.com/go-openapi/spec v0.22.3 // indirect
|
||||
github.com/go-openapi/swag/conv v0.25.4 // indirect
|
||||
github.com/go-openapi/swag/jsonname v0.25.4 // indirect
|
||||
github.com/go-openapi/swag/jsonutils v0.25.4 // indirect
|
||||
github.com/go-openapi/swag/loading v0.25.4 // indirect
|
||||
github.com/go-openapi/swag/stringutils v0.25.4 // indirect
|
||||
github.com/go-openapi/swag/typeutils v0.25.4 // indirect
|
||||
github.com/go-openapi/swag/yamlutils v0.25.4 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.22.5 // indirect
|
||||
github.com/go-openapi/jsonreference v0.21.5 // indirect
|
||||
github.com/go-openapi/spec v0.22.4 // indirect
|
||||
github.com/go-openapi/swag/conv v0.25.5 // indirect
|
||||
github.com/go-openapi/swag/jsonname v0.25.5 // indirect
|
||||
github.com/go-openapi/swag/jsonutils v0.25.5 // indirect
|
||||
github.com/go-openapi/swag/loading v0.25.5 // indirect
|
||||
github.com/go-openapi/swag/stringutils v0.25.5 // indirect
|
||||
github.com/go-openapi/swag/typeutils v0.25.5 // indirect
|
||||
github.com/go-openapi/swag/yamlutils v0.25.5 // 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
|
||||
@@ -136,7 +136,7 @@ require (
|
||||
github.com/godoc-lint/godoc-lint v0.11.2 // indirect
|
||||
github.com/gofrs/flock v0.13.0 // indirect
|
||||
github.com/gohugoio/hugo v0.149.1 // indirect
|
||||
github.com/golang/geo v0.0.0-20260129164528-943061e2742c // indirect
|
||||
github.com/golang/geo v0.0.0-20260302211937-87f5a40ea07a // indirect
|
||||
github.com/golang/protobuf v1.5.3 // indirect
|
||||
github.com/golangci/asciicheck v0.5.0 // indirect
|
||||
github.com/golangci/dupl v0.0.0-20250308024227-f665c8d69b32 // indirect
|
||||
@@ -272,13 +272,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.19.0 // indirect
|
||||
golang.org/x/text v0.34.0 // indirect
|
||||
golang.org/x/sync v0.20.0 // indirect
|
||||
golang.org/x/text v0.35.0 // indirect
|
||||
google.golang.org/protobuf v1.36.8 // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
honnef.co/go/tools v0.7.0 // indirect
|
||||
modernc.org/libc v1.68.0 // indirect
|
||||
modernc.org/libc v1.70.0 // indirect
|
||||
modernc.org/mathutil v1.7.1 // indirect
|
||||
modernc.org/memory v1.11.0 // indirect
|
||||
mvdan.cc/gofumpt v0.9.2 // indirect
|
||||
|
||||
+62
-62
@@ -244,8 +244,8 @@ github.com/dsoprea/go-utility/v2 v2.0.0-20221003172846-a3e1774ef349 h1:DilThiXje
|
||||
github.com/dsoprea/go-utility/v2 v2.0.0-20221003172846-a3e1774ef349/go.mod h1:4GC5sXji84i/p+irqghpPFZBF8tRN/Q7+700G0/DLe8=
|
||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/ebitengine/purego v0.10.0-alpha.4 h1:JzPbdf+cqbyT9sZtP4xnqelwUXwf7LvD8xKS6+ofTds=
|
||||
github.com/ebitengine/purego v0.10.0-alpha.4/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
|
||||
github.com/ebitengine/purego v0.10.0 h1:QIw4xfpWT6GWTzaW5XEKy3HXoqrJGx1ijYHzTF0/ISU=
|
||||
github.com/ebitengine/purego v0.10.0/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
@@ -298,33 +298,33 @@ github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V
|
||||
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
|
||||
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
||||
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-openapi/jsonpointer v0.22.4 h1:dZtK82WlNpVLDW2jlA1YCiVJFVqkED1MegOUy9kR5T4=
|
||||
github.com/go-openapi/jsonpointer v0.22.4/go.mod h1:elX9+UgznpFhgBuaMQ7iu4lvvX1nvNsesQ3oxmYTw80=
|
||||
github.com/go-openapi/jsonreference v0.21.4 h1:24qaE2y9bx/q3uRK/qN+TDwbok1NhbSmGjjySRCHtC8=
|
||||
github.com/go-openapi/jsonreference v0.21.4/go.mod h1:rIENPTjDbLpzQmQWCj5kKj3ZlmEh+EFVbz3RTUh30/4=
|
||||
github.com/go-openapi/spec v0.22.3 h1:qRSmj6Smz2rEBxMnLRBMeBWxbbOvuOoElvSvObIgwQc=
|
||||
github.com/go-openapi/spec v0.22.3/go.mod h1:iIImLODL2loCh3Vnox8TY2YWYJZjMAKYyLH2Mu8lOZs=
|
||||
github.com/go-openapi/jsonpointer v0.22.5 h1:8on/0Yp4uTb9f4XvTrM2+1CPrV05QPZXu+rvu2o9jcA=
|
||||
github.com/go-openapi/jsonpointer v0.22.5/go.mod h1:gyUR3sCvGSWchA2sUBJGluYMbe1zazrYWIkWPjjMUY0=
|
||||
github.com/go-openapi/jsonreference v0.21.5 h1:6uCGVXU/aNF13AQNggxfysJ+5ZcU4nEAe+pJyVWRdiE=
|
||||
github.com/go-openapi/jsonreference v0.21.5/go.mod h1:u25Bw85sX4E2jzFodh1FOKMTZLcfifd1Q+iKKOUxExw=
|
||||
github.com/go-openapi/spec v0.22.4 h1:4pxGjipMKu0FzFiu/DPwN3CTBRlVM2yLf/YTWorYfDQ=
|
||||
github.com/go-openapi/spec v0.22.4/go.mod h1:WQ6Ai0VPWMZgMT4XySjlRIE6GP1bGQOtEThn3gcWLtQ=
|
||||
github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=
|
||||
github.com/go-openapi/swag/conv v0.25.4 h1:/Dd7p0LZXczgUcC/Ikm1+YqVzkEeCc9LnOWjfkpkfe4=
|
||||
github.com/go-openapi/swag/conv v0.25.4/go.mod h1:3LXfie/lwoAv0NHoEuY1hjoFAYkvlqI/Bn5EQDD3PPU=
|
||||
github.com/go-openapi/swag/jsonname v0.25.4 h1:bZH0+MsS03MbnwBXYhuTttMOqk+5KcQ9869Vye1bNHI=
|
||||
github.com/go-openapi/swag/jsonname v0.25.4/go.mod h1:GPVEk9CWVhNvWhZgrnvRA6utbAltopbKwDu8mXNUMag=
|
||||
github.com/go-openapi/swag/jsonutils v0.25.4 h1:VSchfbGhD4UTf4vCdR2F4TLBdLwHyUDTd1/q4i+jGZA=
|
||||
github.com/go-openapi/swag/jsonutils v0.25.4/go.mod h1:7OYGXpvVFPn4PpaSdPHJBtF0iGnbEaTk8AvBkoWnaAY=
|
||||
github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.4 h1:IACsSvBhiNJwlDix7wq39SS2Fh7lUOCJRmx/4SN4sVo=
|
||||
github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.4/go.mod h1:Mt0Ost9l3cUzVv4OEZG+WSeoHwjWLnarzMePNDAOBiM=
|
||||
github.com/go-openapi/swag/loading v0.25.4 h1:jN4MvLj0X6yhCDduRsxDDw1aHe+ZWoLjW+9ZQWIKn2s=
|
||||
github.com/go-openapi/swag/loading v0.25.4/go.mod h1:rpUM1ZiyEP9+mNLIQUdMiD7dCETXvkkC30z53i+ftTE=
|
||||
github.com/go-openapi/swag/stringutils v0.25.4 h1:O6dU1Rd8bej4HPA3/CLPciNBBDwZj9HiEpdVsb8B5A8=
|
||||
github.com/go-openapi/swag/stringutils v0.25.4/go.mod h1:GTsRvhJW5xM5gkgiFe0fV3PUlFm0dr8vki6/VSRaZK0=
|
||||
github.com/go-openapi/swag/typeutils v0.25.4 h1:1/fbZOUN472NTc39zpa+YGHn3jzHWhv42wAJSN91wRw=
|
||||
github.com/go-openapi/swag/typeutils v0.25.4/go.mod h1:Ou7g//Wx8tTLS9vG0UmzfCsjZjKhpjxayRKTHXf2pTE=
|
||||
github.com/go-openapi/swag/yamlutils v0.25.4 h1:6jdaeSItEUb7ioS9lFoCZ65Cne1/RZtPBZ9A56h92Sw=
|
||||
github.com/go-openapi/swag/yamlutils v0.25.4/go.mod h1:MNzq1ulQu+yd8Kl7wPOut/YHAAU/H6hL91fF+E2RFwc=
|
||||
github.com/go-openapi/testify/enable/yaml/v2 v2.0.2 h1:0+Y41Pz1NkbTHz8NngxTuAXxEodtNSI1WG1c/m5Akw4=
|
||||
github.com/go-openapi/testify/enable/yaml/v2 v2.0.2/go.mod h1:kme83333GCtJQHXQ8UKX3IBZu6z8T5Dvy5+CW3NLUUg=
|
||||
github.com/go-openapi/testify/v2 v2.0.2 h1:X999g3jeLcoY8qctY/c/Z8iBHTbwLz7R2WXd6Ub6wls=
|
||||
github.com/go-openapi/testify/v2 v2.0.2/go.mod h1:HCPmvFFnheKK2BuwSA0TbbdxJ3I16pjwMkYkP4Ywn54=
|
||||
github.com/go-openapi/swag/conv v0.25.5 h1:wAXBYEXJjoKwE5+vc9YHhpQOFj2JYBMF2DUi+tGu97g=
|
||||
github.com/go-openapi/swag/conv v0.25.5/go.mod h1:CuJ1eWvh1c4ORKx7unQnFGyvBbNlRKbnRyAvDvzWA4k=
|
||||
github.com/go-openapi/swag/jsonname v0.25.5 h1:8p150i44rv/Drip4vWI3kGi9+4W9TdI3US3uUYSFhSo=
|
||||
github.com/go-openapi/swag/jsonname v0.25.5/go.mod h1:jNqqikyiAK56uS7n8sLkdaNY/uq6+D2m2LANat09pKU=
|
||||
github.com/go-openapi/swag/jsonutils v0.25.5 h1:XUZF8awQr75MXeC+/iaw5usY/iM7nXPDwdG3Jbl9vYo=
|
||||
github.com/go-openapi/swag/jsonutils v0.25.5/go.mod h1:48FXUaz8YsDAA9s5AnaUvAmry1UcLcNVWUjY42XkrN4=
|
||||
github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.5 h1:SX6sE4FrGb4sEnnxbFL/25yZBb5Hcg1inLeErd86Y1U=
|
||||
github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.5/go.mod h1:/2KvOTrKWjVA5Xli3DZWdMCZDzz3uV/T7bXwrKWPquo=
|
||||
github.com/go-openapi/swag/loading v0.25.5 h1:odQ/umlIZ1ZVRteI6ckSrvP6e2w9UTF5qgNdemJHjuU=
|
||||
github.com/go-openapi/swag/loading v0.25.5/go.mod h1:I8A8RaaQ4DApxhPSWLNYWh9NvmX2YKMoB9nwvv6oW6g=
|
||||
github.com/go-openapi/swag/stringutils v0.25.5 h1:NVkoDOA8YBgtAR/zvCx5rhJKtZF3IzXcDdwOsYzrB6M=
|
||||
github.com/go-openapi/swag/stringutils v0.25.5/go.mod h1:PKK8EZdu4QJq8iezt17HM8RXnLAzY7gW0O1KKarrZII=
|
||||
github.com/go-openapi/swag/typeutils v0.25.5 h1:EFJ+PCga2HfHGdo8s8VJXEVbeXRCYwzzr9u4rJk7L7E=
|
||||
github.com/go-openapi/swag/typeutils v0.25.5/go.mod h1:itmFmScAYE1bSD8C4rS0W+0InZUBrB2xSPbWt6DLGuc=
|
||||
github.com/go-openapi/swag/yamlutils v0.25.5 h1:kASCIS+oIeoc55j28T4o8KwlV2S4ZLPT6G0iq2SSbVQ=
|
||||
github.com/go-openapi/swag/yamlutils v0.25.5/go.mod h1:Gek1/SjjfbYvM+Iq4QGwa/2lEXde9n2j4a3wI3pNuOQ=
|
||||
github.com/go-openapi/testify/enable/yaml/v2 v2.4.0 h1:7SgOMTvJkM8yWrQlU8Jm18VeDPuAvB/xWrdxFJkoFag=
|
||||
github.com/go-openapi/testify/enable/yaml/v2 v2.4.0/go.mod h1:14iV8jyyQlinc9StD7w1xVPW3CO3q1Gj04Jy//Kw4VM=
|
||||
github.com/go-openapi/testify/v2 v2.4.0 h1:8nsPrHVCWkQ4p8h1EsRVymA2XABB4OT40gcvAu+voFM=
|
||||
github.com/go-openapi/testify/v2 v2.4.0/go.mod h1:HCPmvFFnheKK2BuwSA0TbbdxJ3I16pjwMkYkP4Ywn54=
|
||||
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||
@@ -393,8 +393,8 @@ github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w
|
||||
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-20260129164528-943061e2742c h1:ysO2h2Odnl1AJM1I2Lm/fa6JvO0pECMSt2CwBaa+ITo=
|
||||
github.com/golang/geo v0.0.0-20260129164528-943061e2742c/go.mod h1:Mymr9kRGDc64JPr03TSZmuIBODZ3KyswLzm1xL0HFA8=
|
||||
github.com/golang/geo v0.0.0-20260302211937-87f5a40ea07a h1:7f/cr+n7Jtd7mzCLrx47sdVIGabpSSxCHrf6fJczDp0=
|
||||
github.com/golang/geo v0.0.0-20260302211937-87f5a40ea07a/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=
|
||||
@@ -560,8 +560,8 @@ github.com/kovidgoyal/go-parallel v1.1.1 h1:1OzpNjtrUkBPq3UaqrnvOoB2F9RttSt811ui
|
||||
github.com/kovidgoyal/go-parallel v1.1.1/go.mod h1:BJNIbe6+hxyFWv7n6oEDPj3PA5qSw5OCtf0hcVxWJiw=
|
||||
github.com/kovidgoyal/go-shm v1.0.0 h1:HJEel9D1F9YhULvClEHJLawoRSj/1u/EDV7MJbBPgQo=
|
||||
github.com/kovidgoyal/go-shm v1.0.0/go.mod h1:Yzb80Xf9L3kaoB2RGok9hHwMIt7Oif61kT6t3+VnZds=
|
||||
github.com/kovidgoyal/imaging v1.8.19 h1:zWJdQqF2tfSKjvoB7XpLRhVGbYsze++M0iaqZ4ZkhNk=
|
||||
github.com/kovidgoyal/imaging v1.8.19/go.mod h1:I0q8RdoEuyc4G8GFOF9CaluTUHQSf68d6TmsqpvfRI8=
|
||||
github.com/kovidgoyal/imaging v1.8.20 h1:74GZ7C2rIm3rqmGEjK1GvvPOOnJ0SS5iDOa6Flfo0b0=
|
||||
github.com/kovidgoyal/imaging v1.8.20/go.mod h1:d3phGYkTChGYkY4y++IjpHgUGhWGELDc2NEQAqxwZZg=
|
||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
@@ -624,8 +624,8 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
|
||||
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/mattn/go-sqlite3 v1.14.34 h1:3NtcvcUnFBPsuRcno8pUtupspG/GM+9nZ88zgJcp6Zk=
|
||||
github.com/mattn/go-sqlite3 v1.14.34/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||
github.com/mattn/go-sqlite3 v1.14.37 h1:3DOZp4cXis1cUIpCfXLtmlGolNLp2VEqhiB/PARNBIg=
|
||||
github.com/mattn/go-sqlite3 v1.14.37/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/mgechev/revive v1.14.0 h1:CC2Ulb3kV7JFYt+izwORoS3VT/+Plb8BvslI/l1yZsc=
|
||||
@@ -908,8 +908,8 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
|
||||
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
|
||||
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
|
||||
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
|
||||
golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4=
|
||||
golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA=
|
||||
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=
|
||||
@@ -928,8 +928,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.36.0 h1:Iknbfm1afbgtwPTmHnS2gTM/6PPZfH+z2EFuOkSbqwc=
|
||||
golang.org/x/image v0.36.0/go.mod h1:YsWD2TyyGKiIX1kZlu9QfKIsQ4nAAK9bdgdrIsE7xy4=
|
||||
golang.org/x/image v0.37.0 h1:ZiRjArKI8GwxZOoEtUfhrBtaCN+4b/7709dlT6SSnQA=
|
||||
golang.org/x/image v0.37.0/go.mod h1:/3f6vaXC+6CEanU4KJxbcUZyEePbyKbaLoDOe4ehFYY=
|
||||
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=
|
||||
@@ -955,8 +955,8 @@ golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8=
|
||||
golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w=
|
||||
golang.org/x/mod v0.34.0 h1:xIHgNUUnW6sYkcM5Jleh05DvLOtwc6RitGHbDk4akRI=
|
||||
golang.org/x/mod v0.34.0/go.mod h1:ykgH52iCZe79kzLLMhyCUzhMci+nQj+0XkbXpNYtVjY=
|
||||
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=
|
||||
@@ -999,16 +999,16 @@ golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
|
||||
golang.org/x/net v0.16.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
|
||||
golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60=
|
||||
golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM=
|
||||
golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0=
|
||||
golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw=
|
||||
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=
|
||||
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.35.0 h1:Mv2mzuHuZuY2+bkyWXIHMfhNdJAdwW3FuWeCPYN5GVQ=
|
||||
golang.org/x/oauth2 v0.35.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
|
||||
golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs=
|
||||
golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
@@ -1024,8 +1024,8 @@ golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJ
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
||||
golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
||||
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
||||
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
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/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=
|
||||
@@ -1078,8 +1078,8 @@ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
|
||||
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
|
||||
golang.org/x/sys v0.42.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=
|
||||
@@ -1096,13 +1096,13 @@ 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.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
|
||||
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
|
||||
golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8=
|
||||
golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA=
|
||||
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=
|
||||
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
|
||||
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
|
||||
golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U=
|
||||
golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
@@ -1153,8 +1153,8 @@ 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.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
|
||||
golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg=
|
||||
golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k=
|
||||
golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0=
|
||||
golang.org/x/tools v0.43.0 h1:12BdW9CeB3Z+J/I/wj34VMl8X+fEXBxVR90JeMX5E7s=
|
||||
golang.org/x/tools v0.43.0/go.mod h1:uHkMso649BX2cZK6+RpuIPXS3ho2hZo4FVwfoy1vIk0=
|
||||
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=
|
||||
@@ -1273,18 +1273,18 @@ 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.27.1 h1:9W30zRlYrefrDV2JE2O8VDtJ1yPGownxciz5rrbQZis=
|
||||
modernc.org/cc/v4 v4.27.1/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0=
|
||||
modernc.org/ccgo/v4 v4.30.2 h1:4yPaaq9dXYXZ2V8s1UgrC3KIj580l2N4ClrLwnbv2so=
|
||||
modernc.org/ccgo/v4 v4.30.2/go.mod h1:yZMnhWEdW0qw3EtCndG1+ldRrVGS+bIwyWmAWzS0XEw=
|
||||
modernc.org/fileutil v1.3.40 h1:ZGMswMNc9JOCrcrakF1HrvmergNLAmxOPjizirpfqBA=
|
||||
modernc.org/fileutil v1.3.40/go.mod h1:HxmghZSZVAz/LXcMNwZPA/DRrQZEVP9VX0V4LQGQFOc=
|
||||
modernc.org/ccgo/v4 v4.32.0 h1:hjG66bI/kqIPX1b2yT6fr/jt+QedtP2fqojG2VrFuVw=
|
||||
modernc.org/ccgo/v4 v4.32.0/go.mod h1:6F08EBCx5uQc38kMGl+0Nm0oWczoo1c7cgpzEry7Uc0=
|
||||
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=
|
||||
modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=
|
||||
modernc.org/gc/v3 v3.1.2 h1:ZtDCnhonXSZexk/AYsegNRV1lJGgaNZJuKjJSWKyEqo=
|
||||
modernc.org/gc/v3 v3.1.2/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.68.0 h1:PJ5ikFOV5pwpW+VqCK1hKJuEWsonkIJhhIXyuF/91pQ=
|
||||
modernc.org/libc v1.68.0/go.mod h1:NnKCYeoYgsEqnY3PgvNgAeaJnso968ygU8Z0DxjoEc0=
|
||||
modernc.org/libc v1.70.0 h1:U58NawXqXbgpZ/dcdS9kMshu08aiA6b7gusEusqzNkw=
|
||||
modernc.org/libc v1.70.0/go.mod h1:OVmxFGP1CI/Z4L3E0Q3Mf1PDE0BucwMkcXjjLntvHJo=
|
||||
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=
|
||||
@@ -1293,8 +1293,8 @@ modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8=
|
||||
modernc.org/opt v0.1.4/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.46.1 h1:eFJ2ShBLIEnUWlLy12raN0Z1plqmFX9Qe3rjQTKt6sU=
|
||||
modernc.org/sqlite v1.46.1/go.mod h1:CzbrU2lSB1DKUusvwGz7rqEKIq+NUd8GWuBBZDs9/nA=
|
||||
modernc.org/sqlite v1.47.0 h1:R1XyaNpoW4Et9yly+I2EeX7pBza/w+pmYee/0HJDyKk=
|
||||
modernc.org/sqlite v1.47.0/go.mod h1:hWjRO6Tj/5Ik8ieqxQybiEOUXy0NJFNp2tpvVpKlvig=
|
||||
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=
|
||||
|
||||
@@ -6,10 +6,10 @@ import (
|
||||
"embed"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"io/fs"
|
||||
"net"
|
||||
"net/http"
|
||||
"text/template"
|
||||
"time"
|
||||
|
||||
_ "net/http/pprof"
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"maps"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"slices"
|
||||
@@ -44,6 +45,8 @@ func (u *userInfoUnmarshaller) UnmarshalJSON(data []byte) error {
|
||||
if u.groupsClaim != "" {
|
||||
if v, ok := raw[u.groupsClaim]; ok {
|
||||
switch val := v.(type) {
|
||||
case map[string]interface{}:
|
||||
u.userInfo.Groups = slices.Collect(maps.Keys(val))
|
||||
case []interface{}:
|
||||
groups := make([]string, len(val))
|
||||
for i, g := range val {
|
||||
|
||||
@@ -339,7 +339,13 @@ func processOnlyOfficeCallback(w http.ResponseWriter, r *http.Request, d *reques
|
||||
// Extract clean parameters from query string
|
||||
source := r.URL.Query().Get("source")
|
||||
path := r.URL.Query().Get("path")
|
||||
user := d.user
|
||||
|
||||
if d.share != nil {
|
||||
source = d.share.GetSourceName()
|
||||
path = d.IndexPath
|
||||
user = d.shareUser
|
||||
}
|
||||
// Validate required parameters
|
||||
if (path == "" || source == "") && d.fileInfo.Hash == "" {
|
||||
logger.Errorf("OnlyOffice callback missing required parameters: source=%s, path=%s", source, path)
|
||||
@@ -354,35 +360,6 @@ func processOnlyOfficeCallback(w http.ResponseWriter, r *http.Request, d *reques
|
||||
}
|
||||
path = cleanPath
|
||||
|
||||
var sourceInfo *settings.Source
|
||||
var ok bool
|
||||
if d.fileInfo.Hash != "" {
|
||||
sourceInfo, ok = settings.Config.Server.SourceMap[d.share.Source]
|
||||
if !ok {
|
||||
logger.Error("OnlyOffice: share source not found")
|
||||
return returnOnlyOfficeError(w, r, 404, "source not found")
|
||||
}
|
||||
} else {
|
||||
sourceInfo, ok = settings.Config.Server.NameToSource[source]
|
||||
if !ok {
|
||||
logger.Error("OnlyOffice: source not found")
|
||||
return returnOnlyOfficeError(w, r, 404, "source not found")
|
||||
}
|
||||
}
|
||||
|
||||
if d.fileInfo.Hash == "" {
|
||||
// Regular user request - need to resolve scope
|
||||
userScope, scopeErr := d.user.GetScopeForSourceName(source)
|
||||
if scopeErr != nil {
|
||||
logger.Errorf("OnlyOffice callback: source %s not available for user %s: %v", source, d.user.Username, scopeErr)
|
||||
return returnOnlyOfficeError(w, r, 403, "source not available")
|
||||
}
|
||||
path = utils.JoinPathAsUnix(userScope, path)
|
||||
} else {
|
||||
source = sourceInfo.Name
|
||||
// path is index path, so we build from share path
|
||||
path = utils.JoinPathAsUnix(d.share.Path, path)
|
||||
}
|
||||
// Handle document closure - clean up document key cache
|
||||
if data.Status == onlyOfficeStatusDocumentClosedWithChanges ||
|
||||
data.Status == onlyOfficeStatusDocumentClosedWithNoChanges {
|
||||
@@ -487,7 +464,7 @@ func processOnlyOfficeCallback(w http.ResponseWriter, r *http.Request, d *reques
|
||||
}
|
||||
|
||||
// Check share permissions first if this is a share request
|
||||
if d.fileInfo.Hash != "" {
|
||||
if d.share != nil {
|
||||
if !d.share.AllowModify {
|
||||
logger.Errorf("OnlyOffice callback: edit permission not allowed for this share")
|
||||
return returnOnlyOfficeError(w, r, 403, "edit permission not allowed for this share")
|
||||
@@ -528,7 +505,7 @@ func processOnlyOfficeCallback(w http.ResponseWriter, r *http.Request, d *reques
|
||||
_, err = files.FileInfoFaster(utils.FileOptions{
|
||||
Source: source,
|
||||
Path: path,
|
||||
}, store.Access, d.user)
|
||||
}, store.Access, user)
|
||||
if err != nil {
|
||||
logger.Errorf("OnlyOffice callback: original file no longer exists at path=%s: %v",
|
||||
path, err)
|
||||
|
||||
+8
-36
@@ -4,14 +4,12 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/gtsteffaniak/filebrowser/backend/adapters/fs/files"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/common/errors"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/common/settings"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/common/utils"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/database/share"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/preview"
|
||||
"github.com/gtsteffaniak/go-logger/logger"
|
||||
|
||||
@@ -246,8 +244,8 @@ func publicPutHandler(w http.ResponseWriter, r *http.Request, d *requestContext)
|
||||
if !d.share.AllowModify {
|
||||
return http.StatusForbidden, fmt.Errorf("create is not allowed for this share")
|
||||
}
|
||||
source, err := d.share.GetSourceName()
|
||||
if err != nil {
|
||||
sourceName := d.share.GetSourceName()
|
||||
if sourceName == "" {
|
||||
return http.StatusNotFound, fmt.Errorf("source not available")
|
||||
}
|
||||
|
||||
@@ -264,7 +262,7 @@ func publicPutHandler(w http.ResponseWriter, r *http.Request, d *requestContext)
|
||||
}
|
||||
|
||||
resolvedPath := utils.JoinPathAsUnix(d.share.Path, cleanPath)
|
||||
err = files.WriteFile(source, resolvedPath, r.Body)
|
||||
err = files.WriteFile(sourceName, resolvedPath, r.Body)
|
||||
// hide the error
|
||||
if err != nil {
|
||||
logger.Errorf("public put handler: error updating resource with error %v", err)
|
||||
@@ -344,8 +342,8 @@ func publicPatchHandler(w http.ResponseWriter, r *http.Request, d *requestContex
|
||||
}
|
||||
|
||||
// Get the source from the share
|
||||
source, err := d.share.GetSourceName()
|
||||
if err != nil {
|
||||
sourceName := d.share.GetSourceName()
|
||||
if sourceName == "" {
|
||||
return http.StatusNotFound, fmt.Errorf("source not available")
|
||||
}
|
||||
|
||||
@@ -371,9 +369,9 @@ func publicPatchHandler(w http.ResponseWriter, r *http.Request, d *requestContex
|
||||
// Note: Share paths are absolute, so we don't strip user scope here
|
||||
// resourcePatchHandler will skip adding scope for shares
|
||||
for i := range req.Items {
|
||||
req.Items[i].FromSource = source
|
||||
req.Items[i].FromSource = sourceName
|
||||
req.Items[i].FromPath = utils.JoinPathAsUnix(d.share.Path, req.Items[i].FromPath)
|
||||
req.Items[i].ToSource = source
|
||||
req.Items[i].ToSource = sourceName
|
||||
req.Items[i].ToPath = utils.JoinPathAsUnix(d.share.Path, req.Items[i].ToPath)
|
||||
}
|
||||
d.Data = req
|
||||
@@ -419,7 +417,7 @@ func getShareImage(w http.ResponseWriter, r *http.Request, d *requestContext) (i
|
||||
return http.StatusNotFound, fmt.Errorf("user for share no longer exists")
|
||||
}
|
||||
|
||||
sourceName, assetPath, err := getShareImagePartsHelper(d.share, isBanner)
|
||||
sourceName, assetPath, err := d.share.GetShareImagePartsHelper(isBanner)
|
||||
if err != nil {
|
||||
return http.StatusBadRequest, fmt.Errorf("invalid asset configuration: %v", err)
|
||||
}
|
||||
@@ -464,29 +462,3 @@ func getShareImage(w http.ResponseWriter, r *http.Request, d *requestContext) (i
|
||||
|
||||
return status, err
|
||||
}
|
||||
|
||||
func getShareImagePartsHelper(share *share.Link, isBanner bool) (string, string, error) {
|
||||
|
||||
// Get the asset query string from share
|
||||
var assetQueryString string
|
||||
if isBanner {
|
||||
assetQueryString = share.Banner
|
||||
} else {
|
||||
assetQueryString = share.Favicon
|
||||
}
|
||||
|
||||
if assetQueryString == "" {
|
||||
return "", "", fmt.Errorf("asset not configured for this share")
|
||||
}
|
||||
|
||||
// Parse the query string to extract source and path
|
||||
assetParams, err := url.ParseQuery(assetQueryString)
|
||||
if err != nil {
|
||||
return "", "", fmt.Errorf("invalid asset configuration: %v", err)
|
||||
}
|
||||
|
||||
sourceName := assetParams.Get("source")
|
||||
assetPath := assetParams.Get("path")
|
||||
|
||||
return sourceName, assetPath, nil
|
||||
}
|
||||
+14
-34
@@ -14,7 +14,6 @@ import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/gtsteffaniak/filebrowser/backend/adapters/fs/files"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/adapters/fs/fileutils"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/common/utils"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/indexing"
|
||||
@@ -143,16 +142,6 @@ func addFile(source string, path string, d *requestContext, tarWriter *tar.Write
|
||||
}
|
||||
}
|
||||
|
||||
// Verify file exists
|
||||
_, err := files.FileInfoFaster(utils.FileOptions{
|
||||
Path: path,
|
||||
Source: source,
|
||||
Expand: false,
|
||||
FollowSymlinks: true,
|
||||
}, store.Access, d.user)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
realPath, _, _ := idx.GetRealPath(path)
|
||||
info, err := os.Stat(realPath)
|
||||
if err != nil {
|
||||
@@ -298,6 +287,7 @@ func rawFilesHandler(w http.ResponseWriter, r *http.Request, d *requestContext,
|
||||
var documentId string
|
||||
var logContext *OnlyOfficeLogContext
|
||||
|
||||
// modify all filepaths for user scope
|
||||
if d.share == nil {
|
||||
userscope, err = d.user.GetScopeForSourceName(source)
|
||||
if err != nil {
|
||||
@@ -319,8 +309,11 @@ func rawFilesHandler(w http.ResponseWriter, r *http.Request, d *requestContext,
|
||||
}
|
||||
return http.StatusForbidden, err
|
||||
}
|
||||
firstFilePath = utils.JoinPathAsUnix(userscope, firstFilePath)
|
||||
for i, filePath := range fileList {
|
||||
fileList[i] = utils.JoinPathAsUnix(userscope, filePath)
|
||||
}
|
||||
}
|
||||
firstFilePath = fileList[0]
|
||||
// For shares, the path is already correctly resolved by publicRawHandler
|
||||
idx := indexing.GetIndex(source)
|
||||
if idx == nil {
|
||||
@@ -505,7 +498,7 @@ func rawFilesHandler(w http.ResponseWriter, r *http.Request, d *requestContext,
|
||||
_, err = io.Copy(w, reader)
|
||||
os.Remove(archiveData) // Remove the file after streaming
|
||||
if err != nil {
|
||||
logger.Errorf("Failed to copy archive data to response: %v", err)
|
||||
logger.Debugf("client likely aborted: %v, failed to copy archive data to response: %v", r.Context().Err(), err)
|
||||
return http.StatusInternalServerError, err
|
||||
}
|
||||
|
||||
@@ -519,30 +512,17 @@ func computeArchiveSize(source string, fileList []string, d *requestContext) (in
|
||||
return 0, fmt.Errorf("source %s is not available", source)
|
||||
}
|
||||
|
||||
var userScope string
|
||||
var err error
|
||||
if d.share == nil {
|
||||
userScope, err = d.user.GetScopeForSourceName(source)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("source %s is not available for user %s", source, d.user.Username)
|
||||
}
|
||||
}
|
||||
|
||||
for _, path := range fileList {
|
||||
var fullPath string
|
||||
if d.share == nil {
|
||||
fullPath = utils.JoinPathAsUnix(userScope, path)
|
||||
// Check access control for each file in the archive
|
||||
// Silently skip if access is denied (as if the file doesn't exist)
|
||||
if store.Access != nil && !store.Access.Permitted(idx.Path, fullPath, d.user.Username) {
|
||||
if store.Access != nil && !store.Access.Permitted(idx.Path, path, d.user.Username) {
|
||||
continue // Skip this file and continue with the next one
|
||||
}
|
||||
} else {
|
||||
fullPath = path
|
||||
}
|
||||
|
||||
// For shares, the path is already correctly resolved by publicRawHandler
|
||||
realPath, isDir, err := idx.GetRealPath(fullPath)
|
||||
realPath, isDir, err := idx.GetRealPath(path)
|
||||
if err != nil {
|
||||
return http.StatusInternalServerError, err
|
||||
}
|
||||
@@ -568,11 +548,11 @@ func createZip(d *requestContext, source string, tmpDirPath string, filenames ..
|
||||
|
||||
zipWriter := zip.NewWriter(file)
|
||||
|
||||
for _, fname := range filenames {
|
||||
err := addFile(source, fname, d, nil, zipWriter, false)
|
||||
for _, filepath := range filenames {
|
||||
err := addFile(source, filepath, d, nil, zipWriter, false)
|
||||
if err != nil {
|
||||
// Access control failures return nil, so any error here is a real error
|
||||
logger.Errorf("Failed to add %s to ZIP: %v", fname, err)
|
||||
logger.Errorf("Failed to add %s to ZIP: %v", filepath, err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -595,10 +575,10 @@ func createTarGz(d *requestContext, source string, tmpDirPath string, filenames
|
||||
gzWriter := gzip.NewWriter(file)
|
||||
tarWriter := tar.NewWriter(gzWriter)
|
||||
|
||||
for _, fname := range filenames {
|
||||
err := addFile(source, fname, d, tarWriter, nil, false)
|
||||
for _, filepath := range filenames {
|
||||
err := addFile(source, filepath, d, tarWriter, nil, false)
|
||||
if err != nil {
|
||||
logger.Errorf("Failed to add %s to TAR.GZ: %v", fname, err)
|
||||
logger.Errorf("Failed to add %s to TAR.GZ: %v", filepath, err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
@@ -252,12 +252,12 @@ func resourceBulkDeleteHandler(w http.ResponseWriter, r *http.Request, d *reques
|
||||
}
|
||||
|
||||
if d.share != nil {
|
||||
source, err := d.share.GetSourceName()
|
||||
if err != nil {
|
||||
sourceName := d.share.GetSourceName()
|
||||
if sourceName == "" {
|
||||
return http.StatusNotFound, fmt.Errorf("source not available")
|
||||
}
|
||||
// get user scope path from share
|
||||
userScope, err := d.shareUser.GetScopeForSourceName(source)
|
||||
userScope, err := d.shareUser.GetScopeForSourceName(sourceName)
|
||||
if err != nil {
|
||||
response.Failed = append(response.Failed, BulkDeleteItem{
|
||||
Source: item.Source,
|
||||
@@ -272,7 +272,7 @@ func resourceBulkDeleteHandler(w http.ResponseWriter, r *http.Request, d *reques
|
||||
fileInfo, err := files.FileInfoFaster(utils.FileOptions{
|
||||
FollowSymlinks: true,
|
||||
Path: indexPath,
|
||||
Source: source,
|
||||
Source: sourceName,
|
||||
ShowHidden: true,
|
||||
}, store.Access, filePermUser)
|
||||
if err != nil {
|
||||
@@ -280,7 +280,7 @@ func resourceBulkDeleteHandler(w http.ResponseWriter, r *http.Request, d *reques
|
||||
}
|
||||
|
||||
// Delete the file/directory
|
||||
err = files.DeleteFiles(source, fileInfo.RealPath, fileInfo.Type == "directory")
|
||||
err = files.DeleteFiles(sourceName, fileInfo.RealPath, fileInfo.Type == "directory")
|
||||
if err != nil {
|
||||
logger.Errorf("resource bulk delete handler: error deleting file/directory: %v", err)
|
||||
response.Failed = append(response.Failed, BulkDeleteItem{
|
||||
|
||||
+44
-17
@@ -33,13 +33,14 @@ type ShareResponse struct {
|
||||
}
|
||||
|
||||
// convertToFrontendShareResponse converts shares to response format with usernames
|
||||
func convertToFrontendShareResponse(r *http.Request, shares []*share.Link) ([]*ShareResponse, error) {
|
||||
func convertToFrontendShareResponse(r *http.Request, shares []*share.Link, user *users.User) ([]*ShareResponse, error) {
|
||||
responses := make([]*ShareResponse, 0, len(shares))
|
||||
for _, s := range shares {
|
||||
user, err := store.Users.Get(s.UserID)
|
||||
// Look for the username of the user who created the share
|
||||
creator, err := store.Users.Get(s.UserID)
|
||||
username := ""
|
||||
if err == nil {
|
||||
username = user.Username
|
||||
username = creator.Username
|
||||
}
|
||||
|
||||
// Get source info to convert path to name for frontend
|
||||
@@ -65,6 +66,9 @@ func convertToFrontendShareResponse(r *http.Request, shares []*share.Link) ([]*S
|
||||
s.CommonShare.HasPassword = s.HasPassword()
|
||||
s.DownloadURL = getShareURL(r, s.Hash, true, s.Token)
|
||||
s.ShareURL = getShareURL(r, s.Hash, false, s.Token)
|
||||
if s.UserCanEdit(user) {
|
||||
s.CommonShare.SourceURL = s.SourceURL(user)
|
||||
}
|
||||
// Create response with source name (overrides the embedded Link's source field)
|
||||
responses = append(responses, &ShareResponse{
|
||||
Link: s,
|
||||
@@ -97,7 +101,7 @@ func shareListHandler(w http.ResponseWriter, r *http.Request, d *requestContext)
|
||||
return http.StatusInternalServerError, err
|
||||
}
|
||||
shares = utils.NonNilSlice(shares)
|
||||
sharesWithUsernames, err := convertToFrontendShareResponse(r, shares)
|
||||
sharesWithUsernames, err := convertToFrontendShareResponse(r, shares, d.user)
|
||||
if err != nil {
|
||||
return http.StatusInternalServerError, err
|
||||
}
|
||||
@@ -137,7 +141,7 @@ func shareGetHandler(w http.ResponseWriter, r *http.Request, d *requestContext)
|
||||
if err != nil {
|
||||
return http.StatusInternalServerError, fmt.Errorf("error getting share info from server")
|
||||
}
|
||||
sharesWithUsernames, err := convertToFrontendShareResponse(r, s)
|
||||
sharesWithUsernames, err := convertToFrontendShareResponse(r, s, d.user)
|
||||
if err != nil {
|
||||
return http.StatusInternalServerError, err
|
||||
}
|
||||
@@ -162,7 +166,16 @@ func shareDeleteHandler(w http.ResponseWriter, r *http.Request, d *requestContex
|
||||
return http.StatusBadRequest, nil
|
||||
}
|
||||
|
||||
err := store.Share.Delete(hash)
|
||||
// only allow users to delete their own shares
|
||||
thisShare, err := store.Share.GetByHash(hash)
|
||||
if err != nil {
|
||||
return http.StatusBadRequest, fmt.Errorf("share not found")
|
||||
}
|
||||
if thisShare.UserID != d.user.ID && !d.user.Permissions.Admin {
|
||||
return http.StatusForbidden, fmt.Errorf("you are not allowed to delete this share")
|
||||
}
|
||||
|
||||
err = store.Share.Delete(hash)
|
||||
if err != nil {
|
||||
return errToStatus(err), err
|
||||
}
|
||||
@@ -196,8 +209,16 @@ func sharePatchHandler(w http.ResponseWriter, r *http.Request, d *requestContext
|
||||
return http.StatusBadRequest, fmt.Errorf("hash and path are required")
|
||||
}
|
||||
|
||||
// only allow users to update their own shares
|
||||
thisShare, err := store.Share.GetByHash(body.Hash)
|
||||
if err != nil {
|
||||
return http.StatusBadRequest, fmt.Errorf("share not found")
|
||||
}
|
||||
if thisShare.UserID != d.user.ID && !d.user.Permissions.Admin {
|
||||
return http.StatusForbidden, fmt.Errorf("you are not allowed to update this share")
|
||||
}
|
||||
// Update the share path
|
||||
err := store.Share.UpdateSharePath(body.Hash, body.Path)
|
||||
err = store.Share.UpdateSharePath(body.Hash, body.Path)
|
||||
if err != nil {
|
||||
return http.StatusInternalServerError, err
|
||||
}
|
||||
@@ -209,7 +230,7 @@ func sharePatchHandler(w http.ResponseWriter, r *http.Request, d *requestContext
|
||||
}
|
||||
|
||||
// Convert to response format
|
||||
sharesWithUsernames, err := convertToFrontendShareResponse(r, []*share.Link{updatedShare})
|
||||
sharesWithUsernames, err := convertToFrontendShareResponse(r, []*share.Link{updatedShare}, d.user)
|
||||
if err != nil {
|
||||
return http.StatusInternalServerError, err
|
||||
}
|
||||
@@ -389,7 +410,7 @@ func sharePostHandler(w http.ResponseWriter, r *http.Request, d *requestContext)
|
||||
if err = store.Share.Save(s); err != nil {
|
||||
return http.StatusInternalServerError, err
|
||||
}
|
||||
sharesWithUsernames, err := convertToFrontendShareResponse(r, []*share.Link{s})
|
||||
sharesWithUsernames, err := convertToFrontendShareResponse(r, []*share.Link{s}, d.user)
|
||||
if err != nil {
|
||||
return http.StatusInternalServerError, err
|
||||
}
|
||||
@@ -611,16 +632,11 @@ func shareInfoHandler(w http.ResponseWriter, r *http.Request, d *requestContext)
|
||||
}
|
||||
commonShare := shareLink.CommonShare
|
||||
commonShare.ShareURL = getShareURL(r, hash, false, "")
|
||||
_, _, err = getShareImagePartsHelper(shareLink, true)
|
||||
if err == nil {
|
||||
commonShare.BannerUrl = fmt.Sprintf("%spublic/api/share/image?banner=true&hash=%s", config.Server.BaseURL, hash)
|
||||
}
|
||||
_, _, err = getShareImagePartsHelper(shareLink, false)
|
||||
if err == nil {
|
||||
commonShare.FaviconUrl = fmt.Sprintf("%spublic/api/share/image?favicon=true&hash=%s", config.Server.BaseURL, hash)
|
||||
}
|
||||
commonShare.BannerUrl = shareLink.BannerURL()
|
||||
commonShare.FaviconUrl = shareLink.FaviconURL()
|
||||
commonShare.Source = ""
|
||||
commonShare.Path = ""
|
||||
commonShare.DownloadURL = ""
|
||||
commonShare.SidebarLinks = []users.SidebarLink{}
|
||||
for _, link := range shareLink.SidebarLinks {
|
||||
if link.Category == "download" && shareLink.ShareType == "upload" {
|
||||
@@ -629,6 +645,17 @@ func shareInfoHandler(w http.ResponseWriter, r *http.Request, d *requestContext)
|
||||
commonShare.SidebarLinks = append(commonShare.SidebarLinks, link)
|
||||
}
|
||||
}
|
||||
commonShare.SourceURL = shareLink.SourceURL(d.user)
|
||||
commonShare.CanEdit = shareLink.UserCanEdit(d.user)
|
||||
if commonShare.SourceURL != "" {
|
||||
link := users.SidebarLink{
|
||||
Name: "sourceLocation",
|
||||
Category: "custom",
|
||||
Target: commonShare.SourceURL,
|
||||
}
|
||||
commonShare.SidebarLinks = append(commonShare.SidebarLinks, link)
|
||||
}
|
||||
|
||||
return renderJSON(w, r, commonShare)
|
||||
}
|
||||
|
||||
|
||||
+21
-24
@@ -3,12 +3,12 @@ package http
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"io/fs"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
"github.com/gtsteffaniak/filebrowser/backend/common/settings"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/common/utils"
|
||||
@@ -25,10 +25,18 @@ type TemplateRenderer struct {
|
||||
|
||||
// Render renders a template document with headers and data
|
||||
func (t *TemplateRenderer) Render(w http.ResponseWriter, name string, data interface{}) error {
|
||||
// If in dev mode, reload templates on every render.
|
||||
templates := t.templates
|
||||
// If in dev mode, reload templates on every render by creating a fresh instance
|
||||
if t.devMode {
|
||||
var err error
|
||||
t.templates, err = t.templates.ParseFS(assetFs, "public/index.html")
|
||||
templates = template.New("").Funcs(template.FuncMap{
|
||||
"marshal": func(v interface{}) (string, error) {
|
||||
var a []byte
|
||||
a, err = json.Marshal(v)
|
||||
return string(a), err
|
||||
},
|
||||
})
|
||||
templates, err = templates.ParseFS(assetFs, "public/index.html")
|
||||
if err != nil {
|
||||
return fmt.Errorf("error reloading template: %w", err)
|
||||
}
|
||||
@@ -39,7 +47,7 @@ func (t *TemplateRenderer) Render(w http.ResponseWriter, name string, data inter
|
||||
w.Header().Set("X-Accel-Expires", "0")
|
||||
w.Header().Set("Transfer-Encoding", "identity")
|
||||
// Execute the template with the provided data
|
||||
return t.templates.ExecuteTemplate(w, name, data)
|
||||
return templates.ExecuteTemplate(w, name, data)
|
||||
}
|
||||
|
||||
func handleWithStaticData(w http.ResponseWriter, r *http.Request, d *requestContext, file, contentType string) (int, error) {
|
||||
@@ -111,21 +119,10 @@ func handleWithStaticData(w http.ResponseWriter, r *http.Request, d *requestCont
|
||||
if d.share.DisableSidebar {
|
||||
disableSidebar = true
|
||||
}
|
||||
if strings.HasPrefix(strings.ToLower(d.share.Banner), "http") {
|
||||
banner = d.share.Banner
|
||||
} else {
|
||||
_, _, err := getShareImagePartsHelper(d.share, true)
|
||||
if err == nil {
|
||||
banner = fmt.Sprintf("%s%spublic/api/share/image?banner=true&hash=%s", config.Server.ExternalUrl, config.Server.BaseURL, d.share.Hash)
|
||||
}
|
||||
}
|
||||
if strings.HasPrefix(strings.ToLower(d.share.Favicon), "http") {
|
||||
favicon = d.share.Favicon
|
||||
} else {
|
||||
_, _, err := getShareImagePartsHelper(d.share, false)
|
||||
if err == nil {
|
||||
favicon = fmt.Sprintf("%s%spublic/api/share/image?favicon=true&hash=%s", config.Server.ExternalUrl, config.Server.BaseURL, d.share.Hash)
|
||||
}
|
||||
banner = d.share.BannerURL()
|
||||
shareFavicon := d.share.FaviconURL()
|
||||
if shareFavicon != "" {
|
||||
favicon = shareFavicon
|
||||
}
|
||||
shareHash = d.share.Hash
|
||||
}
|
||||
@@ -180,8 +177,8 @@ func handleWithStaticData(w http.ResponseWriter, r *http.Request, d *requestCont
|
||||
|
||||
data["htmlVars"] = map[string]interface{}{
|
||||
"title": title,
|
||||
"customCSS": config.Frontend.Styling.CustomCSSRaw,
|
||||
"userSelectedTheme": userSelectedTheme,
|
||||
"customCSS": template.CSS(config.Frontend.Styling.CustomCSSRaw),
|
||||
"userSelectedTheme": template.CSS(userSelectedTheme),
|
||||
"lightBackground": config.Frontend.Styling.LightBackground,
|
||||
"darkBackground": config.Frontend.Styling.DarkBackground,
|
||||
"staticURL": staticURL,
|
||||
@@ -192,7 +189,7 @@ func handleWithStaticData(w http.ResponseWriter, r *http.Request, d *requestCont
|
||||
"winIcon": staticURL + "/mstile-256x256.png",
|
||||
"appIcon": staticURL + "/apple-touch-icon.png",
|
||||
"description": description,
|
||||
"loadingSpinnersCSS": loadingSpinnersCSS,
|
||||
"loadingSpinnersCSS": template.CSS(loadingSpinnersCSS),
|
||||
"banner": banner,
|
||||
"image": ogImage,
|
||||
"url": fullURL,
|
||||
@@ -238,8 +235,8 @@ func handleWithStaticData(w http.ResponseWriter, r *http.Request, d *requestCont
|
||||
return http.StatusInternalServerError, fmt.Errorf("error marshaling globalVars: %w", err)
|
||||
}
|
||||
|
||||
// Replace with JSON strings for direct template usage
|
||||
data["globalVars"] = string(globalVarsJSON)
|
||||
// Mark as JS for html/template to avoid escaping
|
||||
data["globalVars"] = template.JS(globalVarsJSON)
|
||||
|
||||
// Render the template with global variables
|
||||
if err := templateRenderer.Render(w, file, data); err != nil {
|
||||
|
||||
@@ -196,7 +196,7 @@ func TestProcessDirectoryItem(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
itemInfo, size, shouldCount := idx.processDirectoryItem(
|
||||
tt.file, tt.combinedPath,
|
||||
@@ -212,6 +212,7 @@ func TestProcessDirectoryItem(t *testing.T) {
|
||||
|
||||
if itemInfo == nil {
|
||||
t.Fatalf("expected non-nil itemInfo")
|
||||
return
|
||||
}
|
||||
|
||||
if size != tt.expectSize {
|
||||
|
||||
+23
-5
@@ -11,6 +11,7 @@ import (
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gen2brain/go-fitz"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/indexing/iteminfo"
|
||||
@@ -32,10 +33,14 @@ func (s *Service) GenerateImageFromDoc(ctx context.Context, file iteminfo.Extend
|
||||
s.docGenMutex.Lock()
|
||||
defer s.docGenMutex.Unlock()
|
||||
|
||||
// 2. Lock the current goroutine to a single OS thread for CGo calls
|
||||
// Lock the current goroutine to a single OS thread for CGo calls
|
||||
runtime.LockOSThread()
|
||||
defer runtime.UnlockOSThread()
|
||||
|
||||
// Create a 2-second timeout for document generation after acquiring locks
|
||||
timeoutCtx, cancel := context.WithTimeout(ctx, 2*time.Second)
|
||||
defer cancel()
|
||||
|
||||
docPath := file.RealPath
|
||||
// copy file to a temporary location if needed
|
||||
if strings.HasPrefix(file.Type, "text") && !strings.HasSuffix(file.RealPath, ".txt") {
|
||||
@@ -75,18 +80,26 @@ func (s *Service) GenerateImageFromDoc(ctx context.Context, file iteminfo.Extend
|
||||
docPath = tempFilePath // Update docPath to point to the new temporary text snippet file
|
||||
}
|
||||
}
|
||||
|
||||
// Check timeout before opening document
|
||||
if timeoutCtx.Err() != nil {
|
||||
return nil, fmt.Errorf("document preview generation timed out after 2 seconds for '%s'", file.Name)
|
||||
}
|
||||
|
||||
doc, err := fitz.New(docPath) // This calls the CGo version
|
||||
if err != nil {
|
||||
// The error message you received: "failed to open PDF: fitz: cannot open memory"
|
||||
return nil, fmt.Errorf("failed to open PDF from memory for file '%s': %w", docPath, err)
|
||||
}
|
||||
defer doc.Close()
|
||||
|
||||
// Check timeout after opening document
|
||||
if timeoutCtx.Err() != nil {
|
||||
return nil, fmt.Errorf("document preview generation timed out after 2 seconds for '%s'", file.Name)
|
||||
}
|
||||
|
||||
// Get the image from the doc page
|
||||
// Ensure pageNumber is valid (e.g., >= 0 for 0-indexed or >= 1 for 1-indexed, check go-fitz docs)
|
||||
// And pageNumber < doc.NumPage()
|
||||
numPages := doc.NumPage()
|
||||
if pageNumber < 0 || pageNumber >= numPages { // Assuming 0-indexed for Image()
|
||||
if pageNumber < 0 || pageNumber >= numPages {
|
||||
return nil, fmt.Errorf("invalid page number %d for PDF with %d pages ('%s')", pageNumber, numPages, docPath)
|
||||
}
|
||||
|
||||
@@ -95,6 +108,11 @@ func (s *Service) GenerateImageFromDoc(ctx context.Context, file iteminfo.Extend
|
||||
return nil, fmt.Errorf("failed to get image from page %d of '%s': %w", pageNumber, docPath, err)
|
||||
}
|
||||
|
||||
// Check timeout after rendering image
|
||||
if timeoutCtx.Err() != nil {
|
||||
return nil, fmt.Errorf("document preview generation timed out after 2 seconds for '%s'", file.Name)
|
||||
}
|
||||
|
||||
// Create a new buffer to hold the image bytes
|
||||
var buf bytes.Buffer
|
||||
|
||||
|
||||
@@ -3614,6 +3614,9 @@ const docTemplate = `{
|
||||
"bannerUrl": {
|
||||
"type": "string"
|
||||
},
|
||||
"canEdit": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"description": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -3725,6 +3728,9 @@ const docTemplate = `{
|
||||
"description": "Override embedded field to show source name",
|
||||
"type": "string"
|
||||
},
|
||||
"sourceURL": {
|
||||
"type": "string"
|
||||
},
|
||||
"themeColor": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -5062,6 +5068,9 @@ const docTemplate = `{
|
||||
"bannerUrl": {
|
||||
"type": "string"
|
||||
},
|
||||
"canEdit": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"description": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -5158,6 +5167,9 @@ const docTemplate = `{
|
||||
"description": "backend source is path to maintain between name changes",
|
||||
"type": "string"
|
||||
},
|
||||
"sourceURL": {
|
||||
"type": "string"
|
||||
},
|
||||
"themeColor": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -5200,6 +5212,9 @@ const docTemplate = `{
|
||||
"bannerUrl": {
|
||||
"type": "string"
|
||||
},
|
||||
"canEdit": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"description": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -5305,6 +5320,9 @@ const docTemplate = `{
|
||||
"description": "backend source is path to maintain between name changes",
|
||||
"type": "string"
|
||||
},
|
||||
"sourceURL": {
|
||||
"type": "string"
|
||||
},
|
||||
"themeColor": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -5350,6 +5368,9 @@ const docTemplate = `{
|
||||
"bannerUrl": {
|
||||
"type": "string"
|
||||
},
|
||||
"canEdit": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"description": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -5458,6 +5479,9 @@ const docTemplate = `{
|
||||
"description": "backend source is path to maintain between name changes",
|
||||
"type": "string"
|
||||
},
|
||||
"sourceURL": {
|
||||
"type": "string"
|
||||
},
|
||||
"themeColor": {
|
||||
"type": "string"
|
||||
},
|
||||
|
||||
@@ -3603,6 +3603,9 @@
|
||||
"bannerUrl": {
|
||||
"type": "string"
|
||||
},
|
||||
"canEdit": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"description": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -3714,6 +3717,9 @@
|
||||
"description": "Override embedded field to show source name",
|
||||
"type": "string"
|
||||
},
|
||||
"sourceURL": {
|
||||
"type": "string"
|
||||
},
|
||||
"themeColor": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -5051,6 +5057,9 @@
|
||||
"bannerUrl": {
|
||||
"type": "string"
|
||||
},
|
||||
"canEdit": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"description": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -5147,6 +5156,9 @@
|
||||
"description": "backend source is path to maintain between name changes",
|
||||
"type": "string"
|
||||
},
|
||||
"sourceURL": {
|
||||
"type": "string"
|
||||
},
|
||||
"themeColor": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -5189,6 +5201,9 @@
|
||||
"bannerUrl": {
|
||||
"type": "string"
|
||||
},
|
||||
"canEdit": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"description": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -5294,6 +5309,9 @@
|
||||
"description": "backend source is path to maintain between name changes",
|
||||
"type": "string"
|
||||
},
|
||||
"sourceURL": {
|
||||
"type": "string"
|
||||
},
|
||||
"themeColor": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -5339,6 +5357,9 @@
|
||||
"bannerUrl": {
|
||||
"type": "string"
|
||||
},
|
||||
"canEdit": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"description": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -5447,6 +5468,9 @@
|
||||
"description": "backend source is path to maintain between name changes",
|
||||
"type": "string"
|
||||
},
|
||||
"sourceURL": {
|
||||
"type": "string"
|
||||
},
|
||||
"themeColor": {
|
||||
"type": "string"
|
||||
},
|
||||
|
||||
@@ -122,6 +122,8 @@ definitions:
|
||||
type: string
|
||||
bannerUrl:
|
||||
type: string
|
||||
canEdit:
|
||||
type: boolean
|
||||
description:
|
||||
type: string
|
||||
disableAnonymous:
|
||||
@@ -199,6 +201,8 @@ definitions:
|
||||
source:
|
||||
description: Override embedded field to show source name
|
||||
type: string
|
||||
sourceURL:
|
||||
type: string
|
||||
themeColor:
|
||||
type: string
|
||||
title:
|
||||
@@ -1199,6 +1203,8 @@ definitions:
|
||||
type: string
|
||||
bannerUrl:
|
||||
type: string
|
||||
canEdit:
|
||||
type: boolean
|
||||
description:
|
||||
type: string
|
||||
disableAnonymous:
|
||||
@@ -1266,6 +1272,8 @@ definitions:
|
||||
source:
|
||||
description: backend source is path to maintain between name changes
|
||||
type: string
|
||||
sourceURL:
|
||||
type: string
|
||||
themeColor:
|
||||
type: string
|
||||
title:
|
||||
@@ -1296,6 +1304,8 @@ definitions:
|
||||
type: string
|
||||
bannerUrl:
|
||||
type: string
|
||||
canEdit:
|
||||
type: boolean
|
||||
description:
|
||||
type: string
|
||||
disableAnonymous:
|
||||
@@ -1369,6 +1379,8 @@ definitions:
|
||||
source:
|
||||
description: backend source is path to maintain between name changes
|
||||
type: string
|
||||
sourceURL:
|
||||
type: string
|
||||
themeColor:
|
||||
type: string
|
||||
title:
|
||||
@@ -1401,6 +1413,8 @@ definitions:
|
||||
type: string
|
||||
bannerUrl:
|
||||
type: string
|
||||
canEdit:
|
||||
type: boolean
|
||||
description:
|
||||
type: string
|
||||
disableAnonymous:
|
||||
@@ -1476,6 +1490,8 @@ definitions:
|
||||
source:
|
||||
description: backend source is path to maintain between name changes
|
||||
type: string
|
||||
sourceURL:
|
||||
type: string
|
||||
themeColor:
|
||||
type: string
|
||||
title:
|
||||
|
||||
Generated
+254
-1206
File diff suppressed because it is too large.
Load diff
@@ -27,7 +27,6 @@
|
||||
"css-vars-ponyfill": "^2.4.3",
|
||||
"dompurify": "^3.2.4",
|
||||
"epubjs": "^0.3.93",
|
||||
"file-loader": "^6.2.0",
|
||||
"glob": "^9.3.5",
|
||||
"highlight.js": "^11.11.1",
|
||||
"mammoth": "^1.9.1",
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
|
||||
<title>{{ .htmlVars.title }}</title>
|
||||
<title>"{{ .htmlVars.title }}"</title>
|
||||
|
||||
<link rel="icon" type="image/png" sizes="256x256" href="{{ .htmlVars.favicon }}">
|
||||
|
||||
|
||||
@@ -34,8 +34,13 @@
|
||||
</div>
|
||||
|
||||
<div v-else-if="!isPickingFile">
|
||||
<div aria-label="share-path" class="searchContext button"> {{ $t('general.path', { suffix: ':' }) }} {{
|
||||
<div v-if="isFiles" aria-label="share-path" class="searchContext button"> {{ $t('general.path', { suffix: ':' }) }} {{
|
||||
displayPath }}</div>
|
||||
<div v-else>
|
||||
<a :href="link.sourceURL" class="button button--block">
|
||||
{{ $t('buttons.goToSource') }}
|
||||
</a>
|
||||
</div>
|
||||
<p> {{ $t('share.notice') }} </p>
|
||||
|
||||
<div v-if="listing">
|
||||
@@ -474,6 +479,9 @@ export default {
|
||||
isListing() {
|
||||
return getters.isListing(); // Access getter directly from the store
|
||||
},
|
||||
isFiles() {
|
||||
return getters.isFiles(); // Access getter directly from the store
|
||||
},
|
||||
url() {
|
||||
if (state.isSearchActive) {
|
||||
const file = /** @type {FilebrowserFile} */ (this.selected[0]);
|
||||
|
||||
@@ -20,7 +20,10 @@
|
||||
class="link-drag-handle">
|
||||
<i class="material-icons">drag_indicator</i>
|
||||
</div>
|
||||
<div class="link-icon">
|
||||
<div v-if="link.category === 'divider'" class="link-icon">
|
||||
<i class="material-icons">horizontal_rule</i>
|
||||
</div>
|
||||
<div v-else class="link-icon">
|
||||
<i :class="getIconClass(link.icon)">{{ link.icon }}</i>
|
||||
</div>
|
||||
<div class="link-details">
|
||||
@@ -66,6 +69,7 @@
|
||||
<option value="share">{{ $t('general.share') }}</option>
|
||||
<option v-if="context === 'user'" value="tool">{{ $t('general.tool') }}</option>
|
||||
<option value="custom">{{ $t('sidebar.customLink') }}</option>
|
||||
<option value="divider">{{ $t('general.divider') }}</option>
|
||||
<option v-if="context === 'share'" value="shareInfo">{{ $t('share.shareInfo') }}</option>
|
||||
<option v-if="context === 'share'" value="download">{{ $t('general.download') }}</option>
|
||||
</select>
|
||||
@@ -148,6 +152,13 @@
|
||||
<p>{{ $t('share.downloadDescription') }}</p>
|
||||
</div>
|
||||
|
||||
<!-- Divider - special visual separator -->
|
||||
<div v-if="newLink.category === 'divider'" class="form-group">
|
||||
<p>{{ $t('sidebar.linkName') }}</p>
|
||||
<input aria-label="Link Name" v-model="newLink.name" type="text" class="input"
|
||||
:placeholder="$t('sidebar.dividerNamePlaceholder')" />
|
||||
</div>
|
||||
|
||||
<!-- Custom Link Input -->
|
||||
<div v-if="newLink.category === 'custom'" class="form-group">
|
||||
<p>{{ $t('sidebar.linkName') }}</p>
|
||||
@@ -159,8 +170,8 @@
|
||||
:placeholder="$t('sidebar.linkUrlPlaceholder')" />
|
||||
</div>
|
||||
|
||||
<!-- Icon Selection - Available for ALL link types -->
|
||||
<div v-if="newLink.category" class="form-group">
|
||||
<!-- Icon Selection - Available for ALL link types except divider -->
|
||||
<div v-if="newLink.category && newLink.category !== 'divider'" class="form-group">
|
||||
<p>{{ $t('sidebar.linkIcon') }}</p>
|
||||
<div class="icon-input-group">
|
||||
<input v-model="newLink.icon" type="text" class="input icon-input"
|
||||
@@ -285,8 +296,10 @@ export default {
|
||||
isNewLinkValid() {
|
||||
if (!this.newLink.category) return false;
|
||||
|
||||
// Special link types for shares don't need additional validation
|
||||
if (this.newLink.category === "shareInfo" || this.newLink.category === "download") {
|
||||
// Special link types that don't need additional validation
|
||||
if (this.newLink.category === "shareInfo" ||
|
||||
this.newLink.category === "download" ||
|
||||
this.newLink.category === "divider") {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -392,10 +405,16 @@ export default {
|
||||
share: this.$t('general.share'),
|
||||
shareInfo: this.$t('share.shareInfo'),
|
||||
download: this.$t('general.download'),
|
||||
divider: this.$t('general.divider'),
|
||||
};
|
||||
return labels[category] || category;
|
||||
},
|
||||
getLinkDisplayName(link) {
|
||||
// For dividers, show the name or a default
|
||||
if (link.category === 'divider') {
|
||||
return link.name || this.$t('general.divider');
|
||||
}
|
||||
|
||||
// Check if the name looks like a translation key that needs translating
|
||||
if (link.category === 'shareInfo' && link.name === 'share.shareInfo') {
|
||||
return this.$t('share.shareInfo');
|
||||
@@ -546,6 +565,11 @@ export default {
|
||||
} else if (this.newLink.category === "download") {
|
||||
// Download is a special action link
|
||||
linkData.target = "#";
|
||||
} else if (this.newLink.category === "divider") {
|
||||
// Divider is a visual separator with no action
|
||||
linkData.target = "#";
|
||||
linkData.name = this.newLink.name || ""; // Keep the custom name if provided
|
||||
linkData.icon = ""; // Dividers don't need an icon
|
||||
} else if (this.newLink.category === "custom") {
|
||||
linkData.target = this.processCustomUrl(this.newLink.target);
|
||||
} else if (this.newLink.category === "source") {
|
||||
|
||||
@@ -7,17 +7,33 @@
|
||||
</div>
|
||||
|
||||
<!-- Links section header -->
|
||||
<div class="sidebar-links-header" :class="{ 'no-edit-options': isShare, 'with-top-spacing': isShare && !disableShareCard }">
|
||||
<i v-if="!isShare" @click="goHome()" class="material-icons action">home</i>
|
||||
<div class="sidebar-links-header"
|
||||
:class="{ 'with-top-spacing': isShare && !disableShareCard }">
|
||||
<i :class="{ 'disabled': !isLoggedIn }" @click="goHome()" class="material-icons action">home</i>
|
||||
<span>{{ $t("general.links") }}</span>
|
||||
<i v-if="!isShare" @mouseenter="showTooltip($event, $t('sidebar.customizeLinks'))" @mouseleave="hideTooltip"
|
||||
<i v-if="isShare" aria-label="Edit Share" @mouseenter="showTooltip($event, editShareText())" @mouseleave="hideTooltip"
|
||||
:class="{ 'disabled': !canEdit }"
|
||||
@click="showEditShareHover" class="material-icons action">edit</i>
|
||||
<i v-else @mouseenter="showTooltip($event, $t('sidebar.customizeLinks'))" @mouseleave="hideTooltip"
|
||||
@click="openSidebarLinksPrompt" class="material-icons action">edit</i>
|
||||
</div>
|
||||
|
||||
<transition-group name="expand" tag="div" class="inner-card">
|
||||
<template v-for="(link, index) in sidebarLinksToDisplay" :key="`link-${index}-${link.category}`">
|
||||
<!-- Divider: renders as hr line or text label -->
|
||||
<div v-if="link.category === 'divider'" class="sidebar-divider-container">
|
||||
<hr v-if="!link.name || link.name.toLowerCase() === 'divider'" class="sidebar-divider" />
|
||||
<span v-else class="sidebar-divider-text">{{ link.name }}</span>
|
||||
</div>
|
||||
<a v-else-if="link.category == 'custom' && link.name == 'sourceLocation'" :href="link.target"
|
||||
:aria-label="link.name" class="action button sidebar-link-button" @click.prevent="handleLinkClick(link)">
|
||||
<div class="link-container">
|
||||
<i class="material-icons link-icon">open_in_new</i>
|
||||
<span>{{ $t('buttons.goToSource') }}</span>
|
||||
</div>
|
||||
</a>
|
||||
<!-- Source-type links: styled exactly like original sources -->
|
||||
<a v-if="link.category === 'source'" :href="getLinkHref(link)"
|
||||
<a v-else-if="link.category === 'source'" :href="getLinkHref(link)"
|
||||
class="action button source-button sidebar-link-button" :class="{
|
||||
active: isLinkActive(link),
|
||||
disabled: !isLinkAccessible(link)
|
||||
@@ -46,12 +62,11 @@
|
||||
</i>
|
||||
</div>
|
||||
<div v-if="hasUsageInfo(link)" class="usage-info">
|
||||
<ProgressBar
|
||||
<ProgressBar
|
||||
:key="`progress-${link.sourceName}-${sourceInfo[link.sourceName]?.used || 0}-${sourceInfo[link.sourceName]?.total || 0}`"
|
||||
:val="getProgressBarValue(sourceInfo[link.sourceName] || {})"
|
||||
:max="(sourceInfo[link.sourceName] || {}).total || 1"
|
||||
:status="getProgressBarStatus(sourceInfo[link.sourceName] || {})"
|
||||
unit="bytes">
|
||||
:val="getProgressBarValue(sourceInfo[link.sourceName] || {})"
|
||||
:max="(sourceInfo[link.sourceName] || {}).total || 1"
|
||||
:status="getProgressBarStatus(sourceInfo[link.sourceName] || {})" unit="bytes">
|
||||
</ProgressBar>
|
||||
</div>
|
||||
</a>
|
||||
@@ -59,22 +74,12 @@
|
||||
<!-- Non-source links: tool and custom links with simple icon style -->
|
||||
<a v-else :aria-label="link.name" :href="getLinkHref(link)" class="action button sidebar-link-button"
|
||||
:class="{ active: isLinkActive(link) }" @click.prevent="handleLinkClick(link)">
|
||||
<div class="link-container">
|
||||
<div class="link-container">
|
||||
<i :class="getIconClass(link.icon) + ' link-icon'">{{ link.icon }}</i>
|
||||
<span>{{ link.name }}</span>
|
||||
</div>
|
||||
</a>
|
||||
</template>
|
||||
|
||||
<!-- Edit Share link - only shown when viewing a share and user has share permissions -->
|
||||
<a v-if="isShare && canEditShare" :aria-label="$t('general.edit', { suffix: ' ' + $t('general.share') })" href="#"
|
||||
class="action button sidebar-link-button"
|
||||
@click.prevent="showEditShareHover">
|
||||
<div class="link-container">
|
||||
<i class="material-icons link-icon">edit</i>
|
||||
<span>{{ $t("general.edit", { suffix: " " + $t("general.share") }) }}</span>
|
||||
</div>
|
||||
</a>
|
||||
</transition-group>
|
||||
</div>
|
||||
</transition>
|
||||
@@ -97,11 +102,17 @@ export default {
|
||||
ShareInfo,
|
||||
},
|
||||
computed: {
|
||||
canEdit() {
|
||||
return state.shareInfo?.canEdit || false;
|
||||
},
|
||||
isLoggedIn() {
|
||||
return state.user?.username !== 'anonymous';
|
||||
},
|
||||
isShare: () => getters.isShare(),
|
||||
hasLinks() {
|
||||
return this.sidebarLinksToDisplay?.length > 0;
|
||||
},
|
||||
user: () => (state.user || {username: 'anonymous'}),
|
||||
user: () => (state.user || { username: 'anonymous' }),
|
||||
sourceInfo() {
|
||||
// Access state.sources.info to create reactive dependency
|
||||
return state.sources.info;
|
||||
@@ -117,10 +128,6 @@ export default {
|
||||
// Check if user has customized their links
|
||||
return this.user?.sidebarLinks && this.user.sidebarLinks.length > 0;
|
||||
},
|
||||
canEditShare() {
|
||||
// Check if user is logged in and has share permissions
|
||||
return state.user && state.user.permissions && state.user.permissions.share;
|
||||
},
|
||||
// Share info card props
|
||||
disableShareCard() {
|
||||
return state.shareInfo?.disableShareCard;
|
||||
@@ -151,6 +158,9 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
getIconClass,
|
||||
editShareText() {
|
||||
return this.$t("general.edit") + " " + this.$t("general.share");
|
||||
},
|
||||
hasUsageInfo(link) {
|
||||
// Check if usage info should be displayed for this link
|
||||
// Returns true when link is accessible and has usage > 0
|
||||
@@ -185,6 +195,9 @@ export default {
|
||||
return baseURL + target;
|
||||
},
|
||||
goHome() {
|
||||
if (!this.isLoggedIn) {
|
||||
return;
|
||||
}
|
||||
this.$router.push('/');
|
||||
},
|
||||
getDefaultLinks() {
|
||||
@@ -320,6 +333,9 @@ export default {
|
||||
mutations.closeHovers();
|
||||
},
|
||||
openSidebarLinksPrompt() {
|
||||
if (!this.isLoggedIn) {
|
||||
return;
|
||||
}
|
||||
mutations.showHover({
|
||||
name: "SidebarLinks",
|
||||
});
|
||||
@@ -407,12 +423,12 @@ export default {
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
.no-edit-options {
|
||||
justify-content: center !important;
|
||||
}
|
||||
|
||||
.share-info-section {
|
||||
margin-top: 0 !important;
|
||||
margin-bottom: 1em;
|
||||
padding-bottom: 0.5em;
|
||||
border-bottom: 1px solid var(--borderColor);
|
||||
@@ -627,6 +643,7 @@ a.sidebar-link-button {
|
||||
.realtime-pulse.warning>.center {
|
||||
fill: rgb(255, 157, 0);
|
||||
}
|
||||
|
||||
.vue-simple-progress {
|
||||
margin-top: 0 !important;
|
||||
}
|
||||
@@ -640,4 +657,25 @@ a.sidebar-link-button {
|
||||
.edit-share-button .link-icon {
|
||||
color: var(--primaryColor);
|
||||
}
|
||||
|
||||
.sidebar-divider-container {
|
||||
width: 100%;
|
||||
margin-top: 0.5em;
|
||||
}
|
||||
|
||||
.sidebar-divider {
|
||||
width: 50%;
|
||||
border: none;
|
||||
border-top: 0.1em solid var(--divider);
|
||||
}
|
||||
|
||||
.sidebar-divider-text {
|
||||
display: flex;
|
||||
font-size: .85em;
|
||||
color: var(--textSecondary);
|
||||
letter-spacing: .05em;
|
||||
justify-content: center;
|
||||
align-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
||||
@@ -8,6 +8,7 @@
|
||||
border: 1px solid rgba(0, 0, 0, 0.05);
|
||||
box-shadow: 0 0 5px rgba(0, 0, 0, 0.05);
|
||||
transition: .1s ease all;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.button--block {
|
||||
|
||||
@@ -31,7 +31,8 @@
|
||||
"goToItem": "الانتقال إلى العنصر",
|
||||
"watchFile": "ملف المشاهدة",
|
||||
"showMore": "إظهار الخيارات المتقدمة",
|
||||
"showLess": "إخفاء الخيارات المتقدمة"
|
||||
"showLess": "إخفاء الخيارات المتقدمة",
|
||||
"goToSource": "الانتقال إلى موقع المصدر"
|
||||
},
|
||||
"download": {
|
||||
"downloadFile": "Download File",
|
||||
@@ -371,7 +372,8 @@
|
||||
"pickIconDescription": "بحث وتحديد أيقونة من مكتبة الأيقونات المادية",
|
||||
"shareLinksUpdatedSuccess": "تم تحديث روابط الشريط الجانبي المشاركة بنجاح!",
|
||||
"shareHome": "مشاركة الصفحة الرئيسية",
|
||||
"linkIconPlaceholder": "تحديد اسم رمز المادة اختياريًا"
|
||||
"linkIconPlaceholder": "تحديد اسم رمز المادة اختياريًا",
|
||||
"dividerNamePlaceholder": "نص المقسم (اتركه فارغًا لإظهار سطر)"
|
||||
},
|
||||
"success": {
|
||||
"linkCopied": "تم نسخ الملف"
|
||||
@@ -627,7 +629,8 @@
|
||||
"lastUpdate": "آخر تحديث{suffix}",
|
||||
"latency": "الكمون{suffix}",
|
||||
"all": "الكل{suffix}",
|
||||
"expiration": "انتهاء الصلاحية{suffix}"
|
||||
"expiration": "انتهاء الصلاحية{suffix}",
|
||||
"divider": "مقسم{suffix}"
|
||||
},
|
||||
"access": {
|
||||
"rules": "قواعد الوصول",
|
||||
|
||||
@@ -31,7 +31,8 @@
|
||||
"goToItem": "Přejít na položku",
|
||||
"watchFile": "Sledovat soubor",
|
||||
"showMore": "Zobrazit rozšířené možnosti",
|
||||
"showLess": "Skrytí pokročilých možností"
|
||||
"showLess": "Skrytí pokročilých možností",
|
||||
"goToSource": "Přejít na zdrojové umístění"
|
||||
},
|
||||
"download": {
|
||||
"downloadFile": "Stáhnout soubor",
|
||||
@@ -508,7 +509,8 @@
|
||||
"pickIconDescription": "Vyhledejte a vyberte ikonu z knihovny Material Icons.",
|
||||
"shareLinksUpdatedSuccess": "Odkazy na sdílení postranního panelu byly úspěšně aktualizovány!",
|
||||
"shareHome": "Sdílet Domů",
|
||||
"linkIconPlaceholder": "Volitelně zadejte název ikony materiálu"
|
||||
"linkIconPlaceholder": "Volitelně zadejte název ikony materiálu",
|
||||
"dividerNamePlaceholder": "Text oddělovače (pro zobrazení řádku nechte prázdný)"
|
||||
},
|
||||
"success": {
|
||||
"linkCopied": "Odkaz zkopírován!"
|
||||
@@ -707,7 +709,8 @@
|
||||
"lastUpdate": "Poslední aktualizace{suffix}",
|
||||
"latency": "Zpoždění{suffix}",
|
||||
"all": "Všechny{suffix}",
|
||||
"expiration": "Expirace{suffix}"
|
||||
"expiration": "Expirace{suffix}",
|
||||
"divider": "Rozdělovač{suffix}"
|
||||
},
|
||||
"access": {
|
||||
"rules": "Pravidla přístupu",
|
||||
|
||||
@@ -31,7 +31,8 @@
|
||||
"goToItem": "Weiter zu Artikel",
|
||||
"watchFile": "Datei beobachten",
|
||||
"showMore": "Erweiterte Optionen anzeigen",
|
||||
"showLess": "Erweiterte Optionen ausblenden"
|
||||
"showLess": "Erweiterte Optionen ausblenden",
|
||||
"goToSource": "Zum Quellort gehen"
|
||||
},
|
||||
"download": {
|
||||
"downloadFile": "Datei herunterladen",
|
||||
@@ -371,7 +372,8 @@
|
||||
"pickIconDescription": "Suchen und wählen Sie ein Symbol aus der Bibliothek der Materialsymbole",
|
||||
"shareLinksUpdatedSuccess": "Links in der Seitenleiste erfolgreich aktualisiert!",
|
||||
"shareHome": "Startseite teilen",
|
||||
"linkIconPlaceholder": "Optional können Sie einen Materialsymbolnamen angeben"
|
||||
"linkIconPlaceholder": "Optional können Sie einen Materialsymbolnamen angeben",
|
||||
"dividerNamePlaceholder": "Teilertext (leer lassen, um eine Zeile anzuzeigen)"
|
||||
},
|
||||
"success": {
|
||||
"linkCopied": "Link wurde kopiert!"
|
||||
@@ -627,7 +629,8 @@
|
||||
"lastUpdate": "Letzte Aktualisierung{suffix}",
|
||||
"latency": "Latenzzeit{suffix}",
|
||||
"all": "Alle{suffix}",
|
||||
"expiration": "Verfallsdatum{suffix}"
|
||||
"expiration": "Verfallsdatum{suffix}",
|
||||
"divider": "Teiler{suffix}"
|
||||
},
|
||||
"access": {
|
||||
"rules": "Zugangsregeln",
|
||||
|
||||
@@ -31,7 +31,8 @@
|
||||
"goToItem": "Πηγαίνετε στο στοιχείο",
|
||||
"watchFile": "Παρακολουθήστε το αρχείο",
|
||||
"showMore": "Εμφάνιση προηγμένων επιλογών",
|
||||
"showLess": "Απόκρυψη προηγμένων επιλογών"
|
||||
"showLess": "Απόκρυψη προηγμένων επιλογών",
|
||||
"goToSource": "Πηγαίνετε στην τοποθεσία της πηγής"
|
||||
},
|
||||
"download": {
|
||||
"downloadFile": "Λήψη αρχείου",
|
||||
@@ -374,7 +375,8 @@
|
||||
"pickIconDescription": "Αναζήτηση και επιλογή ενός εικονιδίου από τη βιβλιοθήκη Material Icons",
|
||||
"shareLinksUpdatedSuccess": "Οι σύνδεσμοι της πλαϊνής μπάρας κοινής χρήσης ενημερώθηκαν με επιτυχία!",
|
||||
"shareHome": "Μοιραστείτε το Home",
|
||||
"linkIconPlaceholder": "Προαιρετικά καθορίστε ένα όνομα εικονιδίου υλικού"
|
||||
"linkIconPlaceholder": "Προαιρετικά καθορίστε ένα όνομα εικονιδίου υλικού",
|
||||
"dividerNamePlaceholder": "Κείμενο διαχωριστικού (αφήστε το κενό για να εμφανιστεί μια γραμμή)"
|
||||
},
|
||||
"success": {
|
||||
"linkCopied": "Ο σύνδεσμος αντιγράφηκε!"
|
||||
@@ -627,7 +629,8 @@
|
||||
"lastUpdate": "Τελευταία ενημέρωση{suffix}",
|
||||
"latency": "Καθυστέρηση{suffix}",
|
||||
"all": "Όλα{suffix}",
|
||||
"expiration": "Λήξη{suffix}"
|
||||
"expiration": "Λήξη{suffix}",
|
||||
"divider": "Διαχωριστικό{suffix}"
|
||||
},
|
||||
"access": {
|
||||
"rules": "Κανόνες πρόσβασης",
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
"incompleteResultsDetails": "Showing max results ({max}). Try narrowing your search to get more results under {max}."
|
||||
},
|
||||
"general": {
|
||||
"divider": "Divider{suffix}",
|
||||
"expiration": "Expiration{suffix}",
|
||||
"all": "All{suffix}",
|
||||
"name": "Name{suffix}",
|
||||
@@ -150,7 +151,8 @@
|
||||
"saveAndExit": "Save and Exit",
|
||||
"keepEditing": "Keep Editing",
|
||||
"closeAnyway": "Close Anyways",
|
||||
"goToItem": "Go to Item"
|
||||
"goToItem": "Go to Item",
|
||||
"goToSource": "Go to Source Location"
|
||||
},
|
||||
"download": {
|
||||
"downloadFile": "Download File",
|
||||
@@ -592,6 +594,7 @@
|
||||
"adminOptions": "Admin user options"
|
||||
},
|
||||
"sidebar": {
|
||||
"dividerNamePlaceholder": "Divider text (leave blank to show a line)",
|
||||
"siteSettings": "Site Settings",
|
||||
"updateIsAvailable": "An update is available!",
|
||||
"fileActions": "File actions",
|
||||
|
||||
@@ -31,7 +31,8 @@
|
||||
"goToItem": "Ir al elemento",
|
||||
"watchFile": "Ver archivo",
|
||||
"showMore": "Mostrar opciones avanzadas",
|
||||
"showLess": "Ocultar opciones avanzadas"
|
||||
"showLess": "Ocultar opciones avanzadas",
|
||||
"goToSource": "Ir a la ubicación de origen"
|
||||
},
|
||||
"download": {
|
||||
"downloadFile": "Descargar archivo",
|
||||
@@ -371,7 +372,8 @@
|
||||
"pickIconDescription": "Aquí puedes explorar los iconos más populates de la biblioteca \"Material Icons\"",
|
||||
"shareLinksUpdatedSuccess": "¡Se actualizaron los accesos directos del recurso compartido!",
|
||||
"shareHome": "Recurso compartido",
|
||||
"linkIconPlaceholder": "Opcionalmente, puedes elegir un Material Icon que más te guste"
|
||||
"linkIconPlaceholder": "Opcionalmente, puedes elegir un Material Icon que más te guste",
|
||||
"dividerNamePlaceholder": "Texto divisor (déjelo en blanco para mostrar una línea)"
|
||||
},
|
||||
"success": {
|
||||
"linkCopied": "¡Enlace copiado!"
|
||||
@@ -627,7 +629,8 @@
|
||||
"lastUpdate": "Última actualización{suffix}",
|
||||
"latency": "Latencia{suffix}",
|
||||
"all": "Todos{suffix}",
|
||||
"expiration": "Caducidad{suffix}"
|
||||
"expiration": "Caducidad{suffix}",
|
||||
"divider": "Divisor{suffix}"
|
||||
},
|
||||
"access": {
|
||||
"rules": "Reglas de acceso",
|
||||
|
||||
@@ -31,7 +31,8 @@
|
||||
"goToItem": "Aller à l'article",
|
||||
"watchFile": "Regarder le dossier",
|
||||
"showMore": "Afficher les options avancées",
|
||||
"showLess": "Masquer les options avancées"
|
||||
"showLess": "Masquer les options avancées",
|
||||
"goToSource": "Aller à l'emplacement de la source"
|
||||
},
|
||||
"download": {
|
||||
"downloadFile": "Télécharger le fichier",
|
||||
@@ -371,7 +372,8 @@
|
||||
"pickIconDescription": "Rechercher et sélectionner une icône dans la bibliothèque des icônes matérielles",
|
||||
"shareLinksUpdatedSuccess": "Les liens de la barre latérale de partage ont été mis à jour avec succès !",
|
||||
"shareHome": "Partager l'accueil",
|
||||
"linkIconPlaceholder": "Spécifier éventuellement un nom d'icône de matériau"
|
||||
"linkIconPlaceholder": "Spécifier éventuellement un nom d'icône de matériau",
|
||||
"dividerNamePlaceholder": "Texte du séparateur (laisser en blanc pour montrer une ligne)"
|
||||
},
|
||||
"success": {
|
||||
"linkCopied": "Lien copié !"
|
||||
@@ -627,7 +629,8 @@
|
||||
"lastUpdate": "Dernière mise à jour{suffix}",
|
||||
"latency": "Temps de latence{suffix}",
|
||||
"all": "Tous{suffix}",
|
||||
"expiration": "Expiration{suffix}"
|
||||
"expiration": "Expiration{suffix}",
|
||||
"divider": "Diviseur{suffix}"
|
||||
},
|
||||
"access": {
|
||||
"rules": "Règles d'accès",
|
||||
|
||||
@@ -31,7 +31,8 @@
|
||||
"goToItem": "עבור לפריט",
|
||||
"watchFile": "צפה בקובץ",
|
||||
"showMore": "הצג אפשרויות מתקדמות",
|
||||
"showLess": "הסתר אפשרויות מתקדמות"
|
||||
"showLess": "הסתר אפשרויות מתקדמות",
|
||||
"goToSource": "עבור למיקום המקור"
|
||||
},
|
||||
"download": {
|
||||
"downloadFile": "הורד קובץ",
|
||||
@@ -371,7 +372,8 @@
|
||||
"pickIconDescription": "חפש ובחר סמל מספריית Material Icons.",
|
||||
"shareLinksUpdatedSuccess": "קישורי הסרגל הצדדי שותפו בהצלחה!",
|
||||
"shareHome": "שתף בית",
|
||||
"linkIconPlaceholder": "אפשר לציין שם סמל חומר"
|
||||
"linkIconPlaceholder": "אפשר לציין שם סמל חומר",
|
||||
"dividerNamePlaceholder": "טקסט מפריד (השאר ריק כדי להציג קו)"
|
||||
},
|
||||
"success": {
|
||||
"linkCopied": "הקישור הועתק!"
|
||||
@@ -627,7 +629,8 @@
|
||||
"lastUpdate": "עדכון אחרון{suffix}",
|
||||
"latency": "חביון{suffix}",
|
||||
"all": "כל{suffix}",
|
||||
"expiration": "תוקף{suffix}"
|
||||
"expiration": "תוקף{suffix}",
|
||||
"divider": "מפריד{suffix}"
|
||||
},
|
||||
"access": {
|
||||
"rules": "כללי גישה",
|
||||
|
||||
@@ -31,7 +31,8 @@
|
||||
"goToItem": "Tovább a tételhez",
|
||||
"watchFile": "Nézze meg a fájlt",
|
||||
"showMore": "Speciális beállítások megjelenítése",
|
||||
"showLess": "Speciális beállítások elrejtése"
|
||||
"showLess": "Speciális beállítások elrejtése",
|
||||
"goToSource": "Menj a forráshelyre"
|
||||
},
|
||||
"download": {
|
||||
"downloadFile": "Fájl letöltése",
|
||||
@@ -371,7 +372,8 @@
|
||||
"pickIconDescription": "Keressen és válasszon ki egy ikont az Anyagi ikonok könyvtárból.",
|
||||
"shareLinksUpdatedSuccess": "Megosztás oldalsáv linkek sikeresen frissítve!",
|
||||
"shareHome": "Megosztás",
|
||||
"linkIconPlaceholder": "Opcionálisan megadhatja az anyag ikon nevét"
|
||||
"linkIconPlaceholder": "Opcionálisan megadhatja az anyag ikon nevét",
|
||||
"dividerNamePlaceholder": "Választószöveg (üresen hagyva egy sor megjelenítéséhez)"
|
||||
},
|
||||
"success": {
|
||||
"linkCopied": "Link másolva!"
|
||||
@@ -627,7 +629,8 @@
|
||||
"lastUpdate": "Utolsó frissítés{suffix}",
|
||||
"latency": "Késleltetés{suffix}",
|
||||
"all": "Minden{suffix}",
|
||||
"expiration": "Lejárat{suffix}"
|
||||
"expiration": "Lejárat{suffix}",
|
||||
"divider": "Választó{suffix}"
|
||||
},
|
||||
"access": {
|
||||
"rules": "Hozzáférési szabályok",
|
||||
|
||||
@@ -31,7 +31,8 @@
|
||||
"goToItem": "Vai all'articolo",
|
||||
"watchFile": "Guarda il file",
|
||||
"showMore": "Mostra opzioni avanzate",
|
||||
"showLess": "Nascondere le opzioni avanzate"
|
||||
"showLess": "Nascondere le opzioni avanzate",
|
||||
"goToSource": "Vai alla posizione della fonte"
|
||||
},
|
||||
"download": {
|
||||
"downloadFile": "Scarica file",
|
||||
@@ -371,7 +372,8 @@
|
||||
"pickIconDescription": "Cercare e selezionare un'icona dalla libreria Material Icons.",
|
||||
"shareLinksUpdatedSuccess": "I link della barra laterale di condivisione sono stati aggiornati con successo!",
|
||||
"shareHome": "Condividi Casa",
|
||||
"linkIconPlaceholder": "Specificare facoltativamente il nome dell'icona del materiale"
|
||||
"linkIconPlaceholder": "Specificare facoltativamente il nome dell'icona del materiale",
|
||||
"dividerNamePlaceholder": "Testo del divisore (lasciare vuoto per mostrare una riga)"
|
||||
},
|
||||
"success": {
|
||||
"linkCopied": "Link copiato!"
|
||||
@@ -627,7 +629,8 @@
|
||||
"lastUpdate": "Ultimo aggiornamento{suffix}",
|
||||
"latency": "Latenza{suffix}",
|
||||
"all": "Tutti{suffix}",
|
||||
"expiration": "Scadenza{suffix}"
|
||||
"expiration": "Scadenza{suffix}",
|
||||
"divider": "Divisore{suffix}"
|
||||
},
|
||||
"access": {
|
||||
"rules": "Regole di accesso",
|
||||
|
||||
@@ -31,7 +31,8 @@
|
||||
"goToItem": "アイテムへ",
|
||||
"watchFile": "ウォッチファイル",
|
||||
"showMore": "詳細オプションを表示",
|
||||
"showLess": "詳細オプションを隠す"
|
||||
"showLess": "詳細オプションを隠す",
|
||||
"goToSource": "ソース・ロケーションへ"
|
||||
},
|
||||
"download": {
|
||||
"downloadFile": "ファイルをダウンロード",
|
||||
@@ -371,7 +372,8 @@
|
||||
"pickIconDescription": "マテリアル・アイコン・ライブラリからアイコンを検索して選択します。",
|
||||
"shareLinksUpdatedSuccess": "サイドバーの共有リンクが正常に更新されました!",
|
||||
"shareHome": "シェア・ホーム",
|
||||
"linkIconPlaceholder": "オプションで素材アイコン名を指定"
|
||||
"linkIconPlaceholder": "オプションで素材アイコン名を指定",
|
||||
"dividerNamePlaceholder": "分割テキスト(行を表示するには空白にする)"
|
||||
},
|
||||
"success": {
|
||||
"linkCopied": "リンクがコピーされました!"
|
||||
@@ -627,7 +629,8 @@
|
||||
"lastUpdate": "最終更新{suffix}",
|
||||
"latency": "レイテンシー{suffix}",
|
||||
"all": "すべて{suffix}",
|
||||
"expiration": "有効期限{suffix}"
|
||||
"expiration": "有効期限{suffix}",
|
||||
"divider": "ディバイダー{suffix}"
|
||||
},
|
||||
"access": {
|
||||
"rules": "アクセスルール",
|
||||
|
||||
@@ -31,7 +31,8 @@
|
||||
"goToItem": "항목으로 이동",
|
||||
"watchFile": "파일 보기",
|
||||
"showMore": "고급 옵션 표시",
|
||||
"showLess": "고급 옵션 숨기기"
|
||||
"showLess": "고급 옵션 숨기기",
|
||||
"goToSource": "소스 위치로 이동"
|
||||
},
|
||||
"download": {
|
||||
"downloadFile": "파일 다운로드",
|
||||
@@ -371,7 +372,8 @@
|
||||
"pickIconDescription": "머티리얼 아이콘 라이브러리에서 아이콘을 검색하고 선택합니다.",
|
||||
"shareLinksUpdatedSuccess": "공유 사이드바 링크가 성공적으로 업데이트되었습니다!",
|
||||
"shareHome": "홈 공유",
|
||||
"linkIconPlaceholder": "선택적으로 머티리얼 아이콘 이름을 지정합니다."
|
||||
"linkIconPlaceholder": "선택적으로 머티리얼 아이콘 이름을 지정합니다.",
|
||||
"dividerNamePlaceholder": "디바이더 텍스트(줄을 표시하려면 비워둡니다)"
|
||||
},
|
||||
"success": {
|
||||
"linkCopied": "링크가 복사되었습니다!"
|
||||
@@ -627,7 +629,8 @@
|
||||
"lastUpdate": "최근 업데이트{suffix}",
|
||||
"latency": "지연 시간{suffix}",
|
||||
"all": "모두{suffix}",
|
||||
"expiration": "만료{suffix}"
|
||||
"expiration": "만료{suffix}",
|
||||
"divider": "디바이더{suffix}"
|
||||
},
|
||||
"access": {
|
||||
"rules": "액세스 규칙",
|
||||
|
||||
@@ -31,7 +31,8 @@
|
||||
"goToItem": "Ga naar item",
|
||||
"watchFile": "Bestand bekijken",
|
||||
"showMore": "Geavanceerde opties weergeven",
|
||||
"showLess": "Geavanceerde opties verbergen"
|
||||
"showLess": "Geavanceerde opties verbergen",
|
||||
"goToSource": "Ga naar de bronlocatie"
|
||||
},
|
||||
"download": {
|
||||
"downloadFile": "Bestand downloaden",
|
||||
@@ -371,7 +372,8 @@
|
||||
"pickIconDescription": "Zoek en selecteer een pictogram uit de bibliotheek met materiaalpictogrammen",
|
||||
"shareLinksUpdatedSuccess": "Zijbalk links delen succesvol bijgewerkt!",
|
||||
"shareHome": "Thuis delen",
|
||||
"linkIconPlaceholder": "Geef optioneel een materiaalpictogramnaam op"
|
||||
"linkIconPlaceholder": "Geef optioneel een materiaalpictogramnaam op",
|
||||
"dividerNamePlaceholder": "Scheidingstekst (leeg laten om een lijn weer te geven)"
|
||||
},
|
||||
"success": {
|
||||
"linkCopied": "Link gekopieerd!"
|
||||
@@ -707,7 +709,8 @@
|
||||
"lastUpdate": "Laatste update{suffix}",
|
||||
"latency": "Latency{suffix}",
|
||||
"all": "Alle{suffix}",
|
||||
"expiration": "Vervaldatum{suffix}"
|
||||
"expiration": "Vervaldatum{suffix}",
|
||||
"divider": "Verdeler{suffix}"
|
||||
},
|
||||
"access": {
|
||||
"rules": "Toegangsregels",
|
||||
|
||||
@@ -116,7 +116,8 @@
|
||||
"lines": "Regels{suffix}",
|
||||
"lastUpdate": "Laatste update{suffix}",
|
||||
"latency": "Vertraging{suffix}",
|
||||
"expiration": "Vervaldatum{suffix}"
|
||||
"expiration": "Vervaldatum{suffix}",
|
||||
"divider": "Verdeler{suffix}"
|
||||
},
|
||||
"buttons": {
|
||||
"generateNewOtp": "Herstellen en nieuwe twee-factorcode genereren",
|
||||
@@ -150,7 +151,8 @@
|
||||
"saveAndExit": "Opslaan en afsluiten",
|
||||
"keepEditing": "Blijven bewerken",
|
||||
"closeAnyway": "Sluiten",
|
||||
"goToItem": "Ga naar item"
|
||||
"goToItem": "Ga naar item",
|
||||
"goToSource": "Ga naar de bronlocatie"
|
||||
},
|
||||
"download": {
|
||||
"downloadFile": "Bestand downloaden",
|
||||
@@ -627,7 +629,8 @@
|
||||
"linksUpdateFailed": "Bijwerken van koppelingen in zijbalk is mislukt",
|
||||
"sourceNotAccessible": "Deze bron is niet langer toegankelijk voor jou",
|
||||
"noLinksYet": "Er zijn nog geen links geconfigureerd. Voeg hieronder uw eerste link toe.",
|
||||
"shareHome": "Thuis delen"
|
||||
"shareHome": "Thuis delen",
|
||||
"dividerNamePlaceholder": "Scheidingstekst (leeg laten om een lijn weer te geven)"
|
||||
},
|
||||
"success": {
|
||||
"linkCopied": "Koppeling gekopieerd!"
|
||||
@@ -850,4 +853,4 @@
|
||||
"reconnected": "Opnieuw verbinding gemaakt met server.",
|
||||
"connectionLost": "De verbinding met de server is verbroken. Probeer opnieuw verbinding te maken..."
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -31,7 +31,8 @@
|
||||
"goToItem": "Przejdź do pozycji",
|
||||
"watchFile": "Oglądaj plik",
|
||||
"showMore": "Pokaż opcje zaawansowane",
|
||||
"showLess": "Ukryj opcje zaawansowane"
|
||||
"showLess": "Ukryj opcje zaawansowane",
|
||||
"goToSource": "Przejdź do lokalizacji źródłowej"
|
||||
},
|
||||
"download": {
|
||||
"downloadFile": "Pobierz plik",
|
||||
@@ -371,7 +372,8 @@
|
||||
"pickIconDescription": "Wyszukaj i wybierz ikonę z biblioteki Material Icons",
|
||||
"shareLinksUpdatedSuccess": "Linki do paska bocznego zostały pomyślnie zaktualizowane!",
|
||||
"shareHome": "Share Home",
|
||||
"linkIconPlaceholder": "Opcjonalnie można określić nazwę ikony materiału"
|
||||
"linkIconPlaceholder": "Opcjonalnie można określić nazwę ikony materiału",
|
||||
"dividerNamePlaceholder": "Tekst podziału (pozostaw pusty, aby wyświetlić linię)"
|
||||
},
|
||||
"success": {
|
||||
"linkCopied": "Link Skopiowany!"
|
||||
@@ -627,7 +629,8 @@
|
||||
"lastUpdate": "Ostatnia aktualizacja{suffix}",
|
||||
"latency": "Opóźnienie{suffix}",
|
||||
"all": "Wszystkie{suffix}",
|
||||
"expiration": "Wygaśnięcie{suffix}"
|
||||
"expiration": "Wygaśnięcie{suffix}",
|
||||
"divider": "Dzielnik{suffix}"
|
||||
},
|
||||
"access": {
|
||||
"rules": "Zasady dostępu",
|
||||
|
||||
@@ -31,7 +31,8 @@
|
||||
"goToItem": "Ir para o item",
|
||||
"watchFile": "Assista ao arquivo",
|
||||
"showMore": "Mostrar opções avançadas",
|
||||
"showLess": "Ocultar opções avançadas"
|
||||
"showLess": "Ocultar opções avançadas",
|
||||
"goToSource": "Ir para o local de origem"
|
||||
},
|
||||
"download": {
|
||||
"downloadFile": "Baixar arquivo",
|
||||
@@ -371,7 +372,8 @@
|
||||
"pickIconDescription": "Pesquise e selecione um ícone na biblioteca de ícones de materiais",
|
||||
"shareLinksUpdatedSuccess": "Os links da barra lateral de compartilhamento foram atualizados com sucesso!",
|
||||
"shareHome": "Compartilhar casa",
|
||||
"linkIconPlaceholder": "Opcionalmente, especifique um nome de ícone de material"
|
||||
"linkIconPlaceholder": "Opcionalmente, especifique um nome de ícone de material",
|
||||
"dividerNamePlaceholder": "Texto do divisor (deixe em branco para mostrar uma linha)"
|
||||
},
|
||||
"success": {
|
||||
"linkCopied": "Link copiado!"
|
||||
@@ -627,7 +629,8 @@
|
||||
"lastUpdate": "Última atualização{suffix}",
|
||||
"latency": "Latência{suffix}",
|
||||
"all": "Todos{suffix}",
|
||||
"expiration": "Vencimento{suffix}"
|
||||
"expiration": "Vencimento{suffix}",
|
||||
"divider": "Divisor{suffix}"
|
||||
},
|
||||
"access": {
|
||||
"rules": "Regras de acesso",
|
||||
|
||||
@@ -31,7 +31,8 @@
|
||||
"goToItem": "Ir para o item",
|
||||
"watchFile": "Ver ficheiro",
|
||||
"showMore": "Mostrar opções avançadas",
|
||||
"showLess": "Ocultar opções avançadas"
|
||||
"showLess": "Ocultar opções avançadas",
|
||||
"goToSource": "Ir para o local de origem"
|
||||
},
|
||||
"download": {
|
||||
"downloadFile": "Descarregar ficheiro",
|
||||
@@ -371,7 +372,8 @@
|
||||
"pickIconDescription": "Procurar e selecionar um ícone na biblioteca de ícones de materiais",
|
||||
"shareLinksUpdatedSuccess": "As ligações da barra lateral de partilha foram actualizadas com êxito!",
|
||||
"shareHome": "Partilhar casa",
|
||||
"linkIconPlaceholder": "Especificar opcionalmente um nome de ícone de material"
|
||||
"linkIconPlaceholder": "Especificar opcionalmente um nome de ícone de material",
|
||||
"dividerNamePlaceholder": "Texto do separador (deixar em branco para mostrar uma linha)"
|
||||
},
|
||||
"success": {
|
||||
"linkCopied": "Link copiado!"
|
||||
@@ -707,7 +709,8 @@
|
||||
"lastUpdate": "Última atualização{suffix}",
|
||||
"latency": "Latência{suffix}",
|
||||
"all": "Todos{suffix}",
|
||||
"expiration": "Prazo de validade{suffix}"
|
||||
"expiration": "Prazo de validade{suffix}",
|
||||
"divider": "Divisor{suffix}"
|
||||
},
|
||||
"access": {
|
||||
"rules": "Regras de acesso",
|
||||
|
||||
@@ -31,7 +31,8 @@
|
||||
"goToItem": "Du-te la articol",
|
||||
"watchFile": "Fișier de vizionare",
|
||||
"showMore": "Afișare opțiuni avansate",
|
||||
"showLess": "Ascundeți opțiunile avansate"
|
||||
"showLess": "Ascundeți opțiunile avansate",
|
||||
"goToSource": "Du-te la locația sursei"
|
||||
},
|
||||
"download": {
|
||||
"downloadFile": "Descarcă fișier",
|
||||
@@ -371,7 +372,8 @@
|
||||
"pickIconDescription": "Căutați și selectați o pictogramă din biblioteca Material Icons",
|
||||
"shareLinksUpdatedSuccess": "Link-urile din bara laterală de partajare au fost actualizate cu succes!",
|
||||
"shareHome": "Share Home",
|
||||
"linkIconPlaceholder": "Opțional, specificați un nume de pictogramă de material"
|
||||
"linkIconPlaceholder": "Opțional, specificați un nume de pictogramă de material",
|
||||
"dividerNamePlaceholder": "Textul separatorului (lăsați gol pentru a afișa o linie)"
|
||||
},
|
||||
"success": {
|
||||
"linkCopied": "Legătură copiată!"
|
||||
@@ -627,7 +629,8 @@
|
||||
"lastUpdate": "Ultima actualizare{suffix}",
|
||||
"latency": "Latență{suffix}",
|
||||
"all": "Toate{suffix}",
|
||||
"expiration": "Expirare{suffix}"
|
||||
"expiration": "Expirare{suffix}",
|
||||
"divider": "Divizor{suffix}"
|
||||
},
|
||||
"access": {
|
||||
"rules": "Reguli de acces",
|
||||
|
||||
@@ -31,7 +31,8 @@
|
||||
"goToItem": "Перейти к пункту",
|
||||
"watchFile": "Смотреть файл",
|
||||
"showMore": "Показать дополнительные параметры",
|
||||
"showLess": "Скрыть дополнительные параметры"
|
||||
"showLess": "Скрыть дополнительные параметры",
|
||||
"goToSource": "Перейти к исходному местоположению"
|
||||
},
|
||||
"download": {
|
||||
"downloadFile": "Скачать файл",
|
||||
@@ -371,7 +372,8 @@
|
||||
"pickIconDescription": "Найдите и выберите значок в библиотеке Material Icons.",
|
||||
"shareLinksUpdatedSuccess": "Ссылки на боковой панели успешно обновлены!",
|
||||
"shareHome": "Поделиться Домой",
|
||||
"linkIconPlaceholder": "Опционально укажите имя значка материала"
|
||||
"linkIconPlaceholder": "Опционально укажите имя значка материала",
|
||||
"dividerNamePlaceholder": "Текст разделителя (оставьте пустым, чтобы показать строку)"
|
||||
},
|
||||
"success": {
|
||||
"linkCopied": "Ссылка скопирована!"
|
||||
@@ -627,7 +629,8 @@
|
||||
"lastUpdate": "Последнее обновление{suffix}",
|
||||
"latency": "Латентность{suffix}",
|
||||
"all": "Все{suffix}",
|
||||
"expiration": "Срок действия{suffix}"
|
||||
"expiration": "Срок действия{suffix}",
|
||||
"divider": "Разделитель{suffix}"
|
||||
},
|
||||
"access": {
|
||||
"rules": "Правила доступа",
|
||||
|
||||
@@ -31,7 +31,8 @@
|
||||
"goToItem": "Prejsť na položku",
|
||||
"watchFile": "Sledovať súbor",
|
||||
"showMore": "Zobraziť rozšírené možnosti",
|
||||
"showLess": "Skryť rozšírené možnosti"
|
||||
"showLess": "Skryť rozšírené možnosti",
|
||||
"goToSource": "Prejsť na zdrojové miesto"
|
||||
},
|
||||
"download": {
|
||||
"downloadFile": "Stiahnuť súbor",
|
||||
@@ -371,7 +372,8 @@
|
||||
"pickIconDescription": "Vyhľadajte a vyberte ikonu z knižnice Material Icons",
|
||||
"shareLinksUpdatedSuccess": "Zdieľanie bočného panela odkazy úspešne aktualizované!",
|
||||
"shareHome": "Zdieľať Domov",
|
||||
"linkIconPlaceholder": "Voliteľne zadajte názov ikony materiálu"
|
||||
"linkIconPlaceholder": "Voliteľne zadajte názov ikony materiálu",
|
||||
"dividerNamePlaceholder": "Text oddeľovača (nechajte prázdny, aby sa zobrazil riadok)"
|
||||
},
|
||||
"success": {
|
||||
"linkCopied": "Odkaz skopírovaný!"
|
||||
@@ -627,7 +629,8 @@
|
||||
"lastUpdate": "Posledná aktualizácia{suffix}",
|
||||
"latency": "Latencia{suffix}",
|
||||
"all": "Všetky{suffix}",
|
||||
"expiration": "Uplynutie platnosti{suffix}"
|
||||
"expiration": "Uplynutie platnosti{suffix}",
|
||||
"divider": "Rozdeľovač{suffix}"
|
||||
},
|
||||
"access": {
|
||||
"rules": "Pravidlá prístupu",
|
||||
|
||||
@@ -31,7 +31,8 @@
|
||||
"goToItem": "Gå till artikel",
|
||||
"watchFile": "Titta på filen",
|
||||
"showMore": "Visa avancerade alternativ",
|
||||
"showLess": "Dölj avancerade alternativ"
|
||||
"showLess": "Dölj avancerade alternativ",
|
||||
"goToSource": "Gå till källplatsen"
|
||||
},
|
||||
"download": {
|
||||
"downloadFile": "Ladda ner fil",
|
||||
@@ -371,7 +372,8 @@
|
||||
"pickIconDescription": "Sök och välj en ikon från Material Icons-biblioteket",
|
||||
"shareLinksUpdatedSuccess": "Dela sidofältets länkar uppdaterades framgångsrikt!",
|
||||
"shareHome": "Dela hem",
|
||||
"linkIconPlaceholder": "Ange eventuellt ett namn på materialikonen"
|
||||
"linkIconPlaceholder": "Ange eventuellt ett namn på materialikonen",
|
||||
"dividerNamePlaceholder": "Delningstext (lämna tom för att visa en rad)"
|
||||
},
|
||||
"success": {
|
||||
"linkCopied": "Länk kopierad"
|
||||
@@ -707,7 +709,8 @@
|
||||
"lastUpdate": "Senaste uppdatering{suffix}",
|
||||
"latency": "Fördröjning{suffix}",
|
||||
"all": "Alla{suffix}",
|
||||
"expiration": "Förfallodag{suffix}"
|
||||
"expiration": "Förfallodag{suffix}",
|
||||
"divider": "Uppdelare{suffix}"
|
||||
},
|
||||
"access": {
|
||||
"rules": "Regler för åtkomst",
|
||||
|
||||
@@ -31,7 +31,8 @@
|
||||
"goToItem": "Öğeye git",
|
||||
"watchFile": "Dosyayı izle",
|
||||
"showMore": "Gelişmiş Seçenekleri Göster",
|
||||
"showLess": "Gelişmiş Seçenekleri Gizle"
|
||||
"showLess": "Gelişmiş Seçenekleri Gizle",
|
||||
"goToSource": "Kaynak Konuma Git"
|
||||
},
|
||||
"download": {
|
||||
"downloadFile": "Dosyayı indir",
|
||||
@@ -371,7 +372,8 @@
|
||||
"pickIconDescription": "Materyal Simgeleri kitaplığından bir simge arayın ve seçin",
|
||||
"shareLinksUpdatedSuccess": "Kenar çubuğu bağlantılarını paylaşma başarıyla güncellendi!",
|
||||
"shareHome": "Ev Paylaşımı",
|
||||
"linkIconPlaceholder": "İsteğe bağlı olarak bir malzeme simgesi adı belirtin"
|
||||
"linkIconPlaceholder": "İsteğe bağlı olarak bir malzeme simgesi adı belirtin",
|
||||
"dividerNamePlaceholder": "Bölücü metin (bir çizgi göstermek için boş bırakın)"
|
||||
},
|
||||
"success": {
|
||||
"linkCopied": "Link kopyalandı!"
|
||||
@@ -627,7 +629,8 @@
|
||||
"lastUpdate": "Son Güncelleme{suffix}",
|
||||
"latency": "Gecikme{suffix}",
|
||||
"all": "Tümü{suffix}",
|
||||
"expiration": "Son kullanma tarihi{suffix}"
|
||||
"expiration": "Son kullanma tarihi{suffix}",
|
||||
"divider": "Bölücü{suffix}"
|
||||
},
|
||||
"access": {
|
||||
"rules": "Erişim kuralları",
|
||||
|
||||
@@ -31,7 +31,8 @@
|
||||
"goToItem": "Перейти до пункту",
|
||||
"watchFile": "Переглянути файл",
|
||||
"showMore": "Показати додаткові параметри",
|
||||
"showLess": "Приховати додаткові параметри"
|
||||
"showLess": "Приховати додаткові параметри",
|
||||
"goToSource": "Перейдіть до Розташування джерела"
|
||||
},
|
||||
"download": {
|
||||
"downloadFile": "Завантажити файл",
|
||||
@@ -371,7 +372,8 @@
|
||||
"pickIconDescription": "Знайдіть і виберіть піктограму з бібліотеки піктограм матеріалів",
|
||||
"shareLinksUpdatedSuccess": "Посилання на бічну панель обміну успішно оновлено!",
|
||||
"shareHome": "Поділитися Додому",
|
||||
"linkIconPlaceholder": "За бажанням вкажіть назву іконки матеріалу"
|
||||
"linkIconPlaceholder": "За бажанням вкажіть назву іконки матеріалу",
|
||||
"dividerNamePlaceholder": "Текст роздільника (залиште порожнім, щоб показати лінію)"
|
||||
},
|
||||
"success": {
|
||||
"linkCopied": "Посилання скопійоване!"
|
||||
@@ -707,7 +709,8 @@
|
||||
"lastUpdate": "Останнє оновлення{suffix}",
|
||||
"latency": "Затримка{suffix}",
|
||||
"all": "Усе.{suffix}",
|
||||
"expiration": "Закінчення терміну дії{suffix}"
|
||||
"expiration": "Закінчення терміну дії{suffix}",
|
||||
"divider": "Перегородка{suffix}"
|
||||
},
|
||||
"access": {
|
||||
"rules": "Правила доступу",
|
||||
|
||||
@@ -31,7 +31,8 @@
|
||||
"goToItem": "转到项目",
|
||||
"watchFile": "观看文件",
|
||||
"showMore": "显示高级选项",
|
||||
"showLess": "隐藏高级选项"
|
||||
"showLess": "隐藏高级选项",
|
||||
"goToSource": "转到源位置"
|
||||
},
|
||||
"download": {
|
||||
"downloadFile": "下载文件",
|
||||
@@ -371,7 +372,8 @@
|
||||
"pickIconDescription": "从材质图标库中搜索并选择图标",
|
||||
"shareLinksUpdatedSuccess": "共享侧边栏链接更新成功!",
|
||||
"shareHome": "共享主页",
|
||||
"linkIconPlaceholder": "可选择指定材料图标名称"
|
||||
"linkIconPlaceholder": "可选择指定材料图标名称",
|
||||
"dividerNamePlaceholder": "分隔线文本(留空以显示一行)"
|
||||
},
|
||||
"success": {
|
||||
"linkCopied": "链接已复制!"
|
||||
@@ -707,7 +709,8 @@
|
||||
"lastUpdate": "最新更新{suffix}",
|
||||
"latency": "延迟{suffix}",
|
||||
"all": "全部{suffix}",
|
||||
"expiration": "到期{suffix}"
|
||||
"expiration": "到期{suffix}",
|
||||
"divider": "分割器{suffix}"
|
||||
},
|
||||
"access": {
|
||||
"rules": "访问规则",
|
||||
|
||||
@@ -31,7 +31,8 @@
|
||||
"goToItem": "前往項目",
|
||||
"watchFile": "觀看檔案",
|
||||
"showMore": "顯示進階選項",
|
||||
"showLess": "隱藏進階選項"
|
||||
"showLess": "隱藏進階選項",
|
||||
"goToSource": "前往來源位置"
|
||||
},
|
||||
"download": {
|
||||
"downloadFile": "下載檔案",
|
||||
@@ -371,7 +372,8 @@
|
||||
"pickIconDescription": "從 Material Icons 圖示庫搜尋並選取圖示",
|
||||
"shareLinksUpdatedSuccess": "分享側邊欄連結更新成功!",
|
||||
"shareHome": "分享首頁",
|
||||
"linkIconPlaceholder": "可選擇指定材質圖示名稱"
|
||||
"linkIconPlaceholder": "可選擇指定材質圖示名稱",
|
||||
"dividerNamePlaceholder": "分割線文字(留空顯示一行)"
|
||||
},
|
||||
"success": {
|
||||
"linkCopied": "連結已複製!"
|
||||
@@ -707,7 +709,8 @@
|
||||
"lastUpdate": "最後更新{suffix}",
|
||||
"latency": "延遲{suffix}",
|
||||
"all": "全部{suffix}",
|
||||
"expiration": "到期日{suffix}"
|
||||
"expiration": "到期日{suffix}",
|
||||
"divider": "分割器{suffix}"
|
||||
},
|
||||
"access": {
|
||||
"rules": "存取規則",
|
||||
|
||||
@@ -34,12 +34,12 @@ test("check previously created share has correct sidebar links", async ({ page,
|
||||
// navigate to the share sidebar
|
||||
await page.goto("/public/share/" + shareHash);
|
||||
await expect(page).toHaveTitle("Graham's Filebrowser - Share - text-files");
|
||||
// sidebar should have three items (ShareInfo, Download, Edit Share)
|
||||
// sidebar should have 3 items (ShareInfo, Download, Go to source location)
|
||||
await expect(page.locator('.sidebar-links .inner-card').locator('a')).toHaveCount(3);
|
||||
// check items exist
|
||||
await page.locator('a[aria-label="Share QR Code and Info"]').waitFor({ state: 'visible' });
|
||||
await page.locator('a[aria-label="Download"]').waitFor({ state: 'visible' });
|
||||
await page.locator('a[aria-label="Edit Share"]').waitFor({ state: 'visible' });
|
||||
await page.locator('i[aria-label="Edit Share"]').waitFor({ state: 'visible' });
|
||||
checkForErrors();
|
||||
});
|
||||
|
||||
@@ -76,12 +76,11 @@ test("edit previously created links and ensure they are updated", async ({ page,
|
||||
// navigate to the share sidebar
|
||||
await page.goto("/public/share/" + shareHash);
|
||||
await expect(page).toHaveTitle("Graham's Filebrowser - Share - text-files");
|
||||
// sidebar should have four items (ShareInfo, Download, New Custom Link, Edit Share)
|
||||
// sidebar should have 4 items (ShareInfo, Download, New Custom Link, Go to source location)
|
||||
await expect(page.locator('.sidebar-links .inner-card').locator('a')).toHaveCount(4);
|
||||
// check items exist
|
||||
await page.locator('a[aria-label="Share QR Code and Info"]').waitFor({ state: 'visible' });
|
||||
await page.locator('a[aria-label="Download"]').waitFor({ state: 'visible' });
|
||||
await page.locator('a[aria-label="New Custom Link"]').waitFor({ state: 'visible' });
|
||||
await page.locator('a[aria-label="Edit Share"]').waitFor({ state: 'visible' });
|
||||
checkForErrors();
|
||||
});
|
||||
Reference in new issue
Block a user