@hellotext/hellotext 2.3.9 → 2.4.0

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 +165 -22
  19. package/lib/controllers/webchat_controller.js +173 -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 +187 -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,33 @@ 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) {
393
+ this.teaserTarget.innerHTML = this.messageTeaserValue;
394
+ this.teaserTarget.classList.toggle('invisible', this.openValue);
395
+ }
349
396
  }
350
397
  }, {
351
398
  key: "insertCarouselMessage",
@@ -353,6 +400,8 @@ let _default = /*#__PURE__*/function (_Controller) {
353
400
  var _element$querySelecto;
354
401
  const html = message.html;
355
402
  const element = new DOMParser().parseFromString(html, 'text/html').body.firstElementChild;
403
+ element.setAttribute('data-id', message.id);
404
+ element.setAttribute('data-hellotext--webchat-target', 'message');
356
405
  this.clearTypingIndicator();
357
406
  this.messagesContainerTarget.appendChild(element);
358
407
  element.scrollIntoView({
@@ -362,13 +411,12 @@ let _default = /*#__PURE__*/function (_Controller) {
362
411
  ...message,
363
412
  body: ((_element$querySelecto = element.querySelector('[data-body]')) === null || _element$querySelecto === void 0 ? void 0 : _element$querySelecto.innerText) || ''
364
413
  });
414
+ this.updateMessageTeaser(message.teaser);
365
415
  if (this.openValue) {
366
416
  this.messagesAPI.markAsSeen(message.id);
367
417
  return;
368
418
  }
369
- this.unreadCounterTarget.style.display = 'flex';
370
- const unreadCount = (parseInt(this.unreadCounterTarget.innerText) || 0) + 1;
371
- this.unreadCounterTarget.innerText = unreadCount > 99 ? '99+' : unreadCount;
419
+ this.incrementUnreadCounter();
372
420
  }
373
421
  }, {
374
422
  key: "resizeInput",
@@ -394,7 +442,8 @@ let _default = /*#__PURE__*/function (_Controller) {
394
442
  cardElement
395
443
  }
396
444
  }) {
397
- var _cardElement$querySel;
445
+ var _this$dismissTeaserFo2, _cardElement$querySel;
446
+ (_this$dismissTeaserFo2 = this.dismissTeaserForSession) === null || _this$dismissTeaserFo2 === void 0 ? void 0 : _this$dismissTeaserFo2.call(this);
398
447
  const formData = new FormData();
399
448
  formData.append('message[body]', body);
400
449
  formData.append('message[replied_to]', id);
@@ -402,13 +451,15 @@ let _default = /*#__PURE__*/function (_Controller) {
402
451
  formData.append('message[button]', buttonId);
403
452
  formData.append('session', _hellotext.default.session);
404
453
  formData.append('locale', _core.Locale.toString());
454
+ this.appendOpeningSequenceMessageIds(formData);
405
455
  const element = this.buildMessageElement();
406
456
  const attachment = (_cardElement$querySel = cardElement.querySelector('img')) === null || _cardElement$querySel === void 0 ? void 0 : _cardElement$querySel.cloneNode(true);
407
457
  element.querySelector('[data-body]').innerText = body;
408
458
  if (attachment) {
459
+ var _this$messageAttachme3;
409
460
  attachment.removeAttribute('width');
410
461
  attachment.removeAttribute('height');
411
- element.querySelector('[data-attachment-container]').appendChild(attachment);
462
+ (_this$messageAttachme3 = this.messageAttachmentsContainer(element)) === null || _this$messageAttachme3 === void 0 ? void 0 : _this$messageAttachme3.appendChild(attachment);
412
463
  }
413
464
  if (this.typingIndicatorVisible && this.hasTypingIndicatorTarget) {
414
465
  this.messagesContainerTarget.insertBefore(element, this.typingIndicatorTarget);
@@ -437,6 +488,7 @@ let _default = /*#__PURE__*/function (_Controller) {
437
488
  target: element,
438
489
  detail: data.id
439
490
  });
491
+ this.clearRevealedOpeningSequenceMessageIds();
440
492
  const message = {
441
493
  id: data.id,
442
494
  body: body,
@@ -448,10 +500,80 @@ let _default = /*#__PURE__*/function (_Controller) {
448
500
  };
449
501
  _hellotext.default.eventEmitter.dispatch('webchat:message:sent', message);
450
502
  }
503
+ }, {
504
+ key: "sendTeaserQuickReply",
505
+ value: async function sendTeaserQuickReply(event) {
506
+ var _this$dismissTeaserFo3;
507
+ event.preventDefault();
508
+ event.stopPropagation();
509
+ const button = event.currentTarget;
510
+ const value = (button.dataset.value || '').trim();
511
+ const label = [button.dataset.text, button.textContent].map(text => (text || '').trim()).find(text => text.length > 0);
512
+ const text = value || label;
513
+ if (!text) return;
514
+ (_this$dismissTeaserFo3 = this.dismissTeaserForSession) === null || _this$dismissTeaserFo3 === void 0 ? void 0 : _this$dismissTeaserFo3.call(this);
515
+ this.show();
516
+ const buttonType = (button.dataset.type || '').trim() || 'quick_reply';
517
+ const formData = new FormData();
518
+ formData.append('message[body]', text);
519
+ formData.append('session', _hellotext.default.session);
520
+ formData.append('locale', _core.Locale.toString());
521
+ this.appendOpeningSequenceMessageIds(formData);
522
+ const element = this.buildMessageElement();
523
+ element.querySelector('[data-body]').innerText = text;
524
+ if (this.typingIndicatorVisible && this.hasTypingIndicatorTarget) {
525
+ this.messagesContainerTarget.insertBefore(element, this.typingIndicatorTarget);
526
+ } else {
527
+ this.messagesContainerTarget.appendChild(element);
528
+ }
529
+ element.scrollIntoView({
530
+ behavior: 'smooth'
531
+ });
532
+ this.broadcastChannel.postMessage({
533
+ type: 'message:sent',
534
+ element: element.outerHTML
535
+ });
536
+ if (!this.typingIndicatorVisible) {
537
+ clearTimeout(this.optimisticTypingTimeout);
538
+ this.optimisticTypingTimeout = setTimeout(() => {
539
+ this.showOptimisticTypingIndicator();
540
+ }, this.optimisticTypingIndicatorWaitValue);
541
+ }
542
+ const response = await this.messagesAPI.create(formData);
543
+ if (response.failed) {
544
+ clearTimeout(this.optimisticTypingTimeout);
545
+ this.broadcastChannel.postMessage({
546
+ type: 'message:failed',
547
+ id: element.id
548
+ });
549
+ return element.classList.add('failed');
550
+ }
551
+ const data = await response.json();
552
+ element.setAttribute('data-id', data.id);
553
+ this.clearRevealedOpeningSequenceMessageIds();
554
+ _hellotext.default.eventEmitter.dispatch('webchat:message:sent', {
555
+ id: data.id,
556
+ body: text,
557
+ attachments: [],
558
+ type: 'quick_reply',
559
+ teaser: {
560
+ text: label || text,
561
+ value: value || text,
562
+ type: buttonType
563
+ }
564
+ });
565
+ if (data.conversation && data.conversation !== this.conversationIdValue) {
566
+ this.conversationIdValue = data.conversation;
567
+ this.webChatChannel.updateSubscriptionWith(this.conversationIdValue);
568
+ }
569
+ if (this.typingIndicatorVisible) {
570
+ this.resetTypingIndicatorTimer();
571
+ }
572
+ }
451
573
  }, {
452
574
  key: "sendMessage",
453
575
  value: async function sendMessage(e) {
454
- const formData = new FormData();
576
+ var _this$dismissTeaserFo4;
455
577
  const message = {
456
578
  body: this.inputTarget.value,
457
579
  attachments: this.files
@@ -462,6 +584,8 @@ let _default = /*#__PURE__*/function (_Controller) {
462
584
  }
463
585
  return;
464
586
  }
587
+ (_this$dismissTeaserFo4 = this.dismissTeaserForSession) === null || _this$dismissTeaserFo4 === void 0 ? void 0 : _this$dismissTeaserFo4.call(this);
588
+ const formData = new FormData();
465
589
  if (this.inputTarget.value.trim().length > 0) {
466
590
  formData.append('message[body]', this.inputTarget.value);
467
591
  } else {
@@ -472,6 +596,7 @@ let _default = /*#__PURE__*/function (_Controller) {
472
596
  });
473
597
  formData.append('session', _hellotext.default.session);
474
598
  formData.append('locale', _core.Locale.toString());
599
+ this.appendOpeningSequenceMessageIds(formData);
475
600
  const element = this.buildMessageElement();
476
601
  if (this.inputTarget.value.trim().length > 0) {
477
602
  element.querySelector('[data-body]').innerText = this.inputTarget.value;
@@ -481,7 +606,8 @@ let _default = /*#__PURE__*/function (_Controller) {
481
606
  const attachments = this.attachmentContainerTarget.querySelectorAll('img');
482
607
  if (attachments.length > 0) {
483
608
  attachments.forEach(attachment => {
484
- element.querySelector('[data-attachment-container]').appendChild(attachment.cloneNode(true));
609
+ var _this$messageAttachme4;
610
+ (_this$messageAttachme4 = this.messageAttachmentsContainer(element)) === null || _this$messageAttachme4 === void 0 ? void 0 : _this$messageAttachme4.appendChild(attachment.cloneNode(true));
485
611
  });
486
612
  }
487
613
 
@@ -528,6 +654,7 @@ let _default = /*#__PURE__*/function (_Controller) {
528
654
  const data = await response.json();
529
655
  element.setAttribute('data-id', data.id);
530
656
  message.id = data.id;
657
+ this.clearRevealedOpeningSequenceMessageIds();
531
658
  _hellotext.default.eventEmitter.dispatch('webchat:message:sent', message);
532
659
  if (data.conversation !== this.conversationIdValue) {
533
660
  this.conversationIdValue = data.conversation;
@@ -551,6 +678,18 @@ let _default = /*#__PURE__*/function (_Controller) {
551
678
  element.setAttribute('data-hellotext--webchat-target', 'message');
552
679
  return element;
553
680
  }
681
+ }, {
682
+ key: "messageAttachmentsContainer",
683
+ value: function messageAttachmentsContainer(element) {
684
+ return element.querySelector('[data-attachments-container], [data-attachment-container]');
685
+ }
686
+ }, {
687
+ key: "incrementUnreadCounter",
688
+ value: function incrementUnreadCounter() {
689
+ this.unreadCounterTarget.style.display = 'flex';
690
+ const unreadCount = (parseInt(this.unreadCounterTarget.innerText) || 0) + 1;
691
+ this.unreadCounterTarget.innerText = Math.min(unreadCount, 9);
692
+ }
554
693
  }, {
555
694
  key: "openAttachment",
556
695
  value: function openAttachment() {
@@ -716,7 +855,11 @@ _default.values = {
716
855
  optimisticTypingIndicatorWait: {
717
856
  type: Number,
718
857
  default: 1000
719
- } // 1 second
858
+ },
859
+ // 1 second,
860
+ teaser: Object,
861
+ messageTeaser: String,
862
+ behaviour: Object
720
863
  };
721
864
  _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'];
865
+ _default.targets = ['trigger', 'popover', 'input', 'attachmentInput', 'attachmentButton', 'errorMessageContainer', 'attachmentTemplate', 'attachmentContainer', 'attachment', 'messageTemplate', 'messagesContainer', 'title', 'attachmentImage', 'footer', 'toolbar', 'message', 'unreadCounter', 'typingIndicator', 'typingIndicatorTemplate', 'teaser', 'openingSequence', 'openingSequenceMessage'];