@progress/kendo-angular-conversational-ui 20.0.0-develop.3 → 20.0.0-develop.5

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 (86) hide show
  1. package/chat/api/action.interface.d.ts +1 -1
  2. package/chat/api/attachment.interface.d.ts +1 -1
  3. package/chat/api/chat-file-interface.d.ts +41 -0
  4. package/chat/api/chat-suggestion.interface.d.ts +25 -0
  5. package/chat/api/file-action.d.ts +42 -0
  6. package/chat/api/file-download-event.interface.d.ts +21 -0
  7. package/chat/api/index.d.ts +8 -0
  8. package/chat/api/message-action.d.ts +42 -0
  9. package/{esm2022/chat/chat.directives.mjs → chat/api/message-toolbar-visibility.d.ts} +4 -4
  10. package/chat/api/message-width-mode.d.ts +10 -0
  11. package/chat/api/message.interface.d.ts +30 -12
  12. package/chat/api/send-button-settings.d.ts +15 -0
  13. package/chat/api/user.interface.d.ts +4 -0
  14. package/chat/attachment.component.d.ts +1 -1
  15. package/chat/cards/hero-card.component.d.ts +1 -1
  16. package/chat/chat-file.component.d.ts +34 -0
  17. package/chat/chat-item.d.ts +1 -0
  18. package/chat/chat-view.d.ts +1 -1
  19. package/chat/chat.component.d.ts +218 -19
  20. package/chat/chat.module.d.ts +8 -4
  21. package/chat/common/chat.service.d.ts +51 -0
  22. package/chat/{chat.directives.d.ts → common/models/default-model-fields.d.ts} +5 -4
  23. package/chat/common/models/message-box-options.d.ts +1 -1
  24. package/chat/common/models/model-fields.d.ts +111 -0
  25. package/chat/common/utils.d.ts +50 -0
  26. package/chat/l10n/messages.d.ts +40 -3
  27. package/chat/message-attachments.component.d.ts +1 -4
  28. package/chat/message-box.component.d.ts +54 -25
  29. package/chat/message-list.component.d.ts +18 -11
  30. package/chat/message-reference-content.component.d.ts +24 -0
  31. package/chat/message.component.d.ts +54 -6
  32. package/chat/suggested-actions.component.d.ts +17 -4
  33. package/chat/templates/header-template.directive.d.ts +24 -0
  34. package/chat/{message-box.directive.d.ts → templates/message-box.directive.d.ts} +1 -1
  35. package/chat/templates/status-template.directive.d.ts +24 -0
  36. package/chat/templates/suggestion-template.directive.d.ts +24 -0
  37. package/chat/templates/timestamp-template.directive.d.ts +28 -0
  38. package/codemods/template-transformer/index.js +94 -0
  39. package/codemods/utils.js +609 -0
  40. package/codemods/v20/chat-user.js +50 -0
  41. package/conversational-ui.module.d.ts +11 -7
  42. package/directives.d.ts +9 -5
  43. package/esm2022/chat/api/chat-file-interface.mjs +5 -0
  44. package/esm2022/chat/api/chat-suggestion.interface.mjs +5 -0
  45. package/esm2022/chat/api/file-action.mjs +5 -0
  46. package/esm2022/chat/api/file-download-event.interface.mjs +5 -0
  47. package/esm2022/chat/api/index.mjs +8 -0
  48. package/esm2022/chat/api/message-action.mjs +5 -0
  49. package/esm2022/chat/api/message-toolbar-visibility.mjs +5 -0
  50. package/esm2022/chat/api/message-width-mode.mjs +5 -0
  51. package/esm2022/chat/api/send-button-settings.mjs +5 -0
  52. package/esm2022/chat/attachment.component.mjs +1 -1
  53. package/esm2022/chat/builtin-actions.mjs +1 -1
  54. package/esm2022/chat/cards/hero-card.component.mjs +1 -1
  55. package/esm2022/chat/chat-file.component.mjs +141 -0
  56. package/esm2022/chat/chat-item.mjs +1 -0
  57. package/esm2022/chat/chat-view.mjs +2 -2
  58. package/esm2022/chat/chat.component.mjs +518 -57
  59. package/esm2022/chat/chat.module.mjs +8 -4
  60. package/esm2022/chat/common/chat.service.mjs +97 -0
  61. package/esm2022/chat/common/models/default-model-fields.mjs +26 -0
  62. package/esm2022/chat/common/models/model-fields.mjs +5 -0
  63. package/esm2022/chat/common/utils.mjs +127 -0
  64. package/esm2022/chat/l10n/messages.mjs +60 -5
  65. package/esm2022/chat/message-attachments.component.mjs +1 -4
  66. package/esm2022/chat/message-box.component.mjs +360 -111
  67. package/esm2022/chat/message-list.component.mjs +166 -96
  68. package/esm2022/chat/message-reference-content.component.mjs +75 -0
  69. package/esm2022/chat/message.component.mjs +448 -35
  70. package/esm2022/chat/suggested-actions.component.mjs +151 -41
  71. package/esm2022/chat/templates/header-template.directive.mjs +33 -0
  72. package/esm2022/chat/{message-box.directive.mjs → templates/message-box.directive.mjs} +1 -1
  73. package/esm2022/chat/templates/status-template.directive.mjs +33 -0
  74. package/esm2022/chat/templates/suggestion-template.directive.mjs +33 -0
  75. package/esm2022/chat/templates/timestamp-template.directive.mjs +39 -0
  76. package/esm2022/conversational-ui.module.mjs +12 -8
  77. package/esm2022/directives.mjs +12 -4
  78. package/esm2022/index.mjs +9 -3
  79. package/esm2022/package-metadata.mjs +2 -2
  80. package/fesm2022/progress-kendo-angular-conversational-ui.mjs +4986 -3125
  81. package/index.d.ts +10 -3
  82. package/package.json +29 -12
  83. /package/chat/{attachment-template.directive.d.ts → templates/attachment-template.directive.d.ts} +0 -0
  84. /package/chat/{message-template.directive.d.ts → templates/message-template.directive.d.ts} +0 -0
  85. /package/esm2022/chat/{attachment-template.directive.mjs → templates/attachment-template.directive.mjs} +0 -0
  86. /package/esm2022/chat/{message-template.directive.mjs → templates/message-template.directive.mjs} +0 -0
