@chat21/chat21-web-widget 5.1.0-rc7 → 5.1.0-rc8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -6,6 +6,9 @@
6
6
  ### **Copyrigth**:
7
7
  *Tiledesk SRL*
8
8
 
9
+ # 5.1.0-rc.8
10
+ - **added**: ability to filter on urls attached to message textarea
11
+
9
12
  # 5.1.0-rc.7
10
13
  - **added**: ability to allows emoji after message is sent
11
14
 
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.0-rc7",
4
+ "version": "5.1.0-rc8",
5
5
  "license": "MIT",
6
6
  "homepage": "https://www.tiledesk.com",
7
7
  "repository": {
@@ -231,7 +231,9 @@ export class ConversationComponent implements OnInit, AfterViewInit, OnChanges {
231
231
  'LABEL_PLACEHOLDER',
232
232
  'GUEST_LABEL',
233
233
  'LABEL_START_NW_CONV',
234
- 'CONTINUE'
234
+ 'CONTINUE',
235
+ 'EMOJI_NOT_ELLOWED',
236
+ 'DOMAIN_NOT_ALLOWED'
235
237
  ];
236
238
 
237
239
  const keysContent = [
@@ -1,7 +1,23 @@
1
- <!-- LOGO-->
2
- <div id="hiddenFooter" *ngIf="!hideTextAreaContent && poweredBy" class="fade-in-bottom"
3
- [class.hideTextReply]="hideTextReply">
4
- <div tabindex="-1" class="c21-powered-by" [innerHTML]="poweredBy" (click)="managePoweredBy($event)"></div>
1
+ <div class="footerContainerAlert">
2
+ <!-- LOGO-->
3
+ <div id="hiddenFooter" *ngIf="!hideTextAreaContent && poweredBy" class="fade-in-bottom" [class.hideTextReply]="hideTextReply">
4
+ <div tabindex="-1" class="c21-powered-by" [innerHTML]="poweredBy" (click)="managePoweredBy($event)"></div>
5
+ </div>
6
+
7
+ <!-- ALERT EMOJI & URLS -->
8
+ <div id="textAlert" *ngIf="!hideTextAreaContent && showAlertEmoji" class="fade-in-bottom" [class.hideTextReply]="hideTextReply">
9
+ <svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" width="24px" height="24px" version="1.1" viewBox="0 0 110 135">
10
+ <path d="M55,25.8c-23,0-41.7,18.7-41.7,41.7s18.7,41.7,41.7,41.7,41.7-18.7,41.7-41.7-18.7-41.7-41.7-41.7ZM55,91.5c-3.4,0-6.2-2.8-6.2-6.2s2.8-6.2,6.2-6.2,6.2,2.8,6.2,6.2-2.8,6.2-6.2,6.2ZM60.3,70.1c-.2,2.8-2.5,4.9-5.3,4.9s-5.1-2.2-5.3-4.9l-1.6-22.3c-.3-4,2.9-7.4,6.9-7.4s7.2,3.4,6.9,7.4l-1.6,22.3Z"/>
11
+ </svg>
12
+ <div tabindex="-1" class="alertText">{{translationMap.get('EMOJI_NOT_ELLOWED')}}</div>
13
+ </div>
14
+
15
+ <div id="textAlert" *ngIf="!hideTextAreaContent && showAlertUrl" class="fade-in-bottom" [class.hideTextReply]="hideTextReply">
16
+ <svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" width="24px" height="24px" version="1.1" viewBox="0 0 110 135">
17
+ <path d="M55,25.8c-23,0-41.7,18.7-41.7,41.7s18.7,41.7,41.7,41.7,41.7-18.7,41.7-41.7-18.7-41.7-41.7-41.7ZM55,91.5c-3.4,0-6.2-2.8-6.2-6.2s2.8-6.2,6.2-6.2,6.2,2.8,6.2,6.2-2.8,6.2-6.2,6.2ZM60.3,70.1c-.2,2.8-2.5,4.9-5.3,4.9s-5.1-2.2-5.3-4.9l-1.6-22.3c-.3-4,2.9-7.4,6.9-7.4s7.2,3.4,6.9,7.4l-1.6,22.3Z"/>
18
+ </svg>
19
+ <div tabindex="-1" class="alertText">{{translationMap.get('DOMAIN_NOT_ALLOWED')}}</div>
20
+ </div>
5
21
  </div>
6
22
 
7
23
  <!-- TEXTAREA + ICONS: conv active-->
@@ -44,7 +60,7 @@
44
60
 
45
61
 
46
62
 
47
- <div *ngIf="!isStopRec" class="visible-text-area" [class.disabled] = "( isConversationArchived || hideTextReply)? true : null">
63
+ <div *ngIf="!isStopRec" class="visible-text-area" [class.hasError]="showAlertEmoji || showAlertUrl" [class.disabled] = "( isConversationArchived || hideTextReply)? true : null">
48
64
  <!-- isFilePendingToUpload || -->
49
65
  <textarea
50
66
  [attr.disabled] = "(hideTextReply)? true : null"
@@ -67,7 +83,7 @@
67
83
  </div>
68
84
 
69
85
  <!-- ICON SEND -->
70
- <div *ngIf="(textInputTextArea !== '' && !isStopRec) || !showAudioRecorderFooterButton" tabindex="-1" class="chat21-textarea-button" [class.active]="textInputTextArea && !hideTextReply" id="chat21-button-send" (click)="onSendPressed($event)">
86
+ <div *ngIf="(textInputTextArea !== '' && !isStopRec) || !showAudioRecorderFooterButton" tabindex="-1" class="chat21-textarea-button" [class.disabled]="showAlertEmoji || showAlertUrl" [class.active]="textInputTextArea && !hideTextReply" id="chat21-button-send" (click)="onSendPressed($event)">
71
87
  <span class="v-align-center">
72
88
  <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" height="24" width="24" viewBox="0 0 24 24" style="enable-background:new 0 0 24 24;" xml:space="preserve">
73
89
  <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"/>
@@ -39,6 +39,9 @@
39
39
  &.disabled {
40
40
  background-color: rgb(232, 233, 237);
41
41
  }
42
+ &.hasError{
43
+ box-shadow: 0 0 0 1px var(--chat-footer-border-color-error) inset;
44
+ }
42
45
  }
43
46
 
44
47
  .chat21-textarea-button {
@@ -52,6 +55,12 @@
52
55
  opacity: 1;
53
56
  cursor: pointer;
54
57
  }
58
+ &.disabled{
59
+ opacity: 0.3 !important;
60
+ pointer-events: none !important;
61
+ cursor: not-allowed !important;
62
+
63
+ }
55
64
  }
