@guidelytech/guide-runtime 0.10.0 → 0.11.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.js CHANGED
@@ -3,10 +3,10 @@ import {
3
3
  describeElement,
4
4
  isGuidelyElement,
5
5
  normalizeText
6
- } from "./chunk-GUH2PQJ7.js";
6
+ } from "./chunk-VQJ7MZ2K.js";
7
7
  import {
8
8
  createScope
9
- } from "./chunk-PG7CMBHD.js";
9
+ } from "./chunk-VJSQM23R.js";
10
10
 
11
11
  // src/capture-styles.ts
12
12
  var CAPTURE_STYLES = `
@@ -0,0 +1,585 @@
1
+ import {
2
+ prefersReducedMotion
3
+ } from "./chunk-VJSQM23R.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, RC_MODULE_IDS, RC_MODULE_LABELS, 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
+ searchWrap.hidden = !!data.modules && !data.modules.includes("search");
376
+ const sections = resourceCenterSections(data.collections, data.articles, member);
377
+ const q = query.trim().toLowerCase();
378
+ if (q) {
379
+ const walkthroughs = (data.recommended ?? []).filter((g) => matchesHelpQuery(g.name, q) && startableGuide(options.guides?.() ?? data.recommended, g.id));
380
+ if (walkthroughs.length) {
381
+ section(`Walkthroughs (${walkthroughs.length})`);
382
+ for (const g of walkthroughs) body.appendChild(resultRow(doc, "\u25B6", g.name, "Show me, step by step", () => options.onStartGuide?.(g.id)));
383
+ }
384
+ }
385
+ body.append(...options.extra?.(q) ?? []);
386
+ if (q) {
387
+ const visible = sections.flatMap((s) => s.articles);
388
+ const local = visible.filter((a) => matchesHelpQuery(`${a.title} ${a.summary}`, q));
389
+ const seen = new Set(local.map((a) => a.id));
390
+ const hidden = new Set(data.articles.map((a) => a.id).filter((id) => !visible.some((v) => v.id === id)));
391
+ const merged = [...local, ...serverResults.filter((a) => !seen.has(a.id) && !hidden.has(a.id) && isArticleListed(data.collections, a, member))];
392
+ section(`Results (${merged.length})`);
393
+ if (!merged.length) body.appendChild(el(doc, "div", "g-rc-empty", "No articles match your search."));
394
+ merged.forEach(articleButton);
395
+ return;
396
+ }
397
+ if (data.suggested?.items.length) {
398
+ section(data.suggested.title);
399
+ const glyph = { guide: "\u25B6", howto: "\u2630", article: "\u{1F4C4}" };
400
+ data.suggested.items.forEach((item) => {
401
+ const b = listItem(doc, glyph[item.kind], item.title, item.summary);
402
+ b.addEventListener("click", item.run);
403
+ body.appendChild(b);
404
+ });
405
+ }
406
+ const renderers = {
407
+ search: () => {
408
+ },
409
+ announcements: () => {
410
+ if (!data.announcements?.length) return;
411
+ section(RC_MODULE_LABELS.announcements);
412
+ const seen = data.announcementsSeenAt ? Date.parse(data.announcementsSeenAt) : NaN;
413
+ data.announcements.slice(0, 20).forEach((n) => body.appendChild(announcementItem(n, Number.isNaN(seen) || Date.parse(n.publishedAt) > seen)));
414
+ },
415
+ guides: () => {
416
+ if (!data.recommended?.length) return;
417
+ section("Recommended");
418
+ data.recommended.forEach((g) => {
419
+ const b = listItem(doc, "\u25B6", g.name, "Start guide");
420
+ b.addEventListener("click", () => options.onStartGuide?.(g.id));
421
+ body.appendChild(b);
422
+ });
423
+ },
424
+ checklists: () => {
425
+ if (!data.checklists?.length) return;
426
+ section("Checklists");
427
+ data.checklists.forEach((g) => {
428
+ const b = listItem(doc, "\u2611", g.name, "Open checklist");
429
+ b.addEventListener("click", () => options.onOpenChecklist?.(g.id));
430
+ body.appendChild(b);
431
+ });
432
+ },
433
+ articles: () => {
434
+ for (const s of sections) {
435
+ section(s.collection ? s.path.join(" \u203A ") : "Articles", s.depth);
436
+ s.articles.forEach(articleButton);
437
+ }
438
+ },
439
+ faqs: () => {
440
+ if (!data.faqs?.length) return;
441
+ section("FAQ");
442
+ data.faqs.forEach((f) => {
443
+ const b = listItem(doc, "?", f.question);
444
+ b.setAttribute("aria-expanded", "false");
445
+ const a = el(doc, "div", "g-faq-a");
446
+ a.id = `g-faq-${f.id}`;
447
+ b.setAttribute("aria-controls", a.id);
448
+ setSanitizedHtml(a, f.answer);
449
+ a.hidden = true;
450
+ b.addEventListener("click", () => {
451
+ a.hidden = !a.hidden;
452
+ b.setAttribute("aria-expanded", String(!a.hidden));
453
+ });
454
+ body.append(b, a);
455
+ });
456
+ },
457
+ links: () => {
458
+ if (!data.links?.length) return;
459
+ section("Links");
460
+ data.links.forEach((l) => {
461
+ const a = el(doc, "a", "g-rc-item");
462
+ a.href = l.url;
463
+ a.target = "_blank";
464
+ a.rel = "noopener noreferrer";
465
+ const i = el(doc, "span", "g-rc-icon", "\u2197");
466
+ i.setAttribute("aria-hidden", "true");
467
+ a.append(i, el(doc, "span", "g-rc-title", l.label));
468
+ a.setAttribute("aria-label", `${l.label} (opens in a new tab)`);
469
+ body.appendChild(a);
470
+ });
471
+ }
472
+ };
473
+ for (const id of data.modules ?? RC_MODULE_IDS) renderers[id]?.();
474
+ if (!body.childNodes.length) body.appendChild(el(doc, "div", "g-rc-empty", "Nothing here yet."));
475
+ }
476
+ function announcementItem(n, unread) {
477
+ const item = el(doc, "article", `g-rc-news${unread ? " g-rc-news-unread" : ""}`);
478
+ const head = el(doc, "div", "g-rc-news-head");
479
+ const title = el(doc, "h3", "g-rc-news-title", n.title);
480
+ head.appendChild(title);
481
+ if (unread) {
482
+ const dot = el(doc, "span", "g-rc-news-new", "New");
483
+ head.appendChild(dot);
484
+ }
485
+ const date = el(doc, "time", "g-rc-news-date");
486
+ date.dateTime = n.publishedAt;
487
+ try {
488
+ date.textContent = new Date(n.publishedAt).toLocaleDateString(void 0, { year: "numeric", month: "short", day: "numeric" });
489
+ } catch {
490
+ date.textContent = n.publishedAt.slice(0, 10);
491
+ }
492
+ item.append(head, date);
493
+ if (n.body) {
494
+ const b = el(doc, "div", "g-body g-rc-news-body");
495
+ setSanitizedHtml(b, n.body);
496
+ item.appendChild(b);
497
+ }
498
+ const actions = el(doc, "div", "g-rc-news-actions");
499
+ const guide = n.guideId ? startableGuide(options.guides?.(), n.guideId) : null;
500
+ if (guide) {
501
+ const b = el(doc, "button", "g-btn g-btn-primary", n.linkLabel || "Show me");
502
+ b.type = "button";
503
+ b.addEventListener("click", () => options.onShowMe ? options.onShowMe(guide.key) : options.onStartGuide?.(guide.id));
504
+ actions.appendChild(b);
505
+ }
506
+ if (n.articleId) {
507
+ const listed = options.data().articles.find((a) => a.id === n.articleId);
508
+ if (listed) {
509
+ const b = el(doc, "button", "g-btn g-btn-secondary", guide ? "Read more" : n.linkLabel || "Read more");
510
+ b.type = "button";
511
+ b.addEventListener("click", () => openArticle(listed));
512
+ actions.appendChild(b);
513
+ }
514
+ }
515
+ if (n.url && /^https?:\/\//i.test(n.url)) {
516
+ const a = el(doc, "a", "g-btn g-btn-link", !guide && !n.articleId && n.linkLabel ? n.linkLabel : "Learn more");
517
+ a.href = n.url;
518
+ a.target = "_blank";
519
+ a.rel = "noopener noreferrer";
520
+ actions.appendChild(a);
521
+ }
522
+ if (actions.childNodes.length) item.appendChild(actions);
523
+ return item;
524
+ }
525
+ let debounce = null;
526
+ const onInput = () => {
527
+ query = input.value;
528
+ openSeq++;
529
+ setActive(null);
530
+ render();
531
+ if (debounce) clearTimeout(debounce);
532
+ if (!options.search || !query.trim()) {
533
+ serverResults = [];
534
+ return;
535
+ }
536
+ const seq = ++searchSeq;
537
+ debounce = setTimeout(() => {
538
+ options.search?.(query.trim()).then((results) => {
539
+ if (seq !== searchSeq) return;
540
+ serverResults = Array.isArray(results) ? results : [];
541
+ render();
542
+ }).catch(() => {
543
+ });
544
+ }, 250);
545
+ };
546
+ input.addEventListener("input", onInput);
547
+ return {
548
+ el: root,
549
+ render,
550
+ reset() {
551
+ openSeq++;
552
+ setActive(null);
553
+ query = "";
554
+ input.value = "";
555
+ serverResults = [];
556
+ },
557
+ openArticle(id) {
558
+ const listed = options.data().articles.find((a) => a.id === id) ?? { id, slug: id, title: "Article", summary: "", updatedAt: "", bodyOmitted: true };
559
+ openArticle(listed);
560
+ },
561
+ isArticleOpen: () => !!active,
562
+ focusSearch: () => input.focus?.({ preventScroll: true }),
563
+ setQuery(q) {
564
+ input.value = q;
565
+ onInput();
566
+ },
567
+ destroy() {
568
+ if (debounce) clearTimeout(debounce);
569
+ input.removeEventListener("input", onInput);
570
+ root.remove();
571
+ }
572
+ };
573
+ }
574
+
575
+ export {
576
+ sanitizeToFragment,
577
+ sanitizeHtml,
578
+ setSanitizedHtml,
579
+ enhancePdfViewers,
580
+ startableGuide,
581
+ resultRow,
582
+ el,
583
+ createHelpView
584
+ };
585
+ //# sourceMappingURL=chunk-H3CROHPR.js.map