@progress/kendo-angular-conversational-ui 20.1.2-develop.1 → 21.0.0-develop.10

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 (103) 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/codemods/template-transformer/index.js +1 -2
  27. package/codemods/utils.js +2 -2
  28. package/codemods/v20/chat-user.js +1 -1
  29. package/conversational-ui.module.d.ts +18 -11
  30. package/directives.d.ts +9 -2
  31. package/esm2022/ai-prompt/aiprompt.component.mjs +13 -11
  32. package/esm2022/ai-prompt/aiprompt.module.mjs +4 -4
  33. package/esm2022/ai-prompt/common/aiprompt.service.mjs +3 -3
  34. package/esm2022/ai-prompt/common/output-card.component.mjs +4 -4
  35. package/esm2022/ai-prompt/common/toolbar-focusable.directive.mjs +4 -4
  36. package/esm2022/ai-prompt/common/toolbar-navigation.service.mjs +4 -4
  37. package/esm2022/ai-prompt/localization/custom-messages.component.mjs +4 -4
  38. package/esm2022/ai-prompt/localization/localized-messages.directive.mjs +4 -4
  39. package/esm2022/ai-prompt/localization/messages.mjs +3 -3
  40. package/esm2022/ai-prompt/templates/aiprompt-output-body-template.directive.mjs +4 -4
  41. package/esm2022/ai-prompt/templates/aiprompt-output-template.directive.mjs +4 -4
  42. package/esm2022/ai-prompt/templates/toolbar-actions.template.mjs +5 -5
  43. package/esm2022/ai-prompt/views/base-view.mjs +5 -5
  44. package/esm2022/ai-prompt/views/command-view.component.mjs +4 -4
  45. package/esm2022/ai-prompt/views/custom-view.component.mjs +4 -4
  46. package/esm2022/ai-prompt/views/output-view.component.mjs +4 -4
  47. package/esm2022/ai-prompt/views/prompt-view.component.mjs +4 -4
  48. package/esm2022/chat/api/index.mjs +3 -1
  49. package/{chat/api/message-toolbar-visibility.d.ts → esm2022/chat/api/message-settings.interface.mjs} +1 -4
  50. package/esm2022/chat/api/suggestions-layout.mjs +5 -0
  51. package/esm2022/chat/attachment.component.mjs +3 -3
  52. package/esm2022/chat/builtin-actions.mjs +2 -0
  53. package/esm2022/chat/cards/hero-card.component.mjs +3 -3
  54. package/esm2022/chat/chat-file.component.mjs +6 -6
  55. package/esm2022/chat/chat.component.mjs +268 -74
  56. package/esm2022/chat/chat.directives.mjs +18 -0
  57. package/esm2022/chat/chat.module.mjs +18 -11
  58. package/esm2022/chat/common/chat.service.mjs +86 -7
  59. package/esm2022/chat/common/models/default-model-fields.mjs +0 -1
  60. package/esm2022/chat/common/scroll-anchor.directive.mjs +4 -4
  61. package/esm2022/chat/common/scroll-button.component.mjs +81 -0
  62. package/esm2022/chat/common/scroll.service.mjs +110 -0
  63. package/esm2022/chat/common/utils.mjs +22 -3
  64. package/esm2022/chat/l10n/custom-messages.component.mjs +4 -4
  65. package/esm2022/chat/l10n/localized-messages.directive.mjs +4 -4
  66. package/esm2022/chat/l10n/messages.mjs +15 -3
  67. package/esm2022/chat/message-attachments.component.mjs +6 -6
  68. package/esm2022/chat/message-box.component.mjs +12 -7
  69. package/esm2022/chat/message-list.component.mjs +168 -22
  70. package/esm2022/chat/message-reference-content.component.mjs +4 -4
  71. package/esm2022/chat/message.component.mjs +491 -328
  72. package/esm2022/chat/suggested-actions.component.mjs +299 -81
  73. package/esm2022/chat/templates/attachment-template.directive.mjs +5 -5
  74. package/esm2022/chat/templates/author-message-content-template.directive.mjs +39 -0
  75. package/esm2022/chat/templates/author-message-template.directive.mjs +39 -0
  76. package/esm2022/chat/templates/header-template.directive.mjs +4 -4
  77. package/esm2022/chat/templates/message-box.directive.mjs +4 -4
  78. package/esm2022/chat/templates/message-content-template.directive.mjs +39 -0
  79. package/esm2022/chat/templates/message-template.directive.mjs +6 -6
  80. package/esm2022/chat/templates/no-data-template.directive.mjs +38 -0
  81. package/esm2022/chat/templates/receiver-message-content-template.directive.mjs +39 -0
  82. package/esm2022/chat/templates/receiver-message-template.directive.mjs +39 -0
  83. package/esm2022/chat/templates/status-template.directive.mjs +4 -4
  84. package/esm2022/chat/templates/suggestion-template.directive.mjs +4 -4
  85. package/esm2022/chat/templates/timestamp-template.directive.mjs +5 -5
  86. package/esm2022/chat/templates/user-status-template.directive.mjs +38 -0
  87. package/esm2022/conversational-ui.module.mjs +21 -14
  88. package/esm2022/directives.mjs +15 -1
  89. package/esm2022/index.mjs +7 -0
  90. package/esm2022/inline-ai-prompt/inlineaiprompt-content.component.mjs +5 -5
  91. package/esm2022/inline-ai-prompt/inlineaiprompt.component.mjs +4 -4
  92. package/esm2022/inline-ai-prompt/inlineaiprompt.module.mjs +4 -4
  93. package/esm2022/inline-ai-prompt/inlineaiprompt.service.mjs +4 -4
  94. package/esm2022/inline-ai-prompt/localization/custom-messages.component.mjs +4 -4
  95. package/esm2022/inline-ai-prompt/localization/localized-messages.directive.mjs +4 -4
  96. package/esm2022/inline-ai-prompt/localization/messages.mjs +3 -3
  97. package/esm2022/inline-ai-prompt/output-template.directive.mjs +5 -5
  98. package/esm2022/package-metadata.mjs +2 -2
  99. package/fesm2022/progress-kendo-angular-conversational-ui.mjs +1969 -739
  100. package/index.d.ts +7 -0
  101. package/package.json +18 -18
  102. package/schematics/ngAdd/index.js +1 -1
  103. /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);
