Merge branch 'release/v1.1.0' into feature/translate-1.1.0

This commit is contained in:
Alexey Safronov 2021-11-30 11:46:22 +03:00
commit b2c123d6f4
72 changed files with 727 additions and 485 deletions

View File

@ -159,7 +159,8 @@ namespace ASC.Common.Web
AddMimeMapping(".docm", "application/vnd.ms-word.document.macroEnabled.12");
AddMimeMapping(".doct", "application/doct");
AddMimeMapping(".docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document");
AddMimeMapping(".docxf", "application/vnd.openxmlformats-officedocument.wordprocessingml.document");
AddMimeMapping(".docxf", "application/vnd.openxmlformats-officedocument.wordprocessingml.document");
AddMimeMapping(".docxf", "application/vnd.openxmlformats-officedocument.wordprocessingml.document.docxf");
AddMimeMapping(".dot", "application/msword");
AddMimeMapping(".dotm", "application/vnd.ms-word.template.macroEnabled.12");
AddMimeMapping(".dotx", "application/vnd.openxmlformats-officedocument.wordprocessingml.template");
@ -431,7 +432,8 @@ namespace ASC.Common.Web
AddMimeMapping(".odp", "application/vnd.oasis.opendocument.presentation");
AddMimeMapping(".ods", "application/vnd.oasis.opendocument.spreadsheet");
AddMimeMapping(".odt", "application/vnd.oasis.opendocument.text");
AddMimeMapping(".oform", "application/vnd.openxmlformats-officedocument.wordprocessingml.document");
AddMimeMapping(".oform", "application/vnd.openxmlformats-officedocument.wordprocessingml.document");
AddMimeMapping(".oform", "application/vnd.openxmlformats-officedocument.wordprocessingml.document.oform");
AddMimeMapping(".oga", "audio/ogg");
AddMimeMapping(".ogg", "video/ogg");
AddMimeMapping(".ogg", "audio/ogg");

View File

@ -299,11 +299,25 @@ namespace ASC.ApiSystem.Controllers
//return null;
}
public bool ValidateRecaptcha(string response, string ip)
public bool ValidateRecaptcha(string response, RecaptchaType recaptchaType, string ip)
{
try
{
var data = string.Format("secret={0}&remoteip={1}&response={2}", Configuration["recaptcha:private-key"], ip, response);
{
string privateKey;
switch (recaptchaType)
{
case RecaptchaType.AndroidV2:
privateKey = Configuration["recaptcha:private-key:android"];
break;
case RecaptchaType.iOSV2:
privateKey = Configuration["recaptcha:private-key:ios"];
break;
default:
privateKey = Configuration["recaptcha:private-key"];
break;
}
var data = string.Format("secret={0}&remoteip={1}&response={2}", privateKey, ip, response);
var url = Configuration["recaptcha:verify-url"] ?? "https://www.recaptcha.net/recaptcha/api/siteverify";
var webRequest = (HttpWebRequest)WebRequest.Create(url);

View File

@ -671,12 +671,12 @@ namespace ASC.ApiSystem.Controllers
{
Log.DebugFormat("PortalName = {0}; Elapsed ms. ValidateRecaptcha via app key: {1}. {2}", model.PortalName, model.AppKey, sw.ElapsedMilliseconds);
return true;
}
var data = string.Format("{0} {1} {2} {3} {4}", model.PortalName, model.FirstName, model.LastName, model.Email, model.Phone);
}
var data = string.Format("{0} {1} {2} {3} {4} {5}", model.PortalName, model.FirstName, model.LastName, model.Email, model.Phone, model.RecaptchaType);
/*** validate recaptcha ***/
if (!CommonMethods.ValidateRecaptcha(model.RecaptchaResponse, clientIP))
if (!CommonMethods.ValidateRecaptcha(model.RecaptchaResponse, model.RecaptchaType, clientIP))
{
Log.DebugFormat("PortalName = {0}; Elapsed ms. ValidateRecaptcha error: {1} {2}", model.PortalName, sw.ElapsedMilliseconds, data);
sw.Stop();

View File

@ -0,0 +1,25 @@
/*
*
* (c) Copyright Ascensio System Limited 2010-2021
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
namespace ASC.ApiSystem.Models
{
public enum RecaptchaType
{
Default = 0,
AndroidV2 = 1,
iOSV2 = 2
}
}

View File

@ -73,7 +73,9 @@ namespace ASC.ApiSystem.Models
[StringLength(32)]
public string Phone { get; set; }
public string RecaptchaResponse { get; set; }
public string RecaptchaResponse { get; set; }
public RecaptchaType RecaptchaType { get; set; }
[StringLength(20)]
public string Region { get; set; }

View File

@ -60,7 +60,7 @@
"commented-docs": [ ".docx", ".docxf", ".xlsx", ".pptx" ],
"convert-docs": [ ".pptm", ".ppt", ".ppsm", ".pps", ".potx", ".potm", ".pot", ".odp", ".fodp", ".otp", ".xlsm", ".xls", ".xltx", ".xltm", ".xlt", ".ods", ".fods", ".ots", ".docm", ".doc", ".dotx", ".dotm", ".dot", ".odt", ".fodt", ".ott", ".rtf" ],
"edited-docs": [ ".pptx", ".pptm", ".ppt", ".ppsx", ".ppsm", ".pps", ".potx", ".potm", ".pot", ".odp", ".fodp", ".otp", ".xlsx", ".xlsm", ".xls", ".xltx", ".xltm", ".xlt", ".ods", ".fods", ".ots", ".csv", ".docx", ".docxf", ".oform", ".docm", ".doc", ".dotx", ".dotm", ".dot", ".odt", ".fodt", ".ott", ".txt", ".rtf", ".mht", ".html", ".htm" ],
"encrypted-docs": [ ".docx", ".docxf", ".xlsx", ".pptx" ],
"encrypted-docs": [ ".docx", ".docxf", ".xlsx", ".pptx", ".oform" ],
"formfilling-docs": [ ".oform" ],
"customfilter-docs": [ ".xlsx" ],
"reviewed-docs": [ ".docx", ".docxf" ],

View File

@ -1,9 +1,7 @@
module.exports = {
globDirectory: 'build/deploy/',
globPatterns: [
"**/*.{js,css,woff2,svg}"
],
globIgnores: ['**/remoteEntry.js'],
swSrc: 'packages/asc-web-common/utils/sw-template.js',
swDest: 'build/deploy/public/sw.js'
};
globDirectory: "build/deploy/",
globPatterns: ["**/*.{js,css,woff2,svg}"],
globIgnores: ["**/remoteEntry.js"],
swSrc: "packages/asc-web-common/sw/template.js",
swDest: "build/deploy/public/sw.js",
};

View File

@ -797,3 +797,15 @@ export function checkFillFormDraft(fileId) {
data: { fileId },
});
}
export function fileCopyAs(fileId, destTitle, destFolderId, enableExternalExt) {
return request({
method: "post",
url: `files/file/${fileId}/copyas`,
data: {
destTitle,
destFolderId,
enableExternalExt,
},
});
}

View File

