@chat21/chat21-web-widget 5.1.32-rc9 → 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 +54 -4
  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 +17 -7
  93. package/src/app/component/conversation-detail/conversation-content/conversation-content.component.scss +15 -3
  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 +192 -84
  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 -18
  111. package/src/app/component/conversation-detail/stream-audio-spectrum/stream-audio-spectrum.component.scss +56 -2
  112. package/src/app/component/conversation-detail/stream-audio-spectrum/stream-audio-spectrum.component.ts +135 -5
  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 -0
  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 +133 -86
  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 +39 -16
  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 +517 -13
  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,7 +66,7 @@
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
71
  [class.fullSizeMessage]="isStreamAudioActive"
66
72
  [style.margin-left]="isSameSender(message?.sender, i) ? 'calc(var(--avatar-width) + 10px)' : null"
@@ -69,6 +75,8 @@
69
75
  [message]="message"
70
76
  [fontColor]="stylesMap.get('bubbleReceivedTextColor')"
71
77
  [stylesMap]="stylesMap"
78
+ [translationMap]="translationMap"
79
+ [streamOnArrival]="false"
72
80
  (onBeforeMessageRender)="onBeforeMessageRenderFN($event)"
73
81
  (onAfterMessageRender)="onAfterMessageRenderFN($event)"
74
82
  (onElementRendered)="onElementRenderedFN($event)">
@@ -111,6 +119,7 @@
111
119
  [isConversationArchived]="isConversationArchived"
112
120
  [isLastMessage] = "isLastMessage(message?.uid)"
113
121
  [stylesMap]="stylesMap"
122
+ [translationMap]="translationMap"
114
123
  (onElementRendered)="onElementRenderedFN($event)"
115
124
  (onAttachmentButtonClicked)="onAttachmentButtonClickedFN($event)">
116
125
  </chat-carousel>
@@ -149,6 +158,7 @@
149
158
  <user-typing class="loading thinking-dots"
150
159
  [class.fullSize]="isStreamAudioActive"
151
160
  [color]="stylesMap?.get('iconColor')"
161
+ [class.fullSize]="isStreamAudioActive"
152
162
  [translationMap]="translationMap"
153
163
  [idUserTypingNow]="idUserTypingNow"
154
164
  [nameUserTypingNow]="nameUserTypingNow">
@@ -27,9 +27,8 @@
27
27
  margin: 25px 50px
28
28
  }
29
29
 
30
-
31
30
  :host .loading.fullSize ::ng-deep > div.spinner{
32
- margin: 50px 0px !important;
31
+ margin: 15px 0px !important;
33
32
  }
34
33
 
35
34
  // ============= CSS c21-body ================= //
@@ -169,6 +168,12 @@
169
168
  min-width: 14px;
170
169
  border: 0.1px solid #0000000f;
171
170
  }
171
+ .msg_sent.json-resources{
172
+ border: 0 !important;
173
+ width: 100%;
174
+ max-width: 652px;
175
+ flex: 1 1 auto;
176
+ }
172
177
  .message_innerhtml {
173
178
  padding: 8px;
174
179
  }
@@ -247,6 +252,13 @@
247
252
  }
248
253
 
249
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;
261
+ }
250
262
 
251
263
 
252
264
  .message_innerhtml {
@@ -351,4 +363,4 @@
351
363
  }
352
364
  }
353
365
 
354
- // ============= END CSS c21-body ================= //
366
+ // ============= END CSS c21-body ================= //