onlyoffice share support (#1181)
This commit is contained in:
1 parent
99786eafde
commit
d4977713e7
53 files changed
+1126
-974
No files matched your search
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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, " ")
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:"-"`
|
||||
}
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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' })
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -408,4 +408,8 @@ button:hover,
|
||||
.divider {
|
||||
border-color: var(--divider);
|
||||
width: 90%;
|
||||
}
|
||||
|
||||
.tooltip-close-button {
|
||||
max-width: 4em !important;
|
||||
}
|
||||
@@ -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": "أحمر",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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.",
|
||||
|
||||
@@ -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": "Απέτυχε η αρχικοποίηση του συντάκτη. Παρακαλούμε επαναφορτώστε.",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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.",
|
||||
|
||||
@@ -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": "לא ניתן לאתחל את העורך. אנא טען מחדש.",
|
||||
|
||||
@@ -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.",
|
||||
|
||||
@@ -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.",
|
||||
|
||||
@@ -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": "エディタの初期化に失敗しました。再読み込みしてください。",
|
||||
|
||||
@@ -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": "편집기를 초기화하지 못했습니다. 다시 로드하세요.",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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.",
|
||||
|
||||
@@ -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.",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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.",
|
||||
|
||||
@@ -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": "Не удалось инициализировать редактор. Пожалуйста, перезагрузитесь.",
|
||||
|
||||
@@ -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.",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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.",
|
||||
|
||||
@@ -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": "Ім'я",
|
||||
|
||||
@@ -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": "名称",
|
||||
|
||||
@@ -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": "雙因素驗證",
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -275,7 +275,7 @@ export default {
|
||||
return state.user.gallerySize;
|
||||
},
|
||||
isDarkMode() {
|
||||
return state.user?.darkMode;
|
||||
return getters.isDarkMode();
|
||||
},
|
||||
getMultiple() {
|
||||
return state.multiple;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
@@ -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();
|
||||
|
||||
Reference in new issue
Block a user