@trixwell/ngx-parl 0.0.0-watch
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +91 -0
- package/fesm2022/trixwell-ngx-parl.mjs +894 -0
- package/fesm2022/trixwell-ngx-parl.mjs.map +1 -0
- package/package.json +47 -0
|
@@ -0,0 +1,894 @@
|
|
|
1
|
+
import * as i0 from '@angular/core';
|
|
2
|
+
import { input, model, computed, Component, Injectable, Pipe, effect, ViewChild, signal, isDevMode } from '@angular/core';
|
|
3
|
+
import { NgClass, NgOptimizedImage, DatePipe } from '@angular/common';
|
|
4
|
+
import { FormsModule } from '@angular/forms';
|
|
5
|
+
import * as i1 from '@angular/material/icon';
|
|
6
|
+
import { MatIcon } from '@angular/material/icon';
|
|
7
|
+
import { MatMenu, MatMenuItem, MatMenuTrigger } from '@angular/material/menu';
|
|
8
|
+
import * as i2$1 from '@ngneat/transloco';
|
|
9
|
+
import { TranslocoPipe, TranslocoModule, provideTransloco } from '@ngneat/transloco';
|
|
10
|
+
import * as i2 from '@angular/platform-browser';
|
|
11
|
+
import * as i1$1 from '@angular/common/http';
|
|
12
|
+
import { MatDialogContent, MatDialogTitle } from '@angular/material/dialog';
|
|
13
|
+
import { MatProgressSpinner } from '@angular/material/progress-spinner';
|
|
14
|
+
import { of } from 'rxjs';
|
|
15
|
+
|
|
16
|
+
class ChatMessage {
|
|
17
|
+
id;
|
|
18
|
+
chat_id;
|
|
19
|
+
cr_time;
|
|
20
|
+
type;
|
|
21
|
+
user;
|
|
22
|
+
content;
|
|
23
|
+
avatar;
|
|
24
|
+
file_path;
|
|
25
|
+
checked;
|
|
26
|
+
edit = false;
|
|
27
|
+
constructor(data) {
|
|
28
|
+
this.id = data.id;
|
|
29
|
+
this.chat_id = data.chat_id;
|
|
30
|
+
this.cr_time = data.cr_time;
|
|
31
|
+
this.type = data.type;
|
|
32
|
+
this.user = data.user;
|
|
33
|
+
this.content = data.content;
|
|
34
|
+
this.avatar = data.avatar ?? null;
|
|
35
|
+
this.file_path = data.file_path ?? null;
|
|
36
|
+
this.checked = data.checked ?? null;
|
|
37
|
+
}
|
|
38
|
+
get dateSimple() {
|
|
39
|
+
const d = new Date(this.cr_time.replace(' ', 'T'));
|
|
40
|
+
const dd = String(d.getDate()).padStart(2, '0');
|
|
41
|
+
const mm = String(d.getMonth() + 1).padStart(2, '0');
|
|
42
|
+
const yyyy = d.getFullYear();
|
|
43
|
+
return `${dd}.${mm}.${yyyy}`;
|
|
44
|
+
}
|
|
45
|
+
get timeHHmm() {
|
|
46
|
+
const d = new Date(this.cr_time.replace(' ', 'T'));
|
|
47
|
+
const hh = String(d.getHours()).padStart(2, '0');
|
|
48
|
+
const mm = String(d.getMinutes()).padStart(2, '0');
|
|
49
|
+
return `${hh}:${mm}`;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
var MessageType;
|
|
53
|
+
(function (MessageType) {
|
|
54
|
+
MessageType["Incoming"] = "incoming";
|
|
55
|
+
MessageType["Outgoing"] = "outgoing";
|
|
56
|
+
})(MessageType || (MessageType = {}));
|
|
57
|
+
|
|
58
|
+
class ChatMessageComponent {
|
|
59
|
+
iconRegistry;
|
|
60
|
+
sanitizer;
|
|
61
|
+
currentMessage = input.required(...(ngDevMode ? [{ debugName: "currentMessage" }] : []));
|
|
62
|
+
edit = model(false, ...(ngDevMode ? [{ debugName: "edit" }] : []));
|
|
63
|
+
requestEdit = model(null, ...(ngDevMode ? [{ debugName: "requestEdit" }] : []));
|
|
64
|
+
requestDelete = model(null, ...(ngDevMode ? [{ debugName: "requestDelete" }] : []));
|
|
65
|
+
constructor(iconRegistry, sanitizer) {
|
|
66
|
+
this.iconRegistry = iconRegistry;
|
|
67
|
+
this.sanitizer = sanitizer;
|
|
68
|
+
this.iconRegistry.addSvgIcon('checked-message', this.sanitizer.bypassSecurityTrustResourceUrl('assets/ngx-parl/icons/checked-message.svg'));
|
|
69
|
+
this.iconRegistry.addSvgIcon('no-check', this.sanitizer.bypassSecurityTrustResourceUrl('assets/ngx-parl/icons/no-check.svg'));
|
|
70
|
+
this.iconRegistry.addSvgIcon('trash', this.sanitizer.bypassSecurityTrustResourceUrl('assets/ngx-parl/icons/trash.svg'));
|
|
71
|
+
this.iconRegistry.addSvgIcon('icon-edit', this.sanitizer.bypassSecurityTrustResourceUrl('assets/ngx-parl/icons/icon-edit.svg'));
|
|
72
|
+
setTimeout(() => {
|
|
73
|
+
this.currentMessage().checked = true;
|
|
74
|
+
}, 600);
|
|
75
|
+
}
|
|
76
|
+
normalizeSourcePath(sourcePath) {
|
|
77
|
+
const cleanedPath = (sourcePath ?? '').trim();
|
|
78
|
+
if (!cleanedPath) {
|
|
79
|
+
return '';
|
|
80
|
+
}
|
|
81
|
+
if (cleanedPath.startsWith('data:') || cleanedPath.startsWith('blob:') || /^https?:\/\//i.test(cleanedPath)) {
|
|
82
|
+
return cleanedPath;
|
|
83
|
+
}
|
|
84
|
+
const assetsIndex = cleanedPath.indexOf('assets/');
|
|
85
|
+
if (assetsIndex >= 0) {
|
|
86
|
+
return '/' + cleanedPath.slice(assetsIndex);
|
|
87
|
+
}
|
|
88
|
+
return cleanedPath.replace(/^\.{1,2}\//, '/');
|
|
89
|
+
}
|
|
90
|
+
attachments = computed(() => {
|
|
91
|
+
const message = this.currentMessage();
|
|
92
|
+
const filePath = message.file_path;
|
|
93
|
+
if (Array.isArray(filePath)) {
|
|
94
|
+
return filePath.map(p => this.normalizeSourcePath(p)).filter(Boolean);
|
|
95
|
+
}
|
|
96
|
+
const rawFilePath = filePath ?? '';
|
|
97
|
+
if (typeof rawFilePath !== 'string' || !rawFilePath.trim) {
|
|
98
|
+
return [];
|
|
99
|
+
}
|
|
100
|
+
if (rawFilePath.trim().startsWith('[')) {
|
|
101
|
+
try {
|
|
102
|
+
const parsed = JSON.parse(rawFilePath);
|
|
103
|
+
if (Array.isArray(parsed)) {
|
|
104
|
+
return parsed
|
|
105
|
+
.map(item => (typeof item === 'string' ? this.normalizeSourcePath(item) : ''))
|
|
106
|
+
.filter(Boolean);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
catch { }
|
|
110
|
+
}
|
|
111
|
+
if (rawFilePath.startsWith('data:')) {
|
|
112
|
+
return [rawFilePath];
|
|
113
|
+
}
|
|
114
|
+
if (rawFilePath.includes('|')) {
|
|
115
|
+
return rawFilePath.split('|').map(p => this.normalizeSourcePath(p)).filter(Boolean);
|
|
116
|
+
}
|
|
117
|
+
if (rawFilePath.includes(',')) {
|
|
118
|
+
return rawFilePath.split(',').map(p => this.normalizeSourcePath(p)).filter(Boolean);
|
|
119
|
+
}
|
|
120
|
+
return [];
|
|
121
|
+
}, ...(ngDevMode ? [{ debugName: "attachments" }] : []));
|
|
122
|
+
avatarSrc = computed(() => {
|
|
123
|
+
const message = this.currentMessage();
|
|
124
|
+
const fallback = message.type === 'incoming'
|
|
125
|
+
? 'assets/ngx-parl/icons/avatar_anonym.svg'
|
|
126
|
+
: 'assets/ngx-parl/icons/avatar_manager.svg';
|
|
127
|
+
return message.avatar || fallback;
|
|
128
|
+
}, ...(ngDevMode ? [{ debugName: "avatarSrc" }] : []));
|
|
129
|
+
openContextMenu(event, trigger) {
|
|
130
|
+
event.preventDefault();
|
|
131
|
+
event.stopPropagation();
|
|
132
|
+
trigger.openMenu();
|
|
133
|
+
return this;
|
|
134
|
+
}
|
|
135
|
+
editMessage(message) {
|
|
136
|
+
this.edit.set(true);
|
|
137
|
+
this.requestEdit.set(message);
|
|
138
|
+
return this;
|
|
139
|
+
}
|
|
140
|
+
deleteMessage(message) {
|
|
141
|
+
this.requestDelete.set(message.id);
|
|
142
|
+
queueMicrotask(() => this.requestDelete.set(null));
|
|
143
|
+
return this;
|
|
144
|
+
}
|
|
145
|
+
canDelete(message) {
|
|
146
|
+
return message.type === this.messageType.Outgoing;
|
|
147
|
+
}
|
|
148
|
+
messageType = MessageType;
|
|
149
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: ChatMessageComponent, deps: [{ token: i1.MatIconRegistry }, { token: i2.DomSanitizer }], target: i0.ɵɵFactoryTarget.Component });
|
|
150
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.7", type: ChatMessageComponent, isStandalone: true, selector: "lib-chat-message", inputs: { currentMessage: { classPropertyName: "currentMessage", publicName: "currentMessage", isSignal: true, isRequired: true, transformFunction: null }, edit: { classPropertyName: "edit", publicName: "edit", isSignal: true, isRequired: false, transformFunction: null }, requestEdit: { classPropertyName: "requestEdit", publicName: "requestEdit", isSignal: true, isRequired: false, transformFunction: null }, requestDelete: { classPropertyName: "requestDelete", publicName: "requestDelete", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { edit: "editChange", requestEdit: "requestEditChange", requestDelete: "requestDeleteChange" }, ngImport: i0, template: "<div class=\"message\"\n [ngClass]=\"{\n 'message--outgoing': currentMessage().type === messageType.Outgoing,\n 'message--incoming': currentMessage().type === messageType.Incoming\n }\">\n <div class=\"message__avatar\">\n <img [ngSrc]=\"avatarSrc()\" width=\"36\" height=\"36\" alt=\"avatar\"/>\n </div>\n\n <div class=\"message__body\"\n #menuTrigger=\"matMenuTrigger\"\n [matMenuTriggerFor]=\"messageMenu\"\n (contextmenu)=\"openContextMenu($event, menuTrigger)\"\n (keydown.shift.F10)=\"openContextMenu($event, menuTrigger)\">\n\n @if (attachments().length) {\n <div class=\"message__attachments\">\n @for (file of attachments(); track file) {\n <img [src]=\"file\"\n alt=\"attachment\"\n class=\"message__image\"\n loading=\"lazy\"/>\n }\n </div>\n }\n\n <div class=\"message__bubble\" tabindex=\"0\">\n <div class=\"message__text\">{{ currentMessage().content }}</div>\n\n <div class=\"message__meta\">\n <time class=\"message__time\">{{ currentMessage().cr_time | date:'HH:mm' }}</time>\n @if (currentMessage().type === messageType.Outgoing) {\n @if (currentMessage().checked) {\n <img ngSrc=\"assets/ngx-parl/icons/checked-message.svg\"\n class=\"message__icon\"\n width=\"18\" height=\"18\" alt=\"hide\" priority/>\n } @else {\n <img ngSrc=\"assets/ngx-parl/icons/no-check\"\n class=\"message__icon\"\n width=\"18\" height=\"18\" alt=\"hide\" priority/>\n }\n }\n </div>\n </div>\n </div>\n</div>\n\n<mat-menu #messageMenu=\"matMenu\" xPosition=\"before\" yPosition=\"below\" class=\"message__menu\">\n @if (currentMessage().type === messageType.Outgoing) {\n <button mat-menu-item (click)=\"editMessage(currentMessage())\">\n <img ngSrc=\"assets/ngx-parl/icons/icon-edit\"\n class=\"message__icon\"\n width=\"18\" height=\"18\" alt=\"hide\" priority/>\n <span>{{ 'chat.edit' | transloco }}</span>\n </button>\n }\n @if (canDelete(currentMessage())) {\n <button mat-menu-item (click)=\"deleteMessage(currentMessage())\">\n <img ngSrc=\"assets/ngx-parl/icons/trash\"\n class=\"message__icon\"\n width=\"18\" height=\"18\" alt=\"hide\" priority/>\n <span>{{ 'chat.remove' | transloco }}</span>\n </button>\n }\n</mat-menu>\n", styles: [".flow-theme-primary ::ng-deep .modal-chat__header{background-color:#5a72d7;color:#fff}.flow-theme-primary ::ng-deep .message--incoming .message__body{background:#e9ecef;color:#343a40}.flow-theme-primary ::ng-deep .message--outgoing .message__body{background:#4656ca;color:#fff}:root{--shadow-sm: 0px 1px 2px 0px rgba(0, 0, 0, .05);--shadow-md: 0px 4px 12px rgba(0, 0, 0, .08);--shadow-lg: 0px 8px 24px rgba(0, 0, 0, .12);--shadow-soft: 0px 0px 6px 0px rgba(46, 46, 46, .12) }.flow-theme-secondary ::ng-deep .modal-chat__header{background-color:#848af5;color:#fefefe}.flow-theme-secondary ::ng-deep .message--incoming .message__body{background:#848af5;color:#fefefe}.flow-theme-secondary ::ng-deep .message--outgoing .message__body{background:#efefef;color:#464646}.message{display:flex;gap:8px;align-items:flex-end;max-width:80%;animation:pop .12s ease-out}.message__avatar img{display:block;border-radius:1000px}.message__body{padding:8px 12px;min-width:0;font:400 16px/24px roboto,sans-serif;border-radius:16px}.message__bubble{display:grid;grid-template-columns:1fr auto;column-gap:8px;align-items:end;max-width:100%;word-break:break-word;white-space:pre-wrap}.message__text{min-width:0}.message__meta{display:inline-flex;align-items:center;gap:4px;-webkit-user-select:none;user-select:none;line-height:1;margin:0}.message__time{font:12px/14px roboto,sans-serif}.message__attachments{display:grid;gap:8px;justify-content:start;width:100%;margin-bottom:8px}.message__attachments:has(:only-child){grid-template-columns:repeat(1,minmax(0,140px))}.message__attachments:has(:nth-child(2):last-child){grid-template-columns:repeat(2,minmax(0,140px))}.message__attachments:has(:nth-child(3)){grid-template-columns:repeat(3,minmax(0,140px))}.message__image{display:block;width:100%;height:140px;object-fit:cover;border-radius:12px;aspect-ratio:1/1}.message__bubble:after{content:\"\";display:block;clear:both}.message__time{font:12px/14px roboto,sans-serif;min-width:30px}.message__icon{width:14px;height:14px;vertical-align:middle}.message--incoming{margin-right:auto}.message--incoming .message__body{border-bottom-right-radius:16px}.message--incoming .message__bubble{border-top-left-radius:4px}.message--incoming .message__time{color:#adb5bd}.message--outgoing{margin-left:auto;flex-direction:row-reverse}.message--outgoing .message__body{border-bottom-left-radius:16px}.message--outgoing .message__bubble{border-top-right-radius:4px}.message--outgoing .message__time{color:#e9ecef}@keyframes pop{0%{transform:translateY(2px);opacity:0}to{transform:translateY(0);opacity:1}}::ng-deep .mat-mdc-menu-content{background:#fff;width:160px;border-radius:8px;gap:4px;border:1px solid #E1E7F8;padding:8px 4px;box-shadow:0 2px 4px #0000001a;overflow:hidden;font:14px/18px roboto,sans-serif}::ng-deep .mat-mdc-menu-item-text{font:14px/18px roboto,sans-serif!important;font-weight:500!important;color:#343a40!important}\n"], dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: NgOptimizedImage, selector: "img[ngSrc]", inputs: ["ngSrc", "ngSrcset", "sizes", "width", "height", "decoding", "loading", "priority", "loaderParams", "disableOptimizedSrcset", "fill", "placeholder", "placeholderConfig", "src", "srcset"] }, { kind: "component", type: MatMenu, selector: "mat-menu", inputs: ["backdropClass", "aria-label", "aria-labelledby", "aria-describedby", "xPosition", "yPosition", "overlapTrigger", "hasBackdrop", "class", "classList"], outputs: ["closed", "close"], exportAs: ["matMenu"] }, { kind: "component", type: MatMenuItem, selector: "[mat-menu-item]", inputs: ["role", "disabled", "disableRipple"], exportAs: ["matMenuItem"] }, { kind: "directive", type: MatMenuTrigger, selector: "[mat-menu-trigger-for], [matMenuTriggerFor]", inputs: ["mat-menu-trigger-for", "matMenuTriggerFor", "matMenuTriggerData", "matMenuTriggerRestoreFocus"], outputs: ["menuOpened", "onMenuOpen", "menuClosed", "onMenuClose"], exportAs: ["matMenuTrigger"] }, { kind: "pipe", type: DatePipe, name: "date" }, { kind: "pipe", type: TranslocoPipe, name: "transloco" }] });
|
|
151
|
+
}
|
|
152
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: ChatMessageComponent, decorators: [{
|
|
153
|
+
type: Component,
|
|
154
|
+
args: [{ selector: 'lib-chat-message', imports: [
|
|
155
|
+
NgClass,
|
|
156
|
+
NgOptimizedImage,
|
|
157
|
+
MatIcon,
|
|
158
|
+
DatePipe,
|
|
159
|
+
MatMenu,
|
|
160
|
+
MatMenuItem,
|
|
161
|
+
MatMenuTrigger,
|
|
162
|
+
TranslocoPipe,
|
|
163
|
+
], standalone: true, template: "<div class=\"message\"\n [ngClass]=\"{\n 'message--outgoing': currentMessage().type === messageType.Outgoing,\n 'message--incoming': currentMessage().type === messageType.Incoming\n }\">\n <div class=\"message__avatar\">\n <img [ngSrc]=\"avatarSrc()\" width=\"36\" height=\"36\" alt=\"avatar\"/>\n </div>\n\n <div class=\"message__body\"\n #menuTrigger=\"matMenuTrigger\"\n [matMenuTriggerFor]=\"messageMenu\"\n (contextmenu)=\"openContextMenu($event, menuTrigger)\"\n (keydown.shift.F10)=\"openContextMenu($event, menuTrigger)\">\n\n @if (attachments().length) {\n <div class=\"message__attachments\">\n @for (file of attachments(); track file) {\n <img [src]=\"file\"\n alt=\"attachment\"\n class=\"message__image\"\n loading=\"lazy\"/>\n }\n </div>\n }\n\n <div class=\"message__bubble\" tabindex=\"0\">\n <div class=\"message__text\">{{ currentMessage().content }}</div>\n\n <div class=\"message__meta\">\n <time class=\"message__time\">{{ currentMessage().cr_time | date:'HH:mm' }}</time>\n @if (currentMessage().type === messageType.Outgoing) {\n @if (currentMessage().checked) {\n <img ngSrc=\"assets/ngx-parl/icons/checked-message.svg\"\n class=\"message__icon\"\n width=\"18\" height=\"18\" alt=\"hide\" priority/>\n } @else {\n <img ngSrc=\"assets/ngx-parl/icons/no-check\"\n class=\"message__icon\"\n width=\"18\" height=\"18\" alt=\"hide\" priority/>\n }\n }\n </div>\n </div>\n </div>\n</div>\n\n<mat-menu #messageMenu=\"matMenu\" xPosition=\"before\" yPosition=\"below\" class=\"message__menu\">\n @if (currentMessage().type === messageType.Outgoing) {\n <button mat-menu-item (click)=\"editMessage(currentMessage())\">\n <img ngSrc=\"assets/ngx-parl/icons/icon-edit\"\n class=\"message__icon\"\n width=\"18\" height=\"18\" alt=\"hide\" priority/>\n <span>{{ 'chat.edit' | transloco }}</span>\n </button>\n }\n @if (canDelete(currentMessage())) {\n <button mat-menu-item (click)=\"deleteMessage(currentMessage())\">\n <img ngSrc=\"assets/ngx-parl/icons/trash\"\n class=\"message__icon\"\n width=\"18\" height=\"18\" alt=\"hide\" priority/>\n <span>{{ 'chat.remove' | transloco }}</span>\n </button>\n }\n</mat-menu>\n", styles: [".flow-theme-primary ::ng-deep .modal-chat__header{background-color:#5a72d7;color:#fff}.flow-theme-primary ::ng-deep .message--incoming .message__body{background:#e9ecef;color:#343a40}.flow-theme-primary ::ng-deep .message--outgoing .message__body{background:#4656ca;color:#fff}:root{--shadow-sm: 0px 1px 2px 0px rgba(0, 0, 0, .05);--shadow-md: 0px 4px 12px rgba(0, 0, 0, .08);--shadow-lg: 0px 8px 24px rgba(0, 0, 0, .12);--shadow-soft: 0px 0px 6px 0px rgba(46, 46, 46, .12) }.flow-theme-secondary ::ng-deep .modal-chat__header{background-color:#848af5;color:#fefefe}.flow-theme-secondary ::ng-deep .message--incoming .message__body{background:#848af5;color:#fefefe}.flow-theme-secondary ::ng-deep .message--outgoing .message__body{background:#efefef;color:#464646}.message{display:flex;gap:8px;align-items:flex-end;max-width:80%;animation:pop .12s ease-out}.message__avatar img{display:block;border-radius:1000px}.message__body{padding:8px 12px;min-width:0;font:400 16px/24px roboto,sans-serif;border-radius:16px}.message__bubble{display:grid;grid-template-columns:1fr auto;column-gap:8px;align-items:end;max-width:100%;word-break:break-word;white-space:pre-wrap}.message__text{min-width:0}.message__meta{display:inline-flex;align-items:center;gap:4px;-webkit-user-select:none;user-select:none;line-height:1;margin:0}.message__time{font:12px/14px roboto,sans-serif}.message__attachments{display:grid;gap:8px;justify-content:start;width:100%;margin-bottom:8px}.message__attachments:has(:only-child){grid-template-columns:repeat(1,minmax(0,140px))}.message__attachments:has(:nth-child(2):last-child){grid-template-columns:repeat(2,minmax(0,140px))}.message__attachments:has(:nth-child(3)){grid-template-columns:repeat(3,minmax(0,140px))}.message__image{display:block;width:100%;height:140px;object-fit:cover;border-radius:12px;aspect-ratio:1/1}.message__bubble:after{content:\"\";display:block;clear:both}.message__time{font:12px/14px roboto,sans-serif;min-width:30px}.message__icon{width:14px;height:14px;vertical-align:middle}.message--incoming{margin-right:auto}.message--incoming .message__body{border-bottom-right-radius:16px}.message--incoming .message__bubble{border-top-left-radius:4px}.message--incoming .message__time{color:#adb5bd}.message--outgoing{margin-left:auto;flex-direction:row-reverse}.message--outgoing .message__body{border-bottom-left-radius:16px}.message--outgoing .message__bubble{border-top-right-radius:4px}.message--outgoing .message__time{color:#e9ecef}@keyframes pop{0%{transform:translateY(2px);opacity:0}to{transform:translateY(0);opacity:1}}::ng-deep .mat-mdc-menu-content{background:#fff;width:160px;border-radius:8px;gap:4px;border:1px solid #E1E7F8;padding:8px 4px;box-shadow:0 2px 4px #0000001a;overflow:hidden;font:14px/18px roboto,sans-serif}::ng-deep .mat-mdc-menu-item-text{font:14px/18px roboto,sans-serif!important;font-weight:500!important;color:#343a40!important}\n"] }]
|
|
164
|
+
}], ctorParameters: () => [{ type: i1.MatIconRegistry }, { type: i2.DomSanitizer }], propDecorators: { currentMessage: [{ type: i0.Input, args: [{ isSignal: true, alias: "currentMessage", required: true }] }], edit: [{ type: i0.Input, args: [{ isSignal: true, alias: "edit", required: false }] }, { type: i0.Output, args: ["editChange"] }], requestEdit: [{ type: i0.Input, args: [{ isSignal: true, alias: "requestEdit", required: false }] }, { type: i0.Output, args: ["requestEditChange"] }], requestDelete: [{ type: i0.Input, args: [{ isSignal: true, alias: "requestDelete", required: false }] }, { type: i0.Output, args: ["requestDeleteChange"] }] } });
|
|
165
|
+
|
|
166
|
+
class UtilsService {
|
|
167
|
+
http;
|
|
168
|
+
constructor(http) {
|
|
169
|
+
this.http = http;
|
|
170
|
+
}
|
|
171
|
+
langToLocale(lang) {
|
|
172
|
+
switch (lang) {
|
|
173
|
+
case 'uk':
|
|
174
|
+
return 'uk-UA';
|
|
175
|
+
case 'en':
|
|
176
|
+
default:
|
|
177
|
+
return 'en-US';
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
getLocalISODate() {
|
|
181
|
+
const d = new Date();
|
|
182
|
+
const year = d.getFullYear();
|
|
183
|
+
const month = String(d.getMonth() + 1).padStart(2, '0');
|
|
184
|
+
const day = String(d.getDate()).padStart(2, '0');
|
|
185
|
+
const hours = String(d.getHours()).padStart(2, '0');
|
|
186
|
+
const minutes = String(d.getMinutes()).padStart(2, '0');
|
|
187
|
+
const seconds = String(d.getSeconds()).padStart(2, '0');
|
|
188
|
+
return `${year}-${month}-${day}T${hours}:${minutes}:${seconds}`;
|
|
189
|
+
}
|
|
190
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: UtilsService, deps: [{ token: i1$1.HttpClient }], target: i0.ɵɵFactoryTarget.Injectable });
|
|
191
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: UtilsService, providedIn: 'root' });
|
|
192
|
+
}
|
|
193
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: UtilsService, decorators: [{
|
|
194
|
+
type: Injectable,
|
|
195
|
+
args: [{
|
|
196
|
+
providedIn: 'root'
|
|
197
|
+
}]
|
|
198
|
+
}], ctorParameters: () => [{ type: i1$1.HttpClient }] });
|
|
199
|
+
|
|
200
|
+
class ChatStartDayPipe {
|
|
201
|
+
utils;
|
|
202
|
+
transloco;
|
|
203
|
+
constructor(utils, transloco) {
|
|
204
|
+
this.utils = utils;
|
|
205
|
+
this.transloco = transloco;
|
|
206
|
+
}
|
|
207
|
+
transform(value, format = 'd MMMM') {
|
|
208
|
+
if (!value) {
|
|
209
|
+
return '';
|
|
210
|
+
}
|
|
211
|
+
const locale = this.utils.langToLocale(this.transloco.getActiveLang());
|
|
212
|
+
const datePipe = new DatePipe(locale);
|
|
213
|
+
const valueDate = new Date(value);
|
|
214
|
+
const today = new Date();
|
|
215
|
+
const isToday = datePipe.transform(valueDate, 'shortDate') === datePipe.transform(today, 'shortDate');
|
|
216
|
+
return isToday
|
|
217
|
+
? (locale.startsWith('uk') ? 'Сьогодні' : 'Today')
|
|
218
|
+
: (datePipe.transform(valueDate, format) ?? '');
|
|
219
|
+
}
|
|
220
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: ChatStartDayPipe, deps: [{ token: UtilsService }, { token: i2$1.TranslocoService }], target: i0.ɵɵFactoryTarget.Pipe });
|
|
221
|
+
static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "20.3.7", ngImport: i0, type: ChatStartDayPipe, isStandalone: true, name: "chatStartDay" });
|
|
222
|
+
}
|
|
223
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: ChatStartDayPipe, decorators: [{
|
|
224
|
+
type: Pipe,
|
|
225
|
+
args: [{
|
|
226
|
+
name: 'chatStartDay'
|
|
227
|
+
}]
|
|
228
|
+
}], ctorParameters: () => [{ type: UtilsService }, { type: i2$1.TranslocoService }] });
|
|
229
|
+
|
|
230
|
+
class ToggleDisplayChatStartDayPipe {
|
|
231
|
+
utils;
|
|
232
|
+
transloco;
|
|
233
|
+
constructor(utils, transloco) {
|
|
234
|
+
this.utils = utils;
|
|
235
|
+
this.transloco = transloco;
|
|
236
|
+
}
|
|
237
|
+
transform(message, messages, i) {
|
|
238
|
+
const locale = this.utils.langToLocale(this.transloco.getActiveLang());
|
|
239
|
+
const datePipe = new DatePipe(locale);
|
|
240
|
+
const prev = i > 0 ? messages[i - 1] : undefined;
|
|
241
|
+
const currDay = datePipe.transform(new Date(message.cr_time), 'shortDate');
|
|
242
|
+
const prevDay = prev ? datePipe.transform(new Date(prev.cr_time), 'shortDate') : undefined;
|
|
243
|
+
return prev ? currDay !== prevDay : true;
|
|
244
|
+
}
|
|
245
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: ToggleDisplayChatStartDayPipe, deps: [{ token: UtilsService }, { token: i2$1.TranslocoService }], target: i0.ɵɵFactoryTarget.Pipe });
|
|
246
|
+
static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "20.3.7", ngImport: i0, type: ToggleDisplayChatStartDayPipe, isStandalone: true, name: "toggleDisplayChatStartDay" });
|
|
247
|
+
}
|
|
248
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: ToggleDisplayChatStartDayPipe, decorators: [{
|
|
249
|
+
type: Pipe,
|
|
250
|
+
args: [{
|
|
251
|
+
name: 'toggleDisplayChatStartDay'
|
|
252
|
+
}]
|
|
253
|
+
}], ctorParameters: () => [{ type: UtilsService }, { type: i2$1.TranslocoService }] });
|
|
254
|
+
|
|
255
|
+
class ChatFlowComponent {
|
|
256
|
+
iconRegistry;
|
|
257
|
+
sanitizer;
|
|
258
|
+
flowRef;
|
|
259
|
+
messageListInput = model.required(...(ngDevMode ? [{ debugName: "messageListInput" }] : []));
|
|
260
|
+
messageList = computed(() => this.messageListInput(), ...(ngDevMode ? [{ debugName: "messageList" }] : []));
|
|
261
|
+
selectedForEdit = model.required(...(ngDevMode ? [{ debugName: "selectedForEdit" }] : []));
|
|
262
|
+
constructor(iconRegistry, sanitizer) {
|
|
263
|
+
this.iconRegistry = iconRegistry;
|
|
264
|
+
this.sanitizer = sanitizer;
|
|
265
|
+
this.iconRegistry.addSvgIcon('lucide_send', this.sanitizer.bypassSecurityTrustResourceUrl('assets/ngx-parl/icons/lucide_send.svg'));
|
|
266
|
+
effect(() => {
|
|
267
|
+
const length = this.messageList().length;
|
|
268
|
+
if (length > 0) {
|
|
269
|
+
queueMicrotask(() => this.scrollToBottomSmooth());
|
|
270
|
+
}
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
scrollToBottomSmooth() {
|
|
274
|
+
const element = this.flowRef?.nativeElement;
|
|
275
|
+
if (!element) {
|
|
276
|
+
return this;
|
|
277
|
+
}
|
|
278
|
+
element.scrollTo({ top: element.scrollHeight, behavior: 'smooth' });
|
|
279
|
+
return this;
|
|
280
|
+
}
|
|
281
|
+
startEdit(message) {
|
|
282
|
+
this.messageList().forEach(currMessage => {
|
|
283
|
+
if (currMessage.id !== message.id && currMessage.edit) {
|
|
284
|
+
currMessage.edit = false;
|
|
285
|
+
}
|
|
286
|
+
});
|
|
287
|
+
message.edit = true;
|
|
288
|
+
if (this.selectedForEdit()?.id === message.id) {
|
|
289
|
+
this.selectedForEdit.set(null);
|
|
290
|
+
queueMicrotask(() => this.selectedForEdit.set(message));
|
|
291
|
+
}
|
|
292
|
+
else {
|
|
293
|
+
this.selectedForEdit.set(message);
|
|
294
|
+
}
|
|
295
|
+
return this;
|
|
296
|
+
}
|
|
297
|
+
onEditChange(id, isEdit) {
|
|
298
|
+
const messageList = this.messageList().find(message => message.id === id);
|
|
299
|
+
if (!messageList) {
|
|
300
|
+
return this;
|
|
301
|
+
}
|
|
302
|
+
if (isEdit) {
|
|
303
|
+
return this.startEdit(messageList);
|
|
304
|
+
}
|
|
305
|
+
else {
|
|
306
|
+
messageList.edit = false;
|
|
307
|
+
if (this.selectedForEdit()?.id === id) {
|
|
308
|
+
this.selectedForEdit.set(null);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
return this;
|
|
312
|
+
}
|
|
313
|
+
onRequestEdit(message) {
|
|
314
|
+
if (message) {
|
|
315
|
+
return this.startEdit(message);
|
|
316
|
+
}
|
|
317
|
+
this.selectedForEdit.set(null);
|
|
318
|
+
return this;
|
|
319
|
+
}
|
|
320
|
+
onRequestDelete(messageId) {
|
|
321
|
+
if (!messageId) {
|
|
322
|
+
return this;
|
|
323
|
+
}
|
|
324
|
+
const updatedList = this.messageList().filter(m => m.id !== messageId);
|
|
325
|
+
this.selectedForEdit.set(null);
|
|
326
|
+
queueMicrotask(() => this.messageListInput.set(updatedList));
|
|
327
|
+
return this;
|
|
328
|
+
}
|
|
329
|
+
trackByMessageId(_index, message) {
|
|
330
|
+
// return message.id;
|
|
331
|
+
return `${message.chat_id}-${message.type}-${message.id}`;
|
|
332
|
+
}
|
|
333
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: ChatFlowComponent, deps: [{ token: i1.MatIconRegistry }, { token: i2.DomSanitizer }], target: i0.ɵɵFactoryTarget.Component });
|
|
334
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.7", type: ChatFlowComponent, isStandalone: true, selector: "app-chat-flow", inputs: { messageListInput: { classPropertyName: "messageListInput", publicName: "messageListInput", isSignal: true, isRequired: true, transformFunction: null }, selectedForEdit: { classPropertyName: "selectedForEdit", publicName: "selectedForEdit", isSignal: true, isRequired: true, transformFunction: null } }, outputs: { messageListInput: "messageListInputChange", selectedForEdit: "selectedForEditChange" }, viewQueries: [{ propertyName: "flowRef", first: true, predicate: ["chatFlowRef"], descendants: true, static: true }], ngImport: i0, template: "<div class=\"chat\">\n @if (messageList()) {\n <div class=\"chat__flow\" #chatFlowRef>\n @for (message of messageList(); track trackByMessageId(i, message); let i = $index) {\n @if (message | toggleDisplayChatStartDay: messageList() : i) {\n <span class=\"chat__start-day\">\n {{ message.cr_time | chatStartDay:'d MMMM' }}\n </span>\n }\n\n <lib-chat-message [currentMessage]=\"message\"\n [edit]=\"message.edit ?? false\"\n (editChange)=\"onEditChange(message.id, $event)\"\n (requestEditChange)=\"onRequestEdit($event)\"\n (requestDeleteChange)=\"onRequestDelete($event)\">\n </lib-chat-message>\n }\n </div>\n } @else {\n <div class=\"chat__flow__empty\">\n <img ngSrc=\"assets/ngx-parl/icons/lucide_send.svg\"\n class=\"message__icon\"\n width=\"18\" height=\"24\" alt=\"hide\" priority/>\n\n\n <div class=\"chat__flow__empty--header\">\n <h3 class=\"chat__flow__empty--title\">{{ 'chat.chat_empty_title' | transloco }}</h3>\n <p class=\"chat__flow__empty--subscription\">{{ 'chat.chat_empty_subscription' | transloco }}</p>\n </div>\n </div>\n }\n</div>\n", styles: [":host{display:block;height:100%;min-height:0}.chat{background:#fff;height:100%;display:flex;flex-direction:column;min-height:0;width:100%}.chat__flow{flex:1 1 auto;min-height:0;overflow:auto;display:flex;flex-direction:column;gap:12px;padding:12px 16px;scroll-behavior:smooth;overscroll-behavior:contain}.chat__flow__empty{display:flex;gap:16px;flex-direction:column;align-items:center;justify-content:center;padding:72px 16px;height:100%}.chat__flow__empty .message__icon{width:56px;height:56px}.chat__flow__empty--header{width:285px;display:flex;gap:12px;flex-direction:column;align-items:center}.chat__flow__empty--title{margin:0;font:500 16px/24px roboto,sans-serif;color:#212529}.chat__flow__empty--subscription{margin:0;font:400 16px/24px roboto,sans-serif;color:#495057}.chat__start-day{display:flex;justify-content:center;background:#f8f9fa;color:#6c757d;font:12px/14px roboto,sans-serif;width:78px;height:22px;margin:0 auto;border-radius:1000px;gap:8px;padding:4px 12px}.chat ::-webkit-scrollbar{width:6px}.chat ::-webkit-scrollbar-track{background:transparent}.chat ::-webkit-scrollbar-thumb{background-color:#e9ecef;border-radius:4px;border:1px solid #E9ECEF}.chat ::-webkit-scrollbar-button{display:none}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "component", type: ChatMessageComponent, selector: "lib-chat-message", inputs: ["currentMessage", "edit", "requestEdit", "requestDelete"], outputs: ["editChange", "requestEditChange", "requestDeleteChange"] }, { kind: "directive", type: NgOptimizedImage, selector: "img[ngSrc]", inputs: ["ngSrc", "ngSrcset", "sizes", "width", "height", "decoding", "loading", "priority", "loaderParams", "disableOptimizedSrcset", "fill", "placeholder", "placeholderConfig", "src", "srcset"] }, { kind: "pipe", type: ChatStartDayPipe, name: "chatStartDay" }, { kind: "pipe", type: ToggleDisplayChatStartDayPipe, name: "toggleDisplayChatStartDay" }, { kind: "pipe", type: TranslocoPipe, name: "transloco" }] });
|
|
335
|
+
}
|
|
336
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: ChatFlowComponent, decorators: [{
|
|
337
|
+
type: Component,
|
|
338
|
+
args: [{ selector: 'app-chat-flow', imports: [
|
|
339
|
+
FormsModule,
|
|
340
|
+
ChatMessageComponent,
|
|
341
|
+
ChatStartDayPipe,
|
|
342
|
+
ToggleDisplayChatStartDayPipe,
|
|
343
|
+
ChatMessageComponent,
|
|
344
|
+
TranslocoPipe,
|
|
345
|
+
MatIcon,
|
|
346
|
+
NgOptimizedImage
|
|
347
|
+
], standalone: true, template: "<div class=\"chat\">\n @if (messageList()) {\n <div class=\"chat__flow\" #chatFlowRef>\n @for (message of messageList(); track trackByMessageId(i, message); let i = $index) {\n @if (message | toggleDisplayChatStartDay: messageList() : i) {\n <span class=\"chat__start-day\">\n {{ message.cr_time | chatStartDay:'d MMMM' }}\n </span>\n }\n\n <lib-chat-message [currentMessage]=\"message\"\n [edit]=\"message.edit ?? false\"\n (editChange)=\"onEditChange(message.id, $event)\"\n (requestEditChange)=\"onRequestEdit($event)\"\n (requestDeleteChange)=\"onRequestDelete($event)\">\n </lib-chat-message>\n }\n </div>\n } @else {\n <div class=\"chat__flow__empty\">\n <img ngSrc=\"assets/ngx-parl/icons/lucide_send.svg\"\n class=\"message__icon\"\n width=\"18\" height=\"24\" alt=\"hide\" priority/>\n\n\n <div class=\"chat__flow__empty--header\">\n <h3 class=\"chat__flow__empty--title\">{{ 'chat.chat_empty_title' | transloco }}</h3>\n <p class=\"chat__flow__empty--subscription\">{{ 'chat.chat_empty_subscription' | transloco }}</p>\n </div>\n </div>\n }\n</div>\n", styles: [":host{display:block;height:100%;min-height:0}.chat{background:#fff;height:100%;display:flex;flex-direction:column;min-height:0;width:100%}.chat__flow{flex:1 1 auto;min-height:0;overflow:auto;display:flex;flex-direction:column;gap:12px;padding:12px 16px;scroll-behavior:smooth;overscroll-behavior:contain}.chat__flow__empty{display:flex;gap:16px;flex-direction:column;align-items:center;justify-content:center;padding:72px 16px;height:100%}.chat__flow__empty .message__icon{width:56px;height:56px}.chat__flow__empty--header{width:285px;display:flex;gap:12px;flex-direction:column;align-items:center}.chat__flow__empty--title{margin:0;font:500 16px/24px roboto,sans-serif;color:#212529}.chat__flow__empty--subscription{margin:0;font:400 16px/24px roboto,sans-serif;color:#495057}.chat__start-day{display:flex;justify-content:center;background:#f8f9fa;color:#6c757d;font:12px/14px roboto,sans-serif;width:78px;height:22px;margin:0 auto;border-radius:1000px;gap:8px;padding:4px 12px}.chat ::-webkit-scrollbar{width:6px}.chat ::-webkit-scrollbar-track{background:transparent}.chat ::-webkit-scrollbar-thumb{background-color:#e9ecef;border-radius:4px;border:1px solid #E9ECEF}.chat ::-webkit-scrollbar-button{display:none}\n"] }]
|
|
348
|
+
}], ctorParameters: () => [{ type: i1.MatIconRegistry }, { type: i2.DomSanitizer }], propDecorators: { flowRef: [{
|
|
349
|
+
type: ViewChild,
|
|
350
|
+
args: ['chatFlowRef', { static: true }]
|
|
351
|
+
}], messageListInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "messageListInput", required: true }] }, { type: i0.Output, args: ["messageListInputChange"] }], selectedForEdit: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectedForEdit", required: true }] }, { type: i0.Output, args: ["selectedForEditChange"] }] } });
|
|
352
|
+
|
|
353
|
+
class ImageFile {
|
|
354
|
+
id;
|
|
355
|
+
url;
|
|
356
|
+
cr_time;
|
|
357
|
+
constructor(id, url, cr_time) {
|
|
358
|
+
this.id = id;
|
|
359
|
+
this.url = url;
|
|
360
|
+
this.cr_time = cr_time;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
var FileType;
|
|
364
|
+
(function (FileType) {
|
|
365
|
+
FileType["IMAGE"] = "image";
|
|
366
|
+
FileType["GIF"] = "gif";
|
|
367
|
+
})(FileType || (FileType = {}));
|
|
368
|
+
|
|
369
|
+
class InputMessageComponent {
|
|
370
|
+
inputTextElement;
|
|
371
|
+
mirrorElement;
|
|
372
|
+
editMessage = input(null, ...(ngDevMode ? [{ debugName: "editMessage" }] : []));
|
|
373
|
+
hasOriginalAttachments = computed(() => {
|
|
374
|
+
const filePaths = this.editFilePaths();
|
|
375
|
+
return filePaths.length > 0;
|
|
376
|
+
}, ...(ngDevMode ? [{ debugName: "hasOriginalAttachments" }] : []));
|
|
377
|
+
hasNewAttachments = computed(() => (this.previews()?.length ?? 0) > 0, ...(ngDevMode ? [{ debugName: "hasNewAttachments" }] : []));
|
|
378
|
+
cancelEdit = model(null, ...(ngDevMode ? [{ debugName: "cancelEdit" }] : []));
|
|
379
|
+
input_text = model('', ...(ngDevMode ? [{ debugName: "input_text" }] : []));
|
|
380
|
+
draft = signal('', ...(ngDevMode ? [{ debugName: "draft" }] : []));
|
|
381
|
+
focused = signal(false, ...(ngDevMode ? [{ debugName: "focused" }] : []));
|
|
382
|
+
sending = signal(false, ...(ngDevMode ? [{ debugName: "sending" }] : []));
|
|
383
|
+
hasText = computed(() => this.draft().trim().length > 0, ...(ngDevMode ? [{ debugName: "hasText" }] : []));
|
|
384
|
+
isEditMode = computed(() => !!this.editMessage(), ...(ngDevMode ? [{ debugName: "isEditMode" }] : []));
|
|
385
|
+
canSend = computed(() => this.hasText() ||
|
|
386
|
+
this.hasNewAttachments() ||
|
|
387
|
+
(this.isEditMode() && this.hasOriginalAttachments()), ...(ngDevMode ? [{ debugName: "canSend" }] : []));
|
|
388
|
+
files = model([], ...(ngDevMode ? [{ debugName: "files" }] : []));
|
|
389
|
+
previews = model([], ...(ngDevMode ? [{ debugName: "previews" }] : []));
|
|
390
|
+
lastHeightPx = 0;
|
|
391
|
+
lastRows = 1;
|
|
392
|
+
resizeRaf = null;
|
|
393
|
+
constructor() {
|
|
394
|
+
effect(() => {
|
|
395
|
+
const message = this.editMessage();
|
|
396
|
+
const element = this.inputTextElement?.nativeElement;
|
|
397
|
+
if (!element) {
|
|
398
|
+
return;
|
|
399
|
+
}
|
|
400
|
+
if (message) {
|
|
401
|
+
const content = message.content ?? '';
|
|
402
|
+
this.draft.set(content);
|
|
403
|
+
element.innerText = content;
|
|
404
|
+
queueMicrotask(() => {
|
|
405
|
+
this.autoResizeByRows();
|
|
406
|
+
element.focus();
|
|
407
|
+
this.focused.set(true);
|
|
408
|
+
});
|
|
409
|
+
}
|
|
410
|
+
});
|
|
411
|
+
}
|
|
412
|
+
ngAfterViewInit() {
|
|
413
|
+
const element = this.inputTextElement.nativeElement;
|
|
414
|
+
element.style.transition = 'height 160ms ease';
|
|
415
|
+
this.initMirror();
|
|
416
|
+
const computedStyle = getComputedStyle(element);
|
|
417
|
+
const lineHeight = this.cssNum(computedStyle.lineHeight, 24);
|
|
418
|
+
element.style.height = `${lineHeight}px`;
|
|
419
|
+
this.lastHeightPx = lineHeight;
|
|
420
|
+
this.lastRows = 1;
|
|
421
|
+
this.updateOverflow(1);
|
|
422
|
+
requestAnimationFrame(() => {
|
|
423
|
+
const { rows, nextHeightPx } = this.measureByMirror();
|
|
424
|
+
element.style.height = `${nextHeightPx}px`;
|
|
425
|
+
this.lastRows = rows;
|
|
426
|
+
this.lastHeightPx = nextHeightPx;
|
|
427
|
+
this.updateOverflow(rows);
|
|
428
|
+
});
|
|
429
|
+
}
|
|
430
|
+
ngOnDestroy() {
|
|
431
|
+
if (this.resizeRaf) {
|
|
432
|
+
cancelAnimationFrame(this.resizeRaf);
|
|
433
|
+
this.resizeRaf = null;
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
editFilePaths() {
|
|
437
|
+
const message = this.editMessage();
|
|
438
|
+
if (!message) {
|
|
439
|
+
return [];
|
|
440
|
+
}
|
|
441
|
+
const file_path = message.file_path;
|
|
442
|
+
return Array.isArray(file_path) ? file_path : [];
|
|
443
|
+
}
|
|
444
|
+
collectAttachmentSources() {
|
|
445
|
+
return (this.previews() ?? []).map(p => p.src).filter(Boolean);
|
|
446
|
+
}
|
|
447
|
+
cancelEditMessage() {
|
|
448
|
+
const message = this.editMessage();
|
|
449
|
+
this.cancelEdit.set(message?.id ?? null);
|
|
450
|
+
queueMicrotask(() => this.cancelEdit.set(null));
|
|
451
|
+
this.draft.set('');
|
|
452
|
+
const element = this.inputTextElement?.nativeElement;
|
|
453
|
+
if (element) {
|
|
454
|
+
element.innerHTML = '';
|
|
455
|
+
this.autoResizeByRows();
|
|
456
|
+
element.focus();
|
|
457
|
+
}
|
|
458
|
+
return this;
|
|
459
|
+
}
|
|
460
|
+
enterDown() {
|
|
461
|
+
const element = this.inputTextElement.nativeElement;
|
|
462
|
+
const text = this.draft().trim();
|
|
463
|
+
if (!this.canSend())
|
|
464
|
+
return this;
|
|
465
|
+
this.sending.set(true);
|
|
466
|
+
const files = this.collectAttachmentSources();
|
|
467
|
+
const message = this.editMessage();
|
|
468
|
+
if (message) {
|
|
469
|
+
this.input_text.set({ id: message.id, content: text, files: files.length ? files : undefined });
|
|
470
|
+
}
|
|
471
|
+
else {
|
|
472
|
+
this.input_text.set({ content: text, files: files.length ? files : undefined });
|
|
473
|
+
}
|
|
474
|
+
this.draft.set('');
|
|
475
|
+
element.innerHTML = '';
|
|
476
|
+
this.files.set([]);
|
|
477
|
+
this.previews.set([]);
|
|
478
|
+
element.focus();
|
|
479
|
+
this.autoResizeByRows();
|
|
480
|
+
setTimeout(() => this.sending.set(false), 150);
|
|
481
|
+
return this;
|
|
482
|
+
}
|
|
483
|
+
onFocus() {
|
|
484
|
+
if (this.inputTextElement.nativeElement.innerHTML === '<br>') {
|
|
485
|
+
this.inputTextElement.nativeElement.innerHTML = '';
|
|
486
|
+
}
|
|
487
|
+
this.focused.set(true);
|
|
488
|
+
return this;
|
|
489
|
+
}
|
|
490
|
+
onBlur() {
|
|
491
|
+
this.focused.set(false);
|
|
492
|
+
return this;
|
|
493
|
+
}
|
|
494
|
+
onKeyDown(event) {
|
|
495
|
+
if (event.key === 'Enter' && !event.shiftKey) {
|
|
496
|
+
event.preventDefault();
|
|
497
|
+
this.enterDown();
|
|
498
|
+
return this;
|
|
499
|
+
}
|
|
500
|
+
queueMicrotask(() => this.autoResizeByRows());
|
|
501
|
+
return this;
|
|
502
|
+
}
|
|
503
|
+
onInput() {
|
|
504
|
+
this.draft.set(this.inputTextElement.nativeElement.innerText ?? '');
|
|
505
|
+
this.autoResizeByRows();
|
|
506
|
+
return this;
|
|
507
|
+
}
|
|
508
|
+
onPaste() {
|
|
509
|
+
queueMicrotask(() => {
|
|
510
|
+
this.draft.set(this.inputTextElement.nativeElement.innerText ?? '');
|
|
511
|
+
this.autoResizeByRows();
|
|
512
|
+
});
|
|
513
|
+
return this;
|
|
514
|
+
}
|
|
515
|
+
inputFileChange(event) {
|
|
516
|
+
const inputEl = event.target;
|
|
517
|
+
const selected = inputEl.files;
|
|
518
|
+
if (!selected?.length) {
|
|
519
|
+
inputEl.value = '';
|
|
520
|
+
return this;
|
|
521
|
+
}
|
|
522
|
+
const list = Array.from(selected).filter(f => (f.type || '').startsWith('image/'));
|
|
523
|
+
if (!list.length) {
|
|
524
|
+
inputEl.value = '';
|
|
525
|
+
return this;
|
|
526
|
+
}
|
|
527
|
+
this.files.set([...(this.files() ?? []), ...list]);
|
|
528
|
+
Promise.all(list.map(async (f) => {
|
|
529
|
+
const src = await this.readFileAsDataURL(f);
|
|
530
|
+
const originalKind = (f.type || '') === 'image/gif' ? FileType.GIF : FileType.IMAGE;
|
|
531
|
+
return { src, originalKind, name: f.name, type: f.type || '', size: f.size };
|
|
532
|
+
}))
|
|
533
|
+
.then(items => this.previews.set([...(this.previews() ?? []), ...items]))
|
|
534
|
+
.finally(() => (inputEl.value = ''));
|
|
535
|
+
return this;
|
|
536
|
+
}
|
|
537
|
+
removeFile(index, event) {
|
|
538
|
+
event?.stopPropagation();
|
|
539
|
+
event?.preventDefault();
|
|
540
|
+
const previews = [...(this.previews() ?? [])];
|
|
541
|
+
previews.splice(index, 1);
|
|
542
|
+
this.previews.set(previews);
|
|
543
|
+
const filesArr = [...(this.files() ?? [])];
|
|
544
|
+
if (index >= 0 && index < filesArr.length) {
|
|
545
|
+
filesArr.splice(index, 1);
|
|
546
|
+
this.files.set(filesArr);
|
|
547
|
+
}
|
|
548
|
+
return this;
|
|
549
|
+
}
|
|
550
|
+
openPreview(_item, _index) {
|
|
551
|
+
return this;
|
|
552
|
+
}
|
|
553
|
+
autoResizeByRows() {
|
|
554
|
+
const element = this.inputTextElement.nativeElement;
|
|
555
|
+
const { rows, nextHeightPx } = this.measureByMirror();
|
|
556
|
+
if (rows === this.lastRows) {
|
|
557
|
+
this.updateOverflow(rows);
|
|
558
|
+
return this;
|
|
559
|
+
}
|
|
560
|
+
if (this.resizeRaf) {
|
|
561
|
+
cancelAnimationFrame(this.resizeRaf);
|
|
562
|
+
}
|
|
563
|
+
element.style.height = `${this.lastHeightPx}px`;
|
|
564
|
+
this.resizeRaf = requestAnimationFrame(() => {
|
|
565
|
+
element.style.height = `${nextHeightPx}px`;
|
|
566
|
+
this.lastHeightPx = nextHeightPx;
|
|
567
|
+
this.lastRows = rows;
|
|
568
|
+
this.updateOverflow(rows);
|
|
569
|
+
});
|
|
570
|
+
return this;
|
|
571
|
+
}
|
|
572
|
+
measureByMirror() {
|
|
573
|
+
const inputEl = this.inputTextElement.nativeElement;
|
|
574
|
+
const mirrorEl = this.mirrorElement.nativeElement;
|
|
575
|
+
const computedStyle = getComputedStyle(inputEl);
|
|
576
|
+
let text = inputEl.innerText;
|
|
577
|
+
if (!text || text === '\n') {
|
|
578
|
+
text = '\u00A0';
|
|
579
|
+
}
|
|
580
|
+
mirrorEl.style.width = computedStyle.width;
|
|
581
|
+
mirrorEl.textContent = text;
|
|
582
|
+
const lineHeight = this.cssNum(computedStyle.lineHeight, 24);
|
|
583
|
+
const paddingTop = this.cssNum(computedStyle.paddingTop, 0);
|
|
584
|
+
const paddingBottom = this.cssNum(computedStyle.paddingBottom, 0);
|
|
585
|
+
const paddingY = paddingTop + paddingBottom;
|
|
586
|
+
const maxRowsCss = computedStyle.getPropertyValue('--max-rows').trim();
|
|
587
|
+
const maxRows = maxRowsCss ? this.cssNum(maxRowsCss, 8) : 8;
|
|
588
|
+
const contentH = mirrorEl.offsetHeight;
|
|
589
|
+
const rawRows = Math.max(1, Math.ceil(contentH / lineHeight));
|
|
590
|
+
const rows = Math.min(rawRows, maxRows);
|
|
591
|
+
const nextHeightPx = Math.round(rows * lineHeight + paddingY);
|
|
592
|
+
return { rows, nextHeightPx };
|
|
593
|
+
}
|
|
594
|
+
initMirror() {
|
|
595
|
+
const mirror = this.mirrorElement.nativeElement;
|
|
596
|
+
const input = this.inputTextElement.nativeElement;
|
|
597
|
+
const computedStyle = getComputedStyle(input);
|
|
598
|
+
mirror.style.position = 'absolute';
|
|
599
|
+
mirror.style.visibility = 'hidden';
|
|
600
|
+
mirror.style.pointerEvents = 'none';
|
|
601
|
+
mirror.style.zIndex = '-1';
|
|
602
|
+
mirror.style.whiteSpace = 'pre-wrap';
|
|
603
|
+
mirror.style.overflowWrap = 'break-word';
|
|
604
|
+
mirror.style.wordBreak = 'normal';
|
|
605
|
+
const properties = [
|
|
606
|
+
'font', 'font-size', 'font-family', 'font-weight', 'font-style',
|
|
607
|
+
'line-height', 'letter-spacing', 'word-spacing',
|
|
608
|
+
'padding-top', 'padding-bottom', 'padding-left', 'padding-right',
|
|
609
|
+
'border-top-width', 'border-bottom-width', 'border-left-width', 'border-right-width',
|
|
610
|
+
'white-space', 'text-transform', 'box-sizing'
|
|
611
|
+
];
|
|
612
|
+
properties.forEach(property => mirror.style[property] = computedStyle.getPropertyValue(property));
|
|
613
|
+
mirror.style.paddingTop = '0px';
|
|
614
|
+
mirror.style.paddingBottom = '0px';
|
|
615
|
+
}
|
|
616
|
+
updateOverflow(rows) {
|
|
617
|
+
const element = this.inputTextElement.nativeElement;
|
|
618
|
+
const computedStyle = getComputedStyle(element);
|
|
619
|
+
const maxRowsCss = computedStyle.getPropertyValue('--max-rows').trim();
|
|
620
|
+
const maxRows = maxRowsCss ? this.cssNum(maxRowsCss, 8) : 8;
|
|
621
|
+
element.style.overflowY = rows >= maxRows ? 'auto' : 'hidden';
|
|
622
|
+
return this;
|
|
623
|
+
}
|
|
624
|
+
readFileAsDataURL(file) {
|
|
625
|
+
return new Promise((resolve, reject) => {
|
|
626
|
+
const reader = new FileReader();
|
|
627
|
+
reader.onload = e => resolve(e.target?.result || '');
|
|
628
|
+
reader.onerror = reject;
|
|
629
|
+
reader.readAsDataURL(file);
|
|
630
|
+
});
|
|
631
|
+
}
|
|
632
|
+
cssNum(v, fb = 0) {
|
|
633
|
+
const n = parseFloat(v);
|
|
634
|
+
return Number.isFinite(n) ? n : fb;
|
|
635
|
+
}
|
|
636
|
+
FileType = FileType;
|
|
637
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: InputMessageComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
638
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.7", type: InputMessageComponent, isStandalone: true, selector: "app-input-message", inputs: { editMessage: { classPropertyName: "editMessage", publicName: "editMessage", isSignal: true, isRequired: false, transformFunction: null }, cancelEdit: { classPropertyName: "cancelEdit", publicName: "cancelEdit", isSignal: true, isRequired: false, transformFunction: null }, input_text: { classPropertyName: "input_text", publicName: "input_text", isSignal: true, isRequired: false, transformFunction: null }, files: { classPropertyName: "files", publicName: "files", isSignal: true, isRequired: false, transformFunction: null }, previews: { classPropertyName: "previews", publicName: "previews", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { cancelEdit: "cancelEditChange", input_text: "input_textChange", files: "filesChange", previews: "previewsChange" }, viewQueries: [{ propertyName: "inputTextElement", first: true, predicate: ["inputText"], descendants: true }, { propertyName: "mirrorElement", first: true, predicate: ["mirror"], descendants: true }], ngImport: i0, template: "<section class=\"message\"\n [class.message--focus]=\"focused()\"\n [class.message--filled]=\"hasText()\"\n [class.message--sending]=\"sending()\">\n\n @if (isEditMode()) {\n <div class=\"message__wrap\">\n <div class=\"message__wrap--input\">\n <button class=\"message__button message__button--attach\">\n <img ngSrc=\"assets/ngx-parl/icons/icon-edit.svg\"\n class=\"message__icon message__icon--edit\"\n width=\"18\" height=\"18\" alt=\"hide\" priority/>\n </button>\n\n <div class=\"message__edit-hint\">\n <p>{{ editMessage()?.content }}</p>\n </div>\n </div>\n\n <button class=\"message__button message__button--close\"\n type=\"button\"\n (click)=\"cancelEditMessage()\">\n <img ngSrc=\"assets/ngx-parl/icons/close.svg\"\n class=\"message__icon message__icon--close\"\n width=\"18\" height=\"18\" alt=\"hide\" priority/>\n </button>\n </div>\n }\n\n <div class=\"message__wrap\">\n <div class=\"message__wrap--input\">\n <button type=\"button\"\n class=\"message__button message__button--attach\"\n [attr.aria-label]=\"'chat.attach' | transloco\"\n (click)=\"fileDialog.click()\">\n <img ngSrc=\"assets/ngx-parl/icons/attach-filled.svg\"\n class=\"message__icon message__icon--attach\"\n width=\"18\" height=\"18\" alt=\"hide\" priority/>\n </button>\n\n <input #fileDialog\n type=\"file\"\n class=\"hidden\"\n accept=\"image/*\"\n multiple\n (change)=\"inputFileChange($event)\">\n\n <div #inputText\n role=\"textbox\"\n class=\"message__input\"\n spellcheck=\"true\"\n contenteditable=\"true\"\n aria-multiline=\"true\"\n [attr.data-placeholder]=\"isEditMode()\n ? ('chat.edit_placeholder' | transloco)\n : ('chat.placeholder' | transloco)\"\n (blur)=\"onBlur()\"\n (focus)=\"onFocus()\"\n (input)=\"onInput()\"\n (paste)=\"onPaste()\"\n (keydown)=\"onKeyDown($event)\">\n </div>\n\n <div #mirror class=\"message__input-mirror\" aria-hidden=\"true\"></div>\n\n <ng-content></ng-content>\n </div>\n\n <button class=\"message__button message__button--send\"\n type=\"button\"\n (click)=\"enterDown()\"\n [attr.aria-label]=\"'chat.send' | transloco\"\n [disabled]=\"!canSend()\">\n <img ngSrc=\"assets/ngx-parl/icons/send.svg\"\n class=\"message__icon message__icon--send\"\n width=\"18\" height=\"18\" alt=\"hide\" priority/>\n\n </button>\n </div>\n\n @if (previews().length) {\n <div class=\"message__files\">\n @for (item of previews(); track $index) {\n <div class=\"message__file\" [attr.data-kind]=\"item.originalKind\">\n <button type=\"button\"\n class=\"message__file-remove\"\n [attr.aria-label]=\"'chat.delete_file' | transloco\"\n (click)=\"removeFile($index, $event)\">\n <img ngSrc=\"assets/ngx-parl/icons/remove.svg\"\n class=\"message__icon message__icon--file\"\n width=\"18\" height=\"18\" alt=\"hide\" priority/>\n </button>\n\n <div class=\"message__file-media\" (click)=\"openPreview(item, $index)\">\n <img [src]=\"item.src\" [alt]=\"item.name\" class=\"message__file-img\"/>\n\n @if (item.originalKind === FileType.GIF) {\n <span class=\"message__file-badge message__file-badge--gif\">\n {{ 'chat.gif' | transloco }}\n </span>\n }\n </div>\n </div>\n }\n </div>\n }\n</section>\n\n\n", styles: [":host{display:block}.message{--lh: 24px;--max-rows: 8;display:flex;flex-direction:column;justify-content:space-between;background:#fff;border-top:1px solid #E1E7F8;padding:4px 8px;margin-top:8px;gap:8px;border-bottom-left-radius:16px;border-bottom-right-radius:16px;transition:box-shadow .18s ease,background-color .18s ease,border-color .18s ease}.message__wrap{display:flex;width:100%;min-width:0}.message__wrap--input{position:relative;display:flex;align-items:center;gap:12px;flex:1;min-width:0;padding:8px 0}.message__wrap--input:focus-within{border-color:#f8f9fa}.message__wrap--input::-webkit-scrollbar{width:4px;height:4px}.message__wrap--input::-webkit-scrollbar-track{background:transparent}.message__wrap--input::-webkit-scrollbar-thumb{background-color:#e9ecef;border-radius:4px;border:1px solid #E9ECEF}.message__wrap--input::-webkit-scrollbar-button{display:none}.message__input{flex:1 1 auto;min-height:var(--lh);line-height:var(--lh);font:400 16px/24px roboto,sans-serif;outline:none;border:none;background:transparent;color:#343a40;box-sizing:content-box;resize:none;white-space:pre-wrap;overflow-wrap:break-word;border-radius:8px;caret-color:#3c46b9;transition:height .16s ease;padding:0 4px 0 0;overflow-y:auto;max-height:calc(var(--lh) * var(--max-rows))}.message__input:empty:before{content:attr(data-placeholder);color:#ced4da;pointer-events:none;display:inline-block;animation:subtle-rise .18s ease both}.message__input-mirror{position:absolute;visibility:hidden;pointer-events:none;z-index:-1;white-space:pre-wrap;overflow-wrap:break-word;word-break:normal}.message__button{cursor:pointer;background:none;border:none;transition:transform .12s ease,opacity .12s ease,background-color .12s ease}.message__button:active{transform:scale(.96)}.message__button:disabled{opacity:.45;pointer-events:none}.message__icon{display:inline-flex;width:22px;height:22px}.message__icon:not(.message__icon--file):hover{filter:invert(33%) sepia(79%) saturate(2933%) hue-rotate(205deg) brightness(93%) contrast(91%)}.message__icon--focus,.message__icon--filled{filter:invert(33%) sepia(79%) saturate(2933%) hue-rotate(205deg) brightness(93%) contrast(91%)}.message__icon--edit{filter:invert(33%) sepia(79%) saturate(2933%) hue-rotate(205deg) brightness(73%) contrast(99%)}.message__icon--close{filter:invert(1%) sepia(1%) saturate(1%) hue-rotate(100deg) brightness(100%) contrast(30%)}.message__files{display:flex;flex-wrap:wrap;gap:8px}.message__file{position:relative;width:40px;height:40px;flex:0 0 auto;overflow:visible}.message__file-media{width:100%;height:100%;border-radius:8px;overflow:hidden;cursor:pointer;position:relative;background:#f8f9fa}.message__file-img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;display:block}.message__file-badge{position:absolute;left:6px;top:6px;z-index:2;display:inline-flex;align-items:center;justify-content:center;height:18px;padding:0 6px;border-radius:4px;font:400 16px/24px roboto,sans-serif;background:#0009;color:#fff;letter-spacing:.3px;line-height:1}.message__file-badge--video{width:28px;height:28px;padding:0;left:50%;top:50%;transform:translate(-50%,-50%);border-radius:50%;background:#00000073}.message__file-badge--video mat-icon{width:18px;height:18px;filter:invert(100%)}.message__file-badge--gif{font-weight:700}.message__file-duration{position:absolute;right:6px;bottom:6px;background:#0009;color:#fff;border-radius:4px;padding:1px;font:400 16px/24px roboto,sans-serif;z-index:2px;line-height:1.1}.message__file-remove{position:absolute;top:-6px;right:-6px;width:16px;height:16px;padding:0;border:none;border-radius:50%;display:inline-flex;align-items:center;justify-content:center;z-index:3;cursor:pointer;transition:transform .12s ease,box-shadow .12s ease}.message__file-remove:hover{transform:scale(1.05)}.message__file-remove:active{transform:scale(.96)}.message__file-remove .message__icon--file{width:14px;height:14px}.message__edit-hint{max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;display:flex;align-items:center;gap:8px;border-radius:4px;border-left:2px solid #4656CA;padding:4px 8px}.message__edit-hint p{color:#ced4da;font:400 16px/24px roboto,sans-serif;margin:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.hidden{position:absolute;opacity:0;width:0;height:0;pointer-events:none}@keyframes subtle-rise{0%{transform:translateY(2px);opacity:.6}to{transform:translateY(0);opacity:1}}\n"], dependencies: [{ kind: "directive", type: NgOptimizedImage, selector: "img[ngSrc]", inputs: ["ngSrc", "ngSrcset", "sizes", "width", "height", "decoding", "loading", "priority", "loaderParams", "disableOptimizedSrcset", "fill", "placeholder", "placeholderConfig", "src", "srcset"] }, { kind: "pipe", type: TranslocoPipe, name: "transloco" }] });
|
|
639
|
+
}
|
|
640
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: InputMessageComponent, decorators: [{
|
|
641
|
+
type: Component,
|
|
642
|
+
args: [{ selector: 'app-input-message', imports: [TranslocoPipe, NgOptimizedImage], standalone: true, template: "<section class=\"message\"\n [class.message--focus]=\"focused()\"\n [class.message--filled]=\"hasText()\"\n [class.message--sending]=\"sending()\">\n\n @if (isEditMode()) {\n <div class=\"message__wrap\">\n <div class=\"message__wrap--input\">\n <button class=\"message__button message__button--attach\">\n <img ngSrc=\"assets/ngx-parl/icons/icon-edit.svg\"\n class=\"message__icon message__icon--edit\"\n width=\"18\" height=\"18\" alt=\"hide\" priority/>\n </button>\n\n <div class=\"message__edit-hint\">\n <p>{{ editMessage()?.content }}</p>\n </div>\n </div>\n\n <button class=\"message__button message__button--close\"\n type=\"button\"\n (click)=\"cancelEditMessage()\">\n <img ngSrc=\"assets/ngx-parl/icons/close.svg\"\n class=\"message__icon message__icon--close\"\n width=\"18\" height=\"18\" alt=\"hide\" priority/>\n </button>\n </div>\n }\n\n <div class=\"message__wrap\">\n <div class=\"message__wrap--input\">\n <button type=\"button\"\n class=\"message__button message__button--attach\"\n [attr.aria-label]=\"'chat.attach' | transloco\"\n (click)=\"fileDialog.click()\">\n <img ngSrc=\"assets/ngx-parl/icons/attach-filled.svg\"\n class=\"message__icon message__icon--attach\"\n width=\"18\" height=\"18\" alt=\"hide\" priority/>\n </button>\n\n <input #fileDialog\n type=\"file\"\n class=\"hidden\"\n accept=\"image/*\"\n multiple\n (change)=\"inputFileChange($event)\">\n\n <div #inputText\n role=\"textbox\"\n class=\"message__input\"\n spellcheck=\"true\"\n contenteditable=\"true\"\n aria-multiline=\"true\"\n [attr.data-placeholder]=\"isEditMode()\n ? ('chat.edit_placeholder' | transloco)\n : ('chat.placeholder' | transloco)\"\n (blur)=\"onBlur()\"\n (focus)=\"onFocus()\"\n (input)=\"onInput()\"\n (paste)=\"onPaste()\"\n (keydown)=\"onKeyDown($event)\">\n </div>\n\n <div #mirror class=\"message__input-mirror\" aria-hidden=\"true\"></div>\n\n <ng-content></ng-content>\n </div>\n\n <button class=\"message__button message__button--send\"\n type=\"button\"\n (click)=\"enterDown()\"\n [attr.aria-label]=\"'chat.send' | transloco\"\n [disabled]=\"!canSend()\">\n <img ngSrc=\"assets/ngx-parl/icons/send.svg\"\n class=\"message__icon message__icon--send\"\n width=\"18\" height=\"18\" alt=\"hide\" priority/>\n\n </button>\n </div>\n\n @if (previews().length) {\n <div class=\"message__files\">\n @for (item of previews(); track $index) {\n <div class=\"message__file\" [attr.data-kind]=\"item.originalKind\">\n <button type=\"button\"\n class=\"message__file-remove\"\n [attr.aria-label]=\"'chat.delete_file' | transloco\"\n (click)=\"removeFile($index, $event)\">\n <img ngSrc=\"assets/ngx-parl/icons/remove.svg\"\n class=\"message__icon message__icon--file\"\n width=\"18\" height=\"18\" alt=\"hide\" priority/>\n </button>\n\n <div class=\"message__file-media\" (click)=\"openPreview(item, $index)\">\n <img [src]=\"item.src\" [alt]=\"item.name\" class=\"message__file-img\"/>\n\n @if (item.originalKind === FileType.GIF) {\n <span class=\"message__file-badge message__file-badge--gif\">\n {{ 'chat.gif' | transloco }}\n </span>\n }\n </div>\n </div>\n }\n </div>\n }\n</section>\n\n\n", styles: [":host{display:block}.message{--lh: 24px;--max-rows: 8;display:flex;flex-direction:column;justify-content:space-between;background:#fff;border-top:1px solid #E1E7F8;padding:4px 8px;margin-top:8px;gap:8px;border-bottom-left-radius:16px;border-bottom-right-radius:16px;transition:box-shadow .18s ease,background-color .18s ease,border-color .18s ease}.message__wrap{display:flex;width:100%;min-width:0}.message__wrap--input{position:relative;display:flex;align-items:center;gap:12px;flex:1;min-width:0;padding:8px 0}.message__wrap--input:focus-within{border-color:#f8f9fa}.message__wrap--input::-webkit-scrollbar{width:4px;height:4px}.message__wrap--input::-webkit-scrollbar-track{background:transparent}.message__wrap--input::-webkit-scrollbar-thumb{background-color:#e9ecef;border-radius:4px;border:1px solid #E9ECEF}.message__wrap--input::-webkit-scrollbar-button{display:none}.message__input{flex:1 1 auto;min-height:var(--lh);line-height:var(--lh);font:400 16px/24px roboto,sans-serif;outline:none;border:none;background:transparent;color:#343a40;box-sizing:content-box;resize:none;white-space:pre-wrap;overflow-wrap:break-word;border-radius:8px;caret-color:#3c46b9;transition:height .16s ease;padding:0 4px 0 0;overflow-y:auto;max-height:calc(var(--lh) * var(--max-rows))}.message__input:empty:before{content:attr(data-placeholder);color:#ced4da;pointer-events:none;display:inline-block;animation:subtle-rise .18s ease both}.message__input-mirror{position:absolute;visibility:hidden;pointer-events:none;z-index:-1;white-space:pre-wrap;overflow-wrap:break-word;word-break:normal}.message__button{cursor:pointer;background:none;border:none;transition:transform .12s ease,opacity .12s ease,background-color .12s ease}.message__button:active{transform:scale(.96)}.message__button:disabled{opacity:.45;pointer-events:none}.message__icon{display:inline-flex;width:22px;height:22px}.message__icon:not(.message__icon--file):hover{filter:invert(33%) sepia(79%) saturate(2933%) hue-rotate(205deg) brightness(93%) contrast(91%)}.message__icon--focus,.message__icon--filled{filter:invert(33%) sepia(79%) saturate(2933%) hue-rotate(205deg) brightness(93%) contrast(91%)}.message__icon--edit{filter:invert(33%) sepia(79%) saturate(2933%) hue-rotate(205deg) brightness(73%) contrast(99%)}.message__icon--close{filter:invert(1%) sepia(1%) saturate(1%) hue-rotate(100deg) brightness(100%) contrast(30%)}.message__files{display:flex;flex-wrap:wrap;gap:8px}.message__file{position:relative;width:40px;height:40px;flex:0 0 auto;overflow:visible}.message__file-media{width:100%;height:100%;border-radius:8px;overflow:hidden;cursor:pointer;position:relative;background:#f8f9fa}.message__file-img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;display:block}.message__file-badge{position:absolute;left:6px;top:6px;z-index:2;display:inline-flex;align-items:center;justify-content:center;height:18px;padding:0 6px;border-radius:4px;font:400 16px/24px roboto,sans-serif;background:#0009;color:#fff;letter-spacing:.3px;line-height:1}.message__file-badge--video{width:28px;height:28px;padding:0;left:50%;top:50%;transform:translate(-50%,-50%);border-radius:50%;background:#00000073}.message__file-badge--video mat-icon{width:18px;height:18px;filter:invert(100%)}.message__file-badge--gif{font-weight:700}.message__file-duration{position:absolute;right:6px;bottom:6px;background:#0009;color:#fff;border-radius:4px;padding:1px;font:400 16px/24px roboto,sans-serif;z-index:2px;line-height:1.1}.message__file-remove{position:absolute;top:-6px;right:-6px;width:16px;height:16px;padding:0;border:none;border-radius:50%;display:inline-flex;align-items:center;justify-content:center;z-index:3;cursor:pointer;transition:transform .12s ease,box-shadow .12s ease}.message__file-remove:hover{transform:scale(1.05)}.message__file-remove:active{transform:scale(.96)}.message__file-remove .message__icon--file{width:14px;height:14px}.message__edit-hint{max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;display:flex;align-items:center;gap:8px;border-radius:4px;border-left:2px solid #4656CA;padding:4px 8px}.message__edit-hint p{color:#ced4da;font:400 16px/24px roboto,sans-serif;margin:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.hidden{position:absolute;opacity:0;width:0;height:0;pointer-events:none}@keyframes subtle-rise{0%{transform:translateY(2px);opacity:.6}to{transform:translateY(0);opacity:1}}\n"] }]
|
|
643
|
+
}], ctorParameters: () => [], propDecorators: { inputTextElement: [{
|
|
644
|
+
type: ViewChild,
|
|
645
|
+
args: ['inputText', { static: false }]
|
|
646
|
+
}], mirrorElement: [{
|
|
647
|
+
type: ViewChild,
|
|
648
|
+
args: ['mirror', { static: false }]
|
|
649
|
+
}], editMessage: [{ type: i0.Input, args: [{ isSignal: true, alias: "editMessage", required: false }] }], cancelEdit: [{ type: i0.Input, args: [{ isSignal: true, alias: "cancelEdit", required: false }] }, { type: i0.Output, args: ["cancelEditChange"] }], input_text: [{ type: i0.Input, args: [{ isSignal: true, alias: "input_text", required: false }] }, { type: i0.Output, args: ["input_textChange"] }], files: [{ type: i0.Input, args: [{ isSignal: true, alias: "files", required: false }] }, { type: i0.Output, args: ["filesChange"] }], previews: [{ type: i0.Input, args: [{ isSignal: true, alias: "previews", required: false }] }, { type: i0.Output, args: ["previewsChange"] }] } });
|
|
650
|
+
|
|
651
|
+
var FlowTheme;
|
|
652
|
+
(function (FlowTheme) {
|
|
653
|
+
FlowTheme["PRIMARY"] = "primary";
|
|
654
|
+
FlowTheme["SECONDARY"] = "secondary";
|
|
655
|
+
})(FlowTheme || (FlowTheme = {}));
|
|
656
|
+
|
|
657
|
+
class NgxParlComponent {
|
|
658
|
+
utils;
|
|
659
|
+
transloco;
|
|
660
|
+
ai_run_in_progress = false;
|
|
661
|
+
lastUpdateKey = null;
|
|
662
|
+
theme = input(FlowTheme.PRIMARY, ...(ngDevMode ? [{ debugName: "theme" }] : []));
|
|
663
|
+
header = input(true, ...(ngDevMode ? [{ debugName: "header" }] : []));
|
|
664
|
+
language = input('en', ...(ngDevMode ? [{ debugName: "language" }] : []));
|
|
665
|
+
messageList = model([], ...(ngDevMode ? [{ debugName: "messageList" }] : []));
|
|
666
|
+
messageUpdate = model(...(ngDevMode ? [undefined, { debugName: "messageUpdate" }] : []));
|
|
667
|
+
selectedForEdit = model(null, ...(ngDevMode ? [{ debugName: "selectedForEdit" }] : []));
|
|
668
|
+
incomingUser = computed(() => {
|
|
669
|
+
return (this.messageList().find((message) => message.type === MessageType.Incoming)?.user ?? '');
|
|
670
|
+
}, ...(ngDevMode ? [{ debugName: "incomingUser" }] : []));
|
|
671
|
+
hideHandler = input(null, ...(ngDevMode ? [{ debugName: "hideHandler" }] : []));
|
|
672
|
+
closeHandler = input(null, ...(ngDevMode ? [{ debugName: "closeHandler" }] : []));
|
|
673
|
+
constructor(utils, transloco) {
|
|
674
|
+
this.utils = utils;
|
|
675
|
+
this.transloco = transloco;
|
|
676
|
+
effect(() => {
|
|
677
|
+
const lang = this.language();
|
|
678
|
+
this.transloco.setActiveLang(lang);
|
|
679
|
+
});
|
|
680
|
+
effect(() => {
|
|
681
|
+
const updatedMessage = this.messageUpdate();
|
|
682
|
+
if (!updatedMessage) {
|
|
683
|
+
return;
|
|
684
|
+
}
|
|
685
|
+
const key = `${updatedMessage.id}-${updatedMessage.cr_time}-${updatedMessage.type}`;
|
|
686
|
+
if (this.lastUpdateKey === key) {
|
|
687
|
+
return;
|
|
688
|
+
}
|
|
689
|
+
this.lastUpdateKey = key;
|
|
690
|
+
if (updatedMessage.type !== MessageType.Incoming) {
|
|
691
|
+
return;
|
|
692
|
+
}
|
|
693
|
+
this.messageList.update((currentList) => {
|
|
694
|
+
const list = [...currentList];
|
|
695
|
+
const incomingIndex = list.findIndex((message) => message.id === updatedMessage.id &&
|
|
696
|
+
message.type === MessageType.Incoming);
|
|
697
|
+
if (incomingIndex > -1) {
|
|
698
|
+
list[incomingIndex] = updatedMessage;
|
|
699
|
+
return list;
|
|
700
|
+
}
|
|
701
|
+
list.push(updatedMessage);
|
|
702
|
+
return list;
|
|
703
|
+
});
|
|
704
|
+
});
|
|
705
|
+
}
|
|
706
|
+
onCancelEdit(messageId) {
|
|
707
|
+
if (messageId != null) {
|
|
708
|
+
this.messageList.update((currentList) => {
|
|
709
|
+
const updatedList = [...currentList];
|
|
710
|
+
const index = updatedList.findIndex((message) => message.id === messageId);
|
|
711
|
+
if (index > -1) {
|
|
712
|
+
updatedList[index].edit = false;
|
|
713
|
+
}
|
|
714
|
+
return updatedList;
|
|
715
|
+
});
|
|
716
|
+
}
|
|
717
|
+
this.selectedForEdit.set(null);
|
|
718
|
+
return this;
|
|
719
|
+
}
|
|
720
|
+
sendMessage(event) {
|
|
721
|
+
if (!event) {
|
|
722
|
+
return this;
|
|
723
|
+
}
|
|
724
|
+
// edit message
|
|
725
|
+
if (typeof event !== 'string' && 'id' in event) {
|
|
726
|
+
const { id, content, files } = event;
|
|
727
|
+
this.messageList.update((currentList) => {
|
|
728
|
+
const updatedList = [...currentList];
|
|
729
|
+
const index = updatedList.findIndex((message) => message.id === id);
|
|
730
|
+
if (index > -1) {
|
|
731
|
+
updatedList[index].content = (content ?? '').trim();
|
|
732
|
+
if (Array.isArray(files)) {
|
|
733
|
+
updatedList[index].file_path = files.length ? files : null;
|
|
734
|
+
}
|
|
735
|
+
updatedList[index].edit = false;
|
|
736
|
+
}
|
|
737
|
+
return updatedList;
|
|
738
|
+
});
|
|
739
|
+
this.selectedForEdit.set(null);
|
|
740
|
+
return this;
|
|
741
|
+
}
|
|
742
|
+
// new message
|
|
743
|
+
if (typeof event === 'string') {
|
|
744
|
+
const text = event.trim();
|
|
745
|
+
if (!text) {
|
|
746
|
+
return this;
|
|
747
|
+
}
|
|
748
|
+
const messages = this.messageList();
|
|
749
|
+
const lastId = messages.at(-1)?.id ?? 0;
|
|
750
|
+
const lastOutgoing = [...messages]
|
|
751
|
+
.reverse()
|
|
752
|
+
.find((message) => message.type === MessageType.Outgoing);
|
|
753
|
+
const dto = {
|
|
754
|
+
id: lastId + 1,
|
|
755
|
+
chat_id: lastOutgoing?.chat_id ?? 1,
|
|
756
|
+
cr_time: this.utils.getLocalISODate(),
|
|
757
|
+
type: MessageType.Outgoing,
|
|
758
|
+
user: lastOutgoing?.user ?? '',
|
|
759
|
+
content: text,
|
|
760
|
+
avatar: lastOutgoing?.avatar ?? null,
|
|
761
|
+
file_path: null,
|
|
762
|
+
checked: false,
|
|
763
|
+
};
|
|
764
|
+
this.messageList.update((list) => [...list, new ChatMessage(dto)]);
|
|
765
|
+
return this;
|
|
766
|
+
}
|
|
767
|
+
// new message + files
|
|
768
|
+
const { content, files } = event;
|
|
769
|
+
const text = (content ?? '').trim();
|
|
770
|
+
const hasFiles = Array.isArray(files) && files.length > 0;
|
|
771
|
+
if (!text && !hasFiles) {
|
|
772
|
+
return this;
|
|
773
|
+
}
|
|
774
|
+
const messages = this.messageList();
|
|
775
|
+
const lastId = messages.at(-1)?.id ?? 0;
|
|
776
|
+
const lastOutgoing = [...messages]
|
|
777
|
+
.reverse()
|
|
778
|
+
.find((message) => message.type === MessageType.Outgoing);
|
|
779
|
+
const dto = {
|
|
780
|
+
id: lastId + 1,
|
|
781
|
+
chat_id: lastOutgoing?.chat_id ?? 1,
|
|
782
|
+
cr_time: this.utils.getLocalISODate(),
|
|
783
|
+
type: MessageType.Outgoing,
|
|
784
|
+
user: lastOutgoing?.user ?? '',
|
|
785
|
+
content: text,
|
|
786
|
+
avatar: lastOutgoing?.avatar ?? null,
|
|
787
|
+
file_path: hasFiles ? files : null,
|
|
788
|
+
checked: false,
|
|
789
|
+
};
|
|
790
|
+
this.messageList.update((list) => [...list, new ChatMessage(dto)]);
|
|
791
|
+
return this;
|
|
792
|
+
}
|
|
793
|
+
onHideClick() {
|
|
794
|
+
const handler = this.hideHandler();
|
|
795
|
+
if (handler) {
|
|
796
|
+
handler();
|
|
797
|
+
}
|
|
798
|
+
return this;
|
|
799
|
+
}
|
|
800
|
+
onCloseClick() {
|
|
801
|
+
const handler = this.closeHandler();
|
|
802
|
+
if (handler) {
|
|
803
|
+
handler();
|
|
804
|
+
}
|
|
805
|
+
return this;
|
|
806
|
+
}
|
|
807
|
+
FlowTheme = FlowTheme;
|
|
808
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: NgxParlComponent, deps: [{ token: UtilsService }, { token: i2$1.TranslocoService }], target: i0.ɵɵFactoryTarget.Component });
|
|
809
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.7", type: NgxParlComponent, isStandalone: true, selector: "ngx-parl", inputs: { theme: { classPropertyName: "theme", publicName: "theme", isSignal: true, isRequired: false, transformFunction: null }, header: { classPropertyName: "header", publicName: "header", isSignal: true, isRequired: false, transformFunction: null }, language: { classPropertyName: "language", publicName: "language", isSignal: true, isRequired: false, transformFunction: null }, messageList: { classPropertyName: "messageList", publicName: "messageList", isSignal: true, isRequired: false, transformFunction: null }, messageUpdate: { classPropertyName: "messageUpdate", publicName: "messageUpdate", isSignal: true, isRequired: false, transformFunction: null }, selectedForEdit: { classPropertyName: "selectedForEdit", publicName: "selectedForEdit", isSignal: true, isRequired: false, transformFunction: null }, hideHandler: { classPropertyName: "hideHandler", publicName: "hideHandler", isSignal: true, isRequired: false, transformFunction: null }, closeHandler: { classPropertyName: "closeHandler", publicName: "closeHandler", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { messageList: "messageListChange", messageUpdate: "messageUpdateChange", selectedForEdit: "selectedForEditChange" }, providers: [], ngImport: i0, template: "<div class=\"modal-chat\" [ngClass]=\"'flow-theme-' + theme()\">\n <div class=\"modal-chat__body\">\n @if (header()) {\n <header class=\"modal-chat__header\" mat-dialog-title>\n <div class=\"modal-chat__hide-block\"></div>\n\n <div class=\"modal-chat__title\">\n <h1 class=\"modal-chat__heading\">{{ 'chat.title' | transloco }} {{ incomingUser() }}</h1>\n </div>\n\n <div class=\"modal-chat__actions\">\n <img ngSrc=\"assets/ngx-parl/icons/hide.svg\" class=\"modal-chat__icon modal-chat__icon--hide\"\n width=\"24\" height=\"24\" alt=\"hide\" priority\n (click)=\"onHideClick()\"/>\n <img ngSrc=\"assets/ngx-parl/icons/close.svg\" class=\"modal-chat__icon modal-chat__icon--close\"\n width=\"24\" height=\"24\" alt=\"close\" priority\n (click)=\"onCloseClick()\"/>\n </div>\n </header>\n }\n\n <mat-dialog-content class=\"modal-chat__content\">\n <app-chat-flow [messageListInput]=\"messageList()\" [(selectedForEdit)]=\"selectedForEdit\"></app-chat-flow>\n\n <app-input-message [editMessage]=\"selectedForEdit()\"\n (cancelEditChange)=\"onCancelEdit($event)\"\n (input_textChange)=\"sendMessage($event)\">\n @if (ai_run_in_progress) {\n <mat-spinner [diameter]=\"30\"></mat-spinner>\n }\n </app-input-message>\n </mat-dialog-content>\n </div>\n</div>\n", styles: [".flow-theme-primary ::ng-deep .modal-chat__header{background-color:#5a72d7;color:#fff}.flow-theme-primary ::ng-deep .message--incoming .message__body{background:#e9ecef;color:#343a40}.flow-theme-primary ::ng-deep .message--outgoing .message__body{background:#4656ca;color:#fff}:root{--shadow-sm: 0px 1px 2px 0px rgba(0, 0, 0, .05);--shadow-md: 0px 4px 12px rgba(0, 0, 0, .08);--shadow-lg: 0px 8px 24px rgba(0, 0, 0, .12);--shadow-soft: 0px 0px 6px 0px rgba(46, 46, 46, .12) }.flow-theme-secondary ::ng-deep .modal-chat__header{background-color:#848af5;color:#fefefe}.flow-theme-secondary ::ng-deep .message--incoming .message__body{background:#848af5;color:#fefefe}.flow-theme-secondary ::ng-deep .message--outgoing .message__body{background:#efefef;color:#464646}.modal-chat{width:800px;height:600px;border-radius:16px}.modal-chat__body{display:flex;flex-direction:column;height:100%}.modal-chat__header{height:56px;display:flex;align-items:center;justify-content:space-between;padding:0 16px;gap:12px;border-top-left-radius:16px;border-top-right-radius:16px}.modal-chat__hide-block{width:48px;color:#5a72d7}.modal-chat__title .modal-chat__heading{color:#fff;font:500 16px/24px roboto,sans-serif}.modal-chat__actions{display:flex;gap:8px}.modal-chat__content{flex:1 1 auto;display:flex;flex-direction:column}.modal-chat__content app-chat-flow{flex:1 1 auto;min-height:0px;overflow:auto;display:flex;align-items:flex-end}.modal-chat__input{flex:0px 0px auto;border-bottom-left-radius:16px;border-bottom-right-radius:16px}:host ::ng-deep .mat-mdc-dialog-content.modal-chat__content{flex:1 1 auto;display:flex;flex-direction:column;padding:0;max-height:none;overflow:hidden}img{cursor:pointer}\n"], dependencies: [{ kind: "directive", type: NgOptimizedImage, selector: "img[ngSrc]", inputs: ["ngSrc", "ngSrcset", "sizes", "width", "height", "decoding", "loading", "priority", "loaderParams", "disableOptimizedSrcset", "fill", "placeholder", "placeholderConfig", "src", "srcset"] }, { kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: MatDialogContent, selector: "[mat-dialog-content], mat-dialog-content, [matDialogContent]" }, { kind: "directive", type: MatDialogTitle, selector: "[mat-dialog-title], [matDialogTitle]", inputs: ["id"], exportAs: ["matDialogTitle"] }, { kind: "component", type: MatProgressSpinner, selector: "mat-progress-spinner, mat-spinner", inputs: ["color", "mode", "value", "diameter", "strokeWidth"], exportAs: ["matProgressSpinner"] }, { kind: "component", type: ChatFlowComponent, selector: "app-chat-flow", inputs: ["messageListInput", "selectedForEdit"], outputs: ["messageListInputChange", "selectedForEditChange"] }, { kind: "component", type: InputMessageComponent, selector: "app-input-message", inputs: ["editMessage", "cancelEdit", "input_text", "files", "previews"], outputs: ["cancelEditChange", "input_textChange", "filesChange", "previewsChange"] }, { kind: "ngmodule", type: TranslocoModule }, { kind: "pipe", type: i2$1.TranslocoPipe, name: "transloco" }] });
|
|
810
|
+
}
|
|
811
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: NgxParlComponent, decorators: [{
|
|
812
|
+
type: Component,
|
|
813
|
+
args: [{ selector: 'ngx-parl', standalone: true, imports: [
|
|
814
|
+
NgOptimizedImage, NgClass, MatDialogContent, MatDialogTitle, MatProgressSpinner, ChatFlowComponent, InputMessageComponent,
|
|
815
|
+
TranslocoModule,
|
|
816
|
+
TranslocoPipe
|
|
817
|
+
], providers: [], template: "<div class=\"modal-chat\" [ngClass]=\"'flow-theme-' + theme()\">\n <div class=\"modal-chat__body\">\n @if (header()) {\n <header class=\"modal-chat__header\" mat-dialog-title>\n <div class=\"modal-chat__hide-block\"></div>\n\n <div class=\"modal-chat__title\">\n <h1 class=\"modal-chat__heading\">{{ 'chat.title' | transloco }} {{ incomingUser() }}</h1>\n </div>\n\n <div class=\"modal-chat__actions\">\n <img ngSrc=\"assets/ngx-parl/icons/hide.svg\" class=\"modal-chat__icon modal-chat__icon--hide\"\n width=\"24\" height=\"24\" alt=\"hide\" priority\n (click)=\"onHideClick()\"/>\n <img ngSrc=\"assets/ngx-parl/icons/close.svg\" class=\"modal-chat__icon modal-chat__icon--close\"\n width=\"24\" height=\"24\" alt=\"close\" priority\n (click)=\"onCloseClick()\"/>\n </div>\n </header>\n }\n\n <mat-dialog-content class=\"modal-chat__content\">\n <app-chat-flow [messageListInput]=\"messageList()\" [(selectedForEdit)]=\"selectedForEdit\"></app-chat-flow>\n\n <app-input-message [editMessage]=\"selectedForEdit()\"\n (cancelEditChange)=\"onCancelEdit($event)\"\n (input_textChange)=\"sendMessage($event)\">\n @if (ai_run_in_progress) {\n <mat-spinner [diameter]=\"30\"></mat-spinner>\n }\n </app-input-message>\n </mat-dialog-content>\n </div>\n</div>\n", styles: [".flow-theme-primary ::ng-deep .modal-chat__header{background-color:#5a72d7;color:#fff}.flow-theme-primary ::ng-deep .message--incoming .message__body{background:#e9ecef;color:#343a40}.flow-theme-primary ::ng-deep .message--outgoing .message__body{background:#4656ca;color:#fff}:root{--shadow-sm: 0px 1px 2px 0px rgba(0, 0, 0, .05);--shadow-md: 0px 4px 12px rgba(0, 0, 0, .08);--shadow-lg: 0px 8px 24px rgba(0, 0, 0, .12);--shadow-soft: 0px 0px 6px 0px rgba(46, 46, 46, .12) }.flow-theme-secondary ::ng-deep .modal-chat__header{background-color:#848af5;color:#fefefe}.flow-theme-secondary ::ng-deep .message--incoming .message__body{background:#848af5;color:#fefefe}.flow-theme-secondary ::ng-deep .message--outgoing .message__body{background:#efefef;color:#464646}.modal-chat{width:800px;height:600px;border-radius:16px}.modal-chat__body{display:flex;flex-direction:column;height:100%}.modal-chat__header{height:56px;display:flex;align-items:center;justify-content:space-between;padding:0 16px;gap:12px;border-top-left-radius:16px;border-top-right-radius:16px}.modal-chat__hide-block{width:48px;color:#5a72d7}.modal-chat__title .modal-chat__heading{color:#fff;font:500 16px/24px roboto,sans-serif}.modal-chat__actions{display:flex;gap:8px}.modal-chat__content{flex:1 1 auto;display:flex;flex-direction:column}.modal-chat__content app-chat-flow{flex:1 1 auto;min-height:0px;overflow:auto;display:flex;align-items:flex-end}.modal-chat__input{flex:0px 0px auto;border-bottom-left-radius:16px;border-bottom-right-radius:16px}:host ::ng-deep .mat-mdc-dialog-content.modal-chat__content{flex:1 1 auto;display:flex;flex-direction:column;padding:0;max-height:none;overflow:hidden}img{cursor:pointer}\n"] }]
|
|
818
|
+
}], ctorParameters: () => [{ type: UtilsService }, { type: i2$1.TranslocoService }], propDecorators: { theme: [{ type: i0.Input, args: [{ isSignal: true, alias: "theme", required: false }] }], header: [{ type: i0.Input, args: [{ isSignal: true, alias: "header", required: false }] }], language: [{ type: i0.Input, args: [{ isSignal: true, alias: "language", required: false }] }], messageList: [{ type: i0.Input, args: [{ isSignal: true, alias: "messageList", required: false }] }, { type: i0.Output, args: ["messageListChange"] }], messageUpdate: [{ type: i0.Input, args: [{ isSignal: true, alias: "messageUpdate", required: false }] }, { type: i0.Output, args: ["messageUpdateChange"] }], selectedForEdit: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectedForEdit", required: false }] }, { type: i0.Output, args: ["selectedForEditChange"] }], hideHandler: [{ type: i0.Input, args: [{ isSignal: true, alias: "hideHandler", required: false }] }], closeHandler: [{ type: i0.Input, args: [{ isSignal: true, alias: "closeHandler", required: false }] }] } });
|
|
819
|
+
|
|
820
|
+
var chat$1 = {
|
|
821
|
+
title: "Telegram",
|
|
822
|
+
hide: "Hide",
|
|
823
|
+
close: "Close",
|
|
824
|
+
attach: "Attach",
|
|
825
|
+
send: "Send message",
|
|
826
|
+
placeholder: "Type a message...",
|
|
827
|
+
edit_placeholder: "Update a message...",
|
|
828
|
+
delete_file: "Remove file",
|
|
829
|
+
gif: "GIF",
|
|
830
|
+
video: "Video",
|
|
831
|
+
edit: "Edit",
|
|
832
|
+
remove: "Delete",
|
|
833
|
+
chat_empty_title: "No messages",
|
|
834
|
+
chat_empty_subscription: "Your chat history will be displayed here. Write your first message to get started!"
|
|
835
|
+
};
|
|
836
|
+
var en = {
|
|
837
|
+
chat: chat$1
|
|
838
|
+
};
|
|
839
|
+
|
|
840
|
+
var chat = {
|
|
841
|
+
title: "Telegram",
|
|
842
|
+
hide: "Згорнути",
|
|
843
|
+
close: "Закрити",
|
|
844
|
+
attach: "Прикріпити",
|
|
845
|
+
send: "Відправити повідомлення",
|
|
846
|
+
placeholder: "Напишіть повідомлення...",
|
|
847
|
+
edit_placeholder: "Змініть текст повідомлення…",
|
|
848
|
+
delete_file: "Видалити файл",
|
|
849
|
+
gif: "GIF",
|
|
850
|
+
video: "Відео",
|
|
851
|
+
edit: "Редагувати",
|
|
852
|
+
remove: "Видалити",
|
|
853
|
+
chat_empty_title: "Повідомлень ще немає",
|
|
854
|
+
chat_empty_subscription: "Тут буде відображатись ваша історія спілкування. Напишіть перше повідомлення, щоб почати!"
|
|
855
|
+
};
|
|
856
|
+
var uk = {
|
|
857
|
+
chat: chat
|
|
858
|
+
};
|
|
859
|
+
|
|
860
|
+
class ParlTranslocoLoader {
|
|
861
|
+
getTranslation(lang) {
|
|
862
|
+
switch (lang) {
|
|
863
|
+
case 'uk':
|
|
864
|
+
return of(uk);
|
|
865
|
+
case 'en':
|
|
866
|
+
default:
|
|
867
|
+
return of(en);
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: ParlTranslocoLoader, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
871
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: ParlTranslocoLoader });
|
|
872
|
+
}
|
|
873
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: ParlTranslocoLoader, decorators: [{
|
|
874
|
+
type: Injectable
|
|
875
|
+
}] });
|
|
876
|
+
|
|
877
|
+
function provideNgxParl() {
|
|
878
|
+
return provideTransloco({
|
|
879
|
+
config: {
|
|
880
|
+
availableLangs: ['en', 'uk'],
|
|
881
|
+
defaultLang: 'en',
|
|
882
|
+
reRenderOnLangChange: true,
|
|
883
|
+
prodMode: !isDevMode(),
|
|
884
|
+
},
|
|
885
|
+
loader: ParlTranslocoLoader,
|
|
886
|
+
});
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
/**
|
|
890
|
+
* Generated bundle index. Do not edit.
|
|
891
|
+
*/
|
|
892
|
+
|
|
893
|
+
export { NgxParlComponent, provideNgxParl };
|
|
894
|
+
//# sourceMappingURL=trixwell-ngx-parl.mjs.map
|