yelda-webchat 1.1.5 → 1.1.9

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "yelda-webchat",
3
- "version": "1.1.5",
3
+ "version": "1.1.9",
4
4
  "description": "Website Chat Plugin",
5
5
  "main": "./dist/js/yeldaWebchatInjector.min.js",
6
6
  "license": "MIT",
@@ -15,7 +15,8 @@ module.exports = {
15
15
  SENT: {
16
16
  OPEN_CHAT: 'openChat',
17
17
  LEAVE_VIEWPORT: 'leaveViewPort',
18
- SEND_USER_MESSAGE: 'sendUserMessage'
18
+ SEND_USER_MESSAGE: 'sendUserMessage',
19
+ URL_UPDATE: 'urlUpdate'
19
20
  },
20
21
  RECEIVED: {
21
22
  OPEN_CHAT: 'openChat',
@@ -23,7 +24,11 @@ module.exports = {
23
24
  ABSTAIN_LEAVE_VIEWPORT: 'abstainLeaveViewport',
24
25
  OPEN_LIGHT_GALLERY: 'openLightGallery',
25
26
  IS_SENDING_MESSAGE: 'isSendingMessage',
26
- LISTEN_LEAVE_VIEWPORT: 'listenLeaveViewport'
27
+ LISTEN_LEAVE_VIEWPORT: 'listenLeaveViewport',
28
+ LISTEN_URL_UPDATE: 'listenUrlUpdate',
29
+ ABSTAIN_URL_UPDATE: 'abstainUrlUpdate',
30
+ ADD_BUBBLE_TEXT: 'addBubbleText',
31
+ ADD_MINIMAL_NOTIFICATION_TEXT: 'addMinimalNotificationText'
27
32
  }
28
33
  }
29
34
  }
@@ -2,8 +2,34 @@
2
2
 
3
3
 
4
4
  .yelda_assistant_img {
5
- opacity: 0.93;
6
5
  cursor: pointer;
6
+ opacity: 1;
7
+ height: auto;
8
+ transition-property: opacity, height;
9
+ transition-duration: 1s, 1ms;
10
+ transition-delay: 1.2s, 1.2s;
11
+ animation-name: appear;
12
+ animation-duration: 0.8s;
13
+ animation-delay: 1s;
14
+ animation-timing-function: cubic-bezier(0.230, 1.000, 0.320, 1.000);
15
+ animation-iteration-count: 1;
16
+ animation-fill-mode: forwards;
17
+ opacity: 0;
18
+ overflow: hidden;
19
+ }
20
+
21
+ .yelda_container.y_active .yelda_assistant_img{
22
+ opacity:0 ;
23
+ height: 0;
24
+ transition-property: opacity, height;
25
+ transition-duration: 1ms, 1ms;
26
+ transition-delay: 0, 0;
27
+ animation: none;
28
+ }
29
+
30
+ .yelda_assistant_img.activated{
31
+ transition-delay: 1s;
32
+ /*animation-delay: 0s;*/
7
33
  }
8
34
 
9
35
  .yelda_assistant_img.default {
@@ -33,32 +59,27 @@
33
59
  }
34
60
 
35
61
  .yelda_assistant_img.default i{
36
- font-size: 32px !important;
37
- }
38
-
39
- .yelda_assistant_img:hover{
40
- animation-name: fonta;
41
- animation-duration: 300ms;
42
- opacity: 1;
43
- }
44
- .yelda_assistant_img.default:hover i{
45
62
  height: 60px;
46
63
  width: 60px;
47
64
  line-height: 65px;
48
65
  font-size: 35px !important;
49
66
  }
50
67
 
68
+ .yelda_container {
69
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif,
70
+ 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol';
71
+ font-weight: normal;
72
+ display: flex;
73
+ flex-direction: column-reverse;
74
+ align-items: flex-end;
75
+ }
76
+
51
77
  .yelda_container.inner {
52
78
  position: absolute !important;
53
79
  left: 45px;
54
80
  top: 80px;
55
81
  }
56
82
 
