대화내용 검색 기능 추가.
This commit is contained in:
parent
ccc88e194e
commit
b4a6081edd
|
@ -127,6 +127,13 @@
|
||||||
>
|
>
|
||||||
파일함
|
파일함
|
||||||
</button>
|
</button>
|
||||||
|
<button
|
||||||
|
mat-menu-item
|
||||||
|
*ngIf="getShowContextMenu('CHAT_SEARCH')"
|
||||||
|
(click)="onClickContextMenu('CHAT_SEARCH')"
|
||||||
|
>
|
||||||
|
대화내용 검색
|
||||||
|
</button>
|
||||||
<button
|
<button
|
||||||
mat-menu-item
|
mat-menu-item
|
||||||
*ngIf="getShowContextMenu('OPEN_ROOM_USER')"
|
*ngIf="getShowContextMenu('OPEN_ROOM_USER')"
|
||||||
|
@ -174,6 +181,17 @@
|
||||||
>
|
>
|
||||||
<mat-progress-bar mode="indeterminate"></mat-progress-bar>
|
<mat-progress-bar mode="indeterminate"></mat-progress-bar>
|
||||||
</div>
|
</div>
|
||||||
|
<div *ngIf="isShowSearchArea" class="char-search">
|
||||||
|
<ucap-chat-search
|
||||||
|
[totalCount]="searchTotalCount"
|
||||||
|
[curIndex]="searchCurrentIndex"
|
||||||
|
(searchText)="onSearchChat($event)"
|
||||||
|
(prevSearch)="onPrevSearch()"
|
||||||
|
(nextSearch)="onNextSearch()"
|
||||||
|
(searchAndPrev)="onSearchAndPrev()"
|
||||||
|
(closeSearchArea)="onCloseSearchArea()"
|
||||||
|
></ucap-chat-search>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- CHAT CONTENT -->
|
<!-- CHAT CONTENT -->
|
||||||
<div
|
<div
|
||||||
|
@ -196,6 +214,7 @@
|
||||||
>
|
>
|
||||||
<ucap-chat-messages
|
<ucap-chat-messages
|
||||||
[eventList]="eventList"
|
[eventList]="eventList"
|
||||||
|
[searchedList]="searchedList"
|
||||||
[roomInfo]="roomInfo"
|
[roomInfo]="roomInfo"
|
||||||
[eventInfoStatus]="eventInfoStatus"
|
[eventInfoStatus]="eventInfoStatus"
|
||||||
[eventRemain]="eventRemain$ | async"
|
[eventRemain]="eventRemain$ | async"
|
||||||
|
@ -269,7 +288,7 @@
|
||||||
[hasBackdrop]="false"
|
[hasBackdrop]="false"
|
||||||
(ucapClickOutside)="messageContextMenuTrigger.closeMenu()"
|
(ucapClickOutside)="messageContextMenuTrigger.closeMenu()"
|
||||||
>
|
>
|
||||||
<ng-template matMenuContent let-message="message" let-loginRes="loginRes">
|
<ng-template matMenuContent let-message="message">
|
||||||
<ng-container *ngIf="!isRecalledMessage(message.type)">
|
<ng-container *ngIf="!isRecalledMessage(message.type)">
|
||||||
<button
|
<button
|
||||||
mat-menu-item
|
mat-menu-item
|
||||||
|
|
|
@ -34,7 +34,8 @@ import {
|
||||||
InfoResponse,
|
InfoResponse,
|
||||||
EventJson,
|
EventJson,
|
||||||
FileEventJson,
|
FileEventJson,
|
||||||
StickerEventJson
|
StickerEventJson,
|
||||||
|
MassTextEventJson
|
||||||
} from '@ucap-webmessenger/protocol-event';
|
} from '@ucap-webmessenger/protocol-event';
|
||||||
|
|
||||||
import * as AppStore from '@app/store';
|
import * as AppStore from '@app/store';
|
||||||
|
@ -152,6 +153,15 @@ export class MessagesComponent implements OnInit, OnDestroy, AfterViewInit {
|
||||||
/** Timer 대화방의 대화 삭제를 위한 interval */
|
/** Timer 대화방의 대화 삭제를 위한 interval */
|
||||||
interval: any;
|
interval: any;
|
||||||
|
|
||||||
|
/** About Searching */
|
||||||
|
isShowSearchArea = true;
|
||||||
|
moreSearchProcessing = false;
|
||||||
|
searchText = '';
|
||||||
|
searchedList: Info<EventJson>[];
|
||||||
|
searchedFocusEvent: Info<EventJson>;
|
||||||
|
searchTotalCount = 0;
|
||||||
|
searchCurrentIndex = 0;
|
||||||
|
|
||||||
/** About Sticker */
|
/** About Sticker */
|
||||||
isShowStickerSelector = false;
|
isShowStickerSelector = false;
|
||||||
selectedSticker: StickerFilesInfo;
|
selectedSticker: StickerFilesInfo;
|
||||||
|
@ -253,7 +263,6 @@ export class MessagesComponent implements OnInit, OnDestroy, AfterViewInit {
|
||||||
infoList.length > 0 &&
|
infoList.length > 0 &&
|
||||||
!!roomInfo &&
|
!!roomInfo &&
|
||||||
!!roomInfo.lastReadEventSeq &&
|
!!roomInfo.lastReadEventSeq &&
|
||||||
roomInfo.lastReadEventSeq > 0 &&
|
|
||||||
this.baseEventSeq <= roomInfo.lastReadEventSeq
|
this.baseEventSeq <= roomInfo.lastReadEventSeq
|
||||||
) {
|
) {
|
||||||
// 조회된 내용중에 read here 가 있을 경우.
|
// 조회된 내용중에 read here 가 있을 경우.
|
||||||
|
@ -263,9 +272,17 @@ export class MessagesComponent implements OnInit, OnDestroy, AfterViewInit {
|
||||||
|
|
||||||
this.eventList = infoList;
|
this.eventList = infoList;
|
||||||
|
|
||||||
if (!!infoList && infoList.length > 0) {
|
if (this.moreSearchProcessing) {
|
||||||
|
const baseseq = this.baseEventSeq;
|
||||||
|
setTimeout(() => {
|
||||||
|
this.onSearchChat(this.searchText, baseseq);
|
||||||
|
}, 800);
|
||||||
this.baseEventSeq = infoList[0].seq;
|
this.baseEventSeq = infoList[0].seq;
|
||||||
this.readyToReply();
|
} else {
|
||||||
|
if (!!infoList && infoList.length > 0) {
|
||||||
|
this.baseEventSeq = infoList[0].seq;
|
||||||
|
this.readyToReply();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
@ -340,6 +357,9 @@ export class MessagesComponent implements OnInit, OnDestroy, AfterViewInit {
|
||||||
this.isShowStickerSelector = false;
|
this.isShowStickerSelector = false;
|
||||||
this.selectedSticker = undefined;
|
this.selectedSticker = undefined;
|
||||||
|
|
||||||
|
// Chat Search Clear..
|
||||||
|
this.onCloseSearchArea();
|
||||||
|
|
||||||
this.firstcheckReadHere = true;
|
this.firstcheckReadHere = true;
|
||||||
|
|
||||||
if (!!this.chatForm) {
|
if (!!this.chatForm) {
|
||||||
|
@ -1001,6 +1021,11 @@ export class MessagesComponent implements OnInit, OnDestroy, AfterViewInit {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case 'CHAT_SEARCH':
|
||||||
|
{
|
||||||
|
this.onShowToggleSearchArea();
|
||||||
|
}
|
||||||
|
break;
|
||||||
case 'OPEN_ROOM_USER':
|
case 'OPEN_ROOM_USER':
|
||||||
{
|
{
|
||||||
this.store.dispatch(
|
this.store.dispatch(
|
||||||
|
@ -1168,7 +1193,6 @@ export class MessagesComponent implements OnInit, OnDestroy, AfterViewInit {
|
||||||
this.selectedSticker = null;
|
this.selectedSticker = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onSelectedSticker(stickerInfo: StickerFilesInfo) {
|
onSelectedSticker(stickerInfo: StickerFilesInfo) {
|
||||||
this.selectedSticker = stickerInfo;
|
this.selectedSticker = stickerInfo;
|
||||||
}
|
}
|
||||||
|
@ -1195,4 +1219,134 @@ export class MessagesComponent implements OnInit, OnDestroy, AfterViewInit {
|
||||||
getStickerHistory(): string[] {
|
getStickerHistory(): string[] {
|
||||||
return this.localStorageService.get<string[]>(KEY_STICKER_HISTORY);
|
return this.localStorageService.get<string[]>(KEY_STICKER_HISTORY);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** About Chat Search */
|
||||||
|
onShowToggleSearchArea() {
|
||||||
|
this.isShowSearchArea = !this.isShowSearchArea;
|
||||||
|
|
||||||
|
if (!this.isShowSearchArea) {
|
||||||
|
this.searchedList = [];
|
||||||
|
this.searchedFocusEvent = null;
|
||||||
|
this.searchText = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onCloseSearchArea() {
|
||||||
|
this.isShowSearchArea = false;
|
||||||
|
this.searchedList = [];
|
||||||
|
this.searchedFocusEvent = null;
|
||||||
|
this.searchText = '';
|
||||||
|
|
||||||
|
this.moreSearchProcessing = false;
|
||||||
|
this.searchTotalCount = 0;
|
||||||
|
this.searchCurrentIndex = 0;
|
||||||
|
|
||||||
|
this.store.dispatch(EventStore.infoForSearchEnd({}));
|
||||||
|
}
|
||||||
|
onSearchChat(searchText: string, baseSeq?: number) {
|
||||||
|
this.searchText = searchText;
|
||||||
|
|
||||||
|
this.searchedList = this.eventList.filter(event => {
|
||||||
|
let contents = '';
|
||||||
|
if (event.type === EventType.Character) {
|
||||||
|
contents = event.sentMessage;
|
||||||
|
} else if (event.type === EventType.Sticker && !!event.sentMessageJson) {
|
||||||
|
contents = (event.sentMessageJson as StickerEventJson).chat;
|
||||||
|
} else if (event.type === EventType.File && !!event.sentMessageJson) {
|
||||||
|
contents = (event.sentMessageJson as FileEventJson).fileName;
|
||||||
|
} else if (event.type === EventType.MassText && !!event.sentMessageJson) {
|
||||||
|
contents = (event.sentMessageJson as MassTextEventJson).content;
|
||||||
|
}
|
||||||
|
|
||||||
|
return contents.indexOf(searchText) > -1;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!!this.searchedList && this.searchedList.length > 0) {
|
||||||
|
this.searchTotalCount = this.searchedList.length;
|
||||||
|
|
||||||
|
if (!!baseSeq && baseSeq > 0) {
|
||||||
|
this.searchedList.forEach((searched, index) => {
|
||||||
|
if (searched.seq <= baseSeq) {
|
||||||
|
this.searchCurrentIndex = index + 1;
|
||||||
|
this.searchedFocusEvent = searched;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.searchCurrentIndex = this.searchedList.length;
|
||||||
|
this.searchedFocusEvent = this.searchedList[
|
||||||
|
this.searchedList.length - 1
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
this.store.dispatch(EventStore.infoForSearchEnd({}));
|
||||||
|
|
||||||
|
this.goSearchPosition(this.searchedFocusEvent.seq);
|
||||||
|
} else {
|
||||||
|
this.searchTotalCount = 0;
|
||||||
|
this.searchCurrentIndex = 0;
|
||||||
|
|
||||||
|
this.searchedFocusEvent = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onPrevSearch() {
|
||||||
|
this.searchedList.forEach((event, index) => {
|
||||||
|
if (event.seq === this.searchedFocusEvent.seq && index > 0) {
|
||||||
|
this.searchCurrentIndex = this.searchCurrentIndex - 1;
|
||||||
|
this.searchedFocusEvent = this.searchedList[index - 1];
|
||||||
|
this.goSearchPosition(this.searchedFocusEvent.seq);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
onNextSearch() {
|
||||||
|
// let exist = false;
|
||||||
|
// this.searchedList.forEach((event, index) => {
|
||||||
|
// if (
|
||||||
|
// event.seq === this.searchedFocusEvent.seq &&
|
||||||
|
// index < this.searchedList.length - 2 &&
|
||||||
|
// !exist
|
||||||
|
// ) {
|
||||||
|
// exist = true;
|
||||||
|
// this.searchCurrentIndex = this.searchCurrentIndex + 1;
|
||||||
|
// this.searchedFocusEvent = this.searchedList[index + 1];
|
||||||
|
// this.goSearchPosition(this.searchedFocusEvent.seq);
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
|
||||||
|
this.searchedFocusEvent = this.searchedList[this.searchCurrentIndex];
|
||||||
|
this.searchCurrentIndex = this.searchCurrentIndex + 1;
|
||||||
|
this.goSearchPosition(this.searchedFocusEvent.seq);
|
||||||
|
}
|
||||||
|
onSearchAndPrev() {
|
||||||
|
if (
|
||||||
|
!!this.searchText &&
|
||||||
|
this.searchText.trim().length > 0 &&
|
||||||
|
this.eventRemain
|
||||||
|
) {
|
||||||
|
this.moreSearchProcessing = true;
|
||||||
|
this.eventMorePosition = this.psChatContent.directiveRef.elementRef.nativeElement.scrollHeight;
|
||||||
|
|
||||||
|
this.store.dispatch(
|
||||||
|
EventStore.infoForSearch({
|
||||||
|
req: {
|
||||||
|
roomSeq: this.roomInfo.roomSeq,
|
||||||
|
baseSeq: this.eventList[0].seq,
|
||||||
|
requestCount:
|
||||||
|
environment.productConfig.CommonSetting.eventRequestDefaultCount
|
||||||
|
},
|
||||||
|
searchText: this.searchText
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
goSearchPosition(eventSeq: number) {
|
||||||
|
if (this.psChatContent.directiveRef) {
|
||||||
|
this.psChatContent.directiveRef.update();
|
||||||
|
|
||||||
|
const element = document.getElementById(eventSeq.toString());
|
||||||
|
if (!!element) {
|
||||||
|
setTimeout(() => {
|
||||||
|
this.psChatContent.directiveRef.scrollToTop(element.offsetTop - 200);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,6 +39,14 @@ export const infoFailure = createAction(
|
||||||
'[Messenger::Event] Info Failure',
|
'[Messenger::Event] Info Failure',
|
||||||
props<{ error: any }>()
|
props<{ error: any }>()
|
||||||
);
|
);
|
||||||
|
export const infoForSearch = createAction(
|
||||||
|
'[Messenger::Event] Info for search',
|
||||||
|
props<{ req: InfoRequest; searchText?: string }>()
|
||||||
|
);
|
||||||
|
export const infoForSearchEnd = createAction(
|
||||||
|
'[Messenger::Event] Info for search End',
|
||||||
|
props()
|
||||||
|
);
|
||||||
|
|
||||||
export const fileInfo = createAction(
|
export const fileInfo = createAction(
|
||||||
'[Messenger::Event] File Info',
|
'[Messenger::Event] File Info',
|
||||||
|
|
|
@ -38,7 +38,10 @@ import {
|
||||||
ReadNotification,
|
ReadNotification,
|
||||||
SSVC_TYPE_EVENT_SEND_RES,
|
SSVC_TYPE_EVENT_SEND_RES,
|
||||||
SSVC_TYPE_EVENT_SEND_NOTI,
|
SSVC_TYPE_EVENT_SEND_NOTI,
|
||||||
EventJson
|
EventJson,
|
||||||
|
StickerEventJson,
|
||||||
|
FileEventJson,
|
||||||
|
MassTextEventJson
|
||||||
} from '@ucap-webmessenger/protocol-event';
|
} from '@ucap-webmessenger/protocol-event';
|
||||||
|
|
||||||
import * as ChatStore from '@app/store/messenger/chat';
|
import * as ChatStore from '@app/store/messenger/chat';
|
||||||
|
@ -75,7 +78,9 @@ import {
|
||||||
fileInfo,
|
fileInfo,
|
||||||
fileInfoSuccess,
|
fileInfoSuccess,
|
||||||
fileInfoFailure,
|
fileInfoFailure,
|
||||||
roomOpenAfterForward
|
roomOpenAfterForward,
|
||||||
|
infoForSearch,
|
||||||
|
infoForSearchEnd
|
||||||
} from './actions';
|
} from './actions';
|
||||||
import { SessionStorageService } from '@ucap-webmessenger/web-storage';
|
import { SessionStorageService } from '@ucap-webmessenger/web-storage';
|
||||||
import {
|
import {
|
||||||
|
@ -103,6 +108,12 @@ import {
|
||||||
} from '@ucap-webmessenger/protocol-file';
|
} from '@ucap-webmessenger/protocol-file';
|
||||||
import { environment } from '../../../../environments/environment';
|
import { environment } from '../../../../environments/environment';
|
||||||
import { RoomUserData } from '@ucap-webmessenger/protocol-sync';
|
import { RoomUserData } from '@ucap-webmessenger/protocol-sync';
|
||||||
|
import {
|
||||||
|
AlertDialogComponent,
|
||||||
|
AlertDialogResult,
|
||||||
|
AlertDialogData,
|
||||||
|
DialogService
|
||||||
|
} from '@ucap-webmessenger/ui';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class Effects {
|
export class Effects {
|
||||||
|
@ -236,6 +247,128 @@ export class Effects {
|
||||||
{ dispatch: false }
|
{ dispatch: false }
|
||||||
);
|
);
|
||||||
|
|
||||||
|
infoForSearch$ = createEffect(
|
||||||
|
() => {
|
||||||
|
let infoList: Info<EventJson>[];
|
||||||
|
|
||||||
|
return this.actions$.pipe(
|
||||||
|
ofType(infoForSearch),
|
||||||
|
tap(() => {
|
||||||
|
infoList = [];
|
||||||
|
}),
|
||||||
|
withLatestFrom(
|
||||||
|
this.store.pipe(
|
||||||
|
select(
|
||||||
|
(state: any) =>
|
||||||
|
state.messenger.event.infoSearchListProcessing as boolean
|
||||||
|
)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
switchMap(([action, processing]) => {
|
||||||
|
return this.eventProtocolService.info(action.req).pipe(
|
||||||
|
map(async res => {
|
||||||
|
const req = action.req;
|
||||||
|
|
||||||
|
switch (res.SSVC_TYPE) {
|
||||||
|
case SSVC_TYPE_EVENT_INFO_DATA:
|
||||||
|
infoList.push(...(res as InfoData).infoList);
|
||||||
|
break;
|
||||||
|
case SSVC_TYPE_EVENT_INFO_RES:
|
||||||
|
{
|
||||||
|
if (req.baseSeq === 0) {
|
||||||
|
this.store.dispatch(
|
||||||
|
infoSuccess({
|
||||||
|
infoList,
|
||||||
|
res: res as InfoResponse,
|
||||||
|
remainInfo:
|
||||||
|
infoList.length === req.requestCount ? true : false
|
||||||
|
})
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
this.store.dispatch(
|
||||||
|
infoMoreSuccess({
|
||||||
|
infoList,
|
||||||
|
res: res as InfoResponse,
|
||||||
|
remainInfo:
|
||||||
|
infoList.length === req.requestCount ? true : false
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 검색어가 있을경우 조회된 이벤트 리스트 중 검색어를 찾고, 없으면 재귀한다.
|
||||||
|
if (!!action.searchText && infoList.length > 0) {
|
||||||
|
const searchList = infoList.filter(event => {
|
||||||
|
let contents = '';
|
||||||
|
if (event.type === EventType.Character) {
|
||||||
|
contents = event.sentMessage;
|
||||||
|
} else if (
|
||||||
|
event.type === EventType.Sticker &&
|
||||||
|
!!event.sentMessageJson
|
||||||
|
) {
|
||||||
|
contents = (event.sentMessageJson as StickerEventJson)
|
||||||
|
.chat;
|
||||||
|
} else if (
|
||||||
|
event.type === EventType.File &&
|
||||||
|
!!event.sentMessageJson
|
||||||
|
) {
|
||||||
|
contents = (event.sentMessageJson as FileEventJson)
|
||||||
|
.fileName;
|
||||||
|
} else if (
|
||||||
|
event.type === EventType.MassText &&
|
||||||
|
!!event.sentMessageJson
|
||||||
|
) {
|
||||||
|
contents = (event.sentMessageJson as MassTextEventJson)
|
||||||
|
.content;
|
||||||
|
}
|
||||||
|
|
||||||
|
return contents.indexOf(action.searchText) > -1;
|
||||||
|
});
|
||||||
|
if (
|
||||||
|
searchList.length === 0 &&
|
||||||
|
infoList.length === action.req.requestCount &&
|
||||||
|
processing
|
||||||
|
) {
|
||||||
|
this.store.dispatch(
|
||||||
|
infoForSearch({
|
||||||
|
req: {
|
||||||
|
roomSeq: req.roomSeq,
|
||||||
|
baseSeq: infoList[0].seq,
|
||||||
|
requestCount: req.requestCount
|
||||||
|
},
|
||||||
|
searchText: action.searchText
|
||||||
|
})
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
if (infoList.length < action.req.requestCount) {
|
||||||
|
this.store.dispatch(infoForSearchEnd({}));
|
||||||
|
|
||||||
|
await this.dialogService.open<
|
||||||
|
AlertDialogComponent,
|
||||||
|
AlertDialogData,
|
||||||
|
AlertDialogResult
|
||||||
|
>(AlertDialogComponent, {
|
||||||
|
width: '360px',
|
||||||
|
disableClose: true,
|
||||||
|
data: {
|
||||||
|
title: '',
|
||||||
|
message: '더이상 검색할 내용이 없습니다.'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
catchError(error => of(infoFailure({ error })))
|
||||||
|
);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
},
|
||||||
|
{ dispatch: false }
|
||||||
|
);
|
||||||
|
|
||||||
fileInfo$ = createEffect(
|
fileInfo$ = createEffect(
|
||||||
() => {
|
() => {
|
||||||
let fileInfoList: FileInfo[];
|
let fileInfoList: FileInfo[];
|
||||||
|
@ -740,6 +873,7 @@ export class Effects {
|
||||||
private fileProtocolService: FileProtocolService,
|
private fileProtocolService: FileProtocolService,
|
||||||
private roomProtocolService: RoomProtocolService,
|
private roomProtocolService: RoomProtocolService,
|
||||||
private sessionStorageService: SessionStorageService,
|
private sessionStorageService: SessionStorageService,
|
||||||
|
private dialogService: DialogService,
|
||||||
private logger: NGXLogger
|
private logger: NGXLogger
|
||||||
) {}
|
) {}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,9 @@ import {
|
||||||
recallInfoList,
|
recallInfoList,
|
||||||
delInfoList,
|
delInfoList,
|
||||||
infoMoreSuccess,
|
infoMoreSuccess,
|
||||||
fileInfoSuccess
|
fileInfoSuccess,
|
||||||
|
infoForSearch,
|
||||||
|
infoForSearchEnd
|
||||||
} from './actions';
|
} from './actions';
|
||||||
import * as AuthenticationStore from '@app/store/account/authentication';
|
import * as AuthenticationStore from '@app/store/account/authentication';
|
||||||
import * as ChatStore from '@app/store/messenger/chat';
|
import * as ChatStore from '@app/store/messenger/chat';
|
||||||
|
@ -27,6 +29,19 @@ export const reducer = createReducer(
|
||||||
infoListProcessing: true
|
infoListProcessing: true
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
|
on(infoForSearch, (state, action) => {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
infoListProcessing: true,
|
||||||
|
infoSearchListProcessing: true
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
on(infoForSearchEnd, (state, action) => {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
infoSearchListProcessing: false
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
|
||||||
on(infoSuccess, (state, action) => {
|
on(infoSuccess, (state, action) => {
|
||||||
return {
|
return {
|
||||||
|
@ -88,7 +103,9 @@ export const reducer = createReducer(
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
infoList: adapterInfoList.upsertOne(eventinfo, { ...state.infoList })
|
infoList: adapterInfoList.upsertOne(eventinfo, {
|
||||||
|
...state.infoList
|
||||||
|
})
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
|
|
@ -13,6 +13,7 @@ export interface FileInfoCheckListState extends EntityState<FileDownloadInfo> {}
|
||||||
|
|
||||||
export interface State {
|
export interface State {
|
||||||
infoListProcessing: boolean;
|
infoListProcessing: boolean;
|
||||||
|
infoSearchListProcessing: boolean;
|
||||||
infoList: InfoListState;
|
infoList: InfoListState;
|
||||||
infoStatus: InfoResponse | null;
|
infoStatus: InfoResponse | null;
|
||||||
remainInfo: boolean;
|
remainInfo: boolean;
|
||||||
|
@ -52,6 +53,7 @@ const fileInfoCheckListInitialState: FileInfoCheckListState = adapterFileInfoChe
|
||||||
|
|
||||||
export const initialState: State = {
|
export const initialState: State = {
|
||||||
infoListProcessing: false,
|
infoListProcessing: false,
|
||||||
|
infoSearchListProcessing: false,
|
||||||
infoList: infoListInitialState,
|
infoList: infoListInitialState,
|
||||||
infoStatus: null,
|
infoStatus: null,
|
||||||
remainInfo: false,
|
remainInfo: false,
|
||||||
|
@ -100,32 +102,22 @@ export function selectors<S>(selector: Selector<any, State>) {
|
||||||
selector,
|
selector,
|
||||||
(state: State) => state.infoListProcessing
|
(state: State) => state.infoListProcessing
|
||||||
),
|
),
|
||||||
remainInfo: createSelector(
|
infoSearchListProcessing: createSelector(
|
||||||
selector,
|
selector,
|
||||||
(state: State) => state.remainInfo
|
(state: State) => state.infoSearchListProcessing
|
||||||
),
|
|
||||||
infoList: createSelector(
|
|
||||||
selector,
|
|
||||||
(state: State) => state.infoList
|
|
||||||
),
|
|
||||||
infoStatus: createSelector(
|
|
||||||
selector,
|
|
||||||
(state: State) => state.infoStatus
|
|
||||||
),
|
),
|
||||||
|
remainInfo: createSelector(selector, (state: State) => state.remainInfo),
|
||||||
|
infoList: createSelector(selector, (state: State) => state.infoList),
|
||||||
|
infoStatus: createSelector(selector, (state: State) => state.infoStatus),
|
||||||
|
|
||||||
selectAllInfoList: createSelector(
|
selectAllInfoList: createSelector(selectInfoList, ngeSelectAllInfoList),
|
||||||
selectInfoList,
|
|
||||||
ngeSelectAllInfoList
|
|
||||||
),
|
|
||||||
selectEntitiesInfoList: createSelector(
|
selectEntitiesInfoList: createSelector(
|
||||||
selectInfoList,
|
selectInfoList,
|
||||||
ngeSelectEntitiesInfoList
|
ngeSelectEntitiesInfoList
|
||||||
),
|
),
|
||||||
selectInfoList: (seq: number) =>
|
selectInfoList: (seq: number) =>
|
||||||
createSelector(
|
createSelector(selectInfoList, ngeSelectEntitiesInfoList, (_, entities) =>
|
||||||
selectInfoList,
|
!!entities ? entities[seq] : undefined
|
||||||
ngeSelectEntitiesInfoList,
|
|
||||||
(_, entities) => (!!entities ? entities[seq] : undefined)
|
|
||||||
),
|
),
|
||||||
|
|
||||||
fileInfoListProcessing: createSelector(
|
fileInfoListProcessing: createSelector(
|
||||||
|
|
|
@ -19,9 +19,11 @@
|
||||||
<div
|
<div
|
||||||
*ngFor="let message of messages; let i = index"
|
*ngFor="let message of messages; let i = index"
|
||||||
class="message-row"
|
class="message-row"
|
||||||
|
[id]="message.seq"
|
||||||
[ngClass]="{
|
[ngClass]="{
|
||||||
me: message.senderSeq === loginRes.userSeq,
|
me: message.senderSeq === loginRes.userSeq,
|
||||||
contact: message.senderSeq !== loginRes.userSeq
|
contact: message.senderSeq !== loginRes.userSeq,
|
||||||
|
searched: getEventSearched(message.seq)
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
<ucap-chat-message-box-read-here
|
<ucap-chat-message-box-read-here
|
||||||
|
|
|
@ -237,6 +237,9 @@ $meBox-bg: #ffffff;
|
||||||
text-align: end;
|
text-align: end;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
&.searched {
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.message-row.me > .bubble {
|
.message-row.me > .bubble {
|
||||||
|
|
|
@ -35,6 +35,8 @@ export class MessagesComponent implements OnInit {
|
||||||
this.messages = elist;
|
this.messages = elist;
|
||||||
}
|
}
|
||||||
@Input()
|
@Input()
|
||||||
|
searchedList: Info<EventJson>[];
|
||||||
|
@Input()
|
||||||
eventInfoStatus?: InfoResponse;
|
eventInfoStatus?: InfoResponse;
|
||||||
@Input()
|
@Input()
|
||||||
eventRemain: boolean;
|
eventRemain: boolean;
|
||||||
|
@ -123,6 +125,12 @@ export class MessagesComponent implements OnInit {
|
||||||
return userInfo[0];
|
return userInfo[0];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
getEventSearched(seq: number): boolean {
|
||||||
|
return (
|
||||||
|
!!this.searchedList &&
|
||||||
|
this.searchedList.filter(event => event.seq === seq).length > 0
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
getUnreadCount(message: Info<EventJson>): string | number {
|
getUnreadCount(message: Info<EventJson>): string | number {
|
||||||
const unreadCnt = this.userInfos.filter(user => {
|
const unreadCnt = this.userInfos.filter(user => {
|
||||||
|
@ -178,7 +186,6 @@ export class MessagesComponent implements OnInit {
|
||||||
if (
|
if (
|
||||||
!!this.roomInfo &&
|
!!this.roomInfo &&
|
||||||
!!this.roomInfo.lastReadEventSeq &&
|
!!this.roomInfo.lastReadEventSeq &&
|
||||||
this.roomInfo.lastReadEventSeq > 0 &&
|
|
||||||
this.lastEventSeq - this.roomInfo.lastReadEventSeq > 5
|
this.lastEventSeq - this.roomInfo.lastReadEventSeq > 5
|
||||||
) {
|
) {
|
||||||
if (
|
if (
|
||||||
|
|
|
@ -0,0 +1,38 @@
|
||||||
|
<div fxFlex fxLayout="row">
|
||||||
|
<div fxLayout="row" fxLayoutAlign="start center" class="input">
|
||||||
|
<form [formGroup]="fgSearch" class="w-100-p">
|
||||||
|
<mat-form-field floatLabel="never">
|
||||||
|
<input
|
||||||
|
matInput
|
||||||
|
#inputSearch
|
||||||
|
type="text"
|
||||||
|
placeholder="대화방 내용 검색"
|
||||||
|
value=""
|
||||||
|
formControlName="searchInput"
|
||||||
|
(keydown.enter)="onKeyDownEnter($event, inputSearch.value)"
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
mat-button
|
||||||
|
matSuffix
|
||||||
|
mat-icon-button
|
||||||
|
aria-label="Clear"
|
||||||
|
(click)="inputSearch.value = ''; onClickSearchCancel()"
|
||||||
|
>
|
||||||
|
<mat-icon>close</mat-icon>
|
||||||
|
</button>
|
||||||
|
</mat-form-field>
|
||||||
|
{{ curIndex }} / {{ totalCount }}
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div class="btn">
|
||||||
|
<button mat-stroked-button (click)="onClickSearchAndPrev()">
|
||||||
|
<span class="mdi mdi-arrow-up-bold-box-outline mid-18px"></span>
|
||||||
|
</button>
|
||||||
|
<button mat-stroked-button (click)="onClickPrevSearch()">
|
||||||
|
<span class="mdi mdi-arrow-up-bold mid-18px"></span>
|
||||||
|
</button>
|
||||||
|
<button mat-stroked-button (click)="onClickNextSearch()">
|
||||||
|
<span class="mdi mdi-arrow-down-bold mid-18px"></span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
|
@ -0,0 +1,25 @@
|
||||||
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { SearchComponent } from './search.component';
|
||||||
|
|
||||||
|
describe('SearchComponent', () => {
|
||||||
|
let component: SearchComponent;
|
||||||
|
let fixture: ComponentFixture<SearchComponent>;
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
declarations: [ SearchComponent ]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(SearchComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,65 @@
|
||||||
|
import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core';
|
||||||
|
import { FormGroup, FormBuilder } from '@angular/forms';
|
||||||
|
import { NGXLogger } from 'ngx-logger';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'ucap-chat-search',
|
||||||
|
templateUrl: './search.component.html',
|
||||||
|
styleUrls: ['./search.component.scss']
|
||||||
|
})
|
||||||
|
export class SearchComponent implements OnInit {
|
||||||
|
@Input()
|
||||||
|
totalCount = 0;
|
||||||
|
@Input()
|
||||||
|
curIndex = 0;
|
||||||
|
|
||||||
|
@Output()
|
||||||
|
searchText = new EventEmitter<string>();
|
||||||
|
@Output()
|
||||||
|
prevSearch = new EventEmitter();
|
||||||
|
@Output()
|
||||||
|
nextSearch = new EventEmitter();
|
||||||
|
@Output()
|
||||||
|
searchAndPrev = new EventEmitter();
|
||||||
|
@Output()
|
||||||
|
closeSearchArea = new EventEmitter();
|
||||||
|
|
||||||
|
fgSearch: FormGroup;
|
||||||
|
isSearch = false;
|
||||||
|
|
||||||
|
constructor(private formBuilder: FormBuilder, private logger: NGXLogger) {}
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
this.fgSearch = this.formBuilder.group({
|
||||||
|
searchInput: null
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onClickSearchCancel() {
|
||||||
|
this.isSearch = false;
|
||||||
|
this.fgSearch.reset();
|
||||||
|
this.closeSearchArea.emit();
|
||||||
|
}
|
||||||
|
|
||||||
|
onKeyDownEnter(event: KeyboardEvent, search: string) {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
|
||||||
|
if (search.trim().length > 0) {
|
||||||
|
this.isSearch = true;
|
||||||
|
this.searchText.emit(search.trim());
|
||||||
|
} else {
|
||||||
|
this.isSearch = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onClickPrevSearch() {
|
||||||
|
this.prevSearch.emit();
|
||||||
|
}
|
||||||
|
onClickNextSearch() {
|
||||||
|
this.nextSearch.emit();
|
||||||
|
}
|
||||||
|
onClickSearchAndPrev() {
|
||||||
|
this.searchAndPrev.emit();
|
||||||
|
}
|
||||||
|
}
|
|
@ -31,10 +31,12 @@ import { TextComponent as MBTextComponent } from './components/message-box/text.
|
||||||
import { TranslationComponent as MBTranslationComponent } from './components/message-box/translation.component';
|
import { TranslationComponent as MBTranslationComponent } from './components/message-box/translation.component';
|
||||||
import { VideoComponent as MBVideoComponent } from './components/message-box/video.component';
|
import { VideoComponent as MBVideoComponent } from './components/message-box/video.component';
|
||||||
import { VideoConferenceComponent as MBVideoConferenceComponent } from './components/message-box/video-conference.component';
|
import { VideoConferenceComponent as MBVideoConferenceComponent } from './components/message-box/video-conference.component';
|
||||||
|
import { SearchComponent } from './components/search.component';
|
||||||
|
|
||||||
const COMPONENTS = [
|
const COMPONENTS = [
|
||||||
FormComponent,
|
FormComponent,
|
||||||
MessagesComponent,
|
MessagesComponent,
|
||||||
|
SearchComponent,
|
||||||
|
|
||||||
MBDateSplitterComponent,
|
MBDateSplitterComponent,
|
||||||
MBFileComponent,
|
MBFileComponent,
|
||||||
|
|
Loading…
Reference in New Issue
Block a user