@tfw.in/structura-lib 0.2.0 → 0.2.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.
Files changed (49) hide show
  1. package/README.md +9 -361
  2. package/dist/cjs/EditableContent.js +45 -18
  3. package/dist/cjs/HtmlViewer.js +217 -83
  4. package/dist/cjs/MathRenderer.js +88 -0
  5. package/dist/cjs/PdfDocumentViewer.js +1 -1
  6. package/dist/cjs/SemanticTagParser.js +189 -0
  7. package/dist/cjs/SemanticTagRenderer.js +135 -0
  8. package/dist/cjs/Structura.js +20 -66
  9. package/dist/cjs/Table.js +73 -8
  10. package/dist/cjs/TableCell.js +33 -10
  11. package/dist/cjs/index.js +12 -0
  12. package/dist/cjs/node_modules/react-icons/fa/index.esm.js +6 -0
  13. package/dist/cjs/styles.css +2 -4
  14. package/dist/cjs/styles.css.map +1 -1
  15. package/dist/esm/EditableContent.js +49 -19
  16. package/dist/esm/HtmlViewer.js +259 -100
  17. package/dist/esm/MathRenderer.js +85 -0
  18. package/dist/esm/PdfDocumentViewer.js +1 -1
  19. package/dist/esm/SemanticTagParser.js +187 -0
  20. package/dist/esm/SemanticTagRenderer.js +140 -0
  21. package/dist/esm/Structura.js +21 -69
  22. package/dist/esm/Table.js +82 -8
  23. package/dist/esm/TableCell.js +32 -6
  24. package/dist/esm/index.js +3 -0
  25. package/dist/esm/node_modules/react-icons/fa/index.esm.js +5 -1
  26. package/dist/esm/styles.css +2 -4
  27. package/dist/esm/styles.css.map +1 -1
  28. package/dist/esm/types/DocumentOutline.d.ts +7 -0
  29. package/dist/esm/types/EditableContent.d.ts +7 -1
  30. package/dist/esm/types/HtmlViewer.d.ts +1 -2
  31. package/dist/esm/types/MathRenderer.d.ts +25 -0
  32. package/dist/esm/types/SemanticTagParser.d.ts +33 -0
  33. package/dist/esm/types/SemanticTagRenderer.d.ts +17 -0
  34. package/dist/esm/types/Structura.d.ts +1 -2
  35. package/dist/esm/types/Table.d.ts +3 -1
  36. package/dist/esm/types/TableCell.d.ts +6 -1
  37. package/dist/esm/types/helpers/index.d.ts +0 -1
  38. package/dist/esm/types/index.d.ts +3 -0
  39. package/dist/esm/types/test-app/src/App.d.ts +1 -2
  40. package/dist/index.d.ts +78 -4
  41. package/package.json +9 -16
  42. package/PRODUCTION_ARCHITECTURE.md +0 -511
  43. package/SAVE_FUNCTIONALITY_COMPLETE.md +0 -448
  44. package/dist/cjs/ui/badge.js +0 -34
  45. package/dist/esm/types/helpers/jsonToHtml.d.ts +0 -40
  46. package/dist/esm/ui/badge.js +0 -31
  47. package/server/README.md +0 -203
  48. package/server/db.js +0 -142
  49. package/server/server.js +0 -165
@@ -9,6 +9,7 @@ var index_esm = require('./node_modules/react-icons/fa/index.esm.js');
9
9
  var Table = require('./Table.js');
10
10
  var EditableContent = require('./EditableContent.js');
11
11
  var accuracyMetrics = require('./accuracyMetrics.js');
12
+ var SemanticTagParser = require('./SemanticTagParser.js');
12
13
 
13
14
  // Utility function to clean HTML content
14
15
  const cleanHtml = html => {
@@ -140,24 +141,121 @@ function AnalyticsModal({
140
141
  })
141
142
  });
142
143
  }
