Web: Doceditor: refactoring server

This commit is contained in:
Artem Tarasov 2022-07-17 12:46:17 +03:00
parent 4d9422ef0d
commit 3ac7597ef7
11 changed files with 532 additions and 2078 deletions

View File

@ -83,7 +83,6 @@ export const initDocEditor = async (req) => {
const actionLink = config?.editorConfig?.actionLink || null;
return {
props: {
fileInfo,
docApiUrl,
config,
@ -97,7 +96,6 @@ export const initDocEditor = async (req) => {
doc,
fileId,
view,
},
};
} catch (err) {
error = { errorMessage: typeof err === "string" ? err : err.message };

View File

@ -1,140 +0,0 @@
const express = require("express");
const fs = require("fs");
const path = require("path");
const React = require("react");
const ReactDOMServer = require("react-dom/server");
const compression = require("compression");
const i18nextMiddleware = require("i18next-express-middleware");
const i18next = require("i18next");
const Backend = require("i18next-fs-backend");
const process = require("process");
const { ServerStyleSheet } = require("styled-components");
const pkg = require("../../package.json");
const title = pkg.title;
const { App } = require("../client/App");
const initDocEditor = require("./lib/initDocEditor");
const loadPath = (lng, ns) => {
let resourcePath = path.resolve(
process.cwd(),
`dist/locales/${lng}/${ns}.json`
);
if (ns === "Common")
resourcePath = path.join(
__dirname,
`../../../../public/locales/${lng}/${ns}.json`
);
return resourcePath;
};
const app = express();
const port = process.env.PORT || 5013;
i18next.use(Backend).init({
backend: {
loadPath: loadPath,
allowMultiLoading: true,
crossDomain: false,
},
fallbackLng: "en",
load: "currentOnly",
saveMissing: true,
ns: ["Editor", "Common"],
defaultNS: "Editor",
interpolation: {
escapeValue: false, // not needed for react as it escapes by default
format: function (value, format) {
if (format === "lowercase") return value.toLowerCase();
return value;
},
},
});
app.set("view engine", "ejs");
app.set("views", path.join(__dirname, "views"));
// app.get(
// /\.(js|css|map|ico)$/,
// express.static(path.resolve(process.cwd(), "dist"))
// );
app.use(i18nextMiddleware.handle(i18next));
app.use(compression());
app.use(
"/products/files/doceditor/",
express.static(path.resolve(process.cwd(), "dist"))
);
//app.use(express.static(path.resolve(process.cwd(), "dist")));
app.get("/products/files/doceditor", async (req, res) => {
const manifest = fs.readFileSync(
path.join(process.cwd(), "dist/manifest.json"),
"utf-8"
);
const initialState = await initDocEditor(req);
const assets = JSON.parse(manifest);
const sheet = new ServerStyleSheet();
const component = ReactDOMServer.renderToString(
sheet.collectStyles(React.createElement(App))
);
const styleTags = sheet.getStyleTags();
const userLng = initialState?.props?.user?.cultureName || "en";
const initialI18nStore = {};
i18next.changeLanguage(userLng).then(() => {
const initialLanguage = userLng;
const usedNamespaces = req.i18n.reportNamespaces
? req.i18n.reportNamespaces.getUsedNamespaces()
: ["Common", "Editor"];
initialI18nStore[initialLanguage] = {};
usedNamespaces.forEach((namespace) => {
initialI18nStore[initialLanguage][namespace] =
req.i18n.services.resourceStore.data[initialLanguage][namespace];
});
const parsedI18nStore = JSON.stringify(initialI18nStore);
const parsedInitialState = JSON.stringify(initialState);
const docApiUrl = initialState?.props?.docApiUrl || "";
res.render("editor", {
assets,
component,
title,
parsedI18nStore,
initialLanguage,
parsedInitialState,
docApiUrl,
styleTags,
});
});
});
const server = app.listen(port, () => {
console.log(`Server is listening on port <http://localhost:${port}>`);
});
if (process.env.NODE_ENV === "development") {
const ws = require("./websocket");
const wss = ws(server);
const manifestFile = path.resolve(process.cwd(), "dist/manifest.json");
let fsWait = false;
fs.watch(manifestFile, (event, filename) => {
if (filename && event === "change") {
if (fsWait) return;
fsWait = true;
fsWait = setTimeout(() => {
fsWait = false;
}, 100);
wss.broadcast("reload");
}
});
}

View File

@ -1,107 +1,104 @@
// import express from "express";
// import template from "./template";
// import render from "./render";
// 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 webpack from "webpack";
// // import WebpackDevMiddleware from "webpack-dev-middleware";
// // import WebpackHotMiddleware from "webpack-hot-middleware";
import express from "express";
import template from "./render";
import initMiddleware from "./initMiddleware";
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 "./websocket";
import fs from "fs";
// const loadPath = (lng, ns) => {
// let resourcePath =
// path.resolve(process.cwd(), "dist/client") + `/locales/${lng}/${ns}.json`;
// if (ns === "Common")
// resourcePath = path.join(
// __dirname,
// `../../../../public/locales/${lng}/${ns}.json`
// );
const loadPath = (lng, ns) => {
let resourcePath =
path.resolve(process.cwd(), "dist/client") + `/locales/${lng}/${ns}.json`;
if (ns === "Common")
resourcePath = path.resolve(
process.cwd(),
`../../public/locales/${lng}/${ns}.json`
);
// return resourcePath;
// };
return resourcePath;
};
// const app = express();
// const port = process.env.PORT || 5013;
const port = PORT || 5013;
const app = express();
// i18next.use(Backend).init({
// backend: {
// loadPath: loadPath,
// allowMultiLoading: true,
// crossDomain: false,
// },
// fallbackLng: "en",
// load: "currentOnly",
i18next.use(Backend).init({
backend: {
loadPath: loadPath,
allowMultiLoading: true,
crossDomain: false,
},
fallbackLng: "en",
load: "currentOnly",
// saveMissing: true,
// ns: ["Editor", "Common"],
// defaultNS: "Editor",
saveMissing: true,
ns: ["Editor", "Common"],
defaultNS: "Editor",
// interpolation: {
// escapeValue: false, // not needed for react as it escapes by default
// format: function (value, format) {
// if (format === "lowercase") return value.toLowerCase();
// return value;
// },
// },
// });
// // if (process.env.NODE_ENV === "development") {
// // const webpackConfig = require("../../webpack/dev/webpack.dev.client.js");
// // const compiler = webpack(webpackConfig);
// // app.use(
// // WebpackDevMiddleware(compiler, {
// // publicPath: webpackConfig.output.publicPath,
// // serverSideRender: true,
// // })
// // );
// //
// // }
// app.use(i18nextMiddleware.handle(i18next));
// app.use(compression());
// app.use(
// "/products/files/doceditor/static/",
// express.static(path.resolve(__dirname, "../../dist/client/static"))
// );
// app.use(express.static(path.resolve(__dirname, "../../dist/client")));
// app.get("/products/files/doceditor", async (req, res) => {
// const { props, content, styleTags, extractor } = await render(req);
// const userLng = props?.user?.cultureName || "en";
// const initialI18nStore = {};
// i18next.changeLanguage(userLng).then(() => {
// const initialLanguage = userLng;
// const usedNamespaces = req.i18n.reportNamespaces.getUsedNamespaces();
// initialI18nStore[initialLanguage] = {};
// usedNamespaces.forEach((namespace) => {
// initialI18nStore[initialLanguage][namespace] =
// req.i18n.services.resourceStore.data[initialLanguage][namespace];
// });
// const response = template(
// props,
// content,
// styleTags,
// initialI18nStore,
// initialLanguage,
// extractor
// );
// res.send(response);
// });
// });
// app.listen(port, () => {
// console.log(`Server is listening on port ${port}`);
// });
const path = require("path");
require("@babel/register")({
configFile: path.resolve(__dirname, "../../babel.config.js"),
interpolation: {
escapeValue: false,
format: function (value, format) {
if (format === "lowercase") return value.toLowerCase();
return value;
},
},
});
require("./app.js");
app.use(i18nextMiddleware.handle(i18next));
app.use(compression());
app.use(
"/products/files/doceditor/static/",
express.static(path.resolve(process.cwd(), "dist/client/static"))
);
app.use(express.static(path.resolve(process.cwd(), "dist/client")));
app.use(initMiddleware);
app.get("/products/files/doceditor", async (req, res) => {
const { i18n, initialState, appComponent, styleTags, assets } = req;
const userLng = initialState?.user?.cultureName || "en";
const initialI18nStore = {};
await i18next.changeLanguage(userLng);
const usedNamespaces = i18n.reportNamespaces.getUsedNamespaces();
initialI18nStore[userLng] = {};
usedNamespaces.forEach((namespace) => {
initialI18nStore[namespace] =
i18n.services.resourceStore.data[userLng][namespace];
});
const htmlString = template(
initialState,
appComponent,
styleTags,
initialI18nStore,
userLng,
assets
);
res.send(htmlString);
});
const server = app.listen(port, () => {
console.log(`Server is listening on port ${port}`);
});
if (IS_DEVELOPMENT) {
const wss = ws(server);
const manifestFile = path.resolve(process.cwd(), "dist/client/manifest.json");
let fsWait = false;
fs.watch(manifestFile, (event, filename) => {
if (filename && event === "change") {
if (fsWait) return;
fsWait = true;
fsWait = setTimeout(() => {
fsWait = false;
}, 100);
wss.broadcast("reload");
}
});
}

View File

@ -0,0 +1,56 @@
import React from "react";
import { renderToString } from "react-dom/server";
import App from "../client/Editor.js";
import { initDocEditor } from "../helpers/utils";
import { ServerStyleSheet, createGlobalStyle } from "styled-components";
import path from "path";
import { I18nextProvider } from "react-i18next";
import fs from "fs";
const GlobalStyle = createGlobalStyle`
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;
}
`;
const sheet = new ServerStyleSheet();
export default async (req, res, next) => {
const manifest = fs.readFileSync(
path.join(process.cwd(), "dist/client/manifest.json"),
"utf-8"
);
req.initialState = await initDocEditor(req);
req.assets = JSON.parse(manifest);
req.appComponent = renderToString(
sheet.collectStyles(
<I18nextProvider i18n={req.i18n}>
<GlobalStyle />
<App {...req.initialState} />
</I18nextProvider>
)
);
req.styleTags = sheet.getStyleTags();
next();
};

File diff suppressed because it is too large Load Diff

View File

@ -1,63 +0,0 @@
const request = require("./requestManager");
const Encoder = require("./encoder");
exports.getDocServiceUrl = (headers) => {
return request({ method: "get", url: `/files/docservice`, headers });
};
exports.getFileInfo = (fileId, headers) => {
const options = {
method: "get",
url: `/files/file/${fileId}`,
headers,
};
return request(options);
};
exports.openEdit = (fileId, version, doc, view, headers) => {
const params = []; // doc ? `?doc=${doc}` : "";
if (view) {
params.push(`view=${view}`);
}
if (version) {
params.push(`version=${version}`);
}
if (doc) {
params.push(`doc=${doc}`);
}
const paramsString = params.length > 0 ? `?${params.join("&")}` : "";
const options = {
method: "get",
url: `/files/file/${fileId}/openedit${paramsString}`,
headers,
};
return request(options);
};
exports.getSettings = (headers) => {
return request({
method: "get",
url: "/settings.json",
headers,
});
};
exports.getUser = (userName = null, headers) => {
return request({
method: "get",
url: `/people/${userName || "@self"}.json`,
skipUnauthorized: true,
headers,
}).then((user) => {
if (user && user.displayName) {
user.displayName = Encoder.htmlDecode(user.displayName);
}
return user;
});
};

View File

@ -1,48 +0,0 @@
const axios = require("axios");
const apiPrefixURL = "/api/2.0";
const apiTimeout = 30000;
module.exports = (options) => {
const basePath = options.basePath;
const xRewriterUrl = options.headers["x-rewriter-url"];
const url = `${xRewriterUrl}${apiPrefixURL}${options.url}`;
const axiosOptions = {
baseURL: url,
responseType: "json",
timeout: apiTimeout,
headers: options.headers,
};
const getResponseError = (res) => {
if (!res) return;
if (res.data && res.data.error) {
return res.data.error.message;
}
if (res.isAxiosError && res.message) {
return res.message;
}
};
const onSuccess = (response) => {
const error = getResponseError(response);
if (error) throw new Error(error);
if (!response || !response.data || response.isAxiosError) return null;
if (response.request.responseType === "text") return response.data;
return response.data.response;
};
const onError = (error) => {
const errorText = error.response
? getResponseError(error.response)
: error.message;
return Promise.reject(errorText || error);
};
const request = axios.create(axiosOptions);
return request().then(onSuccess).catch(onError);
};

View File

@ -1,110 +0,0 @@
const {
getDocServiceUrl,
getFileInfo,
openEdit,
getUser,
getSettings,
} = require("./api/init");
const combineUrl = (host = "", ...params) => {
let url = host.replace(/\/+$/, "");
params.forEach((part) => {
const newPart = part.trim().replace(/^\/+/, "");
url += newPart
? url.length > 0 && url[url.length - 1] === "/"
? newPart
: `/${newPart}`
: "";
});
return url;
};
module.exports = async (req) => {
if (!req) return false;
const { headers, url, query } = req;
const { version, desktop: isDesktop } = query;
let error = null;
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] = await Promise.all([
getUser(null, headers),
getSettings(headers),
]);
const successAuth = !!user;
const personal = settings?.personal;
if (!successAuth && !doc) {
error = {
unAuthorized: true,
redirectPath: combineUrl(
"", //AppServerConfig.proxyURL,
personal ? "/sign-in" : "/login"
),
};
return {
props: {
error,
},
};
}
let [config, docApiUrl, fileInfo] = await Promise.all([
openEdit(fileId, fileVersion, doc, view, headers),
getDocServiceUrl(headers),
getFileInfo(fileId, headers),
]);
const isSharingAccess = fileInfo && fileInfo.canShare;
if (view) {
config.editorConfig.mode = "view";
}
const actionLink = config?.editorConfig?.actionLink || null;
return {
props: {
fileInfo,
docApiUrl,
config,
personal,
successAuth,
user,
error,
actionLink,
isSharingAccess,
url,
doc,
fileId,
view,
},
};
} catch (err) {
error = { errorMessage: typeof err === "string" ? err : err.message };
return {
props: {
error,
},
};
}
};

View File

@ -1,57 +1,372 @@
import React from "react";
import { renderToString } from "react-dom/server";
import App from "../client/Editor.js";
import { initDocEditor } from "../helpers/utils";
import { ServerStyleSheet, createGlobalStyle } from "styled-components";
import { ChunkExtractor, ChunkExtractorManager } from "@loadable/server";
import path from "path";
import { I18nextProvider } from "react-i18next";
import { getFavicon } from "../helpers/utils";
import pkg from "../../package.json";
const GlobalStyle = createGlobalStyle`
html,
body {
height: 100%;
}
export default function template(
initialState = {},
appComponent = "",
styleTags,
initialI18nStore,
initialLanguage,
assets
) {
const { title } = pkg;
const { docApiUrl } = initialState;
const faviconHref = getFavicon(initialState?.config?.documentType);
#root {
min-height: 100%;
const scripts = `
<script id="__ASC_INITIAL_STATE__">
window.__ASC_INITIAL_STATE__ = ${JSON.stringify(initialState)}
</script>
<script id="__ASC_I18N_INIT__">
window.initialI18nStore = ${JSON.stringify(initialI18nStore)}
window.initialLanguage = '${initialLanguage}'
</script>
<script>
const tempElm = document.getElementById("loader");
tempElm.style.backgroundColor =
localStorage.theme === "Dark" ? "#333333" : "#f4f4f4";
.pageLoader {
position: fixed;
left: calc(50% - 20px);
top: 35%;
}
}
body {
margin: 0;
}
console.log("It's Editor INIT");
</script>
<script type='text/javascript' id='scripDocServiceAddress' src="${docApiUrl}" async></script>
<script defer="defer" src='${assets["client.js"]}'></script>
body.loading * {
cursor: wait !important;
}
`;
// <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} />
<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="root">${appComponent}</div>
<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>
${scripts}
const sheet = new ServerStyleSheet();
const loadableJson = path.resolve(__dirname, "./client/loadable-stats.json");
export default async (req) => {
const props = await initDocEditor(req);
</body>
</html>
`;
const extractor = new ChunkExtractor({
statsFile: loadableJson,
entrypoints: ["client"],
});
const content = renderToString(
sheet.collectStyles(
<ChunkExtractorManager extractor={extractor}>
<GlobalStyle />
<I18nextProvider i18n={req.i18n}>
<App />
</I18nextProvider>
</ChunkExtractorManager>
)
);
const styleTags = sheet.getStyleTags();
return { ...props, content, styleTags, extractor };
};
return page;
}

