@hellotext/hellotext 2.1.2 → 2.1.4
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/dist/hellotext.js +1 -1
- package/lib/api/forms.cjs +2 -1
- package/lib/api/forms.js +2 -1
- package/lib/api/webchat/messages.cjs +4 -3
- package/lib/api/webchat/messages.js +4 -2
- package/lib/api/webchats.cjs +2 -0
- package/lib/api/webchats.js +3 -1
- package/lib/builders/logo_builder.cjs +1 -1
- package/lib/builders/logo_builder.js +1 -1
- package/lib/channels/application_channel.cjs +9 -6
- package/lib/channels/application_channel.js +9 -6
- package/lib/channels/webchat_channel.cjs +42 -17
- package/lib/channels/webchat_channel.js +42 -17
- package/lib/controllers/webchat/emoji_picker_controller.cjs +6 -6
- package/lib/controllers/webchat/emoji_picker_controller.js +5 -14
- package/lib/controllers/webchat_controller.cjs +174 -47
- package/lib/controllers/webchat_controller.js +175 -48
- package/lib/core/configuration/locale.cjs +87 -0
- package/lib/core/configuration/locale.js +81 -0
- package/lib/core/configuration.cjs +11 -0
- package/lib/core/configuration.js +11 -0
- package/lib/core/index.cjs +15 -1
- package/lib/core/index.js +4 -2
- package/lib/locales/en.cjs +1 -1
- package/lib/locales/en.js +1 -1
- package/lib/locales/es.cjs +1 -1
- package/lib/locales/es.js +1 -1
- package/lib/models/business.cjs +11 -0
- package/lib/models/business.js +11 -0
- package/lib/models/form_collection.cjs +1 -0
- package/lib/models/form_collection.js +2 -1
- package/lib/models/index.cjs +4 -4
- package/lib/models/index.js +4 -4
- package/lib/models/query.cjs +1 -2
- package/lib/models/query.js +1 -2
- package/lib/models/session.cjs +1 -1
- package/lib/models/session.js +1 -1
- package/package.json +2 -1
- package/src/api/forms.js +3 -1
- package/src/api/webchat/messages.js +8 -6
- package/src/api/webchats.js +3 -1
- package/src/builders/logo_builder.js +1 -1
- package/src/channels/application_channel.js +12 -9
- package/src/channels/webchat_channel.js +33 -19
- package/src/controllers/webchat/emoji_picker_controller.js +9 -15
- package/src/controllers/webchat_controller.js +164 -43
- package/src/core/configuration/locale.js +50 -0
- package/src/core/configuration.js +11 -0
- package/src/core/index.js +3 -1
- package/src/locales/en.js +4 -4
- package/src/locales/es.js +4 -4
- package/src/models/business.js +12 -0
- package/src/models/form_collection.js +2 -1
- package/src/models/index.js +3 -3
- package/src/models/query.js +1 -3
- package/src/models/session.js +1 -1
|
@@ -5,6 +5,7 @@ import WebchatMessagesAPI from '../api/webchat/messages'
|
|
|
5
5
|
import WebchatChannel from '../channels/webchat_channel'
|
|
6
6
|
import Hellotext from '../hellotext'
|
|
7
7
|
|
|
8
|
+
import { Locale } from '../core'
|
|
8
9
|
import { Webchat as WebchatConfiguration, behaviors } from '../core/configuration/webchat'
|
|
9
10
|
|
|
10
11
|
import { LogoBuilder } from '../builders/logo_builder'
|
|
@@ -21,8 +22,15 @@ export default class extends Controller {
|
|
|
21
22
|
autoPlacement: { type: Boolean, default: false },
|
|
22
23
|
disabled: { type: Boolean, default: false },
|
|
23
24
|
nextPage: { type: Number, default: undefined },
|
|
25
|
+
fullScreenThreshold: { type: Number, default: 1024 },
|
|
26
|
+
typingIndicatorKeepAlive: { type: Number, default: 30000 }, // 30 seconds
|
|
27
|
+
offset: { type: Number, default: 24 },
|
|
28
|
+
padding: { type: Number, default: 24 },
|
|
29
|
+
optimisticTypingIndicatorWait: { type: Number, default: 1000 }, // 1 second
|
|
24
30
|
}
|
|
25
31
|
|
|
32
|
+
static classes = ['fadeOut']
|
|
33
|
+
|
|
26
34
|
static targets = [
|
|
27
35
|
'trigger',
|
|
28
36
|
'popover',
|
|
@@ -36,12 +44,13 @@ export default class extends Controller {
|
|
|
36
44
|
'messageTemplate',
|
|
37
45
|
'messagesContainer',
|
|
38
46
|
'title',
|
|
39
|
-
'onlineStatus',
|
|
40
47
|
'attachmentImage',
|
|
41
48
|
'footer',
|
|
42
49
|
'toolbar',
|
|
43
50
|
'message',
|
|
44
51
|
'unreadCounter',
|
|
52
|
+
'typingIndicator',
|
|
53
|
+
'typingIndicatorTemplate',
|
|
45
54
|
]
|
|
46
55
|
|
|
47
56
|
initialize() {
|
|
@@ -56,9 +65,8 @@ export default class extends Controller {
|
|
|
56
65
|
this.files = []
|
|
57
66
|
|
|
58
67
|
this.onMessageReceived = this.onMessageReceived.bind(this)
|
|
59
|
-
this.onConversationAssignment = this.onConversationAssignment.bind(this)
|
|
60
|
-
this.onAgentOnline = this.onAgentOnline.bind(this)
|
|
61
68
|
this.onMessageReaction = this.onMessageReaction.bind(this)
|
|
69
|
+
this.onTypingStart = this.onTypingStart.bind(this)
|
|
62
70
|
|
|
63
71
|
this.onScroll = this.onScroll.bind(this)
|
|
64
72
|
|
|
@@ -77,9 +85,8 @@ export default class extends Controller {
|
|
|
77
85
|
this.setupFloatingUI({ trigger: this.triggerTarget, popover: this.popoverTarget })
|
|
78
86
|
|
|
79
87
|
this.webChatChannel.onMessage(this.onMessageReceived)
|
|
80
|
-
this.webChatChannel.
|
|
88
|
+
this.webChatChannel.onTypingStart(this.onTypingStart)
|
|
81
89
|
|
|
82
|
-
this.webChatChannel.onAgentOnline(this.onAgentOnline)
|
|
83
90
|
this.webChatChannel.onReaction(this.onMessageReaction)
|
|
84
91
|
|
|
85
92
|
this.messagesContainerTarget.addEventListener('scroll', this.onScroll)
|
|
@@ -88,7 +95,7 @@ export default class extends Controller {
|
|
|
88
95
|
this.toolbarTarget.appendChild(LogoBuilder.build())
|
|
89
96
|
}
|
|
90
97
|
|
|
91
|
-
if (
|
|
98
|
+
if (this.shouldOpenOnMount) {
|
|
92
99
|
this.openValue = true
|
|
93
100
|
}
|
|
94
101
|
|
|
@@ -102,12 +109,94 @@ export default class extends Controller {
|
|
|
102
109
|
this.broadcastChannel.removeEventListener('message', this.onOutboundMessageSent)
|
|
103
110
|
this.messagesContainerTarget.removeEventListener('scroll', this.onScroll)
|
|
104
111
|
|
|
112
|
+
// Clean up typing indicator timeouts
|
|
113
|
+
this.clearTypingIndicator()
|
|
114
|
+
|
|
105
115
|
this.broadcastChannel.close()
|
|
106
116
|
this.floatingUICleanup()
|
|
107
117
|
|
|
108
118
|
super.disconnect()
|
|
109
119
|
}
|
|
110
120
|
|
|
121
|
+
onTypingStart() {
|
|
122
|
+
if (this.typingIndicatorVisible) {
|
|
123
|
+
return this.resetTypingIndicatorTimer()
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
this.showTypingIndicator()
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
showOptimisticTypingIndicator() {
|
|
130
|
+
if (this.typingIndicatorVisible) return
|
|
131
|
+
|
|
132
|
+
this.showTypingIndicator()
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
showTypingIndicator() {
|
|
136
|
+
this.clearTypingIndicator()
|
|
137
|
+
|
|
138
|
+
this.typingIndicatorVisible = true
|
|
139
|
+
const indicator = this.typingIndicatorTemplateTarget.cloneNode(true)
|
|
140
|
+
|
|
141
|
+
indicator.setAttribute('data-hellotext--webchat-target', 'typingIndicator')
|
|
142
|
+
indicator.style.display = 'flex'
|
|
143
|
+
|
|
144
|
+
this.messagesContainerTarget.appendChild(indicator)
|
|
145
|
+
|
|
146
|
+
requestAnimationFrame(() => {
|
|
147
|
+
this.messagesContainerTarget.scroll({
|
|
148
|
+
top: this.messagesContainerTarget.scrollHeight,
|
|
149
|
+
behavior: 'instant',
|
|
150
|
+
})
|
|
151
|
+
})
|
|
152
|
+
|
|
153
|
+
const timeout = this.typingIndicatorKeepAliveValue
|
|
154
|
+
|
|
155
|
+
this.incomingTypingIndicatorTimeout = setTimeout(() => {
|
|
156
|
+
this.clearTypingIndicator()
|
|
157
|
+
}, timeout)
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
resetTypingIndicatorTimer() {
|
|
161
|
+
if (!this.typingIndicatorVisible) return
|
|
162
|
+
|
|
163
|
+
// Clear existing timeout
|
|
164
|
+
clearTimeout(this.incomingTypingIndicatorTimeout)
|
|
165
|
+
clearTimeout(this.optimisticTypingTimeout)
|
|
166
|
+
|
|
167
|
+
// Use unified timeout for all typing indicators
|
|
168
|
+
const timeout = this.typingIndicatorKeepAliveValue
|
|
169
|
+
|
|
170
|
+
this.incomingTypingIndicatorTimeout = setTimeout(() => {
|
|
171
|
+
this.clearTypingIndicator()
|
|
172
|
+
}, timeout)
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
clearTypingIndicator() {
|
|
176
|
+
if (this.hasTypingIndicatorTarget) {
|
|
177
|
+
this.typingIndicatorTarget.remove()
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
this.typingIndicatorVisible = false
|
|
181
|
+
|
|
182
|
+
clearTimeout(this.incomingTypingIndicatorTimeout)
|
|
183
|
+
clearTimeout(this.optimisticTypingTimeout)
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
onMessageInputChange() {
|
|
187
|
+
this.resizeInput()
|
|
188
|
+
clearTimeout(this.typingIndicatorTimeout)
|
|
189
|
+
|
|
190
|
+
if (!this.hasSentTypingIndicator) {
|
|
191
|
+
this.webChatChannel.startTypingIndicator()
|
|
192
|
+
this.hasSentTypingIndicator = true
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
this.typingIndicatorTimeout = setTimeout(() => {
|
|
196
|
+
this.hasSentTypingIndicator = false
|
|
197
|
+
}, 3000)
|
|
198
|
+
}
|
|
199
|
+
|
|
111
200
|
onOutboundMessageSent(event) {
|
|
112
201
|
const { data } = event
|
|
113
202
|
|
|
@@ -116,7 +205,13 @@ export default class extends Controller {
|
|
|
116
205
|
const element = new DOMParser().parseFromString(data.element, 'text/html').body
|
|
117
206
|
.firstElementChild
|
|
118
207
|
|
|
119
|
-
|
|
208
|
+
// Insert message before typing indicator if one exists
|
|
209
|
+
if (this.typingIndicatorVisible && this.hasTypingIndicatorTarget) {
|
|
210
|
+
this.messagesContainerTarget.insertBefore(element, this.typingIndicatorTarget)
|
|
211
|
+
} else {
|
|
212
|
+
this.messagesContainerTarget.appendChild(element)
|
|
213
|
+
}
|
|
214
|
+
|
|
120
215
|
element.scrollIntoView({ behavior: 'instant' })
|
|
121
216
|
},
|
|
122
217
|
'message:failed': data => {
|
|
@@ -205,8 +300,18 @@ export default class extends Controller {
|
|
|
205
300
|
}
|
|
206
301
|
}
|
|
207
302
|
|
|
303
|
+
closePopover() {
|
|
304
|
+
this.popoverTarget.classList.add(...this.fadeOutClasses)
|
|
305
|
+
|
|
306
|
+
setTimeout(() => {
|
|
307
|
+
this.openValue = false
|
|
308
|
+
}, 250)
|
|
309
|
+
}
|
|
310
|
+
|
|
208
311
|
onPopoverOpened() {
|
|
209
|
-
this.
|
|
312
|
+
if (!this.onMobile) {
|
|
313
|
+
this.inputTarget.focus()
|
|
314
|
+
}
|
|
210
315
|
|
|
211
316
|
if (!this.scrolled) {
|
|
212
317
|
requestAnimationFrame(() => {
|
|
@@ -220,7 +325,6 @@ export default class extends Controller {
|
|
|
220
325
|
}
|
|
221
326
|
|
|
222
327
|
Hellotext.eventEmitter.dispatch('webchat:opened')
|
|
223
|
-
|
|
224
328
|
localStorage.setItem(`hellotext--webchat--${this.idValue}`, 'opened')
|
|
225
329
|
|
|
226
330
|
if (this.unreadCounterTarget.style.display === 'none') return
|
|
@@ -233,11 +337,6 @@ export default class extends Controller {
|
|
|
233
337
|
|
|
234
338
|
onPopoverClosed() {
|
|
235
339
|
Hellotext.eventEmitter.dispatch('webchat:closed')
|
|
236
|
-
|
|
237
|
-
setTimeout(() => {
|
|
238
|
-
this.inputTarget.value = ''
|
|
239
|
-
})
|
|
240
|
-
|
|
241
340
|
localStorage.setItem(`hellotext--webchat--${this.idValue}`, 'closed')
|
|
242
341
|
}
|
|
243
342
|
|
|
@@ -266,7 +365,7 @@ export default class extends Controller {
|
|
|
266
365
|
}
|
|
267
366
|
|
|
268
367
|
onMessageReceived(message) {
|
|
269
|
-
const { body, attachments } = message
|
|
368
|
+
const { id, body, attachments } = message
|
|
270
369
|
|
|
271
370
|
const div = document.createElement('div')
|
|
272
371
|
div.innerHTML = body
|
|
@@ -287,6 +386,11 @@ export default class extends Controller {
|
|
|
287
386
|
})
|
|
288
387
|
}
|
|
289
388
|
|
|
389
|
+
// Clear any typing indicator when message is received
|
|
390
|
+
if (this.typingIndicatorVisible) {
|
|
391
|
+
this.clearTypingIndicator()
|
|
392
|
+
}
|
|
393
|
+
|
|
290
394
|
this.messagesContainerTarget.appendChild(element)
|
|
291
395
|
|
|
292
396
|
Hellotext.eventEmitter.dispatch('webchat:message:received', {
|
|
@@ -295,9 +399,11 @@ export default class extends Controller {
|
|
|
295
399
|
})
|
|
296
400
|
|
|
297
401
|
element.scrollIntoView({ behavior: 'smooth' })
|
|
298
|
-
this.setOfflineTimeout()
|
|
299
402
|
|
|
300
|
-
if (this.openValue)
|
|
403
|
+
if (this.openValue) {
|
|
404
|
+
this.messagesAPI.markAsSeen(id)
|
|
405
|
+
return
|
|
406
|
+
}
|
|
301
407
|
|
|
302
408
|
this.unreadCounterTarget.style.display = 'flex'
|
|
303
409
|
|
|
@@ -305,24 +411,20 @@ export default class extends Controller {
|
|
|
305
411
|
this.unreadCounterTarget.innerText = unreadCount > 99 ? '99+' : unreadCount
|
|
306
412
|
}
|
|
307
413
|
|
|
308
|
-
|
|
309
|
-
const
|
|
414
|
+
resizeInput() {
|
|
415
|
+
const maxHeight = 96
|
|
310
416
|
|
|
311
|
-
|
|
417
|
+
// Temporarily reset height to its natural content size
|
|
418
|
+
this.inputTarget.style.height = 'auto'
|
|
312
419
|
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
this.onlineStatusTarget.style.display = 'none'
|
|
317
|
-
}
|
|
318
|
-
}
|
|
420
|
+
// Set the height to the scrollHeight, which is the minimum height
|
|
421
|
+
// the element needs to fit its content without a scrollbar.
|
|
422
|
+
const scrollHeight = this.inputTarget.scrollHeight
|
|
319
423
|
|
|
320
|
-
|
|
321
|
-
this.onlineStatusTarget.style.display = 'flex'
|
|
322
|
-
this.setOfflineTimeout()
|
|
424
|
+
this.inputTarget.style.height = `${Math.min(scrollHeight, maxHeight)}px`
|
|
323
425
|
}
|
|
324
426
|
|
|
325
|
-
async sendMessage() {
|
|
427
|
+
async sendMessage(e) {
|
|
326
428
|
const formData = new FormData()
|
|
327
429
|
|
|
328
430
|
const message = {
|
|
@@ -331,6 +433,10 @@ export default class extends Controller {
|
|
|
331
433
|
}
|
|
332
434
|
|
|
333
435
|
if (this.inputTarget.value.trim().length === 0 && this.files.length === 0) {
|
|
436
|
+
if (e && e.target) {
|
|
437
|
+
e.preventDefault()
|
|
438
|
+
}
|
|
439
|
+
|
|
334
440
|
return
|
|
335
441
|
}
|
|
336
442
|
|
|
@@ -345,6 +451,7 @@ export default class extends Controller {
|
|
|
345
451
|
})
|
|
346
452
|
|
|
347
453
|
formData.append('session', Hellotext.session)
|
|
454
|
+
formData.append('locale', Locale.toString())
|
|
348
455
|
|
|
349
456
|
const element = this.messageTemplateTarget.cloneNode(true)
|
|
350
457
|
|
|
@@ -369,7 +476,13 @@ export default class extends Controller {
|
|
|
369
476
|
})
|
|
370
477
|
}
|
|
371
478
|
|
|
372
|
-
|
|
479
|
+
// Insert message before typing indicator if one exists
|
|
480
|
+
if (this.typingIndicatorVisible && this.hasTypingIndicatorTarget) {
|
|
481
|
+
this.messagesContainerTarget.insertBefore(element, this.typingIndicatorTarget)
|
|
482
|
+
} else {
|
|
483
|
+
this.messagesContainerTarget.appendChild(element)
|
|
484
|
+
}
|
|
485
|
+
|
|
373
486
|
element.scrollIntoView({ behavior: 'smooth' })
|
|
374
487
|
|
|
375
488
|
this.broadcastChannel.postMessage({
|
|
@@ -378,6 +491,7 @@ export default class extends Controller {
|
|
|
378
491
|
})
|
|
379
492
|
|
|
380
493
|
this.inputTarget.value = ''
|
|
494
|
+
this.resizeInput()
|
|
381
495
|
this.files = []
|
|
382
496
|
|
|
383
497
|
this.attachmentInputTarget.value = ''
|
|
@@ -410,6 +524,15 @@ export default class extends Controller {
|
|
|
410
524
|
this.webChatChannel.updateSubscriptionWith(this.conversationIdValue)
|
|
411
525
|
}
|
|
412
526
|
|
|
527
|
+
if (this.typingIndicatorVisible) {
|
|
528
|
+
this.resetTypingIndicatorTimer()
|
|
529
|
+
} else {
|
|
530
|
+
clearTimeout(this.optimisticTypingTimeout)
|
|
531
|
+
this.optimisticTypingTimeout = setTimeout(() => {
|
|
532
|
+
this.showOptimisticTypingIndicator()
|
|
533
|
+
}, this.optimisticTypingIndicatorWaitValue)
|
|
534
|
+
}
|
|
535
|
+
|
|
413
536
|
this.attachmentContainerTarget.style.display = ''
|
|
414
537
|
}
|
|
415
538
|
|
|
@@ -522,23 +645,21 @@ export default class extends Controller {
|
|
|
522
645
|
this.inputTarget.focus()
|
|
523
646
|
}
|
|
524
647
|
|
|
525
|
-
setOfflineTimeout() {
|
|
526
|
-
clearTimeout(this.offlineTimeout)
|
|
527
|
-
|
|
528
|
-
this.offlineTimeout = setTimeout(() => {
|
|
529
|
-
this.onlineStatusTarget.style.display = 'none'
|
|
530
|
-
}, this.fiveMinutes)
|
|
531
|
-
}
|
|
532
|
-
|
|
533
648
|
byteToMegabyte(bytes) {
|
|
534
649
|
return Math.ceil(bytes / 1024 / 1024)
|
|
535
650
|
}
|
|
536
651
|
|
|
537
|
-
get
|
|
538
|
-
return
|
|
652
|
+
get middlewares() {
|
|
653
|
+
return [offset(this.offsetValue), shift({ padding: this.paddingValue }), flip()]
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
get shouldOpenOnMount() {
|
|
657
|
+
return (
|
|
658
|
+
localStorage.getItem(`hellotext--webchat--${this.idValue}`) === 'opened' && !this.onMobile
|
|
659
|
+
)
|
|
539
660
|
}
|
|
540
661
|
|
|
541
|
-
get
|
|
542
|
-
return
|
|
662
|
+
get onMobile() {
|
|
663
|
+
return window.matchMedia(`(max-width: ${this.fullScreenThresholdValue}px)`).matches
|
|
543
664
|
}
|
|
544
665
|
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @class Locale
|
|
3
|
+
* @classdesc
|
|
4
|
+
* Handles locale detection and configuration for the Hellotext library.
|
|
5
|
+
* Provides automatic locale detection from HTML lang attribute, meta tags,
|
|
6
|
+
* and browser language with fallback to 'en'.
|
|
7
|
+
*/
|
|
8
|
+
class Locale {
|
|
9
|
+
static _identifier
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Sets the locale identifier explicitly.
|
|
13
|
+
* @param {string} value - The locale identifier (e.g., 'en', 'es')
|
|
14
|
+
*/
|
|
15
|
+
static set identifier(value) {
|
|
16
|
+
this._identifier = value
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Gets the effective locale identifier.
|
|
21
|
+
* Falls back to auto-detection if not explicitly set.
|
|
22
|
+
* @returns {string} The locale identifier
|
|
23
|
+
*/
|
|
24
|
+
static get identifier() {
|
|
25
|
+
if (this._identifier) return this._identifier
|
|
26
|
+
return this.#fromHtmlLangProperty || this.#fromMetaTag || this.#fromBrowserLanguage || 'en'
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Returns the locale identifier as a string.
|
|
31
|
+
* @returns {string} The locale identifier
|
|
32
|
+
*/
|
|
33
|
+
static toString() {
|
|
34
|
+
return this.identifier
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
static get #fromHtmlLangProperty() {
|
|
38
|
+
return document?.documentElement?.lang
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
static get #fromMetaTag() {
|
|
42
|
+
return document?.querySelector('meta[name="locale"]')?.content
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
static get #fromBrowserLanguage() {
|
|
46
|
+
return navigator?.language?.split('-')[0] // Extract primary language
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export { Locale }
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Forms } from './configuration/forms'
|
|
2
|
+
import { Locale } from './configuration/locale'
|
|
2
3
|
import { Webchat } from './configuration/webchat'
|
|
3
4
|
|
|
4
5
|
/**
|
|
@@ -9,9 +10,11 @@ import { Webchat } from './configuration/webchat'
|
|
|
9
10
|
* @property {String} [session] - session id
|
|
10
11
|
* @property {Forms} [forms] - form configuration
|
|
11
12
|
* @property {Webchat} [webchat] - webchat configuration
|
|
13
|
+
* @property {Locale} [locale] - locale configuration
|
|
12
14
|
*/
|
|
13
15
|
class Configuration {
|
|
14
16
|
static apiRoot = 'https://api.hellotext.com/v1'
|
|
17
|
+
static actionCableUrl = 'wss://www.hellotext.com/cable'
|
|
15
18
|
|
|
16
19
|
static autoGenerateSession = true
|
|
17
20
|
static session = null
|
|
@@ -43,6 +46,14 @@ class Configuration {
|
|
|
43
46
|
return this
|
|
44
47
|
}
|
|
45
48
|
|
|
49
|
+
static set locale(locale) {
|
|
50
|
+
Locale.identifier = locale
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
static get locale() {
|
|
54
|
+
return Locale.toString()
|
|
55
|
+
}
|
|
56
|
+
|
|
46
57
|
static endpoint(path) {
|
|
47
58
|
return `${this.apiRoot}/${path}`
|
|
48
59
|
}
|
package/src/core/index.js
CHANGED
package/src/locales/en.js
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
export default {
|
|
2
2
|
white_label: {
|
|
3
|
-
powered_by: '
|
|
3
|
+
powered_by: 'By',
|
|
4
4
|
},
|
|
5
5
|
errors: {
|
|
6
6
|
parameter_not_unique: 'This value is taken.',
|
|
7
|
-
blank: 'This field is required.'
|
|
7
|
+
blank: 'This field is required.',
|
|
8
8
|
},
|
|
9
9
|
forms: {
|
|
10
10
|
phone: 'Click the link sent via SMS to verify your submission.',
|
|
11
11
|
email: 'Click the link sent via email to verify your submission.',
|
|
12
12
|
phone_and_email: 'Click the links sent via SMS and email to verify your submission.',
|
|
13
|
-
none: 'Your submission has been received.'
|
|
14
|
-
}
|
|
13
|
+
none: 'Your submission has been received.',
|
|
14
|
+
},
|
|
15
15
|
}
|
package/src/locales/es.js
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
export default {
|
|
2
2
|
white_label: {
|
|
3
|
-
powered_by: '
|
|
3
|
+
powered_by: 'Por',
|
|
4
4
|
},
|
|
5
5
|
errors: {
|
|
6
6
|
parameter_not_unique: 'Este valor ya está en uso.',
|
|
7
|
-
blank: 'Este campo es obligatorio.'
|
|
7
|
+
blank: 'Este campo es obligatorio.',
|
|
8
8
|
},
|
|
9
9
|
forms: {
|
|
10
10
|
phone: 'Haga clic en el enlace enviado por SMS para verificar su envío.',
|
|
11
11
|
email: 'Haga clic en el enlace enviado por e-mail para verificar su envío.',
|
|
12
12
|
phone_and_email: 'Haga clic en los enlaces enviados por SMS y e-mail para verificar su envío.',
|
|
13
|
-
none: 'Su envío ha sido recibido.'
|
|
14
|
-
}
|
|
13
|
+
none: 'Su envío ha sido recibido.',
|
|
14
|
+
},
|
|
15
15
|
}
|
package/src/models/business.js
CHANGED
|
@@ -30,6 +30,18 @@ class Business {
|
|
|
30
30
|
return this.data.whitelist !== 'disabled'
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
+
setLocale(locale) {
|
|
34
|
+
if (!locales[locale]) {
|
|
35
|
+
return console.warn(`Locale ${locale} not found`)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (!this.data) {
|
|
39
|
+
this.data = {}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
this.data.locale = locale
|
|
43
|
+
}
|
|
44
|
+
|
|
33
45
|
get locale() {
|
|
34
46
|
return locales[this.data.locale]
|
|
35
47
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import API from '../api/forms'
|
|
2
2
|
import Hellotext from '../hellotext'
|
|
3
3
|
|
|
4
|
-
import { Configuration } from '../core'
|
|
4
|
+
import { Configuration, Locale } from '../core'
|
|
5
5
|
import { Form } from './form'
|
|
6
6
|
|
|
7
7
|
import { NotInitializedError } from '../errors'
|
|
@@ -84,6 +84,7 @@ class FormCollection {
|
|
|
84
84
|
|
|
85
85
|
if (!Hellotext.business.data) {
|
|
86
86
|
Hellotext.business.setData(data.business)
|
|
87
|
+
Hellotext.business.setLocale(Locale.toString())
|
|
87
88
|
}
|
|
88
89
|
|
|
89
90
|
if (!Hellotext.business.enabledWhitelist) {
|
package/src/models/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export { Business } from './business'
|
|
2
|
-
export { Form } from './form'
|
|
3
|
-
export { Query } from './query'
|
|
4
2
|
export { Cookies } from './cookies'
|
|
3
|
+
export { Form } from './form'
|
|
5
4
|
export { FormCollection } from './form_collection'
|
|
6
|
-
export {
|
|
5
|
+
export { Query } from './query'
|
|
7
6
|
export { Session } from './session'
|
|
7
|
+
export { Webchat } from './webchat'
|
package/src/models/query.js
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import { Cookies } from './cookies'
|
|
2
|
-
|
|
3
1
|
class Query {
|
|
4
2
|
static get inPreviewMode() {
|
|
5
3
|
return new this().inPreviewMode
|
|
@@ -22,7 +20,7 @@ class Query {
|
|
|
22
20
|
}
|
|
23
21
|
|
|
24
22
|
get session() {
|
|
25
|
-
return this.get('session')
|
|
23
|
+
return this.get('session')
|
|
26
24
|
}
|
|
27
25
|
|
|
28
26
|
toHellotextParam(param) {
|
package/src/models/session.js
CHANGED
|
@@ -18,7 +18,7 @@ class Session {
|
|
|
18
18
|
static initialize() {
|
|
19
19
|
this.#query = new Query()
|
|
20
20
|
|
|
21
|
-
this.session =
|
|
21
|
+
this.session = this.#query.session || Configuration.session || Cookies.get('hello_session')
|
|
22
22
|
|
|
23
23
|
if (!this.session && Configuration.autoGenerateSession) {
|
|
24
24
|
this.session = crypto.randomUUID()
|