@hellotext/hellotext 2.4.3 → 2.4.5
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/webchat/messages.cjs +8 -0
- package/lib/api/webchat/messages.js +8 -0
- package/lib/channels/application_channel.cjs +143 -10
- package/lib/channels/application_channel.js +143 -10
- package/lib/channels/webchat_channel.cjs +57 -0
- package/lib/channels/webchat_channel.js +57 -0
- package/lib/controllers/webchat_controller.cjs +167 -22
- package/lib/controllers/webchat_controller.js +172 -18
- package/package.json +1 -1
- package/src/api/webchat/messages.js +7 -0
- package/src/channels/application_channel.js +152 -9
- package/src/channels/webchat_channel.js +61 -1
- package/src/controllers/webchat_controller.js +193 -21
|
@@ -6,7 +6,7 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
6
6
|
exports.default = void 0;
|
|
7
7
|
var _dom = require("@floating-ui/dom");
|
|
8
8
|
var _stimulus = require("@hotwired/stimulus");
|
|
9
|
-
var
|
|
9
|
+
var _messages2 = _interopRequireDefault(require("../api/webchat/messages"));
|
|
10
10
|
var _webchat_channel = _interopRequireDefault(require("../channels/webchat_channel"));
|
|
11
11
|
var _hellotext = _interopRequireDefault(require("../hellotext"));
|
|
12
12
|
var _core = require("../core");
|
|
@@ -35,6 +35,11 @@ const MESSAGE_TIMESTAMP_FORMAT_OPTIONS = {
|
|
|
35
35
|
hour: 'numeric',
|
|
36
36
|
minute: '2-digit'
|
|
37
37
|
};
|
|
38
|
+
const MOBILE_USER_AGENT_PATTERN = /Android|iPhone|iPad|iPod/i;
|
|
39
|
+
const SCROLL_ISOLATION_EVENT_OPTIONS = {
|
|
40
|
+
capture: true,
|
|
41
|
+
passive: true
|
|
42
|
+
};
|
|
38
43
|
let _default = /*#__PURE__*/function (_Controller) {
|
|
39
44
|
_inherits(_default, _Controller);
|
|
40
45
|
var _super = _createSuper(_default);
|
|
@@ -45,17 +50,23 @@ let _default = /*#__PURE__*/function (_Controller) {
|
|
|
45
50
|
_createClass(_default, [{
|
|
46
51
|
key: "initialize",
|
|
47
52
|
value: function initialize() {
|
|
48
|
-
this.messagesAPI = new
|
|
53
|
+
this.messagesAPI = new _messages2.default(this.idValue);
|
|
49
54
|
this.webChatChannel = new _webchat_channel.default(this.idValue, _hellotext.default.session, this.conversationIdValue);
|
|
50
55
|
this.files = [];
|
|
51
56
|
this.messageIds = new Set();
|
|
57
|
+
this.catchUpAfterMessageId = null;
|
|
58
|
+
this.fetchingCatchUpMessages = false;
|
|
52
59
|
this.onMessageReceived = this.onMessageReceived.bind(this);
|
|
53
60
|
this.onMessageReaction = this.onMessageReaction.bind(this);
|
|
54
61
|
this.onTypingStart = this.onTypingStart.bind(this);
|
|
62
|
+
this.captureCatchUpCursor = this.captureCatchUpCursor.bind(this);
|
|
63
|
+
this.catchUpMessages = this.catchUpMessages.bind(this);
|
|
55
64
|
this.onScroll = this.onScroll.bind(this);
|
|
56
65
|
this.onOutboundMessageSent = this.onOutboundMessageSent.bind(this);
|
|
57
66
|
this.closePopoverOnEscape = this.closePopoverOnEscape.bind(this);
|
|
58
67
|
this.broadcastChannel = new BroadcastChannel(`hellotext--webchat--${this.idValue}`);
|
|
68
|
+
this.webChatChannel.onDisconnect(this.captureCatchUpCursor);
|
|
69
|
+
this.webChatChannel.onReconnect(this.catchUpMessages);
|
|
59
70
|
_get(_getPrototypeOf(_default.prototype), "initialize", this).call(this);
|
|
60
71
|
}
|
|
61
72
|
}, {
|
|
@@ -82,7 +93,10 @@ let _default = /*#__PURE__*/function (_Controller) {
|
|
|
82
93
|
this.webChatChannel.onMessage(this.onMessageReceived);
|
|
83
94
|
this.webChatChannel.onTypingStart(this.onTypingStart);
|
|
84
95
|
this.webChatChannel.onReaction(this.onMessageReaction);
|
|
96
|
+
this.setupMessagesContainerScrollIsolation();
|
|
85
97
|
this.messagesContainerTarget.addEventListener('scroll', this.onScroll);
|
|
98
|
+
this.messagesContainerTarget.addEventListener('wheel', this.stopHostScrollPropagation, SCROLL_ISOLATION_EVENT_OPTIONS);
|
|
99
|
+
this.messagesContainerTarget.addEventListener('touchmove', this.stopHostScrollPropagation, SCROLL_ISOLATION_EVENT_OPTIONS);
|
|
86
100
|
if (this.shouldOpenOnMount) {
|
|
87
101
|
this.openValue = true;
|
|
88
102
|
}
|
|
@@ -101,6 +115,8 @@ let _default = /*#__PURE__*/function (_Controller) {
|
|
|
101
115
|
this.teardownOpeningSequence();
|
|
102
116
|
this.broadcastChannel.removeEventListener('message', this.onOutboundMessageSent);
|
|
103
117
|
this.messagesContainerTarget.removeEventListener('scroll', this.onScroll);
|
|
118
|
+
this.messagesContainerTarget.removeEventListener('wheel', this.stopHostScrollPropagation, SCROLL_ISOLATION_EVENT_OPTIONS);
|
|
119
|
+
this.messagesContainerTarget.removeEventListener('touchmove', this.stopHostScrollPropagation, SCROLL_ISOLATION_EVENT_OPTIONS);
|
|
104
120
|
window.removeEventListener('keydown', this.closePopoverOnEscape, true);
|
|
105
121
|
|
|
106
122
|
// Clean up typing indicator timeouts
|
|
@@ -109,6 +125,21 @@ let _default = /*#__PURE__*/function (_Controller) {
|
|
|
109
125
|
this.floatingUICleanup();
|
|
110
126
|
_get(_getPrototypeOf(_default.prototype), "disconnect", this).call(this);
|
|
111
127
|
}
|
|
128
|
+
}, {
|
|
129
|
+
key: "setupMessagesContainerScrollIsolation",
|
|
130
|
+
value: function setupMessagesContainerScrollIsolation() {
|
|
131
|
+
this.messagesContainerTarget.style.overscrollBehavior = 'contain';
|
|
132
|
+
this.messagesContainerTarget.style.webkitOverflowScrolling = 'touch';
|
|
133
|
+
this.messagesContainerTarget.style.touchAction = 'pan-y';
|
|
134
|
+
this.messagesContainerTarget.setAttribute('data-lenis-prevent', '');
|
|
135
|
+
this.messagesContainerTarget.setAttribute('data-lenis-prevent-wheel', '');
|
|
136
|
+
this.messagesContainerTarget.setAttribute('data-lenis-prevent-touch', '');
|
|
137
|
+
}
|
|
138
|
+
}, {
|
|
139
|
+
key: "stopHostScrollPropagation",
|
|
140
|
+
value: function stopHostScrollPropagation(event) {
|
|
141
|
+
event.stopPropagation();
|
|
142
|
+
}
|
|
112
143
|
}, {
|
|
113
144
|
key: "onTypingStart",
|
|
114
145
|
value: function onTypingStart() {
|
|
@@ -237,7 +268,12 @@ let _default = /*#__PURE__*/function (_Controller) {
|
|
|
237
268
|
const div = document.createElement('div');
|
|
238
269
|
div.innerHTML = body;
|
|
239
270
|
const element = this.messageTemplateTarget.cloneNode(true);
|
|
271
|
+
element.classList.add('hellotext--webchat-message');
|
|
240
272
|
element.setAttribute('data-hellotext--webchat-target', 'message');
|
|
273
|
+
element.setAttribute('data-id', message.id);
|
|
274
|
+
if (createdAt) {
|
|
275
|
+
element.setAttribute('data-created-at', createdAt);
|
|
276
|
+
}
|
|
241
277
|
element.style.removeProperty('display');
|
|
242
278
|
element.querySelector('[data-body]').innerHTML = div.innerHTML;
|
|
243
279
|
if (message.state === 'received') {
|
|
@@ -307,7 +343,7 @@ let _default = /*#__PURE__*/function (_Controller) {
|
|
|
307
343
|
this.popoverTarget.classList.remove(...this.fadeOutClasses);
|
|
308
344
|
(_this$dismissTeaserFo = this.dismissTeaserForSession) === null || _this$dismissTeaserFo === void 0 ? void 0 : _this$dismissTeaserFo.call(this);
|
|
309
345
|
if (!this.onMobile) {
|
|
310
|
-
this.
|
|
346
|
+
this.focusComposeInput();
|
|
311
347
|
}
|
|
312
348
|
if (!this.scrolled) {
|
|
313
349
|
requestAnimationFrame(() => {
|
|
@@ -362,7 +398,7 @@ let _default = /*#__PURE__*/function (_Controller) {
|
|
|
362
398
|
}
|
|
363
399
|
}, {
|
|
364
400
|
key: "onMessageReceived",
|
|
365
|
-
value: function onMessageReceived(message) {
|
|
401
|
+
value: function onMessageReceived(message, options = {}) {
|
|
366
402
|
var _this$hideTeaser;
|
|
367
403
|
const {
|
|
368
404
|
id,
|
|
@@ -374,15 +410,17 @@ let _default = /*#__PURE__*/function (_Controller) {
|
|
|
374
410
|
if (!this.claimMessageId(id)) return;
|
|
375
411
|
(_this$hideTeaser = this.hideTeaser) === null || _this$hideTeaser === void 0 ? void 0 : _this$hideTeaser.call(this);
|
|
376
412
|
if (message.carousel) {
|
|
377
|
-
return this.insertCarouselMessage(message);
|
|
413
|
+
return this.insertCarouselMessage(message, options);
|
|
378
414
|
}
|
|
379
415
|
const div = document.createElement('div');
|
|
380
416
|
div.innerHTML = body;
|
|
381
417
|
const element = this.messageTemplateTarget.cloneNode(true);
|
|
418
|
+
element.classList.add('hellotext--webchat-message');
|
|
382
419
|
element.style.display = 'flex';
|
|
383
420
|
element.querySelector('[data-body]').innerHTML = div.innerHTML;
|
|
384
421
|
element.setAttribute('data-id', id);
|
|
385
422
|
element.setAttribute('data-hellotext--webchat-target', 'message');
|
|
423
|
+
this.setMessageCreatedAt(element, createdAt);
|
|
386
424
|
this.localizeMessageTimestamp(element.querySelector('[data-message-timestamp]'), createdAt);
|
|
387
425
|
if (attachments) {
|
|
388
426
|
attachments.forEach(attachmentUrl => {
|
|
@@ -394,14 +432,16 @@ let _default = /*#__PURE__*/function (_Controller) {
|
|
|
394
432
|
});
|
|
395
433
|
}
|
|
396
434
|
this.clearTypingIndicator();
|
|
397
|
-
this.
|
|
435
|
+
this.insertMessageElement(element);
|
|
398
436
|
_hellotext.default.eventEmitter.dispatch('webchat:message:received', {
|
|
399
437
|
...message,
|
|
400
438
|
body: element.querySelector('[data-body]').innerText
|
|
401
439
|
});
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
440
|
+
if (options.scroll !== false) {
|
|
441
|
+
element.scrollIntoView({
|
|
442
|
+
behavior: 'smooth'
|
|
443
|
+
});
|
|
444
|
+
}
|
|
405
445
|
this.updateMessageTeaser(teaser);
|
|
406
446
|
if (this.openValue) {
|
|
407
447
|
this.messagesAPI.markAsSeen(id);
|
|
@@ -421,6 +461,70 @@ let _default = /*#__PURE__*/function (_Controller) {
|
|
|
421
461
|
this.messageIds.add(id);
|
|
422
462
|
return !messageTargets.some(element => element.dataset.id === id);
|
|
423
463
|
}
|
|
464
|
+
}, {
|
|
465
|
+
key: "captureCatchUpCursor",
|
|
466
|
+
value: function captureCatchUpCursor() {
|
|
467
|
+
this.catchUpAfterMessageId = this.lastRenderedMessageId;
|
|
468
|
+
}
|
|
469
|
+
}, {
|
|
470
|
+
key: "catchUpMessages",
|
|
471
|
+
value: async function catchUpMessages() {
|
|
472
|
+
const afterId = this.catchUpAfterMessageId;
|
|
473
|
+
if (!afterId || this.fetchingCatchUpMessages) return;
|
|
474
|
+
this.fetchingCatchUpMessages = true;
|
|
475
|
+
try {
|
|
476
|
+
const response = await this.messagesAPI.catchUp(afterId);
|
|
477
|
+
const {
|
|
478
|
+
messages = []
|
|
479
|
+
} = await response.json();
|
|
480
|
+
messages.forEach(message => this.onMessageReceived(message, {
|
|
481
|
+
scroll: false
|
|
482
|
+
}));
|
|
483
|
+
this.catchUpAfterMessageId = this.lastRenderedMessageId;
|
|
484
|
+
} finally {
|
|
485
|
+
this.fetchingCatchUpMessages = false;
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
}, {
|
|
489
|
+
key: "lastRenderedMessageId",
|
|
490
|
+
get: function () {
|
|
491
|
+
var _messages;
|
|
492
|
+
const messages = this.persistedMessageElements;
|
|
493
|
+
return ((_messages = messages[messages.length - 1]) === null || _messages === void 0 ? void 0 : _messages.dataset.id) || null;
|
|
494
|
+
}
|
|
495
|
+
}, {
|
|
496
|
+
key: "persistedMessageElements",
|
|
497
|
+
get: function () {
|
|
498
|
+
return Array.from(this.messagesContainerTarget.querySelectorAll('.hellotext--webchat-message[data-id]'));
|
|
499
|
+
}
|
|
500
|
+
}, {
|
|
501
|
+
key: "setMessageCreatedAt",
|
|
502
|
+
value: function setMessageCreatedAt(element, createdAt) {
|
|
503
|
+
if (createdAt) {
|
|
504
|
+
element.setAttribute('data-created-at', createdAt);
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
}, {
|
|
508
|
+
key: "insertMessageElement",
|
|
509
|
+
value: function insertMessageElement(element) {
|
|
510
|
+
const nextElement = this.nextMessageElementFor(element);
|
|
511
|
+
if (nextElement) {
|
|
512
|
+
this.messagesContainerTarget.insertBefore(element, nextElement);
|
|
513
|
+
} else {
|
|
514
|
+
this.messagesContainerTarget.appendChild(element);
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
}, {
|
|
518
|
+
key: "nextMessageElementFor",
|
|
519
|
+
value: function nextMessageElementFor(element) {
|
|
520
|
+
const createdAt = Date.parse(element.dataset.createdAt);
|
|
521
|
+
if (Number.isNaN(createdAt)) return null;
|
|
522
|
+
return this.persistedMessageElements.find(messageElement => {
|
|
523
|
+
if (messageElement === element) return false;
|
|
524
|
+
const messageCreatedAt = Date.parse(messageElement.dataset.createdAt);
|
|
525
|
+
return !Number.isNaN(messageCreatedAt) && messageCreatedAt > createdAt;
|
|
526
|
+
});
|
|
527
|
+
}
|
|
424
528
|
}, {
|
|
425
529
|
key: "updateMessageTeaser",
|
|
426
530
|
value: function updateMessageTeaser(teaser) {
|
|
@@ -435,18 +539,23 @@ let _default = /*#__PURE__*/function (_Controller) {
|
|
|
435
539
|
}
|
|
436
540
|
}, {
|
|
437
541
|
key: "insertCarouselMessage",
|
|
438
|
-
value: function insertCarouselMessage(message) {
|
|
542
|
+
value: function insertCarouselMessage(message, options = {}) {
|
|
439
543
|
var _element$querySelecto;
|
|
440
544
|
const html = message.html;
|
|
545
|
+
const createdAt = message.created_at || message.createdAt;
|
|
441
546
|
const element = new DOMParser().parseFromString(html, 'text/html').body.firstElementChild;
|
|
547
|
+
element.classList.add('hellotext--webchat-message');
|
|
442
548
|
element.setAttribute('data-id', message.id);
|
|
443
549
|
element.setAttribute('data-hellotext--webchat-target', 'message');
|
|
550
|
+
this.setMessageCreatedAt(element, createdAt);
|
|
444
551
|
this.localizeMessageTimestamps(element);
|
|
445
552
|
this.clearTypingIndicator();
|
|
446
|
-
this.
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
553
|
+
this.insertMessageElement(element);
|
|
554
|
+
if (options.scroll !== false) {
|
|
555
|
+
element.scrollIntoView({
|
|
556
|
+
behavior: 'smooth'
|
|
557
|
+
});
|
|
558
|
+
}
|
|
450
559
|
_hellotext.default.eventEmitter.dispatch('webchat:message:received', {
|
|
451
560
|
...message,
|
|
452
561
|
body: ((_element$querySelecto = element.querySelector('[data-body]')) === null || _element$querySelecto === void 0 ? void 0 : _element$querySelecto.innerText) || ''
|
|
@@ -665,7 +774,7 @@ let _default = /*#__PURE__*/function (_Controller) {
|
|
|
665
774
|
this.attachmentContainerTarget.innerHTML = '';
|
|
666
775
|
this.attachmentContainerTarget.style.display = 'none';
|
|
667
776
|
this.errorMessageContainerTarget.style.display = 'none';
|
|
668
|
-
this.
|
|
777
|
+
this.focusComposeInput();
|
|
669
778
|
|
|
670
779
|
// Set up optimistic typing indicator BEFORE making the API call
|
|
671
780
|
// This prevents race conditions with server responses
|
|
@@ -718,12 +827,10 @@ let _default = /*#__PURE__*/function (_Controller) {
|
|
|
718
827
|
} = event;
|
|
719
828
|
const ignoredSelector = ['button', 'a', 'input', 'textarea', 'select', 'label', '[role="button"]', 'em-emoji-picker', '[data-hellotext--webchat--emoji-target~="popover"]', '[data-controller~="hellotext--webchat--emoji"]'].join(', ');
|
|
720
829
|
if (!this.hasInputTarget || target.closest(ignoredSelector)) return;
|
|
830
|
+
if (!this.focusComposeInput({
|
|
831
|
+
moveCursorToEnd: true
|
|
832
|
+
})) return;
|
|
721
833
|
event.preventDefault();
|
|
722
|
-
this.inputTarget.focus();
|
|
723
|
-
if (typeof this.inputTarget.selectionStart === 'number') {
|
|
724
|
-
const position = this.inputTarget.value.length;
|
|
725
|
-
this.inputTarget.setSelectionRange(position, position);
|
|
726
|
-
}
|
|
727
834
|
}
|
|
728
835
|
}, {
|
|
729
836
|
key: "closePopoverFromHeader",
|
|
@@ -872,7 +979,7 @@ let _default = /*#__PURE__*/function (_Controller) {
|
|
|
872
979
|
this.files = [...this.files, ...newFiles];
|
|
873
980
|
this.errorMessageContainerTarget.innerText = '';
|
|
874
981
|
newFiles.forEach(file => this.createAttachmentElement(file));
|
|
875
|
-
this.
|
|
982
|
+
this.focusComposeInput();
|
|
876
983
|
}
|
|
877
984
|
}, {
|
|
878
985
|
key: "createAttachmentElement",
|
|
@@ -907,7 +1014,7 @@ let _default = /*#__PURE__*/function (_Controller) {
|
|
|
907
1014
|
this.files = this.files.filter(file => file.name !== attachment.dataset.name);
|
|
908
1015
|
this.attachmentInputTarget.value = '';
|
|
909
1016
|
attachment.remove();
|
|
910
|
-
this.
|
|
1017
|
+
this.focusComposeInput();
|
|
911
1018
|
}
|
|
912
1019
|
}, {
|
|
913
1020
|
key: "attachmentTargetDisconnected",
|
|
@@ -936,7 +1043,22 @@ let _default = /*#__PURE__*/function (_Controller) {
|
|
|
936
1043
|
const end = this.inputTarget.selectionEnd;
|
|
937
1044
|
this.inputTarget.value = value.slice(0, start) + emoji + value.slice(end);
|
|
938
1045
|
this.inputTarget.selectionStart = this.inputTarget.selectionEnd = start + emoji.length;
|
|
1046
|
+
this.focusComposeInput();
|
|
1047
|
+
}
|
|
1048
|
+
}, {
|
|
1049
|
+
key: "focusComposeInput",
|
|
1050
|
+
value: function focusComposeInput({
|
|
1051
|
+
moveCursorToEnd = false
|
|
1052
|
+
} = {}) {
|
|
1053
|
+
if (!this.shouldAutofocusCompose) return false;
|
|
1054
|
+
if (this.hasInputTarget === false) return false;
|
|
1055
|
+
if (this.hasInputTarget === undefined && !this.inputTarget) return false;
|
|
939
1056
|
this.inputTarget.focus();
|
|
1057
|
+
if (moveCursorToEnd && typeof this.inputTarget.selectionStart === 'number') {
|
|
1058
|
+
const position = this.inputTarget.value.length;
|
|
1059
|
+
this.inputTarget.setSelectionRange(position, position);
|
|
1060
|
+
}
|
|
1061
|
+
return true;
|
|
940
1062
|
}
|
|
941
1063
|
}, {
|
|
942
1064
|
key: "byteToMegabyte",
|
|
@@ -955,9 +1077,32 @@ let _default = /*#__PURE__*/function (_Controller) {
|
|
|
955
1077
|
get: function () {
|
|
956
1078
|
return localStorage.getItem(`hellotext--webchat--${this.idValue}`) === 'opened' && !this.onMobile;
|
|
957
1079
|
}
|
|
1080
|
+
}, {
|
|
1081
|
+
key: "shouldAutofocusCompose",
|
|
1082
|
+
get: function () {
|
|
1083
|
+
return !this.usesVirtualKeyboard;
|
|
1084
|
+
}
|
|
1085
|
+
}, {
|
|
1086
|
+
key: "usesVirtualKeyboard",
|
|
1087
|
+
get: function () {
|
|
1088
|
+
var _navigator$userAgentD;
|
|
1089
|
+
if (typeof navigator === 'undefined') return false;
|
|
1090
|
+
const userAgent = navigator.userAgent || '';
|
|
1091
|
+
const isIPadOS = navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1;
|
|
1092
|
+
const isMobileUserAgent = MOBILE_USER_AGENT_PATTERN.test(userAgent);
|
|
1093
|
+
const isUserAgentDataMobile = ((_navigator$userAgentD = navigator.userAgentData) === null || _navigator$userAgentD === void 0 ? void 0 : _navigator$userAgentD.mobile) === true;
|
|
1094
|
+
return isMobileUserAgent || isIPadOS || isUserAgentDataMobile || this.hasTouchOnlyPointer;
|
|
1095
|
+
}
|
|
1096
|
+
}, {
|
|
1097
|
+
key: "hasTouchOnlyPointer",
|
|
1098
|
+
get: function () {
|
|
1099
|
+
if (typeof window === 'undefined' || typeof window.matchMedia !== 'function') return false;
|
|
1100
|
+
return window.matchMedia('(pointer: coarse)').matches && window.matchMedia('(hover: none)').matches;
|
|
1101
|
+
}
|
|
958
1102
|
}, {
|
|
959
1103
|
key: "onMobile",
|
|
960
1104
|
get: function () {
|
|
1105
|
+
if (typeof window === 'undefined' || typeof window.matchMedia !== 'function') return false;
|
|
961
1106
|
return window.matchMedia(`(max-width: ${this.fullScreenThresholdValue}px)`).matches;
|
|
962
1107
|
}
|
|
963
1108
|
}], [{
|