@lobehub/ui 2.16.1 → 2.16.3

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,5 +1,53 @@
1
+ function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
2
+ function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
3
+ function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, _toPropertyKey(descriptor.key), descriptor); } }
4
+ function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); Object.defineProperty(Constructor, "prototype", { writable: false }); return Constructor; }
5
+ function _defineProperty(obj, key, value) { key = _toPropertyKey(key); if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
6
+ function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : String(i); }
7
+ function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
1
8
  import { renderToString } from 'katex';
2
9
 
10
+ // ============================================================================
11
+ // Utility Classes
12
+ // ============================================================================
13
+
14
+ /**
15
+ * PlaceholderManager - Manages temporary replacement and restoration of protected content
16
+ * Used to protect code blocks and LaTeX expressions during preprocessing
17
+ */
18
+ var PlaceholderManager = /*#__PURE__*/function () {
19
+ function PlaceholderManager() {
20
+ var prefix = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'PROTECTED';
21
+ _classCallCheck(this, PlaceholderManager);
22
+ _defineProperty(this, "placeholders", []);
23
+ _defineProperty(this, "prefix", void 0);
24
+ this.prefix = prefix;
25
+ }
26
+ _createClass(PlaceholderManager, [{
27
+ key: "add",
28
+ value: function add(content) {
29
+ var index = this.placeholders.length;
30
+ this.placeholders.push(content);
31
+ return "<<".concat(this.prefix, "_").concat(index, ">>");
32
+ }
33
+ }, {
34
+ key: "restore",
35
+ value: function restore(text) {
36
+ var _this = this;
37
+ return text.replaceAll(new RegExp("<<".concat(this.prefix, "_(\\d+)>>"), 'g'), function (_, index) {
38
+ return _this.placeholders[Number.parseInt(index)] || '';
39
+ });
40
+ }
41
+ }, {
42
+ key: "clear",
43
+ value: function clear() {
44
+ this.placeholders = [];
45
+ }
46
+ }]);
47
+ return PlaceholderManager;
48
+ }(); // ============================================================================
49
+ // Helper Functions
50
+ // ============================================================================
3
51
  // Helper: replace unescaped pipes with \vert within a LaTeX math fragment
