@guidelytech/guide-runtime 0.8.0 → 0.10.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/dist/capture.cjs +160 -34
- package/dist/capture.cjs.map +1 -1
- package/dist/capture.js +9 -14
- package/dist/capture.js.map +1 -1
- package/dist/chunk-GUH2PQJ7.js +462 -0
- package/dist/chunk-GUH2PQJ7.js.map +1 -0
- package/dist/chunk-KJRXCITD.js +515 -0
- package/dist/chunk-KJRXCITD.js.map +1 -0
- package/dist/chunk-PG7CMBHD.js +330 -0
- package/dist/chunk-PG7CMBHD.js.map +1 -0
- package/dist/chunk-VNJPNGZ4.js +301 -0
- package/dist/chunk-VNJPNGZ4.js.map +1 -0
- package/dist/index.cjs +356 -64
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +62 -8
- package/dist/index.d.ts +62 -8
- package/dist/index.js +107 -23
- package/dist/index.js.map +1 -1
- package/dist/messenger.cjs +586 -35
- package/dist/messenger.cjs.map +1 -1
- package/dist/messenger.d.cts +21 -1
- package/dist/messenger.d.ts +21 -1
- package/dist/messenger.js +527 -23
- package/dist/messenger.js.map +1 -1
- package/package.json +5 -5
package/dist/messenger.cjs
CHANGED
|
@@ -25,7 +25,7 @@ __export(messenger_exports, {
|
|
|
25
25
|
renderMessenger: () => renderMessenger
|
|
26
26
|
});
|
|
27
27
|
module.exports = __toCommonJS(messenger_exports);
|
|
28
|
-
var
|
|
28
|
+
var import_runtime5 = require("@guidelytech/shared/runtime");
|
|
29
29
|
|
|
30
30
|
// src/help-view.ts
|
|
31
31
|
var import_runtime3 = require("@guidelytech/shared/runtime");
|
|
@@ -270,6 +270,38 @@ function setSanitizedHtml(el2, html) {
|
|
|
270
270
|
}
|
|
271
271
|
|
|
272
272
|
// src/help-view.ts
|
|
273
|
+
var SHOW_ME_KINDS = /* @__PURE__ */ new Set(["flow", "modal", "tooltip", "announcement"]);
|
|
274
|
+
function startableGuide(guides, keyOrId) {
|
|
275
|
+
if (!keyOrId || !guides) return null;
|
|
276
|
+
return guides.find((g) => (g.key === keyOrId || g.id === keyOrId) && SHOW_ME_KINDS.has(g.kind)) ?? null;
|
|
277
|
+
}
|
|
278
|
+
function resultRow(doc, icon2, title, summary, onOpen, showMe) {
|
|
279
|
+
const item = listItem(doc, icon2, title, summary);
|
|
280
|
+
item.addEventListener("click", onOpen);
|
|
281
|
+
if (!showMe) return item;
|
|
282
|
+
const row = el(doc, "div", "g-rc-row");
|
|
283
|
+
const b = el(doc, "button", "g-btn g-btn-primary g-rc-showme", "\u25B6 Show me");
|
|
284
|
+
b.type = "button";
|
|
285
|
+
b.setAttribute("aria-label", `Show me: ${title}`);
|
|
286
|
+
b.addEventListener("click", showMe);
|
|
287
|
+
row.append(item, b);
|
|
288
|
+
return row;
|
|
289
|
+
}
|
|
290
|
+
function enhanceShowMe(root, doc, canStart, start) {
|
|
291
|
+
for (const a of Array.from(root.querySelectorAll(`a[href^="${import_runtime3.SHOW_ME_HREF_PREFIX}"]`))) {
|
|
292
|
+
const key = (0, import_runtime3.showMeKeyFromHref)(a.getAttribute("href"));
|
|
293
|
+
const holder = a.closest("p.gd-show-me") ?? a;
|
|
294
|
+
if (!key || !canStart(key)) {
|
|
295
|
+
if (holder !== a) holder.remove();
|
|
296
|
+
else a.replaceWith(doc.createTextNode(a.textContent ?? ""));
|
|
297
|
+
continue;
|
|
298
|
+
}
|
|
299
|
+
const b = el(doc, "button", "g-btn g-btn-primary g-help-showme", `\u25B6 ${(a.textContent ?? "").trim() || "Show me"}`);
|
|
300
|
+
b.type = "button";
|
|
301
|
+
b.addEventListener("click", () => start(key));
|
|
302
|
+
a.replaceWith(b);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
273
305
|
function el(doc, tag, cls, text) {
|
|
274
306
|
const e = doc.createElement(tag);
|
|
275
307
|
if (cls) e.className = cls;
|
|
@@ -294,6 +326,11 @@ function createHelpView(options) {
|
|
|
294
326
|
let searchSeq = 0;
|
|
295
327
|
let openSeq = 0;
|
|
296
328
|
let active = null;
|
|
329
|
+
const setActive = (next) => {
|
|
330
|
+
const was = !!active;
|
|
331
|
+
active = next;
|
|
332
|
+
if (was !== !!next) options.onArticleChange?.(!!next);
|
|
333
|
+
};
|
|
297
334
|
const root = el(doc, "div", "g-help");
|
|
298
335
|
const searchWrap = el(doc, "div", "g-rc-search");
|
|
299
336
|
const input = el(doc, "input", "g-input");
|
|
@@ -304,6 +341,8 @@ function createHelpView(options) {
|
|
|
304
341
|
const body = el(doc, "div", "g-panel-body");
|
|
305
342
|
root.append(searchWrap, body);
|
|
306
343
|
const hasBody = (a) => typeof a.body === "string" && !a.bodyOmitted;
|
|
344
|
+
const canShowMe = (key) => !!options.onShowMe && !!startableGuide(options.guides?.(), key);
|
|
345
|
+
const showMe = (key) => options.onShowMe?.(startableGuide(options.guides?.(), key)?.key ?? key);
|
|
307
346
|
const openArticle = (article) => {
|
|
308
347
|
if (article.externalUrl) {
|
|
309
348
|
window.open(article.externalUrl, "_blank", "noopener,noreferrer");
|
|
@@ -312,20 +351,20 @@ function createHelpView(options) {
|
|
|
312
351
|
}
|
|
313
352
|
const seq = ++openSeq;
|
|
314
353
|
if (hasBody(article) || !options.loadArticle) {
|
|
315
|
-
|
|
354
|
+
setActive({ article, loading: false });
|
|
316
355
|
if (hasBody(article)) options.onArticleView?.(article);
|
|
317
356
|
return render();
|
|
318
357
|
}
|
|
319
|
-
|
|
358
|
+
setActive({ article, loading: true });
|
|
320
359
|
render();
|
|
321
360
|
options.loadArticle(article.id).then((full) => {
|
|
322
361
|
if (seq !== openSeq || !active) return;
|
|
323
|
-
|
|
362
|
+
setActive({ article: full ?? { ...article, body: "" }, loading: false });
|
|
324
363
|
if (full) options.onArticleView?.(full);
|
|
325
364
|
render();
|
|
326
365
|
}).catch(() => {
|
|
327
366
|
if (seq !== openSeq || !active) return;
|
|
328
|
-
|
|
367
|
+
setActive({ article: { ...article, body: "" }, loading: false });
|
|
329
368
|
render();
|
|
330
369
|
});
|
|
331
370
|
};
|
|
@@ -335,7 +374,7 @@ function createHelpView(options) {
|
|
|
335
374
|
back.type = "button";
|
|
336
375
|
back.addEventListener("click", () => {
|
|
337
376
|
openSeq++;
|
|
338
|
-
|
|
377
|
+
setActive(null);
|
|
339
378
|
render();
|
|
340
379
|
});
|
|
341
380
|
const title = el(doc, "h3", "g-title", article.title);
|
|
@@ -344,6 +383,7 @@ function createHelpView(options) {
|
|
|
344
383
|
else if (article.body) {
|
|
345
384
|
setSanitizedHtml(content, article.body);
|
|
346
385
|
enhancePdfViewers(content, doc);
|
|
386
|
+
enhanceShowMe(content, doc, canShowMe, showMe);
|
|
347
387
|
} else content.appendChild(el(doc, "div", "g-rc-empty", "This article couldn't be loaded."));
|
|
348
388
|
wrap.append(back, title, content);
|
|
349
389
|
body.appendChild(wrap);
|
|
@@ -355,9 +395,9 @@ function createHelpView(options) {
|
|
|
355
395
|
return s;
|
|
356
396
|
};
|
|
357
397
|
const articleButton = (a) => {
|
|
358
|
-
const
|
|
359
|
-
|
|
360
|
-
body.appendChild(
|
|
398
|
+
const key = a.walkthroughs?.find(canShowMe);
|
|
399
|
+
const summary = a.hasVideo ? a.summary ? `Video \xB7 ${a.summary}` : "Video" : a.summary;
|
|
400
|
+
body.appendChild(resultRow(doc, a.hasVideo ? "\u25B6" : "\u{1F4C4}", a.title, summary, () => openArticle(a), key ? () => showMe(key) : void 0));
|
|
361
401
|
};
|
|
362
402
|
function render() {
|
|
363
403
|
body.textContent = "";
|
|
@@ -366,10 +406,17 @@ function createHelpView(options) {
|
|
|
366
406
|
const member = options.audienceIds?.() ?? [];
|
|
367
407
|
const sections = (0, import_runtime3.resourceCenterSections)(data.collections, data.articles, member);
|
|
368
408
|
const q = query.trim().toLowerCase();
|
|
409
|
+
if (q) {
|
|
410
|
+
const walkthroughs = (data.recommended ?? []).filter((g) => (0, import_runtime3.matchesHelpQuery)(g.name, q) && startableGuide(options.guides?.() ?? data.recommended, g.id));
|
|
411
|
+
if (walkthroughs.length) {
|
|
412
|
+
section(`Walkthroughs (${walkthroughs.length})`);
|
|
413
|
+
for (const g of walkthroughs) body.appendChild(resultRow(doc, "\u25B6", g.name, "Show me, step by step", () => options.onStartGuide?.(g.id)));
|
|
414
|
+
}
|
|
415
|
+
}
|
|
369
416
|
body.append(...options.extra?.(q) ?? []);
|
|
370
417
|
if (q) {
|
|
371
418
|
const visible = sections.flatMap((s) => s.articles);
|
|
372
|
-
const local = visible.filter((a) => a.title
|
|
419
|
+
const local = visible.filter((a) => (0, import_runtime3.matchesHelpQuery)(`${a.title} ${a.summary}`, q));
|
|
373
420
|
const seen = new Set(local.map((a) => a.id));
|
|
374
421
|
const hidden = new Set(data.articles.map((a) => a.id).filter((id) => !visible.some((v) => v.id === id)));
|
|
375
422
|
const merged = [...local, ...serverResults.filter((a) => !seen.has(a.id) && !hidden.has(a.id) && (0, import_runtime3.isArticleListed)(data.collections, a, member))];
|
|
@@ -441,7 +488,7 @@ function createHelpView(options) {
|
|
|
441
488
|
const onInput = () => {
|
|
442
489
|
query = input.value;
|
|
443
490
|
openSeq++;
|
|
444
|
-
|
|
491
|
+
setActive(null);
|
|
445
492
|
render();
|
|
446
493
|
if (debounce) clearTimeout(debounce);
|
|
447
494
|
if (!options.search || !query.trim()) {
|
|
@@ -464,7 +511,7 @@ function createHelpView(options) {
|
|
|
464
511
|
render,
|
|
465
512
|
reset() {
|
|
466
513
|
openSeq++;
|
|
467
|
-
|
|
514
|
+
setActive(null);
|
|
468
515
|
query = "";
|
|
469
516
|
input.value = "";
|
|
470
517
|
serverResults = [];
|
|
@@ -473,6 +520,7 @@ function createHelpView(options) {
|
|
|
473
520
|
const listed = options.data().articles.find((a) => a.id === id) ?? { id, slug: id, title: "Article", summary: "", updatedAt: "", bodyOmitted: true };
|
|
474
521
|
openArticle(listed);
|
|
475
522
|
},
|
|
523
|
+
isArticleOpen: () => !!active,
|
|
476
524
|
focusSearch: () => input.focus?.({ preventScroll: true }),
|
|
477
525
|
setQuery(q) {
|
|
478
526
|
input.value = q;
|
|
@@ -545,6 +593,15 @@ function renderHowTo(doc, howTo, onShowMe) {
|
|
|
545
593
|
show.addEventListener("click", () => onShowMe(guideKey));
|
|
546
594
|
wrap.appendChild(show);
|
|
547
595
|
}
|
|
596
|
+
if (howTo.video && /^https?:\/\//i.test(howTo.video.url)) {
|
|
597
|
+
const video = el(doc, "video", "g-howto-video");
|
|
598
|
+
video.src = howTo.video.url;
|
|
599
|
+
video.controls = true;
|
|
600
|
+
video.preload = "metadata";
|
|
601
|
+
video.playsInline = true;
|
|
602
|
+
video.setAttribute("aria-label", `Video: ${howTo.title}`);
|
|
603
|
+
wrap.appendChild(video);
|
|
604
|
+
}
|
|
548
605
|
const list = el(doc, "ol", "g-howto-steps");
|
|
549
606
|
let n = 0;
|
|
550
607
|
for (const s of howTo.steps) {
|
|
@@ -578,6 +635,275 @@ function renderHowTo(doc, howTo, onShowMe) {
|
|
|
578
635
|
return wrap;
|
|
579
636
|
}
|
|
580
637
|
|
|
638
|
+
// src/complaint-view.ts
|
|
639
|
+
var import_runtime4 = require("@guidelytech/shared/runtime");
|
|
640
|
+
var emptyComplaintDraft = (description = "") => ({ description, answers: {}, email: "", attachment: null, error: null, sending: false, checked: false });
|
|
641
|
+
var COMPLAINT_STEPS = [
|
|
642
|
+
["open", "Received"],
|
|
643
|
+
["in_review", "In review"],
|
|
644
|
+
["resolved", "Resolved"]
|
|
645
|
+
];
|
|
646
|
+
var complaintStatusLabel = (s) => COMPLAINT_STEPS.find(([k]) => k === s)?.[1] ?? s;
|
|
647
|
+
var EMAIL = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
648
|
+
function complaintProblem(category, draft, needsEmail) {
|
|
649
|
+
for (const f of category.fields) if (f.required && !(draft.answers[f.id] ?? "").trim()) return `${f.label} is required.`;
|
|
650
|
+
if (!draft.description.trim()) return "Tell us what happened.";
|
|
651
|
+
if (needsEmail && !EMAIL.test(draft.email.trim())) return "Enter your email address so the team can reply.";
|
|
652
|
+
return null;
|
|
653
|
+
}
|
|
654
|
+
function complaintAnswers(category, draft) {
|
|
655
|
+
const out = {};
|
|
656
|
+
for (const f of category.fields) {
|
|
657
|
+
const v = (draft.answers[f.id] ?? "").trim();
|
|
658
|
+
if (v) out[f.id] = v.slice(0, import_runtime4.MAX_COMPLAINT_ANSWER_LENGTH);
|
|
659
|
+
}
|
|
660
|
+
return out;
|
|
661
|
+
}
|
|
662
|
+
function statusPill(doc, status) {
|
|
663
|
+
return el(doc, "span", `g-m-pill g-m-pill-${status}`, complaintStatusLabel(status));
|
|
664
|
+
}
|
|
665
|
+
function dot(doc, color) {
|
|
666
|
+
const d = el(doc, "span", "g-m-cat-dot");
|
|
667
|
+
if (/^#[0-9a-fA-F]{6}$/.test(color)) d.style.background = color;
|
|
668
|
+
d.setAttribute("aria-hidden", "true");
|
|
669
|
+
return d;
|
|
670
|
+
}
|
|
671
|
+
function categoryList(doc, categories, onPick) {
|
|
672
|
+
const wrap = el(doc, "div", "g-m-form");
|
|
673
|
+
const intro = el(doc, "div");
|
|
674
|
+
intro.append(el(doc, "h3", "g-m-form-title", "What's it about?"), el(doc, "p", "g-m-card-sub", "Picking the right one gets it to the right team faster."));
|
|
675
|
+
wrap.appendChild(intro);
|
|
676
|
+
const list = el(doc, "div", "g-m-cats");
|
|
677
|
+
list.setAttribute("role", "list");
|
|
678
|
+
for (const c of categories) {
|
|
679
|
+
const b = el(doc, "button", "g-m-card g-m-row g-m-center g-m-cat");
|
|
680
|
+
b.type = "button";
|
|
681
|
+
b.setAttribute("role", "listitem");
|
|
682
|
+
b.appendChild(dot(doc, c.color));
|
|
683
|
+
const text = el(doc, "span", "g-m-grow");
|
|
684
|
+
text.appendChild(el(doc, "span", "g-m-card-title", c.name));
|
|
685
|
+
if (c.description) text.appendChild(el(doc, "span", "g-m-card-sub", c.description));
|
|
686
|
+
b.appendChild(text);
|
|
687
|
+
b.addEventListener("click", () => onPick(c));
|
|
688
|
+
list.appendChild(b);
|
|
689
|
+
}
|
|
690
|
+
wrap.appendChild(list);
|
|
691
|
+
return wrap;
|
|
692
|
+
}
|
|
693
|
+
var fieldSeq = 0;
|
|
694
|
+
function field(doc, label, control, opts = {}) {
|
|
695
|
+
const id = `g-m-f${++fieldSeq}`;
|
|
696
|
+
control.id = id;
|
|
697
|
+
const wrap = el(doc, "div", "g-m-field");
|
|
698
|
+
const l = el(doc, "label", "g-m-label", label);
|
|
699
|
+
l.htmlFor = id;
|
|
700
|
+
if (opts.optional) l.appendChild(el(doc, "span", "g-m-optional", " (optional)"));
|
|
701
|
+
wrap.append(l, control);
|
|
702
|
+
if (opts.hint) {
|
|
703
|
+
const hint = el(doc, "span", "g-m-hint", opts.hint);
|
|
704
|
+
hint.id = `${id}-hint`;
|
|
705
|
+
control.setAttribute("aria-describedby", hint.id);
|
|
706
|
+
wrap.appendChild(hint);
|
|
707
|
+
}
|
|
708
|
+
if (opts.invalid) control.setAttribute("aria-invalid", "true");
|
|
709
|
+
return wrap;
|
|
710
|
+
}
|
|
711
|
+
function complaintForm(doc, o) {
|
|
712
|
+
const { category, draft } = o;
|
|
713
|
+
const form = el(doc, "form", "g-m-form");
|
|
714
|
+
form.noValidate = true;
|
|
715
|
+
const picked = el(doc, "div", "g-m-picked g-m-row g-m-center");
|
|
716
|
+
picked.append(dot(doc, category.color), el(doc, "span", "g-m-grow g-m-card-title", category.name));
|
|
717
|
+
if (o.canChangeCategory) {
|
|
718
|
+
const change = el(doc, "button", "g-m-text-btn", "Change");
|
|
719
|
+
change.type = "button";
|
|
720
|
+
change.setAttribute("aria-label", `Change category (${category.name})`);
|
|
721
|
+
change.addEventListener("click", o.onChangeCategory);
|
|
722
|
+
picked.appendChild(change);
|
|
723
|
+
}
|
|
724
|
+
form.appendChild(picked);
|
|
725
|
+
for (const f of category.fields) {
|
|
726
|
+
const missing = draft.checked && f.required && !(draft.answers[f.id] ?? "").trim();
|
|
727
|
+
let control;
|
|
728
|
+
if (f.type === "choice") {
|
|
729
|
+
const select = el(doc, "select", "g-input");
|
|
730
|
+
const option = (label, value) => {
|
|
731
|
+
const o2 = el(doc, "option", "", label);
|
|
732
|
+
o2.value = value;
|
|
733
|
+
return o2;
|
|
734
|
+
};
|
|
735
|
+
select.appendChild(option(f.required ? "Choose one" : "Choose one (optional)", ""));
|
|
736
|
+
for (const opt of f.options ?? []) select.appendChild(option(opt, opt));
|
|
737
|
+
select.value = draft.answers[f.id] ?? "";
|
|
738
|
+
select.addEventListener("change", () => draft.answers[f.id] = select.value);
|
|
739
|
+
control = select;
|
|
740
|
+
} else {
|
|
741
|
+
const input = el(doc, "input", "g-input");
|
|
742
|
+
input.type = "text";
|
|
743
|
+
input.maxLength = import_runtime4.MAX_COMPLAINT_ANSWER_LENGTH;
|
|
744
|
+
input.value = draft.answers[f.id] ?? "";
|
|
745
|
+
input.addEventListener("input", () => draft.answers[f.id] = input.value);
|
|
746
|
+
control = input;
|
|
747
|
+
}
|
|
748
|
+
if (f.required) control.required = true;
|
|
749
|
+
form.appendChild(field(doc, f.label, control, { optional: !f.required, invalid: missing }));
|
|
750
|
+
}
|
|
751
|
+
const what = el(doc, "textarea", "g-input g-m-textarea");
|
|
752
|
+
what.rows = 5;
|
|
753
|
+
what.maxLength = import_runtime4.MAX_COMPLAINT_LENGTH;
|
|
754
|
+
what.required = true;
|
|
755
|
+
what.value = draft.description;
|
|
756
|
+
what.placeholder = "What went wrong, and when?";
|
|
757
|
+
what.addEventListener("input", () => draft.description = what.value);
|
|
758
|
+
form.appendChild(field(doc, "What happened?", what, { invalid: draft.checked && !draft.description.trim() }));
|
|
759
|
+
if (o.needsEmail) {
|
|
760
|
+
const email = el(doc, "input", "g-input");
|
|
761
|
+
email.type = "email";
|
|
762
|
+
email.autocomplete = "email";
|
|
763
|
+
email.required = true;
|
|
764
|
+
email.value = draft.email;
|
|
765
|
+
email.addEventListener("input", () => draft.email = email.value);
|
|
766
|
+
form.appendChild(field(doc, "Your email", email, { hint: "So the team can reply to you.", invalid: draft.checked && !EMAIL.test(draft.email.trim()) }));
|
|
767
|
+
}
|
|
768
|
+
const files = el(doc, "div", "g-m-attach");
|
|
769
|
+
if (draft.attachment) {
|
|
770
|
+
const a = draft.attachment;
|
|
771
|
+
const row = el(doc, "div", "g-m-row g-m-center g-m-attached");
|
|
772
|
+
if (a.previewUrl) {
|
|
773
|
+
const img = el(doc, "img", "g-m-thumb");
|
|
774
|
+
img.src = a.previewUrl;
|
|
775
|
+
img.alt = "";
|
|
776
|
+
img.addEventListener("error", () => img.remove());
|
|
777
|
+
row.appendChild(img);
|
|
778
|
+
}
|
|
779
|
+
row.appendChild(el(doc, "span", "g-m-grow g-m-ellipsis", a.fileName));
|
|
780
|
+
const remove = el(doc, "button", "g-m-text-btn", "Remove");
|
|
781
|
+
remove.type = "button";
|
|
782
|
+
remove.setAttribute("aria-label", `Remove ${a.fileName}`);
|
|
783
|
+
remove.addEventListener("click", o.onRemoveFile);
|
|
784
|
+
row.appendChild(remove);
|
|
785
|
+
files.appendChild(row);
|
|
786
|
+
} else {
|
|
787
|
+
const input = el(doc, "input", "g-m-file");
|
|
788
|
+
input.type = "file";
|
|
789
|
+
input.accept = "image/png,image/jpeg,image/webp,image/gif";
|
|
790
|
+
input.tabIndex = -1;
|
|
791
|
+
input.setAttribute("aria-hidden", "true");
|
|
792
|
+
input.addEventListener("change", () => {
|
|
793
|
+
const file = input.files?.[0];
|
|
794
|
+
if (file) o.onPickFile(file);
|
|
795
|
+
});
|
|
796
|
+
const pick = el(doc, "button", "g-m-attach-btn", "Attach a screenshot");
|
|
797
|
+
pick.type = "button";
|
|
798
|
+
pick.addEventListener("click", () => input.click());
|
|
799
|
+
files.append(input, pick);
|
|
800
|
+
}
|
|
801
|
+
form.appendChild(files);
|
|
802
|
+
const foot = el(doc, "div", "g-m-form-foot");
|
|
803
|
+
foot.appendChild(el(doc, "p", "g-m-hint", "We'll include the page you're on so the team can look into it."));
|
|
804
|
+
if (draft.error) {
|
|
805
|
+
const err = el(doc, "p", "g-m-error", draft.error);
|
|
806
|
+
err.setAttribute("role", "alert");
|
|
807
|
+
foot.appendChild(err);
|
|
808
|
+
}
|
|
809
|
+
const submit = el(doc, "button", "g-btn g-btn-primary g-m-wide", draft.sending ? "Sending\u2026" : "Submit complaint");
|
|
810
|
+
submit.type = "submit";
|
|
811
|
+
submit.disabled = draft.sending;
|
|
812
|
+
foot.appendChild(submit);
|
|
813
|
+
form.appendChild(foot);
|
|
814
|
+
form.addEventListener("submit", (e) => {
|
|
815
|
+
e.preventDefault();
|
|
816
|
+
if (!draft.sending) o.onSubmit();
|
|
817
|
+
});
|
|
818
|
+
return form;
|
|
819
|
+
}
|
|
820
|
+
function statusTracker(doc, status) {
|
|
821
|
+
const list = el(doc, "ol", "g-m-steps");
|
|
822
|
+
list.setAttribute("aria-label", "Progress");
|
|
823
|
+
const at = COMPLAINT_STEPS.findIndex(([k]) => k === status);
|
|
824
|
+
COMPLAINT_STEPS.forEach(([, label], i) => {
|
|
825
|
+
const li = el(doc, "li", `g-m-step${i <= at ? " g-m-step-done" : ""}`);
|
|
826
|
+
if (i === at) li.setAttribute("aria-current", "step");
|
|
827
|
+
li.append(el(doc, "span", "g-m-step-dot"), el(doc, "span", "g-m-step-label", label));
|
|
828
|
+
list.appendChild(li);
|
|
829
|
+
});
|
|
830
|
+
return list;
|
|
831
|
+
}
|
|
832
|
+
function complaintSent(doc, o) {
|
|
833
|
+
const wrap = el(doc, "div", "g-m-form g-m-sent");
|
|
834
|
+
const top = el(doc, "div", "g-m-sent-top");
|
|
835
|
+
const tick = el(doc, "span", "g-m-sent-tick");
|
|
836
|
+
tick.setAttribute("aria-hidden", "true");
|
|
837
|
+
tick.textContent = "\u2713";
|
|
838
|
+
const p = el(doc, "p", "g-m-card-sub");
|
|
839
|
+
p.append("Your reference is ", el(doc, "strong", "", o.complaint.reference), ". The team will reply in this conversation, so check back here.");
|
|
840
|
+
top.append(tick, el(doc, "h3", "g-m-form-title", "We've got your complaint"), p);
|
|
841
|
+
wrap.appendChild(top);
|
|
842
|
+
const details = el(doc, "dl", "g-m-card g-m-static g-m-details");
|
|
843
|
+
const row = (k, v) => {
|
|
844
|
+
const r = el(doc, "div", "g-m-detail");
|
|
845
|
+
r.append(el(doc, "dt", "", k), el(doc, "dd", "", v));
|
|
846
|
+
details.appendChild(r);
|
|
847
|
+
};
|
|
848
|
+
row("Category", o.complaint.categoryName);
|
|
849
|
+
o.answers.slice(0, 3).forEach(([k, v]) => row(k, v));
|
|
850
|
+
wrap.appendChild(details);
|
|
851
|
+
wrap.appendChild(statusTracker(doc, o.complaint.status));
|
|
852
|
+
const open = el(doc, "button", "g-btn g-btn-primary g-m-wide", "Add more detail");
|
|
853
|
+
open.type = "button";
|
|
854
|
+
open.addEventListener("click", o.onOpenConversation);
|
|
855
|
+
const home = el(doc, "button", "g-btn g-btn-secondary g-m-wide", "Back to home");
|
|
856
|
+
home.type = "button";
|
|
857
|
+
home.addEventListener("click", o.onHome);
|
|
858
|
+
wrap.append(open, home);
|
|
859
|
+
return wrap;
|
|
860
|
+
}
|
|
861
|
+
var MAX_SIDE = 1600;
|
|
862
|
+
async function readScreenshot(doc, file) {
|
|
863
|
+
if (!/^image\/(png|jpeg|webp|gif)$/.test(file.type)) throw new Error("Attach a PNG, JPEG, WebP or GIF image.");
|
|
864
|
+
if (file.size > 25 * 1024 * 1024) throw new Error("That image is too large.");
|
|
865
|
+
const win = doc.defaultView;
|
|
866
|
+
const asDataUrl = (blob) => new Promise((resolve, reject) => {
|
|
867
|
+
const r = new FileReader();
|
|
868
|
+
r.onload = () => resolve(String(r.result));
|
|
869
|
+
r.onerror = () => reject(new Error("The image couldn't be read."));
|
|
870
|
+
r.readAsDataURL(blob);
|
|
871
|
+
});
|
|
872
|
+
let dataUrl = null;
|
|
873
|
+
let scaled = false;
|
|
874
|
+
try {
|
|
875
|
+
const url = win?.URL.createObjectURL(file);
|
|
876
|
+
if (!url) throw new Error("no URL");
|
|
877
|
+
const img = await new Promise((resolve, reject) => {
|
|
878
|
+
const i = new Image();
|
|
879
|
+
i.onload = () => resolve(i);
|
|
880
|
+
i.onerror = () => reject(new Error("decode"));
|
|
881
|
+
i.src = url;
|
|
882
|
+
}).finally(() => win?.URL.revokeObjectURL(url));
|
|
883
|
+
const scale = Math.min(1, MAX_SIDE / Math.max(img.naturalWidth || 1, img.naturalHeight || 1));
|
|
884
|
+
const canvas = doc.createElement("canvas");
|
|
885
|
+
canvas.width = Math.max(1, Math.round(img.naturalWidth * scale));
|
|
886
|
+
canvas.height = Math.max(1, Math.round(img.naturalHeight * scale));
|
|
887
|
+
const ctx = canvas.getContext("2d");
|
|
888
|
+
if (!ctx) throw new Error("no canvas");
|
|
889
|
+
ctx.fillStyle = "#fff";
|
|
890
|
+
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
|
891
|
+
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
|
|
892
|
+
for (const q of [0.85, 0.7, 0.5]) {
|
|
893
|
+
dataUrl = canvas.toDataURL("image/jpeg", q);
|
|
894
|
+
if ((dataUrl.length - dataUrl.indexOf(",") - 1) * 0.75 <= import_runtime4.MAX_COMPLAINT_ATTACHMENT_BYTES) break;
|
|
895
|
+
}
|
|
896
|
+
scaled = true;
|
|
897
|
+
} catch {
|
|
898
|
+
dataUrl = file.size <= import_runtime4.MAX_COMPLAINT_ATTACHMENT_BYTES ? await asDataUrl(file) : null;
|
|
899
|
+
}
|
|
900
|
+
if (!dataUrl) throw new Error("That image is too large. Screenshots can be up to 2 MB.");
|
|
901
|
+
const data = dataUrl.slice(dataUrl.indexOf(",") + 1);
|
|
902
|
+
if (data.length * 0.75 > import_runtime4.MAX_COMPLAINT_ATTACHMENT_BYTES) throw new Error("That image is too large. Screenshots can be up to 2 MB.");
|
|
903
|
+
const name = (file.name || "screenshot").slice(0, 180);
|
|
904
|
+
return { fileName: scaled ? `${name.replace(/\.[a-z0-9]+$/i, "")}.jpg` : name, data, previewUrl: dataUrl };
|
|
905
|
+
}
|
|
906
|
+
|
|
581
907
|
// src/messenger-styles.ts
|
|
582
908
|
var MESSENGER_STYLES = `
|
|
583
909
|
:host([data-g-messenger="bottom-right"]) .g-launcher.g-pos-bottom-right{right:72px}
|
|
@@ -587,7 +913,15 @@ var MESSENGER_STYLES = `
|
|
|
587
913
|
.g-m-launcher:focus-visible{outline:2px solid var(--g-primary);outline-offset:3px}
|
|
588
914
|
.g-m-launcher svg{width:26px;height:26px;fill:currentColor}
|
|
589
915
|
.g-m-badge{position:absolute;top:-2px;right:-2px;min-width:20px;height:20px;padding:0 5px;border-radius:999px;background:#e11d48;color:#fff;font-size:11px;font-weight:700;display:inline-flex;align-items:center;justify-content:center;border:2px solid var(--g-bg)}
|
|
590
|
-
.g-messenger{bottom:76px;height:min(640px,calc(100vh - 110px))}
|
|
916
|
+
.g-messenger{bottom:76px;height:min(640px,calc(100vh - 110px));transition:width .2s ease,height .2s ease,max-height .2s ease}
|
|
917
|
+
.g-messenger.g-m-expanded{width:min(720px,calc(100vw - 32px));height:calc(100vh - 110px);max-height:calc(100vh - 110px)}
|
|
918
|
+
.g-m-expanded .g-rc-article,.g-m-expanded .g-m-scroll{padding:24px clamp(20px,5vw,40px)}
|
|
919
|
+
.g-m-expanded .g-rc-article>.g-title{font-size:24px;line-height:1.25;margin-bottom:12px}
|
|
920
|
+
.g-m-expanded .g-rc-article .g-body{font-size:15px}
|
|
921
|
+
.g-m-head.g-m-sizable{padding-right:92px}
|
|
922
|
+
.g-m-size{position:absolute;right:48px;top:50%;transform:translateY(-50%);width:40px;height:40px;border:0;border-radius:999px;background:transparent;color:inherit;cursor:pointer;display:inline-flex;align-items:center;justify-content:center;padding:0}
|
|
923
|
+
.g-m-size .g-m-ico{width:18px;height:18px}
|
|
924
|
+
.g-m-size:hover,.g-m-size:focus-visible{background:rgba(255,255,255,.18);outline:none}
|
|
591
925
|
.g-m-head{padding:16px 48px 14px 20px;background:var(--g-primary);color:var(--g-primary-text);position:relative;display:flex;align-items:center;gap:8px;min-height:56px}
|
|
592
926
|
.g-m-head .g-close{color:inherit;top:50%;transform:translateY(-50%)}
|
|
593
927
|
.g-m-head .g-close:hover,.g-m-head .g-close:focus-visible{background:rgba(255,255,255,.18);color:inherit}
|
|
@@ -657,6 +991,7 @@ var MESSENGER_STYLES = `
|
|
|
657
991
|
.g-howto-desc,.g-howto-note{color:var(--g-muted);font-size:13px}
|
|
658
992
|
.g-howto p{margin:0 0 6px;white-space:pre-line}
|
|
659
993
|
.g-howto-show{width:100%;margin:6px 0 4px}
|
|
994
|
+
.g-howto-video{display:block;width:100%;max-height:260px;margin:8px 0 4px;border-radius:8px;background:#000}
|
|
660
995
|
.g-howto-steps{list-style:none;margin:12px 0 0;padding:0;display:flex;flex-direction:column;gap:16px}
|
|
661
996
|
.g-howto-step h4{margin:4px 0 0;font-size:15px}
|
|
662
997
|
.g-howto-tip,.g-howto-alert{border-left:3px solid var(--g-primary);background:var(--g-subtle);border-radius:6px;padding:8px 10px;font-size:13px}
|
|
@@ -726,6 +1061,52 @@ var MESSENGER_STYLES = `
|
|
|
726
1061
|
.g-m-asks{align-items:flex-start}
|
|
727
1062
|
.g-m-chip.g-m-ask{border-color:var(--g-primary);color:var(--g-primary);font-weight:600;border-radius:999px;padding:8px 14px;min-height:36px}
|
|
728
1063
|
.g-m-chip.g-m-ask:disabled{opacity:.5;cursor:not-allowed}
|
|
1064
|
+
.g-m-side{display:flex;flex-direction:column;align-items:flex-end;gap:4px;flex:none}
|
|
1065
|
+
.g-m-pill{display:inline-block;font-size:11px;font-weight:700;line-height:1.5;padding:1px 8px;border-radius:999px;white-space:nowrap;background:#fef3c7;color:#92400e}
|
|
1066
|
+
.g-m-pill-in_review{background:#e0e7ff;color:#3730a3}
|
|
1067
|
+
.g-m-pill-resolved{background:#dcfce7;color:#166534}
|
|
1068
|
+
.g-m-complain .g-m-badge-ico{background:#fef3e2;color:#b45309}
|
|
1069
|
+
.g-m-complaint{align-self:stretch;gap:10px;border:1px solid var(--g-border);background:var(--g-subtle);border-radius:10px;padding:10px 12px}
|
|
1070
|
+
.g-m-chip-ico{width:16px;height:16px}
|
|
1071
|
+
.g-m-offer{gap:6px}
|
|
1072
|
+
.g-m-form{display:flex;flex-direction:column;gap:14px;margin:0}
|
|
1073
|
+
.g-m-form-title{margin:0;font-size:17px;font-weight:600}
|
|
1074
|
+
.g-m-cats{display:flex;flex-direction:column;gap:10px}
|
|
1075
|
+
.g-m-cats .g-m-card{margin:0;gap:12px;min-height:44px}
|
|
1076
|
+
.g-m-cat-dot{width:10px;height:10px;border-radius:999px;background:var(--g-muted);flex:none}
|
|
1077
|
+
.g-m-picked{gap:8px;border:1px solid var(--g-border);background:var(--g-subtle);border-radius:10px;padding:8px 12px}
|
|
1078
|
+
.g-m-text-btn{border:0;background:transparent;color:var(--g-primary);font:inherit;font-size:13px;font-weight:600;cursor:pointer;padding:6px 4px;min-height:32px}
|
|
1079
|
+
.g-m-text-btn:hover{text-decoration:underline}
|
|
1080
|
+
.g-m-text-btn:focus-visible{outline:2px solid var(--g-primary);outline-offset:2px;border-radius:4px}
|
|
1081
|
+
.g-m-field{display:flex;flex-direction:column;gap:4px}
|
|
1082
|
+
.g-m-label{font-weight:600;font-size:13px}
|
|
1083
|
+
.g-m-optional{font-weight:400;color:var(--g-muted)}
|
|
1084
|
+
.g-m-hint{color:var(--g-muted);font-size:12px;margin:0}
|
|
1085
|
+
.g-m-error{color:#b91c1c;font-size:13px;margin:0}
|
|
1086
|
+
.g-scope[data-theme="dark"] .g-m-error{color:#fca5a5}
|
|
1087
|
+
.g-m-field .g-input[aria-invalid="true"]{border-color:#dc2626}
|
|
1088
|
+
.g-m-textarea{min-height:110px}
|
|
1089
|
+
.g-m-file{position:absolute;width:1px;height:1px;opacity:0;pointer-events:none}
|
|
1090
|
+
.g-m-attach-btn{align-self:flex-start;border:1px dashed var(--g-border);background:var(--g-bg);color:var(--g-text);font:inherit;font-size:13px;border-radius:8px;padding:8px 12px;cursor:pointer;min-height:36px}
|
|
1091
|
+
.g-m-attach-btn:hover{background:var(--g-subtle)}
|
|
1092
|
+
.g-m-attach-btn:focus-visible{outline:2px solid var(--g-primary);outline-offset:2px}
|
|
1093
|
+
.g-m-attached{gap:10px;border:1px solid var(--g-border);border-radius:8px;padding:6px 8px;font-size:13px}
|
|
1094
|
+
.g-m-thumb{width:40px;height:40px;object-fit:cover;border-radius:6px;background:var(--g-subtle);flex:none}
|
|
1095
|
+
.g-m-form-foot{display:flex;flex-direction:column;gap:10px;margin-top:4px}
|
|
1096
|
+
.g-m-sent{gap:12px}
|
|
1097
|
+
.g-m-sent-top{display:flex;flex-direction:column;align-items:center;text-align:center;gap:6px;padding-top:8px}
|
|
1098
|
+
.g-m-sent-top p{max-width:300px;margin:0}
|
|
1099
|
+
.g-m-sent-tick{width:52px;height:52px;border-radius:999px;background:#dcfce7;color:#15803d;display:inline-flex;align-items:center;justify-content:center;font-size:26px;font-weight:700}
|
|
1100
|
+
.g-m-details{margin:0;display:flex;flex-direction:column;gap:8px}
|
|
1101
|
+
.g-m-detail{display:flex;justify-content:space-between;gap:12px;font-size:13px}
|
|
1102
|
+
.g-m-detail dt{color:var(--g-muted)}
|
|
1103
|
+
.g-m-detail dd{margin:0;font-weight:600;text-align:right;overflow-wrap:anywhere}
|
|
1104
|
+
.g-m-steps{list-style:none;margin:4px 0;padding:0;display:flex;justify-content:space-between;position:relative}
|
|
1105
|
+
.g-m-steps::before{content:"";position:absolute;top:6px;left:12%;right:12%;height:2px;background:var(--g-border)}
|
|
1106
|
+
.g-m-step{position:relative;display:flex;flex-direction:column;align-items:center;gap:4px;flex:1;font-size:12px;color:var(--g-muted)}
|
|
1107
|
+
.g-m-step-dot{width:14px;height:14px;border-radius:999px;border:2px solid var(--g-border);background:var(--g-bg);box-sizing:border-box}
|
|
1108
|
+
.g-m-step-done{color:var(--g-text);font-weight:600}
|
|
1109
|
+
.g-m-step-done .g-m-step-dot{background:var(--g-primary);border-color:var(--g-primary)}
|
|
729
1110
|
`;
|
|
730
1111
|
|
|
731
1112
|
// src/render-safe.ts
|
|
@@ -831,7 +1212,10 @@ var ICONS = {
|
|
|
831
1212
|
article: ["M6 3h9l4 4v14H6z", "M9 12h6", "M9 16h6"],
|
|
832
1213
|
ask: ["M4 5h16v11H8l-4 4z", "M9 9.5h6", "M9 12.5h4"],
|
|
833
1214
|
search: ["M11 4a7 7 0 1 0 0 14 7 7 0 0 0 0-14z", "m20 20-3.5-3.5"],
|
|
834
|
-
circle: ["M12 3a9 9 0 1 0 0 18 9 9 0 0 0 0-18z"]
|
|
1215
|
+
circle: ["M12 3a9 9 0 1 0 0 18 9 9 0 0 0 0-18z"],
|
|
1216
|
+
expand: ["M15 3h6v6", "M21 3l-7 7", "M9 21H3v-6", "M3 21l7-7"],
|
|
1217
|
+
flag: ["M5 21V4", "M5 4h12l-2.5 4L17 12H5"],
|
|
1218
|
+
collapse: ["M4 14h6v6", "M3 21l7-7", "M20 10h-6V4", "M21 3l-7 7"]
|
|
835
1219
|
};
|
|
836
1220
|
function icon(doc, name, cls = "g-m-ico") {
|
|
837
1221
|
const svg = doc.createElementNS(SVG_NS, "svg");
|
|
@@ -910,6 +1294,7 @@ function renderMessenger(options) {
|
|
|
910
1294
|
let current = null;
|
|
911
1295
|
const streaming = /* @__PURE__ */ new Map();
|
|
912
1296
|
let sending = false;
|
|
1297
|
+
let complaintDraft = emptyComplaintDraft();
|
|
913
1298
|
let draft = "";
|
|
914
1299
|
let articles = null;
|
|
915
1300
|
let articlesLoading = false;
|
|
@@ -984,8 +1369,38 @@ function renderMessenger(options) {
|
|
|
984
1369
|
options.onOpenChecklist?.(id);
|
|
985
1370
|
},
|
|
986
1371
|
onArticleView: options.onArticleView,
|
|
987
|
-
extra: (q) => howToItems(q)
|
|
1372
|
+
extra: (q) => howToItems(q),
|
|
1373
|
+
guides: () => options.guides ?? [],
|
|
1374
|
+
onShowMe: (key) => startWalkthrough(key),
|
|
1375
|
+
onArticleChange: () => syncSize()
|
|
988
1376
|
});
|
|
1377
|
+
let docCollapsed = false;
|
|
1378
|
+
let sizeButton = null;
|
|
1379
|
+
const readingDoc = () => open && (view.kind === "howto" || view.kind === "tab" && view.tab === "help" && help.isArticleOpen());
|
|
1380
|
+
function syncSize() {
|
|
1381
|
+
const reading = readingDoc();
|
|
1382
|
+
if (!reading) docCollapsed = false;
|
|
1383
|
+
panel.classList.toggle("g-m-expanded", reading && !docCollapsed);
|
|
1384
|
+
head.classList.toggle("g-m-sizable", reading);
|
|
1385
|
+
if (!reading) {
|
|
1386
|
+
sizeButton?.remove();
|
|
1387
|
+
sizeButton = null;
|
|
1388
|
+
return;
|
|
1389
|
+
}
|
|
1390
|
+
if (!sizeButton) {
|
|
1391
|
+
sizeButton = el(doc, "button", "g-m-size");
|
|
1392
|
+
sizeButton.type = "button";
|
|
1393
|
+
sizeButton.addEventListener("click", () => {
|
|
1394
|
+
docCollapsed = !docCollapsed;
|
|
1395
|
+
syncSize();
|
|
1396
|
+
});
|
|
1397
|
+
head.appendChild(sizeButton);
|
|
1398
|
+
}
|
|
1399
|
+
sizeButton.textContent = "";
|
|
1400
|
+
sizeButton.appendChild(icon(doc, docCollapsed ? "expand" : "collapse"));
|
|
1401
|
+
sizeButton.setAttribute("aria-label", docCollapsed ? "Expand" : "Collapse");
|
|
1402
|
+
sizeButton.title = docCollapsed ? "Expand" : "Collapse";
|
|
1403
|
+
}
|
|
989
1404
|
const unreadTotal = () => conversations.reduce((n, c) => n + (c.unread > 0 ? 1 : 0), 0);
|
|
990
1405
|
const updateBadge = () => {
|
|
991
1406
|
const n = unreadTotal();
|
|
@@ -993,7 +1408,13 @@ function renderMessenger(options) {
|
|
|
993
1408
|
badge.textContent = n > 9 ? "9+" : String(n);
|
|
994
1409
|
launcher.setAttribute("aria-label", n ? `Open ${config.name}, ${n} unread` : `Open ${config.name}`);
|
|
995
1410
|
};
|
|
996
|
-
const
|
|
1411
|
+
const withComplaint = (c) => {
|
|
1412
|
+
if (c.complaint) return c;
|
|
1413
|
+
const known = conversations.find((x) => x.id === c.id)?.complaint ?? (current?.id === c.id ? current.complaint : void 0);
|
|
1414
|
+
return known ? { ...c, complaint: known } : c;
|
|
1415
|
+
};
|
|
1416
|
+
const upsertConversation = (incoming) => {
|
|
1417
|
+
const c = withComplaint(incoming);
|
|
997
1418
|
conversations = [c, ...conversations.filter((x) => x.id !== c.id)].sort((a, b) => b.lastMessageAt.localeCompare(a.lastMessageAt));
|
|
998
1419
|
if (current?.id === c.id) current = c;
|
|
999
1420
|
updateBadge();
|
|
@@ -1028,15 +1449,19 @@ function renderMessenger(options) {
|
|
|
1028
1449
|
const pageHowTos = () => (howTos?.forPage ?? []).map((id) => howTos?.howTos.find((h) => h.id === id)).filter((h) => !!h);
|
|
1029
1450
|
const stepsLabel = (h) => `${h.stepCount} ${h.stepCount === 1 ? "step" : "steps"}`;
|
|
1030
1451
|
const openHowTo = (id) => go({ kind: "howto", id, from: view });
|
|
1452
|
+
const startWalkthrough = (guideKey) => {
|
|
1453
|
+
setOpen(false);
|
|
1454
|
+
options.onStartGuide?.(guideKey);
|
|
1455
|
+
};
|
|
1031
1456
|
function howToItems(q) {
|
|
1032
|
-
const list = q ? (howTos?.howTos ?? []).filter((h) => h.title
|
|
1457
|
+
const list = q ? (howTos?.howTos ?? []).filter((h) => (0, import_runtime5.matchesHelpQuery)(`${h.title} ${h.description}`, q)).slice(0, 10) : [];
|
|
1033
1458
|
if (!list.length) return [];
|
|
1034
1459
|
return [
|
|
1035
1460
|
el(doc, "div", "g-rc-section", `How-tos (${list.length})`),
|
|
1036
1461
|
...list.map((h) => {
|
|
1037
|
-
const
|
|
1038
|
-
|
|
1039
|
-
return
|
|
1462
|
+
const key = h.guideKey && startableGuide(options.guides, h.guideKey) ? h.guideKey : null;
|
|
1463
|
+
const sub = [h.hasVideo ? "Video" : "", stepsLabel(h)].filter(Boolean).join(" \xB7 ");
|
|
1464
|
+
return resultRow(doc, "\u{1F4CB}", h.title, h.description ? `${sub} \xB7 ${h.description}` : sub, () => openHowTo(h.id), key ? () => startWalkthrough(key) : void 0);
|
|
1040
1465
|
})
|
|
1041
1466
|
];
|
|
1042
1467
|
}
|
|
@@ -1048,6 +1473,7 @@ function renderMessenger(options) {
|
|
|
1048
1473
|
});
|
|
1049
1474
|
function renderHead(title, back) {
|
|
1050
1475
|
head.textContent = "";
|
|
1476
|
+
sizeButton = null;
|
|
1051
1477
|
head.hidden = false;
|
|
1052
1478
|
if (back) {
|
|
1053
1479
|
const b = el(doc, "button", "g-m-back");
|
|
@@ -1087,9 +1513,9 @@ function renderMessenger(options) {
|
|
|
1087
1513
|
i.appendChild(icon(doc, ico));
|
|
1088
1514
|
b.append(i, el(doc, "span", "", label));
|
|
1089
1515
|
if (tab === "news" && latest.some((n) => n.unread)) {
|
|
1090
|
-
const
|
|
1091
|
-
|
|
1092
|
-
i.appendChild(
|
|
1516
|
+
const dot2 = el(doc, "span", "g-m-tab-dot");
|
|
1517
|
+
dot2.setAttribute("aria-label", "New");
|
|
1518
|
+
i.appendChild(dot2);
|
|
1093
1519
|
}
|
|
1094
1520
|
b.addEventListener("click", () => go({ kind: "tab", tab }));
|
|
1095
1521
|
tabs.appendChild(b);
|
|
@@ -1103,7 +1529,10 @@ function renderMessenger(options) {
|
|
|
1103
1529
|
const text = el(doc, "div", "g-m-grow");
|
|
1104
1530
|
if (label) text.appendChild(el(doc, "div", "g-m-card-label", label));
|
|
1105
1531
|
text.append(el(doc, "div", "g-m-card-title g-m-ellipsis", c.subject || "Conversation"), el(doc, "div", "g-m-card-sub g-m-ellipsis", c.lastMessagePreview));
|
|
1106
|
-
|
|
1532
|
+
const side = el(doc, "span", "g-m-side");
|
|
1533
|
+
side.appendChild(el(doc, "span", "g-m-time", relativeTime(c.lastMessageAt)));
|
|
1534
|
+
if (c.complaint) side.appendChild(statusPill(doc, c.complaint.status));
|
|
1535
|
+
row.append(text, side);
|
|
1107
1536
|
card.appendChild(row);
|
|
1108
1537
|
card.setAttribute("aria-label", `${label ? `${label}: ` : ""}${c.subject || "Conversation"}${c.unread ? ", unread" : ""}`);
|
|
1109
1538
|
card.addEventListener("click", () => go({ kind: "thread", conversationId: c.id }));
|
|
@@ -1314,13 +1743,16 @@ function renderMessenger(options) {
|
|
|
1314
1743
|
hello.append(el(doc, "span", "g-m-hello-1", v.firstName ? `Hi ${v.firstName}.` : "Hi there."), el(doc, "span", "g-m-hello-2", "How can we help?"));
|
|
1315
1744
|
inner.append(top, hello);
|
|
1316
1745
|
inner.appendChild(actionCard({ title: "Send us a message", sub: messageSub(), trail: true, onClick: () => go({ kind: "thread", conversationId: null }) }));
|
|
1317
|
-
|
|
1746
|
+
if (canComplain()) inner.appendChild(actionCard({ title: "File a complaint", sub: "Tell us what went wrong. You'll get a reference.", lead: iconTile("flag"), onClick: () => startComplaint(null), cls: "g-m-complain" }));
|
|
1747
|
+
const filed = latestComplaint();
|
|
1748
|
+
if (filed) inner.appendChild(conversationCard(filed, "Your complaint"));
|
|
1749
|
+
const recent = conversations.find((c) => c.id !== filed?.id);
|
|
1318
1750
|
if (recent) inner.appendChild(conversationCard(recent, recent.status === "closed" && !recent.unread ? "Recent conversation" : "Continue the conversation"));
|
|
1319
1751
|
if (config.status) inner.appendChild(statusCard(config.status));
|
|
1320
1752
|
if (cards.progress) progressCards(v).forEach((c) => inner.appendChild(c));
|
|
1321
1753
|
const suggestions = pageSuggestions(v);
|
|
1322
1754
|
const questions = cards.suggestions ? (page?.questions ?? []).slice(0, 2) : [];
|
|
1323
|
-
const label = v.path ? (0,
|
|
1755
|
+
const label = v.path ? (0, import_runtime5.pageLabel)(v.path) : null;
|
|
1324
1756
|
const fallback = !suggestions.length && !questions.length ? rcExtras(options.resourceCenter?.recommendedGuideIds).slice(0, 3) : [];
|
|
1325
1757
|
if (suggestions.length || questions.length || fallback.length) {
|
|
1326
1758
|
const box = el(doc, "div", "g-m-card g-m-static g-m-suggest");
|
|
@@ -1339,6 +1771,102 @@ function renderMessenger(options) {
|
|
|
1339
1771
|
scroll.append(hero, inner);
|
|
1340
1772
|
main.appendChild(scroll);
|
|
1341
1773
|
}
|
|
1774
|
+
const complaintCategories = () => config.complaints?.categories ?? [];
|
|
1775
|
+
const needsEmail = () => visitor().identified === false;
|
|
1776
|
+
const canComplain = () => !!api.fileComplaint && complaintCategories().length > 0 && (!needsEmail() || !!config.complaints?.anonymous);
|
|
1777
|
+
function latestComplaint() {
|
|
1778
|
+
let best = null;
|
|
1779
|
+
for (const c of conversations) if (c.complaint && (!best || c.complaint.createdAt > best.complaint.createdAt)) best = c;
|
|
1780
|
+
return best;
|
|
1781
|
+
}
|
|
1782
|
+
function userMessagesText() {
|
|
1783
|
+
return messages.filter((m) => m.author === "user" && m.body.trim()).slice(-3).map((m) => m.body.trim()).join("\n\n").slice(0, 4e3);
|
|
1784
|
+
}
|
|
1785
|
+
function startComplaint(categoryId) {
|
|
1786
|
+
const cats = complaintCategories();
|
|
1787
|
+
const picked = categoryId ? cats.find((c) => c.id === categoryId) : cats.length === 1 ? cats[0] : void 0;
|
|
1788
|
+
const from = view.kind === "tab" || view.kind === "thread" ? view : { kind: "tab", tab: "home" };
|
|
1789
|
+
complaintDraft = emptyComplaintDraft(complaintDraft.description);
|
|
1790
|
+
if (picked) go({ kind: "complaint-form", categoryId: picked.id, conversationId: null, from });
|
|
1791
|
+
else go({ kind: "complaint-category", conversationId: null, from });
|
|
1792
|
+
}
|
|
1793
|
+
function complaintBanner(c) {
|
|
1794
|
+
const b = el(doc, "div", "g-m-complaint g-m-row g-m-center");
|
|
1795
|
+
b.setAttribute("role", "status");
|
|
1796
|
+
b.appendChild(icon(doc, "flag", "g-m-ico g-m-lead"));
|
|
1797
|
+
const text = el(doc, "span", "g-m-grow");
|
|
1798
|
+
text.append(el(doc, "span", "g-m-card-title", c.reference), el(doc, "span", "g-m-card-sub", c.categoryName));
|
|
1799
|
+
b.append(text, statusPill(doc, c.status));
|
|
1800
|
+
return b;
|
|
1801
|
+
}
|
|
1802
|
+
function renderComplaintCategories(v) {
|
|
1803
|
+
renderHead("File a complaint", () => go(v.from));
|
|
1804
|
+
const scroll = el(doc, "div", "g-m-scroll");
|
|
1805
|
+
scroll.appendChild(categoryList(doc, complaintCategories(), (c) => go({ kind: "complaint-form", categoryId: c.id, conversationId: v.conversationId, from: v.from })));
|
|
1806
|
+
main.appendChild(scroll);
|
|
1807
|
+
}
|
|
1808
|
+
function renderComplaintForm(v) {
|
|
1809
|
+
const category = complaintCategories().find((c) => c.id === v.categoryId);
|
|
1810
|
+
if (!category) return go({ kind: "complaint-category", conversationId: v.conversationId, from: v.from });
|
|
1811
|
+
const back = () => complaintCategories().length > 1 && !v.conversationId ? go({ kind: "complaint-category", conversationId: v.conversationId, from: v.from }) : go(v.from);
|
|
1812
|
+
renderHead("File a complaint", back);
|
|
1813
|
+
const scroll = el(doc, "div", "g-m-scroll");
|
|
1814
|
+
const draft2 = complaintDraft;
|
|
1815
|
+
scroll.appendChild(
|
|
1816
|
+
complaintForm(doc, {
|
|
1817
|
+
category,
|
|
1818
|
+
draft: draft2,
|
|
1819
|
+
needsEmail: needsEmail(),
|
|
1820
|
+
canChangeCategory: complaintCategories().length > 1,
|
|
1821
|
+
onChangeCategory: () => go({ kind: "complaint-category", conversationId: v.conversationId, from: v.from }),
|
|
1822
|
+
onPickFile: (file) => {
|
|
1823
|
+
draft2.error = null;
|
|
1824
|
+
readScreenshot(doc, file).then((a) => draft2.attachment = a).catch((e) => draft2.error = e instanceof Error ? e.message : "That image couldn't be attached.").finally(() => {
|
|
1825
|
+
if (view === v) render();
|
|
1826
|
+
});
|
|
1827
|
+
},
|
|
1828
|
+
onRemoveFile: () => {
|
|
1829
|
+
draft2.attachment = null;
|
|
1830
|
+
render();
|
|
1831
|
+
},
|
|
1832
|
+
onSubmit: () => submitComplaint(v, category)
|
|
1833
|
+
})
|
|
1834
|
+
);
|
|
1835
|
+
main.appendChild(scroll);
|
|
1836
|
+
}
|
|
1837
|
+
function submitComplaint(v, category) {
|
|
1838
|
+
const draft2 = complaintDraft;
|
|
1839
|
+
const problem = complaintProblem(category, draft2, needsEmail());
|
|
1840
|
+
draft2.checked = true;
|
|
1841
|
+
draft2.error = problem;
|
|
1842
|
+
if (problem || !api.fileComplaint) return render();
|
|
1843
|
+
draft2.sending = true;
|
|
1844
|
+
render();
|
|
1845
|
+
const answers = complaintAnswers(category, draft2);
|
|
1846
|
+
api.fileComplaint({
|
|
1847
|
+
categoryId: category.id,
|
|
1848
|
+
description: draft2.description.trim(),
|
|
1849
|
+
answers,
|
|
1850
|
+
email: needsEmail() ? draft2.email.trim() : null,
|
|
1851
|
+
conversationId: v.conversationId,
|
|
1852
|
+
attachment: draft2.attachment ? { fileName: draft2.attachment.fileName, data: draft2.attachment.data } : null
|
|
1853
|
+
}).then((res) => {
|
|
1854
|
+
upsertConversation({ ...res.conversation, unread: 0 });
|
|
1855
|
+
complaintDraft = emptyComplaintDraft();
|
|
1856
|
+
const labels = category.fields.filter((f) => answers[f.id]).map((f) => [f.label, answers[f.id]]);
|
|
1857
|
+
if (view === v) go({ kind: "complaint-sent", complaint: res.complaint, conversationId: res.conversation.id, answers: labels });
|
|
1858
|
+
}).catch((e) => {
|
|
1859
|
+
draft2.sending = false;
|
|
1860
|
+
draft2.error = (e instanceof Error && e.message ? e.message : "Your complaint couldn't be sent.").replace(/^[\w.]+: /, "");
|
|
1861
|
+
if (view === v) render();
|
|
1862
|
+
});
|
|
1863
|
+
}
|
|
1864
|
+
function renderComplaintSent(v) {
|
|
1865
|
+
renderHead(v.complaint.reference, () => go({ kind: "tab", tab: "home" }));
|
|
1866
|
+
const scroll = el(doc, "div", "g-m-scroll");
|
|
1867
|
+
scroll.appendChild(complaintSent(doc, { complaint: v.complaint, answers: v.answers, onOpenConversation: () => go({ kind: "thread", conversationId: v.conversationId }), onHome: () => go({ kind: "tab", tab: "home" }) }));
|
|
1868
|
+
main.appendChild(scroll);
|
|
1869
|
+
}
|
|
1342
1870
|
function renderHowToView(id, from) {
|
|
1343
1871
|
const cached = howToCache.get(id);
|
|
1344
1872
|
renderHead(cached?.title ?? "How-to", () => go(from));
|
|
@@ -1374,7 +1902,7 @@ function renderMessenger(options) {
|
|
|
1374
1902
|
const v = visitor();
|
|
1375
1903
|
const items = pageSuggestions(v, 5).map((s) => ({ kind: s.icon === "play" ? "guide" : s.icon === "steps" ? "howto" : "article", title: s.title, summary: s.sub, run: s.run }));
|
|
1376
1904
|
if (!items.length) return void 0;
|
|
1377
|
-
const label = v.path ? (0,
|
|
1905
|
+
const label = v.path ? (0, import_runtime5.pageLabel)(v.path) : null;
|
|
1378
1906
|
return { title: label ? `For the ${label} page` : "Suggested for you", items };
|
|
1379
1907
|
}
|
|
1380
1908
|
function renderHelpTab() {
|
|
@@ -1401,7 +1929,7 @@ function renderMessenger(options) {
|
|
|
1401
1929
|
pageKey = null;
|
|
1402
1930
|
return;
|
|
1403
1931
|
}
|
|
1404
|
-
const key = (0,
|
|
1932
|
+
const key = (0, import_runtime5.foldPathIds)(path).toLowerCase();
|
|
1405
1933
|
if (!force && key === pageKey && (pageLoading || Date.now() - pageAt < PAGE_TTL_MS)) return;
|
|
1406
1934
|
if (key !== pageKey) page = null;
|
|
1407
1935
|
pageKey = key;
|
|
@@ -1451,8 +1979,20 @@ function renderMessenger(options) {
|
|
|
1451
1979
|
if (m.status === "pending") bubble.setAttribute("aria-busy", "true");
|
|
1452
1980
|
bubble.dataset.messageId = m.id;
|
|
1453
1981
|
wrap.appendChild(bubble);
|
|
1454
|
-
|
|
1982
|
+
const offer = m.complaintOffer && canComplain() ? complaintCategories().find((c) => c.id === m.complaintOffer.categoryId) : void 0;
|
|
1983
|
+
if (m.citations.length || m.suggestedGuides.length || m.howTos?.length || offer) {
|
|
1455
1984
|
const chips = el(doc, "div", "g-m-chips");
|
|
1985
|
+
if (offer) {
|
|
1986
|
+
const b = el(doc, "button", "g-m-chip g-m-guide g-m-offer");
|
|
1987
|
+
b.type = "button";
|
|
1988
|
+
b.append(icon(doc, "flag", "g-m-ico g-m-chip-ico"), el(doc, "span", "", `File a complaint \xB7 ${offer.name}`));
|
|
1989
|
+
b.addEventListener("click", () => {
|
|
1990
|
+
const conversationId = current?.id ?? null;
|
|
1991
|
+
complaintDraft = emptyComplaintDraft(userMessagesText());
|
|
1992
|
+
go({ kind: "complaint-form", categoryId: offer.id, conversationId, from: view });
|
|
1993
|
+
});
|
|
1994
|
+
chips.appendChild(b);
|
|
1995
|
+
}
|
|
1456
1996
|
for (const g of m.suggestedGuides) {
|
|
1457
1997
|
const b = el(doc, "button", "g-m-chip g-m-guide", `\u25B6 Show me: ${g.name}`);
|
|
1458
1998
|
b.type = "button";
|
|
@@ -1482,9 +2022,10 @@ function renderMessenger(options) {
|
|
|
1482
2022
|
if (!log) return;
|
|
1483
2023
|
const atBottom = log.scrollHeight - log.scrollTop - log.clientHeight < 40;
|
|
1484
2024
|
log.textContent = "";
|
|
2025
|
+
if (current?.complaint) log.appendChild(complaintBanner(current.complaint));
|
|
1485
2026
|
if (!messages.length) {
|
|
1486
2027
|
const v = visitor();
|
|
1487
|
-
const label = v.path ? (0,
|
|
2028
|
+
const label = v.path ? (0, import_runtime5.pageLabel)(v.path) : null;
|
|
1488
2029
|
if (label && config.aiAvailable && !current) log.appendChild(el(doc, "div", "g-m-context", `Asking from the ${label} page`));
|
|
1489
2030
|
const intro = { id: "intro", author: config.aiAvailable ? "scout" : "agent", authorName: "Support team", body: personalGreeting(config.greeting, v.firstName), citations: [], suggestedGuides: [], status: "complete", createdAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
1490
2031
|
log.appendChild(messageNode(intro));
|
|
@@ -1601,6 +2142,9 @@ function renderMessenger(options) {
|
|
|
1601
2142
|
sendButton = null;
|
|
1602
2143
|
if (view.kind === "thread") renderThread(view.conversationId);
|
|
1603
2144
|
else if (view.kind === "howto") renderHowToView(view.id, view.from);
|
|
2145
|
+
else if (view.kind === "complaint-category") renderComplaintCategories(view);
|
|
2146
|
+
else if (view.kind === "complaint-form") renderComplaintForm(view);
|
|
2147
|
+
else if (view.kind === "complaint-sent") renderComplaintSent(view);
|
|
1604
2148
|
else if (view.tab === "home") renderHome();
|
|
1605
2149
|
else if (view.tab === "messages") renderMessagesTab();
|
|
1606
2150
|
else if (view.tab === "news" && news().length) renderNewsTab();
|
|
@@ -1609,6 +2153,7 @@ function renderMessenger(options) {
|
|
|
1609
2153
|
renderHome();
|
|
1610
2154
|
} else renderHelpTab();
|
|
1611
2155
|
renderTabs();
|
|
2156
|
+
syncSize();
|
|
1612
2157
|
}
|
|
1613
2158
|
const handlers = (seq) => ({
|
|
1614
2159
|
onConversation(c) {
|
|
@@ -1637,8 +2182,8 @@ function renderMessenger(options) {
|
|
|
1637
2182
|
upsertMessage(m);
|
|
1638
2183
|
options.onMessage?.(m, c);
|
|
1639
2184
|
}
|
|
1640
|
-
|
|
1641
|
-
current
|
|
2185
|
+
current = withComplaint({ ...c, unread: 0 });
|
|
2186
|
+
upsertConversation(current);
|
|
1642
2187
|
},
|
|
1643
2188
|
onError() {
|
|
1644
2189
|
}
|
|
@@ -1691,8 +2236,8 @@ function renderMessenger(options) {
|
|
|
1691
2236
|
return !prev || prev.status !== m.status || prev.body !== m.body;
|
|
1692
2237
|
});
|
|
1693
2238
|
res.messages.forEach(upsertMessage);
|
|
1694
|
-
|
|
1695
|
-
current
|
|
2239
|
+
current = withComplaint({ ...res.conversation, unread: 0 });
|
|
2240
|
+
upsertConversation(current);
|
|
1696
2241
|
if (changed || messages.length !== before) {
|
|
1697
2242
|
renderLog();
|
|
1698
2243
|
res.messages.filter((m) => m.author !== "user").forEach((m) => options.onMessage?.(m, res.conversation));
|
|
@@ -1718,7 +2263,7 @@ function renderMessenger(options) {
|
|
|
1718
2263
|
api.loadMessages(next.conversationId).then((res) => {
|
|
1719
2264
|
if (seq !== threadSeq) return;
|
|
1720
2265
|
res.messages.forEach(upsertMessage);
|
|
1721
|
-
current = { ...res.conversation, unread: 0 };
|
|
2266
|
+
current = withComplaint({ ...res.conversation, unread: 0 });
|
|
1722
2267
|
upsertConversation(current);
|
|
1723
2268
|
renderHead(current.subject || "Conversation", () => go({ kind: "tab", tab: "messages" }));
|
|
1724
2269
|
renderLog();
|
|
@@ -1756,6 +2301,7 @@ function renderMessenger(options) {
|
|
|
1756
2301
|
} else {
|
|
1757
2302
|
if (threadTimer) clearTimeout(threadTimer);
|
|
1758
2303
|
threadTimer = null;
|
|
2304
|
+
syncSize();
|
|
1759
2305
|
options.onClose?.();
|
|
1760
2306
|
}
|
|
1761
2307
|
};
|
|
@@ -1793,6 +2339,11 @@ function renderMessenger(options) {
|
|
|
1793
2339
|
setOpen(true);
|
|
1794
2340
|
openArticle(id);
|
|
1795
2341
|
},
|
|
2342
|
+
openComplaint(categoryId) {
|
|
2343
|
+
if (!canComplain()) return;
|
|
2344
|
+
setOpen(true);
|
|
2345
|
+
startComplaint(categoryId ?? null);
|
|
2346
|
+
},
|
|
1796
2347
|
refresh() {
|
|
1797
2348
|
if (destroyed) return;
|
|
1798
2349
|
if (!open) {
|