Merge branch 'release/v1.0.0' of https://github.com/ONLYOFFICE/DocSpace into release/v1.0.0
This commit is contained in:
commit
7716e32573
@ -26,7 +26,7 @@
|
||||
"SetAppDescription": "Включена двухфакторная аутентификация. Чтобы продолжить работу в DocSpace, настройте приложение для аутентификации. Вы можете использовать Google Authenticator для <1>Android</1> и <4>iOS</4> или Authenticator для <8>Windows Phone</8>.",
|
||||
"SetAppInstallDescription": "Для подключения приложения отсканируйте QR-код или вручную введите секретный ключ <1>{{ secretKey }}</1>, а затем введите 6-значный код из приложения в поле ниже.",
|
||||
"SetAppTitle": "Настройте приложение для аутентификации",
|
||||
"SuccessDeactivate": "Ваш аккаунт успешно деактивирован. Через 10 секунд вы будете перенаправлены на сайт <1>site</1>.",
|
||||
"SuccessReactivate": "Ваша учетная запись успешно повторно активирована. Через 10 секунд вы будете перенаправлены на <1>portal</1>.",
|
||||
"SuccessDeactivate": "Ваш аккаунт успешно деактивирован. Через 10 секунд вы будете перенаправлены на <1>сайт</1>.",
|
||||
"SuccessReactivate": "Ваша учетная запись успешно повторно активирована. Через 10 секунд вы будете перенаправлены на <1>портал</1>.",
|
||||
"WelcomeUser": "Добро пожаловать на DocSpace!\nЧтобы приступить к работе зарегистрируйтесь, или войдите через социальную сеть."
|
||||
}
|
||||
|
@ -43,7 +43,8 @@ const StyledRowContainer = styled(RowContainer)`
|
||||
.user-row {
|
||||
border-top: ${(props) =>
|
||||
`1px ${props.theme.filesSection.tableView.row.borderColor} solid`};
|
||||
margin-top: -3px;
|
||||
margin-top: -4px;
|
||||
|
||||
${marginStyles}
|
||||
}
|
||||
}
|
||||
@ -52,7 +53,8 @@ const StyledRowContainer = styled(RowContainer)`
|
||||
.user-row {
|
||||
border-top: ${(props) =>
|
||||
`1px ${props.theme.filesSection.tableView.row.borderColor} solid`};
|
||||
margin-top: -3px;
|
||||
margin-top: -4px;
|
||||
|
||||
${marginStyles}
|
||||
}
|
||||
}
|
||||
@ -68,6 +70,7 @@ const StyledRowContainer = styled(RowContainer)`
|
||||
border-bottom: ${(props) =>
|
||||
`1px ${props.theme.filesSection.tableView.row.borderColor} solid`};
|
||||
padding-bottom: 1px;
|
||||
padding-top: 1px;
|
||||
${marginStyles}
|
||||
}
|
||||
.user-row::after {
|
||||
@ -78,10 +81,47 @@ const StyledRowContainer = styled(RowContainer)`
|
||||
.user-row {
|
||||
border-top: ${(props) =>
|
||||
`1px ${props.theme.filesSection.tableView.row.borderColor} solid`};
|
||||
margin-top: -3px;
|
||||
margin-top: -4px;
|
||||
|
||||
${marginStyles}
|
||||
}
|
||||
}
|
||||
|
||||
.row-selected {
|
||||
.user-row {
|
||||
.styled-element {
|
||||
padding-top: 1px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
.row-selected {
|
||||
.user-row {
|
||||
margin-top: -3px !important;
|
||||
padding-bottom: 0.8px !important;
|
||||
padding-top: 0.8px !important;
|
||||
|
||||
.styled-element {
|
||||
padding-bottom: 0.8px;
|
||||
|
||||
.owner_icon {
|
||||
padding-bottom: 0.8px;
|
||||
}
|
||||
}
|
||||
|
||||
.expandButton {
|
||||
padding-bottom: 0.8px;
|
||||
}
|
||||
|
||||
.mainIcons {
|
||||
.paid-badge {
|
||||
margin-top: -1px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const PeopleRowContainer = ({
|
||||
@ -120,7 +160,7 @@ const PeopleRowContainer = ({
|
||||
hasMoreFiles={hasMoreAccounts}
|
||||
itemCount={filterTotal}
|
||||
filesLength={peopleList.length}
|
||||
itemHeight={58}
|
||||
itemHeight={57.6}
|
||||
>
|
||||
{peopleList.map((item) => (
|
||||
<SimpleUserRow
|
||||
|
@ -70,9 +70,48 @@ const StyledSimpleUserRow = styled(Row)`
|
||||
cursor: pointer;
|
||||
${checkedStyle}
|
||||
|
||||
margin-top: -3px;
|
||||
border-top: ${(props) =>
|
||||
`1px ${props.theme.filesSection.tableView.row.borderColor} solid`};
|
||||
@media (min-width: 1024px) {
|
||||
margin-top: -4px !important;
|
||||
padding-top: 1px;
|
||||
padding-bottom: 1px;
|
||||
|
||||
border-top: ${(props) =>
|
||||
`1px ${props.theme.filesSection.tableView.row.borderColor} solid`};
|
||||
border-bottom: ${(props) =>
|
||||
`1px ${props.theme.filesSection.tableView.row.borderColor} solid`};
|
||||
|
||||
.styled-checkbox-container {
|
||||
.styled-element {
|
||||
padding-top: 1px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
margin-top: -3px !important;
|
||||
|
||||
padding-top: 0.8px;
|
||||
padding-bottom: 0.8px;
|
||||
|
||||
border-top: ${(props) =>
|
||||
`1px ${props.theme.filesSection.tableView.row.borderColor} solid`};
|
||||
border-bottom: ${(props) =>
|
||||
`1px ${props.theme.filesSection.tableView.row.borderColor} solid`};
|
||||
|
||||
.owner_icon {
|
||||
padding-bottom: 0.8px;
|
||||
}
|
||||
|
||||
.mainIcons {
|
||||
.paid-badge {
|
||||
margin-top: -1px;
|
||||
}
|
||||
}
|
||||
|
||||
.expandButton {
|
||||
padding-bottom: 0.8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
`}
|
||||
|
||||
|
@ -43,6 +43,24 @@ const StyledTableContainer = styled(TableContainer)`
|
||||
.table-container_row-context-menu-wrapper {
|
||||
${contextCss}
|
||||
}
|
||||
|
||||
.table-container_cell {
|
||||
.paid-badge {
|
||||
p {
|
||||
padding-top: 1px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
:hover {
|
||||
.table-container_cell {
|
||||
.paid-badge {
|
||||
p {
|
||||
padding-top: 1px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.table-row-selected + .table-row-selected {
|
||||
|
@ -29,6 +29,12 @@ const StyledPeopleRow = styled(TableRow)`
|
||||
border-top: ${(props) =>
|
||||
`1px solid ${props.theme.filesSection.tableView.row.borderColor}`};
|
||||
margin-top: -1px;
|
||||
|
||||
.paid-badge {
|
||||
p {
|
||||
padding-top: 1px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.table-container_user-name-cell {
|
||||
@ -338,6 +344,7 @@ const PeopleTableRow = (props) => {
|
||||
hasAccess={true}
|
||||
className="table-container_row-checkbox-wrapper"
|
||||
checked={isChecked}
|
||||
style={{ borderBottom: "none" }}
|
||||
>
|
||||
<div className="table-container_element">{element}</div>
|
||||
<Checkbox
|
||||
|
@ -1,13 +1,27 @@
|
||||
import React from "react";
|
||||
import styled from "styled-components";
|
||||
import ErrorContainer from "@docspace/common/components/ErrorContainer";
|
||||
import { I18nextProvider, useTranslation } from "react-i18next";
|
||||
import i18n from "./i18n";
|
||||
import DocspaceLogo from "../../../DocspaceLogo";
|
||||
|
||||
const ErrorUnavailable = () => {
|
||||
const StyledWrapper = styled.div`
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 64px;
|
||||
`;
|
||||
|
||||
const ErrorUnavailable = ({ logoUrl }) => {
|
||||
const { t, ready } = useTranslation("Errors");
|
||||
|
||||
return ready ? (
|
||||
<ErrorContainer headerText={t("ErrorUnavailableText")} />
|
||||
<StyledWrapper>
|
||||
<DocspaceLogo />
|
||||
<ErrorContainer headerText={t("ErrorUnavailableText")} />
|
||||
</StyledWrapper>
|
||||
) : (
|
||||
<></>
|
||||
);
|
||||
|
@ -47,6 +47,17 @@ const StyledRowContainer = styled(RowContainer)`
|
||||
}
|
||||
}
|
||||
|
||||
.row-hotkey-border {
|
||||
.files-row {
|
||||
margin-top: -3px;
|
||||
padding-top: 2px;
|
||||
|
||||
.row_content {
|
||||
padding-top: 1px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.row-selected:last-child {
|
||||
.files-row {
|
||||
border-bottom: ${(props) =>
|
||||
@ -61,10 +72,18 @@ const StyledRowContainer = styled(RowContainer)`
|
||||
.files-row {
|
||||
border-top: ${(props) =>
|
||||
`1px ${props.theme.filesSection.tableView.row.borderColor} solid`};
|
||||
margin-top: -2px;
|
||||
margin-top: -2.2px;
|
||||
padding-top: 1px;
|
||||
padding-bottom: 1px;
|
||||
${marginStyles};
|
||||
|
||||
.row_content {
|
||||
padding-top: 1px;
|
||||
|
||||
.mainIcons {
|
||||
padding-bottom: 1px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
@ -135,7 +154,7 @@ const FilesRowContainer = ({
|
||||
hasMoreFiles={hasMoreFiles}
|
||||
draggable
|
||||
useReactWindow={!withPaging}
|
||||
itemHeight={59}
|
||||
itemHeight={59.2}
|
||||
>
|
||||
{filesListNode}
|
||||
</StyledRowContainer>
|
||||
|
@ -40,13 +40,27 @@ const StyledSimpleFilesRow = styled(Row)`
|
||||
cursor: pointer;
|
||||
${checkedStyle}
|
||||
|
||||
margin-top: -2px;
|
||||
margin-top: -2.2px;
|
||||
padding-top: 1px;
|
||||
padding-bottom: 1px;
|
||||
border-top: ${(props) =>
|
||||
`1px ${props.theme.filesSection.tableView.row.borderColor} solid`};
|
||||
border-bottom: ${(props) =>
|
||||
`1px ${props.theme.filesSection.tableView.row.borderColor} solid`};
|
||||
|
||||
.row_content {
|
||||
padding-top: 1px;
|
||||
|
||||
.mainIcons {
|
||||
padding-bottom: 1px;
|
||||
}
|
||||
}
|
||||
|
||||
.styled-checkbox-container {
|
||||
.styled-element {
|
||||
padding-top: 1px;
|
||||
}
|
||||
}
|
||||
}
|
||||
`};
|
||||
|
||||
|
@ -48,12 +48,14 @@ const StyledTableRow = styled(TableRow)`
|
||||
background: ${(props) =>
|
||||
`${props.theme.filesSection.tableView.row.backgroundActive} !important`};
|
||||
|
||||
margin-top: ${(props) => (props.showHotkeyBorder ? "-2px" : "-1px")};
|
||||
margin-top: ${(props) =>
|
||||
props.showHotkeyBorder ? "-2px" : "-0.8px"};
|
||||
|
||||
${(props) =>
|
||||
!props.showHotkeyBorder &&
|
||||
css`
|
||||
border-top: ${(props) =>
|
||||
`1px solid ${props.theme.filesSection.tableView.row.borderColor}`};
|
||||
`0.8px solid ${props.theme.filesSection.tableView.row.borderColor}`};
|
||||
`}
|
||||
}
|
||||
.table-container_file-name-cell {
|
||||
|
@ -31,6 +31,10 @@ const StyledTableContainer = styled(TableContainer)`
|
||||
.table-row-selected {
|
||||
.table-container_file-name-cell {
|
||||
${fileNameCss}
|
||||
|
||||
.table-container_element-container,.additional-badges {
|
||||
padding-top: 1px;
|
||||
}
|
||||
}
|
||||
|
||||
.table-container_row-context-menu-wrapper {
|
||||
@ -77,6 +81,14 @@ const StyledTableContainer = styled(TableContainer)`
|
||||
}
|
||||
}
|
||||
|
||||
.table-hotkey-border {
|
||||
.table-row {
|
||||
.tablet-badge {
|
||||
padding-top: 1px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.files-item:not(.table-row-selected) + .table-row-selected {
|
||||
.table-row {
|
||||
.table-container_file-name-cell {
|
||||
@ -228,7 +240,7 @@ const Table = ({
|
||||
useReactWindow={!withPaging}
|
||||
infoPanelVisible={infoPanelVisible}
|
||||
columnInfoPanelStorageName={columnInfoPanelStorageName}
|
||||
itemHeight={49}
|
||||
itemHeight={48.8}
|
||||
>
|
||||
{filesListNode}
|
||||
</TableBody>
|
||||
|
@ -36,6 +36,7 @@ const FileNameCell = ({
|
||||
className="table-container_element-wrapper"
|
||||
hasAccess={true}
|
||||
checked={checked}
|
||||
style={{ borderBottom: "none" }}
|
||||
>
|
||||
<div className="table-container_element-container">
|
||||
<div className="table-container_element">{element}</div>
|
||||
|
@ -44,6 +44,7 @@ const RoomsRowDataComponent = (props) => {
|
||||
checked={checkedProps}
|
||||
element={element}
|
||||
inProgress={inProgress}
|
||||
isRoomTable={true}
|
||||
{...props}
|
||||
/>
|
||||
<StyledBadgesContainer showHotkeyBorder={showHotkeyBorder}>
|
||||
|
@ -428,6 +428,7 @@ class Tile extends React.PureComponent {
|
||||
|
||||
this.cm = React.createRef();
|
||||
this.tile = React.createRef();
|
||||
this.checkboxContainerRef = React.createRef();
|
||||
}
|
||||
|
||||
onError = () => {
|
||||
@ -503,7 +504,8 @@ class Tile extends React.PureComponent {
|
||||
e.target.nodeName !== "INPUT" &&
|
||||
e.target.nodeName !== "rect" &&
|
||||
e.target.nodeName !== "path" &&
|
||||
e.target.nodeName !== "svg"
|
||||
e.target.nodeName !== "svg" &&
|
||||
!this.checkboxContainerRef.current?.contains(e.target)
|
||||
) {
|
||||
setSelection && setSelection([]);
|
||||
}
|
||||
@ -637,7 +639,10 @@ class Tile extends React.PureComponent {
|
||||
{renderElement && !(!fileExst && id === -1) && !isEdit && (
|
||||
<>
|
||||
{!inProgress ? (
|
||||
<div className="file-icon_container">
|
||||
<div
|
||||
className="file-icon_container"
|
||||
ref={this.checkboxContainerRef}
|
||||
>
|
||||
<StyledElement
|
||||
className="file-icon"
|
||||
isRoom={isRoom}
|
||||
|
@ -81,26 +81,28 @@ const StyledBody = styled.div`
|
||||
}
|
||||
`;
|
||||
|
||||
const PaymentContainer = ({
|
||||
isFreeTariff,
|
||||
isGracePeriod,
|
||||
theme,
|
||||
isNotPaidPeriod,
|
||||
payerEmail,
|
||||
user,
|
||||
isPaidPeriod,
|
||||
currencySymbol,
|
||||
startValue,
|
||||
currentTariffPlanTitle,
|
||||
tariffPlanTitle,
|
||||
expandArticle,
|
||||
gracePeriodEndDate,
|
||||
delayDaysCount,
|
||||
const PaymentContainer = (props) => {
|
||||
const {
|
||||
isFreeTariff,
|
||||
isGracePeriod,
|
||||
theme,
|
||||
isNotPaidPeriod,
|
||||
payerEmail,
|
||||
user,
|
||||
isPaidPeriod,
|
||||
currencySymbol,
|
||||
startValue,
|
||||
currentTariffPlanTitle,
|
||||
tariffPlanTitle,
|
||||
expandArticle,
|
||||
gracePeriodEndDate,
|
||||
delayDaysCount,
|
||||
|
||||
isAlreadyPaid,
|
||||
paymentDate,
|
||||
t,
|
||||
}) => {
|
||||
isAlreadyPaid,
|
||||
paymentDate,
|
||||
t,
|
||||
isNonProfit,
|
||||
} = props;
|
||||
const renderTooltip = () => {
|
||||
return (
|
||||
<>
|
||||
@ -164,6 +166,8 @@ const PaymentContainer = ({
|
||||
};
|
||||
|
||||
const planSuggestion = () => {
|
||||
if (isNonProfit) return;
|
||||
|
||||
if (isFreeTariff) {
|
||||
return (
|
||||
<Text
|
||||
@ -228,6 +232,41 @@ const PaymentContainer = ({
|
||||
return;
|
||||
};
|
||||
|
||||
const planDescription = () => {
|
||||
if (isFreeTariff || isNonProfit) return;
|
||||
|
||||
if (isGracePeriod)
|
||||
return (
|
||||
<Text noSelect fontSize={"14px"} lineHeight={"16px"}>
|
||||
<Trans t={t} i18nKey="GracePeriodActivatedInfo" ns="Payments">
|
||||
Grace period activated
|
||||
<strong>
|
||||
from {{ fromDate: paymentDate }} to
|
||||
{{ byDate: gracePeriodEndDate }}
|
||||
</strong>
|
||||
(days remaining: {{ delayDaysCount }})
|
||||
</Trans>{" "}
|
||||
<Text as="span" fontSize="14px" lineHeight="16px">
|
||||
{t("GracePeriodActivatedDescription")}
|
||||
</Text>
|
||||
</Text>
|
||||
);
|
||||
|
||||
if (isPaidPeriod)
|
||||
return (
|
||||
<Text
|
||||
noSelect
|
||||
fontSize={"14px"}
|
||||
lineHeight={"16px"}
|
||||
className="payment-info_managers-price"
|
||||
>
|
||||
<Trans t={t} i18nKey="BusinessFinalDateInfo" ns="Payments">
|
||||
{{ finalDate: paymentDate }}
|
||||
</Trans>
|
||||
</Text>
|
||||
);
|
||||
};
|
||||
|
||||
const isPayer = user.email === payerEmail;
|
||||
const isFreeAfterPaidPeriod = isFreeTariff && payerEmail?.length !== 0;
|
||||
|
||||
@ -244,7 +283,7 @@ const PaymentContainer = ({
|
||||
? expiredTitleSubscriptionWarning()
|
||||
: currentPlanTitle()}
|
||||
|
||||
{isAlreadyPaid && (
|
||||
{!isNonProfit && isAlreadyPaid && (
|
||||
<PayerInformationContainer
|
||||
isPayer={isPayer}
|
||||
isFreeAfterPaidPeriod={isFreeAfterPaidPeriod}
|
||||
@ -254,59 +293,37 @@ const PaymentContainer = ({
|
||||
<CurrentTariffContainer />
|
||||
|
||||
{planSuggestion()}
|
||||
{planDescription()}
|
||||
|
||||
{isGracePeriod && (
|
||||
<Text noSelect fontSize={"14px"} lineHeight={"16px"}>
|
||||
<Trans t={t} i18nKey="GracePeriodActivatedInfo" ns="Payments">
|
||||
Grace period activated
|
||||
<strong>
|
||||
from {{ fromDate: paymentDate }} to
|
||||
{{ byDate: gracePeriodEndDate }}
|
||||
</strong>
|
||||
(days remaining: {{ delayDaysCount }})
|
||||
</Trans>{" "}
|
||||
<Text as="span" fontSize="14px" lineHeight="16px">
|
||||
{t("GracePeriodActivatedDescription")}
|
||||
</Text>
|
||||
</Text>
|
||||
)}
|
||||
{!isNonProfit &&
|
||||
!isGracePeriod &&
|
||||
!isNotPaidPeriod &&
|
||||
!isFreeAfterPaidPeriod && (
|
||||
<div className="payment-info_wrapper">
|
||||
<Text
|
||||
noSelect
|
||||
fontWeight={600}
|
||||
fontSize={"14px"}
|
||||
className="payment-info_managers-price"
|
||||
>
|
||||
<Trans t={t} i18nKey="PerUserMonth" ns="Common">
|
||||
From {{ currencySymbol }}
|
||||
{{ price: startValue }} per admin/power user /month
|
||||
</Trans>
|
||||
</Text>
|
||||
|
||||
{isPaidPeriod && !isFreeTariff && (
|
||||
<Text
|
||||
noSelect
|
||||
fontSize={"14px"}
|
||||
lineHeight={"16px"}
|
||||
className="payment-info_managers-price"
|
||||
>
|
||||
<Trans t={t} i18nKey="BusinessFinalDateInfo" ns="Payments">
|
||||
{{ finalDate: paymentDate }}
|
||||
</Trans>
|
||||
</Text>
|
||||
)}
|
||||
{renderTooltip()}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!isGracePeriod && !isNotPaidPeriod && !isFreeAfterPaidPeriod && (
|
||||
<div className="payment-info_wrapper">
|
||||
<Text
|
||||
noSelect
|
||||
fontWeight={600}
|
||||
fontSize={"14px"}
|
||||
className="payment-info_managers-price"
|
||||
>
|
||||
<Trans t={t} i18nKey="PerUserMonth" ns="Common">
|
||||
From {{ currencySymbol }}
|
||||
{{ price: startValue }} per admin/power user /month
|
||||
</Trans>
|
||||
</Text>
|
||||
|
||||
{renderTooltip()}
|
||||
</div>
|
||||
)}
|
||||
<div className="payment-info">
|
||||
<PriceCalculation
|
||||
t={t}
|
||||
isPayer={isPayer}
|
||||
isFreeAfterPaidPeriod={isFreeAfterPaidPeriod}
|
||||
/>
|
||||
{!isNonProfit && (
|
||||
<PriceCalculation
|
||||
t={t}
|
||||
isPayer={isPayer}
|
||||
isFreeAfterPaidPeriod={isFreeAfterPaidPeriod}
|
||||
/>
|
||||
)}
|
||||
|
||||
<BenefitsContainer t={t} />
|
||||
</div>
|
||||
@ -327,7 +344,9 @@ export default inject(({ auth, payments }) => {
|
||||
} = auth;
|
||||
const { showText: expandArticle } = settingsStore;
|
||||
|
||||
const { isFreeTariff, currentTariffPlanTitle } = currentQuotaStore;
|
||||
const { isFreeTariff, currentTariffPlanTitle, isNonProfit } =
|
||||
currentQuotaStore;
|
||||
|
||||
const {
|
||||
isNotPaidPeriod,
|
||||
isPaidPeriod,
|
||||
@ -372,5 +391,6 @@ export default inject(({ auth, payments }) => {
|
||||
currentTariffPlanTitle,
|
||||
portalTariffStatus,
|
||||
portalPaymentQuotas,
|
||||
isNonProfit,
|
||||
};
|
||||
})(withRouter(observer(PaymentContainer)));
|
||||
|
@ -35,7 +35,7 @@ class HotkeyStore {
|
||||
scrollToCaret = () => {
|
||||
const { offsetTop, item } = this.getItemOffset();
|
||||
const scroll = document.getElementsByClassName("section-scroll")[0];
|
||||
const scrollRect = scroll.getBoundingClientRect();
|
||||
const scrollRect = scroll?.getBoundingClientRect();
|
||||
|
||||
if (item && item[0]) {
|
||||
const el = item[0];
|
||||
|
@ -3,8 +3,10 @@ import HotkeysReactSvgUrl from "PUBLIC_DIR/images/hotkeys.react.svg?url";
|
||||
import ProfileReactSvgUrl from "PUBLIC_DIR/images/profile.react.svg?url";
|
||||
import PaymentsReactSvgUrl from "PUBLIC_DIR/images/payments.react.svg?url";
|
||||
import HelpCenterReactSvgUrl from "PUBLIC_DIR/images/help.center.react.svg?url";
|
||||
import SupportReactSvgUrl from "PUBLIC_DIR/images/support.react.svg?url";
|
||||
import VideoGuidesReactSvgUrl from "PUBLIC_DIR/images/video.guides.react.svg?url";
|
||||
import EmailReactSvgUrl from "PUBLIC_DIR/images/email.react.svg?url";
|
||||
import LiveChatReactSvgUrl from "PUBLIC_DIR/images/support.react.svg?url";
|
||||
import BookTrainingReactSvgUrl from "PUBLIC_DIR/images/book.training.react.svg?url";
|
||||
//import VideoGuidesReactSvgUrl from "PUBLIC_DIR/images/video.guides.react.svg?url";
|
||||
import InfoOutlineReactSvgUrl from "PUBLIC_DIR/images/info.outline.react.svg?url";
|
||||
import LogoutReactSvgUrl from "PUBLIC_DIR/images/logout.react.svg?url";
|
||||
import { makeAutoObservable } from "mobx";
|
||||
@ -93,6 +95,10 @@ class ProfileActionsStore {
|
||||
window.open(helpUrl, "_blank");
|
||||
};
|
||||
|
||||
onLiveChatClick = () => {
|
||||
//window.open(supportUrl, "_blank");
|
||||
};
|
||||
|
||||
onSupportClick = () => {
|
||||
const supportUrl = this.authStore.settingsStore.additionalResourcesData
|
||||
?.feedbackAndSupportUrl;
|
||||
@ -100,6 +106,13 @@ class ProfileActionsStore {
|
||||
window.open(supportUrl, "_blank");
|
||||
};
|
||||
|
||||
onBookTraining = () => {
|
||||
const trainingEmail = this.authStore.settingsStore.additionalResourcesData
|
||||
?.trainingEmail;
|
||||
|
||||
trainingEmail && window.open(`mailto:${trainingEmail}`, "_blank");
|
||||
};
|
||||
|
||||
// onVideoGuidesClick = () => {
|
||||
// window.open(VIDEO_GUIDES_URL, "_blank");
|
||||
// };
|
||||
@ -173,6 +186,29 @@ class ProfileActionsStore {
|
||||
};
|
||||
}
|
||||
// }
|
||||
|
||||
let liveChat = null;
|
||||
|
||||
if (!isMobile) {
|
||||
liveChat = {
|
||||
key: "user-menu-live-chat",
|
||||
icon: LiveChatReactSvgUrl,
|
||||
label: t("Common:LiveChat"),
|
||||
onClick: this.onLiveChatClick,
|
||||
};
|
||||
}
|
||||
|
||||
let bookTraining = null;
|
||||
|
||||
if (!isMobile) {
|
||||
bookTraining = {
|
||||
key: "user-menu-book-training",
|
||||
icon: BookTrainingReactSvgUrl,
|
||||
label: t("Common:BookTraining"),
|
||||
onClick: this.onBookTraining,
|
||||
};
|
||||
}
|
||||
|
||||
const actions = [
|
||||
{
|
||||
key: "user-menu-profile",
|
||||
@ -187,18 +223,16 @@ class ProfileActionsStore {
|
||||
label: t("Common:PaymentsTitle"),
|
||||
onClick: this.onPaymentsClick,
|
||||
},
|
||||
{
|
||||
isSeparator: true,
|
||||
key: "separator1",
|
||||
},
|
||||
{
|
||||
key: "user-menu-help-center",
|
||||
icon: HelpCenterReactSvgUrl,
|
||||
label: t("Common:HelpCenter"),
|
||||
onClick: this.onHelpCenterClick,
|
||||
},
|
||||
{
|
||||
key: "user-menu-support",
|
||||
icon: SupportReactSvgUrl,
|
||||
label: t("Common:FeedbackAndSupport"),
|
||||
onClick: this.onSupportClick,
|
||||
},
|
||||
// {
|
||||
// key: "user-menu-video",
|
||||
// icon: VideoGuidesReactSvgUrl,
|
||||
@ -206,6 +240,18 @@ class ProfileActionsStore {
|
||||
// onClick: this.onVideoGuidesClick,
|
||||
// },
|
||||
hotkeys,
|
||||
{
|
||||
isSeparator: true,
|
||||
key: "separator2",
|
||||
},
|
||||
liveChat,
|
||||
{
|
||||
key: "user-menu-support",
|
||||
icon: EmailReactSvgUrl,
|
||||
label: t("Common:FeedbackAndSupport"),
|
||||
onClick: this.onSupportClick,
|
||||
},
|
||||
bookTraining,
|
||||
{
|
||||
key: "user-menu-about",
|
||||
icon: InfoOutlineReactSvgUrl,
|
||||
@ -214,7 +260,7 @@ class ProfileActionsStore {
|
||||
},
|
||||
{
|
||||
isSeparator: true,
|
||||
key: "separator",
|
||||
key: "separator3",
|
||||
},
|
||||
{
|
||||
key: "user-menu-logout",
|
||||
@ -226,7 +272,7 @@ class ProfileActionsStore {
|
||||
];
|
||||
|
||||
if (debugInfo) {
|
||||
actions.splice(3, 0, {
|
||||
actions.splice(4, 0, {
|
||||
key: "user-menu-debug",
|
||||
icon: InfoOutlineReactSvgUrl,
|
||||
label: "Debug Info",
|
||||
|
23
packages/common/components/AlertComponent/StyledComponent.js
Normal file
23
packages/common/components/AlertComponent/StyledComponent.js
Normal file
@ -0,0 +1,23 @@
|
||||
import styled, { css } from "styled-components";
|
||||
|
||||
const StyledAlertComponent = styled.div`
|
||||
position: relative;
|
||||
border: ${(props) => `1px solid ${props.borderColor}`};
|
||||
border-radius: 6px;
|
||||
margin: 32px 0px;
|
||||
padding: 12px;
|
||||
${(props) => !!props.onClick && "cursor:pointer"};
|
||||
display: grid;
|
||||
|
||||
grid-template-columns: ${(props) =>
|
||||
props.needArrowIcon ? "1fr 16px" : "1fr"};
|
||||
|
||||
.alert-component_title {
|
||||
color: ${(props) => props.titleColor};
|
||||
}
|
||||
.alert-component_icons {
|
||||
margin: auto 0;
|
||||
}
|
||||
`;
|
||||
|
||||
export { StyledAlertComponent };
|
106
packages/common/components/AlertComponent/index.js
Normal file
106
packages/common/components/AlertComponent/index.js
Normal file
@ -0,0 +1,106 @@
|
||||
import React from "react";
|
||||
import { inject, observer } from "mobx-react";
|
||||
import { withRouter } from "react-router";
|
||||
import styled from "styled-components";
|
||||
|
||||
import Text from "@docspace/components/text";
|
||||
import commonIconsStyles from "@docspace/components/utils/common-icons-style";
|
||||
|
||||
import ArrowRightIcon from "PUBLIC_DIR/images/arrow.right.react.svg";
|
||||
import CrossReactSvg from "PUBLIC_DIR/images/cross.react.svg";
|
||||
|
||||
import Loaders from "../Loaders";
|
||||
import { StyledAlertComponent } from "./StyledComponent";
|
||||
import Link from "@docspace/components/link";
|
||||
|
||||
const StyledArrowRightIcon = styled(ArrowRightIcon)`
|
||||
margin: auto 0;
|
||||
path {
|
||||
fill: ${(props) => props.theme.alertComponent.iconColor};
|
||||
}
|
||||
`;
|
||||
const StyledCrossIcon = styled(CrossReactSvg)`
|
||||
position: absolute;
|
||||
right: 0px;
|
||||
margin-right: 8px;
|
||||
margin-top: 8px;
|
||||
cursor: pointer;
|
||||
|
||||
${commonIconsStyles}
|
||||
path {
|
||||
fill: ${(props) => props.color};
|
||||
}
|
||||
`;
|
||||
|
||||
const AlertComponent = (props) => {
|
||||
const {
|
||||
id,
|
||||
description,
|
||||
title,
|
||||
titleFontSize,
|
||||
additionalDescription,
|
||||
needArrowIcon = false,
|
||||
needCloseIcon = false,
|
||||
link,
|
||||
linkColor,
|
||||
linkTitle,
|
||||
onAlertClick,
|
||||
onCloseClick,
|
||||
titleColor,
|
||||
borderColor,
|
||||
theme,
|
||||
} = props;
|
||||
return (
|
||||
<StyledAlertComponent
|
||||
theme={theme}
|
||||
titleColor={titleColor}
|
||||
borderColor={borderColor}
|
||||
onClick={onAlertClick}
|
||||
needArrowIcon={needArrowIcon}
|
||||
id={id}
|
||||
>
|
||||
<div>
|
||||
<Text
|
||||
className="alert-component_title"
|
||||
fontSize={titleFontSize ?? "12px"}
|
||||
fontWeight={600}
|
||||
>
|
||||
{title}
|
||||
</Text>
|
||||
{additionalDescription && (
|
||||
<Text fontWeight={600}>{additionalDescription}</Text>
|
||||
)}
|
||||
<Text
|
||||
noSelect
|
||||
fontSize="12px"
|
||||
color={theme.alertComponent.descriptionColor}
|
||||
>
|
||||
{description}
|
||||
</Text>
|
||||
{link && (
|
||||
<Link type="page" href={link} noHover color={linkColor}>
|
||||
{linkTitle}
|
||||
</Link>
|
||||
)}
|
||||
</div>
|
||||
{needCloseIcon && (
|
||||
<StyledCrossIcon size="extraSmall" onClick={onCloseClick} />
|
||||
)}
|
||||
{needArrowIcon && (
|
||||
<StyledArrowRightIcon className="alert-component_arrow" />
|
||||
)}
|
||||
</StyledAlertComponent>
|
||||
);
|
||||
};
|
||||
|
||||
export default withRouter(
|
||||
inject(({ auth }) => {
|
||||
const { settingsStore } = auth;
|
||||
|
||||
const { theme } = settingsStore;
|
||||
|
||||
return {
|
||||
theme,
|
||||
};
|
||||
})(observer(AlertComponent))
|
||||
);
|
@ -15,6 +15,7 @@ import SubArticleMainButton from "./sub-components/article-main-button";
|
||||
import SubArticleBody from "./sub-components/article-body";
|
||||
import ArticleProfile from "./sub-components/article-profile";
|
||||
import ArticlePaymentAlert from "./sub-components/article-payment-alert";
|
||||
import ArticleTeamTrainingAlert from "./sub-components/article-team-training";
|
||||
import { StyledArticle } from "./styled-article";
|
||||
import HideArticleMenuButton from "./sub-components/article-hide-menu-button";
|
||||
import Portal from "@docspace/components/portal";
|
||||
@ -40,6 +41,8 @@ const Article = ({
|
||||
withSendAgain,
|
||||
mainBarVisible,
|
||||
isBannerVisible,
|
||||
isNonProfit,
|
||||
isTeamTrainingAlertAvailable,
|
||||
...rest
|
||||
}) => {
|
||||
const [articleHeaderContent, setArticleHeaderContent] = React.useState(null);
|
||||
@ -178,6 +181,7 @@ const Article = ({
|
||||
<ArticleProfile showText={showText} />
|
||||
)}
|
||||
{isPaymentPageAvailable &&
|
||||
!isNonProfit &&
|
||||
(isFreeTariff || isGracePeriod) &&
|
||||
showText && (
|
||||
<ArticlePaymentAlert
|
||||
@ -185,6 +189,9 @@ const Article = ({
|
||||
toggleArticleOpen={toggleArticleOpen}
|
||||
/>
|
||||
)}
|
||||
{isTeamTrainingAlertAvailable && showText && (
|
||||
<ArticleTeamTrainingAlert />
|
||||
)}
|
||||
</SubArticleBody>
|
||||
</StyledArticle>
|
||||
{articleOpen && (isMobileOnly || window.innerWidth <= 375) && (
|
||||
@ -247,9 +254,10 @@ export default inject(({ auth }) => {
|
||||
currentTariffStatusStore,
|
||||
userStore,
|
||||
isPaymentPageAvailable,
|
||||
isTeamTrainingAlertAvailable,
|
||||
bannerStore,
|
||||
} = auth;
|
||||
const { isFreeTariff } = currentQuotaStore;
|
||||
const { isFreeTariff, isNonProfit } = currentQuotaStore;
|
||||
const { isGracePeriod } = currentTariffStatusStore;
|
||||
|
||||
const { withSendAgain } = userStore;
|
||||
@ -283,5 +291,7 @@ export default inject(({ auth }) => {
|
||||
withSendAgain,
|
||||
mainBarVisible,
|
||||
isBannerVisible,
|
||||
isNonProfit,
|
||||
isTeamTrainingAlertAvailable,
|
||||
};
|
||||
})(observer(Article));
|
||||
|
@ -2,20 +2,11 @@ import React, { useEffect } from "react";
|
||||
import { inject, observer } from "mobx-react";
|
||||
import { withRouter } from "react-router";
|
||||
import { useTranslation, Trans } from "react-i18next";
|
||||
import Text from "@docspace/components/text";
|
||||
import ArrowRightIcon from "PUBLIC_DIR/images/arrow.right.react.svg";
|
||||
import { StyledArticlePaymentAlert } from "../styled-article";
|
||||
import styled from "styled-components";
|
||||
|
||||
import { combineUrl } from "@docspace/common/utils";
|
||||
import history from "@docspace/common/history";
|
||||
import Loaders from "../../Loaders";
|
||||
|
||||
const StyledArrowRightIcon = styled(ArrowRightIcon)`
|
||||
margin: auto 0;
|
||||
path {
|
||||
fill: ${(props) => props.color};
|
||||
}
|
||||
`;
|
||||
import AlertComponent from "../../AlertComponent";
|
||||
|
||||
const PROXY_BASE_URL = combineUrl(
|
||||
window.DocSpaceConfig?.proxy?.url,
|
||||
@ -52,52 +43,47 @@ const ArticlePaymentAlert = ({
|
||||
toggleArticleOpen();
|
||||
};
|
||||
|
||||
const title = isFreeTariff ? (
|
||||
<Trans t={t} i18nKey="FreeStartupPlan" ns="Common">
|
||||
{{ planName: currentTariffPlanTitle }}
|
||||
</Trans>
|
||||
) : (
|
||||
t("Common:LatePayment")
|
||||
);
|
||||
|
||||
const description = isFreeTariff
|
||||
? pricePerManager && (
|
||||
<Trans t={t} i18nKey="PerUserMonth" ns="Common">
|
||||
From {{ currencySymbol }}
|
||||
{{ price: pricePerManager }} per admin/power user /month
|
||||
</Trans>
|
||||
)
|
||||
: t("Common:PayBeforeTheEndGracePeriod");
|
||||
|
||||
const additionalDescription = isFreeTariff
|
||||
? t("Common:ActivateBusinessPlan", { planName: tariffPlanTitle })
|
||||
: t("Common:GracePeriodActivated");
|
||||
|
||||
const color = isFreeTariff
|
||||
? theme.catalog.paymentAlert.color
|
||||
: theme.catalog.paymentAlert.warningColor;
|
||||
|
||||
const isShowLoader = !ready;
|
||||
|
||||
return isShowLoader ? (
|
||||
<Loaders.Rectangle width="210px" height="88px" />
|
||||
) : (
|
||||
<StyledArticlePaymentAlert
|
||||
onClick={onClick}
|
||||
isFreeTariff={isFreeTariff}
|
||||
theme={theme}
|
||||
<AlertComponent
|
||||
id="document_catalog-payment-alert"
|
||||
>
|
||||
<div>
|
||||
<Text className="article-payment_border">
|
||||
{isFreeTariff ? (
|
||||
<Trans t={t} i18nKey="FreeStartupPlan" ns="Common">
|
||||
{{ planName: currentTariffPlanTitle }}
|
||||
</Trans>
|
||||
) : (
|
||||
t("Common:LatePayment")
|
||||
)}
|
||||
</Text>
|
||||
<Text fontWeight={600}>
|
||||
{isFreeTariff
|
||||
? t("Common:ActivateBusinessPlan", { planName: tariffPlanTitle })
|
||||
: t("Common:GracePeriodActivated")}
|
||||
</Text>
|
||||
<Text noSelect fontSize={"12px"}>
|
||||
{isFreeTariff ? (
|
||||
<>
|
||||
{pricePerManager ? (
|
||||
<Trans t={t} i18nKey="PerUserMonth" ns="Common">
|
||||
From {{ currencySymbol }}
|
||||
{{ price: pricePerManager }} per admin/power user /month
|
||||
</Trans>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
t("Common:PayBeforeTheEndGracePeriod")
|
||||
)}
|
||||
</Text>
|
||||
</div>
|
||||
|
||||
<StyledArrowRightIcon />
|
||||
</StyledArticlePaymentAlert>
|
||||
borderColor={color}
|
||||
titleColor={color}
|
||||
onAlertClick={onClick}
|
||||
title={title}
|
||||
titleFontSize="11px"
|
||||
description={description}
|
||||
additionalDescription={additionalDescription}
|
||||
needArrowIcon
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@ -105,7 +91,7 @@ export default withRouter(
|
||||
inject(({ auth }) => {
|
||||
const { paymentQuotasStore, currentQuotaStore, settingsStore } = auth;
|
||||
const { currentTariffPlanTitle } = currentQuotaStore;
|
||||
const { theme } = auth;
|
||||
const { theme } = settingsStore;
|
||||
const {
|
||||
setPortalPaymentQuotas,
|
||||
planCost,
|
||||
|
@ -0,0 +1,56 @@
|
||||
import React, { useState } from "react";
|
||||
import { inject, observer } from "mobx-react";
|
||||
import { withRouter } from "react-router";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import AlertComponent from "../../AlertComponent";
|
||||
|
||||
const ArticleTeamTrainingAlert = ({
|
||||
theme,
|
||||
trainingEmail,
|
||||
organizationName,
|
||||
}) => {
|
||||
const { t, ready } = useTranslation("Common");
|
||||
const [isClose, setIsClose] = useState(
|
||||
localStorage.getItem("teamTrainingAlertClose")
|
||||
);
|
||||
|
||||
const onClick = () => {
|
||||
localStorage.setItem("teamTrainingAlertClose", true);
|
||||
setIsClose(true);
|
||||
};
|
||||
|
||||
if (isClose) return <></>;
|
||||
|
||||
const isShowLoader = !ready;
|
||||
|
||||
return isShowLoader ? (
|
||||
<Loaders.Rectangle width="210px" height="88px" />
|
||||
) : (
|
||||
<AlertComponent
|
||||
titleColor={theme.catalog.teamTrainingAlert.titleColor}
|
||||
linkColor={theme.catalog.teamTrainingAlert.linkColor}
|
||||
borderColor={theme.catalog.teamTrainingAlert.borderColor}
|
||||
title={t("Common:UseLikePro", { organizationName })}
|
||||
description={t("Common:BookTeamTraining")}
|
||||
link={`mailto:${trainingEmail}`}
|
||||
linkTitle={t("Common:BookNow")}
|
||||
onCloseClick={onClick}
|
||||
needCloseIcon
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default withRouter(
|
||||
inject(({ auth }) => {
|
||||
const { settingsStore } = auth;
|
||||
|
||||
const { theme, additionalResourcesData, organizationName } = settingsStore;
|
||||
|
||||
return {
|
||||
theme,
|
||||
trainingEmail: additionalResourcesData.trainingEmail,
|
||||
organizationName,
|
||||
};
|
||||
})(observer(ArticleTeamTrainingAlert))
|
||||
);
|
@ -130,7 +130,9 @@ class AuthStore {
|
||||
|
||||
if (!user) return false;
|
||||
|
||||
return !user.isAdmin && !user.isOwner && !user.isVisitor;
|
||||
return (
|
||||
!user.isAdmin && !user.isOwner && !user.isVisitor && !user.isCollaborator
|
||||
);
|
||||
}
|
||||
|
||||
get isPaymentPageAvailable() {
|
||||
@ -141,6 +143,14 @@ class AuthStore {
|
||||
return user.isOwner || user.isAdmin;
|
||||
}
|
||||
|
||||
get isTeamTrainingAlertAvailable() {
|
||||
const { user } = this.userStore;
|
||||
|
||||
if (!user) return false;
|
||||
|
||||
return user.isOwner || user.isAdmin || this.isRoomAdmin;
|
||||
}
|
||||
|
||||
login = async (user, hash, session = true) => {
|
||||
try {
|
||||
const response = await api.user.login(user, hash, session);
|
||||
|
@ -207,6 +207,10 @@ class QuotasStore {
|
||||
);
|
||||
}
|
||||
|
||||
get isNonProfit() {
|
||||
return this.currentPortalQuota?.nonProfit;
|
||||
}
|
||||
|
||||
setPortalQuotaValue = (res) => {
|
||||
this.currentPortalQuota = res;
|
||||
this.currentPortalQuotaFeatures = res.features;
|
||||
|
@ -389,6 +389,9 @@ class SettingsStore {
|
||||
|
||||
setAdditionalResourcesData = (data) => {
|
||||
this.additionalResourcesData = data;
|
||||
if (!this.additionalResourcesData?.trainingEmail) {
|
||||
this.additionalResourcesData.trainingEmail = "training@onlyoffice.com";
|
||||
}
|
||||
};
|
||||
|
||||
setAdditionalResourcesIsDefault = (additionalResourcesIsDefault) => {
|
||||
|
@ -1912,9 +1912,18 @@ const Base = {
|
||||
paymentAlert: {
|
||||
color: "#ed7309",
|
||||
warningColor: "#F21C0E",
|
||||
border: "1px solid #ed7309",
|
||||
warningBorder: "1px solid #F21C0E",
|
||||
},
|
||||
|
||||
teamTrainingAlert: {
|
||||
titleColor: "#388BDE",
|
||||
borderColor: "#388BDE",
|
||||
linkColor: "#5299E0",
|
||||
},
|
||||
},
|
||||
|
||||
alertComponent: {
|
||||
descriptionColor: "#555F65",
|
||||
iconColor: "#657077",
|
||||
},
|
||||
|
||||
catalogItem: {
|
||||
|
@ -1903,9 +1903,18 @@ const Dark = {
|
||||
paymentAlert: {
|
||||
color: "#ed7309",
|
||||
warningColor: "#E06451",
|
||||
border: "1px solid #ed7309",
|
||||
warningBorder: "1px solid #E06451",
|
||||
},
|
||||
|
||||
teamTrainingAlert: {
|
||||
titleColor: "#FFFFFF",
|
||||
borderColor: "#388BDE",
|
||||
linkColor: "#5299E0",
|
||||
},
|
||||
},
|
||||
|
||||
alertComponent: {
|
||||
descriptionColor: "#ADADAD",
|
||||
iconColor: "#ADADAD",
|
||||
},
|
||||
|
||||
catalogItem: {
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { css } from "styled-components";
|
||||
|
||||
const iconSizes = {
|
||||
extraSmall: 8,
|
||||
small: 12,
|
||||
medium: 16,
|
||||
big: 24,
|
||||
@ -14,6 +15,7 @@ const getSizeStyle = (size) => {
|
||||
height: 100%;
|
||||
}
|
||||
`;
|
||||
case "extraSmall":
|
||||
case "small":
|
||||
case "medium":
|
||||
case "big":
|
||||
|
@ -60,10 +60,17 @@ public class CountRoomCheckerStatistic : ITenantQuotaFeatureStat<CountRoomFeatur
|
||||
|
||||
public async Task<int> GetValue()
|
||||
{
|
||||
var daoFactory = _serviceProvider.GetService<IDaoFactory>();
|
||||
var folderDao = _serviceProvider.GetService<IFolderDao<int>>();
|
||||
var globalFolderHelper = _serviceProvider.GetService<GlobalFolderHelper>();
|
||||
var globalFolder = _serviceProvider.GetService<GlobalFolder>();
|
||||
|
||||
var parentId = await globalFolderHelper.GetFolderVirtualRooms<int>();
|
||||
var parentId = await globalFolder.GetFolderVirtualRoomsAsync(daoFactory, false);
|
||||
|
||||
if (parentId == default)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return await folderDao.GetFoldersAsync(parentId).CountAsync();
|
||||
}
|
||||
}
|
||||
|
@ -31,7 +31,7 @@ public class GlobalNotify
|
||||
{
|
||||
public ILogger Logger { get; set; }
|
||||
private readonly ICacheNotify<AscCacheItem> _notify;
|
||||
|
||||
|
||||
public GlobalNotify(ICacheNotify<AscCacheItem> notify, ILoggerProvider options, CoreBaseSettings coreBaseSettings)
|
||||
{
|
||||
_notify = notify;
|
||||
@ -41,7 +41,7 @@ public class GlobalNotify
|
||||
ClearCache();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void ClearCache()
|
||||
{
|
||||
try
|
||||
@ -105,7 +105,7 @@ public class Global
|
||||
private readonly DisplayUserSettingsHelper _displayUserSettingsHelper;
|
||||
private readonly CustomNamingPeople _customNamingPeople;
|
||||
private readonly FileSecurityCommon _fileSecurityCommon;
|
||||
|
||||
|
||||
public Global(
|
||||
IConfiguration configuration,
|
||||
AuthContext authContext,
|
||||
@ -133,17 +133,17 @@ public class Global
|
||||
}
|
||||
}
|
||||
|
||||
#region Property
|
||||
#region Property
|
||||
|
||||
public DocThumbnailExtension DocThumbnailExtension;
|
||||
public ThumbnailExtension ThumbnailExtension;
|
||||
|
||||
|
||||
public const int MaxTitle = 170;
|
||||
|
||||
|
||||
public static readonly Regex InvalidTitleChars = new Regex("[\t*\\+:\"<>?|\\\\/\\p{Cs}]");
|
||||
|
||||
|
||||
public bool EnableUploadFilter => bool.TrueString.Equals(_configuration["files:upload-filter"] ?? "false", StringComparison.InvariantCultureIgnoreCase);
|
||||
|
||||
|
||||
public TimeSpan StreamUrlExpire
|
||||
{
|
||||
get
|
||||
@ -157,27 +157,27 @@ public class Global
|
||||
return TimeSpan.FromMinutes(validateTimespan);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public bool IsDocSpaceAdministrator => _fileSecurityCommon.IsDocSpaceAdministrator(_authContext.CurrentAccount.ID);
|
||||
|
||||
|
||||
public string GetDocDbKey()
|
||||
{
|
||||
const string dbKey = "UniqueDocument";
|
||||
var resultKey = _coreSettings.GetSetting(dbKey);
|
||||
|
||||
|
||||
if (!string.IsNullOrEmpty(resultKey))
|
||||
{
|
||||
return resultKey;
|
||||
}
|
||||
|
||||
|
||||
resultKey = Guid.NewGuid().ToString();
|
||||
_coreSettings.SaveSetting(dbKey, resultKey);
|
||||
|
||||
|
||||
return resultKey;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
public static string ReplaceInvalidCharsAndTruncate(string title)
|
||||
{
|
||||
if (string.IsNullOrEmpty(title))
|
||||
@ -201,27 +201,27 @@ public class Global
|
||||
|
||||
return InvalidTitleChars.Replace(title, "_");
|
||||
}
|
||||
|
||||
|
||||
public string GetUserName(Guid userId, bool alive = false)
|
||||
{
|
||||
if (userId.Equals(_authContext.CurrentAccount.ID))
|
||||
{
|
||||
return FilesCommonResource.Author_Me;
|
||||
}
|
||||
|
||||
|
||||
if (userId.Equals(ASC.Core.Configuration.Constants.Guest.ID))
|
||||
{
|
||||
return FilesCommonResource.Guest;
|
||||
}
|
||||
|
||||
|
||||
var userInfo = _userManager.GetUsers(userId);
|
||||
if (userInfo.Equals(Constants.LostUser))
|
||||
{
|
||||
return alive ? FilesCommonResource.Guest : _customNamingPeople.Substitute<FilesCommonResource>("ProfileRemoved");
|
||||
}
|
||||
}
|
||||
|
||||
return userInfo.DisplayUserName(false, _displayUserSettingsHelper);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Scope]
|
||||
@ -229,18 +229,18 @@ public class GlobalStore
|
||||
{
|
||||
private readonly StorageFactory _storageFactory;
|
||||
private readonly TenantManager _tenantManager;
|
||||
|
||||
|
||||
public GlobalStore(StorageFactory storageFactory, TenantManager tenantManager)
|
||||
{
|
||||
_storageFactory = storageFactory;
|
||||
_tenantManager = tenantManager;
|
||||
}
|
||||
|
||||
|
||||
public IDataStore GetStore(bool currentTenant = true)
|
||||
{
|
||||
return _storageFactory.GetStorage(currentTenant ? _tenantManager.GetCurrentTenant().Id : null, FileConstant.StorageModule);
|
||||
}
|
||||
|
||||
|
||||
public IDataStore GetStoreTemplate()
|
||||
{
|
||||
return _storageFactory.GetStorage(null, FileConstant.StorageTemplate);
|
||||
@ -252,18 +252,18 @@ public class GlobalSpace
|
||||
{
|
||||
private readonly FilesSpaceUsageStatManager _filesSpaceUsageStatManager;
|
||||
private readonly AuthContext _authContext;
|
||||
|
||||
|
||||
public GlobalSpace(FilesSpaceUsageStatManager filesSpaceUsageStatManager, AuthContext authContext)
|
||||
{
|
||||
_filesSpaceUsageStatManager = filesSpaceUsageStatManager;
|
||||
_authContext = authContext;
|
||||
}
|
||||
|
||||
|
||||
public Task<long> GetUserUsedSpaceAsync()
|
||||
{
|
||||
return GetUserUsedSpaceAsync(_authContext.CurrentAccount.ID);
|
||||
}
|
||||
|
||||
|
||||
public Task<long> GetUserUsedSpaceAsync(Guid userId)
|
||||
{
|
||||
return _filesSpaceUsageStatManager.GetUserSpaceUsageAsync(userId);
|
||||
@ -283,7 +283,7 @@ public class GlobalFolder
|
||||
private readonly GlobalStore _globalStore;
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
|
||||
public GlobalFolder(
|
||||
CoreBaseSettings coreBaseSettings,
|
||||
WebItemManager webItemManager,
|
||||
@ -294,56 +294,56 @@ public class GlobalFolder
|
||||
SettingsManager settingsManager,
|
||||
GlobalStore globalStore,
|
||||
ILoggerProvider options,
|
||||
IServiceProvider serviceProvider
|
||||
IServiceProvider serviceProvider
|
||||
)
|
||||
{
|
||||
_coreBaseSettings = coreBaseSettings;
|
||||
_webItemManager = webItemManager;
|
||||
_webItemSecurity = webItemSecurity;
|
||||
_authContext = authContext;
|
||||
_tenantManager = tenantManager;
|
||||
_userManager = userManager;
|
||||
_settingsManager = settingsManager;
|
||||
_globalStore = globalStore;
|
||||
_serviceProvider = serviceProvider;
|
||||
_coreBaseSettings = coreBaseSettings;
|
||||
_webItemManager = webItemManager;
|
||||
_webItemSecurity = webItemSecurity;
|
||||
_authContext = authContext;
|
||||
_tenantManager = tenantManager;
|
||||
_userManager = userManager;
|
||||
_settingsManager = settingsManager;
|
||||
_globalStore = globalStore;
|
||||
_serviceProvider = serviceProvider;
|
||||
_logger = options.CreateLogger("ASC.Files");
|
||||
}
|
||||
|
||||
|
||||
internal static readonly IDictionary<int, int> ProjectsRootFolderCache =
|
||||
new ConcurrentDictionary<int, int>(); /*Use SYNCHRONIZED for cross thread blocks*/
|
||||
|
||||
|
||||
public async ValueTask<int> GetFolderProjectsAsync(IDaoFactory daoFactory)
|
||||
{
|
||||
if (_coreBaseSettings.Personal)
|
||||
{
|
||||
return default;
|
||||
}
|
||||
|
||||
|
||||
if (_webItemManager[WebItemManager.ProjectsProductID].IsDisabled(_webItemSecurity, _authContext))
|
||||
{
|
||||
return default;
|
||||
}
|
||||
|
||||
|
||||
var folderDao = daoFactory.GetFolderDao<int>();
|
||||
if (!ProjectsRootFolderCache.TryGetValue(_tenantManager.GetCurrentTenant().Id, out var result))
|
||||
{
|
||||
result = await folderDao.GetFolderIDProjectsAsync(true);
|
||||
|
||||
|
||||
ProjectsRootFolderCache[_tenantManager.GetCurrentTenant().Id] = result;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
public async ValueTask<T> GetFolderProjectsAsync<T>(IDaoFactory daoFactory)
|
||||
{
|
||||
return (T)Convert.ChangeType(await GetFolderProjectsAsync(daoFactory), typeof(T));
|
||||
}
|
||||
|
||||
|
||||
internal static readonly ConcurrentDictionary<string, int> DocSpaceFolderCache =
|
||||
new ConcurrentDictionary<string, int>();
|
||||
|
||||
public async ValueTask<int> GetFolderVirtualRoomsAsync(IDaoFactory daoFactory)
|
||||
public async ValueTask<int> GetFolderVirtualRoomsAsync(IDaoFactory daoFactory, bool createIfNotExist = true)
|
||||
{
|
||||
if (_coreBaseSettings.DisableDocSpace)
|
||||
{
|
||||
@ -352,10 +352,15 @@ public class GlobalFolder
|
||||
|
||||
var key = $"vrooms/{_tenantManager.GetCurrentTenant().Id}";
|
||||
|
||||
if (!DocSpaceFolderCache.TryGetValue(key, out var result))
|
||||
if (DocSpaceFolderCache.TryGetValue(key, out var result))
|
||||
{
|
||||
result = await daoFactory.GetFolderDao<int>().GetFolderIDVirtualRooms(true);
|
||||
return result;
|
||||
}
|
||||
|
||||
result = await daoFactory.GetFolderDao<int>().GetFolderIDVirtualRooms(createIfNotExist);
|
||||
|
||||
if (result != default)
|
||||
{
|
||||
DocSpaceFolderCache[key] = result;
|
||||
}
|
||||
|
||||
@ -390,22 +395,22 @@ public class GlobalFolder
|
||||
{
|
||||
return (T)Convert.ChangeType(await GetFolderArchiveAsync(daoFactory), typeof(T));
|
||||
}
|
||||
|
||||
|
||||
internal static readonly ConcurrentDictionary<string, Lazy<int>> UserRootFolderCache =
|
||||
new ConcurrentDictionary<string, Lazy<int>>(); /*Use SYNCHRONIZED for cross thread blocks*/
|
||||
|
||||
|
||||
public T GetFolderMy<T>(FileMarker fileMarker, IDaoFactory daoFactory)
|
||||
{
|
||||
return (T)Convert.ChangeType(GetFolderMy(fileMarker, daoFactory), typeof(T));
|
||||
}
|
||||
|
||||
|
||||
public int GetFolderMy(FileMarker fileMarker, IDaoFactory daoFactory)
|
||||
{
|
||||
if (!_authContext.IsAuthenticated)
|
||||
{
|
||||
return default;
|
||||
}
|
||||
|
||||
|
||||
if (_userManager.IsUser(_authContext.CurrentAccount.ID))
|
||||
{
|
||||
return default;
|
||||
@ -417,8 +422,8 @@ public class GlobalFolder
|
||||
|
||||
return myFolderId.Value;
|
||||
}
|
||||
|
||||
protected internal void SetFolderMy(object value)
|
||||
|
||||
protected internal void SetFolderMy(object value)
|
||||
{
|
||||
var cacheKey = string.Format("my/{0}/{1}", _tenantManager.GetCurrentTenant().Id, value);
|
||||
UserRootFolderCache.Remove(cacheKey, out _);
|
||||
@ -441,23 +446,23 @@ public class GlobalFolder
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
internal static readonly IDictionary<int, int> CommonFolderCache =
|
||||
new ConcurrentDictionary<int, int>(); /*Use SYNCHRONIZED for cross thread blocks*/
|
||||
|
||||
|
||||
public async ValueTask<T> GetFolderCommonAsync<T>(FileMarker fileMarker, IDaoFactory daoFactory)
|
||||
{
|
||||
return (T)Convert.ChangeType(await GetFolderCommonAsync(fileMarker, daoFactory), typeof(T));
|
||||
}
|
||||
|
||||
|
||||
public async ValueTask<int> GetFolderCommonAsync(FileMarker fileMarker, IDaoFactory daoFactory)
|
||||
{
|
||||
if (_coreBaseSettings.Personal)
|
||||
{
|
||||
return default;
|
||||
}
|
||||
|
||||
if (!CommonFolderCache.TryGetValue(_tenantManager.GetCurrentTenant().Id, out var commonFolderId))
|
||||
|
||||
if (!CommonFolderCache.TryGetValue(_tenantManager.GetCurrentTenant().Id, out var commonFolderId))
|
||||
{
|
||||
commonFolderId = await GetFolderIdAndProccessFirstVisitAsync(fileMarker, daoFactory, false);
|
||||
if (!Equals(commonFolderId, 0))
|
||||
@ -471,7 +476,7 @@ public class GlobalFolder
|
||||
|
||||
internal static readonly IDictionary<int, int> ShareFolderCache =
|
||||
new ConcurrentDictionary<int, int>(); /*Use SYNCHRONIZED for cross thread blocks*/
|
||||
|
||||
|
||||
public async ValueTask<int> GetFolderShareAsync(IDaoFactory daoFactory)
|
||||
{
|
||||
if (_coreBaseSettings.Personal)
|
||||
@ -483,12 +488,12 @@ public class GlobalFolder
|
||||
{
|
||||
return default;
|
||||
}
|
||||
|
||||
if (!ShareFolderCache.TryGetValue(_tenantManager.GetCurrentTenant().Id, out var sharedFolderId))
|
||||
|
||||
if (!ShareFolderCache.TryGetValue(_tenantManager.GetCurrentTenant().Id, out var sharedFolderId))
|
||||
{
|
||||
sharedFolderId = await daoFactory.GetFolderDao<int>().GetFolderIDShareAsync(true);
|
||||
|
||||
if (!sharedFolderId.Equals(default))
|
||||
if (!sharedFolderId.Equals(default))
|
||||
{
|
||||
ShareFolderCache[_tenantManager.GetCurrentTenant().Id] = sharedFolderId;
|
||||
}
|
||||
@ -496,7 +501,7 @@ public class GlobalFolder
|
||||
|
||||
return sharedFolderId;
|
||||
}
|
||||
|
||||
|
||||
public async ValueTask<T> GetFolderShareAsync<T>(IDaoFactory daoFactory)
|
||||
{
|
||||
return (T)Convert.ChangeType(await GetFolderShareAsync(daoFactory), typeof(T));
|
||||
@ -615,10 +620,10 @@ public class GlobalFolder
|
||||
return privacyFolderId;
|
||||
}
|
||||
|
||||
|
||||
|
||||
internal static readonly IDictionary<string, object> TrashFolderCache =
|
||||
new ConcurrentDictionary<string, object>(); /*Use SYNCHRONIZED for cross thread blocks*/
|
||||
|
||||
|
||||
public async Task<T> GetFolderTrashAsync<T>(IDaoFactory daoFactory)
|
||||
{
|
||||
return (T)Convert.ChangeType(await GetFolderTrashAsync(daoFactory), typeof(T));
|
||||
@ -630,8 +635,8 @@ public class GlobalFolder
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var cacheKey = string.Format("trash/{0}/{1}", _tenantManager.GetCurrentTenant().Id, _authContext.CurrentAccount.ID);
|
||||
|
||||
var cacheKey = string.Format("trash/{0}/{1}", _tenantManager.GetCurrentTenant().Id, _authContext.CurrentAccount.ID);
|
||||
|
||||
if (!TrashFolderCache.TryGetValue(cacheKey, out var trashFolderId))
|
||||
{
|
||||
@ -641,8 +646,8 @@ public class GlobalFolder
|
||||
|
||||
return trashFolderId;
|
||||
}
|
||||
|
||||
protected internal void SetFolderTrash(object value)
|
||||
|
||||
protected internal void SetFolderTrash(object value)
|
||||
{
|
||||
var cacheKey = string.Format("trash/{0}/{1}", _tenantManager.GetCurrentTenant().Id, value);
|
||||
TrashFolderCache.Remove(cacheKey);
|
||||
@ -652,30 +657,30 @@ public class GlobalFolder
|
||||
{
|
||||
var folderDao = (FolderDao)daoFactory.GetFolderDao<int>();
|
||||
var fileDao = (FileDao)daoFactory.GetFileDao<int>();
|
||||
|
||||
|
||||
var id = my ? await folderDao.GetFolderIDUserAsync(false) : await folderDao.GetFolderIDCommonAsync(false);
|
||||
|
||||
|
||||
if (Equals(id, 0)) //TODO: think about 'null'
|
||||
{
|
||||
id = my ? await folderDao.GetFolderIDUserAsync(true) : await folderDao.GetFolderIDCommonAsync(true);
|
||||
|
||||
|
||||
//Copy start document
|
||||
if (_settingsManager.LoadForDefaultTenant<AdditionalWhiteLabelSettings>().StartDocsEnabled)
|
||||
{
|
||||
try
|
||||
{
|
||||
var storeTemplate = _globalStore.GetStoreTemplate();
|
||||
|
||||
|
||||
var culture = my ? _userManager.GetUsers(_authContext.CurrentAccount.ID).GetCulture() : _tenantManager.GetCurrentTenant().GetCulture();
|
||||
var path = FileConstant.StartDocPath + culture + "/";
|
||||
|
||||
|
||||
if (!await storeTemplate.IsDirectoryAsync(path))
|
||||
{
|
||||
path = FileConstant.StartDocPath + "en-US/";
|
||||
}
|
||||
|
||||
path += my ? "my/" : "corporate/";
|
||||
|
||||
|
||||
await SaveStartDocumentAsync(fileMarker, folderDao, fileDao, id, path, storeTemplate);
|
||||
}
|
||||
catch (Exception ex)
|
||||
@ -684,31 +689,31 @@ public class GlobalFolder
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
|
||||
private async Task SaveStartDocumentAsync(FileMarker fileMarker, FolderDao folderDao, FileDao fileDao, int folderId, string path, IDataStore storeTemplate)
|
||||
{
|
||||
var files = await storeTemplate.ListFilesRelativeAsync("", path, "*", false).ToListAsync();
|
||||
foreach (var file in files)
|
||||
foreach (var file in files)
|
||||
{
|
||||
await SaveFileAsync(fileMarker, fileDao, folderId, path + file, storeTemplate, files);
|
||||
await SaveFileAsync(fileMarker, fileDao, folderId, path + file, storeTemplate, files);
|
||||
}
|
||||
|
||||
|
||||
await foreach (var folderName in storeTemplate.ListDirectoriesRelativeAsync(path, false))
|
||||
{
|
||||
var folder = _serviceProvider.GetService<Folder<int>>();
|
||||
folder.Title = folderName;
|
||||
folder.ParentId = folderId;
|
||||
|
||||
|
||||
var subFolderId = await folderDao.SaveFolderAsync(folder);
|
||||
|
||||
|
||||
await SaveStartDocumentAsync(fileMarker, folderDao, fileDao, subFolderId, path + folderName + "/", storeTemplate);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task SaveFileAsync(FileMarker fileMarker, FileDao fileDao, int folder, string filePath, IDataStore storeTemp, IEnumerable<string> files)
|
||||
private async Task SaveFileAsync(FileMarker fileMarker, FileDao fileDao, int folder, string filePath, IDataStore storeTemp, IEnumerable<string> files)
|
||||
{
|
||||
try
|
||||
{
|
||||
@ -718,7 +723,7 @@ public class GlobalFolder
|
||||
if (FileUtility.GetFileExtension(filePath) == "." + ext
|
||||
&& files.Contains(Regex.Replace(fileName, "\\." + ext + "$", "")))
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
var file = _serviceProvider.GetService<File<int>>();
|
||||
|
||||
@ -739,7 +744,7 @@ public class GlobalFolder
|
||||
_logger.ErrorSaveFile(ex);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public bool IsOutsider => _userManager.IsOutsider(_authContext.CurrentAccount.ID);
|
||||
}
|
||||
|
||||
@ -749,14 +754,14 @@ public class GlobalFolderHelper
|
||||
private readonly FileMarker _fileMarker;
|
||||
private readonly IDaoFactory _daoFactory;
|
||||
private readonly GlobalFolder _globalFolder;
|
||||
|
||||
|
||||
public GlobalFolderHelper(FileMarker fileMarker, IDaoFactory daoFactory, GlobalFolder globalFolder)
|
||||
{
|
||||
_fileMarker = fileMarker;
|
||||
_daoFactory = daoFactory;
|
||||
_globalFolder = globalFolder;
|
||||
}
|
||||
|
||||
|
||||
public ValueTask<int> FolderProjectsAsync => _globalFolder.GetFolderProjectsAsync(_daoFactory);
|
||||
public ValueTask<int> FolderCommonAsync => _globalFolder.GetFolderCommonAsync(_fileMarker, _daoFactory);
|
||||
public int FolderMy => _globalFolder.GetFolderMy(_fileMarker, _daoFactory);
|
||||
@ -806,16 +811,16 @@ public class GlobalFolderHelper
|
||||
{
|
||||
_globalFolder.SetFolderMy(val);
|
||||
}
|
||||
|
||||
|
||||
public async ValueTask<T> GetFolderShareAsync<T>()
|
||||
{
|
||||
return (T)Convert.ChangeType(await FolderShareAsync, typeof(T));
|
||||
}
|
||||
public ValueTask<int> FolderShareAsync => _globalFolder.GetFolderShareAsync(_daoFactory);
|
||||
|
||||
|
||||
public object FolderTrash
|
||||
{
|
||||
get => _globalFolder.GetFolderTrashAsync(_daoFactory).Result;
|
||||
set => _globalFolder.SetFolderTrash(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -842,6 +842,15 @@ namespace ASC.Files.Core.Resources {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to You don't have enough permission to archive the room.
|
||||
/// </summary>
|
||||
public static string ErrorMessage_SecurityException_ArchiveRoom {
|
||||
get {
|
||||
return ResourceManager.GetString("ErrorMessage_SecurityException_ArchiveRoom", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to You don't have permission to copy to this folder.
|
||||
/// </summary>
|
||||
@ -870,11 +879,11 @@ namespace ASC.Files.Core.Resources {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to You don't have enough permission to archive the room.
|
||||
/// Looks up a localized string similar to You don't have enough permission to unarchive the room.
|
||||
/// </summary>
|
||||
public static string ErrorMessage_UnarchiveRoom {
|
||||
public static string ErrorMessage_SecurityException_UnarchiveRoom {
|
||||
get {
|
||||
return ResourceManager.GetString("ErrorMessage_UnarchiveRoom", resourceCulture);
|
||||
return ResourceManager.GetString("ErrorMessage_SecurityException_UnarchiveRoom", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -375,8 +375,8 @@
|
||||
<data name="ErrorMessage_SecurityException_MoveToFolder" xml:space="preserve">
|
||||
<value>You don't have permission to move to this folder</value>
|
||||
</data>
|
||||
<data name="ErrorMessage_UnarchiveRoom" xml:space="preserve">
|
||||
<value>You don't have enough permission to archive the room</value>
|
||||
<data name="ErrorMessage_SecurityException_UnarchiveRoom" xml:space="preserve">
|
||||
<value>You don't have enough permission to unarchive the room</value>
|
||||
</data>
|
||||
<data name="ErrorMessage_UpdateArchivedRoom" xml:space="preserve">
|
||||
<value>You cannot edit archived rooms</value>
|
||||
@ -472,4 +472,7 @@ Highest compatibility with docx, xlsx, pptx. </value>
|
||||
<data name="AceStatusEnum_Collaborator" xml:space="preserve">
|
||||
<value>Collaborator</value>
|
||||
</data>
|
||||
<data name="ErrorMessage_SecurityException_ArchiveRoom" xml:space="preserve">
|
||||
<value>You don't have enough permission to archive the room</value>
|
||||
</data>
|
||||
</root>
|
@ -146,6 +146,7 @@ class FileMoveCopyOperation<T> : FileOperation<FileMoveCopyOperationData<T>, T>
|
||||
if (0 < Folders.Count)
|
||||
{
|
||||
var firstFolder = await FolderDao.GetFolderAsync(Folders[0]);
|
||||
var isRoom = DocSpaceHelper.IsRoom(firstFolder.FolderType);
|
||||
|
||||
if (_copy && !await FilesSecurity.CanCopyAsync(firstFolder))
|
||||
{
|
||||
@ -155,7 +156,17 @@ class FileMoveCopyOperation<T> : FileOperation<FileMoveCopyOperationData<T>, T>
|
||||
}
|
||||
if (!_copy && !await FilesSecurity.CanMoveAsync(firstFolder))
|
||||
{
|
||||
this[Err] = FilesCommonResource.ErrorMassage_SecurityException_MoveFile;
|
||||
if (isRoom)
|
||||
{
|
||||
this[Err] = toFolder.FolderType == FolderType.Archive
|
||||
? FilesCommonResource.ErrorMessage_SecurityException_ArchiveRoom
|
||||
: FilesCommonResource.ErrorMessage_SecurityException_UnarchiveRoom;
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
this[Err] = FilesCommonResource.ErrorMassage_SecurityException_MoveFolder;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
@ -187,7 +198,9 @@ class FileMoveCopyOperation<T> : FileOperation<FileMoveCopyOperationData<T>, T>
|
||||
}
|
||||
if (!_copy && !await fileSecurity.CanMoveToAsync(toFolder))
|
||||
{
|
||||
this[Err] = FilesCommonResource.ErrorMessage_SecurityException_MoveToFolder;
|
||||
this[Err] = toFolder.FolderType == FolderType.VirtualRooms ? FilesCommonResource.ErrorMessage_SecurityException_UnarchiveRoom :
|
||||
toFolder.FolderType == FolderType.Archive ? FilesCommonResource.ErrorMessage_SecurityException_UnarchiveRoom :
|
||||
FilesCommonResource.ErrorMessage_SecurityException_MoveToFolder;
|
||||
|
||||
return;
|
||||
}
|
||||
@ -270,7 +283,16 @@ class FileMoveCopyOperation<T> : FileOperation<FileMoveCopyOperationData<T>, T>
|
||||
}
|
||||
else if (!copy && checkPermissions && !canMoveOrCopy)
|
||||
{
|
||||
this[Err] = FilesCommonResource.ErrorMassage_SecurityException_MoveFolder;
|
||||
if (isRoom)
|
||||
{
|
||||
this[Err] = toFolder.FolderType == FolderType.Archive
|
||||
? FilesCommonResource.ErrorMessage_SecurityException_ArchiveRoom
|
||||
: FilesCommonResource.ErrorMessage_SecurityException_UnarchiveRoom;
|
||||
}
|
||||
else
|
||||
{
|
||||
this[Err] = FilesCommonResource.ErrorMassage_SecurityException_MoveFolder;
|
||||
}
|
||||
}
|
||||
else if (!isRoom && (toFolder.FolderType == FolderType.VirtualRooms || toFolder.RootFolderType == FolderType.Archive))
|
||||
{
|
||||
@ -278,7 +300,7 @@ class FileMoveCopyOperation<T> : FileOperation<FileMoveCopyOperationData<T>, T>
|
||||
}
|
||||
else if (isRoom && toFolder.FolderType != FolderType.VirtualRooms && toFolder.FolderType != FolderType.Archive)
|
||||
{
|
||||
this[Err] = FilesCommonResource.ErrorMessage_UnarchiveRoom;
|
||||
this[Err] = FilesCommonResource.ErrorMessage_SecurityException_UnarchiveRoom;
|
||||
}
|
||||
else if (!isRoom && folder.Private && !await CompliesPrivateRoomRulesAsync(folder, toFolderParents))
|
||||
{
|
||||
|
6
public/images/book.training.react.svg
Normal file
6
public/images/book.training.react.svg
Normal file
@ -0,0 +1,6 @@
|
||||
<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="M7.25807 1.11309C7.53755 0.967522 7.86929 0.962209 8.1533 1.09875L15.4333 4.59875C15.7797 4.76529 16 5.11564 16 5.5C16 5.88436 15.7797 6.23471 15.4333 6.40125L8.1533 9.90125C7.86929 10.0378 7.53755 10.0325 7.25807 9.88691L2 7.14834V9C2 9.55228 1.55228 10 1 10C0.447715 10 0 9.55228 0 9V5.5C0 5.12717 0.207401 4.78531 0.538066 4.61309L7.25807 1.11309ZM7.73791 7.88182L12.6921 5.5L7.73791 3.11818L3.16481 5.5L7.73791 7.88182Z" fill="#333333"/>
|
||||
<path d="M2 12C2 11.4477 1.55228 11 1 11C0.447715 11 0 11.4477 0 12C0 12.5523 0.447715 13 1 13C1.55228 13 2 12.5523 2 12Z" fill="#333333"/>
|
||||
<path d="M13 9.01632L11 9.97786V12.4812C9.92129 13.198 8.93284 13.5144 7.99797 13.5156C7.06354 13.5167 6.07649 13.2032 5 12.4832V10.1471L3 9.10542V13C3 13.3154 3.14878 13.6123 3.40141 13.8011C4.88035 14.9062 6.40699 15.5176 8.0005 15.5156C9.59296 15.5135 11.1183 14.8991 12.5958 13.8032C12.8501 13.6145 13 13.3166 13 13V9.01632Z" fill="#333333"/>
|
||||
<path d="M5 9H4.96241L5 9.01958V9Z" fill="#333333"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.1 KiB |
@ -20,6 +20,9 @@
|
||||
"Audio": "Audio",
|
||||
"BarMaintenanceDescription": "We apologize for any short-term technical issues in service functioning that may appear on {{targetDate}} during the update of {{productName}}.",
|
||||
"BarMaintenanceDisclaimer": "Please, make sure that all the changes are successfully saved during this day.",
|
||||
"BookTraining": "Book a training",
|
||||
"BookTeamTraining": "Book a team training session with our best specialists",
|
||||
"BookNow": "Book now",
|
||||
"ByFirstNameSorting": "By first name",
|
||||
"ByLastNameSorting": "By last name",
|
||||
"Bytes": "bytes",
|
||||
@ -124,6 +127,7 @@
|
||||
"LastName": "Last name",
|
||||
"LatePayment": "Late payment",
|
||||
"LearnMore": "Learn more",
|
||||
"LiveChat": "Live chat",
|
||||
"Load": "Load",
|
||||
"LoadingDescription": "Please wait...",
|
||||
"LoadingProcessing": "Loading...",
|
||||
@ -236,6 +240,7 @@
|
||||
"UnknownError": "Unknown error",
|
||||
"User": "User",
|
||||
"UsersInvited": "Users invited",
|
||||
"UseLikePro": "Use {{organizationName}} like a pro",
|
||||
"Version": "Version",
|
||||
"Video": "Video",
|
||||
"View": "View",
|
||||
|
Loading…
Reference in New Issue
Block a user