@frontmcp/ui 1.12.0-rc.1 → 1.12.1

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.
@@ -1 +1 @@
1
- {"version":3,"file":"bridge-factory.d.ts","sourceRoot":"","sources":["../../../src/bridge/core/bridge-factory.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EACV,mBAAmB,EACnB,YAAY,EAGZ,WAAW,EACX,uBAAuB,EACvB,WAAW,EACX,eAAe,EACf,UAAU,EACX,MAAM,UAAU,CAAC;AAClB,OAAO,EAAmB,KAAK,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAU3E;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,qBAAa,cAAe,YAAW,uBAAuB;IAC5D,OAAO,CAAC,OAAO,CAAe;IAC9B,OAAO,CAAC,SAAS,CAAkB;IACnC,OAAO,CAAC,QAAQ,CAA8B;IAC9C,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,YAAY,CAA4B;IAEhD;;;;OAIG;gBACS,MAAM,GAAE,YAAiB,EAAE,QAAQ,CAAC,EAAE,eAAe;IAyBjE;;OAEG;IACH,IAAI,WAAW,IAAI,OAAO,CAEzB;IAED;;OAEG;IACH,IAAI,SAAS,IAAI,MAAM,GAAG,SAAS,CAElC;IAED;;OAEG;IACH,IAAI,YAAY,IAAI,mBAAmB,GAAG,SAAS,CAElD;IAMD;;;;;;OAMG;IACG,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAkBjC;;OAEG;YACW,aAAa;IAiC3B;;OAEG;IACH,OAAO,IAAI,IAAI;IAaf;;OAEG;IACH,UAAU,IAAI,eAAe,GAAG,SAAS;IAIzC;;;OAGG;IACH,aAAa,CAAC,GAAG,EAAE,MAAM,mBAAmB,GAAG,OAAO;IAQtD;;OAEG;IACH,QAAQ,IAAI,OAAO,GAAG,MAAM;IAK5B;;OAEG;IACH,cAAc,IAAI,WAAW;IAK7B;;OAEG;IACH,YAAY,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAKvC;;OAEG;IACH,aAAa,IAAI,OAAO;IAKxB;;OAEG;IACH,oBAAoB,CAAC,CAAC,GAAG,OAAO,KAAK,CAAC,GAAG,IAAI;IAK7C;;OAEG;IACH,cAAc,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAKzC;;OAEG;IACH,cAAc,IAAI,WAAW;IAS7B;;;;OAIG;IACG,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC;IAQ7E;;;OAGG;IACG,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAQjD;;;OAGG;IACG,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAK1C;;;OAGG;IACG,kBAAkB,CAAC,IAAI,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAK1D;;;OAGG;IACG,OAAO,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;IAK9C;;OAEG;IACG,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC;IAKnC;;;OAGG;IACH,cAAc,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI;IASpD;;;;OAIG;IACH,eAAe,CAAC,QAAQ,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC,WAAW,CAAC,KAAK,IAAI,GAAG,MAAM,IAAI;IAK9E;;;;OAIG;IACH,YAAY,CAAC,QAAQ,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,IAAI,GAAG,MAAM,IAAI;IAS7D;;;OAGG;IACH,OAAO,CAAC,kBAAkB;IAO1B;;OAEG;IACH,OAAO,CAAC,YAAY;IAkBpB;;OAEG;IACH,OAAO,CAAC,UAAU;IAWlB;;OAEG;IACH,OAAO,CAAC,IAAI;CAKb;AAED;;;;;;;;;GASG;AACH,wBAAsB,YAAY,CAAC,MAAM,CAAC,EAAE,YAAY,EAAE,QAAQ,CAAC,EAAE,eAAe,GAAG,OAAO,CAAC,cAAc,CAAC,CAI7G;AAQD;;;GAGG;AACH,wBAAsB,eAAe,CAAC,MAAM,CAAC,EAAE,YAAY,GAAG,OAAO,CAAC,cAAc,CAAC,CAMpF;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,IAAI,IAAI,CAKxC"}
1
+ {"version":3,"file":"bridge-factory.d.ts","sourceRoot":"","sources":["../../../src/bridge/core/bridge-factory.ts"],"names":[],"mappings":"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"}
package/bridge/index.js CHANGED
@@ -284,6 +284,16 @@ var DEFAULT_CONFIG = {
284
284
  debug: false,
285
285
  initTimeout: 5e3
286
286
  };
287
+ function limitToTrustedOrigins(adapterConfig, trustedOrigins) {
288
+ const ownOrigins = adapterConfig?.options?.["trustedOrigins"];
289
+ const origins = Array.isArray(ownOrigins) && ownOrigins.length > 0 ? ownOrigins.filter((origin) => trustedOrigins.includes(origin)) : trustedOrigins;
290
+ if (origins.length === 0) {
291
+ throw new Error(
292
+ 'The bridge trustedOrigins and adapterConfigs["ext-apps"].options.trustedOrigins have no origin in common. List the host origin in both, or set only one of them.'
293
+ );
294
+ }
295
+ return { ...adapterConfig, options: { ...adapterConfig?.options, trustedOrigins: origins } };
296
+ }
287
297
  var FrontMcpBridge = class {
288
298
  _config;
289
299
  _registry;
@@ -309,6 +319,13 @@ var FrontMcpBridge = class {
309
319
  this._registry.configure(id, adapterConfig);
310
320
  }
311
321
  }
322
+ if (this._config.trustedOrigins?.length) {
323
+ const extAppsConfig = limitToTrustedOrigins(
324
+ this._config.adapterConfigs?.["ext-apps"],
325
+ this._config.trustedOrigins
326
+ );
327
+ this._registry.configure("ext-apps", extAppsConfig);
328
+ }
312
329
  }
313
330
  // ============================================
314
331
  // Public Properties
@@ -1308,6 +1325,7 @@ var ExtAppsAdapter = class extends BaseAdapter {
1308
1325
  * Handle incoming postMessage events.
1309
1326
  */
1310
1327
  _handleMessage(event) {
1328
+ if (event.source !== window.parent) return;
1311
1329
  if (!this._isOriginTrusted(event.origin)) {
1312
1330
  return;
1313
1331
  }
@@ -232,6 +232,16 @@ var DEFAULT_CONFIG = {
232
232
  debug: false,
233
233
  initTimeout: 5e3
234
234
  };
235
+ function limitToTrustedOrigins(adapterConfig, trustedOrigins) {
236
+ const ownOrigins = adapterConfig?.options?.["trustedOrigins"];
237
+ const origins = Array.isArray(ownOrigins) && ownOrigins.length > 0 ? ownOrigins.filter((origin) => trustedOrigins.includes(origin)) : trustedOrigins;
238
+ if (origins.length === 0) {
239
+ throw new Error(
240
+ 'The bridge trustedOrigins and adapterConfigs["ext-apps"].options.trustedOrigins have no origin in common. List the host origin in both, or set only one of them.'
241
+ );
242
+ }
243
+ return { ...adapterConfig, options: { ...adapterConfig?.options, trustedOrigins: origins } };
244
+ }
235
245
  var FrontMcpBridge = class {
236
246
  _config;
237
247
  _registry;
@@ -257,6 +267,13 @@ var FrontMcpBridge = class {
257
267
  this._registry.configure(id, adapterConfig);
258
268
  }
259
269
  }
270
+ if (this._config.trustedOrigins?.length) {
271
+ const extAppsConfig = limitToTrustedOrigins(
272
+ this._config.adapterConfigs?.["ext-apps"],
273
+ this._config.trustedOrigins
274
+ );
275
+ this._registry.configure("ext-apps", extAppsConfig);
276
+ }
260
277
  }
261
278
  // ============================================
262
279
  // Public Properties
@@ -1256,6 +1273,7 @@ var ExtAppsAdapter = class extends BaseAdapter {
1256
1273
  * Handle incoming postMessage events.
1257
1274
  */
1258
1275
  _handleMessage(event) {
1276
+ if (event.source !== window.parent) return;
1259
1277
  if (!this._isOriginTrusted(event.origin)) {
1260
1278
  return;
1261
1279
  }
package/esm/index.mjs CHANGED
@@ -226,6 +226,16 @@ var DEFAULT_CONFIG = {
226
226
  debug: false,
227
227
  initTimeout: 5e3
228
228
  };
229
+ function limitToTrustedOrigins(adapterConfig, trustedOrigins) {
230
+ const ownOrigins = adapterConfig?.options?.["trustedOrigins"];
231
+ const origins = Array.isArray(ownOrigins) && ownOrigins.length > 0 ? ownOrigins.filter((origin) => trustedOrigins.includes(origin)) : trustedOrigins;
232
+ if (origins.length === 0) {
233
+ throw new Error(
234
+ 'The bridge trustedOrigins and adapterConfigs["ext-apps"].options.trustedOrigins have no origin in common. List the host origin in both, or set only one of them.'
235
+ );
236
+ }
237
+ return { ...adapterConfig, options: { ...adapterConfig?.options, trustedOrigins: origins } };
238
+ }
229
239
  var FrontMcpBridge = class {
230
240
  _config;
231
241
  _registry;
@@ -251,6 +261,13 @@ var FrontMcpBridge = class {
251
261
  this._registry.configure(id, adapterConfig);
252
262
  }
253
263
  }
264
+ if (this._config.trustedOrigins?.length) {
265
+ const extAppsConfig = limitToTrustedOrigins(
266
+ this._config.adapterConfigs?.["ext-apps"],
267
+ this._config.trustedOrigins
268
+ );
269
+ this._registry.configure("ext-apps", extAppsConfig);
270
+ }
254
271
  }
255
272
  // ============================================
256
273
  // Public Properties
package/esm/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@frontmcp/ui",
3
- "version": "1.12.0-rc.1",
3
+ "version": "1.12.1",
4
4
  "description": "React components, bridge hooks and content renderers (built on MUI) for FrontMCP tool widgets in ChatGPT, Claude and other MCP Apps hosts",
5
5
  "author": "AgentFront <info@agentfront.dev>",
6
6
  "homepage": "https://frontmcp.dev/reference/ui",
@@ -436,7 +436,7 @@
436
436
  }
437
437
  },
