# 이슈처리
184 1:1대화방 상대방이 방 나가기 시대화 상대 없음 으로 표시됨 185 회의중, 상태메시지 입력 후 X버튼 후 편집버튼 선택 시 오류 186 그룹대화방 생성 후 메시지 발신 시 여기까지 읽음 기능 표시됨 187 그룹 대화방 읽음 표시 카운트 오류
This commit is contained in:
parent
1d94b4d1eb
commit
77bc91aa39
|
@ -467,6 +467,11 @@ export class MessagesComponent implements OnInit, OnDestroy, AfterViewInit {
|
||||||
}
|
}
|
||||||
|
|
||||||
get _roomUserInfos() {
|
get _roomUserInfos() {
|
||||||
|
if (this.roomInfoSubject.value.roomType === RoomType.Single) {
|
||||||
|
return this.userInfoListSubject.value.filter(roomUserInfo => {
|
||||||
|
return this.loginResSubject.value.userSeq !== roomUserInfo.seq;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
return this.userInfoListSubject.value
|
return this.userInfoListSubject.value
|
||||||
.filter(roomUserInfo => {
|
.filter(roomUserInfo => {
|
||||||
return (
|
return (
|
||||||
|
@ -476,6 +481,7 @@ export class MessagesComponent implements OnInit, OnDestroy, AfterViewInit {
|
||||||
})
|
})
|
||||||
.sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0));
|
.sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
getRoomNameByRoomUser(roomUserInfos: (UserInfo | UserInfoShort)[]) {
|
getRoomNameByRoomUser(roomUserInfos: (UserInfo | UserInfoShort)[]) {
|
||||||
let roomName = new TranslatePipe(
|
let roomName = new TranslatePipe(
|
||||||
|
|
|
@ -361,7 +361,10 @@
|
||||||
onApplyStatusMessage(1, statusMessage1.value)
|
onApplyStatusMessage(1, statusMessage1.value)
|
||||||
"
|
"
|
||||||
(edit)="$event.stopPropagation()"
|
(edit)="$event.stopPropagation()"
|
||||||
(cancel)="$event.stopPropagation()"
|
(cancel)="
|
||||||
|
$event.stopPropagation();
|
||||||
|
statusMessage1.value = loginRes?.statusMessage1
|
||||||
|
"
|
||||||
class="form-eidt"
|
class="form-eidt"
|
||||||
>
|
>
|
||||||
<span ucapInlineEditInput="view">{{ loginRes?.statusMessage1 }}</span>
|
<span ucapInlineEditInput="view">{{ loginRes?.statusMessage1 }}</span>
|
||||||
|
@ -386,7 +389,10 @@
|
||||||
onApplyStatusMessage(2, statusMessage2.value)
|
onApplyStatusMessage(2, statusMessage2.value)
|
||||||
"
|
"
|
||||||
(edit)="$event.stopPropagation()"
|
(edit)="$event.stopPropagation()"
|
||||||
(cancel)="$event.stopPropagation()"
|
(cancel)="
|
||||||
|
$event.stopPropagation();
|
||||||
|
statusMessage2.value = loginRes?.statusMessage2
|
||||||
|
"
|
||||||
class="form-eidt"
|
class="form-eidt"
|
||||||
>
|
>
|
||||||
<span ucapInlineEditInput="view">{{ loginRes?.statusMessage2 }}</span>
|
<span ucapInlineEditInput="view">{{ loginRes?.statusMessage2 }}</span>
|
||||||
|
@ -411,7 +417,10 @@
|
||||||
onApplyStatusMessage(3, statusMessage3.value)
|
onApplyStatusMessage(3, statusMessage3.value)
|
||||||
"
|
"
|
||||||
(edit)="$event.stopPropagation()"
|
(edit)="$event.stopPropagation()"
|
||||||
(cancel)="$event.stopPropagation()"
|
(cancel)="
|
||||||
|
$event.stopPropagation();
|
||||||
|
statusMessage3.value = loginRes?.statusMessage3
|
||||||
|
"
|
||||||
class="form-eidt"
|
class="form-eidt"
|
||||||
>
|
>
|
||||||
<span ucapInlineEditInput="view">{{ loginRes?.statusMessage3 }}</span>
|
<span ucapInlineEditInput="view">{{ loginRes?.statusMessage3 }}</span>
|
||||||
|
|
|
@ -11,7 +11,8 @@ import {
|
||||||
AuthenticationProtocolService,
|
AuthenticationProtocolService,
|
||||||
LogoutResponse,
|
LogoutResponse,
|
||||||
LogoutRemoteNotification,
|
LogoutRemoteNotification,
|
||||||
LogoutNotification
|
LogoutNotification,
|
||||||
|
LoginResponse
|
||||||
} from '@ucap-webmessenger/protocol-authentication';
|
} from '@ucap-webmessenger/protocol-authentication';
|
||||||
|
|
||||||
import { NGXLogger } from 'ngx-logger';
|
import { NGXLogger } from 'ngx-logger';
|
||||||
|
@ -446,7 +447,15 @@ export class AppNotificationService {
|
||||||
.subscribe();
|
.subscribe();
|
||||||
this.roomProtocolService.notification$
|
this.roomProtocolService.notification$
|
||||||
.pipe(
|
.pipe(
|
||||||
tap(notiOrRes => {
|
withLatestFrom(
|
||||||
|
this.store.pipe(
|
||||||
|
select(
|
||||||
|
(state: any) =>
|
||||||
|
state.account.authentication.loginRes as LoginResponse
|
||||||
|
)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
tap(([notiOrRes, loginResInfo]) => {
|
||||||
switch (notiOrRes.SSVC_TYPE) {
|
switch (notiOrRes.SSVC_TYPE) {
|
||||||
case SSVC_TYPE_ROOM_INVITE_RES:
|
case SSVC_TYPE_ROOM_INVITE_RES:
|
||||||
{
|
{
|
||||||
|
@ -506,11 +515,29 @@ export class AppNotificationService {
|
||||||
'Notification::roomProtocolService::ExitNotification',
|
'Notification::roomProtocolService::ExitNotification',
|
||||||
noti
|
noti
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (noti.SENDER_SEQ === loginResInfo.userSeq) {
|
||||||
this.store.dispatch(
|
this.store.dispatch(
|
||||||
RoomStore.exitNotification({
|
RoomStore.exitNotification({
|
||||||
noti
|
noti
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
} else {
|
||||||
|
this.store.dispatch(
|
||||||
|
RoomStore.exitNotificationOthers({
|
||||||
|
noti
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!!noti && !!noti.SENDER_SEQ) {
|
||||||
|
this.store.dispatch(
|
||||||
|
SyncStore.clearRoomUsers({
|
||||||
|
roomSeq: noti.roomSeq,
|
||||||
|
userSeqs: [noti.SENDER_SEQ]
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case SSVC_TYPE_ROOM_EXIT_FORCING_RES:
|
case SSVC_TYPE_ROOM_EXIT_FORCING_RES:
|
||||||
|
|
|
@ -42,6 +42,11 @@ export const infoFailure = createAction(
|
||||||
props<{ error: any }>()
|
props<{ error: any }>()
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const clearRoomUser = createAction(
|
||||||
|
'[Messenger::Room] clear room users',
|
||||||
|
props<{ userSeqs: number[] }>()
|
||||||
|
);
|
||||||
|
|
||||||
export const inviteNotification = createAction(
|
export const inviteNotification = createAction(
|
||||||
'[Messenger::Room] Invite Notification',
|
'[Messenger::Room] Invite Notification',
|
||||||
props<{ noti: InviteNotification }>()
|
props<{ noti: InviteNotification }>()
|
||||||
|
@ -51,6 +56,10 @@ export const exitNotification = createAction(
|
||||||
'[Messenger::Room] Exit Notification',
|
'[Messenger::Room] Exit Notification',
|
||||||
props<{ noti: ExitNotification }>()
|
props<{ noti: ExitNotification }>()
|
||||||
);
|
);
|
||||||
|
export const exitNotificationOthers = createAction(
|
||||||
|
'[Messenger::Room] Exit Notification By Others',
|
||||||
|
props<{ noti: ExitNotification }>()
|
||||||
|
);
|
||||||
|
|
||||||
export const exitForcing = createAction(
|
export const exitForcing = createAction(
|
||||||
'[Messenger::Room] Exit Forcing',
|
'[Messenger::Room] Exit Forcing',
|
||||||
|
|
|
@ -67,7 +67,9 @@ import {
|
||||||
updateTimeRoomIntervalFailure,
|
updateTimeRoomIntervalFailure,
|
||||||
exitForcing,
|
exitForcing,
|
||||||
exitForcingFailure,
|
exitForcingFailure,
|
||||||
exitForcingSuccess
|
exitForcingSuccess,
|
||||||
|
exitNotificationOthers,
|
||||||
|
clearRoomUser
|
||||||
} from './actions';
|
} from './actions';
|
||||||
import { SessionStorageService } from '@ucap-webmessenger/web-storage';
|
import { SessionStorageService } from '@ucap-webmessenger/web-storage';
|
||||||
import { LoginInfo, KEY_LOGIN_INFO } from '@app/types';
|
import { LoginInfo, KEY_LOGIN_INFO } from '@app/types';
|
||||||
|
@ -340,6 +342,31 @@ export class Effects {
|
||||||
},
|
},
|
||||||
{ dispatch: false }
|
{ dispatch: false }
|
||||||
);
|
);
|
||||||
|
exitNotificationOthers$ = createEffect(
|
||||||
|
() => {
|
||||||
|
return this.actions$.pipe(
|
||||||
|
ofType(exitNotificationOthers),
|
||||||
|
withLatestFrom(
|
||||||
|
this.store.pipe(
|
||||||
|
select((state: any) => state.messenger.room.roomInfo as RoomInfo)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
tap(([action, roomInfo]) => {
|
||||||
|
if (
|
||||||
|
!!roomInfo &&
|
||||||
|
roomInfo.roomSeq === action.noti.roomSeq &&
|
||||||
|
!!action.noti &&
|
||||||
|
!!action.noti.SENDER_SEQ
|
||||||
|
) {
|
||||||
|
this.store.dispatch(
|
||||||
|
clearRoomUser({ userSeqs: [action.noti.SENDER_SEQ] })
|
||||||
|
);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
},
|
||||||
|
{ dispatch: false }
|
||||||
|
);
|
||||||
|
|
||||||
updateFontNotification$ = createEffect(
|
updateFontNotification$ = createEffect(
|
||||||
() => {
|
() => {
|
||||||
|
|
|
@ -4,7 +4,8 @@ import {
|
||||||
infoSuccess,
|
infoSuccess,
|
||||||
updateSuccess,
|
updateSuccess,
|
||||||
updateRoomUserLastReadSeq,
|
updateRoomUserLastReadSeq,
|
||||||
updateTimeRoomIntervalSuccess
|
updateTimeRoomIntervalSuccess,
|
||||||
|
clearRoomUser
|
||||||
} from './actions';
|
} from './actions';
|
||||||
|
|
||||||
import * as AuthenticationStore from '@app/store/account/authentication';
|
import * as AuthenticationStore from '@app/store/account/authentication';
|
||||||
|
@ -26,6 +27,18 @@ export const reducer = createReducer(
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
on(clearRoomUser, (state, action) => {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
userInfoList: adapterUserInfo.removeMany(action.userSeqs, {
|
||||||
|
...state.userInfoList
|
||||||
|
}),
|
||||||
|
userInfoShortList: adapterUserInfoShort.removeMany(action.userSeqs, {
|
||||||
|
...state.userInfoShortList
|
||||||
|
})
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
|
||||||
on(updateSuccess, (state, action) => {
|
on(updateSuccess, (state, action) => {
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
|
|
|
@ -263,3 +263,9 @@ export const delGroupFailure = createAction(
|
||||||
'[Messenger::Sync] Group Del Failure',
|
'[Messenger::Sync] Group Del Failure',
|
||||||
props<{ error: any }>()
|
props<{ error: any }>()
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/** 방 인원 클리어 */
|
||||||
|
export const clearRoomUsers = createAction(
|
||||||
|
'[Messenger::Sync] Clear room users.',
|
||||||
|
props<{ roomSeq: string; userSeqs: number[] }>()
|
||||||
|
);
|
||||||
|
|
|
@ -17,7 +17,8 @@ import {
|
||||||
createGroupSuccess,
|
createGroupSuccess,
|
||||||
delBuddySuccess,
|
delBuddySuccess,
|
||||||
delGroupSuccess,
|
delGroupSuccess,
|
||||||
updateBuddySuccess
|
updateBuddySuccess,
|
||||||
|
clearRoomUsers
|
||||||
} from './actions';
|
} from './actions';
|
||||||
import {
|
import {
|
||||||
RoomUserDetailData,
|
RoomUserDetailData,
|
||||||
|
@ -101,6 +102,52 @@ export const reducer = createReducer(
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
on(clearRoomUsers, (state, action) => {
|
||||||
|
let roomUserList: RoomUserDetailData = {
|
||||||
|
...state.roomUser.entities[action.roomSeq]
|
||||||
|
};
|
||||||
|
if (
|
||||||
|
!!roomUserList &&
|
||||||
|
!!roomUserList.userInfos &&
|
||||||
|
roomUserList.userInfos.length > 0
|
||||||
|
) {
|
||||||
|
const userInfos = roomUserList.userInfos.filter(
|
||||||
|
userInfo => action.userSeqs.indexOf(userInfo.seq) < 0
|
||||||
|
);
|
||||||
|
roomUserList = {
|
||||||
|
...roomUserList,
|
||||||
|
userInfos
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
let roomUserShortList: RoomUserData = {
|
||||||
|
...state.roomUserShort.entities[action.roomSeq]
|
||||||
|
};
|
||||||
|
if (
|
||||||
|
!!roomUserShortList &&
|
||||||
|
!!roomUserShortList.userInfos &&
|
||||||
|
roomUserShortList.userInfos.length > 0
|
||||||
|
) {
|
||||||
|
const userInfos = roomUserShortList.userInfos.filter(
|
||||||
|
userInfo => action.userSeqs.indexOf(userInfo.seq) < 0
|
||||||
|
);
|
||||||
|
roomUserShortList = {
|
||||||
|
...roomUserShortList,
|
||||||
|
userInfos
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
roomUser: adapterRoomUser.upsertOne(roomUserList, {
|
||||||
|
...state.roomUser
|
||||||
|
}),
|
||||||
|
roomUserShort: adapterRoomUserShort.upsertOne(roomUserShortList, {
|
||||||
|
...state.roomUserShort
|
||||||
|
})
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
|
||||||
on(updateRoomForNewEventMessage, (state, action) => {
|
on(updateRoomForNewEventMessage, (state, action) => {
|
||||||
const finalEventMessage:
|
const finalEventMessage:
|
||||||
| string
|
| string
|
||||||
|
|
|
@ -141,6 +141,7 @@ export class MessagesComponent implements OnInit, OnDestroy {
|
||||||
moment = moment;
|
moment = moment;
|
||||||
|
|
||||||
readToHereEvent: Info<EventJson>;
|
readToHereEvent: Info<EventJson>;
|
||||||
|
existReadToHereEvent = true;
|
||||||
swapped = false;
|
swapped = false;
|
||||||
hidden = false;
|
hidden = false;
|
||||||
|
|
||||||
|
@ -168,15 +169,26 @@ export class MessagesComponent implements OnInit, OnDestroy {
|
||||||
|
|
||||||
this.initEventMore();
|
this.initEventMore();
|
||||||
this.roomInfo = roomInfo;
|
this.roomInfo = roomInfo;
|
||||||
|
|
||||||
|
/** [S] initializing by changed room */
|
||||||
|
// reset :: roomLastEventSeq
|
||||||
|
if (!!roomInfo && !!roomInfo.finalEventSeq) {
|
||||||
|
this.initRoomLastEventSeq = roomInfo.finalEventSeq;
|
||||||
|
}
|
||||||
|
// clear :: readToHearEvent object
|
||||||
|
this.readToHereEvent = undefined;
|
||||||
|
this.existReadToHereEvent = true;
|
||||||
|
/** [E] initializing by changed room */
|
||||||
});
|
});
|
||||||
this.eventListSubscription = this.eventList$.subscribe(eventList => {
|
this.eventListSubscription = this.eventList$.subscribe(eventList => {
|
||||||
if (!!eventList && eventList.length > 0 && this.baseEventSeq === 0) {
|
this.eventList = eventList;
|
||||||
this.initRoomLastEventSeq = eventList[eventList.length - 1].seq;
|
|
||||||
|
if (!!eventList && eventList.length > 0) {
|
||||||
|
if (!this.readToHereEvent && this.existReadToHereEvent) {
|
||||||
|
this.readToHereEvent = this.getReadHere();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!!eventList &&
|
|
||||||
eventList.length > 0 &&
|
|
||||||
this.baseEventSeq > 0 &&
|
this.baseEventSeq > 0 &&
|
||||||
!!this.roomInfo &&
|
!!this.roomInfo &&
|
||||||
!!this.roomInfo.lastReadEventSeq &&
|
!!this.roomInfo.lastReadEventSeq &&
|
||||||
|
@ -185,10 +197,9 @@ export class MessagesComponent implements OnInit, OnDestroy {
|
||||||
// 기존 대화 내용이 있는 상태에서 추가로 조회된 내용중에 read here 가 있을 경우.
|
// 기존 대화 내용이 있는 상태에서 추가로 조회된 내용중에 read here 가 있을 경우.
|
||||||
this.firstCheckReadHere = false;
|
this.firstCheckReadHere = false;
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
this.eventList = eventList;
|
this.readToHereEvent = undefined;
|
||||||
|
}
|
||||||
this.readToHereEvent = this.getReadHere();
|
|
||||||
|
|
||||||
this.changeDetectorRef.detectChanges();
|
this.changeDetectorRef.detectChanges();
|
||||||
|
|
||||||
|
@ -298,13 +309,9 @@ export class MessagesComponent implements OnInit, OnDestroy {
|
||||||
// if (!this.userInfos || 0 === this.userInfos.length) {
|
// if (!this.userInfos || 0 === this.userInfos.length) {
|
||||||
// return '';
|
// return '';
|
||||||
// }
|
// }
|
||||||
const unreadCnt = this.userInfos.filter(user => {
|
const unreadCnt = this.userInfos
|
||||||
if (message.senderSeq === user.seq) {
|
.filter(user => user.isJoinRoom && user.seq !== message.senderSeq)
|
||||||
// 본인 글은 unreadCount 에 포함하지 않는다.
|
.filter(user => user.lastReadEventSeq < message.seq).length;
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return user.lastReadEventSeq < message.seq;
|
|
||||||
}).length;
|
|
||||||
return unreadCnt === 0 ? '' : unreadCnt;
|
return unreadCnt === 0 ? '' : unreadCnt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -363,6 +370,8 @@ export class MessagesComponent implements OnInit, OnDestroy {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
this.existReadToHereEvent = false;
|
||||||
}
|
}
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
|
@ -73,7 +73,9 @@
|
||||||
$event.stopPropagation(); onApplyIntroMessage(introMessage.value)
|
$event.stopPropagation(); onApplyIntroMessage(introMessage.value)
|
||||||
"
|
"
|
||||||
(edit)="$event.stopPropagation()"
|
(edit)="$event.stopPropagation()"
|
||||||
(cancel)="$event.stopPropagation()"
|
(cancel)="
|
||||||
|
$event.stopPropagation(); introMessage.value = userInfo.intro
|
||||||
|
"
|
||||||
class="form-eidt"
|
class="form-eidt"
|
||||||
>
|
>
|
||||||
<span ucapInlineEditInput="view">{{ userInfo.intro }}</span>
|
<span ucapInlineEditInput="view">{{ userInfo.intro }}</span>
|
||||||
|
@ -167,20 +169,6 @@
|
||||||
|
|
||||||
<mat-card-content>
|
<mat-card-content>
|
||||||
<ul class="userInfo-list">
|
<ul class="userInfo-list">
|
||||||
<!--기존 삭제해주세요
|
|
||||||
<li>
|
|
||||||
<dt class="division">사업장</dt>
|
|
||||||
<dd>{{ userInfo.workplace }}</dd>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<dt class="division">담당업무/근무지</dt>
|
|
||||||
<dd>{{ userInfo.responsibilities }}</dd>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<dt class="division">사원직무/거래처</dt>
|
|
||||||
<dd>가나다라마바사아자차카타파하</dd>
|
|
||||||
</li>
|
|
||||||
-->
|
|
||||||
<li class="company">
|
<li class="company">
|
||||||
<dt class="division">{{ 'profile.fieldCompany' | translate }}</dt>
|
<dt class="division">{{ 'profile.fieldCompany' | translate }}</dt>
|
||||||
<dd>{{ userInfo.companyName | ucapStringEmptycheck }}</dd>
|
<dd>{{ userInfo.companyName | ucapStringEmptycheck }}</dd>
|
||||||
|
|
|
@ -94,14 +94,21 @@ export class ListItemComponent implements OnInit {
|
||||||
}
|
}
|
||||||
|
|
||||||
get _roomUserInfos() {
|
get _roomUserInfos() {
|
||||||
|
if (this.roomInfo.roomType === RoomType.Single) {
|
||||||
|
return this.roomUserInfo.filter(roomUserInfo => {
|
||||||
|
return this.loginRes.userSeq !== roomUserInfo.seq;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
return this.roomUserInfo
|
return this.roomUserInfo
|
||||||
.filter(roomUserInfo => {
|
.filter(roomUserInfo => {
|
||||||
return (
|
return (
|
||||||
this.loginRes.userSeq !== roomUserInfo.seq && roomUserInfo.isJoinRoom
|
this.loginRes.userSeq !== roomUserInfo.seq &&
|
||||||
|
roomUserInfo.isJoinRoom
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
.sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0));
|
.sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
getRoomNameByRoomUser(roomUserInfos: (RoomUserInfo | UserInfoShort)[]) {
|
getRoomNameByRoomUser(roomUserInfos: (RoomUserInfo | UserInfoShort)[]) {
|
||||||
let roomName = new TranslatePipe(
|
let roomName = new TranslatePipe(
|
||||||
|
|
Loading…
Reference in New Issue
Block a user