Merge branch 'develop' into feature/private-virtual-rooms

This commit is contained in:
Maksim Chegulov 2022-08-29 17:39:57 +03:00
commit 3db342ffec
401 changed files with 34063 additions and 3354 deletions

10
build/run/WebPlugins.xml Normal file
View File

@ -0,0 +1,10 @@
<service>
<id>OnlyofficeWebPlugins</id>
<name>ONLYOFFICE WebPlugins Server</name>
<startmode>manual</startmode>
<executable>node</executable>
<arguments>../../common/ASC.WebPlugins/dist/src/main.js</arguments>
<log mode="none"/>
<delayedAutoStart>true</delayedAutoStart>
<onfailure action="restart" delay="5 sec" />
</service>

View File

@ -0,0 +1,9 @@
PUSHD %~dp0..
cd %~dp0../../common/ASC.WebPlugins/
call yarn install --immutable
call yarn build
POPD

View File

@ -28,7 +28,11 @@ namespace ASC.Data.Backup.Core.IntegrationEvents.Events;
[ProtoContract]
public record BackupRestoreRequestIntegrationEvent : IntegrationEvent
{
{
private BackupRestoreRequestIntegrationEvent() : base()
{
StorageParams = new Dictionary<string, string>();
}
public BackupRestoreRequestIntegrationEvent(BackupStorageType storageType,
int tenantId,
Guid createBy,

View File

@ -51,7 +51,7 @@
namespace ASC.Data.Backup.Services;
[Transient]
[Transient(Additional = typeof(RestoreProgressItemExtention))]
public class RestoreProgressItem : BaseBackupProgressItem
{
private readonly IConfiguration _configuration;
@ -237,4 +237,12 @@ public class RestoreProgressItem : BaseBackupProgressItem
{
return MemberwiseClone();
}
}
public static class RestoreProgressItemExtention
{
public static void Register(DIHelper services)
{
services.TryAdd<RestorePortalTask>();
}
}

View File

@ -124,19 +124,27 @@ public class BackupPortalTask : PortalTaskBase
private void DoDump(IDataWriteOperator writer)
{
var databases = new Dictionary<Tuple<string, string>, List<string>>();
using (var connection = DbFactory.OpenConnection())
{
var command = connection.CreateCommand();
command.CommandText = "select id, connection_string from mail_server_server";
ExecuteList(command).ForEach(r =>
{
var connectionString = GetConnectionString((int)r[0], JsonConvert.DeserializeObject<Dictionary<string, object>>(Convert.ToString(r[1]))["DbConnection"].ToString());
try
{
using (var connection = DbFactory.OpenConnection())
{
var command = connection.CreateCommand();
command.CommandText = "show tables";
var tables = ExecuteList(command).Select(r => Convert.ToString(r[0])).ToList();
databases.Add(new Tuple<string, string>(connectionString.Name, connectionString.ConnectionString), tables);
});
command.CommandText = "select id, connection_string from mail_server_server";
ExecuteList(command).ForEach(r =>
{
var connectionString = GetConnectionString((int)r[0], JsonConvert.DeserializeObject<Dictionary<string, object>>(Convert.ToString(r[1]))["DbConnection"].ToString());
var command = connection.CreateCommand();
command.CommandText = "show tables";
var tables = ExecuteList(command).Select(r => Convert.ToString(r[0])).ToList();
databases.Add(new Tuple<string, string>(connectionString.Name, connectionString.ConnectionString), tables);
});
}
}
catch (Exception e)
{
_logger.ErrorWithException(e);
}
using (var connection = DbFactory.OpenConnection())

View File

@ -207,16 +207,22 @@ public class RestorePortalTask : PortalTaskBase
Task.WaitAll(tasks.ToArray());
}
using (var connection = DbFactory.OpenConnection())
{
var command = connection.CreateCommand();
command.CommandText = "select id, connection_string from mail_server_server";
ExecuteList(command).ForEach(r =>
try
{
using (var connection = DbFactory.OpenConnection())
{
var connectionString = GetConnectionString((int)r[0], JsonConvert.DeserializeObject<Dictionary<string, object>>(Convert.ToString(r[1]))["DbConnection"].ToString());
databases.Add(new Tuple<string, string>(connectionString.Name, connectionString.ConnectionString), databasesFromDirs[connectionString.Name]);
});
var command = connection.CreateCommand();
command.CommandText = "select id, connection_string from mail_server_server";
ExecuteList(command).ForEach(r =>
{
var connectionString = GetConnectionString((int)r[0], JsonConvert.DeserializeObject<Dictionary<string, object>>(Convert.ToString(r[1]))["DbConnection"].ToString());
databases.Add(new Tuple<string, string>(connectionString.Name, connectionString.ConnectionString), databasesFromDirs[connectionString.Name]);
});
}
}
catch (Exception e)
{
_logger.ErrorWithException(e);
}
foreach (var database in databases)

View File

@ -31,6 +31,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Amazon.Extensions.S3.Encryption" Version="2.0.3" />
<PackageReference Include="AWSSDK.CloudFront" Version="3.7.5" />
<PackageReference Include="AWSSDK.S3" Version="3.7.8.23" />
<PackageReference Include="Google.Api.Gax" Version="3.7.0" />

View File

@ -132,6 +132,7 @@ public class StorageSettingsHelper
private readonly SettingsManager _settingsManager;
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly ConsumerFactory _consumerFactory;
private readonly IServiceProvider _serviceProvider;
private IDataStore _dataStore;
public StorageSettingsHelper(
@ -142,7 +143,8 @@ public class StorageSettingsHelper
ILogger<StorageSettingsHelper> options,
TenantManager tenantManager,
SettingsManager settingsManager,
ConsumerFactory consumerFactory)
ConsumerFactory consumerFactory,
IServiceProvider serviceProvider)
{
baseStorageSettingsListener.Subscribe();
_storageFactoryConfig = storageFactoryConfig;
@ -152,6 +154,7 @@ public class StorageSettingsHelper
_tenantManager = tenantManager;
_settingsManager = settingsManager;
_consumerFactory = consumerFactory;
_serviceProvider = serviceProvider;
}
public StorageSettingsHelper(
@ -163,8 +166,9 @@ public class StorageSettingsHelper
TenantManager tenantManager,
SettingsManager settingsManager,
IHttpContextAccessor httpContextAccessor,
ConsumerFactory consumerFactory)
: this(baseStorageSettingsListener, storageFactoryConfig, pathUtils, cache, options, tenantManager, settingsManager, consumerFactory)
ConsumerFactory consumerFactory,
IServiceProvider serviceProvider)
: this(baseStorageSettingsListener, storageFactoryConfig, pathUtils, cache, options, tenantManager, settingsManager, consumerFactory, serviceProvider)
{
_httpContextAccessor = httpContextAccessor;
}
@ -219,8 +223,7 @@ public class StorageSettingsHelper
return null;
}
return _dataStore = ((IDataStore)
Activator.CreateInstance(DataStoreConsumer(baseStorageSettings).HandlerType, _tenantManager, _pathUtils, _httpContextAccessor, _options))
return _dataStore = ((IDataStore)_serviceProvider.GetService(DataStoreConsumer(baseStorageSettings).HandlerType))
.Configure(_tenantManager.GetCurrentTenant().Id.ToString(), null, null, DataStoreConsumer(baseStorageSettings));
}

View File

@ -24,6 +24,10 @@
// 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 Amazon.Extensions.S3.Encryption;
using Amazon.Extensions.S3.Encryption.Primitives;
using Amazon.S3.Internal;
namespace ASC.Data.Storage.S3;
[Scope]
@ -39,16 +43,19 @@ public class S3Storage : BaseStorage
private string _recycleDir = string.Empty;
private Uri _bucketRoot;
private Uri _bucketSSlRoot;
private string _region = string.Empty;
private string _region = "";
private string _serviceurl;
private bool _forcepathstyle;
private string _secretAccessKeyId = string.Empty;
private ServerSideEncryptionMethod _sse = ServerSideEncryptionMethod.AES256;
private readonly ServerSideEncryptionMethod _sse = ServerSideEncryptionMethod.AES256;
private bool _useHttp = true;
private bool _lowerCasing = true;
private bool _revalidateCloudFront;
private string _distributionId = string.Empty;
private string _subDir = string.Empty;
private string _distributionId = "";
private string _subDir = "";
private EncryptionMethod _encryptionMethod = EncryptionMethod.None;
private string _encryptionKey;
public S3Storage(
TempStream tempStream,
@ -177,17 +184,17 @@ public class S3Storage : BaseStorage
return SaveAsync(domain, path, stream, contentType, contentDisposition, ACL.Auto);
}
private bool EnableQuotaCheck(string domain)
{
return (QuotaController != null) && !domain.EndsWith("_temp");
}
private bool EnableQuotaCheck(string domain)
{
return (QuotaController != null) && !domain.EndsWith("_temp");
}
public async Task<Uri> SaveAsync(string domain, string path, Stream stream, string contentType,
string contentDisposition, ACL acl, string contentEncoding = null, int cacheDays = 5)
{
var buffered = _tempStream.GetBuffered(stream);
if (EnableQuotaCheck(domain))
if (EnableQuotaCheck(domain))
{
QuotaController.QuotaUsedCheck(buffered.Length);
}
@ -213,6 +220,13 @@ public class S3Storage : BaseStorage
}
};
if (!(client is IAmazonS3Encryption))
{
string kmsKeyId;
request.ServerSideEncryptionMethod = GetServerSideEncryptionMethod(out kmsKeyId);
request.ServerSideEncryptionKeyManagementServiceKeyId = kmsKeyId;
}
if (!WorkContext.IsMono) // System.Net.Sockets.SocketException: Connection reset by peer
{
switch (acl)
@ -273,11 +287,16 @@ public class S3Storage : BaseStorage
var request = new InitiateMultipartUploadRequest
{
BucketName = _bucket,
Key = MakePath(domain, path),
ServerSideEncryptionMethod = _sse
Key = MakePath(domain, path)
};
using var s3 = GetClient();
if (!(s3 is IAmazonS3Encryption))
{
string kmsKeyId;
request.ServerSideEncryptionMethod = GetServerSideEncryptionMethod(out kmsKeyId);
request.ServerSideEncryptionKeyManagementServiceKeyId = kmsKeyId;
}
var response = await s3.InitiateMultipartUploadAsync(request);
return response.UploadId;
@ -908,15 +927,21 @@ public class S3Storage : BaseStorage
if (props.TryGetValue("sse", out var sse) && !string.IsNullOrEmpty(sse))
{
_sse = sse.ToLower() switch
_encryptionMethod = sse.ToLower() switch
{
"none" => ServerSideEncryptionMethod.None,
"aes256" => ServerSideEncryptionMethod.AES256,
"awskms" => ServerSideEncryptionMethod.AWSKMS,
_ => ServerSideEncryptionMethod.None,
"none" => EncryptionMethod.None,
"aes256" => EncryptionMethod.ServerS3,
"awskms" => EncryptionMethod.ServerKms,
"clientawskms" => EncryptionMethod.ClientKms,
_ => EncryptionMethod.None,
};
}
if (props.ContainsKey("ssekey") && !string.IsNullOrEmpty(props["ssekey"]))
{
_encryptionKey = props["ssekey"];
}
_bucketRoot = props.ContainsKey("cname") && Uri.IsWellFormedUriString(props["cname"], UriKind.Absolute)
? new Uri(props["cname"], UriKind.Absolute)
: new Uri($"http://s3.{_region}.amazonaws.com/{_bucket}/", UriKind.Absolute);
@ -1170,10 +1195,16 @@ public class S3Storage : BaseStorage
{
BucketName = _bucket,
Key = destinationKey,
CannedACL = GetDomainACL(newdomain),
ServerSideEncryptionMethod = _sse
CannedACL = GetDomainACL(newdomain)
};
if (!(client is IAmazonS3Encryption))
{
string kmsKeyId;
initiateRequest.ServerSideEncryptionMethod = GetServerSideEncryptionMethod(out kmsKeyId);
initiateRequest.ServerSideEncryptionKeyManagementServiceKeyId = kmsKeyId;
}
if (storageClass != null)
{
initiateRequest.StorageClass = storageClass;
@ -1225,10 +1256,16 @@ public class S3Storage : BaseStorage
DestinationBucket = _bucket,
DestinationKey = destinationKey,
CannedACL = GetDomainACL(newdomain),
ServerSideEncryptionMethod = _sse,
MetadataDirective = metadataDirective,
};
if (!(client is IAmazonS3Encryption))
{
string kmsKeyId;
request.ServerSideEncryptionMethod = GetServerSideEncryptionMethod(out kmsKeyId);
request.ServerSideEncryptionKeyManagementServiceKeyId = kmsKeyId;
}
if (storageClass != null)
{
request.StorageClass = storageClass;
@ -1247,6 +1284,13 @@ public class S3Storage : BaseStorage
private IAmazonS3 GetClient()
{
var encryptionClient = GetEncryptionClient();
if (encryptionClient != null)
{
return encryptionClient;
}
var cfg = new AmazonS3Config { MaxErrorRetry = 3 };
if (!string.IsNullOrEmpty(_serviceurl))
@ -1318,4 +1362,89 @@ public class S3Storage : BaseStorage
}
}
}
private IAmazonS3 GetEncryptionClient()
{
if (!string.IsNullOrEmpty(_encryptionKey))
{
return null;
}
EncryptionMaterialsV2 encryptionMaterials = null;
switch (_encryptionMethod)
{
case EncryptionMethod.ClientKms:
var encryptionContext = new Dictionary<string, string>();
encryptionMaterials = new EncryptionMaterialsV2(_encryptionKey, KmsType.KmsContext, encryptionContext);
break;
//case EncryptionMethod.ClientAes:
// var symmetricAlgorithm = Aes.Create();
// symmetricAlgorithm.Key = Encoding.UTF8.GetBytes(_encryptionKey);
// encryptionMaterials = new EncryptionMaterialsV2(symmetricAlgorithm, SymmetricAlgorithmType.AesGcm);
// break;
//case EncryptionMethod.ClientRsa:
// var asymmetricAlgorithm = RSA.Create();
// asymmetricAlgorithm.FromXmlString(_encryptionKey);
// encryptionMaterials = new EncryptionMaterialsV2(asymmetricAlgorithm, AsymmetricAlgorithmType.RsaOaepSha1);
// break;
}
if (encryptionMaterials == null)
{
return null;
}
var cfg = new AmazonS3CryptoConfigurationV2(SecurityProfile.V2AndLegacy)
{
StorageMode = CryptoStorageMode.ObjectMetadata,
MaxErrorRetry = 3
};
if (!string.IsNullOrEmpty(_serviceurl))
{
cfg.ServiceURL = _serviceurl;
cfg.ForcePathStyle = _forcepathstyle;
}
else
{
cfg.RegionEndpoint = RegionEndpoint.GetBySystemName(_region);
}
cfg.UseHttp = _useHttp;
return new AmazonS3EncryptionClientV2(_accessKeyId, _secretAccessKeyId, cfg, encryptionMaterials);
}
private ServerSideEncryptionMethod GetServerSideEncryptionMethod(out string kmsKeyId)
{
kmsKeyId = null;
var method = ServerSideEncryptionMethod.None;
switch (_encryptionMethod)
{
case EncryptionMethod.ServerS3:
method = ServerSideEncryptionMethod.AES256;
break;
case EncryptionMethod.ServerKms:
method = ServerSideEncryptionMethod.AWSKMS;
if (!string.IsNullOrEmpty(_encryptionKey))
{
kmsKeyId = _encryptionKey;
}
break;
}
return method;
}
private enum EncryptionMethod
{
None,
ServerS3,
ServerKms,
ClientKms,
//ClientAes,
//ClientRsa
}
}

