leejinho 26b5259b3c # 이슈처리
106 입력된 내용 다 지웠지만 1로 보여짐
132 쪽지 이미지 삽입 시 드래그된것처럼 보여짐
2020-02-03 17:53:19 +09:00

608 lines
15 KiB
TypeScript

import {
Component,
OnInit,
Output,
EventEmitter,
ViewChild,
AfterViewInit,
ChangeDetectorRef,
OnDestroy,
ElementRef,
Input
} from '@angular/core';
import {
ucapAnimations,
DialogService,
AlertDialogResult,
AlertDialogComponent,
AlertDialogData
} from '@ucap-webmessenger/ui';
import { NGXLogger } from 'ngx-logger';
import moment from 'moment';
import { FileUtil } from '@ucap-webmessenger/core';
import { FormGroup, FormBuilder } from '@angular/forms';
import {
ContentType,
CategoryType,
MessageType,
DetailResponse,
DetailReceiver,
DetailContent
} from '@ucap-webmessenger/api-message';
import { FileUploadItem } from '@ucap-webmessenger/api';
import { UserInfo } from '@ucap-webmessenger/protocol-sync';
import {
ScheduleSendDialogComponent,
ScheduleSendDialogData,
ScheduleSendDialogResult
} from '../dialogs/schedule-send.dialog.component';
import { RoleCode } from '@ucap-webmessenger/protocol-authentication';
import { EmployeeType } from '@ucap-webmessenger/protocol-room';
import { TranslateService } from '@ngx-translate/core';
import { CommonApiService } from '@ucap-webmessenger/api-common';
const ATTR_FILE = 'UCAP_ATTR_FILE';
interface Content {
contentType: ContentType;
content: string | File;
resSeq: number;
}
export interface Message {
category: CategoryType;
type: MessageType;
title: string;
listOrder: ContentType[];
textContent: { text: string }[];
recvUserList: { userSeq: number; userName: string }[];
files?: File[];
fileUploadItem?: FileUploadItem;
reservationTime?: string;
smsYn?: boolean;
}
export interface MessageModify extends Message {
resSeqList: number[];
reservationTime: string;
msgId: number;
}
@Component({
selector: 'ucap-message-write',
templateUrl: './write.component.html',
styleUrls: ['./write.component.scss'],
animations: ucapAnimations
})
export class WriteComponent implements OnInit, OnDestroy, AfterViewInit {
@Input()
set curReceiverList(userInfoList: UserInfo[]) {
if (!!userInfoList && userInfoList.length > 0) {
this.receiverList = userInfoList;
}
}
@Input()
detail?: DetailResponse;
@Input()
detailContents?: string;
@Input()
isModify = false;
@Input()
fileAllowSize: number;
@Output()
send = new EventEmitter<Message | MessageModify>();
@Output()
cancel = new EventEmitter<void>();
@Output()
selectReceiver = new EventEmitter<UserInfo[]>();
@ViewChild('editor', { static: true })
editor: ElementRef<HTMLDivElement>;
@ViewChild('fileInput', { static: true })
fileInput: ElementRef<HTMLInputElement>;
messageWriteForm: FormGroup;
oldAttachmentList: DetailContent[] = [];
attachmentList: File[];
fileUploadItem: FileUploadItem;
receiverList: UserInfo[] = [];
contentLength = 0;
constructor(
private formBuilder: FormBuilder,
private dialogService: DialogService,
private changeDetectorRef: ChangeDetectorRef,
private translateService: TranslateService,
private commonApiService: CommonApiService,
private logger: NGXLogger
) {}
ngOnInit() {
this.messageWriteForm = this.formBuilder.group({
title: ['', []]
});
if (this.isModify) {
if (!!this.detail.msgInfo.title) {
this.messageWriteForm.setValue({ title: this.detail.msgInfo.title });
}
if (!!this.detailContents) {
this.editor.nativeElement.innerHTML = this.detailContents;
this.onInputEditor();
}
this.detail.contents.forEach(content => {
if (content.resType === ContentType.AttachFile) {
this.oldAttachmentList.push(content);
}
});
}
}
ngOnDestroy(): void {}
ngAfterViewInit(): void {}
onClickDeleteOldAttachment(oldAttachment: DetailContent) {
this.oldAttachmentList = this.oldAttachmentList.filter(
detailContent => detailContent.resSeq !== oldAttachment.resSeq
);
}
onClickDelelteAttachment(attachment: File) {
this.attachmentList = this.attachmentList.filter(
attFile => attFile !== attachment
);
}
onClickImage() {
this.fileInput.nativeElement.setAttribute('accept', 'image/*');
this.fileInput.nativeElement.click();
const self = this;
this.fileInput.nativeElement.onchange = async () => {
const fileList: FileList = self.fileInput.nativeElement.files;
if (!this.validUploadFile(fileList)) {
return;
}
for (let i = 0; i < fileList.length; i++) {
const file = fileList.item(i);
const dataUrl = await FileUtil.fromBlobToDataUrl(file);
const img = document.createElement('img');
img.src = dataUrl as string;
img[ATTR_FILE] = file;
self.insertNode(img);
}
self.fileInput.nativeElement.value = '';
this.fileInput.nativeElement.onchange = undefined;
};
}
onClickAttachment() {
this.fileInput.nativeElement.removeAttribute('accept');
this.fileInput.nativeElement.click();
const self = this;
this.fileInput.nativeElement.onchange = () => {
const fileList: FileList = this.fileInput.nativeElement.files;
if (!this.validUploadFile(fileList)) {
return;
}
if (!self.attachmentList) {
self.attachmentList = [];
}
for (let i = 0; i < fileList.length; i++) {
const file = fileList.item(i);
self.attachmentList.push(file);
}
self.changeDetectorRef.detectChanges();
self.fileInput.nativeElement.value = '';
};
}
validUploadFile(fileList: FileList): boolean {
let valid = true;
if (this.fileAllowSize > 0) {
for (let i = 0; i < fileList.length; i++) {
const file = fileList.item(i);
if (file.size > this.fileAllowSize * 1024 * 1024) {
valid = false;
break;
}
}
if (!valid) {
this.dialogService.open<
AlertDialogComponent,
AlertDialogData,
AlertDialogResult
>(AlertDialogComponent, {
width: '360px',
data: {
title: '',
message: this.translateService.instant(
'common.file.errors.oversize',
{
maxSize: this.fileAllowSize
}
)
}
});
return valid;
}
}
const checkExt = this.commonApiService.acceptableExtensionForFileTalk(
FileUtil.getExtensions(fileList)
);
if (!checkExt.accept) {
this.dialogService.open<
AlertDialogComponent,
AlertDialogData,
AlertDialogResult
>(AlertDialogComponent, {
data: {
title: 'Alert',
html: `${this.translateService.instant(
'common.file.errors.notSupporedType'
)} ${
checkExt.reject.length > 0
? '<br/>(' + checkExt.reject.join(',') + ')'
: ''
}`
}
});
return valid;
}
return valid;
}
onPasteEditor(event: ClipboardEvent) {
const text = document.createTextNode(
event.clipboardData.getData('text/plain')
);
this.insertNode(text, true);
this.checkContentLength();
return false;
}
onInputEditor() {
this.checkContentLength();
}
onRemovedReceiver(receiver: UserInfo) {
const index = this.receiverList.indexOf(receiver);
if (index >= 0) {
this.receiverList.splice(index, 1);
}
}
onClickReceiverList() {
this.selectReceiver.emit(this.receiverList);
}
onClickSend() {
this.sendMessage();
}
onClickCancel() {
this.cancel.emit();
}
async onClickSendSchedule() {
const reservationDate = this.isModify
? moment(this.detail.msgInfo.reservationTime)
: undefined;
const result = await this.dialogService.open<
ScheduleSendDialogComponent,
ScheduleSendDialogData,
ScheduleSendDialogResult
>(ScheduleSendDialogComponent, {
width: '600px',
height: '600px',
disableClose: true,
data: {
reservationDate
}
});
if (!!result && !!result.scheduleSendDate) {
this.sendMessage(result.scheduleSendDate);
}
}
private checkContentLength() {
const result = this.parseContent();
if (!result) {
this.contentLength = 0;
return;
}
const { textContent } = result;
if (!textContent || 0 === textContent.length) {
this.contentLength = 0;
} else {
let len = 0;
textContent.forEach(c => {
len += c.text.length;
});
this.contentLength = len;
}
}
private sendMessage(reservationDate?: moment.Moment) {
const result = this.parseContent();
if (!result) {
return;
}
const { listOrder, textContent, files, resSeqList } = result;
if (!listOrder || 0 === listOrder.length) {
return;
}
const recvUserList: { userSeq: number; userName: string }[] = [];
const title = this.messageWriteForm.get('title').value;
this.receiverList.forEach(v => {
recvUserList.push({
userSeq: v.seq,
userName: v.name
});
});
this.fileUploadItem = FileUploadItem.from();
if (!this.isModify) {
const message: Message = {
category: CategoryType.General,
type: !!reservationDate ? MessageType.Reservation : MessageType.Send,
title,
listOrder,
textContent,
recvUserList,
reservationTime: !!reservationDate
? `${reservationDate.format('YYYY-MM-DD HH:mm')}:00`
: undefined,
files,
fileUploadItem: this.fileUploadItem
};
this.send.emit(message);
} else {
const message: MessageModify = {
category: CategoryType.General,
type: !!reservationDate ? MessageType.Reservation : MessageType.Send,
msgId: this.detail.msgInfo.msgId,
title,
listOrder,
resSeqList,
textContent,
recvUserList,
reservationTime: !!reservationDate
? `${reservationDate.format('YYYY-MM-DD HH:mm')}:00`
: undefined,
files,
fileUploadItem: this.fileUploadItem
};
this.send.emit(message);
}
}
private parseContent():
| {
listOrder: ContentType[];
textContent: { text: string }[];
files: File[];
resSeqList: number[];
}
| undefined {
const contentList: Content[] = this.generateContent();
if (!contentList || 0 === contentList.length) {
return;
}
const listOrder: ContentType[] = [];
const textContent: { text: string }[] = [];
const files: File[] = [];
const resSeqList: number[] = [];
contentList.forEach(v => {
listOrder.push(v.contentType);
resSeqList.push(v.resSeq);
switch (v.contentType) {
case ContentType.Text:
let content = v.content as string;
if ('\n' === content.charAt(content.length - 1)) {
content = content.substring(0, content.length - 1);
}
textContent.push({ text: content });
break;
case ContentType.Image:
case ContentType.AttachFile:
{
if (v.resSeq === 0) {
files.push(v.content as File);
}
}
break;
default:
break;
}
});
return {
listOrder,
textContent,
files,
resSeqList
};
}
private generateContent(): Content[] {
const contentList: Content[] = [];
this.editor.nativeElement.childNodes.forEach((v, k) => {
this.parseNode(contentList, v);
});
if (!!this.oldAttachmentList && 0 < this.oldAttachmentList.length) {
this.oldAttachmentList.forEach(v => {
contentList.push({
contentType: ContentType.AttachFile,
content: v.resContent,
resSeq: v.resSeq
});
});
}
if (!!this.attachmentList && 0 < this.attachmentList.length) {
this.attachmentList.forEach(v => {
contentList.push({
contentType: ContentType.AttachFile,
content: v,
resSeq: 0
});
});
}
return contentList;
}
private parseNode(contentList: Content[], node: ChildNode) {
switch (node.nodeType) {
case Node.ELEMENT_NODE:
{
if (0 < node.childNodes.length) {
let prevNode: ChildNode;
node.childNodes.forEach(v => {
if (
!!prevNode &&
'IMG' === prevNode.nodeName &&
'BR' === v.nodeName
) {
prevNode = v;
return;
}
prevNode = v;
this.parseNode(contentList, v);
});
} else {
if ('IMG' === node.nodeName) {
const img: HTMLImageElement = node as HTMLImageElement;
this.appendNode(
contentList,
ContentType.Image,
img[ATTR_FILE],
!!img.getAttribute('id') ? Number(img.getAttribute('id')) : 0
);
} else if ('BR' === node.nodeName) {
this.appendNode(contentList, ContentType.Text, `\n`);
} else {
}
}
}
break;
case Node.TEXT_NODE:
this.appendNode(contentList, ContentType.Text, node.textContent);
break;
default:
break;
}
}
private appendNode(
contentList: Content[],
contentType: ContentType,
content: string | File,
resSeq: number = 0
) {
const prevContent = contentList[contentList.length - 1];
switch (contentType) {
case ContentType.Text:
if (!!prevContent && ContentType.Text === prevContent.contentType) {
prevContent.content = `${prevContent.content}${content}`;
} else {
contentList.push({
contentType: ContentType.Text,
content,
resSeq
});
}
break;
default:
contentList.push({ contentType, content, resSeq });
break;
}
}
private insertNode(node: Node, removeSelected: boolean = false) {
const selection: Selection = document.getSelection();
const range: Range = selection.getRangeAt(0);
let inEditor = false;
if (removeSelected) {
// onPaste
selection.empty();
inEditor = true;
} else {
inEditor = this.inEditor(selection.anchorNode);
}
if (inEditor) {
range.insertNode(node);
} else {
this.editor.nativeElement.appendChild(node);
}
selection.empty();
}
private inEditor(el) {
if (!!el.classList) {
let root = false;
(el.classList as DOMTokenList).forEach(className => {
if (className === 'ucap-message-write') {
root = true;
}
});
if (!!root) {
return false;
}
}
if (el.tagName === 'BODY') {
return false;
}
if (
!!el.className &&
el.className === 'ucap-message-write-editor' &&
el.getAttribute('contenteditable')
) {
return true;
} else {
return this.inEditor(el.parentNode);
}
}
}