@chat21/chat21-web-widget 5.1.33-rc9 → 5.1.34-rc1

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 (101) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/package.json +1 -1
  3. package/src/app/component/conversation-detail/conversation/conversation.component.ts +3 -1
  4. package/src/app/component/conversation-detail/conversation-content/conversation-content.component.spec.ts +0 -7
  5. package/src/app/component/conversation-detail/conversation-content/conversation-content.component.ts +7 -5
  6. package/src/app/component/conversation-detail/conversation-footer/conversation-footer.component.html +4 -3
  7. package/src/app/component/conversation-detail/conversation-footer/conversation-footer.component.scss +18 -18
  8. package/src/app/component/conversation-detail/conversation-footer/conversation-footer.component.ts +6 -0
  9. package/src/app/component/conversation-detail/stream-audio-spectrum/stream-audio-spectrum.component.html +8 -5
  10. package/src/app/component/conversation-detail/stream-audio-spectrum/stream-audio-spectrum.component.scss +5 -1
  11. package/src/app/component/form/inputs/form-text/form-text.component.ts +9 -3
  12. package/src/app/component/message/bubble-message/bubble-message.component.scss +5 -0
  13. package/src/app/component/message/bubble-message/bubble-message.component.ts +14 -0
  14. package/src/app/component/message/json-sources/json-sources.component.scss +12 -8
  15. package/src/app/pipe/marked.pipe.ts +51 -41
  16. package/src/app/providers/global-settings.service.ts +31 -0
  17. package/src/app/providers/json-sources-parser.service.ts +25 -32
  18. package/src/app/providers/voice/voice-streaming.service.ts +11 -19
  19. package/src/app/providers/voice/voice-streaming.types.ts +0 -1
  20. package/src/app/providers/voice/voice.service.spec.ts +12 -45
  21. package/src/app/providers/voice/voice.service.ts +215 -45
  22. package/src/app/utils/globals.ts +10 -0
  23. package/src/assets/i18n/en.json +106 -125
  24. package/src/assets/i18n/es.json +1 -0
  25. package/src/assets/i18n/fr.json +1 -0
  26. package/src/assets/i18n/it.json +1 -0
  27. package/src/assets/sounds/keyboard.mp3 +0 -0
  28. package/src/assets/twp/chatbot-panel.html +3 -1
  29. package/src/chat21-core/utils/utils-message.ts +15 -5
  30. package/src/widget-config-template.json +1 -0
  31. package/src/widget-config.json +30 -28
  32. package/.playwright-mcp/console-2026-05-08T15-31-09-000Z.log +0 -17
  33. package/.playwright-mcp/console-2026-05-08T15-32-19-412Z.log +0 -89
  34. package/.playwright-mcp/console-2026-05-08T16-18-48-424Z.log +0 -133
  35. package/.playwright-mcp/console-2026-05-11T12-54-06-869Z.log +0 -13
  36. package/.playwright-mcp/console-2026-05-11T12-54-56-229Z.log +0 -147
  37. package/.playwright-mcp/console-2026-05-11T12-55-47-174Z.log +0 -183
  38. package/.playwright-mcp/console-2026-05-11T15-34-03-590Z.log +0 -210
  39. package/.playwright-mcp/console-2026-05-12T15-07-31-880Z.log +0 -118
  40. package/.playwright-mcp/page-2026-05-08T15-32-19-900Z.yml +0 -851
  41. package/.playwright-mcp/page-2026-05-08T15-32-47-264Z.yml +0 -857
  42. package/.playwright-mcp/page-2026-05-08T15-33-17-089Z.yml +0 -1110
  43. package/.playwright-mcp/page-2026-05-08T15-33-23-486Z.yml +0 -1069
  44. package/.playwright-mcp/page-2026-05-08T15-33-45-390Z.yml +0 -1076
  45. package/.playwright-mcp/page-2026-05-08T15-33-52-666Z.yml +0 -1072
  46. package/.playwright-mcp/page-2026-05-08T15-34-01-338Z.yml +0 -1085
  47. package/.playwright-mcp/page-2026-05-08T15-34-07-227Z.yml +0 -1072
  48. package/.playwright-mcp/page-2026-05-08T15-34-13-875Z.yml +0 -1072
  49. package/.playwright-mcp/page-2026-05-08T15-34-21-885Z.yml +0 -1109
  50. package/.playwright-mcp/page-2026-05-08T15-34-32-755Z.yml +0 -1109
  51. package/.playwright-mcp/page-2026-05-08T15-35-09-607Z.yml +0 -1119
  52. package/.playwright-mcp/page-2026-05-08T15-35-14-242Z.yml +0 -1109
  53. package/.playwright-mcp/page-2026-05-08T16-18-48-671Z.yml +0 -44
  54. package/.playwright-mcp/page-2026-05-08T16-18-52-753Z.png +0 -0
  55. package/.playwright-mcp/page-2026-05-08T16-19-13-919Z.yml +0 -68
  56. package/.playwright-mcp/page-2026-05-08T16-19-17-977Z.png +0 -0
  57. package/.playwright-mcp/page-2026-05-08T16-19-25-733Z.yml +0 -120
  58. package/.playwright-mcp/page-2026-05-08T16-19-29-252Z.png +0 -0
  59. package/.playwright-mcp/page-2026-05-08T16-19-39-269Z.yml +0 -80
  60. package/.playwright-mcp/page-2026-05-08T16-19-43-915Z.png +0 -0
  61. package/.playwright-mcp/page-2026-05-08T16-20-04-407Z.yml +0 -81
  62. package/.playwright-mcp/page-2026-05-08T16-20-08-984Z.png +0 -0
  63. package/.playwright-mcp/page-2026-05-08T16-20-32-397Z.png +0 -0
  64. package/.playwright-mcp/page-2026-05-08T16-20-58-658Z.png +0 -0
  65. package/.playwright-mcp/page-2026-05-08T16-21-12-320Z.yml +0 -86
  66. package/.playwright-mcp/page-2026-05-08T16-21-39-154Z.yml +0 -91
  67. package/.playwright-mcp/page-2026-05-08T16-21-45-420Z.png +0 -0
  68. package/.playwright-mcp/page-2026-05-08T16-22-21-062Z.yml +0 -0
  69. package/.playwright-mcp/page-2026-05-08T16-22-58-232Z.yml +0 -91
  70. package/.playwright-mcp/page-2026-05-08T16-23-36-520Z.yml +0 -0
  71. package/.playwright-mcp/page-2026-05-08T16-23-46-805Z.yml +0 -100
  72. package/.playwright-mcp/page-2026-05-08T16-23-55-169Z.png +0 -0
  73. package/.playwright-mcp/page-2026-05-08T16-24-26-574Z.yml +0 -91
  74. package/.playwright-mcp/page-2026-05-08T16-25-34-414Z.png +0 -0
  75. package/.playwright-mcp/page-2026-05-08T16-25-59-831Z.png +0 -0
  76. package/.playwright-mcp/page-2026-05-08T16-26-21-809Z.yml +0 -91
  77. package/.playwright-mcp/page-2026-05-08T16-26-47-443Z.yml +0 -105
  78. package/.playwright-mcp/page-2026-05-08T16-26-56-136Z.png +0 -0
  79. package/.playwright-mcp/page-2026-05-08T16-27-59-610Z.yml +0 -48
  80. package/.playwright-mcp/page-2026-05-11T12-54-07-180Z.yml +0 -44
  81. package/.playwright-mcp/page-2026-05-11T12-54-56-946Z.yml +0 -4
  82. package/.playwright-mcp/page-2026-05-11T12-55-47-503Z.yml +0 -24
  83. package/.playwright-mcp/page-2026-05-11T12-56-00-766Z.yml +0 -28
  84. package/.playwright-mcp/page-2026-05-11T12-56-06-438Z.yml +0 -90
  85. package/.playwright-mcp/page-2026-05-11T12-57-56-838Z.yml +0 -106
  86. package/.playwright-mcp/page-2026-05-11T12-58-00-124Z.yml +0 -106
  87. package/.playwright-mcp/page-2026-05-11T12-59-08-836Z.yml +0 -61
  88. package/.playwright-mcp/page-2026-05-11T12-59-12-088Z.yml +0 -61
  89. package/.playwright-mcp/page-2026-05-11T12-59-26-215Z.yml +0 -69
  90. package/.playwright-mcp/page-2026-05-11T12-59-29-519Z.yml +0 -69
  91. package/.playwright-mcp/page-2026-05-11T12-59-37-309Z.yml +0 -0
  92. package/.playwright-mcp/page-2026-05-11T12-59-39-968Z.yml +0 -79
  93. package/.playwright-mcp/page-2026-05-11T12-59-45-983Z.yml +0 -78
  94. package/.playwright-mcp/page-2026-05-11T12-59-49-951Z.yml +0 -78
  95. package/.playwright-mcp/page-2026-05-11T15-34-04-515Z.yml +0 -0
  96. package/.playwright-mcp/page-2026-05-12T15-07-32-171Z.yml +0 -44
  97. package/.playwright-mcp/page-2026-05-12T15-08-09-820Z.yml +0 -119
  98. package/docs/TILEDESK_WIDGET_ACCESSIBILITY_STATEMENT_COMPLETE.md +0 -379
  99. package/playwright-report/index.html +0 -90
  100. package/src/app/component/conversation-detail/conversation-footer/conversation-footer.component copy.html +0 -172
  101. package/test-results/.last-run.json +0 -4
