Merge pull request #322 from ONLYOFFICE/bugfix/doceditor-optimization

Bugfix/doceditor optimization
This commit is contained in:
Alexey Safronov 2024-04-04 14:18:43 +04:00 committed by GitHub
commit 14dbe3b022
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
39 changed files with 716 additions and 622 deletions

View File

@ -37,6 +37,8 @@ import SimulatePassword from "../../SimulatePassword";
import StyledComponent from "./StyledConvertPasswordDialog";
import config from "PACKAGE_FILE";
let _isMounted = false;
const ConvertPasswordDialogComponent = (props) => {
const {
t,

View File

@ -11,7 +11,7 @@
"deploy": "shx --silent mkdir -p ../../../publish/web/editor && shx --silent mkdir -p ../../../publish/web/editor/.next && shx --silent mkdir -p ../../../publish/web/editor/config && shx cp -r config/* ../../../publish/web/editor/config && shx --silent mkdir -p ../../../publish/web/editor/node_modules && shx --silent mkdir -p ../../../publish/web/editor/.next/static && shx cp -r .next/standalone/node_modules/* ../../../publish/web/editor/node_modules && shx cp -r .next/static/* ../../../publish/web/editor/.next/static && shx cp -r .next/standalone/packages/doceditor/.next/* ../../../publish/web/editor/.next && shx cp -f server.prod.js ../../../publish/web/editor/server.js"
},
"dependencies": {
"@onlyoffice/document-editor-react": "file:./onlyoffice-document-editor-react-1.5.1.tgz",
"@onlyoffice/document-editor-react": "^1.5.1",
"i18next": "^20.6.1",
"next": "14.0.4",
"react": "^18.2.0",

View File

@ -0,0 +1,33 @@
// (c) Copyright Ascensio System SIA 2009-2024
//
// This program is a free software product.
// You can redistribute it and/or modify it under the terms
// of the GNU Affero General Public License (AGPL) version 3 as published by the Free Software
// Foundation. In accordance with Section 7(a) of the GNU AGPL its Section 15 shall be amended
// to the effect that Ascensio System SIA expressly excludes the warranty of non-infringement of
// any third-party rights.
//
// This program is distributed WITHOUT ANY WARRANTY, without even the implied warranty
// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For details, see
// the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html
//
// You can contact Ascensio System SIA at Lubanas st. 125a-25, Riga, Latvia, EU, LV-1021.
//
// The interactive user interfaces in modified source and object code versions of the Program must
// display Appropriate Legal Notices, as required under Section 5 of the GNU AGPL version 3.
//
// Pursuant to Section 7(b) of the License you must retain the original Product logo when
// distributing the program. Pursuant to Section 7(e) we decline to grant you any rights under
// trademark law for use of our trademarks.
//
// All the Product's GUI elements, including illustrations and icon sets, as well as technical writing
// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
export default function CreateLayout({
children,
}: {
children: React.ReactNode;
}) {
return <>{children}</>;
}

View File

@ -24,23 +24,9 @@
// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
import React from "react";
import { i18n } from "i18next";
import { I18nextProvider } from "react-i18next";
import AppLoader from "@docspace/shared/components/app-loader";
import { ShareProps } from "./Share.types";
import Share from ".";
const FilesSelectorWrapper = ({
i18nProp,
...rest
}: ShareProps & { i18nProp: i18n }) => {
return (
<I18nextProvider i18n={i18nProp}>
<Share {...rest} />
</I18nextProvider>
);
};
export default FilesSelectorWrapper;
export default function Loading() {
// You can add any UI inside Loading, including a Skeleton.
return <AppLoader />;
}

View File

@ -24,11 +24,12 @@
// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
import { redirect } from "next/navigation";
import { permanentRedirect, redirect } from "next/navigation";
import { getBaseUrl } from "@docspace/shared/utils/next-ssr-helper";
import { createFile, fileCopyAs } from "@/utils/actions";
import CreateFileError from "@/components/CreateFileError";
import { getBaseUrl } from "@docspace/shared/utils/next-ssr-helper";
type TSearchParams = {
parentId: string;
@ -91,9 +92,8 @@ async function Page({ searchParams }: { searchParams: TSearchParams }) {
if (file?.id) fileId = file.id;
if (fileId || !fileError) {
const redirectURL = `${baseURL}/doceditor/?fileId=${fileId}`;
return redirect(redirectURL);
const redirectURL = `${baseURL}/doceditor?fileId=${fileId}`;
return permanentRedirect(redirectURL);
}
return (
@ -101,7 +101,6 @@ async function Page({ searchParams }: { searchParams: TSearchParams }) {
error={fileError}
fileInfo={fileInfo}
fromFile={!!fromFile}
fromTemplate={!!fromTemplate}
/>
);
}

View File

@ -24,64 +24,32 @@
// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
import "../styles/globals.scss";
import Script from "next/script";
import Scripts from "@/components/Scripts";
import StyledComponentsRegistry from "@/utils/registry";
// import StyledComponentsRegistry from "@/utils/registry";
import "../styles/globals.scss";
import Providers from "@/providers";
import { getSettings, getUser } from "@/utils/actions";
export default async function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
const [user, settings] = await Promise.all([getUser(), getSettings()]);
return (
<html lang="en">
<head>
<link id="favicon" rel="shortcut icon" type="image/x-icon" />
</head>
<body>
{children}
<Script
id="browser-detector"
src="/static/scripts/browserDetector.js"
/>
<Script id="docspace-config">
{`
console.log("It's DocEditor INIT");
fetch("/static/scripts/config.json")
.then((response) => {
if (!response.ok) {
throw new Error("HTTP error " + response.status);
}
return response.json();
})
.then((config) => {
window.DocSpaceConfig = {
...config,
};
if (
window.navigator.userAgent.includes("ZoomWebKit") ||
window.navigator.userAgent.includes("ZoomApps")
) {
window.DocSpaceConfig.editor = {
openOnNewPage: false,
requestClose: true,
};
}
//console.log({ DocSpaceConfig: window.DocSpaceConfig });
})
.catch((e) => {
console.error(e);
window.DocSpaceConfig = {
errorOnLoad: e,
};
});
`}
</Script>
<StyledComponentsRegistry>
<Providers contextData={{ user, settings }}>{children}</Providers>
</StyledComponentsRegistry>
<Scripts />
</body>
</html>
);
}

View File

@ -24,23 +24,9 @@
// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
import React from "react";
import { i18n } from "i18next";
import { I18nextProvider } from "react-i18next";
import AppLoader from "@docspace/shared/components/app-loader";
import { FilesSelectorProps } from "./FilesSelector.types";
import FilesSelector from ".";
const FilesSelectorWrapper = ({
i18nProp,
...rest
}: FilesSelectorProps & { i18nProp: i18n }) => {
return (
<I18nextProvider i18n={i18nProp}>
<FilesSelector {...rest} />
</I18nextProvider>
);
};
export default FilesSelectorWrapper;
export default function Loading() {
// You can add any UI inside Loading, including a Skeleton.
return <AppLoader />;
}

View File

@ -25,10 +25,16 @@
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
import type { Metadata } from "next";
import dynamic from "next/dynamic";
import AppLoader from "@docspace/shared/components/app-loader";
import { getData } from "@/utils/actions";
import Root from "@/components/Root";
const Root = dynamic(() => import("@/components/Root"), {
ssr: false,
loading: () => <AppLoader />,
});
export const metadata: Metadata = {
title: "Onlyoffice DocEditor page",
@ -41,26 +47,19 @@ async function Page({
}: {
searchParams?: { [key: string]: string };
}) {
const {
fileId,
fileid,
fileVersion: version,
doc,
action,
share,
editorType,
} = searchParams || {
fileId: undefined,
fileid: undefined,
fileVersion: undefined,
doc: undefined,
action: undefined,
share: undefined,
editorType: undefined,
};
const { fileId, fileid, version, doc, action, share, editorType } =
searchParams || {
fileId: undefined,
fileid: undefined,
version: undefined,
doc: undefined,
action: undefined,
share: undefined,
editorType: undefined,
};
const data = await getData(
fileId ?? fileid,
fileId ?? fileid ?? "",
version,
doc,
action === "view",
@ -72,4 +71,3 @@ async function Page({
}
export default Page;

View File

@ -27,55 +27,20 @@
"use client";
import React from "react";
import { toast } from "react-toastify";
import { Toast } from "@docspace/shared/components/toast";
import { ThemeProvider } from "@docspace/shared/components/theme-provider";
import { toastr } from "@docspace/shared/components/toast";
import { Error520SSR } from "@docspace/shared/components/errors/Error520";
import { TUser } from "@docspace/shared/api/people/types";
import {
TFirebaseSettings,
TSettings,
} from "@docspace/shared/api/settings/types";
import FirebaseHelper from "@docspace/shared/utils/firebase";
import useDeviceType from "@/hooks/useDeviceType";
import useWhiteLabel from "@/hooks/useWhiteLabel";
import useI18N from "@/hooks/useI18N";
import useTheme from "@/hooks/useTheme";
import pkg from "../../package.json";
type CreateFileErrorProps = {
error: Error;
fileInfo: object;
fromTemplate: boolean;
fromFile: boolean;
settings?: TSettings;
user?: TUser;
};
const CreateFileError = ({
error,
fileInfo,
fromFile,
fromTemplate,
settings,
user,
}: CreateFileErrorProps) => {
const firebaseHelper = new FirebaseHelper({} as TFirebaseSettings);
const { i18n } = useI18N({ settings, user });
const { currentDeviceType } = useDeviceType();
const { logoUrls } = useWhiteLabel();
const { theme } = useTheme({ user, i18n });
const t = i18n.t ? i18n.t.bind(i18n) : null;
const message = error.message ?? error ?? "";
const showingToast = React.useRef(false);
React.useEffect(() => {
if (fromFile && message.includes("password")) {
const searchParams = new URLSearchParams();
@ -85,30 +50,11 @@ const CreateFileError = ({
`${window.location.origin}?${searchParams.toString()}`,
);
} else {
if (!t || showingToast.current) return;
showingToast.current = true;
toastr.error(message, t?.("Common:Warning"));
throw new Error(message);
}
}, [fileInfo, fromFile, message, t]);
}, [fileInfo, fromFile, message]);
if (fromFile && message.includes("password")) return null;
return (
<ThemeProvider theme={theme}>
<Toast />
{logoUrls && (
<Error520SSR
i18nProp={i18n}
errorLog={error}
user={user ?? ({} as TUser)}
currentDeviceType={currentDeviceType}
version={pkg.version}
firebaseHelper={firebaseHelper}
whiteLabelLogoUrls={logoUrls}
/>
)}
</ThemeProvider>
);
return null;
};
export default CreateFileError;

View File

@ -28,6 +28,7 @@
import React from "react";
import { isMobile } from "react-device-detect";
import { useTranslation } from "react-i18next";
import { DocumentEditor } from "@onlyoffice/document-editor-react";
import IConfig from "@onlyoffice/document-editor-react/dist/esm/types/model/config";
@ -53,13 +54,13 @@ const Editor = ({
config,
successAuth,
user,
view,
doc,
documentserverUrl,
fileInfo,
isSharingAccess,
errorMessage,
t,
onSDKRequestSharingSettings,
onSDKRequestSaveAs,
onSDKRequestInsertImage,
@ -67,6 +68,8 @@ const Editor = ({
onSDKRequestSelectDocument,
onSDKRequestReferenceSource,
}: EditorProps) => {
const { t } = useTranslation(["Common", "Editor", "DeepLink"]);
const {
onDocumentReady,
onSDKRequestOpen,

View File

@ -28,15 +28,10 @@
import React from "react";
import useI18N from "@/hooks/useI18N";
import { Error404Wrapper } from "@docspace/shared/components/errors/Error404";
import Error404 from "@docspace/shared/components/errors/Error404";
const NotFoundError = ({}) => {
const { i18n } = useI18N({});
return <Error404Wrapper i18nProp={i18n} />;
return <Error404 />;
};
export default NotFoundError;

View File

@ -27,24 +27,16 @@
"use client";
import React from "react";
import { toast } from "react-toastify";
import { I18nextProvider } from "react-i18next";
import { useTranslation } from "react-i18next";
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 AppLoader from "@docspace/shared/components/app-loader";
import { Error520SSR } from "@docspace/shared/components/errors/Error520";
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";
@ -55,17 +47,12 @@ import useSocketHelper from "@/hooks/useSocketHelper";
import useShareDialog from "@/hooks/useShareDialog";
import useFilesSettings from "@/hooks/useFilesSettings";
import useUpdateSearchParamId from "@/hooks/useUpdateSearchParamId";
import { IS_VIEW } from "@/utils/constants";
import StyledComponentsRegistry from "@/utils/registry";
import pkgFile from "../../package.json";
import DeepLink from "./deep-link";
import Editor from "./Editor";
import SelectFileDialog from "./SelectFileDialog";
import SelectFolderDialog from "./SelectFolderDialog";
import SharingDialog from "./ShareDialog";
import { EditorConfigErrorType } from "@docspace/shared/enums";
const Root = ({
settings,
@ -74,34 +61,32 @@ const Root = ({
user,
error,
isSharingAccess,
editorUrl,
doc,
fileId,
hash,
}: TResponse) => {
const documentserverUrl = editorUrl?.docServiceUrl;
const documentserverUrl = config?.editorUrl ?? error?.editorUrl;
const fileInfo = config?.file;
const firebaseHelper = new FirebaseHelper(
settings?.firebase ?? ({} as TFirebaseSettings),
);
const instanceId = config?.document?.referenceData.instanceId;
const isSkipError =
error?.status === "not-found" ||
error?.status === "access-denied" ||
error?.status === "not-supported";
const { t } = useTranslation(["Editor", "Common"]);
useRootInit({
documentType: config?.documentType,
});
const { i18n } = useI18N({ settings, user });
const t = i18n.t ? i18n.t.bind(i18n) : null;
const { onError, getErrorMessage } = useError({
const { getErrorMessage } = useError({
error,
editorUrl: documentserverUrl,
t,
});
const { theme, currentColorTheme } = useTheme({ user, i18n });
const { currentDeviceType } = useDeviceType();
const { logoUrls } = useWhiteLabel();
const { isShowDeepLink, setIsShowDeepLink } = useDeepLink({
@ -118,6 +103,7 @@ const Root = ({
onCloseSelectFolderDialog,
onSubmitSelectFolderDialog,
getIsDisabledSelectFolderDialog,
isVisibleSelectFolderDialog,
titleSelectorFolderDialog,
extensionSelectorFolderDialog,
@ -132,125 +118,99 @@ const Root = ({
getIsDisabledSelectFileDialog,
selectFileDialogFileTypeDetection,
selectFileDialogVisible,
} = useSelectFileDialog({ instanceId: instanceId ?? "" });
const {
isSharingDialogVisible,
onCloseSharingDialog,
onSDKRequestSharingSettings,
} = useShareDialog();
useUpdateSearchParamId(fileId, hash);
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}
>
{!fileId || false ? (
<AppLoader />
) : isShowDeepLink ? (
<DeepLink
fileInfo={fileInfo}
logoUrls={logoUrls}
userEmail={user?.email}
theme={theme}
currentDeviceType={currentDeviceType}
deepLinkConfig={settings?.deepLink}
setIsShowDeepLink={setIsShowDeepLink}
/>
) : error && error.message === "restore-backup" && !isSkipError ? (
<StyledComponentsRegistry>
<ErrorContainer
headerText={t?.("Common:Error")}
customizedBodyText={getErrorMessage()}
isEditor
/>
</StyledComponentsRegistry>
) : error && error.message !== "unauthorized" && !isSkipError ? (
<Error520SSR
i18nProp={i18n}
errorLog={error as Error}
version={pkgFile.version}
user={user ?? ({} as TUser)}
whiteLabelLogoUrls={logoUrls}
firebaseHelper={firebaseHelper}
currentDeviceType={currentDeviceType}
/>
) : isShowDeepLink ? null : (
<div style={{ width: "100%", height: "100%" }}>
{documentserverUrl && (
<Editor
config={config}
user={user}
view={IS_VIEW}
successAuth={successAuth}
doc={doc}
isSharingAccess={isSharingAccess}
t={t}
documentserverUrl={documentserverUrl}
fileInfo={fileInfo}
errorMessage={error?.message}
onSDKRequestSharingSettings={onSDKRequestSharingSettings}
onSDKRequestSaveAs={onSDKRequestSaveAs}
onSDKRequestInsertImage={onSDKRequestInsertImage}
onSDKRequestReferenceSource={onSDKRequestReferenceSource}
onSDKRequestSelectDocument={onSDKRequestSelectDocument}
onSDKRequestSelectSpreadsheet={onSDKRequestSelectSpreadsheet}
/>
)}
<Toast isSSR />
{isVisibleSelectFolderDialog && !!socketHelper && (
<SelectFolderDialog
socketHelper={socketHelper}
isVisible={isVisibleSelectFolderDialog}
onSubmit={onSubmitSelectFolderDialog}
onClose={onCloseSelectFolderDialog}
titleSelectorFolder={titleSelectorFolderDialog}
fileInfo={fileInfo ?? ({} as TFile)}
getIsDisabled={getIsDisabledSelectFolderDialog}
i18n={i18n}
filesSettings={filesSettings}
fileSaveAsExtension={extensionSelectorFolderDialog}
/>
)}
{selectFileDialogVisible && !!socketHelper && (
<SelectFileDialog
socketHelper={socketHelper}
isVisible={selectFileDialogVisible}
onSubmit={onSubmitSelectFileDialog}
onClose={onCloseSelectFileDialog}
getIsDisabled={getIsDisabledSelectFileDialog}
fileTypeDetection={selectFileDialogFileTypeDetection}
fileInfo={fileInfo ?? ({} as TFile)}
i18n={i18n}
filesSettings={filesSettings}
/>
)}
{isSharingDialogVisible && !!socketHelper && fileInfo && (
<SharingDialog
isVisible={isSharingDialogVisible}
fileInfo={fileInfo}
onCancel={onCloseSharingDialog}
theme={theme}
i18n={i18n}
/>
)}
</div>
)}
</ErrorBoundary>
</ThemeProvider>
</I18nextProvider>
React.useEffect(() => {
if (
error &&
error.message !== "restore-backup" &&
error.message !== "unauthorized" &&
!isSkipError
) {
throw new Error(error.message);
}
}, [error, isSkipError]);
return !fileId ? (
<AppLoader />
) : isShowDeepLink ? (
<DeepLink
fileInfo={fileInfo}
logoUrls={logoUrls}
userEmail={user?.email}
currentDeviceType={currentDeviceType}
deepLinkConfig={settings?.deepLink}
setIsShowDeepLink={setIsShowDeepLink}
/>
) : error && error.message === "restore-backup" && !isSkipError ? (
<ErrorContainer
headerText={t("Common:Error")}
customizedBodyText={getErrorMessage()}
isEditor
/>
) : (
<div style={{ width: "100%", height: "100%" }}>
{documentserverUrl && (
<Editor
config={config}
user={user}
successAuth={successAuth}
doc={doc}
isSharingAccess={isSharingAccess}
documentserverUrl={documentserverUrl}
fileInfo={fileInfo}
errorMessage={error?.message}
onSDKRequestSharingSettings={onSDKRequestSharingSettings}
onSDKRequestSaveAs={onSDKRequestSaveAs}
onSDKRequestInsertImage={onSDKRequestInsertImage}
onSDKRequestReferenceSource={onSDKRequestReferenceSource}
onSDKRequestSelectDocument={onSDKRequestSelectDocument}
onSDKRequestSelectSpreadsheet={onSDKRequestSelectSpreadsheet}
/>
)}
{isVisibleSelectFolderDialog && !!socketHelper && fileInfo && (
<SelectFolderDialog
socketHelper={socketHelper}
isVisible={isVisibleSelectFolderDialog}
onSubmit={onSubmitSelectFolderDialog}
onClose={onCloseSelectFolderDialog}
titleSelectorFolder={titleSelectorFolderDialog}
fileInfo={fileInfo}
getIsDisabled={getIsDisabledSelectFolderDialog}
filesSettings={filesSettings}
fileSaveAsExtension={extensionSelectorFolderDialog}
/>
)}
{selectFileDialogVisible && !!socketHelper && fileInfo && (
<SelectFileDialog
socketHelper={socketHelper}
filesSettings={filesSettings}
isVisible={selectFileDialogVisible}
onSubmit={onSubmitSelectFileDialog}
onClose={onCloseSelectFileDialog}
getIsDisabled={getIsDisabledSelectFileDialog}
fileTypeDetection={selectFileDialogFileTypeDetection}
fileInfo={fileInfo}
/>
)}
{isSharingDialogVisible && !!socketHelper && fileInfo && (
<SharingDialog
isVisible={isSharingDialogVisible}
fileInfo={fileInfo}
onCancel={onCloseSharingDialog}
/>
)}
</div>
);
};

View File

@ -0,0 +1,72 @@
// (c) Copyright Ascensio System SIA 2009-2024
//
// This program is a free software product.
// You can redistribute it and/or modify it under the terms
// of the GNU Affero General Public License (AGPL) version 3 as published by the Free Software
// Foundation. In accordance with Section 7(a) of the GNU AGPL its Section 15 shall be amended
// to the effect that Ascensio System SIA expressly excludes the warranty of non-infringement of
// any third-party rights.
//
// This program is distributed WITHOUT ANY WARRANTY, without even the implied warranty
// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For details, see
// the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html
//
// You can contact Ascensio System SIA at Lubanas st. 125a-25, Riga, Latvia, EU, LV-1021.
//
// The interactive user interfaces in modified source and object code versions of the Program must
// display Appropriate Legal Notices, as required under Section 5 of the GNU AGPL version 3.
//
// Pursuant to Section 7(b) of the License you must retain the original Product logo when
// distributing the program. Pursuant to Section 7(e) we decline to grant you any rights under
// trademark law for use of our trademarks.
//
// All the Product's GUI elements, including illustrations and icon sets, as well as technical writing
// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
import Script from "next/script";
const Scripts = () => {
return (
<>
<Script id="browser-detector" src="/static/scripts/browserDetector.js" />
<Script id="docspace-config">
{`
console.log("It's DocEditor INIT");
fetch("/static/scripts/config.json")
.then((response) => {
if (!response.ok) {
throw new Error("HTTP error " + response.status);
}
return response.json();
})
.then((config) => {
window.DocSpaceConfig = {
...config,
};
if (
window.navigator.userAgent.includes("ZoomWebKit") ||
window.navigator.userAgent.includes("ZoomApps")
) {
window.DocSpaceConfig.editor = {
openOnNewPage: false,
requestClose: true,
};
}
//console.log({ DocSpaceConfig: window.DocSpaceConfig });
})
.catch((e) => {
console.error(e);
window.DocSpaceConfig = {
errorOnLoad: e,
};
});
`}
</Script>
</>
);
};
export default Scripts;

View File

@ -27,7 +27,7 @@
import React from "react";
import { useTranslation } from "react-i18next";
import FilesSelectorWrapper from "@docspace/shared/selectors/Files/FilesSelector.wrapper";
import FilesSelectorWrapper from "@docspace/shared/selectors/Files";
import { DeviceType, FilesSelectorFilterTypes } from "@docspace/shared/enums";
@ -42,7 +42,6 @@ const SelectFileDialog = ({
onSubmit,
fileInfo,
filesSettings,
i18n,
}: SelectFileDialogProps) => {
const { t } = useTranslation();
@ -79,7 +78,6 @@ const SelectFileDialog = ({
return (
<FilesSelectorWrapper
filesSettings={filesSettings}
i18nProp={i18n}
withoutBackButton
withSearch
withBreadCrumbs
@ -117,4 +115,3 @@ const SelectFileDialog = ({
};
export default SelectFileDialog;

View File

@ -29,7 +29,7 @@
import React from "react";
import { useTranslation } from "react-i18next";
import FilesSelectorWrapper from "@docspace/shared/selectors/Files/FilesSelector.wrapper";
import FilesSelectorWrapper from "@docspace/shared/selectors/Files";
import { DeviceType } from "@docspace/shared/enums";
import { SelectFolderDialogProps } from "@/types";
@ -44,16 +44,16 @@ const SelectFolderDialog = ({
fileInfo,
getIsDisabled,
filesSettings,
i18n,
fileSaveAsExtension,
}: SelectFolderDialogProps) => {
const { t } = useTranslation();
const { t } = useTranslation(["Common", "Editor"]);
const sessionPath = sessionStorage.getItem("filesSelectorPath");
const cancelButtonProps: TSelectorCancelButton = {
withCancelButton: true,
onCancel: onClose,
cancelButtonLabel: t?.("Common:CancelButton") ?? "",
cancelButtonLabel: t("Common:CancelButton"),
cancelButtonId: "select-file-modal-cancel",
};
@ -62,7 +62,6 @@ const SelectFolderDialog = ({
return (
<FilesSelectorWrapper
i18nProp={i18n}
filesSettings={filesSettings}
{...cancelButtonProps}
withHeader
@ -70,16 +69,16 @@ const SelectFolderDialog = ({
withSearch
withoutBackButton
withCancelButton
headerLabel={i18n.t?.("Common:SaveButton") ?? ""}
headerLabel={t("Common:SaveButton")}
disabledItems={[]}
onSubmit={onSubmit}
submitButtonLabel={i18n.t?.("Common:SaveHereButton") ?? ""}
submitButtonLabel={t("Common:SaveHereButton")}
submitButtonId="select-file-modal-submit"
socketHelper={socketHelper}
socketSubscribers={socketHelper.socketSubscribers}
footerInputHeader={i18n.t?.("Editor:FileName") ?? ""}
footerInputHeader={t("Editor:FileName")}
currentFooterInputValue={titleSelectorFolder}
footerCheckboxLabel={i18n.t?.("Editor:OpenSavedDocument") ?? ""}
footerCheckboxLabel={t("Editor:OpenSavedDocument")}
isPanelVisible={isVisible}
isRoomsOnly={false}
isThirdParty={false}
@ -98,4 +97,3 @@ const SelectFolderDialog = ({
};
export default SelectFolderDialog;

View File

@ -26,15 +26,14 @@
import React from "react";
import styled from "styled-components";
import { i18n } from "i18next";
import { useTranslation } from "react-i18next";
import Share from "@docspace/shared/components/share/Share.wrapper";
import Share from "@docspace/shared/components/share";
import { Backdrop } from "@docspace/shared/components/backdrop";
import { Aside } from "@docspace/shared/components/aside";
import { Text } from "@docspace/shared/components/text";
import { NoUserSelect } from "@docspace/shared/utils/commonStyles";
import { Base, TTheme } from "@docspace/shared/themes";
import { Base } from "@docspace/shared/themes";
import { TFile } from "@docspace/shared/api/files/types";
const StyledWrapper = styled.div`
@ -66,16 +65,12 @@ type SharingDialogProps = {
fileInfo: TFile;
onCancel: () => void;
isVisible: boolean;
theme: TTheme;
i18n: i18n;
};
const SharingDialog = ({
fileInfo,
onCancel,
isVisible,
theme,
i18n,
}: SharingDialogProps) => {
const { t } = useTranslation(["Common"]);
@ -85,17 +80,17 @@ const SharingDialog = ({
onClick={onCancel}
visible={isVisible}
zIndex={310}
isAside={true}
isAside
withoutBackground={false}
withoutBlur={false}
/>
<Aside visible={isVisible} onClose={onCancel} withoutBodyScroll>
<StyledWrapper theme={theme}>
<StyledWrapper>
<div className="share-file_header">
<Text className="share-file_heading">{t("Common:Share")}</Text>
</div>
<div className="share-file_body">
<Share infoPanelSelection={fileInfo} i18nProp={i18n} />
<Share infoPanelSelection={fileInfo} />
</div>
</StyledWrapper>
</Aside>
@ -104,4 +99,3 @@ const SharingDialog = ({
};
export default SharingDialog;

View File

@ -36,11 +36,8 @@ export interface DeepLinkProps {
logoUrls: TWhiteLabel[];
userEmail?: string;
theme: TTheme;
currentDeviceType: DeviceType;
deepLinkConfig?: TDeepLinkConfig;
setIsShowDeepLink: (value: boolean) => void;
}

View File

@ -27,6 +27,7 @@
/* eslint-disable @next/next/no-img-element */
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { useTheme } from "styled-components";
import { Text } from "@docspace/shared/components/text";
import { Checkbox } from "@docspace/shared/components/checkbox";
@ -38,7 +39,6 @@ import { getLogoFromPath } from "@docspace/shared/utils";
import { DeviceType } from "@docspace/shared/enums";
import { getDeepLink } from "./DeepLink.helper";
import {
StyledSimpleNav,
StyledDeepLink,
@ -55,12 +55,13 @@ const DeepLink = ({
fileInfo,
userEmail,
setIsShowDeepLink,
theme,
logoUrls,
currentDeviceType,
deepLinkConfig,
}: DeepLinkProps) => {
const { t } = useTranslation(["DeepLink", "Common"]);
const theme = useTheme();
const [isRemember, setIsRemember] = useState(false);
const onChangeCheckbox = () => {
@ -104,13 +105,13 @@ const DeepLink = ({
if (currentDeviceType === DeviceType.mobile) {
return (
<StyledSimpleNav theme={theme}>
<StyledSimpleNav>
<img src={logo} alt="" />
</StyledSimpleNav>
);
} else {
return (
<LogoWrapper theme={theme}>
<LogoWrapper>
<img src={logo} alt="docspace-logo" />
</LogoWrapper>
);
@ -128,7 +129,7 @@ const DeepLink = ({
<StyledDeepLink>
<StyledBodyWrapper>
<Text className="title">{t("DeepLink:OpeningDocument")}</Text>
<StyledFileTile theme={theme}>
<StyledFileTile>
<img src={getFileIcon()} alt="docspace-logo" />
<Text fontSize="14px" fontWeight="600" truncate>
{getFileTitle()}
@ -168,4 +169,3 @@ const DeepLink = ({
};
export default DeepLink;

View File

@ -127,7 +127,7 @@ const useEditorEvents = ({
winEditor?.close();
docEditor?.showMessage?.(
(e as { message?: string })?.message ??
t?.("ErrorConnectionLost") ??
t("ErrorConnectionLost") ??
"",
);
}
@ -232,17 +232,17 @@ const useEditorEvents = ({
? "xlsx"
: "docxf";
let fileName = t?.("Common:NewDocument");
let fileName = t("Common:NewDocument");
switch (fileExt) {
case "xlsx":
fileName = t?.("Common:NewSpreadsheet");
fileName = t("Common:NewSpreadsheet");
break;
case "pptx":
fileName = t?.("Common:NewPresentation");
fileName = t("Common:NewPresentation");
break;
case "docxf":
fileName = t?.("Common:NewMasterForm");
fileName = t("Common:NewMasterForm");
break;
default:
break;
@ -464,8 +464,7 @@ const useEditorEvents = ({
});
} catch (e) {
docEditor?.showMessage?.(
((e as { message?: string })?.message ||
t?.("ErrorConnectionLost")) ??
((e as { message?: string })?.message || t("ErrorConnectionLost")) ??
"",
);
}

View File

@ -28,14 +28,14 @@ import React from "react";
import { combineUrl } from "@docspace/shared/utils/combineUrl";
import { frameCallCommand } from "@docspace/shared/utils/common";
import { Nullable, TTranslation } from "@docspace/shared/types";
import { TTranslation } from "@docspace/shared/types";
import { TError } from "@/types";
interface UseErrorProps {
error?: TError;
editorUrl?: string;
t?: Nullable<TTranslation>;
t: TTranslation;
}
const useError = ({ error, editorUrl, t }: UseErrorProps) => {
@ -69,23 +69,15 @@ const useError = ({ error, editorUrl, t }: UseErrorProps) => {
}
}, [editorUrl, error]);
const onError = React.useCallback(() => {
// window.open(
// combineUrl(window.DocSpaceConfig?.proxy?.url, "/login"),
// "_self",
// );
}, []);
const getErrorMessage = React.useCallback(() => {
if (typeof error !== "string") return error?.message;
if (error === "restore-backup") return t?.("Common:PreparationPortalTitle");
if (error === "restore-backup") return t("Common:PreparationPortalTitle");
return error;
}, [error, t]);
return { onError, getErrorMessage };
return { getErrorMessage };
};
export default useError;

View File

@ -37,7 +37,10 @@ interface UseI18NProps {
}
const useI18N = ({ settings, user }: UseI18NProps) => {
const [i18n, setI18N] = React.useState<i18n>({} as i18n);
const [i18n, setI18N] = React.useState<i18n>(
getI18NInstance(user?.cultureName ?? "en", settings?.culture ?? "en") ??
({} as i18n),
);
const isInit = React.useRef(false);
@ -61,4 +64,3 @@ const useI18N = ({ settings, user }: UseI18NProps) => {
};
export default useI18N;

View File

@ -44,9 +44,16 @@ const useTheme = ({ user, i18n }: UseThemeProps) => {
const [currentColorTheme, setCurrentColorTheme] =
React.useState<TColorScheme>({} as TColorScheme);
const [theme, setTheme] = React.useState<TTheme>({
...Base,
currentColorScheme: currentColorTheme,
const [theme, setTheme] = React.useState<TTheme>(() => {
if (user?.theme === ThemeKeys.DarkStr)
return {
...Dark,
currentColorScheme: currentColorTheme,
};
return {
...Base,
currentColorScheme: currentColorTheme,
};
});
const isRequestRunning = React.useRef(false);

View File

@ -0,0 +1,68 @@
// (c) Copyright Ascensio System SIA 2009-2024
//
// This program is a free software product.
// You can redistribute it and/or modify it under the terms
// of the GNU Affero General Public License (AGPL) version 3 as published by the Free Software
// Foundation. In accordance with Section 7(a) of the GNU AGPL its Section 15 shall be amended
// to the effect that Ascensio System SIA expressly excludes the warranty of non-infringement of
// any third-party rights.
//
// This program is distributed WITHOUT ANY WARRANTY, without even the implied warranty
// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For details, see
// the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html
//
// You can contact Ascensio System SIA at Lubanas st. 125a-25, Riga, Latvia, EU, LV-1021.
//
// The interactive user interfaces in modified source and object code versions of the Program must
// display Appropriate Legal Notices, as required under Section 5 of the GNU AGPL version 3.
//
// Pursuant to Section 7(b) of the License you must retain the original Product logo when
// distributing the program. Pursuant to Section 7(e) we decline to grant you any rights under
// trademark law for use of our trademarks.
//
// All the Product's GUI elements, including illustrations and icon sets, as well as technical writing
// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
"use client";
import React from "react";
import ErrorBoundary from "@docspace/shared/components/error-boundary/ErrorBoundary";
import { TUser } from "@docspace/shared/api/people/types";
import {
TSettings,
TFirebaseSettings,
} from "@docspace/shared/api/settings/types";
import FirebaseHelper from "@docspace/shared/utils/firebase";
import pkgFile from "../../package.json";
import useDeviceType from "@/hooks/useDeviceType";
import useWhiteLabel from "@/hooks/useWhiteLabel";
type TErrorProvider = {
user: TUser | undefined;
settings: TSettings | undefined;
children: React.ReactNode;
};
const ErrorProvider = ({ children, user, settings }: TErrorProvider) => {
const firebaseHelper = new FirebaseHelper(
settings?.firebase ?? ({} as TFirebaseSettings),
);
const { currentDeviceType } = useDeviceType();
const { logoUrls } = useWhiteLabel();
return (
<ErrorBoundary
user={user ?? ({} as TUser)}
version={pkgFile.version}
firebaseHelper={firebaseHelper}
currentDeviceType={currentDeviceType}
whiteLabelLogoUrls={logoUrls}
>
{children}
</ErrorBoundary>
);
};
export default ErrorProvider;

View File

@ -24,48 +24,36 @@
// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
import CreateFileError from "@/components/CreateFileError";
import { getErrorData } from "@/utils/actions";
"use client";
type TSearchParams = {
error?: string;
fileInfo?: string;
createFile?: string;
fromFile?: string;
fromTemplate?: string;
import React from "react";
import { ThemeProvider as ComponentThemeProvider } from "@docspace/shared/components/theme-provider";
import { TUser } from "@docspace/shared/api/people/types";
import { TSettings } from "@docspace/shared/api/settings/types";
import useTheme from "@/hooks/useTheme";
import useI18N from "@/hooks/useI18N";
type TThemeProvider = {
children: React.ReactNode;
settings: TSettings | undefined;
user: TUser | undefined;
};
async function Page({ searchParams }: { searchParams: TSearchParams }) {
const error = searchParams.error ? JSON.parse(searchParams.error) : "";
const fileInfo = searchParams.fileInfo
? JSON.parse(searchParams.fileInfo)
: "";
const fromTemplate = searchParams.fromTemplate
? JSON.parse(searchParams.fromTemplate)
: "";
const fromFile = searchParams.fromFile
? JSON.parse(searchParams.fromFile)
: "";
const ThemeProvider = ({ children, user, settings }: TThemeProvider) => {
const { i18n } = useI18N({ settings, user });
console.log("searchParams here", searchParams);
const { theme, currentColorTheme } = useTheme({ user, i18n });
if (searchParams.createFile) {
const { settings, user } = await getErrorData();
return (
<CreateFileError
error={error}
fileInfo={fileInfo}
fromFile={!!fromFile}
fromTemplate={!!fromTemplate}
settings={settings}
user={user}
/>
);
}
return <div></div>;
}
export default Page;
return (
<ComponentThemeProvider
theme={theme}
currentColorScheme={currentColorTheme}
>
{children}
</ComponentThemeProvider>
);
};
export default ThemeProvider;

View File

@ -0,0 +1,53 @@
// (c) Copyright Ascensio System SIA 2009-2024
//
// This program is a free software product.
// You can redistribute it and/or modify it under the terms
// of the GNU Affero General Public License (AGPL) version 3 as published by the Free Software
// Foundation. In accordance with Section 7(a) of the GNU AGPL its Section 15 shall be amended
// to the effect that Ascensio System SIA expressly excludes the warranty of non-infringement of
// any third-party rights.
//
// This program is distributed WITHOUT ANY WARRANTY, without even the implied warranty
// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For details, see
// the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html
//
// You can contact Ascensio System SIA at Lubanas st. 125a-25, Riga, Latvia, EU, LV-1021.
//
// The interactive user interfaces in modified source and object code versions of the Program must
// display Appropriate Legal Notices, as required under Section 5 of the GNU AGPL version 3.
//
// Pursuant to Section 7(b) of the License you must retain the original Product logo when
// distributing the program. Pursuant to Section 7(e) we decline to grant you any rights under
// trademark law for use of our trademarks.
//
// All the Product's GUI elements, including illustrations and icon sets, as well as technical writing
// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
"use client";
import React from "react";
import { I18nextProvider } from "react-i18next";
import { TUser } from "@docspace/shared/api/people/types";
import { TSettings } from "@docspace/shared/api/settings/types";
import useI18N from "@/hooks/useI18N";
type TTranslationProvider = {
children: React.ReactNode;
settings: TSettings | undefined;
user: TUser | undefined;
};
const TranslationProvider = ({
children,
settings,
user,
}: TTranslationProvider) => {
const { i18n } = useI18N({ settings, user });
return <I18nextProvider i18n={i18n}>{children}</I18nextProvider>;
};
export default TranslationProvider;

View File

@ -0,0 +1,58 @@
// (c) Copyright Ascensio System SIA 2009-2024
//
// This program is a free software product.
// You can redistribute it and/or modify it under the terms
// of the GNU Affero General Public License (AGPL) version 3 as published by the Free Software
// Foundation. In accordance with Section 7(a) of the GNU AGPL its Section 15 shall be amended
// to the effect that Ascensio System SIA expressly excludes the warranty of non-infringement of
// any third-party rights.
//
// This program is distributed WITHOUT ANY WARRANTY, without even the implied warranty
// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For details, see
// the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html
//
// You can contact Ascensio System SIA at Lubanas st. 125a-25, Riga, Latvia, EU, LV-1021.
//
// The interactive user interfaces in modified source and object code versions of the Program must
// display Appropriate Legal Notices, as required under Section 5 of the GNU AGPL version 3.
//
// Pursuant to Section 7(b) of the License you must retain the original Product logo when
// distributing the program. Pursuant to Section 7(e) we decline to grant you any rights under
// trademark law for use of our trademarks.
//
// All the Product's GUI elements, including illustrations and icon sets, as well as technical writing
// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
import { TUser } from "@docspace/shared/api/people/types";
import { TSettings } from "@docspace/shared/api/settings/types";
import { Toast } from "@docspace/shared/components/toast/Toast";
import ThemeProvider from "./ThemeProvider";
import TranslationProvider from "./TranslationProvider";
import ErrorProvider from "./ErrorProvider";
export type TContextData = {
user: TUser | undefined;
settings: TSettings | undefined;
};
export type TProviders = {
children: React.ReactNode;
contextData: TContextData;
};
const Providers = ({ children, contextData }: TProviders) => {
return (
<TranslationProvider {...contextData}>
<ThemeProvider {...contextData}>
<ErrorProvider {...contextData}>
{children}
<Toast isSSR />
</ErrorProvider>
</ThemeProvider>
</TranslationProvider>
);
};
export default Providers;

View File

@ -56,4 +56,3 @@ body {
body.loading * {
cursor: wait !important;
}

View File

@ -42,8 +42,7 @@ import { TSelectedFileInfo } from "@docspace/shared/selectors/Files/FilesSelecto
import SocketIOHelper from "@docspace/shared/utils/socket";
import { FilesSelectorFilterTypes } from "@docspace/shared/enums";
import { TRoomSecurity } from "@docspace/shared/api/rooms/types";
import { Nullable, TTranslation } from "@docspace/shared/types";
import { i18n } from "i18next";
import { TTranslation } from "@docspace/shared/types";
export type TGoBack = {
requestClose: boolean;
@ -172,14 +171,15 @@ export type TError = {
message: "unauthorized" | "restore-backup" | string;
status?: "not-found" | "access-denied" | number | string;
type?: string;
editorUrl?: string;
};
export type TResponse =
| {
config: IInitialConfig;
editorUrl: TDocServiceLocation;
user: TUser;
settings: TSettings;
user?: TUser;
settings?: TSettings;
successAuth: boolean;
isSharingAccess: boolean;
error?: TError;
@ -190,7 +190,7 @@ export type TResponse =
| {
error: TError;
config?: undefined;
editorUrl?: undefined;
user?: undefined;
settings?: undefined;
successAuth?: undefined;
@ -204,13 +204,12 @@ export type EditorProps = {
config?: IInitialConfig;
successAuth?: boolean;
user?: TUser;
view?: boolean;
doc?: string;
documentserverUrl: string;
fileInfo?: TFile;
isSharingAccess?: boolean;
errorMessage?: string;
t: TTranslation | null;
onSDKRequestSharingSettings: () => void;
onSDKRequestSaveAs: (event: object) => void;
onSDKRequestInsertImage: (event: object) => void;
@ -273,7 +272,6 @@ export interface SelectFolderDialogProps {
) => Promise<void>;
fileInfo: TFile;
filesSettings: TFilesSettings;
i18n: i18n;
fileSaveAsExtension?: string;
}
@ -310,7 +308,6 @@ export interface SelectFileDialogProps {
) => Promise<void>;
fileInfo: TFile;
filesSettings: TFilesSettings;
i18n: i18n;
}
export interface UseSocketHelperProps {
@ -324,7 +321,7 @@ export interface UseEventsProps {
config?: IInitialConfig;
doc?: string;
errorMessage?: string;
t?: Nullable<TTranslation>;
t: TTranslation;
}
export interface UseInitProps {
@ -332,7 +329,7 @@ export interface UseInitProps {
successAuth?: boolean;
fileInfo?: TFile;
user?: TUser;
t: Nullable<TTranslation>;
t: TTranslation;
setDocTitle: (value: string) => void;
documentReady: boolean;

View File

@ -28,78 +28,35 @@
import { headers } from "next/headers";
import { TenantStatus, EditorConfigErrorType } from "@docspace/shared/enums";
import {
createRequest,
getBaseUrl,
} from "@docspace/shared/utils/next-ssr-helper";
import type { TDocServiceLocation } from "@docspace/shared/api/files/types";
import { TenantStatus, EditorConfigErrorType } from "@docspace/shared/enums";
import type {
TDocServiceLocation,
TFile,
} from "@docspace/shared/api/files/types";
import { TUser } from "@docspace/shared/api/people/types";
import { TSettings } from "@docspace/shared/api/settings/types";
import type { IInitialConfig, TCatchError, TError, TResponse } from "@/types";
import { REPLACED_URL_PATH } from "./constants";
import { isTemplateFile } from ".";
export async function getErrorData() {
const hdrs = headers();
const cookie = hdrs.get("cookie");
const [getSettings, getUser] = createRequest(
[
`/settings?withPassword=${cookie?.includes("asc_auth_key") ? "false" : "true"}`,
`/people/@self`,
],
[["", ""]],
"GET",
);
const resActions = [];
resActions.push(fetch(getSettings));
resActions.push(fetch(getUser));
const [settingsRes, userRes] = await Promise.all(resActions);
const actions = [];
actions.push(settingsRes.json());
if (userRes.status !== 401) actions.push(userRes.json());
const [settings, user] = await Promise.all(actions);
return { settings: settings.response, user: user?.response };
}
const processFillFormDraft = async (
config: IInitialConfig,
searchParams: URLSearchParams,
editorSearchParams: URLSearchParams,
share?: string,
): Promise<
| [
string,
IInitialConfig,
TDocServiceLocation | undefined,
string | undefined,
]
| void
> => {
): Promise<[string, IInitialConfig | TError, string | undefined] | void> => {
const templateFileId = config.file.id;
const [checkFillFormDraft] = createRequest(
[`/files/masterform/${templateFileId}/checkfillformdraft`],
[
share ? ["Request-Token", share] : ["", ""],
["Content-Type", "application/json;charset=utf-8"],
],
"POST",
JSON.stringify({ fileId: templateFileId }),
);
const formUrl = await checkFillFromDraft(templateFileId, share);
const response = await fetch(checkFillFormDraft);
if (!response.ok) return;
const { response: formUrl } = await response.json();
if (!formUrl) return;
const basePath = getBaseUrl();
const url = new URL(basePath + formUrl);
@ -116,45 +73,13 @@ const processFillFormDraft = async (
...Object.fromEntries(url.searchParams),
});
const editorVersion = editorSearchParams.get("version");
const queries = [
`/files/file/${queryFileId}/openedit?${combinedSearchParams.toString()}`,
const actions: [Promise<IInitialConfig | TError>] = [
openEdit(queryFileId, combinedSearchParams.toString(), share),
];
if (queryVersion && queryVersion !== editorVersion) {
editorSearchParams.set("version", queryVersion);
queries.push(`/files/docservice?${editorSearchParams.toString()}`);
}
const [newConfig] = await Promise.all(actions);
const [getConfig, getEditorUrl] = createRequest(
queries,
[share ? ["Request-Token", share] : ["", ""]],
"GET",
);
const resActions = [];
resActions.push(fetch(getConfig));
getEditorUrl && resActions.push(fetch(getEditorUrl));
const [configRes, editorUrlRes] = await Promise.all(resActions);
if (!configRes.ok) return;
const actions = [];
actions.push(configRes.json());
editorUrlRes && actions.push(editorUrlRes.json());
const [newConfig, newEditorUrl] = await Promise.all(actions);
return [
queryFileId,
newConfig.response,
newEditorUrl?.response,
url.hash ?? "",
];
return [queryFileId, newConfig, url.hash ?? ""];
};
export async function fileCopyAs(
@ -163,7 +88,13 @@ export async function fileCopyAs(
destFolderId: string,
enableExternalExt?: boolean,
password?: string,
) {
): Promise<{
file: TFile | undefined;
error:
| string
| { message: string; status: number; type: string; stack: string }
| undefined;
}> {
try {
const [createFile] = createRequest(
[`/files/file/${fileId}/copyas`],
@ -216,7 +147,13 @@ export async function createFile(
title: string,
templateId?: string,
formId?: string,
) {
): Promise<{
file: TFile | undefined;
error:
| string
| { message: string; status: number; type: string; stack: string }
| undefined;
}> {
try {
const [createFile] = createRequest(
[`/files/${parentId}/file`],
@ -258,7 +195,7 @@ export async function createFile(
}
export async function getData(
fileId?: string,
fileId: string,
version?: string,
doc?: string,
view?: boolean,
@ -268,56 +205,28 @@ export async function getData(
try {
const hdrs = headers();
const cookie = hdrs.get("cookie");
const searchParams = new URLSearchParams();
const editorSearchParams = new URLSearchParams();
if (view) searchParams.append("view", view ? "true" : "false");
if (version) {
searchParams.append("version", version);
editorSearchParams.append("version", version);
}
if (doc) searchParams.append("doc", doc);
if (share) searchParams.append("share", share);
if (editorType) searchParams.append("editorType", editorType);
const [getConfig, getEditorUrl, getSettings, getUser] = createRequest(
[
`/files/file/${fileId}/openedit?${searchParams.toString()}`,
`/files/docservice?${editorSearchParams.toString()}`,
`/settings?withPassword=${cookie?.includes("asc_auth_key") ? "false" : "true"}`,
`/people/@self`,
],
[share ? ["Request-Token", share] : ["", ""]],
"GET",
);
const [config, user, settings] = await Promise.all([
openEdit(fileId, searchParams.toString(), share),
const resActions = [];
getUser(share),
getSettings(share),
]);
resActions.push(fetch(getConfig));
resActions.push(fetch(getEditorUrl));
resActions.push(fetch(getSettings));
resActions.push(fetch(getUser));
const [configRes, editorUrlRes, settingsRes, userRes] =
await Promise.all(resActions);
const actions = [];
actions.push(configRes.json());
actions.push(editorUrlRes.json());
actions.push(settingsRes.json());
if (userRes.status !== 401) actions.push(userRes.json());
const [config, editorUrl, settings, user] = await Promise.all(actions);
if (configRes.ok) {
if ("token" in config) {
const response: TResponse = {
config: config.response,
editorUrl: editorUrl.response,
user: user?.response,
settings: settings.response,
config,
user,
settings,
successAuth: false,
isSharingAccess: false,
doc,
@ -328,21 +237,20 @@ export async function getData(
const result = await processFillFormDraft(
response.config,
searchParams,
editorSearchParams,
share,
);
if (result) {
const [newFileId, newConfig, newEditorUrl, hash] = result;
const [newFileId, newConfig, hash] = result;
response.fileId = newFileId;
response.config = newConfig;
if (newEditorUrl) response.editorUrl = newEditorUrl;
response.config = newConfig as IInitialConfig;
if (hash) response.hash = hash;
}
}
if (response.settings.tenantStatus === TenantStatus.PortalRestore) {
if (response.settings?.tenantStatus === TenantStatus.PortalRestore) {
response.error = { message: "restore-backup" };
}
@ -364,38 +272,21 @@ export async function getData(
return response;
}
console.log("initDocEditor failed", config.error);
const status =
config.error.type === EditorConfigErrorType.NotFoundScope
? "not-found"
: config.error.type === EditorConfigErrorType.AccessDeniedScope
? "access-denied"
: configRes.status === 415
? "not-supported"
: undefined;
const message = status ? config.error.message : undefined;
console.log("initDocEditor failed", config);
const response: TResponse = {
error:
user || share
? config.error.type === EditorConfigErrorType.LinkScope
? { message: message ?? "unauthorized", status }
: { ...config.error, status }
: { message: message ?? "unauthorized", status },
user: user?.response,
settings: settings?.response,
error: config,
fileId,
editorUrl: editorUrl.response,
};
return response;
} catch (e) {
console.log(e);
const err = e as TCatchError;
console.error("initDocEditor failed", err);
const editorUrl = (await getEditorUrl("", share)).docServiceUrl;
let message = "";
if (typeof err === "string") message = err;
else
@ -414,7 +305,143 @@ export async function getData(
const error: TError = {
message,
status,
editorUrl,
};
return { error };
}
}
export async function getUser(share?: string) {
const hdrs = headers();
const cookie = hdrs.get("cookie");
const [getUser] = createRequest(
[`/people/@self`],
[share ? ["Request-Token", share] : ["", ""]],
"GET",
);
if (!cookie?.includes("asc_auth_key")) return undefined;
const userRes = await fetch(getUser);
if (userRes.status === 401) return undefined;
const user = await userRes.json();
return user.response as TUser;
}
export async function getSettings(share?: string) {
const hdrs = headers();
const cookie = hdrs.get("cookie");
const [getSettings] = createRequest(
[
`/settings?withPassword=${cookie?.includes("asc_auth_key") ? "false" : "true"}`,
],
[share ? ["Request-Token", share] : ["", ""]],
"GET",
);
const resActions = [];
resActions.push(fetch(getSettings));
const [settingsRes] = await Promise.all(resActions);
const actions = [];
actions.push(settingsRes.json());
const [settings] = await Promise.all(actions);
return settings.response as TSettings;
}
export async function checkFillFromDraft(
templateFileId: number,
share?: string,
) {
const [checkFillFormDraft] = createRequest(
[`/files/masterform/${templateFileId}/checkfillformdraft`],
[
share ? ["Request-Token", share] : ["", ""],
["Content-Type", "application/json;charset=utf-8"],
],
"POST",
JSON.stringify({ fileId: templateFileId }),
);
const response = await fetch(checkFillFormDraft);
if (!response.ok) return null;
const { response: formUrl } = await response.json();
return formUrl as string;
}
export async function openEdit(
fileId: number | string,
searchParams: string,
share?: string,
) {
const hdrs = headers();
const cookie = hdrs.get("cookie");
const [getConfig] = createRequest(
[`/files/file/${fileId}/openedit?${searchParams}`],
[share ? ["Request-Token", share] : ["", ""]],
"GET",
);
const res = await fetch(getConfig);
const config = await res.json();
if (res.ok) {
config.response.editorUrl = (
config.response as IInitialConfig
).editorUrl.replace(REPLACED_URL_PATH, "");
return config.response as IInitialConfig;
}
const editorUrl = (await getEditorUrl("", share)).docServiceUrl;
const status =
config.error.type === EditorConfigErrorType.NotFoundScope
? "not-found"
: config.error.type === EditorConfigErrorType.AccessDeniedScope
? "access-denied"
: res.status === 415
? "not-supported"
: undefined;
const message = status ? config.error.message : undefined;
const error =
cookie?.includes("asc_auth_key") || share
? config.error.type === EditorConfigErrorType.LinkScope
? { message: message ?? "unauthorized", status, editorUrl }
: { ...config.error, status, editorUrl }
: { message: message ?? "unauthorized", status, editorUrl };
return error as TError;
}
export async function getEditorUrl(
editorSearchParams?: string,
share?: string,
) {
const [request] = createRequest(
[`/files/docservice?${editorSearchParams ? editorSearchParams : ""}`],
[share ? ["Request-Token", share] : ["", ""]],
"GET",
);
const res = await fetch(request);
const editorUrl = await res.json();
return editorUrl.response as TDocServiceLocation;
}

View File

@ -37,3 +37,4 @@ export const IS_VIEW =
? window.location.search.indexOf("action=view") !== -1
: false;
export const REPLACED_URL_PATH = "/web-apps/apps/api/documents/api.js";

View File

@ -24,19 +24,14 @@
// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
import React from "react";
import i18n from "i18next";
import { initReactI18next } from "react-i18next";
import config from "../../../../../buildtools/config/appsettings.json";
import { translations } from "./autoGeneratedTranslations";
export const getI18NInstance = (lng: string, portalLng: string) => {
if (typeof window === "undefined") return;
// const cultures = config.web.cultures.split(",");
i18n.use(initReactI18next).init({
lng,
fallbackLng: "en",

View File

@ -24,6 +24,8 @@
// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
"use client";
import React from "react";
import { Loader, LoaderTypes } from "../loader";

View File

@ -26,8 +26,6 @@
import React, { ErrorInfo } from "react";
import { I18nextProvider } from "react-i18next";
import Error520 from "../errors/Error520";
import type {
@ -68,32 +66,11 @@ class ErrorBoundary extends React.Component<
currentDeviceType,
whiteLabelLogoUrls,
currentColorScheme,
isNextJS,
theme,
i18n,
} = this.props;
if (error) {
// You can render any custom fallback UI
if (isNextJS && !i18n?.language) return null;
if (isNextJS && theme && i18n) {
return (
<I18nextProvider i18n={i18n}>
<Error520
user={user}
errorLog={error}
version={version}
firebaseHelper={firebaseHelper}
currentDeviceType={currentDeviceType}
whiteLabelLogoUrls={whiteLabelLogoUrls}
currentColorScheme={theme.currentColorScheme}
/>
</I18nextProvider>
);
}
return (
<Error520
user={user}

View File

@ -25,8 +25,8 @@
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
import React from "react";
import { i18n } from "i18next";
import { I18nextProvider, useTranslation } from "react-i18next";
import { useTranslation } from "react-i18next";
import ErrorContainer from "../error-container/ErrorContainer";
@ -37,12 +37,3 @@ const Error404 = () => {
};
export default Error404;
export const Error404Wrapper = ({ i18nProp }: { i18nProp: i18n }) => {
if (!i18nProp.language) return null;
return (
<I18nextProvider i18n={i18nProp}>
<Error404 />
</I18nextProvider>
);
};

View File

@ -32,7 +32,7 @@ const GlobalStyle = createGlobalStyle<{ theme: TTheme }>`
margin: 0;
background-color: ${(props) => props.theme.backgroundColor};
color: ${(props) => props.theme.color};
font-family: ${(props) => props.theme.fontFamily};
@ -42,7 +42,6 @@ const GlobalStyle = createGlobalStyle<{ theme: TTheme }>`
}
body {
direction: ${(props) => props.theme.interfaceDirection};
}
`;

View File

@ -24,6 +24,8 @@
// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
"use client";
import React, { useCallback, useEffect, useState } from "react";
import { ToastClassName, cssTransition } from "react-toastify";
import { isMobileOnly } from "react-device-detect";

View File

@ -25,6 +25,9 @@
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
/* eslint-disable react-hooks/exhaustive-deps */
"use client";
import { useEffect } from "react";
export const useClickOutside = (

View File

@ -3163,7 +3163,7 @@ __metadata:
version: 0.0.0-use.local
resolution: "@docspace/doceditor@workspace:packages/doceditor"
dependencies:
"@onlyoffice/document-editor-react": "file:./onlyoffice-document-editor-react-1.5.1.tgz"
"@onlyoffice/document-editor-react": "npm:^1.5.1"
"@svgr/webpack": "npm:^8.1.0"
"@types/node": "npm:^20"
"@types/react": "npm:^18"
@ -5224,15 +5224,15 @@ __metadata:
languageName: node
linkType: hard
"@onlyoffice/document-editor-react@file:./onlyoffice-document-editor-react-1.5.1.tgz::locator=%40docspace%2Fdoceditor%40workspace%3Apackages%2Fdoceditor":
"@onlyoffice/document-editor-react@npm:^1.5.1":
version: 1.5.1
resolution: "@onlyoffice/document-editor-react@file:./onlyoffice-document-editor-react-1.5.1.tgz#./onlyoffice-document-editor-react-1.5.1.tgz::hash=0845a7&locator=%40docspace%2Fdoceditor%40workspace%3Apackages%2Fdoceditor"
resolution: "@onlyoffice/document-editor-react@npm:1.5.1"
dependencies:
lodash: "npm:4.17.21"
peerDependencies:
react: ^16.9.0 || ^17 || ^18
react-dom: ^16.9.0 || ^17 || ^18
checksum: d7f1ac9ed3510484a8622da828d4a5edad127d4f8af66cd55990253557ad10dc0100b3fb8b39e696a00ee51e3ca9b64f7c031d50ad540f37cd3bdb7d8cb0f8e0
checksum: f70f876afc7518a58a5fc0a8e1d644883e87471c6a5c0d035a73eb7c1911b059f751b14f123a83aaaaac64fcabfa76b984056f427778eaa62f12bcf2a1ace4b3
languageName: node
linkType: hard