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

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 (193) hide show
  1. package/.github/workflows/docker-community-push-latest.yml +13 -23
  2. package/.github/workflows/docker-image-tag-community-tag-push.yml +12 -22
  3. package/CHANGELOG.md +8 -129
  4. package/Dockerfile +5 -4
  5. package/angular.json +3 -21
  6. package/docs/changelog/this-branch.md +0 -36
  7. package/env.sample +2 -3
  8. package/nginx.conf +2 -22
  9. package/package.json +3 -10
  10. package/src/app/app.component.html +2 -2
  11. package/src/app/app.component.scss +14 -25
  12. package/src/app/app.component.spec.ts +6 -21
  13. package/src/app/app.component.ts +9 -10
  14. package/src/app/app.module.ts +0 -13
  15. package/src/app/component/conversation-detail/conversation/conversation.component.html +11 -25
  16. package/src/app/component/conversation-detail/conversation/conversation.component.scss +2 -40
  17. package/src/app/component/conversation-detail/conversation/conversation.component.spec.ts +75 -644
  18. package/src/app/component/conversation-detail/conversation/conversation.component.ts +14 -100
  19. package/src/app/component/conversation-detail/conversation-audio-recorder/conversation-audio-recorder.component.html +13 -25
  20. package/src/app/component/conversation-detail/conversation-audio-recorder/conversation-audio-recorder.component.spec.ts +5 -123
  21. package/src/app/component/conversation-detail/conversation-audio-recorder/conversation-audio-recorder.component.ts +0 -1
  22. package/src/app/component/conversation-detail/conversation-content/conversation-content.component.html +10 -23
  23. package/src/app/component/conversation-detail/conversation-content/conversation-content.component.scss +1 -19
  24. package/src/app/component/conversation-detail/conversation-content/conversation-content.component.spec.ts +149 -242
  25. package/src/app/component/conversation-detail/conversation-content/conversation-content.component.ts +5 -8
  26. package/src/app/component/conversation-detail/conversation-emojii/conversation-emojii.component.spec.ts +3 -53
  27. package/src/app/component/conversation-detail/conversation-footer/conversation-footer.component.html +96 -200
  28. package/src/app/component/conversation-detail/conversation-footer/conversation-footer.component.scss +6 -211
  29. package/src/app/component/conversation-detail/conversation-footer/conversation-footer.component.spec.ts +78 -452
  30. package/src/app/component/conversation-detail/conversation-footer/conversation-footer.component.ts +76 -291
  31. package/src/app/component/conversation-detail/conversation-header/conversation-header.component.html +53 -113
  32. package/src/app/component/conversation-detail/conversation-header/conversation-header.component.scss +4 -12
  33. package/src/app/component/conversation-detail/conversation-header/conversation-header.component.spec.ts +29 -274
  34. package/src/app/component/conversation-detail/conversation-internal-frame/conversation-internal-frame.component.html +9 -23
  35. package/src/app/component/conversation-detail/conversation-internal-frame/conversation-internal-frame.component.spec.ts +8 -80
  36. package/src/app/component/conversation-detail/conversation-preview/conversation-preview.component.html +23 -29
  37. package/src/app/component/conversation-detail/conversation-preview/conversation-preview.component.spec.ts +16 -185
  38. package/src/app/component/conversation-detail/conversation-preview/conversation-preview.component.ts +14 -34
  39. package/src/app/component/error-alert/error-alert.component.spec.ts +5 -65
  40. package/src/app/component/eyeeye-catcher-card/eyeeye-catcher-card.component.html +7 -16
  41. package/src/app/component/eyeeye-catcher-card/eyeeye-catcher-card.component.scss +0 -21
  42. package/src/app/component/eyeeye-catcher-card/eyeeye-catcher-card.component.spec.ts +7 -89
  43. package/src/app/component/form/form-builder/form-builder.component.html +1 -1
  44. package/src/app/component/form/form-builder/form-builder.component.spec.ts +21 -163
  45. package/src/app/component/form/inputs/form-checkbox/form-checkbox.component.html +4 -8
  46. package/src/app/component/form/inputs/form-checkbox/form-checkbox.component.scss +5 -10
  47. package/src/app/component/form/inputs/form-checkbox/form-checkbox.component.spec.ts +16 -90
  48. package/src/app/component/form/inputs/form-checkbox/form-checkbox.component.ts +0 -26
  49. package/src/app/component/form/inputs/form-label/form-label.component.spec.ts +11 -45
  50. package/src/app/component/form/inputs/form-radio-button/form-radio-button.component.spec.ts +6 -24
  51. package/src/app/component/form/inputs/form-select/form-select.component.spec.ts +5 -14
  52. package/src/app/component/form/inputs/form-text/form-text.component.html +12 -14
  53. package/src/app/component/form/inputs/form-text/form-text.component.scss +1 -11
  54. package/src/app/component/form/inputs/form-text/form-text.component.spec.ts +17 -113
  55. package/src/app/component/form/inputs/form-text/form-text.component.ts +3 -35
  56. package/src/app/component/form/inputs/form-textarea/form-textarea.component.html +11 -13
  57. package/src/app/component/form/inputs/form-textarea/form-textarea.component.scss +5 -6
  58. package/src/app/component/form/inputs/form-textarea/form-textarea.component.spec.ts +13 -149
  59. package/src/app/component/form/inputs/form-textarea/form-textarea.component.ts +0 -26
  60. package/src/app/component/form/prechat-form/prechat-form.component.html +11 -14
  61. package/src/app/component/form/prechat-form/prechat-form.component.spec.ts +10 -102
  62. package/src/app/component/form/prechat-form/prechat-form.component.ts +1 -8
  63. package/src/app/component/home/home.component.html +31 -38
  64. package/src/app/component/home/home.component.scss +2 -4
  65. package/src/app/component/home/home.component.spec.ts +11 -226
  66. package/src/app/component/home-conversations/home-conversations.component.html +26 -30
  67. package/src/app/component/home-conversations/home-conversations.component.scss +0 -3
  68. package/src/app/component/home-conversations/home-conversations.component.spec.ts +36 -212
  69. package/src/app/component/last-message/last-message.component.html +9 -15
  70. package/src/app/component/last-message/last-message.component.scss +2 -16
  71. package/src/app/component/last-message/last-message.component.spec.ts +23 -204
  72. package/src/app/component/last-message/last-message.component.ts +1 -4
  73. package/src/app/component/launcher-button/launcher-button.component.html +13 -8
  74. package/src/app/component/launcher-button/launcher-button.component.spec.ts +8 -104
  75. package/src/app/component/list-all-conversations/list-all-conversations.component.html +17 -12
  76. package/src/app/component/list-all-conversations/list-all-conversations.component.scss +0 -2
  77. package/src/app/component/list-conversations/list-conversations.component.html +22 -22
  78. package/src/app/component/menu-options/menu-options.component.html +20 -30
  79. package/src/app/component/menu-options/menu-options.component.spec.ts +9 -125
  80. package/src/app/component/message/audio/audio.component.html +15 -13
  81. package/src/app/component/message/audio/audio.component.spec.ts +5 -140
  82. package/src/app/component/message/audio/audio.component.ts +5 -1
  83. package/src/app/component/message/avatar/avatar.component.html +2 -2
  84. package/src/app/component/message/avatar/avatar.component.spec.ts +7 -99
  85. package/src/app/component/message/bubble-message/bubble-message.component.html +51 -38
  86. package/src/app/component/message/bubble-message/bubble-message.component.scss +1 -54
  87. package/src/app/component/message/bubble-message/bubble-message.component.spec.ts +57 -154
  88. package/src/app/component/message/bubble-message/bubble-message.component.ts +11 -89
  89. package/src/app/component/message/buttons/action-button/action-button.component.html +4 -3
  90. package/src/app/component/message/buttons/action-button/action-button.component.spec.ts +5 -49
  91. package/src/app/component/message/buttons/link-button/link-button.component.scss +8 -5
  92. package/src/app/component/message/buttons/link-button/link-button.component.spec.ts +5 -50
  93. package/src/app/component/message/buttons/text-button/text-button.component.spec.ts +5 -44
  94. package/src/app/component/message/carousel/carousel.component.html +16 -29
  95. package/src/app/component/message/carousel/carousel.component.scss +8 -20
  96. package/src/app/component/message/carousel/carousel.component.spec.ts +3 -80
  97. package/src/app/component/message/carousel/carousel.component.ts +0 -16
  98. package/src/app/component/message/frame/frame.component.html +4 -9
  99. package/src/app/component/message/frame/frame.component.spec.ts +15 -34
  100. package/src/app/component/message/frame/frame.component.ts +2 -7
  101. package/src/app/component/message/html/html.component.html +1 -1
  102. package/src/app/component/message/html/html.component.scss +1 -1
  103. package/src/app/component/message/html/html.component.spec.ts +7 -24
  104. package/src/app/component/message/image/image.component.html +10 -12
  105. package/src/app/component/message/image/image.component.scss +0 -16
  106. package/src/app/component/message/image/image.component.spec.ts +15 -101
  107. package/src/app/component/message/image/image.component.ts +51 -90
  108. package/src/app/component/message/info-message/info-message.component.spec.ts +14 -26
  109. package/src/app/component/message/like-unlike/like-unlike.component.html +9 -7
  110. package/src/app/component/message/like-unlike/like-unlike.component.spec.ts +3 -31
  111. package/src/app/component/message/return-receipt/return-receipt.component.spec.ts +17 -38
  112. package/src/app/component/message/text/text.component.html +3 -3
  113. package/src/app/component/message/text/text.component.scss +86 -80
  114. package/src/app/component/message/text/text.component.spec.ts +13 -106
  115. package/src/app/component/message-attachment/message-attachment.component.spec.ts +13 -134
  116. package/src/app/component/selection-department/selection-department.component.html +23 -21
  117. package/src/app/component/selection-department/selection-department.component.spec.ts +14 -159
  118. package/src/app/component/selection-department/selection-department.component.ts +1 -8
  119. package/src/app/component/send-button/send-button.component.html +13 -5
  120. package/src/app/component/send-button/send-button.component.spec.ts +2 -2
  121. package/src/app/component/star-rating-widget/star-rating-widget.component.html +81 -51
  122. package/src/app/directives/tooltip.directive.spec.ts +4 -8
  123. package/src/app/modals/confirm-close/confirm-close.component.html +8 -20
  124. package/src/app/modals/confirm-close/confirm-close.component.scss +0 -3
  125. package/src/app/modals/confirm-close/confirm-close.component.spec.ts +4 -13
  126. package/src/app/modals/confirm-close/confirm-close.component.ts +1 -8
  127. package/src/app/pipe/html-entites-encode.pipe.spec.ts +2 -35
  128. package/src/app/pipe/marked.pipe.spec.ts +2 -38
  129. package/src/app/pipe/marked.pipe.ts +41 -51
  130. package/src/app/providers/app-config.service.ts +2 -4
  131. package/src/app/providers/brand.service.spec.ts +2 -23
  132. package/src/app/providers/brand.service.ts +1 -1
  133. package/src/app/providers/global-settings.service.spec.ts +14 -1009
  134. package/src/app/providers/global-settings.service.ts +2 -82
  135. package/src/app/providers/translator.service.ts +6 -26
  136. package/src/app/sass/_variables.scss +0 -3
  137. package/src/app/sass/animations.scss +1 -19
  138. package/src/app/utils/globals.ts +1 -21
  139. package/src/app/utils/utils-resources.ts +1 -1
  140. package/src/assets/i18n/en.json +99 -106
  141. package/src/assets/i18n/es.json +100 -107
  142. package/src/assets/i18n/fr.json +100 -107
  143. package/src/assets/i18n/it.json +98 -107
  144. package/src/assets/twp/index-dev.html +0 -18
  145. package/src/chat21-core/models/message.ts +1 -2
  146. package/src/chat21-core/providers/firebase/firebase-conversation-handler.ts +2 -3
  147. package/src/chat21-core/providers/mqtt/mqtt-conversation-handler.ts +0 -12
  148. package/src/chat21-core/providers/scripts/script.service.spec.ts +2 -12
  149. package/src/chat21-core/providers/tiledesk/tiledesk-requests.service.ts +1 -1
  150. package/src/chat21-core/utils/utils-message.ts +0 -7
  151. package/src/chat21-core/utils/utils.ts +2 -5
  152. package/src/widget-config-template.json +1 -4
  153. package/src/widget-config.json +1 -4
  154. package/tsconfig.json +0 -5
  155. package/.angular-mcp-cache/package.json +0 -1
  156. package/.cursor/angular18-accessibility-auditor-skill.md +0 -442
  157. package/.cursor/mcp.json +0 -15
  158. package/.github/workflows/build.yml +0 -22
  159. package/.github/workflows/playwright.yml +0 -27
  160. package/mocks/voice-websocket-mock/server.cjs +0 -245
  161. package/playwright.config.ts +0 -41
  162. package/src/app/component/conversation-detail/stream-audio-spectrum/stream-audio-spectrum.component.html +0 -46
  163. package/src/app/component/conversation-detail/stream-audio-spectrum/stream-audio-spectrum.component.scss +0 -83
  164. package/src/app/component/conversation-detail/stream-audio-spectrum/stream-audio-spectrum.component.ts +0 -192
  165. package/src/app/component/form/prechat-form-test-mock.ts +0 -35
  166. package/src/app/component/message/audio-sync/audio-sync.component.html +0 -18
  167. package/src/app/component/message/audio-sync/audio-sync.component.scss +0 -65
  168. package/src/app/component/message/audio-sync/audio-sync.component.spec.ts +0 -103
  169. package/src/app/component/message/audio-sync/audio-sync.component.ts +0 -643
  170. package/src/app/providers/tts-audio-playback-coordinator.service.spec.ts +0 -117
  171. package/src/app/providers/tts-audio-playback-coordinator.service.ts +0 -109
  172. package/src/app/providers/voice/STT&TTS/openai-voice.config.ts +0 -12
  173. package/src/app/providers/voice/STT&TTS/openai-voice.provider.ts +0 -171
  174. package/src/app/providers/voice/STT&TTS/speech-provider.abstract.ts +0 -39
  175. package/src/app/providers/voice/audio.types.ts +0 -40
  176. package/src/app/providers/voice/vad.service.spec.ts +0 -28
  177. package/src/app/providers/voice/vad.service.ts +0 -70
  178. package/src/app/providers/voice/voice-streaming.service.spec.ts +0 -23
  179. package/src/app/providers/voice/voice-streaming.service.ts +0 -702
  180. package/src/app/providers/voice/voice-streaming.types.ts +0 -112
  181. package/src/app/providers/voice/voice.service.spec.ts +0 -227
  182. package/src/app/providers/voice/voice.service.ts +0 -973
  183. package/src/app/shims/onnxruntime-web-wasm.ts +0 -4
  184. package/src/assets/onnx/ort-wasm-simd-threaded.mjs +0 -59
  185. package/src/assets/onnx/ort-wasm-simd-threaded.wasm +0 -0
  186. package/src/assets/sounds/keyboard.mp3 +0 -0
  187. package/src/assets/twp/tiledesk_widget_files/widget-css-override-example.css +0 -14
  188. package/src/assets/vad/silero_vad_legacy.onnx +0 -0
  189. package/src/assets/vad/vad.worklet.bundle.min.js +0 -1
  190. package/src/chat21-core/providers/chat-manager.spec.ts +0 -72
  191. package/tests/widget-form-rich.spec.ts +0 -67
  192. package/tests/widget-index-dev-settings.spec.ts +0 -52
  193. package/tests/widget-twp-iframe.spec.ts +0 -39
