@signiphi/pdf-compose 0.1.0-beta.0
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/dist/index.js +4966 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +4930 -0
- package/dist/index.mjs.map +1 -0
- package/dist/styles/index.css +2366 -0
- package/package.json +92 -0
- package/src/styles/index.css +229 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,4930 @@
|
|
|
1
|
+
import * as React12 from 'react';
|
|
2
|
+
import React12__default, { createContext, useCallback, useState, useRef, useEffect, useMemo, useContext } from 'react';
|
|
3
|
+
import { AlertTriangle, RefreshCw, FileText, ChevronDown, Circle, CheckSquare, Calendar, Hash, PenTool, Type, Braces, ChevronLeft, ChevronRight, ZoomOut, ZoomIn, Maximize2, Loader2, Plus, PanelLeftOpen, CheckCircle2, FileDown, PanelLeftClose, Undo, Redo, Bold, Italic, Underline as Underline$1, Code, Heading1, Heading2, Heading3, List, ListOrdered, Quote, Minus, AlignLeft, AlignCenter, AlignRight, Trash2, X, PenLine, Upload, WrapText, Download } from 'lucide-react';
|
|
4
|
+
import { ReactNodeViewRenderer, NodeViewWrapper, useEditor, EditorContent } from '@tiptap/react';
|
|
5
|
+
import StarterKit from '@tiptap/starter-kit';
|
|
6
|
+
import Underline from '@tiptap/extension-underline';
|
|
7
|
+
import TextAlign from '@tiptap/extension-text-align';
|
|
8
|
+
import Placeholder from '@tiptap/extension-placeholder';
|
|
9
|
+
import { Node, mergeAttributes } from '@tiptap/core';
|
|
10
|
+
import { clsx } from 'clsx';
|
|
11
|
+
import { twMerge } from 'tailwind-merge';
|
|
12
|
+
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
13
|
+
import { v4 } from 'uuid';
|
|
14
|
+
import { PDFDocument, StandardFonts, rgb, PDFName as PDFName$1, PDFString as PDFString$1 } from 'pdf-lib';
|
|
15
|
+
import { NodeSelection } from '@tiptap/pm/state';
|
|
16
|
+
import { Slot } from '@radix-ui/react-slot';
|
|
17
|
+
import { cva } from 'class-variance-authority';
|
|
18
|
+
import * as TooltipPrimitive from '@radix-ui/react-tooltip';
|
|
19
|
+
import * as PopoverPrimitive from '@radix-ui/react-popover';
|
|
20
|
+
import * as LabelPrimitive from '@radix-ui/react-label';
|
|
21
|
+
|
|
22
|
+
var __defProp = Object.defineProperty;
|
|
23
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
24
|
+
var __publicField = (obj, key, value) => __defNormalProp(obj, key + "" , value);
|
|
25
|
+
function cn(...inputs) {
|
|
26
|
+
return twMerge(clsx(inputs));
|
|
27
|
+
}
|
|
28
|
+
var FIELD_TYPE_CONFIG = {
|
|
29
|
+
text: { icon: Type, color: "bg-blue-100 text-blue-700 border-blue-300", label: "Text" },
|
|
30
|
+
signature: { icon: PenTool, color: "bg-purple-100 text-purple-700 border-purple-300", label: "Signature" },
|
|
31
|
+
initials: { icon: Hash, color: "bg-green-100 text-green-700 border-green-300", label: "Initials" },
|
|
32
|
+
date: { icon: Calendar, color: "bg-amber-100 text-amber-700 border-amber-300", label: "Date" },
|
|
33
|
+
checkbox: { icon: CheckSquare, color: "bg-teal-100 text-teal-700 border-teal-300", label: "Checkbox" },
|
|
34
|
+
radio: { icon: Circle, color: "bg-pink-100 text-pink-700 border-pink-300", label: "Radio" },
|
|
35
|
+
dropdown: { icon: ChevronDown, color: "bg-indigo-100 text-indigo-700 border-indigo-300", label: "Dropdown" },
|
|
36
|
+
text_label: { icon: FileText, color: "bg-gray-100 text-gray-700 border-gray-300", label: "Label" }
|
|
37
|
+
};
|
|
38
|
+
function FieldNodeView({ node, selected, getPos, editor }) {
|
|
39
|
+
const { fieldType, fieldLabel, fieldName, required } = node.attrs;
|
|
40
|
+
const config = FIELD_TYPE_CONFIG[fieldType] || FIELD_TYPE_CONFIG.text;
|
|
41
|
+
const Icon = config.icon;
|
|
42
|
+
const handleDoubleClick = useCallback((e) => {
|
|
43
|
+
const pos = typeof getPos === "function" ? getPos() : null;
|
|
44
|
+
if (pos !== null && pos !== void 0) {
|
|
45
|
+
editor.commands.setNodeSelection(pos);
|
|
46
|
+
const event = new CustomEvent("signiphi:edit-field", {
|
|
47
|
+
detail: { fieldId: node.attrs.fieldId, pos, anchorEl: e.currentTarget }
|
|
48
|
+
});
|
|
49
|
+
window.dispatchEvent(event);
|
|
50
|
+
}
|
|
51
|
+
}, [editor, getPos, node.attrs.fieldId]);
|
|
52
|
+
return /* @__PURE__ */ jsx(NodeViewWrapper, { as: "span", className: "inline", children: /* @__PURE__ */ jsxs(
|
|
53
|
+
"span",
|
|
54
|
+
{
|
|
55
|
+
className: cn(
|
|
56
|
+
"inline-flex items-center gap-1 px-2 py-0.5 rounded-md border text-xs font-medium",
|
|
57
|
+
"cursor-pointer select-none align-baseline",
|
|
58
|
+
config.color,
|
|
59
|
+
selected && "ring-2 ring-primary ring-offset-1"
|
|
60
|
+
),
|
|
61
|
+
onDoubleClick: handleDoubleClick,
|
|
62
|
+
title: `${config.label}: ${fieldLabel || fieldName}${required ? " (required)" : ""}`,
|
|
63
|
+
children: [
|
|
64
|
+
/* @__PURE__ */ jsx(Icon, { size: 12 }),
|
|
65
|
+
/* @__PURE__ */ jsx("span", { children: fieldLabel || fieldName }),
|
|
66
|
+
required && /* @__PURE__ */ jsx("span", { className: "text-red-500", children: "*" })
|
|
67
|
+
]
|
|
68
|
+
}
|
|
69
|
+
) });
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// src/extensions/field-node.ts
|
|
73
|
+
var FieldNode = Node.create({
|
|
74
|
+
name: "fieldNode",
|
|
75
|
+
group: "inline",
|
|
76
|
+
inline: true,
|
|
77
|
+
atom: true,
|
|
78
|
+
selectable: true,
|
|
79
|
+
draggable: false,
|
|
80
|
+
addAttributes() {
|
|
81
|
+
return {
|
|
82
|
+
fieldId: { default: "" },
|
|
83
|
+
fieldType: { default: "text" },
|
|
84
|
+
fieldName: { default: "" },
|
|
85
|
+
fieldLabel: { default: "" },
|
|
86
|
+
required: { default: false },
|
|
87
|
+
options: { default: "" },
|
|
88
|
+
placeholder: { default: "" },
|
|
89
|
+
fontSize: { default: null },
|
|
90
|
+
defaultValue: { default: "" },
|
|
91
|
+
multiline: { default: false },
|
|
92
|
+
maxLength: { default: null },
|
|
93
|
+
acknowledgements: { default: "" }
|
|
94
|
+
};
|
|
95
|
+
},
|
|
96
|
+
parseHTML() {
|
|
97
|
+
return [{ tag: "span[data-field-node]" }];
|
|
98
|
+
},
|
|
99
|
+
renderHTML({ HTMLAttributes }) {
|
|
100
|
+
return ["span", mergeAttributes({ "data-field-node": "" }, HTMLAttributes)];
|
|
101
|
+
},
|
|
102
|
+
addNodeView() {
|
|
103
|
+
return ReactNodeViewRenderer(FieldNodeView);
|
|
104
|
+
},
|
|
105
|
+
addCommands() {
|
|
106
|
+
return {
|
|
107
|
+
insertField: (attrs) => ({ chain }) => {
|
|
108
|
+
return chain().insertContent({
|
|
109
|
+
type: this.name,
|
|
110
|
+
attrs
|
|
111
|
+
}).run();
|
|
112
|
+
},
|
|
113
|
+
updateField: (fieldId, attrs) => ({ tr, state, dispatch }) => {
|
|
114
|
+
let found = false;
|
|
115
|
+
state.doc.descendants((node, pos) => {
|
|
116
|
+
if (node.type.name === this.name && node.attrs.fieldId === fieldId) {
|
|
117
|
+
if (dispatch) {
|
|
118
|
+
tr.setNodeMarkup(pos, void 0, { ...node.attrs, ...attrs });
|
|
119
|
+
}
|
|
120
|
+
found = true;
|
|
121
|
+
return false;
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
return found;
|
|
125
|
+
},
|
|
126
|
+
removeField: (fieldId) => ({ tr, state, dispatch }) => {
|
|
127
|
+
let found = false;
|
|
128
|
+
state.doc.descendants((node, pos) => {
|
|
129
|
+
if (node.type.name === this.name && node.attrs.fieldId === fieldId) {
|
|
130
|
+
if (dispatch) {
|
|
131
|
+
tr.delete(pos, pos + node.nodeSize);
|
|
132
|
+
}
|
|
133
|
+
found = true;
|
|
134
|
+
return false;
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
return found;
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
},
|
|
141
|
+
addKeyboardShortcuts() {
|
|
142
|
+
return {
|
|
143
|
+
Backspace: ({ editor }) => {
|
|
144
|
+
const { state } = editor;
|
|
145
|
+
const { $from } = state.selection;
|
|
146
|
+
if (state.selection.empty && $from.nodeBefore?.type.name === this.name) {
|
|
147
|
+
return editor.commands.deleteRange({
|
|
148
|
+
from: $from.pos - $from.nodeBefore.nodeSize,
|
|
149
|
+
to: $from.pos
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
return false;
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
function VariableNodeView({ node, selected, getPos, editor }) {
|
|
158
|
+
const { varName, varLabel, varDefault } = node.attrs;
|
|
159
|
+
const marks = node.marks || [];
|
|
160
|
+
const isBold = marks.some((m) => m.type.name === "bold");
|
|
161
|
+
const isItalic = marks.some((m) => m.type.name === "italic");
|
|
162
|
+
const isUnderline = marks.some((m) => m.type.name === "underline");
|
|
163
|
+
const handleDoubleClick = useCallback((e) => {
|
|
164
|
+
const pos = typeof getPos === "function" ? getPos() : null;
|
|
165
|
+
if (pos !== null && pos !== void 0) {
|
|
166
|
+
editor.commands.setNodeSelection(pos);
|
|
167
|
+
const event = new CustomEvent("signiphi:edit-variable", {
|
|
168
|
+
detail: { varName, pos, anchorEl: e.currentTarget }
|
|
169
|
+
});
|
|
170
|
+
window.dispatchEvent(event);
|
|
171
|
+
}
|
|
172
|
+
}, [editor, getPos, varName]);
|
|
173
|
+
return /* @__PURE__ */ jsx(NodeViewWrapper, { as: "span", className: "inline", children: /* @__PURE__ */ jsxs(
|
|
174
|
+
"span",
|
|
175
|
+
{
|
|
176
|
+
"data-variable-node": "",
|
|
177
|
+
"data-var-name": varName,
|
|
178
|
+
className: cn(
|
|
179
|
+
"inline-flex items-center gap-1 px-2 py-0.5 rounded-md border text-xs font-medium",
|
|
180
|
+
"cursor-pointer select-none align-baseline",
|
|
181
|
+
"bg-orange-100 text-orange-700 border-orange-300",
|
|
182
|
+
selected && "ring-2 ring-primary ring-offset-1"
|
|
183
|
+
),
|
|
184
|
+
onDoubleClick: handleDoubleClick,
|
|
185
|
+
title: `Variable: ${varLabel || varName}${varDefault ? ` (default: ${varDefault})` : ""}`,
|
|
186
|
+
children: [
|
|
187
|
+
/* @__PURE__ */ jsx(Braces, { size: 12 }),
|
|
188
|
+
/* @__PURE__ */ jsx(
|
|
189
|
+
"span",
|
|
190
|
+
{
|
|
191
|
+
className: cn(
|
|
192
|
+
isBold && "font-bold",
|
|
193
|
+
isItalic && "italic",
|
|
194
|
+
isUnderline && "underline"
|
|
195
|
+
),
|
|
196
|
+
children: varLabel || varName
|
|
197
|
+
}
|
|
198
|
+
)
|
|
199
|
+
]
|
|
200
|
+
}
|
|
201
|
+
) });
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// src/extensions/variable-node.ts
|
|
205
|
+
var VariableNode = Node.create({
|
|
206
|
+
name: "variableNode",
|
|
207
|
+
group: "inline",
|
|
208
|
+
inline: true,
|
|
209
|
+
atom: true,
|
|
210
|
+
selectable: true,
|
|
211
|
+
draggable: false,
|
|
212
|
+
addAttributes() {
|
|
213
|
+
return {
|
|
214
|
+
varName: { default: "" },
|
|
215
|
+
varLabel: { default: "" },
|
|
216
|
+
varDefault: { default: "" }
|
|
217
|
+
};
|
|
218
|
+
},
|
|
219
|
+
parseHTML() {
|
|
220
|
+
return [{ tag: "span[data-variable-node]" }];
|
|
221
|
+
},
|
|
222
|
+
renderHTML({ HTMLAttributes }) {
|
|
223
|
+
return ["span", mergeAttributes({ "data-variable-node": "" }, HTMLAttributes)];
|
|
224
|
+
},
|
|
225
|
+
addNodeView() {
|
|
226
|
+
return ReactNodeViewRenderer(VariableNodeView);
|
|
227
|
+
},
|
|
228
|
+
addCommands() {
|
|
229
|
+
return {
|
|
230
|
+
insertVariable: (attrs) => ({ chain }) => {
|
|
231
|
+
return chain().insertContent({ type: this.name, attrs }).run();
|
|
232
|
+
},
|
|
233
|
+
updateVariable: (varName, attrs) => ({ tr, state, dispatch }) => {
|
|
234
|
+
let found = false;
|
|
235
|
+
state.doc.descendants((node, pos) => {
|
|
236
|
+
if (node.type.name === this.name && node.attrs.varName === varName) {
|
|
237
|
+
if (dispatch) {
|
|
238
|
+
tr.setNodeMarkup(pos, void 0, { ...node.attrs, ...attrs });
|
|
239
|
+
}
|
|
240
|
+
found = true;
|
|
241
|
+
}
|
|
242
|
+
});
|
|
243
|
+
return found;
|
|
244
|
+
},
|
|
245
|
+
removeVariable: (varName) => ({ tr, state, dispatch }) => {
|
|
246
|
+
const positions = [];
|
|
247
|
+
state.doc.descendants((node, pos) => {
|
|
248
|
+
if (node.type.name === this.name && node.attrs.varName === varName) {
|
|
249
|
+
positions.push(pos);
|
|
250
|
+
}
|
|
251
|
+
});
|
|
252
|
+
if (positions.length === 0) return false;
|
|
253
|
+
if (dispatch) {
|
|
254
|
+
for (let i = positions.length - 1; i >= 0; i--) {
|
|
255
|
+
const pos = positions[i];
|
|
256
|
+
const node = state.doc.nodeAt(pos);
|
|
257
|
+
if (node) {
|
|
258
|
+
tr.delete(pos, pos + node.nodeSize);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
return true;
|
|
263
|
+
}
|
|
264
|
+
};
|
|
265
|
+
},
|
|
266
|
+
addKeyboardShortcuts() {
|
|
267
|
+
return {
|
|
268
|
+
Backspace: ({ editor }) => {
|
|
269
|
+
const { state } = editor;
|
|
270
|
+
const { $from } = state.selection;
|
|
271
|
+
if (state.selection.empty && $from.nodeBefore?.type.name === this.name) {
|
|
272
|
+
return editor.commands.deleteRange({
|
|
273
|
+
from: $from.pos - $from.nodeBefore.nodeSize,
|
|
274
|
+
to: $from.pos
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
return false;
|
|
278
|
+
}
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
// src/types/index.ts
|
|
284
|
+
var FormFieldType = /* @__PURE__ */ ((FormFieldType2) => {
|
|
285
|
+
FormFieldType2["TEXT"] = "text";
|
|
286
|
+
FormFieldType2["SIGNATURE"] = "signature";
|
|
287
|
+
FormFieldType2["INITIALS"] = "initials";
|
|
288
|
+
FormFieldType2["DATE"] = "date";
|
|
289
|
+
FormFieldType2["CHECKBOX"] = "checkbox";
|
|
290
|
+
FormFieldType2["RADIO"] = "radio";
|
|
291
|
+
FormFieldType2["DROPDOWN"] = "dropdown";
|
|
292
|
+
FormFieldType2["TEXT_LABEL"] = "text_label";
|
|
293
|
+
return FormFieldType2;
|
|
294
|
+
})(FormFieldType || {});
|
|
295
|
+
function generateFieldName(type) {
|
|
296
|
+
return `${type}_${Date.now()}`;
|
|
297
|
+
}
|
|
298
|
+
function createFieldAttrs(type, overrides) {
|
|
299
|
+
const name = overrides?.fieldName || generateFieldName(type);
|
|
300
|
+
let defaultOptions = "";
|
|
301
|
+
let defaultFontSize = null;
|
|
302
|
+
let defaultMultiline = false;
|
|
303
|
+
let defaultDefaultValue = "";
|
|
304
|
+
if (type === "radio" /* RADIO */ || type === "dropdown" /* DROPDOWN */) {
|
|
305
|
+
defaultOptions = "Option A,Option B,Option C";
|
|
306
|
+
}
|
|
307
|
+
if (type === "text" /* TEXT */ || type === "text_label" /* TEXT_LABEL */) {
|
|
308
|
+
defaultFontSize = 12;
|
|
309
|
+
}
|
|
310
|
+
if (type === "text" /* TEXT */) {
|
|
311
|
+
defaultMultiline = false;
|
|
312
|
+
}
|
|
313
|
+
if (type === "text_label" /* TEXT_LABEL */) {
|
|
314
|
+
defaultDefaultValue = "Text Label";
|
|
315
|
+
}
|
|
316
|
+
return {
|
|
317
|
+
fieldId: v4(),
|
|
318
|
+
fieldType: type,
|
|
319
|
+
fieldName: name,
|
|
320
|
+
fieldLabel: overrides?.fieldLabel || "",
|
|
321
|
+
required: overrides?.required ?? false,
|
|
322
|
+
options: overrides?.options || defaultOptions,
|
|
323
|
+
placeholder: overrides?.placeholder || "",
|
|
324
|
+
fontSize: overrides?.fontSize ?? defaultFontSize,
|
|
325
|
+
defaultValue: overrides?.defaultValue || defaultDefaultValue,
|
|
326
|
+
multiline: overrides?.multiline ?? defaultMultiline,
|
|
327
|
+
maxLength: overrides?.maxLength ?? null,
|
|
328
|
+
acknowledgements: overrides?.acknowledgements ?? "",
|
|
329
|
+
...overrides
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
function extractFieldsFromContent(content) {
|
|
333
|
+
const fields = [];
|
|
334
|
+
function walk(node) {
|
|
335
|
+
if (node.type === "fieldNode" && node.attrs) {
|
|
336
|
+
fields.push({
|
|
337
|
+
fieldId: node.attrs.fieldId,
|
|
338
|
+
type: node.attrs.fieldType,
|
|
339
|
+
name: node.attrs.fieldName,
|
|
340
|
+
label: node.attrs.fieldLabel,
|
|
341
|
+
required: node.attrs.required ?? false,
|
|
342
|
+
options: node.attrs.options ? node.attrs.options.split(",").map((s) => s.trim()).filter(Boolean) : void 0,
|
|
343
|
+
placeholder: node.attrs.placeholder || void 0,
|
|
344
|
+
fontSize: node.attrs.fontSize || void 0,
|
|
345
|
+
defaultValue: node.attrs.defaultValue || void 0,
|
|
346
|
+
multiline: node.attrs.multiline || void 0,
|
|
347
|
+
maxLength: node.attrs.maxLength || void 0,
|
|
348
|
+
acknowledgements: node.attrs.acknowledgements ? JSON.parse(node.attrs.acknowledgements) : void 0
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
if (node.content) {
|
|
352
|
+
node.content.forEach(walk);
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
if (content.content) {
|
|
356
|
+
content.content.forEach(walk);
|
|
357
|
+
}
|
|
358
|
+
return fields;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// src/utils/variable-helpers.ts
|
|
362
|
+
function extractVariablesFromContent(content) {
|
|
363
|
+
const seen = /* @__PURE__ */ new Map();
|
|
364
|
+
function walk(node) {
|
|
365
|
+
if (node.type === "variableNode" && node.attrs) {
|
|
366
|
+
const varName = node.attrs.varName;
|
|
367
|
+
if (varName && !seen.has(varName)) {
|
|
368
|
+
seen.set(varName, {
|
|
369
|
+
varName,
|
|
370
|
+
varLabel: node.attrs.varLabel || varName,
|
|
371
|
+
varDefault: node.attrs.varDefault || ""
|
|
372
|
+
});
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
if (node.content) {
|
|
376
|
+
node.content.forEach(walk);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
if (content.content) {
|
|
380
|
+
content.content.forEach(walk);
|
|
381
|
+
}
|
|
382
|
+
return Array.from(seen.values());
|
|
383
|
+
}
|
|
384
|
+
function replaceVariablesInContent(content, values) {
|
|
385
|
+
function walkNode(node) {
|
|
386
|
+
if (node.type === "variableNode" && node.attrs) {
|
|
387
|
+
const varName = node.attrs.varName;
|
|
388
|
+
const replacement = values[varName] || node.attrs.varDefault || `[${node.attrs.varLabel || varName}]`;
|
|
389
|
+
const textNode = { type: "text", text: replacement };
|
|
390
|
+
if (node.marks && node.marks.length > 0) {
|
|
391
|
+
textNode.marks = node.marks;
|
|
392
|
+
}
|
|
393
|
+
return textNode;
|
|
394
|
+
}
|
|
395
|
+
if (node.content) {
|
|
396
|
+
return { ...node, content: node.content.map(walkNode) };
|
|
397
|
+
}
|
|
398
|
+
return node;
|
|
399
|
+
}
|
|
400
|
+
return walkNode(content);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
// src/utils/markdown-writer.ts
|
|
404
|
+
function serializeFieldToken(attrs) {
|
|
405
|
+
const parts = ["field"];
|
|
406
|
+
if (attrs.fieldType) parts.push(`type:${attrs.fieldType}`);
|
|
407
|
+
if (attrs.fieldName) parts.push(`name:${attrs.fieldName}`);
|
|
408
|
+
if (attrs.fieldLabel) parts.push(`label:${attrs.fieldLabel}`);
|
|
409
|
+
if (attrs.fieldId) parts.push(`id:${attrs.fieldId}`);
|
|
410
|
+
if (attrs.required) parts.push(`required:true`);
|
|
411
|
+
if (attrs.options) parts.push(`options:${attrs.options}`);
|
|
412
|
+
if (attrs.fontSize) parts.push(`fontSize:${attrs.fontSize}`);
|
|
413
|
+
if (attrs.placeholder) parts.push(`placeholder:${attrs.placeholder}`);
|
|
414
|
+
if (attrs.defaultValue) parts.push(`defaultValue:${attrs.defaultValue}`);
|
|
415
|
+
if (attrs.multiline) parts.push(`multiline:true`);
|
|
416
|
+
if (attrs.maxLength) parts.push(`maxLength:${attrs.maxLength}`);
|
|
417
|
+
if (attrs.acknowledgements) parts.push(`acks:${btoa(attrs.acknowledgements)}`);
|
|
418
|
+
return `{{${parts.join("|")}}}`;
|
|
419
|
+
}
|
|
420
|
+
function serializeVariableToken(attrs) {
|
|
421
|
+
const parts = ["var"];
|
|
422
|
+
if (attrs.varName) parts.push(`name:${attrs.varName}`);
|
|
423
|
+
if (attrs.varLabel) parts.push(`label:${attrs.varLabel}`);
|
|
424
|
+
if (attrs.varDefault) parts.push(`default:${attrs.varDefault}`);
|
|
425
|
+
return `{{${parts.join("|")}}}`;
|
|
426
|
+
}
|
|
427
|
+
function serializeInline(content) {
|
|
428
|
+
if (!content) return "";
|
|
429
|
+
let result = "";
|
|
430
|
+
for (const node of content) {
|
|
431
|
+
if (node.type === "fieldNode") {
|
|
432
|
+
result += serializeFieldToken(node.attrs || {});
|
|
433
|
+
continue;
|
|
434
|
+
}
|
|
435
|
+
if (node.type === "variableNode") {
|
|
436
|
+
let token = serializeVariableToken(node.attrs || {});
|
|
437
|
+
const varMarks = node.marks || [];
|
|
438
|
+
const isBold = varMarks.some((m) => m.type === "bold");
|
|
439
|
+
const isItalic = varMarks.some((m) => m.type === "italic");
|
|
440
|
+
const isUnderline = varMarks.some((m) => m.type === "underline");
|
|
441
|
+
if (isBold && isItalic) token = `***${token}***`;
|
|
442
|
+
else if (isBold) token = `**${token}**`;
|
|
443
|
+
else if (isItalic) token = `*${token}*`;
|
|
444
|
+
if (isUnderline) token = `__${token}__`;
|
|
445
|
+
result += token;
|
|
446
|
+
continue;
|
|
447
|
+
}
|
|
448
|
+
if (node.type === "text") {
|
|
449
|
+
let text = node.text || "";
|
|
450
|
+
const marks = node.marks || [];
|
|
451
|
+
for (const mark of marks) {
|
|
452
|
+
if (mark.type === "bold") text = `**${text}**`;
|
|
453
|
+
else if (mark.type === "italic") text = `*${text}*`;
|
|
454
|
+
else if (mark.type === "underline") text = `__${text}__`;
|
|
455
|
+
else if (mark.type === "code") text = `\`${text}\``;
|
|
456
|
+
}
|
|
457
|
+
result += text;
|
|
458
|
+
}
|
|
459
|
+
if (node.type === "hardBreak") {
|
|
460
|
+
result += " \n";
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
return result;
|
|
464
|
+
}
|
|
465
|
+
function serializeBlock(node) {
|
|
466
|
+
switch (node.type) {
|
|
467
|
+
case "heading": {
|
|
468
|
+
const level = node.attrs?.level || 1;
|
|
469
|
+
const prefix = "#".repeat(level);
|
|
470
|
+
return `${prefix} ${serializeInline(node.content)}`;
|
|
471
|
+
}
|
|
472
|
+
case "paragraph": {
|
|
473
|
+
const inline = serializeInline(node.content);
|
|
474
|
+
return inline;
|
|
475
|
+
}
|
|
476
|
+
case "bulletList": {
|
|
477
|
+
return (node.content || []).map((item) => {
|
|
478
|
+
const inner = (item.content || []).map(serializeBlock).join("\n");
|
|
479
|
+
return `- ${inner}`;
|
|
480
|
+
}).join("\n");
|
|
481
|
+
}
|
|
482
|
+
case "orderedList": {
|
|
483
|
+
return (node.content || []).map((item, i) => {
|
|
484
|
+
const inner = (item.content || []).map(serializeBlock).join("\n");
|
|
485
|
+
return `${i + 1}. ${inner}`;
|
|
486
|
+
}).join("\n");
|
|
487
|
+
}
|
|
488
|
+
case "listItem": {
|
|
489
|
+
return (node.content || []).map(serializeBlock).join("\n");
|
|
490
|
+
}
|
|
491
|
+
case "blockquote": {
|
|
492
|
+
return (node.content || []).map(serializeBlock).map((line) => `> ${line}`).join("\n");
|
|
493
|
+
}
|
|
494
|
+
case "codeBlock": {
|
|
495
|
+
const text = serializeInline(node.content);
|
|
496
|
+
return `\`\`\`
|
|
497
|
+
${text}
|
|
498
|
+
\`\`\``;
|
|
499
|
+
}
|
|
500
|
+
case "horizontalRule": {
|
|
501
|
+
return "---";
|
|
502
|
+
}
|
|
503
|
+
default:
|
|
504
|
+
return serializeInline(node.content);
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
function tiptapToMarkdown(doc) {
|
|
508
|
+
if (!doc.content) return "";
|
|
509
|
+
return doc.content.map(serializeBlock).join("\n\n");
|
|
510
|
+
}
|
|
511
|
+
var TOKEN_REGEX = /\{\{(field|var)\|([^}]+)\}\}/g;
|
|
512
|
+
function injectMarks(token, marks) {
|
|
513
|
+
const existingMatch = token.match(/\|_marks:([^}|]+)/);
|
|
514
|
+
if (existingMatch) {
|
|
515
|
+
const existing = existingMatch[1].split(",");
|
|
516
|
+
const merged = [.../* @__PURE__ */ new Set([...existing, ...marks])];
|
|
517
|
+
return token.replace(/\|_marks:[^}|]+/, `|_marks:${merged.join(",")}`);
|
|
518
|
+
}
|
|
519
|
+
return token.replace(/\}\}$/, `|_marks:${marks.join(",")}}}`);
|
|
520
|
+
}
|
|
521
|
+
function preprocessMarkedTokens(text) {
|
|
522
|
+
const tk = `\\{\\{(?:var|field)\\|[^}]+\\}\\}`;
|
|
523
|
+
text = text.replace(new RegExp(`__(\\*\\*\\*(${tk})\\*\\*\\*)__`, "g"), (_, _inner, token) => `__${injectMarks(token, ["bold", "italic"])}__`);
|
|
524
|
+
text = text.replace(new RegExp(`__(\\*\\*(${tk})\\*\\*)__`, "g"), (_, _inner, token) => `__${injectMarks(token, ["bold"])}__`);
|
|
525
|
+
text = text.replace(new RegExp(`__(\\*(${tk})\\*)__`, "g"), (_, _inner, token) => `__${injectMarks(token, ["italic"])}__`);
|
|
526
|
+
text = text.replace(new RegExp(`__(${tk})__`, "g"), (_, token) => injectMarks(token, ["underline"]));
|
|
527
|
+
text = text.replace(new RegExp(`\\*\\*\\*(${tk})\\*\\*\\*`, "g"), (_, token) => injectMarks(token, ["bold", "italic"]));
|
|
528
|
+
text = text.replace(new RegExp(`\\*\\*(${tk})\\*\\*`, "g"), (_, token) => injectMarks(token, ["bold"]));
|
|
529
|
+
text = text.replace(new RegExp(`(?<!\\*)\\*(${tk})\\*(?!\\*)`, "g"), (_, token) => injectMarks(token, ["italic"]));
|
|
530
|
+
return text;
|
|
531
|
+
}
|
|
532
|
+
function extractAndRemoveMarks(attrs) {
|
|
533
|
+
const marksStr = attrs._marks;
|
|
534
|
+
delete attrs._marks;
|
|
535
|
+
if (!marksStr) return [];
|
|
536
|
+
return marksStr.split(",").map((m) => ({ type: m.trim() }));
|
|
537
|
+
}
|
|
538
|
+
function parseFieldToken(tokenBody) {
|
|
539
|
+
const attrs = {
|
|
540
|
+
fieldId: "",
|
|
541
|
+
fieldType: "text",
|
|
542
|
+
fieldName: "",
|
|
543
|
+
fieldLabel: "",
|
|
544
|
+
required: false,
|
|
545
|
+
options: "",
|
|
546
|
+
placeholder: "",
|
|
547
|
+
fontSize: null,
|
|
548
|
+
defaultValue: "",
|
|
549
|
+
multiline: false,
|
|
550
|
+
maxLength: null,
|
|
551
|
+
acknowledgements: ""
|
|
552
|
+
};
|
|
553
|
+
const pairs = tokenBody.split("|");
|
|
554
|
+
for (const pair of pairs) {
|
|
555
|
+
const colonIdx = pair.indexOf(":");
|
|
556
|
+
if (colonIdx === -1) continue;
|
|
557
|
+
const key = pair.slice(0, colonIdx).trim();
|
|
558
|
+
const value = pair.slice(colonIdx + 1).trim();
|
|
559
|
+
switch (key) {
|
|
560
|
+
case "type":
|
|
561
|
+
attrs.fieldType = value;
|
|
562
|
+
break;
|
|
563
|
+
case "name":
|
|
564
|
+
attrs.fieldName = value;
|
|
565
|
+
break;
|
|
566
|
+
case "label":
|
|
567
|
+
attrs.fieldLabel = value;
|
|
568
|
+
break;
|
|
569
|
+
case "id":
|
|
570
|
+
attrs.fieldId = value;
|
|
571
|
+
break;
|
|
572
|
+
case "required":
|
|
573
|
+
attrs.required = value === "true";
|
|
574
|
+
break;
|
|
575
|
+
case "options":
|
|
576
|
+
attrs.options = value;
|
|
577
|
+
break;
|
|
578
|
+
case "fontSize":
|
|
579
|
+
attrs.fontSize = parseInt(value, 10) || null;
|
|
580
|
+
break;
|
|
581
|
+
case "placeholder":
|
|
582
|
+
attrs.placeholder = value;
|
|
583
|
+
break;
|
|
584
|
+
case "defaultValue":
|
|
585
|
+
attrs.defaultValue = value;
|
|
586
|
+
break;
|
|
587
|
+
case "multiline":
|
|
588
|
+
attrs.multiline = value === "true";
|
|
589
|
+
break;
|
|
590
|
+
case "maxLength":
|
|
591
|
+
attrs.maxLength = parseInt(value, 10) || null;
|
|
592
|
+
break;
|
|
593
|
+
case "acks":
|
|
594
|
+
attrs.acknowledgements = atob(value);
|
|
595
|
+
break;
|
|
596
|
+
case "_marks":
|
|
597
|
+
attrs._marks = value;
|
|
598
|
+
break;
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
if (!attrs.fieldId) {
|
|
602
|
+
attrs.fieldId = v4();
|
|
603
|
+
}
|
|
604
|
+
return attrs;
|
|
605
|
+
}
|
|
606
|
+
function parseVariableToken(tokenBody) {
|
|
607
|
+
const attrs = {
|
|
608
|
+
varName: "",
|
|
609
|
+
varLabel: "",
|
|
610
|
+
varDefault: ""
|
|
611
|
+
};
|
|
612
|
+
const pairs = tokenBody.split("|");
|
|
613
|
+
for (const pair of pairs) {
|
|
614
|
+
const colonIdx = pair.indexOf(":");
|
|
615
|
+
if (colonIdx === -1) continue;
|
|
616
|
+
const key = pair.slice(0, colonIdx).trim();
|
|
617
|
+
const value = pair.slice(colonIdx + 1).trim();
|
|
618
|
+
switch (key) {
|
|
619
|
+
case "name":
|
|
620
|
+
attrs.varName = value;
|
|
621
|
+
break;
|
|
622
|
+
case "label":
|
|
623
|
+
attrs.varLabel = value;
|
|
624
|
+
break;
|
|
625
|
+
case "default":
|
|
626
|
+
attrs.varDefault = value;
|
|
627
|
+
break;
|
|
628
|
+
case "_marks":
|
|
629
|
+
attrs._marks = value;
|
|
630
|
+
break;
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
return attrs;
|
|
634
|
+
}
|
|
635
|
+
function parseInline(text) {
|
|
636
|
+
text = preprocessMarkedTokens(text);
|
|
637
|
+
const nodes = [];
|
|
638
|
+
let lastIndex = 0;
|
|
639
|
+
TOKEN_REGEX.lastIndex = 0;
|
|
640
|
+
let match;
|
|
641
|
+
while ((match = TOKEN_REGEX.exec(text)) !== null) {
|
|
642
|
+
if (match.index > lastIndex) {
|
|
643
|
+
const beforeText = text.slice(lastIndex, match.index);
|
|
644
|
+
nodes.push(...parseFormattedText(beforeText));
|
|
645
|
+
}
|
|
646
|
+
if (match[1] === "field") {
|
|
647
|
+
const attrs = parseFieldToken(match[2]);
|
|
648
|
+
const marks = extractAndRemoveMarks(attrs);
|
|
649
|
+
const node = { type: "fieldNode", attrs };
|
|
650
|
+
if (marks.length > 0) node.marks = marks;
|
|
651
|
+
nodes.push(node);
|
|
652
|
+
} else if (match[1] === "var") {
|
|
653
|
+
const attrs = parseVariableToken(match[2]);
|
|
654
|
+
const marks = extractAndRemoveMarks(attrs);
|
|
655
|
+
const node = { type: "variableNode", attrs };
|
|
656
|
+
if (marks.length > 0) node.marks = marks;
|
|
657
|
+
nodes.push(node);
|
|
658
|
+
}
|
|
659
|
+
lastIndex = match.index + match[0].length;
|
|
660
|
+
}
|
|
661
|
+
if (lastIndex < text.length) {
|
|
662
|
+
nodes.push(...parseFormattedText(text.slice(lastIndex)));
|
|
663
|
+
}
|
|
664
|
+
return nodes;
|
|
665
|
+
}
|
|
666
|
+
function parseFormattedText(text) {
|
|
667
|
+
if (!text) return [];
|
|
668
|
+
const nodes = [];
|
|
669
|
+
const parts = text.split(/(\*\*[^*]+\*\*|\*[^*]+\*|__[^_]+__|`[^`]+`)/);
|
|
670
|
+
for (const part of parts) {
|
|
671
|
+
if (!part) continue;
|
|
672
|
+
if (part.startsWith("**") && part.endsWith("**")) {
|
|
673
|
+
nodes.push({
|
|
674
|
+
type: "text",
|
|
675
|
+
text: part.slice(2, -2),
|
|
676
|
+
marks: [{ type: "bold" }]
|
|
677
|
+
});
|
|
678
|
+
} else if (part.startsWith("*") && part.endsWith("*")) {
|
|
679
|
+
nodes.push({
|
|
680
|
+
type: "text",
|
|
681
|
+
text: part.slice(1, -1),
|
|
682
|
+
marks: [{ type: "italic" }]
|
|
683
|
+
});
|
|
684
|
+
} else if (part.startsWith("__") && part.endsWith("__")) {
|
|
685
|
+
nodes.push({
|
|
686
|
+
type: "text",
|
|
687
|
+
text: part.slice(2, -2),
|
|
688
|
+
marks: [{ type: "underline" }]
|
|
689
|
+
});
|
|
690
|
+
} else if (part.startsWith("`") && part.endsWith("`")) {
|
|
691
|
+
nodes.push({
|
|
692
|
+
type: "text",
|
|
693
|
+
text: part.slice(1, -1),
|
|
694
|
+
marks: [{ type: "code" }]
|
|
695
|
+
});
|
|
696
|
+
} else {
|
|
697
|
+
nodes.push({ type: "text", text: part });
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
return nodes;
|
|
701
|
+
}
|
|
702
|
+
function markdownToTiptap(markdown) {
|
|
703
|
+
const lines = markdown.split("\n");
|
|
704
|
+
const content = [];
|
|
705
|
+
let i = 0;
|
|
706
|
+
while (i < lines.length) {
|
|
707
|
+
const line = lines[i];
|
|
708
|
+
if (line.trim() === "") {
|
|
709
|
+
i++;
|
|
710
|
+
continue;
|
|
711
|
+
}
|
|
712
|
+
if (/^---+$/.test(line.trim())) {
|
|
713
|
+
content.push({ type: "horizontalRule" });
|
|
714
|
+
i++;
|
|
715
|
+
continue;
|
|
716
|
+
}
|
|
717
|
+
const headingMatch = line.match(/^(#{1,3})\s+(.+)/);
|
|
718
|
+
if (headingMatch) {
|
|
719
|
+
const level = headingMatch[1].length;
|
|
720
|
+
const text = headingMatch[2];
|
|
721
|
+
content.push({
|
|
722
|
+
type: "heading",
|
|
723
|
+
attrs: { level },
|
|
724
|
+
content: parseInline(text)
|
|
725
|
+
});
|
|
726
|
+
i++;
|
|
727
|
+
continue;
|
|
728
|
+
}
|
|
729
|
+
if (/^\s*[-*]\s+/.test(line)) {
|
|
730
|
+
const items = [];
|
|
731
|
+
while (i < lines.length && /^\s*[-*]\s+/.test(lines[i])) {
|
|
732
|
+
const itemText = lines[i].replace(/^\s*[-*]\s+/, "");
|
|
733
|
+
items.push({
|
|
734
|
+
type: "listItem",
|
|
735
|
+
content: [{ type: "paragraph", content: parseInline(itemText) }]
|
|
736
|
+
});
|
|
737
|
+
i++;
|
|
738
|
+
}
|
|
739
|
+
content.push({ type: "bulletList", content: items });
|
|
740
|
+
continue;
|
|
741
|
+
}
|
|
742
|
+
if (/^\s*\d+\.\s+/.test(line)) {
|
|
743
|
+
const items = [];
|
|
744
|
+
while (i < lines.length && /^\s*\d+\.\s+/.test(lines[i])) {
|
|
745
|
+
const itemText = lines[i].replace(/^\s*\d+\.\s+/, "");
|
|
746
|
+
items.push({
|
|
747
|
+
type: "listItem",
|
|
748
|
+
content: [{ type: "paragraph", content: parseInline(itemText) }]
|
|
749
|
+
});
|
|
750
|
+
i++;
|
|
751
|
+
}
|
|
752
|
+
content.push({ type: "orderedList", content: items });
|
|
753
|
+
continue;
|
|
754
|
+
}
|
|
755
|
+
if (line.startsWith("> ")) {
|
|
756
|
+
const quoteLines = [];
|
|
757
|
+
while (i < lines.length && lines[i].startsWith("> ")) {
|
|
758
|
+
quoteLines.push(lines[i].slice(2));
|
|
759
|
+
i++;
|
|
760
|
+
}
|
|
761
|
+
content.push({
|
|
762
|
+
type: "blockquote",
|
|
763
|
+
content: [{ type: "paragraph", content: parseInline(quoteLines.join("\n")) }]
|
|
764
|
+
});
|
|
765
|
+
continue;
|
|
766
|
+
}
|
|
767
|
+
if (line.startsWith("```")) {
|
|
768
|
+
i++;
|
|
769
|
+
const codeLines = [];
|
|
770
|
+
while (i < lines.length && !lines[i].startsWith("```")) {
|
|
771
|
+
codeLines.push(lines[i]);
|
|
772
|
+
i++;
|
|
773
|
+
}
|
|
774
|
+
if (i < lines.length) i++;
|
|
775
|
+
content.push({
|
|
776
|
+
type: "codeBlock",
|
|
777
|
+
content: [{ type: "text", text: codeLines.join("\n") }]
|
|
778
|
+
});
|
|
779
|
+
continue;
|
|
780
|
+
}
|
|
781
|
+
content.push({
|
|
782
|
+
type: "paragraph",
|
|
783
|
+
content: parseInline(line)
|
|
784
|
+
});
|
|
785
|
+
i++;
|
|
786
|
+
}
|
|
787
|
+
return { type: "doc", content };
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
// src/utils/error-helpers.ts
|
|
791
|
+
function getErrorMessage(error) {
|
|
792
|
+
if (error instanceof Error) return error.message;
|
|
793
|
+
if (typeof error === "string") return error;
|
|
794
|
+
try {
|
|
795
|
+
return JSON.stringify(error);
|
|
796
|
+
} catch {
|
|
797
|
+
return String(error);
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
function formatError(context, error) {
|
|
801
|
+
const message = getErrorMessage(error);
|
|
802
|
+
return `${context}: ${message}`;
|
|
803
|
+
}
|
|
804
|
+
var CONTEXT_CHARS = 25;
|
|
805
|
+
function extractPosition(error, inputLength) {
|
|
806
|
+
const msg = error.message;
|
|
807
|
+
const v8 = /position\s+(\d+)/i.exec(msg);
|
|
808
|
+
if (v8) return Number(v8[1]);
|
|
809
|
+
if (/unexpected end/i.test(msg) || /unterminated/i.test(msg)) {
|
|
810
|
+
return inputLength > 0 ? inputLength - 1 : 0;
|
|
811
|
+
}
|
|
812
|
+
return null;
|
|
813
|
+
}
|
|
814
|
+
function stripNativePosition(msg) {
|
|
815
|
+
const v8Stripped = msg.replace(/\s+in JSON at position\s+\d+(\s*\(line\s+\d+\s+column\s+\d+\))?/i, "");
|
|
816
|
+
if (v8Stripped !== msg) return v8Stripped;
|
|
817
|
+
const ffStripped = msg.replace(/\s+at line\s+\d+\s+column\s+\d+\s+of the JSON data/i, "");
|
|
818
|
+
if (ffStripped !== msg) return ffStripped;
|
|
819
|
+
return msg;
|
|
820
|
+
}
|
|
821
|
+
function posToLineCol(text, pos) {
|
|
822
|
+
let line = 1;
|
|
823
|
+
let lastNewline = -1;
|
|
824
|
+
for (let i = 0; i < pos && i < text.length; i++) {
|
|
825
|
+
if (text[i] === "\n") {
|
|
826
|
+
line++;
|
|
827
|
+
lastNewline = i;
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
return { line, col: pos - lastNewline };
|
|
831
|
+
}
|
|
832
|
+
function extractLineCol(error) {
|
|
833
|
+
const ff = /line\s+(\d+)\s+column\s+(\d+)/i.exec(error.message);
|
|
834
|
+
if (ff) return { line: Number(ff[1]), col: Number(ff[2]) };
|
|
835
|
+
return null;
|
|
836
|
+
}
|
|
837
|
+
function buildContextSnippet(text, pos) {
|
|
838
|
+
const start = Math.max(0, pos - CONTEXT_CHARS);
|
|
839
|
+
const end = Math.min(text.length, pos + CONTEXT_CHARS);
|
|
840
|
+
const prefix = start > 0 ? "..." : "";
|
|
841
|
+
const suffix = end < text.length ? "..." : "";
|
|
842
|
+
return `${prefix}${text.slice(start, end)}${suffix}`;
|
|
843
|
+
}
|
|
844
|
+
function parseJsonWithDetails(input) {
|
|
845
|
+
try {
|
|
846
|
+
const data = JSON.parse(input);
|
|
847
|
+
return { valid: true, data };
|
|
848
|
+
} catch (err) {
|
|
849
|
+
if (!(err instanceof SyntaxError)) {
|
|
850
|
+
return { valid: false, message: getErrorMessage(err), line: 0, col: 0 };
|
|
851
|
+
}
|
|
852
|
+
const pos = extractPosition(err, input.length);
|
|
853
|
+
let lc = extractLineCol(err);
|
|
854
|
+
if (!lc && pos !== null) {
|
|
855
|
+
lc = posToLineCol(input, pos);
|
|
856
|
+
}
|
|
857
|
+
const line = lc?.line ?? 0;
|
|
858
|
+
const col = lc?.col ?? 0;
|
|
859
|
+
let message = stripNativePosition(err.message);
|
|
860
|
+
if (line > 0) {
|
|
861
|
+
message += ` (line ${line}, col ${col})`;
|
|
862
|
+
}
|
|
863
|
+
if (pos !== null) {
|
|
864
|
+
const snippet = buildContextSnippet(input, pos);
|
|
865
|
+
message += `
|
|
866
|
+
near: ${snippet}`;
|
|
867
|
+
}
|
|
868
|
+
return { valid: false, message, line, col };
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
// src/utils/pdf-metadata.ts
|
|
873
|
+
var PDFName;
|
|
874
|
+
var PDFString;
|
|
875
|
+
function initPdfMetadata(pdfLibModule) {
|
|
876
|
+
PDFName = pdfLibModule.PDFName;
|
|
877
|
+
PDFString = pdfLibModule.PDFString;
|
|
878
|
+
}
|
|
879
|
+
function setSigniphiMetadata(pdfDoc, metadata) {
|
|
880
|
+
if (!PDFName || !PDFString) {
|
|
881
|
+
throw new Error("PDF metadata classes not initialized \u2014 call initPdfMetadata before setSigniphiMetadata");
|
|
882
|
+
}
|
|
883
|
+
try {
|
|
884
|
+
const infoRef = pdfDoc.context.trailerInfo.Info;
|
|
885
|
+
const infoObj = pdfDoc.context.lookup(infoRef);
|
|
886
|
+
if (!infoObj || typeof infoObj.set !== "function") {
|
|
887
|
+
throw new Error("Cannot access PDF document info dictionary for metadata storage");
|
|
888
|
+
}
|
|
889
|
+
const infoDict = infoObj;
|
|
890
|
+
const metadataString = JSON.stringify(metadata);
|
|
891
|
+
infoDict.set(PDFName.of("SigniphiMetadata"), PDFString.of(metadataString));
|
|
892
|
+
} catch (error) {
|
|
893
|
+
throw new Error(`Failed to embed document metadata: ${getErrorMessage(error)}`);
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
function buildMetadataObject(fields, actualFieldNames) {
|
|
897
|
+
const metadata = {
|
|
898
|
+
version: "1.0",
|
|
899
|
+
fields: {},
|
|
900
|
+
fieldIdIndex: {}
|
|
901
|
+
};
|
|
902
|
+
fields.forEach((field) => {
|
|
903
|
+
const hasMetadata = field.fieldId || field.label || field.placeholder || field.acknowledgements?.length || field.required !== void 0 || field.options?.length;
|
|
904
|
+
if (hasMetadata) {
|
|
905
|
+
const actualFieldName = actualFieldNames?.get(field.name) || field.name;
|
|
906
|
+
metadata.fields[actualFieldName] = {
|
|
907
|
+
fieldId: field.fieldId,
|
|
908
|
+
label: field.label,
|
|
909
|
+
placeholder: field.placeholder,
|
|
910
|
+
acknowledgements: field.acknowledgements,
|
|
911
|
+
required: field.required,
|
|
912
|
+
options: field.options
|
|
913
|
+
};
|
|
914
|
+
if (field.fieldId) {
|
|
915
|
+
metadata.fieldIdIndex[field.fieldId] = actualFieldName;
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
});
|
|
919
|
+
return metadata;
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
// src/utils/pdf-generator.ts
|
|
923
|
+
var PAGE_WIDTH = 595.28;
|
|
924
|
+
var PAGE_HEIGHT = 841.89;
|
|
925
|
+
var MARGIN = 72;
|
|
926
|
+
var CONTENT_WIDTH = PAGE_WIDTH - 2 * MARGIN;
|
|
927
|
+
var CONTENT_HEIGHT = PAGE_HEIGHT - 2 * MARGIN;
|
|
928
|
+
var LINE_HEIGHT_FACTOR = 1.4;
|
|
929
|
+
var FIELD_DISPLAY = {
|
|
930
|
+
["text" /* TEXT */]: { label: "Text", width: 120, height: 30 },
|
|
931
|
+
["signature" /* SIGNATURE */]: { label: "Signature", width: 200, height: 60 },
|
|
932
|
+
["initials" /* INITIALS */]: { label: "Initials", width: 120, height: 30 },
|
|
933
|
+
["date" /* DATE */]: { label: "Date", width: 100, height: 30 },
|
|
934
|
+
["checkbox" /* CHECKBOX */]: { label: "Checkbox", width: 20, height: 20 },
|
|
935
|
+
["radio" /* RADIO */]: { label: "Radio", width: 100, height: 88 },
|
|
936
|
+
["dropdown" /* DROPDOWN */]: { label: "Dropdown", width: 120, height: 30 },
|
|
937
|
+
["text_label" /* TEXT_LABEL */]: { label: "Label", width: 150, height: 25 }
|
|
938
|
+
};
|
|
939
|
+
var INLINE_FIELD_TYPES = /* @__PURE__ */ new Set([
|
|
940
|
+
"text" /* TEXT */,
|
|
941
|
+
"date" /* DATE */,
|
|
942
|
+
"dropdown" /* DROPDOWN */,
|
|
943
|
+
"text_label" /* TEXT_LABEL */
|
|
944
|
+
]);
|
|
945
|
+
function getFieldDimensions(fieldType, attrs, font, fontSize) {
|
|
946
|
+
if (!INLINE_FIELD_TYPES.has(fieldType)) {
|
|
947
|
+
const display = FIELD_DISPLAY[fieldType] || FIELD_DISPLAY["text" /* TEXT */];
|
|
948
|
+
return { width: display.width, height: display.height };
|
|
949
|
+
}
|
|
950
|
+
const label = attrs?.fieldLabel || attrs?.fieldName || fieldType;
|
|
951
|
+
const textLineHeight = fontSize * LINE_HEIGHT_FACTOR;
|
|
952
|
+
const labelText = `[ ${label} ]`;
|
|
953
|
+
const textWidth = font.widthOfTextAtSize(labelText, fontSize);
|
|
954
|
+
const padding = 12;
|
|
955
|
+
const width = Math.max(textWidth + padding, 60);
|
|
956
|
+
const height = textLineHeight + 4;
|
|
957
|
+
return { width, height };
|
|
958
|
+
}
|
|
959
|
+
function ensureSpace(state, neededHeight) {
|
|
960
|
+
if (state.y + neededHeight > CONTENT_HEIGHT) {
|
|
961
|
+
const newPage = state.pdfDoc.addPage([PAGE_WIDTH, PAGE_HEIGHT]);
|
|
962
|
+
state.currentPage = newPage;
|
|
963
|
+
state.pageIndex++;
|
|
964
|
+
state.y = 0;
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
function drawText(state, text, font, fontSize, indent = 0) {
|
|
968
|
+
const lineHeight = fontSize * LINE_HEIGHT_FACTOR;
|
|
969
|
+
const maxWidth = CONTENT_WIDTH - indent;
|
|
970
|
+
const words = text.split(/\s+/);
|
|
971
|
+
let line = "";
|
|
972
|
+
let totalAdvance = 0;
|
|
973
|
+
for (const word of words) {
|
|
974
|
+
const testLine = line ? `${line} ${word}` : word;
|
|
975
|
+
const testWidth = font.widthOfTextAtSize(testLine, fontSize);
|
|
976
|
+
if (testWidth > maxWidth && line) {
|
|
977
|
+
ensureSpace(state, lineHeight);
|
|
978
|
+
const pdfY = PAGE_HEIGHT - MARGIN - state.y - fontSize;
|
|
979
|
+
state.currentPage.drawText(line, {
|
|
980
|
+
x: MARGIN + indent,
|
|
981
|
+
y: pdfY,
|
|
982
|
+
size: fontSize,
|
|
983
|
+
font,
|
|
984
|
+
color: rgb(0, 0, 0)
|
|
985
|
+
});
|
|
986
|
+
state.y += lineHeight;
|
|
987
|
+
totalAdvance += lineHeight;
|
|
988
|
+
line = word;
|
|
989
|
+
} else {
|
|
990
|
+
line = testLine;
|
|
991
|
+
}
|
|
992
|
+
}
|
|
993
|
+
if (line) {
|
|
994
|
+
ensureSpace(state, lineHeight);
|
|
995
|
+
const pdfY = PAGE_HEIGHT - MARGIN - state.y - fontSize;
|
|
996
|
+
state.currentPage.drawText(line, {
|
|
997
|
+
x: MARGIN + indent,
|
|
998
|
+
y: pdfY,
|
|
999
|
+
size: fontSize,
|
|
1000
|
+
font,
|
|
1001
|
+
color: rgb(0, 0, 0)
|
|
1002
|
+
});
|
|
1003
|
+
state.y += lineHeight;
|
|
1004
|
+
totalAdvance += lineHeight;
|
|
1005
|
+
}
|
|
1006
|
+
return totalAdvance;
|
|
1007
|
+
}
|
|
1008
|
+
function collectInlineSegments(content, fonts, fontSize) {
|
|
1009
|
+
const segments = [];
|
|
1010
|
+
for (const node of content) {
|
|
1011
|
+
if (node.type === "fieldNode") {
|
|
1012
|
+
const fieldType = node.attrs?.fieldType || "text";
|
|
1013
|
+
const { width, height } = getFieldDimensions(fieldType, node.attrs, fonts.italic, fontSize);
|
|
1014
|
+
segments.push({ kind: "field", attrs: node.attrs || {}, width, height });
|
|
1015
|
+
} else if (node.type === "text") {
|
|
1016
|
+
const text = node.text || "";
|
|
1017
|
+
const marks = node.marks || [];
|
|
1018
|
+
const isBold = marks.some((m) => m.type === "bold");
|
|
1019
|
+
const isItalic = marks.some((m) => m.type === "italic");
|
|
1020
|
+
let font = fonts.regular;
|
|
1021
|
+
if (isBold && isItalic) font = fonts.boldItalic;
|
|
1022
|
+
else if (isBold) font = fonts.bold;
|
|
1023
|
+
else if (isItalic) font = fonts.italic;
|
|
1024
|
+
segments.push({ kind: "text", text, font, fontSize });
|
|
1025
|
+
} else if (node.type === "variableNode") {
|
|
1026
|
+
const label = node.attrs?.varLabel || node.attrs?.varName || "Variable";
|
|
1027
|
+
const varMarks = node.marks || [];
|
|
1028
|
+
const varIsBold = varMarks.some((m) => m.type === "bold");
|
|
1029
|
+
const varIsItalic = varMarks.some((m) => m.type === "italic");
|
|
1030
|
+
let varFont = fonts.regular;
|
|
1031
|
+
if (varIsBold && varIsItalic) varFont = fonts.boldItalic;
|
|
1032
|
+
else if (varIsBold) varFont = fonts.bold;
|
|
1033
|
+
else if (varIsItalic) varFont = fonts.italic;
|
|
1034
|
+
segments.push({ kind: "text", text: `[${label}]`, font: varFont, fontSize });
|
|
1035
|
+
} else if (node.type === "hardBreak") {
|
|
1036
|
+
segments.push({ kind: "break" });
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
return segments;
|
|
1040
|
+
}
|
|
1041
|
+
function layoutInlineSegments(state, segments, fontSize, indent = 0) {
|
|
1042
|
+
const textLineHeight = fontSize * LINE_HEIGHT_FACTOR;
|
|
1043
|
+
const maxX = MARGIN + CONTENT_WIDTH;
|
|
1044
|
+
const startX = MARGIN + indent;
|
|
1045
|
+
let currentX = startX;
|
|
1046
|
+
let currentLineHeight = 0;
|
|
1047
|
+
let lineHasContent = false;
|
|
1048
|
+
function flushLine() {
|
|
1049
|
+
if (lineHasContent) {
|
|
1050
|
+
state.y += currentLineHeight;
|
|
1051
|
+
}
|
|
1052
|
+
currentX = startX;
|
|
1053
|
+
currentLineHeight = 0;
|
|
1054
|
+
lineHasContent = false;
|
|
1055
|
+
}
|
|
1056
|
+
for (const seg of segments) {
|
|
1057
|
+
if (seg.kind === "break") {
|
|
1058
|
+
if (!lineHasContent) {
|
|
1059
|
+
currentLineHeight = textLineHeight;
|
|
1060
|
+
lineHasContent = true;
|
|
1061
|
+
}
|
|
1062
|
+
flushLine();
|
|
1063
|
+
continue;
|
|
1064
|
+
}
|
|
1065
|
+
if (seg.kind === "field") {
|
|
1066
|
+
const fieldGap = 4;
|
|
1067
|
+
const totalFieldWidth = seg.width + fieldGap;
|
|
1068
|
+
if (lineHasContent && currentX + seg.width > maxX) {
|
|
1069
|
+
flushLine();
|
|
1070
|
+
}
|
|
1071
|
+
ensureSpace(state, Math.max(seg.height, textLineHeight));
|
|
1072
|
+
const fieldX = currentX;
|
|
1073
|
+
const pdfY = PAGE_HEIGHT - MARGIN - state.y - seg.height;
|
|
1074
|
+
if (state.drawFieldPlaceholders) {
|
|
1075
|
+
const fieldType = seg.attrs.fieldType || "text";
|
|
1076
|
+
const fieldLabel = seg.attrs.fieldLabel || seg.attrs.fieldName || fieldType;
|
|
1077
|
+
const isInline = INLINE_FIELD_TYPES.has(fieldType);
|
|
1078
|
+
state.currentPage.drawRectangle({
|
|
1079
|
+
x: fieldX,
|
|
1080
|
+
y: pdfY,
|
|
1081
|
+
width: seg.width,
|
|
1082
|
+
height: seg.height,
|
|
1083
|
+
borderColor: rgb(0.5, 0.5, 0.7),
|
|
1084
|
+
borderWidth: isInline ? 0.5 : 1,
|
|
1085
|
+
color: rgb(0.95, 0.95, 1),
|
|
1086
|
+
borderDashArray: [4, 2]
|
|
1087
|
+
});
|
|
1088
|
+
const display = FIELD_DISPLAY[fieldType] || FIELD_DISPLAY["text" /* TEXT */];
|
|
1089
|
+
const labelFontSize = isInline ? fontSize - 2 : 8;
|
|
1090
|
+
const labelText = isInline ? `[ ${fieldLabel} ]` : `[ ${display.label}: ${fieldLabel} ]`;
|
|
1091
|
+
const labelPadding = isInline ? 6 : 4;
|
|
1092
|
+
state.currentPage.drawText(labelText, {
|
|
1093
|
+
x: fieldX + labelPadding,
|
|
1094
|
+
y: pdfY + seg.height / 2 - labelFontSize / 2,
|
|
1095
|
+
size: labelFontSize,
|
|
1096
|
+
font: state.fonts.italic,
|
|
1097
|
+
color: rgb(0.4, 0.4, 0.6),
|
|
1098
|
+
maxWidth: seg.width - labelPadding * 2
|
|
1099
|
+
});
|
|
1100
|
+
}
|
|
1101
|
+
const fieldId = seg.attrs.fieldId;
|
|
1102
|
+
if (fieldId) {
|
|
1103
|
+
state.fieldPositions.set(fieldId, {
|
|
1104
|
+
x: fieldX,
|
|
1105
|
+
y: pdfY,
|
|
1106
|
+
width: seg.width,
|
|
1107
|
+
height: seg.height,
|
|
1108
|
+
page: state.pageIndex + 1
|
|
1109
|
+
});
|
|
1110
|
+
}
|
|
1111
|
+
currentX += totalFieldWidth;
|
|
1112
|
+
currentLineHeight = Math.max(currentLineHeight, seg.height + 4);
|
|
1113
|
+
lineHasContent = true;
|
|
1114
|
+
continue;
|
|
1115
|
+
}
|
|
1116
|
+
if (seg.kind === "text") {
|
|
1117
|
+
const { text, font } = seg;
|
|
1118
|
+
const spaceWidth = font.widthOfTextAtSize(" ", fontSize);
|
|
1119
|
+
const words = text.split(/(\s+)/);
|
|
1120
|
+
for (const token of words) {
|
|
1121
|
+
if (!token) continue;
|
|
1122
|
+
if (/^\s+$/.test(token)) {
|
|
1123
|
+
if (lineHasContent) {
|
|
1124
|
+
currentX += spaceWidth;
|
|
1125
|
+
}
|
|
1126
|
+
continue;
|
|
1127
|
+
}
|
|
1128
|
+
const wordWidth = font.widthOfTextAtSize(token, fontSize);
|
|
1129
|
+
if (lineHasContent && currentX + wordWidth > maxX) {
|
|
1130
|
+
flushLine();
|
|
1131
|
+
}
|
|
1132
|
+
ensureSpace(state, textLineHeight);
|
|
1133
|
+
const pdfY = PAGE_HEIGHT - MARGIN - state.y - fontSize;
|
|
1134
|
+
state.currentPage.drawText(token, {
|
|
1135
|
+
x: currentX,
|
|
1136
|
+
y: pdfY,
|
|
1137
|
+
size: fontSize,
|
|
1138
|
+
font,
|
|
1139
|
+
color: rgb(0, 0, 0)
|
|
1140
|
+
});
|
|
1141
|
+
currentX += wordWidth;
|
|
1142
|
+
currentLineHeight = Math.max(currentLineHeight, textLineHeight);
|
|
1143
|
+
lineHasContent = true;
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1146
|
+
}
|
|
1147
|
+
flushLine();
|
|
1148
|
+
}
|
|
1149
|
+
function processInlineContent(state, content, fontSize, indent = 0) {
|
|
1150
|
+
if (!content) return;
|
|
1151
|
+
const segments = collectInlineSegments(content, state.fonts, fontSize);
|
|
1152
|
+
if (segments.length === 0) return;
|
|
1153
|
+
layoutInlineSegments(state, segments, fontSize, indent);
|
|
1154
|
+
}
|
|
1155
|
+
function processBlock(state, node) {
|
|
1156
|
+
switch (node.type) {
|
|
1157
|
+
case "heading": {
|
|
1158
|
+
const level = node.attrs?.level || 1;
|
|
1159
|
+
const fontSizes = { 1: 24, 2: 20, 3: 16 };
|
|
1160
|
+
const fontSize = fontSizes[level] || 16;
|
|
1161
|
+
const spacing = fontSize * 0.6;
|
|
1162
|
+
ensureSpace(state, fontSize * LINE_HEIGHT_FACTOR + spacing);
|
|
1163
|
+
state.y += spacing / 2;
|
|
1164
|
+
if (!node.content) break;
|
|
1165
|
+
const headingFonts = {
|
|
1166
|
+
regular: state.fonts.bold,
|
|
1167
|
+
bold: state.fonts.bold,
|
|
1168
|
+
italic: state.fonts.boldItalic,
|
|
1169
|
+
boldItalic: state.fonts.boldItalic
|
|
1170
|
+
};
|
|
1171
|
+
const segments = collectInlineSegments(node.content, headingFonts, fontSize);
|
|
1172
|
+
layoutInlineSegments(state, segments, fontSize);
|
|
1173
|
+
state.y += spacing / 2;
|
|
1174
|
+
break;
|
|
1175
|
+
}
|
|
1176
|
+
case "paragraph": {
|
|
1177
|
+
const fontSize = 12;
|
|
1178
|
+
if (!node.content || node.content.length === 0) {
|
|
1179
|
+
state.y += fontSize * LINE_HEIGHT_FACTOR;
|
|
1180
|
+
break;
|
|
1181
|
+
}
|
|
1182
|
+
processInlineContent(state, node.content, fontSize);
|
|
1183
|
+
state.y += 4;
|
|
1184
|
+
break;
|
|
1185
|
+
}
|
|
1186
|
+
case "bulletList": {
|
|
1187
|
+
for (const item of node.content || []) {
|
|
1188
|
+
for (const child of item.content || []) {
|
|
1189
|
+
const fontSize = 12;
|
|
1190
|
+
ensureSpace(state, fontSize * LINE_HEIGHT_FACTOR);
|
|
1191
|
+
const bulletPdfY = PAGE_HEIGHT - MARGIN - state.y - fontSize;
|
|
1192
|
+
state.currentPage.drawText("\u2022", {
|
|
1193
|
+
x: MARGIN + 8,
|
|
1194
|
+
y: bulletPdfY,
|
|
1195
|
+
size: fontSize,
|
|
1196
|
+
font: state.fonts.regular,
|
|
1197
|
+
color: rgb(0, 0, 0)
|
|
1198
|
+
});
|
|
1199
|
+
processInlineContent(state, child.content, fontSize, 24);
|
|
1200
|
+
}
|
|
1201
|
+
}
|
|
1202
|
+
state.y += 4;
|
|
1203
|
+
break;
|
|
1204
|
+
}
|
|
1205
|
+
case "orderedList": {
|
|
1206
|
+
let num = 1;
|
|
1207
|
+
for (const item of node.content || []) {
|
|
1208
|
+
for (const child of item.content || []) {
|
|
1209
|
+
const fontSize = 12;
|
|
1210
|
+
ensureSpace(state, fontSize * LINE_HEIGHT_FACTOR);
|
|
1211
|
+
const numPdfY = PAGE_HEIGHT - MARGIN - state.y - fontSize;
|
|
1212
|
+
state.currentPage.drawText(`${num}.`, {
|
|
1213
|
+
x: MARGIN + 4,
|
|
1214
|
+
y: numPdfY,
|
|
1215
|
+
size: fontSize,
|
|
1216
|
+
font: state.fonts.regular,
|
|
1217
|
+
color: rgb(0, 0, 0)
|
|
1218
|
+
});
|
|
1219
|
+
processInlineContent(state, child.content, fontSize, 24);
|
|
1220
|
+
}
|
|
1221
|
+
num++;
|
|
1222
|
+
}
|
|
1223
|
+
state.y += 4;
|
|
1224
|
+
break;
|
|
1225
|
+
}
|
|
1226
|
+
case "blockquote": {
|
|
1227
|
+
const startY = state.y;
|
|
1228
|
+
for (const child of node.content || []) {
|
|
1229
|
+
processInlineContent(state, child.content, 12, 16);
|
|
1230
|
+
}
|
|
1231
|
+
const endY = state.y;
|
|
1232
|
+
const barX = MARGIN + 4;
|
|
1233
|
+
const barTop = PAGE_HEIGHT - MARGIN - startY;
|
|
1234
|
+
const barBottom = PAGE_HEIGHT - MARGIN - endY;
|
|
1235
|
+
state.currentPage.drawLine({
|
|
1236
|
+
start: { x: barX, y: barTop },
|
|
1237
|
+
end: { x: barX, y: barBottom },
|
|
1238
|
+
thickness: 2,
|
|
1239
|
+
color: rgb(0.7, 0.7, 0.7)
|
|
1240
|
+
});
|
|
1241
|
+
state.y += 4;
|
|
1242
|
+
break;
|
|
1243
|
+
}
|
|
1244
|
+
case "horizontalRule": {
|
|
1245
|
+
ensureSpace(state, 16);
|
|
1246
|
+
state.y += 8;
|
|
1247
|
+
const ruleY = PAGE_HEIGHT - MARGIN - state.y;
|
|
1248
|
+
state.currentPage.drawLine({
|
|
1249
|
+
start: { x: MARGIN, y: ruleY },
|
|
1250
|
+
end: { x: PAGE_WIDTH - MARGIN, y: ruleY },
|
|
1251
|
+
thickness: 1,
|
|
1252
|
+
color: rgb(0.7, 0.7, 0.7)
|
|
1253
|
+
});
|
|
1254
|
+
state.y += 8;
|
|
1255
|
+
break;
|
|
1256
|
+
}
|
|
1257
|
+
case "codeBlock": {
|
|
1258
|
+
const fontSize = 10;
|
|
1259
|
+
const text = node.content?.map((c) => c.text || "").join("") || "";
|
|
1260
|
+
const lines = text.split("\n");
|
|
1261
|
+
const lineHeight = fontSize * LINE_HEIGHT_FACTOR;
|
|
1262
|
+
const blockHeight = lines.length * lineHeight + 16;
|
|
1263
|
+
ensureSpace(state, blockHeight);
|
|
1264
|
+
const boxY = PAGE_HEIGHT - MARGIN - state.y - blockHeight;
|
|
1265
|
+
state.currentPage.drawRectangle({
|
|
1266
|
+
x: MARGIN,
|
|
1267
|
+
y: boxY,
|
|
1268
|
+
width: CONTENT_WIDTH,
|
|
1269
|
+
height: blockHeight,
|
|
1270
|
+
color: rgb(0.95, 0.95, 0.95),
|
|
1271
|
+
borderColor: rgb(0.85, 0.85, 0.85),
|
|
1272
|
+
borderWidth: 0.5
|
|
1273
|
+
});
|
|
1274
|
+
state.y += 8;
|
|
1275
|
+
for (const line of lines) {
|
|
1276
|
+
drawText(state, line || " ", state.fonts.regular, fontSize, 8);
|
|
1277
|
+
}
|
|
1278
|
+
state.y += 8;
|
|
1279
|
+
break;
|
|
1280
|
+
}
|
|
1281
|
+
default:
|
|
1282
|
+
if (node.content) {
|
|
1283
|
+
processInlineContent(state, node.content, 12);
|
|
1284
|
+
}
|
|
1285
|
+
break;
|
|
1286
|
+
}
|
|
1287
|
+
}
|
|
1288
|
+
function ensureFieldSuffix(fieldName, suffix) {
|
|
1289
|
+
const cleanName = fieldName.replace(/_signature$/i, "").replace(/_initials$/i, "").replace(/_date$/i, "");
|
|
1290
|
+
return `${cleanName}${suffix}`;
|
|
1291
|
+
}
|
|
1292
|
+
async function addFormFields(pdfDoc, fieldPositions, fields) {
|
|
1293
|
+
const form = pdfDoc.getForm();
|
|
1294
|
+
const font = await pdfDoc.embedFont(StandardFonts.Helvetica);
|
|
1295
|
+
const pages = pdfDoc.getPages();
|
|
1296
|
+
const actualFieldNames = /* @__PURE__ */ new Map();
|
|
1297
|
+
const warnings = [];
|
|
1298
|
+
let baseTimestamp = Date.now();
|
|
1299
|
+
for (const field of fields) {
|
|
1300
|
+
const position = fieldPositions.get(field.fieldId);
|
|
1301
|
+
if (!position || !field.name) continue;
|
|
1302
|
+
const pdfFieldName = `${field.type}_${baseTimestamp++}`;
|
|
1303
|
+
const pageIndex = position.page - 1;
|
|
1304
|
+
const page = pages[pageIndex];
|
|
1305
|
+
if (!page) continue;
|
|
1306
|
+
const pdfX = position.x;
|
|
1307
|
+
const pdfY = position.y;
|
|
1308
|
+
try {
|
|
1309
|
+
switch (field.type) {
|
|
1310
|
+
case "text" /* TEXT */: {
|
|
1311
|
+
const textField = form.createTextField(pdfFieldName);
|
|
1312
|
+
actualFieldNames.set(field.name, pdfFieldName);
|
|
1313
|
+
textField.addToPage(page, {
|
|
1314
|
+
x: pdfX,
|
|
1315
|
+
y: pdfY,
|
|
1316
|
+
width: position.width,
|
|
1317
|
+
height: position.height,
|
|
1318
|
+
borderColor: rgb(0.5, 0.5, 0.5),
|
|
1319
|
+
backgroundColor: rgb(1, 1, 1)
|
|
1320
|
+
});
|
|
1321
|
+
if (field.label && field.label.trim()) {
|
|
1322
|
+
let tuValue = field.label;
|
|
1323
|
+
if (field.fontSize && field.fontSize >= 8 && field.fontSize <= 72) {
|
|
1324
|
+
tuValue = `${field.label}|fontSize:${field.fontSize}`;
|
|
1325
|
+
}
|
|
1326
|
+
textField.acroField.dict.set(
|
|
1327
|
+
PDFName$1.of("TU"),
|
|
1328
|
+
PDFString$1.of(tuValue)
|
|
1329
|
+
);
|
|
1330
|
+
}
|
|
1331
|
+
if (field.defaultValue && field.defaultValue.trim()) {
|
|
1332
|
+
textField.setText(field.defaultValue);
|
|
1333
|
+
}
|
|
1334
|
+
if (field.fontSize && field.fontSize >= 8 && field.fontSize <= 72) {
|
|
1335
|
+
try {
|
|
1336
|
+
textField.setFontSize(field.fontSize);
|
|
1337
|
+
} catch (e) {
|
|
1338
|
+
}
|
|
1339
|
+
}
|
|
1340
|
+
if (field.multiline) {
|
|
1341
|
+
try {
|
|
1342
|
+
textField.enableMultiline();
|
|
1343
|
+
} catch (e) {
|
|
1344
|
+
}
|
|
1345
|
+
}
|
|
1346
|
+
if (field.maxLength && field.maxLength > 0) {
|
|
1347
|
+
try {
|
|
1348
|
+
textField.setMaxLength(field.maxLength);
|
|
1349
|
+
} catch (e) {
|
|
1350
|
+
}
|
|
1351
|
+
}
|
|
1352
|
+
if (field.required) {
|
|
1353
|
+
textField.enableRequired();
|
|
1354
|
+
}
|
|
1355
|
+
break;
|
|
1356
|
+
}
|
|
1357
|
+
case "signature" /* SIGNATURE */: {
|
|
1358
|
+
const fieldNameWithSuffix = ensureFieldSuffix(pdfFieldName, "_signature");
|
|
1359
|
+
actualFieldNames.set(field.name, fieldNameWithSuffix);
|
|
1360
|
+
const sigField = form.createTextField(fieldNameWithSuffix);
|
|
1361
|
+
sigField.addToPage(page, {
|
|
1362
|
+
x: pdfX,
|
|
1363
|
+
y: pdfY,
|
|
1364
|
+
width: position.width,
|
|
1365
|
+
height: position.height,
|
|
1366
|
+
borderColor: rgb(0, 0, 1),
|
|
1367
|
+
backgroundColor: rgb(0.9, 0.9, 1)
|
|
1368
|
+
});
|
|
1369
|
+
if (field.label && field.label.trim()) {
|
|
1370
|
+
sigField.acroField.dict.set(
|
|
1371
|
+
PDFName$1.of("TU"),
|
|
1372
|
+
PDFString$1.of(field.label)
|
|
1373
|
+
);
|
|
1374
|
+
}
|
|
1375
|
+
sigField.enableReadOnly();
|
|
1376
|
+
if (field.required) sigField.enableRequired();
|
|
1377
|
+
break;
|
|
1378
|
+
}
|
|
1379
|
+
case "initials" /* INITIALS */: {
|
|
1380
|
+
const fieldNameWithSuffix = ensureFieldSuffix(pdfFieldName, "_initials");
|
|
1381
|
+
actualFieldNames.set(field.name, fieldNameWithSuffix);
|
|
1382
|
+
const initialsField = form.createTextField(fieldNameWithSuffix);
|
|
1383
|
+
initialsField.addToPage(page, {
|
|
1384
|
+
x: pdfX,
|
|
1385
|
+
y: pdfY,
|
|
1386
|
+
width: position.width,
|
|
1387
|
+
height: position.height,
|
|
1388
|
+
borderColor: rgb(0, 0, 1),
|
|
1389
|
+
backgroundColor: rgb(0.9, 1, 0.9)
|
|
1390
|
+
});
|
|
1391
|
+
if (field.label && field.label.trim()) {
|
|
1392
|
+
initialsField.acroField.dict.set(
|
|
1393
|
+
PDFName$1.of("TU"),
|
|
1394
|
+
PDFString$1.of(field.label)
|
|
1395
|
+
);
|
|
1396
|
+
}
|
|
1397
|
+
initialsField.enableReadOnly();
|
|
1398
|
+
if (field.required) initialsField.enableRequired();
|
|
1399
|
+
break;
|
|
1400
|
+
}
|
|
1401
|
+
case "date" /* DATE */: {
|
|
1402
|
+
const fieldNameWithSuffix = ensureFieldSuffix(pdfFieldName, "_date");
|
|
1403
|
+
actualFieldNames.set(field.name, fieldNameWithSuffix);
|
|
1404
|
+
const dateField = form.createTextField(fieldNameWithSuffix);
|
|
1405
|
+
dateField.addToPage(page, {
|
|
1406
|
+
x: pdfX,
|
|
1407
|
+
y: pdfY,
|
|
1408
|
+
width: position.width,
|
|
1409
|
+
height: position.height,
|
|
1410
|
+
borderColor: rgb(0.5, 0.5, 0.5),
|
|
1411
|
+
backgroundColor: rgb(1, 1, 1)
|
|
1412
|
+
});
|
|
1413
|
+
const tuValue = field.placeholder && field.placeholder.trim() ? field.placeholder : field.label && field.label.trim() ? field.label : void 0;
|
|
1414
|
+
if (tuValue) {
|
|
1415
|
+
dateField.acroField.dict.set(
|
|
1416
|
+
PDFName$1.of("TU"),
|
|
1417
|
+
PDFString$1.of(tuValue)
|
|
1418
|
+
);
|
|
1419
|
+
}
|
|
1420
|
+
if (field.required) dateField.enableRequired();
|
|
1421
|
+
break;
|
|
1422
|
+
}
|
|
1423
|
+
case "checkbox" /* CHECKBOX */: {
|
|
1424
|
+
const checkBox = form.createCheckBox(pdfFieldName);
|
|
1425
|
+
actualFieldNames.set(field.name, pdfFieldName);
|
|
1426
|
+
checkBox.addToPage(page, {
|
|
1427
|
+
x: pdfX,
|
|
1428
|
+
y: pdfY,
|
|
1429
|
+
width: position.width,
|
|
1430
|
+
height: position.height,
|
|
1431
|
+
borderColor: rgb(0.5, 0.5, 0.5),
|
|
1432
|
+
backgroundColor: rgb(1, 1, 1)
|
|
1433
|
+
});
|
|
1434
|
+
if (field.defaultValue === "true" || field.defaultValue === "checked") {
|
|
1435
|
+
checkBox.check();
|
|
1436
|
+
}
|
|
1437
|
+
if (field.required) checkBox.enableRequired();
|
|
1438
|
+
break;
|
|
1439
|
+
}
|
|
1440
|
+
case "dropdown" /* DROPDOWN */: {
|
|
1441
|
+
const dropdown = form.createDropdown(pdfFieldName);
|
|
1442
|
+
actualFieldNames.set(field.name, pdfFieldName);
|
|
1443
|
+
if (field.options && field.options.length > 0) {
|
|
1444
|
+
dropdown.setOptions(field.options);
|
|
1445
|
+
}
|
|
1446
|
+
dropdown.addToPage(page, {
|
|
1447
|
+
x: pdfX,
|
|
1448
|
+
y: pdfY,
|
|
1449
|
+
width: position.width,
|
|
1450
|
+
height: position.height,
|
|
1451
|
+
borderColor: rgb(0.5, 0.5, 0.5),
|
|
1452
|
+
backgroundColor: rgb(1, 1, 1)
|
|
1453
|
+
});
|
|
1454
|
+
if (field.required) dropdown.enableRequired();
|
|
1455
|
+
break;
|
|
1456
|
+
}
|
|
1457
|
+
case "radio" /* RADIO */: {
|
|
1458
|
+
const radioGroup = form.createRadioGroup(pdfFieldName);
|
|
1459
|
+
actualFieldNames.set(field.name, pdfFieldName);
|
|
1460
|
+
if (field.options && field.options.length > 0) {
|
|
1461
|
+
const radioButtonSize = 20;
|
|
1462
|
+
const buttonGap = 4;
|
|
1463
|
+
const radioSpacing = radioButtonSize + buttonGap;
|
|
1464
|
+
const topPadding = 8;
|
|
1465
|
+
const fieldTopY = pdfY + position.height;
|
|
1466
|
+
field.options.forEach((option, index) => {
|
|
1467
|
+
const radioX = pdfX;
|
|
1468
|
+
const radioY = fieldTopY - topPadding - radioButtonSize - index * radioSpacing;
|
|
1469
|
+
radioGroup.addOptionToPage(option, page, {
|
|
1470
|
+
x: radioX,
|
|
1471
|
+
y: radioY,
|
|
1472
|
+
width: radioButtonSize,
|
|
1473
|
+
height: radioButtonSize,
|
|
1474
|
+
borderColor: rgb(0.5, 0.5, 0.5),
|
|
1475
|
+
backgroundColor: rgb(1, 1, 1)
|
|
1476
|
+
});
|
|
1477
|
+
});
|
|
1478
|
+
}
|
|
1479
|
+
if (field.required) radioGroup.enableRequired();
|
|
1480
|
+
break;
|
|
1481
|
+
}
|
|
1482
|
+
case "text_label" /* TEXT_LABEL */: {
|
|
1483
|
+
const textContent = field.defaultValue || "Text Label";
|
|
1484
|
+
const labelFontSize = field.fontSize && field.fontSize >= 8 && field.fontSize <= 72 ? field.fontSize : Math.max(8, Math.min(16, position.height * 0.8));
|
|
1485
|
+
const textX = pdfX;
|
|
1486
|
+
const textY = pdfY + (position.height - labelFontSize) / 2;
|
|
1487
|
+
page.drawText(textContent, {
|
|
1488
|
+
x: textX,
|
|
1489
|
+
y: textY,
|
|
1490
|
+
size: labelFontSize,
|
|
1491
|
+
font,
|
|
1492
|
+
color: rgb(0, 0, 0)
|
|
1493
|
+
});
|
|
1494
|
+
break;
|
|
1495
|
+
}
|
|
1496
|
+
}
|
|
1497
|
+
} catch (err) {
|
|
1498
|
+
warnings.push(`Failed to create ${field.type} field "${field.name}": ${getErrorMessage(err)}`);
|
|
1499
|
+
}
|
|
1500
|
+
}
|
|
1501
|
+
initPdfMetadata({ PDFName: PDFName$1, PDFString: PDFString$1 });
|
|
1502
|
+
const metadata = buildMetadataObject(fields, actualFieldNames);
|
|
1503
|
+
setSigniphiMetadata(pdfDoc, metadata);
|
|
1504
|
+
return warnings;
|
|
1505
|
+
}
|
|
1506
|
+
async function generatePdfFromContent(content, options = {}) {
|
|
1507
|
+
const { drawFieldPlaceholders = false, embedFormFields = false, fields = [] } = options;
|
|
1508
|
+
const pdfDoc = await PDFDocument.create();
|
|
1509
|
+
const firstPage = pdfDoc.addPage([PAGE_WIDTH, PAGE_HEIGHT]);
|
|
1510
|
+
const fonts = {
|
|
1511
|
+
regular: await pdfDoc.embedFont(StandardFonts.Helvetica),
|
|
1512
|
+
bold: await pdfDoc.embedFont(StandardFonts.HelveticaBold),
|
|
1513
|
+
italic: await pdfDoc.embedFont(StandardFonts.HelveticaOblique),
|
|
1514
|
+
boldItalic: await pdfDoc.embedFont(StandardFonts.HelveticaBoldOblique)
|
|
1515
|
+
};
|
|
1516
|
+
const state = {
|
|
1517
|
+
currentPage: firstPage,
|
|
1518
|
+
pageIndex: 0,
|
|
1519
|
+
y: 0,
|
|
1520
|
+
fonts,
|
|
1521
|
+
pdfDoc,
|
|
1522
|
+
fieldPositions: /* @__PURE__ */ new Map(),
|
|
1523
|
+
drawFieldPlaceholders
|
|
1524
|
+
};
|
|
1525
|
+
if (content.content) {
|
|
1526
|
+
for (const block of content.content) {
|
|
1527
|
+
processBlock(state, block);
|
|
1528
|
+
}
|
|
1529
|
+
}
|
|
1530
|
+
let fieldWarnings;
|
|
1531
|
+
if (embedFormFields && fields.length > 0) {
|
|
1532
|
+
const warnings = await addFormFields(pdfDoc, state.fieldPositions, fields);
|
|
1533
|
+
if (warnings.length > 0) fieldWarnings = warnings;
|
|
1534
|
+
}
|
|
1535
|
+
const pdfBytes = await pdfDoc.save({ useObjectStreams: false });
|
|
1536
|
+
return {
|
|
1537
|
+
pdfBytes,
|
|
1538
|
+
fieldPositions: state.fieldPositions,
|
|
1539
|
+
fieldWarnings
|
|
1540
|
+
};
|
|
1541
|
+
}
|
|
1542
|
+
|
|
1543
|
+
// src/utils/pdf-preview.ts
|
|
1544
|
+
async function pdfToImages(pdfBytes, scale = 2) {
|
|
1545
|
+
let pdfjsLib;
|
|
1546
|
+
try {
|
|
1547
|
+
pdfjsLib = await import('pdfjs-dist');
|
|
1548
|
+
} catch (err) {
|
|
1549
|
+
throw new Error(formatError(
|
|
1550
|
+
"Failed to load the PDF preview library \u2014 ensure pdfjs-dist is installed and the worker is accessible",
|
|
1551
|
+
err
|
|
1552
|
+
));
|
|
1553
|
+
}
|
|
1554
|
+
if (!pdfjsLib.GlobalWorkerOptions.workerSrc) {
|
|
1555
|
+
pdfjsLib.GlobalWorkerOptions.workerSrc = "/pdfjs/build/pdf.worker.mjs";
|
|
1556
|
+
}
|
|
1557
|
+
let pdf;
|
|
1558
|
+
try {
|
|
1559
|
+
const loadingTask = pdfjsLib.getDocument({ data: pdfBytes });
|
|
1560
|
+
pdf = await loadingTask.promise;
|
|
1561
|
+
} catch (err) {
|
|
1562
|
+
throw new Error(formatError("Failed to parse the generated PDF for preview", err));
|
|
1563
|
+
}
|
|
1564
|
+
const pages = [];
|
|
1565
|
+
const pageErrors = [];
|
|
1566
|
+
for (let i = 1; i <= pdf.numPages; i++) {
|
|
1567
|
+
try {
|
|
1568
|
+
const page = await pdf.getPage(i);
|
|
1569
|
+
const viewport = page.getViewport({ scale });
|
|
1570
|
+
const canvas = document.createElement("canvas");
|
|
1571
|
+
canvas.width = viewport.width;
|
|
1572
|
+
canvas.height = viewport.height;
|
|
1573
|
+
const context = canvas.getContext("2d");
|
|
1574
|
+
if (!context) {
|
|
1575
|
+
throw new Error("Browser could not create a 2D canvas rendering context");
|
|
1576
|
+
}
|
|
1577
|
+
await page.render({
|
|
1578
|
+
canvasContext: context,
|
|
1579
|
+
viewport
|
|
1580
|
+
}).promise;
|
|
1581
|
+
const imageUrl = canvas.toDataURL("image/png");
|
|
1582
|
+
const originalViewport = page.getViewport({ scale: 1 });
|
|
1583
|
+
pages.push({
|
|
1584
|
+
pageNumber: i,
|
|
1585
|
+
width: originalViewport.width,
|
|
1586
|
+
height: originalViewport.height,
|
|
1587
|
+
imageUrl
|
|
1588
|
+
});
|
|
1589
|
+
page.cleanup();
|
|
1590
|
+
} catch (err) {
|
|
1591
|
+
pageErrors.push(`Page ${i}: ${getErrorMessage(err)}`);
|
|
1592
|
+
}
|
|
1593
|
+
}
|
|
1594
|
+
pdf.destroy();
|
|
1595
|
+
if (pages.length === 0 && pageErrors.length > 0) {
|
|
1596
|
+
throw new Error(`Failed to render PDF pages:
|
|
1597
|
+
${pageErrors.join("\n")}`);
|
|
1598
|
+
}
|
|
1599
|
+
return pages;
|
|
1600
|
+
}
|
|
1601
|
+
|
|
1602
|
+
// src/hooks/useDocumentGenerator.ts
|
|
1603
|
+
function useDocumentGenerator(options = {}) {
|
|
1604
|
+
const [fields, setFields] = useState([]);
|
|
1605
|
+
const [variables, setVariables] = useState([]);
|
|
1606
|
+
const [fieldPositions, setFieldPositions] = useState(/* @__PURE__ */ new Map());
|
|
1607
|
+
const [markdown, setMarkdown] = useState(options.initialMarkdown || "");
|
|
1608
|
+
const [pdfBytes, setPdfBytes] = useState(null);
|
|
1609
|
+
const [pdfPages, setPdfPages] = useState([]);
|
|
1610
|
+
const [isGenerating, setIsGenerating] = useState(false);
|
|
1611
|
+
const [error, setError] = useState(null);
|
|
1612
|
+
const [fieldWarnings, setFieldWarnings] = useState([]);
|
|
1613
|
+
const debounceRef = useRef();
|
|
1614
|
+
const onChangeRef = useRef(options.onChange);
|
|
1615
|
+
onChangeRef.current = options.onChange;
|
|
1616
|
+
const initialContent = options.initialContent || (options.initialMarkdown ? markdownToTiptap(options.initialMarkdown) : void 0);
|
|
1617
|
+
const editor = useEditor({
|
|
1618
|
+
extensions: [
|
|
1619
|
+
StarterKit.configure({
|
|
1620
|
+
heading: { levels: [1, 2, 3] }
|
|
1621
|
+
}),
|
|
1622
|
+
Underline,
|
|
1623
|
+
TextAlign.configure({
|
|
1624
|
+
types: ["heading", "paragraph"]
|
|
1625
|
+
}),
|
|
1626
|
+
Placeholder.configure({
|
|
1627
|
+
placeholder: options.placeholder || "Start writing your document..."
|
|
1628
|
+
}),
|
|
1629
|
+
FieldNode,
|
|
1630
|
+
VariableNode
|
|
1631
|
+
],
|
|
1632
|
+
content: initialContent || { type: "doc", content: [{ type: "paragraph" }] },
|
|
1633
|
+
onUpdate: ({ editor: ed }) => {
|
|
1634
|
+
clearTimeout(debounceRef.current);
|
|
1635
|
+
debounceRef.current = setTimeout(() => {
|
|
1636
|
+
const content = ed.getJSON();
|
|
1637
|
+
const extractedFields = extractFieldsFromContent(content);
|
|
1638
|
+
const extractedVariables = extractVariablesFromContent(content);
|
|
1639
|
+
const md = tiptapToMarkdown(content);
|
|
1640
|
+
setFields(extractedFields);
|
|
1641
|
+
setVariables(extractedVariables);
|
|
1642
|
+
setMarkdown(md);
|
|
1643
|
+
onChangeRef.current?.({ markdown: md, content, fields: extractedFields, variables: extractedVariables });
|
|
1644
|
+
}, 150);
|
|
1645
|
+
}
|
|
1646
|
+
});
|
|
1647
|
+
useEffect(() => {
|
|
1648
|
+
if (!editor) return;
|
|
1649
|
+
const content = editor.getJSON();
|
|
1650
|
+
const extractedFields = extractFieldsFromContent(content);
|
|
1651
|
+
const extractedVariables = extractVariablesFromContent(content);
|
|
1652
|
+
const md = tiptapToMarkdown(content);
|
|
1653
|
+
setFields(extractedFields);
|
|
1654
|
+
setVariables(extractedVariables);
|
|
1655
|
+
setMarkdown(md);
|
|
1656
|
+
}, [editor]);
|
|
1657
|
+
useEffect(() => {
|
|
1658
|
+
return () => clearTimeout(debounceRef.current);
|
|
1659
|
+
}, []);
|
|
1660
|
+
const insertField = useCallback(
|
|
1661
|
+
(type, overrides) => {
|
|
1662
|
+
if (!editor) return;
|
|
1663
|
+
const attrs = createFieldAttrs(type, {
|
|
1664
|
+
fieldType: type,
|
|
1665
|
+
...overrides
|
|
1666
|
+
});
|
|
1667
|
+
editor.commands.insertField(attrs);
|
|
1668
|
+
},
|
|
1669
|
+
[editor]
|
|
1670
|
+
);
|
|
1671
|
+
const updateField = useCallback(
|
|
1672
|
+
(fieldId, attrs) => {
|
|
1673
|
+
if (!editor) return;
|
|
1674
|
+
editor.commands.updateField(fieldId, attrs);
|
|
1675
|
+
},
|
|
1676
|
+
[editor]
|
|
1677
|
+
);
|
|
1678
|
+
const deleteField = useCallback(
|
|
1679
|
+
(fieldId) => {
|
|
1680
|
+
if (!editor) return;
|
|
1681
|
+
editor.commands.removeField(fieldId);
|
|
1682
|
+
},
|
|
1683
|
+
[editor]
|
|
1684
|
+
);
|
|
1685
|
+
const insertVariable = useCallback(
|
|
1686
|
+
(attrs) => {
|
|
1687
|
+
if (!editor) return;
|
|
1688
|
+
editor.commands.insertVariable(attrs);
|
|
1689
|
+
},
|
|
1690
|
+
[editor]
|
|
1691
|
+
);
|
|
1692
|
+
const updateVariable = useCallback(
|
|
1693
|
+
(varName, attrs) => {
|
|
1694
|
+
if (!editor) return;
|
|
1695
|
+
editor.commands.updateVariable(varName, attrs);
|
|
1696
|
+
},
|
|
1697
|
+
[editor]
|
|
1698
|
+
);
|
|
1699
|
+
const deleteVariable = useCallback(
|
|
1700
|
+
(varName) => {
|
|
1701
|
+
if (!editor) return;
|
|
1702
|
+
editor.commands.removeVariable(varName);
|
|
1703
|
+
},
|
|
1704
|
+
[editor]
|
|
1705
|
+
);
|
|
1706
|
+
const generatePdf = useCallback(async () => {
|
|
1707
|
+
if (!editor) return;
|
|
1708
|
+
setIsGenerating(true);
|
|
1709
|
+
setError(null);
|
|
1710
|
+
setFieldWarnings([]);
|
|
1711
|
+
try {
|
|
1712
|
+
const content = editor.getJSON();
|
|
1713
|
+
setFields(extractFieldsFromContent(content));
|
|
1714
|
+
setVariables(extractVariablesFromContent(content));
|
|
1715
|
+
const result = await generatePdfFromContent(content);
|
|
1716
|
+
setPdfBytes(result.pdfBytes);
|
|
1717
|
+
setFieldPositions(result.fieldPositions);
|
|
1718
|
+
if (result.fieldWarnings) {
|
|
1719
|
+
setFieldWarnings(result.fieldWarnings);
|
|
1720
|
+
}
|
|
1721
|
+
const pages = await pdfToImages(result.pdfBytes);
|
|
1722
|
+
setPdfPages(pages);
|
|
1723
|
+
} catch (err) {
|
|
1724
|
+
console.error("Failed to generate PDF:", err);
|
|
1725
|
+
setError(formatError("PDF generation failed", err));
|
|
1726
|
+
} finally {
|
|
1727
|
+
setIsGenerating(false);
|
|
1728
|
+
}
|
|
1729
|
+
}, [editor]);
|
|
1730
|
+
const generateWithVariables = useCallback(
|
|
1731
|
+
async (values) => {
|
|
1732
|
+
if (!editor) throw new Error("Editor is not initialized yet \u2014 wait for the editor to load before generating");
|
|
1733
|
+
const content = editor.getJSON();
|
|
1734
|
+
const replacedContent = replaceVariablesInContent(content, values);
|
|
1735
|
+
const result = await generatePdfFromContent(replacedContent);
|
|
1736
|
+
const pages = await pdfToImages(result.pdfBytes);
|
|
1737
|
+
return { pdfBytes: result.pdfBytes, pdfPages: pages };
|
|
1738
|
+
},
|
|
1739
|
+
[editor]
|
|
1740
|
+
);
|
|
1741
|
+
const PDF_PAGE_HEIGHT = 841.89;
|
|
1742
|
+
const positionedFields = fields.map((f) => {
|
|
1743
|
+
const pos = fieldPositions.get(f.fieldId);
|
|
1744
|
+
return {
|
|
1745
|
+
id: f.name,
|
|
1746
|
+
fieldId: f.fieldId,
|
|
1747
|
+
type: f.type,
|
|
1748
|
+
name: f.name,
|
|
1749
|
+
label: f.label,
|
|
1750
|
+
position: pos || { x: 0, y: 0, width: 0, height: 0, page: 1 },
|
|
1751
|
+
required: f.required,
|
|
1752
|
+
options: f.options,
|
|
1753
|
+
placeholder: f.placeholder,
|
|
1754
|
+
fontSize: f.fontSize,
|
|
1755
|
+
defaultValue: f.defaultValue,
|
|
1756
|
+
multiline: f.multiline,
|
|
1757
|
+
maxLength: f.maxLength,
|
|
1758
|
+
acknowledgements: f.acknowledgements
|
|
1759
|
+
};
|
|
1760
|
+
});
|
|
1761
|
+
const exportDocument = useCallback(async () => {
|
|
1762
|
+
if (!editor) return null;
|
|
1763
|
+
setError(null);
|
|
1764
|
+
setFieldWarnings([]);
|
|
1765
|
+
try {
|
|
1766
|
+
const content = editor.getJSON();
|
|
1767
|
+
const exportFields = extractFieldsFromContent(content);
|
|
1768
|
+
const result = await generatePdfFromContent(content, {
|
|
1769
|
+
embedFormFields: true,
|
|
1770
|
+
fields: exportFields
|
|
1771
|
+
});
|
|
1772
|
+
if (result.fieldWarnings) {
|
|
1773
|
+
setFieldWarnings(result.fieldWarnings);
|
|
1774
|
+
}
|
|
1775
|
+
const blob = new Blob([result.pdfBytes], { type: "application/pdf" });
|
|
1776
|
+
return {
|
|
1777
|
+
pdfBlob: blob,
|
|
1778
|
+
fields: exportFields.map((f) => {
|
|
1779
|
+
const pos = result.fieldPositions.get(f.fieldId);
|
|
1780
|
+
const exportPos = pos ? { ...pos, y: PDF_PAGE_HEIGHT - pos.y - pos.height } : { x: 0, y: 0, width: 0, height: 0, page: 1 };
|
|
1781
|
+
return {
|
|
1782
|
+
id: f.name,
|
|
1783
|
+
fieldId: f.fieldId,
|
|
1784
|
+
type: f.type,
|
|
1785
|
+
name: f.name,
|
|
1786
|
+
label: f.label,
|
|
1787
|
+
position: exportPos,
|
|
1788
|
+
required: f.required,
|
|
1789
|
+
options: f.options,
|
|
1790
|
+
placeholder: f.placeholder,
|
|
1791
|
+
fontSize: f.fontSize,
|
|
1792
|
+
defaultValue: f.defaultValue,
|
|
1793
|
+
multiline: f.multiline,
|
|
1794
|
+
maxLength: f.maxLength,
|
|
1795
|
+
acknowledgements: f.acknowledgements
|
|
1796
|
+
};
|
|
1797
|
+
}),
|
|
1798
|
+
markdown
|
|
1799
|
+
};
|
|
1800
|
+
} catch (err) {
|
|
1801
|
+
console.error("Document export failed:", err);
|
|
1802
|
+
setError(formatError("Document export failed", err));
|
|
1803
|
+
return null;
|
|
1804
|
+
}
|
|
1805
|
+
}, [editor, markdown]);
|
|
1806
|
+
const exportMarkdown = useCallback(() => {
|
|
1807
|
+
if (!editor) return "";
|
|
1808
|
+
return tiptapToMarkdown(editor.getJSON());
|
|
1809
|
+
}, [editor]);
|
|
1810
|
+
const getDocument = useCallback(
|
|
1811
|
+
(title) => ({
|
|
1812
|
+
title,
|
|
1813
|
+
markdown,
|
|
1814
|
+
editorContent: editor?.getJSON() || { type: "doc", content: [] },
|
|
1815
|
+
fields,
|
|
1816
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1817
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1818
|
+
}),
|
|
1819
|
+
[editor, markdown, fields]
|
|
1820
|
+
);
|
|
1821
|
+
const loadDocument = useCallback(
|
|
1822
|
+
(doc) => {
|
|
1823
|
+
if (!editor) return;
|
|
1824
|
+
if (doc.editorContent) {
|
|
1825
|
+
editor.commands.setContent(doc.editorContent);
|
|
1826
|
+
} else if (doc.markdown) {
|
|
1827
|
+
const content = markdownToTiptap(doc.markdown);
|
|
1828
|
+
editor.commands.setContent(content);
|
|
1829
|
+
}
|
|
1830
|
+
},
|
|
1831
|
+
[editor]
|
|
1832
|
+
);
|
|
1833
|
+
return {
|
|
1834
|
+
editor,
|
|
1835
|
+
fields,
|
|
1836
|
+
variables,
|
|
1837
|
+
fieldPositions,
|
|
1838
|
+
positionedFields,
|
|
1839
|
+
markdown,
|
|
1840
|
+
pdfBytes,
|
|
1841
|
+
pdfPages,
|
|
1842
|
+
isGenerating,
|
|
1843
|
+
error,
|
|
1844
|
+
fieldWarnings,
|
|
1845
|
+
insertField,
|
|
1846
|
+
updateField,
|
|
1847
|
+
deleteField,
|
|
1848
|
+
insertVariable,
|
|
1849
|
+
updateVariable,
|
|
1850
|
+
deleteVariable,
|
|
1851
|
+
generatePdf,
|
|
1852
|
+
generateWithVariables,
|
|
1853
|
+
exportDocument,
|
|
1854
|
+
exportMarkdown,
|
|
1855
|
+
getDocument,
|
|
1856
|
+
loadDocument
|
|
1857
|
+
};
|
|
1858
|
+
}
|
|
1859
|
+
var buttonVariants = cva(
|
|
1860
|
+
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
|
|
1861
|
+
{
|
|
1862
|
+
variants: {
|
|
1863
|
+
variant: {
|
|
1864
|
+
default: "bg-primary text-primary-foreground hover:bg-primary/90",
|
|
1865
|
+
destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
|
|
1866
|
+
outline: "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
|
|
1867
|
+
secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
|
1868
|
+
ghost: "hover:bg-accent hover:text-accent-foreground",
|
|
1869
|
+
link: "text-primary underline-offset-4 hover:underline"
|
|
1870
|
+
},
|
|
1871
|
+
size: {
|
|
1872
|
+
default: "h-10 px-4 py-2",
|
|
1873
|
+
sm: "h-9 rounded-md px-3",
|
|
1874
|
+
lg: "h-11 md:h-12 rounded-md px-8",
|
|
1875
|
+
icon: "h-10 w-10"
|
|
1876
|
+
}
|
|
1877
|
+
},
|
|
1878
|
+
defaultVariants: {
|
|
1879
|
+
variant: "default",
|
|
1880
|
+
size: "default"
|
|
1881
|
+
}
|
|
1882
|
+
}
|
|
1883
|
+
);
|
|
1884
|
+
var Button = React12.forwardRef(
|
|
1885
|
+
({ className, variant, size, asChild = false, ...props }, ref) => {
|
|
1886
|
+
const Comp = asChild ? Slot : "button";
|
|
1887
|
+
return /* @__PURE__ */ jsx(
|
|
1888
|
+
Comp,
|
|
1889
|
+
{
|
|
1890
|
+
className: cn(buttonVariants({ variant, size }), className),
|
|
1891
|
+
ref,
|
|
1892
|
+
...props
|
|
1893
|
+
}
|
|
1894
|
+
);
|
|
1895
|
+
}
|
|
1896
|
+
);
|
|
1897
|
+
Button.displayName = "Button";
|
|
1898
|
+
var TooltipProvider = ({ children, ...props }) => /* @__PURE__ */ jsx(TooltipPrimitive.Provider, { delayDuration: 0, ...props, children });
|
|
1899
|
+
var Tooltip = TooltipPrimitive.Root;
|
|
1900
|
+
var TooltipTrigger = TooltipPrimitive.Trigger;
|
|
1901
|
+
var TooltipContent = React12.forwardRef(({ className, sideOffset = 4, children, ...props }, ref) => /* @__PURE__ */ jsx(TooltipPrimitive.Portal, { children: /* @__PURE__ */ jsx(
|
|
1902
|
+
TooltipPrimitive.Content,
|
|
1903
|
+
{
|
|
1904
|
+
ref,
|
|
1905
|
+
sideOffset,
|
|
1906
|
+
className: "signiphi-pdf-compose",
|
|
1907
|
+
...props,
|
|
1908
|
+
children: /* @__PURE__ */ jsx("div", { className: cn(
|
|
1909
|
+
"z-50 overflow-hidden rounded-md border border-border bg-popover px-3 py-1.5 text-sm text-popover-foreground shadow-md",
|
|
1910
|
+
className
|
|
1911
|
+
), children })
|
|
1912
|
+
}
|
|
1913
|
+
) }));
|
|
1914
|
+
TooltipContent.displayName = TooltipPrimitive.Content.displayName;
|
|
1915
|
+
function ToolbarButton({ icon: Icon, label, isActive, disabled, onClick }) {
|
|
1916
|
+
return /* @__PURE__ */ jsxs(Tooltip, { children: [
|
|
1917
|
+
/* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
|
|
1918
|
+
Button,
|
|
1919
|
+
{
|
|
1920
|
+
variant: "ghost",
|
|
1921
|
+
size: "icon",
|
|
1922
|
+
className: cn(
|
|
1923
|
+
"h-8 w-8",
|
|
1924
|
+
isActive && "bg-accent text-accent-foreground"
|
|
1925
|
+
),
|
|
1926
|
+
disabled,
|
|
1927
|
+
onClick,
|
|
1928
|
+
children: /* @__PURE__ */ jsx(Icon, { size: 16 })
|
|
1929
|
+
}
|
|
1930
|
+
) }),
|
|
1931
|
+
/* @__PURE__ */ jsx(TooltipContent, { side: "bottom", children: label })
|
|
1932
|
+
] });
|
|
1933
|
+
}
|
|
1934
|
+
function ToolbarSeparator() {
|
|
1935
|
+
return /* @__PURE__ */ jsx("div", { className: "mx-1 h-6 w-px bg-border" });
|
|
1936
|
+
}
|
|
1937
|
+
function isMarkActiveOnSelection(editor, markName) {
|
|
1938
|
+
if (editor.isActive(markName)) return true;
|
|
1939
|
+
const { selection } = editor.state;
|
|
1940
|
+
if (selection instanceof NodeSelection && selection.node.type.name === "variableNode") {
|
|
1941
|
+
const markType = editor.schema.marks[markName];
|
|
1942
|
+
return markType ? !!markType.isInSet(selection.node.marks) : false;
|
|
1943
|
+
}
|
|
1944
|
+
return false;
|
|
1945
|
+
}
|
|
1946
|
+
function EditorToolbar({ editor, insertFieldButton, insertVariableButton, onCollapse }) {
|
|
1947
|
+
const toggle = useCallback(
|
|
1948
|
+
(command) => {
|
|
1949
|
+
if (!editor) return;
|
|
1950
|
+
command();
|
|
1951
|
+
editor.commands.focus();
|
|
1952
|
+
},
|
|
1953
|
+
[editor]
|
|
1954
|
+
);
|
|
1955
|
+
if (!editor) return null;
|
|
1956
|
+
return /* @__PURE__ */ jsx(TooltipProvider, { children: /* @__PURE__ */ jsxs("div", { className: "flex items-center flex-wrap gap-0.5 px-2 py-1.5 bg-muted/30", children: [
|
|
1957
|
+
onCollapse && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1958
|
+
/* @__PURE__ */ jsx(
|
|
1959
|
+
ToolbarButton,
|
|
1960
|
+
{
|
|
1961
|
+
icon: PanelLeftClose,
|
|
1962
|
+
label: "Collapse",
|
|
1963
|
+
onClick: onCollapse
|
|
1964
|
+
}
|
|
1965
|
+
),
|
|
1966
|
+
/* @__PURE__ */ jsx(ToolbarSeparator, {})
|
|
1967
|
+
] }),
|
|
1968
|
+
/* @__PURE__ */ jsx(
|
|
1969
|
+
ToolbarButton,
|
|
1970
|
+
{
|
|
1971
|
+
icon: Undo,
|
|
1972
|
+
label: "Undo",
|
|
1973
|
+
disabled: !editor.can().undo(),
|
|
1974
|
+
onClick: () => toggle(() => editor.chain().focus().undo().run())
|
|
1975
|
+
}
|
|
1976
|
+
),
|
|
1977
|
+
/* @__PURE__ */ jsx(
|
|
1978
|
+
ToolbarButton,
|
|
1979
|
+
{
|
|
1980
|
+
icon: Redo,
|
|
1981
|
+
label: "Redo",
|
|
1982
|
+
disabled: !editor.can().redo(),
|
|
1983
|
+
onClick: () => toggle(() => editor.chain().focus().redo().run())
|
|
1984
|
+
}
|
|
1985
|
+
),
|
|
1986
|
+
/* @__PURE__ */ jsx(ToolbarSeparator, {}),
|
|
1987
|
+
/* @__PURE__ */ jsx(
|
|
1988
|
+
ToolbarButton,
|
|
1989
|
+
{
|
|
1990
|
+
icon: Bold,
|
|
1991
|
+
label: "Bold",
|
|
1992
|
+
isActive: isMarkActiveOnSelection(editor, "bold"),
|
|
1993
|
+
onClick: () => toggle(() => editor.chain().focus().toggleBold().run())
|
|
1994
|
+
}
|
|
1995
|
+
),
|
|
1996
|
+
/* @__PURE__ */ jsx(
|
|
1997
|
+
ToolbarButton,
|
|
1998
|
+
{
|
|
1999
|
+
icon: Italic,
|
|
2000
|
+
label: "Italic",
|
|
2001
|
+
isActive: isMarkActiveOnSelection(editor, "italic"),
|
|
2002
|
+
onClick: () => toggle(() => editor.chain().focus().toggleItalic().run())
|
|
2003
|
+
}
|
|
2004
|
+
),
|
|
2005
|
+
/* @__PURE__ */ jsx(
|
|
2006
|
+
ToolbarButton,
|
|
2007
|
+
{
|
|
2008
|
+
icon: Underline$1,
|
|
2009
|
+
label: "Underline",
|
|
2010
|
+
isActive: isMarkActiveOnSelection(editor, "underline"),
|
|
2011
|
+
onClick: () => toggle(() => editor.chain().focus().toggleUnderline().run())
|
|
2012
|
+
}
|
|
2013
|
+
),
|
|
2014
|
+
/* @__PURE__ */ jsx(
|
|
2015
|
+
ToolbarButton,
|
|
2016
|
+
{
|
|
2017
|
+
icon: Code,
|
|
2018
|
+
label: "Code",
|
|
2019
|
+
isActive: editor.isActive("code"),
|
|
2020
|
+
onClick: () => toggle(() => editor.chain().focus().toggleCode().run())
|
|
2021
|
+
}
|
|
2022
|
+
),
|
|
2023
|
+
/* @__PURE__ */ jsx(ToolbarSeparator, {}),
|
|
2024
|
+
/* @__PURE__ */ jsx(
|
|
2025
|
+
ToolbarButton,
|
|
2026
|
+
{
|
|
2027
|
+
icon: Heading1,
|
|
2028
|
+
label: "Heading 1",
|
|
2029
|
+
isActive: editor.isActive("heading", { level: 1 }),
|
|
2030
|
+
onClick: () => toggle(() => editor.chain().focus().toggleHeading({ level: 1 }).run())
|
|
2031
|
+
}
|
|
2032
|
+
),
|
|
2033
|
+
/* @__PURE__ */ jsx(
|
|
2034
|
+
ToolbarButton,
|
|
2035
|
+
{
|
|
2036
|
+
icon: Heading2,
|
|
2037
|
+
label: "Heading 2",
|
|
2038
|
+
isActive: editor.isActive("heading", { level: 2 }),
|
|
2039
|
+
onClick: () => toggle(() => editor.chain().focus().toggleHeading({ level: 2 }).run())
|
|
2040
|
+
}
|
|
2041
|
+
),
|
|
2042
|
+
/* @__PURE__ */ jsx(
|
|
2043
|
+
ToolbarButton,
|
|
2044
|
+
{
|
|
2045
|
+
icon: Heading3,
|
|
2046
|
+
label: "Heading 3",
|
|
2047
|
+
isActive: editor.isActive("heading", { level: 3 }),
|
|
2048
|
+
onClick: () => toggle(() => editor.chain().focus().toggleHeading({ level: 3 }).run())
|
|
2049
|
+
}
|
|
2050
|
+
),
|
|
2051
|
+
/* @__PURE__ */ jsx(ToolbarSeparator, {}),
|
|
2052
|
+
/* @__PURE__ */ jsx(
|
|
2053
|
+
ToolbarButton,
|
|
2054
|
+
{
|
|
2055
|
+
icon: List,
|
|
2056
|
+
label: "Bullet List",
|
|
2057
|
+
isActive: editor.isActive("bulletList"),
|
|
2058
|
+
onClick: () => toggle(() => editor.chain().focus().toggleBulletList().run())
|
|
2059
|
+
}
|
|
2060
|
+
),
|
|
2061
|
+
/* @__PURE__ */ jsx(
|
|
2062
|
+
ToolbarButton,
|
|
2063
|
+
{
|
|
2064
|
+
icon: ListOrdered,
|
|
2065
|
+
label: "Ordered List",
|
|
2066
|
+
isActive: editor.isActive("orderedList"),
|
|
2067
|
+
onClick: () => toggle(() => editor.chain().focus().toggleOrderedList().run())
|
|
2068
|
+
}
|
|
2069
|
+
),
|
|
2070
|
+
/* @__PURE__ */ jsx(ToolbarSeparator, {}),
|
|
2071
|
+
/* @__PURE__ */ jsx(
|
|
2072
|
+
ToolbarButton,
|
|
2073
|
+
{
|
|
2074
|
+
icon: Quote,
|
|
2075
|
+
label: "Blockquote",
|
|
2076
|
+
isActive: editor.isActive("blockquote"),
|
|
2077
|
+
onClick: () => toggle(() => editor.chain().focus().toggleBlockquote().run())
|
|
2078
|
+
}
|
|
2079
|
+
),
|
|
2080
|
+
/* @__PURE__ */ jsx(
|
|
2081
|
+
ToolbarButton,
|
|
2082
|
+
{
|
|
2083
|
+
icon: Minus,
|
|
2084
|
+
label: "Horizontal Rule",
|
|
2085
|
+
onClick: () => toggle(() => editor.chain().focus().setHorizontalRule().run())
|
|
2086
|
+
}
|
|
2087
|
+
),
|
|
2088
|
+
/* @__PURE__ */ jsx(ToolbarSeparator, {}),
|
|
2089
|
+
/* @__PURE__ */ jsx(
|
|
2090
|
+
ToolbarButton,
|
|
2091
|
+
{
|
|
2092
|
+
icon: AlignLeft,
|
|
2093
|
+
label: "Align Left",
|
|
2094
|
+
isActive: editor.isActive({ textAlign: "left" }),
|
|
2095
|
+
onClick: () => toggle(() => editor.chain().focus().setTextAlign("left").run())
|
|
2096
|
+
}
|
|
2097
|
+
),
|
|
2098
|
+
/* @__PURE__ */ jsx(
|
|
2099
|
+
ToolbarButton,
|
|
2100
|
+
{
|
|
2101
|
+
icon: AlignCenter,
|
|
2102
|
+
label: "Align Center",
|
|
2103
|
+
isActive: editor.isActive({ textAlign: "center" }),
|
|
2104
|
+
onClick: () => toggle(() => editor.chain().focus().setTextAlign("center").run())
|
|
2105
|
+
}
|
|
2106
|
+
),
|
|
2107
|
+
/* @__PURE__ */ jsx(
|
|
2108
|
+
ToolbarButton,
|
|
2109
|
+
{
|
|
2110
|
+
icon: AlignRight,
|
|
2111
|
+
label: "Align Right",
|
|
2112
|
+
isActive: editor.isActive({ textAlign: "right" }),
|
|
2113
|
+
onClick: () => toggle(() => editor.chain().focus().setTextAlign("right").run())
|
|
2114
|
+
}
|
|
2115
|
+
),
|
|
2116
|
+
/* @__PURE__ */ jsx(ToolbarSeparator, {}),
|
|
2117
|
+
insertFieldButton,
|
|
2118
|
+
insertVariableButton
|
|
2119
|
+
] }) });
|
|
2120
|
+
}
|
|
2121
|
+
var Popover = PopoverPrimitive.Root;
|
|
2122
|
+
var PopoverTrigger = PopoverPrimitive.Trigger;
|
|
2123
|
+
var PopoverAnchor = PopoverPrimitive.Anchor;
|
|
2124
|
+
var PopoverContent = React12.forwardRef(({ className, align = "center", sideOffset = 4, children, ...props }, ref) => /* @__PURE__ */ jsx(PopoverPrimitive.Portal, { children: /* @__PURE__ */ jsx(
|
|
2125
|
+
PopoverPrimitive.Content,
|
|
2126
|
+
{
|
|
2127
|
+
ref,
|
|
2128
|
+
align,
|
|
2129
|
+
sideOffset,
|
|
2130
|
+
className: "signiphi-pdf-compose",
|
|
2131
|
+
...props,
|
|
2132
|
+
children: /* @__PURE__ */ jsx("div", { className: cn(
|
|
2133
|
+
"z-50 w-72 rounded-md border border-border bg-popover p-4 text-popover-foreground shadow-md outline-none",
|
|
2134
|
+
className
|
|
2135
|
+
), children })
|
|
2136
|
+
}
|
|
2137
|
+
) }));
|
|
2138
|
+
PopoverContent.displayName = PopoverPrimitive.Content.displayName;
|
|
2139
|
+
var Input = React12.forwardRef(
|
|
2140
|
+
({ className, type, ...props }, ref) => {
|
|
2141
|
+
return /* @__PURE__ */ jsx(
|
|
2142
|
+
"input",
|
|
2143
|
+
{
|
|
2144
|
+
type,
|
|
2145
|
+
className: cn(
|
|
2146
|
+
"flex h-10 w-full rounded-md border-2 border-input bg-background px-3 py-2 text-sm file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground transition-all duration-200 focus:outline-none focus:border-primary focus:ring-0 focus:shadow-[0_0_0_3px_rgba(0,153,205,0.15)] focus-visible:outline-none focus-visible:border-primary focus-visible:ring-0 focus-visible:shadow-[0_0_0_3px_rgba(0,153,205,0.15)] disabled:cursor-not-allowed disabled:opacity-50",
|
|
2147
|
+
className
|
|
2148
|
+
),
|
|
2149
|
+
ref,
|
|
2150
|
+
...props
|
|
2151
|
+
}
|
|
2152
|
+
);
|
|
2153
|
+
}
|
|
2154
|
+
);
|
|
2155
|
+
Input.displayName = "Input";
|
|
2156
|
+
var labelVariants = cva(
|
|
2157
|
+
"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
|
2158
|
+
);
|
|
2159
|
+
var Label = React12.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(
|
|
2160
|
+
LabelPrimitive.Root,
|
|
2161
|
+
{
|
|
2162
|
+
ref,
|
|
2163
|
+
className: cn(labelVariants(), className),
|
|
2164
|
+
...props
|
|
2165
|
+
}
|
|
2166
|
+
));
|
|
2167
|
+
Label.displayName = LabelPrimitive.Root.displayName;
|
|
2168
|
+
var FIELD_TYPES = [
|
|
2169
|
+
{ type: "text" /* TEXT */, icon: Type, label: "Text", color: "bg-blue-100 text-blue-700 border-blue-300 hover:bg-blue-200" },
|
|
2170
|
+
{ type: "signature" /* SIGNATURE */, icon: PenTool, label: "Signature", color: "bg-purple-100 text-purple-700 border-purple-300 hover:bg-purple-200" },
|
|
2171
|
+
{ type: "initials" /* INITIALS */, icon: Hash, label: "Initials", color: "bg-green-100 text-green-700 border-green-300 hover:bg-green-200" },
|
|
2172
|
+
{ type: "date" /* DATE */, icon: Calendar, label: "Date", color: "bg-amber-100 text-amber-700 border-amber-300 hover:bg-amber-200" },
|
|
2173
|
+
{ type: "checkbox" /* CHECKBOX */, icon: CheckSquare, label: "Checkbox", color: "bg-teal-100 text-teal-700 border-teal-300 hover:bg-teal-200" },
|
|
2174
|
+
{ type: "radio" /* RADIO */, icon: Circle, label: "Radio", color: "bg-pink-100 text-pink-700 border-pink-300 hover:bg-pink-200" },
|
|
2175
|
+
{ type: "dropdown" /* DROPDOWN */, icon: ChevronDown, label: "Dropdown", color: "bg-indigo-100 text-indigo-700 border-indigo-300 hover:bg-indigo-200" },
|
|
2176
|
+
{ type: "text_label" /* TEXT_LABEL */, icon: FileText, label: "Label", color: "bg-gray-100 text-gray-700 border-gray-300 hover:bg-gray-200" }
|
|
2177
|
+
];
|
|
2178
|
+
function FieldInsertPopover({
|
|
2179
|
+
open,
|
|
2180
|
+
onOpenChange,
|
|
2181
|
+
onInsert,
|
|
2182
|
+
children
|
|
2183
|
+
}) {
|
|
2184
|
+
const [step, setStep] = useState("type");
|
|
2185
|
+
const [selectedType, setSelectedType] = useState(null);
|
|
2186
|
+
const [label, setLabel] = useState("");
|
|
2187
|
+
const [required, setRequired] = useState(false);
|
|
2188
|
+
const [options, setOptions] = useState("");
|
|
2189
|
+
const [placeholder, setPlaceholder] = useState("");
|
|
2190
|
+
const [defaultValue, setDefaultValue] = useState("");
|
|
2191
|
+
const [advancedOpen, setAdvancedOpen] = useState(false);
|
|
2192
|
+
const [fontSize, setFontSize] = useState("");
|
|
2193
|
+
const [multiline, setMultiline] = useState(false);
|
|
2194
|
+
const [maxLength, setMaxLength] = useState("");
|
|
2195
|
+
const [acknowledgements, setAcknowledgements] = useState([]);
|
|
2196
|
+
const [isAddingAck, setIsAddingAck] = useState(false);
|
|
2197
|
+
const [ackTitle, setAckTitle] = useState("");
|
|
2198
|
+
const [ackDescription, setAckDescription] = useState("");
|
|
2199
|
+
const reset = useCallback(() => {
|
|
2200
|
+
setStep("type");
|
|
2201
|
+
setSelectedType(null);
|
|
2202
|
+
setLabel("");
|
|
2203
|
+
setRequired(false);
|
|
2204
|
+
setOptions("");
|
|
2205
|
+
setPlaceholder("");
|
|
2206
|
+
setDefaultValue("");
|
|
2207
|
+
setAdvancedOpen(false);
|
|
2208
|
+
setFontSize("");
|
|
2209
|
+
setMultiline(false);
|
|
2210
|
+
setMaxLength("");
|
|
2211
|
+
setAcknowledgements([]);
|
|
2212
|
+
setIsAddingAck(false);
|
|
2213
|
+
setAckTitle("");
|
|
2214
|
+
setAckDescription("");
|
|
2215
|
+
}, []);
|
|
2216
|
+
const handleOpenChange = useCallback(
|
|
2217
|
+
(nextOpen) => {
|
|
2218
|
+
if (!nextOpen) reset();
|
|
2219
|
+
onOpenChange(nextOpen);
|
|
2220
|
+
},
|
|
2221
|
+
[onOpenChange, reset]
|
|
2222
|
+
);
|
|
2223
|
+
const handleTypeSelect = useCallback((type) => {
|
|
2224
|
+
setSelectedType(type);
|
|
2225
|
+
setStep("configure");
|
|
2226
|
+
}, []);
|
|
2227
|
+
const handleInsert = useCallback(() => {
|
|
2228
|
+
if (!selectedType) return;
|
|
2229
|
+
const attrs = {};
|
|
2230
|
+
if (label) attrs.fieldLabel = label;
|
|
2231
|
+
attrs.required = required;
|
|
2232
|
+
if (options) attrs.options = options;
|
|
2233
|
+
if (placeholder) attrs.placeholder = placeholder;
|
|
2234
|
+
if (defaultValue) attrs.defaultValue = defaultValue;
|
|
2235
|
+
if (fontSize) attrs.fontSize = parseInt(fontSize, 10) || null;
|
|
2236
|
+
if (multiline) attrs.multiline = multiline;
|
|
2237
|
+
if (maxLength) attrs.maxLength = parseInt(maxLength, 10) || null;
|
|
2238
|
+
if (acknowledgements.length > 0) {
|
|
2239
|
+
attrs.acknowledgements = JSON.stringify(acknowledgements);
|
|
2240
|
+
}
|
|
2241
|
+
onInsert(selectedType, attrs);
|
|
2242
|
+
handleOpenChange(false);
|
|
2243
|
+
}, [selectedType, label, required, options, placeholder, defaultValue, fontSize, multiline, maxLength, acknowledgements, onInsert, handleOpenChange]);
|
|
2244
|
+
const addAcknowledgement = useCallback(() => {
|
|
2245
|
+
if (!ackTitle.trim()) return;
|
|
2246
|
+
const newAck = {
|
|
2247
|
+
id: v4(),
|
|
2248
|
+
title: ackTitle.trim(),
|
|
2249
|
+
description: ackDescription.trim()
|
|
2250
|
+
};
|
|
2251
|
+
setAcknowledgements((prev) => {
|
|
2252
|
+
const updated = [...prev, newAck];
|
|
2253
|
+
if (updated.length === 1) setRequired(true);
|
|
2254
|
+
return updated;
|
|
2255
|
+
});
|
|
2256
|
+
setAckTitle("");
|
|
2257
|
+
setAckDescription("");
|
|
2258
|
+
setIsAddingAck(false);
|
|
2259
|
+
}, [ackTitle, ackDescription]);
|
|
2260
|
+
const removeAcknowledgement = useCallback((id) => {
|
|
2261
|
+
setAcknowledgements((prev) => prev.filter((a) => a.id !== id));
|
|
2262
|
+
}, []);
|
|
2263
|
+
const showLabel = selectedType !== "text_label" /* TEXT_LABEL */;
|
|
2264
|
+
const showPlaceholder = selectedType === "text" /* TEXT */ || selectedType === "date" /* DATE */;
|
|
2265
|
+
const showOptions = selectedType === "dropdown" /* DROPDOWN */ || selectedType === "radio" /* RADIO */;
|
|
2266
|
+
const showDefaultValue = selectedType === "text" /* TEXT */ || selectedType === "text_label" /* TEXT_LABEL */;
|
|
2267
|
+
const showFontSize = selectedType === "text" /* TEXT */ || selectedType === "text_label" /* TEXT_LABEL */;
|
|
2268
|
+
const showMultiline = selectedType === "text" /* TEXT */;
|
|
2269
|
+
const showMaxLength = selectedType === "text" /* TEXT */;
|
|
2270
|
+
const showRequired = selectedType !== "text_label" /* TEXT_LABEL */;
|
|
2271
|
+
const showAcknowledgements = selectedType !== "text_label" /* TEXT_LABEL */;
|
|
2272
|
+
const hasAdvancedFields = showFontSize || showMultiline || showMaxLength || showAcknowledgements;
|
|
2273
|
+
return /* @__PURE__ */ jsxs(Popover, { open, onOpenChange: handleOpenChange, children: [
|
|
2274
|
+
/* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, children }),
|
|
2275
|
+
/* @__PURE__ */ jsxs(PopoverContent, { className: "w-80 p-0", align: "start", sideOffset: 8, children: [
|
|
2276
|
+
step === "type" && /* @__PURE__ */ jsxs("div", { className: "p-3", children: [
|
|
2277
|
+
/* @__PURE__ */ jsx("p", { className: "mb-2 text-sm font-medium text-foreground", children: "Select field type" }),
|
|
2278
|
+
/* @__PURE__ */ jsx("div", { className: "grid grid-cols-2 gap-2", children: FIELD_TYPES.map(({ type, icon: Icon, label: typeLabel, color }) => /* @__PURE__ */ jsxs(
|
|
2279
|
+
"button",
|
|
2280
|
+
{
|
|
2281
|
+
className: cn(
|
|
2282
|
+
"flex items-center gap-2 rounded-md border px-3 py-2 text-xs font-medium transition-colors",
|
|
2283
|
+
color
|
|
2284
|
+
),
|
|
2285
|
+
onClick: () => handleTypeSelect(type),
|
|
2286
|
+
children: [
|
|
2287
|
+
/* @__PURE__ */ jsx(Icon, { size: 14 }),
|
|
2288
|
+
typeLabel
|
|
2289
|
+
]
|
|
2290
|
+
},
|
|
2291
|
+
type
|
|
2292
|
+
)) })
|
|
2293
|
+
] }),
|
|
2294
|
+
step === "configure" && selectedType && /* @__PURE__ */ jsxs("div", { className: "p-3 space-y-3", children: [
|
|
2295
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
|
|
2296
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm font-medium text-foreground", children: "Configure field" }),
|
|
2297
|
+
/* @__PURE__ */ jsx(
|
|
2298
|
+
"button",
|
|
2299
|
+
{
|
|
2300
|
+
className: "text-xs text-muted-foreground hover:text-foreground",
|
|
2301
|
+
onClick: () => setStep("type"),
|
|
2302
|
+
children: "Back"
|
|
2303
|
+
}
|
|
2304
|
+
)
|
|
2305
|
+
] }),
|
|
2306
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
2307
|
+
showLabel && /* @__PURE__ */ jsxs("div", { children: [
|
|
2308
|
+
/* @__PURE__ */ jsx(Label, { htmlFor: "field-label", className: "text-xs", children: "Label" }),
|
|
2309
|
+
/* @__PURE__ */ jsx(
|
|
2310
|
+
Input,
|
|
2311
|
+
{
|
|
2312
|
+
id: "field-label",
|
|
2313
|
+
value: label,
|
|
2314
|
+
onChange: (e) => setLabel(e.target.value),
|
|
2315
|
+
placeholder: "e.g. Customer Signature",
|
|
2316
|
+
className: "h-8 text-xs"
|
|
2317
|
+
}
|
|
2318
|
+
)
|
|
2319
|
+
] }),
|
|
2320
|
+
showDefaultValue && /* @__PURE__ */ jsxs("div", { children: [
|
|
2321
|
+
/* @__PURE__ */ jsx(Label, { htmlFor: "field-default", className: "text-xs", children: selectedType === "text_label" /* TEXT_LABEL */ ? "Text Content" : "Default Value" }),
|
|
2322
|
+
/* @__PURE__ */ jsx(
|
|
2323
|
+
Input,
|
|
2324
|
+
{
|
|
2325
|
+
id: "field-default",
|
|
2326
|
+
value: defaultValue,
|
|
2327
|
+
onChange: (e) => setDefaultValue(e.target.value),
|
|
2328
|
+
placeholder: selectedType === "text_label" /* TEXT_LABEL */ ? "Text Label" : "Default value",
|
|
2329
|
+
className: "h-8 text-xs"
|
|
2330
|
+
}
|
|
2331
|
+
)
|
|
2332
|
+
] }),
|
|
2333
|
+
showPlaceholder && /* @__PURE__ */ jsxs("div", { children: [
|
|
2334
|
+
/* @__PURE__ */ jsx(Label, { htmlFor: "field-placeholder", className: "text-xs", children: "Placeholder" }),
|
|
2335
|
+
/* @__PURE__ */ jsx(
|
|
2336
|
+
Input,
|
|
2337
|
+
{
|
|
2338
|
+
id: "field-placeholder",
|
|
2339
|
+
value: placeholder,
|
|
2340
|
+
onChange: (e) => setPlaceholder(e.target.value),
|
|
2341
|
+
placeholder: "e.g. Enter your name",
|
|
2342
|
+
className: "h-8 text-xs"
|
|
2343
|
+
}
|
|
2344
|
+
)
|
|
2345
|
+
] }),
|
|
2346
|
+
showOptions && /* @__PURE__ */ jsxs("div", { children: [
|
|
2347
|
+
/* @__PURE__ */ jsx(Label, { htmlFor: "field-options", className: "text-xs", children: "Options (comma-separated)" }),
|
|
2348
|
+
/* @__PURE__ */ jsx(
|
|
2349
|
+
Input,
|
|
2350
|
+
{
|
|
2351
|
+
id: "field-options",
|
|
2352
|
+
value: options,
|
|
2353
|
+
onChange: (e) => setOptions(e.target.value),
|
|
2354
|
+
placeholder: "e.g. Option A, Option B, Option C",
|
|
2355
|
+
className: "h-8 text-xs"
|
|
2356
|
+
}
|
|
2357
|
+
)
|
|
2358
|
+
] }),
|
|
2359
|
+
showRequired && /* @__PURE__ */ jsxs("label", { className: "flex items-center gap-2 cursor-pointer", children: [
|
|
2360
|
+
/* @__PURE__ */ jsx(
|
|
2361
|
+
"input",
|
|
2362
|
+
{
|
|
2363
|
+
type: "checkbox",
|
|
2364
|
+
checked: required,
|
|
2365
|
+
onChange: (e) => setRequired(e.target.checked),
|
|
2366
|
+
className: "h-3.5 w-3.5 rounded border-border"
|
|
2367
|
+
}
|
|
2368
|
+
),
|
|
2369
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: "Required field" })
|
|
2370
|
+
] })
|
|
2371
|
+
] }),
|
|
2372
|
+
hasAdvancedFields && /* @__PURE__ */ jsxs("div", { className: "border-t border-border pt-2", children: [
|
|
2373
|
+
/* @__PURE__ */ jsxs(
|
|
2374
|
+
"button",
|
|
2375
|
+
{
|
|
2376
|
+
className: "flex items-center gap-1 text-xs font-medium text-muted-foreground hover:text-foreground w-full",
|
|
2377
|
+
onClick: () => setAdvancedOpen(!advancedOpen),
|
|
2378
|
+
children: [
|
|
2379
|
+
advancedOpen ? /* @__PURE__ */ jsx(ChevronDown, { size: 12 }) : /* @__PURE__ */ jsx(ChevronRight, { size: 12 }),
|
|
2380
|
+
"Advanced"
|
|
2381
|
+
]
|
|
2382
|
+
}
|
|
2383
|
+
),
|
|
2384
|
+
advancedOpen && /* @__PURE__ */ jsxs("div", { className: "mt-2 space-y-2", children: [
|
|
2385
|
+
showFontSize && /* @__PURE__ */ jsxs("div", { children: [
|
|
2386
|
+
/* @__PURE__ */ jsx(Label, { htmlFor: "field-fontsize", className: "text-xs", children: "Font Size (8-72)" }),
|
|
2387
|
+
/* @__PURE__ */ jsx(
|
|
2388
|
+
Input,
|
|
2389
|
+
{
|
|
2390
|
+
id: "field-fontsize",
|
|
2391
|
+
type: "number",
|
|
2392
|
+
min: "8",
|
|
2393
|
+
max: "72",
|
|
2394
|
+
value: fontSize,
|
|
2395
|
+
onChange: (e) => setFontSize(e.target.value),
|
|
2396
|
+
placeholder: "12",
|
|
2397
|
+
className: "h-8 text-xs"
|
|
2398
|
+
}
|
|
2399
|
+
)
|
|
2400
|
+
] }),
|
|
2401
|
+
showMultiline && /* @__PURE__ */ jsxs("label", { className: "flex items-center gap-2 cursor-pointer", children: [
|
|
2402
|
+
/* @__PURE__ */ jsx(
|
|
2403
|
+
"input",
|
|
2404
|
+
{
|
|
2405
|
+
type: "checkbox",
|
|
2406
|
+
checked: multiline,
|
|
2407
|
+
onChange: (e) => setMultiline(e.target.checked),
|
|
2408
|
+
className: "h-3.5 w-3.5 rounded border-border"
|
|
2409
|
+
}
|
|
2410
|
+
),
|
|
2411
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: "Multiline" })
|
|
2412
|
+
] }),
|
|
2413
|
+
showMaxLength && /* @__PURE__ */ jsxs("div", { children: [
|
|
2414
|
+
/* @__PURE__ */ jsx(Label, { htmlFor: "field-maxlength", className: "text-xs", children: "Max Length" }),
|
|
2415
|
+
/* @__PURE__ */ jsx(
|
|
2416
|
+
Input,
|
|
2417
|
+
{
|
|
2418
|
+
id: "field-maxlength",
|
|
2419
|
+
type: "number",
|
|
2420
|
+
min: "1",
|
|
2421
|
+
value: maxLength,
|
|
2422
|
+
onChange: (e) => setMaxLength(e.target.value),
|
|
2423
|
+
placeholder: "No limit",
|
|
2424
|
+
className: "h-8 text-xs"
|
|
2425
|
+
}
|
|
2426
|
+
)
|
|
2427
|
+
] }),
|
|
2428
|
+
showAcknowledgements && /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
|
|
2429
|
+
/* @__PURE__ */ jsx(Label, { className: "text-xs", children: "Acknowledgements" }),
|
|
2430
|
+
acknowledgements.length === 0 && !isAddingAck && /* @__PURE__ */ jsx("p", { className: "text-[10px] text-muted-foreground", children: "No acknowledgements added" }),
|
|
2431
|
+
acknowledgements.map((ack) => /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-1.5 rounded border border-border p-1.5", children: [
|
|
2432
|
+
/* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
|
|
2433
|
+
/* @__PURE__ */ jsx("p", { className: "text-[11px] font-medium text-foreground truncate", children: ack.title }),
|
|
2434
|
+
ack.description && /* @__PURE__ */ jsx("p", { className: "text-[10px] text-muted-foreground truncate", children: ack.description })
|
|
2435
|
+
] }),
|
|
2436
|
+
/* @__PURE__ */ jsx(
|
|
2437
|
+
"button",
|
|
2438
|
+
{
|
|
2439
|
+
className: "text-muted-foreground hover:text-destructive shrink-0 mt-0.5",
|
|
2440
|
+
onClick: () => removeAcknowledgement(ack.id),
|
|
2441
|
+
children: /* @__PURE__ */ jsx(X, { size: 12 })
|
|
2442
|
+
}
|
|
2443
|
+
)
|
|
2444
|
+
] }, ack.id)),
|
|
2445
|
+
isAddingAck ? /* @__PURE__ */ jsxs("div", { className: "space-y-1.5 rounded border border-border p-2", children: [
|
|
2446
|
+
/* @__PURE__ */ jsx(
|
|
2447
|
+
Input,
|
|
2448
|
+
{
|
|
2449
|
+
value: ackTitle,
|
|
2450
|
+
onChange: (e) => setAckTitle(e.target.value),
|
|
2451
|
+
placeholder: "Title (required)",
|
|
2452
|
+
className: "h-7 text-xs"
|
|
2453
|
+
}
|
|
2454
|
+
),
|
|
2455
|
+
/* @__PURE__ */ jsx(
|
|
2456
|
+
Input,
|
|
2457
|
+
{
|
|
2458
|
+
value: ackDescription,
|
|
2459
|
+
onChange: (e) => setAckDescription(e.target.value),
|
|
2460
|
+
placeholder: "Description (optional)",
|
|
2461
|
+
className: "h-7 text-xs"
|
|
2462
|
+
}
|
|
2463
|
+
),
|
|
2464
|
+
/* @__PURE__ */ jsxs("div", { className: "flex gap-1.5", children: [
|
|
2465
|
+
/* @__PURE__ */ jsx(
|
|
2466
|
+
Button,
|
|
2467
|
+
{
|
|
2468
|
+
size: "sm",
|
|
2469
|
+
className: "h-6 text-[10px] flex-1",
|
|
2470
|
+
onClick: addAcknowledgement,
|
|
2471
|
+
disabled: !ackTitle.trim(),
|
|
2472
|
+
children: "Add"
|
|
2473
|
+
}
|
|
2474
|
+
),
|
|
2475
|
+
/* @__PURE__ */ jsx(
|
|
2476
|
+
Button,
|
|
2477
|
+
{
|
|
2478
|
+
variant: "outline",
|
|
2479
|
+
size: "sm",
|
|
2480
|
+
className: "h-6 text-[10px]",
|
|
2481
|
+
onClick: () => {
|
|
2482
|
+
setIsAddingAck(false);
|
|
2483
|
+
setAckTitle("");
|
|
2484
|
+
setAckDescription("");
|
|
2485
|
+
},
|
|
2486
|
+
children: "Cancel"
|
|
2487
|
+
}
|
|
2488
|
+
)
|
|
2489
|
+
] })
|
|
2490
|
+
] }) : /* @__PURE__ */ jsxs(
|
|
2491
|
+
"button",
|
|
2492
|
+
{
|
|
2493
|
+
className: "flex items-center gap-1 text-[11px] text-primary hover:text-primary/80",
|
|
2494
|
+
onClick: () => setIsAddingAck(true),
|
|
2495
|
+
children: [
|
|
2496
|
+
/* @__PURE__ */ jsx(Plus, { size: 11 }),
|
|
2497
|
+
"Add acknowledgement"
|
|
2498
|
+
]
|
|
2499
|
+
}
|
|
2500
|
+
)
|
|
2501
|
+
] })
|
|
2502
|
+
] })
|
|
2503
|
+
] }),
|
|
2504
|
+
/* @__PURE__ */ jsxs("div", { className: "flex gap-2 pt-1", children: [
|
|
2505
|
+
/* @__PURE__ */ jsx(
|
|
2506
|
+
Button,
|
|
2507
|
+
{
|
|
2508
|
+
size: "sm",
|
|
2509
|
+
className: "h-8 flex-1 text-xs",
|
|
2510
|
+
onClick: handleInsert,
|
|
2511
|
+
children: "Insert"
|
|
2512
|
+
}
|
|
2513
|
+
),
|
|
2514
|
+
/* @__PURE__ */ jsx(
|
|
2515
|
+
Button,
|
|
2516
|
+
{
|
|
2517
|
+
variant: "outline",
|
|
2518
|
+
size: "sm",
|
|
2519
|
+
className: "h-8 text-xs",
|
|
2520
|
+
onClick: () => handleOpenChange(false),
|
|
2521
|
+
children: "Cancel"
|
|
2522
|
+
}
|
|
2523
|
+
)
|
|
2524
|
+
] })
|
|
2525
|
+
] })
|
|
2526
|
+
] })
|
|
2527
|
+
] });
|
|
2528
|
+
}
|
|
2529
|
+
var FIELD_TYPE_META = {
|
|
2530
|
+
["text" /* TEXT */]: { icon: Type, label: "Text", color: "text-blue-600" },
|
|
2531
|
+
["signature" /* SIGNATURE */]: { icon: PenTool, label: "Signature", color: "text-purple-600" },
|
|
2532
|
+
["initials" /* INITIALS */]: { icon: Hash, label: "Initials", color: "text-green-600" },
|
|
2533
|
+
["date" /* DATE */]: { icon: Calendar, label: "Date", color: "text-amber-600" },
|
|
2534
|
+
["checkbox" /* CHECKBOX */]: { icon: CheckSquare, label: "Checkbox", color: "text-teal-600" },
|
|
2535
|
+
["radio" /* RADIO */]: { icon: Circle, label: "Radio", color: "text-pink-600" },
|
|
2536
|
+
["dropdown" /* DROPDOWN */]: { icon: ChevronDown, label: "Dropdown", color: "text-indigo-600" },
|
|
2537
|
+
["text_label" /* TEXT_LABEL */]: { icon: FileText, label: "Label", color: "text-gray-600" }
|
|
2538
|
+
};
|
|
2539
|
+
function FieldEditPopover({
|
|
2540
|
+
fieldId,
|
|
2541
|
+
attrs,
|
|
2542
|
+
anchorEl,
|
|
2543
|
+
onUpdate,
|
|
2544
|
+
onDelete,
|
|
2545
|
+
onClose
|
|
2546
|
+
}) {
|
|
2547
|
+
const [label, setLabel] = useState("");
|
|
2548
|
+
const [required, setRequired] = useState(false);
|
|
2549
|
+
const [options, setOptions] = useState("");
|
|
2550
|
+
const [placeholder, setPlaceholder] = useState("");
|
|
2551
|
+
const [defaultValue, setDefaultValue] = useState("");
|
|
2552
|
+
const [advancedOpen, setAdvancedOpen] = useState(false);
|
|
2553
|
+
const [fontSize, setFontSize] = useState("");
|
|
2554
|
+
const [multiline, setMultiline] = useState(false);
|
|
2555
|
+
const [maxLength, setMaxLength] = useState("");
|
|
2556
|
+
const [acknowledgements, setAcknowledgements] = useState([]);
|
|
2557
|
+
const [isAddingAck, setIsAddingAck] = useState(false);
|
|
2558
|
+
const [ackTitle, setAckTitle] = useState("");
|
|
2559
|
+
const [ackDescription, setAckDescription] = useState("");
|
|
2560
|
+
useEffect(() => {
|
|
2561
|
+
if (attrs) {
|
|
2562
|
+
setLabel(String(attrs.fieldLabel || ""));
|
|
2563
|
+
setRequired(Boolean(attrs.required));
|
|
2564
|
+
setOptions(String(attrs.options || ""));
|
|
2565
|
+
setPlaceholder(String(attrs.placeholder || ""));
|
|
2566
|
+
setDefaultValue(String(attrs.defaultValue || ""));
|
|
2567
|
+
setFontSize(attrs.fontSize ? String(attrs.fontSize) : "");
|
|
2568
|
+
setMultiline(Boolean(attrs.multiline));
|
|
2569
|
+
setMaxLength(attrs.maxLength ? String(attrs.maxLength) : "");
|
|
2570
|
+
try {
|
|
2571
|
+
const acks = attrs.acknowledgements ? JSON.parse(attrs.acknowledgements) : [];
|
|
2572
|
+
setAcknowledgements(Array.isArray(acks) ? acks : []);
|
|
2573
|
+
} catch {
|
|
2574
|
+
setAcknowledgements([]);
|
|
2575
|
+
}
|
|
2576
|
+
setAdvancedOpen(false);
|
|
2577
|
+
setIsAddingAck(false);
|
|
2578
|
+
setAckTitle("");
|
|
2579
|
+
setAckDescription("");
|
|
2580
|
+
}
|
|
2581
|
+
}, [attrs]);
|
|
2582
|
+
const handleSave = useCallback(() => {
|
|
2583
|
+
if (!fieldId) return;
|
|
2584
|
+
const updates = {
|
|
2585
|
+
fieldLabel: label,
|
|
2586
|
+
required,
|
|
2587
|
+
options,
|
|
2588
|
+
placeholder,
|
|
2589
|
+
defaultValue,
|
|
2590
|
+
fontSize: fontSize ? parseInt(fontSize, 10) || null : null,
|
|
2591
|
+
multiline,
|
|
2592
|
+
maxLength: maxLength ? parseInt(maxLength, 10) || null : null,
|
|
2593
|
+
acknowledgements: acknowledgements.length > 0 ? JSON.stringify(acknowledgements) : ""
|
|
2594
|
+
};
|
|
2595
|
+
onUpdate(fieldId, updates);
|
|
2596
|
+
onClose();
|
|
2597
|
+
}, [fieldId, label, required, options, placeholder, defaultValue, fontSize, multiline, maxLength, acknowledgements, onUpdate, onClose]);
|
|
2598
|
+
const handleDelete = useCallback(() => {
|
|
2599
|
+
if (!fieldId) return;
|
|
2600
|
+
onDelete(fieldId);
|
|
2601
|
+
onClose();
|
|
2602
|
+
}, [fieldId, onDelete, onClose]);
|
|
2603
|
+
const addAcknowledgement = useCallback(() => {
|
|
2604
|
+
if (!ackTitle.trim()) return;
|
|
2605
|
+
const newAck = {
|
|
2606
|
+
id: v4(),
|
|
2607
|
+
title: ackTitle.trim(),
|
|
2608
|
+
description: ackDescription.trim()
|
|
2609
|
+
};
|
|
2610
|
+
setAcknowledgements((prev) => {
|
|
2611
|
+
const updated = [...prev, newAck];
|
|
2612
|
+
if (updated.length === 1) setRequired(true);
|
|
2613
|
+
return updated;
|
|
2614
|
+
});
|
|
2615
|
+
setAckTitle("");
|
|
2616
|
+
setAckDescription("");
|
|
2617
|
+
setIsAddingAck(false);
|
|
2618
|
+
}, [ackTitle, ackDescription]);
|
|
2619
|
+
const removeAcknowledgement = useCallback((id) => {
|
|
2620
|
+
setAcknowledgements((prev) => prev.filter((a) => a.id !== id));
|
|
2621
|
+
}, []);
|
|
2622
|
+
const isOpen = fieldId !== null && attrs !== null;
|
|
2623
|
+
const fieldType = attrs?.fieldType;
|
|
2624
|
+
const meta = FIELD_TYPE_META[fieldType] || FIELD_TYPE_META["text" /* TEXT */];
|
|
2625
|
+
const Icon = meta.icon;
|
|
2626
|
+
const showLabel = fieldType !== "text_label" /* TEXT_LABEL */;
|
|
2627
|
+
const showPlaceholder = fieldType === "text" /* TEXT */ || fieldType === "date" /* DATE */;
|
|
2628
|
+
const showOptions = fieldType === "dropdown" /* DROPDOWN */ || fieldType === "radio" /* RADIO */;
|
|
2629
|
+
const showDefaultValue = fieldType === "text" /* TEXT */ || fieldType === "text_label" /* TEXT_LABEL */;
|
|
2630
|
+
const showFontSize = fieldType === "text" /* TEXT */ || fieldType === "text_label" /* TEXT_LABEL */;
|
|
2631
|
+
const showMultiline = fieldType === "text" /* TEXT */;
|
|
2632
|
+
const showMaxLength = fieldType === "text" /* TEXT */;
|
|
2633
|
+
const showRequired = fieldType !== "text_label" /* TEXT_LABEL */;
|
|
2634
|
+
const showAcknowledgements = fieldType !== "text_label" /* TEXT_LABEL */;
|
|
2635
|
+
const hasAdvancedFields = showFontSize || showMultiline || showMaxLength || showAcknowledgements;
|
|
2636
|
+
return /* @__PURE__ */ jsxs(Popover, { open: isOpen, onOpenChange: (open) => {
|
|
2637
|
+
if (!open) onClose();
|
|
2638
|
+
}, children: [
|
|
2639
|
+
anchorEl && /* @__PURE__ */ jsx(PopoverAnchor, { virtualRef: { current: anchorEl } }),
|
|
2640
|
+
/* @__PURE__ */ jsx(PopoverContent, { className: "w-72 p-0", align: "start", sideOffset: 8, children: /* @__PURE__ */ jsxs("div", { className: "p-3 space-y-3", children: [
|
|
2641
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
|
|
2642
|
+
/* @__PURE__ */ jsxs("div", { className: cn("flex items-center gap-1.5 text-sm font-medium", meta.color), children: [
|
|
2643
|
+
/* @__PURE__ */ jsx(Icon, { size: 14 }),
|
|
2644
|
+
/* @__PURE__ */ jsxs("span", { children: [
|
|
2645
|
+
meta.label,
|
|
2646
|
+
" Field"
|
|
2647
|
+
] })
|
|
2648
|
+
] }),
|
|
2649
|
+
/* @__PURE__ */ jsx(
|
|
2650
|
+
Button,
|
|
2651
|
+
{
|
|
2652
|
+
variant: "ghost",
|
|
2653
|
+
size: "icon",
|
|
2654
|
+
className: "h-7 w-7 text-destructive hover:text-destructive",
|
|
2655
|
+
onClick: handleDelete,
|
|
2656
|
+
children: /* @__PURE__ */ jsx(Trash2, { size: 14 })
|
|
2657
|
+
}
|
|
2658
|
+
)
|
|
2659
|
+
] }),
|
|
2660
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
2661
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
2662
|
+
/* @__PURE__ */ jsx(Label, { htmlFor: "edit-field-name", className: "text-xs", children: "Name" }),
|
|
2663
|
+
/* @__PURE__ */ jsx(
|
|
2664
|
+
Input,
|
|
2665
|
+
{
|
|
2666
|
+
id: "edit-field-name",
|
|
2667
|
+
value: String(attrs?.fieldName || ""),
|
|
2668
|
+
disabled: true,
|
|
2669
|
+
className: "h-8 text-xs opacity-60"
|
|
2670
|
+
}
|
|
2671
|
+
),
|
|
2672
|
+
/* @__PURE__ */ jsx("p", { className: "text-[10px] text-muted-foreground mt-0.5", children: "Auto-generated identifier" })
|
|
2673
|
+
] }),
|
|
2674
|
+
showLabel && /* @__PURE__ */ jsxs("div", { children: [
|
|
2675
|
+
/* @__PURE__ */ jsx(Label, { htmlFor: "edit-field-label", className: "text-xs", children: "Label" }),
|
|
2676
|
+
/* @__PURE__ */ jsx(
|
|
2677
|
+
Input,
|
|
2678
|
+
{
|
|
2679
|
+
id: "edit-field-label",
|
|
2680
|
+
value: label,
|
|
2681
|
+
onChange: (e) => setLabel(e.target.value),
|
|
2682
|
+
className: "h-8 text-xs"
|
|
2683
|
+
}
|
|
2684
|
+
)
|
|
2685
|
+
] }),
|
|
2686
|
+
showDefaultValue && /* @__PURE__ */ jsxs("div", { children: [
|
|
2687
|
+
/* @__PURE__ */ jsx(Label, { htmlFor: "edit-field-default", className: "text-xs", children: fieldType === "text_label" /* TEXT_LABEL */ ? "Text Content" : "Default Value" }),
|
|
2688
|
+
/* @__PURE__ */ jsx(
|
|
2689
|
+
Input,
|
|
2690
|
+
{
|
|
2691
|
+
id: "edit-field-default",
|
|
2692
|
+
value: defaultValue,
|
|
2693
|
+
onChange: (e) => setDefaultValue(e.target.value),
|
|
2694
|
+
className: "h-8 text-xs"
|
|
2695
|
+
}
|
|
2696
|
+
)
|
|
2697
|
+
] }),
|
|
2698
|
+
showPlaceholder && /* @__PURE__ */ jsxs("div", { children: [
|
|
2699
|
+
/* @__PURE__ */ jsx(Label, { htmlFor: "edit-field-placeholder", className: "text-xs", children: "Placeholder" }),
|
|
2700
|
+
/* @__PURE__ */ jsx(
|
|
2701
|
+
Input,
|
|
2702
|
+
{
|
|
2703
|
+
id: "edit-field-placeholder",
|
|
2704
|
+
value: placeholder,
|
|
2705
|
+
onChange: (e) => setPlaceholder(e.target.value),
|
|
2706
|
+
className: "h-8 text-xs"
|
|
2707
|
+
}
|
|
2708
|
+
)
|
|
2709
|
+
] }),
|
|
2710
|
+
showOptions && /* @__PURE__ */ jsxs("div", { children: [
|
|
2711
|
+
/* @__PURE__ */ jsx(Label, { htmlFor: "edit-field-options", className: "text-xs", children: "Options (comma-separated)" }),
|
|
2712
|
+
/* @__PURE__ */ jsx(
|
|
2713
|
+
Input,
|
|
2714
|
+
{
|
|
2715
|
+
id: "edit-field-options",
|
|
2716
|
+
value: options,
|
|
2717
|
+
onChange: (e) => setOptions(e.target.value),
|
|
2718
|
+
className: "h-8 text-xs"
|
|
2719
|
+
}
|
|
2720
|
+
)
|
|
2721
|
+
] }),
|
|
2722
|
+
showRequired && /* @__PURE__ */ jsxs("label", { className: "flex items-center gap-2 cursor-pointer", children: [
|
|
2723
|
+
/* @__PURE__ */ jsx(
|
|
2724
|
+
"input",
|
|
2725
|
+
{
|
|
2726
|
+
type: "checkbox",
|
|
2727
|
+
checked: required,
|
|
2728
|
+
onChange: (e) => setRequired(e.target.checked),
|
|
2729
|
+
className: "h-3.5 w-3.5 rounded border-border"
|
|
2730
|
+
}
|
|
2731
|
+
),
|
|
2732
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: "Required field" })
|
|
2733
|
+
] })
|
|
2734
|
+
] }),
|
|
2735
|
+
hasAdvancedFields && /* @__PURE__ */ jsxs("div", { className: "border-t border-border pt-2", children: [
|
|
2736
|
+
/* @__PURE__ */ jsxs(
|
|
2737
|
+
"button",
|
|
2738
|
+
{
|
|
2739
|
+
className: "flex items-center gap-1 text-xs font-medium text-muted-foreground hover:text-foreground w-full",
|
|
2740
|
+
onClick: () => setAdvancedOpen(!advancedOpen),
|
|
2741
|
+
children: [
|
|
2742
|
+
advancedOpen ? /* @__PURE__ */ jsx(ChevronDown, { size: 12 }) : /* @__PURE__ */ jsx(ChevronRight, { size: 12 }),
|
|
2743
|
+
"Advanced"
|
|
2744
|
+
]
|
|
2745
|
+
}
|
|
2746
|
+
),
|
|
2747
|
+
advancedOpen && /* @__PURE__ */ jsxs("div", { className: "mt-2 space-y-2", children: [
|
|
2748
|
+
showFontSize && /* @__PURE__ */ jsxs("div", { children: [
|
|
2749
|
+
/* @__PURE__ */ jsx(Label, { htmlFor: "edit-field-fontsize", className: "text-xs", children: "Font Size (8-72)" }),
|
|
2750
|
+
/* @__PURE__ */ jsx(
|
|
2751
|
+
Input,
|
|
2752
|
+
{
|
|
2753
|
+
id: "edit-field-fontsize",
|
|
2754
|
+
type: "number",
|
|
2755
|
+
min: "8",
|
|
2756
|
+
max: "72",
|
|
2757
|
+
value: fontSize,
|
|
2758
|
+
onChange: (e) => setFontSize(e.target.value),
|
|
2759
|
+
placeholder: "12",
|
|
2760
|
+
className: "h-8 text-xs"
|
|
2761
|
+
}
|
|
2762
|
+
)
|
|
2763
|
+
] }),
|
|
2764
|
+
showMultiline && /* @__PURE__ */ jsxs("label", { className: "flex items-center gap-2 cursor-pointer", children: [
|
|
2765
|
+
/* @__PURE__ */ jsx(
|
|
2766
|
+
"input",
|
|
2767
|
+
{
|
|
2768
|
+
type: "checkbox",
|
|
2769
|
+
checked: multiline,
|
|
2770
|
+
onChange: (e) => setMultiline(e.target.checked),
|
|
2771
|
+
className: "h-3.5 w-3.5 rounded border-border"
|
|
2772
|
+
}
|
|
2773
|
+
),
|
|
2774
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: "Multiline" })
|
|
2775
|
+
] }),
|
|
2776
|
+
showMaxLength && /* @__PURE__ */ jsxs("div", { children: [
|
|
2777
|
+
/* @__PURE__ */ jsx(Label, { htmlFor: "edit-field-maxlength", className: "text-xs", children: "Max Length" }),
|
|
2778
|
+
/* @__PURE__ */ jsx(
|
|
2779
|
+
Input,
|
|
2780
|
+
{
|
|
2781
|
+
id: "edit-field-maxlength",
|
|
2782
|
+
type: "number",
|
|
2783
|
+
min: "1",
|
|
2784
|
+
value: maxLength,
|
|
2785
|
+
onChange: (e) => setMaxLength(e.target.value),
|
|
2786
|
+
placeholder: "No limit",
|
|
2787
|
+
className: "h-8 text-xs"
|
|
2788
|
+
}
|
|
2789
|
+
)
|
|
2790
|
+
] }),
|
|
2791
|
+
showAcknowledgements && /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
|
|
2792
|
+
/* @__PURE__ */ jsx(Label, { className: "text-xs", children: "Acknowledgements" }),
|
|
2793
|
+
acknowledgements.length === 0 && !isAddingAck && /* @__PURE__ */ jsx("p", { className: "text-[10px] text-muted-foreground", children: "No acknowledgements added" }),
|
|
2794
|
+
acknowledgements.map((ack) => /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-1.5 rounded border border-border p-1.5", children: [
|
|
2795
|
+
/* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
|
|
2796
|
+
/* @__PURE__ */ jsx("p", { className: "text-[11px] font-medium text-foreground truncate", children: ack.title }),
|
|
2797
|
+
ack.description && /* @__PURE__ */ jsx("p", { className: "text-[10px] text-muted-foreground truncate", children: ack.description })
|
|
2798
|
+
] }),
|
|
2799
|
+
/* @__PURE__ */ jsx(
|
|
2800
|
+
"button",
|
|
2801
|
+
{
|
|
2802
|
+
className: "text-muted-foreground hover:text-destructive shrink-0 mt-0.5",
|
|
2803
|
+
onClick: () => removeAcknowledgement(ack.id),
|
|
2804
|
+
children: /* @__PURE__ */ jsx(X, { size: 12 })
|
|
2805
|
+
}
|
|
2806
|
+
)
|
|
2807
|
+
] }, ack.id)),
|
|
2808
|
+
isAddingAck ? /* @__PURE__ */ jsxs("div", { className: "space-y-1.5 rounded border border-border p-2", children: [
|
|
2809
|
+
/* @__PURE__ */ jsx(
|
|
2810
|
+
Input,
|
|
2811
|
+
{
|
|
2812
|
+
value: ackTitle,
|
|
2813
|
+
onChange: (e) => setAckTitle(e.target.value),
|
|
2814
|
+
placeholder: "Title (required)",
|
|
2815
|
+
className: "h-7 text-xs"
|
|
2816
|
+
}
|
|
2817
|
+
),
|
|
2818
|
+
/* @__PURE__ */ jsx(
|
|
2819
|
+
Input,
|
|
2820
|
+
{
|
|
2821
|
+
value: ackDescription,
|
|
2822
|
+
onChange: (e) => setAckDescription(e.target.value),
|
|
2823
|
+
placeholder: "Description (optional)",
|
|
2824
|
+
className: "h-7 text-xs"
|
|
2825
|
+
}
|
|
2826
|
+
),
|
|
2827
|
+
/* @__PURE__ */ jsxs("div", { className: "flex gap-1.5", children: [
|
|
2828
|
+
/* @__PURE__ */ jsx(
|
|
2829
|
+
Button,
|
|
2830
|
+
{
|
|
2831
|
+
size: "sm",
|
|
2832
|
+
className: "h-6 text-[10px] flex-1",
|
|
2833
|
+
onClick: addAcknowledgement,
|
|
2834
|
+
disabled: !ackTitle.trim(),
|
|
2835
|
+
children: "Add"
|
|
2836
|
+
}
|
|
2837
|
+
),
|
|
2838
|
+
/* @__PURE__ */ jsx(
|
|
2839
|
+
Button,
|
|
2840
|
+
{
|
|
2841
|
+
variant: "outline",
|
|
2842
|
+
size: "sm",
|
|
2843
|
+
className: "h-6 text-[10px]",
|
|
2844
|
+
onClick: () => {
|
|
2845
|
+
setIsAddingAck(false);
|
|
2846
|
+
setAckTitle("");
|
|
2847
|
+
setAckDescription("");
|
|
2848
|
+
},
|
|
2849
|
+
children: "Cancel"
|
|
2850
|
+
}
|
|
2851
|
+
)
|
|
2852
|
+
] })
|
|
2853
|
+
] }) : /* @__PURE__ */ jsxs(
|
|
2854
|
+
"button",
|
|
2855
|
+
{
|
|
2856
|
+
className: "flex items-center gap-1 text-[11px] text-primary hover:text-primary/80",
|
|
2857
|
+
onClick: () => setIsAddingAck(true),
|
|
2858
|
+
children: [
|
|
2859
|
+
/* @__PURE__ */ jsx(Plus, { size: 11 }),
|
|
2860
|
+
"Add acknowledgement"
|
|
2861
|
+
]
|
|
2862
|
+
}
|
|
2863
|
+
)
|
|
2864
|
+
] })
|
|
2865
|
+
] })
|
|
2866
|
+
] }),
|
|
2867
|
+
/* @__PURE__ */ jsxs("div", { className: "flex gap-2 pt-1", children: [
|
|
2868
|
+
/* @__PURE__ */ jsx(
|
|
2869
|
+
Button,
|
|
2870
|
+
{
|
|
2871
|
+
size: "sm",
|
|
2872
|
+
className: "h-8 flex-1 text-xs",
|
|
2873
|
+
onClick: handleSave,
|
|
2874
|
+
children: "Save"
|
|
2875
|
+
}
|
|
2876
|
+
),
|
|
2877
|
+
/* @__PURE__ */ jsx(
|
|
2878
|
+
Button,
|
|
2879
|
+
{
|
|
2880
|
+
variant: "outline",
|
|
2881
|
+
size: "sm",
|
|
2882
|
+
className: "h-8 text-xs",
|
|
2883
|
+
onClick: onClose,
|
|
2884
|
+
children: "Cancel"
|
|
2885
|
+
}
|
|
2886
|
+
)
|
|
2887
|
+
] })
|
|
2888
|
+
] }) })
|
|
2889
|
+
] });
|
|
2890
|
+
}
|
|
2891
|
+
function labelToVarName(label) {
|
|
2892
|
+
return label.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_|_$/g, "");
|
|
2893
|
+
}
|
|
2894
|
+
function VariableInsertPopover({
|
|
2895
|
+
open,
|
|
2896
|
+
onOpenChange,
|
|
2897
|
+
onInsert,
|
|
2898
|
+
children
|
|
2899
|
+
}) {
|
|
2900
|
+
const [varLabel, setVarLabel] = useState("");
|
|
2901
|
+
const [varName, setVarName] = useState("");
|
|
2902
|
+
const [varDefault, setVarDefault] = useState("");
|
|
2903
|
+
const [nameManuallyEdited, setNameManuallyEdited] = useState(false);
|
|
2904
|
+
const reset = useCallback(() => {
|
|
2905
|
+
setVarLabel("");
|
|
2906
|
+
setVarName("");
|
|
2907
|
+
setVarDefault("");
|
|
2908
|
+
setNameManuallyEdited(false);
|
|
2909
|
+
}, []);
|
|
2910
|
+
const handleOpenChange = useCallback(
|
|
2911
|
+
(nextOpen) => {
|
|
2912
|
+
if (!nextOpen) reset();
|
|
2913
|
+
onOpenChange(nextOpen);
|
|
2914
|
+
},
|
|
2915
|
+
[onOpenChange, reset]
|
|
2916
|
+
);
|
|
2917
|
+
useEffect(() => {
|
|
2918
|
+
if (!nameManuallyEdited) {
|
|
2919
|
+
setVarName(labelToVarName(varLabel));
|
|
2920
|
+
}
|
|
2921
|
+
}, [varLabel, nameManuallyEdited]);
|
|
2922
|
+
const handleInsert = useCallback(() => {
|
|
2923
|
+
if (!varLabel.trim() || !varName.trim()) return;
|
|
2924
|
+
onInsert({
|
|
2925
|
+
varName: varName.trim(),
|
|
2926
|
+
varLabel: varLabel.trim(),
|
|
2927
|
+
varDefault: varDefault.trim()
|
|
2928
|
+
});
|
|
2929
|
+
handleOpenChange(false);
|
|
2930
|
+
}, [varLabel, varName, varDefault, onInsert, handleOpenChange]);
|
|
2931
|
+
return /* @__PURE__ */ jsxs(Popover, { open, onOpenChange: handleOpenChange, children: [
|
|
2932
|
+
/* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, children }),
|
|
2933
|
+
/* @__PURE__ */ jsx(PopoverContent, { className: "w-72 p-0", align: "start", sideOffset: 8, children: /* @__PURE__ */ jsxs("div", { className: "p-3 space-y-3", children: [
|
|
2934
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5 text-sm font-medium text-orange-700", children: [
|
|
2935
|
+
/* @__PURE__ */ jsx(Braces, { size: 14 }),
|
|
2936
|
+
/* @__PURE__ */ jsx("span", { children: "Insert Variable" })
|
|
2937
|
+
] }),
|
|
2938
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
2939
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
2940
|
+
/* @__PURE__ */ jsx(Label, { htmlFor: "var-label", className: "text-xs", children: "Label" }),
|
|
2941
|
+
/* @__PURE__ */ jsx(
|
|
2942
|
+
Input,
|
|
2943
|
+
{
|
|
2944
|
+
id: "var-label",
|
|
2945
|
+
value: varLabel,
|
|
2946
|
+
onChange: (e) => setVarLabel(e.target.value),
|
|
2947
|
+
placeholder: "e.g. Company Name",
|
|
2948
|
+
className: "h-8 text-xs"
|
|
2949
|
+
}
|
|
2950
|
+
)
|
|
2951
|
+
] }),
|
|
2952
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
2953
|
+
/* @__PURE__ */ jsx(Label, { htmlFor: "var-name", className: "text-xs", children: "Name (identifier)" }),
|
|
2954
|
+
/* @__PURE__ */ jsx(
|
|
2955
|
+
Input,
|
|
2956
|
+
{
|
|
2957
|
+
id: "var-name",
|
|
2958
|
+
value: varName,
|
|
2959
|
+
onChange: (e) => {
|
|
2960
|
+
setVarName(e.target.value);
|
|
2961
|
+
setNameManuallyEdited(true);
|
|
2962
|
+
},
|
|
2963
|
+
placeholder: "e.g. company_name",
|
|
2964
|
+
className: "h-8 text-xs font-mono"
|
|
2965
|
+
}
|
|
2966
|
+
),
|
|
2967
|
+
/* @__PURE__ */ jsx("p", { className: "text-[10px] text-muted-foreground mt-0.5", children: "Used as key in data objects" })
|
|
2968
|
+
] }),
|
|
2969
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
2970
|
+
/* @__PURE__ */ jsx(Label, { htmlFor: "var-default", className: "text-xs", children: "Default Value" }),
|
|
2971
|
+
/* @__PURE__ */ jsx(
|
|
2972
|
+
Input,
|
|
2973
|
+
{
|
|
2974
|
+
id: "var-default",
|
|
2975
|
+
value: varDefault,
|
|
2976
|
+
onChange: (e) => setVarDefault(e.target.value),
|
|
2977
|
+
placeholder: "Optional",
|
|
2978
|
+
className: "h-8 text-xs"
|
|
2979
|
+
}
|
|
2980
|
+
)
|
|
2981
|
+
] })
|
|
2982
|
+
] }),
|
|
2983
|
+
/* @__PURE__ */ jsxs("div", { className: "flex gap-2 pt-1", children: [
|
|
2984
|
+
/* @__PURE__ */ jsx(
|
|
2985
|
+
Button,
|
|
2986
|
+
{
|
|
2987
|
+
size: "sm",
|
|
2988
|
+
className: "h-8 flex-1 text-xs",
|
|
2989
|
+
onClick: handleInsert,
|
|
2990
|
+
disabled: !varLabel.trim() || !varName.trim(),
|
|
2991
|
+
children: "Insert"
|
|
2992
|
+
}
|
|
2993
|
+
),
|
|
2994
|
+
/* @__PURE__ */ jsx(
|
|
2995
|
+
Button,
|
|
2996
|
+
{
|
|
2997
|
+
variant: "outline",
|
|
2998
|
+
size: "sm",
|
|
2999
|
+
className: "h-8 text-xs",
|
|
3000
|
+
onClick: () => handleOpenChange(false),
|
|
3001
|
+
children: "Cancel"
|
|
3002
|
+
}
|
|
3003
|
+
)
|
|
3004
|
+
] })
|
|
3005
|
+
] }) })
|
|
3006
|
+
] });
|
|
3007
|
+
}
|
|
3008
|
+
function VariableEditPopover({
|
|
3009
|
+
varName,
|
|
3010
|
+
attrs,
|
|
3011
|
+
anchorEl,
|
|
3012
|
+
onUpdate,
|
|
3013
|
+
onDelete,
|
|
3014
|
+
onClose
|
|
3015
|
+
}) {
|
|
3016
|
+
const [varLabel, setVarLabel] = useState("");
|
|
3017
|
+
const [varDefault, setVarDefault] = useState("");
|
|
3018
|
+
useEffect(() => {
|
|
3019
|
+
if (attrs) {
|
|
3020
|
+
setVarLabel(String(attrs.varLabel || ""));
|
|
3021
|
+
setVarDefault(String(attrs.varDefault || ""));
|
|
3022
|
+
}
|
|
3023
|
+
}, [attrs]);
|
|
3024
|
+
const handleSave = useCallback(() => {
|
|
3025
|
+
if (!varName) return;
|
|
3026
|
+
onUpdate(varName, {
|
|
3027
|
+
varLabel,
|
|
3028
|
+
varDefault
|
|
3029
|
+
});
|
|
3030
|
+
onClose();
|
|
3031
|
+
}, [varName, varLabel, varDefault, onUpdate, onClose]);
|
|
3032
|
+
const handleDelete = useCallback(() => {
|
|
3033
|
+
if (!varName) return;
|
|
3034
|
+
onDelete(varName);
|
|
3035
|
+
onClose();
|
|
3036
|
+
}, [varName, onDelete, onClose]);
|
|
3037
|
+
const isOpen = varName !== null && attrs !== null;
|
|
3038
|
+
return /* @__PURE__ */ jsxs(Popover, { open: isOpen, onOpenChange: (open) => {
|
|
3039
|
+
if (!open) onClose();
|
|
3040
|
+
}, children: [
|
|
3041
|
+
anchorEl && /* @__PURE__ */ jsx(PopoverAnchor, { virtualRef: { current: anchorEl } }),
|
|
3042
|
+
/* @__PURE__ */ jsx(PopoverContent, { className: "w-72 p-0", align: "start", sideOffset: 8, children: /* @__PURE__ */ jsxs("div", { className: "p-3 space-y-3", children: [
|
|
3043
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
|
|
3044
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5 text-sm font-medium text-orange-700", children: [
|
|
3045
|
+
/* @__PURE__ */ jsx(Braces, { size: 14 }),
|
|
3046
|
+
/* @__PURE__ */ jsx("span", { children: "Edit Variable" })
|
|
3047
|
+
] }),
|
|
3048
|
+
/* @__PURE__ */ jsx(
|
|
3049
|
+
Button,
|
|
3050
|
+
{
|
|
3051
|
+
variant: "ghost",
|
|
3052
|
+
size: "icon",
|
|
3053
|
+
className: "h-7 w-7 text-destructive hover:text-destructive",
|
|
3054
|
+
onClick: handleDelete,
|
|
3055
|
+
children: /* @__PURE__ */ jsx(Trash2, { size: 14 })
|
|
3056
|
+
}
|
|
3057
|
+
)
|
|
3058
|
+
] }),
|
|
3059
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
3060
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
3061
|
+
/* @__PURE__ */ jsx(Label, { htmlFor: "edit-var-name", className: "text-xs", children: "Name (identifier)" }),
|
|
3062
|
+
/* @__PURE__ */ jsx(
|
|
3063
|
+
Input,
|
|
3064
|
+
{
|
|
3065
|
+
id: "edit-var-name",
|
|
3066
|
+
value: String(attrs?.varName || ""),
|
|
3067
|
+
disabled: true,
|
|
3068
|
+
className: "h-8 text-xs font-mono opacity-60"
|
|
3069
|
+
}
|
|
3070
|
+
),
|
|
3071
|
+
/* @__PURE__ */ jsx("p", { className: "text-[10px] text-muted-foreground mt-0.5", children: "Same name = same value across document" })
|
|
3072
|
+
] }),
|
|
3073
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
3074
|
+
/* @__PURE__ */ jsx(Label, { htmlFor: "edit-var-label", className: "text-xs", children: "Label" }),
|
|
3075
|
+
/* @__PURE__ */ jsx(
|
|
3076
|
+
Input,
|
|
3077
|
+
{
|
|
3078
|
+
id: "edit-var-label",
|
|
3079
|
+
value: varLabel,
|
|
3080
|
+
onChange: (e) => setVarLabel(e.target.value),
|
|
3081
|
+
className: "h-8 text-xs"
|
|
3082
|
+
}
|
|
3083
|
+
)
|
|
3084
|
+
] }),
|
|
3085
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
3086
|
+
/* @__PURE__ */ jsx(Label, { htmlFor: "edit-var-default", className: "text-xs", children: "Default Value" }),
|
|
3087
|
+
/* @__PURE__ */ jsx(
|
|
3088
|
+
Input,
|
|
3089
|
+
{
|
|
3090
|
+
id: "edit-var-default",
|
|
3091
|
+
value: varDefault,
|
|
3092
|
+
onChange: (e) => setVarDefault(e.target.value),
|
|
3093
|
+
placeholder: "Optional",
|
|
3094
|
+
className: "h-8 text-xs"
|
|
3095
|
+
}
|
|
3096
|
+
)
|
|
3097
|
+
] })
|
|
3098
|
+
] }),
|
|
3099
|
+
/* @__PURE__ */ jsxs("div", { className: "flex gap-2 pt-1", children: [
|
|
3100
|
+
/* @__PURE__ */ jsx(
|
|
3101
|
+
Button,
|
|
3102
|
+
{
|
|
3103
|
+
size: "sm",
|
|
3104
|
+
className: "h-8 flex-1 text-xs",
|
|
3105
|
+
onClick: handleSave,
|
|
3106
|
+
children: "Save"
|
|
3107
|
+
}
|
|
3108
|
+
),
|
|
3109
|
+
/* @__PURE__ */ jsx(
|
|
3110
|
+
Button,
|
|
3111
|
+
{
|
|
3112
|
+
variant: "outline",
|
|
3113
|
+
size: "sm",
|
|
3114
|
+
className: "h-8 text-xs",
|
|
3115
|
+
onClick: onClose,
|
|
3116
|
+
children: "Cancel"
|
|
3117
|
+
}
|
|
3118
|
+
)
|
|
3119
|
+
] })
|
|
3120
|
+
] }) })
|
|
3121
|
+
] });
|
|
3122
|
+
}
|
|
3123
|
+
function getFieldIcon(type) {
|
|
3124
|
+
switch (type) {
|
|
3125
|
+
case "text" /* TEXT */:
|
|
3126
|
+
return "T";
|
|
3127
|
+
case "signature" /* SIGNATURE */:
|
|
3128
|
+
return "\u270D";
|
|
3129
|
+
case "initials" /* INITIALS */:
|
|
3130
|
+
return "I";
|
|
3131
|
+
case "date" /* DATE */:
|
|
3132
|
+
return "\u{1F4C5}";
|
|
3133
|
+
case "checkbox" /* CHECKBOX */:
|
|
3134
|
+
return "\u2610";
|
|
3135
|
+
case "radio" /* RADIO */:
|
|
3136
|
+
return "\u25CB";
|
|
3137
|
+
case "dropdown" /* DROPDOWN */:
|
|
3138
|
+
return "\u25BC";
|
|
3139
|
+
case "text_label" /* TEXT_LABEL */:
|
|
3140
|
+
return "A";
|
|
3141
|
+
default:
|
|
3142
|
+
return "T";
|
|
3143
|
+
}
|
|
3144
|
+
}
|
|
3145
|
+
function getFieldColors(type) {
|
|
3146
|
+
switch (type) {
|
|
3147
|
+
case "signature" /* SIGNATURE */:
|
|
3148
|
+
return { bg: "bg-purple-50", border: "border-purple-300" };
|
|
3149
|
+
case "initials" /* INITIALS */:
|
|
3150
|
+
return { bg: "bg-green-50", border: "border-green-400" };
|
|
3151
|
+
case "date" /* DATE */:
|
|
3152
|
+
return { bg: "bg-orange-50", border: "border-orange-400" };
|
|
3153
|
+
default:
|
|
3154
|
+
return { bg: "bg-card", border: "border-border" };
|
|
3155
|
+
}
|
|
3156
|
+
}
|
|
3157
|
+
function FieldOverlay({ field, renderScale, pageHeight }) {
|
|
3158
|
+
const { position } = field;
|
|
3159
|
+
const displayX = position.x * renderScale;
|
|
3160
|
+
const displayY = (pageHeight - position.y - position.height) * renderScale;
|
|
3161
|
+
const displayWidth = position.width * renderScale;
|
|
3162
|
+
const displayHeight = position.height * renderScale;
|
|
3163
|
+
const colors = getFieldColors(field.type);
|
|
3164
|
+
const icon = getFieldIcon(field.type);
|
|
3165
|
+
return /* @__PURE__ */ jsx(
|
|
3166
|
+
"div",
|
|
3167
|
+
{
|
|
3168
|
+
className: cn(
|
|
3169
|
+
"absolute box-border border-2 border-dashed rounded pointer-events-none",
|
|
3170
|
+
colors.bg,
|
|
3171
|
+
colors.border
|
|
3172
|
+
),
|
|
3173
|
+
style: {
|
|
3174
|
+
left: displayX,
|
|
3175
|
+
top: displayY,
|
|
3176
|
+
width: displayWidth,
|
|
3177
|
+
height: displayHeight
|
|
3178
|
+
},
|
|
3179
|
+
children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1 h-full px-1 text-xs overflow-hidden", children: [
|
|
3180
|
+
/* @__PURE__ */ jsx("span", { className: "shrink-0", children: icon }),
|
|
3181
|
+
/* @__PURE__ */ jsx("span", { className: "overflow-hidden text-ellipsis whitespace-nowrap", children: field.label })
|
|
3182
|
+
] })
|
|
3183
|
+
}
|
|
3184
|
+
);
|
|
3185
|
+
}
|
|
3186
|
+
function PreviewPanel({
|
|
3187
|
+
pages,
|
|
3188
|
+
isGenerating,
|
|
3189
|
+
positionedFields,
|
|
3190
|
+
headerLeft,
|
|
3191
|
+
className
|
|
3192
|
+
}) {
|
|
3193
|
+
const [currentPageIndex, setCurrentPageIndex] = useState(0);
|
|
3194
|
+
const [zoomLevel, setZoomLevel] = useState(1);
|
|
3195
|
+
const currentPage = pages.length > 0 ? pages[currentPageIndex] : null;
|
|
3196
|
+
useEffect(() => {
|
|
3197
|
+
if (pages.length > 0 && currentPageIndex >= pages.length) {
|
|
3198
|
+
setCurrentPageIndex(0);
|
|
3199
|
+
}
|
|
3200
|
+
}, [pages.length, currentPageIndex]);
|
|
3201
|
+
const zoomIn = () => setZoomLevel((prev) => Math.min(prev + 0.25, 4));
|
|
3202
|
+
const zoomOut = () => setZoomLevel((prev) => Math.max(prev - 0.25, 0.25));
|
|
3203
|
+
const resetZoom = () => setZoomLevel(1);
|
|
3204
|
+
const handleZoomSliderChange = (e) => {
|
|
3205
|
+
setZoomLevel(parseFloat(e.target.value));
|
|
3206
|
+
};
|
|
3207
|
+
const pageDisplaySize = useMemo(() => {
|
|
3208
|
+
if (!currentPage) return null;
|
|
3209
|
+
const renderWidth = currentPage.width;
|
|
3210
|
+
const renderHeight = currentPage.height;
|
|
3211
|
+
const baseScale = Math.min(700 / renderWidth, 900 / renderHeight, 1);
|
|
3212
|
+
const viewportWidth = renderWidth * baseScale;
|
|
3213
|
+
const viewportHeight = renderHeight * baseScale;
|
|
3214
|
+
const contentWidth = renderWidth * baseScale * zoomLevel;
|
|
3215
|
+
const contentHeight = renderHeight * baseScale * zoomLevel;
|
|
3216
|
+
const renderScale = baseScale * zoomLevel;
|
|
3217
|
+
return { viewportWidth, viewportHeight, contentWidth, contentHeight, renderScale };
|
|
3218
|
+
}, [currentPage, zoomLevel]);
|
|
3219
|
+
const pageFields = useMemo(() => {
|
|
3220
|
+
if (!currentPage || !positionedFields) return [];
|
|
3221
|
+
return positionedFields.filter(
|
|
3222
|
+
(f) => f.position.page === currentPageIndex + 1 && f.position.width > 0
|
|
3223
|
+
);
|
|
3224
|
+
}, [positionedFields, currentPage, currentPageIndex]);
|
|
3225
|
+
const showScrollbars = zoomLevel > 1;
|
|
3226
|
+
return /* @__PURE__ */ jsxs("div", { className: cn("flex flex-col h-full border border-border rounded-lg overflow-hidden bg-muted/20", className), children: [
|
|
3227
|
+
/* @__PURE__ */ jsx("div", { className: "border-b border-border px-2 py-1.5 flex-shrink-0", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-2 flex-wrap", children: [
|
|
3228
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
|
|
3229
|
+
headerLeft,
|
|
3230
|
+
/* @__PURE__ */ jsxs(
|
|
3231
|
+
Button,
|
|
3232
|
+
{
|
|
3233
|
+
variant: "outline",
|
|
3234
|
+
size: "sm",
|
|
3235
|
+
className: "h-8 px-2 text-xs",
|
|
3236
|
+
disabled: currentPageIndex <= 0 || pages.length === 0,
|
|
3237
|
+
onClick: () => setCurrentPageIndex((p) => Math.max(0, p - 1)),
|
|
3238
|
+
children: [
|
|
3239
|
+
/* @__PURE__ */ jsx(ChevronLeft, { size: 14 }),
|
|
3240
|
+
"Prev"
|
|
3241
|
+
]
|
|
3242
|
+
}
|
|
3243
|
+
),
|
|
3244
|
+
/* @__PURE__ */ jsxs(
|
|
3245
|
+
Button,
|
|
3246
|
+
{
|
|
3247
|
+
variant: "outline",
|
|
3248
|
+
size: "sm",
|
|
3249
|
+
className: "h-8 px-2 text-xs",
|
|
3250
|
+
disabled: currentPageIndex >= pages.length - 1 || pages.length === 0,
|
|
3251
|
+
onClick: () => setCurrentPageIndex((p) => Math.min(pages.length - 1, p + 1)),
|
|
3252
|
+
children: [
|
|
3253
|
+
"Next",
|
|
3254
|
+
/* @__PURE__ */ jsx(ChevronRight, { size: 14 })
|
|
3255
|
+
]
|
|
3256
|
+
}
|
|
3257
|
+
),
|
|
3258
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground ml-1", children: pages.length > 0 ? `Page ${currentPageIndex + 1} of ${pages.length}` : "No pages" })
|
|
3259
|
+
] }),
|
|
3260
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
3261
|
+
pageFields.length > 0 && /* @__PURE__ */ jsxs("span", { className: "text-xs text-muted-foreground", children: [
|
|
3262
|
+
pageFields.length,
|
|
3263
|
+
" field",
|
|
3264
|
+
pageFields.length !== 1 ? "s" : "",
|
|
3265
|
+
" on this page"
|
|
3266
|
+
] }),
|
|
3267
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
|
|
3268
|
+
/* @__PURE__ */ jsx(
|
|
3269
|
+
Button,
|
|
3270
|
+
{
|
|
3271
|
+
variant: "outline",
|
|
3272
|
+
size: "sm",
|
|
3273
|
+
className: "h-8 w-8 p-0",
|
|
3274
|
+
disabled: pages.length === 0 || zoomLevel <= 0.25,
|
|
3275
|
+
onClick: zoomOut,
|
|
3276
|
+
children: /* @__PURE__ */ jsx(ZoomOut, { size: 14 })
|
|
3277
|
+
}
|
|
3278
|
+
),
|
|
3279
|
+
/* @__PURE__ */ jsx(
|
|
3280
|
+
"input",
|
|
3281
|
+
{
|
|
3282
|
+
type: "range",
|
|
3283
|
+
min: "0.25",
|
|
3284
|
+
max: "4",
|
|
3285
|
+
step: "0.25",
|
|
3286
|
+
value: zoomLevel,
|
|
3287
|
+
onChange: handleZoomSliderChange,
|
|
3288
|
+
disabled: pages.length === 0,
|
|
3289
|
+
className: "w-20 h-1.5 bg-muted rounded-lg appearance-none cursor-pointer accent-primary disabled:opacity-50 disabled:cursor-not-allowed"
|
|
3290
|
+
}
|
|
3291
|
+
),
|
|
3292
|
+
/* @__PURE__ */ jsx(
|
|
3293
|
+
Button,
|
|
3294
|
+
{
|
|
3295
|
+
variant: "outline",
|
|
3296
|
+
size: "sm",
|
|
3297
|
+
className: "h-8 w-8 p-0",
|
|
3298
|
+
disabled: pages.length === 0 || zoomLevel >= 4,
|
|
3299
|
+
onClick: zoomIn,
|
|
3300
|
+
children: /* @__PURE__ */ jsx(ZoomIn, { size: 14 })
|
|
3301
|
+
}
|
|
3302
|
+
),
|
|
3303
|
+
/* @__PURE__ */ jsxs("span", { className: "text-xs text-muted-foreground font-medium min-w-[2.5rem] text-center", children: [
|
|
3304
|
+
Math.round(zoomLevel * 100),
|
|
3305
|
+
"%"
|
|
3306
|
+
] }),
|
|
3307
|
+
/* @__PURE__ */ jsx(
|
|
3308
|
+
Button,
|
|
3309
|
+
{
|
|
3310
|
+
variant: "outline",
|
|
3311
|
+
size: "sm",
|
|
3312
|
+
className: "h-8 w-8 p-0",
|
|
3313
|
+
disabled: pages.length === 0 || zoomLevel === 1,
|
|
3314
|
+
onClick: resetZoom,
|
|
3315
|
+
children: /* @__PURE__ */ jsx(Maximize2, { size: 14 })
|
|
3316
|
+
}
|
|
3317
|
+
)
|
|
3318
|
+
] })
|
|
3319
|
+
] })
|
|
3320
|
+
] }) }),
|
|
3321
|
+
/* @__PURE__ */ jsxs(
|
|
3322
|
+
"div",
|
|
3323
|
+
{
|
|
3324
|
+
className: cn(
|
|
3325
|
+
"flex-1 min-h-0 bg-muted/50 flex justify-center overflow-auto p-4",
|
|
3326
|
+
!isGenerating && currentPage && pageDisplaySize ? "items-start" : "items-center"
|
|
3327
|
+
),
|
|
3328
|
+
children: [
|
|
3329
|
+
isGenerating && /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center justify-center gap-2 text-muted-foreground", children: [
|
|
3330
|
+
/* @__PURE__ */ jsx(Loader2, { size: 24, className: "animate-spin" }),
|
|
3331
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm", children: "Generating PDF..." })
|
|
3332
|
+
] }),
|
|
3333
|
+
!isGenerating && pages.length === 0 && /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center justify-center gap-2 text-muted-foreground", children: [
|
|
3334
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm font-medium", children: "No preview available" }),
|
|
3335
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs", children: 'Click "Generate PDF" to see a preview' })
|
|
3336
|
+
] }),
|
|
3337
|
+
!isGenerating && currentPage && pageDisplaySize && /* @__PURE__ */ jsx(
|
|
3338
|
+
"div",
|
|
3339
|
+
{
|
|
3340
|
+
className: cn(
|
|
3341
|
+
"border border-border rounded-lg bg-muted/30 shrink-0",
|
|
3342
|
+
showScrollbars ? "overflow-auto" : "overflow-hidden"
|
|
3343
|
+
),
|
|
3344
|
+
style: {
|
|
3345
|
+
width: pageDisplaySize.viewportWidth,
|
|
3346
|
+
height: pageDisplaySize.viewportHeight
|
|
3347
|
+
},
|
|
3348
|
+
children: /* @__PURE__ */ jsxs(
|
|
3349
|
+
"div",
|
|
3350
|
+
{
|
|
3351
|
+
className: "relative bg-white",
|
|
3352
|
+
style: {
|
|
3353
|
+
width: pageDisplaySize.contentWidth,
|
|
3354
|
+
height: pageDisplaySize.contentHeight
|
|
3355
|
+
},
|
|
3356
|
+
children: [
|
|
3357
|
+
/* @__PURE__ */ jsx(
|
|
3358
|
+
"img",
|
|
3359
|
+
{
|
|
3360
|
+
src: currentPage.imageUrl,
|
|
3361
|
+
alt: `Page ${currentPage.pageNumber}`,
|
|
3362
|
+
style: {
|
|
3363
|
+
width: "100%",
|
|
3364
|
+
height: "100%",
|
|
3365
|
+
objectFit: "contain"
|
|
3366
|
+
},
|
|
3367
|
+
className: "absolute inset-0 pointer-events-none",
|
|
3368
|
+
draggable: false
|
|
3369
|
+
}
|
|
3370
|
+
),
|
|
3371
|
+
pageFields.map((field) => /* @__PURE__ */ jsx(
|
|
3372
|
+
FieldOverlay,
|
|
3373
|
+
{
|
|
3374
|
+
field,
|
|
3375
|
+
renderScale: pageDisplaySize.renderScale,
|
|
3376
|
+
pageHeight: currentPage.height
|
|
3377
|
+
},
|
|
3378
|
+
field.fieldId
|
|
3379
|
+
))
|
|
3380
|
+
]
|
|
3381
|
+
}
|
|
3382
|
+
)
|
|
3383
|
+
}
|
|
3384
|
+
)
|
|
3385
|
+
]
|
|
3386
|
+
}
|
|
3387
|
+
)
|
|
3388
|
+
] });
|
|
3389
|
+
}
|
|
3390
|
+
|
|
3391
|
+
// src/utils/markdown-validator.ts
|
|
3392
|
+
function validateMarkdown(markdown) {
|
|
3393
|
+
const errors = [];
|
|
3394
|
+
const warnings = [];
|
|
3395
|
+
if (!markdown || !markdown.trim()) {
|
|
3396
|
+
return { valid: false, errors: ["Content is empty"], warnings: [], variables: [], fields: [] };
|
|
3397
|
+
}
|
|
3398
|
+
const lines = markdown.split("\n");
|
|
3399
|
+
for (let i = 0; i < lines.length; i++) {
|
|
3400
|
+
const line = lines[i];
|
|
3401
|
+
const lineNum = i + 1;
|
|
3402
|
+
let searchFrom = 0;
|
|
3403
|
+
while (true) {
|
|
3404
|
+
const openIdx = line.indexOf("{{", searchFrom);
|
|
3405
|
+
if (openIdx === -1) break;
|
|
3406
|
+
const closeIdx = line.indexOf("}}", openIdx + 2);
|
|
3407
|
+
if (closeIdx === -1) {
|
|
3408
|
+
errors.push(`Unclosed token at line ${lineNum}: missing }}`);
|
|
3409
|
+
break;
|
|
3410
|
+
}
|
|
3411
|
+
const tokenContent = line.slice(openIdx + 2, closeIdx);
|
|
3412
|
+
const pipeIdx = tokenContent.indexOf("|");
|
|
3413
|
+
if (pipeIdx === -1) {
|
|
3414
|
+
errors.push(`Malformed token at line ${lineNum}: missing | delimiter`);
|
|
3415
|
+
} else {
|
|
3416
|
+
const tokenType = tokenContent.slice(0, pipeIdx);
|
|
3417
|
+
if (tokenType !== "field" && tokenType !== "var") {
|
|
3418
|
+
errors.push(`Unknown token type "${tokenType}" at line ${lineNum} (expected "field" or "var")`);
|
|
3419
|
+
} else {
|
|
3420
|
+
const body = tokenContent.slice(pipeIdx + 1);
|
|
3421
|
+
const pairs = body.split("|");
|
|
3422
|
+
if (tokenType === "field") {
|
|
3423
|
+
const hasType = pairs.some((p) => p.startsWith("type:"));
|
|
3424
|
+
if (!hasType) {
|
|
3425
|
+
errors.push(`Field token at line ${lineNum} is missing required "type" attribute`);
|
|
3426
|
+
}
|
|
3427
|
+
} else if (tokenType === "var") {
|
|
3428
|
+
const hasName = pairs.some((p) => p.startsWith("name:"));
|
|
3429
|
+
if (!hasName) {
|
|
3430
|
+
errors.push(`Variable token at line ${lineNum} is missing required "name" attribute`);
|
|
3431
|
+
}
|
|
3432
|
+
}
|
|
3433
|
+
}
|
|
3434
|
+
}
|
|
3435
|
+
searchFrom = closeIdx + 2;
|
|
3436
|
+
}
|
|
3437
|
+
}
|
|
3438
|
+
let fields = [];
|
|
3439
|
+
let variables = [];
|
|
3440
|
+
try {
|
|
3441
|
+
const content = markdownToTiptap(markdown);
|
|
3442
|
+
fields = extractFieldsFromContent(content);
|
|
3443
|
+
variables = extractVariablesFromContent(content);
|
|
3444
|
+
} catch (e) {
|
|
3445
|
+
errors.push(`Failed to parse markdown: ${e.message}`);
|
|
3446
|
+
}
|
|
3447
|
+
const varMap = /* @__PURE__ */ new Map();
|
|
3448
|
+
for (const v of variables) {
|
|
3449
|
+
const existing = varMap.get(v.varName);
|
|
3450
|
+
if (existing) {
|
|
3451
|
+
if (existing.varLabel !== v.varLabel || existing.varDefault !== v.varDefault) {
|
|
3452
|
+
warnings.push(`Variable "${v.varName}" has conflicting labels or defaults`);
|
|
3453
|
+
}
|
|
3454
|
+
} else {
|
|
3455
|
+
varMap.set(v.varName, v);
|
|
3456
|
+
}
|
|
3457
|
+
}
|
|
3458
|
+
for (const v of variables) {
|
|
3459
|
+
if (!v.varDefault) {
|
|
3460
|
+
warnings.push(`Variable "${v.varName}" has no default value`);
|
|
3461
|
+
}
|
|
3462
|
+
}
|
|
3463
|
+
return {
|
|
3464
|
+
valid: errors.length === 0,
|
|
3465
|
+
errors,
|
|
3466
|
+
warnings,
|
|
3467
|
+
variables,
|
|
3468
|
+
fields
|
|
3469
|
+
};
|
|
3470
|
+
}
|
|
3471
|
+
var sizeClasses = {
|
|
3472
|
+
md: "px-3 py-1.5 text-xs font-medium",
|
|
3473
|
+
sm: "px-2 py-0.5 text-[10px] font-medium"
|
|
3474
|
+
};
|
|
3475
|
+
function ToggleGroup({
|
|
3476
|
+
value,
|
|
3477
|
+
onChange,
|
|
3478
|
+
options,
|
|
3479
|
+
size = "md",
|
|
3480
|
+
className
|
|
3481
|
+
}) {
|
|
3482
|
+
return /* @__PURE__ */ jsx("div", { className: cn("inline-flex items-center gap-1 bg-muted/50 p-0.5 rounded-lg", className), children: options.map((opt) => /* @__PURE__ */ jsx(
|
|
3483
|
+
"button",
|
|
3484
|
+
{
|
|
3485
|
+
className: cn(
|
|
3486
|
+
"rounded-md transition-colors",
|
|
3487
|
+
sizeClasses[size],
|
|
3488
|
+
value === opt.value ? "bg-background text-foreground shadow-sm" : "text-muted-foreground hover:text-foreground"
|
|
3489
|
+
),
|
|
3490
|
+
onClick: () => onChange(opt.value),
|
|
3491
|
+
children: opt.label
|
|
3492
|
+
},
|
|
3493
|
+
opt.value
|
|
3494
|
+
)) });
|
|
3495
|
+
}
|
|
3496
|
+
function parseCsv(text) {
|
|
3497
|
+
const lines = text.split(/\r?\n/).filter((l) => l.trim() !== "");
|
|
3498
|
+
if (lines.length < 2) return [];
|
|
3499
|
+
function parseLine(line) {
|
|
3500
|
+
const fields = [];
|
|
3501
|
+
let current = "";
|
|
3502
|
+
let inQuotes = false;
|
|
3503
|
+
for (let i = 0; i < line.length; i++) {
|
|
3504
|
+
const ch = line[i];
|
|
3505
|
+
if (inQuotes) {
|
|
3506
|
+
if (ch === '"' && line[i + 1] === '"') {
|
|
3507
|
+
current += '"';
|
|
3508
|
+
i++;
|
|
3509
|
+
} else if (ch === '"') {
|
|
3510
|
+
inQuotes = false;
|
|
3511
|
+
} else {
|
|
3512
|
+
current += ch;
|
|
3513
|
+
}
|
|
3514
|
+
} else {
|
|
3515
|
+
if (ch === '"') {
|
|
3516
|
+
inQuotes = true;
|
|
3517
|
+
} else if (ch === ",") {
|
|
3518
|
+
fields.push(current.trim());
|
|
3519
|
+
current = "";
|
|
3520
|
+
} else {
|
|
3521
|
+
current += ch;
|
|
3522
|
+
}
|
|
3523
|
+
}
|
|
3524
|
+
}
|
|
3525
|
+
fields.push(current.trim());
|
|
3526
|
+
return fields;
|
|
3527
|
+
}
|
|
3528
|
+
const headers = parseLine(lines[0]);
|
|
3529
|
+
const rows = [];
|
|
3530
|
+
for (let i = 1; i < lines.length; i++) {
|
|
3531
|
+
const values = parseLine(lines[i]);
|
|
3532
|
+
const row = {};
|
|
3533
|
+
for (let j = 0; j < headers.length; j++) {
|
|
3534
|
+
row[headers[j]] = values[j] || "";
|
|
3535
|
+
}
|
|
3536
|
+
rows.push(row);
|
|
3537
|
+
}
|
|
3538
|
+
return rows;
|
|
3539
|
+
}
|
|
3540
|
+
function GeneratePanel({
|
|
3541
|
+
editorMarkdown,
|
|
3542
|
+
editorContent,
|
|
3543
|
+
editorVariables,
|
|
3544
|
+
onGeneratePdf,
|
|
3545
|
+
initialBulkData
|
|
3546
|
+
}) {
|
|
3547
|
+
const [templateSource, setTemplateSource] = useState("editor");
|
|
3548
|
+
const [importedMarkdown, setImportedMarkdown] = useState(null);
|
|
3549
|
+
const [importedFileName, setImportedFileName] = useState(null);
|
|
3550
|
+
const [validationResult, setValidationResult] = useState(null);
|
|
3551
|
+
const [mode, setMode] = useState("single");
|
|
3552
|
+
const [variableValues, setVariableValues] = useState({});
|
|
3553
|
+
const [bulkInputFormat, setBulkInputFormat] = useState("json");
|
|
3554
|
+
const [bulkInput, setBulkInput] = useState("");
|
|
3555
|
+
const [bulkData, setBulkData] = useState(null);
|
|
3556
|
+
const [bulkError, setBulkError] = useState(null);
|
|
3557
|
+
const [csvFileName, setCsvFileName] = useState(null);
|
|
3558
|
+
const [previewPages, setPreviewPages] = useState([]);
|
|
3559
|
+
const [previewFields, setPreviewFields] = useState([]);
|
|
3560
|
+
const [isGenerating, setIsGenerating] = useState(false);
|
|
3561
|
+
const [isExporting, setIsExporting] = useState(false);
|
|
3562
|
+
const [previewFresh, setPreviewFresh] = useState(false);
|
|
3563
|
+
const [previewError, setPreviewError] = useState(null);
|
|
3564
|
+
const [exportError, setExportError] = useState(null);
|
|
3565
|
+
const [exportSuccess, setExportSuccess] = useState(null);
|
|
3566
|
+
const [jsonCursor, setJsonCursor] = useState({ line: 1, col: 1 });
|
|
3567
|
+
const [isDragOver, setIsDragOver] = useState(false);
|
|
3568
|
+
const [isCsvDragOver, setIsCsvDragOver] = useState(false);
|
|
3569
|
+
const fileInputRef = useRef(null);
|
|
3570
|
+
const csvInputRef = useRef(null);
|
|
3571
|
+
const jsonGutterRef = useRef(null);
|
|
3572
|
+
const updateJsonCursor = useCallback((el) => {
|
|
3573
|
+
const pos = el.selectionStart ?? 0;
|
|
3574
|
+
const textBefore = el.value.slice(0, pos);
|
|
3575
|
+
const line = (textBefore.match(/\n/g) || []).length + 1;
|
|
3576
|
+
const col = pos - textBefore.lastIndexOf("\n");
|
|
3577
|
+
setJsonCursor({ line, col });
|
|
3578
|
+
}, []);
|
|
3579
|
+
const activeVariables = templateSource === "imported" && validationResult?.valid ? validationResult.variables : editorVariables;
|
|
3580
|
+
const hasVariables = activeVariables.length > 0;
|
|
3581
|
+
const hasEditorContent = editorMarkdown.trim().length > 0;
|
|
3582
|
+
const hasImportedContent = templateSource === "imported" && importedMarkdown != null;
|
|
3583
|
+
const hasContent = hasImportedContent || hasEditorContent;
|
|
3584
|
+
const getActiveContent = useCallback(() => {
|
|
3585
|
+
if (templateSource === "imported" && importedMarkdown) {
|
|
3586
|
+
return markdownToTiptap(importedMarkdown);
|
|
3587
|
+
}
|
|
3588
|
+
if (editorContent) {
|
|
3589
|
+
return editorContent;
|
|
3590
|
+
}
|
|
3591
|
+
return markdownToTiptap(editorMarkdown);
|
|
3592
|
+
}, [templateSource, importedMarkdown, editorContent, editorMarkdown]);
|
|
3593
|
+
const handleFileImport = useCallback((file) => {
|
|
3594
|
+
if (!file.name.endsWith(".md")) {
|
|
3595
|
+
setValidationResult({
|
|
3596
|
+
valid: false,
|
|
3597
|
+
errors: ["Only .md files are supported"],
|
|
3598
|
+
warnings: [],
|
|
3599
|
+
variables: [],
|
|
3600
|
+
fields: []
|
|
3601
|
+
});
|
|
3602
|
+
return;
|
|
3603
|
+
}
|
|
3604
|
+
const reader = new FileReader();
|
|
3605
|
+
reader.onload = (e) => {
|
|
3606
|
+
const text = e.target?.result;
|
|
3607
|
+
const result = validateMarkdown(text);
|
|
3608
|
+
setValidationResult(result);
|
|
3609
|
+
if (result.valid) {
|
|
3610
|
+
setImportedMarkdown(text);
|
|
3611
|
+
setImportedFileName(file.name);
|
|
3612
|
+
setTemplateSource("imported");
|
|
3613
|
+
const defaults = {};
|
|
3614
|
+
for (const v of result.variables) {
|
|
3615
|
+
defaults[v.varName] = v.varDefault || "";
|
|
3616
|
+
}
|
|
3617
|
+
setVariableValues(defaults);
|
|
3618
|
+
setPreviewPages([]);
|
|
3619
|
+
}
|
|
3620
|
+
};
|
|
3621
|
+
reader.readAsText(file);
|
|
3622
|
+
}, []);
|
|
3623
|
+
const handleCsvImport = useCallback(
|
|
3624
|
+
(file) => {
|
|
3625
|
+
if (!file.name.endsWith(".csv")) {
|
|
3626
|
+
setBulkError("Only .csv files are supported");
|
|
3627
|
+
return;
|
|
3628
|
+
}
|
|
3629
|
+
const reader = new FileReader();
|
|
3630
|
+
reader.onload = (e) => {
|
|
3631
|
+
const text = e.target?.result;
|
|
3632
|
+
try {
|
|
3633
|
+
const rows = parseCsv(text);
|
|
3634
|
+
if (rows.length === 0) {
|
|
3635
|
+
setBulkError("CSV is empty or has no data rows");
|
|
3636
|
+
setBulkData(null);
|
|
3637
|
+
setCsvFileName(null);
|
|
3638
|
+
return;
|
|
3639
|
+
}
|
|
3640
|
+
const csvHeaders = Object.keys(rows[0]);
|
|
3641
|
+
const varNames = activeVariables.map((v) => v.varName);
|
|
3642
|
+
const matchingHeaders = csvHeaders.filter((h) => varNames.includes(h));
|
|
3643
|
+
if (matchingHeaders.length === 0) {
|
|
3644
|
+
setBulkError(
|
|
3645
|
+
`No CSV headers match variable names. Expected: ${varNames.join(", ")}. Got: ${csvHeaders.join(", ")}`
|
|
3646
|
+
);
|
|
3647
|
+
setBulkData(null);
|
|
3648
|
+
setCsvFileName(null);
|
|
3649
|
+
return;
|
|
3650
|
+
}
|
|
3651
|
+
setBulkData(rows);
|
|
3652
|
+
setBulkError(null);
|
|
3653
|
+
setCsvFileName(file.name);
|
|
3654
|
+
} catch (err) {
|
|
3655
|
+
setBulkError(`Failed to parse CSV file: ${getErrorMessage(err)}`);
|
|
3656
|
+
setBulkData(null);
|
|
3657
|
+
setCsvFileName(null);
|
|
3658
|
+
}
|
|
3659
|
+
};
|
|
3660
|
+
reader.readAsText(file);
|
|
3661
|
+
},
|
|
3662
|
+
[activeVariables]
|
|
3663
|
+
);
|
|
3664
|
+
const handleDragOver = useCallback((e) => {
|
|
3665
|
+
e.preventDefault();
|
|
3666
|
+
e.stopPropagation();
|
|
3667
|
+
setIsDragOver(true);
|
|
3668
|
+
}, []);
|
|
3669
|
+
const handleDragLeave = useCallback((e) => {
|
|
3670
|
+
e.preventDefault();
|
|
3671
|
+
e.stopPropagation();
|
|
3672
|
+
setIsDragOver(false);
|
|
3673
|
+
}, []);
|
|
3674
|
+
const handleDrop = useCallback(
|
|
3675
|
+
(e) => {
|
|
3676
|
+
e.preventDefault();
|
|
3677
|
+
e.stopPropagation();
|
|
3678
|
+
setIsDragOver(false);
|
|
3679
|
+
const files = e.dataTransfer.files;
|
|
3680
|
+
if (files.length > 0) {
|
|
3681
|
+
handleFileImport(files[0]);
|
|
3682
|
+
}
|
|
3683
|
+
},
|
|
3684
|
+
[handleFileImport]
|
|
3685
|
+
);
|
|
3686
|
+
const handleCsvDragOver = useCallback((e) => {
|
|
3687
|
+
e.preventDefault();
|
|
3688
|
+
e.stopPropagation();
|
|
3689
|
+
setIsCsvDragOver(true);
|
|
3690
|
+
}, []);
|
|
3691
|
+
const handleCsvDragLeave = useCallback((e) => {
|
|
3692
|
+
e.preventDefault();
|
|
3693
|
+
e.stopPropagation();
|
|
3694
|
+
setIsCsvDragOver(false);
|
|
3695
|
+
}, []);
|
|
3696
|
+
const handleCsvDrop = useCallback(
|
|
3697
|
+
(e) => {
|
|
3698
|
+
e.preventDefault();
|
|
3699
|
+
e.stopPropagation();
|
|
3700
|
+
setIsCsvDragOver(false);
|
|
3701
|
+
const files = e.dataTransfer.files;
|
|
3702
|
+
if (files.length > 0) {
|
|
3703
|
+
handleCsvImport(files[0]);
|
|
3704
|
+
}
|
|
3705
|
+
},
|
|
3706
|
+
[handleCsvImport]
|
|
3707
|
+
);
|
|
3708
|
+
const clearImport = useCallback(() => {
|
|
3709
|
+
setTemplateSource("editor");
|
|
3710
|
+
setImportedMarkdown(null);
|
|
3711
|
+
setImportedFileName(null);
|
|
3712
|
+
setValidationResult(null);
|
|
3713
|
+
setPreviewPages([]);
|
|
3714
|
+
const defaults = {};
|
|
3715
|
+
for (const v of editorVariables) {
|
|
3716
|
+
defaults[v.varName] = v.varDefault || "";
|
|
3717
|
+
}
|
|
3718
|
+
setVariableValues(defaults);
|
|
3719
|
+
}, [editorVariables]);
|
|
3720
|
+
const updateVariableValue = useCallback((varName, value) => {
|
|
3721
|
+
setVariableValues((prev) => ({ ...prev, [varName]: value }));
|
|
3722
|
+
}, []);
|
|
3723
|
+
const buildPositionedFields = useCallback(
|
|
3724
|
+
(fields, positions) => fields.map((f) => {
|
|
3725
|
+
const pos = positions.get(f.fieldId);
|
|
3726
|
+
return {
|
|
3727
|
+
id: f.name,
|
|
3728
|
+
fieldId: f.fieldId,
|
|
3729
|
+
type: f.type,
|
|
3730
|
+
name: f.name,
|
|
3731
|
+
label: f.label,
|
|
3732
|
+
position: pos || { x: 0, y: 0, width: 0, height: 0, page: 1 },
|
|
3733
|
+
required: f.required,
|
|
3734
|
+
options: f.options,
|
|
3735
|
+
placeholder: f.placeholder,
|
|
3736
|
+
fontSize: f.fontSize,
|
|
3737
|
+
defaultValue: f.defaultValue,
|
|
3738
|
+
multiline: f.multiline,
|
|
3739
|
+
maxLength: f.maxLength,
|
|
3740
|
+
acknowledgements: f.acknowledgements
|
|
3741
|
+
};
|
|
3742
|
+
}),
|
|
3743
|
+
[]
|
|
3744
|
+
);
|
|
3745
|
+
const handlePreview = useCallback(async () => {
|
|
3746
|
+
if (!hasContent) return;
|
|
3747
|
+
setIsGenerating(true);
|
|
3748
|
+
setPreviewError(null);
|
|
3749
|
+
try {
|
|
3750
|
+
const content = getActiveContent();
|
|
3751
|
+
const replaced = replaceVariablesInContent(content, variableValues);
|
|
3752
|
+
const fields = extractFieldsFromContent(replaced);
|
|
3753
|
+
const result = await generatePdfFromContent(replaced);
|
|
3754
|
+
const pages = await pdfToImages(result.pdfBytes);
|
|
3755
|
+
setPreviewPages(pages);
|
|
3756
|
+
setPreviewFields(buildPositionedFields(fields, result.fieldPositions));
|
|
3757
|
+
setPreviewFresh(true);
|
|
3758
|
+
} catch (err) {
|
|
3759
|
+
console.error("Preview generation failed:", err);
|
|
3760
|
+
setPreviewError(formatError("Preview generation failed", err));
|
|
3761
|
+
} finally {
|
|
3762
|
+
setIsGenerating(false);
|
|
3763
|
+
}
|
|
3764
|
+
}, [getActiveContent, variableValues, hasContent, buildPositionedFields]);
|
|
3765
|
+
const handleExportSingle = useCallback(async () => {
|
|
3766
|
+
if (!hasContent) return;
|
|
3767
|
+
setIsExporting(true);
|
|
3768
|
+
setExportError(null);
|
|
3769
|
+
setExportSuccess(null);
|
|
3770
|
+
try {
|
|
3771
|
+
const content = getActiveContent();
|
|
3772
|
+
const replaced = replaceVariablesInContent(content, variableValues);
|
|
3773
|
+
const fields = extractFieldsFromContent(replaced);
|
|
3774
|
+
const result = await generatePdfFromContent(replaced, {
|
|
3775
|
+
drawFieldPlaceholders: false,
|
|
3776
|
+
embedFormFields: true,
|
|
3777
|
+
fields
|
|
3778
|
+
});
|
|
3779
|
+
const blob = new Blob([result.pdfBytes], { type: "application/pdf" });
|
|
3780
|
+
const fileName = importedFileName ? importedFileName.replace(".md", ".pdf") : "document.pdf";
|
|
3781
|
+
if (onGeneratePdf) {
|
|
3782
|
+
onGeneratePdf(blob, fileName);
|
|
3783
|
+
} else {
|
|
3784
|
+
const url = URL.createObjectURL(blob);
|
|
3785
|
+
const a = document.createElement("a");
|
|
3786
|
+
a.href = url;
|
|
3787
|
+
a.download = fileName;
|
|
3788
|
+
a.click();
|
|
3789
|
+
URL.revokeObjectURL(url);
|
|
3790
|
+
}
|
|
3791
|
+
setExportSuccess(`Exported ${fileName} successfully`);
|
|
3792
|
+
if (result.fieldWarnings && result.fieldWarnings.length > 0) {
|
|
3793
|
+
setExportError(`PDF exported with warnings: ${result.fieldWarnings.join("; ")}`);
|
|
3794
|
+
}
|
|
3795
|
+
} catch (err) {
|
|
3796
|
+
console.error("Export failed:", err);
|
|
3797
|
+
setExportError(formatError("PDF export failed", err));
|
|
3798
|
+
} finally {
|
|
3799
|
+
setIsExporting(false);
|
|
3800
|
+
}
|
|
3801
|
+
}, [getActiveContent, variableValues, hasContent, onGeneratePdf, importedFileName]);
|
|
3802
|
+
const parseBulkInput = useCallback(() => {
|
|
3803
|
+
const result = parseJsonWithDetails(bulkInput);
|
|
3804
|
+
if (!result.valid) {
|
|
3805
|
+
setBulkError(result.message);
|
|
3806
|
+
setBulkData(null);
|
|
3807
|
+
return;
|
|
3808
|
+
}
|
|
3809
|
+
const parsed = result.data;
|
|
3810
|
+
if (!Array.isArray(parsed)) {
|
|
3811
|
+
setBulkError("Input must be a JSON array (wrap objects in [ ])");
|
|
3812
|
+
setBulkData(null);
|
|
3813
|
+
return;
|
|
3814
|
+
}
|
|
3815
|
+
if (parsed.length === 0) {
|
|
3816
|
+
setBulkError("Array is empty \u2014 add at least one object");
|
|
3817
|
+
setBulkData(null);
|
|
3818
|
+
return;
|
|
3819
|
+
}
|
|
3820
|
+
for (let i = 0; i < parsed.length; i++) {
|
|
3821
|
+
if (typeof parsed[i] !== "object" || parsed[i] === null) {
|
|
3822
|
+
setBulkError(`Entry ${i + 1} is not a valid object \u2014 each array element must be a { } object`);
|
|
3823
|
+
setBulkData(null);
|
|
3824
|
+
return;
|
|
3825
|
+
}
|
|
3826
|
+
}
|
|
3827
|
+
setBulkData(parsed);
|
|
3828
|
+
setBulkError(null);
|
|
3829
|
+
}, [bulkInput]);
|
|
3830
|
+
const handleFormatJson = useCallback(() => {
|
|
3831
|
+
const result = parseJsonWithDetails(bulkInput);
|
|
3832
|
+
if (result.valid) {
|
|
3833
|
+
setBulkInput(JSON.stringify(result.data, null, 2));
|
|
3834
|
+
setBulkError(null);
|
|
3835
|
+
} else {
|
|
3836
|
+
setBulkError(result.message);
|
|
3837
|
+
}
|
|
3838
|
+
}, [bulkInput]);
|
|
3839
|
+
const handleBulkGenerate = useCallback(async () => {
|
|
3840
|
+
if (!bulkData || !hasContent) return;
|
|
3841
|
+
setIsExporting(true);
|
|
3842
|
+
setBulkError(null);
|
|
3843
|
+
setExportSuccess(null);
|
|
3844
|
+
try {
|
|
3845
|
+
const JSZip = (await import('jszip')).default;
|
|
3846
|
+
const zip = new JSZip();
|
|
3847
|
+
const content = getActiveContent();
|
|
3848
|
+
const baseFields = extractFieldsFromContent(content);
|
|
3849
|
+
const allWarnings = [];
|
|
3850
|
+
for (let i = 0; i < bulkData.length; i++) {
|
|
3851
|
+
const values = bulkData[i];
|
|
3852
|
+
const replaced = replaceVariablesInContent(content, values);
|
|
3853
|
+
const fields = extractFieldsFromContent(replaced);
|
|
3854
|
+
const result = await generatePdfFromContent(replaced, {
|
|
3855
|
+
drawFieldPlaceholders: false,
|
|
3856
|
+
embedFormFields: true,
|
|
3857
|
+
fields: fields.length > 0 ? fields : baseFields
|
|
3858
|
+
});
|
|
3859
|
+
if (result.fieldWarnings) {
|
|
3860
|
+
allWarnings.push(`Document ${i + 1}: ${result.fieldWarnings.join(", ")}`);
|
|
3861
|
+
}
|
|
3862
|
+
const fileName = `document_${i + 1}.pdf`;
|
|
3863
|
+
zip.file(fileName, result.pdfBytes);
|
|
3864
|
+
}
|
|
3865
|
+
const zipBlob = await zip.generateAsync({ type: "blob" });
|
|
3866
|
+
if (onGeneratePdf) {
|
|
3867
|
+
onGeneratePdf(zipBlob, "documents.zip");
|
|
3868
|
+
} else {
|
|
3869
|
+
const url = URL.createObjectURL(zipBlob);
|
|
3870
|
+
const a = document.createElement("a");
|
|
3871
|
+
a.href = url;
|
|
3872
|
+
a.download = "documents.zip";
|
|
3873
|
+
a.click();
|
|
3874
|
+
URL.revokeObjectURL(url);
|
|
3875
|
+
}
|
|
3876
|
+
const count = bulkData.length;
|
|
3877
|
+
setExportSuccess(`Generated ${count} PDF${count !== 1 ? "s" : ""} successfully`);
|
|
3878
|
+
if (allWarnings.length > 0) {
|
|
3879
|
+
setBulkError(`Generated with warnings:
|
|
3880
|
+
${allWarnings.join("\n")}`);
|
|
3881
|
+
}
|
|
3882
|
+
} catch (err) {
|
|
3883
|
+
console.error("Bulk generation failed:", err);
|
|
3884
|
+
setBulkError(formatError("Bulk generation failed", err));
|
|
3885
|
+
} finally {
|
|
3886
|
+
setIsExporting(false);
|
|
3887
|
+
}
|
|
3888
|
+
}, [bulkData, getActiveContent, hasContent, onGeneratePdf]);
|
|
3889
|
+
const initialBulkJson = React12__default.useMemo(
|
|
3890
|
+
() => initialBulkData && initialBulkData.length > 0 ? JSON.stringify(initialBulkData, null, 2) : "",
|
|
3891
|
+
[initialBulkData]
|
|
3892
|
+
);
|
|
3893
|
+
const switchBulkFormat = useCallback((format) => {
|
|
3894
|
+
setBulkInputFormat(format);
|
|
3895
|
+
setBulkData(null);
|
|
3896
|
+
setBulkError(null);
|
|
3897
|
+
setCsvFileName(null);
|
|
3898
|
+
setBulkInput(format === "json" ? initialBulkJson : "");
|
|
3899
|
+
}, [initialBulkJson]);
|
|
3900
|
+
useEffect(() => {
|
|
3901
|
+
if (mode === "bulk" && bulkInputFormat === "json" && !bulkInput && initialBulkJson) {
|
|
3902
|
+
setBulkInput(initialBulkJson);
|
|
3903
|
+
}
|
|
3904
|
+
}, [mode, bulkInputFormat, bulkInput, initialBulkJson]);
|
|
3905
|
+
React12__default.useEffect(() => {
|
|
3906
|
+
if (templateSource === "editor") {
|
|
3907
|
+
const defaults = {};
|
|
3908
|
+
for (const v of editorVariables) {
|
|
3909
|
+
defaults[v.varName] = variableValues[v.varName] || v.varDefault || "";
|
|
3910
|
+
}
|
|
3911
|
+
setVariableValues(defaults);
|
|
3912
|
+
}
|
|
3913
|
+
}, [editorVariables, templateSource]);
|
|
3914
|
+
useEffect(() => {
|
|
3915
|
+
setPreviewFresh(false);
|
|
3916
|
+
}, [variableValues, templateSource, importedMarkdown, editorMarkdown, editorContent]);
|
|
3917
|
+
useEffect(() => {
|
|
3918
|
+
if (exportSuccess) {
|
|
3919
|
+
const timer = setTimeout(() => setExportSuccess(null), 3e3);
|
|
3920
|
+
return () => clearTimeout(timer);
|
|
3921
|
+
}
|
|
3922
|
+
}, [exportSuccess]);
|
|
3923
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex-1 min-h-0 grid grid-cols-[1fr_1px_1fr]", children: [
|
|
3924
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-col min-h-0 min-w-0", children: [
|
|
3925
|
+
/* @__PURE__ */ jsxs("div", { className: "flex-1 overflow-y-auto p-4 space-y-4", children: [
|
|
3926
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
3927
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between mb-2", children: [
|
|
3928
|
+
/* @__PURE__ */ jsx("h3", { className: "text-sm font-medium", children: "Template" }),
|
|
3929
|
+
templateSource === "imported" && /* @__PURE__ */ jsxs(
|
|
3930
|
+
Button,
|
|
3931
|
+
{
|
|
3932
|
+
variant: "ghost",
|
|
3933
|
+
size: "sm",
|
|
3934
|
+
className: "h-7 text-xs",
|
|
3935
|
+
onClick: clearImport,
|
|
3936
|
+
children: [
|
|
3937
|
+
/* @__PURE__ */ jsx(X, { size: 12, className: "mr-1" }),
|
|
3938
|
+
"Clear"
|
|
3939
|
+
]
|
|
3940
|
+
}
|
|
3941
|
+
)
|
|
3942
|
+
] }),
|
|
3943
|
+
templateSource === "editor" ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
3944
|
+
hasEditorContent && /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-2.5 p-3 rounded-lg bg-muted/40 mb-2", children: [
|
|
3945
|
+
/* @__PURE__ */ jsx(PenLine, { size: 14, className: "text-muted-foreground shrink-0 mt-0.5" }),
|
|
3946
|
+
/* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0 text-xs text-muted-foreground", children: [
|
|
3947
|
+
/* @__PURE__ */ jsxs("p", { children: [
|
|
3948
|
+
"Generating from the ",
|
|
3949
|
+
/* @__PURE__ */ jsx("span", { className: "font-medium text-foreground", children: "Editor tab" }),
|
|
3950
|
+
" content.",
|
|
3951
|
+
editorVariables.length > 0 ? ` ${editorVariables.length} variable${editorVariables.length !== 1 ? "s" : ""} detected.` : " No variables detected."
|
|
3952
|
+
] }),
|
|
3953
|
+
/* @__PURE__ */ jsx("p", { className: "mt-0.5", children: "You can also import a different .md template below." })
|
|
3954
|
+
] })
|
|
3955
|
+
] }),
|
|
3956
|
+
/* @__PURE__ */ jsxs(
|
|
3957
|
+
"div",
|
|
3958
|
+
{
|
|
3959
|
+
className: cn(
|
|
3960
|
+
"border-2 border-dashed rounded-lg p-5 text-center transition-colors cursor-pointer border-border hover:border-muted-foreground/50",
|
|
3961
|
+
isDragOver && "border-muted-foreground/50 bg-muted/30"
|
|
3962
|
+
),
|
|
3963
|
+
onDragOver: handleDragOver,
|
|
3964
|
+
onDragLeave: handleDragLeave,
|
|
3965
|
+
onDrop: handleDrop,
|
|
3966
|
+
onClick: () => fileInputRef.current?.click(),
|
|
3967
|
+
children: [
|
|
3968
|
+
/* @__PURE__ */ jsx(
|
|
3969
|
+
Upload,
|
|
3970
|
+
{
|
|
3971
|
+
size: 20,
|
|
3972
|
+
className: "mx-auto mb-1.5 text-muted-foreground"
|
|
3973
|
+
}
|
|
3974
|
+
),
|
|
3975
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: hasEditorContent ? "Import a different .md template" : "Drop .md file here or click to browse" }),
|
|
3976
|
+
!hasEditorContent && /* @__PURE__ */ jsx("p", { className: "text-[10px] text-muted-foreground/70 mt-1", children: "Or add content in the Editor tab" }),
|
|
3977
|
+
/* @__PURE__ */ jsx(
|
|
3978
|
+
"input",
|
|
3979
|
+
{
|
|
3980
|
+
ref: fileInputRef,
|
|
3981
|
+
type: "file",
|
|
3982
|
+
accept: ".md",
|
|
3983
|
+
className: "hidden",
|
|
3984
|
+
onChange: (e) => {
|
|
3985
|
+
const file = e.target.files?.[0];
|
|
3986
|
+
if (file) handleFileImport(file);
|
|
3987
|
+
e.target.value = "";
|
|
3988
|
+
}
|
|
3989
|
+
}
|
|
3990
|
+
)
|
|
3991
|
+
]
|
|
3992
|
+
}
|
|
3993
|
+
)
|
|
3994
|
+
] }) : /* @__PURE__ */ jsx("div", { className: "border border-border rounded-lg p-3 bg-muted/20", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
3995
|
+
/* @__PURE__ */ jsx(
|
|
3996
|
+
FileText,
|
|
3997
|
+
{
|
|
3998
|
+
size: 16,
|
|
3999
|
+
className: "text-muted-foreground shrink-0"
|
|
4000
|
+
}
|
|
4001
|
+
),
|
|
4002
|
+
/* @__PURE__ */ jsx("span", { className: "text-sm font-medium truncate", children: importedFileName })
|
|
4003
|
+
] }) }),
|
|
4004
|
+
validationResult && !validationResult.valid && /* @__PURE__ */ jsx("div", { className: "mt-2 p-2 rounded-md bg-destructive/10 border border-destructive/20", children: /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-1.5", children: [
|
|
4005
|
+
/* @__PURE__ */ jsx(
|
|
4006
|
+
AlertTriangle,
|
|
4007
|
+
{
|
|
4008
|
+
size: 14,
|
|
4009
|
+
className: "text-destructive shrink-0 mt-0.5"
|
|
4010
|
+
}
|
|
4011
|
+
),
|
|
4012
|
+
/* @__PURE__ */ jsx("div", { className: "text-xs text-destructive space-y-0.5", children: validationResult.errors.map((err, i) => /* @__PURE__ */ jsx("p", { children: err }, i)) })
|
|
4013
|
+
] }) }),
|
|
4014
|
+
validationResult?.valid && validationResult.warnings.length > 0 && /* @__PURE__ */ jsx("div", { className: "mt-2 p-2 rounded-md bg-orange-500/10 border border-orange-500/20", children: /* @__PURE__ */ jsx("div", { className: "text-xs text-orange-700 space-y-0.5", children: validationResult.warnings.map((warn, i) => /* @__PURE__ */ jsx("p", { children: warn }, i)) }) })
|
|
4015
|
+
] }),
|
|
4016
|
+
!hasContent && /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center justify-center py-8 text-muted-foreground", children: [
|
|
4017
|
+
/* @__PURE__ */ jsx(Braces, { size: 32, className: "mb-2 opacity-50" }),
|
|
4018
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm font-medium", children: "No template content" }),
|
|
4019
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs mt-1", children: "Import a .md template above or add content in the Editor tab." })
|
|
4020
|
+
] }),
|
|
4021
|
+
hasVariables && hasContent && /* @__PURE__ */ jsxs("div", { className: "border border-border rounded-lg overflow-hidden", children: [
|
|
4022
|
+
/* @__PURE__ */ jsx("div", { className: "flex items-center gap-2 px-3 py-2 bg-muted/30 border-b border-border", children: /* @__PURE__ */ jsx(
|
|
4023
|
+
ToggleGroup,
|
|
4024
|
+
{
|
|
4025
|
+
value: mode,
|
|
4026
|
+
onChange: setMode,
|
|
4027
|
+
options: [
|
|
4028
|
+
{ value: "single", label: "Single" },
|
|
4029
|
+
{ value: "bulk", label: "Bulk" }
|
|
4030
|
+
]
|
|
4031
|
+
}
|
|
4032
|
+
) }),
|
|
4033
|
+
mode === "single" && /* @__PURE__ */ jsxs("div", { className: "p-3 space-y-3", children: [
|
|
4034
|
+
/* @__PURE__ */ jsx("h3", { className: "text-xs font-medium text-muted-foreground uppercase tracking-wide", children: "Variables" }),
|
|
4035
|
+
activeVariables.map((v) => /* @__PURE__ */ jsxs("div", { children: [
|
|
4036
|
+
/* @__PURE__ */ jsx(Label, { htmlFor: `gen-${v.varName}`, className: "text-xs", children: v.varLabel || v.varName }),
|
|
4037
|
+
/* @__PURE__ */ jsx(
|
|
4038
|
+
Input,
|
|
4039
|
+
{
|
|
4040
|
+
id: `gen-${v.varName}`,
|
|
4041
|
+
value: variableValues[v.varName] || "",
|
|
4042
|
+
onChange: (e) => updateVariableValue(v.varName, e.target.value),
|
|
4043
|
+
placeholder: v.varDefault || `Enter ${v.varLabel || v.varName}`,
|
|
4044
|
+
className: "h-8 text-xs"
|
|
4045
|
+
}
|
|
4046
|
+
)
|
|
4047
|
+
] }, v.varName))
|
|
4048
|
+
] }),
|
|
4049
|
+
mode === "bulk" && /* @__PURE__ */ jsxs("div", { className: "p-3 space-y-3", children: [
|
|
4050
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
|
|
4051
|
+
/* @__PURE__ */ jsx("h3", { className: "text-xs font-medium text-muted-foreground uppercase tracking-wide", children: "Bulk Data" }),
|
|
4052
|
+
/* @__PURE__ */ jsx(
|
|
4053
|
+
ToggleGroup,
|
|
4054
|
+
{
|
|
4055
|
+
value: bulkInputFormat,
|
|
4056
|
+
onChange: switchBulkFormat,
|
|
4057
|
+
options: [
|
|
4058
|
+
{ value: "json", label: "JSON" },
|
|
4059
|
+
{ value: "csv", label: "CSV" }
|
|
4060
|
+
],
|
|
4061
|
+
size: "sm"
|
|
4062
|
+
}
|
|
4063
|
+
)
|
|
4064
|
+
] }),
|
|
4065
|
+
bulkInputFormat === "json" && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
4066
|
+
/* @__PURE__ */ jsxs(
|
|
4067
|
+
"div",
|
|
4068
|
+
{
|
|
4069
|
+
className: "rounded-md border border-input bg-background overflow-hidden focus-within:ring-2 focus-within:ring-ring",
|
|
4070
|
+
style: { resize: "vertical" },
|
|
4071
|
+
children: [
|
|
4072
|
+
/* @__PURE__ */ jsxs("div", { className: "flex h-64", style: { resize: "vertical", overflow: "hidden" }, children: [
|
|
4073
|
+
/* @__PURE__ */ jsx(
|
|
4074
|
+
"div",
|
|
4075
|
+
{
|
|
4076
|
+
ref: jsonGutterRef,
|
|
4077
|
+
"aria-hidden": true,
|
|
4078
|
+
className: "shrink-0 py-2 pl-2 pr-1.5 text-xs font-mono text-muted-foreground/50 text-right select-none overflow-hidden border-r border-input bg-muted/30",
|
|
4079
|
+
style: { lineHeight: "1.35rem" },
|
|
4080
|
+
children: Array.from(
|
|
4081
|
+
{ length: Math.max((bulkInput || "\n").split("\n").length, 1) },
|
|
4082
|
+
(_, i) => /* @__PURE__ */ jsx("div", { children: i + 1 }, i)
|
|
4083
|
+
)
|
|
4084
|
+
}
|
|
4085
|
+
),
|
|
4086
|
+
/* @__PURE__ */ jsx(
|
|
4087
|
+
"textarea",
|
|
4088
|
+
{
|
|
4089
|
+
value: bulkInput,
|
|
4090
|
+
onChange: (e) => {
|
|
4091
|
+
setBulkInput(e.target.value);
|
|
4092
|
+
updateJsonCursor(e.target);
|
|
4093
|
+
},
|
|
4094
|
+
onKeyUp: (e) => updateJsonCursor(e.target),
|
|
4095
|
+
onClick: (e) => updateJsonCursor(e.target),
|
|
4096
|
+
onScroll: (e) => {
|
|
4097
|
+
if (jsonGutterRef.current) {
|
|
4098
|
+
jsonGutterRef.current.scrollTop = e.target.scrollTop;
|
|
4099
|
+
}
|
|
4100
|
+
},
|
|
4101
|
+
placeholder: `[
|
|
4102
|
+
{ ${activeVariables.map((v) => `"${v.varName}": "..."`).join(", ")} },
|
|
4103
|
+
...
|
|
4104
|
+
]`,
|
|
4105
|
+
className: "flex-1 py-2 px-2.5 text-xs font-mono bg-transparent resize-none focus:outline-none overflow-y-auto",
|
|
4106
|
+
style: { lineHeight: "1.35rem" }
|
|
4107
|
+
}
|
|
4108
|
+
)
|
|
4109
|
+
] }),
|
|
4110
|
+
bulkInput.trim() && /* @__PURE__ */ jsx("div", { className: "flex items-center justify-end px-2 py-0.5 border-t border-input bg-muted/30", children: /* @__PURE__ */ jsxs("span", { className: "text-[10px] font-mono text-muted-foreground/60", children: [
|
|
4111
|
+
"Ln ",
|
|
4112
|
+
jsonCursor.line,
|
|
4113
|
+
", Col ",
|
|
4114
|
+
jsonCursor.col
|
|
4115
|
+
] }) })
|
|
4116
|
+
]
|
|
4117
|
+
}
|
|
4118
|
+
),
|
|
4119
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
4120
|
+
/* @__PURE__ */ jsxs(
|
|
4121
|
+
Button,
|
|
4122
|
+
{
|
|
4123
|
+
variant: "outline",
|
|
4124
|
+
size: "sm",
|
|
4125
|
+
className: "h-7 text-xs",
|
|
4126
|
+
onClick: handleFormatJson,
|
|
4127
|
+
disabled: !bulkInput.trim(),
|
|
4128
|
+
children: [
|
|
4129
|
+
/* @__PURE__ */ jsx(WrapText, { size: 12 }),
|
|
4130
|
+
"Format"
|
|
4131
|
+
]
|
|
4132
|
+
}
|
|
4133
|
+
),
|
|
4134
|
+
/* @__PURE__ */ jsx(
|
|
4135
|
+
Button,
|
|
4136
|
+
{
|
|
4137
|
+
variant: "outline",
|
|
4138
|
+
size: "sm",
|
|
4139
|
+
className: "h-7 text-xs",
|
|
4140
|
+
onClick: parseBulkInput,
|
|
4141
|
+
disabled: !bulkInput.trim(),
|
|
4142
|
+
children: "Parse JSON"
|
|
4143
|
+
}
|
|
4144
|
+
)
|
|
4145
|
+
] })
|
|
4146
|
+
] }),
|
|
4147
|
+
bulkInputFormat === "csv" && /* @__PURE__ */ jsx(Fragment, { children: !csvFileName ? /* @__PURE__ */ jsxs(
|
|
4148
|
+
"div",
|
|
4149
|
+
{
|
|
4150
|
+
className: cn(
|
|
4151
|
+
"border-2 border-dashed rounded-lg p-4 text-center cursor-pointer transition-colors border-border hover:border-muted-foreground/50",
|
|
4152
|
+
isCsvDragOver && "border-muted-foreground/50 bg-muted/30"
|
|
4153
|
+
),
|
|
4154
|
+
onClick: () => csvInputRef.current?.click(),
|
|
4155
|
+
onDragOver: handleCsvDragOver,
|
|
4156
|
+
onDragLeave: handleCsvDragLeave,
|
|
4157
|
+
onDrop: handleCsvDrop,
|
|
4158
|
+
children: [
|
|
4159
|
+
/* @__PURE__ */ jsx(Upload, { size: 18, className: "mx-auto mb-1.5 text-muted-foreground" }),
|
|
4160
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "Click to upload a .csv file" }),
|
|
4161
|
+
/* @__PURE__ */ jsxs("p", { className: "text-[10px] text-muted-foreground/70 mt-1", children: [
|
|
4162
|
+
"First row must be headers matching variable names:",
|
|
4163
|
+
" ",
|
|
4164
|
+
activeVariables.map((v) => v.varName).join(", ")
|
|
4165
|
+
] }),
|
|
4166
|
+
/* @__PURE__ */ jsx(
|
|
4167
|
+
"input",
|
|
4168
|
+
{
|
|
4169
|
+
ref: csvInputRef,
|
|
4170
|
+
type: "file",
|
|
4171
|
+
accept: ".csv",
|
|
4172
|
+
className: "hidden",
|
|
4173
|
+
onChange: (e) => {
|
|
4174
|
+
const file = e.target.files?.[0];
|
|
4175
|
+
if (file) handleCsvImport(file);
|
|
4176
|
+
e.target.value = "";
|
|
4177
|
+
}
|
|
4178
|
+
}
|
|
4179
|
+
)
|
|
4180
|
+
]
|
|
4181
|
+
}
|
|
4182
|
+
) : /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 p-2.5 border border-border rounded-lg bg-muted/20", children: [
|
|
4183
|
+
/* @__PURE__ */ jsx(FileText, { size: 14, className: "text-muted-foreground shrink-0" }),
|
|
4184
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs font-medium truncate flex-1", children: csvFileName }),
|
|
4185
|
+
/* @__PURE__ */ jsx(
|
|
4186
|
+
Button,
|
|
4187
|
+
{
|
|
4188
|
+
variant: "ghost",
|
|
4189
|
+
size: "sm",
|
|
4190
|
+
className: "h-6 w-6 p-0",
|
|
4191
|
+
onClick: () => {
|
|
4192
|
+
setCsvFileName(null);
|
|
4193
|
+
setBulkData(null);
|
|
4194
|
+
setBulkError(null);
|
|
4195
|
+
},
|
|
4196
|
+
children: /* @__PURE__ */ jsx(X, { size: 12 })
|
|
4197
|
+
}
|
|
4198
|
+
)
|
|
4199
|
+
] }) }),
|
|
4200
|
+
bulkError && /* @__PURE__ */ jsx("div", { className: "p-2 rounded-md bg-destructive/10 border border-destructive/20", children: /* @__PURE__ */ jsx("p", { className: "text-xs text-destructive", children: bulkError }) }),
|
|
4201
|
+
bulkData && /* @__PURE__ */ jsxs("div", { className: "border border-border rounded-md overflow-auto max-h-48", children: [
|
|
4202
|
+
/* @__PURE__ */ jsxs("table", { className: "w-full text-xs", children: [
|
|
4203
|
+
/* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { className: "border-b border-border bg-muted/30", children: [
|
|
4204
|
+
/* @__PURE__ */ jsx("th", { className: "px-2 py-1 text-left font-medium text-muted-foreground", children: "#" }),
|
|
4205
|
+
activeVariables.map((v) => /* @__PURE__ */ jsx(
|
|
4206
|
+
"th",
|
|
4207
|
+
{
|
|
4208
|
+
className: "px-2 py-1 text-left font-medium text-muted-foreground",
|
|
4209
|
+
children: v.varLabel || v.varName
|
|
4210
|
+
},
|
|
4211
|
+
v.varName
|
|
4212
|
+
))
|
|
4213
|
+
] }) }),
|
|
4214
|
+
/* @__PURE__ */ jsx("tbody", { children: bulkData.map((row, i) => /* @__PURE__ */ jsxs(
|
|
4215
|
+
"tr",
|
|
4216
|
+
{
|
|
4217
|
+
className: "border-b border-border last:border-b-0",
|
|
4218
|
+
children: [
|
|
4219
|
+
/* @__PURE__ */ jsx("td", { className: "px-2 py-1 text-muted-foreground", children: i + 1 }),
|
|
4220
|
+
activeVariables.map((v) => /* @__PURE__ */ jsx("td", { className: "px-2 py-1", children: row[v.varName] || /* @__PURE__ */ jsx("span", { className: "text-muted-foreground/50", children: "-" }) }, v.varName))
|
|
4221
|
+
]
|
|
4222
|
+
},
|
|
4223
|
+
i
|
|
4224
|
+
)) })
|
|
4225
|
+
] }),
|
|
4226
|
+
/* @__PURE__ */ jsxs("div", { className: "px-2 py-1 text-xs text-muted-foreground bg-muted/20 border-t border-border", children: [
|
|
4227
|
+
bulkData.length,
|
|
4228
|
+
" row",
|
|
4229
|
+
bulkData.length !== 1 ? "s" : ""
|
|
4230
|
+
] })
|
|
4231
|
+
] })
|
|
4232
|
+
] })
|
|
4233
|
+
] }),
|
|
4234
|
+
!hasVariables && hasContent && /* @__PURE__ */ jsx("div", { className: "p-3 rounded-md bg-muted/30 border border-border", children: /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: 'This template has no variables. You can still preview and export the PDF as-is, or add variables in the Editor tab using the "Insert Variable" button.' }) })
|
|
4235
|
+
] }),
|
|
4236
|
+
previewError && /* @__PURE__ */ jsx("div", { className: "px-4 pb-0", children: /* @__PURE__ */ jsx("div", { className: "p-2 rounded-md bg-destructive/10 border border-destructive/20", children: /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-1.5", children: [
|
|
4237
|
+
/* @__PURE__ */ jsx(AlertTriangle, { size: 14, className: "text-destructive shrink-0 mt-0.5" }),
|
|
4238
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs text-destructive", children: previewError })
|
|
4239
|
+
] }) }) }),
|
|
4240
|
+
exportError && /* @__PURE__ */ jsx("div", { className: "px-4 pb-0", children: /* @__PURE__ */ jsx("div", { className: "p-2 rounded-md bg-destructive/10 border border-destructive/20", children: /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-1.5", children: [
|
|
4241
|
+
/* @__PURE__ */ jsx(AlertTriangle, { size: 14, className: "text-destructive shrink-0 mt-0.5" }),
|
|
4242
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs text-destructive", children: exportError })
|
|
4243
|
+
] }) }) }),
|
|
4244
|
+
exportSuccess && /* @__PURE__ */ jsx("div", { className: "px-4 pb-0", children: /* @__PURE__ */ jsx("div", { className: "p-2 rounded-md bg-emerald-500/10 border border-emerald-500/20", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5", children: [
|
|
4245
|
+
/* @__PURE__ */ jsx(CheckCircle2, { size: 14, className: "text-emerald-600 shrink-0" }),
|
|
4246
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs text-emerald-700", children: exportSuccess })
|
|
4247
|
+
] }) }) }),
|
|
4248
|
+
hasContent && /* @__PURE__ */ jsx("div", { className: "border-t border-border px-4 py-3 flex items-center gap-2 bg-gradient-to-b from-background to-muted/20", children: mode === "single" || !hasVariables ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
4249
|
+
/* @__PURE__ */ jsxs(
|
|
4250
|
+
Button,
|
|
4251
|
+
{
|
|
4252
|
+
variant: "secondary",
|
|
4253
|
+
onClick: handlePreview,
|
|
4254
|
+
disabled: isGenerating,
|
|
4255
|
+
className: "h-10 px-4 font-semibold shadow-sm hover:shadow-md transition-all duration-200 text-sm",
|
|
4256
|
+
children: [
|
|
4257
|
+
isGenerating ? /* @__PURE__ */ jsx(Loader2, { size: 16, className: "animate-spin" }) : /* @__PURE__ */ jsx(RefreshCw, { size: 16 }),
|
|
4258
|
+
isGenerating ? "Generating..." : "Generate PDF"
|
|
4259
|
+
]
|
|
4260
|
+
}
|
|
4261
|
+
),
|
|
4262
|
+
/* @__PURE__ */ jsxs(
|
|
4263
|
+
Button,
|
|
4264
|
+
{
|
|
4265
|
+
onClick: handleExportSingle,
|
|
4266
|
+
disabled: !previewFresh || isExporting,
|
|
4267
|
+
className: "h-10 px-4 font-semibold bg-primary hover:bg-primary/90 text-primary-foreground shadow-sm hover:shadow-md transition-all duration-200 text-sm",
|
|
4268
|
+
children: [
|
|
4269
|
+
isExporting ? /* @__PURE__ */ jsx(Loader2, { size: 16, className: "animate-spin" }) : /* @__PURE__ */ jsx(Download, { size: 16 }),
|
|
4270
|
+
isExporting ? "Exporting..." : "Export PDF"
|
|
4271
|
+
]
|
|
4272
|
+
}
|
|
4273
|
+
)
|
|
4274
|
+
] }) : /* @__PURE__ */ jsxs(
|
|
4275
|
+
Button,
|
|
4276
|
+
{
|
|
4277
|
+
onClick: handleBulkGenerate,
|
|
4278
|
+
disabled: isExporting || !bulkData || bulkData.length === 0,
|
|
4279
|
+
className: "h-10 px-4 font-semibold bg-primary hover:bg-primary/90 text-primary-foreground shadow-lg hover:shadow-xl transition-all duration-200 text-sm",
|
|
4280
|
+
children: [
|
|
4281
|
+
isExporting ? /* @__PURE__ */ jsx(Loader2, { size: 16, className: "animate-spin" }) : /* @__PURE__ */ jsx(Download, { size: 16 }),
|
|
4282
|
+
isExporting ? "Generating..." : `Generate All (${bulkData?.length || 0} PDFs)`
|
|
4283
|
+
]
|
|
4284
|
+
}
|
|
4285
|
+
) })
|
|
4286
|
+
] }),
|
|
4287
|
+
/* @__PURE__ */ jsx("div", { className: "bg-border" }),
|
|
4288
|
+
/* @__PURE__ */ jsx("div", { className: "flex flex-col min-h-0 min-w-0", children: /* @__PURE__ */ jsx(
|
|
4289
|
+
PreviewPanel,
|
|
4290
|
+
{
|
|
4291
|
+
pages: previewPages,
|
|
4292
|
+
isGenerating,
|
|
4293
|
+
positionedFields: previewFields,
|
|
4294
|
+
className: "flex-1 border-0 rounded-none"
|
|
4295
|
+
}
|
|
4296
|
+
) })
|
|
4297
|
+
] });
|
|
4298
|
+
}
|
|
4299
|
+
|
|
4300
|
+
// src/utils/theme-helpers.ts
|
|
4301
|
+
function parseThemeColor(color) {
|
|
4302
|
+
if (!color || typeof color !== "string") {
|
|
4303
|
+
return "#0099CD";
|
|
4304
|
+
}
|
|
4305
|
+
const trimmed = color.trim();
|
|
4306
|
+
if (trimmed.startsWith("#") || trimmed.startsWith("rgb") || trimmed.startsWith("hsl") || trimmed.startsWith("oklch") || /^[a-z]+$/i.test(trimmed)) {
|
|
4307
|
+
return trimmed;
|
|
4308
|
+
}
|
|
4309
|
+
return trimmed;
|
|
4310
|
+
}
|
|
4311
|
+
function getContrastColor(backgroundColor) {
|
|
4312
|
+
if (typeof window === "undefined") {
|
|
4313
|
+
return "#000000";
|
|
4314
|
+
}
|
|
4315
|
+
const tempDiv = document.createElement("div");
|
|
4316
|
+
tempDiv.style.color = backgroundColor;
|
|
4317
|
+
tempDiv.style.position = "absolute";
|
|
4318
|
+
tempDiv.style.visibility = "hidden";
|
|
4319
|
+
document.body.appendChild(tempDiv);
|
|
4320
|
+
const computedColor = window.getComputedStyle(tempDiv).color;
|
|
4321
|
+
document.body.removeChild(tempDiv);
|
|
4322
|
+
const rgbMatch = computedColor.match(/\d+/g);
|
|
4323
|
+
if (!rgbMatch || rgbMatch.length < 3) {
|
|
4324
|
+
return "#000000";
|
|
4325
|
+
}
|
|
4326
|
+
const r = parseInt(rgbMatch[0], 10);
|
|
4327
|
+
const g = parseInt(rgbMatch[1], 10);
|
|
4328
|
+
const b = parseInt(rgbMatch[2], 10);
|
|
4329
|
+
const luminance = (0.299 * r + 0.587 * g + 0.114 * b) / 255;
|
|
4330
|
+
return luminance > 0.5 ? "#000000" : "#ffffff";
|
|
4331
|
+
}
|
|
4332
|
+
function applyThemeColor(element, color, config) {
|
|
4333
|
+
if (!element) return;
|
|
4334
|
+
const parsedColor = parseThemeColor(color);
|
|
4335
|
+
element.style.setProperty("--xpc-primary", parsedColor);
|
|
4336
|
+
element.style.setProperty("--xpc-ring", parsedColor);
|
|
4337
|
+
const primaryForeground = config?.primaryForeground || getContrastColor(parsedColor);
|
|
4338
|
+
element.style.setProperty("--xpc-primary-foreground", primaryForeground);
|
|
4339
|
+
if (config) {
|
|
4340
|
+
Object.entries(config).forEach(([key, value]) => {
|
|
4341
|
+
if (value) {
|
|
4342
|
+
const cssKey = key.replace(/([A-Z])/g, "-$1").toLowerCase();
|
|
4343
|
+
element.style.setProperty(`--xpc-${cssKey}`, parseThemeColor(value));
|
|
4344
|
+
}
|
|
4345
|
+
});
|
|
4346
|
+
}
|
|
4347
|
+
}
|
|
4348
|
+
function detectSystemTheme() {
|
|
4349
|
+
if (typeof window === "undefined" || !window.matchMedia) {
|
|
4350
|
+
return "light";
|
|
4351
|
+
}
|
|
4352
|
+
try {
|
|
4353
|
+
return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
|
|
4354
|
+
} catch {
|
|
4355
|
+
return "light";
|
|
4356
|
+
}
|
|
4357
|
+
}
|
|
4358
|
+
function getEffectiveThemeMode(themeMode = "auto") {
|
|
4359
|
+
if (themeMode === "auto") {
|
|
4360
|
+
return detectSystemTheme();
|
|
4361
|
+
}
|
|
4362
|
+
return themeMode;
|
|
4363
|
+
}
|
|
4364
|
+
var ThemeContext = createContext(void 0);
|
|
4365
|
+
function useTheme() {
|
|
4366
|
+
const context = useContext(ThemeContext);
|
|
4367
|
+
if (!context) {
|
|
4368
|
+
throw new Error("useTheme must be used within ThemeProvider");
|
|
4369
|
+
}
|
|
4370
|
+
return context;
|
|
4371
|
+
}
|
|
4372
|
+
function ThemeProvider({
|
|
4373
|
+
themeMode: initialMode = "auto",
|
|
4374
|
+
themeColor: initialColor = "#0099CD",
|
|
4375
|
+
themeConfig,
|
|
4376
|
+
rootElement,
|
|
4377
|
+
children
|
|
4378
|
+
}) {
|
|
4379
|
+
const [themeMode, setThemeModeState] = useState(initialMode);
|
|
4380
|
+
const rootRef = useRef(rootElement || null);
|
|
4381
|
+
const effectiveMode = useMemo(() => getEffectiveThemeMode(themeMode), [themeMode]);
|
|
4382
|
+
useEffect(() => {
|
|
4383
|
+
if (themeMode !== "auto") return;
|
|
4384
|
+
const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
|
|
4385
|
+
const handleChange = () => setThemeModeState("auto");
|
|
4386
|
+
if (mediaQuery.addEventListener) {
|
|
4387
|
+
mediaQuery.addEventListener("change", handleChange);
|
|
4388
|
+
return () => mediaQuery.removeEventListener("change", handleChange);
|
|
4389
|
+
} else {
|
|
4390
|
+
mediaQuery.addListener(handleChange);
|
|
4391
|
+
return () => mediaQuery.removeListener(handleChange);
|
|
4392
|
+
}
|
|
4393
|
+
}, [themeMode]);
|
|
4394
|
+
useEffect(() => {
|
|
4395
|
+
const element = rootRef.current || document.querySelector(".signiphi-pdf-compose");
|
|
4396
|
+
if (!element) return;
|
|
4397
|
+
const htmlElement = element;
|
|
4398
|
+
htmlElement.setAttribute("data-theme", effectiveMode);
|
|
4399
|
+
applyThemeColor(htmlElement, initialColor, themeConfig);
|
|
4400
|
+
if (effectiveMode === "dark") {
|
|
4401
|
+
htmlElement.classList.add("dark");
|
|
4402
|
+
} else {
|
|
4403
|
+
htmlElement.classList.remove("dark");
|
|
4404
|
+
}
|
|
4405
|
+
}, [effectiveMode, initialColor, themeConfig]);
|
|
4406
|
+
const setThemeMode = (mode) => setThemeModeState(mode);
|
|
4407
|
+
const contextValue = useMemo(
|
|
4408
|
+
() => ({
|
|
4409
|
+
themeMode: effectiveMode,
|
|
4410
|
+
originalMode: themeMode,
|
|
4411
|
+
themeColor: parseThemeColor(initialColor),
|
|
4412
|
+
themeConfig,
|
|
4413
|
+
setThemeMode
|
|
4414
|
+
}),
|
|
4415
|
+
[effectiveMode, themeMode, initialColor, themeConfig]
|
|
4416
|
+
);
|
|
4417
|
+
return /* @__PURE__ */ jsx(ThemeContext.Provider, { value: contextValue, children });
|
|
4418
|
+
}
|
|
4419
|
+
var ComposeErrorBoundary = class extends React12__default.Component {
|
|
4420
|
+
constructor(props) {
|
|
4421
|
+
super(props);
|
|
4422
|
+
__publicField(this, "handleReset", () => {
|
|
4423
|
+
this.setState({ hasError: false, error: null });
|
|
4424
|
+
});
|
|
4425
|
+
this.state = { hasError: false, error: null };
|
|
4426
|
+
}
|
|
4427
|
+
static getDerivedStateFromError(error) {
|
|
4428
|
+
return { hasError: true, error };
|
|
4429
|
+
}
|
|
4430
|
+
componentDidCatch(error, errorInfo) {
|
|
4431
|
+
console.error("pdf-compose render error:", error, errorInfo);
|
|
4432
|
+
}
|
|
4433
|
+
render() {
|
|
4434
|
+
if (this.state.hasError) {
|
|
4435
|
+
return /* @__PURE__ */ jsx("div", { className: this.props.fallbackClassName, children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center justify-center p-8 text-center gap-3", children: [
|
|
4436
|
+
/* @__PURE__ */ jsx(AlertTriangle, { size: 32, className: "text-destructive" }),
|
|
4437
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
4438
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm font-medium text-foreground", children: "Something went wrong" }),
|
|
4439
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground mt-1", children: this.state.error?.message || "An unexpected error occurred in the document composer." })
|
|
4440
|
+
] }),
|
|
4441
|
+
/* @__PURE__ */ jsxs(Button, { variant: "outline", size: "sm", onClick: this.handleReset, children: [
|
|
4442
|
+
/* @__PURE__ */ jsx(RefreshCw, { size: 14 }),
|
|
4443
|
+
"Try Again"
|
|
4444
|
+
] })
|
|
4445
|
+
] }) });
|
|
4446
|
+
}
|
|
4447
|
+
return this.props.children;
|
|
4448
|
+
}
|
|
4449
|
+
};
|
|
4450
|
+
function DocumentGeneratorInner({
|
|
4451
|
+
initialMarkdown,
|
|
4452
|
+
initialContent,
|
|
4453
|
+
onChange,
|
|
4454
|
+
onExport,
|
|
4455
|
+
readOnly = false,
|
|
4456
|
+
className,
|
|
4457
|
+
showPreview = true,
|
|
4458
|
+
placeholder,
|
|
4459
|
+
exportButtonText = "Export Document",
|
|
4460
|
+
showToolbar = true,
|
|
4461
|
+
showGenerateTab = true,
|
|
4462
|
+
onGeneratePdf,
|
|
4463
|
+
initialBulkData
|
|
4464
|
+
}) {
|
|
4465
|
+
const {
|
|
4466
|
+
editor,
|
|
4467
|
+
fields,
|
|
4468
|
+
variables,
|
|
4469
|
+
positionedFields,
|
|
4470
|
+
markdown,
|
|
4471
|
+
pdfPages,
|
|
4472
|
+
isGenerating,
|
|
4473
|
+
error: generationError,
|
|
4474
|
+
fieldWarnings,
|
|
4475
|
+
insertField,
|
|
4476
|
+
updateField,
|
|
4477
|
+
deleteField,
|
|
4478
|
+
insertVariable,
|
|
4479
|
+
updateVariable,
|
|
4480
|
+
deleteVariable,
|
|
4481
|
+
generatePdf,
|
|
4482
|
+
exportDocument,
|
|
4483
|
+
exportMarkdown
|
|
4484
|
+
} = useDocumentGenerator({
|
|
4485
|
+
initialMarkdown,
|
|
4486
|
+
initialContent,
|
|
4487
|
+
placeholder,
|
|
4488
|
+
onChange
|
|
4489
|
+
});
|
|
4490
|
+
const [activeTab, setActiveTab] = useState("editor");
|
|
4491
|
+
const [insertPopoverOpen, setInsertPopoverOpen] = useState(false);
|
|
4492
|
+
const [insertVarPopoverOpen, setInsertVarPopoverOpen] = useState(false);
|
|
4493
|
+
const [editFieldId, setEditFieldId] = useState(null);
|
|
4494
|
+
const [editFieldAttrs, setEditFieldAttrs] = useState(null);
|
|
4495
|
+
const [editFieldAnchorEl, setEditFieldAnchorEl] = useState(null);
|
|
4496
|
+
const [editVarName, setEditVarName] = useState(null);
|
|
4497
|
+
const [editVarAttrs, setEditVarAttrs] = useState(null);
|
|
4498
|
+
const [editVarAnchorEl, setEditVarAnchorEl] = useState(null);
|
|
4499
|
+
const editorWrapperRef = useRef(null);
|
|
4500
|
+
const [editorCollapsed, setEditorCollapsed] = useState(false);
|
|
4501
|
+
const [mdExportError, setMdExportError] = useState(null);
|
|
4502
|
+
const [exportSuccess, setExportSuccess] = useState(null);
|
|
4503
|
+
useEffect(() => {
|
|
4504
|
+
const handler = (e) => {
|
|
4505
|
+
const detail = e.detail;
|
|
4506
|
+
if (!detail?.fieldId || !editor) return;
|
|
4507
|
+
let foundAttrs = null;
|
|
4508
|
+
editor.state.doc.descendants((node) => {
|
|
4509
|
+
if (node.type.name === "fieldNode" && node.attrs.fieldId === detail.fieldId) {
|
|
4510
|
+
foundAttrs = node.attrs;
|
|
4511
|
+
return false;
|
|
4512
|
+
}
|
|
4513
|
+
});
|
|
4514
|
+
if (foundAttrs) {
|
|
4515
|
+
setEditFieldId(detail.fieldId);
|
|
4516
|
+
setEditFieldAttrs(foundAttrs);
|
|
4517
|
+
setEditFieldAnchorEl(detail.anchorEl || editorWrapperRef.current);
|
|
4518
|
+
}
|
|
4519
|
+
};
|
|
4520
|
+
window.addEventListener("signiphi:edit-field", handler);
|
|
4521
|
+
return () => window.removeEventListener("signiphi:edit-field", handler);
|
|
4522
|
+
}, [editor]);
|
|
4523
|
+
useEffect(() => {
|
|
4524
|
+
const handler = (e) => {
|
|
4525
|
+
const detail = e.detail;
|
|
4526
|
+
if (!detail?.varName || !editor) return;
|
|
4527
|
+
let foundAttrs = null;
|
|
4528
|
+
editor.state.doc.descendants((node) => {
|
|
4529
|
+
if (node.type.name === "variableNode" && node.attrs.varName === detail.varName) {
|
|
4530
|
+
foundAttrs = node.attrs;
|
|
4531
|
+
return false;
|
|
4532
|
+
}
|
|
4533
|
+
});
|
|
4534
|
+
if (foundAttrs) {
|
|
4535
|
+
setEditVarName(detail.varName);
|
|
4536
|
+
setEditVarAttrs(foundAttrs);
|
|
4537
|
+
setEditVarAnchorEl(detail.anchorEl || editorWrapperRef.current);
|
|
4538
|
+
}
|
|
4539
|
+
};
|
|
4540
|
+
window.addEventListener("signiphi:edit-variable", handler);
|
|
4541
|
+
return () => window.removeEventListener("signiphi:edit-variable", handler);
|
|
4542
|
+
}, [editor]);
|
|
4543
|
+
const handleInsertField = useCallback(
|
|
4544
|
+
(type, attrs) => {
|
|
4545
|
+
insertField(type, attrs);
|
|
4546
|
+
},
|
|
4547
|
+
[insertField]
|
|
4548
|
+
);
|
|
4549
|
+
const handleInsertVariable = useCallback(
|
|
4550
|
+
(attrs) => {
|
|
4551
|
+
insertVariable(attrs);
|
|
4552
|
+
},
|
|
4553
|
+
[insertVariable]
|
|
4554
|
+
);
|
|
4555
|
+
const handleCloseFieldEditPopover = useCallback(() => {
|
|
4556
|
+
setEditFieldId(null);
|
|
4557
|
+
setEditFieldAttrs(null);
|
|
4558
|
+
setEditFieldAnchorEl(null);
|
|
4559
|
+
}, []);
|
|
4560
|
+
const handleCloseVarEditPopover = useCallback(() => {
|
|
4561
|
+
setEditVarName(null);
|
|
4562
|
+
setEditVarAttrs(null);
|
|
4563
|
+
setEditVarAnchorEl(null);
|
|
4564
|
+
}, []);
|
|
4565
|
+
const handleExport = useCallback(async () => {
|
|
4566
|
+
setExportSuccess(null);
|
|
4567
|
+
const result = await exportDocument();
|
|
4568
|
+
if (result && onExport) {
|
|
4569
|
+
onExport(result);
|
|
4570
|
+
setExportSuccess("Document exported successfully");
|
|
4571
|
+
}
|
|
4572
|
+
}, [exportDocument, onExport]);
|
|
4573
|
+
const handleExportMarkdown = useCallback(() => {
|
|
4574
|
+
const md = exportMarkdown();
|
|
4575
|
+
if (!md.trim()) {
|
|
4576
|
+
setMdExportError("Editor content is empty");
|
|
4577
|
+
return;
|
|
4578
|
+
}
|
|
4579
|
+
const validation = validateMarkdown(md);
|
|
4580
|
+
if (!validation.valid) {
|
|
4581
|
+
setMdExportError(validation.errors.join("; "));
|
|
4582
|
+
return;
|
|
4583
|
+
}
|
|
4584
|
+
setMdExportError(null);
|
|
4585
|
+
const blob = new Blob([md], { type: "text/markdown" });
|
|
4586
|
+
const url = URL.createObjectURL(blob);
|
|
4587
|
+
const a = document.createElement("a");
|
|
4588
|
+
a.href = url;
|
|
4589
|
+
a.download = "template.md";
|
|
4590
|
+
a.click();
|
|
4591
|
+
URL.revokeObjectURL(url);
|
|
4592
|
+
}, [exportMarkdown]);
|
|
4593
|
+
useEffect(() => {
|
|
4594
|
+
if (mdExportError) setMdExportError(null);
|
|
4595
|
+
}, [markdown]);
|
|
4596
|
+
useEffect(() => {
|
|
4597
|
+
if (exportSuccess) {
|
|
4598
|
+
const timer = setTimeout(() => setExportSuccess(null), 3e3);
|
|
4599
|
+
return () => clearTimeout(timer);
|
|
4600
|
+
}
|
|
4601
|
+
}, [exportSuccess]);
|
|
4602
|
+
const insertFieldButton = /* @__PURE__ */ jsx(
|
|
4603
|
+
FieldInsertPopover,
|
|
4604
|
+
{
|
|
4605
|
+
open: insertPopoverOpen,
|
|
4606
|
+
onOpenChange: setInsertPopoverOpen,
|
|
4607
|
+
onInsert: handleInsertField,
|
|
4608
|
+
children: /* @__PURE__ */ jsxs(
|
|
4609
|
+
Button,
|
|
4610
|
+
{
|
|
4611
|
+
variant: "outline",
|
|
4612
|
+
size: "sm",
|
|
4613
|
+
className: "h-8 gap-1.5 text-xs font-medium",
|
|
4614
|
+
children: [
|
|
4615
|
+
/* @__PURE__ */ jsx(Plus, { size: 14 }),
|
|
4616
|
+
"Insert Field"
|
|
4617
|
+
]
|
|
4618
|
+
}
|
|
4619
|
+
)
|
|
4620
|
+
}
|
|
4621
|
+
);
|
|
4622
|
+
const insertVariableButton = /* @__PURE__ */ jsx(
|
|
4623
|
+
VariableInsertPopover,
|
|
4624
|
+
{
|
|
4625
|
+
open: insertVarPopoverOpen,
|
|
4626
|
+
onOpenChange: setInsertVarPopoverOpen,
|
|
4627
|
+
onInsert: handleInsertVariable,
|
|
4628
|
+
children: /* @__PURE__ */ jsxs(
|
|
4629
|
+
Button,
|
|
4630
|
+
{
|
|
4631
|
+
variant: "outline",
|
|
4632
|
+
size: "sm",
|
|
4633
|
+
className: "h-8 gap-1.5 text-xs font-medium",
|
|
4634
|
+
children: [
|
|
4635
|
+
/* @__PURE__ */ jsx(Braces, { size: 14 }),
|
|
4636
|
+
"Insert Variable"
|
|
4637
|
+
]
|
|
4638
|
+
}
|
|
4639
|
+
)
|
|
4640
|
+
}
|
|
4641
|
+
);
|
|
4642
|
+
return /* @__PURE__ */ jsxs("div", { "data-compose-root": true, className: cn("signiphi-pdf-compose", className), children: [
|
|
4643
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between px-4 py-2 border-b border-border bg-background", children: [
|
|
4644
|
+
showGenerateTab && /* @__PURE__ */ jsx(
|
|
4645
|
+
ToggleGroup,
|
|
4646
|
+
{
|
|
4647
|
+
value: activeTab,
|
|
4648
|
+
onChange: setActiveTab,
|
|
4649
|
+
options: [
|
|
4650
|
+
{ value: "editor", label: "Editor" },
|
|
4651
|
+
{ value: "generate", label: "Generate" }
|
|
4652
|
+
]
|
|
4653
|
+
}
|
|
4654
|
+
),
|
|
4655
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 text-xs text-muted-foreground", children: [
|
|
4656
|
+
fields.length > 0 && /* @__PURE__ */ jsxs("span", { children: [
|
|
4657
|
+
fields.length,
|
|
4658
|
+
" field",
|
|
4659
|
+
fields.length !== 1 ? "s" : ""
|
|
4660
|
+
] }),
|
|
4661
|
+
variables.length > 0 && /* @__PURE__ */ jsxs("span", { children: [
|
|
4662
|
+
variables.length,
|
|
4663
|
+
" variable",
|
|
4664
|
+
variables.length !== 1 ? "s" : ""
|
|
4665
|
+
] })
|
|
4666
|
+
] })
|
|
4667
|
+
] }),
|
|
4668
|
+
/* @__PURE__ */ jsxs("div", { className: cn(
|
|
4669
|
+
"flex flex-col flex-1 min-h-0",
|
|
4670
|
+
activeTab !== "editor" && "hidden"
|
|
4671
|
+
), children: [
|
|
4672
|
+
/* @__PURE__ */ jsxs("div", { className: cn(
|
|
4673
|
+
"flex-1 min-h-0",
|
|
4674
|
+
showPreview && !editorCollapsed ? "grid grid-cols-[1fr_1px_1fr]" : "flex flex-col"
|
|
4675
|
+
), children: [
|
|
4676
|
+
!editorCollapsed && /* @__PURE__ */ jsxs("div", { className: "flex flex-col min-h-0 min-w-0", children: [
|
|
4677
|
+
showToolbar && !readOnly && /* @__PURE__ */ jsx("div", { className: "border-b border-border", children: /* @__PURE__ */ jsx(
|
|
4678
|
+
EditorToolbar,
|
|
4679
|
+
{
|
|
4680
|
+
editor,
|
|
4681
|
+
insertFieldButton,
|
|
4682
|
+
insertVariableButton,
|
|
4683
|
+
onCollapse: showPreview ? () => setEditorCollapsed(true) : void 0
|
|
4684
|
+
}
|
|
4685
|
+
) }),
|
|
4686
|
+
/* @__PURE__ */ jsx("div", { ref: editorWrapperRef, className: "flex-1 overflow-y-auto min-h-0", children: /* @__PURE__ */ jsx(
|
|
4687
|
+
EditorContent,
|
|
4688
|
+
{
|
|
4689
|
+
editor,
|
|
4690
|
+
className: "prose prose-sm max-w-none p-4 focus-within:outline-none"
|
|
4691
|
+
}
|
|
4692
|
+
) })
|
|
4693
|
+
] }),
|
|
4694
|
+
showPreview && !editorCollapsed && /* @__PURE__ */ jsx("div", { className: "bg-border" }),
|
|
4695
|
+
showPreview && /* @__PURE__ */ jsx(
|
|
4696
|
+
PreviewPanel,
|
|
4697
|
+
{
|
|
4698
|
+
pages: pdfPages,
|
|
4699
|
+
isGenerating,
|
|
4700
|
+
positionedFields,
|
|
4701
|
+
headerLeft: editorCollapsed ? /* @__PURE__ */ jsx(
|
|
4702
|
+
Button,
|
|
4703
|
+
{
|
|
4704
|
+
variant: "ghost",
|
|
4705
|
+
size: "sm",
|
|
4706
|
+
className: "h-8 w-8 p-0",
|
|
4707
|
+
onClick: () => setEditorCollapsed(false),
|
|
4708
|
+
children: /* @__PURE__ */ jsx(PanelLeftOpen, { size: 14 })
|
|
4709
|
+
}
|
|
4710
|
+
) : void 0,
|
|
4711
|
+
className: "flex-1 border-0 rounded-none"
|
|
4712
|
+
}
|
|
4713
|
+
)
|
|
4714
|
+
] }),
|
|
4715
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between px-4 py-3 border-t border-border bg-gradient-to-b from-background to-muted/20", children: [
|
|
4716
|
+
/* @__PURE__ */ jsxs(
|
|
4717
|
+
Button,
|
|
4718
|
+
{
|
|
4719
|
+
variant: "secondary",
|
|
4720
|
+
onClick: generatePdf,
|
|
4721
|
+
disabled: isGenerating || !editor,
|
|
4722
|
+
className: "h-10 px-4 font-semibold shadow-sm hover:shadow-md transition-all duration-200 text-sm",
|
|
4723
|
+
children: [
|
|
4724
|
+
isGenerating ? /* @__PURE__ */ jsx(Loader2, { size: 16, className: "animate-spin" }) : /* @__PURE__ */ jsx(RefreshCw, { size: 16 }),
|
|
4725
|
+
isGenerating ? "Generating..." : "Generate PDF"
|
|
4726
|
+
]
|
|
4727
|
+
}
|
|
4728
|
+
),
|
|
4729
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-1", children: [
|
|
4730
|
+
mdExportError && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5 text-xs text-destructive max-w-[300px]", children: [
|
|
4731
|
+
/* @__PURE__ */ jsx(AlertTriangle, { size: 14, className: "shrink-0" }),
|
|
4732
|
+
/* @__PURE__ */ jsx("span", { className: "truncate", children: mdExportError })
|
|
4733
|
+
] }),
|
|
4734
|
+
generationError && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5 text-xs text-destructive max-w-[300px]", children: [
|
|
4735
|
+
/* @__PURE__ */ jsx(AlertTriangle, { size: 14, className: "shrink-0" }),
|
|
4736
|
+
/* @__PURE__ */ jsx("span", { className: "truncate", children: generationError })
|
|
4737
|
+
] }),
|
|
4738
|
+
fieldWarnings.length > 0 && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5 text-xs text-orange-600 max-w-[400px]", children: [
|
|
4739
|
+
/* @__PURE__ */ jsx(AlertTriangle, { size: 14, className: "shrink-0" }),
|
|
4740
|
+
/* @__PURE__ */ jsxs("span", { className: "truncate", children: [
|
|
4741
|
+
fieldWarnings.length,
|
|
4742
|
+
" field",
|
|
4743
|
+
fieldWarnings.length !== 1 ? "s" : "",
|
|
4744
|
+
" had warnings during generation"
|
|
4745
|
+
] })
|
|
4746
|
+
] }),
|
|
4747
|
+
exportSuccess && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5 text-xs text-emerald-600 max-w-[300px]", children: [
|
|
4748
|
+
/* @__PURE__ */ jsx(CheckCircle2, { size: 14, className: "shrink-0" }),
|
|
4749
|
+
/* @__PURE__ */ jsx("span", { children: exportSuccess })
|
|
4750
|
+
] })
|
|
4751
|
+
] }),
|
|
4752
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
4753
|
+
/* @__PURE__ */ jsxs(
|
|
4754
|
+
Button,
|
|
4755
|
+
{
|
|
4756
|
+
onClick: handleExportMarkdown,
|
|
4757
|
+
disabled: !editor || !markdown.trim(),
|
|
4758
|
+
className: "h-10 px-4 font-semibold bg-primary hover:bg-primary/90 text-primary-foreground shadow-sm hover:shadow-md transition-all duration-200 text-sm",
|
|
4759
|
+
children: [
|
|
4760
|
+
/* @__PURE__ */ jsx(FileText, { size: 16 }),
|
|
4761
|
+
"Export MD"
|
|
4762
|
+
]
|
|
4763
|
+
}
|
|
4764
|
+
),
|
|
4765
|
+
/* @__PURE__ */ jsxs(
|
|
4766
|
+
Button,
|
|
4767
|
+
{
|
|
4768
|
+
onClick: handleExport,
|
|
4769
|
+
disabled: isGenerating || !editor || pdfPages.length === 0,
|
|
4770
|
+
className: "h-10 px-4 font-semibold bg-primary hover:bg-primary/90 text-primary-foreground shadow-sm hover:shadow-md transition-all duration-200 text-sm",
|
|
4771
|
+
children: [
|
|
4772
|
+
/* @__PURE__ */ jsx(FileDown, { size: 16 }),
|
|
4773
|
+
exportButtonText
|
|
4774
|
+
]
|
|
4775
|
+
}
|
|
4776
|
+
)
|
|
4777
|
+
] })
|
|
4778
|
+
] })
|
|
4779
|
+
] }),
|
|
4780
|
+
showGenerateTab && activeTab === "generate" && /* @__PURE__ */ jsx(
|
|
4781
|
+
GeneratePanel,
|
|
4782
|
+
{
|
|
4783
|
+
editorMarkdown: markdown,
|
|
4784
|
+
editorContent: editor?.getJSON(),
|
|
4785
|
+
editorVariables: variables,
|
|
4786
|
+
onGeneratePdf,
|
|
4787
|
+
initialBulkData
|
|
4788
|
+
}
|
|
4789
|
+
),
|
|
4790
|
+
/* @__PURE__ */ jsx(
|
|
4791
|
+
FieldEditPopover,
|
|
4792
|
+
{
|
|
4793
|
+
fieldId: editFieldId,
|
|
4794
|
+
attrs: editFieldAttrs,
|
|
4795
|
+
anchorEl: editFieldAnchorEl,
|
|
4796
|
+
onUpdate: updateField,
|
|
4797
|
+
onDelete: deleteField,
|
|
4798
|
+
onClose: handleCloseFieldEditPopover
|
|
4799
|
+
},
|
|
4800
|
+
editFieldId
|
|
4801
|
+
),
|
|
4802
|
+
/* @__PURE__ */ jsx(
|
|
4803
|
+
VariableEditPopover,
|
|
4804
|
+
{
|
|
4805
|
+
varName: editVarName,
|
|
4806
|
+
attrs: editVarAttrs,
|
|
4807
|
+
anchorEl: editVarAnchorEl,
|
|
4808
|
+
onUpdate: updateVariable,
|
|
4809
|
+
onDelete: deleteVariable,
|
|
4810
|
+
onClose: handleCloseVarEditPopover
|
|
4811
|
+
},
|
|
4812
|
+
editVarName
|
|
4813
|
+
)
|
|
4814
|
+
] });
|
|
4815
|
+
}
|
|
4816
|
+
function DocumentGenerator(props) {
|
|
4817
|
+
return /* @__PURE__ */ jsx(
|
|
4818
|
+
ThemeProvider,
|
|
4819
|
+
{
|
|
4820
|
+
themeColor: props.themeColor,
|
|
4821
|
+
themeMode: props.themeMode,
|
|
4822
|
+
themeConfig: props.themeConfig,
|
|
4823
|
+
children: /* @__PURE__ */ jsx(ComposeErrorBoundary, { fallbackClassName: props.className, children: /* @__PURE__ */ jsx(DocumentGeneratorInner, { ...props }) })
|
|
4824
|
+
}
|
|
4825
|
+
);
|
|
4826
|
+
}
|
|
4827
|
+
function EditorPanel({
|
|
4828
|
+
editor,
|
|
4829
|
+
showToolbar = true,
|
|
4830
|
+
readOnly = false,
|
|
4831
|
+
className,
|
|
4832
|
+
onInsertField,
|
|
4833
|
+
onUpdateField,
|
|
4834
|
+
onDeleteField
|
|
4835
|
+
}) {
|
|
4836
|
+
const [insertPopoverOpen, setInsertPopoverOpen] = useState(false);
|
|
4837
|
+
const [editFieldId, setEditFieldId] = useState(null);
|
|
4838
|
+
const [editFieldAttrs, setEditFieldAttrs] = useState(null);
|
|
4839
|
+
const [editAnchorEl, setEditAnchorEl] = useState(null);
|
|
4840
|
+
const editorWrapperRef = useRef(null);
|
|
4841
|
+
useEffect(() => {
|
|
4842
|
+
const handler = (e) => {
|
|
4843
|
+
const detail = e.detail;
|
|
4844
|
+
if (!detail?.fieldId || !editor) return;
|
|
4845
|
+
let foundAttrs = null;
|
|
4846
|
+
editor.state.doc.descendants((node) => {
|
|
4847
|
+
if (node.type.name === "fieldNode" && node.attrs.fieldId === detail.fieldId) {
|
|
4848
|
+
foundAttrs = node.attrs;
|
|
4849
|
+
return false;
|
|
4850
|
+
}
|
|
4851
|
+
});
|
|
4852
|
+
if (foundAttrs) {
|
|
4853
|
+
setEditFieldId(detail.fieldId);
|
|
4854
|
+
setEditFieldAttrs(foundAttrs);
|
|
4855
|
+
const fieldEl = editorWrapperRef.current?.querySelector(
|
|
4856
|
+
`[data-field-node][data-field-id="${detail.fieldId}"], span[data-field-node]`
|
|
4857
|
+
);
|
|
4858
|
+
if (fieldEl) {
|
|
4859
|
+
setEditAnchorEl(fieldEl);
|
|
4860
|
+
} else {
|
|
4861
|
+
setEditAnchorEl(editorWrapperRef.current);
|
|
4862
|
+
}
|
|
4863
|
+
}
|
|
4864
|
+
};
|
|
4865
|
+
window.addEventListener("signiphi:edit-field", handler);
|
|
4866
|
+
return () => window.removeEventListener("signiphi:edit-field", handler);
|
|
4867
|
+
}, [editor]);
|
|
4868
|
+
const handleInsertField = useCallback(
|
|
4869
|
+
(type, attrs) => {
|
|
4870
|
+
onInsertField(type, attrs);
|
|
4871
|
+
},
|
|
4872
|
+
[onInsertField]
|
|
4873
|
+
);
|
|
4874
|
+
const handleCloseEditPopover = useCallback(() => {
|
|
4875
|
+
setEditFieldId(null);
|
|
4876
|
+
setEditFieldAttrs(null);
|
|
4877
|
+
setEditAnchorEl(null);
|
|
4878
|
+
}, []);
|
|
4879
|
+
const insertFieldButton = /* @__PURE__ */ jsx(
|
|
4880
|
+
FieldInsertPopover,
|
|
4881
|
+
{
|
|
4882
|
+
open: insertPopoverOpen,
|
|
4883
|
+
onOpenChange: setInsertPopoverOpen,
|
|
4884
|
+
onInsert: handleInsertField,
|
|
4885
|
+
children: /* @__PURE__ */ jsxs(
|
|
4886
|
+
Button,
|
|
4887
|
+
{
|
|
4888
|
+
variant: "outline",
|
|
4889
|
+
size: "sm",
|
|
4890
|
+
className: "h-8 gap-1.5 text-xs font-medium",
|
|
4891
|
+
children: [
|
|
4892
|
+
/* @__PURE__ */ jsx(Plus, { size: 14 }),
|
|
4893
|
+
"Insert Field"
|
|
4894
|
+
]
|
|
4895
|
+
}
|
|
4896
|
+
)
|
|
4897
|
+
}
|
|
4898
|
+
);
|
|
4899
|
+
return /* @__PURE__ */ jsxs("div", { className: cn("flex flex-col h-full border border-border rounded-lg overflow-hidden bg-background", className), children: [
|
|
4900
|
+
showToolbar && !readOnly && /* @__PURE__ */ jsx(
|
|
4901
|
+
EditorToolbar,
|
|
4902
|
+
{
|
|
4903
|
+
editor,
|
|
4904
|
+
insertFieldButton
|
|
4905
|
+
}
|
|
4906
|
+
),
|
|
4907
|
+
/* @__PURE__ */ jsx("div", { ref: editorWrapperRef, className: "flex-1 overflow-y-auto", children: /* @__PURE__ */ jsx(
|
|
4908
|
+
EditorContent,
|
|
4909
|
+
{
|
|
4910
|
+
editor,
|
|
4911
|
+
className: "prose prose-sm max-w-none p-4 min-h-full focus-within:outline-none"
|
|
4912
|
+
}
|
|
4913
|
+
) }),
|
|
4914
|
+
/* @__PURE__ */ jsx(
|
|
4915
|
+
FieldEditPopover,
|
|
4916
|
+
{
|
|
4917
|
+
fieldId: editFieldId,
|
|
4918
|
+
attrs: editFieldAttrs,
|
|
4919
|
+
anchorEl: editAnchorEl,
|
|
4920
|
+
onUpdate: onUpdateField,
|
|
4921
|
+
onDelete: onDeleteField,
|
|
4922
|
+
onClose: handleCloseEditPopover
|
|
4923
|
+
}
|
|
4924
|
+
)
|
|
4925
|
+
] });
|
|
4926
|
+
}
|
|
4927
|
+
|
|
4928
|
+
export { DocumentGenerator, EditorPanel, FormFieldType, PreviewPanel, ThemeProvider, useDocumentGenerator, useTheme };
|
|
4929
|
+
//# sourceMappingURL=index.mjs.map
|
|
4930
|
+
//# sourceMappingURL=index.mjs.map
|