@eshal-bot/chat-widget 0.2.16 → 0.2.18

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.
@@ -88402,6 +88402,52 @@
88402
88402
  return typeof u === 'string' && /^https?:\/\//i.test(u) ? u : undefined;
88403
88403
  }
88404
88404
 
88405
+ /**
88406
+ * The same rule for images a card draws itself (A2UI card, list item,
88407
+ * avatar, gallery), which never pass through markdown. Returns `src`
88408
+ * unchanged while the policy is off (the default) or in report mode, and
88409
+ * undefined when enforce blocks the host, so the caller skips the <img>.
88410
+ */
88411
+ function policedImageSrc(src) {
88412
+ return src;
88413
+ }
88414
+ function rehypeImagePolicy() {
88415
+ return tree => {
88416
+ return;
88417
+ };
88418
+ }
88419
+
88420
+ const LINK_RE = /\[([^\]\n]+)\]\((https?:\/\/[^\s)]+)\)|(https?:\/\/[^\s<>()]+[^\s<>().,;:!?'"])/g;
88421
+ const LINK_STYLE = {
88422
+ color: '#1d4ed8',
88423
+ textDecoration: 'underline',
88424
+ overflowWrap: 'anywhere'
88425
+ };
88426
+ function linkifyText(text) {
88427
+ if (!text) return text;
88428
+ const out = [];
88429
+ let last = 0;
88430
+ let key = 0;
88431
+ for (const m of String(text).matchAll(LINK_RE)) {
88432
+ const idx = m.index ?? 0;
88433
+ const label = m[1] ?? m[3];
88434
+ const url = safeUrl(m[2] ?? m[3]);
88435
+ if (!url) continue;
88436
+ if (idx > last) out.push(text.slice(last, idx));
88437
+ out.push(/*#__PURE__*/jsxRuntimeExports.jsx("a", {
88438
+ href: url,
88439
+ target: "_blank",
88440
+ rel: "noopener noreferrer",
88441
+ style: LINK_STYLE,
88442
+ children: label
88443
+ }, key++));
88444
+ last = idx + m[0].length;
88445
+ }
88446
+ if (out.length === 0) return text;
88447
+ if (last < text.length) out.push(text.slice(last));
88448
+ return out;
88449
+ }
88450
+
88405
88451
  // Kept in its own tiny module (not A2uiBlock.jsx): MessageList imports it
88406
88452
  // eagerly, and A2uiBlock/MarkdownRenderer are lazy-loaded to keep charts and
88407
88453
  // react-markdown out of the widget's initial bundle.
@@ -88448,6 +88494,12 @@
88448
88494
  * bug this file fixes (12-06-2026).
88449
88495
  */
88450
88496
 
88497
+
88498
+ // Card images (banner, list item, avatar, gallery) go through the chat image
88499
+ // policy too. Unchanged while it is off, which is the default.
88500
+ const cardImageSrc = u => policedImageSrc(safeUrl(u));
88501
+ // True only when the policy itself drops a URL that would otherwise render.
88502
+ const cardImageBlocked = u => safeUrl(u) !== undefined && cardImageSrc(u) === undefined;
88451
88503
  function A2uiBlock({
88452
88504
  spec,
88453
88505
  isDark = false,
@@ -88821,7 +88873,7 @@
88821
88873
  whiteSpace: 'pre-wrap',
88822
88874
  wordBreak: 'break-word'
88823
88875
  },
88824
- children: String(spec.body)
88876
+ children: linkifyText(String(spec.body))
88825
88877
  })]
88826
88878
  });
88827
88879
  }
@@ -88882,7 +88934,7 @@
88882
88934
  const summary = String(spec.body || '').trim();
88883
88935
  const full = String(spec.details || '').trim();
88884
88936
  const hasMore = summary.length > 0 && full.length > summary.length + 2;
88885
- const imageUrl = safeUrl(spec.image);
88937
+ const imageUrl = cardImageSrc(spec.image);
88886
88938
  const pill = status && status.label ? _STATUS_PILL(isDark)[status.variant || 'success'] : null;
