@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.
- package/bridge/core/bridge-factory.d.ts.map +1 -1
- package/bridge/index.js +18 -0
- package/esm/bridge/index.mjs +18 -0
- package/esm/index.mjs +17 -0
- package/esm/package.json +2 -2
- package/esm/react/index.mjs +17 -0
- package/esm/renderer/index.mjs +103 -14
- package/esm/renderer/maps/index.mjs +7 -1
- package/esm/renderer/math/index.mjs +66 -10
- package/esm/renderer/mdx/index.mjs +19 -2
- package/esm/renderer/pdf/index.mjs +9 -1
- package/index.js +17 -0
- package/package.json +2 -2
- package/react/index.js +17 -0
- package/renderer/index.js +103 -14
- package/renderer/maps/index.d.ts.map +1 -1
- package/renderer/maps/index.js +7 -1
- package/renderer/math/index.d.ts.map +1 -1
- package/renderer/math/index.js +66 -10
- package/renderer/math/math-spans.d.ts +15 -0
- package/renderer/math/math-spans.d.ts.map +1 -0
- package/renderer/mdx/index.d.ts.map +1 -1
- package/renderer/mdx/index.js +19 -2
- package/renderer/pdf/index.d.ts.map +1 -1
- package/renderer/pdf/index.js +9 -1
|
@@ -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,
|
|
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,EAEnB,YAAY,EAGZ,WAAW,EACX,uBAAuB,EACvB,WAAW,EACX,eAAe,EACf,UAAU,EACX,MAAM,UAAU,CAAC;AAClB,OAAO,EAAmB,KAAK,eAAe,EAAE,MAAM,oBAAoB,CAAC;AA2B3E;;;;;;;;;;;;;;;;;;;;;;;;;;;;;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;IAiCjE;;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"}
|
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
|
}
|
package/esm/bridge/index.mjs
CHANGED
|
@@ -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.
|
|
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.
|
|
439
|
+
"@frontmcp/uipack": "1.12.1"
|
|
440
440
|
},
|
|
441
441
|
"devDependencies": {
|
|
442
442
|
"@types/dompurify": "^3.0.0",
|
package/esm/react/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/renderer/index.mjs
CHANGED
|
@@ -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
|
-
|
|
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) ||
|
|
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
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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) ||
|
|
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
|
-
|
|
142
|
-
|
|
143
|
-
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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) ||
|
|
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
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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;
|
|
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"}
|
package/renderer/maps/index.js
CHANGED
|
@@ -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
|
-
|
|
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;
|
|
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"}
|
package/renderer/math/index.js
CHANGED
|
@@ -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) ||
|
|
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
|
-
|
|
178
|
-
|
|
179
|
-
|
|
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 =
|
|
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 =
|
|
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;
|
|
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"}
|
package/renderer/mdx/index.js
CHANGED
|
@@ -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
|
-
|
|
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;
|
|
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"}
|
package/renderer/pdf/index.js
CHANGED
|
@@ -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",
|