@mrrocket/sdk 0.16.0 → 0.18.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 +25 -0
- package/README.md +11 -2
- package/dist/api-client.d.ts +7 -1
- package/dist/api-client.js.map +1 -1
- package/dist/browser/mrrocket-flows.js +16 -2
- package/dist/browser/mrrocket-messenger.js +43 -44
- 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 +23 -3
- package/dist/client.js +15 -1
- package/dist/client.js.map +1 -1
- package/dist/context.d.ts +3 -1
- package/dist/context.js.map +1 -1
- package/dist/core.d.ts +3 -1
- package/dist/core.js +1 -1
- package/dist/core.js.map +1 -1
- package/dist/finish-web.d.ts +18 -0
- package/dist/finish-web.js +139 -0
- package/dist/finish-web.js.map +1 -0
- package/dist/flows-ui.d.ts +1 -10
- package/dist/flows-ui.js +165 -16
- package/dist/flows-ui.js.map +1 -1
- package/dist/flows-web.d.ts +8 -1
- package/dist/flows-web.js +8 -0
- package/dist/flows-web.js.map +1 -1
- package/dist/flows.d.ts +203 -5
- package/dist/flows.js +306 -15
- package/dist/flows.js.map +1 -1
- package/dist/icons.d.ts +24 -0
- package/dist/icons.js +61 -0
- package/dist/icons.js.map +1 -0
- package/dist/index.d.ts +4 -2
- package/dist/index.js +10 -1
- package/dist/index.js.map +1 -1
- package/dist/messenger-ui.d.ts +10 -2
- package/dist/messenger-ui.js +134 -98
- package/dist/messenger-ui.js.map +1 -1
- package/dist/onboarding-state.d.ts +23 -0
- package/dist/onboarding-state.js +43 -0
- package/dist/onboarding-state.js.map +1 -0
- package/dist/react-native/index.d.cts +216 -15
- package/dist/react-native/index.d.ts +216 -15
- package/dist/react-native/index.js +880 -270
- package/dist/react-native/index.js.map +1 -1
- package/dist/styles-css.d.ts +1 -1
- package/dist/styles-css.js +40 -41
- package/dist/styles-css.js.map +1 -1
- package/package.json +1 -1
package/dist/messenger-ui.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The web messenger, after Crisp's: a round launcher (
|
|
2
|
+
* The web messenger, after Crisp's: a round launcher (a click opens it, no card on hover) and a panel
|
|
3
3
|
* with a bar on top (Home, a "Chat · Help" switch while there are both, a ⋮ menu to minimize or widen it). Home shows the greeting,
|
|
4
4
|
* the latest chat and the help search; Chat is the chat with the team, answered in MRRocket › Support by the owner or
|
|
5
5
|
* the Customer experience agent; Help lists the product's published help articles by collection, searchable, read in
|
|
@@ -57,8 +57,14 @@ export interface MessengerUiOptions {
|
|
|
57
57
|
header?: "plain" | "color";
|
|
58
58
|
/** "Powered by MRRocket" at the bottom; MRRocket's server says whether the plan may hide it. */
|
|
59
59
|
branding?: boolean;
|
|
60
|
-
/** The
|
|
60
|
+
/** The logo at the top left of Home (MRRocket sends the uploaded logo, else the favicon); without it the title's first letter. */
|
|
61
61
|
logo?: string;
|
|
62
|
+
/** The round mark in the chat's head (MRRocket sends the favicon); without it the title's first letter. */
|
|
63
|
+
icon?: string;
|
|
64
|
+
/** The AI agent's name on its answers (default "AI agent"). */
|
|
65
|
+
agentName?: string;
|
|
66
|
+
/** The AI agent's picture beside its answers. */
|
|
67
|
+
agentPhoto?: string;
|
|
62
68
|
/** MRRocket's own preview: mount inside this element (positioned) instead of the page. */
|
|
63
69
|
container?: HTMLElement;
|
|
64
70
|
/** MRRocket's own preview: answers from here instead of the server. */
|
|
@@ -82,5 +88,7 @@ export interface MessengerHandle {
|
|
|
82
88
|
/**
|
|
83
89
|
* Mounts the messenger with its look from MRRocket (Support › Messenger) underneath what the code passes: an option the
|
|
84
90
|
* code sets wins, one it leaves out comes from MRRocket, then the default. Calls made before the look arrived wait.
|
|
91
|
+
* A repeat visit starts with the look this browser saw last; a look that arrives later than that (changed, or slower
|
|
92
|
+
* than LOOK_WAIT_MS) replaces the launcher while the panel is closed, or once it closes.
|
|
85
93
|
*/
|
|
86
94
|
export declare const mountMessenger: (o: MessengerUiOptions) => MessengerHandle;
|
package/dist/messenger-ui.js
CHANGED
|
@@ -13,9 +13,9 @@ var POLL_OPEN_MS = 4e3;
|
|
|
13
13
|
var POLL_WAITING_MS = 8e3;
|
|
14
14
|
var POLL_IDLE_MS = 3e4;
|
|
15
15
|
var WAITING_FOR_MS = 10 * 6e4;
|
|
16
|
+
var SLOW_SEND_MS = 1500;
|
|
16
17
|
var EMAIL = /^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/;
|
|
17
18
|
var SEEN_KEY = "ckw_msgr_seen";
|
|
18
|
-
var whoLabel = { visitor: "You", team: "Team", ai: "AI agent", auto: "Team" };
|
|
19
19
|
var readSeen = () => {
|
|
20
20
|
try {
|
|
21
21
|
return window.localStorage.getItem(SEEN_KEY) ?? "";
|
|
@@ -167,20 +167,52 @@ var blockOf = (b) => {
|
|
|
167
167
|
}
|
|
168
168
|
};
|
|
169
169
|
var LOOK_WAIT_MS = 2500;
|
|
170
|
+
var LOOK_KEY = "ckw_msgr_look:";
|
|
171
|
+
var readCachedLook = (apiKey) => {
|
|
172
|
+
try {
|
|
173
|
+
const raw = window.localStorage.getItem(LOOK_KEY + apiKey);
|
|
174
|
+
return raw ? JSON.parse(raw) : null;
|
|
175
|
+
} catch {
|
|
176
|
+
return null;
|
|
177
|
+
}
|
|
178
|
+
};
|
|
179
|
+
var writeCachedLook = (apiKey, look) => {
|
|
180
|
+
try {
|
|
181
|
+
window.localStorage.setItem(LOOK_KEY + apiKey, JSON.stringify(look));
|
|
182
|
+
} catch {
|
|
183
|
+
}
|
|
184
|
+
};
|
|
170
185
|
var mountMessenger = (o) => {
|
|
171
186
|
let real = null;
|
|
187
|
+
let shown = "";
|
|
172
188
|
let gone = false;
|
|
173
189
|
const queued = [];
|
|
174
190
|
const later = (fn) => real ? fn(real) : queued.push(fn);
|
|
175
191
|
const ctrl = new AbortController();
|
|
192
|
+
const caching = !o.client && !!o.apiKey;
|
|
176
193
|
const start = (look) => {
|
|
177
|
-
if (
|
|
194
|
+
if (gone) return;
|
|
195
|
+
const key = JSON.stringify(look ?? {});
|
|
196
|
+
if (real && key === shown) return;
|
|
197
|
+
if (real?.isOpen()) {
|
|
198
|
+
real.onClose(() => start(look));
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
real?.destroy();
|
|
202
|
+
shown = key;
|
|
178
203
|
const defined = Object.fromEntries(Object.entries(o).filter(([, v]) => v !== void 0));
|
|
179
204
|
real = build({ ...look ?? {}, ...defined, accent: o.accent ?? look?.accent, theme: o.theme ?? look?.theme, branding: look?.branding ?? true });
|
|
180
205
|
for (const fn of queued.splice(0)) fn(real);
|
|
181
206
|
};
|
|
182
|
-
const
|
|
183
|
-
|
|
207
|
+
const cached = caching ? readCachedLook(o.apiKey) : null;
|
|
208
|
+
if (cached) start(cached);
|
|
209
|
+
const timer = cached ? void 0 : setTimeout(() => start(null), LOOK_WAIT_MS);
|
|
210
|
+
(o.client ?? createApi({ host: o.host, apiKey: o.apiKey, distinctId: o.distinctId(), user: o.user(), userHash: o.userHash?.() })).messengerLook(ctrl.signal).then((look) => {
|
|
211
|
+
if (caching && look) writeCachedLook(o.apiKey, look);
|
|
212
|
+
start(look);
|
|
213
|
+
}).catch(() => {
|
|
214
|
+
if (!real) start(null);
|
|
215
|
+
}).finally(() => clearTimeout(timer));
|
|
184
216
|
return {
|
|
185
217
|
open: (v) => later((h) => h.open(v)),
|
|
186
218
|
close: () => real?.close(),
|
|
@@ -203,6 +235,7 @@ var build = (o) => {
|
|
|
203
235
|
const label = o.label ?? "";
|
|
204
236
|
const reply = replyLine(o.replyTime);
|
|
205
237
|
const initial = (title.trim()[0] ?? "?").toUpperCase();
|
|
238
|
+
const whoLabel = { visitor: "You", team: "Team", ai: o.agentName?.trim() || "AI agent", auto: "Team" };
|
|
206
239
|
const launcherOn = o.launcher ?? true;
|
|
207
240
|
const helpOn = o.help ?? true;
|
|
208
241
|
const soundOn = o.sound ?? true;
|
|
@@ -223,11 +256,15 @@ var build = (o) => {
|
|
|
223
256
|
o.glow?.slice(0, 2).forEach((c, i) => /^#[0-9a-f]{6}$/i.test(c) && root.style.setProperty(`--ckw-glow${i + 2}`, c));
|
|
224
257
|
(o.container ?? document.body).appendChild(root);
|
|
225
258
|
let open = false;
|
|
259
|
+
let afterClose = null;
|
|
226
260
|
let view = "home";
|
|
227
261
|
let thread = null;
|
|
228
262
|
let loaded = false;
|
|
229
263
|
let error = "";
|
|
230
|
-
|
|
264
|
+
const sayable = (e) => e.status === 401 ? "The chat is not available right now. Please try again later." : e.message;
|
|
265
|
+
let outbox = [];
|
|
266
|
+
let outboxSeq = 0;
|
|
267
|
+
let flushing = false;
|
|
231
268
|
let lastKnown = null;
|
|
232
269
|
let unseen = [];
|
|
233
270
|
let previewGone = false;
|
|
@@ -268,13 +305,22 @@ var build = (o) => {
|
|
|
268
305
|
const id = lastTeamId(thread);
|
|
269
306
|
if (id) writeSeen(id);
|
|
270
307
|
};
|
|
271
|
-
const
|
|
308
|
+
const letter = (size) => el("span", { class: `ckw-msgr-av${size ? " ckw-msgr-av-sm" : ""}`, "aria-hidden": "true" }, initial);
|
|
309
|
+
const avatar = (size) => {
|
|
310
|
+
if (!o.icon) return letter(size);
|
|
311
|
+
const av = el("span", { class: `ckw-msgr-av ckw-msgr-av-img${size ? " ckw-msgr-av-sm" : ""}`, "aria-hidden": "true" });
|
|
312
|
+
const img = el("img", { src: o.icon, alt: "" });
|
|
313
|
+
img.addEventListener("error", () => av.replaceWith(letter(size)));
|
|
314
|
+
av.append(img);
|
|
315
|
+
return av;
|
|
316
|
+
};
|
|
272
317
|
const logo = el("span", { class: "ckw-msgr-logo", "aria-hidden": "true" });
|
|
273
318
|
if (o.logo) {
|
|
274
319
|
const img = el("img", { src: o.logo, alt: "" });
|
|
275
320
|
img.addEventListener("error", () => logo.replaceChildren(avatar("sm")));
|
|
276
321
|
logo.append(img);
|
|
277
322
|
} else logo.append(avatar("sm"));
|
|
323
|
+
const agentFace = () => o.agentPhoto ? el("img", { class: "ckw-msg-face", src: o.agentPhoto, alt: "" }) : null;
|
|
278
324
|
const sky = el("div", { class: "ckw-msgr-sky", "aria-hidden": "true" });
|
|
279
325
|
const leaving = /* @__PURE__ */ new Map();
|
|
280
326
|
const enter = (node, parent = root) => {
|
|
@@ -326,7 +372,6 @@ var build = (o) => {
|
|
|
326
372
|
const drawPreview = () => {
|
|
327
373
|
const m = unseen.at(-1);
|
|
328
374
|
if (!previewShown() || !m) return leave(prev);
|
|
329
|
-
hideTeaser(true);
|
|
330
375
|
const x = el("button", { type: "button", class: "ckw-msgr-preview-x", "aria-label": "Dismiss" }, icon(I.cross, 14));
|
|
331
376
|
x.addEventListener("click", (e) => {
|
|
332
377
|
e.stopPropagation();
|
|
@@ -347,43 +392,8 @@ var build = (o) => {
|
|
|
347
392
|
prev.replaceChildren(card, x);
|
|
348
393
|
enter(prev);
|
|
349
394
|
};
|
|
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
|
-
}
|
|
384
395
|
fab.addEventListener("click", () => {
|
|
385
396
|
chime.unlock();
|
|
386
|
-
hideTeaser(true);
|
|
387
397
|
if (open) close();
|
|
388
398
|
else show(unseen.length ? "messages" : "home");
|
|
389
399
|
});
|
|
@@ -477,7 +487,7 @@ var build = (o) => {
|
|
|
477
487
|
};
|
|
478
488
|
const form = el("form", { class: "ckw-msgr-compose" });
|
|
479
489
|
const err = el("p", { class: "ckw-msgr-error", role: "alert" });
|
|
480
|
-
const email = el("input", { class: "ckw-input", type: "email", inputmode: "email", autocomplete: "email", placeholder: "
|
|
490
|
+
const email = el("input", { class: "ckw-input ckw-msgr-email", type: "email", inputmode: "email", autocomplete: "email", placeholder: "Email for our reply (optional)", "aria-label": "Your email (optional)", maxlength: "200", "data-1p-ignore": "", "data-lpignore": "true", "data-bwignore": "", "data-form-type": "other" });
|
|
481
491
|
const row = el("div", { class: "ckw-msgr-box" });
|
|
482
492
|
const text = el("textarea", { class: "ckw-textarea ckw-msgr-input", rows: "1", placeholder: "Write your message\u2026", "aria-label": "Message", maxlength: "4000" });
|
|
483
493
|
const send = el("button", { type: "submit", class: "ckw-msgr-send", "aria-label": "Send" }, icon(I.up));
|
|
@@ -638,7 +648,7 @@ var build = (o) => {
|
|
|
638
648
|
const list = el("ol", { class: "ckw-msgs", "aria-live": "polite", "aria-label": "Messages" });
|
|
639
649
|
const needEmail = () => askEmail && !o.user()?.email && !thread?.hasEmail;
|
|
640
650
|
const emailOk = () => !email.value.trim() || EMAIL.test(email.value.trim());
|
|
641
|
-
const canSend = () => (!!text.value.trim() || picked.length > 0) &&
|
|
651
|
+
const canSend = () => (!!text.value.trim() || picked.length > 0) && emailOk();
|
|
642
652
|
const go = (v) => {
|
|
643
653
|
const changed = JSON.stringify(v) !== JSON.stringify(view);
|
|
644
654
|
view = v;
|
|
@@ -660,6 +670,7 @@ var build = (o) => {
|
|
|
660
670
|
return img;
|
|
661
671
|
};
|
|
662
672
|
const bubble = (m, isPending = false) => {
|
|
673
|
+
const slow = isPending && Date.now() - Date.parse(m.at) >= SLOW_SEND_MS;
|
|
663
674
|
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);
|
|
664
675
|
if (isPending) li.setAttribute("aria-busy", "true");
|
|
665
676
|
if (m.attachments?.length)
|
|
@@ -677,7 +688,7 @@ var build = (o) => {
|
|
|
677
688
|
chip.addEventListener("click", () => go({ article: a.id }));
|
|
678
689
|
li.append(chip);
|
|
679
690
|
}
|
|
680
|
-
li.append(el("span", { class: "ckw-msg-meta" }, `${m.from !== "visitor" ? `${whoLabel[m.from]} \xB7 ` : ""}${
|
|
691
|
+
li.append(el("span", { class: "ckw-msg-meta" }, m.from === "ai" ? agentFace() : null, `${m.from !== "visitor" ? `${whoLabel[m.from]} \xB7 ` : ""}${slow ? "Sending\u2026" : timeAgo(m.at)}`));
|
|
681
692
|
return li;
|
|
682
693
|
};
|
|
683
694
|
const articleRow = (a) => {
|
|
@@ -748,7 +759,7 @@ var build = (o) => {
|
|
|
748
759
|
askIn.value = "";
|
|
749
760
|
askGo.disabled = true;
|
|
750
761
|
go("messages");
|
|
751
|
-
if (canSend())
|
|
762
|
+
if (canSend()) submit();
|
|
752
763
|
});
|
|
753
764
|
const drawHome = () => {
|
|
754
765
|
const first = o.user()?.name?.trim().split(/\s+/)[0];
|
|
@@ -878,19 +889,19 @@ var build = (o) => {
|
|
|
878
889
|
const drawMessages = () => {
|
|
879
890
|
const msgs = thread?.messages ?? [];
|
|
880
891
|
const parts = [];
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
const sig = `${msgs.map((m) => m.id).join(",")}|${
|
|
892
|
+
{
|
|
893
|
+
const waiting = outbox.map((o2) => o2.message);
|
|
894
|
+
const sig = `${msgs.map((m) => m.id).join(",")}|${waiting.map((m) => `${m.id}:${Date.now() - Date.parse(m.at) >= SLOW_SEND_MS}`).join(",")}|${Math.floor(Date.now() / 6e4)}`;
|
|
884
895
|
if (sig !== listSig) {
|
|
885
896
|
listSig = sig;
|
|
886
|
-
const greet = el("li", { class: "ckw-msg", "data-from": "them" }, el("div", { class: "ckw-bubble" }, greeting));
|
|
887
|
-
const items = [greet, ...msgs.map((m) => bubble(m)), ...
|
|
897
|
+
const greet = msgs.length || waiting.length ? [] : [el("li", { class: "ckw-msg", "data-from": "them" }, el("div", { class: "ckw-bubble" }, greeting))];
|
|
898
|
+
const items = [...greet, ...msgs.map((m) => bubble(m)), ...waiting.map((m) => bubble(m, true))];
|
|
888
899
|
if (listCount >= 0) items.slice(listCount).forEach((li) => li.setAttribute("data-new", ""));
|
|
889
900
|
listCount = items.length;
|
|
890
901
|
list.replaceChildren(...items);
|
|
891
902
|
}
|
|
892
903
|
parts.push(list);
|
|
893
|
-
const top = !msgs.some((m) => m.from === "visitor") && !
|
|
904
|
+
const top = !msgs.some((m) => m.from === "visitor") && !outbox.length ? (articles ?? []).slice(0, 3) : [];
|
|
894
905
|
if (top.length) {
|
|
895
906
|
if (suggestions.childNodes.length !== top.length)
|
|
896
907
|
suggestions.replaceChildren(
|
|
@@ -903,9 +914,9 @@ var build = (o) => {
|
|
|
903
914
|
parts.push(suggestions);
|
|
904
915
|
}
|
|
905
916
|
}
|
|
906
|
-
if (thread?.conversation?.status === "solved" && !
|
|
917
|
+
if (thread?.conversation?.status === "solved" && !outbox.length) parts.push(el("p", { class: "ckw-fine ckw-msgr-note" }, "This conversation is closed. Write to start a new one."));
|
|
907
918
|
if (body.childNodes.length !== parts.length || parts.some((x, i) => body.childNodes[i] !== x)) body.replaceChildren(...parts);
|
|
908
|
-
const count = msgs.length +
|
|
919
|
+
const count = msgs.length + outbox.length;
|
|
909
920
|
if (count !== drawn) {
|
|
910
921
|
drawn = count;
|
|
911
922
|
body.scrollTop = body.scrollHeight;
|
|
@@ -916,7 +927,7 @@ var build = (o) => {
|
|
|
916
927
|
if (needEmail()) {
|
|
917
928
|
if (emailOk()) email.removeAttribute("aria-invalid");
|
|
918
929
|
else email.setAttribute("aria-invalid", "true");
|
|
919
|
-
if (!email.isConnected)
|
|
930
|
+
if (!email.isConnected) row.insertBefore(email, text);
|
|
920
931
|
} else email.remove();
|
|
921
932
|
drawTray();
|
|
922
933
|
send.disabled = !canSend();
|
|
@@ -936,15 +947,18 @@ var build = (o) => {
|
|
|
936
947
|
if (pop.childNodes.length !== kids.length || kids.some((k, i) => pop.childNodes[i] !== k)) pop.replaceChildren(...kids);
|
|
937
948
|
};
|
|
938
949
|
const tick = async () => {
|
|
939
|
-
ctrl
|
|
940
|
-
ctrl = new AbortController();
|
|
950
|
+
if (ctrl) return;
|
|
951
|
+
const mine = ctrl = new AbortController();
|
|
941
952
|
try {
|
|
942
|
-
take(await api().chat(
|
|
953
|
+
take(await api().chat(mine.signal));
|
|
943
954
|
error = "";
|
|
944
955
|
} catch (e) {
|
|
945
956
|
if (e.name === "AbortError") return;
|
|
946
|
-
if (open && view === "messages") error = e
|
|
957
|
+
if (open && view === "messages") error = sayable(e);
|
|
958
|
+
} finally {
|
|
959
|
+
if (ctrl === mine) ctrl = null;
|
|
947
960
|
}
|
|
961
|
+
if (!loaded) listCount = -1;
|
|
948
962
|
loaded = true;
|
|
949
963
|
draw();
|
|
950
964
|
drawFab();
|
|
@@ -963,58 +977,75 @@ var build = (o) => {
|
|
|
963
977
|
else clearTimeout(poll);
|
|
964
978
|
};
|
|
965
979
|
document.addEventListener("visibilitychange", onVisible);
|
|
966
|
-
const
|
|
980
|
+
const flush = async () => {
|
|
981
|
+
if (flushing) return;
|
|
982
|
+
flushing = true;
|
|
983
|
+
while (outbox.length) {
|
|
984
|
+
const { message, files, email: given } = outbox[0];
|
|
985
|
+
await Promise.all(files.map((p) => p.up));
|
|
986
|
+
const ids = files.filter((p) => p.id).map((p) => p.id);
|
|
987
|
+
try {
|
|
988
|
+
if (!message.body && !ids.length) throw new Error(error || "The file could not be sent.");
|
|
989
|
+
const sent = await api().sendChat({
|
|
990
|
+
body: message.body,
|
|
991
|
+
email: given,
|
|
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
|
+
ctrl?.abort();
|
|
998
|
+
ctrl = null;
|
|
999
|
+
take(sent);
|
|
1000
|
+
outbox.shift();
|
|
1001
|
+
for (const p of files) {
|
|
1002
|
+
localUrls.delete(p.key);
|
|
1003
|
+
URL.revokeObjectURL(p.local);
|
|
1004
|
+
}
|
|
1005
|
+
} catch (e) {
|
|
1006
|
+
const back = outbox.splice(0);
|
|
1007
|
+
text.value = [...back.map((b) => b.message.body), text.value.trim()].filter(Boolean).join("\n");
|
|
1008
|
+
picked = [...back.flatMap((b) => b.files.filter((p) => p.id)), ...picked];
|
|
1009
|
+
for (const b of back) for (const p of b.files) localUrls.delete(p.key);
|
|
1010
|
+
error = sayable(e);
|
|
1011
|
+
}
|
|
1012
|
+
draw();
|
|
1013
|
+
}
|
|
1014
|
+
flushing = false;
|
|
1015
|
+
schedule();
|
|
1016
|
+
};
|
|
1017
|
+
const submit = () => {
|
|
967
1018
|
const msg = text.value.trim();
|
|
968
1019
|
const files = picked;
|
|
969
|
-
if (!msg && !files.length ||
|
|
1020
|
+
if (!msg && !files.length || !emailOk()) return;
|
|
970
1021
|
chime.unlock();
|
|
971
1022
|
for (const p of files) localUrls.set(p.key, p.local);
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
1023
|
+
outbox.push({
|
|
1024
|
+
message: {
|
|
1025
|
+
id: `pending-${++outboxSeq}`,
|
|
1026
|
+
from: "visitor",
|
|
1027
|
+
body: msg,
|
|
1028
|
+
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1029
|
+
...files.length ? { attachments: files.map((p) => ({ id: p.key, name: p.name, type: p.type, size: p.size })) } : {}
|
|
1030
|
+
},
|
|
1031
|
+
files,
|
|
1032
|
+
email: needEmail() && email.value.trim() ? email.value.trim() : void 0
|
|
1033
|
+
});
|
|
979
1034
|
text.value = "";
|
|
980
1035
|
picked = [];
|
|
981
1036
|
error = "";
|
|
982
1037
|
draw();
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
let sent = false;
|
|
986
|
-
try {
|
|
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;
|
|
999
|
-
} catch (e) {
|
|
1000
|
-
text.value = msg;
|
|
1001
|
-
picked = files.filter((p) => p.id);
|
|
1002
|
-
error = e.message;
|
|
1003
|
-
}
|
|
1004
|
-
pending = null;
|
|
1005
|
-
for (const p of files) localUrls.delete(p.key);
|
|
1006
|
-
draw();
|
|
1007
|
-
if (sent) for (const p of files) URL.revokeObjectURL(p.local);
|
|
1008
|
-
schedule();
|
|
1038
|
+
setTimeout(draw, SLOW_SEND_MS + 50);
|
|
1039
|
+
void flush();
|
|
1009
1040
|
};
|
|
1010
1041
|
form.addEventListener("submit", (e) => {
|
|
1011
1042
|
e.preventDefault();
|
|
1012
|
-
|
|
1043
|
+
submit();
|
|
1013
1044
|
});
|
|
1014
1045
|
text.addEventListener("keydown", (e) => {
|
|
1015
1046
|
if (e.key === "Enter" && !e.shiftKey && !e.isComposing) {
|
|
1016
1047
|
e.preventDefault();
|
|
1017
|
-
|
|
1048
|
+
submit();
|
|
1018
1049
|
}
|
|
1019
1050
|
});
|
|
1020
1051
|
text.addEventListener("input", () => send.disabled = !canSend());
|
|
@@ -1046,7 +1077,6 @@ var build = (o) => {
|
|
|
1046
1077
|
view = v === "help" && !helpOn ? "home" : v;
|
|
1047
1078
|
drawn = -1;
|
|
1048
1079
|
previewGone = true;
|
|
1049
|
-
hideTeaser(true);
|
|
1050
1080
|
pop.toggleAttribute("data-still", still);
|
|
1051
1081
|
enter(pop);
|
|
1052
1082
|
if (was) replay(body, "ckw-msgr-enter");
|
|
@@ -1067,6 +1097,9 @@ var build = (o) => {
|
|
|
1067
1097
|
drawFab();
|
|
1068
1098
|
schedule();
|
|
1069
1099
|
if (launcherOn) fab.focus();
|
|
1100
|
+
const fn = afterClose;
|
|
1101
|
+
afterClose = null;
|
|
1102
|
+
if (fn) setTimeout(fn, 220);
|
|
1070
1103
|
}
|
|
1071
1104
|
pop.addEventListener("click", () => amenuOpen() && closeAmenu());
|
|
1072
1105
|
const onKey = (e) => {
|
|
@@ -1088,9 +1121,12 @@ var build = (o) => {
|
|
|
1088
1121
|
open: (v) => show(v ?? "home"),
|
|
1089
1122
|
close,
|
|
1090
1123
|
toggle: () => open ? close() : show(),
|
|
1124
|
+
isOpen: () => open,
|
|
1125
|
+
onClose: (fn) => {
|
|
1126
|
+
afterClose = fn;
|
|
1127
|
+
},
|
|
1091
1128
|
destroy: () => {
|
|
1092
1129
|
clearTimeout(poll);
|
|
1093
|
-
clearTimeout(teaserTimer);
|
|
1094
1130
|
for (const t of leaving.values()) clearTimeout(t);
|
|
1095
1131
|
ctrl?.abort();
|
|
1096
1132
|
for (const p of picked) forget(p);
|