@guidelytech/react-native 0.4.1 → 0.5.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/index.cjs CHANGED
@@ -54,7 +54,7 @@ module.exports = __toCommonJS(index_exports);
54
54
  var import_react8 = require("react");
55
55
  var import_react_native11 = require("react-native");
56
56
 
57
- // ../shared/dist/chunk-QUKMOTT4.js
57
+ // ../shared/dist/chunk-5IQVBZO6.js
58
58
  function fnv1a(input) {
59
59
  let h = 2166136261;
60
60
  for (let i = 0; i < input.length; i++) {
@@ -358,6 +358,148 @@ function sanitizeProperties(props) {
358
358
  return out;
359
359
  }
360
360
  var SOURCEMAP_MAX_FILE_BYTES = 25 * 1024 * 1024;
361
+ var MEDIA_IMAGE_MAX_BYTES = 10 * 1024 * 1024;
362
+ var MEDIA_VIDEO_MAX_BYTES = 200 * 1024 * 1024;
363
+ var YOUTUBE_ID = /^[A-Za-z0-9_-]{11}$/;
364
+ var VIMEO_ID = /^\d{1,12}$/;
365
+ var VIMEO_HASH = /^[0-9a-f]{6,20}$/i;
366
+ var LOOM_ID = /^[0-9a-f]{32}$/i;
367
+ var WISTIA_ID = /^[a-z0-9]{10}$/i;
368
+ function parseUrl(input) {
369
+ const s = input.trim();
370
+ if (!s) return null;
371
+ try {
372
+ const u = new URL(/^[a-z][a-z0-9+.-]*:/i.test(s) ? s : `https://${s}`);
373
+ if (u.protocol !== "https:" && u.protocol !== "http:") return null;
374
+ if (u.username || u.password || u.port && u.port !== "443" && u.port !== "80") return null;
375
+ return u;
376
+ } catch {
377
+ return null;
378
+ }
379
+ }
380
+ function parseStart(v) {
381
+ if (!v) return 0;
382
+ if (/^\d+$/.test(v)) return Math.min(Number(v), 86400);
383
+ const m = /^(?:(\d+)h)?(?:(\d+)m)?(?:(\d+)s)?$/.exec(v);
384
+ if (!m || !m[0]) return 0;
385
+ return Math.min(Number(m[1] ?? 0) * 3600 + Number(m[2] ?? 0) * 60 + Number(m[3] ?? 0), 86400);
386
+ }
387
+ function youtube(id, start) {
388
+ if (!YOUTUBE_ID.test(id)) return null;
389
+ return {
390
+ provider: "youtube",
391
+ src: `https://www.youtube-nocookie.com/embed/${id}${start ? `?start=${start}` : ""}`,
392
+ watchUrl: `https://www.youtube.com/watch?v=${id}${start ? `&t=${start}s` : ""}`,
393
+ title: "YouTube video"
394
+ };
395
+ }
396
+ function vimeo(id, hash) {
397
+ if (!VIMEO_ID.test(id) || hash !== null && !VIMEO_HASH.test(hash)) return null;
398
+ const h = hash ? hash.toLowerCase() : null;
399
+ return { provider: "vimeo", src: `https://player.vimeo.com/video/${id}${h ? `?h=${h}` : ""}`, watchUrl: `https://vimeo.com/${id}${h ? `/${h}` : ""}`, title: "Vimeo video" };
400
+ }
401
+ function toEmbedUrl(input) {
402
+ const u = parseUrl(input);
403
+ if (!u) return null;
404
+ const host = u.hostname.toLowerCase().replace(/^www\./, "");
405
+ const parts = u.pathname.split("/").filter(Boolean);
406
+ switch (host) {
407
+ case "youtube.com":
408
+ case "m.youtube.com":
409
+ case "youtube-nocookie.com": {
410
+ const start = parseStart(u.searchParams.get("start") ?? u.searchParams.get("t"));
411
+ if (parts[0] === "watch" && parts.length === 1) return youtube(u.searchParams.get("v") ?? "", start);
412
+ if ((parts[0] === "embed" || parts[0] === "shorts" || parts[0] === "live") && parts.length === 2) return youtube(parts[1], start);
413
+ return null;
414
+ }
415
+ case "youtu.be":
416
+ return parts.length === 1 ? youtube(parts[0], parseStart(u.searchParams.get("t") ?? u.searchParams.get("start"))) : null;
417
+ case "vimeo.com":
418
+ if (parts.length === 1) return vimeo(parts[0], null);
419
+ if (parts.length === 2 && VIMEO_ID.test(parts[0])) return vimeo(parts[0], parts[1]);
420
+ return null;
421
+ case "player.vimeo.com":
422
+ return parts[0] === "video" && parts.length === 2 ? vimeo(parts[1], u.searchParams.get("h")) : null;
423
+ case "loom.com":
424
+ if ((parts[0] === "share" || parts[0] === "embed") && parts.length === 2 && LOOM_ID.test(parts[1])) {
425
+ const id = parts[1].toLowerCase();
426
+ return { provider: "loom", src: `https://www.loom.com/embed/${id}`, watchUrl: `https://www.loom.com/share/${id}`, title: "Loom video" };
427
+ }
428
+ return null;
429
+ default: {
430
+ let id;
431
+ if (host === "fast.wistia.net" || host === "fast.wistia.com") id = parts[0] === "embed" && parts[1] === "iframe" && parts.length === 3 ? parts[2] : void 0;
432
+ else if (host === "wi.st" || host === "wistia.com" || /^[a-z0-9-]+\.wistia\.com$/.test(host)) id = parts[0] === "medias" && parts.length === 2 ? parts[1] : void 0;
433
+ if (!id || !WISTIA_ID.test(id)) return null;
434
+ return { provider: "wistia", src: `https://fast.wistia.net/embed/iframe/${id}`, watchUrl: `https://fast.wistia.net/embed/iframe/${id}`, title: "Wistia video" };
435
+ }
436
+ }
437
+ }
438
+ function isAllowedEmbedSrc(src) {
439
+ if (!src) return false;
440
+ return toEmbedUrl(src)?.src === src;
441
+ }
442
+ function isSafeMediaUrl(url) {
443
+ if (!url) return false;
444
+ try {
445
+ const u = new URL(url);
446
+ return (u.protocol === "https:" || u.protocol === "http:") && !u.username && !u.password;
447
+ } catch {
448
+ return false;
449
+ }
450
+ }
451
+ var MAX_COLLECTION_DEPTH = 3;
452
+ function matchesAudiences(required, member) {
453
+ if (!required || required.length === 0) return true;
454
+ if (!member) return false;
455
+ const has2 = member instanceof Set ? (id) => member.has(id) : (id) => member.includes(id);
456
+ return required.some(has2);
457
+ }
458
+ function resourceCenterSections(collections, articles, member) {
459
+ const byId = new Map(collections.map((c) => [c.id, c]));
460
+ const children = /* @__PURE__ */ new Map();
461
+ for (const c of collections) {
462
+ const parent = c.parentId && c.parentId !== c.id && byId.has(c.parentId) ? c.parentId : null;
463
+ const list = children.get(parent) ?? [];
464
+ list.push(c);
465
+ children.set(parent, list);
466
+ }
467
+ for (const list of children.values()) list.sort((a, b) => a.order - b.order || a.name.localeCompare(b.name));
468
+ const direct = /* @__PURE__ */ new Map();
469
+ const uncategorized = [];
470
+ for (const a of articles) {
471
+ if (!matchesAudiences(a.audienceIds, member)) continue;
472
+ if (a.collectionId && byId.has(a.collectionId)) {
473
+ const list = direct.get(a.collectionId) ?? [];
474
+ list.push(a);
475
+ direct.set(a.collectionId, list);
476
+ } else uncategorized.push(a);
477
+ }
478
+ const sections = [];
479
+ const seen = /* @__PURE__ */ new Set();
480
+ const visit = (c, path) => {
481
+ if (seen.has(c.id)) return;
482
+ seen.add(c.id);
483
+ if (!matchesAudiences(c.audienceIds, member)) return;
484
+ const here = [...path, c.name];
485
+ const list = direct.get(c.id);
486
+ if (list?.length) sections.push({ collection: c, path: here, depth: here.length - 1, articles: list });
487
+ for (const child of children.get(c.id) ?? []) visit(child, here);
488
+ };
489
+ for (const root of children.get(null) ?? []) visit(root, []);
490
+ if (uncategorized.length) sections.push({ collection: null, path: [], depth: 0, articles: uncategorized });
491
+ return sections;
492
+ }
493
+ function isArticleListed(collections, article, member) {
494
+ if (!matchesAudiences(article.audienceIds, member)) return false;
495
+ const byId = new Map(collections.map((c) => [c.id, c]));
496
+ let cur = article.collectionId ? byId.get(article.collectionId) : void 0;
497
+ for (let guard = 0; cur && guard <= MAX_COLLECTION_DEPTH * 4; guard++) {
498
+ if (!matchesAudiences(cur.audienceIds, member)) return false;
499
+ cur = cur.parentId ? byId.get(cur.parentId) : void 0;
500
+ }
501
+ return true;
502
+ }
361
503
 
362
504
  // ../sdk-core/dist/chunk-GJT7CFAC.js
363
505
  var __defProp2 = Object.defineProperty;
@@ -1612,7 +1754,7 @@ var PersistentStore = class {
1612
1754
 
1613
1755
  // src/core/client.ts
1614
1756
  var SDK_NAME = "@guidelytech/react-native";
1615
- var SDK_VERSION = "0.4.1";
1757
+ var SDK_VERSION = "0.5.0";
1616
1758
  var DEFAULT_API_URL = "https://guidelytech.com";
1617
1759
  var PAGE_SCOPED = /* @__PURE__ */ new Set(["page_load", "url_match", "element_click", "element_visible", "delay"]);
1618
1760
  var nowIso = () => (/* @__PURE__ */ new Date()).toISOString();
@@ -2477,6 +2619,10 @@ var GuidelyClient = class {
2477
2619
  if (this.activeSurvey?.guide.id !== guideId) return;
2478
2620
  this.finishSurvey(this.surveyView?.submitted ? "submitted" : "dismissed");
2479
2621
  }
2622
+ /** The visitor's audiences (from the last identify), for audience-targeted help articles. */
2623
+ getAudienceIds() {
2624
+ return this.state.audienceIds;
2625
+ }
2480
2626
  getResourceCenter() {
2481
2627
  const rc = this.config?.resourceCenter;
2482
2628
  return rc && rc.enabled ? rc : null;
@@ -2814,7 +2960,8 @@ function decodeEntities(s) {
2814
2960
  });
2815
2961
  }
2816
2962
  var BLOCK_TAGS = /* @__PURE__ */ new Set(["p", "div", "section", "article", "header", "footer", "h1", "h2", "h3", "h4", "h5", "h6", "li", "blockquote", "ul", "ol", "table", "tr", "pre"]);
2817
- var SKIP_TAGS = /* @__PURE__ */ new Set(["script", "style", "head", "title", "svg", "template"]);
2963
+ var SKIP_TAGS = /* @__PURE__ */ new Set(["script", "style", "head", "title", "svg", "template", "iframe", "video"]);
2964
+ var PROVIDER_LABEL = { youtube: "Watch on YouTube", vimeo: "Watch on Vimeo", loom: "Watch on Loom", wistia: "Watch video" };
2818
2965
  function attr(attrs, name) {
2819
2966
  const m = attrs.match(new RegExp(`\\b${name}\\s*=\\s*("([^"]*)"|'([^']*)'|([^\\s>]+))`, "i"));
2820
2967
  return m ? decodeEntities(m[2] ?? m[3] ?? m[4] ?? "") : void 0;
@@ -2826,6 +2973,10 @@ function parseHtml(html) {
2826
2973
  const lists = [];
2827
2974
  let current = null;
2828
2975
  let skipping = null;
2976
+ const media = (block) => {
2977
+ flush();
2978
+ blocks.push({ level: 1, runs: [], ...block });
2979
+ };
2829
2980
  const flush = () => {
2830
2981
  if (current) {
2831
2982
  const runs = current.runs;
@@ -2873,7 +3024,16 @@ function parseHtml(html) {
2873
3024
  continue;
2874
3025
  }
2875
3026
  if (SKIP_TAGS.has(tag)) {
2876
- if (!closing && !token.endsWith("/>")) skipping = tag;
3027
+ if (closing) continue;
3028
+ if (tag === "iframe") {
3029
+ const src = attr(attrs, "src");
3030
+ const embed = isAllowedEmbedSrc(src) ? toEmbedUrl(src) : null;
3031
+ if (embed) media({ kind: "video", href: embed.watchUrl, alt: PROVIDER_LABEL[embed.provider] });
3032
+ } else if (tag === "video") {
3033
+ const src = attr(attrs, "src");
3034
+ if (isSafeMediaUrl(src)) media({ kind: "video", href: src, alt: "Play video" });
3035
+ }
3036
+ if (!token.endsWith("/>")) skipping = tag;
2877
3037
  continue;
2878
3038
  }
2879
3039
  if (closing) {
@@ -2934,8 +3094,13 @@ function parseHtml(html) {
2934
3094
  default:
2935
3095
  if (BLOCK_TAGS.has(tag)) open("paragraph");
2936
3096
  else if (tag === "img") {
2937
- const alt = attr(attrs, "alt");
2938
- if (alt) push(alt);
3097
+ const src = attr(attrs, "src") ?? "";
3098
+ const alt = attr(attrs, "alt") ?? "";
3099
+ if (isSafeMediaUrl(src) || /^data:image\//i.test(src)) {
3100
+ const w = Number(attr(attrs, "width"));
3101
+ const h = Number(attr(attrs, "height"));
3102
+ media({ kind: "image", src, alt, ...w > 0 && h > 0 ? { width: w, height: h } : {} });
3103
+ } else if (alt) push(alt);
2939
3104
  }
2940
3105
  }
2941
3106
  }
@@ -2943,7 +3108,7 @@ function parseHtml(html) {
2943
3108
  return blocks;
2944
3109
  }
2945
3110
  function htmlToText(html) {
2946
- return parseHtml(html).map((b) => (b.marker ? `${b.marker} ` : "") + b.runs.map((r) => r.text).join("")).join("\n");
3111
+ return parseHtml(html).map((b) => b.kind === "image" || b.kind === "video" ? b.alt ?? "" : (b.marker ? `${b.marker} ` : "") + b.runs.map((r) => r.text).join("")).filter(Boolean).join("\n");
2947
3112
  }
2948
3113
 
2949
3114
  // src/ui/RichText.tsx
@@ -2960,11 +3125,32 @@ function runStyle(run, p) {
2960
3125
  color: run.href ? p.primary : void 0
2961
3126
  };
2962
3127
  }
3128
+ function BodyImage({ block, palette: p }) {
3129
+ const [ratio, setRatio] = (0, import_react.useState)(block.width && block.height ? block.width / block.height : 16 / 9);
3130
+ (0, import_react.useEffect)(() => {
3131
+ if (block.width && block.height) return;
3132
+ let live = true;
3133
+ import_react_native2.Image.getSize(block.src, (w, h) => live && w > 0 && h > 0 && setRatio(w / h), () => {
3134
+ });
3135
+ return () => {
3136
+ live = false;
3137
+ };
3138
+ }, [block.src, block.width, block.height]);
3139
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.Image, { source: { uri: block.src }, accessible: true, accessibilityRole: "image", accessibilityLabel: block.alt || void 0, resizeMode: "contain", style: [styles2.image, { aspectRatio: ratio, backgroundColor: p.surface }] });
3140
+ }
3141
+ function BodyVideo({ block, palette: p, onLink }) {
3142
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native2.Pressable, { accessibilityRole: "link", accessibilityLabel: block.alt, onPress: () => onLink(block.href), style: ({ pressed }) => [styles2.video, { borderColor: p.border, backgroundColor: p.surface, opacity: pressed ? 0.7 : 1 }], children: [
3143
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.Text, { style: [styles2.play, { color: p.primary }], children: "\u25B6" }),
3144
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.Text, { style: { color: p.primary, fontFamily: p.fontFamily, fontWeight: "600" }, children: block.alt })
3145
+ ] });
3146
+ }
2963
3147
  function RichText({ html, palette: p, onLink, size = 15 }) {
2964
3148
  const blocks = (0, import_react.useMemo)(() => parseHtml(html), [html]);
2965
3149
  if (!blocks.length) return null;
2966
3150
  const base = { color: p.text, fontSize: size, lineHeight: Math.round(size * 1.42), fontFamily: p.fontFamily };
2967
3151
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.View, { style: styles2.wrap, children: blocks.map((b, i) => {
3152
+ if (b.kind === "image") return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(BodyImage, { block: b, palette: p }, i);
3153
+ if (b.kind === "video") return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(BodyVideo, { block: b, palette: p, onLink }, i);
2968
3154
  const runs = b.runs.map((r, j) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.Text, { style: runStyle(r, p), onPress: r.href ? () => onLink(r.href) : void 0, accessibilityRole: r.href ? "link" : void 0, children: r.text }, j));
2969
3155
  if (b.kind === "list-item") {
2970
3156
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native2.View, { style: [styles2.li, { paddingLeft: (b.level - 1) * 14 }], children: [
@@ -2983,7 +3169,10 @@ var styles2 = import_react_native2.StyleSheet.create({
2983
3169
  wrap: { gap: 6 },
2984
3170
  li: { flexDirection: "row", gap: 6 },
2985
3171
  marker: { minWidth: 14 },
2986
- flex: { flex: 1 }
3172
+ flex: { flex: 1 },
3173
+ image: { width: "100%", borderRadius: 6 },
3174
+ video: { flexDirection: "row", alignItems: "center", gap: 8, paddingVertical: 12, paddingHorizontal: 14, borderWidth: 1, borderRadius: 8 },
3175
+ play: { fontSize: 14 }
2987
3176
  });
2988
3177
 
2989
3178
  // src/ui/Checklist.tsx
@@ -3333,18 +3522,15 @@ function Help({ store, client, palette: p }) {
3333
3522
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native4.Text, { accessibilityRole: "header", style: [styles4.sectionTitle, { color: p.muted }], children: title.toUpperCase() }),
3334
3523
  children
3335
3524
  ] }, title);
3336
- const collections = [...articles?.collections ?? []].sort((a, b) => a.order - b.order);
3337
- const loose = (articles?.articles ?? []).filter((a) => !a.collectionId || !collections.some((c) => c.id === a.collectionId));
3525
+ const audiences = client.getAudienceIds();
3526
+ const sections = resourceCenterSections(articles?.collections ?? [], articles?.articles ?? [], audiences);
3527
+ const listed = results?.filter((a) => isArticleListed(articles?.collections ?? [], a, audiences));
3338
3528
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react_native4.ScrollView, { contentContainerStyle: styles4.content, keyboardShouldPersistTaps: "handled", children: [
3339
3529
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native4.TextInput, { value: q, onChangeText: setQ, placeholder: "Search articles", placeholderTextColor: p.muted, accessibilityLabel: "Search help articles", returnKeyType: "search", style: [styles4.search, { color: p.text, backgroundColor: p.surface, borderRadius: Math.min(10, p.radius) }] }),
3340
- results ? section(`Results for \u201C${q.trim()}\u201D`, results.length ? results.map((a) => row(a.id, a.title, a.summary, () => open(a))) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native4.Text, { style: [styles4.rowSub, { color: p.muted }], children: "No articles found." })) : /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
3530
+ listed ? section(`Results for \u201C${q.trim()}\u201D`, listed.length ? listed.map((a) => row(a.id, a.title, a.summary, () => open(a))) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native4.Text, { style: [styles4.rowSub, { color: p.muted }], children: "No articles found." })) : /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
3341
3531
  recommended.length ? section("Recommended", recommended.map((g) => row(g.id, g.name, void 0, () => store.startGuide(g.key), "\u25B6"))) : null,
3342
3532
  !articles ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native4.ActivityIndicator, { color: p.muted }) : null,
3343
- collections.map((c) => {
3344
- const items = (articles?.articles ?? []).filter((a) => a.collectionId === c.id);
3345
- return items.length ? section(c.name, items.map((a) => row(a.id, a.title, a.summary, () => open(a)))) : null;
3346
- }),
3347
- loose.length ? section("Articles", loose.map((a) => row(a.id, a.title, a.summary, () => open(a)))) : null,
3533
+ sections.map((s) => section(s.collection ? s.path.join(" \u203A ") : "Articles", s.articles.map((a) => row(a.id, a.title, a.summary, () => open(a))))),
3348
3534
  rc?.faqs.length ? section(
3349
3535
  "FAQ",
3350
3536
  rc.faqs.map((f) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react_native4.View, { style: [styles4.row, styles4.faq, { borderColor: p.border }], children: [
@@ -3475,8 +3661,9 @@ function ResourceCenter({ client, palette: p, open, topInset, bottomInset }) {
3475
3661
  /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native5.Text, { accessibilityRole: "header", style: [styles5.sectionTitle, { color: p.muted }], children: title.toUpperCase() }),
3476
3662
  children
3477
3663
  ] }, title);
3478
- const collections = [...rc.collections].sort((a, b) => a.order - b.order);
3479
- const loose = rc.articles.filter((a) => !a.collectionId || !rc.collections.some((c) => c.id === a.collectionId));
3664
+ const audiences = client.getAudienceIds();
3665
+ const sections = resourceCenterSections(rc.collections, rc.articles, audiences);
3666
+ const listed = results?.filter((a) => isArticleListed(rc.collections, a, audiences));
3480
3667
  return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native5.Modal, { visible: open, animationType: "slide", presentationStyle: import_react_native5.Platform.OS === "ios" ? "pageSheet" : "fullScreen", onRequestClose: () => article ? setArticle(null) : close(), children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_react_native5.View, { style: [styles5.sheet, { backgroundColor: p.background, paddingTop: import_react_native5.Platform.OS === "ios" ? 8 : topInset }], children: [
3481
3668
  /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_react_native5.View, { style: [styles5.header, { borderColor: p.border }], children: [
3482
3669
  article ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native5.Pressable, { accessibilityRole: "button", accessibilityLabel: "Back", onPress: () => setArticle(null), hitSlop: 10, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native5.Text, { style: [styles5.back, { color: p.primary }], children: "\u2039 Back" }) }) : null,
@@ -3500,17 +3687,13 @@ function ResourceCenter({ client, palette: p, open, topInset, bottomInset }) {
3500
3687
  style: [styles5.search, { color: p.text, backgroundColor: p.surface, borderRadius: Math.min(10, p.radius) }]
3501
3688
  }
3502
3689
  ),
3503
- results ? section(
3690
+ listed ? section(
3504
3691
  `Results for \u201C${query.trim()}\u201D`,
3505
- results.length ? results.map((a) => row(a.id, a.title, a.summary, () => openArticle(a))) : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native5.Text, { style: [styles5.rowSub, { color: p.muted }], children: "No articles found." })
3692
+ listed.length ? listed.map((a) => row(a.id, a.title, a.summary, () => openArticle(a))) : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native5.Text, { style: [styles5.rowSub, { color: p.muted }], children: "No articles found." })
3506
3693
  ) : /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
3507
3694
  recommended.length ? section("Recommended", recommended.map((g) => row(g.id, g.name, void 0, () => (close(), client.startGuide(g.id)), "\u25B6"))) : null,
3508
3695
  checklists.length ? section("Checklists", checklists.map((g) => row(g.id, g.name, void 0, () => (close(), client.openChecklist(g.id)), "\u2611"))) : null,
3509
- collections.map((c) => {
3510
- const items = rc.articles.filter((a) => a.collectionId === c.id);
3511
- return items.length ? section(c.name, items.map((a) => row(a.id, a.title, a.summary, () => openArticle(a)))) : null;
3512
- }),
3513
- loose.length ? section("Articles", loose.map((a) => row(a.id, a.title, a.summary, () => openArticle(a)))) : null,
3696
+ sections.map((s) => section(s.collection ? s.path.join(" \u203A ") : "Articles", s.articles.map((a) => row(a.id, a.title, a.summary, () => openArticle(a))))),
3514
3697
  rc.faqs.length ? section(
3515
3698
  "FAQ",
3516
3699
  rc.faqs.map((f) => /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_react_native5.View, { style: [styles5.row, styles5.faq, { borderColor: p.border }], children: [