@chat21/chat21-web-widget 5.1.32-rc8 → 5.1.33-rc8

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 (246) hide show
  1. package/.angular-mcp-cache/package.json +1 -0
  2. package/.cursor/angular18-accessibility-auditor-skill.md +442 -0
  3. package/.cursor/mcp.json +15 -0
  4. package/.github/workflows/playwright.yml +27 -0
  5. package/.playwright-mcp/console-2026-05-08T15-31-09-000Z.log +17 -0
  6. package/.playwright-mcp/console-2026-05-08T15-32-19-412Z.log +89 -0
  7. package/.playwright-mcp/console-2026-05-08T16-18-48-424Z.log +133 -0
  8. package/.playwright-mcp/console-2026-05-11T12-54-06-869Z.log +13 -0
  9. package/.playwright-mcp/console-2026-05-11T12-54-56-229Z.log +147 -0
  10. package/.playwright-mcp/console-2026-05-11T12-55-47-174Z.log +183 -0
  11. package/.playwright-mcp/console-2026-05-11T15-34-03-590Z.log +210 -0
  12. package/.playwright-mcp/console-2026-05-12T15-07-31-880Z.log +118 -0
  13. package/.playwright-mcp/page-2026-05-08T15-32-19-900Z.yml +851 -0
  14. package/.playwright-mcp/page-2026-05-08T15-32-47-264Z.yml +857 -0
  15. package/.playwright-mcp/page-2026-05-08T15-33-17-089Z.yml +1110 -0
  16. package/.playwright-mcp/page-2026-05-08T15-33-23-486Z.yml +1069 -0
  17. package/.playwright-mcp/page-2026-05-08T15-33-45-390Z.yml +1076 -0
  18. package/.playwright-mcp/page-2026-05-08T15-33-52-666Z.yml +1072 -0
  19. package/.playwright-mcp/page-2026-05-08T15-34-01-338Z.yml +1085 -0
  20. package/.playwright-mcp/page-2026-05-08T15-34-07-227Z.yml +1072 -0
  21. package/.playwright-mcp/page-2026-05-08T15-34-13-875Z.yml +1072 -0
  22. package/.playwright-mcp/page-2026-05-08T15-34-21-885Z.yml +1109 -0
  23. package/.playwright-mcp/page-2026-05-08T15-34-32-755Z.yml +1109 -0
  24. package/.playwright-mcp/page-2026-05-08T15-35-09-607Z.yml +1119 -0
  25. package/.playwright-mcp/page-2026-05-08T15-35-14-242Z.yml +1109 -0
  26. package/.playwright-mcp/page-2026-05-08T16-18-48-671Z.yml +44 -0
  27. package/.playwright-mcp/page-2026-05-08T16-18-52-753Z.png +0 -0
  28. package/.playwright-mcp/page-2026-05-08T16-19-13-919Z.yml +68 -0
  29. package/.playwright-mcp/page-2026-05-08T16-19-17-977Z.png +0 -0
  30. package/.playwright-mcp/page-2026-05-08T16-19-25-733Z.yml +120 -0
  31. package/.playwright-mcp/page-2026-05-08T16-19-29-252Z.png +0 -0
  32. package/.playwright-mcp/page-2026-05-08T16-19-39-269Z.yml +80 -0
  33. package/.playwright-mcp/page-2026-05-08T16-19-43-915Z.png +0 -0
  34. package/.playwright-mcp/page-2026-05-08T16-20-04-407Z.yml +81 -0
  35. package/.playwright-mcp/page-2026-05-08T16-20-08-984Z.png +0 -0
  36. package/.playwright-mcp/page-2026-05-08T16-20-32-397Z.png +0 -0
  37. package/.playwright-mcp/page-2026-05-08T16-20-58-658Z.png +0 -0
  38. package/.playwright-mcp/page-2026-05-08T16-21-12-320Z.yml +86 -0
  39. package/.playwright-mcp/page-2026-05-08T16-21-39-154Z.yml +91 -0
  40. package/.playwright-mcp/page-2026-05-08T16-21-45-420Z.png +0 -0
  41. package/.playwright-mcp/page-2026-05-08T16-22-21-062Z.yml +0 -0
  42. package/.playwright-mcp/page-2026-05-08T16-22-58-232Z.yml +91 -0
  43. package/.playwright-mcp/page-2026-05-08T16-23-36-520Z.yml +0 -0
  44. package/.playwright-mcp/page-2026-05-08T16-23-46-805Z.yml +100 -0
  45. package/.playwright-mcp/page-2026-05-08T16-23-55-169Z.png +0 -0
  46. package/.playwright-mcp/page-2026-05-08T16-24-26-574Z.yml +91 -0
  47. package/.playwright-mcp/page-2026-05-08T16-25-34-414Z.png +0 -0
  48. package/.playwright-mcp/page-2026-05-08T16-25-59-831Z.png +0 -0
  49. package/.playwright-mcp/page-2026-05-08T16-26-21-809Z.yml +91 -0
  50. package/.playwright-mcp/page-2026-05-08T16-26-47-443Z.yml +105 -0
  51. package/.playwright-mcp/page-2026-05-08T16-26-56-136Z.png +0 -0
  52. package/.playwright-mcp/page-2026-05-08T16-27-59-610Z.yml +48 -0
  53. package/.playwright-mcp/page-2026-05-11T12-54-07-180Z.yml +44 -0
  54. package/.playwright-mcp/page-2026-05-11T12-54-56-946Z.yml +4 -0
  55. package/.playwright-mcp/page-2026-05-11T12-55-47-503Z.yml +24 -0
  56. package/.playwright-mcp/page-2026-05-11T12-56-00-766Z.yml +28 -0
  57. package/.playwright-mcp/page-2026-05-11T12-56-06-438Z.yml +90 -0
  58. package/.playwright-mcp/page-2026-05-11T12-57-56-838Z.yml +106 -0
  59. package/.playwright-mcp/page-2026-05-11T12-58-00-124Z.yml +106 -0
  60. package/.playwright-mcp/page-2026-05-11T12-59-08-836Z.yml +61 -0
  61. package/.playwright-mcp/page-2026-05-11T12-59-12-088Z.yml +61 -0
  62. package/.playwright-mcp/page-2026-05-11T12-59-26-215Z.yml +69 -0
  63. package/.playwright-mcp/page-2026-05-11T12-59-29-519Z.yml +69 -0
  64. package/.playwright-mcp/page-2026-05-11T12-59-37-309Z.yml +0 -0
  65. package/.playwright-mcp/page-2026-05-11T12-59-39-968Z.yml +79 -0
  66. package/.playwright-mcp/page-2026-05-11T12-59-45-983Z.yml +78 -0
  67. package/.playwright-mcp/page-2026-05-11T12-59-49-951Z.yml +78 -0
  68. package/.playwright-mcp/page-2026-05-11T15-34-04-515Z.yml +0 -0
  69. package/.playwright-mcp/page-2026-05-12T15-07-32-171Z.yml +44 -0
  70. package/.playwright-mcp/page-2026-05-12T15-08-09-820Z.yml +119 -0
  71. package/CHANGELOG.md +61 -3
  72. package/angular.json +20 -3
  73. package/deploy_amazon_beta.sh +7 -17
  74. package/deploy_amazon_prod.sh +41 -0
  75. package/docs/TILEDESK_WIDGET_ACCESSIBILITY_STATEMENT_COMPLETE.md +379 -0
  76. package/env.sample +3 -2
  77. package/mocks/voice-websocket-mock/server.cjs +245 -0
  78. package/package.json +7 -3
  79. package/playwright-report/index.html +90 -0
  80. package/playwright.config.ts +41 -0
  81. package/src/app/app.component.html +2 -2
  82. package/src/app/app.component.scss +25 -14
  83. package/src/app/app.component.spec.ts +21 -6
  84. package/src/app/app.module.ts +4 -0
  85. package/src/app/component/conversation-detail/conversation/conversation.component.html +19 -11
  86. package/src/app/component/conversation-detail/conversation/conversation.component.scss +28 -0
  87. package/src/app/component/conversation-detail/conversation/conversation.component.spec.ts +644 -75
  88. package/src/app/component/conversation-detail/conversation/conversation.component.ts +61 -17
  89. package/src/app/component/conversation-detail/conversation-audio-recorder/conversation-audio-recorder.component.html +25 -13
  90. package/src/app/component/conversation-detail/conversation-audio-recorder/conversation-audio-recorder.component.spec.ts +123 -5
  91. package/src/app/component/conversation-detail/conversation-audio-recorder/conversation-audio-recorder.component.ts +1 -0
  92. package/src/app/component/conversation-detail/conversation-content/conversation-content.component.html +22 -9
  93. package/src/app/component/conversation-detail/conversation-content/conversation-content.component.scss +23 -1
  94. package/src/app/component/conversation-detail/conversation-content/conversation-content.component.spec.ts +249 -149
  95. package/src/app/component/conversation-detail/conversation-content/conversation-content.component.ts +0 -1
  96. package/src/app/component/conversation-detail/conversation-emojii/conversation-emojii.component.spec.ts +53 -3
  97. package/src/app/component/conversation-detail/conversation-footer/conversation-footer.component copy.html +172 -0
  98. package/src/app/component/conversation-detail/conversation-footer/conversation-footer.component.html +112 -62
  99. package/src/app/component/conversation-detail/conversation-footer/conversation-footer.component.scss +133 -7
  100. package/src/app/component/conversation-detail/conversation-footer/conversation-footer.component.spec.ts +452 -78
  101. package/src/app/component/conversation-detail/conversation-footer/conversation-footer.component.ts +193 -79
  102. package/src/app/component/conversation-detail/conversation-header/conversation-header.component.html +113 -53
  103. package/src/app/component/conversation-detail/conversation-header/conversation-header.component.scss +12 -4
  104. package/src/app/component/conversation-detail/conversation-header/conversation-header.component.spec.ts +274 -29
  105. package/src/app/component/conversation-detail/conversation-internal-frame/conversation-internal-frame.component.html +23 -9
  106. package/src/app/component/conversation-detail/conversation-internal-frame/conversation-internal-frame.component.spec.ts +80 -8
  107. package/src/app/component/conversation-detail/conversation-preview/conversation-preview.component.html +29 -23
  108. package/src/app/component/conversation-detail/conversation-preview/conversation-preview.component.spec.ts +185 -16
  109. package/src/app/component/conversation-detail/conversation-preview/conversation-preview.component.ts +34 -14
  110. package/src/app/component/conversation-detail/stream-audio-spectrum/stream-audio-spectrum.component.html +43 -19
  111. package/src/app/component/conversation-detail/stream-audio-spectrum/stream-audio-spectrum.component.scss +63 -10
  112. package/src/app/component/conversation-detail/stream-audio-spectrum/stream-audio-spectrum.component.ts +142 -12
  113. package/src/app/component/error-alert/error-alert.component.spec.ts +65 -5
  114. package/src/app/component/eyeeye-catcher-card/eyeeye-catcher-card.component.html +16 -7
  115. package/src/app/component/eyeeye-catcher-card/eyeeye-catcher-card.component.scss +21 -0
  116. package/src/app/component/eyeeye-catcher-card/eyeeye-catcher-card.component.spec.ts +89 -7
  117. package/src/app/component/form/form-builder/form-builder.component.html +1 -1
  118. package/src/app/component/form/form-builder/form-builder.component.spec.ts +163 -21
  119. package/src/app/component/form/inputs/form-checkbox/form-checkbox.component.html +8 -4
  120. package/src/app/component/form/inputs/form-checkbox/form-checkbox.component.scss +10 -5
  121. package/src/app/component/form/inputs/form-checkbox/form-checkbox.component.spec.ts +90 -16
  122. package/src/app/component/form/inputs/form-checkbox/form-checkbox.component.ts +26 -0
  123. package/src/app/component/form/inputs/form-label/form-label.component.spec.ts +45 -11
  124. package/src/app/component/form/inputs/form-radio-button/form-radio-button.component.spec.ts +24 -6
  125. package/src/app/component/form/inputs/form-select/form-select.component.spec.ts +14 -5
  126. package/src/app/component/form/inputs/form-text/form-text.component.html +14 -12
  127. package/src/app/component/form/inputs/form-text/form-text.component.scss +11 -1
  128. package/src/app/component/form/inputs/form-text/form-text.component.spec.ts +113 -17
  129. package/src/app/component/form/inputs/form-text/form-text.component.ts +26 -0
  130. package/src/app/component/form/inputs/form-textarea/form-textarea.component.html +13 -11
  131. package/src/app/component/form/inputs/form-textarea/form-textarea.component.scss +6 -5
  132. package/src/app/component/form/inputs/form-textarea/form-textarea.component.spec.ts +149 -13
  133. package/src/app/component/form/inputs/form-textarea/form-textarea.component.ts +26 -0
  134. package/src/app/component/form/prechat-form/prechat-form.component.html +14 -11
  135. package/src/app/component/form/prechat-form/prechat-form.component.spec.ts +102 -10
  136. package/src/app/component/form/prechat-form/prechat-form.component.ts +8 -1
  137. package/src/app/component/form/prechat-form-test-mock.ts +35 -0
  138. package/src/app/component/home/home.component.html +38 -31
  139. package/src/app/component/home/home.component.scss +4 -2
  140. package/src/app/component/home/home.component.spec.ts +226 -11
  141. package/src/app/component/home-conversations/home-conversations.component.html +30 -26
  142. package/src/app/component/home-conversations/home-conversations.component.scss +3 -0
  143. package/src/app/component/home-conversations/home-conversations.component.spec.ts +212 -36
  144. package/src/app/component/last-message/last-message.component.html +15 -9
  145. package/src/app/component/last-message/last-message.component.scss +16 -2
  146. package/src/app/component/last-message/last-message.component.spec.ts +204 -23
  147. package/src/app/component/launcher-button/launcher-button.component.html +8 -13
  148. package/src/app/component/launcher-button/launcher-button.component.spec.ts +104 -8
  149. package/src/app/component/list-all-conversations/list-all-conversations.component.html +12 -17
  150. package/src/app/component/list-all-conversations/list-all-conversations.component.scss +2 -0
  151. package/src/app/component/list-conversations/list-conversations.component.html +22 -22
  152. package/src/app/component/menu-options/menu-options.component.html +30 -20
  153. package/src/app/component/menu-options/menu-options.component.spec.ts +125 -9
  154. package/src/app/component/message/audio/audio.component.html +13 -15
  155. package/src/app/component/message/audio/audio.component.spec.ts +140 -5
  156. package/src/app/component/message/audio/audio.component.ts +1 -0
  157. package/src/app/component/message/audio-sync/audio-sync.component.scss +1 -1
  158. package/src/app/component/message/audio-sync/audio-sync.component.spec.ts +81 -1
  159. package/src/app/component/message/audio-sync/audio-sync.component.ts +134 -24
  160. package/src/app/component/message/avatar/avatar.component.html +2 -2
  161. package/src/app/component/message/avatar/avatar.component.spec.ts +99 -7
  162. package/src/app/component/message/bubble-message/bubble-message.component.html +39 -52
  163. package/src/app/component/message/bubble-message/bubble-message.component.scss +54 -1
  164. package/src/app/component/message/bubble-message/bubble-message.component.spec.ts +154 -57
  165. package/src/app/component/message/bubble-message/bubble-message.component.ts +138 -110
  166. package/src/app/component/message/buttons/action-button/action-button.component.html +3 -4
  167. package/src/app/component/message/buttons/action-button/action-button.component.spec.ts +49 -5
  168. package/src/app/component/message/buttons/link-button/link-button.component.scss +5 -8
  169. package/src/app/component/message/buttons/link-button/link-button.component.spec.ts +50 -5
  170. package/src/app/component/message/buttons/text-button/text-button.component.spec.ts +44 -5
  171. package/src/app/component/message/carousel/carousel.component.html +29 -16
  172. package/src/app/component/message/carousel/carousel.component.scss +20 -8
  173. package/src/app/component/message/carousel/carousel.component.spec.ts +80 -3
  174. package/src/app/component/message/carousel/carousel.component.ts +16 -0
  175. package/src/app/component/message/frame/frame.component.html +9 -4
  176. package/src/app/component/message/frame/frame.component.spec.ts +34 -15
  177. package/src/app/component/message/frame/frame.component.ts +7 -2
  178. package/src/app/component/message/html/html.component.html +1 -1
  179. package/src/app/component/message/html/html.component.scss +1 -1
  180. package/src/app/component/message/html/html.component.spec.ts +24 -7
  181. package/src/app/component/message/image/image.component.html +12 -10
  182. package/src/app/component/message/image/image.component.scss +16 -0
  183. package/src/app/component/message/image/image.component.spec.ts +101 -15
  184. package/src/app/component/message/image/image.component.ts +90 -51
  185. package/src/app/component/message/info-message/info-message.component.spec.ts +26 -14
  186. package/src/app/component/message/json-sources/json-sources.component.html +38 -0
  187. package/src/app/component/message/json-sources/json-sources.component.scss +197 -0
  188. package/src/app/component/message/json-sources/json-sources.component.ts +89 -0
  189. package/src/app/component/message/like-unlike/like-unlike.component.html +7 -9
  190. package/src/app/component/message/like-unlike/like-unlike.component.spec.ts +31 -3
  191. package/src/app/component/message/return-receipt/return-receipt.component.spec.ts +38 -17
  192. package/src/app/component/message/text/text.component.html +3 -3
  193. package/src/app/component/message/text/text.component.scss +80 -86
  194. package/src/app/component/message/text/text.component.spec.ts +106 -13
  195. package/src/app/component/message-attachment/message-attachment.component.spec.ts +134 -13
  196. package/src/app/component/selection-department/selection-department.component.html +21 -23
  197. package/src/app/component/selection-department/selection-department.component.spec.ts +159 -14
  198. package/src/app/component/selection-department/selection-department.component.ts +8 -1
  199. package/src/app/component/send-button/send-button.component.html +5 -13
  200. package/src/app/component/send-button/send-button.component.spec.ts +2 -2
  201. package/src/app/component/star-rating-widget/star-rating-widget.component.html +51 -81
  202. package/src/app/directives/tooltip.directive.spec.ts +8 -4
  203. package/src/app/modals/confirm-close/confirm-close.component.html +20 -8
  204. package/src/app/modals/confirm-close/confirm-close.component.scss +3 -0
  205. package/src/app/modals/confirm-close/confirm-close.component.spec.ts +13 -4
  206. package/src/app/modals/confirm-close/confirm-close.component.ts +8 -1
  207. package/src/app/pipe/html-entites-encode.pipe.spec.ts +35 -2
  208. package/src/app/pipe/marked.pipe.spec.ts +38 -2
  209. package/src/app/providers/app-config.service.ts +4 -2
  210. package/src/app/providers/brand.service.spec.ts +23 -2
  211. package/src/app/providers/brand.service.ts +1 -1
  212. package/src/app/providers/global-settings.service.spec.ts +1009 -14
  213. package/src/app/providers/global-settings.service.ts +30 -2
  214. package/src/app/providers/json-sources-parser.service.ts +182 -0
  215. package/src/app/providers/translator.service.ts +24 -6
  216. package/src/app/providers/tts-audio-playback-coordinator.service.spec.ts +117 -0
  217. package/src/app/providers/tts-audio-playback-coordinator.service.ts +45 -7
  218. package/src/app/providers/url-preview.service.ts +82 -0
  219. package/src/app/providers/voice/audio.types.ts +6 -0
  220. package/src/app/providers/voice/voice-streaming.service.spec.ts +23 -0
  221. package/src/app/providers/voice/voice-streaming.service.ts +710 -0
  222. package/src/app/providers/voice/voice-streaming.types.ts +113 -0
  223. package/src/app/providers/voice/voice.service.spec.ts +203 -3
  224. package/src/app/providers/voice/voice.service.ts +521 -12
  225. package/src/app/sass/_variables.scss +1 -1
  226. package/src/app/sass/animations.scss +19 -1
  227. package/src/app/utils/globals.ts +4 -0
  228. package/src/app/utils/json-sources-utils.ts +27 -0
  229. package/src/app/utils/url-utils.ts +98 -0
  230. package/src/app/utils/utils-resources.ts +1 -1
  231. package/src/assets/i18n/en.json +26 -1
  232. package/src/assets/i18n/es.json +106 -101
  233. package/src/assets/i18n/fr.json +106 -101
  234. package/src/assets/i18n/it.json +106 -99
  235. package/src/assets/twp/index-dev.html +18 -0
  236. package/src/assets/twp/tiledesk_widget_files/widget-css-override-example.css +14 -0
  237. package/src/chat21-core/providers/chat-manager.spec.ts +72 -0
  238. package/src/chat21-core/providers/scripts/script.service.spec.ts +12 -2
  239. package/src/chat21-core/utils/constants.ts +4 -0
  240. package/src/chat21-core/utils/utils-message.ts +23 -1
  241. package/src/widget-config-template.json +3 -1
  242. package/src/widget-config.json +28 -27
  243. package/test-results/.last-run.json +4 -0
  244. package/tests/widget-form-rich.spec.ts +67 -0
  245. package/tests/widget-index-dev-settings.spec.ts +52 -0
  246. package/tests/widget-twp-iframe.spec.ts +39 -0