57
- @keyframes fonta {
58
- from {font-size:32px;}
59
- to {font-size:35px;}
60
- }
61
-
62
83
  #web_chat_frame{
63
84
  height: 100% !important;
64
85
  width: 100% !important;
@@ -73,12 +94,15 @@
73
94
 
74
95
  .yelda_iframe_container {
75
96
  width: 430px;
76
- height: calc(100vh - 170px);
97
+ height: 0;
77
98
  max-height: 600px;
78
- transition-duration: 1s;
79
- margin: 0px 20px 0 0px;
99
+ margin: 0;
80
100
  overflow: hidden;
101
+ transition-property: opacity, transform, height;
102
+ transition-duration: 1s, 1s, 1ms;
103
+ transition-delay: 0s, 0s, 1s;
81
104
  opacity: 0;
105
+ transform: translateY(20px);
82
106
  }
83
107
 
84
108
  .yelda_iframe_container:not(.voiceFirstUI) {
@@ -86,9 +110,13 @@
86
110
  border-radius: 6px;
87
111
  }
88
112
 
89
- .yelda_iframe_container.y_active{
113
+ .y_active .yelda_iframe_container{
114
+ height: calc(100vh - 170px);
115
+ transition-property: opacity, transform, height;
116
+ transition-duration: 1s, 1s, 1ms;
117
+ transition-delay: 0s, 0s, 0s;
90
118
  opacity: 1 !important;
91
- margin-bottom: 20px !important;
119
+ transform: translateY(0);
92
120
  }
93
121
 
94
122
  .lg-backdrop {
@@ -126,19 +154,109 @@
126
154
  border-radius: 0;
127
155
  margin: 0 !important;
128
156
  padding: 0;
129
- transition-duration: 0s;
130
157
  }
131
158
 
132
159
  .yelda_iframe_container.inner {
133
160
  width: 100%;
134
161
  box-shadow: 0 5px 32px rgba(0,0,0,.16);
135
162
  border-radius: 6px;
136
- transition-duration: 1s;
137
163
  margin: 0px 20px 0 0px;
138
164
  overflow: hidden;
139
165
  }
140
166
  }
167
+ /** CSS for assistant bubble text */
168
+ #yelda_assistant_bubble_text {
169
+ animation-name: fadeAndSlideInFromRight;
170
+ animation-duration: 1s;
171
+ animation-delay: 2s;
172
+ animation-timing-function: cubic-bezier(0.230, 1.000, 0.320, 1.000);
173
+ animation-iteration-count: 1;
174
+ animation-fill-mode: both;
175
+ padding: 7px 10px 6px;
176
+ border-radius: 8px;
177
+ box-shadow: #00000030 0 2px 8px 0;
178
+ background-color: white;
179
+ cursor: pointer;
180
+ right: 0;
181
+ bottom: 76px;
182
+ max-width: 300px;
183
+ text-align: left;
184
+ font-size: 14px;
185
+ font-weight: normal;
186
+ display: inline-block;
187
+ margin-bottom: 10px;
188
+ }
189
+ #yelda_assistant_bubble_text.y_notification{
190
+ animation-delay: 0s;
191
+ }
141
192
 
193
+ #yelda_assistant_bubble_text.hidden {
194
+ display: none;
195
+ }
142
196
 
197
+ .y_active #yelda_assistant_bubble_text{
198
+ display: none;
199
+ }
143
200
 
201
+ /* Close button for bubble text */
202
+ .bubbleCloseButton {
203
+ position: absolute;
204
+ color: white;
205
+ background: grey;
206
+ border-radius: 68%;
207
+ padding: 0;
208
+ font-size: 20px;
209
+ width: 27px;
210
+ height: 27px;
211
+ line-height: 22px;
212
+ text-align: center;
213
+ right: -14px;
214
+ top: -17px;
215
+ text-decoration: none !important;
216
+ transition: all 0.15s ease-in;
217
+ transform: scale(0.7);
218
+ opacity:0; /* Don't show the close button by default */
219
+ }
144
220
 
