@muraai/mnl-chat 0.0.1

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.
Files changed (88) hide show
  1. package/README.md +24 -0
  2. package/esm2020/lib/chat.module.mjs +87 -0
  3. package/esm2020/lib/components/ng-chat/ng-chat.component.mjs +588 -0
  4. package/esm2020/lib/components/ng-chat-friends-list/ng-chat-friends-list.component.mjs +128 -0
  5. package/esm2020/lib/components/ng-chat-options/ng-chat-options.component.mjs +29 -0
  6. package/esm2020/lib/components/ng-chat-window/ng-chat-window.component.mjs +247 -0
  7. package/esm2020/lib/core/chat-adapter.mjs +18 -0
  8. package/esm2020/lib/core/chat-controller.mjs +2 -0
  9. package/esm2020/lib/core/chat-group-adapter.mjs +2 -0
  10. package/esm2020/lib/core/chat-option.mjs +2 -0
  11. package/esm2020/lib/core/chat-participant-status-descriptor.mjs +6 -0
  12. package/esm2020/lib/core/chat-participant-status.enum.mjs +8 -0
  13. package/esm2020/lib/core/chat-participant-type.enum.mjs +6 -0
  14. package/esm2020/lib/core/chat-participant.mjs +2 -0
  15. package/esm2020/lib/core/default-file-upload-adapter.mjs +43 -0
  16. package/esm2020/lib/core/file-upload-adapter.mjs +2 -0
  17. package/esm2020/lib/core/group.mjs +14 -0
  18. package/esm2020/lib/core/guid.mjs +11 -0
  19. package/esm2020/lib/core/localization.mjs +2 -0
  20. package/esm2020/lib/core/message-counter.mjs +25 -0
  21. package/esm2020/lib/core/message-type.enum.mjs +7 -0
  22. package/esm2020/lib/core/message.mjs +7 -0
  23. package/esm2020/lib/core/paged-history-chat-adapter.mjs +9 -0
  24. package/esm2020/lib/core/participant-metadata.mjs +6 -0
  25. package/esm2020/lib/core/participant-response.mjs +3 -0
  26. package/esm2020/lib/core/scroll-direction.enum.mjs +6 -0
  27. package/esm2020/lib/core/theme.enum.mjs +7 -0
  28. package/esm2020/lib/core/user.mjs +7 -0
  29. package/esm2020/lib/core/window.mjs +20 -0
  30. package/esm2020/lib/firstLetter.pipe.mjs +20 -0
  31. package/esm2020/lib/material.module.mjs +50 -0
  32. package/esm2020/lib/pipes/emojify.pipe.mjs +41 -0
  33. package/esm2020/lib/pipes/group-message-display-name.pipe.mjs +24 -0
  34. package/esm2020/lib/pipes/linkfy.pipe.mjs +34 -0
  35. package/esm2020/lib/pipes/sanitize.pipe.mjs +21 -0
  36. package/esm2020/lib/services/chat.service.mjs +14 -0
  37. package/esm2020/muraai-mnl-chat.mjs +5 -0
  38. package/esm2020/public-api.mjs +25 -0
  39. package/fesm2015/muraai-mnl-chat.mjs +1426 -0
  40. package/fesm2015/muraai-mnl-chat.mjs.map +1 -0
  41. package/fesm2020/muraai-mnl-chat.mjs +1424 -0
  42. package/fesm2020/muraai-mnl-chat.mjs.map +1 -0
  43. package/index.d.ts +5 -0
  44. package/lib/chat.module.d.ts +24 -0
  45. package/lib/components/ng-chat/ng-chat.component.d.ts +130 -0
  46. package/lib/components/ng-chat-friends-list/ng-chat-friends-list.component.d.ts +45 -0
  47. package/lib/components/ng-chat-options/ng-chat-options.component.d.ts +12 -0
  48. package/lib/components/ng-chat-window/ng-chat-window.component.d.ts +65 -0
  49. package/lib/core/chat-adapter.d.ts +15 -0
  50. package/lib/core/chat-controller.d.ts +6 -0
  51. package/lib/core/chat-group-adapter.d.ts +4 -0
  52. package/lib/core/chat-option.d.ts +9 -0
  53. package/lib/core/chat-participant-status-descriptor.d.ts +3 -0
  54. package/lib/core/chat-participant-status.enum.d.ts +6 -0
  55. package/lib/core/chat-participant-type.enum.d.ts +4 -0
  56. package/lib/core/chat-participant.d.ts +9 -0
  57. package/lib/core/default-file-upload-adapter.d.ts +14 -0
  58. package/lib/core/file-upload-adapter.d.ts +5 -0
  59. package/lib/core/group.d.ts +13 -0
  60. package/lib/core/guid.d.ts +3 -0
  61. package/lib/core/localization.d.ts +15 -0
  62. package/lib/core/message-counter.d.ts +10 -0
  63. package/lib/core/message-type.enum.d.ts +5 -0
  64. package/lib/core/message.d.ts +10 -0
  65. package/lib/core/paged-history-chat-adapter.d.ts +10 -0
  66. package/lib/core/participant-metadata.d.ts +3 -0
  67. package/lib/core/participant-response.d.ts +6 -0
  68. package/lib/core/scroll-direction.enum.d.ts +4 -0
  69. package/lib/core/theme.enum.d.ts +5 -0
  70. package/lib/core/user.d.ts +10 -0
  71. package/lib/core/window.d.ts +13 -0
  72. package/lib/firstLetter.pipe.d.ts +7 -0
  73. package/lib/material.module.d.ts +14 -0
  74. package/lib/pipes/emojify.pipe.d.ts +7 -0
  75. package/lib/pipes/group-message-display-name.pipe.d.ts +9 -0
  76. package/lib/pipes/linkfy.pipe.d.ts +7 -0
  77. package/lib/pipes/sanitize.pipe.d.ts +10 -0
  78. package/lib/services/chat.service.d.ts +6 -0
  79. package/package.json +31 -0
  80. package/public-api.d.ts +21 -0
  81. package/src/assets/icons.css +140 -0
  82. package/src/assets/loading-spinner.css +59 -0
  83. package/src/assets/ng-chat.component.default.css +106 -0
  84. package/src/assets/notification.png +0 -0
  85. package/src/assets/notification.wav +0 -0
  86. package/src/assets/themes/ng-chat.theme.dark.scss +71 -0
  87. package/src/assets/themes/ng-chat.theme.default.scss +63 -0
  88. package/src/assets/user.png +0 -0
