@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.
- package/README.md +9 -361
- package/dist/cjs/EditableContent.js +45 -18
- package/dist/cjs/HtmlViewer.js +217 -83
- package/dist/cjs/MathRenderer.js +88 -0
- package/dist/cjs/PdfDocumentViewer.js +1 -1
- package/dist/cjs/SemanticTagParser.js +189 -0
- package/dist/cjs/SemanticTagRenderer.js +135 -0
- package/dist/cjs/Structura.js +20 -66
- package/dist/cjs/Table.js +73 -8
- package/dist/cjs/TableCell.js +33 -10
- package/dist/cjs/index.js +12 -0
- package/dist/cjs/node_modules/react-icons/fa/index.esm.js +6 -0
- package/dist/cjs/styles.css +2 -4
- package/dist/cjs/styles.css.map +1 -1
- package/dist/esm/EditableContent.js +49 -19
- package/dist/esm/HtmlViewer.js +259 -100
- package/dist/esm/MathRenderer.js +85 -0
- package/dist/esm/PdfDocumentViewer.js +1 -1
- package/dist/esm/SemanticTagParser.js +187 -0
- package/dist/esm/SemanticTagRenderer.js +140 -0
- package/dist/esm/Structura.js +21 -69
- package/dist/esm/Table.js +82 -8
- package/dist/esm/TableCell.js +32 -6
- package/dist/esm/index.js +3 -0
- package/dist/esm/node_modules/react-icons/fa/index.esm.js +5 -1
- package/dist/esm/styles.css +2 -4
- package/dist/esm/styles.css.map +1 -1
- package/dist/esm/types/DocumentOutline.d.ts +7 -0
- package/dist/esm/types/EditableContent.d.ts +7 -1
- package/dist/esm/types/HtmlViewer.d.ts +1 -2
- package/dist/esm/types/MathRenderer.d.ts +25 -0
- package/dist/esm/types/SemanticTagParser.d.ts +33 -0
- package/dist/esm/types/SemanticTagRenderer.d.ts +17 -0
- package/dist/esm/types/Structura.d.ts +1 -2
- package/dist/esm/types/Table.d.ts +3 -1
- package/dist/esm/types/TableCell.d.ts +6 -1
- package/dist/esm/types/helpers/index.d.ts +0 -1
- package/dist/esm/types/index.d.ts +3 -0
- package/dist/esm/types/test-app/src/App.d.ts +1 -2
- package/dist/index.d.ts +78 -4
- package/package.json +9 -16
- package/PRODUCTION_ARCHITECTURE.md +0 -511
- package/SAVE_FUNCTIONALITY_COMPLETE.md +0 -448
- package/dist/cjs/ui/badge.js +0 -34
- package/dist/esm/types/helpers/jsonToHtml.d.ts +0 -40
- package/dist/esm/ui/badge.js +0 -31
- package/server/README.md +0 -203
- package/server/db.js +0 -142
- 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;
|
package/dist/cjs/Structura.js
CHANGED
|
@@ -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
|
-
//
|
|
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
|
|
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("
|
|
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.
|
|
599
|
+
children: jsxRuntime.jsx(card.Card, {
|
|
625
600
|
className: "h-full border-0 rounded-none",
|
|
626
|
-
children:
|
|
627
|
-
className: "
|
|
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.
|
|
704
|
+
children: jsxRuntime.jsx(card.Card, {
|
|
742
705
|
className: "h-full border-0 rounded-none",
|
|
743
|
-
children:
|
|
744
|
-
className: "
|
|
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
|
-
|
|
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
|
|
87
|
-
const childNode = (_a = node.children) === null || _a === void 0 ? void 0 : _a[
|
|
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-${
|
|
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-${
|
|
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
|
|
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(
|
|
218
|
+
onNodeClick: onNodeClick ? () => onNodeClick(node.id) : undefined,
|
|
219
|
+
isEditMode: isEditMode,
|
|
220
|
+
isJsonMode: isJsonMode
|
|
156
221
|
}, cell.id))
|
|
157
222
|
}, rowIndex))
|
|
158
223
|
})
|