package/CHANGELOG.md CHANGED
@@ -6,6 +6,20 @@
6
6
  ### **Copyrigth**:
7
7
  *Tiledesk SRL*
8
8
 
9
+
10
+ # 5.1.34-rc1
11
+ - **added**: onPageChangeVisibilityDesktop:'open' and 'onPageChangeVisibilityMobile: 'open' in chatbot-panel.html file
12
+
13
+ # 5.1.33-rc12
14
+ - **bug-fixed**: if last message is ulr_preview shows previous message buttons
15
+
16
+ # 5.1.33-rc11
17
+ - **changed**: **Stream audio** — updated the streaming/voice-mode footer icon.
18
+ - **added**: **Stream audio** — tooltip on the stream button (i18n key `STREAM_AUDIO`, e.g. “Use voice mode”) via SVG `<title>` and `aria-label`.
19
+
20
+ # 5.1.33-rc10
21
+ - **bug-fixed**: fixed bug with knowledge base json sources without URLs
22
+
9
23
  # 5.1.33-rc9
10
24
  - **changed**: **Conversation footer** — accessibility-focused markup (ARIA roles/labels, live regions, semantic send control), stream-audio layout (wrapper + voice mode: hide attach/emoji while streaming, inline status, stream button + spectrum), optional **Close chat** action when `closeChatInConversation` is enabled; emoji restriction alert uses assertive live region semantics.
