Merge branch 'release/v1.1.0' of https://github.com/ONLYOFFICE/DocSpace into release/v1.1.0

This commit is contained in:
Maria Sukhova 2023-06-15 18:18:18 +03:00
commit b85c3dcceb
38 changed files with 435 additions and 174 deletions

View File

@ -62,12 +62,12 @@ Depends: {{product}}-common (= {{package_header_tag_version}}), dotnet-sdk-7.0,
Recommends: ffmpeg, elasticsearch (= 7.10.0)
Description: {{product}}-files-services
The service which launches additional services related to file management:
- ElasticSearchIndexService - indexes documents using elasticsearch;
- FeedAggregatorService - aggregates notifications;
- FeedCleanerService - removes notifications;
- FileConverterService - converts documents;
- ThumbnailBuilderService - generates thumbnails for documents;
- Launcher - removes outdated files from Trash;
- ElasticSearchIndexService - indexes documents using elasticsearch;
- FeedAggregatorService - aggregates notifications;
- FeedCleanerService - removes notifications;
- FileConverterService - converts documents;
- ThumbnailBuilderService - generates thumbnails for documents;
- Launcher - removes outdated files from Trash;
Package: {{product}}-notify
Architecture: any
@ -90,7 +90,7 @@ Architecture: any
Depends: {{product}}-common (= {{package_header_tag_version}}), nodejs (>= 16), ${misc:Depends}, ${shlibs:Depends}
Recommends: redis-server
Description: {{product}}-socket
The service which provides two-way communication between a web browser and the server
The service which provides two-way communication between a client and a server
Package: {{product}}-studio-notify
Architecture: any
@ -112,7 +112,7 @@ Package: {{product}}-api-system
Architecture: any
Depends: {{product}}-common (= {{package_header_tag_version}}), dotnet-sdk-7.0, ${misc:Depends}, ${shlibs:Depends}
Description: {{product}}-api-system
The service which is used for working with portals (creating, removing portals, etc.)
The service which is used for working with portals (creating, removing, etc.)
Package: {{product}}-studio
Architecture: any
@ -141,11 +141,11 @@ Architecture: any
Depends: {{product}}-common (= {{package_header_tag_version}}), dotnet-sdk-7.0, ${misc:Depends}, ${shlibs:Depends}
Description: {{product}}-backup-background
The service which launches additional services related to backup creation:
- BackupWorkerService - launches WorkerService which runs backup/restore, etc.;
- BackupListenerService - waits for a signal to delete backups;
- BackupCleanerTempFileService - removes temporary backup files;
- BackupCleanerService - removes outdated backup files;
- BackupSchedulerService - runs backup according to a schedule;
- BackupWorkerService - launches WorkerService which runs backup/restore, etc;
- BackupListenerService - waits for a signal to delete backups;
- BackupCleanerTempFileService - removes temporary backup files;
- BackupCleanerService - removes outdated backup files;
- BackupSchedulerService - runs backup according to a schedule;
Package: {{product}}-clear-events
Architecture: any

View File

@ -14,7 +14,7 @@ Summary: Common
Group: Applications/Internet
Requires: logrotate
%description common
A package containing configs and scripts
A package containing configure and scripts
%package files-services
Packager: %{packager}
@ -26,12 +26,12 @@ Requires: ffmpeg
AutoReqProv: no
%description files-services
The service which launches additional services related to file management:
- ElasticSearchIndexService - indexes documents using elasticsearch;
- FeedAggregatorService - aggregates notifications;
- FeedCleanerService - removes notifications;
- FileConverterService - converts documents;
- ThumbnailBuilderService - generates thumbnails for documents;
- Launcher - removes outdated files from Trash;
- ElasticSearchIndexService - indexes documents using Elasticsearch;
- FeedAggregatorService - aggregates notifications;
- FeedCleanerService - removes notifications;
- FileConverterService - converts documents;
- ThumbnailBuilderService - generates thumbnails for documents;
- Launcher - removes outdated files from Trash;
%package notify
Packager: %{packager}
@ -99,7 +99,7 @@ Requires: %name-common = %version-%release
Requires: nodejs >= 16.0
AutoReqProv: no
%description socket
The service which provides two-way communication between a web browser and the server
The service which provides two-way communication between a client and a server
%package studio
Packager: %{packager}
@ -131,7 +131,7 @@ Requires: %name-common = %version-%release
Requires: dotnet-sdk-7.0
AutoReqProv: no
%description api-system
The service which is used for working with portals (creating, removing portals, etc.)
The service which is used for working with portals (creating, removing, etc.)
%package ssoauth
Packager: %{packager}
@ -141,7 +141,9 @@ Requires: %name-common = %version-%release
Requires: nodejs >= 16.0
AutoReqProv: no
%description ssoauth
Ssoauth
The service responsible for enabling and configuring
SAML-based single sign-on (SSO) authentication to provide a more quick,
easy and secure way to access DocSpace for users
%package clear-events
Packager: %{packager}
@ -151,7 +153,8 @@ Requires: %name-common = %version-%release
Requires: dotnet-sdk-7.0
AutoReqProv: no
%description clear-events
Clear-events
The service responsible for clearing the login_events and audit_events tables
by LoginHistoryLifeTime and AuditTrailLifeTime to log out users after a timeout
%package backup-background
Packager: %{packager}
@ -161,7 +164,12 @@ Requires: %name-common = %version-%release
Requires: dotnet-sdk-7.0
AutoReqProv: no
%description backup-background
Backup-background
The service which launches additional services related to backup creation:
- BackupWorkerService - launches WorkerService which runs backup/restore, etc;
- BackupListenerService - waits for a signal to delete backups;
- BackupCleanerTempFileService - removes temporary backup files;
- BackupCleanerService - removes outdated backup files;
- BackupSchedulerService - runs backup according to a schedule;
%package radicale
Packager: %{packager}
@ -174,7 +182,9 @@ Requires: python3-requests
Requires: python3-setuptools
AutoReqProv: no
%description radicale
Radicale
Radicale is a server designed to support the CalDav and CardDav protocols.
It operates either as a standalone package using its own internal HTTP server
or can be integrated with an existing web server
%package doceditor
Packager: %{packager}
@ -184,7 +194,7 @@ Requires: %name-common = %version-%release
Requires: nodejs >= 16.0
AutoReqProv: no
%description doceditor
Doceditor
The service which allows interaction with document-server
%package migration-runner
Packager: %{packager}
@ -194,7 +204,9 @@ Requires: %name-common = %version-%release
Requires: dotnet-sdk-7.0
AutoReqProv: no
%description migration-runner
Migration-runner
The service responsible for the database creation.
A database connection is transferred to the service and
the service creates tables and populates them with values
%package login
Packager: %{packager}
@ -204,7 +216,7 @@ Requires: %name-common = %version-%release
Requires: nodejs >= 16.0
AutoReqProv: no
%description login
Login
The service which is used for logging users and displaying the wizard
%package healthchecks
Packager: %{packager}
@ -214,4 +226,4 @@ Requires: %name-common = %version-%release
Requires: dotnet-sdk-7.0
AutoReqProv: no
%description healthchecks
Healthchecks
The service which displays launched services

