@hellotext/hellotext 2.1.2 → 2.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/dist/hellotext.js +1 -1
  2. package/lib/api/forms.cjs +2 -1
  3. package/lib/api/forms.js +2 -1
  4. package/lib/api/webchat/messages.cjs +4 -3
  5. package/lib/api/webchat/messages.js +4 -2
  6. package/lib/api/webchats.cjs +2 -0
  7. package/lib/api/webchats.js +3 -1
  8. package/lib/builders/logo_builder.cjs +1 -1
  9. package/lib/builders/logo_builder.js +1 -1
  10. package/lib/channels/application_channel.cjs +9 -6
  11. package/lib/channels/application_channel.js +9 -6
  12. package/lib/channels/webchat_channel.cjs +42 -17
  13. package/lib/channels/webchat_channel.js +42 -17
  14. package/lib/controllers/webchat/emoji_picker_controller.cjs +6 -6
  15. package/lib/controllers/webchat/emoji_picker_controller.js +5 -14
  16. package/lib/controllers/webchat_controller.cjs +174 -47
  17. package/lib/controllers/webchat_controller.js +175 -48
  18. package/lib/core/configuration/locale.cjs +87 -0
  19. package/lib/core/configuration/locale.js +81 -0
  20. package/lib/core/configuration.cjs +11 -0
  21. package/lib/core/configuration.js +11 -0
  22. package/lib/core/index.cjs +15 -1
  23. package/lib/core/index.js +4 -2
  24. package/lib/locales/en.cjs +1 -1
  25. package/lib/locales/en.js +1 -1
  26. package/lib/locales/es.cjs +1 -1
  27. package/lib/locales/es.js +1 -1
  28. package/lib/models/business.cjs +11 -0
  29. package/lib/models/business.js +11 -0
  30. package/lib/models/form_collection.cjs +1 -0
  31. package/lib/models/form_collection.js +2 -1
  32. package/lib/models/index.cjs +4 -4
  33. package/lib/models/index.js +4 -4
  34. package/lib/models/query.cjs +1 -2
  35. package/lib/models/query.js +1 -2
  36. package/lib/models/session.cjs +1 -1
  37. package/lib/models/session.js +1 -1
  38. package/package.json +2 -1
  39. package/src/api/forms.js +3 -1
  40. package/src/api/webchat/messages.js +8 -6
  41. package/src/api/webchats.js +3 -1
  42. package/src/builders/logo_builder.js +1 -1
  43. package/src/channels/application_channel.js +12 -9
  44. package/src/channels/webchat_channel.js +33 -19
  45. package/src/controllers/webchat/emoji_picker_controller.js +9 -15
  46. package/src/controllers/webchat_controller.js +164 -43
  47. package/src/core/configuration/locale.js +50 -0
  48. package/src/core/configuration.js +11 -0
  49. package/src/core/index.js +3 -1
  50. package/src/locales/en.js +4 -4
  51. package/src/locales/es.js +4 -4
  52. package/src/models/business.js +12 -0
  53. package/src/models/form_collection.js +2 -1
  54. package/src/models/index.js +3 -3
  55. package/src/models/query.js +1 -3
  56. package/src/models/session.js +1 -1
@@ -9,6 +9,7 @@ var _stimulus = require("@hotwired/stimulus");
9
9
  var _messages = _interopRequireDefault(require("../api/webchat/messages"));
10
10
  var _webchat_channel = _interopRequireDefault(require("../channels/webchat_channel"));
11
11
  var _hellotext = _interopRequireDefault(require("../hellotext"));
12
+ var _core = require("../core");
12
13
  var _webchat = require("../core/configuration/webchat");
13
14
  var _logo_builder = require("../builders/logo_builder");
14
15
  var _usePopover = require("./mixins/usePopover");
