@tfw.in/structura-lib 0.2.0 → 0.2.2

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 +72 -323
  2. package/dist/cjs/EditableContent.js +46 -18
  3. package/dist/cjs/HtmlViewer.js +238 -85
  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 +49 -76
  9. package/dist/cjs/Table.js +75 -8
  10. package/dist/cjs/TableCell.js +34 -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 +51 -19
  16. package/dist/esm/HtmlViewer.js +287 -103
  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 +57 -80
  22. package/dist/esm/Table.js +85 -8
  23. package/dist/esm/TableCell.js +34 -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 +8 -1
  30. package/dist/esm/types/HtmlViewer.d.ts +9 -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 +13 -8
  35. package/dist/esm/types/Table.d.ts +4 -1
  36. package/dist/esm/types/TableCell.d.ts +7 -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 +90 -10
  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,129 @@ 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
252
  onNodeClick,
153
- onSave
253
+ editMode: enableEditMode = true,
254
+ jsonMode: enableJsonMode = true,
255
+ mathRendering: enableMathRendering = true,
256
+ semanticTags: enableSemanticTags = true,
257
+ headerFooterBadges: enableHeaderFooterBadges = true,
258
+ defaultViewMode = 'read',
259
+ onContentChange: onContentChangeCallback,
260
+ onExport: onExportCallback
154
261
  }) {
155
262
  const [editedData, setEditedData] = React.useState(jsonData);
156
263
  const [hasChanges, setHasChanges] = React.useState(false);
157
264
  const [isModalOpen, setIsModalOpen] = React.useState(false);
158
265
  const [modalData, setModalData] = React.useState(null);
159
- const [showJsonIcons, setShowJsonIcons] = React.useState(true);
160
- const [activeFormat, setActiveFormat] = React.useState("Show JSON");
266
+ const [viewMode, setViewMode] = React.useState(defaultViewMode);
161
267
  const [accuracyMetrics$1, setAccuracyMetrics] = React.useState({
162
268
  totalChanges: 0,
163
269
  totalCharactersEdited: 0,
@@ -167,6 +273,9 @@ function HtmlViewer({
167
273
  blockTypeStats: {}
168
274
  });
169
275
  const [isAnalyticsOpen, setIsAnalyticsOpen] = React.useState(false);
276
+ const [isTagModalOpen, setIsTagModalOpen] = React.useState(false);
277
+ const [selectedTag, setSelectedTag] = React.useState(null);
278
+ const [showSemanticTags, setShowSemanticTags] = React.useState(enableSemanticTags);
170
279
  React.useEffect(() => {
171
280
  // Reset state when jsonData changes
172
281
  // console.log("HtmlViewer received new jsonData");
@@ -185,6 +294,10 @@ function HtmlViewer({
185
294
  setModalData(node);
186
295
  setIsModalOpen(true);
187
296
  };
297
+ const handleSemanticTagClick = tag => {
298
+ setSelectedTag(tag);
299
+ setIsTagModalOpen(true);
300
+ };
188
301
  const updateAccuracyMetrics = React.useCallback((nodeId, blockType, originalContent, newContent) => {
189
302
  const differences = accuracyMetrics.calculateDifferences(originalContent, newContent);
190
303
  const change = {
@@ -228,12 +341,13 @@ function HtmlViewer({
228
341
  }, [jsonData]);
229
342
  const handleContentChange = React.useCallback((nodeId, newContent) => {
230
343
  setHasChanges(true);
344
+ let originalContent = "";
231
345
  setEditedData(prevData => {
232
346
  const newData = JSON.parse(JSON.stringify(prevData));
233
347
  const updateNode = node => {
234
348
  if (!node) return false;
235
349
  if (node.id === nodeId) {
236
- const originalContent = node.html || "";
350
+ originalContent = node.html || "";
237
351
  const blockType = node.block_type || "unknown";
238
352
  // Update accuracy metrics before changing content
239
353
  updateAccuracyMetrics(nodeId, blockType, originalContent, newContent);
@@ -250,9 +364,17 @@ function HtmlViewer({
250
364
  updateNode(newData);
251
365
  return newData;
252
366
  });
253
- }, [updateAccuracyMetrics]);
367
+ // Call external callback if provided
368
+ if (onContentChangeCallback) {
369
+ onContentChangeCallback(nodeId, originalContent, newContent);
370
+ }
371
+ }, [updateAccuracyMetrics, onContentChangeCallback]);
254
372
  const handleDownload = () => {
255
373
  if (!editedData) return;
374
+ // Call external callback if provided
375
+ if (onExportCallback) {
376
+ onExportCallback(editedData);
377
+ }
256
378
  const jsonString = JSON.stringify(editedData, null, 2);
257
379
  const blob = new Blob([jsonString], {
258
380
  type: "application/json"
@@ -268,10 +390,7 @@ function HtmlViewer({
268
390
  };
269
391
  // Function to get the appropriate HTML content based on settings
270
392
  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 || '');
393
+ return cleanHtml(node.html || "");
275
394
  };
276
395
  const mergedTablesMap = React.useMemo(() => {
277
396
  const map = new Map();
@@ -413,10 +532,13 @@ function HtmlViewer({
413
532
  const hasChildren = node.children && node.children.length > 0;
414
533
  const isTable = ((_a = node.block_type) === null || _a === void 0 ? void 0 : _a.toLowerCase()) === "table";
415
534
  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";
535
+ node.block_type === "Text";
536
+ node.block_type === "Handwriting";
537
+ node.block_type === "SectionHeader";
419
538
  const isPage = node.block_type === "Page";
539
+ const isFigure = node.block_type === "Figure" || node.block_type === "Picture";
540
+ const isPageHeader = node.block_type === "PageHeader";
541
+ const isPageFooter = node.block_type === "PageFooter";
420
542
  // Get the appropriate HTML content
421
543
  const htmlContent = getHtmlContent(node);
422
544
  const isHeading = htmlContent && (htmlContent.startsWith("<h1") || htmlContent.startsWith("<h2") || htmlContent.startsWith("<h3") || htmlContent.startsWith("<h4"));
@@ -437,46 +559,66 @@ function HtmlViewer({
437
559
  onNodeClick(node.id);
438
560
  }
439
561
  };
440
- return jsxRuntime.jsxs("div", {
562
+ return jsxRuntime.jsx("div", {
441
563
  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" : ""}`,
564
+ 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
565
  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, {
566
+ children: isTable || isTableOfContents ? jsxRuntime.jsx(Table.default, {
455
567
  node: node,
456
568
  selectedBboxId: selectedBboxId,
457
569
  onJsonClick: handleJsonClick,
458
570
  onContentChange: handleContentChange,
459
571
  mergedTables: node.merged_table_id ? (mergedTablesMap.get(node.merged_table_id) || []).slice(1) : [],
460
572
  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",
573
+ showJsonIcons: false,
574
+ onNodeClick: onNodeClick,
575
+ isEditMode: viewMode === 'edit',
576
+ isJsonMode: viewMode === 'json',
577
+ enableMathRendering: enableMathRendering
578
+ }) : isFigure && node.images ? jsxRuntime.jsx("div", {
579
+ className: `my-2 ${viewMode === 'json' ? 'cursor-pointer hover:opacity-80' : ''}`,
580
+ onClick: viewMode === 'json' ? () => handleJsonClick(node) : undefined,
581
+ children: 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: `Figure ${node.id}`,
584
+ className: "max-w-full h-auto rounded border border-gray-200"
585
+ }, key))
586
+ }) : (isPageHeader || isPageFooter) && enableHeaderFooterBadges ? jsxRuntime.jsxs("div", {
587
+ className: `my-1 px-3 py-1.5 rounded-md border text-xs flex items-center gap-2
588
+ ${isPageHeader ? 'bg-amber-50 border-amber-200 text-amber-800' : 'bg-slate-50 border-slate-200 text-slate-600'}
589
+ ${viewMode === 'json' ? 'cursor-pointer hover:opacity-80' : ''}`,
590
+ onClick: viewMode === 'json' ? () => handleJsonClick(node) : undefined,
591
+ children: [jsxRuntime.jsx("span", {
592
+ className: `font-semibold uppercase tracking-wide ${isPageHeader ? 'text-amber-600' : 'text-slate-500'}`,
593
+ children: isPageHeader ? '⬆ Header' : '⬇ Footer'
594
+ }), htmlContent && jsxRuntime.jsx("span", {
595
+ className: "text-gray-600",
472
596
  dangerouslySetInnerHTML: {
473
597
  __html: htmlContent
474
598
  }
599
+ }), node.images && Object.entries(node.images).map(([key, imageData]) => jsxRuntime.jsx("img", {
600
+ src: typeof imageData === 'string' && imageData.startsWith('data:') ? imageData : `data:image/png;base64,${imageData}`,
601
+ alt: `${isPageHeader ? 'Header' : 'Footer'} image`,
602
+ className: "h-6 w-auto"
603
+ }, key))]
604
+ }) : jsxRuntime.jsxs(jsxRuntime.Fragment, {
605
+ children: [jsxRuntime.jsx(EditableContent.default, {
606
+ id: node.id,
607
+ content: htmlContent,
608
+ onContentChange: handleContentChange,
609
+ isHeading: !!isHeading,
610
+ isEditMode: viewMode === 'edit',
611
+ isJsonMode: viewMode === 'json',
612
+ onJsonClick: () => handleJsonClick(node),
613
+ onNodeClick: onNodeClick && !isPage ? () => onNodeClick(node.id) : undefined,
614
+ enableSemanticTags: showSemanticTags,
615
+ onSemanticTagClick: handleSemanticTagClick,
616
+ enableMathRendering: enableMathRendering
475
617
  }), hasChildren && jsxRuntime.jsx("div", {
476
- className: "ml-4 mt-2 border-l-2 border-gray-200 pl-4 max-w-full overflow-hidden",
618
+ className: "ml-2 mt-1 border-l border-gray-200 pl-2 max-w-full overflow-hidden",
477
619
  children: node.children.map(child => renderHtmlContent(child))
478
620
  })]
479
- })]
621
+ })
480
622
  }, node.id);
481
623
  };
482
624
  const getAllNodes = data => {
@@ -494,63 +636,70 @@ function HtmlViewer({
494
636
  });
495
637
  };
496
638
  const allNodes = getAllNodes(editedData);
497
- // Update the header section to include both buttons
639
+ // Update the header section with toggle buttons
498
640
  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"]
510
- }), 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"]
517
- })]
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", {
641
+ className: "sticky top-0 z-20 bg-white border-b border-gray-200 p-3 flex justify-between items-center flex-shrink-0",
642
+ children: [jsxRuntime.jsxs("div", {
643
+ className: "flex gap-1",
644
+ children: [(enableJsonMode || enableEditMode) && jsxRuntime.jsxs("div", {
529
645
  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"
646
+ children: [enableJsonMode && jsxRuntime.jsxs("button", {
647
+ onClick: () => setViewMode(viewMode === 'json' ? 'read' : 'json'),
648
+ 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"}`,
649
+ title: "JSON Mode - Click any element to view its JSON",
650
+ children: [jsxRuntime.jsx(VscJsonIcon, {
651
+ size: 16
652
+ }), jsxRuntime.jsx("span", {
653
+ className: "text-sm font-medium",
654
+ children: "JSON"
655
+ })]
656
+ }), enableEditMode && jsxRuntime.jsxs("button", {
657
+ onClick: () => setViewMode(viewMode === 'edit' ? 'read' : 'edit'),
658
+ 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"}`,
659
+ title: "Edit Mode - Click any element to edit it",
660
+ children: [jsxRuntime.jsx(FaEditIcon, {
661
+ size: 16
662
+ }), jsxRuntime.jsx("span", {
663
+ className: "text-sm font-medium",
664
+ children: "Edit"
665
+ })]
666
+ })]
667
+ }), enableSemanticTags && jsxRuntime.jsxs("button", {
668
+ onClick: () => setShowSemanticTags(!showSemanticTags),
669
+ 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"}`,
670
+ title: showSemanticTags ? "Hide Semantic Tags" : "Show Semantic Tags",
671
+ children: [jsxRuntime.jsx(FaTagsIcon, {
672
+ size: 16
673
+ }), jsxRuntime.jsx("span", {
674
+ className: "text-sm font-medium",
675
+ children: "Tags"
676
+ })]
677
+ }), hasChanges && jsxRuntime.jsxs("button", {
678
+ onClick: handleDownload,
679
+ 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",
680
+ title: "Download Updated JSON",
681
+ children: [jsxRuntime.jsx(FaFileDownloadIcon, {
682
+ size: 16
683
+ }), jsxRuntime.jsx("span", {
684
+ className: "text-sm font-medium",
685
+ children: "Save"
544
686
  })]
545
687
  })]
688
+ }), jsxRuntime.jsx("button", {
689
+ onClick: () => setIsAnalyticsOpen(true),
690
+ 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",
691
+ title: "View Analytics",
692
+ children: jsxRuntime.jsx(FaChartBarIcon, {
693
+ size: 16
694
+ })
546
695
  })]
547
696
  });
548
697
  return jsxRuntime.jsxs("div", {
549
698
  className: "w-full h-full max-w-full flex flex-col overflow-hidden",
550
699
  children: [renderHeader(), jsxRuntime.jsx("div", {
551
- className: "flex-1 overflow-auto min-h-0 max-w-full",
700
+ className: `flex-1 overflow-auto min-h-0 max-w-full ${viewMode === 'edit' ? 'bg-green-50/50' : viewMode === 'json' ? 'bg-blue-50/50' : ''}`,
552
701
  children: jsxRuntime.jsx("div", {
553
- className: "p-4 max-w-full",
702
+ className: "p-2 max-w-full",
554
703
  children: allNodes.map(node => jsxRuntime.jsx("div", {
555
704
  className: "w-full max-w-full overflow-hidden break-words",
556
705
  children: renderHtmlContent(node)
@@ -564,6 +713,10 @@ function HtmlViewer({
564
713
  isOpen: isModalOpen,
565
714
  onClose: () => setIsModalOpen(false),
566
715
  data: modalData
716
+ }), jsxRuntime.jsx(TagModal, {
717
+ isOpen: isTagModalOpen,
718
+ onClose: () => setIsTagModalOpen(false),
719
+ tag: selectedTag
567
720
  })]
568
721
  });
569
722
  }
@@ -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]);