88887
88939
  return /*#__PURE__*/jsxRuntimeExports.jsxs("div", {
88888
88940
  style: {
@@ -88961,7 +89013,7 @@
88961
89013
  whiteSpace: 'pre-wrap',
88962
89014
  wordBreak: 'break-word'
88963
89015
  },
88964
- children: showFull ? full || summary : summary || full
89016
+ children: linkifyText(showFull ? full || summary : summary || full)
88965
89017
  }), hasMore && /*#__PURE__*/jsxRuntimeExports.jsx("button", {
88966
89018
  type: "button",
88967
89019
  onClick: () => setShowFull(v => !v),
@@ -89000,7 +89052,7 @@
89000
89052
  minWidth: 0,
89001
89053
  wordBreak: 'break-word'
89002
89054
  },
89003
- children: f.value
89055
+ children: typeof f.value === 'string' ? linkifyText(f.value) : f.value
89004
89056
  })]
89005
89057
  }, i))
89006
89058
  }), actions.length > 0 && /*#__PURE__*/jsxRuntimeExports.jsx("div", {
@@ -89541,7 +89593,7 @@
89541
89593
  whiteSpace: 'pre-wrap',
89542
89594
  wordBreak: 'break-word'
89543
89595
  },
89544
- children: String(spec.body)
89596
+ children: linkifyText(String(spec.body))
89545
89597
  }), /*#__PURE__*/jsxRuntimeExports.jsx(ButtonGroup, {
89546
89598
  spec: {
89547
89599
  buttons: lockedButtons
@@ -89696,8 +89748,8 @@
89696
89748
  display: 'flex',
89697
89749
  flexDirection: 'column'
89698
89750
  },
89699
- children: [safeUrl(item.image) && /*#__PURE__*/jsxRuntimeExports.jsx("img", {
89700
- src: safeUrl(item.image),
89751
+ children: [cardImageSrc(item.image) && /*#__PURE__*/jsxRuntimeExports.jsx("img", {
89752
+ src: cardImageSrc(item.image),
89701
89753
  alt: item.name || '',
89702
89754
  referrerPolicy: "no-referrer",
89703
89755
  onError: e => {
@@ -89811,8 +89863,8 @@
89811
89863
  fontWeight: 700,
89812
89864
  fontSize: 16
89813
89865
  },
89814
- children: safeUrl(spec.avatar) ? /*#__PURE__*/jsxRuntimeExports.jsx("img", {
89815
- src: safeUrl(spec.avatar),
89866
+ children: cardImageSrc(spec.avatar) ? /*#__PURE__*/jsxRuntimeExports.jsx("img", {
89867
+ src: cardImageSrc(spec.avatar),
89816
89868
  alt: spec.name || '',
89817
89869
  referrerPolicy: "no-referrer",
89818
89870
  onError: e => {
@@ -90250,8 +90302,8 @@
90250
90302
  border: '1px solid ' + (isDark ? '#374151' : '#e5e7eb'),
90251
90303
  background: isDark ? '#111827' : '#f9fafb'
90252
90304
  },
90253
- children: [/*#__PURE__*/jsxRuntimeExports.jsx("img", {
90254
- src: safeUrl(img.url),
90305
+ children: [!cardImageBlocked(img.url) && /*#__PURE__*/jsxRuntimeExports.jsx("img", {
90306
+ src: cardImageSrc(img.url),
90255
90307
  alt: img.caption || `Image ${i + 1}`,
90256
90308
  referrerPolicy: "no-referrer",
90257
90309
  onError: e => {
@@ -92232,8 +92284,9 @@
92232
92284
  // Skip the rehypeCitations plugin when the org opted out -
92233
92285
  // markers then render as plain text so nothing squeaks
92234
92286
  // through as a placeholder pill.
92287
+ // rehypeImagePolicy is a no-op unless the backend sends chatImagePolicy.
92235
92288
  ,
92236
- rehypePlugins: provideCitations ? [rehypeCitations] : [],
92289
+ rehypePlugins: provideCitations ? [rehypeCitations, rehypeImagePolicy] : [rehypeImagePolicy],
92237
92290
  components: {
92238
92291
  // Citation-marker → pill swap. Every `<span>` normally passes
92239
92292
  // through unchanged; when the HAST plugin has tagged it as a