@chat21/chat21-web-widget 5.1.12 → 5.1.14

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.
package/CHANGELOG.md CHANGED
@@ -6,10 +6,21 @@
6
6
  ### **Copyrigth**:
7
7
  *Tiledesk SRL*
8
8
 
9
+
10
+ # 5.1.14
11
+ - **bug-fixed**: stopped loading local language json file
12
+
13
+ # 5.1.13
14
+ - **bug-fixed**: set default widget size
15
+ - **changed**: Updated the translations of the tooltips in the footer-component
16
+ - **changed**: Refactored the network-offline component and made it generic for displaying errors (now error-alert.component)
17
+ - **bug-fixed**: set the color of the buttons with visibility control to the font color (setButtonColors function)
18
+
9
19
  # 5.1.12
10
20
  - **bug-fixed**: check showEmojiFooterButton to enable/disable emojii
11
21
  - **bug-fixed**: markdown is fired as an emojii and blocked by isEmojii check fn
12
22
 
23
+
13
24
  # 5.1.7-rc5
14
25
  - **bug-fixed**: bug fixed BUTTON STYLES
15
26
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@chat21/chat21-web-widget",
3
3
  "author": "Tiledesk SRL",
4
- "version": "5.1.12",
4
+ "version": "5.1.14",
5
5
  "license": "MIT",
6
6
  "homepage": "https://www.tiledesk.com",
