@chat21/chat21-web-widget 5.1.32-rc9 → 5.1.33-rc11

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 -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.config.ts +41 -0
  80. package/src/app/app.component.html +2 -2
  81. package/src/app/app.component.scss +25 -14
  82. package/src/app/app.component.spec.ts +21 -6
  83. package/src/app/app.module.ts +4 -0
  84. package/src/app/component/conversation-detail/conversation/conversation.component.html +19 -11
  85. package/src/app/component/conversation-detail/conversation/conversation.component.scss +28 -0
  86. package/src/app/component/conversation-detail/conversation/conversation.component.spec.ts +644 -75
  87. package/src/app/component/conversation-detail/conversation/conversation.component.ts +63 -17
  88. package/src/app/component/conversation-detail/conversation-audio-recorder/conversation-audio-recorder.component.html +25 -13
  89. package/src/app/component/conversation-detail/conversation-audio-recorder/conversation-audio-recorder.component.spec.ts +123 -5
  90. package/src/app/component/conversation-detail/conversation-audio-recorder/conversation-audio-recorder.component.ts +1 -0
  91. package/src/app/component/conversation-detail/conversation-content/conversation-content.component.html +17 -7
  92. package/src/app/component/conversation-detail/conversation-content/conversation-content.component.scss +15 -3
  93. package/src/app/component/conversation-detail/conversation-content/conversation-content.component.spec.ts +242 -149
  94. package/src/app/component/conversation-detail/conversation-content/conversation-content.component.ts +7 -6
  95. package/src/app/component/conversation-detail/conversation-emojii/conversation-emojii.component.spec.ts +53 -3
  96. package/src/app/component/conversation-detail/conversation-footer/conversation-footer.component copy.html +172 -0
  97. package/src/app/component/conversation-detail/conversation-footer/conversation-footer.component.html +112 -61
  98. package/src/app/component/conversation-detail/conversation-footer/conversation-footer.component.scss +133 -16
  99. package/src/app/component/conversation-detail/conversation-footer/conversation-footer.component.spec.ts +452 -78
  100. package/src/app/component/conversation-detail/conversation-footer/conversation-footer.component.ts +198 -84
  101. package/src/app/component/conversation-detail/conversation-header/conversation-header.component.html +113 -53
  102. package/src/app/component/conversation-detail/conversation-header/conversation-header.component.scss +12 -4
  103. package/src/app/component/conversation-detail/conversation-header/conversation-header.component.spec.ts +274 -29
  104. package/src/app/component/conversation-detail/conversation-internal-frame/conversation-internal-frame.component.html +23 -9
  105. package/src/app/component/conversation-detail/conversation-internal-frame/conversation-internal-frame.component.spec.ts +80 -8
  106. package/src/app/component/conversation-detail/conversation-preview/conversation-preview.component.html +29 -23
  107. package/src/app/component/conversation-detail/conversation-preview/conversation-preview.component.spec.ts +185 -16
  108. package/src/app/component/conversation-detail/conversation-preview/conversation-preview.component.ts +34 -14
  109. package/src/app/component/conversation-detail/stream-audio-spectrum/stream-audio-spectrum.component.html +46 -18
  110. package/src/app/component/conversation-detail/stream-audio-spectrum/stream-audio-spectrum.component.scss +60 -2
  111. package/src/app/component/conversation-detail/stream-audio-spectrum/stream-audio-spectrum.component.ts +135 -5
  112. package/src/app/component/error-alert/error-alert.component.spec.ts +65 -5
  113. package/src/app/component/eyeeye-catcher-card/eyeeye-catcher-card.component.html +16 -7
  114. package/src/app/component/eyeeye-catcher-card/eyeeye-catcher-card.component.scss +21 -0
  115. package/src/app/component/eyeeye-catcher-card/eyeeye-catcher-card.component.spec.ts +89 -7
  116. package/src/app/component/form/form-builder/form-builder.component.html +1 -1
  117. package/src/app/component/form/form-builder/form-builder.component.spec.ts +163 -21
  118. package/src/app/component/form/inputs/form-checkbox/form-checkbox.component.html +8 -4
  119. package/src/app/component/form/inputs/form-checkbox/form-checkbox.component.scss +10 -5
  120. package/src/app/component/form/inputs/form-checkbox/form-checkbox.component.spec.ts +90 -16
  121. package/src/app/component/form/inputs/form-checkbox/form-checkbox.component.ts +26 -0
  122. package/src/app/component/form/inputs/form-label/form-label.component.spec.ts +45 -11
  123. package/src/app/component/form/inputs/form-radio-button/form-radio-button.component.spec.ts +24 -6
  124. package/src/app/component/form/inputs/form-select/form-select.component.spec.ts +14 -5
  125. package/src/app/component/form/inputs/form-text/form-text.component.html +14 -12
  126. package/src/app/component/form/inputs/form-text/form-text.component.scss +11 -1
  127. package/src/app/component/form/inputs/form-text/form-text.component.spec.ts +113 -17
  128. package/src/app/component/form/inputs/form-text/form-text.component.ts +35 -3
  129. package/src/app/component/form/inputs/form-textarea/form-textarea.component.html +13 -11
  130. package/src/app/component/form/inputs/form-textarea/form-textarea.component.scss +6 -5
  131. package/src/app/component/form/inputs/form-textarea/form-textarea.component.spec.ts +149 -13
  132. package/src/app/component/form/inputs/form-textarea/form-textarea.component.ts +26 -0
  133. package/src/app/component/form/prechat-form/prechat-form.component.html +14 -11
  134. package/src/app/component/form/prechat-form/prechat-form.component.spec.ts +102 -10
  135. package/src/app/component/form/prechat-form/prechat-form.component.ts +8 -1
  136. package/src/app/component/form/prechat-form-test-mock.ts +35 -0
  137. package/src/app/component/home/home.component.html +38 -31
  138. package/src/app/component/home/home.component.scss +4 -2
  139. package/src/app/component/home/home.component.spec.ts +226 -11
  140. package/src/app/component/home-conversations/home-conversations.component.html +30 -26
  141. package/src/app/component/home-conversations/home-conversations.component.scss +3 -0
  142. package/src/app/component/home-conversations/home-conversations.component.spec.ts +212 -36
  143. package/src/app/component/last-message/last-message.component.html +15 -9
  144. package/src/app/component/last-message/last-message.component.scss +16 -2
  145. package/src/app/component/last-message/last-message.component.spec.ts +204 -23
  146. package/src/app/component/launcher-button/launcher-button.component.html +8 -13
  147. package/src/app/component/launcher-button/launcher-button.component.spec.ts +104 -8
  148. package/src/app/component/list-all-conversations/list-all-conversations.component.html +12 -17
  149. package/src/app/component/list-all-conversations/list-all-conversations.component.scss +2 -0
  150. package/src/app/component/list-conversations/list-conversations.component.html +22 -22
  151. package/src/app/component/menu-options/menu-options.component.html +30 -20
  152. package/src/app/component/menu-options/menu-options.component.spec.ts +125 -9
  153. package/src/app/component/message/audio/audio.component.html +13 -15
  154. package/src/app/component/message/audio/audio.component.spec.ts +140 -5
  155. package/src/app/component/message/audio/audio.component.ts +1 -0
  156. package/src/app/component/message/audio-sync/audio-sync.component.scss +1 -0
  157. package/src/app/component/message/audio-sync/audio-sync.component.spec.ts +81 -1
  158. package/src/app/component/message/audio-sync/audio-sync.component.ts +133 -86
  159. package/src/app/component/message/avatar/avatar.component.html +2 -2
  160. package/src/app/component/message/avatar/avatar.component.spec.ts +99 -7
  161. package/src/app/component/message/bubble-message/bubble-message.component.html +39 -52
  162. package/src/app/component/message/bubble-message/bubble-message.component.scss +59 -1
  163. package/src/app/component/message/bubble-message/bubble-message.component.spec.ts +154 -57
  164. package/src/app/component/message/bubble-message/bubble-message.component.ts +152 -110
  165. package/src/app/component/message/buttons/action-button/action-button.component.html +3 -4
  166. package/src/app/component/message/buttons/action-button/action-button.component.spec.ts +49 -5
  167. package/src/app/component/message/buttons/link-button/link-button.component.scss +5 -8
  168. package/src/app/component/message/buttons/link-button/link-button.component.spec.ts +50 -5
  169. package/src/app/component/message/buttons/text-button/text-button.component.spec.ts +44 -5
  170. package/src/app/component/message/carousel/carousel.component.html +29 -16
  171. package/src/app/component/message/carousel/carousel.component.scss +20 -8
  172. package/src/app/component/message/carousel/carousel.component.spec.ts +80 -3
  173. package/src/app/component/message/carousel/carousel.component.ts +16 -0
  174. package/src/app/component/message/frame/frame.component.html +9 -4
  175. package/src/app/component/message/frame/frame.component.spec.ts +34 -15
  176. package/src/app/component/message/frame/frame.component.ts +7 -2
  177. package/src/app/component/message/html/html.component.html +1 -1
  178. package/src/app/component/message/html/html.component.scss +1 -1
  179. package/src/app/component/message/html/html.component.spec.ts +24 -7
  180. package/src/app/component/message/image/image.component.html +12 -10
  181. package/src/app/component/message/image/image.component.scss +16 -0
  182. package/src/app/component/message/image/image.component.spec.ts +101 -15
  183. package/src/app/component/message/image/image.component.ts +90 -51
  184. package/src/app/component/message/info-message/info-message.component.spec.ts +26 -14
  185. package/src/app/component/message/json-sources/json-sources.component.html +38 -0
  186. package/src/app/component/message/json-sources/json-sources.component.scss +201 -0
  187. package/src/app/component/message/json-sources/json-sources.component.ts +89 -0
  188. package/src/app/component/message/like-unlike/like-unlike.component.html +7 -9
  189. package/src/app/component/message/like-unlike/like-unlike.component.spec.ts +31 -3
  190. package/src/app/component/message/return-receipt/return-receipt.component.spec.ts +38 -17
  191. package/src/app/component/message/text/text.component.html +3 -3
  192. package/src/app/component/message/text/text.component.scss +80 -86
  193. package/src/app/component/message/text/text.component.spec.ts +106 -13
  194. package/src/app/component/message-attachment/message-attachment.component.spec.ts +134 -13
  195. package/src/app/component/selection-department/selection-department.component.html +21 -23
  196. package/src/app/component/selection-department/selection-department.component.spec.ts +159 -14
  197. package/src/app/component/selection-department/selection-department.component.ts +8 -1
  198. package/src/app/component/send-button/send-button.component.html +5 -13
  199. package/src/app/component/send-button/send-button.component.spec.ts +2 -2
  200. package/src/app/component/star-rating-widget/star-rating-widget.component.html +51 -81
  201. package/src/app/directives/tooltip.directive.spec.ts +8 -4
  202. package/src/app/modals/confirm-close/confirm-close.component.html +20 -8
  203. package/src/app/modals/confirm-close/confirm-close.component.scss +3 -0
  204. package/src/app/modals/confirm-close/confirm-close.component.spec.ts +13 -4
  205. package/src/app/modals/confirm-close/confirm-close.component.ts +8 -1
  206. package/src/app/pipe/html-entites-encode.pipe.spec.ts +35 -2
  207. package/src/app/pipe/marked.pipe.spec.ts +38 -2
  208. package/src/app/pipe/marked.pipe.ts +51 -41
  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 +59 -2
  214. package/src/app/providers/json-sources-parser.service.ts +175 -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 +702 -0
  222. package/src/app/providers/voice/voice-streaming.types.ts +112 -0
  223. package/src/app/providers/voice/voice.service.spec.ts +170 -3
  224. package/src/app/providers/voice/voice.service.ts +691 -17
  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 +14 -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 +106 -100
  232. package/src/assets/i18n/es.json +107 -101
  233. package/src/assets/i18n/fr.json +107 -101
  234. package/src/assets/i18n/it.json +107 -99
  235. package/src/assets/sounds/keyboard.mp3 +0 -0
  236. package/src/assets/twp/index-dev.html +18 -0
  237. package/src/assets/twp/tiledesk_widget_files/widget-css-override-example.css +14 -0
  238. package/src/chat21-core/providers/chat-manager.spec.ts +72 -0
  239. package/src/chat21-core/providers/scripts/script.service.spec.ts +12 -2
  240. package/src/chat21-core/utils/constants.ts +4 -0
  241. package/src/chat21-core/utils/utils-message.ts +23 -1
  242. package/src/widget-config-template.json +3 -1
  243. package/src/widget-config.json +28 -27
  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,21 @@ 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',