View File

@ -26,19 +26,41 @@
#nullable enable
using System.Reflection;
namespace ASC.EventBus.Serializers;
public class ProtobufSerializer : IIntegrationEventSerializer
{
private readonly SynchronizedCollection<string> _processedProtoTypes;
private readonly int _baseFieldNumber;
private int _baseFieldNumber;
public ProtobufSerializer()
{
{
_processedProtoTypes = new SynchronizedCollection<string>();
_baseFieldNumber = 100;
}
_baseFieldNumber = 100;
Array.ForEach(AppDomain.CurrentDomain.GetAssemblies(), a => BuildTypeModelFromAssembly(a));
}
private void BuildTypeModelFromAssembly(Assembly assembly)
{
var name = assembly.GetName().Name;
if (name == null || !name.StartsWith("ASC."))
{
return;
}
var types = assembly.GetExportedTypes()
.Where(t => t.GetCustomAttributes<ProtoContractAttribute>().Any());
foreach (var type in types)
{
ProcessProtoType(type);
}
}
/// <inheritdoc/>
public byte[] Serialize<T>(T? item)
{
@ -47,8 +69,6 @@ public class ProtobufSerializer : IIntegrationEventSerializer
return Array.Empty<byte>();
}
ProcessProtoType(item.GetType());
using var ms = new MemoryStream();
Serializer.Serialize(ms, item);
@ -58,9 +78,7 @@ public class ProtobufSerializer : IIntegrationEventSerializer
/// <inheritdoc/>
public T Deserialize<T>(byte[] serializedObject)
{
ProcessProtoType(typeof(T));
{
using var ms = new MemoryStream(serializedObject);
return Serializer.Deserialize<T>(ms);
@ -69,8 +87,6 @@ public class ProtobufSerializer : IIntegrationEventSerializer
/// <inheritdoc/>
public object Deserialize(byte[] serializedObject, Type returnType)
{
ProcessProtoType(returnType);
using var ms = new MemoryStream(serializedObject);
return Serializer.Deserialize(returnType, ms);
@ -83,7 +99,7 @@ public class ProtobufSerializer : IIntegrationEventSerializer
return;
}
if (protoType.BaseType == null && protoType.BaseType == typeof(object))
if (protoType.BaseType == null || protoType.BaseType == typeof(object))
{
return;
}
@ -95,9 +111,11 @@ public class ProtobufSerializer : IIntegrationEventSerializer
if (!baseType.GetSubtypes().Any(s => s.DerivedType == itemType))
{
baseType.AddSubType(_baseFieldNumber, protoType);
_baseFieldNumber++;
_processedProtoTypes.Add(protoType.FullName);
}
_processedProtoTypes.Add(protoType.FullName);
}
}

View File

@ -0,0 +1,31 @@
module.exports = {
parser: '@typescript-eslint/parser',
parserOptions: {
project: 'tsconfig.json',
tsconfigRootDir : __dirname,
sourceType: 'module',
},
plugins: ['@typescript-eslint/eslint-plugin'],
extends: [
'plugin:@typescript-eslint/recommended',
'plugin:prettier/recommended',
],
root: true,
env: {
node: true,
jest: true,
},
ignorePatterns: ['.eslintrc.js'],
'prettier/prettier': [
'error',
{
'endOfLine': 'auto',
}
],
rules: {
'@typescript-eslint/interface-name-prefix': 'off',
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/no-explicit-any': 'off',
},
};

35
common/ASC.WebPlugins/.gitignore vendored Normal file
View File

@ -0,0 +1,35 @@
# compiled output
/dist
/node_modules
# Logs
logs
*.log
npm-debug.log*
pnpm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
# OS
.DS_Store
# Tests
/coverage
/.nyc_output
# IDEs and editors
/.idea
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace
# IDE - VSCode
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,7 @@
{
"app": {
"port": 5014,
"appsettings": "../../../../config",
"environment": "Development"
}
}

View File

@ -0,0 +1,33 @@
import * as nconf from "nconf";
import * as path from "path";
import * as fs from "fs";
import * as conf from "./config.json";
nconf.argv().env().file("config", path.join(__dirname, "config.json"));
getAndSaveAppsettings();
export default nconf;
function getAndSaveAppsettings() {
var appsettings = nconf.get("app").appsettings;
if (!path.isAbsolute(appsettings)) {
appsettings = path.join(__dirname, appsettings);
}
var env = nconf.get("app").environment;
var valueEnv = nconf.get(env);
var fileWithEnv = path.join(appsettings, "appsettings." + valueEnv + ".json");
if (fs.existsSync(fileWithEnv)) {
nconf.file("appsettings", fileWithEnv);
} else {
nconf.file("appsettings", path.join(appsettings, "appsettings.json"));
}
nconf.file("pluginsConf", path.join(appsettings, "plugins.json"));
}

View File

@ -0,0 +1,6 @@
{
"$schema": "https://json.schemastore.org/nest-cli",
"collection": "@nestjs/schematics",
"sourceRoot": "src",
"entryFile": "src/main"
}

View File

@ -0,0 +1,70 @@
{
"name": "plugin-system",
"version": "1.0.0",
"description": "Server for docspace-plugins",
"private": true,
"scripts": {
"prebuild": "rimraf dist",
"build": "rimraf dist & nest build",
"start": "rimraf dist & nest start",
"start:dev": "rimraf dist & nest start --watch",
"start:debug": "nest start --debug --watch",
"start:prod": "node dist/main",
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix"
},
"dependencies": {
"@nestjs/common": "^9.0.0",
"@nestjs/core": "^9.0.0",
"@nestjs/platform-express": "^9.0.0",
"@nestjs/typeorm": "^9.0.0",
"mysql2": "^2.3.3",
"nconf": "^0.12.0",
"reflect-metadata": "^0.1.13",
"rimraf": "^3.0.2",
"rxjs": "^7.2.0",
"typeorm": "^0.3.7",
"winston": "^3.8.1",
"winston-daily-rotate-file": "^4.7.1"
},
"devDependencies": {
"@nestjs/cli": "^9.0.0",
"@nestjs/schematics": "^9.0.0",
"@nestjs/testing": "^9.0.0",
"@types/express": "^4.17.13",
"@types/jest": "28.1.4",
"@types/multer": "^1.4.7",
"@types/node": "^16.0.0",
"@types/supertest": "^2.0.11",
"@typescript-eslint/eslint-plugin": "^5.0.0",
"@typescript-eslint/parser": "^5.0.0",
"eslint": "^8.0.1",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-prettier": "^4.0.0",
"jest": "28.1.2",
"prettier": "^2.3.2",
"source-map-support": "^0.5.20",
"supertest": "^6.1.3",
"ts-jest": "28.0.5",
"ts-loader": "^9.2.3",
"ts-node": "^10.0.0",
"tsconfig-paths": "4.0.0",
"typescript": "^4.3.5"
},
"jest": {
"moduleFileExtensions": [
"js",
"json",
"ts"
],
"rootDir": "src",
"testRegex": ".*\\.spec\\.ts$",
"transform": {
"^.+\\.(t|j)s$": "ts-jest"
},
"collectCoverageFrom": [
"**/*.(t|j)s"
],
"coverageDirectory": "../coverage",
"testEnvironment": "node"
}
}

View File

@ -0,0 +1,7 @@
CREATE TABLE IF NOT EXISTS `plugin` (
`id` varchar(200) NOT NULL,
`name` text NOT NULL,
`filename` text NOT NULL,
`isActive` int(11) NOT NULL DEFAULT '1',
PRIMARY KEY (`id`)
) DEFAULT CHARSET=utf8;

View File

@ -0,0 +1,23 @@
import { Module } from "@nestjs/common";
import { TypeOrmModule } from "@nestjs/typeorm";
import { PluginsModule } from "./plugins/plugins.module";
import { Plugin } from "./entities/plugin.entity";
@Module({
imports: [
TypeOrmModule.forRoot({
type: "mysql",
host: "localhost",
port: 3306,
username: "root",
password: "root",
database: "onlyoffice",
entities: [Plugin],
synchronize: true,
}),
PluginsModule,
],
})
export class AppModule {}

View File

@ -0,0 +1,16 @@
import { Entity, Column, PrimaryGeneratedColumn } from "typeorm";
@Entity()
export class Plugin {
@PrimaryGeneratedColumn()
id: number;
@Column()
name: string;
@Column()
filename: string;
@Column({ default: true })
isActive: boolean;
}

View File

@ -0,0 +1,33 @@
import { Injectable, CanActivate, ExecutionContext } from "@nestjs/common";
import { Observable } from "rxjs";
import * as config from "../../config";
const { enabled, allow } = config.default.get("plugins");
@Injectable()
export class PluginGuard implements CanActivate {
canActivate(
context: ExecutionContext
): boolean | Promise<boolean> | Observable<boolean> {
return enabled === "true";
}
}
@Injectable()
export class PluginUploadGuard implements CanActivate {
canActivate(
context: ExecutionContext
): boolean | Promise<boolean> | Observable<boolean> {
return enabled === "true" && allow.includes("upload");
}
}
@Injectable()
export class PluginDeleteGuard implements CanActivate {
canActivate(
context: ExecutionContext
): boolean | Promise<boolean> | Observable<boolean> {
return enabled === "true" && allow.includes("delete");
}
}

View File

@ -0,0 +1,57 @@
import * as winston from "winston";
import "winston-daily-rotate-file";
import * as path from "path";
import * as fs from "fs";
let logpath = process.env.logpath || null;
if (logpath != null) {
if (!path.isAbsolute(logpath)) {
logpath = path.join(__dirname, "..", logpath);
}
}
const fileName = logpath
? path.join(logpath, "plugins.%DATE%.log")
: path.join(__dirname, "..", "..", "..", "..", "Logs", "plugins.%DATE%.log");
const dirName = path.dirname(fileName);
if (!fs.existsSync(dirName)) {
fs.mkdirSync(dirName);
}
const options = {
file: {
filename: fileName,
datePattern: "MM-DD",
handleExceptions: true,
humanReadableUnhandledException: true,
zippedArchive: true,
maxSize: "50m",
maxFiles: "30d",
json: true,
},
console: {
level: "debug",
handleExceptions: true,
json: false,
colorize: true,
},
};
const transports = [
new winston.transports.Console(options.console),
new winston.transports.DailyRotateFile(options.file),
];
export default winston.createLogger({
format: winston.format.combine(
winston.format.timestamp({
format: "YYYY-MM-DD HH:mm:ss",
}),
winston.format.json()
),
transports: transports,
exitOnError: false,
});

View File

@ -0,0 +1,19 @@
import { NestFactory } from "@nestjs/core";
import { AppModule } from "./app.module";
import * as config from "../config";
const winston = require("./log.js");
const port = config.default.get("app").port || 5014;
async function bootstrap() {
try {
const app = await NestFactory.create(AppModule, { cors: false });
// app.enableCors();
await app.listen(port);
} catch (e) {
winston.error(e);
}
}
bootstrap();

View File

@ -0,0 +1,85 @@
import {
Controller,
Param,
Body,
Get,
Post,
UploadedFiles,
UseInterceptors,
Put,
Delete,
UseGuards,
} from "@nestjs/common";
import { AnyFilesInterceptor } from "@nestjs/platform-express";
import { storage } from "src/utils";
import { Plugin } from "src/entities/plugin.entity";
import {
PluginGuard,
PluginUploadGuard,
PluginDeleteGuard,
} from "src/guards/plugin.guard";
import { PluginsService } from "./plugins.service";
import fileFilter from "src/utils/file-filter";
@Controller("/api/2.0/plugins")
@UseGuards(PluginGuard)
export class PluginsController {
constructor(private pluginsService: PluginsService) {}
@Get()
async findAll(): Promise<{ response: Plugin[] }> {
const plugins: Plugin[] = await this.pluginsService.findAll();
return { response: plugins };
}
@Put("activate/:id")
async activate(@Param("id") id: number): Promise<{ response: Plugin }> {
const plugin: Plugin = await this.pluginsService.activate(id);
return { response: plugin };
}
@Post("upload")
@UseGuards(PluginUploadGuard)
@UseInterceptors(
AnyFilesInterceptor({
storage: storage,
fileFilter: fileFilter,
})
)
async upload(
@UploadedFiles() files: Express.Multer.File[]
): Promise<{ response: Plugin | { error: string } }> {
try {
if (files[0]) {
const plugin = await this.pluginsService.upload(
files[0].originalname,
files[0].filename
);
return { response: plugin };
} else {
return {
response: { error: "Invalid file format or file already exists" },
};
}
} catch (e) {
console.log(e);
return {
response: { error: "Invalid file format or file already exists" },
};
}
}
@Delete("delete/:id")
@UseGuards(PluginDeleteGuard)
async delete(@Param("id") id: number) {
await this.pluginsService.delete(id);
}
}
export default PluginsController;

View File

@ -0,0 +1,14 @@
import { Module } from "@nestjs/common";
import { TypeOrmModule } from "@nestjs/typeorm";
import { PluginsController } from "./plugins.controller";
import { PluginsService } from "./plugins.service";
import { Plugin } from "src/entities/plugin.entity";
@Module({
imports: [TypeOrmModule.forFeature([Plugin])],
controllers: [PluginsController],
providers: [PluginsService],
})
export class PluginsModule {}

View File

@ -0,0 +1,100 @@
import { Injectable } from "@nestjs/common";
import { InjectRepository } from "@nestjs/typeorm";
import { Repository } from "typeorm";
import * as path from "path";
import * as fs from "fs";
import * as config from "../../config";
const { path: pathToPlugins } = config.default.get("pluginsConf");
import { Plugin } from "src/entities/plugin.entity";
@Injectable()
export class PluginsService {
constructor(
@InjectRepository(Plugin)
private pluginsRepository: Repository<Plugin>
) {}
findAll(): Promise<Plugin[]> {
return this.pluginsRepository.find();
}
findOne(id: number): Promise<Plugin> {
return this.pluginsRepository.findOneBy({ id });
}
async remove(id: string): Promise<void> {
await this.pluginsRepository.delete(id);
}
async activate(id: number): Promise<Plugin> {
const plugin: Plugin = await this.pluginsRepository.findOneBy({ id });
plugin.isActive = !plugin.isActive;
await this.pluginsRepository.save(plugin);
return plugin;
}
async upload(
originalname: string,
filename: string
): Promise<Plugin | { error: string }> {
const plugin: Plugin = new Plugin();
const dir = path.join(__dirname, pathToPlugins, `${filename}`);
const name = originalname.split(".")[0];
const contents = fs.readFileSync(dir, "utf8");
let isPlugin = true;
isPlugin = contents.includes(`"dependencies":{"docspace-plugin"`);
const splitName = name.split("");
splitName[0].toUpperCase();
isPlugin = contents.includes(`window.Plugins.${splitName.join("")}`);
isPlugin = contents.includes(".prototype.getPluginName=function()");
isPlugin = contents.includes(".prototype.getPluginVersion=function()");
isPlugin = contents.includes(".prototype.activate=function()");
isPlugin = contents.includes(".prototype.deactivate=function()");
plugin.name = name;
plugin.filename = filename;
plugin.isActive = true;
if (!isPlugin) {
fs.unlink(dir, (err) => {
err && console.log(err);
});
return { error: "It is not a plugin" };
}
return plugin;
}
async delete(id: number) {
const plugin: Plugin = await this.pluginsRepository.findOneBy({ id });
const fileName = plugin.filename;
const dir = path.join(__dirname, pathToPlugins, `${fileName}`);
fs.unlink(dir, (err) => {
err && console.log(err);
});
await this.pluginsRepository.delete(id);
}
}

View File

@ -0,0 +1,25 @@
import * as fs from "fs";
import * as path from "path";
import * as config from "../../config";
const { path: pathToPlugins } = config.default.get("pluginsConf");
const fileFilter = (req, file, cb) => {
const pluginsDir = path.join(__dirname, pathToPlugins);
let files = null;
let isUniqName = true;
if (fs.existsSync(pluginsDir)) {
files = fs.readdirSync(pluginsDir);
isUniqName = !files?.includes(file.originalname);
}
if (file.mimetype === "text/javascript" && isUniqName) return cb(null, true);
return cb(null, false);
};
export default fileFilter;

View File

@ -0,0 +1,3 @@
import storage from "./storage";
export { storage };

View File

@ -0,0 +1,24 @@
import { diskStorage } from "multer";
import * as path from "path";
import * as fs from "fs";
import * as config from "../../config";
const { path: pathToPlugins } = config.default.get("pluginsConf");
const storage = diskStorage({
destination: function (req, file, cb) {
const dir = path.join(__dirname, pathToPlugins);
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir);
}
cb(null, dir);
},
filename: function (req, file, cb) {
cb(null, file.originalname);
},
});
export default storage;