View File

@ -1,56 +0,0 @@
import { getFavicon } from "../helpers/utils";
import pkg from "../../package.json";
export default function template(
initialState = {},
content = "",
styleTags,
//scriptTags,
initialI18nStore,
initialLanguage,
extractor
) {
const { title } = pkg;
const { docApiUrl } = initialState;
const faviconHref = getFavicon(initialState?.config?.documentType);
const scripts = `
<script id="__ASC_INITIAL_STATE__">
window.__ASC_INITIAL_STATE__ = ${JSON.stringify(initialState)}
</script>
<script id="__ASC_I18N_INIT__">
window.initialI18nStore = ${JSON.stringify(initialI18nStore)}
window.initialLanguage = '${initialLanguage}'
</script>
<script type='text/javascript' id='scripDocServiceAddress' src="${docApiUrl}" async></script>
${extractor.getScriptTags()}
`;
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} />
<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}
</head>
<body>
<div id="root">${content}</div>
${scripts}
</body>
</html>
`;
return page;
}

View File

@ -1,351 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<%- styleTags %>
<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" />
<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><%- 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"><%- component %></div>
<script defer="defer" src="<%= assets["client.js"] %>"></script>
<script defer="defer" src="<%= assets["runtime.js"] %>"></script>
<script defer="defer" src="<%= assets["vendor.js"] %>"></script>
<script>
window.__ASC_INITIAL_STATE__ = JSON.parse(`<%- parsedInitialState%>`);
const initialI18nStore = `<%- parsedI18nStore%>`
window.initialI18nStore = JSON.parse(initialI18nStore);
window.initialLanguage = '<%- initialLanguage%>';
const tempElm = document.getElementById("loader");
tempElm.style.backgroundColor =
localStorage.theme === "Dark" ? "#333333" : "#f4f4f4";
console.log("It's Editor INIT");
</script>
<script defer type='text/javascript' id='scripDocServiceAddress' src="<%= docApiUrl%>" async></script>
</body>
</html>