4
52
  var replaceUnescapedPipes = function replaceUnescapedPipes(formula) {
5
53
  return (
@@ -52,13 +100,30 @@ export function escapeLatexPipes(text) {
52
100
  // remark-gfm table parsing won't treat them as column separators.
53
101
  // Leave code blocks/inline code untouched.
54
102
  // Also ignore escaped dollars (\$) which are currency symbols
55
- var pattern = /(```[\S\s]*?```|`[^\n`]*`)|\$\$([\S\s]*?)\$\$|(?<!\\)(?<!\$)\$(?!\$)([\S\s]*?)(?<!\\)(?<!\$)\$(?!\$)/g;
56
- return text.replaceAll(pattern, function (match, code, display, inline) {
57
- if (code !== undefined) return code; // preserve code fences/inline code
58
- if (display !== undefined) return "$$".concat(replaceUnescapedPipes(display), "$$");
59
- if (inline !== undefined) return "$".concat(replaceUnescapedPipes(inline), "$");
60
- return match;
103
+
104
+ // Process code blocks first to protect them
105
+ var codeBlocks = [];
106
+ var content = text.replaceAll(/(```[\S\s]*?```|`[^\n`]*`)/g, function (match) {
107
+ codeBlocks.push(match);
108
+ return "<<CODE_".concat(codeBlocks.length - 1, ">>");
109
+ });
110
+
111
+ // For display math, allow multiline
112
+ content = content.replaceAll(/\$\$([\S\s]*?)\$\$/g, function (match, display) {
113
+ return "$$".concat(replaceUnescapedPipes(display), "$$");
114
+ });
115
+
116
+ // For inline math, use non-greedy match that DOES NOT cross newlines
117
+ // This prevents issues in tables where $ might appear in different cells
118
+ content = content.replaceAll(/(?<!\\)\$(?!\$)([^\n$]*?)(?<!\\)\$(?!\$)/g, function (match, inline) {
119
+ return "$".concat(replaceUnescapedPipes(inline), "$");
61
120
  });
121
+
122
+ // Restore code blocks
123
+ content = content.replaceAll(/<<CODE_(\d+)>>/g, function (_, index) {
124
+ return codeBlocks[Number.parseInt(index)];
125
+ });
126
+ return content;
62
127
  }
63
128
 
64
129
  /**
@@ -94,7 +159,7 @@ export function escapeTextUnderscores(text) {
94
159
  */
95
160
  export function escapeCurrencyDollars(text) {
96
161
  // Protect code blocks and existing LaTeX expressions from processing
97
- var protectedStrings = [];
162
+ var manager = new PlaceholderManager('PROTECTED');
98
163
  var content = text.replaceAll(
99
164
  // Match patterns to protect (in order):
100
165
  // 1. Code blocks: ```...```
@@ -104,77 +169,26 @@ export function escapeCurrencyDollars(text) {
104
169
  // 5. LaTeX bracket notation: \[...\]
105
170
  // 6. LaTeX parenthesis notation: \(...\)
106
171
  /(```[\S\s]*?```|`[^\n`]*`|\$\$[\S\s]*?\$\$|(?<!\\)\$(?!\$)(?=[\S\s]*?\\)[\S\s]*?(?<!\\)\$(?!\$)|\\\[[\S\s]*?\\]|\\\(.*?\\\))/g, function (match) {
107
- protectedStrings.push(match);
108
- return "<<PROTECTED_".concat(protectedStrings.length - 1, ">>");
172
+ return manager.add(match);
109
173
  });
110
174
 
111
175
  // Escape dollar signs that are clearly currency:
112
176
  // - $ followed by a digit
113
177
  // - Not preceded by another $ (to avoid breaking $$)
178
+ // - Not followed immediately by another $ (to avoid breaking $1$ LaTeX)
114
179
  // - Followed by number patterns with optional commas, decimals, ranges, or plus signs
115
180
  // Match patterns like: $20, $1,000, $19.99, $20-50, $300+, $1,000-2,000+
181
+ // But NOT: $1$, $2$ (these are LaTeX formulas)
116
182
  // In the replacement: \\ = backslash, $$ = literal $, $1 = capture group 1
117
- content = content.replaceAll(/(?<!\$)\$(\d{1,3}(?:,\d{3})*(?:\.\d+)?(?:-\d{1,3}(?:,\d{3})*(?:\.\d+)?)?\+?)/g, '\\$$$1');
183
+ content = content.replaceAll(/(?<!\$)\$(\d{1,3}(?:,\d{3})*(?:\.\d+)?(?:-\d{1,3}(?:,\d{3})*(?:\.\d+)?)?\+?)(?!\$)/g, '\\$$$1');
118
184
 
119
185
  // Restore protected content
120
- content = content.replaceAll(/<<PROTECTED_(\d+)>>/g, function (_, index) {
121
- return protectedStrings[Number.parseInt(index)];
122
- });
186
+ content = manager.restore(content);
123
187
  return content;
124
188
  }
125
189
 
126
- /**
127
- * Preprocesses LaTeX content by performing multiple operations:
128
- * 1. Protects code blocks from processing
129
- * 2. Protects existing LaTeX expressions
130
- * 3. Escapes dollar signs that likely represent currency
131
- * 4. Converts LaTeX delimiters
132
- * 5. Escapes mhchem commands and pipes
133
- *
134
- * @param content The input string containing LaTeX expressions
135
- * @returns The processed string with proper LaTeX formatting
136
- */
137
- export function preprocessLaTeX(str) {
138
- // Step 1: Protect code blocks
139
- // const codeBlocks: string[] = [];
140
- // let content = str.replaceAll(/(```[\S\s]*?```|`[^\n`]+`)/g, (match, code) => {
141
- // codeBlocks.push(code);
142
- // return `<<CODE_BLOCK_${codeBlocks.length - 1}>>`;
143
- // });
144
-
145
- // // Step 2: Protect existing LaTeX expressions
146
- // const latexExpressions: string[] = [];
147
- // content = content.replaceAll(/(\$\$[\S\s]*?\$\$|\\\[[\S\s]*?\\]|\\\(.*?\\\))/g, (match) => {
148
- // latexExpressions.push(match);
149
- // return `<<LATEX_${latexExpressions.length - 1}>>`;
150
- // });
151
-
152
- // Step 3: Escape dollar signs that are likely currency indicators
153
- // Deprecated, as it causes parsing errors for formulas starting with a number, such as `$1$`
154
- // content = content.replaceAll(/\$(?=\d)/g, '\\$');
155
-
156
- // Step 4: Restore LaTeX expressions
157
- // content = content.replaceAll(
158
- // /<<LATEX_(\d+)>>/g,
159
- // (_, index) => latexExpressions[Number.parseInt(index)],
160
- // );
161
-
162
- // // Step 5: Restore code blocks
163
- // content = content.replaceAll(
164
- // /<<CODE_BLOCK_(\d+)>>/g,
165
- // (_, index) => codeBlocks[Number.parseInt(index)],
166
- // );
167
- var content = str;
168
-
169
- // Step 6: Apply additional escaping functions
170
- // Escape currency dollar signs FIRST before other LaTeX processing
171
- content = escapeCurrencyDollars(content);
172
- content = convertLatexDelimiters(content);
173
- content = escapeMhchemCommands(content);
174
- content = escapeLatexPipes(content);
175
- content = escapeTextUnderscores(content);
176
- return content;
177
- }
190
+ // Old simple preprocessLaTeX has been replaced by the comprehensive version below
191
+ // The new preprocessLaTeX provides the same default behavior with optional advanced featuresgit
178
192
 
179
193
  /**
180
194
  * Extracts the LaTeX formula after the last $$ delimiter if there's an odd number of $$ delimiters.
@@ -220,4 +234,294 @@ export var isLastFormulaRenderable = function isLastFormulaRenderable(text) {
220
234
  console.log("LaTeX formula rendering error: ".concat(error));
221
235
  return false;
222
236
  }
223
- };
237
+ };
238
+
239
+ // ============================================================================
240
+ // Advanced Preprocessing Functions
241
+ // ============================================================================
242
+
243
+ /**
244
+ * Fixes common LaTeX syntax errors automatically
245
+ * - Balances unmatched braces
246
+ * - Balances \left and \right delimiters
247
+ *
248
+ * @param text The input string containing LaTeX expressions
249
+ * @returns The string with fixed LaTeX expressions
250
+ */
251
+ export function fixCommonLaTeXErrors(text) {
252
+ return text.replaceAll(/(\$\$[\S\s]*?\$\$|\$[\S\s]*?\$)/g, function (match) {
253
+ var fixed = match;
254
+
255
+ // Fix unbalanced braces
256
+ var openBraces = (fixed.match(/(?<!\\){/g) || []).length;
257
+ var closeBraces = (fixed.match(/(?<!\\)}/g) || []).length;
258
+ if (openBraces > closeBraces) {
259
+ var diff = openBraces - closeBraces;
260
+ var closingBraces = '}'.repeat(diff);
261
+ // Insert before the closing delimiter
262
+ fixed = fixed.replace(/(\$\$?)$/, closingBraces + '$1');
263
+ }
264
+
265
+ // Fix unbalanced \left and \right
266
+ var leftDelims = (fixed.match(/\\left[(.<[{|]/g) || []).length;
267
+ var rightDelims = (fixed.match(/\\right[).>\]|}]/g) || []).length;
268
+ if (leftDelims > rightDelims) {
269
+ var _diff = leftDelims - rightDelims;
270
+ var rightDots = '\\right.'.repeat(_diff);
271
+ fixed = fixed.replace(/(\$\$?)$/, rightDots + '$1');
272
+ }
273
+ return fixed;
274
+ });
275
+ }
276
+
277
+ /**
278
+ * Normalizes whitespace in LaTeX expressions
279
+ * - Removes extra spaces around $ delimiters
280
+ * - Normalizes multiple spaces to single space inside formulas
281
+ *
282
+ * @param text The input string containing LaTeX expressions
283
+ * @returns The string with normalized whitespace
284
+ */
285
+ export function normalizeLatexSpacing(text) {
286
+ var result = text;
287
+
288
+ // Remove spaces inside $ delimiters (at the edges)
289
+ result = result.replaceAll(/\$\s+/g, '$');
290
+ result = result.replaceAll(/\s+\$/g, '$');
291
+ result = result.replaceAll(/\$\$\s+/g, '$$');
292
+ result = result.replaceAll(/\s+\$\$/g, '$$');
293
+
294
+ // Normalize multiple spaces inside formulas to single space
295
+ result = result.replaceAll(/(\$\$[\S\s]*?\$\$|\$[\S\s]*?\$)/g, function (match) {
296
+ return match.replaceAll(/\s{2,}/g, ' ');
297
+ });
298
+ return result;
299
+ }
300
+
301
+ /**
302
+ * Validates all LaTeX expressions in the text
303
+ * Returns detailed information about validation results
304
+ *
305
+ * @param text The input string containing LaTeX expressions
306
+ * @returns Validation results with errors if any
307
+ */
308
+ export function validateLatexExpressions(text) {
309
+ var errors = [];
310
+ var totalExpressions = 0;
311
+ var pattern = /\$\$([\S\s]*?)\$\$|(?<!\\)\$(?!\$)([\S\s]*?)(?<!\\)\$(?!\$)/g;
312
+ var match;
313
+ while ((match = pattern.exec(text)) !== null) {
314
+ totalExpressions++;
315
+ var formula = match[1] || match[2];
316
+ var isDisplay = match[0].startsWith('$$');
317
+ try {
318
+ renderToString(formula, {
319
+ displayMode: isDisplay,
320
+ strict: 'warn',
321
+ throwOnError: true,
322
+ trust: false
323
+ });
324
+ } catch (error) {
325
+ errors.push({
326
+ formula: formula.slice(0, 50) + (formula.length > 50 ? '...' : ''),
327
+ message: error instanceof Error ? error.message : String(error),
328
+ position: match.index,
329
+ type: isDisplay ? 'display' : 'inline'
330
+ });
331
+ }
332
+ }
333
+ return {
334
+ errors: errors,
335
+ totalExpressions: totalExpressions,
336
+ valid: errors.length === 0
337
+ };
338
+ }
339
+
340
+ /**
341
+ * Handles CJK (Chinese, Japanese, Korean) characters mixed with LaTeX
342
+ * Optionally adds spaces between CJK characters and LaTeX expressions for better rendering
343
+ *
344
+ * @param text The input string
345
+ * @param addSpaces Whether to add spaces between CJK and LaTeX (default: false)
346
+ * @returns The processed string
347
+ */
348
+ export function handleCJKWithLatex(text) {
349
+ var addSpaces = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
350
+ if (!addSpaces) return text;
351
+ var result = text;
352
+
353
+ // Add space between CJK character and opening $
354
+ result = result.replaceAll(/([\u3040-\u30FF\u4E00-\u9FA5])(\$)/g, '$1 $2');
355
+
356
+ // Add space between closing $ and CJK character
357
+ result = result.replaceAll(/(\$)([\u3040-\u30FF\u4E00-\u9FA5])/g, '$1 $2');
358
+ return result;
359
+ }
360
+
361
+ // ============================================================================
362
+ // Advanced Preprocessing Options
363
+ // ============================================================================
364
+
365
+ /**
366
+ * Comprehensive LaTeX preprocessing with configurable options
367
+ *
368
+ * This is the main preprocessing function that handles:
369
+ * - Currency symbol escaping (e.g., $20 → \$20)
370
+ * - LaTeX delimiter conversion (\[...\] → $$...$$)
371
+ * - Special character escaping (pipes, underscores, mhchem)
372
+ * - Optional error fixing and validation
373
+ * - Optional CJK character handling
374
+ *
375
+ * @param text The input string containing LaTeX and Markdown
376
+ * @param options Configuration options for fine-grained control
377
+ * @returns The preprocessed string
378
+ *
379
+ * @example
380
+ * ```ts
381
+ * // Default behavior (same as old preprocessLaTeX)
382
+ * preprocessLaTeX('向量$90^\\circ$,非 $0^\\circ$ 和 $180^\\circ$')
383
+ *
384
+ * // With custom options
385
+ * preprocessLaTeX(text, {
386
+ * fixErrors: true,
387
+ * validate: true,
388
+ * handleCJK: true
389
+ * })
390
+ * ```
391
+ */
392
+ export function preprocessLaTeX(text) {
393
+ var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
394
+ var _options$addCJKSpaces = options.addCJKSpaces,
395
+ addCJKSpaces = _options$addCJKSpaces === void 0 ? false : _options$addCJKSpaces,
396
+ _options$convertBrack = options.convertBrackets,
397
+ convertBrackets = _options$convertBrack === void 0 ? true : _options$convertBrack,
398
+ _options$escapeCurren = options.escapeCurrency,
399
+ escapeCurrency = _options$escapeCurren === void 0 ? true : _options$escapeCurren,
400
+ _options$escapeMhchem = options.escapeMhchem,
401
+ escapeMhchem = _options$escapeMhchem === void 0 ? true : _options$escapeMhchem,
402
+ _options$escapePipes = options.escapePipes,
403
+ escapePipes = _options$escapePipes === void 0 ? true : _options$escapePipes,
404
+ _options$escapeUnders = options.escapeUnderscores,
405
+ escapeUnderscores = _options$escapeUnders === void 0 ? true : _options$escapeUnders,
406
+ _options$fixErrors = options.fixErrors,
407
+ fixErrors = _options$fixErrors === void 0 ? false : _options$fixErrors,
408
+ _options$handleCJK = options.handleCJK,
409
+ handleCJK = _options$handleCJK === void 0 ? false : _options$handleCJK,
410
+ _options$normalizeSpa = options.normalizeSpacing,
411
+ normalizeSpacing = _options$normalizeSpa === void 0 ? false : _options$normalizeSpa,
412
+ _options$throwOnValid = options.throwOnValidationError,
413
+ throwOnValidationError = _options$throwOnValid === void 0 ? false : _options$throwOnValid,
414
+ _options$validate = options.validate,
415
+ validate = _options$validate === void 0 ? false : _options$validate;
416
+ var content = text;
417
+
418
+ // Phase 1: Currency escaping (if enabled)
419
+ if (escapeCurrency) {
420
+ content = escapeCurrencyDollars(content);
421
+ }
422
+
423
+ // Phase 2: Bracket conversion (if enabled)
424
+ if (convertBrackets) {
425
+ content = convertLatexDelimiters(content);
426
+ }
427
+
428
+ // Phase 3: LaTeX-specific escaping
429
+ if (escapeMhchem) {
430
+ content = escapeMhchemCommands(content);
431
+ }
432
+ if (escapePipes) {
433
+ content = escapeLatexPipes(content);
434
+ }
435
+ if (escapeUnderscores) {
436
+ content = escapeTextUnderscores(content);
437
+ }
438
+
439
+ // Phase 4: Error fixing (if enabled)
440
+ if (fixErrors) {
441
+ content = fixCommonLaTeXErrors(content);
442
+ }
443
+
444
+ // Phase 5: Whitespace normalization (if enabled)
445
+ if (normalizeSpacing) {
446
+ content = normalizeLatexSpacing(content);
447
+ }
448
+
449
+ // Phase 6: CJK handling (if enabled)
450
+ if (handleCJK) {
451
+ content = handleCJKWithLatex(content, addCJKSpaces);
452
+ }
453
+
454
+ // Phase 7: Validation (if enabled)
455
+ if (validate) {
456
+ var validation = validateLatexExpressions(content);
457
+ if (!validation.valid) {
458
+ var errorMessage = "LaTeX validation failed (".concat(validation.errors.length, "/").concat(validation.totalExpressions, " expressions have errors):\n").concat(validation.errors.map(function (e) {
459
+ return " - [".concat(e.type, "] at position ").concat(e.position, ": ").concat(e.message, "\n Formula: ").concat(e.formula);
460
+ }).join('\n'));
461
+ if (throwOnValidationError) {
462
+ throw new Error(errorMessage);
463
+ } else {
464
+ console.warn(errorMessage);
465
+ }
466
+ }
467
+ }
468
+ return content;
469
+ }
470
+
471
+ /**
472
+ * Strict preprocessing mode - enables all safety features and validations
473
+ * Use this when you want maximum correctness and are willing to accept the performance cost
474
+ *
475
+ * @param text The input string
476
+ * @returns The preprocessed string with all features enabled
477
+ *
478
+ * @example
479
+ * ```ts
480
+ * const processed = preprocessLaTeXStrict(userInput)
481
+ * // Enables: error fixing, validation, CJK handling, space normalization
482
+ * ```
483
+ */
484
+ export function preprocessLaTeXStrict(text) {
485
+ return preprocessLaTeX(text, {
486
+ addCJKSpaces: false,
487
+ // Usually don't want extra spaces
488
+ convertBrackets: true,
489
+ escapeCurrency: true,
490
+ escapeMhchem: true,
491
+ escapePipes: true,
492
+ escapeUnderscores: true,
493
+ fixErrors: true,
494
+ handleCJK: true,
495
+ normalizeSpacing: true,
496
+ throwOnValidationError: false,
497
+ // Warn but don't throw
498
+ validate: true
499
+ });
500
+ }
501
+
502
+ /**
503
+ * Minimal preprocessing mode - only essential operations
504
+ * Use this for better performance when you control the input
505
+ *
506
+ * @param text The input string
507
+ * @returns The preprocessed string with minimal processing
508
+ *
509
+ * @example
510
+ * ```ts
511
+ * const processed = preprocessLaTeXMinimal(trustedInput)
512
+ * // Only escapes currency and converts brackets
513
+ * ```
514
+ */
515
+ export function preprocessLaTeXMinimal(text) {
516
+ return preprocessLaTeX(text, {
517
+ convertBrackets: true,
518
+ escapeCurrency: true,
519
+ escapeMhchem: false,
520
+ escapePipes: false,
521
+ escapeUnderscores: false,
522
+ fixErrors: false,
523
+ handleCJK: false,
524
+ normalizeSpacing: false,
525
+ validate: false
526
+ });
527
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lobehub/ui",
3
- "version": "2.16.1",
3
+ "version": "2.16.3",
4
4
  "description": "Lobe UI is an open-source UI component library for building AIGC web apps",
5
5
  "keywords": [
6
6
  "lobehub",