@nyaruka/temba-components 0.132.0 → 0.134.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 (181) hide show
  1. package/CHANGELOG.md +31 -1
  2. package/demo/components/flow/example.html +1 -0
  3. package/demo/components/webchat/example.html +1 -1
  4. package/demo/static/css/tailwind.css +30019 -0
  5. package/dist/locales/es.js +5 -5
  6. package/dist/locales/es.js.map +1 -1
  7. package/dist/locales/fr.js +5 -5
  8. package/dist/locales/fr.js.map +1 -1
  9. package/dist/locales/locale-codes.js +2 -11
  10. package/dist/locales/locale-codes.js.map +1 -1
  11. package/dist/locales/pt.js +5 -5
  12. package/dist/locales/pt.js.map +1 -1
  13. package/dist/temba-components.js +555 -476
  14. package/dist/temba-components.js.map +1 -1
  15. package/out-tsc/src/display/Chat.js +248 -95
  16. package/out-tsc/src/display/Chat.js.map +1 -1
  17. package/out-tsc/src/display/FloatingTab.js +4 -4
  18. package/out-tsc/src/display/FloatingTab.js.map +1 -1
  19. package/out-tsc/src/display/TembaUser.js +3 -3
  20. package/out-tsc/src/display/TembaUser.js.map +1 -1
  21. package/out-tsc/src/events.js.map +1 -1
  22. package/out-tsc/src/flow/CanvasNode.js +132 -58
  23. package/out-tsc/src/flow/CanvasNode.js.map +1 -1
  24. package/out-tsc/src/flow/Editor.js +183 -58
  25. package/out-tsc/src/flow/Editor.js.map +1 -1
  26. package/out-tsc/src/flow/utils.js +141 -0
  27. package/out-tsc/src/flow/utils.js.map +1 -1
  28. package/out-tsc/src/interfaces.js.map +1 -1
  29. package/out-tsc/src/layout/FloatingWindow.js +1 -2
  30. package/out-tsc/src/layout/FloatingWindow.js.map +1 -1
  31. package/out-tsc/src/list/ContentMenu.js +1 -0
  32. package/out-tsc/src/list/ContentMenu.js.map +1 -1
  33. package/out-tsc/src/list/SortableList.js +3 -2
  34. package/out-tsc/src/list/SortableList.js.map +1 -1
  35. package/out-tsc/src/live/ContactChat.js +184 -205
  36. package/out-tsc/src/live/ContactChat.js.map +1 -1
  37. package/out-tsc/src/locales/es.js +5 -5
  38. package/out-tsc/src/locales/es.js.map +1 -1
  39. package/out-tsc/src/locales/fr.js +5 -5
  40. package/out-tsc/src/locales/fr.js.map +1 -1
  41. package/out-tsc/src/locales/locale-codes.js +2 -11
  42. package/out-tsc/src/locales/locale-codes.js.map +1 -1
  43. package/out-tsc/src/locales/pt.js +5 -5
  44. package/out-tsc/src/locales/pt.js.map +1 -1
  45. package/out-tsc/src/store/AppState.js +34 -0
  46. package/out-tsc/src/store/AppState.js.map +1 -1
  47. package/out-tsc/src/store/Store.js +5 -5
  48. package/out-tsc/src/store/Store.js.map +1 -1
  49. package/out-tsc/src/utils.js +3 -3
  50. package/out-tsc/src/utils.js.map +1 -1
  51. package/out-tsc/src/webchat/WebChat.js +22 -9
  52. package/out-tsc/src/webchat/WebChat.js.map +1 -1
  53. package/out-tsc/test/ActionHelper.js +6 -5
  54. package/out-tsc/test/ActionHelper.js.map +1 -1
  55. package/out-tsc/test/actions/send_broadcast.test.js +9 -4
  56. package/out-tsc/test/actions/send_broadcast.test.js.map +1 -1
  57. package/out-tsc/test/temba-contact-chat.test.js +1 -1
  58. package/out-tsc/test/temba-contact-chat.test.js.map +1 -1
  59. package/out-tsc/test/temba-floating-window.test.js +0 -2
  60. package/out-tsc/test/temba-floating-window.test.js.map +1 -1
  61. package/out-tsc/test/temba-flow-collision.test.js +673 -0
  62. package/out-tsc/test/temba-flow-collision.test.js.map +1 -0
  63. package/out-tsc/test/temba-flow-editor-node.test.js +195 -0
  64. package/out-tsc/test/temba-flow-editor-node.test.js.map +1 -1
  65. package/out-tsc/test/temba-utils-uuid.test.js +45 -1
  66. package/out-tsc/test/temba-utils-uuid.test.js.map +1 -1
  67. package/out-tsc/test/utils.test.js +2 -2
  68. package/out-tsc/test/utils.test.js.map +1 -1
  69. package/package.json +1 -1
  70. package/screenshots/truth/actions/add_contact_groups/render/descriptive-group-names.png +0 -0
  71. package/screenshots/truth/actions/add_contact_groups/render/long-group-names.png +0 -0
  72. package/screenshots/truth/actions/add_contact_groups/render/many-groups.png +0 -0
  73. package/screenshots/truth/actions/add_contact_groups/render/multiple-groups.png +0 -0
  74. package/screenshots/truth/actions/add_contact_groups/render/single-group.png +0 -0
  75. package/screenshots/truth/actions/add_contact_urn/render/expression-facebook.png +0 -0
  76. package/screenshots/truth/actions/add_contact_urn/render/expression-phone.png +0 -0
  77. package/screenshots/truth/actions/add_contact_urn/render/facebook-id.png +0 -0
  78. package/screenshots/truth/actions/add_contact_urn/render/instagram-handle.png +0 -0
  79. package/screenshots/truth/actions/add_contact_urn/render/line-id.png +0 -0
  80. package/screenshots/truth/actions/add_contact_urn/render/phone-number.png +0 -0
  81. package/screenshots/truth/actions/add_contact_urn/render/telegram-id.png +0 -0
  82. package/screenshots/truth/actions/add_contact_urn/render/viber-id.png +0 -0
  83. package/screenshots/truth/actions/add_contact_urn/render/wechat-id.png +0 -0
  84. package/screenshots/truth/actions/add_contact_urn/render/whatsapp.png +0 -0
  85. package/screenshots/truth/actions/remove_contact_groups/render/cleanup-groups.png +0 -0
  86. package/screenshots/truth/actions/remove_contact_groups/render/long-descriptive-group-names.png +0 -0
  87. package/screenshots/truth/actions/remove_contact_groups/render/many-groups.png +0 -0
  88. package/screenshots/truth/actions/remove_contact_groups/render/multiple-groups.png +0 -0
  89. package/screenshots/truth/actions/remove_contact_groups/render/remove-from-all-groups.png +0 -0
  90. package/screenshots/truth/actions/remove_contact_groups/render/single-group.png +0 -0
  91. package/screenshots/truth/actions/send_broadcast/render/contacts-only.png +0 -0
  92. package/screenshots/truth/actions/send_broadcast/render/groups-and-contacts.png +0 -0
  93. package/screenshots/truth/actions/send_broadcast/render/groups-only.png +0 -0
  94. package/screenshots/truth/actions/send_broadcast/render/many-groups.png +0 -0
  95. package/screenshots/truth/actions/send_broadcast/render/multiline-text.png +0 -0
  96. package/screenshots/truth/actions/send_broadcast/render/with-attachments.png +0 -0
  97. package/screenshots/truth/actions/send_email/render/complex-business-email.png +0 -0
  98. package/screenshots/truth/actions/send_email/render/empty-body.png +0 -0
  99. package/screenshots/truth/actions/send_email/render/empty-subject.png +0 -0
  100. package/screenshots/truth/actions/send_email/render/long-subject.png +0 -0
  101. package/screenshots/truth/actions/send_email/render/multiline-body.png +0 -0
  102. package/screenshots/truth/actions/send_email/render/multiple-recipients.png +0 -0
  103. package/screenshots/truth/actions/send_email/render/simple-email.png +0 -0
  104. package/screenshots/truth/actions/send_email/render/with-expressions.png +0 -0
  105. package/screenshots/truth/actions/send_msg/render/long-quick-replies.png +0 -0
  106. package/screenshots/truth/actions/send_msg/render/multiline-text-with-replies.png +0 -0
  107. package/screenshots/truth/actions/send_msg/render/simple-text.png +0 -0
  108. package/screenshots/truth/actions/send_msg/render/text-with-linebreaks.png +0 -0
  109. package/screenshots/truth/actions/send_msg/render/text-with-many-quick-replies.png +0 -0
  110. package/screenshots/truth/actions/send_msg/render/text-with-quick-replies.png +0 -0
  111. package/screenshots/truth/actions/send_msg/render/text-without-quick-replies.png +0 -0
  112. package/screenshots/truth/actions/start_session/render/contact-query.png +0 -0
  113. package/screenshots/truth/actions/start_session/render/contacts-only.png +0 -0
  114. package/screenshots/truth/actions/start_session/render/create-contact.png +0 -0
  115. package/screenshots/truth/actions/start_session/render/groups-and-contacts.png +0 -0
  116. package/screenshots/truth/actions/start_session/render/groups-only.png +0 -0
  117. package/screenshots/truth/actions/start_session/render/many-recipients.png +0 -0
  118. package/screenshots/truth/contacts/chat-failure.png +0 -0
  119. package/screenshots/truth/contacts/chat-for-archived-contact.png +0 -0
  120. package/screenshots/truth/contacts/chat-for-blocked-contact.png +0 -0
  121. package/screenshots/truth/contacts/chat-for-stopped-contact.png +0 -0
  122. package/screenshots/truth/contacts/chat-sends-attachments-only.png +0 -0
  123. package/screenshots/truth/contacts/chat-sends-text-and-attachments.png +0 -0
  124. package/screenshots/truth/contacts/chat-sends-text-only.png +0 -0
  125. package/screenshots/truth/floating-tab/default.png +0 -0
  126. package/screenshots/truth/floating-tab/gray.png +0 -0
  127. package/screenshots/truth/floating-tab/green.png +0 -0
  128. package/screenshots/truth/floating-tab/hover.png +0 -0
  129. package/screenshots/truth/floating-tab/purple.png +0 -0
  130. package/screenshots/truth/nodes/split_by_llm/render/information-extraction.png +0 -0
  131. package/screenshots/truth/nodes/split_by_llm/render/sentiment-analysis.png +0 -0
  132. package/screenshots/truth/nodes/split_by_llm/render/summarization.png +0 -0
  133. package/screenshots/truth/nodes/split_by_llm/render/translation-task.png +0 -0
  134. package/screenshots/truth/nodes/split_by_llm_categorize/editor/feedback-categorization.png +0 -0
  135. package/screenshots/truth/nodes/split_by_llm_categorize/render/basic-categorization.png +0 -0
  136. package/screenshots/truth/nodes/split_by_llm_categorize/render/custom-input-and-result-name.png +0 -0
  137. package/screenshots/truth/nodes/split_by_llm_categorize/render/feedback-categorization.png +0 -0
  138. package/screenshots/truth/nodes/split_by_llm_categorize/render/many-categories.png +0 -0
  139. package/screenshots/truth/nodes/split_by_llm_categorize/render/minimal-categories.png +0 -0
  140. package/screenshots/truth/nodes/split_by_random/render/ab-test-multiple-variants.png +0 -0
  141. package/screenshots/truth/nodes/split_by_random/render/sampling-split.png +0 -0
  142. package/screenshots/truth/nodes/split_by_random/render/three-way-split.png +0 -0
  143. package/screenshots/truth/nodes/split_by_random/render/two-bucket-split.png +0 -0
  144. package/screenshots/truth/nodes/wait_for_digits/render/basic-digits-wait.png +0 -0
  145. package/screenshots/truth/nodes/wait_for_digits/render/phone-number-collection.png +0 -0
  146. package/screenshots/truth/nodes/wait_for_digits/render/single-digit-with-timeout.png +0 -0
  147. package/screenshots/truth/nodes/wait_for_digits/render/verification-code.png +0 -0
  148. package/screenshots/truth/nodes/wait_for_response/render/basic-wait.png +0 -0
  149. package/screenshots/truth/nodes/wait_for_response/render/custom-result-name.png +0 -0
  150. package/screenshots/truth/nodes/wait_for_response/render/no-timeout.png +0 -0
  151. package/screenshots/truth/nodes/wait_for_response/render/short-timeout.png +0 -0
  152. package/src/display/Chat.ts +331 -135
  153. package/src/display/FloatingTab.ts +4 -4
  154. package/src/display/TembaUser.ts +3 -2
  155. package/src/events.ts +12 -12
  156. package/src/flow/CanvasNode.ts +140 -57
  157. package/src/flow/Editor.ts +240 -58
  158. package/src/flow/utils.ts +207 -1
  159. package/src/interfaces.ts +7 -0
  160. package/src/layout/FloatingWindow.ts +1 -3
  161. package/src/list/ContentMenu.ts +1 -0
  162. package/src/list/SortableList.ts +3 -2
  163. package/src/live/ContactChat.ts +195 -221
  164. package/src/locales/es.ts +13 -18
  165. package/src/locales/fr.ts +13 -18
  166. package/src/locales/locale-codes.ts +2 -11
  167. package/src/locales/pt.ts +13 -18
  168. package/src/store/AppState.ts +43 -0
  169. package/src/store/Store.ts +5 -5
  170. package/src/utils.ts +3 -3
  171. package/src/webchat/WebChat.ts +24 -10
  172. package/test/ActionHelper.ts +13 -5
  173. package/test/actions/send_broadcast.test.ts +4 -2
  174. package/test/temba-contact-chat.test.ts +1 -1
  175. package/test/temba-floating-window.test.ts +0 -2
  176. package/test/temba-flow-collision.test.ts +833 -0
  177. package/test/temba-flow-editor-node.test.ts +224 -0
  178. package/test/temba-utils-uuid.test.ts +61 -1
  179. package/test/utils.test.ts +7 -2
  180. package/test-assets/contacts/history.json +22 -9
  181. package/web-test-runner.config.mjs +3 -3
