From 08f37b90ce3d0714c21ff811912c8b04ca80e44a Mon Sep 17 00:00:00 2001
From: DrosoCode
Date: Mon, 31 May 2021 13:36:58 +0200
Subject: [PATCH] feat: onlyoffice editor
---
cmd/config_init.go | 4 +
cmd/config_set.go | 4 +
files/file.go | 3 +
frontend/src/i18n/ar.json | 2 +
frontend/src/i18n/de.json | 4 +-
frontend/src/i18n/en.json | 5 +-
frontend/src/i18n/es.json | 4 +-
frontend/src/i18n/fr.json | 4 +-
frontend/src/i18n/is.json | 4 +-
frontend/src/i18n/it.json | 4 +-
frontend/src/i18n/ja.json | 4 +-
frontend/src/i18n/ko.json | 4 +-
frontend/src/i18n/nl-be.json | 4 +-
frontend/src/i18n/pl.json | 4 +-
frontend/src/i18n/pt-br.json | 4 +-
frontend/src/i18n/pt.json | 4 +-
frontend/src/i18n/ro.json | 4 +-
frontend/src/i18n/ru.json | 4 +-
frontend/src/i18n/sv-se.json | 4 +-
frontend/src/i18n/zh-cn.json | 4 +-
frontend/src/i18n/zh-tw.json | 4 +-
frontend/src/utils/constants.ts | 2 +
frontend/src/views/Files.vue | 4 +
frontend/src/views/files/OnlyOfficeEditor.vue | 170 ++++++++++++++++++
frontend/src/views/settings/Global.vue | 25 +++
http/http.go | 2 +
http/onlyoffice.go | 64 +++++++
http/settings.go | 3 +
http/static.go | 1 +
settings/onlyoffice.go | 7 +
settings/settings.go | 1 +
test.docx | Bin 0 -> 24698 bytes
32 files changed, 344 insertions(+), 17 deletions(-)
create mode 100644 frontend/src/views/files/OnlyOfficeEditor.vue
create mode 100644 http/onlyoffice.go
create mode 100644 settings/onlyoffice.go
create mode 100644 test.docx
diff --git a/cmd/config_init.go b/cmd/config_init.go
index 60a0f37b..a8426e7c 100644
--- a/cmd/config_init.go
+++ b/cmd/config_init.go
@@ -42,6 +42,10 @@ override the options.`,
Theme: mustGetString(flags, "branding.theme"),
Files: mustGetString(flags, "branding.files"),
},
+ OnlyOffice: settings.OnlyOffice{
+ URL: mustGetString(flags, "onlyoffice.url"),
+ JWTSecret: mustGetString(flags, "onlyoffice.jwtSecret"),
+ },
}
ser := &settings.Server{
diff --git a/cmd/config_set.go b/cmd/config_set.go
index 23ff7e1b..11a40040 100644
--- a/cmd/config_set.go
+++ b/cmd/config_set.go
@@ -63,6 +63,10 @@ you want to change. Other options will remain unchanged.`,
set.Branding.DisableUsedPercentage = mustGetBool(flags, flag.Name)
case "branding.files":
set.Branding.Files = mustGetString(flags, flag.Name)
+ case "onlyoffice.url":
+ set.OnlyOffice.URL = mustGetString(flags, flag.Name)
+ case "onlyoffice.jwtSecret":
+ set.OnlyOffice.JWTSecret = mustGetString(flags, flag.Name)
}
})
diff --git a/files/file.go b/files/file.go
index 03b3a6f9..7d3a695b 100644
--- a/files/file.go
+++ b/files/file.go
@@ -276,6 +276,9 @@ func (i *FileInfo) detectType(modify, saveContent, readHeader bool) error {
i.Content = string(content)
}
return nil
+ case strings.HasPrefix(mimetype, "application/vnd.openxmlformats-officedocument"):
+ i.Type = "officedocument"
+ return nil
default:
i.Type = "blob"
}
diff --git a/frontend/src/i18n/ar.json b/frontend/src/i18n/ar.json
index 0236a9f3..ede9b9c3 100644
--- a/frontend/src/i18n/ar.json
+++ b/frontend/src/i18n/ar.json
@@ -195,6 +195,8 @@
"newPassword": "كلمة المرور الجديدة",
"newPasswordConfirm": "تأكيد كلمة المرور",
"newUser": "مستخدم جديد",
+ "onlyOffice": "Only Office Integration",
+ "onlyOfficeUrl": "Only Office URL (leave blank to disable)",
"password": "كلمة المرور",
"passwordUpdated": "تم تغيير كلمة المرور!",
"path": "المسار",
diff --git a/frontend/src/i18n/de.json b/frontend/src/i18n/de.json
index 2609a5f3..0c45a09d 100644
--- a/frontend/src/i18n/de.json
+++ b/frontend/src/i18n/de.json
@@ -182,6 +182,8 @@
"newPassword": "Ihr neues Passwort.",
"newPasswordConfirm": "Bestätigen Sie Ihr neues Passwort",
"newUser": "Neuer Benutzer",
+ "onlyOffice": "Only Office Integration",
+ "onlyOfficeUrl": "Only Office URL (leave blank to disable)",
"password": "Passwort",
"passwordUpdated": "Passwort aktualisiert!",
"path": "Pfad",
@@ -247,4 +249,4 @@
"seconds": "Sekunden",
"unit": "Zeiteinheit"
}
-}
+}
\ No newline at end of file
diff --git a/frontend/src/i18n/en.json b/frontend/src/i18n/en.json
index 00d43b9d..9127ee72 100644
--- a/frontend/src/i18n/en.json
+++ b/frontend/src/i18n/en.json
@@ -195,6 +195,9 @@
"newPassword": "Your new password",
"newPasswordConfirm": "Confirm your new password",
"newUser": "New User",
+ "onlyOffice": "Only Office Integration",
+ "onlyOfficeUrl": "Only Office URL (leave blank to disable)",
+ "onlyOfficeJwtSecret": "Only Office JWT Secret (works only with https, leave blank to disable)",
"password": "Password",
"passwordUpdated": "Password updated!",
"path": "Path",
@@ -261,4 +264,4 @@
"seconds": "Seconds",
"unit": "Time Unit"
}
-}
+}
\ No newline at end of file
diff --git a/frontend/src/i18n/es.json b/frontend/src/i18n/es.json
index 06197a79..43d4dc92 100644
--- a/frontend/src/i18n/es.json
+++ b/frontend/src/i18n/es.json
@@ -182,6 +182,8 @@
"newPassword": "Tu nueva contraseña",
"newPasswordConfirm": "Confirma tu contraseña",
"newUser": "Nuevo usuario",
+ "onlyOffice": "Only Office Integration",
+ "onlyOfficeUrl": "Only Office URL (leave blank to disable)",
"password": "Contraseña",
"passwordUpdated": "¡Contraseña actualizada!",
"path": "Ruta",
@@ -247,4 +249,4 @@
"seconds": "Segundos",
"unit": "Unidad"
}
-}
+}
\ No newline at end of file
diff --git a/frontend/src/i18n/fr.json b/frontend/src/i18n/fr.json
index 30725641..d03d26b2 100644
--- a/frontend/src/i18n/fr.json
+++ b/frontend/src/i18n/fr.json
@@ -181,6 +181,8 @@
"newPassword": "Votre nouveau mot de passe",
"newPasswordConfirm": "Confirmation du nouveau mot de passe",
"newUser": "Nouvel Utilisateur",
+ "onlyOffice": "Intégration Only Office",
+ "onlyOfficeUrl": "URL vers OnlyOffice (laisser vide pour désactiver)",
"password": "Mot de passe",
"passwordUpdated": "Mot de passe mis à jour !",
"path": "",
@@ -246,4 +248,4 @@
"seconds": "Secondes",
"unit": "Unité de temps"
}
-}
+}
\ No newline at end of file
diff --git a/frontend/src/i18n/is.json b/frontend/src/i18n/is.json
index d53874c8..603aa7b9 100644
--- a/frontend/src/i18n/is.json
+++ b/frontend/src/i18n/is.json
@@ -169,6 +169,8 @@
"newPassword": "Nýja lykilorðið þitt",
"newPasswordConfirm": "Staðfestu nýja lykilorðið",
"newUser": "Nýr notandi",
+ "onlyOffice": "Only Office Integration",
+ "onlyOfficeUrl": "Only Office URL (leave blank to disable)",
"password": "Lykilorð",
"passwordUpdated": "Lykilorð vistað!",
"path": "",
@@ -232,4 +234,4 @@
"seconds": "Sekúndur",
"unit": "Tímastilling"
}
-}
+}
\ No newline at end of file
diff --git a/frontend/src/i18n/it.json b/frontend/src/i18n/it.json
index a03c7f42..64451c56 100644
--- a/frontend/src/i18n/it.json
+++ b/frontend/src/i18n/it.json
@@ -170,6 +170,8 @@
"newPassword": "La tua nuova password",
"newPasswordConfirm": "Conferma la password",
"newUser": "Nuovo utente",
+ "onlyOffice": "Only Office Integration",
+ "onlyOfficeUrl": "Only Office URL (leave blank to disable)",
"password": "Password",
"passwordUpdated": "Password aggiornata!",
"path": "Percorso",
@@ -233,4 +235,4 @@
"seconds": "Secondi",
"unit": "Unità di tempo"
}
-}
+}
\ No newline at end of file
diff --git a/frontend/src/i18n/ja.json b/frontend/src/i18n/ja.json
index f16a9d16..81634557 100644
--- a/frontend/src/i18n/ja.json
+++ b/frontend/src/i18n/ja.json
@@ -191,6 +191,8 @@
"newPassword": "新しいパスワード",
"newPasswordConfirm": "新しいパスワード(再入力)",
"newUser": "新規ユーザー作成",
+ "onlyOffice": "Only Office Integration",
+ "onlyOfficeUrl": "Only Office URL (leave blank to disable)",
"password": "パスワード",
"passwordUpdated": "パスワードを更新しました!",
"path": "パス",
@@ -256,4 +258,4 @@
"seconds": "秒",
"unit": "時間の単位"
}
-}
+}
\ No newline at end of file
diff --git a/frontend/src/i18n/ko.json b/frontend/src/i18n/ko.json
index 046a712b..7ae3de8c 100644
--- a/frontend/src/i18n/ko.json
+++ b/frontend/src/i18n/ko.json
@@ -169,6 +169,8 @@
"newPassword": "새로운 비밀번호",
"newPasswordConfirm": "새로운 비밀번호 확인",
"newUser": "새로운 사용자",
+ "onlyOffice": "Only Office Integration",
+ "onlyOfficeUrl": "Only Office URL (leave blank to disable)",
"password": "비밀번호",
"passwordUpdated": "비밀번호 수정 완료!",
"path": "",
@@ -232,4 +234,4 @@
"seconds": "초",
"unit": "Time Unit"
}
-}
+}
\ No newline at end of file
diff --git a/frontend/src/i18n/nl-be.json b/frontend/src/i18n/nl-be.json
index 913e48f0..2e94a45a 100644
--- a/frontend/src/i18n/nl-be.json
+++ b/frontend/src/i18n/nl-be.json
@@ -169,6 +169,8 @@
"newPassword": "Uw nieuw wachtwoord",
"newPasswordConfirm": "Bevestig uw nieuw wachtwoord",
"newUser": "Nieuwe gebruiker",
+ "onlyOffice": "Only Office Integration",
+ "onlyOfficeUrl": "Only Office URL (leave blank to disable)",
"password": "Wachtwoord",
"passwordUpdated": "Wachtwoord bijgewerkt!",
"path": "",
@@ -232,4 +234,4 @@
"seconds": "Seconden",
"unit": "Tijdseenheid"
}
-}
+}
\ No newline at end of file
diff --git a/frontend/src/i18n/pl.json b/frontend/src/i18n/pl.json
index 9fcd4afa..95ad5fc3 100644
--- a/frontend/src/i18n/pl.json
+++ b/frontend/src/i18n/pl.json
@@ -169,6 +169,8 @@
"newPassword": "Twoje nowe hasło",
"newPasswordConfirm": "Potwierdź swoje hasło",
"newUser": "Nowy Użytkownik",
+ "onlyOffice": "Only Office Integration",
+ "onlyOfficeUrl": "Only Office URL (leave blank to disable)",
"password": "Hasło",
"passwordUpdated": "Hasło zostało zapisane!",
"path": "Ścieżka",
@@ -232,4 +234,4 @@
"seconds": "Sekundy",
"unit": "Jednostka czasu"
}
-}
+}
\ No newline at end of file
diff --git a/frontend/src/i18n/pt-br.json b/frontend/src/i18n/pt-br.json
index 53d855d1..f78c3b2b 100644
--- a/frontend/src/i18n/pt-br.json
+++ b/frontend/src/i18n/pt-br.json
@@ -182,6 +182,8 @@
"newPassword": "Nova senha",
"newPasswordConfirm": "Confirme a nova senha",
"newUser": "Novo usuário",
+ "onlyOffice": "Only Office Integration",
+ "onlyOfficeUrl": "Only Office URL (leave blank to disable)",
"password": "Senha",
"passwordUpdated": "Senha atualizada!",
"path": "",
@@ -247,4 +249,4 @@
"seconds": "Segundos",
"unit": "Unidades de Tempo"
}
-}
+}
\ No newline at end of file
diff --git a/frontend/src/i18n/pt.json b/frontend/src/i18n/pt.json
index e7cd9efa..47740180 100644
--- a/frontend/src/i18n/pt.json
+++ b/frontend/src/i18n/pt.json
@@ -170,6 +170,8 @@
"newPassword": "Nova palavra-passe",
"newPasswordConfirm": "Confirme a nova palavra-passe",
"newUser": "Novo utilizador",
+ "onlyOffice": "Only Office Integration",
+ "onlyOfficeUrl": "Only Office URL (leave blank to disable)",
"password": "Palavra-passe",
"passwordUpdated": "Palavra-passe atualizada!",
"path": "",
@@ -233,4 +235,4 @@
"seconds": "Segundos",
"unit": "Unidades de tempo"
}
-}
+}
\ No newline at end of file
diff --git a/frontend/src/i18n/ro.json b/frontend/src/i18n/ro.json
index 6d564461..068c3c4f 100644
--- a/frontend/src/i18n/ro.json
+++ b/frontend/src/i18n/ro.json
@@ -169,6 +169,8 @@
"newPassword": "Noua ta parolă",
"newPasswordConfirm": "Confirmă noua parolă",
"newUser": "Utilizator nou",
+ "onlyOffice": "Only Office Integration",
+ "onlyOfficeUrl": "Only Office URL (leave blank to disable)",
"password": "Parola",
"passwordUpdated": "Parola actualizată!",
"path": "",
@@ -232,4 +234,4 @@
"seconds": "Secunde",
"unit": "Unitate de timp"
}
-}
+}
\ No newline at end of file
diff --git a/frontend/src/i18n/ru.json b/frontend/src/i18n/ru.json
index bb6e8c92..8d261ae9 100644
--- a/frontend/src/i18n/ru.json
+++ b/frontend/src/i18n/ru.json
@@ -177,6 +177,8 @@
"newPassword": "Новый пароль",
"newPasswordConfirm": "Повтор нового пароля",
"newUser": "Новый пользователь",
+ "onlyOffice": "Only Office Integration",
+ "onlyOfficeUrl": "Only Office URL (leave blank to disable)",
"password": "Пароль",
"passwordUpdated": "Пароль обновлен!",
"path": "Путь",
@@ -242,4 +244,4 @@
"seconds": "Секунды",
"unit": "Единица времени"
}
-}
+}
\ No newline at end of file
diff --git a/frontend/src/i18n/sv-se.json b/frontend/src/i18n/sv-se.json
index d6bb1676..b53257c6 100644
--- a/frontend/src/i18n/sv-se.json
+++ b/frontend/src/i18n/sv-se.json
@@ -169,6 +169,8 @@
"newPassword": "Ditt nya lösenord",
"newPasswordConfirm": "Bekräfta ditt nya lösenord",
"newUser": "Ny användare",
+ "onlyOffice": "Only Office Integration",
+ "onlyOfficeUrl": "Only Office URL (leave blank to disable)",
"password": "Lösenord",
"passwordUpdated": "Lösenord uppdaterat",
"path": "",
@@ -232,4 +234,4 @@
"seconds": "Sekunder",
"unit": "Tidsenhet"
}
-}
+}
\ No newline at end of file
diff --git a/frontend/src/i18n/zh-cn.json b/frontend/src/i18n/zh-cn.json
index 51801a59..a0ab108c 100644
--- a/frontend/src/i18n/zh-cn.json
+++ b/frontend/src/i18n/zh-cn.json
@@ -191,6 +191,8 @@
"newPassword": "你的新密码",
"newPasswordConfirm": "再次输入以确认你的新密码",
"newUser": "新建用户",
+ "onlyOffice": "Only Office Integration",
+ "onlyOfficeUrl": "Only Office URL (leave blank to disable)",
"password": "密码",
"passwordUpdated": "密码已更新!",
"path": "路径",
@@ -256,4 +258,4 @@
"seconds": "秒",
"unit": "时间单位"
}
-}
+}
\ No newline at end of file
diff --git a/frontend/src/i18n/zh-tw.json b/frontend/src/i18n/zh-tw.json
index 26da1978..2f176dae 100644
--- a/frontend/src/i18n/zh-tw.json
+++ b/frontend/src/i18n/zh-tw.json
@@ -169,6 +169,8 @@
"newPassword": "您的新密碼",
"newPasswordConfirm": "重輸一遍新密碼",
"newUser": "建立使用者",
+ "onlyOffice": "Only Office Integration",
+ "onlyOfficeUrl": "Only Office URL (leave blank to disable)",
"password": "密碼",
"passwordUpdated": "密碼已更新!",
"path": "",
@@ -232,4 +234,4 @@
"seconds": "秒",
"unit": "時間單位"
}
-}
+}
\ No newline at end of file
diff --git a/frontend/src/utils/constants.ts b/frontend/src/utils/constants.ts
index 3f131b1a..c437b0d2 100644
--- a/frontend/src/utils/constants.ts
+++ b/frontend/src/utils/constants.ts
@@ -18,6 +18,7 @@ const enableExec: boolean = window.FileBrowser.EnableExec;
const tusSettings = window.FileBrowser.TusSettings;
const origin = window.location.origin;
const tusEndpoint = `/api/tus`;
+const onlyOffice = window.FileBrowser.OnlyOffice;
export {
name,
@@ -39,4 +40,5 @@ export {
tusSettings,
origin,
tusEndpoint,
+ onlyOffice,
};
diff --git a/frontend/src/views/Files.vue b/frontend/src/views/Files.vue
index 2e82fc6b..ea6196d2 100644
--- a/frontend/src/views/Files.vue
+++ b/frontend/src/views/Files.vue
@@ -37,6 +37,7 @@ import { storeToRefs } from "pinia";
import { useFileStore } from "@/stores/file";
import { useLayoutStore } from "@/stores/layout";
import { useUploadStore } from "@/stores/upload";
+import { onlyOffice } from "@/utils/constants";
import HeaderBar from "@/components/header/HeaderBar.vue";
import Breadcrumbs from "@/components/Breadcrumbs.vue";
@@ -47,6 +48,7 @@ import FileListing from "@/views/files/FileListing.vue";
import { StatusError } from "@/api/utils";
const Editor = defineAsyncComponent(() => import("@/views/files/Editor.vue"));
const Preview = defineAsyncComponent(() => import("@/views/files/Preview.vue"));
+const OnlyOfficeEditor = defineAsyncComponent(() => import("@/views/files/OnlyOfficeEditor.vue"));
const layoutStore = useLayoutStore();
const fileStore = useFileStore();
@@ -77,6 +79,8 @@ const currentView = computed(() => {
fileStore.req.type === "textImmutable"
) {
return Editor;
+ } else if (fileStore.req.type === "officedocument" && onlyOffice !== "") {
+ return OnlyOfficeEditor;
} else {
return Preview;
}
diff --git a/frontend/src/views/files/OnlyOfficeEditor.vue b/frontend/src/views/files/OnlyOfficeEditor.vue
new file mode 100644
index 00000000..bb76dce8
--- /dev/null
+++ b/frontend/src/views/files/OnlyOfficeEditor.vue
@@ -0,0 +1,170 @@
+
+
+
+
+ {{ req.name }}
+
+
+
+
+
+
+
+
+
+
+
diff --git a/frontend/src/views/settings/Global.vue b/frontend/src/views/settings/Global.vue
index 40a3ec04..1b77e9e5 100644
--- a/frontend/src/views/settings/Global.vue
+++ b/frontend/src/views/settings/Global.vue
@@ -136,6 +136,31 @@
/>
+
+ {{ $t("settings.onlyOffice") }}
+
+
+
+
+
+
+
+
+
diff --git a/http/http.go b/http/http.go
index 620c43fd..1da832bd 100644
--- a/http/http.go
+++ b/http/http.go
@@ -60,6 +60,8 @@ func NewHandler(
users.Handle("/{id:[0-9]+}", monkey(userGetHandler, "")).Methods("GET")
users.Handle("/{id:[0-9]+}", monkey(userDeleteHandler, "")).Methods("DELETE")
+ api.PathPrefix("/onlyoffice").Handler(monkey(onlyofficeCallbackHandler, "/api/onlyoffice/callback")).Methods("POST")
+
api.PathPrefix("/resources").Handler(monkey(resourceGetHandler, "/api/resources")).Methods("GET")
api.PathPrefix("/resources").Handler(monkey(resourceDeleteHandler(fileCache), "/api/resources")).Methods("DELETE")
api.PathPrefix("/resources").Handler(monkey(resourcePostHandler(fileCache), "/api/resources")).Methods("POST")
diff --git a/http/onlyoffice.go b/http/onlyoffice.go
new file mode 100644
index 00000000..55d6416e
--- /dev/null
+++ b/http/onlyoffice.go
@@ -0,0 +1,64 @@
+package http
+
+import (
+ "encoding/json"
+ "errors"
+ "io"
+ "net/http"
+)
+
+type OnlyOfficeCallback struct {
+ ChangesURL string `json:"changesurl,omitempty"`
+ Key string `json:"key"`
+ Status int `json:"status"`
+ URL string `json:"url,omitempty"`
+ Users []string `json:"users,omitempty"`
+ UserData string `json:"userdata,omitempty"`
+}
+
+var onlyofficeCallbackHandler = withUser(func(w http.ResponseWriter, r *http.Request, d *data) (int, error) {
+ body, err := io.ReadAll(r.Body)
+ if err != nil {
+ return http.StatusInternalServerError, err
+ }
+
+ var data OnlyOfficeCallback
+ err = json.Unmarshal(body, &data)
+ if err != nil {
+ return http.StatusInternalServerError, err
+ }
+
+ if data.Status == 2 || data.Status == 6 {
+ docPath := r.URL.Query().Get("save")
+ if docPath == "" {
+ return http.StatusInternalServerError, errors.New("unable to get file save path")
+ }
+
+ if !d.user.Perm.Modify || !d.Check(docPath) {
+ return http.StatusForbidden, nil
+ }
+
+ doc, err := http.Get(data.URL)
+ if err != nil {
+ return http.StatusInternalServerError, err
+ }
+ defer doc.Body.Close()
+
+ err = d.RunHook(func() error {
+ _, writeErr := writeFile(d.user.Fs, docPath, doc.Body)
+ if writeErr != nil {
+ return writeErr
+ }
+ return nil
+ }, "save", docPath, "", d.user)
+
+ if err != nil {
+ return http.StatusInternalServerError, err
+ }
+ }
+
+ resp := map[string]int{
+ "error": 0,
+ }
+ return renderJSON(w, r, resp)
+})
diff --git a/http/settings.go b/http/settings.go
index de3f22ad..4c294148 100644
--- a/http/settings.go
+++ b/http/settings.go
@@ -18,6 +18,7 @@ type settingsData struct {
Tus settings.Tus `json:"tus"`
Shell []string `json:"shell"`
Commands map[string][]string `json:"commands"`
+ OnlyOffice settings.OnlyOffice `json:"onlyoffice"`
}
var settingsGetHandler = withAdmin(func(w http.ResponseWriter, r *http.Request, d *data) (int, error) {
@@ -31,6 +32,7 @@ var settingsGetHandler = withAdmin(func(w http.ResponseWriter, r *http.Request,
Tus: d.settings.Tus,
Shell: d.settings.Shell,
Commands: d.settings.Commands,
+ OnlyOffice: d.settings.OnlyOffice,
}
return renderJSON(w, r, data)
@@ -52,6 +54,7 @@ var settingsPutHandler = withAdmin(func(_ http.ResponseWriter, r *http.Request,
d.settings.Tus = req.Tus
d.settings.Shell = req.Shell
d.settings.Commands = req.Commands
+ d.settings.OnlyOffice = req.OnlyOffice
err = d.store.Settings.Save(d.settings)
return errToStatus(err), err
diff --git a/http/static.go b/http/static.go
index 47d828c6..d8f19c4c 100644
--- a/http/static.go
+++ b/http/static.go
@@ -46,6 +46,7 @@ func handleWithStaticData(w http.ResponseWriter, _ *http.Request, d *data, fSys
"ResizePreview": d.server.ResizePreview,
"EnableExec": d.server.EnableExec,
"TusSettings": d.settings.Tus,
+ "OnlyOffice": d.settings.OnlyOffice,
}
if d.settings.Branding.Files != "" {
diff --git a/settings/onlyoffice.go b/settings/onlyoffice.go
new file mode 100644
index 00000000..1b6cb185
--- /dev/null
+++ b/settings/onlyoffice.go
@@ -0,0 +1,7 @@
+package settings
+
+// OnlyOffice contains the onlyoffice server connection settings of the app.
+type OnlyOffice struct {
+ URL string `json:"url"`
+ JWTSecret string `json:"jwtSecret"`
+}
diff --git a/settings/settings.go b/settings/settings.go
index 22908396..1ee0b1c1 100644
--- a/settings/settings.go
+++ b/settings/settings.go
@@ -27,6 +27,7 @@ type Settings struct {
Commands map[string][]string `json:"commands"`
Shell []string `json:"shell"`
Rules []rules.Rule `json:"rules"`
+ OnlyOffice OnlyOffice `json:"onlyoffice"`
}
// GetRules implements rules.Provider.
diff --git a/test.docx b/test.docx
new file mode 100644
index 0000000000000000000000000000000000000000..adf9a8c004ab9f4965602b5c8cf748e335541a6e
GIT binary patch
literal 24698
zcmcG$1zcRsl0H0xy99#EAi>>&OK{iV?he5T?k+(S2o3=P1Pd129fAiZ1PSgC{C_6z
zeedpl_wMf9Z@;B}Gce~&S3OnL-KYEMrd8x&;c!8J{7`_Vz{r^Fh#(O3g#%pF6L)fS
zvv71X((rb+a5Z4@a
?~}Q5DP~$CvzJ|Dln_@p
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
literal 0
HcmV?d00001