11
25
  - **bug-fixed**: **`getConversationDetail` Tiledesk fallback** — when `getMyRequests()` rejects, the conversation is treated as archived (`isConversationArchived = true`) and the handler returns immediately instead of resetting state from an empty fallback payload.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@chat21/chat21-web-widget",
3
3
  "author": "Tiledesk SRL",
4
- "version": "5.1.33-rc9",
4
+ "version": "5.1.34-rc1",
5
5
  "license": "MIT",
6
6
  "homepage": "https://www.tiledesk.com",
7
7
  "repository": {
@@ -264,7 +264,9 @@ export class ConversationComponent implements OnInit, AfterViewInit, OnChanges {
264
264
  'CLOSE',
265
265
  'VOICE_CONNECTING',
266
266
  'VOICE_LISTENING',
267
- 'VOICE_PROCESSING'
267
+ 'VOICE_PROCESSING',
268
+ 'STREAM_AUDIO',
269
+ 'MAX_ATTACHMENT'
268
270
  ];
269
271
 
270
272
  const keysContent = [
@@ -264,11 +264,4 @@ describe('ConversationContentComponent', () => {
264
264
  });
265
265
  });
266
266
 
267
- describe('ngAfterContentChecked', () => {
268
- it('should trigger change detection', () => {
269
- spyOn((component as any).cdref, 'detectChanges');
270
- component.ngAfterContentChecked();
271
- expect((component as any).cdref.detectChanges).toHaveBeenCalled();
272
- });
273
- });
274
267
  });
@@ -1,4 +1,5 @@
1
- import { ChangeDetectorRef, Component, ElementRef, EventEmitter, Input, OnInit, Output, SimpleChanges, ViewChild } from '@angular/core';
1
+ import { ChangeDetectorRef, Component, ElementRef, EventEmitter, Input, OnInit, OnDestroy, Output, SimpleChanges, ViewChild } from '@angular/core';
2
+ import { Subscription } from 'rxjs';
2
3
  import { MAX_WIDTH_IMAGES, MSG_STATUS_RETURN_RECEIPT, MSG_STATUS_SENT, MSG_STATUS_SENT_SERVER } from 'src/app/utils/constants';
3
4
  import { MessageModel } from 'src/chat21-core/models/message';
4
5
  import { LoggerService } from 'src/chat21-core/providers/abstract/logger.service';
@@ -12,7 +13,7 @@ import { isCarousel, isEmojii, isFirstMessage, isFrame, isImage, isInfo, isLastM
12
13
  templateUrl: './conversation-content.component.html',
13
14
  styleUrls: ['./conversation-content.component.scss']
14
15
  })
