Merge branch 'release/v1.1.0' of https://github.com/ONLYOFFICE/DocSpace into release/v1.1.0
This commit is contained in:
commit
b85c3dcceb
@ -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
|
||||
|
@ -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
|
||||
|
@ -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" />
|
||||
|
@ -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)));
|
||||
});
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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
|
||||
|
114
common/ASC.Api.Core/Middleware/UnhandledExceptionMiddleware.cs
Normal file
114
common/ASC.Api.Core/Middleware/UnhandledExceptionMiddleware.cs
Normal 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>();
|
||||
}
|
||||
}
|
@ -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" />
|
||||
|
@ -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" />
|
||||
|
@ -23,8 +23,4 @@
|
||||
<ItemGroup>
|
||||
<Folder Include="Properties\" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Telegram.Bot.Extensions.Polling" Version="1.0.2" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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>
|
||||
|
@ -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",
|
||||
|
@ -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."
|
||||
}
|
||||
|
@ -113,5 +113,6 @@
|
||||
"ViewList": "Список",
|
||||
"ViewOnlyRooms": "Просмотр",
|
||||
"ViewTiles": "Плитки",
|
||||
"WithSubfolders": "С подпапками"
|
||||
"WithSubfolders": "С подпапками",
|
||||
"DocumentEdited": "Невозможно выполнить действие, так как документ редактируется."
|
||||
}
|
||||
|
@ -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`
|
||||
|
@ -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>
|
||||
|
@ -45,7 +45,7 @@ const AccessSelector = ({
|
||||
: {};
|
||||
|
||||
return (
|
||||
<StyledAccessSelector>
|
||||
<StyledAccessSelector className="invite-panel_access-selector">
|
||||
<AccessRightSelect
|
||||
selectedOption={selectedOption}
|
||||
onSelect={onSelectAccess}
|
||||
|
@ -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) => {
|
||||
|
@ -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}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
|
@ -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}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
@ -75,7 +75,7 @@ class PureVersionHistoryPanel extends React.Component {
|
||||
</StyledHeaderContent>
|
||||
|
||||
<StyledBody className="version-history-panel-body">
|
||||
<SectionBodyContent />
|
||||
<SectionBodyContent onClose={this.onClose} />
|
||||
</StyledBody>
|
||||
{showProgressBar && (
|
||||
<FloatingButton
|
||||
|
@ -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 {
|
||||
|
@ -16,7 +16,7 @@ const VersionBadge = ({
|
||||
className={className}
|
||||
marginProp="0 8px"
|
||||
displayProp="flex"
|
||||
isVersion={isVersion}
|
||||
isVersion={true}
|
||||
theme={theme}
|
||||
index={index}
|
||||
{...rest}
|
||||
|
@ -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(
|
||||
|
@ -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}`}
|
||||
|
@ -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,
|
||||
},
|
||||
];
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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>
|
||||
|
@ -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};
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -62,7 +62,7 @@ export const PlayerTimelineWrapper = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
margin-top: 92px;
|
||||
margin-top: 12px;
|
||||
|
||||
height: 4px;
|
||||
width: 100%;
|
||||
|
@ -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`
|
||||
|
@ -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}
|
||||
|
@ -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)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -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()
|
||||
{
|
||||
|
@ -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"
|
||||
|
Loading…
Reference in New Issue
Block a user