@hellotext/hellotext 2.3.9 → 2.4.1

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 (39) hide show
  1. package/dist/hellotext.js +1 -1
  2. package/dist/webchat-emoji-en.js +1 -0
  3. package/dist/webchat-emoji-es.js +1 -0
  4. package/dist/webchat-emoji.js +1 -0
  5. package/index.d.ts +30 -4
  6. package/lib/api/webchats.cjs +20 -0
  7. package/lib/api/webchats.js +20 -0
  8. package/lib/controllers/mixins/usePopover.cjs +7 -2
  9. package/lib/controllers/mixins/usePopover.js +7 -2
  10. package/lib/controllers/webchat/emoji_picker_controller.cjs +49 -10
  11. package/lib/controllers/webchat/emoji_picker_controller.js +66 -8
  12. package/lib/controllers/webchat/useBehaviour.cjs +74 -0
  13. package/lib/controllers/webchat/useBehaviour.js +67 -0
  14. package/lib/controllers/webchat/useOpeningSequence.cjs +103 -0
  15. package/lib/controllers/webchat/useOpeningSequence.js +96 -0
  16. package/lib/controllers/webchat/useTeaser.cjs +163 -0
  17. package/lib/controllers/webchat/useTeaser.js +156 -0
  18. package/lib/controllers/webchat_controller.cjs +168 -22
  19. package/lib/controllers/webchat_controller.js +176 -24
  20. package/lib/core/configuration/webchat.cjs +117 -42
  21. package/lib/core/configuration/webchat.js +121 -43
  22. package/lib/hellotext.cjs +28 -4
  23. package/lib/hellotext.js +28 -1
  24. package/lib/models/business.cjs +2 -0
  25. package/lib/models/business.js +2 -0
  26. package/lib/models/webchat.cjs +25 -0
  27. package/lib/models/webchat.js +25 -0
  28. package/package.json +1 -1
  29. package/src/api/webchats.js +17 -0
  30. package/src/controllers/mixins/usePopover.js +4 -2
  31. package/src/controllers/webchat/emoji_picker_controller.js +51 -9
  32. package/src/controllers/webchat/useBehaviour.js +81 -0
  33. package/src/controllers/webchat/useOpeningSequence.js +127 -0
  34. package/src/controllers/webchat/useTeaser.js +192 -0
  35. package/src/controllers/webchat_controller.js +200 -23
  36. package/src/core/configuration/webchat.js +127 -43
  37. package/src/hellotext.js +32 -4
  38. package/src/models/business.js +2 -0
  39. package/src/models/webchat.js +28 -0
@@ -11,8 +11,10 @@ var _webchat_channel = _interopRequireDefault(require("../channels/webchat_chann
11
11
  var _hellotext = _interopRequireDefault(require("../hellotext"));
12
12
  var _core = require("../core");
13
13
  var _webchat = require("../core/configuration/webchat");
14
- var _logo_builder = require("../builders/logo_builder");
15
14
  var _usePopover = require("./mixins/usePopover");
15
+ var _useBehaviour = require("./webchat/useBehaviour");
16
+ var _useOpeningSequence = require("./webchat/useOpeningSequence");
17
+ var _useTeaser = require("./webchat/useTeaser");
16
18
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
17
19
  function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
18
20
  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); } }