@@ -1,21 +1,49 @@
1
1
  import { __decorate } from "tslib";
2
2
  import { html, css } from 'lit';
3
3
  import { property } from 'lit/decorators.js';
4
+ import { repeat } from 'lit/directives/repeat.js';
4
5
  import { RapidElement } from '../RapidElement';
5
6
  import { CustomEventType } from '../interfaces';
6
7
  import { DEFAULT_AVATAR } from '../webchat/assets';
7
- import { hashCode } from '../utils';
8
8
  const BATCH_TIME_WINDOW = 60 * 60 * 1000;
9
- const SCROLL_FETCH_BUFFER = 0.05;
9
+ const SCROLL_FETCH_BUFFER = 200; // pixels from top
10
10
  const MIN_FETCH_TIME = 250;
11
+ const getUnsendableReasonMessage = (reason) => {
12
+ switch (reason) {
13
+ case 'no_route':
14
+ return 'No channel available to send message';
15
+ case 'contact_blocked':
16
+ return 'Contact has been blocked';
17
+ case 'contact_stopped':
18
+ return 'Contact has been stopped';
19
+ case 'contact_archived':
20
+ return 'Contact is archived';
21
+ case 'org_suspended':
22
+ return 'Workspace is suspended';
23
+ case 'looping':
24
+ return 'Message loop detected';
25
+ default:
26
+ return 'Unable to send message';
27
+ }
28
+ };
29
+ const getStatusReasonMessage = (reason) => {
30
+ switch (reason) {
31
+ case 'error_limit':
32
+ return 'Error limit reached';
33
+ case 'too_old':
34
+ return 'Message is too old to send';
35
+ case 'channel_removed':
36
+ return 'Channel was removed';
37
+ default:
38
+ return 'Message failed to send';
39
+ }
40
+ };
11
41
  export var MessageType;
