onlyoffice share support (#1181)

This commit is contained in:
Graham Steffaniak authored and GitHub committed 2025-09-03 19:32:02 -05:00
1 parent 99786eafde
commit d4977713e7
53 files changed
+1126 -974

No files matched your search

+3
View File
@@ -9,6 +9,9 @@ All notable changes to this project will be documented in this file. For commit
- added new default media player (can be disabled in user profile settings) @Kurami32 #1160
- Option to disable update check #1134
- added `frontend.favicon` and `frontend.description` for html overrides
- onlyoffice viewing and editing is supported in shares
- Added only office debug view and wiki to assist with debugging issues #1068 #911 #1074
- Dark mode enforcement possible for shared links #1029
**Notes**:
- access management: specific folders/files with access are shown instead permission denied for parent folder
+1
View File
@@ -171,4 +171,5 @@ type UserDefaults struct {
DisableOnlyOfficeExt string `json:"disableOnlyOfficeExt"` // list of file extensions to disable onlyoffice editor for
CustomTheme string `json:"customTheme"` // Name of theme to use chosen from custom themes config.
ShowSelectMultiple bool `json:"showSelectMultiple"` // show select multiple files on desktop
DebugOffice bool `json:"debugOffice"` // debug onlyoffice editor
}
+7
View File
@@ -121,3 +121,10 @@ func NonNilSlice[T any](in []T) []T {
}
return in
}
func Ternary[T any](cond bool, vtrue, vfalse T) T {
if cond {
return vtrue
}
return vfalse
}
+25 -22
View File
@@ -4,28 +4,31 @@ import "sync"
type CommonShare struct {
//AllowEdit bool `json:"allowEdit,omitempty"`
AllowUpload bool `json:"allowUpload,omitempty"`
DisableFileViewer bool `json:"disableFileViewer,omitempty"`
DownloadsLimit int `json:"downloadsLimit,omitempty"`
ShareTheme string `json:"shareTheme,omitempty"`
DisableAnonymous bool `json:"disableAnonymous,omitempty"`
MaxBandwidth int `json:"maxBandwidth,omitempty"`
DisableThumbnails bool `json:"disableThumbnails,omitempty"`
KeepAfterExpiration bool `json:"keepAfterExpiration,omitempty"`
AllowedUsernames []string `json:"allowedUsernames,omitempty"`
ThemeColor string `json:"themeColor,omitempty"`
Banner string `json:"banner,omitempty"`
Title string `json:"title,omitempty"`
Description string `json:"description,omitempty"`
Favicon string `json:"favicon,omitempty"`
QuickDownload bool `json:"quickDownload,omitempty"`
HideNavButtons bool `json:"hideNavButtons,omitempty"`
DisableSidebar bool `json:"disableSidebar"`
ViewMode string `json:"viewMode,omitempty"`
Source string `json:"source,omitempty"` // backend source is path to maintain between name changes
Path string `json:"path,omitempty"`
DownloadURL string `json:"downloadURL,omitempty"`
DisableShareCard bool `json:"disableShareCard,omitempty"`
AllowUpload bool `json:"allowUpload,omitempty"`
DisableFileViewer bool `json:"disableFileViewer,omitempty"`
DownloadsLimit int `json:"downloadsLimit,omitempty"`
ShareTheme string `json:"shareTheme,omitempty"`
DisableAnonymous bool `json:"disableAnonymous,omitempty"`
MaxBandwidth int `json:"maxBandwidth,omitempty"`
DisableThumbnails bool `json:"disableThumbnails,omitempty"`
KeepAfterExpiration bool `json:"keepAfterExpiration,omitempty"`
AllowedUsernames []string `json:"allowedUsernames,omitempty"`
ThemeColor string `json:"themeColor,omitempty"`
Banner string `json:"banner,omitempty"`
Title string `json:"title,omitempty"`
Description string `json:"description,omitempty"`
Favicon string `json:"favicon,omitempty"`
QuickDownload bool `json:"quickDownload,omitempty"`
HideNavButtons bool `json:"hideNavButtons,omitempty"`
DisableSidebar bool `json:"disableSidebar"`
ViewMode string `json:"viewMode,omitempty"`
Source string `json:"source,omitempty"` // backend source is path to maintain between name changes
Path string `json:"path,omitempty"`
DownloadURL string `json:"downloadURL,omitempty"`
DisableShareCard bool `json:"disableShareCard,omitempty"`
EnforceDarkLightMode string `json:"enforceDarkLightMode,omitempty"` // "dark" or "light"
EnableOnlyOffice bool `json:"enableOnlyOffice,omitempty"`
EnableOnlyOfficeEditing bool `json:"enableOnlyOfficeEditing,omitempty"`
}
type CreateBody struct {
CommonShare
+1 -4
View File
@@ -104,6 +104,7 @@ type NonAdminEditable struct {
DisableViewingExt string `json:"disableViewingExt"` // space separated list of file extensions to disable viewing for
CustomTheme string `json:"customTheme"` // Name of theme to use chosen from custom themes config.
ShowSelectMultiple bool `json:"showSelectMultiple"` // show select multiple files on desktop
DebugOffice bool `json:"debugOffice"` // debug onlyoffice editor
}
type FileLoading struct {
@@ -111,10 +112,6 @@ type FileLoading struct {
ChunkSize int `json:"uploadChunkSizeMb"`
}
var AnonymousUser = User{
Username: "anonymous", // temp user not registered
}
func CleanUsername(s string) string {
// Remove any trailing space to avoid ending on -
s = strings.Trim(s, " ")
+3 -4
View File
@@ -145,6 +145,9 @@ func StartHttp(ctx context.Context, storage *bolt.BoltStore, shutdownComplete ch
publicAPI.HandleFunc("GET /preview", withHashFile(publicPreviewHandler))
publicAPI.HandleFunc("GET /resources", withHashFile(publicShareHandler))
publicAPI.HandleFunc("GET /users", withUser(userGetHandler))
publicAPI.HandleFunc("POST /onlyoffice/callback", withHashFile(onlyofficeCallbackHandler))
publicAPI.HandleFunc("GET /onlyoffice/config", withHashFile(onlyofficeClientConfigGetHandler))
// Settings routes
api.HandleFunc("GET /settings", withAdmin(settingsGetHandler))
@@ -157,11 +160,7 @@ func StartHttp(ctx context.Context, storage *bolt.BoltStore, shutdownComplete ch
api.HandleFunc("GET /jobs/{action}/{target}", withUser(getJobsHandler))
api.HandleFunc("GET /onlyoffice/config", withUser(onlyofficeClientConfigGetHandler))
publicAPI.HandleFunc("GET /onlyoffice/config", withHashFile(onlyofficeClientConfigGetHandler))
api.HandleFunc("POST /onlyoffice/callback", withUser(onlyofficeCallbackHandler))
publicAPI.HandleFunc("POST /onlyoffice/callback", withHashFile(onlyofficeCallbackHandler))
api.HandleFunc("GET /onlyoffice/getToken", withUser(onlyofficeGetTokenHandler))
publicAPI.HandleFunc("GET /onlyoffice/getToken", withHashFile(onlyofficeGetTokenHandler))
api.HandleFunc("GET /search", withUser(searchHandler))
// Mount the public API sub-router
+9 -2
View File
@@ -15,6 +15,7 @@ import (
"github.com/gtsteffaniak/filebrowser/backend/adapters/fs/files"
"github.com/gtsteffaniak/filebrowser/backend/auth"
"github.com/gtsteffaniak/filebrowser/backend/common/errors"
"github.com/gtsteffaniak/filebrowser/backend/common/settings"
"github.com/gtsteffaniak/filebrowser/backend/common/utils"
"github.com/gtsteffaniak/filebrowser/backend/database/share"
"github.com/gtsteffaniak/filebrowser/backend/database/users"
@@ -90,11 +91,16 @@ func withHashFileHelper(fn handleFunc) handleFunc {
Expand: true,
Content: getContent,
})
file.Token = link.Token
if err != nil {
logger.Errorf("error fetching file info for share. hash=%v path=%v error=%v", hash, path, err)
return errToStatus(err), fmt.Errorf("error fetching share from server")
}
file.Token = link.Token
file.Source = ""
file.Hash = link.Hash
if !link.EnableOnlyOffice || !link.DisableFileViewer {
file.OnlyOfficeId = ""
}
if getContent && file.Content != "" {
link.Mu.Lock()
link.Downloads++
@@ -180,7 +186,8 @@ func withOrWithoutUserHelper(fn handleFunc) handleFunc {
}
// Only fall back to anonymous if authentication actually failed
if status == http.StatusUnauthorized || status == http.StatusForbidden {
data.user = &users.AnonymousUser
data.user = &users.User{Username: "anonymous"}
settings.ApplyUserDefaults(data.user)
// If user authentication failed, call the handler without user context
// Clear any user data that might have been partially set
data.token = ""
+224 -100
View File
@@ -38,68 +38,100 @@ func onlyofficeClientConfigGetHandler(w http.ResponseWriter, r *http.Request, d
if settings.Config.Integrations.OnlyOffice.Url == "" {
return http.StatusInternalServerError, errors.New("only-office integration must be configured in settings")
}
encodedUrl := r.URL.Query().Get("url")
// Decode the URL-encoded path
givenUrl, err := url.QueryUnescape(encodedUrl)
if err != nil {
return http.StatusBadRequest, fmt.Errorf("invalid path encoding: %v", err)
// Extract clean parameters from request
source := r.URL.Query().Get("source")
path := r.URL.Query().Get("path")
// Validate required parameters
if (path == "" || source == "") && d.fileInfo.Hash == "" {
logger.Errorf("OnlyOffice callback missing required parameters: source=%s, path=%s", source, path)
return http.StatusBadRequest, errors.New("missing required parameters: path + source/hash are required")
}
// get path from url
pathParts := strings.Split(givenUrl, "/api/raw?files=")
origPathParts := strings.Split(encodedUrl, "/api/raw?files=")
encodedPath := origPathParts[len(origPathParts)-1]
sourceFile := pathParts[len(pathParts)-1]
sourceSplit := strings.Split(sourceFile, "::")
if len(sourceSplit) != 2 {
return http.StatusBadRequest, fmt.Errorf("invalid url path %v", givenUrl)
}
source := sourceSplit[0]
path := sourceSplit[1]
urlFirst := pathParts[0]
if settings.Config.Server.InternalUrl != "" {
urlFirst = settings.Config.Server.InternalUrl + settings.Config.Server.BaseURL
whatToReplace := strings.Split(givenUrl, "/api/raw")[0] + "/"
givenUrl = strings.Replace(givenUrl, whatToReplace, urlFirst, 1)
}
userscope, err := settings.GetScopeFromSourceName(d.user.Scopes, source)
if err != nil {
return http.StatusForbidden, err
}
fileInfo, err := files.FileInfoFaster(iteminfo.FileOptions{
Path: utils.JoinPathAsUnix(userscope, path),
Modify: d.user.Permissions.Modify,
Source: source,
Expand: false,
})
if err != nil {
logger.Debugf("onlyofficeClientConfigGetHandler: failed to get file info for file source %v, path %v: %v", source, path, err)
return errToStatus(err), err
}
id, err := getOnlyOfficeId(source, fileInfo.Path)
if err != nil {
logger.Debugf("getOnlyOfficeId failed for file source %v, path %v: %v", source, fileInfo.Path, err)
return http.StatusNotFound, err
}
split := strings.Split(fileInfo.Name, ".")
fileType := split[len(split)-1]
theme := "light"
if d.user.DarkMode {
theme = "dark"
themeMode := utils.Ternary(d.user.DarkMode, "dark", "light")
if d.fileInfo.Hash == "" {
// Build file info based on whether this is a share or regular request
// Regular user request - need to resolve scope
userScope, scopeErr := settings.GetScopeFromSourceName(d.user.Scopes, source)
if scopeErr != nil {
logger.Errorf("OnlyOffice: source %s not available for user %s: %v", source, d.user.Username, scopeErr)
return http.StatusForbidden, fmt.Errorf("source %s is not available", source)
}
resolvedPath := utils.JoinPathAsUnix(userScope, path)
logger.Debugf("OnlyOffice user request: resolved path=%s", resolvedPath)
fileInfo, err := files.FileInfoFaster(iteminfo.FileOptions{
Path: resolvedPath,
Modify: d.user.Permissions.Modify,
Source: source,
Expand: false,
})
if err != nil {
logger.Errorf("OnlyOffice: failed to get file info for source=%s, path=%s: %v", source, resolvedPath, err)
return errToStatus(err), err
}
d.fileInfo = *fileInfo
} else {
// is a share, use the file info from the share middleware
sourceInfo, ok := settings.Config.Server.SourceMap[d.share.Source]
if !ok {
logger.Error("OnlyOffice: source from share not found")
return http.StatusInternalServerError, fmt.Errorf("source not found for share")
}
source = sourceInfo.Name
// path is index path, so we build from share path
path = utils.JoinPathAsUnix(d.share.Path, path)
if d.share.EnforceDarkLightMode == "dark" {
themeMode = "dark"
}
if d.share.EnforceDarkLightMode == "light" {
themeMode = "light"
}
}
// Determine file type and editing permissions
fileType := getFileExtension(d.fileInfo.Name)
canEdit := iteminfo.CanEditOnlyOffice(d.user.Permissions.Modify, fileType)
mode := "view"
if canEdit {
mode = "edit"
canEditMode := utils.Ternary(canEdit, "edit", "view")
if d.fileInfo.Hash != "" {
if d.share.EnableOnlyOfficeEditing {
canEditMode = "edit"
}
}
callbackURL := fmt.Sprintf("%v/api/onlyoffice/callback?files=%v&auth=%v", strings.TrimSuffix(urlFirst, "/"), encodedPath, d.token)
// For shares, we need to keep track of the original relative path for the callback URL
var callbackPath string
if d.fileInfo.Hash != "" {
// For shares, use the original path parameter (relative to share)
callbackPath = r.URL.Query().Get("path")
} else {
// For regular requests, use the processed path
callbackPath = path
}
// Generate document ID for OnlyOffice
documentId, err := getOnlyOfficeId(source, path)
if err != nil {
logger.Errorf("OnlyOffice: failed to generate document ID for source=%s, path=%s: %v", source, path, err)
return http.StatusNotFound, fmt.Errorf("failed to generate document ID: %v", err)
}
// Build download URL that OnlyOffice server will use
downloadURL := buildOnlyOfficeDownloadURL(source, callbackPath, d.fileInfo.Hash, d.token)
// Build callback URL for OnlyOffice to notify us of changes
callbackURL := buildOnlyOfficeCallbackURL(source, callbackPath, d.fileInfo.Hash, d.token)
// Build OnlyOffice client configuration
clientConfig := map[string]interface{}{
"document": map[string]interface{}{
"fileType": fileType,
"key": id,
"title": fileInfo.Name,
"url": givenUrl + "&auth=" + d.token,
"key": documentId,
"title": d.fileInfo.Name,
"url": downloadURL,
"permissions": map[string]interface{}{
"edit": canEdit,
"edit": canEditMode,
"download": true,
"print": true,
},
@@ -113,78 +145,196 @@ func onlyofficeClientConfigGetHandler(w http.ResponseWriter, r *http.Request, d
"customization": map[string]interface{}{
"autosave": true,
"forcesave": true,
"uiTheme": theme,
"uiTheme": themeMode,
},
"lang": d.user.Locale,
"mode": mode,
"mode": canEditMode,
},
}
// Sign configuration with JWT if secret is configured
if settings.Config.Integrations.OnlyOffice.Secret != "" {
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims(clientConfig))
signature, err := token.SignedString([]byte(settings.Config.Integrations.OnlyOffice.Secret))
if err != nil {
return http.StatusInternalServerError, fmt.Errorf("failed to sign JWT")
logger.Errorf("OnlyOffice: failed to sign JWT: %v", err)
return http.StatusInternalServerError, fmt.Errorf("failed to sign configuration")
}
clientConfig["token"] = signature
}
return renderJSON(w, r, clientConfig)
}
// getFileExtension extracts file extension from filename
func getFileExtension(filename string) string {
parts := strings.Split(filename, ".")
if len(parts) < 2 {
return ""
}
return parts[len(parts)-1]
}
// buildOnlyOfficeDownloadURL constructs the download URL that OnlyOffice server will use to fetch the file
func buildOnlyOfficeDownloadURL(source, path, hash, token string) string {
// Determine base URL (internal URL takes priority for OnlyOffice server communication)
baseURL := settings.Config.Server.BaseURL
if settings.Config.Server.InternalUrl != "" {
baseURL = settings.Config.Server.InternalUrl + settings.Config.Server.BaseURL
}
var downloadURL string
if hash != "" {
// Share download URL - don't expose source name, just use the path relative to share
filesParam := url.QueryEscape(path)
downloadURL = fmt.Sprintf("%s/public/api/raw?files=%s&hash=%s&token=%s&auth=%s",
strings.TrimSuffix(baseURL, "/"), filesParam, hash, token, token)
} else {
// Regular download URL - include source for non-share requests
filesParam := url.QueryEscape(source + "::" + path)
downloadURL = fmt.Sprintf("%s/api/raw?files=%s&auth=%s",
strings.TrimSuffix(baseURL, "/"), filesParam, token)
}
return downloadURL
}
// buildOnlyOfficeCallbackURL constructs the callback URL that OnlyOffice server will use to notify us of changes
func buildOnlyOfficeCallbackURL(source, path, hash, token string) string {
baseURL := settings.Config.Server.BaseURL
if settings.Config.Server.InternalUrl != "" {
baseURL = settings.Config.Server.InternalUrl + settings.Config.Server.BaseURL
}
var callbackURL string
if hash != "" {
// Share callback URL - use public API and don't expose source, use path relative to share
params := url.Values{}
params.Set("hash", hash)
params.Set("path", path) // This should be the path relative to the share, not the full filesystem path
params.Set("auth", token)
callbackURL = fmt.Sprintf("%s/public/api/onlyoffice/callback?%s",
strings.TrimSuffix(baseURL, "/"), params.Encode())
} else {
// Regular callback URL - include source for non-share requests
params := url.Values{}
params.Set("source", source)
params.Set("path", path)
params.Set("auth", token)
callbackURL = fmt.Sprintf("%s/api/onlyoffice/callback?%s",
strings.TrimSuffix(baseURL, "/"), params.Encode())
}
return callbackURL
}
func onlyofficeCallbackHandler(w http.ResponseWriter, r *http.Request, d *requestContext) (int, error) {
// Parse OnlyOffice callback data
body, err := io.ReadAll(r.Body)
if err != nil {
return http.StatusInternalServerError, err
}
var data OnlyOfficeCallback
err = json.Unmarshal(body, &data)
if err != nil {
logger.Errorf("OnlyOffice callback: failed to read request body: %v", err)
return http.StatusInternalServerError, err
}
encodedPath := r.URL.Query().Get("files")
pathParts := strings.Split(encodedPath, "::")
if len(pathParts) < 2 {
return http.StatusBadRequest, fmt.Errorf("invalid path encoding: %v", err)
}
source := pathParts[0]
// Decode the URL-encoded path
path, err := url.QueryUnescape(pathParts[1])
var data OnlyOfficeCallback
err = json.Unmarshal(body, &data)
if err != nil {
return http.StatusBadRequest, fmt.Errorf("invalid path encoding: %v", err)
logger.Errorf("OnlyOffice callback: failed to parse JSON: %v", err)
return http.StatusInternalServerError, err
}
// Extract clean parameters from query string
source := r.URL.Query().Get("source")
path := r.URL.Query().Get("path")
// Validate required parameters
if (path == "" || source == "") && d.fileInfo.Hash == "" {
logger.Errorf("OnlyOffice callback missing required parameters: source=%s, path=%s", source, path)
return http.StatusBadRequest, errors.New("missing required parameters: path + source/hash are required")
}
if d.fileInfo.Hash == "" {
// Regular user request - need to resolve scope
userScope, scopeErr := settings.GetScopeFromSourceName(d.user.Scopes, source)
if scopeErr != nil {
logger.Errorf("OnlyOffice callback: source %s not available for user %s: %v", source, d.user.Username, scopeErr)
return http.StatusForbidden, fmt.Errorf("source %s is not available", source)
}
path = utils.JoinPathAsUnix(userScope, path)
} else {
// is a share, use the file info from the share middleware
sourceInfo, ok := settings.Config.Server.SourceMap[d.share.Source]
if !ok {
logger.Error("OnlyOffice: source from share not found")
return http.StatusInternalServerError, fmt.Errorf("source not found for share")
}
source = sourceInfo.Name
// path is index path, so we build from share path
path = utils.JoinPathAsUnix(d.share.Path, path)
}
fmt.Println("source", source)
fmt.Println("path", path)
// Handle document closure - clean up document key cache
if data.Status == onlyOfficeStatusDocumentClosedWithChanges ||
data.Status == onlyOfficeStatusDocumentClosedWithNoChanges {
// Refer to only-office documentation
// Refer to OnlyOffice documentation:
// - https://api.onlyoffice.com/editors/coedit
// - https://api.onlyoffice.com/editors/callback
//
// When the document is fully closed by all editors,
// then the document key should no longer be re-used.
// the document key should no longer be re-used.
logger.Debugf("OnlyOffice: document closed, cleaning up document ID for source=%s, path=%s", source, path)
deleteOfficeId(source, path)
}
// Handle document save operations
if data.Status == onlyOfficeStatusDocumentClosedWithChanges ||
data.Status == onlyOfficeStatusForceSaveWhileDocumentStillOpen {
// Verify user has modify permissions
if !d.user.Permissions.Modify {
logger.Warningf("OnlyOffice callback: user %s lacks modify permissions for source=%s, path=%s",
d.user.Username, source, path)
return http.StatusForbidden, nil
}
// Download the updated document from OnlyOffice server
doc, err := http.Get(data.URL)
if err != nil {
logger.Errorf("OnlyOffice callback: failed to download updated document: %v", err)
return http.StatusInternalServerError, err
}
defer doc.Body.Close()
// Resolve file path for writing (same logic as in config handler)
var resolvedPath string
if d.fileInfo.Hash == "" {
// Regular user request - need to resolve scope
userScope, scopeErr := settings.GetScopeFromSourceName(d.user.Scopes, source)
if scopeErr != nil {
logger.Errorf("OnlyOffice callback: source %s not available for user %s: %v",
source, d.user.Username, scopeErr)
return http.StatusForbidden, fmt.Errorf("source %s is not available", source)
}
resolvedPath = utils.JoinPathAsUnix(userScope, path)
}
// Write the updated document
fileOpts := iteminfo.FileOptions{
Path: path,
Path: resolvedPath,
Source: source,
}
writeErr := files.WriteFile(fileOpts, doc.Body)
if writeErr != nil {
logger.Errorf("OnlyOffice callback: failed to write updated document: %v", writeErr)
return http.StatusInternalServerError, writeErr
}
}
// Return success response to OnlyOffice server
resp := map[string]int{
"error": 0,
}
@@ -215,29 +365,3 @@ func deleteOfficeId(source, path string) {
realpath, _, _ := idx.GetRealPath(path)
utils.OnlyOfficeCache.Delete(realpath)
}
func onlyofficeGetTokenHandler(w http.ResponseWriter, r *http.Request, d *requestContext) (int, error) {
// get config from body
body, err := io.ReadAll(r.Body)
if err != nil {
return http.StatusInternalServerError, err
}
defer r.Body.Close()
var payload map[string]interface{}
// marshall to struct
err = json.Unmarshal(body, &payload)
if err != nil {
return http.StatusInternalServerError, err
}
if settings.Config.Integrations.OnlyOffice.Secret != "" {
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims(payload))
ss, err := token.SignedString([]byte(settings.Config.Integrations.OnlyOffice.Secret))
if err != nil {
return 500, errors.New("could not generate a new jwt")
}
return renderJSON(w, r, map[string]string{"token": ss})
}
return 400, fmt.Errorf("bad request")
}
+13 -7
View File
@@ -7,6 +7,7 @@ import (
"net/url"
"strings"
"github.com/gtsteffaniak/filebrowser/backend/common/settings"
"github.com/gtsteffaniak/filebrowser/backend/common/utils"
"github.com/gtsteffaniak/go-logger/logger"
@@ -37,12 +38,19 @@ func publicRawHandler(w http.ResponseWriter, r *http.Request, d *requestContext)
d.share.Mu.Unlock()
encodedFiles := r.URL.Query().Get("files")
// Decode the URL-encoded path
f, err := url.QueryUnescape(encodedFiles)
// Decode the URL-encoded path - use PathUnescape to preserve + as literal character
f, err := url.PathUnescape(encodedFiles)
if err != nil {
return http.StatusBadRequest, fmt.Errorf("invalid path encoding: %v", err)
}
// Get the actual source name from the share's source mapping
sourceInfo, ok := settings.Config.Server.SourceMap[d.share.Source]
if !ok {
return http.StatusInternalServerError, fmt.Errorf("source not found for share")
}
actualSourceName := sourceInfo.Name
fileList := []string{}
for _, file := range strings.Split(f, "||") {
// Check if file already contains source prefix (source::path format)
@@ -57,12 +65,12 @@ func publicRawHandler(w http.ResponseWriter, r *http.Request, d *requestContext)
} else {
// Fallback: treat as plain path
filePath := utils.JoinPathAsUnix(d.share.Path, file)
fileList = append(fileList, d.fileInfo.Source+"::"+filePath)
fileList = append(fileList, actualSourceName+"::"+filePath)
}
} else {
// Plain path without source prefix
// Plain path without source prefix - use the actual source name from share
filePath := utils.JoinPathAsUnix(d.share.Path, file)
fileList = append(fileList, d.fileInfo.Source+"::"+filePath)
fileList = append(fileList, actualSourceName+"::"+filePath)
}
}
@@ -76,8 +84,6 @@ func publicRawHandler(w http.ResponseWriter, r *http.Request, d *requestContext)
}
func publicShareHandler(w http.ResponseWriter, r *http.Request, d *requestContext) (int, error) {
// disable onlyoffice for public share
d.fileInfo.OnlyOfficeId = ""
return renderJSON(w, r, d.fileInfo)
}
+3
View File
@@ -116,6 +116,9 @@ func handleWithStaticData(w http.ResponseWriter, r *http.Request, d *requestCont
shareProps["disableSidebar"] = d.share.DisableSidebar
shareProps["isPasswordProtected"] = d.share.PasswordHash != ""
shareProps["downloadURL"] = getDownloadURL(r, d.share.Hash)
shareProps["enforceDarkLightMode"] = d.share.EnforceDarkLightMode
shareProps["enableOnlyOffice"] = d.share.EnableOnlyOffice
shareProps["enableOnlyOfficeEditing"] = d.share.EnableOnlyOfficeEditing
if d.share.Favicon != "" {
if strings.HasPrefix(d.share.Favicon, "http") {
data["favicon"] = d.share.Favicon
+2 -1
View File
@@ -34,7 +34,8 @@ type ExtendedFileInfo struct {
Checksums map[string]string `json:"checksums,omitempty"` // checksums for the file
Token string `json:"token,omitempty"` // token for the file -- used for sharing
OnlyOfficeId string `json:"onlyOfficeId,omitempty"` // id for onlyoffice files
Source string `json:"source"` // associated index source for the file
Source string `json:"source,omitempty"` // associated index source for the file
Hash string `json:"hash,omitempty"` // hash for the file -- used for sharing
RealPath string `json:"-"`
}
+28
View File
@@ -3162,6 +3162,10 @@ const docTemplate = `{
"description": "when false, the date is relative, when true, the date is an exact timestamp",
"type": "boolean"
},
"debugOffice": {
"description": "debug onlyoffice editor",
"type": "boolean"
},
"deleteWithoutConfirming": {
"description": "delete files without confirmation",
"type": "boolean"
@@ -3306,6 +3310,16 @@ const docTemplate = `{
"downloadsLimit": {
"type": "integer"
},
"enableOnlyOffice": {
"type": "boolean"
},
"enableOnlyOfficeEditing": {
"type": "boolean"
},
"enforceDarkLightMode": {
"description": "\"dark\" or \"light\"",
"type": "string"
},
"expires": {
"type": "string"
},
@@ -3397,6 +3411,16 @@ const docTemplate = `{
"downloadsLimit": {
"type": "integer"
},
"enableOnlyOffice": {
"type": "boolean"
},
"enableOnlyOfficeEditing": {
"type": "boolean"
},
"enforceDarkLightMode": {
"description": "\"dark\" or \"light\"",
"type": "string"
},
"expire": {
"type": "integer"
},
@@ -3600,6 +3624,10 @@ const docTemplate = `{
"description": "when false, the date is relative, when true, the date is an exact timestamp",
"type": "boolean"
},
"debugOffice": {
"description": "debug onlyoffice editor",
"type": "boolean"
},
"deleteWithoutConfirming": {
"description": "delete files without confirmation",
"type": "boolean"
+28
View File
@@ -3151,6 +3151,10 @@
"description": "when false, the date is relative, when true, the date is an exact timestamp",
"type": "boolean"
},
"debugOffice": {
"description": "debug onlyoffice editor",
"type": "boolean"
},
"deleteWithoutConfirming": {
"description": "delete files without confirmation",
"type": "boolean"
@@ -3295,6 +3299,16 @@
"downloadsLimit": {
"type": "integer"
},
"enableOnlyOffice": {
"type": "boolean"
},
"enableOnlyOfficeEditing": {
"type": "boolean"
},
"enforceDarkLightMode": {
"description": "\"dark\" or \"light\"",
"type": "string"
},
"expires": {
"type": "string"
},
@@ -3386,6 +3400,16 @@
"downloadsLimit": {
"type": "integer"
},
"enableOnlyOffice": {
"type": "boolean"
},
"enableOnlyOfficeEditing": {
"type": "boolean"
},
"enforceDarkLightMode": {
"description": "\"dark\" or \"light\"",
"type": "string"
},
"expire": {
"type": "integer"
},
@@ -3589,6 +3613,10 @@
"description": "when false, the date is relative, when true, the date is an exact timestamp",
"type": "boolean"
},
"debugOffice": {
"description": "debug onlyoffice editor",
"type": "boolean"
},
"deleteWithoutConfirming": {
"description": "delete files without confirmation",
"type": "boolean"
+20
View File
@@ -591,6 +591,9 @@ definitions:
description: when false, the date is relative, when true, the date is an exact
timestamp
type: boolean
debugOffice:
description: debug onlyoffice editor
type: boolean
deleteWithoutConfirming:
description: delete files without confirmation
type: boolean
@@ -696,6 +699,13 @@ definitions:
type: string
downloadsLimit:
type: integer
enableOnlyOffice:
type: boolean
enableOnlyOfficeEditing:
type: boolean
enforceDarkLightMode:
description: '"dark" or "light"'
type: string
expires:
type: string
favicon:
@@ -757,6 +767,13 @@ definitions:
type: integer
downloadsLimit:
type: integer
enableOnlyOffice:
type: boolean
enableOnlyOfficeEditing:
type: boolean
enforceDarkLightMode:
description: '"dark" or "light"'
type: string
expire:
type: integer
favicon:
@@ -900,6 +917,9 @@ definitions:
description: when false, the date is relative, when true, the date is an exact
timestamp
type: boolean
debugOffice:
description: debug onlyoffice editor
type: boolean
deleteWithoutConfirming:
description: delete files without confirmation
type: boolean
-740
View File
@@ -1,740 +0,0 @@
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 92478a24..5085e058 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -11,17 +11,16 @@ All notable changes to this project will be documented in this file. For commit
**New Features**:
- New access control system that works like shares. You can add new allow / deny rules for users/groups for specific paths on specific sources.
- #- The concept of 'groups', either automatically from OIDC groups claim or defined https://github.com/gtsteffaniak/filebrowser/issues/545
+ - The concept of 'groups', either automatically from OIDC groups claim or defined https://github.com/gtsteffaniak/filebrowser/issues/545
- share view changes -- now aligns with the standard listing view. This means files can be viewed and edited (if permission allows) just like a normal listing.
- many share links customization enhancements
- #- only share to certain authenticated users https://github.com/gtsteffaniak/filebrowser/issues/656 https://github.com/gtsteffaniak/filebrowser/issues/985
- #- one-time download links
- #- customize share theme, banner image, and icons https://github.com/gtsteffaniak/filebrowser/issues/827 https://github.com/gtsteffaniak/filebrowser/issues/1029
- #- share link info customization https://github.com/gtsteffaniak/filebrowser/issues/792 https://github.com/gtsteffaniak/filebrowser/issues/841 https://github.com/gtsteffaniak/filebrowser/issues/520
- #- "shared with me" and "share history" https://github.com/gtsteffaniak/filebrowser/issues/943
+ - only share to certain authenticated users https://github.com/gtsteffaniak/filebrowser/issues/656 https://github.com/gtsteffaniak/filebrowser/issues/985
+ - one-time download links
+ - customize share theme, banner image, and icons https://github.com/gtsteffaniak/filebrowser/issues/827 https://github.com/gtsteffaniak/filebrowser/issues/1029
+ - share link info customization https://github.com/gtsteffaniak/filebrowser/issues/792 https://github.com/gtsteffaniak/filebrowser/issues/841 https://github.com/gtsteffaniak/filebrowser/issues/520
+ - "shared with me" and "share history" https://github.com/gtsteffaniak/filebrowser/issues/943
- upload to share links https://github.com/gtsteffaniak/filebrowser/issues/661
- share link public changes https://github.com/gtsteffaniak/filebrowser/issues/473
- -
- public/private folder logic https://github.com/gtsteffaniak/filebrowser/issues/505
**BugFixes**:
diff --git a/backend/http/middleware.go b/backend/http/middleware.go
index 5ba397fe..595f00e1 100644
--- a/backend/http/middleware.go
+++ b/backend/http/middleware.go
@@ -6,7 +6,6 @@ import (
"encoding/json"
"fmt"
"net/http"
- "net/url"
"runtime"
"strings"
"time"
@@ -44,14 +43,8 @@ type handleFunc func(w http.ResponseWriter, r *http.Request, data *requestContex
// Middleware to handle file requests by hash and pass it to the handler
func withHashFileHelper(fn handleFunc) handleFunc {
return withOrWithoutUserHelper(func(w http.ResponseWriter, r *http.Request, data *requestContext) (int, error) {
+ path := r.URL.Query().Get("path")
hash := r.URL.Query().Get("hash")
- encodedPath := r.URL.Query().Get("path")
- // Decode the URL-encoded path
- path, err := url.QueryUnescape(encodedPath)
- if err != nil {
- return http.StatusBadRequest, fmt.Errorf("invalid path encoding: %v", err)
- }
- data.path = path
// Get the file link by hash
link, err := store.Share.GetByHash(hash)
if err != nil {
@@ -71,6 +64,11 @@ func withHashFileHelper(fn handleFunc) handleFunc {
return status, fmt.Errorf("could not authenticate share request")
}
}
+ data.path = strings.TrimSuffix(link.Path, "/") + "/" + strings.TrimPrefix(path, "/")
+ if path == "" || path == "/" {
+ data.path = link.Path
+ }
+
source, ok := config.Server.SourceMap[link.Source]
if !ok {
return http.StatusNotFound, fmt.Errorf("source not found")
diff --git a/backend/http/middleware_test.go b/backend/http/middleware_test.go
index ff7c173e..eb257951 100644
--- a/backend/http/middleware_test.go
+++ b/backend/http/middleware_test.go
@@ -155,20 +155,6 @@ func TestPublicShareHandlerAuthentication(t *testing.T) {
},
expectedStatusCode: 0, // zero means 200 on helpers
},
- {
- name: "Private share, valid password when token exists",
- share: &share.Link{
- Hash: "pw_and_token_hash",
- UserID: 1,
- PasswordHash: passwordBcrypt,
- Token: "some_random_token",
- Source: "/srv",
- },
- extraHeaders: map[string]string{
- "X-SHARE-PASSWORD": "password",
- },
- expectedStatusCode: 0, // zero means 200 on helpers
- },
{
name: "Private share, no auth provided",
share: &share.Link{
diff --git a/backend/http/public.go b/backend/http/public.go
index ef65dfa6..0e641fbd 100644
--- a/backend/http/public.go
+++ b/backend/http/public.go
@@ -7,6 +7,9 @@ import (
"net/url"
"strings"
+ "github.com/gtsteffaniak/filebrowser/backend/adapters/fs/files"
+ "github.com/gtsteffaniak/filebrowser/backend/common/utils"
+ "github.com/gtsteffaniak/filebrowser/backend/indexing/iteminfo"
"github.com/gtsteffaniak/go-logger/logger"
_ "github.com/gtsteffaniak/filebrowser/backend/swagger/docs"
@@ -49,7 +52,7 @@ func publicRawHandler(w http.ResponseWriter, r *http.Request, d *requestContext)
}
func publicShareHandler(w http.ResponseWriter, r *http.Request, d *requestContext) (int, error) {
- d.fileInfo.Path = "/" + strings.TrimPrefix(d.fileInfo.Path, d.share.Path)
+ d.fileInfo.Path = strings.TrimPrefix(d.fileInfo.Path, d.share.Path)
return renderJSON(w, r, d.fileInfo)
}
@@ -91,5 +94,24 @@ func publicPreviewHandler(w http.ResponseWriter, r *http.Request, d *requestCont
if config.Server.DisablePreviews {
return http.StatusNotImplemented, fmt.Errorf("preview is disabled")
}
+ path := r.URL.Query().Get("path")
+ var err error
+ if path == "" {
+ return http.StatusBadRequest, fmt.Errorf("invalid request path")
+ }
+ fileInfo, err := files.FileInfoFaster(iteminfo.FileOptions{
+ Path: utils.JoinPathAsUnix(d.share.Path, path),
+ Modify: false, // TODO make this configurable
+ Source: d.fileInfo.Source,
+ Expand: true,
+ })
+ if err != nil {
+ logger.Debugf("public preview handler: error getting file info: %v", err)
+ return 400, fmt.Errorf("file not found")
+ }
+ if fileInfo.Type == "directory" {
+ return http.StatusBadRequest, fmt.Errorf("can't create preview for directory")
+ }
+ d.fileInfo = fileInfo
return previewHelperFunc(w, r, d)
}
diff --git a/frontend/src/api/index.js b/frontend/src/api/index.js
index 9399a292..2ef5f38e 100644
--- a/frontend/src/api/index.js
+++ b/frontend/src/api/index.js
@@ -7,3 +7,6 @@ import search from "./search";
// Note: shareApi has been consolidated into publicApi
export { filesApi, publicApi, usersApi, settingsApi, search, accessApi };
+
+// For backward compatibility, export publicApi as shareApi as well
+export { publicApi as shareApi };
diff --git a/frontend/src/api/public.js b/frontend/src/api/public.js
index 1dcb4125..d94713c5 100644
--- a/frontend/src/api/public.js
+++ b/frontend/src/api/public.js
@@ -2,7 +2,6 @@ import { fetchURL, fetchJSON, adjustedData } from "./utils";
import { notify } from "@/notify";
import { getApiPath, removePrefix } from "@/utils/url.js";
import { externalUrl, baseURL } from "@/utils/constants";
-import { state } from "@/store";
// ============================================================================
// PUBLIC API ENDPOINTS (hash-based authentication)
@@ -13,8 +12,7 @@ export async function fetchPub(path, hash, password = "", content = false) {
const params = {
path,
hash,
- ...(content && { content: 'true' }),
- ...(state.share.token && { token: state.share.token })
+ ...(content && { content: 'true' })
}
const apiPath = getApiPath("public/api/shared", params);
const response = await fetch(apiPath, {
@@ -24,17 +22,7 @@ export async function fetchPub(path, hash, password = "", content = false) {
});
if (!response.ok) {
- const error = new Error(response.statusText);
- // attempt to marshal json response
- let data = null;
- try {
- data = await response.json()
- } catch (e) {
- // ignore
- }
- if (data) {
- error.message = data.message;
- }
+ const error = new Error("Failed to connect to the server.");
error.status = response.status;
throw error;
}
@@ -57,14 +45,13 @@ export function getDownloadURL(share, files) {
}
// Generate a preview URL for public shares
-export function getPreviewURL(path) {
+export function getPreviewURL(hash, path) {
try {
const params = {
path: encodeURIComponent(path),
size: 'small',
- hash: state.share.hash,
- inline: 'true',
- ...(state.share.token && { token: state.share.token })
+ hash: hash,
+ inline: 'true'
}
const apiPath = getApiPath('public/api/preview', params)
return window.origin + apiPath
diff --git a/frontend/src/api/share.js b/frontend/src/api/share.js
new file mode 100644
index 00000000..729b7599
--- /dev/null
+++ b/frontend/src/api/share.js
@@ -0,0 +1,66 @@
+import { fetchURL, fetchJSON, adjustedData } from "./utils";
+import { notify } from "@/notify";
+import { getApiPath } from "@/utils/url.js";
+import { externalUrl } from "@/utils/constants";
+
+export async function list() {
+ const apiPath = getApiPath("api/shares");
+ return fetchJSON(apiPath);
+}
+
+export async function get(path, source) {
+ try {
+ const params = { path: encodeURIComponent(path), source: encodeURIComponent(source) };
+ const apiPath = getApiPath("api/share",params);
+ let data = fetchJSON(apiPath);
+ return adjustedData(data, path);
+ } catch (err) {
+ notify.showError(err.message || "Error fetching data");
+ throw err;
+ }
+}
+
+export async function remove(hash) {
+ const params = { hash };
+ const apiPath = getApiPath("api/share",params);
+ await fetchURL(apiPath, {
+ method: "DELETE",
+ });
+}
+
+export async function create(path, source, password = "", expires = "", unit = "hours") {
+ const params = { path: encodeURIComponent(path), source: encodeURIComponent(source) };
+ const apiPath = getApiPath("api/share",params);
+ let body = "{}";
+ if (password != "" || expires !== "" || unit !== "hours") {
+ body = JSON.stringify({ password: password, expires: expires, unit: unit });
+ }
+ return fetchJSON(apiPath, {
+ method: "POST",
+ body: body,
+ });
+}
+
+export function getShareURL(share) {
+ if (externalUrl) {
+ const apiPath = getApiPath(`share/${share.hash}`)
+ return externalUrl + apiPath
+ }
+ return window.origin+getApiPath(`share/${share.hash}`);
+}
+
+export function getPreviewURL(hash, path) {
+ try {
+ const params = {
+ path: encodeURIComponent(path),
+ size: 'small',
+ hash: hash,
+ inline: 'true'
+ }
+ const apiPath = getApiPath('api/public/preview', params)
+ return window.origin + apiPath
+ } catch (err) {
+ notify.showError(err.message || 'Error getting preview URL')
+ throw err
+ }
+}
diff --git a/frontend/src/components/files/ListingItem.vue b/frontend/src/components/files/ListingItem.vue
index 29c2749f..93848c1c 100644
--- a/frontend/src/components/files/ListingItem.vue
+++ b/frontend/src/components/files/ListingItem.vue
@@ -67,7 +67,7 @@ import { enableThumbs } from "@/utils/constants";
import downloadFiles from "@/utils/download";
import { getHumanReadableFilesize } from "@/utils/filesizes";
-import { filesApi,publicApi } from "@/api";
+import { filesApi, shareApi } from "@/api";
import * as upload from "@/utils/upload";
import { state, getters, mutations } from "@/store"; // Import your custom store
import { url } from "@/utils";
@@ -162,8 +162,8 @@ export default {
// @ts-ignore
let path = url.removeTrailingSlash(state.req.path) + "/" + this.name;
if (getters.isShare()) {
- let urlPath = getters.getSharePath(this.name) ;
- return publicApi.getPreviewURL(urlPath);
+ let urlPath = getters.getSharePath() + "/" + this.name;
+ return shareApi.getPreviewURL(state.share.hash, urlPath, state.req.modified);
}
// @ts-ignore
return filesApi.getPreviewURL(state.req.source, path, this.modified);
@@ -226,7 +226,7 @@ export default {
},
getUrl() {
if (this.hash) {
- return baseURL + "public/share/" + this.hash + "/" + url.encodedPath(this.path);
+ return baseURL + "share/" + this.hash + url.encodedPath(this.path);
}
if (serverHasMultipleSources) {
return baseURL + "files/" + encodeURIComponent(this.source) + url.encodedPath(this.path);
diff --git a/frontend/src/components/prompts/ActionApi.vue b/frontend/src/components/prompts/ActionApi.vue
index f2ebaa3f..1a381565 100644
--- a/frontend/src/components/prompts/ActionApi.vue
+++ b/frontend/src/components/prompts/ActionApi.vue
@@ -90,3 +90,20 @@ export default {
},
};
</script>
+
+<style>
+/* Basic styling for the prompt */
+.card.floating {
+ padding: 20px;
+}
+.card-content {
+ margin-bottom: 16px;
+}
+.card-action {
+ display: flex;
+ justify-content: flex-end;
+}
+.card-action .button {
+ margin-left: 8px;
+}
+</style>
diff --git a/frontend/src/components/prompts/Share.vue b/frontend/src/components/prompts/Share.vue
index be1ad5ad..8689100d 100644
--- a/frontend/src/components/prompts/Share.vue
+++ b/frontend/src/components/prompts/Share.vue
@@ -2,11 +2,10 @@
<div class="card-title">
<h2>{{ $t("buttons.share") }}</h2>
</div>
- <div class="card-content">
- <div aria-label="share-path" class="searchContext"> {{ $t('search.path') }} {{ subpath }}</div>
- <p> {{ $t('share.notice') }} </p>
-
- <div v-if="listing">
+ <div aria-label="share-path" class="searchContext"> {{ $t('search.path') }} {{ subpath }}</div>
+ <p> {{ $t('share.notice') }} </p>
+ <template v-if="listing">
+ <div class="card-content">
<table>
<tbody>
<tr>
@@ -46,7 +45,20 @@
</table>
</div>
- <div v-else>
+ <div class="card-action">
+ <button class="button button--flat button--grey" @click="closeHovers" :aria-label="$t('buttons.close')"
+ :title="$t('buttons.close')">
+ {{ $t("buttons.close") }}
+ </button>
+ <button class="button button--flat button--blue" @click="() => switchListing()" :aria-label="$t('buttons.new')"
+ :title="$t('buttons.new')">
+ {{ $t("buttons.new") }}
+ </button>
+ </div>
+ </template>
+
+ <template v-else>
+ <div class="card-content">
<p>{{ $t("settings.shareDuration") }}</p>
<div class="form-flex-group">
<input class="form-grow input flat-right" v-focus type="number" max="2147483647" min="1" @keyup.enter="submit" v-model.trim="time" />
@@ -58,7 +70,7 @@
</select>
</div>
<p>{{ $t("prompts.optionalPassword") }}</p>
- <input class="input" type="password" autocomplete="off" v-model.trim="password" />
+ <input class="input" type="password" v-model.trim="password" />
<div class="settings-items">
<ToggleSwitch class="item" v-model="readOnly" :name="'Read-only access'" />
@@ -80,32 +92,23 @@
</select>
</div>
</div>
- </div>
- <div class="card-action">
- <button v-if="listing" class="button button--flat button--grey" @click="closeHovers" :aria-label="$t('buttons.close')"
- :title="$t('buttons.close')">
- {{ $t("buttons.close") }}
- </button>
- <button v-if="listing" class="button button--flat button--blue" @click="() => switchListing()" :aria-label="$t('buttons.new')"
- :title="$t('buttons.new')">
- {{ $t("buttons.new") }}
- </button>
-
- <button v-if="!listing" class="button button--flat button--grey" @click="() => switchListing()" :aria-label="$t('buttons.cancel')"
- :title="$t('buttons.cancel')">
- {{ $t("buttons.cancel") }}
- </button>
- <button v-if="!listing" class="button button--flat button--blue" @click="submit" aria-label="Share-Confirm"
- :title="$t('buttons.share')">
- {{ $t("buttons.share") }}
- </button>
- </div>
+ <div class="card-action">
+ <button class="button button--flat button--grey" @click="() => switchListing()" :aria-label="$t('buttons.cancel')"
+ :title="$t('buttons.cancel')">
+ {{ $t("buttons.cancel") }}
+ </button>
+ <button class="button button--flat button--blue" @click="submit" aria-label="Share-Confirm"
+ :title="$t('buttons.share')">
+ {{ $t("buttons.share") }}
+ </button>
+ </div>
+ </template>
</template>
<script>
import { notify } from "@/notify";
import { state, getters, mutations } from "@/store";
-import { publicApi } from "@/api";
+import { shareApi, publicApi } from "@/api";
import Clipboard from "clipboard";
import { fromNow } from "@/utils/moment";
import { buildItemUrl } from "@/utils/url";
@@ -188,7 +191,7 @@ export default {
try {
// get last element of the path
- const links = await publicApi.get(this.subpath, this.source);
+ const links = await shareApi.get(this.subpath, this.source);
this.links = links;
} catch (err) {
notify.showError(err);
@@ -215,9 +218,9 @@ export default {
let isPermanent = !this.time || this.time == 0;
let res = null;
if (isPermanent) {
- res = await publicApi.create(this.subpath, this.source, this.password);
+ res = await shareApi.create(this.subpath, this.source, this.password);
} else {
- res = await publicApi.create(
+ res = await shareApi.create(
this.subpath,
this.source,
this.password,
@@ -241,7 +244,7 @@ export default {
async deleteLink(event, link) {
event.preventDefault();
try {
- await publicApi.remove(link.hash);
+ await shareApi.remove(link.hash);
this.links = this.links.filter((item) => item.hash !== link.hash);
if (this.links.length === 0) {
this.listing = false;
@@ -254,7 +257,7 @@ export default {
return fromNow(time, state.user.locale)
},
buildLink(share) {
- return publicApi.getShareURL(share);
+ return shareApi.getShareURL(share);
},
hasDownloadLink() {
if (state.isSearchActive) {
diff --git a/frontend/src/components/sidebar/General.vue b/frontend/src/components/sidebar/General.vue
index 58f02c10..0e030aeb 100644
--- a/frontend/src/components/sidebar/General.vue
+++ b/frontend/src/components/sidebar/General.vue
@@ -159,7 +159,7 @@ export default {
isStickySidebar: () => getters.isStickySidebar(),
isMobile: () => getters.isMobile(),
isListingView: () => getters.currentView() == "listingView",
- user: () => (state.user),
+ user: () => (console.log(state.user), state.user),
isDarkMode: () => getters.isDarkMode(),
showSources: () => !getters.isShare(),
currentPrompt: () => getters.currentPrompt(),
diff --git a/frontend/src/css/dashboard.css b/frontend/src/css/dashboard.css
index 2e1fbc8f..77da5938 100644
--- a/frontend/src/css/dashboard.css
+++ b/frontend/src/css/dashboard.css
@@ -226,10 +226,6 @@ body.rtl .card-title>*:first-child {
overflow: auto;
}
-.card-content h3 {
- text-align: center;
-}
-
.card h2 {
font-weight: 500;
}
diff --git a/frontend/src/store/getters.js b/frontend/src/store/getters.js
index cb062702..d97e7eed 100644
--- a/frontend/src/store/getters.js
+++ b/frontend/src/store/getters.js
@@ -201,9 +201,10 @@ export const getters = {
sharePathBase: () => {
return '/public/share/' + getters.shareHash() + '/'
},
- getSharePath: (subPath = "") => {
+ getSharePath: () => {
let urlPath = getters.routePath('public/share')
- return "/" + removeLeadingSlash(urlPath.split(state.share.hash)[1]) + "/" + subPath
+ let parts = urlPath.split('/')
+ return "/" + removeLeadingSlash(parts.slice(2).join('/'))
},
currentView: () => {
let listingView = ''
@@ -288,6 +289,7 @@ export const getters = {
},
currentPromptName: () => {
+ console.log("currentPromptName", state.prompts);
// Ensure state.prompts is an array
if (!Array.isArray(state.prompts) || state.prompts.length === 0) {
return ""
diff --git a/frontend/src/store/mutations.js b/frontend/src/store/mutations.js
index 8dc0b19f..b2b13461 100644
--- a/frontend/src/store/mutations.js
+++ b/frontend/src/store/mutations.js
@@ -191,6 +191,7 @@ export const mutations = {
emitStateChanged();
},
showHover: (value) => {
+ console.log("showHover", value);
if (typeof value === "object") {
state.prompts.push({
name: value?.name,
@@ -206,6 +207,7 @@ export const mutations = {
props: value?.props,
});
}
+ console.log("showHover", state.prompts);
emitStateChanged();
},
setLoading: (loadType, status) => {
@@ -221,6 +223,7 @@ export const mutations = {
emitStateChanged();
},
setCurrentUser: (value) => {
+ console.log('setCurrentUser', value);
try {
// If value is null or undefined, emit state change and exit early
if (!value) {
@@ -243,6 +246,7 @@ export const mutations = {
} catch (error) {
console.log(error);
}
+ console.log('setCurrentUser', state.user);
emitStateChanged();
},
setJWT: (value) => {
@@ -303,6 +307,7 @@ export const mutations = {
if (!state.user) {
state.user = {};
}
+ console.log('updateCurrentUser', value);
// Store previous state for comparison
const previousUser = { ...state.user };
@@ -315,12 +320,14 @@ export const mutations = {
i18n.default.locale = state.user.locale;
localStorage.setItem("userLocale", state.user.locale);
}
+ console.log('updateCurrentUser2', state.user);
// Update localStorage if stickySidebar exists
if (state.user.stickySidebar && getters.currentView() == "listingView") {
state.multiButtonState = "menu";
} else if (state.showSidebar) {
state.multiButtonState = "back";
}
+ console.log('updateCurrentUser3', state.user);
// Update users if there's any change in state.user
if (JSON.stringify(state.user) !== JSON.stringify(previousUser)) {
// Only update the properties that were actually provided in the input
diff --git a/frontend/src/views/Files.vue b/frontend/src/views/Files.vue
index 4c69ecd7..6f8b892b 100644
--- a/frontend/src/views/Files.vue
+++ b/frontend/src/views/Files.vue
@@ -9,9 +9,8 @@
<div class="card-title">
<h2>{{ $t("general.password") }}</h2>
</div>
- <div class="card-content form-flex-group">
+ <div class="card-content">
<input
- class="input share-password"
v-focus
type="password"
:placeholder="$t('general.password')"
@@ -149,10 +148,8 @@ export default {
});
// Add glow effect
element.classList.add('scroll-glow');
- console.log("scrollToHash", element)
// Remove glow effect after animation completes
setTimeout(() => {
- console.log("scrollToHash remove glow", element)
element.classList.remove('scroll-glow');
}, 1000);
}
@@ -211,15 +208,19 @@ export default {
this.shareHash = parts[1]
this.shareSubPath = "/" + parts.slice(2).join("/");
+ // Store share data in state for use by components
+ mutations.setShareData({
+ hash: this.shareHash,
+ token: this.shareToken,
+ subPath: this.shareSubPath,
+ });
+
// Handle password
- if (this.sharePassword === "") {
+ if (this.sharePassword === "" || this.sharePassword === null) {
this.sharePassword = localStorage.getItem("sharepass:" + this.shareHash);
} else {
localStorage.setItem("sharepass:" + this.shareHash, this.sharePassword);
}
- if (this.sharePassword === null) {
- this.sharePassword = "";
- }
mutations.resetSelected();
mutations.setMultiple(false);
@@ -229,15 +230,11 @@ export default {
let file = await publicApi.fetchPub(this.shareSubPath, this.shareHash, this.sharePassword);
file.hash = this.shareHash;
this.shareToken = file.token;
- // Store share data in state for use by components
- mutations.setShareData({
- hash: this.shareHash,
- token: this.shareToken,
- subPath: this.shareSubPath,
- });
+
// If not a directory, fetch content for preview components
if (file.type != "directory") {
const content = !getters.fileViewingDisabled(file.name);
+
file = await publicApi.fetchPub(this.shareSubPath, this.shareHash, this.sharePassword, content);
file.hash = this.shareHash;
this.shareToken = file.token;
@@ -324,7 +321,7 @@ export default {
};
</script>
-<style>
+<style scoped>
.share__wrong__password {
color: #ff4757;
text-align: center;
@@ -351,8 +348,4 @@ export default {
color: inherit;
}
}
-
-.share-password {
- width: 100%;
-}
</style>
diff --git a/frontend/src/views/Settings.vue b/frontend/src/views/Settings.vue
index 4547e3d2..38d75dc1 100644
--- a/frontend/src/views/Settings.vue
+++ b/frontend/src/views/Settings.vue
@@ -122,4 +122,7 @@ export default {
background-color: var(--surfaceSecondary);
}
+.card-content {
+ padding: 1em;
+}
</style>
diff --git a/frontend/src/views/settings/Profile.vue b/frontend/src/views/settings/Profile.vue
index f65fe30f..e34e405a 100644
--- a/frontend/src/views/settings/Profile.vue
+++ b/frontend/src/views/settings/Profile.vue
@@ -428,6 +428,9 @@ export default {
</script>
<style scoped>
+.card-content h3 {
+ text-align: center;
+}
#disablePreviews,
#disableViewing,
diff --git a/frontend/src/views/settings/Shares.vue b/frontend/src/views/settings/Shares.vue
index cd28af70..6d39166e 100644
--- a/frontend/src/views/settings/Shares.vue
+++ b/frontend/src/views/settings/Shares.vue
@@ -49,7 +49,7 @@
<script>
import { notify } from "@/notify";
-import { publicApi } from "@/api";
+import { shareApi } from "@/api";
import { state, mutations, getters } from "@/store";
import Clipboard from "clipboard";
import Errors from "@/views/Errors.vue";
@@ -70,7 +70,7 @@ export default {
async created() {
mutations.setLoading("shares", true);
try {
- let links = await publicApi.list();
+ let links = await shareApi.list();
if (links.length === 0) {
this.links = [];
return;
@@ -115,7 +115,7 @@ export default {
mutations.closeHovers();
try {
- publicApi.remove(link.hash);
+ shareApi.remove(link.hash);
this.links = this.links.filter((item) => item.hash !== link.hash);
notify.showSuccess(this.$t("settings.shareDeleted"));
} catch (e) {
@@ -128,7 +128,7 @@ export default {
return fromNow(time);
},
buildLink(share) {
- return publicApi.getShareURL(share);
+ return shareApi.getShareURL(share);
},
},
};
+1
View File
@@ -151,6 +151,7 @@ userDefaults:
disableOnlyOfficeExt: "" # list of file extensions to disable onlyoffice editor for
customTheme: "" # Name of theme to use chosen from custom themes config.
showSelectMultiple: false # show select multiple files on desktop
debugOffice: false # debug onlyoffice editor
integrations:
office: # validate:omitempty
url: "" # The URL to the OnlyOffice Document Server, needs to be accessible to the user. validate:required
+15 -1
View File
@@ -1,5 +1,5 @@
import { fetchURL, adjustedData } from './utils'
import { getApiPath, doubleEncode } from '@/utils/url.js'
import { getApiPath, doubleEncode, getPublicApiPath } from '@/utils/url.js'
import { mutations } from '@/store'
import { notify } from '@/notify'
import { externalUrl } from '@/utils/constants'
@@ -265,3 +265,17 @@ export async function sources() {
throw err
}
}
export async function GetOfficeConfig(req) {
const params = {
path: encodeURIComponent(req.path),
...(req.hash && { hash: encodeURIComponent(req.hash) }),
...(req.source && { source: encodeURIComponent(req.source) })
}
let apiPath = getApiPath('api/onlyoffice/config', params)
if (req.hash) {
apiPath = getPublicApiPath('onlyoffice/config', params)
}
const res = await fetchURL(apiPath)
return await res.json()
}
+3 -1
View File
@@ -57,8 +57,10 @@ export async function fetchPub(path, hash, password = "", content = false) {
* @returns {string}
*/
export function getDownloadURL(share, files, inline=false) {
// Join files array with || delimiter and then URL encode
const filesParam = Array.isArray(files) ? files.join('||') : files;
const params = {
files: files,
files: encodeURIComponent(filesParam),
hash: share.hash,
token: share.token,
...(inline && { inline: 'true' })
+32 -4
View File
@@ -3,14 +3,14 @@
ref="tooltipRef"
v-show="tooltip.show"
class="floating-tooltip fb-shadow"
:class="{ 'dark-mode': isDarkMode }"
:class="{ 'dark-mode': isDarkMode, 'pointer-enabled': tooltip.pointerEvents }"
:style="tooltipStyle"
v-html="tooltip.content"
></div>
</template>
<script>
import { state } from "@/store";
import { state,getters } from "@/store";
export default {
name: "Tooltip",
@@ -26,13 +26,27 @@ export default {
return state.tooltip;
},
isDarkMode() {
return state.user.darkMode;
return getters.isDarkMode();
},
tooltipStyle() {
return {
const style = {
top: `${this.adjustedY}px`,
left: `${this.adjustedX}px`,
};
// Add custom width if specified
if (this.tooltip.width) {
style.maxWidth = this.tooltip.width;
style.width = this.tooltip.width;
}
// Add max height and scrolling for viewport overflow
if (this.tooltip.width || this.tooltip.pointerEvents) {
style.maxHeight = '80vh';
style.overflowY = 'auto';
}
return style;
},
},
watch: {
@@ -53,6 +67,10 @@ export default {
},
},
methods: {
/**
* @param {number} x - X coordinate
* @param {number} y - Y coordinate
*/
updatePosition(x, y) {
this.$nextTick(() => {
const tooltipEl = this.$refs.tooltipRef;
@@ -101,6 +119,16 @@ export default {
white-space: normal;
overflow-wrap: break-word;
}
.floating-tooltip.pointer-enabled {
pointer-events: auto;
cursor: auto;
}
.floating-tooltip.pointer-enabled a {
pointer-events: auto;
cursor: pointer;
}
.tooltip-info-icon {
font-size: 1em !important;
padding: 0.1em !important;
+32 -1
View File
@@ -89,6 +89,25 @@
/>
<ToggleSwitch class="item" v-model="disableAnonymous" :name="$t('share.disableAnonymous')" :description="$t('share.disableAnonymousDescription')" />
<ToggleSwitch class="item" v-model="enableAllowedUsernames" :name="$t('share.enableAllowedUsernames')" :description="$t('share.enableAllowedUsernamesDescription')" />
<ToggleSwitch v-if="onlyOfficeAvailable" class="item" v-model="enableOnlyOffice" :name="$t('share.enableOnlyOffice')" :description="$t('share.enableOnlyOfficeDescription')" />
<ToggleSwitch v-if="onlyOfficeAvailable" class="item" v-model="enableOnlyOfficeEditing" :name="$t('share.enableOnlyOfficeEditing')" :description="$t('share.enableOnlyOfficeEditingDescription')" />
<p>
{{ $t("share.enforceDarkLightMode") }}
<i
class="no-select material-symbols-outlined tooltip-info-icon"
@mouseenter="showTooltip($event, $t('share.enforceDarkLightModeDescription'))"
@mouseleave="hideTooltip"
>
help
</i>
</p>
<select class="input" v-model="enforceDarkLightMode">
<option value="default">{{ $t("share.default") }}</option>
<option value="dark">{{ $t("share.dark") }}</option>
<option value="light">{{ $t("share.light") }}</option>
</select>
<div v-if="enableAllowedUsernames" class="item">
<input class="input" type="text" v-model.trim="allowedUsernames" :placeholder="$t('share.allowedUsernamesPlaceholder')" />
</div>
@@ -255,7 +274,7 @@ import Clipboard from "clipboard";
import { fromNow } from "@/utils/moment";
import { buildItemUrl, fixDownloadURL } from "@/utils/url";
import ToggleSwitch from "@/components/settings/ToggleSwitch.vue";
import { userSelectableThemes, externalUrl } from "@/utils/constants";
import { userSelectableThemes, externalUrl, onlyOfficeUrl } from "@/utils/constants";
import { eventBus } from "@/store/eventBus";
//import ViewMode from "@/components/settings/ViewMode.vue";
@@ -315,10 +334,16 @@ export default {
disableNavButtons: false,
disableShareCard: false,
disableSidebar: false,
enforceDarkLightMode: "default",
enableOnlyOffice: false,
enableOnlyOfficeEditing: false,
//viewMode: "normal",
};
},
computed: {
onlyOfficeAvailable() {
return onlyOfficeUrl !== "";
},
availableThemes() {
return userSelectableThemes || {};
},
@@ -390,6 +415,9 @@ export default {
this.disableNavButtons = this.link.hideNavButtons || false;
this.disableShareCard = this.link.disableShareCard || false;
this.disableSidebar = this.link.disableSidebar || false;
this.enforceDarkLightMode = this.link.enforceDarkLightMode || "default";
this.enableOnlyOffice = this.link.enableOnlyOffice || false;
this.enableOnlyOfficeEditing = this.link.enableOnlyOfficeEditing || false;
//this.viewMode = this.link.viewMode || "normal";
}
},
@@ -507,6 +535,9 @@ export default {
hideNavButtons: this.disableNavButtons,
disableShareCard: this.disableShareCard,
disableSidebar: this.disableSidebar,
enforceDarkLightMode: this.enforceDarkLightMode,
enableOnlyOffice: this.enableOnlyOffice,
enableOnlyOfficeEditing: this.enableOnlyOfficeEditing,
};
if (this.isEditMode) {
payload.hash = this.link.hash;
+3 -1
View File
@@ -53,6 +53,7 @@
<i class="material-icons">ads_click</i>
</div>
<div
v-if="darkModeTogglePossible"
class="clickable"
:class="{ active: user?.darkMode }"
@click="toggleDarkMode"
@@ -140,7 +141,7 @@
<script>
import * as auth from "@/utils/auth";
import { signup, disableExternal, noAuth, loginPage } from "@/utils/constants";
import { signup, disableExternal, noAuth, loginPage, shareInfo } from "@/utils/constants";
import ProgressBar from "@/components/ProgressBar.vue";
import { state, getters, mutations } from "@/store"; // Import your custom store
import { getHumanReadableFilesize } from "@/utils/filesizes.js";
@@ -178,6 +179,7 @@ export default {
sourceInfo: () => state.sources.info,
activeSource: () => state.sources.current,
realtimeActive: () => state.realtimeActive,
darkModeTogglePossible: () => shareInfo.enforceDarkLightMode != "dark" && shareInfo.enforceDarkLightMode != "light",
},
watch: {
route() {
+4
View File
@@ -408,4 +408,8 @@ button:hover,
.divider {
border-color: var(--divider);
width: 90%;
}
.tooltip-close-button {
max-width: 4em !important;
}
+13 -2
View File
@@ -365,7 +365,16 @@
"disableShareCard": "تعطيل بطاقة المشاركة المعطلة",
"disableShareCardDescription": "قم بتعطيل بطاقة المشاركة على الصفحة المشتركة في الشريط الجانبي أو في أعلى الصفحة على الهاتف المحمول.",
"disableSidebar": "تعطيل الشريط الجانبي",
"disableSidebarDescription": "تعطيل الشريط الجانبي في الصفحة المشتركة."
"disableSidebarDescription": "تعطيل الشريط الجانبي في الصفحة المشتركة.",
"enforceDarkLightMode": "فرض وضع السمة",
"enforceDarkLightModeDescription": "فرض وضع سمة معينة (داكنة أو فاتحة) لهذه المشاركة، وتجاوز تفضيلات المستخدم.",
"default": "لا تفرض وضعاً",
"dark": "داكن",
"light": "خفيف",
"enableOnlyOffice": "تمكين عارض OnlyOffice فقط",
"enableOnlyOfficeDescription": "السماح بعرض ملفات المكتب باستخدام OnlyOffice في هذه المشاركة.",
"enableOnlyOfficeEditing": "تمكين التحرير في المكتب فقط",
"enableOnlyOfficeEditingDescription": "السماح بتحرير الملفات المكتبية باستخدام OnlyOffice في هذه الحصة."
},
"api": {
"title": "مفاتيح واجهة برمجة التطبيقات (API):",
@@ -510,7 +519,9 @@
"showSelectMultiple": "إظهار تحديد متعدد في قائمة السياق على سطح المكتب",
"showSelectMultipleDescription": "بشكل افتراضي، لا تحتوي قائمة سياق سطح المكتب على خيار \"تحديد متعدد\" مثل الهاتف المحمول. يظهر هذا الخيار دائمًا في قائمة السياق.",
"defaultMediaPlayer": "استخدم مشغل الوسائط الأصلي في متصفحك",
"defaultMediaPlayerDescription": "استخدم مشغل الوسائط الأصلي في متصفحك لملفات الفيديو والصوت بدلاً من مشغل الوسائط \"vue-plyr\" المضمن في متصفح FileBrowser."
"defaultMediaPlayerDescription": "استخدم مشغل الوسائط الأصلي في متصفحك لملفات الفيديو والصوت بدلاً من مشغل الوسائط \"vue-plyr\" المضمن في متصفح FileBrowser.",
"debugOfficeEditor": "تمكين وضع التصحيح فقط",
"debugOfficeEditorDescription": "إظهار نافذة منبثقة للتصحيح مع معلومات إضافية في محرر OnlyOffice."
},
"colors": {
"red": "أحمر",
+13 -2
View File
@@ -167,7 +167,16 @@
"disableShareCard": "Zakázat sdílenou kartu",
"disableShareCardDescription": "Zakázat kartu sdílení na sdílené stránce v postranním panelu nebo v horní části stránky na mobilním telefonu.",
"disableSidebar": "Zakázat postranní panel",
"disableSidebarDescription": "Zakázat postranní panel na sdílené stránce."
"disableSidebarDescription": "Zakázat postranní panel na sdílené stránce.",
"enforceDarkLightMode": "Vynucení režimu tématu",
"enforceDarkLightModeDescription": "Vynucení určitého režimu motivu (tmavého nebo světlého) pro tuto sdílenou složku, který je nadřazen uživatelským preferencím.",
"default": "Nevynucujte režim",
"dark": "Dark",
"light": "Světlo",
"enableOnlyOffice": "Povolení prohlížeče OnlyOffice",
"enableOnlyOfficeDescription": "Povolit prohlížení kancelářských souborů pomocí OnlyOffice v této sdílené složce.",
"enableOnlyOfficeEditing": "Povolení úprav OnlyOffice",
"enableOnlyOfficeEditingDescription": "Povolit úpravy kancelářských souborů pomocí OnlyOffice v této sdílené složce."
},
"prompts": {
"copy": "Kopírovat",
@@ -293,7 +302,9 @@
"showSelectMultiple": "Zobrazení výběru více položek v kontextové nabídce na ploše",
"showSelectMultipleDescription": "Ve výchozím nastavení nemá kontextová nabídka na ploše možnost \"vybrat více\" jako v mobilním telefonu. Tato možnost se v kontextové nabídce zobrazuje vždy.",
"defaultMediaPlayer": "Použití nativního přehrávače médií v prohlížeči",
"defaultMediaPlayerDescription": "Pro video a zvukové soubory používejte nativní přehrávač médií prohlížeče, nikoli přehrávač médií vue-plyr, který je součástí aplikace FileBrowser."
"defaultMediaPlayerDescription": "Pro video a zvukové soubory používejte nativní přehrávač médií prohlížeče, nikoli přehrávač médií vue-plyr, který je součástí aplikace FileBrowser.",
"debugOfficeEditor": "Povolení režimu ladění OnlyOffice",
"debugOfficeEditorDescription": "Zobrazení vyskakovacího okna pro ladění s dalšími informacemi v editoru OnlyOffice."
},
"settings": {
"enterPassword": "Zadejte heslo",
+13 -2
View File
@@ -365,7 +365,16 @@
"disableShareCard": "Freigabekarte deaktivieren",
"disableShareCardDescription": "Deaktivieren Sie die Freigabekarte auf der freigegebenen Seite in der Seitenleiste oder oben auf der Seite auf dem Handy.",
"disableSidebar": "Seitenleiste deaktivieren",
"disableSidebarDescription": "Deaktivieren Sie die Seitenleiste auf der gemeinsamen Seite."
"disableSidebarDescription": "Deaktivieren Sie die Seitenleiste auf der gemeinsamen Seite.",
"enforceDarkLightMode": "Themenmodus erzwingen",
"enforceDarkLightModeDescription": "Erzwingt einen bestimmten Themenmodus (dunkel oder hell) für diese Freigabe und setzt die Benutzereinstellungen außer Kraft.",
"default": "Keinen Modus erzwingen",
"dark": "Dunkelheit",
"light": "Licht",
"enableOnlyOffice": "OnlyOffice viewer aktivieren",
"enableOnlyOfficeDescription": "Erlauben Sie die Anzeige von Office-Dateien mit OnlyOffice in dieser Freigabe.",
"enableOnlyOfficeEditing": "OnlyOffice-Bearbeitung aktivieren",
"enableOnlyOfficeEditingDescription": "Erlauben Sie die Bearbeitung von Office-Dateien mit OnlyOffice in dieser Freigabe."
},
"api": {
"title": "API-Schlüssel:",
@@ -518,7 +527,9 @@
"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."
"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."
},
"editor": {
"uninitialized": "Der Editor konnte nicht initialisiert werden. Bitte neu laden.",
+13 -2
View File
@@ -365,7 +365,16 @@
"disableShareCard": "Απενεργοποίηση κάρτας κοινής χρήσης",
"disableShareCardDescription": "Απενεργοποιήστε την κάρτα κοινής χρήσης στην κοινόχρηστη σελίδα στην πλαϊνή μπάρα ή στην κορυφή της σελίδας στο κινητό.",
"disableSidebar": "Απενεργοποίηση πλευρικής μπάρας",
"disableSidebarDescription": "Απενεργοποιήστε την πλαϊνή μπάρα στην κοινή σελίδα."
"disableSidebarDescription": "Απενεργοποιήστε την πλαϊνή μπάρα στην κοινή σελίδα.",
"enforceDarkLightMode": "Επιβολή λειτουργίας θέματος",
"enforceDarkLightModeDescription": "Επιβάλλει μια συγκεκριμένη λειτουργία θέματος (σκούρο ή ανοιχτό) για αυτό το κοινόχρηστο, παρακάμπτοντας τις προτιμήσεις του χρήστη.",
"default": "Μην επιβάλλετε μια λειτουργία",
"dark": "Σκούρο",
"light": "Φως",
"enableOnlyOffice": "Ενεργοποίηση της προβολής OnlyOffice",
"enableOnlyOfficeDescription": "Επιτρέψτε την προβολή αρχείων γραφείου χρησιμοποιώντας το OnlyOffice σε αυτό το κοινόχρηστο χώρο.",
"enableOnlyOfficeEditing": "Ενεργοποίηση της επεξεργασίας OnlyOffice",
"enableOnlyOfficeEditingDescription": "Επιτρέψτε την επεξεργασία αρχείων γραφείου χρησιμοποιώντας το OnlyOffice σε αυτό το κοινόχρηστο χώρο."
},
"api": {
"title": "Κλειδιά API:",
@@ -518,7 +527,9 @@
"showSelectMultiple": "Εμφάνιση πολλαπλών επιλογών στο μενού περιβάλλοντος στην επιφάνεια εργασίας",
"showSelectMultipleDescription": "Από προεπιλογή, το μενού περιβάλλοντος της επιφάνειας εργασίας δεν διαθέτει την επιλογή \"επιλογή πολλαπλών\" όπως το κινητό. Αυτή η επιλογή εμφανίζεται πάντα στο μενού περιβάλλοντος.",
"defaultMediaPlayer": "Χρησιμοποιήστε το εγγενές πρόγραμμα αναπαραγωγής πολυμέσων του προγράμματος περιήγησής σας",
"defaultMediaPlayerDescription": "Χρησιμοποιήστε το εγγενές πρόγραμμα αναπαραγωγής πολυμέσων του προγράμματος περιήγησης για αρχεία βίντεο και ήχου αντί για το πρόγραμμα αναπαραγωγής πολυμέσων \"vue-plyr\" που περιλαμβάνεται στο FileBrowser."
"defaultMediaPlayerDescription": "Χρησιμοποιήστε το εγγενές πρόγραμμα αναπαραγωγής πολυμέσων του προγράμματος περιήγησης για αρχεία βίντεο και ήχου αντί για το πρόγραμμα αναπαραγωγής πολυμέσων \"vue-plyr\" που περιλαμβάνεται στο FileBrowser.",
"debugOfficeEditor": "Ενεργοποίηση λειτουργίας εντοπισμού σφαλμάτων OnlyOffice",
"debugOfficeEditorDescription": "Εμφάνιση ενός αναδυόμενου παραθύρου εντοπισμού σφαλμάτων με πρόσθετες πληροφορίες στον επεξεργαστή OnlyOffice."
},
"editor": {
"uninitialized": "Απέτυχε η αρχικοποίηση του συντάκτη. Παρακαλούμε επαναφορτώστε.",
+12 -1
View File
@@ -167,7 +167,16 @@
"disableShareCard": "Disable share card",
"disableShareCardDescription": "Disable the share card on the shared page in the sidebar or at the top of the page on mobile.",
"disableSidebar": "Disable sidebar",
"disableSidebarDescription": "Disable the sidebar on the shared page."
"disableSidebarDescription": "Disable the sidebar on the shared page.",
"enforceDarkLightMode": "Enforce theme mode",
"enforceDarkLightModeDescription": "Force a specific theme mode (dark or light) for this share, overriding user preferences.",
"default": "Do not enforce a mode",
"dark": "Dark",
"light": "Light",
"enableOnlyOffice": "Enable OnlyOffice viewer",
"enableOnlyOfficeDescription": "Allow viewing office files using OnlyOffice in this share.",
"enableOnlyOfficeEditing": "Enable OnlyOffice editing",
"enableOnlyOfficeEditingDescription": "Allow editing of office files using OnlyOffice in this share."
},
"prompts": {
"downloadsLimit": "Number of downloads limit",
@@ -243,6 +252,8 @@
"helpText2": "The index: Search utilizes the index which automatically gets updated on the configured interval (default: 5 minutes). Searching when the program has just started may result in incomplete results."
},
"profileSettings": {
"debugOfficeEditor": "Enable OnlyOffice Debug Mode",
"debugOfficeEditorDescription": "Show a debug popup with additional information in the OnlyOffice editor.",
"defaultMediaPlayer": "Use your browser's native media player",
"defaultMediaPlayerDescription": "Use your browser's native media player for video and audio files rather than FileBrowser's included 'vue-plyr' media player.",
"showSelectMultiple": "Show select multiple in context menu on desktop",
+13 -2
View File
@@ -365,7 +365,16 @@
"disableShareCard": "Desactivar tarjeta compartida",
"disableShareCardDescription": "Desactivar la tarjeta de compartir en la página compartida en el panel lateral o en la parte superior de la página en móvil.",
"disableSidebar": "Desactivar el panel lateral",
"disableSidebarDescription": "Desactivar el panel lateral en la página compartida."
"disableSidebarDescription": "Desactivar el panel lateral en la página compartida.",
"enforceDarkLightMode": "Aplicar el modo temático",
"enforceDarkLightModeDescription": "Forzar un modo de tema específico (oscuro o claro) para esta acción, anulando las preferencias del usuario.",
"default": "No imponer un modo",
"dark": "Oscuro",
"light": "Luz",
"enableOnlyOffice": "Activar el visor de OnlyOffice",
"enableOnlyOfficeDescription": "Permitir la visualización de archivos de oficina utilizando OnlyOffice en este recurso compartido.",
"enableOnlyOfficeEditing": "Habilitar la edición de OnlyOffice",
"enableOnlyOfficeEditingDescription": "Permitir la edición de archivos de oficina utilizando OnlyOffice en este recurso compartido."
},
"api": {
"title": "Claves API",
@@ -518,7 +527,9 @@
"deleteWithoutConfirming": "Eliminar archivos sin mensaje de confirmación",
"deleteWithoutConfirmingDescription": "Eliminar archivos sin mensaje de confirmación. Esto sólo funcionará si el archivo es un archivo multimedia.",
"defaultMediaPlayer": "Usar el reproductor multimedia nativo del navegador",
"defaultMediaPlayerDescription": "Usar el reproductor multimedia nativo de tu navegador para los archivos de vídeo y audio en lugar del reproductor multimedia incluido en FileBrowser."
"defaultMediaPlayerDescription": "Usar el reproductor multimedia nativo de tu navegador para los archivos de vídeo y audio en lugar del reproductor multimedia incluido en FileBrowser.",
"debugOfficeEditor": "Activar el modo de depuración de OnlyOffice",
"debugOfficeEditorDescription": "Mostrar una ventana emergente de depuración con información adicional en el editor OnlyOffice."
},
"editor": {
"uninitialized": "Error al iniciar el editor, inténtelo de nuevo",
+13 -2
View File
@@ -365,7 +365,16 @@
"disableShareCard": "Désactiver la carte de partage",
"disableShareCardDescription": "Désactiver la carte de partage sur la page partagée dans la barre latérale ou en haut de la page sur mobile.",
"disableSidebar": "Désactiver la barre latérale",
"disableSidebarDescription": "Désactiver la barre latérale sur la page partagée."
"disableSidebarDescription": "Désactiver la barre latérale sur la page partagée.",
"enforceDarkLightMode": "Renforcer le mode thématique",
"enforceDarkLightModeDescription": "Forcer un mode de thème spécifique (sombre ou clair) pour ce partage, sans tenir compte des préférences de l'utilisateur.",
"default": "Ne pas imposer de mode",
"dark": "Sombre",
"light": "Lumière",
"enableOnlyOffice": "Activer la visionneuse OnlyOffice",
"enableOnlyOfficeDescription": "Autoriser la visualisation de fichiers bureautiques en utilisant OnlyOffice dans ce partage.",
"enableOnlyOfficeEditing": "Activer l'édition OnlyOffice",
"enableOnlyOfficeEditingDescription": "Autoriser l'édition de fichiers bureautiques en utilisant OnlyOffice dans ce partage."
},
"api": {
"title": "Clés API :",
@@ -518,7 +527,9 @@
"showSelectMultiple": "Afficher la sélection multiple dans le menu contextuel sur le bureau",
"showSelectMultipleDescription": "Par défaut, le menu contextuel de l'ordinateur de bureau n'a pas l'option \"sélectionner plusieurs\" comme celui de l'ordinateur mobile. Cette option apparaît toujours dans le menu contextuel.",
"defaultMediaPlayer": "Utilisez le lecteur multimédia natif de votre navigateur",
"defaultMediaPlayerDescription": "Utilisez le lecteur multimédia natif de votre navigateur pour les fichiers vidéo et audio plutôt que le lecteur multimédia \"vue-plyr\" inclus dans FileBrowser."
"defaultMediaPlayerDescription": "Utilisez le lecteur multimédia natif de votre navigateur pour les fichiers vidéo et audio plutôt que le lecteur multimédia \"vue-plyr\" inclus dans FileBrowser.",
"debugOfficeEditor": "Activer le mode de débogage d'OnlyOffice",
"debugOfficeEditorDescription": "Afficher une fenêtre de débogage avec des informations supplémentaires dans l'éditeur OnlyOffice."
},
"editor": {
"uninitialized": "Échec de l'initialisation de l'éditeur. Veuillez recharger.",
+13 -2
View File
@@ -365,7 +365,16 @@
"disableShareCard": "השבת כרטיס שיתוף",
"disableShareCardDescription": "השבת את כרטיס השיתוף בדף המשותף בסרגל הצד או בחלק העליון של הדף במכשירים ניידים.",
"disableSidebar": "השבת סרגל צד",
"disableSidebarDescription": "השבת את סרגל הצד בדף המשותף."
"disableSidebarDescription": "השבת את סרגל הצד בדף המשותף.",
"enforceDarkLightMode": "אכוף מצב ערכת נושא",
"enforceDarkLightModeDescription": "כפה מצב ערכת נושא ספציפי (כהה או בהיר) עבור שיתוף זה, תוך עקיפת העדפות המשתמש.",
"default": "אל תכפה מצב",
"dark": "אפל",
"light": "אור",
"enableOnlyOffice": "הפעל את תוכנת הצפייה OnlyOffice",
"enableOnlyOfficeDescription": "אפשר צפייה בקבצי Office באמצעות OnlyOffice בשיתוף זה.",
"enableOnlyOfficeEditing": "אפשר עריכה ב-OnlyOffice",
"enableOnlyOfficeEditingDescription": "אפשר עריכת קבצי Office באמצעות OnlyOffice בשיתוף זה."
},
"api": {
"title": "מפתחות API",
@@ -518,7 +527,9 @@
"showSelectMultiple": "הצג בחירה מרובה בתפריט ההקשר בשולחן העבודה",
"showSelectMultipleDescription": "כברירת מחדל, בתפריט ההקשר של שולחן העבודה אין אפשרות \"בחר מספר פריטים\" כמו במכשירים ניידים. אפשרות זו תמיד מוצגת בתפריט ההקשר.",
"defaultMediaPlayer": "השתמש בנגן המדיה המובנה בדפדפן שלך",
"defaultMediaPlayerDescription": "השתמש בנגן המדיה המובנה בדפדפן שלך עבור קבצי וידאו ואודיו, במקום בנגן המדיה 'vue-plyr' הכלול ב-FileBrowser."
"defaultMediaPlayerDescription": "השתמש בנגן המדיה המובנה בדפדפן שלך עבור קבצי וידאו ואודיו, במקום בנגן המדיה 'vue-plyr' הכלול ב-FileBrowser.",
"debugOfficeEditor": "הפעל מצב ניפוי באגים של OnlyOffice",
"debugOfficeEditorDescription": "הצג חלון קופץ לאיתור באגים עם מידע נוסף בעורך OnlyOffice."
},
"editor": {
"uninitialized": "לא ניתן לאתחל את העורך. אנא טען מחדש.",
+13 -2
View File
@@ -365,7 +365,16 @@
"disableShareCard": "Megosztási kártya letiltása",
"disableShareCardDescription": "Kapcsolja ki a megosztási kártyát a megosztott oldalon az oldalsávban vagy mobilon az oldal tetején.",
"disableSidebar": "Oldalsáv kikapcsolása",
"disableSidebarDescription": "Kapcsolja ki az oldalsávot a megosztott oldalon."
"disableSidebarDescription": "Kapcsolja ki az oldalsávot a megosztott oldalon.",
"enforceDarkLightMode": "Témamód érvényesítése",
"enforceDarkLightModeDescription": "Egy adott témamód (sötét vagy világos) kikényszerítése a megosztás számára, felülírva a felhasználói beállításokat.",
"default": "Ne kényszerítsen ki üzemmódot",
"dark": "Sötét",
"light": "Fény",
"enableOnlyOffice": "OnlyOffice megjelenítő engedélyezése",
"enableOnlyOfficeDescription": "Engedélyezze az irodai fájlok megtekintését az OnlyOffice használatával ezen a megosztáson.",
"enableOnlyOfficeEditing": "Engedélyezze az OnlyOffice szerkesztést",
"enableOnlyOfficeEditingDescription": "Engedélyezze az irodai fájlok szerkesztését az OnlyOffice használatával ezen a megosztáson."
},
"api": {
"title": "API-kulcsok:",
@@ -518,7 +527,9 @@
"showSelectMultiple": "Többszörös kijelölés megjelenítése az asztali kontextusmenüben",
"showSelectMultipleDescription": "Alapértelmezés szerint az asztali kontextusmenü nem rendelkezik a \"többszörös kiválasztás\" opcióval, mint a mobilon. Ez az opció mindig megjelenik a kontextusmenüben.",
"defaultMediaPlayer": "Használja a böngésző natív médialejátszóját",
"defaultMediaPlayerDescription": "Használja a böngésző natív médialejátszóját a videó- és hangfájlokhoz, nem pedig a FileBrowser mellékelt \"vue-plyr\" médialejátszóját."
"defaultMediaPlayerDescription": "Használja a böngésző natív médialejátszóját a videó- és hangfájlokhoz, nem pedig a FileBrowser mellékelt \"vue-plyr\" médialejátszóját.",
"debugOfficeEditor": "OnlyOffice hibakeresési mód engedélyezése",
"debugOfficeEditorDescription": "További információkat tartalmazó hibakeresési felugró ablak megjelenítése az OnlyOffice szerkesztőben."
},
"editor": {
"uninitialized": "Nem sikerült inicializálni a szerkesztőt. Kérjük, töltse újra.",
+13 -2
View File
@@ -365,7 +365,16 @@
"disableShareCard": "Disattivare la scheda di condivisione",
"disableShareCardDescription": "Disattivare la scheda di condivisione sulla pagina condivisa nella barra laterale o nella parte superiore della pagina su mobile.",
"disableSidebar": "Disattivare la barra laterale",
"disableSidebarDescription": "Disattivare la barra laterale della pagina condivisa."
"disableSidebarDescription": "Disattivare la barra laterale della pagina condivisa.",
"enforceDarkLightMode": "Applicare la modalità a tema",
"enforceDarkLightModeDescription": "Forza una modalità tema specifica (scuro o chiaro) per questa condivisione, ignorando le preferenze dell'utente.",
"default": "Non applicare una modalità",
"dark": "Scuro",
"light": "Luce",
"enableOnlyOffice": "Abilitate il visualizzatore OnlyOffice",
"enableOnlyOfficeDescription": "Consente di visualizzare i file di Office utilizzando OnlyOffice in questa condivisione.",
"enableOnlyOfficeEditing": "Abilita la modifica di OnlyOffice",
"enableOnlyOfficeEditingDescription": "Consentire la modifica dei file di Office utilizzando OnlyOffice in questa condivisione."
},
"api": {
"title": "Chiavi API:",
@@ -518,7 +527,9 @@
"showSelectMultiple": "Mostra selezione multipla nel menu contestuale sul desktop",
"showSelectMultipleDescription": "Per impostazione predefinita, il menu contestuale del desktop non dispone dell'opzione \"seleziona più\" come quello del cellulare. Questa opzione viene sempre visualizzata nel menu contestuale.",
"defaultMediaPlayer": "Utilizzare il lettore multimediale nativo del browser",
"defaultMediaPlayerDescription": "Utilizzate il lettore multimediale nativo del vostro browser per i file video e audio piuttosto che il lettore multimediale \"vue-plyr\" incluso in FileBrowser."
"defaultMediaPlayerDescription": "Utilizzate il lettore multimediale nativo del vostro browser per i file video e audio piuttosto che il lettore multimediale \"vue-plyr\" incluso in FileBrowser.",
"debugOfficeEditor": "Abilita la modalità di debug di OnlyOffice",
"debugOfficeEditorDescription": "Mostra un popup di debug con informazioni aggiuntive nell'editor di OnlyOffice."
},
"editor": {
"uninitialized": "Impossibile inizializzare l'editor. Ricaricare.",
+13 -2
View File
@@ -365,7 +365,16 @@
"disableShareCard": "シェアカードを無効にする",
"disableShareCardDescription": "サイドバーの共有ページ、またはモバイルのページ上部にある共有カードを無効にする。",
"disableSidebar": "サイドバーを無効にする",
"disableSidebarDescription": "共有ページのサイドバーを無効にする。"
"disableSidebarDescription": "共有ページのサイドバーを無効にする。",
"enforceDarkLightMode": "テーマモードの強制",
"enforceDarkLightModeDescription": "この共有に特定のテーマモード(ダークまたはライト)を強制します。",
"default": "モードを強制しない",
"dark": "暗い",
"light": "ライト",
"enableOnlyOffice": "OnlyOfficeビューアを有効にする",
"enableOnlyOfficeDescription": "この共有でOnlyOfficeを使用したオフィスファイルの閲覧を許可する。",
"enableOnlyOfficeEditing": "OnlyOffice編集を有効にする",
"enableOnlyOfficeEditingDescription": "この共有でOnlyOfficeを使ったオフィスファイルの編集を許可する。"
},
"api": {
"title": "APIキー:",
@@ -518,7 +527,9 @@
"showSelectMultiple": "デスクトップのコンテキストメニューに複数選択を表示する",
"showSelectMultipleDescription": "デフォルトでは、デスクトップのコンテキストメニューには、モバイルのような「複数選択」オプションはありません。このオプションは常にコンテキストメニューに表示されます。",
"defaultMediaPlayer": "ブラウザのネイティブ・メディア・プレーヤーを使う",
"defaultMediaPlayerDescription": "ビデオやオーディオファイルには、FileBrowserに含まれる「vue-plyr」メディアプレーヤーではなく、ブラウザのネイティブメディアプレーヤーを使用してください。"
"defaultMediaPlayerDescription": "ビデオやオーディオファイルには、FileBrowserに含まれる「vue-plyr」メディアプレーヤーではなく、ブラウザのネイティブメディアプレーヤーを使用してください。",
"debugOfficeEditor": "OnlyOfficeデバッグモードを有効にする",
"debugOfficeEditorDescription": "OnlyOfficeエディタに追加情報のデバッグポップアップを表示します。"
},
"editor": {
"uninitialized": "エディタの初期化に失敗しました。再読み込みしてください。",
+13 -2
View File
@@ -365,7 +365,16 @@
"disableShareCard": "공유 카드 비활성화",
"disableShareCardDescription": "사이드바의 공유 페이지 또는 모바일의 페이지 상단에서 공유 카드를 비활성화합니다.",
"disableSidebar": "사이드바 비활성화",
"disableSidebarDescription": "공유 페이지에서 사이드바를 비활성화합니다."
"disableSidebarDescription": "공유 페이지에서 사이드바를 비활성화합니다.",
"enforceDarkLightMode": "테마 모드 적용",
"enforceDarkLightModeDescription": "이 공유에 특정 테마 모드(어둡거나 밝음)를 강제로 적용하여 사용자 기본 설정을 재정의합니다.",
"default": "모드 강제 적용 안 함",
"dark": "Dark",
"light": "빛",
"enableOnlyOffice": "Office 뷰어만 사용",
"enableOnlyOfficeDescription": "이 공유에서 OnlyOffice를 사용하여 Office 파일을 볼 수 있도록 허용합니다.",
"enableOnlyOfficeEditing": "Office 편집만 사용",
"enableOnlyOfficeEditingDescription": "이 공유에서 OnlyOffice를 사용하여 Office 파일을 편집할 수 있도록 허용합니다."
},
"api": {
"title": "API 키:",
@@ -518,7 +527,9 @@
"showSelectMultiple": "데스크톱의 컨텍스트 메뉴에서 여러 개 선택 표시",
"showSelectMultipleDescription": "기본적으로 데스크톱 컨텍스트 메뉴에는 모바일처럼 '여러 개 선택' 옵션이 없습니다. 이 옵션은 항상 컨텍스트 메뉴에 표시됩니다.",
"defaultMediaPlayer": "브라우저의 기본 미디어 플레이어 사용",
"defaultMediaPlayerDescription": "비디오 및 오디오 파일은 FileBrowser에 포함된 'vue-plyr' 미디어 플레이어가 아닌 브라우저의 기본 미디어 플레이어를 사용하세요."
"defaultMediaPlayerDescription": "비디오 및 오디오 파일은 FileBrowser에 포함된 'vue-plyr' 미디어 플레이어가 아닌 브라우저의 기본 미디어 플레이어를 사용하세요.",
"debugOfficeEditor": "Office 디버그 모드만 사용",
"debugOfficeEditorDescription": "OnlyOffice 편집기에서 추가 정보가 포함된 디버그 팝업을 표시합니다."
},
"editor": {
"uninitialized": "편집기를 초기화하지 못했습니다. 다시 로드하세요.",
+13 -2
View File
@@ -365,7 +365,16 @@
"disableShareCard": "Aandelenkaart uitschakelen",
"disableShareCardDescription": "Schakel de deelkaart uit op de gedeelde pagina in de zijbalk of bovenaan de pagina op mobiel.",
"disableSidebar": "Zijbalk uitschakelen",
"disableSidebarDescription": "Schakel de zijbalk op de gedeelde pagina uit."
"disableSidebarDescription": "Schakel de zijbalk op de gedeelde pagina uit.",
"enforceDarkLightMode": "Themamodus afdwingen",
"enforceDarkLightModeDescription": "Forceer een specifieke themamodus (donker of licht) voor deze share, waarbij gebruikersvoorkeuren worden overschreven.",
"default": "Een modus niet afdwingen",
"dark": "Donker",
"light": "Licht",
"enableOnlyOffice": "OnlyOffice viewer inschakelen",
"enableOnlyOfficeDescription": "Sta het bekijken van Office-bestanden toe met OnlyOffice in deze share.",
"enableOnlyOfficeEditing": "Bewerken met OnlyOffice inschakelen",
"enableOnlyOfficeEditingDescription": "Laat het bewerken van Office-bestanden met OnlyOffice toe in deze share."
},
"api": {
"title": "API-sleutels",
@@ -482,7 +491,9 @@
"showSelectMultiple": "Meerdere selecteren tonen in contextmenu op bureaublad",
"showSelectMultipleDescription": "Standaard heeft het contextmenu op de desktop geen optie \"meerdere selecteren\" zoals mobiel. Deze optie wordt altijd weergegeven in het contextmenu.",
"defaultMediaPlayer": "Gebruik de ingebouwde mediaspeler van uw browser",
"defaultMediaPlayerDescription": "Gebruik de eigen mediaspeler van uw browser voor video- en audiobestanden in plaats van de meegeleverde 'vue-plyr' mediaspeler van FileBrowser."
"defaultMediaPlayerDescription": "Gebruik de eigen mediaspeler van uw browser voor video- en audiobestanden in plaats van de meegeleverde 'vue-plyr' mediaspeler van FileBrowser.",
"debugOfficeEditor": "Debugmodus OnlyOffice inschakelen",
"debugOfficeEditorDescription": "Een foutopsporingspop-up weergeven met aanvullende informatie in de OnlyOffice-editor."
},
"general": {
"name": "Naam",
+13 -2
View File
@@ -365,7 +365,16 @@
"disableShareCard": "Wyłączanie karty udostępniania",
"disableShareCardDescription": "Wyłącz kartę udostępniania na udostępnianej stronie na pasku bocznym lub u góry strony na urządzeniach mobilnych.",
"disableSidebar": "Wyłącz pasek boczny",
"disableSidebarDescription": "Wyłącz pasek boczny na udostępnionej stronie."
"disableSidebarDescription": "Wyłącz pasek boczny na udostępnionej stronie.",
"enforceDarkLightMode": "Wymuszanie trybu motywu",
"enforceDarkLightModeDescription": "Wymusza określony tryb motywu (ciemny lub jasny) dla tego udziału, zastępując preferencje użytkownika.",
"default": "Nie wymuszaj trybu",
"dark": "Ciemny",
"light": "Światło",
"enableOnlyOffice": "Włącz przeglądarkę OnlyOffice",
"enableOnlyOfficeDescription": "Zezwalaj na wyświetlanie plików biurowych przy użyciu OnlyOffice w tym udziale.",
"enableOnlyOfficeEditing": "Włącz edycję OnlyOffice",
"enableOnlyOfficeEditingDescription": "Zezwalaj na edycję plików biurowych za pomocą OnlyOffice w tym udziale."
},
"api": {
"title": "Klucze API:",
@@ -518,7 +527,9 @@
"showSelectMultiple": "Wyświetlanie wyboru wielokrotnego w menu kontekstowym na pulpicie",
"showSelectMultipleDescription": "Domyślnie menu kontekstowe pulpitu nie ma opcji \"wybierz wiele\", tak jak w przypadku urządzeń mobilnych. Opcja ta jest zawsze widoczna w menu kontekstowym.",
"defaultMediaPlayer": "Użyj natywnego odtwarzacza multimediów w przeglądarce",
"defaultMediaPlayerDescription": "Do odtwarzania plików wideo i audio należy używać natywnego odtwarzacza multimediów przeglądarki, a nie dołączonego do FileBrowser odtwarzacza multimediów \"vue-plyr\"."
"defaultMediaPlayerDescription": "Do odtwarzania plików wideo i audio należy używać natywnego odtwarzacza multimediów przeglądarki, a nie dołączonego do FileBrowser odtwarzacza multimediów \"vue-plyr\".",
"debugOfficeEditor": "Włącz tryb debugowania OnlyOffice",
"debugOfficeEditorDescription": "Wyświetla wyskakujące okienko debugowania z dodatkowymi informacjami w edytorze OnlyOffice."
},
"editor": {
"uninitialized": "Nie udało się zainicjować edytora. Przeładuj ponownie.",
+13 -2
View File
@@ -365,7 +365,16 @@
"disableShareCard": "Desativar cartão compartilhado",
"disableShareCardDescription": "Desative o cartão de compartilhamento na página compartilhada na barra lateral ou na parte superior da página no celular.",
"disableSidebar": "Desativar a barra lateral",
"disableSidebarDescription": "Desative a barra lateral na página compartilhada."
"disableSidebarDescription": "Desative a barra lateral na página compartilhada.",
"enforceDarkLightMode": "Aplicar o modo de tema",
"enforceDarkLightModeDescription": "Força um modo de tema específico (escuro ou claro) para esse compartilhamento, substituindo as preferências do usuário.",
"default": "Não imponha um modo",
"dark": "Escuro",
"light": "Luz",
"enableOnlyOffice": "Ativar o visualizador do OnlyOffice",
"enableOnlyOfficeDescription": "Permitir a visualização de arquivos do Office usando o OnlyOffice nesse compartilhamento.",
"enableOnlyOfficeEditing": "Ativar a edição do OnlyOffice",
"enableOnlyOfficeEditingDescription": "Permitir a edição de arquivos do Office usando o OnlyOffice nesse compartilhamento."
},
"api": {
"title": "Chaves de API:",
@@ -518,7 +527,9 @@
"showSelectMultiple": "Mostrar seleção múltipla no menu de contexto na área de trabalho",
"showSelectMultipleDescription": "Por padrão, o menu de contexto do desktop não tem a opção \"selecionar vários\" como no celular. Essa opção sempre a mostra no menu de contexto.",
"defaultMediaPlayer": "Use o reprodutor de mídia nativo do seu navegador",
"defaultMediaPlayerDescription": "Use o reprodutor de mídia nativo do seu navegador para arquivos de vídeo e áudio em vez do reprodutor de mídia 'vue-plyr' incluído no FileBrowser."
"defaultMediaPlayerDescription": "Use o reprodutor de mídia nativo do seu navegador para arquivos de vídeo e áudio em vez do reprodutor de mídia 'vue-plyr' incluído no FileBrowser.",
"debugOfficeEditor": "Ativar o modo de depuração do OnlyOffice",
"debugOfficeEditorDescription": "Mostrar um pop-up de depuração com informações adicionais no editor do OnlyOffice."
},
"editor": {
"uninitialized": "Falha ao inicializar o editor. Favor recarregar.",
+13 -2
View File
@@ -365,7 +365,16 @@
"disableShareCard": "Desativar o cartão de partilha",
"disableShareCardDescription": "Desativar o cartão de partilha na página partilhada na barra lateral ou na parte superior da página no telemóvel.",
"disableSidebar": "Desativar a barra lateral",
"disableSidebarDescription": "Desativar a barra lateral na página partilhada."
"disableSidebarDescription": "Desativar a barra lateral na página partilhada.",
"enforceDarkLightMode": "Aplicar o modo de tema",
"enforceDarkLightModeDescription": "Força um modo de tema específico (escuro ou claro) para esta partilha, substituindo as preferências do utilizador.",
"default": "Não impor um modo",
"dark": "Escuro",
"light": "Luz",
"enableOnlyOffice": "Ativar o visualizador OnlyOffice",
"enableOnlyOfficeDescription": "Permitir a visualização de ficheiros de escritório utilizando o OnlyOffice nesta partilha.",
"enableOnlyOfficeEditing": "Ativar a edição OnlyOffice",
"enableOnlyOfficeEditingDescription": "Permitir a edição de ficheiros do Office utilizando o OnlyOffice nesta partilha."
},
"api": {
"title": "Chaves da API",
@@ -482,7 +491,9 @@
"showSelectMultiple": "Mostrar seleção múltipla no menu de contexto no ambiente de trabalho",
"showSelectMultipleDescription": "Por predefinição, o menu de contexto do ambiente de trabalho não tem a opção \"selecionar vários\" como no telemóvel. Esta opção mostra-a sempre no menu de contexto.",
"defaultMediaPlayer": "Utilize o leitor multimédia nativo do seu navegador",
"defaultMediaPlayerDescription": "Utilize o leitor multimédia nativo do seu navegador para ficheiros de vídeo e áudio em vez do leitor multimédia 'vue-plyr' incluído no FileBrowser."
"defaultMediaPlayerDescription": "Utilize o leitor multimédia nativo do seu navegador para ficheiros de vídeo e áudio em vez do leitor multimédia 'vue-plyr' incluído no FileBrowser.",
"debugOfficeEditor": "Ativar o modo de depuração do OnlyOffice",
"debugOfficeEditorDescription": "Mostrar um popup de depuração com informações adicionais no editor OnlyOffice."
},
"general": {
"name": "Nome",
+13 -2
View File
@@ -365,7 +365,16 @@
"disableShareCard": "Dezactivați cardul de partajare",
"disableShareCardDescription": "Dezactivați cardul de partajare pe pagina partajată în bara laterală sau în partea de sus a paginii pe mobil.",
"disableSidebar": "Dezactivați bara laterală",
"disableSidebarDescription": "Dezactivați bara laterală de pe pagina partajată."
"disableSidebarDescription": "Dezactivați bara laterală de pe pagina partajată.",
"enforceDarkLightMode": "Impuneți modul tematic",
"enforceDarkLightModeDescription": "Forțează un anumit mod tematic (întunecat sau deschis) pentru această partajare, trecând peste preferințele utilizatorului.",
"default": "Nu impuneți un mod",
"dark": "Întuneric",
"light": "Lumină",
"enableOnlyOffice": "Activați vizualizatorul OnlyOffice",
"enableOnlyOfficeDescription": "Permiteți vizualizarea fișierelor office utilizând OnlyOffice în această partajare.",
"enableOnlyOfficeEditing": "Activați editarea OnlyOffice",
"enableOnlyOfficeEditingDescription": "Permiteți editarea fișierelor de birou utilizând OnlyOffice în această partajare."
},
"api": {
"title": "Chei API:",
@@ -518,7 +527,9 @@
"showSelectMultiple": "Afișarea selecției multiple în meniul contextual de pe desktop",
"showSelectMultipleDescription": "În mod implicit, meniul contextual de pe desktop nu are opțiunea \"select multiple\", ca pe mobil. Această opțiune se afișează întotdeauna în meniul contextual.",
"defaultMediaPlayer": "Utilizați playerul media nativ al browserului dvs.",
"defaultMediaPlayerDescription": "Utilizați playerul media nativ al browserului dvs. pentru fișierele video și audio mai degrabă decât playerul media \"vue-plyr\" inclus în FileBrowser."
"defaultMediaPlayerDescription": "Utilizați playerul media nativ al browserului dvs. pentru fișierele video și audio mai degrabă decât playerul media \"vue-plyr\" inclus în FileBrowser.",
"debugOfficeEditor": "Activați modul de depanare OnlyOffice",
"debugOfficeEditorDescription": "Afișați o fereastră pop-up de depanare cu informații suplimentare în editorul OnlyOffice."
},
"editor": {
"uninitialized": "Nu s-a reușit inițializarea editorului. Vă rugăm să reîncărcați.",
+13 -2
View File
@@ -365,7 +365,16 @@
"disableShareCard": "Отключение карты доступа",
"disableShareCardDescription": "Отключите карточку общего доступа на странице общего доступа в боковой панели или в верхней части страницы на мобильных устройствах.",
"disableSidebar": "Отключить боковую панель",
"disableSidebarDescription": "Отключите боковую панель на общей странице."
"disableSidebarDescription": "Отключите боковую панель на общей странице.",
"enforceDarkLightMode": "Включение режима темы",
"enforceDarkLightModeDescription": "Принудительно устанавливает определенный режим темы (темный или светлый) для этого ресурса, отменяя предпочтения пользователя.",
"default": "Не вводите режим",
"dark": "Темнота",
"light": "Свет",
"enableOnlyOffice": "Включите программу просмотра OnlyOffice",
"enableOnlyOfficeDescription": "Разрешите просмотр офисных файлов с помощью OnlyOffice в этом ресурсе.",
"enableOnlyOfficeEditing": "Включить редактирование OnlyOffice",
"enableOnlyOfficeEditingDescription": "Разрешите редактирование офисных файлов с помощью OnlyOffice в этом ресурсе."
},
"api": {
"title": "Ключи API:",
@@ -518,7 +527,9 @@
"showSelectMultiple": "Отображение выбора нескольких в контекстном меню на рабочем столе",
"showSelectMultipleDescription": "По умолчанию в контекстном меню рабочего стола нет опции \"выбрать несколько\", как в мобильном. Эта опция всегда отображается в контекстном меню.",
"defaultMediaPlayer": "Используйте встроенный медиаплеер вашего браузера",
"defaultMediaPlayerDescription": "Для воспроизведения видео- и аудиофайлов используйте штатный медиаплеер браузера, а не медиаплеер 'vue-plyr', входящий в комплект поставки FileBrowser."
"defaultMediaPlayerDescription": "Для воспроизведения видео- и аудиофайлов используйте штатный медиаплеер браузера, а не медиаплеер 'vue-plyr', входящий в комплект поставки FileBrowser.",
"debugOfficeEditor": "Включить режим отладки OnlyOffice",
"debugOfficeEditorDescription": "Отображение всплывающего окна отладки с дополнительной информацией в редакторе OnlyOffice."
},
"editor": {
"uninitialized": "Не удалось инициализировать редактор. Пожалуйста, перезагрузитесь.",
+13 -2
View File
@@ -365,7 +365,16 @@
"disableShareCard": "Zakázanie zdieľanej karty",
"disableShareCardDescription": "Zakážte kartu zdieľania na zdieľanej stránke v bočnom paneli alebo v hornej časti stránky na mobilnom zariadení.",
"disableSidebar": "Zakázanie bočného panela",
"disableSidebarDescription": "Zakážte bočný panel na zdieľanej stránke."
"disableSidebarDescription": "Zakážte bočný panel na zdieľanej stránke.",
"enforceDarkLightMode": "Vynútiť režim témy",
"enforceDarkLightModeDescription": "Vynútenie konkrétneho režimu motívu (tmavý alebo svetlý) pre túto akciu, ktorý je nadradený používateľským nastaveniam.",
"default": "Nevynucujte režim",
"dark": "Dark",
"light": "Svetlo",
"enableOnlyOffice": "Povolenie prehliadača OnlyOffice",
"enableOnlyOfficeDescription": "Povoľte zobrazovanie kancelárskych súborov pomocou OnlyOffice v tejto zdieľanej zložke.",
"enableOnlyOfficeEditing": "Povolenie úprav OnlyOffice",
"enableOnlyOfficeEditingDescription": "Povoľte úpravu kancelárskych súborov pomocou aplikácie OnlyOffice v tejto zdieľanej zložke."
},
"api": {
"title": "Kľúče API:",
@@ -518,7 +527,9 @@
"showSelectMultiple": "Zobrazenie výberu viacerých v kontextovom menu na ploche",
"showSelectMultipleDescription": "V predvolenom nastavení kontextová ponuka na ploche nemá možnosť \"vybrať viacero\" ako v mobilných zariadeniach. Táto možnosť sa v kontextovej ponuke zobrazuje vždy.",
"defaultMediaPlayer": "Používanie natívneho prehrávača médií prehliadača",
"defaultMediaPlayerDescription": "Na prehrávanie video a zvukových súborov použite natívny prehrávač médií prehliadača, a nie mediálny prehrávač vue-plyr, ktorý je súčasťou aplikácie FileBrowser."
"defaultMediaPlayerDescription": "Na prehrávanie video a zvukových súborov použite natívny prehrávač médií prehliadača, a nie mediálny prehrávač vue-plyr, ktorý je súčasťou aplikácie FileBrowser.",
"debugOfficeEditor": "Povolenie režimu ladenia OnlyOffice",
"debugOfficeEditorDescription": "Zobrazenie vyskakovacieho okna ladenia s ďalšími informáciami v editore OnlyOffice."
},
"editor": {
"uninitialized": "Nepodarilo sa inicializovať editor. Prosím, načítajte ho znova.",
+13 -2
View File
@@ -365,7 +365,16 @@
"disableShareCard": "Inaktivera aktiekort",
"disableShareCardDescription": "Inaktivera delningskortet på den delade sidan i sidofältet eller högst upp på sidan på mobilen.",
"disableSidebar": "Inaktivera sidofält",
"disableSidebarDescription": "Inaktivera sidofältet på den delade sidan."
"disableSidebarDescription": "Inaktivera sidofältet på den delade sidan.",
"enforceDarkLightMode": "Genomför temaläge",
"enforceDarkLightModeDescription": "Tvinga fram ett specifikt temaläge (mörkt eller ljust) för den här aktien, utan hänsyn till användarens preferenser.",
"default": "Inte tvinga fram ett läge",
"dark": "Mörk",
"light": "Ljus",
"enableOnlyOffice": "Aktivera OnlyOffice-visningsprogram",
"enableOnlyOfficeDescription": "Tillåt visning av kontorsfiler med OnlyOffice i denna delning.",
"enableOnlyOfficeEditing": "Aktivera redigering av OnlyOffice",
"enableOnlyOfficeEditingDescription": "Tillåt redigering av kontorsfiler med hjälp av OnlyOffice i denna andel."
},
"api": {
"title": "API-nycklar",
@@ -482,7 +491,9 @@
"showSelectMultiple": "Visa markera flera i snabbmenyn på skrivbordet",
"showSelectMultipleDescription": "Som standard har snabbmenyn på skrivbordet inte alternativet \"välj flera\" som på mobilen. Det här alternativet visas alltid i snabbmenyn.",
"defaultMediaPlayer": "Använd den inbyggda mediaspelaren i din webbläsare",
"defaultMediaPlayerDescription": "Använd webbläsarens inbyggda mediaspelare för video- och ljudfiler i stället för FileBrowsers medföljande \"vue-plyr\"-mediaspelare."
"defaultMediaPlayerDescription": "Använd webbläsarens inbyggda mediaspelare för video- och ljudfiler i stället för FileBrowsers medföljande \"vue-plyr\"-mediaspelare.",
"debugOfficeEditor": "Aktivera OnlyOffice felsökningsläge",
"debugOfficeEditorDescription": "Visa en debug-popup med ytterligare information i OnlyOffice-redigeraren."
},
"general": {
"name": "Namn",
+13 -2
View File
@@ -365,7 +365,16 @@
"disableShareCard": "Paylaşım kartını devre dışı bırak",
"disableShareCardDescription": "Kenar çubuğunda paylaşılan sayfadaki veya mobil cihazlarda sayfanın üst kısmındaki paylaşım kartını devre dışı bırakın.",
"disableSidebar": "Kenar çubuğunu devre dışı bırak",
"disableSidebarDescription": "Paylaşılan sayfadaki kenar çubuğunu devre dışı bırakın."
"disableSidebarDescription": "Paylaşılan sayfadaki kenar çubuğunu devre dışı bırakın.",
"enforceDarkLightMode": "Tema modunu zorla",
"enforceDarkLightModeDescription": "Kullanıcı tercihlerini geçersiz kılarak bu paylaşım için belirli bir tema modunu (koyu veya açık) zorlayın.",
"default": "Bir modu zorlamayın",
"dark": "Karanlık",
"light": "Işık",
"enableOnlyOffice": "OnlyOffice görüntüleyiciyi etkinleştirin",
"enableOnlyOfficeDescription": "Bu paylaşımda OnlyOffice kullanarak ofis dosyalarının görüntülenmesine izin verin.",
"enableOnlyOfficeEditing": "OnlyOffice düzenlemesini etkinleştirin",
"enableOnlyOfficeEditingDescription": "Bu paylaşımda OnlyOffice kullanarak ofis dosyalarının düzenlenmesine izin verin."
},
"api": {
"title": "API Anahtarları:",
@@ -518,7 +527,9 @@
"showSelectMultiple": "Masaüstündeki bağlam menüsünde çoklu seçmeyi göster",
"showSelectMultipleDescription": "Varsayılan olarak, masaüstü içerik menüsünde mobil cihazlardaki gibi \"çoklu seç\" seçeneği yoktur. Bu seçenek her zaman içerik menüsünde gösterilir.",
"defaultMediaPlayer": "Tarayıcınızın yerel medya oynatıcısını kullanın",
"defaultMediaPlayerDescription": "Video ve ses dosyaları için FileBrowser'ın dahil ettiği 'vue-plyr' medya oynatıcısı yerine tarayıcınızın yerel medya oynatıcısını kullanın."
"defaultMediaPlayerDescription": "Video ve ses dosyaları için FileBrowser'ın dahil ettiği 'vue-plyr' medya oynatıcısı yerine tarayıcınızın yerel medya oynatıcısını kullanın.",
"debugOfficeEditor": "OnlyOffice Hata Ayıklama Modunu Etkinleştir",
"debugOfficeEditorDescription": "OnlyOffice düzenleyicisinde ek bilgiler içeren bir hata ayıklama açılır penceresi gösterin."
},
"editor": {
"uninitialized": "Düzenleyici başlatılamadı. Lütfen yeniden yükleyin.",
+13 -2
View File
@@ -365,7 +365,16 @@
"disableShareCard": "Вимкнути картку спільного доступу",
"disableShareCardDescription": "Вимкніть картку спільного доступу на сторінці, до якої надається спільний доступ, на бічній панелі або вгорі сторінки на мобільних пристроях.",
"disableSidebar": "Вимкнути бічну панель",
"disableSidebarDescription": "Вимкнути бічну панель на спільній сторінці."
"disableSidebarDescription": "Вимкнути бічну панель на спільній сторінці.",
"enforceDarkLightMode": "Увімкнути режим теми",
"enforceDarkLightModeDescription": "Примусово встановлює певний режим теми (темний або світлий) для цього ресурсу, перекриваючи налаштування користувача.",
"default": "Не застосовуйте примусовий режим",
"dark": "Темнота.",
"light": "Світло",
"enableOnlyOffice": "Увімкнути переглядач OnlyOffice",
"enableOnlyOfficeDescription": "Дозволити перегляд офісних файлів за допомогою OnlyOffice у цьому ресурсі.",
"enableOnlyOfficeEditing": "Увімкнути редагування OnlyOffice",
"enableOnlyOfficeEditingDescription": "Дозволити редагування офісних файлів за допомогою OnlyOffice у цьому ресурсі."
},
"api": {
"title": "Ключі API",
@@ -482,7 +491,9 @@
"showSelectMultiple": "Показувати вибране кілька в контекстному меню на робочому столі",
"showSelectMultipleDescription": "За замовчуванням, контекстне меню десктопа не має опції \"вибрати декілька\", як у мобільному. Ця опція завжди відображається у контекстному меню.",
"defaultMediaPlayer": "Використовуйте вбудований медіаплеєр вашого браузера",
"defaultMediaPlayerDescription": "Використовуйте власний медіаплеєр вашого браузера для відео- та аудіофайлів, а не медіаплеєр \"vue-plyr\", що входить до складу FileBrowser."
"defaultMediaPlayerDescription": "Використовуйте власний медіаплеєр вашого браузера для відео- та аудіофайлів, а не медіаплеєр \"vue-plyr\", що входить до складу FileBrowser.",
"debugOfficeEditor": "Увімкнути режим налагодження OnlyOffice",
"debugOfficeEditorDescription": "Показати налагоджувальне спливаюче вікно з додатковою інформацією в редакторі OnlyOffice."
},
"general": {
"name": "Ім'я",
+13 -2
View File
@@ -365,7 +365,16 @@
"disableShareCard": "禁用共享卡",
"disableShareCardDescription": "禁用共享页面侧边栏或手机页面顶部的共享卡。",
"disableSidebar": "禁用侧边栏",
"disableSidebarDescription": "禁用共享页面上的边栏。"
"disableSidebarDescription": "禁用共享页面上的边栏。",
"enforceDarkLightMode": "执行主题模式",
"enforceDarkLightModeDescription": "为该共享强制设置特定主题模式(深色或浅色),覆盖用户偏好。",
"default": "不执行模式",
"dark": "黑暗",
"light": "灯光",
"enableOnlyOffice": "启用 OnlyOffice 查看器",
"enableOnlyOfficeDescription": "允许在此共享中使用 OnlyOffice 查看办公文件。",
"enableOnlyOfficeEditing": "启用 OnlyOffice 编辑功能",
"enableOnlyOfficeEditingDescription": "允许在此共享中使用 OnlyOffice 编辑办公文件。"
},
"api": {
"title": "应用程序接口密钥",
@@ -482,7 +491,9 @@
"showSelectMultiple": "在桌面右键菜单中显示多个选择",
"showSelectMultipleDescription": "默认情况下,桌面右键菜单不像手机那样有 \"选择多个 \"选项。该选项始终显示在右键菜单中。",
"defaultMediaPlayer": "使用浏览器的本地媒体播放器",
"defaultMediaPlayerDescription": "使用浏览器的本地媒体播放器播放视频和音频文件,而不是 FileBrowser 附带的 \"vue-plyr \"媒体播放器。"
"defaultMediaPlayerDescription": "使用浏览器的本地媒体播放器播放视频和音频文件,而不是 FileBrowser 附带的 \"vue-plyr \"媒体播放器。",
"debugOfficeEditor": "启用 OnlyOffice 调试模式",
"debugOfficeEditorDescription": "在 OnlyOffice 编辑器中显示包含附加信息的调试弹出窗口。"
},
"general": {
"name": "名称",
+13 -2
View File
@@ -365,7 +365,16 @@
"disableShareCard": "停用分享卡",
"disableShareCardDescription": "停用分享頁面側邊欄或行動版頁面頂端的分享卡。",
"disableSidebar": "停用側邊欄",
"disableSidebarDescription": "停用共用頁面上的側邊欄。"
"disableSidebarDescription": "停用共用頁面上的側邊欄。",
"enforceDarkLightMode": "強制執行主題模式",
"enforceDarkLightModeDescription": "強制此共用的特定主題模式 (深色或淺色),凌駕使用者偏好設定。",
"default": "不要強制執行模式",
"dark": "黑暗",
"light": "燈光",
"enableOnlyOffice": "啟用 OnlyOffice 檢視器",
"enableOnlyOfficeDescription": "允許在此共用中使用 OnlyOffice 檢視辦公室檔案。",
"enableOnlyOfficeEditing": "啟用 OnlyOffice 編輯",
"enableOnlyOfficeEditingDescription": "允許在此共用中使用 OnlyOffice 編輯辦公室檔案。"
},
"api": {
"title": "API 金鑰",
@@ -456,7 +465,9 @@
"showSelectMultiple": "在桌面的上下文功能表中顯示多重選擇",
"showSelectMultipleDescription": "預設情況下,桌上型電腦的上下文功能表不像行動裝置一樣有「選取多個」選項。此選項總是在上下文功能表中顯示。",
"defaultMediaPlayer": "使用瀏覽器的本機媒體播放器",
"defaultMediaPlayerDescription": "使用瀏覽器的本機媒體播放器來播放視訊和音訊檔案,而非 FileBrowser 隨附的「vue-plyr」媒體播放器。"
"defaultMediaPlayerDescription": "使用瀏覽器的本機媒體播放器來播放視訊和音訊檔案,而非 FileBrowser 隨附的「vue-plyr」媒體播放器。",
"debugOfficeEditor": "啟用 OnlyOffice 除錯模式",
"debugOfficeEditorDescription": "在 OnlyOffice 編輯器中顯示包含其他資訊的除錯彈出式視窗。"
},
"otp": {
"name": "雙因素驗證",
+10 -5
View File
@@ -44,6 +44,12 @@ export const getters = {
return shareInfo.isShare
},
isDarkMode: () => {
if (shareInfo.enforceDarkLightMode == "dark") {
return true
}
if (shareInfo.enforceDarkLightMode == "light") {
return false
}
if (state.user == null) {
return true
}
@@ -342,12 +348,11 @@ export const getters = {
return false
},
officeViewingDisabled: filename => {
if (shareInfo.isShare) {
return true
}
const ext = ' ' + getFileExtension(filename)
if (state.user.disableOfficePreviewExt) {
const disabledExts = ' ' + state.user.disableOfficePreviewExt.toLowerCase()
const hasDisabled = shareInfo.disableOfficePreviewExt || state.user.disableOfficePreviewExt
if (hasDisabled) {
const disabledList = shareInfo.disableOfficePreviewExt + ' ' + state.user.disableOfficePreviewExt
const disabledExts = ' ' + disabledList.toLowerCase()
if (disabledExts.includes(ext.toLowerCase())) {
return true
}
+3
View File
@@ -7,6 +7,8 @@ export const state = reactive({
content: "",
x: 0,
y: 0,
pointerEvents: false,
width: null,
},
previousHistoryItem: {
name: "",
@@ -59,6 +61,7 @@ export const state = reactive({
permissions: {}, // Default to an empty object for permissions
darkMode: true, // Default to false, assuming this is a boolean
disableSettings: false,
debugOffice: false, // Debug mode for OnlyOffice integration
profile: { // Example of additional user properties
username: '', // Default to an empty string
email: '', // Default to an empty string
-4
View File
@@ -136,11 +136,9 @@ export default {
// scroll to previous item either from location hash or from previousItemHashId state
// prefers location hash
const noHashChange = window.location.hash === this.lastHash
console.log("noHashChange", noHashChange, state.previousItemHashId);
if (noHashChange && state.previousItemHashId === "") return;
this.lastHash = window.location.hash;
if (window.location.hash) {
console.log("scroll to hash based on location hash", window.location.hash);
const rawHash = window.location.hash.slice(1);
let decodedName = rawHash;
try {
@@ -152,10 +150,8 @@ export default {
scrollToId = url.base64Encode(encodeURIComponent(decodedName));
} else if (state.previousItemHashId) {
console.log("scroll to hash based on previousItemHashId", state.previousItemHashId);
scrollToId = url.base64Encode(encodeURIComponent(state.previousItemHashId));
}
console.log("scrollToId", scrollToId);
const element = document.getElementById(scrollToId);
if (element) {
element.scrollIntoView({
+1 -1
View File
@@ -275,7 +275,7 @@ export default {
return state.user.gallerySize;
},
isDarkMode() {
return state.user?.darkMode;
return getters.isDarkMode();
},
getMultiple() {
return state.multiple;
+3 -3
View File
@@ -5,7 +5,7 @@
<script lang="ts">
import { marked } from "marked";
import DOMPurify from 'dompurify';
import { state, mutations } from "@/store";
import { state, mutations, getters } from "@/store";
import hljs from 'highlight.js';
// --- We have removed all `marked.use()` configuration ---
@@ -205,7 +205,7 @@ export default {
});
},
darkMode() {
this.setHighlightTheme(state.user.darkMode);
this.setHighlightTheme(getters.isDarkMode());
}
},
computed: {
@@ -235,7 +235,7 @@ export default {
type: state.req.type,
source: state.req.source,
});
this.setHighlightTheme(state.user.darkMode);
this.setHighlightTheme(getters.isDarkMode());
// Set initial content. The `watch` will trigger the first highlight.
const fileContent = state.req.content == "empty-file-x6OlSil" ? "" : state.req.content || "";
this.content = fileContent;
+329 -26
View File
@@ -10,15 +10,16 @@
</div>
</template>
<script lang="ts">
<script>
import { DocumentEditor } from "@onlyoffice/document-editor-vue";
import { onlyOfficeUrl } from "@/utils/constants";
import { state, getters } from "@/store";
import { fetchJSON } from "@/api/utils";
import { filesApi, publicApi } from "@/api";
import { state, getters, mutations } from "@/store";
import { baseURL } from "@/utils/constants";
import { removeLastDir } from "@/utils/url";
import { filesApi } from "@/api";
const wikiLink = "https://github.com/gtsteffaniak/filebrowser/wiki/Office-Support#onlyoffice-integration-troubleshooting-guide"
const wikiLinkText = "📖 View Troubleshooting Guide"
export default {
name: "onlyOfficeEditor",
components: {
@@ -29,6 +30,12 @@ export default {
ready: false, // Flag to indicate whether the setup is complete
clientConfig: {},
floatIn: false, // Flag for the float-in animation
source: "",
path: "",
debugInfo: "",
debugMode: false,
hasErrors: false,
onlyOfficeServerCheck: "pending",
};
},
computed: {
@@ -40,45 +47,340 @@ export default {
},
},
async mounted() {
// Perform the setup and update the config
this.source = state.req.source;
this.path = state.req.path;
// Expose closeTooltip method to global window for tooltip button access
window.closeTooltip = () => this.closeTooltip();
// Initialize debug mode if enabled
if (!state.user.debugOffice) {
this.debugMode = true;
this.showDebugInfo();
}
// Perform the setup and update the config with simplified API parameters
try {
const refUrl = getters.isShare()
? publicApi.getDownloadURL({
path: state.share.subPath,
hash: state.share.hash,
token: state.share.token,
}, [state.req.path])
: await filesApi.getDownloadURL(
state.req.source,
state.req.path,
false,
true
);
let configData;
let configUrl = `api/onlyoffice/config?url=${encodeURIComponent(refUrl)}`;
if (getters.isShare()) {
configUrl = configUrl + `&hash=${state.share.hash}`;
// Update debug info
if (this.debugMode) {
this.updateDebugStatus("✅ Configuration Check - URL Built");
}
// Fetch configuration from backend
const configData = await filesApi.GetOfficeConfig(state.req)
if (this.debugMode) {
this.updateDebugStatus("✅ API Request - Config Received");
}
configData = await fetchJSON(baseURL + configUrl);
configData.type = state.isMobile ? "mobile" : "desktop";
this.clientConfig = configData;
console.log("Client config:", this.clientConfig);
console.log("OnlyOffice client config received:", this.clientConfig);
if (this.debugMode) {
this.updateDebugStatus("✅ OnlyOffice Server Connection - Config Sent");
}
this.ready = true;
// Trigger float-in animation
setTimeout(() => {
this.floatIn = true;
// Monitor for successful initialization and check OnlyOffice server
if (this.debugMode) {
this.checkOnlyOfficeServer();
}
}, 100); // slight delay to allow rendering
} catch (error) {
console.error("Error during setup:", error);
// Handle setup failure if needed
console.error("Error during OnlyOffice setup:", error);
if (this.debugMode) {
const errorMsg = (error && typeof error === 'object' && 'message' in error) ? error.message : String(error);
this.updateDebugStatus(`❌ Setup Error: ${errorMsg}<br/><a href="${wikiLink}" target="_blank">${wikiLinkText}</a>`);
}
// TODO: Show user-friendly error message
}
},
methods: {
close() {
const current = window.location.pathname;
const newpath = removeLastDir(current);
window.location = newpath + "#" + state.req.name;
// Get filename from path
const filename = this.path.split('/').pop() || "";
window.location.href = newpath + "#" + filename;
},
closeTooltip() {
mutations.hideTooltip();
},
showDebugInfo() {
if (!this.debugMode) return;
// Just initialize with the first step - no full HTML structure here
this.debugInfo = "⏳ Starting OnlyOffice initialization...";
// Define variables needed for template
const filename = this.path.split('/').pop() || "";
const isShare = getters.isShare();
// Build and show the initial tooltip with enhanced properties
let content = `
<div style="font-family: monospace; font-size: 12px; line-height: 1.4;">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px;">
<h3 style="margin: 0; color: #2196F3;">🔧 OnlyOffice Debug Trace</h3>
<button class="button tooltip-close-button" onclick="window.closeTooltip()" >x</button>
</div>
<div style="margin-bottom: 15px; padding: 10px; background: #f5f5f5; border-radius: 4px;">
<strong>Configuration:</strong><br/>
OnlyOffice URL: ${this.onlyOfficeUrl}<br/>
Internal URL: Check server logs<br/>
Base URL: ${baseURL}<br/>
Source: ${this.source}<br/>
Path: ${this.path}<br/>
Filename: ${filename}<br/>
${isShare ? `Share Hash: ${state.share.hash}` : 'User Request'}<br/>
</div>
<div style="margin-bottom: 15px;">
<strong>Process Steps:</strong><br/>
${this.debugInfo}
</div>
</div>
`;
state.tooltip.content = content;
state.tooltip.show = true;
state.tooltip.pointerEvents = true; // Enable clicks for links
state.tooltip.width = "50vw"; // Wider for debug info
state.tooltip.x = 50;
state.tooltip.y = 50;
},
updateDebugStatus(message) {
if (!this.debugMode) return;
// Append new message to debug info
this.debugInfo = this.debugInfo + "<br/>" + message;
// Update the tooltip
this.updateDebugTooltip();
},
updateDebugTooltip() {
if (!this.debugMode) return;
const filename = this.path.split('/').pop() || "";
const isShare = getters.isShare();
// Build config details if available
let configDetailsHtml = "";
if (this.clientConfig && this.clientConfig.document) {
const doc = this.clientConfig.document;
const editor = this.clientConfig.editorConfig;
// Extract domains for network flow analysis
const downloadDomain = doc.url ? new URL(doc.url).origin : 'N/A';
const callbackDomain = editor && editor.callbackUrl ? new URL(editor.callbackUrl).origin : 'N/A';
configDetailsHtml = `
<div style="margin-bottom: 15px; padding: 10px; background: #e3f2fd; border-radius: 4px;">
<strong>OnlyOffice Configuration Details:</strong><br/>
Document Key: ${doc.key}<br/>
File Type: ${doc.fileType}<br/>
Edit Mode: ${editor ? editor.mode : 'N/A'}<br/>
Download URL: ${doc.url ? doc.url.substring(0, 80) + '...' : 'N/A'}<br/>
Callback URL: ${editor && editor.callbackUrl ? editor.callbackUrl.substring(0, 80) + '...' : 'N/A'}<br/>
<br/>
<strong>Network Flow:</strong><br/>
Browser ↔ OnlyOffice: ${this.onlyOfficeUrl}<br/>
OnlyOffice → FileBrowser: ${downloadDomain}<br/>
OnlyOffice → FileBrowser: ${callbackDomain}<br/>
</div>
`;
}
// Determine overall status
let overallStatus = "";
if (this.hasErrors) {
overallStatus = `
<div style="margin-top: 15px; padding: 10px; background: #ffebee; border-radius: 4px; color: #c62828;">
<strong>❌ Error Detected</strong><br/>
OnlyOffice integration failed. Check the failed steps above.<br/>
<a href="${wikiLink}" target="_blank" style="color: #1976d2;">
${wikiLinkText}
</a>
</div>
`;
} else if (this.debugInfo.includes("🎉") && !this.hasErrors) {
overallStatus = `
<div style="margin-top: 15px; padding: 10px; background: #e8f5e8; border-radius: 4px; color: #2e7d32;">
<strong>🎉 Success!</strong><br/>
OnlyOffice integration completed successfully. No issues found!
</div>
`;
}
let content = `
<div style="font-family: monospace; font-size: 11px; line-height: 1.4;">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px;">
<h3 style="margin: 0; color: #2196F3;">🔧 OnlyOffice Debug Trace</h3>
<button class="button tooltip-close-button" onclick="window.closeTooltip()" >x</button>
</div>
<div style="margin-bottom: 15px; padding: 10px; background: #f5f5f5; border-radius: 4px;">
<strong>Basic Configuration:</strong><br/>
OnlyOffice URL: ${this.onlyOfficeUrl}<br/>
Internal URL: Check server logs<br/>
Base URL: ${baseURL}<br/>
Source: ${this.source}<br/>
Path: ${this.path}<br/>
Filename: ${filename}<br/>
${isShare ? `Share Hash: ${state.share.hash}` : 'User Request'}<br/>
</div>
${configDetailsHtml}
<div style="margin-bottom: 15px;">
<strong>Process Steps:</strong><br/>
${this.debugInfo}
</div>
${overallStatus}
</div>
`;
state.tooltip.content = content;
state.tooltip.show = true;
state.tooltip.pointerEvents = true; // Enable clicks for troubleshooting links
state.tooltip.width = "50vw"; // Wider for debug info
state.tooltip.x = 50;
state.tooltip.y = 50; // Position at top since content is longer now
},
showConfigDetails(configData) {
if (!this.debugMode) return;
// Store config for tooltip display
this.clientConfig = configData;
// Extract key URLs and domains
const docUrl = configData.document ? configData.document.url : 'Not found';
const callbackUrl = configData.editorConfig ? configData.editorConfig.callbackUrl : 'Not found';
let urlAnalysis = "";
try {
if (docUrl !== 'Not found') {
const downloadDomain = new URL(docUrl).origin;
urlAnalysis += `Download Domain: ${downloadDomain}<br/>`;
}
if (callbackUrl !== 'Not found') {
const callbackDomain = new URL(callbackUrl).origin;
urlAnalysis += `Callback Domain: ${callbackDomain}<br/>`;
}
} catch (error) {
urlAnalysis = "⚠️ URL parsing error - check configuration<br/>";
}
this.addDebugStep("Config Analysis", "success", urlAnalysis + "URLs generated for OnlyOffice server");
},
checkOnlyOfficeServer() {
if (!this.debugMode) return;
const testUrl = `${this.onlyOfficeUrl}/web-apps/apps/api/documents/api.js`;
this.updateDebugStatus("🔄 Testing OnlyOffice server connectivity...");
// Monitor browser console for OnlyOffice errors
const originalConsoleError = console.error;
let detectedNetworkError = false;
console.error = (...args) => {
originalConsoleError.apply(console, args);
const errorStr = args.join(' ');
if (errorStr.includes('ERR_CONNECTION_REFUSED') && errorStr.includes(this.onlyOfficeUrl)) {
detectedNetworkError = true;
this.hasErrors = true;
let errorMsg = `❌ OnlyOffice Server Connection Refused<br/>`;
errorMsg += `<strong>Failed URL:</strong> ${errorStr.match(/GET ([^\s]+)/)?.[1] || testUrl}<br/>`;
errorMsg += `<strong>OnlyOffice Server:</strong> ${this.onlyOfficeUrl}<br/><br/>`;
errorMsg += `<strong>This means:</strong><br/>`;
errorMsg += `• OnlyOffice server is <strong>NOT RUNNING</strong><br/>`;
errorMsg += `• Wrong OnlyOffice URL configured<br/>`;
errorMsg += `• Network/firewall blocking connection<br/><br/>`;
errorMsg += `<strong>To fix:</strong><br/>`;
errorMsg += `• Start OnlyOffice Document Server<br/>`;
errorMsg += `• Verify ${this.onlyOfficeUrl} is accessible<br/>`;
errorMsg += `• Check Docker containers if using Docker<br/>`;
errorMsg += `<br/><a href="${wikiLink}" target="_blank" style="color: #1976d2;">${wikiLinkText}</a>`;
this.updateDebugStatus(errorMsg);
}
};
// Test basic connectivity
setTimeout(() => {
fetch(testUrl, { method: 'HEAD', mode: 'no-cors' })
.then(() => {
if (!detectedNetworkError) {
this.updateDebugStatus("✅ OnlyOffice server appears reachable");
}
})
.catch(() => {
if (!detectedNetworkError) {
this.hasErrors = true;
this.updateDebugStatus(`❌ OnlyOffice server test failed: ${testUrl}<br/>• Check if OnlyOffice server is running<br/>• Verify URL configuration`);
}
});
// Monitor for successful completion
setTimeout(() => {
console.error = originalConsoleError; // Restore original console.error
if (!this.hasErrors && !detectedNetworkError) {
this.updateDebugStatus("✅ Document Download - Complete");
this.updateDebugStatus("🎉 Editor Initialization - Success! All steps completed.");
}
}, 4000);
}, 1000);
},
onLoadComponentError(errorInfo) {
console.error("OnlyOffice component load error:", errorInfo);
if (this.debugMode) {
this.hasErrors = true;
let errorMsg = "❌ OnlyOffice Component Load Error<br/>";
if (errorInfo === -2) {
errorMsg += "<strong>Error Code -2:</strong> OnlyOffice server connection failed<br/>";
errorMsg += "• Verify OnlyOffice server is running<br/>";
errorMsg += `• Test URL manually: ${this.onlyOfficeUrl}<br/>`;
errorMsg += "• Check network connectivity<br/>";
} else if (errorInfo === -3) {
errorMsg += "<strong>Error Code -3:</strong> Document loading failed<br/>";
errorMsg += "• File may be corrupted or unsupported<br/>";
errorMsg += "• Check file permissions<br/>";
} else if (errorInfo === -4) {
errorMsg += "<strong>Error Code -4:</strong> Document download failed<br/>";
errorMsg += "• Authentication may have expired<br/>";
errorMsg += "• Check download URL accessibility<br/>";
} else {
errorMsg += `<strong>Error Code:</strong> ${errorInfo || 'Unknown error'}<br/>`;
errorMsg += "• Check browser console for detailed error messages<br/>";
}
errorMsg += `<br/><a href="${wikiLink}" target="_blank" style="color: #1976d2;">${wikiLinkText}</a>`;
this.updateDebugStatus(errorMsg);
}
},
},
};
@@ -106,4 +408,5 @@ export default {
padding-right: 1em;
padding-left: 1em;
}
</style>
+12
View File
@@ -234,6 +234,17 @@
{{ $t("buttons.save") }}
</button>
</div>
<div class="settings-items">
<br/>
<ToggleSwitch
class="item"
v-model="localuser.debugOffice"
@change="updateSettings"
:name="$t('profileSettings.debugOfficeEditor')"
:description="$t('profileSettings.debugOfficeEditorDescription')"
/>
</div>
</div>
<h3>{{ $t("settings.themeColor") }}</h3>
<ButtonGroup
@@ -417,6 +428,7 @@ export default {
"hideSidebarFileActions",
"editorQuickSave",
"showSelectMultiple",
"debugOffice",
]);
if (themeChanged) {
window.location.reload();