@hellotext/hellotext 2.1.51 → 2.2.0
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/controllers/message_controller.cjs +124 -0
- package/lib/controllers/message_controller.js +120 -0
- package/lib/controllers/webchat_controller.cjs +104 -6
- package/lib/controllers/webchat_controller.js +115 -11
- package/lib/index.cjs +2 -0
- package/lib/index.js +2 -0
- package/package.json +1 -1
- package/src/controllers/message_controller.js +95 -0
- package/src/controllers/webchat_controller.js +111 -9
- package/src/index.js +2 -0
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { Controller } from '@hotwired/stimulus'
|
|
2
|
+
|
|
3
|
+
export default class extends Controller {
|
|
4
|
+
static values = {
|
|
5
|
+
id: String,
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
static targets = ['carouselContainer', 'leftFade', 'rightFade', 'carouselCard']
|
|
9
|
+
|
|
10
|
+
connect() {
|
|
11
|
+
this.updateFades()
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
setId({ detail: id }) {
|
|
15
|
+
this.idValue = id
|
|
16
|
+
this.element.id = id
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
onScroll() {
|
|
20
|
+
this.updateFades()
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
quickReply({ currentTarget }) {
|
|
24
|
+
const card = currentTarget.closest('[data-hellotext--message-target="carouselCard"]')
|
|
25
|
+
|
|
26
|
+
this.dispatch('quickReply', {
|
|
27
|
+
detail: {
|
|
28
|
+
id: this.idValue,
|
|
29
|
+
product: card.dataset.id,
|
|
30
|
+
buttonId: currentTarget.dataset.id,
|
|
31
|
+
body: currentTarget.dataset.text,
|
|
32
|
+
cardElement: card,
|
|
33
|
+
},
|
|
34
|
+
})
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
moveToLeft() {
|
|
38
|
+
if (!this.hasCarouselContainerTarget) return
|
|
39
|
+
|
|
40
|
+
const scrollAmount = this.getScrollAmount()
|
|
41
|
+
|
|
42
|
+
this.carouselContainerTarget.scrollBy({
|
|
43
|
+
left: -scrollAmount,
|
|
44
|
+
behavior: 'smooth',
|
|
45
|
+
})
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
moveToRight() {
|
|
49
|
+
if (!this.hasCarouselContainerTarget) return
|
|
50
|
+
|
|
51
|
+
const scrollAmount = this.getScrollAmount()
|
|
52
|
+
|
|
53
|
+
this.carouselContainerTarget.scrollBy({
|
|
54
|
+
left: scrollAmount,
|
|
55
|
+
behavior: 'smooth',
|
|
56
|
+
})
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
getScrollAmount() {
|
|
60
|
+
// Get the actual card width from DOM
|
|
61
|
+
const firstCard = this.carouselContainerTarget.querySelector('.message__carousel_card')
|
|
62
|
+
|
|
63
|
+
if (!firstCard) {
|
|
64
|
+
return 280 // Fallback to default desktop card width
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const cardWidth = firstCard.offsetWidth
|
|
68
|
+
const gap = 16 // gap-x-4 = 1rem = 16px
|
|
69
|
+
|
|
70
|
+
return cardWidth + gap
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
updateFades() {
|
|
74
|
+
if (!this.hasCarouselContainerTarget) return
|
|
75
|
+
|
|
76
|
+
const scrollLeft = this.carouselContainerTarget.scrollLeft
|
|
77
|
+
const maxScroll =
|
|
78
|
+
this.carouselContainerTarget.scrollWidth - this.carouselContainerTarget.clientWidth
|
|
79
|
+
|
|
80
|
+
// Show left fade if scrolled past start
|
|
81
|
+
if (scrollLeft > 0) {
|
|
82
|
+
this.leftFadeTarget.classList.remove('hidden')
|
|
83
|
+
} else {
|
|
84
|
+
this.leftFadeTarget.classList.add('hidden')
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Show right fade if not at end
|
|
88
|
+
if (scrollLeft < maxScroll - 1) {
|
|
89
|
+
// -1 for rounding errors
|
|
90
|
+
this.rightFadeTarget.classList.remove('hidden')
|
|
91
|
+
} else {
|
|
92
|
+
this.rightFadeTarget.classList.add('hidden')
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
@@ -369,6 +369,10 @@ export default class extends Controller {
|
|
|
369
369
|
onMessageReceived(message) {
|
|
370
370
|
const { id, body, attachments } = message
|
|
371
371
|
|
|
372
|
+
if (message.carousel) {
|
|
373
|
+
return this.insertCarouselMessage(message)
|
|
374
|
+
}
|
|
375
|
+
|
|
372
376
|
const div = document.createElement('div')
|
|
373
377
|
div.innerHTML = body
|
|
374
378
|
|
|
@@ -409,6 +413,30 @@ export default class extends Controller {
|
|
|
409
413
|
this.unreadCounterTarget.innerText = unreadCount > 99 ? '99+' : unreadCount
|
|
410
414
|
}
|
|
411
415
|
|
|
416
|
+
insertCarouselMessage(message) {
|
|
417
|
+
const html = message.html
|
|
418
|
+
const element = new DOMParser().parseFromString(html, 'text/html').body.firstElementChild
|
|
419
|
+
|
|
420
|
+
this.clearTypingIndicator()
|
|
421
|
+
this.messagesContainerTarget.appendChild(element)
|
|
422
|
+
|
|
423
|
+
element.scrollIntoView({ behavior: 'smooth' })
|
|
424
|
+
|
|
425
|
+
Hellotext.eventEmitter.dispatch('webchat:message:received', {
|
|
426
|
+
...message,
|
|
427
|
+
body: element.querySelector('[data-body]')?.innerText || '',
|
|
428
|
+
})
|
|
429
|
+
|
|
430
|
+
if (this.openValue) {
|
|
431
|
+
this.messagesAPI.markAsSeen(message.id)
|
|
432
|
+
return
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
this.unreadCounterTarget.style.display = 'flex'
|
|
436
|
+
const unreadCount = (parseInt(this.unreadCounterTarget.innerText) || 0) + 1
|
|
437
|
+
this.unreadCounterTarget.innerText = unreadCount > 99 ? '99+' : unreadCount
|
|
438
|
+
}
|
|
439
|
+
|
|
412
440
|
resizeInput() {
|
|
413
441
|
const maxHeight = 96
|
|
414
442
|
|
|
@@ -422,6 +450,73 @@ export default class extends Controller {
|
|
|
422
450
|
this.inputTarget.style.height = `${Math.min(scrollHeight, maxHeight)}px`
|
|
423
451
|
}
|
|
424
452
|
|
|
453
|
+
async sendQuickReplyMessage({ detail: { id, product, buttonId, body, cardElement } }) {
|
|
454
|
+
const formData = new FormData()
|
|
455
|
+
|
|
456
|
+
formData.append('message[body]', body)
|
|
457
|
+
formData.append('message[replied_to]', id)
|
|
458
|
+
formData.append('message[product]', product)
|
|
459
|
+
formData.append('message[button]', buttonId)
|
|
460
|
+
|
|
461
|
+
formData.append('session', Hellotext.session)
|
|
462
|
+
formData.append('locale', Locale.toString())
|
|
463
|
+
|
|
464
|
+
const element = this.buildMessageElement()
|
|
465
|
+
const attachment = cardElement.querySelector('img')?.cloneNode(true)
|
|
466
|
+
|
|
467
|
+
element.querySelector('[data-body]').innerText = body
|
|
468
|
+
|
|
469
|
+
if (attachment) {
|
|
470
|
+
attachment.removeAttribute('width')
|
|
471
|
+
attachment.removeAttribute('height')
|
|
472
|
+
|
|
473
|
+
element.querySelector('[data-attachment-container]').appendChild(attachment)
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
if (this.typingIndicatorVisible && this.hasTypingIndicatorTarget) {
|
|
477
|
+
this.messagesContainerTarget.insertBefore(element, this.typingIndicatorTarget)
|
|
478
|
+
} else {
|
|
479
|
+
this.messagesContainerTarget.appendChild(element)
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
element.scrollIntoView({ behavior: 'smooth' })
|
|
483
|
+
|
|
484
|
+
this.broadcastChannel.postMessage({
|
|
485
|
+
type: 'message:sent',
|
|
486
|
+
element: element.outerHTML,
|
|
487
|
+
})
|
|
488
|
+
|
|
489
|
+
const response = await this.messagesAPI.create(formData)
|
|
490
|
+
|
|
491
|
+
if (response.failed) {
|
|
492
|
+
// Clear the optimistic typing indicator on failure
|
|
493
|
+
clearTimeout(this.optimisticTypingTimeout)
|
|
494
|
+
|
|
495
|
+
this.broadcastChannel.postMessage({
|
|
496
|
+
type: 'message:failed',
|
|
497
|
+
id: element.id,
|
|
498
|
+
})
|
|
499
|
+
|
|
500
|
+
return element.classList.add('failed')
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
const data = await response.json()
|
|
504
|
+
|
|
505
|
+
this.dispatch('set:id', { target: element, detail: data.id })
|
|
506
|
+
|
|
507
|
+
const message = {
|
|
508
|
+
id: data.id,
|
|
509
|
+
body: body,
|
|
510
|
+
attachments: attachment ? [attachment.src] : [],
|
|
511
|
+
replied_to: id,
|
|
512
|
+
product: product,
|
|
513
|
+
button: buttonId,
|
|
514
|
+
type: 'quick_reply',
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
Hellotext.eventEmitter.dispatch('webchat:message:sent', message)
|
|
518
|
+
}
|
|
519
|
+
|
|
425
520
|
async sendMessage(e) {
|
|
426
521
|
const formData = new FormData()
|
|
427
522
|
|
|
@@ -451,14 +546,7 @@ export default class extends Controller {
|
|
|
451
546
|
formData.append('session', Hellotext.session)
|
|
452
547
|
formData.append('locale', Locale.toString())
|
|
453
548
|
|
|
454
|
-
const element = this.
|
|
455
|
-
|
|
456
|
-
element.id = `hellotext--webchat--${this.idValue}--message--${Date.now()}`
|
|
457
|
-
|
|
458
|
-
element.classList.add('received')
|
|
459
|
-
element.style.removeProperty('display')
|
|
460
|
-
|
|
461
|
-
element.setAttribute('data-hellotext--webchat-target', 'message')
|
|
549
|
+
const element = this.buildMessageElement()
|
|
462
550
|
|
|
463
551
|
if (this.inputTarget.value.trim().length > 0) {
|
|
464
552
|
element.querySelector('[data-body]').innerText = this.inputTarget.value
|
|
@@ -470,7 +558,7 @@ export default class extends Controller {
|
|
|
470
558
|
|
|
471
559
|
if (attachments.length > 0) {
|
|
472
560
|
attachments.forEach(attachment => {
|
|
473
|
-
element.querySelector('[data-attachment-container]').appendChild(attachment)
|
|
561
|
+
element.querySelector('[data-attachment-container]').appendChild(attachment.cloneNode(true))
|
|
474
562
|
})
|
|
475
563
|
}
|
|
476
564
|
|
|
@@ -542,6 +630,20 @@ export default class extends Controller {
|
|
|
542
630
|
this.attachmentContainerTarget.style.display = ''
|
|
543
631
|
}
|
|
544
632
|
|
|
633
|
+
buildMessageElement() {
|
|
634
|
+
const element = this.messageTemplateTarget.cloneNode(true)
|
|
635
|
+
|
|
636
|
+
element.id = `hellotext--webchat--${this.idValue}--message--${Date.now()}`
|
|
637
|
+
|
|
638
|
+
element.classList.add('received')
|
|
639
|
+
element.style.removeProperty('display')
|
|
640
|
+
|
|
641
|
+
element.setAttribute('data-controller', 'hellotext--message')
|
|
642
|
+
element.setAttribute('data-hellotext--webchat-target', 'message')
|
|
643
|
+
|
|
644
|
+
return element
|
|
645
|
+
}
|
|
646
|
+
|
|
545
647
|
openAttachment() {
|
|
546
648
|
this.attachmentInputTarget.click()
|
|
547
649
|
}
|
package/src/index.js
CHANGED
|
@@ -2,6 +2,7 @@ import { Application } from '@hotwired/stimulus'
|
|
|
2
2
|
import Hellotext from './hellotext'
|
|
3
3
|
|
|
4
4
|
import FormController from './controllers/form_controller'
|
|
5
|
+
import MessageController from './controllers/message_controller'
|
|
5
6
|
import WebChatEmojiController from './controllers/webchat/emoji_picker_controller'
|
|
6
7
|
import WebchatController from './controllers/webchat_controller'
|
|
7
8
|
|
|
@@ -10,6 +11,7 @@ const application = Application.start()
|
|
|
10
11
|
application.register('hellotext--form', FormController)
|
|
11
12
|
application.register('hellotext--webchat', WebchatController)
|
|
12
13
|
application.register('hellotext--webchat--emoji', WebChatEmojiController)
|
|
14
|
+
application.register('hellotext--message', MessageController)
|
|
13
15
|
|
|
14
16
|
window.Hellotext = Hellotext
|
|
15
17
|
|