Doceditor: refactoring

This commit is contained in:
Timofey Boyko 2024-02-19 11:40:07 +03:00
parent a9e9eb0c4e
commit b0322cd68b
12 changed files with 1363 additions and 121 deletions

View File

@ -1,122 +1,225 @@
"use client";
import React from "react";
import { ThemeProvider } from "styled-components";
import { I18nextProvider } from "react-i18next";
import { i18n } from "i18next";
import { isMobile } from "react-device-detect";
import { DocumentEditor } from "@onlyoffice/document-editor-react";
import IConfig from "@onlyoffice/document-editor-react/dist/esm/model/config";
import IConfig from "@onlyoffice/document-editor-react/dist/esm/types/model/config";
import { EditorProps, TGoBack } from "@/types";
import { EditorProps } from "@/types";
import useSocketHelper from "@/hooks/useSocketHelper";
import useSelectFolderDialog from "@/hooks/useSelectFolderDialog";
import useSelectFileDialog from "@/hooks/useSelectFileDialog";
import useTheme from "@/hooks/useTheme";
import useI18N from "@/hooks/useI18N";
import useInit from "@/hooks/useInit";
import useEditorEvents from "@/hooks/useEditorEvents";
import SelectFolderDialog from "./SelectFolderDialog";
import SelectFileDialog from "./SelectFileDialog";
import { FolderType } from "@docspace/shared/enums";
import { getBackUrl, getIsZoom } from "@/utils";
import { IS_DESKTOP_EDITOR } from "@/utils/constants";
import {
onSDKRequestHistoryClose,
onSDKRequestEditRights,
onSDKInfo,
onSDKWarning,
onSDKError,
onSDKRequestRename,
} from "@/utils/events";
import { getEditorTheme } from "@docspace/shared/utils";
const Editor = ({
config,
editorUrl,
settings,
successAuth,
user,
view,
doc,
documentserverUrl,
fileInfo,
t,
onSDKRequestSaveAs,
onSDKRequestInsertImage,
onSDKRequestSelectSpreadsheet,
onSDKRequestSelectDocument,
onSDKRequestReferenceSource,
}: EditorProps) => {
const fileInfo = config?.file;
const documentserverUrl = editorUrl.docServiceUrl;
const instanceId = config?.document?.referenceData.instanceId;
const { i18n } = useI18N({ settings, user });
const { theme } = useTheme({ user });
const { socketHelper } = useSocketHelper({ socketUrl: settings.socketUrl });
const {
onSDKRequestSaveAs,
onCloseSelectFolderDialog,
onSubmitSelectFolderDialog,
getIsDisabledSelectFolderDialog,
isVisibleSelectFolderDialog,
titleSelectorFolderDialog,
} = useSelectFolderDialog({});
const {
onSDKRequestInsertImage,
onSDKRequestReferenceSource,
onSDKRequestSelectDocument,
onSDKRequestSelectSpreadsheet,
onCloseSelectFileDialog,
onSubmitSelectFileDialog,
getIsDisabledSelectFileDialog,
onDocumentReady,
onSDKRequestOpen,
onSDKRequestReferenceData,
onSDKAppReady,
onSDKRequestClose,
onSDKRequestCreateNew,
onSDKRequestHistory,
onSDKRequestUsers,
onSDKRequestSendNotify,
onSDKRequestRestore,
onSDKRequestHistoryData,
onDocumentStateChange,
onMetaChange,
onMakeActionLink,
selectFileDialogFileTypeDetection,
createUrl,
documentReady,
usersInRoom,
setDocTitle,
} = useEditorEvents({
user,
successAuth,
fileInfo,
config,
doc,
t,
});
selectFileDialogVisible,
} = useSelectFileDialog({ instanceId });
const onDocumentReady = (): void => {
throw new Error("Function not implemented.");
};
useInit({
config,
successAuth,
fileInfo,
user,
documentReady,
setDocTitle,
t,
});
const newConfig: IConfig = {
document: config.document,
documentType: config.documentType,
editorConfig: config.editorConfig,
token: config.token,
type: config.type,
events: {},
};
if (successAuth && newConfig.events) {
newConfig.editorConfig = { ...config.editorConfig };
if (view && newConfig.editorConfig) newConfig.editorConfig.mode = "view";
if (isMobile) config.type = "mobile";
let goBack: TGoBack = {} as TGoBack;
if (fileInfo) {
const search = typeof window !== "undefined" ? window.location.search : "";
const editorGoBack = new URLSearchParams(search).get("editorGoBack");
if (editorGoBack === "false") {
} else if (editorGoBack === "event") {
goBack = {
requestClose: true,
text: t?.("FileLocation"),
};
} else {
goBack = {
requestClose:
typeof window !== "undefined"
? window.DocSpaceConfig?.editor?.requestClose ?? false
: false,
text: t?.("FileLocation"),
};
if (
typeof window !== "undefined" &&
!window.DocSpaceConfig?.editor?.requestClose
) {
goBack.blank =
typeof window !== "undefined"
? window.DocSpaceConfig?.editor?.openOnNewPage ?? true
: false;
goBack.url = getBackUrl(fileInfo.rootFolderId, fileInfo.folderId);
}
}
}
if (newConfig.editorConfig)
newConfig.editorConfig.customization = {
...newConfig.editorConfig.customization,
goback: { ...goBack },
uiTheme: getEditorTheme(user?.theme),
};
if (newConfig.document && newConfig.document.info)
newConfig.document.info.favorite = false;
// const url = window.location.href;
// if (url.indexOf("anchor") !== -1) {
// const splitUrl = url.split("anchor=");
// const decodeURI = decodeURIComponent(splitUrl[1]);
// const obj = JSON.parse(decodeURI);
// config.editorConfig.actionLink = {
// action: obj.action,
// };
// }
newConfig.events = {
onDocumentReady,
onRequestHistoryClose: onSDKRequestHistoryClose,
onRequestEditRights: () => onSDKRequestEditRights(fileInfo),
onAppReady: onSDKAppReady,
onInfo: onSDKInfo,
onWarning: onSDKWarning,
onError: onSDKError,
onRequestHistoryData: onSDKRequestHistoryData,
onDocumentStateChange,
onMetaChange,
onMakeActionLink,
};
if (successAuth) {
if (fileInfo?.rootFolderType !== FolderType.USER) {
//TODO: remove condition for share in my
newConfig.events.onRequestUsers = onSDKRequestUsers;
newConfig.events.onRequestSendNotify = onSDKRequestSendNotify;
}
if (!user.isVisitor) {
newConfig.events.onRequestSaveAs = onSDKRequestSaveAs;
if (
IS_DESKTOP_EDITOR ||
(typeof window !== "undefined" &&
window.DocSpaceConfig?.editor?.openOnNewPage === false)
) {
newConfig.events.onRequestCreateNew = onSDKRequestCreateNew;
}
}
newConfig.events.onRequestInsertImage = onSDKRequestInsertImage;
// restore for 1.4 editor version
// newConfig.events.onRequestSelectSpreadsheet = onSDKRequestSelectSpreadsheet;
// newConfig.events.onRequestSelectDocument = onSDKRequestSelectDocument;
// newConfig.events.onRequestReferenceSource = onSDKRequestReferenceSource;
if (!user.isVisitor) newConfig.events.onRequestSaveAs = onSDKRequestSaveAs;
newConfig.events.onRequestSelectSpreadsheet = onSDKRequestSelectSpreadsheet;
newConfig.events.onRequestSelectDocument = onSDKRequestSelectDocument;
newConfig.events.onRequestReferenceSource = onSDKRequestReferenceSource;
}
if (!fileInfo.providerKey) {
newConfig.events.onRequestReferenceData = onSDKRequestReferenceData;
const isZoom = getIsZoom();
if (!isZoom) {
newConfig.events.onRequestOpen = onSDKRequestOpen;
}
}
if (fileInfo.security.Rename) {
newConfig.events.onRequestRename = (obj: object) =>
onSDKRequestRename(obj, fileInfo.id);
}
if (fileInfo.security.ReadHistory) {
newConfig.events.onRequestHistory = onSDKRequestHistory;
}
if (fileInfo.security.EditHistory) {
newConfig.events.onRequestRestore = onSDKRequestRestore;
}
if (
typeof window !== "undefined" &&
window.DocSpaceConfig?.editor?.requestClose
) {
newConfig.events.onRequestClose = onSDKRequestClose;
}
return (
<>
{" "}
<DocumentEditor
id={"docspace_editor"}
documentServerUrl={documentserverUrl}
config={newConfig}
height="100%"
width="100%"
events_onDocumentReady={onDocumentReady}
/>
{theme && i18n && (
<I18nextProvider i18n={i18n}>
<ThemeProvider theme={theme}>
{isVisibleSelectFolderDialog && !!socketHelper && (
<SelectFolderDialog
socketHelper={socketHelper}
isVisible={isVisibleSelectFolderDialog}
onSubmit={onSubmitSelectFolderDialog}
onClose={onCloseSelectFolderDialog}
titleSelectorFolder={titleSelectorFolderDialog}
fileInfo={fileInfo}
getIsDisabled={getIsDisabledSelectFolderDialog}
/>
)}
{selectFileDialogVisible && !!socketHelper && (
<SelectFileDialog
socketHelper={socketHelper}
isVisible={selectFileDialogVisible}
onSubmit={onSubmitSelectFileDialog}
onClose={onCloseSelectFileDialog}
getIsDisabled={getIsDisabledSelectFileDialog}
fileTypeDetection={selectFileDialogFileTypeDetection}
fileInfo={fileInfo}
/>
)}
</ThemeProvider>
</I18nextProvider>
)}
</>
<DocumentEditor
id={"docspace_editor"}
documentServerUrl={documentserverUrl}
config={newConfig}
height="100%"
width="100%"
events_onDocumentReady={onDocumentReady}
/>
);
};

