@hellotext/hellotext 2.5.7 → 2.5.8
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/README.md +0 -25
- package/dist/hellotext.js +1 -1
- package/index.d.ts +0 -6
- package/lib/api/acks.cjs +17 -31
- package/lib/api/acks.js +17 -42
- package/lib/api/businesses.cjs +14 -28
- package/lib/api/businesses.js +13 -36
- package/lib/api/events.cjs +26 -40
- package/lib/api/events.js +28 -52
- package/lib/api/forms.cjs +24 -40
- package/lib/api/forms.js +24 -57
- package/lib/api/identifications.cjs +15 -29
- package/lib/api/identifications.js +15 -40
- package/lib/api/index.cjs +25 -51
- package/lib/api/index.js +27 -54
- package/lib/api/push/identities.cjs +30 -46
- package/lib/api/push/identities.js +30 -63
- package/lib/api/response.cjs +30 -52
- package/lib/api/response.js +31 -60
- package/lib/api/submissions.cjs +18 -34
- package/lib/api/submissions.js +18 -48
- package/lib/api/webchat/messages.cjs +44 -64
- package/lib/api/webchat/messages.js +44 -80
- package/lib/api/webchats.cjs +37 -56
- package/lib/api/webchats.js +37 -65
- package/lib/api/whatsapp_widgets.cjs +49 -72
- package/lib/api/whatsapp_widgets.js +48 -91
- package/lib/builders/input_builder.cjs +36 -48
- package/lib/builders/input_builder.js +36 -48
- package/lib/builders/logo_builder.cjs +10 -29
- package/lib/builders/logo_builder.js +20 -28
- package/lib/channels/application_channel.cjs +136 -178
- package/lib/channels/application_channel.js +137 -180
- package/lib/channels/webchat_channel.cjs +125 -170
- package/lib/channels/webchat_channel.js +125 -170
- package/lib/controllers/form_controller.cjs +82 -121
- package/lib/controllers/form_controller.js +82 -130
- package/lib/controllers/message_controller.cjs +211 -282
- package/lib/controllers/message_controller.js +209 -284
- package/lib/controllers/mixins/usePopover.cjs +3 -6
- package/lib/controllers/mixins/usePopover.js +17 -22
- package/lib/controllers/webchat/emoji_picker_controller.cjs +106 -150
- package/lib/controllers/webchat/emoji_picker_controller.js +105 -170
- package/lib/controllers/webchat/useBehaviour.js +5 -5
- package/lib/controllers/webchat/useOpeningSequence.js +6 -6
- package/lib/controllers/webchat/useTeaser.js +9 -9
- package/lib/controllers/webchat_controller.cjs +916 -1088
- package/lib/controllers/webchat_controller.js +926 -1152
- package/lib/core/configuration/forms.cjs +21 -35
- package/lib/core/configuration/forms.js +20 -35
- package/lib/core/configuration/locale.cjs +36 -71
- package/lib/core/configuration/locale.js +36 -70
- package/lib/core/configuration/push.cjs +9 -21
- package/lib/core/configuration/push.js +8 -20
- package/lib/core/configuration/webchat.cjs +142 -182
- package/lib/core/configuration/webchat.js +145 -189
- package/lib/core/configuration/whatsapp.cjs +76 -102
- package/lib/core/configuration/whatsapp.js +76 -105
- package/lib/core/configuration.cjs +60 -78
- package/lib/core/configuration.js +59 -78
- package/lib/core/event.cjs +36 -59
- package/lib/core/event.js +35 -61
- package/lib/core/sanitize_html.js +4 -4
- package/lib/errors/invalid_event.cjs +5 -26
- package/lib/errors/invalid_event.js +5 -26
- package/lib/errors/not_initialized_error.cjs +5 -26
- package/lib/errors/not_initialized_error.js +5 -26
- package/lib/hellotext.cjs +185 -223
- package/lib/hellotext.js +188 -247
- package/lib/index.js +1 -1
- package/lib/models/business.cjs +114 -152
- package/lib/models/business.js +117 -162
- package/lib/models/cookies.cjs +34 -51
- package/lib/models/cookies.js +34 -51
- package/lib/models/fingerprint.cjs +8 -23
- package/lib/models/fingerprint.js +25 -58
- package/lib/models/form.cjs +106 -142
- package/lib/models/form.js +105 -153
- package/lib/models/form_collection.cjs +63 -101
- package/lib/models/form_collection.js +61 -107
- package/lib/models/page.cjs +106 -129
- package/lib/models/page.js +106 -131
- package/lib/models/push.cjs +278 -322
- package/lib/models/push.js +284 -357
- package/lib/models/query.cjs +21 -41
- package/lib/models/query.js +21 -41
- package/lib/models/session.cjs +33 -65
- package/lib/models/session.js +30 -63
- package/lib/models/user.cjs +29 -51
- package/lib/models/user.js +29 -51
- package/lib/models/utm.cjs +15 -27
- package/lib/models/utm.js +17 -32
- package/lib/models/webchat.cjs +52 -76
- package/lib/models/webchat.js +52 -90
- package/lib/models/whatsapp_widget.cjs +41 -59
- package/lib/models/whatsapp_widget.js +41 -73
- package/lib/vanilla.cjs +4 -7
- package/lib/vanilla.js +2 -1
- package/package.json +1 -9
- package/src/index.bundle.js +0 -1
- package/src/vanilla.js +2 -1
- package/styles/index.css +0 -30
|
@@ -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 _messages = _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");
|
|
@@ -17,20 +17,6 @@ var _useBehaviour = require("./webchat/useBehaviour");
|
|
|
17
17
|
var _useOpeningSequence = require("./webchat/useOpeningSequence");
|
|
18
18
|
var _useTeaser = require("./webchat/useTeaser");
|
|
19
19
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
20
|
-
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
|
|
21
|
-
function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, _toPropertyKey(descriptor.key), descriptor); } }
|
|
22
|
-
function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); Object.defineProperty(Constructor, "prototype", { writable: false }); return Constructor; }
|
|
23
|
-
function _toPropertyKey(arg) { var key = _toPrimitive(arg, "string"); return typeof key === "symbol" ? key : String(key); }
|
|
24
|
-
function _toPrimitive(input, hint) { if (typeof input !== "object" || input === null) return input; var prim = input[Symbol.toPrimitive]; if (prim !== undefined) { var res = prim.call(input, hint || "default"); if (typeof res !== "object") return res; throw new TypeError("@@toPrimitive must return a primitive value."); } return (hint === "string" ? String : Number)(input); }
|
|
25
|
-
function _get() { if (typeof Reflect !== "undefined" && Reflect.get) { _get = Reflect.get.bind(); } else { _get = function _get(target, property, receiver) { var base = _superPropBase(target, property); if (!base) return; var desc = Object.getOwnPropertyDescriptor(base, property); if (desc.get) { return desc.get.call(arguments.length < 3 ? target : receiver); } return desc.value; }; } return _get.apply(this, arguments); }
|
|
26
|
-
function _superPropBase(object, property) { while (!Object.prototype.hasOwnProperty.call(object, property)) { object = _getPrototypeOf(object); if (object === null) break; } return object; }
|
|
27
|
-
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); Object.defineProperty(subClass, "prototype", { writable: false }); if (superClass) _setPrototypeOf(subClass, superClass); }
|
|
28
|
-
function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf ? Object.setPrototypeOf.bind() : function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); }
|
|
29
|
-
function _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; }
|
|
30
|
-
function _possibleConstructorReturn(self, call) { if (call && (typeof call === "object" || typeof call === "function")) { return call; } else if (call !== void 0) { throw new TypeError("Derived constructors may only return object or undefined"); } return _assertThisInitialized(self); }
|
|
31
|
-
function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; }
|
|
32
|
-
function _isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === "function") return true; try { Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); return true; } catch (e) { return false; } }
|
|
33
|
-
function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf.bind() : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); }
|
|
34
20
|
const POPOVER_ANIMATION_DURATION = 120;
|
|
35
21
|
const MESSAGE_TIMESTAMP_FORMAT_OPTIONS = {
|
|
36
22
|
hour: 'numeric',
|
|
@@ -41,1140 +27,982 @@ const SCROLL_ISOLATION_EVENT_OPTIONS = {
|
|
|
41
27
|
capture: true,
|
|
42
28
|
passive: true
|
|
43
29
|
};
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
30
|
+
class _default extends _stimulus.Controller {
|
|
31
|
+
static messageTimestampFormatters = {};
|
|
32
|
+
static values = {
|
|
33
|
+
id: String,
|
|
34
|
+
conversationId: String,
|
|
35
|
+
media: Object,
|
|
36
|
+
fileSizeErrorMessage: String,
|
|
37
|
+
placement: {
|
|
38
|
+
type: String,
|
|
39
|
+
default: 'bottom-end'
|
|
40
|
+
},
|
|
41
|
+
open: {
|
|
42
|
+
type: Boolean,
|
|
43
|
+
default: false
|
|
44
|
+
},
|
|
45
|
+
autoPlacement: {
|
|
46
|
+
type: Boolean,
|
|
47
|
+
default: false
|
|
48
|
+
},
|
|
49
|
+
disabled: {
|
|
50
|
+
type: Boolean,
|
|
51
|
+
default: false
|
|
52
|
+
},
|
|
53
|
+
nextPage: {
|
|
54
|
+
type: Number,
|
|
55
|
+
default: undefined
|
|
56
|
+
},
|
|
57
|
+
fullScreenThreshold: {
|
|
58
|
+
type: Number,
|
|
59
|
+
default: 1024
|
|
60
|
+
},
|
|
61
|
+
typingIndicatorKeepAlive: {
|
|
62
|
+
type: Number,
|
|
63
|
+
default: 30000
|
|
64
|
+
},
|
|
65
|
+
// 30 seconds
|
|
66
|
+
offset: {
|
|
67
|
+
type: Number,
|
|
68
|
+
default: 24
|
|
69
|
+
},
|
|
70
|
+
padding: {
|
|
71
|
+
type: Number,
|
|
72
|
+
default: 24
|
|
73
|
+
},
|
|
74
|
+
optimisticTypingIndicatorWait: {
|
|
75
|
+
type: Number,
|
|
76
|
+
default: 1000
|
|
77
|
+
},
|
|
78
|
+
// 1 second,
|
|
79
|
+
teaser: Object,
|
|
80
|
+
messageTeaser: String,
|
|
81
|
+
behaviour: Object
|
|
82
|
+
};
|
|
83
|
+
static classes = ['fadeOut'];
|
|
84
|
+
static targets = ['trigger', 'popover', 'input', 'attachmentInput', 'attachmentButton', 'errorMessageContainer', 'attachmentTemplate', 'attachmentContainer', 'attachment', 'messageTemplate', 'messagesContainer', 'title', 'attachmentImage', 'footer', 'toolbar', 'message', 'unreadCounter', 'typingIndicator', 'typingIndicatorTemplate', 'teaser', 'teaserMessage', 'inboundMessageTeaser', 'inboundMessageTeaserBody', 'openingSequence', 'openingSequenceMessage'];
|
|
85
|
+
initialize() {
|
|
86
|
+
this.messagesAPI = new _messages.default(this.idValue);
|
|
87
|
+
this.webChatChannel = new _webchat_channel.default(this.idValue, _hellotext.default.session, this.conversationIdValue);
|
|
88
|
+
this.files = [];
|
|
89
|
+
this.messageIds = new Set();
|
|
90
|
+
this.catchUpAfterMessageId = null;
|
|
91
|
+
this.fetchingCatchUpMessages = false;
|
|
92
|
+
this.onMessageReceived = this.onMessageReceived.bind(this);
|
|
93
|
+
this.onMessageReaction = this.onMessageReaction.bind(this);
|
|
94
|
+
this.onTypingStart = this.onTypingStart.bind(this);
|
|
95
|
+
this.captureCatchUpCursor = this.captureCatchUpCursor.bind(this);
|
|
96
|
+
this.catchUpMessages = this.catchUpMessages.bind(this);
|
|
97
|
+
this.onScroll = this.onScroll.bind(this);
|
|
98
|
+
this.onOutboundMessageSent = this.onOutboundMessageSent.bind(this);
|
|
99
|
+
this.closePopoverOnEscape = this.closePopoverOnEscape.bind(this);
|
|
100
|
+
this.broadcastChannel = new BroadcastChannel(`hellotext--webchat--${this.idValue}`);
|
|
101
|
+
this.webChatChannel.onDisconnect(this.captureCatchUpCursor);
|
|
102
|
+
this.webChatChannel.onReconnect(this.catchUpMessages);
|
|
103
|
+
super.initialize();
|
|
104
|
+
}
|
|
105
|
+
connect() {
|
|
106
|
+
(0, _useBehaviour.useBehaviour)(this);
|
|
107
|
+
(0, _usePopover.usePopover)(this);
|
|
108
|
+
(0, _useOpeningSequence.useOpeningSequence)(this);
|
|
109
|
+
(0, _useTeaser.useTeaser)(this);
|
|
110
|
+
this.setupFloatingUI({
|
|
111
|
+
trigger: this.triggerTarget,
|
|
112
|
+
popover: this.popoverTarget
|
|
113
|
+
});
|
|
114
|
+
if (this.hasTeaserTarget) {
|
|
80
115
|
this.setupFloatingUI({
|
|
81
116
|
trigger: this.triggerTarget,
|
|
82
|
-
popover: this.
|
|
117
|
+
popover: this.teaserTarget,
|
|
118
|
+
strategy: 'absolute'
|
|
83
119
|
});
|
|
84
|
-
if (this.hasTeaserTarget) {
|
|
85
|
-
this.setupFloatingUI({
|
|
86
|
-
trigger: this.triggerTarget,
|
|
87
|
-
popover: this.teaserTarget,
|
|
88
|
-
strategy: 'absolute'
|
|
89
|
-
});
|
|
90
|
-
}
|
|
91
|
-
this.setupTeaser();
|
|
92
|
-
this.setupOpeningSequence();
|
|
93
|
-
this.localizeMessageTimestamps();
|
|
94
|
-
this.webChatChannel.onMessage(this.onMessageReceived);
|
|
95
|
-
this.webChatChannel.onTypingStart(this.onTypingStart);
|
|
96
|
-
this.webChatChannel.onReaction(this.onMessageReaction);
|
|
97
|
-
this.setupMessagesContainerScrollIsolation();
|
|
98
|
-
this.messagesContainerTarget.addEventListener('scroll', this.onScroll);
|
|
99
|
-
this.messagesContainerTarget.addEventListener('wheel', this.stopHostScrollPropagation, SCROLL_ISOLATION_EVENT_OPTIONS);
|
|
100
|
-
this.messagesContainerTarget.addEventListener('touchmove', this.stopHostScrollPropagation, SCROLL_ISOLATION_EVENT_OPTIONS);
|
|
101
|
-
if (this.shouldOpenOnMount) {
|
|
102
|
-
this.openValue = true;
|
|
103
|
-
}
|
|
104
|
-
_hellotext.default.eventEmitter.dispatch('webchat:mounted');
|
|
105
|
-
this.broadcastChannel.addEventListener('message', this.onOutboundMessageSent);
|
|
106
|
-
window.addEventListener('keydown', this.closePopoverOnEscape, true);
|
|
107
|
-
this.scheduleBehaviourOpen();
|
|
108
|
-
_get(_getPrototypeOf(_default.prototype), "connect", this).call(this);
|
|
109
120
|
}
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
121
|
+
this.setupTeaser();
|
|
122
|
+
this.setupOpeningSequence();
|
|
123
|
+
this.localizeMessageTimestamps();
|
|
124
|
+
this.webChatChannel.onMessage(this.onMessageReceived);
|
|
125
|
+
this.webChatChannel.onTypingStart(this.onTypingStart);
|
|
126
|
+
this.webChatChannel.onReaction(this.onMessageReaction);
|
|
127
|
+
this.setupMessagesContainerScrollIsolation();
|
|
128
|
+
this.messagesContainerTarget.addEventListener('scroll', this.onScroll);
|
|
129
|
+
this.messagesContainerTarget.addEventListener('wheel', this.stopHostScrollPropagation, SCROLL_ISOLATION_EVENT_OPTIONS);
|
|
130
|
+
this.messagesContainerTarget.addEventListener('touchmove', this.stopHostScrollPropagation, SCROLL_ISOLATION_EVENT_OPTIONS);
|
|
131
|
+
if (this.shouldOpenOnMount) {
|
|
132
|
+
this.openValue = true;
|
|
133
|
+
}
|
|
134
|
+
_hellotext.default.eventEmitter.dispatch('webchat:mounted');
|
|
135
|
+
this.broadcastChannel.addEventListener('message', this.onOutboundMessageSent);
|
|
136
|
+
window.addEventListener('keydown', this.closePopoverOnEscape, true);
|
|
137
|
+
this.scheduleBehaviourOpen();
|
|
138
|
+
super.connect();
|
|
139
|
+
}
|
|
140
|
+
disconnect() {
|
|
141
|
+
this.cancelBehaviourOpen();
|
|
142
|
+
this.clearPopoverOpenAnimation();
|
|
143
|
+
this.teardownTeaser();
|
|
144
|
+
this.teardownOpeningSequence();
|
|
145
|
+
this.broadcastChannel.removeEventListener('message', this.onOutboundMessageSent);
|
|
146
|
+
this.messagesContainerTarget.removeEventListener('scroll', this.onScroll);
|
|
147
|
+
this.messagesContainerTarget.removeEventListener('wheel', this.stopHostScrollPropagation, SCROLL_ISOLATION_EVENT_OPTIONS);
|
|
148
|
+
this.messagesContainerTarget.removeEventListener('touchmove', this.stopHostScrollPropagation, SCROLL_ISOLATION_EVENT_OPTIONS);
|
|
149
|
+
window.removeEventListener('keydown', this.closePopoverOnEscape, true);
|
|
122
150
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
}
|
|
144
|
-
}, {
|
|
145
|
-
key: "onTypingStart",
|
|
146
|
-
value: function onTypingStart() {
|
|
147
|
-
if (this.typingIndicatorVisible) {
|
|
148
|
-
return this.resetTypingIndicatorTimer();
|
|
149
|
-
}
|
|
150
|
-
this.showTypingIndicator();
|
|
151
|
-
}
|
|
152
|
-
}, {
|
|
153
|
-
key: "showOptimisticTypingIndicator",
|
|
154
|
-
value: function showOptimisticTypingIndicator() {
|
|
155
|
-
if (this.typingIndicatorVisible) return;
|
|
156
|
-
this.showTypingIndicator();
|
|
151
|
+
// Clean up typing indicator timeouts
|
|
152
|
+
this.clearTypingIndicator();
|
|
153
|
+
this.broadcastChannel.close();
|
|
154
|
+
this.floatingUICleanup();
|
|
155
|
+
super.disconnect();
|
|
156
|
+
}
|
|
157
|
+
setupMessagesContainerScrollIsolation() {
|
|
158
|
+
this.messagesContainerTarget.style.overscrollBehavior = 'contain';
|
|
159
|
+
this.messagesContainerTarget.style.webkitOverflowScrolling = 'touch';
|
|
160
|
+
this.messagesContainerTarget.style.touchAction = 'pan-y';
|
|
161
|
+
this.messagesContainerTarget.setAttribute('data-lenis-prevent', '');
|
|
162
|
+
this.messagesContainerTarget.setAttribute('data-lenis-prevent-wheel', '');
|
|
163
|
+
this.messagesContainerTarget.setAttribute('data-lenis-prevent-touch', '');
|
|
164
|
+
}
|
|
165
|
+
stopHostScrollPropagation(event) {
|
|
166
|
+
event.stopPropagation();
|
|
167
|
+
}
|
|
168
|
+
onTypingStart() {
|
|
169
|
+
if (this.typingIndicatorVisible) {
|
|
170
|
+
return this.resetTypingIndicatorTimer();
|
|
157
171
|
}
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
+
this.showTypingIndicator();
|
|
173
|
+
}
|
|
174
|
+
showOptimisticTypingIndicator() {
|
|
175
|
+
if (this.typingIndicatorVisible) return;
|
|
176
|
+
this.showTypingIndicator();
|
|
177
|
+
}
|
|
178
|
+
showTypingIndicator() {
|
|
179
|
+
this.clearTypingIndicator();
|
|
180
|
+
this.typingIndicatorVisible = true;
|
|
181
|
+
const indicator = this.typingIndicatorTemplateTarget.cloneNode(true);
|
|
182
|
+
indicator.setAttribute('data-hellotext--webchat-target', 'typingIndicator');
|
|
183
|
+
indicator.style.display = 'flex';
|
|
184
|
+
this.messagesContainerTarget.appendChild(indicator);
|
|
185
|
+
requestAnimationFrame(() => {
|
|
186
|
+
this.messagesContainerTarget.scroll({
|
|
187
|
+
top: this.messagesContainerTarget.scrollHeight,
|
|
188
|
+
behavior: 'instant'
|
|
172
189
|
});
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
if (!this.typingIndicatorVisible) return;
|
|
190
|
+
});
|
|
191
|
+
const timeout = this.typingIndicatorKeepAliveValue;
|
|
192
|
+
this.incomingTypingIndicatorTimeout = setTimeout(() => {
|
|
193
|
+
this.clearTypingIndicator();
|
|
194
|
+
}, timeout);
|
|
195
|
+
}
|
|
196
|
+
resetTypingIndicatorTimer() {
|
|
197
|
+
if (!this.typingIndicatorVisible) return;
|
|
182
198
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
199
|
+
// Clear existing timeout
|
|
200
|
+
clearTimeout(this.incomingTypingIndicatorTimeout);
|
|
201
|
+
clearTimeout(this.optimisticTypingTimeout);
|
|
186
202
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
if (this.hasTypingIndicatorTarget) {
|
|
197
|
-
this.typingIndicatorTarget.remove();
|
|
198
|
-
}
|
|
199
|
-
this.typingIndicatorVisible = false;
|
|
200
|
-
clearTimeout(this.incomingTypingIndicatorTimeout);
|
|
201
|
-
clearTimeout(this.optimisticTypingTimeout);
|
|
202
|
-
}
|
|
203
|
-
}, {
|
|
204
|
-
key: "onMessageInputChange",
|
|
205
|
-
value: function onMessageInputChange() {
|
|
206
|
-
this.resizeInput();
|
|
207
|
-
clearTimeout(this.typingIndicatorTimeout);
|
|
208
|
-
if (!this.hasSentTypingIndicator) {
|
|
209
|
-
this.webChatChannel.startTypingIndicator();
|
|
210
|
-
this.hasSentTypingIndicator = true;
|
|
211
|
-
}
|
|
212
|
-
this.typingIndicatorTimeout = setTimeout(() => {
|
|
213
|
-
this.hasSentTypingIndicator = false;
|
|
214
|
-
}, 3000);
|
|
203
|
+
// Use unified timeout for all typing indicators
|
|
204
|
+
const timeout = this.typingIndicatorKeepAliveValue;
|
|
205
|
+
this.incomingTypingIndicatorTimeout = setTimeout(() => {
|
|
206
|
+
this.clearTypingIndicator();
|
|
207
|
+
}, timeout);
|
|
208
|
+
}
|
|
209
|
+
clearTypingIndicator() {
|
|
210
|
+
if (this.hasTypingIndicatorTarget) {
|
|
211
|
+
this.typingIndicatorTarget.remove();
|
|
215
212
|
}
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
213
|
+
this.typingIndicatorVisible = false;
|
|
214
|
+
clearTimeout(this.incomingTypingIndicatorTimeout);
|
|
215
|
+
clearTimeout(this.optimisticTypingTimeout);
|
|
216
|
+
}
|
|
217
|
+
onMessageInputChange() {
|
|
218
|
+
this.resizeInput();
|
|
219
|
+
clearTimeout(this.typingIndicatorTimeout);
|
|
220
|
+
if (!this.hasSentTypingIndicator) {
|
|
221
|
+
this.webChatChannel.startTypingIndicator();
|
|
222
|
+
this.hasSentTypingIndicator = true;
|
|
223
|
+
}
|
|
224
|
+
this.typingIndicatorTimeout = setTimeout(() => {
|
|
225
|
+
this.hasSentTypingIndicator = false;
|
|
226
|
+
}, 3000);
|
|
227
|
+
}
|
|
228
|
+
onOutboundMessageSent(event) {
|
|
229
|
+
const {
|
|
230
|
+
data
|
|
231
|
+
} = event;
|
|
232
|
+
const callbacks = {
|
|
233
|
+
'message:sent': data => {
|
|
234
|
+
const element = new DOMParser().parseFromString(data.element, 'text/html').body.firstElementChild;
|
|
235
|
+
this.localizeMessageTimestamps(element);
|
|
226
236
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
} else {
|
|
231
|
-
this.messagesContainerTarget.appendChild(element);
|
|
232
|
-
}
|
|
233
|
-
element.scrollIntoView({
|
|
234
|
-
behavior: 'instant'
|
|
235
|
-
});
|
|
236
|
-
},
|
|
237
|
-
'message:failed': data => {
|
|
238
|
-
const element = this.messagesContainerTarget.querySelector(`#${data.id}`);
|
|
239
|
-
this.markMessageFailed(element, data.reason);
|
|
240
|
-
}
|
|
241
|
-
};
|
|
242
|
-
if (callbacks[data.type]) {
|
|
243
|
-
callbacks[data.type](data);
|
|
244
|
-
} else {
|
|
245
|
-
console.log(`Unhandled message event: ${data.type}`);
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
}, {
|
|
249
|
-
key: "onScroll",
|
|
250
|
-
value: async function onScroll() {
|
|
251
|
-
if (this.messagesContainerTarget.scrollTop > 300 || !this.nextPageValue || this.fetchingNextPage) return;
|
|
252
|
-
this.fetchingNextPage = true;
|
|
253
|
-
const response = await this.messagesAPI.index({
|
|
254
|
-
page: this.nextPageValue,
|
|
255
|
-
session: _hellotext.default.session
|
|
256
|
-
});
|
|
257
|
-
const {
|
|
258
|
-
next: nextPage,
|
|
259
|
-
messages
|
|
260
|
-
} = await response.json();
|
|
261
|
-
this.nextPageValue = nextPage;
|
|
262
|
-
this.oldScrollHeight = this.messagesContainerTarget.scrollHeight;
|
|
263
|
-
messages.forEach(message => {
|
|
264
|
-
const {
|
|
265
|
-
body,
|
|
266
|
-
attachments
|
|
267
|
-
} = message;
|
|
268
|
-
const createdAt = message.created_at || message.createdAt;
|
|
269
|
-
const element = this.messageTemplateTarget.cloneNode(true);
|
|
270
|
-
element.classList.add('hellotext--webchat-message');
|
|
271
|
-
element.setAttribute('data-hellotext--webchat-target', 'message');
|
|
272
|
-
element.setAttribute('data-id', message.id);
|
|
273
|
-
if (createdAt) {
|
|
274
|
-
element.setAttribute('data-created-at', createdAt);
|
|
275
|
-
}
|
|
276
|
-
element.style.removeProperty('display');
|
|
277
|
-
(0, _sanitize_html.setSanitizedRichText)(element.querySelector('[data-body]'), body);
|
|
278
|
-
if (message.state === 'received') {
|
|
279
|
-
element.classList.add('received');
|
|
237
|
+
// Insert message before typing indicator if one exists
|
|
238
|
+
if (this.typingIndicatorVisible && this.hasTypingIndicatorTarget) {
|
|
239
|
+
this.messagesContainerTarget.insertBefore(element, this.typingIndicatorTarget);
|
|
280
240
|
} else {
|
|
281
|
-
|
|
241
|
+
this.messagesContainerTarget.appendChild(element);
|
|
282
242
|
}
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
var _this$messageAttachme;
|
|
286
|
-
const image = this.attachmentImageTarget.cloneNode(true);
|
|
287
|
-
image.removeAttribute('data-hellotext--webchat-target');
|
|
288
|
-
image.src = attachmentUrl;
|
|
289
|
-
image.style.display = 'block';
|
|
290
|
-
(_this$messageAttachme = this.messageAttachmentsContainer(element)) === null || _this$messageAttachme === void 0 ? void 0 : _this$messageAttachme.appendChild(image);
|
|
291
|
-
});
|
|
292
|
-
}
|
|
293
|
-
element.setAttribute('data-body', body);
|
|
294
|
-
this.localizeMessageTimestamp(element.querySelector('[data-message-timestamp]'), createdAt);
|
|
295
|
-
this.messagesContainerTarget.prepend(element);
|
|
296
|
-
});
|
|
297
|
-
this.messagesContainerTarget.scroll({
|
|
298
|
-
top: this.messagesContainerTarget.scrollHeight - this.oldScrollHeight,
|
|
299
|
-
behavior: 'instant'
|
|
300
|
-
});
|
|
301
|
-
this.fetchingNextPage = false;
|
|
302
|
-
}
|
|
303
|
-
}, {
|
|
304
|
-
key: "onClickOutside",
|
|
305
|
-
value: function onClickOutside(event) {
|
|
306
|
-
if (_webchat.Webchat.mode === _webchat.modes.POPOVER && this.openValue && event.target.nodeType && this.element.contains(event.target) === false) {
|
|
307
|
-
this.openValue = false;
|
|
308
|
-
}
|
|
309
|
-
}
|
|
310
|
-
}, {
|
|
311
|
-
key: "closePopover",
|
|
312
|
-
value: function closePopover() {
|
|
313
|
-
this.clearPopoverOpenAnimation();
|
|
314
|
-
this.popoverTarget.classList.remove(...this.fadeOutClasses);
|
|
315
|
-
this.openValue = false;
|
|
316
|
-
}
|
|
317
|
-
}, {
|
|
318
|
-
key: "preparePopoverOpenAnimation",
|
|
319
|
-
value: function preparePopoverOpenAnimation() {
|
|
320
|
-
this.clearPopoverOpenAnimation();
|
|
321
|
-
this.popoverTarget.classList.remove(...this.fadeOutClasses);
|
|
322
|
-
this.popoverTarget.classList.add('hellotext--webchat-popover-opening');
|
|
323
|
-
this.popoverOpenAnimationTimeout = setTimeout(() => {
|
|
324
|
-
this.popoverTarget.classList.remove('hellotext--webchat-popover-opening');
|
|
325
|
-
this.popoverOpenAnimationTimeout = null;
|
|
326
|
-
}, POPOVER_ANIMATION_DURATION);
|
|
327
|
-
}
|
|
328
|
-
}, {
|
|
329
|
-
key: "clearPopoverOpenAnimation",
|
|
330
|
-
value: function clearPopoverOpenAnimation() {
|
|
331
|
-
var _this$popoverTarget;
|
|
332
|
-
if (this.popoverOpenAnimationTimeout) {
|
|
333
|
-
clearTimeout(this.popoverOpenAnimationTimeout);
|
|
334
|
-
this.popoverOpenAnimationTimeout = null;
|
|
335
|
-
}
|
|
336
|
-
(_this$popoverTarget = this.popoverTarget) === null || _this$popoverTarget === void 0 ? void 0 : _this$popoverTarget.classList.remove('hellotext--webchat-popover-opening');
|
|
337
|
-
}
|
|
338
|
-
}, {
|
|
339
|
-
key: "onPopoverOpened",
|
|
340
|
-
value: function onPopoverOpened() {
|
|
341
|
-
var _this$dismissTeaserFo;
|
|
342
|
-
this.popoverTarget.classList.remove(...this.fadeOutClasses);
|
|
343
|
-
(_this$dismissTeaserFo = this.dismissTeaserForSession) === null || _this$dismissTeaserFo === void 0 ? void 0 : _this$dismissTeaserFo.call(this);
|
|
344
|
-
if (!this.onMobile) {
|
|
345
|
-
this.focusComposeInput();
|
|
346
|
-
}
|
|
347
|
-
if (!this.scrolled) {
|
|
348
|
-
requestAnimationFrame(() => {
|
|
349
|
-
this.messagesContainerTarget.scroll({
|
|
350
|
-
top: this.messagesContainerTarget.scrollHeight,
|
|
351
|
-
behavior: 'instant'
|
|
352
|
-
});
|
|
243
|
+
element.scrollIntoView({
|
|
244
|
+
behavior: 'instant'
|
|
353
245
|
});
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
if (this.messageTeaserValue) {
|
|
359
|
-
this.messageTeaserValue = null;
|
|
360
|
-
}
|
|
361
|
-
this.startOpeningSequence();
|
|
362
|
-
if (this.unreadCounterTarget.style.display === 'none') return;
|
|
363
|
-
this.unreadCounterTarget.style.display = 'none';
|
|
364
|
-
this.unreadCounterTarget.innerText = '0';
|
|
365
|
-
this.messagesAPI.markAsSeen();
|
|
366
|
-
}
|
|
367
|
-
}, {
|
|
368
|
-
key: "onPopoverClosed",
|
|
369
|
-
value: function onPopoverClosed() {
|
|
370
|
-
this.clearPopoverOpenAnimation();
|
|
371
|
-
_hellotext.default.eventEmitter.dispatch('webchat:closed');
|
|
372
|
-
localStorage.setItem(`hellotext--webchat--${this.idValue}`, 'closed');
|
|
373
|
-
}
|
|
374
|
-
}, {
|
|
375
|
-
key: "onMessageReaction",
|
|
376
|
-
value: function onMessageReaction(message) {
|
|
377
|
-
const {
|
|
378
|
-
message: messageId,
|
|
379
|
-
reaction,
|
|
380
|
-
type
|
|
381
|
-
} = message;
|
|
382
|
-
const messageElement = this.messageTargets.find(element => element.dataset.id === messageId);
|
|
383
|
-
const reactionsContainer = messageElement.querySelector('[data-reactions]');
|
|
384
|
-
if (type === 'reaction.destroy') {
|
|
385
|
-
const reactionElement = reactionsContainer.querySelector(`[data-id="${reaction.id}"]`);
|
|
386
|
-
return reactionElement.remove();
|
|
387
|
-
}
|
|
388
|
-
if (reactionsContainer.querySelector(`[data-id="${reaction.id}"]`)) {
|
|
389
|
-
const reactionElement = reactionsContainer.querySelector(`[data-id="${reaction.id}"]`);
|
|
390
|
-
reactionElement.innerText = reaction.emoji;
|
|
391
|
-
} else {
|
|
392
|
-
const reactionElement = document.createElement('span');
|
|
393
|
-
reactionElement.innerText = reaction.emoji;
|
|
394
|
-
reactionElement.setAttribute('data-id', reaction.id);
|
|
395
|
-
reactionsContainer.appendChild(reactionElement);
|
|
246
|
+
},
|
|
247
|
+
'message:failed': data => {
|
|
248
|
+
const element = this.messagesContainerTarget.querySelector(`#${data.id}`);
|
|
249
|
+
this.markMessageFailed(element, data.reason);
|
|
396
250
|
}
|
|
251
|
+
};
|
|
252
|
+
if (callbacks[data.type]) {
|
|
253
|
+
callbacks[data.type](data);
|
|
254
|
+
} else {
|
|
255
|
+
console.log(`Unhandled message event: ${data.type}`);
|
|
397
256
|
}
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
257
|
+
}
|
|
258
|
+
async onScroll() {
|
|
259
|
+
if (this.messagesContainerTarget.scrollTop > 300 || !this.nextPageValue || this.fetchingNextPage) return;
|
|
260
|
+
this.fetchingNextPage = true;
|
|
261
|
+
const response = await this.messagesAPI.index({
|
|
262
|
+
page: this.nextPageValue,
|
|
263
|
+
session: _hellotext.default.session
|
|
264
|
+
});
|
|
265
|
+
const {
|
|
266
|
+
next: nextPage,
|
|
267
|
+
messages
|
|
268
|
+
} = await response.json();
|
|
269
|
+
this.nextPageValue = nextPage;
|
|
270
|
+
this.oldScrollHeight = this.messagesContainerTarget.scrollHeight;
|
|
271
|
+
messages.forEach(message => {
|
|
402
272
|
const {
|
|
403
|
-
id,
|
|
404
273
|
body,
|
|
405
|
-
attachments
|
|
406
|
-
teaser
|
|
274
|
+
attachments
|
|
407
275
|
} = message;
|
|
408
276
|
const createdAt = message.created_at || message.createdAt;
|
|
409
|
-
if (!this.claimMessageId(id)) return;
|
|
410
|
-
(_this$hideTeaser = this.hideTeaser) === null || _this$hideTeaser === void 0 ? void 0 : _this$hideTeaser.call(this);
|
|
411
|
-
if (message.carousel) {
|
|
412
|
-
return this.insertCarouselMessage(message, options);
|
|
413
|
-
}
|
|
414
277
|
const element = this.messageTemplateTarget.cloneNode(true);
|
|
415
278
|
element.classList.add('hellotext--webchat-message');
|
|
416
|
-
element.style.display = 'flex';
|
|
417
|
-
(0, _sanitize_html.setSanitizedRichText)(element.querySelector('[data-body]'), body);
|
|
418
|
-
element.setAttribute('data-id', id);
|
|
419
279
|
element.setAttribute('data-hellotext--webchat-target', 'message');
|
|
420
|
-
|
|
421
|
-
|
|
280
|
+
element.setAttribute('data-id', message.id);
|
|
281
|
+
if (createdAt) {
|
|
282
|
+
element.setAttribute('data-created-at', createdAt);
|
|
283
|
+
}
|
|
284
|
+
element.style.removeProperty('display');
|
|
285
|
+
(0, _sanitize_html.setSanitizedRichText)(element.querySelector('[data-body]'), body);
|
|
286
|
+
if (message.state === 'received') {
|
|
287
|
+
element.classList.add('received');
|
|
288
|
+
} else {
|
|
289
|
+
element.classList.remove('received');
|
|
290
|
+
}
|
|
422
291
|
if (attachments) {
|
|
423
292
|
attachments.forEach(attachmentUrl => {
|
|
424
|
-
var _this$messageAttachme2;
|
|
425
293
|
const image = this.attachmentImageTarget.cloneNode(true);
|
|
294
|
+
image.removeAttribute('data-hellotext--webchat-target');
|
|
426
295
|
image.src = attachmentUrl;
|
|
427
296
|
image.style.display = 'block';
|
|
428
|
-
|
|
297
|
+
this.messageAttachmentsContainer(element)?.appendChild(image);
|
|
429
298
|
});
|
|
430
299
|
}
|
|
431
|
-
|
|
432
|
-
this.
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
this.messagesAPI.markAsSeen(id);
|
|
445
|
-
return;
|
|
446
|
-
}
|
|
447
|
-
this.incrementUnreadCounter();
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
// TCP broadcasts may deliver the same incoming message twice before Stimulus
|
|
451
|
-
// has connected the appended message target. Claiming the id in memory closes
|
|
452
|
-
// that window, while the target check still drops messages rendered earlier.
|
|
453
|
-
}, {
|
|
454
|
-
key: "claimMessageId",
|
|
455
|
-
value: function claimMessageId(id) {
|
|
456
|
-
const messageTargets = this.messageTargets || [];
|
|
457
|
-
if (this.messageIds.has(id)) return false;
|
|
458
|
-
this.messageIds.add(id);
|
|
459
|
-
return !messageTargets.some(element => element.dataset.id === id);
|
|
460
|
-
}
|
|
461
|
-
}, {
|
|
462
|
-
key: "captureCatchUpCursor",
|
|
463
|
-
value: function captureCatchUpCursor() {
|
|
464
|
-
this.catchUpAfterMessageId = this.lastRenderedMessageId;
|
|
465
|
-
}
|
|
466
|
-
}, {
|
|
467
|
-
key: "catchUpMessages",
|
|
468
|
-
value: async function catchUpMessages() {
|
|
469
|
-
const afterId = this.catchUpAfterMessageId;
|
|
470
|
-
if (!afterId || this.fetchingCatchUpMessages) return;
|
|
471
|
-
this.fetchingCatchUpMessages = true;
|
|
472
|
-
try {
|
|
473
|
-
const response = await this.messagesAPI.catchUp(afterId);
|
|
474
|
-
const {
|
|
475
|
-
messages = []
|
|
476
|
-
} = await response.json();
|
|
477
|
-
messages.forEach(message => this.onMessageReceived(message, {
|
|
478
|
-
scroll: false
|
|
479
|
-
}));
|
|
480
|
-
this.catchUpAfterMessageId = this.lastRenderedMessageId;
|
|
481
|
-
} finally {
|
|
482
|
-
this.fetchingCatchUpMessages = false;
|
|
483
|
-
}
|
|
484
|
-
}
|
|
485
|
-
}, {
|
|
486
|
-
key: "lastRenderedMessageId",
|
|
487
|
-
get: function () {
|
|
488
|
-
var _messages;
|
|
489
|
-
const messages = this.persistedMessageElements;
|
|
490
|
-
return ((_messages = messages[messages.length - 1]) === null || _messages === void 0 ? void 0 : _messages.dataset.id) || null;
|
|
491
|
-
}
|
|
492
|
-
}, {
|
|
493
|
-
key: "persistedMessageElements",
|
|
494
|
-
get: function () {
|
|
495
|
-
return Array.from(this.messagesContainerTarget.querySelectorAll('.hellotext--webchat-message[data-id]'));
|
|
496
|
-
}
|
|
497
|
-
}, {
|
|
498
|
-
key: "setMessageCreatedAt",
|
|
499
|
-
value: function setMessageCreatedAt(element, createdAt) {
|
|
500
|
-
if (createdAt) {
|
|
501
|
-
element.setAttribute('data-created-at', createdAt);
|
|
502
|
-
}
|
|
503
|
-
}
|
|
504
|
-
}, {
|
|
505
|
-
key: "insertMessageElement",
|
|
506
|
-
value: function insertMessageElement(element) {
|
|
507
|
-
const nextElement = this.nextMessageElementFor(element);
|
|
508
|
-
if (nextElement) {
|
|
509
|
-
this.messagesContainerTarget.insertBefore(element, nextElement);
|
|
510
|
-
} else {
|
|
511
|
-
this.messagesContainerTarget.appendChild(element);
|
|
512
|
-
}
|
|
300
|
+
element.setAttribute('data-body', body);
|
|
301
|
+
this.localizeMessageTimestamp(element.querySelector('[data-message-timestamp]'), createdAt);
|
|
302
|
+
this.messagesContainerTarget.prepend(element);
|
|
303
|
+
});
|
|
304
|
+
this.messagesContainerTarget.scroll({
|
|
305
|
+
top: this.messagesContainerTarget.scrollHeight - this.oldScrollHeight,
|
|
306
|
+
behavior: 'instant'
|
|
307
|
+
});
|
|
308
|
+
this.fetchingNextPage = false;
|
|
309
|
+
}
|
|
310
|
+
onClickOutside(event) {
|
|
311
|
+
if (_webchat.Webchat.mode === _webchat.modes.POPOVER && this.openValue && event.target.nodeType && this.element.contains(event.target) === false) {
|
|
312
|
+
this.openValue = false;
|
|
513
313
|
}
|
|
514
|
-
}
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
314
|
+
}
|
|
315
|
+
closePopover() {
|
|
316
|
+
this.clearPopoverOpenAnimation();
|
|
317
|
+
this.popoverTarget.classList.remove(...this.fadeOutClasses);
|
|
318
|
+
this.openValue = false;
|
|
319
|
+
}
|
|
320
|
+
preparePopoverOpenAnimation() {
|
|
321
|
+
this.clearPopoverOpenAnimation();
|
|
322
|
+
this.popoverTarget.classList.remove(...this.fadeOutClasses);
|
|
323
|
+
this.popoverTarget.classList.add('hellotext--webchat-popover-opening');
|
|
324
|
+
this.popoverOpenAnimationTimeout = setTimeout(() => {
|
|
325
|
+
this.popoverTarget.classList.remove('hellotext--webchat-popover-opening');
|
|
326
|
+
this.popoverOpenAnimationTimeout = null;
|
|
327
|
+
}, POPOVER_ANIMATION_DURATION);
|
|
328
|
+
}
|
|
329
|
+
clearPopoverOpenAnimation() {
|
|
330
|
+
if (this.popoverOpenAnimationTimeout) {
|
|
331
|
+
clearTimeout(this.popoverOpenAnimationTimeout);
|
|
332
|
+
this.popoverOpenAnimationTimeout = null;
|
|
524
333
|
}
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
this.teaserMessageTargets.forEach(teaserMessage => teaserMessage.classList.add('hidden'));
|
|
533
|
-
this.inboundMessageTeaserBodyTarget.textContent = this.messageTeaserValue;
|
|
534
|
-
this.inboundMessageTeaserTarget.classList.remove('hidden');
|
|
535
|
-
this.teaserTarget.classList.toggle('invisible', this.openValue);
|
|
334
|
+
this.popoverTarget?.classList.remove('hellotext--webchat-popover-opening');
|
|
335
|
+
}
|
|
336
|
+
onPopoverOpened() {
|
|
337
|
+
this.popoverTarget.classList.remove(...this.fadeOutClasses);
|
|
338
|
+
this.dismissTeaserForSession?.();
|
|
339
|
+
if (!this.onMobile) {
|
|
340
|
+
this.focusComposeInput();
|
|
536
341
|
}
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
const createdAt = message.created_at || message.createdAt;
|
|
543
|
-
const element = (0, _sanitize_html.sanitizedWebchatComponentFragment)(html).firstElementChild;
|
|
544
|
-
element.classList.add('hellotext--webchat-message');
|
|
545
|
-
element.setAttribute('data-id', message.id);
|
|
546
|
-
element.setAttribute('data-hellotext--webchat-target', 'message');
|
|
547
|
-
this.setMessageCreatedAt(element, createdAt);
|
|
548
|
-
this.localizeMessageTimestamps(element);
|
|
549
|
-
this.clearTypingIndicator();
|
|
550
|
-
this.insertMessageElement(element);
|
|
551
|
-
if (options.scroll !== false) {
|
|
552
|
-
element.scrollIntoView({
|
|
553
|
-
behavior: 'smooth'
|
|
342
|
+
if (!this.scrolled) {
|
|
343
|
+
requestAnimationFrame(() => {
|
|
344
|
+
this.messagesContainerTarget.scroll({
|
|
345
|
+
top: this.messagesContainerTarget.scrollHeight,
|
|
346
|
+
behavior: 'instant'
|
|
554
347
|
});
|
|
555
|
-
}
|
|
556
|
-
_hellotext.default.eventEmitter.dispatch('webchat:message:received', {
|
|
557
|
-
...message,
|
|
558
|
-
body: ((_element$querySelecto = element.querySelector('[data-body]')) === null || _element$querySelecto === void 0 ? void 0 : _element$querySelecto.innerText) || ''
|
|
559
|
-
});
|
|
560
|
-
this.updateMessageTeaser(message.teaser);
|
|
561
|
-
if (this.openValue) {
|
|
562
|
-
this.messagesAPI.markAsSeen(message.id);
|
|
563
|
-
return;
|
|
564
|
-
}
|
|
565
|
-
this.incrementUnreadCounter();
|
|
566
|
-
}
|
|
567
|
-
}, {
|
|
568
|
-
key: "resizeInput",
|
|
569
|
-
value: function resizeInput() {
|
|
570
|
-
const maxHeight = 96;
|
|
571
|
-
|
|
572
|
-
// Temporarily reset height to its natural content size
|
|
573
|
-
this.inputTarget.style.height = 'auto';
|
|
574
|
-
|
|
575
|
-
// Set the height to the scrollHeight, which is the minimum height
|
|
576
|
-
// the element needs to fit its content without a scrollbar.
|
|
577
|
-
const scrollHeight = this.inputTarget.scrollHeight;
|
|
578
|
-
this.inputTarget.style.height = `${Math.min(scrollHeight, maxHeight)}px`;
|
|
579
|
-
}
|
|
580
|
-
}, {
|
|
581
|
-
key: "sendQuickReplyMessage",
|
|
582
|
-
value: async function sendQuickReplyMessage({
|
|
583
|
-
detail: {
|
|
584
|
-
id,
|
|
585
|
-
product,
|
|
586
|
-
buttonId,
|
|
587
|
-
body,
|
|
588
|
-
cardElement
|
|
589
|
-
}
|
|
590
|
-
}) {
|
|
591
|
-
var _this$dismissTeaserFo2, _cardElement$querySel;
|
|
592
|
-
(_this$dismissTeaserFo2 = this.dismissTeaserForSession) === null || _this$dismissTeaserFo2 === void 0 ? void 0 : _this$dismissTeaserFo2.call(this);
|
|
593
|
-
const formData = new FormData();
|
|
594
|
-
formData.append('message[body]', body);
|
|
595
|
-
if (id) formData.append('message[replied_to]', id);
|
|
596
|
-
if (product) formData.append('message[product]', product);
|
|
597
|
-
if (buttonId) formData.append('message[button]', buttonId);
|
|
598
|
-
formData.append('session', _hellotext.default.session);
|
|
599
|
-
formData.append('locale', _core.Locale.toString());
|
|
600
|
-
this.appendOpeningSequenceMessageIds(formData);
|
|
601
|
-
const element = this.buildMessageElement();
|
|
602
|
-
const attachment = cardElement === null || cardElement === void 0 ? void 0 : (_cardElement$querySel = cardElement.querySelector('img')) === null || _cardElement$querySel === void 0 ? void 0 : _cardElement$querySel.cloneNode(true);
|
|
603
|
-
element.querySelector('[data-body]').innerText = body;
|
|
604
|
-
if (attachment) {
|
|
605
|
-
var _this$messageAttachme3;
|
|
606
|
-
attachment.removeAttribute('width');
|
|
607
|
-
attachment.removeAttribute('height');
|
|
608
|
-
(_this$messageAttachme3 = this.messageAttachmentsContainer(element)) === null || _this$messageAttachme3 === void 0 ? void 0 : _this$messageAttachme3.appendChild(attachment);
|
|
609
|
-
}
|
|
610
|
-
if (this.typingIndicatorVisible && this.hasTypingIndicatorTarget) {
|
|
611
|
-
this.messagesContainerTarget.insertBefore(element, this.typingIndicatorTarget);
|
|
612
|
-
} else {
|
|
613
|
-
this.messagesContainerTarget.appendChild(element);
|
|
614
|
-
}
|
|
615
|
-
element.scrollIntoView({
|
|
616
|
-
behavior: 'smooth'
|
|
617
|
-
});
|
|
618
|
-
this.broadcastChannel.postMessage({
|
|
619
|
-
type: 'message:sent',
|
|
620
|
-
element: element.outerHTML
|
|
621
348
|
});
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
349
|
+
this.scrolled = true;
|
|
350
|
+
}
|
|
351
|
+
_hellotext.default.eventEmitter.dispatch('webchat:opened');
|
|
352
|
+
localStorage.setItem(`hellotext--webchat--${this.idValue}`, 'opened');
|
|
353
|
+
if (this.messageTeaserValue) {
|
|
354
|
+
this.messageTeaserValue = null;
|
|
355
|
+
}
|
|
356
|
+
this.startOpeningSequence();
|
|
357
|
+
if (this.unreadCounterTarget.style.display === 'none') return;
|
|
358
|
+
this.unreadCounterTarget.style.display = 'none';
|
|
359
|
+
this.unreadCounterTarget.innerText = '0';
|
|
360
|
+
this.messagesAPI.markAsSeen();
|
|
361
|
+
}
|
|
362
|
+
onPopoverClosed() {
|
|
363
|
+
this.clearPopoverOpenAnimation();
|
|
364
|
+
_hellotext.default.eventEmitter.dispatch('webchat:closed');
|
|
365
|
+
localStorage.setItem(`hellotext--webchat--${this.idValue}`, 'closed');
|
|
366
|
+
}
|
|
367
|
+
onMessageReaction(message) {
|
|
368
|
+
const {
|
|
369
|
+
message: messageId,
|
|
370
|
+
reaction,
|
|
371
|
+
type
|
|
372
|
+
} = message;
|
|
373
|
+
const messageElement = this.messageTargets.find(element => element.dataset.id === messageId);
|
|
374
|
+
const reactionsContainer = messageElement.querySelector('[data-reactions]');
|
|
375
|
+
if (type === 'reaction.destroy') {
|
|
376
|
+
const reactionElement = reactionsContainer.querySelector(`[data-id="${reaction.id}"]`);
|
|
377
|
+
return reactionElement.remove();
|
|
378
|
+
}
|
|
379
|
+
if (reactionsContainer.querySelector(`[data-id="${reaction.id}"]`)) {
|
|
380
|
+
const reactionElement = reactionsContainer.querySelector(`[data-id="${reaction.id}"]`);
|
|
381
|
+
reactionElement.innerText = reaction.emoji;
|
|
382
|
+
} else {
|
|
383
|
+
const reactionElement = document.createElement('span');
|
|
384
|
+
reactionElement.innerText = reaction.emoji;
|
|
385
|
+
reactionElement.setAttribute('data-id', reaction.id);
|
|
386
|
+
reactionsContainer.appendChild(reactionElement);
|
|
645
387
|
}
|
|
646
|
-
}
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
this.
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
this.
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
});
|
|
675
|
-
this.broadcastChannel.postMessage({
|
|
676
|
-
type: 'message:sent',
|
|
677
|
-
element: element.outerHTML
|
|
678
|
-
});
|
|
679
|
-
if (!this.typingIndicatorVisible) {
|
|
680
|
-
clearTimeout(this.optimisticTypingTimeout);
|
|
681
|
-
this.optimisticTypingTimeout = setTimeout(() => {
|
|
682
|
-
this.showOptimisticTypingIndicator();
|
|
683
|
-
}, this.optimisticTypingIndicatorWaitValue);
|
|
684
|
-
}
|
|
685
|
-
const response = await this.messagesAPI.create(formData);
|
|
686
|
-
if (response.failed) {
|
|
687
|
-
clearTimeout(this.optimisticTypingTimeout);
|
|
688
|
-
return this.markMessageFailedFromResponse(response, element);
|
|
689
|
-
}
|
|
690
|
-
const data = await response.json();
|
|
691
|
-
element.setAttribute('data-id', data.id);
|
|
692
|
-
this.localizeMessageTimestamp(element.querySelector('[data-message-timestamp]'), data.created_at || data.createdAt);
|
|
693
|
-
this.clearRevealedOpeningSequenceMessageIds();
|
|
694
|
-
_hellotext.default.eventEmitter.dispatch('webchat:message:sent', {
|
|
695
|
-
id: data.id,
|
|
696
|
-
body: text,
|
|
697
|
-
attachments: [],
|
|
698
|
-
type: 'quick_reply',
|
|
699
|
-
teaser: {
|
|
700
|
-
text: label || text,
|
|
701
|
-
value: value || text,
|
|
702
|
-
type: buttonType
|
|
703
|
-
}
|
|
388
|
+
}
|
|
389
|
+
onMessageReceived(message, options = {}) {
|
|
390
|
+
const {
|
|
391
|
+
id,
|
|
392
|
+
body,
|
|
393
|
+
attachments,
|
|
394
|
+
teaser
|
|
395
|
+
} = message;
|
|
396
|
+
const createdAt = message.created_at || message.createdAt;
|
|
397
|
+
if (!this.claimMessageId(id)) return;
|
|
398
|
+
this.hideTeaser?.();
|
|
399
|
+
if (message.carousel) {
|
|
400
|
+
return this.insertCarouselMessage(message, options);
|
|
401
|
+
}
|
|
402
|
+
const element = this.messageTemplateTarget.cloneNode(true);
|
|
403
|
+
element.classList.add('hellotext--webchat-message');
|
|
404
|
+
element.style.display = 'flex';
|
|
405
|
+
(0, _sanitize_html.setSanitizedRichText)(element.querySelector('[data-body]'), body);
|
|
406
|
+
element.setAttribute('data-id', id);
|
|
407
|
+
element.setAttribute('data-hellotext--webchat-target', 'message');
|
|
408
|
+
this.setMessageCreatedAt(element, createdAt);
|
|
409
|
+
this.localizeMessageTimestamp(element.querySelector('[data-message-timestamp]'), createdAt);
|
|
410
|
+
if (attachments) {
|
|
411
|
+
attachments.forEach(attachmentUrl => {
|
|
412
|
+
const image = this.attachmentImageTarget.cloneNode(true);
|
|
413
|
+
image.src = attachmentUrl;
|
|
414
|
+
image.style.display = 'block';
|
|
415
|
+
this.messageAttachmentsContainer(element)?.appendChild(image);
|
|
704
416
|
});
|
|
705
|
-
if (data.conversation && data.conversation !== this.conversationIdValue) {
|
|
706
|
-
this.conversationIdValue = data.conversation;
|
|
707
|
-
this.webChatChannel.updateSubscriptionWith(this.conversationIdValue);
|
|
708
|
-
}
|
|
709
|
-
if (this.typingIndicatorVisible) {
|
|
710
|
-
this.resetTypingIndicatorTimer();
|
|
711
|
-
}
|
|
712
417
|
}
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
};
|
|
721
|
-
if (this.inputTarget.value.trim().length === 0 && this.files.length === 0) {
|
|
722
|
-
if (e && e.target) {
|
|
723
|
-
e.preventDefault();
|
|
724
|
-
}
|
|
725
|
-
return;
|
|
726
|
-
}
|
|
727
|
-
(_this$dismissTeaserFo4 = this.dismissTeaserForSession) === null || _this$dismissTeaserFo4 === void 0 ? void 0 : _this$dismissTeaserFo4.call(this);
|
|
728
|
-
const formData = new FormData();
|
|
729
|
-
if (this.inputTarget.value.trim().length > 0) {
|
|
730
|
-
formData.append('message[body]', this.inputTarget.value);
|
|
731
|
-
} else {
|
|
732
|
-
delete message.body;
|
|
733
|
-
}
|
|
734
|
-
this.files.forEach(file => {
|
|
735
|
-
formData.append('message[attachments][]', file);
|
|
736
|
-
});
|
|
737
|
-
formData.append('session', _hellotext.default.session);
|
|
738
|
-
formData.append('locale', _core.Locale.toString());
|
|
739
|
-
this.appendOpeningSequenceMessageIds(formData);
|
|
740
|
-
const element = this.buildMessageElement();
|
|
741
|
-
if (this.inputTarget.value.trim().length > 0) {
|
|
742
|
-
element.querySelector('[data-body]').innerText = this.inputTarget.value;
|
|
743
|
-
} else {
|
|
744
|
-
element.querySelector('[data-message-bubble]').remove();
|
|
745
|
-
}
|
|
746
|
-
const attachments = this.attachmentContainerTarget.querySelectorAll('img');
|
|
747
|
-
if (attachments.length > 0) {
|
|
748
|
-
attachments.forEach(attachment => {
|
|
749
|
-
var _this$messageAttachme4;
|
|
750
|
-
(_this$messageAttachme4 = this.messageAttachmentsContainer(element)) === null || _this$messageAttachme4 === void 0 ? void 0 : _this$messageAttachme4.appendChild(attachment.cloneNode(true));
|
|
751
|
-
});
|
|
752
|
-
}
|
|
753
|
-
|
|
754
|
-
// Insert message before typing indicator if one exists
|
|
755
|
-
if (this.typingIndicatorVisible && this.hasTypingIndicatorTarget) {
|
|
756
|
-
this.messagesContainerTarget.insertBefore(element, this.typingIndicatorTarget);
|
|
757
|
-
} else {
|
|
758
|
-
this.messagesContainerTarget.appendChild(element);
|
|
759
|
-
}
|
|
418
|
+
this.clearTypingIndicator();
|
|
419
|
+
this.insertMessageElement(element);
|
|
420
|
+
_hellotext.default.eventEmitter.dispatch('webchat:message:received', {
|
|
421
|
+
...message,
|
|
422
|
+
body: element.querySelector('[data-body]').innerText
|
|
423
|
+
});
|
|
424
|
+
if (options.scroll !== false) {
|
|
760
425
|
element.scrollIntoView({
|
|
761
426
|
behavior: 'smooth'
|
|
762
427
|
});
|
|
763
|
-
this.broadcastChannel.postMessage({
|
|
764
|
-
type: 'message:sent',
|
|
765
|
-
element: element.outerHTML
|
|
766
|
-
});
|
|
767
|
-
this.inputTarget.value = '';
|
|
768
|
-
this.resizeInput();
|
|
769
|
-
this.files = [];
|
|
770
|
-
this.attachmentInputTarget.value = '';
|
|
771
|
-
this.attachmentContainerTarget.innerHTML = '';
|
|
772
|
-
this.attachmentContainerTarget.style.display = 'none';
|
|
773
|
-
this.errorMessageContainerTarget.style.display = 'none';
|
|
774
|
-
this.focusComposeInput();
|
|
775
|
-
|
|
776
|
-
// Set up optimistic typing indicator BEFORE making the API call
|
|
777
|
-
// This prevents race conditions with server responses
|
|
778
|
-
if (!this.typingIndicatorVisible) {
|
|
779
|
-
clearTimeout(this.optimisticTypingTimeout);
|
|
780
|
-
this.optimisticTypingTimeout = setTimeout(() => {
|
|
781
|
-
this.showOptimisticTypingIndicator();
|
|
782
|
-
}, this.optimisticTypingIndicatorWaitValue);
|
|
783
|
-
}
|
|
784
|
-
const response = await this.messagesAPI.create(formData);
|
|
785
|
-
if (response.failed) {
|
|
786
|
-
// Clear the optimistic typing indicator on failure
|
|
787
|
-
clearTimeout(this.optimisticTypingTimeout);
|
|
788
|
-
return this.markMessageFailedFromResponse(response, element);
|
|
789
|
-
}
|
|
790
|
-
const data = await response.json();
|
|
791
|
-
element.setAttribute('data-id', data.id);
|
|
792
|
-
message.id = data.id;
|
|
793
|
-
this.localizeMessageTimestamp(element.querySelector('[data-message-timestamp]'), data.created_at || data.createdAt);
|
|
794
|
-
this.clearRevealedOpeningSequenceMessageIds();
|
|
795
|
-
_hellotext.default.eventEmitter.dispatch('webchat:message:sent', message);
|
|
796
|
-
if (data.conversation !== this.conversationIdValue) {
|
|
797
|
-
this.conversationIdValue = data.conversation;
|
|
798
|
-
this.webChatChannel.updateSubscriptionWith(this.conversationIdValue);
|
|
799
|
-
}
|
|
800
|
-
|
|
801
|
-
// If there's already a typing indicator visible, reset its timer
|
|
802
|
-
if (this.typingIndicatorVisible) {
|
|
803
|
-
this.resetTypingIndicatorTimer();
|
|
804
|
-
}
|
|
805
|
-
this.attachmentContainerTarget.style.display = '';
|
|
806
428
|
}
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
element.id = `hellotext--webchat--${this.idValue}--message--${Date.now()}`;
|
|
812
|
-
element.classList.add('received');
|
|
813
|
-
element.style.removeProperty('display');
|
|
814
|
-
element.setAttribute('data-controller', 'hellotext--message');
|
|
815
|
-
element.setAttribute('data-hellotext--webchat-target', 'message');
|
|
816
|
-
this.localizeMessageTimestamp(element.querySelector('[data-message-timestamp]'), new Date());
|
|
817
|
-
return element;
|
|
429
|
+
this.updateMessageTeaser(teaser);
|
|
430
|
+
if (this.openValue) {
|
|
431
|
+
this.messagesAPI.markAsSeen(id);
|
|
432
|
+
return;
|
|
818
433
|
}
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
434
|
+
this.incrementUnreadCounter();
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
// TCP broadcasts may deliver the same incoming message twice before Stimulus
|
|
438
|
+
// has connected the appended message target. Claiming the id in memory closes
|
|
439
|
+
// that window, while the target check still drops messages rendered earlier.
|
|
440
|
+
claimMessageId(id) {
|
|
441
|
+
const messageTargets = this.messageTargets || [];
|
|
442
|
+
if (this.messageIds.has(id)) return false;
|
|
443
|
+
this.messageIds.add(id);
|
|
444
|
+
return !messageTargets.some(element => element.dataset.id === id);
|
|
445
|
+
}
|
|
446
|
+
captureCatchUpCursor() {
|
|
447
|
+
this.catchUpAfterMessageId = this.lastRenderedMessageId;
|
|
448
|
+
}
|
|
449
|
+
async catchUpMessages() {
|
|
450
|
+
const afterId = this.catchUpAfterMessageId;
|
|
451
|
+
if (!afterId || this.fetchingCatchUpMessages) return;
|
|
452
|
+
this.fetchingCatchUpMessages = true;
|
|
453
|
+
try {
|
|
454
|
+
const response = await this.messagesAPI.catchUp(afterId);
|
|
822
455
|
const {
|
|
823
|
-
|
|
824
|
-
} =
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
456
|
+
messages = []
|
|
457
|
+
} = await response.json();
|
|
458
|
+
messages.forEach(message => this.onMessageReceived(message, {
|
|
459
|
+
scroll: false
|
|
460
|
+
}));
|
|
461
|
+
this.catchUpAfterMessageId = this.lastRenderedMessageId;
|
|
462
|
+
} finally {
|
|
463
|
+
this.fetchingCatchUpMessages = false;
|
|
831
464
|
}
|
|
832
|
-
}
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
465
|
+
}
|
|
466
|
+
get lastRenderedMessageId() {
|
|
467
|
+
const messages = this.persistedMessageElements;
|
|
468
|
+
return messages[messages.length - 1]?.dataset.id || null;
|
|
469
|
+
}
|
|
470
|
+
get persistedMessageElements() {
|
|
471
|
+
return Array.from(this.messagesContainerTarget.querySelectorAll('.hellotext--webchat-message[data-id]'));
|
|
472
|
+
}
|
|
473
|
+
setMessageCreatedAt(element, createdAt) {
|
|
474
|
+
if (createdAt) {
|
|
475
|
+
element.setAttribute('data-created-at', createdAt);
|
|
841
476
|
}
|
|
842
|
-
}
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
this.closePopover();
|
|
850
|
-
(_this$triggerTarget = this.triggerTarget) === null || _this$triggerTarget === void 0 ? void 0 : (_this$triggerTarget$f = _this$triggerTarget.focus) === null || _this$triggerTarget$f === void 0 ? void 0 : _this$triggerTarget$f.call(_this$triggerTarget);
|
|
477
|
+
}
|
|
478
|
+
insertMessageElement(element) {
|
|
479
|
+
const nextElement = this.nextMessageElementFor(element);
|
|
480
|
+
if (nextElement) {
|
|
481
|
+
this.messagesContainerTarget.insertBefore(element, nextElement);
|
|
482
|
+
} else {
|
|
483
|
+
this.messagesContainerTarget.appendChild(element);
|
|
851
484
|
}
|
|
852
|
-
}
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
485
|
+
}
|
|
486
|
+
nextMessageElementFor(element) {
|
|
487
|
+
const createdAt = Date.parse(element.dataset.createdAt);
|
|
488
|
+
if (Number.isNaN(createdAt)) return null;
|
|
489
|
+
return this.persistedMessageElements.find(messageElement => {
|
|
490
|
+
if (messageElement === element) return false;
|
|
491
|
+
const messageCreatedAt = Date.parse(messageElement.dataset.createdAt);
|
|
492
|
+
return !Number.isNaN(messageCreatedAt) && messageCreatedAt > createdAt;
|
|
493
|
+
});
|
|
494
|
+
}
|
|
495
|
+
updateMessageTeaser(teaser) {
|
|
496
|
+
this.messageTeaserValue = teaser;
|
|
497
|
+
if (!this.messageTeaserValue || !this.hasTeaserTarget || !this.hasInboundMessageTeaserTarget || !this.hasInboundMessageTeaserBodyTarget) {
|
|
498
|
+
return;
|
|
499
|
+
}
|
|
500
|
+
this.teaserMessageTargets.forEach(teaserMessage => teaserMessage.classList.add('hidden'));
|
|
501
|
+
this.inboundMessageTeaserBodyTarget.textContent = this.messageTeaserValue;
|
|
502
|
+
this.inboundMessageTeaserTarget.classList.remove('hidden');
|
|
503
|
+
this.teaserTarget.classList.toggle('invisible', this.openValue);
|
|
504
|
+
}
|
|
505
|
+
insertCarouselMessage(message, options = {}) {
|
|
506
|
+
const html = message.html;
|
|
507
|
+
const createdAt = message.created_at || message.createdAt;
|
|
508
|
+
const element = (0, _sanitize_html.sanitizedWebchatComponentFragment)(html).firstElementChild;
|
|
509
|
+
element.classList.add('hellotext--webchat-message');
|
|
510
|
+
element.setAttribute('data-id', message.id);
|
|
511
|
+
element.setAttribute('data-hellotext--webchat-target', 'message');
|
|
512
|
+
this.setMessageCreatedAt(element, createdAt);
|
|
513
|
+
this.localizeMessageTimestamps(element);
|
|
514
|
+
this.clearTypingIndicator();
|
|
515
|
+
this.insertMessageElement(element);
|
|
516
|
+
if (options.scroll !== false) {
|
|
517
|
+
element.scrollIntoView({
|
|
518
|
+
behavior: 'smooth'
|
|
861
519
|
});
|
|
862
520
|
}
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
timestamp.textContent = reason;
|
|
872
|
-
}
|
|
873
|
-
}
|
|
874
|
-
}, {
|
|
875
|
-
key: "localizeMessageTimestamps",
|
|
876
|
-
value: function localizeMessageTimestamps(root = this.element) {
|
|
877
|
-
var _root$matches, _root$querySelectorAl;
|
|
878
|
-
if (!root) return;
|
|
879
|
-
const timestamps = (_root$matches = root.matches) !== null && _root$matches !== void 0 && _root$matches.call(root, 'time[datetime][data-message-timestamp]') ? [root] : Array.from(((_root$querySelectorAl = root.querySelectorAll) === null || _root$querySelectorAl === void 0 ? void 0 : _root$querySelectorAl.call(root, 'time[datetime][data-message-timestamp]')) || []);
|
|
880
|
-
timestamps.forEach(timestamp => this.localizeMessageTimestamp(timestamp));
|
|
881
|
-
}
|
|
882
|
-
}, {
|
|
883
|
-
key: "localizeMessageTimestamp",
|
|
884
|
-
value: function localizeMessageTimestamp(timestamp, value = timestamp === null || timestamp === void 0 ? void 0 : timestamp.getAttribute('datetime')) {
|
|
885
|
-
if (!timestamp || !value) return;
|
|
886
|
-
const date = value instanceof Date ? value : new Date(value);
|
|
887
|
-
if (Number.isNaN(date.getTime())) return;
|
|
888
|
-
timestamp.setAttribute('datetime', date.toISOString());
|
|
889
|
-
timestamp.textContent = this.formatMessageTimestamp(date);
|
|
890
|
-
}
|
|
891
|
-
}, {
|
|
892
|
-
key: "formatMessageTimestamp",
|
|
893
|
-
value: function formatMessageTimestamp(date) {
|
|
894
|
-
return this.constructor.messageTimestampFormatterFor(_core.Locale.toString()).format(date);
|
|
895
|
-
}
|
|
896
|
-
}, {
|
|
897
|
-
key: "messageFailureReason",
|
|
898
|
-
value: async function messageFailureReason(response) {
|
|
899
|
-
const nativeResponse = (response === null || response === void 0 ? void 0 : response.data) || (response === null || response === void 0 ? void 0 : response.response);
|
|
900
|
-
const fallback = (nativeResponse === null || nativeResponse === void 0 ? void 0 : nativeResponse.statusText) || 'Message failed';
|
|
901
|
-
try {
|
|
902
|
-
var _jsonResponse$json;
|
|
903
|
-
const jsonResponse = nativeResponse !== null && nativeResponse !== void 0 && nativeResponse.clone ? nativeResponse.clone() : nativeResponse;
|
|
904
|
-
const payload = await (jsonResponse === null || jsonResponse === void 0 ? void 0 : (_jsonResponse$json = jsonResponse.json) === null || _jsonResponse$json === void 0 ? void 0 : _jsonResponse$json.call(jsonResponse));
|
|
905
|
-
const reason = this.messageFailureReasonFromPayload(payload);
|
|
906
|
-
if (reason) return reason;
|
|
907
|
-
} catch (_) {
|
|
908
|
-
// Fall through to text parsing/fallback. Some stores strip content-type or
|
|
909
|
-
// return non-JSON failures, so the timestamp still needs a useful reason.
|
|
910
|
-
}
|
|
911
|
-
try {
|
|
912
|
-
var _textResponse$text;
|
|
913
|
-
const textResponse = nativeResponse !== null && nativeResponse !== void 0 && nativeResponse.clone ? nativeResponse.clone() : nativeResponse;
|
|
914
|
-
const text = await (textResponse === null || textResponse === void 0 ? void 0 : (_textResponse$text = textResponse.text) === null || _textResponse$text === void 0 ? void 0 : _textResponse$text.call(textResponse));
|
|
915
|
-
return this.messageFailureReasonFromText(text) || fallback;
|
|
916
|
-
} catch (_) {
|
|
917
|
-
return fallback;
|
|
918
|
-
}
|
|
919
|
-
}
|
|
920
|
-
}, {
|
|
921
|
-
key: "messageFailureReasonFromText",
|
|
922
|
-
value: function messageFailureReasonFromText(text) {
|
|
923
|
-
if (typeof text !== 'string') return null;
|
|
924
|
-
const value = text.trim();
|
|
925
|
-
if (!value || value.startsWith('<')) return null;
|
|
926
|
-
try {
|
|
927
|
-
return this.messageFailureReasonFromPayload(JSON.parse(value)) || value;
|
|
928
|
-
} catch (_) {
|
|
929
|
-
return value;
|
|
930
|
-
}
|
|
931
|
-
}
|
|
932
|
-
}, {
|
|
933
|
-
key: "messageFailureReasonFromPayload",
|
|
934
|
-
value: function messageFailureReasonFromPayload(payload) {
|
|
935
|
-
var _payload$error, _payload$errors, _payload$errors2, _payload$errors2$, _payload$errors3, _payload$errors3$;
|
|
936
|
-
if (!payload) return null;
|
|
937
|
-
return [(_payload$error = payload.error) === null || _payload$error === void 0 ? void 0 : _payload$error.message, payload.message, (_payload$errors = payload.errors) === null || _payload$errors === void 0 ? void 0 : _payload$errors.message, (_payload$errors2 = payload.errors) === null || _payload$errors2 === void 0 ? void 0 : (_payload$errors2$ = _payload$errors2[0]) === null || _payload$errors2$ === void 0 ? void 0 : _payload$errors2$.message, (_payload$errors3 = payload.errors) === null || _payload$errors3 === void 0 ? void 0 : (_payload$errors3$ = _payload$errors3[0]) === null || _payload$errors3$ === void 0 ? void 0 : _payload$errors3$.description].find(reason => typeof reason === 'string' && reason.trim().length > 0);
|
|
938
|
-
}
|
|
939
|
-
}, {
|
|
940
|
-
key: "messageAttachmentsContainer",
|
|
941
|
-
value: function messageAttachmentsContainer(element) {
|
|
942
|
-
return element.querySelector('[data-attachments-container], [data-attachment-container]');
|
|
521
|
+
_hellotext.default.eventEmitter.dispatch('webchat:message:received', {
|
|
522
|
+
...message,
|
|
523
|
+
body: element.querySelector('[data-body]')?.innerText || ''
|
|
524
|
+
});
|
|
525
|
+
this.updateMessageTeaser(message.teaser);
|
|
526
|
+
if (this.openValue) {
|
|
527
|
+
this.messagesAPI.markAsSeen(message.id);
|
|
528
|
+
return;
|
|
943
529
|
}
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
530
|
+
this.incrementUnreadCounter();
|
|
531
|
+
}
|
|
532
|
+
resizeInput() {
|
|
533
|
+
const maxHeight = 96;
|
|
534
|
+
|
|
535
|
+
// Temporarily reset height to its natural content size
|
|
536
|
+
this.inputTarget.style.height = 'auto';
|
|
537
|
+
|
|
538
|
+
// Set the height to the scrollHeight, which is the minimum height
|
|
539
|
+
// the element needs to fit its content without a scrollbar.
|
|
540
|
+
const scrollHeight = this.inputTarget.scrollHeight;
|
|
541
|
+
this.inputTarget.style.height = `${Math.min(scrollHeight, maxHeight)}px`;
|
|
542
|
+
}
|
|
543
|
+
async sendQuickReplyMessage({
|
|
544
|
+
detail: {
|
|
545
|
+
id,
|
|
546
|
+
product,
|
|
547
|
+
buttonId,
|
|
548
|
+
body,
|
|
549
|
+
cardElement
|
|
550
|
+
}
|
|
551
|
+
}) {
|
|
552
|
+
this.dismissTeaserForSession?.();
|
|
553
|
+
const formData = new FormData();
|
|
554
|
+
formData.append('message[body]', body);
|
|
555
|
+
if (id) formData.append('message[replied_to]', id);
|
|
556
|
+
if (product) formData.append('message[product]', product);
|
|
557
|
+
if (buttonId) formData.append('message[button]', buttonId);
|
|
558
|
+
formData.append('session', _hellotext.default.session);
|
|
559
|
+
formData.append('locale', _core.Locale.toString());
|
|
560
|
+
this.appendOpeningSequenceMessageIds(formData);
|
|
561
|
+
const element = this.buildMessageElement();
|
|
562
|
+
const attachment = cardElement?.querySelector('img')?.cloneNode(true);
|
|
563
|
+
element.querySelector('[data-body]').innerText = body;
|
|
564
|
+
if (attachment) {
|
|
565
|
+
attachment.removeAttribute('width');
|
|
566
|
+
attachment.removeAttribute('height');
|
|
567
|
+
this.messageAttachmentsContainer(element)?.appendChild(attachment);
|
|
568
|
+
}
|
|
569
|
+
if (this.typingIndicatorVisible && this.hasTypingIndicatorTarget) {
|
|
570
|
+
this.messagesContainerTarget.insertBefore(element, this.typingIndicatorTarget);
|
|
571
|
+
} else {
|
|
572
|
+
this.messagesContainerTarget.appendChild(element);
|
|
573
|
+
}
|
|
574
|
+
element.scrollIntoView({
|
|
575
|
+
behavior: 'smooth'
|
|
576
|
+
});
|
|
577
|
+
this.broadcastChannel.postMessage({
|
|
578
|
+
type: 'message:sent',
|
|
579
|
+
element: element.outerHTML
|
|
580
|
+
});
|
|
581
|
+
const response = await this.messagesAPI.create(formData);
|
|
582
|
+
if (response.failed) {
|
|
583
|
+
// Clear the optimistic typing indicator on failure
|
|
584
|
+
clearTimeout(this.optimisticTypingTimeout);
|
|
585
|
+
return this.markMessageFailedFromResponse(response, element);
|
|
586
|
+
}
|
|
587
|
+
const data = await response.json();
|
|
588
|
+
this.dispatch('set:id', {
|
|
589
|
+
target: element,
|
|
590
|
+
detail: data.id
|
|
591
|
+
});
|
|
592
|
+
this.localizeMessageTimestamp(element.querySelector('[data-message-timestamp]'), data.created_at || data.createdAt);
|
|
593
|
+
this.clearRevealedOpeningSequenceMessageIds();
|
|
594
|
+
const message = {
|
|
595
|
+
id: data.id,
|
|
596
|
+
body: body,
|
|
597
|
+
attachments: attachment ? [attachment.src] : [],
|
|
598
|
+
replied_to: id,
|
|
599
|
+
product: product,
|
|
600
|
+
button: buttonId,
|
|
601
|
+
type: 'quick_reply'
|
|
602
|
+
};
|
|
603
|
+
_hellotext.default.eventEmitter.dispatch('webchat:message:sent', message);
|
|
604
|
+
}
|
|
605
|
+
async sendTeaserQuickReply(event) {
|
|
606
|
+
event.preventDefault();
|
|
607
|
+
event.stopPropagation();
|
|
608
|
+
const button = event.currentTarget;
|
|
609
|
+
const value = (button.dataset.value || '').trim();
|
|
610
|
+
const label = [button.dataset.text, button.textContent].map(text => (text || '').trim()).find(text => text.length > 0);
|
|
611
|
+
const text = value || label;
|
|
612
|
+
if (!text) return;
|
|
613
|
+
this.dismissTeaserForSession?.();
|
|
614
|
+
this.show();
|
|
615
|
+
const buttonType = (button.dataset.type || '').trim() || 'quick_reply';
|
|
616
|
+
const formData = new FormData();
|
|
617
|
+
formData.append('message[body]', text);
|
|
618
|
+
formData.append('session', _hellotext.default.session);
|
|
619
|
+
formData.append('locale', _core.Locale.toString());
|
|
620
|
+
this.appendOpeningSequenceMessageIds(formData);
|
|
621
|
+
const element = this.buildMessageElement();
|
|
622
|
+
element.querySelector('[data-body]').innerText = text;
|
|
623
|
+
if (this.typingIndicatorVisible && this.hasTypingIndicatorTarget) {
|
|
624
|
+
this.messagesContainerTarget.insertBefore(element, this.typingIndicatorTarget);
|
|
625
|
+
} else {
|
|
626
|
+
this.messagesContainerTarget.appendChild(element);
|
|
627
|
+
}
|
|
628
|
+
element.scrollIntoView({
|
|
629
|
+
behavior: 'smooth'
|
|
630
|
+
});
|
|
631
|
+
this.broadcastChannel.postMessage({
|
|
632
|
+
type: 'message:sent',
|
|
633
|
+
element: element.outerHTML
|
|
634
|
+
});
|
|
635
|
+
if (!this.typingIndicatorVisible) {
|
|
636
|
+
clearTimeout(this.optimisticTypingTimeout);
|
|
637
|
+
this.optimisticTypingTimeout = setTimeout(() => {
|
|
638
|
+
this.showOptimisticTypingIndicator();
|
|
639
|
+
}, this.optimisticTypingIndicatorWaitValue);
|
|
950
640
|
}
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
this.
|
|
641
|
+
const response = await this.messagesAPI.create(formData);
|
|
642
|
+
if (response.failed) {
|
|
643
|
+
clearTimeout(this.optimisticTypingTimeout);
|
|
644
|
+
return this.markMessageFailedFromResponse(response, element);
|
|
645
|
+
}
|
|
646
|
+
const data = await response.json();
|
|
647
|
+
element.setAttribute('data-id', data.id);
|
|
648
|
+
this.localizeMessageTimestamp(element.querySelector('[data-message-timestamp]'), data.created_at || data.createdAt);
|
|
649
|
+
this.clearRevealedOpeningSequenceMessageIds();
|
|
650
|
+
_hellotext.default.eventEmitter.dispatch('webchat:message:sent', {
|
|
651
|
+
id: data.id,
|
|
652
|
+
body: text,
|
|
653
|
+
attachments: [],
|
|
654
|
+
type: 'quick_reply',
|
|
655
|
+
teaser: {
|
|
656
|
+
text: label || text,
|
|
657
|
+
value: value || text,
|
|
658
|
+
type: buttonType
|
|
659
|
+
}
|
|
660
|
+
});
|
|
661
|
+
if (data.conversation && data.conversation !== this.conversationIdValue) {
|
|
662
|
+
this.conversationIdValue = data.conversation;
|
|
663
|
+
this.webChatChannel.updateSubscriptionWith(this.conversationIdValue);
|
|
664
|
+
}
|
|
665
|
+
if (this.typingIndicatorVisible) {
|
|
666
|
+
this.resetTypingIndicatorTimer();
|
|
955
667
|
}
|
|
956
|
-
}
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
this.
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
668
|
+
}
|
|
669
|
+
async sendMessage(e) {
|
|
670
|
+
const message = {
|
|
671
|
+
body: this.inputTarget.value,
|
|
672
|
+
attachments: this.files
|
|
673
|
+
};
|
|
674
|
+
if (this.inputTarget.value.trim().length === 0 && this.files.length === 0) {
|
|
675
|
+
if (e && e.target) {
|
|
676
|
+
e.preventDefault();
|
|
677
|
+
}
|
|
678
|
+
return;
|
|
679
|
+
}
|
|
680
|
+
this.dismissTeaserForSession?.();
|
|
681
|
+
const formData = new FormData();
|
|
682
|
+
if (this.inputTarget.value.trim().length > 0) {
|
|
683
|
+
formData.append('message[body]', this.inputTarget.value);
|
|
684
|
+
} else {
|
|
685
|
+
delete message.body;
|
|
686
|
+
}
|
|
687
|
+
this.files.forEach(file => {
|
|
688
|
+
formData.append('message[attachments][]', file);
|
|
689
|
+
});
|
|
690
|
+
formData.append('session', _hellotext.default.session);
|
|
691
|
+
formData.append('locale', _core.Locale.toString());
|
|
692
|
+
this.appendOpeningSequenceMessageIds(formData);
|
|
693
|
+
const element = this.buildMessageElement();
|
|
694
|
+
if (this.inputTarget.value.trim().length > 0) {
|
|
695
|
+
element.querySelector('[data-body]').innerText = this.inputTarget.value;
|
|
696
|
+
} else {
|
|
697
|
+
element.querySelector('[data-message-bubble]').remove();
|
|
698
|
+
}
|
|
699
|
+
const attachments = this.attachmentContainerTarget.querySelectorAll('img');
|
|
700
|
+
if (attachments.length > 0) {
|
|
701
|
+
attachments.forEach(attachment => {
|
|
702
|
+
this.messageAttachmentsContainer(element)?.appendChild(attachment.cloneNode(true));
|
|
969
703
|
});
|
|
970
|
-
if (fileMaxSizeTooMuch) {
|
|
971
|
-
const type = fileMaxSizeTooMuch.type.split('/')[0];
|
|
972
|
-
const mediaType = ['image', 'audio', 'video'].includes(type) ? type : 'document';
|
|
973
|
-
this.errorMessageContainerTarget.innerText = this.fileSizeErrorMessageValue.replace('%{limit}', this.byteToMegabyte(this.mediaValue[mediaType].max_size));
|
|
974
|
-
return this.errorMessageContainerTarget.style.display = 'block';
|
|
975
|
-
}
|
|
976
|
-
this.files = [...this.files, ...newFiles];
|
|
977
|
-
this.errorMessageContainerTarget.innerText = '';
|
|
978
|
-
newFiles.forEach(file => this.createAttachmentElement(file));
|
|
979
|
-
this.focusComposeInput();
|
|
980
|
-
}
|
|
981
|
-
}, {
|
|
982
|
-
key: "createAttachmentElement",
|
|
983
|
-
value: function createAttachmentElement(file) {
|
|
984
|
-
const element = this.attachmentElement();
|
|
985
|
-
this.attachmentContainerTarget.style.display = '';
|
|
986
|
-
element.setAttribute('data-name', file.name);
|
|
987
|
-
if (file.type.startsWith('image/')) {
|
|
988
|
-
const thumbnail = this.attachmentImageTarget.cloneNode(true);
|
|
989
|
-
thumbnail.src = URL.createObjectURL(file);
|
|
990
|
-
thumbnail.style.display = 'block';
|
|
991
|
-
element.appendChild(thumbnail);
|
|
992
|
-
this.attachmentContainerTarget.appendChild(element);
|
|
993
|
-
this.attachmentContainerTarget.style.display = 'flex';
|
|
994
|
-
} else {
|
|
995
|
-
const main = element.querySelector('main');
|
|
996
|
-
main.style.height = '5rem';
|
|
997
|
-
main.style.borderRadius = '0.375rem';
|
|
998
|
-
main.style.backgroundColor = '#e5e7eb';
|
|
999
|
-
main.style.padding = '0.25rem';
|
|
1000
|
-
element.querySelector('p[data-attachment-name]').innerText = file.name;
|
|
1001
|
-
this.attachmentContainerTarget.appendChild(element);
|
|
1002
|
-
this.attachmentContainerTarget.style.display = 'flex';
|
|
1003
|
-
}
|
|
1004
704
|
}
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
}
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
value: function onEmojiSelect({
|
|
1036
|
-
detail: emoji
|
|
1037
|
-
}) {
|
|
1038
|
-
const value = this.inputTarget.value;
|
|
1039
|
-
const start = this.inputTarget.selectionStart;
|
|
1040
|
-
const end = this.inputTarget.selectionEnd;
|
|
1041
|
-
this.inputTarget.value = value.slice(0, start) + emoji + value.slice(end);
|
|
1042
|
-
this.inputTarget.selectionStart = this.inputTarget.selectionEnd = start + emoji.length;
|
|
1043
|
-
this.focusComposeInput();
|
|
1044
|
-
}
|
|
1045
|
-
}, {
|
|
1046
|
-
key: "focusComposeInput",
|
|
1047
|
-
value: function focusComposeInput({
|
|
1048
|
-
moveCursorToEnd = false
|
|
1049
|
-
} = {}) {
|
|
1050
|
-
if (!this.shouldAutofocusCompose) return false;
|
|
1051
|
-
if (this.hasInputTarget === false) return false;
|
|
1052
|
-
if (this.hasInputTarget === undefined && !this.inputTarget) return false;
|
|
1053
|
-
this.inputTarget.focus();
|
|
1054
|
-
if (moveCursorToEnd && typeof this.inputTarget.selectionStart === 'number') {
|
|
1055
|
-
const position = this.inputTarget.value.length;
|
|
1056
|
-
this.inputTarget.setSelectionRange(position, position);
|
|
1057
|
-
}
|
|
1058
|
-
return true;
|
|
705
|
+
|
|
706
|
+
// Insert message before typing indicator if one exists
|
|
707
|
+
if (this.typingIndicatorVisible && this.hasTypingIndicatorTarget) {
|
|
708
|
+
this.messagesContainerTarget.insertBefore(element, this.typingIndicatorTarget);
|
|
709
|
+
} else {
|
|
710
|
+
this.messagesContainerTarget.appendChild(element);
|
|
711
|
+
}
|
|
712
|
+
element.scrollIntoView({
|
|
713
|
+
behavior: 'smooth'
|
|
714
|
+
});
|
|
715
|
+
this.broadcastChannel.postMessage({
|
|
716
|
+
type: 'message:sent',
|
|
717
|
+
element: element.outerHTML
|
|
718
|
+
});
|
|
719
|
+
this.inputTarget.value = '';
|
|
720
|
+
this.resizeInput();
|
|
721
|
+
this.files = [];
|
|
722
|
+
this.attachmentInputTarget.value = '';
|
|
723
|
+
this.attachmentContainerTarget.innerHTML = '';
|
|
724
|
+
this.attachmentContainerTarget.style.display = 'none';
|
|
725
|
+
this.errorMessageContainerTarget.style.display = 'none';
|
|
726
|
+
this.focusComposeInput();
|
|
727
|
+
|
|
728
|
+
// Set up optimistic typing indicator BEFORE making the API call
|
|
729
|
+
// This prevents race conditions with server responses
|
|
730
|
+
if (!this.typingIndicatorVisible) {
|
|
731
|
+
clearTimeout(this.optimisticTypingTimeout);
|
|
732
|
+
this.optimisticTypingTimeout = setTimeout(() => {
|
|
733
|
+
this.showOptimisticTypingIndicator();
|
|
734
|
+
}, this.optimisticTypingIndicatorWaitValue);
|
|
1059
735
|
}
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
736
|
+
const response = await this.messagesAPI.create(formData);
|
|
737
|
+
if (response.failed) {
|
|
738
|
+
// Clear the optimistic typing indicator on failure
|
|
739
|
+
clearTimeout(this.optimisticTypingTimeout);
|
|
740
|
+
return this.markMessageFailedFromResponse(response, element);
|
|
741
|
+
}
|
|
742
|
+
const data = await response.json();
|
|
743
|
+
element.setAttribute('data-id', data.id);
|
|
744
|
+
message.id = data.id;
|
|
745
|
+
this.localizeMessageTimestamp(element.querySelector('[data-message-timestamp]'), data.created_at || data.createdAt);
|
|
746
|
+
this.clearRevealedOpeningSequenceMessageIds();
|
|
747
|
+
_hellotext.default.eventEmitter.dispatch('webchat:message:sent', message);
|
|
748
|
+
if (data.conversation !== this.conversationIdValue) {
|
|
749
|
+
this.conversationIdValue = data.conversation;
|
|
750
|
+
this.webChatChannel.updateSubscriptionWith(this.conversationIdValue);
|
|
1064
751
|
}
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
padding: this.paddingValue
|
|
1070
|
-
}), (0, _dom.flip)()];
|
|
752
|
+
|
|
753
|
+
// If there's already a typing indicator visible, reset its timer
|
|
754
|
+
if (this.typingIndicatorVisible) {
|
|
755
|
+
this.resetTypingIndicatorTimer();
|
|
1071
756
|
}
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
757
|
+
this.attachmentContainerTarget.style.display = '';
|
|
758
|
+
}
|
|
759
|
+
buildMessageElement() {
|
|
760
|
+
const element = this.messageTemplateTarget.cloneNode(true);
|
|
761
|
+
element.id = `hellotext--webchat--${this.idValue}--message--${Date.now()}`;
|
|
762
|
+
element.classList.add('received');
|
|
763
|
+
element.style.removeProperty('display');
|
|
764
|
+
element.setAttribute('data-controller', 'hellotext--message');
|
|
765
|
+
element.setAttribute('data-hellotext--webchat-target', 'message');
|
|
766
|
+
this.localizeMessageTimestamp(element.querySelector('[data-message-timestamp]'), new Date());
|
|
767
|
+
return element;
|
|
768
|
+
}
|
|
769
|
+
focusCompose(event) {
|
|
770
|
+
const {
|
|
771
|
+
target
|
|
772
|
+
} = event;
|
|
773
|
+
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(', ');
|
|
774
|
+
if (!this.hasInputTarget || target.closest(ignoredSelector)) return;
|
|
775
|
+
if (!this.focusComposeInput({
|
|
776
|
+
moveCursorToEnd: true
|
|
777
|
+
})) return;
|
|
778
|
+
event.preventDefault();
|
|
779
|
+
}
|
|
780
|
+
closePopoverFromHeader(event) {
|
|
781
|
+
const {
|
|
782
|
+
target
|
|
783
|
+
} = event;
|
|
784
|
+
if (target.closest('.hellotext--webchat-header-channel-button, .hellotext--webchat-close-button')) return;
|
|
785
|
+
event.preventDefault();
|
|
786
|
+
this.closePopover();
|
|
787
|
+
}
|
|
788
|
+
closePopoverOnEscape(event) {
|
|
789
|
+
if (event.key !== 'Escape' || !this.openValue) return;
|
|
790
|
+
event.preventDefault();
|
|
791
|
+
event.stopPropagation();
|
|
792
|
+
this.closePopover();
|
|
793
|
+
this.triggerTarget?.focus?.();
|
|
794
|
+
}
|
|
795
|
+
async markMessageFailedFromResponse(response, element) {
|
|
796
|
+
const reason = await this.messageFailureReason(response);
|
|
797
|
+
this.markMessageFailed(element, reason);
|
|
798
|
+
this.broadcastChannel.postMessage({
|
|
799
|
+
type: 'message:failed',
|
|
800
|
+
id: element.id,
|
|
801
|
+
reason
|
|
802
|
+
});
|
|
803
|
+
}
|
|
804
|
+
markMessageFailed(element, reason) {
|
|
805
|
+
if (!element) return;
|
|
806
|
+
element.classList.add('failed');
|
|
807
|
+
if (!reason) return;
|
|
808
|
+
const timestamp = element.querySelector('[data-message-timestamp]');
|
|
809
|
+
if (timestamp) {
|
|
810
|
+
timestamp.textContent = reason;
|
|
1076
811
|
}
|
|
1077
|
-
}
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
812
|
+
}
|
|
813
|
+
localizeMessageTimestamps(root = this.element) {
|
|
814
|
+
if (!root) return;
|
|
815
|
+
const timestamps = root.matches?.('time[datetime][data-message-timestamp]') ? [root] : Array.from(root.querySelectorAll?.('time[datetime][data-message-timestamp]') || []);
|
|
816
|
+
timestamps.forEach(timestamp => this.localizeMessageTimestamp(timestamp));
|
|
817
|
+
}
|
|
818
|
+
localizeMessageTimestamp(timestamp, value = timestamp?.getAttribute('datetime')) {
|
|
819
|
+
if (!timestamp || !value) return;
|
|
820
|
+
const date = value instanceof Date ? value : new Date(value);
|
|
821
|
+
if (Number.isNaN(date.getTime())) return;
|
|
822
|
+
timestamp.setAttribute('datetime', date.toISOString());
|
|
823
|
+
timestamp.textContent = this.formatMessageTimestamp(date);
|
|
824
|
+
}
|
|
825
|
+
formatMessageTimestamp(date) {
|
|
826
|
+
return this.constructor.messageTimestampFormatterFor(_core.Locale.toString()).format(date);
|
|
827
|
+
}
|
|
828
|
+
static messageTimestampFormatterFor(locale) {
|
|
829
|
+
const key = locale || 'default';
|
|
830
|
+
if (!this.messageTimestampFormatters[key]) {
|
|
831
|
+
this.messageTimestampFormatters[key] = this.buildMessageTimestampFormatter(locale);
|
|
1081
832
|
}
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
const isMobileUserAgent = MOBILE_USER_AGENT_PATTERN.test(userAgent);
|
|
1090
|
-
const isUserAgentDataMobile = ((_navigator$userAgentD = navigator.userAgentData) === null || _navigator$userAgentD === void 0 ? void 0 : _navigator$userAgentD.mobile) === true;
|
|
1091
|
-
return isMobileUserAgent || isIPadOS || isUserAgentDataMobile || this.hasTouchOnlyPointer;
|
|
833
|
+
return this.messageTimestampFormatters[key];
|
|
834
|
+
}
|
|
835
|
+
static buildMessageTimestampFormatter(locale) {
|
|
836
|
+
try {
|
|
837
|
+
return new Intl.DateTimeFormat(locale || undefined, MESSAGE_TIMESTAMP_FORMAT_OPTIONS);
|
|
838
|
+
} catch (_) {
|
|
839
|
+
return new Intl.DateTimeFormat(undefined, MESSAGE_TIMESTAMP_FORMAT_OPTIONS);
|
|
1092
840
|
}
|
|
1093
|
-
}
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
841
|
+
}
|
|
842
|
+
async messageFailureReason(response) {
|
|
843
|
+
const nativeResponse = response?.data || response?.response;
|
|
844
|
+
const fallback = nativeResponse?.statusText || 'Message failed';
|
|
845
|
+
try {
|
|
846
|
+
const jsonResponse = nativeResponse?.clone ? nativeResponse.clone() : nativeResponse;
|
|
847
|
+
const payload = await jsonResponse?.json?.();
|
|
848
|
+
const reason = this.messageFailureReasonFromPayload(payload);
|
|
849
|
+
if (reason) return reason;
|
|
850
|
+
} catch (_) {
|
|
851
|
+
// Fall through to text parsing/fallback. Some stores strip content-type or
|
|
852
|
+
// return non-JSON failures, so the timestamp still needs a useful reason.
|
|
853
|
+
}
|
|
854
|
+
try {
|
|
855
|
+
const textResponse = nativeResponse?.clone ? nativeResponse.clone() : nativeResponse;
|
|
856
|
+
const text = await textResponse?.text?.();
|
|
857
|
+
return this.messageFailureReasonFromText(text) || fallback;
|
|
858
|
+
} catch (_) {
|
|
859
|
+
return fallback;
|
|
1098
860
|
}
|
|
1099
|
-
}
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
861
|
+
}
|
|
862
|
+
messageFailureReasonFromText(text) {
|
|
863
|
+
if (typeof text !== 'string') return null;
|
|
864
|
+
const value = text.trim();
|
|
865
|
+
if (!value || value.startsWith('<')) return null;
|
|
866
|
+
try {
|
|
867
|
+
return this.messageFailureReasonFromPayload(JSON.parse(value)) || value;
|
|
868
|
+
} catch (_) {
|
|
869
|
+
return value;
|
|
1104
870
|
}
|
|
1105
|
-
}
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
871
|
+
}
|
|
872
|
+
messageFailureReasonFromPayload(payload) {
|
|
873
|
+
if (!payload) return null;
|
|
874
|
+
return [payload.error?.message, payload.message, payload.errors?.message, payload.errors?.[0]?.message, payload.errors?.[0]?.description].find(reason => typeof reason === 'string' && reason.trim().length > 0);
|
|
875
|
+
}
|
|
876
|
+
messageAttachmentsContainer(element) {
|
|
877
|
+
return element.querySelector('[data-attachments-container], [data-attachment-container]');
|
|
878
|
+
}
|
|
879
|
+
incrementUnreadCounter() {
|
|
880
|
+
this.unreadCounterTarget.style.display = 'flex';
|
|
881
|
+
const unreadCount = (parseInt(this.unreadCounterTarget.innerText) || 0) + 1;
|
|
882
|
+
this.unreadCounterTarget.innerText = Math.min(unreadCount, 9);
|
|
883
|
+
}
|
|
884
|
+
openAttachment() {
|
|
885
|
+
this.attachmentInputTarget.click();
|
|
886
|
+
}
|
|
887
|
+
onFileInputChange() {
|
|
888
|
+
this.errorMessageContainerTarget.style.display = 'none';
|
|
889
|
+
const newFiles = Array.from(this.attachmentInputTarget.files);
|
|
890
|
+
this.attachmentInputTarget.value = '';
|
|
891
|
+
const fileMaxSizeTooMuch = newFiles.find(file => {
|
|
892
|
+
const type = file.type.split('/')[0];
|
|
893
|
+
if (['image', 'video', 'audio'].includes(type)) {
|
|
894
|
+
return this.mediaValue[type].max_size < file.size;
|
|
895
|
+
} else {
|
|
896
|
+
return this.mediaValue.document.max_size < file.size;
|
|
897
|
+
}
|
|
898
|
+
});
|
|
899
|
+
if (fileMaxSizeTooMuch) {
|
|
900
|
+
const type = fileMaxSizeTooMuch.type.split('/')[0];
|
|
901
|
+
const mediaType = ['image', 'audio', 'video'].includes(type) ? type : 'document';
|
|
902
|
+
this.errorMessageContainerTarget.innerText = this.fileSizeErrorMessageValue.replace('%{limit}', this.byteToMegabyte(this.mediaValue[mediaType].max_size));
|
|
903
|
+
return this.errorMessageContainerTarget.style.display = 'block';
|
|
904
|
+
}
|
|
905
|
+
this.files = [...this.files, ...newFiles];
|
|
906
|
+
this.errorMessageContainerTarget.innerText = '';
|
|
907
|
+
newFiles.forEach(file => this.createAttachmentElement(file));
|
|
908
|
+
this.focusComposeInput();
|
|
909
|
+
}
|
|
910
|
+
createAttachmentElement(file) {
|
|
911
|
+
const element = this.attachmentElement();
|
|
912
|
+
this.attachmentContainerTarget.style.display = '';
|
|
913
|
+
element.setAttribute('data-name', file.name);
|
|
914
|
+
if (file.type.startsWith('image/')) {
|
|
915
|
+
const thumbnail = this.attachmentImageTarget.cloneNode(true);
|
|
916
|
+
thumbnail.src = URL.createObjectURL(file);
|
|
917
|
+
thumbnail.style.display = 'block';
|
|
918
|
+
element.appendChild(thumbnail);
|
|
919
|
+
this.attachmentContainerTarget.appendChild(element);
|
|
920
|
+
this.attachmentContainerTarget.style.display = 'flex';
|
|
921
|
+
} else {
|
|
922
|
+
const main = element.querySelector('main');
|
|
923
|
+
main.style.height = '5rem';
|
|
924
|
+
main.style.borderRadius = '0.375rem';
|
|
925
|
+
main.style.backgroundColor = '#e5e7eb';
|
|
926
|
+
main.style.padding = '0.25rem';
|
|
927
|
+
element.querySelector('p[data-attachment-name]').innerText = file.name;
|
|
928
|
+
this.attachmentContainerTarget.appendChild(element);
|
|
929
|
+
this.attachmentContainerTarget.style.display = 'flex';
|
|
1113
930
|
}
|
|
1114
|
-
}
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
931
|
+
}
|
|
932
|
+
removeAttachment({
|
|
933
|
+
currentTarget
|
|
934
|
+
}) {
|
|
935
|
+
const attachment = currentTarget.closest("[data-hellotext--webchat-target='attachment']");
|
|
936
|
+
this.files = this.files.filter(file => file.name !== attachment.dataset.name);
|
|
937
|
+
this.attachmentInputTarget.value = '';
|
|
938
|
+
attachment.remove();
|
|
939
|
+
this.focusComposeInput();
|
|
940
|
+
}
|
|
941
|
+
attachmentTargetDisconnected() {
|
|
942
|
+
if (this.attachmentTargets.length === 0) {
|
|
943
|
+
this.attachmentContainerTarget.innerHTML = '';
|
|
944
|
+
this.attachmentContainerTarget.style.display = 'none';
|
|
1122
945
|
}
|
|
1123
|
-
}
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
}
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
}
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
946
|
+
}
|
|
947
|
+
attachmentElement() {
|
|
948
|
+
const element = this.attachmentTemplateTarget.cloneNode(true);
|
|
949
|
+
element.removeAttribute('hidden');
|
|
950
|
+
element.style.display = 'flex';
|
|
951
|
+
element.setAttribute('data-hellotext--webchat-target', 'attachment');
|
|
952
|
+
return element;
|
|
953
|
+
}
|
|
954
|
+
onEmojiSelect({
|
|
955
|
+
detail: emoji
|
|
956
|
+
}) {
|
|
957
|
+
const value = this.inputTarget.value;
|
|
958
|
+
const start = this.inputTarget.selectionStart;
|
|
959
|
+
const end = this.inputTarget.selectionEnd;
|
|
960
|
+
this.inputTarget.value = value.slice(0, start) + emoji + value.slice(end);
|
|
961
|
+
this.inputTarget.selectionStart = this.inputTarget.selectionEnd = start + emoji.length;
|
|
962
|
+
this.focusComposeInput();
|
|
963
|
+
}
|
|
964
|
+
focusComposeInput({
|
|
965
|
+
moveCursorToEnd = false
|
|
966
|
+
} = {}) {
|
|
967
|
+
if (!this.shouldAutofocusCompose) return false;
|
|
968
|
+
if (this.hasInputTarget === false) return false;
|
|
969
|
+
if (this.hasInputTarget === undefined && !this.inputTarget) return false;
|
|
970
|
+
this.inputTarget.focus();
|
|
971
|
+
if (moveCursorToEnd && typeof this.inputTarget.selectionStart === 'number') {
|
|
972
|
+
const position = this.inputTarget.value.length;
|
|
973
|
+
this.inputTarget.setSelectionRange(position, position);
|
|
974
|
+
}
|
|
975
|
+
return true;
|
|
976
|
+
}
|
|
977
|
+
byteToMegabyte(bytes) {
|
|
978
|
+
return Math.ceil(bytes / 1024 / 1024);
|
|
979
|
+
}
|
|
980
|
+
get middlewares() {
|
|
981
|
+
return [(0, _dom.offset)(this.offsetValue), (0, _dom.shift)({
|
|
982
|
+
padding: this.paddingValue
|
|
983
|
+
}), (0, _dom.flip)()];
|
|
984
|
+
}
|
|
985
|
+
get shouldOpenOnMount() {
|
|
986
|
+
return localStorage.getItem(`hellotext--webchat--${this.idValue}`) === 'opened' && !this.onMobile;
|
|
987
|
+
}
|
|
988
|
+
get shouldAutofocusCompose() {
|
|
989
|
+
return !this.usesVirtualKeyboard;
|
|
990
|
+
}
|
|
991
|
+
get usesVirtualKeyboard() {
|
|
992
|
+
if (typeof navigator === 'undefined') return false;
|
|
993
|
+
const userAgent = navigator.userAgent || '';
|
|
994
|
+
const isIPadOS = navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1;
|
|
995
|
+
const isMobileUserAgent = MOBILE_USER_AGENT_PATTERN.test(userAgent);
|
|
996
|
+
const isUserAgentDataMobile = navigator.userAgentData?.mobile === true;
|
|
997
|
+
return isMobileUserAgent || isIPadOS || isUserAgentDataMobile || this.hasTouchOnlyPointer;
|
|
998
|
+
}
|
|
999
|
+
get hasTouchOnlyPointer() {
|
|
1000
|
+
if (typeof window === 'undefined' || typeof window.matchMedia !== 'function') return false;
|
|
1001
|
+
return window.matchMedia('(pointer: coarse)').matches && window.matchMedia('(hover: none)').matches;
|
|
1002
|
+
}
|
|
1003
|
+
get onMobile() {
|
|
1004
|
+
if (typeof window === 'undefined' || typeof window.matchMedia !== 'function') return false;
|
|
1005
|
+
return window.matchMedia(`(max-width: ${this.fullScreenThresholdValue}px)`).matches;
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
1008
|
+
exports.default = _default;
|