@progress/kendo-angular-conversational-ui 21.0.0-develop.8 → 21.0.0-develop.9

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 (62) hide show
  1. package/chat/api/files-layout.d.ts +12 -0
  2. package/chat/api/index.d.ts +3 -1
  3. package/chat/api/message-settings.interface.d.ts +33 -0
  4. package/chat/api/message.interface.d.ts +5 -1
  5. package/chat/api/suggestions-layout.d.ts +20 -0
  6. package/chat/chat.component.d.ts +92 -34
  7. package/chat/chat.directives.d.ts +18 -0
  8. package/chat/chat.module.d.ts +15 -8
  9. package/chat/common/chat.service.d.ts +32 -3
  10. package/chat/common/models/model-fields.d.ts +0 -6
  11. package/chat/common/scroll-button.component.d.ts +32 -0
  12. package/chat/common/scroll.service.d.ts +39 -0
  13. package/chat/common/utils.d.ts +13 -1
  14. package/chat/l10n/messages.d.ts +9 -1
  15. package/chat/message-list.component.d.ts +20 -3
  16. package/chat/message.component.d.ts +40 -16
  17. package/chat/suggested-actions.component.d.ts +30 -5
  18. package/chat/templates/author-message-content-template.directive.d.ts +28 -0
  19. package/chat/templates/author-message-template.directive.d.ts +28 -0
  20. package/chat/templates/message-content-template.directive.d.ts +28 -0
  21. package/chat/templates/message-template.directive.d.ts +1 -1
  22. package/chat/templates/no-data-template.directive.d.ts +27 -0
  23. package/chat/templates/receiver-message-content-template.directive.d.ts +28 -0
  24. package/chat/templates/receiver-message-template.directive.d.ts +28 -0
  25. package/chat/templates/user-status-template.directive.d.ts +27 -0
  26. package/conversational-ui.module.d.ts +18 -11
  27. package/directives.d.ts +9 -2
  28. package/esm2022/chat/api/index.mjs +3 -1
  29. package/{chat/api/message-toolbar-visibility.d.ts → esm2022/chat/api/message-settings.interface.mjs} +1 -4
  30. package/esm2022/chat/api/suggestions-layout.mjs +5 -0
  31. package/esm2022/chat/builtin-actions.mjs +2 -0
  32. package/esm2022/chat/chat-file.component.mjs +2 -2
  33. package/esm2022/chat/chat.component.mjs +265 -71
  34. package/esm2022/chat/chat.directives.mjs +18 -0
  35. package/esm2022/chat/chat.module.mjs +16 -9
  36. package/esm2022/chat/common/chat.service.mjs +83 -4
  37. package/esm2022/chat/common/models/default-model-fields.mjs +0 -1
  38. package/esm2022/chat/common/scroll-button.component.mjs +81 -0
  39. package/esm2022/chat/common/scroll.service.mjs +110 -0
  40. package/esm2022/chat/common/utils.mjs +22 -3
  41. package/esm2022/chat/l10n/messages.mjs +13 -1
  42. package/esm2022/chat/message-attachments.component.mjs +2 -2
  43. package/esm2022/chat/message-box.component.mjs +5 -2
  44. package/esm2022/chat/message-list.component.mjs +165 -19
  45. package/esm2022/chat/message.component.mjs +487 -326
  46. package/esm2022/chat/suggested-actions.component.mjs +298 -80
  47. package/esm2022/chat/templates/author-message-content-template.directive.mjs +39 -0
  48. package/esm2022/chat/templates/author-message-template.directive.mjs +39 -0
  49. package/esm2022/chat/templates/message-content-template.directive.mjs +39 -0
  50. package/esm2022/chat/templates/message-template.directive.mjs +1 -1
  51. package/esm2022/chat/templates/no-data-template.directive.mjs +38 -0
  52. package/esm2022/chat/templates/receiver-message-content-template.directive.mjs +39 -0
  53. package/esm2022/chat/templates/receiver-message-template.directive.mjs +39 -0
  54. package/esm2022/chat/templates/user-status-template.directive.mjs +38 -0
  55. package/esm2022/conversational-ui.module.mjs +19 -12
  56. package/esm2022/directives.mjs +15 -1
  57. package/esm2022/index.mjs +7 -0
  58. package/esm2022/package-metadata.mjs +2 -2
  59. package/fesm2022/progress-kendo-angular-conversational-ui.mjs +1771 -547
  60. package/index.d.ts +7 -0
  61. package/package.json +14 -14
  62. /package/esm2022/chat/api/{message-toolbar-visibility.mjs → files-layout.mjs} +0 -0
@@ -2,24 +2,29 @@
2
2
  * Copyright © 2025 Progress Software Corporation. All rights reserved.
3
3
  * Licensed under commercial license. See LICENSE.md in the project root for more information
4
4
  *-------------------------------------------------------------------------------------------*/
5
- import { Component, ElementRef, EventEmitter, forwardRef, HostBinding, HostListener, Input, Output } from '@angular/core';
5
+ import { ChangeDetectorRef, Component, ElementRef, forwardRef, HostBinding, HostListener, Input } from '@angular/core';
6
6
  import { ChatItem } from './chat-item';
7
- import { MessageTemplateDirective } from './templates/message-template.directive';
7
+ import { MessageContentTemplateDirective } from './templates/message-content-template.directive';
8
8
  import { IntlService } from '@progress/kendo-angular-intl';
9
9
  import { KENDO_BUTTONS } from '@progress/kendo-angular-buttons';
10
- import { ContextMenuComponent } from '@progress/kendo-angular-menu';
11
10
  import { NgClass, NgFor, NgIf, NgTemplateOutlet } from '@angular/common';
12
11
  import { IconWrapperComponent } from '@progress/kendo-angular-icons';
13
12
  import { ToolBarButtonComponent, ToolBarComponent } from '@progress/kendo-angular-toolbar';
14
13
  import { chevronDownIcon, chevronUpIcon, downloadIcon } from '@progress/kendo-svg-icons';
15
14
  import { ChatService } from './common/chat.service';
16
15
  import { LocalizationService } from '@progress/kendo-angular-l10n';
17
- import { URL_REGEX, FILE_ACTION_BTN_SELECTOR, DOWNLOAD_ALL_SELECTOR, MENU_ITEM_SELECTOR } from './common/utils';
16
+ import { URL_REGEX, FILE_ACTION_BTN_SELECTOR, DOWNLOAD_ALL_SELECTOR, MENU_ITEM_SELECTOR, transformActions } from './common/utils';
18
17
  import { isAuthor } from './chat-view';
19
18
  import { ChatFileComponent } from './chat-file.component';
20
19
  import { MessageReferenceComponent } from './message-reference-content.component';
21
20
  import { ChatStatusTemplateDirective } from './templates/status-template.directive';
