@chat21/chat21-web-widget 5.1.20 → 5.1.24-rc1
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/.github/workflows/docker-community-push-latest.yml +23 -13
- package/.github/workflows/docker-image-tag-community-tag-push.yml +22 -12
- package/CHANGELOG.md +64 -2
- package/Dockerfile +4 -5
- package/angular.json +2 -1
- package/deploy_amazon_beta.sh +17 -7
- package/package.json +1 -1
- package/src/app/app.component.html +8 -1
- package/src/app/app.component.scss +59 -0
- package/src/app/app.component.ts +92 -34
- package/src/app/component/conversation-detail/conversation/conversation.component.ts +18 -4
- package/src/app/component/conversation-detail/conversation-footer/conversation-footer.component.scss +25 -0
- package/src/app/component/conversation-detail/conversation-footer/conversation-footer.component.ts +2 -1
- package/src/app/component/home-conversations/home-conversations.component.html +16 -6
- package/src/app/component/home-conversations/home-conversations.component.ts +29 -0
- package/src/app/component/launcher-button/launcher-button.component.html +1 -1
- package/src/app/component/launcher-button/launcher-button.component.ts +3 -2
- package/src/app/component/list-conversations/list-conversations.component.html +8 -3
- package/src/app/component/list-conversations/list-conversations.component.ts +29 -0
- package/src/app/pipe/marked.pipe.ts +27 -154
- package/src/app/providers/global-settings.service.ts +1 -1
- package/src/app/providers/translator.service.ts +2 -0
- package/src/app/utils/globals.ts +1 -1
- package/src/assets/i18n/en.json +1 -0
- package/src/assets/i18n/es.json +1 -0
- package/src/assets/i18n/fr.json +1 -0
- package/src/assets/i18n/it.json +1 -0
- package/src/chat21-core/providers/abstract/upload.service.ts +5 -1
- package/src/chat21-core/providers/firebase/firebase-upload.service.ts +141 -12
- package/src/chat21-core/providers/native/native-image-repo.ts +1 -1
- package/src/chat21-core/providers/native/native-upload-service.ts +143 -46
- package/src/chat21-core/providers/tiledesk/tiledesk-requests.service.ts +1 -1
- package/src/chat21-core/utils/utils.ts +5 -2
- package/src/iframe-style.css +5 -5
- package/deploy_amazon_prod.sh +0 -41
package/src/app/component/conversation-detail/conversation-footer/conversation-footer.component.scss
CHANGED
|
@@ -419,3 +419,28 @@ textarea:active{
|
|
|
419
419
|
border: none;
|
|
420
420
|
// margin: -2px -2px 0px;
|
|
421
421
|
}
|
|
422
|
+
|
|
423
|
+
|
|
424
|
+
// aggiungi un'animazione di fade in e fade out quando .star-rating-widget è visibile con transition
|
|
425
|
+
.star-rating-widget {
|
|
426
|
+
transition: all 0.5s ease-in-out;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
.star-rating-widget {
|
|
430
|
+
position: absolute;
|
|
431
|
+
left: 0;
|
|
432
|
+
right: 0;
|
|
433
|
+
bottom: -52px;
|
|
434
|
+
height: 100%;
|
|
435
|
+
width: 100%;
|
|
436
|
+
flex-direction: row;
|
|
437
|
+
justify-content: center;
|
|
438
|
+
background-color: rgb(255, 255, 255);
|
|
439
|
+
flex-wrap: nowrap;
|
|
440
|
+
&.active {
|
|
441
|
+
bottom: 0px;
|
|
442
|
+
}
|
|
443
|
+
&.inactive {
|
|
444
|
+
bottom: -52px;
|
|
445
|
+
}
|
|
446
|
+
}
|
package/src/app/component/conversation-detail/conversation-footer/conversation-footer.component.ts
CHANGED
|
@@ -87,6 +87,7 @@ export class ConversationFooterComponent implements OnInit, OnChanges {
|
|
|
87
87
|
|
|
88
88
|
file_size_limit = FILE_SIZE_LIMIT;
|
|
89
89
|
attachmentTooltip: string = '';
|
|
90
|
+
isErrorNetwork: boolean = false;
|
|
90
91
|
|
|
91
92
|
|
|
92
93
|
convertColorToRGBA = convertColorToRGBA;
|
|
@@ -316,7 +317,7 @@ export class ConversationFooterComponent implements OnInit, OnChanges {
|
|
|
316
317
|
// });
|
|
317
318
|
// this.resetLoadImage();
|
|
318
319
|
|
|
319
|
-
this.uploadService.
|
|
320
|
+
this.uploadService.uploadFile(this.senderId, currentUpload).then(data => {
|
|
320
321
|
that.logger.log('[CONV-FOOTER] AppComponent::uploadSingle:: downloadURL', data);
|
|
321
322
|
that.logger.log(`[CONV-FOOTER] Successfully uploaded file and got download link - ${data}`);
|
|
322
323
|
|
|
@@ -38,9 +38,14 @@
|
|
|
38
38
|
<!--CASE: no conversations EXIST - >1 agents is available -->
|
|
39
39
|
<div *ngIf="(!listConversations || listConversations.length == 0) && availableAgents && availableAgents.length > 1 && g.showAvailableAgents === true" style="display: flex; margin: 20px 30px;">
|
|
40
40
|
<div *ngFor="let agent of availableAgents" class="c21-pallozzo">
|
|
41
|
-
<div class="c21-ball" [ngStyle] = "{ 'background-color':setColorFromString(agent.firstname) }" >
|
|
42
|
-
<span class="c21-ball-label">{{avatarPlaceholder(agent.firstname)}}</span>
|
|
43
|
-
<
|
|
41
|
+
<div class="c21-ball" [ngStyle] = "{ 'background-color': isImageLoaded(agent) ? 'transparent' : setColorFromString(agent.firstname) }" >
|
|
42
|
+
<span *ngIf="!isImageLoaded(agent)" class="c21-ball-label">{{avatarPlaceholder(agent.firstname)}}</span>
|
|
43
|
+
<img *ngIf="agent.imageurl"
|
|
44
|
+
[src]="agent.imageurl"
|
|
45
|
+
style="display: none;"
|
|
46
|
+
(load)="onImageLoad(agent)"
|
|
47
|
+
(error)="onImageError(agent)">
|
|
48
|
+
<div *ngIf="isImageLoaded(agent)" #avatarImage class="c21-avatar-image" [style.background-image]="'url(' + agent.imageurl + ')'"></div>
|
|
44
49
|
</div>
|
|
45
50
|
</div>
|
|
46
51
|
</div>
|
|
@@ -48,9 +53,14 @@
|
|
|
48
53
|
<!--CASE: no conversations EXIST - 1 agents is available -->
|
|
49
54
|
<div class="flex-container" *ngIf="(!listConversations || listConversations.length == 0) && availableAgents && availableAgents.length === 1 && g.showAvailableAgents === true">
|
|
50
55
|
<div *ngFor="let agent of availableAgents" class="c21-pallozzo flex-inline-agent ">
|
|
51
|
-
<div class="c21-ball" [ngStyle] = "{ 'background-color':setColorFromString(agent.firstname) }" >
|
|
52
|
-
<span class="c21-ball-label">{{avatarPlaceholder(agent.firstname)}}</span>
|
|
53
|
-
<
|
|
56
|
+
<div class="c21-ball" [ngStyle] = "{ 'background-color': isImageLoaded(agent) ? 'transparent' : setColorFromString(agent.firstname) }" >
|
|
57
|
+
<span *ngIf="!isImageLoaded(agent)" class="c21-ball-label">{{avatarPlaceholder(agent.firstname)}}</span>
|
|
58
|
+
<img *ngIf="agent.imageurl"
|
|
59
|
+
[src]="agent.imageurl"
|
|
60
|
+
style="display: none;"
|
|
61
|
+
(load)="onImageLoad(agent)"
|
|
62
|
+
(error)="onImageError(agent)">
|
|
63
|
+
<div *ngIf="isImageLoaded(agent)" #avatarImage class="c21-avatar-image" [style.background-image]="'url(' + agent.imageurl + ')'"></div>
|
|
54
64
|
</div>
|
|
55
65
|
</div>
|
|
56
66
|
<button tabindex="1040" aflistconv #aflistconv class="c21-button-primary" (click)="openNewConversation()" [ngStyle]="{'background-color': g.themeColor, 'border-color': g.themeColor, 'color': g.themeForegroundColor }">
|
|
@@ -65,6 +65,7 @@ export class HomeConversationsComponent implements OnInit, OnDestroy {
|
|
|
65
65
|
themeForegroundColor = '';
|
|
66
66
|
LABEL_START_NW_CONV: string;
|
|
67
67
|
availableAgents: Array<UserAgent> = [];
|
|
68
|
+
imageLoadedMap: Map<string, boolean> = new Map<string, boolean>();
|
|
68
69
|
// ========= end:: variabili del componente ======== //
|
|
69
70
|
|
|
70
71
|
waitingTime: number;
|
|
@@ -203,6 +204,34 @@ export class HomeConversationsComponent implements OnInit, OnDestroy {
|
|
|
203
204
|
this.onConversationLoaded.emit(conversation)
|
|
204
205
|
}
|
|
205
206
|
|
|
207
|
+
/**
|
|
208
|
+
* Verifica se l'immagine dell'agente esiste e si carica correttamente
|
|
209
|
+
*/
|
|
210
|
+
isImageLoaded(agent: UserAgent): boolean {
|
|
211
|
+
if (!agent?.imageurl) {
|
|
212
|
+
return false;
|
|
213
|
+
}
|
|
214
|
+
return this.imageLoadedMap.get(agent.id) === true;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Gestisce il caricamento riuscito dell'immagine
|
|
219
|
+
*/
|
|
220
|
+
onImageLoad(agent: UserAgent) {
|
|
221
|
+
if (agent?.id && agent?.imageurl) {
|
|
222
|
+
this.imageLoadedMap.set(agent.id, true);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Gestisce l'errore di caricamento dell'immagine
|
|
228
|
+
*/
|
|
229
|
+
onImageError(agent: UserAgent) {
|
|
230
|
+
if (agent?.id) {
|
|
231
|
+
this.imageLoadedMap.set(agent.id, false);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
206
235
|
private openConversationByID(conversation) {
|
|
207
236
|
this.logger.debug('[HOMECONVERSATIONS] openConversationByID: ', conversation);
|
|
208
237
|
if ( conversation ) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<!-- tabindex="000"-->
|
|
2
2
|
|
|
3
3
|
<button aflauncherbutton #aflauncherbutton id="c21-launcher-button" class="c21-button-clean scale-in-center"
|
|
4
|
-
*ngIf="g.
|
|
4
|
+
*ngIf="g.isOpen == false"
|
|
5
5
|
[ngClass]="{'c21-align-left' : g.align === 'left', 'c21-align-right' : g.align !== 'left'}"
|
|
6
6
|
[ngStyle]="{ 'background-color': g.baloonImage? null: g.themeColor, 'bottom': g.marginY+'px!important', 'left':(g.align==='left')?g.marginX+'px!important':'', 'right':(g.align==='right')?g.marginX+'px!important':'', 'width': g.launcherWidth, 'height': g.launcherHeight, 'border-radius': g.baloonShape}"
|
|
7
7
|
(click)="openCloseWidget()"
|
|
@@ -67,8 +67,9 @@ export class LauncherButtonComponent implements OnInit, AfterViewInit {
|
|
|
67
67
|
// this.g.isOpen = !this.g.isOpen;
|
|
68
68
|
// this.g.setIsOpen(!this.g.isOpen);
|
|
69
69
|
// this.appStorageService.setItem('isOpen', this.g.isOpen);
|
|
70
|
-
|
|
71
|
-
}
|
|
70
|
+
|
|
71
|
+
}
|
|
72
|
+
this.onButtonClicked.emit( this.g.isOpen );
|
|
72
73
|
}
|
|
73
74
|
|
|
74
75
|
}
|
|
@@ -3,9 +3,14 @@
|
|
|
3
3
|
<button tabindex="1103" class="c21-item-conversation" (click)="openConversationByID(conversation)">
|
|
4
4
|
<div class="c21-body-conv">
|
|
5
5
|
<div class="c21-left-conv">
|
|
6
|
-
<div class="c21-ball" [ngStyle]="{'background': 'linear-gradient(rgb(255,255,255) -125%, ' + conversation.color + ')'}">
|
|
7
|
-
<span class="c21-ball-label">{{conversation?.avatar}}</span>
|
|
8
|
-
<
|
|
6
|
+
<div class="c21-ball" [ngStyle]="{'background': isImageLoaded(conversation) ? 'transparent' : 'linear-gradient(rgb(255,255,255) -125%, ' + conversation.color + ')'}">
|
|
7
|
+
<span *ngIf="!isImageLoaded(conversation)" class="c21-ball-label">{{conversation?.avatar}}</span>
|
|
8
|
+
<img *ngIf="conversation?.image"
|
|
9
|
+
[src]="conversation.image"
|
|
10
|
+
style="display: none;"
|
|
11
|
+
(load)="onImageLoad(conversation)"
|
|
12
|
+
(error)="onImageError(conversation)">
|
|
13
|
+
<div *ngIf="isImageLoaded(conversation)" #avatarImage class="c21-avatar-image" [style.background-image]="'url(' + conversation.image + ')'"></div>
|
|
9
14
|
</div>
|
|
10
15
|
</div>
|
|
11
16
|
<div class="c21-right-conv">
|
|
@@ -35,6 +35,7 @@ export class ListConversationsComponent implements OnInit {
|
|
|
35
35
|
arrayDiffer: any;
|
|
36
36
|
|
|
37
37
|
uidConvSelected: string;
|
|
38
|
+
imageLoadedMap: Map<string, boolean> = new Map<string, boolean>();
|
|
38
39
|
constructor(private iterableDiffers: IterableDiffers) {
|
|
39
40
|
this.iterableDifferListConv = this.iterableDiffers.find([]).create(null);
|
|
40
41
|
|
|
@@ -67,5 +68,33 @@ export class ListConversationsComponent implements OnInit {
|
|
|
67
68
|
|
|
68
69
|
}
|
|
69
70
|
|
|
71
|
+
/**
|
|
72
|
+
* Verifica se l'immagine esiste e si carica correttamente
|
|
73
|
+
*/
|
|
74
|
+
isImageLoaded(conversation: ConversationModel): boolean {
|
|
75
|
+
if (!conversation?.image) {
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
return this.imageLoadedMap.get(conversation.uid) === true;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Gestisce il caricamento riuscito dell'immagine
|
|
83
|
+
*/
|
|
84
|
+
onImageLoad(conversation: ConversationModel) {
|
|
85
|
+
if (conversation?.uid && conversation?.image) {
|
|
86
|
+
this.imageLoadedMap.set(conversation.uid, true);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Gestisce l'errore di caricamento dell'immagine
|
|
92
|
+
*/
|
|
93
|
+
onImageError(conversation: ConversationModel) {
|
|
94
|
+
if (conversation?.uid) {
|
|
95
|
+
this.imageLoadedMap.set(conversation.uid, false);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
70
99
|
|
|
71
100
|
}
|
|
@@ -1,184 +1,57 @@
|
|
|
1
1
|
import { Pipe, PipeTransform } from '@angular/core';
|
|
2
2
|
import { marked } from 'marked';
|
|
3
|
-
import { BLOCKED_DOMAINS } from '../utils/utils';
|
|
4
|
-
import { htmlEntities } from 'src/chat21-core/utils/utils';
|
|
5
|
-
|
|
6
3
|
|
|
7
4
|
@Pipe({
|
|
8
5
|
name: 'marked'
|
|
9
6
|
})
|
|
10
|
-
|
|
11
7
|
export class MarkedPipe implements PipeTransform {
|
|
8
|
+
|
|
12
9
|
transform(value: any): any {
|
|
13
|
-
//
|
|
14
|
-
// - Do not allow raw HTML from chat messages to be interpreted as DOM.
|
|
15
|
-
// - Keep Markdown working (marked will generate the needed HTML tags).
|
|
16
|
-
// This makes inputs like "<h1>Title</h1>" render exactly as typed.
|
|
10
|
+
// Convertiamo tutto in stringa sicura
|
|
17
11
|
const input =
|
|
18
12
|
typeof value === 'string'
|
|
19
13
|
? value
|
|
20
14
|
: (value === null || value === undefined) ? '' : String(value);
|
|
21
|
-
|
|
22
|
-
// Converti
|
|
23
|
-
// così il markdown con breaks: true li renderizzerà correttamente
|
|
15
|
+
|
|
16
|
+
// Converti \n letterali in newline reali
|
|
24
17
|
const inputWithNewlines = input.replace(/\\n/g, '\n');
|
|
25
|
-
|
|
26
|
-
// Proteggi i > usati per i blockquote markdown (all'inizio di riga)
|
|
27
|
-
// sostituendoli temporaneamente con un placeholder
|
|
28
|
-
const BLOCKQUOTE_PLACEHOLDER = '___MARKDOWN_BLOCKQUOTE___';
|
|
29
|
-
const protectedInput = inputWithNewlines.replace(/^(\s*)>/gm, (match, spaces) => {
|
|
30
|
-
return spaces + BLOCKQUOTE_PLACEHOLDER;
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
// Applica htmlEntities (che codificherà tutti gli altri >)
|
|
34
|
-
let safeInput = htmlEntities(protectedInput);
|
|
35
|
-
|
|
36
|
-
// Ripristina i > dei blockquote
|
|
37
|
-
safeInput = safeInput.replace(new RegExp(BLOCKQUOTE_PLACEHOLDER, 'g'), '>');
|
|
38
18
|
|
|
19
|
+
// Renderer custom solo per i link
|
|
39
20
|
const renderer = new marked.Renderer();
|
|
21
|
+
const originalLinkRenderer = renderer.link.bind(renderer);
|
|
22
|
+
|
|
23
|
+
// Lista protocolli / pattern pericolosi
|
|
24
|
+
const dangerousPatterns = [
|
|
25
|
+
/^javascript:/i,
|
|
26
|
+
/^data:/i,
|
|
27
|
+
/^vbscript:/i
|
|
28
|
+
];
|
|
29
|
+
|
|
40
30
|
renderer.link = function({ href, title, tokens }) {
|
|
41
|
-
|
|
42
|
-
const normalized = (href || '').trim().toLowerCase();
|
|
43
|
-
// Pattern pericolosi da cercare nell'intero URL (non solo all'inizio)
|
|
44
|
-
const dangerousPatterns = [
|
|
45
|
-
/javascript:/i, // javascript: protocol
|
|
46
|
-
/data:/i, // data: protocol
|
|
47
|
-
/vbscript:/i, // vbscript: protocol
|
|
48
|
-
/on\w+\s*=/i, // event handlers (onclick, onload, etc.)
|
|
49
|
-
/alert\s*\(/i, // alert() function
|
|
50
|
-
/eval\s*\(/i, // eval() function
|
|
51
|
-
/document\./i, // document object access
|
|
52
|
-
/window\./i, // window object access
|
|
53
|
-
/\.appendChild\s*\(/i, // DOM manipulation
|
|
54
|
-
/\.createElement\s*\(/i, // DOM creation
|
|
55
|
-
/<script/i, // script tags
|
|
56
|
-
/<\/script>/i, // closing script tags
|
|
57
|
-
/function\s*\(/i, // function definitions
|
|
58
|
-
/\(function/i, // IIFE patterns
|
|
59
|
-
/setTimeout\s*\(/i, // setTimeout
|
|
60
|
-
/setInterval\s*\(/i, // setInterval
|
|
61
|
-
/location\./i, // location object manipulation
|
|
62
|
-
/history\./i, // history object manipulation
|
|
63
|
-
/localStorage\./i, // localStorage access
|
|
64
|
-
/sessionStorage\./i, // sessionStorage access
|
|
65
|
-
/cookie/i, // cookie manipulation
|
|
66
|
-
/fetch\s*\(/i, // fetch API
|
|
67
|
-
/XMLHttpRequest/i, // XHR
|
|
68
|
-
/FormData/i, // FormData
|
|
69
|
-
/Blob\s*\(/i, // Blob constructor
|
|
70
|
-
/FileReader/i, // FileReader
|
|
71
|
-
/crypto\./i, // crypto object
|
|
72
|
-
/btoa\s*\(/i, // base64 encoding
|
|
73
|
-
/atob\s*\(/i, // base64 decoding
|
|
74
|
-
/decodeURI/i, // URI decoding
|
|
75
|
-
/encodeURI/i, // URI encoding
|
|
76
|
-
/String\.fromCharCode/i, // character code conversion
|
|
77
|
-
/unescape\s*\(/i, // unescape function
|
|
78
|
-
/escape\s*\(/i // escape function
|
|
79
|
-
];
|
|
31
|
+
const normalized = (href || '').trim();
|
|
80
32
|
|
|
81
|
-
//
|
|
33
|
+
// Se il link è pericoloso, restituisci solo il testo
|
|
82
34
|
const isDangerous = dangerousPatterns.some(pattern => pattern.test(normalized));
|
|
83
35
|
if (isDangerous) {
|
|
84
|
-
|
|
85
|
-
return tokens ? tokens.map(token => token.raw).join('') : href || '';
|
|
36
|
+
return tokens ? tokens.map(t => t.raw).join('') : href || '';
|
|
86
37
|
}
|
|
87
38
|
|
|
88
|
-
//
|
|
89
|
-
|
|
90
|
-
const text = tokens
|
|
91
|
-
? tokens.map(token => token.raw).join('')
|
|
92
|
-
: href; // fallback se tokens non c'è
|
|
93
|
-
if (!href) return text;
|
|
94
|
-
|
|
95
|
-
return `<a href="${href}" target="_blank" rel="noopener noreferrer">${text}</a>`;
|
|
39
|
+
// Altrimenti delega al renderer originale di marked
|
|
40
|
+
return originalLinkRenderer({ href, title, tokens });
|
|
96
41
|
};
|
|
97
|
-
|
|
42
|
+
|
|
43
|
+
// Opzioni marked
|
|
98
44
|
marked.setOptions({
|
|
99
45
|
renderer,
|
|
100
46
|
gfm: true,
|
|
101
47
|
breaks: true
|
|
102
48
|
});
|
|
103
49
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
return safeInput;
|
|
110
|
-
}
|
|
50
|
+
try {
|
|
51
|
+
return marked.parse(inputWithNewlines);
|
|
52
|
+
} catch (err) {
|
|
53
|
+
console.error('Errore nel parsing markdown:', err);
|
|
54
|
+
return inputWithNewlines;
|
|
111
55
|
}
|
|
112
|
-
return safeInput;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
private cleanInput(input: string): string {
|
|
117
|
-
if (!input) return '';
|
|
118
|
-
let cleaned = (input || '').trim().toLowerCase();
|
|
119
|
-
|
|
120
|
-
BLOCKED_DOMAINS.forEach(domain => {
|
|
121
|
-
const escapedDomain = domain.replace(/\./g, '\\.');
|
|
122
|
-
// Pattern che copre TUTTI i casi
|
|
123
|
-
const comprehensivePattern = new RegExp(
|
|
124
|
-
`(\\[([^\\]]*)\\]\\([^)]*(?:https?://)?(?:www\\.)?${escapedDomain}(?:/[^)]*)?\\))|((?:https?://)?(?:www\\.)?${escapedDomain}(?:/\\S*)?)`,
|
|
125
|
-
'gi'
|
|
126
|
-
);
|
|
127
|
-
|
|
128
|
-
cleaned = cleaned.replace(comprehensivePattern, (match, p1, p2, p3) => {
|
|
129
|
-
// Se è un link markdown [text](url), mantieni il testo
|
|
130
|
-
if (p2) return `${p2} 🔒`;
|
|
131
|
-
// Se è un URL diretto, sostituisci con dominio + 🔒
|
|
132
|
-
if (p3) return `${domain} 🔒`;
|
|
133
|
-
return match;
|
|
134
|
-
});
|
|
135
|
-
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
// Pattern che sostituisce i link pericolosi con solo il testo
|
|
140
|
-
const dangerousLinkPatterns = [
|
|
141
|
-
// Sostituisce [text](javascript:...) con "text"
|
|
142
|
-
/\[([^\]]*)\]\(javascript:[^)]*\)/gi,
|
|
143
|
-
/\[([^\]]*)\]\(data:[^)]*\)/gi,
|
|
144
|
-
/\[([^\]]*)\]\(vbscript:[^)]*\)/gi,
|
|
145
|
-
/\[([^\]]*)\]\([^)]*alert\([^)]*\)/gi
|
|
146
|
-
];
|
|
147
|
-
|
|
148
|
-
dangerousLinkPatterns.forEach(pattern => {
|
|
149
|
-
cleaned = cleaned.replace(pattern, '$1'); // $1 = il testo del link
|
|
150
|
-
});
|
|
151
|
-
|
|
152
|
-
// Pattern generali per sicurezza (rimuovono completamente)
|
|
153
|
-
const generalPatterns = [
|
|
154
|
-
/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,
|
|
155
|
-
/<script[^>]*>[\s\S]*?<\/script>/gi, // Script multi-linea
|
|
156
|
-
/<script[^>]*>.*?<\/script>/gi, // Script single-line
|
|
157
|
-
/<script[^>]*>/gi, // Solo tag di apertura
|
|
158
|
-
/<\/script>/gi, // Solo tag di chiusura
|
|
159
|
-
/javascript:/gi,
|
|
160
|
-
/vbscript:/gi,
|
|
161
|
-
/data:/gi,
|
|
162
|
-
/on\w+\s*=/gi,
|
|
163
|
-
/alert\(/gi,
|
|
164
|
-
/eval\(/gi,
|
|
165
|
-
/document\./gi,
|
|
166
|
-
/window\./gi,
|
|
167
|
-
/\(function\s*\(\)\s*\{/gi,
|
|
168
|
-
/\.appendChild\(/gi,
|
|
169
|
-
/\.createElement\(/gi,
|
|
170
|
-
/\.getElementsByTagName\(/gi,
|
|
171
|
-
|
|
172
|
-
// ✅ PATTERN PER FUNZIONI IIFE (Immediately Invoked Function Expression):
|
|
173
|
-
/\(function\s*\(\s*\)\s*\{[\s\S]*?\}\)\(\s*\)\s*;/gi,
|
|
174
|
-
/\(function\s*\(\)\s*\{/gi
|
|
175
|
-
];
|
|
176
|
-
|
|
177
|
-
generalPatterns.forEach(pattern => {
|
|
178
|
-
cleaned = cleaned.replace(pattern, '');
|
|
179
|
-
});
|
|
180
|
-
|
|
181
|
-
return cleaned;
|
|
182
56
|
}
|
|
183
|
-
|
|
184
|
-
}
|
|
57
|
+
}
|
|
@@ -1963,7 +1963,7 @@ export class GlobalSettingsService {
|
|
|
1963
1963
|
this.logger.debug('[GLOBAL-SET] setDepartmentFromExternal > END departmentID ::::' + this.globals.departmentID + isValidID);
|
|
1964
1964
|
}
|
|
1965
1965
|
//remove default department from list
|
|
1966
|
-
this.globals.departments = this.globals.departments
|
|
1966
|
+
this.globals.departments = this.globals.departments?.filter(obj => obj['default'] !== true) ?? []
|
|
1967
1967
|
if(this.globals.departments && this.globals.departments.length === 1){
|
|
1968
1968
|
this.setDepartment(this.globals.departments[0])
|
|
1969
1969
|
}
|
|
@@ -302,6 +302,7 @@ export class TranslatorService {
|
|
|
302
302
|
'CLOSED',
|
|
303
303
|
'LABEL_PREVIEW',
|
|
304
304
|
'MAX_ATTACHMENT',
|
|
305
|
+
'MAX_ATTACHMENT_ERROR',
|
|
305
306
|
'EMOJI'
|
|
306
307
|
];
|
|
307
308
|
|
|
@@ -358,6 +359,7 @@ export class TranslatorService {
|
|
|
358
359
|
globals.LABEL_PREVIEW = res['LABEL_PREVIEW']
|
|
359
360
|
globals.LABEL_ERROR_FIELD_REQUIRED= res['LABEL_ERROR_FIELD_REQUIRED']
|
|
360
361
|
globals.MAX_ATTACHMENT = res['MAX_ATTACHMENT']
|
|
362
|
+
globals.MAX_ATTACHMENT_ERROR = res['MAX_ATTACHMENT_ERROR']
|
|
361
363
|
globals.EMOJI = res['EMOJI']
|
|
362
364
|
|
|
363
365
|
|
package/src/app/utils/globals.ts
CHANGED
|
@@ -247,7 +247,7 @@ export class Globals {
|
|
|
247
247
|
|
|
248
248
|
// ============ BEGIN: SET EXTERNAL PARAMETERS ==============//
|
|
249
249
|
this.baseLocation = 'https://widget.tiledesk.com/v2';
|
|
250
|
-
this.autoStart =
|
|
250
|
+
this.autoStart = false;
|
|
251
251
|
/** start Authentication and startUI */
|
|
252
252
|
this.startHidden = false;
|
|
253
253
|
/** show/hide all widget -> js call: showAllWidget */
|
package/src/assets/i18n/en.json
CHANGED
|
@@ -96,5 +96,6 @@
|
|
|
96
96
|
"EMOJI_NOT_ELLOWED":"Emoji not allowed",
|
|
97
97
|
"DOMAIN_NOT_ALLOWED":"URL contains a non-allowed domain",
|
|
98
98
|
"MAX_ATTACHMENT": "Max allowed size {{FILE_SIZE_LIMIT}}Mb",
|
|
99
|
+
"MAX_ATTACHMENT_ERROR": "The file exceeds the maximum allowed size",
|
|
99
100
|
"EMOJI": "Emoji"
|
|
100
101
|
}
|
package/src/assets/i18n/es.json
CHANGED
|
@@ -96,5 +96,6 @@
|
|
|
96
96
|
"EMOJI_NOT_ELLOWED":"Emoji no permitido",
|
|
97
97
|
"DOMAIN_NOT_ALLOWED":"La URL contiene un dominio no permitido",
|
|
98
98
|
"MAX_ATTACHMENT": "Tamaño máximo permitido {{FILE_SIZE_LIMIT}}Mb",
|
|
99
|
+
"MAX_ATTACHMENT_ERROR": "El archivo supera el tamaño máximo permitido",
|
|
99
100
|
"EMOJI": "Emoji"
|
|
100
101
|
}
|
package/src/assets/i18n/fr.json
CHANGED
|
@@ -96,5 +96,6 @@
|
|
|
96
96
|
"EMOJI_NOT_ELLOWED":"Emoji non autorisé",
|
|
97
97
|
"DOMAIN_NOT_ALLOWED":"L'URL contient un domaine non autorisé",
|
|
98
98
|
"MAX_ATTACHMENT": "Taille maximale autorisée {{FILE_SIZE_LIMIT}}Mo",
|
|
99
|
+
"MAX_ATTACHMENT_ERROR": "Le fichier dépasse la taille maximale autorisée",
|
|
99
100
|
"EMOJI": "Emoji"
|
|
100
101
|
}
|
package/src/assets/i18n/it.json
CHANGED
|
@@ -94,5 +94,6 @@
|
|
|
94
94
|
"EMOJI_NOT_ELLOWED":"Emoji non consentiti",
|
|
95
95
|
"DOMAIN_NOT_ALLOWED":"L'URL contiene un dominio non consentito",
|
|
96
96
|
"MAX_ATTACHMENT": "Dimensione massima consentita {{FILE_SIZE_LIMIT}}Mb",
|
|
97
|
+
"MAX_ATTACHMENT_ERROR": "Il file supera la dimensione massima consentita",
|
|
97
98
|
"EMOJI": "Emoji"
|
|
98
99
|
}
|
|
@@ -27,9 +27,13 @@ export abstract class UploadService {
|
|
|
27
27
|
abstract BSStateUpload: BehaviorSubject<any>;
|
|
28
28
|
|
|
29
29
|
// functions
|
|
30
|
-
abstract initialize(): void;
|
|
30
|
+
abstract initialize(projectId?: string): void;
|
|
31
31
|
abstract upload(userId: string, upload: UploadModel): Promise<{downloadURL: string, src: string}>;
|
|
32
|
+
abstract uploadFile(userId: string, upload: UploadModel): Promise<{downloadURL: string, src: string}>;
|
|
33
|
+
abstract uploadAsset(userId: string, upload: UploadModel, expiration?: number): Promise<{downloadURL: string, src: string}>;
|
|
32
34
|
abstract uploadProfile(userId: string, upload: UploadModel): Promise<any>;
|
|
33
35
|
abstract delete(userId: string, path: string): Promise<any>;
|
|
36
|
+
abstract deleteFile(userId: string, path: string): Promise<any>;
|
|
37
|
+
abstract deleteAsset(userId: string, path: string): Promise<any>
|
|
34
38
|
abstract deleteProfile(userId: string, path: string): Promise<any>
|
|
35
39
|
}
|