144
+ function TagModal({
145
+ isOpen,
146
+ onClose,
147
+ tag
148
+ }) {
149
+ if (!isOpen || !tag) return null;
150
+ const typeInfo = SemanticTagParser.getTagTypeInfo(tag.type);
151
+ return jsxRuntime.jsx("div", {
152
+ className: "fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50",
153
+ children: jsxRuntime.jsxs("div", {
154
+ className: "bg-white rounded-lg p-6 max-w-md w-full shadow-xl",
155
+ children: [jsxRuntime.jsxs("div", {
156
+ className: "flex justify-between items-center mb-4",
157
+ children: [jsxRuntime.jsxs("div", {
158
+ className: "flex items-center gap-2",
159
+ children: [jsxRuntime.jsx("span", {
160
+ className: "text-2xl",
161
+ children: typeInfo.icon
162
+ }), jsxRuntime.jsxs("h3", {
163
+ className: "text-lg font-semibold",
164
+ children: [typeInfo.label, " Tag"]
165
+ })]
166
+ }), jsxRuntime.jsx("button", {
167
+ onClick: onClose,
168
+ className: "text-gray-500 hover:text-gray-700",
169
+ children: "\u2715"
170
+ })]
171
+ }), jsxRuntime.jsx("div", {
172
+ className: `p-4 rounded-lg border ${typeInfo.bgColor} mb-4`,
173
+ children: jsxRuntime.jsxs("div", {
174
+ className: `text-2xl font-bold ${typeInfo.color}`,
175
+ children: [tag.value, tag.attributes.uom && jsxRuntime.jsx("span", {
176
+ className: "text-lg ml-2 opacity-75",
177
+ children: tag.attributes.uom
178
+ })]
179
+ })
180
+ }), jsxRuntime.jsxs("div", {
181
+ className: "space-y-3",
182
+ children: [jsxRuntime.jsxs("div", {
183
+ className: "flex justify-between py-2 border-b",
184
+ children: [jsxRuntime.jsx("span", {
185
+ className: "text-gray-600",
186
+ children: "Type"
187
+ }), jsxRuntime.jsx("span", {
188
+ className: `font-medium ${typeInfo.color}`,
189
+ children: tag.type
190
+ })]
191
+ }), tag.attributes.uom && jsxRuntime.jsxs("div", {
192
+ className: "flex justify-between py-2 border-b",
193
+ children: [jsxRuntime.jsx("span", {
194
+ className: "text-gray-600",
195
+ children: "Unit of Measure"
196
+ }), jsxRuntime.jsx("span", {
197
+ className: "font-medium",
198
+ children: tag.attributes.uom
199
+ })]
200
+ }), tag.attributes.id && jsxRuntime.jsxs("div", {
201
+ className: "py-2 border-b",
202
+ children: [jsxRuntime.jsx("span", {
203
+ className: "text-gray-600 block mb-1",
204
+ children: "Tag ID"
205
+ }), jsxRuntime.jsx("code", {
206
+ className: "text-xs bg-gray-100 p-2 rounded block break-all",
207
+ children: tag.attributes.id
208
+ })]
209
+ }), tag.attributes.format && jsxRuntime.jsxs("div", {
210
+ className: "flex justify-between py-2 border-b",
211
+ children: [jsxRuntime.jsx("span", {
212
+ className: "text-gray-600",
213
+ children: "Format"
214
+ }), jsxRuntime.jsx("span", {
215
+ className: "font-mono text-sm",
216
+ children: tag.attributes.format
217
+ })]
218
+ }), tag.attributes.filter && jsxRuntime.jsxs("div", {
219
+ className: "flex justify-between py-2 border-b",
220
+ children: [jsxRuntime.jsx("span", {
221
+ className: "text-gray-600",
222
+ children: "File Filter"
223
+ }), jsxRuntime.jsx("span", {
224
+ className: "font-mono text-sm",
225
+ children: tag.attributes.filter
226
+ })]
227
+ }), jsxRuntime.jsxs("div", {
228
+ className: "py-2",
229
+ children: [jsxRuntime.jsx("span", {
230
+ className: "text-gray-600 block mb-1",
231
+ children: "Raw Tag"
232
+ }), jsxRuntime.jsx("code", {
233
+ className: "text-xs bg-gray-100 p-2 rounded block break-all text-gray-700",
234
+ children: tag.rawMatch
235
+ })]
236
+ })]
237
+ })]
238
+ })
239
+ });
240
+ }
143
241
  // Update icon usage in the JSX