268
+ 'STREAM_AUDIO',
269
+ 'MAX_ATTACHMENT'
255
270
  ];
256
271
 
257
272
  const keysContent = [
@@ -271,13 +286,21 @@ export class ConversationComponent implements OnInit, AfterViewInit, OnChanges {
271
286
  'LABEL_THINKING',
272
287
  'LABEL_TO',
273
288
  'ARRAY_DAYS',
289
+ 'CONVERSATION_LOG_LABEL',
290
+ 'BUTTON_SCROLL_TO_BOTTOM',
291
+ 'CAROUSEL_PREVIOUS',
292
+ 'CAROUSEL_NEXT',
293
+ 'CAROUSEL_LABEL',
294
+ 'CAROUSEL_SLIDE_LABEL'
274
295
  ];
275
296
 
276
297
  const keysPreview= [
277
298
  'BACK',
278
299
  'CLOSE',
279
300
  'LABEL_PLACEHOLDER',
280
- 'LABEL_PREVIEW'
301
+ 'LABEL_PREVIEW',
302
+ 'BUTTON_CLOSE_PREVIEW',
303
+ 'BUTTON_SEND_MESSAGE'
281
304
  ];
282
305
 
283
306
  const keysCloseChatDialog= [
@@ -506,27 +529,31 @@ export class ConversationComponent implements OnInit, AfterViewInit, OnChanges {
506
529
  return this.isConversationArchived;
507
530
  }
508
531
 
509
- // //FALLBACK TO TILEDESK
510
- const requests_list = await this.tiledeskRequestService.getMyRequests().catch(err => {
532
+ // FALLBACK TO TILEDESK
533
+ let requests_list: { requests: any[] };
534
+ try {
535
+ requests_list = await this.tiledeskRequestService.getMyRequests();
536
+ } catch (err) {
511
537
  this.logger.error('[CONV-COMP] getConversationDetail: error getting request from Tiledesk', err);
512
- this.isConversationArchived=true
513
- return { requests: [] }
514
- });
538
+ this.isConversationArchived = true;
539
+ return this.isConversationArchived;
540
+ }
541
+
515
542
  if (requests_list && requests_list.requests.length > 0) {
516
543
  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
544
+ const conversation = requests_list.requests.find((request) => request.request_id === this.conversationId);
545
+ if (conversation) {
546
+ this.isConversationArchived = false;
547
+ return this.isConversationArchived;
521
548
  }
522
549
  this.logger.debug('[CONV-COMP] getConversationDetail: request NOT EXIST on Tiledesk', requests_list);
523
- this.isConversationArchived = true
524
- return this.isConversationArchived
550
+ this.isConversationArchived = true;
551
+ return this.isConversationArchived;
525
552
  }
526
553
 
527
- this.isConversationArchived = false;
528
- return null;
529
- }
554
+ this.isConversationArchived = false;
555
+ return null;
556
+ }
530
557
 
