Merge branch 'develop' into feature/inifinite-scroll
This commit is contained in:
commit
af377fae99
@ -36,7 +36,6 @@ powershell -Command "(gc build\deploy\nginx\onlyoffice.conf) -replace '#', '' |
|
||||
xcopy config\nginx\sites-enabled\* build\deploy\nginx\sites-enabled\ /E /R /Y
|
||||
|
||||
REM fix paths
|
||||
powershell -Command "(gc build\deploy\nginx\sites-enabled\onlyoffice-editor.conf) -replace 'ROOTPATH', '%~dp0deploy\products\ASC.Files\editor' -replace '\\', '/' | Out-File -encoding ASCII build\deploy\nginx\sites-enabled\onlyoffice-editor.conf"
|
||||
powershell -Command "(gc build\deploy\nginx\sites-enabled\onlyoffice-files.conf) -replace 'ROOTPATH', '%~dp0deploy\products\ASC.Files\client' -replace '\\', '/' | Out-File -encoding ASCII build\deploy\nginx\sites-enabled\onlyoffice-files.conf"
|
||||
powershell -Command "(gc build\deploy\nginx\sites-enabled\onlyoffice-login.conf) -replace 'ROOTPATH', '%~dp0deploy\studio\login' -replace '\\', '/' | Out-File -encoding ASCII build\deploy\nginx\sites-enabled\onlyoffice-login.conf"
|
||||
powershell -Command "(gc build\deploy\nginx\sites-enabled\onlyoffice-people.conf) -replace 'ROOTPATH', '%~dp0deploy\products\ASC.People\client' -replace '\\', '/' | Out-File -encoding ASCII build\deploy\nginx\sites-enabled\onlyoffice-people.conf"
|
||||
|
@ -33,16 +33,16 @@ public static partial class CommonLogger
|
||||
[LoggerMessage(Level = LogLevel.Error)]
|
||||
public static partial void ErrorWithException(this ILogger logger, string message, Exception exception);
|
||||
|
||||
[LoggerMessage(Level = LogLevel.Error)]
|
||||
[LoggerMessage(Level = LogLevel.Error, Message = "{message}")]
|
||||
public static partial void Error(this ILogger logger, string message);
|
||||
|
||||
[LoggerMessage(Level = LogLevel.Debug)]
|
||||
[LoggerMessage(Level = LogLevel.Debug, Message = "{message}")]
|
||||
public static partial void Debug(this ILogger logger, string message);
|
||||
|
||||
[LoggerMessage(Level = LogLevel.Information)]
|
||||
[LoggerMessage(Level = LogLevel.Information, Message = "{message}")]
|
||||
public static partial void Information(this ILogger logger, string message);
|
||||
|
||||
[LoggerMessage(Level = LogLevel.Warning)]
|
||||
[LoggerMessage(Level = LogLevel.Warning, Message = "{message}")]
|
||||
public static partial void Warning(this ILogger logger, string message);
|
||||
|
||||
[LoggerMessage(Level = LogLevel.Warning)]
|
||||
|
@ -1,9 +0,0 @@
|
||||
server {
|
||||
listen 5013;
|
||||
root /var/www/products/ASC.Files/editor;
|
||||
index index.html;
|
||||
|
||||
location / {
|
||||
try_files $uri /index.html =404;
|
||||
}
|
||||
}
|
@ -248,12 +248,13 @@ server {
|
||||
}
|
||||
|
||||
location ~* /files/doceditor {
|
||||
#rewrite products/files/doceditor/(.*) /$1 break;
|
||||
|
||||
proxy_pass http://localhost:5013;
|
||||
proxy_redirect off;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header Host $this_host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $remote_addr;
|
||||
proxy_set_header X-REWRITER-URL $X_REWRITER_URL;
|
||||
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
|
@ -1,9 +0,0 @@
|
||||
server {
|
||||
listen 5013;
|
||||
root "ROOTPATH";
|
||||
index index.html;
|
||||
|
||||
location / {
|
||||
try_files $uri /index.html =404;
|
||||
}
|
||||
}
|
@ -1,107 +1,15 @@
|
||||
import axios from "axios";
|
||||
import { AppServerConfig } from "../constants";
|
||||
import { combineUrl } from "../utils";
|
||||
import AxiosClient from "../utils/axiosClient";
|
||||
|
||||
const { proxyURL, apiPrefixURL, apiTimeout } = AppServerConfig;
|
||||
const origin = window.location.origin;
|
||||
const client = new AxiosClient();
|
||||
|
||||
const apiBaseURL = combineUrl(origin, proxyURL, apiPrefixURL);
|
||||
const loginURL = combineUrl(proxyURL, "/login");
|
||||
const paymentsURL = combineUrl(proxyURL, "/payments");
|
||||
|
||||
window.AppServer = {
|
||||
...window.AppServer,
|
||||
origin,
|
||||
proxyURL,
|
||||
apiPrefixURL,
|
||||
apiBaseURL,
|
||||
apiTimeout,
|
||||
paymentsURL,
|
||||
export const initSSR = (headers) => {
|
||||
client.initSSR(headers);
|
||||
};
|
||||
|
||||
/**
|
||||
* @description axios instance for ajax requests
|
||||
*/
|
||||
|
||||
const client = axios.create({
|
||||
baseURL: apiBaseURL,
|
||||
responseType: "json",
|
||||
timeout: apiTimeout, // default is `0` (no timeout)
|
||||
});
|
||||
|
||||
export function setWithCredentialsStatus(state) {
|
||||
client.defaults.withCredentials = state;
|
||||
}
|
||||
|
||||
export function setClientBasePath(path) {
|
||||
if (!path) return;
|
||||
|
||||
client.defaults.baseURL = path;
|
||||
}
|
||||
|
||||
const getResponseError = (res) => {
|
||||
if (!res) return;
|
||||
|
||||
if (res.data && res.data.error) {
|
||||
return res.data.error.message;
|
||||
}
|
||||
|
||||
if (res.isAxiosError && res.message) {
|
||||
//console.error(res.message);
|
||||
return res.message;
|
||||
}
|
||||
export const request = (options) => {
|
||||
return client.request(options);
|
||||
};
|
||||
|
||||
/**
|
||||
* @description wrapper for making ajax requests
|
||||
* @param {object} object with method,url,data etc.
|
||||
*/
|
||||
export const request = function (options) {
|
||||
const onSuccess = function (response) {
|
||||
const error = getResponseError(response);
|
||||
if (error) throw new Error(error);
|
||||
|
||||
if (!response || !response.data || response.isAxiosError) return null;
|
||||
|
||||
if (response.data.hasOwnProperty("total"))
|
||||
return { total: +response.data.total, items: response.data.response };
|
||||
|
||||
if (response.request.responseType === "text") return response.data;
|
||||
|
||||
return response.data.response;
|
||||
};
|
||||
|
||||
const onError = function (error) {
|
||||
//console.error("Request Failed:", error);
|
||||
|
||||
const errorText = error.response
|
||||
? getResponseError(error.response)
|
||||
: error.message;
|
||||
|
||||
switch (error.response?.status) {
|
||||
case 401:
|
||||
if (options.skipUnauthorized) return Promise.resolve();
|
||||
if (options.skipLogout) return Promise.reject(errorText || error);
|
||||
|
||||
request({
|
||||
method: "post",
|
||||
url: "/authentication/logout",
|
||||
}).then(() => {
|
||||
setWithCredentialsStatus(false);
|
||||
window.location.href = window?.AppServer?.personal ? "/" : loginURL;
|
||||
});
|
||||
break;
|
||||
case 402:
|
||||
if (!window.location.pathname.includes("payments")) {
|
||||
window.location.href = paymentsURL;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return Promise.reject(errorText || error);
|
||||
};
|
||||
|
||||
return client(options).then(onSuccess).catch(onError);
|
||||
export const setWithCredentialsStatus = (state) => {
|
||||
return client.setWithCredentialsStatus(state);
|
||||
};
|
||||
|
@ -192,21 +192,23 @@ const Selector = (props) => {
|
||||
return;
|
||||
}
|
||||
|
||||
newGroupList[0].selectedCount = isChecked
|
||||
? newGroupList[0].selectedCount - 1
|
||||
: newGroupList[0].selectedCount + 1;
|
||||
if (newGroupList.length) {
|
||||
newGroupList[0].selectedCount = isChecked
|
||||
? newGroupList[0].selectedCount - 1
|
||||
: newGroupList[0].selectedCount + 1;
|
||||
|
||||
option.groups.forEach((group) => {
|
||||
const groupIndex = newGroupList.findIndex(
|
||||
(item) => item.key === group
|
||||
);
|
||||
option.groups.forEach((group) => {
|
||||
const groupIndex = newGroupList.findIndex(
|
||||
(item) => item.key === group
|
||||
);
|
||||
|
||||
if (groupIndex > 0) {
|
||||
newGroupList[groupIndex].selectedCount = isChecked
|
||||
? newGroupList[groupIndex].selectedCount - 1
|
||||
: newGroupList[groupIndex].selectedCount + 1;
|
||||
}
|
||||
});
|
||||
if (groupIndex > 0) {
|
||||
newGroupList[groupIndex].selectedCount = isChecked
|
||||
? newGroupList[groupIndex].selectedCount - 1
|
||||
: newGroupList[groupIndex].selectedCount + 1;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
setSelectedOptionList(newSelected);
|
||||
|
@ -27,7 +27,7 @@ const StyledFilterInput = styled.div`
|
||||
|
||||
.filter-input_selected-row {
|
||||
width: 100%;
|
||||
height: 32px;
|
||||
min-height: 32px;
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
@ -19,6 +19,7 @@ const StyledSelectedItem = styled.div`
|
||||
padding: 6px 8px;
|
||||
|
||||
margin-right: 4px;
|
||||
margin-bottom: 4px;
|
||||
|
||||
background: #eceef1;
|
||||
|
||||
|
@ -104,6 +104,7 @@ module.exports = {
|
||||
"react-device-detect": {
|
||||
singleton: true,
|
||||
requiredVersion: compDeps["react-device-detect"],
|
||||
// eager: true,
|
||||
},
|
||||
"react-dropzone": {
|
||||
singleton: true,
|
||||
|
1
packages/asc-web-common/fonts.js
Normal file
1
packages/asc-web-common/fonts.js
Normal file
@ -0,0 +1 @@
|
||||
export { default as fonts } from "./opensansoffline.scss";
|
140
packages/asc-web-common/utils/axiosClient.js
Normal file
140
packages/asc-web-common/utils/axiosClient.js
Normal file
@ -0,0 +1,140 @@
|
||||
import axios from "axios";
|
||||
import { AppServerConfig } from "../constants";
|
||||
import combineUrl from "./combineUrl";
|
||||
|
||||
const { proxyURL, apiPrefixURL, apiTimeout } = AppServerConfig;
|
||||
|
||||
class AxiosClient {
|
||||
constructor() {
|
||||
if (typeof window !== "undefined") this.initCSR();
|
||||
}
|
||||
|
||||
initCSR = () => {
|
||||
this.isSSR = false;
|
||||
const origin = window.location.origin;
|
||||
|
||||
const apiBaseURL = combineUrl(origin, proxyURL, apiPrefixURL);
|
||||
const paymentsURL = combineUrl(proxyURL, "/payments");
|
||||
this.paymentsURL = paymentsURL;
|
||||
|
||||
window.AppServer = {
|
||||
...window.AppServer,
|
||||
origin,
|
||||
proxyURL,
|
||||
apiPrefixURL,
|
||||
apiBaseURL,
|
||||
apiTimeout,
|
||||
paymentsURL,
|
||||
};
|
||||
|
||||
this.client = axios.create({
|
||||
baseURL: apiBaseURL,
|
||||
responseType: "json",
|
||||
timeout: apiTimeout, // default is `0` (no timeout)
|
||||
});
|
||||
};
|
||||
|
||||
initSSR = (headers) => {
|
||||
this.isSSR = true;
|
||||
const xRewriterUrl = headers["x-rewriter-url"];
|
||||
const apiBaseURL = combineUrl(xRewriterUrl, proxyURL, apiPrefixURL);
|
||||
|
||||
this.client = axios.create({
|
||||
baseURL: apiBaseURL,
|
||||
responseType: "json",
|
||||
timeout: apiTimeout,
|
||||
headers: headers,
|
||||
});
|
||||
};
|
||||
|
||||
setWithCredentialsStatus = (state) => {
|
||||
this.client.defaults.withCredentials = state;
|
||||
};
|
||||
|
||||
setClientBasePath = (path) => {
|
||||
if (!path) return;
|
||||
|
||||
this.client.defaults.baseURL = path;
|
||||
};
|
||||
|
||||
getResponseError = (res) => {
|
||||
if (!res) return;
|
||||
|
||||
if (res.data && res.data.error) {
|
||||
return res.data.error.message;
|
||||
}
|
||||
|
||||
if (res.isAxiosError && res.message) {
|
||||
//console.error(res.message);
|
||||
return res.message;
|
||||
}
|
||||
};
|
||||
|
||||
request = (options) => {
|
||||
const onSuccess = (response) => {
|
||||
const error = this.getResponseError(response);
|
||||
if (error) throw new Error(error);
|
||||
|
||||
if (!response || !response.data || response.isAxiosError) return null;
|
||||
|
||||
if (response.data.hasOwnProperty("total"))
|
||||
return { total: +response.data.total, items: response.data.response };
|
||||
|
||||
if (response.request.responseType === "text") return response.data;
|
||||
|
||||
return response.data.response;
|
||||
};
|
||||
|
||||
const onError = (error) => {
|
||||
//console.error("Request Failed:", error);
|
||||
|
||||
let errorText = error.response
|
||||
? this.getResponseError(error.response)
|
||||
: error.message;
|
||||
|
||||
if (error?.response?.status === 401 && this.isSSR) errorText = 401;
|
||||
|
||||
const loginURL = combineUrl(proxyURL, "/login");
|
||||
if (!this.isSSR) {
|
||||
switch (error.response?.status) {
|
||||
case 401:
|
||||
if (options.skipUnauthorized) return Promise.resolve();
|
||||
if (options.skipLogout) return Promise.reject(errorText || error);
|
||||
|
||||
this.request({
|
||||
method: "post",
|
||||
url: "/authentication/logout",
|
||||
}).then(() => {
|
||||
this.setWithCredentialsStatus(false);
|
||||
window.location.href = window?.AppServer?.personal
|
||||
? "/"
|
||||
: loginURL;
|
||||
});
|
||||
break;
|
||||
case 402:
|
||||
if (!window.location.pathname.includes("payments")) {
|
||||
window.location.href = this.paymentsURL;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return Promise.reject(errorText || error);
|
||||
} else {
|
||||
switch (error.response?.status) {
|
||||
case 401:
|
||||
return Promise.resolve();
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return Promise.reject(errorText || error);
|
||||
}
|
||||
};
|
||||
return this.client(options).then(onSuccess).catch(onError);
|
||||
};
|
||||
}
|
||||
|
||||
export default AxiosClient;
|
@ -1,7 +1,6 @@
|
||||
import { LANGUAGE } from "../constants";
|
||||
import sjcl from "sjcl";
|
||||
import { isMobile } from "react-device-detect";
|
||||
import history from "../history";
|
||||
import TopLoaderService from "@appserver/components/top-loading-indicator";
|
||||
|
||||
import { Encoder } from "./encoder";
|
||||
@ -145,20 +144,6 @@ export function showLoader() {
|
||||
|
||||
export { withLayoutSize } from "./withLayoutSize";
|
||||
|
||||
export function tryRedirectTo(page) {
|
||||
if (
|
||||
window.location.pathname === page ||
|
||||
window.location.pathname.indexOf(page) !== -1
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
//TODO: check if we already on default page
|
||||
//window.location.replace(page);
|
||||
history.push(page);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
export function isMe(user, userName) {
|
||||
return (
|
||||
user && user.id && (userName === "@self" || user.userName === userName)
|
||||
|
14
packages/asc-web-common/utils/tryRedirectTo.js
Normal file
14
packages/asc-web-common/utils/tryRedirectTo.js
Normal file
@ -0,0 +1,14 @@
|
||||
import history from "../history";
|
||||
export default function (page) {
|
||||
if (
|
||||
window.location.pathname === page ||
|
||||
window.location.pathname.indexOf(page) !== -1
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
//TODO: check if we already on default page
|
||||
window.location.replace(page);
|
||||
history.push(page); // SSR crash
|
||||
|
||||
return true;
|
||||
}
|
@ -7,7 +7,9 @@ let percentage = 0;
|
||||
let increasePercentage = 0.75;
|
||||
let moreIncreasePercentage = 3;
|
||||
|
||||
let elem = document.getElementById("ipl-progress-indicator");
|
||||
let elem =
|
||||
typeof document !== "undefined" &&
|
||||
document.getElementById("ipl-progress-indicator");
|
||||
|
||||
const startInterval = () => {
|
||||
if (timerId) return;
|
||||
|
@ -82,9 +82,9 @@
|
||||
"style-loader": "3.2.1",
|
||||
"terser-webpack-plugin": "^5.2.4",
|
||||
"webpack": "5.52.1",
|
||||
"webpack-cli": "4.9.0",
|
||||
"webpack-cli": "4.10.0",
|
||||
"webpack-dev-server": "4.3.1"
|
||||
},
|
||||
"id": "e67be73d-f9ae-4ce1-8fec-1880cb518cb4",
|
||||
"title": "ONLYOFFICE"
|
||||
}
|
||||
}
|
@ -6,6 +6,7 @@ import throttle from "lodash/throttle";
|
||||
import SelectFileDialogAsideView from "./AsideView";
|
||||
import utils from "@appserver/components/utils";
|
||||
import { FilterType } from "@appserver/common/constants";
|
||||
import isEqual from "lodash/isEqual";
|
||||
import SelectionPanel from "../SelectionPanel/SelectionPanelBody";
|
||||
import toastr from "studio/toastr";
|
||||
|
||||
@ -131,6 +132,13 @@ class SelectFileDialog extends React.Component {
|
||||
resultingFolderTree: tree,
|
||||
});
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
if (!isEqual(prevProps, this.props)) {
|
||||
this.setFilter();
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
const {
|
||||
setFolderId,
|
||||
|
@ -199,6 +199,7 @@ class SelectFolderDialog extends React.Component {
|
||||
isRecycleBin,
|
||||
currentFolderId,
|
||||
selectionFiles,
|
||||
selectionButtonPrimary,
|
||||
} = this.props;
|
||||
const {
|
||||
displayType,
|
||||
|
@ -445,7 +445,7 @@ class FilesTableHeader extends React.Component {
|
||||
const sortOrder = isRooms ? roomsFilter.sortOrder : filter.sortOrder;
|
||||
|
||||
// TODO: make some better
|
||||
let needReset = false;
|
||||
let needReset = this.props.isRooms !== this.state.isRooms;
|
||||
let currentColumnStorageName = columnStorageName;
|
||||
let currentColumnInfoPanelStorageName = columnInfoPanelStorageName;
|
||||
|
||||
|
@ -1 +1 @@
|
||||
Subproject commit 022000362f747b9492183e77245f93bf6295b387
|
||||
Subproject commit 2bb4d878b42e8c648dd44fed7d445163772551fa
|
@ -80,9 +80,9 @@
|
||||
"style-loader": "3.2.1",
|
||||
"terser-webpack-plugin": "^5.2.4",
|
||||
"webpack": "5.52.1",
|
||||
"webpack-cli": "4.9.0",
|
||||
"webpack-cli": "4.10.0",
|
||||
"webpack-dev-server": "4.3.1"
|
||||
},
|
||||
"id": "f4d98afd-d336-4332-8778-3c6945c81ea0",
|
||||
"title": "ONLYOFFICE"
|
||||
}
|
||||
}
|
@ -75,8 +75,8 @@
|
||||
"style-loader": "3.2.1",
|
||||
"terser-webpack-plugin": "^5.2.4",
|
||||
"webpack": "5.52.1",
|
||||
"webpack-cli": "4.9.0",
|
||||
"webpack-cli": "4.10.0",
|
||||
"webpack-dev-server": "4.3.1"
|
||||
},
|
||||
"title": "ONLYOFFICE"
|
||||
}
|
||||
}
|
@ -3,7 +3,8 @@ import { withRouter } from "react-router";
|
||||
import PropTypes from "prop-types";
|
||||
import Loader from "@appserver/components/loader";
|
||||
import Section from "@appserver/common/components/Section";
|
||||
import { combineUrl, tryRedirectTo } from "@appserver/common/utils";
|
||||
import { combineUrl } from "@appserver/common/utils";
|
||||
import tryRedirectTo from "@appserver/common/utils/tryRedirectTo";
|
||||
import { inject, observer } from "mobx-react";
|
||||
import { AppServerConfig } from "@appserver/common/constants";
|
||||
|
||||
|
@ -4,7 +4,8 @@ import PropTypes from "prop-types";
|
||||
import { inject, observer } from "mobx-react";
|
||||
import Loader from "@appserver/components/loader";
|
||||
import Section from "@appserver/common/components/Section";
|
||||
import { combineUrl, tryRedirectTo } from "@appserver/common/utils";
|
||||
import { combineUrl } from "@appserver/common/utils";
|
||||
import tryRedirectTo from "@appserver/common/utils/tryRedirectTo";
|
||||
import { AppServerConfig } from "@appserver/common/constants";
|
||||
|
||||
class ChangeEmail extends React.PureComponent {
|
||||
|
@ -10,7 +10,8 @@ import { inject, observer } from "mobx-react";
|
||||
import { StyledPage, StyledBody, StyledHeader } from "./StyledConfirm";
|
||||
import withLoader from "../withLoader";
|
||||
import { getPasswordErrorMessage } from "../../../../helpers/utils";
|
||||
import { createPasswordHash, tryRedirectTo } from "@appserver/common/utils";
|
||||
import { createPasswordHash } from "@appserver/common/utils";
|
||||
import tryRedirectTo from "@appserver/common/utils/tryRedirectTo";
|
||||
import toastr from "@appserver/components/toast/toastr";
|
||||
|
||||
const ChangePasswordForm = (props) => {
|
||||
|
@ -5,7 +5,7 @@ import { useTranslation } from "react-i18next";
|
||||
import Text from "@appserver/components/text";
|
||||
import toastr from "studio/toastr";
|
||||
import Section from "@appserver/common/components/Section";
|
||||
import { tryRedirectTo } from "@appserver/common/utils";
|
||||
import tryRedirectTo from "@appserver/common/utils/tryRedirectTo";
|
||||
import { setDocumentTitle } from "../../../helpers/utils";
|
||||
import { inject, observer } from "mobx-react";
|
||||
import { HomeIllustration, ModuleTile, HomeContainer } from "./sub-components";
|
||||
|
@ -1,7 +1,19 @@
|
||||
{
|
||||
"presets": ["@babel/preset-react", "@babel/preset-env"],
|
||||
"presets": [
|
||||
[
|
||||
"@babel/preset-env",
|
||||
{
|
||||
"bugfixes": true
|
||||
}
|
||||
],
|
||||
"@babel/preset-react"
|
||||
],
|
||||
"plugins": [
|
||||
"@babel/plugin-transform-runtime",
|
||||
"@babel/plugin-proposal-class-properties"
|
||||
]
|
||||
"@loadable/babel-plugin",
|
||||
"babel-plugin-styled-components",
|
||||
["@babel/plugin-proposal-class-properties", { "loose": false }],
|
||||
"@babel/plugin-proposal-export-default-from"
|
||||
],
|
||||
"ignore": ["node_modules", "build"],
|
||||
"sourceType": "unambiguous"
|
||||
}
|
||||
|
11
web/ASC.Web.Editor/babel.config.js
Normal file
11
web/ASC.Web.Editor/babel.config.js
Normal file
@ -0,0 +1,11 @@
|
||||
module.exports = {
|
||||
presets: ["@babel/preset-env", "@babel/preset-react"],
|
||||
plugins: [
|
||||
"@loadable/babel-plugin",
|
||||
"babel-plugin-styled-components",
|
||||
["@babel/plugin-proposal-class-properties", { loose: false }],
|
||||
"@babel/plugin-proposal-export-default-from",
|
||||
],
|
||||
//ignore: ["node_modules", "build"],
|
||||
//sourceType: "unambiguous",
|
||||
};
|
@ -4,45 +4,79 @@
|
||||
"private": true,
|
||||
"homepage": "/products/files/doceditor",
|
||||
"scripts": {
|
||||
"build": "webpack --mode production",
|
||||
"build:personal": "webpack --mode production --env personal=true",
|
||||
"build:test": "webpack --env minimize=false --mode production",
|
||||
"build:test.translation": "webpack --env minimize=false hideText=true --mode production",
|
||||
"build:test.translation:personal": "webpack --env minimize=false hideText=true personal=true --mode production",
|
||||
"build": "yarn clean && yarn build:client && yarn build:server",
|
||||
"build:personal": "yarn clean && yarn build:client-personal && yarn build:server-personal",
|
||||
"build:server": "webpack --mode production --config webpack/webpack.server.js",
|
||||
"build:client": "webpack --mode production --config webpack/webpack.client.js",
|
||||
"build:dev-server": "webpack --config webpack/webpack.server.js",
|
||||
"build:dev-client": "webpack --config webpack/webpack.client.js",
|
||||
"build:server-personal": "webpack --mode production --env personal=true --config webpack/webpack.server.js",
|
||||
"build:client-personal": "webpack --mode production --env personal=true --config webpack/webpack.client.js",
|
||||
"build:dev-server-personal": "webpack --env personal=true --config webpack/webpack.server.js",
|
||||
"build:dev-client-personal": "webpack --env personal=true --config webpack/webpack.client.js",
|
||||
"clean": "shx rm -rf dist",
|
||||
"deploy": "shx --silent mkdir -p ../../build/deploy/products/ASC.Files/editor && shx cp -r dist/* ../../build/deploy/products/ASC.Files/editor",
|
||||
"serve": "serve dist -p 5013",
|
||||
"start": "webpack-cli serve",
|
||||
"start:personal": "webpack-cli serve --env personal=true",
|
||||
"start-prod": "webpack --mode production && serve dist -p 5013"
|
||||
"start": "yarn clean && npm-run-all --parallel start:*",
|
||||
"start-personal": "yarn clean && npm-run-all --parallel start-personal:* ",
|
||||
"start:client": "webpack --config webpack/webpack.client.js --watch --no-cache",
|
||||
"start:server": "webpack --config webpack/webpack.server.js --watch --no-cache",
|
||||
"start:common": "yarn build:dev-client && yarn build:dev-server && nodemon --watch dist/server.js dist/server.js",
|
||||
"start-personal:client": "webpack --env personal=true --config webpack/webpack.client.js --watch --no-cache",
|
||||
"start-personal:server": "webpack --env personal=true --config webpack/webpack.server.js --watch --no-cache",
|
||||
"start-personal:common": "yarn build:dev-client-personal && yarn build:dev-server-personal && nodemon --watch dist/server.js dist/server.js",
|
||||
"deploy": "shx --silent mkdir -p ../../build/deploy/products/ASC.Files/editor && shx cp -r dist/* ../../build/deploy/products/ASC.Files/editor"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.15.5",
|
||||
"@babel/plugin-proposal-class-properties": "^7.14.5",
|
||||
"@babel/plugin-proposal-export-default-from": "^7.14.5",
|
||||
"@babel/plugin-transform-runtime": "^7.15.0",
|
||||
"@babel/preset-env": "^7.15.6",
|
||||
"@babel/preset-react": "^7.14.5",
|
||||
"@svgr/webpack": "^5.5.0",
|
||||
"@babel/core": "^7.18.6",
|
||||
"@babel/plugin-proposal-class-properties": "^7.13.0",
|
||||
"@babel/plugin-proposal-export-default-from": "^7.17.12",
|
||||
"@babel/plugin-transform-runtime": "^7.18.6",
|
||||
"@babel/preset-env": "^7.14.4",
|
||||
"@babel/preset-react": "^7.13.13",
|
||||
"@babel/register": "^7.18.6",
|
||||
"@loadable/babel-plugin": "^5.13.2",
|
||||
"@loadable/component": "^5.15.2",
|
||||
"@loadable/server": "^5.15.2",
|
||||
"@loadable/webpack-plugin": "^5.15.0",
|
||||
"@svgr/webpack": "^6.2.1",
|
||||
"babel-loader": "^8.2.2",
|
||||
"babel-plugin-styled-components": "^1.12.0",
|
||||
"clean-webpack-plugin": "^4.0.0",
|
||||
"copy-webpack-plugin": "^9.0.1",
|
||||
"css-loader": "^6.2.0",
|
||||
"copy-webpack-plugin": "^11.0.0",
|
||||
"cross-env": "^7.0.3",
|
||||
"css-loader": "^6.7.1",
|
||||
"dotenv": "^16.0.1",
|
||||
"external-remotes-plugin": "^1.0.0",
|
||||
"file-loader": "^6.2.0",
|
||||
"html-webpack-plugin": "5.3.2",
|
||||
"json-loader": "^0.5.7",
|
||||
"sass": "^1.39.2",
|
||||
"sass-loader": "^12.1.0",
|
||||
"serve": "12.0.1",
|
||||
"shx": "^0.3.3",
|
||||
"source-map-loader": "^3.0.0",
|
||||
"style-loader": "3.2.1",
|
||||
"terser-webpack-plugin": "^5.2.4",
|
||||
"nodemon": "^2.0.7",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"sass": "^1.53.0",
|
||||
"sass-loader": "^13.0.2",
|
||||
"shx": "^0.3.4",
|
||||
"source-map-loader": "^4.0.0",
|
||||
"style-loader": "^3.3.1",
|
||||
"styled-components": "^5.3.5",
|
||||
"terser-webpack-plugin": "^5.1.2",
|
||||
"webpack": "5.52.1",
|
||||
"webpack-cli": "4.9.0",
|
||||
"webpack-dev-server": "4.3.1"
|
||||
"webpack-cli": "4.10.0",
|
||||
"webpack-dev-server": "4.3.1",
|
||||
"webpack-hot-middleware": "^2.25.0",
|
||||
"webpack-manifest-plugin": "^5.0.0",
|
||||
"webpack-merge": "^5.7.3",
|
||||
"webpack-node-externals": "^3.0.0",
|
||||
"ws": "^8.8.0"
|
||||
},
|
||||
"id": "e67be73d-f9ae-4ce1-8fec-1880cb518cb4",
|
||||
"title": "ONLYOFFICE"
|
||||
}
|
||||
"title": "ONLYOFFICE",
|
||||
"socketPath": "/products/files/doceditor/ws",
|
||||
"dependencies": {
|
||||
"compression": "^1.7.4",
|
||||
"express": "^4.18.1",
|
||||
"i18next-express-middleware": "^2.0.0",
|
||||
"i18next-fs-backend": "^1.1.4",
|
||||
"morgan": "^1.10.0",
|
||||
"winston": "^3.8.1",
|
||||
"winston-daily-rotate-file": "^4.7.1"
|
||||
}
|
||||
}
|
@ -1,361 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width, initial-scale=1, shrink-to-fit=no, user-scalable=no, viewport-fit=cover"
|
||||
/>
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<!--
|
||||
manifest.json provides metadata used when your web app is added to the
|
||||
homescreen on Android. See https://developers.google.com/web/fundamentals/engage-and-retain/web-app-manifest/
|
||||
-->
|
||||
<link id="favicon" rel="shortcut icon" sizes="any" href="/favicon.ico" />
|
||||
|
||||
<link rel="manifest" href="/manifest.json" />
|
||||
<!-- Tell the browser it's a PWA -->
|
||||
<!-- <meta name="mobile-web-app-capable" content="yes" /> -->
|
||||
<!-- Tell iOS it's a PWA -->
|
||||
<!-- <meta name="apple-mobile-web-app-capable" content="yes" /> -->
|
||||
<link rel="apple-touch-icon" href="/appIcon-180.png" />
|
||||
|
||||
<!--
|
||||
Notice the use of %PUBLIC_URL% in the tags above.
|
||||
It will be replaced with the URL of the `public` folder during the build.
|
||||
Only files inside the `public` folder can be referenced from the HTML.
|
||||
|
||||
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
|
||||
work correctly both with client-side routing and a non-root public URL.
|
||||
Learn how to configure a non-root public URL by running `npm run build`.
|
||||
-->
|
||||
<style type="text/css">
|
||||
.loadmask {
|
||||
left: 0;
|
||||
top: 0;
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
border: none;
|
||||
background-color: #f4f4f4;
|
||||
z-index: 1001;
|
||||
}
|
||||
.loader-page {
|
||||
width: 100%;
|
||||
height: 170px;
|
||||
bottom: 42%;
|
||||
position: absolute;
|
||||
text-align: center;
|
||||
line-height: 10px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.loader-page-romb {
|
||||
width: 40px;
|
||||
display: inline-block;
|
||||
}
|
||||
.romb {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
position: absolute;
|
||||
background: red;
|
||||
border-radius: 6px;
|
||||
-webkit-transform: rotate(135deg) skew(20deg, 20deg);
|
||||
-moz-transform: rotate(135deg) skew(20deg, 20deg);
|
||||
-ms-transform: rotate(135deg) skew(20deg, 20deg);
|
||||
-o-transform: rotate(135deg) skew(20deg, 20deg);
|
||||
-webkit-animation: movedown 3s infinite ease;
|
||||
-moz-animation: movedown 3s infinite ease;
|
||||
-ms-animation: movedown 3s infinite ease;
|
||||
-o-animation: movedown 3s infinite ease;
|
||||
animation: movedown 3s infinite ease;
|
||||
}
|
||||
#blue {
|
||||
z-index: 3;
|
||||
background: #55bce6;
|
||||
-webkit-animation-name: blue;
|
||||
-moz-animation-name: blue;
|
||||
-ms-animation-name: blue;
|
||||
-o-animation-name: blue;
|
||||
animation-name: blue;
|
||||
}
|
||||
#red {
|
||||
z-index: 1;
|
||||
background: #de7a59;
|
||||
-webkit-animation-name: red;
|
||||
-moz-animation-name: red;
|
||||
-ms-animation-name: red;
|
||||
-o-animation-name: red;
|
||||
animation-name: red;
|
||||
}
|
||||
#green {
|
||||
z-index: 2;
|
||||
background: #a1cb5c;
|
||||
-webkit-animation-name: green;
|
||||
-moz-animation-name: green;
|
||||
-ms-animation-name: green;
|
||||
-o-animation-name: green;
|
||||
animation-name: green;
|
||||
}
|
||||
@-webkit-keyframes red {
|
||||
0% {
|
||||
top: 120px;
|
||||
background: #de7a59;
|
||||
}
|
||||
10% {
|
||||
top: 120px;
|
||||
background: #f2cbbf;
|
||||
}
|
||||
14% {
|
||||
background: #f4f4f4;
|
||||
top: 120px;
|
||||
}
|
||||
15% {
|
||||
background: #f4f4f4;
|
||||
top: 0;
|
||||
}
|
||||
20% {
|
||||
background: #e6e4e4;
|
||||
}
|
||||
30% {
|
||||
background: #d2d2d2;
|
||||
}
|
||||
40% {
|
||||
top: 120px;
|
||||
}
|
||||
100% {
|
||||
top: 120px;
|
||||
background: #de7a59;
|
||||
}
|
||||
}
|
||||
@keyframesred {
|
||||
0% {
|
||||
top: 120px;
|
||||
background: #de7a59;
|
||||
}
|
||||
10% {
|
||||
top: 120px;
|
||||
background: #f2cbbf;
|
||||
}
|
||||
14% {
|
||||
background: #f4f4f4;
|
||||
top: 120px;
|
||||
}
|
||||
15% {
|
||||
background: #f4f4f4;
|
||||
top: 0;
|
||||
}
|
||||
20% {
|
||||
background: #e6e4e4;
|
||||
}
|
||||
30% {
|
||||
background: #d2d2d2;
|
||||
}
|
||||
40% {
|
||||
top: 120px;
|
||||
}
|
||||
100% {
|
||||
top: 120px;
|
||||
background: #de7a59;
|
||||
}
|
||||
}
|
||||
@-webkit-keyframes green {
|
||||
0% {
|
||||
top: 110px;
|
||||
background: #a1cb5c;
|
||||
opacity: 1;
|
||||
}
|
||||
10% {
|
||||
top: 110px;
|
||||
background: #cbe0ac;
|
||||
opacity: 1;
|
||||
}
|
||||
14% {
|
||||
background: #f4f4f4;
|
||||
top: 110px;
|
||||
opacity: 1;
|
||||
}
|
||||
15% {
|
||||
background: #f4f4f4;
|
||||
top: 0;
|
||||
opacity: 1;
|
||||
}
|
||||
20% {
|
||||
background: #f4f4f4;
|
||||
top: 0;
|
||||
opacity: 0;
|
||||
}
|
||||
25% {
|
||||
background: #efefef;
|
||||
top: 0;
|
||||
opacity: 1;
|
||||
}
|
||||
30% {
|
||||
background: #e6e4e4;
|
||||
}
|
||||
70% {
|
||||
top: 110px;
|
||||
}
|
||||
100% {
|
||||
top: 110px;
|
||||
background: #a1cb5c;
|
||||
}
|
||||
}
|
||||
@keyframes green {
|
||||
0% {
|
||||
top: 110px;
|
||||
background: #a1cb5c;
|
||||
opacity: 1;
|
||||
}
|
||||
10% {
|
||||
top: 110px;
|
||||
background: #cbe0ac;
|
||||
opacity: 1;
|
||||
}
|
||||
14% {
|
||||
background: #f4f4f4;
|
||||
top: 110px;
|
||||
opacity: 1;
|
||||
}
|
||||
15% {
|
||||
background: #f4f4f4;
|
||||
top: 0;
|
||||
opacity: 1;
|
||||
}
|
||||
20% {
|
||||
background: #f4f4f4;
|
||||
top: 0;
|
||||
opacity: 0;
|
||||
}
|
||||
25% {
|
||||
background: #efefef;
|
||||
top: 0;
|
||||
opacity: 1;
|
||||
}
|
||||
30% {
|
||||
background: #e6e4e4;
|
||||
}
|
||||
70% {
|
||||
top: 110px;
|
||||
}
|
||||
100% {
|
||||
top: 110px;
|
||||
background: #a1cb5c;
|
||||
}
|
||||
}
|
||||
@-webkit-keyframes blue {
|
||||
0% {
|
||||
top: 100px;
|
||||
background: #55bce6;
|
||||
opacity: 1;
|
||||
}
|
||||
10% {
|
||||
top: 100px;
|
||||
background: #bfe8f8;
|
||||
opacity: 1;
|
||||
}
|
||||
14% {
|
||||
background: #f4f4f4;
|
||||
top: 100px;
|
||||
opacity: 1;
|
||||
}
|
||||
15% {
|
||||
background: #f4f4f4;
|
||||
top: 0;
|
||||
opacity: 1;
|
||||
}
|
||||
20% {
|
||||
background: #f4f4f4;
|
||||
top: 0;
|
||||
opacity: 0;
|
||||
}
|
||||
25% {
|
||||
background: #f4f4f4;
|
||||
top: 0;
|
||||
opacity: 0;
|
||||
}
|
||||
45% {
|
||||
background: #efefef;
|
||||
top: 0;
|
||||
opacity: 0.2;
|
||||
}
|
||||
100% {
|
||||
top: 100px;
|
||||
background: #55bce6;
|
||||
}
|
||||
}
|
||||
@keyframes blue {
|
||||
0% {
|
||||
top: 100px;
|
||||
background: #55bce6;
|
||||
opacity: 1;
|
||||
}
|
||||
10% {
|
||||
top: 100px;
|
||||
background: #bfe8f8;
|
||||
opacity: 1;
|
||||
}
|
||||
14% {
|
||||
background: #f4f4f4;
|
||||
top: 100px;
|
||||
opacity: 1;
|
||||
}
|
||||
15% {
|
||||
background: #f4f4f4;
|
||||
top: 0;
|
||||
opacity: 1;
|
||||
}
|
||||
20% {
|
||||
background: #f4f4f4;
|
||||
top: 0;
|
||||
opacity: 0;
|
||||
}
|
||||
25% {
|
||||
background: #fff;
|
||||
top: 0;
|
||||
opacity: 0;
|
||||
}
|
||||
45% {
|
||||
background: #efefef;
|
||||
top: 0;
|
||||
opacity: 0.2;
|
||||
}
|
||||
100% {
|
||||
top: 100px;
|
||||
background: #55bce6;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<title><%= htmlWebpackPlugin.options.title %></title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript> You need to enable JavaScript to run this app. </noscript>
|
||||
<div id="loader" class="loadmask">
|
||||
<div class="loader-page">
|
||||
<div class="loader-page-romb">
|
||||
<div class="romb" id="blue"></div>
|
||||
<div class="romb" id="green"></div>
|
||||
<div class="romb" id="red"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="root"></div>
|
||||
<!--
|
||||
This HTML file is a template.
|
||||
If you open it directly in the browser, you will see an empty page.
|
||||
|
||||
You can add webfonts, meta tags, or analytics to this file.
|
||||
The build step will place the bundled scripts into the <body> tag.
|
||||
|
||||
To begin the development, run `npm start` or `yarn start`.
|
||||
To create a production bundle, use `npm run build` or `yarn build`.
|
||||
-->
|
||||
<script>
|
||||
const tempElm = document.getElementById("loader");
|
||||
tempElm.style.backgroundColor =
|
||||
localStorage.theme === "Dark" ? "#333333" : "#f4f4f4";
|
||||
|
||||
console.log("It's Editor INIT");
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
@ -1,19 +0,0 @@
|
||||
//import "@appserver/common/utils/wdyr";
|
||||
import React from "react";
|
||||
import Editor from "./Editor";
|
||||
import ErrorBoundary from "@appserver/common/components/ErrorBoundary";
|
||||
import "@appserver/common/custom.scss";
|
||||
import { AppServerConfig } from "@appserver/common/constants";
|
||||
import { combineUrl } from "@appserver/common/utils";
|
||||
|
||||
const App = () => {
|
||||
const onError = () =>
|
||||
window.open(combineUrl(AppServerConfig.proxyURL, "/login"), "_self");
|
||||
return (
|
||||
<ErrorBoundary onError={onError}>
|
||||
<Editor />
|
||||
</ErrorBoundary>
|
||||
);
|
||||
};
|
||||
|
||||
export default App;
|
@ -1,983 +0,0 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
|
||||
import Toast from "@appserver/components/toast";
|
||||
import toastr from "studio/toastr";
|
||||
import { toast } from "react-toastify";
|
||||
import { Trans } from "react-i18next";
|
||||
import Box from "@appserver/components/box";
|
||||
import { regDesktop } from "@appserver/common/desktop";
|
||||
import {
|
||||
combineUrl,
|
||||
getObjectByLocation,
|
||||
loadScript,
|
||||
isRetina,
|
||||
getCookie,
|
||||
setCookie,
|
||||
assign,
|
||||
} from "@appserver/common/utils";
|
||||
import {
|
||||
getDocServiceUrl,
|
||||
openEdit,
|
||||
setEncryptionKeys,
|
||||
getEncryptionAccess,
|
||||
getFileInfo,
|
||||
updateFile,
|
||||
removeFromFavorite,
|
||||
markAsFavorite,
|
||||
getPresignedUri,
|
||||
convertFile,
|
||||
checkFillFormDraft,
|
||||
getEditHistory,
|
||||
getEditDiff,
|
||||
restoreDocumentsVersion,
|
||||
getSettingsFiles,
|
||||
} from "@appserver/common/api/files";
|
||||
import FilesFilter from "@appserver/common/api/files/filter";
|
||||
|
||||
import throttle from "lodash/throttle";
|
||||
import { isIOS, deviceType } from "react-device-detect";
|
||||
import { homepage } from "../package.json";
|
||||
|
||||
import { AppServerConfig } from "@appserver/common/constants";
|
||||
import SharingDialog from "files/SharingDialog";
|
||||
import { getDefaultFileName, SaveAs } from "files/utils";
|
||||
import SelectFileDialog from "files/SelectFileDialog";
|
||||
import SelectFolderDialog from "files/SelectFolderDialog";
|
||||
import PreparationPortalDialog from "studio/PreparationPortalDialog";
|
||||
import { StyledSelectFolder } from "./StyledEditor";
|
||||
import i18n from "./i18n";
|
||||
import Text from "@appserver/components/text";
|
||||
import TextInput from "@appserver/components/text-input";
|
||||
import Checkbox from "@appserver/components/checkbox";
|
||||
import { isMobile } from "react-device-detect";
|
||||
import store from "studio/store";
|
||||
|
||||
import ThemeProvider from "@appserver/components/theme-provider";
|
||||
|
||||
const { auth: authStore } = store;
|
||||
const theme = store.auth.settingsStore.theme;
|
||||
|
||||
let documentIsReady = false;
|
||||
|
||||
const text = "word";
|
||||
const spreadSheet = "cell";
|
||||
const presentation = "slide";
|
||||
const insertImageAction = "imageFileType";
|
||||
const mailMergeAction = "mailMergeFileType";
|
||||
const compareFilesAction = "documentsFileType";
|
||||
|
||||
let docTitle = null;
|
||||
let actionLink;
|
||||
let docSaved = null;
|
||||
let docEditor;
|
||||
let fileInfo;
|
||||
let successAuth;
|
||||
let isSharingAccess;
|
||||
let user = null;
|
||||
let personal = IS_PERSONAL || null;
|
||||
let config;
|
||||
let url = window.location.href;
|
||||
const filesUrl = url.substring(0, url.indexOf("/doceditor"));
|
||||
//const doc = url.indexOf("doc=") !== -1 ? url.split("doc=")[1] : null;
|
||||
|
||||
toast.configure();
|
||||
|
||||
const Editor = () => {
|
||||
const urlParams = getObjectByLocation(window.location);
|
||||
const decodedId = urlParams
|
||||
? urlParams.fileId || urlParams.fileid || null
|
||||
: null;
|
||||
let fileId =
|
||||
typeof decodedId === "string" ? encodeURIComponent(decodedId) : decodedId;
|
||||
let version = urlParams ? urlParams.version || null : null;
|
||||
const doc = urlParams ? urlParams.doc || null : null;
|
||||
const isDesktop = window["AscDesktopEditor"] !== undefined;
|
||||
const view = url.indexOf("action=view") !== -1;
|
||||
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [isAuthenticated, setIsAuthenticated] = useState(true);
|
||||
const [titleSelectorFolder, setTitleSelectorFolder] = useState("");
|
||||
const [extension, setExtension] = useState();
|
||||
const [urlSelectorFolder, setUrlSelectorFolder] = useState("");
|
||||
const [openNewTab, setNewOpenTab] = useState(false);
|
||||
const [typeInsertImageAction, setTypeInsertImageAction] = useState();
|
||||
const throttledChangeTitle = throttle(() => changeTitle(), 500);
|
||||
|
||||
const [settings, setSettings] = useState(null);
|
||||
|
||||
const [
|
||||
preparationPortalDialogVisible,
|
||||
setPreparationPortalDialogVisible,
|
||||
] = useState(false);
|
||||
|
||||
let filesSettings;
|
||||
|
||||
useEffect(() => {
|
||||
const tempElm = document.getElementById("loader");
|
||||
tempElm.style.backgroundColor = theme.backgroundColor;
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (isRetina() && getCookie("is_retina") == null) {
|
||||
setCookie("is_retina", true, { path: "/" });
|
||||
}
|
||||
|
||||
init();
|
||||
}, []);
|
||||
|
||||
const canConvert = (extension) => {
|
||||
const array = filesSettings?.extsMustConvert || [];
|
||||
const result = array.findIndex((item) => item === extension);
|
||||
return result === -1 ? false : true;
|
||||
};
|
||||
|
||||
const loadUsersRightsList = () => {
|
||||
SharingDialog.getSharingSettings(fileId).then((sharingSettings) => {
|
||||
docEditor.setSharingSettings({
|
||||
sharingSettings,
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const insertImage = (link) => {
|
||||
const token = link.token;
|
||||
|
||||
docEditor.insertImage({
|
||||
...typeInsertImageAction,
|
||||
fileType: link.filetype,
|
||||
...(token && { token }),
|
||||
url: link.url,
|
||||
});
|
||||
};
|
||||
|
||||
const mailMerge = (link) => {
|
||||
const token = link.token;
|
||||
|
||||
docEditor.setMailMergeRecipients({
|
||||
fileType: link.filetype,
|
||||
...(token && { token }),
|
||||
url: link.url,
|
||||
});
|
||||
};
|
||||
|
||||
const compareFiles = (link) => {
|
||||
const token = link.token;
|
||||
|
||||
docEditor.setRevisedFile({
|
||||
fileType: link.filetype,
|
||||
...(token && { token }),
|
||||
url: link.url,
|
||||
});
|
||||
};
|
||||
const updateFavorite = (favorite) => {
|
||||
docEditor.setFavorite(favorite);
|
||||
};
|
||||
|
||||
const initDesktop = (cfg) => {
|
||||
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) => {
|
||||
console.log(error);
|
||||
toastr.error(
|
||||
typeof error === "string" ? error : error.message,
|
||||
null,
|
||||
0,
|
||||
true
|
||||
);
|
||||
});
|
||||
},
|
||||
i18n.t
|
||||
);
|
||||
};
|
||||
|
||||
const convertDocumentUrl = async () => {
|
||||
const convert = await convertFile(fileId, null, true);
|
||||
return convert && convert[0]?.result;
|
||||
};
|
||||
|
||||
const init = async () => {
|
||||
try {
|
||||
if (!fileId) return;
|
||||
|
||||
console.log(
|
||||
`Editor componentDidMount fileId=${fileId}, version=${version}, doc=${doc}`
|
||||
);
|
||||
|
||||
if (isIPad()) {
|
||||
const vh = window.innerHeight * 0.01;
|
||||
document.documentElement.style.setProperty("--vh", `${vh}px`);
|
||||
}
|
||||
|
||||
//showLoader();
|
||||
const docApiUrl = await getDocServiceUrl();
|
||||
|
||||
try {
|
||||
await authStore.init(true);
|
||||
user = authStore.userStore.user;
|
||||
if (user) {
|
||||
filesSettings = await getSettingsFiles();
|
||||
setSettings(filesSettings);
|
||||
}
|
||||
personal = authStore.settingsStore.personal;
|
||||
successAuth = !!user;
|
||||
|
||||
const { socketHelper } = authStore.settingsStore;
|
||||
socketHelper.emit({
|
||||
command: "subscribe",
|
||||
data: "backup-restore",
|
||||
});
|
||||
socketHelper.on("restore-backup", () => {
|
||||
setPreparationPortalDialogVisible(true);
|
||||
});
|
||||
} catch (e) {
|
||||
successAuth = false;
|
||||
}
|
||||
|
||||
if (!doc && !successAuth) {
|
||||
localStorage.setItem("redirectPath", window.location.href);
|
||||
|
||||
window.open(
|
||||
combineUrl(
|
||||
AppServerConfig.proxyURL,
|
||||
personal ? "/sign-in" : "/login"
|
||||
),
|
||||
"_self",
|
||||
"",
|
||||
true
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (successAuth) {
|
||||
try {
|
||||
fileInfo = await getFileInfo(fileId);
|
||||
|
||||
if (url.indexOf("#message/") > -1) {
|
||||
if (canConvert(fileInfo.fileExst)) {
|
||||
const result = await convertDocumentUrl();
|
||||
|
||||
const splitUrl = url.split("#message/");
|
||||
|
||||
if (result) {
|
||||
const newUrl = `${result.webUrl}#message/${splitUrl[1]}`;
|
||||
|
||||
history.pushState({}, null, newUrl);
|
||||
|
||||
fileInfo = result;
|
||||
url = newUrl;
|
||||
fileId = result.id;
|
||||
version = result.version;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
|
||||
setIsAuthenticated(successAuth);
|
||||
}
|
||||
|
||||
config = await openEdit(fileId, version, doc, view);
|
||||
|
||||
if (
|
||||
!view &&
|
||||
fileInfo &&
|
||||
fileInfo.canWebRestrictedEditing &&
|
||||
fileInfo.canFillForms &&
|
||||
!fileInfo.canEdit
|
||||
) {
|
||||
try {
|
||||
const formUrl = await checkFillFormDraft(fileId);
|
||||
history.pushState({}, null, formUrl);
|
||||
|
||||
document.location.reload();
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
}
|
||||
|
||||
actionLink = config?.editorConfig?.actionLink;
|
||||
|
||||
if (isDesktop) {
|
||||
initDesktop(config);
|
||||
}
|
||||
|
||||
isSharingAccess = fileInfo && fileInfo.canShare;
|
||||
|
||||
if (view) {
|
||||
config.editorConfig.mode = "view";
|
||||
}
|
||||
|
||||
setIsLoading(false);
|
||||
|
||||
loadScript(docApiUrl, "scripDocServiceAddress", () => onLoad());
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
toastr.error(
|
||||
typeof error === "string" ? error : error.message,
|
||||
null,
|
||||
0,
|
||||
true
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const isIPad = () => {
|
||||
return isIOS && deviceType === "tablet";
|
||||
};
|
||||
|
||||
const setFavicon = (documentType) => {
|
||||
const favicon = document.getElementById("favicon");
|
||||
if (!favicon) return;
|
||||
let icon = null;
|
||||
switch (documentType) {
|
||||
case text:
|
||||
icon = "text.ico";
|
||||
break;
|
||||
case presentation:
|
||||
icon = "presentation.ico";
|
||||
break;
|
||||
case spreadSheet:
|
||||
icon = "spreadsheet.ico";
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (icon) favicon.href = `${homepage}/images/${icon}`;
|
||||
};
|
||||
|
||||
const changeTitle = () => {
|
||||
docSaved ? setDocumentTitle(docTitle) : setDocumentTitle(`*${docTitle}`);
|
||||
};
|
||||
|
||||
const setDocumentTitle = (subTitle = null) => {
|
||||
//const { isAuthenticated, settingsStore, product: currentModule } = auth;
|
||||
//const { organizationName } = settingsStore;
|
||||
const organizationName = "ONLYOFFICE"; //TODO: Replace to API variant
|
||||
const moduleTitle = "Documents"; //TODO: Replace to API variant
|
||||
|
||||
let title;
|
||||
if (subTitle) {
|
||||
if (isAuthenticated && moduleTitle) {
|
||||
title = subTitle + " - " + moduleTitle;
|
||||
} else {
|
||||
title = subTitle + " - " + organizationName;
|
||||
}
|
||||
} else if (moduleTitle && organizationName) {
|
||||
title = moduleTitle + " - " + organizationName;
|
||||
} else {
|
||||
title = organizationName;
|
||||
}
|
||||
|
||||
document.title = title;
|
||||
};
|
||||
|
||||
const onLoad = () => {
|
||||
try {
|
||||
if (!window.DocsAPI) throw new Error("DocsAPI is not defined");
|
||||
|
||||
console.log("Editor config: ", config);
|
||||
|
||||
docTitle = config.document.title;
|
||||
|
||||
setFavicon(config.documentType);
|
||||
setDocumentTitle(docTitle);
|
||||
|
||||
if (isMobile) {
|
||||
config.type = "mobile";
|
||||
}
|
||||
|
||||
let goBack;
|
||||
|
||||
if (fileInfo) {
|
||||
const filterObj = FilesFilter.getDefault();
|
||||
filterObj.folder = fileInfo.folderId;
|
||||
const urlFilter = filterObj.toUrlParams();
|
||||
|
||||
goBack = {
|
||||
blank: true,
|
||||
requestClose: false,
|
||||
text: i18n.t("FileLocation"),
|
||||
url: `${combineUrl(filesUrl, `/filter?${urlFilter}`)}`,
|
||||
};
|
||||
}
|
||||
|
||||
config.editorConfig.customization = {
|
||||
...config.editorConfig.customization,
|
||||
goback: goBack,
|
||||
};
|
||||
|
||||
if (personal && !fileInfo) {
|
||||
//TODO: add conditions for SaaS
|
||||
config.document.info.favorite = null;
|
||||
}
|
||||
|
||||
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,
|
||||
};
|
||||
}
|
||||
|
||||
if (successAuth) {
|
||||
const documentType = config.documentType;
|
||||
const fileExt =
|
||||
documentType === text
|
||||
? "docx"
|
||||
: documentType === presentation
|
||||
? "pptx"
|
||||
: "xlsx";
|
||||
|
||||
const defaultFileName = getDefaultFileName(fileExt);
|
||||
|
||||
if (!user.isVisitor)
|
||||
config.editorConfig.createUrl = combineUrl(
|
||||
window.location.origin,
|
||||
AppServerConfig.proxyURL,
|
||||
"products/files/",
|
||||
`/httphandlers/filehandler.ashx?action=create&doctype=text&title=${encodeURIComponent(
|
||||
defaultFileName
|
||||
)}`
|
||||
);
|
||||
}
|
||||
let onRequestSharingSettings,
|
||||
onRequestRename,
|
||||
onRequestSaveAs,
|
||||
onRequestInsertImage,
|
||||
onRequestMailMergeRecipients,
|
||||
onRequestCompareFile,
|
||||
onRequestRestore;
|
||||
|
||||
if (isSharingAccess) {
|
||||
onRequestSharingSettings = onSDKRequestSharingSettings;
|
||||
}
|
||||
|
||||
if (fileInfo && fileInfo.canEdit && !fileInfo.providerKey) {
|
||||
onRequestRename = onSDKRequestRename;
|
||||
}
|
||||
|
||||
if (successAuth) {
|
||||
onRequestSaveAs = onSDKRequestSaveAs;
|
||||
onRequestInsertImage = onSDKRequestInsertImage;
|
||||
onRequestMailMergeRecipients = onSDKRequestMailMergeRecipients;
|
||||
onRequestCompareFile = onSDKRequestCompareFile;
|
||||
}
|
||||
|
||||
if (!!config.document.permissions.changeHistory) {
|
||||
onRequestRestore = onSDKRequestRestore;
|
||||
}
|
||||
const events = {
|
||||
events: {
|
||||
onAppReady: onSDKAppReady,
|
||||
onDocumentStateChange: onDocumentStateChange,
|
||||
onMetaChange: onMetaChange,
|
||||
onDocumentReady: onDocumentReady,
|
||||
onInfo: onSDKInfo,
|
||||
onWarning: onSDKWarning,
|
||||
onError: onSDKError,
|
||||
onRequestSharingSettings,
|
||||
onRequestRename,
|
||||
onMakeActionLink: onMakeActionLink,
|
||||
onRequestInsertImage,
|
||||
onRequestSaveAs,
|
||||
onRequestMailMergeRecipients,
|
||||
onRequestCompareFile,
|
||||
onRequestEditRights: onSDKRequestEditRights,
|
||||
onRequestHistory: onSDKRequestHistory,
|
||||
onRequestHistoryClose: onSDKRequestHistoryClose,
|
||||
onRequestHistoryData: onSDKRequestHistoryData,
|
||||
onRequestRestore,
|
||||
},
|
||||
};
|
||||
|
||||
const newConfig = Object.assign(config, events);
|
||||
|
||||
docEditor = window.DocsAPI.DocEditor("editor", newConfig);
|
||||
|
||||
assign(window, ["ASC", "Files", "Editor", "docEditor"], docEditor); //Do not remove: it's for Back button on Mobile App
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
toastr.error(error.message, null, 0, true);
|
||||
}
|
||||
};
|
||||
|
||||
const onSDKRequestHistoryData = async (event) => {
|
||||
const version = event.data;
|
||||
|
||||
try {
|
||||
const versionDifference = await getEditDiff(fileId, version, doc);
|
||||
const changesUrl = versionDifference.changesUrl;
|
||||
const previous = versionDifference.previous;
|
||||
const token = versionDifference.token;
|
||||
|
||||
docEditor.setHistoryData({
|
||||
...(changesUrl && { changesUrl }),
|
||||
key: versionDifference.key,
|
||||
fileType: versionDifference.fileType,
|
||||
...(previous && {
|
||||
previous: {
|
||||
fileType: previous.fileType,
|
||||
key: previous.key,
|
||||
url: previous.url,
|
||||
},
|
||||
}),
|
||||
...(token && { token }),
|
||||
url: versionDifference.url,
|
||||
version,
|
||||
});
|
||||
} catch (e) {
|
||||
docEditor.setHistoryData({
|
||||
error: `${e}`, //TODO: maybe need to display something else.
|
||||
version,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const onSDKRequestHistoryClose = () => {
|
||||
document.location.reload();
|
||||
};
|
||||
|
||||
const getDocumentHistory = (fileHistory, historyLength) => {
|
||||
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;
|
||||
};
|
||||
const getCurrentDocumentVersion = (fileHistory, historyLength) => {
|
||||
return url.indexOf("&version=") !== -1
|
||||
? +url.split("&version=")[1]
|
||||
: fileHistory[historyLength - 1].version;
|
||||
};
|
||||
const onSDKRequestHistory = async () => {
|
||||
try {
|
||||
const fileHistory = await getEditHistory(fileId, doc);
|
||||
const historyLength = fileHistory.length;
|
||||
|
||||
docEditor.refreshHistory({
|
||||
currentVersion: getCurrentDocumentVersion(fileHistory, historyLength),
|
||||
history: getDocumentHistory(fileHistory, historyLength),
|
||||
});
|
||||
} catch (e) {
|
||||
docEditor.refreshHistory({
|
||||
error: `${e}`, //TODO: maybe need to display something else.
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const onSDKRequestRestore = async (event) => {
|
||||
const restoreVersion = event.data.version;
|
||||
try {
|
||||
const updateVersions = await restoreDocumentsVersion(
|
||||
fileId,
|
||||
restoreVersion,
|
||||
doc
|
||||
);
|
||||
const historyLength = updateVersions.length;
|
||||
docEditor.refreshHistory({
|
||||
currentVersion: getCurrentDocumentVersion(
|
||||
updateVersions,
|
||||
historyLength
|
||||
),
|
||||
history: getDocumentHistory(updateVersions, historyLength),
|
||||
});
|
||||
} catch (e) {
|
||||
docEditor.refreshHistory({
|
||||
error: `${e}`, //TODO: maybe need to display something else.
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const onSDKAppReady = () => {
|
||||
console.log("ONLYOFFICE Document Editor is ready");
|
||||
|
||||
const tempElm = document.getElementById("loader");
|
||||
if (tempElm) {
|
||||
tempElm.outerHTML = "";
|
||||
}
|
||||
|
||||
const index = url.indexOf("#message/");
|
||||
if (index > -1) {
|
||||
const splitUrl = url.split("#message/");
|
||||
if (splitUrl.length === 2) {
|
||||
let raw = splitUrl[1]?.trim();
|
||||
const message = decodeURIComponent(raw).replace(/\+/g, " ");
|
||||
docEditor.showMessage(message);
|
||||
history.pushState({}, null, url.substring(0, index));
|
||||
}
|
||||
} else {
|
||||
if (config?.Error) docEditor.showMessage(config.Error);
|
||||
}
|
||||
};
|
||||
|
||||
const onSDKInfo = (event) => {
|
||||
console.log(
|
||||
"ONLYOFFICE Document Editor is opened in mode " + event.data.mode
|
||||
);
|
||||
};
|
||||
|
||||
const onSDKRequestEditRights = async () => {
|
||||
console.log("ONLYOFFICE Document Editor requests editing rights");
|
||||
const index = url.indexOf("&action=view");
|
||||
|
||||
if (index) {
|
||||
let convertUrl = url.substring(0, index);
|
||||
|
||||
if (canConvert(fileInfo.fileExst)) {
|
||||
const newUrl = await convertDocumentUrl();
|
||||
if (newUrl) {
|
||||
convertUrl = newUrl.webUrl;
|
||||
}
|
||||
}
|
||||
|
||||
history.pushState({}, null, convertUrl);
|
||||
document.location.reload();
|
||||
}
|
||||
};
|
||||
|
||||
const [isVisible, setIsVisible] = useState(false);
|
||||
const [isFileDialogVisible, setIsFileDialogVisible] = useState(false);
|
||||
const [isFolderDialogVisible, setIsFolderDialogVisible] = useState(false);
|
||||
const [filesType, setFilesType] = useState("");
|
||||
|
||||
const onSDKRequestSharingSettings = () => {
|
||||
setIsVisible(true);
|
||||
};
|
||||
|
||||
const onSDKRequestRename = (event) => {
|
||||
const title = event.data;
|
||||
|
||||
updateFile(fileId, title);
|
||||
};
|
||||
|
||||
const onMakeActionLink = (event) => {
|
||||
var ACTION_DATA = event.data;
|
||||
|
||||
const link = generateLink(ACTION_DATA);
|
||||
|
||||
const urlFormation = !actionLink ? url : url.split("&anchor=")[0];
|
||||
|
||||
const linkFormation = `${urlFormation}&anchor=${link}`;
|
||||
|
||||
docEditor.setActionLink(linkFormation);
|
||||
};
|
||||
|
||||
const generateLink = (actionData) => {
|
||||
return encodeURIComponent(JSON.stringify(actionData));
|
||||
};
|
||||
|
||||
const onCancel = () => {
|
||||
setIsVisible(false);
|
||||
};
|
||||
|
||||
const onSDKWarning = (event) => {
|
||||
console.log(
|
||||
"ONLYOFFICE Document Editor reports a warning: code " +
|
||||
event.data.warningCode +
|
||||
", description " +
|
||||
event.data.warningDescription
|
||||
);
|
||||
};
|
||||
|
||||
const onSDKError = (event) => {
|
||||
console.log(
|
||||
"ONLYOFFICE Document Editor reports an error: code " +
|
||||
event.data.errorCode +
|
||||
", description " +
|
||||
event.data.errorDescription
|
||||
);
|
||||
};
|
||||
|
||||
const onDocumentStateChange = (event) => {
|
||||
if (!documentIsReady) return;
|
||||
|
||||
docSaved = !event.data;
|
||||
throttledChangeTitle();
|
||||
};
|
||||
|
||||
const onDocumentReady = () => {
|
||||
documentIsReady = true;
|
||||
|
||||
if (isSharingAccess) {
|
||||
loadUsersRightsList();
|
||||
}
|
||||
};
|
||||
|
||||
const onMetaChange = (event) => {
|
||||
const newTitle = event.data.title;
|
||||
const favorite = event.data.favorite;
|
||||
|
||||
if (newTitle && newTitle !== docTitle) {
|
||||
setDocumentTitle(newTitle);
|
||||
docTitle = newTitle;
|
||||
}
|
||||
|
||||
if (!newTitle) {
|
||||
const onlyNumbers = new RegExp("^[0-9]+$");
|
||||
const isFileWithoutProvider = onlyNumbers.test(fileId);
|
||||
|
||||
const convertFileId = isFileWithoutProvider
|
||||
? +fileId
|
||||
: decodeURIComponent(fileId);
|
||||
|
||||
favorite
|
||||
? markAsFavorite([convertFileId])
|
||||
.then(() => updateFavorite(favorite))
|
||||
.catch((error) => console.log("error", error))
|
||||
: removeFromFavorite([convertFileId])
|
||||
.then(() => updateFavorite(favorite))
|
||||
.catch((error) => console.log("error", error));
|
||||
}
|
||||
};
|
||||
|
||||
const onSDKRequestInsertImage = (event) => {
|
||||
setTypeInsertImageAction(event.data);
|
||||
setFilesType(insertImageAction);
|
||||
setIsFileDialogVisible(true);
|
||||
};
|
||||
|
||||
const onSDKRequestMailMergeRecipients = () => {
|
||||
setFilesType(mailMergeAction);
|
||||
setIsFileDialogVisible(true);
|
||||
};
|
||||
|
||||
const onSDKRequestCompareFile = () => {
|
||||
setFilesType(compareFilesAction);
|
||||
setIsFileDialogVisible(true);
|
||||
};
|
||||
const onSelectFile = async (file) => {
|
||||
try {
|
||||
const link = await getPresignedUri(file.id);
|
||||
|
||||
if (filesType === insertImageAction) insertImage(link);
|
||||
if (filesType === mailMergeAction) mailMerge(link);
|
||||
if (filesType === compareFilesAction) compareFiles(link);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
};
|
||||
|
||||
const onCloseFileDialog = () => {
|
||||
setIsFileDialogVisible(false);
|
||||
};
|
||||
|
||||
const onSDKRequestSaveAs = (event) => {
|
||||
setTitleSelectorFolder(event.data.title);
|
||||
setUrlSelectorFolder(event.data.url);
|
||||
setExtension(event.data.title.split(".").pop());
|
||||
|
||||
setIsFolderDialogVisible(true);
|
||||
};
|
||||
|
||||
const onCloseFolderDialog = () => {
|
||||
setIsFolderDialogVisible(false);
|
||||
setNewOpenTab(false);
|
||||
};
|
||||
|
||||
const getSavingInfo = async (title, folderId) => {
|
||||
const savingInfo = await SaveAs(
|
||||
title,
|
||||
urlSelectorFolder,
|
||||
folderId,
|
||||
openNewTab
|
||||
);
|
||||
|
||||
if (savingInfo) {
|
||||
const convertedInfo = savingInfo.split(": ").pop();
|
||||
docEditor.showMessage(convertedInfo);
|
||||
}
|
||||
};
|
||||
const onClickSaveSelectFolder = (e, folderId) => {
|
||||
const currentExst = titleSelectorFolder.split(".").pop();
|
||||
|
||||
const title =
|
||||
currentExst !== extension
|
||||
? titleSelectorFolder.concat(`.${extension}`)
|
||||
: titleSelectorFolder;
|
||||
|
||||
if (openNewTab) {
|
||||
SaveAs(title, urlSelectorFolder, folderId, openNewTab);
|
||||
} else {
|
||||
getSavingInfo(title, folderId);
|
||||
}
|
||||
};
|
||||
|
||||
const onChangeInput = (e) => {
|
||||
setTitleSelectorFolder(e.target.value);
|
||||
};
|
||||
|
||||
const onClickCheckbox = () => {
|
||||
setNewOpenTab(!openNewTab);
|
||||
};
|
||||
|
||||
const getFileTypeTranslation = () => {
|
||||
switch (filesType) {
|
||||
case mailMergeAction:
|
||||
return i18n.t("MailMergeFileType");
|
||||
case insertImageAction:
|
||||
return i18n.t("ImageFileType");
|
||||
case compareFilesAction:
|
||||
return i18n.t("DocumentsFileType");
|
||||
}
|
||||
};
|
||||
const selectFilesListTitle = () => {
|
||||
return (
|
||||
<>
|
||||
{filesType === mailMergeAction ? (
|
||||
getFileTypeTranslation()
|
||||
) : (
|
||||
<Trans i18n={i18n} i18nKey="SelectFilesType" ns="Editor">
|
||||
Select files of type: {{ fileType: getFileTypeTranslation() }}
|
||||
</Trans>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const insertImageActionProps = {
|
||||
isImageOnly: true,
|
||||
};
|
||||
|
||||
const mailMergeActionProps = {
|
||||
isTablesOnly: true,
|
||||
searchParam: ".xlsx",
|
||||
};
|
||||
const compareFilesActionProps = {
|
||||
isDocumentsOnly: true,
|
||||
};
|
||||
|
||||
const fileTypeDetection = () => {
|
||||
if (filesType === insertImageAction) {
|
||||
return insertImageActionProps;
|
||||
}
|
||||
if (filesType === mailMergeAction) {
|
||||
return mailMergeActionProps;
|
||||
}
|
||||
if (filesType === compareFilesAction) {
|
||||
return compareFilesActionProps;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<ThemeProvider theme={theme}>
|
||||
<Box
|
||||
widthProp="100vw"
|
||||
heightProp={isIPad() ? "calc(var(--vh, 1vh) * 100)" : "100vh"}
|
||||
>
|
||||
<Toast />
|
||||
{!isLoading ? (
|
||||
<>
|
||||
<div id="editor"></div>
|
||||
{isSharingAccess && isVisible && (
|
||||
<SharingDialog
|
||||
settings={settings} //TODO: Maybe init filesSettings in editor?
|
||||
isVisible={isVisible}
|
||||
sharingObject={fileInfo}
|
||||
onCancel={onCancel}
|
||||
onSuccess={loadUsersRightsList}
|
||||
/>
|
||||
)}
|
||||
|
||||
{isFileDialogVisible && (
|
||||
<SelectFileDialog
|
||||
settings={settings} //TODO: Maybe init filesSettings in editor?
|
||||
resetTreeFolders
|
||||
onSelectFile={onSelectFile}
|
||||
isPanelVisible={isFileDialogVisible}
|
||||
onClose={onCloseFileDialog}
|
||||
foldersType="exceptPrivacyTrashFolders"
|
||||
{...fileTypeDetection()}
|
||||
filesListTitle={selectFilesListTitle()}
|
||||
/>
|
||||
)}
|
||||
|
||||
<SelectFolderDialog
|
||||
folderId={fileInfo?.folderId}
|
||||
isPanelVisible={isFolderDialogVisible}
|
||||
onClose={onCloseFolderDialog}
|
||||
foldersType="exceptSortedByTags"
|
||||
onSave={onClickSaveSelectFolder}
|
||||
isDisableButton={!titleSelectorFolder.trim()}
|
||||
header={
|
||||
<StyledSelectFolder>
|
||||
<Text className="editor-select-folder_text" fontWeight={600}>
|
||||
{i18n.t("FileName")}
|
||||
</Text>
|
||||
<TextInput
|
||||
className="editor-select-folder_text-input"
|
||||
scale
|
||||
onChange={onChangeInput}
|
||||
value={titleSelectorFolder}
|
||||
/>
|
||||
</StyledSelectFolder>
|
||||
}
|
||||
{...(extension !== "fb2" && {
|
||||
footer: (
|
||||
<StyledSelectFolder>
|
||||
<Checkbox
|
||||
className="editor-select-folder_checkbox"
|
||||
label={i18n.t("OpenSavedDocument")}
|
||||
onChange={onClickCheckbox}
|
||||
isChecked={openNewTab}
|
||||
/>
|
||||
</StyledSelectFolder>
|
||||
),
|
||||
})}
|
||||
/>
|
||||
|
||||
{preparationPortalDialogVisible && (
|
||||
<PreparationPortalDialog
|
||||
visible={preparationPortalDialogVisible}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</Box>
|
||||
</ThemeProvider>
|
||||
);
|
||||
};
|
||||
|
||||
export default Editor;
|
8
web/ASC.Web.Editor/src/bootstrap.js
vendored
8
web/ASC.Web.Editor/src/bootstrap.js
vendored
@ -1,8 +0,0 @@
|
||||
import React from "react";
|
||||
import ReactDOM from "react-dom";
|
||||
import App from "./App";
|
||||
import { registerSW } from "@appserver/common/sw/helper";
|
||||
|
||||
ReactDOM.render(<App />, document.getElementById("root"));
|
||||
|
||||
registerSW();
|
75
web/ASC.Web.Editor/src/client/App.js
Normal file
75
web/ASC.Web.Editor/src/client/App.js
Normal file
@ -0,0 +1,75 @@
|
||||
import React, { useEffect } from "react";
|
||||
import Editor from "./components/Editor.js";
|
||||
import { useSSR } from "react-i18next";
|
||||
import useMfScripts from "./helpers/useMfScripts";
|
||||
import {
|
||||
combineUrl,
|
||||
isRetina,
|
||||
getCookie,
|
||||
setCookie,
|
||||
} from "@appserver/common/utils";
|
||||
import { AppServerConfig } from "@appserver/common/constants";
|
||||
import initDesktop from "./helpers/initDesktop";
|
||||
import ErrorBoundary from "./components/ErrorBoundary";
|
||||
import store from "studio/store";
|
||||
import i18n from "./i18n";
|
||||
import { I18nextProvider } from "react-i18next";
|
||||
import { fonts } from "@appserver/common/fonts.js";
|
||||
import GlobalStyle from "./components/GlobalStyle.js";
|
||||
import { inject, observer, Provider as MobxProvider } from "mobx-react";
|
||||
import ThemeProvider from "@appserver/components/theme-provider";
|
||||
|
||||
const isDesktopEditor = window["AscDesktopEditor"] !== undefined;
|
||||
|
||||
const ThemeProviderWrapper = inject(({ auth }) => {
|
||||
const { settingsStore } = auth;
|
||||
return { theme: settingsStore.theme };
|
||||
})(observer(ThemeProvider));
|
||||
|
||||
const App = ({ initialLanguage, initialI18nStoreASC, ...rest }) => {
|
||||
const [isInitialized, isErrorLoading] = useMfScripts();
|
||||
useSSR(initialI18nStoreASC, initialLanguage);
|
||||
|
||||
useEffect(() => {
|
||||
const tempElm = document.getElementById("loader");
|
||||
if (tempElm && !rest.error && !rest.needLoader && rest.docApiUrl) {
|
||||
tempElm.outerHTML = "";
|
||||
}
|
||||
|
||||
if (isRetina() && getCookie("is_retina") == null) {
|
||||
setCookie("is_retina", true, { path: "/" });
|
||||
}
|
||||
}, []);
|
||||
|
||||
const onError = () => {
|
||||
console.log("Error");
|
||||
window.open(
|
||||
combineUrl(
|
||||
AppServerConfig.proxyURL,
|
||||
rest.personal ? "sign-in" : "/login"
|
||||
),
|
||||
"_self"
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<ErrorBoundary onError={onError}>
|
||||
<MobxProvider {...store}>
|
||||
<I18nextProvider i18n={i18n}>
|
||||
<ThemeProviderWrapper>
|
||||
<GlobalStyle fonts={fonts} />
|
||||
<Editor
|
||||
mfReady={isInitialized}
|
||||
mfFailed={isErrorLoading}
|
||||
isDesktopEditor={isDesktopEditor}
|
||||
initDesktop={initDesktop}
|
||||
{...rest}
|
||||
/>
|
||||
</ThemeProviderWrapper>
|
||||
</I18nextProvider>
|
||||
</MobxProvider>
|
||||
</ErrorBoundary>
|
||||
);
|
||||
};
|
||||
|
||||
export default App;
|
54
web/ASC.Web.Editor/src/client/bootstrap.js
vendored
Normal file
54
web/ASC.Web.Editor/src/client/bootstrap.js
vendored
Normal file
@ -0,0 +1,54 @@
|
||||
import React from "react";
|
||||
import { hydrate } from "react-dom";
|
||||
import { registerSW } from "@appserver/common/sw/helper";
|
||||
import App from "./App.js";
|
||||
import pkg from "../../package.json";
|
||||
import { initI18n } from "./helpers/utils.js";
|
||||
|
||||
const propsObj = window.__ASC_INITIAL_EDITOR_STATE__;
|
||||
const initialI18nStoreASC = window.initialI18nStoreASC;
|
||||
const initialLanguage = window.initialLanguage;
|
||||
|
||||
initI18n(initialI18nStoreASC);
|
||||
|
||||
hydrate(
|
||||
<App
|
||||
initialLanguage={initialLanguage}
|
||||
initialI18nStoreASC={initialI18nStoreASC}
|
||||
{...propsObj}
|
||||
/>,
|
||||
document.getElementById("root")
|
||||
);
|
||||
|
||||
if (IS_DEVELOPMENT) {
|
||||
const port = PORT || 5013;
|
||||
const socketPath = pkg.socketPath;
|
||||
|
||||
const ws = new WebSocket(`ws://localhost:${port}${socketPath}`);
|
||||
let isErrorConnection = false;
|
||||
|
||||
ws.onopen = (event) => {
|
||||
console.log("[editor-dev] Socket is connected. Live reload enabled");
|
||||
};
|
||||
|
||||
ws.onmessage = function (event) {
|
||||
if (event.data === "reload") {
|
||||
console.log("[editor-dev] App updated. Reloading...");
|
||||
location.reload();
|
||||
}
|
||||
};
|
||||
|
||||
ws.onclose = function (event) {
|
||||
console.log("[editor-dev] Socket is disconnected! Reloading...");
|
||||
setTimeout(() => {
|
||||
!isErrorConnection && location.reload();
|
||||
}, 1500);
|
||||
};
|
||||
|
||||
ws.onerror = (event) => {
|
||||
isErrorConnection = true;
|
||||
console.log("[editor-dev] Socket connect error!");
|
||||
};
|
||||
}
|
||||
|
||||
registerSW();
|
116
web/ASC.Web.Editor/src/client/components/DynamicComponent.js
Normal file
116
web/ASC.Web.Editor/src/client/components/DynamicComponent.js
Normal file
@ -0,0 +1,116 @@
|
||||
import React from "react";
|
||||
// import AppLoader from "@appserver/common/components/AppLoader";
|
||||
// import ErrorBoundary from "@appserver/common/components/ErrorBoundary";
|
||||
// import Error520 from "studio/Error520";
|
||||
// import Error404 from "studio/Error404";
|
||||
|
||||
export function loadComponent(scope, module) {
|
||||
return async () => {
|
||||
// Initializes the share scope. This fills it with known provided modules from this build and all remotes
|
||||
await __webpack_init_sharing__("default");
|
||||
const container = window[scope]; // or get the container somewhere else
|
||||
// Initialize the container, it may provide shared modules
|
||||
await container.init(__webpack_share_scopes__.default);
|
||||
const factory = await window[scope].get(module);
|
||||
const Module = factory();
|
||||
return Module;
|
||||
};
|
||||
}
|
||||
|
||||
export const useDynamicScript = (args) => {
|
||||
const [ready, setReady] = React.useState(false);
|
||||
const [failed, setFailed] = React.useState(false);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!args.url) {
|
||||
return;
|
||||
}
|
||||
|
||||
const exists = document.getElementById(args.id);
|
||||
|
||||
if (exists || args?.isInit) {
|
||||
setReady(true);
|
||||
setFailed(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const element = document.createElement("script");
|
||||
|
||||
element.id = args.id;
|
||||
element.src = args.url;
|
||||
element.type = "text/javascript";
|
||||
element.async = true;
|
||||
|
||||
setReady(false);
|
||||
setFailed(false);
|
||||
|
||||
element.onload = () => {
|
||||
console.log(`Dynamic Script Loaded: ${args.url}`);
|
||||
setReady(true);
|
||||
};
|
||||
|
||||
element.onerror = () => {
|
||||
console.error(`Dynamic Script Error: ${args.url}`);
|
||||
setReady(false);
|
||||
setFailed(true);
|
||||
};
|
||||
|
||||
document.head.appendChild(element);
|
||||
|
||||
//TODO: Comment if you don't want to remove loaded remoteEntry
|
||||
// return () => {
|
||||
// console.log(`Dynamic Script Removed: ${args.url}`);
|
||||
// document.head.removeChild(element);
|
||||
// };
|
||||
}, [args.url]);
|
||||
|
||||
return {
|
||||
ready,
|
||||
failed,
|
||||
};
|
||||
};
|
||||
|
||||
const DynamicComponent = ({ system, needProxy, ...rest }) => {
|
||||
const [isInitialized, setIsInitialized] = React.useState(false);
|
||||
const [LoadedComponent, setLoadedComponent] = React.useState();
|
||||
|
||||
const { ready, failed } = useDynamicScript({
|
||||
url: system && system.url,
|
||||
id: system && system.scope,
|
||||
isInit: isInitialized,
|
||||
});
|
||||
|
||||
if (!system) {
|
||||
console.log(`Not system specified`);
|
||||
throw Error("Not system specified");
|
||||
}
|
||||
|
||||
if (!ready) {
|
||||
console.log(`Loading dynamic script: ${system.url}`);
|
||||
return <div className={rest.className} />;
|
||||
}
|
||||
|
||||
if (failed) {
|
||||
console.log(`Failed to load dynamic script: ${system.url}`);
|
||||
throw Error("failed");
|
||||
}
|
||||
|
||||
if (ready && !isInitialized) {
|
||||
setIsInitialized(true);
|
||||
const Component = React.lazy(loadComponent(system.scope, system.module));
|
||||
|
||||
setLoadedComponent(Component);
|
||||
}
|
||||
|
||||
const Component = React.lazy(loadComponent(system.scope, system.module));
|
||||
|
||||
return (
|
||||
<React.Suspense fallback={<div />}>
|
||||
{needProxy
|
||||
? LoadedComponent && <LoadedComponent {...rest} />
|
||||
: Component && <Component {...rest} />}
|
||||
</React.Suspense>
|
||||
);
|
||||
};
|
||||
|
||||
export default DynamicComponent;
|
576
web/ASC.Web.Editor/src/client/components/Editor.js
Normal file
576
web/ASC.Web.Editor/src/client/components/Editor.js
Normal file
@ -0,0 +1,576 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { isMobile, isIOS, deviceType } from "react-device-detect";
|
||||
import FilesFilter from "@appserver/common/api/files/filter";
|
||||
import combineUrl from "@appserver/common/utils/combineUrl";
|
||||
import { AppServerConfig } from "@appserver/common/constants";
|
||||
import throttle from "lodash/throttle";
|
||||
import Toast from "@appserver/components/toast";
|
||||
import { toast } from "react-toastify";
|
||||
import {
|
||||
restoreDocumentsVersion,
|
||||
markAsFavorite,
|
||||
removeFromFavorite,
|
||||
getEditDiff,
|
||||
getEditHistory,
|
||||
updateFile,
|
||||
checkFillFormDraft,
|
||||
} from "@appserver/common/api/files";
|
||||
import { EditorWrapper } from "../components/StyledEditor";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import withDialogs from "../helpers/withDialogs";
|
||||
import { canConvert, convertDocumentUrl } from "../helpers/utils";
|
||||
import { assign } from "@appserver/common/utils";
|
||||
|
||||
toast.configure();
|
||||
|
||||
const onSDKInfo = (event) => {
|
||||
console.log(
|
||||
"ONLYOFFICE Document Editor is opened in mode " + event.data.mode
|
||||
);
|
||||
};
|
||||
|
||||
const onSDKWarning = (event) => {
|
||||
console.log(
|
||||
"ONLYOFFICE Document Editor reports a warning: code " +
|
||||
event.data.warningCode +
|
||||
", description " +
|
||||
event.data.warningDescription
|
||||
);
|
||||
};
|
||||
|
||||
const onSDKError = (event) => {
|
||||
console.log(
|
||||
"ONLYOFFICE Document Editor reports an error: code " +
|
||||
event.data.errorCode +
|
||||
", description " +
|
||||
event.data.errorDescription
|
||||
);
|
||||
};
|
||||
|
||||
const text = "text";
|
||||
const presentation = "presentation";
|
||||
let documentIsReady = false;
|
||||
let docSaved = null;
|
||||
let docTitle = null;
|
||||
let docEditor;
|
||||
|
||||
function Editor({
|
||||
config,
|
||||
personal,
|
||||
successAuth,
|
||||
isSharingAccess,
|
||||
user,
|
||||
doc,
|
||||
actionLink,
|
||||
error,
|
||||
sharingDialog,
|
||||
onSDKRequestSharingSettings,
|
||||
loadUsersRightsList,
|
||||
isVisible,
|
||||
selectFileDialog,
|
||||
onSDKRequestInsertImage,
|
||||
onSDKRequestMailMergeRecipients,
|
||||
onSDKRequestCompareFile,
|
||||
selectFolderDialog,
|
||||
onSDKRequestSaveAs,
|
||||
isFileDialogVisible,
|
||||
isFolderDialogVisible,
|
||||
isDesktopEditor,
|
||||
initDesktop,
|
||||
view,
|
||||
preparationPortalDialog,
|
||||
mfReady,
|
||||
...rest
|
||||
}) {
|
||||
const [fileInfo, setFileInfo] = useState(rest.fileInfo);
|
||||
const [url, setUrl] = useState(rest.url);
|
||||
const [fileId, setFileId] = useState(rest.fileId);
|
||||
const [version, setVersion] = useState(rest.version);
|
||||
|
||||
const { t } = useTranslation(["Editor", "Common"]);
|
||||
|
||||
useEffect(() => {
|
||||
if (error && mfReady) {
|
||||
if (error?.unAuthorized && error?.redirectPath) {
|
||||
localStorage.setItem("redirectPath", window.location.href);
|
||||
window.location.href = error?.redirectPath;
|
||||
}
|
||||
const errorText = typeof error === "string" ? error : error.errorMessage;
|
||||
window.toastr.error(errorText);
|
||||
}
|
||||
}, [mfReady, error]);
|
||||
|
||||
useEffect(() => {
|
||||
if (config) {
|
||||
document.getElementById("scripDocServiceAddress").onload = onLoad();
|
||||
setDocumentTitle(config?.document?.title);
|
||||
|
||||
if (isIOS && deviceType === "tablet") {
|
||||
const vh = window.innerHeight * 0.01;
|
||||
document.documentElement.style.setProperty("--vh", `${vh}px`);
|
||||
}
|
||||
|
||||
if (
|
||||
!view &&
|
||||
fileInfo &&
|
||||
fileInfo.canWebRestrictedEditing &&
|
||||
fileInfo.canFillForms &&
|
||||
!fileInfo.canEdit
|
||||
) {
|
||||
try {
|
||||
initForm();
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
}
|
||||
|
||||
if (view) {
|
||||
config.editorConfig.mode = "view";
|
||||
}
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (config) {
|
||||
if (isDesktopEditor) {
|
||||
initDesktop(config, user, fileId, t);
|
||||
}
|
||||
}
|
||||
}, [isDesktopEditor]);
|
||||
|
||||
useEffect(() => {
|
||||
try {
|
||||
const url = window.location.href;
|
||||
|
||||
if (
|
||||
successAuth &&
|
||||
url.indexOf("#message/") > -1 &&
|
||||
fileInfo &&
|
||||
fileInfo?.fileExst &&
|
||||
canConvert(fileInfo.fileExst)
|
||||
) {
|
||||
showDocEditorMessage(url);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
}, [url, fileInfo?.fileExst]);
|
||||
|
||||
const initForm = async () => {
|
||||
const formUrl = await checkFillFormDraft(fileId);
|
||||
history.pushState({}, null, formUrl);
|
||||
|
||||
document.location.reload();
|
||||
};
|
||||
|
||||
const showDocEditorMessage = async (url) => {
|
||||
const result = await convertDocumentUrl();
|
||||
const splitUrl = url.split("#message/");
|
||||
|
||||
if (result) {
|
||||
const newUrl = `${result.webUrl}#message/${splitUrl[1]}`;
|
||||
|
||||
history.pushState({}, null, newUrl);
|
||||
|
||||
setFileInfo(result);
|
||||
setUrl(newUrl);
|
||||
setFileId(fileId);
|
||||
setVersion(version);
|
||||
}
|
||||
};
|
||||
|
||||
const getDefaultFileName = (format) => {
|
||||
switch (format) {
|
||||
case "docx":
|
||||
return t("NewDocument");
|
||||
case "xlsx":
|
||||
return t("NewSpreadsheet");
|
||||
case "pptx":
|
||||
return t("NewPresentation");
|
||||
case "docxf":
|
||||
return t("NewMasterForm");
|
||||
default:
|
||||
return t("NewFolder");
|
||||
}
|
||||
};
|
||||
|
||||
const throttledChangeTitle = throttle(() => changeTitle(), 500);
|
||||
|
||||
const onSDKRequestHistoryClose = () => {
|
||||
document.location.reload();
|
||||
};
|
||||
|
||||
const onSDKRequestEditRights = async () => {
|
||||
console.log("ONLYOFFICE Document Editor requests editing rights");
|
||||
const index = url.indexOf("&action=view");
|
||||
|
||||
if (index) {
|
||||
let convertUrl = url.substring(0, index);
|
||||
|
||||
if (canConvert(fileInfo.fileExst)) {
|
||||
const newUrl = await convertDocumentUrl();
|
||||
if (newUrl) {
|
||||
convertUrl = newUrl.webUrl;
|
||||
}
|
||||
}
|
||||
history.pushState({}, null, convertUrl);
|
||||
document.location.reload();
|
||||
}
|
||||
};
|
||||
|
||||
const onMakeActionLink = (event) => {
|
||||
const actionData = event.data;
|
||||
|
||||
const link = generateLink(actionData);
|
||||
|
||||
const urlFormation = !actionLink ? url : url.split("&anchor=")[0];
|
||||
|
||||
const linkFormation = `${urlFormation}&anchor=${link}`;
|
||||
|
||||
docEditor.setActionLink(linkFormation);
|
||||
};
|
||||
|
||||
const generateLink = (actionData) => {
|
||||
return encodeURIComponent(JSON.stringify(actionData));
|
||||
};
|
||||
|
||||
const onSDKRequestRename = (event) => {
|
||||
const title = event.data;
|
||||
updateFile(fileInfo.id, title);
|
||||
};
|
||||
|
||||
const onSDKRequestRestore = async (event) => {
|
||||
const restoreVersion = event.data.version;
|
||||
try {
|
||||
const updateVersions = await restoreDocumentsVersion(
|
||||
fileId,
|
||||
restoreVersion,
|
||||
doc
|
||||
);
|
||||
const historyLength = updateVersions.length;
|
||||
docEditor.refreshHistory({
|
||||
currentVersion: getCurrentDocumentVersion(
|
||||
updateVersions,
|
||||
historyLength
|
||||
),
|
||||
history: getDocumentHistory(updateVersions, historyLength),
|
||||
});
|
||||
} catch (e) {
|
||||
docEditor.refreshHistory({
|
||||
error: `${e}`, //TODO: maybe need to display something else.
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const getDocumentHistory = (fileHistory, historyLength) => {
|
||||
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;
|
||||
};
|
||||
|
||||
const getCurrentDocumentVersion = (fileHistory, historyLength) => {
|
||||
return url.indexOf("&version=") !== -1
|
||||
? +url.split("&version=")[1]
|
||||
: fileHistory[historyLength - 1].version;
|
||||
};
|
||||
|
||||
const onSDKRequestHistory = async () => {
|
||||
try {
|
||||
const fileHistory = await getEditHistory(fileId, doc);
|
||||
const historyLength = fileHistory.length;
|
||||
|
||||
docEditor.refreshHistory({
|
||||
currentVersion: getCurrentDocumentVersion(fileHistory, historyLength),
|
||||
history: getDocumentHistory(fileHistory, historyLength),
|
||||
});
|
||||
} catch (e) {
|
||||
docEditor.refreshHistory({
|
||||
error: `${e}`, //TODO: maybe need to display something else.
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const onSDKRequestHistoryData = async (event) => {
|
||||
const version = event.data;
|
||||
|
||||
try {
|
||||
const versionDifference = await getEditDiff(fileId, version, doc);
|
||||
const changesUrl = versionDifference.changesUrl;
|
||||
const previous = versionDifference.previous;
|
||||
const token = versionDifference.token;
|
||||
|
||||
docEditor.setHistoryData({
|
||||
...(changesUrl && { changesUrl }),
|
||||
key: versionDifference.key,
|
||||
fileType: versionDifference.fileType,
|
||||
...(previous && {
|
||||
previous: {
|
||||
fileType: previous.fileType,
|
||||
key: previous.key,
|
||||
url: previous.url,
|
||||
},
|
||||
}),
|
||||
...(token && { token }),
|
||||
url: versionDifference.url,
|
||||
version,
|
||||
});
|
||||
} catch (e) {
|
||||
docEditor.setHistoryData({
|
||||
error: `${e}`, //TODO: maybe need to display something else.
|
||||
version,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const onDocumentReady = () => {
|
||||
documentIsReady = true;
|
||||
|
||||
if (isSharingAccess) {
|
||||
loadUsersRightsList(docEditor);
|
||||
}
|
||||
};
|
||||
|
||||
const updateFavorite = (favorite) => {
|
||||
docEditor.setFavorite(favorite);
|
||||
};
|
||||
|
||||
const onMetaChange = (event) => {
|
||||
const newTitle = event.data.title;
|
||||
const favorite = event.data.favorite;
|
||||
|
||||
if (newTitle && newTitle !== docTitle) {
|
||||
setDocumentTitle(newTitle);
|
||||
docTitle = newTitle;
|
||||
}
|
||||
|
||||
if (!newTitle) {
|
||||
const onlyNumbers = new RegExp("^[0-9]+$");
|
||||
const isFileWithoutProvider = onlyNumbers.test(fileId);
|
||||
|
||||
const convertFileId = isFileWithoutProvider ? +fileId : fileId;
|
||||
|
||||
favorite
|
||||
? markAsFavorite([convertFileId])
|
||||
.then(() => updateFavorite(favorite))
|
||||
.catch((error) => console.log("error", error))
|
||||
: removeFromFavorite([convertFileId])
|
||||
.then(() => updateFavorite(favorite))
|
||||
.catch((error) => console.log("error", error));
|
||||
}
|
||||
};
|
||||
|
||||
const setDocumentTitle = (subTitle = null) => {
|
||||
//const { isAuthenticated, settingsStore, product: currentModule } = auth;
|
||||
//const { organizationName } = settingsStore;
|
||||
const organizationName = "ONLYOFFICE"; //TODO: Replace to API variant
|
||||
const moduleTitle = "Documents"; //TODO: Replace to API variant
|
||||
|
||||
let title;
|
||||
if (subTitle) {
|
||||
if (successAuth && moduleTitle) {
|
||||
title = subTitle + " - " + moduleTitle;
|
||||
} else {
|
||||
title = subTitle + " - " + organizationName;
|
||||
}
|
||||
} else if (moduleTitle && organizationName) {
|
||||
title = moduleTitle + " - " + organizationName;
|
||||
} else {
|
||||
title = organizationName;
|
||||
}
|
||||
document.title = title;
|
||||
};
|
||||
|
||||
const changeTitle = () => {
|
||||
docSaved ? setDocumentTitle(docTitle) : setDocumentTitle(`*${docTitle}`);
|
||||
};
|
||||
|
||||
const onDocumentStateChange = (event) => {
|
||||
if (!documentIsReady) return;
|
||||
|
||||
docSaved = !event.data;
|
||||
throttledChangeTitle();
|
||||
}; //+++
|
||||
|
||||
const onSDKAppReady = () => {
|
||||
console.log("ONLYOFFICE Document Editor is ready");
|
||||
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(raw).replace(/\+/g, " ");
|
||||
docEditor.showMessage(message);
|
||||
history.pushState({}, null, url.substring(0, index));
|
||||
} else {
|
||||
if (config?.Error) docEditor.showMessage(config.Error);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const onLoad = () => {
|
||||
try {
|
||||
if (!window.DocsAPI) throw new Error("DocsAPI is not defined");
|
||||
|
||||
if (isMobile) {
|
||||
config.type = "mobile";
|
||||
}
|
||||
|
||||
let goBack;
|
||||
const url = window.location.href;
|
||||
|
||||
if (fileInfo) {
|
||||
const filterObj = FilesFilter.getDefault();
|
||||
filterObj.folder = fileInfo.folderId;
|
||||
const urlFilter = filterObj.toUrlParams();
|
||||
const filesUrl = url.substring(0, url.indexOf("/doceditor"));
|
||||
goBack = {
|
||||
blank: true,
|
||||
requestClose: false,
|
||||
text: t("FileLocation"),
|
||||
url: `${combineUrl(filesUrl, `/filter?${urlFilter}`)}`,
|
||||
};
|
||||
}
|
||||
|
||||
config.editorConfig.customization = {
|
||||
...config.editorConfig.customization,
|
||||
goback: goBack,
|
||||
};
|
||||
|
||||
if (personal && !fileInfo) {
|
||||
//TODO: add conditions for SaaS
|
||||
config.document.info.favorite = null;
|
||||
}
|
||||
|
||||
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,
|
||||
};
|
||||
}
|
||||
|
||||
if (successAuth) {
|
||||
const documentType = config.documentType;
|
||||
const fileExt =
|
||||
documentType === text
|
||||
? "docx"
|
||||
: documentType === presentation
|
||||
? "pptx"
|
||||
: "xlsx";
|
||||
|
||||
const defaultFileName = getDefaultFileName(fileExt);
|
||||
|
||||
if (!user.isVisitor)
|
||||
config.editorConfig.createUrl = combineUrl(
|
||||
window.location.origin,
|
||||
AppServerConfig.proxyURL,
|
||||
"products/files/",
|
||||
`/httphandlers/filehandler.ashx?action=create&doctype=text&title=${encodeURIComponent(
|
||||
defaultFileName
|
||||
)}`
|
||||
);
|
||||
}
|
||||
|
||||
let onRequestSharingSettings,
|
||||
onRequestRename,
|
||||
onRequestSaveAs,
|
||||
onRequestInsertImage,
|
||||
onRequestMailMergeRecipients,
|
||||
onRequestCompareFile,
|
||||
onRequestRestore;
|
||||
|
||||
if (isSharingAccess) {
|
||||
onRequestSharingSettings = onSDKRequestSharingSettings;
|
||||
}
|
||||
|
||||
if (fileInfo && fileInfo.canEdit) {
|
||||
onRequestRename = onSDKRequestRename;
|
||||
}
|
||||
|
||||
if (successAuth) {
|
||||
onRequestSaveAs = onSDKRequestSaveAs;
|
||||
onRequestInsertImage = onSDKRequestInsertImage;
|
||||
onRequestMailMergeRecipients = onSDKRequestMailMergeRecipients;
|
||||
onRequestCompareFile = onSDKRequestCompareFile;
|
||||
}
|
||||
|
||||
if (!!config.document.permissions.changeHistory) {
|
||||
onRequestRestore = onSDKRequestRestore;
|
||||
}
|
||||
const events = {
|
||||
events: {
|
||||
onAppReady: onSDKAppReady,
|
||||
onDocumentStateChange: onDocumentStateChange,
|
||||
onMetaChange: onMetaChange,
|
||||
onDocumentReady: onDocumentReady,
|
||||
onInfo: onSDKInfo,
|
||||
onWarning: onSDKWarning,
|
||||
onError: onSDKError,
|
||||
onRequestSharingSettings,
|
||||
onRequestRename,
|
||||
onMakeActionLink: onMakeActionLink,
|
||||
onRequestInsertImage,
|
||||
onRequestSaveAs,
|
||||
onRequestMailMergeRecipients,
|
||||
onRequestCompareFile,
|
||||
onRequestEditRights: onSDKRequestEditRights,
|
||||
onRequestHistory: onSDKRequestHistory,
|
||||
onRequestHistoryClose: onSDKRequestHistoryClose,
|
||||
onRequestHistoryData: onSDKRequestHistoryData,
|
||||
onRequestRestore,
|
||||
},
|
||||
};
|
||||
|
||||
const newConfig = Object.assign(config, events);
|
||||
|
||||
docEditor = window.docEditor = window.DocsAPI.DocEditor(
|
||||
"editor",
|
||||
newConfig
|
||||
);
|
||||
|
||||
assign(window, ["ASC", "Files", "Editor", "docEditor"], docEditor); //Do not remove: it's for Back button on Mobile App
|
||||
} catch (error) {
|
||||
window.toastr.error(error.message, null, 0, true);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<EditorWrapper isVisibleSharingDialog={isVisible}>
|
||||
<div id="editor"></div>
|
||||
{sharingDialog}
|
||||
{selectFileDialog}
|
||||
{selectFolderDialog}
|
||||
{preparationPortalDialog}
|
||||
<Toast />
|
||||
</EditorWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
export default withDialogs(Editor);
|
47
web/ASC.Web.Editor/src/client/components/ErrorBoundary.js
Normal file
47
web/ASC.Web.Editor/src/client/components/ErrorBoundary.js
Normal file
@ -0,0 +1,47 @@
|
||||
import React from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import ErrorContainer from "@appserver/common/components/ErrorContainer";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const Error520 = ({ match }) => {
|
||||
const { t } = useTranslation(["Common"]);
|
||||
const { error } = (match && match.params) || {};
|
||||
|
||||
return (
|
||||
<ErrorContainer headerText={t("SomethingWentWrong")} bodyText={error} />
|
||||
);
|
||||
};
|
||||
|
||||
class ErrorBoundary extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = { hasError: false };
|
||||
}
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
static getDerivedStateFromError(error) {
|
||||
// Update state so the next render will show the fallback UI.
|
||||
return { hasError: true };
|
||||
}
|
||||
|
||||
componentDidCatch(error, errorInfo) {
|
||||
// You can also log the error to an error reporting service
|
||||
console.error(error, errorInfo);
|
||||
this.props.onError && this.props.onError();
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.state.hasError) {
|
||||
// You can render any custom fallback UI
|
||||
return <Error520 />;
|
||||
}
|
||||
|
||||
return this.props.children;
|
||||
}
|
||||
}
|
||||
|
||||
ErrorBoundary.propTypes = {
|
||||
children: PropTypes.any,
|
||||
onError: PropTypes.func,
|
||||
};
|
||||
|
||||
export default ErrorBoundary;
|
30
web/ASC.Web.Editor/src/client/components/GlobalStyle.js
Normal file
30
web/ASC.Web.Editor/src/client/components/GlobalStyle.js
Normal file
@ -0,0 +1,30 @@
|
||||
import { createGlobalStyle } from "styled-components";
|
||||
|
||||
const GlobalStyle = createGlobalStyle`
|
||||
html,
|
||||
body {
|
||||
height: 100%;
|
||||
${(props) => props?.fonts}
|
||||
font-family: 'Open Sans', sans-serif, Arial;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
#root {
|
||||
min-height: 100%;
|
||||
|
||||
.pageLoader {
|
||||
position: fixed;
|
||||
left: calc(50% - 20px);
|
||||
top: 35%;
|
||||
}
|
||||
}
|
||||
body {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
body.loading * {
|
||||
cursor: wait !important;
|
||||
}
|
||||
`;
|
||||
|
||||
export default GlobalStyle;
|
@ -20,4 +20,12 @@ const StyledSelectFile = styled.div`
|
||||
}
|
||||
`;
|
||||
|
||||
export { StyledSelectFolder, StyledSelectFile };
|
||||
const EditorWrapper = styled.div`
|
||||
height: 100vh;
|
||||
|
||||
.dynamic-sharing-dialog {
|
||||
${(props) => !props.isVisibleSharingDialog && "display: none"}
|
||||
}
|
||||
`;
|
||||
|
||||
export { StyledSelectFolder, StyledSelectFile, EditorWrapper };
|
4
web/ASC.Web.Editor/src/client/helpers/constants.js
Normal file
4
web/ASC.Web.Editor/src/client/helpers/constants.js
Normal file
@ -0,0 +1,4 @@
|
||||
export const FILES_SCOPE = "files";
|
||||
export const FILES_REMOTE_ENTRY_URL = "/products/files/remoteEntry.js";
|
||||
export const STUDIO_SCOPE = "studio";
|
||||
export const STUDIO_REMOTE_ENTRY_URL = "/remoteEntry.js";
|
39
web/ASC.Web.Editor/src/client/helpers/initDesktop.js
Normal file
39
web/ASC.Web.Editor/src/client/helpers/initDesktop.js
Normal file
@ -0,0 +1,39 @@
|
||||
import {
|
||||
setEncryptionKeys,
|
||||
getEncryptionAccess,
|
||||
} from "@appserver/common/api/files";
|
||||
import { regDesktop } from "@appserver/common/desktop";
|
||||
|
||||
const initDesktop = (cfg, user, fileId, t) => {
|
||||
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) => {
|
||||
window?.toastr.error(
|
||||
typeof error === "string" ? error : error.message,
|
||||
null,
|
||||
0,
|
||||
true
|
||||
);
|
||||
});
|
||||
},
|
||||
t
|
||||
);
|
||||
};
|
||||
|
||||
export default initDesktop;
|
55
web/ASC.Web.Editor/src/client/helpers/useMfScripts.js
Normal file
55
web/ASC.Web.Editor/src/client/helpers/useMfScripts.js
Normal file
@ -0,0 +1,55 @@
|
||||
import React from "react";
|
||||
import {
|
||||
loadComponent,
|
||||
useDynamicScript,
|
||||
} from "../components/DynamicComponent";
|
||||
import {
|
||||
STUDIO_SCOPE,
|
||||
FILES_SCOPE,
|
||||
STUDIO_REMOTE_ENTRY_URL,
|
||||
FILES_REMOTE_ENTRY_URL,
|
||||
} from "../helpers/constants";
|
||||
|
||||
function useMfScripts() {
|
||||
const [isInitialized, setIsInitialized] = React.useState(false);
|
||||
const [isError, setIsError] = React.useState(false);
|
||||
|
||||
const { ready: filesReady, failed: filesFailed } = useDynamicScript({
|
||||
id: FILES_SCOPE,
|
||||
url: FILES_REMOTE_ENTRY_URL,
|
||||
});
|
||||
const { ready: studioReady, failed: studioFailed } = useDynamicScript({
|
||||
id: STUDIO_SCOPE,
|
||||
url: STUDIO_REMOTE_ENTRY_URL,
|
||||
});
|
||||
|
||||
React.useEffect(() => {
|
||||
if (filesReady && studioReady) {
|
||||
initMfScripts();
|
||||
}
|
||||
|
||||
if (studioFailed || filesFailed) {
|
||||
setIsError(true);
|
||||
setIsInitialized(false);
|
||||
}
|
||||
}, [filesReady, studioReady]);
|
||||
|
||||
const initMfScripts = async () => {
|
||||
const SharingDialog = await loadComponent(FILES_SCOPE, "./SharingDialog")();
|
||||
const toastr = await loadComponent(STUDIO_SCOPE, "./toastr")();
|
||||
const filesUtils = await loadComponent(FILES_SCOPE, "./utils")();
|
||||
const authStore = await loadComponent(STUDIO_SCOPE, "./store")();
|
||||
|
||||
window.toastr = toastr.default;
|
||||
window.filesUtils = filesUtils;
|
||||
window.SharingDialog = SharingDialog.default;
|
||||
window.authStore = authStore.default;
|
||||
|
||||
setIsInitialized(true);
|
||||
setIsError(false);
|
||||
};
|
||||
|
||||
return [isInitialized, isError];
|
||||
}
|
||||
|
||||
export default useMfScripts;
|
39
web/ASC.Web.Editor/src/client/helpers/utils.js
Normal file
39
web/ASC.Web.Editor/src/client/helpers/utils.js
Normal file
@ -0,0 +1,39 @@
|
||||
import { convertFile } from "@appserver/common/api/files";
|
||||
import pkg from "../../../package.json";
|
||||
|
||||
export const canConvert = (extension, filesSettings) => {
|
||||
const array = filesSettings?.extsMustConvert || [];
|
||||
const result = array.findIndex((item) => item === extension);
|
||||
return result === -1 ? false : true;
|
||||
};
|
||||
|
||||
export const convertDocumentUrl = async () => {
|
||||
const convert = await convertFile(fileId, null, true);
|
||||
return convert && convert[0]?.result;
|
||||
};
|
||||
|
||||
export const initI18n = (initialI18nStoreASC) => {
|
||||
if (!initialI18nStoreASC || window.i18n) return;
|
||||
|
||||
const { homepage } = pkg;
|
||||
|
||||
window.i18n = {};
|
||||
window.i18n.inLoad = [];
|
||||
window.i18n.loaded = {};
|
||||
|
||||
for (let lng in initialI18nStoreASC) {
|
||||
for (let ns in initialI18nStoreASC[lng]) {
|
||||
if (ns === "Common") {
|
||||
window.i18n.loaded[`/static/locales/${lng}/${ns}.json`] = {
|
||||
namespaces: ns,
|
||||
data: initialI18nStoreASC[lng][ns],
|
||||
};
|
||||
} else {
|
||||
window.i18n.loaded[`${homepage}/locales/${lng}/${ns}.json`] = {
|
||||
namespaces: ns,
|
||||
data: initialI18nStoreASC[lng][ns],
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
344
web/ASC.Web.Editor/src/client/helpers/withDialogs.js
Normal file
344
web/ASC.Web.Editor/src/client/helpers/withDialogs.js
Normal file
@ -0,0 +1,344 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import DynamicComponent from "../components/DynamicComponent";
|
||||
import { getPresignedUri } from "@appserver/common/api/files";
|
||||
import {
|
||||
FILES_REMOTE_ENTRY_URL,
|
||||
FILES_SCOPE,
|
||||
STUDIO_SCOPE,
|
||||
STUDIO_REMOTE_ENTRY_URL,
|
||||
} from "./constants";
|
||||
import Text from "@appserver/components/text";
|
||||
import TextInput from "@appserver/components/text-input";
|
||||
import Checkbox from "@appserver/components/checkbox";
|
||||
import { StyledSelectFolder } from "../components/StyledEditor";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const insertImageAction = "imageFileType";
|
||||
const mailMergeAction = "mailMergeFileType";
|
||||
const compareFilesAction = "documentsFileType";
|
||||
|
||||
const withDialogs = (WrappedComponent) => {
|
||||
return (props) => {
|
||||
const [isVisible, setIsVisible] = useState(false);
|
||||
const [filesType, setFilesType] = useState("");
|
||||
const [isFileDialogVisible, setIsFileDialogVisible] = useState(false);
|
||||
const [typeInsertImageAction, setTypeInsertImageAction] = useState();
|
||||
const [isFolderDialogVisible, setIsFolderDialogVisible] = useState(false);
|
||||
const [titleSelectorFolder, setTitleSelectorFolder] = useState("");
|
||||
const [urlSelectorFolder, setUrlSelectorFolder] = useState("");
|
||||
const [extension, setExtension] = useState();
|
||||
const [openNewTab, setNewOpenTab] = useState(false);
|
||||
const [
|
||||
preparationPortalDialogVisible,
|
||||
setPreparationPortalDialogVisible,
|
||||
] = useState(false);
|
||||
|
||||
const { t } = useTranslation(["Editor", "Common"]);
|
||||
|
||||
const { fileInfo, fileId, mfReady } = props;
|
||||
|
||||
useEffect(() => {
|
||||
if (window.authStore) {
|
||||
initSocketHelper();
|
||||
}
|
||||
}, [mfReady]);
|
||||
|
||||
const initSocketHelper = async () => {
|
||||
await window.authStore.auth.init(true);
|
||||
|
||||
const { socketHelper } = window.authStore.auth.settingsStore;
|
||||
socketHelper.emit({
|
||||
command: "subscribe",
|
||||
data: "backup-restore",
|
||||
});
|
||||
socketHelper.on("restore-backup", () => {
|
||||
setPreparationPortalDialogVisible(true);
|
||||
});
|
||||
};
|
||||
|
||||
const onSDKRequestSharingSettings = () => {
|
||||
setIsVisible(true);
|
||||
};
|
||||
|
||||
const onCancel = () => {
|
||||
setIsVisible(false);
|
||||
};
|
||||
|
||||
const loadUsersRightsList = () => {
|
||||
window.SharingDialog.getSharingSettings(fileId).then(
|
||||
(sharingSettings) => {
|
||||
window.docEditor.setSharingSettings({
|
||||
sharingSettings,
|
||||
});
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
const onCloseFileDialog = () => {
|
||||
setIsFileDialogVisible(false);
|
||||
};
|
||||
|
||||
const onSDKRequestCompareFile = () => {
|
||||
setFilesType(compareFilesAction);
|
||||
setIsFileDialogVisible(true);
|
||||
};
|
||||
|
||||
const onSDKRequestMailMergeRecipients = () => {
|
||||
setFilesType(mailMergeAction);
|
||||
setIsFileDialogVisible(true);
|
||||
};
|
||||
|
||||
const onSDKRequestInsertImage = (event) => {
|
||||
setTypeInsertImageAction(event.data);
|
||||
setFilesType(insertImageAction);
|
||||
setIsFileDialogVisible(true);
|
||||
};
|
||||
|
||||
const insertImage = (link) => {
|
||||
const token = link.token;
|
||||
|
||||
window.docEditor.insertImage({
|
||||
...typeInsertImageAction,
|
||||
fileType: link.filetype,
|
||||
...(token && { token }),
|
||||
url: link.url,
|
||||
});
|
||||
};
|
||||
|
||||
const mailMerge = (link) => {
|
||||
const token = link.token;
|
||||
|
||||
window.docEditor.setMailMergeRecipients({
|
||||
fileType: link.filetype,
|
||||
...(token && { token }),
|
||||
url: link.url,
|
||||
});
|
||||
};
|
||||
|
||||
const compareFiles = (link) => {
|
||||
const token = link.token;
|
||||
|
||||
window.docEditor.setRevisedFile({
|
||||
fileType: link.filetype,
|
||||
...(token && { token }),
|
||||
url: link.url,
|
||||
});
|
||||
};
|
||||
|
||||
const insertImageActionProps = {
|
||||
isImageOnly: true,
|
||||
};
|
||||
|
||||
const mailMergeActionProps = {
|
||||
isTablesOnly: true,
|
||||
searchParam: ".xlsx",
|
||||
};
|
||||
const compareFilesActionProps = {
|
||||
isDocumentsOnly: true,
|
||||
};
|
||||
|
||||
const fileTypeDetection = () => {
|
||||
if (filesType === insertImageAction) {
|
||||
return insertImageActionProps;
|
||||
}
|
||||
if (filesType === mailMergeAction) {
|
||||
return mailMergeActionProps;
|
||||
}
|
||||
if (filesType === compareFilesAction) {
|
||||
return compareFilesActionProps;
|
||||
}
|
||||
};
|
||||
|
||||
const onSelectFile = async (file) => {
|
||||
try {
|
||||
const link = await getPresignedUri(file.id);
|
||||
|
||||
if (filesType === insertImageAction) insertImage(link);
|
||||
if (filesType === mailMergeAction) mailMerge(link);
|
||||
if (filesType === compareFilesAction) compareFiles(link);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
};
|
||||
|
||||
const getFileTypeTranslation = () => {
|
||||
switch (filesType) {
|
||||
case mailMergeAction:
|
||||
return t("MailMergeFileType");
|
||||
case insertImageAction:
|
||||
return t("ImageFileType");
|
||||
case compareFilesAction:
|
||||
return t("DocumentsFileType");
|
||||
}
|
||||
};
|
||||
|
||||
const selectFilesListTitle = () => {
|
||||
const type = getFileTypeTranslation();
|
||||
return filesType === mailMergeAction
|
||||
? type
|
||||
: t("SelectFilesType", { fileType: type });
|
||||
};
|
||||
|
||||
const onSDKRequestSaveAs = (event) => {
|
||||
setTitleSelectorFolder(event.data.title);
|
||||
setUrlSelectorFolder(event.data.url);
|
||||
setExtension(event.data.title.split(".").pop());
|
||||
|
||||
setIsFolderDialogVisible(true);
|
||||
};
|
||||
|
||||
const onCloseFolderDialog = () => {
|
||||
setIsFolderDialogVisible(false);
|
||||
setNewOpenTab(false);
|
||||
};
|
||||
|
||||
const getSavingInfo = async (title, folderId) => {
|
||||
const savingInfo = await window.filesUtils.SaveAs(
|
||||
title,
|
||||
urlSelectorFolder,
|
||||
folderId,
|
||||
openNewTab
|
||||
);
|
||||
|
||||
if (savingInfo) {
|
||||
const convertedInfo = savingInfo.split(": ").pop();
|
||||
docEditor.showMessage(convertedInfo);
|
||||
}
|
||||
};
|
||||
|
||||
const onClickSaveSelectFolder = (e, folderId) => {
|
||||
const currentExst = titleSelectorFolder.split(".").pop();
|
||||
|
||||
const title =
|
||||
currentExst !== extension
|
||||
? titleSelectorFolder.concat(`.${extension}`)
|
||||
: titleSelectorFolder;
|
||||
|
||||
if (openNewTab) {
|
||||
window.filesUtils.SaveAs(
|
||||
title,
|
||||
urlSelectorFolder,
|
||||
folderId,
|
||||
openNewTab
|
||||
);
|
||||
} else {
|
||||
getSavingInfo(title, folderId);
|
||||
}
|
||||
};
|
||||
|
||||
const onClickCheckbox = () => {
|
||||
setNewOpenTab(!openNewTab);
|
||||
};
|
||||
|
||||
const onChangeInput = (e) => {
|
||||
setTitleSelectorFolder(e.target.value);
|
||||
};
|
||||
|
||||
const sharingDialog = mfReady && (
|
||||
<DynamicComponent
|
||||
className="dynamic-sharing-dialog"
|
||||
system={{
|
||||
scope: FILES_SCOPE,
|
||||
url: FILES_REMOTE_ENTRY_URL,
|
||||
module: "./SharingDialog",
|
||||
name: "SharingDialog",
|
||||
}}
|
||||
isVisible={isVisible}
|
||||
sharingObject={fileInfo}
|
||||
onCancel={onCancel}
|
||||
onSuccess={loadUsersRightsList}
|
||||
settings={props.filesSettings}
|
||||
/>
|
||||
);
|
||||
|
||||
const selectFileDialog = mfReady && props.successAuth && (
|
||||
<DynamicComponent
|
||||
system={{
|
||||
scope: FILES_SCOPE,
|
||||
url: FILES_REMOTE_ENTRY_URL,
|
||||
module: "./SelectFileDialog",
|
||||
}}
|
||||
resetTreeFolders
|
||||
foldersType="exceptPrivacyTrashFolders"
|
||||
isPanelVisible={isFileDialogVisible}
|
||||
onSelectFile={onSelectFile}
|
||||
onClose={onCloseFileDialog}
|
||||
{...fileTypeDetection()}
|
||||
filesListTitle={selectFilesListTitle()}
|
||||
settings={props.filesSettings}
|
||||
/>
|
||||
);
|
||||
|
||||
const selectFolderDialog = mfReady && props.successAuth && (
|
||||
<DynamicComponent
|
||||
system={{
|
||||
scope: FILES_SCOPE,
|
||||
url: FILES_REMOTE_ENTRY_URL,
|
||||
module: "./SelectFolderDialog",
|
||||
}}
|
||||
needProxy
|
||||
folderId={fileInfo?.folderId}
|
||||
isPanelVisible={isFolderDialogVisible}
|
||||
onClose={onCloseFolderDialog}
|
||||
foldersType="exceptSortedByTags"
|
||||
onSave={onClickSaveSelectFolder}
|
||||
isDisableButton={!titleSelectorFolder.trim()}
|
||||
header={
|
||||
<StyledSelectFolder>
|
||||
<Text className="editor-select-folder_text">{t("FileName")}</Text>
|
||||
<TextInput
|
||||
className="editor-select-folder_text-input"
|
||||
scale
|
||||
onChange={onChangeInput}
|
||||
value={titleSelectorFolder}
|
||||
/>
|
||||
</StyledSelectFolder>
|
||||
}
|
||||
{...(extension !== "fb2" && {
|
||||
footer: (
|
||||
<StyledSelectFolder>
|
||||
<Checkbox
|
||||
className="editor-select-folder_checkbox"
|
||||
label={t("OpenSavedDocument")}
|
||||
onChange={onClickCheckbox}
|
||||
isChecked={openNewTab}
|
||||
/>
|
||||
</StyledSelectFolder>
|
||||
),
|
||||
})}
|
||||
/>
|
||||
);
|
||||
|
||||
const preparationPortalDialog = mfReady && (
|
||||
<DynamicComponent
|
||||
system={{
|
||||
scope: STUDIO_SCOPE,
|
||||
url: STUDIO_REMOTE_ENTRY_URL,
|
||||
module: "./PreparationPortalDialog",
|
||||
}}
|
||||
visible={preparationPortalDialogVisible}
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<WrappedComponent
|
||||
{...props}
|
||||
sharingDialog={sharingDialog}
|
||||
onSDKRequestSharingSettings={onSDKRequestSharingSettings}
|
||||
loadUsersRightsList={loadUsersRightsList}
|
||||
isVisible={isVisible}
|
||||
selectFileDialog={selectFileDialog}
|
||||
onSDKRequestInsertImage={onSDKRequestInsertImage}
|
||||
onSDKRequestMailMergeRecipients={onSDKRequestMailMergeRecipients}
|
||||
onSDKRequestCompareFile={onSDKRequestCompareFile}
|
||||
isFileDialogVisible={isFileDialogVisible}
|
||||
selectFolderDialog={selectFolderDialog}
|
||||
onSDKRequestSaveAs={onSDKRequestSaveAs}
|
||||
isFolderDialogVisible={isFolderDialogVisible}
|
||||
preparationPortalDialog={preparationPortalDialog}
|
||||
/>
|
||||
);
|
||||
};
|
||||
};
|
||||
|
||||
export default withDialogs;
|
@ -1,7 +1,7 @@
|
||||
import i18n from "i18next";
|
||||
import { initReactI18next } from "react-i18next";
|
||||
import Backend from "@appserver/common/utils/i18next-http-backend";
|
||||
import config from "../package.json";
|
||||
import config from "../../package.json";
|
||||
import { LANGUAGE } from "@appserver/common/constants";
|
||||
import { loadLanguagePath } from "@appserver/common/utils";
|
||||
|
@ -1,24 +0,0 @@
|
||||
// Override default variables before the import
|
||||
//$font-family-base: "Open Sans", sans-serif;
|
||||
|
||||
html,
|
||||
body {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#root {
|
||||
min-height: 100%;
|
||||
|
||||
.pageLoader {
|
||||
position: fixed;
|
||||
left: calc(50% - 20px);
|
||||
top: 35%;
|
||||
}
|
||||
}
|
||||
body {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
body.loading * {
|
||||
cursor: wait !important;
|
||||
}
|
182
web/ASC.Web.Editor/src/server/index.js
Normal file
182
web/ASC.Web.Editor/src/server/index.js
Normal file
@ -0,0 +1,182 @@
|
||||
import express from "express";
|
||||
import template from "./lib/template";
|
||||
import devMiddleware from "./lib/middleware/devMiddleware";
|
||||
import i18nextMiddleware from "i18next-express-middleware";
|
||||
import i18next from "i18next";
|
||||
import Backend from "i18next-fs-backend";
|
||||
import path from "path";
|
||||
import compression from "compression";
|
||||
import ws from "./lib/websocket";
|
||||
import fs from "fs";
|
||||
import logger from "morgan";
|
||||
import winston from "./lib/logger.js";
|
||||
import { getAssets, initDocEditor } from "./lib/helpers";
|
||||
import GlobalStyle from "../client/components/GlobalStyle.js";
|
||||
import { I18nextProvider } from "react-i18next";
|
||||
import React from "react";
|
||||
import { renderToString } from "react-dom/server";
|
||||
import Editor from "../client/components/Editor.js";
|
||||
import { ServerStyleSheet } from "styled-components";
|
||||
|
||||
const sheet = new ServerStyleSheet();
|
||||
const fallbackLng = "en";
|
||||
const port = PORT || 5013;
|
||||
|
||||
const renderApp = (i18n, initialEditorState) => {
|
||||
return renderToString(
|
||||
sheet.collectStyles(
|
||||
<I18nextProvider i18n={i18n}>
|
||||
<GlobalStyle />
|
||||
<Editor {...initialEditorState} />
|
||||
</I18nextProvider>
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
winston.stream = {
|
||||
write: (message) => winston.info(message),
|
||||
};
|
||||
|
||||
const loadPath = (lng, ns) => {
|
||||
let resourcePath = path.resolve(
|
||||
path.join(__dirname, "client", `locales/${lng}/${ns}.json`)
|
||||
);
|
||||
if (ns === "Common")
|
||||
resourcePath = path.resolve(
|
||||
path.join(__dirname, `../../../public/locales/${lng}/${ns}.json`)
|
||||
);
|
||||
|
||||
return resourcePath;
|
||||
};
|
||||
|
||||
const app = express();
|
||||
|
||||
i18next.use(Backend).init({
|
||||
backend: {
|
||||
loadPath: loadPath,
|
||||
allowMultiLoading: true,
|
||||
crossDomain: false,
|
||||
},
|
||||
fallbackLng: fallbackLng,
|
||||
load: "currentOnly",
|
||||
|
||||
saveMissing: true,
|
||||
ns: ["Editor", "Common"],
|
||||
defaultNS: "Editor",
|
||||
|
||||
interpolation: {
|
||||
escapeValue: false,
|
||||
format: function (value, format) {
|
||||
if (format === "lowercase") return value.toLowerCase();
|
||||
return value;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
app.use(i18nextMiddleware.handle(i18next));
|
||||
app.use(compression());
|
||||
app.use(
|
||||
"/products/files/doceditor/",
|
||||
express.static(path.resolve(path.join(__dirname, "client")))
|
||||
);
|
||||
app.use(logger("dev", { stream: winston.stream }));
|
||||
|
||||
if (IS_DEVELOPMENT) {
|
||||
app.use(devMiddleware);
|
||||
|
||||
app.get("/products/files/doceditor", async (req, res) => {
|
||||
const { i18n, initialEditorState, assets } = req;
|
||||
const userLng = initialEditorState?.user?.cultureName || "en";
|
||||
|
||||
await i18next.changeLanguage(userLng);
|
||||
const initialI18nStoreASC = i18n.services.resourceStore.data;
|
||||
|
||||
if (initialEditorState?.error) {
|
||||
winston.error(initialEditorState.error.errorMessage);
|
||||
}
|
||||
|
||||
const appComponent = renderApp(i18n, initialEditorState);
|
||||
const styleTags = sheet.getStyleTags();
|
||||
|
||||
const htmlString = template(
|
||||
initialEditorState,
|
||||
appComponent,
|
||||
styleTags,
|
||||
initialI18nStoreASC,
|
||||
userLng,
|
||||
assets
|
||||
);
|
||||
|
||||
res.send(htmlString);
|
||||
});
|
||||
|
||||
const server = app.listen(port, () => {
|
||||
winston.info(`Server is listening on port ${port}`);
|
||||
});
|
||||
|
||||
const wss = ws(server);
|
||||
|
||||
const manifestFile = path.resolve(
|
||||
path.join(__dirname, "client/manifest.json")
|
||||
);
|
||||
|
||||
let fsWait = false;
|
||||
let waitTimeout = null;
|
||||
fs.watch(manifestFile, (event, filename) => {
|
||||
if (filename && event === "change") {
|
||||
if (fsWait) return;
|
||||
fsWait = true;
|
||||
waitTimeout = setTimeout(() => {
|
||||
fsWait = false;
|
||||
clearTimeout(waitTimeout);
|
||||
wss.broadcast("reload");
|
||||
}, 100);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
let assets;
|
||||
|
||||
try {
|
||||
assets = getAssets();
|
||||
} catch (e) {
|
||||
winston.error(e.message);
|
||||
}
|
||||
|
||||
app.get("/products/files/doceditor", async (req, res) => {
|
||||
const { i18n } = req;
|
||||
let initialEditorState;
|
||||
|
||||
try {
|
||||
initialEditorState = await initDocEditor(req);
|
||||
} catch (e) {
|
||||
winston.error(e.message);
|
||||
}
|
||||
|
||||
const userLng = initialEditorState?.user?.cultureName || "en";
|
||||
|
||||
await i18next.changeLanguage(userLng);
|
||||
const initialI18nStoreASC = i18n.services.resourceStore.data;
|
||||
|
||||
if (initialEditorState?.error) {
|
||||
winston.error(initialEditorState.error.errorMessage);
|
||||
}
|
||||
|
||||
const appComponent = renderApp(i18n, initialEditorState);
|
||||
const styleTags = sheet.getStyleTags();
|
||||
|
||||
const htmlString = template(
|
||||
initialEditorState,
|
||||
appComponent,
|
||||
styleTags,
|
||||
initialI18nStoreASC,
|
||||
userLng,
|
||||
assets
|
||||
);
|
||||
|
||||
res.send(htmlString);
|
||||
});
|
||||
|
||||
app.listen(port, () => {
|
||||
winston.info(`Server is listening on port ${port}`);
|
||||
});
|
||||
}
|
141
web/ASC.Web.Editor/src/server/lib/helpers/index.js
Normal file
141
web/ASC.Web.Editor/src/server/lib/helpers/index.js
Normal file
@ -0,0 +1,141 @@
|
||||
import path from "path";
|
||||
import fs from "fs";
|
||||
import { initSSR } from "@appserver/common/api/client";
|
||||
import { getUser } from "@appserver/common/api/people";
|
||||
import { getSettings } from "@appserver/common/api/settings";
|
||||
import combineUrl from "@appserver/common/utils/combineUrl";
|
||||
import { AppServerConfig } from "@appserver/common/constants";
|
||||
import {
|
||||
getDocServiceUrl,
|
||||
getFileInfo,
|
||||
openEdit,
|
||||
getSettingsFiles,
|
||||
} from "@appserver/common/api/files";
|
||||
import pkg from "../../../../package.json";
|
||||
|
||||
export const getFavicon = (documentType) => {
|
||||
const { homepage } = pkg;
|
||||
let icon = null;
|
||||
|
||||
switch (documentType) {
|
||||
case "word":
|
||||
icon = "text.ico";
|
||||
break;
|
||||
case "slide":
|
||||
icon = "presentation.ico";
|
||||
break;
|
||||
case "cell":
|
||||
icon = "spreadsheet.ico";
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
const favicon = icon ? `${homepage}/images/${icon}` : "/favicon.ico";
|
||||
return favicon;
|
||||
};
|
||||
|
||||
export const initDocEditor = async (req) => {
|
||||
if (!req) return false;
|
||||
let personal = IS_PERSONAL || null;
|
||||
const { headers, url, query } = req;
|
||||
const { version, desktop: isDesktop } = query;
|
||||
let error = null;
|
||||
initSSR(headers);
|
||||
|
||||
try {
|
||||
const decodedId = query.fileId || query.fileid || null;
|
||||
const fileId =
|
||||
typeof decodedId === "string" ? encodeURIComponent(decodedId) : decodedId;
|
||||
|
||||
if (!fileId) {
|
||||
return {
|
||||
props: {
|
||||
needLoader: true,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const doc = query?.doc || null;
|
||||
const view = url.indexOf("action=view") !== -1;
|
||||
const fileVersion = version || null;
|
||||
|
||||
const [user, settings, filesSettings] = await Promise.all([
|
||||
getUser(),
|
||||
getSettings(),
|
||||
getSettingsFiles(),
|
||||
]);
|
||||
|
||||
const successAuth = !!user;
|
||||
personal = settings?.personal;
|
||||
|
||||
if (!successAuth && !doc) {
|
||||
error = {
|
||||
unAuthorized: true,
|
||||
redirectPath: combineUrl(
|
||||
AppServerConfig.proxyURL,
|
||||
personal ? "/sign-in" : "/login"
|
||||
),
|
||||
};
|
||||
return { error };
|
||||
}
|
||||
|
||||
let [config, docApiUrl, fileInfo] = await Promise.all([
|
||||
openEdit(fileId, fileVersion, doc, view),
|
||||
getDocServiceUrl(),
|
||||
getFileInfo(fileId),
|
||||
]);
|
||||
|
||||
const isSharingAccess = fileInfo && fileInfo.canShare;
|
||||
|
||||
if (view) {
|
||||
config.editorConfig.mode = "view";
|
||||
}
|
||||
|
||||
const actionLink = config?.editorConfig?.actionLink || null;
|
||||
|
||||
return {
|
||||
fileInfo,
|
||||
docApiUrl,
|
||||
config,
|
||||
personal,
|
||||
successAuth,
|
||||
user,
|
||||
error,
|
||||
actionLink,
|
||||
isSharingAccess,
|
||||
url,
|
||||
doc,
|
||||
fileId,
|
||||
view,
|
||||
filesSettings,
|
||||
};
|
||||
} catch (err) {
|
||||
error = { errorMessage: typeof err === "string" ? err : err.message };
|
||||
return { error };
|
||||
}
|
||||
};
|
||||
|
||||
export const getAssets = () => {
|
||||
const manifest = fs.readFileSync(
|
||||
path.join(__dirname, "client/manifest.json"),
|
||||
"utf-8"
|
||||
);
|
||||
|
||||
const assets = JSON.parse(manifest);
|
||||
|
||||
return assets;
|
||||
};
|
||||
|
||||
export const getScripts = (assets) => {
|
||||
const regTest = /static\/js\/.*/;
|
||||
const keys = [];
|
||||
|
||||
for (let key in assets) {
|
||||
if (assets.hasOwnProperty(key) && regTest.test(key)) {
|
||||
keys.push(key);
|
||||
}
|
||||
}
|
||||
|
||||
return keys;
|
||||
};
|
67
web/ASC.Web.Editor/src/server/lib/logger.js
Normal file
67
web/ASC.Web.Editor/src/server/lib/logger.js
Normal file
@ -0,0 +1,67 @@
|
||||
import winston from "winston";
|
||||
import "winston-daily-rotate-file";
|
||||
import path from "path";
|
||||
import fs from "fs";
|
||||
|
||||
let logpath = process.env.logpath || null;
|
||||
|
||||
if (logpath != null) {
|
||||
if (!path.isAbsolute(logpath)) {
|
||||
logpath = path.join(__dirname, "..", logpath);
|
||||
}
|
||||
}
|
||||
|
||||
const fileName = !IS_DEVELOPMENT
|
||||
? path.join(__dirname, "..", "..", "..", "Logs", "editor.%DATE%.log")
|
||||
: logpath
|
||||
? path.join(logpath, "editor.%DATE%.log")
|
||||
: path.join(
|
||||
__dirname,
|
||||
"..",
|
||||
"..",
|
||||
"..",
|
||||
"..",
|
||||
"..",
|
||||
"Logs",
|
||||
"editor.%DATE%.log"
|
||||
);
|
||||
const dirName = path.dirname(fileName);
|
||||
|
||||
if (!fs.existsSync(dirName)) {
|
||||
fs.mkdirSync(dirName);
|
||||
}
|
||||
|
||||
const options = {
|
||||
file: {
|
||||
filename: fileName,
|
||||
datePattern: "MM-DD",
|
||||
handleExceptions: true,
|
||||
humanReadableUnhandledException: true,
|
||||
zippedArchive: true,
|
||||
maxSize: "50m",
|
||||
maxFiles: "30d",
|
||||
json: true,
|
||||
},
|
||||
console: {
|
||||
level: "debug",
|
||||
handleExceptions: true,
|
||||
json: false,
|
||||
colorize: true,
|
||||
},
|
||||
};
|
||||
|
||||
const transports = [
|
||||
new winston.transports.Console(options.console),
|
||||
new winston.transports.DailyRotateFile(options.file),
|
||||
];
|
||||
|
||||
export default new winston.createLogger({
|
||||
format: winston.format.combine(
|
||||
winston.format.timestamp({
|
||||
format: "YYYY-MM-DD HH:mm:ss",
|
||||
}),
|
||||
winston.format.json()
|
||||
),
|
||||
transports: transports,
|
||||
exitOnError: false,
|
||||
});
|
@ -0,0 +1,17 @@
|
||||
import winston from "../logger.js";
|
||||
import { getAssets, initDocEditor } from "../helpers";
|
||||
|
||||
winston.stream = {
|
||||
write: (message) => winston.info(message),
|
||||
};
|
||||
|
||||
export default async (req, res, next) => {
|
||||
try {
|
||||
const assets = await getAssets();
|
||||
req.initialEditorState = await initDocEditor(req);
|
||||
req.assets = assets;
|
||||
} catch (e) {
|
||||
winston.error(e.message);
|
||||
}
|
||||
next();
|
||||
};
|
391
web/ASC.Web.Editor/src/server/lib/template.js
Normal file
391
web/ASC.Web.Editor/src/server/lib/template.js
Normal file
@ -0,0 +1,391 @@
|
||||
import { getFavicon, getScripts } from "./helpers";
|
||||
import pkg from "../../../package.json";
|
||||
|
||||
export default function template(
|
||||
initialEditorState = {},
|
||||
appComponent = "",
|
||||
styleTags,
|
||||
initialI18nStoreASC,
|
||||
initialLanguage,
|
||||
assets
|
||||
) {
|
||||
const { title } = pkg;
|
||||
const { docApiUrl, error } = initialEditorState;
|
||||
|
||||
const faviconHref = getFavicon(initialEditorState?.config?.documentType);
|
||||
|
||||
let clientScripts =
|
||||
assets && assets.hasOwnProperty("client.js")
|
||||
? `<script defer="defer" src='${assets["client.js"]}'></script>`
|
||||
: "";
|
||||
|
||||
const editorApiScript =
|
||||
error || !docApiUrl
|
||||
? ""
|
||||
: `<script type='text/javascript' id='scripDocServiceAddress' src="${docApiUrl}" async></script>`;
|
||||
|
||||
if (!IS_DEVELOPMENT) {
|
||||
const productionBundleKeys = getScripts(assets);
|
||||
productionBundleKeys.map((key) => {
|
||||
clientScripts =
|
||||
clientScripts + `<script defer="defer" src='${assets[key]}'></script>`;
|
||||
});
|
||||
}
|
||||
|
||||
const scripts = `
|
||||
<script id="__ASC_INITIAL_EDITOR_STATE__">
|
||||
window.__ASC_INITIAL_EDITOR_STATE__ = ${JSON.stringify(
|
||||
initialEditorState
|
||||
)}
|
||||
</script>
|
||||
<script id="__ASC_INITIAL_EDITOR_I18N__">
|
||||
window.initialI18nStoreASC = ${JSON.stringify(initialI18nStoreASC)}
|
||||
window.initialLanguage = '${initialLanguage}'
|
||||
</script>
|
||||
${clientScripts}
|
||||
<script>
|
||||
const tempElm = document.getElementById("loader");
|
||||
tempElm.style.backgroundColor =
|
||||
localStorage.theme === "Dark" ? "#333333" : "#f4f4f4";
|
||||
console.log("It's Editor INIT");
|
||||
</script>
|
||||
${editorApiScript}
|
||||
|
||||
`;
|
||||
// <script defer="defer" src='${assets["runtime.js"]}'></script>
|
||||
// <script defer="defer" src='${assets["vendor.js"]}'></script>
|
||||
const page = `
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title> ${title} </title>
|
||||
<meta charset="utf-8" />
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width, initial-scale=1, shrink-to-fit=no, user-scalable=no, viewport-fit=cover"
|
||||
/>
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<link id="favicon" rel="shortcut icon" href=${faviconHref} type="image/x-icon"/>
|
||||
<link rel="manifest" href="/manifest.json" />
|
||||
<meta name="mobile-web-app-capable" content="yes" />
|
||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||
<link rel="apple-touch-icon" href="/appIcon.png" />
|
||||
${styleTags}
|
||||
<style type="text/css">
|
||||
.loadmask {
|
||||
left: 0;
|
||||
top: 0;
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
border: none;
|
||||
background-color: #f4f4f4;
|
||||
z-index: 1001;
|
||||
}
|
||||
.loader-page {
|
||||
width: 100%;
|
||||
height: 170px;
|
||||
bottom: 42%;
|
||||
position: absolute;
|
||||
text-align: center;
|
||||
line-height: 10px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.loader-page-romb {
|
||||
width: 40px;
|
||||
display: inline-block;
|
||||
}
|
||||
.romb {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
position: absolute;
|
||||
background: red;
|
||||
border-radius: 6px;
|
||||
-webkit-transform: rotate(135deg) skew(20deg, 20deg);
|
||||
-moz-transform: rotate(135deg) skew(20deg, 20deg);
|
||||
-ms-transform: rotate(135deg) skew(20deg, 20deg);
|
||||
-o-transform: rotate(135deg) skew(20deg, 20deg);
|
||||
-webkit-animation: movedown 3s infinite ease;
|
||||
-moz-animation: movedown 3s infinite ease;
|
||||
-ms-animation: movedown 3s infinite ease;
|
||||
-o-animation: movedown 3s infinite ease;
|
||||
animation: movedown 3s infinite ease;
|
||||
}
|
||||
#blue {
|
||||
z-index: 3;
|
||||
background: #55bce6;
|
||||
-webkit-animation-name: blue;
|
||||
-moz-animation-name: blue;
|
||||
-ms-animation-name: blue;
|
||||
-o-animation-name: blue;
|
||||
animation-name: blue;
|
||||
}
|
||||
#red {
|
||||
z-index: 1;
|
||||
background: #de7a59;
|
||||
-webkit-animation-name: red;
|
||||
-moz-animation-name: red;
|
||||
-ms-animation-name: red;
|
||||
-o-animation-name: red;
|
||||
animation-name: red;
|
||||
}
|
||||
#green {
|
||||
z-index: 2;
|
||||
background: #a1cb5c;
|
||||
-webkit-animation-name: green;
|
||||
-moz-animation-name: green;
|
||||
-ms-animation-name: green;
|
||||
-o-animation-name: green;
|
||||
animation-name: green;
|
||||
}
|
||||
@-webkit-keyframes red {
|
||||
0% {
|
||||
top: 120px;
|
||||
background: #de7a59;
|
||||
}
|
||||
10% {
|
||||
top: 120px;
|
||||
background: #f2cbbf;
|
||||
}
|
||||
14% {
|
||||
background: #f4f4f4;
|
||||
top: 120px;
|
||||
}
|
||||
15% {
|
||||
background: #f4f4f4;
|
||||
top: 0;
|
||||
}
|
||||
20% {
|
||||
background: #e6e4e4;
|
||||
}
|
||||
30% {
|
||||
background: #d2d2d2;
|
||||
}
|
||||
40% {
|
||||
top: 120px;
|
||||
}
|
||||
100% {
|
||||
top: 120px;
|
||||
background: #de7a59;
|
||||
}
|
||||
}
|
||||
@keyframesred {
|
||||
0% {
|
||||
top: 120px;
|
||||
background: #de7a59;
|
||||
}
|
||||
10% {
|
||||
top: 120px;
|
||||
background: #f2cbbf;
|
||||
}
|
||||
14% {
|
||||
background: #f4f4f4;
|
||||
top: 120px;
|
||||
}
|
||||
15% {
|
||||
background: #f4f4f4;
|
||||
top: 0;
|
||||
}
|
||||
20% {
|
||||
background: #e6e4e4;
|
||||
}
|
||||
30% {
|
||||
background: #d2d2d2;
|
||||
}
|
||||
40% {
|
||||
top: 120px;
|
||||
}
|
||||
100% {
|
||||
top: 120px;
|
||||
background: #de7a59;
|
||||
}
|
||||
}
|
||||
@-webkit-keyframes green {
|
||||
0% {
|
||||
top: 110px;
|
||||
background: #a1cb5c;
|
||||
opacity: 1;
|
||||
}
|
||||
10% {
|
||||
top: 110px;
|
||||
background: #cbe0ac;
|
||||
opacity: 1;
|
||||
}
|
||||
14% {
|
||||
background: #f4f4f4;
|
||||
top: 110px;
|
||||
opacity: 1;
|
||||
}
|
||||
15% {
|
||||
background: #f4f4f4;
|
||||
top: 0;
|
||||
opacity: 1;
|
||||
}
|
||||
20% {
|
||||
background: #f4f4f4;
|
||||
top: 0;
|
||||
opacity: 0;
|
||||
}
|
||||
25% {
|
||||
background: #efefef;
|
||||
top: 0;
|
||||
opacity: 1;
|
||||
}
|
||||
30% {
|
||||
background: #e6e4e4;
|
||||
}
|
||||
70% {
|
||||
top: 110px;
|
||||
}
|
||||
100% {
|
||||
top: 110px;
|
||||
background: #a1cb5c;
|
||||
}
|
||||
}
|
||||
@keyframes green {
|
||||
0% {
|
||||
top: 110px;
|
||||
background: #a1cb5c;
|
||||
opacity: 1;
|
||||
}
|
||||
10% {
|
||||
top: 110px;
|
||||
background: #cbe0ac;
|
||||
opacity: 1;
|
||||
}
|
||||
14% {
|
||||
background: #f4f4f4;
|
||||
top: 110px;
|
||||
opacity: 1;
|
||||
}
|
||||
15% {
|
||||
background: #f4f4f4;
|
||||
top: 0;
|
||||
opacity: 1;
|
||||
}
|
||||
20% {
|
||||
background: #f4f4f4;
|
||||
top: 0;
|
||||
opacity: 0;
|
||||
}
|
||||
25% {
|
||||
background: #efefef;
|
||||
top: 0;
|
||||
opacity: 1;
|
||||
}
|
||||
30% {
|
||||
background: #e6e4e4;
|
||||
}
|
||||
70% {
|
||||
top: 110px;
|
||||
}
|
||||
100% {
|
||||
top: 110px;
|
||||
background: #a1cb5c;
|
||||
}
|
||||
}
|
||||
@-webkit-keyframes blue {
|
||||
0% {
|
||||
top: 100px;
|
||||
background: #55bce6;
|
||||
opacity: 1;
|
||||
}
|
||||
10% {
|
||||
top: 100px;
|
||||
background: #bfe8f8;
|
||||
opacity: 1;
|
||||
}
|
||||
14% {
|
||||
background: #f4f4f4;
|
||||
top: 100px;
|
||||
opacity: 1;
|
||||
}
|
||||
15% {
|
||||
background: #f4f4f4;
|
||||
top: 0;
|
||||
opacity: 1;
|
||||
}
|
||||
20% {
|
||||
background: #f4f4f4;
|
||||
top: 0;
|
||||
opacity: 0;
|
||||
}
|
||||
25% {
|
||||
background: #f4f4f4;
|
||||
top: 0;
|
||||
opacity: 0;
|
||||
}
|
||||
45% {
|
||||
background: #efefef;
|
||||
top: 0;
|
||||
opacity: 0.2;
|
||||
}
|
||||
100% {
|
||||
top: 100px;
|
||||
background: #55bce6;
|
||||
}
|
||||
}
|
||||
@keyframes blue {
|
||||
0% {
|
||||
top: 100px;
|
||||
background: #55bce6;
|
||||
opacity: 1;
|
||||
}
|
||||
10% {
|
||||
top: 100px;
|
||||
background: #bfe8f8;
|
||||
opacity: 1;
|
||||
}
|
||||
14% {
|
||||
background: #f4f4f4;
|
||||
top: 100px;
|
||||
opacity: 1;
|
||||
}
|
||||
15% {
|
||||
background: #f4f4f4;
|
||||
top: 0;
|
||||
opacity: 1;
|
||||
}
|
||||
20% {
|
||||
background: #f4f4f4;
|
||||
top: 0;
|
||||
opacity: 0;
|
||||
}
|
||||
25% {
|
||||
background: #fff;
|
||||
top: 0;
|
||||
opacity: 0;
|
||||
}
|
||||
45% {
|
||||
background: #efefef;
|
||||
top: 0;
|
||||
opacity: 0.2;
|
||||
}
|
||||
100% {
|
||||
top: 100px;
|
||||
background: #55bce6;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="loader" class="loadmask">
|
||||
<div class="loader-page">
|
||||
<div class="loader-page-romb">
|
||||
<div class="romb" id="blue"></div>
|
||||
<div class="romb" id="green"></div>
|
||||
<div class="romb" id="red"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="root">${appComponent}</div>
|
||||
<noscript> You need to enable JavaScript to run this app. </noscript>
|
||||
${scripts}
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
|
||||
return page;
|
||||
}
|
30
web/ASC.Web.Editor/src/server/lib/websocket.js
Normal file
30
web/ASC.Web.Editor/src/server/lib/websocket.js
Normal file
@ -0,0 +1,30 @@
|
||||
const WebSocket = require("ws");
|
||||
const pkg = require("../../../package.json");
|
||||
const { socketPath } = pkg;
|
||||
|
||||
module.exports = (expressServer) => {
|
||||
const wss = new WebSocket.Server({
|
||||
noServer: true,
|
||||
path: socketPath,
|
||||
});
|
||||
|
||||
expressServer.on("upgrade", (request, socket, head) => {
|
||||
wss.handleUpgrade(request, socket, head, (websocket) => {
|
||||
wss.emit("connection", websocket, request);
|
||||
});
|
||||
});
|
||||
|
||||
wss.on("connection", function connection(ws) {
|
||||
ws.on("message", function (message) {
|
||||
wss.broadcast(message);
|
||||
});
|
||||
});
|
||||
|
||||
wss.broadcast = function broadcast(msg) {
|
||||
wss.clients.forEach(function each(client) {
|
||||
client.send(msg);
|
||||
});
|
||||
};
|
||||
|
||||
return wss;
|
||||
};
|
@ -1,217 +0,0 @@
|
||||
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
|
||||
const CopyPlugin = require("copy-webpack-plugin");
|
||||
const HtmlWebpackPlugin = require("html-webpack-plugin");
|
||||
const ModuleFederationPlugin = require("webpack").container
|
||||
.ModuleFederationPlugin;
|
||||
const ExternalTemplateRemotesPlugin = require("external-remotes-plugin");
|
||||
const TerserPlugin = require("terser-webpack-plugin");
|
||||
const DefinePlugin = require("webpack").DefinePlugin;
|
||||
|
||||
const combineUrl = require("@appserver/common/utils/combineUrl");
|
||||
const minifyJson = require("@appserver/common/utils/minifyJson");
|
||||
const AppServerConfig = require("@appserver/common/constants/AppServerConfig");
|
||||
const sharedDeps = require("@appserver/common/constants/sharedDependencies");
|
||||
|
||||
const { proxyURL } = AppServerConfig;
|
||||
|
||||
const path = require("path");
|
||||
const pkg = require("./package.json");
|
||||
const deps = pkg.dependencies || {};
|
||||
const homepage = pkg.homepage; // combineUrl(AppServerConfig.proxyURL, pkg.homepage);
|
||||
const title = pkg.title;
|
||||
|
||||
const config = {
|
||||
entry: "./src/index",
|
||||
target: "web",
|
||||
mode: "development",
|
||||
|
||||
devServer: {
|
||||
devMiddleware: {
|
||||
publicPath: homepage,
|
||||
},
|
||||
static: {
|
||||
directory: path.join(__dirname, "dist"),
|
||||
publicPath: homepage,
|
||||
},
|
||||
port: 5013,
|
||||
historyApiFallback: {
|
||||
// Paths with dots should still use the history fallback.
|
||||
// See https://github.com/facebook/create-react-app/issues/387.
|
||||
disableDotRule: true,
|
||||
index: homepage,
|
||||
},
|
||||
hot: false,
|
||||
headers: {
|
||||
"Access-Control-Allow-Origin": "*",
|
||||
"Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, PATCH, OPTIONS",
|
||||
"Access-Control-Allow-Headers":
|
||||
"X-Requested-With, content-type, Authorization",
|
||||
},
|
||||
},
|
||||
|
||||
resolve: {
|
||||
extensions: [".jsx", ".js", ".json"],
|
||||
fallback: {
|
||||
crypto: false,
|
||||
},
|
||||
},
|
||||
|
||||
output: {
|
||||
publicPath: "auto",
|
||||
chunkFilename: "static/js/[id].[contenthash].js",
|
||||
//assetModuleFilename: "static/images/[hash][ext][query]",
|
||||
path: path.resolve(process.cwd(), "dist"),
|
||||
filename: "static/js/[name].[contenthash].bundle.js",
|
||||
},
|
||||
|
||||
performance: {
|
||||
maxEntrypointSize: 512000,
|
||||
maxAssetSize: 512000,
|
||||
},
|
||||
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.(png|jpe?g|gif|ico)$/i,
|
||||
type: "asset/resource",
|
||||
generator: {
|
||||
filename: "static/images/[hash][ext][query]",
|
||||
},
|
||||
},
|
||||
{
|
||||
test: /\.m?js/,
|
||||
type: "javascript/auto",
|
||||
resolve: {
|
||||
fullySpecified: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
test: /\.react.svg$/,
|
||||
use: [
|
||||
{
|
||||
loader: "@svgr/webpack",
|
||||
options: {
|
||||
svgoConfig: {
|
||||
plugins: [{ removeViewBox: false }],
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{ test: /\.json$/, loader: "json-loader" },
|
||||
{
|
||||
test: /\.css$/i,
|
||||
use: ["style-loader", "css-loader"],
|
||||
},
|
||||
{
|
||||
test: /\.s[ac]ss$/i,
|
||||
use: [
|
||||
// Creates `style` nodes from JS strings
|
||||
"style-loader",
|
||||
// Translates CSS into CommonJS
|
||||
{
|
||||
loader: "css-loader",
|
||||
options: {
|
||||
url: {
|
||||
filter: (url, resourcePath) => {
|
||||
// resourcePath - path to css file
|
||||
|
||||
// Don't handle `/static` urls
|
||||
if (url.startsWith("/static") || url.startsWith("data:")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
// Compiles Sass to CSS
|
||||
"sass-loader",
|
||||
],
|
||||
},
|
||||
|
||||
{
|
||||
test: /\.(js|jsx)$/,
|
||||
exclude: /node_modules/,
|
||||
use: [
|
||||
{
|
||||
loader: "babel-loader",
|
||||
options: {
|
||||
presets: ["@babel/preset-react", "@babel/preset-env"],
|
||||
plugins: [
|
||||
"@babel/plugin-transform-runtime",
|
||||
"@babel/plugin-proposal-class-properties",
|
||||
"@babel/plugin-proposal-export-default-from",
|
||||
],
|
||||
},
|
||||
},
|
||||
"source-map-loader",
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
plugins: [
|
||||
new CleanWebpackPlugin(),
|
||||
new ModuleFederationPlugin({
|
||||
name: "editor",
|
||||
filename: "remoteEntry.js",
|
||||
remotes: {
|
||||
studio: `studio@${combineUrl(proxyURL, "/remoteEntry.js")}`,
|
||||
files: `files@${combineUrl(
|
||||
proxyURL,
|
||||
"/products/files/remoteEntry.js"
|
||||
)}`,
|
||||
},
|
||||
exposes: {
|
||||
"./app": "./src/Editor.jsx",
|
||||
},
|
||||
shared: {
|
||||
...deps,
|
||||
...sharedDeps,
|
||||
},
|
||||
}),
|
||||
new ExternalTemplateRemotesPlugin(),
|
||||
new HtmlWebpackPlugin({
|
||||
template: "./public/index.html",
|
||||
publicPath: homepage,
|
||||
title: title,
|
||||
base: `${homepage}/`,
|
||||
}),
|
||||
new CopyPlugin({
|
||||
patterns: [
|
||||
{
|
||||
context: path.resolve(__dirname, "public"),
|
||||
from: "images/**/*.*",
|
||||
},
|
||||
{
|
||||
context: path.resolve(__dirname, "public"),
|
||||
from: "locales/**/*.json",
|
||||
transform: minifyJson,
|
||||
},
|
||||
],
|
||||
}),
|
||||
],
|
||||
};
|
||||
|
||||
module.exports = (env, argv) => {
|
||||
if (argv.mode === "production") {
|
||||
config.mode = "production";
|
||||
config.optimization = {
|
||||
splitChunks: { chunks: "all" },
|
||||
minimize: !env.minimize,
|
||||
minimizer: [new TerserPlugin()],
|
||||
};
|
||||
} else {
|
||||
config.devtool = "cheap-module-source-map";
|
||||
}
|
||||
|
||||
config.plugins.push(
|
||||
new DefinePlugin({
|
||||
IS_PERSONAL: env.personal || false,
|
||||
})
|
||||
);
|
||||
|
||||
return config;
|
||||
};
|
45
web/ASC.Web.Editor/webpack/webpack.base.js
Normal file
45
web/ASC.Web.Editor/webpack/webpack.base.js
Normal file
@ -0,0 +1,45 @@
|
||||
const scriptExtensions = /\.(tsx|ts|js|jsx|mjs)$/;
|
||||
const imageExtensions = /\.(bmp|gif|jpg|jpeg|png)$/;
|
||||
const fontsExtension = /\.(eot|otf|ttf|woff|woff2)$/;
|
||||
|
||||
module.exports = {
|
||||
resolve: {
|
||||
extensions: [".js", ".jsx", ".json", ".ts", ".tsx"],
|
||||
fallback: {
|
||||
crypto: false,
|
||||
},
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: scriptExtensions,
|
||||
exclude: /node_modules/,
|
||||
use: [
|
||||
{
|
||||
loader: "babel-loader",
|
||||
options: {
|
||||
presets: ["@babel/preset-react", "@babel/preset-env"],
|
||||
plugins: [
|
||||
"@babel/plugin-transform-runtime",
|
||||
"@babel/plugin-proposal-class-properties",
|
||||
"@babel/plugin-proposal-export-default-from",
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
test: fontsExtension,
|
||||
type: "asset",
|
||||
},
|
||||
{
|
||||
test: /\.svg/,
|
||||
type: "asset/inline",
|
||||
},
|
||||
{
|
||||
test: imageExtensions,
|
||||
type: "asset/resource",
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
132
web/ASC.Web.Editor/webpack/webpack.client.js
Normal file
132
web/ASC.Web.Editor/webpack/webpack.client.js
Normal file
@ -0,0 +1,132 @@
|
||||
const { merge } = require("webpack-merge");
|
||||
const path = require("path");
|
||||
const ModuleFederationPlugin = require("webpack").container
|
||||
.ModuleFederationPlugin;
|
||||
const DefinePlugin = require("webpack").DefinePlugin;
|
||||
const { WebpackManifestPlugin } = require("webpack-manifest-plugin");
|
||||
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
|
||||
const ExternalTemplateRemotesPlugin = require("external-remotes-plugin");
|
||||
const CopyPlugin = require("copy-webpack-plugin");
|
||||
const TerserPlugin = require("terser-webpack-plugin");
|
||||
const combineUrl = require("@appserver/common/utils/combineUrl");
|
||||
const minifyJson = require("@appserver/common/utils/minifyJson");
|
||||
const AppServerConfig = require("@appserver/common/constants/AppServerConfig");
|
||||
const { proxyURL } = AppServerConfig;
|
||||
const sharedDeps = require("@appserver/common/constants/sharedDependencies");
|
||||
const baseConfig = require("./webpack.base.js");
|
||||
|
||||
for (let dep in sharedDeps) {
|
||||
sharedDeps[dep].eager = true;
|
||||
}
|
||||
|
||||
const clientConfig = {
|
||||
target: "web",
|
||||
// mode: "development",
|
||||
entry: {
|
||||
client: ["./src/client/index.js"],
|
||||
},
|
||||
devtool: "inline-cheap-module-source-map",
|
||||
|
||||
output: {
|
||||
path: path.resolve(process.cwd(), "dist/client"),
|
||||
filename: "static/js/[name].[contenthash].bundle.js",
|
||||
publicPath: "/products/files/doceditor/",
|
||||
chunkFilename: "static/js/[id].[contenthash].js",
|
||||
},
|
||||
|
||||
performance: {
|
||||
maxEntrypointSize: 512000,
|
||||
maxAssetSize: 512000,
|
||||
},
|
||||
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.s[ac]ss$/i,
|
||||
use: [
|
||||
// Creates `style` nodes from JS strings
|
||||
"style-loader",
|
||||
// Translates CSS into CommonJS
|
||||
{
|
||||
loader: "css-loader",
|
||||
options: {
|
||||
url: {
|
||||
filter: (url, resourcePath) => {
|
||||
// resourcePath - path to css file
|
||||
|
||||
// Don't handle `/static` urls
|
||||
if (url.startsWith("/static") || url.startsWith("data:")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
// Compiles Sass to CSS
|
||||
"sass-loader",
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
plugins: [
|
||||
new CleanWebpackPlugin(),
|
||||
new ModuleFederationPlugin({
|
||||
name: "editor",
|
||||
filename: "remoteEntry.js",
|
||||
remotes: {
|
||||
studio: `studio@${combineUrl(proxyURL, "/remoteEntry.js")}`,
|
||||
files: `files@${combineUrl(
|
||||
proxyURL,
|
||||
"/products/files/remoteEntry.js"
|
||||
)}`,
|
||||
},
|
||||
exposes: {
|
||||
"./app": "./src/client/index.js",
|
||||
},
|
||||
shared: { ...sharedDeps },
|
||||
}),
|
||||
new ExternalTemplateRemotesPlugin(),
|
||||
new CopyPlugin({
|
||||
patterns: [
|
||||
{
|
||||
context: path.resolve(process.cwd(), "public"),
|
||||
from: "images/**/*.*",
|
||||
},
|
||||
{
|
||||
context: path.resolve(process.cwd(), "public"),
|
||||
from: "locales/**/*.json",
|
||||
transform: minifyJson,
|
||||
},
|
||||
],
|
||||
}),
|
||||
new WebpackManifestPlugin(),
|
||||
],
|
||||
};
|
||||
|
||||
module.exports = (env, argv) => {
|
||||
if (argv.mode === "production") {
|
||||
clientConfig.mode = "production";
|
||||
clientConfig.optimization = {
|
||||
splitChunks: { chunks: "all" },
|
||||
minimize: !env.minimize,
|
||||
minimizer: [new TerserPlugin()],
|
||||
};
|
||||
} else {
|
||||
clientConfig.mode = "development";
|
||||
clientConfig.devtool = "cheap-module-source-map";
|
||||
}
|
||||
|
||||
clientConfig.plugins = [
|
||||
...clientConfig.plugins,
|
||||
new DefinePlugin({
|
||||
IS_DEVELOPMENT: argv.mode !== "production",
|
||||
PORT: process.env.PORT || 5013,
|
||||
IS_PERSONAL: env.personal || false,
|
||||
}),
|
||||
];
|
||||
|
||||
return merge(baseConfig, clientConfig);
|
||||
};
|
44
web/ASC.Web.Editor/webpack/webpack.server.js
Normal file
44
web/ASC.Web.Editor/webpack/webpack.server.js
Normal file
@ -0,0 +1,44 @@
|
||||
const { merge } = require("webpack-merge");
|
||||
const baseConfig = require("./webpack.base.js");
|
||||
const webpackNodeExternals = require("webpack-node-externals");
|
||||
const path = require("path");
|
||||
const DefinePlugin = require("webpack").DefinePlugin;
|
||||
const TerserPlugin = require("terser-webpack-plugin");
|
||||
|
||||
const serverConfig = {
|
||||
target: "node",
|
||||
//mode: "development",
|
||||
name: "server",
|
||||
entry: {
|
||||
server: "./src/server/index.js",
|
||||
},
|
||||
|
||||
output: {
|
||||
path: path.resolve(process.cwd(), "dist/"),
|
||||
filename: "[name].js",
|
||||
libraryTarget: "commonjs2",
|
||||
chunkFilename: "chunks/[name].js",
|
||||
},
|
||||
externals: [webpackNodeExternals(), { express: "express" }],
|
||||
};
|
||||
|
||||
module.exports = (env, argv) => {
|
||||
if (argv.mode === "production") {
|
||||
serverConfig.mode = "production";
|
||||
serverConfig.optimization = {
|
||||
minimize: !env.minimize,
|
||||
minimizer: [new TerserPlugin()],
|
||||
};
|
||||
} else {
|
||||
serverConfig.mode = "development";
|
||||
}
|
||||
serverConfig.plugins = [
|
||||
new DefinePlugin({
|
||||
IS_DEVELOPMENT: argv.mode !== "production",
|
||||
PORT: process.env.PORT || 5013,
|
||||
IS_PERSONAL: env.personal || false,
|
||||
}),
|
||||
];
|
||||
|
||||
return merge(baseConfig, serverConfig);
|
||||
};
|
@ -70,8 +70,8 @@
|
||||
"style-loader": "3.2.1",
|
||||
"terser-webpack-plugin": "^5.2.4",
|
||||
"webpack": "5.52.1",
|
||||
"webpack-cli": "4.9.0",
|
||||
"webpack-cli": "4.10.0",
|
||||
"webpack-dev-server": "4.3.1"
|
||||
},
|
||||
"title": "ONLYOFFICE"
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user