438
438
  "dependencies": {
439
- "@frontmcp/uipack": "1.12.0-rc.1"
439
+ "@frontmcp/uipack": "1.12.1"
440
440
  },
441
441
  "devDependencies": {
442
442
  "@types/dompurify": "^3.0.0",
@@ -226,6 +226,16 @@ var DEFAULT_CONFIG = {
226
226
  debug: false,
227
227
  initTimeout: 5e3
228
228
  };
229
+ function limitToTrustedOrigins(adapterConfig, trustedOrigins) {
230
+ const ownOrigins = adapterConfig?.options?.["trustedOrigins"];
231
+ const origins = Array.isArray(ownOrigins) && ownOrigins.length > 0 ? ownOrigins.filter((origin) => trustedOrigins.includes(origin)) : trustedOrigins;
232
+ if (origins.length === 0) {
233
+ throw new Error(
234
+ 'The bridge trustedOrigins and adapterConfigs["ext-apps"].options.trustedOrigins have no origin in common. List the host origin in both, or set only one of them.'
235
+ );
236
+ }
237
+ return { ...adapterConfig, options: { ...adapterConfig?.options, trustedOrigins: origins } };
238
+ }
229
239
  var FrontMcpBridge = class {
230
240
  _config;
231
241
  _registry;
@@ -251,6 +261,13 @@ var FrontMcpBridge = class {
251
261
  this._registry.configure(id, adapterConfig);
252
262
  }
253
263
  }
264
+ if (this._config.trustedOrigins?.length) {
265
+ const extAppsConfig = limitToTrustedOrigins(
266
+ this._config.adapterConfigs?.["ext-apps"],
267
+ this._config.trustedOrigins
268
+ );
269
+ this._registry.configure("ext-apps", extAppsConfig);
270
+ }
254
271
  }
255
272
  // ============================================
256
273
  // Public Properties
@@ -1037,6 +1037,7 @@ import Paper from "@mui/material/Paper";
1037
1037
  import { styled as styled6 } from "@mui/material/styles";
1038
1038
  import Typography4 from "@mui/material/Typography";
1039
1039
  import React7, { useEffect as useEffect5 } from "react";
1040
+ import { sanitizeHtml } from "@frontmcp/uipack/shell";
1040
1041
  var LEAFLET_CSS_URL = "https://esm.sh/leaflet@1.9.4/dist/leaflet.css";
1041
1042
  var LEAFLET_CSS_ID = "fmcp-leaflet-css";
1042
1043
  var DEFAULT_TILE_URL = "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png";
@@ -1087,7 +1088,12 @@ function parseMapConfig(content) {
1087
1088
  geojson: parsed
1088
1089
  };
1089
1090
  }
1090
- return parsed;
1091
+ const config = parsed;
1092
+ const tileLayer = config.tileLayer;
1093
+ if (tileLayer?.attribution !== void 0) {
1094
+ return { ...config, tileLayer: { ...tileLayer, attribution: sanitizeHtml(String(tileLayer.attribution)) } };
1095
+ }
1096
+ return config;
1091
1097
  }
