@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
@@ -0,0 +1,189 @@
1
+ 'use strict';
2
+
3
+ // Semantic Tag Parser for SOP/Batch Record domain tags
4
+ // Supports: MeasurementTag, HiddenMeasurementTag, ValueTag, DateTimeTag, FileTag, ImageTag
5
+ // Regular expression patterns for tag detection
6
+ // Standard tag: [[value][type:TYPE][attr:value]...]
7
+ // Hidden tag: {[value][type:TYPE][attr:value]...]}
8
+ const STANDARD_TAG_PATTERN = /\[\[([^\]]+)\](\[([^\]]+)\])*\]/g;
9
+ const HIDDEN_TAG_PATTERN = /\{\[([^\]]+)\](\[([^\]]+)\])*\]\}/g;
10
+ // Parse a single tag's attribute string like "[type:MEASUREMENT][uom:mL/min][id:abc-123]"
11
+ function parseTagAttributes(attributeStr) {
12
+ const attributes = {};
13
+ let type = 'VALUE'; // Default type
14
+ // Match all [key:value] pairs
15
+ const attrPattern = /\[([^:\]]+):([^\]]+)\]/g;
16
+ let match;
17
+ while ((match = attrPattern.exec(attributeStr)) !== null) {
18
+ const key = match[1].toLowerCase();
19
+ const value = match[2];
20
+ switch (key) {
21
+ case 'type':
22
+ type = value.toUpperCase();
23
+ break;
24
+ case 'uom':
25
+ attributes.uom = value;
26
+ break;
27
+ case 'id':
28
+ attributes.id = value;
29
+ break;
30
+ case 'format':
31
+ attributes.format = value;
32
+ break;
33
+ case 'filter':
34
+ attributes.filter = value;
35
+ break;
36
+ }
37
+ }
38
+ return {
39
+ type,
40
+ attributes
41
+ };
42
+ }
43
+ // Parse a single tag match
44
+ function parseTag(match, isHidden) {
45
+ const rawMatch = match[0];
46
+ const startIndex = match.index;
47
+ const endIndex = startIndex + rawMatch.length;
48
+ // Extract the value (first bracket content) and attributes (remaining brackets)
49
+ let innerContent;
50
+ if (isHidden) {
51
+ // Remove {[ and ]} wrapper
52
+ innerContent = rawMatch.slice(2, -2);
53
+ } else {
54
+ // Remove [[ and ]] wrapper (but there might be more brackets for attributes)
55
+ innerContent = rawMatch.slice(2, -1);
56
+ }
57
+ // Split by ][ to get value and attributes
58
+ const parts = innerContent.split('][');
59
+ const value = parts[0];
60
+ const attributeStr = parts.length > 1 ? '[' + parts.slice(1).join('][') + ']' : '';
61
+ const {
62
+ type,
63
+ attributes
64
+ } = parseTagAttributes(attributeStr);
65
+ // Adjust type for hidden tags
66
+ const finalType = isHidden ? 'HIDDEN_MEASUREMENT' : type;
67
+ return {
68
+ type: finalType,
69
+ value,
70
+ rawMatch,
71
+ startIndex,
72
+ endIndex,
73
+ attributes,
74
+ isHidden
75
+ };
76
+ }
77
+ // Main parsing function
78
+ function parseSemanticTags(text) {
79
+ const tags = [];
80
+ // Find all standard tags
81
+ let match;
82
+ const standardPattern = new RegExp(STANDARD_TAG_PATTERN.source, 'g');
83
+ while ((match = standardPattern.exec(text)) !== null) {
84
+ tags.push(parseTag(match, false));
85
+ }
86
+ // Find all hidden tags
87
+ const hiddenPattern = new RegExp(HIDDEN_TAG_PATTERN.source, 'g');
88
+ while ((match = hiddenPattern.exec(text)) !== null) {
89
+ tags.push(parseTag(match, true));
90
+ }
91
+ // Sort tags by start index
92
+ tags.sort((a, b) => a.startIndex - b.startIndex);
93
+ // Build segments
94
+ const segments = [];
95
+ let currentIndex = 0;
96
+ for (const tag of tags) {
97
+ // Add text before this tag
98
+ if (tag.startIndex > currentIndex) {
99
+ segments.push({
100
+ type: 'text',
101
+ content: text.slice(currentIndex, tag.startIndex)
102
+ });
103
+ }
104
+ // Add the tag segment (skip hidden tags in output)
105
+ if (!tag.isHidden) {
106
+ segments.push({
107
+ type: 'tag',
108
+ content: tag.value,
109
+ tag
110
+ });
111
+ }
112
+ currentIndex = tag.endIndex;
113
+ }
114
+ // Add remaining text after last tag
115
+ if (currentIndex < text.length) {
116
+ segments.push({
117
+ type: 'text',
118
+ content: text.slice(currentIndex)
119
+ });
120
+ }
121
+ return {
122
+ originalText: text,
123
+ tags,
124
+ segments
125
+ };
126
+ }
127
+ // Check if text contains any semantic tags
128
+ function hasSemanticTags(text) {
129
+ if (!text) return false;
130
+ return STANDARD_TAG_PATTERN.test(text) || HIDDEN_TAG_PATTERN.test(text);
131
+ }
132
+ // Get tag type display info (for styling)
133
+ function getTagTypeInfo(type) {
134
+ switch (type) {
135
+ case 'MEASUREMENT':
136
+ return {
137
+ color: 'text-blue-700',
138
+ bgColor: 'bg-blue-100 border-blue-300',
139
+ icon: '📏',
140
+ label: 'Measurement'
141
+ };
142
+ case 'VALUE':
143
+ return {
144
+ color: 'text-emerald-700',
145
+ bgColor: 'bg-emerald-100 border-emerald-300',
146
+ icon: '📝',
147
+ label: 'Value'
148
+ };
149
+ case 'FILE':
150
+ return {
151
+ color: 'text-purple-700',
152
+ bgColor: 'bg-purple-100 border-purple-300',
153
+ icon: '📄',
154
+ label: 'File'
155
+ };
156
+ case 'IMAGE':
157
+ return {
158
+ color: 'text-pink-700',
159
+ bgColor: 'bg-pink-100 border-pink-300',
160
+ icon: '🖼️',
161
+ label: 'Image'
162
+ };
163
+ case 'DATETIME':
164
+ return {
165
+ color: 'text-orange-700',
166
+ bgColor: 'bg-orange-100 border-orange-300',
167
+ icon: '📅',
168
+ label: 'DateTime'
169
+ };
170
+ case 'HIDDEN_MEASUREMENT':
171
+ return {
172
+ color: 'text-gray-500',
173
+ bgColor: 'bg-gray-100 border-gray-300',
174
+ icon: '👁️‍🗨️',
175
+ label: 'Hidden'
176
+ };
177
+ default:
178
+ return {
179
+ color: 'text-gray-700',
180
+ bgColor: 'bg-gray-100 border-gray-300',
181
+ icon: '🏷️',
182
+ label: 'Tag'
183
+ };
184
+ }
185
+ }
186
+
187
+ exports.getTagTypeInfo = getTagTypeInfo;
188
+ exports.hasSemanticTags = hasSemanticTags;
189
+ exports.parseSemanticTags = parseSemanticTags;
@@ -0,0 +1,135 @@
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 SemanticTagParser = require('./SemanticTagParser.js');
8
+
9
+ // Individual tag badge component
10
+ function TagBadge({
11
+ tag,
12
+ showTooltip = true,
13
+ onClick
14
+ }) {
15
+ const [isHovered, setIsHovered] = React.useState(false);
16
+ const typeInfo = SemanticTagParser.getTagTypeInfo(tag.type);
17
+ return jsxRuntime.jsxs("span", {
18
+ className: `relative inline-flex items-center gap-1 px-1.5 py-0.5 mx-0.5 rounded border text-xs font-medium cursor-default transition-all
19
+ ${typeInfo.bgColor} ${typeInfo.color}
20
+ ${onClick ? 'cursor-pointer hover:shadow-md' : ''}
21
+ ${isHovered ? 'ring-2 ring-offset-1 ring-blue-400' : ''}`,
22
+ onMouseEnter: () => setIsHovered(true),
23
+ onMouseLeave: () => setIsHovered(false),
24
+ onClick: onClick,
25
+ children: [jsxRuntime.jsx("span", {
26
+ className: "text-[10px]",
27
+ children: typeInfo.icon
28
+ }), jsxRuntime.jsx("span", {
29
+ children: tag.value
30
+ }), tag.attributes.uom && jsxRuntime.jsx("span", {
31
+ className: "text-[10px] opacity-75 ml-0.5",
32
+ children: tag.attributes.uom
33
+ }), showTooltip && isHovered && jsxRuntime.jsx("div", {
34
+ className: "absolute bottom-full left-1/2 -translate-x-1/2 mb-2 z-50 pointer-events-none",
35
+ children: jsxRuntime.jsxs("div", {
36
+ className: "bg-gray-900 text-white text-xs rounded-lg px-3 py-2 shadow-lg whitespace-nowrap",
37
+ children: [jsxRuntime.jsxs("div", {
38
+ className: "font-semibold mb-1",
39
+ children: [typeInfo.label, " Tag"]
40
+ }), jsxRuntime.jsxs("div", {
41
+ className: "space-y-0.5 text-gray-300",
42
+ children: [jsxRuntime.jsxs("div", {
43
+ children: ["Value: ", jsxRuntime.jsx("span", {
44
+ className: "text-white",
45
+ children: tag.value
46
+ })]
47
+ }), tag.attributes.uom && jsxRuntime.jsxs("div", {
48
+ children: ["Unit: ", jsxRuntime.jsx("span", {
49
+ className: "text-white",
50
+ children: tag.attributes.uom
51
+ })]
52
+ }), tag.attributes.id && jsxRuntime.jsxs("div", {
53
+ className: "text-[10px] text-gray-400 truncate max-w-[200px]",
54
+ children: ["ID: ", tag.attributes.id]
55
+ }), tag.attributes.format && jsxRuntime.jsxs("div", {
56
+ children: ["Format: ", jsxRuntime.jsx("span", {
57
+ className: "text-white",
58
+ children: tag.attributes.format
59
+ })]
60
+ })]
61
+ }), jsxRuntime.jsx("div", {
62
+ className: "absolute top-full left-1/2 -translate-x-1/2 -mt-px",
63
+ children: jsxRuntime.jsx("div", {
64
+ className: "border-4 border-transparent border-t-gray-900"
65
+ })
66
+ })]
67
+ })
68
+ })]
69
+ });
70
+ }
71
+ // Main renderer component
72
+ function SemanticTagRenderer({
73
+ content,
74
+ className = '',
75
+ showTooltips = true,
76
+ onTagClick
77
+ }) {
78
+ // If no semantic tags, just return the content as-is
79
+ if (!SemanticTagParser.hasSemanticTags(content)) {
80
+ return jsxRuntime.jsx("span", {
81
+ className: className,
82
+ dangerouslySetInnerHTML: {
83
+ __html: content
84
+ }
85
+ });
86
+ }
87
+ const parsed = SemanticTagParser.parseSemanticTags(content);
88
+ return jsxRuntime.jsx("span", {
89
+ className: className,
90
+ children: parsed.segments.map((segment, index) => {
91
+ if (segment.type === 'text') {
92
+ // Render plain text (may contain HTML)
93
+ return jsxRuntime.jsx("span", {
94
+ dangerouslySetInnerHTML: {
95
+ __html: segment.content
96
+ }
97
+ }, index);
98
+ } else if (segment.type === 'tag' && segment.tag) {
99
+ // Render semantic tag badge
100
+ return jsxRuntime.jsx(TagBadge, {
101
+ tag: segment.tag,
102
+ showTooltip: showTooltips,
103
+ onClick: onTagClick ? () => onTagClick(segment.tag) : undefined
104
+ }, index);
105
+ }
106
+ return null;
107
+ })
108
+ });
109
+ }
110
+ function SmartContent({
111
+ content,
112
+ className = '',
113
+ enableSemanticTags = true,
114
+ showTooltips = true,
115
+ onTagClick
116
+ }) {
117
+ if (enableSemanticTags && SemanticTagParser.hasSemanticTags(content)) {
118
+ return jsxRuntime.jsx(SemanticTagRenderer, {
119
+ content: content,
120
+ className: className,
121
+ showTooltips: showTooltips,
122
+ onTagClick: onTagClick
123
+ });
124
+ }
125
+ // Regular content without semantic tags
126
+ return jsxRuntime.jsx("span", {
127
+ className: className,
128
+ dangerouslySetInnerHTML: {
129
+ __html: content
130
+ }
131
+ });
132
+ }
133
+
134
+ exports.SmartContent = SmartContent;
135
+ exports.default = SemanticTagRenderer;
@@ -12,7 +12,6 @@ var PdfDocumentViewer = require('./PdfDocumentViewer.js');
12
12
  var HtmlViewer = require('./HtmlViewer.js');
