@mrrocket/sdk 0.12.0 → 0.14.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.
Files changed (45) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/README.md +60 -0
  3. package/dist/browser/mrrocket-flows.js +11 -2
  4. package/dist/browser/mrrocket-messenger.js +143 -65
  5. package/dist/browser/mrrocket.js +1 -1
  6. package/dist/browser.d.ts +1 -1
  7. package/dist/browser.js +2 -1
  8. package/dist/browser.js.map +1 -1
  9. package/dist/client.d.ts +12 -0
  10. package/dist/client.js +22 -0
  11. package/dist/client.js.map +1 -1
  12. package/dist/context.d.ts +3 -0
  13. package/dist/context.js.map +1 -1
  14. package/dist/core.d.ts +4 -1
  15. package/dist/core.js +1 -1
  16. package/dist/core.js.map +1 -1
  17. package/dist/flows-ui.d.ts +6 -4
  18. package/dist/flows-ui.js +99 -4
  19. package/dist/flows-ui.js.map +1 -1
  20. package/dist/index.d.ts +2 -1
  21. package/dist/index.js +4 -0
  22. package/dist/index.js.map +1 -1
  23. package/dist/messenger-ui.d.ts +22 -0
  24. package/dist/messenger-ui.js +313 -79
  25. package/dist/messenger-ui.js.map +1 -1
  26. package/dist/paywall.d.ts +123 -0
  27. package/dist/paywall.js +131 -0
  28. package/dist/paywall.js.map +1 -0
  29. package/dist/proxy.d.ts +1 -0
  30. package/dist/proxy.js +3 -0
  31. package/dist/proxy.js.map +1 -1
  32. package/dist/react-native/index.d.cts +134 -3
  33. package/dist/react-native/index.d.ts +134 -3
  34. package/dist/react-native/index.js +204 -4
  35. package/dist/react-native/index.js.map +1 -1
  36. package/dist/security.d.ts +71 -0
  37. package/dist/security.js +73 -0
  38. package/dist/security.js.map +1 -0
  39. package/dist/server.d.ts +1 -0
  40. package/dist/server.js +8 -1
  41. package/dist/server.js.map +1 -1
  42. package/dist/styles-css.d.ts +1 -1
  43. package/dist/styles-css.js +141 -63
  44. package/dist/styles-css.js.map +1 -1
  45. package/package.json +1 -1