@@ -2,13 +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, forwardRef, HostBinding, Input } from '@angular/core';
5
+ import { Component, ElementRef, EventEmitter, forwardRef, HostBinding, HostListener, Input, Output } from '@angular/core';
6
6
  import { ChatItem } from './chat-item';
7
- import { MessageTemplateDirective } from './message-template.directive';
7
+ import { MessageTemplateDirective } from './templates/message-template.directive';
8
8
  import { IntlService } from '@progress/kendo-angular-intl';
9
- import { NgIf, NgTemplateOutlet } from '@angular/common';
9
+ import { KENDO_BUTTONS } from '@progress/kendo-angular-buttons';
10
+ import { ContextMenuComponent } from '@progress/kendo-angular-menu';
11
+ import { NgClass, NgFor, NgIf, NgTemplateOutlet } from '@angular/common';
12
+ import { IconWrapperComponent } from '@progress/kendo-angular-icons';
13
+ import { ToolBarButtonComponent, ToolBarComponent } from '@progress/kendo-angular-toolbar';
14
+ import { chevronDownIcon, chevronUpIcon, downloadIcon } from '@progress/kendo-svg-icons';
15
+ import { ChatService } from './common/chat.service';
16
+ import { LocalizationService } from '@progress/kendo-angular-l10n';
17
+ import { URL_REGEX, FILE_ACTION_BTN_SELECTOR, DOWNLOAD_ALL_SELECTOR, MENU_ITEM_SELECTOR } from './common/utils';
18
+ import { isAuthor } from './chat-view';
19
+ import { ChatFileComponent } from './chat-file.component';
20
+ import { MessageReferenceComponent } from './message-reference-content.component';
21
+ import { ChatStatusTemplateDirective } from './templates/status-template.directive';
22
+ import { Keys, normalizeNumpadKeys } from '@progress/kendo-angular-common';
10
23
  import * as i0 from "@angular/core";
11
24
  import * as i1 from "@progress/kendo-angular-intl";
