@chat21/chat21-web-widget 5.1.0-rc6 → 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,12 @@
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
+
12
+ # 5.1.0-rc.7
13
+ - **added**: ability to allows emoji after message is sent
14
+
9
15
  # 5.1.0-rc.6
10
16
  - **removed**: hideRestartConversationOptionsMenu
11
17
 
package/Dockerfile CHANGED
@@ -15,7 +15,7 @@ COPY . .
15
15
 
16
16
  ## Build the angular app in production mode and store the artifacts in dist folder
17
17
 
18
- RUN npm run ng build -- --configuration="prod" --output-path=dist --base-href="./" --output-hashing=none
18
+ RUN npx ng build --configuration="prod" --output-path=dist --base-href=./ --output-hashing=none
19
19
 
20
20
 
21
21
  ### STAGE 2: Setup ###
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-rc6",
4
+ "version": "5.1.0-rc8",
5
5
  "license": "MIT",
6
6
  "homepage": "https://www.tiledesk.com",
7
7
  "repository": {
@@ -112,7 +112,7 @@
112
112
  [attributes]="g?.attributes"
113
113
  [senderId]="senderId"
114
114
  [tenant]="g?.tenant"
115
- [projectid]="g?.projectid"
115
+ [project]="g?.project"
116
116
  [channelType]="g?.channelType"
117
117
  [userFullname]="g?.userFullname"
118
118
  [userEmail]="g?.userEmail"
@@ -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';
@@ -11,7 +11,8 @@ import { ChatManager } from 'src/chat21-core/providers/chat-manager';
11
11
  import { LoggerInstance } from 'src/chat21-core/providers/logger/loggerInstance';
12
12
  import { TYPE_MSG_FILE, TYPE_MSG_IMAGE, TYPE_MSG_TEXT } from 'src/chat21-core/utils/constants';
13
13
  import { convertColorToRGBA } from 'src/chat21-core/utils/utils';
14
- import { isImage } from 'src/chat21-core/utils/utils-message';
14
+ import { findAndRemoveEmoji, isImage } from 'src/chat21-core/utils/utils-message';
15
+ import { ProjectModel } from 'src/models/project';
15
16
 
16
17
  @Component({
17
18
  selector: 'chat-conversation-footer',
@@ -24,7 +25,7 @@ export class ConversationFooterComponent implements OnInit, OnChanges {
24
25
  @Input() attributes: string;
25
26
  @Input() senderId: string;
26
27
  @Input() tenant: string;
27
- @Input() projectid: string;
28
+ @Input() project: ProjectModel;
28
29
  @Input() channelType: string;
29
30
  @Input() userFullname: string;
30
31
  @Input() userEmail: string;
@@ -81,10 +82,12 @@ export class ConversationFooterComponent implements OnInit, OnChanges {
81
82
  include: [ 'recent', 'people', 'nature', 'activity', 'flags']
82
83
  }
83
84
 
85
+ showAlertEmoji: boolean = false
86
+ showAlertUrl: boolean = false;
87
+
84
88
  convertColorToRGBA = convertColorToRGBA;
85
89
  private logger: LoggerService = LoggerInstance.getInstance()
86
- constructor(public g: Globals,
87
- private chatManager: ChatManager,
90
+ constructor(private chatManager: ChatManager,
88
91
  private typingService: TypingService,
89
92
  private uploadService: UploadService) { }
90
93
 
@@ -253,7 +256,6 @@ export class ConversationFooterComponent implements OnInit, OnChanges {
253
256
  // return snapshot.ref.getDownloadURL(); // Will return a promise with the download link
254
257
  // }).then(downloadURL => {
255
258
  // that.logger.log('[CONV-FOOTER] AppComponent::uploadSingle:: downloadURL', downloadURL]);
256
- // that.g.wdLog([`Successfully uploaded file and got download link - ${downloadURL}`]);
257
259
 
258
260
  // metadata.src = downloadURL;
259
261
  // let type_message = TYPE_MSG_TEXT;
@@ -321,6 +323,8 @@ export class ConversationFooterComponent implements OnInit, OnChanges {
321
323
  (metadata) ? metadata = metadata : metadata = '';
322
324
  this.onEmojiiPickerShow.emit(false)
323
325
  this.logger.log('[CONV-FOOTER] SEND MESSAGE: ', msg, type, metadata, additional_attributes);
326
+
327
+
324
328
  if (msg && msg.trim() !== '' || type === TYPE_MSG_IMAGE || type === TYPE_MSG_FILE ) {
325
329
 
326
330
  // msg = htmlEntities(msg);
@@ -345,7 +349,7 @@ export class ConversationFooterComponent implements OnInit, OnChanges {
345
349
  // fine-sponziello
346
350
  // this.conversationHandlerService = this.chatManager.getConversationHandlerByConversationId(this.conversationWith)
347
351
  const senderId = this.senderId;
348
- const projectid = this.projectid;
352
+ const projectid = this.project.id;
349
353
  const channelType = this.channelType;
350
354
  const userFullname = this.userFullname;
351
355
  const userEmail = this.userEmail;
@@ -514,11 +518,52 @@ export class ConversationFooterComponent implements OnInit, OnChanges {
514
518
  //}, false);
515
519
  }
516
520
 
521
+
522
+ checkForEmojii(text){
523
+ //remove emojii only if "emojii" exist and is set to false
524
+ if(this.project && this.project.settings?.allow_send_emoji === false){
525
+ this.showAlertEmoji = isEmoji(text);
526
+ if(this.showAlertEmoji){
527
+ return false
528
+ }
529
+ this.showAlertEmoji = false;
530
+ return true
531
+ }
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
+
549
+ }
550
+
517
551
 
518
552
 
519
553
  onTextAreaChange(){
520
554
  this.resizeInputField()
521
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
+
522
567
  }
523
568
 
524
569
  onSendPressed(event) {
@@ -587,6 +632,13 @@ export class ConversationFooterComponent implements OnInit, OnChanges {
587
632
 
588
633
  addEmoji(event){
589
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
+
590
642
  this.textInputTextArea = this.textInputTextArea.trimStart() + event.emoji.native + " "
591
643
  this.setFocusOnId('chat21-main-message-context')
592
644
  }
@@ -632,7 +684,6 @@ export class ConversationFooterComponent implements OnInit, OnChanges {
632
684
  * @param str
633
685
  */
634
686
  setWritingMessages(str) {
635
- //this.messagingService.setWritingMessages(str, this.g.channelType);
636
687
  this.typingService.setTyping(this.conversationWith, str, this.senderId, this.userFullname )
637
688
  }
638
689
 
@@ -1,5 +1,7 @@
1
1
  import { Component, ElementRef, AfterViewInit, Input, ViewChild } from '@angular/core';
2
2
  import { DomSanitizer, SafeUrl } from '@angular/platform-browser';
3
+ import { LoggerService } from 'src/chat21-core/providers/abstract/logger.service';
4
+ import { LoggerInstance } from 'src/chat21-core/providers/logger/loggerInstance';
3
5
  import { convertColorToRGBA } from 'src/chat21-core/utils/utils';
4
6
 
5
7
  @Component({
@@ -26,13 +28,13 @@ export class AudioComponent implements AfterViewInit {
26
28
  currentTime: number = 0;
27
29
  isPlaying: boolean = false;
28
30
 
31
+ private logger: LoggerService = LoggerInstance.getInstance();
29
32
  constructor(
30
33
  private sanitizer: DomSanitizer,
31
34
  private elementRef: ElementRef
32
35
  ) {}
33
36
 
34
37
  ngAfterViewInit() {
35
- console.log('stylesssss', this.stylesMap)
36
38
  if (this.audioBlob) {
37
39
  this.rawAudioUrl = URL.createObjectURL(this.audioBlob);
38
40
  this.audioUrl = this.sanitizer.bypassSecurityTrustUrl(this.rawAudioUrl);
@@ -136,21 +138,33 @@ export class AudioComponent implements AfterViewInit {
136
138
  return `${minutes}:${sec < 10 ? '0' + sec : sec}`;
137
139
  }
138
140
 
139
- getAudioDuration() {
140
- const audio = new Audio();
141
- audio.src = this.rawAudioUrl!;
142
- audio.addEventListener('loadedmetadata', () => {
143
- if (audio.duration === Infinity) {
144
- audio.currentTime = Number.MAX_SAFE_INTEGER;
145
- audio.ontimeupdate = () => {
146
- audio.ontimeupdate = null;
147
- audio.currentTime = 0;
148
- this.audioDuration = audio.duration;
149
- };
150
- } else {
151
- this.audioDuration = audio.duration;
152
- }
153
- });
141
+ async getAudioDuration() {
142
+ // const audio = new Audio();
143
+ // audio.src = this.rawAudioUrl!;
144
+ // audio.addEventListener('loadedmetadata', () => {
145
+ // if (audio.duration === Infinity) {
146
+ // audio.currentTime = Number.MAX_SAFE_INTEGER;
147
+ // audio.ontimeupdate = () => {
148
+ // audio.ontimeupdate = null;
149
+ // audio.currentTime = 0;
150
+ // this.audioDuration = audio.duration;
151
+ // };
152
+ // } else {
153
+ // this.audioDuration = audio.duration;
154
+ // }
155
+ // });
156
+
157
+ const response = await fetch(this.rawAudioUrl!);
158
+ this.logger.debug('getAudioDuration: response ---> ', response)
159
+ const arrayBuffer = await response.arrayBuffer();
160
+ this.logger.debug('getAudioDuration: arrayBuffer ---> ', arrayBuffer)
161
+ const audioContext = new (window.AudioContext || (window as any).webkitAudioContext)();
162
+ this.logger.debug('getAudioDuration: audioContext ---> ', audioContext)
163
+ const audioBuffer = await audioContext.decodeAudioData(arrayBuffer);
164
+ this.logger.debug('getAudioDuration: audioBuffer ---> ', audioBuffer)
165
+ this.audioDuration = audioBuffer.duration;
166
+ this.logger.debug('getAudioDuration: audioDuration ---> ', this.audioDuration)
167
+
154
168
  }
155
169
 
156
170
  extractFirstColor(gradient: string): string | null {
@@ -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
  }
@@ -613,7 +613,7 @@
613
613
  var brandJson = JSON.parse(xhr.response)
614
614
  if(brandJson){
615
615
  /** TITLE AND FAVICON **/
616
- brandJson['WIDGET'].META_TITLE? document.body.title = brandJson['WIDGET'].META_TITLE : null;
616
+ brandJson['WIDGET'].META_TITLE? document.title = brandJson['WIDGET'].META_TITLE : null;
617
617
  brandJson['WIDGET'].FAVICON_URL? document.querySelector("link[rel~='icon']").setAttribute('href', brandJson['WIDGET'].FAVICON_URL) : null;
618
618
  /** FOOTER-LOGO **/
619
619
  brandJson['COMMON'].COMPANY_LOGO? document.getElementById('footer-logo').src = brandJson['COMMON'].COMPANY_LOGO : null;
@@ -624,6 +624,13 @@
624
624
  brandJson['COMMON'].BRAND_PRIMARY_COLOR? document.body.style.setProperty('--base-company-logo', brandJson['COMMON'].BRAND_PRIMARY_COLOR): null;
625
625
  /** IFRAME TITLE**/
626
626
  brandJson['COMMON'].COMPANY_NAME? document.getElementById('tiledeskiframe').title = brandJson['COMMON'].COMPANY_NAME + ' Widget' : null;
627
+ /** META sharing ELEMENTS */
628
+ if(brandJson['WIDGET'].META_SHARE_INFO && Object.keys(brandJson['WIDGET'].META_SHARE_INFO).length > 0){
629
+ Object.keys(brandJson['WIDGET'].META_SHARE_INFO).forEach(key => {
630
+ var meta = document.querySelector("meta[property^='og:"+key.toLowerCase()+"']");
631
+ meta.setAttribute('content', brandJson['WIDGET'].META_SHARE_INFO[key])
632
+ })
633
+ }
627
634
  }
628
635
  }
629
636
 
@@ -719,7 +719,7 @@
719
719
  var brandJson = JSON.parse(xhr.response)
720
720
  if(brandJson){
721
721
  /** TITLE AND FAVICON **/
722
- brandJson['WIDGET'].META_TITLE? document.body.title = brandJson['WIDGET'].META_TITLE : null;
722
+ brandJson['WIDGET'].META_TITLE? document.title = brandJson['WIDGET'].META_TITLE : null;
723
723
  brandJson['WIDGET'].FAVICON_URL? document.querySelector("link[rel~='icon']").setAttribute('href', brandJson['WIDGET'].FAVICON_URL) : null;
724
724
  /** FOOTER-LOGO **/
725
725
  brandJson['COMMON'].COMPANY_LOGO? document.getElementById('footer-logo').src = brandJson['COMMON'].COMPANY_LOGO : null;
@@ -737,6 +737,13 @@
737
737
  }
738
738
  /** IFRAME TITLE**/
739
739
  brandJson['COMMON'].COMPANY_LOGO? document.getElementById('tiledeskiframe').title = brandJson['COMMON'].COMPANY_NAME + ' Widget' : null;
740
+ /** META sharing ELEMENTS */
741
+ if(brandJson['WIDGET'].META_SHARE_INFO && Object.keys(brandJson['WIDGET'].META_SHARE_INFO).length > 0){
742
+ Object.keys(brandJson['WIDGET'].META_SHARE_INFO).forEach(key => {
743
+ var meta = document.querySelector("meta[property^='og:"+key.toLowerCase()+"']");
744
+ meta.setAttribute('content', brandJson['WIDGET'].META_SHARE_INFO[key])
745
+ })
746
+ }
740
747
  }
741
748
  }
742
749
  }
@@ -6,7 +6,7 @@
6
6
  <meta charset="utf-8">
7
7
  <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
8
8
 
9
- <title>Widget test page </title>
9
+ <title>Widget test page</title>
10
10
  <style>
11
11
 
12
12
  @-moz-keyframes blink {0%{opacity:1;} 50%{opacity:0;} 100%{opacity:1;}} /* Firefox */
@@ -295,7 +295,10 @@
295
295
  window.Tiledesk('onBeforeInit', function(event_data) {
296
296
  console.log("onBeforeInit Tiledesk FN", event_data);
297
297
 
298
- var brandSrc = event_data.detail.appConfigs.brandSrc? getBrandResources(event_data.detail.appConfigs.brandSrc) : null;
298
+ if(event_data.detail.appConfigs.brandSrc){
299
+ getBrandResources(event_data.detail.appConfigs.brandSrc)
300
+ }
301
+ // var brandSrc = event_data.detail.appConfigs.brandSrc? : null;
299
302
 
300
303
  setTimeout(() => {
301
304
  if(event_data && event_data.detail && event_data.detail.appConfigs){
@@ -408,7 +411,7 @@
408
411
  var brandJson = JSON.parse(xhr.response)
409
412
  if(brandJson){
410
413
  /** TITLE AND FAVICON **/
411
- brandJson['WIDGET'].META_TITLE? document.body.title = brandJson['WIDGET'].META_TITLE : null;
414
+ brandJson['WIDGET'].META_TITLE? document.title = brandJson['WIDGET'].META_TITLE : null;
412
415
  brandJson['WIDGET'].FAVICON_URL? document.querySelector("link[rel~='icon']").setAttribute('href', brandJson['WIDGET'].FAVICON_URL) : null;
413
416
  /** FOOTER-LOGO **/
414
417
  brandJson['COMMON'].COMPANY_LOGO? document.getElementById('footer-logo').src = brandJson['COMMON'].COMPANY_LOGO : null;
@@ -421,6 +424,13 @@
421
424
  brandJson['COMMON'].DOCS? null: document.getElementById('share').style.display = 'none'
422
425
  /** IFRAME TITLE**/
423
426
  brandJson['COMMON'].COMPANY_LOGO? document.getElementById('tiledeskiframe').title = brandJson['COMMON'].COMPANY_NAME + ' Widget' : null;
427
+ /** META sharing ELEMENTS */
428
+ if(brandJson['WIDGET'].META_SHARE_INFO && Object.keys(brandJson['WIDGET'].META_SHARE_INFO).length > 0){
429
+ Object.keys(brandJson['WIDGET'].META_SHARE_INFO).forEach(key => {
430
+ var meta = document.querySelector("meta[property^='og:"+key.toLowerCase()+"']");
431
+ meta.setAttribute('content', brandJson['WIDGET'].META_SHARE_INFO[key])
432
+ })
433
+ }
424
434
  }
425
435
 
426
436
  }
@@ -194,9 +194,13 @@ export function isEmojii(message: any){
194
194
  // https://localcoder.org/javascript-detect-if-a-string-contains-only-unicode-emojis
195
195
  try {
196
196
  if(!message) return false;
197
+ // Removes all characters that are NOT emojis (unicode > \u1eeff)
197
198
  const onlyEmojis = message.replace(new RegExp('[\u0000-\u1eeff]', 'g'), '')
199
+ // Removes spaces, line breaks, and carriage returns from the string
198
200
  const visibleChars = message.replace(new RegExp('[\n\r\s]+|( )+', 'g'), '')
201
+ // Removes Chinese characters from the string
199
202
  const chineseChars = message.replace(new RegExp('[\u4e00-\u9fa5]', 'g'), '')
203
+
200
204
  if(onlyEmojis === '' || visibleChars == '' || chineseChars=='') return false
201
205
  return (onlyEmojis.length === visibleChars.length && onlyEmojis.length <= 2)
202
206
  } catch(e) {
@@ -204,6 +208,22 @@ export function isEmojii(message: any){
204
208
  }
205
209
  }
206
210
 
211
+ export function findAndRemoveEmoji(text): string{
212
+
213
+ if (!text) return text;
214
+
215
+ // Regex to find most unicode emojis
216
+ // Source: https://thekevinscott.com/emojis-in-javascript/
217
+ const emojiRegex = /([\u2700-\u27BF]|[\uE000-\uF8FF]|[\uD83C-\uDBFF][\uDC00-\uDFFF]|[\u2011-\u26FF]|\uD83D[\uDE00-\uDE4F])/g;
218
+
219
+ // Removes all found emojis
220
+ text = text.replace(emojiRegex, '');
221
+
222
+ // Removes any multiple spaces left by the removal of emojis
223
+ text = text.replace(/\s{2,}/g, ' ').trim();
224
+ return text
225
+ }
226
+
207
227
  export function isJustRecived(startedAt, time) {
208
228
  if (time > startedAt) {
209
229
  return true;
@@ -24,7 +24,8 @@ export class ProjectModel {
24
24
  public color?: string,
25
25
  public welcomeTitle?: string,
26
26
  public welcomeMsg?: string,
27
- public attributes?: IRules
27
+ public attributes?: IRules,
28
+ public settings?:{ [key: string]: any },
28
29
  ) { }
29
30
 
30
31
  initialize (
@@ -43,6 +44,7 @@ export class ProjectModel {
43
44
  trialDaysLeft?: number,
44
45
  trialExpired?: boolean,
45
46
  updatedAt?: string,
47
+ settings?: { [key: string]: any },
46
48
  versions?: string,
47
49
  ) {
48
50
  this.id = id;
@@ -60,6 +62,7 @@ export class ProjectModel {
60
62
  this.trialDaysLeft = trialDaysLeft;
61
63
  this.trialExpired = trialExpired;
62
64
  this.updatedAt = updatedAt;
65
+ this.settings = settings;
63
66
  this.versions = versions;
64
67
  }
65
68