56
65
 
57
66
  .chat21-textarea-button span svg:hover {
@@ -273,16 +282,18 @@ textarea:active{
273
282
 
274
283
  }
275
284
 
285
+ .footerContainerAlert{
286
+ position: relative;
287
+ }
276
288
 
277
289
  #hiddenFooter{
278
- // position: absolute;
290
+ position: absolute;
279
291
  bottom: 100%;
280
292
  width: 100%;
281
293
  height: var(--chat-footer-logo-height);
282
294
  display: flex;
283
295
  align-items: center;
284
296
  justify-content: center;
285
- // position: absolute;
286
297
  // box-shadow: inset 0px -22px 16px -15px rgba(0,0,0,0.1);
287
298
  &.hideTextReply{
288
299
  height: var(--chat-footer-height);
@@ -344,6 +355,37 @@ textarea:active{
344
355
  }
345
356
  }
346
357
 
358
+ #textAlert{
359
+ bottom: 100%;
360
+ width: 100%;
361
+ height: var(--chat-footer-logo-height);
362
+ display: flex;
363
+ align-items: center;
364
+ justify-content: center;
365
+ background-color: var(--content-background-color);
366
+ position: absolute;
367
+ svg{
368
+ fill: var(--chat-footer-border-color-error);
369
+ }
370
+ // box-shadow: inset 0px -22px 16px -15px rgba(0,0,0,0.1);
371
+ div.alertText{
372
+ color: var(--dark-gray);
373
+ font-size: 1.2em;
374
+ font-weight: 500;
375
+ line-height: 22px;
376
+ font-family: Mulish, sans-serif;
377
+ letter-spacing: 0.24px;
378
+ -webkit-font-smoothing: antialiased;
379
+ padding: 4px 12px;
380
+ color: var(--chat-footer-border-color-error);
381
+ }
382
+ &.hideTextReply{
383
+ height: var(--chat-footer-height);
384
+ position: unset;
385
+ box-shadow: none;
386
+ }
387
+ }
388
+
347
389
  .fade-in-bottom {
348
390
  -webkit-animation: fade-in-bottom 0.5s cubic-bezier(0.600, -0.280, 0.735, 0.045) 0.0s;
349
391
  animation: fade-in-bottom 0.5s cubic-bezier(0.600, -0.280, 0.735, 0.045) 0.0s;
@@ -367,4 +409,4 @@ textarea:active{
367
409
  // left: 10px;
368
410
  border: none;
369
411
  margin: -2px -2px 0px;
370
- }
412
+ }
@@ -1,6 +1,6 @@
1
1
  import { Component, ComponentFactoryResolver, ElementRef, EventEmitter, Input, OnChanges, OnInit, Output, SimpleChanges, ViewChild, ViewContainerRef } from '@angular/core';