View File

@ -0,0 +1,186 @@
"use client";
import React from "react";
import { I18nextProvider } from "react-i18next";
import { toast } from "react-toastify";
import { Toast } from "@docspace/shared/components/toast";
import { TFile } from "@docspace/shared/api/files/types";
import { ThemeProvider } from "@docspace/shared/components/theme-provider";
import ErrorBoundary from "@docspace/shared/components/error-boundary/ErrorBoundary";
import ErrorContainer from "@docspace/shared/components/error-container/ErrorContainer";
import FirebaseHelper from "@docspace/shared/utils/firebase";
import { TFirebaseSettings } from "@docspace/shared/api/settings/types";
import { TUser } from "@docspace/shared/api/people/types";
import { TResponse } from "@/types";
import useError from "@/hooks/useError";
import useI18N from "@/hooks/useI18N";
import useTheme from "@/hooks/useTheme";
import useDeviceType from "@/hooks/useDeviceType";
import useWhiteLabel from "@/hooks/useWhiteLabel";
import useRootInit from "@/hooks/useRootInit";
import useDeepLink from "@/hooks/useDeepLink";
import useSelectFileDialog from "@/hooks/useSelectFileDialog";
import useSelectFolderDialog from "@/hooks/useSelectFolderDialog";
import useSocketHelper from "@/hooks/useSocketHelper";
import pkgFile from "../../package.json";
import DeepLink from "./deep-link";
import SelectFileDialog from "./SelectFileDialog";
import SelectFolderDialog from "./SelectFolderDialog";
import Editor from "./Editor";
import { IS_VIEW } from "@/utils/constants";
toast.configure();
const Root = ({
settings,
config,
successAuth,
user,
error,
isSharingAccess,
editorUrl,
doc,
}: TResponse) => {
const documentserverUrl = editorUrl?.docServiceUrl;
const fileInfo = config?.file;
const firebaseHelper = new FirebaseHelper(
settings?.firebase ?? ({} as TFirebaseSettings),
);
const instanceId = config?.document?.referenceData.instanceId;
useRootInit({
documentType: config?.documentType,
fileType: config?.file.fileType,
});
const { i18n } = useI18N({ settings, user });
const t = i18n.t ? i18n.t.bind(i18n) : null;
const { onError, getErrorMessage } = useError({
error,
editorUrl: documentserverUrl,
t,
});
const { theme, currentColorTheme } = useTheme({ user });
const { currentDeviceType } = useDeviceType();
const { logoUrls } = useWhiteLabel();
const { isShowDeepLink, setIsShowDeepLink } = useDeepLink({
settings,
fileInfo,
email: user?.email,
});
const { socketHelper } = useSocketHelper({
socketUrl: settings?.socketUrl ?? "",
});
const {
onSDKRequestSaveAs,
onCloseSelectFolderDialog,
onSubmitSelectFolderDialog,
getIsDisabledSelectFolderDialog,
isVisibleSelectFolderDialog,
titleSelectorFolderDialog,
} = useSelectFolderDialog({});
const {
onSDKRequestInsertImage,
onSDKRequestReferenceSource,
onSDKRequestSelectDocument,
onSDKRequestSelectSpreadsheet,
onCloseSelectFileDialog,
onSubmitSelectFileDialog,
getIsDisabledSelectFileDialog,
selectFileDialogFileTypeDetection,
selectFileDialogVisible,
} = useSelectFileDialog({ instanceId: instanceId ?? "" });
return (
<I18nextProvider i18n={i18n}>
<ThemeProvider theme={theme} currentColorScheme={currentColorTheme}>
<ErrorBoundary
user={user ?? ({} as TUser)}
version={pkgFile.version}
firebaseHelper={firebaseHelper}
currentDeviceType={currentDeviceType}
whiteLabelLogoUrls={logoUrls}
isNextJS
theme={theme}
i18n={i18n}
onError={onError}
>
{isShowDeepLink ? (
<DeepLink
fileInfo={fileInfo}
logoUrls={logoUrls}
userEmail={user?.email}
theme={theme}
currentDeviceType={currentDeviceType}
deepLinkConfig={settings?.deepLink}
setIsShowDeepLink={setIsShowDeepLink}
/>
) : error && error.message !== "unauthorized" ? (
<ErrorContainer
headerText={t?.("Common:Error")}
customizedBodyText={getErrorMessage()}
isEditor
/>
) : (
<>
{config && user && documentserverUrl && fileInfo && (
<Editor
config={config}
user={user}
view={IS_VIEW}
successAuth={successAuth}
doc={doc}
t={t}
documentserverUrl={documentserverUrl}
fileInfo={fileInfo}
onSDKRequestSaveAs={onSDKRequestSaveAs}
onSDKRequestInsertImage={onSDKRequestInsertImage}
onSDKRequestReferenceSource={onSDKRequestReferenceSource}
onSDKRequestSelectDocument={onSDKRequestSelectDocument}
onSDKRequestSelectSpreadsheet={onSDKRequestSelectSpreadsheet}
/>
)}
<Toast />
{isVisibleSelectFolderDialog && !!socketHelper && (
<SelectFolderDialog
socketHelper={socketHelper}
isVisible={isVisibleSelectFolderDialog}
onSubmit={onSubmitSelectFolderDialog}
onClose={onCloseSelectFolderDialog}
titleSelectorFolder={titleSelectorFolderDialog}
fileInfo={fileInfo ?? ({} as TFile)}
getIsDisabled={getIsDisabledSelectFolderDialog}
t={t}
i18n={i18n}
/>
)}
{selectFileDialogVisible && !!socketHelper && (
<SelectFileDialog
socketHelper={socketHelper}
isVisible={selectFileDialogVisible}
onSubmit={onSubmitSelectFileDialog}
onClose={onCloseSelectFileDialog}
getIsDisabled={getIsDisabledSelectFileDialog}
fileTypeDetection={selectFileDialogFileTypeDetection}
fileInfo={fileInfo ?? ({} as TFile)}
t={t}
i18n={i18n}
/>
)}
</>
)}
</ErrorBoundary>
</ThemeProvider>
</I18nextProvider>
);
};
export default Root;

