Beta/v0.8.11 (#1453)
Co-authored-by: Kevin B. <kurami32_kbm@proton.me> Co-authored-by: Srinivas A <56465971+srini-abhiram@users.noreply.github.com> Co-authored-by: Chanwoong Kim <me@chanwoong.kim> Co-authored-by: fox34 <github@noecho.de> Co-authored-by: Pyro <pyrodd@gmail.com> Co-authored-by: Noé Larrieu-Lacoste <noelarrieulacoste@yahoo.fr>
This commit is contained in:
138 files changed
+4010
-592
No files matched your search
+3
-2
@@ -3,7 +3,8 @@ backend/tmp
|
||||
backend/dist
|
||||
backend/backend*
|
||||
backend/http/dist/**
|
||||
backend/http/embed/*()
|
||||
backend/vendor/**
|
||||
|
||||
frontend/dist/**
|
||||
frontend/node_modules/**
|
||||
frontend/node_modules/**
|
||||
frontend/pkg/**
|
||||
+4
-1
@@ -10,11 +10,14 @@ rice-box.go
|
||||
/backend/filebrowser.exe
|
||||
frontend/tsconfig.tsbuildinfo
|
||||
/backend/backend.exe
|
||||
/backend/playwright-files.db
|
||||
/backend/tmp
|
||||
/backend/dist
|
||||
/frontend/dist
|
||||
/frontend/pkg
|
||||
/frontend/test-results
|
||||
/frontend/playwright-files
|
||||
/frontend/screenshots
|
||||
/frontend/tests/playwright-files/demo*
|
||||
/frontend/loginAuth.json
|
||||
/frontend/package-lock.json
|
||||
@@ -24,7 +27,7 @@ frontend/tsconfig.tsbuildinfo
|
||||
/backend/generated.yaml
|
||||
/backend/srv
|
||||
/backend/http/dist
|
||||
/backend/http/embed/*
|
||||
/backend/http/embed
|
||||
|
||||
.DS_Store
|
||||
node_modules
|
||||
|
||||
@@ -2,6 +2,33 @@
|
||||
|
||||
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.11-beta
|
||||
|
||||
**New Features**:
|
||||
- OnlyOffice debugger now shows backend logs as well for admin users.
|
||||
- If proxy auth based `username` equals `auth.adminUsername`, the user will be promoted to admin.
|
||||
- Guard against accidently cancelling uploads #1419
|
||||
- Added a quirky halloween theme that automatically applies on october 31st -- you can disable this from happening by setting `frontend.styling.disableEventThemes`
|
||||
|
||||
**Notes**:
|
||||
- Removed share setting `enableOnlyOfficeEditing` -- uses value from `allowEditing`
|
||||
- Anonymous users can now edit if a share has edit permissions enabled.
|
||||
- No "Incompatible user settings detected" message if its first setup
|
||||
- cookie handling revamped
|
||||
- fully backend managed
|
||||
- `auth` cookie name changed to `filebrowser_quantum_jwt`
|
||||
- auth cookie for password users no longer session based #1439
|
||||
- removed state jwt state variable -- 100% cookie based.
|
||||
|
||||
**BugFixes**:
|
||||
- Fixed issue editing onlyoffice on shares #1397
|
||||
- Added proper and robust logging / error handling for the onlyoffice callback. #1422 #1068
|
||||
- If a file was moved/renamed/deleted onlyoffice would re-save the original file without error, now it gives error the file doesn't exist anymore.
|
||||
- OnlyOffice integration does not work behind proxy authentication #1422
|
||||
- proxy-based authentication with docker does not work unless set up with password-based login first #1226
|
||||
- Select all bug on safari mobile #1421
|
||||
- Dragging a item into itself #1446
|
||||
|
||||
## v0.8.10-beta
|
||||
|
||||
**New Features**:
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
[](https://github.com/gtsteffaniak/filebrowser/wiki/Q&A#is-there-a-way-to-donate-or-support-this-project)
|
||||
|
||||
<img width="150" src="https://github.com/user-attachments/assets/59986a2a-f960-4536-aa35-4a9a7c98ad48" title="Logo">
|
||||
<img width="150" src="https://github.com/user-attachments/assets/c40b22c9-33da-47b7-bc4c-ce69bb5cc174" title="Logo">
|
||||
<h3>FileBrowser Quantum</h3>
|
||||
The best free self-hosted web-based file manager.
|
||||
<br/><br/>
|
||||
@@ -20,7 +20,7 @@
|
||||
|
||||
## Pinned
|
||||
|
||||
:loudspeaker: [Stable Release & 0.9.0 Update](https://github.com/gtsteffaniak/filebrowser/discussions/1293)
|
||||
:loudspeaker: [Stable Release & v1.0.0 Update](https://github.com/gtsteffaniak/filebrowser/discussions/1293)
|
||||
|
||||
:pushpin: [Read The Official Docs](https://filebrowserquantum.com/) (currently english-only)
|
||||
|
||||
@@ -98,8 +98,7 @@ a pop-up menu.
|
||||
See the [Official Docs](https://filebrowserquantum.com/). Contributions are welcome and encouraged! See [FilebrowserDocs Github](https://github.com/quantumx-apps/filebrowserDocs).
|
||||
|
||||
## Comparison Chart
|
||||
|
||||
Application Name | <img width="48" src="https://github.com/user-attachments/assets/59986a2a-f960-4536-aa35-4a9a7c98ad48" > Quantum | <img width="48" src="https://github.com/filebrowser/filebrowser/blob/master/frontend/public/img/logo.svg" > Filebrowser | <img width="48" src="https://github.com/mickael-kerjean/filestash/blob/master/public/assets/logo/app_icon.png?raw=true" > Filestash | <img width="48" src="https://avatars.githubusercontent.com/u/19211038?s=200&v=4" > Nextcloud | <img width="48" src="https://upload.wikimedia.org/wikipedia/commons/thumb/d/da/Google_Drive_logo.png/480px-Google_Drive_logo.png" > Google_Drive | <img width="48" src="https://avatars.githubusercontent.com/u/6422152?v=4" > FileRun
|
||||
Application Name | <img width="48" src="https://github.com/user-attachments/assets/c40b22c9-33da-47b7-bc4c-ce69bb5cc174" > Quantum | <img width="48" src="https://github.com/filebrowser/filebrowser/blob/master/frontend/public/img/logo.svg" > Filebrowser | <img width="48" src="https://github.com/mickael-kerjean/filestash/blob/master/public/assets/logo/app_icon.png?raw=true" > Filestash | <img width="48" src="https://avatars.githubusercontent.com/u/19211038?s=200&v=4" > Nextcloud | <img width="48" src="https://upload.wikimedia.org/wikipedia/commons/thumb/d/da/Google_Drive_logo.png/480px-Google_Drive_logo.png" > Google_Drive | <img width="48" src="https://avatars.githubusercontent.com/u/6422152?v=4" > FileRun
|
||||
--- | --- | --- | --- | --- | --- | --- |
|
||||
Filesystem support | ✅ | ✅ | ✅ | ❌ | ❌ | ✅ |
|
||||
Linux | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ |
|
||||
|
||||
@@ -4,6 +4,12 @@ frontend:
|
||||
<<: *frontend-main
|
||||
|
||||
userDefaults:
|
||||
preview:
|
||||
image: true
|
||||
popup: true
|
||||
video: true
|
||||
office: true
|
||||
highQuality: true
|
||||
permissions:
|
||||
download: true
|
||||
create: true
|
||||
@@ -10,7 +10,7 @@ import { defineConfig, devices } from "@playwright/test";
|
||||
* See https://playwright.dev/docs/test-configuration.
|
||||
*/
|
||||
export default defineConfig({
|
||||
globalSetup: "./tests/playwright/local-setup",
|
||||
globalSetup: "./tests/playwright/no-config-setup",
|
||||
timeout: 5000,
|
||||
testDir: "./tests/playwright/no-config",
|
||||
/* Run tests in files in parallel */
|
||||
|
||||
@@ -42,6 +42,12 @@ userDefaults:
|
||||
realtime: true
|
||||
|
||||
userDefaults:
|
||||
preview:
|
||||
image: true
|
||||
popup: true
|
||||
video: true
|
||||
office: true
|
||||
highQuality: true
|
||||
permissions:
|
||||
download: true
|
||||
create: true
|
||||
@@ -28,6 +28,12 @@ auth:
|
||||
adminGroup: "Admins"
|
||||
|
||||
userDefaults:
|
||||
preview:
|
||||
image: true
|
||||
popup: true
|
||||
video: true
|
||||
office: true
|
||||
highQuality: true
|
||||
darkMode: true
|
||||
disableSettings: false
|
||||
singleClick: false
|
||||
|
||||
@@ -28,6 +28,12 @@ auth:
|
||||
logoutRedirectUrl: "https://localhost/api/auth/logout2"
|
||||
|
||||
userDefaults:
|
||||
preview:
|
||||
image: true
|
||||
popup: true
|
||||
video: true
|
||||
office: true
|
||||
highQuality: true
|
||||
darkMode: true
|
||||
disableSettings: false
|
||||
singleClick: false
|
||||
|
||||
@@ -13,6 +13,7 @@ server:
|
||||
config:
|
||||
private: true
|
||||
defaultEnabled: true
|
||||
|
||||
frontend:
|
||||
name: "Graham's Filebrowser"
|
||||
disableDefaultLinks: true
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
FROM gtstef/ffmpeg:8.0-decode AS ffmpeg
|
||||
FROM golang:alpine AS base
|
||||
ARG VERSION
|
||||
ARG REVISION
|
||||
WORKDIR /app
|
||||
COPY ./backend ./
|
||||
RUN apk update && apk add --no-cache gcc musl-dev upx
|
||||
ENV CGO_ENABLED=1
|
||||
ENV GOEXPERIMENT=greenteagc
|
||||
RUN go build -tags mupdf,musl -ldflags="-w -s \
|
||||
-X 'github.com/gtsteffaniak/filebrowser/backend/common/version.Version=${VERSION}' \
|
||||
-X 'github.com/gtsteffaniak/filebrowser/backend/common/version.CommitSHA=${REVISION}'" \
|
||||
-o filebrowser .
|
||||
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
|
||||
COPY --from=ffmpeg [ "/ffmpeg", "/ffprobe", "/usr/local/bin/" ]
|
||||
ENV FILEBROWSER_FFMPEG_PATH="/usr/local/bin/"
|
||||
ENV FILEBROWSER_DATABASE="/home/filebrowser/data/database.db"
|
||||
ENV PATH="$PATH:/home/filebrowser"
|
||||
RUN apk --no-cache add ca-certificates mailcap tzdata curl
|
||||
RUN adduser -D -s /bin/true -u 1000 filebrowser
|
||||
USER filebrowser
|
||||
WORKDIR /home/filebrowser
|
||||
# copy config to two locations for compatibility
|
||||
COPY --from=base --chown=filebrowser:1000 [ "/app/filebrowser", "/app/config.yaml", "./" ]
|
||||
COPY --from=base --chown=filebrowser:1000 [ "/app/config.yaml", "./data/config.yaml" ]
|
||||
COPY --from=base --chown=filebrowser:1000 [ "/app/reduce-rounded-corners.css", "./" ]
|
||||
COPY --from=nbuild --chown=filebrowser:1000 [ "/app/dist/", "./http/dist/" ]
|
||||
## sanity checks
|
||||
RUN [ "filebrowser", "version" ]
|
||||
RUN [ "ffmpeg", "-version" ]
|
||||
RUN [ "ffprobe", "-version" ]
|
||||
USER root
|
||||
# exposing default port for auto discovery.
|
||||
EXPOSE 80
|
||||
HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \
|
||||
CMD curl -f http://localhost:80/health || exit 1
|
||||
ENTRYPOINT [ "./filebrowser" ]
|
||||
@@ -0,0 +1,4 @@
|
||||
FROM node:22-slim
|
||||
WORKDIR /app/frontend
|
||||
RUN npm i @playwright/test oauth2-mock-server @types/node
|
||||
RUN npx playwright install --with-deps firefox
|
||||
@@ -0,0 +1,19 @@
|
||||
FROM ghcr.io/gtsteffaniak/playwright-base:latest
|
||||
WORKDIR /app
|
||||
|
||||
# Copy setup scripts first to leverage caching
|
||||
COPY [ "./_docker/src/general/", "./" ]
|
||||
|
||||
WORKDIR /app/frontend
|
||||
COPY [ "./_docker/src/general/frontend/", "./" ]
|
||||
COPY [ "./frontend/tests/", "./tests" ]
|
||||
|
||||
WORKDIR /app/backend/
|
||||
COPY [ "./backend/filebrowser*", "./"]
|
||||
COPY [ "./backend/reduce-rounded-corners.css", "./no-rounded.css" ]
|
||||
RUN mkdir -p ./http/dist
|
||||
COPY [ "./backend/http/embed/", "./http/dist/"]
|
||||
|
||||
# Run tests
|
||||
RUN ./filebrowser set -u testuser1,testuser1
|
||||
RUN ./filebrowser & sleep 2 && cd ../frontend && npx playwright test
|
||||
@@ -0,0 +1,17 @@
|
||||
FROM ghcr.io/gtsteffaniak/playwright-base:latest
|
||||
WORKDIR /app
|
||||
|
||||
# Copy setup scripts first to leverage caching
|
||||
COPY [ "./_docker/src/no-config/", "./" ]
|
||||
|
||||
WORKDIR /app/frontend
|
||||
COPY [ "./_docker/src/no-config/frontend/", "./" ]
|
||||
COPY [ "./frontend/tests/", "./tests" ]
|
||||
|
||||
WORKDIR /app/backend/
|
||||
COPY [ "./backend/filebrowser*", "./"]
|
||||
RUN mkdir -p ./http/dist
|
||||
COPY [ "./backend/http/embed/", "./http/dist/"]
|
||||
|
||||
# Run tests
|
||||
RUN ./filebrowser & sleep 2 && cd ../frontend && npx playwright test
|
||||
@@ -0,0 +1,17 @@
|
||||
FROM ghcr.io/gtsteffaniak/playwright-base:latest
|
||||
WORKDIR /app
|
||||
|
||||
# Copy setup scripts first to leverage caching
|
||||
COPY [ "./_docker/src/noauth/", "./" ]
|
||||
|
||||
WORKDIR /app/frontend
|
||||
COPY [ "./frontend/tests", "./tests" ]
|
||||
COPY [ "./frontend/tests/playwright-files", "./tests/playwright-files2" ]
|
||||
|
||||
WORKDIR /app/backend/
|
||||
COPY [ "./backend/filebrowser", "./"]
|
||||
RUN mkdir -p ./http/dist
|
||||
COPY [ "./backend/http/embed/", "./http/dist/"]
|
||||
|
||||
# Run tests
|
||||
#RUN ./filebrowser & sleep 2 && cd ../frontend && npx playwright test
|
||||
@@ -0,0 +1,13 @@
|
||||
FROM ghcr.io/gtsteffaniak/playwright-base:latest
|
||||
|
||||
COPY [ "./_docker/src/oidc/frontend/", "./" ]
|
||||
COPY [ "./frontend/tests/", "./tests" ]
|
||||
|
||||
WORKDIR /app/backend/
|
||||
RUN mkdir -p ./http/dist
|
||||
COPY [ "./_docker/src/oidc/backend/", "./"]
|
||||
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
|
||||
@@ -0,0 +1,14 @@
|
||||
FROM ghcr.io/gtsteffaniak/playwright-base:latest
|
||||
WORKDIR /app
|
||||
COPY [ "./_docker/src/proxy/", "./" ]
|
||||
WORKDIR /app/frontend
|
||||
COPY [ "./frontend/tests", "./tests" ]
|
||||
WORKDIR /app/backend/
|
||||
COPY [ "./backend/filebrowser", "./"]
|
||||
RUN mkdir -p ./http/dist
|
||||
COPY [ "./backend/http/embed/", "./http/dist/"]
|
||||
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
|
||||
@@ -0,0 +1,17 @@
|
||||
FROM ghcr.io/gtsteffaniak/playwright-base:latest
|
||||
WORKDIR /app
|
||||
|
||||
# Copy setup scripts first to leverage caching
|
||||
COPY [ "./_docker/src/no-config/", "./" ]
|
||||
|
||||
WORKDIR /app/frontend
|
||||
COPY [ "./_docker/src/no-config/frontend/", "./" ]
|
||||
COPY [ "./frontend/tests/", "./tests" ]
|
||||
|
||||
WORKDIR /app/backend/
|
||||
COPY [ "./backend/filebrowser*", "./"]
|
||||
RUN mkdir -p ./http/dist
|
||||
COPY [ "./backend/http/embed/", "./http/dist/"]
|
||||
|
||||
# Run tests
|
||||
RUN ./filebrowser & sleep 2 && cd ../frontend && npx playwright test
|
||||
@@ -0,0 +1,17 @@
|
||||
FROM ghcr.io/gtsteffaniak/playwright-base:latest
|
||||
WORKDIR /app
|
||||
|
||||
# Copy setup scripts first to leverage caching
|
||||
COPY [ "./_docker/src/sharing/", "./" ]
|
||||
|
||||
WORKDIR /app/frontend
|
||||
COPY [ "./_docker/src/sharing/frontend/", "./" ]
|
||||
COPY [ "./frontend/tests/", "./tests" ]
|
||||
|
||||
WORKDIR /app/backend/
|
||||
COPY [ "./backend/filebrowser*", "./"]
|
||||
RUN mkdir -p ./http/dist
|
||||
COPY [ "./backend/http/embed/", "./http/dist/"]
|
||||
|
||||
# Run tests
|
||||
RUN ./filebrowser & sleep 2 && cd ../frontend && npx playwright test
|
||||
@@ -0,0 +1 @@
|
||||
FROM ghcr.io/gtsteffaniak/playwright-base:latest
|
||||
@@ -0,0 +1,42 @@
|
||||
FROM golang:alpine AS base
|
||||
ARG VERSION
|
||||
ARG REVISION
|
||||
WORKDIR /app
|
||||
COPY ./backend ./
|
||||
RUN apk update && apk add upx
|
||||
ENV GOEXPERIMENT=greenteagc
|
||||
RUN go build -ldflags="-w -s \
|
||||
-X 'github.com/gtsteffaniak/filebrowser/backend/common/version.Version=${VERSION}' \
|
||||
-X 'github.com/gtsteffaniak/filebrowser/backend/common/version.CommitSHA=${REVISION}'" \
|
||||
-o filebrowser .
|
||||
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_DATABASE="/home/filebrowser/data/database.db"
|
||||
ENV PATH="$PATH:/home/filebrowser"
|
||||
RUN apk --no-cache add ca-certificates mailcap tzdata curl
|
||||
RUN adduser -D -s /bin/true -u 1000 filebrowser
|
||||
USER filebrowser
|
||||
WORKDIR /home/filebrowser
|
||||
COPY --from=base --chown=filebrowser:1000 [ "/app/filebrowser", "./" ]
|
||||
COPY --from=base --chown=filebrowser:1000 [ "/app/config.yaml", "./data/config.yaml" ]
|
||||
COPY --from=base --chown=filebrowser:1000 [ "/app/reduce-rounded-corners.css", "./" ]
|
||||
COPY --from=nbuild --chown=filebrowser:1000 [ "/app/dist/", "./http/dist/" ]
|
||||
## sanity checks
|
||||
RUN [ "filebrowser", "version" ]
|
||||
USER root
|
||||
# exposing default port for auto discovery.
|
||||
EXPOSE 80
|
||||
HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \
|
||||
CMD curl -f http://localhost:80/health || exit 1
|
||||
ENTRYPOINT [ "./filebrowser" ]
|
||||
@@ -0,0 +1,17 @@
|
||||
services:
|
||||
nginx-proxy-auth:
|
||||
image: nginx
|
||||
container_name: nginx-proxy-auth
|
||||
ports:
|
||||
- "80:80"
|
||||
volumes:
|
||||
- ./src/proxy/backend/default.conf:/etc/nginx/conf.d/default.conf
|
||||
- ./src/proxy/backend/.htpasswd:/etc/nginx/conf.d/.htpasswd
|
||||
filebrowser:
|
||||
hostname: filebrowser
|
||||
volumes:
|
||||
- '../frontend:/home/frontend'
|
||||
- "./src/proxy/backend/config.yaml:/home/filebrowser/config.yaml"
|
||||
build:
|
||||
context: ../
|
||||
dockerfile: ./_docker/Dockerfile.slim
|
||||
@@ -0,0 +1,15 @@
|
||||
server:
|
||||
<<: *server-main
|
||||
frontend:
|
||||
<<: *frontend-main
|
||||
|
||||
userDefaults:
|
||||
preview:
|
||||
image: true
|
||||
popup: true
|
||||
video: true
|
||||
office: true
|
||||
highQuality: true
|
||||
permissions:
|
||||
download: true
|
||||
create: true
|
||||
@@ -0,0 +1,12 @@
|
||||
frontend-main: &frontend-main
|
||||
name: "Graham's Filebrowser"
|
||||
disableDefaultLinks: true
|
||||
styling:
|
||||
customThemes:
|
||||
no-rounded:
|
||||
description: "No rounded corners" # The description of the theme to display in the UI.
|
||||
css: "no-rounded.css" # The css file path and filename to use for the theme.
|
||||
externalLinks:
|
||||
- text: "A playwright test"
|
||||
url: "https://playwright.dev/"
|
||||
title: "Playwright"
|
||||
@@ -0,0 +1,15 @@
|
||||
server-main: &server-main
|
||||
port: 80
|
||||
baseURL: "/"
|
||||
logging:
|
||||
- levels: "info|error|debug"
|
||||
sources:
|
||||
- path: "../frontend/tests/playwright-files"
|
||||
name: "playwright + files"
|
||||
config:
|
||||
defaultEnabled: true
|
||||
- path: "."
|
||||
name: "docker"
|
||||
config:
|
||||
disableIndexing: true
|
||||
defaultEnabled: true
|
||||
@@ -0,0 +1,47 @@
|
||||
import { defineConfig, devices } from "@playwright/test";
|
||||
|
||||
/**
|
||||
* Read environment variables from file.
|
||||
* https://github.com/motdotla/dotenv
|
||||
*/
|
||||
// require('dotenv').config();
|
||||
|
||||
/**
|
||||
* See https://playwright.dev/docs/test-configuration.
|
||||
*/
|
||||
export default defineConfig({
|
||||
globalSetup: "./tests/playwright/global-setup",
|
||||
timeout: 10000,
|
||||
testDir: "./tests/playwright/general",
|
||||
/* Run tests in files in parallel */
|
||||
fullyParallel: false,
|
||||
/* Fail the build on CI if you accidentally left test.only in the source code. */
|
||||
forbidOnly: false,
|
||||
/* Retry on CI only */
|
||||
retries: 2,
|
||||
/* Opt out of parallel tests on CI. */
|
||||
workers: 1,
|
||||
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
|
||||
reporter: "line",
|
||||
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
|
||||
use: {
|
||||
actionTimeout: 5000,
|
||||
storageState: "loginAuth.json",
|
||||
/* Base URL to use in actions like `await page.goto('/')`. */
|
||||
baseURL: "http://127.0.0.1/",
|
||||
|
||||
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
|
||||
trace: "on-first-retry",
|
||||
|
||||
/* Set default locale to English (US) */
|
||||
locale: "en-US",
|
||||
},
|
||||
|
||||
/* Configure projects for major browsers */
|
||||
projects: [
|
||||
{
|
||||
name: "firefox",
|
||||
use: { ...devices["Desktop Firefox"] },
|
||||
},
|
||||
],
|
||||
});
|
||||
@@ -0,0 +1,47 @@
|
||||
import { defineConfig, devices } from "@playwright/test";
|
||||
|
||||
/**
|
||||
* Read environment variables from file.
|
||||
* https://github.com/motdotla/dotenv
|
||||
*/
|
||||
// require('dotenv').config();
|
||||
|
||||
/**
|
||||
* See https://playwright.dev/docs/test-configuration.
|
||||
*/
|
||||
export default defineConfig({
|
||||
globalSetup: "./tests/playwright/no-config-setup",
|
||||
timeout: 5000,
|
||||
testDir: "./tests/playwright/no-config",
|
||||
/* Run tests in files in parallel */
|
||||
fullyParallel: false,
|
||||
/* Fail the build on CI if you accidentally left test.only in the source code. */
|
||||
forbidOnly: false,
|
||||
/* Retry on CI only */
|
||||
retries: 2,
|
||||
/* Opt out of parallel tests on CI. */
|
||||
workers: 1,
|
||||
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
|
||||
reporter: "line",
|
||||
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
|
||||
use: {
|
||||
actionTimeout: 5000,
|
||||
storageState: "loginAuth.json",
|
||||
/* Base URL to use in actions like `await page.goto('/')`. */
|
||||
baseURL: "http://127.0.0.1/",
|
||||
|
||||
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
|
||||
trace: "on-first-retry",
|
||||
|
||||
/* Set default locale to English (US) */
|
||||
locale: "en-US",
|
||||
},
|
||||
|
||||
/* Configure projects for major browsers */
|
||||
projects: [
|
||||
{
|
||||
name: "firefox",
|
||||
use: { ...devices["Desktop Firefox"] },
|
||||
},
|
||||
],
|
||||
});
|
||||
@@ -0,0 +1,53 @@
|
||||
server:
|
||||
port: 80
|
||||
baseURL: "/files/"
|
||||
logging:
|
||||
- levels: "info|error|debug"
|
||||
sources:
|
||||
- path: "../frontend/tests/playwright-files"
|
||||
name: "exclude"
|
||||
config:
|
||||
defaultEnabled: true
|
||||
conditionals:
|
||||
rules:
|
||||
- folderPath: "excluded"
|
||||
- folderPath: "excludedButVisible"
|
||||
viewable: true
|
||||
- path: "../frontend/tests/playwright-files2"
|
||||
name: "include"
|
||||
config:
|
||||
defaultEnabled: true
|
||||
conditionals:
|
||||
rules:
|
||||
- includeRootItem: "myfolder"
|
||||
- includeRootItem: "subfolderExclusions"
|
||||
- filePath: "/subfolderExclusions/exclusionlist.sh"
|
||||
- folderPath: "/subfolderExclusions/folderItem"
|
||||
viewable: true
|
||||
- fileStartsWith: "startsWith"
|
||||
- folderEndsWith: "Folder"
|
||||
|
||||
auth:
|
||||
methods:
|
||||
noauth: true
|
||||
frontend:
|
||||
name: "Graham's Filebrowser"
|
||||
disableDefaultLinks: true
|
||||
externalLinks:
|
||||
- text: "A playwright test"
|
||||
url: "https://playwright.dev/"
|
||||
title: "Playwright"
|
||||
userDefaults:
|
||||
permissions:
|
||||
realtime: true
|
||||
|
||||
userDefaults:
|
||||
preview:
|
||||
image: true
|
||||
popup: true
|
||||
video: true
|
||||
office: true
|
||||
highQuality: true
|
||||
permissions:
|
||||
download: true
|
||||
create: true
|
||||
@@ -0,0 +1,48 @@
|
||||
import { defineConfig, devices } from "@playwright/test";
|
||||
|
||||
/**
|
||||
* Read environment variables from file.
|
||||
* https://github.com/motdotla/dotenv
|
||||
*/
|
||||
// require('dotenv').config();
|
||||
|
||||
/**
|
||||
* See https://playwright.dev/docs/test-configuration.
|
||||
*/
|
||||
export default defineConfig({
|
||||
globalSetup: "./tests/playwright/noauth-setup",
|
||||
timeout: 10000,
|
||||
testDir: "./tests/playwright/noauth",
|
||||
/* Run tests in files in parallel */
|
||||
fullyParallel: false,
|
||||
/* Fail the build on CI if you accidentally left test.only in the source code. */
|
||||
forbidOnly: false,
|
||||
/* Retry on CI only */
|
||||
retries: 2,
|
||||
/* Opt out of parallel tests on CI. */
|
||||
workers: 1, // required for now! todo parallel some tests
|
||||
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
|
||||
reporter: "line",
|
||||
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
|
||||
use: {
|
||||
storageState: "noauth.json",
|
||||
actionTimeout: 5000,
|
||||
//storageState: "loginAuth.json",
|
||||
/* Base URL to use in actions like `await page.goto('/')`. */
|
||||
baseURL: "http://127.0.0.1",
|
||||
|
||||
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
|
||||
trace: "on-first-retry",
|
||||
|
||||
/* Set default locale to English (US) */
|
||||
locale: "en-US",
|
||||
},
|
||||
|
||||
/* Configure projects for major browsers */
|
||||
projects: [
|
||||
{
|
||||
name: "firefox",
|
||||
use: { ...devices["Desktop Firefox"] },
|
||||
},
|
||||
],
|
||||
});
|
||||
@@ -0,0 +1,46 @@
|
||||
server:
|
||||
logging:
|
||||
- levels: "info|error|warning|debug"
|
||||
sources:
|
||||
- path: "../frontend/tests/playwright-files"
|
||||
config:
|
||||
defaultEnabled: true
|
||||
createUserDir: true
|
||||
|
||||
frontend:
|
||||
name: "Graham's Filebrowser"
|
||||
disableDefaultLinks: true
|
||||
externalLinks:
|
||||
- text: "A playwright test"
|
||||
url: "https://playwright.dev/"
|
||||
title: "Playwright"
|
||||
|
||||
auth:
|
||||
methods:
|
||||
password:
|
||||
enabled: false
|
||||
oidc:
|
||||
enabled: true
|
||||
createUser: true
|
||||
clientId: "vyBrzOmcrU3OokiWEZoObdGBscw0eYPNJauBtOJr"
|
||||
clientSecret: "gIMKbkEcckUhlGrnE2Vxi8eZhEcfxQqfwFgM0ATzk4DciHGrW2YRtQfqcpRTsmzrxEETkKvZ8yeK3sfoXnUV1IQTzqK7wxw3lEMtmeXi5clj0h7nH1rVGFq2Q3HL8zUe"
|
||||
issuerUrl: "http://localhost:8080"
|
||||
adminGroup: "Admins"
|
||||
|
||||
userDefaults:
|
||||
preview:
|
||||
image: true
|
||||
popup: true
|
||||
video: true
|
||||
office: true
|
||||
highQuality: true
|
||||
darkMode: true
|
||||
disableSettings: false
|
||||
singleClick: false
|
||||
permissions:
|
||||
admin: false
|
||||
modify: true
|
||||
share: false
|
||||
api: false
|
||||
download: true
|
||||
create: true
|
||||
@@ -0,0 +1,60 @@
|
||||
|
||||
import { OAuth2Server } from 'oauth2-mock-server';
|
||||
|
||||
// This is a list of pre-defined users. You can add more users here.
|
||||
// The filebrowser application will use this data when a user logs in.
|
||||
const users = {
|
||||
// Default user
|
||||
johndoe: {
|
||||
sub: 'johndoe',
|
||||
preferred_username: 'johndoe',
|
||||
email: 'johndoe@example.com',
|
||||
groups: ['Admins', 'Users'],
|
||||
},
|
||||
// Another example user
|
||||
testuser: {
|
||||
sub: 'testuser',
|
||||
preferred_username: 'testuser',
|
||||
email: 'testuser@example.com',
|
||||
groups: ['Users'],
|
||||
},
|
||||
};
|
||||
|
||||
const server = new OAuth2Server();
|
||||
|
||||
// Generate a new RSA key for the server.
|
||||
await server.issuer.keys.generate('RS256');
|
||||
|
||||
/**
|
||||
* This hook is called before the ID token is signed.
|
||||
* We're using it to add claims to the ID token.
|
||||
* This is the primary way the application will get user information.
|
||||
*/
|
||||
server.service.on('beforeIdTokenSigning', (token, req) => {
|
||||
// By default, the mock server uses 'johndoe' as the subject.
|
||||
const user = users[token.payload.sub] || users.johndoe;
|
||||
console.log(`[ID Token] Customizing token for user: ${user.sub}`);
|
||||
Object.assign(token.payload, user);
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* This hook is called before the userinfo response is sent.
|
||||
* We're using it to provide user claims when the application calls the /userinfo endpoint.
|
||||
*/
|
||||
server.service.on('beforeUserinfo', (userInfoResponse, req) => {
|
||||
const user = users.johndoe;
|
||||
console.log(`[UserInfo] Customizing userinfo for user: ${user.sub}`);
|
||||
userInfoResponse.body = user;
|
||||
});
|
||||
|
||||
|
||||
const port = 8080;
|
||||
const host = '0.0.0.0'; // Bind to 0.0.0.0 to be accessible within Docker
|
||||
await server.start(port, host);
|
||||
console.log(`OAuth 2 Mock Server listening on http://${host}:${port}`);
|
||||
console.log(`Issuer URL: ${server.issuer.url}`);
|
||||
console.log('---');
|
||||
console.log('Pre-defined users:');
|
||||
console.log(JSON.stringify(users, null, 2));
|
||||
console.log('---');
|
||||
@@ -0,0 +1,48 @@
|
||||
import { defineConfig, devices } from "@playwright/test";
|
||||
|
||||
/**
|
||||
* Read environment variables from file.
|
||||
* https://github.com/motdotla/dotenv
|
||||
*/
|
||||
// require('dotenv').config();
|
||||
|
||||
/**
|
||||
* See https://playwright.dev/docs/test-configuration.
|
||||
*/
|
||||
export default defineConfig({
|
||||
globalSetup: "./tests/playwright/oidc-setup",
|
||||
timeout: 5000,
|
||||
testDir: "./tests/playwright/oidc",
|
||||
/* Run tests in files in parallel */
|
||||
fullyParallel: false,
|
||||
/* Fail the build on CI if you accidentally left test.only in the source code. */
|
||||
forbidOnly: false,
|
||||
/* Retry on CI only */
|
||||
retries: 2,
|
||||
/* Opt out of parallel tests on CI. */
|
||||
workers: 1, // required for now! todo parallel some tests
|
||||
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
|
||||
reporter: "line",
|
||||
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
|
||||
use: {
|
||||
storageState: "loginAuth.json",
|
||||
actionTimeout: 5000,
|
||||
//storageState: "loginAuth.json",
|
||||
/* Base URL to use in actions like `await page.goto('/')`. */
|
||||
baseURL: "http://127.0.0.1",
|
||||
|
||||
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
|
||||
trace: "on-first-retry",
|
||||
|
||||
/* Set default locale to English (US) */
|
||||
locale: "en-US",
|
||||
},
|
||||
|
||||
/* Configure projects for major browsers */
|
||||
projects: [
|
||||
{
|
||||
name: "firefox",
|
||||
use: { ...devices["Desktop Firefox"] },
|
||||
},
|
||||
],
|
||||
});
|
||||
@@ -0,0 +1 @@
|
||||
demo-127.0.0.1:$apr1$j5FRA4Bc$RYdNFuM7YAAo9Qtie8w4b/
|
||||
@@ -0,0 +1,46 @@
|
||||
server:
|
||||
port: 8080
|
||||
baseURL: "/subpath"
|
||||
logging:
|
||||
- levels: "info|error|debug"
|
||||
sources:
|
||||
- path: "../frontend/tests/playwright-files"
|
||||
config:
|
||||
defaultEnabled: true
|
||||
createUserDir: true
|
||||
|
||||
frontend:
|
||||
name: "Graham's Filebrowser"
|
||||
disableDefaultLinks: true
|
||||
externalLinks:
|
||||
- text: "A playwright test"
|
||||
url: "https://playwright.dev/"
|
||||
title: "Playwright"
|
||||
|
||||
auth:
|
||||
methods:
|
||||
password:
|
||||
enabled: false
|
||||
proxy:
|
||||
enabled: true
|
||||
header: "X-Username"
|
||||
createUser: true
|
||||
logoutRedirectUrl: "https://localhost/api/auth/logout2"
|
||||
|
||||
userDefaults:
|
||||
preview:
|
||||
image: true
|
||||
popup: true
|
||||
video: true
|
||||
office: true
|
||||
highQuality: true
|
||||
darkMode: true
|
||||
disableSettings: false
|
||||
singleClick: false
|
||||
permissions:
|
||||
admin: false
|
||||
modify: true
|
||||
share: true
|
||||
api: false
|
||||
download: true
|
||||
create: true
|
||||
@@ -0,0 +1,32 @@
|
||||
|
||||
map $remote_addr $uuid {
|
||||
default "demo-${remote_addr}";
|
||||
}
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
server_name localhost 127.0.0.1;
|
||||
|
||||
location /subpath/public/ {
|
||||
proxy_pass http://filebrowser:8080/subpath/public/;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_set_header X-Username $uuid;
|
||||
}
|
||||
|
||||
# requires basic auth demo-127.0.0.1:SecurePass123!
|
||||
location /subpath {
|
||||
auth_basic "Restricted Access";
|
||||
auth_basic_user_file /etc/nginx/conf.d/.htpasswd;
|
||||
proxy_set_header X-Username $uuid;
|
||||
add_header X-Username $uuid;
|
||||
proxy_pass http://filebrowser:8080/subpath;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
import { defineConfig, devices } from "@playwright/test";
|
||||
|
||||
/**
|
||||
* Read environment variables from file.
|
||||
* https://github.com/motdotla/dotenv
|
||||
*/
|
||||
// require('dotenv').config();
|
||||
|
||||
/**
|
||||
* See https://playwright.dev/docs/test-configuration.
|
||||
*/
|
||||
export default defineConfig({
|
||||
globalSetup: "./tests/playwright/proxy-setup.ts",
|
||||
timeout: 5000,
|
||||
testDir: "./tests/playwright/proxy",
|
||||
/* Run tests in files in parallel */
|
||||
fullyParallel: false,
|
||||
/* Fail the build on CI if you accidentally left test.only in the source code. */
|
||||
forbidOnly: false,
|
||||
/* Retry on CI only */
|
||||
retries: 2,
|
||||
/* Opt out of parallel tests on CI. */
|
||||
workers: 1, // required for now! todo parallel some tests
|
||||
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
|
||||
reporter: "line",
|
||||
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
|
||||
use: {
|
||||
actionTimeout: 5000,
|
||||
storageState: "loginAuth.json",
|
||||
/* Base URL to use in actions like `await page.goto('/')`. */
|
||||
baseURL: "http://127.0.0.1",
|
||||
|
||||
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
|
||||
trace: "on-first-retry",
|
||||
|
||||
/* Set default locale to English (US) */
|
||||
locale: "en-US",
|
||||
},
|
||||
|
||||
/* Configure projects for major browsers */
|
||||
projects: [
|
||||
{
|
||||
name: "firefox",
|
||||
use: { ...devices["Desktop Firefox"] },
|
||||
},
|
||||
],
|
||||
});
|
||||
@@ -0,0 +1,27 @@
|
||||
server:
|
||||
port: 80
|
||||
baseURL: "/"
|
||||
logging:
|
||||
- levels: "info|error|debug"
|
||||
sources:
|
||||
- path: "../frontend/tests/playwright-files"
|
||||
name: "playwright + files"
|
||||
config:
|
||||
defaultEnabled: true
|
||||
- path: "."
|
||||
name: "docker"
|
||||
config:
|
||||
disableIndexing: true
|
||||
defaultEnabled: true
|
||||
frontend:
|
||||
name: "Graham's Filebrowser"
|
||||
disableDefaultLinks: true
|
||||
styling:
|
||||
customThemes:
|
||||
no-rounded:
|
||||
description: "No rounded corners" # The description of the theme to display in the UI.
|
||||
css: "no-rounded.css" # The css file path and filename to use for the theme.
|
||||
externalLinks:
|
||||
- text: "A playwright test"
|
||||
url: "https://playwright.dev/"
|
||||
title: "Playwright"
|
||||
@@ -0,0 +1,47 @@
|
||||
import { defineConfig, devices } from "@playwright/test";
|
||||
|
||||
/**
|
||||
* Read environment variables from file.
|
||||
* https://github.com/motdotla/dotenv
|
||||
*/
|
||||
// require('dotenv').config();
|
||||
|
||||
/**
|
||||
* See https://playwright.dev/docs/test-configuration.
|
||||
*/
|
||||
export default defineConfig({
|
||||
globalSetup: "./tests/playwright/global-setup",
|
||||
timeout: 10000,
|
||||
testDir: "./tests/playwright/settings",
|
||||
/* Run tests in files in parallel */
|
||||
fullyParallel: false,
|
||||
/* Fail the build on CI if you accidentally left test.only in the source code. */
|
||||
forbidOnly: false,
|
||||
/* Retry on CI only */
|
||||
retries: 2,
|
||||
/* Opt out of parallel tests on CI. */
|
||||
workers: 1,
|
||||
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
|
||||
reporter: "line",
|
||||
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
|
||||
use: {
|
||||
actionTimeout: 5000,
|
||||
storageState: "loginAuth.json",
|
||||
/* Base URL to use in actions like `await page.goto('/')`. */
|
||||
baseURL: "http://127.0.0.1/",
|
||||
|
||||
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
|
||||
trace: "on-first-retry",
|
||||
|
||||
/* Set default locale to English (US) */
|
||||
locale: "en-US",
|
||||
},
|
||||
|
||||
/* Configure projects for major browsers */
|
||||
projects: [
|
||||
{
|
||||
name: "firefox",
|
||||
use: { ...devices["Desktop Firefox"] },
|
||||
},
|
||||
],
|
||||
});
|
||||
@@ -0,0 +1,23 @@
|
||||
server:
|
||||
port: 80
|
||||
baseURL: "/"
|
||||
logging:
|
||||
- levels: "info|error|debug"
|
||||
sources:
|
||||
- path: "../frontend/tests/playwright-files"
|
||||
name: "playwright + files"
|
||||
config:
|
||||
defaultEnabled: true
|
||||
- path: "."
|
||||
name: "docker"
|
||||
config:
|
||||
private: true
|
||||
defaultEnabled: true
|
||||
|
||||
frontend:
|
||||
name: "Graham's Filebrowser"
|
||||
disableDefaultLinks: true
|
||||
externalLinks:
|
||||
- text: "A playwright test"
|
||||
url: "https://playwright.dev/"
|
||||
title: "Playwright"
|
||||
@@ -0,0 +1,47 @@
|
||||
import { defineConfig, devices } from "@playwright/test";
|
||||
|
||||
/**
|
||||
* Read environment variables from file.
|
||||
* https://github.com/motdotla/dotenv
|
||||
*/
|
||||
// require('dotenv').config();
|
||||
|
||||
/**
|
||||
* See https://playwright.dev/docs/test-configuration.
|
||||
*/
|
||||
export default defineConfig({
|
||||
globalSetup: "./tests/playwright/global-setup",
|
||||
timeout: 10000,
|
||||
testDir: "./tests/playwright/sharing",
|
||||
/* Run tests in files in parallel */
|
||||
fullyParallel: false,
|
||||
/* Fail the build on CI if you accidentally left test.only in the source code. */
|
||||
forbidOnly: false,
|
||||
/* Retry on CI only */
|
||||
retries: 2,
|
||||
/* Opt out of parallel tests on CI. */
|
||||
workers: 1,
|
||||
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
|
||||
reporter: "line",
|
||||
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
|
||||
use: {
|
||||
actionTimeout: 5000,
|
||||
storageState: "loginAuth.json",
|
||||
/* Base URL to use in actions like `await page.goto('/')`. */
|
||||
baseURL: "http://127.0.0.1/",
|
||||
|
||||
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
|
||||
trace: "on-first-retry",
|
||||
|
||||
/* Set default locale to English (US) */
|
||||
locale: "en-US",
|
||||
},
|
||||
|
||||
/* Configure projects for major browsers */
|
||||
projects: [
|
||||
{
|
||||
name: "firefox",
|
||||
use: { ...devices["Desktop Firefox"] },
|
||||
},
|
||||
],
|
||||
});
|
||||
@@ -420,12 +420,12 @@ func WriteDirectory(opts utils.FileOptions) error {
|
||||
return RefreshIndex(idx.Name, opts.Path, true, true)
|
||||
}
|
||||
|
||||
func WriteFile(opts utils.FileOptions, in io.Reader) error {
|
||||
idx := indexing.GetIndex(opts.Source)
|
||||
func WriteFile(source, path string, in io.Reader) error {
|
||||
idx := indexing.GetIndex(source)
|
||||
if idx == nil {
|
||||
return fmt.Errorf("could not get index: %v ", opts.Source)
|
||||
return fmt.Errorf("could not get index: %v ", source)
|
||||
}
|
||||
realPath, _, _ := idx.GetRealPath(opts.Path)
|
||||
realPath, _, _ := idx.GetRealPath(path)
|
||||
// Strip trailing slash from realPath if it's meant to be a file
|
||||
realPath = strings.TrimRight(realPath, "/")
|
||||
// Ensure the parent directories exist
|
||||
@@ -466,7 +466,7 @@ func WriteFile(opts utils.FileOptions, in io.Reader) error {
|
||||
return err
|
||||
}
|
||||
|
||||
return RefreshIndex(opts.Source, opts.Path, false, false)
|
||||
return RefreshIndex(source, path, false, false)
|
||||
}
|
||||
|
||||
// getContent reads and returns the file content if it's considered an editable text file.
|
||||
|
||||
@@ -29,14 +29,14 @@ func MoveFile(src, dst string) error {
|
||||
// fallback
|
||||
err = CopyFile(src, dst)
|
||||
if err != nil {
|
||||
logger.Errorf("CopyFile failed %v %v %v ", src, dst, err)
|
||||
logger.Errorf("CopyFile failed %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
go func() {
|
||||
err = os.RemoveAll(src)
|
||||
if err != nil {
|
||||
logger.Errorf("os.Remove failed %v %v ", src, err)
|
||||
logger.Errorf("os.Remove failed %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
|
||||
+1
-1
@@ -108,7 +108,7 @@ func StartFilebrowser() {
|
||||
for _, source := range settings.Config.Server.SourceMap {
|
||||
go indexing.Initialize(source, false)
|
||||
}
|
||||
validateUserInfo()
|
||||
validateUserInfo(!dbExists)
|
||||
validateOfficeIntegration()
|
||||
validateAccessRules()
|
||||
// Start the rootCMD in a goroutine
|
||||
|
||||
+5
-5
@@ -11,9 +11,9 @@ import (
|
||||
"github.com/gtsteffaniak/go-logger/logger"
|
||||
)
|
||||
|
||||
var createBackup = []bool{}
|
||||
var createBackup = false
|
||||
|
||||
func validateUserInfo() {
|
||||
func validateUserInfo(newDB bool) {
|
||||
// update source info for users if names/sources/paths might have changed
|
||||
usersList, err := store.Users.Gets()
|
||||
if err != nil {
|
||||
@@ -45,8 +45,8 @@ func validateUserInfo() {
|
||||
changePass = true
|
||||
}
|
||||
if updateUser {
|
||||
skipCreateBackup := os.Getenv("FILEBROWSER_DISABLE_AUTOMATIC_BACKUP") == "true"
|
||||
if len(createBackup) == 1 && !skipCreateBackup {
|
||||
skipCreateBackup := os.Getenv("FILEBROWSER_DISABLE_AUTOMATIC_BACKUP") == "true" || newDB
|
||||
if createBackup && !skipCreateBackup {
|
||||
logger.Warning("Incompatible user settings detected, creating backup of database before converting.")
|
||||
err = fileutils.CopyFile(settings.Config.Server.Database, fmt.Sprintf("%s.bak", settings.Config.Server.Database))
|
||||
if err != nil {
|
||||
@@ -157,7 +157,7 @@ func updatePermissions(user *users.User) bool {
|
||||
}
|
||||
user.Version = 1
|
||||
if updateUser {
|
||||
createBackup = append(createBackup, true)
|
||||
createBackup = true
|
||||
}
|
||||
return updateUser
|
||||
}
|
||||
|
||||
@@ -125,7 +125,6 @@ func setupFrontend(generate bool) {
|
||||
if !ok {
|
||||
addCustomTheme("default", "The default theme", "")
|
||||
}
|
||||
|
||||
// Load custom favicon if configured
|
||||
loadCustomFavicon()
|
||||
}
|
||||
@@ -816,6 +815,8 @@ func modifyExcludeInclude(config *Source) {
|
||||
// normalize names
|
||||
config.Config.Conditionals.ItemRules[i].FileNames = normalizeName(rule.FileNames)
|
||||
config.Config.Conditionals.ItemRules[i].FolderNames = normalizeName(rule.FolderNames)
|
||||
config.Config.Conditionals.ItemRules[i].FileName = normalizeName(rule.FileName)
|
||||
config.Config.Conditionals.ItemRules[i].FolderName = normalizeName(rule.FolderName)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -189,9 +189,12 @@ type ConditionalIndexConfig struct {
|
||||
FolderEndsWith string `json:"folderEndsWith"` // (global) exclude folders that end with these suffixes. Eg. ".thumbnails" or ".git"
|
||||
FolderPath string `json:"folderPath"` // (global) exclude folders that match this path. Eg. "/path/to/folder" or "/path/to/folder/subfolder"
|
||||
FilePath string `json:"filePath"` // (global) exclude files that match this path. Eg. "/path/to/file.txt" or "/path/to/file.txt/subfile.txt"
|
||||
FileNames string `json:"fileNames"` // (global) exclude files that match these names. Eg. "file.txt" or "test.csv"
|
||||
FolderNames string `json:"folderNames"` // (global) exclude folders that match these names. Eg. "folder" or "subfolder"
|
||||
Viewable bool `json:"viewable"` // Enable viewing in UI but exclude from indexing
|
||||
FileNames string `json:"fileNames"` // deprecated: exclude files that match these names. Eg. "file.txt" or "test.csv"
|
||||
FolderNames string `json:"folderNames"` // deprecated: exclude folders that match these names. Eg. "folder" or "subfolder"
|
||||
FileName string `json:"fileName"` // (global) exclude files that match these names. Eg. "file.txt" or "test.csv"
|
||||
FolderName string `json:"folderName"` // (global) exclude folders that match these names. Eg. "folder" or "subfolder"
|
||||
|
||||
Viewable bool `json:"viewable"` // Enable viewing in UI but exclude from indexing
|
||||
}
|
||||
|
||||
// ConditionalMaps provides O(1) lookup performance for conditional rules
|
||||
@@ -228,11 +231,12 @@ type Frontend struct {
|
||||
}
|
||||
|
||||
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.
|
||||
DisableEventBasedThemes bool `json:"disableEventThemes"` // disable the event based themes,
|
||||
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
|
||||
}
|
||||
|
||||
+40
-12
@@ -6,17 +6,45 @@ server:
|
||||
sources:
|
||||
- path: "/srv"
|
||||
userDefaults:
|
||||
editorQuickSave: false # show quick save button in editor
|
||||
hideSidebarFileActions: false # hide the file actions in the sidebar
|
||||
disableQuickToggles: false # disable the quick toggles in the sidebar
|
||||
disableSearchOptions: false # disable the search options in the search bar
|
||||
stickySidebar: true # keep sidebar open when navigating
|
||||
darkMode: true # should dark mode be enabled
|
||||
viewMode: "normal" # view mode to use: eg. normal, list, grid, or compact
|
||||
singleClick: false # open directory on single click, also enables middle click to open in new tab
|
||||
showHidden: false # show hidden files in the UI. On windows this includes files starting with a dot and windows hidden files
|
||||
dateFormat: false # when false, the date is relative, when true, the date is an exact timestamp
|
||||
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
|
||||
lockPassword: false # disable the user from changing their password
|
||||
disableSettings: false # disable the user from viewing the settings page
|
||||
preview:
|
||||
image: true
|
||||
popup: true
|
||||
video: false
|
||||
office: false
|
||||
highQuality: false
|
||||
darkMode: true
|
||||
disableSettings: false
|
||||
singleClick: false
|
||||
disableHideSidebar: true # disable the hide sidebar preview for previews and editors
|
||||
highQuality: true # generate high quality thumbnail preview images
|
||||
image: true # show thumbnail preview image for image files
|
||||
video: true # show thumbnail preview image for video files
|
||||
motionVideoPreview: true # show multiple frames for videos in thumbnail preview when hovering
|
||||
office: true # show thumbnail preview image for office files
|
||||
popup: true # show larger popup preview when hovering over thumbnail
|
||||
autoplayMedia: true # autoplay media files in preview
|
||||
defaultMediaPlayer: true # disable html5 media player and use the default media player
|
||||
folder: true # show thumbnail preview image for folder files
|
||||
permissions:
|
||||
admin: false
|
||||
modify: false
|
||||
share: false
|
||||
api: false
|
||||
api: false # allow api access
|
||||
admin: false # allow admin access
|
||||
modify: false # allow modifying files
|
||||
share: false # allow sharing files
|
||||
realtime: false # allow realtime updates
|
||||
delete: false # allow deleting files
|
||||
create: false # allow creating or uploading files
|
||||
download: true # allow downloading files
|
||||
disableUpdateNotifications: false # disable update notifications banner for admin users
|
||||
deleteWithoutConfirming: false # delete files without confirmation
|
||||
disableOnlyOfficeExt: ".md .txt .pdf" # list of file extensions to disable onlyoffice editor for
|
||||
showSelectMultiple: false # show select multiple files on desktop
|
||||
defaultLandingPage: "" # default landing page to use if no redirect is specified: eg. /files/mysource/mysubpath, /settings, etc.
|
||||
@@ -25,7 +25,6 @@ type CommonShare struct {
|
||||
EnforceDarkLightMode string `json:"enforceDarkLightMode,omitempty"` // "dark" or "light"
|
||||
ViewMode string `json:"viewMode,omitempty"` // default view mode for anonymous users: "list", "compact", "normal", "gallery"
|
||||
EnableOnlyOffice bool `json:"enableOnlyOffice,omitempty"`
|
||||
EnableOnlyOfficeEditing bool `json:"enableOnlyOfficeEditing,omitempty"`
|
||||
ShareType string `json:"shareType"` // type of share: normal, upload, max
|
||||
PerUserDownloadLimit bool `json:"perUserDownloadLimit,omitempty"`
|
||||
ExtractEmbeddedSubtitles bool `json:"extractEmbeddedSubtitles,omitempty"` // can be io intensive for large files and take 10-30 seconds.
|
||||
|
||||
+4
-2
@@ -14,7 +14,7 @@ require (
|
||||
github.com/google/go-cmp v0.7.0
|
||||
github.com/gtsteffaniak/go-cache v1.0.0
|
||||
github.com/gtsteffaniak/go-logger v0.2.3
|
||||
github.com/kovidgoyal/imaging v1.7.0
|
||||
github.com/kovidgoyal/imaging v1.7.2
|
||||
github.com/pquerna/otp v1.5.0
|
||||
github.com/spf13/afero v1.15.0
|
||||
github.com/stretchr/testify v1.11.1
|
||||
@@ -129,7 +129,7 @@ require (
|
||||
github.com/godoc-lint/godoc-lint v0.10.0 // indirect
|
||||
github.com/gofrs/flock v0.12.1 // indirect
|
||||
github.com/gohugoio/hugo v0.149.1 // indirect
|
||||
github.com/golang/geo v0.0.0-20251008113719-ccf458c07da6 // indirect
|
||||
github.com/golang/geo v0.0.0-20251020193347-f750d7aa221b // indirect
|
||||
github.com/golang/protobuf v1.5.3 // indirect
|
||||
github.com/golangci/asciicheck v0.5.0 // indirect
|
||||
github.com/golangci/dupl v0.0.0-20250308024227-f665c8d69b32 // indirect
|
||||
@@ -161,6 +161,7 @@ require (
|
||||
github.com/karamaru-alpha/copyloopvar v1.2.1 // indirect
|
||||
github.com/kisielk/errcheck v1.9.0 // indirect
|
||||
github.com/kkHAIKE/contextcheck v1.1.6 // indirect
|
||||
github.com/kovidgoyal/go-parallel v1.0.1 // indirect
|
||||
github.com/kulti/thelper v0.7.1 // indirect
|
||||
github.com/kunwardeep/paralleltest v1.0.14 // indirect
|
||||
github.com/lasiar/canonicalheader v1.1.2 // indirect
|
||||
@@ -210,6 +211,7 @@ 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
|
||||
|
||||
+8
-4
@@ -383,8 +383,8 @@ github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w
|
||||
github.com/golang/geo v0.0.0-20190916061304-5b978397cfec/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI=
|
||||
github.com/golang/geo v0.0.0-20200319012246-673a6f80352d/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI=
|
||||
github.com/golang/geo v0.0.0-20210211234256-740aa86cb551/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI=
|
||||
github.com/golang/geo v0.0.0-20251008113719-ccf458c07da6 h1:9GWB2BLkXw73MNn6FZZIea1ZHtrdBpffeSz0JFGnj8I=
|
||||
github.com/golang/geo v0.0.0-20251008113719-ccf458c07da6/go.mod h1:AN0OjM34c3PbjAsX+QNma1nYtJtRxl+s9MZNV7S+efw=
|
||||
github.com/golang/geo v0.0.0-20251020193347-f750d7aa221b h1:cwGXba7hinTTzhlNQW2qbBqSktSP+KSvrKPbn3d7S0g=
|
||||
github.com/golang/geo v0.0.0-20251020193347-f750d7aa221b/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=
|
||||
@@ -542,8 +542,10 @@ github.com/kkHAIKE/contextcheck v1.1.6 h1:7HIyRcnyzxL9Lz06NGhiKvenXq7Zw6Q0UQu/tt
|
||||
github.com/kkHAIKE/contextcheck v1.1.6/go.mod h1:3dDbMRNBFaq8HFXWC1JyvDSPm43CmE6IuHam8Wr0rkg=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/kovidgoyal/imaging v1.7.0 h1:SFWQPJdEuaRY7WhL+MJyoN5IvslgqU+z0iPaoGl3yuM=
|
||||
github.com/kovidgoyal/imaging v1.7.0/go.mod h1:mBprO214rATK/6OaPAUXmHbSMelPSFEmoBAt/IJdmno=
|
||||
github.com/kovidgoyal/go-parallel v1.0.1 h1:nYUjN+EdpbmQjTg3N5eTUInuXTB3/1oD2vHdaMfuHoI=
|
||||
github.com/kovidgoyal/go-parallel v1.0.1/go.mod h1:BJNIbe6+hxyFWv7n6oEDPj3PA5qSw5OCtf0hcVxWJiw=
|
||||
github.com/kovidgoyal/imaging v1.7.2 h1:mmT6k6Az3mC6dbqdZ6Q9KQCdZFWTAQ+q97NyGZgJ/2c=
|
||||
github.com/kovidgoyal/imaging v1.7.2/go.mod h1:GdkCORjfZMMGFY0Pb7TDmRhj7PDhxF/QShKukSCj0VU=
|
||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
@@ -723,6 +725,8 @@ 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=
|
||||
|
||||
+49
-3
@@ -28,7 +28,7 @@ import (
|
||||
// then checks for query parameter
|
||||
func extractToken(r *http.Request) (string, error) {
|
||||
hasToken := false
|
||||
tokenObj, err := r.Cookie("auth")
|
||||
tokenObj, err := r.Cookie("filebrowser_quantum_jwt")
|
||||
if err == nil {
|
||||
hasToken = true
|
||||
token := tokenObj.Value
|
||||
@@ -81,6 +81,9 @@ func setupProxyUser(r *http.Request, data *requestContext, proxyUser string) (*u
|
||||
Username: proxyUser,
|
||||
}
|
||||
settings.ApplyUserDefaults(&user)
|
||||
if user.Username == config.Auth.AdminUsername {
|
||||
user.Permissions.Admin = true
|
||||
}
|
||||
err = storage.CreateUser(user)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -96,6 +99,13 @@ func setupProxyUser(r *http.Request, data *requestContext, proxyUser string) (*u
|
||||
if data.user.LoginMethod != users.LoginMethodProxy {
|
||||
return nil, errors.ErrWrongLoginMethod
|
||||
}
|
||||
if data.user.Username == config.Auth.AdminUsername && !data.user.Permissions.Admin {
|
||||
data.user.Permissions.Admin = true
|
||||
err = store.Users.Update(data.user, true, "Permissions")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return data.user, nil
|
||||
}
|
||||
|
||||
@@ -110,6 +120,9 @@ func setupProxyUser(r *http.Request, data *requestContext, proxyUser string) (*u
|
||||
// @Failure 500 {object} map[string]string "Internal server error"
|
||||
// @Router /api/auth/login [post]
|
||||
func loginHandler(w http.ResponseWriter, r *http.Request, d *requestContext) (int, error) {
|
||||
if d.user.LoginMethod == users.LoginMethodProxy {
|
||||
return printToken(w, r, d.user)
|
||||
}
|
||||
passwordUser := d.user.LoginMethod == users.LoginMethodPassword
|
||||
enforcedOtp := config.Auth.Methods.PasswordAuth.EnforcedOtp
|
||||
missingOtp := d.user.TOTPSecret == ""
|
||||
@@ -129,6 +142,19 @@ func loginHandler(w http.ResponseWriter, r *http.Request, d *requestContext) (in
|
||||
// @Router /api/auth/logout [post]
|
||||
func logoutHandler(w http.ResponseWriter, r *http.Request, d *requestContext) (int, error) {
|
||||
defer auth.RevokeAPIKey(d.token)
|
||||
|
||||
// Clear the authentication cookie by setting it to expire in the past
|
||||
cookie := &http.Cookie{
|
||||
Name: "filebrowser_quantum_jwt",
|
||||
Value: "",
|
||||
Domain: strings.Split(r.Host, ":")[0],
|
||||
Path: "/",
|
||||
SameSite: http.SameSiteLaxMode,
|
||||
Expires: time.Unix(0, 0), // Expire immediately
|
||||
MaxAge: -1, // Delete cookie
|
||||
}
|
||||
http.SetCookie(w, cookie)
|
||||
|
||||
logoutUrl := fmt.Sprintf("%vlogin", config.Server.BaseURL) // Default fallback
|
||||
if d.user != nil && d.user.LoginMethod == users.LoginMethodProxy {
|
||||
proxyRedirectUrl := config.Auth.Methods.ProxyAuth.LogoutRedirectUrl
|
||||
@@ -221,14 +247,34 @@ func renewHandler(w http.ResponseWriter, r *http.Request, d *requestContext) (in
|
||||
return printToken(w, r, d.user)
|
||||
}
|
||||
|
||||
func printToken(w http.ResponseWriter, _ *http.Request, user *users.User) (int, error) {
|
||||
signed, err := makeSignedTokenAPI(user, "WEB_TOKEN_"+utils.InsecureRandomIdentifier(4), time.Hour*time.Duration(config.Auth.TokenExpirationHours), user.Permissions)
|
||||
func printToken(w http.ResponseWriter, r *http.Request, user *users.User) (int, error) {
|
||||
expires := time.Hour * time.Duration(config.Auth.TokenExpirationHours)
|
||||
signed, err := makeSignedTokenAPI(user, "WEB_TOKEN_"+utils.InsecureRandomIdentifier(4), expires, user.Permissions)
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "key already exists with same name") {
|
||||
return http.StatusConflict, err
|
||||
}
|
||||
return 401, errors.ErrUnauthorized
|
||||
}
|
||||
|
||||
// Add 30 minutes buffer so expired token doesn't get automatically deleted by the browser
|
||||
// This allows backend to identify expired sessions and provide better user feedback
|
||||
expiresTime := time.Now().Add(expires).Add(time.Minute * 30)
|
||||
|
||||
// Set the authentication token as an HTTP cookie
|
||||
cookie := &http.Cookie{
|
||||
Name: "filebrowser_quantum_jwt",
|
||||
Value: signed.Key,
|
||||
Domain: strings.Split(r.Host, ":")[0], // Set domain to the host without port
|
||||
Path: "/",
|
||||
SameSite: http.SameSiteLaxMode,
|
||||
Expires: expiresTime,
|
||||
// HttpOnly: true, // Cannot use HttpOnly since frontend needs to read cookie for renew operations
|
||||
// Secure: true, // Enable this in production with HTTPS
|
||||
}
|
||||
http.SetCookie(w, cookie)
|
||||
|
||||
// Still return token in body for backward compatibility and state management
|
||||
w.Header().Set("Content-Type", "text/plain")
|
||||
if _, err := w.Write([]byte(signed.Key)); err != nil {
|
||||
return 401, errors.ErrUnauthorized
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
*
|
||||
@@ -1,9 +1,12 @@
|
||||
package http
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/gtsteffaniak/filebrowser/backend/common/settings"
|
||||
"github.com/gtsteffaniak/filebrowser/backend/events"
|
||||
@@ -26,7 +29,7 @@ func (msgr messenger) sendEvent(eventType, message string) error {
|
||||
}
|
||||
|
||||
func sseHandler(w http.ResponseWriter, r *http.Request, d *requestContext) (int, error) {
|
||||
if !d.user.Permissions.Realtime {
|
||||
if !(d.user.Permissions.Realtime || d.user.Permissions.Admin) {
|
||||
return http.StatusForbidden, fmt.Errorf("realtime is disabled for this user")
|
||||
}
|
||||
|
||||
@@ -80,3 +83,93 @@ func sseHandler(w http.ResponseWriter, r *http.Request, d *requestContext) (int,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// OnlyOfficeLogContext stores context for OnlyOffice operations
|
||||
type OnlyOfficeLogContext struct {
|
||||
Username string
|
||||
SessionID string
|
||||
DocumentID string
|
||||
FilePath string
|
||||
Source string
|
||||
ShareHash string
|
||||
isAdmin bool
|
||||
StartTime time.Time
|
||||
}
|
||||
|
||||
// OnlyOfficeLogEvent represents a log event for SSE
|
||||
type OnlyOfficeLogEvent struct {
|
||||
EventType string `json:"eventType"`
|
||||
DocumentID string `json:"documentId"`
|
||||
Username string `json:"username"`
|
||||
SessionID string `json:"sessionId"`
|
||||
LogLevel string `json:"logLevel"`
|
||||
Message string `json:"message"`
|
||||
Timestamp string `json:"timestamp"`
|
||||
Component string `json:"component"`
|
||||
}
|
||||
|
||||
// Global map to store OnlyOffice log contexts
|
||||
var onlyOfficeContexts = make(map[string]*OnlyOfficeLogContext)
|
||||
var onlyOfficeContextsMutex sync.RWMutex
|
||||
|
||||
// Helper functions for OnlyOffice log context management
|
||||
func createOnlyOfficeLogContext(username, sessionID, documentID, filePath, source, shareHash string, isAdmin bool) *OnlyOfficeLogContext {
|
||||
return &OnlyOfficeLogContext{
|
||||
Username: username,
|
||||
SessionID: sessionID,
|
||||
DocumentID: documentID,
|
||||
FilePath: filePath,
|
||||
Source: source,
|
||||
ShareHash: shareHash,
|
||||
StartTime: time.Now(),
|
||||
isAdmin: isAdmin,
|
||||
}
|
||||
}
|
||||
|
||||
func storeOnlyOfficeLogContext(documentID string, context *OnlyOfficeLogContext) {
|
||||
onlyOfficeContextsMutex.Lock()
|
||||
defer onlyOfficeContextsMutex.Unlock()
|
||||
onlyOfficeContexts[documentID] = context
|
||||
}
|
||||
|
||||
func getOnlyOfficeLogContext(documentID string) *OnlyOfficeLogContext {
|
||||
onlyOfficeContextsMutex.RLock()
|
||||
defer onlyOfficeContextsMutex.RUnlock()
|
||||
return onlyOfficeContexts[documentID]
|
||||
}
|
||||
|
||||
func removeOnlyOfficeLogContext(documentID string) {
|
||||
onlyOfficeContextsMutex.Lock()
|
||||
defer onlyOfficeContextsMutex.Unlock()
|
||||
delete(onlyOfficeContexts, documentID)
|
||||
}
|
||||
|
||||
func sendOnlyOfficeLogEvent(context *OnlyOfficeLogContext, level, component, message string) {
|
||||
if context == nil {
|
||||
return
|
||||
}
|
||||
if !context.isAdmin {
|
||||
return
|
||||
}
|
||||
|
||||
// Create the log event message
|
||||
logData := map[string]interface{}{
|
||||
"documentId": context.DocumentID,
|
||||
"username": context.Username,
|
||||
"sessionId": context.SessionID,
|
||||
"logLevel": level,
|
||||
"message": message,
|
||||
"timestamp": time.Now().Format(time.RFC3339),
|
||||
"component": component,
|
||||
}
|
||||
|
||||
// Convert to JSON
|
||||
jsonData, err := json.Marshal(logData)
|
||||
if err != nil {
|
||||
logger.Errorf("Failed to marshal OnlyOffice log event: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Send to specific user only (not broadcast to all users)
|
||||
events.SendToUsers("onlyOfficeLog", string(jsonData), []string{context.Username})
|
||||
}
|
||||
+27
-30
@@ -157,15 +157,6 @@ func extractUserFromExpiredToken(r *http.Request, data *requestContext) *users.U
|
||||
return user
|
||||
}
|
||||
|
||||
proxyUser := r.Header.Get(config.Auth.Methods.ProxyAuth.Header)
|
||||
if config.Auth.Methods.ProxyAuth.Enabled && proxyUser != "" {
|
||||
user, err := setupProxyUser(r, data, proxyUser)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return user
|
||||
}
|
||||
|
||||
keyFunc := func(token *jwt.Token) (interface{}, error) {
|
||||
return []byte(config.Auth.Key), nil
|
||||
}
|
||||
@@ -304,14 +295,7 @@ func userWithoutOTPhelper(fn handleFunc) handleFunc {
|
||||
// Call the actual handler function with the updated context
|
||||
username := r.URL.Query().Get("username")
|
||||
password := r.Header.Get("X-Password")
|
||||
proxyUser := r.Header.Get(config.Auth.Methods.ProxyAuth.Header)
|
||||
if config.Auth.Methods.ProxyAuth.Enabled && proxyUser != "" {
|
||||
user, err := setupProxyUser(r, &requestContext{}, proxyUser)
|
||||
if err != nil {
|
||||
return 401, errors.ErrUnauthorized
|
||||
}
|
||||
d.user = user
|
||||
} else if username == "" || password == "" {
|
||||
if username == "" || password == "" {
|
||||
return withUserHelper(fn)(w, r, d)
|
||||
} else {
|
||||
if !config.Auth.Methods.PasswordAuth.Enabled {
|
||||
@@ -353,35 +337,30 @@ func withUserHelper(fn handleFunc) handleFunc {
|
||||
return fn(w, r, data)
|
||||
}
|
||||
proxyUser := r.Header.Get(config.Auth.Methods.ProxyAuth.Header)
|
||||
if config.Auth.Methods.ProxyAuth.Enabled && proxyUser != "" {
|
||||
user, err := setupProxyUser(r, data, proxyUser)
|
||||
if err != nil {
|
||||
return http.StatusForbidden, err
|
||||
}
|
||||
data.user = user
|
||||
setUserInResponseWriter(w, data.user)
|
||||
if fn == nil {
|
||||
return http.StatusOK, nil
|
||||
}
|
||||
return fn(w, r, data)
|
||||
}
|
||||
isProxyUser := config.Auth.Methods.ProxyAuth.Enabled && proxyUser != ""
|
||||
keyFunc := func(token *jwt.Token) (interface{}, error) {
|
||||
return []byte(config.Auth.Key), nil
|
||||
}
|
||||
tokenString, err := extractToken(r)
|
||||
if err != nil {
|
||||
if err != nil && !isProxyUser {
|
||||
return http.StatusUnauthorized, err
|
||||
}
|
||||
data.token = tokenString
|
||||
var tk users.AuthToken
|
||||
token, err := jwt.ParseWithClaims(tokenString, &tk, keyFunc)
|
||||
if err != nil {
|
||||
if isProxyUser {
|
||||
return getProxyUser(w, r, data, fn, proxyUser)
|
||||
}
|
||||
return http.StatusUnauthorized, fmt.Errorf("error processing token, %v", err)
|
||||
}
|
||||
if !token.Valid {
|
||||
return http.StatusUnauthorized, fmt.Errorf("invalid token")
|
||||
}
|
||||
if auth.IsRevokedApiKey(data.token) || tk.Expires < time.Now().Unix() {
|
||||
if isProxyUser {
|
||||
return getProxyUser(w, r, data, fn, proxyUser)
|
||||
}
|
||||
return http.StatusUnauthorized, fmt.Errorf("token expired or revoked")
|
||||
}
|
||||
// Check if the token is about to expire and send a header to renew it
|
||||
@@ -405,6 +384,24 @@ func withUserHelper(fn handleFunc) handleFunc {
|
||||
}
|
||||
}
|
||||
|
||||
func getProxyUser(w http.ResponseWriter, r *http.Request, data *requestContext, fn handleFunc, proxyUser string) (int, error) {
|
||||
// proxy user logic
|
||||
user, err := setupProxyUser(r, data, proxyUser)
|
||||
if err != nil {
|
||||
return http.StatusForbidden, err
|
||||
}
|
||||
data.user = user
|
||||
setUserInResponseWriter(w, data.user)
|
||||
if data.user.Username == "" {
|
||||
return http.StatusForbidden, errors.ErrUnauthorized
|
||||
}
|
||||
// Call the handler function, passing in the context (or return OK if no handler)
|
||||
if fn == nil {
|
||||
return http.StatusOK, nil
|
||||
}
|
||||
return fn(w, r, data)
|
||||
}
|
||||
|
||||
// Middleware to ensure the user is either the requested user or an admin
|
||||
func withSelfOrAdminHelper(fn handleFunc) handleFunc {
|
||||
return withUserHelper(func(w http.ResponseWriter, r *http.Request, data *requestContext) (int, error) {
|
||||
|
||||
@@ -117,7 +117,7 @@ func TestWithAdminHelper(t *testing.T) {
|
||||
t.Fatalf("Error creating request: %v", err)
|
||||
}
|
||||
req.AddCookie(&http.Cookie{
|
||||
Name: "auth",
|
||||
Name: "filebrowser_quantum_jwt",
|
||||
Value: token.Key,
|
||||
})
|
||||
|
||||
|
||||
@@ -329,6 +329,7 @@ func loginWithOidcUser(w http.ResponseWriter, r *http.Request, username string,
|
||||
if err = store.Access.SyncUserGroups(username, groups); err != nil {
|
||||
logger.Warningf("failed to sync oidc user %s groups: %v", username, err)
|
||||
}
|
||||
|
||||
expires := time.Hour * time.Duration(config.Auth.TokenExpirationHours)
|
||||
// Generate a signed token for the user
|
||||
signed, err2 := makeSignedTokenAPI(user, "WEB_TOKEN_"+utils.InsecureRandomIdentifier(4), expires, user.Permissions)
|
||||
@@ -340,16 +341,18 @@ func loginWithOidcUser(w http.ResponseWriter, r *http.Request, username string,
|
||||
return http.StatusInternalServerError, fmt.Errorf("failed to generate authentication token for user %s: %v", username, err)
|
||||
}
|
||||
|
||||
// add 30 minutes so expired token doesn't get automatically deleted by the browser and the backend can see who's making a new request
|
||||
// Add 30 minutes buffer so expired token doesn't get automatically deleted by the browser
|
||||
// This allows backend to identify expired sessions and provide better user feedback
|
||||
expiresTime := time.Now().Add(expires).Add(time.Minute * 30)
|
||||
|
||||
// Set the authentication token as an HTTP cookie
|
||||
cookie := &http.Cookie{
|
||||
Name: "auth", // The name of your auth cookie
|
||||
Value: signed.Key, // The generated token value
|
||||
Name: "filebrowser_quantum_jwt",
|
||||
Value: signed.Key,
|
||||
Domain: strings.Split(r.Host, ":")[0], // Set domain to the host without port
|
||||
Path: "/", // Make the cookie available to the whole site
|
||||
SameSite: http.SameSiteLaxMode, // Lax mode allows cookie on navigation from OIDC provider
|
||||
Expires: expiresTime, // Set cookie expiration
|
||||
Path: "/",
|
||||
SameSite: http.SameSiteLaxMode, // Lax mode allows cookie on navigation from OIDC provider
|
||||
Expires: expiresTime,
|
||||
// HttpOnly: true, // Cannot use HttpOnly since frontend needs to read cookie for renew operations
|
||||
// Secure: true, // Enable this in production with HTTPS
|
||||
}
|
||||
|
||||
+254
-51
@@ -22,18 +22,36 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
onlyOfficeStatusDocumentBeingEdited = 1
|
||||
onlyOfficeStatusDocumentClosedWithChanges = 2
|
||||
onlyOfficeStatusDocumentSavingError = 3
|
||||
onlyOfficeStatusDocumentClosedWithNoChanges = 4
|
||||
onlyOfficeStatusForceSaveWhileDocumentStillOpen = 6
|
||||
onlyOfficeStatusForceSaveError = 7
|
||||
)
|
||||
|
||||
type OnlyOfficeCallback struct {
|
||||
ChangesURL string `json:"changesurl,omitempty"`
|
||||
Key string `json:"key,omitempty"`
|
||||
Status int `json:"status,omitempty"`
|
||||
URL string `json:"url,omitempty"`
|
||||
Users []string `json:"users,omitempty"`
|
||||
UserData string `json:"userdata,omitempty"`
|
||||
Actions []OnlyOfficeAction `json:"actions,omitempty"`
|
||||
ChangesURL string `json:"changesurl,omitempty"`
|
||||
FileType string `json:"filetype,omitempty"`
|
||||
ForceSaveType int `json:"forcesavetype,omitempty"`
|
||||
FormsDataURL string `json:"formsdataurl,omitempty"`
|
||||
History *OnlyOfficeHistory `json:"history,omitempty"`
|
||||
Key string `json:"key,omitempty"`
|
||||
Status int `json:"status,omitempty"`
|
||||
URL string `json:"url,omitempty"`
|
||||
UserData string `json:"userdata,omitempty"`
|
||||
Users []string `json:"users,omitempty"`
|
||||
}
|
||||
|
||||
type OnlyOfficeAction struct {
|
||||
Type int `json:"type"`
|
||||
UserID string `json:"userid"`
|
||||
}
|
||||
|
||||
type OnlyOfficeHistory struct {
|
||||
Changes interface{} `json:"changes"`
|
||||
ServerVersion string `json:"serverVersion"`
|
||||
}
|
||||
|
||||
// OnlyOfficeJWTPayload represents the JWT payload structure for OnlyOffice callbacks
|
||||
@@ -115,18 +133,19 @@ func onlyofficeClientConfigGetHandler(w http.ResponseWriter, r *http.Request, d
|
||||
|
||||
// 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 != "" {
|
||||
// For shares, check both EnableOnlyOfficeEditing and AllowEdit permissions
|
||||
if d.share.EnableOnlyOfficeEditing && d.share.AllowModify {
|
||||
// Editing also requires authentication (not anonymous)
|
||||
if d.user.Username != "anonymous" {
|
||||
canEditMode = "edit"
|
||||
}
|
||||
}
|
||||
|
||||
// Determine modify permissions based on whether this is a share or regular request
|
||||
var modifyPerms bool
|
||||
if d.fileInfo.Hash != "" && d.share != nil {
|
||||
// Share request - check share permissions
|
||||
modifyPerms = d.share.AllowModify
|
||||
} else {
|
||||
// Regular user request - check user permissions
|
||||
modifyPerms = d.user.Permissions.Modify
|
||||
}
|
||||
|
||||
canEdit := iteminfo.CanEditOnlyOffice(modifyPerms, fileType)
|
||||
canEditMode := utils.Ternary(canEdit, "edit", "view")
|
||||
// Generate document ID for OnlyOffice
|
||||
documentId, err := getOnlyOfficeId(d.fileInfo.RealPath)
|
||||
if err != nil {
|
||||
@@ -134,6 +153,31 @@ func onlyofficeClientConfigGetHandler(w http.ResponseWriter, r *http.Request, d
|
||||
return http.StatusNotFound, fmt.Errorf("failed to generate document ID: %v", err)
|
||||
}
|
||||
|
||||
// Create and store log context for this OnlyOffice session
|
||||
sessionID := r.URL.Query().Get("sessionId")
|
||||
if sessionID == "" {
|
||||
sessionID = "unknown"
|
||||
}
|
||||
|
||||
shareHash := ""
|
||||
if d.fileInfo.Hash != "" {
|
||||
shareHash = d.fileInfo.Hash
|
||||
}
|
||||
|
||||
logContext := createOnlyOfficeLogContext(
|
||||
d.user.Username,
|
||||
sessionID,
|
||||
documentId,
|
||||
path,
|
||||
source,
|
||||
shareHash,
|
||||
d.user.Permissions.Admin,
|
||||
)
|
||||
storeOnlyOfficeLogContext(documentId, logContext)
|
||||
|
||||
// Send initial log event
|
||||
sendOnlyOfficeLogEvent(logContext, "INFO", "config", fmt.Sprintf("OnlyOffice session started for document: %s", d.fileInfo.Name))
|
||||
|
||||
// Build download URL that OnlyOffice server will use
|
||||
downloadURL := buildOnlyOfficeDownloadURL(source, providedPath, d.fileInfo.Hash, d.token)
|
||||
|
||||
@@ -253,7 +297,7 @@ func processOnlyOfficeCallback(w http.ResponseWriter, r *http.Request, d *reques
|
||||
// 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")
|
||||
return returnOnlyOfficeError(w, r, 400, "missing required parameters: path + source/hash are required")
|
||||
}
|
||||
var sourceInfo *settings.Source
|
||||
var ok bool
|
||||
@@ -261,13 +305,13 @@ func processOnlyOfficeCallback(w http.ResponseWriter, r *http.Request, d *reques
|
||||
sourceInfo, ok = settings.Config.Server.SourceMap[d.share.Source]
|
||||
if !ok {
|
||||
logger.Error("OnlyOffice: share source not found")
|
||||
return http.StatusInternalServerError, fmt.Errorf("source not found")
|
||||
return returnOnlyOfficeError(w, r, 404, "source not found")
|
||||
}
|
||||
} else {
|
||||
sourceInfo, ok = settings.Config.Server.NameToSource[source]
|
||||
if !ok {
|
||||
logger.Error("OnlyOffice: source not found")
|
||||
return http.StatusInternalServerError, fmt.Errorf("source not found")
|
||||
return returnOnlyOfficeError(w, r, 404, "source not found")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -276,7 +320,7 @@ func processOnlyOfficeCallback(w http.ResponseWriter, r *http.Request, d *reques
|
||||
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)
|
||||
return returnOnlyOfficeError(w, r, 403, "source not available")
|
||||
}
|
||||
path = utils.JoinPathAsUnix(userScope, path)
|
||||
} else {
|
||||
@@ -294,76 +338,193 @@ func processOnlyOfficeCallback(w http.ResponseWriter, r *http.Request, d *reques
|
||||
// When the document is fully closed by all editors,
|
||||
// the document key should no longer be re-used.
|
||||
deleteOfficeId(source, path)
|
||||
|
||||
// Send log event for document closure and clean up log context
|
||||
if logContext := getOnlyOfficeLogContext(data.Key); logContext != nil {
|
||||
statusMsg := "Document closed with changes"
|
||||
if data.Status == onlyOfficeStatusDocumentClosedWithNoChanges {
|
||||
statusMsg = "Document closed with no changes"
|
||||
}
|
||||
sendOnlyOfficeLogEvent(logContext, "INFO", "callback", statusMsg)
|
||||
removeOnlyOfficeLogContext(data.Key)
|
||||
}
|
||||
}
|
||||
|
||||
// Handle document save operations
|
||||
// Handle document being edited (status 1) - just log for now
|
||||
if data.Status == onlyOfficeStatusDocumentBeingEdited {
|
||||
logger.Debugf("OnlyOffice callback: document being edited, key=%s, users=%v", data.Key, data.Users)
|
||||
|
||||
// Send log event for document being edited
|
||||
if logContext := getOnlyOfficeLogContext(data.Key); logContext != nil {
|
||||
sendOnlyOfficeLogEvent(logContext, "DEBUG", "callback", fmt.Sprintf("Document being edited, users: %v", data.Users))
|
||||
}
|
||||
|
||||
// Handle actions if present
|
||||
for _, action := range data.Actions {
|
||||
actionMsg := ""
|
||||
switch action.Type {
|
||||
case 0: // User disconnects
|
||||
actionMsg = fmt.Sprintf("User ID %s disconnected from document", action.UserID)
|
||||
logger.Debugf("OnlyOffice callback: user ID %s disconnected from document", action.UserID)
|
||||
case 1: // New user connects
|
||||
actionMsg = fmt.Sprintf("User ID %s connected to document", action.UserID)
|
||||
logger.Debugf("OnlyOffice callback: user ID %s connected to document", action.UserID)
|
||||
case 2: // User clicked forcesave button
|
||||
actionMsg = fmt.Sprintf("User ID %s clicked forcesave button", action.UserID)
|
||||
logger.Debugf("OnlyOffice callback: user ID %s clicked forcesave button", action.UserID)
|
||||
default:
|
||||
actionMsg = fmt.Sprintf("Unknown action type %d for user ID %s", action.Type, action.UserID)
|
||||
logger.Debugf("OnlyOffice callback: unknown action type %d for user ID %s", action.Type, action.UserID)
|
||||
}
|
||||
|
||||
// Send log event for action
|
||||
if logContext := getOnlyOfficeLogContext(data.Key); logContext != nil {
|
||||
sendOnlyOfficeLogEvent(logContext, "DEBUG", "callback", actionMsg)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle document save operations (status 2, 3, 6, 7)
|
||||
if data.Status == onlyOfficeStatusDocumentClosedWithChanges ||
|
||||
data.Status == onlyOfficeStatusForceSaveWhileDocumentStillOpen {
|
||||
data.Status == onlyOfficeStatusDocumentSavingError ||
|
||||
data.Status == onlyOfficeStatusForceSaveWhileDocumentStillOpen ||
|
||||
data.Status == onlyOfficeStatusForceSaveError {
|
||||
|
||||
// Log the save operation details
|
||||
statusDesc := ""
|
||||
switch data.Status {
|
||||
case onlyOfficeStatusDocumentClosedWithChanges:
|
||||
statusDesc = "document closed with changes"
|
||||
case onlyOfficeStatusDocumentSavingError:
|
||||
statusDesc = "document saving error"
|
||||
case onlyOfficeStatusForceSaveWhileDocumentStillOpen:
|
||||
statusDesc = "force save while document still open"
|
||||
case onlyOfficeStatusForceSaveError:
|
||||
statusDesc = "force save error"
|
||||
}
|
||||
|
||||
logger.Debugf("OnlyOffice callback: processing save operation - %s, key=%s, url=%s, forcesavetype=%d",
|
||||
statusDesc, data.Key, data.URL, data.ForceSaveType)
|
||||
|
||||
// Send log event for save operation
|
||||
if logContext := getOnlyOfficeLogContext(data.Key); logContext != nil {
|
||||
sendOnlyOfficeLogEvent(logContext, "INFO", "callback", fmt.Sprintf("Processing save operation: %s", statusDesc))
|
||||
}
|
||||
|
||||
// Handle history and changes URL if present
|
||||
if data.History != nil {
|
||||
logger.Debugf("OnlyOffice callback: received history data with serverVersion=%s", data.History.ServerVersion)
|
||||
if logContext := getOnlyOfficeLogContext(data.Key); logContext != nil {
|
||||
sendOnlyOfficeLogEvent(logContext, "DEBUG", "callback", fmt.Sprintf("Received history data with serverVersion=%s", data.History.ServerVersion))
|
||||
}
|
||||
}
|
||||
if data.ChangesURL != "" {
|
||||
logger.Debugf("OnlyOffice callback: received changes URL: %s", data.ChangesURL)
|
||||
if logContext := getOnlyOfficeLogContext(data.Key); logContext != nil {
|
||||
sendOnlyOfficeLogEvent(logContext, "DEBUG", "callback", "Received changes URL for document history")
|
||||
}
|
||||
}
|
||||
|
||||
// For status 3 (saving error), don't attempt to save the file
|
||||
if data.Status == onlyOfficeStatusDocumentSavingError {
|
||||
logger.Warningf("OnlyOffice callback: document saving error occurred, not attempting to save")
|
||||
return returnOnlyOfficeSuccess(w, r)
|
||||
}
|
||||
|
||||
// Check share permissions first if this is a share request
|
||||
if d.fileInfo.Hash != "" {
|
||||
if !d.share.AllowModify {
|
||||
logger.Warningf("OnlyOffice callback: edit permission not allowed for this share")
|
||||
return http.StatusForbidden, fmt.Errorf("edit permission not allowed for this share")
|
||||
logger.Errorf("OnlyOffice callback: edit permission not allowed for this share")
|
||||
return returnOnlyOfficeError(w, r, 403, "edit permission not allowed for this share")
|
||||
}
|
||||
// Share edit operations also require authentication (not anonymous)
|
||||
if d.user.Username == "anonymous" {
|
||||
logger.Warningf("OnlyOffice callback: edit operations require authentication")
|
||||
return http.StatusForbidden, fmt.Errorf("edit operations require authentication")
|
||||
} else {
|
||||
// Verify user has modify permissions
|
||||
if !d.user.Permissions.Modify {
|
||||
logger.Errorf("OnlyOffice callback: user %s lacks modify permissions for source=%s, path=%s",
|
||||
d.user.Username, source, path)
|
||||
return returnOnlyOfficeError(w, r, 403, "user lacks modify permissions")
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
return returnOnlyOfficeError(w, r, 500, "failed to download updated document")
|
||||
}
|
||||
defer doc.Body.Close()
|
||||
|
||||
// Check if the download was successful
|
||||
if doc.StatusCode != 200 {
|
||||
logger.Errorf("OnlyOffice callback: failed to download document, status code: %d", doc.StatusCode)
|
||||
return returnOnlyOfficeError(w, r, 500, "failed to download document from OnlyOffice server")
|
||||
}
|
||||
|
||||
// Resolve file path for writing (same logic as in config handler)
|
||||
var resolvedPath string
|
||||
resolvedPath := path
|
||||
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)
|
||||
return returnOnlyOfficeError(w, r, 403, "source not available")
|
||||
}
|
||||
resolvedPath = utils.JoinPathAsUnix(userScope, path)
|
||||
}
|
||||
// Write the updated document
|
||||
fileOpts := utils.FileOptions{
|
||||
Path: resolvedPath,
|
||||
|
||||
logger.Debugf("OnlyOffice callback: saving document to path=%s, source=%s, user=%s",
|
||||
resolvedPath, source, d.user.Username)
|
||||
|
||||
// CRITICAL: Validate that the original file still exists before saving
|
||||
// This prevents creating duplicate files if the original was renamed/moved
|
||||
_, err = files.FileInfoFaster(utils.FileOptions{
|
||||
Source: source,
|
||||
Path: resolvedPath,
|
||||
}, nil)
|
||||
if err != nil {
|
||||
logger.Errorf("OnlyOffice callback: original file no longer exists at path=%s, source=%s: %v",
|
||||
resolvedPath, source, err)
|
||||
|
||||
// Send error log event
|
||||
if logContext := getOnlyOfficeLogContext(data.Key); logContext != nil {
|
||||
sendOnlyOfficeLogEvent(logContext, "ERROR", "callback",
|
||||
fmt.Sprintf("Original file no longer exists: %v -- was it renamed or moved?", err))
|
||||
}
|
||||
|
||||
return returnOnlyOfficeError(w, r, 404, "original file no longer exists - it may have been renamed or moved")
|
||||
}
|
||||
writeErr := files.WriteFile(fileOpts, doc.Body)
|
||||
|
||||
writeErr := files.WriteFile(source, resolvedPath, doc.Body)
|
||||
if writeErr != nil {
|
||||
logger.Errorf("OnlyOffice callback: failed to write updated document: %v", writeErr)
|
||||
return http.StatusInternalServerError, writeErr
|
||||
logger.Errorf("OnlyOffice callback: failed to write updated document to path=%s, source=%s: %v",
|
||||
resolvedPath, source, writeErr)
|
||||
|
||||
// Send error log event
|
||||
if logContext := getOnlyOfficeLogContext(data.Key); logContext != nil {
|
||||
sendOnlyOfficeLogEvent(logContext, "ERROR", "callback", fmt.Sprintf("Failed to save document: %v", writeErr))
|
||||
}
|
||||
|
||||
return returnOnlyOfficeError(w, r, 500, "failed to save document")
|
||||
}
|
||||
|
||||
logger.Infof("OnlyOffice callback: successfully saved document to path=%s, source=%s",
|
||||
resolvedPath, source)
|
||||
|
||||
// Send success log event
|
||||
if logContext := getOnlyOfficeLogContext(data.Key); logContext != nil {
|
||||
sendOnlyOfficeLogEvent(logContext, "INFO", "callback", "Document saved successfully")
|
||||
}
|
||||
}
|
||||
|
||||
// Return success response to OnlyOffice server
|
||||
resp := map[string]int{
|
||||
"error": 0,
|
||||
}
|
||||
return renderJSON(w, r, resp)
|
||||
return returnOnlyOfficeSuccess(w, r)
|
||||
}
|
||||
|
||||
func onlyofficeCallbackHandler(w http.ResponseWriter, r *http.Request, d *requestContext) (int, error) {
|
||||
// Parse callback data based on request method
|
||||
var callbackData *OnlyOfficeCallback
|
||||
var err error
|
||||
|
||||
if r.Method == "GET" {
|
||||
// OnlyOffice sends callback data in Authorization header as JWT
|
||||
callbackData, err = parseOnlyOfficeCallbackFromJWT(r)
|
||||
@@ -371,12 +532,17 @@ func onlyofficeCallbackHandler(w http.ResponseWriter, r *http.Request, d *reques
|
||||
// OnlyOffice sends callback data in request body as JSON
|
||||
callbackData, err = parseOnlyOfficeCallbackFromJSON(r)
|
||||
} else {
|
||||
return http.StatusMethodNotAllowed, fmt.Errorf("unsupported method: %s", r.Method)
|
||||
return returnOnlyOfficeError(w, r, 405, fmt.Sprintf("unsupported method: %s", r.Method))
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
logger.Errorf("OnlyOffice callback: failed to parse callback data: %v", err)
|
||||
return http.StatusBadRequest, err
|
||||
return returnOnlyOfficeError(w, r, 400, "failed to parse callback data")
|
||||
}
|
||||
|
||||
if callbackData == nil {
|
||||
logger.Errorf("OnlyOffice callback: parsed callback data is nil")
|
||||
return returnOnlyOfficeError(w, r, 400, "parsed callback data is nil")
|
||||
}
|
||||
|
||||
// Process the callback data using shared logic
|
||||
@@ -501,3 +667,40 @@ func parseOnlyOfficeJWT(tokenString string) (*OnlyOfficeCallback, error) {
|
||||
|
||||
return callback, nil
|
||||
}
|
||||
|
||||
// returnOnlyOfficeSuccess returns a success response to OnlyOffice server
|
||||
func returnOnlyOfficeSuccess(w http.ResponseWriter, r *http.Request) (int, error) {
|
||||
resp := map[string]int{
|
||||
"error": 0,
|
||||
}
|
||||
return renderJSON(w, r, resp)
|
||||
}
|
||||
|
||||
// returnOnlyOfficeError returns an error response to OnlyOffice server with proper status code
|
||||
func returnOnlyOfficeError(w http.ResponseWriter, r *http.Request, statusCode int, message string) (int, error) {
|
||||
// OnlyOffice expects specific error codes in the response body
|
||||
errorCode := 0
|
||||
switch statusCode {
|
||||
case 400:
|
||||
errorCode = 1 // Bad request
|
||||
case 403:
|
||||
errorCode = 1 // Forbidden (treated as bad request by OnlyOffice)
|
||||
case 404:
|
||||
errorCode = 1 // Not found (treated as bad request by OnlyOffice)
|
||||
case 500:
|
||||
errorCode = 1 // Internal server error (treated as bad request by OnlyOffice)
|
||||
default:
|
||||
errorCode = 1 // Default to bad request
|
||||
}
|
||||
|
||||
resp := map[string]interface{}{
|
||||
"error": errorCode,
|
||||
}
|
||||
|
||||
// Log the error for debugging
|
||||
logger.Errorf("OnlyOffice callback error (HTTP %d): %s", statusCode, message)
|
||||
|
||||
// Set the appropriate HTTP status code
|
||||
w.WriteHeader(statusCode)
|
||||
return renderJSON(w, r, resp)
|
||||
}
|
||||
@@ -289,13 +289,8 @@ func publicPutHandler(w http.ResponseWriter, r *http.Request, d *requestContext)
|
||||
if err != nil {
|
||||
return http.StatusBadRequest, fmt.Errorf("invalid path encoding: %v", err)
|
||||
}
|
||||
fileOpts := utils.FileOptions{
|
||||
Username: d.user.Username,
|
||||
Path: utils.JoinPathAsUnix(d.share.Path, path),
|
||||
Source: source,
|
||||
Expand: false,
|
||||
}
|
||||
err = files.WriteFile(fileOpts, r.Body)
|
||||
resolvedPath := utils.JoinPathAsUnix(d.share.Path, path)
|
||||
err = files.WriteFile(source, resolvedPath, r.Body)
|
||||
return errToStatus(err), err
|
||||
}
|
||||
|
||||
|
||||
@@ -368,8 +368,8 @@ func resourcePostHandler(w http.ResponseWriter, r *http.Request, d *requestConte
|
||||
// Move the completed file from the temp location to the final destination
|
||||
err = fileutils.MoveFile(tempFilePath, realPath)
|
||||
if err != nil {
|
||||
logger.Debugf("could not move temp file to destination: %v", err)
|
||||
return http.StatusInternalServerError, fmt.Errorf("could not move temp file to destination: %v", err)
|
||||
logger.Debugf("could not move file from %v to %v: %v", tempFilePath, realPath, err)
|
||||
return http.StatusInternalServerError, fmt.Errorf("could not move file from chunked folder to destination: %v", err)
|
||||
}
|
||||
go files.RefreshIndex(source, realPath, false, false) //nolint:errcheck
|
||||
}
|
||||
@@ -381,7 +381,7 @@ func resourcePostHandler(w http.ResponseWriter, r *http.Request, d *requestConte
|
||||
if err != nil {
|
||||
preview.DelThumbs(r.Context(), *fileInfo)
|
||||
}
|
||||
err = files.WriteFile(fileOpts, r.Body)
|
||||
err = files.WriteFile(fileOpts.Source, fileOpts.Path, r.Body)
|
||||
if err != nil {
|
||||
logger.Debugf("error writing file: %v", err)
|
||||
return errToStatus(err), err
|
||||
@@ -428,15 +428,6 @@ func resourcePutHandler(w http.ResponseWriter, r *http.Request, d *requestContex
|
||||
if err != nil {
|
||||
return http.StatusForbidden, err
|
||||
}
|
||||
userscope = strings.TrimRight(userscope, "/")
|
||||
|
||||
fileOpts := utils.FileOptions{
|
||||
Username: d.user.Username,
|
||||
Path: utils.JoinPathAsUnix(userscope, path),
|
||||
Source: source,
|
||||
Expand: false,
|
||||
}
|
||||
|
||||
// Check access control for the target path
|
||||
idx := indexing.GetIndex(source)
|
||||
if idx == nil {
|
||||
@@ -447,7 +438,7 @@ func resourcePutHandler(w http.ResponseWriter, r *http.Request, d *requestContex
|
||||
return http.StatusForbidden, fmt.Errorf("access denied to path %s", path)
|
||||
}
|
||||
|
||||
err = files.WriteFile(fileOpts, r.Body)
|
||||
err = files.WriteFile(source, utils.JoinPathAsUnix(userscope, path), r.Body)
|
||||
return errToStatus(err), err
|
||||
}
|
||||
|
||||
|
||||
@@ -102,7 +102,6 @@ func handleWithStaticData(w http.ResponseWriter, r *http.Request, d *requestCont
|
||||
shareProps["enforceDarkLightMode"] = d.share.EnforceDarkLightMode
|
||||
shareProps["viewMode"] = d.share.ViewMode
|
||||
shareProps["enableOnlyOffice"] = d.share.EnableOnlyOffice
|
||||
shareProps["enableOnlyOfficeEditing"] = d.share.EnableOnlyOfficeEditing
|
||||
shareProps["shareType"] = utils.Ternary(d.share.ShareType == "", "normal", d.share.ShareType)
|
||||
shareProps["perUserDownloadLimit"] = d.share.PerUserDownloadLimit
|
||||
shareProps["extractEmbeddedSubtitles"] = d.share.ExtractEmbeddedSubtitles
|
||||
@@ -189,6 +188,7 @@ func handleWithStaticData(w http.ResponseWriter, r *http.Request, d *requestCont
|
||||
"disableNavButtons": disableNavButtons,
|
||||
"userSelectableThemes": config.Frontend.Styling.CustomThemeOptions,
|
||||
"enableHeicConversion": config.Integrations.Media.Convert.ImagePreview[settings.HEICImagePreview],
|
||||
"eventBasedThemes": !config.Frontend.Styling.DisableEventBasedThemes,
|
||||
}
|
||||
|
||||
// Marshal each variable to JSON strings for direct template usage
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
server:
|
||||
port: 8080
|
||||
baseURL: "/"
|
||||
database: "playwright-files.db"
|
||||
logging:
|
||||
- levels: "info|warning|debug|error"
|
||||
sources:
|
||||
- path: "../frontend/playwright-files"
|
||||
config:
|
||||
defaultEnabled: true
|
||||
userDefaults:
|
||||
editorQuickSave: false # show quick save button in editor
|
||||
hideSidebarFileActions: false # hide the file actions in the sidebar
|
||||
disableQuickToggles: false # disable the quick toggles in the sidebar
|
||||
disableSearchOptions: false # disable the search options in the search bar
|
||||
stickySidebar: true # keep sidebar open when navigating
|
||||
darkMode: true # should dark mode be enabled
|
||||
viewMode: "normal" # view mode to use: eg. normal, list, grid, or compact
|
||||
singleClick: false # open directory on single click, also enables middle click to open in new tab
|
||||
showHidden: false # show hidden files in the UI. On windows this includes files starting with a dot and windows hidden files
|
||||
dateFormat: false # when false, the date is relative, when true, the date is an exact timestamp
|
||||
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
|
||||
lockPassword: false # disable the user from changing their password
|
||||
disableSettings: false # disable the user from viewing the settings page
|
||||
preview:
|
||||
disableHideSidebar: true # disable the hide sidebar preview for previews and editors
|
||||
highQuality: true # generate high quality thumbnail preview images
|
||||
image: true # show thumbnail preview image for image files
|
||||
video: true # show thumbnail preview image for video files
|
||||
motionVideoPreview: true # show multiple frames for videos in thumbnail preview when hovering
|
||||
office: true # show thumbnail preview image for office files
|
||||
popup: true # show larger popup preview when hovering over thumbnail
|
||||
autoplayMedia: true # autoplay media files in preview
|
||||
defaultMediaPlayer: true # disable html5 media player and use the default media player
|
||||
folder: true # show thumbnail preview image for folder files
|
||||
permissions:
|
||||
api: false # allow api access
|
||||
admin: false # allow admin access
|
||||
modify: false # allow modifying files
|
||||
share: false # allow sharing files
|
||||
realtime: false # allow realtime updates
|
||||
delete: false # allow deleting files
|
||||
create: false # allow creating or uploading files
|
||||
download: true # allow downloading files
|
||||
disableUpdateNotifications: false # disable update notifications banner for admin users
|
||||
deleteWithoutConfirming: false # delete files without confirmation
|
||||
disableOnlyOfficeExt: ".md .txt .pdf" # list of file extensions to disable onlyoffice editor for
|
||||
showSelectMultiple: false # show select multiple files on desktop
|
||||
defaultLandingPage: "" # default landing page to use if no redirect is specified: eg. /files/mysource/mysubpath, /settings, etc.
|
||||
|
||||
#office:
|
||||
# url: "http://localhost:9052"
|
||||
# secret: "6H1H7GzUk2alRkNxAPav0W02g8JS5oUd"
|
||||
@@ -2922,6 +2922,10 @@ const docTemplate = `{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"hidden": {
|
||||
"description": "deprecated: use ignoreHidden instead to exclude hidden files and folders.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"ignoreHidden": {
|
||||
"description": "exclude hidden files and folders.",
|
||||
"type": "boolean"
|
||||
},
|
||||
@@ -2945,10 +2949,14 @@ const docTemplate = `{
|
||||
"description": "(global) exclude files that end with these suffixes. Eg. \".jpg\" or \".txt\"",
|
||||
"type": "string"
|
||||
},
|
||||
"fileNames": {
|
||||
"fileName": {
|
||||
"description": "(global) exclude files that match these names. Eg. \"file.txt\" or \"test.csv\"",
|
||||
"type": "string"
|
||||
},
|
||||
"fileNames": {
|
||||
"description": "deprecated: exclude files that match these names. Eg. \"file.txt\" or \"test.csv\"",
|
||||
"type": "string"
|
||||
},
|
||||
"filePath": {
|
||||
"description": "(global) exclude files that match this path. Eg. \"/path/to/file.txt\" or \"/path/to/file.txt/subfile.txt\"",
|
||||
"type": "string"
|
||||
@@ -2961,10 +2969,14 @@ const docTemplate = `{
|
||||
"description": "(global) exclude folders that end with these suffixes. Eg. \".thumbnails\" or \".git\"",
|
||||
"type": "string"
|
||||
},
|
||||
"folderNames": {
|
||||
"folderName": {
|
||||
"description": "(global) exclude folders that match these names. Eg. \"folder\" or \"subfolder\"",
|
||||
"type": "string"
|
||||
},
|
||||
"folderNames": {
|
||||
"description": "deprecated: exclude folders that match these names. Eg. \"folder\" or \"subfolder\"",
|
||||
"type": "string"
|
||||
},
|
||||
"folderPath": {
|
||||
"description": "(global) exclude folders that match this path. Eg. \"/path/to/folder\" or \"/path/to/folder/subfolder\"",
|
||||
"type": "string"
|
||||
@@ -3513,6 +3525,10 @@ const docTemplate = `{
|
||||
"description": "Specify a valid CSS color property value to use as the background color in dark mode",
|
||||
"type": "string"
|
||||
},
|
||||
"disableEventThemes": {
|
||||
"description": "disable the event based themes,",
|
||||
"type": "boolean"
|
||||
},
|
||||
"lightBackground": {
|
||||
"description": "specify a valid CSS color property value to use as the background color in light mode",
|
||||
"type": "string"
|
||||
@@ -3705,9 +3721,6 @@ const docTemplate = `{
|
||||
"enableOnlyOffice": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"enableOnlyOfficeEditing": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"enforceDarkLightMode": {
|
||||
"description": "\"dark\" or \"light\"",
|
||||
"type": "string"
|
||||
@@ -3834,9 +3847,6 @@ const docTemplate = `{
|
||||
"enableOnlyOffice": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"enableOnlyOfficeEditing": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"enforceDarkLightMode": {
|
||||
"description": "\"dark\" or \"light\"",
|
||||
"type": "string"
|
||||
|
||||
@@ -2911,6 +2911,10 @@
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"hidden": {
|
||||
"description": "deprecated: use ignoreHidden instead to exclude hidden files and folders.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"ignoreHidden": {
|
||||
"description": "exclude hidden files and folders.",
|
||||
"type": "boolean"
|
||||
},
|
||||
@@ -2934,10 +2938,14 @@
|
||||
"description": "(global) exclude files that end with these suffixes. Eg. \".jpg\" or \".txt\"",
|
||||
"type": "string"
|
||||
},
|
||||
"fileNames": {
|
||||
"fileName": {
|
||||
"description": "(global) exclude files that match these names. Eg. \"file.txt\" or \"test.csv\"",
|
||||
"type": "string"
|
||||
},
|
||||
"fileNames": {
|
||||
"description": "deprecated: exclude files that match these names. Eg. \"file.txt\" or \"test.csv\"",
|
||||
"type": "string"
|
||||
},
|
||||
"filePath": {
|
||||
"description": "(global) exclude files that match this path. Eg. \"/path/to/file.txt\" or \"/path/to/file.txt/subfile.txt\"",
|
||||
"type": "string"
|
||||
@@ -2950,10 +2958,14 @@
|
||||
"description": "(global) exclude folders that end with these suffixes. Eg. \".thumbnails\" or \".git\"",
|
||||
"type": "string"
|
||||
},
|
||||
"folderNames": {
|
||||
"folderName": {
|
||||
"description": "(global) exclude folders that match these names. Eg. \"folder\" or \"subfolder\"",
|
||||
"type": "string"
|
||||
},
|
||||
"folderNames": {
|
||||
"description": "deprecated: exclude folders that match these names. Eg. \"folder\" or \"subfolder\"",
|
||||
"type": "string"
|
||||
},
|
||||
"folderPath": {
|
||||
"description": "(global) exclude folders that match this path. Eg. \"/path/to/folder\" or \"/path/to/folder/subfolder\"",
|
||||
"type": "string"
|
||||
@@ -3502,6 +3514,10 @@
|
||||
"description": "Specify a valid CSS color property value to use as the background color in dark mode",
|
||||
"type": "string"
|
||||
},
|
||||
"disableEventThemes": {
|
||||
"description": "disable the event based themes,",
|
||||
"type": "boolean"
|
||||
},
|
||||
"lightBackground": {
|
||||
"description": "specify a valid CSS color property value to use as the background color in light mode",
|
||||
"type": "string"
|
||||
@@ -3694,9 +3710,6 @@
|
||||
"enableOnlyOffice": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"enableOnlyOfficeEditing": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"enforceDarkLightMode": {
|
||||
"description": "\"dark\" or \"light\"",
|
||||
"type": "string"
|
||||
@@ -3823,9 +3836,6 @@
|
||||
"enableOnlyOffice": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"enableOnlyOfficeEditing": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"enforceDarkLightMode": {
|
||||
"description": "\"dark\" or \"light\"",
|
||||
"type": "string"
|
||||
|
||||
@@ -170,6 +170,10 @@ definitions:
|
||||
settings.ConditionalFilter:
|
||||
properties:
|
||||
hidden:
|
||||
description: 'deprecated: use ignoreHidden instead to exclude hidden files
|
||||
and folders.'
|
||||
type: boolean
|
||||
ignoreHidden:
|
||||
description: exclude hidden files and folders.
|
||||
type: boolean
|
||||
ignoreZeroSizeFolders:
|
||||
@@ -187,10 +191,14 @@ definitions:
|
||||
description: (global) exclude files that end with these suffixes. Eg. ".jpg"
|
||||
or ".txt"
|
||||
type: string
|
||||
fileNames:
|
||||
fileName:
|
||||
description: (global) exclude files that match these names. Eg. "file.txt"
|
||||
or "test.csv"
|
||||
type: string
|
||||
fileNames:
|
||||
description: 'deprecated: exclude files that match these names. Eg. "file.txt"
|
||||
or "test.csv"'
|
||||
type: string
|
||||
filePath:
|
||||
description: (global) exclude files that match this path. Eg. "/path/to/file.txt"
|
||||
or "/path/to/file.txt/subfile.txt"
|
||||
@@ -203,10 +211,14 @@ definitions:
|
||||
description: (global) exclude folders that end with these suffixes. Eg. ".thumbnails"
|
||||
or ".git"
|
||||
type: string
|
||||
folderNames:
|
||||
folderName:
|
||||
description: (global) exclude folders that match these names. Eg. "folder"
|
||||
or "subfolder"
|
||||
type: string
|
||||
folderNames:
|
||||
description: 'deprecated: exclude folders that match these names. Eg. "folder"
|
||||
or "subfolder"'
|
||||
type: string
|
||||
folderPath:
|
||||
description: (global) exclude folders that match this path. Eg. "/path/to/folder"
|
||||
or "/path/to/folder/subfolder"
|
||||
@@ -618,6 +630,9 @@ definitions:
|
||||
description: Specify a valid CSS color property value to use as the background
|
||||
color in dark mode
|
||||
type: string
|
||||
disableEventThemes:
|
||||
description: disable the event based themes,
|
||||
type: boolean
|
||||
lightBackground:
|
||||
description: specify a valid CSS color property value to use as the background
|
||||
color in light mode
|
||||
@@ -761,8 +776,6 @@ definitions:
|
||||
type: integer
|
||||
enableOnlyOffice:
|
||||
type: boolean
|
||||
enableOnlyOfficeEditing:
|
||||
type: boolean
|
||||
enforceDarkLightMode:
|
||||
description: '"dark" or "light"'
|
||||
type: string
|
||||
@@ -851,8 +864,6 @@ definitions:
|
||||
type: integer
|
||||
enableOnlyOffice:
|
||||
type: boolean
|
||||
enableOnlyOfficeEditing:
|
||||
type: boolean
|
||||
enforceDarkLightMode:
|
||||
description: '"dark" or "light"'
|
||||
type: string
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
}
|
||||
},
|
||||
"rules": {
|
||||
"no-unreachable": "off",
|
||||
// vue-i18n rules:
|
||||
// This rule will check if the key used in $t() exists in your locale messages.
|
||||
// By default, it checks against all locales. We want it to primarily use 'en.json'
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
"node": ">=18.0.0"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "vite build --outDir ../backend/http/dist && cp -r ../backend/http/dist/* ../backend/http/embed",
|
||||
"build": "rm -rf ../backend/http/dist/* && rm -rf ../backend/http/embed/* && 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",
|
||||
"build:docker": "vite build",
|
||||
"watch": "DEV_BUILD=true vite build --watch --outDir ../backend/http/dist",
|
||||
|
||||
@@ -12,7 +12,7 @@ import { defineConfig, devices } from "@playwright/test";
|
||||
export default defineConfig({
|
||||
globalSetup: "./tests/playwright/local-setup",
|
||||
timeout: 5000,
|
||||
testDir: "./tests/playwright/copy",
|
||||
testDir: "./tests/playwright/local",
|
||||
/* Run tests in files in parallel */
|
||||
fullyParallel: false,
|
||||
/* Fail the build on CI if you accidentally left test.only in the source code. */
|
||||
@@ -20,7 +20,6 @@ export default defineConfig({
|
||||
/* Retry on CI only */
|
||||
retries: 2,
|
||||
/* Opt out of parallel tests on CI. */
|
||||
workers: 1,
|
||||
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
|
||||
reporter: "line",
|
||||
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
|
||||
@@ -40,8 +39,22 @@ export default defineConfig({
|
||||
/* Configure projects for major browsers */
|
||||
projects: [
|
||||
{
|
||||
name: "firefox",
|
||||
use: { ...devices["Desktop Firefox"] },
|
||||
name: "dark-screenshots",
|
||||
use: {
|
||||
...devices["Desktop Firefox"],
|
||||
theme: 'dark',
|
||||
},
|
||||
testMatch: /.*screenshots.spec.ts/,
|
||||
retries: 0,
|
||||
},
|
||||
{
|
||||
name: "light-screenshots",
|
||||
use: {
|
||||
...devices["Desktop Firefox"],
|
||||
theme: 'light',
|
||||
},
|
||||
testMatch: /.*screenshots.spec.ts/,
|
||||
retries: 0,
|
||||
},
|
||||
],
|
||||
});
|
||||
@@ -28,7 +28,7 @@ server:
|
||||
indexingIntervalMinutes: 0 # optional manual overide interval in minutes to re-index the source
|
||||
disableIndexing: false # disable the indexing of this source
|
||||
conditionals: # conditional rules to apply when indexing to include/exclude certain items
|
||||
hidden: false # exclude hidden files and folders.
|
||||
ignoreHidden: false # exclude hidden files and folders.
|
||||
ignoreZeroSizeFolders: false # ignore folders with 0 size
|
||||
rules: # list of item rules to apply to specific paths
|
||||
- neverWatchPath: "" # index the folder in the first pass to get included in search, but never re-indexed.
|
||||
@@ -39,8 +39,8 @@ server:
|
||||
folderEndsWith: "" # (global) exclude folders that end with these suffixes. Eg. ".thumbnails" or ".git"
|
||||
folderPath: "" # (global) exclude folders that match this path. Eg. "/path/to/folder" or "/path/to/folder/subfolder"
|
||||
filePath: "" # (global) exclude files that match this path. Eg. "/path/to/file.txt" or "/path/to/file.txt/subfile.txt"
|
||||
fileNames: "" # (global) exclude files that match these names. Eg. "file.txt" or "test.csv"
|
||||
folderNames: "" # (global) exclude folders that match these names. Eg. "folder" or "subfolder"
|
||||
fileName: "" # (global) exclude files that match these names. Eg. "file.txt" or "test.csv"
|
||||
folderName: "" # (global) exclude folders that match these names. Eg. "folder" or "subfolder"
|
||||
viewable: false # Enable viewing in UI but exclude from indexing
|
||||
defaultUserScope: "/" # default "/" should match folders under path
|
||||
defaultEnabled: true # should be added as a default source for new users?
|
||||
@@ -99,6 +99,7 @@ frontend:
|
||||
url: "help prompt" # the url to link to validate:required
|
||||
disableNavButtons: false # disable the nav buttons in the sidebar
|
||||
styling:
|
||||
disableEventThemes: false # disable the event based themes,
|
||||
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
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { fetchURL, fetchJSON } from '@/api/utils'
|
||||
import { getApiPath, getPublicApiPath } from '@/utils/url.js'
|
||||
import { notify } from '@/notify' // Import notify for error handling
|
||||
import { setNewToken } from '@/utils/auth.js' // Import setNewToken for token management
|
||||
export async function getAllUsers() {
|
||||
try {
|
||||
const apiPath = getApiPath('api/users')
|
||||
@@ -80,13 +79,9 @@ export async function login(username, password, recaptcha, otp) {
|
||||
body = { message: bodyText };
|
||||
}
|
||||
|
||||
if (res.status === 200) {
|
||||
await setNewToken(bodyText);
|
||||
} else if (res.status === 403) {
|
||||
if (res.status != 200) {
|
||||
const msg = body.message || 'Forbidden';
|
||||
throw new Error(msg);
|
||||
} else {
|
||||
throw new Error(body.message || bodyText || 'Failed to login');
|
||||
}
|
||||
}
|
||||
export async function get(id) {
|
||||
|
||||
@@ -28,7 +28,8 @@ export async function fetchURL(url, opts, auth = true) {
|
||||
}
|
||||
|
||||
if (auth && res.headers.get("X-Renew-Token") === "true") {
|
||||
await renew(state.jwt);
|
||||
// Cookie is automatically sent, no need to pass JWT from state
|
||||
await renew();
|
||||
}
|
||||
|
||||
if (res.status < 200 || res.status > 299) {
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="200" height="200">
|
||||
<defs>
|
||||
<pattern id="halloween-pattern" width="200" height="200" patternUnits="userSpaceOnUse">
|
||||
<rect width="200" height="200" fill="#1a1a1a"/>
|
||||
<g opacity="0.08" fill="#cccccc">
|
||||
<!-- Ghost 1 (top-left) - straight sides ghost -->
|
||||
<path d="M 60 60 A 15 15 0 1 1 90 60 L 90 90 C 80 100, 70 100, 60 90 L 60 60 Z" />
|
||||
<circle cx="70" cy="55" r="2" fill="#1a1a1a"/>
|
||||
<circle cx="80" cy="55" r="2" fill="#1a1a1a"/>
|
||||
|
||||
<!-- Ghost 2 (bottom-right, offset for diagonal pattern) - straight sides ghost -->
|
||||
<path d="M 160 160 A 15 15 0 1 1 190 160 L 190 190 C 180 200, 170 200, 160 190 L 160 160 Z" />
|
||||
<circle cx="170" cy="155" r="2" fill="#1a1a1a"/>
|
||||
<circle cx="180" cy="155" r="2" fill="#1a1a1a"/>
|
||||
</g>
|
||||
</pattern>
|
||||
</defs>
|
||||
<rect width="200" height="200" fill="url(#halloween-pattern)"/>
|
||||
</svg>
|
||||
|
||||
|
After Width: | Height: | Size: 949 B |
@@ -5,7 +5,15 @@
|
||||
@click="clearContext"
|
||||
>
|
||||
<!-- Search input section -->
|
||||
<div id="search-input" @click="open">
|
||||
<div id="search-input" @click="open" :class="{'halloween-eyes': eventTheme === 'halloween'}">
|
||||
<div id="halloween-eyes" v-if="eventTheme=== 'halloween' && active">
|
||||
<div class="eye left">
|
||||
<div class="pupil"></div>
|
||||
</div>
|
||||
<div class="eye right">
|
||||
<div class="pupil"></div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Close button visible when search is active -->
|
||||
<button
|
||||
v-if="active"
|
||||
@@ -22,6 +30,7 @@
|
||||
<input
|
||||
id="main-input"
|
||||
class="main-input"
|
||||
:class="{ 'halloween-theme': eventTheme === 'halloween' }"
|
||||
type="text"
|
||||
@keyup.exact="keyup"
|
||||
@input="submit"
|
||||
@@ -34,7 +43,7 @@
|
||||
</div>
|
||||
|
||||
<!-- Search results for desktop -->
|
||||
<div v-show="active" id="results" class="fb-shadow" ref="result">
|
||||
<div v-show="active" id="results" class="fb-shadow" ref="result">
|
||||
<div class="inputWrapper" style="display: flex">
|
||||
<select
|
||||
v-if="multipleSources"
|
||||
@@ -249,7 +258,7 @@ export default {
|
||||
// For other browsers, use regular contextmenu
|
||||
this.$el.addEventListener("contextmenu", this.openContext);
|
||||
}
|
||||
|
||||
|
||||
// Add keyboard event listener for "/" to activate search
|
||||
this.handleKeydown = (event) => {
|
||||
if (event.key === '/' && !state.isSearchActive) {
|
||||
@@ -257,7 +266,7 @@ export default {
|
||||
this.open();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
document.addEventListener('keydown', this.handleKeydown);
|
||||
},
|
||||
beforeUnmount() {
|
||||
@@ -271,13 +280,16 @@ export default {
|
||||
} else {
|
||||
this.$el.removeEventListener("contextmenu", this.openContext);
|
||||
}
|
||||
|
||||
|
||||
// Clean up keyboard event listener
|
||||
if (this.handleKeydown) {
|
||||
document.removeEventListener('keydown', this.handleKeydown);
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
eventTheme() {
|
||||
return getters.eventTheme();
|
||||
},
|
||||
disableSearchOptions() {
|
||||
return state.user.disableSearchOptions;
|
||||
},
|
||||
|
||||
@@ -357,6 +357,31 @@ export default {
|
||||
});
|
||||
}
|
||||
|
||||
// Filter out items being dropped onto themselves or into their own subdirectories
|
||||
items = items.filter(item => {
|
||||
// Skip if source and destination are the same
|
||||
if (item.from === item.to) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Skip if trying to move a directory into itself
|
||||
// Check if the destination path would be within the source path
|
||||
const fromPath = item.from;
|
||||
const destinationDir = this.path;
|
||||
|
||||
// If destination dir is the same as or contains the source path, skip
|
||||
if (fromPath === destinationDir || fromPath.startsWith(destinationDir + '/')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
// If all items were filtered out, silently skip the operation
|
||||
if (items.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
let checkAction = async () => {
|
||||
if (getters.isShare()) {
|
||||
return await publicApi.fetchPub(this.path, shareInfo.hash);
|
||||
@@ -435,9 +460,13 @@ export default {
|
||||
// @ts-ignore
|
||||
this.contextTimeout = setTimeout(() => {
|
||||
if (!this.isSwipe) {
|
||||
mutations.resetSelected();
|
||||
// @ts-ignore
|
||||
mutations.addSelected(this.index);
|
||||
// Only reset selection if this item is not already selected
|
||||
// This prevents resetting selection when trying to open context menu on selected item
|
||||
if (!this.isSelected) {
|
||||
mutations.resetSelected();
|
||||
// @ts-ignore
|
||||
mutations.addSelected(this.index);
|
||||
}
|
||||
}
|
||||
}, 500);
|
||||
},
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="scroll-wrapper" ref="wrapper">
|
||||
<div class="scroll-wrapper" :class="{ 'halloween-theme': eventTheme === 'halloween' }" ref="wrapper">
|
||||
<slot />
|
||||
<div
|
||||
class="custom-scrollbar"
|
||||
@@ -52,6 +52,9 @@ export default {
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
eventTheme() {
|
||||
return getters.eventTheme();
|
||||
},
|
||||
isScrollable() {
|
||||
return getters.isScrollable();
|
||||
},
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
v-if="enabled && hasPrevious"
|
||||
class="nav-zone nav-zone-left"
|
||||
:class="{ moveWithSidebar: moveWithSidebar }"
|
||||
@mousemove="toggleNavigation"
|
||||
@touchstart="(e) => { handleTouchStart(e); toggleNavigation(e); }"
|
||||
@touchmove="handleTouchMove"
|
||||
></div>
|
||||
@@ -13,7 +12,6 @@
|
||||
<div
|
||||
v-if="enabled && hasNext"
|
||||
class="nav-zone nav-zone-right"
|
||||
@mousemove="toggleNavigation"
|
||||
@touchstart="(e) => { handleTouchStart(e); toggleNavigation(e); }"
|
||||
@touchmove="handleTouchMove"
|
||||
></div>
|
||||
@@ -213,6 +211,7 @@ export default {
|
||||
window.addEventListener("touchmove", this.handleDrag, { passive: false });
|
||||
window.addEventListener("touchend", this.endDrag);
|
||||
document.addEventListener("click", this.handleDocumentClick);
|
||||
window.addEventListener("mousemove", this.handleGlobalMouseMove);
|
||||
|
||||
// Calculate 10em threshold in pixels
|
||||
const emSize = parseFloat(getComputedStyle(document.documentElement).fontSize);
|
||||
@@ -238,6 +237,7 @@ export default {
|
||||
window.removeEventListener("touchmove", this.handleDrag);
|
||||
window.removeEventListener("touchend", this.endDrag);
|
||||
document.removeEventListener("click", this.handleDocumentClick);
|
||||
window.removeEventListener("mousemove", this.handleGlobalMouseMove);
|
||||
|
||||
// Clear our local timeout
|
||||
if (this.navigationTimeout) {
|
||||
@@ -255,6 +255,48 @@ export default {
|
||||
const shouldEnable = previewViews.includes(this.currentView);
|
||||
mutations.setNavigationEnabled(shouldEnable);
|
||||
},
|
||||
async checkForUnsavedChanges() {
|
||||
// Check if editor has unsaved changes
|
||||
const editorDirty = state.editorDirty || false;
|
||||
if (!editorDirty) {
|
||||
return true; // No unsaved changes, allow navigation
|
||||
}
|
||||
|
||||
// There are unsaved changes - show prompt
|
||||
return new Promise((resolve) => {
|
||||
mutations.showHover({
|
||||
name: "SaveBeforeExit",
|
||||
confirm: async () => {
|
||||
// Save and proceed
|
||||
try {
|
||||
const saveHandler = state.editorSaveHandler;
|
||||
if (saveHandler && typeof saveHandler === 'function') {
|
||||
await saveHandler();
|
||||
}
|
||||
// Close the prompt after successful save
|
||||
mutations.closeTopHover();
|
||||
resolve(true); // Allow navigation
|
||||
} catch (error) {
|
||||
// Save failed - keep prompt open by not resolving
|
||||
resolve(false); // Block navigation
|
||||
}
|
||||
},
|
||||
discard: () => {
|
||||
// Discard changes and proceed
|
||||
mutations.setEditorDirty(false);
|
||||
// Close the prompt
|
||||
mutations.closeTopHover();
|
||||
resolve(true); // Allow navigation
|
||||
},
|
||||
cancel: () => {
|
||||
// Cancel navigation
|
||||
// Close the prompt
|
||||
mutations.closeTopHover();
|
||||
resolve(false); // Block navigation
|
||||
},
|
||||
});
|
||||
});
|
||||
},
|
||||
async setupNavigationForCurrentItem() {
|
||||
if (!this.enabled || !state.req || state.req.type === 'directory') {
|
||||
// Clear navigation when not applicable
|
||||
@@ -332,9 +374,15 @@ export default {
|
||||
}, 3000);
|
||||
}
|
||||
},
|
||||
prev() {
|
||||
async prev() {
|
||||
if (this.hasPrevious) {
|
||||
this.hoverNav = false;
|
||||
|
||||
// Check for unsaved changes in editor before navigating
|
||||
if (!await this.checkForUnsavedChanges()) {
|
||||
return; // Navigation blocked
|
||||
}
|
||||
|
||||
// Set transitioning state - keeps old req visible until new one loads
|
||||
// Editor and other components check isTransitioning to prevent saves
|
||||
mutations.setNavigationTransitioning(true);
|
||||
@@ -345,9 +393,14 @@ export default {
|
||||
}
|
||||
}
|
||||
},
|
||||
next() {
|
||||
async next() {
|
||||
if (this.hasNext) {
|
||||
this.hoverNav = false;
|
||||
|
||||
// Check for unsaved changes in editor before navigating
|
||||
if (!await this.checkForUnsavedChanges()) {
|
||||
return; // Navigation blocked
|
||||
}
|
||||
|
||||
// Set transitioning state - keeps old req visible until new one loads
|
||||
// Editor and other components check isTransitioning to prevent saves
|
||||
@@ -945,7 +998,37 @@ export default {
|
||||
isDirectory: item.type === 'directory',
|
||||
originalItem: item
|
||||
}));
|
||||
}
|
||||
},
|
||||
handleGlobalMouseMove(event) {
|
||||
// Check if mouse is in the nav zone areas to show navigation buttons
|
||||
if (!this.enabled) return;
|
||||
|
||||
const emSize = parseFloat(getComputedStyle(document.documentElement).fontSize);
|
||||
const zoneWidth = 5 * emSize; // 5em in pixels
|
||||
|
||||
// Check left zone
|
||||
const sidebarOffset = this.moveWithSidebar ? 20 * emSize : 0; // Account for sidebar
|
||||
if (this.hasPrevious && event.clientX >= sidebarOffset && event.clientX <= (sidebarOffset + zoneWidth)) {
|
||||
const viewportHeight = window.innerHeight;
|
||||
const zoneTop = viewportHeight * 0.25; // 25% from top
|
||||
const zoneBottom = viewportHeight * 0.75; // 25% from bottom (75% of height)
|
||||
|
||||
if (event.clientY >= zoneTop && event.clientY <= zoneBottom) {
|
||||
this.toggleNavigation();
|
||||
}
|
||||
}
|
||||
|
||||
// Check right zone
|
||||
if (this.hasNext && event.clientX >= window.innerWidth - zoneWidth) {
|
||||
const viewportHeight = window.innerHeight;
|
||||
const zoneTop = viewportHeight * 0.25; // 25% from top
|
||||
const zoneBottom = viewportHeight * 0.75; // 25% from bottom (75% of height)
|
||||
|
||||
if (event.clientY >= zoneTop && event.clientY <= zoneBottom) {
|
||||
this.toggleNavigation();
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -957,8 +1040,8 @@ export default {
|
||||
top: 25%; /* Start at 25% from top */
|
||||
bottom: 25%; /* End at 25% from bottom (so middle 50%) */
|
||||
width: 5em;
|
||||
pointer-events: none; /* Allow clicks to pass through to content behind */
|
||||
z-index: -1; /* Negative z-index to ensure content appears above */
|
||||
pointer-events: none; /* Allow clicks and interactions to pass through */
|
||||
z-index: -1; /* Behind content, only used for geometric detection */
|
||||
background: transparent; /* Invisible zones for mouse/touch detection */
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
<template>
|
||||
<div class="card-title">
|
||||
<h2>{{ $t("prompts.closeWithActiveUploads") }}</h2>
|
||||
</div>
|
||||
|
||||
<div class="card-content">
|
||||
<p>{{ $t("prompts.closeWithActiveUploadsMessage") }}</p>
|
||||
</div>
|
||||
|
||||
<div class="card-action">
|
||||
<button
|
||||
class="button button--flat button--grey"
|
||||
@click="stayOnPrompt"
|
||||
:aria-label="$t('buttons.stayOnPrompt')"
|
||||
:title="$t('buttons.stayOnPrompt')">
|
||||
{{ $t("buttons.stayOnPrompt") }}
|
||||
</button>
|
||||
<button
|
||||
class="button button--flat button--red"
|
||||
@click="closeAnyway"
|
||||
:aria-label="$t('buttons.closeAnyway')"
|
||||
:title="$t('buttons.closeAnyway')">
|
||||
{{ $t("buttons.closeAnyway") }}
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getters } from "@/store";
|
||||
|
||||
export default {
|
||||
name: "closeWithActiveUploads",
|
||||
computed: {
|
||||
currentPrompt() {
|
||||
return getters.currentPrompt();
|
||||
},
|
||||
},
|
||||
created() {
|
||||
// Listen for ESC key to treat it as "Stay on Prompt"
|
||||
this.escKeyHandler = (event) => {
|
||||
if (event.keyCode === 27 && getters.currentPromptName() === "CloseWithActiveUploads") {
|
||||
event.stopImmediatePropagation();
|
||||
this.stayOnPrompt();
|
||||
}
|
||||
};
|
||||
window.addEventListener("keydown", this.escKeyHandler, true);
|
||||
},
|
||||
beforeUnmount() {
|
||||
window.removeEventListener("keydown", this.escKeyHandler, true);
|
||||
},
|
||||
methods: {
|
||||
stayOnPrompt() {
|
||||
if (this.currentPrompt && this.currentPrompt.cancel) {
|
||||
this.currentPrompt.cancel();
|
||||
}
|
||||
// Don't call closeTopHover here - the cancel callback handles it
|
||||
},
|
||||
closeAnyway() {
|
||||
if (this.currentPrompt && this.currentPrompt.confirm) {
|
||||
this.currentPrompt.confirm();
|
||||
}
|
||||
// Don't call closeTopHover here - the confirm callback handles it
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
<template>
|
||||
<div class="card-title">
|
||||
<h2>{{ title }}</h2>
|
||||
</div>
|
||||
|
||||
<div class="card-content" v-html="body"></div>
|
||||
|
||||
<div class="card-action">
|
||||
<button
|
||||
v-for="(button, index) in displayButtons"
|
||||
:key="index"
|
||||
:class="getButtonClass(button)"
|
||||
@click="handleButtonClick(button)"
|
||||
:aria-label="button.label"
|
||||
:title="button.label"
|
||||
>
|
||||
{{ button.label }}
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mutations } from "@/store";
|
||||
|
||||
export default {
|
||||
name: "generic-prompt",
|
||||
props: {
|
||||
title: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
body: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
buttons: {
|
||||
type: Array,
|
||||
required: false,
|
||||
default: () => [],
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
displayButtons() {
|
||||
// If buttons are provided, use them
|
||||
if (this.buttons && this.buttons.length > 0) {
|
||||
return this.buttons;
|
||||
}
|
||||
// Otherwise, return a default close button
|
||||
return [
|
||||
{
|
||||
label: this.$t('buttons.close'),
|
||||
action: () => {
|
||||
// Just close the prompt
|
||||
},
|
||||
},
|
||||
];
|
||||
},
|
||||
},
|
||||
created() {
|
||||
console.log("Generic component created");
|
||||
},
|
||||
methods: {
|
||||
handleButtonClick(button) {
|
||||
// Execute the button's action
|
||||
if (typeof button.action === 'function') {
|
||||
button.action();
|
||||
}
|
||||
// Close the prompt unless the button specifies to keep it open
|
||||
if (button.keepOpen !== true) {
|
||||
mutations.closeHovers();
|
||||
}
|
||||
},
|
||||
getButtonClass(button) {
|
||||
// Default button classes
|
||||
let classes = 'button button--flat';
|
||||
// Add custom class if provided
|
||||
if (button.className) {
|
||||
classes += ` ${button.className}`;
|
||||
} else if (button.primary) {
|
||||
// Primary button style (default)
|
||||
// No additional class needed
|
||||
} else {
|
||||
// Secondary button style (grey)
|
||||
classes += ' button--grey';
|
||||
}
|
||||
return classes;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -35,6 +35,8 @@ import PlaybackQueue from "./PlaybackQueue.vue";
|
||||
import FileList from "./FileList.vue";
|
||||
import SaveBeforeExit from "./SaveBeforeExit.vue";
|
||||
import CopyPasteConfirm from "./CopyPasteConfirm.vue";
|
||||
import CloseWithActiveUploads from "./CloseWithActiveUploads.vue";
|
||||
import Generic from "./Generic.vue";
|
||||
import { state, getters, mutations } from "@/store"; // Import your custom store
|
||||
|
||||
export default {
|
||||
@@ -66,6 +68,8 @@ export default {
|
||||
"file-list": FileList,
|
||||
SaveBeforeExit,
|
||||
CopyPasteConfirm,
|
||||
CloseWithActiveUploads,
|
||||
generic: Generic,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@@ -103,6 +107,9 @@ export default {
|
||||
case "replace":
|
||||
prompt.showConfirm(event);
|
||||
break;
|
||||
case "generic":
|
||||
prompt.submit();
|
||||
break;
|
||||
case "CopyPasteConfirm":
|
||||
prompt.confirm();
|
||||
break;
|
||||
|
||||
@@ -117,7 +117,6 @@
|
||||
<input class="input" type="text" v-model.trim="allowedUsernames" :placeholder="$t('share.allowedUsernamesPlaceholder')" />
|
||||
</div>
|
||||
<ToggleSwitch v-if="shareType === 'normal' && onlyOfficeAvailable" class="item" v-model="enableOnlyOffice" :name="$t('share.enableOnlyOffice')" :description="$t('share.enableOnlyOfficeDescription')" />
|
||||
<ToggleSwitch v-if="shareType === 'normal' && onlyOfficeAvailable" class="item" v-model="enableOnlyOfficeEditing" :name="$t('share.enableOnlyOfficeEditing')" :description="$t('share.enableOnlyOfficeEditingDescription')" />
|
||||
<p>
|
||||
{{ $t("share.enforceDarkLightMode") }}
|
||||
<i
|
||||
@@ -365,8 +364,7 @@ export default {
|
||||
disableSidebar: false,
|
||||
enforceDarkLightMode: "default",
|
||||
viewMode: "normal",
|
||||
enableOnlyOffice: false,
|
||||
enableOnlyOfficeEditing: false,
|
||||
enableOnlyOffice: false,
|
||||
shareType: "normal",
|
||||
extractEmbeddedSubtitles: false,
|
||||
/** @type {Share | null} */
|
||||
@@ -469,7 +467,6 @@ export default {
|
||||
this.enforceDarkLightMode = this.link.enforceDarkLightMode || "default";
|
||||
this.viewMode = this.link.viewMode || "normal";
|
||||
this.enableOnlyOffice = this.link.enableOnlyOffice || false;
|
||||
this.enableOnlyOfficeEditing = this.link.enableOnlyOfficeEditing || false;
|
||||
this.shareType = this.link.shareType || "normal";
|
||||
this.extractEmbeddedSubtitles = this.link.extractEmbeddedSubtitles || false;
|
||||
//this.viewMode = this.link.viewMode || "normal";
|
||||
@@ -562,7 +559,6 @@ export default {
|
||||
enforceDarkLightMode: this.enforceDarkLightMode,
|
||||
viewMode: this.viewMode,
|
||||
enableOnlyOffice: this.enableOnlyOffice,
|
||||
enableOnlyOfficeEditing: this.enableOnlyOfficeEditing,
|
||||
shareType: this.shareType,
|
||||
extractEmbeddedSubtitles: this.extractEmbeddedSubtitles,
|
||||
};
|
||||
@@ -638,7 +634,6 @@ export default {
|
||||
this.enforceDarkLightMode = link.enforceDarkLightMode || "default";
|
||||
this.viewMode = link.viewMode || "normal";
|
||||
this.enableOnlyOffice = link.enableOnlyOffice || false;
|
||||
this.enableOnlyOfficeEditing = link.enableOnlyOfficeEditing || false;
|
||||
this.shareType = link.shareType || "normal";
|
||||
this.extractEmbeddedSubtitles = link.extractEmbeddedSubtitles || false;
|
||||
// Store the link being edited
|
||||
|
||||
@@ -315,11 +315,7 @@ export default {
|
||||
}
|
||||
};
|
||||
|
||||
const isUploading = computed(() =>
|
||||
files.value.some(
|
||||
(file) => file.status === "pending" || file.status === "uploading"
|
||||
)
|
||||
);
|
||||
const isUploading = computed(() => state.upload.isUploading);
|
||||
|
||||
watch(isUploading, (active) => {
|
||||
if (active) {
|
||||
@@ -359,6 +355,7 @@ export default {
|
||||
);
|
||||
|
||||
const close = () => {
|
||||
// closeHovers will automatically check isUploading and show warning if needed
|
||||
mutations.closeHovers();
|
||||
};
|
||||
|
||||
@@ -372,6 +369,16 @@ export default {
|
||||
}
|
||||
};
|
||||
|
||||
const handleBeforeUnload = (event) => {
|
||||
// Warn user if they try to leave/refresh the page while uploads are active
|
||||
if (isUploading.value) {
|
||||
event.preventDefault();
|
||||
// Chrome requires returnValue to be set
|
||||
event.returnValue = '';
|
||||
return '';
|
||||
}
|
||||
};
|
||||
|
||||
const processItems = async (items) => {
|
||||
// When items are passed as a prop from ListingView, they can be either
|
||||
// an array of DataTransferItem (from drag and drop) or an array of File (from input).
|
||||
@@ -392,6 +399,7 @@ export default {
|
||||
|
||||
onMounted(async () => {
|
||||
document.addEventListener("visibilitychange", handleVisibilityChange);
|
||||
window.addEventListener("beforeunload", handleBeforeUnload);
|
||||
uploadManager.setOnConflict(handleConflict);
|
||||
if (props.initialItems) {
|
||||
await processItems(props.initialItems);
|
||||
@@ -400,6 +408,7 @@ export default {
|
||||
|
||||
onUnmounted(() => {
|
||||
document.removeEventListener("visibilitychange", handleVisibilityChange);
|
||||
window.removeEventListener("beforeunload", handleBeforeUnload);
|
||||
uploadManager.setOnConflict(() => {}); // cleanup
|
||||
releaseWakeLock();
|
||||
});
|
||||
|
||||
@@ -53,6 +53,7 @@
|
||||
<i class="material-icons">ads_click</i>
|
||||
</div>
|
||||
<div
|
||||
aria-label="Toggle Theme"
|
||||
v-if="darkModeTogglePossible"
|
||||
class="clickable"
|
||||
:class="{ active: user?.darkMode }"
|
||||
|
||||
@@ -5,10 +5,11 @@
|
||||
--dark-red: #D32F2F;
|
||||
--moon-grey: #f2f2f2;
|
||||
--icon-red: #da4453;
|
||||
--icon-orange: #f47750;
|
||||
--icon-orange: #ff8c00;
|
||||
--icon-yellow: #fdbc4b;
|
||||
--icon-green: #2ecc71;
|
||||
--icon-blue: #1d99f3;
|
||||
--icon-violet: #9b59b6;
|
||||
--primaryColor: var(--blue);
|
||||
--divider: lightgray;
|
||||
}
|
||||
@@ -0,0 +1,759 @@
|
||||
/* Halloween Theme Styles - Only apply when halloween-theme class is present */
|
||||
|
||||
/* Halloween-specific CSS variables */
|
||||
.halloween-theme {
|
||||
--primaryColor: var(--icon-orange) !important;
|
||||
--halloween-dark-orange: #ff6600;
|
||||
--halloween-black: #1a1a1a;
|
||||
--halloween-gray: #cccccc;
|
||||
}
|
||||
|
||||
body.halloween-theme,
|
||||
.scroll-wrapper.halloween-theme,
|
||||
#login.halloween-theme {
|
||||
background: black !important;
|
||||
background-color: black !important;
|
||||
}
|
||||
|
||||
/* Tiled Halloween pattern background for scroll wrapper */
|
||||
.scroll-wrapper.halloween-theme {
|
||||
background-image: url("../assets/halloween-pattern.svg") !important;
|
||||
background-repeat: repeat !important;
|
||||
}
|
||||
|
||||
#login.halloween-theme {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* Halloween Background */
|
||||
.halloween-background {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
pointer-events: none;
|
||||
z-index: 0;
|
||||
background: radial-gradient(circle at center, rgba(0, 0, 0, .5) 0%, #000000 70%);
|
||||
}
|
||||
|
||||
/* Tombstone Shape for Login Card */
|
||||
.login-card.tombstone {
|
||||
position: relative;
|
||||
/* Updated background for more subtle levels of detail/texture */
|
||||
background:
|
||||
/* Subtle rock grain/texture */
|
||||
radial-gradient(ellipse at 50% 50%, rgba(0, 0, 0, 0.05) 0%, transparent 80%),
|
||||
/* Light gray moss/ageing */
|
||||
linear-gradient(170deg, rgba(168, 168, 168, 0.05) 5%, transparent 60%),
|
||||
linear-gradient(135deg, rgba(0, 0, 0, 0.1) 0%, transparent 100%),
|
||||
linear-gradient(225deg, rgba(0, 0, 0, 0.1) 0%, transparent 100%),
|
||||
linear-gradient(180deg, #e8e8e8 0%, #c0c0c0 50%, #a8a8a8 100%) !important;
|
||||
border: 4px solid #888 !important;
|
||||
border-radius: 0 0 20px 20px !important;
|
||||
box-shadow:
|
||||
0 15px 50px rgba(255, 140, 0, 0.6),
|
||||
inset 0 2px 20px rgba(0, 0, 0, 0.3),
|
||||
inset 3px 3px 10px rgba(255, 255, 255, 0.2),
|
||||
0 0 40px rgba(255, 140, 0, 0.4) !important;
|
||||
padding: 2.5em 2em 2em 2em !important;
|
||||
z-index: 10;
|
||||
position: relative;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
/* Tombstone rounded top */
|
||||
.login-card.tombstone::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: -70px;
|
||||
left: -4px;
|
||||
right: -4px;
|
||||
height: 70px;
|
||||
background:
|
||||
linear-gradient(135deg, rgba(0, 0, 0, 0.1) 0%, transparent 100%),
|
||||
linear-gradient(225deg, rgba(0, 0, 0, 0.1) 0%, transparent 100%),
|
||||
linear-gradient(180deg, #e8e8e8 0%, #c0c0c0 100%);
|
||||
border: 4px solid #888;
|
||||
border-bottom: none;
|
||||
border-radius: 50% 50% 0 0 / 100% 100% 0 0;
|
||||
box-shadow:
|
||||
inset 0 2px 20px rgba(0, 0, 0, 0.3),
|
||||
inset 3px 3px 10px rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
/* R.I.P. text */
|
||||
.login-card.tombstone::after {
|
||||
content: 'R.I.P.';
|
||||
position: absolute;
|
||||
top: -55px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
font-family: 'Times New Roman', serif;
|
||||
font-size: 2.2em;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
text-shadow:
|
||||
2px 2px 4px rgba(0, 0, 0, 0.5),
|
||||
inset -1px -1px 2px rgba(255, 255, 255, 0.3);
|
||||
letter-spacing: 0.3em;
|
||||
z-index: 1;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
/* Cracks in the tombstone - Increased detail */
|
||||
.halloween-theme .login-card.tombstone {
|
||||
background-image:
|
||||
/* Fine, subtle cracks */
|
||||
linear-gradient(45deg, transparent 30%, rgba(0, 0, 0, 0.15) 30.2%, transparent 30.5%),
|
||||
linear-gradient(135deg, transparent 60%, rgba(0, 0, 0, 0.12) 60.3%, transparent 60.6%),
|
||||
linear-gradient(75deg, transparent 45%, rgba(0, 0, 0, 0.1) 45.1%, transparent 45.2%),
|
||||
linear-gradient(165deg, transparent 70%, rgba(0, 0, 0, 0.08) 70.4%, transparent 70.8%),
|
||||
/* Deeper, more noticeable crack */
|
||||
linear-gradient(-30deg, transparent 55%, rgba(0, 0, 0, 0.3) 55.5%, transparent 56.5%),
|
||||
linear-gradient(150deg, transparent 35%, rgba(0, 0, 0, 0.35) 35.5%, transparent 36.5%),
|
||||
/* Original background gradients */
|
||||
linear-gradient(135deg, rgba(0, 0, 0, 0.1) 0%, transparent 100%),
|
||||
linear-gradient(225deg, rgba(0, 0, 0, 0.1) 0%, transparent 100%),
|
||||
linear-gradient(180deg, #e8e8e8 0%, #c0c0c0 50%, #a8a8a8 100%);
|
||||
}
|
||||
|
||||
/* Tombstone Field Labels */
|
||||
.tombstone-field {
|
||||
position: relative;
|
||||
margin-bottom: 1.8em;
|
||||
}
|
||||
|
||||
.tombstone-label {
|
||||
display: block;
|
||||
font-family: 'Times New Roman', serif;
|
||||
font-size: 1.1em;
|
||||
font-weight: bold;
|
||||
color: #444;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.25em;
|
||||
margin-bottom: 0.6em;
|
||||
text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.3);
|
||||
text-align: center;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.tombstone-field:first-of-type .tombstone-label::before {
|
||||
content: '⚰ BORN ⚰';
|
||||
display: block;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.tombstone-field:last-of-type .tombstone-label::before {
|
||||
content: '† DIED †';
|
||||
display: block;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.tombstone-field .input {
|
||||
background: rgba(0, 0, 0, 0.7) !important;
|
||||
border: 3px solid #ff8c00 !important;
|
||||
color: #ff8c00 !important;
|
||||
text-shadow: 0 0 8px rgba(255, 140, 0, 0.6);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.tombstone-field .input:focus {
|
||||
border-color: #ff6600 !important;
|
||||
box-shadow: 0 0 20px rgba(255, 140, 0, 0.8) !important;
|
||||
background: rgba(0, 0, 0, 0.8) !important;
|
||||
}
|
||||
|
||||
.tombstone-field .input::placeholder {
|
||||
color: #666 !important;
|
||||
}
|
||||
|
||||
/* Floating Clouds - Twice as big and with light coming from them */
|
||||
.cloud {
|
||||
position: absolute;
|
||||
background: rgba(40, 40, 40, 0.7);
|
||||
border-radius: 400px;
|
||||
opacity: 0.9;
|
||||
animation: float-cloud linear infinite;
|
||||
filter: blur(5px);
|
||||
box-shadow:
|
||||
0 0 10px rgba(255, 140, 0, 0.2),
|
||||
0 0 60px rgba(255, 255, 255, 0.3),
|
||||
0 0 30px rgba(40, 40, 40, 0.6);
|
||||
transform: scale(2);
|
||||
}
|
||||
|
||||
.cloud::before,
|
||||
.cloud::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
background: rgba(40, 40, 40, 0.7);
|
||||
border-radius: 400px;
|
||||
filter: blur(4px);
|
||||
}
|
||||
|
||||
/* --- CLOUD START POSITIONS ADJUSTED FOR RANDOMNESS --- */
|
||||
|
||||
.cloud-1 {
|
||||
width: 250px;
|
||||
height: 80px;
|
||||
/* Adjusted top and left for scatter */
|
||||
top: 15%;
|
||||
left: 40%;
|
||||
/* Starts visible on the screen */
|
||||
animation-duration: 80s;
|
||||
}
|
||||
|
||||
.cloud-1::before {
|
||||
width: 120px;
|
||||
height: 100px;
|
||||
top: -50px;
|
||||
left: 40px;
|
||||
}
|
||||
|
||||
.cloud-1::after {
|
||||
width: 140px;
|
||||
height: 90px;
|
||||
top: -40px;
|
||||
right: 30px;
|
||||
}
|
||||
|
||||
.cloud-2 {
|
||||
width: 300px;
|
||||
height: 100px;
|
||||
/* Adjusted top and left for scatter */
|
||||
top: 50%;
|
||||
left: -150px;
|
||||
/* Starts slightly off-screen to the left */
|
||||
animation-duration: 100s;
|
||||
animation-delay: 20s;
|
||||
}
|
||||
|
||||
.cloud-2::before {
|
||||
width: 140px;
|
||||
height: 120px;
|
||||
top: -60px;
|
||||
left: 50px;
|
||||
}
|
||||
|
||||
.cloud-2::after {
|
||||
width: 160px;
|
||||
height: 110px;
|
||||
top: -50px;
|
||||
right: 40px;
|
||||
}
|
||||
|
||||
.cloud-3 {
|
||||
width: 200px;
|
||||
height: 70px;
|
||||
/* Adjusted top and left for scatter */
|
||||
top: 75%;
|
||||
left: 80%;
|
||||
/* Starts visible on the screen far to the right */
|
||||
animation-duration: 70s;
|
||||
animation-delay: 40s;
|
||||
}
|
||||
|
||||
.cloud-3::before {
|
||||
width: 100px;
|
||||
height: 80px;
|
||||
top: -40px;
|
||||
left: 30px;
|
||||
}
|
||||
|
||||
.cloud-3::after {
|
||||
width: 110px;
|
||||
height: 76px;
|
||||
top: -36px;
|
||||
right: 25px;
|
||||
}
|
||||
|
||||
@keyframes float-cloud {
|
||||
0% {
|
||||
transform: translateX(0) scale(2);
|
||||
}
|
||||
|
||||
100% {
|
||||
/* Kept the final position far to the right so they all eventually move off-screen */
|
||||
transform: translateX(calc(100vw + 400px)) scale(2);
|
||||
}
|
||||
}
|
||||
|
||||
/* Lightning Flash Effect - Adjusted to match cloud light */
|
||||
.lightning-flash {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
/* Made light more focused, simulating it coming from above/clouds */
|
||||
background: radial-gradient(circle at 50% 10%, rgba(255, 255, 255, 0.95), rgba(255, 220, 100, 0.8) 15%, transparent 50%);
|
||||
opacity: 0;
|
||||
animation: lightning-flash-animation 12s infinite;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
@keyframes lightning-flash-animation {
|
||||
0%, 10%, 100% {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
10.1% {
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
10.15% {
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
10.2%, 10.35% {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
10.4% {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
10.5% {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Lightning Bolts */
|
||||
.lightning-bolt {
|
||||
position: absolute;
|
||||
width: 50px;
|
||||
height: 250px;
|
||||
opacity: 0;
|
||||
filter: drop-shadow(0 0 15px #fff) drop-shadow(0 0 30px #ffd700) drop-shadow(0 0 45px #ff8c00);
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.lightning-1 {
|
||||
top: 8%;
|
||||
left: 30%;
|
||||
animation: lightning-strike 12s infinite;
|
||||
}
|
||||
|
||||
.lightning-2 {
|
||||
top: 10%;
|
||||
right: 25%;
|
||||
animation: lightning-strike 12s infinite;
|
||||
animation-delay: 0.05s;
|
||||
}
|
||||
|
||||
@keyframes lightning-strike {
|
||||
0%, 10%, 100% {
|
||||
opacity: 0;
|
||||
transform: scaleY(0);
|
||||
transform-origin: top;
|
||||
}
|
||||
|
||||
10.1% {
|
||||
opacity: 1;
|
||||
transform: scaleY(1);
|
||||
}
|
||||
|
||||
10.15% {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
10.2%, 10.35% {
|
||||
opacity: 0;
|
||||
transform: scaleY(0.8);
|
||||
}
|
||||
|
||||
10.4% {
|
||||
opacity: 1;
|
||||
transform: scaleY(1);
|
||||
}
|
||||
|
||||
10.5% {
|
||||
opacity: 0;
|
||||
transform: scaleY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.lightning-bolt path {
|
||||
fill: #fff;
|
||||
animation: bolt-flicker 12s infinite;
|
||||
}
|
||||
|
||||
@keyframes bolt-flicker {
|
||||
0%, 10%, 100% {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
10.1% {
|
||||
opacity: 1;
|
||||
fill: #fff;
|
||||
}
|
||||
|
||||
10.15% {
|
||||
opacity: 0.7;
|
||||
fill: #ffd700;
|
||||
}
|
||||
|
||||
10.2%, 10.35% {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
10.4% {
|
||||
opacity: 1;
|
||||
fill: #fff;
|
||||
}
|
||||
|
||||
10.5% {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Halloween Decorations Container */
|
||||
.halloween-decorations {
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
pointer-events: none;
|
||||
z-index: 5;
|
||||
}
|
||||
|
||||
/* Spooky Cat */
|
||||
.halloween-cat {
|
||||
position: absolute;
|
||||
bottom: 5%;
|
||||
left: 8%;
|
||||
width: 180px;
|
||||
height: 200px;
|
||||
filter: drop-shadow(0 0 20px rgba(255, 140, 0, 0.5));
|
||||
animation: cat-tail-swish 3s ease-in-out infinite;
|
||||
z-index: 6;
|
||||
}
|
||||
|
||||
@keyframes cat-tail-swish {
|
||||
0%, 100% {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
50% {
|
||||
transform: translateY(-5px);
|
||||
}
|
||||
}
|
||||
|
||||
.halloween-cat .cat-eye {
|
||||
animation: cat-blink 5s infinite;
|
||||
}
|
||||
|
||||
@keyframes cat-blink {
|
||||
0%, 96%, 100% {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
97%, 99% {
|
||||
opacity: 0.1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Skeleton */
|
||||
.halloween-skeleton {
|
||||
position: absolute;
|
||||
bottom: 3%;
|
||||
right: 8%;
|
||||
width: 160px;
|
||||
height: 240px;
|
||||
filter: drop-shadow(0 0 20px rgba(255, 140, 0, 0.4));
|
||||
animation: skeleton-wobble 3s ease-in-out infinite;
|
||||
z-index: 6;
|
||||
}
|
||||
|
||||
@keyframes skeleton-wobble {
|
||||
0%, 100% {
|
||||
transform: translateY(0) rotate(-5deg);
|
||||
}
|
||||
|
||||
50% {
|
||||
transform: translateY(-15px) rotate(5deg);
|
||||
}
|
||||
}
|
||||
|
||||
/* Skeleton head bob */
|
||||
.halloween-skeleton .skeleton-head {
|
||||
animation: head-bob 2s ease-in-out infinite;
|
||||
transform-origin: center bottom;
|
||||
}
|
||||
|
||||
@keyframes head-bob {
|
||||
0%, 100% {
|
||||
transform: rotate(-5deg);
|
||||
}
|
||||
|
||||
50% {
|
||||
transform: rotate(5deg);
|
||||
}
|
||||
}
|
||||
|
||||
/* Additional Effects for Login Card in Halloween Theme */
|
||||
.halloween-theme .login-card.tombstone .button {
|
||||
background: linear-gradient(135deg, #1a1a1a 0%, #000000 100%) !important;
|
||||
border: 3px solid #ff8c00 !important;
|
||||
color: #ff8c00 !important;
|
||||
font-weight: bold;
|
||||
text-shadow: 0 0 10px rgba(255, 140, 0, 0.8);
|
||||
box-shadow: 0 4px 20px rgba(255, 140, 0, 0.5) !important;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.halloween-theme .login-card.tombstone .button:hover {
|
||||
background: linear-gradient(135deg, #ff8c00 0%, #ff6600 100%) !important;
|
||||
border-color: #ffa500 !important;
|
||||
color: #000 !important;
|
||||
text-shadow: 0 0 15px rgba(255, 255, 255, 0.9);
|
||||
box-shadow: 0 6px 30px rgba(255, 140, 0, 0.8) !important;
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
/* Brand Icon Glow */
|
||||
.halloween-theme .login-brand i {
|
||||
color: #ff8c00 !important;
|
||||
filter: drop-shadow(0 0 20px rgba(255, 140, 0, 0.8));
|
||||
animation: icon-pulse 3s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes icon-pulse {
|
||||
0%, 100% {
|
||||
filter: drop-shadow(0 0 20px rgba(255, 140, 0, 0.8));
|
||||
}
|
||||
|
||||
50% {
|
||||
filter: drop-shadow(0 0 35px rgba(255, 69, 0, 1));
|
||||
}
|
||||
}
|
||||
|
||||
/* Brand Text */
|
||||
.halloween-theme .brand-text h3 {
|
||||
color: #ff8c00 !important;
|
||||
text-shadow:
|
||||
2px 2px 4px rgba(0, 0, 0, 0.8),
|
||||
0 0 15px rgba(255, 140, 0, 0.6);
|
||||
font-family: 'Courier New', monospace;
|
||||
letter-spacing: 0.1em;
|
||||
}
|
||||
|
||||
/* Error Message */
|
||||
.halloween-theme .wrong-login {
|
||||
background: rgba(0, 0, 0, 0.8) !important;
|
||||
border: 2px solid #ff8c00 !important;
|
||||
color: #ff8c00 !important;
|
||||
box-shadow: 0 0 20px rgba(255, 140, 0, 0.5);
|
||||
}
|
||||
|
||||
/* Loading Spinner */
|
||||
.halloween-theme .loading-spinner i {
|
||||
color: #ff8c00 !important;
|
||||
filter: drop-shadow(0 0 10px rgba(255, 140, 0, 0.8));
|
||||
}
|
||||
|
||||
/* Mobile Responsiveness */
|
||||
@media (max-width: 768px) {
|
||||
.halloween-cat {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
bottom: 5%;
|
||||
left: 5%;
|
||||
}
|
||||
|
||||
.halloween-skeleton {
|
||||
width: 100px;
|
||||
height: 150px;
|
||||
bottom: 3%;
|
||||
right: 5%;
|
||||
}
|
||||
|
||||
.login-card.tombstone::after {
|
||||
font-size: 1.4em;
|
||||
top: -40px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.halloween-cat {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
}
|
||||
|
||||
.halloween-skeleton {
|
||||
width: 70px;
|
||||
height: 110px;
|
||||
}
|
||||
|
||||
.cloud {
|
||||
/* Adjusted scale for mobile to compensate for the overall increase */
|
||||
transform: scale(1.4);
|
||||
}
|
||||
|
||||
@keyframes float-cloud {
|
||||
0% {
|
||||
transform: translateX(0) scale(1.4);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: translateX(calc(100vw + 400px)) scale(1.4);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#search-input {
|
||||
/* ... existing search-input styles ... */
|
||||
background-color: rgba(100, 100, 100, 0.2);
|
||||
display: flex;
|
||||
height: 100%;
|
||||
padding: 0em 0.75em;
|
||||
border-style: solid;
|
||||
border-radius: 1em;
|
||||
border-style: unset;
|
||||
border-width: 1px;
|
||||
align-items: center;
|
||||
height: 3em;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* --- HALLOWEEN EYES STYLES --- */
|
||||
|
||||
.halloween-eyes {
|
||||
position: relative;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* Position the input field above the eyes */
|
||||
.main-input.halloween-theme {
|
||||
position: relative;
|
||||
z-index: 3 !important;
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
#halloween-eyes {
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
height: 30%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 1em;
|
||||
pointer-events: none;
|
||||
z-index: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.eye {
|
||||
width: 45%;
|
||||
height: 80%;
|
||||
background: var(--icon-orange);
|
||||
border-radius: 50% / 60% 60% 40% 40%;
|
||||
position: relative;
|
||||
overflow: hidden; /* Ensure eyelids don't overflow when moving */
|
||||
box-shadow:
|
||||
0 0 10px var(--icon-orange),
|
||||
0 0 30px rgba(255, 255, 0, 0.8),
|
||||
inset 0 0 10px rgba(0,0,0,0.3);
|
||||
/* Removed 'eye-blink' from here, will use pseudo-elements for blink */
|
||||
animation:
|
||||
eye-look 4s infinite alternate ease-in-out;
|
||||
}
|
||||
|
||||
/* NEW: Eyelids using pseudo-elements */
|
||||
.eye::before,
|
||||
.eye::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 50%; /* Half the eye height for each lid */
|
||||
background: #1a1a1a; /* Black for the eyelid */
|
||||
z-index: 1; /* Above the pupil, below the main input */
|
||||
/* Cat-eye shape for eyelids */
|
||||
border-radius: 50% / 60% 60% 40% 40%;
|
||||
transform-origin: center center;
|
||||
animation: lid-blink 5s infinite; /* Eyelid blink animation */
|
||||
}
|
||||
|
||||
.eye::before { /* Top eyelid */
|
||||
top: 0;
|
||||
/* Mask out the bottom half of the eyelid to blend */
|
||||
clip-path: inset(0 0 50% 0);
|
||||
transform: translateY(-100%) scaleY(1.2); /* Start fully open, slightly above eye */
|
||||
/* Ensure the rounded top of the lid is visible */
|
||||
border-bottom-left-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
}
|
||||
|
||||
.eye::after { /* Bottom eyelid */
|
||||
bottom: 0;
|
||||
/* Mask out the top half of the eyelid to blend */
|
||||
clip-path: inset(50% 0 0 0);
|
||||
transform: translateY(100%) scaleY(1.2); /* Start fully open, slightly below eye */
|
||||
/* Ensure the rounded bottom of the lid is visible */
|
||||
border-top-left-radius: 0;
|
||||
border-top-right-radius: 0;
|
||||
}
|
||||
|
||||
|
||||
/* Adjust animation delay for the right eye to be slightly desynced */
|
||||
.eye.right {
|
||||
animation-delay: 0.1s;
|
||||
}
|
||||
.eye.right .pupil {
|
||||
animation-delay: 0.3s;
|
||||
}
|
||||
.eye.right::before, .eye.right::after {
|
||||
animation-delay: 0.2s; /* Slightly delay eyelid blink */
|
||||
}
|
||||
|
||||
.pupil {
|
||||
width: 10%;
|
||||
height: 70%;
|
||||
background: #1a1a1a;
|
||||
border-radius: 50%;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
animation: pupil-look 4s infinite alternate ease-in-out;
|
||||
}
|
||||
|
||||
/* Adjust pupil movement for cat eyes */
|
||||
@keyframes pupil-look {
|
||||
0% { transform: translate(-100%, -50%); }
|
||||
50% { transform: translate(0%, -50%); }
|
||||
75% { transform: translate(-50%, -50%); }
|
||||
100% { transform: translate(-100%, -50%); }
|
||||
}
|
||||
|
||||
/* NEW: Eyelid blinking animation */
|
||||
@keyframes lid-blink-top {
|
||||
0%, 20%, 40%, 60%, 80%, 100% {
|
||||
/* Top lid is up and open */
|
||||
transform: translateY(-100%) scaleY(1.2);
|
||||
}
|
||||
18%, 22%, 38%, 42%, 58%, 62%, 78%, 82% {
|
||||
/* Top lid moves down to cover the eye */
|
||||
transform: translateY(0%) scaleY(1.2);
|
||||
}
|
||||
}
|
||||
@keyframes lid-blink-bottom {
|
||||
0%, 20%, 40%, 60%, 80%, 100% {
|
||||
/* Bottom lid is down and open */
|
||||
transform: translateY(100%) scaleY(1.2);
|
||||
}
|
||||
18%, 22%, 38%, 42%, 58%, 62%, 78%, 82% {
|
||||
/* Bottom lid moves up to cover the eye */
|
||||
transform: translateY(0%) scaleY(1.2);
|
||||
}
|
||||
}
|
||||
@@ -42,6 +42,9 @@ body.rtl #listingView {
|
||||
width: var(--item-width);
|
||||
height: var(--item-height);
|
||||
border-radius: 1em;
|
||||
background: var(--item-secondary);
|
||||
color: var(--textPrimary);
|
||||
border-color: var(--divider);
|
||||
}
|
||||
|
||||
#listingView .item div:last-of-type {
|
||||
@@ -445,4 +448,4 @@ body.rtl #listingView {
|
||||
|
||||
.half-selected {
|
||||
border-color: var(--primaryColor) !important;
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,7 @@
|
||||
@import "./dashboard.css";
|
||||
@import "./login.css";
|
||||
@import "./mobile.css";
|
||||
@import "./halloween.css";
|
||||
|
||||
.link {
|
||||
color: var(--blue);
|
||||
|
||||
@@ -61,7 +61,11 @@
|
||||
"selectAll": "اختر الكل",
|
||||
"discardAndExit": "التخلص والخروج",
|
||||
"saveAndExit": "الحفظ والخروج",
|
||||
"keepEditing": "استمر في التحرير"
|
||||
"keepEditing": "استمر في التحرير",
|
||||
"stayOnPrompt": "ابق في مكانك",
|
||||
"closeAnyway": "إغلاق على أي حال",
|
||||
"disable": "تعطيل",
|
||||
"confirm": "تأكيد"
|
||||
},
|
||||
"download": {
|
||||
"downloadFile": "Download File",
|
||||
@@ -220,7 +224,9 @@
|
||||
"copyItemQuestion": "نسخ هذا العنصر إلى الموقع الجديد؟",
|
||||
"moveItemQuestion": "نقل هذا العنصر إلى الموقع الجديد؟",
|
||||
"sourcePath": "المصدر:",
|
||||
"destinationPath": "الوجهة"
|
||||
"destinationPath": "الوجهة",
|
||||
"closeWithActiveUploadsMessage": "تحذير: لديك تحميلات نشطة وقد يؤدي إغلاق مطالبة التحميل إلى تحميلات غير مكتملة. ماذا تريد أن تفعل؟",
|
||||
"closeWithActiveUploads": "التحميلات النشطة"
|
||||
},
|
||||
"search": {
|
||||
"images": "الصور",
|
||||
@@ -430,8 +436,6 @@
|
||||
"light": "خفيف",
|
||||
"enableOnlyOffice": "تمكين عارض OnlyOffice فقط",
|
||||
"enableOnlyOfficeDescription": "السماح بعرض ملفات المكتب باستخدام OnlyOffice في هذه المشاركة.",
|
||||
"enableOnlyOfficeEditing": "تمكين التحرير في المكتب فقط",
|
||||
"enableOnlyOfficeEditingDescription": "السماح بتحرير الملفات المكتبية باستخدام OnlyOffice في هذه الحصة.",
|
||||
"titleDefault": "الملفات المشتركة - {title}",
|
||||
"defaultViewMode": "وضع العرض الافتراضي",
|
||||
"defaultViewModeDescription": "تعيين وضع العرض الافتراضي للمستخدمين المجهولين الذين يشاهدون هذه المشاركة. لا يزال بإمكان المستخدمين المسجلين الدخول تغيير تفضيلات العرض الخاصة بهم.",
|
||||
@@ -639,5 +643,9 @@
|
||||
"changePlaybackMode": "تغيير وضع التشغيل",
|
||||
"changePlaybackModeHint": "غير وضع التشغيل لرؤية المزيد من الملفات",
|
||||
"emptyQueue": "قائمة التشغيل فارغة"
|
||||
},
|
||||
"halloween": {
|
||||
"title": "عيد هالوين سعيد!",
|
||||
"body": "تم تطبيق سمة الهالوين. يمكنك تعطيله إذا لم يعجبك، أو تعطيله بشكل دائم في إعدادات الملف الشخصي عن طريق تحديد أي سمة أخرى."
|
||||
}
|
||||
}
|
||||
@@ -61,7 +61,11 @@
|
||||
"selectAll": "Vyberte vše",
|
||||
"discardAndExit": "Vyřazení a ukončení",
|
||||
"saveAndExit": "Uložit a ukončit",
|
||||
"keepEditing": "Pokračovat v úpravách"
|
||||
"keepEditing": "Pokračovat v úpravách",
|
||||
"stayOnPrompt": "Zůstaňte na",
|
||||
"closeAnyway": "Stejně zavřít",
|
||||
"disable": "Zakázat",
|
||||
"confirm": "Potvrďte"
|
||||
},
|
||||
"download": {
|
||||
"downloadFile": "Stáhnout soubor",
|
||||
@@ -195,8 +199,6 @@
|
||||
"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}",
|
||||
"defaultViewMode": "Výchozí režim zobrazení",
|
||||
"defaultViewModeDescription": "Nastavení výchozího režimu zobrazení pro anonymní uživatele prohlížející tuto sdílenou složku. Přihlášení uživatelé mohou stále měnit předvolby zobrazení.",
|
||||
@@ -288,7 +290,9 @@
|
||||
"copyItemQuestion": "Zkopírovat tuto položku do nového umístění?",
|
||||
"moveItemQuestion": "Přesunout tuto položku na nové místo?",
|
||||
"sourcePath": "Zdroj:",
|
||||
"destinationPath": "Cílová destinace:"
|
||||
"destinationPath": "Cílová destinace:",
|
||||
"closeWithActiveUploadsMessage": "Varování: Upozornění: Máte aktivní nahrávání a zavření výzvy k nahrávání by mohlo vést k nedokončenému nahrávání. Co byste chtěli udělat?",
|
||||
"closeWithActiveUploads": "Aktivní nahrávání"
|
||||
},
|
||||
"search": {
|
||||
"images": "Obrázky",
|
||||
@@ -639,5 +643,9 @@
|
||||
"changePlaybackMode": "Změnit režim přehrávání",
|
||||
"changePlaybackModeHint": "Změňte režim přehrávání, abyste viděli více souborů",
|
||||
"emptyQueue": "Fronta je prázdná"
|
||||
},
|
||||
"halloween": {
|
||||
"title": "Šťastný Halloween!",
|
||||
"body": "Bylo použito téma Halloween. Pokud se vám nelíbí, můžete jej vypnout nebo trvale zakázat v nastavení profilu výběrem jiného motivu."
|
||||
}
|
||||
}
|
||||
+38
-30
@@ -61,7 +61,11 @@
|
||||
"selectAll": "Alle auswählen",
|
||||
"discardAndExit": "Verwerfen und Beenden",
|
||||
"saveAndExit": "Speichern und Beenden",
|
||||
"keepEditing": "Weiter bearbeiten"
|
||||
"keepEditing": "Weiter bearbeiten",
|
||||
"stayOnPrompt": "Bleiben Sie",
|
||||
"closeAnyway": "Jedenfalls schließen",
|
||||
"disable": "Deaktivieren Sie",
|
||||
"confirm": "Bestätigen Sie"
|
||||
},
|
||||
"download": {
|
||||
"downloadFile": "Datei herunterladen",
|
||||
@@ -109,9 +113,9 @@
|
||||
"f2": "Datei umbenennen",
|
||||
"help": "Hilfe",
|
||||
"f1": "Hilfeprompt anzeigen",
|
||||
"description": "Nachstehend finden Sie die grundlegenden Navigationsoptionen. Für zusätzliche Informationen besuchen Sie bitte",
|
||||
"description": "Nachstehend finden Sie die grundlegenden Navigationsoptionen. Für zusätzliche Informationen besuchen Sie bitte das",
|
||||
"slash": "Suche aktivieren",
|
||||
"wiki": "FileBrowser Quantum Offizielle Dokumente"
|
||||
"wiki": "FileBrowser Quantum-Wiki"
|
||||
},
|
||||
"languages": {
|
||||
"he": "עברית",
|
||||
@@ -220,7 +224,9 @@
|
||||
"copyItemQuestion": "Dieses Element an den neuen Ort kopieren?",
|
||||
"moveItemQuestion": "Diesen Artikel an den neuen Ort verschieben?",
|
||||
"sourcePath": "Quelle:",
|
||||
"destinationPath": "Zielort:"
|
||||
"destinationPath": "Zielort:",
|
||||
"closeWithActiveUploadsMessage": "Warnung: Sie haben aktive Uploads und das Schließen der Upload-Aufforderung könnte zu unvollendeten Uploads führen. Was würden Sie gerne tun?",
|
||||
"closeWithActiveUploads": "Aktive Uploads"
|
||||
},
|
||||
"search": {
|
||||
"images": "Bilder",
|
||||
@@ -300,7 +306,7 @@
|
||||
},
|
||||
"permissions-name": "Berechtigungen",
|
||||
"permissionsHelp": "Sie können einem Benutzer Administratorrechte einräumen oder die Berechtigunen individuell festlegen. Wenn Sie \"Administrator\" auswählen, werden alle anderen Rechte automatisch vergeben. Die Nutzerverwaltung kann nur durch einen Administrator erfolgen.\n",
|
||||
"profileSettings": "Profileinstellungen",
|
||||
"profileSettings": "Darstellung",
|
||||
"ruleExample1": "Verhindert den Zugang zu versteckten Dateien (dot-Files, wie .git, .gitignore) in allen Ordnern\n",
|
||||
"ruleExample2": "Blockiert den Zugang auf Dateien mit dem Namen Caddyfile in der Wurzel/Basis des Scopes.",
|
||||
"rules": "Regeln",
|
||||
@@ -328,7 +334,7 @@
|
||||
"modifyOtherUser": "Benutzer ändern:",
|
||||
"modifyCurrentUser": "Aktuellen Benutzer ändern",
|
||||
"themeColor": "Designfarbe",
|
||||
"listingOptions": "Auflistung der Optionen",
|
||||
"listingOptions": "Listenansicht",
|
||||
"emptyGlobal": "Derzeit gibt es keine globalen Konfigurationsoptionen. Sie können die Konfigurations-Yaml bearbeiten, um die Systemeinstellungen anzupassen.",
|
||||
"scopes": "Definition der zulässigen Quellen und des Benutzerbereichs innerhalb dieser Quellen",
|
||||
"UserManagement": "Benutzerverwaltung",
|
||||
@@ -342,7 +348,7 @@
|
||||
"enterPasswordAgain": "Geben Sie das Passwort erneut ein",
|
||||
"passwordsDoNotMatch": "Passwörter stimmen nicht überein",
|
||||
"loginMethod": "Login-Methode",
|
||||
"shareSettings": "Einstellungen teilen",
|
||||
"shareSettings": "Dateifreigaben",
|
||||
"adminOptions": "Optionen für Verwaltungsbenutzer",
|
||||
"shareDuration": "Abgelaufen",
|
||||
"searchOptions": "Suchoptionen",
|
||||
@@ -351,16 +357,16 @@
|
||||
"configViewer": "Config Viewer",
|
||||
"configViewerShowFull": "Vollständige Konfiguration anzeigen",
|
||||
"configViewerShowComments": "Kommentare anzeigen",
|
||||
"configViewerLoadConfig": "Lastkonfiguration",
|
||||
"configViewerLoadConfig": "Konfiguration laden",
|
||||
"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}.",
|
||||
"loginMethods": {
|
||||
"password": "Passwort",
|
||||
"oidc": "OIDC",
|
||||
"proxy": "Vollmacht"
|
||||
"proxy": "Proxy"
|
||||
},
|
||||
"newUserHintSubFolder": "z.B. '/subfolder' - leer für Standard",
|
||||
"userScopes": "Geltungsbereiche"
|
||||
"userScopes": "Zugangsbereiche"
|
||||
},
|
||||
"sidebar": {
|
||||
"help": "Hilfe",
|
||||
@@ -423,15 +429,13 @@
|
||||
"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.",
|
||||
"enforceDarkLightMode": "Erscheinungsbild erzwingen",
|
||||
"enforceDarkLightModeDescription": "Erzwingt ein bestimmtes Erscheinungsbild (dunkel oder hell) für diese Freigabe und setzt die Benutzereinstellungen außer Kraft.",
|
||||
"default": "Keinen Modus erzwingen",
|
||||
"dark": "Dunkelheit",
|
||||
"light": "Licht",
|
||||
"dark": "Dunkel",
|
||||
"light": "Hell",
|
||||
"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}",
|
||||
"defaultViewMode": "Standard-Ansichtsmodus",
|
||||
"defaultViewModeDescription": "Legen Sie den Standardanzeigemodus für anonyme Benutzer fest, die diese Freigabe anzeigen. Angemeldete Benutzer können ihre Ansichtseinstellungen immer noch ändern.",
|
||||
@@ -457,7 +461,7 @@
|
||||
"allowCreateDescription": "Jeder kann neue Dateien und Ordner in dieser Freigabe erstellen und hochladen."
|
||||
},
|
||||
"api": {
|
||||
"title": "API-Schlüssel:",
|
||||
"title": "API-Schlüssel",
|
||||
"keyName": "API-Schlüsselname:",
|
||||
"createdAt": "Erstellt am:",
|
||||
"expiresAt": "Läuft ab am:",
|
||||
@@ -486,7 +490,7 @@
|
||||
"lastScanned": "Zuletzt gescannt",
|
||||
"quickScan": "Schnell-Scan (Sekunden)",
|
||||
"fullScan": "Vollständiger Scan (Sekunden)",
|
||||
"settingsHover": "Einstellungen für Benutzer",
|
||||
"settingsHover": "Einstellungen",
|
||||
"logout": "Abmelden",
|
||||
"toggleClick": "Einzelklick umschalten",
|
||||
"toggleDark": "Dunkelmodus umschalten",
|
||||
@@ -527,7 +531,7 @@
|
||||
"deniedGroups": "Abgelehnte Gruppen",
|
||||
"allowedUsers": "Erlaubte Benutzer",
|
||||
"allowedGroups": "Erlaubte Gruppen",
|
||||
"allow": "Erlauben Sie",
|
||||
"allow": "Zulassen",
|
||||
"allowDeny": "Zulassen/Verweigern",
|
||||
"userGroup": "Benutzer/Gruppe",
|
||||
"enterName": "Benutzer- oder Gruppenname eingeben",
|
||||
@@ -560,7 +564,7 @@
|
||||
"orange": "orange"
|
||||
},
|
||||
"profileSettings": {
|
||||
"setDateFormat": "Genaues Datumsformat einstellen",
|
||||
"setDateFormat": "Genaues Datum statt relativer Zeitangaben verwenden",
|
||||
"showHiddenFiles": "Versteckte Dateien anzeigen",
|
||||
"showHiddenFilesDescription": "Versteckte Dateien in der Auflistung anzeigen (z.B. .thumbnails, ProgramData), aber sie sind immer noch manuell zugänglich, wenn sie für die Indizierung konfiguriert sind.",
|
||||
"showQuickDownload": "Download-Symbol für schnellen Zugriff anzeigen",
|
||||
@@ -569,9 +573,9 @@
|
||||
"previewImagesDescription": "Thumbnail-Vorschau für Bilder anzeigen, bei Deaktivierung wird ein allgemeines Symbol angezeigt.",
|
||||
"previewVideos": "Miniaturansichten für Videos anzeigen",
|
||||
"previewVideosDescription": "Thumbnail-Vorschau für Videos anzeigen, bei Deaktivierung wird ein allgemeines Symbol angezeigt.",
|
||||
"previewMotionVideos": "Aktivieren der Bewegungsvorschau für Videos",
|
||||
"previewMotionVideosDescription": "Bei der Bewegungsvorschau handelt es sich um eine animierte Popup-Vorschau, die sich durch die Einzelbilder des Videos bewegt.",
|
||||
"highQualityPreview": "Hochwertige Miniaturansichten",
|
||||
"previewMotionVideos": "Live-Vorschau für Videos aktivieren",
|
||||
"previewMotionVideosDescription": "Bei der Live-Vorschau handelt es sich um eine animierte Popup-Vorschau, die sich durch die Einzelbilder des Videos bewegt.",
|
||||
"highQualityPreview": "Hochauflösende Miniaturansichten",
|
||||
"highQualityPreviewDescription": "Betrifft nur die Galerieansicht. Bei einer langsamen Verbindung kann das Laden länger dauern.",
|
||||
"editOffice": "Anzeigen und Bearbeiten von Office-Dateien mit OnlyOffice",
|
||||
"editOfficeDescription": "Wenn nur Office aktiviert ist, wird der Dateibetrachter/-editor für alle Dateierweiterungen deaktiviert, die mit Office-Dateien zusammenhängen.",
|
||||
@@ -584,13 +588,13 @@
|
||||
"disableHideSidebarDescription": "Wenn diese Option aktiviert ist, wird die Seitenleiste nicht automatisch ausgeblendet, wenn Dateien in der Vorschau angezeigt oder bearbeitet werden.",
|
||||
"autoplayMedia": "Autoplay von Mediendateien bei der Anzeige",
|
||||
"autoplayMediaDescription": "Autoplay von Mediendateien bei der Anzeige. Dies funktioniert nur, wenn die Datei eine Mediendatei ist.",
|
||||
"sidebarOptions": "Optionen für die Seitenleiste",
|
||||
"disableQuickToggles": "Quick Toggles deaktivieren",
|
||||
"disableQuickTogglesDescription": "Deaktivieren Sie die Quick-Toggles, die in der Seitenleiste angezeigt werden.",
|
||||
"sidebarOptions": "Seitenleiste",
|
||||
"disableQuickToggles": "Schnelleinstellungen ausblenden",
|
||||
"disableQuickTogglesDescription": "Deaktivieren Sie die Schnelleinstellungen, die in der Seitenleiste angezeigt werden.",
|
||||
"disableSearchOptions": "Suchoptionen deaktivieren",
|
||||
"disableSearchOptionsDescription": "Deaktivieren Sie die Suchoptionen, die in der Seitenleiste angezeigt werden.",
|
||||
"hideSidebarFileActions": "Schaltfläche \"Dateiaktionen\" in der Seitenleiste ausblenden",
|
||||
"deleteWithoutConfirming": "Dateien ohne Sicherheitsabfrage löschen",
|
||||
"deleteWithoutConfirming": "Dateien ohne Rückfrage löschen",
|
||||
"deleteWithoutConfirmingDescription": "Dateien ohne Sicherheitsabfrage löschen. Dies funktioniert nur, wenn die Datei eine Mediendatei ist.",
|
||||
"editorViewerOptions": "Editor- und Viewer-Optionen",
|
||||
"editorQuickSave": "Schaltfläche zum schnellen Speichern im Editor",
|
||||
@@ -606,15 +610,15 @@
|
||||
"disableOfficeEditorDescription": "Eine durch Leerzeichen getrennte Liste von Dateierweiterungen, für die der Editor und der Viewer von OnlyOffice deaktiviert werden sollen. (z. B. '.txt .html .pdf').",
|
||||
"disableOfficeEditor": "Office Viewer für bestimmte Dateierweiterungen deaktivieren",
|
||||
"defaultThemeDescription": "default - Das Standardthema",
|
||||
"customTheme": "Wählen Sie Ihr Thema",
|
||||
"customTheme": "Design",
|
||||
"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",
|
||||
"fileViewerOptions": "Dateibetrachter",
|
||||
"themeAndLanguage": "Design und Sprache",
|
||||
"previewFolder": "Miniaturansichten für Ordner anzeigen, falls vorhanden",
|
||||
"previewFolderDescription": "Zeigen Sie Miniaturvorschauen für Ordner an, deren erstes untergeordnetes Element ein Miniaturbild als Cover hat.",
|
||||
"disableFileExtensions": "Zu deaktivierende Dateierweiterungen eingeben",
|
||||
@@ -639,5 +643,9 @@
|
||||
"changePlaybackMode": "Wiedergabemodus ändern",
|
||||
"changePlaybackModeHint": "Ändern Sie den Wiedergabemodus, um mehr Dateien zu sehen",
|
||||
"emptyQueue": "Warteschlange ist leer"
|
||||
},
|
||||
"halloween": {
|
||||
"title": "Fröhliches Halloween!",
|
||||
"body": "Das Halloween-Thema wurde angewendet. Sie können es deaktivieren, wenn es Ihnen nicht gefällt, oder es in den Profileinstellungen dauerhaft deaktivieren, indem Sie ein anderes Thema auswählen."
|
||||
}
|
||||
}
|
||||
@@ -61,7 +61,11 @@
|
||||
"selectAll": "Επιλέξτε όλα",
|
||||
"discardAndExit": "Απόρριψη και έξοδος",
|
||||
"saveAndExit": "Αποθήκευση και έξοδος",
|
||||
"keepEditing": "Συνεχίστε την επεξεργασία"
|
||||
"keepEditing": "Συνεχίστε την επεξεργασία",
|
||||
"stayOnPrompt": "Μείνετε",
|
||||
"closeAnyway": "Κλείστε Ούτως ή άλλως",
|
||||
"disable": "Απενεργοποίηση",
|
||||
"confirm": "Επιβεβαίωση"
|
||||
},
|
||||
"download": {
|
||||
"downloadFile": "Λήψη αρχείου",
|
||||
@@ -223,7 +227,9 @@
|
||||
"copyItemQuestion": "Αντιγράψτε αυτό το στοιχείο στη νέα θέση;",
|
||||
"moveItemQuestion": "Μετακινήστε αυτό το στοιχείο στη νέα θέση;",
|
||||
"sourcePath": "Πηγή:",
|
||||
"destinationPath": "Προορισμός:"
|
||||
"destinationPath": "Προορισμός:",
|
||||
"closeWithActiveUploadsMessage": "Προειδοποίηση: Το κλείσιμο της προτροπής μεταφόρτωσης μπορεί να οδηγήσει σε ημιτελείς μεταφορτώσεις. Τι θα θέλατε να κάνετε;",
|
||||
"closeWithActiveUploads": "Ενεργές μεταφορτώσεις"
|
||||
},
|
||||
"search": {
|
||||
"images": "Εικόνες",
|
||||
@@ -430,8 +436,6 @@
|
||||
"light": "Φως",
|
||||
"enableOnlyOffice": "Ενεργοποίηση της προβολής OnlyOffice",
|
||||
"enableOnlyOfficeDescription": "Επιτρέψτε την προβολή αρχείων γραφείου χρησιμοποιώντας το OnlyOffice σε αυτό το κοινόχρηστο χώρο.",
|
||||
"enableOnlyOfficeEditing": "Ενεργοποίηση της επεξεργασίας OnlyOffice",
|
||||
"enableOnlyOfficeEditingDescription": "Επιτρέψτε την επεξεργασία αρχείων γραφείου χρησιμοποιώντας το OnlyOffice σε αυτό το κοινόχρηστο χώρο.",
|
||||
"titleDefault": "Κοινόχρηστα αρχεία - {title}",
|
||||
"defaultViewMode": "Προεπιλεγμένη λειτουργία προβολής",
|
||||
"defaultViewModeDescription": "Ορίστε την προεπιλεγμένη λειτουργία προβολής για τους ανώνυμους χρήστες που προβάλλουν αυτή την κοινή χρήση. Οι συνδεδεμένοι χρήστες μπορούν ακόμα να αλλάξουν τις προτιμήσεις προβολής τους.",
|
||||
@@ -639,5 +643,9 @@
|
||||
"changePlaybackMode": "Αλλαγή λειτουργίας αναπαραγωγής",
|
||||
"changePlaybackModeHint": "Αλλάξτε λειτουργία αναπαραγωγής για να δείτε περισσότερα αρχεία",
|
||||
"emptyQueue": "Η ουρά είναι άδεια"
|
||||
},
|
||||
"halloween": {
|
||||
"title": "Καλές Απόκριες!",
|
||||
"body": "Το θέμα των αποκριών έχει εφαρμοστεί. Μπορείτε να το απενεργοποιήσετε αν δεν σας αρέσει ή να το απενεργοποιήσετε μόνιμα στις ρυθμίσεις προφίλ επιλέγοντας οποιοδήποτε άλλο θέμα."
|
||||
}
|
||||
}
|
||||
@@ -61,7 +61,11 @@
|
||||
"openUploadViewer": "Open Upload Manager",
|
||||
"discardAndExit": "Discard and Exit",
|
||||
"saveAndExit": "Save and Exit",
|
||||
"keepEditing": "Keep Editing"
|
||||
"keepEditing": "Keep Editing",
|
||||
"stayOnPrompt": "Stay",
|
||||
"closeAnyway": "Close Anyways",
|
||||
"disable": "Disable",
|
||||
"confirm": "Confirm"
|
||||
},
|
||||
"download": {
|
||||
"downloadFile": "Download File",
|
||||
@@ -200,8 +204,6 @@
|
||||
"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.",
|
||||
"shareType": "Share type",
|
||||
"shareTypeDescription": "The type of share to create. Normal shares allow viewing and downloading files. Upload shares allow uploading files to the share.",
|
||||
"normalShare": "Normal",
|
||||
@@ -288,7 +290,9 @@
|
||||
"copyItemQuestion": "Copy this item to the new location?",
|
||||
"moveItemQuestion": "Move this item to the new location?",
|
||||
"sourcePath": "Source:",
|
||||
"destinationPath": "Destination:"
|
||||
"destinationPath": "Destination:",
|
||||
"closeWithActiveUploads": "Active uploads",
|
||||
"closeWithActiveUploadsMessage": "Warning: You have active uploads and closing the upload prompt could result in unfinished uploads. What would you like to do?"
|
||||
},
|
||||
"search": {
|
||||
"searchContext": "Search Context: {context}",
|
||||
@@ -639,5 +643,9 @@
|
||||
"changePlaybackMode": "Change playback mode",
|
||||
"changePlaybackModeHint": "Change playback mode to see more files",
|
||||
"emptyQueue": "Queue is empty"
|
||||
},
|
||||
"halloween": {
|
||||
"title": "Happy Halloween!",
|
||||
"body": "The Halloween theme has been applied. You can disable it if you don't like it, or permanently disable it in profile settings by selecting any other theme."
|
||||
}
|
||||
}
|
||||
@@ -61,7 +61,11 @@
|
||||
"selectAll": "Seleccionar todo",
|
||||
"discardAndExit": "Descartar y salir",
|
||||
"saveAndExit": "Guardar y salir",
|
||||
"keepEditing": "Seguir editando"
|
||||
"keepEditing": "Seguir editando",
|
||||
"stayOnPrompt": "Permanezca en",
|
||||
"closeAnyway": "Cerrar De todos modos",
|
||||
"disable": "Desactivar",
|
||||
"confirm": "Confirme"
|
||||
},
|
||||
"download": {
|
||||
"downloadFile": "Descargar archivo",
|
||||
@@ -220,7 +224,9 @@
|
||||
"copyItemQuestion": "¿Copiar este elemento en la nueva ubicación?",
|
||||
"moveItemQuestion": "¿Mover este elemento a la nueva ubicación?",
|
||||
"sourcePath": "Fuente:",
|
||||
"destinationPath": "Destino:"
|
||||
"destinationPath": "Destino:",
|
||||
"closeWithActiveUploadsMessage": "Advertencia: Tienes subidas activas y si cierras el aviso de subidas, podrían quedar subidas inacabadas. ¿Qué desea hacer?",
|
||||
"closeWithActiveUploads": "Cargas activas"
|
||||
},
|
||||
"search": {
|
||||
"images": "Imágenes",
|
||||
@@ -431,8 +437,6 @@
|
||||
"light": "Claro",
|
||||
"enableOnlyOffice": "Activar el visor de OnlyOffice",
|
||||
"enableOnlyOfficeDescription": "Permitir la visualización de archivos de oficina utilizando OnlyOffice en este recurso compartido.",
|
||||
"enableOnlyOfficeEditing": "Habilitar editor de OnlyOffice",
|
||||
"enableOnlyOfficeEditingDescription": "Permitir la edición de archivos de oficina utilizando OnlyOffice en este recurso compartido.",
|
||||
"titleDefault": "Archivos compartidos - {title}",
|
||||
"defaultViewMode": "Modo de visualización",
|
||||
"defaultViewModeDescription": "Establece el modo de visualización predeterminado para los usuarios anónimos que vean esta acción. Los usuarios registrados pueden cambiar su preferencia de visualización.",
|
||||
@@ -639,5 +643,9 @@
|
||||
"changePlaybackMode": "Cambiar modo de Reproducción",
|
||||
"changePlaybackModeHint": "Cambia el modo de reproducción para ver más",
|
||||
"emptyQueue": "La cola de reproducción está vacia..."
|
||||
},
|
||||
"halloween": {
|
||||
"title": "¡Feliz Halloween!",
|
||||
"body": "Se ha aplicado el tema de Halloween. Puedes desactivarlo si no te gusta, o desactivarlo permanentemente en los ajustes del perfil seleccionando cualquier otro tema."
|
||||
}
|
||||
}
|
||||
+49
-41
@@ -38,14 +38,14 @@
|
||||
"openFile": "Ouvrir le fichier",
|
||||
"copyDownloadLinkToClipboard": "Copier le lien de téléchargement dans le presse-papier",
|
||||
"continue": "Continuer",
|
||||
"edit": "Editer",
|
||||
"edit": "Éditer",
|
||||
"generateNewOtp": "Réinitialiser et générer un nouveau code à deux facteurs",
|
||||
"verify": "Vérifier",
|
||||
"pause": "Pause",
|
||||
"resume": "Curriculum vitae",
|
||||
"resume": "Reprendre",
|
||||
"retry": "Réessayer",
|
||||
"pauseAll": "Pause tous",
|
||||
"resumeAll": "Curriculum vitae",
|
||||
"pauseAll": "Tout mettre en pause",
|
||||
"resumeAll": "Tout reprendre",
|
||||
"clearCompleted": "Effacement terminé",
|
||||
"showMore": "Afficher plus",
|
||||
"showLess": "Montrer moins",
|
||||
@@ -55,13 +55,17 @@
|
||||
"compactView": "Vue compacte",
|
||||
"normalView": "Vue normale",
|
||||
"galleryView": "Vue de la galerie",
|
||||
"uploadFiles": "Téléchargement de fichiers",
|
||||
"uploadFolder": "Télécharger le dossier",
|
||||
"openUploadViewer": "Ouvrir le gestionnaire de téléchargement",
|
||||
"uploadFiles": "Importer des fichiers",
|
||||
"uploadFolder": "Importer un dossier",
|
||||
"openUploadViewer": "Ouvrir le gestionnaire d'importation",
|
||||
"selectAll": "Sélectionner tout",
|
||||
"discardAndExit": "Rejeter et quitter",
|
||||
"saveAndExit": "Sauvegarder et quitter",
|
||||
"keepEditing": "Continuer à éditer"
|
||||
"keepEditing": "Continuer à éditer",
|
||||
"stayOnPrompt": "Séjour",
|
||||
"closeAnyway": "Fermer Quoi qu'il en soit",
|
||||
"disable": "Désactiver",
|
||||
"confirm": "Confirmer"
|
||||
},
|
||||
"download": {
|
||||
"downloadFile": "Télécharger le fichier",
|
||||
@@ -94,9 +98,9 @@
|
||||
"noDownloadAccess": "Le téléchargement n'est pas disponible."
|
||||
},
|
||||
"help": {
|
||||
"click": "Sélectionner un élément",
|
||||
"click": "sélectionner un élément",
|
||||
"ctrl": {
|
||||
"click": "Sélectionner plusieurs éléments",
|
||||
"click": "sélectionner plusieurs éléments",
|
||||
"d": "télécharger les éléments sélectionnés",
|
||||
"b": "barre latérale à bascule",
|
||||
"comma": "accéder aux paramètres",
|
||||
@@ -104,11 +108,11 @@
|
||||
"v": "coller les éléments copiés/coupés",
|
||||
"x": "couper les articles sélectionnés"
|
||||
},
|
||||
"del": "Supprimer les éléments sélectionnés",
|
||||
"esc": "Désélectionner et/ou fermer la boîte de dialogue",
|
||||
"f2": "Renommer le fichier",
|
||||
"del": "supprimer les éléments sélectionnés",
|
||||
"esc": "désélectionner et/ou fermer la boîte de dialogue",
|
||||
"f2": "renommer le fichier",
|
||||
"help": "Aide",
|
||||
"f1": "show help prompt",
|
||||
"f1": "afficher l'aide",
|
||||
"description": "Vous pouvez consulter les options de navigation de base ci-dessous. Pour plus d'informations, veuillez consulter le site",
|
||||
"slash": "activer la recherche",
|
||||
"wiki": "Documents officiels de FileBrowser Quantum"
|
||||
@@ -206,8 +210,8 @@
|
||||
"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é",
|
||||
"uploadSettingsChunked": "Nombre maximum d'importations simultanées : {maxConcurrentUpload}, taille de chunk : {uploadChunkSizeMb} Mo",
|
||||
"uploadSettingsNoChunk": "Nombre maximum d'importations simultanées : {maxConcurrentUpload}, importation par morceaux désactivée",
|
||||
"quickJump": "Passer rapidement à d'autres fichiers",
|
||||
"moveSuccess": "Les fichiers ont été déplacés avec succès !",
|
||||
"copySuccess": "Les fichiers ont été copiés avec succès !",
|
||||
@@ -220,7 +224,9 @@
|
||||
"copyItemQuestion": "Copier cet élément vers le nouvel emplacement ?",
|
||||
"moveItemQuestion": "Déplacer cet élément vers le nouvel emplacement ?",
|
||||
"sourcePath": "Source :",
|
||||
"destinationPath": "Destination :"
|
||||
"destinationPath": "Destination :",
|
||||
"closeWithActiveUploadsMessage": "Attention : Vous avez des téléchargements actifs et la fermeture de l'invite de téléchargement pourrait entraîner des téléchargements inachevés. Que voulez-vous faire ?",
|
||||
"closeWithActiveUploads": "Téléchargements actifs"
|
||||
},
|
||||
"search": {
|
||||
"images": "Images",
|
||||
@@ -253,7 +259,7 @@
|
||||
"admin": "Admin",
|
||||
"administrator": "Administrateur",
|
||||
"allowCommands": "Exécuter des commandes",
|
||||
"allowEdit": "Editer, renommer et supprimer des fichiers ou des dossiers",
|
||||
"allowEdit": "Éditer, renommer et supprimer des fichiers ou des dossiers",
|
||||
"allowNew": "Créer de nouveaux fichiers et dossiers",
|
||||
"allowPublish": "Publier de nouveaux posts et pages",
|
||||
"allowSignup": "Autoriser les utilisateurs à s'inscrire",
|
||||
@@ -298,8 +304,8 @@
|
||||
"permissions-name": "Permissions",
|
||||
"permissionsHelp": "Vous pouvez définir l'utilisateur comme étant un administrateur ou encore choisir les permissions individuellement. Si vous sélectionnez \"Administrateur\", toutes les autres options seront automatiquement activées. La gestion des utilisateurs est un privilège que seul l'administrateur possède.\n",
|
||||
"profileSettings": "Paramètres du profil",
|
||||
"ruleExample1": "Bloque l'accès à tous les fichiers commençant par un point (comme par exemple .git, .gitignore) dans tous les dossiers",
|
||||
"ruleExample2": "Bloque l'accès au fichier nommé \"Caddyfile\" à la racine du dossier utilisateur",
|
||||
"ruleExample1": "bloque l'accès à tous les fichiers commençant par un point (comme par exemple .git, .gitignore) dans tous les dossiers",
|
||||
"ruleExample2": "bloque l'accès au fichier nommé \"Caddyfile\" à la racine du dossier utilisateur",
|
||||
"rules": "Règles",
|
||||
"rulesHelp": "Vous pouvez définir ici un ensemble de règles pour cet utilisateur. Les fichiers bloqués ne seront pas affichés et ne seront pas accessibles par l'utilisateur. Les expressions régulières sont supportées et les chemins d'accès sont relatifs par rapport au dossier de l'utilisateur.\n",
|
||||
"scope": "Portée du dossier utilisateur",
|
||||
@@ -309,7 +315,7 @@
|
||||
"singleClick": "Utiliser un simple clic pour ouvrir les fichiers et les dossiers",
|
||||
"themes": {
|
||||
"dark": "Sombre",
|
||||
"light": "Lumineux",
|
||||
"light": "Clair",
|
||||
"title": "Thème"
|
||||
},
|
||||
"user": "Utilisateur",
|
||||
@@ -328,10 +334,10 @@
|
||||
"listingOptions": "Options d'inscription",
|
||||
"emptyGlobal": "Actuellement, il n'y a pas d'options de configuration globale. Vous pouvez éditer le fichier de configuration yaml pour ajuster les paramètres du système.",
|
||||
"UserManagement": "Gestion des utilisateurs",
|
||||
"tusUploads": "Téléversements groupés",
|
||||
"tusUploadsHelp": "File Browser prend en charge les téléversements de fichiers par morceaux, ce qui permet de créer des téléversements de fichiers efficaces, fiables, résumables et par morceaux, même sur des réseaux peu fiables.",
|
||||
"tusUploadsChunkSize": "Indique la taille maximale d'une demande (les téléversements directs seront utilisés pour les téléversements plus petits). Vous pouvez saisir un nombre entier simple correspondant à un nombre d'octets ou une chaîne de caractères telle que 10 Mo, 1 Go, etc.",
|
||||
"tusUploadsRetryCount": "Nombre de tentatives à effectuer si un bloc ne parvient pas à être téléversé.",
|
||||
"tusUploads": "Importations groupées",
|
||||
"tusUploadsHelp": "File Browser prend en charge les importations de fichiers par morceaux, ce qui permet de créer des importations de fichiers efficaces, fiables, résumables et par morceaux, même sur des réseaux peu fiables.",
|
||||
"tusUploadsChunkSize": "Indique la taille maximale d'une demande (les importations directes seront utilisées pour les importations plus petites). Vous pouvez saisir un nombre entier simple correspondant à un nombre d'octets ou une chaîne de caractères telle que 10 Mo, 1 Go, etc.",
|
||||
"tusUploadsRetryCount": "Nombre de tentatives à effectuer si un bloc ne parvient pas à être importé.",
|
||||
"userHomeBasePath": "Chemin de base pour les répertoires personnels des utilisateurs",
|
||||
"userScopeGenerationPlaceholder": "Le champ d'application sera généré automatiquement",
|
||||
"createUserHomeDirectory": "Créer le répertoire personnel de l'utilisateur",
|
||||
@@ -389,7 +395,7 @@
|
||||
"unit": "Unité de temps"
|
||||
},
|
||||
"upload": {
|
||||
"abortUpload": "Êtes-vous sûre de vouloir annuler ?"
|
||||
"abortUpload": "Êtes-vous sûr de vouloir annuler ?"
|
||||
},
|
||||
"share": {
|
||||
"notice": "Remarque : lorsqu'un partage est créé, il est davantage exposé au public et les règles d'accès sont différentes. Si votre partage n'est pas configuré de manière sécurisée, n'importe qui peut y accéder.",
|
||||
@@ -427,11 +433,9 @@
|
||||
"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",
|
||||
"light": "Clair",
|
||||
"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}",
|
||||
"defaultViewMode": "Mode d'affichage par défaut",
|
||||
"defaultViewModeDescription": "Définit le mode d'affichage par défaut pour les utilisateurs anonymes qui consultent ce partage. Les utilisateurs connectés peuvent toujours modifier leurs préférences d'affichage.",
|
||||
@@ -483,9 +487,9 @@
|
||||
"index": {
|
||||
"status": "Statut",
|
||||
"assessment": "Évaluation",
|
||||
"lastScanned": "Dernier balayage",
|
||||
"quickScan": "secondes pour un balayage rapide",
|
||||
"fullScan": "secondes pour un balayage complet",
|
||||
"lastScanned": "Dernier scan",
|
||||
"quickScan": "Scan rapide (secondes)",
|
||||
"fullScan": "Scan complet (secondes)",
|
||||
"settingsHover": "Paramètres pour l'utilisateur",
|
||||
"logout": "Déconnexion",
|
||||
"toggleClick": "Activation du mode simple clic",
|
||||
@@ -507,7 +511,7 @@
|
||||
},
|
||||
"general": {
|
||||
"name": "Nom",
|
||||
"path": "Trajectoire",
|
||||
"path": "Chemin",
|
||||
"user": "Utilisateur",
|
||||
"group": "Groupe",
|
||||
"signup": "S'inscrire",
|
||||
@@ -542,14 +546,14 @@
|
||||
"defaultBehavior": "Action par défaut{suffix}"
|
||||
},
|
||||
"fileLoading": {
|
||||
"title": "Chargements et téléchargements",
|
||||
"maxConcurrentUpload": "Nombre maximum de téléchargements simultanés",
|
||||
"maxConcurrentUploadHelp": "Nombre maximal de téléchargements simultanés. Des valeurs plus élevées sont généralement plus performantes, mais cela dépend de nombreux facteurs.",
|
||||
"uploadChunkSizeMb": "Taille de la zone de téléchargement en Mo",
|
||||
"uploadChunkSizeMbHelp": "Généralement de 5 à 50 Mo, une taille inférieure est préférable pour les connexions instables ou restreintes. La valeur 0 désactive le fractionnement de l'upload.",
|
||||
"clearAll": "Clear completed devrait supprimer les erreurs, les pauses et les conflits.",
|
||||
"clearAllDescription": "Le bouton \"Effacer les téléchargements terminés\" efface tous les téléchargements terminés, y compris les erreurs, les pauses et les conflits.",
|
||||
"uploadSettings": "Paramètres de téléchargement"
|
||||
"title": "Importations et téléchargements",
|
||||
"maxConcurrentUpload": "Nombre maximum d'importations simultanées",
|
||||
"maxConcurrentUploadHelp": "Nombre maximal d'importations simultanées. Des valeurs plus élevées sont généralement plus performantes, mais cela dépend de nombreux facteurs.",
|
||||
"uploadChunkSizeMb": "Taille de chunk pour l'importation en Mo",
|
||||
"uploadChunkSizeMbHelp": "Généralement de 5 à 50 Mo, une taille inférieure est préférable pour les connexions instables ou restreintes. La valeur 0 désactive l'importation par morceaux.",
|
||||
"clearAll": "Effacer les importations terminées (erreurs, pauses et conflits inclus)",
|
||||
"clearAllDescription": "Le bouton \"Effacer les importations terminées\" efface toutes les importations terminées, y compris les erreurs, les pauses et les conflits.",
|
||||
"uploadSettings": "Paramètres d'importation"
|
||||
},
|
||||
"colors": {
|
||||
"red": "rouge",
|
||||
@@ -639,5 +643,9 @@
|
||||
"changePlaybackMode": "Changer le mode de lecture",
|
||||
"changePlaybackModeHint": "Changez le mode de lecture pour voir plus de fichiers",
|
||||
"emptyQueue": "La file est vide"
|
||||
},
|
||||
"halloween": {
|
||||
"title": "Joyeux Halloween !",
|
||||
"body": "Le thème Halloween a été appliqué. Vous pouvez le désactiver si vous ne l'aimez pas, ou le désactiver définitivement dans les paramètres du profil en sélectionnant un autre thème."
|
||||
}
|
||||
}
|
||||
@@ -61,7 +61,11 @@
|
||||
"selectAll": "בחר הכל",
|
||||
"discardAndExit": "מחק וצא",
|
||||
"saveAndExit": "שמור וצא",
|
||||
"keepEditing": "המשך עריכה"
|
||||
"keepEditing": "המשך עריכה",
|
||||
"stayOnPrompt": "הישאר",
|
||||
"closeAnyway": "סגור בכל מקרה",
|
||||
"disable": "השבת",
|
||||
"confirm": "אשר"
|
||||
},
|
||||
"download": {
|
||||
"downloadFile": "הורד קובץ",
|
||||
@@ -220,7 +224,9 @@
|
||||
"copyItemQuestion": "האם להעתיק פריט זה למיקום החדש?",
|
||||
"moveItemQuestion": "האם להעביר פריט זה למיקום החדש?",
|
||||
"sourcePath": "מקור:",
|
||||
"destinationPath": "יעד:"
|
||||
"destinationPath": "יעד:",
|
||||
"closeWithActiveUploadsMessage": "אזהרה: יש לך העלאות פעילות וסגירת חלון ההעלאה עלולה לגרום להשלמת העלאות לא גמורות. מה ברצונך לעשות?",
|
||||
"closeWithActiveUploads": "העלאות פעילות"
|
||||
},
|
||||
"search": {
|
||||
"images": "תמונות",
|
||||
@@ -430,8 +436,6 @@
|
||||
"light": "אור",
|
||||
"enableOnlyOffice": "הפעל את תוכנת הצפייה OnlyOffice",
|
||||
"enableOnlyOfficeDescription": "אפשר צפייה בקבצי Office באמצעות OnlyOffice בשיתוף זה.",
|
||||
"enableOnlyOfficeEditing": "אפשר עריכה ב-OnlyOffice",
|
||||
"enableOnlyOfficeEditingDescription": "אפשר עריכת קבצי Office באמצעות OnlyOffice בשיתוף זה.",
|
||||
"titleDefault": "קבצים משותפים - {title}",
|
||||
"defaultViewMode": "מצב תצוגה ברירת מחדל",
|
||||
"defaultViewModeDescription": "הגדר את מצב התצוגה המוגדר כברירת מחדל עבור משתמשים אנונימיים הצופים בשיתוף זה. משתמשים מחוברים עדיין יכולים לשנות את העדפות התצוגה שלהם.",
|
||||
@@ -639,5 +643,9 @@
|
||||
"changePlaybackMode": "שנה מצב ניגון",
|
||||
"changePlaybackModeHint": "שנה מצב ניגון כדי לראות יותר קבצים",
|
||||
"emptyQueue": "התור ריק"
|
||||
},
|
||||
"halloween": {
|
||||
"title": "חג ליל כל הקדושים שמח!",
|
||||
"body": "נושא ליל כל הקדושים הוחל. ניתן להשבית אותו אם אינו מוצא חן בעיניך, או להשבית אותו לצמיתות בהגדרות הפרופיל על ידי בחירת נושא אחר."
|
||||
}
|
||||
}
|
||||
@@ -61,7 +61,11 @@
|
||||
"selectAll": "Válasszon ki mindent",
|
||||
"discardAndExit": "Dobás és kilépés",
|
||||
"saveAndExit": "Mentés és kilépés",
|
||||
"keepEditing": "Szerkesztés folytatása"
|
||||
"keepEditing": "Szerkesztés folytatása",
|
||||
"stayOnPrompt": "Maradj",
|
||||
"closeAnyway": "Egyébként bezárni",
|
||||
"disable": "A letiltása",
|
||||
"confirm": "Megerősítés"
|
||||
},
|
||||
"download": {
|
||||
"downloadFile": "Fájl letöltése",
|
||||
@@ -220,7 +224,9 @@
|
||||
"copyItemQuestion": "Másolja ezt az elemet az új helyre?",
|
||||
"moveItemQuestion": "Áthelyezni ezt az elemet az új helyre?",
|
||||
"sourcePath": "Forrás:",
|
||||
"destinationPath": "Úti cél:"
|
||||
"destinationPath": "Úti cél:",
|
||||
"closeWithActiveUploadsMessage": "Figyelmeztetés: A feltöltési prompt bezárása befejezetlen feltöltéseket eredményezhet. Mit szeretne tenni?",
|
||||
"closeWithActiveUploads": "Aktív feltöltések"
|
||||
},
|
||||
"search": {
|
||||
"images": "Képek",
|
||||
@@ -430,8 +436,6 @@
|
||||
"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}",
|
||||
"defaultViewMode": "Alapértelmezett nézetmód",
|
||||
"defaultViewModeDescription": "A megosztást megtekintő névtelen felhasználók alapértelmezett megtekintési módjának beállítása. A bejelentkezett felhasználók továbbra is módosíthatják a nézeti beállításaikat.",
|
||||
@@ -639,5 +643,9 @@
|
||||
"changePlaybackMode": "Lejátszási mód változtatása",
|
||||
"changePlaybackModeHint": "Változtasd meg a lejátszási módot, hogy több fájlt láss",
|
||||
"emptyQueue": "A sor üres"
|
||||
},
|
||||
"halloween": {
|
||||
"title": "Boldog Halloweent!",
|
||||
"body": "A Halloween témát alkalmazták. Ha nem tetszik, letilthatja, vagy véglegesen letilthatja a profilbeállításokban bármely más téma kiválasztásával."
|
||||
}
|
||||
}
|
||||
@@ -61,7 +61,11 @@
|
||||
"selectAll": "Seleziona tutti",
|
||||
"discardAndExit": "Scartare e uscire",
|
||||
"saveAndExit": "Salvare e uscire",
|
||||
"keepEditing": "Continuare a modificare"
|
||||
"keepEditing": "Continuare a modificare",
|
||||
"stayOnPrompt": "Soggiorno",
|
||||
"closeAnyway": "Chiudere comunque",
|
||||
"disable": "Disattivare",
|
||||
"confirm": "Confermare"
|
||||
},
|
||||
"download": {
|
||||
"downloadFile": "Scarica file",
|
||||
@@ -220,7 +224,9 @@
|
||||
"copyItemQuestion": "Copiare questo elemento nella nuova posizione?",
|
||||
"moveItemQuestion": "Spostare questo elemento nella nuova posizione?",
|
||||
"sourcePath": "Fonte:",
|
||||
"destinationPath": "Destinazione:"
|
||||
"destinationPath": "Destinazione:",
|
||||
"closeWithActiveUploadsMessage": "Attenzione: Ci sono caricamenti attivi e la chiusura del prompt di caricamento potrebbe causare caricamenti non completati. Cosa si desidera fare?",
|
||||
"closeWithActiveUploads": "Carichi attivi"
|
||||
},
|
||||
"search": {
|
||||
"images": "Immagini",
|
||||
@@ -430,8 +436,6 @@
|
||||
"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}",
|
||||
"defaultViewMode": "Modalità di visualizzazione predefinita",
|
||||
"defaultViewModeDescription": "Imposta la modalità di visualizzazione predefinita per gli utenti anonimi che visualizzano questa condivisione. Gli utenti registrati possono comunque modificare le loro preferenze di visualizzazione.",
|
||||
@@ -639,5 +643,9 @@
|
||||
"changePlaybackMode": "Cambia modalità di riproduzione",
|
||||
"changePlaybackModeHint": "Cambia modalità di riproduzione per vedere più file",
|
||||
"emptyQueue": "La coda è vuota"
|
||||
},
|
||||
"halloween": {
|
||||
"title": "Buon Halloween!",
|
||||
"body": "Il tema di Halloween è stato applicato. È possibile disattivarlo se non piace, oppure disattivarlo definitivamente nelle impostazioni del profilo selezionando un altro tema."
|
||||
}
|
||||
}
|
||||
@@ -61,7 +61,11 @@
|
||||
"selectAll": "すべて選択",
|
||||
"discardAndExit": "破棄と終了",
|
||||
"saveAndExit": "保存して終了する",
|
||||
"keepEditing": "編集を続ける"
|
||||
"keepEditing": "編集を続ける",
|
||||
"stayOnPrompt": "滞在",
|
||||
"closeAnyway": "とにかく閉じる",
|
||||
"disable": "無効",
|
||||
"confirm": "確認"
|
||||
},
|
||||
"download": {
|
||||
"downloadFile": "ファイルをダウンロード",
|
||||
@@ -220,7 +224,9 @@
|
||||
"copyItemQuestion": "このアイテムを新しい場所にコピーしますか?",
|
||||
"moveItemQuestion": "このアイテムを新しい場所に移動しますか?",
|
||||
"sourcePath": "ソース",
|
||||
"destinationPath": "目的地だ:"
|
||||
"destinationPath": "目的地だ:",
|
||||
"closeWithActiveUploadsMessage": "警告アクティブなアップロードがあり、アップロードプロンプトを閉じるとアップロードが未完了になる可能性があります。どうしますか?",
|
||||
"closeWithActiveUploads": "アクティブなアップロード"
|
||||
},
|
||||
"search": {
|
||||
"images": "画像",
|
||||
@@ -430,10 +436,8 @@
|
||||
"light": "ライト",
|
||||
"enableOnlyOffice": "OnlyOfficeビューアを有効にする",
|
||||
"enableOnlyOfficeDescription": "この共有でOnlyOfficeを使用したオフィスファイルの閲覧を許可する",
|
||||
"enableOnlyOfficeEditing": "OnlyOffice編集を有効にする",
|
||||
"defaultViewMode": "デフォルトの表示モード",
|
||||
"defaultViewModeDescription": "匿名ユーザーがこの共有を閲覧する際のデフォルトの表示モードを設定します。ログインしているユーザーは、表示モードを変更することができます。",
|
||||
"enableOnlyOfficeEditingDescription": "この共有でOnlyOfficeを使ったオフィスファイルの編集を許可する",
|
||||
"titleDefault": "共有ファイル{title}",
|
||||
"disableFileViewer": "ファイルビューアを無効にする",
|
||||
"shareType": "シェアタイプ",
|
||||
@@ -639,5 +643,9 @@
|
||||
"changePlaybackMode": "再生モードを変更",
|
||||
"changePlaybackModeHint": "再生モードを変更してより多くのファイルを表示",
|
||||
"emptyQueue": "キューは空です"
|
||||
},
|
||||
"halloween": {
|
||||
"title": "ハッピー・ハロウィーン",
|
||||
"body": "ハロウィーンのテーマが適用されました。気に入らなければ無効にするか、プロフィール設定で他のテーマを選択して永久に無効にすることができます。"
|
||||
}
|
||||
}
|
||||
@@ -61,7 +61,11 @@
|
||||
"selectAll": "모두 선택",
|
||||
"discardAndExit": "삭제 및 종료",
|
||||
"saveAndExit": "저장 및 종료",
|
||||
"keepEditing": "계속 편집"
|
||||
"keepEditing": "계속 편집",
|
||||
"stayOnPrompt": "Stay",
|
||||
"closeAnyway": "어쨌든 닫기",
|
||||
"disable": "비활성화",
|
||||
"confirm": "확인"
|
||||
},
|
||||
"download": {
|
||||
"downloadFile": "파일 다운로드",
|
||||
@@ -220,7 +224,9 @@
|
||||
"copyItemQuestion": "이 항목을 새 위치로 복사하시겠습니까?",
|
||||
"moveItemQuestion": "이 항목을 새 위치로 옮기시겠습니까?",
|
||||
"sourcePath": "출처:",
|
||||
"destinationPath": "목적지:"
|
||||
"destinationPath": "목적지:",
|
||||
"closeWithActiveUploadsMessage": "경고: 업로드가 진행 중이며 업로드 프롬프트를 닫으면 완료되지 않은 업로드가 발생할 수 있습니다. 어떻게 하시겠습니까?",
|
||||
"closeWithActiveUploads": "활성 업로드"
|
||||
},
|
||||
"search": {
|
||||
"images": "이미지",
|
||||
@@ -430,8 +436,6 @@
|
||||
"light": "빛",
|
||||
"enableOnlyOffice": "Office 뷰어만 사용",
|
||||
"enableOnlyOfficeDescription": "이 공유에서 OnlyOffice를 사용하여 Office 파일을 볼 수 있도록 허용합니다.",
|
||||
"enableOnlyOfficeEditing": "Office 편집만 사용",
|
||||
"enableOnlyOfficeEditingDescription": "이 공유에서 OnlyOffice를 사용하여 Office 파일을 편집할 수 있도록 허용합니다.",
|
||||
"titleDefault": "공유 파일 - {title}",
|
||||
"defaultViewMode": "기본 보기 모드",
|
||||
"defaultViewModeDescription": "이 공유를 보는 익명 사용자의 기본 보기 모드를 설정합니다. 로그인한 사용자는 여전히 보기 기본 설정을 변경할 수 있습니다.",
|
||||
@@ -639,5 +643,9 @@
|
||||
"changePlaybackMode": "재생 모드 변경",
|
||||
"changePlaybackModeHint": "더 많은 파일을 보려면 재생 모드를 변경하세요",
|
||||
"emptyQueue": "대기열이 비어 있습니다"
|
||||
},
|
||||
"halloween": {
|
||||
"title": "해피 할로윈!",
|
||||
"body": "할로윈 테마가 적용되었습니다. 마음에 들지 않으면 비활성화하거나 프로필 설정에서 다른 테마를 선택하여 영구적으로 비활성화할 수 있습니다."
|
||||
}
|
||||
}
|
||||
@@ -61,7 +61,11 @@
|
||||
"selectAll": "Alles selecteren",
|
||||
"discardAndExit": "Weggooien en afsluiten",
|
||||
"saveAndExit": "Opslaan en afsluiten",
|
||||
"keepEditing": "Blijven bewerken"
|
||||
"keepEditing": "Blijven bewerken",
|
||||
"stayOnPrompt": "Blijf",
|
||||
"closeAnyway": "Sluiten",
|
||||
"disable": "uitschakelen",
|
||||
"confirm": "Bevestig"
|
||||
},
|
||||
"download": {
|
||||
"downloadFile": "Bestand downloaden",
|
||||
@@ -220,7 +224,9 @@
|
||||
"copyItemQuestion": "Dit item kopiëren naar de nieuwe locatie?",
|
||||
"moveItemQuestion": "Dit item verplaatsen naar de nieuwe locatie?",
|
||||
"sourcePath": "Bron:",
|
||||
"destinationPath": "Bestemming:"
|
||||
"destinationPath": "Bestemming:",
|
||||
"closeWithActiveUploadsMessage": "Waarschuwing: U hebt actieve uploads en het sluiten van de uploadprompt kan resulteren in onvoltooide uploads. Wat wilt u doen?",
|
||||
"closeWithActiveUploads": "Actieve uploads"
|
||||
},
|
||||
"search": {
|
||||
"images": "Afbeeldingen",
|
||||
@@ -430,8 +436,6 @@
|
||||
"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}",
|
||||
"defaultViewMode": "Standaard weergavemodus",
|
||||
"defaultViewModeDescription": "Stel de standaard weergavemodus in voor anonieme gebruikers die deze share bekijken. Aangemelde gebruikers kunnen nog steeds hun weergavevoorkeur wijzigen.",
|
||||
@@ -639,5 +643,9 @@
|
||||
"changePlaybackMode": "Afspeelmodus wijzigen",
|
||||
"changePlaybackModeHint": "Wijzig afspeelmodus om meer bestanden te zien",
|
||||
"emptyQueue": "Wachtrij is leeg"
|
||||
},
|
||||
"halloween": {
|
||||
"title": "Gelukkig Halloween!",
|
||||
"body": "Het Halloween-thema is toegepast. Je kunt het uitschakelen als je het niet leuk vindt, of permanent uitschakelen in de profielinstellingen door een ander thema te selecteren."
|
||||
}
|
||||
}
|
||||
@@ -61,7 +61,11 @@
|
||||
"selectAll": "Wybierz wszystko",
|
||||
"discardAndExit": "Odrzuć i wyjdź",
|
||||
"saveAndExit": "Zapisz i wyjdź",
|
||||
"keepEditing": "Kontynuuj edycję"
|
||||
"keepEditing": "Kontynuuj edycję",
|
||||
"stayOnPrompt": "Zostań",
|
||||
"closeAnyway": "W każdym razie blisko",
|
||||
"disable": "Wyłącz",
|
||||
"confirm": "Potwierdzenie"
|
||||
},
|
||||
"download": {
|
||||
"downloadFile": "Pobierz plik",
|
||||
@@ -220,7 +224,9 @@
|
||||
"copyItemQuestion": "Skopiować ten element do nowej lokalizacji?",
|
||||
"moveItemQuestion": "Przenieść ten element do nowej lokalizacji?",
|
||||
"sourcePath": "Źródło:",
|
||||
"destinationPath": "Miejsce docelowe:"
|
||||
"destinationPath": "Miejsce docelowe:",
|
||||
"closeWithActiveUploadsMessage": "Ostrzeżenie: Masz aktywne przesyłanie, a zamknięcie monitu o przesłanie może spowodować niedokończone przesyłanie. Co chcesz zrobić?",
|
||||
"closeWithActiveUploads": "Aktywne przesyłanie"
|
||||
},
|
||||
"search": {
|
||||
"images": "Zdjęcia",
|
||||
@@ -430,8 +436,6 @@
|
||||
"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}",
|
||||
"defaultViewMode": "Domyślny tryb widoku",
|
||||
"defaultViewModeDescription": "Ustawia domyślny tryb widoku dla anonimowych użytkowników przeglądających ten folder udostępniony. Zalogowani użytkownicy mogą nadal zmieniać swoje preferencje widoku.",
|
||||
@@ -639,5 +643,9 @@
|
||||
"changePlaybackMode": "Zmień tryb odtwarzania",
|
||||
"changePlaybackModeHint": "Zmień tryb odtwarzania, aby zobaczyć więcej plików",
|
||||
"emptyQueue": "Kolejka jest pusta"
|
||||
},
|
||||
"halloween": {
|
||||
"title": "Wesołego Halloween!",
|
||||
"body": "Motyw Halloween został zastosowany. Możesz go wyłączyć, jeśli Ci się nie podoba, lub wyłączyć go na stałe w ustawieniach profilu, wybierając inny motyw."
|
||||
}
|
||||
}
|
||||
@@ -61,7 +61,11 @@
|
||||
"selectAll": "Selecionar tudo",
|
||||
"discardAndExit": "Descartar e sair",
|
||||
"saveAndExit": "Salvar e sair",
|
||||
"keepEditing": "Continuar editando"
|
||||
"keepEditing": "Continuar editando",
|
||||
"stayOnPrompt": "Permanecer",
|
||||
"closeAnyway": "Fechar de qualquer maneira",
|
||||
"disable": "Desativar",
|
||||
"confirm": "Confirmar"
|
||||
},
|
||||
"download": {
|
||||
"downloadFile": "Baixar arquivo",
|
||||
@@ -220,7 +224,9 @@
|
||||
"copyItemQuestion": "Copiar esse item para o novo local?",
|
||||
"moveItemQuestion": "Mover esse item para o novo local?",
|
||||
"sourcePath": "Fonte:",
|
||||
"destinationPath": "Destino:"
|
||||
"destinationPath": "Destino:",
|
||||
"closeWithActiveUploadsMessage": "Aviso: Você tem uploads ativos e fechar o prompt de upload pode resultar em uploads inacabados. O que você gostaria de fazer?",
|
||||
"closeWithActiveUploads": "Uploads ativos"
|
||||
},
|
||||
"search": {
|
||||
"images": "Imagens",
|
||||
@@ -430,8 +436,6 @@
|
||||
"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}",
|
||||
"defaultViewMode": "Modo de exibição padrão",
|
||||
"defaultViewModeDescription": "Define o modo de exibição padrão para usuários anônimos que visualizam esse compartilhamento. Os usuários conectados ainda podem alterar sua preferência de visualização.",
|
||||
@@ -639,5 +643,9 @@
|
||||
"changePlaybackMode": "Alterar modo de reprodução",
|
||||
"changePlaybackModeHint": "Altere o modo de reprodução para ver mais arquivos",
|
||||
"emptyQueue": "A fila está vazia"
|
||||
},
|
||||
"halloween": {
|
||||
"title": "Feliz Dia das Bruxas!",
|
||||
"body": "O tema Halloween foi aplicado. Você pode desativá-lo se não gostar dele ou desativá-lo permanentemente nas configurações de perfil, selecionando qualquer outro tema."
|
||||
}
|
||||
}
|
||||
Loaded 100 of 138 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user