2
2
  import { Globals } from 'src/app/utils/globals';
3
- import { checkAcceptedFile } from 'src/app/utils/utils';
3
+ import { checkAcceptedFile, isEmoji, isAllowedUrlInText } from 'src/app/utils/utils';
4
4
  import { MessageModel } from 'src/chat21-core/models/message';
5
5
  import { UploadModel } from 'src/chat21-core/models/upload';
6
6
  import { ConversationHandlerService } from 'src/chat21-core/providers/abstract/conversation-handler.service';
@@ -82,6 +82,9 @@ export class ConversationFooterComponent implements OnInit, OnChanges {
82
82
  include: [ 'recent', 'people', 'nature', 'activity', 'flags']
83
83
  }
84
84
 
85
+ showAlertEmoji: boolean = false
86
+ showAlertUrl: boolean = false;
87
+
85
88
  convertColorToRGBA = convertColorToRGBA;
86
89
  private logger: LoggerService = LoggerInstance.getInstance()
87
90
  constructor(private chatManager: ChatManager,
@@ -321,7 +324,7 @@ export class ConversationFooterComponent implements OnInit, OnChanges {
321
324
  this.onEmojiiPickerShow.emit(false)
322
325
  this.logger.log('[CONV-FOOTER] SEND MESSAGE: ', msg, type, metadata, additional_attributes);
323
326
 
324
- msg = this.checkForEmojii(msg)
327
+
325
328
  if (msg && msg.trim() !== '' || type === TYPE_MSG_IMAGE || type === TYPE_MSG_FILE ) {
326
329
 
327
330
  // msg = htmlEntities(msg);
@@ -519,9 +522,30 @@ export class ConversationFooterComponent implements OnInit, OnChanges {
519
522
  checkForEmojii(text){
520
523
  //remove emojii only if "emojii" exist and is set to false
521
524
  if(this.project && this.project.settings?.allow_send_emoji === false){
522
- return findAndRemoveEmoji(text)
525
+ this.showAlertEmoji = isEmoji(text);
526
+ if(this.showAlertEmoji){
527
+ return false
528
+ }
529
+ this.showAlertEmoji = false;
530
+ return true
523
531
  }
524
- return text
532
+ this.showAlertEmoji = false;
533
+ return true
534
+ }
535
+
536
+ checkForUrlDomain(text){
537
+ if(this.project && this.project.settings?.allowed_urls === true){
538
+ this.showAlertUrl = !isAllowedUrlInText(text, this.project.settings?.allowed_urls_list);
539
+ if(this.showAlertUrl){
540
+ return false
541
+ }
542
+ this.showAlertUrl = false
543
+ return true
544
+ }
545
+ this.showAlertUrl = false
546
+ return true
547
+
548
+
525
549
  }
526
550
 
527
551
 
@@ -529,6 +553,17 @@ export class ConversationFooterComponent implements OnInit, OnChanges {
529
553
  onTextAreaChange(){
530
554
  this.resizeInputField()
531
555
  this.setWritingMessages(this.textInputTextArea)
556
+
557
+ let check = this.checkForEmojii(this.textInputTextArea)
558
+ if(!check){
559
+ return;
560
+ }
561
+
562
+ let checkUrlDomain = this.checkForUrlDomain(this.textInputTextArea)
563
+ if(!checkUrlDomain){
564
+ return
565
+ }
566
+
532
567
  }
533
568
 
534
569
  onSendPressed(event) {
@@ -597,6 +632,13 @@ export class ConversationFooterComponent implements OnInit, OnChanges {
597
632
 
598
633
  addEmoji(event){
599
634
  this.onEmojiiPickerShow.emit(false); //de-activate emojii picker on select
635
+
636
+ let check = this.checkForEmojii(this.textInputTextArea)
637
+ console.log('chekkkkkkk', check)
638
+ if(!check){
639
+ return;
640
+ }
641
+
600
642
  this.textInputTextArea = this.textInputTextArea.trimStart() + event.emoji.native + " "
601
643
  this.setFocusOnId('chat21-main-message-context')
602
644
  }
@@ -93,6 +93,7 @@ export class GlobalSettingsService {
93
93
  project['trialDaysLeft'],
94
94
  project['trialExpired'],
95
95
  project['updatedAt'],
96
+ project['settings'],
96
97
  project['versions']
97
98
  );
98
99
  }
@@ -31,8 +31,13 @@
31
31
  --chat-footer-border-radius: 16px;
32
32
  --chat-footer-background-color: #f6f7fb;
33
33
  --chat-footer-color: #1a1a1a;
34
+ --chat-footer-border-color-error: #aa0404;
34
35
 
35
- --icon-fill-color: #5f6368
36
+ --icon-fill-color: #5f6368;
37
+
38
+
39
+ --content-background-color: #fff;
40
+ --content-text-color: var(--black);
36
41
  }
37
42
 
38
43
  $trasp-black:rgba(0,0,0,0.8);
@@ -210,6 +210,44 @@ export function isEmoji(str: string) {
210
210
  }
211
211
  }
212
212
 
213
+ export function isAllowedUrlInText(text: string, allowedUrls: string[]): boolean {
214
+ // Regex per trovare URL o domini nudi nel testo
215
+ const urlRegex = /https?:\/\/[^\s]+|www\.[^\s]+|(?:\b[\w-]+\.)+[a-z]{2,}(\/[^\s]*)?/gi;
216
+ const foundUrls = text.match(urlRegex);
217
+
218
+ if (!foundUrls) {
219
+ return true; // Nessun URL => testo ammesso
220
+ }
221
+
222
+ // Normalizza dominio: rimuove schema, www., slash finali
223
+ const normalize = (url: string) =>
224
+ url
225
+ .replace(/^https?:\/\//i, '')
226
+ .replace(/^www\./i, '')
227
+ .replace(/\/$/, '')
228
+ .toLowerCase();
229
+
230
+ // Normalizza tutti gli allowed pattern per confronto
231
+ const normalizedAllowedPatterns = allowedUrls.map(pattern =>
232
+ pattern
233
+ .replace(/^https?:\/\//i, '')
234
+ .replace(/^www\./i, '')
235
+ .replace(/\/$/, '')
236
+ .toLowerCase()
237
+ .replace(/\./g, '\\.')
238
+ .replace(/\//g, '\\/')
239
+ .replace(/\*/g, '.*')
240
+ );
241
+
242
+ return foundUrls.every(rawUrl => {
243
+ const url = normalize(rawUrl);
244
+ return normalizedAllowedPatterns.some(pattern => {
245
+ const regex = new RegExp(`^${pattern}$`, 'i');
246
+ return regex.test(url);
247
+ });
248
+ });
249
+ }
250
+
213
251
  export function setColorFromString(str: string) {
214
252
  const arrayBckColor = ['#fba76f', '#80d066', '#73cdd0', '#ecd074', '#6fb1e4', '#f98bae'];
215
253
  let num = 0;
@@ -91,5 +91,7 @@
91
91
 
92
92
  "LABEL_PREVIEW": "Preview",
93
93
  "SWITCH_TO": "Or switch to:",
94
- "CONNECTION_NETWORK_ERROR": "Our apologies. There was some trouble connecting to network"
94
+ "CONNECTION_NETWORK_ERROR": "Our apologies. There was some trouble connecting to network",
95
+ "EMOJI_NOT_ELLOWED":"Emoji not allowed",
96
+ "DOMAIN_NOT_ALLOWED":"Domain not allowed"
95
97
  }