View File

@ -1,11 +1,10 @@
import React from "react";
import { useTranslation } from "react-i18next";
import FilesSelectorWrapper from "@docspace/shared/selectors/Files/FilesSelector.wrapper";
import { DeviceType, FilesSelectorFilterTypes } from "@docspace/shared/enums";
import { SelectFileDialogProps } from "@/types";
import { useTheme } from "styled-components";
const SelectFileDialog = ({
socketHelper,
@ -15,23 +14,23 @@ const SelectFileDialog = ({
onClose,
onSubmit,
fileInfo,
t,
i18n,
}: SelectFileDialogProps) => {
const { t, i18n } = useTranslation(["Common", "Editor"]);
const sessionPath = sessionStorage.getItem("filesSelectorPath");
const headerLabel = fileTypeDetection.filterParam
? t("Common:SelectFile")
: t("Common:SelectAction");
? t?.("Common:SelectFile") ?? ""
: t?.("Common:SelectAction") ?? "";
const getFileTypeTranslation = React.useCallback(() => {
switch (fileTypeDetection.filterParam) {
case FilesSelectorFilterTypes.XLSX:
return t("Editor:MailMergeFileType");
return t?.("Editor:MailMergeFileType") ?? "";
case FilesSelectorFilterTypes.IMG:
return t("Editor:ImageFileType");
return t?.("Editor:ImageFileType") ?? "";
case FilesSelectorFilterTypes.DOCX:
return t("Editor:DocumentsFileType");
return t?.("Editor:DocumentsFileType") ?? "";
default:
return "";
}
@ -41,16 +40,13 @@ const SelectFileDialog = ({
const type = getFileTypeTranslation();
return fileTypeDetection.filterParam === FilesSelectorFilterTypes.XLSX
? type
: t("Editor:SelectFilesType", { fileType: type });
: t?.("Editor:SelectFilesType", { fileType: type }) ?? "";
}, [fileTypeDetection.filterParam, getFileTypeTranslation, t]);
const listTitle = selectFilesListTitle();
const theme = useTheme();
return (
<FilesSelectorWrapper
theme={theme}
i18nProp={i18n}
withoutBackButton
withSearch
@ -73,8 +69,8 @@ const SelectFileDialog = ({
footerCheckboxLabel=""
footerInputHeader=""
currentFooterInputValue=""
submitButtonLabel={t("Common:SelectAction")}
cancelButtonLabel={t("Common:CancelButton")}
submitButtonLabel={t?.("Common:SelectAction") ?? ""}
cancelButtonLabel={t?.("Common:CancelButton") ?? ""}
withCancelButton
descriptionText={listTitle}
currentDeviceType={DeviceType.desktop}

View File

@ -8,7 +8,6 @@ import { DeviceType } from "@docspace/shared/enums";
import { SelectFolderDialogProps } from "@/types";
import { TSelectorCancelButton } from "@docspace/shared/components/selector/Selector.types";
import { useTheme } from "styled-components";
const SelectFolderDialog = ({
socketHelper,
@ -18,23 +17,20 @@ const SelectFolderDialog = ({
titleSelectorFolder,
fileInfo,
getIsDisabled,
t,
i18n,
}: SelectFolderDialogProps) => {
const { t, i18n } = useTranslation(["Common", "Editor"]);
const sessionPath = sessionStorage.getItem("filesSelectorPath");
const cancelButtonProps: TSelectorCancelButton = {
withCancelButton: true,
onCancel: onClose,
cancelButtonLabel: t("CancelButton"),
cancelButtonLabel: t?.("Common:CancelButton") ?? "",
cancelButtonId: "select-file-modal-cancel",
};
const theme = useTheme();
return (
<FilesSelectorWrapper
theme={theme}
i18nProp={i18n}
{...cancelButtonProps}
withHeader
@ -42,16 +38,16 @@ const SelectFolderDialog = ({
withSearch
withoutBackButton
withCancelButton
headerLabel={t("SaveButton")}
headerLabel={i18n.t?.("Common:SaveButton") ?? ""}
disabledItems={[]}
onSubmit={onSubmit}
submitButtonLabel={t("SaveHereButton")}
submitButtonLabel={i18n.t?.("Common:SaveHereButton") ?? ""}
submitButtonId="select-file-modal-submit"
socketHelper={socketHelper}
socketSubscribers={socketHelper.socketSubscribers}
footerInputHeader={t("Editor:FileName")}
footerInputHeader={i18n.t?.("Editor:FileName") ?? ""}
currentFooterInputValue={titleSelectorFolder}
footerCheckboxLabel={t("Editor:OpenSavedDocument")}
footerCheckboxLabel={i18n.t?.("Editor:OpenSavedDocument") ?? ""}
isPanelVisible={isVisible}
isRoomsOnly={false}
isThirdParty={false}

View File

@ -0,0 +1,605 @@
import React from "react";
import IConfig from "@onlyoffice/document-editor-react/dist/esm/types/model/config";
import {
createFile,
getEditDiff,
getEditHistory,
getProtectUsers,
getReferenceData,
getSharedUsers,
restoreDocumentsVersion,
sendEditorNotify,
} from "@docspace/shared/api/files";
import {
TEditHistory,
TGetReferenceData,
} from "@docspace/shared/api/files/types";
import { TUser } from "@docspace/shared/api/people/types";
import { EDITOR_ID } from "@docspace/shared/constants";
import {
assign,
frameCallCommand,
frameCallEvent,
} from "@docspace/shared/utils/common";
import { combineUrl } from "@docspace/shared/utils/combineUrl";
import { FolderType } from "@docspace/shared/enums";
import { toastr } from "@docspace/shared/components/toast";
import { TData } from "@docspace/shared/components/toast/Toast.type";
import { Nullable } from "@docspace/shared/types";
import { IS_DESKTOP_EDITOR } from "@/utils/constants";
import { getCurrentDocumentVersion, setDocumentTitle } from "@/utils";
import {
TCatchError,
TDocEditor,
TEvent,
THistoryData,
UseEventsProps,
} from "@/types";
type IConfigEvents = Pick<IConfig, "events">;
let docEditor: TDocEditor | null = null;
const useEditorEvents = ({
user,
successAuth,
fileInfo,
config,
doc,
t,
}: UseEventsProps) => {
const [events, setEvents] = React.useState<IConfigEvents>({});
const [documentReady, setDocumentReady] = React.useState(false);
const [createUrl, setCreateUrl] = React.useState<Nullable<string>>(null);
const [usersInRoom, setUsersInRoom] = React.useState<TUser[]>([]);
const [docTitle, setDocTitle] = React.useState("");
const [docSaved, setDocSaved] = React.useState(false);
const onSDKRequestReferenceData = React.useCallback(async (event: object) => {
const currEvent = event as TEvent;
const referenceData = await getReferenceData(
currEvent.data.referenceData ??
(currEvent.data as unknown as TGetReferenceData),
);
docEditor?.setReferenceData?.(referenceData);
}, []);
const onSDKRequestOpen = React.useCallback(
async (event: object) => {
const currEvent = event as TEvent;
const windowName = currEvent.data.windowName;
const reference = currEvent.data;
try {
const data = {
fileKey: reference.referenceData
? reference.referenceData.fileKey
: "",
instanceId: reference.referenceData
? reference.referenceData.instanceId
: "",
fileId: fileInfo.id,
path: reference.path || "",
};
const result = await getReferenceData(data);
if (result.error) throw new Error(result.error);
var link = result.link;
window.open(link, windowName);
} catch (e) {
var winEditor = window.open("", windowName);
winEditor?.close();
docEditor?.showMessage?.(
(e as { message?: string })?.message ??
t?.("ErrorConnectionLost") ??
"",
);
}
},
[fileInfo.id, t],
);
const onSDKAppReady = React.useCallback(() => {
docEditor = window.DocEditor.instances[EDITOR_ID];
console.log("ONLYOFFICE Document Editor is ready", docEditor);
const url = window.location.href;
const index = url.indexOf("#message/");
if (index > -1) {
const splitUrl = url.split("#message/");
if (splitUrl.length === 2) {
const message = decodeURIComponent(splitUrl[1]).replace(/\+/g, " ");
docEditor?.showMessage?.(message);
history.pushState({}, "", url.substring(0, index));
} else {
if (config?.Error) docEditor?.showMessage?.(config.Error);
}
}
}, [config.Error]);
const onDocumentReady = React.useCallback(() => {
// console.log("onDocumentReady", arguments, { docEditor });
setDocumentReady(true);
frameCallCommand("setIsLoaded");
if (config?.errorMessage) docEditor?.showMessage?.(config.errorMessage);
// if (config?.file?.canShare) {
// loadUsersRightsList(docEditor);
// }
if (docEditor)
assign(
window as unknown as { [key: string]: {} },
["ASC", "Files", "Editor", "docEditor"],
docEditor,
); //Do not remove: it's for Back button on Mobile App
}, [config.errorMessage]);
const getBackUrl = React.useCallback(() => {
if (!fileInfo) return;
const search = window.location.search;
const shareIndex = search.indexOf("share=");
const key = shareIndex > -1 ? search.substring(shareIndex + 6) : null;
let backUrl = "";
if (fileInfo.rootFolderType === FolderType.Rooms) {
if (key) {
backUrl = `/rooms/share?key=${key}`;
} else {
backUrl = `/rooms/shared/${fileInfo.folderId}/filter?folder=${fileInfo.folderId}`;
}
} else {
backUrl = `/rooms/personal/filter?folder=${fileInfo.folderId}`;
}
const url = window.location.href;
const origin = url.substring(0, url.indexOf("/doceditor"));
return `${combineUrl(origin, backUrl)}`;
}, [fileInfo]);
const onSDKRequestClose = React.useCallback(() => {
const search = window.location.search;
const editorGoBack = new URLSearchParams(search).get("editorGoBack");
if (editorGoBack === "event") {
frameCallEvent({ event: "onEditorCloseCallback" });
} else {
const backUrl = getBackUrl();
if (backUrl) window.location.replace(backUrl);
}
}, [getBackUrl]);
const getDefaultFileName = React.useCallback(
(withExt = false) => {
const documentType = config?.documentType;
const fileExt =
documentType === "word"
? "docx"
: documentType === "slide"
? "pptx"
: documentType === "cell"
? "xlsx"
: "docxf";
let fileName = t?.("Common:NewDocument");
switch (fileExt) {
case "xlsx":
fileName = t?.("Common:NewSpreadsheet");
break;
case "pptx":
fileName = t?.("Common:NewPresentation");
break;
case "docxf":
fileName = t?.("Common:NewMasterForm");
break;
default:
break;
}
if (withExt) {
fileName = `${fileName}.${fileExt}`;
}
return fileName;
},
[config?.documentType, t],
);
const onSDKRequestCreateNew = React.useCallback(() => {
const defaultFileName = getDefaultFileName(true);
createFile(fileInfo.folderId, defaultFileName ?? "")
?.then((newFile) => {
const newUrl = combineUrl(
window.DocSpaceConfig?.proxy?.url,
`/doceditor?fileId=${encodeURIComponent(newFile.id)}`,
);
window.open(
newUrl,
window.DocSpaceConfig?.editor?.openOnNewPage ? "_blank" : "_self",
);
})
.catch((e) => {
toastr.error(e);
});
}, [fileInfo.folderId, getDefaultFileName]);
const getDocumentHistory = React.useCallback(
(fileHistory: TEditHistory[], historyLength: number) => {
let result = [];
for (let i = 0; i < historyLength; i++) {
const changes = fileHistory[i].changes;
const serverVersion = fileHistory[i].serverVersion;
const version = fileHistory[i].version;
const versionGroup = fileHistory[i].versionGroup;
let obj = {
...(changes.length !== 0 && { changes }),
created: `${new Date(fileHistory[i].created).toLocaleString(
config.editorConfig.lang,
)}`,
...(serverVersion && { serverVersion }),
key: fileHistory[i].key,
user: {
id: fileHistory[i].user.id,
name: fileHistory[i].user.name,
},
version,
versionGroup,
};
result.push(obj);
}
return result;
},
[config.editorConfig.lang],
);
const onSDKRequestRestore = React.useCallback(
async (event: object) => {
const restoreVersion = (event as TEvent).data.version;
try {
const updateVersions = await restoreDocumentsVersion(
fileInfo.id,
restoreVersion ?? 0,
doc ?? "",
);
const historyLength = updateVersions.length;
docEditor?.refreshHistory?.({
currentVersion: getCurrentDocumentVersion(
updateVersions,
historyLength,
),
history: getDocumentHistory(updateVersions, historyLength),
});
} catch (error) {
let errorMessage = "";
const typedError = error as TCatchError;
if (typeof typedError === "object") {
errorMessage =
("response" in typedError &&
typedError?.response?.data?.error?.message) ||
("statusText" in typedError && typedError?.statusText) ||
("message" in typedError && typedError?.message) ||
"";
} else {
errorMessage = error as string;
}
docEditor?.refreshHistory?.({
error: `${errorMessage}`, //TODO: maybe need to display something else.
});
}
},
[doc, fileInfo.id, getDocumentHistory],
);
const onSDKRequestHistory = React.useCallback(async () => {
try {
// const search = window.location.search;
// const shareIndex = search.indexOf("share=");
// const requestToken =
// shareIndex > -1 ? search.substring(shareIndex + 6) : null;
// const docIdx = search.indexOf("doc=");
const fileHistory = await getEditHistory(fileInfo.id, doc ?? "");
const historyLength = fileHistory.length;
docEditor?.refreshHistory?.({
currentVersion: getCurrentDocumentVersion(fileHistory, historyLength),
history: getDocumentHistory(fileHistory, historyLength),
});
} catch (error) {
let errorMessage = "";
const typedError = error as TCatchError;
if (typeof typedError === "object") {
errorMessage =
("response" in typedError &&
typedError?.response?.data?.error?.message) ||
("statusText" in typedError && typedError?.statusText) ||
("message" in typedError && typedError?.message) ||
"";
} else {
errorMessage = error as string;
}
docEditor?.refreshHistory?.({
error: `${errorMessage}`, //TODO: maybe need to display something else.
});
}
}, [doc, fileInfo.id, getDocumentHistory]);
const onSDKRequestSendNotify = React.useCallback(
async (event: object) => {
const currEvent = event as TEvent;
const actionData = currEvent.data.actionLink;
const comment = currEvent.data.message;
const emails = currEvent.data.emails;
try {
await sendEditorNotify(
fileInfo.id,
actionData ?? "",
emails ?? [],
comment ?? "",
);
if (usersInRoom.length === 0) return;
const usersNotFound = emails?.filter((row) =>
usersInRoom.every((value) => {
return row !== value.email;
}),
);
usersNotFound &&
usersNotFound.length > 0 &&
docEditor?.showMessage?.(
t?.("UsersWithoutAccess", {
users: usersNotFound,
}) ?? "",
);
} catch (e) {
toastr.error(e as TData);
}
},
[fileInfo.id, t, usersInRoom],
);
const onSDKRequestUsers = React.useCallback(
async (event: object) => {
try {
const currEvent = event as TEvent;
const c = currEvent?.data?.c;
const users = await (c == "protect"
? getProtectUsers(fileInfo.id)
: getSharedUsers(fileInfo.id));
if (c !== "protect") {
const usersArray = users.map(
(item) =>
({
email: item.email,
name: item.name,
}) as unknown as TUser,
);
setUsersInRoom(usersArray);
}
docEditor?.setUsers?.({
c: c ?? "",
users,
});
} catch (e) {
docEditor?.showMessage?.(
((e as { message?: string })?.message ||
t?.("ErrorConnectionLost")) ??
"",
);
}
},
[fileInfo.id, t],
);
const onSDKRequestHistoryData = React.useCallback(
async (event: object) => {
const version = (event as { data: number }).data;
try {
// const search = window.location.search;
// const shareIndex = search.indexOf("share=");
// const requestToken =
// shareIndex > -1 ? search.substring(shareIndex + 6) : null;
const versionDifference = await getEditDiff(
fileInfo.id,
version,
doc ?? "",
// requestToken,
);
const changesUrl = versionDifference.changesUrl;
const previous = versionDifference.previous;
const token = versionDifference.token;
const obj: THistoryData = {
url: versionDifference.url,
version,
key: versionDifference.key,
fileType: versionDifference.fileType,
};
if (changesUrl) obj.changesUrl = changesUrl;
if (previous)
obj.previous = {
fileType: previous.fileType,
key: previous.key,
url: previous.url,
};
if (token) obj.token = token;
docEditor?.setHistoryData?.(obj);
} catch (error) {
let errorMessage = "";
const typedError = error as TCatchError;
if (typeof typedError === "object") {
errorMessage =
("response" in typedError &&
typedError?.response?.data?.error?.message) ||
("statusText" in typedError && typedError?.statusText) ||
("message" in typedError && typedError?.message) ||
"";
} else {
errorMessage = error as string;
}
docEditor?.setHistoryData?.({
error: `${errorMessage}`, //TODO: maybe need to display something else.
version,
});
}
},
[doc, fileInfo.id],
);
const onDocumentStateChange = React.useCallback(
(event: object) => {
if (!documentReady) return;
setDocSaved(!(event as { data: boolean }).data);
setTimeout(() => {
docSaved
? setDocumentTitle(
docTitle,
config.document.fileType,
documentReady,
successAuth,
setDocTitle,
)
: setDocumentTitle(
`*${docTitle}`,
config.document.fileType,
documentReady,
successAuth,
setDocTitle,
);
}, 500);
},
[config.document.fileType, docSaved, docTitle, documentReady, successAuth],
);
const onMetaChange = React.useCallback(
(event: object) => {
const newTitle = (event as { data: { title: string } }).data.title;
//const favorite = event.data.favorite;
if (newTitle && newTitle !== docTitle) {
setDocumentTitle(
newTitle,
config.document.fileType,
documentReady,
successAuth,
setDocTitle,
);
setDocTitle(newTitle);
}
},
[config.document.fileType, docTitle, documentReady, successAuth],
);
const onMakeActionLink = React.useCallback((event: object) => {
const url = window.location.href;
const actionData = (event as { data: {} }).data;
const link = generateLink(actionData);
const urlFormation = url.split("&anchor=")[0];
const linkFormation = `${urlFormation}&anchor=${link}`;
docEditor?.setActionLink?.(linkFormation);
}, []);
const generateLink = (actionData: {}) => {
return encodeURIComponent(JSON.stringify(actionData));
};
React.useEffect(() => {
const tempEvents: IConfigEvents = {};
setEvents(tempEvents);
}, [successAuth, user.isVisitor, config?.documentType, fileInfo]);
React.useEffect(() => {
if (
IS_DESKTOP_EDITOR ||
(typeof window !== "undefined" &&
window.DocSpaceConfig?.editor?.openOnNewPage === false)
)
return;
//FireFox security issue fix (onRequestCreateNew will be blocked)
const documentType = config?.documentType || "word";
const defaultFileName = getDefaultFileName();
const url = new URL(
combineUrl(
window.location.origin,
window.DocSpaceConfig?.proxy?.url,
"/filehandler.ashx",
),
);
url.searchParams.append("action", "create");
url.searchParams.append("doctype", documentType);
url.searchParams.append("title", defaultFileName ?? "");
setCreateUrl(url.toString());
}, [config?.documentType, getDefaultFileName]);
return {
events,
createUrl,
documentReady,
usersInRoom,
onDocumentReady,
onSDKRequestOpen,
onSDKRequestReferenceData,
onSDKAppReady,
onSDKRequestClose,
onSDKRequestCreateNew,
onSDKRequestHistory,
onSDKRequestUsers,
onSDKRequestSendNotify,
onSDKRequestRestore,
onSDKRequestHistoryData,
onDocumentStateChange,
onMetaChange,
onMakeActionLink,
setDocTitle,
};
};
export default useEditorEvents;

View File

@ -0,0 +1,85 @@
import React from "react";
import { isIOS, deviceType } from "react-device-detect";
import { FolderType } from "@docspace/shared/enums";
import { UseInitProps } from "@/types";
import { initForm, setDocumentTitle, showDocEditorMessage } from "@/utils";
import initDesktop from "@/utils/initDesktop";
import { IS_DESKTOP_EDITOR, IS_VIEW } from "@/utils/constants";
const useInit = ({
config,
successAuth,
fileInfo,
user,
t,
setDocTitle,
documentReady,
}: UseInitProps) => {
React.useEffect(() => {
if (isIOS && deviceType === "tablet") {
const vh = window.innerHeight * 0.01;
document.documentElement.style.setProperty("--vh", `${vh}px`);
}
}, []);
React.useEffect(() => {
if (
!IS_VIEW &&
fileInfo &&
fileInfo.viewAccessibility.WebRestrictedEditing &&
fileInfo.security.FillForms &&
fileInfo.rootFolderType === FolderType.Rooms &&
!fileInfo.security.Edit &&
!config.document.isLinkedForMe
) {
try {
initForm(fileInfo.id);
} catch (err) {
console.error(err);
}
}
}, [config.document.isLinkedForMe, fileInfo]);
React.useEffect(() => {
if (!config) return;
setDocumentTitle(
config.document.title,
config.document.fileType,
documentReady,
successAuth,
setDocTitle,
);
}, [config, documentReady, fileInfo, setDocTitle, successAuth]);
React.useEffect(() => {
if (config && IS_DESKTOP_EDITOR) {
initDesktop(config, user, fileInfo.id, t);
}
}, [config, fileInfo.id, t, user]);
React.useEffect(() => {
try {
const url = window.location.href;
if (
successAuth &&
url.indexOf("#message/") > -1 &&
fileInfo &&
fileInfo?.fileExst &&
fileInfo?.viewAccessibility?.MustConvert &&
fileInfo?.security?.Convert
) {
showDocEditorMessage(url, fileInfo.id);
}
} catch (err) {
console.error(err);
}
}, [fileInfo, successAuth]);
return {};
};
export default useInit;

View File

@ -25,9 +25,10 @@ const useSelectFolderDialog = ({}: UseSelectFolderDialogProps) => {
const onSDKRequestSaveAs = useCallback((event: object) => {
if ("data" in event) {
const data = event.data as TEventData;
setTitle(data.title);
setUrl(data.url);
setExtension(data.fileType);
setTitle(data.title ?? "");
setUrl(data.url ?? "");
setExtension(data.fileType ?? "");
setIsVisible(true);
}

View File

@ -15,7 +15,7 @@ const useWhiteLabel = () => {
requestRunning.current = true;
const urls = await getLogoUrls();
requestRunning.current = false;
console.log("====", urls);
setLogoUrls(urls);
alreadyFetched.current = true;
}, []);

View File

@ -0,0 +1,12 @@
export const IZ_ZOOM =
typeof window !== "undefined" &&
(window?.navigator?.userAgent?.includes("ZoomWebKit") ||
window?.navigator?.userAgent?.includes("ZoomApps"));
export const IS_DESKTOP_EDITOR =
typeof window !== "undefined"
? window["AscDesktopEditor"] !== undefined
: false;
export const IS_VIEW =
typeof window !== "undefined"
? window.location.search.indexOf("action=view") !== -1
: false;

View File

@ -0,0 +1,82 @@
import { TFile } from "@docspace/shared/api/files/types";
import { frameCallCommand } from "@docspace/shared/utils/common";
import { convertDocumentUrl } from ".";
import { updateFile } from "@docspace/shared/api/files";
export type TInfoEvent = { data: { mode: string } };
export const onSDKInfo = (event: object) => {
const data = (event as TInfoEvent).data;
console.log("ONLYOFFICE Document Editor is opened in mode " + data.mode);
};
export type TWarningEvent = {
data: { warningCode: string; warningDescription: string };
};
export const onSDKWarning = (event: object) => {
const data = (event as TWarningEvent).data;
frameCallCommand("setIsLoaded");
console.log(
"ONLYOFFICE Document Editor reports a warning: code " +
data.warningCode +
", description " +
data.warningDescription,
);
};
export type TErrorEvent = {
data: { errorCode: string; errorDescription: string };
};
export const onSDKError = (event: object) => {
const data = (event as TErrorEvent).data;
frameCallCommand("setIsLoaded");
console.log(
"ONLYOFFICE Document Editor reports an error: code " +
data.errorCode +
", description " +
data.errorDescription,
);
};
export const onSDKRequestHistoryClose = () => {
document.location.reload();
};
export const onSDKRequestEditRights = async (fileInfo: TFile) => {
console.log("ONLYOFFICE Document Editor requests editing rights");
const url = window.location.href;
const index = url.indexOf("&action=view");
if (index) {
let convertUrl = url.substring(0, index);
if (
fileInfo?.viewAccessibility?.MustConvert &&
fileInfo?.security?.Convert
) {
const newUrl = await convertDocumentUrl(fileInfo.id);
if (newUrl) {
convertUrl = newUrl.webUrl;
}
}
history.pushState({}, "", convertUrl);
document.location.reload();
}
};
export type TRenameEvent = {
data: string;
};
export const onSDKRequestRename = async (
event: object,
id: string | number,
) => {
const title = (event as TRenameEvent).data;
await updateFile(id, title);
};

View File

@ -1,6 +1,61 @@
import { toUrlParams } from "@docspace/shared/utils/common";
import { combineUrl } from "@docspace/shared/utils/combineUrl";
import { request } from "@docspace/shared/api/client";
import { checkFillFormDraft, convertFile } from "@docspace/shared/api/files";
import { TEditHistory } from "@docspace/shared/api/files/types";
import { FolderType } from "@docspace/shared/enums";
export const getBackUrl = (
rootFolderType: FolderType,
folderId: string | number,
) => {
const search = window.location.search;
const shareIndex = search.indexOf("share=");
const key = shareIndex > -1 ? search.substring(shareIndex + 6) : null;
let backUrl = "";
if (rootFolderType === FolderType.Rooms) {
if (key) {
backUrl = `/rooms/share?key=${key}`;
} else {
backUrl = `/rooms/shared/${folderId}/filter?folder=${folderId}`;
}
} else {
backUrl = `/rooms/personal/filter?folder=${folderId}`;
}
const url = window.location.href;
const origin = url.substring(0, url.indexOf("/doceditor"));
return `${combineUrl(origin, backUrl)}`;
};
export const initForm = async (id: string | number) => {
const formUrl = await checkFillFormDraft(id);
history.pushState({}, "", formUrl);
document.location.reload();
};
export const showDocEditorMessage = async (
url: string,
id: string | number,
) => {
const result = await convertDocumentUrl(id);
const splitUrl = url.split("#message/");
if (result) {
const newUrl = `${result.webUrl}#message/${splitUrl[1]}`;
history.pushState({}, "", newUrl);
}
};
export const convertDocumentUrl = async (fileId: number | string) => {
const convert = await convertFile(fileId, null, true);
return convert && convert[0]?.result;
};
export const getDataSaveAs = async (params: string) => {
try {
@ -47,3 +102,76 @@ export const saveAs = (
window.open(handlerUrl, "_blank");
}
};
export const constructTitle = (
firstPart: string,
secondPart: string,
reverse = false,
) => {
return !reverse
? `${firstPart} - ${secondPart}`
: `${secondPart} - ${firstPart}`;
};
export const checkIfFirstSymbolInStringIsRtl = (str: string | null) => {
if (!str) return;
const rtlRegexp = new RegExp(
/[\u04c7-\u0591\u05D0-\u05EA\u05F0-\u05F4\u0600-\u06FF]/,
);
return rtlRegexp.test(str[0]);
};
export const setDocumentTitle = (
subTitle: string | null = null,
fileType: string,
documentReady: boolean,
successAuth: boolean,
callback?: (value: string) => void,
) => {
const organizationName = "ONLYOFFICE"; //TODO: Replace to API variant
const moduleTitle = "Documents"; //TODO: Replace to API variant
let newSubTitle = subTitle;
const isSubTitleRtl = checkIfFirstSymbolInStringIsRtl(subTitle);
// needs to reverse filename and extension for rtl mode
if (newSubTitle && fileType && isSubTitleRtl) {
newSubTitle = `${fileType}.${newSubTitle.replace(`.${fileType}`, "")}`;
}
let title;
if (newSubTitle) {
if (successAuth && moduleTitle) {
title = constructTitle(newSubTitle, moduleTitle, isSubTitleRtl);
} else {
title = constructTitle(newSubTitle, organizationName, isSubTitleRtl);
}
} else if (moduleTitle && organizationName) {
title = constructTitle(moduleTitle, organizationName);
} else {
title = organizationName;
}
if (documentReady) {
callback?.(title);
}
document.title = title;
};
export const getCurrentDocumentVersion = (
fileHistory: TEditHistory[],
historyLength: number,
) => {
return window.location.search.indexOf("&version=") !== -1
? +window.location.search.split("&version=")[1]
: fileHistory[historyLength - 1].version;
};
export const getIsZoom = () =>
typeof window !== "undefined" &&
(window?.navigator?.userAgent?.includes("ZoomWebKit") ||
window?.navigator?.userAgent?.includes("ZoomApps"));

View File

@ -0,0 +1,48 @@
import { IInitialConfig } from "@/types";
import {
setEncryptionKeys,
getEncryptionAccess,
} from "@docspace/shared/api/files";
import { TUser } from "@docspace/shared/api/people/types";
import { toastr } from "@docspace/shared/components/toast";
import { Nullable, TTranslation } from "@docspace/shared/types";
import { regDesktop } from "@docspace/shared/utils/desktop";
const initDesktop = (
cfg: IInitialConfig,
user: TUser,
fileId: string | number,
t: Nullable<TTranslation>,
) => {
const encryptionKeys = cfg?.editorConfig?.encryptionKeys;
regDesktop(
user,
!!encryptionKeys,
encryptionKeys,
(keys) => {
setEncryptionKeys(keys);
},
true,
(callback) => {
getEncryptionAccess?.(fileId)
?.then((keys) => {
var data = {
keys,
};
callback?.(data);
})
.catch((error) => {
toastr.error(
typeof error === "string" ? error : error.message,
"",
0,
true,
);
});
},
t,
);
};
export default initDesktop;