@meetelise/chat 1.5.0 → 1.6.2
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/package.json +1 -2
- package/public/demo/index.html +1 -3
- package/public/dist/bundle.js +1 -1
- package/public/dist/bundle.js.LICENSE.txt +0 -6
- package/public/ts-loader-build/ChatBubble.d.ts +12 -0
- package/public/ts-loader-build/MEChat.d.ts +2 -5
- package/public/ts-loader-build/createConversation.d.ts +1 -1
- package/public/ts-loader-build/fetchBuildingInfo.d.ts +8 -9
- package/public/ts-loader-build/utils.d.ts +0 -1
- package/src/DemoApp.tsx +18 -27
- package/src/MEChat.module.scss +50 -15
- package/src/MEChat.test.ts +16 -26
- package/src/MEChat.tsx +84 -89
- package/src/createConversation.ts +5 -28
- package/src/fetchBuildingInfo.ts +8 -10
- package/src/utils.ts +0 -3
- package/public/ts-loader-build/InHouseLauncher.d.ts +0 -10
- package/public/ts-loader-build/themes.d.ts +0 -179
- package/src/InHouseLauncher.module.scss +0 -120
- package/src/InHouseLauncher.tsx +0 -59
- package/src/themes.ts +0 -88
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
interface ChatBubbleProps {
|
|
3
|
+
messages: {
|
|
4
|
+
title: string;
|
|
5
|
+
text: string;
|
|
6
|
+
}[];
|
|
7
|
+
triggerBounce: () => void;
|
|
8
|
+
bounceIntervalInSeconds: number;
|
|
9
|
+
onClick: () => void;
|
|
10
|
+
}
|
|
11
|
+
declare const ChatBubble: React.FunctionComponent<ChatBubbleProps>;
|
|
12
|
+
export default ChatBubble;
|
|
@@ -46,9 +46,8 @@ export default class MEChat {
|
|
|
46
46
|
show(): void;
|
|
47
47
|
/** Hide the chat button from the screen (but don't remove from the DOM). */
|
|
48
48
|
hide(): void;
|
|
49
|
-
/** Show a
|
|
50
|
-
private
|
|
51
|
-
private getInHouseLauncher;
|
|
49
|
+
/** Show a speech bubble next to the chat button (launcher). Also adds some animations to the button. */
|
|
50
|
+
private addChatBubble;
|
|
52
51
|
private buildingSlug;
|
|
53
52
|
private orgSlug;
|
|
54
53
|
private popup;
|
|
@@ -58,8 +57,6 @@ export default class MEChat {
|
|
|
58
57
|
private chatId;
|
|
59
58
|
private analytics;
|
|
60
59
|
private launchDarklyClient;
|
|
61
|
-
private useInHouseLauncher;
|
|
62
|
-
private isMobile;
|
|
63
60
|
private constructor();
|
|
64
61
|
}
|
|
65
62
|
export interface Options {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import Talk from "talkjs";
|
|
2
2
|
import { Building } from "./fetchBuildingInfo";
|
|
3
3
|
import { Theme } from "./resolveTheme";
|
|
4
|
-
export default function createConversation(session: Talk.Session, building: Building, theme: Theme, chatID: string
|
|
4
|
+
export default function createConversation(session: Talk.Session, building: Building, theme: Theme, chatID: string): Talk.ConversationBuilder;
|
|
@@ -3,13 +3,20 @@
|
|
|
3
3
|
*/
|
|
4
4
|
export interface Building {
|
|
5
5
|
id: number;
|
|
6
|
-
themeId: string;
|
|
7
6
|
avatarInitials: string | null;
|
|
8
7
|
avatarSrc: string | null;
|
|
9
8
|
avatarType: "image" | "initials" | null;
|
|
9
|
+
backgroundColor: string | null;
|
|
10
|
+
bannerColor: string | null;
|
|
11
|
+
bannerTextColor: string | null;
|
|
10
12
|
chatSubtitle: string | null;
|
|
11
13
|
chatTitle: string | null;
|
|
14
|
+
launchButtonColor: string | null;
|
|
15
|
+
launchButtonIconColor: string | null;
|
|
16
|
+
launchButtonSize: string | null;
|
|
12
17
|
logoSrc: string | null;
|
|
18
|
+
messageColor: string | null;
|
|
19
|
+
messageTextColor: string | null;
|
|
13
20
|
name: string;
|
|
14
21
|
primaryColor: string | null;
|
|
15
22
|
userFirstName: string;
|
|
@@ -18,14 +25,6 @@ export interface Building {
|
|
|
18
25
|
welcomeMessage: string | null;
|
|
19
26
|
conversationMaintenanceMode: boolean;
|
|
20
27
|
orgId: number;
|
|
21
|
-
backgroundColor: string | null;
|
|
22
|
-
bannerColor: string | null;
|
|
23
|
-
bannerTextColor: string | null;
|
|
24
|
-
launchButtonColor: string | null;
|
|
25
|
-
launchButtonIconColor: string | null;
|
|
26
|
-
launchButtonSize: string | null;
|
|
27
|
-
messageColor: string | null;
|
|
28
|
-
messageTextColor: string | null;
|
|
29
28
|
}
|
|
30
29
|
/**
|
|
31
30
|
* Load the publicly-available info for a building.
|
package/src/DemoApp.tsx
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { useEffect
|
|
1
|
+
import React, { useEffect } from "react";
|
|
2
2
|
import ReactDOM from "react-dom";
|
|
3
3
|
import { debounce } from "lodash";
|
|
4
4
|
import MEChat from "./MEChat";
|
|
@@ -55,7 +55,6 @@ const DemoApp = () => {
|
|
|
55
55
|
}
|
|
56
56
|
}, []);
|
|
57
57
|
|
|
58
|
-
const [showCookieBanner, setShowCookieBanner] = useState(true);
|
|
59
58
|
return (
|
|
60
59
|
<>
|
|
61
60
|
<h1>Example Page</h1>
|
|
@@ -81,31 +80,23 @@ const DemoApp = () => {
|
|
|
81
80
|
name="launchButtonIconColor"
|
|
82
81
|
/>
|
|
83
82
|
</form>
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
>
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
<div
|
|
102
|
-
style={{ position: "absolute", top: "1rem", right: "1rem" }}
|
|
103
|
-
onClick={() => setShowCookieBanner(false)}
|
|
104
|
-
>
|
|
105
|
-
✕
|
|
106
|
-
</div>
|
|
107
|
-
</div>
|
|
108
|
-
)}
|
|
83
|
+
<div
|
|
84
|
+
id="bottomBanner"
|
|
85
|
+
style={{
|
|
86
|
+
position: "absolute",
|
|
87
|
+
bottom: 0,
|
|
88
|
+
left: 0,
|
|
89
|
+
width: "100%",
|
|
90
|
+
height: "20vh",
|
|
91
|
+
backgroundColor: "lightpink",
|
|
92
|
+
textAlign: "center",
|
|
93
|
+
}}
|
|
94
|
+
>
|
|
95
|
+
<p style={{ marginTop: "5em" }}>
|
|
96
|
+
Hi, I'm a banner that appears at the bottom of the screen! I eat chat
|
|
97
|
+
widgets for breakfast and I'll eat yours if you aren't careful!
|
|
98
|
+
</p>
|
|
99
|
+
</div>
|
|
109
100
|
</>
|
|
110
101
|
);
|
|
111
102
|
};
|
package/src/MEChat.module.scss
CHANGED
|
@@ -1,23 +1,58 @@
|
|
|
1
|
+
.wrapper {
|
|
2
|
+
position: fixed;
|
|
3
|
+
display: flex;
|
|
4
|
+
bottom: 10vh;
|
|
5
|
+
right: 10vh;
|
|
6
|
+
z-index: 100000;
|
|
7
|
+
}
|
|
8
|
+
|
|
1
9
|
:global(#__talkjs_launcher):not(.shouldBeVisible) {
|
|
2
10
|
display: none;
|
|
3
11
|
}
|
|
4
12
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
:
|
|
8
|
-
|
|
13
|
+
:global(a#__talkjs_launcher) {
|
|
14
|
+
box-shadow: 0px 6px 8px rgba(0, 0, 0, 0.25);
|
|
15
|
+
background-position: 50% 50%;
|
|
16
|
+
background-size: 27px 27px;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
:global(a#__talkjs_launcher).bouncingLauncherButton {
|
|
20
|
+
animation-name: bounce;
|
|
21
|
+
animation-duration: 1s;
|
|
22
|
+
animation-timing-function: cubic-bezier(0.28, 0.84, 0.42, 1);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/* https://css-tricks.com/making-css-animations-feel-natural/ */
|
|
26
|
+
@keyframes bounce {
|
|
27
|
+
0% {
|
|
28
|
+
transform: scale(1, 1) translateY(0);
|
|
29
|
+
}
|
|
30
|
+
10% {
|
|
31
|
+
transform: scale(1.1, 0.9) translateY(0);
|
|
32
|
+
}
|
|
33
|
+
30% {
|
|
34
|
+
transform: scale(0.9, 1.1) translateY(-100px);
|
|
35
|
+
}
|
|
36
|
+
50% {
|
|
37
|
+
transform: scale(1.05, 0.95) translateY(0);
|
|
38
|
+
}
|
|
39
|
+
57% {
|
|
40
|
+
transform: scale(1, 1) translateY(-7px);
|
|
41
|
+
}
|
|
42
|
+
64% {
|
|
43
|
+
transform: scale(1, 1) translateY(0);
|
|
44
|
+
}
|
|
45
|
+
100% {
|
|
46
|
+
transform: scale(1, 1) translateY(0);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.fadeOut {
|
|
51
|
+
animation: fadeOut 0.5s;
|
|
9
52
|
}
|
|
10
53
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
width: 100px;
|
|
15
|
-
height: 100px;
|
|
16
|
-
position: relative;
|
|
17
|
-
overflow: hidden;
|
|
18
|
-
}
|
|
19
|
-
&.mobile {
|
|
20
|
-
width: 100%;
|
|
21
|
-
height: 100px;
|
|
54
|
+
@keyframes fadeOut {
|
|
55
|
+
to {
|
|
56
|
+
opacity: 0;
|
|
22
57
|
}
|
|
23
58
|
}
|
package/src/MEChat.test.ts
CHANGED
|
@@ -2,25 +2,14 @@ import { expect } from "@esm-bundle/chai";
|
|
|
2
2
|
import { stub, restore } from "sinon/pkg/sinon-esm";
|
|
3
3
|
import MEChat from "../public/dist/bundle";
|
|
4
4
|
|
|
5
|
-
const TIMEOUT = 15000;
|
|
6
|
-
|
|
7
5
|
const stubResponse = {
|
|
8
6
|
json: stub().resolves({
|
|
9
7
|
id: 42,
|
|
10
|
-
userId: 42.1,
|
|
11
|
-
orgId: 42.2,
|
|
12
8
|
name: "Unit Test Building",
|
|
13
|
-
|
|
14
|
-
avatarSrc: null,
|
|
15
|
-
avatarType: "",
|
|
9
|
+
launchButtonColor: "rgb(180, 190, 0)",
|
|
16
10
|
userFirstName: "Ella",
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
"https://eliseusercontent.meetelise.com/building/3660/test-logo.png",
|
|
20
|
-
chatTitle: "Elise",
|
|
21
|
-
chatSubtitle: "Leasing Agent ExtraordinAIre",
|
|
22
|
-
welcomeMessage:
|
|
23
|
-
"Welcome, I'm Elise! If you have any questions about Unit Test Building or would like to schedule a tour, I'm happy to help.",
|
|
11
|
+
userId: 42.1,
|
|
12
|
+
orgId: 42.2,
|
|
24
13
|
conversationMaintenanceMode: false,
|
|
25
14
|
}),
|
|
26
15
|
};
|
|
@@ -47,8 +36,7 @@ afterEach(() => {
|
|
|
47
36
|
restore();
|
|
48
37
|
});
|
|
49
38
|
|
|
50
|
-
it
|
|
51
|
-
this.timeout(TIMEOUT);
|
|
39
|
+
it("shows the chat icon", async function () {
|
|
52
40
|
// Given an API that returns this building theme
|
|
53
41
|
stub(window, "fetch").resolves(stubResponse);
|
|
54
42
|
|
|
@@ -57,12 +45,16 @@ it.skip("shows the launcher", async function (done) {
|
|
|
57
45
|
organization: "unit-test-org",
|
|
58
46
|
building: "unit-test-building",
|
|
59
47
|
});
|
|
60
|
-
await waitForElementWithSelectorToExist(".
|
|
48
|
+
await waitForElementWithSelectorToExist(".__talkjs_launcher");
|
|
61
49
|
|
|
62
|
-
// Then I should see a launcher
|
|
63
|
-
const launcher =
|
|
64
|
-
".
|
|
65
|
-
).
|
|
50
|
+
// Then I should see a launcher with the right color
|
|
51
|
+
const launcher =
|
|
52
|
+
document.querySelector<HTMLAnchorElement>(".__talkjs_launcher");
|
|
53
|
+
expect(launcher).to.be.an.instanceof(HTMLAnchorElement);
|
|
54
|
+
// TODO: temporarily making the launcher always white because the new icon doesn't look good with all colors
|
|
55
|
+
// const launcherStyle = window.getComputedStyle(launcher);
|
|
56
|
+
// const launcherBG = launcherStyle.getPropertyValue("background-color");
|
|
57
|
+
// expect(launcherBG).to.equal("rgb(180, 190, 0)");
|
|
66
58
|
|
|
67
59
|
// And the popup should be hidden
|
|
68
60
|
const popup = document.querySelector<HTMLSpanElement>(".__talkjs_popup");
|
|
@@ -78,14 +70,12 @@ it.skip("shows the launcher", async function (done) {
|
|
|
78
70
|
const popupStyle2 = window.getComputedStyle(popup);
|
|
79
71
|
const popupDisplay2 = popupStyle2.getPropertyValue("display");
|
|
80
72
|
expect(popupDisplay2).not.to.equal("none");
|
|
81
|
-
|
|
73
|
+
|
|
82
74
|
// Ideally, expect welcome message, but we can't select inside the iframe
|
|
83
75
|
// Ideally, expect theme colors, but we can't select inside the iframe
|
|
84
76
|
});
|
|
85
77
|
|
|
86
|
-
it
|
|
87
|
-
this.timeout(TIMEOUT);
|
|
88
|
-
|
|
78
|
+
it("works via the programmatic interface", async () => {
|
|
89
79
|
// Given an API that returns this building theme
|
|
90
80
|
stub(window, "fetch").resolves(stubResponse);
|
|
91
81
|
|
|
@@ -95,7 +85,7 @@ it.skip("works via the programmatic interface", async function () {
|
|
|
95
85
|
building: "unit-test-building",
|
|
96
86
|
});
|
|
97
87
|
|
|
98
|
-
await waitForElementWithSelectorToExist(".
|
|
88
|
+
await waitForElementWithSelectorToExist(".__talkjs_launcher");
|
|
99
89
|
|
|
100
90
|
// Ideally, verify behavior, but this will at least verify nothing throws
|
|
101
91
|
chat.show();
|
package/src/MEChat.tsx
CHANGED
|
@@ -1,19 +1,16 @@
|
|
|
1
|
-
import * as LDClient from "launchdarkly-js-client-sdk";
|
|
2
|
-
import React from "react";
|
|
3
|
-
import ReactDOM from "react-dom";
|
|
4
1
|
import Talk from "talkjs";
|
|
5
|
-
|
|
6
2
|
import fetchBuildingInfo, { Building } from "./fetchBuildingInfo";
|
|
7
3
|
import { getChatID, createChatID } from "./chatID";
|
|
8
4
|
import createConversation from "./createConversation";
|
|
9
5
|
import installTalkJSStyles from "./installTalkJSStyles";
|
|
10
6
|
import resolveTheme, { Theme } from "./resolveTheme";
|
|
11
7
|
import Analytics from "./analytics";
|
|
12
|
-
import
|
|
13
|
-
import
|
|
8
|
+
import ChatBubble from "./ChatBubble";
|
|
9
|
+
import ReactDOM from "react-dom";
|
|
10
|
+
import React from "react";
|
|
11
|
+
import * as LDClient from "launchdarkly-js-client-sdk";
|
|
14
12
|
import LaunchDarkly from "./launchDarklyManager";
|
|
15
13
|
import styles from "./MEChat.module.scss";
|
|
16
|
-
import { defaultThemeId, themesById } from "./themes";
|
|
17
14
|
|
|
18
15
|
/**
|
|
19
16
|
* The interface to MeetElise chat.
|
|
@@ -74,8 +71,7 @@ export default class MEChat {
|
|
|
74
71
|
session,
|
|
75
72
|
building,
|
|
76
73
|
resolveTheme(building, this.theme),
|
|
77
|
-
this.chatId
|
|
78
|
-
this.isMobile
|
|
74
|
+
this.chatId
|
|
79
75
|
)
|
|
80
76
|
);
|
|
81
77
|
}
|
|
@@ -101,13 +97,7 @@ export default class MEChat {
|
|
|
101
97
|
...theme,
|
|
102
98
|
}));
|
|
103
99
|
popup.select(
|
|
104
|
-
createConversation(
|
|
105
|
-
session,
|
|
106
|
-
building,
|
|
107
|
-
resolvedTheme,
|
|
108
|
-
this.chatId,
|
|
109
|
-
this.isMobile
|
|
110
|
-
)
|
|
100
|
+
createConversation(session, building, resolvedTheme, this.chatId)
|
|
111
101
|
);
|
|
112
102
|
installTalkJSStyles(resolvedTheme);
|
|
113
103
|
return new Promise(requestAnimationFrame);
|
|
@@ -130,7 +120,6 @@ export default class MEChat {
|
|
|
130
120
|
}
|
|
131
121
|
|
|
132
122
|
/** Show the chat button on the screen if it was previously hidden. */
|
|
133
|
-
// TODO: will this work with the new launcher? it needs to be display flex? will this just change the inline style and leave the stylesheet/style tag alone?
|
|
134
123
|
show(): void {
|
|
135
124
|
this.launcher.then((a) => (a.style.display = ""));
|
|
136
125
|
}
|
|
@@ -140,57 +129,73 @@ export default class MEChat {
|
|
|
140
129
|
this.launcher.then((a) => (a.style.display = "none"));
|
|
141
130
|
}
|
|
142
131
|
|
|
143
|
-
/** Show a
|
|
144
|
-
private
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
132
|
+
/** Show a speech bubble next to the chat button (launcher). Also adds some animations to the button. */
|
|
133
|
+
private addChatBubble(popup: Talk.Popup, launcher: HTMLAnchorElement): void {
|
|
134
|
+
const chatBubbleTarget = document.createElement("div");
|
|
135
|
+
// set up scroll listener before mounting the chat bubble component so we don't miss any scroll events
|
|
136
|
+
const closeChatBubble = (shouldFade = false) => {
|
|
137
|
+
if (shouldFade) {
|
|
138
|
+
chatBubbleTarget.classList.add(styles.fadeOut);
|
|
139
|
+
setTimeout(() => {
|
|
140
|
+
ReactDOM.unmountComponentAtNode(chatBubbleTarget);
|
|
141
|
+
}, 500);
|
|
142
|
+
} else {
|
|
143
|
+
ReactDOM.unmountComponentAtNode(chatBubbleTarget);
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
// wrap the launcher and chat bubble so we can position them together but also manipulate them independently
|
|
147
|
+
const wrapper = document.createElement("div");
|
|
148
|
+
// for us, the wrapper contains the chat bubble and launcher. for consumers, we'll just call the wrapper the launcher.
|
|
149
|
+
wrapper.classList.add(styles.wrapper, "meetelise-chat", "launcher");
|
|
150
|
+
launcher.parentNode?.appendChild(wrapper);
|
|
151
|
+
wrapper.appendChild(launcher);
|
|
152
|
+
wrapper.appendChild(chatBubbleTarget);
|
|
153
|
+
// TalkJS positions the launcher, but we want to control its position ourselves
|
|
154
|
+
launcher.style.position = "unset";
|
|
155
|
+
launcher.style.top = "unset";
|
|
156
|
+
launcher.style.right = "unset";
|
|
157
|
+
// we initially hide the launcher in CSS so it doesn't visibly jump when we remove the native TalkJS positioning. Unhide it now.
|
|
158
|
+
launcher.classList.add(styles.shouldBeVisible);
|
|
159
|
+
|
|
160
|
+
popup.on("open", () => closeChatBubble());
|
|
161
|
+
const triggerBounce = () => {
|
|
162
|
+
launcher.classList.add(styles.bouncingLauncherButton);
|
|
163
|
+
launcher.addEventListener("animationend", () => {
|
|
164
|
+
launcher.classList.remove(styles.bouncingLauncherButton);
|
|
165
|
+
});
|
|
151
166
|
};
|
|
152
|
-
const
|
|
153
|
-
let theme = themesById[defaultThemeId];
|
|
154
|
-
if (Object.keys(themesById).includes(building.themeId)) {
|
|
155
|
-
theme = themesById[building.themeId as keyof typeof themesById];
|
|
156
|
-
}
|
|
167
|
+
const bounceInterval = 3;
|
|
157
168
|
ReactDOM.render(
|
|
158
|
-
<
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
169
|
+
<ChatBubble
|
|
170
|
+
messages={[
|
|
171
|
+
{
|
|
172
|
+
title: "Ask us a question",
|
|
173
|
+
text: "I can also help you schedule a tour.",
|
|
174
|
+
},
|
|
175
|
+
]}
|
|
176
|
+
triggerBounce={triggerBounce}
|
|
177
|
+
bounceIntervalInSeconds={bounceInterval}
|
|
178
|
+
onClick={() => {
|
|
179
|
+
popup.show();
|
|
180
|
+
closeChatBubble();
|
|
181
|
+
}}
|
|
164
182
|
/>,
|
|
165
|
-
|
|
183
|
+
chatBubbleTarget
|
|
166
184
|
);
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
const inHouseLauncherTarget = document.createElement("div");
|
|
171
|
-
inHouseLauncherTarget.classList.add(
|
|
172
|
-
styles.inHouseLauncherContainer,
|
|
173
|
-
this.isMobile ? styles.mobile : styles.desktop
|
|
174
|
-
);
|
|
175
|
-
document.body.appendChild(inHouseLauncherTarget);
|
|
176
|
-
this.mountInHouseLauncher(inHouseLauncherTarget, true);
|
|
177
|
-
(await this.popup).on("close", () => {
|
|
178
|
-
this.mountInHouseLauncher(inHouseLauncherTarget, false);
|
|
179
|
-
});
|
|
180
|
-
return inHouseLauncherTarget;
|
|
185
|
+
setTimeout(() => closeChatBubble(true), bounceInterval * 1000 + 3000);
|
|
186
|
+
// TODO: remove? it seems to be triggered immediately on https://www.simpsonpropertygroup.com/apartments/houston-texas/skyhouse-river-oaks-galleria without scrolling
|
|
187
|
+
// document.addEventListener("scroll", () => closeChatBubble(true));
|
|
181
188
|
}
|
|
182
189
|
|
|
183
190
|
private buildingSlug: string;
|
|
184
191
|
private orgSlug: string;
|
|
185
192
|
private popup: Promise<Talk.Popup>;
|
|
186
|
-
private launcher: Promise<
|
|
193
|
+
private launcher: Promise<HTMLAnchorElement>;
|
|
187
194
|
private building: Promise<Building>;
|
|
188
195
|
private theme: Partial<Theme>;
|
|
189
196
|
private chatId: string;
|
|
190
197
|
private analytics: Analytics;
|
|
191
198
|
private launchDarklyClient: LDClient.LDClient;
|
|
192
|
-
private useInHouseLauncher: boolean;
|
|
193
|
-
private isMobile: boolean;
|
|
194
199
|
|
|
195
200
|
private constructor({ organization, building, theme = {} }: Options) {
|
|
196
201
|
this.orgSlug = organization;
|
|
@@ -201,8 +206,6 @@ export default class MEChat {
|
|
|
201
206
|
this.analytics.ping("load");
|
|
202
207
|
this.theme = theme;
|
|
203
208
|
this.building = fetchBuildingInfo(organization, building);
|
|
204
|
-
this.useInHouseLauncher = true;
|
|
205
|
-
this.isMobile = isMobile();
|
|
206
209
|
|
|
207
210
|
this.popup = Promise.all([
|
|
208
211
|
this.building,
|
|
@@ -212,18 +215,7 @@ export default class MEChat {
|
|
|
212
215
|
const resolvedTheme = (this.theme = resolveTheme(building, theme));
|
|
213
216
|
installTalkJSStyles(resolvedTheme);
|
|
214
217
|
const p = session.createPopup(
|
|
215
|
-
createConversation(
|
|
216
|
-
session,
|
|
217
|
-
building,
|
|
218
|
-
resolvedTheme,
|
|
219
|
-
this.chatId,
|
|
220
|
-
this.isMobile
|
|
221
|
-
),
|
|
222
|
-
{
|
|
223
|
-
launcher: this.useInHouseLauncher ? "never" : "always",
|
|
224
|
-
showCloseInHeader: true,
|
|
225
|
-
messageField: { placeholder: "Ask a question..." },
|
|
226
|
-
}
|
|
218
|
+
createConversation(session, building, resolvedTheme, this.chatId)
|
|
227
219
|
);
|
|
228
220
|
p.on("open", () => {
|
|
229
221
|
this.analytics.ping("open");
|
|
@@ -241,35 +233,38 @@ export default class MEChat {
|
|
|
241
233
|
const talkjsPopupElement = document.querySelector(".__talkjs_popup");
|
|
242
234
|
if (!talkjsPopupElement) throw new Error("Failed to find chat window");
|
|
243
235
|
talkjsPopupElement.classList.add("meetelise-chat", "pane");
|
|
244
|
-
if (!this.isMobile) {
|
|
245
|
-
talkjsPopupElement.classList.add(styles.desktop);
|
|
246
|
-
}
|
|
247
236
|
return p;
|
|
248
237
|
});
|
|
249
238
|
|
|
250
239
|
this.launcher = Promise.all([this.popup, LaunchDarkly.isReady]).then(
|
|
251
|
-
async () => {
|
|
252
|
-
|
|
240
|
+
async ([popup]) => {
|
|
241
|
+
const talkjsLauncherElement = document.querySelector<HTMLAnchorElement>(
|
|
242
|
+
"a#__talkjs_launcher"
|
|
243
|
+
);
|
|
244
|
+
if (!talkjsLauncherElement)
|
|
245
|
+
throw new Error("MeetElise Chat: Could not locate launcher.");
|
|
246
|
+
|
|
247
|
+
const webchatBubbleFlag = this.launchDarklyClient.variation(
|
|
248
|
+
"webchat-bubble",
|
|
249
|
+
false
|
|
250
|
+
);
|
|
251
|
+
this.analytics.setFeatureFlags({
|
|
252
|
+
webchatBubble: webchatBubbleFlag,
|
|
253
|
+
});
|
|
254
|
+
this.analytics.ping("receivedFeatureFlags");
|
|
253
255
|
|
|
254
|
-
if (
|
|
255
|
-
// TODO:
|
|
256
|
-
|
|
256
|
+
if (webchatBubbleFlag) {
|
|
257
|
+
// TODO: The new icon hasn't been designed for color customization yet, so temporarily disable the background theme color
|
|
258
|
+
talkjsLauncherElement.style.backgroundColor = "white";
|
|
259
|
+
this.addChatBubble(popup, talkjsLauncherElement);
|
|
257
260
|
} else {
|
|
258
|
-
|
|
259
|
-
"
|
|
261
|
+
talkjsLauncherElement.classList.add(
|
|
262
|
+
"meetelise-chat",
|
|
263
|
+
"launcher",
|
|
264
|
+
styles.shouldBeVisible
|
|
260
265
|
);
|
|
261
|
-
if (!talkjsLauncherElement)
|
|
262
|
-
throw new Error("MeetElise Chat: Could not locate launcher.");
|
|
263
|
-
launcherElement = talkjsLauncherElement;
|
|
264
266
|
}
|
|
265
|
-
|
|
266
|
-
launcherElement.classList.add(
|
|
267
|
-
"meetelise-chat",
|
|
268
|
-
"launcher",
|
|
269
|
-
styles.shouldBeVisible
|
|
270
|
-
);
|
|
271
|
-
|
|
272
|
-
return launcherElement;
|
|
267
|
+
return talkjsLauncherElement;
|
|
273
268
|
}
|
|
274
269
|
);
|
|
275
270
|
}
|
|
@@ -2,17 +2,12 @@ import Talk from "talkjs";
|
|
|
2
2
|
import { Building } from "./fetchBuildingInfo";
|
|
3
3
|
import getAvatarUrl from "./getAvatarUrl";
|
|
4
4
|
import { Theme } from "./resolveTheme";
|
|
5
|
-
import { defaultThemeId, themesById } from "./themes";
|
|
6
|
-
|
|
7
|
-
const defaultAvatarUrl =
|
|
8
|
-
"https://s3.us-west-2.amazonaws.com/meetelise.com/looping-gradient.gif";
|
|
9
5
|
|
|
10
6
|
export default function createConversation(
|
|
11
7
|
session: Talk.Session,
|
|
12
8
|
building: Building,
|
|
13
9
|
theme: Theme,
|
|
14
|
-
chatID: string
|
|
15
|
-
isMobile: boolean
|
|
10
|
+
chatID: string
|
|
16
11
|
): Talk.ConversationBuilder {
|
|
17
12
|
const agent = new Talk.User({
|
|
18
13
|
id: `building_${building.id}`,
|
|
@@ -26,34 +21,16 @@ export default function createConversation(
|
|
|
26
21
|
conversation.subject = theme.chatTitle;
|
|
27
22
|
conversation.setParticipant(session.me);
|
|
28
23
|
conversation.setParticipant(agent);
|
|
29
|
-
// TODO: duplicate identifier theme
|
|
30
|
-
// TODO: typescript abuse
|
|
31
|
-
let themeId = defaultThemeId;
|
|
32
|
-
if (Object.keys(themesById).includes(building.themeId)) {
|
|
33
|
-
themeId = building.themeId as keyof typeof themesById;
|
|
34
|
-
}
|
|
35
|
-
const _theme = themesById[themeId];
|
|
36
24
|
conversation.custom = {
|
|
37
25
|
buildingId: building.id.toString(),
|
|
38
26
|
userId: building.userId.toString(),
|
|
39
27
|
orgId: building.orgId.toString(),
|
|
40
28
|
subtitle: theme.chatSubtitle ?? null,
|
|
29
|
+
bannerColor: theme.bannerColor,
|
|
30
|
+
bannerTextColor: theme.bannerTextColor,
|
|
31
|
+
messageColor: theme.messageColor,
|
|
32
|
+
messageTextColor: theme.messageTextColor,
|
|
41
33
|
url: location.href,
|
|
42
|
-
buildingName: building.name,
|
|
43
|
-
isMobile: isMobile.toString(),
|
|
44
|
-
chatHeaderBackgroundColor: _theme.chatHeader.backgroundColor,
|
|
45
|
-
chatHeaderTextColor: _theme.chatHeader.textColor,
|
|
46
|
-
chatPaneBackgroundColor: _theme.chatPaneBackgroundColor,
|
|
47
|
-
userMessageTextColor: _theme.message.user.textColor,
|
|
48
|
-
userMessageBackgroundColor: _theme.message.user.backgroundColor,
|
|
49
|
-
agentMessageTextColor: _theme.message.agent.textColor,
|
|
50
|
-
agentMessageBackgroundColor: _theme.message.agent.backgroundColor,
|
|
51
|
-
avatarUrl:
|
|
52
|
-
building.avatarType === "image" && building.avatarSrc
|
|
53
|
-
? building.avatarSrc
|
|
54
|
-
: defaultAvatarUrl,
|
|
55
|
-
// uncomment this to test changes to the default avatar if your test building has its own avatar
|
|
56
|
-
// avatarUrl: defaultAvatarUrl,
|
|
57
34
|
};
|
|
58
35
|
return conversation;
|
|
59
36
|
}
|
package/src/fetchBuildingInfo.ts
CHANGED
|
@@ -4,13 +4,20 @@
|
|
|
4
4
|
export interface Building {
|
|
5
5
|
id: number;
|
|
6
6
|
|
|
7
|
-
themeId: string;
|
|
8
7
|
avatarInitials: string | null;
|
|
9
8
|
avatarSrc: string | null;
|
|
10
9
|
avatarType: "image" | "initials" | null;
|
|
10
|
+
backgroundColor: string | null;
|
|
11
|
+
bannerColor: string | null;
|
|
12
|
+
bannerTextColor: string | null;
|
|
11
13
|
chatSubtitle: string | null;
|
|
12
14
|
chatTitle: string | null;
|
|
15
|
+
launchButtonColor: string | null;
|
|
16
|
+
launchButtonIconColor: string | null;
|
|
17
|
+
launchButtonSize: string | null;
|
|
13
18
|
logoSrc: string | null;
|
|
19
|
+
messageColor: string | null;
|
|
20
|
+
messageTextColor: string | null;
|
|
14
21
|
name: string;
|
|
15
22
|
primaryColor: string | null;
|
|
16
23
|
userFirstName: string;
|
|
@@ -19,15 +26,6 @@ export interface Building {
|
|
|
19
26
|
welcomeMessage: string | null;
|
|
20
27
|
conversationMaintenanceMode: boolean;
|
|
21
28
|
orgId: number;
|
|
22
|
-
// old: not sure if still present in API response, but we're not using (may have mised a few above)
|
|
23
|
-
backgroundColor: string | null;
|
|
24
|
-
bannerColor: string | null;
|
|
25
|
-
bannerTextColor: string | null;
|
|
26
|
-
launchButtonColor: string | null;
|
|
27
|
-
launchButtonIconColor: string | null;
|
|
28
|
-
launchButtonSize: string | null;
|
|
29
|
-
messageColor: string | null;
|
|
30
|
-
messageTextColor: string | null;
|
|
31
29
|
}
|
|
32
30
|
|
|
33
31
|
/**
|