View File

@ -23,7 +23,7 @@
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="7.0.2" />
<PackageReference Include="Microsoft.Extensions.Hosting.Systemd" Version="7.0.0" />
<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="7.0.0" />
<PackageReference Include="NLog.Web.AspNetCore" Version="5.2.1" />
<PackageReference Include="NLog.Web.AspNetCore" Version="5.3.0" />
<PackageReference Include="StackExchange.Redis.Extensions.AspNetCore" Version="8.0.5" />
<PackageReference Include="StackExchange.Redis.Extensions.Newtonsoft" Version="8.0.5" />
<PackageReference Include="Autofac.Extensions.DependencyInjection" Version="8.0.0" />

View File

@ -199,7 +199,7 @@ public abstract class BaseStartup
config.Filters.Add(new TypeFilterAttribute(typeof(IpSecurityFilter)));
config.Filters.Add(new TypeFilterAttribute(typeof(ProductSecurityFilter)));
config.Filters.Add(new CustomResponseFilterAttribute());
config.Filters.Add(new CustomExceptionFilterAttribute());
config.Filters.Add<CustomExceptionFilterAttribute>();
config.Filters.Add(new TypeFilterAttribute(typeof(WebhooksGlobalFilterAttribute)));
});

View File

@ -62,7 +62,7 @@ public class LoggerMiddleware
state.Add("tenantMappedDomain", tenant.MappedDomain);
}
using (logger.BeginScope(state.ToArray()))
using (logger.BeginScope(state))
{
await _next.Invoke(context);
}

View File

@ -28,6 +28,13 @@ namespace ASC.Api.Core.Middleware;
public class CustomExceptionFilterAttribute : ExceptionFilterAttribute
{
private readonly ILogger<CustomExceptionFilterAttribute> _logger;
public CustomExceptionFilterAttribute(ILogger<CustomExceptionFilterAttribute> logger)
{
_logger = logger;
}
public override void OnException(ExceptionContext context)
{
var status = (HttpStatusCode)context.HttpContext.Response.StatusCode;
@ -69,6 +76,9 @@ public class CustomExceptionFilterAttribute : ExceptionFilterAttribute
break;
}
_logger.LogError(exception,
$"error during executing {context.HttpContext.Request?.Method}: {context.HttpContext.Request?.Path.Value}");
var result = new ObjectResult(new ErrorApiResponse(status, exception, message, withStackTrace))
{
StatusCode = (int)status

View File

@ -0,0 +1,114 @@
// (c) Copyright Ascensio System SIA 2010-2022
//
// This program is a free software product.
// You can redistribute it and/or modify it under the terms
// of the GNU Affero General Public License (AGPL) version 3 as published by the Free Software
// Foundation. In accordance with Section 7(a) of the GNU AGPL its Section 15 shall be amended
// to the effect that Ascensio System SIA expressly excludes the warranty of non-infringement of
// any third-party rights.
//
// This program is distributed WITHOUT ANY WARRANTY, without even the implied warranty
// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For details, see
// the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html
//
// You can contact Ascensio System SIA at Lubanas st. 125a-25, Riga, Latvia, EU, LV-1021.
//
// The interactive user interfaces in modified source and object code versions of the Program must
// display Appropriate Legal Notices, as required under Section 5 of the GNU AGPL version 3.
//
// Pursuant to Section 7(b) of the License you must retain the original Product logo when
// distributing the program. Pursuant to Section 7(e) we decline to grant you any rights under
// trademark law for use of our trademarks.
//
// All the Product's GUI elements, including illustrations and icon sets, as well as technical writing
// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
namespace ASC.Api.Core.Middleware;
// problem: https://github.com/aspnet/Logging/issues/677
public class UnhandledExceptionMiddleware
{
private readonly RequestDelegate _next;
public UnhandledExceptionMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context,
ILogger<UnhandledExceptionMiddleware> logger)
{
try
{
await _next(context);
}
catch (Exception ex) when (LogError(ex))
{
await OnException(context, ex);
}
bool LogError(Exception ex)
{
logger.LogError(ex,
$"Request {context.Request?.Method}: {context.Request?.Path.Value} failed");
return true;
}
}
public async Task OnException(HttpContext context, Exception exception)
{
var status = (HttpStatusCode)context.Response.StatusCode;
string message = null;
if (status == HttpStatusCode.OK)
{
status = HttpStatusCode.InternalServerError;
}
var withStackTrace = true;
switch (exception)
{
case ItemNotFoundException:
status = HttpStatusCode.NotFound;
message = "The record could not be found";
break;
case ArgumentException:
status = HttpStatusCode.BadRequest;
message = "Invalid arguments";
break;
case SecurityException:
status = HttpStatusCode.Forbidden;
message = "Access denied";
break;
case AuthenticationException:
status = HttpStatusCode.Unauthorized;
withStackTrace = false;
break;
case InvalidOperationException:
status = HttpStatusCode.Forbidden;
break;
case TenantQuotaException:
case BillingNotFoundException:
status = HttpStatusCode.PaymentRequired;
break;
}
var result = new ErrorApiResponse(status, exception, message, withStackTrace);
context.Response.StatusCode = (int)status;
await context.Response.WriteAsJsonAsync(result);
}
}
public static class UnhandledExceptionMiddlewareExtensions
{
public static IApplicationBuilder UseUnhandledExceptionMiddleware(this IApplicationBuilder builder)
{
return builder.UseMiddleware<UnhandledExceptionMiddleware>();
}
}

View File

@ -55,7 +55,7 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> -->
<PackageReference Include="NLog" Version="5.1.1" />
<PackageReference Include="NLog" Version="5.2.0" />
<PackageReference Include="NLog.Targets.Syslog" Version="7.0.0" />
<PackageReference Include="NVelocity" Version="1.2.0" />
<PackageReference Include="protobuf-net" Version="3.1.26" />

View File

@ -63,7 +63,6 @@
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="7.0.0" />
<PackageReference Include="System.Linq.Async" Version="6.0.1" />
<PackageReference Include="Telegram.Bot" Version="18.0.0" />
<PackageReference Include="Telegram.Bot.Extensions.Polling" Version="1.0.2" />
</ItemGroup>
<ItemGroup>
<Protobuf Include="protos\create_client_proto.proto" />

View File

@ -23,8 +23,4 @@
<ItemGroup>
<Folder Include="Properties\" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Telegram.Bot.Extensions.Polling" Version="1.0.2" />
</ItemGroup>
</Project>

View File

@ -188,7 +188,7 @@ public class TelegramHandler
var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(cts.Token, cancellationToken);
client.StartReceiving(updateHandler: (botClient, exception, cancellationToken) => HandleUpdateAsync(botClient, exception, cancellationToken, tenantId),
errorHandler: HandleErrorAsync,
pollingErrorHandler: HandleErrorAsync,
cancellationToken: linkedCts.Token);
}