221
+ a.bubbleCloseButton:hover {
222
+ color: white;
223
+ background: #5a5a5a;
224
+ }
225
+
226
+ /* Show the close button on mouse hover */
227
+ #yelda_assistant_bubble_text:hover .bubbleCloseButton {
228
+ opacity:1;
229
+ transform: scale(1);
230
+ }
231
+
232
+ /* Show the close button always on mobile screen */
233
+ @media only screen and (max-width: 768px) {
234
+ .bubbleCloseButton {
235
+ opacity: 1;
236
+ transform: none;
237
+ }
238
+ }
239
+
240
+ /** Animation for assistant bubble text */
241
+ @keyframes fadeAndSlideInFromRight {
242
+ 0% {
243
+ transform: translateY(10%);
244
+ opacity: 0;
245
+ }
246
+ 100% {
247
+ transform: translateY(0%);
248
+ opacity: 1;
249
+ }
250
+ }
251
+
252
+
253
+ @keyframes appear {
254
+ from {
255
+ transform: scale(0.5);
256
+ opacity: 0;
257
+ }
258
+ to {
259
+ transform: scale(1);
260
+ opacity: 1;
261
+ }
262
+ }
@@ -82,24 +82,56 @@ class YeldaChat {
82
82
 
83
83
  /**
84
84
  * Create an assistantBubbleText component and add it to webChatContainer element
85
+ * @param {String} text - default null
86
+ * @param {String} cssClass - default ''
85
87
  */
86
- addAssistantBubbleText() {
87
- if (!this.webChatContainer || !this.webchatSettings || !this.webchatSettings.bubbleText) {
88
+ addAssistantBubbleText(text = null, cssClass = '') {
89
+ text = text || this.webchatSettings && this.webchatSettings.bubbleText
90
+ if (!this.webChatContainer || !text) {
88
91
  return
89
92
  }
90
93
 
94
+ // Remove the assistant bubble text element before adding a new one
95
+ this.removeElement('yelda_assistant_bubble_text')
91
96
  // create assistantBubbleText element and set the text
92
97
  this.assistantBubbleText = document.createElement('span')
93
98
  this.assistantBubbleText.setAttribute('id', 'yelda_assistant_bubble_text')
94
- this.assistantBubbleText.innerText = this.webchatSettings.bubbleText
99
+ this.assistantBubbleText.setAttribute('class', cssClass)
100
+ this.assistantBubbleText.innerText = text
101
+ this.addCloseButtonForBubbleText()
95
102
 
96
103
  // Add click event to assistant text
97
- this.assistantBubbleText.addEventListener('click', this.openChat)
104
+ this.assistantBubbleText.addEventListener('click', this.openChat.bind(this))
98
105
 
99
106
  // add assistant text to webChatContainer using the webchatSettings
100
107
  this.webChatContainer.appendChild(this.assistantBubbleText)
101
108
  }
102
109
 
110
+ /**
111
+ * Add close button for bubble text
112
+ */
113
+ addCloseButtonForBubbleText() {
114
+ // Create close button and add it to the bubble text container
115
+ const closeButton = document.createElement('a')
116
+ closeButton.setAttribute('class', 'bubbleCloseButton')
117
+ closeButton.setAttribute('id', 'yelda_assistant_bubble_text_close')
118
+ closeButton.setAttribute('href', '#')
119
+ closeButton.innerHTML = '×'
120
+ closeButton.onclick = this.closeBubbleText.bind(this)
121
+ this.assistantBubbleText.prepend(closeButton)
122
+ }
123
+
124
+ /**
125
+ * Callback function for bubble text close button
126
+ * @param {Event} event
127
+ */
128
+ closeBubbleText(event) {
129
+ event.preventDefault()
130
+ event.stopPropagation()
131
+ this.removeElement('yelda_assistant_bubble_text')
132
+ this.assistantBubbleText = null
133
+ }
134
+
103
135
  /**
104
136
  * Create assistantImage element and add it to webChatContainer element
105
137
  */
@@ -131,7 +163,7 @@ class YeldaChat {
131
163
  this.assistantImage.innerHTML = '<i class="fas fa-comment"></i>'
132
164
 
133
165
  // Add click event to assistant image
134
- this.assistantImage.addEventListener('click', this.openChat)
166
+ this.assistantImage.addEventListener('click', this.openChat.bind(this))
135
167
 
136
168
  // add assistantImage to webChatContainer using the webchatSettings
137
169
  this.updateAssistantImageWithAssistantSettings()
@@ -247,6 +279,43 @@ class YeldaChat {
247
279
  return url
248
280
  }
249
281
 
282
+ /**
283
+ * Add 'y_active' class to the webChatContainer if required
284
+ *
285
+ * To control the opacity of the iframe container (.yelda_iframe_container) and the assistant image (.yelda_assistant_img),
286
+ * We add and remove the "y_active" CSS class to the webChatContainer (.yelda_container) which is always visible
287
+ * By default
288
+ * - yelda_iframe_container is hidden (opacity: 0;)
289
+ * - assistant image is visible (opacity: 1 for yelda_assistant_img)
290
+ *
291
+ * When we add the class "y_active" to (webChatContainer)
292
+ * - yelda_iframe_container is now visible (.y_active .yelda_iframe_container => opacity: 1)
293
+ * - the assistant image is hidden (.yelda_container.y_active .yelda_assistant_img => opacity: 0
294
+ *
295
+ * @param {Boolean} data.shouldBeOpened
296
+ * @param {Boolean} data.isStartBtn
297
+ * @param {Boolean} data.canBeClosed
298
+ */
299
+ initWebChatContainerYActiveClass(data) {
300
+ // should be opened on load OR has a start button
301
+ const shoulBeOpened = data.shouldBeOpened || data.isStartBtn
302
+
303
+ // cannot be closed (canBeClosed explicitly set to false)
304
+ const cantBeClosed = data.hasOwnProperty('canBeClosed') && !data.canBeClosed
305
+
306
+ /**
307
+ * If we are sure that the webchat should be opened from the creation and displayed all the time
308
+ * we add the y_active class to webChatContainer at the creation
309
+ *
310
+ * Otherwise yelda_assistant_img click event management will take care of showing and hiding the webchat
311
+ * - closeChat() remove "y_active" from the yelda container
312
+ * - openChat() add y_active" to the yelda container
313
+ */
314
+ if (shoulBeOpened && cantBeClosed) {
315
+ this.webChatContainer.classList.add('y_active')
316
+ }
317
+ }
318
+
250
319
  /**
251
320
  * Create iframeContainer and it's child webchat iframe, and add it to webChatContainer
252
321
  * @param {String} url webchat url
@@ -273,24 +342,7 @@ class YeldaChat {
273
342
  classList += ' inner'
274
343
  }
275
344
 
276
- /**
277
- * add CSS class which controls the opacity of the frame container if the iframe:
278
- * - should be opened on load OR has a start button
279
- * - cannot be closed (canBeClosed explicitly set to false)
280
- *
281
- * If we are sure that the webchat should be opened and displayed all the time, we can add the y_active class right away
282
- * Otherwise,
283
- */
284
- if ((data.shouldBeOpened || data.isStartBtn) && data.hasOwnProperty('canBeClosed') && !data.canBeClosed) {
285
- classList += ' y_active'
286
- } else {
287
- /**
288
- * If the iframe can be closed, hide it by default
289
- * yelda_assistant_img click event management will take care of showing and hiding the webchat
290
- * And if it should be opened, the webchat will trigger the 'openChat' event on its own
291
- */
292
- this.iframeContainer.style.cssText = 'display: none;'
293
- }
345
+ this.initWebChatContainerYActiveClass(data)
294
346
 
295
347
  this.iframeContainer.setAttribute('class', classList)
296
348
  this.webChatContainer.appendChild(this.iframeContainer)
@@ -394,9 +446,23 @@ class YeldaChat {
394
446
  this.listenLeaveViewport(true)
395
447
  break
396
448
  case config.FRAME_EVENT_TYPES.RECEIVED.LISTEN_LEAVE_VIEWPORT:
397
- // Triggered from webchat to listen leave viewport event
398
- this.listenLeaveViewport()
399
- break
449
+ // Triggered from webchat to listen leave viewport event
450
+ this.listenLeaveViewport()
451
+ break
452
+ case config.FRAME_EVENT_TYPES.RECEIVED.LISTEN_URL_UPDATE:
453
+ // Triggered from webchat to listen for url update
454
+ this.listenUrlUpdate()
455
+ break
456
+ case config.FRAME_EVENT_TYPES.RECEIVED.ABSTAIN_URL_UPDATE:
457
+ // Triggered form webchat to stop listening for url update
458
+ this.listenUrlUpdate(true)
459
+ break
460
+ case config.FRAME_EVENT_TYPES.RECEIVED.ADD_BUBBLE_TEXT:
461
+ // Triggered from webchat to add assistant bubble text if possible
462
+ if (!this.configurationData.hasOwnProperty('canBeClosed') || this.configurationData.canBeClosed) {
463
+ this.addAssistantBubbleText()
464
+ }
465
+ break;
400
466
  }
401
467
  return
402
468
  }
@@ -423,6 +489,11 @@ class YeldaChat {
423
489
  window.dispatchEvent(new CustomEvent('isSendingMessage', { detail: eventData.data }))
424
490
  }
425
491
  break
492
+ case config.FRAME_EVENT_TYPES.RECEIVED.ADD_MINIMAL_NOTIFICATION_TEXT:
493
+ if (!this.configurationData.hasOwnProperty('canBeClosed') || this.configurationData.canBeClosed) {
494
+ this.addAssistantBubbleText(eventData.text, 'y_notification')
495
+ }
496
+ break;
426
497
  }
427
498
  }
428
499
 
@@ -479,15 +550,11 @@ class YeldaChat {
479
550
  * Close the webchat window
480
551
  */
481
552
  closeChat() {
482
- const assistantImgElement = document.getElementById('yelda_assistant_img')
483
- if (assistantImgElement !== null) {
484
- assistantImgElement.style.display = 'inline-block'
485
- }
486
-
487
- const yeldaIframeContainerElement = document.getElementById('yelda_iframe_container')
488
- if (yeldaIframeContainerElement !== null) {
489
- yeldaIframeContainerElement.classList.remove('y_active')
490
- yeldaIframeContainerElement.style.display = 'none'
553
+ // Closing the webchat = remove the y_active class on #yelda_container
554
+ // Hiding the iframe + showing back the bubble + animation => All is done in CSS
555
+ const yeldaContainerElement = document.getElementById('yelda_container')
556
+ if (yeldaContainerElement !== null) {
557
+ yeldaContainerElement.classList.remove('y_active')
491
558
  }
492
559
  }
493
560
 
@@ -495,17 +562,28 @@ class YeldaChat {
495
562
  * Open the webchat window
496
563
  */
497
564
  openChat() {
565
+ // Adding the class "activated" allows the CSS to change the transition/animation after the first opening
498
566
  const assistantImgElement = document.getElementById('yelda_assistant_img')
499
567
  if (assistantImgElement !== null) {
500
- assistantImgElement.style.display = 'none'
568
+ assistantImgElement.classList.add('activated')
501
569
  }
502
570
 
503
- const yeldaIframeContainerElement = document.getElementById('yelda_iframe_container')
504
- if (yeldaIframeContainerElement !== null) {
505
- yeldaIframeContainerElement.style.display = 'block'
506
- yeldaIframeContainerElement.classList.add('y_active')
571
+ // Opening the webchat = adding the y_active class on #yelda_container
572
+ // Showing the iframe + hiding bubble + hiding minimal notification + animation => All is done in CSS
573
+ const yeldaContainerElement = document.getElementById('yelda_container')
574
+ if (yeldaContainerElement !== null) {
575
+ yeldaContainerElement.classList.add('y_active')
507
576
  }
508
577
 
578
+ // Hide the assistant bubble text while opening the webchat window
579
+ // Note : bubble text container is also hidden in CSS
580
+ if (this.assistantBubbleText) {
581
+ this.assistantBubbleText = null
582
+ }
583
+
584
+ // Remove assistant bubble text while opening the webchat window
585
+ this.removeElement('yelda_assistant_bubble_text')
586
+
509
587
  // Propagate the event to the webchat
510
588
  document.getElementById('web_chat_frame').contentWindow.postMessage(config.FRAME_EVENT_TYPES.SENT.OPEN_CHAT, '*')
511
589
  }
@@ -779,8 +857,11 @@ class YeldaChat {
779
857
 
780
858
  // Remove event listeners when switching bots
781
859
  this.listenLeaveViewport(true)
860
+ this.listenUrlUpdate(true)
782
861
 
783
862
  this.loadChat(data)
863
+
864
+ this.configurationData = data
784
865
  return resolve()
785
866
  })
786
867
  }
@@ -887,7 +968,6 @@ class YeldaChat {
887
968
  // because the assistant image containing the openChat event wouldn't have been created
888
969
  if (!data.hasOwnProperty('canBeClosed') || data.canBeClosed) {
889
970
  this.addAssistantImage()
890
- this.addAssistantBubbleText()
891
971
  }
892
972
 
893
973
  // add the frame lister to receive message from iframe to the parent
@@ -905,7 +985,7 @@ class YeldaChat {
905
985
  if (this.bubbleContainer) {
906
986
  this.assistantImage.removeAttribute('id', 'yelda_assistant_img')
907
987
  this.assistantImage.removeAttribute('class', 'yelda_assistant_img default')
908
- this.assistantImage.removeEventListener('click', this.openChat)
988
+ this.assistantImage.removeEventListener('click', this.openChat.bind(this))
909
989
  this.assistantImage.replaceWith(this.bubbleContainerClone)
910
990
  } else {
911
991
  this.removeElement('yelda_assistant_img')
@@ -976,6 +1056,45 @@ class YeldaChat {
976
1056
  }
977
1057
  }
978
1058
 
1059
+ /**
1060
+ * Listen to url update on the page
1061
+ * @param {Boolean} shouldRemoveEvent
1062
+ */
1063
+ listenUrlUpdate(shouldRemoveEvent = false) {
1064
+ if (shouldRemoveEvent && this.urlObserver) {
1065
+ this.urlObserver.disconnect()
1066
+ this.urlObserver = null
1067
+ return
1068
+ }
1069
+
1070
+ try {
1071
+ this.previousUrl = ''
1072
+ this.urlObserver = new MutationObserver(() => {
1073
+ if (location.href === this.previousUrl) {
1074
+ return
1075
+ }
1076
+ this.previousUrl = location.href
1077
+ this.urlUpdateListener()
1078
+ })
1079
+
1080
+ this.urlObserver.observe(document, {childList: true, subtree: true})
1081
+ } catch(err) {
1082
+ console.warn('listenUrlUpdate error', err)
1083
+ }
1084
+ }
1085
+
1086
+ /**
1087
+ * Callback for url update listener
1088
+ */
1089
+ urlUpdateListener() {
1090
+ const webchatFrame = document.getElementById('web_chat_frame')
1091
+
1092
+ if (!webchatFrame) {
1093
+ return
1094
+ }
1095
+ webchatFrame.contentWindow.postMessage({ event: config.FRAME_EVENT_TYPES.SENT.URL_UPDATE, data: window.location.href }, '*')
1096
+ }
1097
+
979
1098
  /**
980
1099
  * Listen leave viewport event handling
981
1100
  * @param {Boolean} shouldRemoveEvent - default false
@@ -42,13 +42,11 @@ const argv = yargs
42
42
  })
43
43
  .argv
44
44
 
45
- console.log(argv)
46
-
47
45
  const main = async () => {
48
46
  try {
49
47
  // Check if there are any change that has not been committed before starting the script
50
48
  if (!(await checkIfAllChangesCommitted())) {
51
- //throw new Error('Some changes are not committed. Commit your changes or stash them before running this script.')
49
+ throw new Error('Some changes are not committed. Commit your changes or stash them before running this script.')
52
50
  }
53
51
 
54
52
  await uploadToS3()