@mrrocket/sdk 0.18.0 → 0.22.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 +29 -0
- package/README.md +36 -1
- package/dist/api-client.d.ts +28 -4
- package/dist/api-client.js +30 -13
- package/dist/api-client.js.map +1 -1
- package/dist/browser/mrrocket-flows.js +8 -3
- package/dist/browser/mrrocket-messenger.js +38 -5
- package/dist/browser/mrrocket.js +1 -1
- package/dist/browser.d.ts +2 -2
- package/dist/browser.js +2 -1
- package/dist/browser.js.map +1 -1
- package/dist/client.d.ts +17 -1
- package/dist/client.js +25 -5
- package/dist/client.js.map +1 -1
- package/dist/core.d.ts +5 -1
- package/dist/core.js +1 -1
- package/dist/core.js.map +1 -1
- package/dist/feedback.js +161 -119
- package/dist/feedback.js.map +1 -1
- package/dist/flows-ui.d.ts +1 -1
- package/dist/flows-ui.js +100 -72
- package/dist/flows-ui.js.map +1 -1
- package/dist/flows-web.js +2 -1
- package/dist/flows-web.js.map +1 -1
- package/dist/flows.d.ts +38 -6
- package/dist/flows.js +51 -17
- package/dist/flows.js.map +1 -1
- package/dist/i18n.d.ts +22 -0
- package/dist/i18n.js +74 -0
- package/dist/i18n.js.map +1 -0
- package/dist/index.d.ts +4 -1
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -1
- package/dist/messenger-ui.d.ts +9 -3
- package/dist/messenger-ui.js +324 -152
- package/dist/messenger-ui.js.map +1 -1
- package/dist/messenger.d.ts +3 -1
- package/dist/messenger.js +3 -2
- package/dist/messenger.js.map +1 -1
- package/dist/onboarding-report.d.ts +19 -0
- package/dist/onboarding-report.js +23 -0
- package/dist/onboarding-report.js.map +1 -0
- package/dist/onboarding-state.d.ts +3 -0
- package/dist/onboarding-state.js +7 -1
- package/dist/onboarding-state.js.map +1 -1
- package/dist/paywall.js +2 -1
- package/dist/paywall.js.map +1 -1
- package/dist/react-native/index.d.cts +93 -8
- package/dist/react-native/index.d.ts +93 -8
- package/dist/react-native/index.js +819 -511
- package/dist/react-native/index.js.map +1 -1
- package/dist/server.d.ts +4 -2
- package/dist/server.js +2 -2
- package/dist/server.js.map +1 -1
- package/dist/styles-css.d.ts +1 -1
- package/dist/styles-css.js +35 -2
- package/dist/styles-css.js.map +1 -1
- package/dist/survey.js +18 -14
- package/dist/survey.js.map +1 -1
- package/dist/ui.d.ts +2 -0
- package/dist/ui.js +15 -12
- package/dist/ui.js.map +1 -1
- package/package.json +6 -3
package/dist/messenger-ui.js
CHANGED
|
@@ -8,36 +8,40 @@ import {
|
|
|
8
8
|
inkOn,
|
|
9
9
|
replyLine
|
|
10
10
|
} from "./api-client.js";
|
|
11
|
+
import { currentLocale, onWords, t, tf, wordsLang, wordsVersion } from "./i18n.js";
|
|
11
12
|
import { injectStyles } from "./styles-css.js";
|
|
13
|
+
var UPDATES_SHOWN = 20;
|
|
12
14
|
var POLL_OPEN_MS = 4e3;
|
|
13
15
|
var POLL_WAITING_MS = 8e3;
|
|
14
16
|
var POLL_IDLE_MS = 3e4;
|
|
17
|
+
var POLL_QUIET_MS = 6e4;
|
|
15
18
|
var WAITING_FOR_MS = 10 * 6e4;
|
|
16
19
|
var SLOW_SEND_MS = 1500;
|
|
17
20
|
var EMAIL = /^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/;
|
|
18
21
|
var SEEN_KEY = "ckw_msgr_seen";
|
|
22
|
+
var SEEN_CHATS_KEY = "ckw_msgr_seen_chats";
|
|
19
23
|
var readSeen = () => {
|
|
20
24
|
try {
|
|
21
|
-
return window.localStorage.getItem(SEEN_KEY) ?? "";
|
|
25
|
+
return { old: window.localStorage.getItem(SEEN_KEY) ?? "", chats: JSON.parse(window.localStorage.getItem(SEEN_CHATS_KEY) ?? "{}") };
|
|
22
26
|
} catch {
|
|
23
|
-
return "";
|
|
27
|
+
return { old: "", chats: {} };
|
|
24
28
|
}
|
|
25
29
|
};
|
|
26
|
-
var writeSeen = (
|
|
30
|
+
var writeSeen = (chats) => {
|
|
27
31
|
try {
|
|
28
|
-
window.localStorage.setItem(
|
|
32
|
+
window.localStorage.setItem(SEEN_CHATS_KEY, JSON.stringify(chats));
|
|
29
33
|
} catch {
|
|
30
34
|
}
|
|
31
35
|
};
|
|
32
36
|
var timeAgo = (iso) => {
|
|
33
|
-
const
|
|
34
|
-
if (Number.isNaN(
|
|
35
|
-
const d = Math.max(0, Date.now() -
|
|
36
|
-
if (d < 60) return "just now";
|
|
37
|
-
if (d < 3600) return
|
|
38
|
-
if (d < 86400) return
|
|
39
|
-
if (d < 86400 * 30) return
|
|
40
|
-
return new Date(iso).toLocaleDateString(
|
|
37
|
+
const at = new Date(iso).getTime();
|
|
38
|
+
if (Number.isNaN(at)) return "";
|
|
39
|
+
const d = Math.max(0, Date.now() - at) / 1e3;
|
|
40
|
+
if (d < 60) return t("just now");
|
|
41
|
+
if (d < 3600) return tf("{n}m ago", { n: Math.floor(d / 60) });
|
|
42
|
+
if (d < 86400) return tf("{n}h ago", { n: Math.floor(d / 3600) });
|
|
43
|
+
if (d < 86400 * 30) return tf("{n}d ago", { n: Math.floor(d / 86400) });
|
|
44
|
+
return new Date(iso).toLocaleDateString(currentLocale(), { month: "short", day: "numeric", year: "numeric" });
|
|
41
45
|
};
|
|
42
46
|
var el = (tag, attrs = {}, ...kids) => {
|
|
43
47
|
const e = document.createElement(tag);
|
|
@@ -72,11 +76,12 @@ var I = {
|
|
|
72
76
|
minus: "M5 12h14",
|
|
73
77
|
down: "m6 9 6 6 6-6",
|
|
74
78
|
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",
|
|
79
|
+
updates: "m3 11 18-5v12L3 14zM11.6 16.8a3 3 0 1 1-5.8-1.6",
|
|
75
80
|
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
81
|
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
82
|
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"
|
|
78
83
|
};
|
|
79
|
-
var lineOf = (m) => m.body || (m.attachments?.length === 1 ?
|
|
84
|
+
var lineOf = (m) => m.body || (m.attachments?.length === 1 ? tf("Sent {name}", { name: m.attachments[0].name }) : m.attachments?.length ? tf("Sent {n} files", { n: m.attachments.length }) : "");
|
|
80
85
|
var canShoot = () => typeof navigator !== "undefined" && typeof navigator.mediaDevices?.getDisplayMedia === "function";
|
|
81
86
|
var BUBBLE = "M7.9 20A9 9 0 1 0 4 16.1L2 22z";
|
|
82
87
|
var filled = (d, size) => {
|
|
@@ -100,6 +105,7 @@ 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 1
|
|
|
100
105
|
var SMILE = "M9.5 15.5c1.4 1 3.6 1 5 0";
|
|
101
106
|
var DISC = "M12 22a10 10 0 1 0 0-20 10 10 0 0 0 0 20z";
|
|
102
107
|
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";
|
|
108
|
+
var MEGAPHONE = "m3 11 18-5v12L3 14zM5.8 15.2a3 3 0 0 0 5.8 1.6z";
|
|
103
109
|
var chime = /* @__PURE__ */ (() => {
|
|
104
110
|
let ctx = null;
|
|
105
111
|
const get = () => {
|
|
@@ -121,11 +127,11 @@ var chime = /* @__PURE__ */ (() => {
|
|
|
121
127
|
try {
|
|
122
128
|
const c = get();
|
|
123
129
|
if (!c || c.state !== "running") return;
|
|
124
|
-
const
|
|
130
|
+
const t2 = c.currentTime;
|
|
125
131
|
[880, 1318.5].forEach((f, i) => {
|
|
126
132
|
const o = c.createOscillator();
|
|
127
133
|
const g = c.createGain();
|
|
128
|
-
const at =
|
|
134
|
+
const at = t2 + i * 0.11;
|
|
129
135
|
o.type = "sine";
|
|
130
136
|
o.frequency.value = f;
|
|
131
137
|
g.gain.setValueAtTime(1e-4, at);
|
|
@@ -190,9 +196,11 @@ var mountMessenger = (o) => {
|
|
|
190
196
|
const later = (fn) => real ? fn(real) : queued.push(fn);
|
|
191
197
|
const ctrl = new AbortController();
|
|
192
198
|
const caching = !o.client && !!o.apiKey;
|
|
199
|
+
let lastLook = null;
|
|
193
200
|
const start = (look) => {
|
|
194
201
|
if (gone) return;
|
|
195
|
-
|
|
202
|
+
lastLook = look;
|
|
203
|
+
const key = `${wordsVersion()}:${JSON.stringify(look ?? {})}`;
|
|
196
204
|
if (real && key === shown) return;
|
|
197
205
|
if (real?.isOpen()) {
|
|
198
206
|
real.onClose(() => start(look));
|
|
@@ -207,18 +215,27 @@ var mountMessenger = (o) => {
|
|
|
207
215
|
const cached = caching ? readCachedLook(o.apiKey) : null;
|
|
208
216
|
if (cached) start(cached);
|
|
209
217
|
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) => {
|
|
218
|
+
const readLook = () => (o.client ?? createApi({ host: o.host, apiKey: o.apiKey, distinctId: o.distinctId(), user: o.user(), userHash: o.userHash?.() })).messengerLook(ctrl.signal).then((look) => {
|
|
211
219
|
if (caching && look) writeCachedLook(o.apiKey, look);
|
|
212
220
|
start(look);
|
|
213
221
|
}).catch(() => {
|
|
214
|
-
|
|
222
|
+
start(lastLook);
|
|
215
223
|
}).finally(() => clearTimeout(timer));
|
|
224
|
+
let readIn = currentLocale();
|
|
225
|
+
void readLook();
|
|
226
|
+
const offWords = onWords(() => {
|
|
227
|
+
if (currentLocale() !== readIn) {
|
|
228
|
+
readIn = currentLocale();
|
|
229
|
+
void readLook();
|
|
230
|
+
} else if (real) start(lastLook);
|
|
231
|
+
});
|
|
216
232
|
return {
|
|
217
233
|
open: (v) => later((h) => h.open(v)),
|
|
218
234
|
close: () => real?.close(),
|
|
219
235
|
toggle: () => later((h) => h.toggle()),
|
|
220
236
|
destroy: () => {
|
|
221
237
|
gone = true;
|
|
238
|
+
offWords();
|
|
222
239
|
ctrl.abort();
|
|
223
240
|
clearTimeout(timer);
|
|
224
241
|
real?.destroy();
|
|
@@ -229,15 +246,16 @@ var PLACES = ["top-left", "top-center", "top-right", "middle-left", "middle-righ
|
|
|
229
246
|
var build = (o) => {
|
|
230
247
|
injectStyles();
|
|
231
248
|
const position = PLACES.find((p) => p === o.position) ?? "bottom-right";
|
|
232
|
-
const title = o.title || "Support";
|
|
233
|
-
const greeting = o.greeting || "Hi! How can we help?";
|
|
249
|
+
const title = o.title || t("Support");
|
|
250
|
+
const greeting = o.greeting || t("Hi! How can we help?");
|
|
234
251
|
const askEmail = o.askEmail ?? true;
|
|
235
252
|
const label = o.label ?? "";
|
|
236
253
|
const reply = replyLine(o.replyTime);
|
|
237
254
|
const initial = (title.trim()[0] ?? "?").toUpperCase();
|
|
238
|
-
const whoLabel = { visitor: "You", team: "Team", ai: o.agentName?.trim() || "AI agent", auto: "Team" };
|
|
255
|
+
const whoLabel = { visitor: t("You"), team: t("Team"), ai: o.agentName?.trim() || t("AI agent"), auto: t("Team") };
|
|
239
256
|
const launcherOn = o.launcher ?? true;
|
|
240
257
|
const helpOn = o.help ?? true;
|
|
258
|
+
const updatesOn = o.updates ?? true;
|
|
241
259
|
const soundOn = o.sound ?? true;
|
|
242
260
|
const previewOn = o.preview ?? true;
|
|
243
261
|
const api = () => o.client ?? createApi({ host: o.host, apiKey: o.apiKey, distinctId: o.distinctId(), user: o.user(), userHash: o.userHash?.() });
|
|
@@ -246,7 +264,9 @@ var build = (o) => {
|
|
|
246
264
|
"data-theme": o.theme ?? "auto",
|
|
247
265
|
"data-corners": o.corners === "soft" || o.corners === "square" ? o.corners : "round",
|
|
248
266
|
"data-font": o.font === "system" ? "system" : "site",
|
|
249
|
-
"data-header": o.header === "color" ? "color" : "plain"
|
|
267
|
+
"data-header": o.header === "color" ? "color" : "plain",
|
|
268
|
+
// Screen readers read the panel in the language of its words. Built anew when they change.
|
|
269
|
+
...wordsLang() ? { lang: wordsLang() } : {}
|
|
250
270
|
});
|
|
251
271
|
if (o.container) root.setAttribute("data-contained", "");
|
|
252
272
|
if (o.accent) {
|
|
@@ -259,52 +279,96 @@ var build = (o) => {
|
|
|
259
279
|
let afterClose = null;
|
|
260
280
|
let view = "home";
|
|
261
281
|
let thread = null;
|
|
282
|
+
let chats = [];
|
|
283
|
+
let hasEmail = false;
|
|
284
|
+
let chatId = null;
|
|
285
|
+
let draft = null;
|
|
286
|
+
let drafts = 0;
|
|
287
|
+
let listing = false;
|
|
262
288
|
let loaded = false;
|
|
263
289
|
let error = "";
|
|
264
|
-
const sayable = (e) => e.status === 401 ? "The chat is not available right now. Please try again later." : e.message;
|
|
290
|
+
const sayable = (e) => e.status === 401 ? t("The chat is not available right now. Please try again later.") : e.message;
|
|
265
291
|
let outbox = [];
|
|
266
292
|
let outboxSeq = 0;
|
|
267
293
|
let flushing = false;
|
|
268
|
-
|
|
294
|
+
const known = /* @__PURE__ */ new Map();
|
|
295
|
+
let seen = readSeen();
|
|
269
296
|
let unseen = [];
|
|
270
297
|
let previewGone = false;
|
|
271
298
|
let poll;
|
|
299
|
+
let refused = false;
|
|
272
300
|
let ctrl = null;
|
|
273
301
|
let articles = null;
|
|
274
302
|
let query = "";
|
|
275
303
|
let rated = /* @__PURE__ */ new Set();
|
|
276
304
|
let hasArticles = null;
|
|
305
|
+
let updates = null;
|
|
306
|
+
let updatesNode = null;
|
|
277
307
|
let collection = null;
|
|
278
308
|
let wide = false;
|
|
279
|
-
const
|
|
280
|
-
const lastTeamId = (t) => teamMsgs(t).at(-1)?.id ?? "";
|
|
309
|
+
const isAnswer = (m) => m.from === "team" || m.from === "ai";
|
|
281
310
|
const watching = () => open && view === "messages" && document.visibilityState !== "hidden";
|
|
282
|
-
const
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
311
|
+
const shownChat = () => listing || draft !== null ? null : chatId ?? thread?.conversation?.id ?? null;
|
|
312
|
+
const unseenIn = (chat) => unseen.filter((m) => m.chat === chat).length;
|
|
313
|
+
const learn = (chat, answers, newest, settled) => {
|
|
314
|
+
const was = known.get(chat);
|
|
315
|
+
const after = (at) => answers.filter((m) => m.at > at);
|
|
316
|
+
if (was === void 0) {
|
|
317
|
+
const read = seen.chats[chat];
|
|
318
|
+
const fresh = settled ? [] : read ? after(read) : answers.slice(answers.findIndex((m) => m.id === seen.old) + 1);
|
|
319
|
+
unseen = [...unseen, ...fresh.map((m) => ({ ...m, chat }))];
|
|
320
|
+
if (loaded && fresh.length) {
|
|
321
|
+
if (soundOn) chime.play();
|
|
322
|
+
previewGone = false;
|
|
323
|
+
}
|
|
288
324
|
} else {
|
|
289
|
-
const
|
|
290
|
-
const fresh = team.slice(i + 1).filter((m) => m.from !== "auto");
|
|
325
|
+
const fresh = after(was);
|
|
291
326
|
if (fresh.length) {
|
|
292
327
|
if (soundOn) chime.play();
|
|
293
|
-
if (!watching()) {
|
|
294
|
-
unseen = [...unseen, ...fresh];
|
|
328
|
+
if (!watching() || shownChat() !== chat) {
|
|
329
|
+
unseen = [...unseen, ...fresh.map((m) => ({ ...m, chat }))];
|
|
295
330
|
previewGone = false;
|
|
296
331
|
}
|
|
297
332
|
}
|
|
298
333
|
}
|
|
299
|
-
|
|
300
|
-
|
|
334
|
+
known.set(chat, newest ?? was ?? "");
|
|
335
|
+
};
|
|
336
|
+
const take = (t2) => {
|
|
337
|
+
const read = t2.conversation;
|
|
338
|
+
const last = t2.messages.at(-1);
|
|
339
|
+
chats = t2.conversations ?? (read && last ? [{ id: read.id, status: read.status, subject: lineOf(t2.messages[0]), last }] : []);
|
|
340
|
+
hasEmail = t2.hasEmail;
|
|
341
|
+
chats.forEach((c, i) => {
|
|
342
|
+
if (c.id !== read?.id) learn(c.id, c.last && isAnswer(c.last) ? [c.last] : [], c.last?.at, c.status === "solved" && i > 0);
|
|
343
|
+
});
|
|
344
|
+
if (read) learn(read.id, t2.messages.filter(isAnswer), last?.at, read.status === "solved" && chats[0]?.id !== read.id);
|
|
345
|
+
if (chatId === null || read?.id === chatId) thread = t2;
|
|
301
346
|
if (watching()) markSeen();
|
|
302
347
|
};
|
|
303
348
|
const markSeen = () => {
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
349
|
+
const chat = shownChat();
|
|
350
|
+
if (!chat) return;
|
|
351
|
+
unseen = unseen.filter((m) => m.chat !== chat);
|
|
352
|
+
const at = known.get(chat);
|
|
353
|
+
if (!at || seen.chats[chat] === at) return;
|
|
354
|
+
const kept = Object.fromEntries(Object.entries(seen.chats).filter(([c]) => chats.some((x) => x.id === c)));
|
|
355
|
+
seen = { ...seen, chats: { ...kept, [chat]: at } };
|
|
356
|
+
writeSeen(seen.chats);
|
|
307
357
|
};
|
|
358
|
+
const enterChat = (id, d, list2 = false) => {
|
|
359
|
+
if (id !== chatId || d !== draft) {
|
|
360
|
+
ctrl?.abort();
|
|
361
|
+
ctrl = null;
|
|
362
|
+
thread = null;
|
|
363
|
+
listSig = "";
|
|
364
|
+
listCount = -1;
|
|
365
|
+
}
|
|
366
|
+
chatId = id;
|
|
367
|
+
draft = d;
|
|
368
|
+
listing = list2;
|
|
369
|
+
};
|
|
370
|
+
const enterMessages = () => enterChat(null, null, chats.length > 1);
|
|
371
|
+
const startChat = () => enterChat(null, ++drafts);
|
|
308
372
|
const letter = (size) => el("span", { class: `ckw-msgr-av${size ? " ckw-msgr-av-sm" : ""}`, "aria-hidden": "true" }, initial);
|
|
309
373
|
const avatar = (size) => {
|
|
310
374
|
if (!o.icon) return letter(size);
|
|
@@ -365,14 +429,14 @@ var build = (o) => {
|
|
|
365
429
|
if (!fabCount.isConnected) fab.append(fabCount);
|
|
366
430
|
} else fabCount.remove();
|
|
367
431
|
fab.setAttribute("aria-expanded", String(open));
|
|
368
|
-
fab.setAttribute("aria-label", open ? "Close chat" : unseen.length ?
|
|
432
|
+
fab.setAttribute("aria-label", open ? t("Close chat") : unseen.length === 1 ? t("Open chat, 1 new reply") : unseen.length ? tf("Open chat, {n} new replies", { n: unseen.length }) : t("Open chat"));
|
|
369
433
|
drawPreview();
|
|
370
434
|
};
|
|
371
435
|
const previewShown = () => previewOn && !open && !previewGone && !!unseen.length && launcherOn;
|
|
372
436
|
const drawPreview = () => {
|
|
373
437
|
const m = unseen.at(-1);
|
|
374
438
|
if (!previewShown() || !m) return leave(prev);
|
|
375
|
-
const x = el("button", { type: "button", class: "ckw-msgr-preview-x", "aria-label": "Dismiss" }, icon(I.cross, 14));
|
|
439
|
+
const x = el("button", { type: "button", class: "ckw-msgr-preview-x", "aria-label": t("Dismiss") }, icon(I.cross, 14));
|
|
376
440
|
x.addEventListener("click", (e) => {
|
|
377
441
|
e.stopPropagation();
|
|
378
442
|
previewGone = true;
|
|
@@ -384,18 +448,23 @@ var build = (o) => {
|
|
|
384
448
|
el(
|
|
385
449
|
"span",
|
|
386
450
|
{ class: "ckw-msgr-preview-main" },
|
|
387
|
-
el("span", { class: "ckw-msgr-preview-who" }, `${whoLabel[m.from]}${unseen.length > 1 ? ` \xB7 ${unseen.length}
|
|
451
|
+
el("span", { class: "ckw-msgr-preview-who" }, `${whoLabel[m.from]}${unseen.length > 1 ? ` \xB7 ${tf("{n} new", { n: unseen.length })}` : ""}`),
|
|
388
452
|
el("span", { class: "ckw-msgr-preview-text" }, lineOf(m))
|
|
389
453
|
)
|
|
390
454
|
);
|
|
391
|
-
card.addEventListener("click", () =>
|
|
455
|
+
card.addEventListener("click", () => {
|
|
456
|
+
enterChat(m.chat, null);
|
|
457
|
+
show("messages");
|
|
458
|
+
});
|
|
392
459
|
prev.replaceChildren(card, x);
|
|
393
460
|
enter(prev);
|
|
394
461
|
};
|
|
395
462
|
fab.addEventListener("click", () => {
|
|
396
463
|
chime.unlock();
|
|
397
|
-
if (open) close();
|
|
398
|
-
|
|
464
|
+
if (open) return close();
|
|
465
|
+
const news = unseen.at(-1);
|
|
466
|
+
if (news) enterChat(news.chat, null);
|
|
467
|
+
show(news ? "messages" : "home");
|
|
399
468
|
});
|
|
400
469
|
if (launcherOn) root.appendChild(fab);
|
|
401
470
|
const pop = el("div", { class: "ckw-pop ckw-msgr", "data-pos": position, role: "dialog", "aria-label": title });
|
|
@@ -404,21 +473,23 @@ var build = (o) => {
|
|
|
404
473
|
pop.addEventListener("click", () => chime.unlock());
|
|
405
474
|
const lead = el("span", { class: "ckw-msgr-lead" });
|
|
406
475
|
const mid = el("span", { class: "ckw-msgr-mid" });
|
|
407
|
-
const tabs = el("nav", { class: "ckw-msgr-tabs", "aria-label": "Messenger" });
|
|
476
|
+
const tabs = el("nav", { class: "ckw-msgr-tabs", "aria-label": t("Messenger") });
|
|
408
477
|
const tab = (v, name, line, shape, cut) => {
|
|
409
478
|
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));
|
|
410
479
|
b.addEventListener("click", () => {
|
|
411
480
|
collection = null;
|
|
481
|
+
if (v === "messages") enterMessages();
|
|
412
482
|
go(v);
|
|
413
483
|
});
|
|
414
484
|
return b;
|
|
415
485
|
};
|
|
416
|
-
const homeTab = tab("home", "Home", I.home, HOUSE, SMILE);
|
|
417
|
-
const chatTab = tab("messages", "Messages", I.chat, BUBBLE, DOTS);
|
|
418
|
-
const helpTab = tab("help", "Help", I.help, DISC, QMARK);
|
|
486
|
+
const homeTab = tab("home", t("Home"), I.home, HOUSE, SMILE);
|
|
487
|
+
const chatTab = tab("messages", t("Messages"), I.chat, BUBBLE, DOTS);
|
|
488
|
+
const helpTab = tab("help", t("Help"), I.help, DISC, QMARK);
|
|
489
|
+
const updatesTab = tab("updates", t("Updates"), I.updates, MEGAPHONE, "");
|
|
419
490
|
const tabCount = el("span", { class: "ckw-msgr-count ckw-msgr-count-tab", "aria-hidden": "true" });
|
|
420
|
-
const menuBtn = el("button", { type: "button", class: "ckw-msgr-ib", "aria-label": "More", "aria-haspopup": "menu", "aria-expanded": "false" }, icon(I.dots, 18));
|
|
421
|
-
const xBtn = el("button", { type: "button", class: "ckw-msgr-ib ckw-msgr-x", "aria-label": "Close messenger" }, icon(I.cross));
|
|
491
|
+
const menuBtn = el("button", { type: "button", class: "ckw-msgr-ib", "aria-label": t("More"), "aria-haspopup": "menu", "aria-expanded": "false" }, icon(I.dots, 18));
|
|
492
|
+
const xBtn = el("button", { type: "button", class: "ckw-msgr-ib ckw-msgr-x", "aria-label": t("Close messenger") }, icon(I.cross));
|
|
422
493
|
xBtn.addEventListener("click", () => close());
|
|
423
494
|
bar.append(lead, mid, el("span", { class: "ckw-msgr-trail" }, menuBtn, xBtn));
|
|
424
495
|
const menu = el("div", { class: "ckw-msgr-menu", role: "menu" });
|
|
@@ -440,9 +511,9 @@ var build = (o) => {
|
|
|
440
511
|
e.stopPropagation();
|
|
441
512
|
if (menuOpen()) return closeMenu();
|
|
442
513
|
menu.replaceChildren(
|
|
443
|
-
el("span", { class: "ckw-msgr-menu-h" }, "Quick actions"),
|
|
444
|
-
menuItem(I.minus, "Minimize chat", () => close()),
|
|
445
|
-
menuItem(wide ? I.collapse : I.expand, wide ? "Collapse view" : "Expand view", () => {
|
|
514
|
+
el("span", { class: "ckw-msgr-menu-h" }, t("Quick actions")),
|
|
515
|
+
menuItem(I.minus, t("Minimize chat"), () => close()),
|
|
516
|
+
menuItem(wide ? I.collapse : I.expand, wide ? t("Collapse view") : t("Expand view"), () => {
|
|
446
517
|
wide = !wide;
|
|
447
518
|
pop.toggleAttribute("data-wide", wide);
|
|
448
519
|
})
|
|
@@ -460,26 +531,37 @@ var build = (o) => {
|
|
|
460
531
|
};
|
|
461
532
|
if (typeof view !== "string")
|
|
462
533
|
lead.replaceChildren(
|
|
463
|
-
back(() => go("help"), "Back to help")
|
|
534
|
+
back(() => go("help"), t("Back to help"))
|
|
464
535
|
);
|
|
465
536
|
else if (view === "help" && collection && !query.trim())
|
|
466
537
|
lead.replaceChildren(
|
|
467
538
|
back(() => {
|
|
468
539
|
collection = null;
|
|
469
540
|
go("help");
|
|
470
|
-
}, "All collections")
|
|
541
|
+
}, t("All collections"))
|
|
542
|
+
);
|
|
543
|
+
else if (view === "messages" && !listing && chats.length > 1)
|
|
544
|
+
lead.replaceChildren(
|
|
545
|
+
back(() => {
|
|
546
|
+
enterMessages();
|
|
547
|
+
go("messages");
|
|
548
|
+
}, t("All conversations"))
|
|
471
549
|
);
|
|
472
|
-
else if (view === "messages") lead.replaceChildren(back(() => go("home"), "Back to home"));
|
|
550
|
+
else if (view === "messages") lead.replaceChildren(back(() => go("home"), t("Back to home")));
|
|
473
551
|
else if (view === "home") lead.replaceChildren(logo);
|
|
474
552
|
else lead.replaceChildren();
|
|
475
|
-
if (view === "messages") mid.replaceChildren(
|
|
476
|
-
else if (view === "
|
|
553
|
+
if (view === "messages" && listing) mid.replaceChildren(el("b", {}, t("Messages")));
|
|
554
|
+
else if (view === "messages") mid.replaceChildren(avatar("sm"), el("span", { class: "ckw-msgr-mid-t" }, el("b", {}, title), el("span", { class: "ckw-msgr-online" }, reply)));
|
|
555
|
+
else if (view === "help") mid.replaceChildren(el("b", {}, t("Help")));
|
|
556
|
+
else if (view === "updates") mid.replaceChildren(el("b", {}, t("Updates")));
|
|
477
557
|
else mid.replaceChildren();
|
|
478
|
-
const items = helpOn && hasArticles !== false ? [
|
|
479
|
-
if (tabs.childNodes.length !== items.length) tabs.replaceChildren(...items);
|
|
558
|
+
const items = [homeTab, chatTab, ...helpOn && hasArticles !== false ? [helpTab] : [], ...updatesOn && updates?.length ? [updatesTab] : []];
|
|
559
|
+
if (tabs.childNodes.length !== items.length || items.some((t2, i) => tabs.childNodes[i] !== t2)) tabs.replaceChildren(...items);
|
|
560
|
+
tabs.toggleAttribute("data-four", items.length > 3);
|
|
480
561
|
homeTab.setAttribute("aria-current", view === "home" ? "page" : "false");
|
|
481
562
|
chatTab.setAttribute("aria-current", view === "messages" ? "page" : "false");
|
|
482
|
-
helpTab.setAttribute("aria-current", view !== "home" && view !== "messages" ? "page" : "false");
|
|
563
|
+
helpTab.setAttribute("aria-current", view !== "home" && view !== "messages" && view !== "updates" ? "page" : "false");
|
|
564
|
+
updatesTab.setAttribute("aria-current", view === "updates" ? "page" : "false");
|
|
483
565
|
if (unseen.length && view !== "messages") {
|
|
484
566
|
tabCount.textContent = String(Math.min(unseen.length, 9));
|
|
485
567
|
if (!tabCount.isConnected) chatTab.firstChild.appendChild(tabCount);
|
|
@@ -487,14 +569,14 @@ var build = (o) => {
|
|
|
487
569
|
};
|
|
488
570
|
const form = el("form", { class: "ckw-msgr-compose" });
|
|
489
571
|
const err = el("p", { class: "ckw-msgr-error", role: "alert" });
|
|
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" });
|
|
572
|
+
const email = el("input", { class: "ckw-input ckw-msgr-email", type: "email", inputmode: "email", autocomplete: "email", placeholder: t("Email for our reply (optional)"), "aria-label": t("Your email (optional)"), maxlength: "200", "data-1p-ignore": "", "data-lpignore": "true", "data-bwignore": "", "data-form-type": "other" });
|
|
491
573
|
const row = el("div", { class: "ckw-msgr-box" });
|
|
492
|
-
const text = el("textarea", { class: "ckw-textarea ckw-msgr-input", rows: "1", placeholder: "Write your message\u2026", "aria-label": "Message", maxlength: "4000" });
|
|
493
|
-
const send = el("button", { type: "submit", class: "ckw-msgr-send", "aria-label": "Send" }, icon(I.up));
|
|
574
|
+
const text = el("textarea", { class: "ckw-textarea ckw-msgr-input", rows: "1", placeholder: t("Write your message\u2026"), "aria-label": t("Message"), maxlength: "4000" });
|
|
575
|
+
const send = el("button", { type: "submit", class: "ckw-msgr-send", "aria-label": t("Send") }, icon(I.up));
|
|
494
576
|
const shots = canShoot();
|
|
495
577
|
const clip = el(
|
|
496
578
|
"button",
|
|
497
|
-
{ 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" } : {} },
|
|
579
|
+
{ type: "button", class: "ckw-msgr-clip", "aria-label": shots ? t("Attach a file or a screenshot") : t("Attach a file"), ...shots ? { "aria-haspopup": "menu", "aria-expanded": "false" } : {} },
|
|
498
580
|
icon(I.clip, 18)
|
|
499
581
|
);
|
|
500
582
|
row.append(text, el("div", { class: "ckw-msgr-tools" }, clip, el("span", { class: "ckw-msgr-sp" }), send));
|
|
@@ -502,7 +584,7 @@ var build = (o) => {
|
|
|
502
584
|
form.append(row);
|
|
503
585
|
const picker = el("input", { type: "file", accept: CHAT_FILE_TYPES.join(","), multiple: "", hidden: "", tabindex: "-1", "aria-hidden": "true" });
|
|
504
586
|
form.append(picker);
|
|
505
|
-
const tray = el("div", { class: "ckw-msgr-files", role: "list", "aria-label": "Files to send" });
|
|
587
|
+
const tray = el("div", { class: "ckw-msgr-files", role: "list", "aria-label": t("Files to send") });
|
|
506
588
|
let picked = [];
|
|
507
589
|
const localUrls = /* @__PURE__ */ new Map();
|
|
508
590
|
const fileUrl = (id) => localUrls.get(id) ?? api().attachmentUrl(id);
|
|
@@ -519,15 +601,15 @@ var build = (o) => {
|
|
|
519
601
|
for (const f of list2) {
|
|
520
602
|
const name = f.name || "screenshot.png";
|
|
521
603
|
if (picked.length >= CHAT_FILES_PER_MESSAGE) {
|
|
522
|
-
error =
|
|
604
|
+
error = tf("Up to {n} files per message.", { n: CHAT_FILES_PER_MESSAGE });
|
|
523
605
|
break;
|
|
524
606
|
}
|
|
525
607
|
if (!CHAT_FILE_TYPES.includes(f.type)) {
|
|
526
|
-
error = "Only pictures (PNG, JPEG, WebP, GIF) and PDFs can be sent.";
|
|
608
|
+
error = t("Only pictures (PNG, JPEG, WebP, GIF) and PDFs can be sent.");
|
|
527
609
|
continue;
|
|
528
610
|
}
|
|
529
611
|
if (f.size > CHAT_FILE_MAX_BYTES) {
|
|
530
|
-
error =
|
|
612
|
+
error = tf("{name} is larger than 10 MB.", { name });
|
|
531
613
|
continue;
|
|
532
614
|
}
|
|
533
615
|
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() };
|
|
@@ -545,8 +627,10 @@ var build = (o) => {
|
|
|
545
627
|
);
|
|
546
628
|
picked.push(p);
|
|
547
629
|
}
|
|
548
|
-
if (open && view !== "messages")
|
|
549
|
-
|
|
630
|
+
if (open && (view !== "messages" || listing)) {
|
|
631
|
+
startChat();
|
|
632
|
+
go("messages");
|
|
633
|
+
} else draw();
|
|
550
634
|
text.focus();
|
|
551
635
|
};
|
|
552
636
|
let traySig = "";
|
|
@@ -556,14 +640,14 @@ var build = (o) => {
|
|
|
556
640
|
traySig = sig;
|
|
557
641
|
tray.replaceChildren(
|
|
558
642
|
...picked.map((p) => {
|
|
559
|
-
const x = el("button", { type: "button", class: "ckw-msgr-file-x", "aria-label":
|
|
643
|
+
const x = el("button", { type: "button", class: "ckw-msgr-file-x", "aria-label": tf("Remove {name}", { name: p.name }) }, icon(I.cross, 12));
|
|
560
644
|
x.addEventListener("click", () => {
|
|
561
645
|
dropFile(p);
|
|
562
646
|
draw();
|
|
563
647
|
text.focus();
|
|
564
648
|
});
|
|
565
649
|
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));
|
|
566
|
-
return el("div", { class: "ckw-msgr-file", role: "listitem", title: p.name, "aria-label": p.id ? p.name :
|
|
650
|
+
return el("div", { class: "ckw-msgr-file", role: "listitem", title: p.name, "aria-label": p.id ? p.name : tf("{name}, uploading", { name: p.name }), ...p.id ? {} : { "aria-busy": "true" } }, face, x);
|
|
567
651
|
})
|
|
568
652
|
);
|
|
569
653
|
}
|
|
@@ -576,7 +660,7 @@ var build = (o) => {
|
|
|
576
660
|
stream = await navigator.mediaDevices.getDisplayMedia({ video: { displaySurface: "browser" }, audio: false, preferCurrentTab: true, selfBrowserSurface: "include" });
|
|
577
661
|
} catch (e) {
|
|
578
662
|
if (e.name !== "NotAllowedError" && e.name !== "AbortError") {
|
|
579
|
-
error = "The screenshot did not work here. Attach a picture instead.";
|
|
663
|
+
error = t("The screenshot did not work here. Attach a picture instead.");
|
|
580
664
|
draw();
|
|
581
665
|
}
|
|
582
666
|
return;
|
|
@@ -602,11 +686,11 @@ var build = (o) => {
|
|
|
602
686
|
} catch {
|
|
603
687
|
blob = null;
|
|
604
688
|
} finally {
|
|
605
|
-
for (const
|
|
689
|
+
for (const t2 of stream.getTracks()) t2.stop();
|
|
606
690
|
root.style.visibility = "";
|
|
607
691
|
}
|
|
608
692
|
if (!blob || !blob.size) {
|
|
609
|
-
error = "The screenshot did not work here. Attach a picture instead.";
|
|
693
|
+
error = t("The screenshot did not work here. Attach a picture instead.");
|
|
610
694
|
return draw();
|
|
611
695
|
}
|
|
612
696
|
const stamp = (/* @__PURE__ */ new Date()).toISOString().slice(0, 16).replace(/[:T]/g, "-");
|
|
@@ -633,8 +717,8 @@ var build = (o) => {
|
|
|
633
717
|
if (!shots) return picker.click();
|
|
634
718
|
if (amenuOpen()) return closeAmenu();
|
|
635
719
|
amenu.replaceChildren(
|
|
636
|
-
amenuItem(I.image, "Photo or file", () => picker.click()),
|
|
637
|
-
amenuItem(I.screen, "Screenshot of this page", () => void shoot())
|
|
720
|
+
amenuItem(I.image, t("Photo or file"), () => picker.click()),
|
|
721
|
+
amenuItem(I.screen, t("Screenshot of this page"), () => void shoot())
|
|
638
722
|
);
|
|
639
723
|
clip.setAttribute("aria-expanded", "true");
|
|
640
724
|
enter(amenu, form);
|
|
@@ -644,9 +728,9 @@ var build = (o) => {
|
|
|
644
728
|
addFiles([...picker.files ?? []]);
|
|
645
729
|
picker.value = "";
|
|
646
730
|
});
|
|
647
|
-
const brand = el("a", { class: "ckw-msgr-powered", href: BRAND_URL, target: "_blank", rel: "noopener" }, "Powered by ", el("b", {}, "MRRocket"));
|
|
648
|
-
const list = el("ol", { class: "ckw-msgs", "aria-live": "polite", "aria-label": "Messages" });
|
|
649
|
-
const needEmail = () => askEmail && !o.user()?.email && !
|
|
731
|
+
const brand = el("a", { class: "ckw-msgr-powered", href: BRAND_URL, target: "_blank", rel: "noopener" }, t("Powered by {brand}").split("{brand}")[0], el("b", {}, "MRRocket"), t("Powered by {brand}").split("{brand}")[1]);
|
|
732
|
+
const list = el("ol", { class: "ckw-msgs", "aria-live": "polite", "aria-label": t("Messages") });
|
|
733
|
+
const needEmail = () => askEmail && !o.user()?.email && !hasEmail;
|
|
650
734
|
const emailOk = () => !email.value.trim() || EMAIL.test(email.value.trim());
|
|
651
735
|
const canSend = () => (!!text.value.trim() || picked.length > 0) && emailOk();
|
|
652
736
|
const go = (v) => {
|
|
@@ -688,7 +772,7 @@ var build = (o) => {
|
|
|
688
772
|
chip.addEventListener("click", () => go({ article: a.id }));
|
|
689
773
|
li.append(chip);
|
|
690
774
|
}
|
|
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)}`));
|
|
775
|
+
li.append(el("span", { class: "ckw-msg-meta" }, m.from === "ai" ? agentFace() : null, `${m.from !== "visitor" ? `${whoLabel[m.from]} \xB7 ` : ""}${slow ? t("Sending\u2026") : timeAgo(m.at)}`));
|
|
692
776
|
return li;
|
|
693
777
|
};
|
|
694
778
|
const articleRow = (a) => {
|
|
@@ -706,15 +790,15 @@ var build = (o) => {
|
|
|
706
790
|
b.addEventListener("click", act);
|
|
707
791
|
return b;
|
|
708
792
|
};
|
|
709
|
-
const searchBox = (placeholder = "Search for help") => {
|
|
793
|
+
const searchBox = (placeholder = t("Search for help")) => {
|
|
710
794
|
const wrap = el("label", { class: "ckw-msgr-search" });
|
|
711
|
-
const input = el("input", { class: "ckw-msgr-search-in", type: "search", placeholder, "aria-label": "Search for help", maxlength: "200" });
|
|
795
|
+
const input = el("input", { class: "ckw-msgr-search-in", type: "search", placeholder, "aria-label": t("Search for help"), maxlength: "200" });
|
|
712
796
|
input.value = query;
|
|
713
|
-
let
|
|
797
|
+
let wait;
|
|
714
798
|
input.addEventListener("input", () => {
|
|
715
799
|
query = input.value;
|
|
716
|
-
clearTimeout(
|
|
717
|
-
|
|
800
|
+
clearTimeout(wait);
|
|
801
|
+
wait = setTimeout(() => {
|
|
718
802
|
if (view !== "help") return go("help");
|
|
719
803
|
void loadArticles(true);
|
|
720
804
|
}, 250);
|
|
@@ -728,9 +812,13 @@ var build = (o) => {
|
|
|
728
812
|
wrap.append(icon(I.search, 16), input);
|
|
729
813
|
return wrap;
|
|
730
814
|
};
|
|
731
|
-
const helpSearch = searchBox("Search our help center\u2026");
|
|
815
|
+
const helpSearch = searchBox(t("Search our help center\u2026"));
|
|
732
816
|
const helpInput = helpSearch.querySelector("input");
|
|
733
817
|
let loadingArticles = false;
|
|
818
|
+
const askAnew = () => {
|
|
819
|
+
startChat();
|
|
820
|
+
go("messages");
|
|
821
|
+
};
|
|
734
822
|
const loadArticles = async (force = false) => {
|
|
735
823
|
if (!helpOn || loadingArticles && !force) return;
|
|
736
824
|
loadingArticles = true;
|
|
@@ -747,50 +835,92 @@ var build = (o) => {
|
|
|
747
835
|
}
|
|
748
836
|
draw();
|
|
749
837
|
};
|
|
838
|
+
const loadUpdates = async () => {
|
|
839
|
+
try {
|
|
840
|
+
updates = (await api().changelog()).entries.slice(0, UPDATES_SHOWN);
|
|
841
|
+
} catch {
|
|
842
|
+
updates = [];
|
|
843
|
+
}
|
|
844
|
+
updatesNode = null;
|
|
845
|
+
draw();
|
|
846
|
+
};
|
|
750
847
|
const askForm = el("form", { class: "ckw-msgr-askq" });
|
|
751
|
-
const askIn = el("input", { class: "ckw-msgr-askq-in", placeholder: "Ask a question", "aria-label": "Ask a question", maxlength: "4000", autocomplete: "off" });
|
|
752
|
-
const askGo = el("button", { type: "submit", class: "ckw-msgr-send", "aria-label": "Send", disabled: "" }, icon(I.up));
|
|
848
|
+
const askIn = el("input", { class: "ckw-msgr-askq-in", placeholder: t("Ask a question"), "aria-label": t("Ask a question"), maxlength: "4000", autocomplete: "off" });
|
|
849
|
+
const askGo = el("button", { type: "submit", class: "ckw-msgr-send", "aria-label": t("Send"), disabled: "" }, icon(I.up));
|
|
753
850
|
askForm.append(askIn, askGo);
|
|
754
851
|
askIn.addEventListener("input", () => askGo.disabled = !askIn.value.trim());
|
|
755
852
|
askForm.addEventListener("submit", (e) => {
|
|
756
853
|
e.preventDefault();
|
|
757
|
-
if (!askIn.value.trim())
|
|
854
|
+
if (!askIn.value.trim()) {
|
|
855
|
+
enterMessages();
|
|
856
|
+
return go("messages");
|
|
857
|
+
}
|
|
758
858
|
text.value = askIn.value;
|
|
759
859
|
askIn.value = "";
|
|
760
860
|
askGo.disabled = true;
|
|
861
|
+
startChat();
|
|
761
862
|
go("messages");
|
|
762
863
|
if (canSend()) submit();
|
|
763
864
|
});
|
|
865
|
+
const chatRow = (c) => {
|
|
866
|
+
const m = c.last;
|
|
867
|
+
const n = unseenIn(c.id);
|
|
868
|
+
const closed = c.status === "solved" ? ` \xB7 ${t("Closed")}` : "";
|
|
869
|
+
const row2 = el(
|
|
870
|
+
"button",
|
|
871
|
+
{ type: "button", class: "ckw-msgr-card ckw-msgr-recent", "aria-label": `${tf("Conversation: {subject}", { subject: c.subject })}${closed}${n ? `, ${tf("{n} new", { n })}` : ""}` },
|
|
872
|
+
el(
|
|
873
|
+
"span",
|
|
874
|
+
{ class: "ckw-msgr-recent-row" },
|
|
875
|
+
avatar("sm"),
|
|
876
|
+
el(
|
|
877
|
+
"span",
|
|
878
|
+
{ class: "ckw-msgr-card-main" },
|
|
879
|
+
el("span", { class: "ckw-msgr-card-sub ckw-msgr-recent-text" }, c.subject),
|
|
880
|
+
m && lineOf(m) !== c.subject ? el("span", { class: "ckw-msgr-card-sub" }, lineOf(m)) : null,
|
|
881
|
+
m ? el("span", { class: "ckw-msgr-card-meta" }, `${whoLabel[m.from]} \xB7 ${timeAgo(m.at)}${closed}`) : null
|
|
882
|
+
),
|
|
883
|
+
n ? el("span", { class: "ckw-msgr-badge" }, String(n)) : icon(I.next, 14)
|
|
884
|
+
)
|
|
885
|
+
);
|
|
886
|
+
row2.addEventListener("click", () => {
|
|
887
|
+
enterChat(c.id, null);
|
|
888
|
+
go("messages");
|
|
889
|
+
});
|
|
890
|
+
return row2;
|
|
891
|
+
};
|
|
892
|
+
const chatsBox = el("div", { class: "ckw-msgr-chats" });
|
|
893
|
+
const newChat = el("button", { type: "button", class: "ckw-btn ckw-btn-primary ckw-msgr-new" }, icon(I.chat), t("New conversation"));
|
|
894
|
+
newChat.addEventListener("click", askAnew);
|
|
895
|
+
let chatsSig = "";
|
|
896
|
+
const drawChats = () => {
|
|
897
|
+
const sig = `${chats.map((c) => `${c.id}:${c.status}:${c.last?.id}:${unseenIn(c.id)}`).join()}|${Math.floor(Date.now() / 6e4)}`;
|
|
898
|
+
if (sig !== chatsSig) {
|
|
899
|
+
chatsSig = sig;
|
|
900
|
+
chatsBox.replaceChildren(...chats.map(chatRow));
|
|
901
|
+
}
|
|
902
|
+
if (body.childNodes[0] !== chatsBox) body.replaceChildren(chatsBox, newChat);
|
|
903
|
+
};
|
|
764
904
|
const drawHome = () => {
|
|
765
905
|
const first = o.user()?.name?.trim().split(/\s+/)[0];
|
|
766
|
-
const rest = greeting.replace(/^(hi|hello|hey|hallo)( there)?\s*[
|
|
906
|
+
const rest = (o.greeting ? greeting.replace(/^(hi|hello|hey|hallo|moin|servus|bonjour|salut|hola|ciao|salve|hoi|ol[áa]|cze[śs][ćc]|hej|こんにちは)( there)?\s*[!,.!、。]*\s*/i, "").trim() : t("How can we help?")) || t("How can we help you today?");
|
|
767
907
|
const hero = el(
|
|
768
908
|
"h2",
|
|
769
909
|
{ class: "ckw-msgr-hero ckw-msgr-hello" },
|
|
770
|
-
el("span", {}, first ?
|
|
910
|
+
el("span", {}, `${first ? tf("Hi, {name}", { name: first }) : t("Hi there")} `, el("span", { class: "ckw-msgr-wave", "aria-hidden": "true" }, "\u{1F44B}")),
|
|
771
911
|
el("span", { class: "ckw-msgr-hello-sub" }, rest)
|
|
772
912
|
);
|
|
773
|
-
const last = (thread?.messages ?? []).at(-1);
|
|
774
913
|
const parts = [hero, askForm];
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
{ class: "ckw-msgr-card-main" },
|
|
786
|
-
el("span", { class: "ckw-msgr-card-sub ckw-msgr-recent-text" }, lineOf(last)),
|
|
787
|
-
el("span", { class: "ckw-msgr-card-meta" }, `${last.from === "visitor" ? "You" : whoLabel[last.from]} \xB7 ${timeAgo(last.at)}`)
|
|
788
|
-
),
|
|
789
|
-
unseen.length ? el("span", { class: "ckw-msgr-badge" }, String(unseen.length)) : icon(I.next, 14)
|
|
790
|
-
)
|
|
791
|
-
);
|
|
792
|
-
recent.addEventListener("click", () => go("messages"));
|
|
793
|
-
parts.push(recent);
|
|
914
|
+
const running = chats.filter((c) => c.status !== "solved");
|
|
915
|
+
const rows = (running.length ? running : chats).slice(0, 2);
|
|
916
|
+
parts.push(...rows.map(chatRow));
|
|
917
|
+
if (chats.length > rows.length) {
|
|
918
|
+
const all = el("button", { type: "button", class: "ckw-msgr-all" }, tf("All conversations ({n})", { n: chats.length }), icon(I.next, 14));
|
|
919
|
+
all.addEventListener("click", () => {
|
|
920
|
+
enterMessages();
|
|
921
|
+
go("messages");
|
|
922
|
+
});
|
|
923
|
+
parts.push(all);
|
|
794
924
|
}
|
|
795
925
|
if (helpOn && hasArticles !== false) {
|
|
796
926
|
const helpCard = el("div", { class: "ckw-msgr-card ckw-msgr-card-col" });
|
|
@@ -808,13 +938,13 @@ var build = (o) => {
|
|
|
808
938
|
const groups = /* @__PURE__ */ new Map();
|
|
809
939
|
for (const a of all) groups.set(a.collection, [...groups.get(a.collection) ?? [], a]);
|
|
810
940
|
const inside = !q && collection !== null ? groups.get(collection) ?? [] : null;
|
|
811
|
-
const heading = q ? "Search results" : inside ? collection ?? "" : "Most read articles";
|
|
941
|
+
const heading = q ? t("Search results") : inside ? collection ?? "" : t("Most read articles");
|
|
812
942
|
const parts = [el("h2", { class: "ckw-msgr-help-h" }, heading), helpSearch];
|
|
813
943
|
if (articles === null) parts.push(el("div", { class: "ckw-skel" }));
|
|
814
944
|
else if (!all.length) {
|
|
815
|
-
const ask = el("button", { type: "button", class: "ckw-btn ckw-btn-primary" }, "Send us a message");
|
|
816
|
-
ask.addEventListener("click",
|
|
817
|
-
parts.push(el("div", { class: "ckw-msgr-empty" }, el("p", {}, q ?
|
|
945
|
+
const ask = el("button", { type: "button", class: "ckw-btn ckw-btn-primary" }, t("Send us a message"));
|
|
946
|
+
ask.addEventListener("click", askAnew);
|
|
947
|
+
parts.push(el("div", { class: "ckw-msgr-empty" }, el("p", {}, q ? tf("Nothing found for \u201C{q}\u201D.", { q }) : t("No help articles yet.")), ask));
|
|
818
948
|
} else if (q || inside)
|
|
819
949
|
parts.push(
|
|
820
950
|
el(
|
|
@@ -829,7 +959,7 @@ var build = (o) => {
|
|
|
829
959
|
"div",
|
|
830
960
|
{ class: "ckw-msgr-hlist" },
|
|
831
961
|
...[...groups].map(
|
|
832
|
-
([name, items]) => helpRow(name, items.map((a) => a.title).join(", "),
|
|
962
|
+
([name, items]) => helpRow(name, items.map((a) => a.title).join(", "), items.length === 1 ? t("1 article") : tf("{n} articles", { n: items.length }), () => {
|
|
833
963
|
collection = name;
|
|
834
964
|
go("help");
|
|
835
965
|
replay(body, "ckw-msgr-enter");
|
|
@@ -844,6 +974,14 @@ var build = (o) => {
|
|
|
844
974
|
body.append(...parts.slice(2));
|
|
845
975
|
}
|
|
846
976
|
};
|
|
977
|
+
const drawUpdates = () => {
|
|
978
|
+
updatesNode ??= !updates ? el("div", { class: "ckw-skel" }) : !updates.length ? el("div", { class: "ckw-msgr-empty" }, el("p", {}, t("No updates yet."))) : el(
|
|
979
|
+
"div",
|
|
980
|
+
{ class: "ckw-msgr-updates" },
|
|
981
|
+
...updates.map((e) => el("article", { class: "ckw-msgr-update" }, el("p", { class: "ckw-msgr-doc-c" }, timeAgo(e.publishedAt)), el("h2", { class: "ckw-msgr-update-t" }, e.title), e.body ? el("p", { class: "ckw-msgr-update-b" }, e.body) : null))
|
|
982
|
+
);
|
|
983
|
+
if (body.firstChild !== updatesNode || body.childNodes.length !== 1) body.replaceChildren(updatesNode);
|
|
984
|
+
};
|
|
847
985
|
let articleShown = null;
|
|
848
986
|
const drawArticle = (id) => {
|
|
849
987
|
if (articleShown?.id === id) return body.replaceChildren(articleShown.node);
|
|
@@ -853,26 +991,26 @@ var build = (o) => {
|
|
|
853
991
|
api().article(id).then((a) => {
|
|
854
992
|
const foot = el("div", { class: "ckw-msgr-rate" });
|
|
855
993
|
const drawRate = () => {
|
|
856
|
-
if (rated.has(id)) return foot.replaceChildren(el("span", {}, "Thanks for letting us know."));
|
|
857
|
-
const yes = el("button", { type: "button", class: "ckw-btn" }, "Yes");
|
|
858
|
-
const no = el("button", { type: "button", class: "ckw-btn" }, "No");
|
|
994
|
+
if (rated.has(id)) return foot.replaceChildren(el("span", {}, t("Thanks for letting us know.")));
|
|
995
|
+
const yes = el("button", { type: "button", class: "ckw-btn" }, t("Yes"));
|
|
996
|
+
const no = el("button", { type: "button", class: "ckw-btn" }, t("No"));
|
|
859
997
|
const rate = (helpful) => {
|
|
860
998
|
rated = new Set(rated).add(id);
|
|
861
999
|
drawRate();
|
|
862
1000
|
api().rateArticle(id, helpful).catch(() => void 0);
|
|
863
|
-
if (!helpful)
|
|
1001
|
+
if (!helpful) askAnew();
|
|
864
1002
|
};
|
|
865
1003
|
yes.addEventListener("click", () => rate(true));
|
|
866
1004
|
no.addEventListener("click", () => rate(false));
|
|
867
|
-
foot.replaceChildren(el("span", {}, "Did this answer your question?"), el("span", { class: "ckw-msgr-rate-b" }, yes, no));
|
|
1005
|
+
foot.replaceChildren(el("span", {}, t("Did this answer your question?")), el("span", { class: "ckw-msgr-rate-b" }, yes, no));
|
|
868
1006
|
};
|
|
869
1007
|
drawRate();
|
|
870
|
-
const ask = el("button", { type: "button", class: "ckw-btn ckw-msgr-ask" }, icon(I.chat), "Still need help? Send us a message");
|
|
871
|
-
ask.addEventListener("click",
|
|
1008
|
+
const ask = el("button", { type: "button", class: "ckw-btn ckw-msgr-ask" }, icon(I.chat), t("Still need help? Send us a message"));
|
|
1009
|
+
ask.addEventListener("click", askAnew);
|
|
872
1010
|
node.replaceChildren(
|
|
873
1011
|
el("p", { class: "ckw-msgr-doc-c" }, a.collection),
|
|
874
1012
|
el("h2", { class: "ckw-msgr-doc-t" }, a.title),
|
|
875
|
-
el("p", { class: "ckw-msgr-doc-c" },
|
|
1013
|
+
el("p", { class: "ckw-msgr-doc-c" }, tf("Updated {when}", { when: timeAgo(a.updatedAt) })),
|
|
876
1014
|
el("div", { class: "ckw-msgr-doc-b" }, ...a.blocks.map(blockOf)),
|
|
877
1015
|
foot,
|
|
878
1016
|
ask
|
|
@@ -886,22 +1024,25 @@ var build = (o) => {
|
|
|
886
1024
|
let listSig = "";
|
|
887
1025
|
let listCount = -1;
|
|
888
1026
|
const suggestions = el("div", { class: "ckw-msgr-sugg" });
|
|
1027
|
+
const outboxHere = () => outbox.filter((x) => x.draft === draft && x.chat === chatId);
|
|
889
1028
|
const drawMessages = () => {
|
|
890
|
-
const
|
|
1029
|
+
const current = draft === null ? thread : null;
|
|
1030
|
+
const msgs = current?.messages ?? [];
|
|
1031
|
+
const here = outboxHere();
|
|
891
1032
|
const parts = [];
|
|
892
1033
|
{
|
|
893
|
-
const waiting =
|
|
1034
|
+
const waiting = here.map((o2) => o2.message);
|
|
894
1035
|
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)}`;
|
|
895
1036
|
if (sig !== listSig) {
|
|
896
1037
|
listSig = sig;
|
|
897
|
-
const greet = msgs.length || waiting.length ? [] : [el("li", { class: "ckw-msg", "data-from": "them" }, el("div", { class: "ckw-bubble" }, greeting))];
|
|
1038
|
+
const greet = msgs.length || waiting.length || chatId ? [] : [el("li", { class: "ckw-msg", "data-from": "them" }, el("div", { class: "ckw-bubble" }, greeting))];
|
|
898
1039
|
const items = [...greet, ...msgs.map((m) => bubble(m)), ...waiting.map((m) => bubble(m, true))];
|
|
899
1040
|
if (listCount >= 0) items.slice(listCount).forEach((li) => li.setAttribute("data-new", ""));
|
|
900
1041
|
listCount = items.length;
|
|
901
1042
|
list.replaceChildren(...items);
|
|
902
1043
|
}
|
|
903
1044
|
parts.push(list);
|
|
904
|
-
const top = !msgs.some((m) => m.from === "visitor") && !
|
|
1045
|
+
const top = !msgs.some((m) => m.from === "visitor") && !here.length && !chatId ? (articles ?? []).slice(0, 3) : [];
|
|
905
1046
|
if (top.length) {
|
|
906
1047
|
if (suggestions.childNodes.length !== top.length)
|
|
907
1048
|
suggestions.replaceChildren(
|
|
@@ -914,9 +1055,9 @@ var build = (o) => {
|
|
|
914
1055
|
parts.push(suggestions);
|
|
915
1056
|
}
|
|
916
1057
|
}
|
|
917
|
-
if (
|
|
1058
|
+
if (current?.conversation?.status === "solved" && !here.length) parts.push(el("p", { class: "ckw-fine ckw-msgr-note" }, t("This conversation is closed. Write to start a new one.")));
|
|
918
1059
|
if (body.childNodes.length !== parts.length || parts.some((x, i) => body.childNodes[i] !== x)) body.replaceChildren(...parts);
|
|
919
|
-
const count = msgs.length +
|
|
1060
|
+
const count = msgs.length + here.length;
|
|
920
1061
|
if (count !== drawn) {
|
|
921
1062
|
drawn = count;
|
|
922
1063
|
body.scrollTop = body.scrollHeight;
|
|
@@ -937,11 +1078,12 @@ var build = (o) => {
|
|
|
937
1078
|
pop.dataset.view = typeof view === "string" ? view : "article";
|
|
938
1079
|
drawBar();
|
|
939
1080
|
if (view === "home") drawHome();
|
|
940
|
-
else if (view === "messages") drawMessages();
|
|
1081
|
+
else if (view === "messages") (listing ? drawChats : drawMessages)();
|
|
941
1082
|
else if (view === "help") drawHelp();
|
|
1083
|
+
else if (view === "updates") drawUpdates();
|
|
942
1084
|
else drawArticle(view.article);
|
|
943
1085
|
const kids = view === "home" ? [sky, bar, body] : [bar, body];
|
|
944
|
-
kids.push(view === "messages" ? form : tabs);
|
|
1086
|
+
kids.push(view === "messages" && !listing ? form : tabs);
|
|
945
1087
|
if (o.branding !== false) kids.push(brand);
|
|
946
1088
|
if (menu.isConnected) kids.push(menu);
|
|
947
1089
|
if (pop.childNodes.length !== kids.length || kids.some((k, i) => pop.childNodes[i] !== k)) pop.replaceChildren(...kids);
|
|
@@ -950,15 +1092,22 @@ var build = (o) => {
|
|
|
950
1092
|
if (ctrl) return;
|
|
951
1093
|
const mine = ctrl = new AbortController();
|
|
952
1094
|
try {
|
|
953
|
-
take(await api().chat(mine.signal));
|
|
1095
|
+
take(await api().chat(mine.signal, chatId ?? void 0));
|
|
954
1096
|
error = "";
|
|
1097
|
+
refused = false;
|
|
955
1098
|
} catch (e) {
|
|
956
1099
|
if (e.name === "AbortError") return;
|
|
957
|
-
|
|
1100
|
+
const status = e.status;
|
|
1101
|
+
refused = status === 401 || status === 403;
|
|
1102
|
+
if (status === 404 && chatId) enterMessages();
|
|
1103
|
+
else if (open && view === "messages") error = sayable(e);
|
|
958
1104
|
} finally {
|
|
959
1105
|
if (ctrl === mine) ctrl = null;
|
|
960
1106
|
}
|
|
961
|
-
if (!loaded)
|
|
1107
|
+
if (!loaded) {
|
|
1108
|
+
listCount = -1;
|
|
1109
|
+
if (view === "messages" && chatId === null && draft === null && !text.value.trim() && !outbox.length && !picked.length) enterMessages();
|
|
1110
|
+
}
|
|
962
1111
|
loaded = true;
|
|
963
1112
|
draw();
|
|
964
1113
|
drawFab();
|
|
@@ -966,11 +1115,12 @@ var build = (o) => {
|
|
|
966
1115
|
};
|
|
967
1116
|
function schedule(now) {
|
|
968
1117
|
clearTimeout(poll);
|
|
969
|
-
const
|
|
970
|
-
const
|
|
971
|
-
const recent =
|
|
972
|
-
const
|
|
973
|
-
|
|
1118
|
+
const running = chats.filter((c) => c.status !== "solved");
|
|
1119
|
+
const waiting = running.length > 0;
|
|
1120
|
+
const recent = running.some((c) => c.last && Date.now() - new Date(c.last.at).getTime() < WAITING_FOR_MS);
|
|
1121
|
+
const quiet = now === void 0 && !open && !waiting;
|
|
1122
|
+
const ms = now ?? (document.visibilityState === "hidden" ? 0 : watching() ? POLL_OPEN_MS : waiting && recent ? POLL_WAITING_MS : quiet ? POLL_QUIET_MS : POLL_IDLE_MS);
|
|
1123
|
+
if (ms || now === 0) poll = setTimeout(() => quiet && (refused || !(o.user()?.id || chats.length)) ? schedule() : void tick(), ms);
|
|
974
1124
|
}
|
|
975
1125
|
const onVisible = () => {
|
|
976
1126
|
if (document.visibilityState === "visible") schedule(0);
|
|
@@ -981,13 +1131,15 @@ var build = (o) => {
|
|
|
981
1131
|
if (flushing) return;
|
|
982
1132
|
flushing = true;
|
|
983
1133
|
while (outbox.length) {
|
|
984
|
-
const
|
|
1134
|
+
const item = outbox[0];
|
|
1135
|
+
const { message, files, email: given } = item;
|
|
985
1136
|
await Promise.all(files.map((p) => p.up));
|
|
986
1137
|
const ids = files.filter((p) => p.id).map((p) => p.id);
|
|
987
1138
|
try {
|
|
988
|
-
if (!message.body && !ids.length) throw new Error(error || "The file could not be sent.");
|
|
1139
|
+
if (!message.body && !ids.length) throw new Error(error || t("The file could not be sent."));
|
|
989
1140
|
const sent = await api().sendChat({
|
|
990
1141
|
body: message.body,
|
|
1142
|
+
...item.draft !== null ? { conversation: "new" } : item.chat ? { conversation: item.chat } : {},
|
|
991
1143
|
email: given,
|
|
992
1144
|
url: window.location.href.slice(0, 500),
|
|
993
1145
|
...ids.length ? { attachments: ids } : {},
|
|
@@ -996,6 +1148,18 @@ var build = (o) => {
|
|
|
996
1148
|
});
|
|
997
1149
|
ctrl?.abort();
|
|
998
1150
|
ctrl = null;
|
|
1151
|
+
const went = sent.conversation?.id ?? null;
|
|
1152
|
+
if (went) {
|
|
1153
|
+
const after = outbox.slice(1).filter((x) => x.draft === item.draft && x.chat === item.chat);
|
|
1154
|
+
if (item.draft === draft && item.chat === chatId) {
|
|
1155
|
+
draft = null;
|
|
1156
|
+
chatId = went;
|
|
1157
|
+
}
|
|
1158
|
+
for (const x of after) {
|
|
1159
|
+
x.draft = null;
|
|
1160
|
+
x.chat = went;
|
|
1161
|
+
}
|
|
1162
|
+
}
|
|
999
1163
|
take(sent);
|
|
1000
1164
|
outbox.shift();
|
|
1001
1165
|
for (const p of files) {
|
|
@@ -1004,10 +1168,12 @@ var build = (o) => {
|
|
|
1004
1168
|
}
|
|
1005
1169
|
} catch (e) {
|
|
1006
1170
|
const back = outbox.splice(0);
|
|
1171
|
+
const gone = e.status === 404;
|
|
1172
|
+
enterChat(gone ? null : back[0].chat, gone ? ++drafts : back[0].draft);
|
|
1007
1173
|
text.value = [...back.map((b) => b.message.body), text.value.trim()].filter(Boolean).join("\n");
|
|
1008
1174
|
picked = [...back.flatMap((b) => b.files.filter((p) => p.id)), ...picked];
|
|
1009
1175
|
for (const b of back) for (const p of b.files) localUrls.delete(p.key);
|
|
1010
|
-
error = sayable(e);
|
|
1176
|
+
error = gone ? t("This conversation is no longer available. Your message is still here to send as a new one.") : sayable(e);
|
|
1011
1177
|
}
|
|
1012
1178
|
draw();
|
|
1013
1179
|
}
|
|
@@ -1029,7 +1195,9 @@ var build = (o) => {
|
|
|
1029
1195
|
...files.length ? { attachments: files.map((p) => ({ id: p.key, name: p.name, type: p.type, size: p.size })) } : {}
|
|
1030
1196
|
},
|
|
1031
1197
|
files,
|
|
1032
|
-
email: needEmail() && email.value.trim() ? email.value.trim() : void 0
|
|
1198
|
+
email: needEmail() && email.value.trim() ? email.value.trim() : void 0,
|
|
1199
|
+
chat: chatId,
|
|
1200
|
+
draft
|
|
1033
1201
|
});
|
|
1034
1202
|
text.value = "";
|
|
1035
1203
|
picked = [];
|
|
@@ -1074,7 +1242,7 @@ var build = (o) => {
|
|
|
1074
1242
|
function show(v = "home", still = false) {
|
|
1075
1243
|
const was = open;
|
|
1076
1244
|
open = true;
|
|
1077
|
-
view = v === "help" && !helpOn ? "home" : v;
|
|
1245
|
+
view = v === "help" && !helpOn || v === "updates" && !updatesOn ? "home" : v;
|
|
1078
1246
|
drawn = -1;
|
|
1079
1247
|
previewGone = true;
|
|
1080
1248
|
pop.toggleAttribute("data-still", still);
|
|
@@ -1115,10 +1283,14 @@ var build = (o) => {
|
|
|
1115
1283
|
document.addEventListener("keydown", onKey);
|
|
1116
1284
|
void tick();
|
|
1117
1285
|
if (helpOn) void loadArticles();
|
|
1286
|
+
if (updatesOn) void loadUpdates();
|
|
1118
1287
|
drawFab();
|
|
1119
1288
|
if (o.opened) show(o.opened, true);
|
|
1120
1289
|
return {
|
|
1121
|
-
open: (v) =>
|
|
1290
|
+
open: (v) => {
|
|
1291
|
+
if (v === "messages") enterMessages();
|
|
1292
|
+
show(v ?? "home");
|
|
1293
|
+
},
|
|
1122
1294
|
close,
|
|
1123
1295
|
toggle: () => open ? close() : show(),
|
|
1124
1296
|
isOpen: () => open,
|
|
@@ -1127,7 +1299,7 @@ var build = (o) => {
|
|
|
1127
1299
|
},
|
|
1128
1300
|
destroy: () => {
|
|
1129
1301
|
clearTimeout(poll);
|
|
1130
|
-
for (const
|
|
1302
|
+
for (const t2 of leaving.values()) clearTimeout(t2);
|
|
1131
1303
|
ctrl?.abort();
|
|
1132
1304
|
for (const p of picked) forget(p);
|
|
1133
1305
|
for (const u of localUrls.values()) URL.revokeObjectURL(u);
|