대화내용 검색 기능 추가.
This commit is contained in:
parent
ccc88e194e
commit
b4a6081edd
|
@ -127,6 +127,13 @@
|
|||
>
|
||||
파일함
|
||||
</button>
|
||||
<button
|
||||
mat-menu-item
|
||||
*ngIf="getShowContextMenu('CHAT_SEARCH')"
|
||||
(click)="onClickContextMenu('CHAT_SEARCH')"
|
||||
>
|
||||
대화내용 검색
|
||||
</button>
|
||||
<button
|
||||
mat-menu-item
|
||||
*ngIf="getShowContextMenu('OPEN_ROOM_USER')"
|
||||
|
@ -174,6 +181,17 @@
|
|||
>
|
||||
<mat-progress-bar mode="indeterminate"></mat-progress-bar>
|
||||
</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>
|
||||
<!-- CHAT CONTENT -->
|
||||
<div
|
||||
|
@ -196,6 +214,7 @@
|
|||
>
|
||||
<ucap-chat-messages
|
||||
[eventList]="eventList"
|
||||
[searchedList]="searchedList"
|
||||
[roomInfo]="roomInfo"
|
||||
[eventInfoStatus]="eventInfoStatus"
|
||||
[eventRemain]="eventRemain$ | async"
|
||||
|
@ -269,7 +288,7 @@
|
|||
[hasBackdrop]="false"
|
||||
(ucapClickOutside)="messageContextMenuTrigger.closeMenu()"
|
||||
>
|
||||
<ng-template matMenuContent let-message="message" let-loginRes="loginRes">
|
||||
<ng-template matMenuContent let-message="message">
|
||||
<ng-container *ngIf="!isRecalledMessage(message.type)">
|
||||
<button
|
||||
mat-menu-item
|
||||
|
|
|
@ -34,7 +34,8 @@ import {
|
|||
InfoResponse,
|
||||
EventJson,
|
||||
FileEventJson,
|
||||
StickerEventJson
|
||||
StickerEventJson,
|
||||
MassTextEventJson
|
||||
} from '@ucap-webmessenger/protocol-event';
|
||||
|
||||
import * as AppStore from '@app/store';
|
||||
|
@ -152,6 +153,15 @@ export class MessagesComponent implements OnInit, OnDestroy, AfterViewInit {
|
|||
/** Timer 대화방의 대화 삭제를 위한 interval */
|
||||
interval: any;
|
||||
|
||||
/** About Searching */
|
||||
isShowSearchArea = true;
|
||||
moreSearchProcessing = false;
|
||||
searchText = '';
|
||||
searchedList: Info<EventJson>[];
|
||||
searchedFocusEvent: Info<EventJson>;
|
||||
searchTotalCount = 0;
|
||||
searchCurrentIndex = 0;
|
||||
|
||||
/** About Sticker */
|
||||
isShowStickerSelector = false;
|
||||
selectedSticker: StickerFilesInfo;
|
||||
|
@ -253,7 +263,6 @@ export class MessagesComponent implements OnInit, OnDestroy, AfterViewInit {
|
|||
infoList.length > 0 &&
|
||||
!!roomInfo &&
|
||||
!!roomInfo.lastReadEventSeq &&
|
||||
roomInfo.lastReadEventSeq > 0 &&
|
||||
this.baseEventSeq <= roomInfo.lastReadEventSeq
|
||||
) {
|
||||
// 조회된 내용중에 read here 가 있을 경우.
|
||||
|
@ -263,9 +272,17 @@ export class MessagesComponent implements OnInit, OnDestroy, AfterViewInit {
|
|||
|
||||
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.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.selectedSticker = undefined;
|
||||
|
||||
// Chat Search Clear..
|
||||
this.onCloseSearchArea();
|
||||
|
||||
this.firstcheckReadHere = true;
|
||||
|
||||
if (!!this.chatForm) {
|
||||
|
@ -1001,6 +1021,11 @@ export class MessagesComponent implements OnInit, OnDestroy, AfterViewInit {
|
|||
);
|
||||
}
|
||||
break;
|
||||
case 'CHAT_SEARCH':
|
||||
{
|
||||
this.onShowToggleSearchArea();
|
||||
}
|
||||
break;
|
||||
case 'OPEN_ROOM_USER':
|
||||
{
|
||||
this.store.dispatch(
|
||||
|
@ -1168,7 +1193,6 @@ export class MessagesComponent implements OnInit, OnDestroy, AfterViewInit {
|
|||
this.selectedSticker = null;
|
||||
}
|
||||
}
|
||||
|
||||
onSelectedSticker(stickerInfo: StickerFilesInfo) {
|
||||
this.selectedSticker = stickerInfo;
|
||||
}
|
||||
|
@ -1195,4 +1219,134 @@ export class MessagesComponent implements OnInit, OnDestroy, AfterViewInit {
|
|||
getStickerHistory(): string[] {
|
||||
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',
|
||||
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(
|
||||
'[Messenger::Event] File Info',
|
||||
|
|
|
@ -38,7 +38,10 @@ import {
|
|||
ReadNotification,
|
||||
SSVC_TYPE_EVENT_SEND_RES,
|
||||
SSVC_TYPE_EVENT_SEND_NOTI,
|
||||
EventJson
|
||||
EventJson,
|
||||
StickerEventJson,
|
||||
FileEventJson,
|
||||
MassTextEventJson
|
||||
} from '@ucap-webmessenger/protocol-event';
|
||||
|
||||
import * as ChatStore from '@app/store/messenger/chat';
|
||||
|
@ -75,7 +78,9 @@ import {
|
|||
fileInfo,
|
||||
fileInfoSuccess,
|
||||
fileInfoFailure,
|
||||
roomOpenAfterForward
|
||||
roomOpenAfterForward,
|
||||
infoForSearch,
|
||||
infoForSearchEnd
|
||||
} from './actions';
|
||||
import { SessionStorageService } from '@ucap-webmessenger/web-storage';
|
||||
import {
|
||||
|
@ -103,6 +108,12 @@ import {
|
|||
} from '@ucap-webmessenger/protocol-file';
|
||||
import { environment } from '../../../../environments/environment';
|
||||
import { RoomUserData } from '@ucap-webmessenger/protocol-sync';
|
||||
import {
|
||||
AlertDialogComponent,
|
||||
AlertDialogResult,
|
||||
AlertDialogData,
|
||||
DialogService
|
||||
} from '@ucap-webmessenger/ui';
|
||||
|
||||
@Injectable()
|
||||
export class Effects {
|
||||
|
@ -236,6 +247,128 @@ export class Effects {
|
|||
{ 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(
|
||||
() => {
|
||||
let fileInfoList: FileInfo[];
|
||||
|
@ -740,6 +873,7 @@ export class Effects {
|
|||
private fileProtocolService: FileProtocolService,
|
||||
private roomProtocolService: RoomProtocolService,
|
||||
private sessionStorageService: SessionStorageService,
|
||||
private dialogService: DialogService,
|
||||
private logger: NGXLogger
|
||||
) {}
|
||||
}
|
||||
|
|
|
@ -13,7 +13,9 @@ import {
|
|||
recallInfoList,
|
||||
delInfoList,
|
||||
infoMoreSuccess,
|
||||
fileInfoSuccess
|
||||
fileInfoSuccess,
|
||||
infoForSearch,
|
||||
infoForSearchEnd
|
||||
} from './actions';
|
||||
import * as AuthenticationStore from '@app/store/account/authentication';
|
||||
import * as ChatStore from '@app/store/messenger/chat';
|
||||
|
@ -27,6 +29,19 @@ export const reducer = createReducer(
|
|||
infoListProcessing: true
|
||||
};
|
||||
}),
|
||||
on(infoForSearch, (state, action) => {
|
||||
return {
|
||||
...state,
|
||||
infoListProcessing: true,
|
||||
infoSearchListProcessing: true
|
||||
};
|
||||
}),
|
||||
on(infoForSearchEnd, (state, action) => {
|
||||
return {
|
||||
...state,
|
||||
infoSearchListProcessing: false
|
||||
};
|
||||
}),
|
||||
|
||||
on(infoSuccess, (state, action) => {
|
||||
return {
|
||||
|
@ -88,7 +103,9 @@ export const reducer = createReducer(
|
|||
|
||||
return {
|
||||
...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 {
|
||||
infoListProcessing: boolean;
|
||||
infoSearchListProcessing: boolean;
|
||||
infoList: InfoListState;
|
||||
infoStatus: InfoResponse | null;
|
||||
remainInfo: boolean;
|
||||
|
@ -52,6 +53,7 @@ const fileInfoCheckListInitialState: FileInfoCheckListState = adapterFileInfoChe
|
|||
|
||||
export const initialState: State = {
|
||||
infoListProcessing: false,
|
||||
infoSearchListProcessing: false,
|
||||
infoList: infoListInitialState,
|
||||
infoStatus: null,
|
||||
remainInfo: false,
|
||||
|
@ -100,32 +102,22 @@ export function selectors<S>(selector: Selector<any, State>) {
|
|||
selector,
|
||||
(state: State) => state.infoListProcessing
|
||||
),
|
||||
remainInfo: createSelector(
|
||||
infoSearchListProcessing: createSelector(
|
||||
selector,
|
||||
(state: State) => state.remainInfo
|
||||
),
|
||||
infoList: createSelector(
|
||||
selector,
|
||||
(state: State) => state.infoList
|
||||
),
|
||||
infoStatus: createSelector(
|
||||
selector,
|
||||
(state: State) => state.infoStatus
|
||||
(state: State) => state.infoSearchListProcessing
|
||||
),
|
||||
remainInfo: createSelector(selector, (state: State) => state.remainInfo),
|
||||
infoList: createSelector(selector, (state: State) => state.infoList),
|
||||
infoStatus: createSelector(selector, (state: State) => state.infoStatus),
|
||||
|
||||
selectAllInfoList: createSelector(
|
||||
selectInfoList,
|
||||
ngeSelectAllInfoList
|
||||
),
|
||||
selectAllInfoList: createSelector(selectInfoList, ngeSelectAllInfoList),
|
||||
selectEntitiesInfoList: createSelector(
|
||||
selectInfoList,
|
||||
ngeSelectEntitiesInfoList
|
||||
),
|
||||
selectInfoList: (seq: number) =>
|
||||
createSelector(
|
||||
selectInfoList,
|
||||
ngeSelectEntitiesInfoList,
|
||||
(_, entities) => (!!entities ? entities[seq] : undefined)
|
||||
createSelector(selectInfoList, ngeSelectEntitiesInfoList, (_, entities) =>
|
||||
!!entities ? entities[seq] : undefined
|
||||
),
|
||||
|
||||
fileInfoListProcessing: createSelector(
|
||||
|
|
|
@ -19,9 +19,11 @@
|
|||
<div
|
||||
*ngFor="let message of messages; let i = index"
|
||||
class="message-row"
|
||||
[id]="message.seq"
|
||||
[ngClass]="{
|
||||
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
|
||||
|
|
|
@ -237,6 +237,9 @@ $meBox-bg: #ffffff;
|
|||
text-align: end;
|
||||
}
|
||||
}
|
||||
&.searched {
|
||||
color: red;
|
||||
}
|
||||
}
|
||||
|
||||
.message-row.me > .bubble {
|
||||
|
|
|
@ -35,6 +35,8 @@ export class MessagesComponent implements OnInit {
|
|||
this.messages = elist;
|
||||
}
|
||||
@Input()
|
||||
searchedList: Info<EventJson>[];
|
||||
@Input()
|
||||
eventInfoStatus?: InfoResponse;
|
||||
@Input()
|
||||
eventRemain: boolean;
|
||||
|
@ -123,6 +125,12 @@ export class MessagesComponent implements OnInit {
|
|||
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 {
|
||||
const unreadCnt = this.userInfos.filter(user => {
|
||||
|
@ -178,7 +186,6 @@ export class MessagesComponent implements OnInit {
|
|||
if (
|
||||
!!this.roomInfo &&
|
||||
!!this.roomInfo.lastReadEventSeq &&
|
||||
this.roomInfo.lastReadEventSeq > 0 &&
|
||||
this.lastEventSeq - this.roomInfo.lastReadEventSeq > 5
|
||||
) {
|
||||
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 { VideoComponent as MBVideoComponent } from './components/message-box/video.component';
|
||||
import { VideoConferenceComponent as MBVideoConferenceComponent } from './components/message-box/video-conference.component';
|
||||
import { SearchComponent } from './components/search.component';
|
||||
|
||||
const COMPONENTS = [
|
||||
FormComponent,
|
||||
MessagesComponent,
|
||||
SearchComponent,
|
||||
|
||||
MBDateSplitterComponent,
|
||||
MBFileComponent,
|
||||
|
|
Loading…
Reference in New Issue
Block a user