View File

@ -42,7 +42,11 @@
</targets>
<rules>
<logger name="ASC.SQL" minlevel="Debug" writeTo="sql" final="true" />
<logger name="ASC*" minlevel="Debug" writeTo="web" />
<logger name="ASC*" minlevel="Debug" writeTo="web">
<filters defaultAction="Log">
<when condition="equals('${scope-property:RequestPath}', '/health')" action="Ignore" />
</filters>
</logger>
<logger name="Microsoft.AspNetCore.Hosting.Diagnostics" minlevel="Debug" writeTo="ownFile-web" final="true" />
<logger name="Microsoft.*" maxlevel="Off" final="true" />
</rules>

View File

@ -46,6 +46,7 @@
"file-saver": "^2.0.5",
"firebase": "^8.10.0",
"hex-to-rgba": "^2.0.1",
"queue-promise": "2.2.1",
"react-avatar-editor": "^13.0.0",
"react-colorful": "^5.5.1",
"react-hotkeys-hook": "^3.4.4",

View File

@ -113,5 +113,6 @@
"ViewList": "List",
"ViewOnlyRooms": "View-only",
"ViewTiles": "Tiles",
"WithSubfolders": "With subfolders"
"WithSubfolders": "With subfolders",
"DocumentEdited": "Cannot perform the action because the document is being edited."
}

View File

@ -113,5 +113,6 @@
"ViewList": "Список",
"ViewOnlyRooms": "Просмотр",
"ViewTiles": "Плитки",
"WithSubfolders": "С подпапками"
"WithSubfolders": "С подпапками",
"DocumentEdited": "Невозможно выполнить действие, так как документ редактируется."
}

View File