@@ -0,0 +1,1426 @@
1
+ import * as i1$2 from '@angular/common/http';
2
+ import { HttpClientModule } from '@angular/common/http';
3
+ import * as i2 from '@angular/common';
4
+ import { CommonModule } from '@angular/common';
5
+ import * as i0 from '@angular/core';
6
+ import { NgModule, Injectable, Pipe, EventEmitter, Component, ViewEncapsulation, Input, Output, ViewChild, ViewChildren, HostListener, APP_INITIALIZER } from '@angular/core';
7
+ import { AppConfigService } from '@muraai/mnl-commons';
8
+ import { MatButtonModule } from '@angular/material/button';
9
+ import { MatIconModule } from '@angular/material/icon';
10
+ import { MatInputModule } from '@angular/material/input';
11
+ import { MatFormFieldModule } from '@angular/material/form-field';
12
+ import * as i1$1 from '@angular/forms';
13
+ import { FormsModule, ReactiveFormsModule } from '@angular/forms';
14
+ import { MatMenuModule } from '@angular/material/menu';
15
+ import { __awaiter } from 'tslib';
16
+ import { map } from 'rxjs/operators';
17
+ import * as i1 from '@angular/platform-browser';
18
+
19
+ function modules() {
20
+ return [
21
+ MatButtonModule,
22
+ MatFormFieldModule,
23
+ MatIconModule,
24
+ MatInputModule,
25
+ FormsModule,
26
+ ReactiveFormsModule,
27
+ MatMenuModule,
28
+ ];
29
+ }
30
+ class ChatMaterialModule {
31
+ }
32
+ ChatMaterialModule.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: ChatMaterialModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
33
+ ChatMaterialModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "14.3.0", ngImport: i0, type: ChatMaterialModule, imports: [MatButtonModule,
34
+ MatFormFieldModule,
35
+ MatIconModule,
36
+ MatInputModule,
37
+ FormsModule,
38
+ ReactiveFormsModule,
39
+ MatMenuModule], exports: [MatButtonModule,
40
+ MatFormFieldModule,
41
+ MatIconModule,
42
+ MatInputModule,
43
+ FormsModule,
44
+ ReactiveFormsModule,
45
+ MatMenuModule] });
46
+ ChatMaterialModule.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: ChatMaterialModule, imports: [modules(), MatButtonModule,
47
+ MatFormFieldModule,
48
+ MatIconModule,
49
+ MatInputModule,
50
+ FormsModule,
51
+ ReactiveFormsModule,
52
+ MatMenuModule] });
53
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: ChatMaterialModule, decorators: [{
54
+ type: NgModule,
55
+ args: [{
56
+ imports: modules(),
57
+ exports: modules()
58
+ }]
59
+ }] });
60
+
61
+ class ChatService {
62
+ constructor() { }
63
+ }
64
+ ChatService.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: ChatService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
65
+ ChatService.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: ChatService, providedIn: 'root' });
66
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: ChatService, decorators: [{
67
+ type: Injectable,
68
+ args: [{
69
+ providedIn: 'root'
70
+ }]
71
+ }], ctorParameters: function () { return []; } });
72
+
73
+ var MessageType;
74
+ (function (MessageType) {
75
+ MessageType[MessageType["Text"] = 1] = "Text";
76
+ MessageType[MessageType["File"] = 2] = "File";
77
+ MessageType[MessageType["Image"] = 3] = "Image";
78
+ })(MessageType || (MessageType = {}));
79
+
80
+ class Window {
81
+ constructor(participant, isLoadingHistory, isCollapsed) {
82
+ this.messages = [];
83
+ this.newMessage = "";
84
+ // UI Behavior properties
85
+ this.isCollapsed = false;
86
+ this.isLoadingHistory = false;
87
+ this.hasFocus = false;
88
+ this.hasMoreMessages = true;
89
+ this.historyPage = 0;
90
+ this.participant = participant;
91
+ this.messages = [];
92
+ this.isLoadingHistory = isLoadingHistory;
93
+ this.hasFocus = false; // This will be triggered when the 'newMessage' input gets the current focus
94
+ this.isCollapsed = isCollapsed;
95
+ this.hasMoreMessages = false;
96
+ this.historyPage = 0;
97
+ }
98
+ }
99
+
100
+ var ChatParticipantStatus;
101
+ (function (ChatParticipantStatus) {
102
+ ChatParticipantStatus[ChatParticipantStatus["Online"] = 0] = "Online";
103
+ ChatParticipantStatus[ChatParticipantStatus["Busy"] = 1] = "Busy";
104
+ ChatParticipantStatus[ChatParticipantStatus["Away"] = 2] = "Away";
105
+ ChatParticipantStatus[ChatParticipantStatus["Offline"] = 3] = "Offline";
106
+ })(ChatParticipantStatus || (ChatParticipantStatus = {}));
107
+
108
+ var ScrollDirection;
109
+ (function (ScrollDirection) {
110
+ ScrollDirection[ScrollDirection["Top"] = 0] = "Top";
111
+ ScrollDirection[ScrollDirection["Bottom"] = 1] = "Bottom";
112
+ })(ScrollDirection || (ScrollDirection = {}));
113
+
114
+ class ChatAdapter {
115
+ constructor() {
116
+ // ### Abstract adapter methods ###
117
+ // Event handlers
118
+ /** @internal */
119
+ this.friendsListChangedHandler = (participantsResponse) => { };
120
+ /** @internal */
121
+ this.messageReceivedHandler = (participant, message) => { };
122
+ }
123
+ // ### Adapter/Chat income/ingress events ###
124
+ onFriendsListChanged(participantsResponse) {
125
+ this.friendsListChangedHandler(participantsResponse);
126
+ }
127
+ onMessageReceived(participant, message) {
128
+ this.messageReceivedHandler(participant, message);
129
+ }
130
+ }
131
+
132
+ // import { User } from "./user";
133
+ /**
134
+ * @description Chat Adapter decorator class that adds pagination to load the history of messagesr.
135
+ * You will need an existing @see ChatAdapter implementation
136
+ */
137
+ class PagedHistoryChatAdapter extends ChatAdapter {
138
+ }
139
+
140
+ /*import { IFileUploadAdapter } from './file-upload-adapter';
141
+ import { HttpClient, HttpRequest, HttpEventType, HttpResponse, HttpHeaders } from '@angular/common/http';
142
+ import { Observable, Subject } from 'rxjs';
143
+ import { User } from './user';
144
+ import { Message } from './message';*/
145
+ class DefaultFileUploadAdapter {
146
+ /**
147
+ * @summary Basic file upload adapter implementation for HTTP request form file consumption
148
+ * @param _serverEndpointUrl The API endpoint full qualified address that will receive a form file to process and return the metadata.
149
+ */
150
+ constructor(_serverEndpointUrl, _http) {
151
+ this._serverEndpointUrl = _serverEndpointUrl;
152
+ this._http = _http;
153
+ }
154
+ uploadFile(file, participantId) {
155
+ const formData = new FormData();
156
+ //formData.append('ng-chat-sender-userid', currentUserId);
157
+ formData.append('ng-chat-participant-id', participantId);
158
+ formData.append('file', file, file.name);
159
+ return this._http.post(this._serverEndpointUrl, formData);
160
+ // TODO: Leaving this if we want to track upload progress in detail in the future. Might need a different Subject generic type wrapper
161
+ // const fileRequest = new HttpRequest('POST', this._serverEndpointUrl, formData, {
162
+ // reportProgress: true
163
+ // });
164
+ // const uploadProgress = new Subject<number>();
165
+ // const uploadStatus = uploadProgress.asObservable();
166
+ //const responsePromise = new Subject<Message>();
167
+ // this._http
168
+ // .request(fileRequest)
169
+ // .subscribe(event => {
170
+ // // if (event.type == HttpEventType.UploadProgress)
171
+ // // {
172
+ // // const percentDone = Math.round(100 * event.loaded / event.total);
173
+ // // uploadProgress.next(percentDone);
174
+ // // }
175
+ // // else if (event instanceof HttpResponse)
176
+ // // {
177
+ // // uploadProgress.complete();
178
+ // // }
179
+ // });
180
+ }
181
+ }
182
+
183
+ var Theme;
184
+ (function (Theme) {
185
+ Theme["Custom"] = "custom-theme";
186
+ Theme["Light"] = "light-theme";
187
+ Theme["Dark"] = "dark-theme";
188
+ })(Theme || (Theme = {}));
189
+
190
+ // Poached from: https://github.com/Steve-Fenton/TypeScriptUtilities
191
+ // @dynamic
192
+ class Guid {
193
+ static newGuid() {
194
+ return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
195
+ var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
196
+ return v.toString(16);
197
+ });
198
+ }
199
+ }
200
+
201
+ var ChatParticipantType;
202
+ (function (ChatParticipantType) {
203
+ ChatParticipantType[ChatParticipantType["User"] = 0] = "User";
204
+ ChatParticipantType[ChatParticipantType["Group"] = 1] = "Group";
205
+ })(ChatParticipantType || (ChatParticipantType = {}));
206
+
207
+ class Group {
208
+ constructor(participants) {
209
+ this.id = Guid.newGuid();
210
+ this.participantType = ChatParticipantType.Group;
211
+ this.chattingTo = participants;
212
+ this.status = ChatParticipantStatus.Online;
213
+ // TODO: Add some customization for this in future releases
214
+ this.displayName = participants.map((p) => p.displayName).sort((first, second) => second > first ? -1 : 1).join(", ");
215
+ }
216
+ }
217
+
218
+ class MessageCounter {
219
+ static formatUnreadMessagesTotal(totalUnreadMessages) {
220
+ if (totalUnreadMessages > 0) {
221
+ if (totalUnreadMessages > 99)
222
+ return "99+";
223
+ else
224
+ return String(totalUnreadMessages);
225
+ }
226
+ // Empty fallback.
227
+ return "";
228
+ }
229
+ /**
230
+ * Returns a formatted string containing the total unread messages of a chat window.
231
+ * @param window The window instance to count the unread total messages.
232
+ * @param currentUserId The current chat instance user id. In this context it would be the sender.
233
+ */
234
+ static unreadMessagesTotal(window, currentUserId) {
235
+ let totalUnreadMessages = 0;
236
+ if (window) {
237
+ totalUnreadMessages = window.messages.filter(x => x.fromId != currentUserId && !x.dateSeen).length;
238
+ }
239
+ return MessageCounter.formatUnreadMessagesTotal(totalUnreadMessages);
240
+ }
241
+ }
242
+
243
+ function chatParticipantStatusDescriptor(status, localization) {
244
+ const currentStatus = ChatParticipantStatus[status].toString().toLowerCase();
245
+ return localization.statusDescription[currentStatus];
246
+ }
247
+
248
+ /*
249
+ * Sanitizes an URL resource
250
+ */
251
+ class SanitizePipe {
252
+ constructor(sanitizer) {
253
+ this.sanitizer = sanitizer;
254
+ }
255
+ transform(url) {
256
+ return this.sanitizer.bypassSecurityTrustResourceUrl(url);
257
+ }
258
+ }
259
+ SanitizePipe.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: SanitizePipe, deps: [{ token: i1.DomSanitizer }], target: i0.ɵɵFactoryTarget.Pipe });
260
+ SanitizePipe.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "14.3.0", ngImport: i0, type: SanitizePipe, name: "sanitize" });
261
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: SanitizePipe, decorators: [{
262
+ type: Pipe,
263
+ args: [{ name: 'sanitize' }]
264
+ }], ctorParameters: function () { return [{ type: i1.DomSanitizer }]; } });
265
+
266
+ class FirstLetterPipe {
267
+ transform(value) {
268
+ // const parts = value.split(" ");
269
+ // const firstLetter = parts[0][0];
270
+ // const secondLetters = parts.length > 1 ? parts[1].slice(0, 2) : '';
271
+ let parts = value.split(" ");
272
+ let firstLetter = parts[0].slice(0, 1);
273
+ let secondLetters = parts.length > 1 ? parts[1].slice(0, 1) : '';
274
+ return (firstLetter.toUpperCase() + secondLetters.toUpperCase());
275
+ }
276
+ }
277
+ FirstLetterPipe.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: FirstLetterPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe });
278
+ FirstLetterPipe.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "14.3.0", ngImport: i0, type: FirstLetterPipe, name: "firstLetterPipe" });
279
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: FirstLetterPipe, decorators: [{
280
+ type: Pipe,
281
+ args: [{ name: 'firstLetterPipe' }]
282
+ }] });
283
+
284
+ /* eslint-disable */
285
+ class NgChatFriendsListComponent {
286
+ constructor() {
287
+ this.participantsInteractedWith = [];
288
+ this.onParticipantClicked = new EventEmitter();
289
+ this.onOptionPromptCanceled = new EventEmitter();
290
+ this.onOptionPromptConfirmed = new EventEmitter();
291
+ this.hideFriendsList = false;
292
+ this.onCloseWindowClick = new EventEmitter();
293
+ this.selectedUsersFromFriendsList = [];
294
+ this.searchInput = '';
295
+ // Exposes enums and functions for the ng-template
296
+ this.ChatParticipantStatus = ChatParticipantStatus;
297
+ this.chatParticipantStatusDescriptor = chatParticipantStatusDescriptor;
298
+ this.cleanUpUserSelection = () => this.selectedUsersFromFriendsList = [];
299
+ }
300
+ ngOnChanges(changes) {
301
+ if (this.currentActiveOption) {
302
+ const currentOptionTriggeredBy = this.currentActiveOption && this.currentActiveOption.chattingTo.participant.id;
303
+ const isActivatedUserInSelectedList = (this.selectedUsersFromFriendsList.filter(item => item.id == currentOptionTriggeredBy)).length > 0;
304
+ if (!isActivatedUserInSelectedList) {
305
+ this.selectedUsersFromFriendsList = this.selectedUsersFromFriendsList.concat(this.currentActiveOption.chattingTo.participant);
306
+ }
307
+ }
308
+ // let input = user.displayName;
309
+ // let parts = input.split(" ");
310
+ // let firstLetter = parts[0];
311
+ // let secondLetters = parts[1].slice(0, 2);
312
+ }
313
+ get filteredParticipants() {
314
+ if (this.searchInput.length > 0) {
315
+ // Searches in the friend list by the inputted search string
316
+ return this.participants.filter(x => x.displayName.toUpperCase().includes(this.searchInput.toUpperCase()));
317
+ }
318
+ console.log("this.participants", this.participants);
319
+ return this.participants;
320
+ }
321
+ isUserSelectedFromFriendsList(user) {
322
+ return (this.selectedUsersFromFriendsList.filter(item => item.id == user.id)).length > 0;
323
+ }
324
+ unreadMessagesTotalByParticipant(participant) {
325
+ let openedWindow = this.windows.find(x => x.participant.id == participant.id);
326
+ if (openedWindow) {
327
+ return MessageCounter.unreadMessagesTotal(openedWindow, this.userId);
328
+ }
329
+ else {
330
+ let totalUnreadMessages = this.participantsResponse
331
+ .filter(x => x.participant.id == participant.id && !this.participantsInteractedWith.find(u => u.id == participant.id) && x.metadata && x.metadata.totalUnreadMessages > 0)
332
+ .map((participantResponse) => {
333
+ return participantResponse.metadata.totalUnreadMessages;
334
+ })[0];
335
+ return MessageCounter.formatUnreadMessagesTotal(totalUnreadMessages);
336
+ }
337
+ }
338
+ // Toggle friends list visibility
339
+ onChatTitleClicked() {
340
+ this.isCollapsed = !this.isCollapsed;
341
+ }
342
+ onFriendsListCheckboxChange(selectedUser, isChecked) {
343
+ if (isChecked) {
344
+ this.selectedUsersFromFriendsList.push(selectedUser);
345
+ }
346
+ else {
347
+ this.selectedUsersFromFriendsList.splice(this.selectedUsersFromFriendsList.indexOf(selectedUser), 1);
348
+ }
349
+ }
350
+ onUserClick(clickedUser) {
351
+ this.onParticipantClicked.emit(clickedUser);
352
+ }
353
+ onFriendsListActionCancelClicked() {
354
+ this.onOptionPromptCanceled.emit();
355
+ this.cleanUpUserSelection();
356
+ }
357
+ onFriendsListActionConfirmClicked() {
358
+ this.onOptionPromptConfirmed.emit(this.selectedUsersFromFriendsList);
359
+ this.cleanUpUserSelection();
360
+ }
361
+ onCloseWindow() {
362
+ this.onCloseWindowClick.emit(true);
363
+ console.log('hoi latest friendlist', this.shouldDisplay);
364
+ }
365
+ }
366
+ NgChatFriendsListComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: NgChatFriendsListComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
367
+ NgChatFriendsListComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "14.3.0", type: NgChatFriendsListComponent, selector: "ng-chat-friends-list", inputs: { participants: "participants", participantsResponse: "participantsResponse", participantsInteractedWith: "participantsInteractedWith", windows: "windows", userId: "userId", localization: "localization", shouldDisplay: "shouldDisplay", isCollapsed: "isCollapsed", searchEnabled: "searchEnabled", currentActiveOption: "currentActiveOption", hideFriendsList: "hideFriendsList" }, outputs: { onParticipantClicked: "onParticipantClicked", onOptionPromptCanceled: "onOptionPromptCanceled", onOptionPromptConfirmed: "onOptionPromptConfirmed", onCloseWindowClick: "onCloseWindowClick" }, usesOnChanges: true, ngImport: i0, template: "<div *ngIf=\"shouldDisplay\" id=\"ng-chat-people\" [ngClass]=\"{'primary-outline-color': true, 'primary-background': true, 'ng-chat-people-collapsed': isCollapsed}\">\n\t<a href=\"javascript:void(0);\" class=\"ng-chat-title secondary-background shadowed\" (click)=\"onChatTitleClicked()\">\n\t\t<span>\n\t\t\t{{localization.title}}\n\t\t</span>\n\t<a href=\"javascript:void(0);\" class=\" ng-chat-close \" (click)=\"onCloseWindow()\" >&#x2715;</a>\n\t</a>\n\t<div *ngIf=\"currentActiveOption\" class=\"ng-chat-people-actions\" (click)=\"onFriendsListActionCancelClicked()\">\n\t\t<a href=\"javascript:void(0);\" class=\"ng-chat-people-action\">\n\t\t\t<i class=\"remove-icon\"></i>\n\t\t</a>\n\t\t<a href=\"javascript:void(0);\" class=\"ng-chat-people-action\" (click)=\"onFriendsListActionConfirmClicked()\">\n\t\t\t<i class=\"check-icon\"></i>\n\t\t</a>\n\t</div>\n\t<input *ngIf=\"searchEnabled\" id=\"ng-chat-search_friend\" class=\"friends-search-bar\" type=\"search\" [placeholder]=\"localization.searchPlaceholder\" [(ngModel)]=\"searchInput\" />\n\t<ul id=\"ng-chat-users\" *ngIf=\"!isCollapsed\" [ngClass]=\"{'offset-search': searchEnabled}\">\n\t\t<li *ngFor=\"let user of filteredParticipants\">\n\t\t\t<input \n\t\t\t\t*ngIf=\"currentActiveOption && currentActiveOption.validateContext(user)\" \n\t\t\t\ttype=\"checkbox\" \n\t\t\t\tclass=\"ng-chat-users-checkbox\" \n\t\t\t\t(change)=\"onFriendsListCheckboxChange(user, $event.target.checked)\" \n\t\t\t\t[checked]=\"isUserSelectedFromFriendsList(user)\"/>\n\t\t\t<div [ngClass]=\"{'ng-chat-friends-list-selectable-offset': currentActiveOption, 'ng-chat-friends-list-container': true}\" (click)=\"onUserClick(user)\">\n\t\t\t\t<!-- <div *ngIf=\"!user.avatar\" class=\"icon-wrapper\">\n\t\t\t\t\t<i class=\"user-icon\"></i>\n\t\t\t\t</div> -->\n\t\t\t\t<div class=\"user\" *ngIf=\"!user.avatar\">\n\t\t\t\t\t{{user.displayName|firstLetterPipe}}\n\n\t\t\t\t</div>\n\n\t\t\t\t<img *ngIf=\"user.avatar\" alt=\"\" class=\"avatar\" height=\"30\" width=\"30\" [src]=\"user.avatar | sanitize\"/>\n\t\t\t\t<div class=\"\"></div>\n\t\t\t\t<strong title=\"{{user.displayName}}\">{{user.displayName}}</strong>\n\t\t\t\t<span [ngClass]=\"{'ng-chat-participant-status': true, 'online': user.status === ChatParticipantStatus.Online, 'busy': user.status === ChatParticipantStatus.Busy, 'away': user.status === ChatParticipantStatus.Away, 'offline': user.status === ChatParticipantStatus.Offline}\" title=\"{{chatParticipantStatusDescriptor(user.status, localization)}}\"></span>\n\t\t\t\t<span *ngIf=\"unreadMessagesTotalByParticipant(user).length > 0\" class=\"ng-chat-unread-messages-count unread-messages-counter-container primary-text\">{{unreadMessagesTotalByParticipant(user)}}</span>\n\t\t\t</div>\n\t\t</li>\n\t</ul>\n</div>", styles: ["#ng-chat-people{position:relative;width:240px;height:360px;border-width:1px;border-style:solid;margin-right:20px;box-shadow:0 4px 8px #00000040;border-bottom:0;border-radius:10px!important;box-sizing:border-box!important;overflow:hidden!important;margin-bottom:10px!important;border:1px solid grey!important}#ng-chat-people.ng-chat-people-collapsed{height:30px}#ng-chat-search_friend{display:block;padding:7px 10px;margin:10px auto 0;width:calc(100% - 20px);font-size:.9em;border-radius:20px!important;border:.5px solid grey!important}#ng-chat-users{padding:0 10px;list-style:none;margin:0;overflow:auto;position:absolute;top:42px;bottom:0;width:100%;box-sizing:border-box}#ng-chat-users.offset-search{top:84px}#ng-chat-users .ng-chat-users-checkbox{float:left;margin-right:5px;margin-top:8px}#ng-chat-users li{clear:both;margin-bottom:10px;overflow:hidden;cursor:pointer;max-height:30px}#ng-chat-users li>.ng-chat-friends-list-selectable-offset{margin-left:22px}#ng-chat-users li .ng-chat-friends-list-container{display:inline-block;width:100%}#ng-chat-users li>.ng-chat-friends-list-selectable-offset.ng-chat-friends-list-container{display:block;width:auto}#ng-chat-users li .ng-chat-friends-list-container>img.avatar,#ng-chat-users li .ng-chat-friends-list-container>.user{float:left;display:flex;justify-content:center;margin-right:5px}#ng-chat-users li .ng-chat-friends-list-container>.user{overflow:hidden;width:30px;height:30px}#ng-chat-users li .ng-chat-friends-list-container>.user>i{color:#fff;transform:scale(.7)}#ng-chat-users li .ng-chat-friends-list-container>strong{float:left;line-height:30px;font-size:.8em;max-width:57%;max-height:30px;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}#ng-chat-users li .ng-chat-friends-list-container>.ng-chat-participant-status{float:right}.ng-chat-people-actions{position:absolute;top:4px;right:5px;margin:0;padding:0;z-index:2}.ng-chat-people-actions>a.ng-chat-people-action{display:inline-block;width:21px;height:21px;margin-right:8px;text-decoration:none;border:none;border-radius:25px;padding:1px}@media only screen and (max-width: 581px){#ng-chat-people{width:300px;height:360px;margin-right:0}}.user{display:flex;align-items:center;justify-content:center;width:24px;height:24px;border-radius:50%;background-color:#00b04c;color:#fff;font-size:12px;font-family:inherit}\n"], dependencies: [{ kind: "directive", type: i1$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: i2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "pipe", type: SanitizePipe, name: "sanitize" }, { kind: "pipe", type: FirstLetterPipe, name: "firstLetterPipe" }], encapsulation: i0.ViewEncapsulation.None });
368
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: NgChatFriendsListComponent, decorators: [{
369
+ type: Component,
370
+ args: [{ selector: 'ng-chat-friends-list', encapsulation: ViewEncapsulation.None, template: "<div *ngIf=\"shouldDisplay\" id=\"ng-chat-people\" [ngClass]=\"{'primary-outline-color': true, 'primary-background': true, 'ng-chat-people-collapsed': isCollapsed}\">\n\t<a href=\"javascript:void(0);\" class=\"ng-chat-title secondary-background shadowed\" (click)=\"onChatTitleClicked()\">\n\t\t<span>\n\t\t\t{{localization.title}}\n\t\t</span>\n\t<a href=\"javascript:void(0);\" class=\" ng-chat-close \" (click)=\"onCloseWindow()\" >&#x2715;</a>\n\t</a>\n\t<div *ngIf=\"currentActiveOption\" class=\"ng-chat-people-actions\" (click)=\"onFriendsListActionCancelClicked()\">\n\t\t<a href=\"javascript:void(0);\" class=\"ng-chat-people-action\">\n\t\t\t<i class=\"remove-icon\"></i>\n\t\t</a>\n\t\t<a href=\"javascript:void(0);\" class=\"ng-chat-people-action\" (click)=\"onFriendsListActionConfirmClicked()\">\n\t\t\t<i class=\"check-icon\"></i>\n\t\t</a>\n\t</div>\n\t<input *ngIf=\"searchEnabled\" id=\"ng-chat-search_friend\" class=\"friends-search-bar\" type=\"search\" [placeholder]=\"localization.searchPlaceholder\" [(ngModel)]=\"searchInput\" />\n\t<ul id=\"ng-chat-users\" *ngIf=\"!isCollapsed\" [ngClass]=\"{'offset-search': searchEnabled}\">\n\t\t<li *ngFor=\"let user of filteredParticipants\">\n\t\t\t<input \n\t\t\t\t*ngIf=\"currentActiveOption && currentActiveOption.validateContext(user)\" \n\t\t\t\ttype=\"checkbox\" \n\t\t\t\tclass=\"ng-chat-users-checkbox\" \n\t\t\t\t(change)=\"onFriendsListCheckboxChange(user, $event.target.checked)\" \n\t\t\t\t[checked]=\"isUserSelectedFromFriendsList(user)\"/>\n\t\t\t<div [ngClass]=\"{'ng-chat-friends-list-selectable-offset': currentActiveOption, 'ng-chat-friends-list-container': true}\" (click)=\"onUserClick(user)\">\n\t\t\t\t<!-- <div *ngIf=\"!user.avatar\" class=\"icon-wrapper\">\n\t\t\t\t\t<i class=\"user-icon\"></i>\n\t\t\t\t</div> -->\n\t\t\t\t<div class=\"user\" *ngIf=\"!user.avatar\">\n\t\t\t\t\t{{user.displayName|firstLetterPipe}}\n\n\t\t\t\t</div>\n\n\t\t\t\t<img *ngIf=\"user.avatar\" alt=\"\" class=\"avatar\" height=\"30\" width=\"30\" [src]=\"user.avatar | sanitize\"/>\n\t\t\t\t<div class=\"\"></div>\n\t\t\t\t<strong title=\"{{user.displayName}}\">{{user.displayName}}</strong>\n\t\t\t\t<span [ngClass]=\"{'ng-chat-participant-status': true, 'online': user.status === ChatParticipantStatus.Online, 'busy': user.status === ChatParticipantStatus.Busy, 'away': user.status === ChatParticipantStatus.Away, 'offline': user.status === ChatParticipantStatus.Offline}\" title=\"{{chatParticipantStatusDescriptor(user.status, localization)}}\"></span>\n\t\t\t\t<span *ngIf=\"unreadMessagesTotalByParticipant(user).length > 0\" class=\"ng-chat-unread-messages-count unread-messages-counter-container primary-text\">{{unreadMessagesTotalByParticipant(user)}}</span>\n\t\t\t</div>\n\t\t</li>\n\t</ul>\n</div>", styles: ["#ng-chat-people{position:relative;width:240px;height:360px;border-width:1px;border-style:solid;margin-right:20px;box-shadow:0 4px 8px #00000040;border-bottom:0;border-radius:10px!important;box-sizing:border-box!important;overflow:hidden!important;margin-bottom:10px!important;border:1px solid grey!important}#ng-chat-people.ng-chat-people-collapsed{height:30px}#ng-chat-search_friend{display:block;padding:7px 10px;margin:10px auto 0;width:calc(100% - 20px);font-size:.9em;border-radius:20px!important;border:.5px solid grey!important}#ng-chat-users{padding:0 10px;list-style:none;margin:0;overflow:auto;position:absolute;top:42px;bottom:0;width:100%;box-sizing:border-box}#ng-chat-users.offset-search{top:84px}#ng-chat-users .ng-chat-users-checkbox{float:left;margin-right:5px;margin-top:8px}#ng-chat-users li{clear:both;margin-bottom:10px;overflow:hidden;cursor:pointer;max-height:30px}#ng-chat-users li>.ng-chat-friends-list-selectable-offset{margin-left:22px}#ng-chat-users li .ng-chat-friends-list-container{display:inline-block;width:100%}#ng-chat-users li>.ng-chat-friends-list-selectable-offset.ng-chat-friends-list-container{display:block;width:auto}#ng-chat-users li .ng-chat-friends-list-container>img.avatar,#ng-chat-users li .ng-chat-friends-list-container>.user{float:left;display:flex;justify-content:center;margin-right:5px}#ng-chat-users li .ng-chat-friends-list-container>.user{overflow:hidden;width:30px;height:30px}#ng-chat-users li .ng-chat-friends-list-container>.user>i{color:#fff;transform:scale(.7)}#ng-chat-users li .ng-chat-friends-list-container>strong{float:left;line-height:30px;font-size:.8em;max-width:57%;max-height:30px;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}#ng-chat-users li .ng-chat-friends-list-container>.ng-chat-participant-status{float:right}.ng-chat-people-actions{position:absolute;top:4px;right:5px;margin:0;padding:0;z-index:2}.ng-chat-people-actions>a.ng-chat-people-action{display:inline-block;width:21px;height:21px;margin-right:8px;text-decoration:none;border:none;border-radius:25px;padding:1px}@media only screen and (max-width: 581px){#ng-chat-people{width:300px;height:360px;margin-right:0}}.user{display:flex;align-items:center;justify-content:center;width:24px;height:24px;border-radius:50%;background-color:#00b04c;color:#fff;font-size:12px;font-family:inherit}\n"] }]
371
+ }], ctorParameters: function () { return []; }, propDecorators: { participants: [{
372
+ type: Input
373
+ }], participantsResponse: [{
374
+ type: Input
375
+ }], participantsInteractedWith: [{
376
+ type: Input
377
+ }], windows: [{
378
+ type: Input
379
+ }], userId: [{
380
+ type: Input
381
+ }], localization: [{
382
+ type: Input
383
+ }], shouldDisplay: [{
384
+ type: Input
385
+ }], isCollapsed: [{
386
+ type: Input
387
+ }], searchEnabled: [{
388
+ type: Input
389
+ }], currentActiveOption: [{
390
+ type: Input
391
+ }], onParticipantClicked: [{
392
+ type: Output
393
+ }], onOptionPromptCanceled: [{
394
+ type: Output
395
+ }], onOptionPromptConfirmed: [{
396
+ type: Output
397
+ }], hideFriendsList: [{
398
+ type: Input
399
+ }], onCloseWindowClick: [{
400
+ type: Output
401
+ }] } });
402
+
403
+ class Message {
404
+ constructor() {
405
+ this.type = MessageType.Text;
406
+ }
407
+ }
408
+
409
+ let emojiDictionary = [
410
+ { patterns: [':)', ':-)', '=)'], unicode: '😃' },
411
+ { patterns: [':D', ':-D', '=D'], unicode: '😀' },
412
+ { patterns: [':(', ':-(', '=('], unicode: '🙁' },
413
+ { patterns: [':|', ':-|', '=|'], unicode: '😐' },
414
+ { patterns: [':*', ':-*', '=*'], unicode: '😙' },
415
+ { patterns: ['T_T', 'T.T'], unicode: '😭' },
416
+ { patterns: [':O', ':-O', '=O', ':o', ':-o', '=o'], unicode: '😮' },
417
+ { patterns: [':P', ':-P', '=P', ':p', ':-p', '=p'], unicode: '😋' },
418
+ { patterns: ['>.<'], unicode: '😣' },
419
+ { patterns: ['@.@'], unicode: '😵' },
420
+ { patterns: ['*.*'], unicode: '😍' },
421
+ { patterns: ['<3'], unicode: '❤️' },
422
+ { patterns: ['^.^'], unicode: '😊' },
423
+ { patterns: [':+1'], unicode: '👍' },
424
+ { patterns: [':-1'], unicode: '👎' }
425
+ ];
426
+ /*
427
+ * Transforms common emoji text to UTF encoded emojis
428
+ */
429
+ class EmojifyPipe {
430
+ transform(message, pipeEnabled) {
431
+ if (pipeEnabled && message && message.length > 1) {
432
+ emojiDictionary.forEach(emoji => {
433
+ emoji.patterns.forEach(pattern => {
434
+ message = message.replace(pattern, emoji.unicode);
435
+ });
436
+ });
437
+ }
438
+ return message;
439
+ }
440
+ }
441
+ EmojifyPipe.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: EmojifyPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe });
442
+ EmojifyPipe.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "14.3.0", ngImport: i0, type: EmojifyPipe, name: "emojify" });
443
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: EmojifyPipe, decorators: [{
444
+ type: Pipe,
445
+ args: [{ name: 'emojify' }]
446
+ }] });
447
+
448
+ /*
449
+ * Transforms text containing URLs or E-mails to valid links/mailtos
450
+ */
451
+ class LinkfyPipe {
452
+ transform(message, pipeEnabled) {
453
+ if (pipeEnabled && message && message.length > 1) {
454
+ let replacedText;
455
+ let replacePatternProtocol;
456
+ let replacePatternWWW;
457
+ let replacePatternMailTo;
458
+ // URLs starting with http://, https://, or ftp://
459
+ replacePatternProtocol = /(\b(https?|ftp):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/gim;
460
+ replacedText = message.replace(replacePatternProtocol, '<a href="$1" target="_blank">$1</a>');
461
+ // URLs starting with "www." (ignoring // before it).
462
+ replacePatternWWW = /(^|[^\/])(www\.[\S]+(\b|$))/gim;
463
+ replacedText = replacedText.replace(replacePatternWWW, '$1<a href="http://$2" target="_blank">$2</a>');
464
+ // Change email addresses to mailto:: links.
465
+ replacePatternMailTo = /(([a-zA-Z0-9\-\_\.])+@[a-zA-Z\_]+?(\.[a-zA-Z]{2,6})+)/gim;
466
+ replacedText = replacedText.replace(replacePatternMailTo, '<a href="mailto:$1">$1</a>');
467
+ return replacedText;
468
+ }
469
+ else
470
+ return message;
471
+ }
472
+ }
473
+ LinkfyPipe.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: LinkfyPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe });
474
+ LinkfyPipe.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "14.3.0", ngImport: i0, type: LinkfyPipe, name: "linkfy" });
475
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: LinkfyPipe, decorators: [{
476
+ type: Pipe,
477
+ args: [{ name: 'linkfy' }]
478
+ }] });
479
+
480
+ /*
481
+ * Renders the display name of a participant in a group based on who's sent the message
482
+ */
483
+ class GroupMessageDisplayNamePipe {
484
+ transform(participant, message) {
485
+ if (participant && participant.participantType == ChatParticipantType.Group) {
486
+ let group = participant;
487
+ let userIndex = group.chattingTo.findIndex(x => x.id == message.fromId);
488
+ return group.chattingTo[userIndex >= 0 ? userIndex : 0].displayName;
489
+ }
490
+ else
491
+ return "";
492
+ }
493
+ }
494
+ GroupMessageDisplayNamePipe.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: GroupMessageDisplayNamePipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe });
495
+ GroupMessageDisplayNamePipe.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "14.3.0", ngImport: i0, type: GroupMessageDisplayNamePipe, name: "groupMessageDisplayName" });
496
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: GroupMessageDisplayNamePipe, decorators: [{
497
+ type: Pipe,
498
+ args: [{ name: 'groupMessageDisplayName' }]
499
+ }] });
500
+
501
+ /* eslint-disable */
502
+ class NgChatWindowComponent {
503
+ constructor() {
504
+ this.emojisEnabled = true;
505
+ this.linkfyEnabled = true;
506
+ this.showMessageDate = true;
507
+ this.messageDatePipeFormat = "short";
508
+ this.hasPagedHistory = true;
509
+ this.onChatWindowClosed = new EventEmitter();
510
+ this.onMessagesSeen = new EventEmitter();
511
+ this.onMessageSent = new EventEmitter();
512
+ this.onTabTriggered = new EventEmitter();
513
+ this.onOptionTriggered = new EventEmitter();
514
+ this.onLoadHistoryTriggered = new EventEmitter();
515
+ // File upload state
516
+ this.fileUploadersInUse = []; // Id bucket of uploaders in use
517
+ // Exposes enums and functions for the ng-template
518
+ this.ChatParticipantType = ChatParticipantType;
519
+ this.ChatParticipantStatus = ChatParticipantStatus;
520
+ this.MessageType = MessageType;
521
+ this.chatParticipantStatusDescriptor = chatParticipantStatusDescriptor;
522
+ }
523
+ defaultWindowOptions(currentWindow) {
524
+ if (this.showOptions && currentWindow.participant.participantType == ChatParticipantType.User) {
525
+ return [{
526
+ isActive: false,
527
+ chattingTo: currentWindow,
528
+ validateContext: (participant) => {
529
+ return participant.participantType == ChatParticipantType.User;
530
+ },
531
+ displayLabel: 'Add People' // TODO: Localize this
532
+ }];
533
+ }
534
+ return [];
535
+ }
536
+ // Asserts if a user avatar is visible in a chat cluster
537
+ isAvatarVisible(window, message, index) {
538
+ if (message.fromId != this.userId) {
539
+ if (index == 0) {
540
+ return true; // First message, good to show the thumbnail
541
+ }
542
+ else {
543
+ // Check if the previous message belongs to the same user, if it belongs there is no need to show the avatar again to form the message cluster
544
+ if (window.messages[index - 1].fromId != message.fromId) {
545
+ return true;
546
+ }
547
+ }
548
+ }
549
+ return false;
550
+ }
551
+ getChatWindowAvatar(participant, message) {
552
+ if (participant.participantType == ChatParticipantType.User) {
553
+ return participant.avatar;
554
+ }
555
+ else if (participant.participantType == ChatParticipantType.Group) {
556
+ let group = participant;
557
+ let userIndex = group.chattingTo.findIndex(x => x.id == message.fromId);
558
+ return group.chattingTo[userIndex >= 0 ? userIndex : 0].avatar;
559
+ }
560
+ return null;
561
+ }
562
+ isUploadingFile(window) {
563
+ const fileUploadInstanceId = this.getUniqueFileUploadInstanceId(window);
564
+ return this.fileUploadersInUse.indexOf(fileUploadInstanceId) > -1;
565
+ }
566
+ // Generates a unique file uploader id for each participant
567
+ getUniqueFileUploadInstanceId(window) {
568
+ if (window && window.participant) {
569
+ return `ng-chat-file-upload-${window.participant.id}`;
570
+ }
571
+ return 'ng-chat-file-upload';
572
+ }
573
+ unreadMessagesTotal(window) {
574
+ return MessageCounter.unreadMessagesTotal(window, this.userId);
575
+ }
576
+ // Scrolls a chat window message flow to the bottom
577
+ scrollChatWindow(window, direction) {
578
+ if (!window.isCollapsed) {
579
+ setTimeout(() => {
580
+ if (this.chatMessages) {
581
+ let element = this.chatMessages.nativeElement;
582
+ let position = (direction === ScrollDirection.Top) ? 0 : element.scrollHeight;
583
+ element.scrollTop = position;
584
+ }
585
+ });
586
+ }
587
+ }
588
+ activeOptionTrackerChange(option) {
589
+ this.onOptionTriggered.emit(option);
590
+ }
591
+ // Triggers native file upload for file selection from the user
592
+ triggerNativeFileUpload(window) {
593
+ if (window) {
594
+ if (this.nativeFileInput)
595
+ this.nativeFileInput.nativeElement.click();
596
+ }
597
+ }
598
+ // Toggles a window focus on the focus/blur of a 'newMessage' input
599
+ toggleWindowFocus(window) {
600
+ window.hasFocus = !window.hasFocus;
601
+ if (window.hasFocus) {
602
+ const unreadMessages = window.messages
603
+ .filter(message => message.dateSeen == null
604
+ && (message.toId == this.userId || window.participant.participantType === ChatParticipantType.Group));
605
+ if (unreadMessages && unreadMessages.length > 0) {
606
+ this.onMessagesSeen.emit(unreadMessages);
607
+ }
608
+ }
609
+ }
610
+ markMessagesAsRead(messages) {
611
+ this.onMessagesSeen.emit(messages);
612
+ }
613
+ fetchMessageHistory(window) {
614
+ this.onLoadHistoryTriggered.emit(window);
615
+ }
616
+ // Closes a chat window via the close 'X' button
617
+ onCloseChatWindow() {
618
+ this.onChatWindowClosed.emit({ closedWindow: this.window, closedViaEscapeKey: false });
619
+ }
620
+ /* Monitors pressed keys on a chat window
621
+ - Dispatches a message when the ENTER key is pressed
622
+ - Tabs between windows on TAB or SHIFT + TAB
623
+ - Closes the current focused window on ESC
624
+ */
625
+ onChatInputTyped(event, window) {
626
+ switch (event.keyCode) {
627
+ case 13:
628
+ if (window.newMessage && window.newMessage.trim() != "") {
629
+ let message = new Message();
630
+ message.fromId = this.userId;
631
+ message.toId = window.participant.id;
632
+ message.message = window.newMessage;
633
+ message.dateSent = new Date();
634
+ window.messages.push(message);
635
+ this.onMessageSent.emit(message);
636
+ window.newMessage = ""; // Resets the new message input
637
+ this.scrollChatWindow(window, ScrollDirection.Bottom);
638
+ }
639
+ break;
640
+ case 9:
641
+ event.preventDefault();
642
+ this.onTabTriggered.emit({ triggeringWindow: window, shiftKeyPressed: event.shiftKey });
643
+ break;
644
+ case 27:
645
+ this.onChatWindowClosed.emit({ closedWindow: window, closedViaEscapeKey: true });
646
+ break;
647
+ }
648
+ }
649
+ // Toggles a chat window visibility between maximized/minimized
650
+ onChatWindowClicked(window) {
651
+ window.isCollapsed = !window.isCollapsed;
652
+ this.scrollChatWindow(window, ScrollDirection.Bottom);
653
+ }
654
+ clearInUseFileUploader(fileUploadInstanceId) {
655
+ const uploaderInstanceIdIndex = this.fileUploadersInUse.indexOf(fileUploadInstanceId);
656
+ if (uploaderInstanceIdIndex > -1) {
657
+ this.fileUploadersInUse.splice(uploaderInstanceIdIndex, 1);
658
+ }
659
+ }
660
+ // Handles file selection and uploads the selected file using the file upload adapter
661
+ onFileChosen(window) {
662
+ const fileUploadInstanceId = this.getUniqueFileUploadInstanceId(window);
663
+ const uploadElementRef = this.nativeFileInput;
664
+ if (uploadElementRef) {
665
+ const file = uploadElementRef.nativeElement.files[0];
666
+ this.fileUploadersInUse.push(fileUploadInstanceId);
667
+ this.fileUploadAdapter.uploadFile(file, window.participant.id)
668
+ .subscribe(fileMessage => {
669
+ this.clearInUseFileUploader(fileUploadInstanceId);
670
+ fileMessage.fromId = this.userId;
671
+ // Push file message to current user window
672
+ window.messages.push(fileMessage);
673
+ this.onMessageSent.emit(fileMessage);
674
+ this.scrollChatWindow(window, ScrollDirection.Bottom);
675
+ // Resets the file upload element
676
+ uploadElementRef.nativeElement.value = '';
677
+ }, (error) => {
678
+ this.clearInUseFileUploader(fileUploadInstanceId);
679
+ // Resets the file upload element
680
+ uploadElementRef.nativeElement.value = '';
681
+ // TODO: Invoke a file upload adapter error here
682
+ });
683
+ }
684
+ }
685
+ }
686
+ NgChatWindowComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: NgChatWindowComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
687
+ NgChatWindowComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "14.3.0", type: NgChatWindowComponent, selector: "ng-chat-window", inputs: { fileUploadAdapter: "fileUploadAdapter", window: "window", userId: "userId", localization: "localization", showOptions: "showOptions", emojisEnabled: "emojisEnabled", linkfyEnabled: "linkfyEnabled", showMessageDate: "showMessageDate", messageDatePipeFormat: "messageDatePipeFormat", hasPagedHistory: "hasPagedHistory" }, outputs: { onChatWindowClosed: "onChatWindowClosed", onMessagesSeen: "onMessagesSeen", onMessageSent: "onMessageSent", onTabTriggered: "onTabTriggered", onOptionTriggered: "onOptionTriggered", onLoadHistoryTriggered: "onLoadHistoryTriggered" }, viewQueries: [{ propertyName: "chatMessages", first: true, predicate: ["chatMessages"], descendants: true }, { propertyName: "nativeFileInput", first: true, predicate: ["nativeFileInput"], descendants: true }, { propertyName: "chatWindowInput", first: true, predicate: ["chatWindowInput"], descendants: true }], ngImport: i0, template: "<ng-container *ngIf=\"window && window.isCollapsed\">\n\t<div class=\"ng-chat-title secondary-background\">\n\t\t<div class=\"ng-chat-title-visibility-toggle-area\" (click)=\"onChatWindowClicked(window)\">\n\t\t\t<strong title=\"{{window.participant.displayName}}\">\n\t\t\t\t{{window.participant.displayName}}\n\t\t\t</strong>\n\t\t\t<span [ngClass]=\"{'ng-chat-participant-status': true, 'online': window.participant.status === ChatParticipantStatus.Online, 'busy': window.participant.status === ChatParticipantStatus.Busy, 'away': window.participant.status === ChatParticipantStatus.Away, 'offline': window.participant.status === ChatParticipantStatus.Offline}\" title=\"{{chatParticipantStatusDescriptor(window.participant.status, localization)}}\"></span>\n\t\t\t<span *ngIf=\"unreadMessagesTotal(window).length > 0\" class=\"ng-chat-unread-messages-count unread-messages-counter-container primary-text\">{{unreadMessagesTotal(window)}}</span>\n\t\t</div>\n\t\t<a href=\"javascript:void(0);\" class=\"ng-chat-close primary-text\" (click)=\"onCloseChatWindow()\">&#x2715;</a>\n\t</div>\n</ng-container>\n<ng-container *ngIf=\"window && !window.isCollapsed\"> \n\t<div class=\"ng-chat-title secondary-background\">\n\t\t<div class=\"ng-chat-title-visibility-toggle-area\" (click)=\"onChatWindowClicked(window)\">\n\t\t\t<strong title=\"{{window.participant.displayName}}\">\n\t\t\t\t{{window.participant.displayName}}\n\t\t\t</strong>\n\t\t\t<span [ngClass]=\"{'ng-chat-participant-status': true, 'online': window.participant.status === ChatParticipantStatus.Online, 'busy': window.participant.status === ChatParticipantStatus.Busy, 'away': window.participant.status === ChatParticipantStatus.Away, 'offline': window.participant.status === ChatParticipantStatus.Offline}\" title=\"{{chatParticipantStatusDescriptor(window.participant.status, localization)}}\"></span>\n\t\t\t<span *ngIf=\"unreadMessagesTotal(window).length > 0\" class=\"ng-chat-unread-messages-count unread-messages-counter-container primary-text\">{{unreadMessagesTotal(window)}}</span>\n\t\t</div>\n\t\t<a href=\"javascript:void(0);\" class=\"ng-chat-close primary-text\" (click)=\"onCloseChatWindow()\">&#x2715;</a>\n\t\t<!-- <ng-chat-options [ngClass]=\"'ng-chat-options-container'\" [options]=\"defaultWindowOptions(window)\" (activeOptionTrackerChange)=\"activeOptionTrackerChange($event)\"></ng-chat-options> -->\n\t</div>\n\t\t<div #chatMessages class=\"ng-chat-messages primary-background\">\n\t\t\t<div *ngIf=\"window.isLoadingHistory\" class=\"ng-chat-loading-wrapper\">\n\t\t\t\t<div class=\"loader\">Loading history...</div>\n\t\t\t</div>\n\t\t\t<div *ngIf=\"hasPagedHistory && window.hasMoreMessages && !window.isLoadingHistory\" class=\"ng-chat-load-history\">\n\t\t\t\t<a class=\"load-history-action\" (click)=\"fetchMessageHistory(window)\">{{localization.loadMessageHistoryPlaceholder}}</a>\n\t\t\t</div>\n\n\t\t\t<div *ngFor=\"let message of window.messages; let i = index\" [ngClass]=\"{'ng-chat-message': true, 'ng-chat-message-received': message.fromId !== userId}\">\n\t\t\t\t<ng-container *ngIf=\"isAvatarVisible(window, message, i)\">\n\t\t\t\t\t<div *ngIf=\"!getChatWindowAvatar(window.participant, message)\" class=\"icon-wrapper\">\n\t\t\t\t\t\t<i class=\"user-icon\"></i>\n\t\t\t\t\t</div>\n\t\t\t\t\t<!-- <img *ngIf=\"getChatWindowAvatar(window.participant, message)\" alt=\"\" class=\"avatar\" height=\"30\" width=\"30\" [src]=\"getChatWindowAvatar(window.participant, message) | sanitize\" /> -->\n\t\t\t\t\t<span *ngIf=\"window.participant.participantType === ChatParticipantType.Group\" class=\"ng-chat-participant-name\">{{window.participant | groupMessageDisplayName:message}}</span>\n\t\t\t\t</ng-container>\n\t\t\t\t<ng-container [ngSwitch]=\"message.type\">\n\t\t\t\t\t<div *ngSwitchCase=\"MessageType.Text\" [ngClass]=\"{'sent-chat-message-container': message.fromId === userId, 'received-chat-message-container': message.fromId !== userId}\">\n\t\t\t \t\t\t<span [innerHtml]=\"message.message | emojify:emojisEnabled | linkfy:linkfyEnabled\"></span>\n\t\t\t\t\t\t<span *ngIf=\"showMessageDate && message.dateSent\" class=\"message-sent-date\">{{message.dateSent | date:'dd'}}/{{message.dateSent | date:'MM'}}/{{message.dateSent | date:'y'}} {{message.dateSent | date:'h:mm a '}}</span>\n\t\t\t\t\t</div>\n\t\t\t\t\t<div *ngSwitchCase=\"MessageType.Image\" [ngClass]=\"{'sent-chat-message-container': message.fromId === userId, 'received-chat-message-container': message.fromId !== userId}\">\n\t\n\t\t\t \t\t\t<img src=\"{{message.message}}\" class=\"image-message\" />\n\t\n\t\t\t\t\t\t<span *ngIf=\"showMessageDate && message.dateSent\" class=\"message-sent-date\">{{message.dateSent | date:'dd'}}/{{message.dateSent | date:'MM'}}/{{message.dateSent | date:'y'}} {{message.dateSent | date:'h:mm a '}}</span>\n\t\t\t\t\t</div>\n\t\t\t\t\t<div *ngSwitchCase=\"MessageType.File\" [ngClass]=\"{'file-message-container': true, 'received': message.fromId !== userId}\">\n\t\t\t\t\t\t<div class=\"file-message-icon-container\">\n\t\t\t\t\t\t\t<i class=\"paperclip-icon\"></i>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<!-- <a class=\"file-details\" [attr.href]=\"message.downloadUrl\" target=\"_blank\" rel=\"noopener noreferrer\" (click)=\"this.markMessagesAsRead([message])\" download>\n\t\t\t\t\t\t\t<span class=\"file-message-title\" [attr.title]=\"message.message\">{{message.message}}</span>\n\t\t\t\t\t\t\t<span *ngIf=\"message.fileSizeInBytes\" class=\"file-message-size\">{{message.fileSizeInBytes}} Bytes</span>\n\t\t\t\t\t\t</a> -->\n\t\t\t\t\t</div>\n\t\t\t\t</ng-container>\n\t\t\t</div>\n\t</div>\n\n\t<div class=\"ng-chat-footer primary-outline-color primary-background\">\n\t\t<input #chatWindowInput\n\t\t\ttype=\"text\"\n\t\t\t[ngModel]=\"window.newMessage | emojify:emojisEnabled\"\n\t\t\t(ngModelChange)=\"window.newMessage=$event\"\n\t\t\t[placeholder]=\"localization.messagePlaceholder\"\n\t\t\t[ngClass]=\"{'chat-window-input': true, 'has-side-action': fileUploadAdapter}\"\n\t\t\t(keydown)=\"onChatInputTyped($event, window)\"\n\t\t\t(blur)=\"toggleWindowFocus(window)\"\n\t\t\t(focus)=\"toggleWindowFocus(window)\"/>\n\n\t\t<!-- File Upload -->\n\t\t<ng-container *ngIf=\"fileUploadAdapter\">\n\t\t\t<a *ngIf=\"!isUploadingFile(window)\" class=\"btn-add-file\" (click)=\"triggerNativeFileUpload(window)\">\n\t\t\t\t<i class=\"upload-icon\"></i>\n\t\t\t</a>\n\t\t\t<input\n\t\t\t\ttype=\"file\"\n\t\t\t\t#nativeFileInput\n\t\t\t\tstyle=\"display: none;\"\n\t\t\t\t[attr.id]=\"getUniqueFileUploadInstanceId(window)\"\n\t\t\t\t(change)=\"onFileChosen(window)\" />\n\t\t\t<div *ngIf=\"isUploadingFile(window)\" class=\"loader\"></div>\n\t\t</ng-container>\n\t</div>\n</ng-container>", styles: [".ng-chat-window{right:260px;height:360px;z-index:999;bottom:0;width:300px;position:fixed;border-width:1px;border-style:solid;border-bottom:0;box-shadow:0 4px 8px #00000040;margin-bottom:10px!important;border:1px solid grey!important;border-radius:10px!important;box-sizing:border-box!important;overflow:hidden!important;background-color:#fff;::-webkit-scrollbar {width: 8px !important;} ::-webkit-scrollbar-thumb {background: #1b1b70 !important; border-radius: 5px !important;}}.ng-chat-window-collapsed{height:30px!important}.ng-chat-window .ng-chat-footer{box-sizing:border-box!important;padding-bottom:10px!important;display:block;height:10%;width:95%!important;margin:auto!important;background-color:inherit!important}.ng-chat-window .ng-chat-footer>input{font-size:.8em;box-sizing:border-box!important;padding:15px!important;height:100%;width:100%;border-radius:20px!important;border:1px solid grey!important}.ng-chat-window .ng-chat-footer>input.has-side-action{width:calc(100% - 30px)}.ng-chat-window .ng-chat-footer .btn-add-file{position:absolute;right:5px;bottom:7px;height:20px;width:20px;cursor:pointer}.ng-chat-window .ng-chat-footer .loader{position:absolute;right:14px;bottom:8px}.ng-chat-window .ng-chat-load-history{height:30px;text-align:center;font-size:.8em}.ng-chat-window .ng-chat-load-history>a{border-radius:15px;cursor:pointer;padding:5px 10px}.ng-chat-window .ng-chat-messages{padding:10px;width:100%;height:calc(90% - 30px);box-sizing:border-box;position:relative;overflow:auto}.ng-chat-window .ng-chat-messages .ng-chat-message{clear:both}.ng-chat-window .ng-chat-messages .ng-chat-message>img.avatar,.ng-chat-window .ng-chat-messages .ng-chat-message>.icon-wrapper{position:absolute;left:10px;border-radius:25px}.ng-chat-window .ng-chat-messages .ng-chat-message .ng-chat-participant-name{display:inline-block;margin-left:40px;padding-bottom:5px;font-weight:700;font-size:.8em;text-overflow:ellipsis;max-width:180px}.ng-chat-window .ng-chat-messages .ng-chat-message>.icon-wrapper{background-color:#bababa;overflow:hidden;width:30px;height:30px;padding:0}.ng-chat-window .ng-chat-messages .ng-chat-message>.icon-wrapper>i{color:#fff;transform:scale(.7)}.ng-chat-window .ng-chat-messages .ng-chat-message .message-sent-date{font-size:.5em!important;display:block;text-align:right;margin-top:5px;color:#a1a0a0!important;font-style:italic!important}.ng-chat-window .ng-chat-messages .ng-chat-message>div{float:right;width:182px;border-radius:15px 15px 0!important;padding:10px;margin-top:0;margin-bottom:5px;font-size:.9em;word-wrap:break-word;background-color:#93d4f0!important}.ng-chat-window .ng-chat-message-received>div.received-chat-message-container{float:left;margin-left:40px;padding-top:7px;padding-bottom:7px;margin-top:0;margin-bottom:5px;background-color:#daf2fc!important;border-radius:15px 15px 15px 0!important}.ng-chat-window .ng-chat-messages .ng-chat-message .file-message-container{float:right;width:202px;border-style:solid;border-width:3px;border-radius:5px;overflow:hidden;margin-bottom:5px;display:block;text-decoration:none;font-size:.9em;padding:0;box-sizing:border-box}.ng-chat-window .ng-chat-messages .ng-chat-message .file-message-container.received{float:left;margin-left:40px;width:208px}.ng-chat-window .ng-chat-messages .ng-chat-message .file-message-container>.file-message-icon-container{width:20px;height:35px;padding:10px 5px;float:left}.ng-chat-window .ng-chat-messages .ng-chat-message .file-message-container>.file-message-icon-container i{margin-top:8px}.ng-chat-window .ng-chat-messages .ng-chat-message .file-message-container>.file-details{float:left;padding:10px;width:calc(100% - 60px);color:currentColor;text-decoration:none}.ng-chat-window .ng-chat-messages .ng-chat-message .file-message-container>.file-details:hover{text-decoration:underline}.ng-chat-window .ng-chat-messages .ng-chat-message .file-message-container>.file-details span{display:block;width:100%;text-overflow:ellipsis;overflow:hidden;white-space:nowrap}.ng-chat-window .ng-chat-messages .ng-chat-message .file-message-container>.file-details .file-message-title{font-weight:700}.ng-chat-window .ng-chat-messages .ng-chat-message .file-message-container>.file-details .file-message-size{font-size:.8em;margin-top:5px}.ng-chat-window .image-message{width:100%;height:auto}@media only screen and (max-width: 581px){.ng-chat-window{position:initial}}\n"], dependencies: [{ kind: "directive", type: i1$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: i2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i2.NgSwitch, selector: "[ngSwitch]", inputs: ["ngSwitch"] }, { kind: "directive", type: i2.NgSwitchCase, selector: "[ngSwitchCase]", inputs: ["ngSwitchCase"] }, { kind: "pipe", type: i2.DatePipe, name: "date" }, { kind: "pipe", type: EmojifyPipe, name: "emojify" }, { kind: "pipe", type: LinkfyPipe, name: "linkfy" }, { kind: "pipe", type: GroupMessageDisplayNamePipe, name: "groupMessageDisplayName" }], encapsulation: i0.ViewEncapsulation.None });
688
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: NgChatWindowComponent, decorators: [{
689
+ type: Component,
690
+ args: [{ selector: 'ng-chat-window', encapsulation: ViewEncapsulation.None, template: "<ng-container *ngIf=\"window && window.isCollapsed\">\n\t<div class=\"ng-chat-title secondary-background\">\n\t\t<div class=\"ng-chat-title-visibility-toggle-area\" (click)=\"onChatWindowClicked(window)\">\n\t\t\t<strong title=\"{{window.participant.displayName}}\">\n\t\t\t\t{{window.participant.displayName}}\n\t\t\t</strong>\n\t\t\t<span [ngClass]=\"{'ng-chat-participant-status': true, 'online': window.participant.status === ChatParticipantStatus.Online, 'busy': window.participant.status === ChatParticipantStatus.Busy, 'away': window.participant.status === ChatParticipantStatus.Away, 'offline': window.participant.status === ChatParticipantStatus.Offline}\" title=\"{{chatParticipantStatusDescriptor(window.participant.status, localization)}}\"></span>\n\t\t\t<span *ngIf=\"unreadMessagesTotal(window).length > 0\" class=\"ng-chat-unread-messages-count unread-messages-counter-container primary-text\">{{unreadMessagesTotal(window)}}</span>\n\t\t</div>\n\t\t<a href=\"javascript:void(0);\" class=\"ng-chat-close primary-text\" (click)=\"onCloseChatWindow()\">&#x2715;</a>\n\t</div>\n</ng-container>\n<ng-container *ngIf=\"window && !window.isCollapsed\"> \n\t<div class=\"ng-chat-title secondary-background\">\n\t\t<div class=\"ng-chat-title-visibility-toggle-area\" (click)=\"onChatWindowClicked(window)\">\n\t\t\t<strong title=\"{{window.participant.displayName}}\">\n\t\t\t\t{{window.participant.displayName}}\n\t\t\t</strong>\n\t\t\t<span [ngClass]=\"{'ng-chat-participant-status': true, 'online': window.participant.status === ChatParticipantStatus.Online, 'busy': window.participant.status === ChatParticipantStatus.Busy, 'away': window.participant.status === ChatParticipantStatus.Away, 'offline': window.participant.status === ChatParticipantStatus.Offline}\" title=\"{{chatParticipantStatusDescriptor(window.participant.status, localization)}}\"></span>\n\t\t\t<span *ngIf=\"unreadMessagesTotal(window).length > 0\" class=\"ng-chat-unread-messages-count unread-messages-counter-container primary-text\">{{unreadMessagesTotal(window)}}</span>\n\t\t</div>\n\t\t<a href=\"javascript:void(0);\" class=\"ng-chat-close primary-text\" (click)=\"onCloseChatWindow()\">&#x2715;</a>\n\t\t<!-- <ng-chat-options [ngClass]=\"'ng-chat-options-container'\" [options]=\"defaultWindowOptions(window)\" (activeOptionTrackerChange)=\"activeOptionTrackerChange($event)\"></ng-chat-options> -->\n\t</div>\n\t\t<div #chatMessages class=\"ng-chat-messages primary-background\">\n\t\t\t<div *ngIf=\"window.isLoadingHistory\" class=\"ng-chat-loading-wrapper\">\n\t\t\t\t<div class=\"loader\">Loading history...</div>\n\t\t\t</div>\n\t\t\t<div *ngIf=\"hasPagedHistory && window.hasMoreMessages && !window.isLoadingHistory\" class=\"ng-chat-load-history\">\n\t\t\t\t<a class=\"load-history-action\" (click)=\"fetchMessageHistory(window)\">{{localization.loadMessageHistoryPlaceholder}}</a>\n\t\t\t</div>\n\n\t\t\t<div *ngFor=\"let message of window.messages; let i = index\" [ngClass]=\"{'ng-chat-message': true, 'ng-chat-message-received': message.fromId !== userId}\">\n\t\t\t\t<ng-container *ngIf=\"isAvatarVisible(window, message, i)\">\n\t\t\t\t\t<div *ngIf=\"!getChatWindowAvatar(window.participant, message)\" class=\"icon-wrapper\">\n\t\t\t\t\t\t<i class=\"user-icon\"></i>\n\t\t\t\t\t</div>\n\t\t\t\t\t<!-- <img *ngIf=\"getChatWindowAvatar(window.participant, message)\" alt=\"\" class=\"avatar\" height=\"30\" width=\"30\" [src]=\"getChatWindowAvatar(window.participant, message) | sanitize\" /> -->\n\t\t\t\t\t<span *ngIf=\"window.participant.participantType === ChatParticipantType.Group\" class=\"ng-chat-participant-name\">{{window.participant | groupMessageDisplayName:message}}</span>\n\t\t\t\t</ng-container>\n\t\t\t\t<ng-container [ngSwitch]=\"message.type\">\n\t\t\t\t\t<div *ngSwitchCase=\"MessageType.Text\" [ngClass]=\"{'sent-chat-message-container': message.fromId === userId, 'received-chat-message-container': message.fromId !== userId}\">\n\t\t\t \t\t\t<span [innerHtml]=\"message.message | emojify:emojisEnabled | linkfy:linkfyEnabled\"></span>\n\t\t\t\t\t\t<span *ngIf=\"showMessageDate && message.dateSent\" class=\"message-sent-date\">{{message.dateSent | date:'dd'}}/{{message.dateSent | date:'MM'}}/{{message.dateSent | date:'y'}} {{message.dateSent | date:'h:mm a '}}</span>\n\t\t\t\t\t</div>\n\t\t\t\t\t<div *ngSwitchCase=\"MessageType.Image\" [ngClass]=\"{'sent-chat-message-container': message.fromId === userId, 'received-chat-message-container': message.fromId !== userId}\">\n\t\n\t\t\t \t\t\t<img src=\"{{message.message}}\" class=\"image-message\" />\n\t\n\t\t\t\t\t\t<span *ngIf=\"showMessageDate && message.dateSent\" class=\"message-sent-date\">{{message.dateSent | date:'dd'}}/{{message.dateSent | date:'MM'}}/{{message.dateSent | date:'y'}} {{message.dateSent | date:'h:mm a '}}</span>\n\t\t\t\t\t</div>\n\t\t\t\t\t<div *ngSwitchCase=\"MessageType.File\" [ngClass]=\"{'file-message-container': true, 'received': message.fromId !== userId}\">\n\t\t\t\t\t\t<div class=\"file-message-icon-container\">\n\t\t\t\t\t\t\t<i class=\"paperclip-icon\"></i>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<!-- <a class=\"file-details\" [attr.href]=\"message.downloadUrl\" target=\"_blank\" rel=\"noopener noreferrer\" (click)=\"this.markMessagesAsRead([message])\" download>\n\t\t\t\t\t\t\t<span class=\"file-message-title\" [attr.title]=\"message.message\">{{message.message}}</span>\n\t\t\t\t\t\t\t<span *ngIf=\"message.fileSizeInBytes\" class=\"file-message-size\">{{message.fileSizeInBytes}} Bytes</span>\n\t\t\t\t\t\t</a> -->\n\t\t\t\t\t</div>\n\t\t\t\t</ng-container>\n\t\t\t</div>\n\t</div>\n\n\t<div class=\"ng-chat-footer primary-outline-color primary-background\">\n\t\t<input #chatWindowInput\n\t\t\ttype=\"text\"\n\t\t\t[ngModel]=\"window.newMessage | emojify:emojisEnabled\"\n\t\t\t(ngModelChange)=\"window.newMessage=$event\"\n\t\t\t[placeholder]=\"localization.messagePlaceholder\"\n\t\t\t[ngClass]=\"{'chat-window-input': true, 'has-side-action': fileUploadAdapter}\"\n\t\t\t(keydown)=\"onChatInputTyped($event, window)\"\n\t\t\t(blur)=\"toggleWindowFocus(window)\"\n\t\t\t(focus)=\"toggleWindowFocus(window)\"/>\n\n\t\t<!-- File Upload -->\n\t\t<ng-container *ngIf=\"fileUploadAdapter\">\n\t\t\t<a *ngIf=\"!isUploadingFile(window)\" class=\"btn-add-file\" (click)=\"triggerNativeFileUpload(window)\">\n\t\t\t\t<i class=\"upload-icon\"></i>\n\t\t\t</a>\n\t\t\t<input\n\t\t\t\ttype=\"file\"\n\t\t\t\t#nativeFileInput\n\t\t\t\tstyle=\"display: none;\"\n\t\t\t\t[attr.id]=\"getUniqueFileUploadInstanceId(window)\"\n\t\t\t\t(change)=\"onFileChosen(window)\" />\n\t\t\t<div *ngIf=\"isUploadingFile(window)\" class=\"loader\"></div>\n\t\t</ng-container>\n\t</div>\n</ng-container>", styles: [".ng-chat-window{right:260px;height:360px;z-index:999;bottom:0;width:300px;position:fixed;border-width:1px;border-style:solid;border-bottom:0;box-shadow:0 4px 8px #00000040;margin-bottom:10px!important;border:1px solid grey!important;border-radius:10px!important;box-sizing:border-box!important;overflow:hidden!important;background-color:#fff;::-webkit-scrollbar {width: 8px !important;} ::-webkit-scrollbar-thumb {background: #1b1b70 !important; border-radius: 5px !important;}}.ng-chat-window-collapsed{height:30px!important}.ng-chat-window .ng-chat-footer{box-sizing:border-box!important;padding-bottom:10px!important;display:block;height:10%;width:95%!important;margin:auto!important;background-color:inherit!important}.ng-chat-window .ng-chat-footer>input{font-size:.8em;box-sizing:border-box!important;padding:15px!important;height:100%;width:100%;border-radius:20px!important;border:1px solid grey!important}.ng-chat-window .ng-chat-footer>input.has-side-action{width:calc(100% - 30px)}.ng-chat-window .ng-chat-footer .btn-add-file{position:absolute;right:5px;bottom:7px;height:20px;width:20px;cursor:pointer}.ng-chat-window .ng-chat-footer .loader{position:absolute;right:14px;bottom:8px}.ng-chat-window .ng-chat-load-history{height:30px;text-align:center;font-size:.8em}.ng-chat-window .ng-chat-load-history>a{border-radius:15px;cursor:pointer;padding:5px 10px}.ng-chat-window .ng-chat-messages{padding:10px;width:100%;height:calc(90% - 30px);box-sizing:border-box;position:relative;overflow:auto}.ng-chat-window .ng-chat-messages .ng-chat-message{clear:both}.ng-chat-window .ng-chat-messages .ng-chat-message>img.avatar,.ng-chat-window .ng-chat-messages .ng-chat-message>.icon-wrapper{position:absolute;left:10px;border-radius:25px}.ng-chat-window .ng-chat-messages .ng-chat-message .ng-chat-participant-name{display:inline-block;margin-left:40px;padding-bottom:5px;font-weight:700;font-size:.8em;text-overflow:ellipsis;max-width:180px}.ng-chat-window .ng-chat-messages .ng-chat-message>.icon-wrapper{background-color:#bababa;overflow:hidden;width:30px;height:30px;padding:0}.ng-chat-window .ng-chat-messages .ng-chat-message>.icon-wrapper>i{color:#fff;transform:scale(.7)}.ng-chat-window .ng-chat-messages .ng-chat-message .message-sent-date{font-size:.5em!important;display:block;text-align:right;margin-top:5px;color:#a1a0a0!important;font-style:italic!important}.ng-chat-window .ng-chat-messages .ng-chat-message>div{float:right;width:182px;border-radius:15px 15px 0!important;padding:10px;margin-top:0;margin-bottom:5px;font-size:.9em;word-wrap:break-word;background-color:#93d4f0!important}.ng-chat-window .ng-chat-message-received>div.received-chat-message-container{float:left;margin-left:40px;padding-top:7px;padding-bottom:7px;margin-top:0;margin-bottom:5px;background-color:#daf2fc!important;border-radius:15px 15px 15px 0!important}.ng-chat-window .ng-chat-messages .ng-chat-message .file-message-container{float:right;width:202px;border-style:solid;border-width:3px;border-radius:5px;overflow:hidden;margin-bottom:5px;display:block;text-decoration:none;font-size:.9em;padding:0;box-sizing:border-box}.ng-chat-window .ng-chat-messages .ng-chat-message .file-message-container.received{float:left;margin-left:40px;width:208px}.ng-chat-window .ng-chat-messages .ng-chat-message .file-message-container>.file-message-icon-container{width:20px;height:35px;padding:10px 5px;float:left}.ng-chat-window .ng-chat-messages .ng-chat-message .file-message-container>.file-message-icon-container i{margin-top:8px}.ng-chat-window .ng-chat-messages .ng-chat-message .file-message-container>.file-details{float:left;padding:10px;width:calc(100% - 60px);color:currentColor;text-decoration:none}.ng-chat-window .ng-chat-messages .ng-chat-message .file-message-container>.file-details:hover{text-decoration:underline}.ng-chat-window .ng-chat-messages .ng-chat-message .file-message-container>.file-details span{display:block;width:100%;text-overflow:ellipsis;overflow:hidden;white-space:nowrap}.ng-chat-window .ng-chat-messages .ng-chat-message .file-message-container>.file-details .file-message-title{font-weight:700}.ng-chat-window .ng-chat-messages .ng-chat-message .file-message-container>.file-details .file-message-size{font-size:.8em;margin-top:5px}.ng-chat-window .image-message{width:100%;height:auto}@media only screen and (max-width: 581px){.ng-chat-window{position:initial}}\n"] }]
691
+ }], ctorParameters: function () { return []; }, propDecorators: { fileUploadAdapter: [{
692
+ type: Input
693
+ }], window: [{
694
+ type: Input
695
+ }], userId: [{
696
+ type: Input
697
+ }], localization: [{
698
+ type: Input
699
+ }], showOptions: [{
700
+ type: Input
701
+ }], emojisEnabled: [{
702
+ type: Input
703
+ }], linkfyEnabled: [{
704
+ type: Input
705
+ }], showMessageDate: [{
706
+ type: Input
707
+ }], messageDatePipeFormat: [{
708
+ type: Input
709
+ }], hasPagedHistory: [{
710
+ type: Input
711
+ }], onChatWindowClosed: [{
712
+ type: Output
713
+ }], onMessagesSeen: [{
714
+ type: Output
715
+ }], onMessageSent: [{
716
+ type: Output
717
+ }], onTabTriggered: [{
718
+ type: Output
719
+ }], onOptionTriggered: [{
720
+ type: Output
721
+ }], onLoadHistoryTriggered: [{
722
+ type: Output
723
+ }], chatMessages: [{
724
+ type: ViewChild,
725
+ args: ['chatMessages']
726
+ }], nativeFileInput: [{
727
+ type: ViewChild,
728
+ args: ['nativeFileInput']
729
+ }], chatWindowInput: [{
730
+ type: ViewChild,
731
+ args: ['chatWindowInput']
732
+ }] } });
733
+
734
+ class NgChat {
735
+ constructor(_httpClient) {
736
+ this._httpClient = _httpClient;
737
+ // Exposes enums for the ng-template
738
+ this.ChatParticipantType = ChatParticipantType;
739
+ this.ChatParticipantStatus = ChatParticipantStatus;
740
+ this.MessageType = MessageType;
741
+ this._isDisabled = false;
742
+ this.isCollapsed = false;
743
+ this.maximizeWindowOnNewMessage = true;
744
+ this.pollFriendsList = false;
745
+ this.pollingInterval = 5000;
746
+ this.historyEnabled = true;
747
+ this.emojisEnabled = true;
748
+ this.linkfyEnabled = true;
749
+ this.audioEnabled = true;
750
+ this.searchEnabled = true;
751
+ this.audioSource = '../../../assets/notification.wav';
752
+ this.persistWindowsState = true;
753
+ this.title = "Quick chat";
754
+ this.messagePlaceholder = "Type a message";
755
+ this.searchPlaceholder = "Search";
756
+ this.browserNotificationsEnabled = true;
757
+ this.browserNotificationIconSource = '../../../assets/notification.png';
758
+ this.browserNotificationTitle = "New message from";
759
+ this.historyPageSize = 10;
760
+ this.hideFriendsList = false;
761
+ this.hideFriendsListOnUnsupportedViewport = true;
762
+ this.theme = Theme.Light;
763
+ this.messageDatePipeFormat = "short";
764
+ this.showMessageDate = true;
765
+ this.isViewportOnMobileEnabled = false;
766
+ this.onParticipantClicked = new EventEmitter();
767
+ this.onParticipantChatOpened = new EventEmitter();
768
+ this.onParticipantChatClosed = new EventEmitter();
769
+ this.onMessagesSeen = new EventEmitter();
770
+ this.onCloseWindowClicked = new EventEmitter();
771
+ this.browserNotificationsBootstrapped = false;
772
+ this.hasPagedHistory = false;
773
+ // Don't want to add this as a setting to simplify usage. Previous placeholder and title settings available to be used, or use full Localization object.
774
+ this.statusDescription = {
775
+ online: 'Online',
776
+ busy: 'Busy',
777
+ away: 'Away',
778
+ offline: 'Offline'
779
+ };
780
+ this.participantsInteractedWith = [];
781
+ // Defines the size of each opened window to calculate how many windows can be opened on the viewport at the same time.
782
+ this.windowSizeFactor = 320;
783
+ // Total width size of the friends list section
784
+ this.friendsListWidth = 262;
785
+ // Set to true if there is no space to display at least one chat window and 'hideFriendsListOnUnsupportedViewport' is true
786
+ this.unsupportedViewport = false;
787
+ this.windows = [];
788
+ this.isBootstrapped = false;
789
+ }
790
+ get isDisabled() {
791
+ return this._isDisabled;
792
+ }
793
+ set isDisabled(value) {
794
+ this._isDisabled = value;
795
+ if (value) {
796
+ window.clearInterval(this.pollingIntervalWindowInstance);
797
+ }
798
+ else {
799
+ this.activateFriendListFetch();
800
+ }
801
+ }
802
+ get localStorageKey() {
803
+ return `ng-chat-users-${this.userId}`; // Appending the user id so the state is unique per user in a computer.
804
+ }
805
+ ;
806
+ ngOnInit() {
807
+ this.bootstrapChat();
808
+ }
809
+ onResize(event) {
810
+ this.viewPortTotalArea = event.target.innerWidth;
811
+ this.NormalizeWindows();
812
+ }
813
+ // Checks if there are more opened windows than the view port can display
814
+ NormalizeWindows() {
815
+ const maxSupportedOpenedWindows = Math.floor((this.viewPortTotalArea - (!this.hideFriendsList ? this.friendsListWidth : 0)) / this.windowSizeFactor);
816
+ const difference = this.windows.length - maxSupportedOpenedWindows;
817
+ if (difference >= 0) {
818
+ this.windows.splice(this.windows.length - difference);
819
+ }
820
+ this.updateWindowsState(this.windows);
821
+ // Viewport should have space for at least one chat window but should show in mobile if option is enabled.
822
+ this.unsupportedViewport = this.isViewportOnMobileEnabled ? false : this.hideFriendsListOnUnsupportedViewport && maxSupportedOpenedWindows < 1;
823
+ }
824
+ // Initializes the chat plugin and the messaging adapter
825
+ bootstrapChat() {
826
+ let initializationException = null;
827
+ if (this.adapter != null && this.userId != null) {
828
+ try {
829
+ this.viewPortTotalArea = window.innerWidth;
830
+ this.initializeTheme();
831
+ this.initializeDefaultText();
832
+ this.initializeBrowserNotifications();
833
+ // Binding event listeners
834
+ this.adapter.messageReceivedHandler = (participant, msg) => this.onMessageReceived(participant, msg);
835
+ this.adapter.friendsListChangedHandler = (participantsResponse) => this.onFriendsListChanged(participantsResponse);
836
+ this.activateFriendListFetch();
837
+ this.bufferAudioFile();
838
+ this.hasPagedHistory = this.adapter instanceof PagedHistoryChatAdapter;
839
+ if (this.fileUploadUrl && this.fileUploadUrl !== "") {
840
+ this.fileUploadAdapter = new DefaultFileUploadAdapter(this.fileUploadUrl, this._httpClient);
841
+ }
842
+ this.NormalizeWindows();
843
+ this.isBootstrapped = true;
844
+ }
845
+ catch (ex) {
846
+ initializationException = ex;
847
+ }
848
+ }
849
+ if (!this.isBootstrapped) {
850
+ console.error("ng-chat component couldn't be bootstrapped.");
851
+ if (this.userId == null) {
852
+ console.error("ng-chat can't be initialized without an user id. Please make sure you've provided an userId as a parameter of the ng-chat component.");
853
+ }
854
+ if (this.adapter == null) {
855
+ console.error("ng-chat can't be bootstrapped without a ChatAdapter. Please make sure you've provided a ChatAdapter implementation as a parameter of the ng-chat component.");
856
+ }
857
+ if (initializationException) {
858
+ console.error(`An exception has occurred while initializing ng-chat. Details: ${initializationException.message}`);
859
+ console.error(initializationException);
860
+ }
861
+ }
862
+ }
863
+ activateFriendListFetch() {
864
+ if (this.adapter) {
865
+ // Loading current users list
866
+ if (this.pollFriendsList) {
867
+ // Setting a long poll interval to update the friends list
868
+ this.fetchFriendsList(true);
869
+ this.pollingIntervalWindowInstance = window.setInterval(() => this.fetchFriendsList(false), this.pollingInterval);
870
+ }
871
+ else {
872
+ // Since polling was disabled, a friends list update mechanism will have to be implemented in the ChatAdapter.
873
+ this.fetchFriendsList(true);
874
+ }
875
+ }
876
+ }
877
+ // Initializes browser notifications
878
+ initializeBrowserNotifications() {
879
+ return __awaiter(this, void 0, void 0, function* () {
880
+ if (this.browserNotificationsEnabled && ("Notification" in window)) {
881
+ if ((yield Notification.requestPermission()) === "granted") {
882
+ this.browserNotificationsBootstrapped = true;
883
+ }
884
+ }
885
+ });
886
+ }
887
+ // Initializes default text
888
+ initializeDefaultText() {
889
+ if (!this.localization) {
890
+ this.localization = {
891
+ messagePlaceholder: this.messagePlaceholder,
892
+ searchPlaceholder: this.searchPlaceholder,
893
+ title: this.title,
894
+ statusDescription: this.statusDescription,
895
+ browserNotificationTitle: this.browserNotificationTitle,
896
+ loadMessageHistoryPlaceholder: "Load older messages"
897
+ };
898
+ }
899
+ }
900
+ initializeTheme() {
901
+ if (this.customTheme) {
902
+ this.theme = Theme.Custom;
903
+ }
904
+ else if (this.theme != Theme.Light && this.theme != Theme.Dark) {
905
+ // TODO: Use es2017 in future with Object.values(Theme).includes(this.theme) to do this check
906
+ throw new Error(`Invalid theme configuration for ng-chat. "${this.theme}" is not a valid theme value.`);
907
+ }
908
+ }
909
+ // Sends a request to load the friends list
910
+ fetchFriendsList(isBootstrapping) {
911
+ this.adapter.listFriends()
912
+ .pipe(map((participantsResponse) => {
913
+ this.participantsResponse = participantsResponse;
914
+ this.participants = participantsResponse.map((response) => {
915
+ return response.participant;
916
+ });
917
+ })).subscribe(() => {
918
+ if (isBootstrapping) {
919
+ this.restoreWindowsState();
920
+ }
921
+ });
922
+ }
923
+ fetchMessageHistory(window) {
924
+ // Not ideal but will keep this until we decide if we are shipping pagination with the default adapter
925
+ if (this.adapter instanceof PagedHistoryChatAdapter) {
926
+ window.isLoadingHistory = true;
927
+ this.adapter.getMessageHistoryByPage(window.participant.id, this.historyPageSize, ++window.historyPage)
928
+ .pipe(map((result) => {
929
+ result.forEach((message) => this.assertMessageType(message));
930
+ window.messages = result.concat(window.messages);
931
+ window.isLoadingHistory = false;
932
+ const direction = (window.historyPage == 1) ? ScrollDirection.Bottom : ScrollDirection.Top;
933
+ window.hasMoreMessages = result.length == this.historyPageSize;
934
+ setTimeout(() => this.onFetchMessageHistoryLoaded(result, window, direction, true));
935
+ })).subscribe();
936
+ }
937
+ else {
938
+ this.adapter.getMessageHistory(window.participant.id)
939
+ .pipe(map((result) => {
940
+ result.forEach((message) => this.assertMessageType(message));
941
+ window.messages = result.concat(window.messages);
942
+ window.isLoadingHistory = false;
943
+ setTimeout(() => this.onFetchMessageHistoryLoaded(result, window, ScrollDirection.Bottom));
944
+ })).subscribe();
945
+ }
946
+ }
947
+ onFetchMessageHistoryLoaded(messages, window, direction, forceMarkMessagesAsSeen = false) {
948
+ this.scrollChatWindow(window, direction);
949
+ if (window.hasFocus || forceMarkMessagesAsSeen) {
950
+ const unseenMessages = messages.filter(m => !m.dateSeen);
951
+ this.markMessagesAsRead(unseenMessages);
952
+ }
953
+ }
954
+ // Updates the friends list via the event handler
955
+ onFriendsListChanged(participantsResponse) {
956
+ if (participantsResponse) {
957
+ this.participantsResponse = participantsResponse;
958
+ this.participants = participantsResponse.map((response) => {
959
+ return response.participant;
960
+ });
961
+ this.participantsInteractedWith = [];
962
+ }
963
+ }
964
+ // Handles received messages by the adapter
965
+ onMessageReceived(participant, message) {
966
+ if (participant && message) {
967
+ const chatWindow = this.openChatWindow(participant);
968
+ this.assertMessageType(message);
969
+ if (!chatWindow[1] || !this.historyEnabled) {
970
+ chatWindow[0].messages.push(message);
971
+ this.scrollChatWindow(chatWindow[0], ScrollDirection.Bottom);
972
+ if (chatWindow[0].hasFocus) {
973
+ this.markMessagesAsRead([message]);
974
+ }
975
+ }
976
+ this.emitMessageSound(chatWindow[0]);
977
+ // Do not push browser notifications with message content for privacy purposes if the 'maximizeWindowOnNewMessage' setting is off and this is a new chat window.
978
+ if (this.maximizeWindowOnNewMessage || (!chatWindow[1] && !chatWindow[0].isCollapsed)) {
979
+ // Some messages are not pushed because they are loaded by fetching the history hence why we supply the message here
980
+ this.emitBrowserNotification(chatWindow[0], message);
981
+ }
982
+ }
983
+ }
984
+ onParticipantClickedFromFriendsList(participant) {
985
+ this.openChatWindow(participant, true, true);
986
+ }
987
+ cancelOptionPrompt() {
988
+ if (this.currentActiveOption) {
989
+ this.currentActiveOption.isActive = false;
990
+ this.currentActiveOption = null;
991
+ }
992
+ }
993
+ onOptionPromptCanceled() {
994
+ this.cancelOptionPrompt();
995
+ }
996
+ onOptionPromptConfirmed(event) {
997
+ // For now this is fine as there is only one option available. Introduce option types and type checking if a new option is added.
998
+ this.confirmNewGroup(event);
999
+ // Canceling current state
1000
+ this.cancelOptionPrompt();
1001
+ }
1002
+ confirmNewGroup(users) {
1003
+ const newGroup = new Group(users);
1004
+ this.openChatWindow(newGroup);
1005
+ if (this.groupAdapter) {
1006
+ this.groupAdapter.groupCreated(newGroup);
1007
+ }
1008
+ }
1009
+ // Opens a new chat whindow. Takes care of available viewport
1010
+ // Works for opening a chat window for an user or for a group
1011
+ // Returns => [Window: Window object reference, boolean: Indicates if this window is a new chat window]
1012
+ openChatWindow(participant, focusOnNewWindow = false, invokedByUserClick = false) {
1013
+ // Is this window opened?
1014
+ const openedWindow = this.windows.find(x => x.participant.id == participant.id);
1015
+ if (!openedWindow) {
1016
+ if (invokedByUserClick) {
1017
+ this.onParticipantClicked.emit(participant);
1018
+ }
1019
+ // Refer to issue #58 on Github
1020
+ const collapseWindow = invokedByUserClick ? false : !this.maximizeWindowOnNewMessage;
1021
+ const newChatWindow = new Window(participant, this.historyEnabled, collapseWindow);
1022
+ // Loads the chat history via an RxJs Observable
1023
+ if (this.historyEnabled) {
1024
+ this.fetchMessageHistory(newChatWindow);
1025
+ }
1026
+ this.windows.unshift(newChatWindow);
1027
+ // Is there enough space left in the view port ? but should be displayed in mobile if option is enabled
1028
+ if (!this.isViewportOnMobileEnabled) {
1029
+ if (this.windows.length * this.windowSizeFactor >= this.viewPortTotalArea - (!this.hideFriendsList ? this.friendsListWidth : 0)) {
1030
+ this.windows.pop();
1031
+ }
1032
+ }
1033
+ this.updateWindowsState(this.windows);
1034
+ if (focusOnNewWindow && !collapseWindow) {
1035
+ this.focusOnWindow(newChatWindow);
1036
+ }
1037
+ this.participantsInteractedWith.push(participant);
1038
+ this.onParticipantChatOpened.emit(participant);
1039
+ return [newChatWindow, true];
1040
+ }
1041
+ else {
1042
+ // Returns the existing chat window
1043
+ return [openedWindow, false];
1044
+ }
1045
+ }
1046
+ // Focus on the input element of the supplied window
1047
+ focusOnWindow(window, callback = () => { }) {
1048
+ const windowIndex = this.windows.indexOf(window);
1049
+ if (windowIndex >= 0) {
1050
+ setTimeout(() => {
1051
+ if (this.chatWindows) {
1052
+ const chatWindowToFocus = this.chatWindows.toArray()[windowIndex];
1053
+ chatWindowToFocus.chatWindowInput.nativeElement.focus();
1054
+ }
1055
+ callback();
1056
+ });
1057
+ }
1058
+ }
1059
+ assertMessageType(message) {
1060
+ // Always fallback to "Text" messages to avoid rendenring issues
1061
+ if (!message.type) {
1062
+ message.type = MessageType.Text;
1063
+ }
1064
+ }
1065
+ // Marks all messages provided as read with the current time.
1066
+ markMessagesAsRead(messages) {
1067
+ const currentDate = new Date();
1068
+ messages.forEach((msg) => {
1069
+ msg.dateSeen = currentDate;
1070
+ });
1071
+ this.onMessagesSeen.emit(messages);
1072
+ }
1073
+ // Buffers audio file (For component's bootstrapping)
1074
+ bufferAudioFile() {
1075
+ if (this.audioSource && this.audioSource.length > 0) {
1076
+ this.audioFile = new Audio();
1077
+ this.audioFile.src = this.audioSource;
1078
+ this.audioFile.load();
1079
+ }
1080
+ }
1081
+ // Emits a message notification audio if enabled after every message received
1082
+ emitMessageSound(window) {
1083
+ if (this.audioEnabled && !window.hasFocus && this.audioFile) {
1084
+ this.audioFile.play();
1085
+ }
1086
+ }
1087
+ // Emits a browser notification
1088
+ emitBrowserNotification(window, message) {
1089
+ // if (this.browserNotificationsBootstrapped && !window.hasFocus && message) {
1090
+ if (this.browserNotificationsBootstrapped && message) {
1091
+ const notification = new Notification(`${this.localization.browserNotificationTitle} ${window.participant.displayName}`, {
1092
+ 'body': message.message,
1093
+ 'icon': this.browserNotificationIconSource
1094
+ });
1095
+ setTimeout(() => {
1096
+ notification.close();
1097
+ }, message.message.length <= 50 ? 5000 : 7000); // More time to read longer messages
1098
+ }
1099
+ }
1100
+ // Saves current windows state into local storage if persistence is enabled
1101
+ updateWindowsState(windows) {
1102
+ if (this.persistWindowsState) {
1103
+ const participantIds = windows.map((w) => {
1104
+ return w.participant.id;
1105
+ });
1106
+ localStorage.setItem(this.localStorageKey, JSON.stringify(participantIds));
1107
+ }
1108
+ }
1109
+ restoreWindowsState() {
1110
+ try {
1111
+ if (this.persistWindowsState) {
1112
+ const stringfiedParticipantIds = localStorage.getItem(this.localStorageKey);
1113
+ if (stringfiedParticipantIds && stringfiedParticipantIds.length > 0) {
1114
+ const participantIds = JSON.parse(stringfiedParticipantIds);
1115
+ const participantsToRestore = this.participants.filter(u => participantIds.indexOf(u.id) >= 0);
1116
+ participantsToRestore.forEach((participant) => {
1117
+ this.openChatWindow(participant);
1118
+ });
1119
+ }
1120
+ }
1121
+ }
1122
+ catch (ex) {
1123
+ console.error(`An error occurred while restoring ng-chat windows state. Details: ${ex}`);
1124
+ }
1125
+ }
1126
+ // Gets closest open window if any. Most recent opened has priority (Right)
1127
+ getClosestWindow(window) {
1128
+ const index = this.windows.indexOf(window);
1129
+ if (index > 0) {
1130
+ return this.windows[index - 1];
1131
+ }
1132
+ else if (index == 0 && this.windows.length > 1) {
1133
+ return this.windows[index + 1];
1134
+ }
1135
+ }
1136
+ closeWindow(window) {
1137
+ const index = this.windows.indexOf(window);
1138
+ this.windows.splice(index, 1);
1139
+ this.updateWindowsState(this.windows);
1140
+ this.onParticipantChatClosed.emit(window.participant);
1141
+ }
1142
+ getChatWindowComponentInstance(targetWindow) {
1143
+ const windowIndex = this.windows.indexOf(targetWindow);
1144
+ if (this.chatWindows) {
1145
+ let targetWindow = this.chatWindows.toArray()[windowIndex];
1146
+ return targetWindow;
1147
+ }
1148
+ return null;
1149
+ }
1150
+ // Scrolls a chat window message flow to the bottom
1151
+ scrollChatWindow(window, direction) {
1152
+ const chatWindow = this.getChatWindowComponentInstance(window);
1153
+ if (chatWindow) {
1154
+ chatWindow.scrollChatWindow(window, direction);
1155
+ }
1156
+ }
1157
+ onWindowMessagesSeen(messagesSeen) {
1158
+ this.markMessagesAsRead(messagesSeen);
1159
+ }
1160
+ onWindowChatClosed(payload) {
1161
+ const { closedWindow, closedViaEscapeKey } = payload;
1162
+ if (closedViaEscapeKey) {
1163
+ let closestWindow = this.getClosestWindow(closedWindow);
1164
+ if (closestWindow) {
1165
+ this.focusOnWindow(closestWindow, () => { this.closeWindow(closedWindow); });
1166
+ }
1167
+ else {
1168
+ this.closeWindow(closedWindow);
1169
+ }
1170
+ }
1171
+ else {
1172
+ this.closeWindow(closedWindow);
1173
+ }
1174
+ }
1175
+ onWindowTabTriggered(payload) {
1176
+ const { triggeringWindow, shiftKeyPressed } = payload;
1177
+ const currentWindowIndex = this.windows.indexOf(triggeringWindow);
1178
+ let windowToFocus = this.windows[currentWindowIndex + (shiftKeyPressed ? 1 : -1)]; // Goes back on shift + tab
1179
+ if (!windowToFocus) {
1180
+ // Edge windows, go to start or end
1181
+ windowToFocus = this.windows[currentWindowIndex > 0 ? 0 : this.chatWindows.length - 1];
1182
+ }
1183
+ this.focusOnWindow(windowToFocus);
1184
+ }
1185
+ onWindowMessageSent(messageSent) {
1186
+ this.adapter.sendMessage(messageSent);
1187
+ }
1188
+ onWindowOptionTriggered(option) {
1189
+ this.currentActiveOption = option;
1190
+ }
1191
+ triggerOpenChatWindow(user) {
1192
+ if (user) {
1193
+ this.openChatWindow(user);
1194
+ }
1195
+ }
1196
+ triggerCloseChatWindow(userId) {
1197
+ const openedWindow = this.windows.find(x => x.participant.id == userId);
1198
+ if (openedWindow) {
1199
+ this.closeWindow(openedWindow);
1200
+ }
1201
+ }
1202
+ triggerToggleChatWindowVisibility(userId) {
1203
+ const openedWindow = this.windows.find(x => x.participant.id == userId);
1204
+ if (openedWindow) {
1205
+ const chatWindow = this.getChatWindowComponentInstance(openedWindow);
1206
+ if (chatWindow) {
1207
+ chatWindow.onChatWindowClicked(openedWindow);
1208
+ }
1209
+ }
1210
+ }
1211
+ // closeFriendList(){
1212
+ // this.hideFriendsList = !this.hideFriendsList
1213
+ // this.hideFriendsListChange.emit(this.hideFriendsList)
1214
+ // }
1215
+ onCloseWindowClick(click) {
1216
+ this.onCloseWindowClicked.emit(click);
1217
+ }
1218
+ }
1219
+ NgChat.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: NgChat, deps: [{ token: i1$2.HttpClient }], target: i0.ɵɵFactoryTarget.Component });
1220
+ NgChat.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "14.3.0", type: NgChat, selector: "mnl-chat", inputs: { isDisabled: "isDisabled", adapter: "adapter", groupAdapter: "groupAdapter", userId: "userId", isCollapsed: "isCollapsed", maximizeWindowOnNewMessage: "maximizeWindowOnNewMessage", pollFriendsList: "pollFriendsList", pollingInterval: "pollingInterval", historyEnabled: "historyEnabled", emojisEnabled: "emojisEnabled", linkfyEnabled: "linkfyEnabled", audioEnabled: "audioEnabled", searchEnabled: "searchEnabled", audioSource: "audioSource", persistWindowsState: "persistWindowsState", title: "title", messagePlaceholder: "messagePlaceholder", searchPlaceholder: "searchPlaceholder", browserNotificationsEnabled: "browserNotificationsEnabled", browserNotificationIconSource: "browserNotificationIconSource", browserNotificationTitle: "browserNotificationTitle", historyPageSize: "historyPageSize", localization: "localization", hideFriendsList: "hideFriendsList", hideFriendsListOnUnsupportedViewport: "hideFriendsListOnUnsupportedViewport", fileUploadUrl: "fileUploadUrl", theme: "theme", customTheme: "customTheme", messageDatePipeFormat: "messageDatePipeFormat", showMessageDate: "showMessageDate", isViewportOnMobileEnabled: "isViewportOnMobileEnabled", fileUploadAdapter: "fileUploadAdapter" }, outputs: { onParticipantClicked: "onParticipantClicked", onParticipantChatOpened: "onParticipantChatOpened", onParticipantChatClosed: "onParticipantChatClosed", onMessagesSeen: "onMessagesSeen", onCloseWindowClicked: "onCloseWindowClicked" }, host: { listeners: { "window:resize": "onResize($event)" } }, viewQueries: [{ propertyName: "chatWindows", predicate: ["chatWindow"], descendants: true }], ngImport: i0, template: "<link *ngIf=\"customTheme\" rel=\"stylesheet\" [href]='customTheme | sanitize'>\n\n<div id=\"ng-chat\" *ngIf=\"!isDisabled && isBootstrapped && !unsupportedViewport\" [ngClass]=\"theme\">\n <ng-chat-friends-list\n [localization]=\"localization\"\n [shouldDisplay]=\"!hideFriendsList\"\n [userId]=\"userId\"\n [isCollapsed]=\"isCollapsed\"\n [searchEnabled]=\"searchEnabled\"\n [participants]=\"participants\"\n [participantsResponse]=\"participantsResponse\"\n [participantsInteractedWith]=\"participantsInteractedWith\"\n [windows]=\"windows\"\n [currentActiveOption]=\"currentActiveOption\"\n (onParticipantClicked)=\"onParticipantClickedFromFriendsList($event)\"\n (onOptionPromptCanceled)=\"onOptionPromptCanceled()\"\n (onOptionPromptConfirmed)=\"onOptionPromptConfirmed($event)\"\n (onCloseWindowClick)=\"onCloseWindowClick($event)\"\n >\n </ng-chat-friends-list>\n\n <div *ngFor=\"let window of windows; let i = index\" [ngClass]=\"{'ng-chat-window': true, 'primary-outline-color': true, 'ng-chat-window-collapsed': window.isCollapsed}\" [ngStyle]=\"{'right': (!hideFriendsList ? friendsListWidth : 0) + 20 + windowSizeFactor * i + 'px'}\">\n <ng-chat-window\n #chatWindow\n [fileUploadAdapter]=\"fileUploadAdapter\"\n [localization]=\"localization\"\n [userId]=\"userId\"\n [window]=\"window\"\n [showOptions]=\"groupAdapter\"\n [emojisEnabled]=\"emojisEnabled\"\n [linkfyEnabled]=\"linkfyEnabled\"\n [showMessageDate]=\"showMessageDate\"\n [messageDatePipeFormat]=\"messageDatePipeFormat\"\n [hasPagedHistory]=\"hasPagedHistory\"\n (onMessagesSeen)=\"onWindowMessagesSeen($event)\"\n (onMessageSent)=\"onWindowMessageSent($event)\"\n (onTabTriggered)=\"onWindowTabTriggered($event)\"\n (onChatWindowClosed)=\"onWindowChatClosed($event)\"\n (onOptionTriggered)=\"onWindowOptionTriggered($event)\"\n (onLoadHistoryTriggered)=\"fetchMessageHistory($event)\"\n >\n </ng-chat-window>\n </div>\n</div>\n", styles: [".user-icon{box-sizing:border-box;background-color:#fff;border:2px solid;width:32px;height:20px;border-radius:64px 64px 0 0/64px;margin-top:14px;margin-left:-1px;display:inline-block;vertical-align:middle;position:relative;font-style:normal;color:#ddd;text-align:left;text-indent:-9999px}.user-icon:before{border:2px solid;background-color:#fff;width:12px;height:12px;top:-19px;border-radius:50%;position:absolute;left:50%;transform:translate(-50%)}.user-icon:before,.user-icon:after{content:\"\";pointer-events:none}.upload-icon{position:absolute;margin-left:3px;margin-top:12px;width:13px;height:4px;border:solid 1px currentColor;border-top:none;border-radius:1px}.upload-icon:before{content:\"\";position:absolute;top:-8px;left:6px;width:1px;height:9px;background-color:currentColor}.upload-icon:after{content:\"\";position:absolute;top:-8px;left:4px;width:4px;height:4px;border-top:solid 1px currentColor;border-right:solid 1px currentColor;transform:rotate(-45deg)}.paperclip-icon{position:absolute;margin-left:9px;margin-top:2px;width:6px;height:12px;border-radius:4px 4px 0 0;border-left:solid 1px currentColor;border-right:solid 1px currentColor;border-top:solid 1px currentColor;transform:rotate(45deg)}.paperclip-icon:before{content:\"\";position:absolute;top:11px;left:-1px;width:4px;height:6px;border-radius:0 0 3px 3px;border-left:solid 1px currentColor;border-right:solid 1px currentColor;border-bottom:solid 1px currentColor}.paperclip-icon:after{content:\"\";position:absolute;left:1px;top:1px;width:2px;height:10px;border-radius:4px 4px 0 0;border-left:solid 1px currentColor;border-right:solid 1px currentColor;border-top:solid 1px currentColor}.check-icon{color:#000;position:absolute;margin-left:3px;margin-top:4px;width:14px;height:8px;border-bottom:solid 1px currentColor;border-left:solid 1px currentColor;transform:rotate(-45deg)}.remove-icon{color:#000;position:absolute;margin-left:3px;margin-top:10px}.remove-icon:before{content:\"\";position:absolute;width:15px;height:1px;background-color:currentColor;transform:rotate(45deg)}.remove-icon:after{content:\"\";position:absolute;width:15px;height:1px;background-color:currentColor;transform:rotate(-45deg)}\n", ".loader,.loader:before,.loader:after{background:#E3E3E3;animation:load1 1s infinite ease-in-out;width:1em;height:4em}.loader{color:#e3e3e3;text-indent:-9999em;margin:4px auto 0;position:relative;font-size:4px;transform:translateZ(0);animation-delay:-.16s}.loader:before,.loader:after{position:absolute;top:0;content:\"\"}.loader:before{left:-1.5em;animation-delay:-.32s}.loader:after{left:1.5em}@keyframes load1{0%,80%,to{box-shadow:0 0;height:4em}40%{box-shadow:0 -2em;height:5em}}\n", "#ng-chat{position:fixed;z-index:999;right:0;bottom:0;box-sizing:initial;font-size:11pt;text-align:left}#ng-chat input{outline:none}#ng-chat .shadowed{box-shadow:0 4px 8px #00000040}.ng-chat-loading-wrapper{height:30px;text-align:center;font-size:.9em}.ng-chat-close{text-decoration:none!important;color:red!important;float:right!important;font-weight:900!important;font-size:15px!important}.ng-chat-title,.ng-chat-title:hover{position:relative;z-index:2;height:30px;line-height:30px;font-size:.9em;padding:0 10px;display:block;text-decoration:none;color:#fff!important;font-weight:600!important;cursor:pointer;background-color:#1b1b70!important}.ng-chat-title .ng-chat-title-visibility-toggle-area{display:inline-block;width:85%}.ng-chat-title .ng-chat-title-visibility-toggle-area>strong{font-weight:600;display:block;overflow:hidden;height:30px;text-overflow:ellipsis;white-space:nowrap;max-width:85%;float:left}.ng-chat-title .ng-chat-title-visibility-toggle-area .ng-chat-participant-status{float:left;margin-left:5px}.ng-chat-participant-status{display:inline-block;border-radius:25px;width:8px;height:8px;margin-top:10px}.ng-chat-participant-status.online{background-color:#92a400}.ng-chat-participant-status.busy{background-color:#f91c1e}.ng-chat-participant-status.away{background-color:#f7d21b}.ng-chat-participant-status.offline{background-color:#bababa}.ng-chat-unread-messages-count{margin-left:5px;padding:0 5px;border-radius:25px;font-size:.9em;line-height:30px}.ng-chat-options-container{float:right;margin-right:5px}\n", "#ng-chat.light-theme .primary-text,#ng-chat.light-theme{color:#5c5c5c;font-family:Arial,Helvetica,sans-serif}#ng-chat.light-theme .primary-background{background-color:#fff}#ng-chat.light-theme .secondary-background{background-color:#fafafa}#ng-chat.light-theme .primary-outline-color{border-color:#a3a3a3}#ng-chat.light-theme .friends-search-bar{background-color:#fff}#ng-chat.light-theme .unread-messages-counter-container,#ng-chat.light-theme .ng-chat-people-action,#ng-chat.light-theme .ng-chat-people-action>i{color:#5c5c5c;background-color:#e3e3e3}#ng-chat.light-theme .load-history-action{background-color:#e3e3e3}#ng-chat.light-theme .chat-window-input{background-color:#fff}#ng-chat.light-theme .sent-chat-message-container,#ng-chat.light-theme .file-message-container{background-color:#e3e3e3;border-color:#e3e3e3}#ng-chat.light-theme .received-chat-message-container,#ng-chat.light-theme .file-message-container.received{background-color:#fff;border-color:#e3e3e3}body{font-family:Roboto,Helvetica Neue,sans-serif!important}*{box-shadow:none!important}\n", "#ng-chat.dark-theme .primary-text,#ng-chat.dark-theme{color:#fff;font-family:Arial,Helvetica,sans-serif}#ng-chat.dark-theme .primary-background{background-color:#565656}#ng-chat.dark-theme .secondary-background{background-color:#444}#ng-chat.dark-theme .primary-outline-color{border-color:#353535}#ng-chat.dark-theme .friends-search-bar{background-color:#444;border:1px solid #444;color:#fff}#ng-chat.dark-theme .unread-messages-counter-container,#ng-chat.dark-theme .ng-chat-people-action,#ng-chat.dark-theme .ng-chat-people-action>i{background-color:#fff;color:#444}#ng-chat.dark-theme .load-history-action{background-color:#444}#ng-chat.dark-theme .chat-window-input{background-color:#444;color:#fff}#ng-chat.dark-theme .sent-chat-message-container,#ng-chat.dark-theme .file-message-container{border-color:#444;background-color:#444}#ng-chat.dark-theme .received-chat-message-container,#ng-chat.dark-theme .file-message-container.received{background-color:#565656;border-color:#444}#ng-chat.dark-theme .ng-chat-footer{background-color:#444}#ng-chat.dark-theme .ng-chat-message a{color:#fff}body{font-family:Roboto,Helvetica Neue,sans-serif!important}\n"], dependencies: [{ kind: "directive", type: i2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i2.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "component", type: NgChatFriendsListComponent, selector: "ng-chat-friends-list", inputs: ["participants", "participantsResponse", "participantsInteractedWith", "windows", "userId", "localization", "shouldDisplay", "isCollapsed", "searchEnabled", "currentActiveOption", "hideFriendsList"], outputs: ["onParticipantClicked", "onOptionPromptCanceled", "onOptionPromptConfirmed", "onCloseWindowClick"] }, { kind: "component", type: NgChatWindowComponent, selector: "ng-chat-window", inputs: ["fileUploadAdapter", "window", "userId", "localization", "showOptions", "emojisEnabled", "linkfyEnabled", "showMessageDate", "messageDatePipeFormat", "hasPagedHistory"], outputs: ["onChatWindowClosed", "onMessagesSeen", "onMessageSent", "onTabTriggered", "onOptionTriggered", "onLoadHistoryTriggered"] }, { kind: "pipe", type: SanitizePipe, name: "sanitize" }], encapsulation: i0.ViewEncapsulation.None });
1221
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: NgChat, decorators: [{
1222
+ type: Component,
1223
+ args: [{ selector: 'mnl-chat', encapsulation: ViewEncapsulation.None, template: "<link *ngIf=\"customTheme\" rel=\"stylesheet\" [href]='customTheme | sanitize'>\n\n<div id=\"ng-chat\" *ngIf=\"!isDisabled && isBootstrapped && !unsupportedViewport\" [ngClass]=\"theme\">\n <ng-chat-friends-list\n [localization]=\"localization\"\n [shouldDisplay]=\"!hideFriendsList\"\n [userId]=\"userId\"\n [isCollapsed]=\"isCollapsed\"\n [searchEnabled]=\"searchEnabled\"\n [participants]=\"participants\"\n [participantsResponse]=\"participantsResponse\"\n [participantsInteractedWith]=\"participantsInteractedWith\"\n [windows]=\"windows\"\n [currentActiveOption]=\"currentActiveOption\"\n (onParticipantClicked)=\"onParticipantClickedFromFriendsList($event)\"\n (onOptionPromptCanceled)=\"onOptionPromptCanceled()\"\n (onOptionPromptConfirmed)=\"onOptionPromptConfirmed($event)\"\n (onCloseWindowClick)=\"onCloseWindowClick($event)\"\n >\n </ng-chat-friends-list>\n\n <div *ngFor=\"let window of windows; let i = index\" [ngClass]=\"{'ng-chat-window': true, 'primary-outline-color': true, 'ng-chat-window-collapsed': window.isCollapsed}\" [ngStyle]=\"{'right': (!hideFriendsList ? friendsListWidth : 0) + 20 + windowSizeFactor * i + 'px'}\">\n <ng-chat-window\n #chatWindow\n [fileUploadAdapter]=\"fileUploadAdapter\"\n [localization]=\"localization\"\n [userId]=\"userId\"\n [window]=\"window\"\n [showOptions]=\"groupAdapter\"\n [emojisEnabled]=\"emojisEnabled\"\n [linkfyEnabled]=\"linkfyEnabled\"\n [showMessageDate]=\"showMessageDate\"\n [messageDatePipeFormat]=\"messageDatePipeFormat\"\n [hasPagedHistory]=\"hasPagedHistory\"\n (onMessagesSeen)=\"onWindowMessagesSeen($event)\"\n (onMessageSent)=\"onWindowMessageSent($event)\"\n (onTabTriggered)=\"onWindowTabTriggered($event)\"\n (onChatWindowClosed)=\"onWindowChatClosed($event)\"\n (onOptionTriggered)=\"onWindowOptionTriggered($event)\"\n (onLoadHistoryTriggered)=\"fetchMessageHistory($event)\"\n >\n </ng-chat-window>\n </div>\n</div>\n", styles: [".user-icon{box-sizing:border-box;background-color:#fff;border:2px solid;width:32px;height:20px;border-radius:64px 64px 0 0/64px;margin-top:14px;margin-left:-1px;display:inline-block;vertical-align:middle;position:relative;font-style:normal;color:#ddd;text-align:left;text-indent:-9999px}.user-icon:before{border:2px solid;background-color:#fff;width:12px;height:12px;top:-19px;border-radius:50%;position:absolute;left:50%;transform:translate(-50%)}.user-icon:before,.user-icon:after{content:\"\";pointer-events:none}.upload-icon{position:absolute;margin-left:3px;margin-top:12px;width:13px;height:4px;border:solid 1px currentColor;border-top:none;border-radius:1px}.upload-icon:before{content:\"\";position:absolute;top:-8px;left:6px;width:1px;height:9px;background-color:currentColor}.upload-icon:after{content:\"\";position:absolute;top:-8px;left:4px;width:4px;height:4px;border-top:solid 1px currentColor;border-right:solid 1px currentColor;transform:rotate(-45deg)}.paperclip-icon{position:absolute;margin-left:9px;margin-top:2px;width:6px;height:12px;border-radius:4px 4px 0 0;border-left:solid 1px currentColor;border-right:solid 1px currentColor;border-top:solid 1px currentColor;transform:rotate(45deg)}.paperclip-icon:before{content:\"\";position:absolute;top:11px;left:-1px;width:4px;height:6px;border-radius:0 0 3px 3px;border-left:solid 1px currentColor;border-right:solid 1px currentColor;border-bottom:solid 1px currentColor}.paperclip-icon:after{content:\"\";position:absolute;left:1px;top:1px;width:2px;height:10px;border-radius:4px 4px 0 0;border-left:solid 1px currentColor;border-right:solid 1px currentColor;border-top:solid 1px currentColor}.check-icon{color:#000;position:absolute;margin-left:3px;margin-top:4px;width:14px;height:8px;border-bottom:solid 1px currentColor;border-left:solid 1px currentColor;transform:rotate(-45deg)}.remove-icon{color:#000;position:absolute;margin-left:3px;margin-top:10px}.remove-icon:before{content:\"\";position:absolute;width:15px;height:1px;background-color:currentColor;transform:rotate(45deg)}.remove-icon:after{content:\"\";position:absolute;width:15px;height:1px;background-color:currentColor;transform:rotate(-45deg)}\n", ".loader,.loader:before,.loader:after{background:#E3E3E3;animation:load1 1s infinite ease-in-out;width:1em;height:4em}.loader{color:#e3e3e3;text-indent:-9999em;margin:4px auto 0;position:relative;font-size:4px;transform:translateZ(0);animation-delay:-.16s}.loader:before,.loader:after{position:absolute;top:0;content:\"\"}.loader:before{left:-1.5em;animation-delay:-.32s}.loader:after{left:1.5em}@keyframes load1{0%,80%,to{box-shadow:0 0;height:4em}40%{box-shadow:0 -2em;height:5em}}\n", "#ng-chat{position:fixed;z-index:999;right:0;bottom:0;box-sizing:initial;font-size:11pt;text-align:left}#ng-chat input{outline:none}#ng-chat .shadowed{box-shadow:0 4px 8px #00000040}.ng-chat-loading-wrapper{height:30px;text-align:center;font-size:.9em}.ng-chat-close{text-decoration:none!important;color:red!important;float:right!important;font-weight:900!important;font-size:15px!important}.ng-chat-title,.ng-chat-title:hover{position:relative;z-index:2;height:30px;line-height:30px;font-size:.9em;padding:0 10px;display:block;text-decoration:none;color:#fff!important;font-weight:600!important;cursor:pointer;background-color:#1b1b70!important}.ng-chat-title .ng-chat-title-visibility-toggle-area{display:inline-block;width:85%}.ng-chat-title .ng-chat-title-visibility-toggle-area>strong{font-weight:600;display:block;overflow:hidden;height:30px;text-overflow:ellipsis;white-space:nowrap;max-width:85%;float:left}.ng-chat-title .ng-chat-title-visibility-toggle-area .ng-chat-participant-status{float:left;margin-left:5px}.ng-chat-participant-status{display:inline-block;border-radius:25px;width:8px;height:8px;margin-top:10px}.ng-chat-participant-status.online{background-color:#92a400}.ng-chat-participant-status.busy{background-color:#f91c1e}.ng-chat-participant-status.away{background-color:#f7d21b}.ng-chat-participant-status.offline{background-color:#bababa}.ng-chat-unread-messages-count{margin-left:5px;padding:0 5px;border-radius:25px;font-size:.9em;line-height:30px}.ng-chat-options-container{float:right;margin-right:5px}\n", "#ng-chat.light-theme .primary-text,#ng-chat.light-theme{color:#5c5c5c;font-family:Arial,Helvetica,sans-serif}#ng-chat.light-theme .primary-background{background-color:#fff}#ng-chat.light-theme .secondary-background{background-color:#fafafa}#ng-chat.light-theme .primary-outline-color{border-color:#a3a3a3}#ng-chat.light-theme .friends-search-bar{background-color:#fff}#ng-chat.light-theme .unread-messages-counter-container,#ng-chat.light-theme .ng-chat-people-action,#ng-chat.light-theme .ng-chat-people-action>i{color:#5c5c5c;background-color:#e3e3e3}#ng-chat.light-theme .load-history-action{background-color:#e3e3e3}#ng-chat.light-theme .chat-window-input{background-color:#fff}#ng-chat.light-theme .sent-chat-message-container,#ng-chat.light-theme .file-message-container{background-color:#e3e3e3;border-color:#e3e3e3}#ng-chat.light-theme .received-chat-message-container,#ng-chat.light-theme .file-message-container.received{background-color:#fff;border-color:#e3e3e3}body{font-family:Roboto,Helvetica Neue,sans-serif!important}*{box-shadow:none!important}\n", "#ng-chat.dark-theme .primary-text,#ng-chat.dark-theme{color:#fff;font-family:Arial,Helvetica,sans-serif}#ng-chat.dark-theme .primary-background{background-color:#565656}#ng-chat.dark-theme .secondary-background{background-color:#444}#ng-chat.dark-theme .primary-outline-color{border-color:#353535}#ng-chat.dark-theme .friends-search-bar{background-color:#444;border:1px solid #444;color:#fff}#ng-chat.dark-theme .unread-messages-counter-container,#ng-chat.dark-theme .ng-chat-people-action,#ng-chat.dark-theme .ng-chat-people-action>i{background-color:#fff;color:#444}#ng-chat.dark-theme .load-history-action{background-color:#444}#ng-chat.dark-theme .chat-window-input{background-color:#444;color:#fff}#ng-chat.dark-theme .sent-chat-message-container,#ng-chat.dark-theme .file-message-container{border-color:#444;background-color:#444}#ng-chat.dark-theme .received-chat-message-container,#ng-chat.dark-theme .file-message-container.received{background-color:#565656;border-color:#444}#ng-chat.dark-theme .ng-chat-footer{background-color:#444}#ng-chat.dark-theme .ng-chat-message a{color:#fff}body{font-family:Roboto,Helvetica Neue,sans-serif!important}\n"] }]
1224
+ }], ctorParameters: function () { return [{ type: i1$2.HttpClient }]; }, propDecorators: { isDisabled: [{
1225
+ type: Input
1226
+ }], adapter: [{
1227
+ type: Input
1228
+ }], groupAdapter: [{
1229
+ type: Input
1230
+ }], userId: [{
1231
+ type: Input
1232
+ }], isCollapsed: [{
1233
+ type: Input
1234
+ }], maximizeWindowOnNewMessage: [{
1235
+ type: Input
1236
+ }], pollFriendsList: [{
1237
+ type: Input
1238
+ }], pollingInterval: [{
1239
+ type: Input
1240
+ }], historyEnabled: [{
1241
+ type: Input
1242
+ }], emojisEnabled: [{
1243
+ type: Input
1244
+ }], linkfyEnabled: [{
1245
+ type: Input
1246
+ }], audioEnabled: [{
1247
+ type: Input
1248
+ }], searchEnabled: [{
1249
+ type: Input
1250
+ }], audioSource: [{
1251
+ type: Input
1252
+ }], persistWindowsState: [{
1253
+ type: Input
1254
+ }], title: [{
1255
+ type: Input
1256
+ }], messagePlaceholder: [{
1257
+ type: Input
1258
+ }], searchPlaceholder: [{
1259
+ type: Input
1260
+ }], browserNotificationsEnabled: [{
1261
+ type: Input
1262
+ }], browserNotificationIconSource: [{
1263
+ type: Input
1264
+ }], browserNotificationTitle: [{
1265
+ type: Input
1266
+ }], historyPageSize: [{
1267
+ type: Input
1268
+ }], localization: [{
1269
+ type: Input
1270
+ }], hideFriendsList: [{
1271
+ type: Input
1272
+ }], hideFriendsListOnUnsupportedViewport: [{
1273
+ type: Input
1274
+ }], fileUploadUrl: [{
1275
+ type: Input
1276
+ }], theme: [{
1277
+ type: Input
1278
+ }], customTheme: [{
1279
+ type: Input
1280
+ }], messageDatePipeFormat: [{
1281
+ type: Input
1282
+ }], showMessageDate: [{
1283
+ type: Input
1284
+ }], isViewportOnMobileEnabled: [{
1285
+ type: Input
1286
+ }], fileUploadAdapter: [{
1287
+ type: Input
1288
+ }], onParticipantClicked: [{
1289
+ type: Output
1290
+ }], onParticipantChatOpened: [{
1291
+ type: Output
1292
+ }], onParticipantChatClosed: [{
1293
+ type: Output
1294
+ }], onMessagesSeen: [{
1295
+ type: Output
1296
+ }], onCloseWindowClicked: [{
1297
+ type: Output
1298
+ }], chatWindows: [{
1299
+ type: ViewChildren,
1300
+ args: ['chatWindow']
1301
+ }], onResize: [{
1302
+ type: HostListener,
1303
+ args: ['window:resize', ['$event']]
1304
+ }] } });
1305
+
1306
+ /* eslint-disable */
1307
+ class NgChatOptionsComponent {
1308
+ constructor() {
1309
+ this.activeOptionTrackerChange = new EventEmitter();
1310
+ }
1311
+ onOptionClicked(option) {
1312
+ option.isActive = true;
1313
+ if (option.action) {
1314
+ option.action(option.chattingTo);
1315
+ }
1316
+ this.activeOptionTrackerChange.emit(option);
1317
+ }
1318
+ }
1319
+ NgChatOptionsComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: NgChatOptionsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1320
+ NgChatOptionsComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "14.3.0", type: NgChatOptionsComponent, selector: "ng-chat-options", inputs: { options: "options", activeOptionTracker: "activeOptionTracker" }, outputs: { activeOptionTrackerChange: "activeOptionTrackerChange" }, ngImport: i0, template: "<div *ngIf=\"options && options.length > 0\" class=\"ng-chat-options\">\n\t\t<button class=\"ng-chat-options-activator\">\n\t\t\t<span class=\"primary-text\">...</span>\n\t\t</button>\n\t<div class=\"ng-chat-options-content primary-background shadowed\">\n\t\t<a *ngFor=\"let option of options; let i = index\" [ngClass]=\"'primary-text'\" (click)=\"onOptionClicked(option)\">\n\t\t\t{{option.displayLabel}}\n\t\t</a>\n\t</div> \n</div>\n", styles: [".ng-chat-options-activator{background-color:unset;color:#fff;line-height:28px;border:none;position:relative}.ng-chat-options-activator>span{position:relative;top:-5px;left:0}.ng-chat-options{position:relative;display:inline-block}.ng-chat-options:hover .ng-chat-options-content{display:block}.ng-chat-options:hover .ng-chat-options-activator{background-color:#ddd}.ng-chat-options-content{display:none;position:absolute;min-width:160px;z-index:1}.ng-chat-options-content a:hover{background-color:#ddd}.ng-chat-options-content a{padding:6px 16px;text-decoration:none;display:block}@media only screen and (max-width: 581px){.ng-chat-options-content{right:0}}\n"], dependencies: [{ kind: "directive", type: i2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }] });
1321
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: NgChatOptionsComponent, decorators: [{
1322
+ type: Component,
1323
+ args: [{ selector: 'ng-chat-options', template: "<div *ngIf=\"options && options.length > 0\" class=\"ng-chat-options\">\n\t\t<button class=\"ng-chat-options-activator\">\n\t\t\t<span class=\"primary-text\">...</span>\n\t\t</button>\n\t<div class=\"ng-chat-options-content primary-background shadowed\">\n\t\t<a *ngFor=\"let option of options; let i = index\" [ngClass]=\"'primary-text'\" (click)=\"onOptionClicked(option)\">\n\t\t\t{{option.displayLabel}}\n\t\t</a>\n\t</div> \n</div>\n", styles: [".ng-chat-options-activator{background-color:unset;color:#fff;line-height:28px;border:none;position:relative}.ng-chat-options-activator>span{position:relative;top:-5px;left:0}.ng-chat-options{position:relative;display:inline-block}.ng-chat-options:hover .ng-chat-options-content{display:block}.ng-chat-options:hover .ng-chat-options-activator{background-color:#ddd}.ng-chat-options-content{display:none;position:absolute;min-width:160px;z-index:1}.ng-chat-options-content a:hover{background-color:#ddd}.ng-chat-options-content a{padding:6px 16px;text-decoration:none;display:block}@media only screen and (max-width: 581px){.ng-chat-options-content{right:0}}\n"] }]
1324
+ }], ctorParameters: function () { return []; }, propDecorators: { options: [{
1325
+ type: Input
1326
+ }], activeOptionTracker: [{
1327
+ type: Input
1328
+ }], activeOptionTrackerChange: [{
1329
+ type: Output
1330
+ }] } });
1331
+
1332
+ class ChatModule {
1333
+ static forRoot() {
1334
+ return {
1335
+ ngModule: ChatModule,
1336
+ providers: [
1337
+ {
1338
+ provide: APP_INITIALIZER,
1339
+ useFactory: loadConfig,
1340
+ deps: [
1341
+ AppConfigService,
1342
+ ChatService
1343
+ ],
1344
+ multi: true,
1345
+ },
1346
+ ],
1347
+ };
1348
+ }
1349
+ static forChild() {
1350
+ return {
1351
+ ngModule: ChatModule,
1352
+ };
1353
+ }
1354
+ }
1355
+ ChatModule.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: ChatModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
1356
+ ChatModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "14.3.0", ngImport: i0, type: ChatModule, declarations: [NgChat,
1357
+ EmojifyPipe,
1358
+ LinkfyPipe,
1359
+ SanitizePipe,
1360
+ GroupMessageDisplayNamePipe,
1361
+ NgChatOptionsComponent,
1362
+ NgChatFriendsListComponent,
1363
+ NgChatWindowComponent,
1364
+ FirstLetterPipe], imports: [ChatMaterialModule,
1365
+ CommonModule,
1366
+ FormsModule,
1367
+ HttpClientModule], exports: [NgChat] });
1368
+ ChatModule.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: ChatModule, imports: [ChatMaterialModule,
1369
+ CommonModule,
1370
+ FormsModule,
1371
+ HttpClientModule] });
1372
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: ChatModule, decorators: [{
1373
+ type: NgModule,
1374
+ args: [{
1375
+ declarations: [
1376
+ NgChat,
1377
+ EmojifyPipe,
1378
+ LinkfyPipe,
1379
+ SanitizePipe,
1380
+ GroupMessageDisplayNamePipe,
1381
+ NgChatOptionsComponent,
1382
+ NgChatFriendsListComponent,
1383
+ NgChatWindowComponent,
1384
+ FirstLetterPipe
1385
+ ],
1386
+ imports: [
1387
+ ChatMaterialModule,
1388
+ CommonModule,
1389
+ FormsModule,
1390
+ HttpClientModule
1391
+ ],
1392
+ exports: [
1393
+ NgChat
1394
+ ],
1395
+ }]
1396
+ }] });
1397
+ function loadConfig(config) {
1398
+ const conf = () => config.load();
1399
+ return conf;
1400
+ }
1401
+
1402
+ class User {
1403
+ constructor() {
1404
+ this.participantType = ChatParticipantType.User;
1405
+ }
1406
+ }
1407
+
1408
+ class ParticipantResponse {
1409
+ }
1410
+
1411
+ class ParticipantMetadata {
1412
+ constructor() {
1413
+ this.totalUnreadMessages = 0;
1414
+ }
1415
+ }
1416
+
1417
+ /*
1418
+ * Public API Surface of mnl-chat
1419
+ */
1420
+
1421
+ /**
1422
+ * Generated bundle index. Do not edit.
1423
+ */
1424
+
1425
+ export { ChatAdapter, ChatModule, ChatParticipantStatus, ChatParticipantType, ChatService, Group, Message, MessageType, NgChat, PagedHistoryChatAdapter, ParticipantMetadata, ParticipantResponse, Theme, User, Window, loadConfig };
1426
+ //# sourceMappingURL=muraai-mnl-chat.mjs.map