25
+ import * as i2 from "./common/chat.service";
26
+ import * as i3 from "@progress/kendo-angular-l10n";
27
+ import * as i4 from "@progress/kendo-angular-buttons";
12
28
  // eslint-disable no-forward-ref
13
29
  /**
14
30
  * @hidden
@@ -16,18 +32,69 @@ import * as i1 from "@progress/kendo-angular-intl";
16
32
  export class MessageComponent extends ChatItem {
17
33
  element;
18
34
  intl;
35
+ chatService;
36
+ localization;
19
37
  message;
20
38
  tabbable;
21
39
  template;
40
+ statusTemplate;
41
+ showMessageTime = true;
42
+ authorId;
43
+ contextMenuVisibilityChange = new EventEmitter();
22
44
  cssClass = true;
45
+ get removedClass() {
46
+ return this.message.isDeleted;
47
+ }
48
+ onKeyDown(event) {
49
+ if (this.message.isDeleted) {
50
+ return;
51
+ }
52
+ if (!this.chatService.allowMessageCollapse) {
53
+ return;
54
+ }
55
+ this.onExpandableKeydown(event);
56
+ }
23
57
  selected;
58
+ hovered;
59
+ active = false;
60
+ selectOnMenuClose = false;
24
61
  get tabIndex() {
25
62
  return this.tabbable ? '0' : '-1';
26
63
  }
27
- constructor(element, intl) {
64
+ onContextMenu(event) {
65
+ if (this.message.isDeleted) {
66
+ return;
67
+ }
68
+ this.active = true;
69
+ event.stopPropagation();
70
+ }
71
+ expandIcon = chevronDownIcon;
72
+ collapseIcon = chevronUpIcon;
73
+ downloadIcon = downloadIcon;
74
+ isMessageExpanded = false;
75
+ contextMenuActions = [];
76
+ fileActions = [];
77
+ constructor(element, intl, chatService, localization) {
28
78
  super();
29
79
  this.element = element;
30
80
  this.intl = intl;
81
+ this.chatService = chatService;
82
+ this.localization = localization;
83
+ }
84
+ ngOnInit() {
85
+ this.contextMenuActions = this.transformActions(this.chatService.messageContextMenuActions);
86
+ this.fileActions = this.transformActions(this.chatService.fileActions);
87
+ if (this.message.id) {
88
+ this.chatService.registerMessageElement(this.message.id, this.element);
89
+ }
90
+ }
91
+ ngOnDestroy() {
92
+ if (this.message.id) {
93
+ this.chatService.unregisterMessageElement(this.message.id);
94
+ }
95
+ }
96
+ textFor(key) {
97
+ return this.localization.get(key);
31
98
  }
32
99
  formatTimeStamp(date) {
33
100
  return this.intl.formatDate(date, { datetime: 'short' });
@@ -35,11 +102,132 @@ export class MessageComponent extends ChatItem {
35
102
  focus() {
36
103
  this.element.nativeElement.focus();
37
104
  }
38
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: MessageComponent, deps: [{ token: i0.ElementRef }, { token: i1.IntlService }], target: i0.ɵɵFactoryTarget.Component });
39
- 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" }, host: { properties: { "class.k-message": "this.cssClass", "class.k-selected": "this.selected", "class.k-focus": "this.selected", "attr.tabIndex": "this.tabIndex" } }, providers: [{
105
+ onDownloadAll() {
106
+ this.chatService.emit('fileDownload', { files: this.message.files, message: this.message });
107
+ }
108
+ toggleMessageState(event) {
109
+ event.stopImmediatePropagation();
110
+ this.isMessageExpanded = !this.isMessageExpanded;
111
+ this.chatService.toggleMessageState = false;
112
+ }
113
+ onExpandableKeydown(event) {
114
+ const key = normalizeNumpadKeys(event);
115
+ const isFileActionButton = event.target.closest(FILE_ACTION_BTN_SELECTOR) || event.target.closest(DOWNLOAD_ALL_SELECTOR);
116
+ if (!isFileActionButton && (key === Keys.Enter || key === Keys.Space)) {
117
+ event.preventDefault();
118
+ this.chatService.toggleMessageState = true;
119
+ this.toggleMessageState(event);
120
+ }
121
+ }
122
+ isToolbarVisible() {
123
+ if (!this.chatService.messageToolbarActions || this.chatService.messageToolbarActions.length === 0 || this.message.isDeleted) {
124
+ return false;
125
+ }
126
+ if (this.chatService.messageToolbarVisibility === 'hover') {
127
+ return this.selected || this.hovered;
128
+ }
129
+ return this.chatService.messageToolbarVisibility === 'always';
130
+ }
131
+ onToolbarAction(event, action, message) {
132
+ event.stopImmediatePropagation();
133
+ this.chatService.emit('toolbarAction', { action, message });
134
+ }
135
+ onContextMenuAction(action) {
136
+ if (action.id === 'reply') {
137
+ this.chatService.reply = this.message;
138
+ }
139
+ if (action.id === 'copy') {
140
+ navigator.clipboard.writeText(this.message.text);
141
+ }
142
+ this.chatService.emit('contextMenuAction', { action, message: this.message });
143
+ }
144
+ onFileAction(action, file) {
145
+ if (action.originalAction.id === 'download') {
146
+ this.chatService.emit('fileDownload', { files: [file], message: this.message });
147
+ }
148
+ this.chatService.emit('fileAction', { action: action.originalAction, file });
149
+ }
150
+ transformActions(actions) {
151
+ return actions.map(action => ({
152
+ text: action.label,
153
+ icon: action.icon,
154
+ svgIcon: action.svgIcon,
155
+ disabled: action.disabled,
156
+ originalAction: action
157
+ }));
158
+ }
159
+ getMessageById(id) {
160
+ return this.chatService.getMessageById(id);
161
+ }
162
+ // processes the message text to extract parts that are either plain text or links
163
+ // this allows us not to render an additional wrapper element for the text
164
+ getFormattedTextParts(text) {
165
+ if (!text) {
166
+ return [];
167
+ }
168
+ const parts = [];
169
+ const urlMatches = Array.from(text.matchAll(URL_REGEX));
170
+ let lastIndex = 0;
171
+ for (const match of urlMatches) {
172
+ const url = match[1];
173
+ const matchStart = match.index;
174
+ if (matchStart > lastIndex) {
175
+ parts.push({ type: 'text', content: text.substring(lastIndex, matchStart) });
176
+ }
177
+ parts.push({ type: 'link', content: url, href: url });
178
+ lastIndex = matchStart + match[0].length;
179
+ }
180
+ if (lastIndex < text.length) {
181
+ parts.push({ type: 'text', content: text.substring(lastIndex) });
182
+ }
183
+ return parts;
184
+ }
185
+ onReplyReferenceClick(event, replyToId) {
186
+ event.stopPropagation();
187
+ this.chatService.emit('replyReferenceClick', replyToId);
188
+ }
189
+ handleMenuClose(event) {
190
+ if (event) {
191
+ const originalEvent = event.originalEvent;
192
+ originalEvent && this.onActionButtonClick(originalEvent);
193
+ }
194
+ this.active = false;
195
+ this.contextMenuVisibilityChange.emit(false);
196
+ if (this.selectOnMenuClose) {
197
+ this.selected = true;
198
+ this.focus();
199
+ }
200
+ }
201
+ onActionButtonClick(event) {
202
+ const clickOutsideMessage = event instanceof MouseEvent && !event.target?.closest('.k-chat-bubble');
203
+ const menuItemClick = event instanceof MouseEvent && event.target?.closest(MENU_ITEM_SELECTOR);
204
+ if (clickOutsideMessage && !menuItemClick) {
205
+ this.selectOnMenuClose = false;
206
+ }
207
+ }
208
+ handleMenuOpen() {
209
+ this.selectOnMenuClose = this.selected;
210
+ this.contextMenuVisibilityChange.emit(true);
211
+ }
212
+ onActionPopupChange(expanded) {
213
+ if (expanded) {
214
+ this.active = true;
215
+ this.handleMenuOpen();
216
+ }
217
+ else {
218
+ this.handleMenuClose();
219
+ }
220
+ }
221
+ isOwnMessage(msg) {
222
+ return isAuthor(this.authorId, msg);
223
+ }
224
+ 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 });
225
+ 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: [
226
+ {
40
227
  provide: ChatItem,
41
228
  useExisting: forwardRef(() => MessageComponent)
42
- }], usesInheritance: true, ngImport: i0, template: `
229
+ }
230
+ ], usesInheritance: true, ngImport: i0, template: `
43
231
  <time
44
232
  [attr.aria-hidden]="!selected"
45
233
  class="k-message-time"
@@ -49,15 +237,86 @@ export class MessageComponent extends ChatItem {
49
237
  </time>
50
238
 
51
239
  <ng-container *ngIf="!message.typing; else typing">
52
- <div class="k-chat-bubble" *ngIf="template">
53
- <ng-container
54
- *ngTemplateOutlet="template.templateRef; context: { $implicit: message };"
55
- >
56
- </ng-container>
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
+ }"
249
+ >
250
+ <div class="k-bubble-content">
251
+ <ng-container
252
+ *ngTemplateOutlet="template.templateRef; context: { $implicit: message };"
253
+ >
254
+ </ng-container>
255
+ </div>
57
256
  </div>
58
257
 
59
- <div class="k-chat-bubble" *ngIf="!template && message.text">
60
- {{message.text}}
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">
278
+ <ng-container *ngFor="let part of getFormattedTextParts(message.text)">
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>
304
+ </div>
305
+ </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)"
313
+ >
314
+ <kendo-icon-wrapper
315
+ [name]="isMessageExpanded ? 'chevron-up' : 'chevron-down'"
316
+ [svgIcon]="isMessageExpanded ? collapseIcon : expandIcon"
317
+ >
318
+ </kendo-icon-wrapper>
319
+ </span>
61
320
  </div>
62
321
  </ng-container>
63
322
 
@@ -65,11 +324,33 @@ export class MessageComponent extends ChatItem {
65
324
  class="k-message-status"
66
325
  *ngIf="message.status"
67
326
  >
68
- {{ message.status }}
327
+ <ng-container *ngIf="statusTemplate?.templateRef">
328
+ <ng-template
329
+ [ngTemplateOutlet]="statusTemplate.templateRef"
330
+ [ngTemplateOutletContext]="{ $implicit: message.status, message }"
331
+ >
332
+ </ng-template>
333
+ </ng-container>
334
+ <ng-container *ngIf="!statusTemplate?.templateRef">
335
+ {{ message.status }}
336
+ </ng-container>
69
337
  </span>
70
338
 
339
+ <kendo-toolbar *ngIf="isToolbarVisible()" class="k-chat-message-toolbar" fillMode="flat">
340
+ <kendo-toolbar-button
341
+ *ngFor="let action of chatService.messageToolbarActions"
342
+ fillMode="flat"
343
+ [icon]="action.icon"
344
+ [svgIcon]="action.svgIcon"
345
+ [disabled]="action.disabled"
346
+ [title]="action.label"
347
+ (click)="onToolbarAction($event, action, message)"
348
+ >
349
+ </kendo-toolbar-button>
350
+ </kendo-toolbar>
351
+
71
352
  <ng-template #typing>
72
- <div class="k-chat-bubble">
353
+ <div class="k-chat-bubble k-bubble">
73
354
  <div class="k-typing-indicator">
74
355
  <span></span>
75
356
  <span></span>
@@ -77,16 +358,31 @@ export class MessageComponent extends ChatItem {
77
358
  </div>
78
359
  </div>
79
360
  </ng-template>
80
- `, isInline: true, dependencies: [{ kind: "directive", type: NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }] });
361
+
362
+ <kendo-contextmenu
363
+ *ngIf="!message.isDeleted"
364
+ [target]="element"
365
+ #messageActionsContextMenu
366
+ [items]="contextMenuActions"
367
+ (popupOpen)="handleMenuOpen()"
368
+ (popupClose)="handleMenuClose($event)"
369
+ (select)="onContextMenuAction($event.item.originalAction)"
370
+ [popupAlign]="{ horizontal: 'right', vertical: 'top' }"
371
+ [collision]="{ horizontal: 'flip', vertical: 'flip'}"
372
+ >
373
+ </kendo-contextmenu>
374
+ `, 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"] }] });
81
375
  }
82
376
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: MessageComponent, decorators: [{
83
377
  type: Component,
84
378
  args: [{
85
379
  selector: 'kendo-chat-message',
86
- providers: [{
380
+ providers: [
381
+ {
87
382
  provide: ChatItem,
88
383
  useExisting: forwardRef(() => MessageComponent)
89
- }],
384
+ }
385
+ ],
90
386
  template: `
91
387
  <time
92
388
  [attr.aria-hidden]="!selected"
@@ -97,15 +393,86 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImpo
97
393
  </time>
98
394
 
99
395
  <ng-container *ngIf="!message.typing; else typing">
100
- <div class="k-chat-bubble" *ngIf="template">
101
- <ng-container
102
- *ngTemplateOutlet="template.templateRef; context: { $implicit: message };"
103
- >
104
- </ng-container>
396
+ <div
397
+ class="k-chat-bubble k-bubble"
398
+ *ngIf="template"
399
+ [attr.tabindex]="0"
400
+ [ngClass]="{
401
+ 'k-selected': selected,
402
+ 'k-focus': selected,
403
+ 'k-active': active
404
+ }"
405
+ >
406
+ <div class="k-bubble-content">
407
+ <ng-container
408
+ *ngTemplateOutlet="template.templateRef; context: { $implicit: message };"
409
+ >
410
+ </ng-container>
411
+ </div>
105
412
  </div>
106
413
 
107
- <div class="k-chat-bubble" *ngIf="!template && message.text">
108
- {{message.text}}
414
+ <div
415
+ class="k-chat-bubble k-bubble"
416
+ *ngIf="!template && (message.text || message.files)"
417
+ [attr.tabindex]="0"
418
+ [ngClass]="{
419
+ 'k-bubble-expandable': chatService.allowMessageCollapse,
420
+ 'k-expanded': isMessageExpanded,
421
+ 'k-selected': selected,
422
+ 'k-focus': selected,
423
+ 'k-active': active
424
+ }"
425
+ >
426
+ <div class="k-bubble-content">
427
+ <span class="k-chat-bubble-text" *ngIf="message.text || message.isDeleted">
428
+ <div class="k-message-reference k-message-reference-receiver" *ngIf="message.replyToId && !message.isDeleted" (click)="onReplyReferenceClick($event, message.replyToId)">
429
+ <chat-message-reference-content [message]="getMessageById(message.replyToId)"></chat-message-reference-content>
430
+ </div>
431
+ <ng-container *ngIf="message.isDeleted && isOwnMessage(message)">{{ textFor('deletedMessageSenderText') }}</ng-container>
432
+ <ng-container *ngIf="message.isDeleted && !isOwnMessage(message)">{{ textFor('deletedMessageReceiverText') }}</ng-container>
433
+ <ng-container *ngIf="!message.isDeleted">
434
+ <ng-container *ngFor="let part of getFormattedTextParts(message.text)">
435
+ <ng-container *ngIf="part.type === 'text'">{{part.content}}</ng-container>
436
+ <a *ngIf="part.type === 'link'" [href]="part.href" target="_blank">{{part.content}}</a>
437
+ </ng-container>
438
+ </ng-container>
439
+ </span>
440
+ <ul class="k-chat-file-wrapper" *ngIf="message.files && message.files.length > 0">
441
+ <li
442
+ *ngFor="let file of message.files"
443
+ class="k-chat-file"
444
+ [chatFile]="file"
445
+ [fileActions]="fileActions"
446
+ (actionClick)="onFileAction($event, file)"
447
+ (actionsToggle)="onActionPopupChange($event)"
448
+ (actionButtonClick)="onActionButtonClick($event)"
449
+ ></li>
450
+ </ul>
451
+ <div class="k-chat-download-button-wrapper" *ngIf="message.files?.length > 1">
452
+ <button
453
+ kendoButton
454
+ class="k-chat-download-button"
455
+ fillMode="flat"
456
+ icon="download"
457
+ [svgIcon]="downloadIcon"
458
+ (click)="onDownloadAll()"
459
+ >{{ textFor('downloadAllFilesText') }}</button>
460
+ </div>
461
+ </div>
462
+ <span
463
+ class="k-bubble-expandable-indicator"
464
+ *ngIf="chatService.allowMessageCollapse"
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>
109
476
  </div>
110
477
  </ng-container>
111
478
 
@@ -113,11 +480,33 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImpo
113
480
  class="k-message-status"
114
481
  *ngIf="message.status"
115
482
  >
116
- {{ message.status }}
483
+ <ng-container *ngIf="statusTemplate?.templateRef">
484
+ <ng-template
485
+ [ngTemplateOutlet]="statusTemplate.templateRef"
486
+ [ngTemplateOutletContext]="{ $implicit: message.status, message }"
487
+ >
488
+ </ng-template>
489
+ </ng-container>
490
+ <ng-container *ngIf="!statusTemplate?.templateRef">
491
+ {{ message.status }}
492
+ </ng-container>
117
493
  </span>
118
494
 
495
+ <kendo-toolbar *ngIf="isToolbarVisible()" class="k-chat-message-toolbar" fillMode="flat">
496
+ <kendo-toolbar-button
497
+ *ngFor="let action of chatService.messageToolbarActions"
498
+ fillMode="flat"
499
+ [icon]="action.icon"
500
+ [svgIcon]="action.svgIcon"
501
+ [disabled]="action.disabled"
502
+ [title]="action.label"
503
+ (click)="onToolbarAction($event, action, message)"
504
+ >
505
+ </kendo-toolbar-button>
506
+ </kendo-toolbar>
507
+
119
508
  <ng-template #typing>
120
- <div class="k-chat-bubble">
509
+ <div class="k-chat-bubble k-bubble">
121
510
  <div class="k-typing-indicator">
122
511
  <span></span>
123
512
  <span></span>
@@ -125,26 +514,50 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImpo
125
514
  </div>
126
515
  </div>
127
516
  </ng-template>
517
+
518
+ <kendo-contextmenu
519
+ *ngIf="!message.isDeleted"
520
+ [target]="element"
521
+ #messageActionsContextMenu
522
+ [items]="contextMenuActions"
523
+ (popupOpen)="handleMenuOpen()"
524
+ (popupClose)="handleMenuClose($event)"
525
+ (select)="onContextMenuAction($event.item.originalAction)"
526
+ [popupAlign]="{ horizontal: 'right', vertical: 'top' }"
527
+ [collision]="{ horizontal: 'flip', vertical: 'flip'}"
528
+ >
529
+ </kendo-contextmenu>
128
530
  `,
129
531
  standalone: true,
130
- imports: [NgIf, NgTemplateOutlet]
532
+ imports: [NgIf, NgClass, NgFor, NgTemplateOutlet, IconWrapperComponent, KENDO_BUTTONS, ChatFileComponent, ContextMenuComponent, ToolBarComponent, ToolBarButtonComponent, MessageReferenceComponent],
131
533
  }]
132
- }], ctorParameters: function () { return [{ type: i0.ElementRef }, { type: i1.IntlService }]; }, propDecorators: { message: [{
534
+ }], ctorParameters: function () { return [{ type: i0.ElementRef }, { type: i1.IntlService }, { type: i2.ChatService }, { type: i3.LocalizationService }]; }, propDecorators: { message: [{
133
535
  type: Input
134
536
  }], tabbable: [{
135
537
  type: Input
136
538
  }], template: [{
137
539
  type: Input
540
+ }], statusTemplate: [{
541
+ type: Input
542
+ }], showMessageTime: [{
543
+ type: Input
544
+ }], authorId: [{
545
+ type: Input
546
+ }], contextMenuVisibilityChange: [{
547
+ type: Output
138
548
  }], cssClass: [{
139
549
  type: HostBinding,
140
550
  args: ['class.k-message']
141
- }], selected: [{
142
- type: HostBinding,
143
- args: ['class.k-selected']
144
- }, {
551
+ }], removedClass: [{
145
552
  type: HostBinding,
146
- args: ['class.k-focus']
553
+ args: ['class.k-message-removed']
554
+ }], onKeyDown: [{
555
+ type: HostListener,
556
+ args: ['keydown', ['$event']]
147
557
  }], tabIndex: [{
148
558
  type: HostBinding,
149
559
  args: ['attr.tabIndex']
560
+ }], onContextMenu: [{
561
+ type: HostListener,
562
+ args: ['contextmenu', ['$event']]
150
563
  }] } });