View File

@ -0,0 +1,4 @@
{
"extends": "./tsconfig.json",
"exclude": ["node_modules", "test", "dist", "**/*spec.ts"]
}

View File

@ -0,0 +1,22 @@
{
"compilerOptions": {
"module": "commonjs",
"declaration": true,
"removeComments": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"allowSyntheticDefaultImports": true,
"target": "es2017",
"sourceMap": true,
"outDir": "./dist",
"baseUrl": "./",
"incremental": true,
"skipLibCheck": true,
"strictNullChecks": false,
"noImplicitAny": false,
"strictBindCallApply": false,
"forceConsistentCasingInFileNames": false,
"noFallthroughCasesInSwitch": false,
"resolveJsonModule": true
}
}

File diff suppressed because it is too large Load Diff

View File

@ -29,9 +29,9 @@ global using System.Text.RegularExpressions;
global using ASC.Api.Core;
global using ASC.Api.Core.Extensions;
global using ASC.Common;
global using ASC.Common.Caching;
global using ASC.Common.Caching;
global using ASC.Common.Threading;
global using ASC.Common.Utils;
global using ASC.Common.Utils;
global using ASC.Core;
global using ASC.Core.Billing;
global using ASC.Core.Common.EF;
@ -51,6 +51,7 @@ global using ASC.EventBus.Events;
global using ASC.EventBus.Exceptions;
global using ASC.EventBus.Log;
global using ASC.Files.Core;
global using ASC.Files.Core.EF;
global using ASC.Web.Studio.Core.Notify;
global using Autofac;

View File

@ -23,7 +23,7 @@
// 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
var options = new WebApplicationOptions
{
Args = args,
@ -40,6 +40,7 @@ builder.Host.ConfigureDefault(args, (hostContext, config, env, path) =>
}, (ctx, ser, di) =>
{
ser.AddBaseDbContextPool<BackupsContext>();
ser.AddBaseDbContextPool<FilesDbContext>();
});
builder.WebHost.ConfigureDefaultKestrel();

View File

@ -41,12 +41,13 @@ global using ASC.Data.Backup.Core.IntegrationEvents.Events;
global using ASC.Data.Backup.EF.Context;
global using ASC.Data.Backup.Services;
global using ASC.EventBus.Abstractions;
global using ASC.Files.Core.EF;
global using ASC.Web.Api.Routing;
global using ASC.Web.Studio.Core.Backup;
global using ASC.Web.Studio.Core.Notify;
global using ASC.Web.Studio.Utility;
global using Autofac;
global using Autofac;
global using Microsoft.AspNetCore.Builder;
global using Microsoft.AspNetCore.Hosting;

View File

@ -39,6 +39,7 @@ builder.Host.ConfigureDefault(args, (hostContext, config, env, path) =>
}, (ctx, ser, di) =>
{
ser.AddBaseDbContextPool<BackupsContext>();
ser.AddBaseDbContextPool<FilesDbContext>();
});
builder.WebHost.ConfigureDefaultKestrel();

View File

@ -233,5 +233,9 @@
"radicale": {
"admin": "admin@radicale",
"path": "http://127.0.0.1:5232"
}
},
"plugins": {
"enabled": "false",
"allow": ["upload", "delete"]
}
}

View File

@ -617,6 +617,7 @@
"forcepathstyle": "",
"usehttp": "",
"sse": "",
"ssekey": "",
"cdn": "S3Cdn"
}
}

View File