@ -5,7 +5,7 @@ import IconButton from "@appserver/components/icon-button";
const CloseButton = (props) => {
//console.log("CloseButton render");
const { className, isDisabled, onClick } = props;
const { className, isDisabled, onClick, isClickable } = props;
return (
<div className={`styled-close-button ${className}`}>
<IconButton
@ -17,6 +17,7 @@ const CloseButton = (props) => {
isFill={true}
isDisabled={isDisabled}
onClick={!isDisabled ? onClick : undefined}
isClickable={isClickable}
/>
</div>
);

View File

@ -251,7 +251,7 @@ class FilterItem extends React.Component {
isDisabled={isDisabled}
isClickable={true}
>
<CloseButton isDisabled={isDisabled} onClick={this.onClick} />
<CloseButton isDisabled={isDisabled} isClickable />
</StyledCloseButtonBlock>
</StyledFilterItem>
);

View File

@ -67,7 +67,7 @@ const RowLoader = ({ id, className, style, isRectangle, ...rest }) => {
/>
)}
</StyledBox1>
<StyledBox2>
<StyledBox2 className="row-content">
<RectangleLoader
className="first-row-content__mobile"
title={title}

View File

@ -0,0 +1,11 @@
import registerSW from "./register";
import unregisterSW from "./unregister";
window.SW = {
register: registerSW,
unregister: unregisterSW,
};
export { unregisterSW as registerSW, unregisterSW };
// TODO: Replace 'unregisterSW as registerSW' to 'registerSW' when sw.js is needed
//export { registerSW, unregisterSW };

View File

@ -0,0 +1,131 @@
import React from "react";
import ReactDOM from "react-dom";
import { Workbox } from "workbox-window";
import SnackBar from "@appserver/components/snackbar";
import i18n from "i18next";
import { useTranslation, initReactI18next } from "react-i18next";
import Backend from "i18next-http-backend";
import { LANGUAGE } from "../constants";
import { loadLanguagePath } from "../utils";
i18n
.use(Backend)
.use(initReactI18next)
.init({
lng: localStorage.getItem(LANGUAGE) || "en",
fallbackLng: "en",
load: "currentOnly",
//debug: true,
interpolation: {
escapeValue: false, // not needed for react as it escapes by default
format: function (value, format) {
if (format === "lowercase") return value.toLowerCase();
return value;
},
},
backend: {
loadPath: loadLanguagePath(""),
},
react: {
useSuspense: false,
},
});
const SnackBarWrapper = (props) => {
const { t, ready } = useTranslation("Common", { i18n });
if (ready) {
const barConfig = {
parentElementId: "snackbar",
text: t("NewVersionAvailable"),
btnText: t("Load"),
onAction: () => props.onButtonClick(),
opacity: 1,
countDownTime: 5 * 60 * 1000,
};
return <SnackBar {...barConfig} />;
}
return <></>;
};
export default function () {
if (
process.env.NODE_ENV !== "production" &&
!("serviceWorker" in navigator)
) {
console.log("SKIP registerSW because of DEV mode");
return;
}
const wb = new Workbox(`/sw.js`);
const showSkipWaitingPrompt = (event) => {
console.log(
`A new service worker has installed, but it can't activate` +
`until all tabs running the current version have fully unloaded.`
);
function refresh() {
wb.addEventListener("controlling", () => {
localStorage.removeItem("sw_need_activation");
window.location.reload();
});
// This will postMessage() to the waiting service worker.
wb.messageSkipWaiting();
}
try {
const snackbarNode = document.createElement("div");
snackbarNode.id = "snackbar";
document.body.appendChild(snackbarNode);
ReactDOM.render(
<SnackBarWrapper
onButtonClick={() => {
snackbarNode.remove();
refresh();
}}
/>,
document.getElementById("snackbar")
);
localStorage.setItem("sw_need_activation", true);
} catch (e) {
console.error("showSkipWaitingPrompt", e);
refresh();
}
};
window.addEventListener("beforeunload", async () => {
if (localStorage.getItem("sw_need_activation")) {
localStorage.removeItem("sw_need_activation");
wb.messageSkipWaiting();
}
});
// Add an event listener to detect when the registered
// service worker has installed but is waiting to activate.
wb.addEventListener("waiting", showSkipWaitingPrompt);
wb.register()
.then((reg) => {
console.log("Successful service worker registration", reg);
if (!window.swUpdateTimer) {
console.log("SW timer checks for updates every hour");
window.swUpdateTimer = setInterval(() => {
console.log("SW update timer check");
reg.update().catch((e) => {
console.error("SW update timer FAILED", e);
});
}, 60 * 60 * 1000);
}
})
.catch((err) => console.error("Service worker registration failed", err));
}

View File

@ -0,0 +1,55 @@
function clearCaches() {
try {
caches?.keys()?.then(function (keyList) {
return Promise.all(
keyList.map(function (key) {
if (
key.startsWith("workbox-") ||
key.startsWith("wb6-") ||
key.startsWith("appserver-")
) {
return caches.delete(key);
}
})
);
});
} catch (error) {
console.error("clearCaches failed", error);
}
}
export default function () {
if (
process.env.NODE_ENV !== "production" &&
!("serviceWorker" in navigator)
) {
console.log("SKIP registerSW because of DEV mode");
return;
}
clearCaches();
return navigator.serviceWorker
.getRegistrations()
.then(function (registrations) {
for (let registration of registrations) {
registration
.unregister()
.then(function () {
return self.clients?.matchAll() || [];
})
.then(function (clients) {
clients.forEach((client) => {
if (client.url && "navigate" in client) {
client.navigate(client.url);
}
});
})
.catch((err) => {
console.error(err);
});
}
})
.catch((err) => {
console.error(err);
});
}

View File

@ -1,138 +0,0 @@
import React from "react";
import ReactDOM from "react-dom";
import { Workbox } from "workbox-window";
import SnackBar from "@appserver/components/snackbar";
import i18n from "i18next";
import { useTranslation, initReactI18next } from "react-i18next";
import Backend from "i18next-http-backend";
import { LANGUAGE } from "../constants";
import { loadLanguagePath } from "./";
i18n
.use(Backend)
.use(initReactI18next)
.init({
lng: localStorage.getItem(LANGUAGE) || "en",
fallbackLng: "en",
load: "currentOnly",
//debug: true,
interpolation: {
escapeValue: false, // not needed for react as it escapes by default
format: function (value, format) {
if (format === "lowercase") return value.toLowerCase();
return value;
},
},
backend: {
loadPath: loadLanguagePath(""),
},
react: {
useSuspense: false,
},
});
const SnackBarWrapper = (props) => {
const { t, ready } = useTranslation("Common", { i18n });
if (ready) {
const barConfig = {
parentElementId: "snackbar",
text: t("NewVersionAvailable"),
btnText: t("Load"),
onAction: () => props.onButtonClick(),
opacity: 1,
countDownTime: 5 * 60 * 1000,
};
return <SnackBar {...barConfig} />;
}
return <></>;
};
const registerSW = () => {
return; //TODO: Enable service-worker after fix of infinite reloading (Bug 53063)
/*
if (process.env.NODE_ENV === "production" && "serviceWorker" in navigator) {
const wb = new Workbox(`/sw.js`);
const showSkipWaitingPrompt = (event) => {
console.log(
`A new service worker has installed, but it can't activate` +
`until all tabs running the current version have fully unloaded.`
);
function refresh() {
wb.addEventListener("controlling", () => {
localStorage.removeItem("sw_need_activation");
window.location.reload();
});
// This will postMessage() to the waiting service worker.
wb.messageSkipWaiting();
}
try {
const snackbarNode = document.createElement("div");
snackbarNode.id = "snackbar";
document.body.appendChild(snackbarNode);
ReactDOM.render(
<SnackBarWrapper
onButtonClick={() => {
snackbarNode.remove();
refresh();
}}
/>,
document.getElementById("snackbar")
);
localStorage.setItem("sw_need_activation", true);
} catch (e) {
console.error("showSkipWaitingPrompt", e);
refresh();
}
};
window.addEventListener("beforeunload", async () => {
if (localStorage.getItem("sw_need_activation")) {
localStorage.removeItem("sw_need_activation");
wb.messageSkipWaiting();
}
});
// Add an event listener to detect when the registered
// service worker has installed but is waiting to activate.
wb.addEventListener("waiting", showSkipWaitingPrompt);
wb.register()
.then((reg) => {
console.log("Successful service worker registration", reg);
if (!window.swUpdateTimer) {
console.log("SW timer checks for updates every hour");
window.swUpdateTimer = setInterval(() => {
console.log("SW update timer check");
reg.update().catch((e) => {
console.error("SW update timer FAILED", e);
});
}, 60 * 60 * 1000);
}
})
.catch((err) => console.error("Service worker registration failed", err));
} else {
console.log("SKIP registerSW because of DEV mode");
}
*/
};
window.SW = {
registerSW: registerSW,
};
export { registerSW };

View File

@ -28,6 +28,8 @@ const Content = styled.div`
props.displayType === "modal"
? props.theme.modalDialog.content.modalPadding
: props.theme.modalDialog.content.asidePadding};
border-radius: ${(props) =>
props.theme.modalDialog.content.modalBorderRadius};
.heading {
max-width: ${(props) => props.theme.modalDialog.content.heading.maxWidth};
@ -73,7 +75,7 @@ const CloseButton = styled(CrossSidebarIcon)`
&:hover {
path {
fill: ${(props) => props.theme.modalDialog.closeButton.hoverColor};
stroke: ${(props) => props.theme.modalDialog.closeButton.hoverColor};
}
}
`;

View File

@ -492,6 +492,7 @@ const Base = {
content: {
backgroundColor: white,
modalPadding: "0 12px 12px",
modalBorderRadius: "6px",
asidePadding: "0 16px 16px",
heading: {
maxWidth: "500px",
@ -509,13 +510,13 @@ const Base = {
},
closeButton: {
width: "17px",
height: "17px",
minWidth: "17px",
minHeight: "17px",
width: "14px",
height: "14px",
minWidth: "14px",
minHeight: "14px",
right: "16px",
top: "11px",
top: "13px",
hoverColor: grayMain,
},
},
@ -758,10 +759,10 @@ const Base = {
span: { maxWidth: "300px" },
caret: {
width: "8px",
minWidth: "8px",
height: "8px",
minHeight: "8px",
width: "5px",
minWidth: "5px",
height: "4px",
minHeight: "4px",
marginLeft: "5px",
marginTop: "-4px",
right: "6px",

View File

@ -458,6 +458,7 @@ const Dark = {
content: {
backgroundColor: white,
padding: "0 16px 16px",
modalBorderRadius: "6px",
heading: {
maxWidth: "500px",
@ -472,13 +473,13 @@ const Dark = {
},
closeButton: {
width: "17px",
height: "17px",
minWidth: "17px",
minHeight: "17px",
width: "14px",
height: "14px",
minWidth: "14px",
minHeight: "14px",
right: "16px",
top: "19px",
top: "13px",
hoverColor: grayMain,
},
},
@ -719,10 +720,10 @@ const Dark = {
span: { maxWidth: "300px" },
caret: {
width: "8px",
minWidth: "8px",
height: "8px",
minHeight: "8px",
width: "5px",
minWidth: "5px",
height: "4px",
minHeight: "4px",
marginLeft: "5px",
marginTop: "-4px",
right: "6px",

View File

@ -6,7 +6,7 @@
"scripts": {
"build": "webpack --mode production",
"clean": "shx rm -rf dist",
"deploy": "shx mkdir -p ../../../build/deploy/products/ASC.CRM/client && shx cp -r dist/* ../../../build/deploy/products/ASC.CRM/client",
"deploy": "shx --silent mkdir -p ../../../build/deploy/products/ASC.CRM/client && shx cp -r dist/* ../../../build/deploy/products/ASC.CRM/client",
"serve": "serve dist -p 5014",
"start": "webpack-cli serve",
"start-prod": "webpack --mode production && serve dist -p 5014"

View File

@ -1,7 +1,7 @@
import App from "./App";
import React from "react";
import ReactDOM from "react-dom";
import { registerSW } from "@appserver/common/utils/sw-helper";
import { registerSW } from "@appserver/common/sw/helper";
ReactDOM.render(<App />, document.getElementById("root"));

View File

@ -6,7 +6,7 @@
"scripts": {
"build": "webpack --mode production",
"clean": "shx rm -rf dist",
"deploy": "shx mkdir -p ../../../build/deploy/products/ASC.Calendar/client && shx cp -r dist/* ../../../build/deploy/products/ASC.Calendar/client",
"deploy": "shx --silent mkdir -p ../../../build/deploy/products/ASC.Calendar/client && shx cp -r dist/* ../../../build/deploy/products/ASC.Calendar/client",
"serve": "serve dist -p 5017",
"start": "webpack-cli serve",
"start-prod": "webpack --mode production && serve dist -p 5017"

View File

@ -1,7 +1,7 @@
import App from "./App";
import React from "react";
import ReactDOM from "react-dom";
import { registerSW } from "@appserver/common/utils/sw-helper";
import { registerSW } from "@appserver/common/sw/helper";
ReactDOM.render(<App />, document.getElementById("root"));

View File

@ -6,7 +6,7 @@
"scripts": {
"build": "webpack --mode production",
"clean": "shx rm -rf dist",
"deploy": "shx mkdir -p ../../../build/deploy/products/ASC.Files/client && shx cp -r dist/* ../../../build/deploy/products/ASC.Files/client",
"deploy": "shx --silent mkdir -p ../../../build/deploy/products/ASC.Files/client && shx cp -r dist/* ../../../build/deploy/products/ASC.Files/client",
"serve": "serve dist -p 5008",
"start": "webpack-cli serve",
"start-prod": "webpack --mode production && serve dist -p 5008"

View File

@ -4,10 +4,5 @@
"NewPresentation": "New Presentation",
"NewSpreadsheet": "New Spreadsheet",
"UploadFiles": "Upload files",
"UploadFolder": "Upload folder",
"NewForm": "Master form",
"NewFormFile": "Master form from file",
"CreateMasterFormFromFile": "Create Master Form from file",
"SubNewForm": "From blank",
"SubNewFormFile": "From an existing text file"
"UploadFolder": "Upload folder"
}

View File

@ -40,5 +40,10 @@
"TypeTitleSharePoint": "SharePoint",
"TypeTitleSkyDrive": "OneDrive",
"TypeTitleWebDav": "WebDAV",
"TypeTitleYandex": "Yandex.Disk"
"TypeTitleYandex": "Yandex.Disk",
"NewForm": "Master form",
"NewFormFile": "Master form from file",
"CreateMasterFormFromFile": "Create Master Form from file",
"SubNewForm": "From blank",
"SubNewFormFile": "From an existing text file"
}

View File

@ -20,6 +20,16 @@ export default function withContextOptions(WrappedComponent) {
return this.gotoDocEditor(false);
};
onClickMakeForm = () => {
const { copyAsAction, item, formfillingDocs } = this.props;
const { title, id, folderId, fileExst } = item;
const newTitle =
title.substring(0, title.length - fileExst.length) + formfillingDocs[0];
copyAsAction(id, newTitle, folderId).catch((err) => toastr.error(err));
};
onOpenLocation = () => {
const { item, openLocationAction } = this.props;
const { parentId, folderId, fileExst } = item;
@ -376,6 +386,14 @@ export default function withContextOptions(WrappedComponent) {
onClick: this.onClickLinkFillForm,
disabled: false,
};
case "make-form":
return {
key: option,
label: t("Common:MakeForm"),
icon: "/static/images/form.plus.react.svg",
onClick: this.onClickMakeForm,
disabled: false,
};
case "edit":
return {
key: option,
@ -533,45 +551,48 @@ export default function withContextOptions(WrappedComponent) {
return inject(
(
{
filesStore,
filesActionsStore,
auth,
versionHistoryStore,
mediaViewerDataStore,
dialogsStore,
filesActionsStore,
filesStore,
mediaViewerDataStore,
treeFoldersStore,
uploadDataStore,
versionHistoryStore,
},
{ item }
) => {
const { openDocEditor, fileActionStore } = filesStore;
const { openDocEditor, fileActionStore, formatsStore } = filesStore;
const {
openLocationAction,
finalizeVersionAction,
setFavoriteAction,
lockFileAction,
deleteItemAction,
downloadAction,
duplicateAction,
setThirdpartyInfo,
onSelectItem,
deleteItemAction,
finalizeVersionAction,
lockFileAction,
markAsRead,
onSelectItem,
openLocationAction,
setFavoriteAction,
setThirdpartyInfo,
unsubscribeAction,
} = filesActionsStore;
const {
setChangeOwnerPanelVisible,
setMoveToPanelVisible,
setCopyPanelVisible,
setDownloadDialogVisible,
setRemoveItem,
setDeleteThirdPartyDialogVisible,
setSharingPanelVisible,
setDeleteDialogVisible,
setDeleteThirdPartyDialogVisible,
setDownloadDialogVisible,
setMoveToPanelVisible,
setRemoveItem,
setSharingPanelVisible,
setUnsubscribe,
} = dialogsStore;
const { isTabletView, isDesktopClient } = auth.settingsStore;
const { setIsVerHistoryPanel, fetchFileVersions } = versionHistoryStore;
const { setAction, type, extension, id } = fileActionStore;
const { setMediaViewerData } = mediaViewerDataStore;
const { copyAsAction } = uploadDataStore;
const { formfillingDocs } = formatsStore.docserviceStore;
const { isRecycleBinFolder, isShare } = treeFoldersStore;
const isShareFolder = isShare(item.rootFolderType);
@ -610,6 +631,8 @@ export default function withContextOptions(WrappedComponent) {
setDeleteDialogVisible,
setUnsubscribe,
isDesktop: isDesktopClient,
copyAsAction,
formfillingDocs,
};
}
)(observer(WithContextOptions));

View File

@ -1,7 +1,7 @@
import App from "./App";
import React from "react";
import ReactDOM from "react-dom";
import { registerSW } from "@appserver/common/utils/sw-helper";
import { registerSW } from "@appserver/common/sw/helper";
ReactDOM.render(<App />, document.getElementById("root"));

View File

@ -30,7 +30,7 @@ class ArticleBodyContent extends React.Component {
} = this.props;
setSelectedNode(data);
hideArticle(false);
hideArticle();
setIsLoading(true);
// const selectedFolderTitle =
// (e.node && e.node.props && e.node.props.title) || null;

View File

@ -25,7 +25,8 @@ class ArticleMainButtonContent extends React.Component {
};
onShowSelectFileDialog = () => {
const { setSelectFileDialogVisible } = this.props;
const { setSelectFileDialogVisible, hideArticle } = this.props;
hideArticle();
setSelectFileDialogVisible(true);
};
@ -96,17 +97,17 @@ class ArticleMainButtonContent extends React.Component {
{
className: "main-button_drop-down",
icon: "images/form.react.svg",
label: t("NewForm"),
label: t("Translations:NewForm"),
items: [
{
className: "main-button_drop-down_sub",
label: t("SubNewForm"),
label: t("Translations:SubNewForm"),
onClick: this.onCreate,
action: "docxf",
},
{
className: "main-button_drop-down_sub",
label: t("SubNewFormFile"),
label: t("Translations:SubNewFormFile"),
onClick: this.onShowSelectFileDialog,
},
],
@ -116,14 +117,14 @@ class ArticleMainButtonContent extends React.Component {
{
className: "main-button_drop-down_sub",
icon: "images/form.react.svg",
label: t("NewForm"),
label: t("Translations:NewForm"),
onClick: this.onCreate,
action: "docxf",
},
{
className: "main-button_drop-down_sub",
icon: "images/form.file.react.svg",
label: t("NewFormFile"),
label: t("Translations:NewFormFile"),
onClick: this.onShowSelectFileDialog,
},
];
@ -208,11 +209,12 @@ ArticleMainButtonContent.propTypes = {
};
export default inject(
({ filesStore, uploadDataStore, treeFoldersStore, dialogsStore }) => {
({ auth, filesStore, uploadDataStore, treeFoldersStore, dialogsStore }) => {
const { firstLoad, fileActionStore, filter, canCreate } = filesStore;
const { isPrivacyFolder } = treeFoldersStore;
const { startUpload } = uploadDataStore;
const { setSelectFileDialogVisible } = dialogsStore;
const { hideArticle } = auth.settingsStore;
return {
homepage: config.homepage,
firstLoad,
@ -223,11 +225,12 @@ export default inject(
setAction: fileActionStore.setAction,
startUpload,
setSelectFileDialogVisible,
hideArticle,
};
}
)(
withRouter(
withTranslation(["Article", "Common"])(
withTranslation(["Article", "Common", "Translations"])(
withLoader(observer(ArticleMainButtonContent))(<Loaders.MainButton />)
)
)

View File

@ -38,7 +38,10 @@ const EmptyFolderContainer = ({
{t("Spreadsheet")},
</Link>
<Link data-format="pptx" onClick={onCreate} {...linkStyles}>
{t("Presentation")}
{t("Presentation")},
</Link>
<Link data-format="docxf" onClick={onCreate} {...linkStyles}>
{t("Translations:NewForm")}
</Link>
</Box>
</div>
@ -88,4 +91,4 @@ export default inject(({ filesStore, selectedFolderStore }) => {
setIsLoading: filesStore.setIsLoading,
parentId: selectedFolderStore.parentId,
};
})(withTranslation("Home")(observer(EmptyFolderContainer)));
})(withTranslation(["Home", "Translations"])(observer(EmptyFolderContainer)));

View File

@ -45,7 +45,7 @@ const Panels = (props) => {
setSelectFileDialogVisible,
} = props;
const { t } = useTranslation(["Article", "SelectFile"]);
const { t } = useTranslation(["Translations", "SelectFile"]);
const onClose = () => {
setSelectFileDialogVisible(false);
@ -92,7 +92,7 @@ const Panels = (props) => {
foldersType="exceptPrivacyTrashFolders"
ByExtension
searchParam={"docx"}
headerName={t("Article:CreateMasterFormFromFile")}
headerName={t("Translations:CreateMasterFormFromFile")}
titleFilesList={t("SelectFile:SelectDOCXFormat")}
creationButtonPrimary
/>

View File

@ -134,7 +134,7 @@ class DeleteDialogComponent extends React.Component {
? t("Common:Confirmation")
: unsubscribe
? t("UnsubscribeTitle")
: checkedSelections.length === 1 || isPrivacyFolder
: checkedSelections.length === 1
? checkedSelections[0].fileExst
? t("MoveToTrashOneFileTitle")
: t("MoveToTrashOneFolderTitle")
@ -142,7 +142,7 @@ class DeleteDialogComponent extends React.Component {
const noteText = unsubscribe
? t("UnsubscribeNote")
: checkedSelections.length === 1 || isPrivacyFolder
: checkedSelections.length === 1
? checkedSelections[0].fileExst
? t("MoveToTrashOneFileNote")
: personal

View File

@ -107,6 +107,7 @@ const SelectFileDialogAsideView = ({
<Loaders.Rows
style={{
marginBottom: "24px",
marginTop: "20px",
}}
count={12}
/>

View File

@ -79,9 +79,10 @@ const FilesListBody = ({
>
<Loaders.Rows
style={{
marginBottom: displayType === "aside" ? "24px" : "19px",
marginBottom: displayType === "aside" ? "24px" : "26px",
marginTop: displayType === "aside" ? "8px" : "10px",
}}
count={displayType === "aside" ? 12 : 7}
count={displayType === "aside" ? 12 : 5}
/>
</div>
</div>
@ -102,7 +103,7 @@ const FilesListBody = ({
const isLoaded = isItemLoaded(index);
if (!isLoaded) {
if (countLoad > 1) return renderPageLoader(style);
if (countLoad >= 1) return renderPageLoader(style);
return renderFirstLoader(style);
}
@ -156,7 +157,7 @@ const FilesListBody = ({
<List
height={displayType === "aside" ? height : listHeight}
itemCount={itemCount}
itemSize={displayType === "aside" ? 56 : 41}
itemSize={displayType === "aside" ? 56 : 50}
onItemsRendered={onItemsRendered}
ref={ref}
width={width + 8}

View File

@ -233,6 +233,7 @@ class SelectFileDialogModalView extends React.Component {
selectedFile={selectedFile}
listHeight={isHeaderChildren ? 260 : 303}
onSetLoadingData={this.onSetLoadingData}
displayType={"modal"}
/>
)}
</>

View File

@ -31,7 +31,7 @@ class SelectFileDialogBody extends React.Component {
filter,
creationButtonPrimary,
t,
} = this.props;
} = props;
this.buttonName = creationButtonPrimary
? t("Common:Create")
@ -384,9 +384,9 @@ const SelectFileDialogWrapper = inject(
const { setSelectedNode, setExpandedPanelKeys } = treeFoldersStore;
const { filter } = filesStore;
const { setSelectedFolder } = selectedFolderStore;
const { setSelectedFolder, id } = selectedFolderStore;
return {
storeFolderId,
storeFolderId: storeFolderId || id,
fileInfo,
setFile,
setFolderId,

View File

@ -130,15 +130,6 @@ class SharingRow extends React.Component {
];
const externalLinkOptions = [
{
key: "linkItem_0",
label: t("CopyExternalLink"),
onClick: this.onCopyClick,
},
{
key: "linkItem_1",
isSeparator: true,
},
{
key: "linkItem_2",
label: `${t("ShareVia")} e-mail`,
@ -181,6 +172,7 @@ class SharingRow extends React.Component {
externalLinkData={externalLinkData}
onToggleLink={onToggleLink}
withToggle
onCopyLink={this.onCopyClick}
{...this.props}
/>
)}

View File

@ -1,5 +1,5 @@
import React from "react";
import styled from "styled-components";
import styled, { css } from "styled-components";
import Row from "@appserver/components/row";
import LinkWithDropdown from "@appserver/components/link-with-dropdown";
import ToggleButton from "@appserver/components/toggle-button";
@ -7,6 +7,7 @@ import { StyledLinkRow } from "../StyledPanels";
import AccessComboBox from "./AccessComboBox";
import { ShareAccessRights } from "@appserver/common/constants";
import AccessEditIcon from "../../../../../../../public/images/access.edit.react.svg";
import CopyIcon from "../../../../../../../public/images/copy.react.svg";
import commonIconsStyles from "@appserver/components/utils/common-icons-style";
const StyledAccessEditIcon = styled(AccessEditIcon)`
@ -16,6 +17,19 @@ const StyledAccessEditIcon = styled(AccessEditIcon)`
}
`;
const StyledCopyIcon = styled(CopyIcon)`
${commonIconsStyles}
cursor: pointer;
${(props) =>
props.isDisabled &&
css`
cursor: default;
pointer-events: none;
`}
`;
class LinkRow extends React.Component {
onToggleButtonChange = () => {
const { onToggleLink, item } = this.props;
@ -34,6 +48,7 @@ class LinkRow extends React.Component {
externalAccessOptions,
onChangeItemAccess,
isLoading,
onCopyLink,
} = this.props;
const isChecked = item.access !== ShareAccessRights.DenyAccess;
@ -71,17 +86,27 @@ class LinkRow extends React.Component {
contextButtonSpacerWidth="0px"
>
<>
<LinkWithDropdown
className="sharing_panel-link"
color="#333"
dropdownType="alwaysDashed"
data={options}
fontSize="14px"
fontWeight={600}
isDisabled={isDisabled}
>
{linkText}
</LinkWithDropdown>
<div className="sharing_panel-link-container">
<LinkWithDropdown
className="sharing_panel-link"
color="#333"
dropdownType="alwaysDashed"
data={options}
fontSize="13px"
fontWeight={600}
isDisabled={isDisabled}
>
{linkText}
</LinkWithDropdown>
{onCopyLink && (
<StyledCopyIcon
isDisabled={isDisabled}
size="medium"
onClick={onCopyLink}
title={t("CopyExternalLink")}
/>
)}
</div>
{withToggle && (
<div>
<ToggleButton

View File

@ -572,6 +572,24 @@ const StyledLinkRow = styled.div`
}
}
.sharing_panel-link-container {
display: flex;
.sharing_panel-link {
a {
text-decoration: none;
${(props) =>
props.isDisabled &&
css`
:hover {
text-decoration: none;
}
`};
}
}
}
.link-row {
${(props) => !props.withToggle && "border-bottom:none;"}
}
@ -641,6 +659,16 @@ const StyledSelectFolderPanel = styled.div`
.nav-thumb-horizontal {
height: 0px !important;
}
${(props) =>
props.displayType === "modal" &&
css`
.nav-thumb-vertical {
margin-left: 4px !important;
width: 4px !important;
}
`}
.scroll-body {
overflow-x: hidden !important;
}
@ -673,6 +701,15 @@ const StyledSelectFilePanel = styled.div`
margin-left: -7px !important;
}
`}
${(props) =>
props.displayType === "modal" &&
css`
.nav-thumb-vertical {
margin-left: 4px !important;
width: 4px !important;
}
`}
}
.select-file-dialog_aside_body-files_list {
height: 100%;
@ -724,14 +761,24 @@ const StyledSelectFilePanel = styled.div`
padding: 7px 0px;
}
.panel-loader-wrapper {
${(props) =>
props.displayType === "modal" &&
css`
margin-top: 16px;
`};
.first-row-content__mobile {
width: ${(props) => (props.displayType === "aside" ? "147px" : "402px")};
width: ${(props) => (props.displayType === "aside" ? "147px" : "234px")};
height: ${(props) => (props.displayType === "aside" ? "16px" : "10px")};
}
@media ${desktop} {
.second-row-content__mobile {
max-width: 185px;
height: 8px;
display: block;
}
.row-content {
grid-template-rows: 10px;
grid-row-gap: 6px;
margin-top: -3px;
}
}
.second-row-content__mobile {
width: 229px;
}
@ -759,7 +806,7 @@ const StyledSelectFilePanel = styled.div`
.modal-dialog_body {
display: grid;
grid-template-columns: 212px 493px;
grid-template-columns: 228px 477px;
height: 295px;
grid-template-areas: "tree files-list";
.modal-dialog_tree-body {
@ -805,14 +852,20 @@ const StyledFilesList = styled.div`
}
.files-list_file-owner {
max-width: 213px;
max-width: ${(props) =>
props.displayType === "aside" ? "213px" : "406px"};
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
color: #a3a9ae;
font-weight: 600;
}
${(props) => props.displayType === "modal" && ` font-size: 11px;`}
height: ${(props) => (props.displayType === "aside" ? "16px" : "12px")};
padding-bottom: ${(props) =>
props.displayType === "aside" ? "10px" : "11px"};
}
.file-exst {
color: #a3a9ae;
font-weight: 600;
@ -824,52 +877,46 @@ const StyledFilesList = styled.div`
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
max-width: ${(props) =>
props.displayType === "aside" ? "213px" : "274px"};
max-width: ${(props) => props.displayType === "aside" && "213px"};
grid-area: full-name;
display: flex;
${(props) =>
props.displayType === "aside" &&
css`
padding-top: 4px;
`}
padding-top: 10px;
}
.select-file-dialog_icon {
grid-area: icon-name;
padding-top: 12px;
}
.select-file-dialog_checked {
grid-area: checked-button;
}
.files-list_file-children_wrapper {
grid-area: owner-name;
margin-right: 12px;
${(props) =>
props.displayType === "aside" &&
css`
margin-top: -17px;
`}
margin-top: ${(props) => props.displayType === "modal" && "-8px"};
}
.modal-dialog_file-name {
border-radius: 3px;
padding-right: 12px;
${(props) => props.isChecked && `background:#F8F9F9;`}
cursor: ${(props) => (props.needRowSelection ? "pointer" : "default")};
border-bottom: 1px solid #eceef1;
align-items: center;
display: grid;
${(props) =>
props.displayType === "aside"
? css`
height: 56px;
grid-template-areas: "checked-button icon-name full-name full-name" "checked-button icon-name owner-name owner-name";
grid-template-areas: "checked-button icon-name full-name" "checked-button icon-name owner-name";
`
: css`
height: 41px;
grid-template-areas: "checked-button icon-name full-name owner-name";
height: 49px;
grid-template-areas: "checked-button icon-name full-name" "checked-button icon-name owner-name";
`}
grid-template-columns: 22px 33px 1fr;
${(props) => props.displayType === "modal" && ` grid-row-gap: 4px;`}
padding-left: ${(props) =>
props.displayType === "aside" ? "16px" : "12px"};
box-sizing: border-box;
}
`;
@ -879,6 +926,9 @@ const StyledModalRowContainer = styled.div`
min-height: 47px;
.link-row__container {
display: flex;
align-items: center;
height: 41px;
width: 100%;
.link-row {

View File

@ -174,6 +174,13 @@ class SectionHeaderContent extends React.Component {
createPresentation = () => this.onCreate("pptx");
createForm = () => this.onCreate("docxf");
createFormFromFile = () => {
const { setSelectFileDialogVisible } = this.props;
setSelectFileDialogVisible(true);
};
createFolder = () => this.onCreate();
uploadToFolder = () => console.log("Upload To Folder click");
@ -197,6 +204,14 @@ class SectionHeaderContent extends React.Component {
label: t("NewPresentation"),
onClick: this.createPresentation,
},
{
label: t("Translations:NewForm"),
onClick: this.createForm,
},
{
label: t("Translations:NewFormFile"),
onClick: this.createFormFromFile,
},
{
key: "new-folder",
label: t("NewFolder"),
@ -522,6 +537,7 @@ export default inject(
setCopyPanelVisible,
setDeleteDialogVisible,
setEmptyTrashDialogVisible,
setSelectFileDialogVisible,
} = dialogsStore;
const { isRecycleBinFolder } = treeFoldersStore;
@ -557,6 +573,7 @@ export default inject(
downloadAction,
getHeaderMenu,
getCheckboxItemLabel,
setSelectFileDialogVisible,
isRecycleBinFolder,
setEmptyTrashDialogVisible,

View File

@ -285,7 +285,7 @@ class PureHome extends React.Component {
hideAside={
!!fileActionId ||
primaryProgressDataVisible ||
secondaryProgressDataStoreVisible
secondaryProgressDataStoreVisible //TODO: use hideArticle action
}
isLoaded={!firstLoad}
isHeaderVisible={isHeaderVisible}

View File

@ -489,12 +489,15 @@ class FilesStore {
const shouldFillForm = canFormFillingDocs(item.fileExst);
const shouldEdit = !shouldFillForm && canWebEdit(item.fileExst);
const shouldView = canViewedDocs(item.fileExst);
const isMasterForm = item.fileExst === ".docxf";
let fileOptions = [
//"open",
"fill-form",
"edit",
"preview",
"view",
"make-form",
"separator0",
"sharing-settings",
"external-link",
@ -525,6 +528,9 @@ class FilesStore {
"delete",
];
if (!isMasterForm)
fileOptions = this.removeOptions(fileOptions, ["make-form"]);
if (!shouldFillForm)
fileOptions = this.removeOptions(fileOptions, ["fill-form"]);
@ -631,11 +637,13 @@ class FilesStore {
if (isRecycleBinFolder) {
fileOptions = this.removeOptions(fileOptions, [
"fill-form",
"open",
"open-location",
"view",
"preview",
"edit",
"make-form",
"link-for-portal-users",
"sharing-settings",
"external-link",
@ -1514,7 +1522,7 @@ class FilesStore {
if (webComment) AccessOptions.push("Comment");
if (webReview) AccessOptions.push("Review");
if (formFillingDocs) AccessOptions.push("FormFilling");
if (formFillingDocs && !externalAccess) AccessOptions.push("FormFilling");
if (webFilter) AccessOptions.push("FilterEditing");
return AccessOptions;

View File

@ -15,6 +15,7 @@ import {
getFileConversationProgress,
copyToFolder,
moveToFolder,
fileCopyAs,
} from "@appserver/common/api/files";
class UploadDataStore {
@ -919,6 +920,18 @@ class UploadDataStore {
});
};
copyAsAction = (fileId, title, folderId, enableExternalExt) => {
const { fetchFiles, filter } = this.filesStore;
return fileCopyAs(fileId, title, folderId, enableExternalExt)
.then(() => {
fetchFiles(folderId, filter, true, true);
})
.catch((err) => {
return Promise.reject(err);
});
};
itemOperationToFolder = (data) => {
const {
destFolderId,

View File

@ -462,18 +462,18 @@ namespace ASC.Files.Core.Security
continue;
}
if (e.FileEntryType == FileEntryType.File
&& file.IsFillFormDraft)
{
e.Access = FileShare.FillForms;
//if (e.FileEntryType == FileEntryType.File
// && file.IsFillFormDraft)
//{
// e.Access = FileShare.FillForms;
if (action != FilesSecurityActions.Read
&& action != FilesSecurityActions.FillForms
&& action != FilesSecurityActions.Delete)
{
continue;
}
}
// if (action != FilesSecurityActions.Read
// && action != FilesSecurityActions.FillForms
// && action != FilesSecurityActions.Delete)
// {
// continue;
// }
//}
if (e.RootFolderType == FolderType.USER && e.RootFolderCreator == userId && !isVisitor)
{

View File

@ -0,0 +1,11 @@
namespace ASC.Files.Core.Model
{
public class CopyAsModel<T>
{
public string DestTitle { get; set; }
public T DestFolderId { get; set; }
public bool EnableExternalExt { get; set; }
}
}

View File

@ -320,7 +320,8 @@ namespace ASC.Web.Files.Services.DocumentService
EmbeddedConfig embeddedConfig,
CustomizationConfig<T> customizationConfig,
FilesSettingsHelper filesSettingsHelper,
IDaoFactory daoFactory)
IDaoFactory daoFactory,
EntryManager entryManager)
{
UserManager = userManager;
AuthContext = authContext;
@ -330,6 +331,7 @@ namespace ASC.Web.Files.Services.DocumentService
Customization = customizationConfig;
FilesSettingsHelper = filesSettingsHelper;
DaoFactory = daoFactory;
EntryManager = entryManager;
Plugins = pluginsConfig;
Embedded = embeddedConfig;
_userInfo = userManager.GetUsers(authContext.CurrentAccount.ID);
@ -381,39 +383,43 @@ namespace ASC.Web.Files.Services.DocumentService
}
public List<TemplatesConfig> GetTemplates(EntryManager entryManager)
public List<TemplatesConfig> Templates
{
if (!AuthContext.IsAuthenticated || UserManager.GetUsers(AuthContext.CurrentAccount.ID).IsVisitor(UserManager)) return null;
if (!FilesSettingsHelper.TemplatesSection) return null;
var extension = FileUtility.GetInternalExtension(_configuration.Document.Title).TrimStart('.');
var filter = FilterType.FilesOnly;
switch (_configuration.GetFileType)
set { }
get
{
case FileType.Document:
filter = FilterType.DocumentsOnly;
break;
case FileType.Spreadsheet:
filter = FilterType.SpreadsheetsOnly;
break;
case FileType.Presentation:
filter = FilterType.PresentationsOnly;
break;
}
if (!AuthContext.IsAuthenticated || UserManager.GetUsers(AuthContext.CurrentAccount.ID).IsVisitor(UserManager)) return null;
if (!FilesSettingsHelper.TemplatesSection) return null;
var folderDao = DaoFactory.GetFolderDao<int>();
var fileDao = DaoFactory.GetFileDao<int>();
var files = entryManager.GetTemplates(folderDao, fileDao, filter, false, Guid.Empty, string.Empty, false);
var listTemplates = from file in files
select
new TemplatesConfig
{
Image = BaseCommonLinkUtility.GetFullAbsolutePath("skins/default/images/filetype/thumb/" + extension + ".png"),
Name = file.Title,
Title = file.Title,
Url = BaseCommonLinkUtility.GetFullAbsolutePath(FilesLinkUtility.GetFileWebEditorUrl(file.ID))
};
return listTemplates.ToList();
var extension = FileUtility.GetInternalExtension(_configuration.Document.Title).TrimStart('.');
var filter = FilterType.FilesOnly;
switch (_configuration.GetFileType)
{
case FileType.Document:
filter = FilterType.DocumentsOnly;
break;
case FileType.Spreadsheet:
filter = FilterType.SpreadsheetsOnly;
break;
case FileType.Presentation:
filter = FilterType.PresentationsOnly;
break;
}
var folderDao = DaoFactory.GetFolderDao<int>();
var fileDao = DaoFactory.GetFileDao<int>();
var files = EntryManager.GetTemplates(folderDao, fileDao, filter, false, Guid.Empty, string.Empty, false);
var listTemplates = from file in files
select
new TemplatesConfig
{
Image = BaseCommonLinkUtility.GetFullAbsolutePath("skins/default/images/filetype/thumb/" + extension + ".png"),
Name = file.Title,
Title = file.Title,
Url = BaseCommonLinkUtility.GetFullAbsolutePath(FilesLinkUtility.GetFileWebEditorUrl(file.ID))
};
return listTemplates.ToList();
}
}
public string CallbackUrl { get; set; }
@ -435,6 +441,7 @@ namespace ASC.Web.Files.Services.DocumentService
public CustomizationConfig<T> Customization { get; set; }
private FilesSettingsHelper FilesSettingsHelper { get; }
private IDaoFactory DaoFactory { get; }
private EntryManager EntryManager { get; }
public EmbeddedConfig Embedded
{
@ -465,39 +472,41 @@ namespace ASC.Web.Files.Services.DocumentService
private BaseCommonLinkUtility BaseCommonLinkUtility { get; }
public string SaveAsUrl { get; set; }
public List<RecentConfig> GetRecent(EntryManager entryManager)
public List<RecentConfig> Recent
{
if (!AuthContext.IsAuthenticated || UserManager.GetUsers(AuthContext.CurrentAccount.ID).IsVisitor(UserManager)) return null;
if (!FilesSettingsHelper.RecentSection) return null;
var filter = FilterType.FilesOnly;
switch (_configuration.GetFileType)
get
{
case FileType.Document:
filter = FilterType.DocumentsOnly;
break;
case FileType.Spreadsheet:
filter = FilterType.SpreadsheetsOnly;
break;
case FileType.Presentation:
filter = FilterType.PresentationsOnly;
break;
if (!AuthContext.IsAuthenticated || UserManager.GetUsers(AuthContext.CurrentAccount.ID).IsVisitor(UserManager)) return null;
if (!FilesSettingsHelper.RecentSection) return null;
var filter = FilterType.FilesOnly;
switch (_configuration.GetFileType)
{
case FileType.Document:
filter = FilterType.DocumentsOnly;
break;
case FileType.Spreadsheet:
filter = FilterType.SpreadsheetsOnly;
break;
case FileType.Presentation:
filter = FilterType.PresentationsOnly;
break;
}
var folderDao = DaoFactory.GetFolderDao<int>();
var files = EntryManager.GetRecent(filter, false, Guid.Empty, string.Empty, false).Cast<File<int>>();
var listRecent = from file in files
where !Equals(_configuration.Document.Info.GetFile().ID, file.ID)
select
new RecentConfig
{
Folder = folderDao.GetFolder(file.FolderID).Title,
Title = file.Title,
Url = BaseCommonLinkUtility.GetFullAbsolutePath(FilesLinkUtility.GetFileWebEditorUrl(file.ID))
};
return listRecent.ToList();
}
var folderDao = DaoFactory.GetFolderDao<int>();
var files = entryManager.GetRecent(filter, false, Guid.Empty, string.Empty, false).Cast<File<int>>();
var listRecent = from file in files
where !Equals(_configuration.Document.Info.GetFile().ID, file.ID)
select
new RecentConfig
{
Folder = folderDao.GetFolder(file.FolderID).Title,
Title = file.Title,
Url = BaseCommonLinkUtility.GetFullAbsolutePath(FilesLinkUtility.GetFileWebEditorUrl(file.ID))
};
return listRecent.ToList();
}
public string SharingSettingsUrl { get; set; }
@ -863,15 +872,13 @@ namespace ASC.Web.Files.Services.DocumentService
public class LogoConfig<T>
{
public LogoConfig(
SettingsManager settingsManager,
BaseCommonLinkUtility baseCommonLinkUtility,
CommonLinkUtility commonLinkUtility,
TenantLogoHelper tenantLogoHelper,
FileUtility fileUtility)
{
BaseCommonLinkUtility = baseCommonLinkUtility;
CommonLinkUtility = commonLinkUtility;
TenantLogoHelper = tenantLogoHelper;
FileUtility = fileUtility;
SettingsManager = settingsManager;
}
private Configuration<T> _configuration;
@ -890,8 +897,8 @@ namespace ASC.Web.Files.Services.DocumentService
return
_configuration.EditorType == EditorType.Embedded
|| fillingForm
? BaseCommonLinkUtility.GetFullAbsolutePath(TenantLogoHelper.GetLogo(WhiteLabelLogoTypeEnum.Dark, !_configuration.EditorConfig.Customization.IsRetina))
: BaseCommonLinkUtility.GetFullAbsolutePath(TenantLogoHelper.GetLogo(WhiteLabelLogoTypeEnum.DocsEditor, !_configuration.EditorConfig.Customization.IsRetina));
? CommonLinkUtility.GetFullAbsolutePath(TenantLogoHelper.GetLogo(WhiteLabelLogoTypeEnum.Dark, !_configuration.EditorConfig.Customization.IsRetina))
: CommonLinkUtility.GetFullAbsolutePath(TenantLogoHelper.GetLogo(WhiteLabelLogoTypeEnum.DocsEditor, !_configuration.EditorConfig.Customization.IsRetina));
}
}
@ -903,20 +910,19 @@ namespace ASC.Web.Files.Services.DocumentService
return
_configuration.EditorType != EditorType.Embedded
? null
: BaseCommonLinkUtility.GetFullAbsolutePath(TenantLogoHelper.GetLogo(WhiteLabelLogoTypeEnum.Dark, !_configuration.EditorConfig.Customization.IsRetina));
: CommonLinkUtility.GetFullAbsolutePath(TenantLogoHelper.GetLogo(WhiteLabelLogoTypeEnum.Dark, !_configuration.EditorConfig.Customization.IsRetina));
}
}
public string Url
{
set { }
get { return CompanyWhiteLabelSettings.Instance(SettingsManager).Site; }
get { return CommonLinkUtility.GetFullAbsolutePath(CommonLinkUtility.GetDefault()); }
}
private BaseCommonLinkUtility BaseCommonLinkUtility { get; }
private CommonLinkUtility CommonLinkUtility { get; }
private TenantLogoHelper TenantLogoHelper { get; }
public FileUtility FileUtility { get; }
private SettingsManager SettingsManager { get; }
private FileUtility FileUtility { get; }
}
public class RecentConfig

View File

@ -1007,8 +1007,14 @@ namespace ASC.Web.Files.Utils
if (checkRight && (!forcesave.HasValue || forcesave.Value == ForcesaveType.None) && FileTracker.IsEditing(file.ID)) throw new Exception(FilesCommonResource.ErrorMassage_SecurityException_UpdateEditingFile);
if (file.RootFolderType == FolderType.TRASH) throw new Exception(FilesCommonResource.ErrorMassage_ViewTrashItem);
var currentExt = file.ConvertedExtension;
if (string.IsNullOrEmpty(newExtension)) newExtension = FileUtility.GetInternalExtension(file.Title);
var currentExt = file.ConvertedExtension;
if (string.IsNullOrEmpty(newExtension))
{
if (currentExt != FileUtility.MasterFormExtension)
newExtension = FileUtility.GetInternalExtension(file.Title);
else
newExtension = currentExt;
}
var replaceVersion = false;
if (file.Forcesave != ForcesaveType.None)
@ -1036,8 +1042,13 @@ namespace ASC.Web.Files.Utils
if (!storeTemplate.IsDirectory(path))
{
path = FileConstant.NewDocPath + "en-US/";
}
path += "new" + FileUtility.GetInternalExtension(file.Title);
}
var fileExt = currentExt != FileUtility.MasterFormExtension
? FileUtility.GetInternalExtension(file.Title)
: currentExt;
path += "new" + fileExt;
//todo: think about the criteria for saving after creation
if (!storeTemplate.IsFile(path) || file.ContentLength != storeTemplate.GetFileSize("", path))

View File

@ -1118,6 +1118,32 @@ namespace ASC.Api.Documents
return FilesControllerHelperInt.GetFileInfo(fileId, version);
}
[Create("file/{fileId:int}/copyas", order: int.MaxValue - 1)]
public FileWrapper<int> CopyFileAsFromBody(int fileId, [FromBody] CopyAsModel<int> model)
{
return FilesControllerHelperInt.CopyFileAs(fileId, model.DestFolderId, model.DestTitle);
}
[Create("file/{fileId:int}/copyas", order: int.MaxValue - 1)]
[Consumes("application/x-www-form-urlencoded")]
public FileWrapper<int> CopyFileAsFromForm(int fileId, [FromForm] CopyAsModel<int> model)
{
return FilesControllerHelperInt.CopyFileAs(fileId, model.DestFolderId, model.DestTitle);
}
[Create("file/{fileId}/copyas", order: int.MaxValue)]
public FileWrapper<string> CopyFileAsFromBody(string fileId, [FromBody] CopyAsModel<string> model)
{
return FilesControllerHelperString.CopyFileAs(fileId, model.DestFolderId, model.DestTitle);
}
[Create("file/{fileId}/copyas", order: int.MaxValue)]
[Consumes("application/x-www-form-urlencoded")]
public FileWrapper<string> CopyFileAsFromForm(string fileId, [FromBody] CopyAsModel<string> model)
{
return FilesControllerHelperString.CopyFileAs(fileId, model.DestFolderId, model.DestTitle);
}
/// <summary>
/// Updates the information of the selected file with the parameters specified in the request
/// </summary>

@ -1 +1 @@
Subproject commit 9fea7818de9abb29295c51b39a54c566bead9895
Subproject commit 2a80ac03391aff3bf1f15a5149f6124954b73020

View File

@ -65,6 +65,7 @@ namespace ASC.Files.Helpers
private SettingsManager SettingsManager { get; }
private EncryptionKeyPairHelper EncryptionKeyPairHelper { get; }
private IHttpContextAccessor HttpContextAccessor { get; }
private FileConverter FileConverter { get; }
private ILog Logger { get; set; }
/// <summary>
@ -92,7 +93,8 @@ namespace ASC.Files.Helpers
IOptionsMonitor<ILog> optionMonitor,
SettingsManager settingsManager,
EncryptionKeyPairHelper encryptionKeyPairHelper,
IHttpContextAccessor httpContextAccessor)
IHttpContextAccessor httpContextAccessor,
FileConverter fileConverter)
{
ApiContext = context;
FileStorageService = fileStorageService;
@ -114,6 +116,7 @@ namespace ASC.Files.Helpers
SettingsManager = settingsManager;
EncryptionKeyPairHelper = encryptionKeyPairHelper;
HttpContextAccessor = httpContextAccessor;
FileConverter = fileConverter;
Logger = optionMonitor.Get("ASC.Files");
}
@ -343,6 +346,22 @@ namespace ASC.Files.Helpers
var file = FileStorageService.GetFile(fileId, version).NotFoundIfNull("File not found");
return FileWrapperHelper.Get(file);
}
public FileWrapper<T> CopyFileAs(T fileId, T destFolderId, string destTitle)
{
var file = FileStorageService.GetFile(fileId, -1);
var ext = FileUtility.GetFileExtension(file.Title);
var destExt = FileUtility.GetFileExtension(destTitle);
if (ext == destExt)
{
return CreateFile(destFolderId, destTitle, fileId);
}
using (var fileStream = FileConverter.Exec(file, destExt))
{
return InsertFile(destFolderId, fileStream, destTitle, true);
}
}
public FileWrapper<T> AddToRecent(T fileId, int version = -1)
{

View File

@ -6,7 +6,7 @@
"scripts": {
"build": "webpack --mode production",
"clean": "shx rm -rf dist",
"deploy": "shx mkdir -p ../../../build/deploy/products/ASC.Mail/client && shx cp -r dist/* ../../../build/deploy/products/ASC.Mail/client",
"deploy": "shx --silent mkdir -p ../../../build/deploy/products/ASC.Mail/client && shx cp -r dist/* ../../../build/deploy/products/ASC.Mail/client",
"serve": "serve dist -p 5016",
"start": "webpack-cli serve",
"start-prod": "webpack --mode production && serve dist -p 5016"

View File

@ -1,7 +1,7 @@
import App from "./App";
import React from "react";
import ReactDOM from "react-dom";
import { registerSW } from "@appserver/common/utils/sw-helper";
import { registerSW } from "@appserver/common/sw/helper";
ReactDOM.render(<App />, document.getElementById("root"));

View File

@ -6,7 +6,7 @@
"scripts": {
"build": "webpack --mode production",
"clean": "shx rm -rf dist",
"deploy": "shx mkdir -p ../../../build/deploy/products/ASC.People/client && shx cp -r dist/* ../../../build/deploy/products/ASC.People/client",
"deploy": "shx --silent mkdir -p ../../../build/deploy/products/ASC.People/client && shx cp -r dist/* ../../../build/deploy/products/ASC.People/client",
"serve": "serve dist -p 5002",
"start": "webpack-cli serve",
"start-prod": "webpack --mode production && serve dist -p 5002"

View File

@ -1,7 +1,7 @@
import App from "./App";
import React from "react";
import ReactDOM from "react-dom";
import { registerSW } from "@appserver/common/utils/sw-helper";
import { registerSW } from "@appserver/common/sw/helper";
ReactDOM.render(<App />, document.getElementById("root"));

View File

@ -6,7 +6,7 @@
"scripts": {
"build": "webpack --mode production",
"clean": "shx rm -rf dist",
"deploy": "shx mkdir -p ../../../build/deploy/products/ASC.Projects/client && shx cp -r dist/* ../../../build/deploy/products/ASC.Projects/client",
"deploy": "shx --silent mkdir -p ../../../build/deploy/products/ASC.Projects/client && shx cp -r dist/* ../../../build/deploy/products/ASC.Projects/client",
"serve": "serve dist -p 5015",
"start": "webpack-cli serve",
"start-prod": "webpack --mode production && serve dist -p 5015"

View File

@ -1,7 +1,7 @@
import App from "./App";
import React from "react";
import ReactDOM from "react-dom";
import { registerSW } from "@appserver/common/utils/sw-helper";
import { registerSW } from "@appserver/common/sw/helper";
ReactDOM.render(<App />, document.getElementById("root"));

View File

@ -1,3 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M9.85498 16L1.15188 16C0.518827 16 0.00382324 15.485 0.00378789 14.8519L0.00378713 6.14776C0.00378708 5.51468 0.518826 4.99964 1.15187 4.99964L4.01036 4.99964L4.01036 9.96112C4.01036 11.1721 4.75801 11.9963 5.96898 11.9963L11.0031 11.9963L11.0031 14.8519C11.0031 15.485 10.4881 16 9.85498 16ZM14.8703 11.0013L6.12976 11.0013C5.50682 11.0013 4.99997 10.4945 5 9.87155L5 1.12969C5 0.506788 5.50679 9.17346e-07 6.12973 8.62887e-07L14.8703 9.87639e-08C15.4932 4.43048e-08 16 0.506787 16 1.12973L16 9.87155C16 10.4945 15.4932 11.0013 14.8703 11.0013Z" fill="#A3A9AE"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M6.14502 -1.07443e-06L14.8481 -2.59613e-06C15.4812 -2.70681e-06 15.9962 0.515037 15.9962 1.14808L15.9962 9.85224C15.9962 10.4853 15.4812 11.0004 14.8481 11.0004L11.9896 11.0004L11.9896 6.03888C11.9896 4.82791 11.242 4.00369 10.031 4.00369L4.99694 4.00369L4.99694 1.14809C4.99693 0.515038 5.51194 -9.63738e-07 6.14502 -1.07443e-06ZM1.12973 4.99872L9.87024 4.99872C10.4932 4.99872 11 5.50551 11 6.12845L11 14.8703C11 15.4932 10.4932 16 9.87028 16L1.12973 16C0.50679 16 3.70837e-05 15.4932 2.6e-06 14.8703L1.07154e-06 6.12845C9.62618e-07 5.50551 0.506823 4.99872 1.12973 4.99872Z" fill="#A3A9AE"/>
</svg>

Before

Width:  |  Height:  |  Size: 716 B

After

Width:  |  Height:  |  Size: 747 B

View File

@ -1,3 +1,4 @@
<svg width="17" height="17" viewBox="0 0 17 17" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M16.9862 0.721877L16.2791 0.0147705L8.50091 7.79295L0.722732 0.0147736L0.015625 0.72188L7.7938 8.50006L0.0156276 16.2782L0.722734 16.9853L8.50091 9.20716L16.2791 16.9853L16.9862 16.2782L9.20801 8.50005L16.9862 0.721877Z" fill="#D0D5DA"/>
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12 1.99951L1.99968 11.9998" stroke="#A3A9AE" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M2 1.99951L12.0003 11.9998" stroke="#A3A9AE" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 390 B

After

Width:  |  Height:  |  Size: 344 B

View File

@ -1,12 +1,3 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g stroke="none" stroke-width="1" fill-rule="evenodd">
<g transform="translate(-168.000000, -936.000000)" fill="#000000">
<g transform="translate(24.000000, 888.000000)">
<g transform="translate(144.000000, 48.000000)">
<path fill="#000000" stroke="#000000" stroke-width="0.1" stroke-dasharray="null" stroke-linejoin="null" stroke-linecap="null" d="m1.40265,7.01045c0,0 21.20239,0 21.20239,0c0,0 -10.56768,11.23794 -10.56768,11.23794c0,0 -10.63471,-11.23794 -10.63471,-11.23794z" id="svg_29"/>
</g>
</g>
</g>
</g>
</svg>
<svg width="5" height="4" viewBox="0 0 5 4" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M2.5 4L0 1L5 1L2.5 4Z" fill="#333333"/>
</svg>

Before

Width:  |  Height:  |  Size: 804 B

After

Width:  |  Height:  |  Size: 188 B

View File

@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M0 2C0 0.895431 0.895431 0 2 0H14C15.1046 0 16 0.895431 16 2V7H14H7H2V10H7V16H2C0.895431 16 0 15.1046 0 14V2ZM2 2H14V5H2V2ZM11 9H13V11H15V13H13V15H11V13H9V11H11V9Z" fill="#657077"/>
</svg>

After

Width:  |  Height:  |  Size: 334 B

View File

@ -52,6 +52,7 @@
"LoadingDescription": "Please wait...",
"LoadingProcessing": "Loading...",
"Mail": "Mail",
"MakeForm": "Make form",
"MeLabel": "Me",
"More": "More",
"Next": "Next",

View File

@ -301,7 +301,7 @@ namespace ASC.Web.Api.Controllers
try
{
var token = SecurityContext.AuthenticateMe(user.ID);
CookiesManager.SetCookies(CookiesType.AuthKey, token);
CookiesManager.SetCookies(CookiesType.AuthKey, token, auth.Session);
MessageService.Send(viaEmail ? MessageAction.LoginSuccessViaApi : MessageAction.LoginSuccessViaApiSocialAccount);
@ -436,6 +436,8 @@ namespace ASC.Web.Api.Controllers
{
throw new Exception("user not found");
}
Cache.Insert("loginsec/" + memberModel.UserName, (--counter).ToString(CultureInfo.InvariantCulture), DateTime.UtcNow.Add(TimeSpan.FromMinutes(1)));
}
else
{

View File

@ -9,6 +9,7 @@
public string AccessToken { get; set; }
public string SerializedProfile { get; set; }
public string Code { get; set; }
public bool Session { get; set; }
}
public class MobileModel

View File

@ -6,7 +6,7 @@
"scripts": {
"build": "webpack --mode production",
"clean": "shx rm -rf dist",
"deploy": "shx mkdir -p ../../build/deploy/studio/client && shx cp -r dist/* ../../build/deploy/studio/client",
"deploy": "shx --silent mkdir -p ../../build/deploy/studio/client && shx cp -r dist/* ../../build/deploy/studio/client",
"serve": "serve dist -p 5001",
"start": "webpack-cli serve",
"start-prod": "webpack --mode production && serve dist -p 5001"

View File

@ -1,7 +1,7 @@
import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
import { registerSW } from "@appserver/common/utils/sw-helper";
import { registerSW } from "@appserver/common/sw/helper";
ReactDOM.render(<App />, document.getElementById("root"));

View File

@ -19,16 +19,12 @@ const version = pkg.version;
const config = {
entry: "./src/index",
target: "web",
mode: "development",
stats: {
errorDetails: true,
},
devServer: {
devMiddleware: {
publicPath: homepage,
//writeToDisk: true,
},
static: {
directory: path.join(__dirname, "dist"),
@ -240,11 +236,7 @@ module.exports = (env, argv) => {
config.optimization = {
splitChunks: { chunks: "all" },
minimize: true,
minimizer: [
new TerserPlugin({
include: "./src/store",
}),
],
minimizer: [new TerserPlugin()],
};
} else {
config.devtool = "cheap-module-source-map";

View File

@ -6,7 +6,7 @@
"scripts": {
"build": "webpack --mode production",
"clean": "shx rm -rf dist",
"deploy": "shx mkdir -p ../../build/deploy/products/ASC.Files/editor && shx cp -r dist/* ../../build/deploy/products/ASC.Files/editor",
"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-prod": "webpack --mode production && serve dist -p 5013"

View File

@ -20,8 +20,6 @@ import {
setEncryptionKeys,
getEncryptionAccess,
getFileInfo,
getRecentFolderList,
getFolderInfo,
updateFile,
removeFromFavorite,
markAsFavorite,
@ -78,7 +76,8 @@ const Editor = () => {
const decodedId = urlParams
? urlParams.fileId || urlParams.fileid || null
: null;
const fileId = encodeURIComponent(decodedId);
const fileId =
typeof decodedId === "string" ? encodeURIComponent(decodedId) : decodedId;
const version = urlParams ? urlParams.version || null : null;
const doc = urlParams ? urlParams.doc || null : null;
const isDesktop = window["AscDesktopEditor"] !== undefined;
@ -143,56 +142,6 @@ const Editor = () => {
docEditor.setFavorite(favorite);
};
const getRecent = async (config) => {
try {
const recentFolderList = await getRecentFolderList();
const filesArray = recentFolderList.files.slice(0, 25);
const recentFiles = filesArray.filter(
(file) =>
file.rootFolderType !== FolderType.SHARE &&
((config.documentType === text && file.fileType === 7) ||
(config.documentType === spreadSheet && file.fileType === 5) ||
(config.documentType === presentation && file.fileType === 6))
);
const groupedByFolder = recentFiles.reduce((r, a) => {
r[a.folderId] = [...(r[a.folderId] || []), a];
return r;
}, {});
const requests = Object.entries(groupedByFolder).map((item) =>
getFolderInfo(item[0])
.then((folderInfo) =>
Promise.resolve({
files: item[1],
folderInfo: folderInfo,
})
)
.catch((e) => console.error(e))
);
let recent = [];
let responses = await Promise.all(requests);
for (let res of responses) {
res.files.forEach((file) => {
const convertedData = convertRecentData(file, res.folderInfo);
if (Object.keys(convertedData).length !== 0)
recent.push(convertedData);
});
}
return recent;
} catch (e) {
console.error(e);
}
return null;
};
const initDesktop = (config) => {
const isEncryption = config?.editorConfig["encryptionKeys"] !== undefined;
@ -312,17 +261,6 @@ const Editor = () => {
initDesktop();
}
if (successAuth) {
const recent = await getRecent(config); //TODO: too slow for 1st loading
if (recent) {
config.editorConfig = {
...config.editorConfig,
recent: recent,
};
}
}
isSharingAccess = fileInfo && fileInfo.canShare;
if (view) {
@ -343,20 +281,6 @@ const Editor = () => {
}
};
const convertRecentData = (file, folder) => {
let obj = {};
const folderName = folder.title;
const fileName = file.title;
if (+fileId !== file.id)
obj = {
folder: folderName,
title: fileName,
url: file.webUrl,
};
return obj;
};
const isIPad = () => {
return isIOS && deviceType === "tablet";
};

View File

@ -1,7 +1,7 @@
import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
import { registerSW } from "@appserver/common/utils/sw-helper";
import { registerSW } from "@appserver/common/sw/helper";
ReactDOM.render(<App />, document.getElementById("root"));

View File

@ -6,7 +6,7 @@
"scripts": {
"build": "webpack --mode production",
"clean": "shx rm -rf dist",
"deploy": "shx mkdir -p ../../build/deploy/studio/login && shx cp -r dist/* ../../build/deploy/studio/login",
"deploy": "shx --silent mkdir -p ../../build/deploy/studio/login && shx cp -r dist/* ../../build/deploy/studio/login",
"serve": "serve dist -p 5011",
"start": "webpack-cli serve",
"start-prod": "webpack --mode production && serve dist -p 5011"

View File

@ -1,7 +1,7 @@
import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
import { registerSW } from "@appserver/common/utils/sw-helper";
import { registerSW } from "@appserver/common/sw/helper";
ReactDOM.render(<App />, document.getElementById("root"));