22
- import { Keys, normalizeNumpadKeys } from '@progress/kendo-angular-common';
21
+ import { isPresent, Keys, normalizeNumpadKeys } from '@progress/kendo-angular-common';
22
+ import { MessageTemplateDirective } from './templates/message-template.directive';
23
+ import { AuthorMessageContentTemplateDirective } from './templates/author-message-content-template.directive';
24
+ import { ReceiverMessageContentTemplateDirective } from './templates/receiver-message-content-template.directive';
25
+ import { ReceiverMessageTemplateDirective } from './templates/receiver-message-template.directive';
26
+ import { AuthorMessageTemplateDirective } from './templates/author-message-template.directive';
27
+ import { Subscription } from 'rxjs';
23
28
  import * as i0 from "@angular/core";
24
29
  import * as i1 from "@progress/kendo-angular-intl";
25
30
  import * as i2 from "./common/chat.service";
@@ -34,13 +39,23 @@ export class MessageComponent extends ChatItem {
34
39
  intl;
35
40
  chatService;
36
41
  localization;
37
- message;
42
+ cdr;
43
+ set message(value) {
44
+ this._message = value;
45
+ }
46
+ get message() {
47
+ return this._message;
48
+ }
38
49
  tabbable;
39
- template;
50
+ authorMessageContentTemplate;
51
+ receiverMessageContentTemplate;
52
+ messageContentTemplate;
53
+ authorMessageTemplate;
54
+ receiverMessageTemplate;
55
+ messageTemplate;
40
56
  statusTemplate;
41
57
  showMessageTime = true;
42
58
  authorId;
43
- contextMenuVisibilityChange = new EventEmitter();
44
59
  cssClass = true;
45
60
  get removedClass() {
46
61
  return this.message.isDeleted;
@@ -55,44 +70,142 @@ export class MessageComponent extends ChatItem {
55
70
  this.onExpandableKeydown(event);
56
71
  }
57
72
  selected;
58
- active = false;
59
- selectOnMenuClose = false;
60
73
  get tabIndex() {
61
74
  return this.tabbable ? '0' : '-1';
62
75
  }
63
- onContextMenu(event) {
64
- if (this.message.isDeleted) {
65
- return;
66
- }
67
- this.active = true;
68
- event.stopPropagation();
69
- }
70
76
  expandIcon = chevronDownIcon;
71
77
  collapseIcon = chevronUpIcon;
72
78
  downloadIcon = downloadIcon;
73
79
  isMessageExpanded = false;
74
- contextMenuActions = [];
80
+ showExpandCollapseIcon = false;
75
81
  fileActions = [];
82
+ toolbarActions = [];
76
83
  parts = [];
77
- constructor(element, intl, chatService, localization) {
84
+ get useCustomBubbleTemplate() {
85
+ return !!(this.getActiveBubbleTemplate());
86
+ }
87
+ get useCustomContentTemplate() {
88
+ return !!(this.getActiveContentTemplate());
89
+ }
90
+ get hasMessageContent() {
91
+ return !!(this.message?.text || this.message?.files?.length > 0);
92
+ }
93
+ get hasFiles() {
94
+ return this.message?.files?.length > 0;
95
+ }
96
+ get hasMultipleFiles() {
97
+ return this.message?.files?.length > 1;
98
+ }
99
+ get isActiveMessage() {
100
+ return this.chatService.active && this.message?.id === this.chatService.activeMessage?.id;
101
+ }
102
+ get isMessageExpandable() {
103
+ const isOwn = this.isOwnMessage(this.message);
104
+ const messageSettings = isOwn
105
+ ? this.chatService.authorMessageSettings
106
+ : this.chatService.receiverMessageSettings;
107
+ if (isPresent(messageSettings?.allowMessageCollapse)) {
108
+ return messageSettings.allowMessageCollapse;
109
+ }
110
+ return this.chatService.allowMessageCollapse || false;
111
+ }
112
+ get showToolbar() {
113
+ if (this.message?.isDeleted) {
114
+ return false;
115
+ }
116
+ const hasComponentActions = this.chatService.messageToolbarActions?.length > 0;
117
+ const hasMessageActions = this.toolbarActions?.length > 0;
118
+ return hasComponentActions || hasMessageActions;
119
+ }
120
+ subs = new Subscription();
121
+ _message;
122
+ constructor(element, intl, chatService, localization, cdr) {
78
123
  super();
79
124
  this.element = element;
80
125
  this.intl = intl;
81
126
  this.chatService = chatService;
82
127
  this.localization = localization;
128
+ this.cdr = cdr;
83
129
  }
84
130
  ngOnInit() {
85
- this.contextMenuActions = this.transformActions(this.chatService.messageContextMenuActions);
86
- this.fileActions = this.transformActions(this.chatService.fileActions);
131
+ this.fileActions = this.getFileActions();
132
+ this.toolbarActions = this.getToolbarActions();
133
+ const settingsChange$ = this.isOwnMessage(this.message)
134
+ ? this.chatService.authorMessageSettingsChange$
135
+ : this.chatService.receiverMessageSettingsChange$;
136
+ this.subs.add(settingsChange$.subscribe(() => {
137
+ this.fileActions = this.getFileActions();
138
+ this.toolbarActions = this.getToolbarActions();
139
+ setTimeout(() => {
140
+ this.showExpandCollapseIcon = this.calculateExpandCollapseIconVisibility();
141
+ });
142
+ }));
143
+ this.subs.add(this.chatService.allowMessageCollapseChange$.subscribe(() => {
144
+ setTimeout(() => {
145
+ this.showExpandCollapseIcon = this.calculateExpandCollapseIconVisibility();
146
+ });
147
+ }));
87
148
  if (this.message.id) {
88
149
  this.chatService.registerMessageElement(this.message.id, this.element);
89
150
  }
90
151
  this.parts = this.getFormattedTextParts(this.message.text);
91
152
  }
153
+ ngAfterViewInit() {
154
+ this.showExpandCollapseIcon = this.calculateExpandCollapseIconVisibility();
155
+ this.cdr.detectChanges();
156
+ }
92
157
  ngOnDestroy() {
93
158
  if (this.message.id) {
94
159
  this.chatService.unregisterMessageElement(this.message.id);
95
160
  }
161
+ this.subs.unsubscribe();
162
+ }
163
+ calculateExpandCollapseIconVisibility() {
164
+ if (this.isMessageExpanded) {
165
+ return true;
166
+ }
167
+ const bubbleContent = this.element.nativeElement.querySelector('.k-bubble-content');
168
+ if (!bubbleContent) {
169
+ return false;
170
+ }
171
+ const hasVerticalOverflow = bubbleContent.scrollHeight > bubbleContent.clientHeight;
172
+ const hasHorizontalOverflow = bubbleContent.scrollWidth > bubbleContent.clientWidth;
173
+ if (this.useCustomContentTemplate) {
174
+ return hasVerticalOverflow || hasHorizontalOverflow;
175
+ }
176
+ const messageText = this.element.nativeElement.querySelector('.k-chat-bubble-text');
177
+ const hasTextOverflow = messageText?.scrollWidth > messageText?.clientWidth;
178
+ return hasTextOverflow || hasVerticalOverflow || hasHorizontalOverflow;
179
+ }
180
+ getActiveBubbleTemplate() {
181
+ const isOwn = this.isOwnMessage(this.message);
182
+ if (isOwn && this.authorMessageTemplate) {
183
+ return this.authorMessageTemplate;
184
+ }
185
+ if (!isOwn && this.receiverMessageTemplate) {
186
+ return this.receiverMessageTemplate;
187
+ }
188
+ if (this.messageTemplate) {
189
+ return this.messageTemplate;
190
+ }
191
+ return null;
192
+ }
193
+ getActiveContentTemplate() {
194
+ const isOwn = this.isOwnMessage(this.message);
195
+ if (isOwn && this.authorMessageContentTemplate) {
196
+ return this.authorMessageContentTemplate;
197
+ }
198
+ if (!isOwn && this.receiverMessageContentTemplate) {
199
+ return this.receiverMessageContentTemplate;
200
+ }
201
+ if (this.messageContentTemplate) {
202
+ return this.messageContentTemplate;
203
+ }
204
+ return null;
205
+ }
206
+ getDeletedMessageText() {
207
+ const isOwn = this.isOwnMessage(this.message);
208
+ return isOwn ? this.textFor('deletedMessageSenderText') : this.textFor('deletedMessageReceiverText');
96
209
  }
97
210
  textFor(key) {
98
211
  return this.localization.get(key);
@@ -120,66 +233,19 @@ export class MessageComponent extends ChatItem {
120
233
  this.toggleMessageState(event);
121
234
  }
122
235
  }
123
- isToolbarVisible() {
124
- if (!this.chatService.messageToolbarActions || this.chatService.messageToolbarActions.length === 0 || this.message.isDeleted) {
125
- return false;
126
- }
127
- return this.chatService.messageToolbarVisibility === 'always';
128
- }
129
236
  onToolbarAction(event, action, message) {
130
237
  event.stopImmediatePropagation();
131
238
  this.chatService.emit('toolbarAction', { action, message });
132
239
  }
133
- onContextMenuAction(action) {
134
- if (action.id === 'reply') {
135
- this.chatService.reply = this.message;
136
- }
137
- if (action.id === 'copy') {
138
- navigator.clipboard.writeText(this.message.text);
139
- }
140
- this.chatService.emit('contextMenuAction', { action, message: this.message });
141
- }
142
240
  onFileAction(action, file) {
143
241
  if (action.originalAction.id === 'download') {
144
242
  this.chatService.emit('fileDownload', { files: [file], message: this.message });
145
243
  }
146
244
  this.chatService.emit('fileAction', { action: action.originalAction, file });
147
245
  }
148
- transformActions(actions) {
149
- return actions.map(action => ({
150
- text: action.label,
151
- icon: action.icon,
152
- svgIcon: action.svgIcon,
153
- disabled: action.disabled,
154
- originalAction: action
155
- }));
156
- }
157
246
  getMessageById(id) {
158
247
  return this.chatService.getMessageById(id);
159
248
  }
160
- // processes the message text to extract parts that are either plain text or links
161
- // this allows us not to render an additional wrapper element for the text
162
- getFormattedTextParts(text) {
163
- if (!text) {
164
- return [];
165
- }
166
- const parts = [];
167
- const urlMatches = Array.from(text.matchAll(URL_REGEX));
168
- let lastIndex = 0;
169
- for (const match of urlMatches) {
170
- const url = match[1];
171
- const matchStart = match.index;
172
- if (matchStart > lastIndex) {
173
- parts.push({ type: 'text', content: text.substring(lastIndex, matchStart) });
174
- }
175
- parts.push({ type: 'link', content: url, href: url });
176
- lastIndex = matchStart + match[0].length;
177
- }
178
- if (lastIndex < text.length) {
179
- parts.push({ type: 'text', content: text.substring(lastIndex) });
180
- }
181
- return parts;
182
- }
183
249
  onReplyReferenceClick(event, replyToId) {
184
250
  event.stopPropagation();
185
251
  this.chatService.emit('replyReferenceClick', replyToId);
@@ -191,9 +257,9 @@ export class MessageComponent extends ChatItem {
191
257
  this.onActionButtonClick(originalEvent);
192
258
  }
193
259
  }
194
- this.active = false;
195
- this.contextMenuVisibilityChange.emit(false);
196
- if (this.selectOnMenuClose) {
260
+ this.chatService.active = false;
261
+ this.chatService.emit('contextMenuVisibilityChange', false);
262
+ if (this.chatService.selectOnMenuClose) {
197
263
  this.selected = true;
198
264
  this.focus();
199
265
  }
@@ -202,16 +268,16 @@ export class MessageComponent extends ChatItem {
202
268
  const clickOutsideMessage = event instanceof MouseEvent && !event.target?.closest('.k-chat-bubble');
203
269
  const menuItemClick = event instanceof MouseEvent && event.target?.closest(MENU_ITEM_SELECTOR);
204
270
  if (clickOutsideMessage && !menuItemClick) {
205
- this.selectOnMenuClose = false;
271
+ this.chatService.selectOnMenuClose = false;
206
272
  }
207
273
  }
208
274
  handleMenuOpen() {
209
- this.selectOnMenuClose = this.selected;
210
- this.contextMenuVisibilityChange.emit(true);
275
+ this.chatService.selectOnMenuClose = this.selected;
276
+ this.chatService.emit('contextMenuVisibilityChange', true);
211
277
  }
212
278
  onActionPopupChange(expanded) {
213
279
  if (expanded) {
214
- this.active = true;
280
+ this.chatService.active = true;
215
281
  this.handleMenuOpen();
216
282
  }
217
283
  else {
@@ -221,124 +287,211 @@ export class MessageComponent extends ChatItem {
221
287
  isOwnMessage(msg) {
222
288
  return isAuthor(this.authorId, msg);
223
289
  }
224
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: MessageComponent, deps: [{ token: i0.ElementRef }, { token: i1.IntlService }, { token: i2.ChatService }, { token: i3.LocalizationService }], target: i0.ɵɵFactoryTarget.Component });
225
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: MessageComponent, isStandalone: true, selector: "kendo-chat-message", inputs: { message: "message", tabbable: "tabbable", template: "template", statusTemplate: "statusTemplate", showMessageTime: "showMessageTime", authorId: "authorId" }, outputs: { contextMenuVisibilityChange: "contextMenuVisibilityChange" }, host: { listeners: { "keydown": "onKeyDown($event)", "contextmenu": "onContextMenu($event)" }, properties: { "class.k-message": "this.cssClass", "class.k-message-removed": "this.removedClass", "attr.tabIndex": "this.tabIndex" } }, providers: [
290
+ getFormattedTextParts(text) {
291
+ if (!text) {
292
+ return [];
293
+ }
294
+ const parts = [];
295
+ const urlMatches = Array.from(text.matchAll(URL_REGEX));
296
+ let lastIndex = 0;
297
+ for (const match of urlMatches) {
298
+ const url = match[1];
299
+ const matchStart = match.index;
300
+ if (!isPresent(matchStart)) {
301
+ continue;
302
+ }
303
+ if (matchStart > lastIndex) {
304
+ parts.push({ type: 'text', content: text.substring(lastIndex, matchStart) });
305
+ }
306
+ parts.push({ type: 'link', content: url, href: url });
307
+ lastIndex = matchStart + match[0].length;
308
+ }
309
+ if (lastIndex < text.length) {
310
+ parts.push({ type: 'text', content: text.substring(lastIndex) });
311
+ }
312
+ return parts;
313
+ }
314
+ getMessageSettings() {
315
+ return this.isOwnMessage(this.message)
316
+ ? this.chatService.authorMessageSettings
317
+ : this.chatService.receiverMessageSettings;
318
+ }
319
+ getToolbarActions() {
320
+ const messageSettings = this.getMessageSettings();
321
+ return messageSettings?.messageToolbarActions?.length
322
+ ? messageSettings.messageToolbarActions
323
+ : this.chatService.messageToolbarActions || [];
324
+ }
325
+ getFileActions() {
326
+ const messageSettings = this.getMessageSettings();
327
+ const actions = messageSettings?.fileActions?.length
328
+ ? messageSettings.fileActions
329
+ : this.chatService.fileActions || [];
330
+ return transformActions(actions);
331
+ }
332
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: MessageComponent, deps: [{ token: i0.ElementRef }, { token: i1.IntlService }, { token: i2.ChatService }, { token: i3.LocalizationService }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
333
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: MessageComponent, isStandalone: true, selector: "kendo-chat-message", inputs: { message: "message", tabbable: "tabbable", authorMessageContentTemplate: "authorMessageContentTemplate", receiverMessageContentTemplate: "receiverMessageContentTemplate", messageContentTemplate: "messageContentTemplate", authorMessageTemplate: "authorMessageTemplate", receiverMessageTemplate: "receiverMessageTemplate", messageTemplate: "messageTemplate", statusTemplate: "statusTemplate", showMessageTime: "showMessageTime", authorId: "authorId" }, host: { listeners: { "keydown": "onKeyDown($event)" }, properties: { "class.k-message": "this.cssClass", "class.k-message-removed": "this.removedClass", "attr.tabIndex": "this.tabIndex" } }, providers: [
226
334
  {
227
335
  provide: ChatItem,
228
336
  useExisting: forwardRef(() => MessageComponent)
229
337
  }
230
338
  ], usesInheritance: true, ngImport: i0, template: `
231
- <time
232
- [attr.aria-hidden]="!selected"
233
- class="k-message-time"
234
- *ngIf="message.timestamp"
235
- >
236
- {{ formatTimeStamp(message.timestamp) }}
237
- </time>
339
+ <ng-container *ngIf="useCustomBubbleTemplate">
340
+ <ng-container *ngTemplateOutlet="getActiveBubbleTemplate()?.templateRef; context: { $implicit: message };"></ng-container>
341
+ </ng-container>
238
342
 
239
- <ng-container *ngIf="!message.typing; else typing">
240
- <div
241
- class="k-chat-bubble k-bubble"
242
- *ngIf="template"
243
- [attr.tabindex]="0"
244
- [ngClass]="{
245
- 'k-selected': selected,
246
- 'k-focus': selected,
247
- 'k-active': active
248
- }"
343
+ <ng-container *ngIf="!useCustomBubbleTemplate">
344
+ <time
345
+ [attr.aria-hidden]="!selected"
346
+ class="k-message-time"
347
+ *ngIf="message.timestamp"
249
348
  >
250
- <div class="k-bubble-content">
251
- <ng-container
252
- *ngTemplateOutlet="template.templateRef; context: { $implicit: message };"
253
- >
254
- </ng-container>
255
- </div>
256
- </div>
349
+ {{ formatTimeStamp(message.timestamp) }}
350
+ </time>
257
351
 
258
- <div
259
- class="k-chat-bubble k-bubble"
260
- *ngIf="!template && (message.text || message.files)"
261
- [attr.tabindex]="0"
262
- [ngClass]="{
263
- 'k-bubble-expandable': chatService.allowMessageCollapse,
264
- 'k-expanded': isMessageExpanded,
265
- 'k-selected': selected,
266
- 'k-focus': selected,
267
- 'k-active': active
268
- }"
269
- >
270
- <div class="k-bubble-content">
271
- <span class="k-chat-bubble-text" *ngIf="message.text || message.isDeleted">
272
- <div class="k-message-reference k-message-reference-receiver" *ngIf="message.replyToId && !message.isDeleted" (click)="onReplyReferenceClick($event, message.replyToId)">
273
- <chat-message-reference-content [message]="getMessageById(message.replyToId)"></chat-message-reference-content>
274
- </div>
275
- <ng-container *ngIf="message.isDeleted && isOwnMessage(message)">{{ textFor('deletedMessageSenderText') }}</ng-container>
276
- <ng-container *ngIf="message.isDeleted && !isOwnMessage(message)">{{ textFor('deletedMessageReceiverText') }}</ng-container>
277
- <ng-container *ngIf="!message.isDeleted && parts.length">
278
- <ng-container *ngFor="let part of parts">
279
- <ng-container *ngIf="part.type === 'text'">{{part.content}}</ng-container>
280
- <a *ngIf="part.type === 'link'" [href]="part.href" target="_blank">{{part.content}}</a>
281
- </ng-container>
282
- </ng-container>
283
- </span>
284
- <ul class="k-chat-file-wrapper" *ngIf="message.files && message.files.length > 0">
285
- <li
286
- *ngFor="let file of message.files"
287
- class="k-chat-file"
288
- [chatFile]="file"
289
- [fileActions]="fileActions"
290
- (actionClick)="onFileAction($event, file)"
291
- (actionsToggle)="onActionPopupChange($event)"
292
- (actionButtonClick)="onActionButtonClick($event)"
293
- ></li>
294
- </ul>
295
- <div class="k-chat-download-button-wrapper" *ngIf="message.files?.length > 1">
296
- <button
297
- kendoButton
298
- class="k-chat-download-button"
299
- fillMode="flat"
300
- icon="download"
301
- [svgIcon]="downloadIcon"
302
- (click)="onDownloadAll()"
303
- >{{ textFor('downloadAllFilesText') }}</button>
352
+ <ng-container *ngIf="message.typing">
353
+ <div class="k-chat-bubble k-bubble">
354
+ <div class="k-typing-indicator" [attr.tabindex]="'-1'">
355
+ <span></span>
356
+ <span></span>
357
+ <span></span>
304
358
  </div>
305
359
  </div>
306
- <span
307
- class="k-bubble-expandable-indicator"
308
- *ngIf="chatService.allowMessageCollapse"
309
- [attr.role]="'button'"
310
- [attr.title]="isMessageExpanded ? textFor('collapseTitle') : textFor('expandTitle')"
311
- (mousedown)="chatService.toggleMessageState = true"
312
- (click)="toggleMessageState($event)"
360
+ </ng-container>
361
+
362
+ <ng-container *ngIf="!message.typing">
363
+ <div
364
+ class="k-chat-bubble k-bubble"
365
+ *ngIf="useCustomContentTemplate"
366
+ [attr.tabindex]="0"
367
+ [ngClass]="{
368
+ 'k-bubble-expandable': isMessageExpandable,
369
+ 'k-expanded': isMessageExpanded,
370
+ 'k-selected': selected,
371
+ 'k-focus': selected,
372
+ 'k-active': isActiveMessage
373
+ }"
313
374
  >
314
- <kendo-icon-wrapper
315
- [name]="isMessageExpanded ? 'chevron-up' : 'chevron-down'"
316
- [svgIcon]="isMessageExpanded ? collapseIcon : expandIcon"
375
+ <div class="k-bubble-content">
376
+ <ng-container *ngTemplateOutlet="getActiveContentTemplate()?.templateRef; context: { $implicit: message };"></ng-container>
377
+ </div>
378
+ <span
379
+ class="k-bubble-expandable-indicator"
380
+ *ngIf="isMessageExpandable && showExpandCollapseIcon"
381
+ [attr.tabindex]="'0'"
382
+ [attr.role]="'button'"
383
+ [attr.title]="isMessageExpanded ? textFor('collapseTitle') : textFor('expandTitle')"
384
+ (mousedown)="chatService.toggleMessageState = true"
385
+ (click)="toggleMessageState($event)"
317
386
  >
318
- </kendo-icon-wrapper>
319
- </span>
320
- </div>
321
- </ng-container>
387
+ <kendo-icon-wrapper
388
+ [name]="isMessageExpanded ? 'chevron-up' : 'chevron-down'"
389
+ [svgIcon]="isMessageExpanded ? collapseIcon : expandIcon"
390
+ >
391
+ </kendo-icon-wrapper>
392
+ </span>
393
+ </div>
322
394
 
323
- <span
324
- class="k-message-status"
325
- *ngIf="message.status"
326
- >
327
- <ng-container *ngIf="statusTemplate?.templateRef">
328
- <ng-template
329
- [ngTemplateOutlet]="statusTemplate.templateRef"
330
- [ngTemplateOutletContext]="{ $implicit: message.status, message }"
395
+ <div
396
+ class="k-chat-bubble k-bubble"
397
+ *ngIf="!useCustomContentTemplate && hasMessageContent"
398
+ [attr.tabindex]="0"
399
+ [ngClass]="{
400
+ 'k-bubble-expandable': isMessageExpandable,
401
+ 'k-expanded': isMessageExpanded,
402
+ 'k-selected': selected,
403
+ 'k-focus': selected,
404
+ 'k-active': isActiveMessage
405
+ }"
331
406
  >
332
- </ng-template>
333
- </ng-container>
334
- <ng-container *ngIf="!statusTemplate?.templateRef">
335
- {{ message.status }}
407
+ <div class="k-bubble-content">
408
+ <ng-container *ngIf="message.text || message.isDeleted">
409
+ <div
410
+ class="k-message-reference k-message-reference-receiver"
411
+ *ngIf="message.replyToId && !message.isDeleted"
412
+ (click)="onReplyReferenceClick($event, message.replyToId)"
413
+ >
414
+ <chat-message-reference-content [message]="getMessageById(message.replyToId)"></chat-message-reference-content>
415
+ </div>
416
+
417
+ <span class="k-chat-bubble-text" *ngIf="message.isDeleted">
418
+ {{ getDeletedMessageText() }}
419
+ </span>
420
+
421
+ <span class="k-chat-bubble-text" *ngIf="!message.isDeleted && parts.length > 0">
422
+ <ng-container *ngFor="let part of parts">
423
+ <ng-container *ngIf="part.type === 'text'">{{part.content}}</ng-container>
424
+ <a *ngIf="part.type === 'link'" [href]="part.href" target="_blank">{{part.content}}</a>
425
+ </ng-container>
426
+ </span>
427
+ </ng-container>
428
+
429
+ <ul
430
+ class="k-chat-file-wrapper"
431
+ *ngIf="hasFiles && !message.isDeleted"
432
+ [ngClass]="{
433
+ 'k-chat-files-wrap': chatService.messageFilesLayout === 'wrap',
434
+ 'k-chat-files-horizontal': chatService.messageFilesLayout === 'horizontal'
435
+ }"
436
+ >
437
+ <li
438
+ *ngFor="let file of message.files"
439
+ class="k-chat-file"
440
+ [chatFile]="file"
441
+ [fileActions]="fileActions"
442
+ (actionClick)="onFileAction($event, file)"
443
+ (actionsToggle)="onActionPopupChange($event)"
444
+ (actionButtonClick)="onActionButtonClick($event)"
445
+ ></li>
446
+ </ul>
447
+
448
+ <div class="k-chat-download-button-wrapper" *ngIf="hasMultipleFiles && !message.isDeleted">
449
+ <button
450
+ kendoButton
451
+ class="k-chat-download-button"
452
+ fillMode="flat"
453
+ icon="download"
454
+ [svgIcon]="downloadIcon"
455
+ [attr.title]="textFor('downloadAllFilesText')"
456
+ (click)="onDownloadAll()"
457
+ >{{ textFor('downloadAllFilesText') }}</button>
458
+ </div>
459
+ </div>
460
+
461
+ <span
462
+ class="k-bubble-expandable-indicator"
463
+ *ngIf="isMessageExpandable && showExpandCollapseIcon"
464
+ [attr.tabindex]="'0'"
465
+ [attr.role]="'button'"
466
+ [attr.title]="isMessageExpanded ? textFor('collapseTitle') : textFor('expandTitle')"
467
+ (mousedown)="chatService.toggleMessageState = true"
468
+ (click)="toggleMessageState($event)"
469
+ >
470
+ <kendo-icon-wrapper
471
+ [name]="isMessageExpanded ? 'chevron-up' : 'chevron-down'"
472
+ [svgIcon]="isMessageExpanded ? collapseIcon : expandIcon"
473
+ >
474
+ </kendo-icon-wrapper>
475
+ </span>
476
+ </div>
336
477
  </ng-container>
337
- </span>
338
478
 
339
- <kendo-toolbar *ngIf="isToolbarVisible()" class="k-chat-message-toolbar" fillMode="flat">
479
+ <span class="k-message-status" *ngIf="message.status">
480
+ <ng-container *ngIf="statusTemplate?.templateRef">
481
+ <ng-template
482
+ [ngTemplateOutlet]="statusTemplate.templateRef"
483
+ [ngTemplateOutletContext]="{ $implicit: message.status, message }"
484
+ >
485
+ </ng-template>
486
+ </ng-container>
487
+ <ng-container *ngIf="!statusTemplate?.templateRef">
488
+ {{ message.status }}
489
+ </ng-container>
490
+ </span>
491
+ </ng-container>
492
+ <kendo-toolbar *ngIf="showToolbar" class="k-chat-message-toolbar" fillMode="flat">
340
493
  <kendo-toolbar-button
341
- *ngFor="let action of chatService.messageToolbarActions"
494
+ *ngFor="let action of toolbarActions"
342
495
  fillMode="flat"
343
496
  [icon]="action.icon"
344
497
  [svgIcon]="action.svgIcon"
@@ -348,28 +501,7 @@ export class MessageComponent extends ChatItem {
348
501
  >
349
502
  </kendo-toolbar-button>
350
503
  </kendo-toolbar>
351
-
352
- <ng-template #typing>
353
- <div class="k-chat-bubble k-bubble">
354
- <div class="k-typing-indicator">
355
- <span></span>
356
- <span></span>
357
- <span></span>
358
- </div>
359
- </div>
360
- </ng-template>
361
-
362
- <kendo-contextmenu
363
- *ngIf="!message.isDeleted"
364
- [target]="element"
365
- [items]="contextMenuActions"
366
- (popupOpen)="handleMenuOpen()"
367
- (popupClose)="handleMenuClose($event)"
368
- (select)="onContextMenuAction($event.item.originalAction)"
369
- [popupAlign]="{ horizontal: 'right', vertical: 'top' }"
370
- [collision]="{ horizontal: 'flip', vertical: 'flip'}"
371
- ></kendo-contextmenu>
372
- `, isInline: true, dependencies: [{ kind: "directive", type: NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: NgFor, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: IconWrapperComponent, selector: "kendo-icon-wrapper", inputs: ["name", "svgIcon", "innerCssClass", "customFontClass", "size"], exportAs: ["kendoIconWrapper"] }, { kind: "component", type: i4.ButtonComponent, selector: "button[kendoButton]", inputs: ["arrowIcon", "toggleable", "togglable", "selected", "tabIndex", "imageUrl", "iconClass", "icon", "disabled", "size", "rounded", "fillMode", "themeColor", "svgIcon", "primary", "look"], outputs: ["selectedChange", "click"], exportAs: ["kendoButton"] }, { kind: "component", type: ChatFileComponent, selector: "li[chatFile]", inputs: ["chatFile", "removable", "fileActions"], outputs: ["remove", "actionClick", "actionsToggle", "actionButtonClick"] }, { kind: "component", type: ContextMenuComponent, selector: "kendo-contextmenu", inputs: ["showOn", "target", "filter", "alignToAnchor", "vertical", "popupAnimate", "popupAlign", "anchorAlign", "collision", "appendTo", "ariaLabel"], outputs: ["popupOpen", "popupClose", "select", "open", "close"], exportAs: ["kendoContextMenu"] }, { kind: "component", type: ToolBarComponent, selector: "kendo-toolbar", inputs: ["overflow", "resizable", "popupSettings", "fillMode", "tabindex", "size", "tabIndex", "showIcon", "showText"], outputs: ["open", "close"], exportAs: ["kendoToolBar"] }, { kind: "component", type: ToolBarButtonComponent, selector: "kendo-toolbar-button", inputs: ["showText", "showIcon", "text", "style", "className", "title", "disabled", "toggleable", "look", "togglable", "selected", "fillMode", "rounded", "themeColor", "icon", "iconClass", "svgIcon", "imageUrl"], outputs: ["click", "pointerdown", "selectedChange"], exportAs: ["kendoToolBarButton"] }, { kind: "component", type: MessageReferenceComponent, selector: "chat-message-reference-content", inputs: ["message"] }] });
504
+ `, isInline: true, dependencies: [{ kind: "directive", type: NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: NgFor, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: IconWrapperComponent, selector: "kendo-icon-wrapper", inputs: ["name", "svgIcon", "innerCssClass", "customFontClass", "size"], exportAs: ["kendoIconWrapper"] }, { kind: "component", type: i4.ButtonComponent, selector: "button[kendoButton]", inputs: ["arrowIcon", "toggleable", "togglable", "selected", "tabIndex", "imageUrl", "iconClass", "icon", "disabled", "size", "rounded", "fillMode", "themeColor", "svgIcon", "primary", "look"], outputs: ["selectedChange", "click"], exportAs: ["kendoButton"] }, { kind: "component", type: ChatFileComponent, selector: "li[chatFile]", inputs: ["chatFile", "removable", "fileActions"], outputs: ["remove", "actionClick", "actionsToggle", "actionButtonClick"] }, { kind: "component", type: ToolBarComponent, selector: "kendo-toolbar", inputs: ["overflow", "resizable", "popupSettings", "fillMode", "tabindex", "size", "tabIndex", "showIcon", "showText"], outputs: ["open", "close"], exportAs: ["kendoToolBar"] }, { kind: "component", type: ToolBarButtonComponent, selector: "kendo-toolbar-button", inputs: ["showText", "showIcon", "text", "style", "className", "title", "disabled", "toggleable", "look", "togglable", "selected", "fillMode", "rounded", "themeColor", "icon", "iconClass", "svgIcon", "imageUrl"], outputs: ["click", "pointerdown", "selectedChange"], exportAs: ["kendoToolBarButton"] }, { kind: "component", type: MessageReferenceComponent, selector: "chat-message-reference-content", inputs: ["message"] }] });
373
505
  }
374
506
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: MessageComponent, decorators: [{
375
507
  type: Component,
@@ -382,117 +514,162 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
382
514
  }
383
515
  ],
384
516
  template: `
385
- <time
386
- [attr.aria-hidden]="!selected"
387
- class="k-message-time"
388
- *ngIf="message.timestamp"
389
- >
390
- {{ formatTimeStamp(message.timestamp) }}
391
- </time>
517
+ <ng-container *ngIf="useCustomBubbleTemplate">
518
+ <ng-container *ngTemplateOutlet="getActiveBubbleTemplate()?.templateRef; context: { $implicit: message };"></ng-container>
519
+ </ng-container>
392
520
 
393
- <ng-container *ngIf="!message.typing; else typing">
394
- <div
395
- class="k-chat-bubble k-bubble"
396
- *ngIf="template"
397
- [attr.tabindex]="0"
398
- [ngClass]="{
399
- 'k-selected': selected,
400
- 'k-focus': selected,
401
- 'k-active': active
402
- }"
521
+ <ng-container *ngIf="!useCustomBubbleTemplate">
522
+ <time
523
+ [attr.aria-hidden]="!selected"
524
+ class="k-message-time"
525
+ *ngIf="message.timestamp"
403
526
  >
404
- <div class="k-bubble-content">
405
- <ng-container
406
- *ngTemplateOutlet="template.templateRef; context: { $implicit: message };"
407
- >
408
- </ng-container>
409
- </div>
410
- </div>
527
+ {{ formatTimeStamp(message.timestamp) }}
528
+ </time>
411
529
 
412
- <div
413
- class="k-chat-bubble k-bubble"
414
- *ngIf="!template && (message.text || message.files)"
415
- [attr.tabindex]="0"
416
- [ngClass]="{
417
- 'k-bubble-expandable': chatService.allowMessageCollapse,
418
- 'k-expanded': isMessageExpanded,
419
- 'k-selected': selected,
420
- 'k-focus': selected,
421
- 'k-active': active
422
- }"
423
- >
424
- <div class="k-bubble-content">
425
- <span class="k-chat-bubble-text" *ngIf="message.text || message.isDeleted">
426
- <div class="k-message-reference k-message-reference-receiver" *ngIf="message.replyToId && !message.isDeleted" (click)="onReplyReferenceClick($event, message.replyToId)">
427
- <chat-message-reference-content [message]="getMessageById(message.replyToId)"></chat-message-reference-content>
428
- </div>
429
- <ng-container *ngIf="message.isDeleted && isOwnMessage(message)">{{ textFor('deletedMessageSenderText') }}</ng-container>
430
- <ng-container *ngIf="message.isDeleted && !isOwnMessage(message)">{{ textFor('deletedMessageReceiverText') }}</ng-container>
431
- <ng-container *ngIf="!message.isDeleted && parts.length">
432
- <ng-container *ngFor="let part of parts">
433
- <ng-container *ngIf="part.type === 'text'">{{part.content}}</ng-container>
434
- <a *ngIf="part.type === 'link'" [href]="part.href" target="_blank">{{part.content}}</a>
435
- </ng-container>
436
- </ng-container>
437
- </span>
438
- <ul class="k-chat-file-wrapper" *ngIf="message.files && message.files.length > 0">
439
- <li
440
- *ngFor="let file of message.files"
441
- class="k-chat-file"
442
- [chatFile]="file"
443
- [fileActions]="fileActions"
444
- (actionClick)="onFileAction($event, file)"
445
- (actionsToggle)="onActionPopupChange($event)"
446
- (actionButtonClick)="onActionButtonClick($event)"
447
- ></li>
448
- </ul>
449
- <div class="k-chat-download-button-wrapper" *ngIf="message.files?.length > 1">
450
- <button
451
- kendoButton
452
- class="k-chat-download-button"
453
- fillMode="flat"
454
- icon="download"
455
- [svgIcon]="downloadIcon"
456
- (click)="onDownloadAll()"
457
- >{{ textFor('downloadAllFilesText') }}</button>
530
+ <ng-container *ngIf="message.typing">
531
+ <div class="k-chat-bubble k-bubble">
532
+ <div class="k-typing-indicator" [attr.tabindex]="'-1'">
533
+ <span></span>
534
+ <span></span>
535
+ <span></span>
458
536
  </div>
459
537
  </div>
460
- <span
461
- class="k-bubble-expandable-indicator"
462
- *ngIf="chatService.allowMessageCollapse"
463
- [attr.role]="'button'"
464
- [attr.title]="isMessageExpanded ? textFor('collapseTitle') : textFor('expandTitle')"
465
- (mousedown)="chatService.toggleMessageState = true"
466
- (click)="toggleMessageState($event)"
538
+ </ng-container>
539
+
540
+ <ng-container *ngIf="!message.typing">
541
+ <div
542
+ class="k-chat-bubble k-bubble"
543
+ *ngIf="useCustomContentTemplate"
544
+ [attr.tabindex]="0"
545
+ [ngClass]="{
546
+ 'k-bubble-expandable': isMessageExpandable,
547
+ 'k-expanded': isMessageExpanded,
548
+ 'k-selected': selected,
549
+ 'k-focus': selected,
550
+ 'k-active': isActiveMessage
551
+ }"
467
552
  >
468
- <kendo-icon-wrapper
469
- [name]="isMessageExpanded ? 'chevron-up' : 'chevron-down'"
470
- [svgIcon]="isMessageExpanded ? collapseIcon : expandIcon"
553
+ <div class="k-bubble-content">
554
+ <ng-container *ngTemplateOutlet="getActiveContentTemplate()?.templateRef; context: { $implicit: message };"></ng-container>
555
+ </div>
556
+ <span
557
+ class="k-bubble-expandable-indicator"
558
+ *ngIf="isMessageExpandable && showExpandCollapseIcon"
559
+ [attr.tabindex]="'0'"
560
+ [attr.role]="'button'"
561
+ [attr.title]="isMessageExpanded ? textFor('collapseTitle') : textFor('expandTitle')"
562
+ (mousedown)="chatService.toggleMessageState = true"
563
+ (click)="toggleMessageState($event)"
471
564
  >
472
- </kendo-icon-wrapper>
473
- </span>
474
- </div>
475
- </ng-container>
565
+ <kendo-icon-wrapper
566
+ [name]="isMessageExpanded ? 'chevron-up' : 'chevron-down'"
567
+ [svgIcon]="isMessageExpanded ? collapseIcon : expandIcon"
568
+ >
569
+ </kendo-icon-wrapper>
570
+ </span>
571
+ </div>
476
572
 
477
- <span
478
- class="k-message-status"
479
- *ngIf="message.status"
480
- >
481
- <ng-container *ngIf="statusTemplate?.templateRef">
482
- <ng-template
483
- [ngTemplateOutlet]="statusTemplate.templateRef"
484
- [ngTemplateOutletContext]="{ $implicit: message.status, message }"
573
+ <div
574
+ class="k-chat-bubble k-bubble"
575
+ *ngIf="!useCustomContentTemplate && hasMessageContent"
576
+ [attr.tabindex]="0"
577
+ [ngClass]="{
578
+ 'k-bubble-expandable': isMessageExpandable,
579
+ 'k-expanded': isMessageExpanded,
580
+ 'k-selected': selected,
581
+ 'k-focus': selected,
582
+ 'k-active': isActiveMessage
583
+ }"
485
584
  >
486
- </ng-template>
487
- </ng-container>
488
- <ng-container *ngIf="!statusTemplate?.templateRef">
489
- {{ message.status }}
585
+ <div class="k-bubble-content">
586
+ <ng-container *ngIf="message.text || message.isDeleted">
587
+ <div
588
+ class="k-message-reference k-message-reference-receiver"
589
+ *ngIf="message.replyToId && !message.isDeleted"
590
+ (click)="onReplyReferenceClick($event, message.replyToId)"
591
+ >
592
+ <chat-message-reference-content [message]="getMessageById(message.replyToId)"></chat-message-reference-content>
593
+ </div>
594
+
595
+ <span class="k-chat-bubble-text" *ngIf="message.isDeleted">
596
+ {{ getDeletedMessageText() }}
597
+ </span>
598
+
599
+ <span class="k-chat-bubble-text" *ngIf="!message.isDeleted && parts.length > 0">
600
+ <ng-container *ngFor="let part of parts">
601
+ <ng-container *ngIf="part.type === 'text'">{{part.content}}</ng-container>
602
+ <a *ngIf="part.type === 'link'" [href]="part.href" target="_blank">{{part.content}}</a>
603
+ </ng-container>
604
+ </span>
605
+ </ng-container>
606
+
607
+ <ul
608
+ class="k-chat-file-wrapper"
609
+ *ngIf="hasFiles && !message.isDeleted"
610
+ [ngClass]="{
611
+ 'k-chat-files-wrap': chatService.messageFilesLayout === 'wrap',
612
+ 'k-chat-files-horizontal': chatService.messageFilesLayout === 'horizontal'
613
+ }"
614
+ >
615
+ <li
616
+ *ngFor="let file of message.files"
617
+ class="k-chat-file"
618
+ [chatFile]="file"
619
+ [fileActions]="fileActions"
620
+ (actionClick)="onFileAction($event, file)"
621
+ (actionsToggle)="onActionPopupChange($event)"
622
+ (actionButtonClick)="onActionButtonClick($event)"
623
+ ></li>
624
+ </ul>
625
+
626
+ <div class="k-chat-download-button-wrapper" *ngIf="hasMultipleFiles && !message.isDeleted">
627
+ <button
628
+ kendoButton
629
+ class="k-chat-download-button"
630
+ fillMode="flat"
631
+ icon="download"
632
+ [svgIcon]="downloadIcon"
633
+ [attr.title]="textFor('downloadAllFilesText')"
634
+ (click)="onDownloadAll()"
635
+ >{{ textFor('downloadAllFilesText') }}</button>
636
+ </div>
637
+ </div>
638
+
639
+ <span
640
+ class="k-bubble-expandable-indicator"
641
+ *ngIf="isMessageExpandable && showExpandCollapseIcon"
642
+ [attr.tabindex]="'0'"
643
+ [attr.role]="'button'"
644
+ [attr.title]="isMessageExpanded ? textFor('collapseTitle') : textFor('expandTitle')"
645
+ (mousedown)="chatService.toggleMessageState = true"
646
+ (click)="toggleMessageState($event)"
647
+ >
648
+ <kendo-icon-wrapper
649
+ [name]="isMessageExpanded ? 'chevron-up' : 'chevron-down'"
650
+ [svgIcon]="isMessageExpanded ? collapseIcon : expandIcon"
651
+ >
652
+ </kendo-icon-wrapper>
653
+ </span>
654
+ </div>
490
655
  </ng-container>