@@ -41,9 +42,8 @@ let _default = /*#__PURE__*/function (_Controller) {
41
42
  this.webChatChannel = new _webchat_channel.default(this.idValue, _hellotext.default.session, this.conversationIdValue);
42
43
  this.files = [];
43
44
  this.onMessageReceived = this.onMessageReceived.bind(this);
44
- this.onConversationAssignment = this.onConversationAssignment.bind(this);
45
- this.onAgentOnline = this.onAgentOnline.bind(this);
46
45
  this.onMessageReaction = this.onMessageReaction.bind(this);
46
+ this.onTypingStart = this.onTypingStart.bind(this);
47
47
  this.onScroll = this.onScroll.bind(this);
48
48
  this.onOutboundMessageSent = this.onOutboundMessageSent.bind(this);
49
49
  this.broadcastChannel = new BroadcastChannel(`hellotext--webchat--${this.idValue}`);
@@ -60,14 +60,13 @@ let _default = /*#__PURE__*/function (_Controller) {
60
60
  popover: this.popoverTarget
61
61
  });
62
62
  this.webChatChannel.onMessage(this.onMessageReceived);
63
- this.webChatChannel.onConversationAssignment(this.onConversationAssignment);
64
- this.webChatChannel.onAgentOnline(this.onAgentOnline);
63
+ this.webChatChannel.onTypingStart(this.onTypingStart);
65
64
  this.webChatChannel.onReaction(this.onMessageReaction);
66
65
  this.messagesContainerTarget.addEventListener('scroll', this.onScroll);
67
66
  if (!_hellotext.default.business.features.white_label) {
68
67
  this.toolbarTarget.appendChild(_logo_builder.LogoBuilder.build());
69
68
  }