7
7
  "repository": {
@@ -105,11 +105,11 @@
105
105
 
106
106
  <!--
107
107
  ****************************************
108
- ******* MODALE OFFLINE NETWORK *********
108
+ ********* MODALE ERROR ALERT ***********
109
109
  ****************************************
110
110
  -->
111
- <div *ngIf="g.isOpen && !isOnline" class="modal-page star-rating-widget active">
112
- <chat-network-offline></chat-network-offline>
111
+ <div *ngIf="g.isOpen && isShowErrorMessage" class="modal-page star-rating-widget active">
112
+ <chat-error-alert [errorMessage]="errorMessage" [errorKeyMessage]="errorKeyMessage" [errorParams]="errorParams"></chat-error-alert>
113
113
  </div>
114
114
 
115
115
  </div>
@@ -571,7 +571,7 @@ chat-root {
571
571
  /***************************
572
572
  ***** NETWORK OFFLINE ******
573
573
  ****************************/
574
- chat-network-offline {
574
+ chat-error-alert {
575
575
  position: absolute;
576
576
  width: 100%;
577
577
  height: 100%;
@@ -106,9 +106,12 @@ export class AppComponent implements OnInit, AfterViewInit, OnDestroy {
106
106
 
107
107
  forceDisconnect: boolean = false;
108
108
 
109
- //network status
110
- isOnline: boolean = true;
111
-
109
+ // alert error message
110
+ isShowErrorMessage: boolean = false;
111
+ errorMessage: string = '';
112
+ errorKeyMessage: string = null;
113
+ errorParams: Record<string, any> = {};
114
+
112
115
  private logger: LoggerService = LoggerInstance.getInstance();
113
116
  constructor(
114
117
  private el: ElementRef,
@@ -398,9 +401,11 @@ export class AppComponent implements OnInit, AfterViewInit, OnDestroy {
398
401
  this.triggerLoadParamsEvent(); // first trigger
399
402
  //this.setAvailableAgentsStatus();
400
403
 
401
-
402
404
  /** NETWORK STATUS */
403
- this.listenToNetworkStatus()
405
+ this.listenToNetworkStatus();
406
+
407
+ /** SET WIDGET SIZE */
408
+ this.onWidgetSizeChange(this.g.size);
404
409
 
405
410
  }
406
411
 
@@ -724,7 +729,7 @@ export class AppComponent implements OnInit, AfterViewInit, OnDestroy {
724
729
  // visualizzo l'iframe!!!
725
730
  this.triggerOnViewInit();
726
731
  this.g.setParentBodyStyleMobile(this.g.isOpen, this.g.isMobile);
727
- this.g.setElementStyle(this.g.isOpen)
732
+ this.g.setElementStyle(this.g.isOpen);
728
733
  // this.triggerOnAuthStateChanged(true)
729
734
  // mostro il widget
730
735
  // setTimeout(() => {
@@ -2215,8 +2220,23 @@ export class AppComponent implements OnInit, AfterViewInit, OnDestroy {
2215
2220
  }
2216
2221
 
2217
2222
  private listenToNetworkStatus(){
2218
- window.addEventListener('online', () => this.isOnline = true);
2219
- window.addEventListener('offline', () => this.isOnline = false);
2223
+ window.addEventListener('online', () => {
2224
+ this.isShowErrorMessage = false;
2225
+ this.errorMessage = null;
2226
+ this.errorKeyMessage = null;
2227
+ });
2228
+ window.addEventListener('offline', () => {
2229
+ this.isShowErrorMessage = true;
2230
+ this.errorMessage = null;
2231
+ this.errorKeyMessage = 'CONNECTION_NETWORK_ERROR';
2232
+ });
2233
+ window.addEventListener('tooltipErrorMessage', (event: CustomEvent) => {
2234
+ // console.log('event-------------------> tooltipErrorMessage', event);
2235
+ this.isShowErrorMessage = event.detail?.error;
2236
+ this.errorKeyMessage = event.detail?.keyMessage || null;
2237
+ this.errorMessage = event.detail?.message || null;
2238
+ this.errorParams = event.detail?.params || {};
2239
+ });
2220
2240
  }
2221
2241
 
2222
2242
  // ========= begin:: DESTROY ALL SUBSCRIPTIONS ============//
@@ -60,6 +60,8 @@ import { environment } from 'src/environments/environment';
60
60
 
61
61
  //THIRD-PART MODULES
62
62
  import { TranslateModule } from '@ngx-translate/core';
63
+ // import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
64
+ // import { TranslateHttpLoader } from '@ngx-translate/http-loader';
63
65
  // import { MomentModule } from 'ngx-moment';
64
66
  import { PickerModule } from '@ctrl/ngx-emoji-mart';
65
67
  import { LoggerModule, NGXLogger, NgxLoggerLevel } from "ngx-logger";
@@ -134,7 +136,7 @@ import { Rules } from './utils/rules';
134
136
  import { ScriptService } from 'src/chat21-core/providers/scripts/script.service';
135
137
  import { CarouselComponent } from './component/message/carousel/carousel.component';
136
138
  import { BrandService } from './providers/brand.service';
137
- import { NetworkOfflineComponent } from './component/network-offline/network-offline.component';
139
+ import { ErrorAlertComponent } from './component/error-alert/error-alert.component';
138
140
  import { ConfirmCloseComponent } from './modals/confirm-close/confirm-close.component';
139
141
 
140
142
 
@@ -204,6 +206,13 @@ export function conversationHandlerFactory(chat21Service: Chat21Service, appConf
204
206
  }
205
207
  }
206
208
 
209
+
210
+
211
+ // ngx-translate Http loader factory
212
+ // export function createTranslateLoader(http: HttpClient) {
213
+ // return new TranslateHttpLoader(http, './assets/i18n/', '.json');
214
+ // }
215
+
207
216
  export function typingFactory(chat21Service: Chat21Service, appConfig: AppConfigService) {
208
217
  const config = appConfig.getConfig()
209
218
  if (config.chatEngine === CHAT_ENGINE_MQTT) {
@@ -300,7 +309,7 @@ export function uploadFactory(http: HttpClient, appConfig: AppConfigService, app
300
309
  LikeUnlikeComponent,
301
310
  TooltipDirective,
302
311
  CarouselComponent,
303
- NetworkOfflineComponent,
312
+ ErrorAlertComponent,
304
313
  ConfirmCloseComponent
305
314
  ],
306
315
  imports: [BrowserModule,
@@ -308,13 +317,13 @@ export function uploadFactory(http: HttpClient, appConfig: AppConfigService, app
308
317
  FormsModule,
309
318
  ReactiveFormsModule,
310
319
  PickerModule,
311
- TranslateModule.forRoot(//),
312
- {
313
- // loader: {
314
- // provide: TranslateLoader,
315
- // useFactory: (createTranslateLoader),
316
- // deps: [HttpClient]
317
- // }
320
+ TranslateModule.forRoot({
321
+ // defaultLanguage: 'en',
322
+ // loader: {
323
+ // provide: TranslateLoader,
324
+ // useFactory: (createTranslateLoader),
325
+ // deps: [HttpClient]
326
+ // }
318
327
  }),
319
328
  LoggerModule.forRoot({
320
329
  level: NgxLoggerLevel.DEBUG,
@@ -23,7 +23,7 @@
23
23
  <span class="v-align-center">
24
24
  <svg role="img" aria-labelledby="altIconTitle" xmlns="http://www.w3.org/2000/svg" width="24px" height="24" viewBox="0 0 24 24" fill="currentColor">
25
25
  <path d="M9.9,22.7c0,0-.1,0-.2,0-1.9.3-3.7-.2-5.2-1.4-3-2.3-3.6-6.4-1.4-9.5L9.5,2.5c.4-.5,1.1-.6,1.6-.3.5.4.6,1.1.3,1.6l-6.5,9.4c-1.4,2-1,4.8.9,6.3,1,.8,2.2,1.1,3.5.9,1.3-.2,2.4-.9,3.1-1.9l6-8.7c.9-1.2.6-3-.6-3.9-.6-.5-1.4-.6-2.1-.5-.8.1-1.4.5-1.9,1.1l-5.8,8.2c-.3.5-.2,1.1.2,1.5.2.2.5.3.8.2.3,0,.6-.2.7-.4l4.7-6.2c.4-.5,1.1-.6,1.6-.2.5.4.6,1.1.2,1.6l-4.7,6.2c-.5.7-1.4,1.2-2.3,1.3-.9.1-1.8-.2-2.5-.7-1.4-1.1-1.6-3.1-.6-4.6l5.8-8.2c.8-1.1,2-1.9,3.4-2.1,1.4-.2,2.7.1,3.8,1,2.2,1.7,2.7,4.8,1.1,7.1l-6,8.7c-1.1,1.5-2.6,2.5-4.4,2.8h0Z"/>
26
- <title id="altIconTitle">{{ attachmentTooltip || translationMap?.get('MAX_ATTACHMENT') }}</title>
26
+ <title id="altIconTitle">{{ 'MAX_ATTACHMENT' | translate: { FILE_SIZE_LIMIT: file_size_limit } }}</title>
27
27
  </svg>
28
28
 
29
29
  </span>
@@ -1,4 +1,6 @@
1
1
  import { Component, ComponentFactoryResolver, ElementRef, EventEmitter, Input, OnChanges, OnInit, Output, SimpleChanges, ViewChild, ViewContainerRef } from '@angular/core';
2
+ import { error } from 'console';
3
+ import { FILE_SIZE_LIMIT } from 'src/app/utils/constants';
2
4
  import { Globals } from 'src/app/utils/globals';
3
5
  import { checkAcceptedFile } from 'src/app/utils/utils';
4
6
  import { MessageModel } from 'src/chat21-core/models/message';
@@ -83,9 +85,10 @@ export class ConversationFooterComponent implements OnInit, OnChanges {
83
85
 
84
86
  showAlertEmoji: boolean = false
85
87
 
86
- file_size_limit: number = 10;
88
+ file_size_limit = FILE_SIZE_LIMIT;
87
89
  attachmentTooltip: string = '';
88
90
 
91
+
89
92
  convertColorToRGBA = convertColorToRGBA;
90
93
  private logger: LoggerService = LoggerInstance.getInstance()
91
94
  constructor(private chatManager: ChatManager,
@@ -93,7 +96,7 @@ export class ConversationFooterComponent implements OnInit, OnChanges {
93
96
  private uploadService: UploadService) { }
94
97
 
95
98
  ngOnInit() {
96
- this.updateAttachmentTooltip();
99
+ // this.updateAttachmentTooltip();
97
100
  }
98
101
 
99
102
 
@@ -109,9 +112,9 @@ export class ConversationFooterComponent implements OnInit, OnChanges {
109
112
  this.onDrop(this.dropEvent)
110
113
  }
111
114
 
112
- if(changes['translationMap'] && changes['translationMap'].currentValue !== undefined){
113
- this.updateAttachmentTooltip();
114
- }
115
+ // if(changes['translationMap'] && changes['translationMap'].currentValue !== undefined){
116
+ // this.updateAttachmentTooltip();
117
+ // }
115
118
 
116
119
  }
117
120
 
@@ -120,24 +123,24 @@ export class ConversationFooterComponent implements OnInit, OnChanges {
120
123
  // setTimeout(() => {
121
124
  this.showEmojiPicker = true
122
125
  // }, 500);
123
- this.updateAttachmentTooltip();
126
+ // this.updateAttachmentTooltip();
124
127
  }
125
128
 
126
129
 
127
- updateAttachmentTooltip() {
128
- // Use setTimeout to wait for the async translation map to be populated
129
- setTimeout(() => {
130
- this.logger.log('[CONV-FOOTER] updateAttachmentTooltip - translationMap:', this.translationMap);
131
- if (this.translationMap && this.translationMap.has('MAX_ATTACHMENT')) {
132
- const template = this.translationMap.get('MAX_ATTACHMENT');
133
- this.logger.log('[CONV-FOOTER] MAX_ATTACHMENT template:', template);
134
- this.attachmentTooltip = template.replace('{{file_size_limit}}', this.file_size_limit.toString());
135
- this.logger.log('[CONV-FOOTER] attachmentTooltip:', this.attachmentTooltip);
136
- } else {
137
- this.logger.log('[CONV-FOOTER] MAX_ATTACHMENT not found in translationMap');
138
- }
139
- }, 500);
140
- }
130
+ // updateAttachmentTooltip() {
131
+ // // Use setTimeout to wait for the async translation map to be populated
132
+ // setTimeout(() => {
133
+ // this.logger.log('[CONV-FOOTER] updateAttachmentTooltip - translationMap:', this.translationMap);
134
+ // if (this.translationMap && this.translationMap.has('MAX_ATTACHMENT')) {
135
+ // const template = this.translationMap.get('MAX_ATTACHMENT');
136
+ // this.logger.log('[CONV-FOOTER] MAX_ATTACHMENT template:', template);
137
+ // // this.attachmentTooltip = template.replace('{{file_size_limit}}', this.file_size_limit.toString());
138
+ // this.logger.log('[CONV-FOOTER] attachmentTooltip:', this.attachmentTooltip);
139
+ // } else {
140
+ // this.logger.log('[CONV-FOOTER] MAX_ATTACHMENT not found in translationMap');
141
+ // }
142
+ // }, 500);
143
+ // }
141
144
 
142
145
  // ========= begin:: functions send image ======= //
143
146
  // START LOAD IMAGE //
@@ -227,7 +230,12 @@ export class ConversationFooterComponent implements OnInit, OnChanges {
227
230
  const fileXLoad = this.arrayFilesLoad[0].file;
228
231
  const uid = this.arrayFilesLoad[0].uid;
229
232
  const type = this.arrayFilesLoad[0].type;
230
- const size = this.arrayFilesLoad[0].size
233
+ const size = this.arrayFilesLoad[0].size;
234
+ if(size > this.file_size_limit * 1024 * 1024){
235
+ this.logger.error('[CONV-FOOTER] file size is greater than the limit: ', size, this.file_size_limit * 1024 * 1024);
236
+ this.showErrorNetwork();
237
+ return;
238
+ }
231
239
  this.logger.log('[CONV-FOOTER] that.fileXLoad: ', type);
232
240
  let metadata;
233
241
  if (type.startsWith('image') && !type.includes('svg')) {
@@ -264,6 +272,17 @@ export class ConversationFooterComponent implements OnInit, OnChanges {
264
272
  }
265
273
 
266
274
 
275
+ private showErrorNetwork() {
276
+ // posso anche passare solo keyMessage, nel caso non voglio passare un messaggio custom posso passare message e params(se il messaggio possiede dei parametri),
277
+ //window.dispatchEvent(new CustomEvent('tooltipErrorMessage', { detail: { error: true, message: 'File size is greater than the limit {{file_size_limit}}', keyMessage: null, params: { file_size_limit: this.file_size_limit } } }));
278
+ window.dispatchEvent(new CustomEvent('tooltipErrorMessage', { detail: { error: true, keyMessage: 'MAX_ATTACHMENT' } }));
279
+ setTimeout(() => {
280
+ this.isFilePendingToUpload = false;
281
+ this.hideTextReply = false;
282
+ window.dispatchEvent(new CustomEvent('tooltipErrorMessage', { detail: { error: false, message: '', keyMessage: null } }));
283
+ }, 5000);
284
+ }
285
+
267
286
  uploadSingle(metadata, file, messageText?: string) {
268
287
  const that = this;
269
288
  try {
@@ -3,6 +3,8 @@
3
3
  <path d="M440-280h80v-240h-80v240Zm40-320q17 0 28.5-11.5T520-640q0-17-11.5-28.5T480-680q-17 0-28.5 11.5T440-640q0 17 11.5 28.5T480-600Zm0 520q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Zm0-80q134 0 227-93t93-227q0-134-93-227t-227-93q-134 0-227 93t-93 227q0 134 93 227t227 93Zm0-320Z"/>
4
4
  </svg>
5
5
  <div class="alert-content">
6
- {{ translationMap.get('CONNECTION_NETWORK_ERROR')}}
6
+ {{ errorMessage }}
7
7
  </div>
8
- </div>
8
+ </div>
9
+
10
+
@@ -28,4 +28,6 @@ span.material-icons-outlined{
28
28
  font-size: 11px;
29
29
  line-height: 18px;
30
30
  /* // letter-spacing: -0.01em; */
31
- }
31
+ }
32
+
33
+
@@ -1,18 +1,18 @@
1
1
  import { ComponentFixture, TestBed } from '@angular/core/testing';
2
2
 
3
- import { NetworkOfflineComponent } from './network-offline.component';
3
+ import { ErrorAlertComponent } from './error-alert.component';
4
4
 
5
- describe('NetworkOfflineComponent', () => {
6
- let component: NetworkOfflineComponent;
7
- let fixture: ComponentFixture<NetworkOfflineComponent>;
5
+ describe('ErrorAlertComponent', () => {
6
+ let component: ErrorAlertComponent;
7
+ let fixture: ComponentFixture<ErrorAlertComponent>;
8
8
 
9
9
  beforeEach(async () => {
10
10
  await TestBed.configureTestingModule({
11
- declarations: [ NetworkOfflineComponent ]
11
+ declarations: [ ErrorAlertComponent ]
12
12
  })
13
13
  .compileComponents();
14
14
 
15
- fixture = TestBed.createComponent(NetworkOfflineComponent);
15
+ fixture = TestBed.createComponent(ErrorAlertComponent);
16
16
  component = fixture.componentInstance;
17
17
  fixture.detectChanges();
18
18
  });
@@ -21,3 +21,5 @@ describe('NetworkOfflineComponent', () => {
21
21
  expect(component).toBeTruthy();
22
22
  });
23
23
  });
24
+
25
+
@@ -0,0 +1,47 @@
1
+ import { Component, Input, OnInit } from '@angular/core';
2
+ import { CustomTranslateService } from 'src/chat21-core/providers/custom-translate.service';
3
+ import * as CONSTANTS from 'src/app/utils/constants';
4
+
5
+ @Component({
6
+ selector: 'chat-error-alert',
7
+ templateUrl: './error-alert.component.html',
8
+ styleUrls: ['./error-alert.component.scss']
9
+ })
10
+ export class ErrorAlertComponent implements OnInit {
11
+
12
+ @Input() errorMessage: string = '';
13
+ @Input() errorKeyMessage: string = '';
14
+ @Input() errorParams: Record<string, any> = {};
15
+
16
+ translationMap: Map<string, string>;
17
+
18
+ constructor(
19
+ private customTranslateService: CustomTranslateService,
20
+ ){}
21
+
22
+ ngOnInit(): void {
23
+ let rawMessage: string = '';
24
+ // Combina costanti globali + parametri passati come input
25
+ const replacements = { ...CONSTANTS, ...this.errorParams };
26
+ if (this.errorKeyMessage) {
27
+ // Traduci il messaggio e sostituisci i placeholder
28
+ rawMessage = this.customTranslateService
29
+ .translateLanguage([this.errorKeyMessage])
30
+ .get(this.errorKeyMessage);
31
+ } else if (this.errorMessage) {
32
+ rawMessage = this.errorMessage;
33
+ }
34
+ this.errorMessage = this.interpolate(rawMessage, replacements);
35
+ }
36
+
37
+ /** Sostituisce {{placeholders}} con i valori corrispondenti */
38
+ private interpolate(template: string, variables: Record<string, any>): string {
39
+ return template.replace(/\{\{(.*?)\}\}/g, (_, key) => {
40
+ const trimmedKey = key.trim();
41
+ return variables[trimmedKey] ?? `{{${trimmedKey}}}`;
42
+ });
43
+ }
44
+
45
+ }
46
+
47
+
@@ -1,4 +1,5 @@
1
1
  import { Component, ElementRef, EventEmitter, Input, OnInit, Output, SimpleChanges } from '@angular/core';
2
+ import { getColorBck } from 'src/chat21-core/utils/utils-user';
2
3
 
3
4
  @Component({
4
5
  selector: 'chat-action-button-attachment',
@@ -26,9 +27,10 @@ export class ActionButtonComponent implements OnInit {
26
27
  //decomment if element should have same color of themeColor and fregroundColor
27
28
  if(this.fontSize) this.elementRef.nativeElement.querySelector('.action').style.setProperty('--buttonFontSize', this.fontSize);
28
29
  if(this.backgroundColor) this.elementRef.nativeElement.querySelector('.action').style.setProperty('--buttonBackgroundColor', this.backgroundColor);
29
- if(this.textColor) this.elementRef.nativeElement.querySelector('.action').style.setProperty('--textColor', this.textColor);
30
+ if(this.textColor) this.elementRef.nativeElement.querySelector('.action').style.setProperty('--buttonTextColor', this.textColor);
30
31
  if(this.hoverBackgroundColor) this.elementRef.nativeElement.querySelector('.action').style.setProperty('--hoverBackgroundColor', this.hoverBackgroundColor);
31
32
  if(this.hoverTextColor) this.elementRef.nativeElement.querySelector('.action').style.setProperty('--hoverTextColor', this.hoverTextColor);
33
+
32
34
  }
33
35
 
34
36
  onMouseOver(event){
@@ -10,7 +10,7 @@ import { TemplateBindingParseResult } from '@angular/compiler';
10
10
  import { AppStorageService } from '../../chat21-core/providers/abstract/app-storage.service';
11
11
  import { LoggerService } from '../../chat21-core/providers/abstract/logger.service';
12
12
  import { LoggerInstance } from '../../chat21-core/providers/logger/loggerInstance';
13
- import { invertColor, isAllowedUrlInText, isJsonArray } from '../../chat21-core/utils/utils';
13
+ import { ensureAccessibleTextColor, invertColor, isAllowedUrlInText, isJsonArray, normalizeColorToHex } from '../../chat21-core/utils/utils';
14
14
  import { AppConfigService } from './app-config.service';
15
15
 
16
16
 
@@ -322,10 +322,14 @@ export class GlobalSettingsService {
322
322
  if (response !== null) {
323
323
  this.setVariablesFromService(this.globals, response);
324
324
  }
325
+ /** set button colors */
326
+ this.setButtonColors();
327
+
325
328
  this.setVariableFromStorage(this.globals);
326
329
  this.setVariablesFromSettings(this.globals);
327
330
  this.setVariablesFromAttributeHtml(this.globals, this.el);
328
331
  this.setVariablesFromUrlParameters(this.globals);
332
+
329
333
  this.setDepartmentFromExternal();
330
334
  /** set color with gradient from theme's colors */
331
335
  this.globals.setColorWithGradient();
@@ -333,11 +337,19 @@ export class GlobalSettingsService {
333
337
  this.setCssIframe();
334
338
  /** set main style */
335
339
  this.setStyle();
336
-
337
- this.logger.debug('[GLOBAL-SET] ***** END SET PARAMETERS *****');
338
340
  this.obsSettingsService.next(true);
339
341
  }
340
342
 
343
+ private setButtonColors() {
344
+ this.logger.debug('[GLOBAL-SET] ***** END SET PARAMETERS *****', this.globals);
345
+ const bubbleSentBackground = this.globals?.bubbleSentBackground;
346
+ const buttonBackgroundColor = this.globals?.buttonBackgroundColor;
347
+
348
+ this.globals.buttonTextColor = ensureAccessibleTextColor(buttonBackgroundColor, bubbleSentBackground);
349
+ this.globals.buttonHoverTextColor = ensureAccessibleTextColor(bubbleSentBackground, buttonBackgroundColor);
350
+
351
+ }
352
+
341
353
  /**
342
354
  *
343
355
  */
@@ -38,6 +38,9 @@ export const MAX_HEIGHT_TEXTAREA = 180;
38
38
  export const MAX_WIDTH_IMAGES = 230;
39
39
  export const MIN_WIDTH_IMAGES = 130;
40
40
 
41
+ // FILE SIZE LIMIT
42
+ export const FILE_SIZE_LIMIT = 10;
43
+
41
44
 
42
45
  // pagine
43
46
  export const PARENT_PAGE_USERS = 'users';
@@ -95,6 +95,6 @@
95
95
  "CONNECTION_NETWORK_ERROR": "Our apologies. There was some trouble connecting to network",
96
96
  "EMOJI_NOT_ELLOWED":"Emoji not allowed",
97
97
  "DOMAIN_NOT_ALLOWED":"URL contains a non-allowed domain",
98
- "MAX_ATTACHMENT": "Max allowed size {{file_size_limit}}Mb",
98
+ "MAX_ATTACHMENT": "Max allowed size {{FILE_SIZE_LIMIT}}Mb",
99
99
  "EMOJI": "Emoji"
100
100
  }
@@ -95,6 +95,6 @@
95
95
  "CONNECTION_NETWORK_ERROR": "Nuestras disculpas. Hubo algunos problemas para conectarse a la red",
96
96
  "EMOJI_NOT_ELLOWED":"Emoji no permitido",
97
97
  "DOMAIN_NOT_ALLOWED":"La URL contiene un dominio no permitido",
98
- "MAX_ATTACHMENT": "Tamaño máximo permitido {{file_size_limit}}Mb",
98
+ "MAX_ATTACHMENT": "Tamaño máximo permitido {{FILE_SIZE_LIMIT}}Mb",
99
99
  "EMOJI": "Emoji"
100
100
  }
@@ -95,6 +95,6 @@
95
95
  "CONNECTION_NETWORK_ERROR": "Nos excuses. Il y a eu des problèmes de connexion au réseau",
96
96
  "EMOJI_NOT_ELLOWED":"Emoji non autorisé",
97
97
  "DOMAIN_NOT_ALLOWED":"L'URL contient un domaine non autorisé",
98
- "MAX_ATTACHMENT": "Taille maximale autorisée {{file_size_limit}}Mo",
98
+ "MAX_ATTACHMENT": "Taille maximale autorisée {{FILE_SIZE_LIMIT}}Mo",
99
99
  "EMOJI": "Emoji"
100
100
  }
@@ -93,6 +93,6 @@
93
93
  "CONNECTION_NETWORK_ERROR": "Ci scusiamo. Si sono verificati problemi di connessione di rete",
94
94
  "EMOJI_NOT_ELLOWED":"Emoji non consentiti",
95
95
  "DOMAIN_NOT_ALLOWED":"L'URL contiene un dominio non consentito",
96
- "MAX_ATTACHMENT": "Dimensione massima consentita {{file_size_limit}}Mb",
96
+ "MAX_ATTACHMENT": "Dimensione massima consentita {{FILE_SIZE_LIMIT}}Mb",
97
97
  "EMOJI": "Emoji"
98
98
  }
@@ -389,6 +389,115 @@ export function convertColorToRGBA(color, opacity) {
389
389
  return result;
390
390
  }
391
391
 
392
+ export function normalizeColorToHex(input?: string): string | null {
393
+ if (!input) {
394
+ return null;
395
+ }
396
+ let color = input.trim();
397
+ if (color.toLowerCase().includes('gradient')) {
398
+ const match = color.match(/(#[0-9a-fA-F]{3,8}|rgba?\([^)]*\))/);
399
+ if (!match) {
400
+ return null;
401
+ }
402
+ color = match[0];
403
+ }
404
+ if (color.startsWith('#')) {
405
+ const hex = color.slice(1);
406
+ if (hex.length === 3) {
407
+ return (
408
+ '#' +
409
+ hex
410
+ .split('')
411
+ .map((char) => char + char)
412
+ .join('')
413
+ .toLowerCase()
414
+ );
415
+ }
416
+ if (hex.length === 4) {
417
+ return (
418
+ '#' +
419
+ hex
420
+ .slice(0, 3)
421
+ .split('')
422
+ .map((char) => char + char)
423
+ .join('')
424
+ .toLowerCase()
425
+ );
426
+ }
427
+ if (hex.length === 6 || hex.length === 8) {
428
+ return '#' + hex.slice(0, 6).toLowerCase();
429
+ }
430
+ return null;
431
+ }
432
+ const rgbaMatch = color.match(/rgba?\s*\(\s*([\d.]+)\s*,\s*([\d.]+)\s*,\s*([\d.]+)(?:\s*,\s*([\d.]+))?\s*\)/i);
433
+ if (rgbaMatch) {
434
+ const [, r, g, b] = rgbaMatch;
435
+ const toHex = (value: number) => {
436
+ const clamped = Math.max(0, Math.min(255, Math.round(value)));
437
+ return clamped.toString(16).padStart(2, '0');
438
+ };
439
+ return `#${toHex(parseFloat(r))}${toHex(parseFloat(g))}${toHex(parseFloat(b))}`;
440
+ }
441
+ return null;
442
+ }
443
+
444
+ export function ensureAccessibleTextColor(backgroundColor?: string, fontColor?: string, minContrast = 4.5): string | null {
445
+ const bgHex = normalizeColorToHex(backgroundColor);
446
+ if (!bgHex) {
447
+ return normalizeColorToHex(fontColor);
448
+ }
449
+
450
+ const fontHex = normalizeColorToHex(fontColor);
451
+ if (fontHex && getContrastRatio(bgHex, fontHex) >= minContrast) {
452
+ return fontHex;
453
+ }
454
+
455
+ const blackContrast = getContrastRatio(bgHex, '#000000');
456
+ const whiteContrast = getContrastRatio(bgHex, '#ffffff');
457
+ return blackContrast >= whiteContrast ? '#000000' : '#ffffff';
458
+ }
459
+
460
+ function getContrastRatio(backgroundHex: string, foregroundHex: string): number {
461
+ const bg = hexToRgb(backgroundHex);
462
+ const fg = hexToRgb(foregroundHex);
463
+ if (!bg || !fg) {
464
+ return 1;
465
+ }
466
+ const l1 = relativeLuminance(bg);
467
+ const l2 = relativeLuminance(fg);
468
+ const lighter = Math.max(l1, l2);
469
+ const darker = Math.min(l1, l2);
470
+ return (lighter + 0.05) / (darker + 0.05);
471
+ }
472
+
473
+ function relativeLuminance({ r, g, b }: { r: number; g: number; b: number }): number {
474
+ const srgb = [r, g, b].map((value) => {
475
+ const channel = value / 255;
476
+ return channel <= 0.03928 ? channel / 12.92 : Math.pow((channel + 0.055) / 1.055, 2.4);
477
+ });
478
+ return 0.2126 * srgb[0] + 0.7152 * srgb[1] + 0.0722 * srgb[2];
479
+ }
480
+
481
+ function hexToRgb(hex: string): { r: number; g: number; b: number } | null {
482
+ let value = hex.replace('#', '');
483
+ if (value.length === 3) {
484
+ value = value
485
+ .split('')
486
+ .map((char) => char + char)
487
+ .join('');
488
+ }
489
+ if (value.length !== 6) {
490
+ return null;
491
+ }
492
+ const r = parseInt(value.slice(0, 2), 16);
493
+ const g = parseInt(value.slice(2, 4), 16);
494
+ const b = parseInt(value.slice(4, 6), 16);
495
+ if ([r, g, b].some((channel) => Number.isNaN(channel))) {
496
+ return null;
497
+ }
498
+ return { r, g, b };
499
+ }
500
+
392
501
 
393
502
  // export function setLanguage(windowContext, translatorService) {
394
503
  // if (translatorService.getBrowserLanguage(windowContext)) {
@@ -1,24 +0,0 @@
1
- import { Component, OnInit } from '@angular/core';
2
- import { CustomTranslateService } from 'src/chat21-core/providers/custom-translate.service';
3
-
4
- @Component({
5
- selector: 'chat-network-offline',
6
- templateUrl: './network-offline.component.html',
7
- styleUrls: ['./network-offline.component.scss']
8
- })
9
- export class NetworkOfflineComponent implements OnInit {
10
-
11
- translationMap: Map< string, string>;
12
-
13
- constructor(
14
- private customTranslateService: CustomTranslateService
15
- ){}
16
-
17
- ngOnInit(): void {
18
- let keys = [
19
- 'CONNECTION_NETWORK_ERROR'
20
- ]
21
- this.translationMap = this.customTranslateService.translateLanguage(keys)
22
- }
23
-
24
- }