@ -122,6 +122,10 @@ server {
try_files /scripts/$basename /index.html =404;
}
location ~* /static/plugins/ {
try_files /plugins/$basename /index.html =404;
}
}
location /doceditor {
@ -193,6 +197,11 @@ server {
proxy_pass http://localhost:5012;
proxy_set_header X-REWRITER-URL $X_REWRITER_URL;
}
location ~* /plugins {
proxy_pass http://localhost:5014;
proxy_set_header X-REWRITER-URL $X_REWRITER_URL;
}
location ~* /migration {
proxy_pass http://localhost:5034;

5
config/plugins.json Normal file
View File

@ -0,0 +1,5 @@
{
"pluginsConf": {
"path": "../../../../../public/plugins"
}
}

View File

@ -1,37 +1,43 @@
{
"folders": [{
"name": "🌐 root",
"path": "."
}, {
"name": "🚀 @docspace/client",
"path": "packages\\client"
}, {
"name": "🔑 @docspace/login",
"path": "packages\\login"
}, {
"name": "📄 @docspace/editor",
"path": "packages\\editor"
}, {
"name": "📦 @docspace/common",
"path": "packages\\common"
}, {
"name": "📦 @docspace/components",
"path": "packages\\components"
},
],
"settings": {
"window.zoomLevel": 0,
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"liveServer.settings.multiRootWorkspaceName": "✨ docspace",
"cSpell.words": ["docspace", "browserslist", "debuginfo", "doceditor"],
"jest.autoRun": "false"
"folders": [
{
"name": "🌐 root",
"path": "."
},
"extensions": {
"recommendations": [
"folke.vscode-monorepo-workspace",
"orta.vscode-jest",
"firsttris.vscode-jest-runner"
]
{
"name": "🚀 @docspace/client",
"path": "packages\\client"
},
{
"name": "🔑 @docspace/login",
"path": "packages\\login"
},
{
"name": "📄 @docspace/editor",
"path": "packages\\editor"
},
{
"name": "📦 @docspace/common",
"path": "packages\\common"
},
{
"name": "📦 @docspace/components",
"path": "packages\\components"
}
}
],
"settings": {
"window.zoomLevel": 0,
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"liveServer.settings.multiRootWorkspaceName": "✨ docspace",
"cSpell.words": ["docspace", "browserslist", "debuginfo", "doceditor"],
"jest.autoRun": "false"
},
"extensions": {
"recommendations": [
"folke.vscode-monorepo-workspace",
"orta.vscode-jest",
"firsttris.vscode-jest-runner"
]
}
}

View File

@ -25,12 +25,14 @@
"storybook-build": "yarn workspace @docspace/components run storybook-build",
"test": "yarn workspace @docspace/components test",
"wipe": "shx rm -rf node_modules yarn.lock packages/**/node_modules",
"debug-info": "auto-changelog --unreleased-only --template debuginfo --output public/debuginfo.md",
"e2e.test": "yarn workspaces foreach -vptiR --from '{@docspace/client,@docspace/login}' run test:sequential",
"e2e.test:sequential": "yarn workspace @docspace/client test:sequential && yarn workspace @docspace/login test:sequential",
"e2e.test:model": "yarn workspace @docspace/client test:model && yarn workspace @docspace/login test:model",
"e2e.test:translation": "yarn workspaces foreach -vptiR --from '{@docspace/client,@docspace/login}' run test:translation:model"
},
"devDependencies": {
"auto-changelog": "file:./packages/auto-changelog-2.3.1.tgz",
"shx": "^0.3.3",
"terser": "^5.8.0"
},

Binary file not shown.

View File

@ -42,6 +42,7 @@
"copy-to-clipboard": "^3.3.1",
"file-saver": "^2.0.5",
"firebase": "^8.10.0",
"react-avatar-editor": "^13.0.0",
"react-hotkeys-hook": "^3.4.4",
"react-markdown": "^7.0.1",
"react-smartbanner": "^5.1.4",

View File

@ -1,7 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M11 2.99984C11 2.44756 10.5523 1.99984 10 1.99984H9.97954C9.42725 1.99984 8.97954 2.44756 8.97954 2.99984V3.99984C8.97954 4.55213 9.42725 4.99984 9.97954 4.99984H10C10.5523 4.99984 11 4.55213 11 3.99984V2.99984Z" fill="#657077"/>
<path d="M13.3134 0.295272C13.1257 0.106281 12.8703 0 12.6039 0H1C0.447716 0 0 0.447715 0 1V15C0 15.5523 0.447715 16 1 16H15C15.5523 16 16 15.5523 16 15V3.41225C16 3.14819 15.8956 2.89486 15.7095 2.70752L13.3134 0.295272ZM3 2C3 1.44772 3.44772 1 4 1H11C11.5523 1 12 1.44772 12 2V5C12 5.55228 11.5523 6 11 6H4C3.44772 6 3 5.55228 3 5V2ZM13 14C13 14.5523 12.5523 15 12 15H4C3.44772 15 3 14.5523 3 14V9C3 8.44771 3.44772 8 4 8H12C12.5523 8 13 8.44772 13 9V14Z" fill="#657077"/>
<path d="M4 11.5C4 11.7761 4.22386 12 4.5 12H8.50001C8.77615 12 9.00001 11.7761 9 11.5C9 11.2239 8.77614 11 8.5 11H4.50001C4.22386 11 4.00001 11.2239 4 11.5Z" fill="#657077"/>
<path d="M4 9.5C4 9.77614 4.22386 10 4.5 10H6.5C6.77614 10 7 9.77614 7 9.5C7 9.22386 6.77614 9 6.5 9H4.5C4.22386 9 4 9.22386 4 9.5Z" fill="#657077"/>
<path d="M10.5 12C10.635 12 10.76 11.9447 10.855 11.8541C10.945 11.7585 11 11.6277 11 11.4969C11 11.366 10.945 11.2347 10.855 11.1396C10.67 10.9535 10.335 10.9535 10.15 11.1396C10.055 11.2347 10 11.3605 10 11.4969C10 11.6327 10.0545 11.7585 10.145 11.8541C10.2395 11.9447 10.3645 12 10.5 12Z" fill="#657077"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M14 14V5.09509L10.9049 2H10V5C10 5.55228 9.55228 6 9 6H5C4.44772 6 4 5.55228 4 5V2H2V14H3V9C3 8.44771 3.44772 8 4 8H12C12.5523 8 13 8.44771 13 9V14H14ZM12 16H15C15.5523 16 16 15.5523 16 15V4.68088C16 4.41566 15.8946 4.16131 15.7071 3.97377L12.0262 0.292893C11.8387 0.105357 11.5843 0 11.3191 0H9H5H1C0.447715 0 0 0.447715 0 1V15C0 15.5523 0.447715 16 1 16H4H12ZM11 14V10H5V14H11ZM6 2H8V4H6V2Z" fill="#657077"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 563 B

View File

@ -0,0 +1,3 @@
<svg width="216" height="216" viewBox="0 0 216 216" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M144.761 0.744576V71.7716H215.787V72.7315H144.761L144.761 143.758H215.787V144.718H144.761V215.745H143.802V144.718H72.772V215.745H71.8122V144.718H0.78653V143.758H71.8122L71.8122 72.7315H0.78653V71.7716H71.8122V0.744576H72.772V71.7716H143.802V0.744576H144.761ZM72.772 72.7315L72.772 143.758H143.802V72.7315H72.772ZM2.77727 0.744576H30.6476V2.73532H2.77727V30.6057H0.78653V2.73532V0.744576H2.77727ZM185.925 0.744576H213.796H215.787V2.73532L215.787 30.6057H213.796V2.73532H185.925V0.744576ZM185.925 213.754H213.796V185.883H215.787L215.787 213.754V215.745H213.796H185.925V213.754ZM2.77727 213.754H30.6476V215.745H2.77727H0.78653V213.754V185.883H2.77727V213.754Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 829 B

View File

@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M8 2C6.34287 2 4.84424 2.67048 3.75736 3.75736L3.74997 3.76475L3.74343 3.77102L3.74331 3.77114L3.74326 3.77118L3.74296 3.77147L3.7414 3.77298L3.72851 3.78563C3.71606 3.79793 3.696 3.81791 3.66896 3.84542C3.632 3.88303 3.58201 3.93468 3.52058 4H5V6H1C0.447715 6 0 5.55228 0 5V1H2V2.69795C2.10045 2.58976 2.18243 2.50467 2.24255 2.44351C2.27684 2.40861 2.30404 2.38149 2.3235 2.36228L2.34682 2.33941L2.34903 2.33727C3.79519 0.894098 5.79413 0 8 0C12.4183 0 16 3.58172 16 8C16 12.4183 12.4183 16 8 16C3.58172 16 0 12.4183 0 8H2C2 11.3137 4.68629 14 8 14C11.3137 14 14 11.3137 14 8C14 4.68629 11.3137 2 8 2ZM7 3.5V9C7 9.55228 7.44772 10 8 10H11V8H9V3.5H7Z" fill="#657077"/>
</svg>

After

Width:  |  Height:  |  Size: 822 B

View File

@ -49,8 +49,6 @@
"DeactivateOrDeletePortal": "Portalı deaktivləşdirin və ya silin.",
"Disabled": "Deaktiv edildi",
"DocumentsAdministratorsCan": "Sənəd administratorları Dropbox, Box və digər hesablarını Ümumi Sənədlərlə əlaqələndirə və bu bölmədə giriş hüququ təyin edə bilərlər",
"DocumentsModule": "Sənəd modulu",
"DocumentsModuleDescription": "Ehtiyat nüsxələr Ümumi sənədlər qovluğunda yadda saxlanılacaq.",
"DownloadCopy": "Nüsxəni endirin",
"Employees": "istifadəçilər",
"EmptyBackupList": "Hələ heç bir ehtiyat nüsxəsi yaradılmayıb. Onların bu siyahıda görünməsi üçün bir və ya bir neçə ehtiyat nüsxəsi yaradın.",

View File

@ -49,8 +49,6 @@
"DeactivateOrDeletePortal": "Деактивирай или изтрий портал.",
"Disabled": "Деактивиран",
"DocumentsAdministratorsCan": "Администраторите на документи могат да свързват Dropbox, Box и други профили към Общите Документи и да задават права за достъп в този раздел",
"DocumentsModule": "Модул 'Документи'",
"DocumentsModuleDescription": "Архивното копие ще бъде запазено в папката Общи документи.",
"DownloadCopy": "Изтеглете копието",
"Employees": "потребители",
"EmptyBackupList": "Все още не са създадени резервни копия. Създайте едно или повече резервни копия, които да се показват в този списък.",

View File

@ -49,8 +49,6 @@
"DeactivateOrDeletePortal": "Deaktivovat nebo odstranit portál.",
"Disabled": "Deaktivováno",
"DocumentsAdministratorsCan": "Správci dokumentů mohou propojit účty Dropbox, Box a další účty se společnými dokumenty a nastavit přístupová práva v této sekci",
"DocumentsModule": "Modul Dokumenty",
"DocumentsModuleDescription": "Záloha bude uložena ve složce Společné dokumenty.",
"DownloadCopy": "Stáhnout kopii",
"Employees": "uživatelé",
"EmptyBackupList": "Zatím nebyly vytvořeny žádné zálohy. Vytvořte jednu nebo více záloh, aby byly zobrazeny v seznamu.",

View File

@ -49,8 +49,6 @@
"DeactivateOrDeletePortal": "Deaktivieren oder entfernen Sie das Portal.",
"Disabled": "Deaktiviert",
"DocumentsAdministratorsCan": "Administratoren von Dokumenten können Dropbox, Box und andere Konten mit gemeinsamen Dokumenten verbinden und Zugriffsrechte in dieser Sektion einstellen",
"DocumentsModule": "Dokumentemodul",
"DocumentsModuleDescription": "Die Sicherungskopie wird im Ordner 'Allgemeine Dokumente' gespeichert.",
"DownloadCopy": "Die Sicherungskopie herunterladen",
"Employees": "Benutzer",
"EmptyBackupList": "Keine Sicherungskopien wurden noch erstellt. Erstellen Sie eine oder mehrere Sicherungskopien, damit sie in dieser Liste erscheint.",

View File

@ -49,8 +49,6 @@
"DeactivateOrDeletePortal": "Απενεργοποίηση ή διαγραφή πύλης.",
"Disabled": "Απενεργοποιημένο",
"DocumentsAdministratorsCan": "Οι διαχειριστές εγγράφων μπορούν να συνδέσουν τους λογαριασμούς Dropbox, Box και άλλους λογαριασμούς με τα κοινά έγγραφα και να ορίσουν δικαιώματα πρόσβασης σε αυτό το τμήμα",
"DocumentsModule": "Ενότητα εγγράφων",
"DocumentsModuleDescription": "Το αντίγραφο ασφαλείας θα αποθηκευτεί στο φάκελο Κοινά έγγραφα.",
"DownloadCopy": "Κατεβάστε το αντίγραφο",
"Employees": "χρήστες",
"EmptyBackupList": "Δεν έχουν δημιουργηθεί ακόμη αντίγραφα ασφαλείας. Δημιουργήστε ένα ή περισσότερα αντίγραφα ασφαλείας για να εμφανιστούν σε αυτήν τη λίστα.",

View File

@ -0,0 +1,41 @@
{
"CreateRoom": "Create room",
"ChooseRoomType": "Choose room type",
"RoomEditing": "Room editing",
"FillingFormsRoomTitle": "Filling forms room",
"CollaborationRoomTitle": "Collaboration room",
"ReviewRoomTitle": "Review room",
"ViewOnlyRoomTitle": "View-only room",
"CustomRoomTitle": "Custom room",
"FillingFormsRoomDescription": "Build, share and fill document templates or work with the ready presets to quickly create documents of any type",
"CollaborationRoomDescription": "Collaborate on one or multiple documents with your team",
"ReviewRoomDescription": "Request a review or comments on the documents",
"ViewOnlyRoomDescription": "Share any ready documents, reports, documentation, and other files for viewing",
"CustomRoomDescription": "Apply your own settings to use this room for any custom purpose",
"NamePlaceholder": "Enter name",
"TagsPlaceholder": "Add a tag",
"CreateTagOption": "Create tag",
"MakeRoomPrivateTitle": "Make the Room Private",
"MakeRoomPrivateDescription": "All files in this room will be encrypted",
"MakeRoomPrivateLimitationsWarningDescription": "With this feature, you can invite only existing users on the portal. After creating a room, you will not be able to change the list of users.",
"ThirdPartyStorageTitle": "Third party storage",
"ThirdPartyStorageDescription": "Use third-party services as data storage for this room. A new folder for storing this rooms data will be created in the connected storage",
"ThirdPartyStorageComboBoxPlaceholder": "Select storage",
"ThirdPartyStorageNoStorageAlert": "No storage is connected in the portal Settings. Go to the Integrations section to enable data storage on a third-party",
"ThirdPartyStorageNoStorageAlertLink": "Third-party services",
"ThirdPartyStorageRememberChoice": "Remember this choice for new rooms",
"ThirdPartyStoragePermanentSettingDescription": "Files are stored in a third-party {{thirdpartyTitle}} storage in the \"{{thirdpartyFolderName}}\" folder.\n<strong>{{thirdpartyPath}}</strong>",
"FolderNameTitle": "Folder Name",
"FolderNameDescription": "A new folder for storing this rooms data will be created in the connected storage",
"DropzoneTitleLink": "Select new image",
"DropzoneTitleSecondary": "or drop file here",
"DropzoneTitleExsts": "(JPG or PNG, max 1 MB)"
}

View File

@ -55,7 +55,6 @@
"NewRoom": "New room",
"NewSpreadsheet": "New spreadsheet",
"NoSubfolders": "No subfolders",
"NoTag": "No tag",
"Open": "Open",
"OpenLocation": "Open location",
"Presentation": "Presentation",

View File

@ -7,6 +7,14 @@
"AccessRightsSubTitle": "This section allows you to transfer portal owner rights and manage administrator access rights.",
"AccessRightsUsersFromList": "{{users}} from the list",
"AccessSettings": "Access settings",
"AmazonServiceTip": "This is an optional property; change it only if you want to try a different service endpoint",
"AmazonBucketTip": "Enter the unique name of the Amazon S3 bucket where you want to store your backups",
"AmazonRegionTip": "Enter the AWS region where your Amazon bucket resides",
"AmazonHTTPTip": "If this property is set to true, the client attempts to use HTTP protocol, if the target endpoint supports it. By default, this property is set to false",
"AmazonForcePathStyleTip": "When true, requests will always use path style addressing",
"AmazonSSETip": "The Server-side encryption algorithm used when storing this object in S3",
"AmazonSSE": "Server-Side Encryption",
"AmazonCSE": "Client-Side Encryption",
"AddAdmins": "Add admins",
"AddAllowedIP": "Add allowed IP address",
"AddName": "Add Name",
@ -50,11 +58,10 @@
"CustomTitlesWelcome": "Welcome Page Settings",
"Customization": "Customization",
"CustomizationDescription": "This subsection allows you to change the look and feel of your portal. You can use your own company logo, name and text to match your organization brand.",
"DataBackup": "Data backup",
"DeactivateOrDeletePortal": "Deactivate or delete portal.",
"Disabled": "Disabled",
"DocumentsAdministratorsCan": "Documents administrators can link Dropbox, Box, and other accounts to Common Documents and set up access rights in this section",
"DocumentsModule": "Documents module",
"DocumentsModuleDescription": "Backup will be saved in the Common documents folder.",
"DownloadCopy": "Download the copy",
"Employees": "users",
"EmptyBackupList": "No backups have been created yet. Create one or more backups for them to appear in this list.",
@ -120,13 +127,15 @@
"ProductUserOpportunities": "View profiles and groups",
"RecoveryFileNotSelected": "Recovery error. Recovery file not selected",
"RegistrationDate": "Registration date",
"RestoreBackup": "Data Restore",
"RestoreBackup": "Restore",
"RestoreBackupDescription": "Use this option to restore your portal from the previously saved backup file.",
"RestoreBackupHelp": "<strong>Data Restore</strong> option is used to restore your previously saved portal data (from a local server or SaaS portal).",
"RestoreBackupHelpNote": "Select the storage where the data is saved, enter necessary details and check the <strong>Send notification to portal users</strong> to alert your portal users about the backup/restore operations.",
"RestoreBackupResetInfoWarningText": "All current passwords will be reset. Portal users will get an email with the access restoration link.",
"RestoreBackupWarningText": "The portal will become unavailable during the restore process. After the restore is complete all the changes made after the date of the selected restore point will be lost.",
"RestoreDefaultButton": "Restore to Default",
"RoomsModule": "Backup room",
"RoomsModuleDescription": "You may create a new room specifically for the backup, choose one of the existing rooms, or save the copy in their {{roomName}} room.",
"SelectFileInGZFormat": "Select the file in .GZ format",
"SendNotificationAboutRestoring": "Send notification about portal restoring to users",
"ServerSideEncryptionMethod": "Server Side Encryption Method",
@ -158,7 +167,7 @@
"ThirdPartyTitle": "Third-party services",
"ThirdPartyTitleDescription": "With Authorization keys, you can connect third-party services to your portal. Sign in easily with Facebook, Twitter, or LinkedIn; add Dropbox, OneDrive, etc. to work with files stored there from the Documents module.",
"ThirdPartyResource": "Third-party resource",
"ThirdPartyResourceDescription": "Backup can be saved to your third-party account (Dropbox, Box.com, OneDrive or Google Drive). You need to connect your third-party account (Dropbox, Box.com, OneDrive or Google Drive) to the Common documents folder before you will be able to save your backup there.",
"ThirdPartyResourceDescription": "Backup can be saved to your third-party account (Dropbox, Box.com, OneDrive or Google Drive). You need to connect your third-party account (Dropbox, Box.com, OneDrive or Google Drive) before you will be able to save your backup there.",
"ThirdPartyStorage": "Third-party storage",
"ThirdPartyStorageDescription": "Backup can be saved to a third-party storage. Before, you need to connect the corresponding service in the 'Integration' section. Otherwise, the following settings will be inactive.",
"ThirdPartyTitleDescription": "With Authorization keys, you can connect third-party services to your portal. Sign in easily with Facebook, Twitter, or LinkedIn; add Dropbox, OneDrive, etc. to work with files stored there from the Documents module.",

View File

@ -49,8 +49,6 @@
"DeactivateOrDeletePortal": "Desactivar o eliminar el portal.",
"Disabled": "Deshabilitado",
"DocumentsAdministratorsCan": "Los administradores de Documentos pueden conectar Dropbox, Box y otras cuentas a los Documentos Comunes y configurar los derechos de acceso en esta sección",
"DocumentsModule": "Documentos del módulo",
"DocumentsModuleDescription": "La copia de seguridad se guardará en la carpeta Documentos comunes.",
"DownloadCopy": "Descargar la copia",
"Employees": "usuarios",
"EmptyBackupList": "No hay ningunas copias de seguridad creadas todavía. Cree por lo menos una copia para que se muestre en esta lista.",

View File

@ -49,8 +49,6 @@
"DeactivateOrDeletePortal": "Ota portaali pois käytöstä tai poista se.",
"Disabled": "Poistettu käytöstä",
"DocumentsAdministratorsCan": "Tässä osiossa asiakirjojen järjestelmänvalvojat voivat linkittää Dropbox-, Box- ja muut tilit yhteisiin asiakirjoihin ja määrittää käyttöoikeudet",
"DocumentsModule": "Asiakirjamoduuli",
"DocumentsModuleDescription": "Varmuuskopio tallennetaan Yleiset tiedostot -kansioon.",
"DownloadCopy": "Lataa kopio",
"Employees": "Käyttäjät",
"EmptyBackupList": "Ei varmuuskopioita. Luo yksi tai useampi varmuuskopio ja ne näkyvät tässä listassa.",

View File

@ -49,8 +49,6 @@
"DeactivateOrDeletePortal": "Désactiver ou supprimer le portail.",
"Disabled": "Désactivé",
"DocumentsAdministratorsCan": "Les administrateurs de documents peuvent connecter Dropbox, Box et d'autres comptes aux documents communs et définir les droits d'accès dans cette section.",
"DocumentsModule": "Module Documents",
"DocumentsModuleDescription": "La sauvegarde sera enregistrée dans le dossier des documents communs.",
"DownloadCopy": "Télécharger la copie",
"Employees": "Utilisateurs",
"EmptyBackupList": "Aucune sauvegarde n'a encore été créée. Créez une ou plusieurs sauvegardes pour qu'elles apparaissent dans cette liste.",

View File

@ -49,8 +49,6 @@
"DeactivateOrDeletePortal": "Ապագործունացնել կամ ջնջել կայքէջը",
"Disabled": "Անջատված է",
"DocumentsAdministratorsCan": "Փաստաթղթերի ադմինիստրատորները կարող են կապել Dropbox, Box, և այլ հաշիվներ Ընդհանուր փաստաթղթեր-ում և սահմանել մուտքի իրավունքներ այս բաժնում",
"DocumentsModule": "Փաստաթղթերի մոդուլ",
"DocumentsModuleDescription": "Կրկնօրինակը կպահվի Ընդհանուր փաստաթղթերի պանակում.",
"DownloadCopy": "Ներբեռնել պատճենը",
"Employees": "Օգտվողներ",
"EmptyBackupList": "Պահուստային պատճեններ դեռ չեն ստեղծվել:Ստեղծեք մեկ կամ մի քանի կրկնօրինակներ, որպեսզի դրանք հայտնվեն այս ցանկում։",

View File

@ -49,8 +49,6 @@
"DeactivateOrDeletePortal": "Disattivare o cancellare il portale",
"Disabled": "Disattivato",
"DocumentsAdministratorsCan": "Gli amministratori dei documenti possono collegare Dropbox, Box e altri account ai Documenti Comuni ed impostare i diritti di accesso alla sezione",
"DocumentsModule": "Modulo Documenti",
"DocumentsModuleDescription": "Il backup verrà salvato nella cartella Documenti comuni.",
"DownloadCopy": "Scaricare la copia",
"Employees": "utenti",
"EmptyBackupList": "Nessun backup creato. Crea almeno una copia di backup per visualizzarla in questo elenco.",

View File

@ -49,8 +49,6 @@
"DeactivateOrDeletePortal": "ポータルを無効にする、または削除する。",
"Disabled": "無効",
"DocumentsAdministratorsCan": "ドキュメントの管理者は、DropboxやBoxなどのアカウントをコモン・ドキュメントにリンクし、このセクションでアクセス権を設定することができます。",
"DocumentsModule": "「ドキュメント」モジュール",
"DocumentsModuleDescription": "バックアップは「共通ドキュメント」フォルダに保存されます。",
"DownloadCopy": "コピーを保存",
"Employees": "ユーザー",
"EmptyBackupList": "バックアップはまだ作成されていません。バックアップを作成すると、このリストに表示されるようになります。",

View File

@ -49,8 +49,6 @@
"DeactivateOrDeletePortal": "포털 비활성화 또는 삭제.",
"Disabled": "비활성화",
"DocumentsAdministratorsCan": "문서 관리자는 공통 문서에 Dropbox, Box 및 기타 계정을 연결하고 이 섹션에 대한 액세스 권리를 설정할 수 있습니다",
"DocumentsModule": "문서 모듈",
"DocumentsModuleDescription": "백업이 공통 문서 폴더에 저장됩니다.",
"DownloadCopy": "복사본 다운로드",
"Employees": "사용자",
"EmptyBackupList": "아직 백업이 생성되지 않았습니다. 이 목록에 표시될 하나 이상의 백업을 생성하세요.",

View File

@ -48,8 +48,6 @@
"DeactivateOrDeletePortal": "ປິດການນຳໃຊ້ ຫຼື ລືບສາຍ.",
"Disabled": "ປິດສິດ",
"DocumentsAdministratorsCan": "ຜູ້ດູແລລະບົບເອກະສານສາມາດເຊື່ອມຕໍ່ Dropbox, Box ແລະ ບັນຊີອື່ນໆ ກັບເອກະສານທົ່ວໄປ ແລະ ຕັ້ງຄ່າການເຂົ້າເຖິງສິດສ່ວນນີ້",
"DocumentsModule": "ເອກະສານ ໂມດູນ",
"DocumentsModuleDescription": "ການສໍາຮອງຂໍ້ມູນຈະຖືກບັນທຶກໄວ້ໃນໂຟນເດີເອກະສານທົ່ວໄປ.",
"DownloadCopy": "ດາວໂຫລດ ໄດ້ ສຳເນົາ",
"Employees": "ຜຸ້ໃຊ້",
"EmptyBackupList": "ຍັງ​ບໍ່​ມີ​ການ​ສ້າງ​ການ​ສໍາ​ຮອງ​ຂໍ້​ມູນ​ໃດໆ​ເທື່ອ. ສ້າງ​ຫນຶ່ງ​ຫຼື​ຫຼາຍ​ສໍາ​ຮອງ​ຂໍ້​ມູນ​ສໍາ​ລັບ​ການ​ໃຫ້​ເຂົາ​ເຈົ້າ​ປາ​ກົດ​ຢູ່​ໃນ​ບັນ​ຊີ​ລາຍ​ການ​ນີ້​.",

View File

@ -49,8 +49,6 @@
"DeactivateOrDeletePortal": "Deaktivizēt vai dzēst portālu.",
"Disabled": "Atspējots",
"DocumentsAdministratorsCan": "Dokumentu administratori var saistīt Dropbox, Box un citus kontus ar kopējiem dokumentiem un iestatīt piekļuves tiesības šajā sadaļā",
"DocumentsModule": "Dokumentu modulis",
"DocumentsModuleDescription": "Dublējums tiks saglabāts mapē Kopējie dokumenti.",
"DownloadCopy": "Lejupielādēt kopiju",
"Employees": "lietotāji",
"EmptyBackupList": "Vēl nav izveidotas rezerves kopijas. Izveidojiet vismaz vienu rezerves kopiju, lai tā parādītos šajā sarakstā.",

View File

@ -49,8 +49,6 @@
"DeactivateOrDeletePortal": "Portaal deactiveren of verwijderen.",
"Disabled": "Uitgeschakeld",
"DocumentsAdministratorsCan": "Documentenbeheerders kunnen Dropbox-, Box-, en andere accounts aan Gemeenschappelijke Documenten koppelen en toegangsrechten instellen in dit gedeelte",
"DocumentsModule": "Documentenmodule",
"DocumentsModuleDescription": "Back-up zal worden opgeslagen in de Gemeenschappelijke documenten map.",
"DownloadCopy": "Download de kopie",
"Employees": "gebruikers",
"EmptyBackupList": "Er zijn nog geen back-ups gemaakt. Maak een of meer back-ups om ze in de lijst te laten zien.",

View File

@ -49,8 +49,6 @@
"DeactivateOrDeletePortal": "Dezaktywuj lub usuń portal.",
"Disabled": "Wyłączono",
"DocumentsAdministratorsCan": "Administratorzy dokumentów mogą podłączyć Dropbox, Box oraz inne konta do Wspólnych Dokumentów i skonfigurować prawa dostępu w tej sekcji",
"DocumentsModule": "Moduł Dokumenty",
"DocumentsModuleDescription": "Kopia zapasowa zostanie zapisana w katalogu Wspólne dokumenty.",
"DownloadCopy": "Pobierz kopię",
"Employees": "użytkownicy",
"EmptyBackupList": "Nie utworzono jeszcze kopii zapasowych. Utwórz jedną lub więcej kopii zapasowych, aby je wyświetlić na tej liście.",

View File

@ -49,8 +49,6 @@
"DeactivateOrDeletePortal": "Desative ou exclua o portal.",
"Disabled": "Desabilitado",
"DocumentsAdministratorsCan": "Os administradores de documentos podem vincular Dropbox, Box e outras contas a Documentos Comuns e configurar direitos de acesso nesta seção",
"DocumentsModule": "Módulo Documentos",
"DocumentsModuleDescription": "O backup será salvo na pasta de Documentos comuns.",
"DownloadCopy": "Baixe a cópia",
"Employees": "Usuários",
"EmptyBackupList": "Nenhum backup foi criado ainda, crie um ou mais backups para eles aparecerem nesta lista.",

View File

@ -49,8 +49,6 @@
"DeactivateOrDeletePortal": "Desativar ou eliminar portal.",
"Disabled": "Desativado",
"DocumentsAdministratorsCan": "Os administradores dos documentos podem ligar a Dropbox, Box, e outras contas a Documentos Comuns e configurar os direitos de acesso nesta secção",
"DocumentsModule": "Módulo dos documentos",
"DocumentsModuleDescription": "A cópia de segurança será guardada na pasta de Documentos comuns.",
"DownloadCopy": "Fazer download da cópia",
"Employees": "utilizadores",
"EmptyBackupList": "Ainda não foi criada nenhuma cópia de segurança. Crie uma ou mais cópias de segurança para que elas apareçam na lista.",

View File

@ -49,8 +49,6 @@
"DeactivateOrDeletePortal": "Dezactivarea sau ștergerea portalului.",
"Disabled": "Dezactivat",
"DocumentsAdministratorsCan": "Administratorii modulului Documente pot conecta Dropbox, Box și alte conturile la Documente comune și seta drepturile de acces din acestă secțiune.",
"DocumentsModule": "Modulul Documente",
"DocumentsModuleDescription": "Copia de rezervă va fi salvată în folderul Comun din modul Documente.",
"DownloadCopy": "Descărcare copie",
"Employees": "utilizatori",
"EmptyBackupList": "Nu există nicio copie de rezervă salvată. Creați una sau mai multe copii de rezervă pentru ca acestea să apară în lista.",

View File

@ -0,0 +1,41 @@
{
"CreateRoom": "Создание комнаты",
"ChooseRoomType": "Выберите тип комнаты",
"RoomEditing": "Редактирование комнаты",
"FillingFormsRoomTitle": "Комната для заполнения форм",
"CollaborationRoomTitle": "Комната для совместного редактирования",
"ReviewRoomTitle": "Комната для рецензирования",
"ViewOnlyRoomTitle": "Комната для просмотра",
"CustomRoomTitle": "Пользовательская комната",
"FillingFormsRoomDescription": "Данная комната подойдет для сбора форм/анкет/тестов и тд.",
"CollaborationRoomDescription": "Совместная работа над одним или несколькими документами с вашей командой",
"ReviewRoomDescription": "Запроситe рецензию или комментарии к документам",
"ViewOnlyRoomDescription": "Предоставляйте доступ к любым готовым документам, отчетам, документации и другим файлам для просмотра",
"CustomRoomDescription": "Примените собственные настройки, чтобы использовать эту комнату для любых пользовательских целей",
"NamePlaceholder": "Введите имя",
"TagsPlaceholder": "Добавьте тэг",
"CreateTagOption": "Создать тэг",
"MakeRoomPrivateTitle": "Сделайте комнату приватной",
"MakeRoomPrivateDescription": "Все файлы в этой комнате будут зашифрованы",
"MakeRoomPrivateLimitationsWarningDescription": "С данной функцией Вы можете пригласить только уже существующих пользователей на портале. После создания комнаты изменить список пользобателей будет нельзя.",
"ThirdPartyStorageTitle": "Место хранения",
"ThirdPartyStorageDescription": "Используйте сторонние сервисы в качестве хранилища данных для этой комнаты. В подключенном хранилище будет создана новая папка для хранения данных этой комнаты",
"ThirdPartyStorageComboBoxPlaceholder": "Выберите хранилище",
"ThirdPartyStorageNoStorageAlert": "В Настройках портала не подключено ни одного хранилища. Перейдите в раздел Интеграций, чтобы включить возможность хранения данных на стороннем сервисе ",
"ThirdPartyStorageNoStorageAlertLink": "Сторонние сервисы",
"ThirdPartyStorageRememberChoice": "Запомнить этот выбор для новых комнат",
"ThirdPartyStoragePermanentSettingDescription": "Файлы хранятся в стороннем хранилище {{thirdpartyTitle}} в папке \"{{thirdpartyFolderName}}\".\n<strong>{{thirdpartyPath}}</strong>",
"FolderNameTitle": "Имя папки",
"FolderNameDescription": "В подключенном хранилище будет создана новая папка для хранения данных этой комнаты",
"DropzoneTitleLink": "Выберите новое изображение",
"DropzoneTitleSecondary": "или перетащите сюда файл",
"DropzoneTitleExsts": "(JPG или PNG, не более 1 МБ)"
}

View File

@ -7,11 +7,13 @@
"ByCreationDate": "Создан",
"ByLastModifiedDate": "Изменен",
"ByTitle": "Название",
"CollaborationRooms": "Совместное редактирование",
"CommonEmptyContainerDescription": "В разделе «Общие документы» отображаются все документы, которыми администратор портала предоставил общий доступ. Только администраторы портала могут создавать папки в этом разделе, но с предоставленным доступом пользователи портала также могут загружать свои файлы здесь. Перетащите файлы со своего компьютера сюда, чтобы загрузить их на свой портал еще проще.",
"ContainsSpecCharacter": "Название не должно содержать следующих символов: *+:\"<>?|/",
"Convert": "Конвертация",
"CopyItem": "<strong>{{title}}</strong> скопирован",
"CopyItems": "Скопировано элементов: <strong>{{qty}}</strong>",
"CustomRooms": "Пользовательская",
"Document": "Документ",
"EmptyFile": "Пустой файл",
"EmptyFilterDescriptionText": "В этом разделе нет файлов или папок, соответствующих фильтру. Пожалуйста, выберите другие параметры или очистите фильтр, чтобы показать все файлы в этом разделе. Вы можете также поискать нужный файл в других разделах.",
@ -23,6 +25,7 @@
"FavoritesEmptyContainerDescription": "Чтобы добавить файлы в избранное или удалить их из этого списка, используйте контекстное меню.",
"FileRemoved": "Файл перемещен в корзину",
"FileRenamed": "Документ '{{oldTitle}}' переименован в '{{newTitle}}'",
"FillingFormRooms": "Заполнение форм",
"Filter": "Фильтр",
"FinalizeVersion": "Сформировать версию",
"Folder": "Папка",
@ -64,6 +67,7 @@
"RemoveFromList": "Убрать из списка",
"RemovedFromFavorites": "Удалено из избранного",
"Rename": "Переименовать",
"ReviewRooms": "Рецензирование",
"SendByEmail": "Отправить по почте",
"Share": "Доступ",
"SharedEmptyContainerDescription": "Раздел 'Доступно для меня' используется для отображения файлов, к которым ваши друзья или коллеги предоставили вам доступ. Если вы не видели последние изменения в документах, они помечаются как «новые». Вы можете удалить файлы из списка, нажав соответствующую кнопку.",
@ -79,5 +83,6 @@
"VersionBadge": "B.{{version}}",
"VersionHistory": "История версий",
"ViewList": "Список",
"ViewOnlyRooms": "Просмотр",
"ViewTiles": "Плитки"
}

View File

@ -7,6 +7,14 @@
"AccessRightsSubTitle": "Данный раздел позволяет передавать права владельца портала и управлять правами доступа администраторов.",
"AccessRightsUsersFromList": "Участников со статусом {{users}} из списка",
"AccessSettings": "Настройки доступа",
"AmazonServiceTip": "Это необязательное свойство; измените его, только если вы хотите попробовать другую конечную точку службы",
"AmazonBucketTip": "Введите уникальное имя корзины Amazon S3, в которой вы хотите хранить свои резервные копии",
"AmazonRegionTip": "Введите регион AWS, в котором находится ваш контейнер Amazon",
"AmazonHTTPTip": "Если для этого свойства установлено значение true, клиент пытается использовать протокол HTTP, если целевая конечная точка поддерживает его. По умолчанию для этого свойства установлено значение false",
"AmazonSSETip": "Алгоритм шифрования на стороне сервера, используемый при хранении этого объекта в S3",
"AmazonForcePathStyleTip": "Следует ли принудительно использовать URL-адреса в стиле path для объектов S3",
"AmazonSSE": "Шифрование на стороне сервера",
"AmazonCSE": "Шифрование на стороне клиента",
"AddAdmins": "Добавить администраторов",
"AddAllowedIP": "Добавить разрешенный IP-адрес",
"AddName": "Добавьте наименование",
@ -51,10 +59,9 @@
"Customization": "Кастомизация",
"CustomizationDescription": "Этот раздел позволяет изменить оформление портала. Вы можете использовать логотип, название и слоган своей компании, чтобы портал соответствовал корпоративному стилю.",
"DeactivateOrDeletePortal": "Деактивировать или удалить портал.",
"DataBackup": "Резервное копирование данных",
"Disabled": "Отключено",
"DocumentsAdministratorsCan": "Администраторы модуля Документы могут подключать Dropbox, Box и другие аккаунты в Общих документах и настраивать права доступа в этом разделе",
"DocumentsModule": "Модуль документов",
"DocumentsModuleDescription": "Резервная копия будет сохранена в разделе 'Общие документы'.",
"DownloadCopy": "Скачать копию",
"Employees": "Пользователи",
"EmptyBackupList": "Еще не было создано ни одной резервной копии. Создайте хотя бы одну резервную копию, чтобы она появилась в этом списке.",
@ -119,13 +126,14 @@
"ProductUserOpportunities": "Просматривать профили и группы",
"RecoveryFileNotSelected": "Ошибка восстановления. Файл восстановления не выбран",
"RegistrationDate": "Дата регистрации",
"RestoreBackup": "Восстановление данных",
"RestoreBackup": "Восстановление",
"RestoreBackupDescription": "Используйте эту опцию, чтобы восстановить портал из ранее сохраненного резервного файла.",
"RestoreBackupHelp": "Опция <strong>Восстановление данных</strong> используется для восстановления предварительно сохраненных данных портала (с локального сервера или SaaS-портала).",
"RestoreBackupHelpNote": "Выберите хранилище, в котором находятся данные, укажите нужные сведения и отметьте опцию. <br/><strong>Оповестить пользователей портала</strong>, чтобы предупредить пользователей о проведении резервного копирования/восстановления.",
"RestoreBackupResetInfoWarningText": "Все текущие пароли будут сброшены. Пользователи портала получат письмо со ссылкой для восстановления доступа.",
"RestoreBackupWarningText": "Во время восстановления портал будет недоступен. После восстановления будут утеряны все изменения, совершенные после даты выбранной точки восстановления.",
"RestoreDefaultButton": "Настройки по умолчанию",
"RoomsModuleDescription": "Вы можете создать новую комнату специально для резервного копирования, выбрать одну из существующих комнат или сохранить копию в свою {{roomName}} комнату ",
"SelectFileInGZFormat": "Выбрать файл в формате .GZ",
"SendNotificationAboutRestoring": "Оповестить пользователей о восстановлении портала",
"ServerSideEncryptionMethod": "Метод шифрования на стороне сервера",
@ -150,7 +158,7 @@
"ThirdPartyPropsActivated": "Настройки сервиса успешно обновлены",
"ThirdPartyPropsDeactivated": "Сервис был успешно отключен",
"ThirdPartyResource": "Сторонний ресурс",
"ThirdPartyResourceDescription": "Резервная копия может быть сохранена на вашем стороннем ресурсе (Dropbox, Box.com, OneDrive или Google Drive). Прежде чем Вы сможете сохранять резервные копии в стороннем аккаунте (Dropbox, Box.com, OneDrive или Google Drive), потребуется подключить его к папке 'Общие'",
"ThirdPartyResourceDescription": "Резервная копия может быть сохранена на вашем стороннем ресурсе (Dropbox, Box.com, OneDrive или Google Drive). Прежде чем Вы сможете сохранять резервные копии в стороннем аккаунте (Dropbox, Box.com, OneDrive или Google Drive), потребуется подключить его",
"ThirdPartyStorage": "Стороннее хранилище",
"ThirdPartyStorageDescription": "Резервная копия может быть сохранена на стороннем хранилище. Соответствующий сервис необходимо предварительно подключить в разделе 'Интеграция'. В противном случае указанные ниже настройки будут неактивны.",
"ThirdPartyTitleDescription": "Ключи авторизации позволяют подключить портал ONLYOFFICE к сторонним сервисам. Подключите портал к Facebook, Twitter или LinkedIn, если Вы не хотите каждый раз при входе вводить свои учетные данные на портале. Привяжите портал к таким сервисам, как Dropbox, OneDrive и т.д. чтобы перенести документы из всех этих хранилищ в модуль Документы ONLYOFFICE.",

View File

@ -49,8 +49,6 @@
"DeactivateOrDeletePortal": "Deaktivovať alebo odstrániť portál.",
"Disabled": "Vypnuté",
"DocumentsAdministratorsCan": "Admini dokumentov môžu prepojiť účty Dropbox, Box a ďalšie účty so zdieľanými dokumentmi a nastaviť povolenia v tejto časti",
"DocumentsModule": "Modul dokumentov",
"DocumentsModuleDescription": "Zálohované údaje budú uložené do priečinka Bežné dokumenty.",
"DownloadCopy": "Stiahnuť si kópiu",
"Employees": "užívatelia",
"EmptyBackupList": "Zatiaľ neboli vytvorené žiadne zálohy. Vytvorte jednu alebo viac záloh, aby sa v tomto zozname zobrazili.",

View File

@ -49,8 +49,6 @@
"DeactivateOrDeletePortal": "Deaktivirajte ali izbrišite portal.",
"Disabled": "Onemogočeno",
"DocumentsAdministratorsCan": "Skrbniki dokumentov lahko povežejo Dropbox, Box in druge račune s skupnimi dokumenti in v tem razdelku nastavijo pravice dostopa",
"DocumentsModule": "Modul z dokumenti",
"DocumentsModuleDescription": "Varnostna kopija bo shranjena v mapi s skupnimi dokumenti.",
"DownloadCopy": "Prenesi kopijo",
"Employees": "uporabniki",
"EmptyBackupList": "Varnostno kopiranje še ni bilo ustvarjeno. Ustvarite eno ali več varnostnih kopiranj, če želite, da se prikažejo na seznamu.",

View File

@ -49,8 +49,6 @@
"DeactivateOrDeletePortal": "Portalı devre dışı bırak veya sil.",
"Disabled": "Devre dışı bırakıldı",
"DocumentsAdministratorsCan": "Belge yöneticileri Dropbox, Box ve diğer hesapları Ortak Belgelere bağlayabilir ve bu bölümde erişim haklarını düzenleyebilir.",
"DocumentsModule": "Belge modülü",
"DocumentsModuleDescription": "Yedekleme, Ortak belgeler klasörüne kaydedilecektir.",
"DownloadCopy": "Kopyayı indir",
"Employees": "kullanıcılar",
"EmptyBackupList": "Henüz hiçbir yedekleme yapılmadı. Listede yer alması için bir veya daha fazla yedekleme yapın.",

View File

@ -49,8 +49,6 @@
"DeactivateOrDeletePortal": "Деактивувати або видалити портал.",
"Disabled": "Вимкнено",
"DocumentsAdministratorsCan": "У цьому розділі адміністратори документів можуть прив'язати Dropbox, Box та інші облікові записи до спільних документів та налаштувати права доступу",
"DocumentsModule": "Модуль документа",
"DocumentsModuleDescription": "Резервну копію буде збережено в папці 'Загальні документи'.",
"DownloadCopy": "Завантажити копію",
"Employees": "користувачі",
"EmptyBackupList": "Резервних копій поки не існує. Створіть одну або декілька резервних копій, які з'являтимуться у цьому списку.",

View File

@ -49,8 +49,6 @@
"DeactivateOrDeletePortal": "Tắt hoặc xóa cổng thông tin.",
"Disabled": "Đã vô hiệu hóa",
"DocumentsAdministratorsCan": "Người quản trị tài liệu có thể liên kết Dropbox, Box và các tài khoản khác với Tài liệu chung và thiết lập quyền truy cập trong phần này",
"DocumentsModule": "Mô-đun tài liệu",
"DocumentsModuleDescription": "Bản sao lưu sẽ được lưu trong thư mục Common documents - các tài liệu chung.",
"DownloadCopy": "Tải xuống bản sao",
"Employees": "người dùng",
"EmptyBackupList": "Chưa có bản sao lưu nào được tạo. Tạo một hoặc nhiều bản sao lưu để chúng xuất hiện trong danh sách này.",

View File

@ -49,8 +49,6 @@
"DeactivateOrDeletePortal": "停用或删除门户。",
"Disabled": "已禁用",
"DocumentsAdministratorsCan": "文档管理员可将Dropbox、Box和其他账户链接至常用文档并在其中设置访问权限",
"DocumentsModule": "文件模块",
"DocumentsModuleDescription": "备份将在常用文档文件夹中保存。",
"DownloadCopy": "下载副本",
"Employees": "用户",
"EmptyBackupList": "尚未创建任何备份。请创建一个或多个备份,使其显示在此列表中。",

View File

@ -120,7 +120,6 @@ export default function withContent(WrappedContent) {
},
{ item }
) => {
const { editCompleteAction } = filesActionsStore;
const {
createFile,
createFolder,
@ -164,7 +163,6 @@ export default function withContent(WrappedContent) {
createFile,
createFolder,
culture,
editCompleteAction,
folderFormValidation,
homepage: config.homepage,

View File

@ -176,6 +176,10 @@ export default function withFileActions(WrappedFileItem) {
this.props.selectTag(tag);
};
onSelectType = (type) => {
this.props.selectType(type);
};
getContextModel = () => {
const { getModel, item, t } = this.props;
return getModel(item, t);
@ -237,6 +241,7 @@ export default function withFileActions(WrappedFileItem) {
onMouseClick={this.onMouseClick}
onHideContextMenu={this.onHideContextMenu}
onSelectTag={this.onSelectTag}
onSelectType={this.onSelectType}
getClassName={this.getClassName}
className={className}
isDragging={isDragging}
@ -271,6 +276,7 @@ export default function withFileActions(WrappedFileItem) {
const {
selectRowAction,
selectTag,
selectType,
onSelectItem,
setNewBadgeCount,
openFileAction,
@ -331,7 +337,8 @@ export default function withFileActions(WrappedFileItem) {
)
isActive = true;
const showHotkeyBorder = hotkeyCaret?.id === item.id;
const showHotkeyBorder =
hotkeyCaret?.id === item.id && hotkeyCaret?.isFolder === item.isFolder;
return {
t,
@ -339,6 +346,7 @@ export default function withFileActions(WrappedFileItem) {
selectRowAction,
onSelectItem,
selectTag,
selectType,
setSharingPanelVisible,
isPrivacy: isPrivacyFolder,
isRoomsFolder,

View File

@ -1,9 +1,9 @@
import React, { useEffect } from "react";
import { useHotkeys } from "react-hotkeys-hook";
import { observer, inject } from "mobx-react";
import { FileAction } from "@docspace/common/constants";
import { Events } from "@docspace/client/src/helpers/filesConstants";
import toastr from "client/toastr";
import throttle from "lodash/throttle";
const withHotkeys = (Component) => {
const WithHotkeys = (props) => {
@ -51,6 +51,7 @@ const withHotkeys = (Component) => {
selection,
setFavoriteAction,
filesIsLoading,
} = props;
const hotkeysFilter = {
@ -58,7 +59,11 @@ const withHotkeys = (Component) => {
ev.target?.type === "checkbox" || ev.target?.tagName !== "INPUT",
filterPreventDefault: false,
enableOnTags: ["INPUT"],
enabled: !someDialogIsOpen && enabledHotkeys && !mediaViewerIsVisible,
enabled:
!someDialogIsOpen &&
enabledHotkeys &&
!mediaViewerIsVisible &&
!filesIsLoading,
// keyup: true,
// keydown: false,
};
@ -87,9 +92,12 @@ const withHotkeys = (Component) => {
};
useEffect(() => {
window.addEventListener("keydown", onKeyDown);
const throttledKeyDownEvent = throttle(onKeyDown, 300);
return () => window.removeEventListener("keypress", onKeyDown);
window.addEventListener("keydown", throttledKeyDownEvent);
return () =>
window.removeEventListener("keypress", throttledKeyDownEvent);
});
//Select/deselect item
@ -322,9 +330,9 @@ const withHotkeys = (Component) => {
setSelected,
viewAs,
setViewAs,
fileActionStore,
enabledHotkeys,
selection,
filesIsLoading,
} = filesStore;
const {
@ -413,6 +421,7 @@ const withHotkeys = (Component) => {
selection,
setFavoriteAction,
filesIsLoading,
};
}
)(observer(WithHotkeys));

View File

@ -45,6 +45,7 @@ const ArticleBodyContent = (props) => {
theme,
toggleArticleOpen,
categoryType,
filesIsLoading,
} = props;
const campaigns = (localStorage.getItem("campaigns") || "")
@ -67,6 +68,7 @@ const ArticleBodyContent = (props) => {
archiveFolderId,
} = props;
if (filesIsLoading) return;
const filesSection = window.location.pathname.indexOf("/filter") > 0;
if (filesSection) {
@ -191,6 +193,7 @@ export default inject(
isLoading,
isLoaded,
categoryType,
filesIsLoading,
} = filesStore;
const {
@ -253,6 +256,7 @@ export default inject(
archiveFolderId,
categoryType,
filesIsLoading,
};
}
)(

View File

@ -15,6 +15,7 @@ import { combineUrl } from "@docspace/common/utils";
import config from "PACKAGE_FILE";
import withLoader from "../../../HOCs/withLoader";
import { Events } from "@docspace/client/src/helpers/filesConstants";
import { getMainButtonItems } from "SRC_DIR/helpers/plugins";
const ArticleMainButtonContent = (props) => {
const {
@ -28,6 +29,7 @@ const ArticleMainButtonContent = (props) => {
encrypted,
startUpload,
setAction,
setCreateRoomDialogVisible,
setSelectFileDialogVisible,
isArticleLoading,
isFavoritesFolder,
@ -39,6 +41,7 @@ const ArticleMainButtonContent = (props) => {
currentFolderId,
isRoomsFolder,
isArchiveFolder,
enablePlugins,
} = props;
const inputFilesElement = React.useRef(null);
const inputFolderElement = React.useRef(null);
@ -66,7 +69,6 @@ const ArticleMainButtonContent = (props) => {
const onCreateRoom = React.useCallback(() => {
const event = new Event(Events.ROOM_CREATE);
window.dispatchEvent(event);
}, []);
@ -170,7 +172,7 @@ const ArticleMainButtonContent = (props) => {
id: "main-button_new-room",
className: "main-button_drop-down",
icon: "images/folder.locked.react.svg",
label: t("Home:NewRoom"),
label: t("Files:NewRoom"),
onClick: onCreateRoom,
action: "room",
key: "room",
@ -238,6 +240,18 @@ const ArticleMainButtonContent = (props) => {
menuModel.push(...uploadActions);
setUploadActions(uploadActions);
}
if (enablePlugins) {
const pluginOptions = getMainButtonItems();
if (pluginOptions) {
pluginOptions.forEach((option) => {
menuModel.splice(option.value.position, 0, {
key: option.key,
...option.value,
});
});
}
}
setModel(menuModel);
setActions(actions);
@ -246,6 +260,7 @@ const ArticleMainButtonContent = (props) => {
isPrivacy,
currentFolderId,
isRoomsFolder,
enablePlugins,
onCreate,
onCreateRoom,
onShowSelectFileDialog,
@ -330,10 +345,15 @@ export default inject(
isArchiveFolder,
} = treeFoldersStore;
const { startUpload } = uploadDataStore;
const { setSelectFileDialogVisible } = dialogsStore;
const {
setCreateRoomDialogVisible,
setSelectFileDialogVisible,
} = dialogsStore;
const isArticleLoading = (!isLoaded || isLoading) && firstLoad;
const { enablePlugins } = auth.settingsStore;
const currentFolderId = selectedFolderStore.id;
return {
@ -354,12 +374,15 @@ export default inject(
startUpload,
setCreateRoomDialogVisible,
setSelectFileDialogVisible,
isLoading,
isLoaded,
firstLoad,
currentFolderId,
enablePlugins,
};
}
)(

View File

@ -15,7 +15,12 @@ const linkStyles = {
display: "flex",
};
const EmptyContainer = ({ isFiltered, parentId, theme }) => {
const EmptyContainer = ({
isFiltered,
parentId,
theme,
setCreateRoomDialogVisible,
}) => {
linkStyles.color = theme.filesEmptyContainer.linkColor;
const onCreate = (e) => {
@ -32,11 +37,10 @@ const EmptyContainer = ({ isFiltered, parentId, theme }) => {
window.dispatchEvent(event);
};
const onCreateRoom = React.useCallback(() => {
const onCreateRoom = (e) => {
const event = new Event(Events.ROOM_CREATE);
window.dispatchEvent(event);
}, []);
};
return isFiltered ? (
<EmptyFilterContainer linkStyles={linkStyles} />
@ -52,7 +56,13 @@ const EmptyContainer = ({ isFiltered, parentId, theme }) => {
};
export default inject(
({ auth, filesStore, treeFoldersStore, selectedFolderStore }) => {
({
auth,
filesStore,
dialogsStore,
treeFoldersStore,
selectedFolderStore,
}) => {
const {
authorType,
search,
@ -61,6 +71,8 @@ export default inject(
} = filesStore.filter;
const { isPrivacyFolder } = treeFoldersStore;
const { setCreateRoomDialogVisible } = dialogsStore;
const isFiltered =
(authorType || search || !withSubfolders || filterType) &&
!(isPrivacyFolder && isMobile);
@ -68,6 +80,7 @@ export default inject(
return {
theme: auth.settingsStore.theme,
isFiltered,
setCreateRoomDialogVisible,
parentId: selectedFolderStore.parentId,
};

View File

@ -21,6 +21,7 @@ import {
ThirdPartyDialog,
ConflictResolveDialog,
ConvertDialog,
CreateRoomDialog,
} from "../dialogs";
import ConvertPasswordDialog from "../dialogs/ConvertPasswordDialog";
@ -47,6 +48,7 @@ const Panels = (props) => {
setSelectFileDialogVisible,
hotkeyPanelVisible,
convertPasswordDialogVisible,
createRoomDialogVisible,
} = props;
const { t } = useTranslation(["Translations", "SelectFile"]);
@ -86,6 +88,7 @@ const Panels = (props) => {
<ConflictResolveDialog key="conflict-resolve-dialog" />
),
convertDialogVisible && <ConvertDialog key="convert-dialog" />,
createRoomDialogVisible && <CreateRoomDialog key="create-room-dialog" />,
selectFileDialogVisible && (
<SelectFileDialog
key="select-file-dialog"
@ -126,6 +129,7 @@ export default inject(
newFilesPanelVisible,
conflictResolveDialogVisible,
convertDialogVisible,
createRoomDialogVisible,
convertPasswordDialogVisible,
connectItem, //TODO:
@ -155,6 +159,7 @@ export default inject(
newFilesPanelVisible,
conflictResolveDialogVisible,
convertDialogVisible,
createRoomDialogVisible,
convertPasswordDialogVisible,
selectFileDialogVisible,
createMasterForm,

View File

@ -103,7 +103,7 @@ const CreateEvent = ({
addActiveItems(null, [folder.id]);
setCreatedItem({ id: createdFolderId, type: "folder" });
})
.then(() => editCompleteAction(id, item, false, type))
.then(() => editCompleteAction(item, type, true))
.catch((e) => toastr.error(e))
.finally(() => {
const folderIds = [+id];
@ -123,7 +123,7 @@ const CreateEvent = ({
open && openDocEditor(file.id, file.providerKey, tab);
})
.then(() => editCompleteAction(id, item, false, type))
.then(() => editCompleteAction(item, type))
.catch((err) => {
if (err.indexOf("password") == -1) {
toastr.error(err, t("Common:Warning"));
@ -173,7 +173,7 @@ const CreateEvent = ({
return open && openDocEditor(file.id, file.providerKey, tab);
})
.then(() => editCompleteAction(id, item, false, type))
.then(() => editCompleteAction(item, type))
.catch((e) => toastr.error(e))
.finally(() => {
const fileIds = [+id];
@ -209,7 +209,7 @@ const CreateEvent = ({
return open && openDocEditor(file.id, file.providerKey, tab);
})
.then(() => editCompleteAction(id, item, false, type))
.then(() => editCompleteAction(item, type))
.catch((e) => toastr.error(e))
.finally(() => {
const fileIds = [+id];

View File

@ -1,65 +1,208 @@
import React from "react";
import React, { useState, useEffect } from "react";
import { inject, observer } from "mobx-react";
import { useTranslation } from "react-i18next";
import { CreateRoomDialog } from "../dialogs";
import toastr from "client/toastr";
const CreateRoomEvent = ({
visible,
onClose,
import { RoomsType } from "@docspace/common/constants";
createRoom,
createRoomInThirdpary,
createTag,
addTagsToRoom,
calculateRoomLogoParams,
uploadRoomLogo,
addLogoToRoom,
fetchTags,
import Dialog from "./sub-components/Dialog";
connectItems,
connectDialogVisible,
setConnectDialogVisible,
setRoomCreation,
saveThirdpartyResponse,
openConnectWindow,
setConnectItem,
getOAuthToken,
const CreateRoomEvent = ({ createRoom, updateCurrentFolder, id, onClose }) => {
const options = [
{ key: RoomsType.CustomRoom, label: "Custom room" },
{ key: RoomsType.FillingFormsRoom, label: "Filling form room" },
{ key: RoomsType.EditingRoom, label: "Editing room" },
{ key: RoomsType.ReviewRoom, label: "Review room" },
{ key: RoomsType.ReadOnlyRoom, label: "View-only room" },
];
currrentFolderId,
updateCurrentFolder,
}) => {
const { t } = useTranslation([
"CreateEditRoomDialog",
"Common",
"Files",
"ToastHeaders",
]);
const [fetchedTags, setFetchedTags] = useState([]);
const [isLoading, setIsLoading] = useState(false);
const [selectedOption, setSelectedOption] = React.useState(options[0]);
const onCreate = async (roomParams) => {
const createRoomData = {
roomType: roomParams.type,
title: roomParams.title || t("Files:NewRoom"),
};
const { t } = useTranslation(["Translations", "Common"]);
const isThirdparty =
roomParams.isThirdparty &&
roomParams.storageLocation.isConnected &&
roomParams.storageLocation.thirdpartyFolderId;
const onSelect = (item) => {
setSelectedOption(item);
const addTagsData = roomParams.tags.map((tag) => tag.name);
const createTagsData = roomParams.tags
.filter((t) => t.isNew)
.map((t) => t.name);
const uploadLogoData = new FormData();
uploadLogoData.append(0, roomParams.icon.uploadedFile);
try {
setIsLoading(true);
const room = isThirdparty
? await createRoomInThirdpary(
roomParams.storageLocation.thirdpartyFolderId,
createRoomData
)
: await createRoom(createRoomData);
for (let i = 0; i < createTagsData.length; i++)
await createTag(createTagsData[i]);
await addTagsToRoom(room.id, addTagsData);
if (roomParams.icon.uploadedFile)
await uploadRoomLogo(uploadLogoData).then((response) => {
const url = URL.createObjectURL(roomParams.icon.uploadedFile);
const img = new Image();
img.onload = async () => {
const { x, y, zoom } = roomParams.icon;
await addLogoToRoom(room.id, {
tmpFile: response.data,
...calculateRoomLogoParams(img, x, y, zoom),
});
URL.revokeObjectURL(img.src);
};
img.src = url;
});
} catch (err) {
console.log(err);
} finally {
await updateCurrentFolder(null, currrentFolderId);
setIsLoading(false);
onClose();
}
};
const onSave = (e, value) => {
createRoom(value, selectedOption.key)
.then(() => {
updateCurrentFolder(null, id);
})
.finally(() => {
onClose();
toastr.success(`${value} success created`);
});
};
useEffect(async () => {
let tags = await fetchTags();
setFetchedTags(tags);
}, []);
return (
<Dialog
<CreateRoomDialog
t={t}
title={"Create room"}
startValue={"New room"}
visible={true}
options={options}
selectedOption={selectedOption}
onSelect={onSelect}
onSave={onSave}
onCancel={onClose}
visible={visible && !connectDialogVisible}
onClose={onClose}
onCreate={onCreate}
fetchedTags={fetchedTags}
isLoading={isLoading}
connectItems={connectItems}
connectDialogVisible={connectDialogVisible}
setConnectDialogVisible={setConnectDialogVisible}
setRoomCreation={setRoomCreation}
saveThirdpartyResponse={saveThirdpartyResponse}
openConnectWindow={openConnectWindow}
setConnectItem={setConnectItem}
getOAuthToken={getOAuthToken}
/>
);
};
export default inject(
({ filesStore, filesActionsStore, selectedFolderStore }) => {
const { createRoom } = filesStore;
({
auth,
filesStore,
tagsStore,
filesActionsStore,
selectedFolderStore,
settingsStore,
dialogsStore,
}) => {
const {
createRoom,
createRoomInThirdpary,
addTagsToRoom,
calculateRoomLogoParams,
uploadRoomLogo,
addLogoToRoom,
} = filesStore;
const { createTag, fetchTags } = tagsStore;
const { id: currrentFolderId } = selectedFolderStore;
const { updateCurrentFolder } = filesActionsStore;
const { id } = selectedFolderStore;
const thirdPartyStore = settingsStore.thirdPartyStore;
return { createRoom, updateCurrentFolder, id };
const { openConnectWindow } = settingsStore.thirdPartyStore;
const connectItems = [
thirdPartyStore.googleConnectItem,
thirdPartyStore.boxConnectItem,
thirdPartyStore.dropboxConnectItem,
thirdPartyStore.oneDriveConnectItem,
thirdPartyStore.nextCloudConnectItem,
thirdPartyStore.kDriveConnectItem,
thirdPartyStore.yandexConnectItem,
thirdPartyStore.ownCloudConnectItem,
thirdPartyStore.webDavConnectItem,
thirdPartyStore.sharePointConnectItem,
]
.map(
(item) =>
item && {
isAvialable: !!item,
id: item[0],
providerName: item[0],
isOauth: item.length > 1,
oauthHref: item.length > 1 ? item[1] : "",
}
)
.filter((item) => !!item);
const { getOAuthToken } = auth.settingsStore;
const {
setConnectItem,
connectDialogVisible,
setConnectDialogVisible,
setRoomCreation,
saveThirdpartyResponse,
} = dialogsStore;
return {
createRoom,
createRoomInThirdpary,
createTag,
fetchTags,
addTagsToRoom,
calculateRoomLogoParams,
uploadRoomLogo,
addLogoToRoom,
setConnectItem,
connectDialogVisible,
setConnectDialogVisible,
setRoomCreation,
saveThirdpartyResponse,
saveThirdpartyResponse,
openConnectWindow,
connectItems,
getOAuthToken,
currrentFolderId,
updateCurrentFolder,
};
}
)(observer(CreateRoomEvent));

View File

@ -0,0 +1,190 @@
import React, { useState, useEffect } from "react";
import { inject, observer } from "mobx-react";
import { useTranslation } from "react-i18next";
import { EditRoomDialog } from "../dialogs";
import { Encoder } from "@docspace/common/utils/encoder";
const EditRoomEvent = ({
visible,
onClose,
item,
editRoom,
addTagsToRoom,
removeTagsFromRoom,
createTag,
fetchTags,
getThirdPartyIcon,
calculateRoomLogoParams,
uploadRoomLogo,
setFolder,
removeLogoFromRoom,
addLogoToRoom,
currentFolderId,
updateCurrentFolder,
}) => {
const { t } = useTranslation(["CreateEditRoomDialog", "Common", "Files"]);
const [fetchedTags, setFetchedTags] = useState([]);
const [fetchedImage, setFetchedImage] = useState(null);
const [isLoading, setIsLoading] = useState(false);
const startTags = Object.values(item.tags);
const startObjTags = startTags.map((tag, i) => ({ id: i, name: tag }));
const fetchedRoomParams = {
title: item.title,
type: item.roomType,
tags: startObjTags,
isThirdparty: !!item.providerKey,
storageLocation: {
title: item.title,
parentId: item.parentId,
providerKey: item.providerKey,
iconSrc: getThirdPartyIcon(item.providerKey),
},
isPrivate: false,
icon: {
uploadedFile: item.logo.original,
tmpFile: "",
x: 0.5,
y: 0.5,
zoom: 1,
},
};
const onSave = async (roomParams) => {
const editRoomParams = {
title: roomParams.title || t("Files:NewRoom"),
};
const tags = roomParams.tags.map((tag) => tag.name);
const newTags = roomParams.tags.filter((t) => t.isNew).map((t) => t.name);
const removedTags = startTags.filter((sT) => !tags.includes(sT));
const uploadLogoData = new FormData();
uploadLogoData.append(0, roomParams.icon.uploadedFile);
try {
setIsLoading(true);
const room = await editRoom(item.id, editRoomParams);
for (let i = 0; i < newTags.length; i++) await createTag(newTags[i]);
await addTagsToRoom(room.id, tags);
await removeTagsFromRoom(room.id, removedTags);
if (!!item.logo.original && !roomParams.icon.uploadedFile)
await removeLogoFromRoom(room.id);
if (roomParams.icon.uploadedFile) {
await setFolder({
...room,
logo: { big: item.logo.small },
});
await uploadRoomLogo(uploadLogoData).then((response) => {
const url = URL.createObjectURL(roomParams.icon.uploadedFile);
const img = new Image();
img.onload = async () => {
const { x, y, zoom } = roomParams.icon;
await addLogoToRoom(room.id, {
tmpFile: response.data,
...calculateRoomLogoParams(img, x, y, zoom),
});
URL.revokeObjectURL(img.src);
};
img.src = url;
});
}
} catch (err) {
console.log(err);
} finally {
await updateCurrentFolder(null, currentFolderId);
setIsLoading(false);
onClose();
}
};
useEffect(async () => {
const imgExst = item.logo.original.slice(".")[1];
if (item.logo.original) {
const file = await fetch(item.logo.original)
.then((res) => res.arrayBuffer())
.then(
(buf) =>
new File([buf], "fetchedFile", {
type: `image/${imgExst}`,
})
);
setFetchedImage(file);
}
}, []);
useEffect(async () => {
const tags = await fetchTags();
setFetchedTags(tags);
}, []);
return (
<EditRoomDialog
t={t}
visible={visible}
onClose={onClose}
fetchedRoomParams={fetchedRoomParams}
onSave={onSave}
fetchedTags={fetchedTags}
fetchedImage={fetchedImage}
isLoading={isLoading}
/>
);
};
export default inject(
({
filesStore,
tagsStore,
filesActionsStore,
selectedFolderStore,
settingsStore,
}) => {
const {
editRoom,
addTagsToRoom,
removeTagsFromRoom,
calculateRoomLogoParams,
uploadRoomLogo,
setFolder,
addLogoToRoom,
removeLogoFromRoom,
} = filesStore;
const { createTag, fetchTags } = tagsStore;
const { id: currentFolderId } = selectedFolderStore;
const { updateCurrentFolder } = filesActionsStore;
const { getThirdPartyIcon } = settingsStore.thirdPartyStore;
return {
editRoom,
addTagsToRoom,
removeTagsFromRoom,
createTag,
fetchTags,
getThirdPartyIcon,
calculateRoomLogoParams,
setFolder,
uploadRoomLogo,
removeLogoFromRoom,
addLogoToRoom,
currentFolderId,
updateCurrentFolder,
};
}
)(observer(EditRoomEvent));

View File

@ -48,7 +48,7 @@ const RenameEvent = ({
if (isSameTitle) {
setStartValue(originalTitle);
return editCompleteAction(item.id, item, isSameTitle, type);
return editCompleteAction(item, type);
} else {
timerId = setTimeout(() => {
isFile ? addActiveItems([item.id]) : addActiveItems(null, [item.id]);
@ -57,7 +57,7 @@ const RenameEvent = ({
isFile
? updateFile(item.id, value)
.then(() => editCompleteAction(item.id, item, false, type))
.then(() => editCompleteAction(item, type))
.then(() =>
toastr.success(
t("FileRenamed", {
@ -68,7 +68,7 @@ const RenameEvent = ({
)
.catch((err) => {
toastr.error(err);
editCompleteAction(item.id, item, false, type);
editCompleteAction(item, type);
})
.finally(() => {
clearTimeout(timerId);
@ -79,7 +79,7 @@ const RenameEvent = ({
onClose();
})
: renameFolder(item.id, value)
.then(() => editCompleteAction(item.id, item, false, type))
.then(() => editCompleteAction(item, type))
.then(() =>
toastr.success(
t("FolderRenamed", {
@ -90,7 +90,7 @@ const RenameEvent = ({
)
.catch((err) => {
toastr.error(err);
editCompleteAction(item.id, item, false, type);
editCompleteAction(item, type);
})
.finally(() => {
clearTimeout(timerId);

View File

@ -1,15 +1,15 @@
import React from "react";
import React, { useState, useEffect, useCallback, memo } from "react";
import { FileAction } from "@docspace/common/constants";
import { Events } from "@docspace/client/src/helpers/filesConstants";
import CreateEvent from "./CreateEvent";
import RenameEvent from "./RenameEvent";
import CreateRoomEvent from "./CreateRoomEvent";
import EditRoomEvent from "./EditRoomEvent";
const GlobalEvents = () => {
const [createDialogProps, setCreateDialogProps] = React.useState({
const [createDialogProps, setCreateDialogProps] = useState({
visible: false,
id: null,
type: null,
@ -20,18 +20,24 @@ const GlobalEvents = () => {
onClose: null,
});
const [createRoomDialogProps, setCreateRoomDialogProps] = React.useState({
visible: false,
onClose: null,
});
const [renameDialogProps, setRenameDialogProps] = React.useState({
const [renameDialogProps, setRenameDialogProps] = useState({
visible: false,
item: null,
onClose: null,
});
const onCreate = React.useCallback((e) => {
const [createRoomDialogProps, setCreateRoomDialogProps] = useState({
visible: false,
onClose: null,
});
const [editRoomDialogProps, setEditRoomDialogProps] = useState({
visible: false,
item: null,
onClose: null,
});
const onCreate = useCallback((e) => {
const { payload } = e;
const visible = payload.id ? true : false;
@ -59,15 +65,7 @@ const GlobalEvents = () => {
});
}, []);
const onCreateRoom = React.useCallback((e) => {
setCreateRoomDialogProps({
visible: true,
onClose: () =>
setCreateRoomDialogProps({ visible: false, onClose: null }),
});
}, []);
const onRename = React.useCallback((e) => {
const onRename = useCallback((e) => {
const visible = e.item ? true : false;
setRenameDialogProps({
@ -77,36 +75,65 @@ const GlobalEvents = () => {
onClose: () => {
setRenameDialogProps({
visible: false,
typ: null,
type: null,
item: null,
});
},
});
}, []);
React.useEffect(() => {
const onCreateRoom = useCallback((e) => {
setCreateRoomDialogProps({
visible: true,
onClose: () =>
setCreateRoomDialogProps({ visible: false, onClose: null }),
});
}, []);
const onEditRoom = useCallback((e) => {
const visible = e.item ? true : false;
setEditRoomDialogProps({
visible: visible,
item: e.item,
onClose: () => {
setEditRoomDialogProps({
visible: false,
item: null,
onClose: null,
});
},
});
}, []);
useEffect(() => {
window.addEventListener(Events.CREATE, onCreate);
window.addEventListener(Events.ROOM_CREATE, onCreateRoom);
window.addEventListener(Events.RENAME, onRename);
window.addEventListener(Events.ROOM_CREATE, onCreateRoom);
window.addEventListener(Events.ROOM_EDIT, onEditRoom);
return () => {
window.removeEventListener(Events.CREATE, onCreate);
window.removeEventListener(Events.ROOM_CREATE, onCreateRoom);
window.removeEventListener(Events.RENAME, onRename);
window.removeEventListener(Events.ROOM_CREATE, onCreateRoom);
window.removeEventListener(Events.ROOM_EDIT, onEditRoom);
};
}, [onRename, onCreate]);
}, [onRename, onCreate, onCreateRoom, onEditRoom]);
return [
createDialogProps.visible && (
<CreateEvent key={Events.CREATE} {...createDialogProps} />
),
renameDialogProps.visible && (
<RenameEvent key={Events.RENAME} {...renameDialogProps} />
),
createRoomDialogProps.visible && (
<CreateRoomEvent key={Events.ROOM_CREATE} {...createRoomDialogProps} />
),
renameDialogProps.visible && (
<RenameEvent key={Events.RENAME} {...renameDialogProps} />
editRoomDialogProps.visible && (
<EditRoomEvent key={Events.ROOM_EDIT} {...editRoomDialogProps} />
),
];
};
export default React.memo(GlobalEvents);
export default memo(GlobalEvents);

View File

@ -86,7 +86,7 @@ const Dialog = ({
</ModalDialog.Body>
<ModalDialog.Footer>
<Button
key="SendBtn"
key="GlobalSendBtn"
label={t("Common:SaveButton")}
size="normal"
scale

View File

@ -153,7 +153,7 @@ class ChangeEmailDialogComponent extends React.Component {
</ModalDialog.Body>
<ModalDialog.Footer>
<Button
key="SendBtn"
key="ChangeEmailSendBtn"
label={t("Common:SendButton")}
size="normal"
scale

View File

@ -82,7 +82,7 @@ class ChangePasswordDialogComponent extends React.Component {
</ModalDialog.Body>
<ModalDialog.Footer>
<Button
key="SendBtn"
key="ChangePasswordSendBtn"
label={t("Common:SendButton")}
size="normal"
scale

View File

@ -37,7 +37,7 @@ class ChangePhoneDialogComponent extends React.Component {
</ModalDialog.Body>
<ModalDialog.Footer>
<Button
key="SendBtn"
key="ChangePhoneSendBtn"
label={t("Common:SendButton")}
size="normal"
scale

View File

@ -9,6 +9,7 @@ import FieldContainer from "@docspace/components/field-container";
import { withTranslation } from "react-i18next";
import { inject, observer } from "mobx-react";
import { runInAction } from "mobx";
import { saveSettingsThirdParty } from "@docspace/common/api/files";
const PureConnectDialogContainer = (props) => {
const {
@ -30,6 +31,10 @@ const PureConnectDialogContainer = (props) => {
personal,
getSubfolders,
folderFormValidation,
updateInfo,
isConnectionViaBackupModule,
roomCreation,
setSaveThirdpartyResponse,
} = props;
const {
corporate,
@ -122,6 +127,32 @@ const PureConnectDialogContainer = (props) => {
}
setIsLoading(true);
if (isConnectionViaBackupModule) {
saveSettingsThirdParty(
urlValue,
loginValue,
passwordValue,
oAuthToken,
false,
customerTitle,
provider_key,
provider_id
)
.catch((err) => {
setIsLoading(false);
onClose();
toastr.error(err);
})
.finally(() => {
setIsLoading(false);
updateInfo && updateInfo();
onClose();
});
return;
}
saveThirdParty(
urlValue,
loginValue,
@ -130,9 +161,12 @@ const PureConnectDialogContainer = (props) => {
isCorporate,
customerTitle,
provider_key || key,
provider_id
provider_id,
roomCreation
)
.then(async () => {
.then(async (res) => {
setSaveThirdpartyResponse(res);
const folderId = isCorporate ? commonFolderId : myFolderId;
const subfolders = await getSubfolders(folderId);
const node = treeFolders.find((x) => x.id === folderId);
@ -211,7 +245,12 @@ const PureConnectDialogContainer = (props) => {
</ModalDialog.Header>
<ModalDialog.Body>
{isAccount ? (
<FieldContainer labelVisible labelText={t("Account")} isVertical>
<FieldContainer
style={roomCreation ? { margin: "0" } : {}}
labelVisible
labelText={t("Account")}
isVertical
>
<Button
label={t("Reconnect")}
size="normal"
@ -266,6 +305,7 @@ const PureConnectDialogContainer = (props) => {
isVertical
hasError={!isPasswordValid}
errorMessage={t("Common:RequiredField")}
style={roomCreation ? { margin: "0" } : {}}
>
<PasswordInput
hasError={!isPasswordValid}
@ -279,24 +319,26 @@ const PureConnectDialogContainer = (props) => {
</FieldContainer>
</>
)}
<FieldContainer
labelText={t("ConnectFolderTitle")}
isRequired
isVertical
hasError={!isTitleValid}
errorMessage={t("Common:RequiredField")}
>
<TextInput
{!(isConnectionViaBackupModule || roomCreation) && (
<FieldContainer
labelText={t("ConnectFolderTitle")}
isRequired
isVertical
hasError={!isTitleValid}
isDisabled={isLoading}
tabIndex={4}
scale
value={`${customerTitle}`}
onChange={onChangeFolderName}
/>
</FieldContainer>
{!personal && (
errorMessage={t("Common:RequiredField")}
>
<TextInput
hasError={!isTitleValid}
isDisabled={isLoading}
tabIndex={4}
scale
value={`${customerTitle}`}
onChange={onChangeFolderName}
/>
</FieldContainer>
)}
{!personal && !(isConnectionViaBackupModule || roomCreation) && (
<Checkbox
label={t("ConnectMakeShared")}
isChecked={isCorporate}
@ -323,7 +365,6 @@ const PureConnectDialogContainer = (props) => {
scale={isAccount}
onClick={onClose}
isDisabled={isLoading}
isLoading={isLoading}
/>
</ModalDialog.Footer>
</ModalDialog>
@ -338,14 +379,17 @@ const ConnectDialog = withTranslation([
])(PureConnectDialogContainer);
export default inject(
({
auth,
filesStore,
settingsStore,
treeFoldersStore,
selectedFolderStore,
dialogsStore,
}) => {
(
{
auth,
filesStore,
settingsStore,
treeFoldersStore,
selectedFolderStore,
dialogsStore,
},
{ passedItem, isConnectionViaBackupModule }
) => {
const {
providers,
saveThirdParty,
@ -368,9 +412,13 @@ export default inject(
const {
connectDialogVisible: visible,
setConnectDialogVisible,
connectItem: item,
connectItem,
roomCreation,
setSaveThirdpartyResponse,
} = dialogsStore;
const item = isConnectionViaBackupModule ? passedItem : connectItem;
return {
selectedFolderId: id,
selectedFolderFolders: folders,
@ -380,6 +428,8 @@ export default inject(
providers,
visible,
item,
roomCreation,
setSaveThirdpartyResponse,
folderFormValidation,
getOAuthToken,

View File

@ -35,8 +35,8 @@ const ConvertDialogComponent = (props) => {
if (convertSingleFile && sortedFolder) {
rootFolderTitle = isShareFolder
? rootFoldersTitles[FolderType.USER]
: rootFoldersTitles[convertItem.rootFolderType];
? rootFoldersTitles[FolderType.USER]?.title
: rootFoldersTitles[convertItem.rootFolderType]?.title;
}
const [hideMessage, setHideMessage] = useState(false);

View File

@ -114,7 +114,7 @@ const ConvertPasswordDialogComponent = (props) => {
open && openDocEditor(file.id, file.providerKey, tab);
})
.then(() => {
editCompleteAction(actionId, fileInfo, false);
editCompleteAction(fileInfo);
})
.catch((err) => {
if (err.indexOf("password") == -1) {

View File

@ -0,0 +1,158 @@
import React, { useState } from "react";
import styled, { css } from "styled-components";
import ModalDialog from "@docspace/components/modal-dialog";
import Button from "@docspace/components/button";
import TagHandler from "./handlers/tagHandler";
import SetRoomParams from "./sub-components/SetRoomParams";
import RoomTypeList from "./sub-components/RoomTypeList";
import DialogHeader from "./sub-components/DialogHeader";
const StyledModalDialog = styled(ModalDialog)`
.header-with-button {
display: flex;
align-items: center;
flex-direction: row;
gap: 12px;
}
${(props) =>
props.isOauthWindowOpen &&
css`
#modal-dialog {
display: none;
}
`}
`;
const CreateRoomDialog = ({
t,
visible,
onClose,
onCreate,
connectItems,
setConnectDialogVisible,
setRoomCreation,
saveThirdpartyResponse,
openConnectWindow,
setConnectItem,
getOAuthToken,
fetchedTags,
isLoading,
folderFormValidation,
}) => {
const [isScrollLocked, setIsScrollLocked] = useState(false);
const [isOauthWindowOpen, setIsOauthWindowOpen] = useState(false);
const startRoomParams = {
title: "",
type: undefined,
tags: [],
isPrivate: false,
isThirdparty: false,
storageLocation: {
isConnected: false,
provider: null,
thirdpartyFolderId: "",
storageFolderPath: "",
},
rememberThirdpartyStorage: false,
icon: {
uploadedFile: null,
tmpFile: "",
x: 0.5,
y: 0.5,
zoom: 1,
},
};
const [roomParams, setRoomParams] = useState({ ...startRoomParams });
const setRoomTags = (newTags) =>
setRoomParams({ ...roomParams, tags: newTags });
const tagHandler = new TagHandler(roomParams.tags, setRoomTags, fetchedTags);
const setRoomType = (newRoomType) => {
setRoomParams((prev) => ({
...prev,
type: newRoomType,
}));
};
const onCreateRoom = () => onCreate(roomParams);
const isChooseRoomType = roomParams.type === undefined;
const goBack = () => {
setRoomParams({ ...startRoomParams });
};
return (
<StyledModalDialog
displayType="aside"
withBodyScroll
visible={visible}
onClose={onClose}
isScrollLocked={isScrollLocked}
withFooterBorder
isOauthWindowOpen={isOauthWindowOpen}
>
<ModalDialog.Header>
<DialogHeader
isChooseRoomType={isChooseRoomType}
onArrowClick={goBack}
/>
</ModalDialog.Header>
<ModalDialog.Body>
{isChooseRoomType ? (
<RoomTypeList t={t} setRoomType={setRoomType} />
) : (
<SetRoomParams
t={t}
setIsOauthWindowOpen={setIsOauthWindowOpen}
tagHandler={tagHandler}
roomParams={roomParams}
setRoomParams={setRoomParams}
setRoomType={setRoomType}
setIsScrollLocked={setIsScrollLocked}
connectItems={connectItems}
setConnectDialogVisible={setConnectDialogVisible}
setRoomCreation={setRoomCreation}
saveThirdpartyResponse={saveThirdpartyResponse}
openConnectWindow={openConnectWindow}
setConnectItem={setConnectItem}
getOAuthToken={getOAuthToken}
/>
)}
</ModalDialog.Body>
{!isChooseRoomType && (
<ModalDialog.Footer>
<Button
tabIndex={5}
label={t("Common:Create")}
size="normal"
primary
scale
onClick={onCreateRoom}
isLoading={isLoading}
/>
<Button
tabIndex={5}
label={t("Common:CancelButton")}
size="normal"
scale
onClick={onClose}
/>
</ModalDialog.Footer>
)}
</StyledModalDialog>
);
};
export default CreateRoomDialog;

View File

@ -0,0 +1,95 @@
import React, { useState, useEffect } from "react";
import TagHandler from "./handlers/tagHandler";
import SetRoomParams from "./sub-components/SetRoomParams";
import DialogHeader from "./sub-components/DialogHeader";
import ModalDialog from "@docspace/components/modal-dialog";
import Button from "@docspace/components/button";
const EditRoomDialog = ({
t,
visible,
onClose,
onSave,
isLoading,
fetchedRoomParams,
fetchedTags,
fetchedImage,
folderFormValidation,
}) => {
const [isScrollLocked, setIsScrollLocked] = useState(false);
const [roomParams, setRoomParams] = useState({
...fetchedRoomParams,
});
const setRoomTags = (newTags) =>
setRoomParams({ ...roomParams, tags: newTags });
const tagHandler = new TagHandler(roomParams.tags, setRoomTags, fetchedTags);
const setRoomType = (newRoomType) =>
setRoomParams((prev) => ({
...prev,
type: newRoomType,
}));
const onEditRoom = () => onSave(roomParams);
useEffect(async () => {
if (fetchedImage)
setRoomParams({
...roomParams,
icon: { ...roomParams.icon, uploadedFile: fetchedImage },
});
}, [fetchedImage]);
return (
<ModalDialog
displayType="aside"
withBodyScroll
visible={visible}
onClose={onClose}
isScrollLocked={isScrollLocked}
withFooterBorder
>
<ModalDialog.Header>
<DialogHeader isEdit />
</ModalDialog.Header>
<ModalDialog.Body>
<SetRoomParams
t={t}
tagHandler={tagHandler}
roomParams={roomParams}
setRoomParams={setRoomParams}
setRoomType={setRoomType}
setIsScrollLocked={setIsScrollLocked}
isEdit
/>
</ModalDialog.Body>
<ModalDialog.Footer>
<Button
tabIndex={5}
label={t("Common:SaveButton")}
size="normal"
primary
scale
onClick={onEditRoom}
isLoading={isLoading}
/>
<Button
tabIndex={5}
label={t("Common:CancelButton")}
size="normal"
scale
onClick={onClose}
/>
</ModalDialog.Footer>
</ModalDialog>
);
};
export default EditRoomDialog;

Some files were not shown because too many files have changed in this diff Show More