@zerohive/hive-viewer 0.2.1 → 0.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +123 -6
- package/dist/index.cjs +618 -424
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +620 -423
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +620 -423
- package/dist/styles.css +75 -0
- package/package.json +4 -2
package/dist/index.cjs
CHANGED
|
@@ -37,320 +37,11 @@ module.exports = __toCommonJS(index_exports);
|
|
|
37
37
|
// src/components/DocumentViewer.tsx
|
|
38
38
|
var import_react6 = require("react");
|
|
39
39
|
|
|
40
|
-
// src/utils/locale.ts
|
|
41
|
-
var defaultLocale = {
|
|
42
|
-
"loading": "Loading\u2026",
|
|
43
|
-
"error.title": "Error",
|
|
44
|
-
"toolbar.layout.single": "Single page",
|
|
45
|
-
"toolbar.layout.two": "Side-by-side",
|
|
46
|
-
"toolbar.thumbs": "Thumbnails",
|
|
47
|
-
"toolbar.signatures": "Signatures",
|
|
48
|
-
"toolbar.sign": "Sign Document",
|
|
49
|
-
"toolbar.save": "Save",
|
|
50
|
-
"toolbar.exportPdf": "Export as PDF",
|
|
51
|
-
"thumbnails.title": "Thumbnails",
|
|
52
|
-
"thumbnails.page": "Page",
|
|
53
|
-
"signatures.title": "Signatures",
|
|
54
|
-
"signatures.empty": "No signatures",
|
|
55
|
-
"signatures.placeHint": "Click on the document to place the signature.",
|
|
56
|
-
"a11y.viewer": "Document viewer",
|
|
57
|
-
"a11y.ribbon": "Ribbon",
|
|
58
|
-
"a11y.editor": "Document editor"
|
|
59
|
-
};
|
|
60
|
-
|
|
61
|
-
// src/utils/fileSource.ts
|
|
62
|
-
function guessFileType(name, explicit) {
|
|
63
|
-
if (explicit) return explicit;
|
|
64
|
-
const ext = (name?.split(".").pop() || "").toLowerCase();
|
|
65
|
-
const allowed = ["pdf", "md", "docx", "xlsx", "pptx", "txt", "png", "jpg", "svg"];
|
|
66
|
-
return allowed.includes(ext) ? ext : "txt";
|
|
67
|
-
}
|
|
68
|
-
function arrayBufferToBase64(buf) {
|
|
69
|
-
const bytes = new Uint8Array(buf);
|
|
70
|
-
let binary = "";
|
|
71
|
-
const chunk = 32768;
|
|
72
|
-
for (let i = 0; i < bytes.length; i += chunk) {
|
|
73
|
-
binary += String.fromCharCode(...bytes.subarray(i, i + chunk));
|
|
74
|
-
}
|
|
75
|
-
return btoa(binary);
|
|
76
|
-
}
|
|
77
|
-
async function base64ToArrayBuffer(b64) {
|
|
78
|
-
const bin = atob(b64);
|
|
79
|
-
const len = bin.length;
|
|
80
|
-
const bytes = new Uint8Array(len);
|
|
81
|
-
for (let i = 0; i < len; i++) bytes[i] = bin.charCodeAt(i);
|
|
82
|
-
return bytes.buffer;
|
|
83
|
-
}
|
|
84
|
-
async function resolveSource(args) {
|
|
85
|
-
const fileType = guessFileType(args.fileName, args.fileType);
|
|
86
|
-
const fileName = args.fileName ?? `document.${fileType}`;
|
|
87
|
-
if (args.blob) {
|
|
88
|
-
const ab = await args.blob.arrayBuffer();
|
|
89
|
-
const url = URL.createObjectURL(args.blob);
|
|
90
|
-
return { fileType, fileName, arrayBuffer: ab, url };
|
|
91
|
-
}
|
|
92
|
-
if (args.base64) {
|
|
93
|
-
const ab = await base64ToArrayBuffer(args.base64);
|
|
94
|
-
return { fileType, fileName, arrayBuffer: ab };
|
|
95
|
-
}
|
|
96
|
-
if (!args.fileUrl) throw new Error("No file source provided. Use fileUrl, blob, or base64.");
|
|
97
|
-
const res = await fetch(args.fileUrl);
|
|
98
|
-
if (!res.ok) throw new Error(`Failed to fetch file (${res.status})`);
|
|
99
|
-
const total = Number(res.headers.get("content-length") || "") || void 0;
|
|
100
|
-
if (!res.body) {
|
|
101
|
-
const ab = await res.arrayBuffer();
|
|
102
|
-
args.onProgress?.(ab.byteLength, total);
|
|
103
|
-
return { fileType, fileName, arrayBuffer: ab, url: args.fileUrl };
|
|
104
|
-
}
|
|
105
|
-
const reader = res.body.getReader();
|
|
106
|
-
const chunks = [];
|
|
107
|
-
let loaded = 0;
|
|
108
|
-
while (true) {
|
|
109
|
-
const { done, value } = await reader.read();
|
|
110
|
-
if (done) break;
|
|
111
|
-
if (value) {
|
|
112
|
-
chunks.push(value);
|
|
113
|
-
loaded += value.length;
|
|
114
|
-
args.onProgress?.(loaded, total);
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
const out = new Uint8Array(loaded);
|
|
118
|
-
let offset = 0;
|
|
119
|
-
for (const c of chunks) {
|
|
120
|
-
out.set(c, offset);
|
|
121
|
-
offset += c.length;
|
|
122
|
-
}
|
|
123
|
-
return { fileType, fileName, arrayBuffer: out.buffer, url: args.fileUrl };
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
// src/components/Toolbar.tsx
|
|
127
|
-
var import_jsx_runtime = require("react/jsx-runtime");
|
|
128
|
-
function Toolbar(props) {
|
|
129
|
-
const t = (k, fallback) => props.locale[k] ?? fallback;
|
|
130
|
-
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "hv-toolbar", role: "toolbar", "aria-label": t("a11y.toolbar", "Document toolbar"), children: [
|
|
131
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "hv-toolbar__left", children: [
|
|
132
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "hv-btn", onClick: props.onToggleThumbnails, "aria-pressed": props.showThumbnails, children: t("toolbar.thumbs", "Thumbnails") }),
|
|
133
|
-
props.mode !== "create" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "hv-btn", onClick: props.onToggleSignatures, "aria-pressed": props.showSignatures, children: t("toolbar.signatures", "Signatures") }),
|
|
134
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "hv-sep" }),
|
|
135
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: props.layout === "single" ? "hv-btn hv-btn--active" : "hv-btn", onClick: () => props.onChangeLayout("single"), children: t("toolbar.layout.single", "Single") }),
|
|
136
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: props.layout === "side-by-side" ? "hv-btn hv-btn--active" : "hv-btn", onClick: () => props.onChangeLayout("side-by-side"), children: t("toolbar.layout.two", "Two") })
|
|
137
|
-
] }),
|
|
138
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "hv-toolbar__right", children: [
|
|
139
|
-
props.showHeaderFooterToggle && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { className: "hv-toggle", children: [
|
|
140
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", { type: "checkbox", checked: props.headerFooterEnabled, onChange: props.onToggleHeaderFooter }),
|
|
141
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: t("toolbar.letterhead", "Letterhead") })
|
|
142
|
-
] }),
|
|
143
|
-
props.allowSigning && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "hv-btn hv-btn--primary", onClick: props.onSign, disabled: props.signingDisabled, children: t("toolbar.sign", "Sign Document") }),
|
|
144
|
-
props.canExportPdf && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "hv-btn", onClick: props.onExportPdf, children: t("toolbar.exportPdf", "Export as PDF") }),
|
|
145
|
-
props.canSave && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "hv-btn hv-btn--primary", onClick: props.onSave, children: t("toolbar.save", "Save") })
|
|
146
|
-
] })
|
|
147
|
-
] });
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
// src/components/ThumbnailsSidebar.tsx
|
|
151
|
-
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
152
|
-
function ThumbnailsSidebar(props) {
|
|
153
|
-
const t = props.locale["thumbnails.title"] ?? "Thumbnails";
|
|
154
|
-
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("aside", { className: props.collapsed ? "hv-thumbs hv-thumbs--collapsed" : "hv-thumbs", "aria-label": t, children: [
|
|
155
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "hv-thumbs__header", children: [
|
|
156
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
157
|
-
"button",
|
|
158
|
-
{
|
|
159
|
-
type: "button",
|
|
160
|
-
className: "hv-icon",
|
|
161
|
-
onClick: props.onToggle,
|
|
162
|
-
"aria-label": props.collapsed ? props.locale["thumbnails.open"] ?? "Open thumbnails" : props.locale["thumbnails.close"] ?? "Close thumbnails",
|
|
163
|
-
children: props.collapsed ? "\u25B8" : "\u25BE"
|
|
164
|
-
}
|
|
165
|
-
),
|
|
166
|
-
!props.collapsed ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "hv-thumbs__title", children: t }) : null
|
|
167
|
-
] }),
|
|
168
|
-
!props.collapsed ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "hv-thumbs__list", role: "list", children: props.thumbnails.map((th, idx) => {
|
|
169
|
-
const p = idx + 1;
|
|
170
|
-
const active = p === props.currentPage;
|
|
171
|
-
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
172
|
-
"button",
|
|
173
|
-
{
|
|
174
|
-
type: "button",
|
|
175
|
-
role: "listitem",
|
|
176
|
-
className: active ? "hv-thumb hv-thumb--active" : "hv-thumb",
|
|
177
|
-
onClick: () => props.onSelectPage(p),
|
|
178
|
-
"aria-current": active ? "page" : void 0,
|
|
179
|
-
children: [
|
|
180
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "hv-thumb__img", "aria-hidden": true, children: th.dataUrl ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("img", { src: th.dataUrl, alt: "" }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "hv-thumb__placeholder" }) }),
|
|
181
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "hv-thumb__label", children: th.label })
|
|
182
|
-
]
|
|
183
|
-
},
|
|
184
|
-
th.id
|
|
185
|
-
);
|
|
186
|
-
}) }) : null
|
|
187
|
-
] });
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
// src/components/SignaturePanel.tsx
|
|
191
|
-
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
192
|
-
function SignaturePanel(props) {
|
|
193
|
-
const title = props.locale["signatures.title"] ?? "Signatures";
|
|
194
|
-
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("aside", { className: props.collapsed ? "hv-side hv-side--collapsed" : "hv-side", "aria-label": title, children: [
|
|
195
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "hv-sidebar-header", children: [
|
|
196
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { type: "button", className: "hv-icon", onClick: props.onToggle, "aria-label": props.locale["toolbar.signatures"] ?? "Signatures", children: "\u270D" }),
|
|
197
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "hv-sidebar-title", children: title })
|
|
198
|
-
] }),
|
|
199
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "hv-sidebar-body", children: props.signatures.map((s, idx) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "hv-signature-card", children: [
|
|
200
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("img", { src: s.signatureImageUrl, alt: `Signature by ${s.signedBy}`, className: "hv-signature-img" }),
|
|
201
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "hv-signature-meta", children: [
|
|
202
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "hv-signature-name", children: s.signedBy }),
|
|
203
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "hv-signature-date", children: new Date(s.dateSigned).toLocaleString() }),
|
|
204
|
-
s.comment ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "hv-signature-comment", children: s.comment }) : null
|
|
205
|
-
] })
|
|
206
|
-
] }, `${s.signedBy}-${s.dateSigned}-${idx}`)) })
|
|
207
|
-
] });
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
// src/renderers/PdfRenderer.tsx
|
|
211
|
-
var import_react = require("react");
|
|
212
|
-
var import_pdfjs_dist = require("pdfjs-dist");
|
|
213
|
-
var import_jsx_runtime4 = require("react/jsx-runtime");
|
|
214
|
-
var import_meta = {};
|
|
215
|
-
function PdfRenderer(props) {
|
|
216
|
-
const { url, arrayBuffer } = props;
|
|
217
|
-
const [doc, setDoc] = (0, import_react.useState)(null);
|
|
218
|
-
const [pageCount, setPageCount] = (0, import_react.useState)(0);
|
|
219
|
-
const [rendered, setRendered] = (0, import_react.useState)(/* @__PURE__ */ new Map());
|
|
220
|
-
const [thumbs, setThumbs] = (0, import_react.useState)([]);
|
|
221
|
-
const [size, setSize] = (0, import_react.useState)({ w: 840, h: 1188 });
|
|
222
|
-
const containerRef = (0, import_react.useRef)(null);
|
|
223
|
-
(0, import_react.useEffect)(() => {
|
|
224
|
-
try {
|
|
225
|
-
import_pdfjs_dist.GlobalWorkerOptions.workerSrc = new URL("pdfjs-dist/build/pdf.worker.min.mjs", import_meta.url).toString();
|
|
226
|
-
} catch {
|
|
227
|
-
}
|
|
228
|
-
}, []);
|
|
229
|
-
(0, import_react.useEffect)(() => {
|
|
230
|
-
let cancel = false;
|
|
231
|
-
(async () => {
|
|
232
|
-
setDoc(null);
|
|
233
|
-
setRendered(/* @__PURE__ */ new Map());
|
|
234
|
-
setThumbs([]);
|
|
235
|
-
if (!url && !arrayBuffer) return;
|
|
236
|
-
const task = (0, import_pdfjs_dist.getDocument)(url ? { url, rangeChunkSize: 512 * 1024 } : { data: arrayBuffer });
|
|
237
|
-
const pdf = await task.promise;
|
|
238
|
-
if (cancel) return;
|
|
239
|
-
setDoc(pdf);
|
|
240
|
-
setPageCount(pdf.numPages);
|
|
241
|
-
props.onPageCount(pdf.numPages);
|
|
242
|
-
setThumbs(Array.from({ length: pdf.numPages }));
|
|
243
|
-
const p1 = await pdf.getPage(1);
|
|
244
|
-
const base = p1.getViewport({ scale: 1 });
|
|
245
|
-
const w = Math.min(980, Math.max(640, base.width));
|
|
246
|
-
const s = w / base.width;
|
|
247
|
-
const vp = p1.getViewport({ scale: s });
|
|
248
|
-
setSize({ w: Math.round(vp.width), h: Math.round(vp.height) });
|
|
249
|
-
})().catch(() => {
|
|
250
|
-
});
|
|
251
|
-
return () => {
|
|
252
|
-
cancel = true;
|
|
253
|
-
};
|
|
254
|
-
}, [url, arrayBuffer]);
|
|
255
|
-
(0, import_react.useEffect)(() => {
|
|
256
|
-
props.onThumbs(thumbs);
|
|
257
|
-
}, [thumbs]);
|
|
258
|
-
const pagesToShow = (0, import_react.useMemo)(() => {
|
|
259
|
-
if (props.layout === "side-by-side") {
|
|
260
|
-
const left = props.currentPage;
|
|
261
|
-
const right = Math.min(pageCount || left + 1, left + 1);
|
|
262
|
-
return [left, right];
|
|
263
|
-
}
|
|
264
|
-
return [props.currentPage];
|
|
265
|
-
}, [props.currentPage, props.layout, pageCount]);
|
|
266
|
-
(0, import_react.useEffect)(() => {
|
|
267
|
-
if (!doc) return;
|
|
268
|
-
let cancel = false;
|
|
269
|
-
(async () => {
|
|
270
|
-
for (const p of pagesToShow) {
|
|
271
|
-
if (rendered.has(p)) continue;
|
|
272
|
-
const page = await doc.getPage(p);
|
|
273
|
-
if (cancel) return;
|
|
274
|
-
const base = page.getViewport({ scale: 1 });
|
|
275
|
-
const vp = page.getViewport({ scale: size.w / base.width });
|
|
276
|
-
const canvas = document.createElement("canvas");
|
|
277
|
-
canvas.width = Math.round(vp.width);
|
|
278
|
-
canvas.height = Math.round(vp.height);
|
|
279
|
-
const ctx = canvas.getContext("2d", { alpha: false });
|
|
280
|
-
if (!ctx) continue;
|
|
281
|
-
await page.render({ canvasContext: ctx, viewport: vp }).promise;
|
|
282
|
-
if (cancel) return;
|
|
283
|
-
setRendered((prev) => {
|
|
284
|
-
const next = new Map(prev);
|
|
285
|
-
next.set(p, canvas);
|
|
286
|
-
return next;
|
|
287
|
-
});
|
|
288
|
-
try {
|
|
289
|
-
const tw = 140;
|
|
290
|
-
const th = Math.round(tw * (canvas.height / canvas.width));
|
|
291
|
-
const t = document.createElement("canvas");
|
|
292
|
-
t.width = tw;
|
|
293
|
-
t.height = th;
|
|
294
|
-
const tctx = t.getContext("2d");
|
|
295
|
-
if (tctx) {
|
|
296
|
-
tctx.drawImage(canvas, 0, 0, tw, th);
|
|
297
|
-
const url2 = t.toDataURL("image/jpeg", 0.75);
|
|
298
|
-
setThumbs((prev) => {
|
|
299
|
-
const next = prev.slice();
|
|
300
|
-
next[p - 1] = url2;
|
|
301
|
-
return next;
|
|
302
|
-
});
|
|
303
|
-
}
|
|
304
|
-
} catch {
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
})();
|
|
308
|
-
return () => {
|
|
309
|
-
cancel = true;
|
|
310
|
-
};
|
|
311
|
-
}, [doc, pagesToShow, size.w, rendered]);
|
|
312
|
-
function onWheel(e) {
|
|
313
|
-
if (!pageCount) return;
|
|
314
|
-
if (Math.abs(e.deltaY) < 10) return;
|
|
315
|
-
const dir = e.deltaY > 0 ? 1 : -1;
|
|
316
|
-
const step = props.layout === "side-by-side" ? 2 : 1;
|
|
317
|
-
const next = Math.max(1, Math.min(pageCount, props.currentPage + dir * step));
|
|
318
|
-
props.onCurrentPageChange(next);
|
|
319
|
-
}
|
|
320
|
-
function clickPlace(e, page) {
|
|
321
|
-
const stamp = props.signatureStamp;
|
|
322
|
-
if (!stamp?.armed) return;
|
|
323
|
-
const rect = e.currentTarget.getBoundingClientRect();
|
|
324
|
-
const x = (e.clientX - rect.left) / rect.width;
|
|
325
|
-
const y = (e.clientY - rect.top) / rect.height;
|
|
326
|
-
stamp.onPlaced({ page, x, y, w: 0.22, h: 0.08 });
|
|
327
|
-
}
|
|
328
|
-
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "hv-doc", ref: containerRef, onWheel, children: [
|
|
329
|
-
!doc ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "hv-loading", children: "Loading PDF\u2026" }) : null,
|
|
330
|
-
doc ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: props.layout === "side-by-side" ? "hv-pages hv-pages--two" : "hv-pages", children: pagesToShow.map((p) => {
|
|
331
|
-
const c = rendered.get(p);
|
|
332
|
-
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "hv-page", style: { width: size.w, height: size.h }, onClick: (e) => clickPlace(e, p), children: c ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
333
|
-
"canvas",
|
|
334
|
-
{
|
|
335
|
-
className: "hv-canvas",
|
|
336
|
-
width: c.width,
|
|
337
|
-
height: c.height,
|
|
338
|
-
ref: (node) => {
|
|
339
|
-
if (!node) return;
|
|
340
|
-
const ctx = node.getContext("2d");
|
|
341
|
-
if (ctx) ctx.drawImage(c, 0, 0);
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "hv-loading", children: "Rendering\u2026" }) }, p);
|
|
345
|
-
}) }) : null
|
|
346
|
-
] });
|
|
347
|
-
}
|
|
348
|
-
|
|
349
40
|
// src/editors/RichTextEditor.tsx
|
|
350
|
-
var
|
|
41
|
+
var import_html2canvas = __toESM(require("html2canvas"), 1);
|
|
351
42
|
var import_mammoth = __toESM(require("mammoth"), 1);
|
|
352
43
|
var import_markdown_it = __toESM(require("markdown-it"), 1);
|
|
353
|
-
var
|
|
44
|
+
var import_react = require("react");
|
|
354
45
|
|
|
355
46
|
// src/utils/sanitize.ts
|
|
356
47
|
var import_dompurify = __toESM(require("dompurify"), 1);
|
|
@@ -362,44 +53,53 @@ function sanitizeHtml(html) {
|
|
|
362
53
|
}
|
|
363
54
|
|
|
364
55
|
// src/editors/RichTextEditor.tsx
|
|
365
|
-
var
|
|
56
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
366
57
|
var PAGE_H = 1122;
|
|
367
|
-
var RichTextEditor = (0,
|
|
58
|
+
var RichTextEditor = (0, import_react.forwardRef)((props, ref) => {
|
|
368
59
|
const readOnly = props.mode === "view";
|
|
369
|
-
const md = (0,
|
|
60
|
+
const md = (0, import_react.useMemo)(
|
|
370
61
|
() => new import_markdown_it.default({ html: false, linkify: true, breaks: true }),
|
|
371
62
|
[]
|
|
372
63
|
);
|
|
373
|
-
const scrollerRef = (0,
|
|
374
|
-
const editorRef = (0,
|
|
375
|
-
const captureRef = (0,
|
|
376
|
-
const [html, setHtml] = (0,
|
|
377
|
-
(0,
|
|
64
|
+
const scrollerRef = (0, import_react.useRef)(null);
|
|
65
|
+
const editorRef = (0, import_react.useRef)(null);
|
|
66
|
+
const captureRef = (0, import_react.useRef)(null);
|
|
67
|
+
const [html, setHtml] = (0, import_react.useState)("<p><br/></p>");
|
|
68
|
+
(0, import_react.useEffect)(() => {
|
|
378
69
|
let cancelled = false;
|
|
379
70
|
(async () => {
|
|
380
71
|
if (props.mode === "create") {
|
|
381
72
|
setHtml("<p><br/></p>");
|
|
382
73
|
return;
|
|
383
74
|
}
|
|
384
|
-
if (!props.arrayBuffer)
|
|
75
|
+
if (!props.arrayBuffer) {
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
385
78
|
if (props.fileType === "docx") {
|
|
386
79
|
const res = await import_mammoth.default.convertToHtml({
|
|
387
80
|
arrayBuffer: props.arrayBuffer
|
|
388
81
|
});
|
|
389
|
-
if (!cancelled)
|
|
82
|
+
if (!cancelled) {
|
|
83
|
+
setHtml(sanitizeHtml(res.value || "<p><br/></p>"));
|
|
84
|
+
}
|
|
390
85
|
} else {
|
|
391
86
|
const text = new TextDecoder().decode(props.arrayBuffer);
|
|
392
|
-
if (props.fileType === "md")
|
|
393
|
-
|
|
87
|
+
if (props.fileType === "md") {
|
|
88
|
+
setHtml(sanitizeHtml(md.render(text)));
|
|
89
|
+
} else {
|
|
90
|
+
setHtml(`<pre>${escapeHtml(text)}</pre>`);
|
|
91
|
+
}
|
|
394
92
|
}
|
|
395
93
|
})();
|
|
396
94
|
return () => {
|
|
397
95
|
cancelled = true;
|
|
398
96
|
};
|
|
399
97
|
}, [props.arrayBuffer, props.fileType, props.mode, md]);
|
|
400
|
-
(0,
|
|
98
|
+
(0, import_react.useEffect)(() => {
|
|
401
99
|
const el = scrollerRef.current;
|
|
402
|
-
if (!el)
|
|
100
|
+
if (!el) {
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
403
103
|
const recompute = () => props.onPageCount(Math.max(1, Math.ceil(el.scrollHeight / PAGE_H)));
|
|
404
104
|
recompute();
|
|
405
105
|
const ro = new ResizeObserver(recompute);
|
|
@@ -407,13 +107,19 @@ var RichTextEditor = (0, import_react2.forwardRef)((props, ref) => {
|
|
|
407
107
|
return () => ro.disconnect();
|
|
408
108
|
}, [html, props.headerFooterEnabled]);
|
|
409
109
|
function exec(cmd) {
|
|
410
|
-
if (readOnly)
|
|
110
|
+
if (readOnly) {
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
411
113
|
document.execCommand(cmd);
|
|
412
114
|
}
|
|
413
115
|
function onClick(e) {
|
|
414
|
-
if (!props.armedSignatureUrl)
|
|
116
|
+
if (!props.armedSignatureUrl) {
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
415
119
|
const scroller = scrollerRef.current;
|
|
416
|
-
if (!scroller)
|
|
120
|
+
if (!scroller) {
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
417
123
|
const rect = e.currentTarget.getBoundingClientRect();
|
|
418
124
|
const absY = scroller.scrollTop + (e.clientY - rect.top);
|
|
419
125
|
const page = Math.max(1, Math.floor(absY / PAGE_H) + 1);
|
|
@@ -425,7 +131,9 @@ var RichTextEditor = (0, import_react2.forwardRef)((props, ref) => {
|
|
|
425
131
|
async function requestThumbnail(index) {
|
|
426
132
|
const scroller = scrollerRef.current;
|
|
427
133
|
const capture = captureRef.current;
|
|
428
|
-
if (!scroller || !capture)
|
|
134
|
+
if (!scroller || !capture) {
|
|
135
|
+
return void 0;
|
|
136
|
+
}
|
|
429
137
|
const old = scroller.scrollTop;
|
|
430
138
|
scroller.scrollTop = index * PAGE_H;
|
|
431
139
|
await new Promise((r) => requestAnimationFrame(() => r(null)));
|
|
@@ -465,7 +173,9 @@ var RichTextEditor = (0, import_react2.forwardRef)((props, ref) => {
|
|
|
465
173
|
fileName: replaceExt(props.fileName, "docx")
|
|
466
174
|
})
|
|
467
175
|
});
|
|
468
|
-
if (!response.ok)
|
|
176
|
+
if (!response.ok) {
|
|
177
|
+
throw new Error("Failed to generate DOCX");
|
|
178
|
+
}
|
|
469
179
|
const blob = await response.blob();
|
|
470
180
|
const url = window.URL.createObjectURL(blob);
|
|
471
181
|
const a = document.createElement("a");
|
|
@@ -492,10 +202,10 @@ var RichTextEditor = (0, import_react2.forwardRef)((props, ref) => {
|
|
|
492
202
|
annotations: { signaturePlacements: props.signaturePlacements }
|
|
493
203
|
});
|
|
494
204
|
}
|
|
495
|
-
(0,
|
|
496
|
-
return /* @__PURE__ */ (0,
|
|
497
|
-
/* @__PURE__ */ (0,
|
|
498
|
-
/* @__PURE__ */ (0,
|
|
205
|
+
(0, import_react.useImperativeHandle)(ref, () => ({ save, requestThumbnail }));
|
|
206
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "hv-doc", children: [
|
|
207
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "hv-ribbon", role: "toolbar", children: [
|
|
208
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
499
209
|
"button",
|
|
500
210
|
{
|
|
501
211
|
className: "hv-btn",
|
|
@@ -504,7 +214,7 @@ var RichTextEditor = (0, import_react2.forwardRef)((props, ref) => {
|
|
|
504
214
|
children: "B"
|
|
505
215
|
}
|
|
506
216
|
),
|
|
507
|
-
/* @__PURE__ */ (0,
|
|
217
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
508
218
|
"button",
|
|
509
219
|
{
|
|
510
220
|
className: "hv-btn",
|
|
@@ -513,7 +223,7 @@ var RichTextEditor = (0, import_react2.forwardRef)((props, ref) => {
|
|
|
513
223
|
children: "I"
|
|
514
224
|
}
|
|
515
225
|
),
|
|
516
|
-
/* @__PURE__ */ (0,
|
|
226
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
517
227
|
"button",
|
|
518
228
|
{
|
|
519
229
|
className: "hv-btn",
|
|
@@ -522,11 +232,11 @@ var RichTextEditor = (0, import_react2.forwardRef)((props, ref) => {
|
|
|
522
232
|
children: "U"
|
|
523
233
|
}
|
|
524
234
|
),
|
|
525
|
-
props.armedSignatureUrl ? /* @__PURE__ */ (0,
|
|
235
|
+
props.armedSignatureUrl ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "hv-hint", children: "Click to place signature" }) : null
|
|
526
236
|
] }),
|
|
527
|
-
/* @__PURE__ */ (0,
|
|
528
|
-
props.headerFooterEnabled && props.headerComponent ? /* @__PURE__ */ (0,
|
|
529
|
-
/* @__PURE__ */ (0,
|
|
237
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "hv-scroll", ref: scrollerRef, onClick, children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "hv-pageStage", ref: captureRef, children: [
|
|
238
|
+
props.headerFooterEnabled && props.headerComponent ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "hv-letterhead", children: props.headerComponent }) : null,
|
|
239
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
530
240
|
"div",
|
|
531
241
|
{
|
|
532
242
|
ref: editorRef,
|
|
@@ -537,9 +247,9 @@ var RichTextEditor = (0, import_react2.forwardRef)((props, ref) => {
|
|
|
537
247
|
dangerouslySetInnerHTML: { __html: html }
|
|
538
248
|
}
|
|
539
249
|
),
|
|
540
|
-
props.headerFooterEnabled && props.footerComponent ? /* @__PURE__ */ (0,
|
|
541
|
-
props.mode === "create" && props.signatures.length ? /* @__PURE__ */ (0,
|
|
542
|
-
/* @__PURE__ */ (0,
|
|
250
|
+
props.headerFooterEnabled && props.footerComponent ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "hv-letterhead hv-letterhead--footer", children: props.footerComponent }) : null,
|
|
251
|
+
props.mode === "create" && props.signatures.length ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "hv-signatures-inline", children: props.signatures.map((s, i) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "hv-sign-inline", children: [
|
|
252
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
543
253
|
"img",
|
|
544
254
|
{
|
|
545
255
|
src: s.signatureImageUrl,
|
|
@@ -547,9 +257,9 @@ var RichTextEditor = (0, import_react2.forwardRef)((props, ref) => {
|
|
|
547
257
|
className: "hv-sign-img"
|
|
548
258
|
}
|
|
549
259
|
),
|
|
550
|
-
/* @__PURE__ */ (0,
|
|
551
|
-
/* @__PURE__ */ (0,
|
|
552
|
-
/* @__PURE__ */ (0,
|
|
260
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
|
|
261
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "hv-sign-name", children: s.signedBy }),
|
|
262
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "hv-sign-date", children: new Date(s.dateSigned).toLocaleString() })
|
|
553
263
|
] })
|
|
554
264
|
] }, i)) }) : null
|
|
555
265
|
] }) })
|
|
@@ -564,14 +274,103 @@ function escapeHtml(s) {
|
|
|
564
274
|
}
|
|
565
275
|
|
|
566
276
|
// src/editors/SpreadsheetEditor.tsx
|
|
567
|
-
var
|
|
568
|
-
var XLSX = __toESM(require("xlsx"), 1);
|
|
569
|
-
|
|
570
|
-
|
|
277
|
+
var import_react2 = require("react");
|
|
278
|
+
var XLSX = __toESM(require("xlsx"), 1);
|
|
279
|
+
|
|
280
|
+
// src/utils/fileSource.ts
|
|
281
|
+
function guessFileType(name, explicit) {
|
|
282
|
+
if (explicit) {
|
|
283
|
+
return explicit;
|
|
284
|
+
}
|
|
285
|
+
const ext = (name?.split(".").pop() || "").toLowerCase();
|
|
286
|
+
const allowed = [
|
|
287
|
+
"pdf",
|
|
288
|
+
"md",
|
|
289
|
+
"docx",
|
|
290
|
+
"xlsx",
|
|
291
|
+
"pptx",
|
|
292
|
+
"txt",
|
|
293
|
+
"png",
|
|
294
|
+
"jpg",
|
|
295
|
+
"svg"
|
|
296
|
+
];
|
|
297
|
+
return allowed.includes(ext) ? ext : "txt";
|
|
298
|
+
}
|
|
299
|
+
function arrayBufferToBase64(buf) {
|
|
300
|
+
const bytes = new Uint8Array(buf);
|
|
301
|
+
let binary = "";
|
|
302
|
+
const chunk = 32768;
|
|
303
|
+
for (let i = 0; i < bytes.length; i += chunk) {
|
|
304
|
+
binary += String.fromCharCode(...bytes.subarray(i, i + chunk));
|
|
305
|
+
}
|
|
306
|
+
return btoa(binary);
|
|
307
|
+
}
|
|
308
|
+
async function base64ToArrayBuffer(b64) {
|
|
309
|
+
const bin = atob(b64);
|
|
310
|
+
const len = bin.length;
|
|
311
|
+
const bytes = new Uint8Array(len);
|
|
312
|
+
for (let i = 0; i < len; i++) {
|
|
313
|
+
bytes[i] = bin.charCodeAt(i);
|
|
314
|
+
}
|
|
315
|
+
return bytes.buffer;
|
|
316
|
+
}
|
|
317
|
+
async function resolveSource(args) {
|
|
318
|
+
const fileType = guessFileType(args.fileName, args.fileType);
|
|
319
|
+
const fileName = args.fileName ?? `document.${fileType}`;
|
|
320
|
+
if (args.blob) {
|
|
321
|
+
const ab = await args.blob.arrayBuffer();
|
|
322
|
+
const url = URL.createObjectURL(args.blob);
|
|
323
|
+
return { fileType, fileName, arrayBuffer: ab, url };
|
|
324
|
+
}
|
|
325
|
+
if (args.base64) {
|
|
326
|
+
const ab = await base64ToArrayBuffer(args.base64);
|
|
327
|
+
return { fileType, fileName, arrayBuffer: ab };
|
|
328
|
+
}
|
|
329
|
+
if (!args.fileUrl) {
|
|
330
|
+
throw new Error("No file source provided. Use fileUrl, blob, or base64.");
|
|
331
|
+
}
|
|
332
|
+
const res = await fetch(args.fileUrl);
|
|
333
|
+
if (!res.ok) {
|
|
334
|
+
throw new Error(`Failed to fetch file (${res.status})`);
|
|
335
|
+
}
|
|
336
|
+
const total = Number(res.headers.get("content-length") || "") || void 0;
|
|
337
|
+
if (!res.body) {
|
|
338
|
+
const ab = await res.arrayBuffer();
|
|
339
|
+
args.onProgress?.(ab.byteLength, total);
|
|
340
|
+
return { fileType, fileName, arrayBuffer: ab, url: args.fileUrl };
|
|
341
|
+
}
|
|
342
|
+
const reader = res.body.getReader();
|
|
343
|
+
const chunks = [];
|
|
344
|
+
let loaded = 0;
|
|
345
|
+
while (true) {
|
|
346
|
+
const { done, value } = await reader.read();
|
|
347
|
+
if (done) {
|
|
348
|
+
break;
|
|
349
|
+
}
|
|
350
|
+
if (value) {
|
|
351
|
+
chunks.push(value);
|
|
352
|
+
loaded += value.length;
|
|
353
|
+
args.onProgress?.(loaded, total);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
const out = new Uint8Array(loaded);
|
|
357
|
+
let offset = 0;
|
|
358
|
+
for (const c of chunks) {
|
|
359
|
+
out.set(c, offset);
|
|
360
|
+
offset += c.length;
|
|
361
|
+
}
|
|
362
|
+
return { fileType, fileName, arrayBuffer: out.buffer, url: args.fileUrl };
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// src/editors/SpreadsheetEditor.tsx
|
|
366
|
+
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
367
|
+
var SpreadsheetEditor = (0, import_react2.forwardRef)(function SpreadsheetEditor2(props, ref) {
|
|
571
368
|
const readonly = props.mode === "view";
|
|
572
|
-
const [grid, setGrid] = (0,
|
|
573
|
-
(0,
|
|
574
|
-
if (!props.arrayBuffer)
|
|
369
|
+
const [grid, setGrid] = (0, import_react2.useState)(() => Array.from({ length: 30 }, () => Array.from({ length: 12 }, () => "")));
|
|
370
|
+
(0, import_react2.useEffect)(() => {
|
|
371
|
+
if (!props.arrayBuffer) {
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
575
374
|
try {
|
|
576
375
|
const wb = XLSX.read(props.arrayBuffer, { type: "array" });
|
|
577
376
|
const name = wb.SheetNames[0];
|
|
@@ -595,24 +394,24 @@ var SpreadsheetEditor = (0, import_react3.forwardRef)(function SpreadsheetEditor
|
|
|
595
394
|
const b64 = arrayBufferToBase64(out);
|
|
596
395
|
props.onSave(b64, { fileName: ensureExt(props.fileName, "xlsx"), fileType: "xlsx", exportedAsPdf: !!exportPdf });
|
|
597
396
|
}
|
|
598
|
-
(0,
|
|
397
|
+
(0, import_react2.useImperativeHandle)(ref, () => ({
|
|
599
398
|
save,
|
|
600
399
|
requestThumbnails: async () => void 0
|
|
601
400
|
}));
|
|
602
|
-
const cols = (0,
|
|
603
|
-
return /* @__PURE__ */ (0,
|
|
604
|
-
/* @__PURE__ */ (0,
|
|
605
|
-
/* @__PURE__ */ (0,
|
|
606
|
-
!readonly ? /* @__PURE__ */ (0,
|
|
401
|
+
const cols = (0, import_react2.useMemo)(() => Array.from({ length: grid[0]?.length ?? 0 }, (_, i) => String.fromCharCode(65 + i % 26)), [grid]);
|
|
402
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "hv-sheet", children: [
|
|
403
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "hv-sheetbar", children: [
|
|
404
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "hv-sheetbar-title", children: props.fileName }),
|
|
405
|
+
!readonly ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { className: "hv-btn", type: "button", onClick: () => void save(false), children: props.locale["toolbar.save"] ?? "Save" }) : null
|
|
607
406
|
] }),
|
|
608
|
-
/* @__PURE__ */ (0,
|
|
609
|
-
/* @__PURE__ */ (0,
|
|
610
|
-
/* @__PURE__ */ (0,
|
|
611
|
-
cols.map((c, i) => /* @__PURE__ */ (0,
|
|
407
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "hv-sheetgrid", role: "table", "aria-label": "Spreadsheet", children: [
|
|
408
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "hv-sheetrow hv-sheetrow--header", role: "row", children: [
|
|
409
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "hv-sheetcell hv-sheetcell--corner", role: "columnheader" }),
|
|
410
|
+
cols.map((c, i) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "hv-sheetcell hv-sheetcell--header", role: "columnheader", children: c }, i))
|
|
612
411
|
] }),
|
|
613
|
-
grid.map((row, r) => /* @__PURE__ */ (0,
|
|
614
|
-
/* @__PURE__ */ (0,
|
|
615
|
-
row.map((val, c) => /* @__PURE__ */ (0,
|
|
412
|
+
grid.map((row, r) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "hv-sheetrow", role: "row", children: [
|
|
413
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "hv-sheetcell hv-sheetcell--header", role: "rowheader", children: r + 1 }),
|
|
414
|
+
row.map((val, c) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
616
415
|
"div",
|
|
617
416
|
{
|
|
618
417
|
className: "hv-sheetcell",
|
|
@@ -641,39 +440,279 @@ function ensureExt(name, ext) {
|
|
|
641
440
|
}
|
|
642
441
|
|
|
643
442
|
// src/renderers/ImageRenderer.tsx
|
|
644
|
-
var
|
|
645
|
-
var
|
|
646
|
-
function ImageRenderer({
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
443
|
+
var import_react3 = require("react");
|
|
444
|
+
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
445
|
+
function ImageRenderer({
|
|
446
|
+
arrayBuffer,
|
|
447
|
+
fileType,
|
|
448
|
+
fileName
|
|
449
|
+
}) {
|
|
450
|
+
const [zoom, setZoom] = (0, import_react3.useState)(1);
|
|
451
|
+
const url = (0, import_react3.useMemo)(() => {
|
|
452
|
+
if (!arrayBuffer) {
|
|
453
|
+
return void 0;
|
|
454
|
+
}
|
|
650
455
|
const mime = fileType === "svg" ? "image/svg+xml" : fileType === "png" ? "image/png" : "image/jpeg";
|
|
651
456
|
return URL.createObjectURL(new Blob([arrayBuffer], { type: mime }));
|
|
652
457
|
}, [arrayBuffer, fileType]);
|
|
653
|
-
(0,
|
|
458
|
+
(0, import_react3.useEffect)(() => {
|
|
654
459
|
return () => {
|
|
655
|
-
if (url)
|
|
460
|
+
if (url) {
|
|
461
|
+
URL.revokeObjectURL(url);
|
|
462
|
+
}
|
|
656
463
|
};
|
|
657
464
|
}, [url]);
|
|
658
|
-
return /* @__PURE__ */ (0,
|
|
659
|
-
/* @__PURE__ */ (0,
|
|
660
|
-
/* @__PURE__ */ (0,
|
|
661
|
-
/* @__PURE__ */ (0,
|
|
662
|
-
/* @__PURE__ */ (0,
|
|
663
|
-
|
|
465
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "hv-doc", children: [
|
|
466
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "hv-mini-toolbar", children: [
|
|
467
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "hv-title", children: fileName }),
|
|
468
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "hv-spacer" }),
|
|
469
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
470
|
+
"button",
|
|
471
|
+
{
|
|
472
|
+
type: "button",
|
|
473
|
+
className: "hv-btn",
|
|
474
|
+
onClick: () => setZoom((z) => Math.max(0.25, z - 0.25)),
|
|
475
|
+
children: "-"
|
|
476
|
+
}
|
|
477
|
+
),
|
|
478
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "hv-zoom", children: [
|
|
664
479
|
Math.round(zoom * 100),
|
|
665
480
|
"%"
|
|
666
481
|
] }),
|
|
667
|
-
/* @__PURE__ */ (0,
|
|
482
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
483
|
+
"button",
|
|
484
|
+
{
|
|
485
|
+
type: "button",
|
|
486
|
+
className: "hv-btn",
|
|
487
|
+
onClick: () => setZoom((z) => Math.min(4, z + 0.25)),
|
|
488
|
+
children: "+"
|
|
489
|
+
}
|
|
490
|
+
)
|
|
668
491
|
] }),
|
|
669
|
-
/* @__PURE__ */ (0,
|
|
492
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "hv-center", children: [
|
|
493
|
+
!arrayBuffer && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "hv-error", children: "No image data provided." }),
|
|
494
|
+
arrayBuffer && !url && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "hv-error", children: "Failed to load image." }),
|
|
495
|
+
url && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
496
|
+
"img",
|
|
497
|
+
{
|
|
498
|
+
src: url,
|
|
499
|
+
alt: fileName,
|
|
500
|
+
style: { transform: `scale(${zoom})` },
|
|
501
|
+
className: "hv-image"
|
|
502
|
+
}
|
|
503
|
+
)
|
|
504
|
+
] })
|
|
505
|
+
] });
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
// src/renderers/PdfRenderer.tsx
|
|
509
|
+
var import_pdfjs_dist = require("pdfjs-dist");
|
|
510
|
+
var import_react4 = require("react");
|
|
511
|
+
var import_jsx_runtime4 = require("react/jsx-runtime");
|
|
512
|
+
var import_meta = {};
|
|
513
|
+
function PdfRenderer(props) {
|
|
514
|
+
const { url, arrayBuffer } = props;
|
|
515
|
+
const [doc, setDoc] = (0, import_react4.useState)(null);
|
|
516
|
+
const [pageCount, setPageCount] = (0, import_react4.useState)(0);
|
|
517
|
+
const [rendered, setRendered] = (0, import_react4.useState)(
|
|
518
|
+
/* @__PURE__ */ new Map()
|
|
519
|
+
);
|
|
520
|
+
const [thumbs, setThumbs] = (0, import_react4.useState)([]);
|
|
521
|
+
const [size, setSize] = (0, import_react4.useState)({ w: 840, h: 1188 });
|
|
522
|
+
const [error, setError] = (0, import_react4.useState)(null);
|
|
523
|
+
const [loading, setLoading] = (0, import_react4.useState)(false);
|
|
524
|
+
const containerRef = (0, import_react4.useRef)(null);
|
|
525
|
+
(0, import_react4.useEffect)(() => {
|
|
526
|
+
try {
|
|
527
|
+
import_pdfjs_dist.GlobalWorkerOptions.workerSrc = new URL(
|
|
528
|
+
"pdfjs-dist/build/pdf.worker.min.mjs",
|
|
529
|
+
import_meta.url
|
|
530
|
+
).toString();
|
|
531
|
+
} catch {
|
|
532
|
+
}
|
|
533
|
+
}, []);
|
|
534
|
+
(0, import_react4.useEffect)(() => {
|
|
535
|
+
let cancel = false;
|
|
536
|
+
setError(null);
|
|
537
|
+
setLoading(true);
|
|
538
|
+
(async () => {
|
|
539
|
+
setDoc(null);
|
|
540
|
+
setRendered(/* @__PURE__ */ new Map());
|
|
541
|
+
setThumbs([]);
|
|
542
|
+
if (!url && !arrayBuffer) {
|
|
543
|
+
setError("No PDF source provided.");
|
|
544
|
+
setLoading(false);
|
|
545
|
+
return;
|
|
546
|
+
}
|
|
547
|
+
try {
|
|
548
|
+
const task = (0, import_pdfjs_dist.getDocument)(
|
|
549
|
+
url ? { url, rangeChunkSize: 512 * 1024 } : { data: arrayBuffer }
|
|
550
|
+
);
|
|
551
|
+
const pdf = await task.promise;
|
|
552
|
+
if (cancel) {
|
|
553
|
+
return;
|
|
554
|
+
}
|
|
555
|
+
setDoc(pdf);
|
|
556
|
+
setPageCount(pdf.numPages);
|
|
557
|
+
props.onPageCount(pdf.numPages);
|
|
558
|
+
const p1 = await pdf.getPage(1);
|
|
559
|
+
const base = p1.getViewport({ scale: 1 });
|
|
560
|
+
const w = Math.min(980, Math.max(640, base.width));
|
|
561
|
+
const s = w / base.width;
|
|
562
|
+
const vp = p1.getViewport({ scale: s });
|
|
563
|
+
setSize({ w: Math.round(vp.width), h: Math.round(vp.height) });
|
|
564
|
+
const thumbWidth = 56;
|
|
565
|
+
const thumbsArr = [];
|
|
566
|
+
for (let i = 1; i <= pdf.numPages; i++) {
|
|
567
|
+
const page = await pdf.getPage(i);
|
|
568
|
+
const pageBase = page.getViewport({ scale: 1 });
|
|
569
|
+
const thumbScale = thumbWidth / pageBase.width;
|
|
570
|
+
const thumbVp = page.getViewport({ scale: thumbScale });
|
|
571
|
+
const thumbCanvas = document.createElement("canvas");
|
|
572
|
+
thumbCanvas.width = Math.round(thumbVp.width);
|
|
573
|
+
thumbCanvas.height = Math.round(thumbVp.height);
|
|
574
|
+
const thumbCtx = thumbCanvas.getContext("2d", { alpha: false });
|
|
575
|
+
if (thumbCtx) {
|
|
576
|
+
await page.render({ canvasContext: thumbCtx, viewport: thumbVp }).promise;
|
|
577
|
+
thumbsArr.push(thumbCanvas.toDataURL("image/png"));
|
|
578
|
+
} else {
|
|
579
|
+
thumbsArr.push(void 0);
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
setThumbs(thumbsArr);
|
|
583
|
+
} catch (e) {
|
|
584
|
+
setError(
|
|
585
|
+
"Failed to load PDF. " + (e instanceof Error ? e.message : "")
|
|
586
|
+
);
|
|
587
|
+
} finally {
|
|
588
|
+
setLoading(false);
|
|
589
|
+
}
|
|
590
|
+
})();
|
|
591
|
+
return () => {
|
|
592
|
+
cancel = true;
|
|
593
|
+
};
|
|
594
|
+
}, [url, arrayBuffer]);
|
|
595
|
+
(0, import_react4.useEffect)(() => {
|
|
596
|
+
props.onThumbs(thumbs);
|
|
597
|
+
}, [thumbs]);
|
|
598
|
+
const pagesToShow = (0, import_react4.useMemo)(() => {
|
|
599
|
+
if (props.layout === "side-by-side") {
|
|
600
|
+
const left = props.currentPage;
|
|
601
|
+
const right = Math.min(pageCount || left + 1, left + 1);
|
|
602
|
+
return [left, right];
|
|
603
|
+
}
|
|
604
|
+
return [props.currentPage];
|
|
605
|
+
}, [props.currentPage, props.layout, pageCount]);
|
|
606
|
+
(0, import_react4.useEffect)(() => {
|
|
607
|
+
if (!doc) {
|
|
608
|
+
return;
|
|
609
|
+
}
|
|
610
|
+
let cancel = false;
|
|
611
|
+
(async () => {
|
|
612
|
+
for (const p of pagesToShow) {
|
|
613
|
+
if (rendered.has(p)) {
|
|
614
|
+
continue;
|
|
615
|
+
}
|
|
616
|
+
try {
|
|
617
|
+
const page = await doc.getPage(p);
|
|
618
|
+
if (cancel) {
|
|
619
|
+
return;
|
|
620
|
+
}
|
|
621
|
+
const base = page.getViewport({ scale: 1 });
|
|
622
|
+
const vp = page.getViewport({ scale: size.w / base.width });
|
|
623
|
+
const canvas = document.createElement("canvas");
|
|
624
|
+
canvas.width = Math.round(vp.width);
|
|
625
|
+
canvas.height = Math.round(vp.height);
|
|
626
|
+
const ctx = canvas.getContext("2d", { alpha: false });
|
|
627
|
+
if (!ctx) {
|
|
628
|
+
continue;
|
|
629
|
+
}
|
|
630
|
+
await page.render({ canvasContext: ctx, viewport: vp }).promise;
|
|
631
|
+
if (cancel) {
|
|
632
|
+
return;
|
|
633
|
+
}
|
|
634
|
+
setRendered((prev) => {
|
|
635
|
+
const next = new Map(prev);
|
|
636
|
+
next.set(p, canvas);
|
|
637
|
+
return next;
|
|
638
|
+
});
|
|
639
|
+
} catch {
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
})();
|
|
643
|
+
return () => {
|
|
644
|
+
cancel = true;
|
|
645
|
+
};
|
|
646
|
+
}, [doc, pagesToShow, size.w, rendered]);
|
|
647
|
+
function onWheel(e) {
|
|
648
|
+
if (!pageCount) {
|
|
649
|
+
return;
|
|
650
|
+
}
|
|
651
|
+
if (Math.abs(e.deltaY) < 10) {
|
|
652
|
+
return;
|
|
653
|
+
}
|
|
654
|
+
const dir = e.deltaY > 0 ? 1 : -1;
|
|
655
|
+
const step = props.layout === "side-by-side" ? 2 : 1;
|
|
656
|
+
const next = Math.max(
|
|
657
|
+
1,
|
|
658
|
+
Math.min(pageCount, props.currentPage + dir * step)
|
|
659
|
+
);
|
|
660
|
+
props.onCurrentPageChange(next);
|
|
661
|
+
}
|
|
662
|
+
function clickPlace(e, page) {
|
|
663
|
+
const stamp = props.signatureStamp;
|
|
664
|
+
if (!stamp?.armed) {
|
|
665
|
+
return;
|
|
666
|
+
}
|
|
667
|
+
const rect = e.currentTarget.getBoundingClientRect();
|
|
668
|
+
const x = (e.clientX - rect.left) / rect.width;
|
|
669
|
+
const y = (e.clientY - rect.top) / rect.height;
|
|
670
|
+
stamp.onPlaced({ page, x, y, w: 0.22, h: 0.08 });
|
|
671
|
+
}
|
|
672
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "hv-doc", ref: containerRef, onWheel, children: [
|
|
673
|
+
!doc ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "hv-loading", children: "Loading PDF\u2026" }) : null,
|
|
674
|
+
doc ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
675
|
+
"div",
|
|
676
|
+
{
|
|
677
|
+
className: props.layout === "side-by-side" ? "hv-pages hv-pages--two" : "hv-pages",
|
|
678
|
+
children: pagesToShow.map((p) => {
|
|
679
|
+
const c = rendered.get(p);
|
|
680
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
681
|
+
"div",
|
|
682
|
+
{
|
|
683
|
+
className: "hv-page",
|
|
684
|
+
style: { width: size.w, height: size.h },
|
|
685
|
+
onClick: (e) => clickPlace(e, p),
|
|
686
|
+
children: c ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
687
|
+
"canvas",
|
|
688
|
+
{
|
|
689
|
+
className: "hv-canvas",
|
|
690
|
+
width: c.width,
|
|
691
|
+
height: c.height,
|
|
692
|
+
ref: (node) => {
|
|
693
|
+
if (!node) {
|
|
694
|
+
return;
|
|
695
|
+
}
|
|
696
|
+
const ctx = node.getContext("2d");
|
|
697
|
+
if (ctx) {
|
|
698
|
+
ctx.drawImage(c, 0, 0);
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "hv-loading", children: "Rendering\u2026" })
|
|
703
|
+
},
|
|
704
|
+
p
|
|
705
|
+
);
|
|
706
|
+
})
|
|
707
|
+
}
|
|
708
|
+
) : null
|
|
670
709
|
] });
|
|
671
710
|
}
|
|
672
711
|
|
|
673
712
|
// src/renderers/PptxRenderer.tsx
|
|
674
713
|
var import_react5 = require("react");
|
|
675
714
|
var import_jszip = __toESM(require("jszip"), 1);
|
|
676
|
-
var
|
|
715
|
+
var import_jsx_runtime5 = require("react/jsx-runtime");
|
|
677
716
|
function decodeXml(s) {
|
|
678
717
|
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, '"').replace(/'/g, "'");
|
|
679
718
|
}
|
|
@@ -683,63 +722,208 @@ function extractText(xml) {
|
|
|
683
722
|
function PptxRenderer(props) {
|
|
684
723
|
const [slides, setSlides] = (0, import_react5.useState)([]);
|
|
685
724
|
const [thumbs, setThumbs] = (0, import_react5.useState)([]);
|
|
725
|
+
const [error, setError] = (0, import_react5.useState)(null);
|
|
726
|
+
const [loading, setLoading] = (0, import_react5.useState)(false);
|
|
686
727
|
(0, import_react5.useEffect)(() => {
|
|
687
|
-
let
|
|
728
|
+
let cancel = false;
|
|
729
|
+
setError(null);
|
|
730
|
+
setLoading(true);
|
|
688
731
|
(async () => {
|
|
689
732
|
setSlides([]);
|
|
690
733
|
setThumbs([]);
|
|
691
734
|
if (!props.arrayBuffer) {
|
|
692
|
-
|
|
693
|
-
|
|
735
|
+
setError("No PPTX source provided.");
|
|
736
|
+
setLoading(false);
|
|
694
737
|
return;
|
|
695
738
|
}
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
const
|
|
699
|
-
const
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
739
|
+
try {
|
|
740
|
+
const zip = await import_jszip.default.loadAsync(props.arrayBuffer);
|
|
741
|
+
const slidePaths = Object.keys(zip.files).filter((p) => /^ppt\/slides\/slide\d+\.xml$/.test(p)).sort();
|
|
742
|
+
const slidesOut = [];
|
|
743
|
+
for (let i = 0; i < slidePaths.length; i++) {
|
|
744
|
+
const xml = await zip.files[slidePaths[i]].async("string");
|
|
745
|
+
slidesOut.push({ index: i + 1, text: extractText(xml) });
|
|
746
|
+
}
|
|
747
|
+
if (cancel) return;
|
|
748
|
+
setSlides(
|
|
749
|
+
slidesOut.length ? slidesOut : [{ index: 1, text: "(empty)" }]
|
|
750
|
+
);
|
|
751
|
+
props.onSlideCount(slidesOut.length || 1);
|
|
752
|
+
const thumbWidth = 56;
|
|
753
|
+
const thumbsArr = [];
|
|
754
|
+
for (let i = 0; i < (slidesOut.length || 1); i++) {
|
|
755
|
+
thumbsArr.push(
|
|
756
|
+
`data:image/svg+xml;charset=utf-8,${encodeURIComponent(svgThumb(i + 1))}`
|
|
757
|
+
);
|
|
758
|
+
}
|
|
759
|
+
setThumbs(thumbsArr);
|
|
760
|
+
} catch (e) {
|
|
761
|
+
setSlides([
|
|
762
|
+
{ index: 1, text: "Unable to render this .pptx in-browser." }
|
|
763
|
+
]);
|
|
764
|
+
setThumbs([void 0]);
|
|
765
|
+
setError(
|
|
766
|
+
"Failed to load PPTX. " + (e instanceof Error ? e.message : "")
|
|
767
|
+
);
|
|
768
|
+
} finally {
|
|
769
|
+
setLoading(false);
|
|
706
770
|
}
|
|
707
|
-
|
|
708
|
-
const count = Math.max(1, out.length);
|
|
709
|
-
props.onSlideCount(count);
|
|
710
|
-
setSlides(out.length ? out : [{ index: 1, text: "(empty)" }]);
|
|
711
|
-
setThumbs(Array.from({ length: count }, (_, i) => `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svgThumb(i + 1))}`));
|
|
712
|
-
})().catch(() => {
|
|
713
|
-
props.onSlideCount(1);
|
|
714
|
-
setSlides([{ index: 1, text: "Unable to render this .pptx in-browser." }]);
|
|
715
|
-
setThumbs([void 0]);
|
|
716
|
-
});
|
|
771
|
+
})();
|
|
717
772
|
return () => {
|
|
718
|
-
|
|
773
|
+
cancel = true;
|
|
719
774
|
};
|
|
720
775
|
}, [props.arrayBuffer]);
|
|
721
776
|
(0, import_react5.useEffect)(() => {
|
|
722
777
|
props.onThumbs(thumbs);
|
|
723
778
|
}, [thumbs]);
|
|
724
779
|
const pagesToShow = (0, import_react5.useMemo)(() => {
|
|
725
|
-
if (props.layout === "side-by-side")
|
|
780
|
+
if (props.layout === "side-by-side")
|
|
781
|
+
return [
|
|
782
|
+
props.currentPage,
|
|
783
|
+
Math.min(slides.length || props.currentPage + 1, props.currentPage + 1)
|
|
784
|
+
];
|
|
726
785
|
return [props.currentPage];
|
|
727
786
|
}, [props.currentPage, props.layout, slides.length]);
|
|
728
|
-
return /* @__PURE__ */ (0,
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
787
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "hv-doc", children: [
|
|
788
|
+
loading && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "hv-loading", children: "Loading PPTX\u2026" }),
|
|
789
|
+
error && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "hv-error", children: error }),
|
|
790
|
+
!loading && !error && (!slides || slides.length === 0) && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "hv-error", children: "No slides to display." }),
|
|
791
|
+
!error && slides && slides.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
792
|
+
"div",
|
|
793
|
+
{
|
|
794
|
+
className: props.layout === "side-by-side" ? "hv-pages hv-pages--two" : "hv-pages",
|
|
795
|
+
children: pagesToShow.map((p) => {
|
|
796
|
+
const s = slides[p - 1];
|
|
797
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
|
|
798
|
+
"div",
|
|
799
|
+
{
|
|
800
|
+
className: "hv-slide",
|
|
801
|
+
tabIndex: 0,
|
|
802
|
+
onFocus: () => props.onCurrentPageChange(p),
|
|
803
|
+
children: [
|
|
804
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "hv-slide-title", children: [
|
|
805
|
+
"Slide ",
|
|
806
|
+
p
|
|
807
|
+
] }),
|
|
808
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "hv-slide-text", children: s?.text || "" })
|
|
809
|
+
]
|
|
810
|
+
},
|
|
811
|
+
p
|
|
812
|
+
);
|
|
813
|
+
})
|
|
814
|
+
}
|
|
815
|
+
)
|
|
816
|
+
] });
|
|
738
817
|
}
|
|
739
818
|
function svgThumb(n) {
|
|
740
819
|
return `<svg xmlns="http://www.w3.org/2000/svg" width="180" height="100"><rect width="100%" height="100%" rx="12" fill="#111827"/><text x="50%" y="54%" font-size="18" fill="#e5e7eb" text-anchor="middle">${n}</text></svg>`;
|
|
741
820
|
}
|
|
742
821
|
|
|
822
|
+
// src/utils/locale.ts
|
|
823
|
+
var defaultLocale = {
|
|
824
|
+
"loading": "Loading\u2026",
|
|
825
|
+
"error.title": "Error",
|
|
826
|
+
"toolbar.layout.single": "Single page",
|
|
827
|
+
"toolbar.layout.two": "Side-by-side",
|
|
828
|
+
"toolbar.thumbs": "Thumbnails",
|
|
829
|
+
"toolbar.signatures": "Signatures",
|
|
830
|
+
"toolbar.sign": "Sign Document",
|
|
831
|
+
"toolbar.save": "Save",
|
|
832
|
+
"toolbar.exportPdf": "Export as PDF",
|
|
833
|
+
"thumbnails.title": "Thumbnails",
|
|
834
|
+
"thumbnails.page": "Page",
|
|
835
|
+
"signatures.title": "Signatures",
|
|
836
|
+
"signatures.empty": "No signatures",
|
|
837
|
+
"signatures.placeHint": "Click on the document to place the signature.",
|
|
838
|
+
"a11y.viewer": "Document viewer",
|
|
839
|
+
"a11y.ribbon": "Ribbon",
|
|
840
|
+
"a11y.editor": "Document editor"
|
|
841
|
+
};
|
|
842
|
+
|
|
843
|
+
// src/components/SignaturePanel.tsx
|
|
844
|
+
var import_jsx_runtime6 = require("react/jsx-runtime");
|
|
845
|
+
function SignaturePanel(props) {
|
|
846
|
+
const title = props.locale["signatures.title"] ?? "Signatures";
|
|
847
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("aside", { className: props.collapsed ? "hv-side hv-side--collapsed" : "hv-side", "aria-label": title, children: [
|
|
848
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "hv-sidebar-header", children: [
|
|
849
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { type: "button", className: "hv-icon", onClick: props.onToggle, "aria-label": props.locale["toolbar.signatures"] ?? "Signatures", children: "\u270D" }),
|
|
850
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "hv-sidebar-title", children: title })
|
|
851
|
+
] }),
|
|
852
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "hv-sidebar-body", children: props.signatures.map((s, idx) => /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "hv-signature-card", children: [
|
|
853
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("img", { src: s.signatureImageUrl, alt: `Signature by ${s.signedBy}`, className: "hv-signature-img" }),
|
|
854
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "hv-signature-meta", children: [
|
|
855
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "hv-signature-name", children: s.signedBy }),
|
|
856
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "hv-signature-date", children: new Date(s.dateSigned).toLocaleString() }),
|
|
857
|
+
s.comment ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "hv-signature-comment", children: s.comment }) : null
|
|
858
|
+
] })
|
|
859
|
+
] }, `${s.signedBy}-${s.dateSigned}-${idx}`)) })
|
|
860
|
+
] });
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
// src/components/ThumbnailsSidebar.tsx
|
|
864
|
+
var import_jsx_runtime7 = require("react/jsx-runtime");
|
|
865
|
+
function ThumbnailsSidebar(props) {
|
|
866
|
+
const t = props.locale["thumbnails.title"] ?? "Thumbnails";
|
|
867
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("aside", { className: props.collapsed ? "hv-thumbs hv-thumbs--collapsed" : "hv-thumbs", "aria-label": t, children: [
|
|
868
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "hv-thumbs__header", children: [
|
|
869
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
870
|
+
"button",
|
|
871
|
+
{
|
|
872
|
+
type: "button",
|
|
873
|
+
className: "hv-icon",
|
|
874
|
+
onClick: props.onToggle,
|
|
875
|
+
"aria-label": props.collapsed ? props.locale["thumbnails.open"] ?? "Open thumbnails" : props.locale["thumbnails.close"] ?? "Close thumbnails",
|
|
876
|
+
children: props.collapsed ? "\u25B8" : "\u25BE"
|
|
877
|
+
}
|
|
878
|
+
),
|
|
879
|
+
!props.collapsed ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "hv-thumbs__title", children: t }) : null
|
|
880
|
+
] }),
|
|
881
|
+
!props.collapsed ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "hv-thumbs__list", role: "list", children: props.thumbnails.map((th, idx) => {
|
|
882
|
+
const p = idx + 1;
|
|
883
|
+
const active = p === props.currentPage;
|
|
884
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
|
|
885
|
+
"button",
|
|
886
|
+
{
|
|
887
|
+
type: "button",
|
|
888
|
+
role: "listitem",
|
|
889
|
+
className: active ? "hv-thumb hv-thumb--active" : "hv-thumb",
|
|
890
|
+
onClick: () => props.onSelectPage(p),
|
|
891
|
+
"aria-current": active ? "page" : void 0,
|
|
892
|
+
children: [
|
|
893
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "hv-thumb__img", "aria-hidden": true, children: th.dataUrl ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("img", { src: th.dataUrl, alt: "" }) : /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "hv-thumb__placeholder" }) }),
|
|
894
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "hv-thumb__label", children: th.label })
|
|
895
|
+
]
|
|
896
|
+
},
|
|
897
|
+
th.id
|
|
898
|
+
);
|
|
899
|
+
}) }) : null
|
|
900
|
+
] });
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
// src/components/Toolbar.tsx
|
|
904
|
+
var import_jsx_runtime8 = require("react/jsx-runtime");
|
|
905
|
+
function Toolbar(props) {
|
|
906
|
+
const t = (k, fallback) => props.locale[k] ?? fallback;
|
|
907
|
+
return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "hv-toolbar", role: "toolbar", "aria-label": t("a11y.toolbar", "Document toolbar"), children: [
|
|
908
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "hv-toolbar__left", children: [
|
|
909
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("button", { type: "button", className: "hv-btn", onClick: props.onToggleThumbnails, "aria-pressed": props.showThumbnails, children: t("toolbar.thumbs", "Thumbnails") }),
|
|
910
|
+
props.mode !== "create" && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("button", { type: "button", className: "hv-btn", onClick: props.onToggleSignatures, "aria-pressed": props.showSignatures, children: t("toolbar.signatures", "Signatures") }),
|
|
911
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "hv-sep" }),
|
|
912
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("button", { type: "button", className: props.layout === "single" ? "hv-btn hv-btn--active" : "hv-btn", onClick: () => props.onChangeLayout("single"), children: t("toolbar.layout.single", "Single") }),
|
|
913
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("button", { type: "button", className: props.layout === "side-by-side" ? "hv-btn hv-btn--active" : "hv-btn", onClick: () => props.onChangeLayout("side-by-side"), children: t("toolbar.layout.two", "Two") })
|
|
914
|
+
] }),
|
|
915
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "hv-toolbar__right", children: [
|
|
916
|
+
props.showHeaderFooterToggle && /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("label", { className: "hv-toggle", children: [
|
|
917
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("input", { type: "checkbox", checked: props.headerFooterEnabled, onChange: props.onToggleHeaderFooter }),
|
|
918
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { children: t("toolbar.letterhead", "Letterhead") })
|
|
919
|
+
] }),
|
|
920
|
+
props.allowSigning && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("button", { type: "button", className: "hv-btn hv-btn--primary", onClick: props.onSign, disabled: props.signingDisabled, children: t("toolbar.sign", "Sign Document") }),
|
|
921
|
+
props.canExportPdf && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("button", { type: "button", className: "hv-btn", onClick: props.onExportPdf, children: t("toolbar.exportPdf", "Export as PDF") }),
|
|
922
|
+
props.canSave && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("button", { type: "button", className: "hv-btn hv-btn--primary", onClick: props.onSave, children: t("toolbar.save", "Save") })
|
|
923
|
+
] })
|
|
924
|
+
] });
|
|
925
|
+
}
|
|
926
|
+
|
|
743
927
|
// src/components/DocumentViewer.tsx
|
|
744
928
|
var import_jsx_runtime9 = require("react/jsx-runtime");
|
|
745
929
|
function DocumentViewer(props) {
|
|
@@ -785,10 +969,14 @@ function DocumentViewer(props) {
|
|
|
785
969
|
fileName: props.fileName,
|
|
786
970
|
fileType: props.fileType
|
|
787
971
|
});
|
|
788
|
-
if (cancelled)
|
|
972
|
+
if (cancelled) {
|
|
973
|
+
return;
|
|
974
|
+
}
|
|
789
975
|
setResolved({ fileType: res.fileType, fileName: res.fileName, url: res.url, arrayBuffer: res.arrayBuffer });
|
|
790
976
|
} catch (e) {
|
|
791
|
-
if (cancelled)
|
|
977
|
+
if (cancelled) {
|
|
978
|
+
return;
|
|
979
|
+
}
|
|
792
980
|
setError(e instanceof Error ? e.message : String(e));
|
|
793
981
|
}
|
|
794
982
|
})();
|
|
@@ -805,7 +993,9 @@ function DocumentViewer(props) {
|
|
|
805
993
|
}));
|
|
806
994
|
}, [pageCount, thumbs, locale]);
|
|
807
995
|
async function handleSignRequest() {
|
|
808
|
-
if (!allowSigning || signingBusy || !props.onSignRequest)
|
|
996
|
+
if (!allowSigning || signingBusy || !props.onSignRequest) {
|
|
997
|
+
return;
|
|
998
|
+
}
|
|
809
999
|
setSigningBusy(true);
|
|
810
1000
|
try {
|
|
811
1001
|
const sig = await props.onSignRequest();
|
|
@@ -816,7 +1006,9 @@ function DocumentViewer(props) {
|
|
|
816
1006
|
}
|
|
817
1007
|
}
|
|
818
1008
|
function placeSignature(p) {
|
|
819
|
-
if (!armedSignatureUrl)
|
|
1009
|
+
if (!armedSignatureUrl) {
|
|
1010
|
+
return;
|
|
1011
|
+
}
|
|
820
1012
|
setSigPlacements((prev) => [...prev, { ...p, signatureImageUrl: armedSignatureUrl }]);
|
|
821
1013
|
setArmedSignatureUrl(null);
|
|
822
1014
|
}
|
|
@@ -825,7 +1017,9 @@ function DocumentViewer(props) {
|
|
|
825
1017
|
await editorRef.current.save(!!exportPdf);
|
|
826
1018
|
return;
|
|
827
1019
|
}
|
|
828
|
-
if (!resolved?.arrayBuffer)
|
|
1020
|
+
if (!resolved?.arrayBuffer) {
|
|
1021
|
+
return;
|
|
1022
|
+
}
|
|
829
1023
|
const b64 = arrayBufferToBase642(resolved.arrayBuffer);
|
|
830
1024
|
props.onSave?.(b64, { fileName: resolved.fileName, fileType: resolved.fileType, annotations: { sigPlacements } });
|
|
831
1025
|
}
|
|
@@ -860,7 +1054,7 @@ function DocumentViewer(props) {
|
|
|
860
1054
|
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "hv-error-title", children: locale["error.title"] ?? "Error" }),
|
|
861
1055
|
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "hv-error-body", children: error })
|
|
862
1056
|
] }) : null,
|
|
863
|
-
!resolved && !error ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "hv-loading", "aria-busy": "true", children: locale
|
|
1057
|
+
!resolved && !error ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "hv-loading", "aria-busy": "true", children: locale.loading ?? "Loading\u2026" }) : null,
|
|
864
1058
|
resolved ? /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "hv-shell", children: [
|
|
865
1059
|
mode !== "create" ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
866
1060
|
ThumbnailsSidebar,
|