Compare commits
5
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
605ac449e4 | ||
|
|
509633b913 | ||
|
|
70933aea36 | ||
|
|
8a597e3202 | ||
|
|
c26f62a96e |
No files matched your search
@@ -39,7 +39,7 @@ jobs:
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: dist
|
||||
path: backend/http/dist
|
||||
path: frontend/dist
|
||||
build_backend:
|
||||
name: Build backend
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
+1
-53
@@ -2,58 +2,6 @@
|
||||
|
||||
All notable changes to this project will be documented in this file. For commit guidelines, please refer to [Standard Version](https://github.com/conventional-changelog/standard-version).
|
||||
|
||||
## v0.8.4-beta
|
||||
|
||||
**New Features**:
|
||||
- Remember folder view type (and sorting) for each folder #966
|
||||
- heic is supported in preview and viewer (via conversion) when on non-safari browser #1191
|
||||
|
||||
**Notes**:
|
||||
|
||||
**BugFixes**:
|
||||
- Access Management: issue with access settings #1195
|
||||
- fix shutdown panic related to sse connection
|
||||
|
||||
## v0.8.4-beta
|
||||
|
||||
**New Features**:
|
||||
- New media player styles and features
|
||||
- Custom Media Player: enhanced media player using plyr thanks @Kurami32 (see #1160)
|
||||
- Custom Media Player: also adds support for metadata
|
||||
- added embeded video subtitle support (for both native and custom player). @maxbin123 #1072 #1157
|
||||
- Users can disable the customer player and opt of native in profile settings.
|
||||
- Option to disable backend update check via `server.disableUpdateCheck` #1134
|
||||
- added `frontend.favicon` and `frontend.description` for html overrides
|
||||
- onlyoffice is now supported in shares. Both viewing and editing can be configured per-share.
|
||||
- Added only office debug view and wiki to assist with debugging issues #1068 #911 #1074
|
||||
- Dark mode enforcement possible for shared links #1029
|
||||
- added `System & Admin` section to settings
|
||||
- includes a new config viewer to see current running config (hides secrets) #838
|
||||
- added `server.minSearchLength` to allow adjusting the length requirement for search #1174
|
||||
|
||||
**Notes**:
|
||||
- access management: specific folders/files with access are shown instead permission denied for parent folder
|
||||
- navigation no longer appends last location hash which should fix some unwanted navation behavior #1070
|
||||
- altered the context menu style and behavior.
|
||||
- documentation update: comma or Space separated extensions #1138
|
||||
- Files and folders can be created with "/" or "\" on the name #1126
|
||||
- Share management should not be allowed without authentication #1163
|
||||
- Question about customizing session timeout #1184
|
||||
|
||||
**BugFixes**:
|
||||
- access management: delay showing rule changes in the list fixed. #1131
|
||||
- Color names are not localized #1159
|
||||
- rename issues #1170 #1171
|
||||
- some shortcuts not working #1056
|
||||
- Can't copy/paste text on mobile #1168
|
||||
- Can't change between images inside of the share image viewer. #1144
|
||||
- fixed and updated translations with variables always showing english.
|
||||
|
||||
## v0.8.3-beta
|
||||
|
||||
**BugFixes**:
|
||||
- fixed search bar style bug in mobile #1147
|
||||
|
||||
## v0.8.2-beta
|
||||
|
||||
**New Features**:
|
||||
@@ -65,7 +13,7 @@ All notable changes to this project will be documented in this file. For commit
|
||||
**Notes**:
|
||||
- 8.0 ffmpeg version bundled with docker
|
||||
- go 1.25 upgrade with green tea GC enabled
|
||||
- totp secrets accept non-secure strings, only throwing warning
|
||||
- totp secrets accept non-secure strings, only throwing warning
|
||||
- adjusted download limit so it also counts viewing text "content" of files (like in editor). You can also "disable file viewing" to stop the editor from showing. lower quality file image previews are not counted as downloads.
|
||||
- updated invalid share message to be more clear https://github.com/gtsteffaniak/filebrowser/issues/1120
|
||||
|
||||
|
||||
+25
-14
@@ -4,19 +4,19 @@ Thank you for your interest in contributing to FileBrowser Quantum! This guide w
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- **Go 1.25+** (see `backend/go.mod`)
|
||||
- **Go 1.24+** (see `backend/go.mod`)
|
||||
- **Node.js 18.0.0+** with npm 7.0.0+ (see `frontend/package.json`)
|
||||
- **Docker** (optional, for containerized development)
|
||||
- **Git**
|
||||
|
||||
### Optional Tools
|
||||
### Required Tools
|
||||
|
||||
- **ffmpeg**: For media features (subtitles, thumbnails, heic)
|
||||
- **ffmpeg**: Required for video thumbnail generation
|
||||
- Ubuntu/Debian: `sudo apt-get install ffmpeg`
|
||||
- macOS: `brew install ffmpeg`
|
||||
- Windows: Download from [ffmpeg.org](https://ffmpeg.org/download.html)
|
||||
|
||||
- **mupdf-tools**: For PDF preview generation
|
||||
- **mupdf-tools**: Required for PDF preview generation
|
||||
- Ubuntu/Debian: `sudo apt-get install mupdf-tools`
|
||||
- macOS: `brew install mupdf-tools`
|
||||
- Windows: Download from [mupdf.com](https://mupdf.com/downloads/)
|
||||
@@ -31,8 +31,8 @@ cd filebrowser
|
||||
# 2. Initial setup - installs dependencies and creates test config
|
||||
make setup
|
||||
|
||||
# 3. Run in development mode with hot-reloading
|
||||
make dev
|
||||
# 3. Run in development mode
|
||||
make run
|
||||
```
|
||||
|
||||
`make setup` installs all dependencies and creates a test configuration file.
|
||||
@@ -52,14 +52,14 @@ make dev
|
||||
- **Framework**: Vue 3 + Vite + TypeScript
|
||||
- **State**: Custom store in `frontend/src/store/`
|
||||
- **API Client**: Axios-based in `frontend/src/api/`
|
||||
- **i18n**: 25+ languages with English as main
|
||||
- **i18n**: 25+ languages with English as master
|
||||
- **Components**: Feature-based organization
|
||||
|
||||
## Development Commands
|
||||
|
||||
### Essential Commands
|
||||
```bash
|
||||
make dev # Start development server with hot-reloading
|
||||
make run # Start development server
|
||||
make test # Run all tests
|
||||
make lint # Check code quality
|
||||
make check-all # Lint + tests
|
||||
@@ -70,12 +70,12 @@ make build # Build Docker image
|
||||
```
|
||||
|
||||
### Frontend Development
|
||||
|
||||
Note: consider using make commands above instead.
|
||||
```bash
|
||||
cd frontend
|
||||
npm run dev # Dev server with hot reload
|
||||
npm run lint:fix # Auto-fix linting issues
|
||||
npm run i18n:sync # Sync translations changes
|
||||
npm run typecheck # TypeScript validation
|
||||
npm run i18n:sync # Sync translations
|
||||
```
|
||||
|
||||
## Testing
|
||||
@@ -128,11 +128,12 @@ make build-backend # Build Go binary with embedded assets
|
||||
|
||||
### Docker
|
||||
```bash
|
||||
make build # Full image with ffmpeg and muPDF
|
||||
make build # Full image with ffmpeg (~200MB)
|
||||
make run-proxy # Docker Compose with nginx proxy
|
||||
```
|
||||
|
||||
### Configuration
|
||||
- **Test Config**: `backend/test_config.yaml` Auto-generated after running `make setup`
|
||||
- **Test Config**: `backend/test_config.yaml` Auto-generated after running `make setup`
|
||||
|
||||
## Contributing
|
||||
|
||||
@@ -140,7 +141,7 @@ make build # Full image with ffmpeg and muPDF
|
||||
|
||||
1. Fork and create a feature branch
|
||||
2. Make your changes following the code standards
|
||||
3. Run `make dev` to build and run with your changes. Supports hot-reloading frontend and backend changes.
|
||||
3. Run `make run` to build and run with your changes
|
||||
4. When ready, run `make check-all` to verify tests and linting
|
||||
5. Submit PR with a clear description
|
||||
|
||||
@@ -174,6 +175,16 @@ cd frontend && rm -rf node_modules && npm install
|
||||
cd backend && go mod tidy && go clean -modcache
|
||||
```
|
||||
|
||||
**Debugging:**
|
||||
```bash
|
||||
# Backend with debug logs
|
||||
cd backend
|
||||
FILEBROWSER_NO_EMBEDDED=true go run -tags mupdf . -c test_config.yaml --log-level debug
|
||||
|
||||
# API documentation
|
||||
# Visit /swagger endpoint when running
|
||||
```
|
||||
|
||||
**Authentication Issues:**
|
||||
|
||||
Always first enable debug logging in your config file if you have issues.
|
||||
|
||||
+1
-3
@@ -16,9 +16,6 @@ RUN upx filebrowser
|
||||
FROM node:lts-slim AS nbuild
|
||||
WORKDIR /app
|
||||
COPY ./frontend/package.json ./
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends git ca-certificates \
|
||||
&& git config --global url."https://github.com/".insteadOf "git@github.com:" \
|
||||
&& git config --global url."https://github.com/".insteadOf "ssh://git@github.com/"
|
||||
RUN npm i --maxsockets 1
|
||||
COPY ./frontend/ ./
|
||||
RUN npm run build-docker
|
||||
@@ -27,6 +24,7 @@ FROM alpine:latest
|
||||
COPY --from=ffmpeg [ "/ffmpeg", "/ffprobe", "/usr/local/bin/" ]
|
||||
ENV FILEBROWSER_FFMPEG_PATH="/usr/local/bin/"
|
||||
ENV FILEBROWSER_DATABASE="/home/filebrowser/data/database.db"
|
||||
ENV FILEBROWSER_NO_EMBEDED="true"
|
||||
ENV PATH="$PATH:/home/filebrowser"
|
||||
RUN apk --no-cache add ca-certificates mailcap tzdata
|
||||
RUN adduser -D -s /bin/true -u 1000 filebrowser
|
||||
|
||||
@@ -15,4 +15,4 @@ COPY [ "./backend/http/embed/", "./http/dist/"]
|
||||
|
||||
# Run tests
|
||||
RUN ./filebrowser set -u testuser1,testuser1
|
||||
RUN ./filebrowser & sleep 2 && cd ../frontend && npx playwright test
|
||||
RUN FILEBROWSER_NO_EMBEDED=true ./filebrowser & sleep 2 && cd ../frontend && npx playwright test
|
||||
@@ -13,4 +13,4 @@ RUN mkdir -p ./http/dist
|
||||
COPY [ "./backend/http/embed/", "./http/dist/"]
|
||||
|
||||
# Run tests
|
||||
RUN ./filebrowser & sleep 2 && cd ../frontend && npx playwright test
|
||||
RUN FILEBROWSER_NO_EMBEDED=true ./filebrowser & sleep 2 && cd ../frontend && npx playwright test
|
||||
@@ -10,4 +10,4 @@ COPY ["./backend/filebrowser", "./"]
|
||||
COPY [ "./backend/http/embed/", "./http/dist/"]
|
||||
# Run tests
|
||||
WORKDIR /app/frontend
|
||||
RUN node mock-oidc-server.js & sleep 2 && cd ../backend/ && ./filebrowser & sleep 2 && cd ../frontend && npx playwright test
|
||||
RUN node mock-oidc-server.js & sleep 2 && cd ../backend/ && FILEBROWSER_NO_EMBEDED=true ./filebrowser & sleep 2 && cd ../frontend && npx playwright test
|
||||
@@ -11,4 +11,4 @@ RUN apt update && apt install nginx -y
|
||||
RUN mv default.conf /etc/nginx/conf.d/default.conf
|
||||
RUN mv .htpasswd /etc/nginx/conf.d/.htpasswd
|
||||
RUN sed -i 's/filebrowser/localhost/g' /etc/nginx/conf.d/default.conf
|
||||
RUN nginx & ./filebrowser & sleep 2 && cd ../frontend && npx playwright test
|
||||
RUN nginx & FILEBROWSER_NO_EMBEDED=true ./filebrowser & sleep 2 && cd ../frontend && npx playwright test
|
||||
@@ -14,4 +14,4 @@ RUN mkdir -p ./http/dist
|
||||
COPY [ "./backend/http/embed/", "./http/dist/"]
|
||||
|
||||
# Run tests
|
||||
RUN ./filebrowser & sleep 2 && cd ../frontend && npx playwright test
|
||||
RUN FILEBROWSER_NO_EMBEDED=true ./filebrowser & sleep 2 && cd ../frontend && npx playwright test
|
||||
@@ -14,14 +14,12 @@ RUN upx filebrowser
|
||||
FROM node:lts-slim AS nbuild
|
||||
WORKDIR /app
|
||||
COPY ./frontend/package.json ./
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends git ca-certificates \
|
||||
&& git config --global url."https://github.com/".insteadOf "git@github.com:" \
|
||||
&& git config --global url."https://github.com/".insteadOf "ssh://git@github.com/"
|
||||
RUN npm i --maxsockets 1
|
||||
COPY ./frontend/ ./
|
||||
RUN npm run build-docker
|
||||
|
||||
FROM alpine:latest
|
||||
ENV FILEBROWSER_NO_EMBEDED="true"
|
||||
ENV FILEBROWSER_DATABASE="/home/filebrowser/data/database.db"
|
||||
ENV PATH="$PATH:/home/filebrowser"
|
||||
RUN apk --no-cache add ca-certificates mailcap tzdata
|
||||
|
||||
@@ -11,7 +11,7 @@ import { defineConfig, devices } from "@playwright/test";
|
||||
*/
|
||||
export default defineConfig({
|
||||
globalSetup: "./tests/playwright/proxy-setup.ts",
|
||||
timeout: 5000,
|
||||
timeout: 3000,
|
||||
testDir: "./tests/playwright/proxy",
|
||||
/* Run tests in files in parallel */
|
||||
fullyParallel: false,
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
root = "."
|
||||
tmp_dir = "tmp"
|
||||
|
||||
[build]
|
||||
cmd = "go build -o ./tmp/filebrowser --tags=mupdf --ldflags=\"-w -s -X 'github.com/gtsteffaniak/filebrowser/backend/version.CommitSHA=testingCommit' -X 'github.com/gtsteffaniak/filebrowser/backend/version.Version=testing'\" ."
|
||||
bin = "tmp/filebrowser"
|
||||
full_bin = "FILEBROWSER_DEVMODE=true CGO_ENABLED=1 ./tmp/filebrowser -c test_config.yaml"
|
||||
include_ext = ["go", "tpl", "tmpl", "html"]
|
||||
exclude_dir = ["tmp", "vendor", "http/dist", "http/embed", "swagger/docs"]
|
||||
log = "air.log"
|
||||
delay = 1000 # ms
|
||||
|
||||
[log]
|
||||
time = true
|
||||
|
||||
[misc]
|
||||
clean_on_exit = true
|
||||
@@ -1,5 +0,0 @@
|
||||
tmp/
|
||||
air.log
|
||||
filebrowser
|
||||
generated.yaml
|
||||
test_config.yaml
|
||||
@@ -28,16 +28,18 @@ import (
|
||||
"github.com/gtsteffaniak/go-logger/logger"
|
||||
)
|
||||
|
||||
func FileInfoFaster(opts iteminfo.FileOptions) (*iteminfo.ExtendedFileInfo, error) {
|
||||
response := &iteminfo.ExtendedFileInfo{}
|
||||
func FileInfoFaster(opts iteminfo.FileOptions) (iteminfo.ExtendedFileInfo, error) {
|
||||
response := iteminfo.ExtendedFileInfo{}
|
||||
index := indexing.GetIndex(opts.Source)
|
||||
if index == nil {
|
||||
return response, fmt.Errorf("could not get index: %v ", opts.Source)
|
||||
}
|
||||
|
||||
if opts.Access != nil && !opts.Access.Permitted(index.Path, opts.Path, opts.Username) {
|
||||
return response, errors.ErrPermissionDenied
|
||||
}
|
||||
realPath, isDir, err := index.GetRealPath(opts.Path)
|
||||
if err != nil {
|
||||
return response, fmt.Errorf("could not get real path for requested path: %v", opts.Path)
|
||||
return response, err
|
||||
}
|
||||
opts.IsDir = isDir
|
||||
var info *iteminfo.FileInfo
|
||||
@@ -58,69 +60,31 @@ func FileInfoFaster(opts iteminfo.FileOptions) (*iteminfo.ExtendedFileInfo, erro
|
||||
return response, fmt.Errorf("could not get metadata for path: %v", opts.Path)
|
||||
}
|
||||
}
|
||||
if opts.Content {
|
||||
if info.Size < 20*1024*1024 { // 20 megabytes in bytes
|
||||
content, err := getContent(realPath)
|
||||
if err != nil {
|
||||
logger.Debugf("could not get content for file: "+info.Path, info.Name, err)
|
||||
return response, err
|
||||
}
|
||||
response.Content = content
|
||||
} else {
|
||||
logger.Debug("skipping large text file contents (20MB limit): "+info.Path, info.Name)
|
||||
}
|
||||
}
|
||||
response.FileInfo = *info
|
||||
response.RealPath = realPath
|
||||
response.Source = opts.Source
|
||||
|
||||
if opts.Access != nil && !opts.Access.Permitted(index.Path, opts.Path, opts.Username) {
|
||||
// check if any subpath is permitted
|
||||
// keep track of permitted paths and only show them at the end
|
||||
subFolders := info.Folders
|
||||
subFiles := info.Files
|
||||
response.Folders = make([]iteminfo.ItemInfo, 0)
|
||||
response.Files = make([]iteminfo.ItemInfo, 0)
|
||||
hasPermittedPaths := false
|
||||
for _, subFolder := range subFolders {
|
||||
indexPath := info.Path + subFolder.Name
|
||||
if opts.Access.Permitted(index.Path, indexPath, opts.Username) {
|
||||
hasPermittedPaths = true
|
||||
response.Folders = append(response.Folders, subFolder)
|
||||
}
|
||||
}
|
||||
for _, subFile := range subFiles {
|
||||
indexPath := info.Path + subFile.Name
|
||||
if opts.Access.Permitted(index.Path, indexPath, opts.Username) {
|
||||
hasPermittedPaths = true
|
||||
response.Files = append(response.Files, subFile)
|
||||
}
|
||||
}
|
||||
if !hasPermittedPaths {
|
||||
return response, errors.ErrPermissionDenied
|
||||
}
|
||||
}
|
||||
if opts.Content {
|
||||
processContent(response, index)
|
||||
}
|
||||
response.Source = index.Name
|
||||
if settings.Config.Integrations.OnlyOffice.Secret != "" && info.Type != "directory" && iteminfo.IsOnlyOffice(info.Name) {
|
||||
response.OnlyOfficeId = generateOfficeId(realPath)
|
||||
}
|
||||
|
||||
return response, nil
|
||||
}
|
||||
|
||||
func processContent(info *iteminfo.ExtendedFileInfo, idx *indexing.Index) {
|
||||
isVideo := strings.HasPrefix(info.Type, "video")
|
||||
if isVideo {
|
||||
parentInfo, exists := idx.GetReducedMetadata(filepath.Dir(info.Path), true)
|
||||
if strings.HasPrefix(info.Type, "video") {
|
||||
parentInfo, exists := index.GetReducedMetadata(filepath.Dir(info.Path), true)
|
||||
if exists {
|
||||
info.DetectSubtitles(parentInfo)
|
||||
err := info.LoadSubtitleContent()
|
||||
if err != nil {
|
||||
logger.Debug("failed to load subtitle content: " + err.Error())
|
||||
}
|
||||
response.DetectSubtitles(parentInfo)
|
||||
}
|
||||
return
|
||||
}
|
||||
if info.Size < 20*1024*1024 { // 20 megabytes in bytes
|
||||
content, err := getContent(info.RealPath)
|
||||
if err != nil {
|
||||
logger.Debugf("could not get content for file: "+info.RealPath, info.Name, err)
|
||||
return
|
||||
}
|
||||
info.Content = content
|
||||
} else {
|
||||
logger.Debug("skipping large text file contents (20MB limit): "+info.Path, info.Name)
|
||||
}
|
||||
return response, nil
|
||||
}
|
||||
|
||||
func generateOfficeId(realPath string) string {
|
||||
|
||||
+5
-7
@@ -54,14 +54,12 @@ func StartFilebrowser() {
|
||||
if !keepGoing {
|
||||
return
|
||||
}
|
||||
if !settings.Config.Server.DisableUpdateCheck {
|
||||
info, _ := utils.CheckForUpdates()
|
||||
if info.LatestVersion != "" {
|
||||
logger.Infof("A new version is available: %s (current: %s)", info.LatestVersion, info.CurrentVersion)
|
||||
logger.Infof("Release notes: %s", info.ReleaseNotes)
|
||||
}
|
||||
go utils.StartCheckForUpdates()
|
||||
info, _ := utils.CheckForUpdates()
|
||||
if info.LatestVersion != "" {
|
||||
logger.Infof("A new version is available: %s (current: %s)", info.LatestVersion, info.CurrentVersion)
|
||||
logger.Infof("Release notes: %s", info.ReleaseNotes)
|
||||
}
|
||||
go utils.StartCheckForUpdates()
|
||||
|
||||
// Create context and channels for graceful shutdown
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
|
||||
@@ -12,12 +12,12 @@ import (
|
||||
)
|
||||
|
||||
type Auth struct {
|
||||
TokenExpirationHours int `json:"tokenExpirationHours"` // time in hours each web UI session token is valid for. Default is 2 hours.
|
||||
TokenExpirationHours int `json:"tokenExpirationHours"` // the number of hours until the token expires. Default is 2 hours.
|
||||
Methods LoginMethods `json:"methods"`
|
||||
Key string `json:"key"` // secret: the key used to sign the JWT tokens. If not set, a random key will be generated.
|
||||
AdminUsername string `json:"adminUsername"` // secret: the username of the admin user. If not set, the default is "admin".
|
||||
AdminPassword string `json:"adminPassword"` // secret: the password of the admin user. If not set, the default is "admin".
|
||||
TotpSecret string `json:"totpSecret"` // secret: secret used to encrypt TOTP secrets
|
||||
Key string `json:"key"` // the key used to sign the JWT tokens. If not set, a random key will be generated.
|
||||
AdminUsername string `json:"adminUsername"` // the username of the admin user. If not set, the default is "admin".
|
||||
AdminPassword string `json:"adminPassword"` // the password of the admin user. If not set, the default is "admin".
|
||||
TotpSecret string `json:"totpSecret"` // secret used to encrypt TOTP secrets
|
||||
AuthMethods []string `json:"-"`
|
||||
}
|
||||
|
||||
@@ -52,8 +52,8 @@ type Recaptcha struct {
|
||||
// OpenID OAuth2.0
|
||||
type OidcConfig struct {
|
||||
Enabled bool `json:"enabled"` // whether to enable OIDC authentication
|
||||
ClientID string `json:"clientId"` // secret: client id of the OIDC application
|
||||
ClientSecret string `json:"clientSecret"` // secret: client secret of the OIDC application
|
||||
ClientID string `json:"clientId"` // client id of the OIDC application
|
||||
ClientSecret string `json:"clientSecret"` // client secret of the OIDC application
|
||||
IssuerUrl string `json:"issuerUrl"` // authorization URL of the OIDC provider
|
||||
Scopes string `json:"scopes"` // scopes to request from the OIDC provider
|
||||
UserIdentifier string `json:"userIdentifier"` // the field value to use as the username. Default is "preferred_username", can also be "email" or "username", or "phone"
|
||||
|
||||
@@ -48,9 +48,6 @@ func Initialize(configFile string) {
|
||||
}
|
||||
|
||||
func setupFrontend(generate bool) {
|
||||
if Config.Server.MinSearchLength == 0 {
|
||||
Config.Server.MinSearchLength = 3
|
||||
}
|
||||
if !Config.Frontend.DisableDefaultLinks {
|
||||
Config.Frontend.ExternalLinks = append(Config.Frontend.ExternalLinks, ExternalLink{
|
||||
Text: fmt.Sprintf("(%v)", version.Version),
|
||||
@@ -59,15 +56,12 @@ func setupFrontend(generate bool) {
|
||||
})
|
||||
Config.Frontend.ExternalLinks = append(Config.Frontend.ExternalLinks, ExternalLink{
|
||||
Text: "Help",
|
||||
Url: "help prompt",
|
||||
Url: "https://github.com/gtsteffaniak/filebrowser/wiki",
|
||||
})
|
||||
}
|
||||
if Config.Frontend.Description == "" {
|
||||
Config.Frontend.Description = "FileBrowser Quantum is a file manager for the web which can be used to manage files on your server"
|
||||
}
|
||||
Config.Frontend.Styling.LightBackground = FallbackColor(Config.Frontend.Styling.LightBackground, "#f5f5f5")
|
||||
Config.Frontend.Styling.DarkBackground = FallbackColor(Config.Frontend.Styling.DarkBackground, "#141D24")
|
||||
Config.Frontend.Styling.CustomCSSRaw = readCustomCSS(Config.Frontend.Styling.CustomCSS)
|
||||
Config.Frontend.Styling.CustomCSS = readCustomCSS(Config.Frontend.Styling.CustomCSS)
|
||||
Config.Frontend.Styling.CustomThemeOptions = map[string]CustomTheme{}
|
||||
Config.Frontend.Styling.CustomThemes = map[string]CustomTheme{}
|
||||
for name, theme := range Config.Frontend.Styling.CustomThemes {
|
||||
@@ -91,9 +85,6 @@ func setupFrontend(generate bool) {
|
||||
if !ok {
|
||||
addCustomTheme("default", "The default theme", "")
|
||||
}
|
||||
|
||||
// Load custom favicon if configured
|
||||
loadCustomFavicon()
|
||||
}
|
||||
|
||||
func getRealPath(path string) string {
|
||||
@@ -234,14 +225,8 @@ func setupLogging() {
|
||||
}
|
||||
}
|
||||
for _, logConfig := range Config.Server.Logging {
|
||||
// Enable debug logging automatically in dev mode
|
||||
levels := logConfig.Levels
|
||||
if os.Getenv("FILEBROWSER_DEVMODE") == "true" {
|
||||
levels = "info|warning|error|debug"
|
||||
}
|
||||
|
||||
logConfig := logger.JsonConfig{
|
||||
Levels: levels,
|
||||
Levels: logConfig.Levels,
|
||||
ApiLevels: logConfig.ApiLevels,
|
||||
Output: logConfig.Output,
|
||||
Utc: logConfig.Utc,
|
||||
@@ -520,54 +505,6 @@ func GetSources(u *users.User) []string {
|
||||
return sources
|
||||
}
|
||||
|
||||
func loadCustomFavicon() {
|
||||
// Check if a custom favicon path is configured
|
||||
if Config.Frontend.Favicon == "" {
|
||||
logger.Debug("No custom favicon configured, using default")
|
||||
return
|
||||
}
|
||||
|
||||
// Get absolute path for the favicon
|
||||
faviconPath, err := filepath.Abs(Config.Frontend.Favicon)
|
||||
if err != nil {
|
||||
logger.Warningf("Could not resolve favicon path '%v': %v", Config.Frontend.Favicon, err)
|
||||
Config.Frontend.Favicon = "" // Unset invalid path
|
||||
return
|
||||
}
|
||||
|
||||
// Check if the favicon file exists and get info
|
||||
stat, err := os.Stat(faviconPath)
|
||||
if err != nil {
|
||||
logger.Warningf("Could not access custom favicon file '%v': %v", faviconPath, err)
|
||||
Config.Frontend.Favicon = "" // Unset invalid path
|
||||
return
|
||||
}
|
||||
|
||||
// Check file size (limit to 1MB for security)
|
||||
const maxFaviconSize = 1024 * 1024 // 1MB
|
||||
if stat.Size() > maxFaviconSize {
|
||||
logger.Warningf("Favicon file '%v' is too large (%d bytes), maximum allowed is %d bytes", faviconPath, stat.Size(), maxFaviconSize)
|
||||
Config.Frontend.Favicon = "" // Unset invalid path
|
||||
return
|
||||
}
|
||||
|
||||
// Validate file format based on extension
|
||||
ext := strings.ToLower(filepath.Ext(faviconPath))
|
||||
switch ext {
|
||||
case ".ico", ".png", ".jpg", ".jpeg", ".gif", ".svg", ".webp":
|
||||
// Valid favicon formats
|
||||
default:
|
||||
logger.Warningf("Unsupported favicon format '%v', supported formats: .ico, .png, .jpg, .gif, .svg, .webp", ext)
|
||||
Config.Frontend.Favicon = "" // Unset invalid path
|
||||
return
|
||||
}
|
||||
|
||||
// Update to absolute path and mark as valid
|
||||
Config.Frontend.Favicon = faviconPath
|
||||
|
||||
logger.Infof("Successfully validated custom favicon at '%v' (%d bytes, %s)", faviconPath, stat.Size(), ext)
|
||||
}
|
||||
|
||||
func modifyExcludeInclude(config *Source) {
|
||||
normalize := func(s []string, checkExists bool) {
|
||||
for i, v := range s {
|
||||
|
||||
@@ -14,44 +14,19 @@ import (
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
// CommentsMap[typeName][fieldName] = combined doc+inline comment text
|
||||
type CommentsMap map[string]map[string]string
|
||||
// commentsMap[typeName][fieldName] = combined doc+inline comment text
|
||||
type commentsMap map[string]map[string]string
|
||||
|
||||
// SecretFieldsMap[typeName][fieldName] = true if field should be redacted
|
||||
type SecretFieldsMap map[string]map[string]bool
|
||||
|
||||
// DeprecatedFieldsMap[typeName][fieldName] = true if field is deprecated
|
||||
type DeprecatedFieldsMap map[string]map[string]bool
|
||||
|
||||
// getStringStyle determines whether a string should be quoted in YAML
|
||||
func getStringStyle(value string) yaml.Style {
|
||||
// Always quote all strings for consistency
|
||||
return yaml.DoubleQuotedStyle
|
||||
}
|
||||
|
||||
// CollectComments parses all Go source in the directory of srcPath and returns CommentsMap.
|
||||
func CollectComments(srcPath string) (CommentsMap, error) {
|
||||
comments, _, _, err := CollectCommentsAndSecrets(srcPath)
|
||||
return comments, err
|
||||
}
|
||||
|
||||
// CollectCommentsAndSecrets parses all Go source and returns comments, secrets, and deprecated field mappings
|
||||
func CollectCommentsAndSecrets(srcPath string) (CommentsMap, SecretFieldsMap, DeprecatedFieldsMap, error) {
|
||||
// collectComments parses all Go source in the directory of srcPath and returns commentsMap.
|
||||
func collectComments(srcPath string) (commentsMap, error) {
|
||||
// parse entire package so we capture comments on all types
|
||||
dir := srcPath
|
||||
if filepath.IsAbs(srcPath) {
|
||||
// If it's an absolute path to a file, get the directory
|
||||
dir = filepath.Dir(srcPath)
|
||||
}
|
||||
|
||||
dir := filepath.Dir(srcPath)
|
||||
fset := token.NewFileSet()
|
||||
pkgs, err := parser.ParseDir(fset, dir, nil, parser.ParseComments)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
return nil, err
|
||||
}
|
||||
comments := make(CommentsMap)
|
||||
secrets := make(SecretFieldsMap)
|
||||
deprecated := make(DeprecatedFieldsMap)
|
||||
out := make(commentsMap)
|
||||
for _, pkg := range pkgs {
|
||||
for _, file := range pkg.Files {
|
||||
for _, decl := range file.Decls {
|
||||
@@ -68,81 +43,48 @@ func CollectCommentsAndSecrets(srcPath string) (CommentsMap, SecretFieldsMap, De
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
typeName := ts.Name.Name
|
||||
commentMap := make(map[string]string)
|
||||
secretMap := make(map[string]bool)
|
||||
deprecatedMap := make(map[string]bool)
|
||||
comments[typeName] = commentMap
|
||||
secrets[typeName] = secretMap
|
||||
deprecated[typeName] = deprecatedMap
|
||||
|
||||
m := make(map[string]string)
|
||||
out[ts.Name.Name] = m
|
||||
for _, field := range st.Fields.List {
|
||||
if len(field.Names) == 0 {
|
||||
continue
|
||||
}
|
||||
name := field.Names[0].Name
|
||||
var parts []string
|
||||
var fullComment string
|
||||
|
||||
if field.Doc != nil {
|
||||
docText := strings.TrimSpace(field.Doc.Text())
|
||||
parts = append(parts, docText)
|
||||
fullComment += docText + " "
|
||||
parts = append(parts, strings.TrimSpace(field.Doc.Text()))
|
||||
}
|
||||
if field.Comment != nil {
|
||||
commentText := strings.TrimSpace(field.Comment.Text())
|
||||
parts = append(parts, commentText)
|
||||
fullComment += commentText
|
||||
parts = append(parts, strings.TrimSpace(field.Comment.Text()))
|
||||
}
|
||||
|
||||
if len(parts) > 0 {
|
||||
commentMap[name] = strings.Join(parts, " : ")
|
||||
}
|
||||
|
||||
// Check if field should be treated as secret
|
||||
if strings.Contains(strings.ToLower(fullComment), "secret:") {
|
||||
secretMap[name] = true
|
||||
}
|
||||
|
||||
// Check if field should be treated as deprecated
|
||||
if strings.Contains(strings.ToLower(fullComment), "deprecated:") {
|
||||
deprecatedMap[name] = true
|
||||
m[name] = strings.Join(parts, " : ")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return comments, secrets, deprecated, nil
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// BuildNode constructs a yaml.Node for any Go value, injecting comments on struct fields.
|
||||
func BuildNode(v reflect.Value, comm CommentsMap) (*yaml.Node, error) {
|
||||
return buildNodeWithDefaults(v, comm, reflect.Value{}, SecretFieldsMap{}, DeprecatedFieldsMap{})
|
||||
}
|
||||
|
||||
// buildNodeWithDefaults constructs a yaml.Node for any Go value, skipping fields that match defaults, redacting secrets, and filtering deprecated fields
|
||||
func buildNodeWithDefaults(v reflect.Value, comm CommentsMap, defaults reflect.Value, secrets SecretFieldsMap, deprecated DeprecatedFieldsMap) (*yaml.Node, error) {
|
||||
// buildNode constructs a yaml.Node for any Go value, injecting comments on struct fields.
|
||||
func buildNode(v reflect.Value, comm commentsMap) (*yaml.Node, error) {
|
||||
// Dereference pointers
|
||||
if v.Kind() == reflect.Ptr {
|
||||
if v.IsNil() {
|
||||
return &yaml.Node{Kind: yaml.ScalarNode, Tag: "!!null", Value: "null"}, nil
|
||||
}
|
||||
var defaultsElem reflect.Value
|
||||
if defaults.IsValid() && !defaults.IsNil() {
|
||||
defaultsElem = defaults.Elem()
|
||||
}
|
||||
return buildNodeWithDefaults(v.Elem(), comm, defaultsElem, secrets, deprecated)
|
||||
return buildNode(v.Elem(), comm)
|
||||
}
|
||||
|
||||
switch v.Kind() {
|
||||
case reflect.String:
|
||||
value := v.String()
|
||||
return &yaml.Node{
|
||||
Kind: yaml.ScalarNode,
|
||||
Tag: "!!str",
|
||||
Value: value,
|
||||
Style: getStringStyle(value),
|
||||
Value: v.String(),
|
||||
Style: yaml.DoubleQuotedStyle,
|
||||
}, nil
|
||||
case reflect.Struct:
|
||||
rt := v.Type()
|
||||
@@ -162,26 +104,6 @@ func buildNodeWithDefaults(v reflect.Value, comm CommentsMap, defaults reflect.V
|
||||
continue
|
||||
}
|
||||
|
||||
// Skip deprecated fields if filtering is enabled
|
||||
if len(deprecated) > 0 && deprecated[typeName][sf.Name] {
|
||||
continue
|
||||
}
|
||||
|
||||
currentField := v.Field(i)
|
||||
|
||||
// If we have defaults, compare and skip if values match
|
||||
if defaults.IsValid() && i < defaults.NumField() {
|
||||
defaultField := defaults.Field(i)
|
||||
currentValue := currentField.Interface()
|
||||
defaultValue := defaultField.Interface()
|
||||
|
||||
isEqual := reflect.DeepEqual(currentValue, defaultValue)
|
||||
|
||||
if isEqual {
|
||||
continue // Skip this field as it matches the default
|
||||
}
|
||||
}
|
||||
|
||||
// determine key: yaml tag > json tag > field name
|
||||
yamlTag := sf.Tag.Get("yaml")
|
||||
jsonTag := sf.Tag.Get("json")
|
||||
@@ -196,43 +118,22 @@ func buildNodeWithDefaults(v reflect.Value, comm CommentsMap, defaults reflect.V
|
||||
|
||||
keyNode := &yaml.Node{Kind: yaml.ScalarNode, Value: keyName}
|
||||
|
||||
// attach validate and comments inline (only when comments are enabled)
|
||||
// attach validate and comments inline
|
||||
var parts []string
|
||||
if cm := comm[typeName][sf.Name]; cm != "" {
|
||||
parts = append(parts, cm)
|
||||
}
|
||||
// Only add validation tags if comments map is not empty (meaning comments are enabled)
|
||||
if len(comm) > 0 {
|
||||
if vt := sf.Tag.Get("validate"); vt != "" {
|
||||
parts = append(parts, fmt.Sprintf(" validate:%s", vt))
|
||||
}
|
||||
if vt := sf.Tag.Get("validate"); vt != "" {
|
||||
parts = append(parts, fmt.Sprintf(" validate:%s", vt))
|
||||
}
|
||||
|
||||
if len(parts) > 0 {
|
||||
keyNode.LineComment = strings.Join(parts, " ")
|
||||
}
|
||||
|
||||
// Check if this field should be redacted as a secret
|
||||
var valNode *yaml.Node
|
||||
var err error
|
||||
if secrets[typeName][sf.Name] {
|
||||
valNode = &yaml.Node{
|
||||
Kind: yaml.ScalarNode,
|
||||
Tag: "!!str",
|
||||
Value: "**hidden**",
|
||||
Style: yaml.DoubleQuotedStyle, // Keep secrets quoted for clarity
|
||||
}
|
||||
} else {
|
||||
// Pass through the corresponding default field for recursive comparison
|
||||
var defaultField reflect.Value
|
||||
if defaults.IsValid() && i < defaults.NumField() {
|
||||
defaultField = defaults.Field(i)
|
||||
}
|
||||
|
||||
valNode, err = buildNodeWithDefaults(currentField, comm, defaultField, secrets, deprecated)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
valNode, err := buildNode(v.Field(i), comm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
mapNode.Content = append(mapNode.Content, keyNode, valNode)
|
||||
}
|
||||
@@ -247,11 +148,7 @@ func buildNodeWithDefaults(v reflect.Value, comm CommentsMap, defaults reflect.V
|
||||
// placeholder for empty slice of structs
|
||||
if v.Len() == 0 && v.Type().Elem().Kind() == reflect.Struct {
|
||||
zero := reflect.Zero(v.Type().Elem())
|
||||
var defaultElem reflect.Value
|
||||
if defaults.IsValid() && defaults.Len() > 0 {
|
||||
defaultElem = defaults.Index(0)
|
||||
}
|
||||
n, err := buildNodeWithDefaults(zero, comm, defaultElem, secrets, deprecated)
|
||||
n, err := buildNode(zero, comm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -259,11 +156,7 @@ func buildNodeWithDefaults(v reflect.Value, comm CommentsMap, defaults reflect.V
|
||||
return seq, nil
|
||||
}
|
||||
for i := 0; i < v.Len(); i++ {
|
||||
var defaultElem reflect.Value
|
||||
if defaults.IsValid() && i < defaults.Len() {
|
||||
defaultElem = defaults.Index(i)
|
||||
}
|
||||
n, err := buildNodeWithDefaults(v.Index(i), comm, defaultElem, secrets, deprecated)
|
||||
n, err := buildNode(v.Index(i), comm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -271,33 +164,6 @@ func buildNodeWithDefaults(v reflect.Value, comm CommentsMap, defaults reflect.V
|
||||
}
|
||||
return seq, nil
|
||||
|
||||
case reflect.Map:
|
||||
mapNode := &yaml.Node{Kind: yaml.MappingNode}
|
||||
|
||||
for _, key := range v.MapKeys() {
|
||||
// Handle key
|
||||
keyStr := fmt.Sprintf("%v", key.Interface())
|
||||
keyNode := &yaml.Node{
|
||||
Kind: yaml.ScalarNode,
|
||||
Value: keyStr,
|
||||
}
|
||||
|
||||
// Handle value recursively
|
||||
mapVal := v.MapIndex(key)
|
||||
var defaultVal reflect.Value
|
||||
if defaults.IsValid() {
|
||||
defaultVal = defaults.MapIndex(key)
|
||||
}
|
||||
|
||||
valNode, err := buildNodeWithDefaults(mapVal, comm, defaultVal, secrets, deprecated)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
mapNode.Content = append(mapNode.Content, keyNode, valNode)
|
||||
}
|
||||
return mapNode, nil
|
||||
|
||||
default:
|
||||
n := &yaml.Node{}
|
||||
if err := n.Encode(v.Interface()); err != nil {
|
||||
@@ -320,152 +186,39 @@ func GenerateYaml() {
|
||||
setupSources(true)
|
||||
setupUrls()
|
||||
setupFrontend(true)
|
||||
output := "generated.yaml" // "output YAML file"
|
||||
input := "common/settings/settings.go" // "path to Go source file or directory containing structs"
|
||||
output := "generated.yaml" // "output YAML file"
|
||||
|
||||
// Generate YAML with comments enabled, full config, and deprecated fields filtered
|
||||
// Force the source path to be correct for static generation
|
||||
yamlContent, err := GenerateConfigYamlWithSource(&Config, true, true, true, "common/settings")
|
||||
comm, err := collectComments(input)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "error generating YAML: %v\n", err)
|
||||
fmt.Fprintf(os.Stderr, "error parsing comments: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if err := os.WriteFile(output, []byte(yamlContent), 0644); err != nil {
|
||||
node, err := buildNode(reflect.ValueOf(Config), comm)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "error building YAML node: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
doc := &yaml.Node{Kind: yaml.DocumentNode}
|
||||
doc.Content = []*yaml.Node{node}
|
||||
|
||||
var rawBuf bytes.Buffer
|
||||
enc := yaml.NewEncoder(&rawBuf)
|
||||
enc.SetIndent(2)
|
||||
if err := enc.Encode(doc); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "error encoding YAML: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
aligned := alignComments(rawBuf.String())
|
||||
|
||||
if err := os.WriteFile(output, []byte(aligned), 0644); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "error writing YAML: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Printf("Generated YAML with comments (deprecated fields filtered): %s\n", output)
|
||||
}
|
||||
|
||||
// GenerateConfigYaml generates YAML from a given config with options for comments and filtering
|
||||
func GenerateConfigYaml(config *Settings, showComments bool, showFull bool, filterDeprecated bool) (string, error) {
|
||||
// Try different source paths to handle both runtime and test scenarios
|
||||
sourcePaths := []string{
|
||||
"common/settings", // When running from backend directory
|
||||
".", // When running tests from settings directory
|
||||
"../settings", // Alternative test path
|
||||
}
|
||||
|
||||
for _, sourcePath := range sourcePaths {
|
||||
yamlOutput, err := GenerateConfigYamlWithSource(config, showComments, showFull, filterDeprecated, sourcePath)
|
||||
if err == nil {
|
||||
return yamlOutput, nil
|
||||
}
|
||||
// If it's not a file not found error, return immediately
|
||||
if !strings.Contains(err.Error(), "no such file or directory") && !strings.Contains(err.Error(), "cannot find") {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
// If all paths failed, try with empty maps to at least generate basic YAML
|
||||
return GenerateConfigYamlWithEmptyMaps(config, showFull)
|
||||
}
|
||||
|
||||
// GenerateConfigYamlWithEmptyMaps generates YAML without comment parsing when source files are unavailable
|
||||
func GenerateConfigYamlWithEmptyMaps(config *Settings, showFull bool) (string, error) {
|
||||
// Create empty maps
|
||||
comm := make(CommentsMap)
|
||||
secrets := make(SecretFieldsMap)
|
||||
deprecated := make(DeprecatedFieldsMap)
|
||||
|
||||
var node *yaml.Node
|
||||
var err error
|
||||
|
||||
if showFull {
|
||||
// Show the full current config
|
||||
node, err = buildNodeWithDefaults(reflect.ValueOf(config), comm, reflect.Value{}, secrets, deprecated)
|
||||
} else {
|
||||
// Show only non-default values by comparing with defaults during node building
|
||||
defaultConfig := setDefaults(true)
|
||||
defaultConfig.Server.Sources = []Source{{Path: "."}}
|
||||
|
||||
node, err = buildNodeWithDefaults(reflect.ValueOf(config), comm, reflect.ValueOf(&defaultConfig), secrets, deprecated)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Convert to YAML
|
||||
doc := &yaml.Node{Kind: yaml.DocumentNode}
|
||||
doc.Content = []*yaml.Node{node}
|
||||
|
||||
var rawBuf bytes.Buffer
|
||||
enc := yaml.NewEncoder(&rawBuf)
|
||||
enc.SetIndent(2)
|
||||
if err := enc.Encode(doc); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return AlignComments(rawBuf.String()), nil
|
||||
}
|
||||
|
||||
// GenerateConfigYamlWithSource generates YAML from a given config with options for comments and filtering, using a custom source path
|
||||
func GenerateConfigYamlWithSource(config *Settings, showComments bool, showFull bool, filterDeprecated bool, sourcePath string) (string, error) {
|
||||
var comm CommentsMap
|
||||
var secrets SecretFieldsMap
|
||||
var deprecated DeprecatedFieldsMap
|
||||
var err error
|
||||
|
||||
if showComments {
|
||||
// Collect comments, secrets, and deprecated fields from the settings source files
|
||||
comm, secrets, deprecated, err = CollectCommentsAndSecrets(sourcePath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
} else {
|
||||
// Still need to collect secrets and deprecated fields even if not showing comments
|
||||
_, secrets, deprecated, err = CollectCommentsAndSecrets(sourcePath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
// Create empty comments map
|
||||
comm = make(CommentsMap)
|
||||
}
|
||||
|
||||
// If not filtering deprecated fields, clear the deprecated map
|
||||
if !filterDeprecated {
|
||||
deprecated = make(DeprecatedFieldsMap)
|
||||
}
|
||||
|
||||
var node *yaml.Node
|
||||
|
||||
if showFull {
|
||||
// Show the full current config
|
||||
node, err = buildNodeWithDefaults(reflect.ValueOf(config), comm, reflect.Value{}, secrets, deprecated)
|
||||
} else {
|
||||
// Show only non-default values by comparing with defaults during node building
|
||||
// Create a clean default config (no file loading, just pure defaults)
|
||||
defaultConfig := setDefaults(true)
|
||||
// Apply same setup as a fresh instance would have
|
||||
defaultConfig.Server.Sources = []Source{{Path: "."}}
|
||||
|
||||
node, err = buildNodeWithDefaults(reflect.ValueOf(config), comm, reflect.ValueOf(&defaultConfig), secrets, deprecated)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Create document
|
||||
doc := &yaml.Node{Kind: yaml.DocumentNode}
|
||||
doc.Content = []*yaml.Node{node}
|
||||
|
||||
// Encode to YAML
|
||||
var rawBuf bytes.Buffer
|
||||
enc := yaml.NewEncoder(&rawBuf)
|
||||
enc.SetIndent(2)
|
||||
if err := enc.Encode(doc); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Apply comment alignment if comments are enabled
|
||||
yamlOutput := rawBuf.String()
|
||||
if showComments {
|
||||
yamlOutput = AlignComments(yamlOutput)
|
||||
}
|
||||
|
||||
return yamlOutput, nil
|
||||
fmt.Printf("Generated YAML with comments: %s\n", output)
|
||||
}
|
||||
|
||||
// formatLine applies padding to a single line so its comment starts at a target column.
|
||||
@@ -504,8 +257,8 @@ func formatLine(line string) string {
|
||||
return line
|
||||
}
|
||||
|
||||
// AlignComments formats each line of the YAML string independently.
|
||||
func AlignComments(input string) string {
|
||||
// alignComments formats each line of the YAML string independently.
|
||||
func alignComments(input string) string {
|
||||
lines := strings.Split(input, "\n")
|
||||
outputLines := make([]string, len(lines))
|
||||
|
||||
|
||||
@@ -1,230 +0,0 @@
|
||||
package settings
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestGenerateConfigYaml_Basic(t *testing.T) {
|
||||
reNumber := regexp.MustCompile(`^-?\d+(\.\d+)?$`)
|
||||
// Test using the actual source directory structure
|
||||
config := &Settings{
|
||||
UserDefaults: UserDefaults{
|
||||
Locale: "es", // Non-default value
|
||||
DarkMode: true, // Default value
|
||||
DisableOfficePreviewExt: ".docx .xlsx", // This field is deprecated
|
||||
},
|
||||
Auth: Auth{
|
||||
Key: "secret123", // This is a secret field
|
||||
AdminUsername: "testadmin", // This is a secret field
|
||||
AdminPassword: "testpassword", // This is a secret field
|
||||
},
|
||||
}
|
||||
|
||||
// Test different combinations
|
||||
testCases := []struct {
|
||||
name string
|
||||
showComments bool
|
||||
showFull bool
|
||||
filterDeprecated bool
|
||||
expectSecrets bool
|
||||
expectDeprecated bool
|
||||
}{
|
||||
{
|
||||
name: "API_mode_all_fields",
|
||||
showComments: false,
|
||||
showFull: true,
|
||||
filterDeprecated: false,
|
||||
expectSecrets: true, // Secrets should be hidden
|
||||
expectDeprecated: true, // Deprecated should be shown
|
||||
},
|
||||
{
|
||||
name: "Static_config_filter_deprecated",
|
||||
showComments: true,
|
||||
showFull: true,
|
||||
filterDeprecated: true,
|
||||
expectSecrets: true, // Secrets should be hidden
|
||||
expectDeprecated: false, // Deprecated should be filtered
|
||||
},
|
||||
{
|
||||
name: "Filtered_non_defaults",
|
||||
showComments: false,
|
||||
showFull: false,
|
||||
filterDeprecated: false,
|
||||
expectSecrets: true, // Secrets should be hidden
|
||||
expectDeprecated: false, // Might not appear if matches default
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
// Use the current directory structure for real testing
|
||||
yamlOutput, err := GenerateConfigYaml(config, tc.showComments, tc.showFull, tc.filterDeprecated)
|
||||
if err != nil {
|
||||
t.Fatalf("GenerateConfigYaml failed: %v", err)
|
||||
}
|
||||
|
||||
// Basic validation - should produce some output
|
||||
if len(yamlOutput) == 0 {
|
||||
t.Fatal("Generated empty YAML")
|
||||
}
|
||||
|
||||
// Test string quoting - string values should be quoted, numbers and booleans should not
|
||||
lines := strings.Split(yamlOutput, "\n")
|
||||
for _, line := range lines {
|
||||
if strings.Contains(line, ":") && !strings.HasSuffix(strings.TrimSpace(line), ":") {
|
||||
// This is a key-value line, skip comment portion
|
||||
parts := strings.SplitN(line, ":", 2)
|
||||
if len(parts) == 2 {
|
||||
value := strings.TrimSpace(parts[1])
|
||||
// Remove comments
|
||||
if commentIdx := strings.Index(value, "#"); commentIdx >= 0 {
|
||||
value = strings.TrimSpace(value[:commentIdx])
|
||||
}
|
||||
|
||||
// Skip empty values, arrays, and objects
|
||||
if value == "" || strings.HasPrefix(value, "[") || strings.HasPrefix(value, "{") {
|
||||
continue
|
||||
}
|
||||
|
||||
// Check if it's a number (should NOT be quoted)
|
||||
if reNumber.MatchString(value) {
|
||||
if strings.HasPrefix(value, "\"") {
|
||||
t.Errorf("Numeric value should not be quoted in line: %s (value: '%s')", line, value)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// Check if it's a boolean (should NOT be quoted)
|
||||
if value == "true" || value == "false" {
|
||||
if strings.HasPrefix(value, "\"") {
|
||||
t.Errorf("Boolean value should not be quoted in line: %s (value: '%s')", line, value)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// Everything else should be a quoted string
|
||||
if !strings.HasPrefix(value, "\"") || !strings.HasSuffix(value, "\"") {
|
||||
// Add debug info to see what's wrong
|
||||
t.Logf("DEBUG: Checking value '%s' from line '%s'", value, strings.TrimSpace(line))
|
||||
t.Errorf("String value should be quoted in line: %s (value: %s)", strings.TrimSpace(line), value)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Test secret hiding
|
||||
if tc.expectSecrets {
|
||||
if !strings.Contains(yamlOutput, "**hidden**") {
|
||||
t.Error("Expected secrets to be hidden with **hidden**")
|
||||
}
|
||||
if strings.Contains(yamlOutput, "secret123") || strings.Contains(yamlOutput, "testadmin") || strings.Contains(yamlOutput, "testpassword") {
|
||||
t.Error("Secret values should not appear in output")
|
||||
}
|
||||
}
|
||||
|
||||
// Test deprecated field filtering
|
||||
hasDeprecated := strings.Contains(yamlOutput, "disableOfficePreviewExt")
|
||||
if tc.expectDeprecated && !hasDeprecated {
|
||||
t.Error("Expected deprecated field to be present")
|
||||
}
|
||||
if !tc.expectDeprecated && hasDeprecated && tc.filterDeprecated {
|
||||
t.Error("Expected deprecated field to be filtered out when filterDeprecated=true")
|
||||
}
|
||||
|
||||
// Test comments
|
||||
hasComments := strings.Contains(yamlOutput, "#")
|
||||
if tc.showComments && !hasComments {
|
||||
t.Error("Expected comments to be present when showComments=true")
|
||||
}
|
||||
|
||||
t.Logf("Test case: %s\nGenerated YAML:\n%s\n", tc.name, yamlOutput)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCollectCommentsAndSecrets_Basic(t *testing.T) {
|
||||
// Test the comment and secret collection directly
|
||||
comments, secrets, deprecated, err := CollectCommentsAndSecrets(".")
|
||||
if err != nil {
|
||||
t.Fatalf("CollectCommentsAndSecrets failed: %v", err)
|
||||
}
|
||||
|
||||
// Should find some secrets in the actual source files
|
||||
foundSecrets := false
|
||||
for typeName, typeSecrets := range secrets {
|
||||
for fieldName := range typeSecrets {
|
||||
t.Logf("Found secret: %s.%s", typeName, fieldName)
|
||||
foundSecrets = true
|
||||
}
|
||||
}
|
||||
if !foundSecrets {
|
||||
t.Error("Expected to find some secret fields in the actual source code")
|
||||
}
|
||||
|
||||
// Should find some deprecated fields
|
||||
foundDeprecated := false
|
||||
for typeName, typeDeprecated := range deprecated {
|
||||
for fieldName := range typeDeprecated {
|
||||
t.Logf("Found deprecated: %s.%s", typeName, fieldName)
|
||||
foundDeprecated = true
|
||||
}
|
||||
}
|
||||
if !foundDeprecated {
|
||||
t.Error("Expected to find some deprecated fields in the actual source code")
|
||||
}
|
||||
|
||||
t.Logf("Comments: %d types", len(comments))
|
||||
t.Logf("Secrets: %d types", len(secrets))
|
||||
t.Logf("Deprecated: %d types", len(deprecated))
|
||||
}
|
||||
|
||||
func TestGenerateYaml_StaticGeneration(t *testing.T) {
|
||||
// Test the static generation function that's used by FILEBROWSER_GENERATE_CONFIG=true
|
||||
|
||||
// Create a temporary config for testing
|
||||
config := &Settings{
|
||||
UserDefaults: UserDefaults{
|
||||
Locale: "en",
|
||||
DisableOfficePreviewExt: ".docx .xlsx", // This should be filtered out
|
||||
},
|
||||
Auth: Auth{
|
||||
Key: "test-secret", // This should be redacted
|
||||
},
|
||||
}
|
||||
|
||||
// Test static generation with comments and deprecated filtering
|
||||
yamlOutput, err := GenerateConfigYamlWithSource(config, true, true, true, ".")
|
||||
if err != nil {
|
||||
t.Fatalf("Static generation failed: %v", err)
|
||||
}
|
||||
|
||||
// Verify comments are included
|
||||
if !strings.Contains(yamlOutput, "#") {
|
||||
t.Error("Static generation should include comments")
|
||||
}
|
||||
|
||||
// Verify deprecated field is filtered out
|
||||
if strings.Contains(yamlOutput, "disableOfficePreviewExt") {
|
||||
t.Error("Static generation should filter out deprecated field disableOfficePreviewExt")
|
||||
}
|
||||
|
||||
// Verify secrets are redacted
|
||||
if !strings.Contains(yamlOutput, "**hidden**") {
|
||||
t.Error("Static generation should redact secrets")
|
||||
}
|
||||
if strings.Contains(yamlOutput, "test-secret") {
|
||||
t.Error("Static generation should not contain actual secret values")
|
||||
}
|
||||
|
||||
// Verify it has proper structure
|
||||
if !strings.Contains(yamlOutput, "userDefaults:") {
|
||||
t.Error("Static generation should contain userDefaults section")
|
||||
}
|
||||
if !strings.Contains(yamlOutput, "auth:") {
|
||||
t.Error("Static generation should contain auth section")
|
||||
}
|
||||
|
||||
t.Logf("Static generation successful with comments and proper filtering")
|
||||
}
|
||||
@@ -1,470 +0,0 @@
|
||||
package settings
|
||||
|
||||
import (
|
||||
"os"
|
||||
"regexp"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// Test structs for comprehensive testing
|
||||
type TestConfig struct {
|
||||
PublicField string `json:"publicField"` // A regular public field
|
||||
SecretField string `json:"secretField"` // secret: this should be hidden
|
||||
DeprecatedField string `json:"deprecatedField"` // deprecated: this field is old
|
||||
EmptyField string `json:"emptyField"` // An empty string field
|
||||
DefaultField string `json:"defaultField"` // A field that matches default
|
||||
NonDefaultField string `json:"nonDefaultField"` // A field that differs from default
|
||||
NestedStruct TestNested `json:"nestedStruct"`
|
||||
}
|
||||
|
||||
type TestNested struct {
|
||||
NestedPublic string `json:"nestedPublic"` // A nested public field
|
||||
NestedSecret string `json:"nestedSecret"` // secret: nested secret field
|
||||
NestedDeprecated string `json:"nestedDeprecated"` // deprecated: nested deprecated field
|
||||
}
|
||||
|
||||
// Helper to create test source files for comment parsing
|
||||
func createTestSourceFile(t *testing.T, content string) (string, func()) {
|
||||
tmpFile, err := os.CreateTemp("", "test_*.go")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if _, err := tmpFile.WriteString(content); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
tmpFile.Close()
|
||||
|
||||
cleanup := func() {
|
||||
os.Remove(tmpFile.Name())
|
||||
}
|
||||
|
||||
return tmpFile.Name(), cleanup
|
||||
}
|
||||
|
||||
func TestGenerateConfigYaml_StringQuoting(t *testing.T) {
|
||||
reNumber := regexp.MustCompile(`^-?\d+(\.\d+)?$`)
|
||||
// Create a simple test source file for this test
|
||||
sourceContent := `package settings
|
||||
|
||||
type Settings struct {
|
||||
UserDefaults UserDefaults ` + "`json:\"userDefaults\"`" + `
|
||||
Auth Auth ` + "`json:\"auth\"`" + `
|
||||
}
|
||||
|
||||
type UserDefaults struct {
|
||||
Locale string ` + "`json:\"locale\"`" + `
|
||||
}
|
||||
|
||||
type Auth struct {
|
||||
AdminUsername string ` + "`json:\"adminUsername\"`" + `
|
||||
}
|
||||
`
|
||||
tmpFile, cleanup := createTestSourceFile(t, sourceContent)
|
||||
defer cleanup()
|
||||
sourcePath := tmpFile[:strings.LastIndex(tmpFile, "/")]
|
||||
|
||||
// Create real Settings with string values to test quoting
|
||||
settings := &Settings{
|
||||
UserDefaults: UserDefaults{
|
||||
Locale: "en-US",
|
||||
},
|
||||
Auth: Auth{
|
||||
AdminUsername: "admin",
|
||||
},
|
||||
}
|
||||
|
||||
// Test with no filtering - should quote all strings
|
||||
yamlOutput, err := GenerateConfigYamlWithSource(settings, false, true, false, sourcePath)
|
||||
if err != nil {
|
||||
t.Fatalf("GenerateConfigYaml failed: %v", err)
|
||||
}
|
||||
|
||||
// All string values should be quoted
|
||||
lines := strings.Split(yamlOutput, "\n")
|
||||
for _, line := range lines {
|
||||
if strings.Contains(line, ":") && !strings.HasSuffix(strings.TrimSpace(line), ":") {
|
||||
// This is a key-value line
|
||||
parts := strings.SplitN(line, ":", 2)
|
||||
if len(parts) == 2 {
|
||||
value := strings.TrimSpace(parts[1])
|
||||
|
||||
// Remove comments
|
||||
if commentIdx := strings.Index(value, "#"); commentIdx >= 0 {
|
||||
value = strings.TrimSpace(value[:commentIdx])
|
||||
}
|
||||
|
||||
// Skip empty values, arrays, objects, booleans
|
||||
if value == "" || value == "false" || value == "true" || strings.HasPrefix(value, "[") || strings.HasPrefix(value, "{") {
|
||||
continue
|
||||
}
|
||||
|
||||
// Check if it's a number (should NOT be quoted)
|
||||
if reNumber.MatchString(value) {
|
||||
if strings.HasPrefix(value, "\"") {
|
||||
t.Errorf("Numeric value should not be quoted: %s (value: '%s')", line, value)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// Everything else should be a quoted string
|
||||
if !strings.HasPrefix(value, "\"") || !strings.HasSuffix(value, "\"") {
|
||||
t.Errorf("String value should be quoted: %s (value: '%s')", line, value)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenerateConfigYaml_SecretHiding(t *testing.T) {
|
||||
// Create temporary source file with our test structs
|
||||
sourceContent := `package settings
|
||||
|
||||
type TestConfig struct {
|
||||
PublicField string ` + "`json:\"publicField\"`" + ` // A regular public field
|
||||
SecretField string ` + "`json:\"secretField\"`" + ` // secret: this should be hidden
|
||||
}
|
||||
|
||||
type TestNested struct {
|
||||
NestedPublic string ` + "`json:\"nestedPublic\"`" + ` // A nested public field
|
||||
NestedSecret string ` + "`json:\"nestedSecret\"`" + ` // secret: nested secret field
|
||||
}
|
||||
`
|
||||
|
||||
tmpFile, cleanup := createTestSourceFile(t, sourceContent)
|
||||
defer cleanup()
|
||||
|
||||
// Test secret detection
|
||||
_, secrets, _, err := CollectCommentsAndSecrets(tmpFile)
|
||||
if err != nil {
|
||||
t.Fatalf("CollectCommentsAndSecrets failed: %v", err)
|
||||
}
|
||||
|
||||
// Verify secret fields are detected
|
||||
if !secrets["TestConfig"]["SecretField"] {
|
||||
t.Error("SecretField should be marked as secret")
|
||||
}
|
||||
if !secrets["TestNested"]["NestedSecret"] {
|
||||
t.Error("NestedSecret should be marked as secret")
|
||||
}
|
||||
if secrets["TestConfig"]["PublicField"] {
|
||||
t.Error("PublicField should not be marked as secret")
|
||||
}
|
||||
|
||||
// Test that secrets are properly collected
|
||||
if len(secrets["TestConfig"]) != 1 {
|
||||
t.Errorf("Expected 1 secret field in TestConfig, got %d", len(secrets["TestConfig"]))
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenerateConfigYaml_DeprecatedFiltering(t *testing.T) {
|
||||
// Create temporary source file with deprecated fields
|
||||
sourceContent := `package settings
|
||||
|
||||
type TestConfig struct {
|
||||
PublicField string ` + "`json:\"publicField\"`" + ` // A regular public field
|
||||
DeprecatedField string ` + "`json:\"deprecatedField\"`" + ` // deprecated: this field is old
|
||||
}
|
||||
`
|
||||
|
||||
tmpFile, cleanup := createTestSourceFile(t, sourceContent)
|
||||
defer cleanup()
|
||||
|
||||
// Test deprecated detection
|
||||
_, _, deprecated, err := CollectCommentsAndSecrets(tmpFile)
|
||||
if err != nil {
|
||||
t.Fatalf("CollectCommentsAndSecrets failed: %v", err)
|
||||
}
|
||||
|
||||
// Verify deprecated fields are detected
|
||||
if !deprecated["TestConfig"]["DeprecatedField"] {
|
||||
t.Error("DeprecatedField should be marked as deprecated")
|
||||
}
|
||||
if deprecated["TestConfig"]["PublicField"] {
|
||||
t.Error("PublicField should not be marked as deprecated")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenerateConfigYaml_FullVsFiltered(t *testing.T) {
|
||||
// Create minimal test source
|
||||
sourceContent := `package settings
|
||||
|
||||
type Settings struct {
|
||||
UserDefaults UserDefaults ` + "`json:\"userDefaults\"`" + `
|
||||
}
|
||||
|
||||
type UserDefaults struct {
|
||||
DarkMode bool ` + "`json:\"darkMode\"`" + `
|
||||
Locale string ` + "`json:\"locale\"`" + `
|
||||
}
|
||||
`
|
||||
tmpFile, cleanup := createTestSourceFile(t, sourceContent)
|
||||
defer cleanup()
|
||||
sourcePath := tmpFile[:strings.LastIndex(tmpFile, "/")]
|
||||
|
||||
// Create a config with both default and non-default values
|
||||
config := &Settings{
|
||||
UserDefaults: UserDefaults{
|
||||
DarkMode: true, // This matches default
|
||||
Locale: "es", // This differs from default ("en")
|
||||
},
|
||||
}
|
||||
|
||||
// Test full=true - should show all fields
|
||||
fullYaml, err := GenerateConfigYamlWithSource(config, false, true, false, sourcePath)
|
||||
if err != nil {
|
||||
t.Fatalf("GenerateConfigYaml with full=true failed: %v", err)
|
||||
}
|
||||
|
||||
// Test full=false - should only show non-default fields
|
||||
filteredYaml, err := GenerateConfigYamlWithSource(config, false, false, false, sourcePath)
|
||||
if err != nil {
|
||||
t.Fatalf("GenerateConfigYaml with full=false failed: %v", err)
|
||||
}
|
||||
|
||||
// Full output should be longer than filtered
|
||||
if len(fullYaml) <= len(filteredYaml) {
|
||||
t.Error("Full YAML should be longer than filtered YAML")
|
||||
}
|
||||
|
||||
// Filtered output should contain the non-default field
|
||||
if !strings.Contains(filteredYaml, "locale") {
|
||||
t.Error("Filtered YAML should contain non-default locale field")
|
||||
}
|
||||
|
||||
// Full output should contain default fields
|
||||
if !strings.Contains(fullYaml, "darkMode") {
|
||||
t.Error("Full YAML should contain darkMode field")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenerateConfigYaml_CommentsOnOff(t *testing.T) {
|
||||
// Use the actual settings source directory since comments work there
|
||||
config := &Settings{
|
||||
UserDefaults: UserDefaults{
|
||||
Locale: "en",
|
||||
},
|
||||
}
|
||||
|
||||
// Test with comments=true using the real source
|
||||
withComments, err := GenerateConfigYaml(config, true, true, false)
|
||||
if err != nil {
|
||||
t.Fatalf("GenerateConfigYaml with comments=true failed: %v", err)
|
||||
}
|
||||
|
||||
// Test with comments=false using the real source
|
||||
withoutComments, err := GenerateConfigYaml(config, false, true, false)
|
||||
if err != nil {
|
||||
t.Fatalf("GenerateConfigYaml with comments=false failed: %v", err)
|
||||
}
|
||||
|
||||
// Output with comments should be longer
|
||||
if len(withComments) <= len(withoutComments) {
|
||||
t.Errorf("YAML with comments (%d chars) should be longer than without comments (%d chars)", len(withComments), len(withoutComments))
|
||||
}
|
||||
|
||||
// With comments should contain '#' characters
|
||||
if !strings.Contains(withComments, "#") {
|
||||
t.Error("YAML with comments should contain '#' characters")
|
||||
}
|
||||
|
||||
// Without comments should not contain '#' characters
|
||||
if strings.Contains(withoutComments, "#") {
|
||||
t.Error("YAML without comments should not contain '#' characters")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenerateConfigYaml_IntegrationTest(t *testing.T) {
|
||||
reNumber := regexp.MustCompile(`^-?\d+(\.\d+)?$`)
|
||||
// Comprehensive test with all features
|
||||
config := &Settings{
|
||||
UserDefaults: UserDefaults{
|
||||
Locale: "es", // Non-default
|
||||
DarkMode: true, // Default
|
||||
DisableOfficePreviewExt: ".docx .xlsx", // This is deprecated
|
||||
},
|
||||
Auth: Auth{
|
||||
Key: "secret123", // This is secret
|
||||
AdminUsername: "admin", // This is secret
|
||||
AdminPassword: "password", // This is secret
|
||||
},
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
showComments bool
|
||||
showFull bool
|
||||
filterDeprecated bool
|
||||
expectSecret bool
|
||||
expectDeprecated bool
|
||||
expectFull bool
|
||||
expectComments bool
|
||||
}{
|
||||
{
|
||||
name: "API mode: show all including deprecated",
|
||||
showComments: false,
|
||||
showFull: true,
|
||||
filterDeprecated: false,
|
||||
expectSecret: true, // secrets should be hidden
|
||||
expectDeprecated: true, // deprecated should be shown
|
||||
expectFull: true, // all fields shown
|
||||
expectComments: false, // no comments
|
||||
},
|
||||
{
|
||||
name: "Static config mode: filter deprecated",
|
||||
showComments: true,
|
||||
showFull: true,
|
||||
filterDeprecated: true,
|
||||
expectSecret: true, // secrets should be hidden
|
||||
expectDeprecated: false, // deprecated should be filtered
|
||||
expectFull: true, // all non-deprecated fields shown
|
||||
expectComments: true, // comments included
|
||||
},
|
||||
{
|
||||
name: "Filtered output: only changes",
|
||||
showComments: false,
|
||||
showFull: false,
|
||||
filterDeprecated: false,
|
||||
expectSecret: true, // secrets should be hidden
|
||||
expectDeprecated: false, // might not appear if matches default
|
||||
expectFull: false, // only non-default fields
|
||||
expectComments: false, // no comments
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
yamlOutput, err := GenerateConfigYaml(config, tt.showComments, tt.showFull, tt.filterDeprecated)
|
||||
if err != nil {
|
||||
t.Fatalf("GenerateConfigYaml failed: %v", err)
|
||||
}
|
||||
|
||||
// Check secret hiding
|
||||
if tt.expectSecret {
|
||||
if !strings.Contains(yamlOutput, "**hidden**") {
|
||||
t.Error("Expected secrets to be hidden with **hidden**")
|
||||
}
|
||||
if strings.Contains(yamlOutput, "secret123") {
|
||||
t.Error("Secret values should not appear in output")
|
||||
}
|
||||
}
|
||||
|
||||
// Check deprecated field filtering
|
||||
hasDeprecated := strings.Contains(yamlOutput, "disableOfficePreviewExt")
|
||||
if tt.expectDeprecated && !hasDeprecated {
|
||||
t.Error("Expected deprecated field to be present")
|
||||
}
|
||||
if !tt.expectDeprecated && hasDeprecated && tt.filterDeprecated {
|
||||
t.Error("Expected deprecated field to be filtered out")
|
||||
}
|
||||
|
||||
// Check comments
|
||||
hasComments := strings.Contains(yamlOutput, "#")
|
||||
if tt.expectComments && !hasComments {
|
||||
t.Error("Expected comments to be present")
|
||||
}
|
||||
if !tt.expectComments && hasComments {
|
||||
t.Error("Expected no comments")
|
||||
}
|
||||
|
||||
// Check string quoting - all string values should be quoted
|
||||
lines := strings.Split(yamlOutput, "\n")
|
||||
for _, line := range lines {
|
||||
if strings.Contains(line, ":") && !strings.HasSuffix(strings.TrimSpace(line), ":") {
|
||||
// This is a key-value line
|
||||
parts := strings.SplitN(line, ":", 2)
|
||||
if len(parts) == 2 {
|
||||
value := strings.TrimSpace(parts[1])
|
||||
// Skip comments and empty values
|
||||
if commentIdx := strings.Index(value, "#"); commentIdx >= 0 {
|
||||
// If comment starts at the beginning, there's no value, just a comment
|
||||
if commentIdx == 0 {
|
||||
continue
|
||||
}
|
||||
value = strings.TrimSpace(value[:commentIdx])
|
||||
}
|
||||
// Check if it's a number (should NOT be quoted)
|
||||
if reNumber.MatchString(value) {
|
||||
if strings.HasPrefix(value, "\"") {
|
||||
t.Errorf("Numeric value should not be quoted in line: %s (value: '%s')", line, value)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// Check if it's a boolean (should NOT be quoted)
|
||||
if value == "true" || value == "false" {
|
||||
if strings.HasPrefix(value, "\"") {
|
||||
t.Errorf("Boolean value should not be quoted in line: %s (value: '%s')", line, value)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// Skip arrays, objects, and empty values
|
||||
if value == "" || strings.HasPrefix(value, "[") || strings.HasPrefix(value, "{") || strings.Contains(value, ":") {
|
||||
continue
|
||||
}
|
||||
|
||||
// Everything else should be a quoted string
|
||||
if !strings.HasPrefix(value, "\"") || !strings.HasSuffix(value, "\"") {
|
||||
t.Errorf("String value should be quoted in line: %s (value: '%s')", line, value)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
t.Logf("Generated YAML for %s:\n%s", tt.name, yamlOutput)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenerateConfigYaml_EdgeCases(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
config *Settings
|
||||
desc string
|
||||
}{
|
||||
{
|
||||
name: "empty_config",
|
||||
config: &Settings{},
|
||||
desc: "Empty config should generate valid YAML",
|
||||
},
|
||||
{
|
||||
name: "only_defaults",
|
||||
config: &Settings{
|
||||
UserDefaults: UserDefaults{
|
||||
DarkMode: true, // default value
|
||||
Locale: "en", // default value
|
||||
},
|
||||
},
|
||||
desc: "Config with only default values",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// Test all combinations
|
||||
for _, showComments := range []bool{false, true} {
|
||||
for _, showFull := range []bool{false, true} {
|
||||
for _, filterDeprecated := range []bool{false, true} {
|
||||
yamlOutput, err := GenerateConfigYaml(tt.config, showComments, showFull, filterDeprecated)
|
||||
if err != nil {
|
||||
t.Fatalf("GenerateConfigYaml failed for %s (comments=%v, full=%v, filterDeprecated=%v): %v",
|
||||
tt.desc, showComments, showFull, filterDeprecated, err)
|
||||
}
|
||||
|
||||
// Basic validation - should produce valid YAML structure
|
||||
if yamlOutput == "" {
|
||||
t.Errorf("Generated empty YAML for %s", tt.desc)
|
||||
}
|
||||
|
||||
// Should not contain obvious error messages (but allow "error" in log levels etc)
|
||||
if strings.Contains(yamlOutput, "Error:") || strings.Contains(yamlOutput, "ERROR:") {
|
||||
t.Errorf("YAML contains error for %s", tt.desc)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -21,8 +21,6 @@ type Settings struct {
|
||||
}
|
||||
|
||||
type Server struct {
|
||||
MinSearchLength int `json:"minSearchLength"` // minimum length of search query to begin searching (default: 3)
|
||||
DisableUpdateCheck bool `json:"disableUpdateCheck"` // disables backend update check service
|
||||
NumImageProcessors int `json:"numImageProcessors"` // number of concurrent image processing jobs used to create previews, default is number of cpu cores available.
|
||||
Socket string `json:"socket"` // socket to listen on
|
||||
TLSKey string `json:"tlsKey"` // path to TLS key
|
||||
@@ -54,9 +52,9 @@ type Integrations struct {
|
||||
// onlyoffice secret is stored in the local.json file
|
||||
// docker exec <containerID> /var/www/onlyoffice/documentserver/npm/json -f /etc/onlyoffice/documentserver/local.json 'services.CoAuthoring.secret.session.string'
|
||||
type OnlyOffice struct {
|
||||
Url string `json:"url" validate:"required"` // The URL to the OnlyOffice Document Server, needs to be accessible to the user.
|
||||
InternalUrl string `json:"internalUrl"` // An optional internal address that the filebrowser server can use to communicate with the OnlyOffice Document Server, could be useful to bypass proxy.
|
||||
Secret string `json:"secret" validate:"required"` // secret: authentication key for OnlyOffice integration
|
||||
Url string `json:"url" validate:"required"` // The URL to the OnlyOffice Document Server, needs to be accessible to the user.
|
||||
InternalUrl string `json:"internalUrl"` // An optional internal address that the filebrowser server can use to communicate with the OnlyOffice Document Server, could be useful to bypass proxy.
|
||||
Secret string `json:"secret" validate:"required"`
|
||||
}
|
||||
|
||||
type Media struct {
|
||||
@@ -75,22 +73,22 @@ type LogConfig struct {
|
||||
type Source struct {
|
||||
Path string `json:"path" validate:"required"` // file system path. (Can be relative)
|
||||
Name string `json:"name"` // display name
|
||||
Config SourceConfig `json:"config,omitempty"`
|
||||
Config SourceConfig `json:"config"`
|
||||
}
|
||||
|
||||
type SourceConfig struct {
|
||||
DenyByDefault bool `json:"denyByDefault,omitempty"` // deny access unless an "allow" access rule was specifically created.
|
||||
Private bool `json:"private"` // designate as source as private -- currently just means no sharing permitted.
|
||||
Disabled bool `json:"disabled,omitempty"` // disable the source, this is useful so you don't need to remove it from the config file
|
||||
IndexingInterval uint32 `json:"indexingIntervalMinutes,omitempty"` // optional manual overide interval in minutes to re-index the source
|
||||
DisableIndexing bool `json:"disableIndexing,omitempty"` // disable the indexing of this source
|
||||
MaxWatchers int `json:"maxWatchers"` // number of concurrent watchers to use for this source, currently not supported
|
||||
NeverWatchPaths []string `json:"neverWatchPaths"` // paths that get initially once. Useful for folders that rarely change contents (without source path prefix)
|
||||
Exclude ExcludeIndexFilter `json:"exclude"` // exclude files and folders from indexing, if include is not set
|
||||
Include IncludeIndexFilter `json:"include"` // include files and folders from indexing, if exclude is not set
|
||||
DefaultUserScope string `json:"defaultUserScope"` // default "/" should match folders under path
|
||||
DefaultEnabled bool `json:"defaultEnabled"` // should be added as a default source for new users?
|
||||
CreateUserDir bool `json:"createUserDir"` // create a user directory for each user
|
||||
DenyByDefault bool `json:"denyByDefault"` // deny access unless an "allow" access rule was specifically created.
|
||||
Private bool `json:"private"` // designate as source as private -- currently just means no sharing permitted.
|
||||
Disabled bool `json:"disabled"` // disable the source, this is useful so you don't need to remove it from the config file
|
||||
IndexingInterval uint32 `json:"indexingIntervalMinutes"` // optional manual overide interval in minutes to re-index the source
|
||||
DisableIndexing bool `json:"disableIndexing"` // disable the indexing of this source
|
||||
MaxWatchers int `json:"maxWatchers"` // number of concurrent watchers to use for this source, currently not supported
|
||||
NeverWatchPaths []string `json:"neverWatchPaths"` // paths that get initially once. Useful for folders that rarely change contents (without source path prefix)
|
||||
Exclude ExcludeIndexFilter `json:"exclude"` // exclude files and folders from indexing, if include is not set
|
||||
Include IncludeIndexFilter `json:"include"` // include files and folders from indexing, if exclude is not set
|
||||
DefaultUserScope string `json:"defaultUserScope"` // default "/" should match folders under path
|
||||
DefaultEnabled bool `json:"defaultEnabled"` // should be added as a default source for new users?
|
||||
CreateUserDir bool `json:"createUserDir"` // create a user directory for each user
|
||||
}
|
||||
|
||||
type IncludeIndexFilter struct {
|
||||
@@ -116,24 +114,19 @@ type Frontend struct {
|
||||
ExternalLinks []ExternalLink `json:"externalLinks"`
|
||||
DisableNavButtons bool `json:"disableNavButtons"` // disable the nav buttons in the sidebar
|
||||
Styling StylingConfig `json:"styling"`
|
||||
Favicon string `json:"favicon"` // path to a favicon to use for the frontend
|
||||
Description string `json:"description"` // description that shows up in html head meta description
|
||||
}
|
||||
|
||||
type StylingConfig struct {
|
||||
CustomCSS string `json:"customCSS"` // if a valid path to a css file is provided, it will be applied for all users. (eg. "reduce-rounded-corners.css")
|
||||
CustomCSSRaw string `json:"-"` // The css raw content to use for the custom css.
|
||||
LightBackground string `json:"lightBackground"` // specify a valid CSS color property value to use as the background color in light mode
|
||||
DarkBackground string `json:"darkBackground"` // Specify a valid CSS color property value to use as the background color in dark mode
|
||||
CustomThemes map[string]CustomTheme `json:"customThemes"` // A list of custom css files that each user can select to override the default styling. if "default" is key name then it will be the default option.
|
||||
// In-memory (not exposed to config)
|
||||
CustomThemeOptions map[string]CustomTheme `json:"-"` // not exposed
|
||||
CustomCSS string `json:"customCSS"` // if a valid path to a css file is provided, it will be applied for all users. (eg. "reduce-rounded-corners.css")
|
||||
LightBackground string `json:"lightBackground"` // specify a valid CSS color property value to use as the background color in light mode
|
||||
DarkBackground string `json:"darkBackground"` // Specify a valid CSS color property value to use as the background color in dark mode
|
||||
CustomThemes map[string]CustomTheme `json:"customThemes"` // A list of custom css files that each user can select to override the default styling. if "default" is key name then it will be the default option.
|
||||
CustomThemeOptions map[string]CustomTheme `json:"-"` // not exposed
|
||||
}
|
||||
|
||||
type CustomTheme struct {
|
||||
Description string `json:"description"` // The description of the theme to display in the UI.
|
||||
CSS string `json:"css,omitempty"` // The css file path and filename to use for the theme.
|
||||
CssRaw string `json:"-"` // The css raw content to use for the theme.
|
||||
}
|
||||
|
||||
type ExternalLink struct {
|
||||
@@ -159,8 +152,8 @@ type UserDefaults struct {
|
||||
GallerySize int `json:"gallerySize"` // 0-9 - the size of the gallery thumbnails
|
||||
ThemeColor string `json:"themeColor"` // theme color to use: eg. #ff0000, or var(--red), var(--purple), etc
|
||||
QuickDownload bool `json:"quickDownload"` // show icon to download in one click
|
||||
DisablePreviewExt string `json:"disablePreviewExt"` // space separated list of file extensions to disable preview for
|
||||
DisableViewingExt string `json:"disableViewingExt"` // space separated list of file extensions to disable viewing for
|
||||
DisablePreviewExt string `json:"disablePreviewExt"` // comma separated list of file extensions to disable preview for
|
||||
DisableViewingExt string `json:"disableViewingExt"` // comma separated list of file extensions to disable viewing for
|
||||
LockPassword bool `json:"lockPassword"` // disable the user from changing their password
|
||||
DisableSettings bool `json:"disableSettings,omitempty"` // disable the user from viewing the settings page
|
||||
Preview users.Preview `json:"preview"`
|
||||
@@ -174,5 +167,4 @@ type UserDefaults struct {
|
||||
DisableOnlyOfficeExt string `json:"disableOnlyOfficeExt"` // list of file extensions to disable onlyoffice editor for
|
||||
CustomTheme string `json:"customTheme"` // Name of theme to use chosen from custom themes config.
|
||||
ShowSelectMultiple bool `json:"showSelectMultiple"` // show select multiple files on desktop
|
||||
DebugOffice bool `json:"debugOffice"` // debug onlyoffice editor
|
||||
}
|
||||
@@ -74,21 +74,17 @@ func FallbackColor(val, defaultColor string) string {
|
||||
}
|
||||
|
||||
func addCustomTheme(name, description, cssFilePath string) {
|
||||
// Store only file path in config (for YAML export)
|
||||
Config.Frontend.Styling.CustomThemes[name] = CustomTheme{
|
||||
Description: description,
|
||||
CSS: cssFilePath, // Store file path, not content
|
||||
}
|
||||
|
||||
// Load CSS content for runtime use (frontend consumption)
|
||||
cssContent := ""
|
||||
if cssFilePath != "" {
|
||||
cssContent = readCustomCSS(cssFilePath)
|
||||
}
|
||||
Config.Frontend.Styling.CustomThemes[name] = CustomTheme{
|
||||
Description: description,
|
||||
CSS: cssContent,
|
||||
}
|
||||
Config.Frontend.Styling.CustomThemeOptions[name] = CustomTheme{
|
||||
Description: description,
|
||||
CSS: cssFilePath,
|
||||
CssRaw: cssContent, // Store loaded content
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,5 +12,4 @@ var (
|
||||
SearchResultsCache = cache.NewCache(15*time.Second, 1*time.Hour)
|
||||
OnlyOfficeCache = cache.NewCache(48*time.Hour, 1*time.Hour)
|
||||
JwtCache = cache.NewCache(1*time.Hour, 72*time.Hour)
|
||||
MediaCache = cache.NewCache(24 * time.Hour) // subtitles get cached for 24 hours
|
||||
)
|
||||
@@ -121,10 +121,3 @@ func NonNilSlice[T any](in []T) []T {
|
||||
}
|
||||
return in
|
||||
}
|
||||
|
||||
func Ternary[T any](cond bool, vtrue, vfalse T) T {
|
||||
if cond {
|
||||
return vtrue
|
||||
}
|
||||
return vfalse
|
||||
}
|
||||
@@ -176,7 +176,6 @@ func (s *Storage) DenyUser(sourcePath, indexPath, username string) error {
|
||||
}
|
||||
rule.Deny.Users[username] = struct{}{}
|
||||
s.incrementSourceVersion(sourcePath)
|
||||
accessCache.Set(accessChangedKey+sourcePath, false)
|
||||
return s.SaveToDB()
|
||||
}
|
||||
|
||||
@@ -196,7 +195,6 @@ func (s *Storage) AllowUser(sourcePath, indexPath, username string) error {
|
||||
}
|
||||
rule.Allow.Users[username] = struct{}{}
|
||||
s.incrementSourceVersion(sourcePath)
|
||||
accessCache.Set(accessChangedKey+sourcePath, false)
|
||||
return s.SaveToDB()
|
||||
}
|
||||
|
||||
@@ -214,7 +212,6 @@ func (s *Storage) DenyGroup(sourcePath, indexPath, groupname string) error {
|
||||
}
|
||||
rule.Deny.Groups[groupname] = struct{}{}
|
||||
s.incrementSourceVersion(sourcePath)
|
||||
accessCache.Set(accessChangedKey+sourcePath, false)
|
||||
return s.SaveToDB()
|
||||
}
|
||||
|
||||
@@ -232,7 +229,6 @@ func (s *Storage) AllowGroup(sourcePath, indexPath, groupname string) error {
|
||||
}
|
||||
rule.Allow.Groups[groupname] = struct{}{}
|
||||
s.incrementSourceVersion(sourcePath)
|
||||
accessCache.Set(accessChangedKey+sourcePath, false)
|
||||
return s.SaveToDB()
|
||||
}
|
||||
|
||||
@@ -246,7 +242,6 @@ func (s *Storage) DenyAll(sourcePath, indexPath string) error {
|
||||
}
|
||||
rule.DenyAll = true
|
||||
s.incrementSourceVersion(sourcePath)
|
||||
accessCache.Set(accessChangedKey+sourcePath, false)
|
||||
return s.SaveToDB()
|
||||
}
|
||||
|
||||
@@ -273,91 +268,32 @@ func (s *Storage) Permitted(sourcePath, indexPath, username string) bool {
|
||||
}
|
||||
|
||||
func (s *Storage) computePermitted(sourcePath, indexPath, username string) bool {
|
||||
var rulesFound []*AccessRule
|
||||
|
||||
// Walk up the path hierarchy and collect all relevant rules
|
||||
currentPath := indexPath
|
||||
for {
|
||||
rule, found := s.getRuleAtExactPath(sourcePath, currentPath)
|
||||
permitted, found := s.permittedAtExactPath(sourcePath, indexPath, username)
|
||||
if found {
|
||||
rulesFound = append(rulesFound, rule)
|
||||
}
|
||||
if currentPath == "/" || currentPath == "." || currentPath == "" {
|
||||
break
|
||||
}
|
||||
currentPath = utils.GetParentDirectoryPath(currentPath)
|
||||
}
|
||||
|
||||
// Now evaluate the rules, starting from the most specific (indexPath) to the least specific (root)
|
||||
for _, rule := range rulesFound {
|
||||
permitted, hasSpecificRule := s.evaluateRuleForUser(rule, username)
|
||||
if hasSpecificRule {
|
||||
return permitted
|
||||
}
|
||||
}
|
||||
|
||||
// No specific user or group rule found in the hierarchy.
|
||||
// Check for any DenyAll rule in the path.
|
||||
for _, rule := range rulesFound {
|
||||
if rule.DenyAll {
|
||||
return false
|
||||
indexPath = utils.GetParentDirectoryPath(indexPath)
|
||||
if indexPath == "" {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// No specific rules found anywhere in the path hierarchy.
|
||||
// Fallback to the source's DenyByDefault setting.
|
||||
// No rules found anywhere in the path hierarchy
|
||||
// Check if the source has DenyByDefault configured (acts like a root-level denyAll rule)
|
||||
sourceInfo, ok := settings.Config.Server.SourceMap[sourcePath]
|
||||
if !ok {
|
||||
logger.Errorf("source %s not found in config during access check", sourcePath)
|
||||
return false
|
||||
}
|
||||
|
||||
return !sourceInfo.Config.DenyByDefault
|
||||
if sourceInfo.Config.DenyByDefault {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// getRuleAtExactPath is a helper to get a rule without the recursive logic.
|
||||
func (s *Storage) getRuleAtExactPath(sourcePath, indexPath string) (*AccessRule, bool) {
|
||||
s.mux.RLock()
|
||||
defer s.mux.RUnlock()
|
||||
rulesBySource, ok := s.AllRules[sourcePath]
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
rule, ok := rulesBySource[indexPath]
|
||||
return rule, ok
|
||||
}
|
||||
|
||||
// evaluateRuleForUser evaluates a single rule for a user and returns if a specific rule was found.
|
||||
func (s *Storage) evaluateRuleForUser(rule *AccessRule, username string) (permitted bool, hasSpecificRule bool) {
|
||||
// Check user deny first
|
||||
if _, found := rule.Deny.Users[username]; found {
|
||||
return false, true
|
||||
}
|
||||
|
||||
// Check group deny
|
||||
for group := range rule.Deny.Groups {
|
||||
if s.isUserInGroup(username, group) {
|
||||
return false, true
|
||||
}
|
||||
}
|
||||
|
||||
// Check user allow
|
||||
if _, found := rule.Allow.Users[username]; found {
|
||||
return true, true
|
||||
}
|
||||
|
||||
// Check group allow
|
||||
for group := range rule.Allow.Groups {
|
||||
if s.isUserInGroup(username, group) {
|
||||
return true, true
|
||||
}
|
||||
}
|
||||
|
||||
// No specific rule for this user in this rule set.
|
||||
return false, false
|
||||
}
|
||||
|
||||
/*
|
||||
// permittedAtExactPath checks if a rule exists at the given path and evaluates it if so.
|
||||
func (s *Storage) permittedAtExactPath(sourcePath, indexPath, username string) (bool, bool) {
|
||||
s.mux.RLock()
|
||||
@@ -430,7 +366,6 @@ func (s *Storage) permittedRule(rule *AccessRule, username string, sourcePath st
|
||||
|
||||
return true
|
||||
}
|
||||
*/
|
||||
|
||||
// isUserInGroup checks if a username is in a group.
|
||||
func (s *Storage) isUserInGroup(username, group string) bool {
|
||||
@@ -481,6 +416,8 @@ func (s *Storage) GetFrontendRules(sourcePath, indexPath string) (FrontendAccess
|
||||
frontendRules.Deny.Groups = utils.NonNilSlice(slices.Collect(maps.Keys(rule.Deny.Groups)))
|
||||
frontendRules.Allow.Users = utils.NonNilSlice(slices.Collect(maps.Keys(rule.Allow.Users)))
|
||||
frontendRules.Allow.Groups = utils.NonNilSlice(slices.Collect(maps.Keys(rule.Allow.Groups)))
|
||||
|
||||
logger.Debugf("Returning frontend rules for source: %s and index: %s", sourcePath, indexPath)
|
||||
return frontendRules, ok
|
||||
}
|
||||
|
||||
|
||||
@@ -405,77 +405,3 @@ func TestPermitted_DenyByDefaultWithDenyAll(t *testing.T) {
|
||||
t.Error("bob should not be permitted (DenyByDefault is true and no allow rule)")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPermitted_DenyByDefault_AdminRootAccess(t *testing.T) {
|
||||
// 1. Setup
|
||||
access.ClearCache()
|
||||
s, userStore := createTestStorage(t)
|
||||
createTestUser(t, userStore, "admin")
|
||||
createTestUser(t, userStore, "graham")
|
||||
createTestUser(t, userStore, "bob")
|
||||
err := s.LoadFromDB()
|
||||
if err != nil && err != storm.ErrNotFound {
|
||||
t.Errorf("unexpected error loading from DB: %v", err)
|
||||
}
|
||||
|
||||
// 2. Configure source with DenyByDefault: true
|
||||
originalSourceMap := settings.Config.Server.SourceMap
|
||||
defer func() {
|
||||
settings.Config.Server.SourceMap = originalSourceMap
|
||||
access.ClearCache()
|
||||
}()
|
||||
settings.Config.Server.SourceMap = map[string]settings.Source{
|
||||
"mnt/secure": {
|
||||
Path: "mnt/secure",
|
||||
Name: "secure",
|
||||
Config: settings.SourceConfig{
|
||||
DenyByDefault: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// 3. Set up access rules
|
||||
if err := s.AllowUser("mnt/secure", "/", "admin"); err != nil {
|
||||
t.Fatalf("AllowUser for admin failed: %v", err)
|
||||
}
|
||||
if err := s.AllowUser("mnt/secure", "/test", "graham"); err != nil {
|
||||
t.Fatalf("AllowUser for graham failed: %v", err)
|
||||
}
|
||||
|
||||
// 4. Assertions
|
||||
// Admin checks
|
||||
if !s.Permitted("mnt/secure", "/", "admin") {
|
||||
t.Error("admin should be permitted for /")
|
||||
}
|
||||
if !s.Permitted("mnt/secure", "/test", "admin") {
|
||||
t.Error("admin should be permitted for /test")
|
||||
}
|
||||
if !s.Permitted("mnt/secure", "/test/sub", "admin") {
|
||||
t.Error("admin should be permitted for /test/sub (this is the bug)")
|
||||
}
|
||||
|
||||
// Graham checks
|
||||
if !s.Permitted("mnt/secure", "/test", "graham") {
|
||||
t.Error("graham should be permitted for /test")
|
||||
}
|
||||
if !s.Permitted("mnt/secure", "/test/sub", "graham") {
|
||||
t.Error("graham should be permitted for /test/sub")
|
||||
}
|
||||
if s.Permitted("mnt/secure", "/", "graham") {
|
||||
t.Error("graham should NOT be permitted for /")
|
||||
}
|
||||
if s.Permitted("mnt/secure", "/anotherfolder", "graham") {
|
||||
t.Error("graham should NOT be permitted for /anotherfolder")
|
||||
}
|
||||
|
||||
// Bob checks (should have no access)
|
||||
if s.Permitted("mnt/secure", "/", "bob") {
|
||||
t.Error("bob should NOT be permitted for /")
|
||||
}
|
||||
if s.Permitted("mnt/secure", "/test", "bob") {
|
||||
t.Error("bob should NOT be permitted for /test")
|
||||
}
|
||||
if s.Permitted("mnt/secure", "/test/sub", "bob") {
|
||||
t.Error("bob should NOT be permitted for /test/sub")
|
||||
}
|
||||
}
|
||||
@@ -4,31 +4,28 @@ import "sync"
|
||||
|
||||
type CommonShare struct {
|
||||
//AllowEdit bool `json:"allowEdit,omitempty"`
|
||||
AllowUpload bool `json:"allowUpload,omitempty"`
|
||||
DisableFileViewer bool `json:"disableFileViewer,omitempty"`
|
||||
DownloadsLimit int `json:"downloadsLimit,omitempty"`
|
||||
ShareTheme string `json:"shareTheme,omitempty"`
|
||||
DisableAnonymous bool `json:"disableAnonymous,omitempty"`
|
||||
MaxBandwidth int `json:"maxBandwidth,omitempty"`
|
||||
DisableThumbnails bool `json:"disableThumbnails,omitempty"`
|
||||
KeepAfterExpiration bool `json:"keepAfterExpiration,omitempty"`
|
||||
AllowedUsernames []string `json:"allowedUsernames,omitempty"`
|
||||
ThemeColor string `json:"themeColor,omitempty"`
|
||||
Banner string `json:"banner,omitempty"`
|
||||
Title string `json:"title,omitempty"`
|
||||
Description string `json:"description,omitempty"`
|
||||
Favicon string `json:"favicon,omitempty"`
|
||||
QuickDownload bool `json:"quickDownload,omitempty"`
|
||||
HideNavButtons bool `json:"hideNavButtons,omitempty"`
|
||||
DisableSidebar bool `json:"disableSidebar"`
|
||||
ViewMode string `json:"viewMode,omitempty"`
|
||||
Source string `json:"source,omitempty"` // backend source is path to maintain between name changes
|
||||
Path string `json:"path,omitempty"`
|
||||
DownloadURL string `json:"downloadURL,omitempty"`
|
||||
DisableShareCard bool `json:"disableShareCard,omitempty"`
|
||||
EnforceDarkLightMode string `json:"enforceDarkLightMode,omitempty"` // "dark" or "light"
|
||||
EnableOnlyOffice bool `json:"enableOnlyOffice,omitempty"`
|
||||
EnableOnlyOfficeEditing bool `json:"enableOnlyOfficeEditing,omitempty"`
|
||||
AllowUpload bool `json:"allowUpload,omitempty"`
|
||||
DisableFileViewer bool `json:"disableFileViewer,omitempty"`
|
||||
DownloadsLimit int `json:"downloadsLimit,omitempty"`
|
||||
ShareTheme string `json:"shareTheme,omitempty"`
|
||||
DisableAnonymous bool `json:"disableAnonymous,omitempty"`
|
||||
MaxBandwidth int `json:"maxBandwidth,omitempty"`
|
||||
DisableThumbnails bool `json:"disableThumbnails,omitempty"`
|
||||
KeepAfterExpiration bool `json:"keepAfterExpiration,omitempty"`
|
||||
AllowedUsernames []string `json:"allowedUsernames,omitempty"`
|
||||
ThemeColor string `json:"themeColor,omitempty"`
|
||||
Banner string `json:"banner,omitempty"`
|
||||
Title string `json:"title,omitempty"`
|
||||
Description string `json:"description,omitempty"`
|
||||
Favicon string `json:"favicon,omitempty"`
|
||||
QuickDownload bool `json:"quickDownload,omitempty"`
|
||||
HideNavButtons bool `json:"hideNavButtons,omitempty"`
|
||||
DisableSidebar bool `json:"disableSidebar"`
|
||||
ViewMode string `json:"viewMode,omitempty"`
|
||||
Source string `json:"source,omitempty"` // backend source is path to maintain between name changes
|
||||
Path string `json:"path,omitempty"`
|
||||
DownloadURL string `json:"downloadURL,omitempty"`
|
||||
DisableShareCard bool `json:"disableShareCard,omitempty"`
|
||||
}
|
||||
type CreateBody struct {
|
||||
CommonShare
|
||||
|
||||
@@ -49,7 +49,6 @@ type Preview struct {
|
||||
Office bool `json:"office"` // show preview image for office files
|
||||
PopUp bool `json:"popup"` // show larger popup preview when hovering
|
||||
AutoplayMedia bool `json:"autoplayMedia"` // autoplay media files in preview
|
||||
DefaultMediaPlayer bool `json:"defaultMediaPlayer"` // disable html5 media player and use the default media player
|
||||
}
|
||||
|
||||
// User describes a user.
|
||||
@@ -100,11 +99,10 @@ type NonAdminEditable struct {
|
||||
FileLoading FileLoading `json:"fileLoading"` // upload and download settings
|
||||
DisableOfficePreviewExt string `json:"disableOfficePreviewExt"` // deprecated
|
||||
DisableOnlyOfficeExt string `json:"disableOnlyOfficeExt"` // deprecated
|
||||
DisablePreviewExt string `json:"disablePreviewExt"` // space separated list of file extensions to disable preview for
|
||||
DisableViewingExt string `json:"disableViewingExt"` // space separated list of file extensions to disable viewing for
|
||||
DisablePreviewExt string `json:"disablePreviewExt"` // comma separated list of file extensions to disable preview for
|
||||
DisableViewingExt string `json:"disableViewingExt"` // comma separated list of file extensions to disable viewing for
|
||||
CustomTheme string `json:"customTheme"` // Name of theme to use chosen from custom themes config.
|
||||
ShowSelectMultiple bool `json:"showSelectMultiple"` // show select multiple files on desktop
|
||||
DebugOffice bool `json:"debugOffice"` // debug onlyoffice editor
|
||||
}
|
||||
|
||||
type FileLoading struct {
|
||||
@@ -112,6 +110,10 @@ type FileLoading struct {
|
||||
ChunkSize int `json:"uploadChunkSizeMb"`
|
||||
}
|
||||
|
||||
var AnonymousUser = User{
|
||||
Username: "anonymous", // temp user not registered
|
||||
}
|
||||
|
||||
func CleanUsername(s string) string {
|
||||
// Remove any trailing space to avoid ending on -
|
||||
s = strings.Trim(s, " ")
|
||||
|
||||
@@ -75,12 +75,7 @@ func Register(username string, sources []string) chan EventMessage {
|
||||
|
||||
func Unregister(username string, ch chan EventMessage) {
|
||||
userClientsMu.Lock()
|
||||
defer userClientsMu.Unlock()
|
||||
conns, ok := userClients[username]
|
||||
if !ok {
|
||||
// User already cleaned up by Shutdown, nothing to do.
|
||||
return
|
||||
}
|
||||
conns := userClients[username]
|
||||
for i, c := range conns {
|
||||
if c == ch {
|
||||
userClients[username] = append(conns[:i], conns[i+1:]...)
|
||||
@@ -90,15 +85,18 @@ func Unregister(username string, ch chan EventMessage) {
|
||||
if len(userClients[username]) == 0 {
|
||||
delete(userClients, username)
|
||||
}
|
||||
userClientsMu.Unlock()
|
||||
|
||||
sourceClientsMu.Lock()
|
||||
defer sourceClientsMu.Unlock()
|
||||
for source, clients := range sourceClients {
|
||||
delete(clients, ch)
|
||||
if len(clients) == 0 {
|
||||
delete(sourceClients, source)
|
||||
}
|
||||
}
|
||||
sourceClientsMu.Unlock()
|
||||
|
||||
close(ch)
|
||||
}
|
||||
|
||||
func SendToUsers(eventType, message string, users []string) {
|
||||
@@ -140,24 +138,3 @@ func handleSourceUpdates() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func Shutdown() {
|
||||
userClientsMu.Lock()
|
||||
defer userClientsMu.Unlock()
|
||||
sourceClientsMu.Lock()
|
||||
defer sourceClientsMu.Unlock()
|
||||
|
||||
for username, clientChannels := range userClients {
|
||||
for _, ch := range clientChannels {
|
||||
// Clean up source clients
|
||||
for source, clients := range sourceClients {
|
||||
delete(clients, ch)
|
||||
if len(clients) == 0 {
|
||||
delete(sourceClients, source)
|
||||
}
|
||||
}
|
||||
close(ch)
|
||||
}
|
||||
delete(userClients, username)
|
||||
}
|
||||
}
|
||||
@@ -1,341 +0,0 @@
|
||||
package ffmpeg
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ImageService handles image operations with ffmpeg
|
||||
type ImageService struct {
|
||||
ffmpegPath string
|
||||
ffprobePath string
|
||||
debug bool
|
||||
}
|
||||
|
||||
// NewImageService creates a new image service instance
|
||||
func NewImageService(ffmpegPath, ffprobePath string, debug bool) *ImageService {
|
||||
return &ImageService{
|
||||
ffmpegPath: ffmpegPath,
|
||||
ffprobePath: ffprobePath,
|
||||
debug: debug,
|
||||
}
|
||||
}
|
||||
|
||||
// GetImageDimensions extracts the dimensions of an image file using ffprobe
|
||||
func (s *ImageService) GetImageDimensions(imagePath string) (width, height int, err error) {
|
||||
fmt.Printf("🔍 IMAGE SERVICE: Getting dimensions for %s\n", filepath.Base(imagePath))
|
||||
fmt.Printf("🔧 IMAGE SERVICE: Using ffprobe at: %s\n", s.ffprobePath)
|
||||
|
||||
// Get HEIC dimensions (fallback to individual stream dimensions for nowe re
|
||||
probeCmd := exec.Command(
|
||||
s.ffprobePath,
|
||||
"-v", "error",
|
||||
"-select_streams", "v:0",
|
||||
"-show_entries", "stream=width,height",
|
||||
"-of", "csv=p=0:s=x",
|
||||
imagePath,
|
||||
)
|
||||
|
||||
fmt.Printf("🚀 IMAGE SERVICE: Running command: %s %s\n", s.ffprobePath, strings.Join(probeCmd.Args[1:], " "))
|
||||
|
||||
var probeOut bytes.Buffer
|
||||
var probeErr bytes.Buffer
|
||||
probeCmd.Stdout = &probeOut
|
||||
probeCmd.Stderr = &probeErr
|
||||
|
||||
if err = probeCmd.Run(); err != nil {
|
||||
fmt.Printf("❌ IMAGE SERVICE: ffprobe failed with error: %v\n", err)
|
||||
fmt.Printf("❌ IMAGE SERVICE: ffprobe stderr: %s\n", probeErr.String())
|
||||
return 0, 0, fmt.Errorf("ffprobe command failed on image file '%s': %w", imagePath, err)
|
||||
}
|
||||
|
||||
dimensions := strings.TrimSpace(probeOut.String())
|
||||
fmt.Printf("📏 IMAGE SERVICE: Got raw dimensions string: '%s'\n", dimensions)
|
||||
|
||||
// Handle cases where ffprobe returns multiple lines or extra characters
|
||||
// Take the first line and clean it up
|
||||
lines := strings.Split(dimensions, "\n")
|
||||
cleanDimensions := strings.TrimSpace(lines[0])
|
||||
|
||||
// Remove any trailing 'x' characters that might appear
|
||||
cleanDimensions = strings.TrimSuffix(cleanDimensions, "x")
|
||||
|
||||
fmt.Printf("📏 IMAGE SERVICE: Cleaned dimensions string: '%s'\n", cleanDimensions)
|
||||
|
||||
parts := strings.Split(cleanDimensions, "x")
|
||||
if len(parts) != 2 {
|
||||
fmt.Printf("❌ IMAGE SERVICE: Invalid dimensions format after cleaning: %s\n", cleanDimensions)
|
||||
return 0, 0, fmt.Errorf("invalid dimensions format: %s", cleanDimensions)
|
||||
}
|
||||
|
||||
width, err = strconv.Atoi(parts[0])
|
||||
if err != nil {
|
||||
fmt.Printf("❌ IMAGE SERVICE: Invalid width: %s\n", parts[0])
|
||||
return 0, 0, fmt.Errorf("invalid width: %w", err)
|
||||
}
|
||||
|
||||
height, err = strconv.Atoi(parts[1])
|
||||
if err != nil {
|
||||
fmt.Printf("❌ IMAGE SERVICE: Invalid height: %s\n", parts[1])
|
||||
return 0, 0, fmt.Errorf("invalid height: %w", err)
|
||||
}
|
||||
|
||||
fmt.Printf("✅ IMAGE SERVICE: Parsed dimensions: %dx%d\n", width, height)
|
||||
return width, height, nil
|
||||
}
|
||||
|
||||
// ConvertHEICToJPEG converts a HEIC file to JPEG with specified dimensions and quality using proper tile extraction
|
||||
func (s *ImageService) ConvertHEICToJPEG(heicPath string, targetWidth, targetHeight int, quality string) ([]byte, error) {
|
||||
fmt.Printf("🎯 IMAGE SERVICE: Converting HEIC %s to JPEG (%dx%d, quality %s) using proper tile extraction\n", filepath.Base(heicPath), targetWidth, targetHeight, quality)
|
||||
|
||||
// Create temporary directory for tile processing
|
||||
outputDir := filepath.Dir(heicPath)
|
||||
tempDir := filepath.Join(outputDir, fmt.Sprintf("heic_tiles_%d", os.Getpid()))
|
||||
err := os.MkdirAll(tempDir, 0755)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create temp directory: %w", err)
|
||||
}
|
||||
defer func() {
|
||||
fmt.Printf("🧹 IMAGE SERVICE: Cleaning up temporary directory: %s\n", tempDir)
|
||||
os.RemoveAll(tempDir)
|
||||
}()
|
||||
|
||||
// Step 1: Get grid info using trace output
|
||||
fmt.Printf("📐 IMAGE SERVICE: Step 1 - Getting grid info from HEIC trace\n")
|
||||
|
||||
// Use ffprobe with trace level to get grid information
|
||||
gridCmd := exec.Command(s.ffmpegPath, "-loglevel", "trace", "-i", heicPath, "-f", "null", "-")
|
||||
var gridOut bytes.Buffer
|
||||
var gridErr bytes.Buffer
|
||||
gridCmd.Stdout = &gridOut
|
||||
gridCmd.Stderr = &gridErr
|
||||
_ = gridCmd.Run() // Don't check error, we're just extracting info
|
||||
|
||||
traceOutput := gridErr.String()
|
||||
fmt.Printf("📊 IMAGE SERVICE: Got trace output: %d bytes\n", len(traceOutput))
|
||||
|
||||
// Parse grid dimensions from trace output
|
||||
var gridCols, gridRows = 8, 6 // Default fallback
|
||||
var val int
|
||||
|
||||
// Look for grid_row and grid_col in trace output
|
||||
lines := strings.Split(traceOutput, "\n")
|
||||
for _, line := range lines {
|
||||
if strings.Contains(line, "grid_row") {
|
||||
// Try to extract grid_row value
|
||||
if parts := strings.Split(line, "grid_row"); len(parts) > 1 {
|
||||
if matches := strings.Fields(parts[1]); len(matches) > 0 {
|
||||
if val, err = strconv.Atoi(strings.Trim(matches[0], " :=")); err == nil {
|
||||
gridRows = val + 1 // HEIC grid_row is 0-based
|
||||
fmt.Printf("📐 IMAGE SERVICE: Parsed grid_row: %d (rows: %d)\n", val, gridRows)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if strings.Contains(line, "grid_col") {
|
||||
// Try to extract grid_col value
|
||||
if parts := strings.Split(line, "grid_col"); len(parts) > 1 {
|
||||
if matches := strings.Fields(parts[1]); len(matches) > 0 {
|
||||
if val, err = strconv.Atoi(strings.Trim(matches[0], " :=")); err == nil {
|
||||
gridCols = val + 1 // HEIC grid_col is 0-based
|
||||
fmt.Printf("📐 IMAGE SERVICE: Parsed grid_col: %d (cols: %d)\n", val, gridCols)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Printf("📊 IMAGE SERVICE: Using grid dimensions: %dx%d\n", gridCols, gridRows)
|
||||
|
||||
// Use FFmpeg's auto-orientation feature instead of forced rotation
|
||||
fmt.Printf("🔄 IMAGE SERVICE: Using FFmpeg auto-orientation for proper image orientation\n")
|
||||
orientationFilter := ",auto-orient=1"
|
||||
|
||||
// Step 2: Extract tile streams (skip stream 0 which is compatibility image)
|
||||
fmt.Printf("🔧 IMAGE SERVICE: Step 2 - Extracting tile streams\n")
|
||||
|
||||
// Extract using the exact approach that works: ffmpeg -i input.heic -map 0 output_%d.png
|
||||
// But we'll skip the first output (stream 0) since it's the compatibility image
|
||||
tilesPattern := filepath.Join(tempDir, "output_%d.png")
|
||||
|
||||
extractCmd := exec.Command(
|
||||
s.ffmpegPath,
|
||||
"-i", heicPath,
|
||||
"-map", "0", // Map all streams
|
||||
"-y",
|
||||
tilesPattern,
|
||||
)
|
||||
|
||||
var extractOut bytes.Buffer
|
||||
var extractErr bytes.Buffer
|
||||
extractCmd.Stdout = &extractOut
|
||||
extractCmd.Stderr = &extractErr
|
||||
|
||||
fmt.Printf("🚀 IMAGE SERVICE: Running tile extraction: %s %s\n", s.ffmpegPath, strings.Join(extractCmd.Args[1:], " "))
|
||||
|
||||
if err = extractCmd.Run(); err != nil {
|
||||
fmt.Printf("❌ IMAGE SERVICE: Tile extraction failed: %v\n", err)
|
||||
fmt.Printf("❌ IMAGE SERVICE: stderr: %s\n", extractErr.String())
|
||||
return nil, fmt.Errorf("tile extraction failed: %w", err)
|
||||
}
|
||||
|
||||
fmt.Printf("✅ IMAGE SERVICE: Tiles extracted successfully\n")
|
||||
|
||||
// Step 3: Filter tiles (skip output_0.png which is compatibility image)
|
||||
fmt.Printf("🔧 IMAGE SERVICE: Step 3 - Filtering tiles\n")
|
||||
|
||||
files, err := filepath.Glob(filepath.Join(tempDir, "output_*.png"))
|
||||
if err != nil || len(files) == 0 {
|
||||
return nil, fmt.Errorf("no tiles extracted, found %d files", len(files))
|
||||
}
|
||||
|
||||
fmt.Printf("📊 IMAGE SERVICE: Found %d extracted files\n", len(files))
|
||||
|
||||
// Create filtered tiles, skipping output_0.png (compatibility image)
|
||||
// and rename them to sequential tile_001.png, tile_002.png, etc.
|
||||
tileIndex := 1
|
||||
expectedTiles := gridCols * gridRows
|
||||
|
||||
for i := 1; i <= len(files); i++ { // Start from 1 to skip output_0.png
|
||||
sourceFile := filepath.Join(tempDir, fmt.Sprintf("output_%d.png", i))
|
||||
var info os.FileInfo
|
||||
if info, err = os.Stat(sourceFile); err == nil && info.Size() > 500 {
|
||||
// This is a valid tile, rename it to sequential format
|
||||
targetFile := filepath.Join(tempDir, fmt.Sprintf("tile_%03d.png", tileIndex))
|
||||
if err = os.Rename(sourceFile, targetFile); err != nil {
|
||||
fmt.Printf("⚠️ IMAGE SERVICE: Failed to rename %s: %v\n", filepath.Base(sourceFile), err)
|
||||
continue
|
||||
}
|
||||
fmt.Printf("✅ IMAGE SERVICE: Valid tile %d: %s (%d bytes)\n", tileIndex, filepath.Base(targetFile), info.Size())
|
||||
tileIndex++
|
||||
|
||||
// Stop if we have enough tiles
|
||||
if tileIndex > expectedTiles {
|
||||
break
|
||||
}
|
||||
} else {
|
||||
fmt.Printf("⚠️ IMAGE SERVICE: Skipping invalid tile: output_%d.png\n", i)
|
||||
}
|
||||
}
|
||||
|
||||
actualTiles := tileIndex - 1
|
||||
fmt.Printf("📊 IMAGE SERVICE: Using %d valid tiles (expected %d) with grid %dx%d\n", actualTiles, expectedTiles, gridCols, gridRows)
|
||||
|
||||
// Step 4: Merge tiles back into final image with proper quality and rotation
|
||||
outputFile := filepath.Join(tempDir, "final_output.jpg")
|
||||
|
||||
// Build the video filter chain
|
||||
inputPattern := filepath.Join(tempDir, "tile_%03d.png")
|
||||
|
||||
// Create filter chain: tile assembly + auto-orientation + proper scaling
|
||||
var filterChain string
|
||||
|
||||
// For original size (targetWidth=0, targetHeight=0), don't scale
|
||||
if targetWidth == 0 && targetHeight == 0 {
|
||||
// This is original size - no scaling, maximum quality
|
||||
// Use padding=0:margin=0 to eliminate black borders between tiles
|
||||
filterChain = fmt.Sprintf("tile=%dx%d:nb_frames=%d:padding=0:margin=0%s",
|
||||
gridCols, gridRows, actualTiles, orientationFilter)
|
||||
fmt.Printf("🎯 IMAGE SERVICE: Using ORIGINAL size - no scaling, auto-orientation applied\n")
|
||||
} else {
|
||||
// This is a resize request - apply scaling with proper aspect handling
|
||||
filterChain = fmt.Sprintf("tile=%dx%d:nb_frames=%d:padding=0:margin=0%s,scale=%d:%d:force_original_aspect_ratio=decrease:eval=frame",
|
||||
gridCols, gridRows, actualTiles, orientationFilter, targetWidth, targetHeight)
|
||||
fmt.Printf("🔽 IMAGE SERVICE: Scaling to %dx%d with auto-orientation applied\n", targetWidth, targetHeight)
|
||||
}
|
||||
|
||||
fmt.Printf("🎨 IMAGE SERVICE: Using filter chain: %s\n", filterChain)
|
||||
|
||||
mergeCmd := exec.Command(
|
||||
s.ffmpegPath,
|
||||
"-i", inputPattern,
|
||||
"-vf", filterChain,
|
||||
"-frames:v", "1",
|
||||
"-q:v", quality, // Use the quality setting passed in
|
||||
"-pix_fmt", "yuvj420p", // Use full-range YUV for better JPEG compatibility
|
||||
"-y",
|
||||
outputFile,
|
||||
)
|
||||
|
||||
var mergeOut bytes.Buffer
|
||||
var mergeErr bytes.Buffer
|
||||
mergeCmd.Stdout = &mergeOut
|
||||
mergeCmd.Stderr = &mergeErr
|
||||
|
||||
fmt.Printf("🚀 IMAGE SERVICE: Running tile merge: %s %s\n", s.ffmpegPath, strings.Join(mergeCmd.Args[1:], " "))
|
||||
|
||||
if err = mergeCmd.Run(); err != nil {
|
||||
fmt.Printf("❌ IMAGE SERVICE: Tile merge failed: %v\n", err)
|
||||
fmt.Printf("❌ IMAGE SERVICE: stderr: %s\n", mergeErr.String())
|
||||
return nil, fmt.Errorf("tile merge failed: %w", err)
|
||||
}
|
||||
|
||||
fmt.Printf("✅ IMAGE SERVICE: Tiles merged successfully\n")
|
||||
|
||||
fmt.Printf("📖 IMAGE SERVICE: Reading final merged JPEG file: %s\n", outputFile)
|
||||
jpegBytes, err := os.ReadFile(outputFile)
|
||||
if err != nil {
|
||||
fmt.Printf("❌ IMAGE SERVICE: Failed to read merged JPEG: %v\n", err)
|
||||
return nil, fmt.Errorf("failed to read merged JPEG: %w", err)
|
||||
}
|
||||
|
||||
fmt.Printf("✅ IMAGE SERVICE: Successfully read %d bytes from merged JPEG\n", len(jpegBytes))
|
||||
return jpegBytes, nil
|
||||
}
|
||||
|
||||
// ConvertImageToFormat converts any supported image format to another format
|
||||
func (s *ImageService) ConvertImageToFormat(inputPath, outputFormat string, targetWidth, targetHeight int, quality string) ([]byte, error) {
|
||||
// Create temporary output file
|
||||
outputDir := filepath.Dir(inputPath)
|
||||
ext := ".jpg"
|
||||
if outputFormat == "png" {
|
||||
ext = ".png"
|
||||
}
|
||||
outputFile := filepath.Join(outputDir, fmt.Sprintf("converted_%d_%d%s", targetWidth, targetHeight, ext))
|
||||
defer os.Remove(outputFile) // Clean up
|
||||
|
||||
// Build FFmpeg command
|
||||
args := []string{
|
||||
"-i", inputPath,
|
||||
}
|
||||
|
||||
// Add scaling and auto-orientation
|
||||
if targetWidth > 0 && targetHeight > 0 {
|
||||
scaleFilter := fmt.Sprintf("scale=%d:%d:force_original_aspect_ratio=decrease,auto-orient=1", targetWidth, targetHeight)
|
||||
args = append(args, "-vf", scaleFilter)
|
||||
} else {
|
||||
args = append(args, "-vf", "auto-orient=1")
|
||||
}
|
||||
|
||||
// Add quality settings
|
||||
if quality != "" {
|
||||
args = append(args, "-q:v", quality)
|
||||
}
|
||||
|
||||
// Add output file and overwrite flag
|
||||
args = append(args, "-y", outputFile)
|
||||
|
||||
cmd := exec.Command(s.ffmpegPath, args...)
|
||||
|
||||
if s.debug {
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
}
|
||||
|
||||
if err := cmd.Run(); err != nil {
|
||||
return nil, fmt.Errorf("ffmpeg image conversion failed on file '%s': %w", inputPath, err)
|
||||
}
|
||||
|
||||
// Read the converted file
|
||||
convertedBytes, err := os.ReadFile(outputFile)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read converted image: %w", err)
|
||||
}
|
||||
|
||||
return convertedBytes, nil
|
||||
}
|
||||
@@ -1,289 +0,0 @@
|
||||
package ffmpeg
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gtsteffaniak/filebrowser/backend/common/utils"
|
||||
"github.com/gtsteffaniak/go-logger/logger"
|
||||
)
|
||||
|
||||
// SubtitleTrack represents a subtitle track (embedded or external file)
|
||||
type SubtitleTrack struct {
|
||||
Name string `json:"name"` // filename for external, or descriptive name for embedded
|
||||
Language string `json:"language,omitempty"` // language code
|
||||
Title string `json:"title,omitempty"` // title/description
|
||||
Index *int `json:"index,omitempty"` // stream index for embedded subtitles (nil for external)
|
||||
Codec string `json:"codec,omitempty"` // codec name for embedded subtitles
|
||||
Content string `json:"content,omitempty"` // subtitle content
|
||||
IsFile bool `json:"isFile"` // true for external files, false for embedded streams
|
||||
}
|
||||
|
||||
// FFProbeOutput represents the JSON output from ffprobe
|
||||
type FFProbeOutput struct {
|
||||
Streams []struct {
|
||||
Index int `json:"index"`
|
||||
CodecType string `json:"codec_type"`
|
||||
CodecName string `json:"codec_name"`
|
||||
Tags map[string]string `json:"tags,omitempty"`
|
||||
Disposition map[string]int `json:"disposition,omitempty"`
|
||||
} `json:"streams"`
|
||||
}
|
||||
|
||||
// DetectAllSubtitles finds both embedded and external subtitle tracks
|
||||
func DetectAllSubtitles(videoPath string, parentDir string, modtime time.Time) []SubtitleTrack {
|
||||
key := "all_subtitles:" + videoPath + ":" + modtime.Format(time.RFC3339)
|
||||
|
||||
// Check cache first
|
||||
if cached, ok := utils.MediaCache.Get(key).([]SubtitleTrack); ok {
|
||||
return cached
|
||||
}
|
||||
|
||||
var allSubtitles []SubtitleTrack
|
||||
|
||||
// First, get embedded subtitles
|
||||
embeddedSubs := detectEmbeddedSubtitles(videoPath)
|
||||
allSubtitles = append(allSubtitles, embeddedSubs...)
|
||||
|
||||
// Then, get external subtitle files
|
||||
externalSubs := detectExternalSubtitles(videoPath, parentDir)
|
||||
allSubtitles = append(allSubtitles, externalSubs...)
|
||||
|
||||
// Cache the complete list
|
||||
utils.MediaCache.Set(key, allSubtitles)
|
||||
|
||||
return allSubtitles
|
||||
}
|
||||
|
||||
// detectEmbeddedSubtitles uses ffprobe to find embedded subtitle tracks
|
||||
func detectEmbeddedSubtitles(realPath string) []SubtitleTrack {
|
||||
cmd := exec.Command("ffprobe",
|
||||
"-v", "quiet",
|
||||
"-print_format", "json",
|
||||
"-show_streams",
|
||||
"-select_streams", "s",
|
||||
realPath)
|
||||
|
||||
output, err := cmd.Output()
|
||||
if err != nil {
|
||||
logger.Debug("ffprobe failed for file: " + realPath + ", error: " + err.Error())
|
||||
return nil
|
||||
}
|
||||
|
||||
var probeOutput FFProbeOutput
|
||||
if err := json.Unmarshal(output, &probeOutput); err != nil {
|
||||
logger.Debug("failed to parse ffprobe output for file: " + realPath)
|
||||
return nil
|
||||
}
|
||||
|
||||
var subtitles []SubtitleTrack
|
||||
for _, stream := range probeOutput.Streams {
|
||||
if stream.CodecType == "subtitle" {
|
||||
track := SubtitleTrack{
|
||||
Index: &stream.Index,
|
||||
Codec: stream.CodecName,
|
||||
IsFile: false, // This is an embedded subtitle
|
||||
}
|
||||
|
||||
// Extract language and title from tags
|
||||
if stream.Tags != nil {
|
||||
if lang, ok := stream.Tags["language"]; ok {
|
||||
track.Language = lang
|
||||
}
|
||||
if title, ok := stream.Tags["title"]; ok {
|
||||
track.Title = title
|
||||
}
|
||||
}
|
||||
|
||||
// Generate a descriptive name
|
||||
if track.Title != "" {
|
||||
track.Name = track.Title
|
||||
} else if track.Language != "" {
|
||||
track.Name = "Embedded (" + track.Language + ")"
|
||||
} else {
|
||||
track.Name = "Embedded Subtitle " + strconv.Itoa(stream.Index)
|
||||
}
|
||||
|
||||
subtitles = append(subtitles, track)
|
||||
}
|
||||
}
|
||||
return subtitles
|
||||
}
|
||||
|
||||
// detectExternalSubtitles finds external subtitle files in the same directory
|
||||
func detectExternalSubtitles(videoPath string, parentDir string) []SubtitleTrack {
|
||||
var subtitles []SubtitleTrack
|
||||
|
||||
// Get the base name of the video (without extension)
|
||||
videoBaseName := strings.TrimSuffix(filepath.Base(videoPath), filepath.Ext(videoPath))
|
||||
|
||||
// Common subtitle extensions
|
||||
subtitleExts := []string{".srt", ".vtt", ".lrc", ".sbv", ".ass", ".ssa", ".sub", ".smi"}
|
||||
|
||||
// Look for subtitle files with matching base name
|
||||
for _, ext := range subtitleExts {
|
||||
subtitlePath := filepath.Join(parentDir, videoBaseName+ext)
|
||||
if _, err := os.Stat(subtitlePath); err == nil {
|
||||
// File exists
|
||||
track := SubtitleTrack{
|
||||
Name: filepath.Base(subtitlePath),
|
||||
IsFile: true, // This is an external file
|
||||
}
|
||||
|
||||
// Try to infer language from filename patterns like "video.en.srt"
|
||||
parts := strings.Split(videoBaseName, ".")
|
||||
if len(parts) > 1 {
|
||||
// Check if the last part before extension looks like a language code
|
||||
lastPart := parts[len(parts)-1]
|
||||
if len(lastPart) == 2 || len(lastPart) == 3 {
|
||||
track.Language = lastPart
|
||||
}
|
||||
}
|
||||
|
||||
subtitles = append(subtitles, track)
|
||||
}
|
||||
}
|
||||
|
||||
return subtitles
|
||||
}
|
||||
|
||||
// ExtractSubtitleContent extracts subtitle content without service management
|
||||
func ExtractSubtitleContent(videoPath string, streamIndex int) (string, error) {
|
||||
cmd := exec.Command("ffmpeg",
|
||||
"-i", videoPath,
|
||||
"-map", fmt.Sprintf("0:%d", streamIndex),
|
||||
"-c:s", "webvtt",
|
||||
"-f", "webvtt",
|
||||
"-") // output to stdout
|
||||
|
||||
output, err := cmd.Output()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("subtitle extraction failed: %v", err)
|
||||
}
|
||||
|
||||
return string(output), nil
|
||||
}
|
||||
|
||||
// LoadAndConvertSubtitleFile loads a subtitle file and converts it to WebVTT format
|
||||
func LoadAndConvertSubtitleFile(subtitlePath string) (string, error) {
|
||||
ext := strings.ToLower(filepath.Ext(subtitlePath))
|
||||
|
||||
if ext == ".vtt" {
|
||||
// Already WebVTT, just read it
|
||||
content, err := os.ReadFile(subtitlePath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(content), nil
|
||||
}
|
||||
|
||||
if ext == ".srt" {
|
||||
// Convert SRT to WebVTT using ffmpeg
|
||||
cmd := exec.Command("ffmpeg",
|
||||
"-i", subtitlePath,
|
||||
"-c:s", "webvtt",
|
||||
"-f", "webvtt",
|
||||
"-") // output to stdout
|
||||
|
||||
output, err := cmd.Output()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to convert SRT to WebVTT: %v", err)
|
||||
}
|
||||
return string(output), nil
|
||||
}
|
||||
|
||||
// For other formats, try to read as plain text and hope for the best
|
||||
// This is a fallback - ideally you'd want format-specific converters
|
||||
content, err := os.ReadFile(subtitlePath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Basic conversion wrapper for non-VTT formats
|
||||
vttHeader := "WEBVTT\n\n"
|
||||
return vttHeader + string(content), nil
|
||||
}
|
||||
|
||||
// ExtractSingleSubtitle extracts content for a specific subtitle track by array index
|
||||
func ExtractSingleSubtitle(videoPath string, parentDir string, trackIndex int, modtime time.Time) (SubtitleTrack, error) {
|
||||
// Get all subtitle tracks
|
||||
allTracks := DetectAllSubtitles(videoPath, parentDir, modtime)
|
||||
|
||||
if trackIndex >= len(allTracks) {
|
||||
return SubtitleTrack{}, fmt.Errorf("subtitle track %d not found (only %d tracks available)", trackIndex, len(allTracks))
|
||||
}
|
||||
|
||||
track := allTracks[trackIndex]
|
||||
|
||||
// Load content based on type
|
||||
if track.IsFile {
|
||||
// Load external subtitle file
|
||||
subtitlePath := filepath.Join(parentDir, track.Name)
|
||||
content, err := LoadAndConvertSubtitleFile(subtitlePath)
|
||||
if err != nil {
|
||||
return SubtitleTrack{}, fmt.Errorf("failed to load external subtitle: %v", err)
|
||||
}
|
||||
track.Content = content
|
||||
} else {
|
||||
// Extract embedded subtitle content
|
||||
if track.Index == nil {
|
||||
return SubtitleTrack{}, fmt.Errorf("embedded subtitle track has no stream index")
|
||||
}
|
||||
content, err := ExtractSubtitleContent(videoPath, *track.Index)
|
||||
if err != nil {
|
||||
return SubtitleTrack{}, fmt.Errorf("failed to extract embedded subtitle: %v", err)
|
||||
}
|
||||
track.Content = content
|
||||
}
|
||||
|
||||
return track, nil
|
||||
}
|
||||
|
||||
// LoadAllSubtitleContent loads the actual content for all detected subtitle tracks
|
||||
func LoadAllSubtitleContent(videoPath string, subtitles []SubtitleTrack, modtime time.Time) error {
|
||||
for idx := range subtitles {
|
||||
subtitle := &subtitles[idx]
|
||||
|
||||
// Check if content is already cached
|
||||
contentKey := fmt.Sprintf("subtitle_content:%s:%d:%s", videoPath, idx, modtime.Format(time.RFC3339))
|
||||
if cached, ok := utils.MediaCache.Get(contentKey).(string); ok {
|
||||
subtitle.Content = cached
|
||||
continue
|
||||
}
|
||||
|
||||
var content string
|
||||
var err error
|
||||
|
||||
if subtitle.IsFile {
|
||||
// Load external subtitle file content and convert to WebVTT
|
||||
subtitlePath := filepath.Join(filepath.Dir(videoPath), subtitle.Name)
|
||||
content, err = LoadAndConvertSubtitleFile(subtitlePath)
|
||||
if err != nil {
|
||||
logger.Debug("failed to read/convert subtitle file " + subtitlePath + ": " + err.Error())
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
// Load embedded subtitle content
|
||||
if subtitle.Index == nil {
|
||||
logger.Debug("embedded subtitle has no stream index")
|
||||
continue
|
||||
}
|
||||
content, err = ExtractSubtitleContent(videoPath, *subtitle.Index)
|
||||
if err != nil {
|
||||
logger.Debug("failed to extract embedded subtitle: " + err.Error())
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
subtitle.Content = content
|
||||
// Cache the content for future requests
|
||||
utils.MediaCache.Set(contentKey, content)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1,147 +0,0 @@
|
||||
package ffmpeg
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/gtsteffaniak/go-logger/logger"
|
||||
)
|
||||
|
||||
// VideoService handles video preview operations with ffmpeg
|
||||
type VideoService struct {
|
||||
ffmpegPath string
|
||||
ffprobePath string
|
||||
debug bool
|
||||
semaphore chan struct{}
|
||||
}
|
||||
|
||||
// NewVideoService creates a new video service instance
|
||||
func NewVideoService(ffmpegPath, ffprobePath string, maxConcurrent int, debug bool) *VideoService {
|
||||
return &VideoService{
|
||||
ffmpegPath: ffmpegPath,
|
||||
ffprobePath: ffprobePath,
|
||||
debug: debug,
|
||||
semaphore: make(chan struct{}, maxConcurrent),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *VideoService) acquire(ctx context.Context) error {
|
||||
select {
|
||||
case s.semaphore <- struct{}{}:
|
||||
return nil
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
}
|
||||
}
|
||||
|
||||
func (s *VideoService) release() {
|
||||
<-s.semaphore
|
||||
}
|
||||
|
||||
// GenerateVideoPreview generates a single preview image from a video using ffmpeg.
|
||||
// videoPath: path to the input video file.
|
||||
// outputPath: path where the generated preview image will be saved (e.g., "/tmp/preview.jpg").
|
||||
// seekTime: how many seconds into the video to seek before capturing the frame.
|
||||
func (s *VideoService) GenerateVideoPreview(videoPath, outputPath string, percentageSeek int) ([]byte, error) {
|
||||
if err := s.acquire(context.Background()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer s.release()
|
||||
|
||||
// Step 1: Get video duration from the container format
|
||||
probeCmd := exec.Command(
|
||||
s.ffprobePath,
|
||||
"-v", "error",
|
||||
// Use format=duration for better compatibility
|
||||
"-show_entries", "format=duration",
|
||||
"-of", "default=noprint_wrappers=1:nokey=1",
|
||||
videoPath,
|
||||
)
|
||||
|
||||
var probeOut bytes.Buffer
|
||||
probeCmd.Stdout = &probeOut
|
||||
if s.debug {
|
||||
probeCmd.Stderr = os.Stderr
|
||||
}
|
||||
if err := probeCmd.Run(); err != nil {
|
||||
logger.Errorf("ffprobe command failed on file '%v' : %v", videoPath, err)
|
||||
return nil, fmt.Errorf("ffprobe failed: %w", err)
|
||||
}
|
||||
|
||||
durationStr := strings.TrimSpace(probeOut.String())
|
||||
if durationStr == "" || durationStr == "N/A" {
|
||||
logger.Errorf("could not determine video duration for file '%v' using duration info '%v'", videoPath, durationStr)
|
||||
return nil, fmt.Errorf("could not determine video duration")
|
||||
}
|
||||
|
||||
durationFloat, err := strconv.ParseFloat(durationStr, 64)
|
||||
if err != nil {
|
||||
// The original error you saw would be caught here if "N/A" was still the output
|
||||
return nil, fmt.Errorf("invalid duration: %v", err)
|
||||
}
|
||||
|
||||
if durationFloat <= 0 {
|
||||
return nil, fmt.Errorf("video duration must be positive")
|
||||
}
|
||||
|
||||
// Step 2: Get the duration of the video in whole seconds
|
||||
duration := int(durationFloat)
|
||||
|
||||
// Step 3: Calculate seek time based on percentageSeek (percentage value)
|
||||
seekSeconds := duration * percentageSeek / 100
|
||||
|
||||
// Step 4: Convert seekSeconds to string for ffmpeg command
|
||||
seekTime := strconv.Itoa(seekSeconds)
|
||||
|
||||
// Step 5: Extract frame at seek time
|
||||
cmd := exec.Command(
|
||||
s.ffmpegPath,
|
||||
"-ss", seekTime,
|
||||
"-i", videoPath,
|
||||
"-frames:v", "1",
|
||||
"-q:v", "10",
|
||||
"-y", // overwrite output
|
||||
outputPath,
|
||||
)
|
||||
|
||||
if s.debug {
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
}
|
||||
err = cmd.Run()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("ffmpeg command failed on file '%v' : %w", videoPath, err)
|
||||
}
|
||||
return os.ReadFile(outputPath)
|
||||
}
|
||||
|
||||
// GetVideoDuration extracts the duration of a video file using ffprobe
|
||||
func GetVideoDuration(ffprobePath string, videoPath string) (float64, error) {
|
||||
cmd := exec.Command(ffprobePath,
|
||||
"-v", "error",
|
||||
"-show_entries", "format=duration",
|
||||
"-of", "default=noprint_wrappers=1:nokey=1",
|
||||
videoPath)
|
||||
|
||||
output, err := cmd.Output()
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("ffprobe failed: %w", err)
|
||||
}
|
||||
|
||||
durationStr := strings.TrimSpace(string(output))
|
||||
if durationStr == "" || durationStr == "N/A" {
|
||||
return 0, fmt.Errorf("could not determine video duration")
|
||||
}
|
||||
|
||||
duration, err := strconv.ParseFloat(durationStr, 64)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("invalid duration: %v", err)
|
||||
}
|
||||
|
||||
return duration, nil
|
||||
}
|
||||
+56
-80
@@ -3,7 +3,6 @@ module github.com/gtsteffaniak/filebrowser/backend
|
||||
go 1.25.0
|
||||
|
||||
require (
|
||||
github.com/adrium/goheif v0.0.0-20230113233934-ca402e77a786
|
||||
github.com/asdine/storm/v3 v3.2.1
|
||||
github.com/coreos/go-oidc/v3 v3.15.0
|
||||
github.com/dsoprea/go-exif/v3 v3.0.1
|
||||
@@ -16,62 +15,53 @@ require (
|
||||
github.com/gtsteffaniak/go-logger v0.1.2
|
||||
github.com/kovidgoyal/imaging v1.6.4
|
||||
github.com/pquerna/otp v1.5.0
|
||||
github.com/spf13/afero v1.15.0
|
||||
github.com/stretchr/testify v1.11.1
|
||||
github.com/spf13/afero v1.14.0
|
||||
github.com/stretchr/testify v1.10.0
|
||||
github.com/swaggo/http-swagger v1.3.4
|
||||
github.com/swaggo/swag v1.16.6
|
||||
golang.org/x/crypto v0.42.0
|
||||
golang.org/x/image v0.31.0
|
||||
golang.org/x/mod v0.28.0
|
||||
golang.org/x/oauth2 v0.31.0
|
||||
golang.org/x/sys v0.36.0
|
||||
golang.org/x/time v0.13.0
|
||||
golang.org/x/crypto v0.41.0
|
||||
golang.org/x/image v0.30.0
|
||||
golang.org/x/mod v0.27.0
|
||||
golang.org/x/oauth2 v0.30.0
|
||||
golang.org/x/sys v0.35.0
|
||||
golang.org/x/time v0.12.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
)
|
||||
|
||||
require (
|
||||
4d63.com/gocheckcompilerdirectives v1.3.0 // indirect
|
||||
4d63.com/gochecknoglobals v0.2.2 // indirect
|
||||
codeberg.org/chavacava/garif v0.2.0 // indirect
|
||||
dario.cat/mergo v1.0.2 // indirect
|
||||
dev.gaijin.team/go/exhaustruct/v4 v4.0.0 // indirect
|
||||
dev.gaijin.team/go/golib v0.6.0 // indirect
|
||||
github.com/4meepo/tagalign v1.4.3 // indirect
|
||||
github.com/Abirdcfly/dupword v0.1.6 // indirect
|
||||
github.com/AlwxSin/noinlineerr v1.0.5 // indirect
|
||||
github.com/4meepo/tagalign v1.4.2 // indirect
|
||||
github.com/Abirdcfly/dupword v0.1.3 // indirect
|
||||
github.com/Antonboom/errname v1.1.0 // indirect
|
||||
github.com/Antonboom/nilnil v1.1.0 // indirect
|
||||
github.com/Antonboom/testifylint v1.6.1 // indirect
|
||||
github.com/BurntSushi/toml v1.5.0 // indirect
|
||||
github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24 // indirect
|
||||
github.com/GaijinEntertainment/go-exhaustruct/v3 v3.3.1 // indirect
|
||||
github.com/KyleBanks/depth v1.2.1 // indirect
|
||||
github.com/Masterminds/semver/v3 v3.3.1 // indirect
|
||||
github.com/OpenPeeDeeP/depguard/v2 v2.2.1 // indirect
|
||||
github.com/air-verse/air v1.63.0 // indirect
|
||||
github.com/alecthomas/chroma/v2 v2.20.0 // indirect
|
||||
github.com/alecthomas/chroma/v2 v2.17.2 // indirect
|
||||
github.com/alecthomas/go-check-sumtype v0.3.1 // indirect
|
||||
github.com/alexkohler/nakedret/v2 v2.0.6 // indirect
|
||||
github.com/alexkohler/prealloc v1.0.0 // indirect
|
||||
github.com/alfatraining/structtag v1.0.0 // indirect
|
||||
github.com/alingse/asasalint v0.0.11 // indirect
|
||||
github.com/alingse/nilnesserr v0.2.0 // indirect
|
||||
github.com/ashanbrown/forbidigo/v2 v2.1.0 // indirect
|
||||
github.com/ashanbrown/makezero/v2 v2.0.1 // indirect
|
||||
github.com/ashanbrown/forbidigo v1.6.0 // indirect
|
||||
github.com/ashanbrown/makezero v1.2.0 // indirect
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/bep/godartsass/v2 v2.5.0 // indirect
|
||||
github.com/bep/golibsass v1.2.0 // indirect
|
||||
github.com/bkielbasa/cyclop v1.2.3 // indirect
|
||||
github.com/blizzy78/varnamelen v0.8.0 // indirect
|
||||
github.com/bombsimon/wsl/v4 v4.7.0 // indirect
|
||||
github.com/bombsimon/wsl/v5 v5.1.1 // indirect
|
||||
github.com/boombuler/barcode v1.1.0 // indirect
|
||||
github.com/breml/bidichk v0.3.3 // indirect
|
||||
github.com/breml/errchkjson v0.4.1 // indirect
|
||||
github.com/butuzov/ireturn v0.4.0 // indirect
|
||||
github.com/butuzov/mirror v1.3.0 // indirect
|
||||
github.com/catenacyber/perfsprint v0.9.1 // indirect
|
||||
github.com/ccojocar/zxcvbn-go v1.0.4 // indirect
|
||||
github.com/ccojocar/zxcvbn-go v1.0.2 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/charithe/durationcheck v0.0.10 // indirect
|
||||
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc // indirect
|
||||
@@ -79,11 +69,11 @@ require (
|
||||
github.com/charmbracelet/x/ansi v0.8.0 // indirect
|
||||
github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd // indirect
|
||||
github.com/charmbracelet/x/term v0.2.1 // indirect
|
||||
github.com/chavacava/garif v0.1.0 // indirect
|
||||
github.com/ckaznocha/intrange v0.3.1 // indirect
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.6 // indirect
|
||||
github.com/creack/pty v1.1.24 // indirect
|
||||
github.com/curioswitch/go-reassign v0.3.0 // indirect
|
||||
github.com/daixiang0/gci v0.13.7 // indirect
|
||||
github.com/daixiang0/gci v0.13.6 // indirect
|
||||
github.com/dave/dst v0.27.3 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/denis-tingaikin/go-header v0.5.0 // indirect
|
||||
@@ -95,28 +85,17 @@ require (
|
||||
github.com/fatih/color v1.18.0 // indirect
|
||||
github.com/fatih/structtag v1.2.0 // indirect
|
||||
github.com/firefart/nonamedreturns v1.0.6 // indirect
|
||||
github.com/fsnotify/fsnotify v1.9.0 // indirect
|
||||
github.com/fsnotify/fsnotify v1.5.4 // indirect
|
||||
github.com/fzipp/gocyclo v0.6.0 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.10 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.9 // indirect
|
||||
github.com/ghostiam/protogetter v0.3.15 // indirect
|
||||
github.com/go-critic/go-critic v0.13.0 // indirect
|
||||
github.com/go-errors/errors v1.5.1 // indirect
|
||||
github.com/go-jose/go-jose/v4 v4.1.2 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.22.0 // indirect
|
||||
github.com/go-openapi/jsonreference v0.21.1 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.21.2 // indirect
|
||||
github.com/go-openapi/jsonreference v0.21.0 // indirect
|
||||
github.com/go-openapi/spec v0.21.0 // indirect
|
||||
github.com/go-openapi/swag v0.24.1 // indirect
|
||||
github.com/go-openapi/swag/cmdutils v0.24.0 // indirect
|
||||
github.com/go-openapi/swag/conv v0.24.0 // indirect
|
||||
github.com/go-openapi/swag/fileutils v0.24.0 // indirect
|
||||
github.com/go-openapi/swag/jsonname v0.24.0 // indirect
|
||||
github.com/go-openapi/swag/jsonutils v0.24.0 // indirect
|
||||
github.com/go-openapi/swag/loading v0.24.0 // indirect
|
||||
github.com/go-openapi/swag/mangling v0.24.0 // indirect
|
||||
github.com/go-openapi/swag/netutils v0.24.0 // indirect
|
||||
github.com/go-openapi/swag/stringutils v0.24.0 // indirect
|
||||
github.com/go-openapi/swag/typeutils v0.24.0 // indirect
|
||||
github.com/go-openapi/swag/yamlutils v0.24.0 // indirect
|
||||
github.com/go-openapi/swag v0.23.1 // indirect
|
||||
github.com/go-playground/locales v0.14.1 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||
github.com/go-toolsmith/astcast v1.1.0 // indirect
|
||||
@@ -126,22 +105,20 @@ require (
|
||||
github.com/go-toolsmith/astp v1.1.0 // indirect
|
||||
github.com/go-toolsmith/strparse v1.1.0 // indirect
|
||||
github.com/go-toolsmith/typep v1.1.0 // indirect
|
||||
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
|
||||
github.com/go-viper/mapstructure/v2 v2.2.1 // indirect
|
||||
github.com/go-xmlfmt/xmlfmt v1.1.3 // indirect
|
||||
github.com/gobwas/glob v0.2.3 // indirect
|
||||
github.com/gofrs/flock v0.12.1 // indirect
|
||||
github.com/gohugoio/hugo v0.149.1 // indirect
|
||||
github.com/golang/geo v0.0.0-20250909101022-21d0664d86be // indirect
|
||||
github.com/golang/geo v0.0.0-20250821133510-ecfc33a939ac // indirect
|
||||
github.com/golang/protobuf v1.5.3 // indirect
|
||||
github.com/golangci/dupl v0.0.0-20250308024227-f665c8d69b32 // indirect
|
||||
github.com/golangci/go-printf-func-name v0.1.0 // indirect
|
||||
github.com/golangci/gofmt v0.0.0-20250106114630-d62b90e6713d // indirect
|
||||
github.com/golangci/golangci-lint/v2 v2.4.0 // indirect
|
||||
github.com/golangci/golangci-lint/v2 v2.1.6 // indirect
|
||||
github.com/golangci/golines v0.0.0-20250217134842-442fd0091d95 // indirect
|
||||
github.com/golangci/misspell v0.7.0 // indirect
|
||||
github.com/golangci/plugin-module-register v0.1.2 // indirect
|
||||
github.com/golangci/misspell v0.6.0 // indirect
|
||||
github.com/golangci/plugin-module-register v0.1.1 // indirect
|
||||
github.com/golangci/revgrep v0.8.0 // indirect
|
||||
github.com/golangci/swaggoswag v0.0.0-20250504205917-77f2aca3143e // indirect
|
||||
github.com/golangci/unconvert v0.0.0-20250410112200-a129a6e6413e // indirect
|
||||
github.com/gordonklaus/ineffassign v0.1.0 // indirect
|
||||
github.com/gostaticanalysis/analysisutil v0.7.1 // indirect
|
||||
@@ -154,9 +131,9 @@ require (
|
||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||
github.com/hexops/gotextdiff v1.0.3 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/jgautheron/goconst v1.8.2 // indirect
|
||||
github.com/jgautheron/goconst v1.8.1 // indirect
|
||||
github.com/jingyugao/rowserrcheck v1.1.1 // indirect
|
||||
github.com/jjti/go-spancheck v0.6.5 // indirect
|
||||
github.com/jjti/go-spancheck v0.6.4 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/julz/importas v0.2.0 // indirect
|
||||
github.com/jupiterrider/ffi v0.5.1 // indirect
|
||||
@@ -166,19 +143,18 @@ require (
|
||||
github.com/kulti/thelper v0.6.3 // indirect
|
||||
github.com/kunwardeep/paralleltest v1.0.14 // indirect
|
||||
github.com/lasiar/canonicalheader v1.1.2 // indirect
|
||||
github.com/ldez/exptostd v0.4.4 // indirect
|
||||
github.com/ldez/gomoddirectives v0.7.0 // indirect
|
||||
github.com/ldez/grignotin v0.10.0 // indirect
|
||||
github.com/ldez/exptostd v0.4.3 // indirect
|
||||
github.com/ldez/gomoddirectives v0.6.1 // indirect
|
||||
github.com/ldez/grignotin v0.9.0 // indirect
|
||||
github.com/ldez/tagliatelle v0.7.1 // indirect
|
||||
github.com/ldez/usetesting v0.5.0 // indirect
|
||||
github.com/ldez/usetesting v0.4.3 // indirect
|
||||
github.com/leodido/go-urn v1.4.0 // indirect
|
||||
github.com/leonklingele/grouper v1.1.2 // indirect
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
||||
github.com/macabu/inamedparam v0.2.0 // indirect
|
||||
github.com/magiconair/properties v1.8.6 // indirect
|
||||
github.com/mailru/easyjson v0.9.0 // indirect
|
||||
github.com/manuelarte/embeddedstructfieldcheck v0.3.0 // indirect
|
||||
github.com/manuelarte/funcorder v0.5.0 // indirect
|
||||
github.com/manuelarte/funcorder v0.2.1 // indirect
|
||||
github.com/maratori/testableexamples v1.0.0 // indirect
|
||||
github.com/maratori/testpackage v1.1.1 // indirect
|
||||
github.com/matoous/godox v1.1.0 // indirect
|
||||
@@ -186,15 +162,16 @@ require (
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.16 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
|
||||
github.com/mgechev/revive v1.11.0 // indirect
|
||||
github.com/mgechev/revive v1.9.0 // indirect
|
||||
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
||||
github.com/mitchellh/mapstructure v1.5.1-0.20231216201459-8508981c8b6c // indirect
|
||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
github.com/moricho/tparallel v0.3.2 // indirect
|
||||
github.com/muesli/termenv v0.16.0 // indirect
|
||||
github.com/nakabonne/nestif v0.3.1 // indirect
|
||||
github.com/nishanths/exhaustive v0.12.0 // indirect
|
||||
github.com/nishanths/predeclared v0.2.2 // indirect
|
||||
github.com/nunnatsa/ginkgolinter v0.20.0 // indirect
|
||||
github.com/nunnatsa/ginkgolinter v0.19.1 // indirect
|
||||
github.com/olekukonko/tablewriter v0.0.5 // indirect
|
||||
github.com/pelletier/go-toml v1.9.5 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
@@ -212,22 +189,21 @@ require (
|
||||
github.com/rivo/uniseg v0.4.7 // indirect
|
||||
github.com/rogpeppe/go-internal v1.14.1 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd // indirect
|
||||
github.com/ryancurrah/gomodguard v1.4.1 // indirect
|
||||
github.com/ryanrolds/sqlclosecheck v0.5.1 // indirect
|
||||
github.com/sanposhiho/wastedassign/v2 v2.1.0 // indirect
|
||||
github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 // indirect
|
||||
github.com/santhosh-tekuri/jsonschema/v6 v6.0.1 // indirect
|
||||
github.com/sashamelentyev/interfacebloat v1.1.0 // indirect
|
||||
github.com/sashamelentyev/usestdlibvars v1.29.0 // indirect
|
||||
github.com/securego/gosec/v2 v2.22.7 // indirect
|
||||
github.com/sashamelentyev/usestdlibvars v1.28.0 // indirect
|
||||
github.com/securego/gosec/v2 v2.22.3 // indirect
|
||||
github.com/sirupsen/logrus v1.9.3 // indirect
|
||||
github.com/sivchari/containedctx v1.0.3 // indirect
|
||||
github.com/sonatard/noctx v0.4.0 // indirect
|
||||
github.com/sonatard/noctx v0.1.0 // indirect
|
||||
github.com/sourcegraph/go-diff v0.7.0 // indirect
|
||||
github.com/spf13/cast v1.9.2 // indirect
|
||||
github.com/spf13/cast v1.5.0 // indirect
|
||||
github.com/spf13/cobra v1.9.1 // indirect
|
||||
github.com/spf13/jwalterweatherman v1.1.0 // indirect
|
||||
github.com/spf13/pflag v1.0.7 // indirect
|
||||
github.com/spf13/pflag v1.0.6 // indirect
|
||||
github.com/spf13/viper v1.12.0 // indirect
|
||||
github.com/ssgreg/nlreturn/v2 v2.2.1 // indirect
|
||||
github.com/stbenjam/no-sprintf-host-port v0.2.0 // indirect
|
||||
@@ -235,7 +211,6 @@ require (
|
||||
github.com/subosito/gotenv v1.4.1 // indirect
|
||||
github.com/swaggo/files v1.0.1 // indirect
|
||||
github.com/tdakkota/asciicheck v0.4.1 // indirect
|
||||
github.com/tdewolff/parse/v2 v2.8.3 // indirect
|
||||
github.com/tetafro/godot v1.5.1 // indirect
|
||||
github.com/timakin/bodyclose v0.0.0-20241222091800-1db5c5ca4d67 // indirect
|
||||
github.com/timonwong/loggercheck v0.11.0 // indirect
|
||||
@@ -245,7 +220,7 @@ require (
|
||||
github.com/ultraware/whitespace v0.2.0 // indirect
|
||||
github.com/urfave/cli/v2 v2.3.0 // indirect
|
||||
github.com/uudashr/gocognit v1.2.0 // indirect
|
||||
github.com/uudashr/iface v1.4.1 // indirect
|
||||
github.com/uudashr/iface v1.3.1 // indirect
|
||||
github.com/xen0n/gosmopolitan v1.3.0 // indirect
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
||||
github.com/yagipy/maintidx v1.0.0 // indirect
|
||||
@@ -253,19 +228,21 @@ require (
|
||||
github.com/ykadowak/zerologlint v0.1.5 // indirect
|
||||
gitlab.com/bosi/decorder v0.4.2 // indirect
|
||||
go-simpler.org/musttag v0.13.1 // indirect
|
||||
go-simpler.org/sloglint v0.11.1 // indirect
|
||||
go.augendre.info/arangolint v0.2.0 // indirect
|
||||
go-simpler.org/sloglint v0.11.0 // indirect
|
||||
go.augendre.info/fatcontext v0.8.0 // indirect
|
||||
go.etcd.io/bbolt v1.4.3 // indirect
|
||||
go.uber.org/atomic v1.7.0 // indirect
|
||||
go.uber.org/automaxprocs v1.6.0 // indirect
|
||||
go.uber.org/multierr v1.10.0 // indirect
|
||||
go.uber.org/zap v1.27.0 // indirect
|
||||
golang.org/x/exp/typeparams v0.0.0-20250620022241-b7579e27df2b // indirect
|
||||
golang.org/x/net v0.44.0 // indirect
|
||||
golang.org/x/sync v0.17.0 // indirect
|
||||
golang.org/x/text v0.29.0 // indirect
|
||||
go.uber.org/multierr v1.6.0 // indirect
|
||||
go.uber.org/zap v1.24.0 // indirect
|
||||
golang.org/x/exp/typeparams v0.0.0-20250210185358-939b2ce775ac // indirect
|
||||
golang.org/x/net v0.43.0 // indirect
|
||||
golang.org/x/sync v0.16.0 // indirect
|
||||
golang.org/x/text v0.28.0 // indirect
|
||||
golang.org/x/tools v0.36.0 // indirect
|
||||
google.golang.org/protobuf v1.36.8 // indirect
|
||||
golang.org/x/tools/go/expect v0.1.1-deprecated // indirect
|
||||
golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated // indirect
|
||||
google.golang.org/protobuf v1.36.6 // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
honnef.co/go/tools v0.6.1 // indirect
|
||||
@@ -275,7 +252,6 @@ require (
|
||||
)
|
||||
|
||||
tool (
|
||||
github.com/air-verse/air
|
||||
github.com/golangci/golangci-lint/v2/cmd/golangci-lint
|
||||
github.com/swaggo/swag/cmd/swag
|
||||
)
|
||||
+145
-277
@@ -34,29 +34,17 @@ cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0Zeo
|
||||
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
|
||||
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
|
||||
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
|
||||
codeberg.org/chavacava/garif v0.2.0 h1:F0tVjhYbuOCnvNcU3YSpO6b3Waw6Bimy4K0mM8y6MfY=
|
||||
codeberg.org/chavacava/garif v0.2.0/go.mod h1:P2BPbVbT4QcvLZrORc2T29szK3xEOlnl0GiPTJmEqBQ=
|
||||
dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8=
|
||||
dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA=
|
||||
dev.gaijin.team/go/exhaustruct/v4 v4.0.0 h1:873r7aNneqoBB3IaFIzhvt2RFYTuHgmMjoKfwODoI1Y=
|
||||
dev.gaijin.team/go/exhaustruct/v4 v4.0.0/go.mod h1:aZ/k2o4Y05aMJtiux15x8iXaumE88YdiB0Ai4fXOzPI=
|
||||
dev.gaijin.team/go/golib v0.6.0 h1:v6nnznFTs4bppib/NyU1PQxobwDHwCXXl15P7DV5Zgo=
|
||||
dev.gaijin.team/go/golib v0.6.0/go.mod h1:uY1mShx8Z/aNHWDyAkZTkX+uCi5PdX7KsG1eDQa2AVE=
|
||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||
github.com/4meepo/tagalign v1.4.3 h1:Bnu7jGWwbfpAie2vyl63Zup5KuRv21olsPIha53BJr8=
|
||||
github.com/4meepo/tagalign v1.4.3/go.mod h1:00WwRjiuSbrRJnSVeGWPLp2epS5Q/l4UEy0apLLS37c=
|
||||
github.com/Abirdcfly/dupword v0.1.6 h1:qeL6u0442RPRe3mcaLcbaCi2/Y/hOcdtw6DE9odjz9c=
|
||||
github.com/Abirdcfly/dupword v0.1.6/go.mod h1:s+BFMuL/I4YSiFv29snqyjwzDp4b65W2Kvy+PKzZ6cw=
|
||||
github.com/AlwxSin/noinlineerr v1.0.5 h1:RUjt63wk1AYWTXtVXbSqemlbVTb23JOSRiNsshj7TbY=
|
||||
github.com/AlwxSin/noinlineerr v1.0.5/go.mod h1:+QgkkoYrMH7RHvcdxdlI7vYYEdgeoFOVjU9sUhw/rQc=
|
||||
github.com/4meepo/tagalign v1.4.2 h1:0hcLHPGMjDyM1gHG58cS73aQF8J4TdVR96TZViorO9E=
|
||||
github.com/4meepo/tagalign v1.4.2/go.mod h1:+p4aMyFM+ra7nb41CnFG6aSDXqRxU/w1VQqScKqDARI=
|
||||
github.com/Abirdcfly/dupword v0.1.3 h1:9Pa1NuAsZvpFPi9Pqkd93I7LIYRURj+A//dFd5tgBeE=
|
||||
github.com/Abirdcfly/dupword v0.1.3/go.mod h1:8VbB2t7e10KRNdwTVoxdBaxla6avbhGzb8sCTygUMhw=
|
||||
github.com/Antonboom/errname v1.1.0 h1:A+ucvdpMwlo/myWrkHEUEBWc/xuXdud23S8tmTb/oAE=
|
||||
github.com/Antonboom/errname v1.1.0/go.mod h1:O1NMrzgUcVBGIfi3xlVuvX8Q/VP/73sseCaAppfjqZw=
|
||||
github.com/Antonboom/nilnil v1.1.0 h1:jGxJxjgYS3VUUtOTNk8Z1icwT5ESpLH/426fjmQG+ng=
|
||||
github.com/Antonboom/nilnil v1.1.0/go.mod h1:b7sAlogQjFa1wV8jUW3o4PMzDVFLbTux+xnQdvzdcIE=
|
||||
github.com/Antonboom/testifylint v1.6.1 h1:6ZSytkFWatT8mwZlmRCHkWz1gPi+q6UBSbieji2Gj/o=
|
||||
github.com/Antonboom/testifylint v1.6.1/go.mod h1:k+nEkathI2NFjKO6HvwmSrbzUcQ6FAnbZV+ZRrnXPLI=
|
||||
github.com/BurntSushi/locker v0.0.0-20171006230638-a6e239ea1c69 h1:+tu3HOoMXB7RXEINRVIpxJCT+KdYiI7LAEAUrOw3dIU=
|
||||
github.com/BurntSushi/locker v0.0.0-20171006230638-a6e239ea1c69/go.mod h1:L1AbZdiDllfyYH5l5OkAaZtk7VkWe89bPJFmnDBNHxg=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg=
|
||||
github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
|
||||
@@ -65,6 +53,8 @@ github.com/DataDog/zstd v1.4.1 h1:3oxKN3wbHibqx897utPC2LTQU4J+IHWWJO+glkAkpFM=
|
||||
github.com/DataDog/zstd v1.4.1/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo=
|
||||
github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24 h1:sHglBQTwgx+rWPdisA5ynNEsoARbiCBOyGcJM4/OzsM=
|
||||
github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24/go.mod h1:4UJr5HIiMZrwgkSPdsjy2uOQExX/WEILpIrO9UPGuXs=
|
||||
github.com/GaijinEntertainment/go-exhaustruct/v3 v3.3.1 h1:Sz1JIXEcSfhz7fUi7xHnhpIE0thVASYjvosApmHuD2k=
|
||||
github.com/GaijinEntertainment/go-exhaustruct/v3 v3.3.1/go.mod h1:n/LSCXNuIYqVfBlVXyHfMQkZDdp1/mmxfSjADd3z1Zg=
|
||||
github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
|
||||
github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
|
||||
github.com/Masterminds/semver/v3 v3.3.1 h1:QtNSWtVZ3nBfk8mAOu/B6v7FMJ+NHTIgUPi7rj+4nv4=
|
||||
@@ -73,16 +63,14 @@ github.com/OpenPeeDeeP/depguard/v2 v2.2.1 h1:vckeWVESWp6Qog7UZSARNqfu/cZqvki8zsu
|
||||
github.com/OpenPeeDeeP/depguard/v2 v2.2.1/go.mod h1:q4DKzC4UcVaAvcfd41CZh0PWpGgzrVxUYBlgKNGquUo=
|
||||
github.com/Sereal/Sereal v0.0.0-20190618215532-0b8ac451a863 h1:BRrxwOZBolJN4gIwvZMJY1tzqBvQgpaZiQRuIDD40jM=
|
||||
github.com/Sereal/Sereal v0.0.0-20190618215532-0b8ac451a863/go.mod h1:D0JMgToj/WdxCgd30Kc1UcA9E+WdZoJqeVOuYW7iTBM=
|
||||
github.com/air-verse/air v1.63.0 h1:fwcdHpwaUe4/+q349PxptzAIn8gVE6Yke8TgW0LsDxQ=
|
||||
github.com/air-verse/air v1.63.0/go.mod h1:RyCQVx2+3Zz2BzoqkukYiGmWkWXNKMf0x5ubIFcUB8Q=
|
||||
github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0=
|
||||
github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=
|
||||
github.com/alecthomas/chroma/v2 v2.20.0 h1:sfIHpxPyR07/Oylvmcai3X/exDlE8+FA820NTz+9sGw=
|
||||
github.com/alecthomas/chroma/v2 v2.20.0/go.mod h1:e7tViK0xh/Nf4BYHl00ycY6rV7b8iXBksI9E359yNmA=
|
||||
github.com/alecthomas/chroma/v2 v2.17.2 h1:Rm81SCZ2mPoH+Q8ZCc/9YvzPUN/E7HgPiPJD8SLV6GI=
|
||||
github.com/alecthomas/chroma/v2 v2.17.2/go.mod h1:RVX6AvYm4VfYe/zsk7mjHueLDZor3aWCNE14TFlepBk=
|
||||
github.com/alecthomas/go-check-sumtype v0.3.1 h1:u9aUvbGINJxLVXiFvHUlPEaD7VDULsrxJb4Aq31NLkU=
|
||||
github.com/alecthomas/go-check-sumtype v0.3.1/go.mod h1:A8TSiN3UPRw3laIgWEUOHHLPa6/r9MtoigdlP5h3K/E=
|
||||
github.com/alecthomas/repr v0.5.1 h1:E3G4t2QbHTSNpPKBgMTln5KLkZHLOcU7r37J4pXBuIg=
|
||||
github.com/alecthomas/repr v0.5.1/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
|
||||
github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc=
|
||||
github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
@@ -92,64 +80,30 @@ github.com/alexkohler/nakedret/v2 v2.0.6 h1:ME3Qef1/KIKr3kWX3nti3hhgNxw6aqN5pZmQ
|
||||
github.com/alexkohler/nakedret/v2 v2.0.6/go.mod h1:l3RKju/IzOMQHmsEvXwkqMDzHHvurNQfAgE1eVmT40Q=
|
||||
github.com/alexkohler/prealloc v1.0.0 h1:Hbq0/3fJPQhNkN0dR95AVrr6R7tou91y0uHG5pOcUuw=
|
||||
github.com/alexkohler/prealloc v1.0.0/go.mod h1:VetnK3dIgFBBKmg0YnD9F9x6Icjd+9cvfHR56wJVlKE=
|
||||
github.com/alfatraining/structtag v1.0.0 h1:2qmcUqNcCoyVJ0up879K614L9PazjBSFruTB0GOFjCc=
|
||||
github.com/alfatraining/structtag v1.0.0/go.mod h1:p3Xi5SwzTi+Ryj64DqjLWz7XurHxbGsq6y3ubePJPus=
|
||||
github.com/alingse/asasalint v0.0.11 h1:SFwnQXJ49Kx/1GghOFz1XGqHYKp21Kq1nHad/0WQRnw=
|
||||
github.com/alingse/asasalint v0.0.11/go.mod h1:nCaoMhw7a9kSJObvQyVzNTPBDbNpdocqrSP7t/cW5+I=
|
||||
github.com/alingse/nilnesserr v0.2.0 h1:raLem5KG7EFVb4UIDAXgrv3N2JIaffeKNtcEXkEWd/w=
|
||||
github.com/alingse/nilnesserr v0.2.0/go.mod h1:1xJPrXonEtX7wyTq8Dytns5P2hNzoWymVUIaKm4HNFg=
|
||||
github.com/armon/go-radix v1.0.1-0.20221118154546-54df44f2176c h1:651/eoCRnQ7YtSjAnSzRucrJz+3iGEFt+ysraELS81M=
|
||||
github.com/armon/go-radix v1.0.1-0.20221118154546-54df44f2176c/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
||||
github.com/asdine/storm/v3 v3.2.1 h1:I5AqhkPK6nBZ/qJXySdI7ot5BlXSZ7qvDY1zAn5ZJac=
|
||||
github.com/asdine/storm/v3 v3.2.1/go.mod h1:LEpXwGt4pIqrE/XcTvCnZHT5MgZCV6Ub9q7yQzOFWr0=
|
||||
github.com/ashanbrown/forbidigo/v2 v2.1.0 h1:NAxZrWqNUQiDz19FKScQ/xvwzmij6BiOw3S0+QUQ+Hs=
|
||||
github.com/ashanbrown/forbidigo/v2 v2.1.0/go.mod h1:0zZfdNAuZIL7rSComLGthgc/9/n2FqspBOH90xlCHdA=
|
||||
github.com/ashanbrown/makezero/v2 v2.0.1 h1:r8GtKetWOgoJ4sLyUx97UTwyt2dO7WkGFHizn/Lo8TY=
|
||||
github.com/ashanbrown/makezero/v2 v2.0.1/go.mod h1:kKU4IMxmYW1M4fiEHMb2vc5SFoPzXvgbMR9gIp5pjSw=
|
||||
github.com/ashanbrown/forbidigo v1.6.0 h1:D3aewfM37Yb3pxHujIPSpTf6oQk9sc9WZi8gerOIVIY=
|
||||
github.com/ashanbrown/forbidigo v1.6.0/go.mod h1:Y8j9jy9ZYAEHXdu723cUlraTqbzjKF1MUyfOKL+AjcU=
|
||||
github.com/ashanbrown/makezero v1.2.0 h1:/2Lp1bypdmK9wDIq7uWBlDF1iMUpIIS4A+pF6C9IEUU=
|
||||
github.com/ashanbrown/makezero v1.2.0/go.mod h1:dxlPhHbDMC6N6xICzFBSK+4njQDdK8euNO0qjQMtGY4=
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
|
||||
github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
|
||||
github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
|
||||
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
|
||||
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/bep/clocks v0.5.0 h1:hhvKVGLPQWRVsBP/UB7ErrHYIO42gINVbvqxvYTPVps=
|
||||
github.com/bep/clocks v0.5.0/go.mod h1:SUq3q+OOq41y2lRQqH5fsOoxN8GbxSiT6jvoVVLCVhU=
|
||||
github.com/bep/debounce v1.2.1 h1:v67fRdBA9UQu2NhLFXrSg0Brw7CexQekrBwDMM8bzeY=
|
||||
github.com/bep/debounce v1.2.1/go.mod h1:H8yggRPQKLUhUoqrJC1bO2xNya7vanpDl7xR3ISbCJ0=
|
||||
github.com/bep/gitmap v1.9.0 h1:2pyb1ex+cdwF6c4tsrhEgEKfyNfxE34d5K+s2sa9byc=
|
||||
github.com/bep/gitmap v1.9.0/go.mod h1:Juq6e1qqCRvc1W7nzgadPGI9IGV13ZncEebg5atj4Vo=
|
||||
github.com/bep/goat v0.5.0 h1:S8jLXHCVy/EHIoCY+btKkmcxcXFd34a0Q63/0D4TKeA=
|
||||
github.com/bep/goat v0.5.0/go.mod h1:Md9x7gRxiWKs85yHlVTvHQw9rg86Bm+Y4SuYE8CTH7c=
|
||||
github.com/bep/godartsass/v2 v2.5.0 h1:tKRvwVdyjCIr48qgtLa4gHEdtRkPF8H1OeEhJAEv7xg=
|
||||
github.com/bep/godartsass/v2 v2.5.0/go.mod h1:rjsi1YSXAl/UbsGL85RLDEjRKdIKUlMQHr6ChUNYOFU=
|
||||
github.com/bep/golibsass v1.2.0 h1:nyZUkKP/0psr8nT6GR2cnmt99xS93Ji82ZD9AgOK6VI=
|
||||
github.com/bep/golibsass v1.2.0/go.mod h1:DL87K8Un/+pWUS75ggYv41bliGiolxzDKWJAq3eJ1MA=
|
||||
github.com/bep/goportabletext v0.1.0 h1:8dqym2So1cEqVZiBa4ZnMM1R9l/DnC1h4ONg4J5kujw=
|
||||
github.com/bep/goportabletext v0.1.0/go.mod h1:6lzSTsSue75bbcyvVc0zqd1CdApuT+xkZQ6Re5DzZFg=
|
||||
github.com/bep/gowebp v0.4.0 h1:QihuVnvIKbRoeBNQkN0JPMM8ClLmD6V2jMftTFwSK3Q=
|
||||
github.com/bep/gowebp v0.4.0/go.mod h1:95gtYkAA8iIn1t3HkAPurRCVGV/6NhgaHJ1urz0iIwc=
|
||||
github.com/bep/helpers v0.6.0 h1:qtqMCK8XPFNM9hp5Ztu9piPjxNNkk8PIyUVjg6v8Bsw=
|
||||
github.com/bep/helpers v0.6.0/go.mod h1:IOZlgx5PM/R/2wgyCatfsgg5qQ6rNZJNDpWGXqDR044=
|
||||
github.com/bep/imagemeta v0.12.0 h1:ARf+igs5B7pf079LrqRnwzQ/wEB8Q9v4NSDRZO1/F5k=
|
||||
github.com/bep/imagemeta v0.12.0/go.mod h1:23AF6O+4fUi9avjiydpKLStUNtJr5hJB4rarG18JpN8=
|
||||
github.com/bep/lazycache v0.8.0 h1:lE5frnRjxaOFbkPZ1YL6nijzOPPz6zeXasJq8WpG4L8=
|
||||
github.com/bep/lazycache v0.8.0/go.mod h1:BQ5WZepss7Ko91CGdWz8GQZi/fFnCcyWupv8gyTeKwk=
|
||||
github.com/bep/logg v0.4.0 h1:luAo5mO4ZkhA5M1iDVDqDqnBBnlHjmtZF6VAyTp+nCQ=
|
||||
github.com/bep/logg v0.4.0/go.mod h1:Ccp9yP3wbR1mm++Kpxet91hAZBEQgmWgFgnXX3GkIV0=
|
||||
github.com/bep/overlayfs v0.10.0 h1:wS3eQ6bRsLX+4AAmwGjvoFSAQoeheamxofFiJ2SthSE=
|
||||
github.com/bep/overlayfs v0.10.0/go.mod h1:ouu4nu6fFJaL0sPzNICzxYsBeWwrjiTdFZdK4lI3tro=
|
||||
github.com/bep/tmc v0.5.1 h1:CsQnSC6MsomH64gw0cT5f+EwQDcvZz4AazKunFwTpuI=
|
||||
github.com/bep/tmc v0.5.1/go.mod h1:tGYHN8fS85aJPhDLgXETVKp+PR382OvFi2+q2GkGsq0=
|
||||
github.com/bkielbasa/cyclop v1.2.3 h1:faIVMIGDIANuGPWH031CZJTi2ymOQBULs9H21HSMa5w=
|
||||
github.com/bkielbasa/cyclop v1.2.3/go.mod h1:kHTwA9Q0uZqOADdupvcFJQtp/ksSnytRMe8ztxG8Fuo=
|
||||
github.com/blizzy78/varnamelen v0.8.0 h1:oqSblyuQvFsW1hbBHh1zfwrKe3kcSj0rnXkKzsQ089M=
|
||||
github.com/blizzy78/varnamelen v0.8.0/go.mod h1:V9TzQZ4fLJ1DSrjVDfl89H7aMnTvKkApdHeyESmyR7k=
|
||||
github.com/bombsimon/wsl/v4 v4.7.0 h1:1Ilm9JBPRczjyUs6hvOPKvd7VL1Q++PL8M0SXBDf+jQ=
|
||||
github.com/bombsimon/wsl/v4 v4.7.0/go.mod h1:uV/+6BkffuzSAVYD+yGyld1AChO7/EuLrCF/8xTiapg=
|
||||
github.com/bombsimon/wsl/v5 v5.1.1 h1:cQg5KJf9FlctAH4cpL9vLKnziYknoCMCdqXl0wjl72Q=
|
||||
github.com/bombsimon/wsl/v5 v5.1.1/go.mod h1:Gp8lD04z27wm3FANIUPZycXp+8huVsn0oxc+n4qfV9I=
|
||||
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
|
||||
github.com/boombuler/barcode v1.1.0 h1:ChaYjBR63fr4LFyGn8E8nt7dBSt3MiU3zMOZqFvVkHo=
|
||||
github.com/boombuler/barcode v1.1.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
|
||||
@@ -163,8 +117,8 @@ github.com/butuzov/mirror v1.3.0 h1:HdWCXzmwlQHdVhwvsfBb2Au0r3HyINry3bDWLYXiKoc=
|
||||
github.com/butuzov/mirror v1.3.0/go.mod h1:AEij0Z8YMALaq4yQj9CPPVYOyJQyiexpQEQgihajRfI=
|
||||
github.com/catenacyber/perfsprint v0.9.1 h1:5LlTp4RwTooQjJCvGEFV6XksZvWE7wCOUvjD2z0vls0=
|
||||
github.com/catenacyber/perfsprint v0.9.1/go.mod h1:q//VWC2fWbcdSLEY1R3l8n0zQCDPdE4IjZwyY1HMunM=
|
||||
github.com/ccojocar/zxcvbn-go v1.0.4 h1:FWnCIRMXPj43ukfX000kvBZvV6raSxakYr1nzyNrUcc=
|
||||
github.com/ccojocar/zxcvbn-go v1.0.4/go.mod h1:3GxGX+rHmueTUMvm5ium7irpyjmm7ikxYFOSJB21Das=
|
||||
github.com/ccojocar/zxcvbn-go v1.0.2 h1:na/czXU8RrhXO4EZme6eQJLR4PzcGsahsBOAwU6I3Vg=
|
||||
github.com/ccojocar/zxcvbn-go v1.0.2/go.mod h1:g1qkXtUSvHP8lhHp5GrSmTz6uWALGRMQdw6Qnz/hi60=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
@@ -182,13 +136,13 @@ github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd h1:vy0G
|
||||
github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs=
|
||||
github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ=
|
||||
github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg=
|
||||
github.com/chavacava/garif v0.1.0 h1:2JHa3hbYf5D9dsgseMKAmc/MZ109otzgNFk5s87H9Pc=
|
||||
github.com/chavacava/garif v0.1.0/go.mod h1:XMyYCkEL58DF0oyW4qDjjnPWONs2HBqYKI+UIPD+Gww=
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
github.com/ckaznocha/intrange v0.3.1 h1:j1onQyXvHUsPWujDH6WIjhyH26gkRt/txNlV7LspvJs=
|
||||
github.com/ckaznocha/intrange v0.3.1/go.mod h1:QVepyz1AkUoFQkpEqksSYpNpUo3c5W7nWh/s6SHIJJk=
|
||||
github.com/clbanning/mxj/v2 v2.7.0 h1:WA/La7UGCanFe5NpHF0Q3DNtnCsVoxbPKuyBNHWRyME=
|
||||
github.com/clbanning/mxj/v2 v2.7.0/go.mod h1:hNiWqW14h+kc+MdF9C6/YoRfjEJoR3ou6tn/Qo+ve2s=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/coreos/go-oidc/v3 v3.15.0 h1:R6Oz8Z4bqWR7VFQ+sPSvZPQv4x8M+sJkDO5ojgwlyAg=
|
||||
@@ -196,12 +150,10 @@ github.com/coreos/go-oidc/v3 v3.15.0/go.mod h1:HaZ3szPaZ0e4r6ebqvsLWlk2Tn+aejfmr
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.6 h1:XJtiaUW6dEEqVuZiMTn1ldk455QWwEIsMIJlo5vtkx0=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
||||
github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s=
|
||||
github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE=
|
||||
github.com/curioswitch/go-reassign v0.3.0 h1:dh3kpQHuADL3cobV/sSGETA8DOv457dwl+fbBAhrQPs=
|
||||
github.com/curioswitch/go-reassign v0.3.0/go.mod h1:nApPCCTtqLJN/s8HfItCcKV0jIPwluBOvZP+dsJGA88=
|
||||
github.com/daixiang0/gci v0.13.7 h1:+0bG5eK9vlI08J+J/NWGbWPTNiXPG4WhNLJOkSxWITQ=
|
||||
github.com/daixiang0/gci v0.13.7/go.mod h1:812WVN6JLFY9S6Tv76twqmNqevN0pa3SX3nih0brVzQ=
|
||||
github.com/daixiang0/gci v0.13.6 h1:RKuEOSkGpSadkGbvZ6hJ4ddItT3cVZ9Vn9Rybk6xjl8=
|
||||
github.com/daixiang0/gci v0.13.6/go.mod h1:12etP2OniiIdP4q+kjUGrC/rUagga7ODbqsom5Eo5Yk=
|
||||
github.com/dave/dst v0.27.3 h1:P1HPoMza3cMEquVf9kKy8yXsFirry4zEnWOdYPOoIzY=
|
||||
github.com/dave/dst v0.27.3/go.mod h1:jHh6EOibnHgcUW3WjKHisiooEkYwqpHLBSX1iOBhEyc=
|
||||
github.com/dave/jennifer v1.7.1 h1:B4jJJDHelWcDhlRQxWeo0Npa/pYKBLrirAQoTN45txo=
|
||||
@@ -211,8 +163,6 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/denis-tingaikin/go-header v0.5.0 h1:SRdnP5ZKvcO9KKRP1KJrhFR3RrlGuD+42t4429eC9k8=
|
||||
github.com/denis-tingaikin/go-header v0.5.0/go.mod h1:mMenU5bWrok6Wl2UsZjy+1okegmwQ3UgWl4V1D8gjlY=
|
||||
github.com/disintegration/gift v1.2.1 h1:Y005a1X4Z7Uc+0gLpSAsKhWi4qLtsdEcMIbbdvdZ6pc=
|
||||
github.com/disintegration/gift v1.2.1/go.mod h1:Jh2i7f7Q2BM7Ezno3PhfezbR1xpUg9dUg3/RlKGr4HI=
|
||||
github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ=
|
||||
github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||
github.com/dsoprea/go-exif/v2 v2.0.0-20200321225314-640175a69fe4/go.mod h1:Lm2lMM2zx8p4a34ZemkaUV95AnMl4ZvLbCUbwOvLC2E=
|
||||
@@ -240,29 +190,22 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/ettle/strcase v0.2.0 h1:fGNiVF21fHXpX1niBgk0aROov1LagYsOwV/xqKDKR/Q=
|
||||
github.com/ettle/strcase v0.2.0/go.mod h1:DajmHElDSaX76ITe3/VHVyMin4LWSJN5Z909Wp+ED1A=
|
||||
github.com/evanw/esbuild v0.25.9 h1:aU7GVC4lxJGC1AyaPwySWjSIaNLAdVEEuq3chD0Khxs=
|
||||
github.com/evanw/esbuild v0.25.9/go.mod h1:D2vIQZqV/vIf/VRHtViaUtViZmG7o+kKmlBfVQuRi48=
|
||||
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
|
||||
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
|
||||
github.com/fatih/structtag v1.2.0 h1:/OdNE99OxoI/PqaW/SuSK9uxxT3f/tcSZgon/ssNSx4=
|
||||
github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94=
|
||||
github.com/firefart/nonamedreturns v1.0.6 h1:vmiBcKV/3EqKY3ZiPxCINmpS431OcE1S47AQUwhrg8E=
|
||||
github.com/firefart/nonamedreturns v1.0.6/go.mod h1:R8NisJnSIpvPWheCq0mNRXJok6D8h7fagJTF8EMEwCo=
|
||||
github.com/frankban/quicktest v1.7.2/go.mod h1:jaStnuzAqU1AJdCO0l53JDCJrVDKcS03DbaAcR7Ks/o=
|
||||
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
||||
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
|
||||
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||
github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE=
|
||||
github.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps=
|
||||
github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI=
|
||||
github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU=
|
||||
github.com/fzipp/gocyclo v0.6.0 h1:lsblElZG7d3ALtGMx9fmxeTKZaLLpU8mET09yN4BBLo=
|
||||
github.com/fzipp/gocyclo v0.6.0/go.mod h1:rXPyn8fnlpa0R2csP/31uerbiVBugk5whMdlyaLkLoA=
|
||||
github.com/gabriel-vasile/mimetype v1.4.10 h1:zyueNbySn/z8mJZHLt6IPw0KoZsiQNszIpU+bX4+ZK0=
|
||||
github.com/gabriel-vasile/mimetype v1.4.10/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=
|
||||
github.com/gabriel-vasile/mimetype v1.4.9 h1:5k+WDwEsD9eTLL8Tz3L0VnmVh9QxGjRmjBvAG7U/oYY=
|
||||
github.com/gabriel-vasile/mimetype v1.4.9/go.mod h1:WnSQhFKJuBlRyLiKohA/2DtIlPFAbguNaG7QCHcyGok=
|
||||
github.com/gen2brain/go-fitz v1.24.15 h1:sJNB1MOWkqnzzENPHggFpgxTwW0+S5WF/rM5wUBpJWo=
|
||||
github.com/gen2brain/go-fitz v1.24.15/go.mod h1:SftkiVbTHqF141DuiLwBBM65zP7ig6AVDQpf2WlHamo=
|
||||
github.com/getkin/kin-openapi v0.133.0 h1:pJdmNohVIJ97r4AUFtEXRXwESr8b0bD721u/Tz6k8PQ=
|
||||
github.com/getkin/kin-openapi v0.133.0/go.mod h1:boAciF6cXk5FhPqe/NQeBTeenbjqU4LhWBf09ILVvWE=
|
||||
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/ghostiam/protogetter v0.3.15 h1:1KF5sXel0HE48zh1/vn0Loiw25A9ApyseLzQuif1mLY=
|
||||
github.com/ghostiam/protogetter v0.3.15/go.mod h1:WZ0nw9pfzsgxuRsPOFQomgDVSWtDLJRfQJEhsGbmQMA=
|
||||
github.com/go-critic/go-critic v0.13.0 h1:kJzM7wzltQasSUXtYyTl6UaPVySO6GkaR1thFnJ6afY=
|
||||
@@ -284,38 +227,16 @@ github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vb
|
||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||
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.0 h1:TmMhghgNef9YXxTu1tOopo+0BGEytxA+okbry0HjZsM=
|
||||
github.com/go-openapi/jsonpointer v0.22.0/go.mod h1:xt3jV88UtExdIkkL7NloURjRQjbeUgcxFblMjq2iaiU=
|
||||
github.com/go-openapi/jsonreference v0.21.1 h1:bSKrcl8819zKiOgxkbVNRUBIr6Wwj9KYrDbMjRs0cDA=
|
||||
github.com/go-openapi/jsonreference v0.21.1/go.mod h1:PWs8rO4xxTUqKGu+lEvvCxD5k2X7QYkKAepJyCmSTT8=
|
||||
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
||||
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-openapi/jsonpointer v0.21.2 h1:AqQaNADVwq/VnkCmQg6ogE+M3FOsKTytwges0JdwVuA=
|
||||
github.com/go-openapi/jsonpointer v0.21.2/go.mod h1:50I1STOfbY1ycR8jGz8DaMeLCdXiI6aDteEdRNNzpdk=
|
||||
github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ=
|
||||
github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4=
|
||||
github.com/go-openapi/spec v0.21.0 h1:LTVzPc3p/RzRnkQqLRndbAzjY0d0BCL72A6j3CdL9ZY=
|
||||
github.com/go-openapi/spec v0.21.0/go.mod h1:78u6VdPw81XU44qEWGhtr982gJ5BWg2c0I5XwVMotYk=
|
||||
github.com/go-openapi/swag v0.24.1 h1:DPdYTZKo6AQCRqzwr/kGkxJzHhpKxZ9i/oX0zag+MF8=
|
||||
github.com/go-openapi/swag v0.24.1/go.mod h1:sm8I3lCPlspsBBwUm1t5oZeWZS0s7m/A+Psg0ooRU0A=
|
||||
github.com/go-openapi/swag/cmdutils v0.24.0 h1:KlRCffHwXFI6E5MV9n8o8zBRElpY4uK4yWyAMWETo9I=
|
||||
github.com/go-openapi/swag/cmdutils v0.24.0/go.mod h1:uxib2FAeQMByyHomTlsP8h1TtPd54Msu2ZDU/H5Vuf8=
|
||||
github.com/go-openapi/swag/conv v0.24.0 h1:ejB9+7yogkWly6pnruRX45D1/6J+ZxRu92YFivx54ik=
|
||||
github.com/go-openapi/swag/conv v0.24.0/go.mod h1:jbn140mZd7EW2g8a8Y5bwm8/Wy1slLySQQ0ND6DPc2c=
|
||||
github.com/go-openapi/swag/fileutils v0.24.0 h1:U9pCpqp4RUytnD689Ek/N1d2N/a//XCeqoH508H5oak=
|
||||
github.com/go-openapi/swag/fileutils v0.24.0/go.mod h1:3SCrCSBHyP1/N+3oErQ1gP+OX1GV2QYFSnrTbzwli90=
|
||||
github.com/go-openapi/swag/jsonname v0.24.0 h1:2wKS9bgRV/xB8c62Qg16w4AUiIrqqiniJFtZGi3dg5k=
|
||||
github.com/go-openapi/swag/jsonname v0.24.0/go.mod h1:GXqrPzGJe611P7LG4QB9JKPtUZ7flE4DOVechNaDd7Q=
|
||||
github.com/go-openapi/swag/jsonutils v0.24.0 h1:F1vE1q4pg1xtO3HTyJYRmEuJ4jmIp2iZ30bzW5XgZts=
|
||||
github.com/go-openapi/swag/jsonutils v0.24.0/go.mod h1:vBowZtF5Z4DDApIoxcIVfR8v0l9oq5PpYRUuteVu6f0=
|
||||
github.com/go-openapi/swag/loading v0.24.0 h1:ln/fWTwJp2Zkj5DdaX4JPiddFC5CHQpvaBKycOlceYc=
|
||||
github.com/go-openapi/swag/loading v0.24.0/go.mod h1:gShCN4woKZYIxPxbfbyHgjXAhO61m88tmjy0lp/LkJk=
|
||||
github.com/go-openapi/swag/mangling v0.24.0 h1:PGOQpViCOUroIeak/Uj/sjGAq9LADS3mOyjznmHy2pk=
|
||||
github.com/go-openapi/swag/mangling v0.24.0/go.mod h1:Jm5Go9LHkycsz0wfoaBDkdc4CkpuSnIEf62brzyCbhc=
|
||||
github.com/go-openapi/swag/netutils v0.24.0 h1:Bz02HRjYv8046Ycg/w80q3g9QCWeIqTvlyOjQPDjD8w=
|
||||
github.com/go-openapi/swag/netutils v0.24.0/go.mod h1:WRgiHcYTnx+IqfMCtu0hy9oOaPR0HnPbmArSRN1SkZM=
|
||||
github.com/go-openapi/swag/stringutils v0.24.0 h1:i4Z/Jawf9EvXOLUbT97O0HbPUja18VdBxeadyAqS1FM=
|
||||
github.com/go-openapi/swag/stringutils v0.24.0/go.mod h1:5nUXB4xA0kw2df5PRipZDslPJgJut+NjL7D25zPZ/4w=
|
||||
github.com/go-openapi/swag/typeutils v0.24.0 h1:d3szEGzGDf4L2y1gYOSSLeK6h46F+zibnEas2Jm/wIw=
|
||||
github.com/go-openapi/swag/typeutils v0.24.0/go.mod h1:q8C3Kmk/vh2VhpCLaoR2MVWOGP8y7Jc8l82qCTd1DYI=
|
||||
github.com/go-openapi/swag/yamlutils v0.24.0 h1:bhw4894A7Iw6ne+639hsBNRHg9iZg/ISrOVr+sJGp4c=
|
||||
github.com/go-openapi/swag/yamlutils v0.24.0/go.mod h1:DpKv5aYuaGm/sULePoeiG8uwMpZSfReo1HR3Ik0yaG8=
|
||||
github.com/go-openapi/swag v0.23.1 h1:lpsStH0n2ittzTnbaSloVZLuB5+fvSY/+hnagBjSNZU=
|
||||
github.com/go-openapi/swag v0.23.1/go.mod h1:STZs8TbRvEQQKUA+JZNAm3EWlgaOBGpyFDqQnDHMef0=
|
||||
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=
|
||||
@@ -348,12 +269,10 @@ github.com/go-toolsmith/strparse v1.1.0 h1:GAioeZUK9TGxnLS+qfdqNbA4z0SSm5zVNtCQi
|
||||
github.com/go-toolsmith/strparse v1.1.0/go.mod h1:7ksGy58fsaQkGQlY8WVoBFNyEPMGuJin1rfoPS4lBSQ=
|
||||
github.com/go-toolsmith/typep v1.1.0 h1:fIRYDyF+JywLfqzyhdiHzRop/GQDxxNhLGQ6gFUNHus=
|
||||
github.com/go-toolsmith/typep v1.1.0/go.mod h1:fVIw+7zjdsMxDA3ITWnH1yOiw1rnTQKCsF/sk2H/qig=
|
||||
github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs=
|
||||
github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
||||
github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss=
|
||||
github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
||||
github.com/go-xmlfmt/xmlfmt v1.1.3 h1:t8Ey3Uy7jDSEisW2K3somuMKIpzktkWptA0iFCnRUWY=
|
||||
github.com/go-xmlfmt/xmlfmt v1.1.3/go.mod h1:aUCEOzzezBEjDBbFBoSiya/gduyIiWYRP6CnSFIV8AM=
|
||||
github.com/gobuffalo/flect v1.0.3 h1:xeWBM2nui+qnVvNM4S3foBhCAL2XgPU+a7FdpelbTq4=
|
||||
github.com/gobuffalo/flect v1.0.3/go.mod h1:A5msMlrHtLqh9umBSnvabjsMrCcCpAyzglnDvkbYKHs=
|
||||
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
|
||||
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
|
||||
github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw=
|
||||
@@ -361,29 +280,13 @@ github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7Lk
|
||||
github.com/gofrs/flock v0.12.1 h1:MTLVXXHf8ekldpJk3AKicLij9MdwOWkZ+a/jHHZby9E=
|
||||
github.com/gofrs/flock v0.12.1/go.mod h1:9zxTsyu5xtJ9DK+1tFZyibEV7y3uwDxPPfbxeeHCoD0=
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/gohugoio/go-i18n/v2 v2.1.3-0.20230805085216-e63c13218d0e h1:QArsSubW7eDh8APMXkByjQWvuljwPGAGQpJEFn0F0wY=
|
||||
github.com/gohugoio/go-i18n/v2 v2.1.3-0.20230805085216-e63c13218d0e/go.mod h1:3Ltoo9Banwq0gOtcOwxuHG6omk+AwsQPADyw2vQYOJQ=
|
||||
github.com/gohugoio/hashstructure v0.5.0 h1:G2fjSBU36RdwEJBWJ+919ERvOVqAg9tfcYp47K9swqg=
|
||||
github.com/gohugoio/hashstructure v0.5.0/go.mod h1:Ser0TniXuu/eauYmrwM4o64EBvySxNzITEOLlm4igec=
|
||||
github.com/gohugoio/httpcache v0.7.0 h1:ukPnn04Rgvx48JIinZvZetBfHaWE7I01JR2Q2RrQ3Vs=
|
||||
github.com/gohugoio/httpcache v0.7.0/go.mod h1:fMlPrdY/vVJhAriLZnrF5QpN3BNAcoBClgAyQd+lGFI=
|
||||
github.com/gohugoio/hugo v0.149.1 h1:uWOc8Ve4h4e48FyYhBquRoHCJviyxA5yGrFJLT48yio=
|
||||
github.com/gohugoio/hugo v0.149.1/go.mod h1:HS6BP6e8FGxungP4CHC3zeLDvhBLnTJIjHJZWTZjs7o=
|
||||
github.com/gohugoio/hugo-goldmark-extensions/extras v0.5.0 h1:dco+7YiOryRoPOMXwwaf+kktZSCtlFtreNdiJbETvYE=
|
||||
github.com/gohugoio/hugo-goldmark-extensions/extras v0.5.0/go.mod h1:CRrxQTKeM3imw+UoS4EHKyrqB7Zp6sAJiqHit+aMGTE=
|
||||
github.com/gohugoio/hugo-goldmark-extensions/passthrough v0.3.1 h1:nUzXfRTszLliZuN0JTKeunXTRaiFX6ksaWP0puLLYAY=
|
||||
github.com/gohugoio/hugo-goldmark-extensions/passthrough v0.3.1/go.mod h1:Wy8ThAA8p2/w1DY05vEzq6EIeI2mzDjvHsu7ULBVwog=
|
||||
github.com/gohugoio/locales v0.14.0 h1:Q0gpsZwfv7ATHMbcTNepFd59H7GoykzWJIxi113XGDc=
|
||||
github.com/gohugoio/locales v0.14.0/go.mod h1:ip8cCAv/cnmVLzzXtiTpPwgJ4xhKZranqNqtoIu0b/4=
|
||||
github.com/gohugoio/localescompressed v1.0.1 h1:KTYMi8fCWYLswFyJAeOtuk/EkXR/KPTHHNN9OS+RTxo=
|
||||
github.com/gohugoio/localescompressed v1.0.1/go.mod h1:jBF6q8D7a0vaEmcWPNcAjUZLJaIVNiwvM3WlmTvooB0=
|
||||
github.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI=
|
||||
github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
||||
github.com/golang/geo v0.0.0-20190916061304-5b978397cfec/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI=
|
||||
github.com/golang/geo v0.0.0-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-20250909101022-21d0664d86be h1:LYVqvM1o7RQN4bdv0iQbd7+x2aQ5m0mi5aQp5r/oR9U=
|
||||
github.com/golang/geo v0.0.0-20250909101022-21d0664d86be/go.mod h1:AN0OjM34c3PbjAsX+QNma1nYtJtRxl+s9MZNV7S+efw=
|
||||
github.com/golang/geo v0.0.0-20250821133510-ecfc33a939ac h1:gBtvd74wZT02fkOyoG651Sx2imv5lKdVvDwpUjKPFnw=
|
||||
github.com/golang/geo v0.0.0-20250821133510-ecfc33a939ac/go.mod h1:AN0OjM34c3PbjAsX+QNma1nYtJtRxl+s9MZNV7S+efw=
|
||||
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=
|
||||
@@ -421,18 +324,16 @@ github.com/golangci/go-printf-func-name v0.1.0 h1:dVokQP+NMTO7jwO4bwsRwLWeudOVUP
|
||||
github.com/golangci/go-printf-func-name v0.1.0/go.mod h1:wqhWFH5mUdJQhweRnldEywnR5021wTdZSNgwYceV14s=
|
||||
github.com/golangci/gofmt v0.0.0-20250106114630-d62b90e6713d h1:viFft9sS/dxoYY0aiOTsLKO2aZQAPT4nlQCsimGcSGE=
|
||||
github.com/golangci/gofmt v0.0.0-20250106114630-d62b90e6713d/go.mod h1:ivJ9QDg0XucIkmwhzCDsqcnxxlDStoTl89jDMIoNxKY=
|
||||
github.com/golangci/golangci-lint/v2 v2.4.0 h1:qz6O6vr7kVzXJqyvHjHSz5fA3D+PM8v96QU5gxZCNWM=
|
||||
github.com/golangci/golangci-lint/v2 v2.4.0/go.mod h1:Oq7vuAf6L1iNL34uHDcsIF6Mnc0amOPdsT3/GlpHD+I=
|
||||
github.com/golangci/golangci-lint/v2 v2.1.6 h1:LXqShFfAGM5BDzEOWD2SL1IzJAgUOqES/HRBsfKjI+w=
|
||||
github.com/golangci/golangci-lint/v2 v2.1.6/go.mod h1:EPj+fgv4TeeBq3TcqaKZb3vkiV5dP4hHHKhXhEhzci8=
|
||||
github.com/golangci/golines v0.0.0-20250217134842-442fd0091d95 h1:AkK+w9FZBXlU/xUmBtSJN1+tAI4FIvy5WtnUnY8e4p8=
|
||||
github.com/golangci/golines v0.0.0-20250217134842-442fd0091d95/go.mod h1:k9mmcyWKSTMcPPvQUCfRWWQ9VHJ1U9Dc0R7kaXAgtnQ=
|
||||
github.com/golangci/misspell v0.7.0 h1:4GOHr/T1lTW0hhR4tgaaV1WS/lJ+ncvYCoFKmqJsj0c=
|
||||
github.com/golangci/misspell v0.7.0/go.mod h1:WZyyI2P3hxPY2UVHs3cS8YcllAeyfquQcKfdeE9AFVg=
|
||||
github.com/golangci/plugin-module-register v0.1.2 h1:e5WM6PO6NIAEcij3B053CohVp3HIYbzSuP53UAYgOpg=
|
||||
github.com/golangci/plugin-module-register v0.1.2/go.mod h1:1+QGTsKBvAIvPvoY/os+G5eoqxWn70HYDm2uvUyGuVw=
|
||||
github.com/golangci/misspell v0.6.0 h1:JCle2HUTNWirNlDIAUO44hUsKhOFqGPoC4LZxlaSXDs=
|
||||
github.com/golangci/misspell v0.6.0/go.mod h1:keMNyY6R9isGaSAu+4Q8NMBwMPkh15Gtc8UCVoDtAWo=
|
||||
github.com/golangci/plugin-module-register v0.1.1 h1:TCmesur25LnyJkpsVrupv1Cdzo+2f7zX0H6Jkw1Ol6c=
|
||||
github.com/golangci/plugin-module-register v0.1.1/go.mod h1:TTpqoB6KkwOJMV8u7+NyXMrkwwESJLOkfl9TxR1DGFc=
|
||||
github.com/golangci/revgrep v0.8.0 h1:EZBctwbVd0aMeRnNUsFogoyayvKHyxlV3CdUA46FX2s=
|
||||
github.com/golangci/revgrep v0.8.0/go.mod h1:U4R/s9dlXZsg8uJmaR1GrloUr14D7qDl8gi2iPXJH8k=
|
||||
github.com/golangci/swaggoswag v0.0.0-20250504205917-77f2aca3143e h1:ai0EfmVYE2bRA5htgAG9r7s3tHsfjIhN98WshBTJ9jM=
|
||||
github.com/golangci/swaggoswag v0.0.0-20250504205917-77f2aca3143e/go.mod h1:Vrn4B5oR9qRwM+f54koyeH3yzphlecwERs0el27Fr/s=
|
||||
github.com/golangci/unconvert v0.0.0-20250410112200-a129a6e6413e h1:gD6P7NEo7Eqtt0ssnqSJNNndxe69DOQ24A5h7+i3KpM=
|
||||
github.com/golangci/unconvert v0.0.0-20250410112200-a129a6e6413e/go.mod h1:h+wZwLjUTJnm/P2rwlbJdRPZXOzaT36/FwnPnY2inzc=
|
||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
@@ -447,6 +348,7 @@ github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
||||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
@@ -460,15 +362,13 @@ github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hf
|
||||
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20250607225305-033d6d78b36a h1://KbezygeMJZCSHH+HgUZiTeSoiuFspbMg1ge+eFj18=
|
||||
github.com/google/pprof v0.0.0-20250607225305-033d6d78b36a/go.mod h1:5hDyRhoBCxViHszMt12TnOpEI4VVi+U8Gm9iphldiMA=
|
||||
github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad h1:a6HEuzUHeKH6hwfN/ZoQgRgVIWFJljSWa/zetS2WTvg=
|
||||
github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||
github.com/gordonklaus/ineffassign v0.1.0 h1:y2Gd/9I7MdY1oEIt+n+rowjBNDcLQq3RsH5hwJd0f9s=
|
||||
github.com/gordonklaus/ineffassign v0.1.0/go.mod h1:Qcp2HIAYhR7mNUVSIxZww3Guk4it82ghYcEXIAk+QT0=
|
||||
github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8=
|
||||
github.com/gorilla/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A3b0=
|
||||
github.com/gostaticanalysis/analysisutil v0.7.1 h1:ZMCjoue3DtDWQ5WyU16YbjbQEQ3VuzwxALrpYd+HeKk=
|
||||
github.com/gostaticanalysis/analysisutil v0.7.1/go.mod h1:v21E3hY37WKMGSnbsw2S/ojApNWb6C1//mXO48CXbVc=
|
||||
github.com/gostaticanalysis/comment v1.4.1/go.mod h1:ih6ZxzTHLdadaiSnF5WY3dxUoXfXAlTaRzuaNDlSado=
|
||||
@@ -486,8 +386,6 @@ github.com/gtsteffaniak/go-cache v0.0.0-20250521142451-edc77dfcb063 h1:CoLbeqTmM
|
||||
github.com/gtsteffaniak/go-cache v0.0.0-20250521142451-edc77dfcb063/go.mod h1:rHUZvf4huhMX/bnNK4/oQ3sNAE2fYC2jFpAWZiBPWac=
|
||||
github.com/gtsteffaniak/go-logger v0.1.2 h1:0g6UxxNwU+Q+7IVRw+Yn7AuYsIK0ukiTgbzjTLm+Uy4=
|
||||
github.com/gtsteffaniak/go-logger v0.1.2/go.mod h1:U3ZkdAclcxXNMvoQLq+zDkqcTmdqhX3hTf+6AuTo6v0=
|
||||
github.com/hairyhenderson/go-codeowners v0.7.0 h1:s0W4wF8bdsBEjTWzwzSlsatSthWtTAF2xLgo4a4RwAo=
|
||||
github.com/hairyhenderson/go-codeowners v0.7.0/go.mod h1:wUlNgQ3QjqC4z8DnM5nnCYVq/icpqXJyJOukKx5U8/Q=
|
||||
github.com/hashicorp/go-immutable-radix/v2 v2.1.0 h1:CUW5RYIcysz+D3B+l1mDeXrQ7fUvGGCwJfdASSzbrfo=
|
||||
github.com/hashicorp/go-immutable-radix/v2 v2.1.0/go.mod h1:hgdqLXA4f6NIjRVisM1TJ9aOJVNRqKZj+xDGF6m7PBw=
|
||||
github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=
|
||||
@@ -506,16 +404,14 @@ github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSo
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
github.com/jdkato/prose v1.2.1 h1:Fp3UnJmLVISmlc57BgKUzdjr0lOtjqTZicL3PaYy6cU=
|
||||
github.com/jdkato/prose v1.2.1/go.mod h1:AiRHgVagnEx2JbQRQowVBKjG0bcs/vtkGCH1dYAL1rA=
|
||||
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||
github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4=
|
||||
github.com/jgautheron/goconst v1.8.2 h1:y0XF7X8CikZ93fSNT6WBTb/NElBu9IjaY7CCYQrCMX4=
|
||||
github.com/jgautheron/goconst v1.8.2/go.mod h1:A0oxgBCHy55NQn6sYpO7UdnA9p+h7cPtoOZUmvNIako=
|
||||
github.com/jgautheron/goconst v1.8.1 h1:PPqCYp3K/xlOj5JmIe6O1Mj6r1DbkdbLtR3AJuZo414=
|
||||
github.com/jgautheron/goconst v1.8.1/go.mod h1:A0oxgBCHy55NQn6sYpO7UdnA9p+h7cPtoOZUmvNIako=
|
||||
github.com/jingyugao/rowserrcheck v1.1.1 h1:zibz55j/MJtLsjP1OF4bSdgXxwL1b+Vn7Tjzq7gFzUs=
|
||||
github.com/jingyugao/rowserrcheck v1.1.1/go.mod h1:4yvlZSDb3IyDTUZJUmpZfm2Hwok+Dtp+nu2qOq+er9c=
|
||||
github.com/jjti/go-spancheck v0.6.5 h1:lmi7pKxa37oKYIMScialXUK6hP3iY5F1gu+mLBPgYB8=
|
||||
github.com/jjti/go-spancheck v0.6.5/go.mod h1:aEogkeatBrbYsyW6y5TgDfihCulDYciL1B7rG2vSsrU=
|
||||
github.com/jjti/go-spancheck v0.6.4 h1:Tl7gQpYf4/TMU7AT84MN83/6PutY21Nb9fuQjFTpRRc=
|
||||
github.com/jjti/go-spancheck v0.6.4/go.mod h1:yAEYdKJ2lRkDA8g7X+oKUHXOWVAXSBJRv04OhF+QUjk=
|
||||
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
|
||||
@@ -554,20 +450,18 @@ github.com/kulti/thelper v0.6.3 h1:ElhKf+AlItIu+xGnI990no4cE2+XaSu1ULymV2Yulxs=
|
||||
github.com/kulti/thelper v0.6.3/go.mod h1:DsqKShOvP40epevkFrvIwkCMNYxMeTNjdWL4dqWHZ6I=
|
||||
github.com/kunwardeep/paralleltest v1.0.14 h1:wAkMoMeGX/kGfhQBPODT/BL8XhK23ol/nuQ3SwFaUw8=
|
||||
github.com/kunwardeep/paralleltest v1.0.14/go.mod h1:di4moFqtfz3ToSKxhNjhOZL+696QtJGCFe132CbBLGk=
|
||||
github.com/kyokomi/emoji/v2 v2.2.13 h1:GhTfQa67venUUvmleTNFnb+bi7S3aocF7ZCXU9fSO7U=
|
||||
github.com/kyokomi/emoji/v2 v2.2.13/go.mod h1:JUcn42DTdsXJo1SWanHh4HKDEyPaR5CqkmoirZZP9qE=
|
||||
github.com/lasiar/canonicalheader v1.1.2 h1:vZ5uqwvDbyJCnMhmFYimgMZnJMjwljN5VGY0VKbMXb4=
|
||||
github.com/lasiar/canonicalheader v1.1.2/go.mod h1:qJCeLFS0G/QlLQ506T+Fk/fWMa2VmBUiEI2cuMK4djI=
|
||||
github.com/ldez/exptostd v0.4.4 h1:58AtQjnLcT/tI5W/1KU7xE/O7zW9RAWB6c/ScQAnfus=
|
||||
github.com/ldez/exptostd v0.4.4/go.mod h1:QfdzPw6oHjFVdNV7ILoPu5sw3OZ3OG1JS0I5JN3J4Js=
|
||||
github.com/ldez/gomoddirectives v0.7.0 h1:EOx8Dd56BZYSez11LVgdj025lKwlP0/E5OLSl9HDwsY=
|
||||
github.com/ldez/gomoddirectives v0.7.0/go.mod h1:wR4v8MN9J8kcwvrkzrx6sC9xe9Cp68gWYCsda5xvyGc=
|
||||
github.com/ldez/grignotin v0.10.0 h1:NQPeh1E/Eza4F0exCeC1WkpnLvgUcQDT8MQ1vOLML0E=
|
||||
github.com/ldez/grignotin v0.10.0/go.mod h1:oR4iCKUP9fwoeO6vCQeD7M5SMxCT6xdVas4vg0h1LaI=
|
||||
github.com/ldez/exptostd v0.4.3 h1:Ag1aGiq2epGePuRJhez2mzOpZ8sI9Gimcb4Sb3+pk9Y=
|
||||
github.com/ldez/exptostd v0.4.3/go.mod h1:iZBRYaUmcW5jwCR3KROEZ1KivQQp6PHXbDPk9hqJKCQ=
|
||||
github.com/ldez/gomoddirectives v0.6.1 h1:Z+PxGAY+217f/bSGjNZr/b2KTXcyYLgiWI6geMBN2Qc=
|
||||
github.com/ldez/gomoddirectives v0.6.1/go.mod h1:cVBiu3AHR9V31em9u2kwfMKD43ayN5/XDgr+cdaFaKs=
|
||||
github.com/ldez/grignotin v0.9.0 h1:MgOEmjZIVNn6p5wPaGp/0OKWyvq42KnzAt/DAb8O4Ow=
|
||||
github.com/ldez/grignotin v0.9.0/go.mod h1:uaVTr0SoZ1KBii33c47O1M8Jp3OP3YDwhZCmzT9GHEk=
|
||||
github.com/ldez/tagliatelle v0.7.1 h1:bTgKjjc2sQcsgPiT902+aadvMjCeMHrY7ly2XKFORIk=
|
||||
github.com/ldez/tagliatelle v0.7.1/go.mod h1:3zjxUpsNB2aEZScWiZTHrAXOl1x25t3cRmzfK1mlo2I=
|
||||
github.com/ldez/usetesting v0.5.0 h1:3/QtzZObBKLy1F4F8jLuKJiKBjjVFi1IavpoWbmqLwc=
|
||||
github.com/ldez/usetesting v0.5.0/go.mod h1:Spnb4Qppf8JTuRgblLrEWb7IE6rDmUpGvxY3iRrzvDQ=
|
||||
github.com/ldez/usetesting v0.4.3 h1:pJpN0x3fMupdTf/IapYjnkhiY1nSTN+pox1/GyBRw3k=
|
||||
github.com/ldez/usetesting v0.4.3/go.mod h1:eEs46T3PpQ+9RgN9VjpY6qWdiw2/QmfiDeWmdZdrjIQ=
|
||||
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
||||
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
||||
github.com/leonklingele/grouper v1.1.2 h1:o1ARBDLOmmasUaNDesWqWCIFH3u7hoFlM84YrjT3mIY=
|
||||
@@ -580,18 +474,12 @@ github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamh
|
||||
github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
|
||||
github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4=
|
||||
github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU=
|
||||
github.com/makeworld-the-better-one/dither/v2 v2.4.0 h1:Az/dYXiTcwcRSe59Hzw4RI1rSnAZns+1msaCXetrMFE=
|
||||
github.com/makeworld-the-better-one/dither/v2 v2.4.0/go.mod h1:VBtN8DXO7SNtyGmLiGA7IsFeKrBkQPze1/iAeM95arc=
|
||||
github.com/manuelarte/embeddedstructfieldcheck v0.3.0 h1:VhGqK8gANDvFYDxQkjPbv7/gDJtsGU9k6qj/hC2hgso=
|
||||
github.com/manuelarte/embeddedstructfieldcheck v0.3.0/go.mod h1:LSo/IQpPfx1dXMcX4ibZCYA7Yy6ayZHIaOGM70+1Wy8=
|
||||
github.com/manuelarte/funcorder v0.5.0 h1:llMuHXXbg7tD0i/LNw8vGnkDTHFpTnWqKPI85Rknc+8=
|
||||
github.com/manuelarte/funcorder v0.5.0/go.mod h1:Yt3CiUQthSBMBxjShjdXMexmzpP8YGvGLjrxJNkO2hA=
|
||||
github.com/manuelarte/funcorder v0.2.1 h1:7QJsw3qhljoZ5rH0xapIvjw31EcQeFbF31/7kQ/xS34=
|
||||
github.com/manuelarte/funcorder v0.2.1/go.mod h1:BQQ0yW57+PF9ZpjpeJDKOffEsQbxDFKW8F8zSMe/Zd0=
|
||||
github.com/maratori/testableexamples v1.0.0 h1:dU5alXRrD8WKSjOUnmJZuzdxWOEQ57+7s93SLMxb2vI=
|
||||
github.com/maratori/testableexamples v1.0.0/go.mod h1:4rhjL1n20TUTT4vdh3RDqSizKLyXp7K2u6HgraZCGzE=
|
||||
github.com/maratori/testpackage v1.1.1 h1:S58XVV5AD7HADMmD0fNnziNHqKvSdDuEKdPD1rNTU04=
|
||||
github.com/maratori/testpackage v1.1.1/go.mod h1:s4gRK/ym6AMrqpOa/kEbQTV4Q4jb7WeLZzVhVVVOQMc=
|
||||
github.com/marekm4/color-extractor v1.2.1 h1:3Zb2tQsn6bITZ8MBVhc33Qn1k5/SEuZ18mrXGUqIwn0=
|
||||
github.com/marekm4/color-extractor v1.2.1/go.mod h1:90VjmiHI6M8ez9eYUaXLdcKnS+BAOp7w+NpwBdkJmpA=
|
||||
github.com/matoous/godox v1.1.0 h1:W5mqwbyWrwZv6OQ5Z1a/DHGMOvXYCBP3+Ht7KMoJhq4=
|
||||
github.com/matoous/godox v1.1.0/go.mod h1:jgE/3fUXiTurkdHOLT5WEkThTSuE7yxHv5iWPa80afs=
|
||||
github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE=
|
||||
@@ -600,57 +488,42 @@ github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHP
|
||||
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
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/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.11.0 h1:b/gLLpBE427o+Xmd8G58gSA+KtBwxWinH/A565Awh0w=
|
||||
github.com/mgechev/revive v1.11.0/go.mod h1:tI0oLF/2uj+InHCBLrrqfTKfjtFTBCFFfG05auyzgdw=
|
||||
github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk=
|
||||
github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA=
|
||||
github.com/mgechev/revive v1.9.0 h1:8LaA62XIKrb8lM6VsBSQ92slt/o92z5+hTw3CmrvSrM=
|
||||
github.com/mgechev/revive v1.9.0/go.mod h1:LAPq3+MgOf7GcL5PlWIkHb0PT7XH4NuC2LdWymhb9Mo=
|
||||
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
|
||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/mitchellh/mapstructure v1.5.1-0.20231216201459-8508981c8b6c h1:cqn374mizHuIWj+OSJCajGr/phAmuMug9qIX3l9CflE=
|
||||
github.com/mitchellh/mapstructure v1.5.1-0.20231216201459-8508981c8b6c/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
||||
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw=
|
||||
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8=
|
||||
github.com/moricho/tparallel v0.3.2 h1:odr8aZVFA3NZrNybggMkYO3rgPRcqjeQUlBBFVxKHTI=
|
||||
github.com/moricho/tparallel v0.3.2/go.mod h1:OQ+K3b4Ln3l2TZveGCywybl68glfLEwFGqvnjok8b+U=
|
||||
github.com/muesli/smartcrop v0.3.0 h1:JTlSkmxWg/oQ1TcLDoypuirdE8Y/jzNirQeLkxpA6Oc=
|
||||
github.com/muesli/smartcrop v0.3.0/go.mod h1:i2fCI/UorTfgEpPPLWiFBv4pye+YAG78RwcQLUkocpI=
|
||||
github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc=
|
||||
github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/nakabonne/nestif v0.3.1 h1:wm28nZjhQY5HyYPx+weN3Q65k6ilSBxDb8v5S81B81U=
|
||||
github.com/nakabonne/nestif v0.3.1/go.mod h1:9EtoZochLn5iUprVDmDjqGKPofoUEBL8U4Ngq6aY7OE=
|
||||
github.com/niklasfasching/go-org v1.9.1 h1:/3s4uTPOF06pImGa2Yvlp24yKXZoTYM+nsIlMzfpg/0=
|
||||
github.com/niklasfasching/go-org v1.9.1/go.mod h1:ZAGFFkWvUQcpazmi/8nHqwvARpr1xpb+Es67oUGX/48=
|
||||
github.com/nishanths/exhaustive v0.12.0 h1:vIY9sALmw6T/yxiASewa4TQcFsVYZQQRUQJhKRf3Swg=
|
||||
github.com/nishanths/exhaustive v0.12.0/go.mod h1:mEZ95wPIZW+x8kC4TgC+9YCUgiST7ecevsVDTgc2obs=
|
||||
github.com/nishanths/predeclared v0.2.2 h1:V2EPdZPliZymNAn79T8RkNApBjMmVKh5XRpLm/w98Vk=
|
||||
github.com/nishanths/predeclared v0.2.2/go.mod h1:RROzoN6TnGQupbC+lqggsOlcgysk3LMK/HI84Mp280c=
|
||||
github.com/nunnatsa/ginkgolinter v0.20.0 h1:OmWLkAFO2HUTYcU6mprnKud1Ey5pVdiVNYGO5HVicx8=
|
||||
github.com/nunnatsa/ginkgolinter v0.20.0/go.mod h1:dCIuFlTPfQerXgGUju3VygfAFPdC5aE1mdacCDKDJcQ=
|
||||
github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 h1:G7ERwszslrBzRxj//JalHPu/3yz+De2J+4aLtSRlHiY=
|
||||
github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037/go.mod h1:2bpvgLBZEtENV5scfDFEtB/5+1M4hkQhDQrccEJ/qGw=
|
||||
github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 h1:bQx3WeLcUWy+RletIKwUIt4x3t8n2SxavmoclizMb8c=
|
||||
github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90/go.mod h1:y5+oSEHCPT/DGrS++Wc/479ERge0zTFxaF8PbGKcg2o=
|
||||
github.com/olekukonko/errors v1.1.0 h1:RNuGIh15QdDenh+hNvKrJkmxxjV4hcS50Db478Ou5sM=
|
||||
github.com/olekukonko/errors v1.1.0/go.mod h1:ppzxA5jBKcO1vIpCXQ9ZqgDh8iwODz6OXIGKU8r5m4Y=
|
||||
github.com/olekukonko/ll v0.0.9 h1:Y+1YqDfVkqMWuEQMclsF9HUR5+a82+dxJuL1HHSRpxI=
|
||||
github.com/olekukonko/ll v0.0.9/go.mod h1:En+sEW0JNETl26+K8eZ6/W4UQ7CYSrrgg/EdIYT2H8g=
|
||||
github.com/olekukonko/tablewriter v1.0.9 h1:XGwRsYLC2bY7bNd93Dk51bcPZksWZmLYuaTHR0FqfL8=
|
||||
github.com/olekukonko/tablewriter v1.0.9/go.mod h1:5c+EBPeSqvXnLLgkm9isDdzR3wjfBkHR9Nhfp3NWrzo=
|
||||
github.com/onsi/ginkgo/v2 v2.23.4 h1:ktYTpKJAVZnDT4VjxSbiBenUjmlL/5QkBEocaWXiQus=
|
||||
github.com/onsi/ginkgo/v2 v2.23.4/go.mod h1:Bt66ApGPBFzHyR+JO10Zbt0Gsp4uWxu5mIOTusL46e8=
|
||||
github.com/onsi/gomega v1.37.0 h1:CdEG8g0S133B4OswTDC/5XPSzE1OeP29QOioj2PID2Y=
|
||||
github.com/onsi/gomega v1.37.0/go.mod h1:8D9+Txp43QWKhM24yyOBEdpkzN8FvJyAwecBgsU4KU0=
|
||||
github.com/nunnatsa/ginkgolinter v0.19.1 h1:mjwbOlDQxZi9Cal+KfbEJTCz327OLNfwNvoZ70NJ+c4=
|
||||
github.com/nunnatsa/ginkgolinter v0.19.1/go.mod h1:jkQ3naZDmxaZMXPWaS9rblH+i+GWXQCaS/JFIWcOH2s=
|
||||
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
|
||||
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
|
||||
github.com/onsi/ginkgo/v2 v2.23.3 h1:edHxnszytJ4lD9D5Jjc4tiDkPBZ3siDeJJkUZJJVkp0=
|
||||
github.com/onsi/ginkgo/v2 v2.23.3/go.mod h1:zXTP6xIp3U8aVuXN8ENK9IXRaTjFnpVB9mGmaSRvxnM=
|
||||
github.com/onsi/gomega v1.36.3 h1:hID7cr8t3Wp26+cYnfcjR6HpJ00fdogN6dqZ1t6IylU=
|
||||
github.com/onsi/gomega v1.36.3/go.mod h1:8D9+Txp43QWKhM24yyOBEdpkzN8FvJyAwecBgsU4KU0=
|
||||
github.com/otiai10/copy v1.2.0/go.mod h1:rrF5dJ5F0t/EWSYODDu4j9/vEeYHMkc8jt0zJChqQWw=
|
||||
github.com/otiai10/copy v1.14.0 h1:dCI/t1iTdYGtkvCuBG2BgR6KZa83PTclw4U5n2wAllU=
|
||||
github.com/otiai10/copy v1.14.0/go.mod h1:ECfuL02W+/FkTWZWgQqXPWZgW9oeKCSQ5qVfSc4qc4w=
|
||||
@@ -658,14 +531,10 @@ github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJ
|
||||
github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6j4vs=
|
||||
github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo=
|
||||
github.com/otiai10/mint v1.3.1/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc=
|
||||
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 h1:onHthvaw9LFnH4t2DcNVpwGmV9E1BkGknEliJkfwQj0=
|
||||
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58/go.mod h1:DXv8WO4yhMYhSNPKjeNKa5WY9YCIEBRbNzFFPJbWO6Y=
|
||||
github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
|
||||
github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
|
||||
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
|
||||
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
||||
github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s=
|
||||
github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
@@ -721,22 +590,20 @@ github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7
|
||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd h1:CmH9+J6ZSsIjUK3dcGsnCnO41eRBOnY12zwkn5qVwgc=
|
||||
github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd/go.mod h1:hPqNNc0+uJM6H+SuU8sEs5K5IQeKccPqeSjfgcKGgPk=
|
||||
github.com/ryancurrah/gomodguard v1.4.1 h1:eWC8eUMNZ/wM/PWuZBv7JxxqT5fiIKSIyTvjb7Elr+g=
|
||||
github.com/ryancurrah/gomodguard v1.4.1/go.mod h1:qnMJwV1hX9m+YJseXEBhd2s90+1Xn6x9dLz11ualI1I=
|
||||
github.com/ryanrolds/sqlclosecheck v0.5.1 h1:dibWW826u0P8jNLsLN+En7+RqWWTYrjCB9fJfSfdyCU=
|
||||
github.com/ryanrolds/sqlclosecheck v0.5.1/go.mod h1:2g3dUjoS6AL4huFdv6wn55WpLIDjY7ZgUR4J8HOO/XQ=
|
||||
github.com/sanposhiho/wastedassign/v2 v2.1.0 h1:crurBF7fJKIORrV85u9UUpePDYGWnwvv3+A96WvwXT0=
|
||||
github.com/sanposhiho/wastedassign/v2 v2.1.0/go.mod h1:+oSmSC+9bQ+VUAxA66nBb0Z7N8CK7mscKTDYC6aIek4=
|
||||
github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 h1:KRzFb2m7YtdldCEkzs6KqmJw4nqEVZGK7IN2kJkjTuQ=
|
||||
github.com/santhosh-tekuri/jsonschema/v6 v6.0.2/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU=
|
||||
github.com/santhosh-tekuri/jsonschema/v6 v6.0.1 h1:PKK9DyHxif4LZo+uQSgXNqs0jj5+xZwwfKHgph2lxBw=
|
||||
github.com/santhosh-tekuri/jsonschema/v6 v6.0.1/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU=
|
||||
github.com/sashamelentyev/interfacebloat v1.1.0 h1:xdRdJp0irL086OyW1H/RTZTr1h/tMEOsumirXcOJqAw=
|
||||
github.com/sashamelentyev/interfacebloat v1.1.0/go.mod h1:+Y9yU5YdTkrNvoX0xHc84dxiN1iBi9+G8zZIhPVoNjQ=
|
||||
github.com/sashamelentyev/usestdlibvars v1.29.0 h1:8J0MoRrw4/NAXtjQqTHrbW9NN+3iMf7Knkq057v4XOQ=
|
||||
github.com/sashamelentyev/usestdlibvars v1.29.0/go.mod h1:8PpnjHMk5VdeWlVb4wCdrB8PNbLqZ3wBZTZWkrpZZL8=
|
||||
github.com/securego/gosec/v2 v2.22.7 h1:8/9P+oTYI4yIpAzccQKVsg1/90Po+JzGtAhqoHImDeM=
|
||||
github.com/securego/gosec/v2 v2.22.7/go.mod h1:510TFNDMrIPytokyHQAVLvPeDr41Yihn2ak8P+XQfNE=
|
||||
github.com/sashamelentyev/usestdlibvars v1.28.0 h1:jZnudE2zKCtYlGzLVreNp5pmCdOxXUzwsMDBkR21cyQ=
|
||||
github.com/sashamelentyev/usestdlibvars v1.28.0/go.mod h1:9nl0jgOfHKWNFS43Ojw0i7aRoS4j6EBye3YBhmAIRF8=
|
||||
github.com/securego/gosec/v2 v2.22.3 h1:mRrCNmRF2NgZp4RJ8oJ6yPJ7G4x6OCiAXHd8x4trLRc=
|
||||
github.com/securego/gosec/v2 v2.22.3/go.mod h1:42M9Xs0v1WseinaB/BmNGO8AVqG8vRfhC2686ACY48k=
|
||||
github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ=
|
||||
github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
|
||||
github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk=
|
||||
@@ -749,22 +616,21 @@ github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ
|
||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/sivchari/containedctx v1.0.3 h1:x+etemjbsh2fB5ewm5FeLNi5bUjK0V8n0RB+Wwfd0XE=
|
||||
github.com/sivchari/containedctx v1.0.3/go.mod h1:c1RDvCbnJLtH4lLcYD/GqwiBSSf4F5Qk0xld2rBqzJ4=
|
||||
github.com/sonatard/noctx v0.4.0 h1:7MC/5Gg4SQ4lhLYR6mvOP6mQVSxCrdyiExo7atBs27o=
|
||||
github.com/sonatard/noctx v0.4.0/go.mod h1:64XdbzFb18XL4LporKXp8poqZtPKbCrqQ402CV+kJas=
|
||||
github.com/sonatard/noctx v0.1.0 h1:JjqOc2WN16ISWAjAk8M5ej0RfExEXtkEyExl2hLW+OM=
|
||||
github.com/sonatard/noctx v0.1.0/go.mod h1:0RvBxqY8D4j9cTTTWE8ylt2vqj2EPI8fHmrxHdsaZ2c=
|
||||
github.com/sourcegraph/go-diff v0.7.0 h1:9uLlrd5T46OXs5qpp8L/MTltk0zikUGi0sNNyCpA8G0=
|
||||
github.com/sourcegraph/go-diff v0.7.0/go.mod h1:iBszgVvyxdc8SFZ7gm69go2KDdt3ag071iBaWPF6cjs=
|
||||
github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I=
|
||||
github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg=
|
||||
github.com/spf13/cast v1.9.2 h1:SsGfm7M8QOFtEzumm7UZrZdLLquNdzFYfIbEXntcFbE=
|
||||
github.com/spf13/cast v1.9.2/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo=
|
||||
github.com/spf13/afero v1.14.0 h1:9tH6MapGnn/j0eb0yIXiLjERO8RB6xIVZRDCX7PtqWA=
|
||||
github.com/spf13/afero v1.14.0/go.mod h1:acJQ8t0ohCGuMN3O+Pv0V0hgMxNYDlvdk+VTfyZmbYo=
|
||||
github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w=
|
||||
github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU=
|
||||
github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo=
|
||||
github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0=
|
||||
github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
|
||||
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
|
||||
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/pflag v1.0.7 h1:vN6T9TfwStFPFM5XzjsvmzZkLuaLX+HS+0SeFLRgU6M=
|
||||
github.com/spf13/pflag v1.0.7/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/viper v1.12.0 h1:CZ7eSOd3kZoaYDLbXnmzgQI5RlciuXBMA+18HwHRfZQ=
|
||||
github.com/spf13/viper v1.12.0/go.mod h1:b6COn30jlNxbm/V2IqWiNWkJ+vZNiMNksliPCiuKtSI=
|
||||
github.com/ssgreg/nlreturn/v2 v2.2.1 h1:X4XDI7jstt3ySqGU86YGAURbxw3oTDPK9sPEi6YEwQ0=
|
||||
@@ -773,14 +639,20 @@ github.com/stbenjam/no-sprintf-host-port v0.2.0 h1:i8pxvGrt1+4G0czLr/WnmyH7zbZ8B
|
||||
github.com/stbenjam/no-sprintf-host-port v0.2.0/go.mod h1:eL0bQ9PasS0hsyTyfTjjG+E80QIyPnBVQbYZyv20Jfk=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
|
||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/subosito/gotenv v1.4.1 h1:jyEFiXpy21Wm81FBN71l9VoMMV8H8jG+qIK3GCpY6Qs=
|
||||
github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0=
|
||||
github.com/swaggo/files v1.0.1 h1:J1bVJ4XHZNq0I46UU90611i9/YzdrF7x92oX1ig5IdE=
|
||||
@@ -791,20 +663,12 @@ github.com/swaggo/swag v1.16.6 h1:qBNcx53ZaX+M5dxVyTrgQ0PJ/ACK+NzhwcbieTt+9yI=
|
||||
github.com/swaggo/swag v1.16.6/go.mod h1:ngP2etMK5a0P3QBizic5MEwpRmluJZPHjXcMoj4Xesg=
|
||||
github.com/tdakkota/asciicheck v0.4.1 h1:bm0tbcmi0jezRA2b5kg4ozmMuGAFotKI3RZfrhfovg8=
|
||||
github.com/tdakkota/asciicheck v0.4.1/go.mod h1:0k7M3rCfRXb0Z6bwgvkEIMleKH3kXNz9UqJ9Xuqopr8=
|
||||
github.com/tdewolff/minify/v2 v2.24.2 h1:vnY3nTulEAbCAAlxTxPPDkzG24rsq31SOzp63yT+7mo=
|
||||
github.com/tdewolff/minify/v2 v2.24.2/go.mod h1:1JrCtoZXaDbqioQZfk3Jdmr0GPJKiU7c1Apmb+7tCeE=
|
||||
github.com/tdewolff/parse/v2 v2.8.3 h1:5VbvtJ83cfb289A1HzRA9sf02iT8YyUwN84ezjkdY1I=
|
||||
github.com/tdewolff/parse/v2 v2.8.3/go.mod h1:Hwlni2tiVNKyzR1o6nUs4FOF07URA+JLBLd6dlIXYqo=
|
||||
github.com/tdewolff/test v1.0.11 h1:FdLbwQVHxqG16SlkGveC0JVyrJN62COWTRyUFzfbtBE=
|
||||
github.com/tdewolff/test v1.0.11/go.mod h1:XPuWBzvdUzhCuxWO1ojpXsyzsA5bFoS3tO/Q3kFuTG8=
|
||||
github.com/tenntenn/modver v1.0.1 h1:2klLppGhDgzJrScMpkj9Ujy3rXPUspSjAcev9tSEBgA=
|
||||
github.com/tenntenn/modver v1.0.1/go.mod h1:bePIyQPb7UeioSRkw3Q0XeMhYZSMx9B8ePqg6SAMGH0=
|
||||
github.com/tenntenn/text/transform v0.0.0-20200319021203-7eef512accb3 h1:f+jULpRQGxTSkNYKJ51yaw6ChIqO+Je8UqsTKN/cDag=
|
||||
github.com/tenntenn/text/transform v0.0.0-20200319021203-7eef512accb3/go.mod h1:ON8b8w4BN/kE1EOhwT0o+d62W65a6aPw1nouo9LMgyY=
|
||||
github.com/tetafro/godot v1.5.1 h1:PZnjCol4+FqaEzvZg5+O8IY2P3hfY9JzRBNPv1pEDS4=
|
||||
github.com/tetafro/godot v1.5.1/go.mod h1:cCdPtEndkmqqrhiCfkmxDodMQJ/f3L1BCNskCUZdTwk=
|
||||
github.com/tetratelabs/wazero v1.9.0 h1:IcZ56OuxrtaEz8UYNRHBrUa9bYeX9oVY93KspZZBf/I=
|
||||
github.com/tetratelabs/wazero v1.9.0/go.mod h1:TSbcXCfFP0L2FGkRPxHphadXPjo1T6W+CseNNY7EkjM=
|
||||
github.com/timakin/bodyclose v0.0.0-20241222091800-1db5c5ca4d67 h1:9LPGD+jzxMlnk5r6+hJnar67cgpDIz/iyD+rfl5r2Vk=
|
||||
github.com/timakin/bodyclose v0.0.0-20241222091800-1db5c5ca4d67/go.mod h1:mkjARE7Yr8qU23YcGMSALbIxTQ9r9QBVahQOBRfU460=
|
||||
github.com/timonwong/loggercheck v0.11.0 h1:jdaMpYBl+Uq9mWPXv1r8jc5fC3gyXx4/WGwTnnNKn4M=
|
||||
@@ -821,12 +685,10 @@ github.com/urfave/cli/v2 v2.3.0 h1:qph92Y649prgesehzOrQjdWyxFOp/QVM+6imKHad91M=
|
||||
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
|
||||
github.com/uudashr/gocognit v1.2.0 h1:3BU9aMr1xbhPlvJLSydKwdLN3tEUUrzPSSM8S4hDYRA=
|
||||
github.com/uudashr/gocognit v1.2.0/go.mod h1:k/DdKPI6XBZO1q7HgoV2juESI2/Ofj9AcHPZhBBdrTU=
|
||||
github.com/uudashr/iface v1.4.1 h1:J16Xl1wyNX9ofhpHmQ9h9gk5rnv2A6lX/2+APLTo0zU=
|
||||
github.com/uudashr/iface v1.4.1/go.mod h1:pbeBPlbuU2qkNDn0mmfrxP2X+wjPMIQAy+r1MBXSXtg=
|
||||
github.com/uudashr/iface v1.3.1 h1:bA51vmVx1UIhiIsQFSNq6GZ6VPTk3WNMZgRiCe9R29U=
|
||||
github.com/uudashr/iface v1.3.1/go.mod h1:4QvspiRd3JLPAEXBQ9AiZpLbJlrWWgRChOKDJEuQTdg=
|
||||
github.com/vmihailenco/msgpack v4.0.4+incompatible h1:dSLoQfGFAo3F6OoNhwUmLwVgaUXK79GlxNBwueZn0xI=
|
||||
github.com/vmihailenco/msgpack v4.0.4+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk=
|
||||
github.com/woodsbury/decimal128 v1.3.0 h1:8pffMNWIlC0O5vbyHWFZAt5yWvWcrHA+3ovIIjVWss0=
|
||||
github.com/woodsbury/decimal128 v1.3.0/go.mod h1:C5UTmyTjW3JftjUFzOVhC20BEQa2a4ZKOB5I6Zjb+ds=
|
||||
github.com/xen0n/gosmopolitan v1.3.0 h1:zAZI1zefvo7gcpbCOrPSHJZJYA9ZgLfJqtKzZ5pHqQM=
|
||||
github.com/xen0n/gosmopolitan v1.3.0/go.mod h1:rckfr5T6o4lBtM1ga7mLGKZmLxswUoH1zxHgNXOsEt4=
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
|
||||
@@ -844,20 +706,14 @@ github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9dec
|
||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
github.com/yuin/goldmark v1.7.13 h1:GPddIs617DnBLFFVJFgpo1aBfe/4xcvMc3SB5t/D0pA=
|
||||
github.com/yuin/goldmark v1.7.13/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg=
|
||||
github.com/yuin/goldmark-emoji v1.0.6 h1:QWfF2FYaXwL74tfGOW5izeiZepUDroDJfWubQI9HTHs=
|
||||
github.com/yuin/goldmark-emoji v1.0.6/go.mod h1:ukxJDKFpdFb5x0a5HqbdlcKtebh086iJpI31LTKmWuA=
|
||||
gitlab.com/bosi/decorder v0.4.2 h1:qbQaV3zgwnBZ4zPMhGLW4KZe7A7NwxEhJx39R3shffo=
|
||||
gitlab.com/bosi/decorder v0.4.2/go.mod h1:muuhHoaJkA9QLcYHq4Mj8FJUwDZ+EirSHRiaTcTf6T8=
|
||||
go-simpler.org/assert v0.9.0 h1:PfpmcSvL7yAnWyChSjOz6Sp6m9j5lyK8Ok9pEL31YkQ=
|
||||
go-simpler.org/assert v0.9.0/go.mod h1:74Eqh5eI6vCK6Y5l3PI8ZYFXG4Sa+tkr70OIPJAUr28=
|
||||
go-simpler.org/musttag v0.13.1 h1:lw2sJyu7S1X8lc8zWUAdH42y+afdcCnHhWpnkWvd6vU=
|
||||
go-simpler.org/musttag v0.13.1/go.mod h1:8r450ehpMLQgvpb6sg+hV5Ur47eH6olp/3yEanfG97k=
|
||||
go-simpler.org/sloglint v0.11.1 h1:xRbPepLT/MHPTCA6TS/wNfZrDzkGvCCqUv4Bdwc3H7s=
|
||||
go-simpler.org/sloglint v0.11.1/go.mod h1:2PowwiCOK8mjiF+0KGifVOT8ZsCNiFzvfyJeJOIt8MQ=
|
||||
go.augendre.info/arangolint v0.2.0 h1:2NP/XudpPmfBhQKX4rMk+zDYIj//qbt4hfZmSSTcpj8=
|
||||
go.augendre.info/arangolint v0.2.0/go.mod h1:Vx4KSJwu48tkE+8uxuf0cbBnAPgnt8O1KWiT7bljq7w=
|
||||
go-simpler.org/sloglint v0.11.0 h1:JlR1X4jkbeaffiyjLtymeqmGDKBDO1ikC6rjiuFAOco=
|
||||
go-simpler.org/sloglint v0.11.0/go.mod h1:CFDO8R1i77dlciGfPEPvYke2ZMx4eyGiEIWkyeW2Pvw=
|
||||
go.augendre.info/fatcontext v0.8.0 h1:2dfk6CQbDGeu1YocF59Za5Pia7ULeAM6friJ3LP7lmk=
|
||||
go.augendre.info/fatcontext v0.8.0/go.mod h1:oVJfMgwngMsHO+KB2MdgzcO+RvtNdiCEOlWvSFtax/s=
|
||||
go.etcd.io/bbolt v1.3.4/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
|
||||
@@ -868,14 +724,16 @@ go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
|
||||
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||
go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs=
|
||||
go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8=
|
||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||
go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ=
|
||||
go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
|
||||
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
||||
go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI=
|
||||
go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
|
||||
go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4=
|
||||
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
|
||||
go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60=
|
||||
go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
@@ -885,8 +743,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.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI=
|
||||
golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8=
|
||||
golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4=
|
||||
golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc=
|
||||
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=
|
||||
@@ -897,16 +755,16 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0
|
||||
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
|
||||
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
|
||||
golang.org/x/exp v0.0.0-20250819193227-8b4c13bb791b h1:DXr+pvt3nC887026GRP39Ej11UATqWDmWuS99x26cD0=
|
||||
golang.org/x/exp v0.0.0-20250819193227-8b4c13bb791b/go.mod h1:4QTo5u+SEIbbKW1RacMZq1YEfOBqeXa19JeshGi+zc4=
|
||||
golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 h1:e66Fs6Z+fZTbFBAxKfP3PALWBtpfqks2bwGcexMxgtk=
|
||||
golang.org/x/exp v0.0.0-20240909161429-701f63a606c0/go.mod h1:2TbTHSBQa924w8M6Xs1QcRcFwyucIwBGpK1p2f1YFFY=
|
||||
golang.org/x/exp/typeparams v0.0.0-20220428152302-39d4317da171/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk=
|
||||
golang.org/x/exp/typeparams v0.0.0-20230203172020-98cc5a0785f9/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk=
|
||||
golang.org/x/exp/typeparams v0.0.0-20250620022241-b7579e27df2b h1:KdrhdYPDUvJTvrDK9gdjfFd6JTk8vA1WJoldYSi0kHo=
|
||||
golang.org/x/exp/typeparams v0.0.0-20250620022241-b7579e27df2b/go.mod h1:LKZHyeOpPuZcMgxeHjJp4p5yvxrCX1xDvH10zYHhjjQ=
|
||||
golang.org/x/exp/typeparams v0.0.0-20250210185358-939b2ce775ac h1:TSSpLIG4v+p0rPv1pNOQtl1I8knsO4S9trOxNMOLVP4=
|
||||
golang.org/x/exp/typeparams v0.0.0-20250210185358-939b2ce775ac/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk=
|
||||
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.31.0 h1:mLChjE2MV6g1S7oqbXC0/UcKijjm5fnJLUYKIYrLESA=
|
||||
golang.org/x/image v0.31.0/go.mod h1:R9ec5Lcp96v9FTF+ajwaH3uGxPH4fKfHHAVbUILxghA=
|
||||
golang.org/x/image v0.30.0 h1:jD5RhkmVAnjqaCUXfbGBrn3lpxbknfN9w2UhHHU+5B4=
|
||||
golang.org/x/image v0.30.0/go.mod h1:SAEUTxCCMWSrJcCy/4HwavEsfZZJlYxeHLc6tTiAe/c=
|
||||
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=
|
||||
@@ -929,11 +787,13 @@ golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.9.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.28.0 h1:gQBtGhjxykdjY9YhZpSlZIsbnaE2+PgjfLWUQTnoZ1U=
|
||||
golang.org/x/mod v0.28.0/go.mod h1:yfB/L0NOf/kmEbXjzCPOx1iK1fRutOydrCMsqRhEBxI=
|
||||
golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ=
|
||||
golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc=
|
||||
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=
|
||||
@@ -971,21 +831,23 @@ golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qx
|
||||
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.0.0-20221002022538-bcab6841153b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
|
||||
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
|
||||
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.44.0 h1:evd8IRDyfNBMBTTY5XRF1vaZlD+EmWx6x8PkhR04H/I=
|
||||
golang.org/x/net v0.44.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY=
|
||||
golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
|
||||
golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
|
||||
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.31.0 h1:8Fq0yVZLh4j4YA47vHKFTa9Ew5XIrCP8LC6UeNZnLxo=
|
||||
golang.org/x/oauth2 v0.31.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
|
||||
golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
|
||||
golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=
|
||||
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=
|
||||
@@ -1001,8 +863,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.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
|
||||
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
|
||||
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
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=
|
||||
@@ -1046,21 +908,25 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211105183446-c75c47738b0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
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.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
|
||||
golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
|
||||
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
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.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
|
||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
|
||||
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
|
||||
@@ -1071,16 +937,18 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
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.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk=
|
||||
golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4=
|
||||
golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
|
||||
golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
|
||||
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.13.0 h1:eUlYslOIt32DgYD6utsuUeHs4d7AsEYLuIAdg7FlYgI=
|
||||
golang.org/x/time v0.13.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
|
||||
golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=
|
||||
golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
|
||||
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=
|
||||
@@ -1132,7 +1000,9 @@ golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s=
|
||||
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.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg=
|
||||
@@ -1222,8 +1092,8 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj
|
||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc=
|
||||
google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
|
||||
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
|
||||
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
@@ -1259,8 +1129,6 @@ mvdan.cc/gofumpt v0.8.0/go.mod h1:vEYnSzyGPmjvFkqJWtXkh79UwPWP9/HMxQdGEXZHjpg=
|
||||
mvdan.cc/unparam v0.0.0-20250301125049-0df0534333a4 h1:WjUu4yQoT5BHT1w8Zu56SP8367OuBV5jvo+4Ulppyf8=
|
||||
mvdan.cc/unparam v0.0.0-20250301125049-0df0534333a4/go.mod h1:rthT7OuvRbaGcd5ginj6dA2oLE7YNlta9qhBNNdCaLE=
|
||||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||
rsc.io/qr v0.2.0 h1:6vBLea5/NRMVTz8V66gipeLycZMl/+UlFmk8DvqQ6WY=
|
||||
rsc.io/qr v0.2.0/go.mod h1:IF+uZjkb9fqyeF/4tlBoynqmQxUoPfWEKh921coOuXs=
|
||||
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
||||
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
||||
sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo=
|
||||
|
||||
@@ -71,11 +71,7 @@ func sseHandler(w http.ResponseWriter, r *http.Request, d *requestContext) (int,
|
||||
return http.StatusInternalServerError, fmt.Errorf("error sending broadcast: %v, user: %s", err, username)
|
||||
}
|
||||
|
||||
case msg, ok := <-sendChan:
|
||||
if !ok {
|
||||
logger.Debugf("SSE channel closed for user: %s, SessionId: %s", username, sessionId)
|
||||
return http.StatusOK, nil
|
||||
}
|
||||
case msg := <-sendChan:
|
||||
if err := msgr.sendEvent(msg.EventType, msg.Message); err != nil {
|
||||
return http.StatusInternalServerError, fmt.Errorf("error sending targeted message: %v, user: %s", err, username)
|
||||
}
|
||||
|
||||
+65
-43
@@ -4,7 +4,6 @@ import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"embed"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"net/http"
|
||||
@@ -15,7 +14,6 @@ import (
|
||||
"github.com/gtsteffaniak/filebrowser/backend/common/settings"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/common/version"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/database/storage/bolt"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/events"
|
||||
"github.com/gtsteffaniak/go-logger/logger"
|
||||
// http-swagger middleware
|
||||
)
|
||||
@@ -25,6 +23,9 @@ import (
|
||||
//go:embed embed/*
|
||||
var assets embed.FS
|
||||
|
||||
// Boolean flag to determine whether to use the embedded FS or not
|
||||
var embeddedFS = os.Getenv("FILEBROWSER_NO_EMBEDED") != "true"
|
||||
|
||||
// Custom dirFS to handle both embedded and non-embedded file systems
|
||||
type dirFS struct {
|
||||
http.Dir
|
||||
@@ -44,18 +45,7 @@ var (
|
||||
func StartHttp(ctx context.Context, storage *bolt.BoltStore, shutdownComplete chan struct{}) {
|
||||
store = storage
|
||||
config = &settings.Config
|
||||
|
||||
// Dev mode enables development features like template hot-reloading
|
||||
devMode := os.Getenv("FILEBROWSER_DEVMODE") == "true"
|
||||
_, err := os.Stat("http/dist")
|
||||
// In dev mode, always use filesystem assets. Otherwise, check if http/dist exists
|
||||
var embeddedFS bool
|
||||
if devMode {
|
||||
embeddedFS = false
|
||||
} else {
|
||||
embeddedFS = os.IsNotExist(err)
|
||||
}
|
||||
|
||||
var err error
|
||||
// --- START: ADD THIS DECRYPTION LOGIC ---
|
||||
if embeddedFS {
|
||||
// Embedded mode: Serve files from the embedded assets
|
||||
@@ -67,20 +57,8 @@ func StartHttp(ctx context.Context, storage *bolt.BoltStore, shutdownComplete ch
|
||||
assetFs = dirFS{Dir: http.Dir("http/dist")}
|
||||
}
|
||||
|
||||
// In development mode, we want to reload the templates on each request.
|
||||
// In production (embedded), we parse them once.
|
||||
templates := template.New("").Funcs(template.FuncMap{
|
||||
"marshal": func(v interface{}) (string, error) {
|
||||
a, err := json.Marshal(v)
|
||||
return string(a), err
|
||||
},
|
||||
})
|
||||
if !devMode {
|
||||
templates = template.Must(templates.ParseFS(assetFs, "public/index.html"))
|
||||
}
|
||||
templateRenderer = &TemplateRenderer{
|
||||
templates: templates,
|
||||
devMode: devMode,
|
||||
templates: template.Must(template.ParseFS(assetFs, "public/index.html")),
|
||||
}
|
||||
|
||||
router := http.NewServeMux()
|
||||
@@ -117,7 +95,6 @@ func StartHttp(ctx context.Context, storage *bolt.BoltStore, shutdownComplete ch
|
||||
api.HandleFunc("PATCH /resources", withUser(resourcePatchHandler))
|
||||
api.HandleFunc("GET /raw", withUser(rawHandler))
|
||||
api.HandleFunc("GET /preview", withUser(previewHandler))
|
||||
api.HandleFunc("GET /media/subtitles", withUser(subtitlesHandler))
|
||||
if version.Version == "testing" || version.Version == "untracked" {
|
||||
api.HandleFunc("GET /inspectIndex", inspectIndex)
|
||||
api.HandleFunc("GET /mockData", mockData)
|
||||
@@ -129,28 +106,22 @@ func StartHttp(ctx context.Context, storage *bolt.BoltStore, shutdownComplete ch
|
||||
api.HandleFunc("GET /access/groups", withAdmin(groupGetHandler))
|
||||
api.HandleFunc("POST /access/group", withAdmin(groupPostHandler))
|
||||
api.HandleFunc("DELETE /access/group", withAdmin(groupDeleteHandler))
|
||||
|
||||
// Share management routes (require permission)
|
||||
api.HandleFunc("GET /shares", withPermShare(shareListHandler))
|
||||
api.HandleFunc("GET /share/direct", withPermShare(shareDirectDownloadHandler))
|
||||
api.HandleFunc("GET /share", withPermShare(shareGetHandler))
|
||||
api.HandleFunc("POST /share", withPermShare(sharePostHandler))
|
||||
api.HandleFunc("DELETE /share", withPermShare(shareDeleteHandler))
|
||||
|
||||
// Create API sub-router for public API endpoints
|
||||
publicAPI := http.NewServeMux()
|
||||
// NEW PUBLIC ROUTES - All publicly accessible endpoints
|
||||
// Share management routes (require permission but are publicly accessible)
|
||||
publicRoutes.HandleFunc("GET /shares", withPermShare(shareListHandler))
|
||||
publicRoutes.HandleFunc("GET /share/direct", withPermShare(shareDirectDownloadHandler))
|
||||
publicRoutes.HandleFunc("GET /share", withPermShare(shareGetHandler))
|
||||
publicRoutes.HandleFunc("POST /share", withPermShare(sharePostHandler))
|
||||
publicRoutes.HandleFunc("DELETE /share", withPermShare(shareDeleteHandler))
|
||||
// Public API routes (hash-based authentication)
|
||||
publicAPI.HandleFunc("GET /raw", withHashFile(publicRawHandler))
|
||||
publicAPI.HandleFunc("GET /preview", withHashFile(publicPreviewHandler))
|
||||
publicAPI.HandleFunc("GET /resources", withHashFile(publicShareHandler))
|
||||
publicAPI.HandleFunc("GET /users", withUser(userGetHandler))
|
||||
publicAPI.HandleFunc("POST /onlyoffice/callback", withHashFile(onlyofficeCallbackHandler))
|
||||
publicAPI.HandleFunc("GET /onlyoffice/config", withHashFile(onlyofficeClientConfigGetHandler))
|
||||
|
||||
// Settings routes
|
||||
api.HandleFunc("GET /settings", withAdmin(settingsGetHandler))
|
||||
api.HandleFunc("GET /settings/config", withAdmin(settingsConfigHandler))
|
||||
|
||||
// Events routes
|
||||
api.HandleFunc("GET /events", withUser(func(w http.ResponseWriter, r *http.Request, d *requestContext) (int, error) {
|
||||
@@ -161,9 +132,63 @@ func StartHttp(ctx context.Context, storage *bolt.BoltStore, shutdownComplete ch
|
||||
api.HandleFunc("GET /jobs/{action}/{target}", withUser(getJobsHandler))
|
||||
|
||||
api.HandleFunc("GET /onlyoffice/config", withUser(onlyofficeClientConfigGetHandler))
|
||||
publicAPI.HandleFunc("GET /onlyoffice/config", withHashFile(onlyofficeClientConfigGetHandler))
|
||||
api.HandleFunc("POST /onlyoffice/callback", withUser(onlyofficeCallbackHandler))
|
||||
publicAPI.HandleFunc("POST /onlyoffice/callback", withHashFile(onlyofficeCallbackHandler))
|
||||
api.HandleFunc("GET /onlyoffice/getToken", withUser(onlyofficeGetTokenHandler))
|
||||
publicAPI.HandleFunc("GET /onlyoffice/getToken", withHashFile(onlyofficeGetTokenHandler))
|
||||
|
||||
api.HandleFunc("GET /search", withUser(searchHandler))
|
||||
|
||||
// Share routes (DEPRECATED - maintain for backwards compatibility)
|
||||
// These will redirect to the new /public/shares endpoints
|
||||
api.HandleFunc("GET /shares", func(w http.ResponseWriter, r *http.Request) {
|
||||
newURL := config.Server.BaseURL + "public/shares"
|
||||
if r.URL.RawQuery != "" {
|
||||
newURL += "?" + r.URL.RawQuery
|
||||
}
|
||||
http.Redirect(w, r, newURL, http.StatusMovedPermanently)
|
||||
})
|
||||
api.HandleFunc("GET /share", func(w http.ResponseWriter, r *http.Request) {
|
||||
newURL := config.Server.BaseURL + "public/share"
|
||||
if r.URL.RawQuery != "" {
|
||||
newURL += "?" + r.URL.RawQuery
|
||||
}
|
||||
http.Redirect(w, r, newURL, http.StatusMovedPermanently)
|
||||
})
|
||||
api.HandleFunc("POST /share", func(w http.ResponseWriter, r *http.Request) {
|
||||
http.Redirect(w, r, config.Server.BaseURL+"public/share", http.StatusPermanentRedirect)
|
||||
})
|
||||
api.HandleFunc("DELETE /share", func(w http.ResponseWriter, r *http.Request) {
|
||||
newURL := config.Server.BaseURL + "public/share"
|
||||
if r.URL.RawQuery != "" {
|
||||
newURL += "?" + r.URL.RawQuery
|
||||
}
|
||||
http.Redirect(w, r, newURL, http.StatusPermanentRedirect)
|
||||
})
|
||||
|
||||
api.HandleFunc("GET /public/raw", func(w http.ResponseWriter, r *http.Request) {
|
||||
newURL := config.Server.BaseURL + "public/api/raw"
|
||||
if r.URL.RawQuery != "" {
|
||||
newURL += "?" + r.URL.RawQuery
|
||||
}
|
||||
http.Redirect(w, r, newURL, http.StatusMovedPermanently)
|
||||
})
|
||||
api.HandleFunc("GET /public/share", func(w http.ResponseWriter, r *http.Request) {
|
||||
newURL := config.Server.BaseURL + "public/api/shared"
|
||||
if r.URL.RawQuery != "" {
|
||||
newURL += "?" + r.URL.RawQuery
|
||||
}
|
||||
http.Redirect(w, r, newURL, http.StatusMovedPermanently)
|
||||
})
|
||||
api.HandleFunc("GET /public/preview", func(w http.ResponseWriter, r *http.Request) {
|
||||
newURL := config.Server.BaseURL + "public/api/preview"
|
||||
if r.URL.RawQuery != "" {
|
||||
newURL += "?" + r.URL.RawQuery
|
||||
}
|
||||
http.Redirect(w, r, newURL, http.StatusMovedPermanently)
|
||||
})
|
||||
|
||||
// Mount the public API sub-router
|
||||
publicRoutes.Handle("/api/", http.StripPrefix("/api", publicAPI))
|
||||
|
||||
@@ -256,9 +281,6 @@ func StartHttp(ctx context.Context, storage *bolt.BoltStore, shutdownComplete ch
|
||||
<-ctx.Done()
|
||||
logger.Info("Shutting down HTTP server...")
|
||||
|
||||
// Close all SSE sessions
|
||||
events.Shutdown()
|
||||
|
||||
// Persist in-memory state before shutting down the HTTP server
|
||||
if store != nil {
|
||||
if store.Share != nil {
|
||||
|
||||
@@ -1,83 +0,0 @@
|
||||
package http
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/gtsteffaniak/filebrowser/backend/common/settings"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/ffmpeg"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/indexing"
|
||||
)
|
||||
|
||||
// subtitlesHandler handles subtitle extraction requests
|
||||
// @Summary Extract embedded subtitles
|
||||
// @Description Extracts embedded subtitle content from video files by stream index and returns raw WebVTT content
|
||||
// @Tags Subtitles
|
||||
// @Accept json
|
||||
// @Produce text/vtt
|
||||
// @Param path query string true "Index path to the video file"
|
||||
// @Param source query string true "Source name for the desired source"
|
||||
// @Param index query int false "Stream index for embedded subtitle extraction, defaults to 0"
|
||||
// @Success 200 {string} string "Raw WebVTT subtitle content"
|
||||
// @Failure 400 {object} map[string]string "Bad request"
|
||||
// @Failure 403 {object} map[string]string "Forbidden"
|
||||
// @Failure 404 {object} map[string]string "Resource not found"
|
||||
// @Failure 500 {object} map[string]string "Internal server error"
|
||||
// @Router /api/media/subtitles [get]
|
||||
func subtitlesHandler(w http.ResponseWriter, r *http.Request, d *requestContext) (int, error) {
|
||||
encodedPath := r.URL.Query().Get("path")
|
||||
source := r.URL.Query().Get("source")
|
||||
indexParam := r.URL.Query().Get("index")
|
||||
|
||||
if indexParam == "" {
|
||||
indexParam = "0" // default to first subtitle stream
|
||||
}
|
||||
|
||||
path, err := url.QueryUnescape(encodedPath)
|
||||
if err != nil {
|
||||
return http.StatusBadRequest, fmt.Errorf("invalid path encoding: %v", err)
|
||||
}
|
||||
source, err = url.QueryUnescape(source)
|
||||
if err != nil {
|
||||
return http.StatusBadRequest, fmt.Errorf("invalid source encoding: %v", err)
|
||||
}
|
||||
|
||||
userscope, err := settings.GetScopeFromSourceName(d.user.Scopes, source)
|
||||
if err != nil {
|
||||
return http.StatusForbidden, err
|
||||
}
|
||||
|
||||
idx := indexing.GetIndex(source)
|
||||
if idx == nil {
|
||||
return http.StatusNotFound, fmt.Errorf("source %s not found", source)
|
||||
}
|
||||
realPath, _, err := idx.GetRealPath(userscope, path)
|
||||
if err != nil {
|
||||
return http.StatusNotFound, fmt.Errorf("file not found: %v", err)
|
||||
}
|
||||
metadata, exists := idx.GetMetadataInfo(userscope, true)
|
||||
if !exists {
|
||||
return http.StatusNotFound, fmt.Errorf("file not found: %v", err)
|
||||
}
|
||||
|
||||
index, err := strconv.Atoi(indexParam)
|
||||
if err != nil {
|
||||
return http.StatusBadRequest, fmt.Errorf("invalid index parameter: %v", err)
|
||||
}
|
||||
parentDir := filepath.Dir(realPath)
|
||||
subtitle, err := ffmpeg.ExtractSingleSubtitle(realPath, parentDir, index, metadata.ModTime)
|
||||
if err != nil {
|
||||
return http.StatusInternalServerError, fmt.Errorf("failed to extract subtitle: %v", err)
|
||||
}
|
||||
w.Header().Set("Content-Type", "text/vtt; charset=utf-8")
|
||||
w.Header().Set("Content-Disposition", "inline")
|
||||
w.Header().Set("Cache-Control", "private")
|
||||
http.ServeContent(w, r, fmt.Sprintf("%s-%d.vtt", subtitle.Name, index),
|
||||
time.Now(), bytes.NewReader([]byte(subtitle.Content)))
|
||||
return http.StatusOK, nil
|
||||
}
|
||||
@@ -15,7 +15,6 @@ import (
|
||||
"github.com/gtsteffaniak/filebrowser/backend/adapters/fs/files"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/auth"
|
||||
"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/database/users"
|
||||
@@ -91,16 +90,11 @@ func withHashFileHelper(fn handleFunc) handleFunc {
|
||||
Expand: true,
|
||||
Content: getContent,
|
||||
})
|
||||
file.Token = link.Token
|
||||
if err != nil {
|
||||
logger.Errorf("error fetching file info for share. hash=%v path=%v error=%v", hash, path, err)
|
||||
return errToStatus(err), fmt.Errorf("error fetching share from server")
|
||||
}
|
||||
file.Token = link.Token
|
||||
file.Source = ""
|
||||
file.Hash = link.Hash
|
||||
if !link.EnableOnlyOffice || !link.DisableFileViewer {
|
||||
file.OnlyOfficeId = ""
|
||||
}
|
||||
if getContent && file.Content != "" {
|
||||
link.Mu.Lock()
|
||||
link.Downloads++
|
||||
@@ -108,7 +102,7 @@ func withHashFileHelper(fn handleFunc) handleFunc {
|
||||
}
|
||||
file.Path = "/" + strings.TrimPrefix(strings.TrimPrefix(file.Path, link.Path), "/")
|
||||
// Set the file info in the `data` object
|
||||
data.fileInfo = *file
|
||||
data.fileInfo = file
|
||||
// Call the next handler with the data
|
||||
return fn(w, r, data)
|
||||
})
|
||||
@@ -186,8 +180,7 @@ func withOrWithoutUserHelper(fn handleFunc) handleFunc {
|
||||
}
|
||||
// Only fall back to anonymous if authentication actually failed
|
||||
if status == http.StatusUnauthorized || status == http.StatusForbidden {
|
||||
data.user = &users.User{Username: "anonymous"}
|
||||
settings.ApplyUserDefaults(data.user)
|
||||
data.user = &users.AnonymousUser
|
||||
// If user authentication failed, call the handler without user context
|
||||
// Clear any user data that might have been partially set
|
||||
data.token = ""
|
||||
|
||||
@@ -43,8 +43,8 @@ func mockFileInfoFaster(t *testing.T) {
|
||||
t.Cleanup(func() { FileInfoFasterFunc = originalFileInfoFaster })
|
||||
|
||||
// Mock the function to skip execution
|
||||
FileInfoFasterFunc = func(opts iteminfo.FileOptions) (*iteminfo.ExtendedFileInfo, error) {
|
||||
return &iteminfo.ExtendedFileInfo{
|
||||
FileInfoFasterFunc = func(opts iteminfo.FileOptions) (iteminfo.ExtendedFileInfo, error) {
|
||||
return iteminfo.ExtendedFileInfo{
|
||||
FileInfo: iteminfo.FileInfo{
|
||||
Path: opts.Path,
|
||||
ItemInfo: iteminfo.ItemInfo{
|
||||
|
||||
+103
-228
@@ -7,7 +7,6 @@ import (
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
@@ -39,107 +38,68 @@ func onlyofficeClientConfigGetHandler(w http.ResponseWriter, r *http.Request, d
|
||||
if settings.Config.Integrations.OnlyOffice.Url == "" {
|
||||
return http.StatusInternalServerError, errors.New("only-office integration must be configured in settings")
|
||||
}
|
||||
|
||||
// Extract clean parameters from request
|
||||
source := r.URL.Query().Get("source")
|
||||
path := r.URL.Query().Get("path")
|
||||
|
||||
// Validate required parameters
|
||||
if (path == "" || source == "") && d.fileInfo.Hash == "" {
|
||||
logger.Errorf("OnlyOffice callback missing required parameters: source=%s, path=%s", source, path)
|
||||
return http.StatusBadRequest, errors.New("missing required parameters: path + source/hash are required")
|
||||
}
|
||||
themeMode := utils.Ternary(d.user.DarkMode, "dark", "light")
|
||||
var sourceInfo settings.Source
|
||||
var ok bool
|
||||
if d.fileInfo.Hash != "" {
|
||||
sourceInfo, ok = settings.Config.Server.SourceMap[source]
|
||||
if !ok {
|
||||
logger.Error("OnlyOffice: source not found")
|
||||
return http.StatusInternalServerError, fmt.Errorf("source not found")
|
||||
}
|
||||
} else {
|
||||
sourceInfo, ok = settings.Config.Server.NameToSource[source]
|
||||
if !ok {
|
||||
logger.Error("OnlyOffice: source not found")
|
||||
return http.StatusInternalServerError, fmt.Errorf("source not found")
|
||||
}
|
||||
}
|
||||
if d.fileInfo.Hash == "" {
|
||||
// Build file info based on whether this is a share or regular request
|
||||
// Regular user request - need to resolve scope
|
||||
userScope, scopeErr := settings.GetScopeFromSourceName(d.user.Scopes, source)
|
||||
if scopeErr != nil {
|
||||
logger.Errorf("OnlyOffice: source %s not available for user %s: %v", source, d.user.Username, scopeErr)
|
||||
return http.StatusForbidden, fmt.Errorf("source %s is not available", source)
|
||||
}
|
||||
indexPath := utils.JoinPathAsUnix(userScope, path)
|
||||
logger.Debugf("OnlyOffice user request: resolved path=%s", indexPath)
|
||||
fileInfo, err := files.FileInfoFaster(iteminfo.FileOptions{
|
||||
Path: indexPath,
|
||||
Modify: d.user.Permissions.Modify,
|
||||
Source: source,
|
||||
Expand: false,
|
||||
})
|
||||
if err != nil {
|
||||
logger.Errorf("OnlyOffice: failed to get file info for source=%s, path=%s: %v", source, indexPath, err)
|
||||
return errToStatus(err), err
|
||||
}
|
||||
d.fileInfo = *fileInfo
|
||||
} else {
|
||||
source = sourceInfo.Name
|
||||
// path is index path, so we build from share path
|
||||
path = utils.JoinPathAsUnix(d.share.Path, path)
|
||||
if d.share.EnforceDarkLightMode == "dark" {
|
||||
themeMode = "dark"
|
||||
}
|
||||
if d.share.EnforceDarkLightMode == "light" {
|
||||
themeMode = "light"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Determine file type and editing permissions
|
||||
fileType := strings.TrimPrefix(filepath.Ext(d.fileInfo.Name), ".")
|
||||
canEdit := iteminfo.CanEditOnlyOffice(d.user.Permissions.Modify, fileType)
|
||||
canEditMode := utils.Ternary(canEdit, "edit", "view")
|
||||
if d.fileInfo.Hash != "" {
|
||||
if d.share.EnableOnlyOfficeEditing {
|
||||
canEditMode = "edit"
|
||||
}
|
||||
}
|
||||
// For shares, we need to keep track of the original relative path for the callback URL
|
||||
var callbackPath string
|
||||
if d.fileInfo.Hash != "" {
|
||||
// For shares, use the original path parameter (relative to share)
|
||||
callbackPath = r.URL.Query().Get("path")
|
||||
} else {
|
||||
// For regular requests, use the processed path
|
||||
callbackPath = path
|
||||
}
|
||||
|
||||
// Generate document ID for OnlyOffice
|
||||
documentId, err := getOnlyOfficeId(d.fileInfo.RealPath)
|
||||
encodedUrl := r.URL.Query().Get("url")
|
||||
// Decode the URL-encoded path
|
||||
givenUrl, err := url.QueryUnescape(encodedUrl)
|
||||
if err != nil {
|
||||
logger.Errorf("OnlyOffice: failed to generate document ID for source=%s, path=%s: %v", source, path, err)
|
||||
return http.StatusNotFound, fmt.Errorf("failed to generate document ID: %v", err)
|
||||
return http.StatusBadRequest, fmt.Errorf("invalid path encoding: %v", err)
|
||||
}
|
||||
|
||||
// Build download URL that OnlyOffice server will use
|
||||
downloadURL := buildOnlyOfficeDownloadURL(source, callbackPath, d.fileInfo.Hash, d.token)
|
||||
|
||||
// Build callback URL for OnlyOffice to notify us of changes
|
||||
callbackURL := buildOnlyOfficeCallbackURL(source, callbackPath, d.fileInfo.Hash, d.token)
|
||||
|
||||
// Build OnlyOffice client configuration
|
||||
// get path from url
|
||||
pathParts := strings.Split(givenUrl, "/api/raw?files=")
|
||||
origPathParts := strings.Split(encodedUrl, "/api/raw?files=")
|
||||
encodedPath := origPathParts[len(origPathParts)-1]
|
||||
sourceFile := pathParts[len(pathParts)-1]
|
||||
sourceSplit := strings.Split(sourceFile, "::")
|
||||
if len(sourceSplit) != 2 {
|
||||
return http.StatusBadRequest, fmt.Errorf("invalid url path %v", givenUrl)
|
||||
}
|
||||
source := sourceSplit[0]
|
||||
path := sourceSplit[1]
|
||||
urlFirst := pathParts[0]
|
||||
if settings.Config.Server.InternalUrl != "" {
|
||||
urlFirst = settings.Config.Server.InternalUrl + settings.Config.Server.BaseURL
|
||||
whatToReplace := strings.Split(givenUrl, "/api/raw")[0] + "/"
|
||||
givenUrl = strings.Replace(givenUrl, whatToReplace, urlFirst, 1)
|
||||
}
|
||||
userscope, err := settings.GetScopeFromSourceName(d.user.Scopes, source)
|
||||
if err != nil {
|
||||
return http.StatusForbidden, err
|
||||
}
|
||||
fileInfo, err := files.FileInfoFaster(iteminfo.FileOptions{
|
||||
Path: utils.JoinPathAsUnix(userscope, path),
|
||||
Modify: d.user.Permissions.Modify,
|
||||
Source: source,
|
||||
Expand: false,
|
||||
})
|
||||
if err != nil {
|
||||
logger.Debugf("onlyofficeClientConfigGetHandler: failed to get file info for file source %v, path %v: %v", source, path, err)
|
||||
return errToStatus(err), err
|
||||
}
|
||||
id, err := getOnlyOfficeId(source, fileInfo.Path)
|
||||
if err != nil {
|
||||
logger.Debugf("getOnlyOfficeId failed for file source %v, path %v: %v", source, fileInfo.Path, err)
|
||||
return http.StatusNotFound, err
|
||||
}
|
||||
split := strings.Split(fileInfo.Name, ".")
|
||||
fileType := split[len(split)-1]
|
||||
theme := "light"
|
||||
if d.user.DarkMode {
|
||||
theme = "dark"
|
||||
}
|
||||
canEdit := iteminfo.CanEditOnlyOffice(d.user.Permissions.Modify, fileType)
|
||||
mode := "view"
|
||||
if canEdit {
|
||||
mode = "edit"
|
||||
}
|
||||
callbackURL := fmt.Sprintf("%v/api/onlyoffice/callback?files=%v&auth=%v", strings.TrimSuffix(urlFirst, "/"), encodedPath, d.token)
|
||||
clientConfig := map[string]interface{}{
|
||||
"document": map[string]interface{}{
|
||||
"fileType": fileType,
|
||||
"key": documentId,
|
||||
"title": d.fileInfo.Name,
|
||||
"url": downloadURL,
|
||||
"key": id,
|
||||
"title": fileInfo.Name,
|
||||
"url": givenUrl + "&auth=" + d.token,
|
||||
"permissions": map[string]interface{}{
|
||||
"edit": canEditMode,
|
||||
"edit": canEdit,
|
||||
"download": true,
|
||||
"print": true,
|
||||
},
|
||||
@@ -153,201 +113,90 @@ func onlyofficeClientConfigGetHandler(w http.ResponseWriter, r *http.Request, d
|
||||
"customization": map[string]interface{}{
|
||||
"autosave": true,
|
||||
"forcesave": true,
|
||||
"uiTheme": themeMode,
|
||||
"uiTheme": theme,
|
||||
},
|
||||
"lang": d.user.Locale,
|
||||
"mode": canEditMode,
|
||||
"mode": mode,
|
||||
},
|
||||
}
|
||||
|
||||
// Sign configuration with JWT if secret is configured
|
||||
if settings.Config.Integrations.OnlyOffice.Secret != "" {
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims(clientConfig))
|
||||
signature, err := token.SignedString([]byte(settings.Config.Integrations.OnlyOffice.Secret))
|
||||
if err != nil {
|
||||
logger.Errorf("OnlyOffice: failed to sign JWT: %v", err)
|
||||
return http.StatusInternalServerError, fmt.Errorf("failed to sign configuration")
|
||||
return http.StatusInternalServerError, fmt.Errorf("failed to sign JWT")
|
||||
}
|
||||
clientConfig["token"] = signature
|
||||
}
|
||||
|
||||
return renderJSON(w, r, clientConfig)
|
||||
}
|
||||
|
||||
// buildOnlyOfficeDownloadURL constructs the download URL that OnlyOffice server will use to fetch the file
|
||||
func buildOnlyOfficeDownloadURL(source, path, hash, token string) string {
|
||||
// Determine base URL (internal URL takes priority for OnlyOffice server communication)
|
||||
baseURL := settings.Config.Server.BaseURL
|
||||
if settings.Config.Server.InternalUrl != "" {
|
||||
baseURL = settings.Config.Server.InternalUrl + settings.Config.Server.BaseURL
|
||||
}
|
||||
|
||||
var downloadURL string
|
||||
if hash != "" {
|
||||
// Share download URL - don't expose source name, just use the path relative to share
|
||||
filesParam := url.QueryEscape(path)
|
||||
downloadURL = fmt.Sprintf("%s/public/api/raw?files=%s&hash=%s&token=%s&auth=%s",
|
||||
strings.TrimSuffix(baseURL, "/"), filesParam, hash, token, token)
|
||||
} else {
|
||||
// Regular download URL - include source for non-share requests
|
||||
filesParam := url.QueryEscape(source + "::" + path)
|
||||
downloadURL = fmt.Sprintf("%s/api/raw?files=%s&auth=%s",
|
||||
strings.TrimSuffix(baseURL, "/"), filesParam, token)
|
||||
}
|
||||
|
||||
return downloadURL
|
||||
}
|
||||
|
||||
// buildOnlyOfficeCallbackURL constructs the callback URL that OnlyOffice server will use to notify us of changes
|
||||
func buildOnlyOfficeCallbackURL(source, path, hash, token string) string {
|
||||
baseURL := settings.Config.Server.BaseURL
|
||||
if settings.Config.Server.InternalUrl != "" {
|
||||
baseURL = settings.Config.Server.InternalUrl + settings.Config.Server.BaseURL
|
||||
}
|
||||
|
||||
var callbackURL string
|
||||
if hash != "" {
|
||||
// Share callback URL - use public API and don't expose source, use path relative to share
|
||||
params := url.Values{}
|
||||
params.Set("hash", hash)
|
||||
params.Set("path", path) // This should be the path relative to the share, not the full filesystem path
|
||||
params.Set("auth", token)
|
||||
|
||||
callbackURL = fmt.Sprintf("%s/public/api/onlyoffice/callback?%s",
|
||||
strings.TrimSuffix(baseURL, "/"), params.Encode())
|
||||
} else {
|
||||
// Regular callback URL - include source for non-share requests
|
||||
params := url.Values{}
|
||||
params.Set("source", source)
|
||||
params.Set("path", path)
|
||||
params.Set("auth", token)
|
||||
|
||||
callbackURL = fmt.Sprintf("%s/api/onlyoffice/callback?%s",
|
||||
strings.TrimSuffix(baseURL, "/"), params.Encode())
|
||||
}
|
||||
|
||||
return callbackURL
|
||||
}
|
||||
|
||||
func onlyofficeCallbackHandler(w http.ResponseWriter, r *http.Request, d *requestContext) (int, error) {
|
||||
// Parse OnlyOffice callback data
|
||||
body, err := io.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
logger.Errorf("OnlyOffice callback: failed to read request body: %v", err)
|
||||
return http.StatusInternalServerError, err
|
||||
}
|
||||
|
||||
var data OnlyOfficeCallback
|
||||
err = json.Unmarshal(body, &data)
|
||||
if err != nil {
|
||||
logger.Errorf("OnlyOffice callback: failed to parse JSON: %v", err)
|
||||
return http.StatusInternalServerError, err
|
||||
}
|
||||
|
||||
// Extract clean parameters from query string
|
||||
source := r.URL.Query().Get("source")
|
||||
path := r.URL.Query().Get("path")
|
||||
|
||||
// Validate required parameters
|
||||
if (path == "" || source == "") && d.fileInfo.Hash == "" {
|
||||
logger.Errorf("OnlyOffice callback missing required parameters: source=%s, path=%s", source, path)
|
||||
return http.StatusBadRequest, errors.New("missing required parameters: path + source/hash are required")
|
||||
encodedPath := r.URL.Query().Get("files")
|
||||
pathParts := strings.Split(encodedPath, "::")
|
||||
if len(pathParts) < 2 {
|
||||
return http.StatusBadRequest, fmt.Errorf("invalid path encoding: %v", err)
|
||||
}
|
||||
var sourceInfo settings.Source
|
||||
var ok bool
|
||||
if d.fileInfo.Hash != "" {
|
||||
sourceInfo, ok = settings.Config.Server.SourceMap[source]
|
||||
if !ok {
|
||||
logger.Error("OnlyOffice: source not found")
|
||||
return http.StatusInternalServerError, fmt.Errorf("source not found")
|
||||
}
|
||||
} else {
|
||||
sourceInfo, ok = settings.Config.Server.NameToSource[source]
|
||||
if !ok {
|
||||
logger.Error("OnlyOffice: source not found")
|
||||
return http.StatusInternalServerError, fmt.Errorf("source not found")
|
||||
}
|
||||
source := pathParts[0]
|
||||
// Decode the URL-encoded path
|
||||
path, err := url.QueryUnescape(pathParts[1])
|
||||
if err != nil {
|
||||
return http.StatusBadRequest, fmt.Errorf("invalid path encoding: %v", err)
|
||||
}
|
||||
|
||||
if d.fileInfo.Hash == "" {
|
||||
// Regular user request - need to resolve scope
|
||||
userScope, scopeErr := settings.GetScopeFromSourceName(d.user.Scopes, source)
|
||||
if scopeErr != nil {
|
||||
logger.Errorf("OnlyOffice callback: source %s not available for user %s: %v", source, d.user.Username, scopeErr)
|
||||
return http.StatusForbidden, fmt.Errorf("source %s is not available", source)
|
||||
}
|
||||
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 {
|
||||
// Refer to OnlyOffice documentation:
|
||||
// Refer to only-office documentation
|
||||
// - https://api.onlyoffice.com/editors/coedit
|
||||
// - https://api.onlyoffice.com/editors/callback
|
||||
//
|
||||
// When the document is fully closed by all editors,
|
||||
// the document key should no longer be re-used.
|
||||
logger.Debugf("OnlyOffice: document closed, cleaning up document ID for source=%s, path=%s", source, path)
|
||||
// then the document key should no longer be re-used.
|
||||
deleteOfficeId(source, path)
|
||||
}
|
||||
|
||||
// Handle document save operations
|
||||
if data.Status == onlyOfficeStatusDocumentClosedWithChanges ||
|
||||
data.Status == onlyOfficeStatusForceSaveWhileDocumentStillOpen {
|
||||
|
||||
// Verify user has modify permissions
|
||||
if !d.user.Permissions.Modify {
|
||||
logger.Warningf("OnlyOffice callback: user %s lacks modify permissions for source=%s, path=%s",
|
||||
d.user.Username, source, path)
|
||||
return http.StatusForbidden, nil
|
||||
}
|
||||
|
||||
// Download the updated document from OnlyOffice server
|
||||
doc, err := http.Get(data.URL)
|
||||
if err != nil {
|
||||
logger.Errorf("OnlyOffice callback: failed to download updated document: %v", err)
|
||||
return http.StatusInternalServerError, err
|
||||
}
|
||||
defer doc.Body.Close()
|
||||
|
||||
// Resolve file path for writing (same logic as in config handler)
|
||||
var resolvedPath string
|
||||
if d.fileInfo.Hash == "" {
|
||||
// Regular user request - need to resolve scope
|
||||
userScope, scopeErr := settings.GetScopeFromSourceName(d.user.Scopes, source)
|
||||
if scopeErr != nil {
|
||||
logger.Errorf("OnlyOffice callback: source %s not available for user %s: %v",
|
||||
source, d.user.Username, scopeErr)
|
||||
return http.StatusForbidden, fmt.Errorf("source %s is not available", source)
|
||||
}
|
||||
resolvedPath = utils.JoinPathAsUnix(userScope, path)
|
||||
}
|
||||
|
||||
// Write the updated document
|
||||
fileOpts := iteminfo.FileOptions{
|
||||
Path: resolvedPath,
|
||||
Path: path,
|
||||
Source: source,
|
||||
}
|
||||
writeErr := files.WriteFile(fileOpts, doc.Body)
|
||||
if writeErr != nil {
|
||||
logger.Errorf("OnlyOffice callback: failed to write updated document: %v", writeErr)
|
||||
return http.StatusInternalServerError, writeErr
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Return success response to OnlyOffice server
|
||||
resp := map[string]int{
|
||||
"error": 0,
|
||||
}
|
||||
return renderJSON(w, r, resp)
|
||||
}
|
||||
|
||||
func getOnlyOfficeId(realpath string) (string, error) {
|
||||
func getOnlyOfficeId(source, path string) (string, error) {
|
||||
idx := indexing.GetIndex(source)
|
||||
if idx == nil {
|
||||
return "", fmt.Errorf("source not found")
|
||||
}
|
||||
realpath, _, _ := idx.GetRealPath(path)
|
||||
// error is intentionally ignored in order treat errors
|
||||
// the same as a cache-miss
|
||||
cachedDocumentKey, ok := utils.OnlyOfficeCache.Get(realpath).(string)
|
||||
@@ -366,3 +215,29 @@ func deleteOfficeId(source, path string) {
|
||||
realpath, _, _ := idx.GetRealPath(path)
|
||||
utils.OnlyOfficeCache.Delete(realpath)
|
||||
}
|
||||
|
||||
func onlyofficeGetTokenHandler(w http.ResponseWriter, r *http.Request, d *requestContext) (int, error) {
|
||||
// get config from body
|
||||
body, err := io.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
return http.StatusInternalServerError, err
|
||||
}
|
||||
defer r.Body.Close()
|
||||
|
||||
var payload map[string]interface{}
|
||||
// marshall to struct
|
||||
err = json.Unmarshal(body, &payload)
|
||||
if err != nil {
|
||||
return http.StatusInternalServerError, err
|
||||
}
|
||||
|
||||
if settings.Config.Integrations.OnlyOffice.Secret != "" {
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims(payload))
|
||||
ss, err := token.SignedString([]byte(settings.Config.Integrations.OnlyOffice.Secret))
|
||||
if err != nil {
|
||||
return 500, errors.New("could not generate a new jwt")
|
||||
}
|
||||
return renderJSON(w, r, map[string]string{"token": ss})
|
||||
}
|
||||
return 400, fmt.Errorf("bad request")
|
||||
}
|
||||
@@ -70,7 +70,7 @@ func previewHandler(w http.ResponseWriter, r *http.Request, d *requestContext) (
|
||||
if err != nil {
|
||||
return errToStatus(err), err
|
||||
}
|
||||
d.fileInfo = *fileInfo
|
||||
d.fileInfo = fileInfo
|
||||
return previewHelperFunc(w, r, d)
|
||||
}
|
||||
|
||||
|
||||
+7
-13
@@ -7,7 +7,6 @@ import (
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/gtsteffaniak/filebrowser/backend/common/settings"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/common/utils"
|
||||
"github.com/gtsteffaniak/go-logger/logger"
|
||||
|
||||
@@ -38,19 +37,12 @@ func publicRawHandler(w http.ResponseWriter, r *http.Request, d *requestContext)
|
||||
d.share.Mu.Unlock()
|
||||
encodedFiles := r.URL.Query().Get("files")
|
||||
|
||||
// Decode the URL-encoded path - use PathUnescape to preserve + as literal character
|
||||
f, err := url.PathUnescape(encodedFiles)
|
||||
// Decode the URL-encoded path
|
||||
f, err := url.QueryUnescape(encodedFiles)
|
||||
if err != nil {
|
||||
return http.StatusBadRequest, fmt.Errorf("invalid path encoding: %v", err)
|
||||
}
|
||||
|
||||
// Get the actual source name from the share's source mapping
|
||||
sourceInfo, ok := settings.Config.Server.SourceMap[d.share.Source]
|
||||
if !ok {
|
||||
return http.StatusInternalServerError, fmt.Errorf("source not found for share")
|
||||
}
|
||||
actualSourceName := sourceInfo.Name
|
||||
|
||||
fileList := []string{}
|
||||
for _, file := range strings.Split(f, "||") {
|
||||
// Check if file already contains source prefix (source::path format)
|
||||
@@ -65,12 +57,12 @@ func publicRawHandler(w http.ResponseWriter, r *http.Request, d *requestContext)
|
||||
} else {
|
||||
// Fallback: treat as plain path
|
||||
filePath := utils.JoinPathAsUnix(d.share.Path, file)
|
||||
fileList = append(fileList, actualSourceName+"::"+filePath)
|
||||
fileList = append(fileList, d.fileInfo.Source+"::"+filePath)
|
||||
}
|
||||
} else {
|
||||
// Plain path without source prefix - use the actual source name from share
|
||||
// Plain path without source prefix
|
||||
filePath := utils.JoinPathAsUnix(d.share.Path, file)
|
||||
fileList = append(fileList, actualSourceName+"::"+filePath)
|
||||
fileList = append(fileList, d.fileInfo.Source+"::"+filePath)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,6 +76,8 @@ func publicRawHandler(w http.ResponseWriter, r *http.Request, d *requestContext)
|
||||
}
|
||||
|
||||
func publicShareHandler(w http.ResponseWriter, r *http.Request, d *requestContext) (int, error) {
|
||||
// disable onlyoffice for public share
|
||||
d.fileInfo.OnlyOfficeId = ""
|
||||
return renderJSON(w, r, d.fileInfo)
|
||||
}
|
||||
|
||||
|
||||
@@ -176,7 +176,7 @@ func resourceDeleteHandler(w http.ResponseWriter, r *http.Request, d *requestCon
|
||||
}
|
||||
|
||||
// delete thumbnails
|
||||
preview.DelThumbs(r.Context(), *fileInfo)
|
||||
preview.DelThumbs(r.Context(), fileInfo)
|
||||
|
||||
err = files.DeleteFiles(source, fileInfo.RealPath, filepath.Dir(fileInfo.RealPath))
|
||||
if err != nil {
|
||||
@@ -269,7 +269,7 @@ func resourcePostHandler(w http.ResponseWriter, r *http.Request, d *requestConte
|
||||
}
|
||||
// On the first chunk, check for conflicts or handle override
|
||||
if offset == 0 {
|
||||
var fileInfo *iteminfo.ExtendedFileInfo
|
||||
var fileInfo iteminfo.ExtendedFileInfo
|
||||
fileInfo, err = files.FileInfoFaster(fileOpts)
|
||||
if err == nil { // File exists
|
||||
if r.URL.Query().Get("override") != "true" {
|
||||
@@ -278,7 +278,7 @@ func resourcePostHandler(w http.ResponseWriter, r *http.Request, d *requestConte
|
||||
return http.StatusConflict, nil
|
||||
}
|
||||
// If overriding, delete existing thumbnails
|
||||
preview.DelThumbs(r.Context(), *fileInfo)
|
||||
preview.DelThumbs(r.Context(), fileInfo)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -341,7 +341,7 @@ func resourcePostHandler(w http.ResponseWriter, r *http.Request, d *requestConte
|
||||
return http.StatusConflict, nil
|
||||
}
|
||||
|
||||
preview.DelThumbs(r.Context(), *fileInfo)
|
||||
preview.DelThumbs(r.Context(), fileInfo)
|
||||
}
|
||||
err = files.WriteFile(fileOpts, r.Body)
|
||||
if err != nil {
|
||||
@@ -571,7 +571,7 @@ func patchAction(ctx context.Context, params patchActionParams) error {
|
||||
}
|
||||
|
||||
// delete thumbnails
|
||||
preview.DelThumbs(ctx, *fileInfo)
|
||||
preview.DelThumbs(ctx, fileInfo)
|
||||
return files.MoveResource(params.isSrcDir, params.isDstDir, params.srcIndex, params.dstIndex, params.src, params.dst, store.Share)
|
||||
default:
|
||||
return fmt.Errorf("unsupported action %s: %w", params.action, errors.ErrInvalidRequestParams)
|
||||
|
||||
@@ -64,9 +64,6 @@ func searchHandler(w http.ResponseWriter, r *http.Request, d *requestContext) (i
|
||||
if err != nil {
|
||||
return http.StatusBadRequest, fmt.Errorf("invalid path encoding: %v", err)
|
||||
}
|
||||
if len(query) < settings.Config.Server.MinSearchLength {
|
||||
return http.StatusBadRequest, fmt.Errorf("query is too short, minimum length is %d", settings.Config.Server.MinSearchLength)
|
||||
}
|
||||
searchScope := strings.TrimPrefix(unencodedScope, ".")
|
||||
// Retrieve the User-Agent and X-Auth headers from the request
|
||||
sessionId := r.Header.Get("SessionId")
|
||||
|
||||
@@ -2,8 +2,6 @@ package http
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gtsteffaniak/filebrowser/backend/common/settings"
|
||||
)
|
||||
|
||||
// settingsGetHandler retrieves the current system settings.
|
||||
@@ -36,36 +34,3 @@ func settingsGetHandler(w http.ResponseWriter, r *http.Request, d *requestContex
|
||||
}
|
||||
return renderJSON(w, r, config)
|
||||
}
|
||||
|
||||
// settingsConfigHandler returns the current system settings as YAML.
|
||||
// @Summary Get system settings as YAML
|
||||
// @Description Returns the current running configuration settings as YAML format with optional comments and filtering.
|
||||
// @Tags Settings
|
||||
// @Accept json
|
||||
// @Produce text/plain
|
||||
// @Param full query boolean false "Show all values (true) or only non-default values (false, default)"
|
||||
// @Param comments query boolean false "Include comments in YAML (true) or plain YAML (false, default)"
|
||||
// @Success 200 {string} string "System settings in YAML format"
|
||||
// @Router /api/settings/config [get]
|
||||
func settingsConfigHandler(w http.ResponseWriter, r *http.Request, d *requestContext) (int, error) {
|
||||
// Parse query parameters
|
||||
fullParam := r.URL.Query().Get("full")
|
||||
commentsParam := r.URL.Query().Get("comments")
|
||||
|
||||
showFull := fullParam == "true"
|
||||
showComments := commentsParam == "true"
|
||||
|
||||
// Generate YAML using the existing generator function (don't filter deprecated fields in API)
|
||||
yamlOutput, err := settings.GenerateConfigYaml(config, showComments, showFull, false)
|
||||
if err != nil {
|
||||
return http.StatusInternalServerError, err
|
||||
}
|
||||
|
||||
// Set content type and write response
|
||||
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
|
||||
if _, err := w.Write([]byte(yamlOutput)); err != nil {
|
||||
return http.StatusInternalServerError, err
|
||||
}
|
||||
|
||||
return http.StatusOK, nil
|
||||
}
|
||||
+6
-39
@@ -17,19 +17,10 @@ var templateRenderer *TemplateRenderer
|
||||
|
||||
type TemplateRenderer struct {
|
||||
templates *template.Template
|
||||
devMode bool
|
||||
}
|
||||
|
||||
// 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.
|
||||
if t.devMode {
|
||||
var err error
|
||||
t.templates, err = t.templates.ParseFS(assetFs, "public/index.html")
|
||||
if err != nil {
|
||||
return fmt.Errorf("error reloading template: %w", err)
|
||||
}
|
||||
}
|
||||
// Set headers
|
||||
w.Header().Set("Cache-Control", "no-cache, private, max-age=0")
|
||||
w.Header().Set("Pragma", "no-cache")
|
||||
@@ -48,37 +39,28 @@ func handleWithStaticData(w http.ResponseWriter, r *http.Request, d *requestCont
|
||||
}
|
||||
userSelectedTheme := ""
|
||||
if d.user != nil {
|
||||
theme, ok := config.Frontend.Styling.CustomThemeOptions[d.user.CustomTheme]
|
||||
theme, ok := config.Frontend.Styling.CustomThemes[d.user.CustomTheme]
|
||||
if ok {
|
||||
userSelectedTheme = theme.CssRaw
|
||||
userSelectedTheme = theme.CSS
|
||||
}
|
||||
}
|
||||
|
||||
defaultThemeColor := "#455a64"
|
||||
staticURL := config.Server.BaseURL + "static"
|
||||
publicStaticURL := config.Server.BaseURL + "public/static"
|
||||
|
||||
// Use custom favicon if configured and validated, otherwise fall back to default
|
||||
var favicon string
|
||||
if config.Frontend.Favicon != "" {
|
||||
favicon = staticURL + "/favicon"
|
||||
} else {
|
||||
// Default favicon
|
||||
favicon = staticURL + "/img/icons/favicon-256x256.png"
|
||||
}
|
||||
data := map[string]interface{}{
|
||||
"title": config.Frontend.Name,
|
||||
"customCSS": config.Frontend.Styling.CustomCSSRaw,
|
||||
"customCSS": config.Frontend.Styling.CustomCSS,
|
||||
"userSelectedTheme": userSelectedTheme,
|
||||
"lightBackground": config.Frontend.Styling.LightBackground,
|
||||
"darkBackground": config.Frontend.Styling.DarkBackground,
|
||||
"staticURL": staticURL,
|
||||
"baseURL": config.Server.BaseURL,
|
||||
"favicon": favicon,
|
||||
"favicon": staticURL + "/img/icons/favicon-256x256.png",
|
||||
"color": defaultThemeColor,
|
||||
"winIcon": staticURL + "/img/icons/mstile-144x144.png",
|
||||
"appIcon": staticURL + "/img/icons/android-chrome-256x256.png",
|
||||
"description": config.Frontend.Description,
|
||||
"description": "FileBrowser Quantum is a file manager for the web which can be used to manage files on your server",
|
||||
}
|
||||
shareProps := map[string]interface{}{
|
||||
"isShare": false,
|
||||
@@ -116,9 +98,6 @@ func handleWithStaticData(w http.ResponseWriter, r *http.Request, d *requestCont
|
||||
shareProps["disableSidebar"] = d.share.DisableSidebar
|
||||
shareProps["isPasswordProtected"] = d.share.PasswordHash != ""
|
||||
shareProps["downloadURL"] = getDownloadURL(r, d.share.Hash)
|
||||
shareProps["enforceDarkLightMode"] = d.share.EnforceDarkLightMode
|
||||
shareProps["enableOnlyOffice"] = d.share.EnableOnlyOffice
|
||||
shareProps["enableOnlyOfficeEditing"] = d.share.EnableOnlyOfficeEditing
|
||||
if d.share.Favicon != "" {
|
||||
if strings.HasPrefix(d.share.Favicon, "http") {
|
||||
data["favicon"] = d.share.Favicon
|
||||
@@ -136,19 +115,13 @@ func handleWithStaticData(w http.ResponseWriter, r *http.Request, d *requestCont
|
||||
|
||||
// base url could be different for routes behind proxy
|
||||
data["staticURL"] = publicStaticURL
|
||||
// Use custom favicon for shares too if configured
|
||||
if config.Frontend.Favicon != "" {
|
||||
data["favicon"] = publicStaticURL + "/favicon"
|
||||
} else {
|
||||
data["favicon"] = publicStaticURL + "/img/icons/favicon-256x256.png"
|
||||
}
|
||||
data["favicon"] = publicStaticURL + "/img/icons/favicon-256x256.png"
|
||||
data["winIcon"] = publicStaticURL + "/img/icons/mstile-144x144.png"
|
||||
data["appIcon"] = publicStaticURL + "/img/icons/android-chrome-256x256.png"
|
||||
}
|
||||
// variables consumed by frontend as json
|
||||
data["globalVars"] = map[string]interface{}{
|
||||
"name": config.Frontend.Name,
|
||||
"minSearchLength": config.Server.MinSearchLength,
|
||||
"disableExternal": config.Frontend.DisableDefaultLinks,
|
||||
"darkMode": settings.Config.UserDefaults.DarkMode,
|
||||
"baseURL": config.Server.BaseURL,
|
||||
@@ -189,12 +162,6 @@ func staticFilesHandler(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Cache-Control", fmt.Sprintf("public, max-age=%v", maxAge))
|
||||
w.Header().Set("Content-Security-Policy", `default-src 'self'; style-src 'unsafe-inline';`)
|
||||
|
||||
// Handle custom favicon if configured and requested
|
||||
if r.URL.Path == "favicon" && config.Frontend.Favicon != "" {
|
||||
http.ServeFile(w, r, config.Frontend.Favicon)
|
||||
return
|
||||
}
|
||||
|
||||
adjustedCompressed := r.URL.Path + ".gz"
|
||||
if strings.HasSuffix(r.URL.Path, ".js") {
|
||||
w.Header().Set("Content-Type", "application/javascript; charset=utf-8") // Set the correct MIME type for JavaScript files
|
||||
|
||||
@@ -270,16 +270,11 @@ func (i *ItemInfo) DetectType(realPath string, saveContent bool) {
|
||||
ext := filepath.Ext(name)
|
||||
|
||||
// Attempt MIME detection by file extension
|
||||
switch ext {
|
||||
case ".md":
|
||||
if ext == ".md" {
|
||||
i.Type = "text/markdown"
|
||||
return
|
||||
case ".heic", ".heif":
|
||||
i.Type = "image/heic"
|
||||
return
|
||||
}
|
||||
i.Type = strings.Split(mime.TypeByExtension(ext), ";")[0]
|
||||
|
||||
if i.Type == "" {
|
||||
i.Type = ExtendedMimeTypeCheck(ext)
|
||||
}
|
||||
@@ -290,13 +285,10 @@ func (i *ItemInfo) DetectType(realPath string, saveContent bool) {
|
||||
i.Type = DetectTypeByHeader(realPath)
|
||||
return
|
||||
}
|
||||
if i.Type == "blob" || i.Type == "" {
|
||||
if i.Type == "blob" {
|
||||
i.Type = DetectTypeByHeader(realPath)
|
||||
}
|
||||
}
|
||||
if i.Type == "" || i.Type == "application/octet-stream" {
|
||||
i.Type = "blob"
|
||||
}
|
||||
}
|
||||
|
||||
// DetectTypeByHeader detects the MIME type of a file based on its header.
|
||||
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/gtsteffaniak/filebrowser/backend/database/access"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/ffmpeg"
|
||||
)
|
||||
|
||||
type ItemInfo struct {
|
||||
@@ -29,14 +28,13 @@ type FileInfo struct {
|
||||
// extra calculated fields can be added here
|
||||
type ExtendedFileInfo struct {
|
||||
FileInfo
|
||||
Content string `json:"content,omitempty"` // text content of a file, if requested
|
||||
Subtitles []ffmpeg.SubtitleTrack `json:"subtitles,omitempty"` // subtitles for video files
|
||||
Checksums map[string]string `json:"checksums,omitempty"` // checksums for the file
|
||||
Token string `json:"token,omitempty"` // token for the file -- used for sharing
|
||||
OnlyOfficeId string `json:"onlyOfficeId,omitempty"` // id for onlyoffice files
|
||||
Source string `json:"source,omitempty"` // associated index source for the file
|
||||
Hash string `json:"hash,omitempty"` // hash for the file -- used for sharing
|
||||
RealPath string `json:"-"`
|
||||
Content string `json:"content,omitempty"` // text content of a file, if requested
|
||||
Subtitles []string `json:"subtitles,omitempty"` // subtitles for video files
|
||||
Checksums map[string]string `json:"checksums,omitempty"` // checksums for the file
|
||||
Token string `json:"token,omitempty"` // token for the file -- used for sharing
|
||||
OnlyOfficeId string `json:"onlyOfficeId,omitempty"` // id for onlyoffice files
|
||||
Source string `json:"source"` // associated index source for the file
|
||||
RealPath string `json:"-"`
|
||||
}
|
||||
|
||||
// FileOptions are the options when getting a file info.
|
||||
|
||||
@@ -8,43 +8,29 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/gtsteffaniak/filebrowser/backend/ffmpeg"
|
||||
"github.com/gtsteffaniak/go-logger/logger"
|
||||
)
|
||||
|
||||
type SubtitleTrack struct {
|
||||
Name string `json:"name"` // filename for external, or descriptive name for embedded
|
||||
Language string `json:"language,omitempty"` // language code
|
||||
Title string `json:"title,omitempty"` // title/description
|
||||
Index *int `json:"index,omitempty"` // stream index for embedded subtitles (nil for external)
|
||||
Codec string `json:"codec,omitempty"` // codec name for embedded subtitles
|
||||
IsFile bool `json:"isFile"` // true for external files, false for embedded
|
||||
}
|
||||
|
||||
type FFProbeOutput struct {
|
||||
Streams []struct {
|
||||
Index int `json:"index"`
|
||||
CodecType string `json:"codec_type"`
|
||||
CodecName string `json:"codec_name"`
|
||||
Tags map[string]string `json:"tags,omitempty"`
|
||||
Disposition map[string]int `json:"disposition,omitempty"`
|
||||
} `json:"streams"`
|
||||
}
|
||||
|
||||
// detects subtitles for video files.
|
||||
func (i *ExtendedFileInfo) DetectSubtitles(parentInfo *FileInfo) {
|
||||
if !strings.HasPrefix(i.Type, "video") {
|
||||
logger.Debug("subtitles are not supported for this file : " + i.Name)
|
||||
return
|
||||
}
|
||||
// Use unified subtitle detection that finds both embedded and external
|
||||
parentDir := filepath.Dir(i.RealPath)
|
||||
i.Subtitles = ffmpeg.DetectAllSubtitles(i.RealPath, parentDir, i.ModTime)
|
||||
}
|
||||
ext := filepath.Ext(i.Name)
|
||||
baseWithoutExt := strings.TrimSuffix(i.Name, ext)
|
||||
for _, f := range parentInfo.Files {
|
||||
baseName := strings.TrimSuffix(i.Name, ext)
|
||||
if baseName != baseWithoutExt {
|
||||
continue
|
||||
}
|
||||
|
||||
// LoadSubtitleContent loads the actual content for all detected subtitle tracks
|
||||
func (i *ExtendedFileInfo) LoadSubtitleContent() error {
|
||||
return ffmpeg.LoadAllSubtitleContent(i.RealPath, i.Subtitles, i.ModTime)
|
||||
for _, subtitleExt := range SubtitleExts {
|
||||
if strings.HasSuffix(f.Name, subtitleExt) {
|
||||
i.Subtitles = append(i.Subtitles, f.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (info *FileInfo) SortItems() {
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
package preview
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/gtsteffaniak/filebrowser/backend/indexing/iteminfo"
|
||||
"github.com/gtsteffaniak/go-logger/logger"
|
||||
)
|
||||
|
||||
// isHEICFile checks if the file is a HEIC file based on MIME type or extension
|
||||
func isHEICFile(file iteminfo.ExtendedFileInfo) bool {
|
||||
// Check by MIME type first
|
||||
if file.Type == "image/heic" {
|
||||
return true
|
||||
}
|
||||
|
||||
// Check by file extension (in case MIME type detection fails)
|
||||
ext := strings.ToLower(filepath.Ext(file.Name))
|
||||
return ext == ".heic" || ext == ".heif"
|
||||
}
|
||||
|
||||
// processHEICFile handles HEIC file processing by delegating to FFmpeg conversion
|
||||
func (s *Service) processHEICFile(file iteminfo.ExtendedFileInfo, previewSize string) ([]byte, error) {
|
||||
logger.Infof("🔄 HEIC: Processing HEIC file %s (size: %s)", filepath.Base(file.Name), previewSize)
|
||||
|
||||
// Delegate to FFmpeg conversion logic
|
||||
return s.convertHEICToJPEGWithFFmpeg(file.RealPath, previewSize)
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
package preview
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/gtsteffaniak/filebrowser/backend/ffmpeg"
|
||||
"github.com/gtsteffaniak/go-logger/logger"
|
||||
)
|
||||
|
||||
// convertHEICToJPEGWithFFmpeg converts a HEIC file to JPEG format using FFmpeg
|
||||
// This function handles all FFmpeg-related logic and parameters
|
||||
func (s *Service) convertHEICToJPEGWithFFmpeg(filePath string, previewSize string) ([]byte, error) {
|
||||
logger.Infof("🎯 HEIC FFMPEG: Starting conversion for %s (size: %s)", filepath.Base(filePath), previewSize)
|
||||
|
||||
if s.ffmpegPath == "" {
|
||||
logger.Errorf("❌ HEIC FFMPEG: FFmpeg path is empty")
|
||||
return nil, fmt.Errorf("FFmpeg is not available for HEIC conversion")
|
||||
}
|
||||
|
||||
logger.Infof("🔧 HEIC FFMPEG: Using FFmpeg at: %s", s.ffmpegPath)
|
||||
|
||||
if err := s.acquire(context.Background()); err != nil {
|
||||
logger.Errorf("❌ HEIC FFMPEG: Failed to acquire semaphore: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
defer s.release()
|
||||
|
||||
// Create FFmpeg image service
|
||||
imageService := ffmpeg.NewImageService(s.ffmpegPath, s.ffprobePath, s.debug)
|
||||
|
||||
// Determine target dimensions and quality based on preview size
|
||||
var width, height int
|
||||
var quality string
|
||||
|
||||
switch previewSize {
|
||||
case "large":
|
||||
width, height = 640, 640
|
||||
quality = "2" // High quality for FFmpeg -q:v
|
||||
logger.Infof("📐 HEIC FFMPEG: Using LARGE size - target: %dx%d, quality: %s", width, height, quality)
|
||||
case "small":
|
||||
width, height = 256, 256
|
||||
quality = "5" // Medium quality
|
||||
logger.Infof("📐 HEIC FFMPEG: Using SMALL size - target: %dx%d, quality: %s", width, height, quality)
|
||||
case "original":
|
||||
// For original size - no scaling, maximum quality
|
||||
logger.Infof("📏 HEIC FFMPEG: Using ORIGINAL size - no scaling, maximum quality")
|
||||
width, height = 0, 0 // Signal to not apply scaling
|
||||
quality = "1" // Maximum quality for original
|
||||
logger.Infof("📐 HEIC FFMPEG: Using ORIGINAL mode - no scaling, quality: %s", quality)
|
||||
default:
|
||||
logger.Errorf("❌ HEIC FFMPEG: Unsupported preview size: %s", previewSize)
|
||||
return nil, ErrUnsupportedFormat
|
||||
}
|
||||
|
||||
logger.Infof("🚀 HEIC FFMPEG: Starting FFmpeg conversion with dimensions %dx%d, quality %s", width, height, quality)
|
||||
result, err := imageService.ConvertHEICToJPEG(filePath, width, height, quality)
|
||||
if err != nil {
|
||||
logger.Errorf("❌ HEIC FFMPEG: Conversion failed: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
logger.Infof("✅ HEIC FFMPEG: Conversion successful, output size: %d bytes", len(result))
|
||||
return result, nil
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
//go:generate go-enum --sql --marshal --file $GOFILE
|
||||
package preview
|
||||
|
||||
import (
|
||||
@@ -7,9 +8,9 @@ import (
|
||||
"image"
|
||||
"io"
|
||||
|
||||
"github.com/kovidgoyal/imaging"
|
||||
exif "github.com/dsoprea/go-exif/v3"
|
||||
exifcommon "github.com/dsoprea/go-exif/v3/common"
|
||||
"github.com/kovidgoyal/imaging"
|
||||
)
|
||||
|
||||
// Format is an image file format.
|
||||
@@ -20,7 +21,6 @@ png
|
||||
gif
|
||||
tiff
|
||||
bmp
|
||||
heic
|
||||
)
|
||||
*/
|
||||
type Format int
|
||||
@@ -37,8 +37,6 @@ func (x Format) toImaging() imaging.Format {
|
||||
return imaging.TIFF
|
||||
case FormatBmp:
|
||||
return imaging.BMP
|
||||
case FormatHeic:
|
||||
return imaging.JPEG
|
||||
default:
|
||||
return imaging.JPEG
|
||||
}
|
||||
@@ -75,12 +73,6 @@ fill
|
||||
type ResizeMode int
|
||||
|
||||
func (s *Service) FormatFromExtension(ext string) (Format, error) {
|
||||
// heic is not supported by imaging, so we return FormatHeic
|
||||
switch ext {
|
||||
case ".heic", ".heif":
|
||||
return FormatHeic, nil
|
||||
}
|
||||
|
||||
format, err := imaging.FormatFromExtension(ext)
|
||||
if err != nil {
|
||||
return -1, ErrUnsupportedFormat
|
||||
@@ -147,10 +139,6 @@ func (s *Service) Resize(in io.Reader, width, height int, out io.Writer, options
|
||||
option(&config)
|
||||
}
|
||||
|
||||
if format == FormatHeic {
|
||||
config.format = FormatJpeg
|
||||
}
|
||||
|
||||
if config.quality == QualityLow && format == FormatJpeg {
|
||||
thm, newWrappedReader, errThm := getEmbeddedThumbnail(wrappedReader)
|
||||
wrappedReader = newWrappedReader
|
||||
@@ -162,19 +150,16 @@ func (s *Service) Resize(in io.Reader, width, height int, out io.Writer, options
|
||||
}
|
||||
}
|
||||
|
||||
// For HEIC files, try without AutoOrientation first since it might not work properly
|
||||
img, err := imaging.Decode(wrappedReader)
|
||||
img, err := imaging.Decode(wrappedReader, imaging.AutoOrientation(true))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Note: For HEIC files processed via FFmpeg, orientation is handled automatically
|
||||
|
||||
switch config.resizeMode {
|
||||
case ResizeModeFill:
|
||||
img = imaging.Fill(img, width, height, imaging.Center, config.quality.resampleFilter())
|
||||
case ResizeModeFit:
|
||||
img = imaging.Fit(img, width, height, config.quality.resampleFilter())
|
||||
fallthrough
|
||||
default:
|
||||
img = imaging.Fit(img, width, height, config.quality.resampleFilter())
|
||||
}
|
||||
@@ -191,10 +176,6 @@ func (s *Service) detectFormat(in io.Reader) (Format, io.Reader, error) {
|
||||
return 0, nil, fmt.Errorf("%s: %w", err.Error(), ErrUnsupportedFormat)
|
||||
}
|
||||
|
||||
if imgFormat == "heif" {
|
||||
imgFormat = "heic"
|
||||
}
|
||||
|
||||
format, err := ParseFormat(imgFormat)
|
||||
if err != nil {
|
||||
return 0, nil, ErrUnsupportedFormat
|
||||
|
||||
@@ -19,19 +19,16 @@ const (
|
||||
FormatTiff
|
||||
// FormatBmp is a Format of type Bmp
|
||||
FormatBmp
|
||||
// FormatHeic is a Format of type Heic
|
||||
FormatHeic
|
||||
)
|
||||
|
||||
const _FormatName = "jpegpnggiftiffbmpheic"
|
||||
const _FormatName = "jpegpnggiftiffbmp"
|
||||
|
||||
var _FormatMap = map[Format]string{
|
||||
0: _FormatName[0:4], // jpeg
|
||||
1: _FormatName[4:7], // png
|
||||
2: _FormatName[7:10], // gif
|
||||
3: _FormatName[10:14], // tiff
|
||||
4: _FormatName[14:17], // bmp
|
||||
5: _FormatName[17:21], // heic
|
||||
0: _FormatName[0:4],
|
||||
1: _FormatName[4:7],
|
||||
2: _FormatName[7:10],
|
||||
3: _FormatName[10:14],
|
||||
4: _FormatName[14:17],
|
||||
}
|
||||
|
||||
// String implements the Stringer interface.
|
||||
@@ -43,12 +40,11 @@ func (x Format) String() string {
|
||||
}
|
||||
|
||||
var _FormatValue = map[string]Format{
|
||||
_FormatName[0:4]: 0, // jpeg
|
||||
_FormatName[4:7]: 1, // png
|
||||
_FormatName[7:10]: 2, // gif
|
||||
_FormatName[10:14]: 3, // tiff
|
||||
_FormatName[14:17]: 4, // bmp
|
||||
_FormatName[17:21]: 5, // heic
|
||||
_FormatName[0:4]: 0,
|
||||
_FormatName[4:7]: 1,
|
||||
_FormatName[7:10]: 2,
|
||||
_FormatName[10:14]: 3,
|
||||
_FormatName[14:17]: 4,
|
||||
}
|
||||
|
||||
// ParseFormat attempts to convert a string to a Format
|
||||
|
||||
@@ -70,11 +70,11 @@ func NewPreviewGenerator(concurrencyLimit int, ffmpegPath string, cacheDir strin
|
||||
logger.Fatalf("the configured ffmpeg path is not a valid ffprobe binary %s, err: %v", ffmpegPath, err)
|
||||
}
|
||||
if errprobe == nil && err == nil {
|
||||
logger.Debugf("Media Enabled : %v", errprobe == nil)
|
||||
logger.Infof("Media Enabled : %v", errprobe == nil)
|
||||
settings.Config.Integrations.Media.FfmpegPath = filepath.Base(ffmpegMainPath)
|
||||
}
|
||||
settings.Config.Server.MuPdfAvailable = docEnabled()
|
||||
logger.Debugf("MuPDF Enabled : %v", settings.Config.Server.MuPdfAvailable)
|
||||
logger.Infof("MuPDF Enabled : %v", settings.Config.Server.MuPdfAvailable)
|
||||
return &Service{
|
||||
sem: make(chan struct{}, concurrencyLimit),
|
||||
ffmpegPath: ffmpegMainPath,
|
||||
@@ -126,34 +126,10 @@ func GeneratePreview(file iteminfo.ExtendedFileInfo, previewSize, officeUrl stri
|
||||
return nil, fmt.Errorf("failed to create image for office file: %w", err)
|
||||
}
|
||||
} else if strings.HasPrefix(file.Type, "image") {
|
||||
logger.Infof("🔍 PREVIEW DEBUG: Image file detected - Name: %s, Type: %s, Extension: %s", file.Name, file.Type, filepath.Ext(file.Name))
|
||||
|
||||
// Quick HEIC identification and delegation
|
||||
if isHEICFile(file) {
|
||||
logger.Infof("🔄 HEIC DEBUG: HEIC file detected for %s", filepath.Base(file.Name))
|
||||
|
||||
// HEIC files need FFmpeg conversion to JPEG with proper size/quality handling
|
||||
imageBytes, err = service.processHEICFile(file, previewSize)
|
||||
if err != nil {
|
||||
logger.Errorf("❌ HEIC DEBUG: HEIC processing failed: %v", err)
|
||||
return nil, fmt.Errorf("failed to process HEIC image file: %w", err)
|
||||
}
|
||||
|
||||
logger.Infof("✅ HEIC DEBUG: HEIC processing successful, got %d bytes", len(imageBytes))
|
||||
|
||||
// For HEIC files, we've already done the resize/conversion, so cache and return directly
|
||||
cacheKey := CacheKey(file.RealPath, previewSize, file.ModTime, seekPercentage)
|
||||
if err = service.fileCache.Store(context.Background(), cacheKey, imageBytes); err != nil {
|
||||
logger.Errorf("failed to cache HEIC image: %v", err)
|
||||
}
|
||||
return imageBytes, nil
|
||||
} else {
|
||||
// get image bytes from file (non-HEIC images)
|
||||
logger.Infof("📖 PREVIEW DEBUG: Reading non-HEIC image file directly")
|
||||
imageBytes, err = os.ReadFile(file.RealPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read image file: %w", err)
|
||||
}
|
||||
// get image bytes from file
|
||||
imageBytes, err = os.ReadFile(file.RealPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read image file: %w", err)
|
||||
}
|
||||
} else if strings.HasPrefix(file.Type, "video") {
|
||||
if seekPercentage == 0 {
|
||||
@@ -247,8 +223,6 @@ func AvailablePreview(file iteminfo.ExtendedFileInfo) bool {
|
||||
switch ext {
|
||||
case ".jpg", ".jpeg", ".png", ".bmp", ".tiff":
|
||||
return true
|
||||
case ".heic", ".heif":
|
||||
return service.ffmpegPath != "" // HEIC support available when FFmpeg is available
|
||||
}
|
||||
return file.OnlyOfficeId != ""
|
||||
}
|
||||
|
||||
@@ -1,9 +1,15 @@
|
||||
package preview
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/gtsteffaniak/filebrowser/backend/ffmpeg"
|
||||
"github.com/gtsteffaniak/go-logger/logger"
|
||||
)
|
||||
|
||||
// GenerateVideoPreview generates a single preview image from a video using ffmpeg.
|
||||
@@ -15,8 +21,69 @@ func (s *Service) GenerateVideoPreview(videoPath, outputPath string, percentageS
|
||||
return nil, err
|
||||
}
|
||||
defer s.release()
|
||||
// Step 1: Get video duration from the container format
|
||||
probeCmd := exec.Command(
|
||||
s.ffprobePath,
|
||||
"-v", "error",
|
||||
// Use format=duration for better compatibility
|
||||
"-show_entries", "format=duration",
|
||||
"-of", "default=noprint_wrappers=1:nokey=1",
|
||||
videoPath,
|
||||
)
|
||||
|
||||
// Create a temporary video service for this operation
|
||||
videoSvc := ffmpeg.NewVideoService(s.ffmpegPath, s.ffprobePath, 1, s.debug)
|
||||
return videoSvc.GenerateVideoPreview(videoPath, outputPath, percentageSeek)
|
||||
var probeOut bytes.Buffer
|
||||
probeCmd.Stdout = &probeOut
|
||||
if s.debug {
|
||||
probeCmd.Stderr = os.Stderr
|
||||
}
|
||||
if err := probeCmd.Run(); err != nil {
|
||||
logger.Errorf("ffprobe command failed on file '%v' : %v", videoPath, err)
|
||||
return nil, fmt.Errorf("ffprobe failed: %w", err)
|
||||
}
|
||||
|
||||
durationStr := strings.TrimSpace(probeOut.String())
|
||||
if durationStr == "" || durationStr == "N/A" {
|
||||
logger.Errorf("could not determine video duration for file '%v' using duration info '%v'", videoPath, durationStr)
|
||||
return nil, fmt.Errorf("could not determine video duration")
|
||||
}
|
||||
|
||||
durationFloat, err := strconv.ParseFloat(durationStr, 64)
|
||||
if err != nil {
|
||||
// The original error you saw would be caught here if "N/A" was still the output
|
||||
return nil, fmt.Errorf("invalid duration: %v", err)
|
||||
}
|
||||
|
||||
if durationFloat <= 0 {
|
||||
return nil, fmt.Errorf("video duration must be positive")
|
||||
}
|
||||
|
||||
// The rest of your function remains the same...
|
||||
// Step 2: Get the duration of the video in whole seconds
|
||||
duration := int(durationFloat)
|
||||
|
||||
// Step 3: Calculate seek time based on percentageSeek (percentage value)
|
||||
seekSeconds := duration * percentageSeek / 100
|
||||
|
||||
// Step 4: Convert seekSeconds to string for ffmpeg command
|
||||
seekTime := strconv.Itoa(seekSeconds)
|
||||
// Step 5: Extract frame at seek time
|
||||
cmd := exec.Command(
|
||||
s.ffmpegPath,
|
||||
"-ss", seekTime,
|
||||
"-i", videoPath,
|
||||
"-frames:v", "1",
|
||||
"-q:v", "10",
|
||||
"-y", // overwrite output
|
||||
outputPath,
|
||||
)
|
||||
|
||||
if s.debug {
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
}
|
||||
err = cmd.Run()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("ffmpeg command failed on file '%v' : %w", videoPath, err)
|
||||
}
|
||||
return os.ReadFile(outputPath)
|
||||
}
|
||||
+11
-178
@@ -854,87 +854,6 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/media/subtitles": {
|
||||
"get": {
|
||||
"description": "Extracts embedded subtitle content from video files by stream index and returns raw WebVTT content",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"text/vtt"
|
||||
],
|
||||
"tags": [
|
||||
"Subtitles"
|
||||
],
|
||||
"summary": "Extract embedded subtitles",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Index path to the video file",
|
||||
"name": "path",
|
||||
"in": "query",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Source name for the desired source",
|
||||
"name": "source",
|
||||
"in": "query",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "Stream index for embedded subtitle extraction, defaults to 0",
|
||||
"name": "index",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Raw WebVTT subtitle content",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad request",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "Forbidden",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Resource not found",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal server error",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/preview": {
|
||||
"get": {
|
||||
"description": "Returns a preview image based on the requested path and size.",
|
||||
@@ -1697,43 +1616,6 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/settings/config": {
|
||||
"get": {
|
||||
"description": "Returns the current running configuration settings as YAML format with optional comments and filtering.",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"text/plain"
|
||||
],
|
||||
"tags": [
|
||||
"Settings"
|
||||
],
|
||||
"summary": "Get system settings as YAML",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "boolean",
|
||||
"description": "Show all values (true) or only non-default values (false, default)",
|
||||
"name": "full",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "boolean",
|
||||
"description": "Include comments in YAML (true) or plain YAML (false, default)",
|
||||
"name": "comments",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "System settings in YAML format",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/share": {
|
||||
"get": {
|
||||
"description": "Retrieves all share links associated with a specific resource path for the current user.",
|
||||
@@ -2604,26 +2486,26 @@ const docTemplate = `{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"adminPassword": {
|
||||
"description": "secret: the password of the admin user. If not set, the default is \"admin\".",
|
||||
"description": "the password of the admin user. If not set, the default is \"admin\".",
|
||||
"type": "string"
|
||||
},
|
||||
"adminUsername": {
|
||||
"description": "secret: the username of the admin user. If not set, the default is \"admin\".",
|
||||
"description": "the username of the admin user. If not set, the default is \"admin\".",
|
||||
"type": "string"
|
||||
},
|
||||
"key": {
|
||||
"description": "secret: the key used to sign the JWT tokens. If not set, a random key will be generated.",
|
||||
"description": "the key used to sign the JWT tokens. If not set, a random key will be generated.",
|
||||
"type": "string"
|
||||
},
|
||||
"methods": {
|
||||
"$ref": "#/definitions/settings.LoginMethods"
|
||||
},
|
||||
"tokenExpirationHours": {
|
||||
"description": "time in hours each web UI session token is valid for. Default is 2 hours.",
|
||||
"description": "the number of hours until the token expires. Default is 2 hours.",
|
||||
"type": "integer"
|
||||
},
|
||||
"totpSecret": {
|
||||
"description": "secret: secret used to encrypt TOTP secrets",
|
||||
"description": "secret used to encrypt TOTP secrets",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
@@ -2720,10 +2602,6 @@ const docTemplate = `{
|
||||
"settings.Frontend": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"description": {
|
||||
"description": "description that shows up in html head meta description",
|
||||
"type": "string"
|
||||
},
|
||||
"disableDefaultLinks": {
|
||||
"description": "disable default links in the sidebar",
|
||||
"type": "boolean"
|
||||
@@ -2742,10 +2620,6 @@ const docTemplate = `{
|
||||
"$ref": "#/definitions/settings.ExternalLink"
|
||||
}
|
||||
},
|
||||
"favicon": {
|
||||
"description": "path to a favicon to use for the frontend",
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"description": "display name",
|
||||
"type": "string"
|
||||
@@ -2849,11 +2723,11 @@ const docTemplate = `{
|
||||
"type": "string"
|
||||
},
|
||||
"clientId": {
|
||||
"description": "secret: client id of the OIDC application",
|
||||
"description": "client id of the OIDC application",
|
||||
"type": "string"
|
||||
},
|
||||
"clientSecret": {
|
||||
"description": "secret: client secret of the OIDC application",
|
||||
"description": "client secret of the OIDC application",
|
||||
"type": "string"
|
||||
},
|
||||
"createUser": {
|
||||
@@ -2902,7 +2776,6 @@ const docTemplate = `{
|
||||
"type": "string"
|
||||
},
|
||||
"secret": {
|
||||
"description": "secret: authentication key for OnlyOffice integration",
|
||||
"type": "string"
|
||||
},
|
||||
"url": {
|
||||
@@ -3012,10 +2885,6 @@ const docTemplate = `{
|
||||
"description": "disable type detection by header, useful if filesystem is slow.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"disableUpdateCheck": {
|
||||
"description": "disables backend update check service",
|
||||
"type": "boolean"
|
||||
},
|
||||
"externalUrl": {
|
||||
"description": "used by share links if set (eg. http://mydomain.com)",
|
||||
"type": "string"
|
||||
@@ -3034,10 +2903,6 @@ const docTemplate = `{
|
||||
"description": "max pre-archive combined size of files/folder that are allowed to be archived (in GB)",
|
||||
"type": "integer"
|
||||
},
|
||||
"minSearchLength": {
|
||||
"description": "minimum length of search query to begin searching (default: 3)",
|
||||
"type": "integer"
|
||||
},
|
||||
"numImageProcessors": {
|
||||
"description": "number of concurrent image processing jobs used to create previews, default is number of cpu cores available.",
|
||||
"type": "integer"
|
||||
@@ -3208,10 +3073,6 @@ const docTemplate = `{
|
||||
"description": "when false, the date is relative, when true, the date is an exact timestamp",
|
||||
"type": "boolean"
|
||||
},
|
||||
"debugOffice": {
|
||||
"description": "debug onlyoffice editor",
|
||||
"type": "boolean"
|
||||
},
|
||||
"deleteWithoutConfirming": {
|
||||
"description": "delete files without confirmation",
|
||||
"type": "boolean"
|
||||
@@ -3225,7 +3086,7 @@ const docTemplate = `{
|
||||
"type": "string"
|
||||
},
|
||||
"disablePreviewExt": {
|
||||
"description": "space separated list of file extensions to disable preview for",
|
||||
"description": "comma separated list of file extensions to disable preview for",
|
||||
"type": "string"
|
||||
},
|
||||
"disableQuickToggles": {
|
||||
@@ -3245,7 +3106,7 @@ const docTemplate = `{
|
||||
"type": "boolean"
|
||||
},
|
||||
"disableViewingExt": {
|
||||
"description": "space separated list of file extensions to disable viewing for",
|
||||
"description": "comma separated list of file extensions to disable viewing for",
|
||||
"type": "string"
|
||||
},
|
||||
"editorQuickSave": {
|
||||
@@ -3356,16 +3217,6 @@ const docTemplate = `{
|
||||
"downloadsLimit": {
|
||||
"type": "integer"
|
||||
},
|
||||
"enableOnlyOffice": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"enableOnlyOfficeEditing": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"enforceDarkLightMode": {
|
||||
"description": "\"dark\" or \"light\"",
|
||||
"type": "string"
|
||||
},
|
||||
"expires": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -3457,16 +3308,6 @@ const docTemplate = `{
|
||||
"downloadsLimit": {
|
||||
"type": "integer"
|
||||
},
|
||||
"enableOnlyOffice": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"enableOnlyOfficeEditing": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"enforceDarkLightMode": {
|
||||
"description": "\"dark\" or \"light\"",
|
||||
"type": "string"
|
||||
},
|
||||
"expire": {
|
||||
"type": "integer"
|
||||
},
|
||||
@@ -3593,10 +3434,6 @@ const docTemplate = `{
|
||||
"description": "autoplay media files in preview",
|
||||
"type": "boolean"
|
||||
},
|
||||
"defaultMediaPlayer": {
|
||||
"description": "disable html5 media player and use the default media player",
|
||||
"type": "boolean"
|
||||
},
|
||||
"disableHideSidebar": {
|
||||
"description": "disable the hide sidebar preview for previews and editors",
|
||||
"type": "boolean"
|
||||
@@ -3670,10 +3507,6 @@ const docTemplate = `{
|
||||
"description": "when false, the date is relative, when true, the date is an exact timestamp",
|
||||
"type": "boolean"
|
||||
},
|
||||
"debugOffice": {
|
||||
"description": "debug onlyoffice editor",
|
||||
"type": "boolean"
|
||||
},
|
||||
"deleteWithoutConfirming": {
|
||||
"description": "delete files without confirmation",
|
||||
"type": "boolean"
|
||||
@@ -3687,7 +3520,7 @@ const docTemplate = `{
|
||||
"type": "string"
|
||||
},
|
||||
"disablePreviewExt": {
|
||||
"description": "space separated list of file extensions to disable preview for",
|
||||
"description": "comma separated list of file extensions to disable preview for",
|
||||
"type": "string"
|
||||
},
|
||||
"disableQuickToggles": {
|
||||
@@ -3706,7 +3539,7 @@ const docTemplate = `{
|
||||
"type": "boolean"
|
||||
},
|
||||
"disableViewingExt": {
|
||||
"description": "space separated list of file extensions to disable viewing for",
|
||||
"description": "comma separated list of file extensions to disable viewing for",
|
||||
"type": "string"
|
||||
},
|
||||
"editorQuickSave": {
|
||||
|
||||
@@ -843,87 +843,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/media/subtitles": {
|
||||
"get": {
|
||||
"description": "Extracts embedded subtitle content from video files by stream index and returns raw WebVTT content",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"text/vtt"
|
||||
],
|
||||
"tags": [
|
||||
"Subtitles"
|
||||
],
|
||||
"summary": "Extract embedded subtitles",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Index path to the video file",
|
||||
"name": "path",
|
||||
"in": "query",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Source name for the desired source",
|
||||
"name": "source",
|
||||
"in": "query",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "Stream index for embedded subtitle extraction, defaults to 0",
|
||||
"name": "index",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Raw WebVTT subtitle content",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad request",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "Forbidden",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Resource not found",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal server error",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/preview": {
|
||||
"get": {
|
||||
"description": "Returns a preview image based on the requested path and size.",
|
||||
@@ -1686,43 +1605,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/settings/config": {
|
||||
"get": {
|
||||
"description": "Returns the current running configuration settings as YAML format with optional comments and filtering.",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"text/plain"
|
||||
],
|
||||
"tags": [
|
||||
"Settings"
|
||||
],
|
||||
"summary": "Get system settings as YAML",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "boolean",
|
||||
"description": "Show all values (true) or only non-default values (false, default)",
|
||||
"name": "full",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "boolean",
|
||||
"description": "Include comments in YAML (true) or plain YAML (false, default)",
|
||||
"name": "comments",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "System settings in YAML format",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/share": {
|
||||
"get": {
|
||||
"description": "Retrieves all share links associated with a specific resource path for the current user.",
|
||||
@@ -2593,26 +2475,26 @@
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"adminPassword": {
|
||||
"description": "secret: the password of the admin user. If not set, the default is \"admin\".",
|
||||
"description": "the password of the admin user. If not set, the default is \"admin\".",
|
||||
"type": "string"
|
||||
},
|
||||
"adminUsername": {
|
||||
"description": "secret: the username of the admin user. If not set, the default is \"admin\".",
|
||||
"description": "the username of the admin user. If not set, the default is \"admin\".",
|
||||
"type": "string"
|
||||
},
|
||||
"key": {
|
||||
"description": "secret: the key used to sign the JWT tokens. If not set, a random key will be generated.",
|
||||
"description": "the key used to sign the JWT tokens. If not set, a random key will be generated.",
|
||||
"type": "string"
|
||||
},
|
||||
"methods": {
|
||||
"$ref": "#/definitions/settings.LoginMethods"
|
||||
},
|
||||
"tokenExpirationHours": {
|
||||
"description": "time in hours each web UI session token is valid for. Default is 2 hours.",
|
||||
"description": "the number of hours until the token expires. Default is 2 hours.",
|
||||
"type": "integer"
|
||||
},
|
||||
"totpSecret": {
|
||||
"description": "secret: secret used to encrypt TOTP secrets",
|
||||
"description": "secret used to encrypt TOTP secrets",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
@@ -2709,10 +2591,6 @@
|
||||
"settings.Frontend": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"description": {
|
||||
"description": "description that shows up in html head meta description",
|
||||
"type": "string"
|
||||
},
|
||||
"disableDefaultLinks": {
|
||||
"description": "disable default links in the sidebar",
|
||||
"type": "boolean"
|
||||
@@ -2731,10 +2609,6 @@
|
||||
"$ref": "#/definitions/settings.ExternalLink"
|
||||
}
|
||||
},
|
||||
"favicon": {
|
||||
"description": "path to a favicon to use for the frontend",
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"description": "display name",
|
||||
"type": "string"
|
||||
@@ -2838,11 +2712,11 @@
|
||||
"type": "string"
|
||||
},
|
||||
"clientId": {
|
||||
"description": "secret: client id of the OIDC application",
|
||||
"description": "client id of the OIDC application",
|
||||
"type": "string"
|
||||
},
|
||||
"clientSecret": {
|
||||
"description": "secret: client secret of the OIDC application",
|
||||
"description": "client secret of the OIDC application",
|
||||
"type": "string"
|
||||
},
|
||||
"createUser": {
|
||||
@@ -2891,7 +2765,6 @@
|
||||
"type": "string"
|
||||
},
|
||||
"secret": {
|
||||
"description": "secret: authentication key for OnlyOffice integration",
|
||||
"type": "string"
|
||||
},
|
||||
"url": {
|
||||
@@ -3001,10 +2874,6 @@
|
||||
"description": "disable type detection by header, useful if filesystem is slow.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"disableUpdateCheck": {
|
||||
"description": "disables backend update check service",
|
||||
"type": "boolean"
|
||||
},
|
||||
"externalUrl": {
|
||||
"description": "used by share links if set (eg. http://mydomain.com)",
|
||||
"type": "string"
|
||||
@@ -3023,10 +2892,6 @@
|
||||
"description": "max pre-archive combined size of files/folder that are allowed to be archived (in GB)",
|
||||
"type": "integer"
|
||||
},
|
||||
"minSearchLength": {
|
||||
"description": "minimum length of search query to begin searching (default: 3)",
|
||||
"type": "integer"
|
||||
},
|
||||
"numImageProcessors": {
|
||||
"description": "number of concurrent image processing jobs used to create previews, default is number of cpu cores available.",
|
||||
"type": "integer"
|
||||
@@ -3197,10 +3062,6 @@
|
||||
"description": "when false, the date is relative, when true, the date is an exact timestamp",
|
||||
"type": "boolean"
|
||||
},
|
||||
"debugOffice": {
|
||||
"description": "debug onlyoffice editor",
|
||||
"type": "boolean"
|
||||
},
|
||||
"deleteWithoutConfirming": {
|
||||
"description": "delete files without confirmation",
|
||||
"type": "boolean"
|
||||
@@ -3214,7 +3075,7 @@
|
||||
"type": "string"
|
||||
},
|
||||
"disablePreviewExt": {
|
||||
"description": "space separated list of file extensions to disable preview for",
|
||||
"description": "comma separated list of file extensions to disable preview for",
|
||||
"type": "string"
|
||||
},
|
||||
"disableQuickToggles": {
|
||||
@@ -3234,7 +3095,7 @@
|
||||
"type": "boolean"
|
||||
},
|
||||
"disableViewingExt": {
|
||||
"description": "space separated list of file extensions to disable viewing for",
|
||||
"description": "comma separated list of file extensions to disable viewing for",
|
||||
"type": "string"
|
||||
},
|
||||
"editorQuickSave": {
|
||||
@@ -3345,16 +3206,6 @@
|
||||
"downloadsLimit": {
|
||||
"type": "integer"
|
||||
},
|
||||
"enableOnlyOffice": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"enableOnlyOfficeEditing": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"enforceDarkLightMode": {
|
||||
"description": "\"dark\" or \"light\"",
|
||||
"type": "string"
|
||||
},
|
||||
"expires": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -3446,16 +3297,6 @@
|
||||
"downloadsLimit": {
|
||||
"type": "integer"
|
||||
},
|
||||
"enableOnlyOffice": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"enableOnlyOfficeEditing": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"enforceDarkLightMode": {
|
||||
"description": "\"dark\" or \"light\"",
|
||||
"type": "string"
|
||||
},
|
||||
"expire": {
|
||||
"type": "integer"
|
||||
},
|
||||
@@ -3582,10 +3423,6 @@
|
||||
"description": "autoplay media files in preview",
|
||||
"type": "boolean"
|
||||
},
|
||||
"defaultMediaPlayer": {
|
||||
"description": "disable html5 media player and use the default media player",
|
||||
"type": "boolean"
|
||||
},
|
||||
"disableHideSidebar": {
|
||||
"description": "disable the hide sidebar preview for previews and editors",
|
||||
"type": "boolean"
|
||||
@@ -3659,10 +3496,6 @@
|
||||
"description": "when false, the date is relative, when true, the date is an exact timestamp",
|
||||
"type": "boolean"
|
||||
},
|
||||
"debugOffice": {
|
||||
"description": "debug onlyoffice editor",
|
||||
"type": "boolean"
|
||||
},
|
||||
"deleteWithoutConfirming": {
|
||||
"description": "delete files without confirmation",
|
||||
"type": "boolean"
|
||||
@@ -3676,7 +3509,7 @@
|
||||
"type": "string"
|
||||
},
|
||||
"disablePreviewExt": {
|
||||
"description": "space separated list of file extensions to disable preview for",
|
||||
"description": "comma separated list of file extensions to disable preview for",
|
||||
"type": "string"
|
||||
},
|
||||
"disableQuickToggles": {
|
||||
@@ -3695,7 +3528,7 @@
|
||||
"type": "boolean"
|
||||
},
|
||||
"disableViewingExt": {
|
||||
"description": "space separated list of file extensions to disable viewing for",
|
||||
"description": "comma separated list of file extensions to disable viewing for",
|
||||
"type": "string"
|
||||
},
|
||||
"editorQuickSave": {
|
||||
|
||||
@@ -140,25 +140,22 @@ definitions:
|
||||
settings.Auth:
|
||||
properties:
|
||||
adminPassword:
|
||||
description: 'secret: the password of the admin user. If not set, the default
|
||||
is "admin".'
|
||||
description: the password of the admin user. If not set, the default is "admin".
|
||||
type: string
|
||||
adminUsername:
|
||||
description: 'secret: the username of the admin user. If not set, the default
|
||||
is "admin".'
|
||||
description: the username of the admin user. If not set, the default is "admin".
|
||||
type: string
|
||||
key:
|
||||
description: 'secret: the key used to sign the JWT tokens. If not set, a random
|
||||
key will be generated.'
|
||||
description: the key used to sign the JWT tokens. If not set, a random key
|
||||
will be generated.
|
||||
type: string
|
||||
methods:
|
||||
$ref: '#/definitions/settings.LoginMethods'
|
||||
tokenExpirationHours:
|
||||
description: time in hours each web UI session token is valid for. Default
|
||||
is 2 hours.
|
||||
description: the number of hours until the token expires. Default is 2 hours.
|
||||
type: integer
|
||||
totpSecret:
|
||||
description: 'secret: secret used to encrypt TOTP secrets'
|
||||
description: secret used to encrypt TOTP secrets
|
||||
type: string
|
||||
type: object
|
||||
settings.CustomTheme:
|
||||
@@ -232,9 +229,6 @@ definitions:
|
||||
type: object
|
||||
settings.Frontend:
|
||||
properties:
|
||||
description:
|
||||
description: description that shows up in html head meta description
|
||||
type: string
|
||||
disableDefaultLinks:
|
||||
description: disable default links in the sidebar
|
||||
type: boolean
|
||||
@@ -248,9 +242,6 @@ definitions:
|
||||
items:
|
||||
$ref: '#/definitions/settings.ExternalLink'
|
||||
type: array
|
||||
favicon:
|
||||
description: path to a favicon to use for the frontend
|
||||
type: string
|
||||
name:
|
||||
description: display name
|
||||
type: string
|
||||
@@ -325,10 +316,10 @@ definitions:
|
||||
description: if set, users in this group will be granted admin privileges.
|
||||
type: string
|
||||
clientId:
|
||||
description: 'secret: client id of the OIDC application'
|
||||
description: client id of the OIDC application
|
||||
type: string
|
||||
clientSecret:
|
||||
description: 'secret: client secret of the OIDC application'
|
||||
description: client secret of the OIDC application
|
||||
type: string
|
||||
createUser:
|
||||
description: create user if not exists
|
||||
@@ -366,7 +357,6 @@ definitions:
|
||||
to bypass proxy.
|
||||
type: string
|
||||
secret:
|
||||
description: 'secret: authentication key for OnlyOffice integration'
|
||||
type: string
|
||||
url:
|
||||
description: The URL to the OnlyOffice Document Server, needs to be accessible
|
||||
@@ -450,9 +440,6 @@ definitions:
|
||||
disableTypeDetectionByHeader:
|
||||
description: disable type detection by header, useful if filesystem is slow.
|
||||
type: boolean
|
||||
disableUpdateCheck:
|
||||
description: disables backend update check service
|
||||
type: boolean
|
||||
externalUrl:
|
||||
description: used by share links if set (eg. http://mydomain.com)
|
||||
type: string
|
||||
@@ -468,10 +455,6 @@ definitions:
|
||||
description: max pre-archive combined size of files/folder that are allowed
|
||||
to be archived (in GB)
|
||||
type: integer
|
||||
minSearchLength:
|
||||
description: 'minimum length of search query to begin searching (default:
|
||||
3)'
|
||||
type: integer
|
||||
numImageProcessors:
|
||||
description: number of concurrent image processing jobs used to create previews,
|
||||
default is number of cpu cores available.
|
||||
@@ -602,9 +585,6 @@ definitions:
|
||||
description: when false, the date is relative, when true, the date is an exact
|
||||
timestamp
|
||||
type: boolean
|
||||
debugOffice:
|
||||
description: debug onlyoffice editor
|
||||
type: boolean
|
||||
deleteWithoutConfirming:
|
||||
description: delete files without confirmation
|
||||
type: boolean
|
||||
@@ -615,7 +595,7 @@ definitions:
|
||||
description: list of file extensions to disable onlyoffice editor for
|
||||
type: string
|
||||
disablePreviewExt:
|
||||
description: space separated list of file extensions to disable preview for
|
||||
description: comma separated list of file extensions to disable preview for
|
||||
type: string
|
||||
disableQuickToggles:
|
||||
description: disable the quick toggles in the sidebar
|
||||
@@ -630,7 +610,7 @@ definitions:
|
||||
description: disable update notifications banner for admin users
|
||||
type: boolean
|
||||
disableViewingExt:
|
||||
description: space separated list of file extensions to disable viewing for
|
||||
description: comma separated list of file extensions to disable viewing for
|
||||
type: string
|
||||
editorQuickSave:
|
||||
description: show quick save button in editor
|
||||
@@ -710,13 +690,6 @@ definitions:
|
||||
type: string
|
||||
downloadsLimit:
|
||||
type: integer
|
||||
enableOnlyOffice:
|
||||
type: boolean
|
||||
enableOnlyOfficeEditing:
|
||||
type: boolean
|
||||
enforceDarkLightMode:
|
||||
description: '"dark" or "light"'
|
||||
type: string
|
||||
expires:
|
||||
type: string
|
||||
favicon:
|
||||
@@ -778,13 +751,6 @@ definitions:
|
||||
type: integer
|
||||
downloadsLimit:
|
||||
type: integer
|
||||
enableOnlyOffice:
|
||||
type: boolean
|
||||
enableOnlyOfficeEditing:
|
||||
type: boolean
|
||||
enforceDarkLightMode:
|
||||
description: '"dark" or "light"'
|
||||
type: string
|
||||
expire:
|
||||
type: integer
|
||||
favicon:
|
||||
@@ -873,9 +839,6 @@ definitions:
|
||||
autoplayMedia:
|
||||
description: autoplay media files in preview
|
||||
type: boolean
|
||||
defaultMediaPlayer:
|
||||
description: disable html5 media player and use the default media player
|
||||
type: boolean
|
||||
disableHideSidebar:
|
||||
description: disable the hide sidebar preview for previews and editors
|
||||
type: boolean
|
||||
@@ -928,9 +891,6 @@ definitions:
|
||||
description: when false, the date is relative, when true, the date is an exact
|
||||
timestamp
|
||||
type: boolean
|
||||
debugOffice:
|
||||
description: debug onlyoffice editor
|
||||
type: boolean
|
||||
deleteWithoutConfirming:
|
||||
description: delete files without confirmation
|
||||
type: boolean
|
||||
@@ -941,7 +901,7 @@ definitions:
|
||||
description: deprecated
|
||||
type: string
|
||||
disablePreviewExt:
|
||||
description: space separated list of file extensions to disable preview for
|
||||
description: comma separated list of file extensions to disable preview for
|
||||
type: string
|
||||
disableQuickToggles:
|
||||
description: disable the quick toggles in the sidebar
|
||||
@@ -955,7 +915,7 @@ definitions:
|
||||
description: disable update notifications
|
||||
type: boolean
|
||||
disableViewingExt:
|
||||
description: space separated list of file extensions to disable viewing for
|
||||
description: comma separated list of file extensions to disable viewing for
|
||||
type: string
|
||||
editorQuickSave:
|
||||
description: show quick save button in editor
|
||||
@@ -1594,61 +1554,6 @@ paths:
|
||||
summary: User login
|
||||
tags:
|
||||
- Auth
|
||||
/api/media/subtitles:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Extracts embedded subtitle content from video files by stream index
|
||||
and returns raw WebVTT content
|
||||
parameters:
|
||||
- description: Index path to the video file
|
||||
in: query
|
||||
name: path
|
||||
required: true
|
||||
type: string
|
||||
- description: Source name for the desired source
|
||||
in: query
|
||||
name: source
|
||||
required: true
|
||||
type: string
|
||||
- description: Stream index for embedded subtitle extraction, defaults to 0
|
||||
in: query
|
||||
name: index
|
||||
type: integer
|
||||
produces:
|
||||
- text/vtt
|
||||
responses:
|
||||
"200":
|
||||
description: Raw WebVTT subtitle content
|
||||
schema:
|
||||
type: string
|
||||
"400":
|
||||
description: Bad request
|
||||
schema:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
"403":
|
||||
description: Forbidden
|
||||
schema:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
"404":
|
||||
description: Resource not found
|
||||
schema:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
"500":
|
||||
description: Internal server error
|
||||
schema:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
summary: Extract embedded subtitles
|
||||
tags:
|
||||
- Subtitles
|
||||
/api/preview:
|
||||
get:
|
||||
consumes:
|
||||
@@ -2172,31 +2077,6 @@ paths:
|
||||
summary: Get system settings
|
||||
tags:
|
||||
- Settings
|
||||
/api/settings/config:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Returns the current running configuration settings as YAML format
|
||||
with optional comments and filtering.
|
||||
parameters:
|
||||
- description: Show all values (true) or only non-default values (false, default)
|
||||
in: query
|
||||
name: full
|
||||
type: boolean
|
||||
- description: Include comments in YAML (true) or plain YAML (false, default)
|
||||
in: query
|
||||
name: comments
|
||||
type: boolean
|
||||
produces:
|
||||
- text/plain
|
||||
responses:
|
||||
"200":
|
||||
description: System settings in YAML format
|
||||
schema:
|
||||
type: string
|
||||
summary: Get system settings as YAML
|
||||
tags:
|
||||
- Settings
|
||||
/api/share:
|
||||
get:
|
||||
consumes:
|
||||
|
||||
@@ -0,0 +1,740 @@
|
||||
diff --git a/CHANGELOG.md b/CHANGELOG.md
|
||||
index 92478a24..5085e058 100644
|
||||
--- a/CHANGELOG.md
|
||||
+++ b/CHANGELOG.md
|
||||
@@ -11,17 +11,16 @@ All notable changes to this project will be documented in this file. For commit
|
||||
|
||||
**New Features**:
|
||||
- New access control system that works like shares. You can add new allow / deny rules for users/groups for specific paths on specific sources.
|
||||
- #- The concept of 'groups', either automatically from OIDC groups claim or defined https://github.com/gtsteffaniak/filebrowser/issues/545
|
||||
+ - The concept of 'groups', either automatically from OIDC groups claim or defined https://github.com/gtsteffaniak/filebrowser/issues/545
|
||||
- share view changes -- now aligns with the standard listing view. This means files can be viewed and edited (if permission allows) just like a normal listing.
|
||||
- many share links customization enhancements
|
||||
- #- only share to certain authenticated users https://github.com/gtsteffaniak/filebrowser/issues/656 https://github.com/gtsteffaniak/filebrowser/issues/985
|
||||
- #- one-time download links
|
||||
- #- customize share theme, banner image, and icons https://github.com/gtsteffaniak/filebrowser/issues/827 https://github.com/gtsteffaniak/filebrowser/issues/1029
|
||||
- #- share link info customization https://github.com/gtsteffaniak/filebrowser/issues/792 https://github.com/gtsteffaniak/filebrowser/issues/841 https://github.com/gtsteffaniak/filebrowser/issues/520
|
||||
- #- "shared with me" and "share history" https://github.com/gtsteffaniak/filebrowser/issues/943
|
||||
+ - only share to certain authenticated users https://github.com/gtsteffaniak/filebrowser/issues/656 https://github.com/gtsteffaniak/filebrowser/issues/985
|
||||
+ - one-time download links
|
||||
+ - customize share theme, banner image, and icons https://github.com/gtsteffaniak/filebrowser/issues/827 https://github.com/gtsteffaniak/filebrowser/issues/1029
|
||||
+ - share link info customization https://github.com/gtsteffaniak/filebrowser/issues/792 https://github.com/gtsteffaniak/filebrowser/issues/841 https://github.com/gtsteffaniak/filebrowser/issues/520
|
||||
+ - "shared with me" and "share history" https://github.com/gtsteffaniak/filebrowser/issues/943
|
||||
- upload to share links https://github.com/gtsteffaniak/filebrowser/issues/661
|
||||
- share link public changes https://github.com/gtsteffaniak/filebrowser/issues/473
|
||||
- -
|
||||
- public/private folder logic https://github.com/gtsteffaniak/filebrowser/issues/505
|
||||
|
||||
**BugFixes**:
|
||||
diff --git a/backend/http/middleware.go b/backend/http/middleware.go
|
||||
index 5ba397fe..595f00e1 100644
|
||||
--- a/backend/http/middleware.go
|
||||
+++ b/backend/http/middleware.go
|
||||
@@ -6,7 +6,6 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
- "net/url"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -44,14 +43,8 @@ type handleFunc func(w http.ResponseWriter, r *http.Request, data *requestContex
|
||||
// Middleware to handle file requests by hash and pass it to the handler
|
||||
func withHashFileHelper(fn handleFunc) handleFunc {
|
||||
return withOrWithoutUserHelper(func(w http.ResponseWriter, r *http.Request, data *requestContext) (int, error) {
|
||||
+ path := r.URL.Query().Get("path")
|
||||
hash := r.URL.Query().Get("hash")
|
||||
- encodedPath := r.URL.Query().Get("path")
|
||||
- // Decode the URL-encoded path
|
||||
- path, err := url.QueryUnescape(encodedPath)
|
||||
- if err != nil {
|
||||
- return http.StatusBadRequest, fmt.Errorf("invalid path encoding: %v", err)
|
||||
- }
|
||||
- data.path = path
|
||||
// Get the file link by hash
|
||||
link, err := store.Share.GetByHash(hash)
|
||||
if err != nil {
|
||||
@@ -71,6 +64,11 @@ func withHashFileHelper(fn handleFunc) handleFunc {
|
||||
return status, fmt.Errorf("could not authenticate share request")
|
||||
}
|
||||
}
|
||||
+ data.path = strings.TrimSuffix(link.Path, "/") + "/" + strings.TrimPrefix(path, "/")
|
||||
+ if path == "" || path == "/" {
|
||||
+ data.path = link.Path
|
||||
+ }
|
||||
+
|
||||
source, ok := config.Server.SourceMap[link.Source]
|
||||
if !ok {
|
||||
return http.StatusNotFound, fmt.Errorf("source not found")
|
||||
diff --git a/backend/http/middleware_test.go b/backend/http/middleware_test.go
|
||||
index ff7c173e..eb257951 100644
|
||||
--- a/backend/http/middleware_test.go
|
||||
+++ b/backend/http/middleware_test.go
|
||||
@@ -155,20 +155,6 @@ func TestPublicShareHandlerAuthentication(t *testing.T) {
|
||||
},
|
||||
expectedStatusCode: 0, // zero means 200 on helpers
|
||||
},
|
||||
- {
|
||||
- name: "Private share, valid password when token exists",
|
||||
- share: &share.Link{
|
||||
- Hash: "pw_and_token_hash",
|
||||
- UserID: 1,
|
||||
- PasswordHash: passwordBcrypt,
|
||||
- Token: "some_random_token",
|
||||
- Source: "/srv",
|
||||
- },
|
||||
- extraHeaders: map[string]string{
|
||||
- "X-SHARE-PASSWORD": "password",
|
||||
- },
|
||||
- expectedStatusCode: 0, // zero means 200 on helpers
|
||||
- },
|
||||
{
|
||||
name: "Private share, no auth provided",
|
||||
share: &share.Link{
|
||||
diff --git a/backend/http/public.go b/backend/http/public.go
|
||||
index ef65dfa6..0e641fbd 100644
|
||||
--- a/backend/http/public.go
|
||||
+++ b/backend/http/public.go
|
||||
@@ -7,6 +7,9 @@ import (
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
+ "github.com/gtsteffaniak/filebrowser/backend/adapters/fs/files"
|
||||
+ "github.com/gtsteffaniak/filebrowser/backend/common/utils"
|
||||
+ "github.com/gtsteffaniak/filebrowser/backend/indexing/iteminfo"
|
||||
"github.com/gtsteffaniak/go-logger/logger"
|
||||
|
||||
_ "github.com/gtsteffaniak/filebrowser/backend/swagger/docs"
|
||||
@@ -49,7 +52,7 @@ func publicRawHandler(w http.ResponseWriter, r *http.Request, d *requestContext)
|
||||
}
|
||||
|
||||
func publicShareHandler(w http.ResponseWriter, r *http.Request, d *requestContext) (int, error) {
|
||||
- d.fileInfo.Path = "/" + strings.TrimPrefix(d.fileInfo.Path, d.share.Path)
|
||||
+ d.fileInfo.Path = strings.TrimPrefix(d.fileInfo.Path, d.share.Path)
|
||||
return renderJSON(w, r, d.fileInfo)
|
||||
}
|
||||
|
||||
@@ -91,5 +94,24 @@ func publicPreviewHandler(w http.ResponseWriter, r *http.Request, d *requestCont
|
||||
if config.Server.DisablePreviews {
|
||||
return http.StatusNotImplemented, fmt.Errorf("preview is disabled")
|
||||
}
|
||||
+ path := r.URL.Query().Get("path")
|
||||
+ var err error
|
||||
+ if path == "" {
|
||||
+ return http.StatusBadRequest, fmt.Errorf("invalid request path")
|
||||
+ }
|
||||
+ fileInfo, err := files.FileInfoFaster(iteminfo.FileOptions{
|
||||
+ Path: utils.JoinPathAsUnix(d.share.Path, path),
|
||||
+ Modify: false, // TODO make this configurable
|
||||
+ Source: d.fileInfo.Source,
|
||||
+ Expand: true,
|
||||
+ })
|
||||
+ if err != nil {
|
||||
+ logger.Debugf("public preview handler: error getting file info: %v", err)
|
||||
+ return 400, fmt.Errorf("file not found")
|
||||
+ }
|
||||
+ if fileInfo.Type == "directory" {
|
||||
+ return http.StatusBadRequest, fmt.Errorf("can't create preview for directory")
|
||||
+ }
|
||||
+ d.fileInfo = fileInfo
|
||||
return previewHelperFunc(w, r, d)
|
||||
}
|
||||
diff --git a/frontend/src/api/index.js b/frontend/src/api/index.js
|
||||
index 9399a292..2ef5f38e 100644
|
||||
--- a/frontend/src/api/index.js
|
||||
+++ b/frontend/src/api/index.js
|
||||
@@ -7,3 +7,6 @@ import search from "./search";
|
||||
|
||||
// Note: shareApi has been consolidated into publicApi
|
||||
export { filesApi, publicApi, usersApi, settingsApi, search, accessApi };
|
||||
+
|
||||
+// For backward compatibility, export publicApi as shareApi as well
|
||||
+export { publicApi as shareApi };
|
||||
diff --git a/frontend/src/api/public.js b/frontend/src/api/public.js
|
||||
index 1dcb4125..d94713c5 100644
|
||||
--- a/frontend/src/api/public.js
|
||||
+++ b/frontend/src/api/public.js
|
||||
@@ -2,7 +2,6 @@ import { fetchURL, fetchJSON, adjustedData } from "./utils";
|
||||
import { notify } from "@/notify";
|
||||
import { getApiPath, removePrefix } from "@/utils/url.js";
|
||||
import { externalUrl, baseURL } from "@/utils/constants";
|
||||
-import { state } from "@/store";
|
||||
|
||||
// ============================================================================
|
||||
// PUBLIC API ENDPOINTS (hash-based authentication)
|
||||
@@ -13,8 +12,7 @@ export async function fetchPub(path, hash, password = "", content = false) {
|
||||
const params = {
|
||||
path,
|
||||
hash,
|
||||
- ...(content && { content: 'true' }),
|
||||
- ...(state.share.token && { token: state.share.token })
|
||||
+ ...(content && { content: 'true' })
|
||||
}
|
||||
const apiPath = getApiPath("public/api/shared", params);
|
||||
const response = await fetch(apiPath, {
|
||||
@@ -24,17 +22,7 @@ export async function fetchPub(path, hash, password = "", content = false) {
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
- const error = new Error(response.statusText);
|
||||
- // attempt to marshal json response
|
||||
- let data = null;
|
||||
- try {
|
||||
- data = await response.json()
|
||||
- } catch (e) {
|
||||
- // ignore
|
||||
- }
|
||||
- if (data) {
|
||||
- error.message = data.message;
|
||||
- }
|
||||
+ const error = new Error("Failed to connect to the server.");
|
||||
error.status = response.status;
|
||||
throw error;
|
||||
}
|
||||
@@ -57,14 +45,13 @@ export function getDownloadURL(share, files) {
|
||||
}
|
||||
|
||||
// Generate a preview URL for public shares
|
||||
-export function getPreviewURL(path) {
|
||||
+export function getPreviewURL(hash, path) {
|
||||
try {
|
||||
const params = {
|
||||
path: encodeURIComponent(path),
|
||||
size: 'small',
|
||||
- hash: state.share.hash,
|
||||
- inline: 'true',
|
||||
- ...(state.share.token && { token: state.share.token })
|
||||
+ hash: hash,
|
||||
+ inline: 'true'
|
||||
}
|
||||
const apiPath = getApiPath('public/api/preview', params)
|
||||
return window.origin + apiPath
|
||||
diff --git a/frontend/src/api/share.js b/frontend/src/api/share.js
|
||||
new file mode 100644
|
||||
index 00000000..729b7599
|
||||
--- /dev/null
|
||||
+++ b/frontend/src/api/share.js
|
||||
@@ -0,0 +1,66 @@
|
||||
+import { fetchURL, fetchJSON, adjustedData } from "./utils";
|
||||
+import { notify } from "@/notify";
|
||||
+import { getApiPath } from "@/utils/url.js";
|
||||
+import { externalUrl } from "@/utils/constants";
|
||||
+
|
||||
+export async function list() {
|
||||
+ const apiPath = getApiPath("api/shares");
|
||||
+ return fetchJSON(apiPath);
|
||||
+}
|
||||
+
|
||||
+export async function get(path, source) {
|
||||
+ try {
|
||||
+ const params = { path: encodeURIComponent(path), source: encodeURIComponent(source) };
|
||||
+ const apiPath = getApiPath("api/share",params);
|
||||
+ let data = fetchJSON(apiPath);
|
||||
+ return adjustedData(data, path);
|
||||
+ } catch (err) {
|
||||
+ notify.showError(err.message || "Error fetching data");
|
||||
+ throw err;
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
+export async function remove(hash) {
|
||||
+ const params = { hash };
|
||||
+ const apiPath = getApiPath("api/share",params);
|
||||
+ await fetchURL(apiPath, {
|
||||
+ method: "DELETE",
|
||||
+ });
|
||||
+}
|
||||
+
|
||||
+export async function create(path, source, password = "", expires = "", unit = "hours") {
|
||||
+ const params = { path: encodeURIComponent(path), source: encodeURIComponent(source) };
|
||||
+ const apiPath = getApiPath("api/share",params);
|
||||
+ let body = "{}";
|
||||
+ if (password != "" || expires !== "" || unit !== "hours") {
|
||||
+ body = JSON.stringify({ password: password, expires: expires, unit: unit });
|
||||
+ }
|
||||
+ return fetchJSON(apiPath, {
|
||||
+ method: "POST",
|
||||
+ body: body,
|
||||
+ });
|
||||
+}
|
||||
+
|
||||
+export function getShareURL(share) {
|
||||
+ if (externalUrl) {
|
||||
+ const apiPath = getApiPath(`share/${share.hash}`)
|
||||
+ return externalUrl + apiPath
|
||||
+ }
|
||||
+ return window.origin+getApiPath(`share/${share.hash}`);
|
||||
+}
|
||||
+
|
||||
+export function getPreviewURL(hash, path) {
|
||||
+ try {
|
||||
+ const params = {
|
||||
+ path: encodeURIComponent(path),
|
||||
+ size: 'small',
|
||||
+ hash: hash,
|
||||
+ inline: 'true'
|
||||
+ }
|
||||
+ const apiPath = getApiPath('api/public/preview', params)
|
||||
+ return window.origin + apiPath
|
||||
+ } catch (err) {
|
||||
+ notify.showError(err.message || 'Error getting preview URL')
|
||||
+ throw err
|
||||
+ }
|
||||
+}
|
||||
diff --git a/frontend/src/components/files/ListingItem.vue b/frontend/src/components/files/ListingItem.vue
|
||||
index 29c2749f..93848c1c 100644
|
||||
--- a/frontend/src/components/files/ListingItem.vue
|
||||
+++ b/frontend/src/components/files/ListingItem.vue
|
||||
@@ -67,7 +67,7 @@ import { enableThumbs } from "@/utils/constants";
|
||||
import downloadFiles from "@/utils/download";
|
||||
|
||||
import { getHumanReadableFilesize } from "@/utils/filesizes";
|
||||
-import { filesApi,publicApi } from "@/api";
|
||||
+import { filesApi, shareApi } from "@/api";
|
||||
import * as upload from "@/utils/upload";
|
||||
import { state, getters, mutations } from "@/store"; // Import your custom store
|
||||
import { url } from "@/utils";
|
||||
@@ -162,8 +162,8 @@ export default {
|
||||
// @ts-ignore
|
||||
let path = url.removeTrailingSlash(state.req.path) + "/" + this.name;
|
||||
if (getters.isShare()) {
|
||||
- let urlPath = getters.getSharePath(this.name) ;
|
||||
- return publicApi.getPreviewURL(urlPath);
|
||||
+ let urlPath = getters.getSharePath() + "/" + this.name;
|
||||
+ return shareApi.getPreviewURL(state.share.hash, urlPath, state.req.modified);
|
||||
}
|
||||
// @ts-ignore
|
||||
return filesApi.getPreviewURL(state.req.source, path, this.modified);
|
||||
@@ -226,7 +226,7 @@ export default {
|
||||
},
|
||||
getUrl() {
|
||||
if (this.hash) {
|
||||
- return baseURL + "public/share/" + this.hash + "/" + url.encodedPath(this.path);
|
||||
+ return baseURL + "share/" + this.hash + url.encodedPath(this.path);
|
||||
}
|
||||
if (serverHasMultipleSources) {
|
||||
return baseURL + "files/" + encodeURIComponent(this.source) + url.encodedPath(this.path);
|
||||
diff --git a/frontend/src/components/prompts/ActionApi.vue b/frontend/src/components/prompts/ActionApi.vue
|
||||
index f2ebaa3f..1a381565 100644
|
||||
--- a/frontend/src/components/prompts/ActionApi.vue
|
||||
+++ b/frontend/src/components/prompts/ActionApi.vue
|
||||
@@ -90,3 +90,20 @@ export default {
|
||||
},
|
||||
};
|
||||
</script>
|
||||
+
|
||||
+<style>
|
||||
+/* Basic styling for the prompt */
|
||||
+.card.floating {
|
||||
+ padding: 20px;
|
||||
+}
|
||||
+.card-content {
|
||||
+ margin-bottom: 16px;
|
||||
+}
|
||||
+.card-action {
|
||||
+ display: flex;
|
||||
+ justify-content: flex-end;
|
||||
+}
|
||||
+.card-action .button {
|
||||
+ margin-left: 8px;
|
||||
+}
|
||||
+</style>
|
||||
diff --git a/frontend/src/components/prompts/Share.vue b/frontend/src/components/prompts/Share.vue
|
||||
index be1ad5ad..8689100d 100644
|
||||
--- a/frontend/src/components/prompts/Share.vue
|
||||
+++ b/frontend/src/components/prompts/Share.vue
|
||||
@@ -2,11 +2,10 @@
|
||||
<div class="card-title">
|
||||
<h2>{{ $t("buttons.share") }}</h2>
|
||||
</div>
|
||||
- <div class="card-content">
|
||||
- <div aria-label="share-path" class="searchContext"> {{ $t('search.path') }} {{ subpath }}</div>
|
||||
- <p> {{ $t('share.notice') }} </p>
|
||||
-
|
||||
- <div v-if="listing">
|
||||
+ <div aria-label="share-path" class="searchContext"> {{ $t('search.path') }} {{ subpath }}</div>
|
||||
+ <p> {{ $t('share.notice') }} </p>
|
||||
+ <template v-if="listing">
|
||||
+ <div class="card-content">
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
@@ -46,7 +45,20 @@
|
||||
</table>
|
||||
</div>
|
||||
|
||||
- <div v-else>
|
||||
+ <div class="card-action">
|
||||
+ <button class="button button--flat button--grey" @click="closeHovers" :aria-label="$t('buttons.close')"
|
||||
+ :title="$t('buttons.close')">
|
||||
+ {{ $t("buttons.close") }}
|
||||
+ </button>
|
||||
+ <button class="button button--flat button--blue" @click="() => switchListing()" :aria-label="$t('buttons.new')"
|
||||
+ :title="$t('buttons.new')">
|
||||
+ {{ $t("buttons.new") }}
|
||||
+ </button>
|
||||
+ </div>
|
||||
+ </template>
|
||||
+
|
||||
+ <template v-else>
|
||||
+ <div class="card-content">
|
||||
<p>{{ $t("settings.shareDuration") }}</p>
|
||||
<div class="form-flex-group">
|
||||
<input class="form-grow input flat-right" v-focus type="number" max="2147483647" min="1" @keyup.enter="submit" v-model.trim="time" />
|
||||
@@ -58,7 +70,7 @@
|
||||
</select>
|
||||
</div>
|
||||
<p>{{ $t("prompts.optionalPassword") }}</p>
|
||||
- <input class="input" type="password" autocomplete="off" v-model.trim="password" />
|
||||
+ <input class="input" type="password" v-model.trim="password" />
|
||||
|
||||
<div class="settings-items">
|
||||
<ToggleSwitch class="item" v-model="readOnly" :name="'Read-only access'" />
|
||||
@@ -80,32 +92,23 @@
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
- </div>
|
||||
|
||||
- <div class="card-action">
|
||||
- <button v-if="listing" class="button button--flat button--grey" @click="closeHovers" :aria-label="$t('buttons.close')"
|
||||
- :title="$t('buttons.close')">
|
||||
- {{ $t("buttons.close") }}
|
||||
- </button>
|
||||
- <button v-if="listing" class="button button--flat button--blue" @click="() => switchListing()" :aria-label="$t('buttons.new')"
|
||||
- :title="$t('buttons.new')">
|
||||
- {{ $t("buttons.new") }}
|
||||
- </button>
|
||||
-
|
||||
- <button v-if="!listing" class="button button--flat button--grey" @click="() => switchListing()" :aria-label="$t('buttons.cancel')"
|
||||
- :title="$t('buttons.cancel')">
|
||||
- {{ $t("buttons.cancel") }}
|
||||
- </button>
|
||||
- <button v-if="!listing" class="button button--flat button--blue" @click="submit" aria-label="Share-Confirm"
|
||||
- :title="$t('buttons.share')">
|
||||
- {{ $t("buttons.share") }}
|
||||
- </button>
|
||||
- </div>
|
||||
+ <div class="card-action">
|
||||
+ <button class="button button--flat button--grey" @click="() => switchListing()" :aria-label="$t('buttons.cancel')"
|
||||
+ :title="$t('buttons.cancel')">
|
||||
+ {{ $t("buttons.cancel") }}
|
||||
+ </button>
|
||||
+ <button class="button button--flat button--blue" @click="submit" aria-label="Share-Confirm"
|
||||
+ :title="$t('buttons.share')">
|
||||
+ {{ $t("buttons.share") }}
|
||||
+ </button>
|
||||
+ </div>
|
||||
+ </template>
|
||||
</template>
|
||||
<script>
|
||||
import { notify } from "@/notify";
|
||||
import { state, getters, mutations } from "@/store";
|
||||
-import { publicApi } from "@/api";
|
||||
+import { shareApi, publicApi } from "@/api";
|
||||
import Clipboard from "clipboard";
|
||||
import { fromNow } from "@/utils/moment";
|
||||
import { buildItemUrl } from "@/utils/url";
|
||||
@@ -188,7 +191,7 @@ export default {
|
||||
|
||||
try {
|
||||
// get last element of the path
|
||||
- const links = await publicApi.get(this.subpath, this.source);
|
||||
+ const links = await shareApi.get(this.subpath, this.source);
|
||||
this.links = links;
|
||||
} catch (err) {
|
||||
notify.showError(err);
|
||||
@@ -215,9 +218,9 @@ export default {
|
||||
let isPermanent = !this.time || this.time == 0;
|
||||
let res = null;
|
||||
if (isPermanent) {
|
||||
- res = await publicApi.create(this.subpath, this.source, this.password);
|
||||
+ res = await shareApi.create(this.subpath, this.source, this.password);
|
||||
} else {
|
||||
- res = await publicApi.create(
|
||||
+ res = await shareApi.create(
|
||||
this.subpath,
|
||||
this.source,
|
||||
this.password,
|
||||
@@ -241,7 +244,7 @@ export default {
|
||||
async deleteLink(event, link) {
|
||||
event.preventDefault();
|
||||
try {
|
||||
- await publicApi.remove(link.hash);
|
||||
+ await shareApi.remove(link.hash);
|
||||
this.links = this.links.filter((item) => item.hash !== link.hash);
|
||||
if (this.links.length === 0) {
|
||||
this.listing = false;
|
||||
@@ -254,7 +257,7 @@ export default {
|
||||
return fromNow(time, state.user.locale)
|
||||
},
|
||||
buildLink(share) {
|
||||
- return publicApi.getShareURL(share);
|
||||
+ return shareApi.getShareURL(share);
|
||||
},
|
||||
hasDownloadLink() {
|
||||
if (state.isSearchActive) {
|
||||
diff --git a/frontend/src/components/sidebar/General.vue b/frontend/src/components/sidebar/General.vue
|
||||
index 58f02c10..0e030aeb 100644
|
||||
--- a/frontend/src/components/sidebar/General.vue
|
||||
+++ b/frontend/src/components/sidebar/General.vue
|
||||
@@ -159,7 +159,7 @@ export default {
|
||||
isStickySidebar: () => getters.isStickySidebar(),
|
||||
isMobile: () => getters.isMobile(),
|
||||
isListingView: () => getters.currentView() == "listingView",
|
||||
- user: () => (state.user),
|
||||
+ user: () => (console.log(state.user), state.user),
|
||||
isDarkMode: () => getters.isDarkMode(),
|
||||
showSources: () => !getters.isShare(),
|
||||
currentPrompt: () => getters.currentPrompt(),
|
||||
diff --git a/frontend/src/css/dashboard.css b/frontend/src/css/dashboard.css
|
||||
index 2e1fbc8f..77da5938 100644
|
||||
--- a/frontend/src/css/dashboard.css
|
||||
+++ b/frontend/src/css/dashboard.css
|
||||
@@ -226,10 +226,6 @@ body.rtl .card-title>*:first-child {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
-.card-content h3 {
|
||||
- text-align: center;
|
||||
-}
|
||||
-
|
||||
.card h2 {
|
||||
font-weight: 500;
|
||||
}
|
||||
diff --git a/frontend/src/store/getters.js b/frontend/src/store/getters.js
|
||||
index cb062702..d97e7eed 100644
|
||||
--- a/frontend/src/store/getters.js
|
||||
+++ b/frontend/src/store/getters.js
|
||||
@@ -201,9 +201,10 @@ export const getters = {
|
||||
sharePathBase: () => {
|
||||
return '/public/share/' + getters.shareHash() + '/'
|
||||
},
|
||||
- getSharePath: (subPath = "") => {
|
||||
+ getSharePath: () => {
|
||||
let urlPath = getters.routePath('public/share')
|
||||
- return "/" + removeLeadingSlash(urlPath.split(state.share.hash)[1]) + "/" + subPath
|
||||
+ let parts = urlPath.split('/')
|
||||
+ return "/" + removeLeadingSlash(parts.slice(2).join('/'))
|
||||
},
|
||||
currentView: () => {
|
||||
let listingView = ''
|
||||
@@ -288,6 +289,7 @@ export const getters = {
|
||||
},
|
||||
|
||||
currentPromptName: () => {
|
||||
+ console.log("currentPromptName", state.prompts);
|
||||
// Ensure state.prompts is an array
|
||||
if (!Array.isArray(state.prompts) || state.prompts.length === 0) {
|
||||
return ""
|
||||
diff --git a/frontend/src/store/mutations.js b/frontend/src/store/mutations.js
|
||||
index 8dc0b19f..b2b13461 100644
|
||||
--- a/frontend/src/store/mutations.js
|
||||
+++ b/frontend/src/store/mutations.js
|
||||
@@ -191,6 +191,7 @@ export const mutations = {
|
||||
emitStateChanged();
|
||||
},
|
||||
showHover: (value) => {
|
||||
+ console.log("showHover", value);
|
||||
if (typeof value === "object") {
|
||||
state.prompts.push({
|
||||
name: value?.name,
|
||||
@@ -206,6 +207,7 @@ export const mutations = {
|
||||
props: value?.props,
|
||||
});
|
||||
}
|
||||
+ console.log("showHover", state.prompts);
|
||||
emitStateChanged();
|
||||
},
|
||||
setLoading: (loadType, status) => {
|
||||
@@ -221,6 +223,7 @@ export const mutations = {
|
||||
emitStateChanged();
|
||||
},
|
||||
setCurrentUser: (value) => {
|
||||
+ console.log('setCurrentUser', value);
|
||||
try {
|
||||
// If value is null or undefined, emit state change and exit early
|
||||
if (!value) {
|
||||
@@ -243,6 +246,7 @@ export const mutations = {
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
+ console.log('setCurrentUser', state.user);
|
||||
emitStateChanged();
|
||||
},
|
||||
setJWT: (value) => {
|
||||
@@ -303,6 +307,7 @@ export const mutations = {
|
||||
if (!state.user) {
|
||||
state.user = {};
|
||||
}
|
||||
+ console.log('updateCurrentUser', value);
|
||||
// Store previous state for comparison
|
||||
const previousUser = { ...state.user };
|
||||
|
||||
@@ -315,12 +320,14 @@ export const mutations = {
|
||||
i18n.default.locale = state.user.locale;
|
||||
localStorage.setItem("userLocale", state.user.locale);
|
||||
}
|
||||
+ console.log('updateCurrentUser2', state.user);
|
||||
// Update localStorage if stickySidebar exists
|
||||
if (state.user.stickySidebar && getters.currentView() == "listingView") {
|
||||
state.multiButtonState = "menu";
|
||||
} else if (state.showSidebar) {
|
||||
state.multiButtonState = "back";
|
||||
}
|
||||
+ console.log('updateCurrentUser3', state.user);
|
||||
// Update users if there's any change in state.user
|
||||
if (JSON.stringify(state.user) !== JSON.stringify(previousUser)) {
|
||||
// Only update the properties that were actually provided in the input
|
||||
diff --git a/frontend/src/views/Files.vue b/frontend/src/views/Files.vue
|
||||
index 4c69ecd7..6f8b892b 100644
|
||||
--- a/frontend/src/views/Files.vue
|
||||
+++ b/frontend/src/views/Files.vue
|
||||
@@ -9,9 +9,8 @@
|
||||
<div class="card-title">
|
||||
<h2>{{ $t("general.password") }}</h2>
|
||||
</div>
|
||||
- <div class="card-content form-flex-group">
|
||||
+ <div class="card-content">
|
||||
<input
|
||||
- class="input share-password"
|
||||
v-focus
|
||||
type="password"
|
||||
:placeholder="$t('general.password')"
|
||||
@@ -149,10 +148,8 @@ export default {
|
||||
});
|
||||
// Add glow effect
|
||||
element.classList.add('scroll-glow');
|
||||
- console.log("scrollToHash", element)
|
||||
// Remove glow effect after animation completes
|
||||
setTimeout(() => {
|
||||
- console.log("scrollToHash remove glow", element)
|
||||
element.classList.remove('scroll-glow');
|
||||
}, 1000);
|
||||
}
|
||||
@@ -211,15 +208,19 @@ export default {
|
||||
this.shareHash = parts[1]
|
||||
this.shareSubPath = "/" + parts.slice(2).join("/");
|
||||
|
||||
+ // Store share data in state for use by components
|
||||
+ mutations.setShareData({
|
||||
+ hash: this.shareHash,
|
||||
+ token: this.shareToken,
|
||||
+ subPath: this.shareSubPath,
|
||||
+ });
|
||||
+
|
||||
// Handle password
|
||||
- if (this.sharePassword === "") {
|
||||
+ if (this.sharePassword === "" || this.sharePassword === null) {
|
||||
this.sharePassword = localStorage.getItem("sharepass:" + this.shareHash);
|
||||
} else {
|
||||
localStorage.setItem("sharepass:" + this.shareHash, this.sharePassword);
|
||||
}
|
||||
- if (this.sharePassword === null) {
|
||||
- this.sharePassword = "";
|
||||
- }
|
||||
|
||||
mutations.resetSelected();
|
||||
mutations.setMultiple(false);
|
||||
@@ -229,15 +230,11 @@ export default {
|
||||
let file = await publicApi.fetchPub(this.shareSubPath, this.shareHash, this.sharePassword);
|
||||
file.hash = this.shareHash;
|
||||
this.shareToken = file.token;
|
||||
- // Store share data in state for use by components
|
||||
- mutations.setShareData({
|
||||
- hash: this.shareHash,
|
||||
- token: this.shareToken,
|
||||
- subPath: this.shareSubPath,
|
||||
- });
|
||||
+
|
||||
// If not a directory, fetch content for preview components
|
||||
if (file.type != "directory") {
|
||||
const content = !getters.fileViewingDisabled(file.name);
|
||||
+
|
||||
file = await publicApi.fetchPub(this.shareSubPath, this.shareHash, this.sharePassword, content);
|
||||
file.hash = this.shareHash;
|
||||
this.shareToken = file.token;
|
||||
@@ -324,7 +321,7 @@ export default {
|
||||
};
|
||||
</script>
|
||||
|
||||
-<style>
|
||||
+<style scoped>
|
||||
.share__wrong__password {
|
||||
color: #ff4757;
|
||||
text-align: center;
|
||||
@@ -351,8 +348,4 @@ export default {
|
||||
color: inherit;
|
||||
}
|
||||
}
|
||||
-
|
||||
-.share-password {
|
||||
- width: 100%;
|
||||
-}
|
||||
</style>
|
||||
diff --git a/frontend/src/views/Settings.vue b/frontend/src/views/Settings.vue
|
||||
index 4547e3d2..38d75dc1 100644
|
||||
--- a/frontend/src/views/Settings.vue
|
||||
+++ b/frontend/src/views/Settings.vue
|
||||
@@ -122,4 +122,7 @@ export default {
|
||||
background-color: var(--surfaceSecondary);
|
||||
}
|
||||
|
||||
+.card-content {
|
||||
+ padding: 1em;
|
||||
+}
|
||||
</style>
|
||||
diff --git a/frontend/src/views/settings/Profile.vue b/frontend/src/views/settings/Profile.vue
|
||||
index f65fe30f..e34e405a 100644
|
||||
--- a/frontend/src/views/settings/Profile.vue
|
||||
+++ b/frontend/src/views/settings/Profile.vue
|
||||
@@ -428,6 +428,9 @@ export default {
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
+.card-content h3 {
|
||||
+ text-align: center;
|
||||
+}
|
||||
|
||||
#disablePreviews,
|
||||
#disableViewing,
|
||||
diff --git a/frontend/src/views/settings/Shares.vue b/frontend/src/views/settings/Shares.vue
|
||||
index cd28af70..6d39166e 100644
|
||||
--- a/frontend/src/views/settings/Shares.vue
|
||||
+++ b/frontend/src/views/settings/Shares.vue
|
||||
@@ -49,7 +49,7 @@
|
||||
|
||||
<script>
|
||||
import { notify } from "@/notify";
|
||||
-import { publicApi } from "@/api";
|
||||
+import { shareApi } from "@/api";
|
||||
import { state, mutations, getters } from "@/store";
|
||||
import Clipboard from "clipboard";
|
||||
import Errors from "@/views/Errors.vue";
|
||||
@@ -70,7 +70,7 @@ export default {
|
||||
async created() {
|
||||
mutations.setLoading("shares", true);
|
||||
try {
|
||||
- let links = await publicApi.list();
|
||||
+ let links = await shareApi.list();
|
||||
if (links.length === 0) {
|
||||
this.links = [];
|
||||
return;
|
||||
@@ -115,7 +115,7 @@ export default {
|
||||
mutations.closeHovers();
|
||||
|
||||
try {
|
||||
- publicApi.remove(link.hash);
|
||||
+ shareApi.remove(link.hash);
|
||||
this.links = this.links.filter((item) => item.hash !== link.hash);
|
||||
notify.showSuccess(this.$t("settings.shareDeleted"));
|
||||
} catch (e) {
|
||||
@@ -128,7 +128,7 @@ export default {
|
||||
return fromNow(time);
|
||||
},
|
||||
buildLink(share) {
|
||||
- return publicApi.getShareURL(share);
|
||||
+ return shareApi.getShareURL(share);
|
||||
},
|
||||
},
|
||||
};
|
||||
Symlink
+1
@@ -0,0 +1 @@
|
||||
frontend
|
||||
@@ -8,10 +8,11 @@
|
||||
"node": ">=18.0.0"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "vite build --outDir ../backend/http/dist && cp -r ../backend/http/dist/* ../backend/http/embed",
|
||||
"build-windows": "vite build --outDir ../backend/http/dist && robocopy ../backend/http/dist ../backend/http/embed /e",
|
||||
"dev": "vite dev",
|
||||
"build": "vite build && cp -r dist/* ../backend/http/embed",
|
||||
"build-windows": "vite build && robocopy dist ../backend/http/embed /e",
|
||||
"build-docker": "vite build",
|
||||
"watch": "vite build --watch --outDir ../backend/http/dist",
|
||||
"watch": "vite build --watch",
|
||||
"typecheck": "vue-tsc -p ./tsconfig.json --noEmit",
|
||||
"lint": "eslint --ext .js,.vue,ts src",
|
||||
"lint:fix": "eslint --fix src/",
|
||||
@@ -21,7 +22,6 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@onlyoffice/document-editor-vue": "^1.4.0",
|
||||
"@skjnldsv/vue-plyr": "^7.5.0",
|
||||
"ace-builds": "^1.24.2",
|
||||
"axios": "^1.7.9",
|
||||
"clipboard": "^2.0.4",
|
||||
@@ -31,7 +31,6 @@
|
||||
"file-loader": "^6.2.0",
|
||||
"glob": "^9.3.5",
|
||||
"highlight.js": "^11.11.1",
|
||||
"jsmediatags": "github:Kurami32/jsmediatags",
|
||||
"mammoth": "^1.9.1",
|
||||
"marked": "^15.0.6",
|
||||
"normalize.css": "^8.0.1",
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
server:
|
||||
minSearchLength: 3 # minimum length of search query to begin searching (default: 3)
|
||||
disableUpdateCheck: false # disables backend update check service
|
||||
numImageProcessors: 4 # number of concurrent image processing jobs used to create previews, default is number of cpu cores available.
|
||||
socket: "" # socket to listen on
|
||||
tlsKey: "" # path to TLS key
|
||||
@@ -50,7 +48,7 @@ server:
|
||||
cacheDir: "tmp" # path to the cache directory, used for thumbnails and other cached files
|
||||
maxArchiveSize: 50 # max pre-archive combined size of files/folder that are allowed to be archived (in GB)
|
||||
auth:
|
||||
tokenExpirationHours: 2 # time in hours each web UI session token is valid for. Default is 2 hours.
|
||||
tokenExpirationHours: 2 # the number of hours until the token expires. Default is 2 hours.
|
||||
methods:
|
||||
proxy: # validate:omitempty
|
||||
enabled: false
|
||||
@@ -69,8 +67,8 @@ auth:
|
||||
enforcedOtp: false # if set to true, TOTP is enforced for all password users users. Otherwise, users can choose to enable TOTP.
|
||||
oidc: # validate:omitempty
|
||||
enabled: false # whether to enable OIDC authentication
|
||||
clientId: "**hidden**" # secret: client id of the OIDC application
|
||||
clientSecret: "**hidden**" # secret: client secret of the OIDC application
|
||||
clientId: "" # client id of the OIDC application
|
||||
clientSecret: "" # client secret of the OIDC application
|
||||
issuerUrl: "" # authorization URL of the OIDC provider
|
||||
scopes: "openid email profile" # scopes to request from the OIDC provider
|
||||
userIdentifier: "preferred_username" # the field value to use as the username. Default is "preferred_username", can also be "email" or "username", or "phone"
|
||||
@@ -79,10 +77,10 @@ auth:
|
||||
createUser: false # create user if not exists
|
||||
adminGroup: "" # if set, users in this group will be granted admin privileges.
|
||||
groupsClaim: "groups" # the JSON field name to read groups from. Default is "groups"
|
||||
key: "**hidden**" # secret: the key used to sign the JWT tokens. If not set, a random key will be generated.
|
||||
adminUsername: "**hidden**" # secret: the username of the admin user. If not set, the default is "admin".
|
||||
adminPassword: "**hidden**" # secret: the password of the admin user. If not set, the default is "admin".
|
||||
totpSecret: "**hidden**" # secret: secret used to encrypt TOTP secrets
|
||||
key: "" # the key used to sign the JWT tokens. If not set, a random key will be generated.
|
||||
adminUsername: "admin" # the username of the admin user. If not set, the default is "admin".
|
||||
adminPassword: "admin" # the password of the admin user. If not set, the default is "admin".
|
||||
totpSecret: "" # secret used to encrypt TOTP secrets
|
||||
frontend:
|
||||
name: "FileBrowser Quantum" # display name
|
||||
disableDefaultLinks: false # disable default links in the sidebar
|
||||
@@ -93,21 +91,19 @@ frontend:
|
||||
url: "https://github.com/gtsteffaniak/filebrowser/releases/" # the url to link to validate:required
|
||||
- text: "Help" # the text to display on the link validate:required
|
||||
title: "" # the title to display on hover
|
||||
url: "help prompt" # the url to link to validate:required
|
||||
url: "https://github.com/gtsteffaniak/filebrowser/wiki" # the url to link to validate:required
|
||||
disableNavButtons: false # disable the nav buttons in the sidebar
|
||||
styling:
|
||||
customCSS: "" # if a valid path to a css file is provided, it will be applied for all users. (eg. "reduce-rounded-corners.css")
|
||||
lightBackground: "#f5f5f5" # specify a valid CSS color property value to use as the background color in light mode
|
||||
darkBackground: "#141D24" # Specify a valid CSS color property value to use as the background color in dark mode
|
||||
customThemes: # A list of custom css files that each user can select to override the default styling. if "default" is key name then it will be the default option.
|
||||
default:
|
||||
description: "The default theme" # The description of the theme to display in the UI.
|
||||
css: "" # The css file path and filename to use for the theme.
|
||||
alternative:
|
||||
description: "Reduce rounded corners" # The description of the theme to display in the UI.
|
||||
css: "reduce-rounded-corners.css" # The css file path and filename to use for the theme.
|
||||
favicon: "" # path to a favicon to use for the frontend
|
||||
description: "FileBrowser Quantum is a file manager for the web which can be used to manage files on your server" # description that shows up in html head meta description
|
||||
description: Reduce rounded corners
|
||||
css: reduce-rounded-corners.css
|
||||
default:
|
||||
description: The default theme
|
||||
css: ""
|
||||
userDefaults:
|
||||
editorQuickSave: false # show quick save button in editor
|
||||
hideSidebarFileActions: false # hide the file actions in the sidebar
|
||||
@@ -123,8 +119,8 @@ userDefaults:
|
||||
gallerySize: 3 # 0-9 - the size of the gallery thumbnails
|
||||
themeColor: "var(--blue)" # theme color to use: eg. #ff0000, or var(--red), var(--purple), etc
|
||||
quickDownload: false # show icon to download in one click
|
||||
disablePreviewExt: "" # space separated list of file extensions to disable preview for
|
||||
disableViewingExt: "" # space separated list of file extensions to disable viewing for
|
||||
disablePreviewExt: "" # comma separated list of file extensions to disable preview for
|
||||
disableViewingExt: "" # comma separated list of file extensions to disable viewing for
|
||||
lockPassword: false # disable the user from changing their password
|
||||
disableSettings: false # disable the user from viewing the settings page
|
||||
preview:
|
||||
@@ -136,7 +132,6 @@ userDefaults:
|
||||
office: false
|
||||
popup: false
|
||||
autoplayMedia: false
|
||||
defaultMediaPlayer: false
|
||||
permissions:
|
||||
api: false
|
||||
admin: false
|
||||
@@ -149,14 +144,14 @@ userDefaults:
|
||||
fileLoading: # upload and download settings
|
||||
maxConcurrentUpload: 10
|
||||
uploadChunkSizeMb: 10
|
||||
disableOfficePreviewExt: "" # deprecated: use disablePreviewExt instead
|
||||
disableOnlyOfficeExt: "" # list of file extensions to disable onlyoffice editor for
|
||||
customTheme: "" # Name of theme to use chosen from custom themes config.
|
||||
showSelectMultiple: false # show select multiple files on desktop
|
||||
debugOffice: false # debug onlyoffice editor
|
||||
integrations:
|
||||
office: # validate:omitempty
|
||||
url: "" # The URL to the OnlyOffice Document Server, needs to be accessible to the user. validate:required
|
||||
internalUrl: "" # An optional internal address that the filebrowser server can use to communicate with the OnlyOffice Document Server, could be useful to bypass proxy.
|
||||
secret: "**hidden**" # secret: authentication key for OnlyOffice integration validate:required
|
||||
secret: "" # validate:required
|
||||
media: # validate:omitempty
|
||||
ffmpegPath: "" # path to ffmpeg directory with ffmpeg and ffprobe (eg. /usr/local/bin)
|
||||
@@ -50,15 +50,9 @@ async function translateText(text, targetLanguage, keyPath = '') {
|
||||
return text;
|
||||
}
|
||||
|
||||
const hasPlaceholders = /\{[^}]+\}/.test(text);
|
||||
let textToTranslate = text;
|
||||
const options = {};
|
||||
|
||||
if (hasPlaceholders) {
|
||||
console.log(`Found placeholder in: "${text}". Wrapping for translation.`);
|
||||
textToTranslate = text.replace(/(\{[^}]+\})/g, '<ph>$1</ph>');
|
||||
options.tagHandling = 'xml';
|
||||
options.ignoreTags = ['ph'];
|
||||
if (/\{[^}]+\}/.test(text)) {
|
||||
console.log(`Skipping translation for placeholder string: "${text}"`);
|
||||
return text;
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -66,19 +60,12 @@ async function translateText(text, targetLanguage, keyPath = '') {
|
||||
|
||||
let deeplTargetLang = deeplLangMap[targetLanguage.toLowerCase()] || targetLanguage.toUpperCase();
|
||||
|
||||
const result = await translator.translateText(textToTranslate, masterLanguageCode, deeplTargetLang, options);
|
||||
const result = await translator.translateText(text, masterLanguageCode, deeplTargetLang);
|
||||
|
||||
// Delay to avoid rate-limiting
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
|
||||
let translatedText = result.text;
|
||||
|
||||
if (hasPlaceholders) {
|
||||
// Unwrap the placeholders. The translator might add spaces around tags.
|
||||
translatedText = translatedText.replace(/<ph>\s*(\{[^}]+\})\s*<\/ph>/g, '$1');
|
||||
}
|
||||
|
||||
return translatedText;
|
||||
return result.text;
|
||||
|
||||
} catch (err) {
|
||||
console.error(`⚠️ Translation failed for "${text}" (${keyPath}):`, err?.message || err);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { fetchURL, adjustedData } from './utils'
|
||||
import { getApiPath, doubleEncode, getPublicApiPath } from '@/utils/url.js'
|
||||
import { getApiPath, doubleEncode } from '@/utils/url.js'
|
||||
import { mutations } from '@/store'
|
||||
import { notify } from '@/notify'
|
||||
import { externalUrl } from '@/utils/constants'
|
||||
@@ -265,17 +265,3 @@ export async function sources() {
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
export async function GetOfficeConfig(req) {
|
||||
const params = {
|
||||
path: encodeURIComponent(req.path),
|
||||
...(req.hash && { hash: encodeURIComponent(req.hash) }),
|
||||
...(req.source && { source: encodeURIComponent(req.source) })
|
||||
}
|
||||
let apiPath = getApiPath('api/onlyoffice/config', params)
|
||||
if (req.hash) {
|
||||
apiPath = getPublicApiPath('onlyoffice/config', params)
|
||||
}
|
||||
const res = await fetchURL(apiPath)
|
||||
return await res.json()
|
||||
}
|
||||
@@ -3,8 +3,7 @@ import * as publicApi from "./public";
|
||||
import * as usersApi from "./users";
|
||||
import * as settingsApi from "./settings";
|
||||
import * as accessApi from "./access";
|
||||
import * as shareApi from "./share";
|
||||
import search from "./search";
|
||||
|
||||
// Note: shareApi has been consolidated into publicApi
|
||||
export { filesApi, publicApi, usersApi, settingsApi, shareApi, search, accessApi };
|
||||
export { filesApi, publicApi, usersApi, settingsApi, search, accessApi };
|
||||
@@ -1,4 +1,4 @@
|
||||
import { adjustedData } from "./utils";
|
||||
import { fetchURL, fetchJSON, adjustedData } from "./utils";
|
||||
import { notify } from "@/notify";
|
||||
import { getApiPath, getPublicApiPath } from "@/utils/url.js";
|
||||
import { externalUrl } from "@/utils/constants";
|
||||
@@ -57,10 +57,8 @@ export async function fetchPub(path, hash, password = "", content = false) {
|
||||
* @returns {string}
|
||||
*/
|
||||
export function getDownloadURL(share, files, inline=false) {
|
||||
// Join files array with || delimiter and then URL encode
|
||||
const filesParam = Array.isArray(files) ? files.join('||') : files;
|
||||
const params = {
|
||||
files: encodeURIComponent(filesParam),
|
||||
files: files,
|
||||
hash: share.hash,
|
||||
token: share.token,
|
||||
...(inline && { inline: 'true' })
|
||||
@@ -91,6 +89,60 @@ export function getPreviewURL(path,size="small") {
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// SHARE MANAGEMENT API (permission-based authentication)
|
||||
// ============================================================================
|
||||
|
||||
// List all shares
|
||||
export async function list() {
|
||||
const apiPath = getApiPath("public/shares");
|
||||
return fetchJSON(apiPath);
|
||||
}
|
||||
|
||||
// Get share information
|
||||
/**
|
||||
* @param {string} path
|
||||
* @param {string} source
|
||||
* @returns {Promise<any>}
|
||||
*/
|
||||
export async function get(path, source) {
|
||||
try {
|
||||
const params = { path: encodeURIComponent(path), source: encodeURIComponent(source) };
|
||||
const apiPath = getApiPath("public/share", params);
|
||||
let data = await fetchJSON(apiPath);
|
||||
return adjustedData(data);
|
||||
} catch (/** @type {any} */ err) {
|
||||
notify.showError(err.message || "Error fetching data");
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
// Remove/delete a share
|
||||
/**
|
||||
* @param {string} hash
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
export async function remove(hash) {
|
||||
const params = { hash };
|
||||
const apiPath = getApiPath("public/share", params);
|
||||
await fetchURL(apiPath, {
|
||||
method: "DELETE",
|
||||
});
|
||||
}
|
||||
|
||||
// Create a new share
|
||||
/**
|
||||
* @param {Record<string, any>} bodyObj
|
||||
* @returns {Promise<Share>}
|
||||
*/
|
||||
export async function create(bodyObj = {}) {
|
||||
const apiPath = getApiPath("public/share");
|
||||
return fetchJSON(apiPath, {
|
||||
method: "POST",
|
||||
body: JSON.stringify(bodyObj || {}),
|
||||
});
|
||||
}
|
||||
|
||||
// Get the shareable URL for a share
|
||||
/**
|
||||
* @param {{ hash: string }} share
|
||||
@@ -103,3 +155,21 @@ export function getShareURL(share) {
|
||||
}
|
||||
return window.origin + getApiPath(`public/share/${share.hash}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef {object} Share
|
||||
* @property {string} hash
|
||||
* @property {string} path
|
||||
* @property {string} source
|
||||
* @property {number} expire
|
||||
* @property {number} downloadsLimit
|
||||
* @property {number} maxBandwidth
|
||||
* @property {string} shareTheme
|
||||
* @property {boolean} disableAnonymous
|
||||
* @property {boolean} disableThumbnails
|
||||
* @property {boolean} keepAfterExpiration
|
||||
* @property {string[]} allowedUsernames
|
||||
* @property {string} viewMode
|
||||
* @property {string} token
|
||||
* @property {boolean} inline
|
||||
*/
|
||||
@@ -13,11 +13,3 @@ export async function update(settings) {
|
||||
body: JSON.stringify(settings),
|
||||
});
|
||||
}
|
||||
|
||||
export function getConfig(showFull = false, showComments = false) {
|
||||
const params = {};
|
||||
if (showFull) params.full = "true";
|
||||
if (showComments) params.comments = "true";
|
||||
const path = getApiPath("api/settings/config", params);
|
||||
return fetchURL(path);
|
||||
}
|
||||
@@ -1,76 +0,0 @@
|
||||
import { fetchURL, fetchJSON, adjustedData } from "./utils";
|
||||
import { notify } from "@/notify";
|
||||
import { getApiPath } from "@/utils/url.js";
|
||||
|
||||
|
||||
// ============================================================================
|
||||
// SHARE MANAGEMENT API (permission-based authentication)
|
||||
// ============================================================================
|
||||
|
||||
// List all shares
|
||||
export async function list() {
|
||||
const apiPath = getApiPath("api/shares");
|
||||
return fetchJSON(apiPath);
|
||||
}
|
||||
|
||||
// Get share information
|
||||
/**
|
||||
* @param {string} path
|
||||
* @param {string} source
|
||||
* @returns {Promise<any>}
|
||||
*/
|
||||
export async function get(path, source) {
|
||||
try {
|
||||
const params = { path: encodeURIComponent(path), source: encodeURIComponent(source) };
|
||||
const apiPath = getApiPath("api/share", params);
|
||||
let data = await fetchJSON(apiPath);
|
||||
return adjustedData(data);
|
||||
} catch (/** @type {any} */ err) {
|
||||
notify.showError(err.message || "Error fetching data");
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
// Remove/delete a share
|
||||
/**
|
||||
* @param {string} hash
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
export async function remove(hash) {
|
||||
const params = { hash };
|
||||
const apiPath = getApiPath("api/share", params);
|
||||
await fetchURL(apiPath, {
|
||||
method: "DELETE",
|
||||
});
|
||||
}
|
||||
|
||||
// Create a new share
|
||||
/**
|
||||
* @param {Record<string, any>} bodyObj
|
||||
* @returns {Promise<Share>}
|
||||
*/
|
||||
export async function create(bodyObj = {}) {
|
||||
const apiPath = getApiPath("api/share");
|
||||
return fetchJSON(apiPath, {
|
||||
method: "POST",
|
||||
body: JSON.stringify(bodyObj || {}),
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef {object} Share
|
||||
* @property {string} hash
|
||||
* @property {string} path
|
||||
* @property {string} source
|
||||
* @property {number} expire
|
||||
* @property {number} downloadsLimit
|
||||
* @property {number} maxBandwidth
|
||||
* @property {string} shareTheme
|
||||
* @property {boolean} disableAnonymous
|
||||
* @property {boolean} disableThumbnails
|
||||
* @property {boolean} keepAfterExpiration
|
||||
* @property {string[]} allowedUsernames
|
||||
* @property {string} viewMode
|
||||
* @property {string} token
|
||||
* @property {boolean} inline
|
||||
*/
|
||||
@@ -14,25 +14,15 @@
|
||||
:class="{ 'dark-mode': isDarkMode, 'centered': centered }"
|
||||
:key="showCreate ? 'create-mode' : 'normal-mode'"
|
||||
>
|
||||
<div class="context-menu-header">
|
||||
<div
|
||||
class="action button clickable"
|
||||
v-if="!isSearchActive && userPerms.modify && !isShare"
|
||||
@click="toggleShowCreate"
|
||||
>
|
||||
<i v-if="!showCreate" class="material-icons">add</i>
|
||||
<i v-if="showCreate" class="material-icons">arrow_back</i>
|
||||
</div>
|
||||
<div
|
||||
v-if="selectedCount > 0"
|
||||
@mouseleave="hideTooltip"
|
||||
@mouseenter="showTooltip($event, $t('buttons.selectedCount'))"
|
||||
class="button selected-count-header"
|
||||
>
|
||||
<span>{{ selectedCount }}</span>
|
||||
</div>
|
||||
<div v-if="selectedCount > 0" class="button selected-count-header">
|
||||
<span>{{ selectedCount }} {{ $t("prompts.selected") }} </span>
|
||||
</div>
|
||||
<hr class="divider">
|
||||
<action
|
||||
v-if="!showCreate && !isSearchActive && userPerms.modify && !isShare"
|
||||
icon="add"
|
||||
:label="$t('buttons.new')"
|
||||
@action="startShowCreate"
|
||||
/>
|
||||
<action
|
||||
v-if="showCreate && !isSearchActive && userPerms.modify"
|
||||
icon="create_new_folder"
|
||||
@@ -281,6 +271,14 @@ export default {
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
currentPrompt: {
|
||||
handler(prompt) {
|
||||
if (prompt && prompt.name === "ContextMenu") {
|
||||
this.setPositions();
|
||||
}
|
||||
},
|
||||
deep: true,
|
||||
},
|
||||
hasOverflowItems: {
|
||||
handler(hasItems) {
|
||||
mutations.setContextMenuHasItems(hasItems);
|
||||
@@ -308,19 +306,6 @@ export default {
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
hideTooltip() {
|
||||
mutations.hideTooltip();
|
||||
},
|
||||
showTooltip(event, text) {
|
||||
mutations.showTooltip({
|
||||
content: text,
|
||||
x: event.clientX,
|
||||
y: event.clientY,
|
||||
});
|
||||
},
|
||||
toggleShowCreate() {
|
||||
this.showCreate = !this.showCreate;
|
||||
},
|
||||
shouldShowParentFolder() {
|
||||
return this.isPreview && state.req.path != "/";
|
||||
},
|
||||
@@ -553,10 +538,4 @@ export default {
|
||||
height: 0 !important;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.context-menu-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
</style>
|
||||
@@ -50,7 +50,7 @@
|
||||
</select>
|
||||
|
||||
<!-- Formatted display of selected value -->
|
||||
<div class="searchContext">{{ $t("search.searchContext", { context: getContext }) }}</div>
|
||||
<div class="searchContext">{{ contextText }}</div>
|
||||
</div>
|
||||
|
||||
<div id="result-list">
|
||||
@@ -87,7 +87,7 @@
|
||||
v-model="smallerThan"
|
||||
type="number"
|
||||
min="0"
|
||||
:placeholder="$t('search.number')"
|
||||
placeholder="number"
|
||||
/><p>MB</p> <!-- eslint-disable-line @intlify/vue-i18n/no-raw-text -->
|
||||
</div>
|
||||
<div class="sizeInputWrapper">
|
||||
@@ -96,7 +96,7 @@
|
||||
class="sizeInput"
|
||||
v-model="largerThan"
|
||||
type="number"
|
||||
:placeholder="$t('search.number')"
|
||||
placeholder="number"
|
||||
/><p>MB</p> <!-- eslint-disable-line @intlify/vue-i18n/no-raw-text -->
|
||||
</div>
|
||||
</div>
|
||||
@@ -146,8 +146,9 @@ import { search } from "@/api";
|
||||
import { getters, mutations, state } from "@/store";
|
||||
import { getHumanReadableFilesize } from "@/utils/filesizes";
|
||||
import { url } from "@/utils/";
|
||||
|
||||
import Icon from "@/components/files/Icon.vue";
|
||||
import { serverHasMultipleSources, baseURL, minSearchLength } from "@/utils/constants";
|
||||
import { serverHasMultipleSources, baseURL } from "@/utils/constants";
|
||||
|
||||
var boxes = {
|
||||
folder: { label: "folders", icon: "folder" },
|
||||
@@ -169,22 +170,22 @@ export default {
|
||||
return {
|
||||
largerThan: "",
|
||||
smallerThan: "",
|
||||
noneMessage: this.$t("search.typeToSearch", { minSearchLength: minSearchLength }),
|
||||
noneMessage: "Start typing 3 or more characters to begin searching.",
|
||||
searchTypes: "",
|
||||
isTypeSelectDisabled: false,
|
||||
showHelp: false,
|
||||
folderSelect: [
|
||||
{ label: this.$t("search.onlyFolders"), value: "type:folder" },
|
||||
{ label: this.$t("search.onlyFiles"), value: "type:file" },
|
||||
{ label: "Only Folders", value: "type:folder" },
|
||||
{ label: "Only Files", value: "type:file" },
|
||||
],
|
||||
typeSelect: [
|
||||
{ label: this.$t("search.photos"), value: "type:image" },
|
||||
{ label: this.$t("search.audio"), value: "type:audio" },
|
||||
{ label: this.$t("search.videos"), value: "type:video" },
|
||||
{ label: this.$t("search.documents"), value: "type:doc" },
|
||||
{ label: this.$t("search.archives"), value: "type:archive" },
|
||||
{ label: "Photos", value: "type:image" },
|
||||
{ label: "Audio", value: "type:audio" },
|
||||
{ label: "Videos", value: "type:video" },
|
||||
{ label: "Documents", value: "type:doc" },
|
||||
{ label: "Archives", value: "type:archive" },
|
||||
],
|
||||
toggleOptionButton: [{ label: this.$t("search.showOptions") }],
|
||||
toggleOptionButton: [{ label: "Show Options" }],
|
||||
value: "",
|
||||
ongoing: false,
|
||||
results: [],
|
||||
@@ -294,7 +295,9 @@ export default {
|
||||
if (this.ongoing) {
|
||||
return "";
|
||||
}
|
||||
return this.$t("search.typeToSearch", { minSearchLength: minSearchLength })
|
||||
return this.value === ""
|
||||
? this.$t("search.typeToSearch")
|
||||
: this.$t("search.pressToSearch");
|
||||
},
|
||||
isRunning() {
|
||||
return this.ongoing;
|
||||
@@ -327,6 +330,9 @@ export default {
|
||||
return "/"; // if searching on non-current source, search the whole thing
|
||||
}
|
||||
},
|
||||
contextText() {
|
||||
return `Search Context: ${this.getContext}`; // FIX: Use `this.getContext` as a property, not a function call
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
openContext(event) {
|
||||
@@ -483,9 +489,9 @@ export default {
|
||||
if (event != undefined) {
|
||||
event.preventDefault();
|
||||
}
|
||||
if (this.value === "" || this.value.length < minSearchLength) {
|
||||
if (this.value === "" || this.value.length < 3) {
|
||||
this.ongoing = false;
|
||||
this.noneMessage = this.$t("search.notEnoughCharacters", { minSearchLength: minSearchLength });
|
||||
this.noneMessage = "Not enough characters to search (min 3)";
|
||||
return;
|
||||
}
|
||||
let searchTypesFull = this.searchTypes;
|
||||
@@ -504,7 +510,7 @@ export default {
|
||||
|
||||
this.ongoing = false;
|
||||
if (this.results.length == 0) {
|
||||
this.noneMessage = this.$t("search.noResults");
|
||||
this.noneMessage = "No results found in indexed search.";
|
||||
}
|
||||
},
|
||||
toggleHelp() {
|
||||
@@ -866,72 +872,4 @@ body.rtl #search .boxes h3 {
|
||||
padding-right: 0.5em;
|
||||
min-width: fit-content;
|
||||
}
|
||||
|
||||
@media (max-width: 800px) {
|
||||
#search {
|
||||
min-width: unset;
|
||||
max-width: 60%;
|
||||
}
|
||||
|
||||
#search.active {
|
||||
display: block;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 50%;
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
#search-input {
|
||||
transition: 1s ease all;
|
||||
}
|
||||
|
||||
#search.active #search-input {
|
||||
border-bottom: 3px solid rgba(0, 0, 0, 0.075);
|
||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
|
||||
backdrop-filter: blur(6px);
|
||||
height: 4em;
|
||||
}
|
||||
|
||||
#search.active>div {
|
||||
border-radius: 0 !important;
|
||||
}
|
||||
|
||||
#search.active #result {
|
||||
height: 100vh;
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
#search.active #result>p>i {
|
||||
text-align: center;
|
||||
margin: 0 auto;
|
||||
display: table;
|
||||
}
|
||||
|
||||
#search.active #result ul li a {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: .3em 0;
|
||||
margin-right: .3em;
|
||||
}
|
||||
|
||||
#search-input>.action,
|
||||
#search-input>i {
|
||||
margin-right: 0.3em;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
#result-list {
|
||||
width:100vw !important;
|
||||
max-width: 100vw !important;
|
||||
left: 0;
|
||||
top: 4em;
|
||||
-webkit-box-direction: normal;
|
||||
-ms-flex-direction: column;
|
||||
overflow: scroll;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
@@ -3,14 +3,14 @@
|
||||
ref="tooltipRef"
|
||||
v-show="tooltip.show"
|
||||
class="floating-tooltip fb-shadow"
|
||||
:class="{ 'dark-mode': isDarkMode, 'pointer-enabled': tooltip.pointerEvents }"
|
||||
:class="{ 'dark-mode': isDarkMode }"
|
||||
:style="tooltipStyle"
|
||||
v-html="tooltip.content"
|
||||
></div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { state,getters } from "@/store";
|
||||
import { state } from "@/store";
|
||||
|
||||
export default {
|
||||
name: "Tooltip",
|
||||
@@ -26,27 +26,13 @@ export default {
|
||||
return state.tooltip;
|
||||
},
|
||||
isDarkMode() {
|
||||
return getters.isDarkMode();
|
||||
return state.user.darkMode;
|
||||
},
|
||||
tooltipStyle() {
|
||||
const style = {
|
||||
return {
|
||||
top: `${this.adjustedY}px`,
|
||||
left: `${this.adjustedX}px`,
|
||||
};
|
||||
|
||||
// Add custom width if specified
|
||||
if (this.tooltip.width) {
|
||||
style.maxWidth = this.tooltip.width;
|
||||
style.width = this.tooltip.width;
|
||||
}
|
||||
|
||||
// Add max height and scrolling for viewport overflow
|
||||
if (this.tooltip.width || this.tooltip.pointerEvents) {
|
||||
style.maxHeight = '80vh';
|
||||
style.overflowY = 'auto';
|
||||
}
|
||||
|
||||
return style;
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
@@ -67,10 +53,6 @@ export default {
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* @param {number} x - X coordinate
|
||||
* @param {number} y - Y coordinate
|
||||
*/
|
||||
updatePosition(x, y) {
|
||||
this.$nextTick(() => {
|
||||
const tooltipEl = this.$refs.tooltipRef;
|
||||
@@ -119,16 +101,6 @@ export default {
|
||||
white-space: normal;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
|
||||
.floating-tooltip.pointer-enabled {
|
||||
pointer-events: auto;
|
||||
cursor: auto;
|
||||
}
|
||||
|
||||
.floating-tooltip.pointer-enabled a {
|
||||
pointer-events: auto;
|
||||
cursor: pointer;
|
||||
}
|
||||
.tooltip-info-icon {
|
||||
font-size: 1em !important;
|
||||
padding: 0.1em !important;
|
||||
|
||||
@@ -31,7 +31,7 @@ import {
|
||||
baseURL,
|
||||
} from "@/utils/constants";
|
||||
import { getTypeInfo } from "@/utils/mimetype";
|
||||
import { mutations, state, getters } from "@/store";
|
||||
import { mutations, state } from "@/store";
|
||||
import { shareInfo } from "@/utils/constants";
|
||||
|
||||
// NEW: Define placeholder and error image URLs for easy configuration
|
||||
@@ -126,10 +126,10 @@ export default {
|
||||
return this.imageTargetSrc;
|
||||
},
|
||||
showLargeIcon() {
|
||||
return getters.viewMode() === "gallery" && state.user.preview.highQuality;
|
||||
return state.user.viewMode === "gallery" && state.user.preview.highQuality;
|
||||
},
|
||||
showLarger() {
|
||||
return getters.viewMode() === "gallery" || getters.viewMode() === "normal";
|
||||
return state.user.viewMode === "gallery" || state.user.viewMode === "normal";
|
||||
},
|
||||
hasMotion() {
|
||||
return (
|
||||
|
||||
@@ -109,7 +109,7 @@ export default {
|
||||
],
|
||||
computed: {
|
||||
galleryView() {
|
||||
return getters.viewMode() === "gallery";
|
||||
return state.user.viewMode === "gallery";
|
||||
},
|
||||
quickDownloadEnabled() {
|
||||
// @ts-ignore
|
||||
@@ -465,16 +465,19 @@ export default {
|
||||
) {
|
||||
mutations.resetSelected();
|
||||
}
|
||||
// @ts-ignore
|
||||
mutations.addSelected(this.index);
|
||||
mutations.setLastSelectedIndex(this.index);
|
||||
},
|
||||
open() {
|
||||
const previousHistoryItem = {
|
||||
name: state.req.items[this.index].name,
|
||||
source: state.req.source,
|
||||
path: state.req.path,
|
||||
};
|
||||
url.goToItem(this.source, this.path, previousHistoryItem);
|
||||
if (this.hash) {
|
||||
const shareHash = this.hash;
|
||||
url.goToItem(this.source, this.path, "", shareHash);
|
||||
return;
|
||||
}
|
||||
// @ts-ignore
|
||||
const previousHash = state.req.items[this.index].name;
|
||||
url.goToItem(this.source, this.path, previousHash);
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -4,15 +4,16 @@
|
||||
</div>
|
||||
|
||||
<div class="card-content">
|
||||
<p>{{ $t("help.description") }} <a class="link" href="https://github.com/gtsteffaniak/filebrowser/wiki" target="_blank">{{ $t("help.wiki") }}</a></p>
|
||||
<ul>
|
||||
<li><strong>F1</strong> - {{ $t("help.f1") }}</li> <!-- eslint-disable-line @intlify/vue-i18n/no-raw-text -->
|
||||
<li><strong>F2</strong> - {{ $t("help.f2") }}</li> <!-- eslint-disable-line @intlify/vue-i18n/no-raw-text -->
|
||||
<li><strong>DEL</strong> - {{ $t("help.del") }}</li> <!-- eslint-disable-line @intlify/vue-i18n/no-raw-text -->
|
||||
<li><strong>ESC</strong> - {{ $t("help.esc") }}</li> <!-- eslint-disable-line @intlify/vue-i18n/no-raw-text -->
|
||||
<li><strong>CTRL + S</strong> - {{ $t("help.ctrl.s") }}</li> <!-- eslint-disable-line @intlify/vue-i18n/no-raw-text -->
|
||||
<li><strong>CTRL + F</strong> - {{ $t("help.ctrl.f") }}</li> <!-- eslint-disable-line @intlify/vue-i18n/no-raw-text -->
|
||||
<li><strong>CTRL + Click</strong> - {{ $t("help.ctrl.click") }}</li> <!-- eslint-disable-line @intlify/vue-i18n/no-raw-text -->
|
||||
<li><strong>Click</strong> - {{ $t("help.click") }}</li> <!-- eslint-disable-line @intlify/vue-i18n/no-raw-text -->
|
||||
<li><strong>CTRL + D</strong> - {{ $t("help.ctrl.d") }}</li> <!-- eslint-disable-line @intlify/vue-i18n/no-raw-text -->
|
||||
<li><strong>Double click</strong> - {{ $t("help.doubleClick") }}</li> <!-- eslint-disable-line @intlify/vue-i18n/no-raw-text -->
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -4,16 +4,11 @@
|
||||
</div>
|
||||
|
||||
<div class="card-content">
|
||||
<p>{{ $t("prompts.renameMessage") }}</p>
|
||||
<input class="input" :class="{ 'form-invalid': !validation.valid }" v-focus type="text" @keyup.enter="submit"
|
||||
v-model.trim="name" />
|
||||
<p v-if="!validation.valid && name.length > 0" class="validation-error">
|
||||
<span v-if="validation.reason === 'conflict'">
|
||||
{{ $t("prompts.renameMessageConflict", { filename: name }) }}
|
||||
</span>
|
||||
<span v-else>
|
||||
{{ $t("prompts.renameMessageInvalid") }}
|
||||
</span>
|
||||
<p>{{ $t("prompts.renameMessage", { filename: oldName }) }}</p>
|
||||
<input class="input" :class="{ 'form-invalid': !validateFileName(name) }" v-focus type="text"
|
||||
@keyup.enter="submit" v-model.trim="name" />
|
||||
<p v-if="!validateFileName(name) && name.length > 0" class="validation-error">
|
||||
{{ $t("prompts.invalidName") }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -22,7 +17,7 @@
|
||||
:title="$t('buttons.cancel')">
|
||||
{{ $t("buttons.cancel") }}
|
||||
</button>
|
||||
<button @click="submit" class="button button--flat" :disabled="!validation.valid || name.length === 0"
|
||||
<button @click="submit" class="button button--flat" :disabled="!validateFileName(name) || name.length === 0"
|
||||
type="submit" :aria-label="$t('buttons.rename')" :title="$t('buttons.rename')">
|
||||
{{ $t("buttons.rename") }}
|
||||
</button>
|
||||
@@ -30,7 +25,7 @@
|
||||
</template>
|
||||
<script>
|
||||
import { filesApi } from "@/api";
|
||||
import { mutations, state } from "@/store";
|
||||
import { mutations } from "@/store";
|
||||
import { notify } from "@/notify";
|
||||
|
||||
export default {
|
||||
@@ -54,9 +49,6 @@ export default {
|
||||
oldName() {
|
||||
return this.item?.name || "";
|
||||
},
|
||||
validation() {
|
||||
return this.validateFileName(this.name);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
@@ -64,51 +56,24 @@ export default {
|
||||
*/
|
||||
validateFileName(value) {
|
||||
if (value === "") {
|
||||
return { valid: true };
|
||||
return false;
|
||||
}
|
||||
|
||||
const isFolder = this.item.type === "directory";
|
||||
|
||||
// Check for forbidden characters in file names
|
||||
if (!isFolder) {
|
||||
const forbiddenChars = /[/\\]/;
|
||||
if (forbiddenChars.test(value)) {
|
||||
return { valid: false, reason: 'invalidChar' };
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the item already exists
|
||||
for (const item of state.req.items) {
|
||||
if (item.name.toLowerCase() === value.toLowerCase()) {
|
||||
if (isFolder === (item.type === "directory")) {
|
||||
return { valid: false, reason: 'conflict' };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return { valid: true };
|
||||
// Check for forbidden characters: forward slash and backslash
|
||||
const forbiddenChars = /[/\\]/;
|
||||
return !forbiddenChars.test(value);
|
||||
},
|
||||
async submit() {
|
||||
// remove trailing slashes
|
||||
if (this.name.endsWith("/") || this.name.endsWith("\\")) {
|
||||
this.name = this.name.substring(0, this.name.length - 1);
|
||||
}
|
||||
if (!this.validation.valid) {
|
||||
// Validate before submitting
|
||||
if (!this.validateFileName(this.name) || this.name.length === 0) {
|
||||
notify.showError(this.$t("prompts.invalidName"));
|
||||
return;
|
||||
}
|
||||
|
||||
let newPath = this.item.path.substring(0, this.item.path.lastIndexOf("/"));
|
||||
newPath = `${newPath}/${this.name}`;
|
||||
try {
|
||||
if (this.name === this.item.name) {
|
||||
mutations.closeHovers();
|
||||
return;
|
||||
}
|
||||
|
||||
const items = [{
|
||||
from: this.item.path,
|
||||
fromSource: this.item.source,
|
||||
to: newPath,
|
||||
to: this.item.path.replace(/[^/]+$/, this.name),
|
||||
toSource: this.item.source,
|
||||
}];
|
||||
|
||||
|
||||
@@ -89,25 +89,6 @@
|
||||
/>
|
||||
<ToggleSwitch class="item" v-model="disableAnonymous" :name="$t('share.disableAnonymous')" :description="$t('share.disableAnonymousDescription')" />
|
||||
<ToggleSwitch class="item" v-model="enableAllowedUsernames" :name="$t('share.enableAllowedUsernames')" :description="$t('share.enableAllowedUsernamesDescription')" />
|
||||
|
||||
<ToggleSwitch v-if="onlyOfficeAvailable" class="item" v-model="enableOnlyOffice" :name="$t('share.enableOnlyOffice')" :description="$t('share.enableOnlyOfficeDescription')" />
|
||||
<ToggleSwitch v-if="onlyOfficeAvailable" class="item" v-model="enableOnlyOfficeEditing" :name="$t('share.enableOnlyOfficeEditing')" :description="$t('share.enableOnlyOfficeEditingDescription')" />
|
||||
<p>
|
||||
{{ $t("share.enforceDarkLightMode") }}
|
||||
<i
|
||||
class="no-select material-symbols-outlined tooltip-info-icon"
|
||||
@mouseenter="showTooltip($event, $t('share.enforceDarkLightModeDescription'))"
|
||||
@mouseleave="hideTooltip"
|
||||
>
|
||||
help
|
||||
</i>
|
||||
</p>
|
||||
<select class="input" v-model="enforceDarkLightMode">
|
||||
<option value="default">{{ $t("share.default") }}</option>
|
||||
<option value="dark">{{ $t("share.dark") }}</option>
|
||||
<option value="light">{{ $t("share.light") }}</option>
|
||||
</select>
|
||||
|
||||
<div v-if="enableAllowedUsernames" class="item">
|
||||
<input class="input" type="text" v-model.trim="allowedUsernames" :placeholder="$t('share.allowedUsernamesPlaceholder')" />
|
||||
</div>
|
||||
@@ -130,7 +111,26 @@
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<SettingsItem :title="$t('buttons.showMore')" :collapsable="true" :start-collapsed="true">
|
||||
<div class="advanced-toggle">
|
||||
<button
|
||||
class="button button--flat button--block"
|
||||
@click="showAdvanced = !showAdvanced"
|
||||
:aria-expanded="showAdvanced ? 'true' : 'false'"
|
||||
aria-controls="advanced-settings"
|
||||
:aria-label="showAdvanced ? $t('buttons.showLess') : $t('buttons.showMore')"
|
||||
:title="showAdvanced ? $t('buttons.showLess') : $t('buttons.showMore')"
|
||||
>
|
||||
{{ showAdvanced ? $t('buttons.showLess') : $t('buttons.showMore') }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<transition
|
||||
name="expand"
|
||||
@before-enter="beforeEnter"
|
||||
@enter="enter"
|
||||
@leave="leave"
|
||||
>
|
||||
<div id="advanced-settings" v-show="showAdvanced">
|
||||
<div class="settings-items">
|
||||
<ToggleSwitch class="item" v-model="keepAfterExpiration" :name="$t('share.keepAfterExpiration')" :description="$t('share.keepAfterExpirationDescription')" />
|
||||
<ToggleSwitch class="item" v-model="disableThumbnails" :name="$t('share.disableThumbnails')" :description="$t('share.disableThumbnailsDescription')" />
|
||||
@@ -222,7 +222,8 @@
|
||||
</i>
|
||||
</p>
|
||||
<input class="input" type="text" v-model.trim="favicon" />
|
||||
</SettingsItem>
|
||||
</div>
|
||||
</transition>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -249,13 +250,12 @@
|
||||
<script>
|
||||
import { notify } from "@/notify";
|
||||
import { state, getters, mutations } from "@/store";
|
||||
import { publicApi, shareApi } from "@/api";
|
||||
import { publicApi } from "@/api";
|
||||
import Clipboard from "clipboard";
|
||||
import { fromNow } from "@/utils/moment";
|
||||
import { buildItemUrl, fixDownloadURL } from "@/utils/url";
|
||||
import ToggleSwitch from "@/components/settings/ToggleSwitch.vue";
|
||||
import SettingsItem from "@/components/settings/SettingsItem.vue";
|
||||
import { userSelectableThemes, externalUrl, onlyOfficeUrl } from "@/utils/constants";
|
||||
import { userSelectableThemes, externalUrl } from "@/utils/constants";
|
||||
import { eventBus } from "@/store/eventBus";
|
||||
//import ViewMode from "@/components/settings/ViewMode.vue";
|
||||
|
||||
@@ -263,7 +263,6 @@ export default {
|
||||
name: "share",
|
||||
components: {
|
||||
ToggleSwitch,
|
||||
SettingsItem,
|
||||
//ViewMode,
|
||||
},
|
||||
props: {
|
||||
@@ -311,20 +310,15 @@ export default {
|
||||
title: "",
|
||||
description: "",
|
||||
favicon: "",
|
||||
showAdvanced: false,
|
||||
quickDownload: false,
|
||||
disableNavButtons: false,
|
||||
disableShareCard: false,
|
||||
disableSidebar: false,
|
||||
enforceDarkLightMode: "default",
|
||||
enableOnlyOffice: false,
|
||||
enableOnlyOfficeEditing: false,
|
||||
//viewMode: "normal",
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
onlyOfficeAvailable() {
|
||||
return onlyOfficeUrl !== "";
|
||||
},
|
||||
availableThemes() {
|
||||
return userSelectableThemes || {};
|
||||
},
|
||||
@@ -396,9 +390,6 @@ export default {
|
||||
this.disableNavButtons = this.link.hideNavButtons || false;
|
||||
this.disableShareCard = this.link.disableShareCard || false;
|
||||
this.disableSidebar = this.link.disableSidebar || false;
|
||||
this.enforceDarkLightMode = this.link.enforceDarkLightMode || "default";
|
||||
this.enableOnlyOffice = this.link.enableOnlyOffice || false;
|
||||
this.enableOnlyOfficeEditing = this.link.enableOnlyOfficeEditing || false;
|
||||
//this.viewMode = this.link.viewMode || "normal";
|
||||
}
|
||||
},
|
||||
@@ -410,7 +401,7 @@ export default {
|
||||
}
|
||||
try {
|
||||
// get last element of the path
|
||||
const links = await shareApi.get(this.item.path, this.item.source);
|
||||
const links = await publicApi.get(this.item.path, this.item.source);
|
||||
this.links = links;
|
||||
} catch (err) {
|
||||
notify.showError(err);
|
||||
@@ -429,6 +420,45 @@ export default {
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* @param {Element} el
|
||||
*/
|
||||
beforeEnter(el) {
|
||||
const element = /** @type {HTMLElement} */ (el);
|
||||
element.style.height = '0';
|
||||
element.style.opacity = '0';
|
||||
},
|
||||
/**
|
||||
* @param {Element} el
|
||||
* @param {() => void} done
|
||||
*/
|
||||
enter(el, done) {
|
||||
const element = /** @type {HTMLElement} */ (el);
|
||||
element.style.transition = '';
|
||||
element.style.height = '0';
|
||||
element.style.opacity = '0';
|
||||
void element.offsetHeight;
|
||||
element.style.transition = 'height 0.3s, opacity 0.3s';
|
||||
element.style.height = element.scrollHeight + 'px';
|
||||
element.style.opacity = '1';
|
||||
setTimeout(() => {
|
||||
element.style.height = 'auto';
|
||||
done();
|
||||
}, 300);
|
||||
},
|
||||
/**
|
||||
* @param {Element} el
|
||||
* @param {() => void} done
|
||||
*/
|
||||
leave(el, done) {
|
||||
const element = /** @type {HTMLElement} */ (el);
|
||||
element.style.transition = 'height 0.3s, opacity 0.3s';
|
||||
element.style.height = element.scrollHeight + 'px';
|
||||
void element.offsetHeight;
|
||||
element.style.height = '0';
|
||||
element.style.opacity = '0';
|
||||
setTimeout(done, 300);
|
||||
},
|
||||
/**
|
||||
* @param {MouseEvent} event
|
||||
* @param {string} text
|
||||
@@ -477,15 +507,12 @@ export default {
|
||||
hideNavButtons: this.disableNavButtons,
|
||||
disableShareCard: this.disableShareCard,
|
||||
disableSidebar: this.disableSidebar,
|
||||
enforceDarkLightMode: this.enforceDarkLightMode,
|
||||
enableOnlyOffice: this.enableOnlyOffice,
|
||||
enableOnlyOfficeEditing: this.enableOnlyOfficeEditing,
|
||||
};
|
||||
if (this.isEditMode) {
|
||||
payload.hash = this.link.hash;
|
||||
}
|
||||
|
||||
const res = await shareApi.create(payload);
|
||||
const res = await publicApi.create(payload);
|
||||
|
||||
if (!this.isEditMode) {
|
||||
this.links.push(res);
|
||||
@@ -512,7 +539,7 @@ export default {
|
||||
async deleteLink(event, link) {
|
||||
event.preventDefault();
|
||||
try {
|
||||
await shareApi.remove(link.hash);
|
||||
await publicApi.remove(link.hash);
|
||||
this.links = this.links.filter((item) => item.hash !== link.hash);
|
||||
// emit event to reload shares in settings view
|
||||
eventBus.emit('sharesChanged');
|
||||
@@ -572,6 +599,9 @@ export default {
|
||||
|
||||
<style scoped>
|
||||
|
||||
.advanced-toggle {
|
||||
margin-top: 1em;
|
||||
}
|
||||
.setting-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
@@ -589,4 +619,15 @@ export default {
|
||||
.input {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.expand-enter-active,
|
||||
.expand-leave-active {
|
||||
transition: height 0.3s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
overflow: hidden;
|
||||
}
|
||||
.expand-enter,
|
||||
.expand-leave-to {
|
||||
height: 0 !important;
|
||||
opacity: 0;
|
||||
}
|
||||
</style>
|
||||
@@ -1,166 +0,0 @@
|
||||
<template>
|
||||
<div class="settings-group">
|
||||
<div class="settings-group-title button" :class="{ 'unclickable': !collapsable }"
|
||||
@click="collapsable ? toggleCollapse() : null">
|
||||
<h3>{{ title }}</h3>
|
||||
<i v-if="collapsable" class="material-symbols-outlined collapse-icon" :class="{ 'rotated': !actuallyCollapsed }">
|
||||
expand_more
|
||||
</i>
|
||||
</div>
|
||||
|
||||
<transition name="expand" @before-enter="beforeEnter" @enter="enter" @leave="leave">
|
||||
<div v-show="!actuallyCollapsed" class="settings-content">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</transition>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'SettingsItem',
|
||||
props: {
|
||||
title: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
collapsable: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
startCollapsed: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
forceCollapsed: {
|
||||
type: Boolean,
|
||||
default: null
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isCollapsed: this.startCollapsed
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
actuallyCollapsed() {
|
||||
// If forceCollapsed is explicitly set, use that, otherwise use internal state
|
||||
return this.forceCollapsed !== null ? this.forceCollapsed : this.isCollapsed;
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
forceCollapsed(newVal) {
|
||||
if (newVal !== null) {
|
||||
this.isCollapsed = newVal;
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
toggleCollapse() {
|
||||
if (this.forceCollapsed !== null) {
|
||||
// Emit event for parent to handle accordion logic
|
||||
this.$emit('toggle', this.title);
|
||||
} else {
|
||||
// Handle internally as before
|
||||
this.isCollapsed = !this.isCollapsed;
|
||||
}
|
||||
},
|
||||
/**
|
||||
* @param {Element} el
|
||||
*/
|
||||
beforeEnter(el) {
|
||||
const element = /** @type {HTMLElement} */ (el);
|
||||
element.style.height = '0';
|
||||
element.style.opacity = '0';
|
||||
},
|
||||
/**
|
||||
* @param {Element} el
|
||||
* @param {() => void} done
|
||||
*/
|
||||
enter(el, done) {
|
||||
const element = /** @type {HTMLElement} */ (el);
|
||||
element.style.transition = '';
|
||||
element.style.height = '0';
|
||||
element.style.opacity = '0';
|
||||
void element.offsetHeight;
|
||||
element.style.transition = 'height 0.3s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.3s cubic-bezier(0.4, 0, 0.2, 1)';
|
||||
element.style.height = element.scrollHeight + 'px';
|
||||
element.style.opacity = '1';
|
||||
setTimeout(() => {
|
||||
element.style.height = 'auto';
|
||||
done();
|
||||
}, 300);
|
||||
},
|
||||
/**
|
||||
* @param {Element} el
|
||||
* @param {() => void} done
|
||||
*/
|
||||
leave(el, done) {
|
||||
const element = /** @type {HTMLElement} */ (el);
|
||||
element.style.transition = 'height 0.3s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.3s cubic-bezier(0.4, 0, 0.2, 1)';
|
||||
element.style.height = element.scrollHeight + 'px';
|
||||
void element.offsetHeight;
|
||||
element.style.height = '0';
|
||||
element.style.opacity = '0';
|
||||
setTimeout(done, 300);
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
|
||||
.settings-group-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-top: 1em;
|
||||
background: var(--alt-background) !important;
|
||||
color: var(--textPrimary) !important;
|
||||
padding: 0.5em;
|
||||
}
|
||||
|
||||
.settings-group-title h3 {
|
||||
margin: 0;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.collapse-icon {
|
||||
transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
color: var(--textSecondary);
|
||||
}
|
||||
|
||||
.collapse-icon.rotated {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
.settings-content {
|
||||
overflow: hidden;
|
||||
margin-top: 0.5em;
|
||||
}
|
||||
|
||||
.expand-enter-active,
|
||||
.expand-leave-active {
|
||||
transition: height 0.3s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.expand-enter,
|
||||
.expand-leave-to {
|
||||
height: 0 !important;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.settings-group-title.unclickable {
|
||||
cursor: default;
|
||||
user-select: none;
|
||||
transition: opacity 0.2s ease;
|
||||
}
|
||||
|
||||
/* Prevent content height issues during animation */
|
||||
.settings-content .input,
|
||||
.settings-content .settings-items {
|
||||
height: auto;
|
||||
}
|
||||
</style>
|
||||
@@ -3,7 +3,7 @@
|
||||
<div class="card-wrapper user-card">
|
||||
<div
|
||||
v-if="settingsAllowed && user.username !== 'anonymous'"
|
||||
@click="navigateTo('/settings','#profile-main')"
|
||||
@click="navigateTo('/settings#profile-main')"
|
||||
class="inner-card"
|
||||
>
|
||||
<button
|
||||
@@ -53,7 +53,6 @@
|
||||
<i class="material-icons">ads_click</i>
|
||||
</div>
|
||||
<div
|
||||
v-if="darkModeTogglePossible"
|
||||
class="clickable"
|
||||
:class="{ active: user?.darkMode }"
|
||||
@click="toggleDarkMode"
|
||||
@@ -179,7 +178,6 @@ export default {
|
||||
sourceInfo: () => state.sources.info,
|
||||
activeSource: () => state.sources.current,
|
||||
realtimeActive: () => state.realtimeActive,
|
||||
darkModeTogglePossible: () => shareInfo.enforceDarkLightMode != "dark" && shareInfo.enforceDarkLightMode != "light",
|
||||
},
|
||||
watch: {
|
||||
route() {
|
||||
@@ -220,13 +218,17 @@ export default {
|
||||
}
|
||||
mutations.updateCurrentUser({ stickySidebar: !state.user.stickySidebar });
|
||||
},
|
||||
navigateTo(path,hash) {
|
||||
mutations.setPreviousHistoryItem({
|
||||
name: state.req.name,
|
||||
source: state.req.source,
|
||||
path: state.req.path,
|
||||
});
|
||||
this.$router.push({ path: path, hash: hash });
|
||||
navigateTo(path) {
|
||||
const hashIndex = path.indexOf("#");
|
||||
if (hashIndex !== -1) {
|
||||
// Extract the hash
|
||||
const hash = path.substring(hashIndex);
|
||||
// Remove the hash from the path
|
||||
const cleanPath = path.substring(0, hashIndex);
|
||||
this.$router.push({ path: cleanPath, hash: hash }, () => {});
|
||||
} else {
|
||||
this.$router.push({ path: path }, () => {});
|
||||
}
|
||||
mutations.closeHovers();
|
||||
},
|
||||
navigateToLogin() {
|
||||
@@ -404,7 +406,6 @@ button.action {
|
||||
.quick-toggles .active {
|
||||
background-color: var(--primaryColor) !important;
|
||||
border-radius: 10em;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.inner-card {
|
||||
|
||||
@@ -14,16 +14,7 @@
|
||||
<div class="buffer"></div>
|
||||
<div v-if="!isSettings" class="credits">
|
||||
<span v-for="item in externalLinks" :key="item.title">
|
||||
<a
|
||||
v-if="item.url === 'help prompt'"
|
||||
href="#"
|
||||
@click.prevent="help"
|
||||
:title="$t('sidebar.help')"
|
||||
>{{ $t("sidebar.help") }}</a
|
||||
>
|
||||
<a v-else :href="item.url" target="_blank" :title="item.title">{{
|
||||
item.text
|
||||
}}</a>
|
||||
<a :href="item.url" target="_blank" :title="item.title">{{ item.text }}</a>
|
||||
</span>
|
||||
<span v-if="name != ''">
|
||||
<h4 style="margin: 0">{{ name }}</h4>
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
:root {
|
||||
--blue: #2196f3;
|
||||
--dark-blue: #1E88E5;
|
||||
--red: #f44336;
|
||||
--red: #F44336;
|
||||
--dark-red: #D32F2F;
|
||||
--moon-grey: #f2f2f2;
|
||||
|
||||
--icon-red: #da4453;
|
||||
--icon-orange: #f47750;
|
||||
--icon-yellow: #fdbc4b;
|
||||
|
||||
@@ -50,7 +50,68 @@
|
||||
body.rtl .dashboard .row {
|
||||
margin-right: unset;
|
||||
}
|
||||
#search {
|
||||
min-width: unset;
|
||||
max-width: 60%;
|
||||
}
|
||||
|
||||
#search.active {
|
||||
display: block;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 50%;
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
}
|
||||
#search-input {
|
||||
transition: 1s ease all;
|
||||
}
|
||||
#search.active #search-input {
|
||||
border-bottom: 3px solid rgba(0, 0, 0, 0.075);
|
||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
|
||||
backdrop-filter: blur(6px);
|
||||
height: 4em;
|
||||
}
|
||||
|
||||
#search.active>div {
|
||||
border-radius: 0 !important;
|
||||
}
|
||||
|
||||
#search.active #result {
|
||||
height: 100vh;
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
#search.active #result>p>i {
|
||||
text-align: center;
|
||||
margin: 0 auto;
|
||||
display: table;
|
||||
}
|
||||
|
||||
#search.active #result ul li a {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: .3em 0;
|
||||
margin-right: .3em;
|
||||
}
|
||||
|
||||
#search-input>.action,
|
||||
#search-input>i {
|
||||
margin-right: 0.3em;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
#result-list {
|
||||
width:100vw !important;
|
||||
max-width: 100vw !important;
|
||||
left: 0;
|
||||
top: 4em;
|
||||
-webkit-box-direction: normal;
|
||||
-ms-flex-direction: column;
|
||||
overflow: scroll;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
+189
-200
@@ -8,52 +8,52 @@
|
||||
@import "./listing.css";
|
||||
@import "./dashboard.css";
|
||||
@import "./login.css";
|
||||
@import "./mobile.css";
|
||||
@import './mobile.css';
|
||||
|
||||
.link {
|
||||
color: var(--blue);
|
||||
color: var(--blue);
|
||||
}
|
||||
|
||||
#main .spinner {
|
||||
display: block;
|
||||
text-align: center;
|
||||
line-height: 0;
|
||||
padding: 1em 0;
|
||||
display: block;
|
||||
text-align: center;
|
||||
line-height: 0;
|
||||
padding: 1em 0;
|
||||
}
|
||||
|
||||
#main .spinner > div {
|
||||
width: 0.8em;
|
||||
height: 0.8em;
|
||||
margin: 0 0.1em;
|
||||
font-size: 1em;
|
||||
background-color: rgba(0, 0, 0, 0.3);
|
||||
border-radius: 100%;
|
||||
display: inline-block;
|
||||
animation: sk-bouncedelay 1.4s infinite ease-in-out both;
|
||||
width: .8em;
|
||||
height: .8em;
|
||||
margin: 0 .1em;
|
||||
font-size: 1em;
|
||||
background-color: rgba(0, 0, 0, 0.3);
|
||||
border-radius: 100%;
|
||||
display: inline-block;
|
||||
animation: sk-bouncedelay 1.4s infinite ease-in-out both;
|
||||
}
|
||||
|
||||
#main .spinner .bounce1 {
|
||||
animation-delay: -0.32s;
|
||||
animation-delay: -0.32s;
|
||||
}
|
||||
|
||||
#main .spinner .bounce2 {
|
||||
animation-delay: -0.16s;
|
||||
animation-delay: -0.16s;
|
||||
}
|
||||
|
||||
.delayed {
|
||||
animation: delayed linear 100ms;
|
||||
animation: delayed linear 100ms;
|
||||
}
|
||||
|
||||
@keyframes delayed {
|
||||
0% {
|
||||
opacity: 0;
|
||||
}
|
||||
99% {
|
||||
opacity: 0;
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
0% {
|
||||
opacity: 0;
|
||||
}
|
||||
99% {
|
||||
opacity: 0;
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* * * * * * * * * * * * * * * *
|
||||
@@ -61,222 +61,223 @@
|
||||
* * * * * * * * * * * * * * * */
|
||||
|
||||
.action {
|
||||
display: inline-block;
|
||||
cursor: pointer;
|
||||
transition: 0.2s ease all;
|
||||
border: 0;
|
||||
margin: 0;
|
||||
color: #546e7a;
|
||||
border-radius: 50%;
|
||||
background: transparent;
|
||||
padding: 0;
|
||||
box-shadow: none;
|
||||
vertical-align: middle;
|
||||
text-align: left;
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
cursor: pointer;
|
||||
transition: 0.2s ease all;
|
||||
border: 0;
|
||||
margin: 0;
|
||||
color: #546E7A;
|
||||
border-radius: 50%;
|
||||
background: transparent;
|
||||
padding: 0;
|
||||
box-shadow: none;
|
||||
vertical-align: middle;
|
||||
text-align: left;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.action.disabled {
|
||||
opacity: 0.2;
|
||||
cursor: not-allowed;
|
||||
opacity: 0.2;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.action i {
|
||||
padding: 0.4em;
|
||||
transition: 0.1s ease-in-out all;
|
||||
border-radius: 50%;
|
||||
padding: 0.4em;
|
||||
transition: .1s ease-in-out all;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.action:not(:disabled):hover {
|
||||
background-color: rgba(0, 0, 0, 0.1);
|
||||
background-color: rgba(0, 0, 0, .1);
|
||||
}
|
||||
|
||||
.action ul {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
color: #7d7d7d;
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
flex-direction: column;
|
||||
display: flex;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
color: #7d7d7d;
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
flex-direction: column;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.action ul li {
|
||||
line-height: 1;
|
||||
padding: 0.7em;
|
||||
transition: 0.1s ease background-color;
|
||||
line-height: 1;
|
||||
padding: .7em;
|
||||
transition: .1s ease background-color;
|
||||
}
|
||||
|
||||
.action ul li:hover {
|
||||
background-color: rgba(0, 0, 0, 0.04);
|
||||
background-color: rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
|
||||
.action .counter {
|
||||
display: block;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
background: var(--primaryColor);
|
||||
color: #fff;
|
||||
border-radius: 50%;
|
||||
font-size: 0.75em;
|
||||
width: 1.8em;
|
||||
height: 1.8em;
|
||||
text-align: center;
|
||||
line-height: 1.55em;
|
||||
font-weight: bold;
|
||||
display: block;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
background: var(--primaryColor);
|
||||
color: #fff;
|
||||
border-radius: 50%;
|
||||
font-size: .75em;
|
||||
width: 1.8em;
|
||||
height: 1.8em;
|
||||
text-align: center;
|
||||
line-height: 1.55em;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
|
||||
/* PREVIEWER */
|
||||
|
||||
#previewer {
|
||||
background-color: rgba(0, 0, 0, 0.9);
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 9999;
|
||||
background-color: rgba(0, 0, 0, 0.9);
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 9999;
|
||||
}
|
||||
|
||||
#previewer header {
|
||||
background: none;
|
||||
color: #fff;
|
||||
background: none;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
#previewer header > .action i {
|
||||
color: #fff;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
@media (min-width: 800px) {
|
||||
#previewer header #dropdown .action i {
|
||||
color: #fff;
|
||||
}
|
||||
#previewer header #dropdown .action i {
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
#previewer header .action:hover {
|
||||
background-color: rgba(255, 255, 255, 0.3);
|
||||
background-color: rgba(255, 255, 255, 0.3)
|
||||
}
|
||||
|
||||
#previewer header .action span {
|
||||
display: none;
|
||||
display: none;
|
||||
}
|
||||
|
||||
#previewer .preview {
|
||||
text-align: center;
|
||||
max-height: 100%;
|
||||
max-width: 100%;
|
||||
height: 100%;
|
||||
text-align: center;
|
||||
max-height: 100%;
|
||||
max-width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#previewer .preview pre {
|
||||
text-align: left;
|
||||
overflow: auto;
|
||||
text-align: left;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
#previewer .preview pre,
|
||||
#previewer .preview video,
|
||||
#previewer .preview img {
|
||||
max-height: 100%;
|
||||
margin: 0;
|
||||
max-height: 100%;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
#previewer .preview video {
|
||||
height: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#previewer .preview .info {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
font-size: 1.5em;
|
||||
color: #fff;
|
||||
display: flex;
|
||||
height: 100%;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
font-size: 1.5em;
|
||||
color: #fff;
|
||||
}
|
||||
#previewer .preview .info .title {
|
||||
margin-bottom: 1em;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
#previewer .preview .info .title i {
|
||||
display: block;
|
||||
margin-bottom: 0.1em;
|
||||
font-size: 4em;
|
||||
display: block;
|
||||
margin-bottom: .1em;
|
||||
font-size: 4em;
|
||||
}
|
||||
#previewer .preview .info .button {
|
||||
display: inline-block;
|
||||
display: inline-block;
|
||||
}
|
||||
#previewer .preview .info .button:hover {
|
||||
background-color: rgba(255, 255, 255, 0.2);
|
||||
background-color: rgba(255, 255, 255, 0.2)
|
||||
}
|
||||
#previewer .preview .info .button i {
|
||||
display: block;
|
||||
margin-bottom: 4px;
|
||||
font-size: 1.3em;
|
||||
display: block;
|
||||
margin-bottom: 4px;
|
||||
font-size: 1.3em;
|
||||
}
|
||||
|
||||
#previewer .pdf {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#previewer h2.message {
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
color: rgba(255, 255, 255, 0.5)
|
||||
}
|
||||
|
||||
#previewer > button {
|
||||
margin: 0;
|
||||
position: fixed;
|
||||
top: calc(50% + 1.85em);
|
||||
transform: translateY(-50%);
|
||||
background-color: rgba(80, 80, 80, 0.5);
|
||||
color: white;
|
||||
border-radius: 50%;
|
||||
cursor: pointer;
|
||||
border: 0;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
transition: 0.2s ease all;
|
||||
#previewer>button {
|
||||
margin: 0;
|
||||
position: fixed;
|
||||
top: calc(50% + 1.85em);
|
||||
transform: translateY(-50%);
|
||||
background-color: rgba(80, 80, 80, .5);
|
||||
color: white;
|
||||
border-radius: 50%;
|
||||
cursor: pointer;
|
||||
border: 0;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
transition: 0.2s ease all;
|
||||
}
|
||||
|
||||
#previewer > button.hidden {
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
#previewer>button.hidden {
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
#previewer > button > i {
|
||||
padding: 0.4em;
|
||||
#previewer>button>i {
|
||||
padding: 0.4em;
|
||||
}
|
||||
|
||||
#previewer > button:last-of-type {
|
||||
right: 0.5em;
|
||||
#previewer>button:last-of-type {
|
||||
right: 0.5em;
|
||||
}
|
||||
|
||||
#previewer .spinner {
|
||||
text-align: center;
|
||||
position: fixed;
|
||||
top: calc(50% + 1.85em);
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
text-align: center;
|
||||
position: fixed;
|
||||
top: calc(50% + 1.85em);
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
#previewer .spinner > div {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
background-color: white;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
/* EDITOR */
|
||||
|
||||
#editor-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background-color: none;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background-color: none;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
#editor-container #editor {
|
||||
flex: 1;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
/* * * * * * * * * * * * * * * *
|
||||
@@ -284,9 +285,9 @@
|
||||
* * * * * * * * * * * * * * * */
|
||||
|
||||
@keyframes spin {
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
/* * * * * * * * * * * * * * * *
|
||||
@@ -294,31 +295,31 @@
|
||||
* * * * * * * * * * * * * * * */
|
||||
|
||||
.rules > div {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin: 0.5rem 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin: .5rem 0;
|
||||
}
|
||||
|
||||
.rules input[type="checkbox"] {
|
||||
margin-right: 0.2rem;
|
||||
margin-right: .2rem;
|
||||
}
|
||||
|
||||
.rules input[type="text"] {
|
||||
border: 1px solid#ddd;
|
||||
padding: 0.2rem;
|
||||
border: 1px solid#ddd;
|
||||
padding: .2rem;
|
||||
}
|
||||
|
||||
.rules label {
|
||||
margin-right: 0.5rem;
|
||||
margin-right: .5rem;
|
||||
}
|
||||
|
||||
.rules button {
|
||||
margin-left: auto;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.rules button.delete {
|
||||
padding: 0.2rem 0.5rem;
|
||||
margin-left: 0.5rem;
|
||||
padding: .2rem .5rem;
|
||||
margin-left: .5rem;
|
||||
}
|
||||
|
||||
/* * * * * * * * * * * * * * * *
|
||||
@@ -326,90 +327,78 @@
|
||||
* * * * * * * * * * * * * * * */
|
||||
|
||||
body.rtl .card-content textarea {
|
||||
direction: ltr;
|
||||
text-align: left;
|
||||
direction: ltr;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
body.rtl .card-content .small + input {
|
||||
direction: ltr;
|
||||
text-align: left;
|
||||
direction: ltr;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
body.rtl .card.floating .card-content .file-list {
|
||||
direction: ltr;
|
||||
text-align: left;
|
||||
direction: ltr;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.fb-shadow {
|
||||
box-shadow: 0px 0px 15px 0px rgb(100 100 100 / 75%);
|
||||
-webkit-box-shadow: 0px 0px 15px 0px rgb(100 100 100 / 75%);
|
||||
box-shadow: 0px 0px 15px 0px rgb(100 100 100 / 75%);
|
||||
-webkit-box-shadow: 0px 0px 15px 0px rgb(100 100 100 / 75%);
|
||||
}
|
||||
|
||||
.clickable {
|
||||
cursor: pointer;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.clickable:hover,
|
||||
.plyr .plyr__control:hover,
|
||||
button:hover,
|
||||
.action:hover,
|
||||
.listing-item.drag-hover {
|
||||
box-shadow:
|
||||
inset 0 -3em 3em rgba(217, 217, 217, 0.211),
|
||||
0 0 0 2px var(--alt-background) !important;
|
||||
/* Adjust shadow values as needed */
|
||||
transform: scale(1.02);
|
||||
/* Slightly enlarges the element */
|
||||
box-shadow: inset 0 -3em 3em rgba(217, 217, 217, 0.211), 0 0 0 2px var(--alt-background) !important;
|
||||
/* Adjust shadow values as needed */
|
||||
transform: scale(1.02);
|
||||
/* Slightly enlarges the element */
|
||||
}
|
||||
|
||||
.flat-right {
|
||||
border-top-right-radius: 0 !important;
|
||||
border-bottom-right-radius: 0 !important;
|
||||
border-top-right-radius: 0 !important;
|
||||
border-bottom-right-radius: 0 !important;
|
||||
}
|
||||
|
||||
.flat-left {
|
||||
border-top-left-radius: 0 !important;
|
||||
border-bottom-left-radius: 0 !important;
|
||||
border-top-left-radius: 0 !important;
|
||||
border-bottom-left-radius: 0 !important;
|
||||
}
|
||||
|
||||
.form-grow {
|
||||
flex-grow: 1;
|
||||
width: inherit;
|
||||
min-width: 1em;
|
||||
flex-grow: 1;
|
||||
width: inherit;
|
||||
min-width: 1em;
|
||||
}
|
||||
|
||||
.form-flex-group {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.form-flex-group > .form-compact {
|
||||
min-height: 2em;
|
||||
width: unset !important;
|
||||
min-height: 2em;
|
||||
width: unset !important;
|
||||
}
|
||||
|
||||
.form-button {
|
||||
height: auto !important;
|
||||
height: auto !important
|
||||
}
|
||||
|
||||
.form-invalid {
|
||||
border-color: red !important;
|
||||
border-color: red !important;
|
||||
}
|
||||
|
||||
.form-compact {
|
||||
padding: 0em;
|
||||
padding: 0em;
|
||||
}
|
||||
|
||||
.form-dropdown {
|
||||
width: unset !important;
|
||||
width: unset !important;
|
||||
}
|
||||
|
||||
.divider {
|
||||
border-color: var(--divider);
|
||||
width: 90%;
|
||||
}
|
||||
|
||||
.tooltip-close-button {
|
||||
max-width: 4em !important;
|
||||
}
|
||||
+30
-67
@@ -49,8 +49,7 @@
|
||||
"clearCompleted": "اكتمل المسح",
|
||||
"showMore": "اعرض المزيد",
|
||||
"showLess": "اعرض أقل",
|
||||
"openParentFolder": "فتح المجلد الأصل",
|
||||
"selectedCount": "عدد العناصر المحددة لتنفيذ إجراء ما"
|
||||
"openParentFolder": "فتح المجلد الأصل"
|
||||
},
|
||||
"download": {
|
||||
"downloadFile": "Download File",
|
||||
@@ -85,15 +84,15 @@
|
||||
"click": "حدد الملف أو المجلد",
|
||||
"ctrl": {
|
||||
"click": "حدد أكثر من ملف او مجلد",
|
||||
"d": "تنزيل عناصر مختارة"
|
||||
"f": "إبدأ البحث",
|
||||
"s": "حمل الملف او المجلد في هذا المكان"
|
||||
},
|
||||
"del": "حذف البيانات المحددة",
|
||||
"doubleClick": "فتح المجلد او الملف",
|
||||
"esc": "مسح التحديد وإغلاق النافذة المنبثقة",
|
||||
"f1": "هذه المعلومات",
|
||||
"f2": "إعادة تسمية الملف",
|
||||
"help": "مساعدة",
|
||||
"f1": "إظهار موجه المساعدة",
|
||||
"description": "يمكنك عرض خيارات التنقل الأساسية أدناه. للحصول على معلومات إضافية، يرجى زيارة",
|
||||
"wiki": "ويكي متصفح الملفات الكمي"
|
||||
"help": "مساعدة"
|
||||
},
|
||||
"languages": {
|
||||
"he": "עברית",
|
||||
@@ -135,6 +134,7 @@
|
||||
"prompts": {
|
||||
"copy": "نسخ",
|
||||
"copyMessage": "رجاء حدد المكان لنسخ ملفاتك فيه:",
|
||||
"deleteMessageMultiple": "هل تريد بالتأكيد حذف {count} ملف؟",
|
||||
"deleteMessageSingle": "هل تريد بالتأكيد حذف هذا الملف/المجلد؟",
|
||||
"deleteTitle": "حذف الملفات",
|
||||
"displayName": "الإسم:",
|
||||
@@ -142,6 +142,7 @@
|
||||
"downloadMessage": "حدد إمتداد الملف المراد تحميله.",
|
||||
"error": "لقد حدث خطأ ما",
|
||||
"fileInfo": "معلومات الملف",
|
||||
"lastModified": "آخر تعديل{suffix}",
|
||||
"move": "نقل",
|
||||
"moveMessage": "إختر مكان جديد للملفات أو المجلدات المراد نقلها:",
|
||||
"newArchetype": "إنشاء منشور من المنشور الأصلي. الملف سيتم انشاءه في مجلد المحتويات.",
|
||||
@@ -149,7 +150,10 @@
|
||||
"newDirMessage": "رجاء أدخل اسم المجلد الجديد.",
|
||||
"newFile": "ملف جديد",
|
||||
"newFileMessage": "رجاء ادخل اسم الملف الجديد.",
|
||||
"numberDirs": "عدد المجلدات{suffix}",
|
||||
"numberFiles": "عدد الملفات{suffix}",
|
||||
"rename": "إعادة تسمية",
|
||||
"renameMessage": "إدراج اسم جديد لـ",
|
||||
"replace": "إستبدال",
|
||||
"replaceMessage": "أحد الملفات التي تحاول رفعها يتعارض مع ملف موجود بنفس الإسم. هل تريد إستبدال الملف الموجود؟\n",
|
||||
"schedule": "جدولة",
|
||||
@@ -157,6 +161,7 @@
|
||||
"show": "عرض",
|
||||
"size": "الحجم",
|
||||
"upload": "التحميل",
|
||||
"deleteMessageShare": "Are you sure you want to delete this share: {path} ?",
|
||||
"deleteUserMessage": "هل أنت متأكد من رغبتك في حذف هذا المستخدم؟",
|
||||
"filesSelected": "الملفات المحددة",
|
||||
"optionalPassword": "كلمة مرور اختيارية",
|
||||
@@ -167,6 +172,10 @@
|
||||
"dragAndDrop": "سحب الملفات وإفلاتها هنا",
|
||||
"completed": "مكتمل",
|
||||
"conflictsDetected": "التعارضات المكتشفة",
|
||||
"uploadSettingsChunked": "Max concurrent uploads: {maxConcurrentUpload}, chunk size: {uploadChunkSizeMb} MB",
|
||||
"uploadSettingsNoChunk": "Max concurrent uploads: {maxConcurrentUpload}, chunked upload disabled",
|
||||
"invalidName": "الاسم المقدم غير صالح: لا يمكن أن يحتوي على \"/\" أو \"\\\"",
|
||||
"source": "Source{suffix}",
|
||||
"downloadsLimit": "حد عدد التنزيلات",
|
||||
"maxBandwidth": "أقصى نطاق ترددي للتنزيل (كيلو بايت/ثانية)",
|
||||
"shareTheme": "سمة المشاركة",
|
||||
@@ -177,44 +186,22 @@
|
||||
"shareBanner": "مشاركة عنوان URL للشعار",
|
||||
"shareFavicon": "مشاركة عنوان URL المفضل",
|
||||
"optionalPasswordHelp": "إذا تم تعيينها، يجب على المستخدمين إدخال كلمة المرور هذه لعرض المحتوى المشترك.",
|
||||
"viewMode": "وضع العرض",
|
||||
"renameMessage": "أدخل اسماً جديداً:",
|
||||
"renameMessageInvalid": "لا يمكن أن يحتوي الملف على \"/\" أو \"\\\"",
|
||||
"source": "المصدر{suffix}",
|
||||
"deleteMessageMultiple": "هل تريد بالتأكيد حذف الملف (الملفات) {count} ؟",
|
||||
"deleteMessageShare": "هل أنت متأكد أنك تريد حذف هذه المشاركة؟ {path} ?",
|
||||
"lastModified": "آخر تعديل{suffix}",
|
||||
"numberDirs": "عدد الدلائل{suffix}",
|
||||
"numberFiles": "عدد الملفات{suffix}",
|
||||
"renameMessageConflict": "عنصر باسم \"{filename}\" موجود بالفعل!",
|
||||
"uploadSettingsChunked": "الحد الأقصى للتحميلات المتزامنة: {maxConcurrentUpload} ، حجم القطعة: {uploadChunkSizeMb} ميغابايت",
|
||||
"uploadSettingsNoChunk": "الحد الأقصى للتحميلات المتزامنة: {maxConcurrentUpload} ، تم تعطيل التحميل المقطّع"
|
||||
"viewMode": "وضع العرض"
|
||||
},
|
||||
"search": {
|
||||
"images": "الصور",
|
||||
"music": "الموسيقى",
|
||||
"pdf": "PDF",
|
||||
"pressToSearch": "Press enter to search...",
|
||||
"search": "البحث...",
|
||||
"typeToSearch": "Type to search...",
|
||||
"types": "الأنواع",
|
||||
"video": "فيديوهات",
|
||||
"path": "المسار:",
|
||||
"smallerThan": "أصغر من:",
|
||||
"largerThan": "أكبر من:",
|
||||
"helpText1": "يحدث البحث على كل حرف تكتبه (3 أحرف كحد أدنى لمصطلحات البحث).",
|
||||
"helpText2": "الفهرس: يستخدم البحث الفهرس الذي يتم تحديثه تلقائيًا على الفاصل الزمني الذي تم تكوينه (افتراضيًا: 5 دقائق). قد يؤدي البحث عندما يكون البرنامج قد بدأ للتو إلى نتائج غير مكتملة.",
|
||||
"noResults": "لم يتم العثور على نتائج في البحث المفهرس.",
|
||||
"onlyFolders": "المجلدات فقط",
|
||||
"onlyFiles": "الملفات فقط",
|
||||
"showOptions": "عرض الخيارات",
|
||||
"photos": "الصور",
|
||||
"audio": "الصوت",
|
||||
"videos": "مقاطع الفيديو",
|
||||
"documents": "المستندات",
|
||||
"archives": "المحفوظات",
|
||||
"number": "العدد",
|
||||
"searchContext": "سياق البحث: {context}",
|
||||
"notEnoughCharacters": "أحرف غير كافية للبحث (الحد الأدنى {minSearchLength})",
|
||||
"typeToSearch": "ابدأ الكتابة {minSearchLength} أو أكثر لبدء البحث."
|
||||
"helpText2": "الفهرس: يستخدم البحث الفهرس الذي يتم تحديثه تلقائيًا على الفاصل الزمني الذي تم تكوينه (افتراضيًا: 5 دقائق). قد يؤدي البحث عندما يكون البرنامج قد بدأ للتو إلى نتائج غير مكتملة."
|
||||
},
|
||||
"settings": {
|
||||
"admin": "Admin",
|
||||
@@ -227,8 +214,10 @@
|
||||
"avoidChanges": "(أتركه فارغاً إن لم ترد تغييره)",
|
||||
"branding": "Branding",
|
||||
"brandingDirectoryPath": "Branding directory path",
|
||||
"brandingHelp": "You can customize how your File Browser instance looks and feels by changing its name, replacing the logo, adding custom styles and even disable external links to GitHub.\nFor more information about custom branding, please check out the {0}.",
|
||||
"changePassword": "تغيير كلمة المرور",
|
||||
"commandRunner": "Command runner",
|
||||
"commandRunnerHelp": "Here you can set commands that are executed in the named events. You must write one per line. The environment variables {0} and {1} will be available, being {0} relative to {1}. For more information about this feature and the available environment variables, please read the {2}.",
|
||||
"commandsUpdated": "تم تحديث الأوامر",
|
||||
"createUserDir": "Auto create user home dir while adding new user",
|
||||
"customStylesheet": "ستايل مخصص",
|
||||
@@ -311,14 +300,7 @@
|
||||
"adminOptions": "خيارات المستخدم المسؤول",
|
||||
"shareDuration": "تنتهي صلاحيتها",
|
||||
"searchOptions": "خيارات البحث",
|
||||
"downloads": "التنزيلات",
|
||||
"systemAdmin": "النظام والإدارة",
|
||||
"configViewer": "عارض التكوين",
|
||||
"configViewerShowFull": "إظهار التكوين الكامل",
|
||||
"configViewerShowComments": "إظهار التعليقات",
|
||||
"configViewerLoadConfig": "تكوين التحميل",
|
||||
"brandingHelp": "يمكنك تخصيص شكل ومظهر مثيل متصفح الملفات الخاص بك عن طريق تغيير اسمه، واستبدال الشعار، وإضافة أنماط مخصصة، وحتى تعطيل الروابط الخارجية إلى GitHub.\nلمزيد من المعلومات حول العلامة التجارية المخصصة، يرجى الاطلاع على {0}.",
|
||||
"commandRunnerHelp": "هنا يمكنك تعيين الأوامر التي يتم تنفيذها في الأحداث المسماة. يجب عليك كتابة أمر واحد لكل سطر. سيكون متغيرا البيئة {0} و {1} متاحين، حيث {0} بالنسبة إلى {1}. لمزيد من المعلومات حول هذه الميزة ومتغيرات البيئة المتاحة، يرجى قراءة {2}."
|
||||
"downloads": "التنزيلات"
|
||||
},
|
||||
"sidebar": {
|
||||
"help": "مساعدة",
|
||||
@@ -376,21 +358,12 @@
|
||||
"hideNavButtons": "إخفاء أزرار التنقل",
|
||||
"hideNavButtonsDescription": "قم بإخفاء أزرار التنقل على شريط التنقل في المشاركة لإضفاء مظهر بسيط.",
|
||||
"viewModeDescription": "التخطيط الافتراضي للصفحة المشتركة.",
|
||||
"titleDefault": "Shared files - {title}",
|
||||
"descriptionDefault": "تم إرسال مشاركة إليك لعرضها أو تنزيلها.",
|
||||
"disableShareCard": "تعطيل بطاقة المشاركة المعطلة",
|
||||
"disableShareCardDescription": "قم بتعطيل بطاقة المشاركة على الصفحة المشتركة في الشريط الجانبي أو في أعلى الصفحة على الهاتف المحمول.",
|
||||
"disableSidebar": "تعطيل الشريط الجانبي",
|
||||
"disableSidebarDescription": "تعطيل الشريط الجانبي في الصفحة المشتركة.",
|
||||
"enforceDarkLightMode": "فرض وضع السمة",
|
||||
"enforceDarkLightModeDescription": "فرض وضع سمة معينة (داكنة أو فاتحة) لهذه المشاركة، وتجاوز تفضيلات المستخدم.",
|
||||
"default": "لا تفرض وضعاً",
|
||||
"dark": "داكن",
|
||||
"light": "خفيف",
|
||||
"enableOnlyOffice": "تمكين عارض OnlyOffice فقط",
|
||||
"enableOnlyOfficeDescription": "السماح بعرض ملفات المكتب باستخدام OnlyOffice في هذه المشاركة.",
|
||||
"enableOnlyOfficeEditing": "تمكين التحرير في المكتب فقط",
|
||||
"enableOnlyOfficeEditingDescription": "السماح بتحرير الملفات المكتبية باستخدام OnlyOffice في هذه الحصة.",
|
||||
"titleDefault": "الملفات المشتركة - {title}"
|
||||
"disableSidebarDescription": "تعطيل الشريط الجانبي في الصفحة المشتركة."
|
||||
},
|
||||
"api": {
|
||||
"title": "مفاتيح واجهة برمجة التطبيقات (API):",
|
||||
@@ -474,8 +447,8 @@
|
||||
"totalDenied": "مرفوض",
|
||||
"totalAllowed": "المسموح به",
|
||||
"all": "رفض الكل",
|
||||
"defaultBehaviorDescription": "يوضح هذا ما يحدث عندما يحاول المستخدم الوصول إلى مسار لا توجد فيه قواعد وصول محددة. تعني \"السماح\" أنه يمكن للمستخدمين الوصول بشكل افتراضي، وتعني \"الرفض\" أنه يتم حظر المستخدمين بشكل افتراضي ما لم يُسمح لهم صراحةً.",
|
||||
"defaultBehavior": "الإجراء الافتراضي{suffix}"
|
||||
"defaultBehavior": "Default action{suffix}",
|
||||
"defaultBehaviorDescription": "يوضح هذا ما يحدث عندما يحاول المستخدم الوصول إلى مسار لا توجد فيه قواعد وصول محددة. تعني \"السماح\" أنه يمكن للمستخدمين الوصول بشكل افتراضي، وتعني \"الرفض\" أنه يتم حظر المستخدمين بشكل افتراضي ما لم يُسمح لهم صراحةً."
|
||||
},
|
||||
"fileLoading": {
|
||||
"title": "التحميلات والتنزيلات",
|
||||
@@ -533,13 +506,7 @@
|
||||
"defaultThemeDescription": "افتراضي - السمة الافتراضية",
|
||||
"customTheme": "اختر المظهر الخاص بك",
|
||||
"showSelectMultiple": "إظهار تحديد متعدد في قائمة السياق على سطح المكتب",
|
||||
"showSelectMultipleDescription": "بشكل افتراضي، لا تحتوي قائمة سياق سطح المكتب على خيار \"تحديد متعدد\" مثل الهاتف المحمول. يظهر هذا الخيار دائمًا في قائمة السياق.",
|
||||
"defaultMediaPlayer": "استخدم مشغل الوسائط الأصلي في متصفحك",
|
||||
"defaultMediaPlayerDescription": "استخدم مشغل الوسائط الأصلي في متصفحك لملفات الفيديو والصوت بدلاً من مشغل الوسائط \"vue-plyr\" المضمن في متصفح FileBrowser.",
|
||||
"debugOfficeEditor": "تمكين وضع التصحيح فقط",
|
||||
"debugOfficeEditorDescription": "إظهار نافذة منبثقة للتصحيح مع معلومات إضافية في محرر OnlyOffice.",
|
||||
"fileViewerOptions": "خيارات عارض الملفات",
|
||||
"themeAndLanguage": "الموضوع واللغة"
|
||||
"showSelectMultipleDescription": "بشكل افتراضي، لا تحتوي قائمة سياق سطح المكتب على خيار \"تحديد متعدد\" مثل الهاتف المحمول. يظهر هذا الخيار دائمًا في قائمة السياق."
|
||||
},
|
||||
"colors": {
|
||||
"red": "أحمر",
|
||||
@@ -554,11 +521,7 @@
|
||||
"saveFailed": "فشل حفظ الملف. يرجى المحاولة مرة أخرى.",
|
||||
"saveAborted": "تم إجهاض الحفظ. اسم الملف غير مطابق للملف النشط.",
|
||||
"saveDisabled": "حفظ الملفات المشتركة غير مدعوم.",
|
||||
"saveAbortedMessage": "تم إجهاض عملية الحفظ. الملف النشط للتطبيق ({activeFile}) لا يتطابق مع الملف الذي تحاول حفظه ({tryingToSave}).",
|
||||
"syncFailed": "فشل مزامنة الحالة مع مسار \"{filename}\" بعد 5 محاولات. إجهاض إعداد المحرر لمنع تلف البيانات."
|
||||
},
|
||||
"player": {
|
||||
"LoopEnabled": "تم تمكين الحلقة",
|
||||
"LoopDisabled": "حلقة معطلة"
|
||||
"saveAbortedMessage": "Save operation aborted. The application's active file ({activeFile}) does not match the file you are trying to save ({tryingToSave}).",
|
||||
"syncFailed": "failed to sync state with the route for \"{filename}\" after 5 attempts. Aborting editor setup to prevent data corruption."
|
||||
}
|
||||
}
|
||||
+30
-67
@@ -49,8 +49,7 @@
|
||||
"clearCompleted": "Vymazání dokončeno",
|
||||
"showMore": "Zobrazit více",
|
||||
"showLess": "Zobrazit méně",
|
||||
"openParentFolder": "Otevření nadřazené složky",
|
||||
"selectedCount": "Počet položek vybraných k provedení akce"
|
||||
"openParentFolder": "Otevření nadřazené složky"
|
||||
},
|
||||
"download": {
|
||||
"downloadFile": "Stáhnout soubor",
|
||||
@@ -88,15 +87,15 @@
|
||||
"click": "označit soubor nebo složku",
|
||||
"ctrl": {
|
||||
"click": "označit více souborů nebo složek",
|
||||
"d": "stáhnout vybrané položky"
|
||||
"f": "otevře hledání",
|
||||
"s": "uložit soubor nebo stáhnout aktuální složku"
|
||||
},
|
||||
"del": "smazat označené položky",
|
||||
"doubleClick": "otevřít soubor nebo složku",
|
||||
"esc": "zrušit označení a/nebo zavřít dialog",
|
||||
"f1": "tato nápověda",
|
||||
"f2": "přejmenovat soubor",
|
||||
"help": "Nápověda",
|
||||
"f1": "zobrazit výzvu nápovědy",
|
||||
"description": "Základní možnosti navigace si můžete prohlédnout níže. Další informace naleznete na adrese",
|
||||
"wiki": "FileBrowser Quantum Wiki"
|
||||
"help": "Nápověda"
|
||||
},
|
||||
"languages": {
|
||||
"he": "עברית",
|
||||
@@ -162,26 +161,19 @@
|
||||
"hideNavButtons": "Skrytí navigačních tlačítek",
|
||||
"hideNavButtonsDescription": "Skryjte navigační tlačítka na panelu navigace ve sdíleném okně, abyste vytvořili minimalistický vzhled.",
|
||||
"viewModeDescription": "Výchozí rozložení sdílené stránky.",
|
||||
"titleDefault": "Shared files - {title}",
|
||||
"descriptionDefault": "Byl vám zaslán podíl, který si můžete prohlédnout nebo stáhnout.",
|
||||
"disableShareCard": "Zakázat sdílenou kartu",
|
||||
"disableShareCardDescription": "Zakázat kartu sdílení na sdílené stránce v postranním panelu nebo v horní části stránky na mobilním telefonu.",
|
||||
"disableSidebar": "Zakázat postranní panel",
|
||||
"disableSidebarDescription": "Zakázat postranní panel na sdílené stránce.",
|
||||
"enforceDarkLightMode": "Vynucení režimu tématu",
|
||||
"enforceDarkLightModeDescription": "Vynucení určitého režimu motivu (tmavého nebo světlého) pro tuto sdílenou složku, který je nadřazen uživatelským preferencím.",
|
||||
"default": "Nevynucujte režim",
|
||||
"dark": "Dark",
|
||||
"light": "Světlo",
|
||||
"enableOnlyOffice": "Povolení prohlížeče OnlyOffice",
|
||||
"enableOnlyOfficeDescription": "Povolit prohlížení kancelářských souborů pomocí OnlyOffice v této sdílené složce.",
|
||||
"enableOnlyOfficeEditing": "Povolení úprav OnlyOffice",
|
||||
"enableOnlyOfficeEditingDescription": "Povolit úpravy kancelářských souborů pomocí OnlyOffice v této sdílené složce.",
|
||||
"titleDefault": "Sdílené soubory - {title}"
|
||||
"disableSidebarDescription": "Zakázat postranní panel na sdílené stránce."
|
||||
},
|
||||
"prompts": {
|
||||
"copy": "Kopírovat",
|
||||
"copyMessage": "Vyberte místo, kam chcete soubory kopírovat:",
|
||||
"deleteMessageMultiple": "Opravdu chcete smazat {count} soubor(ů)?",
|
||||
"deleteMessageSingle": "Opravdu chcete smazat tento soubor/složku?",
|
||||
"deleteMessageShare": "Opravdu chcete smazat toto sdílení: {path} ?",
|
||||
"deleteUserMessage": "Opravdu chcete tohoto uživatele smazat?",
|
||||
"deleteTitle": "Smazat soubory",
|
||||
"displayName": "Zobrazované jméno:",
|
||||
@@ -191,6 +183,7 @@
|
||||
"fileInfo": "Informace o souboru",
|
||||
"selected": "označeno",
|
||||
"filesSelected": "souborů vybráno",
|
||||
"lastModified": "Poslední změna",
|
||||
"move": "Přesunout",
|
||||
"moveMessage": "Vyberte nové umístění pro soubor(y)/složku(y):",
|
||||
"newArchetype": "Vytvořit nový příspěvek podle archetypu. Váš soubor bude vytvořen ve složce content.",
|
||||
@@ -198,7 +191,10 @@
|
||||
"newDirMessage": "Zadejte název nové složky.",
|
||||
"newFile": "Nový soubor",
|
||||
"newFileMessage": "Zadejte název nového souboru.",
|
||||
"numberDirs": "Počet složek{suffix}",
|
||||
"numberFiles": "Počet souborů{suffix}",
|
||||
"rename": "Přejmenovat",
|
||||
"renameMessage": "Zadejte nový název pro",
|
||||
"replace": "Nahradit",
|
||||
"replaceMessage": "Jeden ze souborů, které se snažíte nahrát, má konflikt kvůli stejnému názvu. Chcete pokračovat v nahrávání nebo nahradit existující?",
|
||||
"schedule": "Naplánovat",
|
||||
@@ -213,6 +209,10 @@
|
||||
"dragAndDrop": "Přetáhněte sem soubory",
|
||||
"completed": "Dokončeno",
|
||||
"conflictsDetected": "Zjištěné konflikty",
|
||||
"uploadSettingsChunked": "Max concurrent uploads: {maxConcurrentUpload}, chunk size: {uploadChunkSizeMb} MB",
|
||||
"uploadSettingsNoChunk": "Max concurrent uploads: {maxConcurrentUpload}, chunked upload disabled",
|
||||
"invalidName": "Nesprávný zadaný název: nelze použít kontejner \"/\" nebo \"\\\".",
|
||||
"source": "Source{suffix}",
|
||||
"downloadsLimit": "Limit počtu stažení",
|
||||
"maxBandwidth": "Maximální šířka pásma stahování (kbps)",
|
||||
"shareTheme": "Sdílet téma",
|
||||
@@ -223,44 +223,22 @@
|
||||
"shareBanner": "Sdílet adresu URL banneru",
|
||||
"shareFavicon": "Sdílet adresu URL favicon",
|
||||
"optionalPasswordHelp": "Pokud je nastaveno, musí uživatelé toto heslo zadat, aby mohli sdílený obsah zobrazit.",
|
||||
"viewMode": "Režim zobrazení",
|
||||
"renameMessage": "Zadejte nový název:",
|
||||
"renameMessageInvalid": "Soubor nemůže obsahovat \"/\" nebo \"\\\".",
|
||||
"source": "Zdroj:{suffix}",
|
||||
"deleteMessageMultiple": "Opravdu chcete odstranit soubor(y) {count}?",
|
||||
"deleteMessageShare": "Jste si jisti, že chcete tuto sdílenou složku odstranit: {path} ?",
|
||||
"lastModified": "Poslední úprava{suffix}",
|
||||
"numberDirs": "Počet adresářů{suffix}",
|
||||
"numberFiles": "Počet souborů{suffix}",
|
||||
"renameMessageConflict": "Položka s názvem \"{filename}\" již existuje!",
|
||||
"uploadSettingsChunked": "Maximální souběžné nahrávání: {maxConcurrentUpload}, velikost chunků: {uploadChunkSizeMb} MB",
|
||||
"uploadSettingsNoChunk": "Maximální souběžné nahrávání: {maxConcurrentUpload}, vypnuto hromadné nahrávání"
|
||||
"viewMode": "Režim zobrazení"
|
||||
},
|
||||
"search": {
|
||||
"images": "Obrázky",
|
||||
"music": "Hudba",
|
||||
"pdf": "PDF",
|
||||
"pressToSearch": "Ve vyhledávání s indexem nebyly nalezeny žádné výsledky.",
|
||||
"search": "Hledat...",
|
||||
"typeToSearch": "Začněte psát pro hledání... (minimálně 3 znaky)",
|
||||
"path": "Cesta:",
|
||||
"types": "Typy",
|
||||
"video": "Video",
|
||||
"smallerThan": "Menší než:",
|
||||
"largerThan": "Větší než:",
|
||||
"helpText1": "Vyhledávání probíhá po každém napsaném znaku (minimálně 3 znaky pro hledaný výraz).",
|
||||
"helpText2": "Index: Vyhledávání využívá index, který se automaticky aktualizuje v nastaveném intervalu (výchozí: 5 minut). Hledání ihned po spuštění programu může vracet neúplné výsledky.",
|
||||
"noResults": "V indexovaném vyhledávání nebyly nalezeny žádné výsledky.",
|
||||
"onlyFolders": "Pouze složky",
|
||||
"onlyFiles": "Pouze soubory",
|
||||
"showOptions": "Zobrazit možnosti",
|
||||
"photos": "Fotografie",
|
||||
"audio": "Audio",
|
||||
"videos": "Videa",
|
||||
"documents": "Dokumenty",
|
||||
"archives": "Archivy",
|
||||
"number": "Číslo",
|
||||
"searchContext": "Kontext vyhledávání: {context}",
|
||||
"notEnoughCharacters": "Nedostatek znaků pro vyhledávání (min. {minSearchLength})",
|
||||
"typeToSearch": "Pro zahájení vyhledávání začněte psát {minSearchLength} nebo více znaků."
|
||||
"helpText2": "Index: Vyhledávání využívá index, který se automaticky aktualizuje v nastaveném intervalu (výchozí: 5 minut). Hledání ihned po spuštění programu může vracet neúplné výsledky."
|
||||
},
|
||||
"profileSettings": {
|
||||
"setDateFormat": "Nastavit formát data",
|
||||
@@ -311,13 +289,7 @@
|
||||
"defaultThemeDescription": "default - Výchozí téma",
|
||||
"customTheme": "Vyberte si téma",
|
||||
"showSelectMultiple": "Zobrazení výběru více položek v kontextové nabídce na ploše",
|
||||
"showSelectMultipleDescription": "Ve výchozím nastavení nemá kontextová nabídka na ploše možnost \"vybrat více\" jako v mobilním telefonu. Tato možnost se v kontextové nabídce zobrazuje vždy.",
|
||||
"defaultMediaPlayer": "Použití nativního přehrávače médií v prohlížeči",
|
||||
"defaultMediaPlayerDescription": "Pro video a zvukové soubory používejte nativní přehrávač médií prohlížeče, nikoli přehrávač médií vue-plyr, který je součástí aplikace FileBrowser.",
|
||||
"debugOfficeEditor": "Povolení režimu ladění OnlyOffice",
|
||||
"debugOfficeEditorDescription": "Zobrazení vyskakovacího okna pro ladění s dalšími informacemi v editoru OnlyOffice.",
|
||||
"fileViewerOptions": "Možnosti prohlížeče souborů",
|
||||
"themeAndLanguage": "Téma a jazyk"
|
||||
"showSelectMultipleDescription": "Ve výchozím nastavení nemá kontextová nabídka na ploše možnost \"vybrat více\" jako v mobilním telefonu. Tato možnost se v kontextové nabídce zobrazuje vždy."
|
||||
},
|
||||
"settings": {
|
||||
"enterPassword": "Zadejte heslo",
|
||||
@@ -344,8 +316,10 @@
|
||||
"avoidChanges": "Zaškrtněte také 'změnit heslo' pro použití tohoto hesla",
|
||||
"branding": "Branding",
|
||||
"brandingDirectoryPath": "Cesta k adresáři s brandingem",
|
||||
"brandingHelp": "Můžete si upravit vzhled File Browseru změnou názvu, loga, přidáním vlastních stylů nebo zakázáním externích odkazů na GitHub.\nPro více informací o úpravách vzhledu navštivte {0}.",
|
||||
"changePassword": "Změnit heslo",
|
||||
"commandRunner": "Spouštěč příkazů",
|
||||
"commandRunnerHelp": "Zde můžete nastavit příkazy, které se budou spouštět při vybraných událostech. Každý příkaz pište na nový řádek. Proměnné prostředí {0} a {1} budou dostupné; {0} je relativní k {1}. Pro více informací o této funkci a dostupných proměnných si přečtěte {2}.",
|
||||
"commandsUpdated": "Příkazy aktualizovány!",
|
||||
"createUserDir": "Automaticky vytvářet domovský adresář uživatele při přidání nového uživatele",
|
||||
"tusUploads": "Dávkové nahrávání",
|
||||
@@ -414,14 +388,7 @@
|
||||
"username": "Uživatelské jméno",
|
||||
"users": "Uživatelé",
|
||||
"adminOptions": "Možnosti administrátora",
|
||||
"downloads": "Ke stažení na",
|
||||
"systemAdmin": "Systém a správce",
|
||||
"configViewer": "Prohlížeč konfigurace",
|
||||
"configViewerShowFull": "Zobrazit úplnou konfiguraci",
|
||||
"configViewerShowComments": "Zobrazit komentáře",
|
||||
"configViewerLoadConfig": "Konfigurace načítání",
|
||||
"brandingHelp": "Vzhled instance Průzkumníka souborů si můžete přizpůsobit změnou názvu, nahrazením loga, přidáním vlastních stylů a dokonce i zakázáním externích odkazů na GitHub.\nDalší informace o vlastním brandingu naleznete na stránce {0}.",
|
||||
"commandRunnerHelp": "Zde můžete nastavit příkazy, které se provedou v pojmenovaných událostech. Na každý řádek musíte napsat jeden. K dispozici budou proměnné prostředí {0} a {1}, přičemž {0} bude relativní vůči {1}. Další informace o této funkci a dostupných proměnných prostředí naleznete na {2}."
|
||||
"downloads": "Ke stažení na"
|
||||
},
|
||||
"sidebar": {
|
||||
"help": "Nápověda",
|
||||
@@ -546,19 +513,15 @@
|
||||
"name": "Název",
|
||||
"accessManagement": "Správa přístupu",
|
||||
"all": "Odmítnout vše",
|
||||
"defaultBehaviorDescription": "To ukazuje, co se stane, když se uživatel pokusí získat přístup k cestě, kde neexistují žádná specifická pravidla přístupu. \"Povolit\" znamená, že uživatelé mají ve výchozím nastavení přístup, \"Odmítnout\" znamená, že uživatelé jsou ve výchozím nastavení blokováni, pokud není výslovně povoleno.",
|
||||
"defaultBehavior": "Výchozí akce{suffix}"
|
||||
"defaultBehavior": "Default action{suffix}",
|
||||
"defaultBehaviorDescription": "To ukazuje, co se stane, když se uživatel pokusí získat přístup k cestě, kde neexistují žádná specifická pravidla přístupu. \"Povolit\" znamená, že uživatelé mají ve výchozím nastavení přístup, \"Odmítnout\" znamená, že uživatelé jsou ve výchozím nastavení blokováni, pokud není výslovně povoleno."
|
||||
},
|
||||
"editor": {
|
||||
"uninitialized": "Nepodařilo se inicializovat editor. Prosím, načtěte jej znovu.",
|
||||
"saveFailed": "Soubor se nepodařilo uložit. Zkuste to prosím znovu.",
|
||||
"saveAborted": "Uložení přerušeno. Název souboru neodpovídá aktivnímu souboru.",
|
||||
"saveDisabled": "Ukládání sdílených souborů není podporováno.",
|
||||
"saveAbortedMessage": "Operace ukládání byla přerušena. Aktivní soubor aplikace ({activeFile}) neodpovídá souboru, který se pokoušíte uložit ({tryingToSave}).",
|
||||
"syncFailed": "po 5 pokusech se nepodařilo synchronizovat stav s trasou pro \"{filename}\". Přerušení nastavení editoru, aby se zabránilo poškození dat."
|
||||
},
|
||||
"player": {
|
||||
"LoopEnabled": "Povolená smyčka",
|
||||
"LoopDisabled": "Vypnutá smyčka"
|
||||
"saveAbortedMessage": "Save operation aborted. The application's active file ({activeFile}) does not match the file you are trying to save ({tryingToSave}).",
|
||||
"syncFailed": "failed to sync state with the route for \"{filename}\" after 5 attempts. Aborting editor setup to prevent data corruption."
|
||||
}
|
||||
}
|
||||
+30
-67
@@ -49,8 +49,7 @@
|
||||
"clearCompleted": "Abgeschlossene löschen",
|
||||
"showMore": "Mehr anzeigen",
|
||||
"showLess": "Weniger anzeigen",
|
||||
"openParentFolder": "Übergeordneten Ordner öffnen",
|
||||
"selectedCount": "Anzahl der zur Durchführung einer Aktion ausgewählten Elemente"
|
||||
"openParentFolder": "Übergeordneten Ordner öffnen"
|
||||
},
|
||||
"download": {
|
||||
"downloadFile": "Datei herunterladen",
|
||||
@@ -85,15 +84,15 @@
|
||||
"click": "Wähle Datei oder Ordner",
|
||||
"ctrl": {
|
||||
"click": "Markiere mehrere Dateien oder Ordner",
|
||||
"d": "ausgewählte Artikel herunterladen"
|
||||
"f": "Öffnet eine neue Suche",
|
||||
"s": "Speichert eine Datei oder einen Ordner am akutellen Ort"
|
||||
},
|
||||
"del": "Löscht die ausgewählten Elemente",
|
||||
"doubleClick": "Öffnet eine Datei oder einen Ordner",
|
||||
"esc": "Auswahl zurücksetzen und/oder Dialog schließen",
|
||||
"f1": "Diese Informationsseite",
|
||||
"f2": "Datei umbenennen",
|
||||
"help": "Hilfe",
|
||||
"f1": "Hilfeprompt anzeigen",
|
||||
"description": "Nachstehend finden Sie die grundlegenden Navigationsoptionen. Für zusätzliche Informationen besuchen Sie bitte",
|
||||
"wiki": "FileBrowser Quantum Wiki"
|
||||
"help": "Hilfe"
|
||||
},
|
||||
"languages": {
|
||||
"he": "עברית",
|
||||
@@ -135,13 +134,16 @@
|
||||
"prompts": {
|
||||
"copy": "Kopieren",
|
||||
"copyMessage": "Wählen Sie einen Zielort zum Kopieren:",
|
||||
"deleteMessageMultiple": "Sind Sie sicher, dass Sie {count} Datei(en) löschen möchten?",
|
||||
"deleteMessageSingle": "Sind Sie sicher, dass Sie diesen Ordner/diese Datei löschen möchten?",
|
||||
"deleteMessageShare": "Sind Sie sicher, dass Sie diese Freigabe löschen möchten ({path})?",
|
||||
"deleteTitle": "Dateien löschen",
|
||||
"displayName": "Anzeigename:",
|
||||
"download": "Dateien herunterladen",
|
||||
"downloadMessage": "Wählen Sie ein Format zum Herunterladen aus.",
|
||||
"error": "Etwas ist schief gelaufen",
|
||||
"fileInfo": "Dateiinformation",
|
||||
"lastModified": "Zuletzt geändert{suffix}",
|
||||
"move": "Verschieben",
|
||||
"moveMessage": "Wählen Sie einen neuen Ort für ihre Datei(en)/Ordner:",
|
||||
"newArchetype": "Erstelle neuen Beitrag auf Basis eines Archetyps. Ihre Datei wird im Inhalteordner erstellt.",
|
||||
@@ -149,7 +151,10 @@
|
||||
"newDirMessage": "Geben Sie den Namen des neuen Ordners an.",
|
||||
"newFile": "Neue Datei",
|
||||
"newFileMessage": "Geben Sie den Namen der neuen Datei an.",
|
||||
"numberDirs": "Anzahl der Ordner{suffix}",
|
||||
"numberFiles": "Anzahl der Dateien{suffix}",
|
||||
"rename": "Umbenennen",
|
||||
"renameMessage": "Geben Sie einen Namen ein für",
|
||||
"replace": "Ersetzen",
|
||||
"replaceMessage": "Eine der Datei mit dem gleichen Namen, wie die Sie hochladen wollen, existiert bereits. Soll die vorhandene Datei übersprungen oder ersetzt werden?\n",
|
||||
"schedule": "Zeitplan",
|
||||
@@ -167,6 +172,10 @@
|
||||
"dragAndDrop": "Dateien hierher ziehen und ablegen",
|
||||
"completed": "Abgeschlossen",
|
||||
"conflictsDetected": "Erkannte Widersprüche",
|
||||
"uploadSettingsChunked": "Max concurrent uploads: {maxConcurrentUpload}, chunk size: {uploadChunkSizeMb} MB",
|
||||
"uploadSettingsNoChunk": "Max concurrent uploads: {maxConcurrentUpload}, chunked upload disabled",
|
||||
"invalidName": "Ungültiger Name angegeben: kann nicht Container \"/\" oder \"\\\"",
|
||||
"source": "Source{suffix}",
|
||||
"downloadsLimit": "Limit für die Anzahl der Downloads",
|
||||
"maxBandwidth": "Maximale Download-Bandbreite (kbps)",
|
||||
"shareTheme": "Thema teilen",
|
||||
@@ -177,44 +186,22 @@
|
||||
"shareBanner": "Banner-URL freigeben",
|
||||
"shareFavicon": "Favicon-URL freigeben",
|
||||
"optionalPasswordHelp": "Falls festgelegt, müssen die Benutzer dieses Kennwort eingeben, um die freigegebenen Inhalte anzuzeigen.",
|
||||
"viewMode": "Ansichtsmodus",
|
||||
"renameMessage": "Geben Sie einen neuen Namen ein:",
|
||||
"renameMessageInvalid": "Eine Datei kann nicht den Container \"/\" oder \"\\\" enthalten.",
|
||||
"source": "Quelle{suffix}",
|
||||
"deleteMessageMultiple": "Sind Sie sicher, dass Sie die Datei(en) {count} löschen möchten?",
|
||||
"deleteMessageShare": "Sind Sie sicher, dass Sie diese Freigabe löschen möchten: {path} ?",
|
||||
"lastModified": "Zuletzt modifiziert{suffix}",
|
||||
"numberDirs": "Anzahl der Verzeichnisse{suffix}",
|
||||
"numberFiles": "Anzahl der Dateien{suffix}",
|
||||
"renameMessageConflict": "Ein Artikel mit dem Namen \"{filename}\" existiert bereits!",
|
||||
"uploadSettingsChunked": "Max. gleichzeitige Uploads: {maxConcurrentUpload}, Chunk-Größe: {uploadChunkSizeMb} MB",
|
||||
"uploadSettingsNoChunk": "Max. gleichzeitige Uploads: {maxConcurrentUpload}, Chunked Upload deaktiviert"
|
||||
"viewMode": "Ansichtsmodus"
|
||||
},
|
||||
"search": {
|
||||
"images": "Bilder",
|
||||
"music": "Musik",
|
||||
"pdf": "PDF",
|
||||
"pressToSearch": "Drücken Sie Enter, um zu suchen...",
|
||||
"search": "Suche...",
|
||||
"typeToSearch": "Tippen, um zu suchen...",
|
||||
"types": "Typen",
|
||||
"video": "Video",
|
||||
"path": "Pfad:",
|
||||
"smallerThan": "Kleiner als:",
|
||||
"largerThan": "Größer als:",
|
||||
"helpText1": "Die Suche erfolgt bei jedem Zeichen, das Sie eingeben (mindestens 3 Zeichen für Suchbegriffe).",
|
||||
"helpText2": "Der Index: Die Suche verwendet den Index, der automatisch in dem konfigurierten Intervall aktualisiert wird (Standard: 5 Minuten). Eine Suche direkt nach dem Start des Programms kann zu unvollständigen Ergebnissen führen.",
|
||||
"noResults": "Keine Ergebnisse in der indizierten Suche gefunden.",
|
||||
"onlyFolders": "Nur Ordner",
|
||||
"onlyFiles": "Nur Dateien",
|
||||
"showOptions": "Optionen anzeigen",
|
||||
"photos": "Fotos",
|
||||
"audio": "Audio",
|
||||
"videos": "Videos",
|
||||
"documents": "Dokumente",
|
||||
"archives": "Archiv",
|
||||
"number": "Nummer",
|
||||
"searchContext": "Suche im Kontext: {context}",
|
||||
"notEnoughCharacters": "Nicht genügend Zeichen für die Suche (min {minSearchLength})",
|
||||
"typeToSearch": "Beginnen Sie mit der Eingabe von {minSearchLength} oder mehr Zeichen, um die Suche zu starten."
|
||||
"helpText2": "Der Index: Die Suche verwendet den Index, der automatisch in dem konfigurierten Intervall aktualisiert wird (Standard: 5 Minuten). Eine Suche direkt nach dem Start des Programms kann zu unvollständigen Ergebnissen führen."
|
||||
},
|
||||
"settings": {
|
||||
"admin": "Admin",
|
||||
@@ -227,8 +214,10 @@
|
||||
"avoidChanges": "(leer lassen, um Änderungen zu vermeiden)",
|
||||
"branding": "Branding",
|
||||
"brandingDirectoryPath": "Branding-Verzeichnispfad",
|
||||
"brandingHelp": "Sie können das Erscheinungsbild Ihres FileBrowser anpassen, indem Sie den Namen ändern, das Logo austauschen oder eigene Branding definieren bzw. sogar externe Links zu GitHub deaktivieren.\nUm mehr Informationen zum Anpassen des Designs zu bekommen, gehen Sie bitte zu {0}.",
|
||||
"changePassword": "Kennwort ändern",
|
||||
"commandRunner": "Befehlseingabe",
|
||||
"commandRunnerHelp": "Hier könne Sie Befehle eintragen, welche bei den benannten Aktionen ausgeführt werden. Sie müssen pro Zeile jeweils einen Befehl eingeben. Die Umgebungsvariablen {0} und {1} sind verfügbar, wobei {0} relativ zu {1} ist. Für mehr Informationen über diese Funktion und die verfügbaren Umgebungsvariablen lesen Sie bitte die {2}.",
|
||||
"commandsUpdated": "Befehle aktualisiert!",
|
||||
"createUserDir": "Automatisches Erstellen des Home-Verzeichnisses beim Anlegen neuer Benutzer",
|
||||
"tusUploads": "Gestückeltes Hochladen",
|
||||
@@ -311,14 +300,7 @@
|
||||
"adminOptions": "Optionen für Verwaltungsbenutzer",
|
||||
"shareDuration": "Abgelaufen",
|
||||
"searchOptions": "Suchoptionen",
|
||||
"downloads": "Herunterladen",
|
||||
"systemAdmin": "System & Verwaltung",
|
||||
"configViewer": "Config Viewer",
|
||||
"configViewerShowFull": "Vollständige Konfiguration anzeigen",
|
||||
"configViewerShowComments": "Kommentare anzeigen",
|
||||
"configViewerLoadConfig": "Lastkonfiguration",
|
||||
"brandingHelp": "Sie können das Aussehen Ihrer Dateibrowser-Instanz anpassen, indem Sie den Namen ändern, das Logo ersetzen, eigene Stile hinzufügen und sogar externe Links zu GitHub deaktivieren.\nWeitere Informationen zum benutzerdefinierten Branding finden Sie unter {0}.",
|
||||
"commandRunnerHelp": "Hier können Sie Befehle eingeben, die bei den genannten Ereignissen ausgeführt werden. Sie müssen einen pro Zeile schreiben. Die Umgebungsvariablen {0} und {1} werden verfügbar sein, wobei {0} relativ zu {1} ist. Weitere Informationen über diese Funktion und die verfügbaren Umgebungsvariablen finden Sie unter {2}."
|
||||
"downloads": "Herunterladen"
|
||||
},
|
||||
"sidebar": {
|
||||
"help": "Hilfe",
|
||||
@@ -376,21 +358,12 @@
|
||||
"hideNavButtons": "Navigationsschaltflächen ausblenden",
|
||||
"hideNavButtonsDescription": "Blenden Sie die Navigationsschaltflächen auf der Navigationsleiste in der Freigabe aus, um ein minimalistisches Aussehen zu erzielen.",
|
||||
"viewModeDescription": "Standardlayout für die gemeinsame Seite.",
|
||||
"titleDefault": "Shared files - {title}",
|
||||
"descriptionDefault": "Sie haben eine Aktie zur Ansicht oder zum Download erhalten.",
|
||||
"disableShareCard": "Freigabekarte deaktivieren",
|
||||
"disableShareCardDescription": "Deaktivieren Sie die Freigabekarte auf der freigegebenen Seite in der Seitenleiste oder oben auf der Seite auf dem Handy.",
|
||||
"disableSidebar": "Seitenleiste deaktivieren",
|
||||
"disableSidebarDescription": "Deaktivieren Sie die Seitenleiste auf der gemeinsamen Seite.",
|
||||
"enforceDarkLightMode": "Themenmodus erzwingen",
|
||||
"enforceDarkLightModeDescription": "Erzwingt einen bestimmten Themenmodus (dunkel oder hell) für diese Freigabe und setzt die Benutzereinstellungen außer Kraft.",
|
||||
"default": "Keinen Modus erzwingen",
|
||||
"dark": "Dunkelheit",
|
||||
"light": "Licht",
|
||||
"enableOnlyOffice": "OnlyOffice viewer aktivieren",
|
||||
"enableOnlyOfficeDescription": "Erlauben Sie die Anzeige von Office-Dateien mit OnlyOffice in dieser Freigabe.",
|
||||
"enableOnlyOfficeEditing": "OnlyOffice-Bearbeitung aktivieren",
|
||||
"enableOnlyOfficeEditingDescription": "Erlauben Sie die Bearbeitung von Office-Dateien mit OnlyOffice in dieser Freigabe.",
|
||||
"titleDefault": "Gemeinsame Dateien - {title}"
|
||||
"disableSidebarDescription": "Deaktivieren Sie die Seitenleiste auf der gemeinsamen Seite."
|
||||
},
|
||||
"api": {
|
||||
"title": "API-Schlüssel:",
|
||||
@@ -474,8 +447,8 @@
|
||||
"totalDenied": "Verweigert",
|
||||
"totalAllowed": "Erlaubt",
|
||||
"all": "Alle ablehnen",
|
||||
"defaultBehaviorDescription": "Dies zeigt, was passiert, wenn ein Benutzer versucht, auf einen Pfad zuzugreifen, für den keine spezifischen Zugriffsregeln bestehen. Zulassen\" bedeutet, dass Benutzer standardmäßig Zugang haben, \"Verweigern\" bedeutet, dass Benutzer standardmäßig blockiert werden, wenn sie nicht ausdrücklich zugelassen sind.",
|
||||
"defaultBehavior": "Standard-Aktion{suffix}"
|
||||
"defaultBehavior": "Default action{suffix}",
|
||||
"defaultBehaviorDescription": "Dies zeigt, was passiert, wenn ein Benutzer versucht, auf einen Pfad zuzugreifen, für den keine spezifischen Zugriffsregeln bestehen. Zulassen\" bedeutet, dass Benutzer standardmäßig Zugang haben, \"Verweigern\" bedeutet, dass Benutzer standardmäßig blockiert werden, wenn sie nicht ausdrücklich zugelassen sind."
|
||||
},
|
||||
"fileLoading": {
|
||||
"title": "Uploads & Downloads",
|
||||
@@ -541,24 +514,14 @@
|
||||
"defaultThemeDescription": "default - Das Standardthema",
|
||||
"customTheme": "Wählen Sie Ihr Thema",
|
||||
"showSelectMultiple": "Mehrfachauswahl im Kontextmenü auf dem Desktop anzeigen",
|
||||
"showSelectMultipleDescription": "Standardmäßig verfügt das Desktop-Kontextmenü nicht über die Option \"Mehrere auswählen\" wie bei mobilen Geräten. Diese Option wird immer im Kontextmenü angezeigt.",
|
||||
"defaultMediaPlayer": "Verwenden Sie den nativen Media Player Ihres Browsers",
|
||||
"defaultMediaPlayerDescription": "Verwenden Sie für Video- und Audiodateien den nativen Mediaplayer Ihres Browsers und nicht den in FileBrowser enthaltenen 'vue-plyr' Mediaplayer.",
|
||||
"debugOfficeEditor": "OnlyOffice-Debug-Modus einschalten",
|
||||
"debugOfficeEditorDescription": "Ein Debug-Popup mit zusätzlichen Informationen im OnlyOffice-Editor anzeigen.",
|
||||
"fileViewerOptions": "Optionen des Dateibetrachters",
|
||||
"themeAndLanguage": "Thema und Sprache"
|
||||
"showSelectMultipleDescription": "Standardmäßig verfügt das Desktop-Kontextmenü nicht über die Option \"Mehrere auswählen\" wie bei mobilen Geräten. Diese Option wird immer im Kontextmenü angezeigt."
|
||||
},
|
||||
"editor": {
|
||||
"uninitialized": "Der Editor konnte nicht initialisiert werden. Bitte neu laden.",
|
||||
"saveFailed": "Datei konnte nicht gespeichert werden. Bitte versuchen Sie es erneut.",
|
||||
"saveAborted": "Speichern abgebrochen. Der Dateiname stimmt nicht mit der aktiven Datei überein.",
|
||||
"saveDisabled": "Das Speichern von gemeinsam genutzten Dateien wird nicht unterstützt.",
|
||||
"saveAbortedMessage": "Der Speichervorgang wurde abgebrochen. Die aktive Datei der Anwendung ({activeFile}) stimmt nicht mit der Datei überein, die Sie zu speichern versuchen ({tryingToSave}).",
|
||||
"syncFailed": "Nach 5 Versuchen ist es nicht gelungen, den Status mit der Route für \"{filename}\" zu synchronisieren. Abbruch der Editor-Einrichtung, um Datenbeschädigung zu vermeiden."
|
||||
},
|
||||
"player": {
|
||||
"LoopEnabled": "Schleife Aktiviert",
|
||||
"LoopDisabled": "Schleife Deaktiviert"
|
||||
"saveAbortedMessage": "Save operation aborted. The application's active file ({activeFile}) does not match the file you are trying to save ({tryingToSave}).",
|
||||
"syncFailed": "failed to sync state with the route for \"{filename}\" after 5 attempts. Aborting editor setup to prevent data corruption."
|
||||
}
|
||||
}
|
||||
+30
-67
@@ -49,8 +49,7 @@
|
||||
"clearCompleted": "Εκκαθάριση ολοκληρώθηκε",
|
||||
"showMore": "Εμφάνιση περισσότερων",
|
||||
"showLess": "Εμφάνιση λιγότερων",
|
||||
"openParentFolder": "Άνοιγμα γονικού φακέλου",
|
||||
"selectedCount": "Αριθμός επιλεγμένων στοιχείων για την εκτέλεση μιας ενέργειας"
|
||||
"openParentFolder": "Άνοιγμα γονικού φακέλου"
|
||||
},
|
||||
"download": {
|
||||
"downloadFile": "Λήψη αρχείου",
|
||||
@@ -88,15 +87,15 @@
|
||||
"click": "επιλέξτε αρχείο ή φάκελο",
|
||||
"ctrl": {
|
||||
"click": "επιλογή πολλαπλών αρχείων ή φακέλων",
|
||||
"d": "λήψη επιλεγμένων στοιχείων"
|
||||
"f": "ανοίγει την αναζήτηση",
|
||||
"s": "αποθηκεύει ένα αρχείο ή εκκινεί λήψη του φακέλου στον οποίο βρίσκεστε"
|
||||
},
|
||||
"del": "διαγραφή επιλεγμένων στοιχείων",
|
||||
"doubleClick": "ανοίγει ένα αρχείο ή φάκελο",
|
||||
"esc": "καθαρίζει την επιλογή ή/και κλείνει το παράθυρο",
|
||||
"f1": "αυτή η πληροφορία",
|
||||
"f2": "μετονομασία αρχείου",
|
||||
"help": "Βοήθεια",
|
||||
"f1": "Εμφάνιση προτροπής βοήθειας",
|
||||
"description": "Μπορείτε να δείτε τις βασικές επιλογές πλοήγησης παρακάτω. Για πρόσθετες πληροφορίες, επισκεφθείτε τη διεύθυνση",
|
||||
"wiki": "FileBrowser Quantum Wiki"
|
||||
"help": "Βοήθεια"
|
||||
},
|
||||
"languages": {
|
||||
"he": "עברית",
|
||||
@@ -138,13 +137,16 @@
|
||||
"prompts": {
|
||||
"copy": "Αντιγραφή",
|
||||
"copyMessage": "Επιλέξτε τοποθεσία για αντιγραφή των αρχείων σας:",
|
||||
"deleteMessageMultiple": "Είστε σίγουροι ότι θέλετε να διαγράψετε {count} αρχεία;",
|
||||
"deleteMessageSingle": "Είστε σίγουροι ότι θέλετε να διαγράψετε αυτό το αρχείο/φάκελο;",
|
||||
"deleteMessageShare": "Είστε σίγουροι ότι θέλετε να διαγράψετε αυτή την κοινοποίηση ({path});",
|
||||
"deleteTitle": "Διαγραφή αρχείων",
|
||||
"displayName": "Εμφάνιση ονόματος:",
|
||||
"download": "Λήψη αρχείων",
|
||||
"downloadMessage": "Επιλέξτε τη μορφή που θέλετε να λάβετε.",
|
||||
"error": "Προέκυψε κάποιο σφάλμα",
|
||||
"fileInfo": "Πληροφορίες αρχείου",
|
||||
"lastModified": "Τελευταία τροποποίηση{suffix}",
|
||||
"move": "Μετακίνηση",
|
||||
"moveMessage": "Επιλέξτε νέα τοποθεσία για τα αρχεία / τους φακέλους σας:",
|
||||
"newArchetype": "Δημιουργία νέας ανάρτησης με βάση έναν αρχέτυπο. Το αρχείο σας θα δημιουργηθεί στο φάκελο περιεχομένου.",
|
||||
@@ -152,7 +154,10 @@
|
||||
"newDirMessage": "Γράψτε το όνομα του νέου φακέλου.",
|
||||
"newFile": "Νέο αρχείο",
|
||||
"newFileMessage": "Γράψτε το όνομα του νέου αρχείου.",
|
||||
"numberDirs": "Αριθμός φακέλων{suffix}",
|
||||
"numberFiles": "Αριθμός αρχείων{suffix}",
|
||||
"rename": "Μετονομασία",
|
||||
"renameMessage": "Εισαγάγετε ένα νέο όνομα για το",
|
||||
"replace": "Αντικατάσταση",
|
||||
"replaceMessage": "Ένα από τα αρχεία που προσπαθείτε να μεταφορτώσετε δημιουργεί σύγκρουση με υπάρχον αρχείο λόγω του ονόματός του. Θέλετε να συνεχίσετε τη μεταφόρτωση ή να αντικαταστήσετε το υπάρχον;\n",
|
||||
"schedule": "Προγραμματισμός",
|
||||
@@ -170,6 +175,10 @@
|
||||
"dragAndDrop": "Σύρετε και αφήστε αρχεία εδώ",
|
||||
"completed": "Ολοκληρωμένο",
|
||||
"conflictsDetected": "Ανιχνεύονται συγκρούσεις",
|
||||
"uploadSettingsChunked": "Max concurrent uploads: {maxConcurrentUpload}, chunk size: {uploadChunkSizeMb} MB",
|
||||
"uploadSettingsNoChunk": "Max concurrent uploads: {maxConcurrentUpload}, chunked upload disabled",
|
||||
"invalidName": "Μη έγκυρο όνομα: δεν μπορεί να γίνει container \"/\" ή \"\\\"",
|
||||
"source": "Source{suffix}",
|
||||
"downloadsLimit": "Όριο αριθμού λήψεων",
|
||||
"maxBandwidth": "Μέγιστο εύρος ζώνης λήψης (kbps)",
|
||||
"shareTheme": "Μοιραστείτε το θέμα",
|
||||
@@ -180,44 +189,22 @@
|
||||
"shareBanner": "Μοιραστείτε το URL του banner",
|
||||
"shareFavicon": "Μοιραστείτε το URL favicon",
|
||||
"optionalPasswordHelp": "Εάν οριστεί, οι χρήστες πρέπει να εισάγουν αυτόν τον κωδικό πρόσβασης για να προβάλουν το κοινόχρηστο περιεχόμενο.",
|
||||
"viewMode": "Λειτουργία προβολής",
|
||||
"renameMessage": "Εισάγετε ένα νέο όνομα:",
|
||||
"renameMessageInvalid": "Ένα αρχείο δεν μπορεί να περιέχει \"/\" ή \"\\\"",
|
||||
"source": "Πηγή{suffix}",
|
||||
"deleteMessageMultiple": "Είστε σίγουροι ότι θέλετε να διαγράψετε το/τα αρχείο/α {count};",
|
||||
"deleteMessageShare": "Είστε σίγουροι ότι θέλετε να διαγράψετε αυτό το μερίδιο: {path} ?",
|
||||
"lastModified": "Τελευταία τροποποίηση{suffix}",
|
||||
"numberDirs": "Αριθμός καταλόγων{suffix}",
|
||||
"numberFiles": "Αριθμός αρχείων{suffix}",
|
||||
"renameMessageConflict": "Ένα στοιχείο με όνομα \"{filename}\" υπάρχει ήδη!",
|
||||
"uploadSettingsChunked": "Μέγιστες ταυτόχρονες μεταφορτώσεις: {maxConcurrentUpload}, μέγεθος τεμαχίου: {uploadChunkSizeMb} MB",
|
||||
"uploadSettingsNoChunk": "Μέγιστες ταυτόχρονες μεταφορτώσεις: {maxConcurrentUpload}, chunked upload disabled"
|
||||
"viewMode": "Λειτουργία προβολής"
|
||||
},
|
||||
"search": {
|
||||
"images": "Εικόνες",
|
||||
"music": "Μουσική",
|
||||
"pdf": "PDF",
|
||||
"pressToSearch": "Πατήστε Enter για αναζήτηση…",
|
||||
"search": "Αναζήτηση…",
|
||||
"typeToSearch": "Πληκτρολογήστε για αναζήτηση…",
|
||||
"types": "Τύποι",
|
||||
"video": "Βίντεο",
|
||||
"path": "Μονοπάτι:",
|
||||
"smallerThan": "Μικρότερο από:",
|
||||
"largerThan": "Μεγαλύτερο από:",
|
||||
"helpText1": "Η αναζήτηση πραγματοποιείται σε κάθε χαρακτήρα που πληκτρολογείτε (ελάχιστο όριο 3 χαρακτήρων για τους όρους αναζήτησης).",
|
||||
"helpText2": "Ο δείκτης: Η αναζήτηση χρησιμοποιεί το ευρετήριο το οποίο ενημερώνεται αυτόματα στο ρυθμισμένο διάστημα (προεπιλογή: 5 λεπτά). Η αναζήτηση όταν το πρόγραμμα έχει μόλις ξεκινήσει μπορεί να οδηγήσει σε ελλιπή αποτελέσματα.",
|
||||
"noResults": "Δεν βρέθηκαν αποτελέσματα στην ευρετηριασμένη αναζήτηση.",
|
||||
"onlyFolders": "Μόνο φάκελοι",
|
||||
"onlyFiles": "Μόνο αρχεία",
|
||||
"showOptions": "Εμφάνιση επιλογών",
|
||||
"photos": "Φωτογραφίες",
|
||||
"audio": "Ήχος",
|
||||
"videos": "Βίντεο",
|
||||
"documents": "Έγγραφα",
|
||||
"archives": "Αρχεία",
|
||||
"number": "Αριθμός",
|
||||
"searchContext": "Πλαίσιο αναζήτησης: {context}",
|
||||
"notEnoughCharacters": "Δεν υπάρχουν αρκετοί χαρακτήρες για αναζήτηση (min {minSearchLength})",
|
||||
"typeToSearch": "Αρχίστε να πληκτρολογείτε {minSearchLength} ή περισσότερους χαρακτήρες για να ξεκινήσετε την αναζήτηση."
|
||||
"helpText2": "Ο δείκτης: Η αναζήτηση χρησιμοποιεί το ευρετήριο το οποίο ενημερώνεται αυτόματα στο ρυθμισμένο διάστημα (προεπιλογή: 5 λεπτά). Η αναζήτηση όταν το πρόγραμμα έχει μόλις ξεκινήσει μπορεί να οδηγήσει σε ελλιπή αποτελέσματα."
|
||||
},
|
||||
"settings": {
|
||||
"admin": "Διαχειριστής",
|
||||
@@ -230,8 +217,10 @@
|
||||
"avoidChanges": "(αφήστε το κενό για αποφυγή αλλαγών)",
|
||||
"branding": "Εξατομίκευση",
|
||||
"brandingDirectoryPath": "Διαδρομή φακέλου εξατομίκευσης",
|
||||
"brandingHelp": "Μπορείτε να προσαρμόσετε την εμφάνισης της εφαρμογής File Browser αλλάζοντας το όνομά της, αντικαθιστώντας το λογότυπό της, προσθέτοντας προσαρμοσμένα στυλ και ακόμα και απενεργοποιώντας εξωτερικούς συνδέσμους προς το GitHub.\nΓια περισσότερες πληροφορίες σχετικά με αυτές τις προσαρμογές, ελέγξτε το {0}.",
|
||||
"changePassword": "Αλλαγή κωδικού πρόσβασης",
|
||||
"commandRunner": "Εκτέλεση εντολών",
|
||||
"commandRunnerHelp": "Εδώ μπορείτε να ορίσετε εντολές που εκτελούνται στα ονομασμένα γεγονότα και δραστηριότητες. Πρέπει να γράψετε μία εντολή ανά γραμμή. Οι μεταβλητές περιβάλλοντος {0} και {1} θα είναι διαθέσιμες, και θα είναι {0} σχετικές με το {1}. Για περισσότερες πληροφορίες σχετικά με αυτή τη λειτουργία και τις διαθέσιμες μεταβλητές περιβάλλοντος, παρακαλώ διαβάστε το {2}.",
|
||||
"commandsUpdated": "Οι εντολές ενημερώθηκαν!",
|
||||
"createUserDir": "Αυτόματη δημιουργία φακέλου χρήστη κατά την προσθήκη νέου χρήστη",
|
||||
"tusUploads": "Τμηματικές μεταφορές αρχείων",
|
||||
@@ -314,14 +303,7 @@
|
||||
"adminOptions": "Επιλογές χρήστη διαχειριστή",
|
||||
"shareDuration": "Λήγει",
|
||||
"searchOptions": "Επιλογές αναζήτησης",
|
||||
"downloads": "Λήψεις",
|
||||
"systemAdmin": "Σύστημα & Διαχείριση",
|
||||
"configViewer": "Config Viewer",
|
||||
"configViewerShowFull": "Εμφάνιση πλήρων ρυθμίσεων",
|
||||
"configViewerShowComments": "Εμφάνιση σχολίων",
|
||||
"configViewerLoadConfig": "Φόρτωση Config",
|
||||
"brandingHelp": "Μπορείτε να προσαρμόσετε την εμφάνιση και την αίσθηση του File Browser αλλάζοντας το όνομά του, αντικαθιστώντας το λογότυπο, προσθέτοντας προσαρμοσμένα στυλ και ακόμη και απενεργοποιώντας τους εξωτερικούς συνδέσμους προς το GitHub.\nΓια περισσότερες πληροφορίες σχετικά με την προσαρμοσμένη επωνυμία, ανατρέξτε στη διεύθυνση {0}.",
|
||||
"commandRunnerHelp": "Εδώ μπορείτε να ορίσετε τις εντολές που εκτελούνται στα συμβάντα που ονομάζονται. Πρέπει να γράψετε μία ανά γραμμή. Οι περιβαλλοντικές μεταβλητές {0} και {1} θα είναι διαθέσιμες, ενώ η {0} θα είναι σχετική με την {1}. Για περισσότερες πληροφορίες σχετικά με αυτή τη λειτουργία και τις διαθέσιμες μεταβλητές περιβάλλοντος, διαβάστε το {2}."
|
||||
"downloads": "Λήψεις"
|
||||
},
|
||||
"sidebar": {
|
||||
"help": "Βοήθεια",
|
||||
@@ -376,21 +358,12 @@
|
||||
"hideNavButtons": "Απόκρυψη κουμπιών πλοήγησης",
|
||||
"hideNavButtonsDescription": "Κρύψτε τα κουμπιά πλοήγησης στη γραμμή πλοήγησης στο μερίδιο για να δημιουργήσετε μια μινιμαλιστική εμφάνιση.",
|
||||
"viewModeDescription": "Προεπιλεγμένη διάταξη για την κοινή σελίδα.",
|
||||
"titleDefault": "Shared files - {title}",
|
||||
"descriptionDefault": "Σας έχει αποσταλεί ένα share για να το δείτε ή να το κατεβάσετε.",
|
||||
"disableShareCard": "Απενεργοποίηση κάρτας κοινής χρήσης",
|
||||
"disableShareCardDescription": "Απενεργοποιήστε την κάρτα κοινής χρήσης στην κοινόχρηστη σελίδα στην πλαϊνή μπάρα ή στην κορυφή της σελίδας στο κινητό.",
|
||||
"disableSidebar": "Απενεργοποίηση πλευρικής μπάρας",
|
||||
"disableSidebarDescription": "Απενεργοποιήστε την πλαϊνή μπάρα στην κοινή σελίδα.",
|
||||
"enforceDarkLightMode": "Επιβολή λειτουργίας θέματος",
|
||||
"enforceDarkLightModeDescription": "Επιβάλλει μια συγκεκριμένη λειτουργία θέματος (σκούρο ή ανοιχτό) για αυτό το κοινόχρηστο, παρακάμπτοντας τις προτιμήσεις του χρήστη.",
|
||||
"default": "Μην επιβάλλετε μια λειτουργία",
|
||||
"dark": "Σκούρο",
|
||||
"light": "Φως",
|
||||
"enableOnlyOffice": "Ενεργοποίηση της προβολής OnlyOffice",
|
||||
"enableOnlyOfficeDescription": "Επιτρέψτε την προβολή αρχείων γραφείου χρησιμοποιώντας το OnlyOffice σε αυτό το κοινόχρηστο χώρο.",
|
||||
"enableOnlyOfficeEditing": "Ενεργοποίηση της επεξεργασίας OnlyOffice",
|
||||
"enableOnlyOfficeEditingDescription": "Επιτρέψτε την επεξεργασία αρχείων γραφείου χρησιμοποιώντας το OnlyOffice σε αυτό το κοινόχρηστο χώρο.",
|
||||
"titleDefault": "Κοινόχρηστα αρχεία - {title}"
|
||||
"disableSidebarDescription": "Απενεργοποιήστε την πλαϊνή μπάρα στην κοινή σελίδα."
|
||||
},
|
||||
"api": {
|
||||
"title": "Κλειδιά API:",
|
||||
@@ -474,8 +447,8 @@
|
||||
"totalDenied": "Απορρίπτεται",
|
||||
"totalAllowed": "Επιτρεπόμενο",
|
||||
"all": "Άρνηση όλων",
|
||||
"defaultBehaviorDescription": "Αυτό δείχνει τι συμβαίνει όταν ένας χρήστης προσπαθεί να αποκτήσει πρόσβαση σε μια διαδρομή όπου δεν υπάρχουν συγκεκριμένοι κανόνες πρόσβασης. 'Allow' σημαίνει ότι οι χρήστες μπορούν να έχουν πρόσβαση από προεπιλογή, 'Deny' σημαίνει ότι οι χρήστες αποκλείονται από προεπιλογή, εκτός αν επιτρέπεται ρητά.",
|
||||
"defaultBehavior": "Προεπιλεγμένη ενέργεια{suffix}"
|
||||
"defaultBehavior": "Default action{suffix}",
|
||||
"defaultBehaviorDescription": "Αυτό δείχνει τι συμβαίνει όταν ένας χρήστης προσπαθεί να αποκτήσει πρόσβαση σε μια διαδρομή όπου δεν υπάρχουν συγκεκριμένοι κανόνες πρόσβασης. 'Allow' σημαίνει ότι οι χρήστες μπορούν να έχουν πρόσβαση από προεπιλογή, 'Deny' σημαίνει ότι οι χρήστες αποκλείονται από προεπιλογή, εκτός αν επιτρέπεται ρητά."
|
||||
},
|
||||
"fileLoading": {
|
||||
"title": "Ανεβάσματα & λήψεις",
|
||||
@@ -541,24 +514,14 @@
|
||||
"defaultThemeDescription": "default - Το προεπιλεγμένο θέμα",
|
||||
"customTheme": "Επιλέξτε το θέμα σας",
|
||||
"showSelectMultiple": "Εμφάνιση πολλαπλών επιλογών στο μενού περιβάλλοντος στην επιφάνεια εργασίας",
|
||||
"showSelectMultipleDescription": "Από προεπιλογή, το μενού περιβάλλοντος της επιφάνειας εργασίας δεν διαθέτει την επιλογή \"επιλογή πολλαπλών\" όπως το κινητό. Αυτή η επιλογή εμφανίζεται πάντα στο μενού περιβάλλοντος.",
|
||||
"defaultMediaPlayer": "Χρησιμοποιήστε το εγγενές πρόγραμμα αναπαραγωγής πολυμέσων του προγράμματος περιήγησής σας",
|
||||
"defaultMediaPlayerDescription": "Χρησιμοποιήστε το εγγενές πρόγραμμα αναπαραγωγής πολυμέσων του προγράμματος περιήγησης για αρχεία βίντεο και ήχου αντί για το πρόγραμμα αναπαραγωγής πολυμέσων \"vue-plyr\" που περιλαμβάνεται στο FileBrowser.",
|
||||
"debugOfficeEditor": "Ενεργοποίηση λειτουργίας εντοπισμού σφαλμάτων OnlyOffice",
|
||||
"debugOfficeEditorDescription": "Εμφάνιση ενός αναδυόμενου παραθύρου εντοπισμού σφαλμάτων με πρόσθετες πληροφορίες στον επεξεργαστή OnlyOffice.",
|
||||
"fileViewerOptions": "Επιλογές προβολής αρχείων",
|
||||
"themeAndLanguage": "Θέμα & Γλώσσα"
|
||||
"showSelectMultipleDescription": "Από προεπιλογή, το μενού περιβάλλοντος της επιφάνειας εργασίας δεν διαθέτει την επιλογή \"επιλογή πολλαπλών\" όπως το κινητό. Αυτή η επιλογή εμφανίζεται πάντα στο μενού περιβάλλοντος."
|
||||
},
|
||||
"editor": {
|
||||
"uninitialized": "Απέτυχε η αρχικοποίηση του συντάκτη. Παρακαλούμε επαναφορτώστε.",
|
||||
"saveFailed": "Απέτυχε η αποθήκευση του αρχείου. Προσπαθήστε ξανά.",
|
||||
"saveAborted": "Η αποθήκευση διακόπηκε. Το όνομα του αρχείου δεν ταιριάζει με το ενεργό αρχείο.",
|
||||
"saveDisabled": "Δεν υποστηρίζεται η αποθήκευση κοινόχρηστων αρχείων.",
|
||||
"saveAbortedMessage": "Διακοπή της λειτουργίας αποθήκευσης. Το ενεργό αρχείο της εφαρμογής ({activeFile}) δεν ταιριάζει με το αρχείο που προσπαθείτε να αποθηκεύσετε ({tryingToSave}).",
|
||||
"syncFailed": "απέτυχε να συγχρονίσει την κατάσταση με τη διαδρομή για το \"{filename}\" μετά από 5 προσπάθειες. Διακοπή της ρύθμισης του συντάκτη για να αποφευχθεί η αλλοίωση των δεδομένων."
|
||||
},
|
||||
"player": {
|
||||
"LoopEnabled": "Ενεργοποίηση βρόχου",
|
||||
"LoopDisabled": "Βρόχος απενεργοποιημένος"
|
||||
"saveAbortedMessage": "Save operation aborted. The application's active file ({activeFile}) does not match the file you are trying to save ({tryingToSave}).",
|
||||
"syncFailed": "failed to sync state with the route for \"{filename}\" after 5 attempts. Aborting editor setup to prevent data corruption."
|
||||
}
|
||||
}
|
||||
+13
-50
@@ -49,8 +49,7 @@
|
||||
"clearCompleted": "Clear completed",
|
||||
"showMore": "Show more",
|
||||
"showLess": "Show less",
|
||||
"openParentFolder": "Open parent folder",
|
||||
"selectedCount": "Number of items selected to perform an action"
|
||||
"openParentFolder": "Open parent folder"
|
||||
},
|
||||
"download": {
|
||||
"downloadFile": "Download File",
|
||||
@@ -88,15 +87,15 @@
|
||||
"click": "select file or directory",
|
||||
"ctrl": {
|
||||
"click": "select multiple files or directories",
|
||||
"d": "download selected items"
|
||||
"f": "opens search",
|
||||
"s": "save a file or download the directory where you are"
|
||||
},
|
||||
"del": "delete selected items",
|
||||
"doubleClick": "open a file or directory",
|
||||
"esc": "clear selection and/or close the prompt",
|
||||
"f1": "this information",
|
||||
"f2": "rename file",
|
||||
"f1": "show help prompt",
|
||||
"help": "Help",
|
||||
"description": "You can view the basic naviagation options below. For additional information, please visit",
|
||||
"wiki": "FileBrowser Quantum Wiki"
|
||||
"help": "Help"
|
||||
},
|
||||
"languages": {
|
||||
"he": "עברית",
|
||||
@@ -167,16 +166,7 @@
|
||||
"disableShareCard": "Disable share card",
|
||||
"disableShareCardDescription": "Disable the share card on the shared page in the sidebar or at the top of the page on mobile.",
|
||||
"disableSidebar": "Disable sidebar",
|
||||
"disableSidebarDescription": "Disable the sidebar on the shared page.",
|
||||
"enforceDarkLightMode": "Enforce theme mode",
|
||||
"enforceDarkLightModeDescription": "Force a specific theme mode (dark or light) for this share, overriding user preferences.",
|
||||
"default": "Do not enforce a mode",
|
||||
"dark": "Dark",
|
||||
"light": "Light",
|
||||
"enableOnlyOffice": "Enable OnlyOffice viewer",
|
||||
"enableOnlyOfficeDescription": "Allow viewing office files using OnlyOffice in this share.",
|
||||
"enableOnlyOfficeEditing": "Enable OnlyOffice editing",
|
||||
"enableOnlyOfficeEditingDescription": "Allow editing of office files using OnlyOffice in this share."
|
||||
"disableSidebarDescription": "Disable the sidebar on the shared page."
|
||||
},
|
||||
"prompts": {
|
||||
"downloadsLimit": "Number of downloads limit",
|
||||
@@ -190,6 +180,7 @@
|
||||
"shareBanner": "Share banner URL",
|
||||
"shareFavicon": "Share favicon URL",
|
||||
"source": "Source{suffix}",
|
||||
"invalidName": "Invalid name provided: cannot container \"/\" or \"\\\"",
|
||||
"destinationSource": "Destination Source:",
|
||||
"copy": "Copy",
|
||||
"copyMessage": "Choose the place to copy your files:",
|
||||
@@ -218,9 +209,7 @@
|
||||
"numberDirs": "Number of directories{suffix}",
|
||||
"numberFiles": "Number of files{suffix}",
|
||||
"rename": "Rename",
|
||||
"renameMessageConflict": "An item named \"{filename}\" already exists!",
|
||||
"renameMessage": "Enter a new name:",
|
||||
"renameMessageInvalid": "A file can't container \"/\" or \"\\\"",
|
||||
"renameMessage": "An item named \"{filename}\" already exists! Enter a new name:",
|
||||
"replace": "Replace",
|
||||
"replaceMessage": "One of the files you're trying to upload is conflicting because of its name. Do you wish to continue to upload or replace the existing one?\n",
|
||||
"schedule": "Schedule",
|
||||
@@ -237,23 +226,12 @@
|
||||
"conflictsDetected": "Conflicts detected"
|
||||
},
|
||||
"search": {
|
||||
"searchContext": "Search Context: {context}",
|
||||
"onlyFolders": "Only Folders",
|
||||
"onlyFiles": "Only Files",
|
||||
"showOptions": "Show Options",
|
||||
"photos": "Photos",
|
||||
"audio": "Audio",
|
||||
"videos": "Videos",
|
||||
"documents": "Documents",
|
||||
"archives": "Archives",
|
||||
"notEnoughCharacters": "Not enough characters to search (min {minSearchLength})",
|
||||
"typeToSearch": "Start typing {minSearchLength} or more characters to begin searching.",
|
||||
"images": "Images",
|
||||
"music": "Music",
|
||||
"pdf": "PDF",
|
||||
"pressToSearch": "No results found in indexed search.",
|
||||
"search": "Search...",
|
||||
"number": "Number",
|
||||
"noResults": "No results found in indexed search.",
|
||||
"typeToSearch": "Type to search... (3 character minimum)",
|
||||
"path": "Path:",
|
||||
"types": "Types",
|
||||
"video": "Video",
|
||||
@@ -263,10 +241,6 @@
|
||||
"helpText2": "The index: Search utilizes the index which automatically gets updated on the configured interval (default: 5 minutes). Searching when the program has just started may result in incomplete results."
|
||||
},
|
||||
"profileSettings": {
|
||||
"debugOfficeEditor": "Enable OnlyOffice Debug Mode",
|
||||
"debugOfficeEditorDescription": "Show a debug popup with additional information in the OnlyOffice editor.",
|
||||
"defaultMediaPlayer": "Use your browser's native media player",
|
||||
"defaultMediaPlayerDescription": "Use your browser's native media player for video and audio files rather than FileBrowser's included 'vue-plyr' media player.",
|
||||
"showSelectMultiple": "Show select multiple in context menu on desktop",
|
||||
"showSelectMultipleDescription": "By default, desktop context menu does not have \"select multiple\" option like mobile. This option always shows it in the context menu.",
|
||||
"customTheme": "Choose your theme",
|
||||
@@ -315,16 +289,9 @@
|
||||
"disablePreviewExt": "Disable office file previews for certain file extensions",
|
||||
"disablePreviewExtDescription": "A space-separated list of file extensions to disable office file previews for (e.g., '.docx .pptx').",
|
||||
"disableOfficeEditorDescription": "A space-separated list of file extensions to disable the OnlyOffice editor and viewer for. (e.g., '.txt .html .pdf').",
|
||||
"disableOfficeEditor": "Disable office viewer for certain file extensions",
|
||||
"fileViewerOptions": "File Viewer Options",
|
||||
"themeAndLanguage": "Theme & Language"
|
||||
"disableOfficeEditor": "Disable office viewer for certain file extensions"
|
||||
},
|
||||
"settings": {
|
||||
"systemAdmin": "System & Admin",
|
||||
"configViewer": "Config Viewer",
|
||||
"configViewerShowFull": "Show full config",
|
||||
"configViewerShowComments": "Show comments",
|
||||
"configViewerLoadConfig": "Load Config",
|
||||
"enterPassword": "Enter password",
|
||||
"enterPasswordAgain": "Enter the password again",
|
||||
"passwordsDoNotMatch": "Passwords do not match",
|
||||
@@ -556,9 +523,5 @@
|
||||
"saveDisabled":"Saving shared files is not supported.",
|
||||
"saveAbortedMessage":"Save operation aborted. The application's active file ({activeFile}) does not match the file you are trying to save ({tryingToSave}).",
|
||||
"syncFailed":"failed to sync state with the route for \"{filename}\" after 5 attempts. Aborting editor setup to prevent data corruption."
|
||||
},
|
||||
"player": {
|
||||
"LoopEnabled": "Loop Enabled",
|
||||
"LoopDisabled": "Loop Disabled"
|
||||
}
|
||||
}
|
||||
}
|
||||
+96
-133
@@ -31,26 +31,25 @@
|
||||
"share": "Compartir",
|
||||
"shell": "Presiona Enter para buscar...",
|
||||
"submit": "Enviar",
|
||||
"switchView": "Cambiar modo de visualización",
|
||||
"toggleSidebar": "Cambiar visibilidad del panel lateral",
|
||||
"switchView": "Cambiar vista",
|
||||
"toggleSidebar": "Alternar menú",
|
||||
"update": "Actualizar",
|
||||
"upload": "Subir",
|
||||
"openFile": "Abrir archivo",
|
||||
"copyDownloadLinkToClipboard": "Copiar enlace de descarga",
|
||||
"continue": "Continuar",
|
||||
"edit": "Editar",
|
||||
"generateNewOtp": "Generar nuevo código de dos factores (2FA)",
|
||||
"generateNewOtp": "Generar nuevo código 2FA",
|
||||
"verify": "Verificar",
|
||||
"pause": "Pausar",
|
||||
"resume": "Reanudar",
|
||||
"retry": "Reintentar",
|
||||
"pauseAll": "Pausar todo",
|
||||
"resumeAll": "Reanudar todo",
|
||||
"clearCompleted": "Limpiar completados",
|
||||
"clearCompleted": "Limpiar lista",
|
||||
"showMore": "Ver más",
|
||||
"showLess": "Mostrar menos",
|
||||
"openParentFolder": "Abrir carpeta principal",
|
||||
"selectedCount": "Número de elementos seleccionados para realizar una acción"
|
||||
"openParentFolder": "Abrir carpeta principal"
|
||||
},
|
||||
"download": {
|
||||
"downloadFile": "Descargar archivo",
|
||||
@@ -62,7 +61,7 @@
|
||||
"internal": "Error interno :(",
|
||||
"notFound": "No se encontró el directorio",
|
||||
"connection": "No se puede conectar al servidor.",
|
||||
"shareNotFound": "No se encuentra lo que buscas, tal vez el enlace expiró."
|
||||
"shareNotFound": "Acción no válida o caducada."
|
||||
},
|
||||
"files": {
|
||||
"body": "Contenido",
|
||||
@@ -76,7 +75,7 @@
|
||||
"multipleSelectionEnabled": "Selección múltiple activada",
|
||||
"name": "Nombre",
|
||||
"size": "Tamaño",
|
||||
"sortByLastModified": "Ordenar por fecha de modificación",
|
||||
"sortByLastModified": "Ordenar por modificación",
|
||||
"sortByName": "Ordenar por nombre",
|
||||
"sortBySize": "Ordenar por tamaño",
|
||||
"noPreview": "Vista previa no disponible"
|
||||
@@ -85,15 +84,15 @@
|
||||
"click": "Selecciona un archivo o carpeta",
|
||||
"ctrl": {
|
||||
"click": "Selecciona varios archivos o carpetas (Selección multiple)",
|
||||
"d": "descargar artículos seleccionados"
|
||||
"f": "Abrir panel de búsqueda",
|
||||
"s": "Guardar archivo (solo funciona si estás en el editor)"
|
||||
},
|
||||
"del": "Eliminar archivos/carpetas seleccionados",
|
||||
"doubleClick": "Abre un archivo o carpeta",
|
||||
"esc": "Cancela la selección o cierra la ventana actual (ej: Para salir rápido cuando estas viendo una imagen o video)",
|
||||
"f2": "Cambiar nombre del archivo o carpeta",
|
||||
"help": "Ayuda",
|
||||
"f1": "mostrar mensaje de ayuda",
|
||||
"description": "A continuación puede ver las opciones básicas de navegación. Para más información, visite",
|
||||
"wiki": "Wiki de FileBrowser Quantum"
|
||||
"f1": "Esta ventana",
|
||||
"f2": "Cambiar nombre del archivo",
|
||||
"help": "Ayuda"
|
||||
},
|
||||
"languages": {
|
||||
"he": "עברית",
|
||||
@@ -135,13 +134,16 @@
|
||||
"prompts": {
|
||||
"copy": "Copiar",
|
||||
"copyMessage": "Selecciona una ubicación para copiar",
|
||||
"deleteMessageMultiple": "¿Estás seguro que quieres eliminar {count} archivo(s)?",
|
||||
"deleteMessageSingle": "¿Estás seguro que quieres eliminar este archivo/carpeta?",
|
||||
"deleteMessageShare": "¿Estás seguro que quieres eliminar este recurso compartido ({path})?",
|
||||
"deleteTitle": "Borrar archivos",
|
||||
"displayName": "Nombre:",
|
||||
"download": "Descargar archivos",
|
||||
"downloadMessage": "Elige el formato de descarga",
|
||||
"error": "Ha ocurrido un error",
|
||||
"fileInfo": "Información del archivo",
|
||||
"lastModified": "Última modificación{suffix}",
|
||||
"move": "Mover",
|
||||
"moveMessage": "Selecciona un nuevo destino:",
|
||||
"newArchetype": "Crear una nueva publicación en base a una plantilla, tu archivo se creara en la misma carpeta",
|
||||
@@ -149,7 +151,10 @@
|
||||
"newDirMessage": "Nombre de la carpeta",
|
||||
"newFile": "Nuevo archivo",
|
||||
"newFileMessage": "Nombre del archivo",
|
||||
"numberDirs": "Carpetas",
|
||||
"numberFiles": "Archivos",
|
||||
"rename": "Cambiar nombre",
|
||||
"renameMessage": "Nuevo nombre para:",
|
||||
"replace": "Reemplazar",
|
||||
"replaceMessage": "Un archivo con el mismo nombre ya existe. ¿Quieres cambiar el nombre?\n",
|
||||
"schedule": "Programar",
|
||||
@@ -164,57 +169,39 @@
|
||||
"selected": "seleccionado",
|
||||
"deleted": "¡Borrado con éxito!",
|
||||
"destinationSource": "Destino:",
|
||||
"source": "Source{suffix}",
|
||||
"downloadsLimit": "Límite de descargas",
|
||||
"maxBandwidth": "Velocidad máxima de descarga (kbps)",
|
||||
"shareTheme": "Tema",
|
||||
"shareThemeColor": "Color del tema (valor CSS)",
|
||||
"shareTitle": "Título la página",
|
||||
"shareDescription": "Descripción de la página",
|
||||
"shareLogo": "Imagen/Logo de la página",
|
||||
"shareBanner": "Banner de la página",
|
||||
"shareFavicon": "Mini-icono de la página (favicon)",
|
||||
"optionalPasswordHelp": "Si eliges una contraseña, los usuarios que accedan deben introducir esa misma contraseña para ver el contenido.",
|
||||
"maxBandwidth": "Ancho de banda máximo de descarga (kbps)",
|
||||
"shareTheme": "Compartir tema",
|
||||
"shareThemeColor": "Compartir color del tema (valor CSS)",
|
||||
"shareTitle": "Compartir título",
|
||||
"shareDescription": "Compartir descripción",
|
||||
"shareLogo": "Compartir URL del logotipo",
|
||||
"shareBanner": "Compartir URL del banner",
|
||||
"shareFavicon": "Compartir URL de favicon",
|
||||
"optionalPasswordHelp": "Si se establece, los usuarios deben introducir esta contraseña para ver el contenido compartido.",
|
||||
"dragAndDrop": "Arrastra y suelta los archivos aquí",
|
||||
"completed": "Completado",
|
||||
"conflictsDetected": "Conflictos detectados",
|
||||
"viewMode": "Cambiar modo de visualización",
|
||||
"renameMessage": "Introduce un nuevo nombre:",
|
||||
"renameMessageInvalid": "Un archivo no puede contener \"/\" o \"\\\"",
|
||||
"source": "Fuente{suffix}",
|
||||
"deleteMessageMultiple": "¿Está seguro de que desea eliminar los archivos de {count}?",
|
||||
"deleteMessageShare": "¿Estás seguro de que quieres borrar esta acción? {path} ?",
|
||||
"lastModified": "Última modificación{suffix}",
|
||||
"numberDirs": "Número de directorios{suffix}",
|
||||
"numberFiles": "Número de ficheros{suffix}",
|
||||
"renameMessageConflict": "Ya existe un elemento llamado \"{filename}\".",
|
||||
"uploadSettingsChunked": "Carga máxima simultánea: {maxConcurrentUpload} Tamaño del chunk {uploadChunkSizeMb} MB",
|
||||
"uploadSettingsNoChunk": "Carga máxima simultánea: {maxConcurrentUpload} Carga por trozos desactivada"
|
||||
"uploadSettingsChunked": "Subidas simultáneas: {maxConcurrentUpload}, velocidad de subida máxima: {uploadChunkSizeMb} MB",
|
||||
"uploadSettingsNoChunk": "Subidas simultáneas: {maxConcurrentUpload}, subida de archivos desactivada",
|
||||
"invalidName": "Nombre inválido: no puede contener \"/\" o \"\\\".",
|
||||
"viewMode": "Ver modo"
|
||||
},
|
||||
"search": {
|
||||
"images": "Imágenes",
|
||||
"music": "Música",
|
||||
"pdf": "PDF",
|
||||
"pressToSearch": "Enter para buscar...",
|
||||
"search": "Buscar...",
|
||||
"typeToSearch": "Escribe para buscar...",
|
||||
"types": "Tipos",
|
||||
"video": "Vídeo",
|
||||
"path": "Ruta:",
|
||||
"smallerThan": "Menor que:",
|
||||
"largerThan": "Mayor que:",
|
||||
"helpText1": "La búsqueda se realiza por cada carácter (mínimo de 3 caracteres)",
|
||||
"helpText2": "El índice se actualiza automáticamente (cada 5 min por defecto). Si es interrumpido pueden ocurrir errores",
|
||||
"onlyFolders": "Sólo carpetas",
|
||||
"onlyFiles": "Sólo archivos",
|
||||
"showOptions": "Mostrar opciones",
|
||||
"photos": "Fotos",
|
||||
"audio": "Audio",
|
||||
"videos": "Vídeos",
|
||||
"documents": "Documentos",
|
||||
"archives": "Archivos",
|
||||
"number": "Número",
|
||||
"noResults": "No se han encontrado resultados en la búsqueda indexada.",
|
||||
"searchContext": "Contexto de búsqueda: {context}",
|
||||
"notEnoughCharacters": "No hay suficientes caracteres para buscar (min {minSearchLength})",
|
||||
"typeToSearch": "Escriba {minSearchLength} o más caracteres para iniciar la búsqueda."
|
||||
"helpText2": "El índice se actualiza automáticamente (cada 5 min por defecto). Si es interrumpido pueden ocurrir errores"
|
||||
},
|
||||
"settings": {
|
||||
"scopes": "Establece los directorios y los permisos que el usuario puede acceder",
|
||||
@@ -228,8 +215,10 @@
|
||||
"avoidChanges": "(dejar en blanco para evitar cambios)",
|
||||
"branding": "Personalización",
|
||||
"brandingDirectoryPath": "Directorio de tus archivos que usarás para personalizar FileBrowser",
|
||||
"brandingHelp": "Personaliza FileBrowser cambiando el nombre, logo, estilos. Más información en {0}",
|
||||
"changePassword": "Cambiar contraseña",
|
||||
"commandRunner": "Ejecutor de comandos",
|
||||
"commandRunnerHelp": "Comandos para eventos. Variables disponibles: {0} (ruta relativa), {1} (ruta base). Más info en {2}",
|
||||
"commandsUpdated": "¡Comandos actualizados!",
|
||||
"createUserDir": "Crea automaticamente una carpeta cuando se agrega un nuevo usuario",
|
||||
"userHomeBasePath": "Ruta base para los directorios personales de los usuarios",
|
||||
@@ -260,7 +249,7 @@
|
||||
"modify": "Editar archivos",
|
||||
"share": "Compartir archivos",
|
||||
"admin": "Administrador",
|
||||
"api": "Gestión de APIs",
|
||||
"api": "Gestionar claves API",
|
||||
"realtime": "Actualizaciones en tiempo real"
|
||||
},
|
||||
"permissions-name": "Permisos",
|
||||
@@ -272,7 +261,7 @@
|
||||
"rulesHelp": "Puedes definir un conjunto de reglas de permisos para este usuario específico. Los archivos bloqueados no se mostrarán en las listas y no serán accesibles por el usuario. Puedes utilizar regex y rutas relativas a la raíz del usuario.\n",
|
||||
"scope": "Raíz",
|
||||
"settingsUpdated": "¡Ajustes actualizados!",
|
||||
"shareManagement": "Gestión de recursos compartidos",
|
||||
"shareManagement": "Gestión Compartida",
|
||||
"shareDeleted": "¡Compartido eliminado!",
|
||||
"singleClick": "Abrir con un solo clic",
|
||||
"themes": {
|
||||
@@ -286,7 +275,7 @@
|
||||
"userCreated": "¡Usuario creado!",
|
||||
"userDefaults": "Configuración de usuario por defecto",
|
||||
"userDeleted": "¡Usuario eliminado!",
|
||||
"userManagement": "Gestión de usuarios",
|
||||
"userManagement": "Gestionar usuarios",
|
||||
"userUpdated": "¡Usuario actualizado!",
|
||||
"username": "Nombre de Usuario",
|
||||
"users": "Usuarios",
|
||||
@@ -298,27 +287,20 @@
|
||||
"UserManagement": "Gestión de usuarios",
|
||||
"tusUploads": "Subir en partes",
|
||||
"tusUploadsHelp": "File Browser Quantum permite subir archivos en partes, lo que permite que sean subidos de manera eficiente, fiable, que sea reanudable, incluso si tu internet es inestable",
|
||||
"tusUploadsChunkSize": "Tamaño máximo de subida de un archivo. Puede escribir un número entero que indique una entrada en bytes o una cadena como 10 MB, 1 GB, etc",
|
||||
"tusUploadsChunkSize": "Tamaño máximo de subida de un archivo. Puede introducir un número entero que indique una entrada en bytes o una cadena como 10 MB, 1 GB, etc",
|
||||
"tusUploadsRetryCount": "Número de reintentos, si falla la subida de un archivo",
|
||||
"api": "Claves API",
|
||||
"loginMethodDescription": "Método de inicio de sesión permitido",
|
||||
"userNotAdmin": "El usuario no es administrador",
|
||||
"enterPassword": "Escriba su contraseña",
|
||||
"enterPasswordAgain": "Vuelva a escribir su contraseña",
|
||||
"enterPassword": "Indusca la contraseña",
|
||||
"enterPasswordAgain": "Vuelva a introducir la contraseña",
|
||||
"passwordsDoNotMatch": "Las contraseñas no coinciden",
|
||||
"loginMethod": "Método de inicio de sesión",
|
||||
"shareSettings": "Recursos compartidos",
|
||||
"shareSettings": "Compartidos",
|
||||
"adminOptions": "Opciones de administrador",
|
||||
"shareDuration": "Expira en",
|
||||
"searchOptions": "Opciones de búsqueda",
|
||||
"downloads": "Descargas",
|
||||
"systemAdmin": "Sistema y administración",
|
||||
"configViewer": "Visor de configuración",
|
||||
"configViewerShowFull": "Mostrar configuración completa",
|
||||
"configViewerShowComments": "Mostrar comentarios",
|
||||
"configViewerLoadConfig": "Configuración de carga",
|
||||
"brandingHelp": "Puede personalizar el aspecto de su instancia del Navegador de archivos cambiando su nombre, sustituyendo el logotipo, añadiendo estilos personalizados e incluso desactivando los enlaces externos a GitHub.\nPara obtener más información sobre la personalización de la marca, consulta la página {0}.",
|
||||
"commandRunnerHelp": "Aquí puede establecer los comandos que se ejecutan en los eventos nombrados. Debe escribir uno por línea. Las variables de entorno {0} y {1} estarán disponibles, siendo {0} relativa a {1}. Para más información sobre esta característica y las variables de entorno disponibles, por favor lea el {2}."
|
||||
"downloads": "Descargas"
|
||||
},
|
||||
"sidebar": {
|
||||
"help": "Ayuda",
|
||||
@@ -351,70 +333,61 @@
|
||||
},
|
||||
"share": {
|
||||
"notice": "Nota: cuando se crea un recurso compartido, queda más expuesto al público y tiene diferentes normas de acceso. Si su recurso compartido no está configurado de forma segura, cualquiera puede acceder a él.",
|
||||
"disableAnonymousDescription": "Cuando está activado, sólo los usuarios con su permiso pueden acceder al recurso compartido. El usuario debe tener acceso al origen recurso compartido.",
|
||||
"disableAnonymousDescription": "Cuando está activada, sólo los usuarios autenticados pueden acceder al recurso compartido. El usuario debe tener acceso al origen del recurso compartido.",
|
||||
"disableThumbnailsDescription": "Si está activada, las miniaturas de previsualización no se mostrarán en la acción.",
|
||||
"enableAllowedUsernamesDescription": "Cuando se activa, sólo los usuarios que especifique pueden acceder al recurso compartido. El usuario debe tener acceso al origen del recurso compartido.",
|
||||
"enableAllowedUsernamesDescription": "Cuando se activa, sólo los usuarios especificados pueden acceder al recurso compartido. El usuario debe tener acceso al origen del recurso compartido.",
|
||||
"downloadsLimitDescription": "Número máximo de veces que se puede descargar un archivo/carpeta del recurso compartido. Dejar vacío para ilimitado.",
|
||||
"maxBandwidthDescription": "El ancho de banda máximo de descarga en kilobytes por segundo. Dejar vacío para ilimitado.",
|
||||
"shareThemeDescription": "El tema que se utilizará para el enlace compartido.",
|
||||
"disableAnonymous": "Desactivar el acceso anónimo",
|
||||
"disableThumbnails": "Desactivar miniaturas",
|
||||
"enableAllowedUsernames": "Compartir sólo con determinados usuarios",
|
||||
"allowedUsernamesPlaceholder": "Escriba los nombres de usuario (separado por comas)",
|
||||
"keepAfterExpiration": "No eliminar el recurso compartido una vez expirado",
|
||||
"keepAfterExpirationDescription": "Los recursos compartidos no se borraran cuando expiren. Es útil si después quieres ampliar la duración del recurso, o editarlo.",
|
||||
"allowedUsernamesPlaceholder": "Introduzca los nombres de usuario, separados por comas",
|
||||
"keepAfterExpiration": "No eliminar la acción una vez caducada",
|
||||
"keepAfterExpirationDescription": "La acción no se borrará después de que caduque. Esto resulta útil si desea ampliar el plazo de expiración de una acción o editarla después de que expire.",
|
||||
"saveDisabled": "No se pueden guardar los cambios en los archivos compartidos.",
|
||||
"shareDurationDescription": "Duración del recurso compartido. Dejar en blanco para permanente.",
|
||||
"passwordDescription": "Contraseña opcional necesaria para acceder al recurso compartido, incluidos los usuarios autenticados.",
|
||||
"shareDurationDescription": "Duración antes de que caduque la acción. Dejar en blanco para una acción permanente.",
|
||||
"passwordDescription": "Contraseña opcional necesaria para acceder a este recurso compartido, incluidos los usuarios autenticados.",
|
||||
"allowedUsernamesListDescription": "Nombres de usuario separados por comas que pueden acceder al recurso compartido (si está activado).",
|
||||
"shareThemeColorDescription": "Valor de color CSS aplicado al tema de la acción (por ejemplo, rojo, #0ea5e9, rgb(14,165,233)).",
|
||||
"shareTitleDescription": "Título de página personalizado de página",
|
||||
"shareDescriptionHelp": "Breve descripción mostrada en la página (que suele aparecer cuando compartes el enlace)",
|
||||
"shareLogoDescription": "Enlace (URL), o ruta de la imagen del logo que se mostrará en el enlace -- por ejemplo, 'https://ejemplo.com/logo.png' o '/ruta/a/logo.png'.",
|
||||
"shareBannerDescription": "Enlace (URL), o ruta de la imagen del banner que se mostrará en el enlace -- por ejemplo, 'https://ejemplo.com/banner.png' o '/ruta/a/banner.png'.",
|
||||
"shareFaviconDescription": "Enlace (URL) del icono (favicon) que se mostrará en el enlace -- por ejemplo 'https://domain.com/favicon.png' o '/ruta/a/favicon.png'",
|
||||
"shareTitleDescription": "Título de página personalizado que se muestra en la página de compartición.",
|
||||
"shareDescriptionHelp": "Breve descripción mostrada en la página de compartición (puede utilizarse en las metaetiquetas).",
|
||||
"shareLogoDescription": "URL o ruta de la imagen del logotipo accesible por el cliente -- se aceptan rutas abosolutas o de índice: por ejemplo, 'https://domain.com/logo.png' o '/ruta/a/logo.png'.",
|
||||
"shareBannerDescription": "URL de la imagen del banner o ruta accesible por el cliente -- se aceptan rutas abosolutas o de índice: por ejemplo, 'https://domain.com/banner.png' o '/ruta/a/banner.png'.",
|
||||
"shareFaviconDescription": "URL del favicon o ruta accesible por el cliente -- se aceptan rutas abosolutas o de índice: por ejemplo 'https://domain.com/favicon.png' o '/ruta/a/favicon.png'.",
|
||||
"hideNavButtons": "Ocultar botones de navegación",
|
||||
"hideNavButtonsDescription": "Oculte los botones de navegación de la barra de navegación en la acción para crear un aspecto minimalista.",
|
||||
"viewModeDescription": "Diseño por defecto de la página compartida.",
|
||||
"titleDefault": "Shared files - {title}",
|
||||
"descriptionDefault": "Se le ha enviado una acción para que la vea o descargue.",
|
||||
"disableShareCard": "Desactivar tarjeta compartida",
|
||||
"disableShareCardDescription": "Desactivar la tarjeta de compartir en la página compartida en el panel lateral o en la parte superior de la página en móvil.",
|
||||
"disableSidebar": "Desactivar el panel lateral",
|
||||
"disableSidebarDescription": "Desactivar el panel lateral en la página compartida.",
|
||||
"enforceDarkLightMode": "Aplicar el modo temático",
|
||||
"enforceDarkLightModeDescription": "Forzar un modo de tema específico (oscuro o claro) para esta acción, anulando las preferencias del usuario.",
|
||||
"default": "No imponer un modo",
|
||||
"dark": "Oscuro",
|
||||
"light": "Luz",
|
||||
"enableOnlyOffice": "Activar el visor de OnlyOffice",
|
||||
"enableOnlyOfficeDescription": "Permitir la visualización de archivos de oficina utilizando OnlyOffice en este recurso compartido.",
|
||||
"enableOnlyOfficeEditing": "Habilitar la edición de OnlyOffice",
|
||||
"enableOnlyOfficeEditingDescription": "Permitir la edición de archivos de oficina utilizando OnlyOffice en este recurso compartido.",
|
||||
"titleDefault": "Archivos compartidos - {title}"
|
||||
"disableShareCardDescription": "Desactivar la tarjeta de compartir en la página compartida en la barra lateral o en la parte superior de la página en móvil.",
|
||||
"disableSidebar": "Desactivar la barra lateral",
|
||||
"disableSidebarDescription": "Desactivar la barra lateral en la página compartida."
|
||||
},
|
||||
"api": {
|
||||
"title": "Claves API",
|
||||
"keyName": "Nombre para la API",
|
||||
"title": "Crear API:",
|
||||
"keyName": "Nombre de la clave:",
|
||||
"createdAt": "Creado en:",
|
||||
"expiresAt": "Expira en:",
|
||||
"permissions": "Permisos:",
|
||||
"createTitle": "Crear clave API",
|
||||
"keyNamePlaceholder": "Escriba un nombre único",
|
||||
"keyNamePlaceholder": "Nombre único para clave API:",
|
||||
"tokenDuration": "Duración:",
|
||||
"durationNumberPlaceholder": "Número:",
|
||||
"days": "Días",
|
||||
"months": "Meses",
|
||||
"swaggerLinkText": "Documentación de Swagger (API)",
|
||||
"swaggerLinkText": "Documentación de Swagger",
|
||||
"name": "Nombre",
|
||||
"created": "Creado",
|
||||
"expires": "Expira en:",
|
||||
"expires": "Expira en",
|
||||
"actions": "Acciones",
|
||||
"enabled": "Habilitado",
|
||||
"disabled": "Deshabilitado",
|
||||
"permissionNote": "Elija al menos un permiso para la API. Tu usuario también debe tener ese permiso.",
|
||||
"permissionNote": "Elija al menos un permiso para la clave. Su usuario también debe tener el permiso.",
|
||||
"createKeyFailed": "Error al crear la clave API.",
|
||||
"createKeySuccess": "¡Clave API creada con exito!",
|
||||
"description": "Las claves API solo podrán tener los permisos que el usuario tenga. Para más info:"
|
||||
"createKeySuccess": "Clave API creada correctamente.",
|
||||
"description": "Las claves API se basan en el usuario que las crea. Véase"
|
||||
},
|
||||
"index": {
|
||||
"status": "Estado",
|
||||
@@ -426,7 +399,7 @@
|
||||
"logout": "Cerrar sesión",
|
||||
"toggleClick": "Abrir con un solo clic",
|
||||
"toggleDark": "Activar modo oscuro",
|
||||
"toggleSticky": "Cambiar visibilidad del panel lateral",
|
||||
"toggleSticky": "Barra lateral visible",
|
||||
"used": "usado",
|
||||
"noSources": "No hay fuentes disponibles. Añada una o active defaultEnabled: true."
|
||||
},
|
||||
@@ -466,7 +439,7 @@
|
||||
"allow": "Permitir",
|
||||
"allowDeny": "Permitir/Denegar",
|
||||
"userGroup": "Usuario/Grupo",
|
||||
"enterName": "Escriba el nombre del usuario o del grupo",
|
||||
"enterName": "Introduzca el nombre del usuario o del grupo",
|
||||
"deleted": "Regla de acceso eliminada",
|
||||
"added": "Regla de acceso añadida",
|
||||
"name": "Nombre",
|
||||
@@ -474,8 +447,8 @@
|
||||
"totalDenied": "Denegado",
|
||||
"totalAllowed": "Permitido",
|
||||
"all": "Denegar todo",
|
||||
"defaultBehaviorDescription": "Muestra lo que ocurre cuando un usuario intenta acceder a una ruta en la que no existen reglas de acceso específicas. Permitir\" significa que los usuarios pueden acceder por defecto, \"Denegar\" significa que los usuarios están bloqueados por defecto a menos que se les permita explícitamente.",
|
||||
"defaultBehavior": "Acción por defecto{suffix}"
|
||||
"defaultBehavior": "Default action{suffix}",
|
||||
"defaultBehaviorDescription": "Muestra lo que ocurre cuando un usuario intenta acceder a una ruta en la que no existen reglas de acceso específicas. Permitir\" significa que los usuarios pueden acceder por defecto, \"Denegar\" significa que los usuarios están bloqueados por defecto a menos que se les permita explícitamente."
|
||||
},
|
||||
"fileLoading": {
|
||||
"title": "Carga de archivos",
|
||||
@@ -485,17 +458,17 @@
|
||||
"uploadChunkSizeMbHelp": "Típicamente 5 a 50 MB, más pequeño es mejor para conexiones inestables. Si se pone a 0, se desactiva la subida de archivo en partes"
|
||||
},
|
||||
"colors": {
|
||||
"red": "Rojo",
|
||||
"green": "Verde",
|
||||
"blue": "Azul",
|
||||
"yellow": "Amarillo",
|
||||
"violet": "Morado",
|
||||
"orange": "Naranja"
|
||||
"red": "rojo",
|
||||
"green": "verde",
|
||||
"blue": "azul",
|
||||
"yellow": "amarillo",
|
||||
"violet": "violeta",
|
||||
"orange": "naranja"
|
||||
},
|
||||
"profileSettings": {
|
||||
"autoplayMedia": "Reproducción automática de archivos multimedia",
|
||||
"autoplayMediaDescription": "Reproducción automática de archivos multimedia al visualizarlos. Esto sólo funcionará si el archivo es un archivo multimedia",
|
||||
"sidebarOptions": "Opciones del panel lateral",
|
||||
"sidebarOptions": "Opciones de la barra lateral",
|
||||
"editorViewerOptions": "Edición y visualización",
|
||||
"editorQuickSave": "Botón de guardado rápido en el editor",
|
||||
"editorQuickSaveDescription": "El editor de texto mostrará siempre el icono de guardar, ocultando cualquier otra acción",
|
||||
@@ -511,9 +484,9 @@
|
||||
"disableOfficeEditor": "Desactivar el visor y editor de office para las siguientes extensiones:",
|
||||
"defaultThemeDescription": "Predeterminado",
|
||||
"customTheme": "Cambiar tema",
|
||||
"showSelectMultiple": "Mostrar selección múltiple en el menú contextuaL (en PC)",
|
||||
"showSelectMultiple": "Mostrar selección múltiple en el menú contextual del escritorio",
|
||||
"showSelectMultipleDescription": "Por defecto, el menú contextual del escritorio no tiene la opción \"seleccionar múltiples\" como el móvil. Esta opción siempre se muestra en el menú contextual.",
|
||||
"setDateFormat": "Usar fecha exacta (DD/MM/AA)",
|
||||
"setDateFormat": "Establecer el formato exacto de la fecha",
|
||||
"showHiddenFiles": "Mostrar archivos ocultos",
|
||||
"showHiddenFilesDescription": "Muestra los archivos ocultos. (por ejemplo, .thumbnails, ProgramData), pero siguen siendo accesibles manualmente si están configurados para ser indexados.",
|
||||
"showQuickDownload": "Icono de descarga rápida",
|
||||
@@ -531,34 +504,24 @@
|
||||
"popupPreview": "Vista previa emergente",
|
||||
"popupPreviewDescription": "Se mostrará una ventana emergente superpuesta sobre la interfaz de usuario al pasar el ratón por encima de los archivos que tengan una miniatura",
|
||||
"filePreviewOptions": "Opciones de previsualización de archivos",
|
||||
"disableUpdateNotifications": "Desactivar las notificaciones de actualización",
|
||||
"disableUpdateNotificationsDescription": "Desactivar el banner de notificación de actualización que aparece para los usuarios administradores en el panel lateral.",
|
||||
"disableHideSidebar": "Mantener el panel lateral abierto al previsualizar o editar archivos",
|
||||
"disableHideSidebarDescription": "Si está activado, el panel lateral no se ocultará automáticamente al previsualizar o editar archivos.",
|
||||
"disableQuickToggles": "Desactivar ajustes rápidos",
|
||||
"disableQuickTogglesDescription": "Desactiva los ajustes rápidos que aparecen en el panel lateral. (cambiar tema, visibilidad del panel y modo de un solo click)",
|
||||
"disableUpdateNotifications": "Desactivar la visualización de notificaciones de actualización",
|
||||
"disableUpdateNotificationsDescription": "Desactivar el banner de notificación de actualización que aparece para los usuarios administradores en la barra lateral.",
|
||||
"disableHideSidebar": "Mantener la barra lateral abierta al previsualizar o editar archivos",
|
||||
"disableHideSidebarDescription": "Si está activada, la barra lateral no se ocultará automáticamente al previsualizar o editar archivos.",
|
||||
"disableQuickToggles": "Desactivar alternancias rápidas",
|
||||
"disableQuickTogglesDescription": "Desactiva los toggles rápidos que aparecen en la barra lateral.",
|
||||
"disableSearchOptions": "Desactivar las opciones de búsqueda",
|
||||
"disableSearchOptionsDescription": "Desactivar las opciones de búsqueda que aparecen en el panel.",
|
||||
"hideSidebarFileActions": "Ocultar el botón de acciones de archivo en el panel",
|
||||
"disableSearchOptionsDescription": "Desactivar las opciones de búsqueda que aparecen en la barra lateral.",
|
||||
"hideSidebarFileActions": "Ocultar el botón de acciones de archivo en la barra lateral",
|
||||
"deleteWithoutConfirming": "Eliminar archivos sin mensaje de confirmación",
|
||||
"deleteWithoutConfirmingDescription": "Eliminar archivos sin mensaje de confirmación. Esto sólo funcionará si el archivo es un archivo multimedia.",
|
||||
"defaultMediaPlayer": "Usar el reproductor multimedia nativo del navegador",
|
||||
"defaultMediaPlayerDescription": "Usar el reproductor multimedia nativo de tu navegador para los archivos de vídeo y audio en lugar del reproductor multimedia incluido en FileBrowser.",
|
||||
"debugOfficeEditor": "Activar el modo de depuración de OnlyOffice",
|
||||
"debugOfficeEditorDescription": "Mostrar una ventana emergente de depuración con información adicional en el editor OnlyOffice.",
|
||||
"fileViewerOptions": "Opciones del visor de archivos",
|
||||
"themeAndLanguage": "Tema y lengua"
|
||||
"deleteWithoutConfirmingDescription": "Eliminar archivos sin mensaje de confirmación. Esto sólo funcionará si el archivo es un archivo multimedia."
|
||||
},
|
||||
"editor": {
|
||||
"uninitialized": "Error al iniciar el editor, inténtelo de nuevo",
|
||||
"uninitialized": "Error al inicializar el editor. Por favor, vuelva a cargar.",
|
||||
"saveFailed": "No se ha podido guardar el archivo. Por favor, inténtelo de nuevo.",
|
||||
"saveAborted": "No se ha podido guardar el archivo. El nombre del archivo no coincide con el que estás editando.",
|
||||
"saveAborted": "Guardado abortado. El nombre del fichero no coincide con el fichero activo.",
|
||||
"saveDisabled": "No es posible guardar archivos compartidos.",
|
||||
"saveAbortedMessage": "Operación de guardado abortada. El archivo activo de la aplicación ({activeFile}) no coincide con el archivo que está intentando guardar ({tryingToSave}).",
|
||||
"syncFailed": "No se ha podido sincronizar el estado con la ruta para \"{filename}\" después de 5 intentos. Abortar la configuración del editor para evitar la corrupción de datos."
|
||||
},
|
||||
"player": {
|
||||
"LoopEnabled": "Bucle activado",
|
||||
"LoopDisabled": "Bucle desactivado"
|
||||
"saveAbortedMessage": "Save operation aborted. The application's active file ({activeFile}) does not match the file you are trying to save ({tryingToSave}).",
|
||||
"syncFailed": "failed to sync state with the route for \"{filename}\" after 5 attempts. Aborting editor setup to prevent data corruption."
|
||||
}
|
||||
}
|
||||
+30
-67
@@ -49,8 +49,7 @@
|
||||
"clearCompleted": "Effacement terminé",
|
||||
"showMore": "Afficher plus",
|
||||
"showLess": "Montrer moins",
|
||||
"openParentFolder": "Ouvrir le dossier parent",
|
||||
"selectedCount": "Nombre d'éléments sélectionnés pour effectuer une action"
|
||||
"openParentFolder": "Ouvrir le dossier parent"
|
||||
},
|
||||
"download": {
|
||||
"downloadFile": "Télécharger le fichier",
|
||||
@@ -85,15 +84,15 @@
|
||||
"click": "Sélectionner un élément",
|
||||
"ctrl": {
|
||||
"click": "Sélectionner plusieurs éléments",
|
||||
"d": "télécharger les éléments sélectionnés"
|
||||
"f": "Ouvrir l'invite de recherche",
|
||||
"s": "Télécharger l'élément actuel"
|
||||
},
|
||||
"del": "Supprimer les éléments sélectionnés",
|
||||
"doubleClick": "Ouvrir un élément",
|
||||
"esc": "Désélectionner et/ou fermer la boîte de dialogue",
|
||||
"f1": "Ouvrir l'aide",
|
||||
"f2": "Renommer le fichier",
|
||||
"help": "Aide",
|
||||
"f1": "show help prompt",
|
||||
"description": "Vous pouvez consulter les options de navigation de base ci-dessous. Pour plus d'informations, veuillez consulter le site",
|
||||
"wiki": "Wiki FileBrowser Quantum"
|
||||
"help": "Aide"
|
||||
},
|
||||
"languages": {
|
||||
"he": "עברית",
|
||||
@@ -135,13 +134,16 @@
|
||||
"prompts": {
|
||||
"copy": "Copier",
|
||||
"copyMessage": "Choisissez l'emplacement où copier la sélection :",
|
||||
"deleteMessageMultiple": "Êtes-vous sûr de vouloir supprimer ces {count} élément(s) ?",
|
||||
"deleteMessageSingle": "Êtes-vous sûr de vouloir supprimer cet élément ?",
|
||||
"deleteMessageShare": "Êtes-vous sûr de vouloir supprimer ce partage ({path}) ?",
|
||||
"deleteTitle": "Supprimer",
|
||||
"displayName": "Nom :",
|
||||
"download": "Télécharger",
|
||||
"downloadMessage": "Choisissez le format de téléchargement :",
|
||||
"error": "Quelque chose s'est mal passé",
|
||||
"fileInfo": "Informations",
|
||||
"lastModified": "Dernière modification{suffix}",
|
||||
"move": "Déplacer",
|
||||
"moveMessage": "Choisissez l'emplacement où déplacer la sélection :",
|
||||
"newArchetype": "Créer un nouveau post basé sur un archétype. Votre fichier sera créé dans le dossier de contenu.",
|
||||
@@ -149,7 +151,10 @@
|
||||
"newDirMessage": "Nom du nouveau dossier :",
|
||||
"newFile": "Nouveau fichier",
|
||||
"newFileMessage": "Nom du nouveau fichier :",
|
||||
"numberDirs": "Nombre de dossiers{suffix}",
|
||||
"numberFiles": "Nombre de fichiers{suffix}",
|
||||
"rename": "Renommer",
|
||||
"renameMessage": "Nouveau nom pour",
|
||||
"replace": "Remplacer",
|
||||
"replaceMessage": "Un des fichiers que vous êtes en train d'importer a le même nom qu'un autre déjà présent. Voulez-vous remplacer le fichier actuel par le nouveau ?\n",
|
||||
"schedule": "Fixer la date",
|
||||
@@ -167,6 +172,10 @@
|
||||
"dragAndDrop": "Glisser-déposer des fichiers ici",
|
||||
"completed": "Terminé",
|
||||
"conflictsDetected": "Conflits détectés",
|
||||
"uploadSettingsChunked": "Max concurrent uploads: {maxConcurrentUpload}, chunk size: {uploadChunkSizeMb} MB",
|
||||
"uploadSettingsNoChunk": "Max concurrent uploads: {maxConcurrentUpload}, chunked upload disabled",
|
||||
"invalidName": "Nom invalide fourni : ne peut contenir \"/\" ou \"\\\".",
|
||||
"source": "Source{suffix}",
|
||||
"downloadsLimit": "Limite du nombre de téléchargements",
|
||||
"maxBandwidth": "Largeur de bande maximale de téléchargement (kbps)",
|
||||
"shareTheme": "Thème de partage",
|
||||
@@ -177,44 +186,22 @@
|
||||
"shareBanner": "Partager l'URL de la bannière",
|
||||
"shareFavicon": "Partager l'URL de la favicon",
|
||||
"optionalPasswordHelp": "S'il est défini, les utilisateurs doivent saisir ce mot de passe pour consulter le contenu partagé.",
|
||||
"viewMode": "Mode d'affichage",
|
||||
"renameMessage": "Saisissez un nouveau nom :",
|
||||
"renameMessageInvalid": "Un fichier ne peut pas contenir \"/\" ou \"\\\"",
|
||||
"source": "Source{suffix}",
|
||||
"deleteMessageMultiple": "Êtes-vous sûr de vouloir supprimer le(s) fichier(s) {count}?",
|
||||
"deleteMessageShare": "Êtes-vous sûr de vouloir supprimer ce partage : {path} ?",
|
||||
"lastModified": "Dernière modification{suffix}",
|
||||
"numberDirs": "Nombre de répertoires{suffix}",
|
||||
"numberFiles": "Nombre de fichiers{suffix}",
|
||||
"renameMessageConflict": "Un article nommé \"{filename}\" existe déjà !",
|
||||
"uploadSettingsChunked": "Nombre maximum de téléchargements simultanés : {maxConcurrentUpload}, taille de chunk : {uploadChunkSizeMb} MO",
|
||||
"uploadSettingsNoChunk": "Nombre maximum de téléchargements simultanés : {maxConcurrentUpload}, chunked upload désactivé"
|
||||
"viewMode": "Mode d'affichage"
|
||||
},
|
||||
"search": {
|
||||
"images": "Images",
|
||||
"music": "Musique",
|
||||
"pdf": "PDF",
|
||||
"pressToSearch": "Appuyez sur entrée pour chercher...",
|
||||
"search": "Recherche en cours...",
|
||||
"typeToSearch": "Écrivez pour chercher...",
|
||||
"types": "Types",
|
||||
"video": "Vidéo",
|
||||
"path": "Chemin :",
|
||||
"smallerThan": "Plus petit que :",
|
||||
"largerThan": "Plus grand que :",
|
||||
"helpText1": "La recherche s'effectue sur chaque caractère saisi (3 caractères minimum pour les termes de recherche).",
|
||||
"helpText2": "Index : La recherche utilise l'index qui est automatiquement mis à jour à l'intervalle configuré (par défaut : 5 minutes). Une recherche effectuée alors que le programme vient de démarrer peut donner des résultats incomplets.",
|
||||
"noResults": "Aucun résultat n'a été trouvé dans la recherche indexée.",
|
||||
"onlyFolders": "Dossiers uniquement",
|
||||
"onlyFiles": "Uniquement les fichiers",
|
||||
"showOptions": "Afficher les options",
|
||||
"photos": "Photos",
|
||||
"audio": "Audio",
|
||||
"videos": "Vidéos",
|
||||
"documents": "Documents",
|
||||
"archives": "Archives",
|
||||
"number": "Nombre",
|
||||
"searchContext": "Contexte de la recherche : {context}",
|
||||
"notEnoughCharacters": "Pas assez de caractères pour effectuer une recherche (min {minSearchLength})",
|
||||
"typeToSearch": "Commencez à taper {minSearchLength} ou plus de caractères pour lancer la recherche."
|
||||
"helpText2": "Index : La recherche utilise l'index qui est automatiquement mis à jour à l'intervalle configuré (par défaut : 5 minutes). Une recherche effectuée alors que le programme vient de démarrer peut donner des résultats incomplets."
|
||||
},
|
||||
"settings": {
|
||||
"scopes": "Définir les sources autorisées et la portée de l'utilisateur au sein de celles-ci",
|
||||
@@ -228,8 +215,10 @@
|
||||
"avoidChanges": "(Laisser vide pour conserver l'actuel)",
|
||||
"branding": "Image de marque",
|
||||
"brandingDirectoryPath": "Chemin du dossier d'image de marque",
|
||||
"brandingHelp": "Vous pouvez personnaliser l'apparence de votre instance de File Browser en changeant son nom, en remplaçant le logo, en ajoutant des styles personnalisés et même en désactivant les liens externes vers GitHub.\nPour plus d'informations sur la personnalisation de l'image de marque, veuillez consulter la {0}.",
|
||||
"changePassword": "Modifier le mot de passe",
|
||||
"commandRunner": "Fichier de commandes",
|
||||
"commandRunnerHelp": "Ici, vous pouvez définir les commandes qui sont exécutées pour les événements nommés précédemment. Vous devez en écrire une par ligne. Les variables d'environnement {0} et {1} seront disponibles, {0} étant relatif à {1}. Pour plus d'informations sur cette fonctionnalité et les variables d'environnement disponibles, veuillez lire la {2}.",
|
||||
"commandsUpdated": "Commandes mises à jour !",
|
||||
"createUserDir": "Créer automatiquement un dossier pour l'utilisateur",
|
||||
"customStylesheet": "Feuille de style personnalisée",
|
||||
@@ -311,14 +300,7 @@
|
||||
"adminOptions": "Options de l'utilisateur administrateur",
|
||||
"shareDuration": "Expiration",
|
||||
"searchOptions": "Options de recherche",
|
||||
"downloads": "Téléchargements",
|
||||
"systemAdmin": "Système et administration",
|
||||
"configViewer": "Config Viewer",
|
||||
"configViewerShowFull": "Afficher la configuration complète",
|
||||
"configViewerShowComments": "Afficher les commentaires",
|
||||
"configViewerLoadConfig": "Configuration de la charge",
|
||||
"brandingHelp": "Vous pouvez personnaliser l'apparence de votre instance du navigateur de fichiers en changeant son nom, en remplaçant le logo, en ajoutant des styles personnalisés et même en désactivant les liens externes vers GitHub.\nPour plus d'informations sur la personnalisation de l'image de marque, consultez le site {0}.",
|
||||
"commandRunnerHelp": "Vous pouvez définir ici les commandes qui seront exécutées dans les événements nommés. Vous devez en écrire une par ligne. Les variables d'environnement {0} et {1} seront disponibles, {0} étant relatif à {1}. Pour plus d'informations sur cette fonctionnalité et les variables d'environnement disponibles, veuillez consulter le site {2}."
|
||||
"downloads": "Téléchargements"
|
||||
},
|
||||
"sidebar": {
|
||||
"help": "Aide",
|
||||
@@ -376,21 +358,12 @@
|
||||
"hideNavButtons": "Masquer les boutons de navigation",
|
||||
"hideNavButtonsDescription": "Masquez les boutons de navigation de la barre de navigation dans le partage pour créer une apparence minimaliste.",
|
||||
"viewModeDescription": "Mise en page par défaut de la page partagée.",
|
||||
"titleDefault": "Shared files - {title}",
|
||||
"descriptionDefault": "Une action vous a été envoyée pour que vous la consultiez ou la téléchargiez.",
|
||||
"disableShareCard": "Désactiver la carte de partage",
|
||||
"disableShareCardDescription": "Désactiver la carte de partage sur la page partagée dans la barre latérale ou en haut de la page sur mobile.",
|
||||
"disableSidebar": "Désactiver la barre latérale",
|
||||
"disableSidebarDescription": "Désactiver la barre latérale sur la page partagée.",
|
||||
"enforceDarkLightMode": "Renforcer le mode thématique",
|
||||
"enforceDarkLightModeDescription": "Forcer un mode de thème spécifique (sombre ou clair) pour ce partage, sans tenir compte des préférences de l'utilisateur.",
|
||||
"default": "Ne pas imposer de mode",
|
||||
"dark": "Sombre",
|
||||
"light": "Lumière",
|
||||
"enableOnlyOffice": "Activer la visionneuse OnlyOffice",
|
||||
"enableOnlyOfficeDescription": "Autoriser la visualisation de fichiers bureautiques en utilisant OnlyOffice dans ce partage.",
|
||||
"enableOnlyOfficeEditing": "Activer l'édition OnlyOffice",
|
||||
"enableOnlyOfficeEditingDescription": "Autoriser l'édition de fichiers bureautiques en utilisant OnlyOffice dans ce partage.",
|
||||
"titleDefault": "Fichiers partagés - {title}"
|
||||
"disableSidebarDescription": "Désactiver la barre latérale sur la page partagée."
|
||||
},
|
||||
"api": {
|
||||
"title": "Clés API :",
|
||||
@@ -474,8 +447,8 @@
|
||||
"totalDenied": "Refusé",
|
||||
"totalAllowed": "Autorisé",
|
||||
"all": "Refuser tout",
|
||||
"defaultBehaviorDescription": "Ceci montre ce qui se passe lorsqu'un utilisateur tente d'accéder à un chemin pour lequel il n'existe pas de règles d'accès spécifiques. La mention \"Autoriser\" signifie que les utilisateurs peuvent accéder par défaut, la mention \"Refuser\" signifie que les utilisateurs sont bloqués par défaut, à moins qu'ils n'y soient explicitement autorisés.",
|
||||
"defaultBehavior": "Action par défaut{suffix}"
|
||||
"defaultBehavior": "Default action{suffix}",
|
||||
"defaultBehaviorDescription": "Ceci montre ce qui se passe lorsqu'un utilisateur tente d'accéder à un chemin pour lequel il n'existe pas de règles d'accès spécifiques. La mention \"Autoriser\" signifie que les utilisateurs peuvent accéder par défaut, la mention \"Refuser\" signifie que les utilisateurs sont bloqués par défaut, à moins qu'ils n'y soient explicitement autorisés."
|
||||
},
|
||||
"fileLoading": {
|
||||
"title": "Chargements et téléchargements",
|
||||
@@ -541,24 +514,14 @@
|
||||
"defaultThemeDescription": "default - Le thème par défaut",
|
||||
"customTheme": "Choisissez votre thème",
|
||||
"showSelectMultiple": "Afficher la sélection multiple dans le menu contextuel sur le bureau",
|
||||
"showSelectMultipleDescription": "Par défaut, le menu contextuel de l'ordinateur de bureau n'a pas l'option \"sélectionner plusieurs\" comme celui de l'ordinateur mobile. Cette option apparaît toujours dans le menu contextuel.",
|
||||
"defaultMediaPlayer": "Utilisez le lecteur multimédia natif de votre navigateur",
|
||||
"defaultMediaPlayerDescription": "Utilisez le lecteur multimédia natif de votre navigateur pour les fichiers vidéo et audio plutôt que le lecteur multimédia \"vue-plyr\" inclus dans FileBrowser.",
|
||||
"debugOfficeEditor": "Activer le mode de débogage d'OnlyOffice",
|
||||
"debugOfficeEditorDescription": "Afficher une fenêtre de débogage avec des informations supplémentaires dans l'éditeur OnlyOffice.",
|
||||
"fileViewerOptions": "Options de visualisation des fichiers",
|
||||
"themeAndLanguage": "Thème et langue"
|
||||
"showSelectMultipleDescription": "Par défaut, le menu contextuel de l'ordinateur de bureau n'a pas l'option \"sélectionner plusieurs\" comme celui de l'ordinateur mobile. Cette option apparaît toujours dans le menu contextuel."
|
||||
},
|
||||
"editor": {
|
||||
"uninitialized": "Échec de l'initialisation de l'éditeur. Veuillez recharger.",
|
||||
"saveFailed": "Échec de l'enregistrement du fichier. Veuillez réessayer.",
|
||||
"saveAborted": "Sauvegarde interrompue. Le nom du fichier ne correspond pas au fichier actif.",
|
||||
"saveDisabled": "L'enregistrement de fichiers partagés n'est pas possible.",
|
||||
"saveAbortedMessage": "L'opération d'enregistrement a été interrompue. Le fichier actif de l'application ({activeFile}) ne correspond pas au fichier que vous essayez d'enregistrer ({tryingToSave}).",
|
||||
"syncFailed": "n'a pas réussi à synchroniser l'état avec l'itinéraire pour \"{filename}\" après 5 tentatives. Abandon de la configuration de l'éditeur pour éviter la corruption des données."
|
||||
},
|
||||
"player": {
|
||||
"LoopEnabled": "Boucle activée",
|
||||
"LoopDisabled": "Boucle désactivée"
|
||||
"saveAbortedMessage": "Save operation aborted. The application's active file ({activeFile}) does not match the file you are trying to save ({tryingToSave}).",
|
||||
"syncFailed": "failed to sync state with the route for \"{filename}\" after 5 attempts. Aborting editor setup to prevent data corruption."
|
||||
}
|
||||
}
|
||||
+30
-67
@@ -49,8 +49,7 @@
|
||||
"clearCompleted": "השלם",
|
||||
"showMore": "הצג עוד",
|
||||
"showLess": "הצג פחות",
|
||||
"openParentFolder": "פתח את התיקייה הראשית",
|
||||
"selectedCount": "מספר הפריטים שנבחרו לביצוע פעולה"
|
||||
"openParentFolder": "פתח את התיקייה הראשית"
|
||||
},
|
||||
"download": {
|
||||
"downloadFile": "הורד קובץ",
|
||||
@@ -85,15 +84,15 @@
|
||||
"click": "בחר קובץ או תקייה",
|
||||
"ctrl": {
|
||||
"click": "בחר מספר קבצים או תקיות",
|
||||
"d": "הורדת פריטים נבחרים"
|
||||
"f": "פותח את החיפוש",
|
||||
"s": "לשמור קובץ או להוריד את התקייה שבה אתה נמצא"
|
||||
},
|
||||
"del": "מחק את מה שנבחר",
|
||||
"doubleClick": "פתח קובץ או תקייה",
|
||||
"esc": "נקה את הבחירה ו/או סגור את השדה",
|
||||
"f1": "המידע הזה",
|
||||
"f2": "שנה שם קובץ",
|
||||
"help": "עזרה",
|
||||
"f1": "הצג עזרה",
|
||||
"description": "ניתן לצפות באפשרויות הניווט הבסיסיות להלן. למידע נוסף, בקרו בכתובת",
|
||||
"wiki": "FileBrowser Quantum Wiki"
|
||||
"help": "עזרה"
|
||||
},
|
||||
"languages": {
|
||||
"he": "עברית",
|
||||
@@ -135,13 +134,16 @@
|
||||
"prompts": {
|
||||
"copy": "העתק",
|
||||
"copyMessage": "בחר לאן להעתיק את הקבצים:",
|
||||
"deleteMessageMultiple": "האם אתה בטוח שברצונך למחוק {count} קבצים?",
|
||||
"deleteMessageSingle": "האם אתה בטוח שברצונך למחוק את הקובץ או התקייה?",
|
||||
"deleteMessageShare": "האם אתה בטוח שברצונך למחוק את השיתוף הזה?({path})?",
|
||||
"deleteTitle": "מחק קבצים",
|
||||
"displayName": "שם:",
|
||||
"download": "הורד קבצים",
|
||||
"downloadMessage": "בחר את הפורמט שברצונך להוריד",
|
||||
"error": "משהו השתבש",
|
||||
"fileInfo": "מידע על הקובץ",
|
||||
"lastModified": "שונה לאחרונה{suffix}",
|
||||
"move": "העבר",
|
||||
"moveMessage": "בחר מיקום חדש לקובץ / תקייה:",
|
||||
"newArchetype": "צור פוסט חדש. הקובץ יווצר בתקיית התוכן",
|
||||
@@ -149,7 +151,10 @@
|
||||
"newDirMessage": "כתוב את שם התקייה החדשה",
|
||||
"newFile": "קובץ חדש",
|
||||
"newFileMessage": "כתוב את שם הקובץ החדש",
|
||||
"numberDirs": "מספר התקיות{suffix}",
|
||||
"numberFiles": "מספר הקבצים{suffix}",
|
||||
"rename": "שנה שם",
|
||||
"renameMessage": "הכנס שם חדש עבור",
|
||||
"replace": "החלף",
|
||||
"replaceMessage": "אחד הקבצים בעל שם זהה לקובץ קיים, האם ברצונך להחליף את הקובץ הקיים בחדש? זהירות - הקובץ הישן ימחק\n",
|
||||
"schedule": "תזמון",
|
||||
@@ -167,6 +172,10 @@
|
||||
"dragAndDrop": "גרור ושחרר קבצים לכאן",
|
||||
"completed": "הושלם",
|
||||
"conflictsDetected": "סכסוכים שזוהו",
|
||||
"uploadSettingsChunked": "Max concurrent uploads: {maxConcurrentUpload}, chunk size: {uploadChunkSizeMb} MB",
|
||||
"uploadSettingsNoChunk": "Max concurrent uploads: {maxConcurrentUpload}, chunked upload disabled",
|
||||
"invalidName": "שם לא חוקי: לא ניתן להכיל \"/\" או \"\\\"",
|
||||
"source": "Source{suffix}",
|
||||
"downloadsLimit": "הגבלת מספר ההורדות",
|
||||
"maxBandwidth": "רוחב פס הורדה מרבי (kbps)",
|
||||
"shareTheme": "שתף נושא",
|
||||
@@ -177,44 +186,22 @@
|
||||
"shareBanner": "שתף כתובת URL של באנר",
|
||||
"shareFavicon": "שתף כתובת URL של סמל המועדפים",
|
||||
"optionalPasswordHelp": "אם אפשרות זו מוגדרת, על המשתמשים להזין סיסמה זו כדי להציג את התוכן המשותף.",
|
||||
"viewMode": "מצב תצוגה",
|
||||
"renameMessage": "הזן שם חדש:",
|
||||
"renameMessageInvalid": "קובץ לא יכול להכיל את התווים \"/\" או \"\\\"",
|
||||
"source": "מקור{suffix}",
|
||||
"deleteMessageMultiple": "האם אתה בטוח שברצונך למחוק את הקובץ/ים {count}?",
|
||||
"deleteMessageShare": "האם אתה בטוח שברצונך למחוק שיתוף זה: {path}?",
|
||||
"lastModified": "שינוי אחרון{suffix}",
|
||||
"numberDirs": "מספר הספריות{suffix}",
|
||||
"numberFiles": "מספר הקבצים{suffix}",
|
||||
"renameMessageConflict": "פריט בשם \"{filename}\" כבר קיים!",
|
||||
"uploadSettingsChunked": "מספר העלאות מקסימלי בו-זמנית: {maxConcurrentUpload}, גודל קובץ: {uploadChunkSizeMb} MB",
|
||||
"uploadSettingsNoChunk": "מספר העלאות מקסימלי בו-זמנית: {maxConcurrentUpload}, העלאה מקוטעת מושבתת"
|
||||
"viewMode": "מצב תצוגה"
|
||||
},
|
||||
"search": {
|
||||
"images": "תמונות",
|
||||
"music": "מוזיקה",
|
||||
"pdf": "PDF",
|
||||
"pressToSearch": "הקש אנטר לחיפוש...",
|
||||
"search": "חפש...",
|
||||
"typeToSearch": "הקלד לחיפוש...",
|
||||
"types": "סוג",
|
||||
"video": "וידאו",
|
||||
"path": "נתיב:",
|
||||
"smallerThan": "קטן מ:",
|
||||
"largerThan": "גדול מ:",
|
||||
"helpText1": "החיפוש מתבצע על כל תו שאתה מקליד (מינימום 3 תווים עבור מונחי חיפוש).",
|
||||
"helpText2": "האינדקס: החיפוש משתמש באינדקס שמתעדכן אוטומטית במרווח הזמן שהוגדר (ברירת מחדל: 5 דקות). חיפוש בעת הפעלת התוכנית עלול להניב תוצאות חלקיות.",
|
||||
"noResults": "לא נמצאו תוצאות בחיפוש באינדקס.",
|
||||
"onlyFolders": "רק תיקיות",
|
||||
"onlyFiles": "רק קבצים",
|
||||
"showOptions": "הצג אפשרויות",
|
||||
"photos": "תמונות",
|
||||
"audio": "אודיו",
|
||||
"videos": "סרטונים",
|
||||
"documents": "מסמכים",
|
||||
"archives": "ארכיונים",
|
||||
"number": "מספר",
|
||||
"searchContext": "הקשר החיפוש: {context}",
|
||||
"notEnoughCharacters": "אין מספיק תווים לחיפוש (מינימום {minSearchLength})",
|
||||
"typeToSearch": "התחל להקליד {minSearchLength} או יותר תווים כדי להתחיל בחיפוש."
|
||||
"helpText2": "האינדקס: החיפוש משתמש באינדקס שמתעדכן אוטומטית במרווח הזמן שהוגדר (ברירת מחדל: 5 דקות). חיפוש בעת הפעלת התוכנית עלול להניב תוצאות חלקיות."
|
||||
},
|
||||
"settings": {
|
||||
"admin": "מנהל",
|
||||
@@ -227,8 +214,10 @@
|
||||
"avoidChanges": "(השאר ריק כדי למנוע שינויים)",
|
||||
"branding": "מיתוג",
|
||||
"brandingDirectoryPath": "נתיב תקיית מיתוג",
|
||||
"brandingHelp": "אתה יכול להגדיר את האופן שבו האפליקציה תראה על ידי שינוי שם האפליקציה, החלפת הלוגו, הוספת עיצוב מותאם אישית ואפילו השבתת קישורים חיצוניים לGithub.\nלמידע נוסף עיין ב-{0}.",
|
||||
"changePassword": "שנה סיסמא",
|
||||
"commandRunner": "הרצת פקודות",
|
||||
"commandRunnerHelp": "אתה יכול להגדיר פקודות שיבוצעו באירועים שונים. עליך לכתוב אחד בכל שורה. משתני הסביבה {0} ו-{1} יהיו זמינים, בהיותם {0} ביחס ל-{1}. למידע נוסף על תכונה זו ועל משתני הסביבה הזמינים, עיין ב {2}.",
|
||||
"commandsUpdated": "הפקודות עודכנו.",
|
||||
"createUserDir": "צור תקיית בית במהלך הוספת משתמש חדש",
|
||||
"userHomeBasePath": "נתיב ראשי לתקיות הבית של משתמשים",
|
||||
@@ -311,14 +300,7 @@
|
||||
"adminOptions": "אפשרויות משתמש מנהל",
|
||||
"shareDuration": "תוקף",
|
||||
"searchOptions": "אפשרויות חיפוש",
|
||||
"downloads": "הורדות",
|
||||
"systemAdmin": "מערכת וניהול",
|
||||
"configViewer": "מציג תצורה",
|
||||
"configViewerShowFull": "הצג את התצורה המלאה",
|
||||
"configViewerShowComments": "הצג תגובות",
|
||||
"configViewerLoadConfig": "טען תצורה",
|
||||
"brandingHelp": "ניתן להתאים אישית את המראה והתחושה של דפדפן הקבצים על ידי שינוי שמו, החלפת הלוגו, הוספת סגנונות מותאמים אישית ואפילו השבתת קישורים חיצוניים ל-GitHub.\nלמידע נוסף על מיתוג מותאם אישית, בקרו ב- {0}.",
|
||||
"commandRunnerHelp": "כאן תוכל להגדיר פקודות שיבוצעו באירועים המוזכרים. עליך לכתוב פקודה אחת בכל שורה. המשתנים הסביבתיים {0} ו- {1} יהיו זמינים, והם יהיו {0} ביחס ל- {1}. למידע נוסף על תכונה זו ועל המשתנים הסביבתיים הזמינים, אנא קרא את {2}."
|
||||
"downloads": "הורדות"
|
||||
},
|
||||
"sidebar": {
|
||||
"help": "עזרה",
|
||||
@@ -376,21 +358,12 @@
|
||||
"hideNavButtons": "הסתר כפתורי ניווט",
|
||||
"hideNavButtonsDescription": "הסתר את כפתורי הניווט בסרגל הניווט בשיתוף כדי ליצור מראה מינימליסטי.",
|
||||
"viewModeDescription": "פריסת ברירת המחדל עבור הדף המשותף.",
|
||||
"titleDefault": "Shared files - {title}",
|
||||
"descriptionDefault": "נשלחה אליך קובץ לשיתוף או להורדה.",
|
||||
"disableShareCard": "השבת כרטיס שיתוף",
|
||||
"disableShareCardDescription": "השבת את כרטיס השיתוף בדף המשותף בסרגל הצד או בחלק העליון של הדף במכשירים ניידים.",
|
||||
"disableSidebar": "השבת סרגל צד",
|
||||
"disableSidebarDescription": "השבת את סרגל הצד בדף המשותף.",
|
||||
"enforceDarkLightMode": "אכוף מצב ערכת נושא",
|
||||
"enforceDarkLightModeDescription": "כפה מצב ערכת נושא ספציפי (כהה או בהיר) עבור שיתוף זה, תוך עקיפת העדפות המשתמש.",
|
||||
"default": "אל תכפה מצב",
|
||||
"dark": "אפל",
|
||||
"light": "אור",
|
||||
"enableOnlyOffice": "הפעל את תוכנת הצפייה OnlyOffice",
|
||||
"enableOnlyOfficeDescription": "אפשר צפייה בקבצי Office באמצעות OnlyOffice בשיתוף זה.",
|
||||
"enableOnlyOfficeEditing": "אפשר עריכה ב-OnlyOffice",
|
||||
"enableOnlyOfficeEditingDescription": "אפשר עריכת קבצי Office באמצעות OnlyOffice בשיתוף זה.",
|
||||
"titleDefault": "קבצים משותפים - {title}"
|
||||
"disableSidebarDescription": "השבת את סרגל הצד בדף המשותף."
|
||||
},
|
||||
"api": {
|
||||
"title": "מפתחות API",
|
||||
@@ -474,8 +447,8 @@
|
||||
"totalDenied": "נדחה",
|
||||
"totalAllowed": "מותר",
|
||||
"all": "דחה הכל",
|
||||
"defaultBehaviorDescription": "זה מראה מה קורה כאשר משתמש מנסה לגשת לנתיב שבו אין כללי גישה ספציפיים. 'אפשר' פירושו שמשתמשים יכולים לגשת כברירת מחדל, 'דחה' פירושו שמשתמשים נחסמים כברירת מחדל, אלא אם כן הותר להם במפורש.",
|
||||
"defaultBehavior": "פעולה ברירת מחדל{suffix}"
|
||||
"defaultBehavior": "Default action{suffix}",
|
||||
"defaultBehaviorDescription": "זה מראה מה קורה כאשר משתמש מנסה לגשת לנתיב שבו אין כללי גישה ספציפיים. 'אפשר' פירושו שמשתמשים יכולים לגשת כברירת מחדל, 'דחה' פירושו שמשתמשים נחסמים כברירת מחדל, אלא אם כן הותר להם במפורש."
|
||||
},
|
||||
"fileLoading": {
|
||||
"title": "העלאות והורדות",
|
||||
@@ -541,24 +514,14 @@
|
||||
"defaultThemeDescription": "ברירת מחדל - ערכת הנושא המוגדרת כברירת מחדל",
|
||||
"customTheme": "בחר את הנושא שלך",
|
||||
"showSelectMultiple": "הצג בחירה מרובה בתפריט ההקשר בשולחן העבודה",
|
||||
"showSelectMultipleDescription": "כברירת מחדל, בתפריט ההקשר של שולחן העבודה אין אפשרות \"בחר מספר פריטים\" כמו במכשירים ניידים. אפשרות זו תמיד מוצגת בתפריט ההקשר.",
|
||||
"defaultMediaPlayer": "השתמש בנגן המדיה המובנה בדפדפן שלך",
|
||||
"defaultMediaPlayerDescription": "השתמש בנגן המדיה המובנה בדפדפן שלך עבור קבצי וידאו ואודיו, במקום בנגן המדיה 'vue-plyr' הכלול ב-FileBrowser.",
|
||||
"debugOfficeEditor": "הפעל מצב ניפוי באגים של OnlyOffice",
|
||||
"debugOfficeEditorDescription": "הצג חלון קופץ לאיתור באגים עם מידע נוסף בעורך OnlyOffice.",
|
||||
"fileViewerOptions": "אפשרויות תצוגת קבצים",
|
||||
"themeAndLanguage": "נושא ושפה"
|
||||
"showSelectMultipleDescription": "כברירת מחדל, בתפריט ההקשר של שולחן העבודה אין אפשרות \"בחר מספר פריטים\" כמו במכשירים ניידים. אפשרות זו תמיד מוצגת בתפריט ההקשר."
|
||||
},
|
||||
"editor": {
|
||||
"uninitialized": "לא ניתן לאתחל את העורך. אנא טען מחדש.",
|
||||
"saveFailed": "לא ניתן לשמור את הקובץ. אנא נסה שוב.",
|
||||
"saveAborted": "שמירה בוטלה. שם הקובץ אינו תואם לקובץ הפעיל.",
|
||||
"saveDisabled": "שמירת קבצים משותפים אינה נתמכת.",
|
||||
"saveAbortedMessage": "פעולת השמירה בוטלה. הקובץ הפעיל של היישום ({activeFile}) אינו תואם לקובץ שאתה מנסה לשמור ({tryingToSave}).",
|
||||
"syncFailed": "לא הצליח לסנכרן את המצב עם המסלול עבור \"{filename}\" לאחר 5 ניסיונות. מבטל את הגדרת העורך כדי למנוע פגיעה בנתונים."
|
||||
},
|
||||
"player": {
|
||||
"LoopEnabled": "לולאה מופעלת",
|
||||
"LoopDisabled": "לולאה מושבתת"
|
||||
"saveAbortedMessage": "Save operation aborted. The application's active file ({activeFile}) does not match the file you are trying to save ({tryingToSave}).",
|
||||
"syncFailed": "failed to sync state with the route for \"{filename}\" after 5 attempts. Aborting editor setup to prevent data corruption."
|
||||
}
|
||||
}
|
||||
+30
-67
@@ -49,8 +49,7 @@
|
||||
"clearCompleted": "Tiszta befejezve",
|
||||
"showMore": "Többet mutasson",
|
||||
"showLess": "Kevesebb mutatása",
|
||||
"openParentFolder": "Szülői mappa megnyitása",
|
||||
"selectedCount": "A művelet végrehajtásához kiválasztott elemek száma"
|
||||
"openParentFolder": "Szülői mappa megnyitása"
|
||||
},
|
||||
"download": {
|
||||
"downloadFile": "Fájl letöltése",
|
||||
@@ -85,15 +84,15 @@
|
||||
"click": "mappa vagy fájl kijelölése",
|
||||
"ctrl": {
|
||||
"click": "több mappa vagy fájl kijelölése",
|
||||
"d": "kiválasztott elemek letöltése"
|
||||
"f": "keresés megnyitása",
|
||||
"s": "az aktuális fájl vagy mappa letöltése"
|
||||
},
|
||||
"del": "kijelölt elemek törlése",
|
||||
"doubleClick": "fájl vagy mappa megnyitása",
|
||||
"esc": "kijelölés törlése és/vagy parancssor bezárása",
|
||||
"f1": "ezen információ megjelenítése",
|
||||
"f2": "fájl átnevezése",
|
||||
"help": "Súgó",
|
||||
"f1": "show help prompt",
|
||||
"description": "Az alábbiakban megtekintheti az alapvető navigációs lehetőségeket. További információkért kérjük, látogasson el a következő weboldalra",
|
||||
"wiki": "FileBrowser Quantum Wiki"
|
||||
"help": "Súgó"
|
||||
},
|
||||
"languages": {
|
||||
"he": "עברית",
|
||||
@@ -135,13 +134,16 @@
|
||||
"prompts": {
|
||||
"copy": "Másolása",
|
||||
"copyMessage": "Válassza ki a másolás célját:",
|
||||
"deleteMessageMultiple": "Biztosan törölni szeretne {count} fájlt?",
|
||||
"deleteMessageSingle": "Biztosan törölni szeretné ezt a fájl vagy mappát?",
|
||||
"deleteMessageShare": "Biztosan törölni szeretné ezt a megosztást ({path})?",
|
||||
"deleteTitle": "Fájlok törlése",
|
||||
"displayName": "Megjelenített név:",
|
||||
"download": "Fájlok letöltése",
|
||||
"downloadMessage": "Válassza ki a letöltés formátumát.",
|
||||
"error": "Valami rosszul sült el",
|
||||
"fileInfo": "Fájlinformáció",
|
||||
"lastModified": "Utolsó módosítás{suffix}",
|
||||
"move": "Mozgatás",
|
||||
"moveMessage": "Válasszon új helyet a fájl(ok)nak/mappá(k)nak:",
|
||||
"newArchetype": "Új bejegyzést hoz létre egy archetípus alapján. A fájl a tartalom mappában jön létre.",
|
||||
@@ -149,7 +151,10 @@
|
||||
"newDirMessage": "Adja meg az új mappa nevét.",
|
||||
"newFile": "Új fájl",
|
||||
"newFileMessage": "Adja meg az új fájl nevét.",
|
||||
"numberDirs": "Mappák száma{suffix}",
|
||||
"numberFiles": "Fájlok száma{suffix}",
|
||||
"rename": "Átnevezés",
|
||||
"renameMessage": "Adja meg az új nevét:",
|
||||
"replace": "Csere",
|
||||
"replaceMessage": "Az egyik feltölteni kívánt fájl a neve miatt ütközik. Szeretné lecserélni a meglévő fájlt?\n",
|
||||
"schedule": "Ütemezés",
|
||||
@@ -167,6 +172,10 @@
|
||||
"dragAndDrop": "Húzza ide a fájlokat",
|
||||
"completed": "Befejezett",
|
||||
"conflictsDetected": "Feltárt konfliktusok",
|
||||
"uploadSettingsChunked": "Max concurrent uploads: {maxConcurrentUpload}, chunk size: {uploadChunkSizeMb} MB",
|
||||
"uploadSettingsNoChunk": "Max concurrent uploads: {maxConcurrentUpload}, chunked upload disabled",
|
||||
"invalidName": "Érvénytelen név: nem lehet \"/\" vagy \"\\\" konténer",
|
||||
"source": "Source{suffix}",
|
||||
"downloadsLimit": "Letöltések száma limit",
|
||||
"maxBandwidth": "Maximális letöltési sávszélesség (kbps)",
|
||||
"shareTheme": "Megosztás témája",
|
||||
@@ -177,44 +186,22 @@
|
||||
"shareBanner": "Banner URL megosztása",
|
||||
"shareFavicon": "Favicon URL megosztása",
|
||||
"optionalPasswordHelp": "Ha be van állítva, a felhasználóknak ezt a jelszót kell megadniuk a megosztott tartalom megtekintéséhez.",
|
||||
"viewMode": "Nézet mód",
|
||||
"renameMessage": "Adjon meg egy új nevet:",
|
||||
"renameMessageInvalid": "Egy fájl nem tartalmazhat \"/\" vagy \"\\\" konténert.",
|
||||
"source": "Forrás{suffix}",
|
||||
"deleteMessageMultiple": "Biztos, hogy törölni szeretné a {count} fájl(ok)at?",
|
||||
"deleteMessageShare": "Biztos, hogy törölni szeretné ezt a megosztást: {path} ?",
|
||||
"lastModified": "Utoljára módosítva{suffix}",
|
||||
"numberDirs": "A könyvtárak száma{suffix}",
|
||||
"numberFiles": "Fájlok száma{suffix}",
|
||||
"renameMessageConflict": "A \"{filename}\" nevű elem már létezik!",
|
||||
"uploadSettingsChunked": "Maximális egyidejű feltöltés: {maxConcurrentUpload}, chunk méret: {uploadChunkSizeMb} MB",
|
||||
"uploadSettingsNoChunk": "Maximális egyidejű feltöltés: {maxConcurrentUpload}, chunked upload disabled"
|
||||
"viewMode": "Nézet mód"
|
||||
},
|
||||
"search": {
|
||||
"images": "Képek",
|
||||
"music": "Zene",
|
||||
"pdf": "PDF",
|
||||
"pressToSearch": "Keresés indítása Enterrel…",
|
||||
"search": "Keresés…",
|
||||
"typeToSearch": "Keresés indítása beírással…",
|
||||
"types": "Típusok",
|
||||
"video": "Videó",
|
||||
"path": "Útvonal:",
|
||||
"smallerThan": "Kisebb, mint:",
|
||||
"largerThan": "Nagyobb, mint:",
|
||||
"helpText1": "A keresés minden egyes beírt karakterre történik (minimum 3 karakteres keresőkifejezés).",
|
||||
"helpText2": "Az index: A keresés az indexet használja, amely automatikusan frissül a beállított időközönként (alapértelmezett: 5 perc). A program indulásakor végzett keresés hiányos eredményeket eredményezhet.",
|
||||
"noResults": "Az indexelt keresésben nem találtak találatot.",
|
||||
"onlyFolders": "Csak mappák",
|
||||
"onlyFiles": "Csak fájlok",
|
||||
"showOptions": "Beállítások megjelenítése",
|
||||
"photos": "Fotók",
|
||||
"audio": "Audio",
|
||||
"videos": "Videók",
|
||||
"documents": "Dokumentumok",
|
||||
"archives": "Archívum",
|
||||
"number": "Szám",
|
||||
"searchContext": "Keresési kontextus: {context}",
|
||||
"notEnoughCharacters": "Nincs elég karakter a kereséshez (min {minSearchLength})",
|
||||
"typeToSearch": "A keresés megkezdéséhez írja be a {minSearchLength} vagy több karaktert."
|
||||
"helpText2": "Az index: A keresés az indexet használja, amely automatikusan frissül a beállított időközönként (alapértelmezett: 5 perc). A program indulásakor végzett keresés hiányos eredményeket eredményezhet."
|
||||
},
|
||||
"settings": {
|
||||
"admin": "Admin",
|
||||
@@ -227,8 +214,10 @@
|
||||
"avoidChanges": "(üresen hagyva nincs változás)",
|
||||
"branding": "Márkázás",
|
||||
"brandingDirectoryPath": "Márkázás mappaútvonala",
|
||||
"brandingHelp": "Testre szabhatja a File Browser példányának megjelenését a név megváltoztatásával, a logó cseréjével, egyéni stílusok hozzáadásával és még a GitHubra mutató külső hivatkozások letiltásával is.\nAz egyéni márkázással kapcsolatos további információkért tekintse meg: {0}.",
|
||||
"changePassword": "Jelszó módosítása",
|
||||
"commandRunner": "Parancsfuttató",
|
||||
"commandRunnerHelp": "Beállíthatja azokat a parancsokat, amelyek a megnevezett események során végrehajtásra kerülnek. Soronként egyet kell megadni. A {0} és a {1} környezeti változók lesznek elérhetőek, ahol a {0} relatív a {1}-hez. A funkcióról és a rendelkezésre álló környezeti változókról további információ: {2}.",
|
||||
"commandsUpdated": "Parancsok frissítve!",
|
||||
"createUserDir": "Felhasználók saját mappáinak automatikus létrehozása új felhasználók hozzáadásakor",
|
||||
"userHomeBasePath": "Alap elérési útvonal a felhasználók saját mappáihoz",
|
||||
@@ -311,14 +300,7 @@
|
||||
"adminOptions": "Admin felhasználói beállítások",
|
||||
"shareDuration": "Lejárat:",
|
||||
"searchOptions": "Keresési lehetőségek",
|
||||
"downloads": "Letöltések",
|
||||
"systemAdmin": "Rendszer és adminisztráció",
|
||||
"configViewer": "Konfiguráció megtekintő",
|
||||
"configViewerShowFull": "Teljes konfiguráció megjelenítése",
|
||||
"configViewerShowComments": "Hozzászólások megjelenítése",
|
||||
"configViewerLoadConfig": "Konfiguráció betöltése",
|
||||
"brandingHelp": "Testreszabhatja a Fájlböngésző példányának megjelenését és érzetét a név megváltoztatásával, a logó cseréjével, egyéni stílusok hozzáadásával és akár a GitHubra mutató külső hivatkozások letiltásával.\nAz egyéni márkaépítéssel kapcsolatos további információkat a {0} oldalon talál.",
|
||||
"commandRunnerHelp": "Itt állíthatja be azokat a parancsokat, amelyek a megnevezett események során végrehajtásra kerülnek. Soronként egyet kell írni. A {0} és a {1} környezeti változók lesznek elérhetőek, a {0} a {1}-hoz képest . A funkcióról és a rendelkezésre álló környezeti változókról bővebb információt a {2} oldalon talál."
|
||||
"downloads": "Letöltések"
|
||||
},
|
||||
"sidebar": {
|
||||
"help": "Súgó",
|
||||
@@ -376,21 +358,12 @@
|
||||
"hideNavButtons": "Navigációs gombok elrejtése",
|
||||
"hideNavButtonsDescription": "Rejtse el a navigációs gombokat a megosztás navigációs sávján a minimalista megjelenés érdekében.",
|
||||
"viewModeDescription": "A megosztott oldal alapértelmezett elrendezése.",
|
||||
"titleDefault": "Shared files - {title}",
|
||||
"descriptionDefault": "Megosztást küldtünk Önnek megtekintésre vagy letöltésre.",
|
||||
"disableShareCard": "Megosztási kártya letiltása",
|
||||
"disableShareCardDescription": "Kapcsolja ki a megosztási kártyát a megosztott oldalon az oldalsávban vagy mobilon az oldal tetején.",
|
||||
"disableSidebar": "Oldalsáv kikapcsolása",
|
||||
"disableSidebarDescription": "Kapcsolja ki az oldalsávot a megosztott oldalon.",
|
||||
"enforceDarkLightMode": "Témamód érvényesítése",
|
||||
"enforceDarkLightModeDescription": "Egy adott témamód (sötét vagy világos) kikényszerítése a megosztás számára, felülírva a felhasználói beállításokat.",
|
||||
"default": "Ne kényszerítsen ki üzemmódot",
|
||||
"dark": "Sötét",
|
||||
"light": "Fény",
|
||||
"enableOnlyOffice": "OnlyOffice megjelenítő engedélyezése",
|
||||
"enableOnlyOfficeDescription": "Engedélyezze az irodai fájlok megtekintését az OnlyOffice használatával ezen a megosztáson.",
|
||||
"enableOnlyOfficeEditing": "Engedélyezze az OnlyOffice szerkesztést",
|
||||
"enableOnlyOfficeEditingDescription": "Engedélyezze az irodai fájlok szerkesztését az OnlyOffice használatával ezen a megosztáson.",
|
||||
"titleDefault": "Megosztott fájlok - {title}"
|
||||
"disableSidebarDescription": "Kapcsolja ki az oldalsávot a megosztott oldalon."
|
||||
},
|
||||
"api": {
|
||||
"title": "API-kulcsok:",
|
||||
@@ -474,8 +447,8 @@
|
||||
"totalDenied": "Elutasítva",
|
||||
"totalAllowed": "Engedélyezett",
|
||||
"all": "Mindent megtagadni",
|
||||
"defaultBehaviorDescription": "Ez azt mutatja, hogy mi történik, ha egy felhasználó megpróbál hozzáférni egy olyan elérési útvonalhoz, ahol nincsenek speciális hozzáférési szabályok. Az 'Allow' azt jelenti, hogy a felhasználók alapértelmezés szerint hozzáférhetnek, a 'Deny' azt jelenti, hogy a felhasználók alapértelmezés szerint blokkolva vannak, kivéve, ha kifejezetten engedélyezik.",
|
||||
"defaultBehavior": "Alapértelmezett cselekvés{suffix}"
|
||||
"defaultBehavior": "Default action{suffix}",
|
||||
"defaultBehaviorDescription": "Ez azt mutatja, hogy mi történik, ha egy felhasználó megpróbál hozzáférni egy olyan elérési útvonalhoz, ahol nincsenek speciális hozzáférési szabályok. Az 'Allow' azt jelenti, hogy a felhasználók alapértelmezés szerint hozzáférhetnek, a 'Deny' azt jelenti, hogy a felhasználók alapértelmezés szerint blokkolva vannak, kivéve, ha kifejezetten engedélyezik."
|
||||
},
|
||||
"fileLoading": {
|
||||
"title": "Feltöltések és letöltések",
|
||||
@@ -541,24 +514,14 @@
|
||||
"defaultThemeDescription": "default - Az alapértelmezett téma",
|
||||
"customTheme": "Válasszon témát",
|
||||
"showSelectMultiple": "Többszörös kijelölés megjelenítése az asztali kontextusmenüben",
|
||||
"showSelectMultipleDescription": "Alapértelmezés szerint az asztali kontextusmenü nem rendelkezik a \"többszörös kiválasztás\" opcióval, mint a mobilon. Ez az opció mindig megjelenik a kontextusmenüben.",
|
||||
"defaultMediaPlayer": "Használja a böngésző natív médialejátszóját",
|
||||
"defaultMediaPlayerDescription": "Használja a böngésző natív médialejátszóját a videó- és hangfájlokhoz, nem pedig a FileBrowser mellékelt \"vue-plyr\" médialejátszóját.",
|
||||
"debugOfficeEditor": "OnlyOffice hibakeresési mód engedélyezése",
|
||||
"debugOfficeEditorDescription": "További információkat tartalmazó hibakeresési felugró ablak megjelenítése az OnlyOffice szerkesztőben.",
|
||||
"fileViewerOptions": "Fájlnéző beállításai",
|
||||
"themeAndLanguage": "Téma és nyelv"
|
||||
"showSelectMultipleDescription": "Alapértelmezés szerint az asztali kontextusmenü nem rendelkezik a \"többszörös kiválasztás\" opcióval, mint a mobilon. Ez az opció mindig megjelenik a kontextusmenüben."
|
||||
},
|
||||
"editor": {
|
||||
"uninitialized": "Nem sikerült inicializálni a szerkesztőt. Kérjük, töltse újra.",
|
||||
"saveFailed": "A fájl mentése sikertelen. Kérjük, próbálja újra.",
|
||||
"saveAborted": "A mentés megszakadt. A fájl neve nem egyezik az aktív fájléval.",
|
||||
"saveDisabled": "A megosztott fájlok mentése nem támogatott.",
|
||||
"saveAbortedMessage": "A mentési művelet megszakadt. Az alkalmazás aktív fájlja ({activeFile}) nem egyezik meg a menteni kívánt fájllal ({tryingToSave}).",
|
||||
"syncFailed": "5 próbálkozás után nem sikerült szinkronizálni az állapotot a \"{filename}\" útvonallal. A szerkesztő beállításának megszakítása az adatok sérülésének elkerülése érdekében."
|
||||
},
|
||||
"player": {
|
||||
"LoopEnabled": "Hurok engedélyezve",
|
||||
"LoopDisabled": "Hurok letiltva"
|
||||
"saveAbortedMessage": "Save operation aborted. The application's active file ({activeFile}) does not match the file you are trying to save ({tryingToSave}).",
|
||||
"syncFailed": "failed to sync state with the route for \"{filename}\" after 5 attempts. Aborting editor setup to prevent data corruption."
|
||||
}
|
||||
}
|
||||
+30
-67
@@ -49,8 +49,7 @@
|
||||
"clearCompleted": "Completato il lavoro di pulizia",
|
||||
"showMore": "Mostra di più",
|
||||
"showLess": "Mostra meno",
|
||||
"openParentFolder": "Aprire la cartella madre",
|
||||
"selectedCount": "Numero di elementi selezionati per eseguire un'azione"
|
||||
"openParentFolder": "Aprire la cartella madre"
|
||||
},
|
||||
"download": {
|
||||
"downloadFile": "Scarica file",
|
||||
@@ -85,15 +84,15 @@
|
||||
"click": "seleziona un file o una cartella",
|
||||
"ctrl": {
|
||||
"click": "seleziona più file o cartelle",
|
||||
"d": "scaricare gli articoli selezionati"
|
||||
"f": "apre la barra di ricerca",
|
||||
"s": "salva un file o scarica la cartella in cui ci si trova"
|
||||
},
|
||||
"del": "elimina gli elementi selezionati",
|
||||
"doubleClick": "apre un file o una cartella",
|
||||
"esc": "annulla la selezione e/o chiude la finestra aperta",
|
||||
"f1": "questo pannello",
|
||||
"f2": "rinomina un file",
|
||||
"help": "Aiuto",
|
||||
"f1": "mostra la richiesta di aiuto",
|
||||
"description": "Le opzioni di navigazione di base sono riportate di seguito. Per ulteriori informazioni, visitate il sito",
|
||||
"wiki": "Wiki di FileBrowser Quantum"
|
||||
"help": "Aiuto"
|
||||
},
|
||||
"languages": {
|
||||
"he": "עברית",
|
||||
@@ -135,6 +134,7 @@
|
||||
"prompts": {
|
||||
"copy": "Copia",
|
||||
"copyMessage": "Seleziona la cartella in cui copiare i file:",
|
||||
"deleteMessageMultiple": "Sei sicuro di voler eliminare {count} file?",
|
||||
"deleteMessageSingle": "Sei sicuro di voler eliminare questo file/cartella?",
|
||||
"deleteTitle": "Elimina",
|
||||
"displayName": "Nome visualizzato:",
|
||||
@@ -142,6 +142,7 @@
|
||||
"downloadMessage": "Seleziona il formato che vuoi scaricare.",
|
||||
"error": "Qualcosa è andato per il verso storto",
|
||||
"fileInfo": "Informazioni sul file",
|
||||
"lastModified": "Ultima modifica{suffix}",
|
||||
"move": "Sposta",
|
||||
"moveMessage": "Seleziona la nuova posizione per i tuoi file e/o cartella/e:",
|
||||
"newArchetype": "Crea un nuovo post basato su un modello. Il tuo file verrà creato nella cartella.",
|
||||
@@ -149,7 +150,10 @@
|
||||
"newDirMessage": "Scrivi il nome della nuova cartella.",
|
||||
"newFile": "Nuovo file",
|
||||
"newFileMessage": "Scrivi il nome del nuovo file.",
|
||||
"numberDirs": "Numero di cartelle{suffix}",
|
||||
"numberFiles": "Numero di file{suffix}",
|
||||
"rename": "Rinomina",
|
||||
"renameMessage": "Inserisci un nuovo nome per",
|
||||
"replace": "Sostituisci",
|
||||
"replaceMessage": "Uno dei file che stai cercando di caricare sta generando un conflitto per via del suo nome. Desideri sostituire il file già esistente?\n",
|
||||
"schedule": "Pianifica",
|
||||
@@ -157,6 +161,7 @@
|
||||
"show": "Mostra",
|
||||
"size": "Dimensione",
|
||||
"upload": "Carica",
|
||||
"deleteMessageShare": "Are you sure you want to delete this share: {path} ?",
|
||||
"deleteUserMessage": "Siete sicuri di voler eliminare questo utente?",
|
||||
"filesSelected": "file selezionati",
|
||||
"optionalPassword": "Password opzionale",
|
||||
@@ -167,6 +172,10 @@
|
||||
"dragAndDrop": "Trascinare e rilasciare i file qui",
|
||||
"completed": "Completato",
|
||||
"conflictsDetected": "Conflitti rilevati",
|
||||
"uploadSettingsChunked": "Max concurrent uploads: {maxConcurrentUpload}, chunk size: {uploadChunkSizeMb} MB",
|
||||
"uploadSettingsNoChunk": "Max concurrent uploads: {maxConcurrentUpload}, chunked upload disabled",
|
||||
"invalidName": "Nome non valido: non può contenere \"/\" o \"\\\".",
|
||||
"source": "Source{suffix}",
|
||||
"downloadsLimit": "Limite del numero di download",
|
||||
"maxBandwidth": "Larghezza di banda massima per il download (kbps)",
|
||||
"shareTheme": "Tema della condivisione",
|
||||
@@ -177,44 +186,22 @@
|
||||
"shareBanner": "Condividi l'URL del banner",
|
||||
"shareFavicon": "Condividere l'URL della favicon",
|
||||
"optionalPasswordHelp": "Se impostata, gli utenti devono inserire questa password per visualizzare il contenuto condiviso.",
|
||||
"viewMode": "Modalità di visualizzazione",
|
||||
"renameMessage": "Inserire un nuovo nome:",
|
||||
"renameMessageInvalid": "Un file non può contenere \"/\" o \"\\\".",
|
||||
"source": "Fonte{suffix}",
|
||||
"deleteMessageMultiple": "Siete sicuri di voler eliminare i file {count}?",
|
||||
"deleteMessageShare": "Sei sicuro di voler eliminare questa condivisione: {path} ?",
|
||||
"lastModified": "Ultima modifica{suffix}",
|
||||
"numberDirs": "Numero di directory{suffix}",
|
||||
"numberFiles": "Numero di file{suffix}",
|
||||
"renameMessageConflict": "Un elemento chiamato \"{filename}\" esiste già!",
|
||||
"uploadSettingsChunked": "Carichi massimi contemporanei: {maxConcurrentUpload}, dimensione dei pezzi: {uploadChunkSizeMb} MB",
|
||||
"uploadSettingsNoChunk": "Carichi massimi contemporanei: {maxConcurrentUpload}, caricamento chunked disabilitato"
|
||||
"viewMode": "Modalità di visualizzazione"
|
||||
},
|
||||
"search": {
|
||||
"images": "Immagini",
|
||||
"music": "Musica",
|
||||
"pdf": "PDF",
|
||||
"pressToSearch": "Premi Invio per cercare...",
|
||||
"search": "Cerca...",
|
||||
"typeToSearch": "Scrivi per cercare...",
|
||||
"types": "Tipi",
|
||||
"video": "Video",
|
||||
"path": "Percorso:",
|
||||
"smallerThan": "Più piccolo di:",
|
||||
"largerThan": "Più grande di:",
|
||||
"helpText1": "La ricerca avviene per ogni carattere digitato (minimo 3 caratteri per i termini di ricerca).",
|
||||
"helpText2": "L'indice: La ricerca utilizza l'indice che viene aggiornato automaticamente all'intervallo configurato (default: 5 minuti). La ricerca quando il programma è appena stato avviato può dare risultati incompleti.",
|
||||
"noResults": "Nessun risultato trovato nella ricerca indicizzata.",
|
||||
"onlyFolders": "Solo cartelle",
|
||||
"onlyFiles": "Solo file",
|
||||
"showOptions": "Mostra opzioni",
|
||||
"photos": "Foto",
|
||||
"audio": "Audio",
|
||||
"videos": "Video",
|
||||
"documents": "Documenti",
|
||||
"archives": "Archivio",
|
||||
"number": "Numero",
|
||||
"searchContext": "Contesto di ricerca: {context}",
|
||||
"notEnoughCharacters": "Non ci sono abbastanza caratteri per la ricerca (min {minSearchLength})",
|
||||
"typeToSearch": "Iniziare a digitare {minSearchLength} o più caratteri per avviare la ricerca."
|
||||
"helpText2": "L'indice: La ricerca utilizza l'indice che viene aggiornato automaticamente all'intervallo configurato (default: 5 minuti). La ricerca quando il programma è appena stato avviato può dare risultati incompleti."
|
||||
},
|
||||
"settings": {
|
||||
"admin": "Admin",
|
||||
@@ -227,8 +214,10 @@
|
||||
"avoidChanges": "(lascia vuoto per evitare cambiamenti)",
|
||||
"branding": "Branding",
|
||||
"brandingDirectoryPath": "Directory del branding",
|
||||
"brandingHelp": "Puoi personalizzare l'aspetto e il comportamento di File Browser cambiando il suo nome, logo, aggiungendo stili personalizzati e anche disabilitando link esterni verso GitHub.\nPer altre informazioni sul branding personalizzato, vai alla {0}.",
|
||||
"changePassword": "Modifica password",
|
||||
"commandRunner": "Esecutore di comandi",
|
||||
"commandRunnerHelp": "Qui puoi impostare i comandi da eseguire negli eventi nominati. Ne devi scrivere uno per riga. Le variabili d'ambiente {0} e {1} sono disponibili, essendo {0} relativo a {1}. Per altre informazioni su questa funzionalità e sulle variabili d'ambiente utilizzabili, leggi la {2}.",
|
||||
"commandsUpdated": "Comandi aggiornati!",
|
||||
"createUserDir": "Crea automaticamente la home directory dell'utente quando lo aggiungi",
|
||||
"customStylesheet": "Foglio di stile personalizzato",
|
||||
@@ -311,14 +300,7 @@
|
||||
"adminOptions": "Opzioni dell'utente amministratore",
|
||||
"shareDuration": "Scadenza",
|
||||
"searchOptions": "Opzioni di ricerca",
|
||||
"downloads": "Scaricamento",
|
||||
"systemAdmin": "Sistema e amministrazione",
|
||||
"configViewer": "Visualizzatore di configurazione",
|
||||
"configViewerShowFull": "Mostra la configurazione completa",
|
||||
"configViewerShowComments": "Mostra i commenti",
|
||||
"configViewerLoadConfig": "Configurazione del carico",
|
||||
"brandingHelp": "È possibile personalizzare l'aspetto dell'istanza del File Browser cambiandone il nome, sostituendo il logo, aggiungendo stili personalizzati e persino disabilitando i collegamenti esterni a GitHub.\nPer ulteriori informazioni sul branding personalizzato, consultare il sito {0}.",
|
||||
"commandRunnerHelp": "Qui è possibile impostare i comandi che vengono eseguiti negli eventi denominati. È necessario scriverne uno per riga. Le variabili d'ambiente {0} e {1} saranno disponibili, essendo {0} relativa a {1}. Per ulteriori informazioni su questa funzione e sulle variabili d'ambiente disponibili, leggere {2}."
|
||||
"downloads": "Scaricamento"
|
||||
},
|
||||
"sidebar": {
|
||||
"help": "Aiuto",
|
||||
@@ -376,21 +358,12 @@
|
||||
"hideNavButtons": "Nascondere i pulsanti di navigazione",
|
||||
"hideNavButtonsDescription": "Nascondere i pulsanti di navigazione sulla navbar nella condivisione per creare un look minimalista.",
|
||||
"viewModeDescription": "Layout predefinito per la pagina condivisa.",
|
||||
"titleDefault": "Shared files - {title}",
|
||||
"descriptionDefault": "È stata inviata una condivisione da visualizzare o scaricare.",
|
||||
"disableShareCard": "Disattivare la scheda di condivisione",
|
||||
"disableShareCardDescription": "Disattivare la scheda di condivisione sulla pagina condivisa nella barra laterale o nella parte superiore della pagina su mobile.",
|
||||
"disableSidebar": "Disattivare la barra laterale",
|
||||
"disableSidebarDescription": "Disattivare la barra laterale della pagina condivisa.",
|
||||
"enforceDarkLightMode": "Applicare la modalità a tema",
|
||||
"enforceDarkLightModeDescription": "Forza una modalità tema specifica (scuro o chiaro) per questa condivisione, ignorando le preferenze dell'utente.",
|
||||
"default": "Non applicare una modalità",
|
||||
"dark": "Scuro",
|
||||
"light": "Luce",
|
||||
"enableOnlyOffice": "Abilitate il visualizzatore OnlyOffice",
|
||||
"enableOnlyOfficeDescription": "Consente di visualizzare i file di Office utilizzando OnlyOffice in questa condivisione.",
|
||||
"enableOnlyOfficeEditing": "Abilita la modifica di OnlyOffice",
|
||||
"enableOnlyOfficeEditingDescription": "Consentire la modifica dei file di Office utilizzando OnlyOffice in questa condivisione.",
|
||||
"titleDefault": "File condivisi - {title}"
|
||||
"disableSidebarDescription": "Disattivare la barra laterale della pagina condivisa."
|
||||
},
|
||||
"api": {
|
||||
"title": "Chiavi API:",
|
||||
@@ -474,8 +447,8 @@
|
||||
"totalDenied": "Negato",
|
||||
"totalAllowed": "Consentito",
|
||||
"all": "Rifiuta tutto",
|
||||
"defaultBehaviorDescription": "Questo mostra cosa succede quando un utente cerca di accedere a un percorso per il quale non esistono regole di accesso specifiche. 'Consenti' significa che gli utenti possono accedere per impostazione predefinita, 'Rifiuta' significa che gli utenti sono bloccati per impostazione predefinita, a meno che non siano esplicitamente autorizzati.",
|
||||
"defaultBehavior": "Azione predefinita{suffix}"
|
||||
"defaultBehavior": "Default action{suffix}",
|
||||
"defaultBehaviorDescription": "Questo mostra cosa succede quando un utente cerca di accedere a un percorso per il quale non esistono regole di accesso specifiche. 'Consenti' significa che gli utenti possono accedere per impostazione predefinita, 'Rifiuta' significa che gli utenti sono bloccati per impostazione predefinita, a meno che non siano esplicitamente autorizzati."
|
||||
},
|
||||
"fileLoading": {
|
||||
"title": "Caricamenti e download",
|
||||
@@ -541,24 +514,14 @@
|
||||
"defaultThemeDescription": "default - Il tema predefinito",
|
||||
"customTheme": "Scegliete il vostro tema",
|
||||
"showSelectMultiple": "Mostra selezione multipla nel menu contestuale sul desktop",
|
||||
"showSelectMultipleDescription": "Per impostazione predefinita, il menu contestuale del desktop non dispone dell'opzione \"seleziona più\" come quello del cellulare. Questa opzione viene sempre visualizzata nel menu contestuale.",
|
||||
"defaultMediaPlayer": "Utilizzare il lettore multimediale nativo del browser",
|
||||
"defaultMediaPlayerDescription": "Utilizzate il lettore multimediale nativo del vostro browser per i file video e audio piuttosto che il lettore multimediale \"vue-plyr\" incluso in FileBrowser.",
|
||||
"debugOfficeEditor": "Abilita la modalità di debug di OnlyOffice",
|
||||
"debugOfficeEditorDescription": "Mostra un popup di debug con informazioni aggiuntive nell'editor di OnlyOffice.",
|
||||
"fileViewerOptions": "Opzioni del visualizzatore di file",
|
||||
"themeAndLanguage": "Tema e linguaggio"
|
||||
"showSelectMultipleDescription": "Per impostazione predefinita, il menu contestuale del desktop non dispone dell'opzione \"seleziona più\" come quello del cellulare. Questa opzione viene sempre visualizzata nel menu contestuale."
|
||||
},
|
||||
"editor": {
|
||||
"uninitialized": "Impossibile inizializzare l'editor. Ricaricare.",
|
||||
"saveFailed": "Impossibile salvare il file. Riprovare.",
|
||||
"saveAborted": "Salvataggio interrotto. Il nome del file non corrisponde al file attivo.",
|
||||
"saveDisabled": "Il salvataggio di file condivisi non è supportato.",
|
||||
"saveAbortedMessage": "Operazione di salvataggio interrotta. Il file attivo dell'applicazione ({activeFile}) non corrisponde al file che si sta cercando di salvare ({tryingToSave}).",
|
||||
"syncFailed": "non è riuscito a sincronizzare lo stato con il percorso per \"{filename}\" dopo 5 tentativi. Interruzione dell'impostazione dell'editor per evitare la corruzione dei dati."
|
||||
},
|
||||
"player": {
|
||||
"LoopEnabled": "Loop abilitato",
|
||||
"LoopDisabled": "Loop disabilitato"
|
||||
"saveAbortedMessage": "Save operation aborted. The application's active file ({activeFile}) does not match the file you are trying to save ({tryingToSave}).",
|
||||
"syncFailed": "failed to sync state with the route for \"{filename}\" after 5 attempts. Aborting editor setup to prevent data corruption."
|
||||
}
|
||||
}
|
||||
+30
-67
@@ -49,8 +49,7 @@
|
||||
"clearCompleted": "クリア完了",
|
||||
"showMore": "もっと見る",
|
||||
"showLess": "もっと見る",
|
||||
"openParentFolder": "親フォルダを開く",
|
||||
"selectedCount": "アクションを実行するために選択された項目の数"
|
||||
"openParentFolder": "親フォルダを開く"
|
||||
},
|
||||
"download": {
|
||||
"downloadFile": "Download File",
|
||||
@@ -85,15 +84,15 @@
|
||||
"click": "ファイルやディレクトリを選択",
|
||||
"ctrl": {
|
||||
"click": "複数のファイルやディレクトリを選択",
|
||||
"d": "ダウンロード"
|
||||
"f": "検索を有効にする",
|
||||
"s": "ファイルを保存またはカレントディレクトリをダウンロード"
|
||||
},
|
||||
"del": "選択した項目を削除",
|
||||
"doubleClick": "ファイルやディレクトリをオープン",
|
||||
"esc": "選択をクリアーまたはプロンプトを閉じる",
|
||||
"f1": "このヘルプを表示",
|
||||
"f2": "ファイルの名前を変更",
|
||||
"help": "ヘルプ",
|
||||
"f1": "ヘルププロンプトを表示する",
|
||||
"description": "基本的なナビオプションは以下からご覧いただけます。その他の情報については",
|
||||
"wiki": "ファイルブラウザ クォンタム ウィキ"
|
||||
"help": "ヘルプ"
|
||||
},
|
||||
"languages": {
|
||||
"he": "עברית",
|
||||
@@ -135,6 +134,7 @@
|
||||
"prompts": {
|
||||
"copy": "コピー",
|
||||
"copyMessage": "コピーの目標ディレクトリを選択してください:",
|
||||
"deleteMessageMultiple": "{count} つのファイルを本当に削除してよろしいですか。",
|
||||
"deleteMessageSingle": "このファイル/フォルダを本当に削除してよろしいですか。",
|
||||
"deleteTitle": "ファイルを削除",
|
||||
"displayName": "名前:",
|
||||
@@ -142,6 +142,7 @@
|
||||
"downloadMessage": "圧縮形式を選択してください。",
|
||||
"error": "あるエラーが発生しました。",
|
||||
"fileInfo": "ファイル情報",
|
||||
"lastModified": "最終変更{suffix}",
|
||||
"move": "移動",
|
||||
"moveMessage": "移動の目標ディレクトリを選択してください:",
|
||||
"newArchetype": "ある元型に基づいて新しいポストを作成します。ファイルは コンテンツフォルダに作成されます。",
|
||||
@@ -149,7 +150,10 @@
|
||||
"newDirMessage": "新しいディレクトリの名前を入力してください。",
|
||||
"newFile": "新しいファイルを作成",
|
||||
"newFileMessage": "新しいファイルの名前を入力してください。",
|
||||
"numberDirs": "ディレクトリ個数{suffix}",
|
||||
"numberFiles": "ファイル個数{suffix}",
|
||||
"rename": "名前を変更",
|
||||
"renameMessage": "名前を変更しようファイルは:",
|
||||
"replace": "置き換える",
|
||||
"replaceMessage": "アップロードするファイルの中でかち合う名前が一つあります。 既存のファイルを置き換えりませんか。\n",
|
||||
"schedule": "スケジュール",
|
||||
@@ -157,6 +161,7 @@
|
||||
"show": "表示",
|
||||
"size": "サイズ",
|
||||
"upload": "アップロード",
|
||||
"deleteMessageShare": "Are you sure you want to delete this share: {path} ?",
|
||||
"deleteUserMessage": "本当にこのユーザーを削除しますか?",
|
||||
"filesSelected": "選択されたファイル",
|
||||
"optionalPassword": "任意のパスワード",
|
||||
@@ -167,6 +172,10 @@
|
||||
"dragAndDrop": "ここにファイルをドラッグ&ドロップする",
|
||||
"completed": "完成",
|
||||
"conflictsDetected": "コンフリクト検出",
|
||||
"uploadSettingsChunked": "Max concurrent uploads: {maxConcurrentUpload}, chunk size: {uploadChunkSizeMb} MB",
|
||||
"uploadSettingsNoChunk": "Max concurrent uploads: {maxConcurrentUpload}, chunked upload disabled",
|
||||
"invalidName": "無効な名前が指定されました:\"/\"または\"˶\"をコンテナに入れることはできません。",
|
||||
"source": "Source{suffix}",
|
||||
"downloadsLimit": "ダウンロード数制限",
|
||||
"maxBandwidth": "最大ダウンロード帯域幅 (kbps)",
|
||||
"shareTheme": "シェアテーマ",
|
||||
@@ -177,44 +186,22 @@
|
||||
"shareBanner": "共有バナーURL",
|
||||
"shareFavicon": "ファビコンのURLを共有する",
|
||||
"optionalPasswordHelp": "設定されている場合、ユーザーは共有コンテンツを見るためにこのパスワードを入力しなければならない。",
|
||||
"viewMode": "ビューモード",
|
||||
"renameMessage": "新しい名前を入力する:",
|
||||
"renameMessageInvalid": "ファイルには\"/\"や\"˶\"を入れることはできません。",
|
||||
"source": "ソース{suffix}",
|
||||
"deleteMessageMultiple": "本当に{count} ファイルを削除しますか?",
|
||||
"deleteMessageShare": "この共有を削除してもよろしいですか? {path} ?",
|
||||
"lastModified": "最終更新日{suffix}",
|
||||
"numberDirs": "ディレクトリ数{suffix}",
|
||||
"numberFiles": "ファイル数{suffix}",
|
||||
"renameMessageConflict": "{filename}\" という名前のアイテムがすでに存在する!",
|
||||
"uploadSettingsChunked": "最大同時アップロード数{maxConcurrentUpload}チャンクサイズ {uploadChunkSizeMb} MB",
|
||||
"uploadSettingsNoChunk": "最大同時アップロード数{maxConcurrentUpload}チャンクアップロード無効"
|
||||
"viewMode": "ビューモード"
|
||||
},
|
||||
"search": {
|
||||
"images": "画像",
|
||||
"music": "音楽",
|
||||
"pdf": "PDF",
|
||||
"pressToSearch": "Press enter to search...",
|
||||
"search": "検索...",
|
||||
"typeToSearch": "Type to search...",
|
||||
"types": "種類",
|
||||
"video": "ビデオ",
|
||||
"path": "パス",
|
||||
"smallerThan": "より小さい:",
|
||||
"largerThan": "より大きい:",
|
||||
"helpText1": "検索は入力した文字ごとに行われます(検索語は最低3文字)。",
|
||||
"helpText2": "インデックス:検索は、設定された間隔(デフォルト:5分)で自動的に更新されるインデックスを利用します。プログラムが開始したばかりの時に検索すると、不完全な結果になることがあります。",
|
||||
"noResults": "インデックス検索で結果が見つかりませんでした。",
|
||||
"onlyFolders": "フォルダのみ",
|
||||
"onlyFiles": "ファイルのみ",
|
||||
"showOptions": "オプションの表示",
|
||||
"photos": "写真",
|
||||
"audio": "オーディオ",
|
||||
"videos": "ビデオ",
|
||||
"documents": "資料",
|
||||
"archives": "アーカイブス",
|
||||
"number": "番号",
|
||||
"searchContext": "検索コンテキスト:{context}",
|
||||
"notEnoughCharacters": "検索する文字数が足りない (最小値{minSearchLength})",
|
||||
"typeToSearch": "検索を開始するには、{minSearchLength} 以上の文字を入力します。"
|
||||
"helpText2": "インデックス:検索は、設定された間隔(デフォルト:5分)で自動的に更新されるインデックスを利用します。プログラムが開始したばかりの時に検索すると、不完全な結果になることがあります。"
|
||||
},
|
||||
"settings": {
|
||||
"admin": "管理者",
|
||||
@@ -227,8 +214,10 @@
|
||||
"avoidChanges": "(変更を避けるために空白にしてください)",
|
||||
"branding": "Branding",
|
||||
"brandingDirectoryPath": "Branding directory path",
|
||||
"brandingHelp": "You can customize how your File Browser instance looks and feels by changing its name, replacing the logo, adding custom styles and even disable external links to GitHub.\nFor more information about custom branding, please check out the {0}.",
|
||||
"changePassword": "パスワードを変更",
|
||||
"commandRunner": "Command runner",
|
||||
"commandRunnerHelp": "Here you can set commands that are executed in the named events. You must write one per line. The environment variables {0} and {1} will be available, being {0} relative to {1}. For more information about this feature and the available environment variables, please read the {2}.",
|
||||
"commandsUpdated": "コマンドは更新されました!",
|
||||
"createUserDir": "Auto create user home dir while adding new user",
|
||||
"customStylesheet": "カスタムスタイルシ ート",
|
||||
@@ -311,14 +300,7 @@
|
||||
"adminOptions": "管理者ユーザーオプション",
|
||||
"shareDuration": "期限切れ",
|
||||
"searchOptions": "検索オプション",
|
||||
"downloads": "ダウンロード",
|
||||
"systemAdmin": "システム&管理",
|
||||
"configViewer": "コンフィグビューア",
|
||||
"configViewerShowFull": "フルコンフィグを表示",
|
||||
"configViewerShowComments": "コメントを表示",
|
||||
"configViewerLoadConfig": "ロードコンフィグ",
|
||||
"brandingHelp": "ファイルブラウザのインスタンスの見た目や使い勝手は、名前の変更、ロゴの置き換え、カスタムスタイルの追加、GitHub への外部リンクの無効化などでカスタマイズできます。\nカスタムブランディングの詳細については、{0} をご覧ください。",
|
||||
"commandRunnerHelp": "ここでは、指定したイベントで実行されるコマンドを設定することができる。1行に1つ書く必要がある。環境変数{0} と{1} が使用可能になり、{0} は{1} の相対変数になります。この機能と使用可能な環境変数の詳細については、{2} をお読みください。"
|
||||
"downloads": "ダウンロード"
|
||||
},
|
||||
"sidebar": {
|
||||
"help": "ヘルプ",
|
||||
@@ -376,21 +358,12 @@
|
||||
"hideNavButtons": "ナビゲーションボタンを隠す",
|
||||
"hideNavButtonsDescription": "シェア内のナビバーにあるナビゲーションボタンを非表示にして、ミニマルな外観を作る。",
|
||||
"viewModeDescription": "共有ページのデフォルトレイアウト。",
|
||||
"titleDefault": "Shared files - {title}",
|
||||
"descriptionDefault": "シェアが送信されましたので、閲覧またはダウンロードしてください。",
|
||||
"disableShareCard": "シェアカードを無効にする",
|
||||
"disableShareCardDescription": "サイドバーの共有ページ、またはモバイルのページ上部にある共有カードを無効にする。",
|
||||
"disableSidebar": "サイドバーを無効にする",
|
||||
"disableSidebarDescription": "共有ページのサイドバーを無効にする。",
|
||||
"enforceDarkLightMode": "テーマモードの強制",
|
||||
"enforceDarkLightModeDescription": "この共有に特定のテーマモード(ダークまたはライト)を強制します。",
|
||||
"default": "モードを強制しない",
|
||||
"dark": "暗い",
|
||||
"light": "ライト",
|
||||
"enableOnlyOffice": "OnlyOfficeビューアを有効にする",
|
||||
"enableOnlyOfficeDescription": "この共有でOnlyOfficeを使用したオフィスファイルの閲覧を許可する。",
|
||||
"enableOnlyOfficeEditing": "OnlyOffice編集を有効にする",
|
||||
"enableOnlyOfficeEditingDescription": "この共有でOnlyOfficeを使ったオフィスファイルの編集を許可する。",
|
||||
"titleDefault": "共有ファイル{title}"
|
||||
"disableSidebarDescription": "共有ページのサイドバーを無効にする。"
|
||||
},
|
||||
"api": {
|
||||
"title": "APIキー:",
|
||||
@@ -474,8 +447,8 @@
|
||||
"totalDenied": "却下",
|
||||
"totalAllowed": "可",
|
||||
"all": "すべて拒否",
|
||||
"defaultBehaviorDescription": "これは、特定のアクセスルールが存在しないパスにユーザがアクセスしようとしたときに何が起こるかを示している。Allow」はユーザーがデフォルトでアクセスできることを意味し、「Deny」は明示的に許可されない限り、ユーザーがデフォルトでブロックされることを意味する。",
|
||||
"defaultBehavior": "デフォルト・アクション{suffix}"
|
||||
"defaultBehavior": "Default action{suffix}",
|
||||
"defaultBehaviorDescription": "これは、特定のアクセスルールが存在しないパスにユーザがアクセスしようとしたときに何が起こるかを示している。Allow」はユーザーがデフォルトでアクセスできることを意味し、「Deny」は明示的に許可されない限り、ユーザーがデフォルトでブロックされることを意味する。"
|
||||
},
|
||||
"fileLoading": {
|
||||
"title": "アップロードとダウンロード",
|
||||
@@ -541,24 +514,14 @@
|
||||
"defaultThemeDescription": "default - デフォルトのテーマ",
|
||||
"customTheme": "テーマを選ぶ",
|
||||
"showSelectMultiple": "デスクトップのコンテキストメニューに複数選択を表示する",
|
||||
"showSelectMultipleDescription": "デフォルトでは、デスクトップのコンテキストメニューには、モバイルのような「複数選択」オプションはありません。このオプションは常にコンテキストメニューに表示されます。",
|
||||
"defaultMediaPlayer": "ブラウザのネイティブ・メディア・プレーヤーを使う",
|
||||
"defaultMediaPlayerDescription": "ビデオやオーディオファイルには、FileBrowserに含まれる「vue-plyr」メディアプレーヤーではなく、ブラウザのネイティブメディアプレーヤーを使用してください。",
|
||||
"debugOfficeEditor": "OnlyOfficeデバッグモードを有効にする",
|
||||
"debugOfficeEditorDescription": "OnlyOfficeエディタに追加情報のデバッグポップアップを表示します。",
|
||||
"fileViewerOptions": "ファイル・ビューアーのオプション",
|
||||
"themeAndLanguage": "テーマと言語"
|
||||
"showSelectMultipleDescription": "デフォルトでは、デスクトップのコンテキストメニューには、モバイルのような「複数選択」オプションはありません。このオプションは常にコンテキストメニューに表示されます。"
|
||||
},
|
||||
"editor": {
|
||||
"uninitialized": "エディタの初期化に失敗しました。再読み込みしてください。",
|
||||
"saveFailed": "ファイルの保存に失敗しました。もう一度お試しください。",
|
||||
"saveAborted": "保存が中止された。ファイル名がアクティブなファイルと一致しません。",
|
||||
"saveDisabled": "共有ファイルの保存はサポートされていません。",
|
||||
"saveAbortedMessage": "保存操作が中断されました。アプリケーションのアクティブファイル({activeFile} )と保存しようとしているファイル({tryingToSave} )が一致しません。",
|
||||
"syncFailed": "{filename}\" のルートとステートの同期に失敗しました。データの破損を防ぐためにエディタのセットアップを中止しています。"
|
||||
},
|
||||
"player": {
|
||||
"LoopEnabled": "ループ有効",
|
||||
"LoopDisabled": "ループ無効"
|
||||
"saveAbortedMessage": "Save operation aborted. The application's active file ({activeFile}) does not match the file you are trying to save ({tryingToSave}).",
|
||||
"syncFailed": "failed to sync state with the route for \"{filename}\" after 5 attempts. Aborting editor setup to prevent data corruption."
|
||||
}
|
||||
}
|
||||
+30
-67
@@ -49,8 +49,7 @@
|
||||
"clearCompleted": "완료 지우기",
|
||||
"showMore": "자세히 보기",
|
||||
"showLess": "덜 보기",
|
||||
"openParentFolder": "상위 폴더 열기",
|
||||
"selectedCount": "작업을 수행하기 위해 선택한 항목 수"
|
||||
"openParentFolder": "상위 폴더 열기"
|
||||
},
|
||||
"download": {
|
||||
"downloadFile": "파일 다운로드",
|
||||
@@ -85,15 +84,15 @@
|
||||
"click": "파일이나 디렉토리를 선택해주세요.",
|
||||
"ctrl": {
|
||||
"click": "여러 개의 파일이나 디렉토리를 선택해주세요.",
|
||||
"d": "선택한 항목 다운로드"
|
||||
"f": "검색창 열기",
|
||||
"s": "파일 또는 디렉토리 다운로드"
|
||||
},
|
||||
"del": "선택된 파일 삭제",
|
||||
"doubleClick": "파일 또는 디렉토리 열기",
|
||||
"esc": "선택 취소/프롬프트 닫기",
|
||||
"f1": "정보",
|
||||
"f2": "파일 이름 변경",
|
||||
"help": "도움말",
|
||||
"f1": "도움말 프롬프트 표시",
|
||||
"description": "아래에서 기본 탐색 옵션을 확인할 수 있습니다. 자세한 내용은 다음을 참조하세요.",
|
||||
"wiki": "파일브라우저 퀀텀 위키"
|
||||
"help": "도움말"
|
||||
},
|
||||
"languages": {
|
||||
"he": "עברית",
|
||||
@@ -135,6 +134,7 @@
|
||||
"prompts": {
|
||||
"copy": "복사",
|
||||
"copyMessage": "복사할 디렉토리:",
|
||||
"deleteMessageMultiple": "{count} 개의 파일을 삭제하시겠습니까?",
|
||||
"deleteMessageSingle": "파일 혹은 디렉토리를 삭제하시겠습니까?",
|
||||
"deleteTitle": "파일 삭제",
|
||||
"displayName": "게시 이름:",
|
||||
@@ -142,6 +142,7 @@
|
||||
"downloadMessage": "다운로드 포맷 설정.",
|
||||
"error": "에러 발생!",
|
||||
"fileInfo": "파일 정보",
|
||||
"lastModified": "최종 수정{suffix}",
|
||||
"move": "이동",
|
||||
"moveMessage": "이동할 화일 또는 디렉토리를 선택하세요:",
|
||||
"newArchetype": "원형을 유지하는 포스트를 생성합니다. 파일은 컨텐트 폴더에 생성됩니다.",
|
||||
@@ -149,7 +150,10 @@
|
||||
"newDirMessage": "새 디렉토리 이름을 입력해주세요.",
|
||||
"newFile": "새 파일",
|
||||
"newFileMessage": "새 파일 이름을 입력해주세요.",
|
||||
"numberDirs": "디렉토리 수{suffix}",
|
||||
"numberFiles": "파일 수{suffix}",
|
||||
"rename": "이름 변경",
|
||||
"renameMessage": "새로운 이름을 입력하세요.",
|
||||
"replace": "대체하기",
|
||||
"replaceMessage": "동일한 파일 이름이 존재합니다. 현재 파일을 덮어쓸까요?\n",
|
||||
"schedule": "일정",
|
||||
@@ -157,6 +161,7 @@
|
||||
"show": "보기",
|
||||
"size": "크기",
|
||||
"upload": "업로드",
|
||||
"deleteMessageShare": "Are you sure you want to delete this share: {path} ?",
|
||||
"deleteUserMessage": "이 사용자를 삭제하시겠습니까?",
|
||||
"filesSelected": "선택한 파일",
|
||||
"optionalPassword": "선택적 비밀번호",
|
||||
@@ -167,6 +172,10 @@
|
||||
"dragAndDrop": "파일을 여기로 끌어다 놓기",
|
||||
"completed": "완료",
|
||||
"conflictsDetected": "충돌 감지",
|
||||
"uploadSettingsChunked": "Max concurrent uploads: {maxConcurrentUpload}, chunk size: {uploadChunkSizeMb} MB",
|
||||
"uploadSettingsNoChunk": "Max concurrent uploads: {maxConcurrentUpload}, chunked upload disabled",
|
||||
"invalidName": "제공된 이름이 잘못되었습니다: \"/\" 또는 \"\\\"를 포함할 수 없습니다.",
|
||||
"source": "Source{suffix}",
|
||||
"downloadsLimit": "다운로드 횟수 제한",
|
||||
"maxBandwidth": "최대 다운로드 대역폭(kbps)",
|
||||
"shareTheme": "테마 공유",
|
||||
@@ -177,44 +186,22 @@
|
||||
"shareBanner": "배너 URL 공유",
|
||||
"shareFavicon": "파비콘 URL 공유",
|
||||
"optionalPasswordHelp": "이 비밀번호를 설정하면 사용자가 공유 콘텐츠를 보려면 이 비밀번호를 입력해야 합니다.",
|
||||
"viewMode": "보기 모드",
|
||||
"renameMessage": "새 이름을 입력합니다:",
|
||||
"renameMessageInvalid": "파일은 \"/\" 또는 \"\\\"를 포함할 수 없습니다.",
|
||||
"source": "출처{suffix}",
|
||||
"deleteMessageMultiple": "{count} 파일을 정말 삭제하시겠습니까?",
|
||||
"deleteMessageShare": "이 공유를 삭제하시겠습니까? {path} ?",
|
||||
"lastModified": "마지막 수정{suffix}",
|
||||
"numberDirs": "디렉터리 수{suffix}",
|
||||
"numberFiles": "파일 수{suffix}",
|
||||
"renameMessageConflict": "\"{filename}\"라는 이름의 항목이 이미 존재합니다!",
|
||||
"uploadSettingsChunked": "최대 동시 업로드: {maxConcurrentUpload}, 청크 크기: {uploadChunkSizeMb} MB",
|
||||
"uploadSettingsNoChunk": "최대 동시 업로드: {maxConcurrentUpload}, 청크 업로드 비활성화"
|
||||
"viewMode": "보기 모드"
|
||||
},
|
||||
"search": {
|
||||
"images": "이미지",
|
||||
"music": "음악",
|
||||
"pdf": "PDF",
|
||||
"pressToSearch": "검색하려면 엔터를 입력하세요",
|
||||
"search": "검색...",
|
||||
"typeToSearch": "검색어 입력...",
|
||||
"types": "Types",
|
||||
"video": "비디오",
|
||||
"path": "경로:",
|
||||
"smallerThan": "보다 작습니다:",
|
||||
"largerThan": "보다 큽니다:",
|
||||
"helpText1": "검색은 입력하는 각 문자(검색어의 경우 최소 3자)에 대해 이루어집니다.",
|
||||
"helpText2": "색인: 검색은 설정된 간격(기본값: 5분)에 따라 자동으로 업데이트되는 색인을 활용합니다. 프로그램이 막 시작되었을 때 검색하면 불완전한 결과가 나올 수 있습니다.",
|
||||
"noResults": "색인 검색에서 결과를 찾을 수 없습니다.",
|
||||
"onlyFolders": "폴더만",
|
||||
"onlyFiles": "파일만",
|
||||
"showOptions": "옵션 표시",
|
||||
"photos": "사진",
|
||||
"audio": "오디오",
|
||||
"videos": "동영상",
|
||||
"documents": "문서",
|
||||
"archives": "아카이브",
|
||||
"number": "번호",
|
||||
"searchContext": "컨텍스트 검색: {context}",
|
||||
"notEnoughCharacters": "검색할 문자가 부족합니다(최소 {minSearchLength})",
|
||||
"typeToSearch": "검색을 시작하려면 {minSearchLength} 을 입력하세요."
|
||||
"helpText2": "색인: 검색은 설정된 간격(기본값: 5분)에 따라 자동으로 업데이트되는 색인을 활용합니다. 프로그램이 막 시작되었을 때 검색하면 불완전한 결과가 나올 수 있습니다."
|
||||
},
|
||||
"settings": {
|
||||
"admin": "관리자",
|
||||
@@ -227,8 +214,10 @@
|
||||
"avoidChanges": "(수정하지 않으면 비워두세요)",
|
||||
"branding": "브랜딩",
|
||||
"brandingDirectoryPath": "브랜드 디렉토리 경로",
|
||||
"brandingHelp": "File Browser 인스턴스는 이름, 로고, 스타일 등을 변경할 수 있습니다. 자세한 사항은 여기{0}에서 확인하세요.",
|
||||
"changePassword": "비밀번호 변경",
|
||||
"commandRunner": "명령 실행기",
|
||||
"commandRunnerHelp": "이벤트에 해당하는 명령을 설정하세요. 줄당 1개의 명령을 적으세요. 환경 변수{0} 와 {1}이 사용가능하며, {0} 은 {1}에 상대 경로 입니다. 자세한 사항은 {2} 를 참조하세요.",
|
||||
"commandsUpdated": "명령 수정됨!",
|
||||
"createUserDir": "Auto create user home dir while adding new user",
|
||||
"customStylesheet": "커스텀 스타일시트",
|
||||
@@ -311,14 +300,7 @@
|
||||
"adminOptions": "관리자 사용자 옵션",
|
||||
"shareDuration": "만료",
|
||||
"searchOptions": "검색 옵션",
|
||||
"downloads": "다운로드",
|
||||
"systemAdmin": "시스템 및 관리자",
|
||||
"configViewer": "구성 뷰어",
|
||||
"configViewerShowFull": "전체 구성 표시",
|
||||
"configViewerShowComments": "댓글 표시",
|
||||
"configViewerLoadConfig": "로드 구성",
|
||||
"brandingHelp": "파일 브라우저 인스턴스의 이름을 변경하고, 로고를 바꾸고, 사용자 지정 스타일을 추가하고, GitHub에 대한 외부 링크를 비활성화하여 모양과 느낌을 사용자 지정할 수 있습니다.\n사용자 지정 브랜딩에 대한 자세한 내용은 {0} 을 참조하세요.",
|
||||
"commandRunnerHelp": "여기에서 명명된 이벤트에서 실행되는 명령을 설정할 수 있습니다. 한 줄에 하나씩 작성해야 합니다. 환경 변수 {0} 및 {1} 을 사용할 수 있으며, {0} 은 {1} 에 상대적입니다. 이 기능 및 사용 가능한 환경 변수에 대한 자세한 내용은 {2} 을 참조하세요."
|
||||
"downloads": "다운로드"
|
||||
},
|
||||
"sidebar": {
|
||||
"help": "도움말",
|
||||
@@ -376,21 +358,12 @@
|
||||
"hideNavButtons": "탐색 버튼 숨기기",
|
||||
"hideNavButtonsDescription": "공유의 탐색 모음에서 탐색 버튼을 숨겨 미니멀한 디자인을 만듭니다.",
|
||||
"viewModeDescription": "공유 페이지의 기본 레이아웃입니다.",
|
||||
"titleDefault": "Shared files - {title}",
|
||||
"descriptionDefault": "보거나 다운로드할 수 있는 공유물이 전송되었습니다.",
|
||||
"disableShareCard": "공유 카드 비활성화",
|
||||
"disableShareCardDescription": "사이드바의 공유 페이지 또는 모바일의 페이지 상단에서 공유 카드를 비활성화합니다.",
|
||||
"disableSidebar": "사이드바 비활성화",
|
||||
"disableSidebarDescription": "공유 페이지에서 사이드바를 비활성화합니다.",
|
||||
"enforceDarkLightMode": "테마 모드 적용",
|
||||
"enforceDarkLightModeDescription": "이 공유에 특정 테마 모드(어둡거나 밝음)를 강제로 적용하여 사용자 기본 설정을 재정의합니다.",
|
||||
"default": "모드 강제 적용 안 함",
|
||||
"dark": "Dark",
|
||||
"light": "빛",
|
||||
"enableOnlyOffice": "Office 뷰어만 사용",
|
||||
"enableOnlyOfficeDescription": "이 공유에서 OnlyOffice를 사용하여 Office 파일을 볼 수 있도록 허용합니다.",
|
||||
"enableOnlyOfficeEditing": "Office 편집만 사용",
|
||||
"enableOnlyOfficeEditingDescription": "이 공유에서 OnlyOffice를 사용하여 Office 파일을 편집할 수 있도록 허용합니다.",
|
||||
"titleDefault": "공유 파일 - {title}"
|
||||
"disableSidebarDescription": "공유 페이지에서 사이드바를 비활성화합니다."
|
||||
},
|
||||
"api": {
|
||||
"title": "API 키:",
|
||||
@@ -474,8 +447,8 @@
|
||||
"totalDenied": "거부됨",
|
||||
"totalAllowed": "허용됨",
|
||||
"all": "모두 거부",
|
||||
"defaultBehaviorDescription": "사용자가 특정 액세스 규칙이 없는 경로에 액세스하려고 할 때 어떤 일이 발생하는지 보여줍니다. '허용'은 사용자가 기본적으로 액세스할 수 있음을 의미하며, '거부'는 명시적으로 허용되지 않는 한 기본적으로 차단됨을 의미합니다.",
|
||||
"defaultBehavior": "기본 작업{suffix}"
|
||||
"defaultBehavior": "Default action{suffix}",
|
||||
"defaultBehaviorDescription": "사용자가 특정 액세스 규칙이 없는 경로에 액세스하려고 할 때 어떤 일이 발생하는지 보여줍니다. '허용'은 사용자가 기본적으로 액세스할 수 있음을 의미하며, '거부'는 명시적으로 허용되지 않는 한 기본적으로 차단됨을 의미합니다."
|
||||
},
|
||||
"fileLoading": {
|
||||
"title": "업로드 및 다운로드",
|
||||
@@ -541,24 +514,14 @@
|
||||
"defaultThemeDescription": "default - 기본 테마",
|
||||
"customTheme": "테마 선택",
|
||||
"showSelectMultiple": "데스크톱의 컨텍스트 메뉴에서 여러 개 선택 표시",
|
||||
"showSelectMultipleDescription": "기본적으로 데스크톱 컨텍스트 메뉴에는 모바일처럼 '여러 개 선택' 옵션이 없습니다. 이 옵션은 항상 컨텍스트 메뉴에 표시됩니다.",
|
||||
"defaultMediaPlayer": "브라우저의 기본 미디어 플레이어 사용",
|
||||
"defaultMediaPlayerDescription": "비디오 및 오디오 파일은 FileBrowser에 포함된 'vue-plyr' 미디어 플레이어가 아닌 브라우저의 기본 미디어 플레이어를 사용하세요.",
|
||||
"debugOfficeEditor": "Office 디버그 모드만 사용",
|
||||
"debugOfficeEditorDescription": "OnlyOffice 편집기에서 추가 정보가 포함된 디버그 팝업을 표시합니다.",
|
||||
"fileViewerOptions": "파일 뷰어 옵션",
|
||||
"themeAndLanguage": "테마 및 언어"
|
||||
"showSelectMultipleDescription": "기본적으로 데스크톱 컨텍스트 메뉴에는 모바일처럼 '여러 개 선택' 옵션이 없습니다. 이 옵션은 항상 컨텍스트 메뉴에 표시됩니다."
|
||||
},
|
||||
"editor": {
|
||||
"uninitialized": "편집기를 초기화하지 못했습니다. 다시 로드하세요.",
|
||||
"saveFailed": "파일을 저장하지 못했습니다. 다시 시도하세요.",
|
||||
"saveAborted": "저장이 중단되었습니다. 파일 이름이 활성 파일과 일치하지 않습니다.",
|
||||
"saveDisabled": "공유 파일 저장은 지원되지 않습니다.",
|
||||
"saveAbortedMessage": "저장 작업이 중단되었습니다. 애플리케이션의 활성 파일({activeFile})이 저장하려는 파일({tryingToSave})과 일치하지 않습니다.",
|
||||
"syncFailed": "5번 시도 후 \"{filename}\"의 경로와 상태를 동기화하지 못했습니다. 데이터 손상을 방지하기 위해 편집기 설정을 중단합니다."
|
||||
},
|
||||
"player": {
|
||||
"LoopEnabled": "루프 사용",
|
||||
"LoopDisabled": "루프 비활성화"
|
||||
"saveAbortedMessage": "Save operation aborted. The application's active file ({activeFile}) does not match the file you are trying to save ({tryingToSave}).",
|
||||
"syncFailed": "failed to sync state with the route for \"{filename}\" after 5 attempts. Aborting editor setup to prevent data corruption."
|
||||
}
|
||||
}
|
||||
@@ -49,8 +49,7 @@
|
||||
"clearCompleted": "Gereed",
|
||||
"showMore": "Meer tonen",
|
||||
"showLess": "Minder tonen",
|
||||
"openParentFolder": "Bovenliggende map openen",
|
||||
"selectedCount": "Aantal items geselecteerd om een actie uit te voeren"
|
||||
"openParentFolder": "Bovenliggende map openen"
|
||||
},
|
||||
"download": {
|
||||
"downloadFile": "Bestand downloaden",
|
||||
@@ -85,15 +84,15 @@
|
||||
"click": "selecteer bestand of map",
|
||||
"ctrl": {
|
||||
"click": "selecteer meerdere bestanden of mappen",
|
||||
"d": "geselecteerde items downloaden"
|
||||
"f": "opent zoeken",
|
||||
"s": "sla een bestand op of download de map waar u bent"
|
||||
},
|
||||
"del": "verwijder geselecteerde items",
|
||||
"doubleClick": "open een bestand of map",
|
||||
"esc": "Wis selectie en/of sluit de prompt",
|
||||
"f1": "deze informatie",
|
||||
"f2": "bestand herbenoemen",
|
||||
"help": "Help",
|
||||
"f1": "help prompt tonen",
|
||||
"description": "Je kunt de basisnavigatieopties hieronder bekijken. Ga voor aanvullende informatie naar",
|
||||
"wiki": "FileBrowser Quantum Wiki"
|
||||
"help": "Help"
|
||||
},
|
||||
"languages": {
|
||||
"he": "עברית",
|
||||
@@ -135,6 +134,7 @@
|
||||
"prompts": {
|
||||
"copy": "Kopiëren",
|
||||
"copyMessage": "Kies een locatie om uw bestanden te kopiëren: ",
|
||||
"deleteMessageMultiple": "Weet u zeker dat u {count} bestand(en) wil verwijderen?",
|
||||
"deleteMessageSingle": "Weet u zeker dat u dit bestand/map wil verwijderen?",
|
||||
"deleteTitle": "Bestanden verwijderen",
|
||||
"displayName": "Weergavenaam: ",
|
||||
@@ -142,6 +142,7 @@
|
||||
"downloadMessage": "Kies het vermat dat u wilt downloaden. ",
|
||||
"error": "Er ging iets fout",
|
||||
"fileInfo": "Bestandinformatie",
|
||||
"lastModified": "Laatst Bewerkt{suffix}",
|
||||
"move": "Verplaatsen",
|
||||
"moveMessage": "Kies een nieuwe locatie voor je bestand(en)/map(pen)",
|
||||
"newArchetype": "Maak een nieuw bericht op basis van een archetype. Uw bestand wordt aangemaakt in de inhoudsmap.",
|
||||
@@ -149,7 +150,10 @@
|
||||
"newDirMessage": "Schrijf de naam van de nieuwe map",
|
||||
"newFile": "Nieuw bestand",
|
||||
"newFileMessage": "Schrijf de naam van het nieuwe bestand",
|
||||
"numberDirs": "Aantal mappen{suffix}",
|
||||
"numberFiles": "Aantal bestanden{suffix}",
|
||||
"rename": "Hernoemen",
|
||||
"renameMessage": "Voer een nieuwe naam in voor",
|
||||
"replace": "Vervangen",
|
||||
"replaceMessage": "Een van de bestanden die u probeert te uploaden, geeft conflicten i.v.m. de naamgeving. Wilt u de bestaande bestanden vervangen?\n",
|
||||
"schedule": "Plannen",
|
||||
@@ -157,6 +161,7 @@
|
||||
"show": "Tonen",
|
||||
"size": "Grootte",
|
||||
"upload": "Uploaden",
|
||||
"deleteMessageShare": "Are you sure you want to delete this share: {path} ?",
|
||||
"deleteUserMessage": "Weet je zeker dat je deze gebruiker wilt verwijderen?",
|
||||
"selected": "geselecteerd",
|
||||
"filesSelected": "geselecteerde bestanden",
|
||||
@@ -167,6 +172,10 @@
|
||||
"dragAndDrop": "Sleep bestanden hier naartoe",
|
||||
"completed": "Voltooid",
|
||||
"conflictsDetected": "Gedetecteerde conflicten",
|
||||
"uploadSettingsChunked": "Max concurrent uploads: {maxConcurrentUpload}, chunk size: {uploadChunkSizeMb} MB",
|
||||
"uploadSettingsNoChunk": "Max concurrent uploads: {maxConcurrentUpload}, chunked upload disabled",
|
||||
"invalidName": "Ongeldige naam opgegeven: kan geen \"/\" of \"˜\" bevatten.",
|
||||
"source": "Source{suffix}",
|
||||
"downloadsLimit": "Limiet aantal downloads",
|
||||
"maxBandwidth": "Max. downloadbandbreedte (kbps)",
|
||||
"shareTheme": "Thema delen",
|
||||
@@ -177,44 +186,22 @@
|
||||
"shareBanner": "URL banner delen",
|
||||
"shareFavicon": "favicon URL delen",
|
||||
"optionalPasswordHelp": "Indien ingesteld, moeten gebruikers dit wachtwoord invoeren om de gedeelde inhoud te bekijken.",
|
||||
"viewMode": "Weergavemodus",
|
||||
"renameMessage": "Voer een nieuwe naam in:",
|
||||
"renameMessageInvalid": "Een bestand kan geen \"/\" of \"\\\" bevatten.",
|
||||
"source": "Bron{suffix}",
|
||||
"deleteMessageMultiple": "Weet je zeker dat je {count} bestand(en) wilt verwijderen?",
|
||||
"deleteMessageShare": "Weet je zeker dat je deze share wilt verwijderen: {path} ?",
|
||||
"lastModified": "Laatst gewijzigd{suffix}",
|
||||
"numberDirs": "Aantal mappen{suffix}",
|
||||
"numberFiles": "Aantal bestanden{suffix}",
|
||||
"renameMessageConflict": "Een item met de naam \"{filename}\" bestaat al!",
|
||||
"uploadSettingsChunked": "Max gelijktijdige uploads: {maxConcurrentUpload}, brokgrootte: {uploadChunkSizeMb} MB",
|
||||
"uploadSettingsNoChunk": "Max gelijktijdige uploads: {maxConcurrentUpload}, chunked upload uitgeschakeld"
|
||||
"viewMode": "Weergavemodus"
|
||||
},
|
||||
"search": {
|
||||
"images": "Afbeeldingen",
|
||||
"music": "Muziek",
|
||||
"pdf": "PDF",
|
||||
"pressToSearch": "Druk op enter om te zoeken...",
|
||||
"search": "Zoeken...",
|
||||
"typeToSearch": "Typ om te zoeken...",
|
||||
"types": "Types",
|
||||
"video": "Video",
|
||||
"path": "Pad:",
|
||||
"smallerThan": "Kleiner dan:",
|
||||
"largerThan": "Groter dan:",
|
||||
"helpText1": "Er wordt gezocht op elk teken dat je typt (minimaal 3 tekens voor zoektermen).",
|
||||
"helpText2": "De index: Zoeken maakt gebruik van de index die automatisch wordt bijgewerkt op het ingestelde interval (standaard: 5 minuten). Zoeken als het programma net is gestart, kan onvolledige resultaten opleveren.",
|
||||
"noResults": "Geen resultaten gevonden in geïndexeerde zoekopdrachten.",
|
||||
"onlyFolders": "Alleen mappen",
|
||||
"onlyFiles": "Alleen bestanden",
|
||||
"showOptions": "Opties weergeven",
|
||||
"photos": "Foto's",
|
||||
"audio": "Audio",
|
||||
"videos": "Video's",
|
||||
"documents": "Documenten",
|
||||
"archives": "Archief",
|
||||
"number": "Aantal",
|
||||
"searchContext": "Zoek context: {context}",
|
||||
"notEnoughCharacters": "Niet genoeg tekens om te zoeken (min {minSearchLength})",
|
||||
"typeToSearch": "Begin met het typen van {minSearchLength} of meer tekens om te beginnen met zoeken."
|
||||
"helpText2": "De index: Zoeken maakt gebruik van de index die automatisch wordt bijgewerkt op het ingestelde interval (standaard: 5 minuten). Zoeken als het programma net is gestart, kan onvolledige resultaten opleveren."
|
||||
},
|
||||
"settings": {
|
||||
"admin": "Admin",
|
||||
@@ -227,8 +214,10 @@
|
||||
"avoidChanges": "(laat leeg om wijzigingen te voorkomen)",
|
||||
"branding": "Branding",
|
||||
"brandingDirectoryPath": "Branding directory path",
|
||||
"brandingHelp": "U kunt de looks en feels hoe File Browser wordt getoond wijzigen door de naam aan te passen, het logo te vervangen, een aangepast stylesheet toe te voegen en/of de externe links naar GitHub uit te schakelen.\nVoor meer informatie over custom branding, bekijk {0}.",
|
||||
"changePassword": "Wijzig Wachtwoord",
|
||||
"commandRunner": "Command runner",
|
||||
"commandRunnerHelp": "Hier kunt u opdrachten instellen die worden uitgevoerd in de benoemde gebeurtenissen. U moet er één per regel schrijven. De omgevingsvariabelen {0} en {1} zijn beschikbaar, zijnde {0} relatief ten opzichte van {1}. Raadpleeg {2} voor meer informatie over deze functie en de beschikbare omgevingsvariabelen.",
|
||||
"commandsUpdated": "Commando's bijgewerkt!",
|
||||
"createUserDir": "Maak automatisch een thuismap aan wanneer een nieuwe gebruiker wordt aangemaakt",
|
||||
"customStylesheet": "Aangepast Stylesheet",
|
||||
@@ -311,14 +300,7 @@
|
||||
"adminOptions": "Opties voor beheerders",
|
||||
"shareDuration": "Verloopt op",
|
||||
"searchOptions": "Zoekopties",
|
||||
"downloads": "Downloads",
|
||||
"systemAdmin": "Systeem & Beheer",
|
||||
"configViewer": "Configuratiescherm",
|
||||
"configViewerShowFull": "Volledige configuratie weergeven",
|
||||
"configViewerShowComments": "Opmerkingen tonen",
|
||||
"configViewerLoadConfig": "Configuratie laden",
|
||||
"brandingHelp": "Je kunt aanpassen hoe je File Browser instance eruit ziet en aanvoelt door de naam te veranderen, het logo te vervangen, aangepaste stijlen toe te voegen en zelfs externe links naar GitHub uit te schakelen.\nVoor meer informatie over het aanpassen van je branding, kun je kijken op {0}.",
|
||||
"commandRunnerHelp": "Hier kun je commando's instellen die worden uitgevoerd in de genoemde gebeurtenissen. Je moet er één per regel schrijven. De omgevingsvariabelen {0} en {1} zullen beschikbaar zijn, waarbij {0} relatief is ten opzichte van {1}. Lees voor meer informatie over deze functie en de beschikbare omgevingsvariabelen de {2}."
|
||||
"downloads": "Downloads"
|
||||
},
|
||||
"sidebar": {
|
||||
"help": "Help",
|
||||
@@ -376,21 +358,12 @@
|
||||
"hideNavButtons": "Navigatieknoppen verbergen",
|
||||
"hideNavButtonsDescription": "Verberg de navigatieknoppen op de navigatiebalk in de share om een minimalistische look te creëren.",
|
||||
"viewModeDescription": "Standaard lay-out voor de gedeelde pagina.",
|
||||
"titleDefault": "Shared files - {title}",
|
||||
"descriptionDefault": "Er is een share naar je verzonden om te bekijken of te downloaden.",
|
||||
"disableShareCard": "Aandelenkaart uitschakelen",
|
||||
"disableShareCardDescription": "Schakel de deelkaart uit op de gedeelde pagina in de zijbalk of bovenaan de pagina op mobiel.",
|
||||
"disableSidebar": "Zijbalk uitschakelen",
|
||||
"disableSidebarDescription": "Schakel de zijbalk op de gedeelde pagina uit.",
|
||||
"enforceDarkLightMode": "Themamodus afdwingen",
|
||||
"enforceDarkLightModeDescription": "Forceer een specifieke themamodus (donker of licht) voor deze share, waarbij gebruikersvoorkeuren worden overschreven.",
|
||||
"default": "Een modus niet afdwingen",
|
||||
"dark": "Donker",
|
||||
"light": "Licht",
|
||||
"enableOnlyOffice": "OnlyOffice viewer inschakelen",
|
||||
"enableOnlyOfficeDescription": "Sta het bekijken van Office-bestanden toe met OnlyOffice in deze share.",
|
||||
"enableOnlyOfficeEditing": "Bewerken met OnlyOffice inschakelen",
|
||||
"enableOnlyOfficeEditingDescription": "Laat het bewerken van Office-bestanden met OnlyOffice toe in deze share.",
|
||||
"titleDefault": "Gedeelde bestanden - {title}"
|
||||
"disableSidebarDescription": "Schakel de zijbalk op de gedeelde pagina uit."
|
||||
},
|
||||
"api": {
|
||||
"title": "API-sleutels",
|
||||
@@ -505,13 +478,7 @@
|
||||
"defaultThemeDescription": "standaard - Het standaardthema",
|
||||
"customTheme": "Kies je thema",
|
||||
"showSelectMultiple": "Meerdere selecteren tonen in contextmenu op bureaublad",
|
||||
"showSelectMultipleDescription": "Standaard heeft het contextmenu op de desktop geen optie \"meerdere selecteren\" zoals mobiel. Deze optie wordt altijd weergegeven in het contextmenu.",
|
||||
"defaultMediaPlayer": "Gebruik de ingebouwde mediaspeler van uw browser",
|
||||
"defaultMediaPlayerDescription": "Gebruik de eigen mediaspeler van uw browser voor video- en audiobestanden in plaats van de meegeleverde 'vue-plyr' mediaspeler van FileBrowser.",
|
||||
"debugOfficeEditor": "Debugmodus OnlyOffice inschakelen",
|
||||
"debugOfficeEditorDescription": "Een foutopsporingspop-up weergeven met aanvullende informatie in de OnlyOffice-editor.",
|
||||
"fileViewerOptions": "Opties voor bestandsviewer",
|
||||
"themeAndLanguage": "Thema & Taal"
|
||||
"showSelectMultipleDescription": "Standaard heeft het contextmenu op de desktop geen optie \"meerdere selecteren\" zoals mobiel. Deze optie wordt altijd weergegeven in het contextmenu."
|
||||
},
|
||||
"general": {
|
||||
"name": "Naam",
|
||||
@@ -546,19 +513,15 @@
|
||||
"name": "Naam",
|
||||
"accessManagement": "Toegangsbeheer",
|
||||
"all": "Alles weigeren",
|
||||
"defaultBehaviorDescription": "Dit laat zien wat er gebeurt als een gebruiker toegang probeert te krijgen tot een pad waarvoor geen specifieke toegangsregels bestaan. Toestaan' betekent dat gebruikers standaard toegang hebben, 'Weigeren' betekent dat gebruikers standaard worden geblokkeerd tenzij ze expliciet toestemming krijgen.",
|
||||
"defaultBehavior": "Standaardactie{suffix}"
|
||||
"defaultBehavior": "Default action{suffix}",
|
||||
"defaultBehaviorDescription": "Dit laat zien wat er gebeurt als een gebruiker toegang probeert te krijgen tot een pad waarvoor geen specifieke toegangsregels bestaan. Toestaan' betekent dat gebruikers standaard toegang hebben, 'Weigeren' betekent dat gebruikers standaard worden geblokkeerd tenzij ze expliciet toestemming krijgen."
|
||||
},
|
||||
"editor": {
|
||||
"uninitialized": "Het is niet gelukt de editor te initialiseren. Gelieve opnieuw te laden.",
|
||||
"saveFailed": "Het is niet gelukt om het bestand op te slaan. Probeer het opnieuw.",
|
||||
"saveAborted": "Opslaan afgebroken. De bestandsnaam komt niet overeen met het actieve bestand.",
|
||||
"saveDisabled": "Het opslaan van gedeelde bestanden wordt niet ondersteund.",
|
||||
"saveAbortedMessage": "Opslaan afgebroken. Het actieve bestand van de toepassing ({activeFile}) komt niet overeen met het bestand dat u probeert op te slaan ({tryingToSave}).",
|
||||
"syncFailed": "Na 5 pogingen is het niet gelukt om de status te synchroniseren met de route voor \"{filename}\". Installatie van editor afgebroken om gegevensbeschadiging te voorkomen."
|
||||
},
|
||||
"player": {
|
||||
"LoopEnabled": "Lus ingeschakeld",
|
||||
"LoopDisabled": "Lus uitgeschakeld"
|
||||
"saveAbortedMessage": "Save operation aborted. The application's active file ({activeFile}) does not match the file you are trying to save ({tryingToSave}).",
|
||||
"syncFailed": "failed to sync state with the route for \"{filename}\" after 5 attempts. Aborting editor setup to prevent data corruption."
|
||||
}
|
||||
}
|
||||
+30
-67
@@ -49,8 +49,7 @@
|
||||
"clearCompleted": "Czyszczenie zakończone",
|
||||
"showMore": "Pokaż więcej",
|
||||
"showLess": "Pokaż mniej",
|
||||
"openParentFolder": "Otwórz folder nadrzędny",
|
||||
"selectedCount": "Liczba elementów wybranych do wykonania akcji"
|
||||
"openParentFolder": "Otwórz folder nadrzędny"
|
||||
},
|
||||
"download": {
|
||||
"downloadFile": "Pobierz plik",
|
||||
@@ -85,15 +84,15 @@
|
||||
"click": "wybierz plik lub foler",
|
||||
"ctrl": {
|
||||
"click": "wybierz wiele plików lub folderów",
|
||||
"d": "Pobierz wybrane elementy"
|
||||
"f": "otwórz wyszukiwarkę",
|
||||
"s": "pobierz aktywny plik lub folder"
|
||||
},
|
||||
"del": "usuń zaznaczone",
|
||||
"doubleClick": "otwórz plik lub folder",
|
||||
"esc": "wyczyść zaznaczenie i/lub zamknij okno z powiadomieniem",
|
||||
"f1": "ta informacja",
|
||||
"f2": "zmień nazwę pliku",
|
||||
"help": "Pomoc",
|
||||
"f1": "pokaż monit pomocy",
|
||||
"description": "Poniżej znajdują się podstawowe opcje nawigacji. Dodatkowe informacje można znaleźć na stronie",
|
||||
"wiki": "FileBrowser Quantum Wiki"
|
||||
"help": "Pomoc"
|
||||
},
|
||||
"languages": {
|
||||
"he": "עברית",
|
||||
@@ -135,6 +134,7 @@
|
||||
"prompts": {
|
||||
"copy": "Kopiuj",
|
||||
"copyMessage": "Wybierz lokalizację do której mają być skopiowane wybrane pliki",
|
||||
"deleteMessageMultiple": "Czy jesteś pewien że chcesz usunąć {count} plik(ów)?",
|
||||
"deleteMessageSingle": "Czy jesteś pewien, że chcesz usunąć ten plik/folder?",
|
||||
"deleteTitle": "Usuń pliki",
|
||||
"displayName": "Wyświetlana Nazwa:",
|
||||
@@ -142,6 +142,7 @@
|
||||
"downloadMessage": "Wybierz format, jaki chesz pobrać.",
|
||||
"error": "Pojawił się nieznany błąd",
|
||||
"fileInfo": "Informacje o pliku",
|
||||
"lastModified": "Ostatnio Zmodyfikowane{suffix}",
|
||||
"move": "Przenieś",
|
||||
"moveMessage": "Wybierz nową lokalizację dla swoich plik(ów)/folder(ów):",
|
||||
"newArchetype": "Utwórz nowy wpis na bazie wybranego wzorca. Twój plik będzie utworzony w wybranym folderze.",
|
||||
@@ -149,7 +150,10 @@
|
||||
"newDirMessage": "Podaj nazwę tworzonego folderu.",
|
||||
"newFile": "Nowy plik",
|
||||
"newFileMessage": "Podaj nazwętworzonego pliku.",
|
||||
"numberDirs": "Ilość katalogów{suffix}",
|
||||
"numberFiles": "Ilość plików{suffix}",
|
||||
"rename": "Zmień nazwę",
|
||||
"renameMessage": "Podaj nową nazwę dla",
|
||||
"replace": "Zamień",
|
||||
"replaceMessage": "Jednen z plików który próbujesz wrzucić próbje nadpisać plik o tej samej nazwie. Czy chcesz nadpisać poprzedni plik?\n",
|
||||
"schedule": "Grafi",
|
||||
@@ -157,6 +161,7 @@
|
||||
"show": "Pokaż",
|
||||
"size": "Rozmiar",
|
||||
"upload": "Prześlij",
|
||||
"deleteMessageShare": "Are you sure you want to delete this share: {path} ?",
|
||||
"deleteUserMessage": "Czy na pewno chcesz usunąć tego użytkownika?",
|
||||
"filesSelected": "wybrane pliki",
|
||||
"optionalPassword": "Hasło opcjonalne",
|
||||
@@ -167,6 +172,10 @@
|
||||
"dragAndDrop": "Przeciągnij i upuść pliki tutaj",
|
||||
"completed": "Zakończono",
|
||||
"conflictsDetected": "Wykryte konflikty",
|
||||
"uploadSettingsChunked": "Max concurrent uploads: {maxConcurrentUpload}, chunk size: {uploadChunkSizeMb} MB",
|
||||
"uploadSettingsNoChunk": "Max concurrent uploads: {maxConcurrentUpload}, chunked upload disabled",
|
||||
"invalidName": "Nieprawidłowa nazwa: nie można użyć kontenera \"/\" lub \"\\\"",
|
||||
"source": "Source{suffix}",
|
||||
"downloadsLimit": "Limit liczby pobrań",
|
||||
"maxBandwidth": "Maksymalna przepustowość pobierania (kbps)",
|
||||
"shareTheme": "Udostępnij temat",
|
||||
@@ -177,44 +186,22 @@
|
||||
"shareBanner": "Udostępnij adres URL banera",
|
||||
"shareFavicon": "Udostępnij adres URL favicon",
|
||||
"optionalPasswordHelp": "Jeśli jest ustawione, użytkownicy muszą wprowadzić to hasło, aby wyświetlić udostępnioną zawartość.",
|
||||
"viewMode": "Tryb widoku",
|
||||
"renameMessage": "Wprowadź nową nazwę:",
|
||||
"renameMessageInvalid": "Plik nie może być kontenerem \"/\" lub \"\\\"",
|
||||
"source": "Źródło{suffix}",
|
||||
"deleteMessageMultiple": "Czy na pewno chcesz usunąć plik(i) {count}?",
|
||||
"deleteMessageShare": "Czy na pewno chcesz usunąć ten udział: {path} ?",
|
||||
"lastModified": "Ostatnia modyfikacja{suffix}",
|
||||
"numberDirs": "Liczba katalogów{suffix}",
|
||||
"numberFiles": "Liczba plików{suffix}",
|
||||
"renameMessageConflict": "Element o nazwie \"{filename}\" już istnieje!",
|
||||
"uploadSettingsChunked": "Maks. jednoczesne przesyłanie: {maxConcurrentUpload}, rozmiar fragmentu: {uploadChunkSizeMb} MB",
|
||||
"uploadSettingsNoChunk": "Maksymalny jednoczesny upload: {maxConcurrentUpload}, przesyłanie fragmentaryczne wyłączone"
|
||||
"viewMode": "Tryb widoku"
|
||||
},
|
||||
"search": {
|
||||
"images": "Zdjęcia",
|
||||
"music": "Muzyka",
|
||||
"pdf": "PDF",
|
||||
"pressToSearch": "Wciśnij enter, aby wyszukać...",
|
||||
"search": "Szukaj...",
|
||||
"typeToSearch": "Zacznij pisać, aby wyszukać...",
|
||||
"types": "Typy",
|
||||
"video": "Wideo",
|
||||
"path": "Ścieżka:",
|
||||
"smallerThan": "Mniejszy niż:",
|
||||
"largerThan": "Większy niż:",
|
||||
"helpText1": "Wyszukiwanie odbywa się po każdym wpisanym znaku (minimum 3 znaki dla wyszukiwanych haseł).",
|
||||
"helpText2": "Indeks: Wyszukiwanie wykorzystuje indeks, który jest automatycznie aktualizowany w skonfigurowanych odstępach czasu (domyślnie: 5 minut). Wyszukiwanie po uruchomieniu programu może skutkować niekompletnymi wynikami.",
|
||||
"noResults": "Nie znaleziono żadnych wyników w indeksowanym wyszukiwaniu.",
|
||||
"onlyFolders": "Tylko foldery",
|
||||
"onlyFiles": "Tylko pliki",
|
||||
"showOptions": "Pokaż opcje",
|
||||
"photos": "Zdjęcia",
|
||||
"audio": "Audio",
|
||||
"videos": "Filmy",
|
||||
"documents": "Dokumenty",
|
||||
"archives": "Archiwa",
|
||||
"number": "Liczba",
|
||||
"searchContext": "Kontekst wyszukiwania: {context}",
|
||||
"notEnoughCharacters": "Za mało znaków do wyszukania (min. {minSearchLength})",
|
||||
"typeToSearch": "Zacznij wpisywać {minSearchLength} lub więcej znaków, aby rozpocząć wyszukiwanie."
|
||||
"helpText2": "Indeks: Wyszukiwanie wykorzystuje indeks, który jest automatycznie aktualizowany w skonfigurowanych odstępach czasu (domyślnie: 5 minut). Wyszukiwanie po uruchomieniu programu może skutkować niekompletnymi wynikami."
|
||||
},
|
||||
"settings": {
|
||||
"admin": "Admin",
|
||||
@@ -227,8 +214,10 @@
|
||||
"avoidChanges": "(pozostaw puste aby nie zosatało zmienione)",
|
||||
"branding": "Branding",
|
||||
"brandingDirectoryPath": "Folder brandingowy",
|
||||
"brandingHelp": "Możesz dostosować wygląd i doznania użytkownika swojej instancji File Browser poprzez zmianę jej nazwy, zmianę logo, dodanie własnych stylów, a nawet wyłączyć linki zewnętrzne do GitHuba.\nW celu pozyskania większej ilości informacji nt. osobistego brandingu, zapoznaj się z {0}.",
|
||||
"changePassword": "Zmień Hasło",
|
||||
"commandRunner": "Narzędzie do wykonywania poleceń",
|
||||
"commandRunnerHelp": "Tu możesz ustawić komendy, które będą wykonywane przy danych zdarzeniach. Musisz wpisywać po jednej na linjkę. Zmienne środowiskowe {0} i {1} będą dostępne, gdzie {0} jest względne wobec {1}. Więcej informacji o tej funkcji i dostępnych zmiennych środowiskowych znajdziesz tutaj: {2}.",
|
||||
"commandsUpdated": "Polecenie zaktualizowane!",
|
||||
"createUserDir": "Automatycznie utwórz katalog domowy użytkownika podczas dodania nowego użytkownika",
|
||||
"customStylesheet": "Własny arkusz stylów",
|
||||
@@ -311,14 +300,7 @@
|
||||
"adminOptions": "Opcje użytkownika administratora",
|
||||
"shareDuration": "Wygasa",
|
||||
"searchOptions": "Opcje wyszukiwania",
|
||||
"downloads": "Pliki do pobrania",
|
||||
"systemAdmin": "System i administrator",
|
||||
"configViewer": "Przeglądarka konfiguracji",
|
||||
"configViewerShowFull": "Pokaż pełną konfigurację",
|
||||
"configViewerShowComments": "Pokaż komentarze",
|
||||
"configViewerLoadConfig": "Konfiguracja ładowania",
|
||||
"brandingHelp": "Możesz dostosować wygląd i sposób działania swojej instancji przeglądarki plików, zmieniając jej nazwę, zastępując logo, dodając niestandardowe style, a nawet wyłączając zewnętrzne łącza do GitHub.\nWięcej informacji na temat niestandardowego brandingu można znaleźć na stronie {0}.",
|
||||
"commandRunnerHelp": "Tutaj można ustawić polecenia, które będą wykonywane w nazwanych zdarzeniach. Należy wpisać jedną komendę w wierszu. Dostępne będą zmienne środowiskowe {0} i {1}, przy czym {0} będzie relatywna do {1}. Więcej informacji na temat tej funkcji i dostępnych zmiennych środowiskowych można znaleźć na stronie {2}."
|
||||
"downloads": "Pliki do pobrania"
|
||||
},
|
||||
"sidebar": {
|
||||
"help": "Pomoc",
|
||||
@@ -376,21 +358,12 @@
|
||||
"hideNavButtons": "Ukryj przyciski nawigacyjne",
|
||||
"hideNavButtonsDescription": "Ukryj przyciski nawigacyjne na pasku nawigacyjnym w folderze udostępnionym, aby uzyskać minimalistyczny wygląd.",
|
||||
"viewModeDescription": "Domyślny układ strony udostępnionej.",
|
||||
"titleDefault": "Shared files - {title}",
|
||||
"descriptionDefault": "Udział został wysłany do wyświetlenia lub pobrania.",
|
||||
"disableShareCard": "Wyłączanie karty udostępniania",
|
||||
"disableShareCardDescription": "Wyłącz kartę udostępniania na udostępnianej stronie na pasku bocznym lub u góry strony na urządzeniach mobilnych.",
|
||||
"disableSidebar": "Wyłącz pasek boczny",
|
||||
"disableSidebarDescription": "Wyłącz pasek boczny na udostępnionej stronie.",
|
||||
"enforceDarkLightMode": "Wymuszanie trybu motywu",
|
||||
"enforceDarkLightModeDescription": "Wymusza określony tryb motywu (ciemny lub jasny) dla tego udziału, zastępując preferencje użytkownika.",
|
||||
"default": "Nie wymuszaj trybu",
|
||||
"dark": "Ciemny",
|
||||
"light": "Światło",
|
||||
"enableOnlyOffice": "Włącz przeglądarkę OnlyOffice",
|
||||
"enableOnlyOfficeDescription": "Zezwalaj na wyświetlanie plików biurowych przy użyciu OnlyOffice w tym udziale.",
|
||||
"enableOnlyOfficeEditing": "Włącz edycję OnlyOffice",
|
||||
"enableOnlyOfficeEditingDescription": "Zezwalaj na edycję plików biurowych za pomocą OnlyOffice w tym udziale.",
|
||||
"titleDefault": "Udostępnione pliki - {title}"
|
||||
"disableSidebarDescription": "Wyłącz pasek boczny na udostępnionej stronie."
|
||||
},
|
||||
"api": {
|
||||
"title": "Klucze API:",
|
||||
@@ -474,8 +447,8 @@
|
||||
"totalDenied": "Odmowa",
|
||||
"totalAllowed": "Dozwolone",
|
||||
"all": "Odmów wszystkim",
|
||||
"defaultBehaviorDescription": "To pokazuje, co się dzieje, gdy użytkownik próbuje uzyskać dostęp do ścieżki, dla której nie istnieją określone reguły dostępu. \"Zezwól\" oznacza, że użytkownicy mogą uzyskać dostęp domyślnie, \"Odmów\" oznacza, że użytkownicy są domyślnie blokowani, chyba że wyraźnie na to zezwolono.",
|
||||
"defaultBehavior": "Działanie domyślne{suffix}"
|
||||
"defaultBehavior": "Default action{suffix}",
|
||||
"defaultBehaviorDescription": "To pokazuje, co się dzieje, gdy użytkownik próbuje uzyskać dostęp do ścieżki, dla której nie istnieją określone reguły dostępu. \"Zezwól\" oznacza, że użytkownicy mogą uzyskać dostęp domyślnie, \"Odmów\" oznacza, że użytkownicy są domyślnie blokowani, chyba że wyraźnie na to zezwolono."
|
||||
},
|
||||
"fileLoading": {
|
||||
"title": "Przesyłanie i pobieranie",
|
||||
@@ -541,24 +514,14 @@
|
||||
"defaultThemeDescription": "default - domyślny motyw",
|
||||
"customTheme": "Wybierz motyw",
|
||||
"showSelectMultiple": "Wyświetlanie wyboru wielokrotnego w menu kontekstowym na pulpicie",
|
||||
"showSelectMultipleDescription": "Domyślnie menu kontekstowe pulpitu nie ma opcji \"wybierz wiele\", tak jak w przypadku urządzeń mobilnych. Opcja ta jest zawsze widoczna w menu kontekstowym.",
|
||||
"defaultMediaPlayer": "Użyj natywnego odtwarzacza multimediów w przeglądarce",
|
||||
"defaultMediaPlayerDescription": "Do odtwarzania plików wideo i audio należy używać natywnego odtwarzacza multimediów przeglądarki, a nie dołączonego do FileBrowser odtwarzacza multimediów \"vue-plyr\".",
|
||||
"debugOfficeEditor": "Włącz tryb debugowania OnlyOffice",
|
||||
"debugOfficeEditorDescription": "Wyświetla wyskakujące okienko debugowania z dodatkowymi informacjami w edytorze OnlyOffice.",
|
||||
"fileViewerOptions": "Opcje przeglądarki plików",
|
||||
"themeAndLanguage": "Temat i język"
|
||||
"showSelectMultipleDescription": "Domyślnie menu kontekstowe pulpitu nie ma opcji \"wybierz wiele\", tak jak w przypadku urządzeń mobilnych. Opcja ta jest zawsze widoczna w menu kontekstowym."
|
||||
},
|
||||
"editor": {
|
||||
"uninitialized": "Nie udało się zainicjować edytora. Przeładuj ponownie.",
|
||||
"saveFailed": "Nie udało się zapisać pliku. Spróbuj ponownie.",
|
||||
"saveAborted": "Zapisywanie przerwane. Nazwa pliku nie pasuje do aktywnego pliku.",
|
||||
"saveDisabled": "Zapisywanie udostępnionych plików nie jest obsługiwane.",
|
||||
"saveAbortedMessage": "Operacja zapisywania została przerwana. Aktywny plik aplikacji ({activeFile}) nie jest zgodny z plikiem, który próbujesz zapisać ({tryingToSave}).",
|
||||
"syncFailed": "Nie udało się zsynchronizować stanu z trasą dla \"{filename}\" po 5 próbach. Przerwano konfigurację edytora, aby zapobiec uszkodzeniu danych."
|
||||
},
|
||||
"player": {
|
||||
"LoopEnabled": "Włączona pętla",
|
||||
"LoopDisabled": "Pętla wyłączona"
|
||||
"saveAbortedMessage": "Save operation aborted. The application's active file ({activeFile}) does not match the file you are trying to save ({tryingToSave}).",
|
||||
"syncFailed": "failed to sync state with the route for \"{filename}\" after 5 attempts. Aborting editor setup to prevent data corruption."
|
||||
}
|
||||
}
|
||||
@@ -49,8 +49,7 @@
|
||||
"clearCompleted": "Limpar concluído",
|
||||
"showMore": "Mostrar mais",
|
||||
"showLess": "Mostrar menos",
|
||||
"openParentFolder": "Abrir a pasta principal",
|
||||
"selectedCount": "Número de itens selecionados para executar uma ação"
|
||||
"openParentFolder": "Abrir a pasta principal"
|
||||
},
|
||||
"download": {
|
||||
"downloadFile": "Baixar arquivo",
|
||||
@@ -85,15 +84,15 @@
|
||||
"click": "selecionar pasta ou arquivo",
|
||||
"ctrl": {
|
||||
"click": "selecionar várias pastas e arquivos",
|
||||
"d": "baixar itens selecionados"
|
||||
"f": "pesquisar",
|
||||
"s": "salvar um arquivo ou baixar a pasta que você está"
|
||||
},
|
||||
"del": "apagar os arquivos selecionados",
|
||||
"doubleClick": "abrir pasta ou arquivo",
|
||||
"esc": "limpar seleção e/ou fechar menu",
|
||||
"f1": "esta informação",
|
||||
"f2": "renomear arquivo",
|
||||
"help": "Ajuda",
|
||||
"f1": "mostrar prompt de ajuda",
|
||||
"description": "Você pode ver as opções básicas de navegação abaixo. Para obter informações adicionais, visite",
|
||||
"wiki": "Wiki do FileBrowser Quantum"
|
||||
"help": "Ajuda"
|
||||
},
|
||||
"languages": {
|
||||
"he": "עברית",
|
||||
@@ -135,13 +134,16 @@
|
||||
"prompts": {
|
||||
"copy": "Copiar",
|
||||
"copyMessage": "Escolha um lugar para copiar os arquivos:",
|
||||
"deleteMessageMultiple": "Deseja apagar {count} arquivo(s)?",
|
||||
"deleteMessageSingle": "Deseja apagar esta pasta/arquivo?",
|
||||
"deleteMessageShare": "Deseja apagar este compartilhamento ({path})?",
|
||||
"deleteTitle": "Apagar arquivos",
|
||||
"displayName": "Nome:",
|
||||
"download": "Baixar arquivos",
|
||||
"downloadMessage": "Escolha o formato do arquivo.",
|
||||
"error": "Algo de errado ocorreu",
|
||||
"fileInfo": "Informação do arquivo",
|
||||
"lastModified": "Última modificação{suffix}",
|
||||
"move": "Mover",
|
||||
"moveMessage": "Escolha uma nova pasta para os seus arquivos:",
|
||||
"newArchetype": "Criar um novo post baseado num \"archetype\". O seu arquivo será criado na pasta \"content\".",
|
||||
@@ -149,7 +151,10 @@
|
||||
"newDirMessage": "Escreva o nome da nova pasta.",
|
||||
"newFile": "Novo arquivo",
|
||||
"newFileMessage": "Escreva o nome do novo arquivo.",
|
||||
"numberDirs": "Número de pastas{suffix}",
|
||||
"numberFiles": "Número de arquivos{suffix}",
|
||||
"rename": "Renomear",
|
||||
"renameMessage": "Insira um novo nome para",
|
||||
"replace": "Substituir",
|
||||
"replaceMessage": "Já existe um arquivo com nome igual a um dos que está tentando enviar. Deseja substituir?\n",
|
||||
"schedule": "Agendar",
|
||||
@@ -167,6 +172,10 @@
|
||||
"dragAndDrop": "Arraste e solte os arquivos aqui",
|
||||
"completed": "Concluído",
|
||||
"conflictsDetected": "Conflitos detectados",
|
||||
"uploadSettingsChunked": "Max concurrent uploads: {maxConcurrentUpload}, chunk size: {uploadChunkSizeMb} MB",
|
||||
"uploadSettingsNoChunk": "Max concurrent uploads: {maxConcurrentUpload}, chunked upload disabled",
|
||||
"invalidName": "Nome inválido fornecido: não pode conter \"/\" ou \"\\\"",
|
||||
"source": "Source{suffix}",
|
||||
"downloadsLimit": "Limite de número de downloads",
|
||||
"maxBandwidth": "Largura de banda máxima de download (kbps)",
|
||||
"shareTheme": "Compartilhar tema",
|
||||
@@ -177,44 +186,22 @@
|
||||
"shareBanner": "Compartilhar URL do banner",
|
||||
"shareFavicon": "Compartilhar URL do favicon",
|
||||
"optionalPasswordHelp": "Se definida, os usuários deverão digitar essa senha para visualizar o conteúdo compartilhado.",
|
||||
"viewMode": "Modo de exibição",
|
||||
"renameMessage": "Digite um novo nome:",
|
||||
"renameMessageInvalid": "Um arquivo não pode ter o contêiner \"/\" ou \"\\\"",
|
||||
"source": "Fonte{suffix}",
|
||||
"deleteMessageMultiple": "Tem certeza de que deseja excluir o(s) arquivo(s) {count}?",
|
||||
"deleteMessageShare": "Tem certeza de que deseja excluir esse compartilhamento: {path} ?",
|
||||
"lastModified": "Última modificação{suffix}",
|
||||
"numberDirs": "Número de diretórios{suffix}",
|
||||
"numberFiles": "Número de arquivos{suffix}",
|
||||
"renameMessageConflict": "Já existe um item chamado \"{filename}\"!",
|
||||
"uploadSettingsChunked": "Máximo de uploads simultâneos: {maxConcurrentUpload}, chunk size: {uploadChunkSizeMb} MB",
|
||||
"uploadSettingsNoChunk": "Máximo de uploads simultâneos: {maxConcurrentUpload}, upload em pedaços desativado"
|
||||
"viewMode": "Modo de exibição"
|
||||
},
|
||||
"search": {
|
||||
"images": "Imagens",
|
||||
"music": "Músicas",
|
||||
"pdf": "PDF",
|
||||
"pressToSearch": "Pressione Enter para pesquisar...",
|
||||
"search": "Pesquise...",
|
||||
"typeToSearch": "Digite para pesquisar...",
|
||||
"types": "Tipos",
|
||||
"video": "Vídeos",
|
||||
"path": "Caminho:",
|
||||
"smallerThan": "Menor que:",
|
||||
"largerThan": "Maior que:",
|
||||
"helpText1": "A pesquisa ocorre em cada caractere que você digita (mínimo de 3 caracteres para termos de pesquisa).",
|
||||
"helpText2": "O índice: A pesquisa utiliza o índice que é atualizado automaticamente no intervalo configurado (padrão: 5 minutos). A pesquisa quando o programa acaba de ser iniciado pode resultar em resultados incompletos.",
|
||||
"noResults": "Não foram encontrados resultados na pesquisa indexada.",
|
||||
"onlyFolders": "Somente pastas",
|
||||
"onlyFiles": "Somente arquivos",
|
||||
"showOptions": "Mostrar opções",
|
||||
"photos": "Fotos",
|
||||
"audio": "Áudio",
|
||||
"videos": "Vídeos",
|
||||
"documents": "Documentos",
|
||||
"archives": "Arquivos",
|
||||
"number": "Número",
|
||||
"searchContext": "Contexto da pesquisa: {context}",
|
||||
"notEnoughCharacters": "Não há caracteres suficientes para pesquisar (mín. {minSearchLength})",
|
||||
"typeToSearch": "Comece a digitar {minSearchLength} ou mais caracteres para iniciar a busca."
|
||||
"helpText2": "O índice: A pesquisa utiliza o índice que é atualizado automaticamente no intervalo configurado (padrão: 5 minutos). A pesquisa quando o programa acaba de ser iniciado pode resultar em resultados incompletos."
|
||||
},
|
||||
"settings": {
|
||||
"admin": "Admin",
|
||||
@@ -227,8 +214,10 @@
|
||||
"avoidChanges": "(deixe em branco para manter)",
|
||||
"branding": "Customização",
|
||||
"brandingDirectoryPath": "Diretório de customização",
|
||||
"brandingHelp": "Você pode mudar a aparência e experiência de sua instância do File Browser alterando seu nome, logotipo, adicionando estilos customizados e até desabilitando links externos para o GitHub.\nPara mais informações sobre customizações, confira {0}.",
|
||||
"changePassword": "Alterar senha",
|
||||
"commandRunner": "Execução de comandos",
|
||||
"commandRunnerHelp": "Aqui você pode definir comandos que serão executados nos eventos descritos. Escreva um por linha. As variáveis de ambiente {0} e {1} estão disponíveis, sendo {0} relativo a {1}. Para mais informações sobre esta função e as variáveis de ambiente disponíveis, leia a {2}.",
|
||||
"commandsUpdated": "Comandos atualizados!",
|
||||
"createUserDir": "Criar diretório Home para novos usuários",
|
||||
"userHomeBasePath": "Caminho base para diretórios de usuários",
|
||||
@@ -311,14 +300,7 @@
|
||||
"adminOptions": "Opções do usuário administrador",
|
||||
"shareDuration": "Expirações",
|
||||
"searchOptions": "Opções de pesquisa",
|
||||
"downloads": "Downloads",
|
||||
"systemAdmin": "Sistema e administração",
|
||||
"configViewer": "Visualizador de configuração",
|
||||
"configViewerShowFull": "Mostrar configuração completa",
|
||||
"configViewerShowComments": "Mostrar comentários",
|
||||
"configViewerLoadConfig": "Configuração de carga",
|
||||
"brandingHelp": "Você pode personalizar a aparência da instância do Navegador de arquivos alterando seu nome, substituindo o logotipo, adicionando estilos personalizados e até mesmo desativando links externos para o GitHub.\nPara obter mais informações sobre branding personalizado, consulte {0}.",
|
||||
"commandRunnerHelp": "Aqui você pode definir comandos que são executados nos eventos nomeados. Você deve escrever um por linha. As variáveis de ambiente {0} e {1} estarão disponíveis, sendo {0} relativo a {1}. Para obter mais informações sobre esse recurso e as variáveis de ambiente disponíveis, leia {2}."
|
||||
"downloads": "Downloads"
|
||||
},
|
||||
"sidebar": {
|
||||
"help": "Ajuda",
|
||||
@@ -376,21 +358,12 @@
|
||||
"hideNavButtons": "Ocultar botões de navegação",
|
||||
"hideNavButtonsDescription": "Oculte os botões de navegação na barra de navegação do compartilhamento para criar uma aparência minimalista.",
|
||||
"viewModeDescription": "Layout padrão para a página compartilhada.",
|
||||
"titleDefault": "Shared files - {title}",
|
||||
"descriptionDefault": "Um compartilhamento foi enviado a você para visualização ou download.",
|
||||
"disableShareCard": "Desativar cartão compartilhado",
|
||||
"disableShareCardDescription": "Desative o cartão de compartilhamento na página compartilhada na barra lateral ou na parte superior da página no celular.",
|
||||
"disableSidebar": "Desativar a barra lateral",
|
||||
"disableSidebarDescription": "Desative a barra lateral na página compartilhada.",
|
||||
"enforceDarkLightMode": "Aplicar o modo de tema",
|
||||
"enforceDarkLightModeDescription": "Força um modo de tema específico (escuro ou claro) para esse compartilhamento, substituindo as preferências do usuário.",
|
||||
"default": "Não imponha um modo",
|
||||
"dark": "Escuro",
|
||||
"light": "Luz",
|
||||
"enableOnlyOffice": "Ativar o visualizador do OnlyOffice",
|
||||
"enableOnlyOfficeDescription": "Permitir a visualização de arquivos do Office usando o OnlyOffice nesse compartilhamento.",
|
||||
"enableOnlyOfficeEditing": "Ativar a edição do OnlyOffice",
|
||||
"enableOnlyOfficeEditingDescription": "Permitir a edição de arquivos do Office usando o OnlyOffice nesse compartilhamento.",
|
||||
"titleDefault": "Arquivos compartilhados - {title}"
|
||||
"disableSidebarDescription": "Desative a barra lateral na página compartilhada."
|
||||
},
|
||||
"api": {
|
||||
"title": "Chaves de API:",
|
||||
@@ -474,8 +447,8 @@
|
||||
"totalDenied": "Negado",
|
||||
"totalAllowed": "Permitido",
|
||||
"all": "Negar tudo",
|
||||
"defaultBehaviorDescription": "Isso mostra o que acontece quando um usuário tenta acessar um caminho em que não existem regras de acesso específicas. \"Permitir\" significa que os usuários podem acessar por padrão, \"Negar\" significa que os usuários são bloqueados por padrão, a menos que sejam explicitamente permitidos.",
|
||||
"defaultBehavior": "Ação padrão{suffix}"
|
||||
"defaultBehavior": "Default action{suffix}",
|
||||
"defaultBehaviorDescription": "Isso mostra o que acontece quando um usuário tenta acessar um caminho em que não existem regras de acesso específicas. \"Permitir\" significa que os usuários podem acessar por padrão, \"Negar\" significa que os usuários são bloqueados por padrão, a menos que sejam explicitamente permitidos."
|
||||
},
|
||||
"fileLoading": {
|
||||
"title": "Uploads e downloads",
|
||||
@@ -541,24 +514,14 @@
|
||||
"defaultThemeDescription": "default - O tema padrão",
|
||||
"customTheme": "Escolha seu tema",
|
||||
"showSelectMultiple": "Mostrar seleção múltipla no menu de contexto na área de trabalho",
|
||||
"showSelectMultipleDescription": "Por padrão, o menu de contexto do desktop não tem a opção \"selecionar vários\" como no celular. Essa opção sempre a mostra no menu de contexto.",
|
||||
"defaultMediaPlayer": "Use o reprodutor de mídia nativo do seu navegador",
|
||||
"defaultMediaPlayerDescription": "Use o reprodutor de mídia nativo do seu navegador para arquivos de vídeo e áudio em vez do reprodutor de mídia 'vue-plyr' incluído no FileBrowser.",
|
||||
"debugOfficeEditor": "Ativar o modo de depuração do OnlyOffice",
|
||||
"debugOfficeEditorDescription": "Mostrar um pop-up de depuração com informações adicionais no editor do OnlyOffice.",
|
||||
"fileViewerOptions": "Opções do visualizador de arquivos",
|
||||
"themeAndLanguage": "Tema e linguagem"
|
||||
"showSelectMultipleDescription": "Por padrão, o menu de contexto do desktop não tem a opção \"selecionar vários\" como no celular. Essa opção sempre a mostra no menu de contexto."
|
||||
},
|
||||
"editor": {
|
||||
"uninitialized": "Falha ao inicializar o editor. Favor recarregar.",
|
||||
"saveFailed": "Falha ao salvar o arquivo. Tente novamente.",
|
||||
"saveAborted": "Salvamento abortado. O nome do arquivo não corresponde ao arquivo ativo.",
|
||||
"saveDisabled": "Não há suporte para o salvamento de arquivos compartilhados.",
|
||||
"saveAbortedMessage": "Operação de salvamento abortada. O arquivo ativo do aplicativo ({activeFile}) não corresponde ao arquivo que você está tentando salvar ({tryingToSave}).",
|
||||
"syncFailed": "Falha ao sincronizar o estado com a rota para \"{filename}\" após 5 tentativas. Abortar a configuração do editor para evitar a corrupção de dados."
|
||||
},
|
||||
"player": {
|
||||
"LoopEnabled": "Loop ativado",
|
||||
"LoopDisabled": "Loop desativado"
|
||||
"saveAbortedMessage": "Save operation aborted. The application's active file ({activeFile}) does not match the file you are trying to save ({tryingToSave}).",
|
||||
"syncFailed": "failed to sync state with the route for \"{filename}\" after 5 attempts. Aborting editor setup to prevent data corruption."
|
||||
}
|
||||
}
|
||||
+30
-67
@@ -49,8 +49,7 @@
|
||||
"clearCompleted": "Limpar concluído",
|
||||
"showMore": "Mostrar mais",
|
||||
"showLess": "Mostrar menos",
|
||||
"openParentFolder": "Abrir a pasta principal",
|
||||
"selectedCount": "Número de itens selecionados para executar uma ação"
|
||||
"openParentFolder": "Abrir a pasta principal"
|
||||
},
|
||||
"download": {
|
||||
"downloadFile": "Descarregar ficheiro",
|
||||
@@ -85,15 +84,15 @@
|
||||
"click": "selecionar pasta ou ficheiro",
|
||||
"ctrl": {
|
||||
"click": "selecionar várias pastas e ficheiros",
|
||||
"d": "descarregar itens selecionados"
|
||||
"f": "pesquisar",
|
||||
"s": "guardar um ficheiro ou descarrega a pasta em que está a navegar"
|
||||
},
|
||||
"del": "eliminar os ficheiros selecionados",
|
||||
"doubleClick": "abrir pasta ou ficheiro",
|
||||
"esc": "limpar seleção e/ou fechar menu",
|
||||
"f1": "esta informação",
|
||||
"f2": "alterar nome do ficheiro",
|
||||
"help": "Ajuda",
|
||||
"f1": "mostrar prompt de ajuda",
|
||||
"description": "Pode ver as opções básicas de navegação abaixo. Para obter informações adicionais, visite",
|
||||
"wiki": "Wiki do FileBrowser Quantum"
|
||||
"help": "Ajuda"
|
||||
},
|
||||
"languages": {
|
||||
"he": "עברית",
|
||||
@@ -135,6 +134,7 @@
|
||||
"prompts": {
|
||||
"copy": "Copiar",
|
||||
"copyMessage": "Escolha um lugar para onde copiar os ficheiros:",
|
||||
"deleteMessageMultiple": "Quer eliminar {count} ficheiro(s)?",
|
||||
"deleteMessageSingle": "Quer eliminar esta pasta/ficheiro?",
|
||||
"deleteTitle": "Eliminar ficheiros",
|
||||
"displayName": "Nome:",
|
||||
@@ -142,6 +142,7 @@
|
||||
"downloadMessage": "Escolha o formato do ficheiro que quer descarregar.",
|
||||
"error": "Algo correu mal",
|
||||
"fileInfo": "Informação do ficheiro",
|
||||
"lastModified": "Última alteração{suffix}",
|
||||
"move": "Mover",
|
||||
"moveMessage": "Escolha uma nova casa para os seus ficheiros/pastas:",
|
||||
"newArchetype": "Criar um novo post baseado num \"archetype\". O seu ficheiro será criado na pasta \"content\".",
|
||||
@@ -149,7 +150,10 @@
|
||||
"newDirMessage": "Escreva o nome da nova pasta.",
|
||||
"newFile": "Novo ficheiro",
|
||||
"newFileMessage": "Escreva o nome do novo ficheiro.",
|
||||
"numberDirs": "Número de pastas{suffix}",
|
||||
"numberFiles": "Número de ficheiros{suffix}",
|
||||
"rename": "Alterar nome",
|
||||
"renameMessage": "Insira um novo nome para",
|
||||
"replace": "Substituir",
|
||||
"replaceMessage": "Já existe um ficheiro com nome igual a um dos que está a tentar enviar. Quer substituí-lo?\n",
|
||||
"schedule": "Agendar",
|
||||
@@ -157,6 +161,7 @@
|
||||
"show": "Mostrar",
|
||||
"size": "Tamanho",
|
||||
"upload": "Carregar",
|
||||
"deleteMessageShare": "Are you sure you want to delete this share: {path} ?",
|
||||
"deleteUserMessage": "Tem a certeza de que pretende apagar este utilizador?",
|
||||
"selected": "selecionado",
|
||||
"filesSelected": "ficheiros selecionados",
|
||||
@@ -167,6 +172,10 @@
|
||||
"dragAndDrop": "Arrastar e largar ficheiros aqui",
|
||||
"completed": "Concluído",
|
||||
"conflictsDetected": "Conflitos detectados",
|
||||
"uploadSettingsChunked": "Max concurrent uploads: {maxConcurrentUpload}, chunk size: {uploadChunkSizeMb} MB",
|
||||
"uploadSettingsNoChunk": "Max concurrent uploads: {maxConcurrentUpload}, chunked upload disabled",
|
||||
"invalidName": "Nome inválido fornecido: não pode conter \"/\" ou \"\\\"",
|
||||
"source": "Source{suffix}",
|
||||
"downloadsLimit": "Limite do número de descarregamentos",
|
||||
"maxBandwidth": "Largura de banda máxima de descarregamento (kbps)",
|
||||
"shareTheme": "Partilhar o tema",
|
||||
@@ -177,44 +186,22 @@
|
||||
"shareBanner": "Partilhar URL do banner",
|
||||
"shareFavicon": "Partilhar URL do favicon",
|
||||
"optionalPasswordHelp": "Se estiver definida, os utilizadores têm de introduzir esta palavra-passe para ver o conteúdo partilhado.",
|
||||
"viewMode": "Modo de visualização",
|
||||
"renameMessage": "Introduzir um novo nome:",
|
||||
"renameMessageInvalid": "Um ficheiro não pode conter \"/\" ou \"\\\"",
|
||||
"source": "Fonte{suffix}",
|
||||
"deleteMessageMultiple": "Tem a certeza de que pretende apagar o(s) ficheiro(s) {count}?",
|
||||
"deleteMessageShare": "Tem a certeza de que pretende apagar esta partilha: {path} ?",
|
||||
"lastModified": "Última modificação{suffix}",
|
||||
"numberDirs": "Número de diretórios{suffix}",
|
||||
"numberFiles": "Número de ficheiros{suffix}",
|
||||
"renameMessageConflict": "Já existe um item com o nome \"{filename}\"!",
|
||||
"uploadSettingsChunked": "Máximo de carregamentos simultâneos: {maxConcurrentUpload}, tamanho do pedaço: {uploadChunkSizeMb} MB",
|
||||
"uploadSettingsNoChunk": "Máximo de carregamentos simultâneos: {maxConcurrentUpload}, carregamento em pedaços desativado"
|
||||
"viewMode": "Modo de visualização"
|
||||
},
|
||||
"search": {
|
||||
"images": "Imagens",
|
||||
"music": "Música",
|
||||
"pdf": "PDF",
|
||||
"pressToSearch": "Tecla Enter para pesquisar...",
|
||||
"search": "Pesquisar...",
|
||||
"typeToSearch": "Escrever para pesquisar...",
|
||||
"types": "Tipos",
|
||||
"video": "Vídeos",
|
||||
"path": "Caminho:",
|
||||
"smallerThan": "Mais pequeno do que:",
|
||||
"largerThan": "Maior do que:",
|
||||
"helpText1": "A pesquisa ocorre em cada caractere digitado (mínimo de 3 caracteres para termos de pesquisa).",
|
||||
"helpText2": "O índice: A pesquisa utiliza o índice que é atualizado automaticamente no intervalo configurado (predefinição: 5 minutos). A pesquisa quando o programa acabou de ser iniciado pode resultar em resultados incompletos.",
|
||||
"noResults": "Não foram encontrados resultados na pesquisa indexada.",
|
||||
"onlyFolders": "Apenas pastas",
|
||||
"onlyFiles": "Apenas ficheiros",
|
||||
"showOptions": "Mostrar opções",
|
||||
"photos": "Fotos",
|
||||
"audio": "Áudio",
|
||||
"videos": "Vídeos",
|
||||
"documents": "Documentos",
|
||||
"archives": "Arquivos",
|
||||
"number": "Número",
|
||||
"searchContext": "Contexto de pesquisa: {context}",
|
||||
"notEnoughCharacters": "Não há caracteres suficientes para pesquisar (min {minSearchLength})",
|
||||
"typeToSearch": "Comece a escrever {minSearchLength} ou mais caracteres para iniciar a pesquisa."
|
||||
"helpText2": "O índice: A pesquisa utiliza o índice que é atualizado automaticamente no intervalo configurado (predefinição: 5 minutos). A pesquisa quando o programa acabou de ser iniciado pode resultar em resultados incompletos."
|
||||
},
|
||||
"settings": {
|
||||
"admin": "Admin",
|
||||
@@ -227,8 +214,10 @@
|
||||
"avoidChanges": "(deixe em branco para manter)",
|
||||
"branding": "Marca",
|
||||
"brandingDirectoryPath": "Caminho da pasta de marca",
|
||||
"brandingHelp": "Pode personalizar a aparência do seu Navegador de Ficheiros, alterar o nome, substituindo o logótipo, adicionando estilos personalizados e mesmo desativando links externos para o GitHub.\nPara mais informações sobre marca personalizada, por favor veja {0}.",
|
||||
"changePassword": "Alterar palavra-passe",
|
||||
"commandRunner": "Execução de comandos",
|
||||
"commandRunnerHelp": "Aqui pode definir comandos que são executados nos eventos nomeados. Tem de escrever um por linha. As variáveis de ambiente {0} e {1} estarão disponíveis, sendo {0} relativo a {1}. Para mais informações sobre esta funcionalidade e as variáveis de ambiente, veja {2}.",
|
||||
"commandsUpdated": "Comandos atualizados!",
|
||||
"createUserDir": "Criar automaticamente a pasta de início ao adicionar um novo utilizador",
|
||||
"customStylesheet": "Folha de estilos personalizada",
|
||||
@@ -311,14 +300,7 @@
|
||||
"adminOptions": "Opções do utilizador administrador",
|
||||
"shareDuration": "Expirações",
|
||||
"searchOptions": "Opções de pesquisa",
|
||||
"downloads": "Transferências",
|
||||
"systemAdmin": "Sistema e administração",
|
||||
"configViewer": "Visualizador de configuração",
|
||||
"configViewerShowFull": "Mostrar configuração completa",
|
||||
"configViewerShowComments": "Mostrar comentários",
|
||||
"configViewerLoadConfig": "Configuração de carga",
|
||||
"brandingHelp": "É possível personalizar a aparência da instância do Navegador de arquivos alterando seu nome, substituindo o logotipo, adicionando estilos personalizados e até mesmo desativando links externos para o GitHub.\nPara obter mais informações sobre a marca personalizada, consulte {0}.",
|
||||
"commandRunnerHelp": "Aqui pode definir comandos que são executados nos eventos nomeados. Deve escrever um por linha. As variáveis de ambiente {0} e {1} estarão disponíveis, sendo {0} relativo a {1}. Para obter mais informações sobre este recurso e as variáveis de ambiente disponíveis, leia {2}."
|
||||
"downloads": "Transferências"
|
||||
},
|
||||
"sidebar": {
|
||||
"help": "Ajuda",
|
||||
@@ -376,21 +358,12 @@
|
||||
"hideNavButtons": "Ocultar botões de navegação",
|
||||
"hideNavButtonsDescription": "Oculte os botões de navegação na barra de navegação na partilha para criar um aspeto minimalista.",
|
||||
"viewModeDescription": "Esquema predefinido para a página partilhada.",
|
||||
"titleDefault": "Shared files - {title}",
|
||||
"descriptionDefault": "Foi-lhe enviada uma ação para visualizar ou descarregar.",
|
||||
"disableShareCard": "Desativar o cartão de partilha",
|
||||
"disableShareCardDescription": "Desativar o cartão de partilha na página partilhada na barra lateral ou na parte superior da página no telemóvel.",
|
||||
"disableSidebar": "Desativar a barra lateral",
|
||||
"disableSidebarDescription": "Desativar a barra lateral na página partilhada.",
|
||||
"enforceDarkLightMode": "Aplicar o modo de tema",
|
||||
"enforceDarkLightModeDescription": "Força um modo de tema específico (escuro ou claro) para esta partilha, substituindo as preferências do utilizador.",
|
||||
"default": "Não impor um modo",
|
||||
"dark": "Escuro",
|
||||
"light": "Luz",
|
||||
"enableOnlyOffice": "Ativar o visualizador OnlyOffice",
|
||||
"enableOnlyOfficeDescription": "Permitir a visualização de ficheiros de escritório utilizando o OnlyOffice nesta partilha.",
|
||||
"enableOnlyOfficeEditing": "Ativar a edição OnlyOffice",
|
||||
"enableOnlyOfficeEditingDescription": "Permitir a edição de ficheiros do Office utilizando o OnlyOffice nesta partilha.",
|
||||
"titleDefault": "Ficheiros partilhados - {title}"
|
||||
"disableSidebarDescription": "Desativar a barra lateral na página partilhada."
|
||||
},
|
||||
"api": {
|
||||
"title": "Chaves da API",
|
||||
@@ -505,13 +478,7 @@
|
||||
"defaultThemeDescription": "default - O tema predefinido",
|
||||
"customTheme": "Escolha o seu tema",
|
||||
"showSelectMultiple": "Mostrar seleção múltipla no menu de contexto no ambiente de trabalho",
|
||||
"showSelectMultipleDescription": "Por predefinição, o menu de contexto do ambiente de trabalho não tem a opção \"selecionar vários\" como no telemóvel. Esta opção mostra-a sempre no menu de contexto.",
|
||||
"defaultMediaPlayer": "Utilize o leitor multimédia nativo do seu navegador",
|
||||
"defaultMediaPlayerDescription": "Utilize o leitor multimédia nativo do seu navegador para ficheiros de vídeo e áudio em vez do leitor multimédia 'vue-plyr' incluído no FileBrowser.",
|
||||
"debugOfficeEditor": "Ativar o modo de depuração do OnlyOffice",
|
||||
"debugOfficeEditorDescription": "Mostrar um popup de depuração com informações adicionais no editor OnlyOffice.",
|
||||
"fileViewerOptions": "Opções do visualizador de ficheiros",
|
||||
"themeAndLanguage": "Tema e linguagem"
|
||||
"showSelectMultipleDescription": "Por predefinição, o menu de contexto do ambiente de trabalho não tem a opção \"selecionar vários\" como no telemóvel. Esta opção mostra-a sempre no menu de contexto."
|
||||
},
|
||||
"general": {
|
||||
"name": "Nome",
|
||||
@@ -546,19 +513,15 @@
|
||||
"name": "Nome",
|
||||
"accessManagement": "Gestão do acesso",
|
||||
"all": "Recusar tudo",
|
||||
"defaultBehaviorDescription": "Isto mostra o que acontece quando um utilizador tenta aceder a um caminho onde não existem regras de acesso específicas. \"Permitir\" significa que os utilizadores podem aceder por predefinição, \"Negar\" significa que os utilizadores são bloqueados por predefinição, a menos que sejam explicitamente autorizados.",
|
||||
"defaultBehavior": "Ação por defeito{suffix}"
|
||||
"defaultBehavior": "Default action{suffix}",
|
||||
"defaultBehaviorDescription": "Isto mostra o que acontece quando um utilizador tenta aceder a um caminho onde não existem regras de acesso específicas. \"Permitir\" significa que os utilizadores podem aceder por predefinição, \"Negar\" significa que os utilizadores são bloqueados por predefinição, a menos que sejam explicitamente autorizados."
|
||||
},
|
||||
"editor": {
|
||||
"uninitialized": "Falha ao inicializar o editor. Por favor, recarregue.",
|
||||
"saveFailed": "Falha ao guardar o ficheiro. Por favor, tente novamente.",
|
||||
"saveAborted": "Gravação abortada. O nome do ficheiro não corresponde ao ficheiro ativo.",
|
||||
"saveDisabled": "A gravação de ficheiros partilhados não é suportada.",
|
||||
"saveAbortedMessage": "Operação de gravação abortada. O ficheiro ativo da aplicação ({activeFile}) não corresponde ao ficheiro que está a tentar guardar ({tryingToSave}).",
|
||||
"syncFailed": "não conseguiu sincronizar o estado com a rota para \"{filename}\" após 5 tentativas. Abortar a configuração do editor para evitar a corrupção de dados."
|
||||
},
|
||||
"player": {
|
||||
"LoopEnabled": "Loop ativado",
|
||||
"LoopDisabled": "Loop desativado"
|
||||
"saveAbortedMessage": "Save operation aborted. The application's active file ({activeFile}) does not match the file you are trying to save ({tryingToSave}).",
|
||||
"syncFailed": "failed to sync state with the route for \"{filename}\" after 5 attempts. Aborting editor setup to prevent data corruption."
|
||||
}
|
||||
}
|
||||
+30
-67
@@ -49,8 +49,7 @@
|
||||
"clearCompleted": "Golire finalizată",
|
||||
"showMore": "Arată mai multe",
|
||||
"showLess": "Arată mai puțin",
|
||||
"openParentFolder": "Deschideți folderul părinte",
|
||||
"selectedCount": "Numărul de elemente selectate pentru a efectua o acțiune"
|
||||
"openParentFolder": "Deschideți folderul părinte"
|
||||
},
|
||||
"download": {
|
||||
"downloadFile": "Descarcă fișier",
|
||||
@@ -85,15 +84,15 @@
|
||||
"click": "alege fișier sau director",
|
||||
"ctrl": {
|
||||
"click": "alege multiple fișiere sau directoare",
|
||||
"d": "descărcați elementele selectate"
|
||||
"f": "deschide căutarea",
|
||||
"s": "salvează un fișier sau descarcă directorul în care te afli"
|
||||
},
|
||||
"del": "șterge elementele selectate",
|
||||
"doubleClick": "deschide un fișier sau director",
|
||||
"esc": "elibereaza selecția și/sau închide fereastra",
|
||||
"f1": "această informație",
|
||||
"f2": "redenumește fișierul",
|
||||
"help": "Ajutor",
|
||||
"f1": "arată ajutor prompt",
|
||||
"description": "Puteți vizualiza mai jos opțiunile de navigație de bază. Pentru informații suplimentare, vă rugăm să vizitați",
|
||||
"wiki": "FileBrowser Quantum Wiki"
|
||||
"help": "Ajutor"
|
||||
},
|
||||
"languages": {
|
||||
"he": "עברית",
|
||||
@@ -135,6 +134,7 @@
|
||||
"prompts": {
|
||||
"copy": "Copiază",
|
||||
"copyMessage": "Alege unde vrei să copiezi fișierele:",
|
||||
"deleteMessageMultiple": "Ești sigur că vrei să ștergi aceste {count} fișier(e)?",
|
||||
"deleteMessageSingle": "Ești sigur că vrei să ștergi acest fișier/director?",
|
||||
"deleteTitle": "Șterge fișiere",
|
||||
"displayName": "Nume afișat:",
|
||||
@@ -142,6 +142,7 @@
|
||||
"downloadMessage": "Alege formatul în care vrei să descarci.",
|
||||
"error": "Ceva n-a funcționat cum trebuie",
|
||||
"fileInfo": "Informații fișier",
|
||||
"lastModified": "Ultima modificare{suffix}",
|
||||
"move": "Mută",
|
||||
"moveMessage": "Alege destinația:",
|
||||
"newArchetype": "Crează o nouă postare pe baza unui șablon. Fișierul va fi creat in directorul conținut.",
|
||||
@@ -149,7 +150,10 @@
|
||||
"newDirMessage": "Scrie numele noului director.",
|
||||
"newFile": "Fișier nou",
|
||||
"newFileMessage": "Scrie numele noului fișier.",
|
||||
"numberDirs": "Numărul directoarelor{suffix}",
|
||||
"numberFiles": "Numărul fișierelor{suffix}",
|
||||
"rename": "Redenumește",
|
||||
"renameMessage": "Redactează un nou nume pentru",
|
||||
"replace": "Înlocuiește",
|
||||
"replaceMessage": "Unul din fișierele încărcate intră în conflict din cauza denumrii. Vrei să înlocuiești fișierul existent?\n",
|
||||
"schedule": "Programare",
|
||||
@@ -157,6 +161,7 @@
|
||||
"show": "Arată",
|
||||
"size": "Dimensiune",
|
||||
"upload": "Încărcare",
|
||||
"deleteMessageShare": "Are you sure you want to delete this share: {path} ?",
|
||||
"deleteUserMessage": "Sunteți sigur că doriți să ștergeți acest utilizator?",
|
||||
"filesSelected": "fișiere selectate",
|
||||
"optionalPassword": "Parolă opțională",
|
||||
@@ -167,6 +172,10 @@
|
||||
"dragAndDrop": "Drag and drop fișiere aici",
|
||||
"completed": "Completat",
|
||||
"conflictsDetected": "Conflicte detectate",
|
||||
"uploadSettingsChunked": "Max concurrent uploads: {maxConcurrentUpload}, chunk size: {uploadChunkSizeMb} MB",
|
||||
"uploadSettingsNoChunk": "Max concurrent uploads: {maxConcurrentUpload}, chunked upload disabled",
|
||||
"invalidName": "Nume invalid furnizat: nu poate conține \"/\" sau \"\\\"",
|
||||
"source": "Source{suffix}",
|
||||
"downloadsLimit": "Limita numărului de descărcări",
|
||||
"maxBandwidth": "Lățime de bandă maximă de descărcare (kbps)",
|
||||
"shareTheme": "Temă de partajare",
|
||||
@@ -177,44 +186,22 @@
|
||||
"shareBanner": "Partajați URL-ul bannerului",
|
||||
"shareFavicon": "Partajați URL favicon",
|
||||
"optionalPasswordHelp": "Dacă este setată, utilizatorii trebuie să introducă această parolă pentru a vizualiza conținutul partajat.",
|
||||
"viewMode": "Mod de vizualizare",
|
||||
"renameMessage": "Introduceți un nume nou:",
|
||||
"renameMessageInvalid": "Un fișier nu poate conține \"/\" sau \"\\\"",
|
||||
"source": "Sursa{suffix}",
|
||||
"deleteMessageMultiple": "Sunteți sigur că doriți să eliminați fișierul (fișierele) {count}?",
|
||||
"deleteMessageShare": "Sunteți sigur că doriți să ștergeți această partajare: {path} ?",
|
||||
"lastModified": "Ultima modificare{suffix}",
|
||||
"numberDirs": "Număr de directoare{suffix}",
|
||||
"numberFiles": "Număr de fișiere{suffix}",
|
||||
"renameMessageConflict": "Un element numit \"{filename}\" există deja!",
|
||||
"uploadSettingsChunked": "Încărcări simultane maxime: {maxConcurrentUpload}, dimensiunea bucăților: {uploadChunkSizeMb} MB",
|
||||
"uploadSettingsNoChunk": "Încărcări simultane maxime: {maxConcurrentUpload}, încărcare fragmentată dezactivată"
|
||||
"viewMode": "Mod de vizualizare"
|
||||
},
|
||||
"search": {
|
||||
"images": "Imagini",
|
||||
"music": "Muzică",
|
||||
"pdf": "PDF",
|
||||
"pressToSearch": "Apasă enter pentru a căuta...",
|
||||
"search": "Caută...",
|
||||
"typeToSearch": "Scrie pentru a căuta...",
|
||||
"types": "Tipuri",
|
||||
"video": "Video",
|
||||
"path": "Calea:",
|
||||
"smallerThan": "Mai mic decât:",
|
||||
"largerThan": "Mai mare decât:",
|
||||
"helpText1": "Căutarea are loc pe fiecare caracter tastat (minim 3 caractere pentru termenii de căutare).",
|
||||
"helpText2": "Indexul: Căutarea utilizează indexul care se actualizează automat la intervalul configurat (implicit: 5 minute). Căutarea atunci când programul tocmai a început poate duce la rezultate incomplete.",
|
||||
"noResults": "Nu s-au găsit rezultate în căutarea indexată.",
|
||||
"onlyFolders": "Numai foldere",
|
||||
"onlyFiles": "Numai fișiere",
|
||||
"showOptions": "Afișare opțiuni",
|
||||
"photos": "Fotografii",
|
||||
"audio": "Audio",
|
||||
"videos": "Video",
|
||||
"documents": "Documente",
|
||||
"archives": "Arhive",
|
||||
"number": "Număr",
|
||||
"searchContext": "Context de căutare: {context}",
|
||||
"notEnoughCharacters": "Nu sunt suficiente caractere pentru căutare (min {minSearchLength})",
|
||||
"typeToSearch": "Începeți să tastați {minSearchLength} sau mai multe caractere pentru a începe căutarea."
|
||||
"helpText2": "Indexul: Căutarea utilizează indexul care se actualizează automat la intervalul configurat (implicit: 5 minute). Căutarea atunci când programul tocmai a început poate duce la rezultate incomplete."
|
||||
},
|
||||
"settings": {
|
||||
"admin": "Admin",
|
||||
@@ -227,8 +214,10 @@
|
||||
"avoidChanges": "(lasă gol pentru a nu schimba)",
|
||||
"branding": "Branding",
|
||||
"brandingDirectoryPath": "Calea către directorul de branding",
|
||||
"brandingHelp": "Poți personaliza cum arată instanța ta de File Browser modificându-i numele, înlocuindu-i logo-ul, adăugându-i stiluri personalizate si chiar dezactivând linkurile catre GitHub.\nPentru mai multe informații despre branding fă click aici",
|
||||
"changePassword": "Schimbă parola",
|
||||
"commandRunner": "Rulează comenzi",
|
||||
"commandRunnerHelp": "Aici poti seta comenzile care sunt executate in evenimente. Trebuie să scrii una pe linie. Variabilele de mediu {0} și {1} vor fi disponile, {0} fiind relativă la {1}. Pentru mai multe informații despre acest feature si variabilele de mediu disponibile, cititi {2}.",
|
||||
"commandsUpdated": "Comenzi actualizate!",
|
||||
"createUserDir": "Auto create user home dir while adding new user",
|
||||
"customStylesheet": "CSS personalizat",
|
||||
@@ -311,14 +300,7 @@
|
||||
"adminOptions": "Opțiuni utilizator administrator",
|
||||
"shareDuration": "Expiră",
|
||||
"searchOptions": "Opțiuni de căutare",
|
||||
"downloads": "Descărcări",
|
||||
"systemAdmin": "Sistem și administrare",
|
||||
"configViewer": "Vizualizator de configurare",
|
||||
"configViewerShowFull": "Afișați configurația completă",
|
||||
"configViewerShowComments": "Afișați comentariile",
|
||||
"configViewerLoadConfig": "Configurare încărcare",
|
||||
"brandingHelp": "Puteți personaliza aspectul instanței File Browser prin schimbarea numelui, înlocuirea logo-ului, adăugarea de stiluri personalizate și chiar dezactivarea linkurilor externe către GitHub.\nPentru mai multe informații despre brandingul personalizat, consultați {0}.",
|
||||
"commandRunnerHelp": "Aici puteți seta comenzi care sunt executate în cadrul evenimentelor numite. Trebuie să scrieți câte una pe linie. Variabilele de mediu {0} și {1} vor fi disponibile, {0} fiind relativ la {1}. Pentru mai multe informații despre această caracteristică și variabilele de mediu disponibile, vă rugăm să citiți {2}."
|
||||
"downloads": "Descărcări"
|
||||
},
|
||||
"sidebar": {
|
||||
"help": "Ajutor",
|
||||
@@ -376,21 +358,12 @@
|
||||
"hideNavButtons": "Ascundeți butoanele de navigare",
|
||||
"hideNavButtonsDescription": "Ascundeți butoanele de navigare de pe bara de navigare din share pentru a crea un aspect minimalist.",
|
||||
"viewModeDescription": "Layout implicit pentru pagina partajată.",
|
||||
"titleDefault": "Shared files - {title}",
|
||||
"descriptionDefault": "V-a fost trimisă o acțiune pentru a o vizualiza sau descărca.",
|
||||
"disableShareCard": "Dezactivați cardul de partajare",
|
||||
"disableShareCardDescription": "Dezactivați cardul de partajare pe pagina partajată în bara laterală sau în partea de sus a paginii pe mobil.",
|
||||
"disableSidebar": "Dezactivați bara laterală",
|
||||
"disableSidebarDescription": "Dezactivați bara laterală de pe pagina partajată.",
|
||||
"enforceDarkLightMode": "Impuneți modul tematic",
|
||||
"enforceDarkLightModeDescription": "Forțează un anumit mod tematic (întunecat sau deschis) pentru această partajare, trecând peste preferințele utilizatorului.",
|
||||
"default": "Nu impuneți un mod",
|
||||
"dark": "Întuneric",
|
||||
"light": "Lumină",
|
||||
"enableOnlyOffice": "Activați vizualizatorul OnlyOffice",
|
||||
"enableOnlyOfficeDescription": "Permiteți vizualizarea fișierelor office utilizând OnlyOffice în această partajare.",
|
||||
"enableOnlyOfficeEditing": "Activați editarea OnlyOffice",
|
||||
"enableOnlyOfficeEditingDescription": "Permiteți editarea fișierelor de birou utilizând OnlyOffice în această partajare.",
|
||||
"titleDefault": "Fișiere partajate - {title}"
|
||||
"disableSidebarDescription": "Dezactivați bara laterală de pe pagina partajată."
|
||||
},
|
||||
"api": {
|
||||
"title": "Chei API:",
|
||||
@@ -474,8 +447,8 @@
|
||||
"totalDenied": "Refuzat",
|
||||
"totalAllowed": "Permise",
|
||||
"all": "Refuzați toate",
|
||||
"defaultBehaviorDescription": "Aceasta arată ce se întâmplă atunci când un utilizator încearcă să acceseze o cale unde nu există reguli specifice de acces. \"Allow\" înseamnă că utilizatorii pot accesa în mod implicit, \"Deny\" înseamnă că utilizatorii sunt blocați în mod implicit dacă nu li se permite în mod explicit.",
|
||||
"defaultBehavior": "Acțiune implicită{suffix}"
|
||||
"defaultBehavior": "Default action{suffix}",
|
||||
"defaultBehaviorDescription": "Aceasta arată ce se întâmplă atunci când un utilizator încearcă să acceseze o cale unde nu există reguli specifice de acces. \"Allow\" înseamnă că utilizatorii pot accesa în mod implicit, \"Deny\" înseamnă că utilizatorii sunt blocați în mod implicit dacă nu li se permite în mod explicit."
|
||||
},
|
||||
"fileLoading": {
|
||||
"title": "Încărcări și descărcări",
|
||||
@@ -541,24 +514,14 @@
|
||||
"defaultThemeDescription": "default - Tema implicită",
|
||||
"customTheme": "Alege-ți tema",
|
||||
"showSelectMultiple": "Afișarea selecției multiple în meniul contextual de pe desktop",
|
||||
"showSelectMultipleDescription": "În mod implicit, meniul contextual de pe desktop nu are opțiunea \"select multiple\", ca pe mobil. Această opțiune se afișează întotdeauna în meniul contextual.",
|
||||
"defaultMediaPlayer": "Utilizați playerul media nativ al browserului dvs.",
|
||||
"defaultMediaPlayerDescription": "Utilizați playerul media nativ al browserului dvs. pentru fișierele video și audio mai degrabă decât playerul media \"vue-plyr\" inclus în FileBrowser.",
|
||||
"debugOfficeEditor": "Activați modul de depanare OnlyOffice",
|
||||
"debugOfficeEditorDescription": "Afișați o fereastră pop-up de depanare cu informații suplimentare în editorul OnlyOffice.",
|
||||
"fileViewerOptions": "Opțiuni vizualizator de fișiere",
|
||||
"themeAndLanguage": "Temă și limbaj"
|
||||
"showSelectMultipleDescription": "În mod implicit, meniul contextual de pe desktop nu are opțiunea \"select multiple\", ca pe mobil. Această opțiune se afișează întotdeauna în meniul contextual."
|
||||
},
|
||||
"editor": {
|
||||
"uninitialized": "Nu s-a reușit inițializarea editorului. Vă rugăm să reîncărcați.",
|
||||
"saveFailed": "Nu ați reușit să salvați fișierul. Vă rugăm să încercați din nou.",
|
||||
"saveAborted": "Salvare întreruptă. Numele fișierului nu corespunde fișierului activ.",
|
||||
"saveDisabled": "Salvarea fișierelor partajate nu este acceptată.",
|
||||
"saveAbortedMessage": "Operațiunea de salvare a fost întreruptă. Fișierul activ al aplicației ({activeFile}) nu corespunde cu fișierul pe care încercați să îl salvați ({tryingToSave}).",
|
||||
"syncFailed": "nu a reușit să sincronizeze starea cu ruta pentru \"{filename}\" după 5 încercări. Întreruperea configurării editorului pentru a preveni coruperea datelor."
|
||||
},
|
||||
"player": {
|
||||
"LoopEnabled": "Bucla activată",
|
||||
"LoopDisabled": "Bucla dezactivată"
|
||||
"saveAbortedMessage": "Save operation aborted. The application's active file ({activeFile}) does not match the file you are trying to save ({tryingToSave}).",
|
||||
"syncFailed": "failed to sync state with the route for \"{filename}\" after 5 attempts. Aborting editor setup to prevent data corruption."
|
||||
}
|
||||
}
|
||||
+30
-67
@@ -49,8 +49,7 @@
|
||||
"clearCompleted": "Очистка завершена",
|
||||
"showMore": "Показать еще",
|
||||
"showLess": "Показать меньше",
|
||||
"openParentFolder": "Откройте родительскую папку",
|
||||
"selectedCount": "Количество элементов, выбранных для выполнения действия"
|
||||
"openParentFolder": "Откройте родительскую папку"
|
||||
},
|
||||
"download": {
|
||||
"downloadFile": "Скачать файл",
|
||||
@@ -85,15 +84,15 @@
|
||||
"click": "выбрать файл или каталог",
|
||||
"ctrl": {
|
||||
"click": "выбрать несколько файлов или каталогов",
|
||||
"d": "скачать выбранные элементы"
|
||||
"f": "открыть поиск",
|
||||
"s": "скачать файл или текущий каталог"
|
||||
},
|
||||
"del": "удалить выбранные элементы",
|
||||
"doubleClick": "открыть файл или каталог",
|
||||
"esc": "очистить выделение и/или закрыть окно",
|
||||
"f1": "помощь",
|
||||
"f2": "переименовать файл",
|
||||
"help": "Помощь",
|
||||
"f1": "показать подсказку",
|
||||
"description": "Ниже представлены основные варианты навигации. Для получения дополнительной информации, пожалуйста, посетите",
|
||||
"wiki": "FileBrowser Quantum Wiki"
|
||||
"help": "Помощь"
|
||||
},
|
||||
"languages": {
|
||||
"he": "עברית",
|
||||
@@ -135,13 +134,16 @@
|
||||
"prompts": {
|
||||
"copy": "Копировать",
|
||||
"copyMessage": "Копировать в:",
|
||||
"deleteMessageMultiple": "Удалить эти файлы ({count})?",
|
||||
"deleteMessageSingle": "Удалить этот файл/каталог?",
|
||||
"deleteMessageShare": "Удалить этот общий файл/каталог ({path})?",
|
||||
"deleteTitle": "Удалить файлы",
|
||||
"displayName": "Отображаемое имя:",
|
||||
"download": "Скачать файлы",
|
||||
"downloadMessage": "Выберите формат в котором хотите скачать.",
|
||||
"error": "Ошибка",
|
||||
"fileInfo": "Информация о файле",
|
||||
"lastModified": "Последнее изменение{suffix}",
|
||||
"move": "Переместить",
|
||||
"moveMessage": "Переместить в:",
|
||||
"newArchetype": "Создайте новую запись на основе архетипа. Файл будет создан в каталоге.",
|
||||
@@ -149,7 +151,10 @@
|
||||
"newDirMessage": "Имя нового каталога.",
|
||||
"newFile": "Новый файл",
|
||||
"newFileMessage": "Имя нового файла.",
|
||||
"numberDirs": "Количество каталогов{suffix}",
|
||||
"numberFiles": "Количество файлов{suffix}",
|
||||
"rename": "Переименовать",
|
||||
"renameMessage": "Новое имя",
|
||||
"replace": "Заменить",
|
||||
"replaceMessage": "Имя одного из загружаемых файлов совпадает с уже существующим файлом. Вы хотите заменить существующий?\n",
|
||||
"schedule": "Планировка",
|
||||
@@ -167,6 +172,10 @@
|
||||
"dragAndDrop": "Перетащите файлы сюда",
|
||||
"completed": "Завершено",
|
||||
"conflictsDetected": "Обнаруженные конфликты",
|
||||
"uploadSettingsChunked": "Max concurrent uploads: {maxConcurrentUpload}, chunk size: {uploadChunkSizeMb} MB",
|
||||
"uploadSettingsNoChunk": "Max concurrent uploads: {maxConcurrentUpload}, chunked upload disabled",
|
||||
"invalidName": "Неверное имя: не может содержать \"/\" или \"\\\".",
|
||||
"source": "Source{suffix}",
|
||||
"downloadsLimit": "Ограничение на количество загрузок",
|
||||
"maxBandwidth": "Максимальная пропускная способность загрузки (кб/с)",
|
||||
"shareTheme": "Поделиться темой",
|
||||
@@ -177,44 +186,22 @@
|
||||
"shareBanner": "Поделиться URL-адресом баннера",
|
||||
"shareFavicon": "Поделиться URL-адресом favicon",
|
||||
"optionalPasswordHelp": "Если установлен, пользователи должны ввести этот пароль для просмотра общего содержимого.",
|
||||
"viewMode": "Режим просмотра",
|
||||
"renameMessage": "Введите новое имя:",
|
||||
"renameMessageInvalid": "Файл не может содержать \"/\" или \"\\\".",
|
||||
"source": "Источник{suffix}",
|
||||
"deleteMessageMultiple": "Вы уверены, что хотите удалить файл(ы) {count}?",
|
||||
"deleteMessageShare": "Вы уверены, что хотите удалить этот ресурс: {path} ?",
|
||||
"lastModified": "Последнее изменение{suffix}",
|
||||
"numberDirs": "Количество каталогов{suffix}",
|
||||
"numberFiles": "Количество файлов{suffix}",
|
||||
"renameMessageConflict": "Элемент с именем \"{filename}\" уже существует!",
|
||||
"uploadSettingsChunked": "Максимальное количество одновременных загрузок: {maxConcurrentUpload}, размер чанка: {uploadChunkSizeMb} MB",
|
||||
"uploadSettingsNoChunk": "Максимальное количество одновременных загрузок: {maxConcurrentUpload}, загрузка в куски отключена"
|
||||
"viewMode": "Режим просмотра"
|
||||
},
|
||||
"search": {
|
||||
"images": "Изображения",
|
||||
"music": "Музыка",
|
||||
"pdf": "PDF",
|
||||
"pressToSearch": "Нажмите Enter для поиска ...",
|
||||
"search": "Поиск...",
|
||||
"typeToSearch": "Введите имя файла ...",
|
||||
"types": "Типы",
|
||||
"video": "Видео",
|
||||
"path": "Путь:",
|
||||
"smallerThan": "Меньше, чем:",
|
||||
"largerThan": "Больше, чем:",
|
||||
"helpText1": "Поиск осуществляется по каждому введенному символу (минимум 3 символа для поисковых запросов).",
|
||||
"helpText2": "Индекс: Для поиска используется индекс, который автоматически обновляется с заданным интервалом (по умолчанию: 5 минут). Поиск, когда программа только что запущена, может привести к неполным результатам.",
|
||||
"noResults": "В проиндексированном поиске результатов не найдено.",
|
||||
"onlyFolders": "Только папки",
|
||||
"onlyFiles": "Только файлы",
|
||||
"showOptions": "Показать параметры",
|
||||
"photos": "Фотографии",
|
||||
"audio": "Аудио",
|
||||
"videos": "Видео",
|
||||
"documents": "Документы",
|
||||
"archives": "Архивы",
|
||||
"number": "Номер",
|
||||
"searchContext": "Контекст поиска: {context}",
|
||||
"notEnoughCharacters": "Недостаточно символов для поиска (мин. {minSearchLength})",
|
||||
"typeToSearch": "Начните вводить {minSearchLength} или более символов, чтобы начать поиск."
|
||||
"helpText2": "Индекс: Для поиска используется индекс, который автоматически обновляется с заданным интервалом (по умолчанию: 5 минут). Поиск, когда программа только что запущена, может привести к неполным результатам."
|
||||
},
|
||||
"settings": {
|
||||
"admin": "Админ",
|
||||
@@ -227,8 +214,10 @@
|
||||
"avoidChanges": "(оставьте поле пустым, чтобы избежать изменений)",
|
||||
"branding": "Брендинг",
|
||||
"brandingDirectoryPath": "Путь к каталогу брендов",
|
||||
"brandingHelp": "Вы можете настроить внешний вид файлового браузера, изменив его имя, заменив логотип, добавив собственные стили и даже отключив внешние ссылки на GitHub.\nДополнительную информацию о персонализированном брендинге можно найти на странице {0}.",
|
||||
"changePassword": "Изменение пароля",
|
||||
"commandRunner": "Запуск команд",
|
||||
"commandRunnerHelp": "Здесь вы можете установить команды, которые будут выполняться в указанных событиях. Вы должны указать по одной команде в каждой строке. Переменные среды {0} и {1} будут доступны, будучи {0} относительно {1}. Дополнительные сведения об этой функции и доступных переменных среды см. В {2}.",
|
||||
"commandsUpdated": "Команды обновлены!",
|
||||
"createUserDir": "Автоматическое создание домашнего каталога пользователя при добавлении нового пользователя",
|
||||
"customStylesheet": "Свой стиль",
|
||||
@@ -311,14 +300,7 @@
|
||||
"adminOptions": "Параметры пользователя администратора",
|
||||
"shareDuration": "Срок действия:",
|
||||
"searchOptions": "Параметры поиска",
|
||||
"downloads": "Скачать",
|
||||
"systemAdmin": "Система и администрирование",
|
||||
"configViewer": "Программа просмотра конфигурации",
|
||||
"configViewerShowFull": "Показать полную конфигурацию",
|
||||
"configViewerShowComments": "Показать комментарии",
|
||||
"configViewerLoadConfig": "Конфигурация нагрузки",
|
||||
"brandingHelp": "Вы можете настроить внешний вид экземпляра File Browser, изменив его название, заменив логотип, добавив пользовательские стили и даже отключив внешние ссылки на GitHub.\nБолее подробную информацию о пользовательском брендинге можно найти на сайте {0}.",
|
||||
"commandRunnerHelp": "Здесь вы можете задать команды, которые будут выполняться в названных событиях. Вы должны написать по одной команде в строке. Будут доступны переменные окружения {0} и {1}, причем {0} будет относиться к {1}. Для получения дополнительной информации об этой функции и доступных переменных окружения, пожалуйста, прочтите {2}."
|
||||
"downloads": "Скачать"
|
||||
},
|
||||
"sidebar": {
|
||||
"help": "Помощь",
|
||||
@@ -376,21 +358,12 @@
|
||||
"hideNavButtons": "Скрыть кнопки навигации",
|
||||
"hideNavButtonsDescription": "Спрячьте кнопки навигации на навигационной панели в ресурсе, чтобы создать минималистичный вид.",
|
||||
"viewModeDescription": "Макет по умолчанию для общей страницы.",
|
||||
"titleDefault": "Shared files - {title}",
|
||||
"descriptionDefault": "Вам отправлена акция для просмотра или загрузки.",
|
||||
"disableShareCard": "Отключение карты доступа",
|
||||
"disableShareCardDescription": "Отключите карточку общего доступа на странице общего доступа в боковой панели или в верхней части страницы на мобильных устройствах.",
|
||||
"disableSidebar": "Отключить боковую панель",
|
||||
"disableSidebarDescription": "Отключите боковую панель на общей странице.",
|
||||
"enforceDarkLightMode": "Включение режима темы",
|
||||
"enforceDarkLightModeDescription": "Принудительно устанавливает определенный режим темы (темный или светлый) для этого ресурса, отменяя предпочтения пользователя.",
|
||||
"default": "Не вводите режим",
|
||||
"dark": "Темнота",
|
||||
"light": "Свет",
|
||||
"enableOnlyOffice": "Включите программу просмотра OnlyOffice",
|
||||
"enableOnlyOfficeDescription": "Разрешите просмотр офисных файлов с помощью OnlyOffice в этом ресурсе.",
|
||||
"enableOnlyOfficeEditing": "Включить редактирование OnlyOffice",
|
||||
"enableOnlyOfficeEditingDescription": "Разрешите редактирование офисных файлов с помощью OnlyOffice в этом ресурсе.",
|
||||
"titleDefault": "Общие файлы - {title}"
|
||||
"disableSidebarDescription": "Отключите боковую панель на общей странице."
|
||||
},
|
||||
"api": {
|
||||
"title": "Ключи API:",
|
||||
@@ -474,8 +447,8 @@
|
||||
"totalDenied": "Отказано",
|
||||
"totalAllowed": "Разрешено",
|
||||
"all": "Запретить все",
|
||||
"defaultBehaviorDescription": "Здесь показано, что происходит, когда пользователь пытается получить доступ к пути, для которого не существует специальных правил доступа. 'Allow' означает, что пользователи могут получить доступ по умолчанию, 'Deny' означает, что пользователи блокируются по умолчанию, если только они не разрешены явным образом.",
|
||||
"defaultBehavior": "Действие по умолчанию{suffix}"
|
||||
"defaultBehavior": "Default action{suffix}",
|
||||
"defaultBehaviorDescription": "Здесь показано, что происходит, когда пользователь пытается получить доступ к пути, для которого не существует специальных правил доступа. 'Allow' означает, что пользователи могут получить доступ по умолчанию, 'Deny' означает, что пользователи блокируются по умолчанию, если только они не разрешены явным образом."
|
||||
},
|
||||
"fileLoading": {
|
||||
"title": "Загрузка и скачивание",
|
||||
@@ -541,24 +514,14 @@
|
||||
"defaultThemeDescription": "default - Тема по умолчанию",
|
||||
"customTheme": "Выберите тему",
|
||||
"showSelectMultiple": "Отображение выбора нескольких в контекстном меню на рабочем столе",
|
||||
"showSelectMultipleDescription": "По умолчанию в контекстном меню рабочего стола нет опции \"выбрать несколько\", как в мобильном. Эта опция всегда отображается в контекстном меню.",
|
||||
"defaultMediaPlayer": "Используйте встроенный медиаплеер вашего браузера",
|
||||
"defaultMediaPlayerDescription": "Для воспроизведения видео- и аудиофайлов используйте штатный медиаплеер браузера, а не медиаплеер 'vue-plyr', входящий в комплект поставки FileBrowser.",
|
||||
"debugOfficeEditor": "Включить режим отладки OnlyOffice",
|
||||
"debugOfficeEditorDescription": "Отображение всплывающего окна отладки с дополнительной информацией в редакторе OnlyOffice.",
|
||||
"fileViewerOptions": "Параметры просмотра файлов",
|
||||
"themeAndLanguage": "Тема и язык"
|
||||
"showSelectMultipleDescription": "По умолчанию в контекстном меню рабочего стола нет опции \"выбрать несколько\", как в мобильном. Эта опция всегда отображается в контекстном меню."
|
||||
},
|
||||
"editor": {
|
||||
"uninitialized": "Не удалось инициализировать редактор. Пожалуйста, перезагрузитесь.",
|
||||
"saveFailed": "Не удалось сохранить файл. Пожалуйста, попробуйте еще раз.",
|
||||
"saveAborted": "Сохранение прервано. Имя файла не совпадает с именем активного файла.",
|
||||
"saveDisabled": "Сохранение общих файлов не поддерживается.",
|
||||
"saveAbortedMessage": "Операция сохранения прервана. Активный файл приложения ({activeFile}) не совпадает с файлом, который вы пытаетесь сохранить ({tryingToSave}).",
|
||||
"syncFailed": "не удалось синхронизировать состояние с маршрутом для \"{filename}\" после 5 попыток. Прерываю настройку редактора, чтобы предотвратить повреждение данных."
|
||||
},
|
||||
"player": {
|
||||
"LoopEnabled": "Петля включена",
|
||||
"LoopDisabled": "Петля отключена"
|
||||
"saveAbortedMessage": "Save operation aborted. The application's active file ({activeFile}) does not match the file you are trying to save ({tryingToSave}).",
|
||||
"syncFailed": "failed to sync state with the route for \"{filename}\" after 5 attempts. Aborting editor setup to prevent data corruption."
|
||||
}
|
||||
}
|
||||
Loaded 100 of 134 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user