@@ -163,6 +163,7 @@ export class ConversationComponent implements OnInit, AfterViewInit, OnChanges {
163
163
 
164
164
  // ========== begin:: stream audio ======= //
165
165
  public isStreamAudioActive = false;
166
+ public isStreamAudioConnecting = false;
166
167
  // ========== end:: stream audio ======= //
167
168
 
168
169
  @ViewChild(ConversationFooterComponent) conversationFooter: ConversationFooterComponent
@@ -251,7 +252,19 @@ export class ConversationComponent implements OnInit, AfterViewInit, OnChanges {
251
252
  'EMOJI_NOT_ELLOWED',
252
253
  'ATTACHMENT',
253
254
  'EMOJI',
254
- 'CLOSE_CHAT'
255
+ 'BUTTON_ATTACH_FILE',
256
+ 'BUTTON_SEND_MESSAGE',
257
+ 'BUTTON_RECORD_AUDIO',
258
+ 'BUTTON_DELETE_AUDIO',
259
+ 'BUTTON_SEND_AUDIO',
260
+ 'BUTTON_PLAY_AUDIO',
261
+ 'BUTTON_PAUSE_AUDIO',
262
+ 'SKIP_TO_COMPOSER',
263
+ 'CLOSE_CHAT',
264
+ 'CLOSE',
265
+ 'VOICE_CONNECTING',
266
+ 'VOICE_LISTENING',
267
+ 'VOICE_PROCESSING'
255
268
  ];
256
269
 
257
270
  const keysContent = [
@@ -271,13 +284,21 @@ export class ConversationComponent implements OnInit, AfterViewInit, OnChanges {
271
284
  'LABEL_THINKING',
272
285
  'LABEL_TO',
273
286
  'ARRAY_DAYS',
287
+ 'CONVERSATION_LOG_LABEL',
288
+ 'BUTTON_SCROLL_TO_BOTTOM',
289
+ 'CAROUSEL_PREVIOUS',
290
+ 'CAROUSEL_NEXT',
291
+ 'CAROUSEL_LABEL',
292
+ 'CAROUSEL_SLIDE_LABEL'
274
293
  ];
275
294
 
276
295
  const keysPreview= [
277
296
  'BACK',
278
297
  'CLOSE',
279
298
  'LABEL_PLACEHOLDER',
280
- 'LABEL_PREVIEW'
299
+ 'LABEL_PREVIEW',
300
+ 'BUTTON_CLOSE_PREVIEW',
301
+ 'BUTTON_SEND_MESSAGE'
281
302
  ];
282
303
 
283
304
  const keysCloseChatDialog= [
@@ -506,27 +527,31 @@ export class ConversationComponent implements OnInit, AfterViewInit, OnChanges {
506
527
  return this.isConversationArchived;
507
528
  }
508
529
 
509
- // //FALLBACK TO TILEDESK
510
- const requests_list = await this.tiledeskRequestService.getMyRequests().catch(err => {
530
+ // FALLBACK TO TILEDESK
531
+ let requests_list: { requests: any[] };
532
+ try {
533
+ requests_list = await this.tiledeskRequestService.getMyRequests();
534
+ } catch (err) {
511
535
  this.logger.error('[CONV-COMP] getConversationDetail: error getting request from Tiledesk', err);
512
- this.isConversationArchived=true
513
- return { requests: [] }
514
- });
536
+ this.isConversationArchived = true;
537
+ return this.isConversationArchived;
538
+ }
539
+
515
540
  if (requests_list && requests_list.requests.length > 0) {
516
541
  this.logger.debug('[CONV-COMP] getConversationDetail: request exist on Tiledesk', requests_list);
517
- let conversation = requests_list.requests.find((request)=> request.request_id === this.conversationId)
518
- if(conversation){
519
- this.isConversationArchived = false
520
- return this.isConversationArchived
542
+ const conversation = requests_list.requests.find((request) => request.request_id === this.conversationId);
543
+ if (conversation) {
544
+ this.isConversationArchived = false;
545
+ return this.isConversationArchived;
521
546
  }
522
547
  this.logger.debug('[CONV-COMP] getConversationDetail: request NOT EXIST on Tiledesk', requests_list);
523
- this.isConversationArchived = true
524
- return this.isConversationArchived
548
+ this.isConversationArchived = true;
549
+ return this.isConversationArchived;
525
550
  }
526
551
 
527
- this.isConversationArchived = false;
528
- return null;
529
- }
552
+ this.isConversationArchived = false;
553
+ return null;
554
+ }
530
555
 
531
556
  /**
532
557
  * this.g.recipientId:
@@ -1055,6 +1080,21 @@ export class ConversationComponent implements OnInit, AfterViewInit, OnChanges {
1055
1080
 
1056
1081
 
1057
1082
 
1083
+ /**
1084
+ * Programmatically moves keyboard focus to the message composer textarea.
1085
+ * Wired to the visible-on-focus skip link in conversation.component.html (WCAG 2.4.1 Bypass Blocks).
1086
+ */
1087
+ skipToCompose() {
1088
+ try {
1089
+ const textarea = document.getElementById('chat21-main-message-context') as HTMLTextAreaElement | null;
1090
+ if (textarea) {
1091
+ textarea.focus();
1092
+ }
1093
+ } catch(e) {
1094
+ this.logger.warn('[CONV-COMP] skipToCompose error', e);
1095
+ }
1096
+ }
1097
+
1058
1098
  scrollToBottom() {
1059
1099
  this.conversationContent.scrollToBottom();
1060
1100
  // const that = this;
@@ -1411,6 +1451,10 @@ export class ConversationComponent implements OnInit, AfterViewInit, OnChanges {
1411
1451
  onStreamAudioActiveChange(event: boolean){
1412
1452
  this.isStreamAudioActive = event
1413
1453
  }
1454
+ /** CALLED BY: conv-footer when connecting state changes */
1455
+ onStreamAudioConnectingChange(event: boolean){
1456
+ this.isStreamAudioConnecting = event
1457
+ }
1414
1458
  /** CALLED BY: conv-footer component */
1415
1459
  onCloseChatButtonClickedFN(event){
1416
1460
  this.logger.debug('[CONV-COMP] onCloseChatButtonClicked::::', event)
@@ -1423,7 +1467,7 @@ export class ConversationComponent implements OnInit, AfterViewInit, OnChanges {
1423
1467
  * Solo in quel caso il bottom del foglio include `--chat-footer-stream-button-height`.
1424
1468
  */
1425
1469
  closeStreamButtonActiveForSheetBottom(): boolean {
1426
- return !!(this.g?.showAudioStreamFooterButton && this.isStreamAudioActive);
1470
+ return !!(this.g?.showAudioStreamFooterButton && (this.isStreamAudioActive || this.isStreamAudioConnecting));
1427
1471
  }
1428
1472
 
1429
1473
  openInputFiles() {
@@ -1,32 +1,44 @@
1
1
  <div class="audio-recorder">
2
- <button *ngIf="audioUrl" (click)="deleteRecording()">
2
+ <button *ngIf="audioUrl" type="button" [attr.aria-label]="translationMap?.get('BUTTON_DELETE_AUDIO') || 'Delete recording'" (click)="deleteRecording()">
3
3
  <span class="v-align-center">
4
- <svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px">
4
+ <svg aria-hidden="true" focusable="false" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px">
5
5
  <path d="M280-120q-33 0-56.5-23.5T200-200v-520h-40v-80h200v-40h240v40h200v80h-40v520q0 33-23.5 56.5T680-120H280Zm80-160h80v-360h-80v360Zm160 0h80v-360h-80v360Z"/>
6
6
  </svg>
7
- <!-- <i class="material-icons">delete_outline</i> -->
8
7
  </span>
9
8
  </button>
10
9
 
11
- <chat-audio class="test" *ngIf="audioBlob && audioUrl"
12
- [audioBlob] = "audioBlob"
10
+ <chat-audio class="test" *ngIf="audioBlob && audioUrl"
11
+ [audioBlob]="audioBlob"
13
12
  [color]="'var(--chat-footer-color)'"
13
+ [translationMap]="translationMap"
14
14
  [stylesMap]="stylesMap">
15
15
  </chat-audio>
16
-
17
- <button *ngIf="!audioUrl" class="mic-button" (mousedown)="startRecording($event)" (mouseup)="stopRecording($event)" (touchstart)="startRecording($event)" (touchend)="stopRecording($event)">
18
- <svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px">
16
+
17
+ <button *ngIf="!audioUrl"
18
+ type="button"
19
+ class="mic-button"
20
+ [attr.aria-label]="translationMap?.get('BUTTON_RECORD_AUDIO') || 'Hold to record an audio message'"
21
+ [attr.aria-pressed]="isRecording ? 'true' : 'false'"
22
+ (mousedown)="startRecording($event)"
23
+ (mouseup)="stopRecording($event)"
24
+ (touchstart)="startRecording($event)"
25
+ (touchend)="stopRecording($event)"
26
+ (keydown.space)="$event.preventDefault(); !isRecording && startRecording($event)"
27
+ (keyup.space)="$event.preventDefault(); isRecording && stopRecording($event)">
28
+ <svg aria-hidden="true" focusable="false" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px">
19
29
  <path d="M480-400q-50 0-85-35t-35-85v-240q0-50 35-85t85-35q50 0 85 35t35 85v240q0 50-35 85t-85 35Zm0-240Zm-40 520v-123q-104-14-172-93t-68-184h80q0 83 58.5 141.5T480-320q83 0 141.5-58.5T680-520h80q0 105-68 184t-172 93v123h-80Zm40-360q17 0 28.5-11.5T520-520v-240q0-17-11.5-28.5T480-800q-17 0-28.5 11.5T440-760v240q0 17 11.5 28.5T480-480Z"/>
20
30
  </svg>
21
31
  </button>
22
- <!-- <button *ngIf="isRecording" (click)="stopRecording()"><i class="material-icons">pause_circle_outline</i></button> -->
23
-
24
- <button *ngIf="audioUrl" (click)="sendMessage()">
32
+
33
+ <button *ngIf="audioUrl"
34
+ type="button"
35
+ [attr.aria-label]="translationMap?.get('BUTTON_SEND_AUDIO') || 'Send audio message'"
36
+ (click)="sendMessage()">
25
37
  <span class="v-align-center">
26
- <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" height="20" width="24" viewBox="0 0 24 20" xml:space="preserve">
38
+ <svg aria-hidden="true" focusable="false" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" height="20" width="24" viewBox="0 0 24 20" xml:space="preserve">
27
39
  <path d="M1.8,18.9V1.7L22,10.3L1.8,18.9z M3.9,15.6l12.6-5.4L3.9,4.9v3.7l6.4,1.6l-6.4,1.6V15.6z M3.9,15.6V4.9v7V15.6z"/>
28
40
  </svg>
29
41
  </span>
30
42
  </button>
31
43
 
32
- </div>
44
+ </div>
@@ -1,23 +1,141 @@
1
- import { ComponentFixture, TestBed } from '@angular/core/testing';
1
+ import { ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing';
2
2
 
3
3
  import { ConversationAudioRecorderComponent } from './conversation-audio-recorder.component';
4
4
 
5
- describe('AudioRecorderComponent', () => {
5
+ describe('ConversationAudioRecorderComponent', () => {
6
6
  let component: ConversationAudioRecorderComponent;
7
7
  let fixture: ComponentFixture<ConversationAudioRecorderComponent>;
8
+ let stopListeners: { stop?: () => void; data?: (e: { data: Blob }) => void };
9
+ let mediaRecorderInstance: {
10
+ start: jasmine.Spy;
11
+ stop: jasmine.Spy;
12
+ mimeType: string;
13
+ addEventListener: jasmine.Spy;
14
+ };
8
15
 
9
16
  beforeEach(async () => {
17
+ stopListeners = {};
18
+ mediaRecorderInstance = {
19
+ start: jasmine.createSpy('start'),
20
+ stop: jasmine.createSpy('stop').and.callFake(() => {
21
+ const fn = stopListeners.stop;
22
+ if (fn) {
23
+ fn();
24
+ }
25
+ }),
26
+ mimeType: 'audio/webm',
27
+ addEventListener: jasmine.createSpy('addEventListener').and.callFake((ev: string, fn: any) => {
28
+ if (ev === 'stop') {
29
+ stopListeners.stop = fn;
30
+ }
31
+ if (ev === 'dataavailable') {
32
+ stopListeners.data = fn;
33
+ }
34
+ }),
35
+ };
36
+
37
+ const stream = {
38
+ getTracks: () => [{ stop: jasmine.createSpy('trackStop') }],
39
+ };
40
+
41
+ spyOn(window.navigator.mediaDevices, 'getUserMedia').and.returnValue(Promise.resolve(stream as any));
42
+ (window as any).MediaRecorder = jasmine.createSpy('MediaRecorder').and.returnValue(mediaRecorderInstance);
43
+
10
44
  await TestBed.configureTestingModule({
11
- declarations: [ ConversationAudioRecorderComponent ]
12
- })
13
- .compileComponents();
45
+ declarations: [ConversationAudioRecorderComponent],
46
+ }).compileComponents();
14
47
 
15
48
  fixture = TestBed.createComponent(ConversationAudioRecorderComponent);
16
49
  component = fixture.componentInstance;
50
+ component.translationMap = new Map();
51
+ component.stylesMap = new Map();
52
+ spyOn(component.startRecordingEvent, 'emit');
53
+ spyOn(component.endRecordingEvent, 'emit');
17
54
  fixture.detectChanges();
18
55
  });
19
56
 
20
57
  it('should create', () => {
21
58
  expect(component).toBeTruthy();
22
59
  });
60
+
61
+ describe('startRecording', () => {
62
+ it('should preventDefault on touchstart', fakeAsync(() => {
63
+ const ev = { type: 'touchstart', preventDefault: jasmine.createSpy('pd') } as any;
64
+ component.startRecording(ev);
65
+ tick();
66
+ expect(ev.preventDefault).toHaveBeenCalled();
67
+ expect(component.startRecordingEvent.emit).toHaveBeenCalled();
68
+ }));
69
+
70
+ it('should request microphone and start MediaRecorder on mousedown', fakeAsync(() => {
71
+ const ev = new MouseEvent('mousedown');
72
+ component.startRecording(ev);
73
+ tick();
74
+ expect(navigator.mediaDevices.getUserMedia).toHaveBeenCalledWith({ audio: true });
75
+ expect(mediaRecorderInstance.start).toHaveBeenCalled();
76
+ expect(component.isRecording).toBe(true);
77
+ }));
78
+
79
+ it('should log when getUserMedia fails', fakeAsync(() => {
80
+ (navigator.mediaDevices.getUserMedia as jasmine.Spy).and.returnValue(Promise.reject(new Error('denied')));
81
+ spyOn(console, 'error');
82
+ component.startRecording(new MouseEvent('mousedown'));
83
+ tick();
84
+ expect(console.error).toHaveBeenCalled();
85
+ }));
86
+ });
87
+
88
+ describe('stopRecording', () => {
89
+ it('should discard very short press without stopping recorder', fakeAsync(() => {
90
+ component.startTime = Date.now();
91
+ component.stopRecording(new MouseEvent('mouseup'));
92
+ tick(400);
93
+ expect(mediaRecorderInstance.stop).not.toHaveBeenCalled();
94
+ }));
95
+
96
+ it('should stop recorder after long press', fakeAsync(() => {
97
+ component.mediaRecorder = mediaRecorderInstance as any;
98
+ component.startTime = Date.now() - 600;
99
+ component.stopRecording(new MouseEvent('mouseup'));
100
+ tick(400);
101
+ expect(mediaRecorderInstance.stop).toHaveBeenCalled();
102
+ }));
103
+
104
+ it('should preventDefault on touchend', () => {
105
+ const ev = { type: 'touchend', preventDefault: jasmine.createSpy('pd') } as any;
106
+ component.stopRecording(ev);
107
+ expect(ev.preventDefault).toHaveBeenCalled();
108
+ });
109
+ });
110
+
111
+ describe('deleteRecording', () => {
112
+ it('should reset state and emit', () => {
113
+ spyOn(component.deleteRecordingEvent, 'emit');
114
+ component.audioUrl = {} as any;
115
+ component.audioBlob = new Blob();
116
+ component.deleteRecording();
117
+ expect(component.audioUrl).toBeNull();
118
+ expect(component.audioBlob).toBeNull();
119
+ expect(component.deleteRecordingEvent.emit).toHaveBeenCalledWith(null);
120
+ });
121
+ });
122
+
123
+ describe('sendMessage', () => {
124
+ it('should emit blob and clear url when recording exists', () => {
125
+ spyOn(component.sendRecordingEvent, 'emit');
126
+ const b = new Blob(['a'], { type: 'audio/webm' });
127
+ component.audioBlob = b;
128
+ component.audioUrl = {} as any;
129
+ component.sendMessage();
130
+ expect(component.sendRecordingEvent.emit).toHaveBeenCalledWith(b);
131
+ expect(component.audioUrl).toBeNull();
132
+ });
133
+
134
+ it('should no-op when there is no audioUrl', () => {
135
+ spyOn(component.sendRecordingEvent, 'emit');
136
+ component.audioUrl = null;
137
+ component.sendMessage();
138
+ expect(component.sendRecordingEvent.emit).not.toHaveBeenCalled();
139
+ });
140
+ });
23
141
  });
@@ -9,6 +9,7 @@ import { DomSanitizer, SafeUrl } from '@angular/platform-browser';
9
9
  export class ConversationAudioRecorderComponent {
10
10
 
11
11
  @Input() stylesMap: Map<string, string>;
12
+ @Input() translationMap: Map<string, string>;
12
13
  @Output() startRecordingEvent = new EventEmitter<void>();
13
14
  @Output() deleteRecordingEvent = new EventEmitter<void>();
14
15
  @Output() endRecordingEvent = new EventEmitter<Blob | null>();
@@ -2,7 +2,12 @@
2
2
 
3
3
  <div class="c21-body-container">
4
4
 
5
- <div class="c21-body-content" tabindex="1520" aria-label=" messaggi della conversazione: ">
5
+ <div class="c21-body-content"
6
+ role="log"
7
+ aria-live="polite"
8
+ aria-relevant="additions text"
9
+ aria-atomic="false"
10
+ [attr.aria-label]="translationMap?.get('CONVERSATION_LOG_LABEL') || 'Conversation messages'">
6
11
 
7
12
  <!-- USER TYPING (WAIT MESSAGE) -->
8
13
  <span *ngIf="messages && this.messages.length === 0 && !isTypings">
@@ -19,21 +24,22 @@
19
24
  <div #scrollMe id="scroll-me" (scroll)="onScroll($event)">
20
25
 
21
26
  <div id="{{idDivScroll}}" class="c21-contentScroll" > <!-- (resized)="onResized($event)" -->
22
- <div *ngFor="let message of messages; let first = first; let last = last; let i = index" tabindex="1521" class="rowMsg">
23
-
27
+ <div *ngFor="let message of messages; let first = first; let last = last; let i = index" class="rowMsg">
28
+
24
29
  <!-- message SENDER:: -->
25
- <div role="messaggio" *ngIf="messageType(MESSAGE_TYPE_MINE, message) && (!isStreamAudioActive && !message.isJustRecived)" class="msg_container base_sent">
30
+ <div role="article" *ngIf="messageType(MESSAGE_TYPE_MINE, message) && !message.isJustRecived" class="msg_container base_sent">
26
31
 
27
32
  <!--backgroundColor non viene ancora usato -->
28
33
  <!-- class="messages msg_sent slide-in-right" -->
29
34
  <chat-bubble-message class="messages msg_sent"
30
- [class.no-background]="(isImage(message) || isFrame(message)) && ((message?.text && message?.text.trim() === '') || !message?.text)"
35
+ [class.no-background]="(isImage(message) || isFrame(message)) && ((message?.text && message?.text.trim() === '') || !message?.text)"
31
36
  [class.emoticon]="isEmojii(message?.text)"
32
37
  [ngStyle]="{'background': stylesMap.get('bubbleSentBackground'), 'color': stylesMap.get('bubbleSentTextColor')}"
33
38
  [ngClass]="{'button-in-msg' : message?.metadata && message?.metadata?.button}"
34
39
  [message]="message"
35
40
  [fontColor]="stylesMap.get('bubbleSentTextColor')"
36
41
  [stylesMap]="stylesMap"
42
+ [translationMap]="translationMap"
37
43
  (onBeforeMessageRender)="onBeforeMessageRenderFN($event)"
38
44
  (onAfterMessageRender)="onAfterMessageRenderFN($event)"
39
45
  (onElementRendered)="onElementRenderedFN($event)">
@@ -47,7 +53,7 @@
47
53
  </div>
48
54
 
49
55
  <!-- message RECIPIENT:: -->
50
- <div role="messaggio" *ngIf="messageType(MESSAGE_TYPE_OTHERS, message)" class="msg_container base_receive">
56
+ <div role="article" *ngIf="messageType(MESSAGE_TYPE_OTHERS, message)" class="msg_container base_receive">
51
57
 
52
58
  <chat-avatar-image *ngIf="!isSameSender(message?.sender, i) && !isStreamAudioActive"
53
59
  [ngClass]="{'slide-in-left': false}"
@@ -60,14 +66,17 @@
60
66
  <!-- [ngClass]="{'slide-in-left': !isFirstMessage(message?.sender, i)}" -->
61
67
  <chat-bubble-message class="messages msg_receive"
62
68
  [ngClass]="{'slide-in-left': false}"
63
- [class.no-background]="(isImage(message) || isFrame(message) || isCarousel(message)) && ((message?.text && message?.text.trim() === '') || !message?.text)"
69
+ [class.no-background]="(isImage(message) || isFrame(message) || isCarousel(message)) && ((message?.text && message?.text.trim() === '') || !message?.text)"
64
70
  [class.emoticon]="isEmojii(message?.text)"
65
- [style.margin-left]="isStreamAudioActive ? '0px' : (isSameSender(message?.sender, i) ? 'calc(var(--avatar-width) + 10px)' : null)"
71
+ [class.fullSizeMessage]="isStreamAudioActive"
72
+ [style.margin-left]="isSameSender(message?.sender, i) ? 'calc(var(--avatar-width) + 10px)' : null"
66
73
  [ngStyle]="{'background': stylesMap.get('bubbleReceivedBackground'), 'color': stylesMap.get('bubbleReceivedTextColor'), 'width':isFrame(message) ?'100%' : null}"
67
74
  [isSameSender]="isSameSender(message?.sender, i)"
68
75
  [message]="message"
69
76
  [fontColor]="stylesMap.get('bubbleReceivedTextColor')"
70
77
  [stylesMap]="stylesMap"
78
+ [translationMap]="translationMap"
79
+ [streamOnArrival]="false"
71
80
  (onBeforeMessageRender)="onBeforeMessageRenderFN($event)"
72
81
  (onAfterMessageRender)="onAfterMessageRenderFN($event)"
73
82
  (onElementRendered)="onElementRenderedFN($event)">
@@ -110,6 +119,7 @@
110
119
  [isConversationArchived]="isConversationArchived"
111
120
  [isLastMessage] = "isLastMessage(message?.uid)"
112
121
  [stylesMap]="stylesMap"
122
+ [translationMap]="translationMap"
113
123
  (onElementRendered)="onElementRenderedFN($event)"
114
124
  (onAttachmentButtonClicked)="onAttachmentButtonClickedFN($event)">
115
125
  </chat-carousel>
@@ -134,9 +144,10 @@
134
144
  [senderFullname]="nameUserTypingNow"
135
145
  [baseLocation]="baseLocation">
136
146
  </chat-avatar-image>
147
+
137
148
  <user-typing
138
- [ngClass]="{'userTypingNowExist': !idUserTypingNow}"
139
149
  [color]="stylesMap?.get('iconColor')"
150
+ [ngClass]="{'userTypingNowExist': !idUserTypingNow}"
140
151
  [translationMap]="translationMap"
141
152
  [idUserTypingNow]="idUserTypingNow"
142
153
  [nameUserTypingNow]="nameUserTypingNow">
@@ -145,7 +156,9 @@
145
156
 
146
157
  <div *ngIf="showThinkingMessage && lastServerSenderKind === 'bot'" class="msg_container base_receive thinking_receive">
147
158
  <user-typing class="loading thinking-dots"
159
+ [class.fullSize]="isStreamAudioActive"
148
160
  [color]="stylesMap?.get('iconColor')"
161
+ [class.fullSize]="isStreamAudioActive"
149
162
  [translationMap]="translationMap"
150
163
  [idUserTypingNow]="idUserTypingNow"
151
164
  [nameUserTypingNow]="nameUserTypingNow">
@@ -27,6 +27,10 @@
27
27
  margin: 25px 50px
28
28
  }
29
29
 
30
+ :host .loading.fullSize ::ng-deep > div.spinner{
31
+ margin: 15px 0px !important;
32
+ }
33
+
30
34
  // ============= CSS c21-body ================= //
31
35
  .c21-body {
32
36
  // -webkit-box-shadow: inset 0 10px 10px -10px rgba(0,0,0,0.4);
@@ -164,6 +168,12 @@
164
168
  min-width: 14px;
165
169
  border: 0.1px solid #0000000f;
166
170
  }
171
+ .msg_sent.json-resources{
172
+ border: 0 !important;
173
+ width: 100%;
174
+ max-width: 652px;
175
+ flex: 1 1 auto;
176
+ }
167
177
  .message_innerhtml {
168
178
  padding: 8px;
169
179
  }
@@ -236,6 +246,18 @@
236
246
  height: fit-content;
237
247
  width: auto;
238
248
 
249
+ &.fullSizeMessage {
250
+ max-width: 100%;
251
+ margin: auto 0 auto 0 !important;
252
+ }
253
+
254
+ }
255
+ .msg_receive.json-resources{
256
+ min-height: unset;
257
+ padding: 0;
258
+ width: 100%;
259
+ max-width: 652px;
260
+ flex: 1 1 auto;
239
261
  }
240
262
 
241
263
 
@@ -341,4 +363,4 @@
341
363
  }
342
364
  }
343
365
 
344
- // ============= END CSS c21-body ================= //
366
+ // ============= END CSS c21-body ================= //