@@ -40,7 +40,7 @@ var el = (tag, attrs = {}, ...kids) => {
40
40
  var SVG_NS = "http://www.w3.org/2000/svg";
41
41
  var icon = (d, size = 16) => {
42
42
  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 16 16", fill: "none", stroke: "currentColor", "stroke-width": "1.7", "stroke-linecap": "round", "stroke-linejoin": "round", "aria-hidden": "true" }))
43
+ 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
44
  svg.setAttribute(k, v);
45
45
  const path = document.createElementNS(SVG_NS, "path");
46
46
  path.setAttribute("d", d);
@@ -48,16 +48,30 @@ var icon = (d, size = 16) => {
48
48
  return svg;
49
49
  };
50
50
  var I = {
51
- cross: "m4 4 8 8M12 4l-8 8",
52
- chat: "M2.5 4.5a2 2 0 0 1 2-2h7a2 2 0 0 1 2 2v4.5a2 2 0 0 1-2 2H7l-3 2.5V11a2 2 0 0 1-1.5-2z",
53
- send: "M2.5 8h10M8.5 3.5 13 8l-4.5 4.5",
54
- up: "M8 13V3M3.5 7.5 8 3l4.5 4.5",
55
- home: "M2.5 7 8 2.5 13.5 7v6a1 1 0 0 1-1 1h-3v-4h-3v4h-3a1 1 0 0 1-1-1z",
56
- help: "M8 14.5A6.5 6.5 0 1 0 8 1.5a6.5 6.5 0 0 0 0 13zM6.2 6.2a1.9 1.9 0 1 1 2.6 1.8c-.5.2-.8.6-.8 1.1v.4M8 11.6v.1",
57
- back: "M10 3.5 5.5 8l4.5 4.5",
58
- next: "M6 3.5 10.5 8 6 12.5",
59
- search: "M7 11.5a4.5 4.5 0 1 0 0-9 4.5 4.5 0 0 0 0 9zM10.5 10.5l3 3",
60
- doc: "M4 1.5h5.5l3 3V14a.5.5 0 0 1-.5.5H4a.5.5 0 0 1-.5-.5V2a.5.5 0 0 1 .5-.5zM9.5 1.5v3h3M6 8.5h4M6 11h4"
51
+ cross: "M18 6 6 18M6 6l12 12",
52
+ chat: "M7.9 20A9 9 0 1 0 4 16.1L2 22z",
53
+ send: "M5 12h14M12 5l7 7-7 7",
54
+ up: "M12 19V5M5 12l7-7 7 7",
55
+ 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",
56
+ back: "m15 18-6-6 6-6",
57
+ next: "m9 18 6-6-6-6",
58
+ search: "M19 11a8 8 0 1 1-16 0 8 8 0 0 1 16 0zM21 21l-4.3-4.3",
59
+ 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",
60
+ 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",
61
+ 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",
62
+ expand: "M15 3h6v6M21 3l-7 7M3 21l7-7M9 21H3v-6",
63
+ collapse: "m14 10 7-7M20 10h-6V4M3 21l7-7M4 14h6v6",
64
+ minus: "M5 12h14"
65
+ };
66
+ var BUBBLE = "M7.9 20A9 9 0 1 0 4 16.1L2 22z";
67
+ var filled = (d, size) => {
68
+ const svg = document.createElementNS(SVG_NS, "svg");
69
+ 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" }))
70
+ svg.setAttribute(k, v);
71
+ const path = document.createElementNS(SVG_NS, "path");
72
+ path.setAttribute("d", d);
73
+ svg.appendChild(path);
74
+ return svg;
61
75
  };
62
76
  var chime = /* @__PURE__ */ (() => {
63
77
  let ctx = null;
@@ -139,7 +153,7 @@ var mountMessenger = (o) => {
139
153
  for (const fn of queued.splice(0)) fn(real);
140
154
  };
141
155
  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));
156
+ (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
157
  return {
144
158
  open: (v) => later((h) => h.open(v)),
145
159
  close: () => real?.close(),
@@ -165,13 +179,14 @@ var build = (o) => {
165
179
  const helpOn = o.help ?? true;
166
180
  const soundOn = o.sound ?? true;
167
181
  const previewOn = o.preview ?? true;
168
- const api = () => createApi({ host: o.host, apiKey: o.apiKey, distinctId: o.distinctId(), user: o.user() });
182
+ const api = () => o.client ?? createApi({ host: o.host, apiKey: o.apiKey, distinctId: o.distinctId(), user: o.user() });
169
183
  const root = el("div", { class: "ckw-root", "data-theme": o.theme ?? "auto" });
184
+ if (o.container) root.setAttribute("data-contained", "");
170
185
  if (o.accent) {
171
186
  root.style.setProperty("--ckw-accent", o.accent);
172
187
  root.style.setProperty("--ckw-accent-ink", inkOn(o.accent));
173
188
  }
174
- document.body.appendChild(root);
189
+ (o.container ?? document.body).appendChild(root);
175
190
  let open = false;
176
191
  let view = "home";
177
192
  let thread = null;
@@ -187,6 +202,8 @@ var build = (o) => {
187
202
  let query = "";
188
203
  let rated = /* @__PURE__ */ new Set();
189
204
  let hasArticles = null;
205
+ let collection = null;
206
+ let wide = false;
190
207
  const teamMsgs = (t) => (t?.messages ?? []).filter((m) => m.from !== "visitor");
191
208
  const lastTeamId = (t) => teamMsgs(t).at(-1)?.id ?? "";
192
209
  const watching = () => open && view === "messages" && document.visibilityState !== "hidden";
@@ -217,23 +234,57 @@ var build = (o) => {
217
234
  if (id) writeSeen(id);
218
235
  };
219
236
  const avatar = (size) => el("span", { class: `ckw-msgr-av${size ? " ckw-msgr-av-sm" : ""}`, "aria-hidden": "true" }, initial);
237
+ const leaving = /* @__PURE__ */ new Map();
238
+ const enter = (node, parent = root) => {
239
+ clearTimeout(leaving.get(node));
240
+ leaving.delete(node);
241
+ node.removeAttribute("data-out");
242
+ if (!node.isConnected) parent.appendChild(node);
243
+ };
244
+ const leave = (node, ms = 180) => {
245
+ if (!node.isConnected || leaving.has(node)) return;
246
+ node.setAttribute("data-out", "");
247
+ leaving.set(
248
+ node,
249
+ setTimeout(() => {
250
+ leaving.delete(node);
251
+ node.removeAttribute("data-out");
252
+ node.remove();
253
+ }, ms)
254
+ );
255
+ };
256
+ const replay = (node, cls) => {
257
+ node.classList.remove(cls);
258
+ void node.offsetWidth;
259
+ node.classList.add(cls);
260
+ };
220
261
  const fab = el("button", { type: "button", class: `ckw-fab ckw-msgr-fab${label ? "" : " ckw-msgr-round"}`, "data-pos": position, "aria-haspopup": "dialog" });
262
+ const fabIcons = el(
263
+ "span",
264
+ { class: "ckw-msgr-fab-ic", "aria-hidden": "true" },
265
+ el("span", { class: "ckw-msgr-fab-i", "data-i": "chat" }, filled(BUBBLE, label ? 20 : 28)),
266
+ el("span", { class: "ckw-msgr-fab-i", "data-i": "x" }, icon(I.cross, label ? 16 : 22))
267
+ );
268
+ const fabCount = el("span", { class: "ckw-msgr-count", "aria-hidden": "true" });
269
+ fab.append(fabIcons);
270
+ if (label) fab.append(el("span", {}, label));
221
271
  const prev = el("div", { class: "ckw-msgr-preview", "data-pos": position, role: "status" });
222
- let fabShows = null;
223
272
  const drawFab = () => {
224
- const glyph = icon(open ? I.cross : I.chat, label ? 18 : 24);
225
- if (fabShows !== null && fabShows !== open) glyph.classList.add("ckw-turn");
226
- fabShows = open;
227
- fab.replaceChildren(glyph);
228
- if (label) fab.append(el("span", {}, label));
229
- if (unseen.length && !open) fab.append(el("span", { class: "ckw-msgr-count", "aria-hidden": "true" }, String(Math.min(unseen.length, 9))));
273
+ fab.toggleAttribute("data-open", open);
274
+ if (unseen.length && !open) {
275
+ const n = String(Math.min(unseen.length, 9));
276
+ if (fabCount.textContent !== n) fabCount.textContent = n;
277
+ if (!fabCount.isConnected) fab.append(fabCount);
278
+ } else fabCount.remove();
230
279
  fab.setAttribute("aria-expanded", String(open));
231
280
  fab.setAttribute("aria-label", open ? "Close chat" : unseen.length ? `Open chat, ${unseen.length} new ${unseen.length === 1 ? "reply" : "replies"}` : "Open chat");
232
281
  drawPreview();
233
282
  };
283
+ const previewShown = () => previewOn && !open && !previewGone && !!unseen.length && launcherOn;
234
284
  const drawPreview = () => {
235
285
  const m = unseen.at(-1);
236
- if (!previewOn || open || previewGone || !m || !launcherOn) return prev.remove();
286
+ if (!previewShown() || !m) return leave(prev);
287
+ hideTeaser(true);
237
288
  const x = el("button", { type: "button", class: "ckw-msgr-preview-x", "aria-label": "Dismiss" }, icon(I.cross, 12));
238
289
  x.addEventListener("click", (e) => {
239
290
  e.stopPropagation();
@@ -253,40 +304,163 @@ var build = (o) => {
253
304
  );
254
305
  card.addEventListener("click", () => show("messages"));
255
306
  prev.replaceChildren(card, x);
256
- if (!prev.isConnected) root.appendChild(prev);
307
+ enter(prev);
308
+ };
309
+ const teaser = el("div", { class: "ckw-msgr-teaser", "data-pos": position });
310
+ let teaserTimer;
311
+ const showTeaser = () => {
312
+ clearTimeout(teaserTimer);
313
+ if (open || previewShown() || !launcherOn) return;
314
+ const chat = el("button", { type: "button", class: "ckw-msgr-teaser-b", "data-main": "" }, icon(I.chat), el("span", {}, "Chat with us"));
315
+ chat.addEventListener("click", () => show("messages"));
316
+ const buttons = [chat];
317
+ if (helpOn && hasArticles) {
318
+ const search = el("button", { type: "button", class: "ckw-msgr-teaser-b" }, icon(I.search), el("span", {}, "Search"));
319
+ search.addEventListener("click", () => show("help"));
320
+ buttons.push(search);
321
+ }
322
+ teaser.replaceChildren(
323
+ el(
324
+ "div",
325
+ { class: "ckw-msgr-teaser-top" },
326
+ el("span", { class: "ckw-msgr-teaser-t" }, el("b", {}, greeting), el("span", { class: "ckw-msgr-online" }, reply)),
327
+ avatar()
328
+ ),
329
+ el("div", { class: "ckw-msgr-teaser-row" }, ...buttons)
330
+ );
331
+ enter(teaser);
257
332
  };
333
+ function hideTeaser(now = false) {
334
+ clearTimeout(teaserTimer);
335
+ if (now) return leave(teaser, 140);
336
+ teaserTimer = setTimeout(() => leave(teaser, 140), 260);
337
+ }
338
+ const hoverable = (e) => e.pointerType === "mouse";
339
+ for (const node of [fab, teaser]) {
340
+ node.addEventListener("pointerenter", (e) => hoverable(e) && (node === fab ? showTeaser() : clearTimeout(teaserTimer)));
341
+ node.addEventListener("pointerleave", (e) => hoverable(e) && hideTeaser());
342
+ }
258
343
  fab.addEventListener("click", () => {
259
344
  chime.unlock();
345
+ hideTeaser(true);
260
346
  if (open) close();
261
347
  else show(unseen.length ? "messages" : "home");
262
348
  });
263
349
  if (launcherOn) root.appendChild(fab);
264
350
  const pop = el("div", { class: "ckw-pop ckw-msgr", "data-pos": position, role: "dialog", "aria-label": title });
265
- const head = el("div", { class: "ckw-pop-head ckw-msgr-head" });
351
+ const bar = el("div", { class: "ckw-msgr-bar" });
266
352
  const body = el("div", { class: "ckw-pop-body ckw-msgr-body" });
267
- const tabs = el("nav", { class: "ckw-msgr-tabs", "aria-label": "Messenger" });
268
353
  pop.addEventListener("click", () => chime.unlock());
354
+ const lead = el("span", { class: "ckw-msgr-lead" });
355
+ const seg = el("nav", { class: "ckw-msgr-seg", "aria-label": "Messenger" });
356
+ const mark = el("span", { class: "ckw-msgr-seg-mark", "aria-hidden": "true" });
357
+ const segTab = (v, name, d) => {
358
+ const b = el("button", { type: "button", class: "ckw-msgr-seg-b" }, icon(d, 15), el("span", {}, name));
359
+ b.addEventListener("click", () => {
360
+ collection = null;
361
+ go(v);
362
+ });
363
+ return b;
364
+ };
365
+ const chatTab = segTab("messages", "Chat", I.chat);
366
+ const helpTab = segTab("help", "Help", I.book);
367
+ const tabCount = el("span", { class: "ckw-msgr-count ckw-msgr-count-tab", "aria-hidden": "true" });
368
+ const menuBtn = el("button", { type: "button", class: "ckw-msgr-ib", "aria-label": "More", "aria-haspopup": "menu", "aria-expanded": "false" }, icon(I.dots, 18));
369
+ const xBtn = el("button", { type: "button", class: "ckw-msgr-ib ckw-msgr-x", "aria-label": "Close messenger" }, icon(I.cross));
370
+ xBtn.addEventListener("click", () => close());
371
+ bar.append(lead, seg, el("span", { class: "ckw-msgr-trail" }, menuBtn, xBtn));
372
+ const menu = el("div", { class: "ckw-msgr-menu", role: "menu" });
373
+ const menuOpen = () => menu.isConnected && !menu.hasAttribute("data-out");
374
+ const closeMenu = () => {
375
+ menuBtn.setAttribute("aria-expanded", "false");
376
+ leave(menu, 120);
377
+ };
378
+ const menuItem = (d, name, act) => {
379
+ const b = el("button", { type: "button", class: "ckw-msgr-menu-i", role: "menuitem" }, icon(d, 15), el("span", {}, name));
380
+ b.addEventListener("click", (e) => {
381
+ e.stopPropagation();
382
+ closeMenu();
383
+ act();
384
+ });
385
+ return b;
386
+ };
387
+ menuBtn.addEventListener("click", (e) => {
388
+ e.stopPropagation();
389
+ if (menuOpen()) return closeMenu();
390
+ menu.replaceChildren(
391
+ el("span", { class: "ckw-msgr-menu-h" }, "Quick actions"),
392
+ menuItem(I.minus, "Minimize chat", () => close()),
393
+ menuItem(wide ? I.collapse : I.expand, wide ? "Collapse view" : "Expand view", () => {
394
+ wide = !wide;
395
+ pop.toggleAttribute("data-wide", wide);
396
+ requestAnimationFrame(placeMark);
397
+ })
398
+ );
399
+ menuBtn.setAttribute("aria-expanded", "true");
400
+ enter(menu, pop);
401
+ menu.querySelector("button")?.focus();
402
+ });
403
+ pop.addEventListener("click", () => menuOpen() && closeMenu());
404
+ function placeMark() {
405
+ const current = view === "messages" ? chatTab : view !== "home" ? helpTab : null;
406
+ if (!current || !current.isConnected) return mark.removeAttribute("data-on");
407
+ const first = !mark.hasAttribute("data-on");
408
+ if (first) mark.setAttribute("data-still", "");
409
+ mark.style.width = `${current.offsetWidth}px`;
410
+ mark.style.transform = `translateX(${current.offsetLeft}px)`;
411
+ mark.setAttribute("data-on", "");
412
+ if (first) requestAnimationFrame(() => mark.removeAttribute("data-still"));
413
+ }
414
+ const drawBar = () => {
415
+ const back = (to, name) => {
416
+ const b = el("button", { type: "button", class: "ckw-msgr-ib", "aria-label": name }, icon(I.back, 18));
417
+ b.addEventListener("click", to);
418
+ return b;
419
+ };
420
+ if (typeof view !== "string")
421
+ lead.replaceChildren(
422
+ back(() => go("help"), "Back to help")
423
+ );
424
+ else if (view === "help" && collection && !query.trim())
425
+ lead.replaceChildren(
426
+ back(() => {
427
+ collection = null;
428
+ go("help");
429
+ }, "All collections")
430
+ );
431
+ else {
432
+ const home = el("button", { type: "button", class: "ckw-msgr-ib", "aria-label": "Home", "aria-current": view === "home" ? "page" : "false" }, icon(I.home, 17));
433
+ home.addEventListener("click", () => go("home"));
434
+ lead.replaceChildren(home);
435
+ }
436
+ const tabsWanted = helpOn && hasArticles !== false ? [mark, chatTab, helpTab] : [mark, chatTab];
437
+ if (seg.childNodes.length !== tabsWanted.length) seg.replaceChildren(...tabsWanted);
438
+ chatTab.setAttribute("aria-current", view === "messages" ? "page" : "false");
439
+ helpTab.setAttribute("aria-current", view !== "home" && view !== "messages" ? "page" : "false");
440
+ if (unseen.length && view !== "messages") {
441
+ tabCount.textContent = String(Math.min(unseen.length, 9));
442
+ if (!tabCount.isConnected) chatTab.append(tabCount);
443
+ } else tabCount.remove();
444
+ };
269
445
  const form = el("form", { class: "ckw-msgr-compose" });
270
446
  const err = el("p", { class: "ckw-msgr-error", role: "alert" });
271
447
  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-row" });
273
- const text = el("textarea", { class: "ckw-textarea ckw-msgr-input", rows: "1", placeholder: "Write a message\u2026", "aria-label": "Message", maxlength: "4000" });
448
+ const row = el("div", { class: "ckw-msgr-box" });
449
+ const text = el("textarea", { class: "ckw-textarea ckw-msgr-input", rows: "1", placeholder: "Write your message\u2026", "aria-label": "Message", maxlength: "4000" });
274
450
  const send = el("button", { type: "submit", class: "ckw-msgr-send", "aria-label": "Send" }, icon(I.up));
275
- row.append(text, send);
451
+ row.append(text, el("div", { class: "ckw-msgr-box-foot" }, el("span", { class: "ckw-msgr-hint" }, "Enter to send"), send));
452
+ row.addEventListener("click", (e) => e.target === row && text.focus());
276
453
  form.append(row);
277
454
  const list = el("ol", { class: "ckw-msgs", "aria-live": "polite", "aria-label": "Messages" });
278
455
  const needEmail = () => askEmail && !o.user()?.email && !thread?.hasEmail;
279
456
  const emailOk = () => !email.value.trim() || EMAIL.test(email.value.trim());
280
457
  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
- };
286
458
  const go = (v) => {
459
+ const changed = JSON.stringify(v) !== JSON.stringify(view);
287
460
  view = v;
288
461
  drawn = -1;
289
462
  draw();
463
+ if (changed) replay(body, "ckw-msgr-enter");
290
464
  if (v === "messages") {
291
465
  markSeen();
292
466
  drawFab();
@@ -310,9 +484,19 @@ var build = (o) => {
310
484
  b.addEventListener("click", () => go({ article: a.id }));
311
485
  return b;
312
486
  };
313
- const searchBox = () => {
487
+ const helpRow = (name, sub, meta, act) => {
488
+ const b = el(
489
+ "button",
490
+ { type: "button", class: "ckw-msgr-hrow" },
491
+ 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),
492
+ icon(I.next, 16)
493
+ );
494
+ b.addEventListener("click", act);
495
+ return b;
496
+ };
497
+ const searchBox = (placeholder = "Search for help") => {
314
498
  const wrap = el("label", { class: "ckw-msgr-search" });
315
- const input = el("input", { class: "ckw-msgr-search-in", type: "search", placeholder: "Search for help", "aria-label": "Search for help", maxlength: "200" });
499
+ const input = el("input", { class: "ckw-msgr-search-in", type: "search", placeholder, "aria-label": "Search for help", maxlength: "200" });
316
500
  input.value = query;
317
501
  let t;
318
502
  input.addEventListener("input", () => {
@@ -329,10 +513,10 @@ var build = (o) => {
329
513
  if (view !== "help") go("help");
330
514
  }
331
515
  });
332
- wrap.append(input, icon(I.search, 16));
516
+ wrap.append(icon(I.search, 16), input);
333
517
  return wrap;
334
518
  };
335
- const helpSearch = searchBox();
519
+ const helpSearch = searchBox("Search our help center\u2026");
336
520
  const helpInput = helpSearch.querySelector("input");
337
521
  let loadingArticles = false;
338
522
  const loadArticles = async (force = false) => {
@@ -352,8 +536,13 @@ var build = (o) => {
352
536
  draw();
353
537
  };
354
538
  const drawHome = () => {
355
- head.replaceChildren(el("span", { class: "ckw-msgr-brand" }, avatar("sm"), el("span", {}, title)), closeBtn());
356
- const hero = el("div", { class: "ckw-msgr-hero" }, el("p", { class: "ckw-msgr-hello" }, greeting));
539
+ const hero = el(
540
+ "div",
541
+ { class: "ckw-msgr-hero" },
542
+ el("span", { class: "ckw-msgr-brand" }, avatar("sm"), el("span", {}, title)),
543
+ el("p", { class: "ckw-msgr-hello" }, greeting),
544
+ el("span", { class: "ckw-msgr-online" }, reply)
545
+ );
357
546
  const last = (thread?.messages ?? []).at(-1);
358
547
  const parts = [hero];
359
548
  if (last) {
@@ -395,30 +584,50 @@ var build = (o) => {
395
584
  body.replaceChildren(...parts);
396
585
  };
397
586
  const drawHelp = () => {
398
- head.replaceChildren(el("span", {}, "Help"), closeBtn());
399
587
  if (helpInput.value !== query) helpInput.value = query;
400
- const parts = [helpSearch];
588
+ const q = query.trim();
589
+ const all = articles ?? [];
590
+ const groups = /* @__PURE__ */ new Map();
591
+ for (const a of all) groups.set(a.collection, [...groups.get(a.collection) ?? [], a]);
592
+ const inside = !q && collection !== null ? groups.get(collection) ?? [] : null;
593
+ const heading = q ? "Search results" : inside ? collection ?? "" : "Most read articles";
594
+ const parts = [el("h2", { class: "ckw-msgr-help-h" }, heading), helpSearch];
401
595
  if (articles === null) parts.push(el("div", { class: "ckw-skel" }));
402
- else if (!articles.length) {
596
+ else if (!all.length) {
403
597
  const ask = el("button", { type: "button", class: "ckw-btn ckw-btn-primary" }, "Send us a message");
404
598
  ask.addEventListener("click", () => go("messages"));
405
- parts.push(el("div", { class: "ckw-msgr-empty" }, el("p", {}, query.trim() ? `Nothing found for \u201C${query.trim()}\u201D.` : "No help articles yet."), ask));
406
- } else if (query.trim()) parts.push(el("div", { class: "ckw-msgr-card ckw-msgr-card-col" }, ...articles.map(articleRow)));
599
+ parts.push(el("div", { class: "ckw-msgr-empty" }, el("p", {}, q ? `Nothing found for \u201C${q}\u201D.` : "No help articles yet."), ask));
600
+ } else if (q || inside)
601
+ parts.push(
602
+ el(
603
+ "div",
604
+ { class: "ckw-msgr-hlist" },
605
+ ...(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 })))
606
+ )
607
+ );
608
+ else
609
+ parts.push(
610
+ el(
611
+ "div",
612
+ { class: "ckw-msgr-hlist" },
613
+ ...[...groups].map(
614
+ ([name, items]) => helpRow(name, items.map((a) => a.title).join(", "), `${items.length} ${items.length === 1 ? "article" : "articles"}`, () => {
615
+ collection = name;
616
+ go("help");
617
+ replay(body, "ckw-msgr-enter");
618
+ })
619
+ )
620
+ )
621
+ );
622
+ if (body.childNodes[1] !== helpSearch) body.replaceChildren(...parts);
407
623
  else {
408
- const groups = /* @__PURE__ */ new Map();
409
- for (const a of articles) groups.set(a.collection, [...groups.get(a.collection) ?? [], a]);
410
- for (const [name, items] of groups)
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))));
624
+ body.firstChild.replaceWith(parts[0]);
625
+ while (body.childNodes.length > 2) body.lastChild.remove();
626
+ body.append(...parts.slice(2));
412
627
  }
413
- if (body.firstChild !== helpSearch) body.replaceChildren(helpSearch);
414
- while (body.childNodes.length > 1) body.lastChild.remove();
415
- body.append(...parts.slice(1));
416
628
  };
417
629
  let articleShown = null;
418
630
  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
631
  if (articleShown?.id === id) return body.replaceChildren(articleShown.node);
423
632
  const node = el("article", { class: "ckw-msgr-doc" }, el("div", { class: "ckw-skel" }));
424
633
  articleShown = { id, node };
@@ -457,22 +666,42 @@ var build = (o) => {
457
666
  };
458
667
  let drawn = -1;
459
668
  let listSig = "";
669
+ let listCount = -1;
670
+ const intro = () => el(
671
+ "div",
672
+ { class: "ckw-msgr-intro" },
673
+ el("span", { class: "ckw-msgr-intro-av" }, avatar()),
674
+ el("span", { class: "ckw-msgr-intro-t" }, el("b", {}, title), el("span", { class: "ckw-msgr-online" }, reply))
675
+ );
676
+ const introNode = intro();
677
+ const suggestions = el("div", { class: "ckw-msgr-sugg" });
460
678
  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
679
  const msgs = thread?.messages ?? [];
466
- const parts = [];
680
+ const parts = [introNode];
467
681
  if (!loaded) parts.push(el("div", { class: "ckw-skel" }));
468
682
  else {
469
683
  const sig = `${msgs.map((m) => m.id).join(",")}|${pending ? pending.body : ""}|${Math.floor(Date.now() / 6e4)}`;
470
684
  if (sig !== listSig) {
471
685
  listSig = sig;
472
686
  const greet = el("li", { class: "ckw-msg", "data-from": "them" }, el("div", { class: "ckw-bubble" }, greeting));
473
- list.replaceChildren(greet, ...msgs.map((m) => bubble(m)), ...pending ? [bubble(pending, true)] : []);
687
+ const items = [greet, ...msgs.map((m) => bubble(m)), ...pending ? [bubble(pending, true)] : []];
688
+ if (listCount >= 0) items.slice(listCount).forEach((li) => li.setAttribute("data-new", ""));
689
+ listCount = items.length;
690
+ list.replaceChildren(...items);
474
691
  }
475
692
  parts.push(list);
693
+ const top = !msgs.some((m) => m.from === "visitor") && !pending ? (articles ?? []).slice(0, 3) : [];
694
+ if (top.length) {
695
+ if (suggestions.childNodes.length !== top.length)
696
+ suggestions.replaceChildren(
697
+ ...top.map((a) => {
698
+ const b = el("button", { type: "button", class: "ckw-msgr-sugg-b" }, a.title);
699
+ b.addEventListener("click", () => go({ article: a.id }));
700
+ return b;
701
+ })
702
+ );
703
+ parts.push(suggestions);
704
+ }
476
705
  }
477
706
  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
707
  if (body.childNodes.length !== parts.length || parts.some((x, i) => body.childNodes[i] !== x)) body.replaceChildren(...parts);
@@ -491,28 +720,19 @@ var build = (o) => {
491
720
  } else email.remove();
492
721
  send.disabled = !canSend();
493
722
  };
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
723
  const draw = () => {
505
724
  if (!open) return;
506
725
  pop.dataset.view = typeof view === "string" ? view : "article";
726
+ drawBar();
507
727
  if (view === "home") drawHome();
508
728
  else if (view === "messages") drawMessages();
509
729
  else if (view === "help") drawHelp();
510
730
  else drawArticle(view.article);
511
- const kids = [head, body];
731
+ const kids = [bar, body];
512
732
  if (view === "messages") kids.push(form);
513
- kids.push(tabs);
514
- drawTabs();
733
+ if (menu.isConnected) kids.push(menu);
515
734
  if (pop.childNodes.length !== kids.length || kids.some((k, i) => pop.childNodes[i] !== k)) pop.replaceChildren(...kids);
735
+ placeMark();
516
736
  };
517
737
  const tick = async () => {
518
738
  ctrl?.abort();
@@ -537,10 +757,11 @@ var build = (o) => {
537
757
  const ms = now ?? (watching() ? POLL_OPEN_MS : waiting && recent ? POLL_WAITING_MS : open || waiting ? POLL_IDLE_MS : 0);
538
758
  if (ms || now === 0) poll = setTimeout(() => void tick(), ms);
539
759
  }
540
- document.addEventListener("visibilitychange", () => {
760
+ const onVisible = () => {
541
761
  if (document.visibilityState === "visible") schedule(0);
542
762
  else clearTimeout(poll);
543
- });
763
+ };
764
+ document.addEventListener("visibilitychange", onVisible);
544
765
  const submit = async () => {
545
766
  const msg = text.value.trim();
546
767
  if (!msg || pending || !emailOk()) return;
@@ -571,12 +792,16 @@ var build = (o) => {
571
792
  });
572
793
  text.addEventListener("input", () => send.disabled = !canSend());
573
794
  email.addEventListener("input", () => draw());
574
- function show(v = "home") {
795
+ function show(v = "home", still = false) {
796
+ const was = open;
575
797
  open = true;
576
798
  view = v === "help" && !helpOn ? "home" : v;
577
799
  drawn = -1;
578
800
  previewGone = true;
579
- if (!pop.isConnected) root.appendChild(pop);
801
+ hideTeaser(true);
802
+ pop.toggleAttribute("data-still", still);
803
+ enter(pop);
804
+ if (was) replay(body, "ckw-msgr-enter");
580
805
  if (articles === null || view === "help" && query) void loadArticles();
581
806
  draw();
582
807
  drawFab();
@@ -589,26 +814,35 @@ var build = (o) => {
589
814
  function close() {
590
815
  if (!open) return;
591
816
  open = false;
592
- pop.remove();
817
+ if (menu.isConnected) closeMenu();
818
+ leave(pop, 200);
593
819
  drawFab();
594
820
  schedule();
595
821
  if (launcherOn) fab.focus();
596
822
  }
597
823
  const onKey = (e) => {
598
- if (e.key === "Escape" && open) close();
824
+ if (e.key !== "Escape" || !open) return;
825
+ if (menuOpen()) {
826
+ closeMenu();
827
+ menuBtn.focus();
828
+ } else close();
599
829
  };
600
830
  document.addEventListener("keydown", onKey);
601
831
  void tick();
602
832
  if (helpOn) void loadArticles();
603
833
  drawFab();
834
+ if (o.opened) show(o.opened, true);
604
835
  return {
605
836
  open: (v) => show(v ?? "home"),
606
837
  close,
607
838
  toggle: () => open ? close() : show(),
608
839
  destroy: () => {
609
840
  clearTimeout(poll);
841
+ clearTimeout(teaserTimer);
842
+ for (const t of leaving.values()) clearTimeout(t);
610
843
  ctrl?.abort();
611
844
  document.removeEventListener("keydown", onKey);
845
+ document.removeEventListener("visibilitychange", onVisible);
612
846
  root.remove();
613
847
  }
614
848
  };