12
42
  (function (MessageType) {
13
43
  MessageType["Inline"] = "inline";
14
44
  MessageType["Error"] = "error";
15
45
  MessageType["Collapse"] = "collapse";
16
46
  MessageType["Note"] = "note";
17
- MessageType["MsgIn"] = "msg_in";
18
- MessageType["MsgOut"] = "msg_out";
19
47
  })(MessageType || (MessageType = {}));
20
48
  const TIME_FORMAT = { hour: 'numeric', minute: '2-digit' };
21
49
  const VERBOSE_FORMAT = {
@@ -35,7 +63,12 @@ export class Chat extends RapidElement {
35
63
  this.hideBottomScroll = true;
36
64
  this.defaultAvatar = DEFAULT_AVATAR;
37
65
  this.agent = false;
66
+ this.endOfHistory = false;
67
+ this.oldestEventDate = null;
68
+ this.showNewMessageNotification = false;
69
+ this.hasFooter = false;
38
70
  this.msgMap = new Map();
71
+ this.metadataCache = new Map();
39
72
  }
40
73
  static get styles() {
41
74
  return css `
@@ -92,7 +125,8 @@ export class Chat extends RapidElement {
92
125
  text-align: center;
93
126
  font-size: 0.8em;
94
127
  color: #999;
95
- margin-top: 2em;
128
+ margin-bottom: 2em;
129
+ margin-top: 1em;
96
130
  border-top: 1px solid #e9e9e9;
97
131
  padding: 1em;
98
132
  margin-left: 10%;
@@ -204,16 +238,24 @@ export class Chat extends RapidElement {
204
238
  color: rgba(0, 0, 0, 0.5);
205
239
  }
206
240
 
241
+ .failed .bubble,
242
+ .error .bubble {
243
+ border: 1px solid var(--color-error);
244
+ background: #ffe6e6;
245
+ color: #ad4747ff;
246
+ }
247
+
248
+ .error .bubble .name,
249
+ .failed .bubble .name {
250
+ color: #ad47479a;
251
+ }
252
+
207
253
  .message {
208
254
  margin-bottom: 0.5em;
209
255
  line-height: 1.2em;
210
256
  word-break: break-word;
211
257
  }
212
258
 
213
- .message-text {
214
- white-space: pre-line;
215
- }
216
-
217
259
  .chat {
218
260
  width: 28rem;
219
261
  border-radius: var(--curvature);
@@ -424,19 +466,12 @@ export class Chat extends RapidElement {
424
466
  border-radius: var(--curvature);
425
467
  }
426
468
 
427
- .error .bubble {
428
- border: 1px solid var(--color-error);
429
- background: white;
430
- color: #333;
431
- }
432
-
433
- .error .bubble .name {
434
- color: #999;
435
- }
436
-
469
+ .failed temba-thumbnail,
437
470
  .error temba-thumbnail {
438
- --thumb-background: var(--color-error);
439
- --thumb-icon: white;
471
+ --thumb-background: #ffe6e6;
472
+ --thumb-border: var(--color-error);
473
+ border: 1px solid var(--color-error);
474
+ color: #ad4747a8;
440
475
  }
441
476
 
442
477
  .outgoing .popup {
@@ -480,6 +515,37 @@ export class Chat extends RapidElement {
480
515
  opacity: 1;
481
516
  transition-delay: 1s;
482
517
  }
518
+
519
+ .new-message-notification {
520
+ position: absolute;
521
+ bottom: 1em;
522
+ left: 50%;
523
+ transform: translateX(-50%) translateY(100px);
524
+ background: var(--color-primary-dark, #3c92dd);
525
+ color: white;
526
+ padding: 0.75em 1.5em;
527
+ border-radius: var(--curvature);
528
+ box-shadow: rgba(0, 0, 0, 0.2) 0px 3px 7px 0px,
529
+ rgba(0, 0, 0, 0.3) 0px 1px 2px 0px;
530
+ cursor: pointer;
531
+ opacity: 0;
532
+ transition: all 0.3s ease-out;
533
+ z-index: 100;
534
+ font-weight: 500;
535
+ pointer-events: none;
536
+ }
537
+
538
+ .new-message-notification.visible {
539
+ transform: translateX(-50%) translateY(0);
540
+ opacity: 1;
541
+ pointer-events: auto;
542
+ }
543
+
544
+ .new-message-notification:hover {
545
+ background: var(--color-primary-darker, #2b7ac4);
546
+ box-shadow: rgba(0, 0, 0, 0.3) 0px 4px 10px 0px,
547
+ rgba(0, 0, 0, 0.4) 0px 2px 4px 0px;
548
+ }
483
549
  `;
484
550
  }
485
551
  firstUpdated(changed) {
@@ -490,15 +556,6 @@ export class Chat extends RapidElement {
490
556
  this.hideTopScroll = !hasScroll;
491
557
  }
492
558
  addMessages(messages, startTime = null, append = false) {
493
- // make sure our messages have ids
494
- messages.forEach((m) => {
495
- if (!m.id) {
496
- m.id =
497
- hashCode((m.text.strings || []).join('')) +
498
- '_' +
499
- m.date.toISOString();
500
- }
501
- });
502
559
  if (!startTime) {
503
560
  startTime = new Date();
504
561
  }
@@ -508,8 +565,16 @@ export class Chat extends RapidElement {
508
565
  // first add messages to the map
509
566
  const newMessages = [];
510
567
  for (const m of messages) {
568
+ // filter out metadata events - they aren't rendered but cached for later reference
569
+ if (m.type === 'msg_deleted' || m.type === 'msg_status_changed') {
570
+ const msgUuid = m.msg_uuid;
571
+ if (msgUuid) {
572
+ this.metadataCache.set(msgUuid, m);
573
+ }
574
+ continue;
575
+ }
511
576
  if (this.addMessage(m)) {
512
- newMessages.push(m.id);
577
+ newMessages.push(m.uuid);
513
578
  }
514
579
  }
515
580
  if (newMessages.length === 0) {
@@ -517,10 +582,25 @@ export class Chat extends RapidElement {
517
582
  }
518
583
  const ele = this.shadowRoot.querySelector('.scroll');
519
584
  const prevTop = ele.scrollTop;
585
+ const prevScrollHeight = ele.scrollHeight;
586
+ const scrollableHeight = ele.scrollHeight - ele.clientHeight;
587
+ const isScrolledAway = scrollableHeight > 0 && Math.abs(ele.scrollTop) > 50;
520
588
  const grouped = this.groupMessages(newMessages);
521
589
  this.insertGroups(grouped, append);
590
+ // show notification if new messages are appended and user is scrolled away from bottom
591
+ if (append && isScrolledAway && newMessages.length > 0) {
592
+ this.showNewMessageNotification = true;
593
+ }
522
594
  window.setTimeout(() => {
523
- ele.scrollTop = prevTop;
595
+ // when appending (new messages at bottom), adjust scroll to maintain visible content
596
+ // with column-reverse, new content at bottom increases scrollHeight
597
+ if (append && isScrolledAway) {
598
+ const heightDiff = ele.scrollHeight - prevScrollHeight;
599
+ ele.scrollTop = prevTop - heightDiff;
600
+ }
601
+ else {
602
+ ele.scrollTop = prevTop;
603
+ }
524
604
  this.fireCustomEvent(CustomEventType.FetchComplete);
525
605
  }, 100);
526
606
  },
@@ -531,18 +611,20 @@ export class Chat extends RapidElement {
531
611
  }
532
612
  addMessage(msg) {
533
613
  const isNew = !this.messageExists(msg);
534
- this.msgMap.set(msg.id, msg);
614
+ this.msgMap.set(msg.uuid, msg);
535
615
  return isNew;
536
616
  }
537
617
  messageExists(msg) {
538
- return this.msgMap.has(msg.id);
618
+ return this.msgMap.has(msg.uuid);
539
619
  }
540
620
  isSameGroup(msg1, msg2) {
541
621
  var _a, _b;
542
622
  if (msg1 && msg2) {
543
- return (msg1.type === msg2.type &&
544
- ((_a = msg1.user) === null || _a === void 0 ? void 0 : _a.name) === ((_b = msg2.user) === null || _b === void 0 ? void 0 : _b.name) &&
545
- Math.abs(msg1.date.getTime() - msg2.date.getTime()) < BATCH_TIME_WINDOW);
623
+ const sameGroup = msg1.type === msg2.type &&
624
+ ((_a = msg1._user) === null || _a === void 0 ? void 0 : _a.name) === ((_b = msg2._user) === null || _b === void 0 ? void 0 : _b.name) &&
625
+ Math.abs(msg1.created_on.getTime() - msg2.created_on.getTime()) <
626
+ BATCH_TIME_WINDOW;
627
+ return sameGroup;
546
628
  }
547
629
  return false;
548
630
  }
@@ -600,24 +682,39 @@ export class Chat extends RapidElement {
600
682
  }
601
683
  handleScroll(event) {
602
684
  const ele = event.target;
603
- const top = ele.scrollHeight - ele.clientHeight;
604
- const scroll = Math.round(top + ele.scrollTop);
605
- const scrollPct = scroll / top;
606
- this.hideTopScroll = scrollPct <= 0.01;
607
- this.hideBottomScroll = scrollPct >= 0.99;
608
- if (scrollPct < SCROLL_FETCH_BUFFER) {
685
+ const scrollableHeight = ele.scrollHeight - ele.clientHeight;
686
+ if (scrollableHeight <= 0) {
687
+ return;
688
+ }
689
+ // with column-reverse, scrollTop behavior depends on the browser
690
+ // check if scrollTop is negative (some browsers) or positive (others)
691
+ const absScrollTop = Math.abs(ele.scrollTop);
692
+ // when scrolling up to older messages, absScrollTop increases
693
+ // trigger when we're close to the maximum scroll (oldest messages)
694
+ const shouldFetch = absScrollTop >= scrollableHeight - SCROLL_FETCH_BUFFER;
695
+ this.hideTopScroll = absScrollTop >= scrollableHeight - 1;
696
+ this.hideBottomScroll = absScrollTop <= 1;
697
+ // hide notification when scrolled to bottom
698
+ if (absScrollTop <= 10) {
699
+ this.showNewMessageNotification = false;
700
+ }
701
+ if (shouldFetch) {
609
702
  this.fireCustomEvent(CustomEventType.ScrollThreshold);
610
703
  }
611
704
  }
612
705
  scrollToBottom() {
613
706
  const scroll = this.shadowRoot.querySelector('.scroll');
614
707
  if (scroll) {
615
- scroll.scrollTop = scroll.scrollHeight;
708
+ scroll.scrollTop = 0;
616
709
  this.hideBottomScroll = true;
710
+ this.showNewMessageNotification = false;
617
711
  }
618
712
  }
713
+ handleNewMessageClick() {
714
+ this.scrollToBottom();
715
+ }
619
716
  renderMessageGroup(msgIds, idx, groups) {
620
- var _a, _b, _c;
717
+ var _a, _b, _c, _d;
621
718
  const today = new Date();
622
719
  const firstGroup = idx === groups.length - 1;
623
720
  let prevMsg;
@@ -632,91 +729,113 @@ export class Chat extends RapidElement {
632
729
  let timeDisplay = null;
633
730
  if (prevMsg &&
634
731
  !this.isSameGroup(prevMsg, currentMsg) &&
635
- (Math.abs(currentMsg.date.getTime() - prevMsg.date.getTime()) >
636
- BATCH_TIME_WINDOW ||
732
+ (Math.abs(currentMsg.created_on.getTime() - prevMsg.created_on.getTime()) > BATCH_TIME_WINDOW ||
637
733
  idx === groups.length - 1)) {
638
- if (today.getDate() !== prevMsg.date.getDate() ||
639
- prevMsg.date.getDate() !== currentMsg.date.getDate()) {
734
+ if (today.getDate() !== prevMsg.created_on.getDate() ||
735
+ prevMsg.created_on.getDate() !== currentMsg.created_on.getDate()) {
640
736
  timeDisplay = html `<div class="time ${firstGroup ? 'first' : ''}">
641
- ${prevMsg.date.toLocaleTimeString(undefined, VERBOSE_FORMAT)}
737
+ ${prevMsg.created_on.toLocaleTimeString(undefined, VERBOSE_FORMAT)}
642
738
  </div>`;
643
739
  }
644
740
  else {
645
741
  timeDisplay = html `<div class="time ${firstGroup ? 'first' : ''}">
646
- ${prevMsg.date.toLocaleTimeString(undefined, TIME_FORMAT)}
742
+ ${prevMsg.created_on.toLocaleTimeString(undefined, TIME_FORMAT)}
647
743
  </div>`;
648
744
  }
649
745
  }
650
746
  const incoming = this.agent
651
- ? currentMsg.type !== 'msg_in'
652
- : currentMsg.type === 'msg_in';
653
- const name = (_a = currentMsg.user) === null || _a === void 0 ? void 0 : _a.name;
654
- const email = (_b = currentMsg.user) === null || _b === void 0 ? void 0 : _b.email;
655
- const showAvatar = ((currentMsg.type === 'note' ||
656
- currentMsg.type === 'msg_in' ||
657
- currentMsg.type === 'msg_out') &&
747
+ ? currentMsg.type !== 'msg_received'
748
+ : currentMsg.type === 'msg_received';
749
+ const name = (_a = currentMsg._user) === null || _a === void 0 ? void 0 : _a.name;
750
+ const showAvatar = ((currentMsg.type === 'msg_received' ||
751
+ currentMsg.type === 'msg_created') &&
658
752
  this.agent) ||
659
753
  !incoming;
754
+ const isSystem = !((_b = currentMsg._user) === null || _b === void 0 ? void 0 : _b.uuid);
660
755
  return html `
661
- ${!firstGroup ? timeDisplay : null}
662
- <div
663
- class="block ${incoming ? 'incoming' : 'outgoing'} ${currentMsg.type}"
664
- >
756
+ ${timeDisplay}
757
+ <div class="block ${incoming ? 'incoming' : 'outgoing'}">
665
758
  <div class="group-messages" style="flex-grow:1">
666
- ${msgIds.map((msgId, index) => {
759
+ ${repeat(msgIds, (msgId) => msgId, (msgId, index) => {
760
+ var _a, _b;
667
761
  const msg = this.msgMap.get(msgId);
668
- return html `<div class="row message">
669
- ${this.renderMessage(msg, index == 0 ? name : null)}
670
- </div>`;
762
+ const msgEvent = msg;
763
+ const statusClass = msg._status
764
+ ? msg._status.status
765
+ : '';
766
+ const hasError = ((_a = msgEvent.msg) === null || _a === void 0 ? void 0 : _a.unsendable_reason) ||
767
+ (((_b = msgEvent._status) === null || _b === void 0 ? void 0 : _b.reason) &&
768
+ (statusClass === 'failed' || statusClass === 'errored'));
769
+ const unsendableClass = hasError ? 'error' : '';
770
+ return html `<div
771
+ class="row message ${statusClass} ${unsendableClass}"
772
+ >
773
+ ${this.renderMessage(msg, index == 0 ? name : null)}
774
+ </div>`;
671
775
  })}
672
776
  </div>
673
777
  ${showAvatar
674
778
  ? html `<div class="avatar" style="align-self:flex-end">
675
779
  <temba-user
676
- email=${email}
780
+ uuid=${(_c = currentMsg._user) === null || _c === void 0 ? void 0 : _c.uuid}
677
781
  name=${name}
678
- avatar=${(_c = currentMsg.user) === null || _c === void 0 ? void 0 : _c.avatar}
679
- ?system=${!email && !name}
782
+ avatar=${(_d = currentMsg._user) === null || _d === void 0 ? void 0 : _d.avatar}
783
+ ?system=${isSystem}
680
784
  >
681
785
  </temba-user>
682
786
  </div>`
683
787
  : null}
684
788
  </div>
685
- ${firstGroup ? timeDisplay : null}
686
789
  `;
687
790
  }
688
791
  renderMessage(event, name = null) {
689
- if (event.type === MessageType.Error ||
690
- event.type === MessageType.Collapse ||
691
- event.type === MessageType.Inline) {
692
- return html `<div class="event">${event.text}</div>`;
792
+ var _a, _b;
793
+ if (event._rendered) {
794
+ return html `<div class="event">${event._rendered.html}</div>`;
693
795
  }
694
796
  const message = event;
797
+ const unsendableReason = (_a = message.msg) === null || _a === void 0 ? void 0 : _a.unsendable_reason;
798
+ const statusReason = (_b = message._status) === null || _b === void 0 ? void 0 : _b.reason;
799
+ const errorMessage = unsendableReason
800
+ ? getUnsendableReasonMessage(unsendableReason)
801
+ : statusReason
802
+ ? getStatusReasonMessage(statusReason)
803
+ : null;
695
804
  return html `
696
- <div class="bubble-wrap ${message.sendError ? 'error' : ''}">
697
- ${message.popup
698
- ? html `<div class="popup">
699
- ${message.popup}
700
- <div class="arrow">▼</div>
805
+ <div class="bubble-wrap">
806
+ <div class="popup" style="white-space: nowrap;">
807
+ ${errorMessage
808
+ ? html `<div style="color: var(--color-error); margin-right: 1em;">
809
+ ${errorMessage}
701
810
  </div>`
702
811
  : null}
703
-
704
- ${message.text
705
- ? html `
706
- <div class="bubble">
707
- ${name ? html `<div class="name">${name}</div>` : null}
708
- <div class="message message-text">${message.text}</div>
709
-
710
- <!--div>${message.date.toLocaleDateString(undefined, VERBOSE_FORMAT)}</div-->
711
- </div>
712
- `
812
+ <temba-date
813
+ value="${message.created_on.toISOString()}"
814
+ display="relative"
815
+ ></temba-date>
816
+ ${message._logs_url
817
+ ? html `<a
818
+ style="margin-left: 1em; color: var(--color-primary-dark);"
819
+ href="${message._logs_url}"
820
+ target="_blank"
821
+ rel="noopener noreferrer"
822
+ ><temba-icon name="log"></temba-icon
823
+ ></a>`
713
824
  : null}
714
825
 
715
- <div class="attachments">
716
- ${(message.attachments || []).map((attachment) => html `<temba-thumbnail
717
- attachment="${attachment}"
718
- ></temba-thumbnail>`)}
719
- </div>
826
+ <div class="arrow">▼</div>
827
+ </div>
828
+ ${message.msg.text
829
+ ? html `<div class="bubble">
830
+ ${name ? html `<div class="name">${name}</div>` : null}
831
+ <div class="message message-text">${message.msg.text}</div>
832
+ </div>`
833
+ : null}
834
+
835
+ <div class="attachments">
836
+ ${(message.msg.attachments || []).map((attachment) => html `<temba-thumbnail
837
+ attachment="${attachment}"
838
+ ></temba-thumbnail>`)}
720
839
  </div>
721
840
  </div>
722
841
  `;
@@ -726,6 +845,12 @@ export class Chat extends RapidElement {
726
845
  this.messageGroups = [];
727
846
  this.hideBottomScroll = true;
728
847
  this.hideTopScroll = true;
848
+ this.endOfHistory = false;
849
+ this.oldestEventDate = null;
850
+ }
851
+ setEndOfHistory(oldestDate) {
852
+ this.endOfHistory = true;
853
+ this.oldestEventDate = oldestDate;
729
854
  }
730
855
  render() {
731
856
  return html ` <div
@@ -736,13 +861,29 @@ export class Chat extends RapidElement {
736
861
  >
737
862
  <div class="scroll" @scroll=${this.handleScroll}>
738
863
  ${this.messageGroups
739
- ? this.messageGroups.map((msgGroup, idx, groups) => html `${this.renderMessageGroup(msgGroup, idx, groups)}`)
864
+ ? repeat(this.messageGroups, (msgGroup) => msgGroup.join(','), (msgGroup, idx) => html `${this.renderMessageGroup(msgGroup, idx, this.messageGroups)}`)
740
865
  : null}
741
866
 
742
867
  <temba-loading
743
868
  class="${!this.fetching ? 'hidden' : ''}"
744
869
  ></temba-loading>
870
+
871
+ ${this.endOfHistory && this.oldestEventDate
872
+ ? html `<div class="time first">
873
+ ${this.oldestEventDate.toLocaleTimeString(undefined, VERBOSE_FORMAT)}
874
+ </div>`
875
+ : null}
745
876
  </div>
877
+ ${!this.hasFooter
878
+ ? html `<div
879
+ class="new-message-notification ${this.showNewMessageNotification
880
+ ? 'visible'
881
+ : ''}"
882
+ @click=${this.handleNewMessageClick}
883
+ >
884
+ New Messages
885
+ </div>`
886
+ : null}
746
887
  <slot class="header" name="header"></slot>
747
888
  <slot class="footer" name="footer"></slot>
748
889
  </div>`;
@@ -766,4 +907,16 @@ __decorate([
766
907
  __decorate([
767
908
  property({ type: Boolean })
768
909
  ], Chat.prototype, "agent", void 0);
910
+ __decorate([
911
+ property({ type: Boolean, attribute: false })
912
+ ], Chat.prototype, "endOfHistory", void 0);
913
+ __decorate([
914
+ property({ type: Object, attribute: false })
915
+ ], Chat.prototype, "oldestEventDate", void 0);
916
+ __decorate([
917
+ property({ type: Boolean, attribute: false })
918
+ ], Chat.prototype, "showNewMessageNotification", void 0);
919
+ __decorate([
920
+ property({ type: Boolean })
921
+ ], Chat.prototype, "hasFooter", void 0);
769
922
  //# sourceMappingURL=Chat.js.map