531
558
  /**
532
559
  * this.g.recipientId:
@@ -1055,6 +1082,21 @@ export class ConversationComponent implements OnInit, AfterViewInit, OnChanges {
1055
1082
 
1056
1083
 
1057
1084
 
1085
+ /**
1086
+ * Programmatically moves keyboard focus to the message composer textarea.
1087
+ * Wired to the visible-on-focus skip link in conversation.component.html (WCAG 2.4.1 Bypass Blocks).
1088
+ */
1089
+ skipToCompose() {
1090
+ try {
1091
+ const textarea = document.getElementById('chat21-main-message-context') as HTMLTextAreaElement | null;
1092
+ if (textarea) {
1093
+ textarea.focus();
1094
+ }
1095
+ } catch(e) {
1096
+ this.logger.warn('[CONV-COMP] skipToCompose error', e);
1097
+ }
1098
+ }
1099
+
1058
1100
  scrollToBottom() {
1059
1101
  this.conversationContent.scrollToBottom();
1060
1102
  // const that = this;
@@ -1411,6 +1453,10 @@ export class ConversationComponent implements OnInit, AfterViewInit, OnChanges {
1411
1453
  onStreamAudioActiveChange(event: boolean){
1412
1454
  this.isStreamAudioActive = event
1413
1455
  }
1456
+ /** CALLED BY: conv-footer when connecting state changes */
1457
+ onStreamAudioConnectingChange(event: boolean){
1458
+ this.isStreamAudioConnecting = event
1459
+ }
1414
1460
  /** CALLED BY: conv-footer component */
1415
1461
  onCloseChatButtonClickedFN(event){
1416
1462
  this.logger.debug('[CONV-COMP] onCloseChatButtonClicked::::', event)
@@ -1423,7 +1469,7 @@ export class ConversationComponent implements OnInit, AfterViewInit, OnChanges {
1423
1469
  * Solo in quel caso il bottom del foglio include `--chat-footer-stream-button-height`.
1424
1470
  */
1425
1471
  closeStreamButtonActiveForSheetBottom(): boolean {
1426
- return !!(this.g?.showAudioStreamFooterButton && this.isStreamAudioActive);
1472
+ return !!(this.g?.showAudioStreamFooterButton && (this.isStreamAudioActive || this.isStreamAudioConnecting));
1427
1473
  }
1428
1474
 
1429
1475
  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 ================= //