13
13
  var preprocessData = require('./helpers/preprocessData.js');
14
14
  var reactResizablePanels = require('react-resizable-panels');
15
- var badge = require('./ui/badge.js');
16
15
  var progress = require('./ui/progress.js');
17
16
  var scrollArea = require('./ui/scroll-area.js');
18
17
  var route = require('./route.js');
@@ -56,7 +55,7 @@ function Structura({
56
55
  const [selectedField, setSelectedField] = React.useState(null);
57
56
  const [file, setFile] = React.useState(null);
58
57
  const [fileUrl, setFileUrl] = React.useState(initialPdfPath || null);
59
- const [jsonData, setJsonData] = React.useState(null);
58
+ const [jsonData, setJsonData] = React.useState(initialJsonData || null);
60
59
  const [allowedPages, setAllowedPages] = React.useState(null);
61
60
  const pdfWrapperRef = React.useRef(null);
62
61
  const [flattenedFields, setFlattenedFields] = React.useState([]);
@@ -68,18 +67,18 @@ function Structura({
68
67
  const [activeTab, setActiveTab] = React.useState("pdf");
69
68
  const [backendStatus, setBackendStatus] = React.useState('checking');
70
69
  const [isDraggingOver, setIsDraggingOver] = React.useState(false); //NOSONAR
71
- // Load initial JSON data if provided (for faster iteration without API calls)
70
+ // Sync initialJsonData prop with state when it changes
72
71
  React.useEffect(() => {
73
- var _a;
74
72
  if (initialJsonData) {
75
- console.log('[Structura] Loading initial JSON data from props');
76
73
  setJsonData(initialJsonData);
77
- const pageCount = ((_a = initialJsonData.children) === null || _a === void 0 ? void 0 : _a.length) || 0;
78
- setAllowedPages(Array.from({
79
- length: pageCount
80
- }, (_, i) => i + 1));
81
74
  }
82
75
  }, [initialJsonData]);
76
+ // Sync initialPdfPath prop with state when it changes
77
+ React.useEffect(() => {
78
+ if (initialPdfPath) {
79
+ setFileUrl(initialPdfPath);
80
+ }
81
+ }, [initialPdfPath]);
83
82
  React.useEffect(() => {
84
83
  const handleDragOver = e => {
85
84
  e.preventDefault();
@@ -273,12 +272,10 @@ function Structura({
273
272
  // Add logging to the useEffect that watches isFileLoaded
274
273
  React.useEffect(() => {
275
274
  // console.log("!!! [Structura] isFileLoaded changed:", isFileLoaded);
276
- // Skip API call if we already have initialJsonData (cached mode)
275
+ // Skip auto-generation if we already have JSON data from props
277
276
  if (isFileLoaded && !initialJsonData) {
278
277
  // console.log("!!! [Structura] Calling handleGenerateJSON due to isFileLoaded");
279
278
  handleGenerateJSON();
280
- } else if (isFileLoaded && initialJsonData) {
281
- console.log('[Structura] Skipping API call - using cached JSON data');
282
279
  }
283
280
  }, [isFileLoaded, initialJsonData]);
284
281
  // Handle removing the PDF and resetting state
@@ -455,28 +452,7 @@ function Structura({
455
452
  }
456
453
  return jsxRuntime.jsxs("div", {
457
454
  className: "h-screen max-h-screen flex flex-col bg-background text-foreground",
458
- children: [jsxRuntime.jsx("header", {
459
- className: "border-b border-border bg-card p-4",
460
- children: jsxRuntime.jsxs("div", {
461
- className: "container mx-auto flex justify-between items-center",
462
- children: [jsxRuntime.jsxs("div", {
463
- className: "flex items-center space-x-2",
464
- children: [jsxRuntime.jsx(LucideIcons__namespace.FileText, {
465
- className: "h-6 w-6 text-primary"
466
- }), jsxRuntime.jsx("h1", {
467
- className: "text-2xl mt-2.5 font-bold",
468
- children: "PLAYGROUND"
469
- })]
470
- }), jsxRuntime.jsx("div", {
471
- className: "flex items-center gap-2",
472
- children: jsxRuntime.jsx(badge.Badge, {
473
- variant: "outline",
474
- className: "px-3 py-1 text-xs",
475
- children: isFileLoaded ? "Document Loaded" : "No Document"
476
- })
477
- })]
478
- })
479
- }), jsxRuntime.jsx("div", {
455
+ children: [jsxRuntime.jsx("div", {
480
456
  className: "md:hidden",
481
457
  children: jsxRuntime.jsxs(tabs.Tabs, {
482
458
  value: activeTab,
@@ -607,8 +583,7 @@ function Structura({
607
583
  jsonData: jsonData,
608
584
  selectedBboxId: selectedBboxId,
609
585
  isLoading: isLoading,
610
- onNodeClick: handleHtmlNodeClick,
611
- onSave: props.onSave
586
+ onNodeClick: handleHtmlNodeClick
612
587
  })
613
588
  })
614
589
  })]
@@ -621,22 +596,10 @@ function Structura({
621
596
  children: [jsxRuntime.jsx(reactResizablePanels.Panel, {
622
597
  minSize: 20,
623
598
  defaultSize: 50,
624
- children: jsxRuntime.jsxs(card.Card, {
599
+ children: jsxRuntime.jsx(card.Card, {
625
600
  className: "h-full border-0 rounded-none",
626
- children: [jsxRuntime.jsx(card.CardHeader, {
627
- className: "py-3 px-4 border-b bg-muted/30",
628
- children: jsxRuntime.jsxs(card.CardTitle, {
629
- className: "text-base flex items-center gap-2",
630
- children: [jsxRuntime.jsx(LucideIcons__namespace.FileText, {
631
- className: "h-4 w-4"
632
- }), "PDF Document", file && jsxRuntime.jsx(badge.Badge, {
633
- variant: "secondary",
634
- className: "ml-2",
635
- children: file.name
636
- })]
637
- })
638
- }), jsxRuntime.jsx(card.CardContent, {
639
- className: "p-0 flex-1 flex flex-col overflow-hidden",
601
+ children: jsxRuntime.jsx(card.CardContent, {
602
+ className: "p-0 flex-1 flex flex-col overflow-hidden h-full",
640
603
  children: jsxRuntime.jsx(scrollArea.ScrollArea, {
641
604
  className: "w-full h-[calc(100vh-220px)]",
642
605
  children: fileUrl ? jsxRuntime.jsx(PdfDocumentViewer.default, {
@@ -731,33 +694,24 @@ function Structura({
731
694
  })
732
695
  })
733
696
  })
734
- })]
697
+ })
735
698
  })
736
699
  }), jsxRuntime.jsx(reactResizablePanels.PanelResizeHandle, {
737
700
  className: "w-1.5 bg-black hover:bg-primary/20 transition-colors cursor-col-resize"
738
701
  }), jsxRuntime.jsx(reactResizablePanels.Panel, {
739
702
  minSize: 20,
740
703
  defaultSize: 50,
741
- children: jsxRuntime.jsxs(card.Card, {
704
+ children: jsxRuntime.jsx(card.Card, {
742
705
  className: "h-full border-0 rounded-none",
743
- children: [jsxRuntime.jsx(card.CardHeader, {
744
- className: "py-3 px-4 border-b bg-muted/30",
745
- children: jsxRuntime.jsxs(card.CardTitle, {
746
- className: "text-base flex items-center gap-2",
747
- children: [jsxRuntime.jsx(LucideIcons__namespace.FileText, {
748
- className: "h-4 w-4"
749
- }), "HTML Structure"]
750
- })
751
- }), jsxRuntime.jsx(card.CardContent, {
752
- className: "p-0 h-[calc(100%-56px)] relative overflow-hidden",
706
+ children: jsxRuntime.jsx(card.CardContent, {
707
+ className: "p-0 h-full relative overflow-hidden",
753
708
  children: jsxRuntime.jsx("div", {
754
709
  className: "absolute inset-0",
755
710
  children: jsonData ? jsxRuntime.jsx(HtmlViewer.default, {
756
711
  jsonData: jsonData,
757
712
  selectedBboxId: selectedBboxId,
758
713
  isLoading: isLoading,
759
- onNodeClick: handleHtmlNodeClick,
760
- onSave: props.onSave
714
+ onNodeClick: handleHtmlNodeClick
761
715
  }) : jsxRuntime.jsx("div", {
762
716
  className: "flex flex-col items-center justify-center h-full p-8 text-center",
763
717
  children: jsxRuntime.jsxs("div", {
@@ -774,7 +728,7 @@ function Structura({
774
728
  })
775
729
  })
776
730
  })
777
- })]
731
+ })
778
732
  })
779
733
  })]
780
734
  })
package/dist/cjs/Table.js CHANGED
@@ -35,7 +35,9 @@ function Table({
35
35
  mergedTables = [],
36
36
  hasLlmHtml = false,
37
37
  showJsonIcons = true,
38
- onNodeClick
38
+ onNodeClick,
39
+ isEditMode = false,
40
+ isJsonMode = false
39
41
  }) {
40
42
  const [useLlmHtml, setUseLlmHtml] = React.useState(false);
41
43
  // Get the appropriate HTML content
@@ -56,7 +58,40 @@ function Table({
56
58
  const processTable = tableNode => {
57
59
  const tempDiv = document.createElement("div");
58
60
  tempDiv.innerHTML = getHtmlContent(tableNode);
59
- const tableElement = tempDiv.querySelector("table");
61
+ let tableElement = tempDiv.querySelector("table");
62
+ // If no table element found but we have children, reconstruct from children
63
+ if (!tableElement && tableNode.children && tableNode.children.length > 0) {
64
+ const table = document.createElement("table");
65
+ table.className = "w-full border-collapse";
66
+ const tbody = document.createElement("tbody");
67
+ // Determine columns: assume 4 columns for typical signature tables
68
+ const colCount = 4;
69
+ let currentRow = null;
70
+ let cellsInRow = 0;
71
+ tableNode.children.forEach((child, index) => {
72
+ var _a, _b;
73
+ if (child.block_type === "TableCell") {
74
+ if (cellsInRow === 0 || cellsInRow >= colCount) {
75
+ currentRow = document.createElement("tr");
76
+ tbody.appendChild(currentRow);
77
+ cellsInRow = 0;
78
+ }
79
+ const isHeader = ((_a = child.html) === null || _a === void 0 ? void 0 : _a.includes("<th")) || index < colCount;
80
+ const cell = document.createElement(isHeader ? "th" : "td");
81
+ // Strip wrapper th/td tags from child HTML
82
+ const cellHtml = ((_b = child.html) === null || _b === void 0 ? void 0 : _b.replace(/<\/?t[hd][^>]*>/gi, "")) || "";
83
+ cell.innerHTML = cellHtml;
84
+ cell.className = "px-4 py-2 border border-gray-200 text-left";
85
+ cell.id = child.id;
86
+ currentRow.appendChild(cell);
87
+ cellsInRow++;
88
+ }
89
+ });
90
+ table.appendChild(tbody);
91
+ tempDiv.innerHTML = "";
92
+ tempDiv.appendChild(table);
93
+ tableElement = table;
94
+ }
60
95
  if (!tableElement) return null;
61
96
  // Get all rows
62
97
  const rows = Array.from(tableElement.querySelectorAll("tr"));
@@ -79,14 +114,34 @@ function Table({
79
114
  // Combine rows from main table and merged tables
80
115
  const allRows = [...mainTable.rows, ...mergedRows];
81
116
  // Map cells to children
117
+ let globalCellCounter = 0;
82
118
  const processedRows = allRows.map((row, rowIndex) => {
83
119
  const cells = Array.from(row.querySelectorAll("th, td"));
84
120
  return cells.map((cell, colIndex) => {
85
121
  var _a;
86
- const globalCellIndex = rowIndex * cells.length + colIndex;
87
- const childNode = (_a = node.children) === null || _a === void 0 ? void 0 : _a[globalCellIndex];
122
+ const cellElement = cell;
123
+ const childNode = (_a = node.children) === null || _a === void 0 ? void 0 : _a[globalCellCounter];
124
+ globalCellCounter++;
88
125
  let cellContent = cleanHtml(cell.innerHTML);
89
126
  const isHeaderCell = cell.tagName.toLowerCase() === "th"; // Check if this is a header cell
127
+ // Extract rowspan and colspan attributes
128
+ const rowSpan = cellElement.rowSpan > 1 ? cellElement.rowSpan : undefined;
129
+ const colSpan = cellElement.colSpan > 1 ? cellElement.colSpan : undefined;
130
+ // Extract style attribute
131
+ const styleAttr = cellElement.getAttribute("style");
132
+ let cellStyle;
133
+ if (styleAttr) {
134
+ // Parse inline style string to style object
135
+ cellStyle = {};
136
+ styleAttr.split(";").forEach(rule => {
137
+ const [property, value] = rule.split(":").map(s => s.trim());
138
+ if (property && value) {
139
+ // Convert kebab-case to camelCase
140
+ const camelProperty = property.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());
141
+ cellStyle[camelProperty] = value;
142
+ }
143
+ });
144
+ }
90
145
  // If we should use TableCell content and we have a childNode with llm_table_html
91
146
  if (shouldUseTableCellContents() && childNode && childNode.llm_table_html !== undefined) {
92
147
  cellContent = useLlmHtml && childNode.llm_table_html ? cleanHtml(childNode.llm_table_html) : cleanHtml(childNode.html || "");
@@ -94,12 +149,15 @@ function Table({
94
149
  // TODO: to be fixed later - only apply dubious highlighting if the cell has llm_table_html
95
150
  const isDubious = (childNode === null || childNode === void 0 ? void 0 : childNode.dubious) && (childNode === null || childNode === void 0 ? void 0 : childNode.llm_table_html) !== undefined ? true : false;
96
151
  return {
97
- id: (childNode === null || childNode === void 0 ? void 0 : childNode.id) || `${node.id}-cell-${globalCellIndex}`,
152
+ id: (childNode === null || childNode === void 0 ? void 0 : childNode.id) || `${node.id}-cell-${globalCellCounter}`,
98
153
  content: cellContent,
99
154
  isHeader: isHeaderCell,
100
155
  isDubious: isDubious,
156
+ rowSpan,
157
+ colSpan,
158
+ style: cellStyle,
101
159
  nodeData: childNode || {
102
- id: `${node.id}-cell-${globalCellIndex}`,
160
+ id: `${node.id}-cell-${globalCellCounter}`,
103
161
  html: cellContent,
104
162
  block_type: "TableCell"
105
163
  }
@@ -140,9 +198,11 @@ function Table({
140
198
  }), jsxRuntime.jsx("div", {
141
199
  className: "w-full overflow-x-auto",
142
200
  children: jsxRuntime.jsx("table", {
143
- className: "min-w-full divide-y divide-gray-200",
201
+ className: "min-w-full structura-bg-white",
144
202
  children: jsxRuntime.jsx("tbody", {
203
+ className: "structura-bg-white",
145
204
  children: processedRows.map((row, rowIndex) => jsxRuntime.jsx("tr", {
205
+ className: "structura-bg-white",
146
206
  children: row.map(cell => jsxRuntime.jsx(TableCell.default, {
147
207
  id: cell.id,
148
208
  content: cell.content,
@@ -150,9 +210,14 @@ function Table({
150
210
  isSelected: cell.id === selectedBboxId,
151
211
  isHeader: cell.isHeader,
152
212
  isDubious: cell.isDubious,
213
+ rowSpan: cell.rowSpan,
214
+ colSpan: cell.colSpan,
215
+ style: cell.style,
153
216
  onContentChange: onContentChange ? newContent => handleCellContentChange(cell.id, newContent) : undefined,
154
217
  showJsonIcons: showJsonIcons,
155
- onNodeClick: onNodeClick ? () => onNodeClick(cell.id) : undefined
218
+ onNodeClick: onNodeClick ? () => onNodeClick(node.id) : undefined,
219
+ isEditMode: isEditMode,
220
+ isJsonMode: isJsonMode
156
221
  }, cell.id))
157
222
  }, rowIndex))
158
223
  })