491
- </span>
492
656
 
493
- <kendo-toolbar *ngIf="isToolbarVisible()" class="k-chat-message-toolbar" fillMode="flat">
657
+ <span class="k-message-status" *ngIf="message.status">
658
+ <ng-container *ngIf="statusTemplate?.templateRef">
659
+ <ng-template
660
+ [ngTemplateOutlet]="statusTemplate.templateRef"
661
+ [ngTemplateOutletContext]="{ $implicit: message.status, message }"
662
+ >
663
+ </ng-template>
664
+ </ng-container>
665
+ <ng-container *ngIf="!statusTemplate?.templateRef">
666
+ {{ message.status }}
667
+ </ng-container>
668
+ </span>
669
+ </ng-container>
670
+ <kendo-toolbar *ngIf="showToolbar" class="k-chat-message-toolbar" fillMode="flat">
494
671
  <kendo-toolbar-button
495
- *ngFor="let action of chatService.messageToolbarActions"
672
+ *ngFor="let action of toolbarActions"
496
673
  fillMode="flat"
497
674
  [icon]="action.icon"
498
675
  [svgIcon]="action.svgIcon"
@@ -502,36 +679,25 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
502
679
  >
503
680
  </kendo-toolbar-button>
504
681
  </kendo-toolbar>
