@meetelise/chat 1.20.89 → 1.20.91
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 -1
- package/public/demo/index.html +4 -0
- package/public/dist/index.js +121 -14
- package/src/MyPubnub.ts +7 -2
- package/src/WebComponent/me-chat.ts +8 -1
- package/src/WebComponent/pubnub-chat-styles.ts +60 -2
- package/src/WebComponent/pubnub-chat.ts +20 -6
- package/src/WebComponent/pubnub-message.ts +225 -0
package/src/MyPubnub.ts
CHANGED
|
@@ -269,7 +269,7 @@ class MyPubnub {
|
|
|
269
269
|
buildingSlug: this.buildingSlug,
|
|
270
270
|
};
|
|
271
271
|
};
|
|
272
|
-
getChatStorageKey = (): ChatInfo => {
|
|
272
|
+
getChatStorageKey = (createNewIfNotExist = true): ChatInfo => {
|
|
273
273
|
const eliseaiLocalStorageValue = localStorage.getItem(
|
|
274
274
|
"com.eliseai.webchat.slug=" + this.buildingSlug
|
|
275
275
|
);
|
|
@@ -300,7 +300,12 @@ class MyPubnub {
|
|
|
300
300
|
}
|
|
301
301
|
}
|
|
302
302
|
|
|
303
|
-
return this.createChatStorageKey();
|
|
303
|
+
if (createNewIfNotExist) return this.createChatStorageKey();
|
|
304
|
+
return {
|
|
305
|
+
leadId: null,
|
|
306
|
+
timestamp: null,
|
|
307
|
+
buildingSlug: null,
|
|
308
|
+
};
|
|
304
309
|
};
|
|
305
310
|
isChatKeyValid = (storageValueDeconstructed: ChatInfo): boolean => {
|
|
306
311
|
if (
|
|
@@ -484,9 +484,16 @@ export class MEChat extends LitElement {
|
|
|
484
484
|
initializePubnubVariables = async (): Promise<void> => {
|
|
485
485
|
await this.setBuildingDerivedInfo();
|
|
486
486
|
if (!this.building) return;
|
|
487
|
+
if (this.building.conversationMaintenanceMode) {
|
|
488
|
+
// eslint-disable-next-line no-console
|
|
489
|
+
console.warn(
|
|
490
|
+
"MeetElise Chat is in maintenance mode. Chat icon will not appear."
|
|
491
|
+
);
|
|
492
|
+
return;
|
|
493
|
+
}
|
|
487
494
|
|
|
488
495
|
this.myPubnub = new MyPubnub(this.buildingSlug, this.building);
|
|
489
|
-
if (this.myPubnub.isChatKeyValid(this.myPubnub.getChatStorageKey())) {
|
|
496
|
+
if (this.myPubnub.isChatKeyValid(this.myPubnub.getChatStorageKey(false))) {
|
|
490
497
|
await this.myPubnub.initializePubnub();
|
|
491
498
|
}
|
|
492
499
|
this.attachOnClickToLauncher();
|
|
@@ -4,7 +4,7 @@ export const pubnubChatStyles = css`
|
|
|
4
4
|
#pubnub-chat-container {
|
|
5
5
|
position: fixed;
|
|
6
6
|
|
|
7
|
-
z-index:
|
|
7
|
+
z-index: 100001;
|
|
8
8
|
display: flex;
|
|
9
9
|
align-items: center;
|
|
10
10
|
|
|
@@ -116,9 +116,18 @@ export const pubnubChatStyles = css`
|
|
|
116
116
|
padding: 6px 12px;
|
|
117
117
|
line-height: 130%;
|
|
118
118
|
}
|
|
119
|
+
.displayed-message-image {
|
|
120
|
+
max-width: 100%;
|
|
121
|
+
max-height: 300px;
|
|
122
|
+
width: auto;
|
|
123
|
+
height: auto;
|
|
124
|
+
}
|
|
125
|
+
.redirect-link {
|
|
126
|
+
color: black;
|
|
127
|
+
}
|
|
119
128
|
|
|
120
129
|
#loading-message {
|
|
121
|
-
padding:
|
|
130
|
+
padding: 12px;
|
|
122
131
|
}
|
|
123
132
|
.loading-dot {
|
|
124
133
|
width: 6px;
|
|
@@ -169,6 +178,7 @@ export const pubnubChatStyles = css`
|
|
|
169
178
|
box-sizing: border-box;
|
|
170
179
|
gap: 16px;
|
|
171
180
|
padding: 24px;
|
|
181
|
+
z-index: 100001;
|
|
172
182
|
}
|
|
173
183
|
#message-input {
|
|
174
184
|
height: 40px;
|
|
@@ -180,6 +190,7 @@ export const pubnubChatStyles = css`
|
|
|
180
190
|
border: none;
|
|
181
191
|
color: white;
|
|
182
192
|
background: none;
|
|
193
|
+
z-index: 100001;
|
|
183
194
|
}
|
|
184
195
|
#message-input:focus {
|
|
185
196
|
outline: none;
|
|
@@ -191,4 +202,51 @@ export const pubnubChatStyles = css`
|
|
|
191
202
|
border: none;
|
|
192
203
|
cursor: pointer;
|
|
193
204
|
}
|
|
205
|
+
|
|
206
|
+
.website-preview {
|
|
207
|
+
border: 1px solid rgba(0, 0, 0, 0.2);
|
|
208
|
+
border-radius: 8px;
|
|
209
|
+
overflow: hidden;
|
|
210
|
+
display: flex;
|
|
211
|
+
align-items: center;
|
|
212
|
+
flex-direction: column;
|
|
213
|
+
justify-content: center;
|
|
214
|
+
|
|
215
|
+
margin: 8px;
|
|
216
|
+
}
|
|
217
|
+
.website-preview-image {
|
|
218
|
+
max-width: 100%;
|
|
219
|
+
max-height: 200px;
|
|
220
|
+
width: auto;
|
|
221
|
+
height: auto;
|
|
222
|
+
}
|
|
223
|
+
.website-preview-body {
|
|
224
|
+
padding: 8px 0px 12px;
|
|
225
|
+
}
|
|
226
|
+
.website-preview-title {
|
|
227
|
+
font-size: 12px;
|
|
228
|
+
font-weight: bold;
|
|
229
|
+
margin: 0;
|
|
230
|
+
display: -webkit-box;
|
|
231
|
+
-webkit-box-orient: vertical;
|
|
232
|
+
-webkit-line-clamp: 2;
|
|
233
|
+
overflow: hidden;
|
|
234
|
+
text-overflow: ellipsis;
|
|
235
|
+
height: 24px;
|
|
236
|
+
}
|
|
237
|
+
.website-preview-description {
|
|
238
|
+
font-size: 10px;
|
|
239
|
+
margin: 0;
|
|
240
|
+
|
|
241
|
+
display: -webkit-box;
|
|
242
|
+
-webkit-box-orient: vertical;
|
|
243
|
+
-webkit-line-clamp: 3;
|
|
244
|
+
overflow: hidden;
|
|
245
|
+
text-overflow: ellipsis;
|
|
246
|
+
max-height: 34px;
|
|
247
|
+
}
|
|
248
|
+
.redirect-link-preview {
|
|
249
|
+
color: inherit;
|
|
250
|
+
text-decoration: none;
|
|
251
|
+
}
|
|
194
252
|
`;
|
|
@@ -8,6 +8,7 @@ import { SendMessageIconWhite, XBlackOutlineIcon } from "../svgIcons";
|
|
|
8
8
|
import { defaultBrandColor } from "../themes";
|
|
9
9
|
import { hexToAlmostWhite } from "../utils";
|
|
10
10
|
import { pubnubChatStyles } from "./pubnub-chat-styles";
|
|
11
|
+
import "./pubnub-message";
|
|
11
12
|
|
|
12
13
|
@customElement("pubnub-chat")
|
|
13
14
|
export class PubnubChat extends LitElement {
|
|
@@ -49,6 +50,17 @@ export class PubnubChat extends LitElement {
|
|
|
49
50
|
@state()
|
|
50
51
|
isLoadingMessages = false;
|
|
51
52
|
|
|
53
|
+
@state()
|
|
54
|
+
websitePreviewMapping: {
|
|
55
|
+
[messageId: string]: {
|
|
56
|
+
title: string | null;
|
|
57
|
+
description: string | null;
|
|
58
|
+
image: string | null;
|
|
59
|
+
favicon: string | null;
|
|
60
|
+
isLoading: boolean;
|
|
61
|
+
}[];
|
|
62
|
+
} = {};
|
|
63
|
+
|
|
52
64
|
sendMessage = async (message: string): Promise<void> => {
|
|
53
65
|
this.messageInput.value = "";
|
|
54
66
|
await this.myPubnub?.sendMessage(message);
|
|
@@ -63,6 +75,7 @@ export class PubnubChat extends LitElement {
|
|
|
63
75
|
);
|
|
64
76
|
this.onMount();
|
|
65
77
|
}
|
|
78
|
+
|
|
66
79
|
scrollToChatBottom = (): void => {
|
|
67
80
|
this.messageBody.scrollTo({
|
|
68
81
|
top: this.messageBody.scrollHeight - this.messageBody.clientHeight,
|
|
@@ -70,7 +83,7 @@ export class PubnubChat extends LitElement {
|
|
|
70
83
|
});
|
|
71
84
|
};
|
|
72
85
|
|
|
73
|
-
updated(): void {
|
|
86
|
+
async updated(): Promise<void> {
|
|
74
87
|
this.scrollToChatBottom();
|
|
75
88
|
}
|
|
76
89
|
|
|
@@ -133,11 +146,12 @@ export class PubnubChat extends LitElement {
|
|
|
133
146
|
: undefined,
|
|
134
147
|
})}
|
|
135
148
|
>
|
|
136
|
-
<
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
149
|
+
<pubnub-message
|
|
150
|
+
.message=${message}
|
|
151
|
+
.myPubnub=${this.myPubnub}
|
|
152
|
+
.onMount=${() => this.scrollToChatBottom()}
|
|
153
|
+
>
|
|
154
|
+
</pubnub-message>
|
|
141
155
|
</li>
|
|
142
156
|
`;
|
|
143
157
|
})}
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
import { html, LitElement, TemplateResult } from "lit";
|
|
2
|
+
import { customElement, property, state } from "lit/decorators.js";
|
|
3
|
+
import MyPubnub, { ChatMessage } from "../MyPubnub";
|
|
4
|
+
import { pubnubChatStyles } from "./pubnub-chat-styles";
|
|
5
|
+
import { v4 as uuid } from "uuid";
|
|
6
|
+
import axios from "axios";
|
|
7
|
+
|
|
8
|
+
interface WebsitePreview {
|
|
9
|
+
title: string | null;
|
|
10
|
+
description: string | null;
|
|
11
|
+
image: string | null;
|
|
12
|
+
favicon: string | null;
|
|
13
|
+
link: string;
|
|
14
|
+
}
|
|
15
|
+
@customElement("pubnub-message")
|
|
16
|
+
export class PubnubMessage extends LitElement {
|
|
17
|
+
static styles = [pubnubChatStyles];
|
|
18
|
+
|
|
19
|
+
@property({ attribute: true })
|
|
20
|
+
message: ChatMessage | undefined;
|
|
21
|
+
|
|
22
|
+
@property({ attribute: true })
|
|
23
|
+
myPubnub: MyPubnub | undefined;
|
|
24
|
+
|
|
25
|
+
@property({ attribute: true })
|
|
26
|
+
onMount: () => void = () => ({});
|
|
27
|
+
|
|
28
|
+
@state()
|
|
29
|
+
loadingPreviews = true;
|
|
30
|
+
|
|
31
|
+
@state()
|
|
32
|
+
parsedMessage: TemplateResult | undefined;
|
|
33
|
+
|
|
34
|
+
@state()
|
|
35
|
+
websitePreviewInfo: WebsitePreview[] = [];
|
|
36
|
+
|
|
37
|
+
firstUpdated(): void {
|
|
38
|
+
if (!this.message) return;
|
|
39
|
+
const { hyperlinks, markedText } = this.mapAndMarkHyperLinks(
|
|
40
|
+
this.message.message.text
|
|
41
|
+
);
|
|
42
|
+
const urlRegex = /(https?:\/\/[^\s]+)/g; // Regular expression to match URLs
|
|
43
|
+
const imagesToDisplay: {
|
|
44
|
+
redirectTo: string;
|
|
45
|
+
source: string;
|
|
46
|
+
}[] = [];
|
|
47
|
+
|
|
48
|
+
const loadingWebsitePreviews: Promise<WebsitePreview>[] = [];
|
|
49
|
+
this.parsedMessage = html`${markedText.split("\n").map((line) => {
|
|
50
|
+
return html`${line.split(" ").map((word) => {
|
|
51
|
+
if (hyperlinks.find((obj) => obj.id === word)) {
|
|
52
|
+
// EliseAI made hyperlink
|
|
53
|
+
const link = hyperlinks.find((obj) => obj.id === word);
|
|
54
|
+
if (!link) return;
|
|
55
|
+
imagesToDisplay.push({
|
|
56
|
+
redirectTo: link?.link,
|
|
57
|
+
source: link?.link,
|
|
58
|
+
});
|
|
59
|
+
return html`<a
|
|
60
|
+
class="redirect-link"
|
|
61
|
+
href="${link?.link}"
|
|
62
|
+
target="_blank"
|
|
63
|
+
rel="noopener noreferrer"
|
|
64
|
+
>${link?.hyperlink}
|
|
65
|
+
</a>`;
|
|
66
|
+
}
|
|
67
|
+
if (urlRegex.test(word)) {
|
|
68
|
+
// general url
|
|
69
|
+
loadingWebsitePreviews.push(this.getLinkData(word));
|
|
70
|
+
return html`<a
|
|
71
|
+
class="redirect-link"
|
|
72
|
+
href="${word}"
|
|
73
|
+
target="_blank"
|
|
74
|
+
rel="noopener noreferrer"
|
|
75
|
+
>${word}
|
|
76
|
+
</a>`;
|
|
77
|
+
} else {
|
|
78
|
+
return html`${word} `;
|
|
79
|
+
}
|
|
80
|
+
})}<br />`;
|
|
81
|
+
})}
|
|
82
|
+
${imagesToDisplay.map((image) => {
|
|
83
|
+
return html`<a
|
|
84
|
+
href="${image.redirectTo}"
|
|
85
|
+
target="_blank"
|
|
86
|
+
rel="noopener noreferrer"
|
|
87
|
+
><img
|
|
88
|
+
class="displayed-message-image"
|
|
89
|
+
src=${image.source}
|
|
90
|
+
alt="displayed message image"
|
|
91
|
+
/></a>`;
|
|
92
|
+
})}`;
|
|
93
|
+
Promise.all(loadingWebsitePreviews).then((results) => {
|
|
94
|
+
this.websitePreviewInfo = results;
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
private mapAndMarkHyperLinks(text: string): {
|
|
99
|
+
hyperlinks: {
|
|
100
|
+
link: string;
|
|
101
|
+
hyperlink: string;
|
|
102
|
+
mark: string;
|
|
103
|
+
id: string;
|
|
104
|
+
}[];
|
|
105
|
+
markedText: string;
|
|
106
|
+
} {
|
|
107
|
+
const matches = Array.from(text.matchAll(/<https?:\/\/[^\s]+\|[^>]+>/g));
|
|
108
|
+
const listHyperlinks: {
|
|
109
|
+
link: string;
|
|
110
|
+
hyperlink: string;
|
|
111
|
+
mark: string;
|
|
112
|
+
id: string;
|
|
113
|
+
}[] = [];
|
|
114
|
+
matches.forEach((match) => {
|
|
115
|
+
try {
|
|
116
|
+
const link = match[0].split("|")[0].replace("<", "");
|
|
117
|
+
const hyperlink = match[0].split("|")[1].replace(">", "");
|
|
118
|
+
if (link && hyperlink) {
|
|
119
|
+
const uniqueKeyMark = uuid();
|
|
120
|
+
listHyperlinks.push({
|
|
121
|
+
link,
|
|
122
|
+
hyperlink,
|
|
123
|
+
mark: match[0],
|
|
124
|
+
id: uniqueKeyMark,
|
|
125
|
+
});
|
|
126
|
+
text = text.replace(match[0], uniqueKeyMark);
|
|
127
|
+
}
|
|
128
|
+
} catch (_) {
|
|
129
|
+
// pass
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
return { hyperlinks: listHyperlinks, markedText: text };
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
private async getLinkData(url: string): Promise<WebsitePreview> {
|
|
137
|
+
try {
|
|
138
|
+
// Getting link data is unlikely to work unless the site has CORS enabled and/or url is on the same domain
|
|
139
|
+
// For dev testing, can visit https://cors-anywhere.herokuapp.com/ and prefix url with it to bypass CORS
|
|
140
|
+
const response = await axios.get(url);
|
|
141
|
+
const html = response.data;
|
|
142
|
+
const parser = new DOMParser(); // creates a temp dom to parse HTML
|
|
143
|
+
const doc = parser.parseFromString(html, "text/html");
|
|
144
|
+
if (!doc)
|
|
145
|
+
return {
|
|
146
|
+
title: null,
|
|
147
|
+
description: null,
|
|
148
|
+
image: null,
|
|
149
|
+
favicon: null,
|
|
150
|
+
link: url,
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
return {
|
|
154
|
+
title: doc.querySelector("title")?.textContent ?? null,
|
|
155
|
+
description:
|
|
156
|
+
doc
|
|
157
|
+
.querySelector('meta[name="description"]')
|
|
158
|
+
?.getAttribute("content") || null,
|
|
159
|
+
image:
|
|
160
|
+
doc
|
|
161
|
+
.querySelector('meta[property="og:image"]')
|
|
162
|
+
?.getAttribute("content") || null,
|
|
163
|
+
favicon:
|
|
164
|
+
doc
|
|
165
|
+
.querySelector('link[rel="shortcut icon"]')
|
|
166
|
+
?.getAttribute("href") ||
|
|
167
|
+
doc.querySelector('link[rel="icon"]')?.getAttribute("href") ||
|
|
168
|
+
null,
|
|
169
|
+
link: url,
|
|
170
|
+
};
|
|
171
|
+
} catch (error) {
|
|
172
|
+
// eslint-disable-next-line no-console
|
|
173
|
+
console.error("Error fetching website details:", error);
|
|
174
|
+
return {
|
|
175
|
+
title: null,
|
|
176
|
+
description: null,
|
|
177
|
+
image: null,
|
|
178
|
+
favicon: null,
|
|
179
|
+
link: url,
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
updated(): void {
|
|
185
|
+
this.onMount();
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
render(): TemplateResult {
|
|
189
|
+
if (!this.message) return html``;
|
|
190
|
+
return html`<div class="message-inner-body">
|
|
191
|
+
<p class="message-text">${this.parsedMessage}</p>
|
|
192
|
+
${this.websitePreviewInfo.length > 0 &&
|
|
193
|
+
this.websitePreviewInfo.some(
|
|
194
|
+
(preview) => preview.title && (preview.image || preview.favicon)
|
|
195
|
+
)
|
|
196
|
+
? html`<br />`
|
|
197
|
+
: ""}
|
|
198
|
+
${this.websitePreviewInfo.map((preview) => {
|
|
199
|
+
if (!preview.title || !(preview.image || preview.favicon)) return;
|
|
200
|
+
return html` <a
|
|
201
|
+
class="redirect-link-preview"
|
|
202
|
+
href=${preview.link}
|
|
203
|
+
target="_blank"
|
|
204
|
+
rel="noopener noreferrer"
|
|
205
|
+
>
|
|
206
|
+
<div class="website-preview">
|
|
207
|
+
<img
|
|
208
|
+
src=${preview.image ?? preview.favicon}
|
|
209
|
+
class="website-preview-image"
|
|
210
|
+
alt="website preview image"
|
|
211
|
+
width="100%"
|
|
212
|
+
height="100%"
|
|
213
|
+
/>
|
|
214
|
+
<div class="website-preview-body">
|
|
215
|
+
<p class="message-text website-preview-title">${preview.title}</p>
|
|
216
|
+
<p class="message-text website-preview-description">
|
|
217
|
+
${preview.description}
|
|
218
|
+
</p>
|
|
219
|
+
</div>
|
|
220
|
+
</div>
|
|
221
|
+
</a></div> `;
|
|
222
|
+
})}
|
|
223
|
+
</div>`;
|
|
224
|
+
}
|
|
225
|
+
}
|