15
- export class ConversationContentComponent implements OnInit {
16
+ export class ConversationContentComponent implements OnInit, OnDestroy {
16
17
  @ViewChild('scrollMe') public scrollMe: ElementRef;
17
18
 
18
19
  @Input() messages: MessageModel[]
@@ -73,6 +74,7 @@ export class ConversationContentComponent implements OnInit {
73
74
  showUploadProgress: boolean = false;
74
75
  fileType: string;
75
76
  private logger: LoggerService = LoggerInstance.getInstance();
77
+ private uploadSub?: Subscription;
76
78
 
77
79
  constructor(private cdref: ChangeDetectorRef,
78
80
  private elementRef: ElementRef,
@@ -82,8 +84,8 @@ export class ConversationContentComponent implements OnInit {
82
84
  this.listenToUploadFileProgress();
83
85
  }
84
86
 
85
- ngAfterContentChecked() {
86
- this.cdref.detectChanges();
87
+ ngOnDestroy() {
88
+ this.uploadSub?.unsubscribe();
87
89
  }
88
90
 
89
91
  ngOnChanges(changes: SimpleChanges){
@@ -121,7 +123,7 @@ export class ConversationContentComponent implements OnInit {
121
123
 
122
124
  // ENABLE HTML SECTION 'FILE PENDING UPLOAD'
123
125
  listenToUploadFileProgress() {
124
- this.uploadService.BSStateUpload.subscribe((data: any) => {
126
+ this.uploadSub = this.uploadService.BSStateUpload.subscribe((data: any) => {
125
127
  this.logger.debug('[CONV-CONTENT] BSStateUpload', data);
126
128
  // && data.type.startsWith("application")
127
129
  if (data) {
@@ -59,7 +59,9 @@
59
59
  <span class="v-align-center">
60
60
  <svg aria-hidden="true" focusable="false" xmlns="http://www.w3.org/2000/svg" width="24px" height="24" viewBox="0 0 24 24" fill="currentColor">
61
61
  <path d="M9.9,22.7c0,0-.1,0-.2,0-1.9.3-3.7-.2-5.2-1.4-3-2.3-3.6-6.4-1.4-9.5L9.5,2.5c.4-.5,1.1-.6,1.6-.3.5.4.6,1.1.3,1.6l-6.5,9.4c-1.4,2-1,4.8.9,6.3,1,.8,2.2,1.1,3.5.9,1.3-.2,2.4-.9,3.1-1.9l6-8.7c.9-1.2.6-3-.6-3.9-.6-.5-1.4-.6-2.1-.5-.8.1-1.4.5-1.9,1.1l-5.8,8.2c-.3.5-.2,1.1.2,1.5.2.2.5.3.8.2.3,0,.6-.2.7-.4l4.7-6.2c.4-.5,1.1-.6,1.6-.2.5.4.6,1.1.2,1.6l-4.7,6.2c-.5.7-1.4,1.2-2.3,1.3-.9.1-1.8-.2-2.5-.7-1.4-1.1-1.6-3.1-.6-4.6l5.8-8.2c.8-1.1,2-1.9,3.4-2.1,1.4-.2,2.7.1,3.8,1,2.2,1.7,2.7,4.8,1.1,7.1l-6,8.7c-1.1,1.5-2.6,2.5-4.4,2.8h0Z"/>
62
- </svg>
62
+ <title id="altIconTitle">{{ maxAttachmentLabel }}</title>
63
+ </svg>
64
+
63
65
  </span>
64
66
  <input
65
67
  [attr.disabled]="(isFilePendingToUpload || isConversationArchived || hideTextReply)? true : null"
@@ -173,8 +175,7 @@
173
175
  class="chat21-textarea-button chat21-stream-button"
174
176
  [class.active]="isStreamAudioActive || isStreamAudioConnecting || (!textInputTextArea && !hideTextReply)"
175
177
  [class.chat21-stream-button--expanded]="isStreamAudioActive || isStreamAudioConnecting"
176
- (click)="onStreamPressed($event)" [attr.aria-label]="(isStreamAudioActive || isStreamAudioConnecting) ? (translationMap?.get('CLOSE') || 'Chiudi stream') : (translationMap?.get('STREAM_AUDIO') || 'Stream audio')"
177
- [ngStyle]="{ 'background-color': stylesMap?.get('iconColor') || stylesMap?.get('themeColor') }">
178
+ (click)="onStreamPressed($event)" [attr.aria-label]="(isStreamAudioActive || isStreamAudioConnecting) ? (translationMap?.get('CLOSE') || 'Chiudi stream') : (translationMap?.get('STREAM_AUDIO') || 'Stream audio')">
178
179
  <chat-stream-audio-spectrum
179
180
  mode="button"
180
181
  [active]="isStreamAudioActive || isStreamAudioConnecting"
@@ -8,6 +8,15 @@
8
8
  align-items: center;
9
9
  justify-content: space-between;
10
10
  gap: 8px;
11
+
12
+ //if attachment icon OR emoji icon is not in DOM -> increment textarea width
13
+ &:has(:not(#chat21-start-upload-doc), :not(#chat21-emoticon-picker)) .visible-text-area {
14
+ width: 80%;
15
+ }
16
+ //if attachment icon AND emoji icon is not in DOM -> increment textarea width
17
+ &:has(:not(#chat21-start-upload-doc)):has(:not(#chat21-emoticon-picker)) .visible-text-area {
18
+ width: 90%;
19
+ }
11
20
  }
12
21
 
13
22
  .close-chat-container{
@@ -105,28 +114,19 @@
105
114
  box-sizing: border-box;
106
115
  flex-shrink: 0;
107
116
  color: #ffffff;
108
- transition:
109
- min-width 220ms ease,
110
- border-radius 180ms ease,
111
- padding 180ms ease;
112
-
113
- .chat21-stream-button__icon svg {
114
- width: 20px;
115
- height: 20px;
116
- path {
117
- fill: #ffffff;
118
- }
119
- }
117
+ transition: min-width 220ms ease, border-radius 180ms ease, padding 180ms ease;
120
118
 
121
- &.chat21-textarea-button span svg:hover {
122
- background: rgba(255, 255, 255, 0.2) !important;
123
- border-radius: 50%;
119
+ &:hover{
120
+ background: rgba(240, 240, 240, 0.4) !important;
121
+ transition: all 0.45s ease-in-out 0s !important;
122
+ -moz-transition: all 0.45s ease-in-out 0s !important;
123
+ -webkit-transition: all 0.45s ease-in-out 0s !important;
124
124
  }
125
125
 
126
126
  &.chat21-stream-button--expanded {
127
- min-width: 120px;
127
+ min-width: 110px;
128
128
  border-radius: 999px;
129
- padding: 0 14px;
129
+ // padding: 0 14px;
130
130
  justify-content: center;
131
131
 
132
132
  // stop the circle-hover background from looking odd on pill
@@ -427,7 +427,7 @@ textarea:active{
427
427
  flex-direction: column;
428
428
  justify-content: center;
429
429
  position: absolute;
430
- padding: 8px 16px;
430
+ // padding: 8px 16px;
431
431
  overflow: hidden;
432
432
  background-color: color-mix(in srgb, var(--content-background-color) 34%, transparent);
433
433
  backdrop-filter: blur(20px) saturate(1.2);
@@ -124,6 +124,12 @@ export class ConversationFooterComponent implements OnInit, OnChanges, OnDestroy
124
124
  return this.translationMap?.get('VOICE_LISTENING') || 'Listening...';
125
125
  }
126
126
 
127
+ get maxAttachmentLabel(): string {
128
+ const template = this.translationMap?.get('MAX_ATTACHMENT')
129
+ || `Max allowed size {{FILE_SIZE_LIMIT}}Mb`;
130
+ return template.replace(/\{\{FILE_SIZE_LIMIT\}\}/g, String(this.file_size_limit));
131
+ }
132
+
127
133
  file_size_limit = FILE_SIZE_LIMIT;
128
134
  attachmentTooltip: string = '';
129
135
  isErrorNetwork: boolean = false;
@@ -22,11 +22,14 @@
22
22
  <!-- BUTTON: inactive icon / expanded pill content -->
23
23
  <ng-container *ngSwitchCase="'button'">
24
24
  <span class="stream-audio-button__icon" *ngIf="!active" aria-hidden="true">
25
- <svg role="img" xmlns="http://www.w3.org/2000/svg" version="1.1" width="24" height="24" viewBox="0 0 30 30" fill="currentColor" preserveAspectRatio="xMidYMid meet">
26
- <path class="s0" d="m5.21 7.41c-1.21 0-2.21 0.99-2.21 2.21v8.14c0 1.21 0.99 2.21 2.21 2.21 1.22 0 2.21-0.99 2.21-2.21v-8.14c0-1.21-0.99-2.21-2.21-2.21z"/>
27
- <path class="s0" d="m11.64 3.01c-1.22 0-2.21 0.99-2.21 2.2v16.94c0 1.21 0.99 2.2 2.21 2.2 1.22 0 2.21-0.98 2.21-2.2v-16.94c0-1.21-0.99-2.21-2.21-2.21z"/>
28
- <path class="s0" d="m15.86 9.25v8.88c0 1.21 0.99 2.21 2.21 2.21 1.22 0 2.21-0.99 2.21-2.21v-8.88c0-1.22-0.99-2.21-2.21-2.21-1.22 0-2.21 0.99-2.21 2.21z"/>
29
- <path class="s0" d="m24.5 8.97c-1.22 0-2.21 0.99-2.21 2.21v5.02c0 1.22 0.99 2.21 2.21 2.21 1.22 0 2.21-0.99 2.21-2.21v-5.02c0-1.21-0.99-2.21-2.21-2.21z"/>
25
+ <svg role="img" xmlns="http://www.w3.org/2000/svg" width="26" height="26" viewBox="0 0 23 23" aria-labelledby="altIconTitle">
26
+ <rect x="0" y="7.5" height="6px" fill-opacity="1" width="2px" rx="0.75" ry="0.75"></rect>
27
+ <rect x="4" y="5.5" height="10px" fill-opacity="1" width="2px" rx="0.75" ry="0.75"></rect>
28
+ <rect x="8" y="2.5" height="16px" fill-opacity="1" width="2px" rx="0.75" ry="0.75"></rect>
29
+ <rect x="12" y="5.5" height="10px" fill-opacity="1" width="2px" rx="0.75" ry="0.75"></rect>
30
+ <rect x="16" y="2.5" height="16px" fill-opacity="1" width="2px" rx="0.75" ry="0.75"></rect>
31
+ <rect x="20" y="7.5" height="6px" fill-opacity="1" width="2px" rx="0.75" ry="0.75"></rect>
32
+ <title id="altIconTitle">{{ translationMap.get('STREAM_AUDIO') }}</title>
30
33
  </svg>
31
34
  </span>
32
35
 
@@ -39,6 +39,9 @@
39
39
  width: 20px;
40
40
  height: 20px;
41
41
  display: block;
42
+ rect {
43
+ fill: var(--icon-fill-color);
44
+ }
42
45
  }
43
46
 
44
47
  .stream-audio-button__expanded {
@@ -48,6 +51,7 @@
48
51
  gap: 12px;
49
52
  width: 100%;
50
53
  user-select: none;
54
+ color: var(--icon-fill-color);
51
55
  }
52
56
 
53
57
  .stream-audio-button__label {
@@ -74,6 +78,6 @@
74
78
  width: 3px;
75
79
  height: 100%;
76
80
  border-radius: 2px;
77
- background: rgba(255, 255, 255, 0.92);
81
+ background: var(--icon-fill-color);
78
82
  transform-origin: center;
79
83
  }
@@ -1,5 +1,6 @@
1
- import { Component, ElementRef, EventEmitter, Input, OnInit, Output, SimpleChange, ViewChild } from '@angular/core';
1
+ import { Component, ElementRef, EventEmitter, Input, OnInit, OnDestroy, Output, SimpleChange, ViewChild } from '@angular/core';
2
2
  import { FormGroup, FormGroupDirective } from '@angular/forms';
3
+ import { Subscription } from 'rxjs';
3
4
  import { FormArray } from '../../../../../chat21-core/models/formArray';
4
5
 
5
6
  @Component({
@@ -7,7 +8,7 @@ import { FormArray } from '../../../../../chat21-core/models/formArray';
7
8
  templateUrl: './form-text.component.html',
8
9
  styleUrls: ['./form-text.component.scss']
9
10
  })
10
- export class FormTextComponent implements OnInit {
11
+ export class FormTextComponent implements OnInit, OnDestroy {
11
12
 
12
13
  @Input() element: FormArray;
13
14
  @Input() controlName: string;
@@ -19,6 +20,7 @@ export class FormTextComponent implements OnInit {
19
20
  @ViewChild('div_input') input: ElementRef;
20
21
  form: FormGroup<any>;
21
22
  inputType: string = 'text'
23
+ private valueChangesSub?: Subscription;
22
24
 
23
25
  get fieldBaseId(): string {
24
26
  const raw = this.element?.name || this.controlName || 'field';
@@ -51,13 +53,17 @@ export class FormTextComponent implements OnInit {
51
53
  ngOnInit() {
52
54
  this.form = this.rootFormGroup.control as FormGroup<any>;
53
55
  if(this.form && this.form.controls && this.form.controls[this.controlName]){
54
- this.form.controls[this.controlName].valueChanges.subscribe((value) => {
56
+ this.valueChangesSub = this.form.controls[this.controlName].valueChanges.subscribe((value) => {
55
57
  this.hasSubmitted= false;
56
58
  this.setFormStyle();
57
59
  })
58
60
  }
59
61
  }
60
62
 
63
+ ngOnDestroy() {
64
+ this.valueChangesSub?.unsubscribe();
65
+ }
66
+
61
67
  ngOnChanges(changes: SimpleChange){
62
68
  if(this.controlName && (this.controlName.toLowerCase().includes('email') || this.controlName.toLowerCase().includes('e-mail')) ){
63
69
  this.inputType = 'email';
@@ -1,5 +1,10 @@
1
1
  /* ====== SET MESSAGES ====== */
2
2
 
3
+ :host(.hidden-bubble) {
4
+ display: none !important;
5
+ }
6
+
7
+
3
8
  .messages {
4
9
  border-radius: var(--border-radius-bubble-message);
5
10
  padding: 0;
@@ -40,6 +40,18 @@ export class BubbleMessageComponent implements OnInit, OnDestroy {
40
40
 
41
41
  @HostBinding('class.no-background') get hostNoBackground() { return this.jsonSources !== null && this.jsonSources.length > 0; }
42
42
  @HostBinding('class.json-resources') get hostIsJsonResources() { return this.jsonSources !== null && this.jsonSources.length > 0; }
43
+ @HostBinding('class.hidden-bubble') get hostHiddenBubble() { return !this.hasRenderableContent(); }
44
+
45
+ hasRenderableContent(): boolean {
46
+ const msg = this.message;
47
+ if (!msg) return false;
48
+ if (isImage(msg) || isFile(msg) || isFrame(msg) || isAudio(msg)) return true;
49
+ if (this.jsonSources && this.jsonSources.length > 0) return true;
50
+ // For url_preview messages, `text` may carry the raw JSON payload (not display text):
51
+ // if sources parsing yielded nothing, the bubble must stay hidden.
52
+ if (this.isUrlPreviewMessage) return false;
53
+ return !!(msg.text && String(msg.text).trim().length > 0);
54
+ }
43
55
 
44
56
  readonly isImage = isImage;
45
57
  readonly isFile = isFile;
@@ -55,6 +67,7 @@ export class BubbleMessageComponent implements OnInit, OnDestroy {
55
67
  sizeImage: { width: number; height: number } = { width: 0, height: 0 };
56
68
  fullnameColor: string = '';
57
69
  jsonSources: JsonSourceItem[] | null = null;
70
+ isUrlPreviewMessage = false;
58
71
 
59
72
  private urlPreviewReqId = 0;
60
73
 
@@ -139,6 +152,7 @@ export class BubbleMessageComponent implements OnInit, OnDestroy {
139
152
  this.message?.type === TYPE_MSG_URL_PREVIEW
140
153
  || this.message?.metadata?.type === TYPE_MSG_URL_PREVIEW
141
154
  || this.message?.attributes?.type === TYPE_MSG_URL_PREVIEW;
155
+ this.isUrlPreviewMessage = !!urlPreviewLike;
142
156
  if (urlPreviewLike) this.loadJsonSourcesFromUrlPreviewMessage();
143
157
  }
144
158
 
@@ -1,5 +1,5 @@
1
1
  :host {
2
- --panel-bck: #f7f8fa;
2
+ --panel-bck: #ffffff; // #f7f8fa;
3
3
  --row-sep: rgba(66, 133, 244, 0.18);
4
4
  --text: rgba(0, 0, 0, 0.90);
5
5
  --muted: rgba(0, 0, 0, 0.62);
@@ -22,7 +22,7 @@
22
22
  }
23
23
 
24
24
  .sources-header {
25
- display: flex;
25
+ display: none;
26
26
  align-items: center;
27
27
  gap: 8px;
28
28
  padding: 2px 4px 8px 4px;
@@ -56,7 +56,7 @@
56
56
  .sources-panel {
57
57
  display: flex;
58
58
  flex-direction: column;
59
- padding: 12px 12px 10px 12px;
59
+ padding: 18px 12px 10px 12px;
60
60
  background: var(--panel-bck);
61
61
  border-radius: 18px;
62
62
  font-size: 14px;
@@ -79,6 +79,10 @@
79
79
  width: 100%;
80
80
  box-sizing: border-box;
81
81
 
82
+ border-bottom: 1px solid var(--row-sep);
83
+ border-bottom-left-radius: 0;
84
+ border-bottom-right-radius: 0;
85
+
82
86
  &:hover {
83
87
  background: rgba(255, 255, 255, 0.55);
84
88
  .source-row__title {
@@ -86,11 +90,11 @@
86
90
  }
87
91
  }
88
92
 
89
- & + & {
90
- border-top: 1px solid var(--row-sep);
91
- border-top-left-radius: 0;
92
- border-top-right-radius: 0;
93
- }
93
+ // & + & {
94
+ // border-top: 1px solid var(--row-sep);
95
+ // border-top-left-radius: 0;
96
+ // border-top-right-radius: 0;
97
+ // }
94
98
 
95
99
  .source-row__left {
96
100
  min-width: 0;
@@ -5,61 +5,71 @@ import { marked, Tokens } from 'marked';
5
5
  name: 'marked'
6
6
  })
7
7
  export class MarkedPipe implements PipeTransform {
8
+ private static renderer: any = null;
8
9
 
9
- transform(value: any): string {
10
+ private static getRenderer(): any {
11
+ if (!MarkedPipe.renderer) {
12
+ const renderer = new marked.Renderer();
10
13
 
11
- const input =
12
- typeof value === 'string'
13
- ? value
14
- : (value === null || value === undefined) ? '' : String(value);
14
+ /* --------------------------------------------------
15
+ 🔐 1. NON renderizzare HTML raw
16
+ -------------------------------------------------- */
17
+ renderer.html = function(token: Tokens.HTML | Tokens.Tag): string {
18
+ const html = 'text' in token ? token.text : '';
15
19
 
16
- const inputWithNewlines = input.replace(/\\n/g, '\n');
20
+ return html
21
+ .replace(/&/g, '&amp;')
22
+ .replace(/</g, '&lt;')
23
+ .replace(/>/g, '&gt;');
24
+ };
25
+
26
+ /* --------------------------------------------------
27
+ 🔐 2. Link sicuri
28
+ -------------------------------------------------- */
29
+ const originalLinkRenderer = renderer.link.bind(renderer);
30
+
31
+ const dangerousProtocols = [
32
+ /^javascript:/i,
33
+ /^data:/i,
34
+ /^vbscript:/i
35
+ ];
17
36
 
18
- const renderer = new marked.Renderer();
37
+ renderer.link = function({ href, title, tokens }: any) {
19
38
 
20
- /* --------------------------------------------------
21
- 🔐 1. NON renderizzare HTML raw
22
- -------------------------------------------------- */
23
- renderer.html = function(token: Tokens.HTML | Tokens.Tag): string {
24
- const html = 'text' in token ? token.text : '';
39
+ const normalized = (href || '').trim();
25
40
 
26
- return html
27
- .replace(/&/g, '&amp;')
28
- .replace(/</g, '&lt;')
29
- .replace(/>/g, '&gt;');
30
- };
41
+ const isDangerous = dangerousProtocols.some(pattern =>
42
+ pattern.test(normalized)
43
+ );
31
44
 
32
- /* --------------------------------------------------
33
- 🔐 2. Link sicuri
34
- -------------------------------------------------- */
35
- const originalLinkRenderer = renderer.link.bind(renderer);
45
+ if (isDangerous) {
46
+ return tokens ? tokens.map((t: any) => t.raw).join('') : href || '';
47
+ }
36
48
 
37
- const dangerousProtocols = [
38
- /^javascript:/i,
39
- /^data:/i,
40
- /^vbscript:/i
41
- ];
49
+ const html = originalLinkRenderer({ href, title, tokens });
42
50
 
43
- renderer.link = function({ href, title, tokens }) {
51
+ // aggiunge sicurezza ai link
52
+ return html.replace(
53
+ '<a ',
54
+ '<a target="_blank" rel="noopener noreferrer" '
55
+ );
56
+ };
44
57
 
45
- const normalized = (href || '').trim();
58
+ MarkedPipe.renderer = renderer;
59
+ }
60
+ return MarkedPipe.renderer;
61
+ }
46
62
 
47
- const isDangerous = dangerousProtocols.some(pattern =>
48
- pattern.test(normalized)
49
- );
63
+ transform(value: any): string {
50
64
 
51
- if (isDangerous) {
52
- return tokens ? tokens.map(t => t.raw).join('') : href || '';
53
- }
65
+ const input =
66
+ typeof value === 'string'
67
+ ? value
68
+ : (value === null || value === undefined) ? '' : String(value);
54
69
 
55
- const html = originalLinkRenderer({ href, title, tokens });
70
+ const inputWithNewlines = input.replace(/\\n/g, '\n');
56
71
 
57
- // aggiunge sicurezza ai link
58
- return html.replace(
59
- '<a ',
60
- '<a target="_blank" rel="noopener noreferrer" '
61
- );
62
- };
72
+ const renderer = MarkedPipe.getRenderer();
63
73
 
64
74
  marked.setOptions({
65
75
  renderer,
@@ -66,6 +66,8 @@ export class GlobalSettingsService {
66
66
  this.globals.logLevel = this.appConfigService.getConfig().logLevel
67
67
  /**SET PERSISTENCE parameter */
68
68
  this.globals.persistence = this.appConfigService.getConfig().authPersistence
69
+ /**SET CLOSE CHAT IN CONVERSATION parameter */
70
+ this.globals.closeChatInConversation = this.appConfigService.getConfig().closeChatInConversation;
69
71
 
70
72
  // ------------------------------- //
71
73
  /** LOAD PARAMETERS FROM SERVER
@@ -596,6 +598,9 @@ export class GlobalSettingsService {
596
598
  if (variables.hasOwnProperty('allowedUploadExtentions')) {
597
599
  globals['fileUploadAccept'] = variables['allowedUploadExtentions'];
598
600
  }
601
+ if(variables.hasOwnProperty('showAudioStreamFooterButton')) {
602
+ globals['showAudioStreamFooterButton'] = variables['showAudioStreamFooterButton'];
603
+ }
599
604
 
600
605
  }
601
606
  }
@@ -947,6 +952,14 @@ export class GlobalSettingsService {
947
952
  if (TEMP !== undefined) {
948
953
  globals.soundEnabled = TEMP;
949
954
  }
955
+ TEMP = tiledeskSettings['keyboardSoundVolume'];
956
+ if (TEMP !== undefined) {
957
+ globals.keyboardSoundVolume = +TEMP;
958
+ }
959
+ TEMP = tiledeskSettings['keyboardSoundFile'];
960
+ if (TEMP !== undefined) {
961
+ globals.keyboardSoundFile = TEMP;
962
+ }
950
963
  TEMP = tiledeskSettings['openExternalLinkButton'];
951
964
  // this.logger.debug('[GLOBAL-SET] setVariablesFromSettings > openExternalLinkButton:: ', TEMP]);
952
965
  if (TEMP !== undefined) {
@@ -1336,6 +1349,14 @@ export class GlobalSettingsService {
1336
1349
  if (TEMP !== null) {
1337
1350
  this.globals.soundEnabled = TEMP;
1338
1351
  }
1352
+ TEMP = el.nativeElement.getAttribute('keyboardSoundVolume');
1353
+ if (TEMP !== null) {
1354
+ this.globals.keyboardSoundVolume = +TEMP;
1355
+ }
1356
+ TEMP = el.nativeElement.getAttribute('keyboardSoundFile');
1357
+ if (TEMP !== null) {
1358
+ this.globals.keyboardSoundFile = TEMP;
1359
+ }
1339
1360
  TEMP = el.nativeElement.getAttribute('openExternalLinkButton');
1340
1361
  if (TEMP !== null) {
1341
1362
  this.globals.openExternalLinkButton = TEMP;
@@ -1735,6 +1756,16 @@ export class GlobalSettingsService {
1735
1756
  globals.soundEnabled = stringToBoolean(TEMP);
1736
1757
  }
1737
1758
 
1759
+ TEMP = getParameterByName(windowContext, 'tiledesk_keyboardSoundVolume');
1760
+ if (TEMP) {
1761
+ globals.keyboardSoundVolume = +TEMP;
1762
+ }
1763
+
1764
+ TEMP = getParameterByName(windowContext, 'tiledesk_keyboardSoundFile');
1765
+ if (TEMP) {
1766
+ globals.keyboardSoundFile = TEMP;
1767
+ }
1768
+
1738
1769
  TEMP = getParameterByName(windowContext, 'tiledesk_openExternalLinkButton');
1739
1770
  if (TEMP) {
1740
1771
  globals.openExternalLinkButton = stringToBoolean(TEMP);