505
-
506
- <ng-template #typing>
507
- <div class="k-chat-bubble k-bubble">
508
- <div class="k-typing-indicator">
509
- <span></span>
510
- <span></span>
511
- <span></span>
512
- </div>
513
- </div>
514
- </ng-template>
515
-
516
- <kendo-contextmenu
517
- *ngIf="!message.isDeleted"
518
- [target]="element"
519
- [items]="contextMenuActions"
520
- (popupOpen)="handleMenuOpen()"
521
- (popupClose)="handleMenuClose($event)"
522
- (select)="onContextMenuAction($event.item.originalAction)"
523
- [popupAlign]="{ horizontal: 'right', vertical: 'top' }"
524
- [collision]="{ horizontal: 'flip', vertical: 'flip'}"
525
- ></kendo-contextmenu>
526
682
  `,
527
683
  standalone: true,
528
- imports: [NgIf, NgClass, NgFor, NgTemplateOutlet, IconWrapperComponent, KENDO_BUTTONS, ChatFileComponent, ContextMenuComponent, ToolBarComponent, ToolBarButtonComponent, MessageReferenceComponent],
684
+ imports: [NgIf, NgClass, NgFor, NgTemplateOutlet, IconWrapperComponent, KENDO_BUTTONS, ChatFileComponent, ToolBarComponent, ToolBarButtonComponent, MessageReferenceComponent],
529
685
  }]
530
- }], ctorParameters: () => [{ type: i0.ElementRef }, { type: i1.IntlService }, { type: i2.ChatService }, { type: i3.LocalizationService }], propDecorators: { message: [{
686
+ }], ctorParameters: () => [{ type: i0.ElementRef }, { type: i1.IntlService }, { type: i2.ChatService }, { type: i3.LocalizationService }, { type: i0.ChangeDetectorRef }], propDecorators: { message: [{
531
687
  type: Input
532
688
  }], tabbable: [{
533
689
  type: Input
534
- }], template: [{
690
+ }], authorMessageContentTemplate: [{
691
+ type: Input
692
+ }], receiverMessageContentTemplate: [{
693
+ type: Input
694
+ }], messageContentTemplate: [{
695
+ type: Input
696
+ }], authorMessageTemplate: [{
697
+ type: Input
698
+ }], receiverMessageTemplate: [{
699
+ type: Input
700
+ }], messageTemplate: [{
535
701
  type: Input
536
702
  }], statusTemplate: [{
537
703
  type: Input
@@ -539,8 +705,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
539
705
  type: Input
540
706
  }], authorId: [{
541
707
  type: Input
542
- }], contextMenuVisibilityChange: [{
543
- type: Output
544
708
  }], cssClass: [{
545
709
  type: HostBinding,
546
710
  args: ['class.k-message']
@@ -553,7 +717,4 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
553
717
  }], tabIndex: [{
554
718
  type: HostBinding,
555
719
  args: ['attr.tabIndex']
556
- }], onContextMenu: [{
557
- type: HostListener,
558
- args: ['contextmenu', ['$event']]
559
720
  }] } });