@@ -42,17 +42,20 @@
42
42
  transition: background-color .6s ease;
43
43
  .icon-button-action {
44
44
  position: absolute;
45
- top: -1px;
46
- right: 1px;
45
+ top: 50%;
46
+ right: 8px;
47
+ transform: translateY(-50%);
47
48
  svg {
48
- fill: var(--buttonTextColor);
49
+ fill: var(--textColor);
49
50
  }
50
51
  }
51
52
  .icon-button-action-self{
52
53
  position: absolute;
53
- right: 1px;
54
+ top: 50%;
55
+ right: 8px;
56
+ transform: translateY(-50%);
54
57
  svg {
55
- fill: var(--buttonTextColor);
58
+ fill: var(--textColor);
56
59
  }
57
60
  }
58
61
  &:focus,
@@ -1,6 +1,4 @@
1
- import { SimpleChange } from '@angular/core';
2
- import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
3
- import { By } from '@angular/platform-browser';
1
+ import { async, ComponentFixture, TestBed } from '@angular/core/testing';
4
2
 
5
3
  import { LinkButtonComponent } from './link-button.component';
6
4
 
@@ -8,63 +6,20 @@ describe('LinkButtonComponent', () => {
8
6
  let component: LinkButtonComponent;
9
7
  let fixture: ComponentFixture<LinkButtonComponent>;
10
8
 
11
- beforeEach(waitForAsync(() => {
9
+ beforeEach(async(() => {
12
10
  TestBed.configureTestingModule({
13
- declarations: [LinkButtonComponent],
14
- }).compileComponents();
11
+ declarations: [ LinkButtonComponent ]
12
+ })
13
+ .compileComponents();
15
14
  }));
16
15
 
17
16
  beforeEach(() => {
18
17
  fixture = TestBed.createComponent(LinkButtonComponent);
19
18
  component = fixture.componentInstance;
20
- component.button = { value: 'Open', link: 'https://x.test', target: 'blank' };
21
19
  fixture.detectChanges();
22
20
  });
23
21
 
24
22
  it('should create', () => {
25
23
  expect(component).toBeTruthy();
26
24
  });
27
-
28
- it('ngOnChanges should set CSS variables on .url', () => {
29
- component.fontSize = '12px';
30
- component.backgroundColor = '#aaa';
31
- component.textColor = '#bbb';
32
- component.hoverBackgroundColor = '#ccc';
33
- component.hoverTextColor = '#ddd';
34
- component.ngOnChanges({
35
- fontSize: new SimpleChange(null, '12px', true),
36
- });
37
- const el = fixture.nativeElement.querySelector('.url') as HTMLElement;
38
- expect(el.style.getPropertyValue('--buttonFontSize').trim()).toBe('12px');
39
- });
40
-
41
- it('actionButtonUrl should emit when link set', () => {
42
- spyOn(component.onButtonClicked, 'emit');
43
- component.actionButtonUrl();
44
- expect(component.onButtonClicked.emit).toHaveBeenCalled();
45
- });
46
-
47
- it('actionButtonUrl should not emit when link empty', () => {
48
- component.button = { value: 'x', link: '' };
49
- spyOn(component.onButtonClicked, 'emit');
50
- component.actionButtonUrl();
51
- expect(component.onButtonClicked.emit).not.toHaveBeenCalled();
52
- });
53
-
54
- it('should render external icon when target is not self', () => {
55
- component.button = { value: 'L', link: 'u', target: 'blank' };
56
- fixture.detectChanges();
57
- expect(fixture.debugElement.query(By.css('.icon-button-action'))).toBeTruthy();
58
- });
59
-
60
- it('should render self icon when target is self', () => {
61
- component.button = { value: 'L', link: 'u', target: 'self' };
62
- fixture.detectChanges();
63
- expect(fixture.debugElement.query(By.css('.icon-button-action-self'))).toBeTruthy();
64
- });
65
-
66
- it('mouseover and mouseout should not throw', () => {
67
- expect(() => component.onMouseOver({} as any)).not.toThrow();
68
- expect(() => component.onMouseOut({} as any)).not.toThrow();
69
- });
70
25
  });
@@ -1,6 +1,4 @@
1
- import { SimpleChange } from '@angular/core';
2
- import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
3
- import { By } from '@angular/platform-browser';
1
+ import { async, ComponentFixture, TestBed } from '@angular/core/testing';
4
2
 
5
3
  import { TextButtonComponent } from './text-button.component';
6
4
 
@@ -8,57 +6,20 @@ describe('TextButtonComponent', () => {
8
6
  let component: TextButtonComponent;
9
7
  let fixture: ComponentFixture<TextButtonComponent>;
10
8
 
11
- beforeEach(waitForAsync(() => {
9
+ beforeEach(async(() => {
12
10
  TestBed.configureTestingModule({
13
- declarations: [TextButtonComponent],
14
- }).compileComponents();
11
+ declarations: [ TextButtonComponent ]
12
+ })
13
+ .compileComponents();
15
14
  }));
16
15
 
17
16
  beforeEach(() => {
18
17
  fixture = TestBed.createComponent(TextButtonComponent);
19
18
  component = fixture.componentInstance;
20
- component.button = { value: 'Reply' };
21
- component.isConversationArchived = false;
22
19
  fixture.detectChanges();
23
20
  });
24
21
 
25
22
  it('should create', () => {
26
23
  expect(component).toBeTruthy();
27
24
  });
28
-
29
- it('ngOnChanges should set CSS variables on .text', () => {
30
- component.fontSize = '13px';
31
- component.backgroundColor = '#111';
32
- component.textColor = '#222';
33
- component.hoverBackgroundColor = '#333';
34
- component.hoverTextColor = '#444';
35
- component.ngOnChanges({
36
- fontSize: new SimpleChange(null, '13px', true),
37
- });
38
- const el = fixture.nativeElement.querySelector('.text') as HTMLElement;
39
- expect(el.style.getPropertyValue('--buttonFontSize').trim()).toBe('13px');
40
- });
41
-
42
- it('actionButtonText should emit click payload', () => {
43
- spyOn(component.onButtonClicked, 'emit');
44
- component.actionButtonText();
45
- expect(component.onButtonClicked.emit).toHaveBeenCalled();
46
- });
47
-
48
- it('click on template should invoke actionButtonText', () => {
49
- spyOn(component, 'actionButtonText');
50
- fixture.debugElement.query(By.css('.text')).triggerEventHandler('click', {});
51
- expect(component.actionButtonText).toHaveBeenCalled();
52
- });
53
-
54
- it('should add disabled class when conversation archived', () => {
55
- component.isConversationArchived = true;
56
- fixture.detectChanges();
57
- expect(fixture.nativeElement.querySelector('.text').classList.contains('disabled')).toBe(true);
58
- });
59
-
60
- it('mouseover and mouseout should not throw', () => {
61
- expect(() => component.onMouseOver({} as any)).not.toThrow();
62
- expect(() => component.onMouseOut({} as any)).not.toThrow();
63
- });
64
25
  });
@@ -1,28 +1,19 @@
1
- <div class="wrapper"
2
- role="region"
3
- aria-roledescription="carousel"
4
- [attr.aria-label]="translationMap?.get('CAROUSEL_LABEL') || 'Cards carousel'">
5
- <button type="button"
6
- id="left"
7
- class="arrow left c21-button-clean"
8
- [attr.aria-label]="translationMap?.get('CAROUSEL_PREVIOUS') || 'Previous slide'"
9
- (click)="goTo('previous')" *ngIf="activeElement > 1">
10
- <svg aria-hidden="true" focusable="false" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000">
1
+ <div class="wrapper">
2
+ <div id="left" class="arrow left" (click)="goTo('previous')" *ngIf="activeElement > 1">
3
+ <svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000">
11
4
  <path d="M0 0h24v24H0V0z" fill="none"/><path d="M15.41 7.41L14 6l-6 6 6 6 1.41-1.41L10.83 12l4.58-4.59z"/>
12
5
  </svg>
13
- </button>
6
+ </div>
14
7
  <div class="carousel">
15
- <div class="card"
16
- *ngFor="let card of gallery; let i = index"
17
- role="group"
18
- aria-roledescription="slide"
19
- [attr.aria-label]="getSlideLabel(i + 1, gallery?.length)">
8
+ <!-- <div class="card" style="width: 17px;"></div> -->
9
+ <div class="card" *ngFor="let card of gallery; let i = index">
10
+ <!-- <div [style.opacity]="i+1 === activeElement? 1: 0.5"> -->
20
11
  <div>
21
12
  <div class="card-image" *ngIf="card?.preview?.src !== ''">
22
- <img [src]="card?.preview?.src" [attr.alt]="card?.title || 'Carousel image'" draggable="false">
13
+ <img [src]="card?.preview?.src" alt="img" draggable="false">
23
14
  </div>
24
15
  <div class="card-image card-image-placeholder" *ngIf="card?.preview?.src == ''">
25
- <img src="assets/images/icons/no-image.svg" alt="" draggable="false">
16
+ <img src="assets/images/icons/no-image.svg" alt="img" draggable="false">
26
17
  <span>Image not available</span>
27
18
  </div>
28
19
  <div class="card-content">
@@ -30,24 +21,20 @@
30
21
  <div class="card-description">{{card?.description}}</div>
31
22
  </div>
32
23
  <div class="buttons" *ngIf="card?.buttons && card?.buttons.length > 0">
33
- <button type="button" *ngFor="let button of card?.buttons"
34
- class="single-button action c21-button-clean"
24
+ <div *ngFor="let button of card?.buttons"
25
+ class="single-button action"
35
26
  [ngClass]="{'disabled': (isConversationArchived || (!isLastMessage && button.type !== TYPE_BUTTON.URL)), 'active': button?.active}"
36
- [attr.aria-label]="button?.value"
37
27
  (click)="actionButtonClick($event, button, i)" >
38
28
  {{button.value}}
39
- </button>
29
+ </div>
40
30
  </div>
41
31
  </div>
42
32
  </div>
33
+ <!-- <div class="card" style="width: 17px;"></div> -->
43
34
  </div>
44
- <button type="button"
45
- id="right"
46
- class="arrow right c21-button-clean"
47
- [attr.aria-label]="translationMap?.get('CAROUSEL_NEXT') || 'Next slide'"
48
- (click)="goTo('next')" *ngIf="activeElement !== gallery.length">
49
- <svg aria-hidden="true" focusable="false" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000">
35
+ <div id="right" class="arrow right" (click)="goTo('next')" *ngIf="activeElement !== gallery.length">
36
+ <svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000">
50
37
  <path d="M0 0h24v24H0V0z" fill="none"/><path d="M10.02 6L8.61 7.41 13.19 12l-4.58 4.59L10.02 18l6-6-6-6z"/>
51
38
  </svg>
52
- </button>
39
+ </div>
53
40
  </div>
@@ -24,17 +24,13 @@
24
24
  font-size: 14px;
25
25
  // margin: 0 25px;
26
26
  }
27
- .wrapper button.arrow {
27
+ .wrapper div.arrow {
28
28
  top: 50%;
29
29
  height: 40px;
30
30
  width: 40px;
31
31
  cursor: pointer;
32
32
  position: absolute;
33
33
  background: #fff;
34
- border: none;
35
- padding: 0;
36
- -webkit-appearance: none;
37
- appearance: none;
38
34
  border-radius: 50%;
39
35
  box-shadow: 0 3px 6px rgba(0,0,0,0.23);
40
36
  transform: translateY(-50%);
@@ -45,13 +41,13 @@
45
41
  justify-content: center;
46
42
  align-items: center;
47
43
  }
48
- .wrapper button.arrow:active{
44
+ .wrapper div.arrow:active{
49
45
  transform: translateY(-50%) scale(0.85);
50
46
  }
51
- .wrapper button.arrow:first-child{
47
+ .wrapper div.arrow:first-child{
52
48
  left: -22px;
53
49
  }
54
- .wrapper button.arrow:last-child{
50
+ .wrapper div.arrow:last-child{
55
51
  right: -22px;
56
52
  }
57
53
  .wrapper .carousel{
@@ -196,18 +192,13 @@
196
192
 
197
193
 
198
194
  .single-button{
199
- width: 100%;
200
- box-sizing: border-box;
201
- margin: 0;
202
- border: none;
203
- border-top: 1px dashed rgba(0, 0, 0, 0.08);
204
- text-align: center;
205
- -webkit-appearance: none;
206
- appearance: none;
195
+ // border-top-color: rgb(219, 225, 232);
196
+
207
197
  -webkit-box-align: center;
208
198
  -ms-flex-align: center;
209
199
  align-items: center;
210
200
  justify-content: center;
201
+ border-top: 1px dashed rgba(0, 0, 0, 0.08);
211
202
  cursor: pointer;
212
203
  display: -webkit-box;
213
204
  display: -ms-flexbox;
@@ -231,6 +222,7 @@
231
222
  line-height: 16px;
232
223
  padding: 8px 16px!important;
233
224
 
225
+ &:focus,
234
226
  &:hover {
235
227
  color: var(--hoverTextColor);
236
228
  background: var(--hoverBackgroundColor);
@@ -241,10 +233,6 @@
241
233
  }
242
234
  }
243
235
  }
244
- &:focus-visible {
245
- outline: 2px solid var(--hoverBackgroundColor, #1565c0);
246
- outline-offset: 2px;
247
- }
248
236
  &:after {
249
237
  content: "";
250
238
  position: absolute;
@@ -1,6 +1,4 @@
1
1
  import { ComponentFixture, TestBed } from '@angular/core/testing';
2
- import { By } from '@angular/platform-browser';
3
- import { TYPE_BUTTON } from 'src/chat21-core/utils/constants';
4
2
 
5
3
  import { CarouselComponent } from './carousel.component';
6
4
 
@@ -8,93 +6,18 @@ describe('CarouselComponent', () => {
8
6
  let component: CarouselComponent;
9
7
  let fixture: ComponentFixture<CarouselComponent>;
10
8
 
11
- const btnA = { type: TYPE_BUTTON.TEXT, value: 'Go', action: '', link: '', text: 'x', active: false };
12
- const gallery = [
13
- { preview: { src: 'https://a/img.png' }, title: 'A', description: 'da', buttons: [btnA] },
14
- { preview: { src: 'https://b/img.png' }, title: 'B', description: 'db', buttons: [{ type: TYPE_BUTTON.TEXT, value: 'B2', action: 'a', text: 't', active: false }] },
15
- ];
16
-
17
9
  beforeEach(async () => {
18
10
  await TestBed.configureTestingModule({
19
- declarations: [CarouselComponent],
20
- }).compileComponents();
11
+ declarations: [ CarouselComponent ]
12
+ })
13
+ .compileComponents();
21
14
 
22
15
  fixture = TestBed.createComponent(CarouselComponent);
23
16
  component = fixture.componentInstance;
24
- component.message = { attributes: { attachment: { gallery } } } as any;
25
- component.gallery = gallery;
26
- component.stylesMap = new Map<string, string>([
27
- ['buttonFontSize', '14px'],
28
- ['buttonBackgroundColor', '#111'],
29
- ['buttonTextColor', '#222'],
30
- ['buttonHoverBackgroundColor', '#333'],
31
- ['buttonHoverTextColor', '#444'],
32
- ]);
33
- component.isConversationArchived = false;
34
- component.isLastMessage = true;
35
17
  fixture.detectChanges();
36
18
  });
37
19
 
38
20
  it('should create', () => {
39
21
  expect(component).toBeTruthy();
40
- expect(component.gallery.length).toBe(2);
41
- });
42
-
43
- it('ngOnChanges should apply style map to wrapper', () => {
44
- component.ngOnChanges({
45
- stylesMap: {
46
- previousValue: undefined,
47
- currentValue: component.stylesMap,
48
- firstChange: true,
49
- isFirstChange: () => true,
50
- },
51
- });
52
- const wrap = fixture.nativeElement.querySelector('.wrapper') as HTMLElement;
53
- expect(wrap.style.getPropertyValue('--buttonFontSize').trim()).toBe('14px');
54
- });
55
-
56
- it('goTo should bump activeElement for next and previous', () => {
57
- const carouselEl = component.carousel;
58
- spyOnProperty(carouselEl, 'offsetWidth', 'get').and.returnValue(400);
59
- const cards = carouselEl.querySelectorAll('.card');
60
- expect(cards.length).toBeGreaterThanOrEqual(2);
61
- const card1 = cards[1] as HTMLElement;
62
- spyOnProperty(card1, 'offsetWidth', 'get').and.returnValue(180);
63
- const startEl = component.activeElement;
64
- component.goTo('next');
65
- expect(component.activeElement).toBe(startEl + 1);
66
- component.goTo('previous');
67
- expect(component.activeElement).toBe(startEl);
68
- });
69
-
70
- it('actionButtonClick should emit onAttachmentButtonClicked', () => {
71
- spyOn(component.onAttachmentButtonClicked, 'emit');
72
- const ev = { target: { classList: { add: jasmine.createSpy('add') } } };
73
- component.gallery = gallery;
74
- component.actionButtonClick(ev as any, btnA, 0);
75
- expect(component.onAttachmentButtonClicked.emit).toHaveBeenCalled();
76
- expect(btnA.active).toBe(true);
77
- });
78
-
79
- it('actionButtonClick should ignore empty button', () => {
80
- spyOn(component.onAttachmentButtonClicked, 'emit');
81
- const empty = { type: TYPE_BUTTON.TEXT, value: '', action: '', link: '', text: '', active: false };
82
- component.actionButtonClick({ target: { classList: { add: () => {} } } } as any, empty, 0);
83
- expect(component.onAttachmentButtonClicked.emit).not.toHaveBeenCalled();
84
- });
85
-
86
- it('arrow clicks should invoke goTo', () => {
87
- spyOn(component, 'goTo');
88
- component.activeElement = 1;
89
- component.gallery = gallery;
90
- fixture.detectChanges();
91
- const right = fixture.debugElement.query(By.css('.arrow.right'));
92
- expect(right).toBeTruthy();
93
- right!.triggerEventHandler('click', {});
94
- expect(component.goTo).toHaveBeenCalledWith('next');
95
- });
96
-
97
- it('TYPE_BUTTON should be exposed for template', () => {
98
- expect(component.TYPE_BUTTON).toBe(TYPE_BUTTON);
99
22
  });
100
23
  });
@@ -15,7 +15,6 @@ export class CarouselComponent implements OnInit{
15
15
  @Input() isConversationArchived: boolean;
16
16
  @Input() isLastMessage: boolean;
17
17
  @Input() stylesMap: Map<string, string>;
18
- @Input() translationMap: Map<string, string>;
19
18
  @Output() onAttachmentButtonClicked = new EventEmitter<any>();
20
19
  @Output() onElementRendered = new EventEmitter<{element: string, status: boolean}>()
21
20
  // ========= end:: Input/Output values ============//
@@ -109,21 +108,6 @@ export class CarouselComponent implements OnInit{
109
108
 
110
109
  }
111
110
 
112
- /**
113
- * Builds an accessible label for each carousel slide, e.g. "Slide 2 of 5".
114
- * Uses the i18n template `CAROUSEL_SLIDE_LABEL` if available, otherwise falls back to English.
115
- */
116
- getSlideLabel(current: number, total: number): string {
117
- const template = this.translationMap?.get('CAROUSEL_SLIDE_LABEL');
118
- const safeTotal = total || 0;
119
- if (template && typeof template === 'string') {
120
- return template
121
- .replace('{current}', String(current))
122
- .replace('{total}', String(safeTotal));
123
- }
124
- return `Slide ${current} of ${safeTotal}`;
125
- }
126
-
127
111
  actionButtonClick(ev, button, index){
128
112
  this.button = button
129
113
  this.type = button.type
@@ -1,13 +1,8 @@
1
- <div [ngStyle]="{ 'max-width': '100%', 'width': width, 'height': height }">
2
- <div *ngIf="loading" class="loader" [ngStyle]="{ 'width': width, 'height': height }"></div>
3
- <iframe allowfullscreen
1
+ <div [ngStyle] = "{ 'max-width': '100%', 'width': width, 'height': height }">
2
+ <div *ngIf="loading" class="loader" [ngStyle] = "{ 'width': width , 'height': height }"></div>
3
+ <iframe allowfullscreen
4
4
  [ngClass]="{'isLoadingImage': loading}"
5
- width="100%" height="100%"
6
- [title]="frameTitle"
5
+ width = "100%" height = "100%"
7
6
  [src]="url"
8
- sandbox="allow-scripts allow-same-origin allow-popups allow-forms allow-popups-to-escape-sandbox"
9
- referrerpolicy="strict-origin-when-cross-origin"
10
- loading="lazy"
11
7
  (load)="onLoaded($event)"></iframe>
12
8
  </div>
13
-
@@ -1,4 +1,4 @@
1
- import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
1
+ import { async, ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
2
2
  import { DomSanitizer } from '@angular/platform-browser';
3
3
 
4
4
  import { FrameComponent } from './frame.component';
@@ -9,48 +9,29 @@ describe('FrameComponent', () => {
9
9
 
10
10
  beforeEach(waitForAsync(() => {
11
11
  TestBed.configureTestingModule({
12
- declarations: [FrameComponent],
13
- }).compileComponents();
12
+ declarations: [ FrameComponent ],
13
+ providers: [
14
+ {
15
+ provide: DomSanitizer,
16
+ useValue: {
17
+ sanitize: () => 'safeString',
18
+ bypassSecurityTrustResourceUrl: () => 'safeString'
19
+ }
20
+ }
21
+ ,]
22
+ })
23
+ .compileComponents();
14
24
  }));
15
25
 
16
26
  beforeEach(() => {
17
27
  fixture = TestBed.createComponent(FrameComponent);
18
28
  component = fixture.componentInstance;
29
+ fixture.detectChanges();
19
30
  });
20
31
 
21
32
  it('should create', () => {
22
- component.metadata = { src: 'https://www.example.com/embed' };
23
- component.width = 400;
24
- component.height = 300;
33
+ component.url = component['sanitizer'].bypassSecurityTrustResourceUrl('http://www.tiledesk.com');
25
34
  fixture.detectChanges();
26
35
  expect(component).toBeTruthy();
27
36
  });
28
-
29
- it('ngOnInit should trust resource URL when metadata.src present', () => {
30
- component.metadata = { src: 'https://player.example/v/1' };
31
- component.ngOnInit();
32
- expect(component.url).toBeTruthy();
33
- expect((component.url as any).changingThisBreaksApplicationSecurity).toBe('https://player.example/v/1');
34
- });
35
-
36
- it('ngOnInit should leave url null when metadata missing src', () => {
37
- component.metadata = {};
38
- component.ngOnInit();
39
- expect(component.url).toBeNull();
40
- });
41
-
42
- it('onLoaded should clear loading and emit', () => {
43
- spyOn(component.onElementRendered, 'emit');
44
- component.loading = true;
45
- component.onLoaded({} as any);
46
- expect(component.loading).toBe(false);
47
- expect(component.onElementRendered.emit).toHaveBeenCalledWith({ element: 'frame', status: true });
48
- });
49
-
50
- it('ngOnDestroy should clear trusted url', () => {
51
- const sanitizer = TestBed.inject(DomSanitizer);
52
- component.url = sanitizer.bypassSecurityTrustResourceUrl('https://x');
53
- component.ngOnDestroy();
54
- expect(component.url).toBeNull();
55
- });
56
37
  });
@@ -15,19 +15,14 @@ export class FrameComponent implements OnInit {
15
15
 
16
16
  url: SafeResourceUrl = null
17
17
  loading: boolean = true
18
- frameTitle: string = 'Embedded content'
19
18
  constructor(private sanitizer: DomSanitizer) { }
20
19
 
21
20
  ngOnInit() {
22
21
  if(this.metadata && this.metadata.src){
23
22
  this.url = this.sanitizer.bypassSecurityTrustResourceUrl(this.metadata.src);
24
- try {
25
- const host = new URL(this.metadata.src).hostname;
26
- this.frameTitle = `Embedded content from ${host}`;
27
- } catch (_) {
28
- this.frameTitle = 'Embedded content';
29
- }
30
23
  }
24
+ // this.width = this.getSizeImg(this.metadata).width;
25
+ // this.height = this.getSizeImg(this.metadata).height;
31
26
  }
32
27
 
33
28
  ngOnDestroy(){
@@ -2,4 +2,4 @@
2
2
  Security: render HTML messages as plain text (no DOM interpretation).
3
3
  This preserves the exact input (e.g. "<h1>...</h1>") including line breaks.
4
4
  -->
5
- <pre class="htmlCode" #htmlCode [textContent]="htmlText"></pre>
5
+ <pre id="htmlCode" #htmlCode [textContent]="htmlText"></pre>
@@ -77,7 +77,7 @@
77
77
  }
78
78
 
79
79
  /* Render raw HTML source safely */
80
- .htmlCode {
80
+ #htmlCode {
81
81
  white-space: pre-wrap; /* preserve newlines, allow wrapping */
82
82
  word-break: break-word;
83
83
  margin: 0;
@@ -1,7 +1,6 @@
1
- import { SimpleChange } from '@angular/core';
1
+ import { SafeHtmlPipe } from './../../../pipe/safe-html.pipe';
2
2
  import { waitForAsync, ComponentFixture, TestBed } from '@angular/core/testing';
3
3
 
4
- import { SafeHtmlPipe } from './../../../pipe/safe-html.pipe';
5
4
  import { HtmlComponent } from './html.component';
6
5
 
7
6
  describe('HtmlComponent', () => {
@@ -10,8 +9,12 @@ describe('HtmlComponent', () => {
10
9
 
11
10
  beforeEach(waitForAsync(() => {
12
11
  TestBed.configureTestingModule({
13
- declarations: [HtmlComponent, SafeHtmlPipe],
14
- }).compileComponents();
12
+ declarations: [
13
+ HtmlComponent,
14
+ SafeHtmlPipe
15
+ ]
16
+ })
17
+ .compileComponents();
15
18
  }));
16
19
 
17
20
  beforeEach(() => {
@@ -23,24 +26,4 @@ describe('HtmlComponent', () => {
23
26
  it('should create', () => {
24
27
  expect(component).toBeTruthy();
25
28
  });
26
-
27
- it('ngOnChanges should set host CSS variables from inputs', () => {
28
- component.fontSize = '15px';
29
- component.themeColor = '#abc';
30
- component.foregroundColor = '#def';
31
- component.ngOnChanges({
32
- fontSize: new SimpleChange(null, '15px', true),
33
- });
34
- const host = fixture.nativeElement as HTMLElement;
35
- expect(host.style.getPropertyValue('--buttonFontSize').trim()).toBe('15px');
36
- expect(host.style.getPropertyValue('--themeColor').trim()).toBe('#abc');
37
- expect(host.style.getPropertyValue('--foregroundColor').trim()).toBe('#def');
38
- });
39
-
40
- it('should render htmlText as plain text in pre (XSS-safe)', () => {
41
- component.htmlText = '<script>bad()</script>';
42
- fixture.detectChanges();
43
- const pre = fixture.nativeElement.querySelector('pre');
44
- expect(pre?.textContent).toContain('<script>');
45
- });
46
29
  });
@@ -1,14 +1,12 @@
1
+ <!-- [ngStyle] = "{ 'max-width': width +'px', 'max-height': height +'px' }" style="position: relative; text-align: center; margin: auto"-->
1
2
  <div class="c21-img-container">
2
- <button type="button"
3
- class="c21-image-trigger c21-button-clean"
4
- [attr.aria-label]="metadata?.name ? ('Open image: ' + metadata.name) : 'Open image preview'"
5
- (click)="onClickImage()">
6
- <img
7
- class="message-contentX message-content-imageX"
8
- [alt]="metadata?.name || ''"
9
- [ngClass]="{'isLoadingImage': loading}"
10
- [ngStyle]="{ 'width': width + 'px', 'height': height+'px' }"
11
- [src]="metadata.src"
12
- (load)="onLoaded($event)"/>
13
- </button>
3
+ <img
4
+ class="message-contentX message-content-imageX"
5
+ [alt]="metadata?.name"
6
+ [ngClass]="{'isLoadingImage': loading}"
7
+ [ngStyle] = "{ 'width': width + 'px', 'height': height+'px' }"
8
+ [src]="metadata.src"
9
+ (load)="onLoaded($event)"
10
+ (click)="onClickImage()"/>
11
+ <!-- downloadImage(metadata.src, metadata.name); -->
14
12
  </div>