@@ -187,11 +253,13 @@ export class MessageComponent extends ChatItem {
187
253
  handleMenuClose(event) {
188
254
  if (event) {
189
255
  const originalEvent = event.originalEvent;
190
- originalEvent && this.onActionButtonClick(originalEvent);
256
+ if (originalEvent) {
257
+ this.onActionButtonClick(originalEvent);
258
+ }
191
259
  }
192
- this.active = false;
193
- this.contextMenuVisibilityChange.emit(false);
194
- if (this.selectOnMenuClose) {
260
+ this.chatService.active = false;
261
+ this.chatService.emit('contextMenuVisibilityChange', false);
262
+ if (this.chatService.selectOnMenuClose) {
195
263
  this.selected = true;
196
264
  this.focus();
197
265
  }
@@ -200,16 +268,16 @@ export class MessageComponent extends ChatItem {
200
268
  const clickOutsideMessage = event instanceof MouseEvent && !event.target?.closest('.k-chat-bubble');
201
269
  const menuItemClick = event instanceof MouseEvent && event.target?.closest(MENU_ITEM_SELECTOR);
202
270
  if (clickOutsideMessage && !menuItemClick) {
203
- this.selectOnMenuClose = false;
271
+ this.chatService.selectOnMenuClose = false;
204
272
  }
205
273
  }
206
274
  handleMenuOpen() {
207
- this.selectOnMenuClose = this.selected;
208
- this.contextMenuVisibilityChange.emit(true);
275
+ this.chatService.selectOnMenuClose = this.selected;
276
+ this.chatService.emit('contextMenuVisibilityChange', true);
209
277
  }
210
278
  onActionPopupChange(expanded) {
211
279
  if (expanded) {
212
- this.active = true;
280
+ this.chatService.active = true;
213
281
  this.handleMenuOpen();
214
282
  }
215
283
  else {
@@ -219,124 +287,211 @@ export class MessageComponent extends ChatItem {
219
287
  isOwnMessage(msg) {
220
288
  return isAuthor(this.authorId, msg);
221
289
  }
222
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: MessageComponent, deps: [{ token: i0.ElementRef }, { token: i1.IntlService }, { token: i2.ChatService }, { token: i3.LocalizationService }], target: i0.ɵɵFactoryTarget.Component });
223
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "16.2.12", 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: [
224
334
  {
225
335
  provide: ChatItem,
226
336
  useExisting: forwardRef(() => MessageComponent)
227
337
  }
228
338
  ], usesInheritance: true, ngImport: i0, template: `
229
- <time
230
- [attr.aria-hidden]="!selected"
231
- class="k-message-time"
232
- *ngIf="message.timestamp"
233
- >
234
- {{ formatTimeStamp(message.timestamp) }}
235
- </time>
339
+ <ng-container *ngIf="useCustomBubbleTemplate">
340
+ <ng-container *ngTemplateOutlet="getActiveBubbleTemplate()?.templateRef; context: { $implicit: message };"></ng-container>
341
+ </ng-container>
236
342
 
237
- <ng-container *ngIf="!message.typing; else typing">
238
- <div
239
- class="k-chat-bubble k-bubble"
240
- *ngIf="template"
241
- [attr.tabindex]="0"
242
- [ngClass]="{
243
- 'k-selected': selected,
244
- 'k-focus': selected,
245
- 'k-active': active
246
- }"
343
+ <ng-container *ngIf="!useCustomBubbleTemplate">
344
+ <time
345
+ [attr.aria-hidden]="!selected"
346
+ class="k-message-time"
347
+ *ngIf="message.timestamp"
247
348
  >
248
- <div class="k-bubble-content">
249
- <ng-container
250
- *ngTemplateOutlet="template.templateRef; context: { $implicit: message };"
251
- >
252
- </ng-container>
253
- </div>
254
- </div>
349
+ {{ formatTimeStamp(message.timestamp) }}
350
+ </time>
255
351
 
256
- <div
257
- class="k-chat-bubble k-bubble"
258
- *ngIf="!template && (message.text || message.files)"
259
- [attr.tabindex]="0"
260
- [ngClass]="{
261
- 'k-bubble-expandable': chatService.allowMessageCollapse,
262
- 'k-expanded': isMessageExpanded,
263
- 'k-selected': selected,
264
- 'k-focus': selected,
265
- 'k-active': active
266
- }"
267
- >
268
- <div class="k-bubble-content">
269
- <span class="k-chat-bubble-text" *ngIf="message.text || message.isDeleted">
270
- <div class="k-message-reference k-message-reference-receiver" *ngIf="message.replyToId && !message.isDeleted" (click)="onReplyReferenceClick($event, message.replyToId)">
271
- <chat-message-reference-content [message]="getMessageById(message.replyToId)"></chat-message-reference-content>
272
- </div>
273
- <ng-container *ngIf="message.isDeleted && isOwnMessage(message)">{{ textFor('deletedMessageSenderText') }}</ng-container>
274
- <ng-container *ngIf="message.isDeleted && !isOwnMessage(message)">{{ textFor('deletedMessageReceiverText') }}</ng-container>
275
- <ng-container *ngIf="!message.isDeleted && parts.length">
276
- <ng-container *ngFor="let part of parts">
277
- <ng-container *ngIf="part.type === 'text'">{{part.content}}</ng-container>
278
- <a *ngIf="part.type === 'link'" [href]="part.href" target="_blank">{{part.content}}</a>
279
- </ng-container>
280
- </ng-container>
281
- </span>
282
- <ul class="k-chat-file-wrapper" *ngIf="message.files && message.files.length > 0">
283
- <li
284
- *ngFor="let file of message.files"
285
- class="k-chat-file"
286
- [chatFile]="file"
287
- [fileActions]="fileActions"
288
- (actionClick)="onFileAction($event, file)"
289
- (actionsToggle)="onActionPopupChange($event)"
290
- (actionButtonClick)="onActionButtonClick($event)"
291
- ></li>
292
- </ul>
293
- <div class="k-chat-download-button-wrapper" *ngIf="message.files?.length > 1">
294
- <button
295
- kendoButton
296
- class="k-chat-download-button"
297
- fillMode="flat"
298
- icon="download"
299
- [svgIcon]="downloadIcon"
300
- (click)="onDownloadAll()"
301
- >{{ 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>
302
358
  </div>
303
359
  </div>
304
- <span
305
- class="k-bubble-expandable-indicator"
306
- *ngIf="chatService.allowMessageCollapse"
307
- [attr.role]="'button'"
308
- [attr.title]="isMessageExpanded ? textFor('collapseTitle') : textFor('expandTitle')"
309
- (mousedown)="chatService.toggleMessageState = true"
310
- (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
+ }"
311
374
  >
312
- <kendo-icon-wrapper
313
- [name]="isMessageExpanded ? 'chevron-up' : 'chevron-down'"
314
- [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)"
315
386
  >
316
- </kendo-icon-wrapper>
317
- </span>
318
- </div>
319
- </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>
320
394
 
321
- <span
322
- class="k-message-status"
323
- *ngIf="message.status"
324
- >
325
- <ng-container *ngIf="statusTemplate?.templateRef">
326
- <ng-template
327
- [ngTemplateOutlet]="statusTemplate.templateRef"
328
- [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
+ }"
329
406
  >
330
- </ng-template>
331
- </ng-container>
332
- <ng-container *ngIf="!statusTemplate?.templateRef">
333
- {{ 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>
334
477
  </ng-container>
335
- </span>
336
478
 
337
- <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">
338
493
  <kendo-toolbar-button
339
- *ngFor="let action of chatService.messageToolbarActions"
494
+ *ngFor="let action of toolbarActions"
340
495
  fillMode="flat"
341
496
  [icon]="action.icon"
342
497
  [svgIcon]="action.svgIcon"
@@ -346,30 +501,9 @@ export class MessageComponent extends ChatItem {
346
501
  >
347
502
  </kendo-toolbar-button>
348
503
  </kendo-toolbar>
349
-
350
- <ng-template #typing>
351
- <div class="k-chat-bubble k-bubble">
352
- <div class="k-typing-indicator">
353
- <span></span>
354
- <span></span>
355
- <span></span>
356
- </div>
357
- </div>
358
- </ng-template>
359
-
360
- <kendo-contextmenu
361
- *ngIf="!message.isDeleted"
362
- [target]="element"
363
- [items]="contextMenuActions"
364
- (popupOpen)="handleMenuOpen()"
365
- (popupClose)="handleMenuClose($event)"
366
- (select)="onContextMenuAction($event.item.originalAction)"
367
- [popupAlign]="{ horizontal: 'right', vertical: 'top' }"
368
- [collision]="{ horizontal: 'flip', vertical: 'flip'}"
369
- ></kendo-contextmenu>
370
- `, 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"] }] });
371
505
  }
372
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: MessageComponent, decorators: [{
506
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: MessageComponent, decorators: [{
373
507
  type: Component,
374
508
  args: [{
375
509
  selector: 'kendo-chat-message',
@@ -380,117 +514,162 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImpo
380
514
  }
381
515
  ],
382
516
  template: `
383
- <time
384
- [attr.aria-hidden]="!selected"
385
- class="k-message-time"
386
- *ngIf="message.timestamp"
387
- >
388
- {{ formatTimeStamp(message.timestamp) }}
389
- </time>
517
+ <ng-container *ngIf="useCustomBubbleTemplate">
518
+ <ng-container *ngTemplateOutlet="getActiveBubbleTemplate()?.templateRef; context: { $implicit: message };"></ng-container>
519
+ </ng-container>
390
520
 
391
- <ng-container *ngIf="!message.typing; else typing">
392
- <div
393
- class="k-chat-bubble k-bubble"
394
- *ngIf="template"
395
- [attr.tabindex]="0"
396
- [ngClass]="{
397
- 'k-selected': selected,
398
- 'k-focus': selected,
399
- 'k-active': active
400
- }"
521
+ <ng-container *ngIf="!useCustomBubbleTemplate">
522
+ <time
523
+ [attr.aria-hidden]="!selected"
524
+ class="k-message-time"
525
+ *ngIf="message.timestamp"
401
526
  >
402
- <div class="k-bubble-content">
403
- <ng-container
404
- *ngTemplateOutlet="template.templateRef; context: { $implicit: message };"
405
- >
406
- </ng-container>
407
- </div>
408
- </div>
527
+ {{ formatTimeStamp(message.timestamp) }}
528
+ </time>
409
529
 
410
- <div
411
- class="k-chat-bubble k-bubble"
412
- *ngIf="!template && (message.text || message.files)"
413
- [attr.tabindex]="0"
414
- [ngClass]="{
415
- 'k-bubble-expandable': chatService.allowMessageCollapse,
416
- 'k-expanded': isMessageExpanded,
417
- 'k-selected': selected,
418
- 'k-focus': selected,
419
- 'k-active': active
420
- }"
421
- >
422
- <div class="k-bubble-content">
423
- <span class="k-chat-bubble-text" *ngIf="message.text || message.isDeleted">
424
- <div class="k-message-reference k-message-reference-receiver" *ngIf="message.replyToId && !message.isDeleted" (click)="onReplyReferenceClick($event, message.replyToId)">
425
- <chat-message-reference-content [message]="getMessageById(message.replyToId)"></chat-message-reference-content>
426
- </div>
427
- <ng-container *ngIf="message.isDeleted && isOwnMessage(message)">{{ textFor('deletedMessageSenderText') }}</ng-container>
428
- <ng-container *ngIf="message.isDeleted && !isOwnMessage(message)">{{ textFor('deletedMessageReceiverText') }}</ng-container>
429
- <ng-container *ngIf="!message.isDeleted && parts.length">
430
- <ng-container *ngFor="let part of parts">
431
- <ng-container *ngIf="part.type === 'text'">{{part.content}}</ng-container>
432
- <a *ngIf="part.type === 'link'" [href]="part.href" target="_blank">{{part.content}}</a>
433
- </ng-container>
434
- </ng-container>
435
- </span>
436
- <ul class="k-chat-file-wrapper" *ngIf="message.files && message.files.length > 0">
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
- <div class="k-chat-download-button-wrapper" *ngIf="message.files?.length > 1">
448
- <button
449
- kendoButton
450
- class="k-chat-download-button"
451
- fillMode="flat"
452
- icon="download"
453
- [svgIcon]="downloadIcon"
454
- (click)="onDownloadAll()"
455
- >{{ 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>
456
536
  </div>
457
537
  </div>
458
- <span
459
- class="k-bubble-expandable-indicator"
460
- *ngIf="chatService.allowMessageCollapse"
461
- [attr.role]="'button'"
462
- [attr.title]="isMessageExpanded ? textFor('collapseTitle') : textFor('expandTitle')"
463
- (mousedown)="chatService.toggleMessageState = true"
464
- (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
+ }"
465
552
  >
466
- <kendo-icon-wrapper
467
- [name]="isMessageExpanded ? 'chevron-up' : 'chevron-down'"
468
- [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)"
469
564
  >
470
- </kendo-icon-wrapper>
471
- </span>
472
- </div>
473
- </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>
474
572
 
475
- <span
476
- class="k-message-status"
477
- *ngIf="message.status"
478
- >
479
- <ng-container *ngIf="statusTemplate?.templateRef">
480
- <ng-template
481
- [ngTemplateOutlet]="statusTemplate.templateRef"
482
- [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
+ }"
483
584
  >
484
- </ng-template>
485
- </ng-container>
486
- <ng-container *ngIf="!statusTemplate?.templateRef">
487
- {{ 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>
488
655
  </ng-container>
489
- </span>
490
656
 
491
- <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">
492
671
  <kendo-toolbar-button
493
- *ngFor="let action of chatService.messageToolbarActions"
672
+ *ngFor="let action of toolbarActions"
494
673
  fillMode="flat"
495
674
  [icon]="action.icon"
496
675
  [svgIcon]="action.svgIcon"
@@ -500,36 +679,25 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImpo
500
679
  >
501
680
  </kendo-toolbar-button>
502
681
  </kendo-toolbar>
503
-
504
- <ng-template #typing>
505
- <div class="k-chat-bubble k-bubble">
506
- <div class="k-typing-indicator">
507
- <span></span>
508
- <span></span>
509
- <span></span>
510
- </div>
511
- </div>
512
- </ng-template>
513
-
514
- <kendo-contextmenu
515
- *ngIf="!message.isDeleted"
516
- [target]="element"
517
- [items]="contextMenuActions"
518
- (popupOpen)="handleMenuOpen()"
519
- (popupClose)="handleMenuClose($event)"
520
- (select)="onContextMenuAction($event.item.originalAction)"
521
- [popupAlign]="{ horizontal: 'right', vertical: 'top' }"
522
- [collision]="{ horizontal: 'flip', vertical: 'flip'}"
523
- ></kendo-contextmenu>
524
682
  `,
525
683
  standalone: true,
526
- 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],
527
685
  }]
528
- }], ctorParameters: function () { return [{ 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: [{
529
687
  type: Input
530
688
  }], tabbable: [{
531
689
  type: Input
532
- }], 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: [{
533
701
  type: Input
534
702
  }], statusTemplate: [{
535
703
  type: Input
@@ -537,8 +705,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImpo
537
705
  type: Input
538
706
  }], authorId: [{
539
707
  type: Input
540
- }], contextMenuVisibilityChange: [{
541
- type: Output
542
708
  }], cssClass: [{
543
709
  type: HostBinding,
544
710
  args: ['class.k-message']
@@ -551,7 +717,4 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImpo
551
717
  }], tabIndex: [{
552
718
  type: HostBinding,
553
719
  args: ['attr.tabIndex']
554
- }], onContextMenu: [{
555
- type: HostListener,
556
- args: ['contextmenu', ['$event']]
557
720
  }] } });