@@ -41,6 +43,7 @@ let _default = /*#__PURE__*/function (_Controller) {
41
43
  this.messagesAPI = new _messages.default(this.idValue);
42
44
  this.webChatChannel = new _webchat_channel.default(this.idValue, _hellotext.default.session, this.conversationIdValue);
43
45
  this.files = [];
46
+ this.messageIds = new Set();
44
47
  this.onMessageReceived = this.onMessageReceived.bind(this);
45
48
  this.onMessageReaction = this.onMessageReaction.bind(this);
46
49
  this.onTypingStart = this.onTypingStart.bind(this);
@@ -52,30 +55,41 @@ let _default = /*#__PURE__*/function (_Controller) {
52
55
  }, {
53
56
  key: "connect",
54
57
  value: function connect() {
58
+ (0, _useBehaviour.useBehaviour)(this);
55
59
  (0, _usePopover.usePopover)(this);
56
- this.popoverTarget.classList.add(..._webchat.Webchat.classes);
57
- this.triggerTarget.classList.add(..._webchat.Webchat.triggerClasses);
60
+ (0, _useOpeningSequence.useOpeningSequence)(this);
61
+ (0, _useTeaser.useTeaser)(this);
58
62
  this.setupFloatingUI({
59
63
  trigger: this.triggerTarget,
60
64
  popover: this.popoverTarget
61
65
  });
66
+ if (this.hasTeaserTarget) {
67
+ this.setupFloatingUI({
68
+ trigger: this.triggerTarget,
69
+ popover: this.teaserTarget,
70
+ strategy: 'absolute'
71
+ });
72
+ }
73
+ this.setupTeaser();
74
+ this.setupOpeningSequence();
62
75
  this.webChatChannel.onMessage(this.onMessageReceived);
63
76
  this.webChatChannel.onTypingStart(this.onTypingStart);
64
77
  this.webChatChannel.onReaction(this.onMessageReaction);
65
78
  this.messagesContainerTarget.addEventListener('scroll', this.onScroll);
66
- if (!_hellotext.default.business.features.white_label) {
67
- this.toolbarTarget.appendChild(_logo_builder.LogoBuilder.build());
68
- }
69
79
  if (this.shouldOpenOnMount) {
70
80
  this.openValue = true;
71
81
  }
72
82
  _hellotext.default.eventEmitter.dispatch('webchat:mounted');
73
83
  this.broadcastChannel.addEventListener('message', this.onOutboundMessageSent);
84
+ this.scheduleBehaviourOpen();
74
85
  _get(_getPrototypeOf(_default.prototype), "connect", this).call(this);
75
86
  }
76
87
  }, {
77
88
  key: "disconnect",
78
89
  value: function disconnect() {
90
+ this.cancelBehaviourOpen();
91
+ this.teardownTeaser();
92
+ this.teardownOpeningSequence();
79
93
  this.broadcastChannel.removeEventListener('message', this.onOutboundMessageSent);
80
94
  this.messagesContainerTarget.removeEventListener('scroll', this.onScroll);
81
95
 
@@ -221,11 +235,12 @@ let _default = /*#__PURE__*/function (_Controller) {
221
235
  }
222
236
  if (attachments) {
223
237
  attachments.forEach(attachmentUrl => {
238
+ var _this$messageAttachme;
224
239
  const image = this.attachmentImageTarget.cloneNode(true);
225
240
  image.removeAttribute('data-hellotext--webchat-target');
226
241
  image.src = attachmentUrl;
227
242
  image.style.display = 'block';
228
- element.querySelector('[data-attachment-container]').appendChild(image);
243
+ (_this$messageAttachme = this.messageAttachmentsContainer(element)) === null || _this$messageAttachme === void 0 ? void 0 : _this$messageAttachme.appendChild(image);
229
244
  });
230
245
  }
231
246
  element.setAttribute('data-body', body);
@@ -240,7 +255,7 @@ let _default = /*#__PURE__*/function (_Controller) {
240
255
  }, {
241
256
  key: "onClickOutside",
242
257
  value: function onClickOutside(event) {
243
- if (_webchat.Webchat.behaviour === _webchat.behaviors.POPOVER && this.openValue && event.target.nodeType && this.element.contains(event.target) === false) {
258
+ if (_webchat.Webchat.mode === _webchat.modes.POPOVER && this.openValue && event.target.nodeType && this.element.contains(event.target) === false) {
244
259
  this.openValue = false;
245
260
  }
246
261
  }
@@ -255,7 +270,9 @@ let _default = /*#__PURE__*/function (_Controller) {
255
270
  }, {
256
271
  key: "onPopoverOpened",
257
272
  value: function onPopoverOpened() {
273
+ var _this$dismissTeaserFo;
258
274
  this.popoverTarget.classList.remove(...this.fadeOutClasses);
275
+ (_this$dismissTeaserFo = this.dismissTeaserForSession) === null || _this$dismissTeaserFo === void 0 ? void 0 : _this$dismissTeaserFo.call(this);
259
276
  if (!this.onMobile) {
260
277
  this.inputTarget.focus();
261
278
  }
@@ -270,6 +287,10 @@ let _default = /*#__PURE__*/function (_Controller) {
270
287
  }
271
288
  _hellotext.default.eventEmitter.dispatch('webchat:opened');
272
289
  localStorage.setItem(`hellotext--webchat--${this.idValue}`, 'opened');
290
+ if (this.messageTeaserValue) {
291
+ this.messageTeaserValue = null;
292
+ }
293
+ this.startOpeningSequence();
273
294
  if (this.unreadCounterTarget.style.display === 'none') return;
274
295
  this.unreadCounterTarget.style.display = 'none';
275
296
  this.unreadCounterTarget.innerText = '0';
@@ -308,11 +329,15 @@ let _default = /*#__PURE__*/function (_Controller) {
308
329
  }, {
309
330
  key: "onMessageReceived",
310
331
  value: function onMessageReceived(message) {
332
+ var _this$hideTeaser;
311
333
  const {
312
334
  id,
313
335
  body,
314
- attachments
336
+ attachments,
337
+ teaser
315
338
  } = message;
339
+ if (!this.claimMessageId(id)) return;
340
+ (_this$hideTeaser = this.hideTeaser) === null || _this$hideTeaser === void 0 ? void 0 : _this$hideTeaser.call(this);
316
341
  if (message.carousel) {
317
342
  return this.insertCarouselMessage(message);
318
343
  }
@@ -321,13 +346,15 @@ let _default = /*#__PURE__*/function (_Controller) {
321
346
  const element = this.messageTemplateTarget.cloneNode(true);
322
347
  element.style.display = 'flex';
323
348
  element.querySelector('[data-body]').innerHTML = div.innerHTML;
349
+ element.setAttribute('data-id', id);
324
350
  element.setAttribute('data-hellotext--webchat-target', 'message');
325
351
  if (attachments) {
326
352
  attachments.forEach(attachmentUrl => {
353
+ var _this$messageAttachme2;
327
354
  const image = this.attachmentImageTarget.cloneNode(true);
328
355
  image.src = attachmentUrl;
329
356
  image.style.display = 'block';
330
- element.querySelector('[data-attachment-container]').appendChild(image);
357
+ (_this$messageAttachme2 = this.messageAttachmentsContainer(element)) === null || _this$messageAttachme2 === void 0 ? void 0 : _this$messageAttachme2.appendChild(image);
331
358
  });
332
359
  }
333
360
  this.clearTypingIndicator();
@@ -339,13 +366,36 @@ let _default = /*#__PURE__*/function (_Controller) {
339
366
  element.scrollIntoView({
340
367
  behavior: 'smooth'
341
368
  });
369
+ this.updateMessageTeaser(teaser);
342
370
  if (this.openValue) {
343
371
  this.messagesAPI.markAsSeen(id);
344
372
  return;
345
373
  }
346
- this.unreadCounterTarget.style.display = 'flex';
347
- const unreadCount = (parseInt(this.unreadCounterTarget.innerText) || 0) + 1;
348
- this.unreadCounterTarget.innerText = unreadCount > 99 ? '99+' : unreadCount;
374
+ this.incrementUnreadCounter();
375
+ }
376
+
377
+ // TCP broadcasts may deliver the same incoming message twice before Stimulus
378
+ // has connected the appended message target. Claiming the id in memory closes
379
+ // that window, while the target check still drops messages rendered earlier.
380
+ }, {
381
+ key: "claimMessageId",
382
+ value: function claimMessageId(id) {
383
+ const messageTargets = this.messageTargets || [];
384
+ if (this.messageIds.has(id)) return false;
385
+ this.messageIds.add(id);
386
+ return !messageTargets.some(element => element.dataset.id === id);
387
+ }
388
+ }, {
389
+ key: "updateMessageTeaser",
390
+ value: function updateMessageTeaser(teaser) {
391
+ this.messageTeaserValue = teaser;
392
+ if (!this.messageTeaserValue || !this.hasTeaserTarget || !this.hasInboundMessageTeaserTarget || !this.hasInboundMessageTeaserBodyTarget) {
393
+ return;
394
+ }
395
+ this.teaserMessageTargets.forEach(teaserMessage => teaserMessage.classList.add('hidden'));
396
+ this.inboundMessageTeaserBodyTarget.innerHTML = this.messageTeaserValue;
397
+ this.inboundMessageTeaserTarget.classList.remove('hidden');
398
+ this.teaserTarget.classList.toggle('invisible', this.openValue);
349
399
  }
350
400
  }, {
351
401
  key: "insertCarouselMessage",
@@ -353,6 +403,8 @@ let _default = /*#__PURE__*/function (_Controller) {
353
403
  var _element$querySelecto;
354
404
  const html = message.html;
355
405
  const element = new DOMParser().parseFromString(html, 'text/html').body.firstElementChild;
406
+ element.setAttribute('data-id', message.id);
407
+ element.setAttribute('data-hellotext--webchat-target', 'message');
356
408
  this.clearTypingIndicator();
357
409
  this.messagesContainerTarget.appendChild(element);
358
410
  element.scrollIntoView({
@@ -362,13 +414,12 @@ let _default = /*#__PURE__*/function (_Controller) {
362
414
  ...message,
363
415
  body: ((_element$querySelecto = element.querySelector('[data-body]')) === null || _element$querySelecto === void 0 ? void 0 : _element$querySelecto.innerText) || ''
364
416
  });
417
+ this.updateMessageTeaser(message.teaser);
365
418
  if (this.openValue) {
366
419
  this.messagesAPI.markAsSeen(message.id);
367
420
  return;
368
421
  }
369
- this.unreadCounterTarget.style.display = 'flex';
370
- const unreadCount = (parseInt(this.unreadCounterTarget.innerText) || 0) + 1;
371
- this.unreadCounterTarget.innerText = unreadCount > 99 ? '99+' : unreadCount;
422
+ this.incrementUnreadCounter();
372
423
  }
373
424
  }, {
374
425
  key: "resizeInput",
@@ -394,7 +445,8 @@ let _default = /*#__PURE__*/function (_Controller) {
394
445
  cardElement
395
446
  }
396
447
  }) {
397
- var _cardElement$querySel;
448
+ var _this$dismissTeaserFo2, _cardElement$querySel;
449
+ (_this$dismissTeaserFo2 = this.dismissTeaserForSession) === null || _this$dismissTeaserFo2 === void 0 ? void 0 : _this$dismissTeaserFo2.call(this);
398
450
  const formData = new FormData();
399
451
  formData.append('message[body]', body);
400
452
  formData.append('message[replied_to]', id);
@@ -402,13 +454,15 @@ let _default = /*#__PURE__*/function (_Controller) {
402
454
  formData.append('message[button]', buttonId);
403
455
  formData.append('session', _hellotext.default.session);
404
456
  formData.append('locale', _core.Locale.toString());
457
+ this.appendOpeningSequenceMessageIds(formData);
405
458
  const element = this.buildMessageElement();
406
459
  const attachment = (_cardElement$querySel = cardElement.querySelector('img')) === null || _cardElement$querySel === void 0 ? void 0 : _cardElement$querySel.cloneNode(true);
407
460
  element.querySelector('[data-body]').innerText = body;
408
461
  if (attachment) {
462
+ var _this$messageAttachme3;
409
463
  attachment.removeAttribute('width');
410
464
  attachment.removeAttribute('height');
411
- element.querySelector('[data-attachment-container]').appendChild(attachment);
465
+ (_this$messageAttachme3 = this.messageAttachmentsContainer(element)) === null || _this$messageAttachme3 === void 0 ? void 0 : _this$messageAttachme3.appendChild(attachment);
412
466
  }
413
467
  if (this.typingIndicatorVisible && this.hasTypingIndicatorTarget) {
414
468
  this.messagesContainerTarget.insertBefore(element, this.typingIndicatorTarget);
@@ -437,6 +491,7 @@ let _default = /*#__PURE__*/function (_Controller) {
437
491
  target: element,
438
492
  detail: data.id
439
493
  });
494
+ this.clearRevealedOpeningSequenceMessageIds();
440
495
  const message = {
441
496
  id: data.id,
442
497
  body: body,
@@ -448,10 +503,80 @@ let _default = /*#__PURE__*/function (_Controller) {
448
503
  };
449
504
  _hellotext.default.eventEmitter.dispatch('webchat:message:sent', message);
450
505
  }
506
+ }, {
507
+ key: "sendTeaserQuickReply",
508
+ value: async function sendTeaserQuickReply(event) {
509
+ var _this$dismissTeaserFo3;
510
+ event.preventDefault();
511
+ event.stopPropagation();
512
+ const button = event.currentTarget;
513
+ const value = (button.dataset.value || '').trim();
514
+ const label = [button.dataset.text, button.textContent].map(text => (text || '').trim()).find(text => text.length > 0);
515
+ const text = value || label;
516
+ if (!text) return;
517
+ (_this$dismissTeaserFo3 = this.dismissTeaserForSession) === null || _this$dismissTeaserFo3 === void 0 ? void 0 : _this$dismissTeaserFo3.call(this);
518
+ this.show();
519
+ const buttonType = (button.dataset.type || '').trim() || 'quick_reply';
520
+ const formData = new FormData();
521
+ formData.append('message[body]', text);
522
+ formData.append('session', _hellotext.default.session);
523
+ formData.append('locale', _core.Locale.toString());
524
+ this.appendOpeningSequenceMessageIds(formData);
525
+ const element = this.buildMessageElement();
526
+ element.querySelector('[data-body]').innerText = text;
527
+ if (this.typingIndicatorVisible && this.hasTypingIndicatorTarget) {
528
+ this.messagesContainerTarget.insertBefore(element, this.typingIndicatorTarget);
529
+ } else {
530
+ this.messagesContainerTarget.appendChild(element);
531
+ }
532
+ element.scrollIntoView({
533
+ behavior: 'smooth'
534
+ });
535
+ this.broadcastChannel.postMessage({
536
+ type: 'message:sent',
537
+ element: element.outerHTML
538
+ });
539
+ if (!this.typingIndicatorVisible) {
540
+ clearTimeout(this.optimisticTypingTimeout);
541
+ this.optimisticTypingTimeout = setTimeout(() => {
542
+ this.showOptimisticTypingIndicator();
543
+ }, this.optimisticTypingIndicatorWaitValue);
544
+ }
545
+ const response = await this.messagesAPI.create(formData);
546
+ if (response.failed) {
547
+ clearTimeout(this.optimisticTypingTimeout);
548
+ this.broadcastChannel.postMessage({
549
+ type: 'message:failed',
550
+ id: element.id
551
+ });
552
+ return element.classList.add('failed');
553
+ }
554
+ const data = await response.json();
555
+ element.setAttribute('data-id', data.id);
556
+ this.clearRevealedOpeningSequenceMessageIds();
557
+ _hellotext.default.eventEmitter.dispatch('webchat:message:sent', {
558
+ id: data.id,
559
+ body: text,
560
+ attachments: [],
561
+ type: 'quick_reply',
562
+ teaser: {
563
+ text: label || text,
564
+ value: value || text,
565
+ type: buttonType
566
+ }
567
+ });
568
+ if (data.conversation && data.conversation !== this.conversationIdValue) {
569
+ this.conversationIdValue = data.conversation;
570
+ this.webChatChannel.updateSubscriptionWith(this.conversationIdValue);
571
+ }
572
+ if (this.typingIndicatorVisible) {
573
+ this.resetTypingIndicatorTimer();
574
+ }
575
+ }
451
576
  }, {
452
577
  key: "sendMessage",
453
578
  value: async function sendMessage(e) {
454
- const formData = new FormData();
579
+ var _this$dismissTeaserFo4;
455
580
  const message = {
456
581
  body: this.inputTarget.value,
457
582
  attachments: this.files
@@ -462,6 +587,8 @@ let _default = /*#__PURE__*/function (_Controller) {
462
587
  }
463
588
  return;
464
589
  }
590
+ (_this$dismissTeaserFo4 = this.dismissTeaserForSession) === null || _this$dismissTeaserFo4 === void 0 ? void 0 : _this$dismissTeaserFo4.call(this);
591
+ const formData = new FormData();
465
592
  if (this.inputTarget.value.trim().length > 0) {
466
593
  formData.append('message[body]', this.inputTarget.value);
467
594
  } else {
@@ -472,6 +599,7 @@ let _default = /*#__PURE__*/function (_Controller) {
472
599
  });
473
600
  formData.append('session', _hellotext.default.session);
474
601
  formData.append('locale', _core.Locale.toString());
602
+ this.appendOpeningSequenceMessageIds(formData);
475
603
  const element = this.buildMessageElement();
476
604
  if (this.inputTarget.value.trim().length > 0) {
477
605
  element.querySelector('[data-body]').innerText = this.inputTarget.value;
@@ -481,7 +609,8 @@ let _default = /*#__PURE__*/function (_Controller) {
481
609
  const attachments = this.attachmentContainerTarget.querySelectorAll('img');
482
610
  if (attachments.length > 0) {
483
611
  attachments.forEach(attachment => {
484
- element.querySelector('[data-attachment-container]').appendChild(attachment.cloneNode(true));
612
+ var _this$messageAttachme4;
613
+ (_this$messageAttachme4 = this.messageAttachmentsContainer(element)) === null || _this$messageAttachme4 === void 0 ? void 0 : _this$messageAttachme4.appendChild(attachment.cloneNode(true));
485
614
  });
486
615
  }
487
616
 
@@ -528,6 +657,7 @@ let _default = /*#__PURE__*/function (_Controller) {
528
657
  const data = await response.json();
529
658
  element.setAttribute('data-id', data.id);
530
659
  message.id = data.id;
660
+ this.clearRevealedOpeningSequenceMessageIds();
531
661
  _hellotext.default.eventEmitter.dispatch('webchat:message:sent', message);
532
662
  if (data.conversation !== this.conversationIdValue) {
533
663
  this.conversationIdValue = data.conversation;
@@ -551,6 +681,18 @@ let _default = /*#__PURE__*/function (_Controller) {
551
681
  element.setAttribute('data-hellotext--webchat-target', 'message');
552
682
  return element;
553
683
  }
684
+ }, {
685
+ key: "messageAttachmentsContainer",
686
+ value: function messageAttachmentsContainer(element) {
687
+ return element.querySelector('[data-attachments-container], [data-attachment-container]');
688
+ }
689
+ }, {
690
+ key: "incrementUnreadCounter",
691
+ value: function incrementUnreadCounter() {
692
+ this.unreadCounterTarget.style.display = 'flex';
693
+ const unreadCount = (parseInt(this.unreadCounterTarget.innerText) || 0) + 1;
694
+ this.unreadCounterTarget.innerText = Math.min(unreadCount, 9);
695
+ }
554
696
  }, {
555
697
  key: "openAttachment",
556
698
  value: function openAttachment() {
@@ -716,7 +858,11 @@ _default.values = {
716
858
  optimisticTypingIndicatorWait: {
717
859
  type: Number,
718
860
  default: 1000
719
- } // 1 second
861
+ },
862
+ // 1 second,
863
+ teaser: Object,
864
+ messageTeaser: String,
865
+ behaviour: Object
720
866
  };
721
867
  _default.classes = ['fadeOut'];
722
- _default.targets = ['trigger', 'popover', 'input', 'attachmentInput', 'attachmentButton', 'errorMessageContainer', 'attachmentTemplate', 'attachmentContainer', 'attachment', 'messageTemplate', 'messagesContainer', 'title', 'attachmentImage', 'footer', 'toolbar', 'message', 'unreadCounter', 'typingIndicator', 'typingIndicatorTemplate'];
868
+ _default.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'];