@mrrocket/sdk 0.13.0 → 0.15.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 +8 -1
- package/README.md +43 -3
- package/dist/api-client.d.ts +62 -1
- package/dist/api-client.js +28 -1
- package/dist/api-client.js.map +1 -1
- package/dist/browser/mrrocket-flows.js +11 -2
- package/dist/browser/mrrocket-messenger.js +304 -70
- package/dist/browser/mrrocket.js +1 -1
- package/dist/browser.d.ts +1 -1
- package/dist/browser.js +2 -1
- package/dist/browser.js.map +1 -1
- package/dist/client.d.ts +12 -0
- package/dist/client.js +22 -0
- package/dist/client.js.map +1 -1
- package/dist/context.d.ts +3 -0
- package/dist/context.js.map +1 -1
- package/dist/core.d.ts +4 -1
- package/dist/core.js +1 -1
- package/dist/core.js.map +1 -1
- package/dist/flows-ui.d.ts +6 -4
- package/dist/flows-ui.js +147 -5
- package/dist/flows-ui.js.map +1 -1
- package/dist/flows.d.ts +51 -1
- package/dist/flows.js +62 -21
- package/dist/flows.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/messenger-ui.d.ts +37 -2
- package/dist/messenger-ui.js +597 -109
- package/dist/messenger-ui.js.map +1 -1
- package/dist/messenger.d.ts +11 -3
- package/dist/messenger.js +6 -3
- package/dist/messenger.js.map +1 -1
- package/dist/paywall.d.ts +123 -0
- package/dist/paywall.js +131 -0
- package/dist/paywall.js.map +1 -0
- package/dist/react-native/index.d.cts +235 -8
- package/dist/react-native/index.d.ts +235 -8
- package/dist/react-native/index.js +650 -184
- package/dist/react-native/index.js.map +1 -1
- package/dist/styles-css.d.ts +1 -1
- package/dist/styles-css.js +302 -68
- package/dist/styles-css.js.map +1 -1
- package/package.json +5 -1
package/dist/messenger-ui.js
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
// src/messenger-ui.ts
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
BRAND_URL,
|
|
4
|
+
CHAT_FILES_PER_MESSAGE,
|
|
5
|
+
CHAT_FILE_MAX_BYTES,
|
|
6
|
+
CHAT_FILE_TYPES,
|
|
7
|
+
createApi,
|
|
8
|
+
inkOn,
|
|
9
|
+
replyLine
|
|
10
|
+
} from "./api-client.js";
|
|
3
11
|
import { injectStyles } from "./styles-css.js";
|
|
4
12
|
var POLL_OPEN_MS = 4e3;
|
|
5
13
|
var POLL_WAITING_MS = 8e3;
|
|
@@ -40,7 +48,7 @@ var el = (tag, attrs = {}, ...kids) => {
|
|
|
40
48
|
var SVG_NS = "http://www.w3.org/2000/svg";
|
|
41
49
|
var icon = (d, size = 16) => {
|
|
42
50
|
const svg = document.createElementNS(SVG_NS, "svg");
|
|
43
|
-
for (const [k, v] of Object.entries({ class: "ckw-icon", width: String(size), height: String(size), viewBox: "0 0
|
|
51
|
+
for (const [k, v] of Object.entries({ class: "ckw-icon", width: String(size), height: String(size), viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "1.75", "stroke-linecap": "round", "stroke-linejoin": "round", "aria-hidden": "true" }))
|
|
44
52
|
svg.setAttribute(k, v);
|
|
45
53
|
const path = document.createElementNS(SVG_NS, "path");
|
|
46
54
|
path.setAttribute("d", d);
|
|
@@ -48,17 +56,50 @@ var icon = (d, size = 16) => {
|
|
|
48
56
|
return svg;
|
|
49
57
|
};
|
|
50
58
|
var I = {
|
|
51
|
-
cross: "
|
|
52
|
-
chat: "
|
|
53
|
-
send: "
|
|
54
|
-
up: "
|
|
55
|
-
home: "
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
59
|
+
cross: "M18 6 6 18M6 6l12 12",
|
|
60
|
+
chat: "M7.9 20A9 9 0 1 0 4 16.1L2 22z",
|
|
61
|
+
send: "M5 12h14M12 5l7 7-7 7",
|
|
62
|
+
up: "M12 19V5M5 12l7-7 7 7",
|
|
63
|
+
home: "M15 21v-8a1 1 0 0 0-1-1h-4a1 1 0 0 0-1 1v8M3 10a2 2 0 0 1 .709-1.528l7-6a2 2 0 0 1 2.582 0l7 6A2 2 0 0 1 21 10v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z",
|
|
64
|
+
back: "m15 18-6-6 6-6",
|
|
65
|
+
next: "m9 18 6-6-6-6",
|
|
66
|
+
search: "M19 11a8 8 0 1 1-16 0 8 8 0 0 1 16 0zM21 21l-4.3-4.3",
|
|
67
|
+
doc: "M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7zM14 2v4a2 2 0 0 0 2 2h4M10 9H8M16 13H8M16 17H8",
|
|
68
|
+
book: "M12 7v14M3 18a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h5a4 4 0 0 1 4 4 4 4 0 0 1 4-4h5a1 1 0 0 1 1 1v13a1 1 0 0 1-1 1h-6a3 3 0 0 0-3 3 3 3 0 0 0-3-3z",
|
|
69
|
+
dots: "M13 12a1 1 0 1 1-2 0 1 1 0 0 1 2 0zM13 5a1 1 0 1 1-2 0 1 1 0 0 1 2 0zM13 19a1 1 0 1 1-2 0 1 1 0 0 1 2 0z",
|
|
70
|
+
expand: "M15 3h6v6M21 3l-7 7M3 21l7-7M9 21H3v-6",
|
|
71
|
+
collapse: "m14 10 7-7M20 10h-6V4M3 21l7-7M4 14h6v6",
|
|
72
|
+
minus: "M5 12h14",
|
|
73
|
+
down: "m6 9 6 6 6-6",
|
|
74
|
+
help: "M12 22a10 10 0 1 0 0-20 10 10 0 0 0 0 20zM9.1 9a3 3 0 0 1 5.8 1c0 2-3 3-3 3M12 17h.01",
|
|
75
|
+
clip: "m21.44 11.05-9.19 9.19a6 6 0 0 1-8.49-8.49l8.57-8.57A4 4 0 1 1 18 8.84l-8.59 8.57a2 2 0 0 1-2.83-2.83l8.49-8.48",
|
|
76
|
+
image: "M19 3H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V5a2 2 0 0 0-2-2zM9 11a2 2 0 1 0 0-4 2 2 0 0 0 0 4zM21 15l-3.09-3.09a2 2 0 0 0-2.82 0L6 21",
|
|
77
|
+
screen: "M20 3H4a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2V5a2 2 0 0 0-2-2zM8 21h8M12 17v4"
|
|
61
78
|
};
|
|
79
|
+
var lineOf = (m) => m.body || (m.attachments?.length === 1 ? `Sent ${m.attachments[0].name}` : m.attachments?.length ? `Sent ${m.attachments.length} files` : "");
|
|
80
|
+
var canShoot = () => typeof navigator !== "undefined" && typeof navigator.mediaDevices?.getDisplayMedia === "function";
|
|
81
|
+
var BUBBLE = "M7.9 20A9 9 0 1 0 4 16.1L2 22z";
|
|
82
|
+
var filled = (d, size) => {
|
|
83
|
+
const svg = document.createElementNS(SVG_NS, "svg");
|
|
84
|
+
for (const [k, v] of Object.entries({ class: "ckw-icon", width: String(size), height: String(size), viewBox: "0 0 24 24", fill: "currentColor", stroke: "currentColor", "stroke-width": "1.5", "stroke-linejoin": "round", "aria-hidden": "true" }))
|
|
85
|
+
svg.setAttribute(k, v);
|
|
86
|
+
const path = document.createElementNS(SVG_NS, "path");
|
|
87
|
+
path.setAttribute("d", d);
|
|
88
|
+
svg.appendChild(path);
|
|
89
|
+
return svg;
|
|
90
|
+
};
|
|
91
|
+
var duo = (shape, cut, size, width = 2) => {
|
|
92
|
+
const svg = filled(shape, size);
|
|
93
|
+
const path = document.createElementNS(SVG_NS, "path");
|
|
94
|
+
for (const [k, v] of Object.entries({ d: cut, class: "ckw-cut", fill: "none", "stroke-width": String(width), "stroke-linecap": "round", "stroke-linejoin": "round" })) path.setAttribute(k, v);
|
|
95
|
+
svg.appendChild(path);
|
|
96
|
+
return svg;
|
|
97
|
+
};
|
|
98
|
+
var DOTS = "M8 12h.01M12 12h.01M16 12h.01";
|
|
99
|
+
var HOUSE = "M3 10a2 2 0 0 1 .709-1.528l7-6a2 2 0 0 1 2.582 0l7 6A2 2 0 0 1 21 10v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z";
|
|
100
|
+
var SMILE = "M9.5 15.5c1.4 1 3.6 1 5 0";
|
|
101
|
+
var DISC = "M12 22a10 10 0 1 0 0-20 10 10 0 0 0 0 20z";
|
|
102
|
+
var QMARK = "M9.4 9.2a2.7 2.7 0 0 1 5.2 1c0 1.8-2.6 2.5-2.6 2.5M12 16.8h.01";
|
|
62
103
|
var chime = /* @__PURE__ */ (() => {
|
|
63
104
|
let ctx = null;
|
|
64
105
|
const get = () => {
|
|
@@ -135,11 +176,11 @@ var mountMessenger = (o) => {
|
|
|
135
176
|
const start = (look) => {
|
|
136
177
|
if (real || gone) return;
|
|
137
178
|
const defined = Object.fromEntries(Object.entries(o).filter(([, v]) => v !== void 0));
|
|
138
|
-
real = build({ ...look ?? {}, ...defined, accent: o.accent ?? look?.accent, theme: o.theme ?? look?.theme });
|
|
179
|
+
real = build({ ...look ?? {}, ...defined, accent: o.accent ?? look?.accent, theme: o.theme ?? look?.theme, branding: look?.branding ?? true });
|
|
139
180
|
for (const fn of queued.splice(0)) fn(real);
|
|
140
181
|
};
|
|
141
182
|
const timer = setTimeout(() => start(null), LOOK_WAIT_MS);
|
|
142
|
-
createApi({ host: o.host, apiKey: o.apiKey, distinctId: o.distinctId(), user: o.user() }).messengerLook(ctrl.signal).then((look) => start(look)).catch(() => start(null)).finally(() => clearTimeout(timer));
|
|
183
|
+
(o.client ?? createApi({ host: o.host, apiKey: o.apiKey, distinctId: o.distinctId(), user: o.user() })).messengerLook(ctrl.signal).then((look) => start(look)).catch(() => start(null)).finally(() => clearTimeout(timer));
|
|
143
184
|
return {
|
|
144
185
|
open: (v) => later((h) => h.open(v)),
|
|
145
186
|
close: () => real?.close(),
|
|
@@ -152,10 +193,11 @@ var mountMessenger = (o) => {
|
|
|
152
193
|
}
|
|
153
194
|
};
|
|
154
195
|
};
|
|
196
|
+
var PLACES = ["top-left", "top-center", "top-right", "middle-left", "middle-right", "bottom-left", "bottom-center", "bottom-right"];
|
|
155
197
|
var build = (o) => {
|
|
156
198
|
injectStyles();
|
|
157
|
-
const position = o.position ?? "bottom-right";
|
|
158
|
-
const title = o.title || "
|
|
199
|
+
const position = PLACES.find((p) => p === o.position) ?? "bottom-right";
|
|
200
|
+
const title = o.title || "Support";
|
|
159
201
|
const greeting = o.greeting || "Hi! How can we help?";
|
|
160
202
|
const askEmail = o.askEmail ?? true;
|
|
161
203
|
const label = o.label ?? "";
|
|
@@ -165,13 +207,21 @@ var build = (o) => {
|
|
|
165
207
|
const helpOn = o.help ?? true;
|
|
166
208
|
const soundOn = o.sound ?? true;
|
|
167
209
|
const previewOn = o.preview ?? true;
|
|
168
|
-
const api = () => createApi({ host: o.host, apiKey: o.apiKey, distinctId: o.distinctId(), user: o.user() });
|
|
169
|
-
const root = el("div", {
|
|
210
|
+
const api = () => o.client ?? createApi({ host: o.host, apiKey: o.apiKey, distinctId: o.distinctId(), user: o.user() });
|
|
211
|
+
const root = el("div", {
|
|
212
|
+
class: "ckw-root",
|
|
213
|
+
"data-theme": o.theme ?? "auto",
|
|
214
|
+
"data-corners": o.corners === "soft" || o.corners === "square" ? o.corners : "round",
|
|
215
|
+
"data-font": o.font === "system" ? "system" : "site",
|
|
216
|
+
"data-header": o.header === "color" ? "color" : "plain"
|
|
217
|
+
});
|
|
218
|
+
if (o.container) root.setAttribute("data-contained", "");
|
|
170
219
|
if (o.accent) {
|
|
171
220
|
root.style.setProperty("--ckw-accent", o.accent);
|
|
172
221
|
root.style.setProperty("--ckw-accent-ink", inkOn(o.accent));
|
|
173
222
|
}
|
|
174
|
-
|
|
223
|
+
o.glow?.slice(0, 2).forEach((c, i) => /^#[0-9a-f]{6}$/i.test(c) && root.style.setProperty(`--ckw-glow${i + 2}`, c));
|
|
224
|
+
(o.container ?? document.body).appendChild(root);
|
|
175
225
|
let open = false;
|
|
176
226
|
let view = "home";
|
|
177
227
|
let thread = null;
|
|
@@ -187,6 +237,8 @@ var build = (o) => {
|
|
|
187
237
|
let query = "";
|
|
188
238
|
let rated = /* @__PURE__ */ new Set();
|
|
189
239
|
let hasArticles = null;
|
|
240
|
+
let collection = null;
|
|
241
|
+
let wide = false;
|
|
190
242
|
const teamMsgs = (t) => (t?.messages ?? []).filter((m) => m.from !== "visitor");
|
|
191
243
|
const lastTeamId = (t) => teamMsgs(t).at(-1)?.id ?? "";
|
|
192
244
|
const watching = () => open && view === "messages" && document.visibilityState !== "hidden";
|
|
@@ -217,24 +269,65 @@ var build = (o) => {
|
|
|
217
269
|
if (id) writeSeen(id);
|
|
218
270
|
};
|
|
219
271
|
const avatar = (size) => el("span", { class: `ckw-msgr-av${size ? " ckw-msgr-av-sm" : ""}`, "aria-hidden": "true" }, initial);
|
|
272
|
+
const logo = el("span", { class: "ckw-msgr-logo", "aria-hidden": "true" });
|
|
273
|
+
if (o.logo) {
|
|
274
|
+
const img = el("img", { src: o.logo, alt: "" });
|
|
275
|
+
img.addEventListener("error", () => logo.replaceChildren(avatar("sm")));
|
|
276
|
+
logo.append(img);
|
|
277
|
+
} else logo.append(avatar("sm"));
|
|
278
|
+
const sky = el("div", { class: "ckw-msgr-sky", "aria-hidden": "true" });
|
|
279
|
+
const leaving = /* @__PURE__ */ new Map();
|
|
280
|
+
const enter = (node, parent = root) => {
|
|
281
|
+
clearTimeout(leaving.get(node));
|
|
282
|
+
leaving.delete(node);
|
|
283
|
+
node.removeAttribute("data-out");
|
|
284
|
+
if (!node.isConnected) parent.appendChild(node);
|
|
285
|
+
};
|
|
286
|
+
const leave = (node, ms = 180) => {
|
|
287
|
+
if (!node.isConnected || leaving.has(node)) return;
|
|
288
|
+
node.setAttribute("data-out", "");
|
|
289
|
+
leaving.set(
|
|
290
|
+
node,
|
|
291
|
+
setTimeout(() => {
|
|
292
|
+
leaving.delete(node);
|
|
293
|
+
node.removeAttribute("data-out");
|
|
294
|
+
node.remove();
|
|
295
|
+
}, ms)
|
|
296
|
+
);
|
|
297
|
+
};
|
|
298
|
+
const replay = (node, cls) => {
|
|
299
|
+
node.classList.remove(cls);
|
|
300
|
+
void node.offsetWidth;
|
|
301
|
+
node.classList.add(cls);
|
|
302
|
+
};
|
|
220
303
|
const fab = el("button", { type: "button", class: `ckw-fab ckw-msgr-fab${label ? "" : " ckw-msgr-round"}`, "data-pos": position, "aria-haspopup": "dialog" });
|
|
304
|
+
const fabIcons = el(
|
|
305
|
+
"span",
|
|
306
|
+
{ class: "ckw-msgr-fab-ic", "aria-hidden": "true" },
|
|
307
|
+
el("span", { class: "ckw-msgr-fab-i", "data-i": "chat" }, duo(BUBBLE, DOTS, label ? 20 : 27, 2.6)),
|
|
308
|
+
el("span", { class: "ckw-msgr-fab-i", "data-i": "x" }, icon(I.down, label ? 18 : 24))
|
|
309
|
+
);
|
|
310
|
+
const fabCount = el("span", { class: "ckw-msgr-count", "aria-hidden": "true" });
|
|
311
|
+
fab.append(fabIcons);
|
|
312
|
+
if (label) fab.append(el("span", {}, label));
|
|
221
313
|
const prev = el("div", { class: "ckw-msgr-preview", "data-pos": position, role: "status" });
|
|
222
|
-
let fabShows = null;
|
|
223
314
|
const drawFab = () => {
|
|
224
|
-
|
|
225
|
-
if (
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
315
|
+
fab.toggleAttribute("data-open", open);
|
|
316
|
+
if (unseen.length && !open) {
|
|
317
|
+
const n = String(Math.min(unseen.length, 9));
|
|
318
|
+
if (fabCount.textContent !== n) fabCount.textContent = n;
|
|
319
|
+
if (!fabCount.isConnected) fab.append(fabCount);
|
|
320
|
+
} else fabCount.remove();
|
|
230
321
|
fab.setAttribute("aria-expanded", String(open));
|
|
231
322
|
fab.setAttribute("aria-label", open ? "Close chat" : unseen.length ? `Open chat, ${unseen.length} new ${unseen.length === 1 ? "reply" : "replies"}` : "Open chat");
|
|
232
323
|
drawPreview();
|
|
233
324
|
};
|
|
325
|
+
const previewShown = () => previewOn && !open && !previewGone && !!unseen.length && launcherOn;
|
|
234
326
|
const drawPreview = () => {
|
|
235
327
|
const m = unseen.at(-1);
|
|
236
|
-
if (!
|
|
237
|
-
|
|
328
|
+
if (!previewShown() || !m) return leave(prev);
|
|
329
|
+
hideTeaser(true);
|
|
330
|
+
const x = el("button", { type: "button", class: "ckw-msgr-preview-x", "aria-label": "Dismiss" }, icon(I.cross, 14));
|
|
238
331
|
x.addEventListener("click", (e) => {
|
|
239
332
|
e.stopPropagation();
|
|
240
333
|
previewGone = true;
|
|
@@ -243,50 +336,315 @@ var build = (o) => {
|
|
|
243
336
|
const card = el(
|
|
244
337
|
"button",
|
|
245
338
|
{ type: "button", class: "ckw-msgr-preview-card" },
|
|
246
|
-
avatar(),
|
|
247
339
|
el(
|
|
248
340
|
"span",
|
|
249
341
|
{ class: "ckw-msgr-preview-main" },
|
|
250
342
|
el("span", { class: "ckw-msgr-preview-who" }, `${whoLabel[m.from]}${unseen.length > 1 ? ` \xB7 ${unseen.length} new` : ""}`),
|
|
251
|
-
el("span", { class: "ckw-msgr-preview-text" }, m
|
|
343
|
+
el("span", { class: "ckw-msgr-preview-text" }, lineOf(m))
|
|
252
344
|
)
|
|
253
345
|
);
|
|
254
346
|
card.addEventListener("click", () => show("messages"));
|
|
255
347
|
prev.replaceChildren(card, x);
|
|
256
|
-
|
|
348
|
+
enter(prev);
|
|
257
349
|
};
|
|
350
|
+
const teaser = el("div", { class: "ckw-msgr-teaser", "data-pos": position });
|
|
351
|
+
let teaserTimer;
|
|
352
|
+
const showTeaser = () => {
|
|
353
|
+
clearTimeout(teaserTimer);
|
|
354
|
+
if (open || previewShown() || !launcherOn) return;
|
|
355
|
+
const chat = el("button", { type: "button", class: "ckw-msgr-teaser-b", "data-main": "" }, icon(I.chat), el("span", {}, "Chat"));
|
|
356
|
+
chat.addEventListener("click", () => show("messages"));
|
|
357
|
+
const buttons = [chat];
|
|
358
|
+
if (helpOn && hasArticles) {
|
|
359
|
+
const search = el("button", { type: "button", class: "ckw-msgr-teaser-b" }, icon(I.search), el("span", {}, "Search"));
|
|
360
|
+
search.addEventListener("click", () => show("help"));
|
|
361
|
+
buttons.push(search);
|
|
362
|
+
}
|
|
363
|
+
teaser.replaceChildren(
|
|
364
|
+
el(
|
|
365
|
+
"div",
|
|
366
|
+
{ class: "ckw-msgr-teaser-top" },
|
|
367
|
+
el("span", { class: "ckw-msgr-teaser-t" }, el("b", {}, greeting), el("span", { class: "ckw-msgr-online" }, reply)),
|
|
368
|
+
avatar()
|
|
369
|
+
),
|
|
370
|
+
el("div", { class: "ckw-msgr-teaser-row" }, ...buttons)
|
|
371
|
+
);
|
|
372
|
+
enter(teaser);
|
|
373
|
+
};
|
|
374
|
+
function hideTeaser(now = false) {
|
|
375
|
+
clearTimeout(teaserTimer);
|
|
376
|
+
if (now) return leave(teaser, 140);
|
|
377
|
+
teaserTimer = setTimeout(() => leave(teaser, 140), 260);
|
|
378
|
+
}
|
|
379
|
+
const hoverable = (e) => e.pointerType === "mouse";
|
|
380
|
+
for (const node of [fab, teaser]) {
|
|
381
|
+
node.addEventListener("pointerenter", (e) => hoverable(e) && (node === fab ? showTeaser() : clearTimeout(teaserTimer)));
|
|
382
|
+
node.addEventListener("pointerleave", (e) => hoverable(e) && hideTeaser());
|
|
383
|
+
}
|
|
258
384
|
fab.addEventListener("click", () => {
|
|
259
385
|
chime.unlock();
|
|
386
|
+
hideTeaser(true);
|
|
260
387
|
if (open) close();
|
|
261
388
|
else show(unseen.length ? "messages" : "home");
|
|
262
389
|
});
|
|
263
390
|
if (launcherOn) root.appendChild(fab);
|
|
264
391
|
const pop = el("div", { class: "ckw-pop ckw-msgr", "data-pos": position, role: "dialog", "aria-label": title });
|
|
265
|
-
const
|
|
392
|
+
const bar = el("div", { class: "ckw-msgr-bar" });
|
|
266
393
|
const body = el("div", { class: "ckw-pop-body ckw-msgr-body" });
|
|
267
|
-
const tabs = el("nav", { class: "ckw-msgr-tabs", "aria-label": "Messenger" });
|
|
268
394
|
pop.addEventListener("click", () => chime.unlock());
|
|
395
|
+
const lead = el("span", { class: "ckw-msgr-lead" });
|
|
396
|
+
const mid = el("span", { class: "ckw-msgr-mid" });
|
|
397
|
+
const tabs = el("nav", { class: "ckw-msgr-tabs", "aria-label": "Messenger" });
|
|
398
|
+
const tab = (v, name, line, shape, cut) => {
|
|
399
|
+
const b = el("button", { type: "button", class: "ckw-msgr-tab" }, el("span", { class: "ckw-msgr-tab-ic" }, icon(line, 22), duo(shape, cut, 22)), el("span", {}, name));
|
|
400
|
+
b.addEventListener("click", () => {
|
|
401
|
+
collection = null;
|
|
402
|
+
go(v);
|
|
403
|
+
});
|
|
404
|
+
return b;
|
|
405
|
+
};
|
|
406
|
+
const homeTab = tab("home", "Home", I.home, HOUSE, SMILE);
|
|
407
|
+
const chatTab = tab("messages", "Messages", I.chat, BUBBLE, DOTS);
|
|
408
|
+
const helpTab = tab("help", "Help", I.help, DISC, QMARK);
|
|
409
|
+
const tabCount = el("span", { class: "ckw-msgr-count ckw-msgr-count-tab", "aria-hidden": "true" });
|
|
410
|
+
const menuBtn = el("button", { type: "button", class: "ckw-msgr-ib", "aria-label": "More", "aria-haspopup": "menu", "aria-expanded": "false" }, icon(I.dots, 18));
|
|
411
|
+
const xBtn = el("button", { type: "button", class: "ckw-msgr-ib ckw-msgr-x", "aria-label": "Close messenger" }, icon(I.cross));
|
|
412
|
+
xBtn.addEventListener("click", () => close());
|
|
413
|
+
bar.append(lead, mid, el("span", { class: "ckw-msgr-trail" }, menuBtn, xBtn));
|
|
414
|
+
const menu = el("div", { class: "ckw-msgr-menu", role: "menu" });
|
|
415
|
+
const menuOpen = () => menu.isConnected && !menu.hasAttribute("data-out");
|
|
416
|
+
const closeMenu = () => {
|
|
417
|
+
menuBtn.setAttribute("aria-expanded", "false");
|
|
418
|
+
leave(menu, 120);
|
|
419
|
+
};
|
|
420
|
+
const menuItem = (d, name, act) => {
|
|
421
|
+
const b = el("button", { type: "button", class: "ckw-msgr-menu-i", role: "menuitem" }, icon(d, 15), el("span", {}, name));
|
|
422
|
+
b.addEventListener("click", (e) => {
|
|
423
|
+
e.stopPropagation();
|
|
424
|
+
closeMenu();
|
|
425
|
+
act();
|
|
426
|
+
});
|
|
427
|
+
return b;
|
|
428
|
+
};
|
|
429
|
+
menuBtn.addEventListener("click", (e) => {
|
|
430
|
+
e.stopPropagation();
|
|
431
|
+
if (menuOpen()) return closeMenu();
|
|
432
|
+
menu.replaceChildren(
|
|
433
|
+
el("span", { class: "ckw-msgr-menu-h" }, "Quick actions"),
|
|
434
|
+
menuItem(I.minus, "Minimize chat", () => close()),
|
|
435
|
+
menuItem(wide ? I.collapse : I.expand, wide ? "Collapse view" : "Expand view", () => {
|
|
436
|
+
wide = !wide;
|
|
437
|
+
pop.toggleAttribute("data-wide", wide);
|
|
438
|
+
})
|
|
439
|
+
);
|
|
440
|
+
menuBtn.setAttribute("aria-expanded", "true");
|
|
441
|
+
enter(menu, pop);
|
|
442
|
+
menu.querySelector("button")?.focus();
|
|
443
|
+
});
|
|
444
|
+
pop.addEventListener("click", () => menuOpen() && closeMenu());
|
|
445
|
+
const drawBar = () => {
|
|
446
|
+
const back = (to, name) => {
|
|
447
|
+
const b = el("button", { type: "button", class: "ckw-msgr-ib", "aria-label": name }, icon(I.back, 18));
|
|
448
|
+
b.addEventListener("click", to);
|
|
449
|
+
return b;
|
|
450
|
+
};
|
|
451
|
+
if (typeof view !== "string")
|
|
452
|
+
lead.replaceChildren(
|
|
453
|
+
back(() => go("help"), "Back to help")
|
|
454
|
+
);
|
|
455
|
+
else if (view === "help" && collection && !query.trim())
|
|
456
|
+
lead.replaceChildren(
|
|
457
|
+
back(() => {
|
|
458
|
+
collection = null;
|
|
459
|
+
go("help");
|
|
460
|
+
}, "All collections")
|
|
461
|
+
);
|
|
462
|
+
else if (view === "messages") lead.replaceChildren(back(() => go("home"), "Back to home"));
|
|
463
|
+
else if (view === "home") lead.replaceChildren(logo);
|
|
464
|
+
else lead.replaceChildren();
|
|
465
|
+
if (view === "messages") mid.replaceChildren(avatar("sm"), el("span", { class: "ckw-msgr-mid-t" }, el("b", {}, title), el("span", { class: "ckw-msgr-online" }, reply)));
|
|
466
|
+
else if (view === "help") mid.replaceChildren(el("b", {}, "Help"));
|
|
467
|
+
else mid.replaceChildren();
|
|
468
|
+
const items = helpOn && hasArticles !== false ? [homeTab, chatTab, helpTab] : [homeTab, chatTab];
|
|
469
|
+
if (tabs.childNodes.length !== items.length) tabs.replaceChildren(...items);
|
|
470
|
+
homeTab.setAttribute("aria-current", view === "home" ? "page" : "false");
|
|
471
|
+
chatTab.setAttribute("aria-current", view === "messages" ? "page" : "false");
|
|
472
|
+
helpTab.setAttribute("aria-current", view !== "home" && view !== "messages" ? "page" : "false");
|
|
473
|
+
if (unseen.length && view !== "messages") {
|
|
474
|
+
tabCount.textContent = String(Math.min(unseen.length, 9));
|
|
475
|
+
if (!tabCount.isConnected) chatTab.firstChild.appendChild(tabCount);
|
|
476
|
+
} else tabCount.remove();
|
|
477
|
+
};
|
|
269
478
|
const form = el("form", { class: "ckw-msgr-compose" });
|
|
270
479
|
const err = el("p", { class: "ckw-msgr-error", role: "alert" });
|
|
271
480
|
const email = el("input", { class: "ckw-input", type: "email", inputmode: "email", autocomplete: "email", placeholder: "Your email (optional), so we can follow up", "aria-label": "Your email (optional)", maxlength: "200" });
|
|
272
|
-
const row = el("div", { class: "ckw-msgr-
|
|
273
|
-
const text = el("textarea", { class: "ckw-textarea ckw-msgr-input", rows: "1", placeholder: "Write
|
|
481
|
+
const row = el("div", { class: "ckw-msgr-box" });
|
|
482
|
+
const text = el("textarea", { class: "ckw-textarea ckw-msgr-input", rows: "1", placeholder: "Write your message\u2026", "aria-label": "Message", maxlength: "4000" });
|
|
274
483
|
const send = el("button", { type: "submit", class: "ckw-msgr-send", "aria-label": "Send" }, icon(I.up));
|
|
275
|
-
|
|
484
|
+
const shots = canShoot();
|
|
485
|
+
const clip = el(
|
|
486
|
+
"button",
|
|
487
|
+
{ type: "button", class: "ckw-msgr-clip", "aria-label": shots ? "Attach a file or a screenshot" : "Attach a file", ...shots ? { "aria-haspopup": "menu", "aria-expanded": "false" } : {} },
|
|
488
|
+
icon(I.clip, 18)
|
|
489
|
+
);
|
|
490
|
+
row.append(text, el("div", { class: "ckw-msgr-tools" }, clip, el("span", { class: "ckw-msgr-sp" }), send));
|
|
491
|
+
row.addEventListener("click", (e) => e.target === row && text.focus());
|
|
276
492
|
form.append(row);
|
|
493
|
+
const picker = el("input", { type: "file", accept: CHAT_FILE_TYPES.join(","), multiple: "", hidden: "", tabindex: "-1", "aria-hidden": "true" });
|
|
494
|
+
form.append(picker);
|
|
495
|
+
const tray = el("div", { class: "ckw-msgr-files", role: "list", "aria-label": "Files to send" });
|
|
496
|
+
let picked = [];
|
|
497
|
+
const localUrls = /* @__PURE__ */ new Map();
|
|
498
|
+
const fileUrl = (id) => localUrls.get(id) ?? api().attachmentUrl(id);
|
|
499
|
+
const forget = (p) => {
|
|
500
|
+
p.ctrl.abort();
|
|
501
|
+
if (![...localUrls.values()].includes(p.local)) URL.revokeObjectURL(p.local);
|
|
502
|
+
};
|
|
503
|
+
const dropFile = (p) => {
|
|
504
|
+
picked = picked.filter((x) => x !== p);
|
|
505
|
+
forget(p);
|
|
506
|
+
};
|
|
507
|
+
const addFiles = (list2) => {
|
|
508
|
+
error = "";
|
|
509
|
+
for (const f of list2) {
|
|
510
|
+
const name = f.name || "screenshot.png";
|
|
511
|
+
if (picked.length >= CHAT_FILES_PER_MESSAGE) {
|
|
512
|
+
error = `Up to ${CHAT_FILES_PER_MESSAGE} files per message.`;
|
|
513
|
+
break;
|
|
514
|
+
}
|
|
515
|
+
if (!CHAT_FILE_TYPES.includes(f.type)) {
|
|
516
|
+
error = "Only pictures (PNG, JPEG, WebP, GIF) and PDFs can be sent.";
|
|
517
|
+
continue;
|
|
518
|
+
}
|
|
519
|
+
if (f.size > CHAT_FILE_MAX_BYTES) {
|
|
520
|
+
error = `${name} is larger than 10 MB.`;
|
|
521
|
+
continue;
|
|
522
|
+
}
|
|
523
|
+
const p = { key: `local-${Date.now().toString(36)}${Math.random().toString(36).slice(2, 7)}`, name, type: f.type, size: f.size, local: URL.createObjectURL(f), up: Promise.resolve(), ctrl: new AbortController() };
|
|
524
|
+
p.up = api().uploadChatFile(f, p.ctrl.signal).then(
|
|
525
|
+
(r) => {
|
|
526
|
+
p.id = r.id;
|
|
527
|
+
draw();
|
|
528
|
+
},
|
|
529
|
+
(e) => {
|
|
530
|
+
if (e.name === "AbortError") return;
|
|
531
|
+
dropFile(p);
|
|
532
|
+
error = `${p.name}: ${e.message}`;
|
|
533
|
+
draw();
|
|
534
|
+
}
|
|
535
|
+
);
|
|
536
|
+
picked.push(p);
|
|
537
|
+
}
|
|
538
|
+
if (open && view !== "messages") go("messages");
|
|
539
|
+
else draw();
|
|
540
|
+
text.focus();
|
|
541
|
+
};
|
|
542
|
+
let traySig = "";
|
|
543
|
+
const drawTray = () => {
|
|
544
|
+
const sig = picked.map((p) => `${p.key}:${p.id ? 1 : 0}`).join(",");
|
|
545
|
+
if (sig !== traySig) {
|
|
546
|
+
traySig = sig;
|
|
547
|
+
tray.replaceChildren(
|
|
548
|
+
...picked.map((p) => {
|
|
549
|
+
const x = el("button", { type: "button", class: "ckw-msgr-file-x", "aria-label": `Remove ${p.name}` }, icon(I.cross, 12));
|
|
550
|
+
x.addEventListener("click", () => {
|
|
551
|
+
dropFile(p);
|
|
552
|
+
draw();
|
|
553
|
+
text.focus();
|
|
554
|
+
});
|
|
555
|
+
const face = p.type.startsWith("image/") ? el("img", { src: p.local, alt: "" }) : el("span", { class: "ckw-msgr-file-doc" }, icon(I.doc, 18), el("span", {}, p.name));
|
|
556
|
+
return el("div", { class: "ckw-msgr-file", role: "listitem", title: p.name, "aria-label": p.id ? p.name : `${p.name}, uploading`, ...p.id ? {} : { "aria-busy": "true" } }, face, x);
|
|
557
|
+
})
|
|
558
|
+
);
|
|
559
|
+
}
|
|
560
|
+
if (!picked.length) tray.remove();
|
|
561
|
+
else if (!tray.isConnected) form.insertBefore(tray, row);
|
|
562
|
+
};
|
|
563
|
+
const shoot = async () => {
|
|
564
|
+
let stream;
|
|
565
|
+
try {
|
|
566
|
+
stream = await navigator.mediaDevices.getDisplayMedia({ video: { displaySurface: "browser" }, audio: false, preferCurrentTab: true, selfBrowserSurface: "include" });
|
|
567
|
+
} catch (e) {
|
|
568
|
+
if (e.name !== "NotAllowedError" && e.name !== "AbortError") {
|
|
569
|
+
error = "The screenshot did not work here. Attach a picture instead.";
|
|
570
|
+
draw();
|
|
571
|
+
}
|
|
572
|
+
return;
|
|
573
|
+
}
|
|
574
|
+
root.style.visibility = "hidden";
|
|
575
|
+
let blob = null;
|
|
576
|
+
try {
|
|
577
|
+
const video = document.createElement("video");
|
|
578
|
+
video.muted = true;
|
|
579
|
+
video.playsInline = true;
|
|
580
|
+
video.srcObject = stream;
|
|
581
|
+
await video.play();
|
|
582
|
+
await new Promise((r) => setTimeout(r, 300));
|
|
583
|
+
const v = video;
|
|
584
|
+
await new Promise((r) => v.requestVideoFrameCallback ? v.requestVideoFrameCallback(() => r()) : requestAnimationFrame(() => r()));
|
|
585
|
+
const canvas = document.createElement("canvas");
|
|
586
|
+
canvas.width = video.videoWidth;
|
|
587
|
+
canvas.height = video.videoHeight;
|
|
588
|
+
canvas.getContext("2d")?.drawImage(video, 0, 0);
|
|
589
|
+
const encode = (type, q) => new Promise((r) => canvas.toBlob(r, type, q));
|
|
590
|
+
blob = await encode("image/png");
|
|
591
|
+
if (blob && blob.size > 4.5 * 1024 * 1024) blob = await encode("image/jpeg", 0.9);
|
|
592
|
+
} catch {
|
|
593
|
+
blob = null;
|
|
594
|
+
} finally {
|
|
595
|
+
for (const t of stream.getTracks()) t.stop();
|
|
596
|
+
root.style.visibility = "";
|
|
597
|
+
}
|
|
598
|
+
if (!blob || !blob.size) {
|
|
599
|
+
error = "The screenshot did not work here. Attach a picture instead.";
|
|
600
|
+
return draw();
|
|
601
|
+
}
|
|
602
|
+
const stamp = (/* @__PURE__ */ new Date()).toISOString().slice(0, 16).replace(/[:T]/g, "-");
|
|
603
|
+
addFiles([Object.assign(blob, { name: `screenshot-${stamp}.${blob.type === "image/png" ? "png" : "jpg"}` })]);
|
|
604
|
+
};
|
|
605
|
+
const amenu = el("div", { class: "ckw-msgr-menu ckw-msgr-amenu", role: "menu" });
|
|
606
|
+
const amenuOpen = () => amenu.isConnected && !amenu.hasAttribute("data-out");
|
|
607
|
+
const closeAmenu = () => {
|
|
608
|
+
clip.setAttribute("aria-expanded", "false");
|
|
609
|
+
leave(amenu, 120);
|
|
610
|
+
};
|
|
611
|
+
const amenuItem = (d, name, act) => {
|
|
612
|
+
const b = el("button", { type: "button", class: "ckw-msgr-menu-i", role: "menuitem" }, icon(d, 15), el("span", {}, name));
|
|
613
|
+
b.addEventListener("click", (e) => {
|
|
614
|
+
e.stopPropagation();
|
|
615
|
+
closeAmenu();
|
|
616
|
+
act();
|
|
617
|
+
});
|
|
618
|
+
return b;
|
|
619
|
+
};
|
|
620
|
+
clip.addEventListener("click", (e) => {
|
|
621
|
+
e.stopPropagation();
|
|
622
|
+
chime.unlock();
|
|
623
|
+
if (!shots) return picker.click();
|
|
624
|
+
if (amenuOpen()) return closeAmenu();
|
|
625
|
+
amenu.replaceChildren(
|
|
626
|
+
amenuItem(I.image, "Photo or file", () => picker.click()),
|
|
627
|
+
amenuItem(I.screen, "Screenshot of this page", () => void shoot())
|
|
628
|
+
);
|
|
629
|
+
clip.setAttribute("aria-expanded", "true");
|
|
630
|
+
enter(amenu, form);
|
|
631
|
+
amenu.querySelector("button")?.focus();
|
|
632
|
+
});
|
|
633
|
+
picker.addEventListener("change", () => {
|
|
634
|
+
addFiles([...picker.files ?? []]);
|
|
635
|
+
picker.value = "";
|
|
636
|
+
});
|
|
637
|
+
const brand = el("a", { class: "ckw-msgr-powered", href: BRAND_URL, target: "_blank", rel: "noopener" }, "Powered by ", el("b", {}, "MRRocket"));
|
|
277
638
|
const list = el("ol", { class: "ckw-msgs", "aria-live": "polite", "aria-label": "Messages" });
|
|
278
639
|
const needEmail = () => askEmail && !o.user()?.email && !thread?.hasEmail;
|
|
279
640
|
const emailOk = () => !email.value.trim() || EMAIL.test(email.value.trim());
|
|
280
|
-
const canSend = () => !!text.value.trim() && !pending && emailOk();
|
|
281
|
-
const closeBtn = () => {
|
|
282
|
-
const b = el("button", { type: "button", class: "ckw-btn ckw-btn-ghost ckw-msgr-x", "aria-label": "Close messenger" }, icon(I.cross));
|
|
283
|
-
b.addEventListener("click", () => close());
|
|
284
|
-
return b;
|
|
285
|
-
};
|
|
641
|
+
const canSend = () => (!!text.value.trim() || picked.length > 0) && !pending && emailOk();
|
|
286
642
|
const go = (v) => {
|
|
643
|
+
const changed = JSON.stringify(v) !== JSON.stringify(view);
|
|
287
644
|
view = v;
|
|
288
645
|
drawn = -1;
|
|
289
646
|
draw();
|
|
647
|
+
if (changed) replay(body, "ckw-msgr-enter");
|
|
290
648
|
if (v === "messages") {
|
|
291
649
|
markSeen();
|
|
292
650
|
drawFab();
|
|
@@ -294,9 +652,26 @@ var build = (o) => {
|
|
|
294
652
|
} else if (v === "help") helpInput.focus();
|
|
295
653
|
schedule(0);
|
|
296
654
|
};
|
|
655
|
+
const picture = (src, alt) => {
|
|
656
|
+
const img = el("img", { src, alt, loading: "lazy" });
|
|
657
|
+
img.addEventListener("load", () => {
|
|
658
|
+
if (body.scrollHeight - body.scrollTop - body.clientHeight < img.clientHeight + 120) body.scrollTop = body.scrollHeight;
|
|
659
|
+
});
|
|
660
|
+
return img;
|
|
661
|
+
};
|
|
297
662
|
const bubble = (m, isPending = false) => {
|
|
298
|
-
const li = el("li", { class: "ckw-msg", "data-from": m.from === "visitor" ? "me" : "them" }, el("div", { class: "ckw-bubble" }, m.body));
|
|
663
|
+
const li = el("li", { class: "ckw-msg", "data-from": m.from === "visitor" ? "me" : "them" }, m.body || !m.attachments?.length ? el("div", { class: "ckw-bubble" }, m.body) : null);
|
|
299
664
|
if (isPending) li.setAttribute("aria-busy", "true");
|
|
665
|
+
if (m.attachments?.length)
|
|
666
|
+
li.append(
|
|
667
|
+
el(
|
|
668
|
+
"div",
|
|
669
|
+
{ class: "ckw-msg-files" },
|
|
670
|
+
...m.attachments.map(
|
|
671
|
+
(f) => f.type.startsWith("image/") ? el("a", { class: "ckw-msg-img", href: fileUrl(f.id), target: "_blank", rel: "noopener", title: f.name }, picture(fileUrl(f.id), f.name)) : el("a", { class: "ckw-msgr-cite", href: fileUrl(f.id), target: "_blank", rel: "noopener" }, icon(I.doc, 14), el("span", {}, f.name))
|
|
672
|
+
)
|
|
673
|
+
)
|
|
674
|
+
);
|
|
300
675
|
for (const a of m.articles ?? []) {
|
|
301
676
|
const chip = el("button", { type: "button", class: "ckw-msgr-cite" }, icon(I.doc, 14), el("span", {}, a.title));
|
|
302
677
|
chip.addEventListener("click", () => go({ article: a.id }));
|
|
@@ -310,9 +685,19 @@ var build = (o) => {
|
|
|
310
685
|
b.addEventListener("click", () => go({ article: a.id }));
|
|
311
686
|
return b;
|
|
312
687
|
};
|
|
313
|
-
const
|
|
688
|
+
const helpRow = (name, sub, meta, act) => {
|
|
689
|
+
const b = el(
|
|
690
|
+
"button",
|
|
691
|
+
{ type: "button", class: "ckw-msgr-hrow" },
|
|
692
|
+
el("span", { class: "ckw-msgr-hrow-main" }, el("b", {}, name), sub ? el("span", { class: "ckw-msgr-hrow-sub" }, sub) : null, meta ? el("span", { class: "ckw-msgr-hrow-meta" }, meta) : null),
|
|
693
|
+
icon(I.next, 16)
|
|
694
|
+
);
|
|
695
|
+
b.addEventListener("click", act);
|
|
696
|
+
return b;
|
|
697
|
+
};
|
|
698
|
+
const searchBox = (placeholder = "Search for help") => {
|
|
314
699
|
const wrap = el("label", { class: "ckw-msgr-search" });
|
|
315
|
-
const input = el("input", { class: "ckw-msgr-search-in", type: "search", placeholder
|
|
700
|
+
const input = el("input", { class: "ckw-msgr-search-in", type: "search", placeholder, "aria-label": "Search for help", maxlength: "200" });
|
|
316
701
|
input.value = query;
|
|
317
702
|
let t;
|
|
318
703
|
input.addEventListener("input", () => {
|
|
@@ -329,10 +714,10 @@ var build = (o) => {
|
|
|
329
714
|
if (view !== "help") go("help");
|
|
330
715
|
}
|
|
331
716
|
});
|
|
332
|
-
wrap.append(
|
|
717
|
+
wrap.append(icon(I.search, 16), input);
|
|
333
718
|
return wrap;
|
|
334
719
|
};
|
|
335
|
-
const helpSearch = searchBox();
|
|
720
|
+
const helpSearch = searchBox("Search our help center\u2026");
|
|
336
721
|
const helpInput = helpSearch.querySelector("input");
|
|
337
722
|
let loadingArticles = false;
|
|
338
723
|
const loadArticles = async (force = false) => {
|
|
@@ -351,24 +736,43 @@ var build = (o) => {
|
|
|
351
736
|
}
|
|
352
737
|
draw();
|
|
353
738
|
};
|
|
739
|
+
const askForm = el("form", { class: "ckw-msgr-askq" });
|
|
740
|
+
const askIn = el("input", { class: "ckw-msgr-askq-in", placeholder: "Ask a question", "aria-label": "Ask a question", maxlength: "4000", autocomplete: "off" });
|
|
741
|
+
const askGo = el("button", { type: "submit", class: "ckw-msgr-send", "aria-label": "Send", disabled: "" }, icon(I.up));
|
|
742
|
+
askForm.append(askIn, askGo);
|
|
743
|
+
askIn.addEventListener("input", () => askGo.disabled = !askIn.value.trim());
|
|
744
|
+
askForm.addEventListener("submit", (e) => {
|
|
745
|
+
e.preventDefault();
|
|
746
|
+
if (!askIn.value.trim()) return go("messages");
|
|
747
|
+
text.value = askIn.value;
|
|
748
|
+
askIn.value = "";
|
|
749
|
+
askGo.disabled = true;
|
|
750
|
+
go("messages");
|
|
751
|
+
if (canSend()) void submit();
|
|
752
|
+
});
|
|
354
753
|
const drawHome = () => {
|
|
355
|
-
|
|
356
|
-
const
|
|
754
|
+
const first = o.user()?.name?.trim().split(/\s+/)[0];
|
|
755
|
+
const rest = greeting.replace(/^(hi|hello|hey|hallo)( there)?\s*[!,.]*\s*/i, "").trim() || "How can we help you today?";
|
|
756
|
+
const hero = el(
|
|
757
|
+
"h2",
|
|
758
|
+
{ class: "ckw-msgr-hero ckw-msgr-hello" },
|
|
759
|
+
el("span", {}, first ? `Hi, ${first} ` : "Hi there ", el("span", { class: "ckw-msgr-wave", "aria-hidden": "true" }, "\u{1F44B}")),
|
|
760
|
+
el("span", { class: "ckw-msgr-hello-sub" }, rest)
|
|
761
|
+
);
|
|
357
762
|
const last = (thread?.messages ?? []).at(-1);
|
|
358
|
-
const parts = [hero];
|
|
763
|
+
const parts = [hero, askForm];
|
|
359
764
|
if (last) {
|
|
360
765
|
const recent = el(
|
|
361
766
|
"button",
|
|
362
|
-
{ type: "button", class: "ckw-msgr-card ckw-msgr-recent" },
|
|
363
|
-
el("span", { class: "ckw-msgr-card-label" }, "Recent message"),
|
|
767
|
+
{ type: "button", class: "ckw-msgr-card ckw-msgr-recent", "aria-label": `Recent message: ${lineOf(last)}` },
|
|
364
768
|
el(
|
|
365
769
|
"span",
|
|
366
770
|
{ class: "ckw-msgr-recent-row" },
|
|
367
|
-
avatar(),
|
|
771
|
+
avatar("sm"),
|
|
368
772
|
el(
|
|
369
773
|
"span",
|
|
370
774
|
{ class: "ckw-msgr-card-main" },
|
|
371
|
-
el("span", { class: "ckw-msgr-card-sub ckw-msgr-recent-text" }, last
|
|
775
|
+
el("span", { class: "ckw-msgr-card-sub ckw-msgr-recent-text" }, lineOf(last)),
|
|
372
776
|
el("span", { class: "ckw-msgr-card-meta" }, `${last.from === "visitor" ? "You" : whoLabel[last.from]} \xB7 ${timeAgo(last.at)}`)
|
|
373
777
|
),
|
|
374
778
|
unseen.length ? el("span", { class: "ckw-msgr-badge" }, String(unseen.length)) : icon(I.next, 14)
|
|
@@ -377,17 +781,9 @@ var build = (o) => {
|
|
|
377
781
|
recent.addEventListener("click", () => go("messages"));
|
|
378
782
|
parts.push(recent);
|
|
379
783
|
}
|
|
380
|
-
const msgCard = el(
|
|
381
|
-
"button",
|
|
382
|
-
{ type: "button", class: "ckw-msgr-card" },
|
|
383
|
-
el("span", { class: "ckw-msgr-card-main" }, el("b", {}, "Send us a message"), el("span", { class: "ckw-msgr-card-sub" }, reply)),
|
|
384
|
-
el("span", { class: "ckw-msgr-go" }, icon(I.send))
|
|
385
|
-
);
|
|
386
|
-
msgCard.addEventListener("click", () => go("messages"));
|
|
387
|
-
parts.push(msgCard);
|
|
388
784
|
if (helpOn && hasArticles !== false) {
|
|
389
|
-
const helpCard = el("div", { class: "ckw-msgr-card ckw-msgr-card-col" }
|
|
390
|
-
const top = (articles ?? []).slice(0,
|
|
785
|
+
const helpCard = el("div", { class: "ckw-msgr-card ckw-msgr-card-col" });
|
|
786
|
+
const top = (articles ?? []).slice(0, 3);
|
|
391
787
|
if (top.length) helpCard.append(...top.map(articleRow));
|
|
392
788
|
else if (articles === null) helpCard.append(el("div", { class: "ckw-skel ckw-msgr-skel" }));
|
|
393
789
|
parts.push(helpCard);
|
|
@@ -395,30 +791,50 @@ var build = (o) => {
|
|
|
395
791
|
body.replaceChildren(...parts);
|
|
396
792
|
};
|
|
397
793
|
const drawHelp = () => {
|
|
398
|
-
head.replaceChildren(el("span", {}, "Help"), closeBtn());
|
|
399
794
|
if (helpInput.value !== query) helpInput.value = query;
|
|
400
|
-
const
|
|
795
|
+
const q = query.trim();
|
|
796
|
+
const all = articles ?? [];
|
|
797
|
+
const groups = /* @__PURE__ */ new Map();
|
|
798
|
+
for (const a of all) groups.set(a.collection, [...groups.get(a.collection) ?? [], a]);
|
|
799
|
+
const inside = !q && collection !== null ? groups.get(collection) ?? [] : null;
|
|
800
|
+
const heading = q ? "Search results" : inside ? collection ?? "" : "Most read articles";
|
|
801
|
+
const parts = [el("h2", { class: "ckw-msgr-help-h" }, heading), helpSearch];
|
|
401
802
|
if (articles === null) parts.push(el("div", { class: "ckw-skel" }));
|
|
402
|
-
else if (!
|
|
803
|
+
else if (!all.length) {
|
|
403
804
|
const ask = el("button", { type: "button", class: "ckw-btn ckw-btn-primary" }, "Send us a message");
|
|
404
805
|
ask.addEventListener("click", () => go("messages"));
|
|
405
|
-
parts.push(el("div", { class: "ckw-msgr-empty" }, el("p", {},
|
|
406
|
-
} else if (
|
|
806
|
+
parts.push(el("div", { class: "ckw-msgr-empty" }, el("p", {}, q ? `Nothing found for \u201C${q}\u201D.` : "No help articles yet."), ask));
|
|
807
|
+
} else if (q || inside)
|
|
808
|
+
parts.push(
|
|
809
|
+
el(
|
|
810
|
+
"div",
|
|
811
|
+
{ class: "ckw-msgr-hlist" },
|
|
812
|
+
...(inside ?? all).map((a) => helpRow(a.title, a.excerpt.startsWith(a.title) ? a.excerpt.slice(a.title.length).trim() : a.excerpt, q ? a.collection : "", () => go({ article: a.id })))
|
|
813
|
+
)
|
|
814
|
+
);
|
|
815
|
+
else
|
|
816
|
+
parts.push(
|
|
817
|
+
el(
|
|
818
|
+
"div",
|
|
819
|
+
{ class: "ckw-msgr-hlist" },
|
|
820
|
+
...[...groups].map(
|
|
821
|
+
([name, items]) => helpRow(name, items.map((a) => a.title).join(", "), `${items.length} ${items.length === 1 ? "article" : "articles"}`, () => {
|
|
822
|
+
collection = name;
|
|
823
|
+
go("help");
|
|
824
|
+
replay(body, "ckw-msgr-enter");
|
|
825
|
+
})
|
|
826
|
+
)
|
|
827
|
+
)
|
|
828
|
+
);
|
|
829
|
+
if (body.childNodes[1] !== helpSearch) body.replaceChildren(...parts);
|
|
407
830
|
else {
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
parts.push(el("section", { class: "ckw-msgr-group" }, el("h3", { class: "ckw-msgr-group-h" }, `${name} \xB7 ${items.length}`), el("div", { class: "ckw-msgr-card ckw-msgr-card-col" }, ...items.map(articleRow))));
|
|
831
|
+
body.firstChild.replaceWith(parts[0]);
|
|
832
|
+
while (body.childNodes.length > 2) body.lastChild.remove();
|
|
833
|
+
body.append(...parts.slice(2));
|
|
412
834
|
}
|
|
413
|
-
if (body.firstChild !== helpSearch) body.replaceChildren(helpSearch);
|
|
414
|
-
while (body.childNodes.length > 1) body.lastChild.remove();
|
|
415
|
-
body.append(...parts.slice(1));
|
|
416
835
|
};
|
|
417
836
|
let articleShown = null;
|
|
418
837
|
const drawArticle = (id) => {
|
|
419
|
-
const back = el("button", { type: "button", class: "ckw-btn ckw-btn-ghost ckw-msgr-x", "aria-label": "Back to help" }, icon(I.back));
|
|
420
|
-
back.addEventListener("click", () => go("help"));
|
|
421
|
-
head.replaceChildren(back, el("span", { class: "ckw-msgr-head-t" }, "Help"), closeBtn());
|
|
422
838
|
if (articleShown?.id === id) return body.replaceChildren(articleShown.node);
|
|
423
839
|
const node = el("article", { class: "ckw-msgr-doc" }, el("div", { class: "ckw-skel" }));
|
|
424
840
|
articleShown = { id, node };
|
|
@@ -457,22 +873,35 @@ var build = (o) => {
|
|
|
457
873
|
};
|
|
458
874
|
let drawn = -1;
|
|
459
875
|
let listSig = "";
|
|
876
|
+
let listCount = -1;
|
|
877
|
+
const suggestions = el("div", { class: "ckw-msgr-sugg" });
|
|
460
878
|
const drawMessages = () => {
|
|
461
|
-
head.replaceChildren(
|
|
462
|
-
el("span", { class: "ckw-msgr-who" }, avatar(), el("span", { class: "ckw-msgr-who-t" }, el("b", {}, title), el("span", {}, reply))),
|
|
463
|
-
closeBtn()
|
|
464
|
-
);
|
|
465
879
|
const msgs = thread?.messages ?? [];
|
|
466
880
|
const parts = [];
|
|
467
881
|
if (!loaded) parts.push(el("div", { class: "ckw-skel" }));
|
|
468
882
|
else {
|
|
469
|
-
const sig = `${msgs.map((m) => m.id).join(",")}|${pending ? pending.body : ""}|${Math.floor(Date.now() / 6e4)}`;
|
|
883
|
+
const sig = `${msgs.map((m) => m.id).join(",")}|${pending ? `${pending.body}+${pending.attachments?.length ?? 0}` : ""}|${Math.floor(Date.now() / 6e4)}`;
|
|
470
884
|
if (sig !== listSig) {
|
|
471
885
|
listSig = sig;
|
|
472
886
|
const greet = el("li", { class: "ckw-msg", "data-from": "them" }, el("div", { class: "ckw-bubble" }, greeting));
|
|
473
|
-
|
|
887
|
+
const items = [greet, ...msgs.map((m) => bubble(m)), ...pending ? [bubble(pending, true)] : []];
|
|
888
|
+
if (listCount >= 0) items.slice(listCount).forEach((li) => li.setAttribute("data-new", ""));
|
|
889
|
+
listCount = items.length;
|
|
890
|
+
list.replaceChildren(...items);
|
|
474
891
|
}
|
|
475
892
|
parts.push(list);
|
|
893
|
+
const top = !msgs.some((m) => m.from === "visitor") && !pending ? (articles ?? []).slice(0, 3) : [];
|
|
894
|
+
if (top.length) {
|
|
895
|
+
if (suggestions.childNodes.length !== top.length)
|
|
896
|
+
suggestions.replaceChildren(
|
|
897
|
+
...top.map((a) => {
|
|
898
|
+
const b = el("button", { type: "button", class: "ckw-msgr-sugg-b" }, a.title);
|
|
899
|
+
b.addEventListener("click", () => go({ article: a.id }));
|
|
900
|
+
return b;
|
|
901
|
+
})
|
|
902
|
+
);
|
|
903
|
+
parts.push(suggestions);
|
|
904
|
+
}
|
|
476
905
|
}
|
|
477
906
|
if (thread?.conversation?.status === "solved" && !pending) parts.push(el("p", { class: "ckw-fine ckw-msgr-note" }, "This conversation is closed. Write to start a new one."));
|
|
478
907
|
if (body.childNodes.length !== parts.length || parts.some((x, i) => body.childNodes[i] !== x)) body.replaceChildren(...parts);
|
|
@@ -487,31 +916,23 @@ var build = (o) => {
|
|
|
487
916
|
if (needEmail()) {
|
|
488
917
|
if (emailOk()) email.removeAttribute("aria-invalid");
|
|
489
918
|
else email.setAttribute("aria-invalid", "true");
|
|
490
|
-
if (!email.isConnected) form.insertBefore(email, row);
|
|
919
|
+
if (!email.isConnected) form.insertBefore(email, tray.isConnected ? tray : row);
|
|
491
920
|
} else email.remove();
|
|
921
|
+
drawTray();
|
|
492
922
|
send.disabled = !canSend();
|
|
493
923
|
};
|
|
494
|
-
const drawTabs = () => {
|
|
495
|
-
const current = typeof view === "string" ? view : "help";
|
|
496
|
-
const tab = (v, name, d, badge = 0) => {
|
|
497
|
-
const b = el("button", { type: "button", class: "ckw-msgr-tab", "aria-current": current === v ? "page" : "false" }, icon(d, 18), el("span", {}, name));
|
|
498
|
-
if (badge) b.append(el("span", { class: "ckw-msgr-count ckw-msgr-count-tab" }, String(Math.min(badge, 9))));
|
|
499
|
-
b.addEventListener("click", () => go(v));
|
|
500
|
-
return b;
|
|
501
|
-
};
|
|
502
|
-
tabs.replaceChildren(tab("home", "Home", I.home), tab("messages", "Messages", I.chat, view === "messages" ? 0 : unseen.length), ...helpOn && hasArticles !== false ? [tab("help", "Help", I.help)] : []);
|
|
503
|
-
};
|
|
504
924
|
const draw = () => {
|
|
505
925
|
if (!open) return;
|
|
506
926
|
pop.dataset.view = typeof view === "string" ? view : "article";
|
|
927
|
+
drawBar();
|
|
507
928
|
if (view === "home") drawHome();
|
|
508
929
|
else if (view === "messages") drawMessages();
|
|
509
930
|
else if (view === "help") drawHelp();
|
|
510
931
|
else drawArticle(view.article);
|
|
511
|
-
const kids = [
|
|
512
|
-
|
|
513
|
-
kids.push(
|
|
514
|
-
|
|
932
|
+
const kids = view === "home" ? [sky, bar, body] : [bar, body];
|
|
933
|
+
kids.push(view === "messages" ? form : tabs);
|
|
934
|
+
if (o.branding !== false) kids.push(brand);
|
|
935
|
+
if (menu.isConnected) kids.push(menu);
|
|
515
936
|
if (pop.childNodes.length !== kids.length || kids.some((k, i) => pop.childNodes[i] !== k)) pop.replaceChildren(...kids);
|
|
516
937
|
};
|
|
517
938
|
const tick = async () => {
|
|
@@ -537,26 +958,53 @@ var build = (o) => {
|
|
|
537
958
|
const ms = now ?? (watching() ? POLL_OPEN_MS : waiting && recent ? POLL_WAITING_MS : open || waiting ? POLL_IDLE_MS : 0);
|
|
538
959
|
if (ms || now === 0) poll = setTimeout(() => void tick(), ms);
|
|
539
960
|
}
|
|
540
|
-
|
|
961
|
+
const onVisible = () => {
|
|
541
962
|
if (document.visibilityState === "visible") schedule(0);
|
|
542
963
|
else clearTimeout(poll);
|
|
543
|
-
}
|
|
964
|
+
};
|
|
965
|
+
document.addEventListener("visibilitychange", onVisible);
|
|
544
966
|
const submit = async () => {
|
|
545
967
|
const msg = text.value.trim();
|
|
546
|
-
|
|
968
|
+
const files = picked;
|
|
969
|
+
if (!msg && !files.length || pending || !emailOk()) return;
|
|
547
970
|
chime.unlock();
|
|
548
|
-
|
|
971
|
+
for (const p of files) localUrls.set(p.key, p.local);
|
|
972
|
+
pending = {
|
|
973
|
+
id: "pending",
|
|
974
|
+
from: "visitor",
|
|
975
|
+
body: msg,
|
|
976
|
+
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
977
|
+
...files.length ? { attachments: files.map((p) => ({ id: p.key, name: p.name, type: p.type, size: p.size })) } : {}
|
|
978
|
+
};
|
|
549
979
|
text.value = "";
|
|
980
|
+
picked = [];
|
|
550
981
|
error = "";
|
|
551
982
|
draw();
|
|
983
|
+
await Promise.all(files.map((p) => p.up));
|
|
984
|
+
const ids = files.filter((p) => p.id).map((p) => p.id);
|
|
985
|
+
let sent = false;
|
|
552
986
|
try {
|
|
553
|
-
|
|
987
|
+
if (!msg && !ids.length) throw new Error(error || "The file could not be sent.");
|
|
988
|
+
take(
|
|
989
|
+
await api().sendChat({
|
|
990
|
+
body: msg,
|
|
991
|
+
email: needEmail() && email.value.trim() ? email.value.trim() : void 0,
|
|
992
|
+
url: window.location.href.slice(0, 500),
|
|
993
|
+
...ids.length ? { attachments: ids } : {},
|
|
994
|
+
// A little context for a bug report; the server reads browser and system from the user agent.
|
|
995
|
+
context: { viewport: `${window.innerWidth}\xD7${window.innerHeight}` }
|
|
996
|
+
})
|
|
997
|
+
);
|
|
998
|
+
sent = true;
|
|
554
999
|
} catch (e) {
|
|
555
1000
|
text.value = msg;
|
|
1001
|
+
picked = files.filter((p) => p.id);
|
|
556
1002
|
error = e.message;
|
|
557
1003
|
}
|
|
558
1004
|
pending = null;
|
|
1005
|
+
for (const p of files) localUrls.delete(p.key);
|
|
559
1006
|
draw();
|
|
1007
|
+
if (sent) for (const p of files) URL.revokeObjectURL(p.local);
|
|
560
1008
|
schedule();
|
|
561
1009
|
};
|
|
562
1010
|
form.addEventListener("submit", (e) => {
|
|
@@ -570,13 +1018,38 @@ var build = (o) => {
|
|
|
570
1018
|
}
|
|
571
1019
|
});
|
|
572
1020
|
text.addEventListener("input", () => send.disabled = !canSend());
|
|
1021
|
+
text.addEventListener("paste", (e) => {
|
|
1022
|
+
const fs = [...e.clipboardData?.files ?? []];
|
|
1023
|
+
if (!fs.length) return;
|
|
1024
|
+
e.preventDefault();
|
|
1025
|
+
addFiles(fs);
|
|
1026
|
+
});
|
|
1027
|
+
const hasFiles = (e) => !!e.dataTransfer && [...e.dataTransfer.types].includes("Files");
|
|
1028
|
+
pop.addEventListener("dragover", (e) => {
|
|
1029
|
+
if (!hasFiles(e)) return;
|
|
1030
|
+
e.preventDefault();
|
|
1031
|
+
pop.setAttribute("data-drop", "");
|
|
1032
|
+
});
|
|
1033
|
+
pop.addEventListener("dragleave", (e) => {
|
|
1034
|
+
if (!e.relatedTarget || !pop.contains(e.relatedTarget)) pop.removeAttribute("data-drop");
|
|
1035
|
+
});
|
|
1036
|
+
pop.addEventListener("drop", (e) => {
|
|
1037
|
+
if (!hasFiles(e)) return;
|
|
1038
|
+
e.preventDefault();
|
|
1039
|
+
pop.removeAttribute("data-drop");
|
|
1040
|
+
addFiles([...e.dataTransfer?.files ?? []]);
|
|
1041
|
+
});
|
|
573
1042
|
email.addEventListener("input", () => draw());
|
|
574
|
-
function show(v = "home") {
|
|
1043
|
+
function show(v = "home", still = false) {
|
|
1044
|
+
const was = open;
|
|
575
1045
|
open = true;
|
|
576
1046
|
view = v === "help" && !helpOn ? "home" : v;
|
|
577
1047
|
drawn = -1;
|
|
578
1048
|
previewGone = true;
|
|
579
|
-
|
|
1049
|
+
hideTeaser(true);
|
|
1050
|
+
pop.toggleAttribute("data-still", still);
|
|
1051
|
+
enter(pop);
|
|
1052
|
+
if (was) replay(body, "ckw-msgr-enter");
|
|
580
1053
|
if (articles === null || view === "help" && query) void loadArticles();
|
|
581
1054
|
draw();
|
|
582
1055
|
drawFab();
|
|
@@ -589,26 +1062,41 @@ var build = (o) => {
|
|
|
589
1062
|
function close() {
|
|
590
1063
|
if (!open) return;
|
|
591
1064
|
open = false;
|
|
592
|
-
|
|
1065
|
+
if (menu.isConnected) closeMenu();
|
|
1066
|
+
leave(pop, 200);
|
|
593
1067
|
drawFab();
|
|
594
1068
|
schedule();
|
|
595
1069
|
if (launcherOn) fab.focus();
|
|
596
1070
|
}
|
|
1071
|
+
pop.addEventListener("click", () => amenuOpen() && closeAmenu());
|
|
597
1072
|
const onKey = (e) => {
|
|
598
|
-
if (e.key
|
|
1073
|
+
if (e.key !== "Escape" || !open) return;
|
|
1074
|
+
if (amenuOpen()) {
|
|
1075
|
+
closeAmenu();
|
|
1076
|
+
clip.focus();
|
|
1077
|
+
} else if (menuOpen()) {
|
|
1078
|
+
closeMenu();
|
|
1079
|
+
menuBtn.focus();
|
|
1080
|
+
} else close();
|
|
599
1081
|
};
|
|
600
1082
|
document.addEventListener("keydown", onKey);
|
|
601
1083
|
void tick();
|
|
602
1084
|
if (helpOn) void loadArticles();
|
|
603
1085
|
drawFab();
|
|
1086
|
+
if (o.opened) show(o.opened, true);
|
|
604
1087
|
return {
|
|
605
1088
|
open: (v) => show(v ?? "home"),
|
|
606
1089
|
close,
|
|
607
1090
|
toggle: () => open ? close() : show(),
|
|
608
1091
|
destroy: () => {
|
|
609
1092
|
clearTimeout(poll);
|
|
1093
|
+
clearTimeout(teaserTimer);
|
|
1094
|
+
for (const t of leaving.values()) clearTimeout(t);
|
|
610
1095
|
ctrl?.abort();
|
|
1096
|
+
for (const p of picked) forget(p);
|
|
1097
|
+
for (const u of localUrls.values()) URL.revokeObjectURL(u);
|
|
611
1098
|
document.removeEventListener("keydown", onKey);
|
|
1099
|
+
document.removeEventListener("visibilitychange", onVisible);
|
|
612
1100
|
root.remove();
|
|
613
1101
|
}
|
|
614
1102
|
};
|