144
242
  const VscJsonIcon = vsc.VscJson;
145
243
  const FaFileDownloadIcon = index_esm.FaFileDownload;
146
244
  const FaChartBarIcon = index_esm.FaChartBar;
245
+ const FaEditIcon = index_esm.FaEdit;
246
+ const FaTagsIcon = index_esm.FaTags;
147
247
  function HtmlViewer({
148
248
  //NOSONAR
149
249
  jsonData,
150
250
  selectedBboxId,
151
251
  isLoading,
152
- onNodeClick,
153
- onSave
252
+ onNodeClick
154
253
  }) {
155
254
  const [editedData, setEditedData] = React.useState(jsonData);
156
255
  const [hasChanges, setHasChanges] = React.useState(false);
157
256
  const [isModalOpen, setIsModalOpen] = React.useState(false);
158
257
  const [modalData, setModalData] = React.useState(null);
159
- const [showJsonIcons, setShowJsonIcons] = React.useState(true);
160
- const [activeFormat, setActiveFormat] = React.useState("Show JSON");
258
+ const [viewMode, setViewMode] = React.useState('read');
161
259
  const [accuracyMetrics$1, setAccuracyMetrics] = React.useState({
162
260
  totalChanges: 0,
163
261
  totalCharactersEdited: 0,
@@ -167,6 +265,9 @@ function HtmlViewer({
167
265
  blockTypeStats: {}
168
266
  });
169
267
  const [isAnalyticsOpen, setIsAnalyticsOpen] = React.useState(false);
268
+ const [isTagModalOpen, setIsTagModalOpen] = React.useState(false);
269
+ const [selectedTag, setSelectedTag] = React.useState(null);
270
+ const [showSemanticTags, setShowSemanticTags] = React.useState(true);
170
271
  React.useEffect(() => {
171
272
  // Reset state when jsonData changes
172
273
  // console.log("HtmlViewer received new jsonData");
@@ -185,6 +286,10 @@ function HtmlViewer({
185
286
  setModalData(node);
186
287
  setIsModalOpen(true);
187
288
  };
289
+ const handleSemanticTagClick = tag => {
290
+ setSelectedTag(tag);
291
+ setIsTagModalOpen(true);
292
+ };
188
293
  const updateAccuracyMetrics = React.useCallback((nodeId, blockType, originalContent, newContent) => {
189
294
  const differences = accuracyMetrics.calculateDifferences(originalContent, newContent);
190
295
  const change = {
@@ -268,10 +373,7 @@ function HtmlViewer({
268
373
  };
269
374
  // Function to get the appropriate HTML content based on settings
270
375
  const getHtmlContent = node => {
271
- // For individual nodes in the tree view, just return their HTML directly
272
- // This preserves the node structure and click interactivity
273
- // getBlockHtml/jsonToHtml should only be used for full page rendering/export
274
- return cleanHtml(node.html || '');
376
+ return cleanHtml(node.html || "");
275
377
  };
276
378
  const mergedTablesMap = React.useMemo(() => {
277
379
  const map = new Map();
@@ -413,10 +515,13 @@ function HtmlViewer({
413
515
  const hasChildren = node.children && node.children.length > 0;
414
516
  const isTable = ((_a = node.block_type) === null || _a === void 0 ? void 0 : _a.toLowerCase()) === "table";
415
517
  const isTableOfContents = node.block_type === "TableOfContents";
416
- const isText = node.block_type === "Text";
417
- const isHandwritten = node.block_type === "Handwriting";
418
- const isSectionHeader = node.block_type === "SectionHeader";
518
+ node.block_type === "Text";
519
+ node.block_type === "Handwriting";
520
+ node.block_type === "SectionHeader";
419
521
  const isPage = node.block_type === "Page";
522
+ const isFigure = node.block_type === "Figure" || node.block_type === "Picture";
523
+ const isPageHeader = node.block_type === "PageHeader";
524
+ const isPageFooter = node.block_type === "PageFooter";
420
525
  // Get the appropriate HTML content
421
526
  const htmlContent = getHtmlContent(node);
422
527
  const isHeading = htmlContent && (htmlContent.startsWith("<h1") || htmlContent.startsWith("<h2") || htmlContent.startsWith("<h3") || htmlContent.startsWith("<h4"));
@@ -437,46 +542,64 @@ function HtmlViewer({
437
542
  onNodeClick(node.id);
438
543
  }
439
544
  };
440
- return jsxRuntime.jsxs("div", {
545
+ return jsxRuntime.jsx("div", {
441
546
  id: node.id,
442
- className: `p-2 my-1 rounded transition-colors relative group max-w-full overflow-hidden ${isSelected ? "bg-blue-100 border-2 border-blue-500" : ""} ${onNodeClick && !isPage ? "cursor-pointer hover:bg-gray-50" : ""}`,
547
+ className: `px-1 py-0.5 rounded transition-colors relative group max-w-full overflow-hidden ${isSelected ? "bg-blue-100 border-2 border-blue-500" : ""} ${onNodeClick && !isPage ? "cursor-pointer hover:bg-gray-50" : ""}`,
443
548
  onClick: isPage ? undefined : handleContentClick,
444
- children: [!isPage && showJsonIcons && jsxRuntime.jsx("button", {
445
- onClick: e => {
446
- e.stopPropagation(); // Prevent triggering parent onClick
447
- handleJsonClick(node);
448
- },
449
- className: `absolute right-2 top-2 z-10 ${showJsonIcons ? "opacity-0 group-hover:opacity-100 transition-opacity" : "hidden"} text-gray-500 hover:text-gray-700`,
450
- title: "View JSON",
451
- children: jsxRuntime.jsx(VscJsonIcon, {
452
- size: 18
453
- })
454
- }), isTable || isTableOfContents ? jsxRuntime.jsx(Table.default, {
549
+ children: isTable || isTableOfContents ? jsxRuntime.jsx(Table.default, {
455
550
  node: node,
456
551
  selectedBboxId: selectedBboxId,
457
552
  onJsonClick: handleJsonClick,
458
553
  onContentChange: handleContentChange,
459
554
  mergedTables: node.merged_table_id ? (mergedTablesMap.get(node.merged_table_id) || []).slice(1) : [],
460
555
  hasLlmHtml: !!node.llm_table_html,
461
- showJsonIcons: showJsonIcons,
462
- onNodeClick: onNodeClick
463
- }) : isText || isHandwritten || isSectionHeader ? jsxRuntime.jsx(EditableContent.default, {
464
- id: node.id,
465
- content: htmlContent,
466
- onContentChange: handleContentChange,
467
- isHeading: !!isHeading,
468
- onNodeClick: onNodeClick && !isPage ? () => onNodeClick(node.id) : undefined
469
- }) : jsxRuntime.jsxs(jsxRuntime.Fragment, {
470
- children: [jsxRuntime.jsx("div", {
471
- className: "prose max-w-none w-full overflow-hidden break-words",
556
+ showJsonIcons: false,
557
+ onNodeClick: onNodeClick,
558
+ isEditMode: viewMode === 'edit',
559
+ isJsonMode: viewMode === 'json'
560
+ }) : isFigure && node.images ? jsxRuntime.jsx("div", {
561
+ className: `my-2 ${viewMode === 'json' ? 'cursor-pointer hover:opacity-80' : ''}`,
562
+ onClick: viewMode === 'json' ? () => handleJsonClick(node) : undefined,
563
+ children: Object.entries(node.images).map(([key, imageData]) => jsxRuntime.jsx("img", {
564
+ src: typeof imageData === 'string' && imageData.startsWith('data:') ? imageData : `data:image/png;base64,${imageData}`,
565
+ alt: `Figure ${node.id}`,
566
+ className: "max-w-full h-auto rounded border border-gray-200"
567
+ }, key))
568
+ }) : isPageHeader || isPageFooter ? jsxRuntime.jsxs("div", {
569
+ className: `my-1 px-3 py-1.5 rounded-md border text-xs flex items-center gap-2
570
+ ${isPageHeader ? 'bg-amber-50 border-amber-200 text-amber-800' : 'bg-slate-50 border-slate-200 text-slate-600'}
571
+ ${viewMode === 'json' ? 'cursor-pointer hover:opacity-80' : ''}`,
572
+ onClick: viewMode === 'json' ? () => handleJsonClick(node) : undefined,
573
+ children: [jsxRuntime.jsx("span", {
574
+ className: `font-semibold uppercase tracking-wide ${isPageHeader ? 'text-amber-600' : 'text-slate-500'}`,
575
+ children: isPageHeader ? '⬆ Header' : '⬇ Footer'
576
+ }), htmlContent && jsxRuntime.jsx("span", {
577
+ className: "text-gray-600",
472
578
  dangerouslySetInnerHTML: {
473
579
  __html: htmlContent
474
580
  }
581
+ }), node.images && Object.entries(node.images).map(([key, imageData]) => jsxRuntime.jsx("img", {
582
+ src: typeof imageData === 'string' && imageData.startsWith('data:') ? imageData : `data:image/png;base64,${imageData}`,
583
+ alt: `${isPageHeader ? 'Header' : 'Footer'} image`,
584
+ className: "h-6 w-auto"
585
+ }, key))]
586
+ }) : jsxRuntime.jsxs(jsxRuntime.Fragment, {
587
+ children: [jsxRuntime.jsx(EditableContent.default, {
588
+ id: node.id,
589
+ content: htmlContent,
590
+ onContentChange: handleContentChange,
591
+ isHeading: !!isHeading,
592
+ isEditMode: viewMode === 'edit',
593
+ isJsonMode: viewMode === 'json',
594
+ onJsonClick: () => handleJsonClick(node),
595
+ onNodeClick: onNodeClick && !isPage ? () => onNodeClick(node.id) : undefined,
596
+ enableSemanticTags: showSemanticTags,
597
+ onSemanticTagClick: handleSemanticTagClick
475
598
  }), hasChildren && jsxRuntime.jsx("div", {
476
- className: "ml-4 mt-2 border-l-2 border-gray-200 pl-4 max-w-full overflow-hidden",
599
+ className: "ml-2 mt-1 border-l border-gray-200 pl-2 max-w-full overflow-hidden",
477
600
  children: node.children.map(child => renderHtmlContent(child))
478
601
  })]
479
- })]
602
+ })
480
603
  }, node.id);
481
604
  };
482
605
  const getAllNodes = data => {
@@ -494,63 +617,70 @@ function HtmlViewer({
494
617
  });
495
618
  };
496
619
  const allNodes = getAllNodes(editedData);
497
- // Update the header section to include both buttons
620
+ // Update the header section with toggle buttons
498
621
  const renderHeader = () => jsxRuntime.jsxs("div", {
499
- className: "sticky top-0 z-20 bg-white border-b border-gray-200 p-4 flex justify-between items-center flex-shrink-0",
500
- children: [jsxRuntime.jsx("div", {
501
- className: "flex gap-2",
502
- children: hasChanges && jsxRuntime.jsxs(jsxRuntime.Fragment, {
503
- children: [onSave && jsxRuntime.jsxs("button", {
504
- onClick: () => onSave(editedData),
505
- className: "inline-flex items-center gap-2 px-4 py-2 bg-green-600 text-white rounded-md hover:bg-green-700 focus:outline-none focus:ring-2 focus:ring-green-500 focus:ring-offset-2",
506
- title: "Save Changes",
507
- children: [jsxRuntime.jsx(FaFileDownloadIcon, {
508
- size: 18
509
- }), "Save"]
622
+ className: "sticky top-0 z-20 bg-white border-b border-gray-200 p-3 flex justify-between items-center flex-shrink-0",
623
+ children: [jsxRuntime.jsxs("div", {
624
+ className: "flex gap-1",
625
+ children: [jsxRuntime.jsxs("div", {
626
+ className: "flex bg-gray-100 rounded-lg p-1",
627
+ children: [jsxRuntime.jsxs("button", {
628
+ onClick: () => setViewMode(viewMode === 'json' ? 'read' : 'json'),
629
+ className: `inline-flex items-center gap-2 px-3 py-1.5 rounded-md transition-colors ${viewMode === 'json' ? "bg-blue-600 text-white shadow-sm" : "text-gray-600 hover:text-gray-900 hover:bg-gray-200"}`,
630
+ title: "JSON Mode - Click any element to view its JSON",
631
+ children: [jsxRuntime.jsx(VscJsonIcon, {
632
+ size: 16
633
+ }), jsxRuntime.jsx("span", {
634
+ className: "text-sm font-medium",
635
+ children: "JSON"
636
+ })]
510
637
  }), jsxRuntime.jsxs("button", {
511
- onClick: handleDownload,
512
- className: "inline-flex items-center gap-2 px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2",
513
- title: "Download Updated JSON",
514
- children: [jsxRuntime.jsx(FaFileDownloadIcon, {
515
- size: 18
516
- }), "Download"]
638
+ onClick: () => setViewMode(viewMode === 'edit' ? 'read' : 'edit'),
639
+ className: `inline-flex items-center gap-2 px-3 py-1.5 rounded-md transition-colors ${viewMode === 'edit' ? "bg-green-600 text-white shadow-sm" : "text-gray-600 hover:text-gray-900 hover:bg-gray-200"}`,
640
+ title: "Edit Mode - Click any element to edit it",
641
+ children: [jsxRuntime.jsx(FaEditIcon, {
642
+ size: 16
643
+ }), jsxRuntime.jsx("span", {
644
+ className: "text-sm font-medium",
645
+ children: "Edit"
646
+ })]
517
647
  })]
518
- })
519
- }), jsxRuntime.jsxs("div", {
520
- className: "flex gap-2 items-center",
521
- children: [jsxRuntime.jsx("button", {
522
- onClick: () => setIsAnalyticsOpen(true),
523
- className: "inline-flex items-center gap-2 px-4 py-2 bg-green-600 text-white rounded-md hover:bg-green-700 focus:outline-none focus:ring-2 focus:ring-green-500 focus:ring-offset-2",
524
- title: "View Analytics",
525
- children: jsxRuntime.jsx(FaChartBarIcon, {
526
- size: 18
527
- })
528
- }), jsxRuntime.jsxs("div", {
529
- className: "flex bg-gray-100 rounded-lg p-1",
530
- children: [jsxRuntime.jsx("button", {
531
- onClick: () => {
532
- setShowJsonIcons(true);
533
- setActiveFormat("Show JSON");
534
- },
535
- className: `py-1.5 px-3 text-sm font-medium rounded-md transition-colors whitespace-nowrap ${activeFormat === "Show JSON" ? "bg-white text-gray-900 shadow-sm" : "text-gray-600 hover:text-gray-900"}`,
536
- children: "Show JSON"
537
- }), jsxRuntime.jsx("button", {
538
- onClick: () => {
539
- setShowJsonIcons(false);
540
- setActiveFormat("Hide JSON");
541
- },
542
- className: `py-1.5 px-3 text-sm font-medium rounded-md transition-colors whitespace-nowrap ${activeFormat === "Hide JSON" ? "bg-white text-gray-900 shadow-sm" : "text-gray-600 hover:text-gray-900"}`,
543
- children: "Hide JSON"
648
+ }), jsxRuntime.jsxs("button", {
649
+ onClick: () => setShowSemanticTags(!showSemanticTags),
650
+ className: `inline-flex items-center gap-2 px-3 py-1.5 rounded-md transition-colors ml-2 ${showSemanticTags ? "bg-amber-100 text-amber-700 border border-amber-300" : "bg-gray-100 text-gray-500 border border-gray-200"}`,
651
+ title: showSemanticTags ? "Hide Semantic Tags" : "Show Semantic Tags",
652
+ children: [jsxRuntime.jsx(FaTagsIcon, {
653
+ size: 16
654
+ }), jsxRuntime.jsx("span", {
655
+ className: "text-sm font-medium",
656
+ children: "Tags"
657
+ })]
658
+ }), hasChanges && jsxRuntime.jsxs("button", {
659
+ onClick: handleDownload,
660
+ className: "inline-flex items-center gap-2 px-3 py-1.5 bg-purple-600 text-white rounded-md hover:bg-purple-700 focus:outline-none focus:ring-2 focus:ring-purple-500 focus:ring-offset-2 ml-2",
661
+ title: "Download Updated JSON",
662
+ children: [jsxRuntime.jsx(FaFileDownloadIcon, {
663
+ size: 16
664
+ }), jsxRuntime.jsx("span", {
665
+ className: "text-sm font-medium",
666
+ children: "Save"
544
667
  })]
545
668
  })]
669
+ }), jsxRuntime.jsx("button", {
670
+ onClick: () => setIsAnalyticsOpen(true),
671
+ className: "inline-flex items-center gap-1 px-3 py-1.5 text-gray-600 hover:text-gray-900 hover:bg-gray-100 rounded-md",
672
+ title: "View Analytics",
673
+ children: jsxRuntime.jsx(FaChartBarIcon, {
674
+ size: 16
675
+ })
546
676
  })]
547
677
  });
548
678
  return jsxRuntime.jsxs("div", {
549
679
  className: "w-full h-full max-w-full flex flex-col overflow-hidden",
550
680
  children: [renderHeader(), jsxRuntime.jsx("div", {
551
- className: "flex-1 overflow-auto min-h-0 max-w-full",
681
+ className: `flex-1 overflow-auto min-h-0 max-w-full ${viewMode === 'edit' ? 'bg-green-50/50' : viewMode === 'json' ? 'bg-blue-50/50' : ''}`,
552
682
  children: jsxRuntime.jsx("div", {
553
- className: "p-4 max-w-full",
683
+ className: "p-2 max-w-full",
554
684
  children: allNodes.map(node => jsxRuntime.jsx("div", {
555
685
  className: "w-full max-w-full overflow-hidden break-words",
556
686
  children: renderHtmlContent(node)
@@ -564,6 +694,10 @@ function HtmlViewer({
564
694
  isOpen: isModalOpen,
565
695
  onClose: () => setIsModalOpen(false),
566
696
  data: modalData
697
+ }), jsxRuntime.jsx(TagModal, {
698
+ isOpen: isTagModalOpen,
699
+ onClose: () => setIsTagModalOpen(false),
700
+ tag: selectedTag
567
701
  })]
568
702
  });
569
703
  }
@@ -0,0 +1,88 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var jsxRuntime = require('react/jsx-runtime');
6
+ var React = require('react');
7
+ var katex = require('katex');
8
+
9
+ /**
10
+ * Renders LaTeX math expressions in HTML content using KaTeX.
11
+ * Supports both inline ($...$) and display ($$...$$) math.
12
+ */
13
+ // Regex patterns for math delimiters
14
+ const DISPLAY_MATH_REGEX = /\$\$([\s\S]*?)\$\$/g;
15
+ const INLINE_MATH_REGEX = /\$([^\$\n]+?)\$/g;
16
+ /**
17
+ * Render a single LaTeX expression to HTML using KaTeX
18
+ */
19
+ function renderLatex(latex, displayMode) {
20
+ try {
21
+ return katex.renderToString(latex, {
22
+ displayMode,
23
+ throwOnError: false,
24
+ errorColor: "#cc0000",
25
+ strict: false,
26
+ trust: true,
27
+ macros: {
28
+ "\\leq": "\\le",
29
+ "\\geq": "\\ge"
30
+ }
31
+ });
32
+ } catch (error) {
33
+ console.warn("KaTeX render error:", error);
34
+ // Return the original with error styling
35
+ return `<span class="katex-error" style="color: #cc0000;">${latex}</span>`;
36
+ }
37
+ }
38
+ /**
39
+ * Process HTML content and render all math expressions
40
+ */
41
+ function renderMathInHtml(html) {
42
+ if (!html) return "";
43
+ // First, handle display math ($$...$$)
44
+ let result = html.replace(DISPLAY_MATH_REGEX, (match, latex) => {
45
+ return renderLatex(latex.trim(), true);
46
+ });
47
+ // Then, handle inline math ($...$)
48
+ // The $...$ pattern with closing $ is always math, not currency
49
+ result = result.replace(INLINE_MATH_REGEX, (match, latex) => {
50
+ return renderLatex(latex.trim(), false);
51
+ });
52
+ return result;
53
+ }
54
+ /**
55
+ * Check if content contains any math expressions
56
+ */
57
+ function containsMath(html) {
58
+ if (!html) return false;
59
+ return DISPLAY_MATH_REGEX.test(html) || INLINE_MATH_REGEX.test(html);
60
+ }
61
+ /**
62
+ * React component that renders HTML with math expressions
63
+ */
64
+ function MathContent({
65
+ html,
66
+ className = "",
67
+ as: Component = "span"
68
+ }) {
69
+ const renderedHtml = React.useMemo(() => renderMathInHtml(html), [html]);
70
+ return jsxRuntime.jsx(Component, {
71
+ className: className,
72
+ dangerouslySetInnerHTML: {
73
+ __html: renderedHtml
74
+ }
75
+ });
76
+ }
77
+ /**
78
+ * Hook to get rendered math HTML
79
+ */
80
+ function useMathHtml(html) {
81
+ return React.useMemo(() => renderMathInHtml(html), [html]);
82
+ }
83
+
84
+ exports.MathContent = MathContent;
85
+ exports.containsMath = containsMath;
86
+ exports.default = MathContent;
87
+ exports.renderMathInHtml = renderMathInHtml;
88
+ exports.useMathHtml = useMathHtml;
@@ -169,7 +169,7 @@ function PdfDocumentViewer({
169
169
  const currentPath = `${path}/${block.block_type}[${index}](${block.id})`;
170
170
  // Assuming block.polygon is number[][] (array of points)
171
171
  const points = block.polygon;
172
- if (points && Array.isArray(points) && points.length >= 2 && block.block_type !== "Page") {
172
+ if (points && Array.isArray(points) && points.length >= 2 && block.block_type !== "Page" && block.block_type !== "TableCell") {
173
173
  // Calculate bbox from points
174
174
  const x_coords = points.map(p => p[0]);
175
175
  const y_coords = points.map(p => p[1]);