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