@ -91,6 +91,11 @@ const StyledRow = styled.div`
font-size: 14px;
line-height: 16px;
}
.invite-panel_access-selector {
margin-left: auto;
margin-right: 0;
}
`;
const StyledInviteInput = styled.div`

View File

@ -1,4 +1,4 @@
import React, { useEffect, useState } from "react";
import React, { useEffect, useState, useRef } from "react";
import { observer, inject } from "mobx-react";
import { withTranslation } from "react-i18next";
import { isMobileOnly } from "react-device-detect";
@ -52,6 +52,8 @@ const InvitePanel = ({
const [isLoading, setIsLoading] = useState(false);
const [externalLinksVisible, setExternalLinksVisible] = useState(false);
const inputsRef = useRef();
const onChangeExternalLinksVisible = (visible) => {
setExternalLinksVisible(visible);
};
@ -236,6 +238,7 @@ const InvitePanel = ({
onClose={onClose}
roomUsers={roomUsers}
roomType={roomType}
inputsRef={inputsRef}
/>
{!!inviteItems.length && (
@ -245,6 +248,7 @@ const InvitePanel = ({
setHasErrors={setHasErrors}
roomType={roomType}
externalLinksVisible={externalLinksVisible}
inputsRef={inputsRef}
/>
<StyledButtons>

View File

@ -45,7 +45,7 @@ const AccessSelector = ({
: {};
return (
<StyledAccessSelector>
<StyledAccessSelector className="invite-panel_access-selector">
<AccessRightSelect
selectedOption={selectedOption}
onSelect={onSelectAccess}

View File

@ -36,6 +36,7 @@ const InviteInput = ({
roomUsers,
t,
isOwner,
inputsRef,
}) => {
const [inputValue, setInputValue] = useState("");
const [usersList, setUsersList] = useState([]);
@ -44,7 +45,6 @@ const InviteInput = ({
const [selectedAccess, setSelectedAccess] = useState(defaultAccess);
const inputsRef = useRef();
const searchRef = useRef();
const inRoom = (id) => {

View File

@ -8,7 +8,6 @@ import { parseAddresses } from "@docspace/components/utils/email";
import { getAccessOptions } from "../utils";
import {
StyledComboBox,
StyledEditInput,
StyledEditButton,
StyledCheckIcon,
@ -17,6 +16,7 @@ import {
StyledDeleteIcon,
} from "../StyledInvitePanel";
import { filterUserRoleOptions } from "SRC_DIR/helpers/utils";
import AccessSelector from "./AccessSelector";
const Item = ({
t,
@ -27,6 +27,7 @@ const Item = ({
setHasErrors,
roomType,
isOwner,
inputsRef,
}) => {
const { avatar, displayName, email, id, errors, access } = item;
@ -37,7 +38,7 @@ const Item = ({
const [inputValue, setInputValue] = useState(name);
const [parseErrors, setParseErrors] = useState(errors);
const accesses = getAccessOptions(t, roomType, true, false, isOwner);
const accesses = getAccessOptions(t, roomType, true, true, isOwner);
const filteredAccesses = filterUserRoleOptions(accesses, item, true);
@ -130,20 +131,15 @@ const Item = ({
<StyledDeleteIcon size="medium" onClick={removeItem} />
</>
) : (
<StyledComboBox
onSelect={selectItemAccess}
noBorder
options={filteredAccesses}
size="content"
scaled={false}
manualWidth="fit-content"
selectedOption={defaultAccess}
showDisabledItems
modernView
directionX="right"
directionY="bottom"
isDefaultMode={false}
fixedDirection={true}
<AccessSelector
t={t}
roomType={roomType}
defaultAccess={defaultAccess?.access}
onSelectAccess={selectItemAccess}
containerRef={inputsRef}
isOwner={isOwner}
withRemove={true}
filteredAccesses={filteredAccesses}
/>
)}
</>

View File

@ -18,6 +18,7 @@ const Row = memo(({ data, index, style }) => {
setHasErrors,
roomType,
isOwner,
inputsRef,
} = data;
if (inviteItems === undefined) return;
@ -35,6 +36,7 @@ const Row = memo(({ data, index, style }) => {
setHasErrors={setHasErrors}
roomType={roomType}
isOwner={isOwner}
inputsRef={inputsRef}
/>
</StyledRow>
);
@ -49,6 +51,7 @@ const ItemsList = ({
roomType,
isOwner,
externalLinksVisible,
inputsRef,
}) => {
const [bodyHeight, setBodyHeight] = useState(0);
const [offsetTop, setOffsetTop] = useState(0);
@ -87,6 +90,7 @@ const ItemsList = ({
setHasErrors,
roomType,
isOwner,
inputsRef,
t,
}}
outerElementType={CustomScrollbarsVirtualList}

View File

@ -96,6 +96,7 @@ const StyledVersionHistoryPanel = styled.div`
}
.version-history-panel-header {
margin-bottom: 12px;
height: 53px;
margin-left: 0px;
.version-history-panel-heading {
@ -113,11 +114,11 @@ const StyledVersionHistoryPanel = styled.div`
box-sizing: border-box;
.version-comment-wrapper {
margin-left: 79px;
margin-left: 85px;
}
.version_edit-comment {
padding-left: 2px;
padding-left: 7px;
}
}
`;

View File

@ -75,7 +75,7 @@ class PureVersionHistoryPanel extends React.Component {
</StyledHeaderContent>
<StyledBody className="version-history-panel-body">
<SectionBodyContent />
<SectionBodyContent onClose={this.onClose} />
</StyledBody>
{showProgressBar && (
<FloatingButton

View File

@ -118,7 +118,7 @@ StyledVersionList.defaultProps = { theme: Base };
const StyledVersionRow = styled(Row)`
.row_content {
position: relative;
padding-top: 12px;
padding-top: 13px;
padding-bottom: 12px;
height: auto;
${(props) => !props.isTabletView && "padding-right:16px"};
@ -143,10 +143,9 @@ const StyledVersionRow = styled(Row)`
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
@media ${tablet} {
margin-top: -1px;
}
}
.version-link-file:first-child {
margin-bottom: 4px;
}
.icon-link {
@ -164,7 +163,7 @@ const StyledVersionRow = styled(Row)`
}
.textarea-desktop {
margin: 9px 23px 1px -7px;
margin: 6px 31px 1px -7px;
}
.version_content-length {
@ -214,7 +213,7 @@ const StyledVersionRow = styled(Row)`
.row_context-menu-wrapper {
display: block;
position: absolute;
right: 16px !important;
right: 13px !important;
top: 6px;
.expandButton {

View File

@ -16,7 +16,7 @@ const VersionBadge = ({
className={className}
marginProp="0 8px"
displayProp="flex"
isVersion={isVersion}
isVersion={true}
theme={theme}
index={index}
{...rest}

View File

@ -1,3 +1,7 @@
import AccessCommentReactSvgUrl from "PUBLIC_DIR/images/access.comment.react.svg?url";
import RestoreAuthReactSvgUrl from "PUBLIC_DIR/images/restore.auth.react.svg?url";
import { useHistory } from "react-router-dom";
import DownloadReactSvgUrl from "PUBLIC_DIR/images/download.react.svg?url";
import React, { useState, useEffect } from "react";
import styled from "styled-components";
import Link from "@docspace/components/link";
@ -10,7 +14,7 @@ import { withTranslation } from "react-i18next";
import { withRouter } from "react-router";
import VersionBadge from "./VersionBadge";
import { StyledVersionRow } from "./StyledVersionHistory";
import ExternalLinkIcon from "PUBLIC_DIR/images/external.link.react.svg";
import ExternalLinkIcon from "PUBLIC_DIR/images/external.link.react.svg?url";
import commonIconsStyles from "@docspace/components/utils/common-icons-style";
import { inject, observer } from "mobx-react";
import toastr from "@docspace/components/toast/toastr";
@ -43,14 +47,18 @@ const VersionRow = (props) => {
isEditing,
theme,
canChangeVersionFileHistory,
openUser,
onClose,
setIsVisible,
} = props;
const [showEditPanel, setShowEditPanel] = useState(false);
const [commentValue, setCommentValue] = useState(info.comment);
const [isSavingComment, setIsSavingComment] = useState(false);
const title = `${new Date(info.updated).toLocaleString(
culture
)} ${Encoder.htmlDecode(info.updatedBy?.displayName)}`;
const history = useHistory();
const versionDate = `${new Date(info.updated).toLocaleString(culture)}`;
const title = `${Encoder.htmlDecode(info.updatedBy?.displayName)}`;
const linkStyles = { isHovered: true, type: "action" };
@ -66,6 +74,12 @@ const VersionRow = (props) => {
setCommentValue(value);
};
const onUserClick = () => {
onClose(true);
setIsVisible(true);
openUser(info?.updatedBy, history);
};
const onSaveClick = () => {
setIsSavingComment(true);
updateCommentVersion(info.id, commentValue, info.version)
@ -98,27 +112,37 @@ const VersionRow = (props) => {
};
const contextOptions = [
{
key: "open",
icon: ExternalLinkIcon,
label: t("Files:Open"),
onClick: onOpenFile,
},
canChangeVersionFileHistory && {
key: "edit",
icon: AccessCommentReactSvgUrl,
label: t("EditComment"),
onClick: onEditComment,
},
index !== 0 &&
canChangeVersionFileHistory && {
key: "restore",
icon: RestoreAuthReactSvgUrl,
label: t("Common:Restore"),
onClick: onRestoreClick,
},
{
key: "download",
icon: DownloadReactSvgUrl,
label: `${t("Common:Download")} (${info.contentLength})`,
onClick: onDownloadAction,
},
];
const onClickProp = canChangeVersionFileHistory
? { onClick: onVersionClick }
: {};
// uncomment if we want to change versions again
// const onClickProp = canChangeVersionFileHistory
// ? { onClick: onVersionClick }
// : {};
useEffect(() => {
const newRowHeight = document.getElementsByClassName(
@ -147,19 +171,35 @@ const VersionRow = (props) => {
isVersion={isVersion}
index={index}
versionGroup={info.versionGroup}
{...onClickProp}
// {...onClickProp}
t={t}
/>
<Link
onClick={onOpenFile}
fontWeight={600}
fontSize="14px"
title={title}
isTextOverflow={true}
className="version-link-file"
<Box
displayProp="flex"
flexDirection="column"
marginProp="-2px 0 0 0"
>
{title}
</Link>
<Link
onClick={onOpenFile}
fontWeight={600}
fontSize="14px"
title={versionDate}
isTextOverflow={true}
className="version-link-file"
>
{versionDate}
</Link>
<Link
onClick={onUserClick}
fontWeight={600}
fontSize="14px"
title={title}
isTextOverflow={true}
className="version-link-file"
>
{title}
</Link>
</Box>
{/*<Text
className="version_content-length"
@ -170,11 +210,7 @@ const VersionRow = (props) => {
{info.contentLength}
</Text>*/}
</Box>
<Box
className="version-comment-wrapper"
marginProp="0 0 0 70px"
displayProp="flex"
>
<Box className="version-comment-wrapper" displayProp="flex">
<>
{showEditPanel && (
<>
@ -191,7 +227,7 @@ const VersionRow = (props) => {
</>
)}
<Text className="version_text" truncate={true}>
<Text className="version_text" color="#A3A9AE" truncate={true}>
{info.comment}
</Text>
</>
@ -232,6 +268,7 @@ const VersionRow = (props) => {
export default inject(({ auth, versionHistoryStore, selectedFolderStore }) => {
const { user } = auth.userStore;
const { openUser, setIsVisible } = auth.infoPanelStore;
const { culture, isTabletView } = auth.settingsStore;
const language = (user && user.cultureName) || culture || "en";
@ -256,6 +293,8 @@ export default inject(({ auth, versionHistoryStore, selectedFolderStore }) => {
updateCommentVersion,
isEditing: isEdit,
canChangeVersionFileHistory,
openUser,
setIsVisible,
};
})(
withRouter(

View File

@ -64,7 +64,7 @@ class SectionBodyContent extends React.Component {
this.setState((prevState) => ({
rowSizes: {
...prevState.rowSizes,
[i]: itemHeight + 24, //composed of itemHeight = clientHeight of div and padding-top = 12px and padding-bottom = 12px
[i]: itemHeight + 27, //composed of itemHeight = clientHeight of div and padding-top = 13px and padding-bottom = 12px
},
}));
};
@ -74,7 +74,7 @@ class SectionBodyContent extends React.Component {
};
renderRow = memo(({ index, style }) => {
const { versions, culture } = this.props;
const { versions, culture, onClose } = this.props;
const prevVersion = versions[index > 0 ? index - 1 : index].versionGroup;
let isVersion = true;
@ -85,6 +85,7 @@ class SectionBodyContent extends React.Component {
return (
<div style={style}>
<VersionRow
onClose={onClose}
getFileVersions={this.getFileVersions}
isVersion={isVersion}
key={`${versions[index].id}-${index}`}

View File

@ -1,4 +1,4 @@
import HistoryReactSvgUrl from "PUBLIC_DIR/images/history.react.svg?url";
import HistoryReactSvgUrl from "PUBLIC_DIR/images/history.react.svg?url";
import HistoryFinalizedReactSvgUrl from "PUBLIC_DIR/images/history-finalized.react.svg?url";
import MoveReactSvgUrl from "PUBLIC_DIR/images/move.react.svg?url";
import CheckBoxReactSvgUrl from "PUBLIC_DIR/images/check-box.react.svg?url";
@ -614,6 +614,10 @@ class ContextOptionsStore {
onSelectItem({ id: item.id, isFolder: item.isFolder }, true, false);
};
onShowEditingToast = (t) => {
toastr.error(t("Files:DocumentEdited"));
};
onClickMute = (e, item, t) => {
const data = (e.currentTarget && e.currentTarget.dataset) || e;
const { action } = data;
@ -676,7 +680,7 @@ class ContextOptionsStore {
return { pinOptions, muteOptions };
};
getFilesContextOptions = (item, t, isInfoPanel) => {
const { contextOptions } = item;
const { contextOptions, isEditing } = item;
const { enablePlugins } = this.authStore.settingsStore;
@ -729,7 +733,10 @@ class ContextOptionsStore {
key: "finalize-version",
label: t("FinalizeVersion"),
icon: HistoryFinalizedReactSvgUrl,
onClick: () => this.finalizeVersion(item.id, item.security),
onClick: () =>
isEditing
? this.onShowEditingToast(t)
: this.finalizeVersion(item.id, item.security),
disabled: false,
},
{
@ -750,7 +757,10 @@ class ContextOptionsStore {
key: "finalize-version",
label: t("FinalizeVersion"),
icon: HistoryFinalizedReactSvgUrl,
onClick: () => this.finalizeVersion(item.id),
onClick: () =>
isEditing
? this.onShowEditingToast(t)
: this.finalizeVersion(item.id),
disabled: false,
},
{
@ -776,7 +786,9 @@ class ContextOptionsStore {
key: "move-to",
label: t("Common:MoveTo"),
icon: MoveReactSvgUrl,
onClick: this.onMoveAction,
onClick: isEditing
? () => this.onShowEditingToast(t)
: this.onMoveAction,
disabled: false,
},
{
@ -804,7 +816,9 @@ class ContextOptionsStore {
key: "move-to",
label: t("Common:MoveTo"),
icon: MoveReactSvgUrl,
onClick: this.onMoveAction,
onClick: isEditing
? () => this.onShowEditingToast(t)
: this.onMoveAction,
disabled: false,
},
{
@ -1099,7 +1113,8 @@ class ContextOptionsStore {
? t("Common:Disconnect")
: t("Common:Delete"),
icon: TrashReactSvgUrl,
onClick: () => this.onClickDelete(item, t),
onClick: () =>
isEditing ? this.onShowEditingToast(t) : this.onClickDelete(item, t),
disabled: false,
},
];
@ -1144,7 +1159,7 @@ class ContextOptionsStore {
getGroupContextOptions = (t) => {
const { personal } = this.authStore.settingsStore;
const { selection } = this.filesStore;
const { selection, allFilesIsEditing } = this.filesStore;
const { setDeleteDialogVisible } = this.dialogsStore;
const {
isRecycleBinFolder,
@ -1152,12 +1167,7 @@ class ContextOptionsStore {
isArchiveFolder,
} = this.treeFoldersStore;
const {
pinRooms,
unpinRooms,
deleteRooms,
} = this.filesActionsStore;
const { pinRooms, unpinRooms, deleteRooms } = this.filesActionsStore;
if (isRoomsFolder || isArchiveFolder) {
const isPinOption = selection.filter((item) => !item.pinned).length > 0;
@ -1339,7 +1349,9 @@ class ContextOptionsStore {
key: "move-to",
label: t("Common:MoveTo"),
icon: MoveReactSvgUrl,
onClick: this.onMoveAction,
onClick: allFilesIsEditing
? () => this.onShowEditingToast(t)
: this.onMoveAction,
disabled: isRecycleBinFolder || !moveItems,
},
{
@ -1365,23 +1377,25 @@ class ContextOptionsStore {
key: "delete",
label: t("Common:Delete"),
icon: TrashReactSvgUrl,
onClick: () => {
if (this.settingsStore.confirmDelete) {
setDeleteDialogVisible(true);
} else {
const translations = {
deleteOperation: t("Translations:DeleteOperation"),
deleteFromTrash: t("Translations:DeleteFromTrash"),
deleteSelectedElem: t("Translations:DeleteSelectedElem"),
FileRemoved: t("Files:FileRemoved"),
FolderRemoved: t("Files:FolderRemoved"),
};
onClick: allFilesIsEditing
? () => this.onShowEditingToast(t)
: () => {
if (this.settingsStore.confirmDelete) {
setDeleteDialogVisible(true);
} else {
const translations = {
deleteOperation: t("Translations:DeleteOperation"),
deleteFromTrash: t("Translations:DeleteFromTrash"),
deleteSelectedElem: t("Translations:DeleteSelectedElem"),
FileRemoved: t("Files:FileRemoved"),
FolderRemoved: t("Files:FolderRemoved"),
};
this.filesActionsStore
.deleteAction(translations)
.catch((err) => toastr.error(err));
}
},
this.filesActionsStore
.deleteAction(translations)
.catch((err) => toastr.error(err));
}
},
disabled: !deleteItems || isRootThirdPartyFolder,
},
];

View File

@ -28,6 +28,7 @@ import { isDesktop } from "@docspace/components/utils/device";
import { getContextMenuKeysByType } from "SRC_DIR/helpers/plugins";
import { PluginContextMenuItemType } from "SRC_DIR/helpers/plugins/constants";
import debounce from "lodash.debounce";
import Queue from "queue-promise";
const { FilesFilter, RoomsFilter } = api;
const storageViewAs = localStorage.getItem("viewAs");
@ -129,6 +130,11 @@ class FilesStore {
highlightFile = {};
thumbnails = new Set();
movingInProgress = false;
createNewFilesQueue = new Queue({
concurrent: 5,
interval: 500,
start: true,
});
constructor(
authStore,
@ -272,8 +278,43 @@ class FilesStore {
this.createThumbnail(this.files[foundIndex]);
});
this.createNewFilesQueue.on("resolve", this.onResolveNewFile);
}
onResolveNewFile = (fileInfo) => {
if (!fileInfo) return;
//console.log("onResolveNewFiles", { fileInfo });
if (this.files.findIndex((x) => x.id === fileInfo.id) > -1) return;
console.log("[WS] create new file", { fileInfo });
const newFiles = [fileInfo, ...this.files];
if (
newFiles.length > this.filter.pageCount &&
this.authStore.settingsStore.withPaging
) {
newFiles.pop(); // Remove last
}
const newFilter = this.filter;
newFilter.total += 1;
runInAction(() => {
this.setFilter(newFilter);
this.setFiles(newFiles);
});
this.debouncefetchTreeFolders();
};
debouncefetchTreeFolders = debounce(() => {
this.treeFoldersStore.fetchTreeFolders();
}, 1000);
debounceRemoveFiles = debounce(() => {
this.removeFiles(this.tempActionFilesIds);
}, 1000);
@ -296,34 +337,35 @@ class FilesStore {
//To update a file version
if (foundIndex > -1 && !this.authStore.settingsStore.withPaging) {
this.getFileInfo(file.id);
if (
this.files[foundIndex].version !== file.version ||
this.files[foundIndex].versionGroup !== file.versionGroup
) {
this.files[foundIndex].version = file.version;
this.files[foundIndex].versionGroup = file.versionGroup;
}
this.checkSelection(file);
}
if (foundIndex > -1) return;
const fileInfo = await api.files.getFileInfo(file.id);
setTimeout(() => {
const foundIndex = this.files.findIndex((x) => x.id === file.id);
if (foundIndex > -1) {
//console.log("Skip in timeout");
return null;
}
if (this.files.findIndex((x) => x.id === opt?.id) > -1) return;
console.log("[WS] create new file", fileInfo.id, fileInfo.title);
this.createNewFilesQueue.enqueue(() => {
const foundIndex = this.files.findIndex((x) => x.id === file.id);
if (foundIndex > -1) {
//console.log("Skip in queue");
return null;
}
const newFiles = [fileInfo, ...this.files];
if (
newFiles.length > this.filter.pageCount &&
this.authStore.settingsStore.withPaging
) {
newFiles.pop(); // Remove last
}
const newFilter = this.filter;
newFilter.total += 1;
runInAction(() => {
this.setFilter(newFilter);
this.setFiles(newFiles);
this.treeFoldersStore.fetchTreeFolders();
});
return api.files.getFileInfo(file.id);
});
}, 300);
} else if (opt?.type === "folder" && opt?.id) {
const foundIndex = this.folders.findIndex((x) => x.id === opt?.id);
@ -1550,10 +1592,7 @@ class FilesStore {
const canConvert = this.filesSettingsStore.extsConvertible[item.fileExst];
const isEncrypted = item.encrypted;
const isDocuSign = false; //TODO: need this prop;
const isEditing =
(item.fileStatus & FileStatus.IsEditing) === FileStatus.IsEditing;
// const isFileOwner =
// item.createdBy?.id === this.authStore.userStore.user?.id;
const isEditing = false; // (item.fileStatus & FileStatus.IsEditing) === FileStatus.IsEditing;
const { isRecycleBinFolder, isMy, isArchiveFolder } = this.treeFoldersStore;
@ -1614,7 +1653,7 @@ class FilesStore {
"send-by-email",
"docu-sign",
"version", //category
"finalize-version",
// "finalize-version",
"show-version-history",
"show-info",
"block-unblock-version", //need split

View File

@ -906,7 +906,7 @@ class UploadDataStore {
return Promise.reject(res.data.message);
}
const { uploaded, id: fileId } = res.data.data;
const { uploaded, id: fileId, file: fileInfo } = res.data.data;
let uploadedSize, newPercent;
@ -947,7 +947,6 @@ class UploadDataStore {
});
if (uploaded) {
const fileInfo = await getFileInfo(fileId);
runInAction(() => {
this.files[indexOfFile].action = "uploaded";
this.files[indexOfFile].fileId = fileId;

View File

@ -96,12 +96,13 @@ const ArticleApps = React.memo(({ theme, showText }) => {
title={t("Translations:MobileAndroid")}
/>
<IconButton
onClick={() => window.open(iosLink)}
iconName={IOSReactSvgUrl}
iconHoverName={IOSHoverReactSvgUrl}
size="32"
isFill={false}
title={t("Translations:MobileIos")}
onMouseDown={() => window.open(iosLink)}
isClickable={true}
/>
</div>
</StyledArticleApps>

View File

@ -8,15 +8,11 @@ const getDefaultStyles = ({ $currentColorScheme, $isVersion, theme, index }) =>
path {
fill: ${!$isVersion
? theme.filesVersionHistory.badge.defaultFill
: index === 0
? theme.filesVersionHistory.badge.fill
: $currentColorScheme.main.accent};
: theme.filesVersionHistory.badge.fill};
stroke: ${!$isVersion
? theme.filesVersionHistory.badge.stroke
: index === 0
? theme.filesVersionHistory.badge.fill
: $currentColorScheme.main.accent};
: theme.filesVersionHistory.badge.fill};
}
}

View File

@ -62,7 +62,7 @@ export const PlayerTimelineWrapper = styled.div`
display: flex;
align-items: center;
margin-top: 92px;
margin-top: 12px;
height: 4px;
width: 100%;

View File

@ -83,6 +83,12 @@ export const ControlContainer = styled.div`
export const PlayerControlsWrapper = styled.div`
padding: 0 30px;
width: 100%;
margin-top: 80px;
${isMobile &&
css`
margin-top: 0px;
`}
${isMobileOnly &&
css`

View File

@ -533,6 +533,14 @@ function ViewerPlayer({
const onTouchStart = () => {
if (isPlaying && isVideo) restartToolbarVisibleTimer();
};
const stopPropagation = useCallback(
(event: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
event?.stopPropagation();
},
[]
);
const onTouchMove = () => {
if (isPlaying && isVideo) restartToolbarVisibleTimer();
};
@ -599,8 +607,9 @@ function ViewerPlayer({
$isShow={panelVisible && !isLoading}
onTouchStart={onTouchStart}
onTouchMove={onTouchMove}
onClick={handleClickVideo}
>
<PlayerControlsWrapper>
<PlayerControlsWrapper onClick={stopPropagation}>
<PlayerTimeline
value={timeline}
duration={duration}

View File

@ -24,8 +24,6 @@
// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
using System.Text.RegularExpressions;
namespace ASC.Web.Files.HttpHandlers;
public class ChunkedUploaderHandler
@ -53,6 +51,7 @@ public class ChunkedUploaderHandlerService
private readonly ChunkedUploadSessionHolder _chunkedUploadSessionHolder;
private readonly ChunkedUploadSessionHelper _chunkedUploadSessionHelper;
private readonly SocketManager _socketManager;
private readonly FileDtoHelper _filesWrapperHelper;
private readonly ILogger<ChunkedUploaderHandlerService> _logger;
public ChunkedUploaderHandlerService(
@ -66,7 +65,8 @@ public class ChunkedUploaderHandlerService
InstanceCrypto instanceCrypto,
ChunkedUploadSessionHolder chunkedUploadSessionHolder,
ChunkedUploadSessionHelper chunkedUploadSessionHelper,
SocketManager socketManager)
SocketManager socketManager,
FileDtoHelper filesWrapperHelper)
{
_tenantManager = tenantManager;
_fileUploader = fileUploader;
@ -78,6 +78,7 @@ public class ChunkedUploaderHandlerService
_chunkedUploadSessionHolder = chunkedUploadSessionHolder;
_chunkedUploadSessionHelper = chunkedUploadSessionHelper;
_socketManager = socketManager;
_filesWrapperHelper = filesWrapperHelper;
_logger = logger;
}
@ -139,7 +140,7 @@ public class ChunkedUploaderHandlerService
if (resumedSession.BytesUploaded == resumedSession.BytesTotal)
{
await WriteSuccess(context, ToResponseObject(resumedSession.File), (int)HttpStatusCode.Created);
await WriteSuccess(context, await ToResponseObject(resumedSession.File), (int)HttpStatusCode.Created);
_ = _filesMessageService.Send(resumedSession.File, MessageAction.FileUploaded, resumedSession.File.Title);
await _socketManager.CreateFileAsync(resumedSession.File);
@ -218,10 +219,10 @@ public class ChunkedUploaderHandlerService
context.Response.StatusCode = statusCode;
context.Response.ContentType = "application/json";
return context.Response.WriteAsync(JsonConvert.SerializeObject(new { success, data, message }));
return context.Response.WriteAsync(JsonSerializer.Serialize(new { success, data, message }, SocketManager.GetSerializerSettings()));
}
private static object ToResponseObject<T>(File<T> file)
private async Task<object> ToResponseObject<T>(File<T> file)
{
return new
{
@ -230,7 +231,8 @@ public class ChunkedUploaderHandlerService
version = file.Version,
title = file.Title,
provider_key = file.ProviderKey,
uploaded = true
uploaded = true,
file = await _filesWrapperHelper.GetAsync(file)
};
}
}

View File

@ -150,7 +150,7 @@ public class SocketManager : SocketServiceClient
return JsonSerializer.Serialize(await _folderDtoHelper.GetAsync(folder), GetSerializerSettings()); ;
}
private JsonSerializerOptions GetSerializerSettings()
public static JsonSerializerOptions GetSerializerSettings()
{
var serializerSettings = new JsonSerializerOptions()
{

View File

@ -3031,6 +3031,7 @@ __metadata:
html-webpack-plugin: 5.3.2
json-loader: ^0.5.7
playwright: ^1.18.1
queue-promise: 2.2.1
react-avatar-editor: ^13.0.0
react-colorful: ^5.5.1
react-hotkeys-hook: ^3.4.4
@ -20810,6 +20811,13 @@ __metadata:
languageName: node
linkType: hard
"queue-promise@npm:2.2.1":
version: 2.2.1
resolution: "queue-promise@npm:2.2.1"
checksum: 139c1844225580545a94c5a234fcde33e47941f473525bb0df3dacbdd55724993754f048c25c3a8b98961cd84221913b633397383ef8bc92476237d0dca6d721
languageName: node
linkType: hard
"raf@npm:^3.1.0, raf@npm:^3.4.1":
version: 3.4.1
resolution: "raf@npm:3.4.1"