@nyaruka/temba-components 0.91.6 → 0.92.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 (83) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/demo/index.html +1 -1
  3. package/dist/temba-components.js +760 -1189
  4. package/dist/temba-components.js.map +1 -1
  5. package/out-tsc/src/chat/Chat.js +714 -0
  6. package/out-tsc/src/chat/Chat.js.map +1 -0
  7. package/out-tsc/src/completion/helpers.js +1 -29
  8. package/out-tsc/src/completion/helpers.js.map +1 -1
  9. package/out-tsc/src/compose/Compose.js +6 -2
  10. package/out-tsc/src/compose/Compose.js.map +1 -1
  11. package/out-tsc/src/contacts/ContactChat.js +518 -54
  12. package/out-tsc/src/contacts/ContactChat.js.map +1 -1
  13. package/out-tsc/src/contacts/events.js +1 -998
  14. package/out-tsc/src/contacts/events.js.map +1 -1
  15. package/out-tsc/src/lightbox/Lightbox.js +4 -0
  16. package/out-tsc/src/lightbox/Lightbox.js.map +1 -1
  17. package/out-tsc/src/list/TembaMenu.js +0 -1
  18. package/out-tsc/src/list/TembaMenu.js.map +1 -1
  19. package/out-tsc/src/markdown.js +33 -0
  20. package/out-tsc/src/markdown.js.map +1 -0
  21. package/out-tsc/src/select/Select.js +6 -1
  22. package/out-tsc/src/select/Select.js.map +1 -1
  23. package/out-tsc/src/textinput/TextInput.js +1 -1
  24. package/out-tsc/src/textinput/TextInput.js.map +1 -1
  25. package/out-tsc/src/thumbnail/Thumbnail.js +128 -81
  26. package/out-tsc/src/thumbnail/Thumbnail.js.map +1 -1
  27. package/out-tsc/src/utils/index.js +9 -11
  28. package/out-tsc/src/utils/index.js.map +1 -1
  29. package/out-tsc/src/webchat/WebChat.js +109 -358
  30. package/out-tsc/src/webchat/WebChat.js.map +1 -1
  31. package/out-tsc/src/webchat/index.js +17 -0
  32. package/out-tsc/src/webchat/index.js.map +1 -1
  33. package/out-tsc/temba-modules.js +2 -2
  34. package/out-tsc/temba-modules.js.map +1 -1
  35. package/out-tsc/temba-webchat.js +2 -0
  36. package/out-tsc/temba-webchat.js.map +1 -1
  37. package/out-tsc/test/temba-contact-chat.test.js +1 -0
  38. package/out-tsc/test/temba-contact-chat.test.js.map +1 -1
  39. package/out-tsc/test/temba-lightbox.test.js +4 -4
  40. package/out-tsc/test/temba-lightbox.test.js.map +1 -1
  41. package/package.json +1 -1
  42. package/screenshots/truth/contacts/compose-attachments-no-text-failure.png +0 -0
  43. package/screenshots/truth/contacts/compose-attachments-no-text-success.png +0 -0
  44. package/screenshots/truth/contacts/compose-text-and-attachments-failure-attachments.png +0 -0
  45. package/screenshots/truth/contacts/compose-text-and-attachments-failure-generic.png +0 -0
  46. package/screenshots/truth/contacts/compose-text-and-attachments-failure-text-and-attachments.png +0 -0
  47. package/screenshots/truth/contacts/compose-text-and-attachments-failure-text.png +0 -0
  48. package/screenshots/truth/contacts/compose-text-and-attachments-success.png +0 -0
  49. package/screenshots/truth/contacts/compose-text-no-attachments-failure.png +0 -0
  50. package/screenshots/truth/contacts/compose-text-no-attachments-success.png +0 -0
  51. package/screenshots/truth/contacts/contact-active-default.png +0 -0
  52. package/screenshots/truth/contacts/contact-active-show-chatbox.png +0 -0
  53. package/screenshots/truth/contacts/contact-archived-hide-chatbox.png +0 -0
  54. package/screenshots/truth/contacts/contact-blocked-hide-chatbox.png +0 -0
  55. package/screenshots/truth/contacts/contact-stopped-hide-chatbox.png +0 -0
  56. package/screenshots/truth/lightbox/img-zoomed.png +0 -0
  57. package/screenshots/truth/lightbox/img.png +0 -0
  58. package/src/chat/Chat.ts +791 -0
  59. package/src/completion/helpers.ts +2 -40
  60. package/src/compose/Compose.ts +6 -2
  61. package/src/contacts/ContactChat.ts +609 -59
  62. package/src/contacts/events.ts +1 -1068
  63. package/src/lightbox/Lightbox.ts +5 -0
  64. package/src/list/TembaMenu.ts +0 -1
  65. package/src/markdown.ts +41 -0
  66. package/src/select/Select.ts +5 -1
  67. package/src/textinput/TextInput.ts +1 -1
  68. package/src/thumbnail/Thumbnail.ts +130 -81
  69. package/src/utils/index.ts +12 -13
  70. package/src/webchat/WebChat.ts +196 -413
  71. package/src/webchat/index.ts +23 -1
  72. package/static/css/temba-components.css +2 -0
  73. package/temba-modules.ts +2 -2
  74. package/temba-webchat.ts +2 -0
  75. package/test/temba-contact-chat.test.ts +1 -0
  76. package/test/temba-lightbox.test.ts +4 -4
  77. package/test-assets/contacts/history.json +1 -56
  78. package/out-tsc/src/contacts/ContactHistory.js +0 -691
  79. package/out-tsc/src/contacts/ContactHistory.js.map +0 -1
  80. package/out-tsc/test/temba-contact-history.test.js +0 -69
  81. package/out-tsc/test/temba-contact-history.test.js.map +0 -1
  82. package/src/contacts/ContactHistory.ts +0 -875
  83. package/test/temba-contact-history.test.ts +0 -107
