@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.
- package/CHANGELOG.md +31 -1
- package/demo/components/flow/example.html +1 -0
- package/demo/components/webchat/example.html +1 -1
- package/demo/static/css/tailwind.css +30019 -0
- package/dist/locales/es.js +5 -5
- package/dist/locales/es.js.map +1 -1
- package/dist/locales/fr.js +5 -5
- package/dist/locales/fr.js.map +1 -1
- package/dist/locales/locale-codes.js +2 -11
- package/dist/locales/locale-codes.js.map +1 -1
- package/dist/locales/pt.js +5 -5
- package/dist/locales/pt.js.map +1 -1
- package/dist/temba-components.js +555 -476
- package/dist/temba-components.js.map +1 -1
- package/out-tsc/src/display/Chat.js +248 -95
- package/out-tsc/src/display/Chat.js.map +1 -1
- package/out-tsc/src/display/FloatingTab.js +4 -4
- package/out-tsc/src/display/FloatingTab.js.map +1 -1
- package/out-tsc/src/display/TembaUser.js +3 -3
- package/out-tsc/src/display/TembaUser.js.map +1 -1
- package/out-tsc/src/events.js.map +1 -1
- package/out-tsc/src/flow/CanvasNode.js +132 -58
- package/out-tsc/src/flow/CanvasNode.js.map +1 -1
- package/out-tsc/src/flow/Editor.js +183 -58
- package/out-tsc/src/flow/Editor.js.map +1 -1
- package/out-tsc/src/flow/utils.js +141 -0
- package/out-tsc/src/flow/utils.js.map +1 -1
- package/out-tsc/src/interfaces.js.map +1 -1
- package/out-tsc/src/layout/FloatingWindow.js +1 -2
- package/out-tsc/src/layout/FloatingWindow.js.map +1 -1
- package/out-tsc/src/list/ContentMenu.js +1 -0
- package/out-tsc/src/list/ContentMenu.js.map +1 -1
- package/out-tsc/src/list/SortableList.js +3 -2
- package/out-tsc/src/list/SortableList.js.map +1 -1
- package/out-tsc/src/live/ContactChat.js +184 -205
- package/out-tsc/src/live/ContactChat.js.map +1 -1
- package/out-tsc/src/locales/es.js +5 -5
- package/out-tsc/src/locales/es.js.map +1 -1
- package/out-tsc/src/locales/fr.js +5 -5
- package/out-tsc/src/locales/fr.js.map +1 -1
- package/out-tsc/src/locales/locale-codes.js +2 -11
- package/out-tsc/src/locales/locale-codes.js.map +1 -1
- package/out-tsc/src/locales/pt.js +5 -5
- package/out-tsc/src/locales/pt.js.map +1 -1
- package/out-tsc/src/store/AppState.js +34 -0
- package/out-tsc/src/store/AppState.js.map +1 -1
- package/out-tsc/src/store/Store.js +5 -5
- package/out-tsc/src/store/Store.js.map +1 -1
- package/out-tsc/src/utils.js +3 -3
- package/out-tsc/src/utils.js.map +1 -1
- package/out-tsc/src/webchat/WebChat.js +22 -9
- package/out-tsc/src/webchat/WebChat.js.map +1 -1
- package/out-tsc/test/ActionHelper.js +6 -5
- package/out-tsc/test/ActionHelper.js.map +1 -1
- package/out-tsc/test/actions/send_broadcast.test.js +9 -4
- package/out-tsc/test/actions/send_broadcast.test.js.map +1 -1
- package/out-tsc/test/temba-contact-chat.test.js +1 -1
- package/out-tsc/test/temba-contact-chat.test.js.map +1 -1
- package/out-tsc/test/temba-floating-window.test.js +0 -2
- package/out-tsc/test/temba-floating-window.test.js.map +1 -1
- package/out-tsc/test/temba-flow-collision.test.js +673 -0
- package/out-tsc/test/temba-flow-collision.test.js.map +1 -0
- package/out-tsc/test/temba-flow-editor-node.test.js +195 -0
- package/out-tsc/test/temba-flow-editor-node.test.js.map +1 -1
- package/out-tsc/test/temba-utils-uuid.test.js +45 -1
- package/out-tsc/test/temba-utils-uuid.test.js.map +1 -1
- package/out-tsc/test/utils.test.js +2 -2
- package/out-tsc/test/utils.test.js.map +1 -1
- package/package.json +1 -1
- package/screenshots/truth/actions/add_contact_groups/render/descriptive-group-names.png +0 -0
- package/screenshots/truth/actions/add_contact_groups/render/long-group-names.png +0 -0
- package/screenshots/truth/actions/add_contact_groups/render/many-groups.png +0 -0
- package/screenshots/truth/actions/add_contact_groups/render/multiple-groups.png +0 -0
- package/screenshots/truth/actions/add_contact_groups/render/single-group.png +0 -0
- package/screenshots/truth/actions/add_contact_urn/render/expression-facebook.png +0 -0
- package/screenshots/truth/actions/add_contact_urn/render/expression-phone.png +0 -0
- package/screenshots/truth/actions/add_contact_urn/render/facebook-id.png +0 -0
- package/screenshots/truth/actions/add_contact_urn/render/instagram-handle.png +0 -0
- package/screenshots/truth/actions/add_contact_urn/render/line-id.png +0 -0
- package/screenshots/truth/actions/add_contact_urn/render/phone-number.png +0 -0
- package/screenshots/truth/actions/add_contact_urn/render/telegram-id.png +0 -0
- package/screenshots/truth/actions/add_contact_urn/render/viber-id.png +0 -0
- package/screenshots/truth/actions/add_contact_urn/render/wechat-id.png +0 -0
- package/screenshots/truth/actions/add_contact_urn/render/whatsapp.png +0 -0
- package/screenshots/truth/actions/remove_contact_groups/render/cleanup-groups.png +0 -0
- package/screenshots/truth/actions/remove_contact_groups/render/long-descriptive-group-names.png +0 -0
- package/screenshots/truth/actions/remove_contact_groups/render/many-groups.png +0 -0
- package/screenshots/truth/actions/remove_contact_groups/render/multiple-groups.png +0 -0
- package/screenshots/truth/actions/remove_contact_groups/render/remove-from-all-groups.png +0 -0
- package/screenshots/truth/actions/remove_contact_groups/render/single-group.png +0 -0
- package/screenshots/truth/actions/send_broadcast/render/contacts-only.png +0 -0
- package/screenshots/truth/actions/send_broadcast/render/groups-and-contacts.png +0 -0
- package/screenshots/truth/actions/send_broadcast/render/groups-only.png +0 -0
- package/screenshots/truth/actions/send_broadcast/render/many-groups.png +0 -0
- package/screenshots/truth/actions/send_broadcast/render/multiline-text.png +0 -0
- package/screenshots/truth/actions/send_broadcast/render/with-attachments.png +0 -0
- package/screenshots/truth/actions/send_email/render/complex-business-email.png +0 -0
- package/screenshots/truth/actions/send_email/render/empty-body.png +0 -0
- package/screenshots/truth/actions/send_email/render/empty-subject.png +0 -0
- package/screenshots/truth/actions/send_email/render/long-subject.png +0 -0
- package/screenshots/truth/actions/send_email/render/multiline-body.png +0 -0
- package/screenshots/truth/actions/send_email/render/multiple-recipients.png +0 -0
- package/screenshots/truth/actions/send_email/render/simple-email.png +0 -0
- package/screenshots/truth/actions/send_email/render/with-expressions.png +0 -0
- package/screenshots/truth/actions/send_msg/render/long-quick-replies.png +0 -0
- package/screenshots/truth/actions/send_msg/render/multiline-text-with-replies.png +0 -0
- package/screenshots/truth/actions/send_msg/render/simple-text.png +0 -0
- package/screenshots/truth/actions/send_msg/render/text-with-linebreaks.png +0 -0
- package/screenshots/truth/actions/send_msg/render/text-with-many-quick-replies.png +0 -0
- package/screenshots/truth/actions/send_msg/render/text-with-quick-replies.png +0 -0
- package/screenshots/truth/actions/send_msg/render/text-without-quick-replies.png +0 -0
- package/screenshots/truth/actions/start_session/render/contact-query.png +0 -0
- package/screenshots/truth/actions/start_session/render/contacts-only.png +0 -0
- package/screenshots/truth/actions/start_session/render/create-contact.png +0 -0
- package/screenshots/truth/actions/start_session/render/groups-and-contacts.png +0 -0
- package/screenshots/truth/actions/start_session/render/groups-only.png +0 -0
- package/screenshots/truth/actions/start_session/render/many-recipients.png +0 -0
- package/screenshots/truth/contacts/chat-failure.png +0 -0
- package/screenshots/truth/contacts/chat-for-archived-contact.png +0 -0
- package/screenshots/truth/contacts/chat-for-blocked-contact.png +0 -0
- package/screenshots/truth/contacts/chat-for-stopped-contact.png +0 -0
- package/screenshots/truth/contacts/chat-sends-attachments-only.png +0 -0
- package/screenshots/truth/contacts/chat-sends-text-and-attachments.png +0 -0
- package/screenshots/truth/contacts/chat-sends-text-only.png +0 -0
- package/screenshots/truth/floating-tab/default.png +0 -0
- package/screenshots/truth/floating-tab/gray.png +0 -0
- package/screenshots/truth/floating-tab/green.png +0 -0
- package/screenshots/truth/floating-tab/hover.png +0 -0
- package/screenshots/truth/floating-tab/purple.png +0 -0
- package/screenshots/truth/nodes/split_by_llm/render/information-extraction.png +0 -0
- package/screenshots/truth/nodes/split_by_llm/render/sentiment-analysis.png +0 -0
- package/screenshots/truth/nodes/split_by_llm/render/summarization.png +0 -0
- package/screenshots/truth/nodes/split_by_llm/render/translation-task.png +0 -0
- package/screenshots/truth/nodes/split_by_llm_categorize/editor/feedback-categorization.png +0 -0
- package/screenshots/truth/nodes/split_by_llm_categorize/render/basic-categorization.png +0 -0
- package/screenshots/truth/nodes/split_by_llm_categorize/render/custom-input-and-result-name.png +0 -0
- package/screenshots/truth/nodes/split_by_llm_categorize/render/feedback-categorization.png +0 -0
- package/screenshots/truth/nodes/split_by_llm_categorize/render/many-categories.png +0 -0
- package/screenshots/truth/nodes/split_by_llm_categorize/render/minimal-categories.png +0 -0
- package/screenshots/truth/nodes/split_by_random/render/ab-test-multiple-variants.png +0 -0
- package/screenshots/truth/nodes/split_by_random/render/sampling-split.png +0 -0
- package/screenshots/truth/nodes/split_by_random/render/three-way-split.png +0 -0
- package/screenshots/truth/nodes/split_by_random/render/two-bucket-split.png +0 -0
- package/screenshots/truth/nodes/wait_for_digits/render/basic-digits-wait.png +0 -0
- package/screenshots/truth/nodes/wait_for_digits/render/phone-number-collection.png +0 -0
- package/screenshots/truth/nodes/wait_for_digits/render/single-digit-with-timeout.png +0 -0
- package/screenshots/truth/nodes/wait_for_digits/render/verification-code.png +0 -0
- package/screenshots/truth/nodes/wait_for_response/render/basic-wait.png +0 -0
- package/screenshots/truth/nodes/wait_for_response/render/custom-result-name.png +0 -0
- package/screenshots/truth/nodes/wait_for_response/render/no-timeout.png +0 -0
- package/screenshots/truth/nodes/wait_for_response/render/short-timeout.png +0 -0
- package/src/display/Chat.ts +331 -135
- package/src/display/FloatingTab.ts +4 -4
- package/src/display/TembaUser.ts +3 -2
- package/src/events.ts +12 -12
- package/src/flow/CanvasNode.ts +140 -57
- package/src/flow/Editor.ts +240 -58
- package/src/flow/utils.ts +207 -1
- package/src/interfaces.ts +7 -0
- package/src/layout/FloatingWindow.ts +1 -3
- package/src/list/ContentMenu.ts +1 -0
- package/src/list/SortableList.ts +3 -2
- package/src/live/ContactChat.ts +195 -221
- package/src/locales/es.ts +13 -18
- package/src/locales/fr.ts +13 -18
- package/src/locales/locale-codes.ts +2 -11
- package/src/locales/pt.ts +13 -18
- package/src/store/AppState.ts +43 -0
- package/src/store/Store.ts +5 -5
- package/src/utils.ts +3 -3
- package/src/webchat/WebChat.ts +24 -10
- package/test/ActionHelper.ts +13 -5
- package/test/actions/send_broadcast.test.ts +4 -2
- package/test/temba-contact-chat.test.ts +1 -1
- package/test/temba-floating-window.test.ts +0 -2
- package/test/temba-flow-collision.test.ts +833 -0
- package/test/temba-flow-editor-node.test.ts +224 -0
- package/test/temba-utils-uuid.test.ts +61 -1
- package/test/utils.test.ts +7 -2
- package/test-assets/contacts/history.json +22 -9
- package/web-test-runner.config.mjs +3 -3
package/src/list/ContentMenu.ts
CHANGED
package/src/list/SortableList.ts
CHANGED
|
@@ -577,8 +577,9 @@ export class SortableList extends RapidElement {
|
|
|
577
577
|
const fromIdx = originalDragIdx;
|
|
578
578
|
const toIdx = this.pendingDropIndex;
|
|
579
579
|
|
|
580
|
-
// only fire if the position actually changed
|
|
581
|
-
|
|
580
|
+
// only fire if the position actually changed AND this is not an external drag
|
|
581
|
+
// External drags are handled by external drop handlers
|
|
582
|
+
if (fromIdx !== toIdx && !this.isExternalDrag) {
|
|
582
583
|
this.fireCustomEvent(CustomEventType.OrderChanged, {
|
|
583
584
|
swap: [fromIdx, toIdx]
|
|
584
585
|
});
|
package/src/live/ContactChat.ts
CHANGED
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
} from '../interfaces';
|
|
11
11
|
import {
|
|
12
12
|
fetchResults,
|
|
13
|
+
generateUUIDv7,
|
|
13
14
|
getUrl,
|
|
14
15
|
oxfordFn,
|
|
15
16
|
postJSON,
|
|
@@ -23,12 +24,10 @@ import {
|
|
|
23
24
|
CallEvent,
|
|
24
25
|
ChannelEvent,
|
|
25
26
|
ChatStartedEvent,
|
|
26
|
-
ContactEvent,
|
|
27
27
|
ContactGroupsEvent,
|
|
28
28
|
ContactHistoryPage,
|
|
29
29
|
ContactLanguageChangedEvent,
|
|
30
30
|
ContactStatusChangedEvent,
|
|
31
|
-
MsgEvent,
|
|
32
31
|
NameChangedEvent,
|
|
33
32
|
OptInEvent,
|
|
34
33
|
RunEvent,
|
|
@@ -36,11 +35,10 @@ import {
|
|
|
36
35
|
UpdateFieldEvent,
|
|
37
36
|
URNsChangedEvent
|
|
38
37
|
} from '../events';
|
|
39
|
-
import { Chat,
|
|
38
|
+
import { Chat, MessageType, ContactEvent } from '../display/Chat';
|
|
40
39
|
import { DEFAULT_AVATAR } from '../webchat/assets';
|
|
41
40
|
import { UserSelect } from '../form/select/UserSelect';
|
|
42
41
|
import { Select } from '../form/select/Select';
|
|
43
|
-
import { Store } from '../store/Store';
|
|
44
42
|
|
|
45
43
|
/*
|
|
46
44
|
export const SCROLL_THRESHOLD = 100;
|
|
@@ -175,17 +173,40 @@ export const renderTicketAction = (
|
|
|
175
173
|
): TemplateResult => {
|
|
176
174
|
const ticketUUID = event.ticket?.uuid || event.ticket_uuid;
|
|
177
175
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
176
|
+
const actionNote = event.note
|
|
177
|
+
? html`<div
|
|
178
|
+
style="width:85%; background: #fffac3; padding: 1em;margin-bottom: 1em 0; border: 1px solid #ffe97f;border-radius: var(--curvature);"
|
|
179
|
+
>
|
|
180
|
+
<div style="color: #8e830fff; font-size: 1em;margin-bottom:0.25em">
|
|
181
|
+
<strong>${event._user.name}</strong> added a note
|
|
182
|
+
<temba-date
|
|
183
|
+
value=${event.created_on.toISOString()}
|
|
184
|
+
display="relative"
|
|
185
|
+
></temba-date>
|
|
186
|
+
</div>
|
|
187
|
+
${event.note}
|
|
188
|
+
</div>`
|
|
189
|
+
: null;
|
|
190
|
+
|
|
191
|
+
if (action === 'noted') {
|
|
192
|
+
return html`${actionNote}`;
|
|
183
193
|
}
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
194
|
+
|
|
195
|
+
const description = event._user
|
|
196
|
+
? html`<div>
|
|
197
|
+
<strong>${event._user.name}</strong> ${action} a
|
|
198
|
+
<strong><a href="/ticket/all/closed/${ticketUUID}/">ticket</a></strong>
|
|
199
|
+
</div>`
|
|
200
|
+
: html`<div>
|
|
201
|
+
A
|
|
202
|
+
<strong><a href="/ticket/all/closed/${ticketUUID}/">ticket</a></strong>
|
|
203
|
+
was <strong>${action}</strong>
|
|
204
|
+
</div>`;
|
|
205
|
+
|
|
206
|
+
return html`<div style="${actionNote ? 'margin-bottom: 1em;' : ''}">
|
|
207
|
+
${description}
|
|
208
|
+
</div>
|
|
209
|
+
${actionNote}`;
|
|
189
210
|
};
|
|
190
211
|
|
|
191
212
|
export const renderTicketAssigneeChanged = (
|
|
@@ -525,10 +546,12 @@ export class ContactChat extends ContactStoreElement {
|
|
|
525
546
|
private chat: Chat;
|
|
526
547
|
|
|
527
548
|
ticket = null;
|
|
528
|
-
|
|
529
|
-
|
|
549
|
+
beforeUUID: string = null; // for scrolling back through history
|
|
550
|
+
afterUUID: string = null; // for polling new messages
|
|
530
551
|
refreshId = null;
|
|
531
552
|
polling = false;
|
|
553
|
+
pollingInterval = 2000; // start at 2 seconds
|
|
554
|
+
lastFetchTime: number = null;
|
|
532
555
|
|
|
533
556
|
constructor() {
|
|
534
557
|
super();
|
|
@@ -569,9 +592,16 @@ export class ContactChat extends ContactStoreElement {
|
|
|
569
592
|
this.currentContact = this.data;
|
|
570
593
|
}
|
|
571
594
|
|
|
572
|
-
if (changedProperties.has('currentContact')) {
|
|
595
|
+
if (changedProperties.has('currentContact') && this.currentContact) {
|
|
573
596
|
this.chat = this.shadowRoot.querySelector('temba-chat');
|
|
574
|
-
|
|
597
|
+
if (
|
|
598
|
+
this.currentContact.uuid !==
|
|
599
|
+
changedProperties.get('currentContact')?.uuid
|
|
600
|
+
) {
|
|
601
|
+
this.reset();
|
|
602
|
+
} else {
|
|
603
|
+
setTimeout(() => this.checkForNewMessages(), 500);
|
|
604
|
+
}
|
|
575
605
|
this.fetchPreviousMessages();
|
|
576
606
|
}
|
|
577
607
|
}
|
|
@@ -582,10 +612,12 @@ export class ContactChat extends ContactStoreElement {
|
|
|
582
612
|
}
|
|
583
613
|
this.blockFetching = false;
|
|
584
614
|
this.ticket = null;
|
|
585
|
-
this.
|
|
586
|
-
this.
|
|
615
|
+
this.beforeUUID = null;
|
|
616
|
+
this.afterUUID = null;
|
|
587
617
|
this.refreshId = null;
|
|
588
618
|
this.polling = false;
|
|
619
|
+
this.pollingInterval = 2000;
|
|
620
|
+
this.lastFetchTime = null;
|
|
589
621
|
this.errorMessage = null;
|
|
590
622
|
|
|
591
623
|
const compose = this.shadowRoot.querySelector('temba-compose') as Compose;
|
|
@@ -632,8 +664,11 @@ export class ContactChat extends ContactStoreElement {
|
|
|
632
664
|
postJSON(`/contact/chat/${this.currentContact.uuid}/`, payload)
|
|
633
665
|
.then((response) => {
|
|
634
666
|
if (response.status < 400) {
|
|
635
|
-
const
|
|
636
|
-
|
|
667
|
+
const event = response.json.event;
|
|
668
|
+
event.created_on = new Date(event.created_on);
|
|
669
|
+
this.chat.addMessages([event], null, true);
|
|
670
|
+
// reset polling interval to 2 seconds after sending a message
|
|
671
|
+
this.pollingInterval = 2000;
|
|
637
672
|
this.checkForNewMessages();
|
|
638
673
|
composeEle.reset();
|
|
639
674
|
this.fireCustomEvent(CustomEventType.MessageSent, {
|
|
@@ -651,257 +686,188 @@ export class ContactChat extends ContactStoreElement {
|
|
|
651
686
|
|
|
652
687
|
private getEndpoint() {
|
|
653
688
|
if (this.contact) {
|
|
654
|
-
return `/contact/
|
|
689
|
+
return `/contact/chat/${this.contact}/`;
|
|
655
690
|
}
|
|
656
691
|
return null;
|
|
657
692
|
}
|
|
658
693
|
|
|
659
|
-
private scheduleRefresh() {
|
|
660
|
-
// knock five seconds off the newest event time so we are
|
|
661
|
-
// a little more aggressive about refreshing short term
|
|
662
|
-
let window = new Date().getTime() - this.newestEventTime / 1000 - 5000;
|
|
663
|
-
|
|
694
|
+
private scheduleRefresh(hasNewEvents = false) {
|
|
664
695
|
if (this.refreshId) {
|
|
665
696
|
clearTimeout(this.refreshId);
|
|
666
697
|
this.refreshId = null;
|
|
667
698
|
}
|
|
668
699
|
|
|
669
|
-
//
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
700
|
+
// reset to 2 seconds if we received new events
|
|
701
|
+
if (hasNewEvents) {
|
|
702
|
+
this.pollingInterval = 2000;
|
|
703
|
+
} else {
|
|
704
|
+
// increase interval by 1 second up to max of 15 seconds
|
|
705
|
+
this.pollingInterval = Math.min(this.pollingInterval + 1000, 15000);
|
|
706
|
+
}
|
|
674
707
|
|
|
675
708
|
this.refreshId = setTimeout(() => {
|
|
676
709
|
this.checkForNewMessages();
|
|
677
|
-
},
|
|
710
|
+
}, this.pollingInterval);
|
|
678
711
|
}
|
|
679
712
|
|
|
680
|
-
public
|
|
681
|
-
let message = null;
|
|
713
|
+
public prerender(event: ContactEvent) {
|
|
682
714
|
switch (event.type) {
|
|
683
715
|
case Events.AIRTIME_TRANSFERRED:
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
716
|
+
event._rendered = {
|
|
717
|
+
html: renderAirtimeTransferredEvent(event as AirtimeTransferredEvent),
|
|
718
|
+
type: MessageType.Inline
|
|
687
719
|
};
|
|
688
720
|
break;
|
|
689
721
|
case Events.CALL_CREATED:
|
|
690
722
|
case Events.CALL_MISSED:
|
|
691
723
|
case Events.CALL_RECEIVED:
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
724
|
+
event._rendered = {
|
|
725
|
+
html: renderCallEvent(event as CallEvent),
|
|
726
|
+
type: MessageType.Inline
|
|
695
727
|
};
|
|
696
728
|
break;
|
|
697
729
|
case Events.CHAT_STARTED:
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
730
|
+
event._rendered = {
|
|
731
|
+
html: renderChatStartedEvent(event as ChatStartedEvent),
|
|
732
|
+
type: MessageType.Inline
|
|
701
733
|
};
|
|
702
734
|
break;
|
|
703
735
|
case Events.CONTACT_FIELD_CHANGED:
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
736
|
+
event._rendered = {
|
|
737
|
+
html: renderUpdateEvent(event as UpdateFieldEvent),
|
|
738
|
+
type: MessageType.Inline
|
|
707
739
|
};
|
|
708
740
|
break;
|
|
709
741
|
case Events.CONTACT_GROUPS_CHANGED:
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
742
|
+
event._rendered = {
|
|
743
|
+
html: renderContactGroupsEvent(event as ContactGroupsEvent),
|
|
744
|
+
type: MessageType.Inline
|
|
713
745
|
};
|
|
714
746
|
break;
|
|
715
747
|
case Events.CONTACT_LANGUAGE_CHANGED:
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
text: renderContactLanguageChangedEvent(
|
|
748
|
+
event._rendered = {
|
|
749
|
+
html: renderContactLanguageChangedEvent(
|
|
719
750
|
event as ContactLanguageChangedEvent
|
|
720
|
-
)
|
|
751
|
+
),
|
|
752
|
+
type: MessageType.Inline
|
|
721
753
|
};
|
|
722
754
|
break;
|
|
723
755
|
case Events.CONTACT_NAME_CHANGED:
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
756
|
+
event._rendered = {
|
|
757
|
+
html: renderNameChanged(event as NameChangedEvent),
|
|
758
|
+
type: MessageType.Inline
|
|
727
759
|
};
|
|
728
760
|
break;
|
|
729
761
|
case Events.CONTACT_STATUS_CHANGED:
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
text: renderContactStatusChangedEvent(
|
|
762
|
+
event._rendered = {
|
|
763
|
+
html: renderContactStatusChangedEvent(
|
|
733
764
|
event as ContactStatusChangedEvent
|
|
734
|
-
)
|
|
765
|
+
),
|
|
766
|
+
type: MessageType.Inline
|
|
735
767
|
};
|
|
736
768
|
break;
|
|
737
769
|
case Events.CONTACT_URNS_CHANGED:
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
770
|
+
event._rendered = {
|
|
771
|
+
html: renderContactURNsChanged(event as URNsChangedEvent),
|
|
772
|
+
type: MessageType.Inline
|
|
741
773
|
};
|
|
742
774
|
break;
|
|
743
775
|
case Events.OPTIN_REQUESTED:
|
|
744
776
|
case Events.OPTIN_STARTED:
|
|
745
777
|
case Events.OPTIN_STOPPED:
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
778
|
+
event._rendered = {
|
|
779
|
+
html: renderOptInEvent(event as OptInEvent),
|
|
780
|
+
type: MessageType.Inline
|
|
749
781
|
};
|
|
750
782
|
break;
|
|
751
783
|
case Events.RUN_STARTED:
|
|
752
784
|
case Events.RUN_ENDED:
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
785
|
+
event._rendered = {
|
|
786
|
+
html: renderRunEvent(event as RunEvent),
|
|
787
|
+
type: MessageType.Inline
|
|
756
788
|
};
|
|
757
789
|
break;
|
|
758
790
|
case Events.TICKET_ASSIGNEE_CHANGED:
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
791
|
+
event._rendered = {
|
|
792
|
+
html: renderTicketAssigneeChanged(event as TicketEvent),
|
|
793
|
+
type: MessageType.Inline
|
|
762
794
|
};
|
|
763
795
|
break;
|
|
764
796
|
case Events.TICKET_CLOSED:
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
797
|
+
event._rendered = {
|
|
798
|
+
html: renderTicketAction(event as TicketEvent, 'closed'),
|
|
799
|
+
type: MessageType.Inline
|
|
768
800
|
};
|
|
769
801
|
break;
|
|
770
802
|
case Events.TICKET_OPENED:
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
803
|
+
event._rendered = {
|
|
804
|
+
html: renderTicketAction(event as TicketEvent, 'opened'),
|
|
805
|
+
type: MessageType.Inline
|
|
806
|
+
};
|
|
807
|
+
break;
|
|
808
|
+
case Events.TICKET_NOTE_ADDED:
|
|
809
|
+
event._rendered = {
|
|
810
|
+
html: renderTicketAction(event as TicketEvent, 'noted'),
|
|
811
|
+
type: MessageType.Inline
|
|
774
812
|
};
|
|
775
813
|
break;
|
|
776
814
|
case Events.TICKET_REOPENED:
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
815
|
+
event._rendered = {
|
|
816
|
+
html: renderTicketAction(event as TicketEvent, 'reopened'),
|
|
817
|
+
type: MessageType.Inline
|
|
780
818
|
};
|
|
781
819
|
break;
|
|
782
820
|
case Events.TICKET_TOPIC_CHANGED:
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
text: html`<div>
|
|
821
|
+
event._rendered = {
|
|
822
|
+
html: html`<div>
|
|
786
823
|
Topic changed to
|
|
787
824
|
<strong>${(event as TicketEvent).topic.name}</strong>
|
|
788
|
-
</div
|
|
825
|
+
</div>`,
|
|
826
|
+
type: MessageType.Inline
|
|
789
827
|
};
|
|
790
828
|
break;
|
|
791
829
|
case Events.CHANNEL_EVENT: // deprecated
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
830
|
+
event._rendered = {
|
|
831
|
+
html: renderChannelEvent(event as ChannelEvent),
|
|
832
|
+
type: MessageType.Inline
|
|
795
833
|
};
|
|
796
834
|
break;
|
|
797
835
|
default:
|
|
798
836
|
console.error('Unknown event type', event);
|
|
799
837
|
}
|
|
800
|
-
|
|
801
|
-
if (message) {
|
|
802
|
-
message.id = event.uuid;
|
|
803
|
-
message.date = new Date(event.created_on);
|
|
804
|
-
}
|
|
805
|
-
|
|
806
|
-
return message;
|
|
807
|
-
}
|
|
808
|
-
|
|
809
|
-
private getUserForEvent(event: MsgEvent | TicketEvent) {
|
|
810
|
-
if (event.type === 'msg_received') {
|
|
811
|
-
return {
|
|
812
|
-
name: this.currentContact.name
|
|
813
|
-
};
|
|
814
|
-
} else if (event._user) {
|
|
815
|
-
return event._user;
|
|
816
|
-
}
|
|
817
|
-
return null;
|
|
818
838
|
}
|
|
819
839
|
|
|
820
|
-
private
|
|
821
|
-
return {
|
|
822
|
-
id: msgEvent.uuid,
|
|
823
|
-
type: msgEvent.type === 'msg_received' ? 'msg_in' : 'msg_out',
|
|
824
|
-
user: this.getUserForEvent(msgEvent),
|
|
825
|
-
date: new Date(msgEvent.created_on),
|
|
826
|
-
attachments: msgEvent.msg.attachments,
|
|
827
|
-
text: msgEvent.msg.text,
|
|
828
|
-
sendError:
|
|
829
|
-
msgEvent._status &&
|
|
830
|
-
(msgEvent._status.status === 'errored' ||
|
|
831
|
-
msgEvent._status.status === 'failed'),
|
|
832
|
-
popup: html`<div
|
|
833
|
-
style="display: flex; flex-direction: row; align-items:center; justify-content: space-between;font-size:0.9em;line-height:1em;min-width:10em"
|
|
834
|
-
>
|
|
835
|
-
<div style="justify-content:left;text-align:left">
|
|
836
|
-
<temba-date
|
|
837
|
-
value=${msgEvent.created_on}
|
|
838
|
-
display="duration"
|
|
839
|
-
></temba-date>
|
|
840
|
-
|
|
841
|
-
${msgEvent.optin
|
|
842
|
-
? html`<div style="font-size:0.9em;color:#aaa">
|
|
843
|
-
${msgEvent.optin.name}
|
|
844
|
-
</div>`
|
|
845
|
-
: null}
|
|
846
|
-
</div>
|
|
847
|
-
${msgEvent._logs_url
|
|
848
|
-
? html`<a style="margin-left:0.5em" href="${msgEvent._logs_url}"
|
|
849
|
-
><temba-icon name="log"></temba-icon
|
|
850
|
-
></a>`
|
|
851
|
-
: null}
|
|
852
|
-
</div> `
|
|
853
|
-
};
|
|
854
|
-
}
|
|
855
|
-
|
|
856
|
-
private createMessages(page: ContactHistoryPage): ChatEvent[] {
|
|
840
|
+
private createMessages(page: ContactHistoryPage): ContactEvent[] {
|
|
857
841
|
if (page.events) {
|
|
858
|
-
|
|
842
|
+
const messages: ContactEvent[] = [];
|
|
859
843
|
page.events.forEach((event) => {
|
|
860
|
-
|
|
861
|
-
if (
|
|
862
|
-
this.
|
|
844
|
+
// track the UUID of the newest event for polling
|
|
845
|
+
if (
|
|
846
|
+
!this.afterUUID ||
|
|
847
|
+
event.uuid.toLowerCase() > this.afterUUID.toLowerCase()
|
|
848
|
+
) {
|
|
849
|
+
this.afterUUID = event.uuid;
|
|
863
850
|
}
|
|
864
851
|
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
messages.push({
|
|
868
|
-
type: MessageType.Note,
|
|
869
|
-
id: event.created_on + event.type,
|
|
870
|
-
user: this.getUserForEvent(ticketEvent),
|
|
871
|
-
date: new Date(ticketEvent.created_on),
|
|
872
|
-
text: ticketEvent.note
|
|
873
|
-
});
|
|
874
|
-
} else if (event.type === 'ticket_opened') {
|
|
875
|
-
// ticket open events can have a note attached
|
|
876
|
-
const ticketEvent = event as TicketEvent;
|
|
877
|
-
messages.push({
|
|
878
|
-
type: MessageType.Note,
|
|
879
|
-
id: event.created_on + event.type + '_note',
|
|
880
|
-
user: this.getUserForEvent(ticketEvent),
|
|
881
|
-
date: new Date(ticketEvent.created_on),
|
|
882
|
-
text: ticketEvent.note
|
|
883
|
-
});
|
|
852
|
+
// convert to dates
|
|
853
|
+
event.created_on = new Date(event.created_on);
|
|
884
854
|
|
|
885
|
-
|
|
886
|
-
messages.push(this.getEventMessage(event));
|
|
887
|
-
} else if (
|
|
855
|
+
if (
|
|
888
856
|
event.type === 'msg_created' ||
|
|
889
857
|
event.type === 'msg_received' ||
|
|
890
858
|
event.type === 'ivr_created'
|
|
891
859
|
) {
|
|
892
|
-
|
|
893
|
-
messages.push(this.createChatForMessageEvent(msgEvent));
|
|
860
|
+
messages.push(event);
|
|
894
861
|
} else {
|
|
895
|
-
|
|
896
|
-
if (
|
|
897
|
-
messages.push(
|
|
862
|
+
this.prerender(event);
|
|
863
|
+
if (event._rendered) {
|
|
864
|
+
messages.push(event);
|
|
898
865
|
}
|
|
899
866
|
}
|
|
900
867
|
});
|
|
901
868
|
|
|
902
869
|
// remove any messages we don't recognize
|
|
903
|
-
|
|
904
|
-
return messages as ChatEvent[];
|
|
870
|
+
return messages.filter((msg) => !!msg);
|
|
905
871
|
}
|
|
906
872
|
return [];
|
|
907
873
|
}
|
|
@@ -913,9 +879,9 @@ export class ContactChat extends ContactStoreElement {
|
|
|
913
879
|
}
|
|
914
880
|
|
|
915
881
|
const chat = this.chat;
|
|
916
|
-
|
|
917
|
-
if (this.currentContact && this.newestEventTime) {
|
|
882
|
+
if (this.currentContact && this.afterUUID) {
|
|
918
883
|
this.polling = true;
|
|
884
|
+
this.lastFetchTime = Date.now();
|
|
919
885
|
const endpoint = this.getEndpoint();
|
|
920
886
|
if (!endpoint) {
|
|
921
887
|
return;
|
|
@@ -924,23 +890,21 @@ export class ContactChat extends ContactStoreElement {
|
|
|
924
890
|
const fetchContact = this.currentContact.uuid;
|
|
925
891
|
|
|
926
892
|
fetchContactHistory(
|
|
927
|
-
false,
|
|
928
893
|
endpoint,
|
|
929
894
|
this.currentTicket?.uuid,
|
|
930
895
|
null,
|
|
931
|
-
this.
|
|
896
|
+
this.afterUUID
|
|
932
897
|
).then((page: ContactHistoryPage) => {
|
|
898
|
+
const messages = this.createMessages(page);
|
|
899
|
+
messages.reverse();
|
|
933
900
|
if (fetchContact === this.currentContact.uuid) {
|
|
934
|
-
|
|
935
|
-
const messages = this.createMessages(page);
|
|
936
|
-
if (messages.length === 0) {
|
|
937
|
-
contactChat.blockFetching = true;
|
|
938
|
-
}
|
|
939
|
-
messages.reverse();
|
|
901
|
+
const hasNewEvents = messages.length > 0;
|
|
940
902
|
chat.addMessages(messages, null, true);
|
|
903
|
+
this.polling = false;
|
|
904
|
+
this.scheduleRefresh(hasNewEvents);
|
|
905
|
+
} else {
|
|
906
|
+
this.polling = false;
|
|
941
907
|
}
|
|
942
|
-
this.polling = false;
|
|
943
|
-
this.scheduleRefresh();
|
|
944
908
|
});
|
|
945
909
|
}
|
|
946
910
|
}
|
|
@@ -959,19 +923,37 @@ export class ContactChat extends ContactStoreElement {
|
|
|
959
923
|
return;
|
|
960
924
|
}
|
|
961
925
|
|
|
926
|
+
// initialize anchor UUID if not set (first fetch)
|
|
927
|
+
if (!this.beforeUUID && !this.afterUUID) {
|
|
928
|
+
// generate a UUID v7 for current time as the anchor
|
|
929
|
+
const anchorUUID = generateUUIDv7();
|
|
930
|
+
this.beforeUUID = anchorUUID;
|
|
931
|
+
this.afterUUID = anchorUUID;
|
|
932
|
+
}
|
|
933
|
+
|
|
962
934
|
fetchContactHistory(
|
|
963
|
-
false,
|
|
964
935
|
endpoint,
|
|
965
936
|
this.currentTicket?.uuid,
|
|
966
|
-
this.
|
|
937
|
+
this.beforeUUID,
|
|
938
|
+
null
|
|
967
939
|
).then((page: ContactHistoryPage) => {
|
|
968
|
-
this.lastEventTime = page.next_before;
|
|
969
940
|
const messages = this.createMessages(page);
|
|
970
941
|
messages.reverse();
|
|
971
942
|
|
|
972
943
|
if (messages.length === 0) {
|
|
973
944
|
contactChat.blockFetching = true;
|
|
945
|
+
} else if (page.next) {
|
|
946
|
+
// update beforeUUID for next fetch of older messages
|
|
947
|
+
this.beforeUUID = page.next;
|
|
948
|
+
} else {
|
|
949
|
+
// no more history, mark end and show oldest event date
|
|
950
|
+
contactChat.blockFetching = true;
|
|
951
|
+
if (page.events && page.events.length > 0) {
|
|
952
|
+
const oldestEvent = page.events[page.events.length - 1];
|
|
953
|
+
chat.setEndOfHistory(new Date(oldestEvent.created_on));
|
|
954
|
+
}
|
|
974
955
|
}
|
|
956
|
+
|
|
975
957
|
chat.addMessages(messages);
|
|
976
958
|
this.scheduleRefresh();
|
|
977
959
|
});
|
|
@@ -1087,15 +1069,13 @@ export class ContactChat extends ContactStoreElement {
|
|
|
1087
1069
|
if (this.currentTicket) {
|
|
1088
1070
|
fetchResults(`/api/v2/tickets.json?uuid=${this.currentTicket.uuid}`).then(
|
|
1089
1071
|
(values) => {
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
}
|
|
1098
|
-
});
|
|
1072
|
+
if (values.length > 0) {
|
|
1073
|
+
this.fireCustomEvent(CustomEventType.TicketUpdated, {
|
|
1074
|
+
ticket: values[0],
|
|
1075
|
+
previous: this.currentTicket
|
|
1076
|
+
});
|
|
1077
|
+
this.currentTicket = values[0];
|
|
1078
|
+
}
|
|
1099
1079
|
}
|
|
1100
1080
|
);
|
|
1101
1081
|
}
|
|
@@ -1142,6 +1122,7 @@ export class ContactChat extends ContactStoreElement {
|
|
|
1142
1122
|
@temba-fetch-complete=${this.fetchComplete}
|
|
1143
1123
|
avatar=${this.avatar}
|
|
1144
1124
|
agent
|
|
1125
|
+
?hasFooter=${inFlow}
|
|
1145
1126
|
>
|
|
1146
1127
|
${inFlow
|
|
1147
1128
|
? html`
|
|
@@ -1238,37 +1219,33 @@ export const fetchContact = (endpoint: string): Promise<Contact> => {
|
|
|
1238
1219
|
});
|
|
1239
1220
|
};
|
|
1240
1221
|
export const fetchContactHistory = (
|
|
1241
|
-
reset: boolean,
|
|
1242
1222
|
endpoint: string,
|
|
1243
|
-
ticket: string,
|
|
1244
|
-
before:
|
|
1245
|
-
after:
|
|
1223
|
+
ticket: string = undefined,
|
|
1224
|
+
before: string = undefined,
|
|
1225
|
+
after: string = undefined
|
|
1246
1226
|
): Promise<ContactHistoryPage> => {
|
|
1247
|
-
if (reset) {
|
|
1248
|
-
pendingRequests.forEach((controller) => {
|
|
1249
|
-
controller.abort();
|
|
1250
|
-
});
|
|
1251
|
-
pendingRequests = [];
|
|
1252
|
-
}
|
|
1253
|
-
|
|
1254
1227
|
return new Promise<ContactHistoryPage>((resolve) => {
|
|
1255
1228
|
const controller = new AbortController();
|
|
1256
1229
|
pendingRequests.push(controller);
|
|
1257
1230
|
|
|
1258
1231
|
let url = endpoint;
|
|
1232
|
+
const params = [];
|
|
1233
|
+
|
|
1259
1234
|
if (before) {
|
|
1260
|
-
|
|
1235
|
+
params.push(`before=${before}`);
|
|
1261
1236
|
}
|
|
1262
1237
|
|
|
1263
1238
|
if (after) {
|
|
1264
|
-
|
|
1239
|
+
params.push(`after=${after}`);
|
|
1265
1240
|
}
|
|
1266
1241
|
|
|
1267
1242
|
if (ticket) {
|
|
1268
|
-
|
|
1243
|
+
params.push(`ticket=${ticket}`);
|
|
1269
1244
|
}
|
|
1270
1245
|
|
|
1271
|
-
|
|
1246
|
+
if (params.length > 0) {
|
|
1247
|
+
url += (url.includes('?') ? '&' : '?') + params.join('&');
|
|
1248
|
+
}
|
|
1272
1249
|
|
|
1273
1250
|
getUrl(url, controller)
|
|
1274
1251
|
.then((response: WebResponse) => {
|
|
@@ -1279,10 +1256,7 @@ export const fetchContactHistory = (
|
|
|
1279
1256
|
}
|
|
1280
1257
|
);
|
|
1281
1258
|
|
|
1282
|
-
|
|
1283
|
-
store.resolveUsers(page.events, ['created_by']).then(() => {
|
|
1284
|
-
resolve(page);
|
|
1285
|
-
});
|
|
1259
|
+
resolve(response.json as ContactHistoryPage);
|
|
1286
1260
|
})
|
|
1287
1261
|
.catch(() => {
|
|
1288
1262
|
// canceled
|