70
- if (localStorage.getItem(`hellotext--webchat--${this.idValue}`) === 'opened') {
69
+ if (this.shouldOpenOnMount) {
71
70
  this.openValue = true;
72
71
  }
73
72
  _hellotext.default.eventEmitter.dispatch('webchat:mounted');
@@ -79,10 +78,85 @@ let _default = /*#__PURE__*/function (_Controller) {
79
78
  value: function disconnect() {
80
79
  this.broadcastChannel.removeEventListener('message', this.onOutboundMessageSent);
81
80
  this.messagesContainerTarget.removeEventListener('scroll', this.onScroll);
81
+
82
+ // Clean up typing indicator timeouts
83
+ this.clearTypingIndicator();
82
84
  this.broadcastChannel.close();
83
85
  this.floatingUICleanup();
84
86
  _get(_getPrototypeOf(_default.prototype), "disconnect", this).call(this);
85
87
  }
88
+ }, {
89
+ key: "onTypingStart",
90
+ value: function onTypingStart() {
91
+ if (this.typingIndicatorVisible) {
92
+ return this.resetTypingIndicatorTimer();
93
+ }
94
+ this.showTypingIndicator();
95
+ }
96
+ }, {
97
+ key: "showOptimisticTypingIndicator",
98
+ value: function showOptimisticTypingIndicator() {
99
+ if (this.typingIndicatorVisible) return;
100
+ this.showTypingIndicator();
101
+ }
102
+ }, {
103
+ key: "showTypingIndicator",
104
+ value: function showTypingIndicator() {
105
+ this.clearTypingIndicator();
106
+ this.typingIndicatorVisible = true;
107
+ const indicator = this.typingIndicatorTemplateTarget.cloneNode(true);
108
+ indicator.setAttribute('data-hellotext--webchat-target', 'typingIndicator');
109
+ indicator.style.display = 'flex';
110
+ this.messagesContainerTarget.appendChild(indicator);
111
+ requestAnimationFrame(() => {
112
+ this.messagesContainerTarget.scroll({
113
+ top: this.messagesContainerTarget.scrollHeight,
114
+ behavior: 'instant'
115
+ });
116
+ });
117
+ const timeout = this.typingIndicatorKeepAliveValue;
118
+ this.incomingTypingIndicatorTimeout = setTimeout(() => {
119
+ this.clearTypingIndicator();
120
+ }, timeout);
121
+ }
122
+ }, {
123
+ key: "resetTypingIndicatorTimer",
124
+ value: function resetTypingIndicatorTimer() {
125
+ if (!this.typingIndicatorVisible) return;
126
+
127
+ // Clear existing timeout
128
+ clearTimeout(this.incomingTypingIndicatorTimeout);
129
+ clearTimeout(this.optimisticTypingTimeout);
130
+
131
+ // Use unified timeout for all typing indicators
132
+ const timeout = this.typingIndicatorKeepAliveValue;
133
+ this.incomingTypingIndicatorTimeout = setTimeout(() => {
134
+ this.clearTypingIndicator();
135
+ }, timeout);
136
+ }
137
+ }, {
138
+ key: "clearTypingIndicator",
139
+ value: function clearTypingIndicator() {
140
+ if (this.hasTypingIndicatorTarget) {
141
+ this.typingIndicatorTarget.remove();
142
+ }
143
+ this.typingIndicatorVisible = false;
144
+ clearTimeout(this.incomingTypingIndicatorTimeout);
145
+ clearTimeout(this.optimisticTypingTimeout);
146
+ }
147
+ }, {
148
+ key: "onMessageInputChange",
149
+ value: function onMessageInputChange() {
150
+ this.resizeInput();
151
+ clearTimeout(this.typingIndicatorTimeout);
152
+ if (!this.hasSentTypingIndicator) {
153
+ this.webChatChannel.startTypingIndicator();
154
+ this.hasSentTypingIndicator = true;
155
+ }
156
+ this.typingIndicatorTimeout = setTimeout(() => {
157
+ this.hasSentTypingIndicator = false;
158
+ }, 3000);
159
+ }
86
160
  }, {
87
161
  key: "onOutboundMessageSent",
88
162
  value: function onOutboundMessageSent(event) {
@@ -92,7 +166,13 @@ let _default = /*#__PURE__*/function (_Controller) {
92
166
  const callbacks = {
93
167
  'message:sent': data => {
94
168
  const element = new DOMParser().parseFromString(data.element, 'text/html').body.firstElementChild;
95
- this.messagesContainerTarget.appendChild(element);
169
+
170
+ // Insert message before typing indicator if one exists
171
+ if (this.typingIndicatorVisible && this.hasTypingIndicatorTarget) {
172
+ this.messagesContainerTarget.insertBefore(element, this.typingIndicatorTarget);
173
+ } else {
174
+ this.messagesContainerTarget.appendChild(element);
175
+ }
96
176
  element.scrollIntoView({
97
177
  behavior: 'instant'
98
178
  });
@@ -164,10 +244,20 @@ let _default = /*#__PURE__*/function (_Controller) {
164
244
  this.openValue = false;
165
245
  }
166
246
  }
247
+ }, {
248
+ key: "closePopover",
249
+ value: function closePopover() {
250
+ this.popoverTarget.classList.add(...this.fadeOutClasses);
251
+ setTimeout(() => {
252
+ this.openValue = false;
253
+ }, 250);
254
+ }
167
255
  }, {
168
256
  key: "onPopoverOpened",
169
257
  value: function onPopoverOpened() {
170
- this.inputTarget.focus();
258
+ if (!this.onMobile) {
259
+ this.inputTarget.focus();
260
+ }
171
261
  if (!this.scrolled) {
172
262
  requestAnimationFrame(() => {
173
263
  this.messagesContainerTarget.scroll({
@@ -188,9 +278,6 @@ let _default = /*#__PURE__*/function (_Controller) {
188
278
  key: "onPopoverClosed",
189
279
  value: function onPopoverClosed() {
190
280
  _hellotext.default.eventEmitter.dispatch('webchat:closed');
191
- setTimeout(() => {
192
- this.inputTarget.value = '';
193
- });
194
281
  localStorage.setItem(`hellotext--webchat--${this.idValue}`, 'closed');
195
282
  }
196
283
  }, {
@@ -221,6 +308,7 @@ let _default = /*#__PURE__*/function (_Controller) {
221
308
  key: "onMessageReceived",
222
309
  value: function onMessageReceived(message) {
223
310
  const {
311
+ id,
224
312
  body,
225
313
  attachments
226
314
  } = message;
@@ -238,6 +326,11 @@ let _default = /*#__PURE__*/function (_Controller) {
238
326
  element.querySelector('[data-attachment-container]').appendChild(image);
239
327
  });
240
328
  }
329
+
330
+ // Clear any typing indicator when message is received
331
+ if (this.typingIndicatorVisible) {
332
+ this.clearTypingIndicator();
333
+ }
241
334
  this.messagesContainerTarget.appendChild(element);
242
335
  _hellotext.default.eventEmitter.dispatch('webchat:message:received', {
243
336
  ...message,
@@ -246,40 +339,39 @@ let _default = /*#__PURE__*/function (_Controller) {
246
339
  element.scrollIntoView({
247
340
  behavior: 'smooth'
248
341
  });
249
- this.setOfflineTimeout();
250
- if (this.openValue) return;
342
+ if (this.openValue) {
343
+ this.messagesAPI.markAsSeen(id);
344
+ return;
345
+ }
251
346
  this.unreadCounterTarget.style.display = 'flex';
252
347
  const unreadCount = (parseInt(this.unreadCounterTarget.innerText) || 0) + 1;
253
348
  this.unreadCounterTarget.innerText = unreadCount > 99 ? '99+' : unreadCount;
254
349
  }
255
350
  }, {
256
- key: "onConversationAssignment",
257
- value: function onConversationAssignment(conversation) {
258
- const {
259
- to: user
260
- } = conversation;
261
- this.titleTarget.innerText = user.name;
262
- if (user.online) {
263
- this.onlineStatusTarget.style.display = 'flex';
264
- } else {
265
- this.onlineStatusTarget.style.display = 'none';
266
- }
267
- }
268
- }, {
269
- key: "onAgentOnline",
270
- value: function onAgentOnline() {
271
- this.onlineStatusTarget.style.display = 'flex';
272
- this.setOfflineTimeout();
351
+ key: "resizeInput",
352
+ value: function resizeInput() {
353
+ const maxHeight = 96;
354
+
355
+ // Temporarily reset height to its natural content size
356
+ this.inputTarget.style.height = 'auto';
357
+
358
+ // Set the height to the scrollHeight, which is the minimum height
359
+ // the element needs to fit its content without a scrollbar.
360
+ const scrollHeight = this.inputTarget.scrollHeight;
361
+ this.inputTarget.style.height = `${Math.min(scrollHeight, maxHeight)}px`;
273
362
  }
274
363
  }, {
275
364
  key: "sendMessage",
276
- value: async function sendMessage() {
365
+ value: async function sendMessage(e) {
277
366
  const formData = new FormData();
278
367
  const message = {
279
368
  body: this.inputTarget.value,
280
369
  attachments: this.files
281
370
  };
282
371
  if (this.inputTarget.value.trim().length === 0 && this.files.length === 0) {
372
+ if (e && e.target) {
373
+ e.preventDefault();
374
+ }
283
375
  return;
284
376
  }
285
377
  if (this.inputTarget.value.trim().length > 0) {
@@ -291,6 +383,7 @@ let _default = /*#__PURE__*/function (_Controller) {
291
383
  formData.append('message[attachments][]', file);
292
384
  });
293
385
  formData.append('session', _hellotext.default.session);
386
+ formData.append('locale', _core.Locale.toString());
294
387
  const element = this.messageTemplateTarget.cloneNode(true);
295
388
  element.id = `hellotext--webchat--${this.idValue}--message--${Date.now()}`;
296
389
  element.classList.add('received');
@@ -307,7 +400,13 @@ let _default = /*#__PURE__*/function (_Controller) {
307
400
  element.querySelector('[data-attachment-container]').appendChild(attachment);
308
401
  });
309
402
  }
310
- this.messagesContainerTarget.appendChild(element);
403
+
404
+ // Insert message before typing indicator if one exists
405
+ if (this.typingIndicatorVisible && this.hasTypingIndicatorTarget) {
406
+ this.messagesContainerTarget.insertBefore(element, this.typingIndicatorTarget);
407
+ } else {
408
+ this.messagesContainerTarget.appendChild(element);
409
+ }
311
410
  element.scrollIntoView({
312
411
  behavior: 'smooth'
313
412
  });
@@ -316,6 +415,7 @@ let _default = /*#__PURE__*/function (_Controller) {
316
415
  element: element.outerHTML
317
416
  });
318
417
  this.inputTarget.value = '';
418
+ this.resizeInput();
319
419
  this.files = [];
320
420
  this.attachmentInputTarget.value = '';
321
421
  this.attachmentContainerTarget.innerHTML = '';
@@ -338,6 +438,14 @@ let _default = /*#__PURE__*/function (_Controller) {
338
438
  this.conversationIdValue = data.conversation;
339
439
  this.webChatChannel.updateSubscriptionWith(this.conversationIdValue);
340
440
  }
441
+ if (this.typingIndicatorVisible) {
442
+ this.resetTypingIndicatorTimer();
443
+ } else {
444
+ clearTimeout(this.optimisticTypingTimeout);
445
+ this.optimisticTypingTimeout = setTimeout(() => {
446
+ this.showOptimisticTypingIndicator();
447
+ }, this.optimisticTypingIndicatorWaitValue);
448
+ }
341
449
  this.attachmentContainerTarget.style.display = '';
342
450
  }
343
451
  }, {
@@ -434,30 +542,27 @@ let _default = /*#__PURE__*/function (_Controller) {
434
542
  this.inputTarget.selectionStart = this.inputTarget.selectionEnd = start + emoji.length;
435
543
  this.inputTarget.focus();
436
544
  }
437
- }, {
438
- key: "setOfflineTimeout",
439
- value: function setOfflineTimeout() {
440
- clearTimeout(this.offlineTimeout);
441
- this.offlineTimeout = setTimeout(() => {
442
- this.onlineStatusTarget.style.display = 'none';
443
- }, this.fiveMinutes);
444
- }
445
545
  }, {
446
546
  key: "byteToMegabyte",
447
547
  value: function byteToMegabyte(bytes) {
448
548
  return Math.ceil(bytes / 1024 / 1024);
449
549
  }
450
550
  }, {
451
- key: "fiveMinutes",
551
+ key: "middlewares",
452
552
  get: function () {
453
- return 300000;
553
+ return [(0, _dom.offset)(this.offsetValue), (0, _dom.shift)({
554
+ padding: this.paddingValue
555
+ }), (0, _dom.flip)()];
454
556
  }
455
557
  }, {
456
- key: "middlewares",
558
+ key: "shouldOpenOnMount",
457
559
  get: function () {
458
- return [(0, _dom.offset)(5), (0, _dom.shift)({
459
- padding: 24
460
- }), (0, _dom.flip)()];
560
+ return localStorage.getItem(`hellotext--webchat--${this.idValue}`) === 'opened' && !this.onMobile;
561
+ }
562
+ }, {
563
+ key: "onMobile",
564
+ get: function () {
565
+ return window.matchMedia(`(max-width: ${this.fullScreenThresholdValue}px)`).matches;
461
566
  }
462
567
  }]);
463
568
  return _default;
@@ -487,6 +592,28 @@ _default.values = {
487
592
  nextPage: {
488
593
  type: Number,
489
594
  default: undefined
490
- }
595
+ },
596
+ fullScreenThreshold: {
597
+ type: Number,
598
+ default: 1024
599
+ },
600
+ typingIndicatorKeepAlive: {
601
+ type: Number,
602
+ default: 30000
603
+ },
604
+ // 30 seconds
605
+ offset: {
606
+ type: Number,
607
+ default: 24
608
+ },
609
+ padding: {
610
+ type: Number,
611
+ default: 24
612
+ },
613
+ optimisticTypingIndicatorWait: {
614
+ type: Number,
615
+ default: 1000
616
+ } // 1 second
491
617
  };
492
- _default.targets = ['trigger', 'popover', 'input', 'attachmentInput', 'attachmentButton', 'errorMessageContainer', 'attachmentTemplate', 'attachmentContainer', 'attachment', 'messageTemplate', 'messagesContainer', 'title', 'onlineStatus', 'attachmentImage', 'footer', 'toolbar', 'message', 'unreadCounter'];
618
+ _default.classes = ['fadeOut'];
619
+ _default.targets = ['trigger', 'popover', 'input', 'attachmentInput', 'attachmentButton', 'errorMessageContainer', 'attachmentTemplate', 'attachmentContainer', 'attachment', 'messageTemplate', 'messagesContainer', 'title', 'attachmentImage', 'footer', 'toolbar', 'message', 'unreadCounter', 'typingIndicator', 'typingIndicatorTemplate'];