@@ -1,10 +1,189 @@
1
1
  import { __decorate } from "tslib";
2
+ /* eslint-disable @typescript-eslint/no-this-alias */
2
3
  import { css, html } from 'lit';
3
4
  import { property } from 'lit/decorators.js';
4
5
  import { CustomEventType } from '../interfaces';
5
- import { postJSON } from '../utils';
6
+ import { oxford, oxfordFn, postJSON } from '../utils';
6
7
  import { ContactStoreElement } from './ContactStoreElement';
7
- const DEFAULT_REFRESH = 10000;
8
+ import { fetchContactHistory, getDisplayName } from './helpers';
9
+ import { MessageType } from '../chat/Chat';
10
+ import { getUserDisplay } from '../webchat';
11
+ import { DEFAULT_AVATAR } from '../webchat/assets';
12
+ export var Events;
13
+ (function (Events) {
14
+ Events["MESSAGE_CREATED"] = "msg_created";
15
+ Events["MESSAGE_RECEIVED"] = "msg_received";
16
+ Events["BROADCAST_CREATED"] = "broadcast_created";
17
+ Events["IVR_CREATED"] = "ivr_created";
18
+ Events["FLOW_ENTERED"] = "flow_entered";
19
+ Events["FLOW_EXITED"] = "flow_exited";
20
+ Events["RUN_RESULT_CHANGED"] = "run_result_changed";
21
+ Events["CONTACT_FIELD_CHANGED"] = "contact_field_changed";
22
+ Events["CONTACT_GROUPS_CHANGED"] = "contact_groups_changed";
23
+ Events["CONTACT_NAME_CHANGED"] = "contact_name_changed";
24
+ Events["CONTACT_URNS_CHANGED"] = "contact_urns_changed";
25
+ Events["CAMPAIGN_FIRED"] = "campaign_fired";
26
+ Events["CHANNEL_EVENT"] = "channel_event";
27
+ Events["CONTACT_LANGUAGE_CHANGED"] = "contact_language_changed";
28
+ Events["WEBHOOK_CALLED"] = "webhook_called";
29
+ Events["AIRTIME_TRANSFERRED"] = "airtime_transferred";
30
+ Events["CALL_STARTED"] = "call_started";
31
+ Events["EMAIL_SENT"] = "email_sent";
32
+ Events["INPUT_LABELS_ADDED"] = "input_labels_added";
33
+ Events["NOTE_CREATED"] = "note_created";
34
+ Events["TICKET_ASSIGNED"] = "ticket_assigned";
35
+ Events["TICKET_NOTE_ADDED"] = "ticket_note_added";
36
+ Events["TICKET_CLOSED"] = "ticket_closed";
37
+ Events["TICKET_OPENED"] = "ticket_opened";
38
+ Events["TICKET_REOPENED"] = "ticket_reopened";
39
+ Events["OPTIN_REQUESTED"] = "optin_requested";
40
+ Events["ERROR"] = "error";
41
+ Events["FAILURE"] = "failure";
42
+ })(Events || (Events = {}));
43
+ const renderInfoList = (singular, plural, items) => {
44
+ if (items.length === 1) {
45
+ return `${singular} **${items[0].name}**`;
46
+ }
47
+ else {
48
+ const list = items.map((item) => `**${item.name}**`);
49
+ if (list.length === 2) {
50
+ return `${plural} ${list.join(' and ')}`;
51
+ }
52
+ else {
53
+ const last = list.pop();
54
+ return `${plural} ${list.join(', ')}, and ${last}`;
55
+ }
56
+ }
57
+ };
58
+ const toTitleCase = (str) => {
59
+ return str.charAt(0).toUpperCase() + str.slice(1);
60
+ };
61
+ const renderChannelEvent = (event) => {
62
+ var _a, _b;
63
+ if (event.event.type === 'mt_miss') {
64
+ return 'Missed outgoing call';
65
+ }
66
+ else if (event.event.type === 'mo_miss') {
67
+ return 'Missed incoming call';
68
+ }
69
+ else if (event.event.type === 'new_conversation') {
70
+ return 'Started conversation';
71
+ }
72
+ else if (event.channel_event_type === 'welcome_message') {
73
+ return 'Welcome Message Sent';
74
+ }
75
+ else if (event.event.type === 'referral') {
76
+ return 'Referred';
77
+ }
78
+ else if (event.event.type === 'follow') {
79
+ return 'Followed';
80
+ }
81
+ else if (event.event.type === 'stop_contact') {
82
+ return 'Stopped';
83
+ }
84
+ else if (event.event.type === 'mt_call') {
85
+ return 'Outgoing Phone Call';
86
+ }
87
+ else if (event.event.type == 'mo_call') {
88
+ return 'Incoming Phone call';
89
+ }
90
+ else if (event.event.type == 'optin') {
91
+ return `Opted in to **${(_a = event.event.optin) === null || _a === void 0 ? void 0 : _a.name}**`;
92
+ }
93
+ else if (event.event.type == 'optout') {
94
+ return `Opted out of **${(_b = event.event.optin) === null || _b === void 0 ? void 0 : _b.name}**`;
95
+ }
96
+ };
97
+ const renderFlowEvent = (event) => {
98
+ let verb = 'Interrupted';
99
+ if (event.status !== 'I') {
100
+ if (event.type === Events.FLOW_ENTERED) {
101
+ verb = 'Started';
102
+ }
103
+ else {
104
+ verb = 'Completed';
105
+ }
106
+ }
107
+ return `${verb} [**${event.flow.name}**](/flow/editor/${event.flow.uuid}/)`;
108
+ };
109
+ const renderResultEvent = (event) => {
110
+ if (!event.name.startsWith('_') && event.value) {
111
+ return `Updated flow result **${event.name}** to **${event.value}**`;
112
+ }
113
+ };
114
+ const renderUpdateEvent = (event) => {
115
+ return event.value
116
+ ? `Updated **${event.field.name}** to **${event.value.text}**`
117
+ : `Cleared **${event.field.name}**`;
118
+ };
119
+ const renderNameChanged = (event) => {
120
+ return `Updated **Contact Name** to **${event.name}**`;
121
+ };
122
+ const renderContactURNsChanged = (event) => {
123
+ return `Updated **URNs** to ${oxfordFn(event.urns, (urn) => `**${urn.split(':')[1].split('?')[0]}**`)}`;
124
+ };
125
+ const renderEmailSent = (event) => {
126
+ return `Email sent to **${oxford(event.to, 'and')}** with subject **${event.subject}**`;
127
+ };
128
+ const renderLabelsAdded = (event) => {
129
+ return `Applied ${renderInfoList('label', 'labels', event.labels)}`;
130
+ };
131
+ export const renderTicketAction = (event, action) => {
132
+ if (event.created_by) {
133
+ return `**${getUserDisplay(event.created_by)}** ${action} a **[ticket](/ticket/all/closed/${event.ticket.uuid}/)**`;
134
+ }
135
+ return `A **[ticket](/ticket/all/closed/${event.ticket.uuid}/)** was **${action}**`;
136
+ };
137
+ export const renderTicketAssigned = (event) => {
138
+ return event.assignee
139
+ ? event.assignee.id === event.created_by.id
140
+ ? `**${getDisplayName(event.created_by)}** took this ticket`
141
+ : `${getDisplayName(event.created_by)} assigned this ticket to **${getDisplayName(event.assignee)}**`
142
+ : `**${getDisplayName(event.created_by)}** unassigned this ticket`;
143
+ };
144
+ export const renderContactGroupsEvent = (event) => {
145
+ const groupsEvent = event;
146
+ if (groupsEvent.groups_added) {
147
+ return renderInfoList('Added to group', 'Added to groups', groupsEvent.groups_added);
148
+ }
149
+ else if (groupsEvent.groups_removed) {
150
+ return renderInfoList('Removed from group', 'Removed from groups', groupsEvent.groups_removed);
151
+ }
152
+ };
153
+ export const renderCampaignFiredEvent = (event) => {
154
+ return `Campaign ${event.campaign.name}
155
+ ${event.fired_result === 'S' ? 'skipped' : 'triggered'}
156
+ ${event.campaign_event.offset_display}
157
+ ${event.campaign_event.relative_to.name}`;
158
+ };
159
+ export const renderTicketOpened = (event) => {
160
+ return `${event.ticket.topic.name} ticket was opened`;
161
+ };
162
+ export const renderErrorMessage = (event) => {
163
+ return `${event.text} ${event.type === Events.FAILURE
164
+ ? `Run ended prematurely, check the flow design`
165
+ : null}`;
166
+ };
167
+ export const renderWebhookEvent = (event) => {
168
+ return event.status === 'success'
169
+ ? `Successfully called ${event.url}`
170
+ : `Failed to call ${event.url}`;
171
+ };
172
+ export const renderAirtimeTransferredEvent = (event) => {
173
+ if (parseFloat(event.actual_amount) === 0) {
174
+ return `Airtime transfer failed`;
175
+ }
176
+ return `Transferred **${event.actual_amount}** ${event.currency} of airtime`;
177
+ };
178
+ export const renderCallStartedEvent = () => {
179
+ return `Call Started`;
180
+ };
181
+ export const renderContactLanguageChangedEvent = (event) => {
182
+ return `Language updated to **${event.language}**`;
183
+ };
184
+ export const renderOptinRequested = (event) => {
185
+ return `Requested opt-in for ${event.optin.name}`;
186
+ };
8
187
  export class ContactChat extends ContactStoreElement {
9
188
  static get styles() {
10
189
  return css `
@@ -35,7 +214,7 @@ export class ContactChat extends ContactStoreElement {
35
214
  }
36
215
 
37
216
  .chatbox {
38
- box-shadow: 0px -5px 1rem 0rem rgba(0, 0, 0, 0.07);
217
+ background: #fff;
39
218
  display: flex;
40
219
  flex-direction: column;
41
220
  --textarea-min-height: 1em;
@@ -43,10 +222,6 @@ export class ContactChat extends ContactStoreElement {
43
222
  --widget-box-shadow-focused: none;
44
223
  }
45
224
 
46
- .chatbox:focus-within {
47
- --textarea-height: 4em;
48
- }
49
-
50
225
  .chatbox.full {
51
226
  border-bottom-right-radius: 0 !important;
52
227
  }
@@ -81,6 +256,11 @@ export class ContactChat extends ContactStoreElement {
81
256
  --color-focus: transparent;
82
257
  --color-widget-bg-focused: transparent;
83
258
  }
259
+
260
+ .border {
261
+ border-top: 1px solid #f1f1f1;
262
+ margin: 0 1em;
263
+ }
84
264
  `;
85
265
  }
86
266
  constructor() {
@@ -88,39 +268,27 @@ export class ContactChat extends ContactStoreElement {
88
268
  this.contactsEndpoint = '/api/v2/contacts.json';
89
269
  this.currentNote = '';
90
270
  this.showDetails = true;
91
- this.monitor = false;
92
271
  this.currentTicket = null;
93
272
  this.currentContact = null;
94
273
  this.agent = '';
95
- this.refreshInterval = null;
274
+ this.blockFetching = false;
275
+ this.avatar = DEFAULT_AVATAR;
276
+ this.ticket = null;
277
+ this.lastEventTime = null;
278
+ this.newestEventTime = null;
279
+ this.refreshId = null;
280
+ this.polling = false;
281
+ }
282
+ firstUpdated(changed) {
283
+ super.firstUpdated(changed);
96
284
  }
97
285
  connectedCallback() {
98
286
  super.connectedCallback();
99
- if (this.monitor) {
100
- this.refreshInterval = setInterval(() => {
101
- if (this.currentTicket && this.currentTicket.closed_on) {
102
- return;
103
- }
104
- this.refresh();
105
- }, DEFAULT_REFRESH);
106
- }
287
+ this.chat = this.shadowRoot.querySelector('temba-chat');
107
288
  }
108
289
  disconnectedCallback() {
109
- if (this.refreshInterval) {
110
- clearInterval(this.refreshInterval);
111
- }
112
- }
113
- getContactHistory() {
114
- return this.shadowRoot.querySelector('temba-contact-history');
115
- }
116
- refresh(scrollToBottom = false) {
117
- const contactHistory = this.getContactHistory();
118
- if (contactHistory) {
119
- if (scrollToBottom) {
120
- contactHistory.scrollToBottom();
121
- }
122
- contactHistory.refresh();
123
- // super.refresh();
290
+ if (this.refreshId) {
291
+ clearInterval(this.refreshId);
124
292
  }
125
293
  }
126
294
  updated(changedProperties) {
@@ -130,6 +298,22 @@ export class ContactChat extends ContactStoreElement {
130
298
  changedProperties.has('currentContact')) {
131
299
  this.currentContact = this.data;
132
300
  }
301
+ if (changedProperties.has('currentContact')) {
302
+ this.chat = this.shadowRoot.querySelector('temba-chat');
303
+ this.reset();
304
+ this.fetchPreviousMessages();
305
+ }
306
+ }
307
+ reset() {
308
+ this.blockFetching = false;
309
+ this.ticket = null;
310
+ this.lastEventTime = null;
311
+ this.newestEventTime = null;
312
+ this.refreshId = null;
313
+ this.polling = false;
314
+ }
315
+ refresh() {
316
+ this.checkForNewMessages();
133
317
  }
134
318
  handleSend(evt) {
135
319
  const buttonName = evt.detail.name;
@@ -156,8 +340,8 @@ export class ContactChat extends ContactStoreElement {
156
340
  postJSON(`/api/v2/messages.json`, payload)
157
341
  .then((response) => {
158
342
  if (response.status < 400) {
343
+ this.checkForNewMessages();
159
344
  compose.reset();
160
- this.refresh(true);
161
345
  this.fireCustomEvent(CustomEventType.MessageSent, { msg: payload });
162
346
  }
163
347
  else if (response.status < 500) {
@@ -201,15 +385,290 @@ export class ContactChat extends ContactStoreElement {
201
385
  `;
202
386
  return html `${contactHistoryAndChatbox}`;
203
387
  }
388
+ getEndpoint() {
389
+ return `/contact/history/${this.currentContact.uuid}/?_format=json`;
390
+ }
391
+ scheduleRefresh() {
392
+ // knock five seconds off the newest event time so we are
393
+ // a little more aggressive about refreshing short term
394
+ let window = new Date().getTime() - this.newestEventTime / 1000 - 5000;
395
+ if (this.refreshId) {
396
+ clearTimeout(this.refreshId);
397
+ this.refreshId = null;
398
+ }
399
+ // wait no longer than 15 seconds
400
+ window = Math.min(window, 15000);
401
+ // wait at least 2 seconds
402
+ window = Math.max(window, 2000);
403
+ this.refreshId = setTimeout(() => {
404
+ this.checkForNewMessages();
405
+ }, window);
406
+ }
407
+ getEventMessage(event) {
408
+ let message = null;
409
+ switch (event.type) {
410
+ case Events.ERROR:
411
+ case Events.FAILURE:
412
+ message = {
413
+ type: MessageType.Inline,
414
+ text: `Error during flow: ${toTitleCase(event.text)}`
415
+ };
416
+ break;
417
+ case Events.TICKET_OPENED:
418
+ message = {
419
+ type: MessageType.Inline,
420
+ text: renderTicketAction(event, 'opened')
421
+ };
422
+ break;
423
+ case Events.TICKET_ASSIGNED:
424
+ message = {
425
+ type: MessageType.Inline,
426
+ text: renderTicketAssigned(event)
427
+ };
428
+ break;
429
+ case Events.TICKET_REOPENED:
430
+ message = {
431
+ type: MessageType.Inline,
432
+ text: renderTicketAction(event, 'reopened')
433
+ };
434
+ break;
435
+ case Events.TICKET_CLOSED:
436
+ message = {
437
+ type: MessageType.Inline,
438
+ text: renderTicketAction(event, 'closed')
439
+ };
440
+ break;
441
+ case Events.FLOW_ENTERED:
442
+ case Events.FLOW_EXITED:
443
+ message = {
444
+ type: MessageType.Inline,
445
+ text: renderFlowEvent(event)
446
+ };
447
+ break;
448
+ case Events.RUN_RESULT_CHANGED:
449
+ message = {
450
+ type: MessageType.Inline,
451
+ text: renderResultEvent(event)
452
+ };
453
+ break;
454
+ case Events.CONTACT_FIELD_CHANGED:
455
+ message = {
456
+ type: MessageType.Inline,
457
+ text: renderUpdateEvent(event)
458
+ };
459
+ break;
460
+ case Events.CONTACT_NAME_CHANGED:
461
+ message = {
462
+ type: MessageType.Inline,
463
+ text: renderNameChanged(event)
464
+ };
465
+ break;
466
+ case Events.CONTACT_URNS_CHANGED:
467
+ message = {
468
+ type: MessageType.Inline,
469
+ text: renderContactURNsChanged(event)
470
+ };
471
+ break;
472
+ case Events.EMAIL_SENT:
473
+ message = {
474
+ type: MessageType.Inline,
475
+ text: renderEmailSent(event)
476
+ };
477
+ break;
478
+ case Events.INPUT_LABELS_ADDED:
479
+ message = {
480
+ type: MessageType.Inline,
481
+ text: renderLabelsAdded(event)
482
+ };
483
+ break;
484
+ case Events.CONTACT_GROUPS_CHANGED:
485
+ message = {
486
+ type: MessageType.Inline,
487
+ text: renderContactGroupsEvent(event)
488
+ };
489
+ break;
490
+ case Events.WEBHOOK_CALLED:
491
+ message = {
492
+ type: MessageType.Inline,
493
+ text: renderWebhookEvent(event)
494
+ };
495
+ break;
496
+ case Events.AIRTIME_TRANSFERRED:
497
+ message = {
498
+ type: MessageType.Inline,
499
+ text: renderAirtimeTransferredEvent(event)
500
+ };
501
+ break;
502
+ case Events.CALL_STARTED:
503
+ message = {
504
+ type: MessageType.Inline,
505
+ text: renderCallStartedEvent()
506
+ };
507
+ break;
508
+ case Events.CAMPAIGN_FIRED:
509
+ message = {
510
+ type: MessageType.Inline,
511
+ text: renderCampaignFiredEvent(event)
512
+ };
513
+ break;
514
+ case Events.CHANNEL_EVENT:
515
+ message = {
516
+ type: MessageType.Inline,
517
+ text: renderChannelEvent(event)
518
+ };
519
+ break;
520
+ case Events.CONTACT_LANGUAGE_CHANGED:
521
+ message = {
522
+ type: MessageType.Inline,
523
+ text: renderContactLanguageChangedEvent(event)
524
+ };
525
+ break;
526
+ case Events.OPTIN_REQUESTED:
527
+ message = {
528
+ type: MessageType.Inline,
529
+ text: renderOptinRequested(event)
530
+ };
531
+ break;
532
+ }
533
+ message.date = new Date(event.created_on);
534
+ return message;
535
+ }
536
+ getUserForEvent(event) {
537
+ let user = null;
538
+ if (event.created_by) {
539
+ const storeUser = this.store.getUser(event.created_by.email);
540
+ if (storeUser) {
541
+ user = {
542
+ email: event.created_by.email,
543
+ name: [storeUser.first_name, storeUser.last_name].join(' '),
544
+ avatar: storeUser.avatar
545
+ };
546
+ }
547
+ }
548
+ else if (event.type === 'msg_received') {
549
+ user = {
550
+ name: this.currentContact.name
551
+ };
552
+ }
553
+ return user;
554
+ }
555
+ createMessages(page) {
556
+ let messages = page.events.map((event) => {
557
+ const ts = new Date(event.created_on).getTime() * 1000;
558
+ if (ts > this.newestEventTime) {
559
+ this.newestEventTime = ts;
560
+ }
561
+ if (event.type === 'ticket_note_added') {
562
+ const ticketEvent = event;
563
+ return {
564
+ type: MessageType.Note,
565
+ id: event.created_on + event.type,
566
+ user: this.getUserForEvent(ticketEvent),
567
+ date: new Date(ticketEvent.created_on),
568
+ text: ticketEvent.note
569
+ };
570
+ }
571
+ if (event.type === 'msg_created' || event.type === 'msg_received') {
572
+ const msgEvent = event;
573
+ return {
574
+ type: msgEvent.type === 'msg_created' ? 'msg_out' : 'msg_in',
575
+ id: msgEvent.msg.id + '',
576
+ user: this.getUserForEvent(msgEvent),
577
+ date: new Date(msgEvent.created_on),
578
+ attachments: msgEvent.msg.attachments,
579
+ text: msgEvent.msg.text,
580
+ sendError: msgEvent.status === 'E' || msgEvent.status === 'F',
581
+ popup: html `<div
582
+ style="display: flex; flex-direction: row; align-items:center; justify-content: space-between;font-size:0.9em;line-height:1em;min-width:10em"
583
+ >
584
+ <div style="justify-content:left;text-align:left">
585
+ <temba-date
586
+ value=${msgEvent.created_on}
587
+ display="duration"
588
+ ></temba-date>
589
+
590
+ ${msgEvent.failed_reason_display
591
+ ? html `
592
+ <div
593
+ style="margin-top:0.2em;margin-right: 0.5em;min-width:10em;max-width:15em;color:var(--color-error);font-size:0.9em"
594
+ >
595
+ ${msgEvent.failed_reason_display}
596
+ </div>
597
+ `
598
+ : null}
599
+ </div>
600
+ ${msgEvent.logs_url
601
+ ? html `<a style="margin-left:0.5em" href="${msgEvent.logs_url}"
602
+ ><temba-icon name="log"></temba-icon
603
+ ></a>`
604
+ : null}
605
+ </div> `
606
+ };
607
+ }
608
+ else {
609
+ return this.getEventMessage(event);
610
+ }
611
+ });
612
+ // remove any messages we don't recognize
613
+ messages = messages.filter((msg) => !!msg);
614
+ return messages;
615
+ }
616
+ checkForNewMessages() {
617
+ var _a;
618
+ // we are already working on it
619
+ if (this.polling) {
620
+ return;
621
+ }
622
+ const chat = this.chat;
623
+ const contactChat = this;
624
+ if (this.currentContact && this.newestEventTime) {
625
+ this.polling = true;
626
+ const endpoint = this.getEndpoint();
627
+ fetchContactHistory(false, endpoint, (_a = this.currentTicket) === null || _a === void 0 ? void 0 : _a.uuid, null, this.newestEventTime).then((page) => {
628
+ this.lastEventTime = page.next_before;
629
+ const messages = this.createMessages(page);
630
+ if (messages.length === 0) {
631
+ contactChat.blockFetching = true;
632
+ }
633
+ messages.reverse();
634
+ chat.addMessages(messages, null, true);
635
+ this.polling = false;
636
+ this.scheduleRefresh();
637
+ });
638
+ }
639
+ }
640
+ fetchPreviousMessages() {
641
+ var _a;
642
+ const chat = this.chat;
643
+ const contactChat = this;
644
+ if (!chat || chat.fetching || contactChat.blockFetching) {
645
+ return;
646
+ }
647
+ chat.fetching = true;
648
+ if (this.currentContact) {
649
+ const endpoint = this.getEndpoint();
650
+ fetchContactHistory(false, endpoint, (_a = this.currentTicket) === null || _a === void 0 ? void 0 : _a.uuid, this.lastEventTime).then((page) => {
651
+ this.lastEventTime = page.next_before;
652
+ const messages = this.createMessages(page);
653
+ messages.reverse();
654
+ if (messages.length === 0) {
655
+ contactChat.blockFetching = true;
656
+ }
657
+ chat.addMessages(messages);
658
+ this.scheduleRefresh();
659
+ });
660
+ }
661
+ }
662
+ fetchComplete() {
663
+ this.chat.fetching = false;
664
+ }
204
665
  getTembaContactHistory() {
205
- return html ` <temba-contact-history
206
- .uuid=${this.currentContact.uuid}
207
- .contact=${this.currentContact}
208
- .ticket=${this.currentTicket ? this.currentTicket.uuid : null}
209
- .endDate=${this.currentTicket ? this.currentTicket.closed_on : null}
210
- .agent=${this.agent}
211
- >
212
- </temba-contact-history>`;
666
+ return html `<temba-chat
667
+ @temba-scroll-threshold=${this.fetchPreviousMessages}
668
+ @temba-fetch-complete=${this.fetchComplete}
669
+ avatar=${this.avatar}
670
+ agent
671
+ ></temba-chat>`;
213
672
  }
214
673
  getTembaChatbox() {
215
674
  if (this.currentTicket) {
@@ -237,16 +696,18 @@ export class ContactChat extends ContactStoreElement {
237
696
  }
238
697
  }
239
698
  getChatbox() {
240
- return html `<div class="chatbox">
241
- <temba-compose
242
- chatbox
243
- attachments
244
- counter
245
- button
246
- @temba-button-clicked=${this.handleSend.bind(this)}
247
- >
248
- </temba-compose>
249
- </div>`;
699
+ return html `<div class="border"></div>
700
+ <div class="chatbox">
701
+ <temba-compose
702
+ chatbox
703
+ attachments
704
+ counter
705
+ button
706
+ autogrow
707
+ @temba-button-clicked=${this.handleSend.bind(this)}
708
+ >
709
+ </temba-compose>
710
+ </div>`;
250
711
  }
251
712
  }
252
713
  __decorate([
@@ -261,9 +722,6 @@ __decorate([
261
722
  __decorate([
262
723
  property({ type: Boolean })
263
724
  ], ContactChat.prototype, "showDetails", void 0);
264
- __decorate([
265
- property({ type: Boolean })
266
- ], ContactChat.prototype, "monitor", void 0);
267
725
  __decorate([
268
726
  property({ type: Object })
269
727
  ], ContactChat.prototype, "currentTicket", void 0);
@@ -273,4 +731,10 @@ __decorate([
273
731
  __decorate([
274
732
  property({ type: String })
275
733
  ], ContactChat.prototype, "agent", void 0);
734
+ __decorate([
735
+ property({ type: Boolean })
736
+ ], ContactChat.prototype, "blockFetching", void 0);
737
+ __decorate([
738
+ property({ type: String })
739
+ ], ContactChat.prototype, "avatar", void 0);
276
740
  //# sourceMappingURL=ContactChat.js.map