@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.
@@ -0,0 +1,515 @@
1
+ import {
2
+ prefersReducedMotion
3
+ } from "./chunk-PG7CMBHD.js";
4
+
5
+ // src/sanitize.ts
6
+ import { EMBED_IFRAME_ATTRS, VIDEO_ATTRS, isAllowedEmbedSrc, isSafeMediaUrl } from "@guidelytech/shared/runtime";
7
+ var ALLOWED_TAGS = /* @__PURE__ */ new Set(["p", "br", "strong", "b", "em", "i", "u", "a", "ul", "ol", "li", "img", "h1", "h2", "h3", "h4", "code", "pre", "blockquote", "span", "div", "hr", "button"]);
8
+ var DROP_TAGS = /* @__PURE__ */ new Set(["script", "style", "object", "embed", "form", "input", "textarea", "select", "link", "meta", "base", "template", "svg", "math", "frame", "frameset", "noscript"]);
9
+ var ALLOWED_ATTRS = /* @__PURE__ */ new Set(["href", "src", "alt", "title", "target", "rel", "class", "width", "height", "data-g-action", "data-g-value"]);
10
+ function isSafeUrl(value, attr, tag) {
11
+ const v = value.trim().replace(/[\u0000-\u001f\s]/g, "").toLowerCase();
12
+ if (!v) return false;
13
+ const m = /^([a-z][a-z0-9+.-]*):/.exec(v);
14
+ if (!m) return true;
15
+ const scheme = m[1];
16
+ if (scheme === "data") return tag === "img" && attr === "src" && v.startsWith("data:image/");
17
+ return scheme === "http" || scheme === "https" || scheme === "mailto" || scheme === "tel";
18
+ }
19
+ function sanitizeElement(el2) {
20
+ const tag = el2.tagName.toLowerCase();
21
+ for (const attr of Array.from(el2.attributes)) {
22
+ const name = attr.name.toLowerCase();
23
+ if (name.startsWith("on") || !ALLOWED_ATTRS.has(name)) {
24
+ el2.removeAttribute(attr.name);
25
+ continue;
26
+ }
27
+ if ((name === "href" || name === "src") && !isSafeUrl(attr.value, name, tag)) el2.removeAttribute(attr.name);
28
+ }
29
+ if (tag === "a") {
30
+ const target = el2.getAttribute("target");
31
+ if (target && target !== "_blank" && target !== "_self") el2.removeAttribute("target");
32
+ if (el2.getAttribute("target") === "_blank") el2.setAttribute("rel", "noopener noreferrer");
33
+ }
34
+ if (tag === "button") el2.setAttribute("type", "button");
35
+ if (tag === "img") {
36
+ for (const dim of ["width", "height"]) if (el2.hasAttribute(dim) && !/^\d{1,5}$/.test(el2.getAttribute(dim))) el2.removeAttribute(dim);
37
+ el2.setAttribute("loading", "lazy");
38
+ if (!el2.getAttribute("alt")) el2.setAttribute("alt", "");
39
+ }
40
+ }
41
+ function rebuildMedia(el2) {
42
+ const doc = el2.ownerDocument;
43
+ const tag = el2.tagName.toLowerCase();
44
+ const src = el2.getAttribute("src");
45
+ let out;
46
+ if (tag === "iframe") {
47
+ if (!isAllowedEmbedSrc(src)) return null;
48
+ out = doc.createElement("iframe");
49
+ out.setAttribute("src", src);
50
+ out.setAttribute("title", el2.getAttribute("title") || "Embedded video");
51
+ for (const [k, v] of Object.entries(EMBED_IFRAME_ATTRS)) out.setAttribute(k, v);
52
+ } else {
53
+ if (!isSafeMediaUrl(src)) return null;
54
+ out = doc.createElement("video");
55
+ out.setAttribute("src", src);
56
+ const poster = el2.getAttribute("poster");
57
+ if (isSafeMediaUrl(poster)) out.setAttribute("poster", poster);
58
+ for (const [k, v] of Object.entries(VIDEO_ATTRS)) out.setAttribute(k, v);
59
+ }
60
+ return out;
61
+ }
62
+ function walk(node) {
63
+ const children = Array.from(node.childNodes);
64
+ for (const child of children) {
65
+ if (child.nodeType === 8) {
66
+ node.removeChild(child);
67
+ continue;
68
+ }
69
+ if (child.nodeType !== 1) continue;
70
+ const el2 = child;
71
+ const tag = el2.tagName.toLowerCase();
72
+ if (DROP_TAGS.has(tag)) {
73
+ node.removeChild(el2);
74
+ continue;
75
+ }
76
+ if (tag === "iframe" || tag === "video") {
77
+ const media = rebuildMedia(el2);
78
+ if (media) node.replaceChild(media, el2);
79
+ else node.removeChild(el2);
80
+ continue;
81
+ }
82
+ if (!ALLOWED_TAGS.has(tag)) {
83
+ while (el2.firstChild) node.insertBefore(el2.firstChild, el2);
84
+ node.removeChild(el2);
85
+ walk(node);
86
+ return;
87
+ }
88
+ sanitizeElement(el2);
89
+ walk(el2);
90
+ }
91
+ }
92
+ function sanitizeToFragment(html, doc = document) {
93
+ const tpl = doc.createElement("template");
94
+ tpl.innerHTML = html || "";
95
+ walk(tpl.content);
96
+ return tpl.content;
97
+ }
98
+ function sanitizeHtml(html, doc = document) {
99
+ const frag = sanitizeToFragment(html, doc);
100
+ const div = doc.createElement("div");
101
+ div.appendChild(frag);
102
+ return div.innerHTML;
103
+ }
104
+ function setSanitizedHtml(el2, html) {
105
+ el2.textContent = "";
106
+ el2.appendChild(sanitizeToFragment(html, el2.ownerDocument));
107
+ }
108
+
109
+ // src/pdf-viewer.ts
110
+ import { FILE_LINK_CLASS, PDF_BLOCK_CLASS, PDF_PAGES_CLASS } from "@guidelytech/shared/runtime";
111
+ function enhancePdfViewers(root, doc = root.ownerDocument) {
112
+ for (const block of Array.from(root.querySelectorAll(`div.${PDF_BLOCK_CLASS}`))) {
113
+ const link = block.querySelector(`a.${FILE_LINK_CLASS}`);
114
+ const strip = block.querySelector(`.${PDF_PAGES_CLASS}`);
115
+ if (!link || !strip || block.hasAttribute("data-g-ready")) continue;
116
+ for (const img of Array.from(strip.querySelectorAll("img:not([src])"))) img.remove();
117
+ const pages = Array.from(strip.querySelectorAll("img"));
118
+ if (!pages.length) {
119
+ strip.remove();
120
+ continue;
121
+ }
122
+ block.setAttribute("data-g-ready", "");
123
+ const name = (link.textContent || "").trim() || "PDF";
124
+ block.setAttribute("role", "group");
125
+ block.setAttribute("aria-label", `${name}, ${pages.length} ${pages.length === 1 ? "page" : "pages"}`);
126
+ link.setAttribute("aria-label", `Read ${name} full screen`);
127
+ link.addEventListener("click", (e) => {
128
+ e.preventDefault();
129
+ openReader(doc, block, link, pages, 0);
130
+ });
131
+ const viewport = doc.createElement("div");
132
+ viewport.className = "g-pdf-viewport";
133
+ strip.replaceWith(viewport);
134
+ viewport.appendChild(strip);
135
+ strip.tabIndex = 0;
136
+ strip.setAttribute("aria-label", `${name} pages`);
137
+ const counter = doc.createElement("span");
138
+ counter.className = "g-pdf-count";
139
+ counter.setAttribute("aria-live", "polite");
140
+ const prev = navButton(doc, "prev", "Previous page", "\u2039");
141
+ const next = navButton(doc, "next", "Next page", "\u203A");
142
+ viewport.append(prev, next, counter);
143
+ const stride = () => Math.max(1, pages.length > 1 ? pages[1].offsetLeft - pages[0].offsetLeft : strip.clientWidth);
144
+ const current = () => Math.round(strip.scrollLeft / stride());
145
+ const go = (i) => strip.scrollTo({ left: Math.max(0, Math.min(pages.length - 1, i)) * stride(), behavior: prefersReducedMotion() ? "auto" : "smooth" });
146
+ const update = () => {
147
+ const i = Math.min(pages.length - 1, current());
148
+ counter.textContent = `${i + 1} / ${pages.length}`;
149
+ prev.disabled = i === 0;
150
+ next.disabled = i >= pages.length - 1;
151
+ };
152
+ prev.addEventListener("click", () => go(current() - 1));
153
+ next.addEventListener("click", () => go(current() + 1));
154
+ strip.addEventListener("scroll", update, { passive: true });
155
+ strip.addEventListener("keydown", (e) => {
156
+ if (e.key === "ArrowRight" || e.key === "ArrowLeft") {
157
+ e.preventDefault();
158
+ go(current() + (e.key === "ArrowRight" ? 1 : -1));
159
+ } else if (e.key === "Enter") openReader(doc, block, link, pages, current());
160
+ });
161
+ pages.forEach((img, i) => img.addEventListener("click", () => openReader(doc, block, link, pages, i)));
162
+ if (pages.length === 1) viewport.classList.add("g-pdf-single");
163
+ update();
164
+ }
165
+ }
166
+ function navButton(doc, cls, label, text) {
167
+ const b = doc.createElement("button");
168
+ b.type = "button";
169
+ b.className = `g-pdf-nav g-pdf-${cls}`;
170
+ b.setAttribute("aria-label", label);
171
+ b.textContent = text;
172
+ return b;
173
+ }
174
+ function openReader(doc, block, link, pages, start) {
175
+ const host = block.closest(".g-scope") ?? doc.body;
176
+ const returnFocus = block.getRootNode().activeElement;
177
+ const name = (link.textContent || "").trim() || "PDF";
178
+ const reader = doc.createElement("div");
179
+ reader.className = "g-pdf-reader";
180
+ reader.setAttribute("role", "dialog");
181
+ reader.setAttribute("aria-modal", "true");
182
+ reader.setAttribute("aria-label", name);
183
+ const bar = doc.createElement("div");
184
+ bar.className = "g-pdf-bar";
185
+ const title = doc.createElement("span");
186
+ title.className = "g-pdf-title";
187
+ title.textContent = name;
188
+ const original = doc.createElement("a");
189
+ original.className = "g-btn g-btn-secondary";
190
+ original.href = link.href;
191
+ original.target = "_blank";
192
+ original.rel = "noopener noreferrer";
193
+ original.textContent = "Open PDF \u2197";
194
+ const close = doc.createElement("button");
195
+ close.type = "button";
196
+ close.className = "g-close g-pdf-close";
197
+ close.setAttribute("aria-label", "Close");
198
+ close.textContent = "\xD7";
199
+ bar.append(title, original, close);
200
+ const scroller = doc.createElement("div");
201
+ scroller.className = "g-pdf-scroll";
202
+ scroller.tabIndex = 0;
203
+ for (const page of pages) {
204
+ const img = doc.createElement("img");
205
+ img.src = page.src;
206
+ img.alt = page.alt;
207
+ img.decoding = "async";
208
+ for (const dim of ["width", "height"]) if (page.hasAttribute(dim)) img.setAttribute(dim, page.getAttribute(dim));
209
+ scroller.appendChild(img);
210
+ }
211
+ reader.append(bar, scroller);
212
+ const onKey = (e) => {
213
+ if (e.key === "Escape") {
214
+ e.stopPropagation();
215
+ done();
216
+ } else if (e.key === "Tab") {
217
+ const items = [original, close, scroller];
218
+ const at = items.indexOf(reader.getRootNode().activeElement);
219
+ e.preventDefault();
220
+ items[(at + (e.shiftKey ? items.length - 1 : 1)) % items.length].focus();
221
+ }
222
+ };
223
+ const done = () => {
224
+ reader.remove();
225
+ returnFocus?.focus?.({ preventScroll: true });
226
+ };
227
+ close.addEventListener("click", done);
228
+ reader.addEventListener("keydown", onKey);
229
+ host.appendChild(reader);
230
+ close.focus();
231
+ const target = scroller.children[start];
232
+ if (target && start > 0) {
233
+ const land = () => scroller.scrollTop = target.offsetTop - 12;
234
+ requestAnimationFrame(land);
235
+ if (!target.hasAttribute("height")) for (const img of Array.from(scroller.children).slice(0, start)) img.addEventListener("load", land, { once: true });
236
+ }
237
+ }
238
+
239
+ // src/help-view.ts
240
+ import { isArticleListed, matchesHelpQuery, resourceCenterSections, showMeKeyFromHref, SHOW_ME_HREF_PREFIX } from "@guidelytech/shared/runtime";
241
+ var SHOW_ME_KINDS = /* @__PURE__ */ new Set(["flow", "modal", "tooltip", "announcement"]);
242
+ function startableGuide(guides, keyOrId) {
243
+ if (!keyOrId || !guides) return null;
244
+ return guides.find((g) => (g.key === keyOrId || g.id === keyOrId) && SHOW_ME_KINDS.has(g.kind)) ?? null;
245
+ }
246
+ function resultRow(doc, icon, title, summary, onOpen, showMe) {
247
+ const item = listItem(doc, icon, title, summary);
248
+ item.addEventListener("click", onOpen);
249
+ if (!showMe) return item;
250
+ const row = el(doc, "div", "g-rc-row");
251
+ const b = el(doc, "button", "g-btn g-btn-primary g-rc-showme", "\u25B6 Show me");
252
+ b.type = "button";
253
+ b.setAttribute("aria-label", `Show me: ${title}`);
254
+ b.addEventListener("click", showMe);
255
+ row.append(item, b);
256
+ return row;
257
+ }
258
+ function enhanceShowMe(root, doc, canStart, start) {
259
+ for (const a of Array.from(root.querySelectorAll(`a[href^="${SHOW_ME_HREF_PREFIX}"]`))) {
260
+ const key = showMeKeyFromHref(a.getAttribute("href"));
261
+ const holder = a.closest("p.gd-show-me") ?? a;
262
+ if (!key || !canStart(key)) {
263
+ if (holder !== a) holder.remove();
264
+ else a.replaceWith(doc.createTextNode(a.textContent ?? ""));
265
+ continue;
266
+ }
267
+ const b = el(doc, "button", "g-btn g-btn-primary g-help-showme", `\u25B6 ${(a.textContent ?? "").trim() || "Show me"}`);
268
+ b.type = "button";
269
+ b.addEventListener("click", () => start(key));
270
+ a.replaceWith(b);
271
+ }
272
+ }
273
+ function el(doc, tag, cls, text) {
274
+ const e = doc.createElement(tag);
275
+ if (cls) e.className = cls;
276
+ if (text != null) e.textContent = text;
277
+ return e;
278
+ }
279
+ function listItem(doc, icon, title, summary) {
280
+ const b = el(doc, "button", "g-rc-item");
281
+ b.type = "button";
282
+ const i = el(doc, "span", "g-rc-icon", icon);
283
+ i.setAttribute("aria-hidden", "true");
284
+ const text = el(doc, "span");
285
+ text.appendChild(el(doc, "div", "g-rc-title", title));
286
+ if (summary) text.appendChild(el(doc, "div", "g-rc-sum", summary));
287
+ b.append(i, text);
288
+ return b;
289
+ }
290
+ function createHelpView(options) {
291
+ const { doc } = options;
292
+ let query = "";
293
+ let serverResults = [];
294
+ let searchSeq = 0;
295
+ let openSeq = 0;
296
+ let active = null;
297
+ const setActive = (next) => {
298
+ const was = !!active;
299
+ active = next;
300
+ if (was !== !!next) options.onArticleChange?.(!!next);
301
+ };
302
+ const root = el(doc, "div", "g-help");
303
+ const searchWrap = el(doc, "div", "g-rc-search");
304
+ const input = el(doc, "input", "g-input");
305
+ input.type = "search";
306
+ input.placeholder = "Search help articles\u2026";
307
+ input.setAttribute("aria-label", "Search help articles");
308
+ searchWrap.appendChild(input);
309
+ const body = el(doc, "div", "g-panel-body");
310
+ root.append(searchWrap, body);
311
+ const hasBody = (a) => typeof a.body === "string" && !a.bodyOmitted;
312
+ const canShowMe = (key) => !!options.onShowMe && !!startableGuide(options.guides?.(), key);
313
+ const showMe = (key) => options.onShowMe?.(startableGuide(options.guides?.(), key)?.key ?? key);
314
+ const openArticle = (article) => {
315
+ if (article.externalUrl) {
316
+ window.open(article.externalUrl, "_blank", "noopener,noreferrer");
317
+ if (hasBody(article)) options.onArticleView?.(article);
318
+ return;
319
+ }
320
+ const seq = ++openSeq;
321
+ if (hasBody(article) || !options.loadArticle) {
322
+ setActive({ article, loading: false });
323
+ if (hasBody(article)) options.onArticleView?.(article);
324
+ return render();
325
+ }
326
+ setActive({ article, loading: true });
327
+ render();
328
+ options.loadArticle(article.id).then((full) => {
329
+ if (seq !== openSeq || !active) return;
330
+ setActive({ article: full ?? { ...article, body: "" }, loading: false });
331
+ if (full) options.onArticleView?.(full);
332
+ render();
333
+ }).catch(() => {
334
+ if (seq !== openSeq || !active) return;
335
+ setActive({ article: { ...article, body: "" }, loading: false });
336
+ render();
337
+ });
338
+ };
339
+ const renderArticle = ({ article, loading }) => {
340
+ const wrap = el(doc, "div", "g-rc-article");
341
+ const back = el(doc, "button", "g-btn g-btn-link g-rc-back", "\u2190 Back");
342
+ back.type = "button";
343
+ back.addEventListener("click", () => {
344
+ openSeq++;
345
+ setActive(null);
346
+ render();
347
+ });
348
+ const title = el(doc, "h3", "g-title", article.title);
349
+ const content = el(doc, "div", "g-body");
350
+ if (loading) content.appendChild(el(doc, "div", "g-rc-empty", "Loading\u2026"));
351
+ else if (article.body) {
352
+ setSanitizedHtml(content, article.body);
353
+ enhancePdfViewers(content, doc);
354
+ enhanceShowMe(content, doc, canShowMe, showMe);
355
+ } else content.appendChild(el(doc, "div", "g-rc-empty", "This article couldn't be loaded."));
356
+ wrap.append(back, title, content);
357
+ body.appendChild(wrap);
358
+ back.focus?.({ preventScroll: true });
359
+ };
360
+ const section = (label, depth = 0) => {
361
+ const s = body.appendChild(el(doc, "div", "g-rc-section", label));
362
+ if (depth) s.dataset.depth = String(depth);
363
+ return s;
364
+ };
365
+ const articleButton = (a) => {
366
+ const key = a.walkthroughs?.find(canShowMe);
367
+ const summary = a.hasVideo ? a.summary ? `Video \xB7 ${a.summary}` : "Video" : a.summary;
368
+ body.appendChild(resultRow(doc, a.hasVideo ? "\u25B6" : "\u{1F4C4}", a.title, summary, () => openArticle(a), key ? () => showMe(key) : void 0));
369
+ };
370
+ function render() {
371
+ body.textContent = "";
372
+ if (active) return renderArticle(active);
373
+ const data = options.data();
374
+ const member = options.audienceIds?.() ?? [];
375
+ const sections = resourceCenterSections(data.collections, data.articles, member);
376
+ const q = query.trim().toLowerCase();
377
+ if (q) {
378
+ const walkthroughs = (data.recommended ?? []).filter((g) => matchesHelpQuery(g.name, q) && startableGuide(options.guides?.() ?? data.recommended, g.id));
379
+ if (walkthroughs.length) {
380
+ section(`Walkthroughs (${walkthroughs.length})`);
381
+ for (const g of walkthroughs) body.appendChild(resultRow(doc, "\u25B6", g.name, "Show me, step by step", () => options.onStartGuide?.(g.id)));
382
+ }
383
+ }
384
+ body.append(...options.extra?.(q) ?? []);
385
+ if (q) {
386
+ const visible = sections.flatMap((s) => s.articles);
387
+ const local = visible.filter((a) => matchesHelpQuery(`${a.title} ${a.summary}`, q));
388
+ const seen = new Set(local.map((a) => a.id));
389
+ const hidden = new Set(data.articles.map((a) => a.id).filter((id) => !visible.some((v) => v.id === id)));
390
+ const merged = [...local, ...serverResults.filter((a) => !seen.has(a.id) && !hidden.has(a.id) && isArticleListed(data.collections, a, member))];
391
+ section(`Results (${merged.length})`);
392
+ if (!merged.length) body.appendChild(el(doc, "div", "g-rc-empty", "No articles match your search."));
393
+ merged.forEach(articleButton);
394
+ return;
395
+ }
396
+ if (data.suggested?.items.length) {
397
+ section(data.suggested.title);
398
+ const glyph = { guide: "\u25B6", howto: "\u2630", article: "\u{1F4C4}" };
399
+ data.suggested.items.forEach((item) => {
400
+ const b = listItem(doc, glyph[item.kind], item.title, item.summary);
401
+ b.addEventListener("click", item.run);
402
+ body.appendChild(b);
403
+ });
404
+ }
405
+ if (data.recommended?.length) {
406
+ section("Recommended");
407
+ data.recommended.forEach((g) => {
408
+ const b = listItem(doc, "\u25B6", g.name, "Start guide");
409
+ b.addEventListener("click", () => options.onStartGuide?.(g.id));
410
+ body.appendChild(b);
411
+ });
412
+ }
413
+ if (data.checklists?.length) {
414
+ section("Checklists");
415
+ data.checklists.forEach((g) => {
416
+ const b = listItem(doc, "\u2611", g.name, "Open checklist");
417
+ b.addEventListener("click", () => options.onOpenChecklist?.(g.id));
418
+ body.appendChild(b);
419
+ });
420
+ }
421
+ for (const s of sections) {
422
+ section(s.collection ? s.path.join(" \u203A ") : "Articles", s.depth);
423
+ s.articles.forEach(articleButton);
424
+ }
425
+ if (data.faqs?.length) {
426
+ section("FAQ");
427
+ data.faqs.forEach((f) => {
428
+ const b = listItem(doc, "?", f.question);
429
+ b.setAttribute("aria-expanded", "false");
430
+ const a = el(doc, "div", "g-faq-a");
431
+ setSanitizedHtml(a, f.answer);
432
+ a.hidden = true;
433
+ b.addEventListener("click", () => {
434
+ a.hidden = !a.hidden;
435
+ b.setAttribute("aria-expanded", String(!a.hidden));
436
+ });
437
+ body.append(b, a);
438
+ });
439
+ }
440
+ if (data.links?.length) {
441
+ section("Links");
442
+ data.links.forEach((l) => {
443
+ const a = el(doc, "a", "g-rc-item");
444
+ a.href = l.url;
445
+ a.target = "_blank";
446
+ a.rel = "noopener noreferrer";
447
+ const i = el(doc, "span", "g-rc-icon", "\u2197");
448
+ i.setAttribute("aria-hidden", "true");
449
+ a.append(i, el(doc, "span", "g-rc-title", l.label));
450
+ body.appendChild(a);
451
+ });
452
+ }
453
+ if (!body.childNodes.length) body.appendChild(el(doc, "div", "g-rc-empty", "Nothing here yet."));
454
+ }
455
+ let debounce = null;
456
+ const onInput = () => {
457
+ query = input.value;
458
+ openSeq++;
459
+ setActive(null);
460
+ render();
461
+ if (debounce) clearTimeout(debounce);
462
+ if (!options.search || !query.trim()) {
463
+ serverResults = [];
464
+ return;
465
+ }
466
+ const seq = ++searchSeq;
467
+ debounce = setTimeout(() => {
468
+ options.search?.(query.trim()).then((results) => {
469
+ if (seq !== searchSeq) return;
470
+ serverResults = Array.isArray(results) ? results : [];
471
+ render();
472
+ }).catch(() => {
473
+ });
474
+ }, 250);
475
+ };
476
+ input.addEventListener("input", onInput);
477
+ return {
478
+ el: root,
479
+ render,
480
+ reset() {
481
+ openSeq++;
482
+ setActive(null);
483
+ query = "";
484
+ input.value = "";
485
+ serverResults = [];
486
+ },
487
+ openArticle(id) {
488
+ const listed = options.data().articles.find((a) => a.id === id) ?? { id, slug: id, title: "Article", summary: "", updatedAt: "", bodyOmitted: true };
489
+ openArticle(listed);
490
+ },
491
+ isArticleOpen: () => !!active,
492
+ focusSearch: () => input.focus?.({ preventScroll: true }),
493
+ setQuery(q) {
494
+ input.value = q;
495
+ onInput();
496
+ },
497
+ destroy() {
498
+ if (debounce) clearTimeout(debounce);
499
+ input.removeEventListener("input", onInput);
500
+ root.remove();
501
+ }
502
+ };
503
+ }
504
+
505
+ export {
506
+ sanitizeToFragment,
507
+ sanitizeHtml,
508
+ setSanitizedHtml,
509
+ enhancePdfViewers,
510
+ startableGuide,
511
+ resultRow,
512
+ el,
513
+ createHelpView
514
+ };
515
+ //# sourceMappingURL=chunk-KJRXCITD.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/sanitize.ts","../src/pdf-viewer.ts","../src/help-view.ts"],"sourcesContent":["import { EMBED_IFRAME_ATTRS, VIDEO_ATTRS, isAllowedEmbedSrc, isSafeMediaUrl } from \"@guidelytech/shared/runtime\";\n\nconst ALLOWED_TAGS = new Set([\"p\", \"br\", \"strong\", \"b\", \"em\", \"i\", \"u\", \"a\", \"ul\", \"ol\", \"li\", \"img\", \"h1\", \"h2\", \"h3\", \"h4\", \"code\", \"pre\", \"blockquote\", \"span\", \"div\", \"hr\", \"button\"]);\nconst DROP_TAGS = new Set([\"script\", \"style\", \"object\", \"embed\", \"form\", \"input\", \"textarea\", \"select\", \"link\", \"meta\", \"base\", \"template\", \"svg\", \"math\", \"frame\", \"frameset\", \"noscript\"]);\nconst ALLOWED_ATTRS = new Set([\"href\", \"src\", \"alt\", \"title\", \"target\", \"rel\", \"class\", \"width\", \"height\", \"data-g-action\", \"data-g-value\"]);\nfunction isSafeUrl(value: string, attr: string, tag: string): boolean {\n // eslint-disable-next-line no-control-regex\n const v = value.trim().replace(/[\\u0000-\\u001f\\s]/g, \"\").toLowerCase();\n if (!v) return false;\n const m = /^([a-z][a-z0-9+.-]*):/.exec(v);\n if (!m) return true; // relative URL\n const scheme = m[1];\n if (scheme === \"data\") return tag === \"img\" && attr === \"src\" && v.startsWith(\"data:image/\");\n return scheme === \"http\" || scheme === \"https\" || scheme === \"mailto\" || scheme === \"tel\";\n}\n\nfunction sanitizeElement(el: Element): void {\n const tag = el.tagName.toLowerCase();\n for (const attr of Array.from(el.attributes)) {\n const name = attr.name.toLowerCase();\n if (name.startsWith(\"on\") || !ALLOWED_ATTRS.has(name)) {\n el.removeAttribute(attr.name);\n continue;\n }\n if ((name === \"href\" || name === \"src\") && !isSafeUrl(attr.value, name, tag)) el.removeAttribute(attr.name);\n }\n if (tag === \"a\") {\n const target = el.getAttribute(\"target\");\n if (target && target !== \"_blank\" && target !== \"_self\") el.removeAttribute(\"target\");\n if (el.getAttribute(\"target\") === \"_blank\") el.setAttribute(\"rel\", \"noopener noreferrer\");\n }\n if (tag === \"button\") el.setAttribute(\"type\", \"button\");\n if (tag === \"img\") {\n for (const dim of [\"width\", \"height\"]) if (el.hasAttribute(dim) && !/^\\d{1,5}$/.test(el.getAttribute(dim)!)) el.removeAttribute(dim);\n el.setAttribute(\"loading\", \"lazy\");\n if (!el.getAttribute(\"alt\")) el.setAttribute(\"alt\", \"\");\n }\n}\n\n/**\n * Media elements are rebuilt from scratch rather than filtered: an `<iframe>` only for a canonical\n * YouTube/Vimeo/Loom/Wistia embed, a `<video>` only for an absolute http(s) file, each with fixed\n * attributes. Returns null when the element must go.\n */\nfunction rebuildMedia(el: Element): Element | null {\n const doc = el.ownerDocument;\n const tag = el.tagName.toLowerCase();\n const src = el.getAttribute(\"src\");\n let out: Element;\n if (tag === \"iframe\") {\n if (!isAllowedEmbedSrc(src)) return null;\n out = doc.createElement(\"iframe\");\n out.setAttribute(\"src\", src!);\n out.setAttribute(\"title\", el.getAttribute(\"title\") || \"Embedded video\");\n for (const [k, v] of Object.entries(EMBED_IFRAME_ATTRS)) out.setAttribute(k, v);\n } else {\n if (!isSafeMediaUrl(src)) return null;\n out = doc.createElement(\"video\");\n out.setAttribute(\"src\", src!);\n const poster = el.getAttribute(\"poster\");\n if (isSafeMediaUrl(poster)) out.setAttribute(\"poster\", poster!);\n for (const [k, v] of Object.entries(VIDEO_ATTRS)) out.setAttribute(k, v);\n }\n return out;\n}\n\nfunction walk(node: Node): void {\n const children = Array.from(node.childNodes);\n for (const child of children) {\n if (child.nodeType === 8 /* comment */) {\n node.removeChild(child);\n continue;\n }\n if (child.nodeType !== 1) continue;\n const el = child as Element;\n const tag = el.tagName.toLowerCase();\n if (DROP_TAGS.has(tag)) {\n node.removeChild(el);\n continue;\n }\n if (tag === \"iframe\" || tag === \"video\") {\n const media = rebuildMedia(el);\n if (media) node.replaceChild(media, el);\n else node.removeChild(el);\n continue;\n }\n if (!ALLOWED_TAGS.has(tag)) {\n // Unwrap: keep text/children, drop the element itself.\n while (el.firstChild) node.insertBefore(el.firstChild, el);\n node.removeChild(el);\n // Re-walk the parent since children moved up.\n walk(node);\n return;\n }\n sanitizeElement(el);\n walk(el);\n }\n}\n\n/**\n * Parses HTML into a sanitized DocumentFragment using a strict allowlist. No external\n * dependency: a `<template>` element parses inertly and the tree is walked element by element.\n */\nexport function sanitizeToFragment(html: string, doc: Document = document): DocumentFragment {\n const tpl = doc.createElement(\"template\");\n tpl.innerHTML = html || \"\";\n walk(tpl.content);\n return tpl.content;\n}\n\n/** Returns sanitized HTML as a string. */\nexport function sanitizeHtml(html: string, doc: Document = document): string {\n const frag = sanitizeToFragment(html, doc);\n const div = doc.createElement(\"div\");\n div.appendChild(frag);\n return div.innerHTML;\n}\n\n/** Replaces the content of `el` with the sanitized HTML. */\nexport function setSanitizedHtml(el: Element, html: string): void {\n el.textContent = \"\";\n el.appendChild(sanitizeToFragment(html, el.ownerDocument));\n}\n","import { FILE_LINK_CLASS, PDF_BLOCK_CLASS, PDF_PAGES_CLASS } from \"@guidelytech/shared/runtime\";\nimport { prefersReducedMotion } from \"./host\";\n\n/**\n * Turns the PDFs in a sanitized article body (`div.gd-pdf`: the PDF link plus its pages as images)\n * into inline viewers: pages swipe sideways with a page counter and previous/next buttons, and the\n * header or a tap on a page opens a full-screen reader inside the widget, so visitors read the\n * document without downloading it. The original PDF stays one tap away in the reader. Without this\n * (or without script) the CSS alone still shows a scrollable strip of pages.\n */\nexport function enhancePdfViewers(root: Element, doc: Document = root.ownerDocument): void {\n for (const block of Array.from(root.querySelectorAll(`div.${PDF_BLOCK_CLASS}`))) {\n const link = block.querySelector<HTMLAnchorElement>(`a.${FILE_LINK_CLASS}`);\n const strip = block.querySelector<HTMLElement>(`.${PDF_PAGES_CLASS}`);\n if (!link || !strip || block.hasAttribute(\"data-g-ready\")) continue;\n // The sanitizer strips unsafe sources; those images aren't pages.\n for (const img of Array.from(strip.querySelectorAll(\"img:not([src])\"))) img.remove();\n const pages = Array.from(strip.querySelectorAll(\"img\"));\n if (!pages.length) {\n strip.remove();\n continue;\n }\n block.setAttribute(\"data-g-ready\", \"\");\n const name = (link.textContent || \"\").trim() || \"PDF\";\n block.setAttribute(\"role\", \"group\");\n block.setAttribute(\"aria-label\", `${name}, ${pages.length} ${pages.length === 1 ? \"page\" : \"pages\"}`);\n link.setAttribute(\"aria-label\", `Read ${name} full screen`);\n link.addEventListener(\"click\", (e) => {\n e.preventDefault();\n openReader(doc, block, link, pages, 0);\n });\n\n const viewport = doc.createElement(\"div\");\n viewport.className = \"g-pdf-viewport\";\n strip.replaceWith(viewport);\n viewport.appendChild(strip);\n strip.tabIndex = 0;\n strip.setAttribute(\"aria-label\", `${name} pages`);\n\n const counter = doc.createElement(\"span\");\n counter.className = \"g-pdf-count\";\n counter.setAttribute(\"aria-live\", \"polite\");\n const prev = navButton(doc, \"prev\", \"Previous page\", \"‹\");\n const next = navButton(doc, \"next\", \"Next page\", \"›\");\n viewport.append(prev, next, counter);\n\n // Distance between page starts: one page width plus the gap.\n const stride = () => Math.max(1, pages.length > 1 ? pages[1]!.offsetLeft - pages[0]!.offsetLeft : strip.clientWidth);\n const current = () => Math.round(strip.scrollLeft / stride());\n const go = (i: number) => strip.scrollTo({ left: Math.max(0, Math.min(pages.length - 1, i)) * stride(), behavior: prefersReducedMotion() ? \"auto\" : \"smooth\" });\n const update = () => {\n const i = Math.min(pages.length - 1, current());\n counter.textContent = `${i + 1} / ${pages.length}`;\n prev.disabled = i === 0;\n next.disabled = i >= pages.length - 1;\n };\n prev.addEventListener(\"click\", () => go(current() - 1));\n next.addEventListener(\"click\", () => go(current() + 1));\n strip.addEventListener(\"scroll\", update, { passive: true });\n strip.addEventListener(\"keydown\", (e) => {\n if (e.key === \"ArrowRight\" || e.key === \"ArrowLeft\") {\n e.preventDefault();\n go(current() + (e.key === \"ArrowRight\" ? 1 : -1));\n } else if (e.key === \"Enter\") openReader(doc, block, link, pages, current());\n });\n pages.forEach((img, i) => img.addEventListener(\"click\", () => openReader(doc, block, link, pages, i)));\n if (pages.length === 1) viewport.classList.add(\"g-pdf-single\");\n update();\n }\n}\n\nfunction navButton(doc: Document, cls: string, label: string, text: string) {\n const b = doc.createElement(\"button\");\n b.type = \"button\";\n b.className = `g-pdf-nav g-pdf-${cls}`;\n b.setAttribute(\"aria-label\", label);\n b.textContent = text;\n return b;\n}\n\n/** Full-screen reader: every page stacked vertically, the original PDF linked, Escape to close. */\nfunction openReader(doc: Document, block: Element, link: HTMLAnchorElement, pages: HTMLImageElement[], start: number) {\n const host = (block.closest(\".g-scope\") as HTMLElement | null) ?? doc.body;\n // Inside the shadow root, `document.activeElement` is only the host.\n const returnFocus = (block.getRootNode() as Document | ShadowRoot).activeElement as HTMLElement | null;\n const name = (link.textContent || \"\").trim() || \"PDF\";\n\n const reader = doc.createElement(\"div\");\n reader.className = \"g-pdf-reader\";\n reader.setAttribute(\"role\", \"dialog\");\n reader.setAttribute(\"aria-modal\", \"true\");\n reader.setAttribute(\"aria-label\", name);\n const bar = doc.createElement(\"div\");\n bar.className = \"g-pdf-bar\";\n const title = doc.createElement(\"span\");\n title.className = \"g-pdf-title\";\n title.textContent = name;\n const original = doc.createElement(\"a\");\n original.className = \"g-btn g-btn-secondary\";\n original.href = link.href;\n original.target = \"_blank\";\n original.rel = \"noopener noreferrer\";\n original.textContent = \"Open PDF ↗\";\n const close = doc.createElement(\"button\");\n close.type = \"button\";\n close.className = \"g-close g-pdf-close\";\n close.setAttribute(\"aria-label\", \"Close\");\n close.textContent = \"×\";\n bar.append(title, original, close);\n\n const scroller = doc.createElement(\"div\");\n scroller.className = \"g-pdf-scroll\";\n scroller.tabIndex = 0;\n for (const page of pages) {\n const img = doc.createElement(\"img\");\n img.src = page.src;\n img.alt = page.alt;\n img.decoding = \"async\";\n // Sizes up front so every page has its height before loading, and opening at page N lands on it.\n for (const dim of [\"width\", \"height\"]) if (page.hasAttribute(dim)) img.setAttribute(dim, page.getAttribute(dim)!);\n scroller.appendChild(img);\n }\n reader.append(bar, scroller);\n\n const onKey = (e: KeyboardEvent) => {\n if (e.key === \"Escape\") {\n e.stopPropagation();\n done();\n } else if (e.key === \"Tab\") {\n // Keep focus inside the reader.\n const items: HTMLElement[] = [original, close, scroller];\n const at = items.indexOf((reader.getRootNode() as Document | ShadowRoot).activeElement as HTMLElement);\n e.preventDefault();\n items[(at + (e.shiftKey ? items.length - 1 : 1)) % items.length]!.focus();\n }\n };\n const done = () => {\n reader.remove();\n returnFocus?.focus?.({ preventScroll: true });\n };\n close.addEventListener(\"click\", done);\n reader.addEventListener(\"keydown\", onKey);\n host.appendChild(reader);\n close.focus();\n const target = scroller.children[start] as HTMLImageElement | undefined;\n if (target && start > 0) {\n const land = () => (scroller.scrollTop = target.offsetTop - 12);\n requestAnimationFrame(land);\n // Pages without stored sizes only get their height once the pages above have loaded.\n if (!target.hasAttribute(\"height\")) for (const img of Array.from(scroller.children).slice(0, start)) img.addEventListener(\"load\", land, { once: true });\n }\n}\n","import type { ResourceCenterArticle, ResourceCenterConfig } from \"@guidelytech/shared\";\nimport { isArticleListed, matchesHelpQuery, resourceCenterSections, showMeKeyFromHref, SHOW_ME_HREF_PREFIX } from \"@guidelytech/shared/runtime\";\nimport { enhancePdfViewers } from \"./pdf-viewer\";\nimport { setSanitizedHtml } from \"./sanitize\";\n\nexport interface HelpGuideRef {\n id: string;\n key: string;\n name: string;\n kind: string;\n}\n\n/** A page-aware suggestion at the top of the help view (the messenger works out what and how it opens). */\nexport interface HelpSuggestion {\n kind: \"guide\" | \"howto\" | \"article\";\n title: string;\n summary?: string;\n run: () => void;\n}\n\n/** What the help view lists. Article bodies may be missing (`bodyOmitted` or summaries only): they load on open. */\nexport interface HelpData {\n collections: ResourceCenterConfig[\"collections\"];\n articles: Array<Omit<ResourceCenterArticle, \"body\"> & { body?: string }>;\n /** Shown first, under its own title (\"For the Invoices page\"). */\n suggested?: { title: string; items: HelpSuggestion[] };\n recommended?: HelpGuideRef[];\n checklists?: HelpGuideRef[];\n faqs?: ResourceCenterConfig[\"faqs\"];\n links?: ResourceCenterConfig[\"links\"];\n}\n\nexport interface HelpViewOptions {\n doc: Document;\n data: () => HelpData;\n /** Server search. Results are merged with the client-side title/summary filter. */\n search?: (q: string) => Promise<ResourceCenterArticle[]>;\n /** The visitor's audiences; articles and collections targeted at others aren't listed. */\n audienceIds?: () => ReadonlySet<string> | readonly string[];\n /** Loads an article body that isn't in `data()`. */\n loadArticle?: (id: string) => Promise<ResourceCenterArticle | null>;\n onStartGuide?: (guideId: string) => void;\n onOpenChecklist?: (guideId: string) => void;\n onArticleView?: (article: ResourceCenterArticle) => void;\n /** Extra items above the list (`query` empty) or above the article results (the messenger's how-tos). */\n extra?: (query: string) => Node[];\n /** Live guides in this environment: \"Show me\" prompts only appear for ones that can be started. */\n guides?: () => readonly HelpGuideRef[];\n /** Starts a walkthrough from a Show me prompt (an article's, or a search result's). */\n onShowMe?: (guideKey: string) => void;\n /** An article opened (`true`) or the view went back to the list (`false`); hosts enlarge their panel to read it. */\n onArticleChange?: (open: boolean) => void;\n}\n\n/** Guide kinds a Show me prompt can start on demand. */\nconst SHOW_ME_KINDS = new Set([\"flow\", \"modal\", \"tooltip\", \"announcement\"]);\n\n/** The guide a Show me prompt names, when it is live here and can be started. */\nexport function startableGuide(guides: readonly HelpGuideRef[] | undefined, keyOrId: string | null | undefined): HelpGuideRef | null {\n if (!keyOrId || !guides) return null;\n return guides.find((g) => (g.key === keyOrId || g.id === keyOrId) && SHOW_ME_KINDS.has(g.kind)) ?? null;\n}\n\n/**\n * A result row: the item opens on click, and a separate \"Show me\" button starts its walkthrough\n * right away. Two sibling buttons, never one inside the other.\n */\nexport function resultRow(doc: Document, icon: string, title: string, summary: string | undefined, onOpen: () => void, showMe?: () => void): HTMLElement {\n const item = listItem(doc, icon, title, summary);\n item.addEventListener(\"click\", onOpen);\n if (!showMe) return item;\n const row = el(doc, \"div\", \"g-rc-row\");\n const b = el(doc, \"button\", \"g-btn g-btn-primary g-rc-showme\", \"▶ Show me\");\n b.type = \"button\";\n b.setAttribute(\"aria-label\", `Show me: ${title}`);\n b.addEventListener(\"click\", showMe);\n row.append(item, b);\n return row;\n}\n\n/**\n * Turns the Show me links in a rendered article (`<a href=\"#guidely-show-me:<key>\">`) into buttons\n * that start the walkthrough, and removes the ones whose guide isn't live here.\n */\nexport function enhanceShowMe(root: HTMLElement, doc: Document, canStart: (key: string) => boolean, start: (key: string) => void): void {\n for (const a of Array.from(root.querySelectorAll<HTMLAnchorElement>(`a[href^=\"${SHOW_ME_HREF_PREFIX}\"]`))) {\n const key = showMeKeyFromHref(a.getAttribute(\"href\"));\n const holder = a.closest(\"p.gd-show-me\") ?? a;\n if (!key || !canStart(key)) {\n // A prompt alone in its paragraph goes with it; one inside text keeps its words.\n if (holder !== a) holder.remove();\n else a.replaceWith(doc.createTextNode(a.textContent ?? \"\"));\n continue;\n }\n const b = el(doc, \"button\", \"g-btn g-btn-primary g-help-showme\", `▶ ${(a.textContent ?? \"\").trim() || \"Show me\"}`);\n b.type = \"button\";\n b.addEventListener(\"click\", () => start(key));\n a.replaceWith(b);\n }\n}\n\nexport interface HelpView {\n /** Search box + scrollable body. */\n el: HTMLElement;\n render(): void;\n /** Back to the article list and clear the search. */\n reset(): void;\n openArticle(id: string): void;\n /** True while an article is open. */\n isArticleOpen(): boolean;\n focusSearch(): void;\n setQuery(q: string): void;\n destroy(): void;\n}\n\nexport function el<K extends keyof HTMLElementTagNameMap>(doc: Document, tag: K, cls?: string, text?: string): HTMLElementTagNameMap[K] {\n const e = doc.createElement(tag);\n if (cls) e.className = cls;\n if (text != null) e.textContent = text;\n return e;\n}\n\nexport function listItem(doc: Document, icon: string, title: string, summary?: string): HTMLButtonElement {\n const b = el(doc, \"button\", \"g-rc-item\");\n b.type = \"button\";\n const i = el(doc, \"span\", \"g-rc-icon\", icon);\n i.setAttribute(\"aria-hidden\", \"true\");\n const text = el(doc, \"span\");\n text.appendChild(el(doc, \"div\", \"g-rc-title\", title));\n if (summary) text.appendChild(el(doc, \"div\", \"g-rc-sum\", summary));\n b.append(i, text);\n return b;\n}\n\ntype ListedArticle = HelpData[\"articles\"][number];\n\n/** Article browser shared by the resource center and the messenger Help tab. */\nexport function createHelpView(options: HelpViewOptions): HelpView {\n const { doc } = options;\n let query = \"\";\n let serverResults: ResourceCenterArticle[] = [];\n let searchSeq = 0;\n let openSeq = 0;\n let active: { article: ListedArticle; loading: boolean } | null = null;\n const setActive = (next: typeof active) => {\n const was = !!active;\n active = next;\n if (was !== !!next) options.onArticleChange?.(!!next);\n };\n\n const root = el(doc, \"div\", \"g-help\");\n const searchWrap = el(doc, \"div\", \"g-rc-search\");\n const input = el(doc, \"input\", \"g-input\");\n input.type = \"search\";\n input.placeholder = \"Search help articles…\";\n input.setAttribute(\"aria-label\", \"Search help articles\");\n searchWrap.appendChild(input);\n const body = el(doc, \"div\", \"g-panel-body\");\n root.append(searchWrap, body);\n\n const hasBody = (a: ListedArticle): a is ResourceCenterArticle => typeof a.body === \"string\" && !a.bodyOmitted;\n const canShowMe = (key: string) => !!options.onShowMe && !!startableGuide(options.guides?.(), key);\n const showMe = (key: string) => options.onShowMe?.(startableGuide(options.guides?.(), key)?.key ?? key);\n\n const openArticle = (article: ListedArticle) => {\n if (article.externalUrl) {\n window.open(article.externalUrl, \"_blank\", \"noopener,noreferrer\");\n if (hasBody(article)) options.onArticleView?.(article);\n return;\n }\n const seq = ++openSeq;\n if (hasBody(article) || !options.loadArticle) {\n setActive({ article, loading: false });\n if (hasBody(article)) options.onArticleView?.(article);\n return render();\n }\n setActive({ article, loading: true });\n render();\n options\n .loadArticle(article.id)\n .then((full) => {\n if (seq !== openSeq || !active) return;\n setActive({ article: full ?? { ...article, body: \"\" }, loading: false });\n if (full) options.onArticleView?.(full);\n render();\n })\n .catch(() => {\n if (seq !== openSeq || !active) return;\n setActive({ article: { ...article, body: \"\" }, loading: false });\n render();\n });\n };\n\n const renderArticle = ({ article, loading }: NonNullable<typeof active>) => {\n const wrap = el(doc, \"div\", \"g-rc-article\");\n const back = el(doc, \"button\", \"g-btn g-btn-link g-rc-back\", \"← Back\");\n back.type = \"button\";\n back.addEventListener(\"click\", () => {\n openSeq++;\n setActive(null);\n render();\n });\n const title = el(doc, \"h3\", \"g-title\", article.title);\n const content = el(doc, \"div\", \"g-body\");\n if (loading) content.appendChild(el(doc, \"div\", \"g-rc-empty\", \"Loading…\"));\n else if (article.body) {\n setSanitizedHtml(content, article.body);\n enhancePdfViewers(content, doc);\n enhanceShowMe(content, doc, canShowMe, showMe);\n }\n else content.appendChild(el(doc, \"div\", \"g-rc-empty\", \"This article couldn't be loaded.\"));\n wrap.append(back, title, content);\n body.appendChild(wrap);\n back.focus?.({ preventScroll: true });\n };\n\n const section = (label: string, depth = 0) => {\n const s = body.appendChild(el(doc, \"div\", \"g-rc-section\", label));\n if (depth) s.dataset.depth = String(depth);\n return s;\n };\n const articleButton = (a: ListedArticle) => {\n const key = a.walkthroughs?.find(canShowMe);\n const summary = a.hasVideo ? (a.summary ? `Video · ${a.summary}` : \"Video\") : a.summary;\n body.appendChild(resultRow(doc, a.hasVideo ? \"▶\" : \"📄\", a.title, summary, () => openArticle(a), key ? () => showMe(key) : undefined));\n };\n\n function render() {\n body.textContent = \"\";\n if (active) return renderArticle(active);\n const data = options.data();\n const member = options.audienceIds?.() ?? [];\n const sections = resourceCenterSections(data.collections, data.articles, member);\n const q = query.trim().toLowerCase();\n if (q) {\n // Walkthroughs the team lists in Help (recommended guides) whose name matches, ready to start.\n const walkthroughs = (data.recommended ?? []).filter((g) => matchesHelpQuery(g.name, q) && startableGuide(options.guides?.() ?? data.recommended, g.id));\n if (walkthroughs.length) {\n section(`Walkthroughs (${walkthroughs.length})`);\n for (const g of walkthroughs) body.appendChild(resultRow(doc, \"▶\", g.name, \"Show me, step by step\", () => options.onStartGuide?.(g.id)));\n }\n }\n body.append(...(options.extra?.(q) ?? []));\n if (q) {\n const visible = sections.flatMap((s) => s.articles);\n const local = visible.filter((a) => matchesHelpQuery(`${a.title} ${a.summary}`, q));\n const seen = new Set(local.map((a) => a.id));\n // Server search covers every published article: keep only the ones listed for this visitor.\n const hidden = new Set(data.articles.map((a) => a.id).filter((id) => !visible.some((v) => v.id === id)));\n const merged: ListedArticle[] = [...local, ...serverResults.filter((a) => !seen.has(a.id) && !hidden.has(a.id) && isArticleListed(data.collections, a, member))];\n section(`Results (${merged.length})`);\n if (!merged.length) body.appendChild(el(doc, \"div\", \"g-rc-empty\", \"No articles match your search.\"));\n merged.forEach(articleButton);\n return;\n }\n if (data.suggested?.items.length) {\n section(data.suggested.title);\n const glyph = { guide: \"▶\", howto: \"☰\", article: \"📄\" } as const;\n data.suggested.items.forEach((item) => {\n const b = listItem(doc, glyph[item.kind], item.title, item.summary);\n b.addEventListener(\"click\", item.run);\n body.appendChild(b);\n });\n }\n if (data.recommended?.length) {\n section(\"Recommended\");\n data.recommended.forEach((g) => {\n const b = listItem(doc, \"▶\", g.name, \"Start guide\");\n b.addEventListener(\"click\", () => options.onStartGuide?.(g.id));\n body.appendChild(b);\n });\n }\n if (data.checklists?.length) {\n section(\"Checklists\");\n data.checklists.forEach((g) => {\n const b = listItem(doc, \"☑\", g.name, \"Open checklist\");\n b.addEventListener(\"click\", () => options.onOpenChecklist?.(g.id));\n body.appendChild(b);\n });\n }\n // Nested collections read as \"Billing › Invoices\", depth first.\n for (const s of sections) {\n section(s.collection ? s.path.join(\" › \") : \"Articles\", s.depth);\n s.articles.forEach(articleButton);\n }\n if (data.faqs?.length) {\n section(\"FAQ\");\n data.faqs.forEach((f) => {\n const b = listItem(doc, \"?\", f.question);\n b.setAttribute(\"aria-expanded\", \"false\");\n const a = el(doc, \"div\", \"g-faq-a\");\n setSanitizedHtml(a, f.answer);\n a.hidden = true;\n b.addEventListener(\"click\", () => {\n a.hidden = !a.hidden;\n b.setAttribute(\"aria-expanded\", String(!a.hidden));\n });\n body.append(b, a);\n });\n }\n if (data.links?.length) {\n section(\"Links\");\n data.links.forEach((l) => {\n const a = el(doc, \"a\", \"g-rc-item\");\n a.href = l.url;\n a.target = \"_blank\";\n a.rel = \"noopener noreferrer\";\n const i = el(doc, \"span\", \"g-rc-icon\", \"↗\");\n i.setAttribute(\"aria-hidden\", \"true\");\n a.append(i, el(doc, \"span\", \"g-rc-title\", l.label));\n body.appendChild(a);\n });\n }\n if (!body.childNodes.length) body.appendChild(el(doc, \"div\", \"g-rc-empty\", \"Nothing here yet.\"));\n }\n\n let debounce: ReturnType<typeof setTimeout> | null = null;\n const onInput = () => {\n query = input.value;\n openSeq++;\n setActive(null);\n render();\n if (debounce) clearTimeout(debounce);\n if (!options.search || !query.trim()) {\n serverResults = [];\n return;\n }\n const seq = ++searchSeq;\n debounce = setTimeout(() => {\n options\n .search?.(query.trim())\n .then((results) => {\n if (seq !== searchSeq) return;\n serverResults = Array.isArray(results) ? results : [];\n render();\n })\n .catch(() => {});\n }, 250);\n };\n input.addEventListener(\"input\", onInput);\n\n return {\n el: root,\n render,\n reset() {\n openSeq++;\n setActive(null);\n query = \"\";\n input.value = \"\";\n serverResults = [];\n },\n openArticle(id: string) {\n const listed = options.data().articles.find((a) => a.id === id) ?? { id, slug: id, title: \"Article\", summary: \"\", updatedAt: \"\", bodyOmitted: true };\n openArticle(listed);\n },\n isArticleOpen: () => !!active,\n focusSearch: () => input.focus?.({ preventScroll: true }),\n setQuery(q: string) {\n input.value = q;\n onInput();\n },\n destroy() {\n if (debounce) clearTimeout(debounce);\n input.removeEventListener(\"input\", onInput);\n root.remove();\n },\n };\n}\n"],"mappings":"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","names":["el"]}