Merge branch 'feature/files' of https://github.com/ONLYOFFICE/AppServer into feature/files

This commit is contained in:
NikolayRechkin 2020-06-25 10:51:05 +03:00
commit 1337df1101
80 changed files with 264408 additions and 183 deletions

View File

@ -52,10 +52,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AppLimit.CloudComputing.Sha
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ASC.Files.Service", "products\ASC.Files\Service\ASC.Files.Service.csproj", "{5D41FFFF-816C-40B2-95CD-E2DDDCB83784}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ASC.ApiSystem", "common\services\ASC.ApiSystem\ASC.ApiSystem.csproj", "{C2BB03A0-C35B-433F-96D4-3A06CBC06AD7}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ASC.ApiSystem", "common\services\ASC.ApiSystem\ASC.ApiSystem.csproj", "{C2BB03A0-C35B-433F-96D4-3A06CBC06AD7}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ASC.UrlShortener.Svc", "common\services\ASC.UrlShortener.Svc\ASC.UrlShortener.Svc.csproj", "{04A56018-C41E-4634-A185-A13E9250C75A}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ASC.Feed.Aggregator", "common\services\ASC.Feed.Aggregator\ASC.Feed.Aggregator.csproj", "{07CCC11F-76CB-448E-B15A-72E09FBB348B}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -162,6 +164,10 @@ Global
{04A56018-C41E-4634-A185-A13E9250C75A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{04A56018-C41E-4634-A185-A13E9250C75A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{04A56018-C41E-4634-A185-A13E9250C75A}.Release|Any CPU.Build.0 = Release|Any CPU
{07CCC11F-76CB-448E-B15A-72E09FBB348B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{07CCC11F-76CB-448E-B15A-72E09FBB348B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{07CCC11F-76CB-448E-B15A-72E09FBB348B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{07CCC11F-76CB-448E-B15A-72E09FBB348B}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

View File

@ -5,6 +5,7 @@ ARG RELEASE_DATE_SIGN=""
ARG VERSION="8.9.0.190"
ARG SOURCE_REPO_URL="deb http://static.teamlab.com.s3.amazonaws.com/repo/debian squeeze main"
ARG DEBIAN_FRONTEND=noninteractive
ARG GIT_BRANCH="master"
LABEL onlyoffice.community.release-date="${RELEASE_DATE}" \
onlyoffice.community.version="${VERSION}" \
@ -15,6 +16,8 @@ ENV LANG=en_US.UTF-8 \
LC_ALL=en_US.UTF-8
RUN apt-get -y update && \
apt-get -y upgrade && \
apt-get -y dist-upgrade && \
apt-get -yq install gnupg2 ca-certificates && \
apt-get install -yq sudo locales && \
addgroup --system --gid 107 onlyoffice && \
@ -24,6 +27,12 @@ RUN apt-get -y update && \
locale-gen en_US.UTF-8 && \
apt-get -y update && \
apt-get install -yq software-properties-common wget curl cron rsyslog && \
curl -OL http://dev.mysql.com/get/mysql-apt-config_0.8.15-1_all.deb && \
echo "mysql-apt-config mysql-apt-config/repo-codename select bionic" | sudo debconf-set-selections && \
echo "mysql-apt-config mysql-apt-config/repo-distro select ubuntu" | sudo debconf-set-selections && \
echo "mysql-apt-config mysql-apt-config/select-server select mysql-8.0" | sudo debconf-set-selections && \
DEBIAN_FRONTEND=noninteractive dpkg -i mysql-apt-config_0.8.15-1_all.deb && \
rm -f mysql-apt-config_0.8.15-1_all.deb && \
wget http://nginx.org/keys/nginx_signing.key && \
apt-key add nginx_signing.key && \
echo "deb http://nginx.org/packages/mainline/ubuntu/ bionic nginx" >> /etc/apt/sources.list.d/nginx.list && \
@ -33,9 +42,9 @@ RUN apt-get -y update && \
apt-get install -yq apt-transport-https && \
apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 3FA7E0328081BFF6A14DA29AA6A19B38D3D831EF && \
echo "deb https://download.mono-project.com/repo/ubuntu stable-bionic main" | tee /etc/apt/sources.list.d/mono-official.list && \
echo "deb https://artifacts.elastic.co/packages/6.x/apt stable main" | tee -a /etc/apt/sources.list.d/elastic-6.x.list && \
echo "deb https://artifacts.elastic.co/packages/7.x/apt stable main" | tee -a /etc/apt/sources.list.d/elastic-7.x.list && \
apt-get -y update && \
apt-get install -yq elasticsearch=6.5.0 && \
apt-get install -yq elasticsearch=7.4.0 && \
add-apt-repository -y ppa:certbot/certbot && \
curl -sL https://deb.nodesource.com/setup_12.x | sudo -E bash - && \
apt-get install -y nodejs && \
@ -46,9 +55,9 @@ RUN apt-get -y update && \
apt-get -y update && \
apt-get install -yq nginx && \
cd ~ && \
wget http://www-us.apache.org/dist/kafka/2.2.1/kafka_2.12-2.2.1.tgz && \
tar xzf kafka_2.12-2.2.1.tgz && \
rm kafka_2.12-2.2.1.tgz && \
wget https://downloads.apache.org/kafka/2.5.0/kafka_2.12-2.5.0.tgz && \
tar xzf kafka_2.12-2.5.0.tgz && \
rm kafka_2.12-2.5.0.tgz && \
echo "#!/bin/sh\nexit 0" > /usr/sbin/policy-rc.d && \
apt-get install -yq libgdiplus \
python-certbot-nginx \
@ -67,19 +76,20 @@ RUN apt-get -y update && \
mysql-client \
mysql-server
RUN git clone https://github.com/ONLYOFFICE/AppServer.git /app/onlyoffice/src/
RUN git clone https://github.com/ONLYOFFICE/AppServer.git /app/onlyoffice/src/ && \
cd /app/onlyoffice/src/ && \
git checkout ${GIT_BRANCH} && \
git pull
RUN cd /app/onlyoffice/src/ && \
yarn install --cwd web/ASC.Web.Components --frozen-lockfile > build/ASC.Web.Components.log && \
npm run build --prefix web/ASC.Web.Components && \
yarn pack --cwd web/ASC.Web.Components
RUN cd /app/onlyoffice/src/ && \
component=$(ls web/ASC.Web.Components/asc-web-components-v1.*.tgz) && \
yarn remove asc-web-components --cwd web/ASC.Web.Common --peer && \
yarn remove asc-web-components --cwd web/ASC.Web.Common --peer && \
yarn add file:../../$component --cwd web/ASC.Web.Common --cache-folder ../../yarn --peer && \
yarn install --cwd web/ASC.Web.Common --frozen-lockfile > build/ASC.Web.Common.log && \
npm run build --prefix web/ASC.Web.Common && \
yarn pack --cwd web/ASC.Web.Common
RUN cd /app/onlyoffice/src/ && \
@ -89,10 +99,10 @@ RUN cd /app/onlyoffice/src/ && \
RUN cd /app/onlyoffice/src/ && \
component=$(ls web/ASC.Web.Components/asc-web-components-v1.*.tgz) && \
common=$(ls web/ASC.Web.Common/asc-web-common-v1.*.tgz) && \
yarn remove asc-web-components asc-web-common --cwd web/ASC.Web.Client && \
common=$(ls web/ASC.Web.Common/asc-web-common-v1.*.tgz) && \
yarn remove asc-web-components asc-web-common --cwd web/ASC.Web.Client && \
yarn add ../../$component --cwd web/ASC.Web.Client --cache-folder ../../yarn && \
yarn add ../../$common --cwd web/ASC.Web.Client --cache-folder ../../yarn && \
yarn add ../../$common --cwd web/ASC.Web.Client --cache-folder ../../yarn && \
yarn install --cwd web/ASC.Web.Client --frozen-lockfile || (cd web/ASC.Web.Client && npm i && cd ../../) && \
npm run build --prefix web/ASC.Web.Client && \
rm -rf /var/www/studio/client/* && \
@ -101,10 +111,22 @@ RUN cd /app/onlyoffice/src/ && \
RUN cd /app/onlyoffice/src/ && \
component=$(ls web/ASC.Web.Components/asc-web-components-v1.*.tgz) && \
common=$(ls web/ASC.Web.Common/asc-web-common-v1.*.tgz) && \
yarn remove asc-web-components asc-web-common --cwd products/ASC.People/Client && \
common=$(ls web/ASC.Web.Common/asc-web-common-v1.*.tgz) && \
yarn remove asc-web-components asc-web-common --cwd products/ASC.Files/Client && \
yarn add ../../../$component --cwd products/ASC.Files/Client --cache-folder ../../../yarn && \
yarn add ../../../$common --cwd products/ASC.Files/Client --cache-folder ../../../yarn && \
yarn install --cwd products/ASC.Files/Client --frozen-lockfile || (cd products/ASC.Files/Client && npm i && cd ../../../) && \
npm run build --prefix products/ASC.Files/Client && \
mkdir -p /var/www/products/ASC.Files/client && \
cp -Rf products/ASC.Files/Client/build/* /var/www/products/ASC.Files/client && \
mkdir -p /var/www/products/ASC.Files/client/products/files
RUN cd /app/onlyoffice/src/ && \
component=$(ls web/ASC.Web.Components/asc-web-components-v1.*.tgz) && \
common=$(ls web/ASC.Web.Common/asc-web-common-v1.*.tgz) && \
yarn remove asc-web-components asc-web-common --cwd products/ASC.People/Client && \
yarn add ../../../$component --cwd products/ASC.People/Client --cache-folder ../../../yarn && \
yarn add ../../../$common --cwd products/ASC.People/Client --cache-folder ../../../yarn && \
yarn add ../../../$common --cwd products/ASC.People/Client --cache-folder ../../../yarn && \
yarn install --cwd products/ASC.People/Client --frozen-lockfile || (cd products/ASC.People/Client && npm i && cd ../../../) && \
npm run build --prefix products/ASC.People/Client && \
mkdir -p /var/www/products/ASC.People/client && \
@ -113,12 +135,21 @@ RUN cd /app/onlyoffice/src/ && \
RUN cd /app/onlyoffice/src/ && \
rm -f /etc/nginx/conf.d/* && \
cp -rf config/nginx/onlyoffice*.conf /etc/nginx/conf.d/ && \
mkdir -p /var/www/public/ && cp -f public/* /var/www/public/ && \
mkdir -p /app/onlyoffice/config/ && cp -rf config/* /app/onlyoffice/config/ && \
cp -f config/nginx/onlyoffice*.conf /etc/nginx/conf.d/ && \
mkdir -p /etc/nginx/includes/ && cp -f config/nginx/includes/onlyoffice*.conf /etc/nginx/includes/ && \
sed -e 's/#//' -i /etc/nginx/conf.d/onlyoffice.conf && \
cd products/ASC.People/Server && \
dotnet -d publish -o /var/www/products/ASC.People/server && \
cd ../../../ && \
cd products/ASC.Files/Server && \
dotnet -d publish -o /var/www/products/ASC.Files/server && \
cp -avrf DocStore /var/www/products/ASC.Files/server/ && \
cd ../../../ && \
cd products/ASC.Files/Service && \
dotnet -d publish -o /var/www/products/ASC.Files/service && \
cd ../../../ && \
cd web/ASC.Web.Api && \
dotnet -d publish -o /var/www/studio/api && \
cd ../../ && \
@ -128,25 +159,34 @@ RUN cd /app/onlyoffice/src/ && \
cd common/services/ASC.Notify && \
dotnet -d publish -o /var/www/services/notify && \
cd ../../../ && \
cd common/services/ASC.ApiSystem && \
dotnet -d publish -o /var/www/services/apisystem && \
cd ../../../ && \
cd common/services/ASC.UrlShortener.Svc && \
dotnet -d publish -o /services/urlshortener/service && \
cd ../../../ && \
yarn install --cwd common/ASC.UrlShortener --frozen-lockfile && \
mkdir -p /var/www/services/urlshortener/client && cp -Rf common/ASC.UrlShortener/* /var/www/services/urlshortener/client && \
cd common/services/ASC.Studio.Notify && \
dotnet -d publish -o /var/www/services/studio.notify
COPY config/mysql/conf.d/mysql.cnf /etc/mysql/conf.d/mysql.cnf
COPY config/supervisor/supervisord.conf /etc/supervisor/conf.d/supervisord.conf
COPY config/mysql/dotnet_dump.sql /app/onlyoffice/dotnet_dump.sql
RUN sed -i 's/172.18.0.5/localhost/' /app/onlyoffice/config/appsettings.test.json
RUN sed -i 's/Server=.*;Port=/Server=127.0.0.1;Port=/' /app/onlyoffice/config/appsettings.test.json
RUN mkdir -p /var/mysqld/ && \
chown -R mysql:mysql /var/lib/mysql /var/run/mysqld /var/mysqld/ && \
service mysql start && \
sudo -u mysql bash -c "/usr/bin/pidproxy /var/mysqld/mysqld.pid /usr/bin/mysqld_safe --pid-file=/var/mysqld/mysqld.pid &" && \
sleep 5s && \
mysql -e "CREATE DATABASE IF NOT EXISTS onlyoffice CHARACTER SET utf8 COLLATE 'utf8_general_ci'" && \
mysql -D "onlyoffice" < /app/onlyoffice/src/sql/app.sql && \
mysql -D "onlyoffice" < /app/onlyoffice/src/sql/app.data.sql && \
mysql -D "onlyoffice" < /app/onlyoffice/dotnet_dump.sql && \
mysql -D "onlyoffice" -e 'CREATE USER IF NOT EXISTS "onlyoffice_user"@"localhost" IDENTIFIED WITH mysql_native_password BY "onlyoffice_pass";' && \
mysql -D "onlyoffice" -e 'GRANT ALL PRIVILEGES ON *.* TO 'onlyoffice_user'@'localhost' IDENTIFIED BY "onlyoffice_pass";' && \
mysql -D "onlyoffice" -e 'GRANT ALL PRIVILEGES ON *.* TO 'onlyoffice_user'@'localhost';' && \
mysql -D "onlyoffice" -e 'UPDATE core_user SET email = "paul.bannov@gmail.com";' && \
mysql -D "onlyoffice" -e 'UPDATE core_usersecurity SET pwdhash = "vLFfghR5tNV3K9DKhmwArV+SbjWAcgZZzIDTnJ0JgCo=", pwdhashsha512 = "USubvPlB+ogq0Q1trcSupg==";' && \
service mysql stop
killall -u mysql -n mysql
RUN rm -rf /var/lib/apt/lists/*

File diff suppressed because one or more lines are too long

View File

@ -9,46 +9,80 @@ environment=PATH=/usr/local/sbin:/usr/bin:/bin:/usr/local/bin
user=mysql
[program:kafka]
directory=/root/kafka_2.12-2.2.1/
command=/root/kafka_2.12-2.2.1/bin/kafka-server-start.sh /root/kafka_2.12-2.2.1/config/server.properties
directory=/root/kafka_2.12-2.5.0/
command=/root/kafka_2.12-2.5.0/bin/kafka-server-start.sh /root/kafka_2.12-2.5.0/config/server.properties
autostart=true
autorestart=true
[program:kafka_zookeeper]
directory=/root/kafka_2.12-2.2.1/
command=/root/kafka_2.12-2.2.1/bin/zookeeper-server-start.sh /root/kafka_2.12-2.2.1/config/zookeeper.properties
directory=/root/kafka_2.12-2.5.0/
command=/root/kafka_2.12-2.5.0/bin/zookeeper-server-start.sh /root/kafka_2.12-2.5.0/config/zookeeper.properties
autostart=true
autorestart=true
[program:api]
directory=/var/www/studio/api/
command=dotnet ASC.Web.Api.dll --urls=http://0.0.0.0:5000 --pathToConf=/app/onlyoffice/config/ --$STORAGE_ROOT=/app/onlyoffice/data/ --ENVIRONMENT=test
command=dotnet ASC.Web.Api.dll --urls=http://0.0.0.0:5000 --pathToConf=/app/onlyoffice/config/ --$STORAGE_ROOT=/app/onlyoffice/data/ --log:dir=/var/log/onlyoffice --log:name=api --ENVIRONMENT=test
autostart=true
autorestart=true
user=root
environment=ASPNETCORE_ENVIRONMENT=test
[program:notify]
directory=/var/www/services/notify/
command=dotnet ASC.Notify.dll --urls=http://0.0.0.0:5005 --pathToConf=/app/onlyoffice/config/ --$STORAGE_ROOT=/app/onlyoffice/data/ --ENVIRONMENT=test
[program:api_system]
directory=/var/www/services/apisystem
command=dotnet ASC.ApiSystem.dll --urls=http://0.0.0.0:5010 --pathToConf=/app/onlyoffice/config/ --$STORAGE_ROOT=/app/onlyoffice/data/ --log:dir=/var/log/onlyoffice --log:name=apisystem --ENVIRONMENT=test
autostart=true
autorestart=true
user=root
environment=ASPNETCORE_ENVIRONMENT=test
[program:urlshortener]
directory=/services/urlshortener/service
command=dotnet ASC.UrlShortener.Svc.dll --urls=http://0.0.0.0:5015 --pathToConf=/app/onlyoffice/config/ --$STORAGE_ROOT=/app/onlyoffice/data/ --log:dir=/var/log/onlyoffice --log:name=urlshortener --ENVIRONMENT=test
autostart=true
autorestart=true
user=root
environment=ASPNETCORE_ENVIRONMENT=test
[program:studio_notify]
directory=/var/www/services/studio.notify/
command=dotnet ASC.Studio.Notify.dll --urls=http://0.0.0.0:5006 --pathToConf=/app/onlyoffice/config/ --$STORAGE_ROOT=/app/onlyoffice/data/ --ENVIRONMENT=test
command=dotnet ASC.Studio.Notify.dll --urls=http://0.0.0.0:5006 --pathToConf=/app/onlyoffice/config/ --$STORAGE_ROOT=/app/onlyoffice/data/ --log:dir=/var/log/onlyoffice --log:name=notify.studio --core:products:folder=/var/www/products/ --core:products:subfolder=server --ENVIRONMENT=test
autostart=true
autorestart=true
user=root
environment=ASPNETCORE_ENVIRONMENT=test
[program:people]
directory=/var/www/products/ASC.People/server/
command=dotnet ASC.People.dll --urls=http://0.0.0.0:5004 --pathToConf=/app/onlyoffice/config/ --$STORAGE_ROOT=/app/onlyoffice/data/ --ENVIRONMENT=test
command=dotnet ASC.People.dll --urls=http://0.0.0.0:5004 --pathToConf=/app/onlyoffice/config/ --$STORAGE_ROOT=/app/onlyoffice/data/ --log:dir=/var/log/onlyoffice/ --log:name=people --ENVIRONMENT=test
autostart=true
autorestart=true
user=root
environment=ASPNETCORE_ENVIRONMENT=test
[program:files]
directory=/var/www/products/ASC.Files/server/
command=dotnet ASC.Files.dll --urls=http://0.0.0.0:5007 --pathToConf=/app/onlyoffice/config/ --$STORAGE_ROOT=/app/onlyoffice/data/ --log:dir=/var/log/onlyoffice/ --log:name=files --ENVIRONMENT=test
autostart=true
autorestart=true
user=root
environment=ASPNETCORE_ENVIRONMENT=test
[program:files_services]
directory=/var/www/products/ASC.Files/service
command=dotnet ASC.Files.Service.dll --urls=http://0.0.0.0:5009 --pathToConf=/app/onlyoffice/config/ --$STORAGE_ROOT=/app/onlyoffice/data/ --log:dir=/var/log/onlyoffice/ --log:name=filesservice --core:products:folder=/var/www/products --core:products:subfolder=server --ENVIRONMENT=test
autostart=true
autorestart=true
user=root
environment=ASPNETCORE_ENVIRONMENT=test
[program:studio]
directory=/var/www/studio/server/
command=dotnet ASC.Web.Studio.dll --urls=http://0.0.0.0:5003 --pathToConf=/app/onlyoffice/config/ --$STORAGE_ROOT=/app/onlyoffice/data/ --ENVIRONMENT=test
command=dotnet ASC.Web.Studio.dll --urls=http://0.0.0.0:5003 --pathToConf=/app/onlyoffice/config/ --$STORAGE_ROOT=/app/onlyoffice/data/ --log:dir=/var/log/onlyoffice --log:name=web --ENVIRONMENT=test
autostart=true
autorestart=true
user=root
environment=ASPNETCORE_ENVIRONMENT=test
[program:nginx]
command=/usr/sbin/nginx -g "daemon off;"

View File

@ -231,6 +231,13 @@ namespace ASC.Core.Common
return baseUri.ToString().TrimEnd('/');
}
public void Initialize(string serverUri)
{
var uri = new Uri(serverUri.Replace('*', 'x').Replace('+', 'x'));
_serverRoot = new UriBuilder(uri.Scheme, LOCALHOST, uri.Port);
_vpath = "/" + uri.AbsolutePath.Trim('/');
}
}
public static class BaseCommonLinkUtilityExtension

View File

@ -76,25 +76,34 @@ namespace ASC.Core
public UserManager(
IUserService service,
IHttpContextAccessor httpContextAccessor,
TenantManager tenantManager,
PermissionContext permissionContext,
UserManagerConstants userManagerConstants)
{
UserService = service;
Accessor = httpContextAccessor;
TenantManager = tenantManager;
PermissionContext = permissionContext;
UserManagerConstants = userManagerConstants;
Constants = UserManagerConstants.Constants;
}
public UserManager(
IUserService service,
TenantManager tenantManager,
PermissionContext permissionContext,
UserManagerConstants userManagerConstants,
IHttpContextAccessor httpContextAccessor)
: this(service, tenantManager, permissionContext, userManagerConstants)
{
Accessor = httpContextAccessor;
}
public void ClearCache()
{
if (UserService is ICachedService)
if (UserService is ICachedService service)
{
((ICachedService)UserService).InvalidateCache();
service.InvalidateCache();
}
}

View File

@ -8,7 +8,10 @@ namespace ASC.Core.Common.EF.Model
[Table("feed_users")]
public class FeedUsers : BaseEntity
{
[Column("feed_id")]
public string FeedId { get; set; }
[Column("user_id")]
public Guid UserId { get; set; }
public override object[] GetKeys() => new object[] { FeedId, UserId };

View File

@ -49,7 +49,8 @@ namespace ASC.Feed.Data
public TenantUtil TenantUtil { get; }
public FeedDbContext FeedDbContext { get; }
public FeedAggregateDataProvider(DbContextManager<FeedDbContext> dbContextManager)
public FeedAggregateDataProvider(AuthContext authContext, TenantManager tenantManager, TenantUtil tenantUtil, DbContextManager<FeedDbContext> dbContextManager)
: this(authContext, tenantManager, tenantUtil)
{
FeedDbContext = dbContextManager.Get(Constants.FeedDbId);
}
@ -124,7 +125,10 @@ namespace ASC.Feed.Data
if (f.ClearRightsBeforeInsert)
{
var fu = FeedDbContext.FeedUsers.Where(r => r.FeedId == f.Id).FirstOrDefault();
FeedDbContext.FeedUsers.Remove(fu);
if (fu != null)
{
FeedDbContext.FeedUsers.Remove(fu);
}
}
FeedDbContext.AddOrUpdate(r => r.FeedAggregates, feedAggregate);
@ -382,6 +386,8 @@ namespace ASC.Feed.Data
{
public static DIHelper AddFeedAggregateDataProvider(this DIHelper services)
{
services.TryAddScoped<FeedAggregateDataProvider>();
return services
.AddAuthContextService()
.AddTenantManagerService()

View File

@ -0,0 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Library</OutputType>
<TargetFramework>netcoreapp3.1</TargetFramework>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\web\ASC.Web.Core\ASC.Web.Core.csproj" />
<ProjectReference Include="..\..\ASC.Common\ASC.Common.csproj" />
<ProjectReference Include="..\..\ASC.Core.Common\ASC.Core.Common.csproj" />
<ProjectReference Include="..\..\ASC.Feed\ASC.Feed.csproj" />
</ItemGroup>
<ItemGroup>
<Folder Include="Modules\" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,46 @@
using System;
using ASC.Common.Utils;
using Microsoft.Extensions.Configuration;
namespace ASC.Feed.Configuration
{
public class FeedSettings
{
public string ServerRoot { get; set; }
public TimeSpan AggregatePeriod { get; set; }
public TimeSpan AggregateInterval { get; set; }
public TimeSpan RemovePeriod { get; set; }
public static FeedSettings GetInstance(IConfiguration configuration)
{
var result = configuration.GetSetting<FeedSettings>("feed");
if (string.IsNullOrEmpty(result.ServerRoot))
{
result.ServerRoot = "http://*/";
}
if (result.AggregatePeriod == TimeSpan.Zero)
{
result.AggregatePeriod = TimeSpan.FromMinutes(5);
}
if (result.AggregateInterval == TimeSpan.Zero)
{
result.AggregateInterval = TimeSpan.FromDays(14);
}
if (result.RemovePeriod == TimeSpan.Zero)
{
result.RemovePeriod = TimeSpan.FromDays(1);
}
return result;
}
}
}

View File

@ -0,0 +1,131 @@
/*
*
* (c) Copyright Ascensio System Limited 2010-2020
*
* This program is freeware. You can redistribute it and/or modify it under the terms of the GNU
* General Public License (GPL) version 3 as published by the Free Software Foundation (https://www.gnu.org/copyleft/gpl.html).
* In accordance with Section 7(a) of the GNU GPL its Section 15 shall be amended to the effect that
* Ascensio System SIA expressly excludes the warranty of non-infringement of any third-party rights.
*
* THIS PROGRAM IS DISTRIBUTED WITHOUT ANY WARRANTY; WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR
* FITNESS FOR A PARTICULAR PURPOSE. For more details, see GNU GPL at https://www.gnu.org/copyleft/gpl.html
*
* You can contact Ascensio System SIA by email at sales@onlyoffice.com
*
* The interactive user interfaces in modified source and object code versions of ONLYOFFICE must display
* Appropriate Legal Notices, as required under Section 5 of the GNU GPL version 3.
*
* Pursuant to Section 7 § 3(b) of the GNU GPL you must retain the original ONLYOFFICE logo which contains
* relevant author attributions when distributing the software. If the display of the logo in its graphic
* form is not reasonably feasible for technical reasons, you must include the words "Powered by ONLYOFFICE"
* in every copy of the program you distribute.
* Pursuant to Section 7 § 3(e) we decline to grant you any rights under trademark law for use of our trademarks.
*
*/
using System;
using System.Collections.Generic;
using ASC.Core;
using ASC.Feed.Data;
using ASC.Web.Core;
namespace ASC.Feed.Aggregator.Modules
{
public abstract class FeedModule : IFeedModule
{
public abstract string Name { get; }
public abstract string Product { get; }
public abstract Guid ProductID { get; }
protected abstract string DbId { get; }
protected int Tenant
{
get { return TenantManager.GetCurrentTenant().TenantId; }
}
public TenantManager TenantManager { get; }
public WebItemSecurity WebItemSecurity { get; }
public FeedModule(TenantManager tenantManager, WebItemSecurity webItemSecurity)
{
TenantManager = tenantManager;
WebItemSecurity = webItemSecurity;
}
protected string GetGroupId(string item, Guid author, string rootId = null, int action = -1)
{
const int interval = 2;
var now = DateTime.UtcNow;
var hours = now.Hour;
var groupIdHours = hours - (hours % interval);
if (rootId == null)
{
// groupId = {item}_{author}_{date}
return string.Format("{0}_{1}_{2}",
item,
author,
now.ToString("yyyy.MM.dd.") + groupIdHours);
}
if (action == -1)
{
// groupId = {item}_{author}_{date}_{rootId}_{action}
return string.Format("{0}_{1}_{2}_{3}",
item,
author,
now.ToString("yyyy.MM.dd.") + groupIdHours,
rootId);
}
// groupId = {item}_{author}_{date}_{rootId}_{action}
return string.Format("{0}_{1}_{2}_{3}_{4}",
item,
author,
now.ToString("yyyy.MM.dd.") + groupIdHours,
rootId,
action);
}
public abstract IEnumerable<int> GetTenantsWithFeeds(DateTime fromTime);
public abstract IEnumerable<Tuple<Feed, object>> GetFeeds(FeedFilter filter);
public virtual bool VisibleFor(Feed feed, object data, Guid userId)
{
return WebItemSecurity.IsAvailableForUser(ProductID, userId);
}
public virtual void VisibleFor(List<Tuple<FeedRow, object>> feed, Guid userId)
{
if (!WebItemSecurity.IsAvailableForUser(ProductID, userId)) return;
foreach (var tuple in feed)
{
if (VisibleFor(tuple.Item1.Feed, tuple.Item2, userId))
{
tuple.Item1.Users.Add(userId);
}
}
}
protected static Guid ToGuid(object guid)
{
try
{
var str = guid as string;
return !string.IsNullOrEmpty(str) ? new Guid(str) : Guid.Empty;
}
catch (Exception)
{
return Guid.Empty;
}
}
}
}

View File

@ -0,0 +1,47 @@
/*
*
* (c) Copyright Ascensio System Limited 2010-2020
*
* This program is freeware. You can redistribute it and/or modify it under the terms of the GNU
* General Public License (GPL) version 3 as published by the Free Software Foundation (https://www.gnu.org/copyleft/gpl.html).
* In accordance with Section 7(a) of the GNU GPL its Section 15 shall be amended to the effect that
* Ascensio System SIA expressly excludes the warranty of non-infringement of any third-party rights.
*
* THIS PROGRAM IS DISTRIBUTED WITHOUT ANY WARRANTY; WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR
* FITNESS FOR A PARTICULAR PURPOSE. For more details, see GNU GPL at https://www.gnu.org/copyleft/gpl.html
*
* You can contact Ascensio System SIA by email at sales@onlyoffice.com
*
* The interactive user interfaces in modified source and object code versions of ONLYOFFICE must display
* Appropriate Legal Notices, as required under Section 5 of the GNU GPL version 3.
*
* Pursuant to Section 7 § 3(b) of the GNU GPL you must retain the original ONLYOFFICE logo which contains
* relevant author attributions when distributing the software. If the display of the logo in its graphic
* form is not reasonably feasible for technical reasons, you must include the words "Powered by ONLYOFFICE"
* in every copy of the program you distribute.
* Pursuant to Section 7 § 3(e) we decline to grant you any rights under trademark law for use of our trademarks.
*
*/
using System;
using System.Collections.Generic;
using ASC.Feed.Data;
namespace ASC.Feed.Aggregator.Modules
{
public interface IFeedModule
{
string Name { get; }
string Product { get; }
IEnumerable<int> GetTenantsWithFeeds(DateTime fromTime);
IEnumerable<Tuple<Feed, object>> GetFeeds(FeedFilter filter);
bool VisibleFor(Feed feed, object data, Guid userId);
void VisibleFor(List<Tuple<FeedRow, object>> feed, Guid userId);
}
}

View File

@ -0,0 +1,306 @@
/*
*
* (c) Copyright Ascensio System Limited 2010-2020
*
* This program is freeware. You can redistribute it and/or modify it under the terms of the GNU
* General Public License (GPL) version 3 as published by the Free Software Foundation (https://www.gnu.org/copyleft/gpl.html).
* In accordance with Section 7(a) of the GNU GPL its Section 15 shall be amended to the effect that
* Ascensio System SIA expressly excludes the warranty of non-infringement of any third-party rights.
*
* THIS PROGRAM IS DISTRIBUTED WITHOUT ANY WARRANTY; WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR
* FITNESS FOR A PARTICULAR PURPOSE. For more details, see GNU GPL at https://www.gnu.org/copyleft/gpl.html
*
* You can contact Ascensio System SIA by email at sales@onlyoffice.com
*
* The interactive user interfaces in modified source and object code versions of ONLYOFFICE must display
* Appropriate Legal Notices, as required under Section 5 of the GNU GPL version 3.
*
* Pursuant to Section 7 § 3(b) of the GNU GPL you must retain the original ONLYOFFICE logo which contains
* relevant author attributions when distributing the software. If the display of the logo in its graphic
* form is not reasonably feasible for technical reasons, you must include the words "Powered by ONLYOFFICE"
* in every copy of the program you distribute.
* Pursuant to Section 7 § 3(e) we decline to grant you any rights under trademark law for use of our trademarks.
*
*/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using ASC.Common;
using ASC.Common.Caching;
using ASC.Common.Logging;
using ASC.Core;
using ASC.Core.Common;
using ASC.Feed.Aggregator.Modules;
using ASC.Feed.Configuration;
using ASC.Feed.Data;
using Autofac;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Options;
namespace ASC.Feed.Aggregator
{
public class FeedAggregatorService : IHostedService
{
private ILog Log { get; set; }
//private static readonly SignalrServiceClient signalrServiceClient = new SignalrServiceClient("counters");//TODO
private Timer aggregateTimer;
private Timer removeTimer;
private volatile bool isStopped;
private readonly object aggregateLock = new object();
private readonly object removeLock = new object();
public IConfiguration Configuration { get; }
public IServiceProvider ServiceProvider { get; }
public IContainer Container { get; }
public FeedAggregatorService(
IConfiguration configuration,
IServiceProvider serviceProvider,
IContainer container,
IOptionsMonitor<ILog> optionsMonitor)
{
Configuration = configuration;
ServiceProvider = serviceProvider;
Container = container;
Log = optionsMonitor.Get("ASC.Feed.Agregator");
}
public Task StartAsync(CancellationToken cancellationToken)
{
var cfg = FeedSettings.GetInstance(Configuration);
isStopped = false;
aggregateTimer = new Timer(AggregateFeeds, cfg.AggregateInterval, TimeSpan.Zero, cfg.AggregatePeriod);
removeTimer = new Timer(RemoveFeeds, cfg.AggregateInterval, cfg.RemovePeriod, cfg.RemovePeriod);
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken)
{
isStopped = true;
if (aggregateTimer != null)
{
aggregateTimer.Change(Timeout.Infinite, Timeout.Infinite);
aggregateTimer.Dispose();
aggregateTimer = null;
}
if (removeTimer != null)
{
removeTimer.Change(Timeout.Infinite, Timeout.Infinite);
removeTimer.Dispose();
removeTimer = null;
}
return Task.CompletedTask;
}
private void AggregateFeeds(object interval)
{
if (!Monitor.TryEnter(aggregateLock)) return;
try
{
var cfg = FeedSettings.GetInstance(Configuration);
using var scope = ServiceProvider.CreateScope();
var commonLinkUtility = scope.ServiceProvider.GetService<BaseCommonLinkUtility>();
commonLinkUtility.Initialize(cfg.ServerRoot);
var tenantManager = scope.ServiceProvider.GetService<TenantManager>();
var feedAggregateDataProvider = scope.ServiceProvider.GetService<FeedAggregateDataProvider>();
var start = DateTime.UtcNow;
Log.DebugFormat("Start of collecting feeds...");
var unreadUsers = new Dictionary<int, Dictionary<Guid, int>>();
using var autofacScope = Container.BeginLifetimeScope();
var modules = autofacScope.Resolve<IEnumerable<IFeedModule>>();
foreach (var module in modules)
{
var result = new List<FeedRow>();
var fromTime = feedAggregateDataProvider.GetLastTimeAggregate(module.GetType().Name);
if (fromTime == default) fromTime = DateTime.UtcNow.Subtract((TimeSpan)interval);
var toTime = DateTime.UtcNow;
var tenants = Attempt(10, () => module.GetTenantsWithFeeds(fromTime)).ToList();
Log.DebugFormat("Find {1} tenants for module {0}.", module.GetType().Name, tenants.Count());
foreach (var tenant in tenants)
{
// Warning! There is hack here!
// clearing the cache to get the correct acl
var cache = AscCache.Memory;
cache.Remove("acl" + tenant);
cache.Remove("/webitemsecurity/" + tenant);
//cache.Remove(string.Format("sub/{0}/{1}/{2}", tenant, "6045b68c-2c2e-42db-9e53-c272e814c4ad", NotifyConstants.Event_NewCommentForMessage.ID));
try
{
if (tenantManager.GetTenant(tenant) == null)
{
continue;
}
tenantManager.SetCurrentTenant(tenant);
var userManager = scope.ServiceProvider.GetService<UserManager>();
var securityContext = scope.ServiceProvider.GetService<SecurityContext>();
var authManager = scope.ServiceProvider.GetService<AuthManager>();
var users = userManager.GetUsers();
var feeds = Attempt(10, () => module.GetFeeds(new FeedFilter(fromTime, toTime) { Tenant = tenant }).Where(r => r.Item1 != null).ToList());
Log.DebugFormat("{0} feeds in {1} tenant.", feeds.Count, tenant);
var tenant1 = tenant;
var module1 = module;
var feedsRow = feeds
.Select(tuple => new Tuple<FeedRow, object>(new FeedRow(tuple.Item1)
{
Tenant = tenant1,
ProductId = module1.Product
}, tuple.Item2))
.ToList();
foreach (var u in users)
{
if (isStopped)
{
return;
}
if (!TryAuthenticate(securityContext, authManager, tenant1, u.ID))
{
continue;
}
module.VisibleFor(feedsRow, u.ID);
}
result.AddRange(feedsRow.Select(r => r.Item1));
}
catch (Exception ex)
{
Log.ErrorFormat("Tenant: {0}, {1}", tenant, ex);
}
}
feedAggregateDataProvider.SaveFeeds(result, module.GetType().Name, toTime);
foreach (var res in result)
{
foreach (var userGuid in res.Users.Where(userGuid => !userGuid.Equals(res.ModifiedById)))
{
if (!unreadUsers.TryGetValue(res.Tenant, out var dictionary))
{
dictionary = new Dictionary<Guid, int>();
}
if (dictionary.ContainsKey(userGuid))
{
++dictionary[userGuid];
}
else
{
dictionary.Add(userGuid, 1);
}
unreadUsers[res.Tenant] = dictionary;
}
}
}
//signalrServiceClient.SendUnreadUsers(unreadUsers);
Log.DebugFormat("Time of collecting news: {0}", DateTime.UtcNow - start);
}
catch (Exception ex)
{
Log.Error(ex);
}
finally
{
Monitor.Exit(aggregateLock);
}
}
private void RemoveFeeds(object interval)
{
if (!Monitor.TryEnter(removeLock)) return;
try
{
using var scope = ServiceProvider.CreateScope();
var feedAggregateDataProvider = scope.ServiceProvider.GetService<FeedAggregateDataProvider>();
Log.DebugFormat("Start of removing old news");
feedAggregateDataProvider.RemoveFeedAggregate(DateTime.UtcNow.Subtract((TimeSpan)interval));
}
catch (Exception ex)
{
Log.Error(ex);
}
finally
{
Monitor.Exit(removeLock);
}
}
private static T Attempt<T>(int count, Func<T> action)
{
var counter = 0;
while (true)
{
try
{
return action();
}
catch
{
if (count < ++counter)
{
throw;
}
}
}
}
private static bool TryAuthenticate(SecurityContext securityContext, AuthManager authManager, int tenantId, Guid userid)
{
try
{
securityContext.AuthenticateMe(authManager.GetAccountByID(tenantId, userid));
return true;
}
catch
{
return false;
}
}
}
public static class FeedAggregatorServiceExtension
{
public static DIHelper AddFeedAggregatorService(this DIHelper services)
{
services.TryAddSingleton<FeedAggregatorService>();
return services
.AddBaseCommonLinkUtilityService()
.AddTenantManagerService()
.AddUserManagerService()
.AddSecurityContextService()
.AddAuthManager()
.AddFeedAggregateDataProvider();
}
}
}

View File

@ -4,8 +4,11 @@ import { Router, Switch, Redirect } from "react-router-dom";
import { Loader } from "asc-web-components";
import Home from "./components/pages/Home";
import DocEditor from "./components/pages/DocEditor";
import { history, PrivateRoute, PublicRoute, Login, Error404, StudioLayout, Offline } from "asc-web-common";
const VersionHistory = React.lazy(() => import('./components/pages/VersionHistory'));
const withStudioLayout = Component => props => <StudioLayout><Component {...props} /></StudioLayout>;
const App = ({ settings }) => {
@ -21,6 +24,7 @@ const App = ({ settings }) => {
<Redirect exact from="/" to={`${homepage}`} />
<PrivateRoute exact path={[homepage, `${homepage}/filter`]} component={withStudioLayout(Home)} />
<PrivateRoute exact path={`${homepage}/doceditor`} component={DocEditor} />
<PrivateRoute exact path={`${homepage}/:fileId/history`} component={withStudioLayout(VersionHistory)} />
<PublicRoute exact path={["/login","/login/error=:error", "/login/confirmed-email=:confirmedEmail"]} component={Login} />
<PrivateRoute component={withStudioLayout(Error404)} />
</Switch>

View File

@ -1,14 +1,15 @@
import React from "react";
import { TreeMenu, TreeNode, Icons, toastr, utils } from "asc-web-components";
import { api } from "asc-web-common";
import { api, constants } from "asc-web-common";
const { files } = api;
const { FolderType, ShareAccessRights } = constants;
class TreeFolders extends React.Component {
constructor(props) {
super(props);
const treeData = props.data;
this.state = { treeData, expandedKeys: props.expandedKeys, loaded: true };
this.state = { treeData, expandedKeys: props.expandedKeys };
}
getFolderIcon = key => {
@ -32,8 +33,48 @@ class TreeFolders extends React.Component {
}
};
showDragItems = (item) => {
const { isAdmin, myId, commonId, isCommon, isMy, isShare, currentId } = this.props;
if (item.id === currentId) {
return false;
}
if (
item.rootFolderType === FolderType.SHARE &&
item.access === ShareAccessRights.FullAccess
) {
return true;
}
if (isAdmin) {
if (isMy || isCommon || isShare) {
if (
(item.pathParts &&
(item.pathParts[0] === myId || item.pathParts[0] === commonId)) ||
item.rootFolderType === FolderType.USER ||
item.rootFolderType === FolderType.COMMON
) {
return true;
}
}
} else {
if (isMy || isCommon || isShare) {
if (
(item.pathParts && item.pathParts[0] === myId) ||
item.rootFolderType === FolderType.USER
) {
return true;
}
}
}
return false;
};
getItems = data => {
return data.map(item => {
const dragging = this.showDragItems(item);
if (item.folders && item.folders.length > 0) {
return (
<TreeNode
@ -41,6 +82,7 @@ class TreeFolders extends React.Component {
key={item.id}
title={item.title}
icon={this.getFolderIcon(item.key)}
dragging={this.props.dragging && dragging}
>
{this.getItems(item.folders)}
</TreeNode>
@ -51,6 +93,7 @@ class TreeFolders extends React.Component {
id={item.id}
key={item.id}
title={item.title}
dragging={this.props.dragging && dragging}
isLeaf={item.foldersCount ? false : true}
icon={this.getFolderIcon(item.key)}
/>
@ -177,7 +220,7 @@ class TreeFolders extends React.Component {
this.props.setFilter(newFilter);
}
this.setState({ expandedKeys: data, loaded: false });
this.setState({ expandedKeys: data });
};
componentDidUpdate(prevProps) {
@ -195,17 +238,30 @@ class TreeFolders extends React.Component {
}
}
onMouseEnter = (data) => {
if (this.props.dragging) {
if(data.node.props.dragging) {
this.props.setDragItem(data.node.props.id);
}
}
};
onMouseLeave = data => {
if (this.props.dragging) {
this.props.setDragItem(null);
}
};
render() {
const {
selectedKeys,
fakeNewDocuments,
isLoading,
onSelect,
needUpdate
needUpdate,
onBadgeClick
} = this.props;
const { treeData, expandedKeys, loaded } = this.state;
const loadProp = loaded && needUpdate ? { loadData: this.onLoadData } : {};
const { treeData, expandedKeys } = this.state;
const loadProp = needUpdate ? { loadData: this.onLoadData } : {};
return (
<TreeMenu
@ -218,11 +274,13 @@ class TreeFolders extends React.Component {
switcherIcon={this.switcherIcon}
onSelect={onSelect}
selectedKeys={selectedKeys}
badgeLabel={fakeNewDocuments}
onBadgeClick={() => console.log("onBadgeClick")}
//badgeLabel={3}
//onBadgeClick={onBadgeClick}
{...loadProp}
expandedKeys={expandedKeys}
onExpand={this.onExpand}
onMouseEnter={this.onMouseEnter}
onMouseLeave={this.onMouseLeave}
>
{this.getItems(treeData)}
</TreeMenu>

View File

@ -5,12 +5,18 @@ import TreeFolders from "./TreeFolders";
import {
setFilter,
fetchFiles,
setTreeFolders
setTreeFolders,
setDragItem
} from "../../../store/files/actions";
import store from "../../../store/store";
import isEqual from "lodash/isEqual";
import { NewFilesPanel } from "../../panels";
class ArticleBodyContent extends React.Component {
state = { expandedKeys: this.props.filter.treeFolders };
state = {
expandedKeys: this.props.filter.treeFolders,
showNewFilesPanel: false,
};
componentDidUpdate(prevProps) {
if (
@ -30,55 +36,122 @@ class ArticleBodyContent extends React.Component {
newFilter.page = 0;
newFilter.startIndex = 0;
fetchFiles(data[0], newFilter, store.dispatch).catch(err =>
toastr.error(err)
).finally(() => onLoading(false));
fetchFiles(data[0], newFilter, store.dispatch)
.catch(err => toastr.error(err))
.finally(() => onLoading(false));
}
};
shouldComponentUpdate(nextProps, nextState) {
if (!isEqual(this.state, nextState) || !isEqual(this.props, nextProps)) {
return true;
}
return false;
}
onShowNewFilesPanel = () => {
this.setState({showNewFilesPanel: !this.state.showNewFilesPanel});
};
render() {
const {
data,
selectedKeys,
fakeNewDocuments,
filter,
setFilter,
setTreeFolders,
onLoading,
isLoading
isLoading,
dragging,
setDragItem,
isMy,
myId,
isCommon,
commonId,
currentId,
isAdmin,
isShare
} = this.props;
const { showNewFilesPanel, expandedKeys } = this.state;
const fakeFiles = this.props.selection;
//console.log("Article Body render", this.props, this.state.expandedKeys);
return (
<TreeFolders
selectedKeys={selectedKeys}
fakeNewDocuments={fakeNewDocuments}
onSelect={this.onSelect}
data={data}
filter={filter}
setFilter={setFilter}
setTreeFolders={setTreeFolders}
expandedKeys={this.state.expandedKeys}
onLoading={onLoading}
isLoading={isLoading}
/>
<>
{showNewFilesPanel && (
<NewFilesPanel
visible={showNewFilesPanel}
onClose={this.onShowNewFilesPanel}
files={fakeFiles}
/>
)}
<TreeFolders
selectedKeys={selectedKeys}
onSelect={this.onSelect}
data={data}
filter={filter}
setFilter={setFilter}
setTreeFolders={setTreeFolders}
expandedKeys={expandedKeys}
onLoading={onLoading}
isLoading={isLoading}
dragging={dragging}
setDragItem={setDragItem}
isMy={isMy}
myId={myId}
isCommon={isCommon}
commonId={commonId}
currentId={currentId}
isAdmin={isAdmin}
isShare={isShare}
onBadgeClick={this.onShowNewFilesPanel}
/>
</>
);
}
}
function mapStateToProps(state) {
const { treeFolders, selectedFolder, filter } = state.files;
const { treeFolders, selectedFolder, filter, selection } = state.files;
const currentFolderId = selectedFolder.id.toString();
const fakeNewDocuments = 8;
const myFolderIndex = 0;
const shareFolderIndex = 1;
const commonFolderIndex = 2;
const myId = treeFolders[myFolderIndex].id;
const shareId = treeFolders[shareFolderIndex].id;
const commonId = treeFolders[commonFolderIndex].id;
const isMy = selectedFolder &&
selectedFolder.pathParts &&
selectedFolder.pathParts[0] === myId;
const isShare = selectedFolder &&
selectedFolder.pathParts &&
selectedFolder.pathParts[0] === shareId;
const isCommon = selectedFolder &&
selectedFolder.pathParts &&
selectedFolder.pathParts[0] === commonId;
return {
data: treeFolders,
selectedKeys: selectedFolder ? [currentFolderId] : [""],
fakeNewDocuments,
filter
filter,
isMy,
isCommon,
isShare,
myId,
commonId,
currentId: selectedFolder.id,
isAdmin: state.auth.user.isAdmin,
selection
};
}
export default connect(mapStateToProps, { setFilter, setTreeFolders })(
export default connect(mapStateToProps, { setFilter, setTreeFolders, setDragItem })(
ArticleBodyContent
);

View File

@ -173,6 +173,13 @@ class FilesRowContent extends React.PureComponent {
}
};
onShowVersionHistory = (e) => {
const {settings, history} = this.props;
const fileId = e.currentTarget.dataset.id;
history.push(`${settings.homepage}/${fileId}/history`);
}
render() {
const { t, item, fileAction, isLoading, isTrashFolder } = this.props;
const { itemTitle, editingId/*, loading*/ } = this.state;
@ -317,8 +324,9 @@ class FilesRowContent extends React.PureComponent {
fontWeight={800}
label={`Ver.${versionGroup}`}
maxWidth="50px"
onClick={() => { }}
onClick={this.onShowVersionHistory}
padding="0 5px"
data-id={id}
/>
}
{fileStatus === 2 &&
@ -331,8 +339,9 @@ class FilesRowContent extends React.PureComponent {
fontWeight={800}
label={`New`}
maxWidth="50px"
onClick={() => { }}
onClick={this.onShowVersionHistory}
padding="0 5px"
data-id={id}
/>
}
</div>
@ -384,13 +393,15 @@ class FilesRowContent extends React.PureComponent {
function mapStateToProps(state) {
const { filter, fileAction, selectedFolder, treeFolders } = state.files;
const { settings } = state.auth;
const indexOfTrash = 3;
return {
filter,
fileAction,
parentFolder: selectedFolder.id,
isTrashFolder: treeFolders[indexOfTrash].id === selectedFolder.id
isTrashFolder: treeFolders[indexOfTrash].id === selectedFolder.id,
settings
}
}

View File

@ -4,7 +4,9 @@ import { connect } from "react-redux";
import { ReactSVG } from 'react-svg'
import { withTranslation } from "react-i18next";
import isEqual from "lodash/isEqual";
import copy from "copy-to-clipboard";
import styled from "styled-components";
import queryString from 'query-string';
import {
IconButton,
Row,
@ -20,14 +22,15 @@ import {
deleteFolder,
deselectFile,
fetchFiles,
fetchFolder,
selectFile,
setAction,
setTreeFolders,
moveToFolder,
getProgress
copyToFolder,
getProgress,
setDragItem
} from '../../../../../store/files/actions';
import { isFileSelected, getFileIcon, getFolderIcon, getFolderType, loopTreeFolders, isImage, isSound, isVideo } from '../../../../../store/files/selectors';
import { isFileSelected, getFileIcon, getFolderIcon, getFolderType, loopTreeFolders, isImage, isSound, isVideo } from '../../../../../store/files/selectors';
import store from "../../../../../store/store";
import { SharingPanel } from "../../../../panels";
//import { getFilterByLocation } from "../../../../../helpers/converters";
@ -40,6 +43,9 @@ const linkStyles = { isHovered: true, type: "action", fontSize: "14px", classNam
const backgroundDragColor = "#EFEFB2";
const backgroundDragEnterColor = "#F8F7BF";
const extsMediaPreviewed = [".aac", ".flac", ".m4a", ".mp3", ".oga", ".ogg", ".wav", ".f4v", ".m4v", ".mov", ".mp4", ".ogv", ".webm", ".avi", ".mpg", ".mpeg", ".wmv"];
const extsImagePreviewed = [".bmp", ".gif", ".jpeg", ".jpg", ".png", ".ico", ".tif", ".tiff", ".webp"];
const CustomTooltip = styled.div`
position: fixed;
display: none;
@ -90,8 +96,13 @@ class SectionBodyContent extends React.Component {
// .catch(error => toastr.error(error));
// }
// }
let previewId = queryString.parse(this.props.location.search).preview;
if(previewId){
this.showMediaFile(previewId);
}
window.addEventListener("mouseup", this.onMouseUp);
document.addEventListener("mousedown", this.onMouseDown);
document.addEventListener("dragover", this.onDragOver);
document.addEventListener("dragleave", this.onDragLeaveDoc);
@ -99,7 +110,6 @@ class SectionBodyContent extends React.Component {
componentWillUnmount() {
window.removeEventListener("mouseup", this.onMouseUp);
document.removeEventListener("mousedown", this.onMouseDown);
document.removeEventListener("dragover", this.onDragOver);
document.removeEventListener("dragleave", this.onDragLeaveDoc);
@ -117,13 +127,18 @@ class SectionBodyContent extends React.Component {
} */
shouldComponentUpdate(nextProps, nextState) {
if(this.state.showSharingPanel !== nextState.showSharingPanel) {
if (this.state.showSharingPanel !== nextState.showSharingPanel) {
return true;
}
if(!isEqual(this.props, nextProps) || !isEqual(this.state.mediaViewerVisible, nextState.mediaViewerVisible)) {
if (this.props.dragItem !== nextProps.dragItem) {
return false;
}
if (!isEqual(this.props, nextProps) || !isEqual(this.state.mediaViewerVisible, nextState.mediaViewerVisible)) {
return true;
}
return false;
}
@ -187,7 +202,7 @@ class SectionBodyContent extends React.Component {
const { filter, treeFolders, setTreeFolders, currentFolderType, getProgress, setProgressValue, finishFilesOperations } = this.props;
getProgress().then(res => {
const deleteProgress = res.find(x => x.id === id);
if(deleteProgress && deleteProgress.progress !== 100) {
if (deleteProgress && deleteProgress.progress !== 100) {
setProgressValue(deleteProgress.progress);
setTimeout(() => this.loopDeleteProgress(id, folderId, isFolder), 1000);
} else {
@ -233,7 +248,19 @@ class SectionBodyContent extends React.Component {
}
onClickLinkForPortal = item => {
return fetchFolder(item.folderId, store.dispatch);
const {settings} = this.props;
const isFile = !!item.fileExst;
const { t } = this.props;
copy(isFile
?
this.isMediaOrImage(item.fileExst)
? `${window.location.origin + settings.homepage}/filter?folder=${item.folderId}&preview=${item.id}`
: item.webUrl
:
`${window.location.origin + settings.homepage}/filter?folder=${item.id}`);
toastr.success(t("LinkCopySuccess"));
}
onClickDownload = item => {
@ -266,7 +293,7 @@ class SectionBodyContent extends React.Component {
key: "link-for-portal-users",
label: "Link for portal users",
onClick: this.onClickLinkForPortal.bind(this, item),
disabled: true
disabled: false
},
{
key: "sep",
@ -584,34 +611,34 @@ class SectionBodyContent extends React.Component {
/>
)
}
onMediaViewerClose = () =>{
onMediaViewerClose = () => {
this.setState({
mediaViewerVisible: false
});
}
onMediaFileClick = (id) => {
this.setState({
mediaViewerVisible: true,
currentMediaFileId: id
});
this.setState({
mediaViewerVisible: true,
currentMediaFileId: id
});
}
onDownloadMediaFile = (id) => {
if(this.props.files.length > 0){
if (this.props.files.length > 0) {
let viewUrlFile = this.props.files.find(file => file.id === id).viewUrl;
return window.open(viewUrlFile, "_blank");
}
}
onDeleteMediaFile = (id) => {
if(this.props.files.length > 0){
if (this.props.files.length > 0) {
let file = this.props.files.find(file => file.id === id);
if(file)
if (file)
this.onDeleteFile(file.id, file.folderId)
}
}
onDragEnter = (item, e) => {
const isCurrentItem = this.props.selection.find(x => x.id === item.id && x.fileExst === item.fileExst);
if(!item.fileExst && (!isCurrentItem || e.dataTransfer.items.length)) {
if (!item.fileExst && (!isCurrentItem || e.dataTransfer.items.length)) {
e.currentTarget.style.background = backgroundDragColor;
}
}
@ -619,16 +646,16 @@ class SectionBodyContent extends React.Component {
onDragLeave = (item, e) => {
const { selection, dragging, setDragging } = this.props;
const isCurrentItem = selection.find(x => x.id === item.id && x.fileExst === item.fileExst);
if(!e.dataTransfer.items.length) {
if (!e.dataTransfer.items.length) {
e.currentTarget.style.background = "none";
} else if(!item.fileExst && !isCurrentItem) {
} else if (!item.fileExst && !isCurrentItem) {
e.currentTarget.style.background = backgroundDragEnterColor;
}
if(dragging && !e.relatedTarget) { setDragging(false); }
if (dragging && !e.relatedTarget) { setDragging(false); }
}
onDrop = (item, e) => {
if(e.dataTransfer.items.length > 0 && !item.fileExst) {
if (e.dataTransfer.items.length > 0 && !item.fileExst) {
const { setDragging, onDropZoneUpload } = this.props;
e.currentTarget.style.background = backgroundDragEnterColor;
setDragging(false);
@ -639,7 +666,7 @@ class SectionBodyContent extends React.Component {
onDragOver = e => {
e.preventDefault();
const { dragging, setDragging } = this.props;
if(e.dataTransfer.items.length > 0 && !dragging) {
if (e.dataTransfer.items.length > 0 && !dragging) {
setDragging(true);
}
}
@ -647,60 +674,61 @@ class SectionBodyContent extends React.Component {
onDragLeaveDoc = e => {
e.preventDefault();
const { dragging, setDragging } = this.props;
if(dragging && !e.relatedTarget) {
if (dragging && !e.relatedTarget) {
setDragging(false);
}
}
onMouseDown = e => {
const mouseButton = e.which ? e.which !== 1 : e.button ? e.button !== 0 : false;
if(mouseButton || e.target.tagName !== "DIV") { return; }
const label = e.target.getAttribute('label');
if (mouseButton || e.target.tagName !== "DIV" || label) { return; }
document.addEventListener("mousemove", this.onMouseMove);
this.setTooltipPosition(e);
const { selection, setDragging } = this.props;
const elem = e.target.closest('.draggable');
if(!elem) {
if (!elem) {
return;
}
const value = elem.getAttribute('value');
if(!value) {
if (!value) {
return;
}
const splitValue = value.split("_");
let item = null;
if(splitValue[0] === "folder") {
if (splitValue[0] === "folder") {
item = selection.find(x => x.id === Number(splitValue[1]) && !x.fileExst);
} else {
item = selection.find(x => x.id === Number(splitValue[1]) && x.fileExst);
}
if(item) {
if (item) {
setDragging(true);
}
}
onMouseUp = e => {
const { selection, dragging, setDragging } = this.props;
const { selection, dragging, setDragging, dragItem, setDragItem } = this.props;
const mouseButton = e.which ? e.which !== 1 : e.button ? e.button !== 0 : false;
if(mouseButton || !this.tooltipRef.current || !dragging) { return; }
if (mouseButton || !this.tooltipRef.current || !dragging) { return; }
document.removeEventListener("mousemove", this.onMouseMove);
this.tooltipRef.current.style.display = "none";
const elem = e.target.closest('.dropable');
if(elem && selection.length && dragging) {
if (elem && selection.length && dragging) {
const value = elem.getAttribute('value');
if(!value) {
if (!value) {
setDragging(false);
return;
}
const splitValue = value.split("_");
let item = null;
if(splitValue[0] === "folder") {
if (splitValue[0] === "folder") {
item = selection.find(x => x.id === Number(splitValue[1]) && !x.fileExst);
} else {
return;
}
if(item) {
if (item) {
setDragging(false);
return;
} else {
@ -710,18 +738,23 @@ class SectionBodyContent extends React.Component {
}
} else {
setDragging(false);
if (dragItem) {
this.onMoveTo(dragItem);
setDragItem(null);
return;
}
return;
}
}
onMouseMove = e => {
if(this.props.dragging) {
if (this.props.dragging) {
const tooltip = this.tooltipRef.current;
tooltip.style.display = "block";
this.setTooltipPosition(e);
const wrapperElement = document.elementFromPoint(e.clientX, e.clientY);
if(!wrapperElement) { return; }
if (!wrapperElement) { return; }
const droppable = wrapperElement.closest('.dropable');
if (this.currentDroppable !== droppable) {
@ -740,27 +773,70 @@ class SectionBodyContent extends React.Component {
setTooltipPosition = e => {
const tooltip = this.tooltipRef.current;
const margin = 8;
tooltip.style.left = e.pageX + margin + "px";
tooltip.style.top = e.pageY + margin + "px";
if (tooltip) {
const margin = 8;
tooltip.style.left = e.pageX + margin + "px";
tooltip.style.top = e.pageY + margin + "px";
}
};
showMediaFile = (id) => {
this.onMediaFileClick(+id);
}
isMediaOrImage = (fileExst) => {
if(extsMediaPreviewed.includes(fileExst) || extsImagePreviewed.includes(fileExst)) {
return true
}
return false
}
onMoveTo = (destFolderId) => {
const { selection, moveToFolder, finishFilesOperations, startFilesOperations, t, loopFilesOperations } = this.props;
const { selection, startFilesOperations, t, isShare, isCommon, isAdmin } = this.props;
const folderIds = [];
const fileIds = [];
const conflictResolveType = 0; //Skip = 0, Overwrite = 1, Duplicate = 2
const deleteAfter = true;
for(let item of selection) {
if(item.fileExst) {
startFilesOperations(t("MoveToOperation"));
for (let item of selection) {
if (item.fileExst) {
fileIds.push(item.id)
} else {
folderIds.push(item.id)
}
}
startFilesOperations(t("MoveToOperation"));
if (isAdmin) {
if (isShare) {
this.copyTo(destFolderId, folderIds, fileIds, conflictResolveType, deleteAfter);
} else {
this.moveTo(destFolderId, folderIds, fileIds, conflictResolveType, deleteAfter);
}
} else {
if (isShare || isCommon) {
this.copyTo(destFolderId, folderIds, fileIds, conflictResolveType, deleteAfter);
} else {
this.moveTo(destFolderId, folderIds, fileIds, conflictResolveType, deleteAfter);
}
}
}
copyTo = (destFolderId, folderIds, fileIds, conflictResolveType, deleteAfter) => {
const { copyToFolder, loopFilesOperations, finishFilesOperations } = this.props;
copyToFolder(destFolderId, folderIds, fileIds, conflictResolveType, deleteAfter)
.then(res => {
const id = res[0] && res[0].id ? res[0].id : null;
loopFilesOperations(id, destFolderId, true);
})
.catch(err => finishFilesOperations(err))
}
moveTo = (destFolderId, folderIds, fileIds, conflictResolveType, deleteAfter) => {
const { moveToFolder, loopFilesOperations, finishFilesOperations } = this.props;
moveToFolder(destFolderId, folderIds, fileIds, conflictResolveType, deleteAfter)
.then(res => {
const id = res[0] && res[0].id ? res[0].id : null;
@ -769,9 +845,45 @@ class SectionBodyContent extends React.Component {
.catch(err => finishFilesOperations(err))
}
getTooltipLabel = () => {
const { t, selection, isAdmin, isShare, isCommon } = this.props;
const elementTitle = selection.length && selection[0].title;
const elementCount = selection.length;
if (selection.length) {
if (selection.length > 1) {
if (isAdmin) {
if (isShare) {
return t("TooltipElementsCopyMessage", { element: elementCount });
} else {
return t("TooltipElementsMoveMessage", { element: elementCount });
}
} else {
if (isShare || isCommon) {
return t("TooltipElementsCopyMessage", { element: elementCount });
} else {
return t("TooltipElementsMoveMessage", { element: elementCount });
}
}
} else {
if (isAdmin) {
if (isShare) {
return t("TooltipElementCopyMessage", { element: elementTitle });
} else {
return t("TooltipElementMoveMessage", { element: elementTitle });
}
} else {
if (isShare || isCommon) {
return t("TooltipElementCopyMessage", { element: elementTitle });
} else {
return t("TooltipElementMoveMessage", { element: elementTitle });
}
}
}
}
}
render() {
const {
t,
files,
folders,
viewer,
@ -790,12 +902,8 @@ class SectionBodyContent extends React.Component {
const { editingId, showSharingPanel, currentItem } = this.state;
let items = [...folders, ...files];
const tooltipLabel = !selection.length
? ""
: selection.length > 1
? t("TooltipElementsMessage", { element: selection.length })
: t("TooltipElementMessage", { element: selection[0].title });
const tooltipLabel = this.getTooltipLabel();
const SimpleFilesRow = styled(Row)`
${(props) =>
@ -816,11 +924,11 @@ class SectionBodyContent extends React.Component {
});
}
var playlist = [];
let id = 0;
files.forEach(function(file, i, files) {
if(isImage(file.fileExst) || isSound(file.fileExst) || isVideo(file.fileExst)){
files.forEach(function (file, i, files) {
if (isImage(file.fileExst) || isSound(file.fileExst) || isVideo(file.fileExst)) {
playlist.push(
{
id: id,
@ -843,7 +951,7 @@ class SectionBodyContent extends React.Component {
this.renderEmptyFilterContainer()
) : (
<>
<CustomTooltip ref={this.tooltipRef}>{tooltipLabel}</CustomTooltip>
<CustomTooltip ref={this.tooltipRef}>{tooltipLabel}</CustomTooltip>
<RowContainer draggable useReactWindow={false}>
{items.map((item) => {
const isEdit =
@ -859,13 +967,13 @@ class SectionBodyContent extends React.Component {
const checked = isFileSelected(selection, item.id, item.parentId);
const checkedProps = /* isAdmin(viewer) */ isEdit ? {} : { checked };
const element = this.getItemIcon(item, isEdit);
const selectedItem = selection.find(x => x.id === item.id && x.fileExst === item.fileExst);
const isFolder = selectedItem ? false : item.fileExst ? false : true;
const draggable = selectedItem && currentFolderType !== "Trash";
let value = item.fileExst ? `file_${item.id}` : `folder_${item.id}`;
value += draggable ? "_draggable" : "";
const classNameProp = isFolder ? {className: " dropable"} : {};
const classNameProp = isFolder && item.access < 2 ? { className: " dropable" } : {};
return (
<DragAndDrop
@ -873,7 +981,8 @@ class SectionBodyContent extends React.Component {
onDrop={this.onDrop.bind(this, item)}
onDragEnter={this.onDragEnter.bind(this, item)}
onDragLeave={this.onDragLeave.bind(this, item)}
dragging={dragging && isFolder}
onMouseDown={this.onMouseDown}
dragging={dragging && isFolder && item.access < 2}
key={`dnd-key_${item.id}`}
{...contextOptionsProps}
value={value}
@ -904,7 +1013,7 @@ class SectionBodyContent extends React.Component {
</RowContainer>
{playlist.length > 0 && this.state.mediaViewerVisible &&
<MediaViewer
currentFileId = {this.state.currentMediaFileId}
currentFileId={this.state.currentMediaFileId}
allowConvert={true} //TODO
canDelete={(fileId) => { return true }} //TODO
canDownload={(fileId) => { return true }} //TODO
@ -914,8 +1023,8 @@ class SectionBodyContent extends React.Component {
onDownload={this.onDownloadMediaFile}
onClose={this.onMediaViewerClose}
onEmptyPlaylistError={this.onMediaViewerClose}
extsMediaPreviewed={[".aac", ".flac", ".m4a", ".mp3", ".oga", ".ogg", ".wav", ".f4v", ".m4v", ".mov", ".mp4", ".ogv", ".webm", ".avi", ".mpg", ".mpeg", ".wmv"]} //TODO
extsImagePreviewed={[".bmp", ".gif", ".jpeg", ".jpg", ".png", ".ico", ".tif", ".tiff", ".webp"]} //TODO
extsMediaPreviewed={extsMediaPreviewed} //TODO
extsImagePreviewed={extsImagePreviewed} //TODO
/>
}
{showSharingPanel && (
@ -936,12 +1045,17 @@ SectionBodyContent.defaultProps = {
};
const mapStateToProps = state => {
const { selectedFolder, treeFolders, selection } = state.files;
const { id, title, foldersCount, filesCount } = selectedFolder;
const { selectedFolder, treeFolders, selection, dragItem } = state.files;
const { id, title, foldersCount, filesCount, pathParts } = selectedFolder;
const currentFolderType = getFolderType(id, treeFolders);
const myFolderIndex = 0;
const shareFolderIndex = 1;
const commonFolderIndex = 2;
const currentFolderCount = filesCount + foldersCount;
const myDocumentsId = treeFolders[myFolderIndex].id;
const shareFolderId = treeFolders[shareFolderIndex].id;
const commonFolderId = treeFolders[commonFolderIndex].id;
return {
fileAction: state.files.fileAction,
@ -954,12 +1068,16 @@ const mapStateToProps = state => {
selection,
settings: state.auth.settings,
viewer: state.auth.user,
treeFolders: state.files.treeFolders,
treeFolders,
currentFolderType,
title,
myDocumentsId: treeFolders[myFolderIndex].id,
myDocumentsId,
currentFolderCount,
selectedFolderId: id
selectedFolderId: id,
dragItem,
isShare: pathParts[0] === shareFolderId,
isCommon: pathParts[0] === commonFolderId,
isAdmin: state.auth.user.isAdmin
};
};
@ -975,6 +1093,8 @@ export default connect(
setAction,
setTreeFolders,
moveToFolder,
getProgress
copyToFolder,
getProgress,
setDragItem
}
)(withRouter(withTranslation()(SectionBodyContent)));

View File

@ -1,4 +1,5 @@
import React from "react";
import copy from "copy-to-clipboard";
import styled, { css } from "styled-components";
import { withRouter } from "react-router";
import { constants, Headline, store, api } from "asc-web-common";
@ -161,8 +162,14 @@ class SectionHeaderContent extends React.Component {
];
};
createLinkForPortalUsers = () =>
toastr.info("createLinkForPortalUsers click");
createLinkForPortalUsers = () => {
const {currentFolderId} = this.props;
const { t } = this.props;
copy(`${window.location.origin}/products/files/filter?folder=${currentFolderId}`);
toastr.success(t("LinkCopySuccess"));
}
onMoveAction = () => this.setState({ showMoveToPanel: !this.state.showMoveToPanel });
@ -231,7 +238,7 @@ class SectionHeaderContent extends React.Component {
key: "link-portal-users",
label: t("LinkForPortalUsers"),
onClick: this.createLinkForPortalUsers,
disabled: true
disabled: false
},
{ key: "separator-2", isSeparator: true },
{

View File

@ -596,6 +596,7 @@ class PureHome extends React.Component {
<ArticleBodyContent
onLoading={this.onLoading}
isLoading={isLoading}
dragging={dragging}
/>
}
sectionHeaderContent={

View File

@ -6,6 +6,7 @@
"UploadToFolder": "Upload to folder",
"SharingSettings": "Sharing settings",
"LinkForPortalUsers": "Link for portal users",
"LinkCopySuccess": "Link has been copied to the clipboard",
"MoveTo": "Move to",
"Copy": "Copy",
"Download": "Download",
@ -78,6 +79,8 @@
"ErrorUploadMessage": "You cannot upload a folder or an empty file",
"UploadingLabel": "Uploading files: {{file}} of {{totalFiles}}",
"MoveToOperation": "Moving",
"TooltipElementMessage": "Move {{element}}",
"TooltipElementsMessage": "Move {{element}} elements"
"TooltipElementMoveMessage": "Move {{element}}",
"TooltipElementsMoveMessage": "Move {{element}} elements",
"TooltipElementCopyMessage": "Copy {{element}}",
"TooltipElementsCopyMessage": "Copy {{element}} elements"
}

View File

@ -6,6 +6,7 @@
"UploadToFolder": "Загрузить в папку",
"SharingSettings": "Настройки доступа",
"LinkForPortalUsers": "Ссылка для пользователей портала",
"LinkCopySuccess": "Ссылка скопирована в буфер обмена",
"MoveTo": "Переместить",
"Copy": "Копировать",
"Download": "Скачать",
@ -78,6 +79,8 @@
"ErrorUploadMessage": "Нельзя загрузить папку или пустой файл",
"UploadingLabel": "Загружено файлов: {{file}} из {{totalFiles}}",
"MoveToOperation": "Перемещение",
"TooltipElementMessage": "Переместить {{element}}",
"TooltipElementsMessage": "Переместить {{element}} элемента(ов)"
"TooltipElementMoveMessage": "Переместить {{element}}",
"TooltipElementsMoveMessage": "Переместить {{element}} элемента(ов)",
"TooltipElementCopyMessage": "Скопировать {{element}}",
"TooltipElementsCopyMessage": "Скопировать {{element}} элемента(ов)"
}

View File

@ -0,0 +1,33 @@
import React from "react";
import { withRouter } from "react-router";
import { withTranslation } from "react-i18next";
import { Row, RowContainer, Link, Text, Box } from "asc-web-components";
//import i18n from '../../i18n';
const VersionBadge = (props) => (
<svg width="55" height="18" viewBox="0 0 55 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M0 1C0 0.447716 0.447715 0 1 0L53.9994 0C54.6787 0 55.1603 0.662806 54.9505 1.3089L52.5529 8.6911C52.4877 8.89187 52.4877 9.10813 52.5529 9.3089L54.9505 16.6911C55.1603 17.3372 54.6787 18 53.9994 18H0.999999C0.447714 18 0 17.5523 0 17V1Z" fill="#A3A9AE"/>
</svg>);
class SectionBodyContent extends React.PureComponent {
renderRow = info => {
const title = `${new Date(info.created).toLocaleString(this.props.culture)} ${info.createdBy.displayName}`;
return (
<Row key={info.id}>
<Box marginProp="0 8px" displayProp="flex">
<VersionBadge />
<Text style={{position: "absolute", left: "16px"}} color="#FFFFFF" isBold fontSize="12px">Ver.{info.version}</Text>
</Box>
<Link fontWeight={600} fontSize="14px" title={title}>{title}</Link>
</Row>
);
};
render() {
const { versions } = this.props;
console.log("VersionHistory SectionBodyContent render()", versions);
return <RowContainer useReactWindow={false}>{versions.map(this.renderRow)}</RowContainer>;
}
}
export default withRouter(withTranslation()(SectionBodyContent));

View File

@ -0,0 +1,99 @@
import React from "react";
import styled, { css } from "styled-components";
import { withRouter } from "react-router";
import { Headline } from 'asc-web-common';
import { IconButton } from "asc-web-components";
import { connect } from "react-redux";
import { withTranslation } from "react-i18next";
const StyledContainer = styled.div`
display: flex;
align-items: center;
.arrow-button {
margin-right: 16px;
@media (max-width: 1024px) {
padding: 8px 0 8px 8px;
margin-left: -8px;
}
}
@media (min-width: 1024px) {
${props => props.isHeaderVisible && css`width: calc(100% + 76px);`}
}
.group-button-menu-container {
margin: 0 -16px;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
@media (max-width: 1024px) {
& > div:first-child {
position: absolute;
top: 56px;
z-index: 180;
}
}
@media (min-width: 1024px) {
margin: 0 -24px;
}
}
.header-container {
position: relative;
display: flex;
align-items: center;
max-width: calc(100vw - 32px);
.action-button {
margin-left: 16px;
@media (max-width: 1024px) {
margin-left: auto;
& > div:first-child {
padding: 8px 16px 8px 16px;
margin-right: -16px;
}
}
}
}
`;
const SectionHeaderContent = props => {
const { title } = props;
const onClickBack = () => {
const { history, settings } = props;
history.push(settings.homepage);
};
return (
<StyledContainer isHeaderVisible={true}>
<IconButton
iconName="ArrowPathIcon"
size="17"
color="#A3A9AE"
hoverColor="#657077"
isFill={true}
onClick={onClickBack}
className="arrow-button"
/>
<Headline className='headline-header' type="content" truncate={true}>{title}</Headline>
</StyledContainer>
);
};
const mapStateToProps = state => {
return {
settings: state.auth.settings,
};
};
export default connect(
mapStateToProps
)(withTranslation()(withRouter(SectionHeaderContent)));

View File

@ -0,0 +1,2 @@
export { default as SectionHeaderContent } from './Header';
export { default as SectionBodyContent } from './Body';

View File

@ -0,0 +1,54 @@
import i18n from "i18next";
import Backend from "i18next-xhr-backend";
import config from "../../../../package.json";
import { constants } from 'asc-web-common';
const { LANGUAGE } = constants;
const newInstance = i18n.createInstance();
if (process.env.NODE_ENV === "production") {
newInstance
.use(Backend)
.init({
lng: localStorage.getItem(LANGUAGE) || 'en',
fallbackLng: "en",
interpolation: {
escapeValue: false, // not needed for react as it escapes by default
},
react: {
useSuspense: true
},
backend: {
loadPath: `${config.homepage}/locales/Home/{{lng}}/{{ns}}.json`
}
});
} else if (process.env.NODE_ENV === "development") {
const resources = {
en: {
translation: require("./locales/en/translation.json")
},
ru: {
translation: require("./locales/ru/translation.json")
}
};
newInstance.init({
resources: resources,
lng: localStorage.getItem(LANGUAGE) || 'en',
fallbackLng: "en",
debug: true,
interpolation: {
escapeValue: false, // not needed for react as it escapes by default
},
react: {
useSuspense: false
}
});
}
export default newInstance;

View File

@ -0,0 +1,141 @@
import React from "react";
import { connect } from "react-redux";
import PropTypes from "prop-types";
import { withRouter } from "react-router";
import { RequestLoader, Loader } from "asc-web-components";
import { PageLayout, utils, api } from "asc-web-common";
import { withTranslation, I18nextProvider } from "react-i18next";
import i18n from "./i18n";
import {
ArticleHeaderContent,
ArticleBodyContent,
ArticleMainButtonContent
} from "../../Article";
import { SectionHeaderContent, SectionBodyContent } from "./Section";
const { changeLanguage } = utils;
class PureVersionHistory extends React.Component {
constructor(props) {
super(props);
const { files, match } = props;
const { fileId } = match.params;
const found = files.filter(f => f.id == fileId);
this.state = {
isLoading: false,
fileId: props.match.params.fileId,
file: found && found[0],
versions: null
};
}
componentDidMount() {
const { match, t } = this.props;
const { fileId } = match.params;
//document.title = `${t("GroupAction")} ${t("People")}`;
if (fileId) {
//fetchGroup(fileId);
api.files.getFileVersionInfo(fileId)
.then((versions) => {
console.log("getFileVersionInfo result", versions);
this.setState({
versions
})
})
console.log("Loading file versions", fileId);
}
}
onLoading = status => {
this.setState({ isLoading: status });
};
render() {
const { file, versions } = this.state;
const { t, settings } = this.props;
console.log(`FileId ${file.id}`);
return (
<>
<RequestLoader
visible={this.state.isLoading}
zIndex={256}
loaderSize="16px"
loaderColor={"#999"}
label={`${t("LoadingProcessing")} ${t("LoadingDescription")}`}
fontSize="12px"
fontColor={"#999"}
/>
{versions ? (
<PageLayout
withBodyScroll={true}
withBodyAutoFocus={true}
articleHeaderContent={<ArticleHeaderContent />}
articleMainButtonContent={
<ArticleMainButtonContent
onLoading={this.onLoading}
startUpload={this.startUpload}
setProgressVisible={this.setProgressVisible}
setProgressValue={this.setProgressValue}
setProgressLabel={this.setProgressLabel}
/>
}
articleBodyContent={
<ArticleBodyContent
onLoading={this.onLoading}
isLoading={this.state.isLoading}
/>
}
sectionHeaderContent={<SectionHeaderContent title={file.title} />}
sectionBodyContent={
<SectionBodyContent onLoading={this.onLoading} versions={versions} culture={settings.culture} />
}
/>
) : (
<PageLayout
articleHeaderContent={<ArticleHeaderContent />}
articleMainButtonContent={<ArticleMainButtonContent />}
articleBodyContent={<ArticleBodyContent />}
sectionBodyContent={
<Loader className="pageLoader" type="rombs" size="40px" />
}
/>
)}
</>
);
}
}
const VersionHistoryContainer = withTranslation()(PureVersionHistory);
const VersionHistory = props => {
changeLanguage(i18n);
return (
<I18nextProvider i18n={i18n}>
<VersionHistoryContainer {...props} />
</I18nextProvider>
);
};
VersionHistory.propTypes = {
files: PropTypes.array,
history: PropTypes.object.isRequired,
isLoaded: PropTypes.bool
};
function mapStateToProps(state) {
return {
files: state.files.files,
settings: state.auth.settings,
isLoaded: state.auth.isLoaded
};
}
export default connect(mapStateToProps)(withRouter(VersionHistory));

View File

@ -0,0 +1,54 @@
import i18n from "i18next";
import Backend from "i18next-xhr-backend";
import config from "../../../../package.json";
import { constants } from 'asc-web-common';
const { LANGUAGE } = constants;
const newInstance = i18n.createInstance();
if (process.env.NODE_ENV === "production") {
newInstance
.use(Backend)
.init({
lng: localStorage.getItem(LANGUAGE) || 'en',
fallbackLng: "en",
interpolation: {
escapeValue: false, // not needed for react as it escapes by default
},
react: {
useSuspense: true
},
backend: {
loadPath: `${config.homepage}/locales/NewFilesPanel/{{lng}}/{{ns}}.json`
}
});
} else if (process.env.NODE_ENV === "development") {
const resources = {
en: {
translation: require("./locales/en/translation.json")
},
ru: {
translation: require("./locales/ru/translation.json")
},
};
newInstance.init({
resources: resources,
lng: localStorage.getItem(LANGUAGE) || 'en',
fallbackLng: "en",
debug: true,
interpolation: {
escapeValue: false, // not needed for react as it escapes by default
},
react: {
useSuspense: true
}
});
}
export default newInstance;

View File

@ -0,0 +1,137 @@
import React from "react";
import PropTypes from "prop-types";
import { connect } from "react-redux";
import { withRouter } from "react-router";
import {
Backdrop,
Heading,
Aside,
Row,
RowContent,
RowContainer,
Text,
Link,
Button
} from "asc-web-components";
import { withTranslation } from "react-i18next";
import { utils as commonUtils } from "asc-web-common";
import i18n from "./i18n";
import { ReactSVG } from 'react-svg'
import {
StyledAsidePanel,
StyledContent,
StyledHeaderContent,
StyledBody,
StyledFooter
} from "../StyledPanels";
import { getFileIcon, getFolderIcon } from '../../../store/files/selectors';
const { changeLanguage } = commonUtils;
class NewFilesPanelComponent extends React.Component {
constructor(props) {
super(props);
changeLanguage(i18n);
}
getItemIcon = (item, isEdit) => {
const extension = item.fileExst;
const icon = extension
? getFileIcon(extension, 24)
: getFolderIcon(item.providerKey, 24);
return <ReactSVG
beforeInjection={svg => {
svg.setAttribute('style', 'margin-top: 4px');
isEdit && svg.setAttribute('style', 'margin-left: 24px');
}}
src={icon}
loading={this.svgLoader}
/>;
};
render() {
//console.log("NewFiles panel render");
const { t, visible, onClose, files } = this.props;
const zIndex = 310;
return (
<StyledAsidePanel visible={visible}>
<Backdrop onClick={onClose} visible={visible} zIndex={zIndex} />
<Aside className="header_aside-panel" visible={visible}>
<StyledContent>
<StyledHeaderContent className="files-operations-panel">
<Heading size="medium" truncate>
{t("NewFiles")}
</Heading>
</StyledHeaderContent>
<StyledBody className="files-operations-body">
<RowContainer useReactWindow manualHeight="83vh">
{files.map((file) => {
const element = this.getItemIcon(file);
return (
<Row key={file.id} element={element}>
<RowContent>
<Link
containerWidth="100%"
type="page"
fontWeight="bold"
color="#333"
isTextOverflow
truncate
title={file.title}
fontSize="14px"
>
{file.title}
</Link>
<></>
<Text fontSize="12px" containerWidth="auto">
{file.checked && t("ConvertInto")}
</Text>
</RowContent>
</Row>
);
})}
</RowContainer>
</StyledBody>
<StyledFooter>
<Button
label={t("MarkAsRead")}
size="big"
primary
//onClick={this.onSaveClick}
/>
<Button
className="sharing_panel-button"
label={t("CloseButton")}
size="big"
onClick={onClose}
/>
</StyledFooter>
</StyledContent>
</Aside>
</StyledAsidePanel>
);
}
}
NewFilesPanelComponent.propTypes = {
onClose: PropTypes.func,
visible: PropTypes.bool,
};
const NewFilesPanelContainerTranslated = withTranslation()(
NewFilesPanelComponent
);
const NewFilesPanel = (props) => (
<NewFilesPanelContainerTranslated i18n={i18n} {...props} />
);
const mapStateToProps = (state) => {
return {};
};
export default connect(mapStateToProps, {})(withRouter(NewFilesPanel));

View File

@ -0,0 +1,5 @@
{
"NewFiles": "New files",
"MarkAsRead": "Mark all as read",
"CloseButton": "Close"
}

View File

@ -0,0 +1,5 @@
{
"NewFiles": "Новые файлы",
"MarkAsRead": "Пометить всё прочтённым",
"CloseButton": "Закрыть"
}

View File

@ -601,7 +601,7 @@ class SharingPanelComponent extends React.Component {
/>*/}
</div>
</StyledSharingHeaderContent>
<StyledSharingBody>
<StyledSharingBody stype="mediumBlack" style={{height: '83vh'}}>
{shareDataItems.map((item, index) => (
<SharingRow
key={index}

View File

@ -1,4 +1,5 @@
import styled, { css } from "styled-components";
import { Scrollbar } from "asc-web-components";
const PanelStyles = css`
.panel_combo-box {
@ -181,10 +182,21 @@ const StyledSharingHeaderContent = styled.div`
}
`;
const StyledSharingBody = styled.div`
const StyledSharingBody = styled(Scrollbar)`
position: relative;
padding: 16px 0;
.nav-thumb-vertical {
opacity: 0;
transition: opacity 200ms ease;
}
:hover {
.nav-thumb-vertical {
opacity: 1;
}
}
.sharing_panel-text {
line-height: 24px;
}

View File

@ -3,5 +3,6 @@ import AddUsersPanel from "./AddUsersPanel/AddUsersPanel";
import AddGroupsPanel from "./AddGroupsPanel/AddGroupsPanel";
import EmbeddingPanel from "./EmbeddingPanel/EmbeddingPanel";
import OperationsPanel from "./OperationsPanel";
import NewFilesPanel from "./NewFilesPanel";
export { SharingPanel, AddUsersPanel, AddGroupsPanel, EmbeddingPanel, OperationsPanel }
export { SharingPanel, AddUsersPanel, AddGroupsPanel, EmbeddingPanel, OperationsPanel, NewFilesPanel }

View File

@ -7,4 +7,5 @@ export const SORT_BY = "sortby";
export const SORT_ORDER = "sortorder";
export const PAGE = "page";
export const PAGE_COUNT = "pagecount";
export const FOLDER = "folder";
export const FOLDER = "folder";
export const PREVIEW = "preview"

View File

@ -1,5 +1,6 @@
import { api, history } from "asc-web-common";
import axios from "axios";
import queryString from 'query-string';
import {
AUTHOR_TYPE,
FILTER_TYPE,
@ -9,7 +10,8 @@ import {
SEARCH,
SORT_BY,
SORT_ORDER,
FOLDER
FOLDER,
PREVIEW
} from "../../helpers/constants";
import config from "../../../package.json";
import { getTreeFolders } from "./selectors";
@ -29,6 +31,7 @@ export const SET_FILTER = "SET_FILTER";
export const SELECT_FILE = "SELECT_FILE";
export const DESELECT_FILE = "DESELECT_FILE";
export const SET_ACTION = "SET_ACTION";
export const SET_DRAG_ITEM = "SET_DRAG_ITEM";
export function setFile(file) {
return {
@ -93,6 +96,13 @@ export function setTreeFolders(treeFolders) {
};
}
export function setDragItem(dragItem) {
return {
type: SET_DRAG_ITEM,
dragItem
}
}
export function setFilesFilter(filter) {
setFilterUrl(filter);
return {
@ -124,6 +134,7 @@ export function deselectFile(file) {
export function setFilterUrl(filter) {
const defaultFilter = FilesFilter.getDefault();
const params = [];
const URLParams = queryString.parse(window.location.href)
if (filter.filterType) {
params.push(`${FILTER_TYPE}=${filter.filterType}`);
@ -147,6 +158,10 @@ export function setFilterUrl(filter) {
params.push(`${PAGE_COUNT}=${filter.pageCount}`);
}
if(URLParams.preview) {
params.push(`${PREVIEW}=${URLParams.preview}`);
}
params.push(`${PAGE}=${filter.page + 1}`);
params.push(`${SORT_BY}=${filter.sortBy}`);
params.push(`${SORT_ORDER}=${filter.sortOrder}`);

View File

@ -11,7 +11,8 @@ import {
SET_SELECTED,
SET_SELECTION,
SELECT_FILE,
DESELECT_FILE
DESELECT_FILE,
SET_DRAG_ITEM
} from "./actions";
import { api } from "asc-web-common";
import { isFileSelected, skipFile, getFilesBySelected } from "./selectors";
@ -27,7 +28,8 @@ const initialState = {
treeFolders: [],
selected: "none",
selectedFolder: null,
selection: []
selection: [],
dragItem: null
};
const filesReducer = (state = initialState, action) => {
@ -93,6 +95,10 @@ const filesReducer = (state = initialState, action) => {
return Object.assign({}, state, {
fileAction: action.fileAction
})
case SET_DRAG_ITEM:
return Object.assign({}, state, {
dragItem: action.dragItem
});
default:
return state;
}

View File

@ -28,6 +28,7 @@ using System;
using System.Collections.Generic;
using System.IO;
using ASC.Files.Core.Security;
using ASC.Web.Files.Services.DocumentService;
namespace ASC.Files.Core
@ -303,6 +304,10 @@ namespace ASC.Files.Core
bool ContainChanges(T fileId, int fileVersion);
IEnumerable<(File<int>, SmallShareRecord)> GetFeeds(int tenant, DateTime from, DateTime to);
IEnumerable<int> GetTenantsWithFeeds(DateTime fromTime);
#endregion
}
}

View File

@ -28,6 +28,8 @@ using System;
using System.Collections.Generic;
using System.Threading;
using ASC.Files.Core.Security;
namespace ASC.Files.Core
{
public interface IFolderDao<T>
@ -292,6 +294,11 @@ namespace ASC.Files.Core
/// <returns></returns>
Dictionary<string, string> GetBunchObjectIDs(List<T> folderIDs);
IEnumerable<(Folder<T>, SmallShareRecord)> GetFeeds(int tenant, DateTime from, DateTime to);
IEnumerable<T> GetTenantsWithFeeds(DateTime fromTime);
#endregion
}
}

View File

@ -38,6 +38,7 @@ using ASC.Core.Common.Settings;
using ASC.Core.Tenants;
using ASC.ElasticSearch;
using ASC.Files.Core.EF;
using ASC.Files.Core.Security;
using ASC.Files.Core.Thirdparty;
using ASC.Files.Resources;
using ASC.Files.Thirdparty.ProviderDao;
@ -1204,6 +1205,51 @@ namespace ASC.Files.Core.Data
.Any();
}
public IEnumerable<(File<int>, SmallShareRecord)> GetFeeds(int tenant, DateTime from, DateTime to)
{
var q1 = FilesDbContext.Files
.Where(r => r.TenantId == tenant)
.Where(r => r.CurrentVersion)
.Where(r => r.ModifiedOn >= from && r.ModifiedOn <= to);
var q2 = FromQuery(q1)
.Select(r => new DbFileQueryWithSecurity() { DbFileQuery = r, Security = null });
var q3 = FilesDbContext.Files
.Where(r => r.TenantId == tenant)
.Where(r => r.CurrentVersion);
var q4 = FromQuery(q3)
.Join(FilesDbContext.Security.DefaultIfEmpty(), r => r.file.Id.ToString(), s => s.EntryId, (f, s) => new DbFileQueryWithSecurity { DbFileQuery = f, Security = s })
.Where(r => r.Security.TenantId == tenant)
.Where(r => r.Security.EntryType == FileEntryType.File)
.Where(r => r.Security.Security == Security.FileShare.Restrict)
.Where(r => r.Security.TimeStamp >= from && r.Security.TimeStamp <= to);
return q2.Select(ToFileWithShare).ToList().Union(q4.Select(ToFileWithShare).ToList());
}
public IEnumerable<int> GetTenantsWithFeeds(DateTime fromTime)
{
var q1 = FilesDbContext.Files
.Where(r => r.ModifiedOn > fromTime)
.Select(r => r.TenantId)
.GroupBy(r => r)
.Where(r => r.Count() > 0)
.Select(r => r.Key)
.ToList();
var q2 = FilesDbContext.Security
.Where(r => r.TimeStamp > fromTime)
.Select(r => r.TenantId)
.GroupBy(r => r)
.Where(r => r.Count() > 0)
.Select(r => r.Key)
.ToList();
return q1.Union(q2);
}
#endregion
private Func<Selector<DbFile>, Selector<DbFile>> GetFuncForSearch(object parentId, OrderBy orderBy, FilterType filterType, bool subjectGroup, Guid subjectID, string searchText, bool searchInContent, bool withSubfolders = false)
@ -1341,6 +1387,21 @@ namespace ASC.Files.Core.Data
return file;
}
public (File<int>, SmallShareRecord) ToFileWithShare(DbFileQueryWithSecurity r)
{
var file = ToFile(r.DbFileQuery);
var record = r.Security != null
? new SmallShareRecord
{
ShareOn = r.Security.TimeStamp,
ShareBy = r.Security.Owner,
ShareTo = r.Security.Subject
}
: null;
return (file, record);
}
internal protected DbFile InitDocument(DbFile dbFile)
{
if (!FactoryIndexer.CanSearchByContent())
@ -1379,6 +1440,12 @@ namespace ASC.Files.Core.Data
public bool shared { get; set; }
}
public class DbFileQueryWithSecurity
{
public DbFileQuery DbFileQuery { get; set; }
public DbFilesSecurity Security { get; set; }
}
public static class FileDaoExtention
{
public static DIHelper AddFileDaoService(this DIHelper services)

View File

@ -37,6 +37,7 @@ using ASC.Core.Common.Settings;
using ASC.Core.Tenants;
using ASC.ElasticSearch;
using ASC.Files.Core.EF;
using ASC.Files.Core.Security;
using ASC.Files.Core.Thirdparty;
using ASC.Files.Resources;
using ASC.Files.Thirdparty.ProviderDao;
@ -1081,6 +1082,21 @@ namespace ASC.Files.Core.Data
return result;
}
public (Folder<int>, SmallShareRecord) ToFolderWithShare(DbFolderQueryWithSecurity r)
{
var file = ToFolder(r.DbFolderQuery);
var record = r.Security != null
? new SmallShareRecord
{
ShareOn = r.Security.TimeStamp,
ShareBy = r.Security.Owner,
ShareTo = r.Security.Subject
}
: null;
return (file, record);
}
public string GetBunchObjectID(int folderID)
{
return Query(FilesDbContext.BunchObjects)
@ -1096,6 +1112,51 @@ namespace ASC.Files.Core.Data
.ToDictionary(r => r.LeftNode, r => r.RightNode);
}
public IEnumerable<(Folder<int>, SmallShareRecord)> GetFeeds(int tenant, DateTime from, DateTime to)
{
var q1 = FilesDbContext.Folders
.Where(r => r.TenantId == tenant)
.Where(r => r.FolderType == FolderType.DEFAULT)
.Where(r => r.CreateOn >= from && r.ModifiedOn <= to);
var q2 = FromQuery(q1)
.Select(r => new DbFolderQueryWithSecurity() { DbFolderQuery = r, Security = null });
var q3 = FilesDbContext.Folders
.Where(r => r.TenantId == tenant)
.Where(r => r.FolderType == FolderType.DEFAULT);
var q4 = FromQuery(q3)
.Join(FilesDbContext.Security.DefaultIfEmpty(), r => r.folder.Id.ToString(), s => s.EntryId, (f, s) => new DbFolderQueryWithSecurity { DbFolderQuery = f, Security = s })
.Where(r => r.Security.TenantId == tenant)
.Where(r => r.Security.EntryType == FileEntryType.Folder)
.Where(r => r.Security.Security == FileShare.Restrict)
.Where(r => r.Security.TimeStamp >= from && r.Security.TimeStamp <= to);
return q2.Select(ToFolderWithShare).ToList().Union(q4.Select(ToFolderWithShare).ToList());
}
public IEnumerable<int> GetTenantsWithFeeds(DateTime fromTime)
{
var q1 = FilesDbContext.Files
.Where(r => r.ModifiedOn > fromTime)
.Select(r => r.TenantId)
.GroupBy(r => r)
.Where(r => r.Count() > 0)
.Select(r => r.Key)
.ToList();
var q2 = FilesDbContext.Security
.Where(r => r.TimeStamp > fromTime)
.Select(r => r.TenantId)
.GroupBy(r => r)
.Where(r => r.Count() > 0)
.Select(r => r.Key)
.ToList();
return q1.Union(q2);
}
private string GetProjectTitle(object folderID)
{
return "";
@ -1159,6 +1220,12 @@ namespace ASC.Files.Core.Data
public bool shared { get; set; }
}
public class DbFolderQueryWithSecurity
{
public DbFolderQuery DbFolderQuery { get; set; }
public DbFilesSecurity Security { get; set; }
}
public static class FolderDaoExtention
{
public static DIHelper AddFolderDaoService(this DIHelper services)

View File

@ -36,6 +36,7 @@ using ASC.Core.Common.EF;
using ASC.Core.Tenants;
using ASC.Files.Core;
using ASC.Files.Core.EF;
using ASC.Files.Core.Security;
using ASC.Files.Core.Thirdparty;
using ASC.Files.Resources;
using ASC.Web.Core.Files;
@ -538,7 +539,7 @@ namespace ASC.Files.Thirdparty.Box
if (uploadSession.BytesUploaded == uploadSession.BytesTotal)
{
using (var fs = new FileStream(uploadSession.GetItemOrDefault<string>("TempPath"),
FileMode.Open, FileAccess.Read, FileShare.None, 4096, FileOptions.DeleteOnClose))
FileMode.Open, FileAccess.Read, System.IO.FileShare.None, 4096, FileOptions.DeleteOnClose))
{
uploadSession.File = SaveFile(uploadSession.File, fs);
}
@ -606,6 +607,16 @@ namespace ASC.Files.Thirdparty.Box
throw new NotImplementedException();
}
public IEnumerable<(File<int>, SmallShareRecord)> GetFeeds(int tenant, DateTime from, DateTime to)
{
throw new NotImplementedException();
}
public IEnumerable<int> GetTenantsWithFeeds(DateTime fromTime)
{
throw new NotImplementedException();
}
#endregion
}

View File

@ -36,6 +36,7 @@ using ASC.Core.Common.EF;
using ASC.Core.Tenants;
using ASC.Files.Core;
using ASC.Files.Core.EF;
using ASC.Files.Core.Security;
using ASC.Files.Core.Thirdparty;
using ASC.Web.Core.Files;
using ASC.Web.Studio.Core;
@ -510,6 +511,16 @@ namespace ASC.Files.Thirdparty.Box
return null;
}
public IEnumerable<(Folder<string>, SmallShareRecord)> GetFeeds(int tenant, DateTime from, DateTime to)
{
throw new NotImplementedException();
}
public IEnumerable<string> GetTenantsWithFeeds(DateTime fromTime)
{
throw new NotImplementedException();
}
#endregion
}

View File

@ -36,6 +36,7 @@ using ASC.Core.Common.EF;
using ASC.Core.Tenants;
using ASC.Files.Core;
using ASC.Files.Core.EF;
using ASC.Files.Core.Security;
using ASC.Files.Core.Thirdparty;
using ASC.Files.Resources;
using ASC.Web.Core.Files;
@ -590,7 +591,7 @@ namespace ASC.Files.Thirdparty.Dropbox
}
using (var fs = new FileStream(uploadSession.GetItemOrDefault<string>("TempPath"),
FileMode.Open, FileAccess.Read, FileShare.None, 4096, FileOptions.DeleteOnClose))
FileMode.Open, FileAccess.Read, System.IO.FileShare.None, 4096, FileOptions.DeleteOnClose))
{
return SaveFile(uploadSession.File, fs);
}
@ -653,6 +654,16 @@ namespace ASC.Files.Thirdparty.Dropbox
throw new NotImplementedException();
}
public IEnumerable<(File<int>, SmallShareRecord)> GetFeeds(int tenant, DateTime from, DateTime to)
{
throw new NotImplementedException();
}
public IEnumerable<int> GetTenantsWithFeeds(DateTime fromTime)
{
throw new NotImplementedException();
}
#endregion
}

View File

@ -36,6 +36,7 @@ using ASC.Core.Common.EF;
using ASC.Core.Tenants;
using ASC.Files.Core;
using ASC.Files.Core.EF;
using ASC.Files.Core.Security;
using ASC.Files.Core.Thirdparty;
using ASC.Web.Core.Files;
using ASC.Web.Studio.Core;
@ -507,6 +508,16 @@ namespace ASC.Files.Thirdparty.Dropbox
return null;
}
public IEnumerable<(Folder<string>, SmallShareRecord)> GetFeeds(int tenant, DateTime from, DateTime to)
{
throw new NotImplementedException();
}
public IEnumerable<string> GetTenantsWithFeeds(DateTime fromTime)
{
throw new NotImplementedException();
}
#endregion
}

View File

@ -36,6 +36,7 @@ using ASC.Core.Common.EF;
using ASC.Core.Tenants;
using ASC.Files.Core;
using ASC.Files.Core.EF;
using ASC.Files.Core.Security;
using ASC.Files.Core.Thirdparty;
using ASC.Files.Resources;
using ASC.Web.Core.Files;
@ -582,7 +583,7 @@ namespace ASC.Files.Thirdparty.GoogleDrive
return ToFile(GetDriveEntry(googleDriveSession.FileId));
}
using (var fs = new FileStream(uploadSession.GetItemOrDefault<string>("TempPath"), FileMode.Open, FileAccess.Read, FileShare.None, 4096, FileOptions.DeleteOnClose))
using (var fs = new FileStream(uploadSession.GetItemOrDefault<string>("TempPath"), FileMode.Open, FileAccess.Read, System.IO.FileShare.None, 4096, FileOptions.DeleteOnClose))
{
return SaveFile(uploadSession.File, fs);
}
@ -654,6 +655,16 @@ namespace ASC.Files.Thirdparty.GoogleDrive
return null;
}
public IEnumerable<(File<int>, SmallShareRecord)> GetFeeds(int tenant, DateTime from, DateTime to)
{
throw new NotImplementedException();
}
public IEnumerable<int> GetTenantsWithFeeds(DateTime fromTime)
{
throw new NotImplementedException();
}
#endregion
}

View File

@ -36,6 +36,7 @@ using ASC.Core.Common.EF;
using ASC.Core.Tenants;
using ASC.Files.Core;
using ASC.Files.Core.EF;
using ASC.Files.Core.Security;
using ASC.Files.Core.Thirdparty;
using ASC.Web.Core.Files;
using ASC.Web.Studio.Core;
@ -499,6 +500,16 @@ namespace ASC.Files.Thirdparty.GoogleDrive
return null;
}
public IEnumerable<(Folder<string>, SmallShareRecord)> GetFeeds(int tenant, DateTime from, DateTime to)
{
throw new NotImplementedException();
}
public IEnumerable<string> GetTenantsWithFeeds(DateTime fromTime)
{
throw new NotImplementedException();
}
#endregion
}

View File

@ -36,6 +36,7 @@ using ASC.Core.Common.EF;
using ASC.Core.Tenants;
using ASC.Files.Core;
using ASC.Files.Core.EF;
using ASC.Files.Core.Security;
using ASC.Files.Core.Thirdparty;
using ASC.Files.Resources;
using ASC.Web.Core.Files;
@ -581,7 +582,7 @@ namespace ASC.Files.Thirdparty.OneDrive
return ToFile(GetOneDriveItem(oneDriveSession.FileId));
}
using (var fs = new FileStream(uploadSession.GetItemOrDefault<string>("TempPath"), FileMode.Open, FileAccess.Read, FileShare.None, 4096, FileOptions.DeleteOnClose))
using (var fs = new FileStream(uploadSession.GetItemOrDefault<string>("TempPath"), FileMode.Open, FileAccess.Read, System.IO.FileShare.None, 4096, FileOptions.DeleteOnClose))
{
return SaveFile(uploadSession.File, fs);
}
@ -655,6 +656,16 @@ namespace ASC.Files.Thirdparty.OneDrive
throw new NotImplementedException();
}
public IEnumerable<(File<int>, SmallShareRecord)> GetFeeds(int tenant, DateTime from, DateTime to)
{
throw new NotImplementedException();
}
public IEnumerable<int> GetTenantsWithFeeds(DateTime fromTime)
{
throw new NotImplementedException();
}
#endregion
}

View File

@ -36,6 +36,7 @@ using ASC.Core.Common.EF;
using ASC.Core.Tenants;
using ASC.Files.Core;
using ASC.Files.Core.EF;
using ASC.Files.Core.Security;
using ASC.Files.Core.Thirdparty;
using ASC.Web.Core.Files;
using ASC.Web.Studio.Core;
@ -510,6 +511,16 @@ namespace ASC.Files.Thirdparty.OneDrive
return null;
}
public IEnumerable<(Folder<string>, SmallShareRecord)> GetFeeds(int tenant, DateTime from, DateTime to)
{
throw new NotImplementedException();
}
public IEnumerable<string> GetTenantsWithFeeds(DateTime fromTime)
{
throw new NotImplementedException();
}
#endregion
}

View File

@ -33,6 +33,7 @@ using ASC.Common;
using ASC.Core;
using ASC.Files.Core;
using ASC.Files.Core.Data;
using ASC.Files.Core.Security;
using ASC.Files.Core.Thirdparty;
using ASC.Web.Files.Services.DocumentService;
@ -509,6 +510,16 @@ namespace ASC.Files.Thirdparty.ProviderDao
throw new NotImplementedException();
}
public IEnumerable<(File<int>, SmallShareRecord)> GetFeeds(int tenant, DateTime from, DateTime to)
{
throw new NotImplementedException();
}
public IEnumerable<int> GetTenantsWithFeeds(DateTime fromTime)
{
throw new NotImplementedException();
}
#endregion
}

View File

@ -33,6 +33,7 @@ using ASC.Common;
using ASC.Core;
using ASC.Files.Core;
using ASC.Files.Core.Data;
using ASC.Files.Core.Security;
using ASC.Files.Core.Thirdparty;
namespace ASC.Files.Thirdparty.ProviderDao
@ -411,6 +412,16 @@ filterType, subjectGroup, subjectID, searchText, searchSubfolders, checkShare);
throw new NotImplementedException();
}
public IEnumerable<(Folder<string>, SmallShareRecord)> GetFeeds(int tenant, DateTime from, DateTime to)
{
throw new NotImplementedException();
}
public IEnumerable<string> GetTenantsWithFeeds(DateTime fromTime)
{
throw new NotImplementedException();
}
#endregion
}

View File

@ -36,6 +36,7 @@ using ASC.Core.Common.EF;
using ASC.Core.Tenants;
using ASC.Files.Core;
using ASC.Files.Core.EF;
using ASC.Files.Core.Security;
using ASC.Files.Core.Thirdparty;
using ASC.Files.Resources;
using ASC.Web.Core.Files;
@ -484,6 +485,16 @@ namespace ASC.Files.Thirdparty.SharePoint
throw new NotImplementedException();
}
public IEnumerable<(File<int>, SmallShareRecord)> GetFeeds(int tenant, DateTime from, DateTime to)
{
throw new NotImplementedException();
}
public IEnumerable<int> GetTenantsWithFeeds(DateTime fromTime)
{
throw new NotImplementedException();
}
#endregion
}

View File

@ -36,6 +36,7 @@ using ASC.Core.Common.EF;
using ASC.Core.Tenants;
using ASC.Files.Core;
using ASC.Files.Core.EF;
using ASC.Files.Core.Security;
using ASC.Files.Core.Thirdparty;
using ASC.Web.Core.Files;
using ASC.Web.Studio.Core;
@ -466,6 +467,16 @@ namespace ASC.Files.Thirdparty.SharePoint
return null;
}
public IEnumerable<(Folder<string>, SmallShareRecord)> GetFeeds(int tenant, DateTime from, DateTime to)
{
throw new NotImplementedException();
}
public IEnumerable<string> GetTenantsWithFeeds(DateTime fromTime)
{
throw new NotImplementedException();
}
#endregion
}

View File

@ -41,6 +41,7 @@ using ASC.Core.Common.EF;
using ASC.Core.Tenants;
using ASC.Files.Core;
using ASC.Files.Core.EF;
using ASC.Files.Core.Security;
using ASC.Files.Core.Thirdparty;
using ASC.Files.Resources;
using ASC.Web.Core.Files;
@ -261,7 +262,7 @@ namespace ASC.Files.Thirdparty.Sharpbox
{
if (!fileStream.CanSeek)
{
var tempBuffer = new FileStream(Path.GetTempFileName(), FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.Read, 8096, FileOptions.DeleteOnClose);
var tempBuffer = new FileStream(Path.GetTempFileName(), FileMode.OpenOrCreate, FileAccess.ReadWrite, System.IO.FileShare.Read, 8096, FileOptions.DeleteOnClose);
fileStream.CopyTo(tempBuffer);
tempBuffer.Flush();
@ -625,7 +626,7 @@ namespace ASC.Files.Thirdparty.Sharpbox
return ToFile(GetFileById(sharpboxSession.FileId));
}
using (var fs = new FileStream(uploadSession.GetItemOrDefault<string>("TempPath"), FileMode.Open, FileAccess.Read, FileShare.None, 4096, FileOptions.DeleteOnClose))
using (var fs = new FileStream(uploadSession.GetItemOrDefault<string>("TempPath"), FileMode.Open, FileAccess.Read, System.IO.FileShare.None, 4096, FileOptions.DeleteOnClose))
{
return SaveFile(uploadSession.File, fs);
}
@ -710,6 +711,16 @@ namespace ASC.Files.Thirdparty.Sharpbox
throw new NotImplementedException();
}
public IEnumerable<(File<int>, SmallShareRecord)> GetFeeds(int tenant, DateTime from, DateTime to)
{
throw new NotImplementedException();
}
public IEnumerable<int> GetTenantsWithFeeds(DateTime fromTime)
{
throw new NotImplementedException();
}
#endregion
}

View File

@ -41,6 +41,7 @@ using ASC.Core.Common.EF;
using ASC.Core.Tenants;
using ASC.Files.Core;
using ASC.Files.Core.EF;
using ASC.Files.Core.Security;
using ASC.Files.Core.Thirdparty;
using ASC.Files.Resources;
using ASC.Web.Core.Files;
@ -530,6 +531,16 @@ namespace ASC.Files.Thirdparty.Sharpbox
return null;
}
public IEnumerable<(Folder<string>, SmallShareRecord)> GetFeeds(int tenant, DateTime from, DateTime to)
{
throw new NotImplementedException();
}
public IEnumerable<string> GetTenantsWithFeeds(DateTime fromTime)
{
throw new NotImplementedException();
}
#endregion
}

View File

@ -14,6 +14,7 @@
<ProjectReference Include="..\..\..\common\ASC.Common\ASC.Common.csproj" />
<ProjectReference Include="..\..\..\common\ASC.Core.Common\ASC.Core.Common.csproj" />
<ProjectReference Include="..\..\..\common\services\ASC.ElasticSearch\ASC.ElasticSearch.csproj" />
<ProjectReference Include="..\..\..\common\services\ASC.Feed.Aggregator\ASC.Feed.Aggregator.csproj" />
<ProjectReference Include="..\Server\ASC.Files.csproj" />
</ItemGroup>

View File

@ -0,0 +1,194 @@

using System;
using System.Collections.Generic;
using System.Linq;
using ASC.Core;
using ASC.Feed;
using ASC.Feed.Data;
using ASC.Files.Core;
using ASC.Files.Core.Security;
using ASC.Web.Core;
using ASC.Web.Core.Files;
using FeedModule = ASC.Feed.Aggregator.Modules.FeedModule;
namespace ASC.Files.Service.Core
{
public class FilesModule : FeedModule
{
private const string fileItem = "file";
private const string sharedFileItem = "sharedFile";
public override string Name => Constants.FilesModule;
public override string Product => "documents";
public override Guid ProductID => WebItemManager.DocumentsProductID;
protected override string DbId => "files";
public IFileDao<int> FileDao { get; }
public IFolderDao<int> FolderDao { get; }
public UserManager UserManager { get; }
public FilesLinkUtility FilesLinkUtility { get; }
public FileSecurity FileSecurity { get; }
public FilesModule(
TenantManager tenantManager,
UserManager userManager,
WebItemSecurity webItemSecurity,
FilesLinkUtility filesLinkUtility,
FileSecurity fileSecurity,
IDaoFactory daoFactory)
: base(tenantManager, webItemSecurity)
{
FileDao = daoFactory.GetFileDao<int>();
FolderDao = daoFactory.GetFolderDao<int>();
UserManager = userManager;
FilesLinkUtility = filesLinkUtility;
FileSecurity = fileSecurity;
}
public override bool VisibleFor(Feed.Aggregator.Feed feed, object data, Guid userId)
{
if (!WebItemSecurity.IsAvailableForUser(ProductID, userId)) return false;
var tuple = ((File<int>, SmallShareRecord))data;
var file = tuple.Item1;
var shareRecord = tuple.Item2;
bool targetCond;
if (feed.Target != null)
{
if (shareRecord != null && shareRecord.ShareBy == userId) return false;
var owner = (Guid)feed.Target;
var groupUsers = UserManager.GetUsersByGroup(owner).Select(x => x.ID).ToList();
if (!groupUsers.Any())
{
groupUsers.Add(owner);
}
targetCond = groupUsers.Contains(userId);
}
else
{
targetCond = true;
}
return targetCond && FileSecurity.CanRead(file, userId);
}
public override void VisibleFor(List<Tuple<FeedRow, object>> feed, Guid userId)
{
if (!WebItemSecurity.IsAvailableForUser(ProductID, userId)) return;
var feed1 = feed.Select(r =>
{
var tuple = ((File<int>, SmallShareRecord))r.Item2;
return new Tuple<FeedRow, File<int>, SmallShareRecord>(r.Item1, tuple.Item1, tuple.Item2);
})
.ToList();
var files = feed1.Where(r => r.Item1.Feed.Target == null).Select(r => r.Item2).ToList();
foreach (var f in feed1.Where(r => r.Item1.Feed.Target != null && !(r.Item3 != null && r.Item3.ShareBy == userId)))
{
var file = f.Item2;
if (IsTarget(f.Item1.Feed.Target, userId) && !files.Any(r => r.UniqID.Equals(file.UniqID)))
{
files.Add(file);
}
}
var canRead = FileSecurity.CanRead(files, userId).Where(r => r.Item2).ToList();
foreach (var f in feed1)
{
if (IsTarget(f.Item1.Feed.Target, userId) && canRead.Any(r => r.Item1.ID.Equals(f.Item2.ID)))
{
f.Item1.Users.Add(userId);
}
}
}
public override IEnumerable<Tuple<Feed.Aggregator.Feed, object>> GetFeeds(FeedFilter filter)
{
var files = FileDao.GetFeeds(filter.Tenant, filter.Time.From, filter.Time.To)
.Where(f => f.Item1.RootFolderType != FolderType.TRASH && f.Item1.RootFolderType != FolderType.BUNCH)
.ToList();
var folderIDs = files.Select(r => r.Item1.FolderID).ToArray();
var folders = FolderDao.GetFolders(folderIDs, checkShare: false);
return files.Select(f => new Tuple<Feed.Aggregator.Feed, object>(ToFeed(f, folders.FirstOrDefault(r => r.ID.Equals(f.Item1.FolderID))), f));
}
public override IEnumerable<int> GetTenantsWithFeeds(DateTime fromTime)
{
return FileDao.GetTenantsWithFeeds(fromTime);
}
private Feed.Aggregator.Feed ToFeed((File<int>, SmallShareRecord) tuple, Folder<int> rootFolder)
{
var file = tuple.Item1;
var shareRecord = tuple.Item2;
if (shareRecord != null)
{
var feed = new Feed.Aggregator.Feed(shareRecord.ShareBy, shareRecord.ShareOn, true)
{
Item = sharedFileItem,
ItemId = string.Format("{0}_{1}", file.ID, shareRecord.ShareTo),
ItemUrl = FilesLinkUtility.GetFileRedirectPreviewUrl(file.ID, true),
Product = Product,
Module = Name,
Title = file.Title,
ExtraLocation = rootFolder.FolderType == FolderType.DEFAULT ? rootFolder.Title : string.Empty,
ExtraLocationUrl = rootFolder.FolderType == FolderType.DEFAULT ? FilesLinkUtility.GetFileRedirectPreviewUrl(file.FolderID, false) : string.Empty,
AdditionalInfo = file.ContentLengthString,
Keywords = string.Format("{0}", file.Title),
HasPreview = false,
CanComment = false,
Target = shareRecord.ShareTo,
GroupId = GetGroupId(sharedFileItem, shareRecord.ShareBy, file.FolderID.ToString())
};
return feed;
}
var updated = file.Version != 1;
return new Feed.Aggregator.Feed(file.ModifiedBy, file.ModifiedOn, true)
{
Item = fileItem,
ItemId = string.Format("{0}_{1}", file.ID, file.Version > 1 ? 1 : 0),
ItemUrl = FilesLinkUtility.GetFileRedirectPreviewUrl(file.ID, true),
Product = Product,
Module = Name,
Action = updated ? FeedAction.Updated : FeedAction.Created,
Title = file.Title,
ExtraLocation = rootFolder.FolderType == FolderType.DEFAULT ? rootFolder.Title : string.Empty,
ExtraLocationUrl = rootFolder.FolderType == FolderType.DEFAULT ? FilesLinkUtility.GetFileRedirectPreviewUrl(file.FolderID, false) : string.Empty,
AdditionalInfo = file.ContentLengthString,
Keywords = string.Format("{0}", file.Title),
HasPreview = false,
CanComment = false,
Target = null,
GroupId = GetGroupId(fileItem, file.ModifiedBy, file.FolderID.ToString(), updated ? 1 : 0)
};
}
private bool IsTarget(object target, Guid userId)
{
if (target == null) return true;
var owner = (Guid)target;
var groupUsers = UserManager.GetUsersByGroup(owner).Select(x => x.ID).ToList();
if (!groupUsers.Any())
{
groupUsers.Add(owner);
}
return groupUsers.Contains(userId);
}
}
}

View File

@ -0,0 +1,143 @@
using System;
using System.Collections.Generic;
using System.Linq;
using ASC.Core;
using ASC.Feed;
using ASC.Files.Core;
using ASC.Files.Core.Security;
using ASC.Web.Core;
using ASC.Web.Core.Files;
using FeedModule = ASC.Feed.Aggregator.Modules.FeedModule;
namespace ASC.Files.Service.Core
{
public class FoldersModule : FeedModule
{
private const string folderItem = "folder";
private const string sharedFolderItem = "sharedFolder";
protected override string DbId => Constants.FilesDbId;
public override string Name => Constants.FoldersModule;
public override string Product => "documents";
public override Guid ProductID => WebItemManager.DocumentsProductID;
public UserManager UserManager { get; }
public FilesLinkUtility FilesLinkUtility { get; }
public FileSecurity FileSecurity { get; }
public IFileDao<int> FileDao { get; }
public IFolderDao<int> FolderDao { get; }
public FoldersModule(
TenantManager tenantManager,
UserManager userManager,
WebItemSecurity webItemSecurity,
FilesLinkUtility filesLinkUtility,
FileSecurity fileSecurity,
IDaoFactory daoFactory)
: base(tenantManager, webItemSecurity)
{
UserManager = userManager;
FilesLinkUtility = filesLinkUtility;
FileSecurity = fileSecurity;
FileDao = daoFactory.GetFileDao<int>();
FolderDao = daoFactory.GetFolderDao<int>();
}
public override bool VisibleFor(Feed.Aggregator.Feed feed, object data, Guid userId)
{
if (!WebItemSecurity.IsAvailableForUser(ProductID, userId)) return false;
var tuple = (Tuple<Folder<int>, SmallShareRecord>)data;
var folder = tuple.Item1;
var shareRecord = tuple.Item2;
bool targetCond;
if (feed.Target != null)
{
if (shareRecord != null && shareRecord.ShareBy == userId) return false;
var owner = (Guid)feed.Target;
var groupUsers = UserManager.GetUsersByGroup(owner).Select(x => x.ID).ToList();
if (!groupUsers.Any())
{
groupUsers.Add(owner);
}
targetCond = groupUsers.Contains(userId);
}
else
{
targetCond = true;
}
return targetCond && FileSecurity.CanRead(folder, userId);
}
public override IEnumerable<int> GetTenantsWithFeeds(DateTime fromTime)
{
return FolderDao.GetTenantsWithFeeds(fromTime);
}
public override IEnumerable<Tuple<Feed.Aggregator.Feed, object>> GetFeeds(FeedFilter filter)
{
var folders = FolderDao.GetFeeds(filter.Tenant, filter.Time.From, filter.Time.To)
.Where(f => f.Item1.RootFolderType != FolderType.TRASH && f.Item1.RootFolderType != FolderType.BUNCH)
.ToList();
var parentFolderIDs = folders.Select(r => r.Item1.ParentFolderID).ToArray();
var parentFolders = FolderDao.GetFolders(parentFolderIDs, checkShare: false);
return folders.Select(f => new Tuple<Feed.Aggregator.Feed, object>(ToFeed(f, parentFolders.FirstOrDefault(r => r.ID.Equals(f.Item1.ParentFolderID))), f));
}
private Feed.Aggregator.Feed ToFeed((Folder<int>, SmallShareRecord) tuple, Folder<int> rootFolder)
{
var folder = tuple.Item1;
var shareRecord = tuple.Item2;
if (shareRecord != null)
{
var feed = new Feed.Aggregator.Feed(shareRecord.ShareBy, shareRecord.ShareOn, true)
{
Item = sharedFolderItem,
ItemId = string.Format("{0}_{1}", folder.ID, shareRecord.ShareTo),
ItemUrl = FilesLinkUtility.GetFileRedirectPreviewUrl(folder.ID, false),
Product = Product,
Module = Name,
Title = folder.Title,
ExtraLocation = rootFolder.FolderType == FolderType.DEFAULT ? rootFolder.Title : string.Empty,
ExtraLocationUrl = rootFolder.FolderType == FolderType.DEFAULT ? FilesLinkUtility.GetFileRedirectPreviewUrl(folder.ParentFolderID, false) : string.Empty,
Keywords = string.Format("{0}", folder.Title),
HasPreview = false,
CanComment = false,
Target = shareRecord.ShareTo,
GroupId = GetGroupId(sharedFolderItem, shareRecord.ShareBy, folder.ParentFolderID.ToString())
};
return feed;
}
return new Feed.Aggregator.Feed(folder.CreateBy, folder.CreateOn)
{
Item = folderItem,
ItemId = folder.ID.ToString(),
ItemUrl = FilesLinkUtility.GetFileRedirectPreviewUrl(folder.ID, false),
Product = Product,
Module = Name,
Title = folder.Title,
ExtraLocation = rootFolder.FolderType == FolderType.DEFAULT ? rootFolder.Title : string.Empty,
ExtraLocationUrl = rootFolder.FolderType == FolderType.DEFAULT ? FilesLinkUtility.GetFileRedirectPreviewUrl(folder.ParentFolderID, false) : string.Empty,
Keywords = string.Format("{0}", folder.Title),
HasPreview = false,
CanComment = false,
Target = null,
GroupId = GetGroupId(folderItem, folder.CreateBy, folder.ParentFolderID.ToString())
};
}
}
}

View File

@ -7,6 +7,7 @@ using ASC.Common.Caching;
using ASC.Common.DependencyInjection;
using ASC.Common.Logging;
using ASC.ElasticSearch;
using ASC.Feed.Aggregator;
using ASC.Web.Files.Core.Search;
using ASC.Web.Files.Utils;
@ -39,6 +40,7 @@ namespace ASC.Files.Service
)
.AddJsonFile("appsettings.json")
.AddJsonFile($"appsettings.{env}.json", true)
.AddJsonFile($"appsettings.services.json", true)
.AddJsonFile("storage.json")
.AddJsonFile("notify.json")
.AddJsonFile("kafka.json")
@ -58,8 +60,12 @@ namespace ASC.Files.Service
.AddFactoryIndexerFileService()
.AddFactoryIndexerFolderService();
var a = typeof(FactoryIndexer<ISearchItem>).ToString();
services.AddAutofac(hostContext.Configuration, hostContext.HostingEnvironment.ContentRootPath, false, false, "search.json");
diHelper.AddNLogManager("ASC.Feed.Agregator");
services.AddHostedService<FeedAggregatorService>();
diHelper
.AddFeedAggregatorService();
services.AddAutofac(hostContext.Configuration, hostContext.HostingEnvironment.ContentRootPath, true, false, "search.json", "feed.json");
})
.UseConsoleLifetime()
.Build();

View File

@ -0,0 +1,22 @@
{
"components": [
{
"type": "ASC.Files.Service.Core.FilesModule, ASC.Files.Service",
"services": [
{
"type": "ASC.Feed.Aggregator.Modules.IFeedModule, ASC.Feed.Aggregator"
}
],
"instanceScope": "perlifetimescope"
},
{
"type": "ASC.Files.Service.Core.FoldersModule, ASC.Files.Service",
"services": [
{
"type": "ASC.Feed.Aggregator.Modules.IFeedModule, ASC.Feed.Aggregator"
}
],
"instanceScope": "perlifetimescope"
}
]
}

View File

@ -59,7 +59,9 @@ export function getFoldersTree() {
return {
id: folder.id,
title: folder.title,
foldersCount: folder.foldersCount
access: folder.access,
foldersCount: folder.foldersCount,
rootFolderType: folder.rootFolderType
}
}) : null,
pathParts: data.pathParts,
@ -320,3 +322,10 @@ export function moveToFolder(destFolderId, folderIds, fileIds, conflictResolveTy
const data = { destFolderId, folderIds, fileIds, conflictResolveType, deleteAfter };
return request({ method: "put", url: "/files/fileops/move", data });
}
export function getFileVersionInfo(fileId) {
return request({
method: "get",
url: `/files/file/${fileId}/history`
});
}

View File

@ -44,11 +44,16 @@ class DragAndDrop extends Component {
}
};
onMouseDown = e => {
this.props.onMouseDown && this.props.onMouseDown(e);
}
componentDidMount() {
let div = this.dropRef.current;
div.addEventListener("drop", this.onDrop);
div.addEventListener("dragenter", this.onDragEnter);
div.addEventListener("dragleave", this.onDragLeave);
div.addEventListener("mousedown", this.onMouseDown);
}
componentWillUnmount() {
@ -56,6 +61,7 @@ class DragAndDrop extends Component {
div.removeEventListener("drop", this.onDrop);
div.removeEventListener("dragenter", this.onDragEnter);
div.removeEventListener("dragleave", this.onDragLeave);
div.removeEventListener("mousedown", this.onMouseDown);
}
render() {
@ -83,7 +89,8 @@ DragAndDrop.propTypes = {
dragging: PropTypes.bool,
onDragEnter: PropTypes.func,
onDragLeave: PropTypes.func,
onDrop: PropTypes.func
onDrop: PropTypes.func,
onMouseDown: PropTypes.func
};
DragAndDrop.defaultProps = {

View File

@ -59,7 +59,8 @@ class SelectedFrame extends React.Component {
const offsetScroll = this.props.scrollRef.current.viewScrollTop || 0;
if (item.offsetTop - offsetScroll >= topStart && item.offsetTop - offsetScroll <= topEnd) {
if (currentItem.getAttribute("value").split("_")[2]) {
const value = currentItem.getAttribute("value");
if (value && value.split("_")[2]) {
needUpdate = false;
break;
}

View File

@ -1,6 +1,6 @@
{
"name": "asc-web-components",
"version": "1.0.385",
"version": "1.0.388",
"description": "Ascensio System SIA component library",
"license": "AGPL-3.0",
"main": "dist/asc-web-components.js",

View File

@ -19,7 +19,7 @@ const StyledTreeContainer = styled.div`
const StyledTreeMenu = styled(Tree)`
margin: 0;
padding: 0;
width: 100%;
width: 93%;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
&:not(.rc-tree-show-line) .rc-tree-switcher-noop {
@ -80,9 +80,9 @@ const StyledTreeMenu = styled(Tree)`
`
: ''
}
.rc-tree-title {
/* .rc-tree-title {
width: ${props => props.badgeLabel && 'calc(100% - 60px) !important'};
}
} */
`;

View File

@ -48,13 +48,14 @@ const TreeNodeMenu = styled(TreeNode)`
position: relative;
-webkit-touch-callout: none;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
.draggable {
color: #333;
-moz-user-select: none;
-khtml-user-select: none;
-webkit-user-select: none;
-ms-user-select: none;
user-select: none;
/* Required to make elements draggable in old WebKit */
-khtml-user-drag: element;
-webkit-user-drag: element;
@ -91,7 +92,11 @@ const TreeNodeMenu = styled(TreeNode)`
position: absolute;
left: 0;
background: ${props => props.dragging ? "#F8F7BF" : "none"};
:hover {
background: ${props => props.dragging ? "#EFEFB2" : "none"};
}
}
span.rc-tree-switcher,
span.rc-tree-iconEle {
@ -221,6 +226,10 @@ const TreeNodeMenu = styled(TreeNode)`
border-radius: 3px;
z-index: 0;
width: 108%;
:hover {
background: #DFE2E3;
}
}
`;

View File

@ -3,6 +3,7 @@ export { default as AvatarEditor } from './components/avatar-editor'
export { default as Aside } from './components/aside'
export { default as Backdrop } from './components/backdrop'
export { default as Badge } from './components/badge'
export { default as Box } from './components/box'
export { default as Button } from './components/button'
export { default as Calendar } from './components/calendar'
export { default as Checkbox } from './components/checkbox'

View File

@ -42,7 +42,7 @@ namespace ASC.Web.Core.Files
public class FilesLinkUtility
{
public const string FilesBaseVirtualPath = "~/products/files/";
public const string EditorPage = "doceditor.aspx";
public const string EditorPage = "doceditor";
private readonly string FilesUploaderURL;
public CommonLinkUtility CommonLinkUtility { get; set; }
public BaseCommonLinkUtility BaseCommonLinkUtility { get; }
@ -73,7 +73,7 @@ namespace ASC.Web.Core.Files
get { return BaseCommonLinkUtility.ToAbsolute(FilesBaseVirtualPath); }
}
public const string FileId = "fileid";
public const string FileId = "fileId";
public const string FolderId = "folderid";
public const string Version = "version";
public const string FileUri = "fileuri";