1092
1098
  function MapView({ config, className }) {
1093
1099
  useEffect5(() => {
@@ -1158,16 +1164,76 @@ import Alert5 from "@mui/material/Alert";
1158
1164
  import Box7 from "@mui/material/Box";
1159
1165
  import { styled as styled7 } from "@mui/material/styles";
1160
1166
  import React8, { useEffect as useEffect6, useMemo as useMemo6 } from "react";
1167
+
1168
+ // libs/ui/src/renderer/math/math-spans.ts
1169
+ function lastEscapeOf(source, closer) {
1170
+ let last = -1;
1171
+ for (let index = 0; index < source.length; index++) {
1172
+ if (source[index] !== "\\") continue;
1173
+ if (source[index + 1] === closer) last = index;
1174
+ index++;
1175
+ }
1176
+ return last;
1177
+ }
1178
+ function lastEnvironmentEnd(source) {
1179
+ const lastBrace = source.lastIndexOf("}");
1180
+ let last = -1;
1181
+ for (let index = 0; index < source.length; index++) {
1182
+ if (source[index] !== "\\") continue;
1183
+ const nameStart = index + 5;
1184
+ if (source.startsWith("end{", index + 1) && nameStart < lastBrace && source[nameStart] !== "}") last = index;
1185
+ index++;
1186
+ }
1187
+ return last;
1188
+ }
1189
+ function hasEscapedPair(source, opener, closer) {
1190
+ const start = source.indexOf(`\\${opener}`);
1191
+ return start !== -1 && lastEscapeOf(source, closer) >= start + 2;
1192
+ }
1193
+ function createMathSpanFinder(source) {
1194
+ const bracketClose = lastEscapeOf(source, "]");
1195
+ const parenClose = lastEscapeOf(source, ")");
1196
+ const environmentEnd = lastEnvironmentEnd(source);
1197
+ let nextDoubleDollar = -2;
1198
+ return (index) => {
1199
+ if (source.startsWith("$$", index)) {
1200
+ if (nextDoubleDollar !== -1 && nextDoubleDollar < index + 2) nextDoubleDollar = source.indexOf("$$", index + 2);
1201
+ if (nextDoubleDollar > index + 2) return nextDoubleDollar + 2;
1202
+ }
1203
+ if (source[index] === "$") {
1204
+ let close = index + 1;
1205
+ while (close < source.length && source[close] !== "$" && source[close] !== "\n") close++;
1206
+ if (source[close] === "$" && close > index + 1) return close + 1;
1207
+ }
1208
+ if (source.startsWith("\\[", index) && bracketClose >= index + 2) return bracketClose + 2;
1209
+ if (source.startsWith("\\(", index) && parenClose >= index + 2) return parenClose + 2;
1210
+ if (source.startsWith("\\begin{", index) && environmentEnd >= index + 7) {
1211
+ return source.indexOf("}", environmentEnd + 6) + 1;
1212
+ }
1213
+ return -1;
1214
+ };
1215
+ }
1216
+ function findMathSpans(source) {
1217
+ const findSpanEnd = createMathSpanFinder(source);
1218
+ const spans = [];
1219
+ for (let index = 0; index < source.length; index++) {
1220
+ const end = findSpanEnd(index);
1221
+ if (end === -1) continue;
1222
+ spans.push({ start: index, end });
1223
+ index = end - 1;
1224
+ }
1225
+ return spans;
1226
+ }
1227
+
1228
+ // libs/ui/src/renderer/math/index.ts
1161
1229
  var KATEX_CSS_URL = "https://esm.sh/katex@0.16.11/dist/katex.min.css";
1162
1230
  var KATEX_CSS_ID = "fmcp-katex-css";
1163
1231
  var MATH_DISPLAY = /\$\$(?:[^$]|\$(?!\$))+\$\$/s;
1164
1232
  var MATH_INLINE = /\$[^$\n]+?\$/;
1165
- var MATH_BRACKET_DISPLAY = /\\\[(?:[^\\]|\\.)*\\\]/s;
1166
- var MATH_PAREN_INLINE = /\\\((?:[^\\]|\\.)*\\\)/s;
1167
1233
  var MATH_BEGIN = /\\begin\{(?:equation|align|gather|matrix|pmatrix|bmatrix|cases)\}/;
1168
1234
  function isMath(content) {
1169
1235
  const trimmed = content.trim();
1170
- return MATH_DISPLAY.test(trimmed) || MATH_INLINE.test(trimmed) || MATH_BRACKET_DISPLAY.test(trimmed) || MATH_PAREN_INLINE.test(trimmed) || MATH_BEGIN.test(trimmed);
1236
+ return MATH_DISPLAY.test(trimmed) || MATH_INLINE.test(trimmed) || hasEscapedPair(trimmed, "[", "]") || hasEscapedPair(trimmed, "(", ")") || MATH_BEGIN.test(trimmed);
1171
1237
  }
1172
1238
  var lazyKatex = createLazyImport("katex", async () => {
1173
1239
  const mod = await runtimeImportWithFallback("katex", esmShUrl("katex@0.16"));
@@ -1201,14 +1267,12 @@ var MathInline = styled7("span", {
1201
1267
  });
1202
1268
  function parseMathContent(source) {
1203
1269
  const segments = [];
1204
- const mathRegex = /(\$\$(?:[^$]|\$(?!\$))+\$\$|\$[^$\n]+?\$|\\\[(?:[^\\]|\\.)*\\\]|\\\((?:[^\\]|\\.)*\\\)|\\begin\{(?:[^\\]|\\.)*\\end\{[^}]+\})/gs;
1205
1270
  let lastIndex = 0;
1206
- let match;
1207
- while ((match = mathRegex.exec(source)) !== null) {
1208
- if (match.index > lastIndex) {
1209
- segments.push({ type: "text", content: source.slice(lastIndex, match.index) });
1271
+ for (const span of findMathSpans(source)) {
1272
+ if (span.start > lastIndex) {
1273
+ segments.push({ type: "text", content: source.slice(lastIndex, span.start) });
1210
1274
  }
1211
- const raw = match[0];
1275
+ const raw = source.slice(span.start, span.end);
1212
1276
  if (raw.startsWith("$$")) {
1213
1277
  segments.push({ type: "display", content: raw.slice(2, -2).trim() });
1214
1278
  } else if (raw.startsWith("\\[")) {
@@ -1220,7 +1284,7 @@ function parseMathContent(source) {
1220
1284
  } else if (raw.startsWith("$")) {
1221
1285
  segments.push({ type: "inline", content: raw.slice(1, -1).trim() });
1222
1286
  }
1223
- lastIndex = match.index + raw.length;
1287
+ lastIndex = span.end;
1224
1288
  }
1225
1289
  if (lastIndex < source.length) {
1226
1290
  segments.push({ type: "text", content: source.slice(lastIndex) });
@@ -1299,6 +1363,7 @@ import TableHead2 from "@mui/material/TableHead";
1299
1363
  import TableRow2 from "@mui/material/TableRow";
1300
1364
  import Typography5 from "@mui/material/Typography";
1301
1365
  import React9, { useMemo as useMemo7 } from "react";
1366
+ import { sanitizeHtml as sanitizeHtml2 } from "@frontmcp/uipack/shell";
1302
1367
  var lazyReactMarkdown = createLazyImport(
1303
1368
  "react-markdown",
1304
1369
  async () => {
@@ -1321,6 +1386,16 @@ var lazyRehypeRaw = createLazyImport("rehype-raw", async () => {
1321
1386
  const mod = await runtimeImportWithFallback("rehype-raw", esmShUrl("rehype-raw@7"));
1322
1387
  return mod;
1323
1388
  });
1389
+ var RAW_HTML_POLICY = { maxMarkupLength: Number.MAX_SAFE_INTEGER };
1390
+ function rehypeSanitizeRawHtml() {
1391
+ return (tree) => {
1392
+ const pending = [tree];
1393
+ for (let node = pending.pop(); node; node = pending.pop()) {
1394
+ if (node.type === "raw") node.value = sanitizeHtml2(node.value ?? "", RAW_HTML_POLICY);
1395
+ for (const child of node.children ?? []) pending.push(child);
1396
+ }
1397
+ };
1398
+ }
1324
1399
  var MarkdownRoot = styled8(Box8, {
1325
1400
  name: "FrontMcpMarkdown",
1326
1401
  slot: "Root"
@@ -1375,7 +1450,7 @@ var Blockquote = styled8("blockquote", {
1375
1450
  "& > p": { margin: 0 }
1376
1451
  }));
1377
1452
  function createMuiComponents() {
1378
- return {
1453
+ const components = {
1379
1454
  h1: (props) => React9.createElement(Typography5, { ...props, variant: "h4", gutterBottom: true }),
1380
1455
  h2: (props) => React9.createElement(Typography5, { ...props, variant: "h5", gutterBottom: true }),
1381
1456
  h3: (props) => React9.createElement(Typography5, { ...props, variant: "h6", gutterBottom: true }),
@@ -1397,6 +1472,12 @@ function createMuiComponents() {
1397
1472
  th: (props) => React9.createElement(TableCell2, { ...props, sx: { fontWeight: 600 } }),
1398
1473
  td: (props) => React9.createElement(TableCell2, props)
1399
1474
  };
1475
+ return Object.fromEntries(
1476
+ Object.entries(components).map(([tag, render]) => [
1477
+ tag,
1478
+ ({ component: _component, as: _as, ...props }) => render(props)
1479
+ ])
1480
+ );
1400
1481
  }
1401
1482
  function MarkdownView({ content, className }) {
1402
1483
  const components = useMemo7(() => createMuiComponents(), []);
@@ -1410,7 +1491,7 @@ function MarkdownView({ content, className }) {
1410
1491
  const rehypePlugins = [];
1411
1492
  if (remarkGfmMod) remarkPlugins.push(remarkGfmMod.default);
1412
1493
  if (rehypeHighlightMod) rehypePlugins.push(rehypeHighlightMod.default);
1413
- if (rehypeRawMod) rehypePlugins.push(rehypeRawMod.default);
1494
+ if (rehypeRawMod) rehypePlugins.push(rehypeSanitizeRawHtml, rehypeRawMod.default);
1414
1495
  return React9.createElement(
1415
1496
  MarkdownRoot,
1416
1497
  { className },
@@ -1755,8 +1836,9 @@ var lazyReactPdf = createLazyImport("react-pdf", async () => {
1755
1836
  }
1756
1837
  return mod;
1757
1838
  });
1839
+ var PDF_DATA_URI = /^data:application\/pdf[;,]/i;
1758
1840
  function toDataUri(content) {
1759
- if (content.startsWith("data:")) return content;
1841
+ if (content.startsWith("data:")) return PDF_DATA_URI.test(content) ? content : void 0;
1760
1842
  if (content.startsWith("%PDF-")) {
1761
1843
  const base64 = typeof btoa === "function" ? btoa(content) : Buffer.from(content).toString("base64");
1762
1844
  return `data:application/pdf;base64,${base64}`;
@@ -1805,6 +1887,13 @@ function PdfView({ content, className, title }) {
1805
1887
  const goToNext = useCallback3(() => setPageNumber((p) => Math.min(numPages, p + 1)), []);
1806
1888
  const zoomIn = useCallback3(() => setScale((s) => Math.min(3, s + 0.25)), []);
1807
1889
  const zoomOut = useCallback3(() => setScale((s) => Math.max(0.25, s - 0.25)), []);
1890
+ if (!dataUri) {
1891
+ return React12.createElement(
1892
+ Typography6,
1893
+ { className, variant: "body2", color: "error" },
1894
+ "This content is not a PDF document."
1895
+ );
1896
+ }
1808
1897
  if (!reactPdf) {
1809
1898
  return React12.createElement("iframe", {
1810
1899
  className: className ?? "fmcp-pdf-content",
@@ -5,6 +5,7 @@ import Paper from "@mui/material/Paper";
5
5
  import { styled } from "@mui/material/styles";
6
6
  import Typography from "@mui/material/Typography";
7
7
  import React, { useEffect as useEffect2 } from "react";
8
+ import { sanitizeHtml } from "@frontmcp/uipack/shell";
8
9
 
9
10
  // libs/ui/src/renderer/common/inject-stylesheet.ts
10
11
  function injectStylesheet(href, id) {
@@ -145,7 +146,12 @@ function parseMapConfig(content) {
145
146
  geojson: parsed
146
147
  };
147
148
  }
148
- return parsed;
149
+ const config = parsed;
150
+ const tileLayer = config.tileLayer;
151
+ if (tileLayer?.attribution !== void 0) {
152
+ return { ...config, tileLayer: { ...tileLayer, attribution: sanitizeHtml(String(tileLayer.attribution)) } };
153
+ }
154
+ return config;
149
155
  }
150
156
  function MapView({ config, className }) {
151
157
  useEffect2(() => {
@@ -92,17 +92,75 @@ function useLazyModule(lazy) {
92
92
  return lazy.get();
93
93
  }
94
94
 
95
+ // libs/ui/src/renderer/math/math-spans.ts
96
+ function lastEscapeOf(source, closer) {
97
+ let last = -1;
98
+ for (let index = 0; index < source.length; index++) {
99
+ if (source[index] !== "\\") continue;
100
+ if (source[index + 1] === closer) last = index;
101
+ index++;
102
+ }
103
+ return last;
104
+ }
105
+ function lastEnvironmentEnd(source) {
106
+ const lastBrace = source.lastIndexOf("}");
107
+ let last = -1;
108
+ for (let index = 0; index < source.length; index++) {
109
+ if (source[index] !== "\\") continue;
110
+ const nameStart = index + 5;
111
+ if (source.startsWith("end{", index + 1) && nameStart < lastBrace && source[nameStart] !== "}") last = index;
112
+ index++;
113
+ }
114
+ return last;
115
+ }
116
+ function hasEscapedPair(source, opener, closer) {
117
+ const start = source.indexOf(`\\${opener}`);
118
+ return start !== -1 && lastEscapeOf(source, closer) >= start + 2;
119
+ }
120
+ function createMathSpanFinder(source) {
121
+ const bracketClose = lastEscapeOf(source, "]");
122
+ const parenClose = lastEscapeOf(source, ")");
123
+ const environmentEnd = lastEnvironmentEnd(source);
124
+ let nextDoubleDollar = -2;
125
+ return (index) => {
126
+ if (source.startsWith("$$", index)) {
127
+ if (nextDoubleDollar !== -1 && nextDoubleDollar < index + 2) nextDoubleDollar = source.indexOf("$$", index + 2);
128
+ if (nextDoubleDollar > index + 2) return nextDoubleDollar + 2;
129
+ }
130
+ if (source[index] === "$") {
131
+ let close = index + 1;
132
+ while (close < source.length && source[close] !== "$" && source[close] !== "\n") close++;
133
+ if (source[close] === "$" && close > index + 1) return close + 1;
134
+ }
135
+ if (source.startsWith("\\[", index) && bracketClose >= index + 2) return bracketClose + 2;
136
+ if (source.startsWith("\\(", index) && parenClose >= index + 2) return parenClose + 2;
137
+ if (source.startsWith("\\begin{", index) && environmentEnd >= index + 7) {
138
+ return source.indexOf("}", environmentEnd + 6) + 1;
139
+ }
140
+ return -1;
141
+ };
142
+ }
143
+ function findMathSpans(source) {
144
+ const findSpanEnd = createMathSpanFinder(source);
145
+ const spans = [];
146
+ for (let index = 0; index < source.length; index++) {
147
+ const end = findSpanEnd(index);
148
+ if (end === -1) continue;
149
+ spans.push({ start: index, end });
150
+ index = end - 1;
151
+ }
152
+ return spans;
153
+ }
154
+
95
155
  // libs/ui/src/renderer/math/index.ts
96
156
  var KATEX_CSS_URL = "https://esm.sh/katex@0.16.11/dist/katex.min.css";
97
157
  var KATEX_CSS_ID = "fmcp-katex-css";
98
158
  var MATH_DISPLAY = /\$\$(?:[^$]|\$(?!\$))+\$\$/s;
99
159
  var MATH_INLINE = /\$[^$\n]+?\$/;
100
- var MATH_BRACKET_DISPLAY = /\\\[(?:[^\\]|\\.)*\\\]/s;
101
- var MATH_PAREN_INLINE = /\\\((?:[^\\]|\\.)*\\\)/s;
102
160
  var MATH_BEGIN = /\\begin\{(?:equation|align|gather|matrix|pmatrix|bmatrix|cases)\}/;
103
161
  function isMath(content) {
104
162
  const trimmed = content.trim();
105
- return MATH_DISPLAY.test(trimmed) || MATH_INLINE.test(trimmed) || MATH_BRACKET_DISPLAY.test(trimmed) || MATH_PAREN_INLINE.test(trimmed) || MATH_BEGIN.test(trimmed);
163
+ return MATH_DISPLAY.test(trimmed) || MATH_INLINE.test(trimmed) || hasEscapedPair(trimmed, "[", "]") || hasEscapedPair(trimmed, "(", ")") || MATH_BEGIN.test(trimmed);
106
164
  }
107
165
  var lazyKatex = createLazyImport("katex", async () => {
108
166
  const mod = await runtimeImportWithFallback("katex", esmShUrl("katex@0.16"));
@@ -136,14 +194,12 @@ var MathInline = styled("span", {
136
194
  });
137
195
  function parseMathContent(source) {
138
196
  const segments = [];
139
- const mathRegex = /(\$\$(?:[^$]|\$(?!\$))+\$\$|\$[^$\n]+?\$|\\\[(?:[^\\]|\\.)*\\\]|\\\((?:[^\\]|\\.)*\\\)|\\begin\{(?:[^\\]|\\.)*\\end\{[^}]+\})/gs;
140
197
  let lastIndex = 0;
141
- let match;
142
- while ((match = mathRegex.exec(source)) !== null) {
143
- if (match.index > lastIndex) {
144
- segments.push({ type: "text", content: source.slice(lastIndex, match.index) });
198
+ for (const span of findMathSpans(source)) {
199
+ if (span.start > lastIndex) {
200
+ segments.push({ type: "text", content: source.slice(lastIndex, span.start) });
145
201
  }
146
- const raw = match[0];
202
+ const raw = source.slice(span.start, span.end);
147
203
  if (raw.startsWith("$$")) {
148
204
  segments.push({ type: "display", content: raw.slice(2, -2).trim() });
149
205
  } else if (raw.startsWith("\\[")) {
@@ -155,7 +211,7 @@ function parseMathContent(source) {
155
211
  } else if (raw.startsWith("$")) {
156
212
  segments.push({ type: "inline", content: raw.slice(1, -1).trim() });
157
213
  }
158
- lastIndex = match.index + raw.length;
214
+ lastIndex = span.end;
159
215
  }
160
216
  if (lastIndex < source.length) {
161
217
  segments.push({ type: "text", content: source.slice(lastIndex) });
@@ -9,6 +9,7 @@ import TableHead from "@mui/material/TableHead";
9
9
  import TableRow from "@mui/material/TableRow";
10
10
  import Typography from "@mui/material/Typography";
11
11
  import React, { useMemo } from "react";
12
+ import { sanitizeHtml } from "@frontmcp/uipack/shell";
12
13
 
13
14
  // libs/ui/src/renderer/common/lazy-import.ts
14
15
  var ESM_SH_BASE = "https://esm.sh/";
@@ -109,6 +110,16 @@ var lazyRehypeRaw = createLazyImport("rehype-raw", async () => {
109
110
  const mod = await runtimeImportWithFallback("rehype-raw", esmShUrl("rehype-raw@7"));
110
111
  return mod;
111
112
  });
113
+ var RAW_HTML_POLICY = { maxMarkupLength: Number.MAX_SAFE_INTEGER };
114
+ function rehypeSanitizeRawHtml() {
115
+ return (tree) => {
116
+ const pending = [tree];
117
+ for (let node = pending.pop(); node; node = pending.pop()) {
118
+ if (node.type === "raw") node.value = sanitizeHtml(node.value ?? "", RAW_HTML_POLICY);
119
+ for (const child of node.children ?? []) pending.push(child);
120
+ }
121
+ };
122
+ }
112
123
  var MarkdownRoot = styled(Box, {
113
124
  name: "FrontMcpMarkdown",
114
125
  slot: "Root"
@@ -163,7 +174,7 @@ var Blockquote = styled("blockquote", {
163
174
  "& > p": { margin: 0 }
164
175
  }));
165
176
  function createMuiComponents() {
166
- return {
177
+ const components = {
167
178
  h1: (props) => React.createElement(Typography, { ...props, variant: "h4", gutterBottom: true }),
168
179
  h2: (props) => React.createElement(Typography, { ...props, variant: "h5", gutterBottom: true }),
169
180
  h3: (props) => React.createElement(Typography, { ...props, variant: "h6", gutterBottom: true }),
@@ -185,6 +196,12 @@ function createMuiComponents() {
185
196
  th: (props) => React.createElement(TableCell, { ...props, sx: { fontWeight: 600 } }),
186
197
  td: (props) => React.createElement(TableCell, props)
187
198
  };
199
+ return Object.fromEntries(
200
+ Object.entries(components).map(([tag, render]) => [
201
+ tag,
202
+ ({ component: _component, as: _as, ...props }) => render(props)
203
+ ])
204
+ );
188
205
  }
189
206
  function MarkdownView({ content, className }) {
190
207
  const components = useMemo(() => createMuiComponents(), []);
@@ -198,7 +215,7 @@ function MarkdownView({ content, className }) {
198
215
  const rehypePlugins = [];
199
216
  if (remarkGfmMod) remarkPlugins.push(remarkGfmMod.default);
200
217
  if (rehypeHighlightMod) rehypePlugins.push(rehypeHighlightMod.default);
201
- if (rehypeRawMod) rehypePlugins.push(rehypeRawMod.default);
218
+ if (rehypeRawMod) rehypePlugins.push(rehypeSanitizeRawHtml, rehypeRawMod.default);
202
219
  return React.createElement(
203
220
  MarkdownRoot,
204
221
  { className },
@@ -94,8 +94,9 @@ var lazyReactPdf = createLazyImport("react-pdf", async () => {
94
94
  }
95
95
  return mod;
96
96
  });
97
+ var PDF_DATA_URI = /^data:application\/pdf[;,]/i;
97
98
  function toDataUri(content) {
98
- if (content.startsWith("data:")) return content;
99
+ if (content.startsWith("data:")) return PDF_DATA_URI.test(content) ? content : void 0;
99
100
  if (content.startsWith("%PDF-")) {
100
101
  const base64 = typeof btoa === "function" ? btoa(content) : Buffer.from(content).toString("base64");
101
102
  return `data:application/pdf;base64,${base64}`;
@@ -144,6 +145,13 @@ function PdfView({ content, className, title }) {
144
145
  const goToNext = useCallback(() => setPageNumber((p) => Math.min(numPages, p + 1)), []);
145
146
  const zoomIn = useCallback(() => setScale((s) => Math.min(3, s + 0.25)), []);
146
147
  const zoomOut = useCallback(() => setScale((s) => Math.max(0.25, s - 0.25)), []);
148
+ if (!dataUri) {
149
+ return React.createElement(
150
+ Typography,
151
+ { className, variant: "body2", color: "error" },
152
+ "This content is not a PDF document."
153
+ );
154
+ }
147
155
  if (!reactPdf) {
148
156
  return React.createElement("iframe", {
149
157
  className: className ?? "fmcp-pdf-content",
package/index.js CHANGED
@@ -274,6 +274,16 @@ var DEFAULT_CONFIG = {
274
274
  debug: false,
275
275
  initTimeout: 5e3
276
276
  };
277
+ function limitToTrustedOrigins(adapterConfig, trustedOrigins) {
278
+ const ownOrigins = adapterConfig?.options?.["trustedOrigins"];
279
+ const origins = Array.isArray(ownOrigins) && ownOrigins.length > 0 ? ownOrigins.filter((origin) => trustedOrigins.includes(origin)) : trustedOrigins;
280
+ if (origins.length === 0) {
281
+ throw new Error(
282
+ 'The bridge trustedOrigins and adapterConfigs["ext-apps"].options.trustedOrigins have no origin in common. List the host origin in both, or set only one of them.'
283
+ );
284
+ }
285
+ return { ...adapterConfig, options: { ...adapterConfig?.options, trustedOrigins: origins } };
286
+ }
277
287
  var FrontMcpBridge = class {
278
288
  _config;
279
289
  _registry;
@@ -299,6 +309,13 @@ var FrontMcpBridge = class {
299
309
  this._registry.configure(id, adapterConfig);
300
310
  }
301
311
  }
312
+ if (this._config.trustedOrigins?.length) {
313
+ const extAppsConfig = limitToTrustedOrigins(
314
+ this._config.adapterConfigs?.["ext-apps"],
315
+ this._config.trustedOrigins
316
+ );
317
+ this._registry.configure("ext-apps", extAppsConfig);
318
+ }
302
319
  }
303
320
  // ============================================
304
321
  // Public Properties
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@frontmcp/ui",
3
- "version": "1.12.0-rc.1",
3
+ "version": "1.12.1",
4
4
  "description": "React components, bridge hooks and content renderers (built on MUI) for FrontMCP tool widgets in ChatGPT, Claude and other MCP Apps hosts",
5
5
  "author": "AgentFront <info@agentfront.dev>",
6
6
  "homepage": "https://frontmcp.dev/reference/ui",
@@ -436,7 +436,7 @@
436
436
  }
437
437
  },
438
438
  "dependencies": {
439
- "@frontmcp/uipack": "1.12.0-rc.1"
439
+ "@frontmcp/uipack": "1.12.1"
440
440
  },
441
441
  "devDependencies": {
442
442
  "@types/dompurify": "^3.0.0",
package/react/index.js CHANGED
@@ -265,6 +265,16 @@ var DEFAULT_CONFIG = {
265
265
  debug: false,
266
266
  initTimeout: 5e3
267
267
  };
268
+ function limitToTrustedOrigins(adapterConfig, trustedOrigins) {
269
+ const ownOrigins = adapterConfig?.options?.["trustedOrigins"];
270
+ const origins = Array.isArray(ownOrigins) && ownOrigins.length > 0 ? ownOrigins.filter((origin) => trustedOrigins.includes(origin)) : trustedOrigins;
271
+ if (origins.length === 0) {
272
+ throw new Error(
273
+ 'The bridge trustedOrigins and adapterConfigs["ext-apps"].options.trustedOrigins have no origin in common. List the host origin in both, or set only one of them.'
274
+ );
275
+ }
276
+ return { ...adapterConfig, options: { ...adapterConfig?.options, trustedOrigins: origins } };
277
+ }
268
278
  var FrontMcpBridge = class {
269
279
  _config;
270
280
  _registry;
@@ -290,6 +300,13 @@ var FrontMcpBridge = class {
290
300
  this._registry.configure(id, adapterConfig);
291
301
  }
292
302
  }
303
+ if (this._config.trustedOrigins?.length) {
304
+ const extAppsConfig = limitToTrustedOrigins(
305
+ this._config.adapterConfigs?.["ext-apps"],
306
+ this._config.trustedOrigins
307
+ );
308
+ this._registry.configure("ext-apps", extAppsConfig);
309
+ }
293
310
  }
294
311
  // ============================================
295
312
  // Public Properties
package/renderer/index.js CHANGED
@@ -1124,6 +1124,7 @@ var import_Paper = __toESM(require("@mui/material/Paper"));
1124
1124
  var import_styles7 = require("@mui/material/styles");
1125
1125
  var import_Typography4 = __toESM(require("@mui/material/Typography"));
1126
1126
  var import_react9 = __toESM(require("react"));
1127
+ var import_shell = require("@frontmcp/uipack/shell");
1127
1128
  var LEAFLET_CSS_URL = "https://esm.sh/leaflet@1.9.4/dist/leaflet.css";
1128
1129
  var LEAFLET_CSS_ID = "fmcp-leaflet-css";
1129
1130
  var DEFAULT_TILE_URL = "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png";
@@ -1174,7 +1175,12 @@ function parseMapConfig(content) {
1174
1175
  geojson: parsed
1175
1176
  };
1176
1177
  }
1177
- return parsed;
1178
+ const config = parsed;
1179
+ const tileLayer = config.tileLayer;
1180
+ if (tileLayer?.attribution !== void 0) {
1181
+ return { ...config, tileLayer: { ...tileLayer, attribution: (0, import_shell.sanitizeHtml)(String(tileLayer.attribution)) } };
1182
+ }
1183
+ return config;
1178
1184
  }
1179
1185
  function MapView({ config, className }) {
1180
1186
  (0, import_react9.useEffect)(() => {
@@ -1245,16 +1251,76 @@ var import_Alert5 = __toESM(require("@mui/material/Alert"));
1245
1251
  var import_Box7 = __toESM(require("@mui/material/Box"));
1246
1252
  var import_styles8 = require("@mui/material/styles");
1247
1253
  var import_react10 = __toESM(require("react"));
1254
+
1255
+ // libs/ui/src/renderer/math/math-spans.ts
1256
+ function lastEscapeOf(source, closer) {
1257
+ let last = -1;
1258
+ for (let index = 0; index < source.length; index++) {
1259
+ if (source[index] !== "\\") continue;
1260
+ if (source[index + 1] === closer) last = index;
1261
+ index++;
1262
+ }
1263
+ return last;
1264
+ }
1265
+ function lastEnvironmentEnd(source) {
1266
+ const lastBrace = source.lastIndexOf("}");
1267
+ let last = -1;
1268
+ for (let index = 0; index < source.length; index++) {
1269
+ if (source[index] !== "\\") continue;
1270
+ const nameStart = index + 5;
1271
+ if (source.startsWith("end{", index + 1) && nameStart < lastBrace && source[nameStart] !== "}") last = index;
1272
+ index++;
1273
+ }
1274
+ return last;
1275
+ }
1276
+ function hasEscapedPair(source, opener, closer) {
1277
+ const start = source.indexOf(`\\${opener}`);
1278
+ return start !== -1 && lastEscapeOf(source, closer) >= start + 2;
1279
+ }
1280
+ function createMathSpanFinder(source) {
1281
+ const bracketClose = lastEscapeOf(source, "]");
1282
+ const parenClose = lastEscapeOf(source, ")");
1283
+ const environmentEnd = lastEnvironmentEnd(source);
1284
+ let nextDoubleDollar = -2;
1285
+ return (index) => {
1286
+ if (source.startsWith("$$", index)) {
1287
+ if (nextDoubleDollar !== -1 && nextDoubleDollar < index + 2) nextDoubleDollar = source.indexOf("$$", index + 2);
1288
+ if (nextDoubleDollar > index + 2) return nextDoubleDollar + 2;
1289
+ }
1290
+ if (source[index] === "$") {
1291
+ let close = index + 1;
1292
+ while (close < source.length && source[close] !== "$" && source[close] !== "\n") close++;
1293
+ if (source[close] === "$" && close > index + 1) return close + 1;
1294
+ }
1295
+ if (source.startsWith("\\[", index) && bracketClose >= index + 2) return bracketClose + 2;
1296
+ if (source.startsWith("\\(", index) && parenClose >= index + 2) return parenClose + 2;
1297
+ if (source.startsWith("\\begin{", index) && environmentEnd >= index + 7) {
1298
+ return source.indexOf("}", environmentEnd + 6) + 1;
1299
+ }
1300
+ return -1;
1301
+ };
1302
+ }
1303
+ function findMathSpans(source) {
1304
+ const findSpanEnd = createMathSpanFinder(source);
1305
+ const spans = [];
1306
+ for (let index = 0; index < source.length; index++) {
1307
+ const end = findSpanEnd(index);
1308
+ if (end === -1) continue;
1309
+ spans.push({ start: index, end });
1310
+ index = end - 1;
1311
+ }
1312
+ return spans;
1313
+ }
1314
+
1315
+ // libs/ui/src/renderer/math/index.ts
1248
1316
  var KATEX_CSS_URL = "https://esm.sh/katex@0.16.11/dist/katex.min.css";
1249
1317
  var KATEX_CSS_ID = "fmcp-katex-css";
1250
1318
  var MATH_DISPLAY = /\$\$(?:[^$]|\$(?!\$))+\$\$/s;
1251
1319
  var MATH_INLINE = /\$[^$\n]+?\$/;
1252
- var MATH_BRACKET_DISPLAY = /\\\[(?:[^\\]|\\.)*\\\]/s;
1253
- var MATH_PAREN_INLINE = /\\\((?:[^\\]|\\.)*\\\)/s;
1254
1320
  var MATH_BEGIN = /\\begin\{(?:equation|align|gather|matrix|pmatrix|bmatrix|cases)\}/;
1255
1321
  function isMath(content) {
1256
1322
  const trimmed = content.trim();
1257
- return MATH_DISPLAY.test(trimmed) || MATH_INLINE.test(trimmed) || MATH_BRACKET_DISPLAY.test(trimmed) || MATH_PAREN_INLINE.test(trimmed) || MATH_BEGIN.test(trimmed);
1323
+ return MATH_DISPLAY.test(trimmed) || MATH_INLINE.test(trimmed) || hasEscapedPair(trimmed, "[", "]") || hasEscapedPair(trimmed, "(", ")") || MATH_BEGIN.test(trimmed);
1258
1324
  }
1259
1325
  var lazyKatex = createLazyImport("katex", async () => {
1260
1326
  const mod = await runtimeImportWithFallback("katex", esmShUrl("katex@0.16"));
@@ -1288,14 +1354,12 @@ var MathInline = (0, import_styles8.styled)("span", {
1288
1354
  });
1289
1355
  function parseMathContent(source) {
1290
1356
  const segments = [];
1291
- const mathRegex = /(\$\$(?:[^$]|\$(?!\$))+\$\$|\$[^$\n]+?\$|\\\[(?:[^\\]|\\.)*\\\]|\\\((?:[^\\]|\\.)*\\\)|\\begin\{(?:[^\\]|\\.)*\\end\{[^}]+\})/gs;
1292
1357
  let lastIndex = 0;
1293
- let match;
1294
- while ((match = mathRegex.exec(source)) !== null) {
1295
- if (match.index > lastIndex) {
1296
- segments.push({ type: "text", content: source.slice(lastIndex, match.index) });
1358
+ for (const span of findMathSpans(source)) {
1359
+ if (span.start > lastIndex) {
1360
+ segments.push({ type: "text", content: source.slice(lastIndex, span.start) });
1297
1361
  }
1298
- const raw = match[0];
1362
+ const raw = source.slice(span.start, span.end);
1299
1363
  if (raw.startsWith("$$")) {
1300
1364
  segments.push({ type: "display", content: raw.slice(2, -2).trim() });
1301
1365
  } else if (raw.startsWith("\\[")) {
@@ -1307,7 +1371,7 @@ function parseMathContent(source) {
1307
1371
  } else if (raw.startsWith("$")) {
1308
1372
  segments.push({ type: "inline", content: raw.slice(1, -1).trim() });
1309
1373
  }
1310
- lastIndex = match.index + raw.length;
1374
+ lastIndex = span.end;
1311
1375
  }
1312
1376
  if (lastIndex < source.length) {
1313
1377
  segments.push({ type: "text", content: source.slice(lastIndex) });
@@ -1386,6 +1450,7 @@ var import_TableHead2 = __toESM(require("@mui/material/TableHead"));
1386
1450
  var import_TableRow2 = __toESM(require("@mui/material/TableRow"));
1387
1451
  var import_Typography5 = __toESM(require("@mui/material/Typography"));
1388
1452
  var import_react11 = __toESM(require("react"));
1453
+ var import_shell2 = require("@frontmcp/uipack/shell");
1389
1454
  var lazyReactMarkdown = createLazyImport(
1390
1455
  "react-markdown",
1391
1456
  async () => {
@@ -1408,6 +1473,16 @@ var lazyRehypeRaw = createLazyImport("rehype-raw", async () => {
1408
1473
  const mod = await runtimeImportWithFallback("rehype-raw", esmShUrl("rehype-raw@7"));
1409
1474
  return mod;
1410
1475
  });
1476
+ var RAW_HTML_POLICY = { maxMarkupLength: Number.MAX_SAFE_INTEGER };
1477
+ function rehypeSanitizeRawHtml() {
1478
+ return (tree) => {
1479
+ const pending = [tree];
1480
+ for (let node = pending.pop(); node; node = pending.pop()) {
1481
+ if (node.type === "raw") node.value = (0, import_shell2.sanitizeHtml)(node.value ?? "", RAW_HTML_POLICY);
1482
+ for (const child of node.children ?? []) pending.push(child);
1483
+ }
1484
+ };
1485
+ }
1411
1486
  var MarkdownRoot = (0, import_styles9.styled)(import_Box8.default, {
1412
1487
  name: "FrontMcpMarkdown",
1413
1488
  slot: "Root"
@@ -1462,7 +1537,7 @@ var Blockquote = (0, import_styles9.styled)("blockquote", {
1462
1537
  "& > p": { margin: 0 }
1463
1538
  }));
1464
1539
  function createMuiComponents() {
1465
- return {
1540
+ const components = {
1466
1541
  h1: (props) => import_react11.default.createElement(import_Typography5.default, { ...props, variant: "h4", gutterBottom: true }),
1467
1542
  h2: (props) => import_react11.default.createElement(import_Typography5.default, { ...props, variant: "h5", gutterBottom: true }),
1468
1543
  h3: (props) => import_react11.default.createElement(import_Typography5.default, { ...props, variant: "h6", gutterBottom: true }),
@@ -1484,6 +1559,12 @@ function createMuiComponents() {
1484
1559
  th: (props) => import_react11.default.createElement(import_TableCell2.default, { ...props, sx: { fontWeight: 600 } }),
1485
1560
  td: (props) => import_react11.default.createElement(import_TableCell2.default, props)
1486
1561
  };
1562
+ return Object.fromEntries(
1563
+ Object.entries(components).map(([tag, render]) => [
1564
+ tag,
1565
+ ({ component: _component, as: _as, ...props }) => render(props)
1566
+ ])
1567
+ );
1487
1568
  }
1488
1569
  function MarkdownView({ content, className }) {
1489
1570
  const components = (0, import_react11.useMemo)(() => createMuiComponents(), []);
@@ -1497,7 +1578,7 @@ function MarkdownView({ content, className }) {
1497
1578
  const rehypePlugins = [];
1498
1579
  if (remarkGfmMod) remarkPlugins.push(remarkGfmMod.default);
1499
1580
  if (rehypeHighlightMod) rehypePlugins.push(rehypeHighlightMod.default);
1500
- if (rehypeRawMod) rehypePlugins.push(rehypeRawMod.default);
1581
+ if (rehypeRawMod) rehypePlugins.push(rehypeSanitizeRawHtml, rehypeRawMod.default);
1501
1582
  return import_react11.default.createElement(
1502
1583
  MarkdownRoot,
1503
1584
  { className },
@@ -1842,8 +1923,9 @@ var lazyReactPdf = createLazyImport("react-pdf", async () => {
1842
1923
  }
1843
1924
  return mod;
1844
1925
  });
1926
+ var PDF_DATA_URI = /^data:application\/pdf[;,]/i;
1845
1927
  function toDataUri(content) {
1846
- if (content.startsWith("data:")) return content;
1928
+ if (content.startsWith("data:")) return PDF_DATA_URI.test(content) ? content : void 0;
1847
1929
  if (content.startsWith("%PDF-")) {
1848
1930
  const base64 = typeof btoa === "function" ? btoa(content) : Buffer.from(content).toString("base64");
1849
1931
  return `data:application/pdf;base64,${base64}`;
@@ -1892,6 +1974,13 @@ function PdfView({ content, className, title }) {
1892
1974
  const goToNext = (0, import_react14.useCallback)(() => setPageNumber((p) => Math.min(numPages, p + 1)), []);
1893
1975
  const zoomIn = (0, import_react14.useCallback)(() => setScale((s) => Math.min(3, s + 0.25)), []);
1894
1976
  const zoomOut = (0, import_react14.useCallback)(() => setScale((s) => Math.max(0.25, s - 0.25)), []);
1977
+ if (!dataUri) {
1978
+ return import_react14.default.createElement(
1979
+ import_Typography6.default,
1980
+ { className, variant: "body2", color: "error" },
1981
+ "This content is not a PDF document."
1982
+ );
1983
+ }
1895
1984
  if (!reactPdf) {
1896
1985
  return import_react14.default.createElement("iframe", {
1897
1986
  className: className ?? "fmcp-pdf-content",
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/renderer/maps/index.ts"],"names":[],"mappings":"AAKA,OAAO,KAAoB,MAAM,OAAO,CAAC;AAKzC,OAAO,KAAK,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAe/D,MAAM,WAAW,SAAS;IACxB,QAAQ,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC3B,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,SAAS;IACxB,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC1B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,SAAS,EAAE,CAAC;IACtB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC,SAAS,CAAC,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAClD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AASD,wBAAgB,KAAK,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAe9C;AAyID,qBAAa,YAAa,YAAW,eAAe;IAClD,QAAQ,CAAC,IAAI,SAAS;IACtB,QAAQ,CAAC,QAAQ,MAAM;IAEvB,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO;IAInC,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,aAAa,GAAG,KAAK,CAAC,YAAY;CAWrE;AAED,eAAO,MAAM,YAAY,cAAqB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/renderer/maps/index.ts"],"names":[],"mappings":"AAKA,OAAO,KAAoB,MAAM,OAAO,CAAC;AAOzC,OAAO,KAAK,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAe/D,MAAM,WAAW,SAAS;IACxB,QAAQ,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC3B,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,SAAS;IACxB,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC1B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,SAAS,EAAE,CAAC;IACtB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC,SAAS,CAAC,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAClD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AASD,wBAAgB,KAAK,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAe9C;AA+ID,qBAAa,YAAa,YAAW,eAAe;IAClD,QAAQ,CAAC,IAAI,SAAS;IACtB,QAAQ,CAAC,QAAQ,MAAM;IAEvB,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO;IAInC,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,aAAa,GAAG,KAAK,CAAC,YAAY;CAWrE;AAED,eAAO,MAAM,YAAY,cAAqB,CAAC"}
@@ -41,6 +41,7 @@ var import_Paper = __toESM(require("@mui/material/Paper"));
41
41
  var import_styles = require("@mui/material/styles");
42
42
  var import_Typography = __toESM(require("@mui/material/Typography"));
43
43
  var import_react2 = __toESM(require("react"));
44
+ var import_shell = require("@frontmcp/uipack/shell");
44
45
 
45
46
  // libs/ui/src/renderer/common/inject-stylesheet.ts
46
47
  function injectStylesheet(href, id) {
@@ -181,7 +182,12 @@ function parseMapConfig(content) {
181
182
  geojson: parsed
182
183
  };
183
184
  }
184
- return parsed;
185
+ const config = parsed;
186
+ const tileLayer = config.tileLayer;
187
+ if (tileLayer?.attribution !== void 0) {
188
+ return { ...config, tileLayer: { ...tileLayer, attribution: (0, import_shell.sanitizeHtml)(String(tileLayer.attribution)) } };
189
+ }
190
+ return config;
185
191
  }
186
192
  function MapView({ config, className }) {
187
193
  (0, import_react2.useEffect)(() => {
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/renderer/math/index.ts"],"names":[],"mappings":"AAGA,OAAO,KAA6B,MAAM,OAAO,CAAC;AAKlD,OAAO,KAAK,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAmB/D,wBAAgB,MAAM,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAS/C;AAqKD,qBAAa,YAAa,YAAW,eAAe;IAClD,QAAQ,CAAC,IAAI,UAAU;IACvB,QAAQ,CAAC,QAAQ,MAAM;IAEvB,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO;IAInC,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,aAAa,GAAG,KAAK,CAAC,YAAY;CAMrE;AAED,eAAO,MAAM,YAAY,cAAqB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/renderer/math/index.ts"],"names":[],"mappings":"AAGA,OAAO,KAA6B,MAAM,OAAO,CAAC;AAKlD,OAAO,KAAK,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAkB/D,wBAAgB,MAAM,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAS/C;AAgKD,qBAAa,YAAa,YAAW,eAAe;IAClD,QAAQ,CAAC,IAAI,UAAU;IACvB,QAAQ,CAAC,QAAQ,MAAM;IAEvB,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO;IAInC,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,aAAa,GAAG,KAAK,CAAC,YAAY;CAMrE;AAED,eAAO,MAAM,YAAY,cAAqB,CAAC"}
@@ -128,17 +128,75 @@ function useLazyModule(lazy) {
128
128
  return lazy.get();
129
129
  }
130
130
 
131
+ // libs/ui/src/renderer/math/math-spans.ts
132
+ function lastEscapeOf(source, closer) {
133
+ let last = -1;
134
+ for (let index = 0; index < source.length; index++) {
135
+ if (source[index] !== "\\") continue;
136
+ if (source[index + 1] === closer) last = index;
137
+ index++;
138
+ }
139
+ return last;
140
+ }
141
+ function lastEnvironmentEnd(source) {
142
+ const lastBrace = source.lastIndexOf("}");
143
+ let last = -1;
144
+ for (let index = 0; index < source.length; index++) {
145
+ if (source[index] !== "\\") continue;
146
+ const nameStart = index + 5;
147
+ if (source.startsWith("end{", index + 1) && nameStart < lastBrace && source[nameStart] !== "}") last = index;
148
+ index++;
149
+ }
150
+ return last;
151
+ }
152
+ function hasEscapedPair(source, opener, closer) {
153
+ const start = source.indexOf(`\\${opener}`);
154
+ return start !== -1 && lastEscapeOf(source, closer) >= start + 2;
155
+ }
156
+ function createMathSpanFinder(source) {
157
+ const bracketClose = lastEscapeOf(source, "]");
158
+ const parenClose = lastEscapeOf(source, ")");
159
+ const environmentEnd = lastEnvironmentEnd(source);
160
+ let nextDoubleDollar = -2;
161
+ return (index) => {
162
+ if (source.startsWith("$$", index)) {
163
+ if (nextDoubleDollar !== -1 && nextDoubleDollar < index + 2) nextDoubleDollar = source.indexOf("$$", index + 2);
164
+ if (nextDoubleDollar > index + 2) return nextDoubleDollar + 2;
165
+ }
166
+ if (source[index] === "$") {
167
+ let close = index + 1;
168
+ while (close < source.length && source[close] !== "$" && source[close] !== "\n") close++;
169
+ if (source[close] === "$" && close > index + 1) return close + 1;
170
+ }
171
+ if (source.startsWith("\\[", index) && bracketClose >= index + 2) return bracketClose + 2;
172
+ if (source.startsWith("\\(", index) && parenClose >= index + 2) return parenClose + 2;
173
+ if (source.startsWith("\\begin{", index) && environmentEnd >= index + 7) {
174
+ return source.indexOf("}", environmentEnd + 6) + 1;
175
+ }
176
+ return -1;
177
+ };
178
+ }
179
+ function findMathSpans(source) {
180
+ const findSpanEnd = createMathSpanFinder(source);
181
+ const spans = [];
182
+ for (let index = 0; index < source.length; index++) {
183
+ const end = findSpanEnd(index);
184
+ if (end === -1) continue;
185
+ spans.push({ start: index, end });
186
+ index = end - 1;
187
+ }
188
+ return spans;
189
+ }
190
+
131
191
  // libs/ui/src/renderer/math/index.ts
132
192
  var KATEX_CSS_URL = "https://esm.sh/katex@0.16.11/dist/katex.min.css";
133
193
  var KATEX_CSS_ID = "fmcp-katex-css";
134
194
  var MATH_DISPLAY = /\$\$(?:[^$]|\$(?!\$))+\$\$/s;
135
195
  var MATH_INLINE = /\$[^$\n]+?\$/;
136
- var MATH_BRACKET_DISPLAY = /\\\[(?:[^\\]|\\.)*\\\]/s;
137
- var MATH_PAREN_INLINE = /\\\((?:[^\\]|\\.)*\\\)/s;
138
196
  var MATH_BEGIN = /\\begin\{(?:equation|align|gather|matrix|pmatrix|bmatrix|cases)\}/;
139
197
  function isMath(content) {
140
198
  const trimmed = content.trim();
141
- return MATH_DISPLAY.test(trimmed) || MATH_INLINE.test(trimmed) || MATH_BRACKET_DISPLAY.test(trimmed) || MATH_PAREN_INLINE.test(trimmed) || MATH_BEGIN.test(trimmed);
199
+ return MATH_DISPLAY.test(trimmed) || MATH_INLINE.test(trimmed) || hasEscapedPair(trimmed, "[", "]") || hasEscapedPair(trimmed, "(", ")") || MATH_BEGIN.test(trimmed);
142
200
  }
143
201
  var lazyKatex = createLazyImport("katex", async () => {
144
202
  const mod = await runtimeImportWithFallback("katex", esmShUrl("katex@0.16"));
@@ -172,14 +230,12 @@ var MathInline = (0, import_styles.styled)("span", {
172
230
  });
173
231
  function parseMathContent(source) {
174
232
  const segments = [];
175
- const mathRegex = /(\$\$(?:[^$]|\$(?!\$))+\$\$|\$[^$\n]+?\$|\\\[(?:[^\\]|\\.)*\\\]|\\\((?:[^\\]|\\.)*\\\)|\\begin\{(?:[^\\]|\\.)*\\end\{[^}]+\})/gs;
176
233
  let lastIndex = 0;
177
- let match;
178
- while ((match = mathRegex.exec(source)) !== null) {
179
- if (match.index > lastIndex) {
180
- segments.push({ type: "text", content: source.slice(lastIndex, match.index) });
234
+ for (const span of findMathSpans(source)) {
235
+ if (span.start > lastIndex) {
236
+ segments.push({ type: "text", content: source.slice(lastIndex, span.start) });
181
237
  }
182
- const raw = match[0];
238
+ const raw = source.slice(span.start, span.end);
183
239
  if (raw.startsWith("$$")) {
184
240
  segments.push({ type: "display", content: raw.slice(2, -2).trim() });
185
241
  } else if (raw.startsWith("\\[")) {
@@ -191,7 +247,7 @@ function parseMathContent(source) {
191
247
  } else if (raw.startsWith("$")) {
192
248
  segments.push({ type: "inline", content: raw.slice(1, -1).trim() });
193
249
  }
194
- lastIndex = match.index + raw.length;
250
+ lastIndex = span.end;
195
251
  }
196
252
  if (lastIndex < source.length) {
197
253
  segments.push({ type: "text", content: source.slice(lastIndex) });
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Math delimiters found in one pass over the source. The spans are the matches of
3
+ * /\$\$(?:[^$]|\$(?!\$))+\$\$|\$[^$\n]+?\$|\\\[(?:[^\\]|\\.)*\\\]|\\\((?:[^\\]|\\.)*\\\)|\\begin\{(?:[^\\]|\\.)*\\end\{[^}]+\}/gs
4
+ * without its backtracking, which reads the rest of the source again for every delimiter that has no closer.
5
+ */
6
+ /** Start and end positions of a math span in the source, delimiters included. */
7
+ export interface MathSpan {
8
+ start: number;
9
+ end: number;
10
+ }
11
+ /** Whether the source has a backslash-escaped `opener` with a backslash-escaped `closer` after it. */
12
+ export declare function hasEscapedPair(source: string, opener: string, closer: string): boolean;
13
+ /** Find the math spans of the source, left to right and without overlap. */
14
+ export declare function findMathSpans(source: string): MathSpan[];
15
+ //# sourceMappingURL=math-spans.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"math-spans.d.ts","sourceRoot":"","sources":["../../../src/renderer/math/math-spans.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,iFAAiF;AACjF,MAAM,WAAW,QAAQ;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;CACb;AA0BD,sGAAsG;AACtG,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAGtF;AA4BD,4EAA4E;AAC5E,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,QAAQ,EAAE,CAUxD"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/renderer/mdx/index.ts"],"names":[],"mappings":"AASA,OAAO,KAAkB,MAAM,OAAO,CAAC;AAIvC,OAAO,KAAK,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AA6N/D,qBAAa,WAAY,YAAW,eAAe;IACjD,QAAQ,CAAC,IAAI,SAAS;IACtB,QAAQ,CAAC,QAAQ,KAAK;IAEtB,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO;IAUnC,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,aAAa,GAAG,KAAK,CAAC,YAAY;CAMrE;AAED,eAAO,MAAM,WAAW,aAAoB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/renderer/mdx/index.ts"],"names":[],"mappings":"AASA,OAAO,KAAkB,MAAM,OAAO,CAAC;AAMvC,OAAO,KAAK,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAwP/D,qBAAa,WAAY,YAAW,eAAe;IACjD,QAAQ,CAAC,IAAI,SAAS;IACtB,QAAQ,CAAC,QAAQ,KAAK;IAEtB,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO;IAUnC,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,aAAa,GAAG,KAAK,CAAC,YAAY;CAMrE;AAED,eAAO,MAAM,WAAW,aAAoB,CAAC"}
@@ -44,6 +44,7 @@ var import_TableHead = __toESM(require("@mui/material/TableHead"));
44
44
  var import_TableRow = __toESM(require("@mui/material/TableRow"));
45
45
  var import_Typography = __toESM(require("@mui/material/Typography"));
46
46
  var import_react2 = __toESM(require("react"));
47
+ var import_shell = require("@frontmcp/uipack/shell");
47
48
 
48
49
  // libs/ui/src/renderer/common/lazy-import.ts
49
50
  var ESM_SH_BASE = "https://esm.sh/";
@@ -144,6 +145,16 @@ var lazyRehypeRaw = createLazyImport("rehype-raw", async () => {
144
145
  const mod = await runtimeImportWithFallback("rehype-raw", esmShUrl("rehype-raw@7"));
145
146
  return mod;
146
147
  });
148
+ var RAW_HTML_POLICY = { maxMarkupLength: Number.MAX_SAFE_INTEGER };
149
+ function rehypeSanitizeRawHtml() {
150
+ return (tree) => {
151
+ const pending = [tree];
152
+ for (let node = pending.pop(); node; node = pending.pop()) {
153
+ if (node.type === "raw") node.value = (0, import_shell.sanitizeHtml)(node.value ?? "", RAW_HTML_POLICY);
154
+ for (const child of node.children ?? []) pending.push(child);
155
+ }
156
+ };
157
+ }
147
158
  var MarkdownRoot = (0, import_styles.styled)(import_Box.default, {
148
159
  name: "FrontMcpMarkdown",
149
160
  slot: "Root"
@@ -198,7 +209,7 @@ var Blockquote = (0, import_styles.styled)("blockquote", {
198
209
  "& > p": { margin: 0 }
199
210
  }));
200
211
  function createMuiComponents() {
201
- return {
212
+ const components = {
202
213
  h1: (props) => import_react2.default.createElement(import_Typography.default, { ...props, variant: "h4", gutterBottom: true }),
203
214
  h2: (props) => import_react2.default.createElement(import_Typography.default, { ...props, variant: "h5", gutterBottom: true }),
204
215
  h3: (props) => import_react2.default.createElement(import_Typography.default, { ...props, variant: "h6", gutterBottom: true }),
@@ -220,6 +231,12 @@ function createMuiComponents() {
220
231
  th: (props) => import_react2.default.createElement(import_TableCell.default, { ...props, sx: { fontWeight: 600 } }),
221
232
  td: (props) => import_react2.default.createElement(import_TableCell.default, props)
222
233
  };
234
+ return Object.fromEntries(
235
+ Object.entries(components).map(([tag, render]) => [
236
+ tag,
237
+ ({ component: _component, as: _as, ...props }) => render(props)
238
+ ])
239
+ );
223
240
  }
224
241
  function MarkdownView({ content, className }) {
225
242
  const components = (0, import_react2.useMemo)(() => createMuiComponents(), []);
@@ -233,7 +250,7 @@ function MarkdownView({ content, className }) {
233
250
  const rehypePlugins = [];
234
251
  if (remarkGfmMod) remarkPlugins.push(remarkGfmMod.default);
235
252
  if (rehypeHighlightMod) rehypePlugins.push(rehypeHighlightMod.default);
236
- if (rehypeRawMod) rehypePlugins.push(rehypeRawMod.default);
253
+ if (rehypeRawMod) rehypePlugins.push(rehypeSanitizeRawHtml, rehypeRawMod.default);
237
254
  return import_react2.default.createElement(
238
255
  MarkdownRoot,
239
256
  { className },
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/renderer/pdf/index.ts"],"names":[],"mappings":"AAKA,OAAO,KAAyC,MAAM,OAAO,CAAC;AAI9D,OAAO,KAAK,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAqL/D,qBAAa,WAAY,YAAW,eAAe;IACjD,QAAQ,CAAC,IAAI,SAAS;IACtB,QAAQ,CAAC,QAAQ,MAAM;IAEvB,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO;IAQnC,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,aAAa,GAAG,KAAK,CAAC,YAAY;CAOrE;AAED,eAAO,MAAM,WAAW,aAAoB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/renderer/pdf/index.ts"],"names":[],"mappings":"AAKA,OAAO,KAAyC,MAAM,OAAO,CAAC;AAI9D,OAAO,KAAK,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAkM/D,qBAAa,WAAY,YAAW,eAAe;IACjD,QAAQ,CAAC,IAAI,SAAS;IACtB,QAAQ,CAAC,QAAQ,MAAM;IAEvB,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO;IAQnC,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,aAAa,GAAG,KAAK,CAAC,YAAY;CAOrE;AAED,eAAO,MAAM,WAAW,aAAoB,CAAC"}
@@ -129,8 +129,9 @@ var lazyReactPdf = createLazyImport("react-pdf", async () => {
129
129
  }
130
130
  return mod;
131
131
  });
132
+ var PDF_DATA_URI = /^data:application\/pdf[;,]/i;
132
133
  function toDataUri(content) {
133
- if (content.startsWith("data:")) return content;
134
+ if (content.startsWith("data:")) return PDF_DATA_URI.test(content) ? content : void 0;
134
135
  if (content.startsWith("%PDF-")) {
135
136
  const base64 = typeof btoa === "function" ? btoa(content) : Buffer.from(content).toString("base64");
136
137
  return `data:application/pdf;base64,${base64}`;
@@ -179,6 +180,13 @@ function PdfView({ content, className, title }) {
179
180
  const goToNext = (0, import_react2.useCallback)(() => setPageNumber((p) => Math.min(numPages, p + 1)), []);
180
181
  const zoomIn = (0, import_react2.useCallback)(() => setScale((s) => Math.min(3, s + 0.25)), []);
181
182
  const zoomOut = (0, import_react2.useCallback)(() => setScale((s) => Math.max(0.25, s - 0.25)), []);
183
+ if (!dataUri) {
184
+ return import_react2.default.createElement(
185
+ import_Typography.default,
186
+ { className, variant: "body2", color: "error" },
187
+ "This content is not a PDF document."
188
+ );
189
+ }
182
190
  if (!reactPdf) {
183
191
  return import_react2.default.createElement("iframe", {
184
192
  className: className ?? "fmcp-pdf-content",