@kitsy/pdf 0.0.0-stage → 0.2.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.
Files changed (132) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/LICENSE +201 -0
  3. package/NOTICE +16 -0
  4. package/README.md +256 -2
  5. package/THIRD_PARTY_NOTICES.md +48 -0
  6. package/dist/chunk-2K5X7L5J.js +43 -0
  7. package/dist/chunk-2K5X7L5J.js.map +1 -0
  8. package/dist/chunk-4EAYBJ6G.js +362 -0
  9. package/dist/chunk-4EAYBJ6G.js.map +1 -0
  10. package/dist/chunk-4LBJTDV5.js +165 -0
  11. package/dist/chunk-4LBJTDV5.js.map +1 -0
  12. package/dist/chunk-4ZEZ2GLD.js +353 -0
  13. package/dist/chunk-4ZEZ2GLD.js.map +1 -0
  14. package/dist/chunk-5OIMR74G.js +273 -0
  15. package/dist/chunk-5OIMR74G.js.map +1 -0
  16. package/dist/chunk-6NFNAKWA.js +257 -0
  17. package/dist/chunk-6NFNAKWA.js.map +1 -0
  18. package/dist/chunk-7MTEMQLL.js +47 -0
  19. package/dist/chunk-7MTEMQLL.js.map +1 -0
  20. package/dist/chunk-7V4SWTUK.js +83 -0
  21. package/dist/chunk-7V4SWTUK.js.map +1 -0
  22. package/dist/chunk-7XH7MFZ4.js +295 -0
  23. package/dist/chunk-7XH7MFZ4.js.map +1 -0
  24. package/dist/chunk-BK3MYSUS.js +61 -0
  25. package/dist/chunk-BK3MYSUS.js.map +1 -0
  26. package/dist/chunk-BLHNKR3S.js +35 -0
  27. package/dist/chunk-BLHNKR3S.js.map +1 -0
  28. package/dist/chunk-CCJ43SIX.js +342 -0
  29. package/dist/chunk-CCJ43SIX.js.map +1 -0
  30. package/dist/chunk-CNEDWATL.js +210 -0
  31. package/dist/chunk-CNEDWATL.js.map +1 -0
  32. package/dist/chunk-CYTWDJE2.js +152 -0
  33. package/dist/chunk-CYTWDJE2.js.map +1 -0
  34. package/dist/chunk-DKVOICYZ.js +45 -0
  35. package/dist/chunk-DKVOICYZ.js.map +1 -0
  36. package/dist/chunk-DPEJU4P6.js +79 -0
  37. package/dist/chunk-DPEJU4P6.js.map +1 -0
  38. package/dist/chunk-DQPABZTU.js +22 -0
  39. package/dist/chunk-DQPABZTU.js.map +1 -0
  40. package/dist/chunk-EKHEY3LI.js +46 -0
  41. package/dist/chunk-EKHEY3LI.js.map +1 -0
  42. package/dist/chunk-FIEYAKPI.js +421 -0
  43. package/dist/chunk-FIEYAKPI.js.map +1 -0
  44. package/dist/chunk-G467LFKC.js +191 -0
  45. package/dist/chunk-G467LFKC.js.map +1 -0
  46. package/dist/chunk-GLYWSVSB.js +250 -0
  47. package/dist/chunk-GLYWSVSB.js.map +1 -0
  48. package/dist/chunk-GXBIMUO7.js +135 -0
  49. package/dist/chunk-GXBIMUO7.js.map +1 -0
  50. package/dist/chunk-H6EERRCR.js +180 -0
  51. package/dist/chunk-H6EERRCR.js.map +1 -0
  52. package/dist/chunk-HDTM3LXD.js +35 -0
  53. package/dist/chunk-HDTM3LXD.js.map +1 -0
  54. package/dist/chunk-IGMX56C3.js +180 -0
  55. package/dist/chunk-IGMX56C3.js.map +1 -0
  56. package/dist/chunk-J4IRICCJ.js +74 -0
  57. package/dist/chunk-J4IRICCJ.js.map +1 -0
  58. package/dist/chunk-JXKQ7S6W.js +331 -0
  59. package/dist/chunk-JXKQ7S6W.js.map +1 -0
  60. package/dist/chunk-KY6DXS6S.js +34 -0
  61. package/dist/chunk-KY6DXS6S.js.map +1 -0
  62. package/dist/chunk-NOFGCYGS.js +22 -0
  63. package/dist/chunk-NOFGCYGS.js.map +1 -0
  64. package/dist/chunk-OSHUJRKD.js +131 -0
  65. package/dist/chunk-OSHUJRKD.js.map +1 -0
  66. package/dist/chunk-PMH7OWPF.js +40 -0
  67. package/dist/chunk-PMH7OWPF.js.map +1 -0
  68. package/dist/chunk-PRBKSX3A.js +299 -0
  69. package/dist/chunk-PRBKSX3A.js.map +1 -0
  70. package/dist/chunk-SVPUX7AM.js +48 -0
  71. package/dist/chunk-SVPUX7AM.js.map +1 -0
  72. package/dist/chunk-TR6HJ4NO.js +659 -0
  73. package/dist/chunk-TR6HJ4NO.js.map +1 -0
  74. package/dist/chunk-TV3E4YUF.js +69 -0
  75. package/dist/chunk-TV3E4YUF.js.map +1 -0
  76. package/dist/chunk-VCTN77K2.js +112 -0
  77. package/dist/chunk-VCTN77K2.js.map +1 -0
  78. package/dist/chunk-WEOPDD3P.js +120 -0
  79. package/dist/chunk-WEOPDD3P.js.map +1 -0
  80. package/dist/chunk-ZBZ7OC6I.js +228 -0
  81. package/dist/chunk-ZBZ7OC6I.js.map +1 -0
  82. package/dist/config-CJYo46X9.d.ts +19 -0
  83. package/dist/index.d.ts +517 -0
  84. package/dist/index.js +23 -0
  85. package/dist/index.js.map +1 -0
  86. package/dist/react/certificate-sign.d.ts +5 -0
  87. package/dist/react/certificate-sign.js +14 -0
  88. package/dist/react/certificate-sign.js.map +1 -0
  89. package/dist/react/compress.d.ts +5 -0
  90. package/dist/react/compress.js +8 -0
  91. package/dist/react/compress.js.map +1 -0
  92. package/dist/react/fill-form.d.ts +5 -0
  93. package/dist/react/fill-form.js +8 -0
  94. package/dist/react/fill-form.js.map +1 -0
  95. package/dist/react/images-to-pdf.d.ts +5 -0
  96. package/dist/react/images-to-pdf.js +7 -0
  97. package/dist/react/images-to-pdf.js.map +1 -0
  98. package/dist/react/merge.d.ts +5 -0
  99. package/dist/react/merge.js +8 -0
  100. package/dist/react/merge.js.map +1 -0
  101. package/dist/react/metadata.d.ts +5 -0
  102. package/dist/react/metadata.js +8 -0
  103. package/dist/react/metadata.js.map +1 -0
  104. package/dist/react/organize.d.ts +5 -0
  105. package/dist/react/organize.js +12 -0
  106. package/dist/react/organize.js.map +1 -0
  107. package/dist/react/pdf-to-images.d.ts +5 -0
  108. package/dist/react/pdf-to-images.js +13 -0
  109. package/dist/react/pdf-to-images.js.map +1 -0
  110. package/dist/react/protect.d.ts +5 -0
  111. package/dist/react/protect.js +8 -0
  112. package/dist/react/protect.js.map +1 -0
  113. package/dist/react/sign.d.ts +5 -0
  114. package/dist/react/sign.js +13 -0
  115. package/dist/react/sign.js.map +1 -0
  116. package/dist/react/split.d.ts +5 -0
  117. package/dist/react/split.js +11 -0
  118. package/dist/react/split.js.map +1 -0
  119. package/dist/react/verify-signatures.d.ts +5 -0
  120. package/dist/react/verify-signatures.js +8 -0
  121. package/dist/react/verify-signatures.js.map +1 -0
  122. package/dist/react/watermark.d.ts +5 -0
  123. package/dist/react/watermark.js +10 -0
  124. package/dist/react/watermark.js.map +1 -0
  125. package/dist/react.d.ts +40 -0
  126. package/dist/react.js +40 -0
  127. package/dist/react.js.map +1 -0
  128. package/dist/styles.css +2 -0
  129. package/dist/vite.d.ts +5 -0
  130. package/dist/vite.js +9 -0
  131. package/dist/vite.js.map +1 -0
  132. package/package.json +163 -4
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/organize/organizePdfCore.ts"],"names":[],"mappings":"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","file":"chunk-GXBIMUO7.js","sourcesContent":["import { PDFArray, PDFDict, PDFDocument, PDFName, PDFNumber, PDFPage, PDFRef, degrees } from \"pdf-lib\";\nimport { loadPdf } from \"../shared/files\";\nimport { normalizeRotation } from \"../shared/pageGeometry\";\n\n/** One page of the output: which source page (0-based) and extra rotation. */\nexport type PageEdit = { source: number; rotation?: number };\n\n/**\n * Reorder, rotate, and delete pages. Pages not listed are deleted. Edits the\n * document in place so metadata and document-level settings are kept.\n */\nexport async function organizePdf(bytes: ArrayBuffer | Uint8Array, edits: PageEdit[]) {\n if (!edits.length) {\n throw new Error(\"Keep at least one page.\");\n }\n\n const doc = await loadPdf(bytes);\n const pages = doc.getPages();\n const seen = new Set<number>();\n for (const edit of edits) {\n if (!Number.isInteger(edit.source) || edit.source < 0 || edit.source >= pages.length) {\n throw new Error(`Page ${edit.source + 1} does not exist.`);\n }\n if (seen.has(edit.source)) {\n throw new Error(`Page ${edit.source + 1} is listed twice.`);\n }\n seen.add(edit.source);\n }\n\n const removed = pages.filter((_, index) => !seen.has(index));\n\n for (let index = pages.length - 1; index >= 0; index -= 1) {\n doc.removePage(index);\n }\n\n for (const edit of edits) {\n const page = pages[edit.source];\n const turn = normalizeRotation(edit.rotation || 0);\n if (turn) {\n page.setRotation(degrees(normalizeRotation(page.getRotation().angle + turn)));\n }\n doc.addPage(page);\n }\n\n pruneDanglingReferences(doc, removed);\n return doc.save();\n}\n\nconst N = (name: string) => PDFName.of(name);\n\n/** Page a destination points at, if it is an explicit [page ...] array. */\nfunction destinationPage(item: PDFDict) {\n let dest = item.lookup(N(\"Dest\"));\n const action = item.lookup(N(\"A\"));\n if (!dest && action instanceof PDFDict && action.lookup(N(\"S\")) === N(\"GoTo\")) {\n dest = action.lookup(N(\"D\"));\n }\n if (dest instanceof PDFArray && dest.size() > 0) {\n const target = dest.get(0);\n return target instanceof PDFRef ? target.toString() : null;\n }\n return null;\n}\n\n/**\n * Rebuild one level of the outline (bookmark) tree without items that point\n * at deleted pages. An item that still has children keeps them and only loses\n * its dead link. Returns the number of items visible when this level is open.\n */\nfunction pruneOutlineLevel(doc: PDFDocument, parent: PDFDict, removed: Set<string>): number {\n const kept: Array<{ ref: PDFRef; item: PDFDict }> = [];\n let ref = parent.get(N(\"First\"));\n const visited = new Set<string>();\n while (ref instanceof PDFRef && !visited.has(ref.toString())) {\n visited.add(ref.toString());\n const item = doc.context.lookup(ref);\n if (!(item instanceof PDFDict)) break;\n const next = item.get(N(\"Next\"));\n\n const childCount = pruneOutlineLevel(doc, item, removed);\n const hasChildren = item.get(N(\"First\")) instanceof PDFRef;\n const target = destinationPage(item);\n if (target && removed.has(target)) {\n if (hasChildren) {\n item.delete(N(\"Dest\"));\n item.delete(N(\"A\"));\n kept.push({ ref, item });\n }\n } else {\n kept.push({ ref, item });\n }\n\n // Count > 0 means open (children visible); < 0 closed.\n const previous = item.lookup(N(\"Count\"));\n if (hasChildren) {\n const open = !(previous instanceof PDFNumber) || previous.asNumber() >= 0;\n item.set(N(\"Count\"), PDFNumber.of(open ? childCount : -childCount));\n } else {\n item.delete(N(\"Count\"));\n }\n ref = next;\n }\n\n kept.forEach(({ item }, index) => {\n if (index > 0) item.set(N(\"Prev\"), kept[index - 1].ref);\n else item.delete(N(\"Prev\"));\n if (index < kept.length - 1) item.set(N(\"Next\"), kept[index + 1].ref);\n else item.delete(N(\"Next\"));\n });\n if (kept.length) {\n parent.set(N(\"First\"), kept[0].ref);\n parent.set(N(\"Last\"), kept[kept.length - 1].ref);\n } else {\n parent.delete(N(\"First\"));\n parent.delete(N(\"Last\"));\n }\n\n return kept.reduce((sum, { item }) => {\n const count = item.lookup(N(\"Count\"));\n return sum + 1 + (count instanceof PDFNumber && count.asNumber() > 0 ? count.asNumber() : 0);\n }, 0);\n}\n\n/** Drop form fields whose widgets were all on deleted pages; trim the rest. */\nfunction pruneField(doc: PDFDocument, ref: unknown, removedWidgets: Set<string>): boolean {\n if (!(ref instanceof PDFRef)) return true;\n const field = doc.context.lookup(ref);\n if (!(field instanceof PDFDict)) return true;\n const kids = field.lookup(N(\"Kids\"));\n if (kids instanceof PDFArray) {\n const keptKids = kids.asArray().filter((kid) => pruneField(doc, kid, removedWidgets));\n if (!keptKids.length) return false;\n field.set(N(\"Kids\"), doc.context.obj(keptKids));\n return true;\n }\n return !removedWidgets.has(ref.toString());\n}\n\n/**\n * Deleting pages leaves bookmarks and form fields pointing at pages that no\n * longer exist. Remove those references so viewers don't show dead entries.\n */\nexport function pruneDanglingReferences(doc: PDFDocument, removedPages: PDFPage[]) {\n if (!removedPages.length) return;\n const removedRefs = new Set(removedPages.map((page) => page.ref.toString()));\n\n const outlines = doc.catalog.lookup(N(\"Outlines\"));\n if (outlines instanceof PDFDict) {\n const visible = pruneOutlineLevel(doc, outlines, removedRefs);\n if (visible) outlines.set(N(\"Count\"), PDFNumber.of(visible));\n else outlines.delete(N(\"Count\"));\n }\n\n const removedWidgets = new Set<string>();\n for (const page of removedPages) {\n const annots = page.node.lookup(N(\"Annots\"));\n if (annots instanceof PDFArray) {\n annots.asArray().forEach((annot) => annot instanceof PDFRef && removedWidgets.add(annot.toString()));\n }\n }\n const acroForm = doc.catalog.lookup(N(\"AcroForm\"));\n const fields = acroForm instanceof PDFDict ? acroForm.lookup(N(\"Fields\")) : null;\n if (removedWidgets.size && acroForm instanceof PDFDict && fields instanceof PDFArray) {\n acroForm.set(N(\"Fields\"), doc.context.obj(fields.asArray().filter((field) => pruneField(doc, field, removedWidgets))));\n }\n}\n"]}
@@ -0,0 +1,180 @@
1
+ import { createContext, useContext, useRef, useState } from 'react';
2
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
3
+
4
+ // src/shared/ToolLayout.tsx
5
+ var PdfToolsContext = createContext({});
6
+ function PdfToolsProvider({ children, ...options }) {
7
+ return /* @__PURE__ */ jsx(PdfToolsContext.Provider, { value: options, children });
8
+ }
9
+ var pageShellClass = "relative overflow-hidden bg-[radial-gradient(circle_at_top_left,_#e0f2fe_0%,_#f8fafc_42%,_#eef2ff_100%)] dark:bg-none dark:bg-slate-950";
10
+ var pageContainerClass = "relative mx-auto w-full max-w-7xl px-4 sm:px-6 lg:px-10";
11
+ var glassPanelClass = "rounded-3xl border border-slate-200/80 bg-white/80 shadow-lg shadow-slate-200/60 backdrop-blur dark:border-white/8 dark:bg-slate-950/72 dark:shadow-none";
12
+ var primaryButtonClass = "inline-flex items-center gap-2 rounded-xl border border-[#1F8CFF] bg-[#1F8CFF] px-4 py-2 text-xs font-semibold uppercase tracking-[0.14em] text-white transition hover:bg-[#1579E6] disabled:cursor-not-allowed disabled:opacity-60";
13
+ var secondaryButtonClass = "inline-flex items-center gap-2 rounded-xl border border-slate-300 bg-white px-4 py-2 text-xs font-semibold uppercase tracking-[0.14em] text-slate-700 transition hover:bg-slate-100 disabled:cursor-not-allowed disabled:opacity-60 dark:border-slate-700 dark:bg-slate-900 dark:text-slate-200 dark:hover:bg-slate-800";
14
+ var iconButtonClass = "inline-flex h-8 w-8 items-center justify-center rounded-lg border border-slate-200 bg-white text-xs text-slate-600 transition hover:bg-slate-100 disabled:cursor-not-allowed disabled:opacity-40 dark:border-slate-700 dark:bg-slate-900 dark:text-slate-300 dark:hover:bg-slate-800";
15
+ var inputClass = "w-full min-w-0 rounded-lg border border-slate-300 bg-white px-3 py-1.5 text-sm normal-case tracking-normal text-slate-900 outline-none focus:border-[#1F8CFF] disabled:opacity-60 dark:border-slate-700 dark:bg-slate-950 dark:text-slate-100";
16
+ var fieldLabelClass = "block text-xs font-semibold uppercase tracking-[0.12em] text-slate-600 dark:text-slate-300";
17
+ var cardClass = "rounded-2xl border border-slate-200 bg-white/90 p-3 sm:p-4 dark:border-slate-700 dark:bg-slate-900/80";
18
+ function PdfToolPage({ title, description, stats = [], children }) {
19
+ const { header, className = "py-8 sm:py-12" } = useContext(PdfToolsContext);
20
+ return /* @__PURE__ */ jsx("div", { className: `kitsy-pdf ${pageShellClass} ${className}`, children: /* @__PURE__ */ jsxs("div", { className: pageContainerClass, children: [
21
+ header,
22
+ /* @__PURE__ */ jsxs("section", { className: "relative overflow-hidden rounded-3xl border border-slate-200/70 bg-gradient-to-br from-slate-900 via-slate-800 to-rose-900 p-8 text-white shadow-2xl shadow-slate-300/40 sm:p-10", children: [
23
+ /* @__PURE__ */ jsx("div", { className: "absolute -right-20 -top-16 h-52 w-52 rounded-full bg-white/10 blur-3xl" }),
24
+ /* @__PURE__ */ jsx("div", { className: "absolute -left-16 bottom-0 h-36 w-36 rounded-full bg-rose-300/20 blur-3xl" }),
25
+ /* @__PURE__ */ jsxs("div", { className: "relative flex flex-wrap items-end justify-between gap-6", children: [
26
+ /* @__PURE__ */ jsxs("div", { className: "max-w-2xl", children: [
27
+ /* @__PURE__ */ jsx("p", { className: "text-xs font-semibold uppercase tracking-[0.28em] text-rose-200/90", children: "PDF Utility" }),
28
+ /* @__PURE__ */ jsx("h1", { className: "mt-3 text-3xl font-semibold leading-tight sm:text-4xl", children: title }),
29
+ /* @__PURE__ */ jsx("p", { className: "mt-4 text-base text-slate-200", children: description })
30
+ ] }),
31
+ stats.length ? /* @__PURE__ */ jsx("div", { className: "grid grid-cols-2 gap-3", children: stats.map((stat) => /* @__PURE__ */ jsxs(
32
+ "div",
33
+ {
34
+ className: "rounded-2xl border border-white/15 bg-white/10 px-4 py-3 text-center backdrop-blur",
35
+ children: [
36
+ /* @__PURE__ */ jsx("p", { className: "text-2xl font-semibold", children: stat.value }),
37
+ /* @__PURE__ */ jsx("p", { className: "mt-1 text-xs uppercase tracking-[0.2em] text-slate-200", children: stat.label })
38
+ ]
39
+ },
40
+ stat.label
41
+ )) }) : null
42
+ ] })
43
+ ] }),
44
+ children
45
+ ] }) });
46
+ }
47
+ function PdfToolColumns({ main, side }) {
48
+ return /* @__PURE__ */ jsxs("div", { className: "mt-8 grid gap-6 lg:grid-cols-[minmax(0,1fr)_20rem]", children: [
49
+ /* @__PURE__ */ jsx("section", { className: `${glassPanelClass} p-5 sm:p-6`, children: main }),
50
+ /* @__PURE__ */ jsx("section", { className: `${glassPanelClass} p-5 sm:p-6`, children: side })
51
+ ] });
52
+ }
53
+ function SideTips({ tips }) {
54
+ return /* @__PURE__ */ jsxs("div", { className: "mt-4 rounded-xl border border-dashed border-slate-300 bg-slate-50 p-4 text-xs leading-relaxed text-slate-600 dark:border-slate-700 dark:bg-slate-900/70 dark:text-slate-300", children: [
55
+ /* @__PURE__ */ jsx("p", { className: "font-semibold text-slate-800 dark:text-slate-200", children: "Tips" }),
56
+ /* @__PURE__ */ jsx("ul", { className: "mt-2 list-disc space-y-1 pl-4", children: tips.map((tip, index) => /* @__PURE__ */ jsx("li", { children: tip }, index)) })
57
+ ] });
58
+ }
59
+ function SummaryList({ items }) {
60
+ return /* @__PURE__ */ jsx("dl", { className: "mt-4 space-y-2 text-sm", children: items.map((item) => /* @__PURE__ */ jsxs(
61
+ "div",
62
+ {
63
+ className: "flex justify-between gap-3 rounded-xl border border-slate-200 bg-white/90 px-3 py-2 dark:border-slate-700 dark:bg-slate-900/80",
64
+ children: [
65
+ /* @__PURE__ */ jsx("dt", { className: "text-slate-600 dark:text-slate-300", children: item.label }),
66
+ /* @__PURE__ */ jsx("dd", { className: "truncate font-semibold text-slate-900 dark:text-slate-100", children: item.value })
67
+ ]
68
+ },
69
+ item.label
70
+ )) });
71
+ }
72
+ function StatusBanner({ status }) {
73
+ let toneClass = "border-slate-200 bg-slate-50 text-slate-700 dark:border-slate-700 dark:bg-slate-900/80 dark:text-slate-300";
74
+ if (status.tone === "error") {
75
+ toneClass = "border-rose-200 bg-rose-50 text-rose-700 dark:border-rose-500/30 dark:bg-rose-500/10 dark:text-rose-300";
76
+ } else if (status.tone === "success") {
77
+ toneClass = "border-emerald-200 bg-emerald-50 text-emerald-700 dark:border-emerald-500/30 dark:bg-emerald-500/10 dark:text-emerald-300";
78
+ }
79
+ return /* @__PURE__ */ jsx("div", { role: "status", className: `mt-4 rounded-xl border px-4 py-3 text-sm ${toneClass}`, children: status.text });
80
+ }
81
+ function FileDropzone({
82
+ accept,
83
+ multiple = false,
84
+ disabled = false,
85
+ onFiles,
86
+ title,
87
+ hint,
88
+ icon = "fa-solid fa-file-pdf"
89
+ }) {
90
+ const inputRef = useRef(null);
91
+ const [isDropActive, setIsDropActive] = useState(false);
92
+ const handleDrop = (event) => {
93
+ event.preventDefault();
94
+ setIsDropActive(false);
95
+ if (!disabled) onFiles(Array.from(event.dataTransfer.files || []));
96
+ };
97
+ const handleDragOver = (event) => {
98
+ event.preventDefault();
99
+ setIsDropActive(true);
100
+ };
101
+ const handleDragLeave = (event) => {
102
+ event.preventDefault();
103
+ setIsDropActive(false);
104
+ };
105
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
106
+ /* @__PURE__ */ jsx(
107
+ "input",
108
+ {
109
+ ref: inputRef,
110
+ type: "file",
111
+ accept,
112
+ multiple,
113
+ onChange: (event) => {
114
+ onFiles(Array.from(event.target.files || []));
115
+ event.target.value = "";
116
+ },
117
+ disabled,
118
+ className: "hidden"
119
+ }
120
+ ),
121
+ /* @__PURE__ */ jsx(
122
+ "button",
123
+ {
124
+ type: "button",
125
+ onClick: () => inputRef.current?.click(),
126
+ onDrop: handleDrop,
127
+ onDragOver: handleDragOver,
128
+ onDragLeave: handleDragLeave,
129
+ disabled,
130
+ className: `group w-full rounded-2xl border-2 border-dashed p-8 text-left transition ${isDropActive ? "border-[#1F8CFF] bg-blue-50 dark:bg-blue-500/10" : "border-slate-300 bg-white hover:border-[#1F8CFF] hover:bg-slate-50 dark:border-slate-700 dark:bg-slate-900/70 dark:hover:border-[#1F8CFF] dark:hover:bg-slate-800"}`,
131
+ children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center text-center", children: [
132
+ /* @__PURE__ */ jsx("span", { className: "inline-flex h-14 w-14 items-center justify-center rounded-2xl bg-[#1F8CFF]/10 text-[#1F8CFF]", children: /* @__PURE__ */ jsx("i", { className: `${icon} text-xl` }) }),
133
+ /* @__PURE__ */ jsx("p", { className: "mt-4 text-lg font-semibold text-slate-900 dark:text-slate-100", children: title }),
134
+ /* @__PURE__ */ jsx("p", { className: "mt-2 text-sm text-slate-600 dark:text-slate-300", children: hint })
135
+ ] })
136
+ }
137
+ )
138
+ ] });
139
+ }
140
+ function BackToPdfTools() {
141
+ const { backLink, renderLink } = useContext(PdfToolsContext);
142
+ if (!backLink) return null;
143
+ const children = /* @__PURE__ */ jsxs(Fragment, { children: [
144
+ /* @__PURE__ */ jsx("i", { className: "fa-solid fa-arrow-left" }),
145
+ backLink.label || "Back to PDF Tools"
146
+ ] });
147
+ return renderLink ? /* @__PURE__ */ jsx(Fragment, { children: renderLink({ href: backLink.href, className: secondaryButtonClass, children }) }) : /* @__PURE__ */ jsx("a", { href: backLink.href, className: secondaryButtonClass, children });
148
+ }
149
+ function SelectedPdfCard({ file, pageCount, onRemove, disabled, detail = void 0, signed = false }) {
150
+ const card = /* @__PURE__ */ jsxs("div", { className: `mt-5 flex items-center gap-3 ${cardClass}`, children: [
151
+ /* @__PURE__ */ jsx("span", { className: "inline-flex h-10 w-10 shrink-0 items-center justify-center rounded-xl bg-rose-500/10 text-rose-600 dark:text-rose-300", children: /* @__PURE__ */ jsx("i", { className: "fa-solid fa-file-pdf" }) }),
152
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
153
+ /* @__PURE__ */ jsx("p", { className: "truncate text-sm font-medium text-slate-900 dark:text-slate-100", children: file.name }),
154
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-slate-500 dark:text-slate-400", children: detail ?? (pageCount ? `${pageCount} page${pageCount === 1 ? "" : "s"}` : "reading...") })
155
+ ] }),
156
+ /* @__PURE__ */ jsx(
157
+ "button",
158
+ {
159
+ type: "button",
160
+ onClick: onRemove,
161
+ disabled,
162
+ className: iconButtonClass,
163
+ "aria-label": `Remove ${file.name}`,
164
+ children: /* @__PURE__ */ jsx("i", { className: "fa-solid fa-xmark" })
165
+ }
166
+ )
167
+ ] });
168
+ if (!signed) return card;
169
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
170
+ card,
171
+ /* @__PURE__ */ jsxs("p", { className: "mt-2 rounded-xl border border-amber-200 bg-amber-50 px-3 py-2 text-xs text-amber-900 dark:border-amber-500/30 dark:bg-amber-500/10 dark:text-amber-100", children: [
172
+ /* @__PURE__ */ jsx("i", { className: "fa-solid fa-triangle-exclamation mr-1" }),
173
+ "This PDF is digitally signed. Saving a changed copy will invalidate its signature; the original file is not changed."
174
+ ] })
175
+ ] });
176
+ }
177
+
178
+ export { BackToPdfTools, FileDropzone, PdfToolColumns, PdfToolPage, PdfToolsProvider, SelectedPdfCard, SideTips, StatusBanner, SummaryList, cardClass, fieldLabelClass, iconButtonClass, inputClass, primaryButtonClass, secondaryButtonClass };
179
+ //# sourceMappingURL=chunk-H6EERRCR.js.map
180
+ //# sourceMappingURL=chunk-H6EERRCR.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/shared/ToolLayout.tsx"],"names":[],"mappings":";;;;AAeA,IAAM,eAAA,GAAkB,aAAA,CAA+B,EAAE,CAAA;AAGlD,SAAS,gBAAA,CAAiB,EAAE,QAAA,EAAU,GAAG,SAAQ,EAA8C;AACpG,EAAA,2BAAQ,eAAA,CAAgB,QAAA,EAAhB,EAAyB,KAAA,EAAO,SAAU,QAAA,EAAS,CAAA;AAC7D;AAEA,IAAM,cAAA,GACJ,yIAAA;AACF,IAAM,kBAAA,GAAqB,yDAAA;AAC3B,IAAM,eAAA,GACJ,0JAAA;AAEK,IAAM,kBAAA,GACX;AAEK,IAAM,oBAAA,GACX;AAEK,IAAM,eAAA,GACX;AAEK,IAAM,UAAA,GACX;AAEK,IAAM,eAAA,GACX;AAEK,IAAM,SAAA,GACX;AAEK,SAAS,WAAA,CAAY,EAAE,KAAA,EAAO,WAAA,EAAa,QAAQ,EAAC,EAAG,UAAS,EAAG;AACxE,EAAA,MAAM,EAAE,MAAA,EAAQ,SAAA,GAAY,eAAA,EAAgB,GAAI,WAAW,eAAe,CAAA;AAC1E,EAAA,uBACE,GAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAW,CAAA,UAAA,EAAa,cAAc,CAAA,CAAA,EAAI,SAAS,CAAA,CAAA,EACtD,QAAA,kBAAA,IAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAW,kBAAA,EACb,QAAA,EAAA;AAAA,IAAA,MAAA;AAAA,oBAED,IAAA,CAAC,SAAA,EAAA,EAAQ,SAAA,EAAU,kLAAA,EACjB,QAAA,EAAA;AAAA,sBAAA,GAAA,CAAC,KAAA,EAAA,EAAI,WAAU,wEAAA,EAAyE,CAAA;AAAA,sBACxF,GAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,2EAAA,EAA4E,CAAA;AAAA,sBAE3F,IAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,yDAAA,EACb,QAAA,EAAA;AAAA,wBAAA,IAAA,CAAC,KAAA,EAAA,EAAI,WAAU,WAAA,EACb,QAAA,EAAA;AAAA,0BAAA,GAAA,CAAC,GAAA,EAAA,EAAE,SAAA,EAAU,oEAAA,EAAqE,QAAA,EAAA,aAAA,EAElF,CAAA;AAAA,0BACA,GAAA,CAAC,IAAA,EAAA,EAAG,SAAA,EAAU,uDAAA,EAAyD,QAAA,EAAA,KAAA,EAAM,CAAA;AAAA,0BAC7E,GAAA,CAAC,GAAA,EAAA,EAAE,SAAA,EAAU,+BAAA,EAAiC,QAAA,EAAA,WAAA,EAAY;AAAA,SAAA,EAC5D,CAAA;AAAA,QAEC,KAAA,CAAM,yBACL,GAAA,CAAC,KAAA,EAAA,EAAI,WAAU,wBAAA,EACZ,QAAA,EAAA,KAAA,CAAM,GAAA,CAAI,CAAC,IAAA,qBACV,IAAA;AAAA,UAAC,KAAA;AAAA,UAAA;AAAA,YAEC,SAAA,EAAU,oFAAA;AAAA,YAEV,QAAA,EAAA;AAAA,8BAAA,GAAA,CAAC,GAAA,EAAA,EAAE,SAAA,EAAU,wBAAA,EAA0B,QAAA,EAAA,IAAA,CAAK,KAAA,EAAM,CAAA;AAAA,8BAClD,GAAA,CAAC,GAAA,EAAA,EAAE,SAAA,EAAU,wDAAA,EACV,eAAK,KAAA,EACR;AAAA;AAAA,WAAA;AAAA,UANK,IAAA,CAAK;AAAA,SAQb,GACH,CAAA,GACE;AAAA,OAAA,EACN;AAAA,KAAA,EACF,CAAA;AAAA,IAEC;AAAA,GAAA,EACH,CAAA,EACF,CAAA;AAEJ;AAGO,SAAS,cAAA,CAAe,EAAE,IAAA,EAAM,IAAA,EAAK,EAAG;AAC7C,EAAA,uBACE,IAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,oDAAA,EACb,QAAA,EAAA;AAAA,oBAAA,GAAA,CAAC,SAAA,EAAA,EAAQ,SAAA,EAAW,CAAA,EAAG,eAAe,eAAgB,QAAA,EAAA,IAAA,EAAK,CAAA;AAAA,wBAC1D,SAAA,EAAA,EAAQ,SAAA,EAAW,CAAA,EAAG,eAAe,eAAgB,QAAA,EAAA,IAAA,EAAK;AAAA,GAAA,EAC7D,CAAA;AAEJ;AAEO,SAAS,QAAA,CAAS,EAAE,IAAA,EAAK,EAAG;AACjC,EAAA,uBACE,IAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,6KAAA,EACb,QAAA,EAAA;AAAA,oBAAA,GAAA,CAAC,GAAA,EAAA,EAAE,SAAA,EAAU,kDAAA,EAAmD,QAAA,EAAA,MAAA,EAAI,CAAA;AAAA,oBACpE,GAAA,CAAC,IAAA,EAAA,EAAG,SAAA,EAAU,+BAAA,EACX,eAAK,GAAA,CAAI,CAAC,GAAA,EAAK,KAAA,qBACd,GAAA,CAAC,IAAA,EAAA,EAAgB,QAAA,EAAA,GAAA,EAAA,EAAR,KAAY,CACtB,CAAA,EACH;AAAA,GAAA,EACF,CAAA;AAEJ;AAEO,SAAS,WAAA,CAAY,EAAE,KAAA,EAAM,EAAG;AACrC,EAAA,2BACG,IAAA,EAAA,EAAG,SAAA,EAAU,0BACX,QAAA,EAAA,KAAA,CAAM,GAAA,CAAI,CAAC,IAAA,qBACV,IAAA;AAAA,IAAC,KAAA;AAAA,IAAA;AAAA,MAEC,SAAA,EAAU,gIAAA;AAAA,MAEV,QAAA,EAAA;AAAA,wBAAA,GAAA,CAAC,IAAA,EAAA,EAAG,SAAA,EAAU,oCAAA,EAAsC,QAAA,EAAA,IAAA,CAAK,KAAA,EAAM,CAAA;AAAA,wBAC/D,GAAA,CAAC,IAAA,EAAA,EAAG,SAAA,EAAU,2DAAA,EAA6D,eAAK,KAAA,EAAM;AAAA;AAAA,KAAA;AAAA,IAJjF,IAAA,CAAK;AAAA,GAMb,CAAA,EACH,CAAA;AAEJ;AAEO,SAAS,YAAA,CAAa,EAAE,MAAA,EAAO,EAAG;AACvC,EAAA,IAAI,SAAA,GACF,4GAAA;AACF,EAAA,IAAI,MAAA,CAAO,SAAS,OAAA,EAAS;AAC3B,IAAA,SAAA,GACE,yGAAA;AAAA,EACJ,CAAA,MAAA,IAAW,MAAA,CAAO,IAAA,KAAS,SAAA,EAAW;AACpC,IAAA,SAAA,GACE,2HAAA;AAAA,EACJ;AAEA,EAAA,uBACE,GAAA,CAAC,SAAI,IAAA,EAAK,QAAA,EAAS,WAAW,CAAA,yCAAA,EAA4C,SAAS,CAAA,CAAA,EAChF,QAAA,EAAA,MAAA,CAAO,IAAA,EACV,CAAA;AAEJ;AAEO,SAAS,YAAA,CAAa;AAAA,EAC3B,MAAA;AAAA,EACA,QAAA,GAAW,KAAA;AAAA,EACX,QAAA,GAAW,KAAA;AAAA,EACX,OAAA;AAAA,EACA,KAAA;AAAA,EACA,IAAA;AAAA,EACA,IAAA,GAAO;AACT,CAAA,EAAG;AACD,EAAA,MAAM,QAAA,GAAW,OAAO,IAAI,CAAA;AAC5B,EAAA,MAAM,CAAC,YAAA,EAAc,eAAe,CAAA,GAAI,SAAS,KAAK,CAAA;AAEtD,EAAA,MAAM,UAAA,GAAa,CAAC,KAAA,KAAU;AAC5B,IAAA,KAAA,CAAM,cAAA,EAAe;AACrB,IAAA,eAAA,CAAgB,KAAK,CAAA;AACrB,IAAA,IAAI,CAAC,QAAA,EAAU,OAAA,CAAQ,KAAA,CAAM,IAAA,CAAK,MAAM,YAAA,CAAa,KAAA,IAAS,EAAE,CAAC,CAAA;AAAA,EACnE,CAAA;AAEA,EAAA,MAAM,cAAA,GAAiB,CAAC,KAAA,KAAU;AAChC,IAAA,KAAA,CAAM,cAAA,EAAe;AACrB,IAAA,eAAA,CAAgB,IAAI,CAAA;AAAA,EACtB,CAAA;AAEA,EAAA,MAAM,eAAA,GAAkB,CAAC,KAAA,KAAU;AACjC,IAAA,KAAA,CAAM,cAAA,EAAe;AACrB,IAAA,eAAA,CAAgB,KAAK,CAAA;AAAA,EACvB,CAAA;AAEA,EAAA,uBACE,IAAA,CAAA,QAAA,EAAA,EACE,QAAA,EAAA;AAAA,oBAAA,GAAA;AAAA,MAAC,OAAA;AAAA,MAAA;AAAA,QACC,GAAA,EAAK,QAAA;AAAA,QACL,IAAA,EAAK,MAAA;AAAA,QACL,MAAA;AAAA,QACA,QAAA;AAAA,QACA,QAAA,EAAU,CAAC,KAAA,KAAU;AACnB,UAAA,OAAA,CAAQ,MAAM,IAAA,CAAK,KAAA,CAAM,OAAO,KAAA,IAAS,EAAE,CAAC,CAAA;AAC5C,UAAA,KAAA,CAAM,OAAO,KAAA,GAAQ,EAAA;AAAA,QACvB,CAAA;AAAA,QACA,QAAA;AAAA,QACA,SAAA,EAAU;AAAA;AAAA,KACZ;AAAA,oBAEA,GAAA;AAAA,MAAC,QAAA;AAAA,MAAA;AAAA,QACC,IAAA,EAAK,QAAA;AAAA,QACL,OAAA,EAAS,MAAM,QAAA,CAAS,OAAA,EAAS,KAAA,EAAM;AAAA,QACvC,MAAA,EAAQ,UAAA;AAAA,QACR,UAAA,EAAY,cAAA;AAAA,QACZ,WAAA,EAAa,eAAA;AAAA,QACb,QAAA;AAAA,QACA,SAAA,EAAW,CAAA,yEAAA,EACT,YAAA,GACI,iDAAA,GACA,mKACN,CAAA,CAAA;AAAA,QAEA,QAAA,kBAAA,IAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,wCAAA,EACb,QAAA,EAAA;AAAA,0BAAA,GAAA,CAAC,MAAA,EAAA,EAAK,WAAU,8FAAA,EACd,QAAA,kBAAA,GAAA,CAAC,OAAE,SAAA,EAAW,CAAA,EAAG,IAAI,CAAA,QAAA,CAAA,EAAY,CAAA,EACnC,CAAA;AAAA,0BACA,GAAA,CAAC,GAAA,EAAA,EAAE,SAAA,EAAU,+DAAA,EAAiE,QAAA,EAAA,KAAA,EAAM,CAAA;AAAA,0BACpF,GAAA,CAAC,GAAA,EAAA,EAAE,SAAA,EAAU,iDAAA,EAAmD,QAAA,EAAA,IAAA,EAAK;AAAA,SAAA,EACvE;AAAA;AAAA;AACF,GAAA,EACF,CAAA;AAEJ;AAEO,SAAS,cAAA,GAAiB;AAC/B,EAAA,MAAM,EAAE,QAAA,EAAU,UAAA,EAAW,GAAI,WAAW,eAAe,CAAA;AAC3D,EAAA,IAAI,CAAC,UAAU,OAAO,IAAA;AACtB,EAAA,MAAM,2BACJ,IAAA,CAAA,QAAA,EAAA,EACE,QAAA,EAAA;AAAA,oBAAA,GAAA,CAAC,GAAA,EAAA,EAAE,WAAU,wBAAA,EAAyB,CAAA;AAAA,IACrC,SAAS,KAAA,IAAS;AAAA,GAAA,EACrB,CAAA;AAEF,EAAA,OAAO,UAAA,mCACF,QAAA,EAAA,UAAA,CAAW,EAAE,MAAM,QAAA,CAAS,IAAA,EAAM,WAAW,oBAAA,EAAsB,QAAA,EAAU,CAAA,EAAE,CAAA,uBAEjF,GAAA,EAAA,EAAE,IAAA,EAAM,SAAS,IAAA,EAAM,SAAA,EAAW,sBAChC,QAAA,EACH,CAAA;AAEJ;AAEO,SAAS,eAAA,CAAgB,EAAE,IAAA,EAAM,SAAA,EAAW,QAAA,EAAU,UAAU,MAAA,GAAS,MAAA,EAAW,MAAA,GAAS,KAAA,EAAM,EAAG;AAC3G,EAAA,MAAM,uBACJ,IAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAW,CAAA,6BAAA,EAAgC,SAAS,CAAA,CAAA,EACvD,QAAA,EAAA;AAAA,oBAAA,GAAA,CAAC,UAAK,SAAA,EAAU,uHAAA,EACd,8BAAC,GAAA,EAAA,EAAE,SAAA,EAAU,wBAAuB,CAAA,EACtC,CAAA;AAAA,oBACA,IAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,gBAAA,EACb,QAAA,EAAA;AAAA,sBAAA,GAAA,CAAC,GAAA,EAAA,EAAE,SAAA,EAAU,iEAAA,EAAmE,QAAA,EAAA,IAAA,CAAK,IAAA,EAAK,CAAA;AAAA,sBAC1F,GAAA,CAAC,GAAA,EAAA,EAAE,SAAA,EAAU,4CAAA,EACV,qBAAW,SAAA,GAAY,CAAA,EAAG,SAAS,CAAA,KAAA,EAAQ,SAAA,KAAc,CAAA,GAAI,EAAA,GAAK,GAAG,KAAK,YAAA,CAAA,EAC7E;AAAA,KAAA,EACF,CAAA;AAAA,oBACA,GAAA;AAAA,MAAC,QAAA;AAAA,MAAA;AAAA,QACC,IAAA,EAAK,QAAA;AAAA,QACL,OAAA,EAAS,QAAA;AAAA,QACT,QAAA;AAAA,QACA,SAAA,EAAW,eAAA;AAAA,QACX,YAAA,EAAY,CAAA,OAAA,EAAU,IAAA,CAAK,IAAI,CAAA,CAAA;AAAA,QAE/B,QAAA,kBAAA,GAAA,CAAC,GAAA,EAAA,EAAE,SAAA,EAAU,mBAAA,EAAoB;AAAA;AAAA;AACnC,GAAA,EACF,CAAA;AAEF,EAAA,IAAI,CAAC,QAAQ,OAAO,IAAA;AACpB,EAAA,uBACE,IAAA,CAAA,QAAA,EAAA,EACG,QAAA,EAAA;AAAA,IAAA,IAAA;AAAA,oBACD,IAAA,CAAC,GAAA,EAAA,EAAE,SAAA,EAAU,wJAAA,EACX,QAAA,EAAA;AAAA,sBAAA,GAAA,CAAC,GAAA,EAAA,EAAE,WAAU,uCAAA,EAAwC,CAAA;AAAA,MAAE;AAAA,KAAA,EAEzD;AAAA,GAAA,EACF,CAAA;AAEJ","file":"chunk-H6EERRCR.js","sourcesContent":["import React, { createContext, useContext, useRef, useState, type ReactNode } from \"react\";\n\nexport type PdfToolsLinkProps = { href: string; className: string; children: ReactNode };\n\nexport type PdfToolsOptions = {\n /** Link shown at the bottom of each tool, e.g. back to your tool list. Omit to hide it. */\n backLink?: { href: string; label?: string } | null;\n /** Render links with your router (defaults to a plain <a>). */\n renderLink?: (props: PdfToolsLinkProps) => ReactNode;\n /** Rendered above the tool's title card, e.g. your app's navigation. */\n header?: ReactNode;\n /** Extra classes for the outer page wrapper (spacing, background). */\n className?: string;\n};\n\nconst PdfToolsContext = createContext<PdfToolsOptions>({});\n\n/** Optional: configures navigation and page chrome for every tool inside it. */\nexport function PdfToolsProvider({ children, ...options }: PdfToolsOptions & { children: ReactNode }) {\n return <PdfToolsContext.Provider value={options}>{children}</PdfToolsContext.Provider>;\n}\n\nconst pageShellClass =\n \"relative overflow-hidden bg-[radial-gradient(circle_at_top_left,_#e0f2fe_0%,_#f8fafc_42%,_#eef2ff_100%)] dark:bg-none dark:bg-slate-950\";\nconst pageContainerClass = \"relative mx-auto w-full max-w-7xl px-4 sm:px-6 lg:px-10\";\nconst glassPanelClass =\n \"rounded-3xl border border-slate-200/80 bg-white/80 shadow-lg shadow-slate-200/60 backdrop-blur dark:border-white/8 dark:bg-slate-950/72 dark:shadow-none\";\n\nexport const primaryButtonClass =\n \"inline-flex items-center gap-2 rounded-xl border border-[#1F8CFF] bg-[#1F8CFF] px-4 py-2 text-xs font-semibold uppercase tracking-[0.14em] text-white transition hover:bg-[#1579E6] disabled:cursor-not-allowed disabled:opacity-60\";\n\nexport const secondaryButtonClass =\n \"inline-flex items-center gap-2 rounded-xl border border-slate-300 bg-white px-4 py-2 text-xs font-semibold uppercase tracking-[0.14em] text-slate-700 transition hover:bg-slate-100 disabled:cursor-not-allowed disabled:opacity-60 dark:border-slate-700 dark:bg-slate-900 dark:text-slate-200 dark:hover:bg-slate-800\";\n\nexport const iconButtonClass =\n \"inline-flex h-8 w-8 items-center justify-center rounded-lg border border-slate-200 bg-white text-xs text-slate-600 transition hover:bg-slate-100 disabled:cursor-not-allowed disabled:opacity-40 dark:border-slate-700 dark:bg-slate-900 dark:text-slate-300 dark:hover:bg-slate-800\";\n\nexport const inputClass =\n \"w-full min-w-0 rounded-lg border border-slate-300 bg-white px-3 py-1.5 text-sm normal-case tracking-normal text-slate-900 outline-none focus:border-[#1F8CFF] disabled:opacity-60 dark:border-slate-700 dark:bg-slate-950 dark:text-slate-100\";\n\nexport const fieldLabelClass =\n \"block text-xs font-semibold uppercase tracking-[0.12em] text-slate-600 dark:text-slate-300\";\n\nexport const cardClass =\n \"rounded-2xl border border-slate-200 bg-white/90 p-3 sm:p-4 dark:border-slate-700 dark:bg-slate-900/80\";\n\nexport function PdfToolPage({ title, description, stats = [], children }) {\n const { header, className = \"py-8 sm:py-12\" } = useContext(PdfToolsContext);\n return (\n <div className={`kitsy-pdf ${pageShellClass} ${className}`}>\n <div className={pageContainerClass}>\n {header}\n\n <section className=\"relative overflow-hidden rounded-3xl border border-slate-200/70 bg-gradient-to-br from-slate-900 via-slate-800 to-rose-900 p-8 text-white shadow-2xl shadow-slate-300/40 sm:p-10\">\n <div className=\"absolute -right-20 -top-16 h-52 w-52 rounded-full bg-white/10 blur-3xl\" />\n <div className=\"absolute -left-16 bottom-0 h-36 w-36 rounded-full bg-rose-300/20 blur-3xl\" />\n\n <div className=\"relative flex flex-wrap items-end justify-between gap-6\">\n <div className=\"max-w-2xl\">\n <p className=\"text-xs font-semibold uppercase tracking-[0.28em] text-rose-200/90\">\n PDF Utility\n </p>\n <h1 className=\"mt-3 text-3xl font-semibold leading-tight sm:text-4xl\">{title}</h1>\n <p className=\"mt-4 text-base text-slate-200\">{description}</p>\n </div>\n\n {stats.length ? (\n <div className=\"grid grid-cols-2 gap-3\">\n {stats.map((stat) => (\n <div\n key={stat.label}\n className=\"rounded-2xl border border-white/15 bg-white/10 px-4 py-3 text-center backdrop-blur\"\n >\n <p className=\"text-2xl font-semibold\">{stat.value}</p>\n <p className=\"mt-1 text-xs uppercase tracking-[0.2em] text-slate-200\">\n {stat.label}\n </p>\n </div>\n ))}\n </div>\n ) : null}\n </div>\n </section>\n\n {children}\n </div>\n </div>\n );\n}\n\n/** Main form panel plus a 20rem side panel, matching the other app pages. */\nexport function PdfToolColumns({ main, side }) {\n return (\n <div className=\"mt-8 grid gap-6 lg:grid-cols-[minmax(0,1fr)_20rem]\">\n <section className={`${glassPanelClass} p-5 sm:p-6`}>{main}</section>\n <section className={`${glassPanelClass} p-5 sm:p-6`}>{side}</section>\n </div>\n );\n}\n\nexport function SideTips({ tips }) {\n return (\n <div className=\"mt-4 rounded-xl border border-dashed border-slate-300 bg-slate-50 p-4 text-xs leading-relaxed text-slate-600 dark:border-slate-700 dark:bg-slate-900/70 dark:text-slate-300\">\n <p className=\"font-semibold text-slate-800 dark:text-slate-200\">Tips</p>\n <ul className=\"mt-2 list-disc space-y-1 pl-4\">\n {tips.map((tip, index) => (\n <li key={index}>{tip}</li>\n ))}\n </ul>\n </div>\n );\n}\n\nexport function SummaryList({ items }) {\n return (\n <dl className=\"mt-4 space-y-2 text-sm\">\n {items.map((item) => (\n <div\n key={item.label}\n className=\"flex justify-between gap-3 rounded-xl border border-slate-200 bg-white/90 px-3 py-2 dark:border-slate-700 dark:bg-slate-900/80\"\n >\n <dt className=\"text-slate-600 dark:text-slate-300\">{item.label}</dt>\n <dd className=\"truncate font-semibold text-slate-900 dark:text-slate-100\">{item.value}</dd>\n </div>\n ))}\n </dl>\n );\n}\n\nexport function StatusBanner({ status }) {\n let toneClass =\n \"border-slate-200 bg-slate-50 text-slate-700 dark:border-slate-700 dark:bg-slate-900/80 dark:text-slate-300\";\n if (status.tone === \"error\") {\n toneClass =\n \"border-rose-200 bg-rose-50 text-rose-700 dark:border-rose-500/30 dark:bg-rose-500/10 dark:text-rose-300\";\n } else if (status.tone === \"success\") {\n toneClass =\n \"border-emerald-200 bg-emerald-50 text-emerald-700 dark:border-emerald-500/30 dark:bg-emerald-500/10 dark:text-emerald-300\";\n }\n\n return (\n <div role=\"status\" className={`mt-4 rounded-xl border px-4 py-3 text-sm ${toneClass}`}>\n {status.text}\n </div>\n );\n}\n\nexport function FileDropzone({\n accept,\n multiple = false,\n disabled = false,\n onFiles,\n title,\n hint,\n icon = \"fa-solid fa-file-pdf\",\n}) {\n const inputRef = useRef(null);\n const [isDropActive, setIsDropActive] = useState(false);\n\n const handleDrop = (event) => {\n event.preventDefault();\n setIsDropActive(false);\n if (!disabled) onFiles(Array.from(event.dataTransfer.files || []));\n };\n\n const handleDragOver = (event) => {\n event.preventDefault();\n setIsDropActive(true);\n };\n\n const handleDragLeave = (event) => {\n event.preventDefault();\n setIsDropActive(false);\n };\n\n return (\n <>\n <input\n ref={inputRef}\n type=\"file\"\n accept={accept}\n multiple={multiple}\n onChange={(event) => {\n onFiles(Array.from(event.target.files || []));\n event.target.value = \"\";\n }}\n disabled={disabled}\n className=\"hidden\"\n />\n\n <button\n type=\"button\"\n onClick={() => inputRef.current?.click()}\n onDrop={handleDrop}\n onDragOver={handleDragOver}\n onDragLeave={handleDragLeave}\n disabled={disabled}\n className={`group w-full rounded-2xl border-2 border-dashed p-8 text-left transition ${\n isDropActive\n ? \"border-[#1F8CFF] bg-blue-50 dark:bg-blue-500/10\"\n : \"border-slate-300 bg-white hover:border-[#1F8CFF] hover:bg-slate-50 dark:border-slate-700 dark:bg-slate-900/70 dark:hover:border-[#1F8CFF] dark:hover:bg-slate-800\"\n }`}\n >\n <div className=\"flex flex-col items-center text-center\">\n <span className=\"inline-flex h-14 w-14 items-center justify-center rounded-2xl bg-[#1F8CFF]/10 text-[#1F8CFF]\">\n <i className={`${icon} text-xl`} />\n </span>\n <p className=\"mt-4 text-lg font-semibold text-slate-900 dark:text-slate-100\">{title}</p>\n <p className=\"mt-2 text-sm text-slate-600 dark:text-slate-300\">{hint}</p>\n </div>\n </button>\n </>\n );\n}\n\nexport function BackToPdfTools() {\n const { backLink, renderLink } = useContext(PdfToolsContext);\n if (!backLink) return null;\n const children = (\n <>\n <i className=\"fa-solid fa-arrow-left\" />\n {backLink.label || \"Back to PDF Tools\"}\n </>\n );\n return renderLink ? (\n <>{renderLink({ href: backLink.href, className: secondaryButtonClass, children })}</>\n ) : (\n <a href={backLink.href} className={secondaryButtonClass}>\n {children}\n </a>\n );\n}\n\nexport function SelectedPdfCard({ file, pageCount, onRemove, disabled, detail = undefined, signed = false }) {\n const card = (\n <div className={`mt-5 flex items-center gap-3 ${cardClass}`}>\n <span className=\"inline-flex h-10 w-10 shrink-0 items-center justify-center rounded-xl bg-rose-500/10 text-rose-600 dark:text-rose-300\">\n <i className=\"fa-solid fa-file-pdf\" />\n </span>\n <div className=\"min-w-0 flex-1\">\n <p className=\"truncate text-sm font-medium text-slate-900 dark:text-slate-100\">{file.name}</p>\n <p className=\"text-xs text-slate-500 dark:text-slate-400\">\n {detail ?? (pageCount ? `${pageCount} page${pageCount === 1 ? \"\" : \"s\"}` : \"reading...\")}\n </p>\n </div>\n <button\n type=\"button\"\n onClick={onRemove}\n disabled={disabled}\n className={iconButtonClass}\n aria-label={`Remove ${file.name}`}\n >\n <i className=\"fa-solid fa-xmark\" />\n </button>\n </div>\n );\n if (!signed) return card;\n return (\n <>\n {card}\n <p className=\"mt-2 rounded-xl border border-amber-200 bg-amber-50 px-3 py-2 text-xs text-amber-900 dark:border-amber-500/30 dark:bg-amber-500/10 dark:text-amber-100\">\n <i className=\"fa-solid fa-triangle-exclamation mr-1\" />\n This PDF is digitally signed. Saving a changed copy will invalidate its signature; the original file is not changed.\n </p>\n </>\n );\n}\n"]}
@@ -0,0 +1,35 @@
1
+ import { producerName } from './chunk-DQPABZTU.js';
2
+ import { parsePageSelection } from './chunk-BLHNKR3S.js';
3
+ import { loadPdf } from './chunk-EKHEY3LI.js';
4
+ import { PDFDocument } from 'pdf-lib';
5
+
6
+ async function mergePdfs(sources, options = {}) {
7
+ if (!sources.length) {
8
+ throw new Error("Add at least one PDF to merge.");
9
+ }
10
+ const merged = await PDFDocument.create();
11
+ for (const source of sources) {
12
+ let doc;
13
+ try {
14
+ doc = await loadPdf(source.bytes);
15
+ } catch (error) {
16
+ throw new Error(`${source.name}: ${error.message}`);
17
+ }
18
+ let indices;
19
+ try {
20
+ indices = parsePageSelection(source.pages, doc.getPageCount());
21
+ } catch (error) {
22
+ throw new Error(`${source.name}: ${error.message}`);
23
+ }
24
+ const pages = await merged.copyPages(doc, indices);
25
+ pages.forEach((page) => merged.addPage(page));
26
+ }
27
+ if (options.title) merged.setTitle(options.title);
28
+ merged.setProducer(producerName());
29
+ merged.setCreationDate(/* @__PURE__ */ new Date());
30
+ return merged.save();
31
+ }
32
+
33
+ export { mergePdfs };
34
+ //# sourceMappingURL=chunk-HDTM3LXD.js.map
35
+ //# sourceMappingURL=chunk-HDTM3LXD.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/merge/mergePdfCore.ts"],"names":[],"mappings":";;;;;AAmBA,eAAsB,SAAA,CAAU,OAAA,EAA2B,OAAA,GAA2B,EAAC,EAAG;AACxF,EAAA,IAAI,CAAC,QAAQ,MAAA,EAAQ;AACnB,IAAA,MAAM,IAAI,MAAM,gCAAgC,CAAA;AAAA,EAClD;AAEA,EAAA,MAAM,MAAA,GAAS,MAAM,WAAA,CAAY,MAAA,EAAO;AAExC,EAAA,KAAA,MAAW,UAAU,OAAA,EAAS;AAC5B,IAAA,IAAI,GAAA;AACJ,IAAA,IAAI;AACF,MAAA,GAAA,GAAM,MAAM,OAAA,CAAQ,MAAA,CAAO,KAAK,CAAA;AAAA,IAClC,SAAS,KAAA,EAAO;AACd,MAAA,MAAM,IAAI,MAAM,CAAA,EAAG,MAAA,CAAO,IAAI,CAAA,EAAA,EAAM,KAAA,CAAgB,OAAO,CAAA,CAAE,CAAA;AAAA,IAC/D;AAEA,IAAA,IAAI,OAAA;AACJ,IAAA,IAAI;AACF,MAAA,OAAA,GAAU,kBAAA,CAAmB,MAAA,CAAO,KAAA,EAAO,GAAA,CAAI,cAAc,CAAA;AAAA,IAC/D,SAAS,KAAA,EAAO;AACd,MAAA,MAAM,IAAI,MAAM,CAAA,EAAG,MAAA,CAAO,IAAI,CAAA,EAAA,EAAM,KAAA,CAAgB,OAAO,CAAA,CAAE,CAAA;AAAA,IAC/D;AAEA,IAAA,MAAM,KAAA,GAAQ,MAAM,MAAA,CAAO,SAAA,CAAU,KAAK,OAAO,CAAA;AACjD,IAAA,KAAA,CAAM,QAAQ,CAAC,IAAA,KAAS,MAAA,CAAO,OAAA,CAAQ,IAAI,CAAC,CAAA;AAAA,EAC9C;AAEA,EAAA,IAAI,OAAA,CAAQ,KAAA,EAAO,MAAA,CAAO,QAAA,CAAS,QAAQ,KAAK,CAAA;AAChD,EAAA,MAAA,CAAO,WAAA,CAAY,cAAc,CAAA;AACjC,EAAA,MAAA,CAAO,eAAA,iBAAgB,IAAI,IAAA,EAAM,CAAA;AAEjC,EAAA,OAAO,OAAO,IAAA,EAAK;AACrB","file":"chunk-HDTM3LXD.js","sourcesContent":["import { PDFDocument } from \"pdf-lib\";\nimport { loadPdf } from \"../shared/files\";\nimport { parsePageSelection } from \"../shared/pageRanges\";\nimport { producerName } from \"../config\";\n\nexport { isPdfFile, getPdfPageCount, loadPdf } from \"../shared/files\";\nexport { parsePageSelection } from \"../shared/pageRanges\";\n\nexport type MergePdfSource = {\n name: string;\n bytes: ArrayBuffer | Uint8Array;\n /** Optional 1-based page selection, e.g. \"1-3, 5, 8-\". Empty means all pages. */\n pages?: string;\n};\n\nexport type MergePdfOptions = {\n title?: string;\n};\n\nexport async function mergePdfs(sources: MergePdfSource[], options: MergePdfOptions = {}) {\n if (!sources.length) {\n throw new Error(\"Add at least one PDF to merge.\");\n }\n\n const merged = await PDFDocument.create();\n\n for (const source of sources) {\n let doc: PDFDocument;\n try {\n doc = await loadPdf(source.bytes);\n } catch (error) {\n throw new Error(`${source.name}: ${(error as Error).message}`);\n }\n\n let indices: number[];\n try {\n indices = parsePageSelection(source.pages, doc.getPageCount());\n } catch (error) {\n throw new Error(`${source.name}: ${(error as Error).message}`);\n }\n\n const pages = await merged.copyPages(doc, indices);\n pages.forEach((page) => merged.addPage(page));\n }\n\n if (options.title) merged.setTitle(options.title);\n merged.setProducer(producerName());\n merged.setCreationDate(new Date());\n\n return merged.save();\n}\n"]}
@@ -0,0 +1,180 @@
1
+ import { verifyPdfSignatures } from './chunk-5OIMR74G.js';
2
+ import { MAX_SINGLE_PDF_BYTES } from './chunk-BK3MYSUS.js';
3
+ import { PdfToolPage, PdfToolColumns, SideTips, SelectedPdfCard, FileDropzone, StatusBanner, BackToPdfTools, cardClass, SummaryList } from './chunk-H6EERRCR.js';
4
+ import { isPdfFile } from './chunk-EKHEY3LI.js';
5
+ import { useRef, useState, useEffect, useCallback } from 'react';
6
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
7
+
8
+ var formatDay = (date) => date.toLocaleDateString(void 0, { year: "numeric", month: "short", day: "numeric" });
9
+ function verdict(check) {
10
+ if (check.intact && check.coversWholeFile) {
11
+ return { tone: "good", icon: "fa-circle-check", title: "Signature is valid", detail: "The document has not changed since it was signed." };
12
+ }
13
+ if (check.intact) {
14
+ return {
15
+ tone: "warn",
16
+ icon: "fa-triangle-exclamation",
17
+ title: "Valid, but the file was changed later",
18
+ detail: "The signed version is intact, but content was added after signing (for example a later signature, comments, or edits)."
19
+ };
20
+ }
21
+ if (check.intact === false) {
22
+ return { tone: "bad", icon: "fa-circle-xmark", title: "Signature is invalid", detail: check.problem || "The document was changed after it was signed." };
23
+ }
24
+ return { tone: "warn", icon: "fa-circle-question", title: "Could not be checked", detail: check.problem || "This signature could not be verified." };
25
+ }
26
+ var TONES = {
27
+ good: "border-emerald-200 bg-emerald-50 text-emerald-900 dark:border-emerald-500/30 dark:bg-emerald-500/10 dark:text-emerald-100",
28
+ warn: "border-amber-200 bg-amber-50 text-amber-900 dark:border-amber-500/30 dark:bg-amber-500/10 dark:text-amber-100",
29
+ bad: "border-rose-200 bg-rose-50 text-rose-900 dark:border-rose-500/30 dark:bg-rose-500/10 dark:text-rose-100"
30
+ };
31
+ function SignatureCard({ check, index }) {
32
+ const result = verdict(check);
33
+ const signer = check.signer;
34
+ return /* @__PURE__ */ jsxs("section", { className: `mt-5 ${cardClass}`, "data-testid": "signature-result", children: [
35
+ /* @__PURE__ */ jsxs("div", { className: `flex items-start gap-3 rounded-xl border px-4 py-3 ${TONES[result.tone]}`, children: [
36
+ /* @__PURE__ */ jsx("i", { className: `fa-solid ${result.icon} mt-0.5` }),
37
+ /* @__PURE__ */ jsxs("div", { children: [
38
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-semibold", children: result.title }),
39
+ /* @__PURE__ */ jsx("p", { className: "mt-0.5 text-xs", children: result.detail })
40
+ ] })
41
+ ] }),
42
+ /* @__PURE__ */ jsxs("h3", { className: "mt-4 text-sm font-semibold text-slate-900 dark:text-slate-100", children: [
43
+ "Signature ",
44
+ index + 1,
45
+ /* @__PURE__ */ jsxs("span", { className: "ml-2 font-normal text-slate-500 dark:text-slate-400", children: [
46
+ "(",
47
+ check.fieldName,
48
+ ")"
49
+ ] })
50
+ ] }),
51
+ /* @__PURE__ */ jsx(
52
+ SummaryList,
53
+ {
54
+ items: [
55
+ { label: "Signed by", value: signer?.commonName || "-" },
56
+ { label: "Organisation", value: signer?.organization || "-" },
57
+ { label: "Email", value: signer?.email || "-" },
58
+ { label: "Certificate issued by", value: signer ? signer.issuerCommonName || signer.issuerOrganization || "-" : "-" },
59
+ { label: "Certificate valid", value: signer ? `${formatDay(signer.notBefore)} \u2013 ${formatDay(signer.notAfter)}` : "-" },
60
+ { label: "Signed on", value: check.signingTime ? check.signingTime.toLocaleString() : "-" },
61
+ { label: "Reason", value: check.reason || "-" },
62
+ { label: "Location", value: check.location || "-" },
63
+ { label: "Format", value: check.subFilter || "-" }
64
+ ]
65
+ }
66
+ ),
67
+ !check.coversWholeFile ? /* @__PURE__ */ jsx("p", { className: "mt-2 text-xs text-amber-700 dark:text-amber-300", children: "Signed on, reason, and location are read from the file's latest version, which was changed after signing, so they may not be what the signer wrote." }) : null,
68
+ signer?.selfSigned ? /* @__PURE__ */ jsx("p", { className: "mt-2 text-xs text-amber-700 dark:text-amber-300", children: "The certificate is self-signed, so anyone could have made it." }) : null,
69
+ check.certificateValidAtSigning === false ? /* @__PURE__ */ jsx("p", { className: "mt-2 text-xs text-rose-700 dark:text-rose-300", children: "The certificate was not valid at the claimed signing time." }) : null
70
+ ] });
71
+ }
72
+ function VerifyPdfSignatures() {
73
+ const requestRef = useRef(0);
74
+ const [file, setFile] = useState(null);
75
+ const [checks, setChecks] = useState(null);
76
+ const [isReading, setIsReading] = useState(false);
77
+ const [status, setStatus] = useState({ tone: "info", text: "Add a signed PDF to check its digital signatures." });
78
+ useEffect(() => {
79
+ document.title = "Verify PDF Signatures | Apps";
80
+ }, []);
81
+ const clear = () => {
82
+ requestRef.current += 1;
83
+ setFile(null);
84
+ setChecks(null);
85
+ setIsReading(false);
86
+ setStatus({ tone: "info", text: "Add a signed PDF to check its digital signatures." });
87
+ };
88
+ const selectFiles = useCallback(async (files) => {
89
+ const next = (files || []).find(isPdfFile);
90
+ if (!next) {
91
+ if (files?.length) setStatus({ tone: "error", text: "Only PDF files are supported." });
92
+ return;
93
+ }
94
+ if (next.size > MAX_SINGLE_PDF_BYTES) {
95
+ setStatus({ tone: "error", text: "This PDF is larger than 200 MB." });
96
+ return;
97
+ }
98
+ const request = ++requestRef.current;
99
+ setFile(next);
100
+ setChecks(null);
101
+ setIsReading(true);
102
+ setStatus({ tone: "info", text: "Checking signatures..." });
103
+ try {
104
+ const result = await verifyPdfSignatures(await next.arrayBuffer());
105
+ if (request !== requestRef.current) return;
106
+ setChecks(result);
107
+ const valid = result.filter((check) => check.intact && check.coversWholeFile).length;
108
+ setStatus(
109
+ result.length ? {
110
+ tone: valid === result.length ? "success" : "error",
111
+ text: `${result.length} signature${result.length === 1 ? "" : "s"} found, ${valid} fully valid.`
112
+ } : { tone: "info", text: `${next.name} has no digital signatures.` }
113
+ );
114
+ } catch (error) {
115
+ if (request !== requestRef.current) return;
116
+ setFile(null);
117
+ setStatus({ tone: "error", text: error?.message || "Could not read this PDF." });
118
+ } finally {
119
+ if (request === requestRef.current) setIsReading(false);
120
+ }
121
+ }, []);
122
+ return /* @__PURE__ */ jsx(
123
+ PdfToolPage,
124
+ {
125
+ title: "Verify PDF Signatures",
126
+ description: "Check the digital signatures in a PDF: who signed it, when, and whether it has changed since. Runs in your browser; files never leave your device.",
127
+ stats: [
128
+ { value: checks ? checks.length : "-", label: "Signatures" },
129
+ { value: checks ? checks.filter((check) => check.intact && check.coversWholeFile).length : "-", label: "Valid" }
130
+ ],
131
+ children: /* @__PURE__ */ jsx(
132
+ PdfToolColumns,
133
+ {
134
+ main: /* @__PURE__ */ jsxs("div", { children: [
135
+ file ? /* @__PURE__ */ jsx(
136
+ SelectedPdfCard,
137
+ {
138
+ file,
139
+ pageCount: 0,
140
+ detail: isReading ? "checking..." : `${checks?.length ?? 0} signature${checks?.length === 1 ? "" : "s"}`,
141
+ onRemove: clear,
142
+ disabled: isReading
143
+ }
144
+ ) : /* @__PURE__ */ jsx(
145
+ FileDropzone,
146
+ {
147
+ accept: ".pdf,application/pdf",
148
+ disabled: isReading,
149
+ onFiles: selectFiles,
150
+ title: "Drag and drop a signed PDF here",
151
+ hint: "or click to browse from your device"
152
+ }
153
+ ),
154
+ /* @__PURE__ */ jsx(StatusBanner, { status }),
155
+ (checks || []).map((check, index) => /* @__PURE__ */ jsx(SignatureCard, { check, index }, `${check.fieldName}-${index}`)),
156
+ /* @__PURE__ */ jsx("div", { className: "mt-5 flex flex-wrap gap-3", children: /* @__PURE__ */ jsx(BackToPdfTools, {}) })
157
+ ] }),
158
+ side: /* @__PURE__ */ jsxs(Fragment, { children: [
159
+ /* @__PURE__ */ jsx("h2", { className: "text-base font-semibold text-slate-900 dark:text-slate-100", children: "What is checked" }),
160
+ /* @__PURE__ */ jsx(
161
+ SideTips,
162
+ {
163
+ tips: [
164
+ "Integrity: the signed bytes still match the signature.",
165
+ "Coverage: whether anything was added to the file after signing.",
166
+ "Signer details come from the certificate inside the signature.",
167
+ "Not checked: whether the certificate is trusted, revoked, or issued by a licensed authority. Use Adobe Reader for a trust check."
168
+ ]
169
+ }
170
+ )
171
+ ] })
172
+ }
173
+ )
174
+ }
175
+ );
176
+ }
177
+
178
+ export { VerifyPdfSignatures };
179
+ //# sourceMappingURL=chunk-IGMX56C3.js.map
180
+ //# sourceMappingURL=chunk-IGMX56C3.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/certsign/VerifyPdfSignatures.tsx"],"names":[],"mappings":"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","file":"chunk-IGMX56C3.js","sourcesContent":["import React, { useCallback, useEffect, useRef, useState } from \"react\";\nimport { isPdfFile } from \"../shared/files\";\nimport {\n BackToPdfTools,\n FileDropzone,\n PdfToolColumns,\n PdfToolPage,\n SelectedPdfCard,\n SideTips,\n StatusBanner,\n SummaryList,\n cardClass,\n} from \"../shared/ToolLayout\";\nimport { MAX_SINGLE_PDF_BYTES } from \"../shared/useSinglePdf\";\nimport { verifyPdfSignatures, type SignatureCheck } from \"./verifyCore\";\n\nconst formatDay = (date: Date) =>\n date.toLocaleDateString(undefined, { year: \"numeric\", month: \"short\", day: \"numeric\" });\n\nfunction verdict(check: SignatureCheck) {\n if (check.intact && check.coversWholeFile) {\n return { tone: \"good\", icon: \"fa-circle-check\", title: \"Signature is valid\", detail: \"The document has not changed since it was signed.\" };\n }\n if (check.intact) {\n return {\n tone: \"warn\",\n icon: \"fa-triangle-exclamation\",\n title: \"Valid, but the file was changed later\",\n detail: \"The signed version is intact, but content was added after signing (for example a later signature, comments, or edits).\",\n };\n }\n if (check.intact === false) {\n return { tone: \"bad\", icon: \"fa-circle-xmark\", title: \"Signature is invalid\", detail: check.problem || \"The document was changed after it was signed.\" };\n }\n return { tone: \"warn\", icon: \"fa-circle-question\", title: \"Could not be checked\", detail: check.problem || \"This signature could not be verified.\" };\n}\n\nconst TONES = {\n good: \"border-emerald-200 bg-emerald-50 text-emerald-900 dark:border-emerald-500/30 dark:bg-emerald-500/10 dark:text-emerald-100\",\n warn: \"border-amber-200 bg-amber-50 text-amber-900 dark:border-amber-500/30 dark:bg-amber-500/10 dark:text-amber-100\",\n bad: \"border-rose-200 bg-rose-50 text-rose-900 dark:border-rose-500/30 dark:bg-rose-500/10 dark:text-rose-100\",\n};\n\nfunction SignatureCard({ check, index }: { check: SignatureCheck; index: number }) {\n const result = verdict(check);\n const signer = check.signer;\n return (\n <section className={`mt-5 ${cardClass}`} data-testid=\"signature-result\">\n <div className={`flex items-start gap-3 rounded-xl border px-4 py-3 ${TONES[result.tone]}`}>\n <i className={`fa-solid ${result.icon} mt-0.5`} />\n <div>\n <p className=\"text-sm font-semibold\">{result.title}</p>\n <p className=\"mt-0.5 text-xs\">{result.detail}</p>\n </div>\n </div>\n <h3 className=\"mt-4 text-sm font-semibold text-slate-900 dark:text-slate-100\">\n Signature {index + 1}\n <span className=\"ml-2 font-normal text-slate-500 dark:text-slate-400\">({check.fieldName})</span>\n </h3>\n <SummaryList\n items={[\n { label: \"Signed by\", value: signer?.commonName || \"-\" },\n { label: \"Organisation\", value: signer?.organization || \"-\" },\n { label: \"Email\", value: signer?.email || \"-\" },\n { label: \"Certificate issued by\", value: signer ? signer.issuerCommonName || signer.issuerOrganization || \"-\" : \"-\" },\n { label: \"Certificate valid\", value: signer ? `${formatDay(signer.notBefore)} – ${formatDay(signer.notAfter)}` : \"-\" },\n { label: \"Signed on\", value: check.signingTime ? check.signingTime.toLocaleString() : \"-\" },\n { label: \"Reason\", value: check.reason || \"-\" },\n { label: \"Location\", value: check.location || \"-\" },\n { label: \"Format\", value: check.subFilter || \"-\" },\n ]}\n />\n {!check.coversWholeFile ? (\n <p className=\"mt-2 text-xs text-amber-700 dark:text-amber-300\">\n Signed on, reason, and location are read from the file's latest version, which was changed after signing, so\n they may not be what the signer wrote.\n </p>\n ) : null}\n {signer?.selfSigned ? (\n <p className=\"mt-2 text-xs text-amber-700 dark:text-amber-300\">The certificate is self-signed, so anyone could have made it.</p>\n ) : null}\n {check.certificateValidAtSigning === false ? (\n <p className=\"mt-2 text-xs text-rose-700 dark:text-rose-300\">The certificate was not valid at the claimed signing time.</p>\n ) : null}\n </section>\n );\n}\n\nexport default function VerifyPdfSignatures() {\n const requestRef = useRef(0);\n const [file, setFile] = useState(null);\n const [checks, setChecks] = useState<SignatureCheck[] | null>(null);\n const [isReading, setIsReading] = useState(false);\n const [status, setStatus] = useState({ tone: \"info\", text: \"Add a signed PDF to check its digital signatures.\" });\n\n useEffect(() => {\n document.title = \"Verify PDF Signatures | Apps\";\n }, []);\n\n const clear = () => {\n requestRef.current += 1;\n setFile(null);\n setChecks(null);\n setIsReading(false);\n setStatus({ tone: \"info\", text: \"Add a signed PDF to check its digital signatures.\" });\n };\n\n // Own loader: encrypted PDFs can still be verified, so this skips useSinglePdf.\n const selectFiles = useCallback(async (files) => {\n const next = (files || []).find(isPdfFile);\n if (!next) {\n if (files?.length) setStatus({ tone: \"error\", text: \"Only PDF files are supported.\" });\n return;\n }\n if (next.size > MAX_SINGLE_PDF_BYTES) {\n setStatus({ tone: \"error\", text: \"This PDF is larger than 200 MB.\" });\n return;\n }\n const request = ++requestRef.current;\n setFile(next);\n setChecks(null);\n setIsReading(true);\n setStatus({ tone: \"info\", text: \"Checking signatures...\" });\n try {\n const result = await verifyPdfSignatures(await next.arrayBuffer());\n if (request !== requestRef.current) return;\n setChecks(result);\n const valid = result.filter((check) => check.intact && check.coversWholeFile).length;\n setStatus(\n result.length\n ? {\n tone: valid === result.length ? \"success\" : \"error\",\n text: `${result.length} signature${result.length === 1 ? \"\" : \"s\"} found, ${valid} fully valid.`,\n }\n : { tone: \"info\", text: `${next.name} has no digital signatures.` }\n );\n } catch (error) {\n if (request !== requestRef.current) return;\n setFile(null);\n setStatus({ tone: \"error\", text: error?.message || \"Could not read this PDF.\" });\n } finally {\n if (request === requestRef.current) setIsReading(false);\n }\n }, []);\n\n return (\n <PdfToolPage\n title=\"Verify PDF Signatures\"\n description=\"Check the digital signatures in a PDF: who signed it, when, and whether it has changed since. Runs in your browser; files never leave your device.\"\n stats={[\n { value: checks ? checks.length : \"-\", label: \"Signatures\" },\n { value: checks ? checks.filter((check) => check.intact && check.coversWholeFile).length : \"-\", label: \"Valid\" },\n ]}\n >\n <PdfToolColumns\n main={\n <div>\n {file ? (\n <SelectedPdfCard\n file={file}\n pageCount={0}\n detail={isReading ? \"checking...\" : `${checks?.length ?? 0} signature${checks?.length === 1 ? \"\" : \"s\"}`}\n onRemove={clear}\n disabled={isReading}\n />\n ) : (\n <FileDropzone\n accept=\".pdf,application/pdf\"\n disabled={isReading}\n onFiles={selectFiles}\n title=\"Drag and drop a signed PDF here\"\n hint=\"or click to browse from your device\"\n />\n )}\n <StatusBanner status={status} />\n {(checks || []).map((check, index) => (\n <SignatureCard key={`${check.fieldName}-${index}`} check={check} index={index} />\n ))}\n <div className=\"mt-5 flex flex-wrap gap-3\">\n <BackToPdfTools />\n </div>\n </div>\n }\n side={\n <>\n <h2 className=\"text-base font-semibold text-slate-900 dark:text-slate-100\">What is checked</h2>\n <SideTips\n tips={[\n \"Integrity: the signed bytes still match the signature.\",\n \"Coverage: whether anything was added to the file after signing.\",\n \"Signer details come from the certificate inside the signature.\",\n \"Not checked: whether the certificate is trusted, revoked, or issued by a licensed authority. Use Adobe Reader for a trust check.\",\n ]}\n />\n </>\n }\n />\n </PdfToolPage>\n );\n}\n"]}
@@ -0,0 +1,74 @@
1
+ import { openRenderDocument, renderThumbnail } from './chunk-DPEJU4P6.js';
2
+ import { useState, useEffect } from 'react';
3
+
4
+ function useRenderDocument(bytes) {
5
+ const [doc, setDoc] = useState(null);
6
+ const [error, setError] = useState("");
7
+ useEffect(() => {
8
+ if (!bytes) {
9
+ setDoc(null);
10
+ setError("");
11
+ return void 0;
12
+ }
13
+ let cancelled = false;
14
+ let opened = null;
15
+ setDoc(null);
16
+ setError("");
17
+ openRenderDocument(bytes).then((result) => {
18
+ opened = result;
19
+ if (cancelled) {
20
+ result.destroy();
21
+ } else {
22
+ setDoc(result);
23
+ }
24
+ }).catch((reason) => {
25
+ if (!cancelled) setError(reason?.message || "Could not render this PDF.");
26
+ });
27
+ return () => {
28
+ cancelled = true;
29
+ opened?.destroy();
30
+ };
31
+ }, [bytes]);
32
+ return { doc, error };
33
+ }
34
+ function useThumbnails(doc, width = 160) {
35
+ const [thumbnails, setThumbnails] = useState([]);
36
+ useEffect(() => {
37
+ if (!doc) {
38
+ setThumbnails([]);
39
+ return void 0;
40
+ }
41
+ let cancelled = false;
42
+ const urls = [];
43
+ setThumbnails(Array.from({ length: doc.numPages }, () => null));
44
+ (async () => {
45
+ for (let pageNumber = 1; pageNumber <= doc.numPages; pageNumber += 1) {
46
+ if (cancelled) return;
47
+ try {
48
+ const thumb = await renderThumbnail(doc, pageNumber, width);
49
+ if (cancelled) {
50
+ URL.revokeObjectURL(thumb.url);
51
+ return;
52
+ }
53
+ urls.push(thumb.url);
54
+ setThumbnails((current) => {
55
+ const next = [...current];
56
+ next[pageNumber - 1] = thumb;
57
+ return next;
58
+ });
59
+ } catch {
60
+ if (cancelled) return;
61
+ }
62
+ }
63
+ })();
64
+ return () => {
65
+ cancelled = true;
66
+ urls.forEach((url) => URL.revokeObjectURL(url));
67
+ };
68
+ }, [doc, width]);
69
+ return thumbnails;
70
+ }
71
+
72
+ export { useRenderDocument, useThumbnails };
73
+ //# sourceMappingURL=chunk-J4IRICCJ.js.map
74
+ //# sourceMappingURL=chunk-J4IRICCJ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/shared/useRenderDocument.ts"],"names":[],"mappings":"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","file":"chunk-J4IRICCJ.js","sourcesContent":["import { useEffect, useState } from \"react\";\nimport { openRenderDocument, renderThumbnail } from \"./pdfRender\";\n\n/** Open bytes in pdf.js; destroys the previous document when bytes change. */\nexport function useRenderDocument(bytes) {\n const [doc, setDoc] = useState(null);\n const [error, setError] = useState(\"\");\n\n useEffect(() => {\n if (!bytes) {\n setDoc(null);\n setError(\"\");\n return undefined;\n }\n\n let cancelled = false;\n let opened = null;\n setDoc(null);\n setError(\"\");\n openRenderDocument(bytes)\n .then((result) => {\n opened = result;\n if (cancelled) {\n result.destroy();\n } else {\n setDoc(result);\n }\n })\n .catch((reason) => {\n if (!cancelled) setError(reason?.message || \"Could not render this PDF.\");\n });\n\n return () => {\n cancelled = true;\n opened?.destroy();\n };\n }, [bytes]);\n\n return { doc, error };\n}\n\n/**\n * Render page thumbnails one after another (to keep memory and the worker\n * calm), exposing each as soon as it is ready. Index i is page i + 1.\n */\nexport function useThumbnails(doc, width = 160) {\n const [thumbnails, setThumbnails] = useState([]);\n\n useEffect(() => {\n if (!doc) {\n setThumbnails([]);\n return undefined;\n }\n\n let cancelled = false;\n const urls = [];\n setThumbnails(Array.from({ length: doc.numPages }, () => null));\n\n (async () => {\n for (let pageNumber = 1; pageNumber <= doc.numPages; pageNumber += 1) {\n if (cancelled) return;\n try {\n const thumb = await renderThumbnail(doc, pageNumber, width);\n if (cancelled) {\n URL.revokeObjectURL(thumb.url);\n return;\n }\n urls.push(thumb.url);\n setThumbnails((current) => {\n const next = [...current];\n next[pageNumber - 1] = thumb;\n return next;\n });\n } catch {\n if (cancelled) return;\n }\n }\n })();\n\n return () => {\n cancelled = true;\n urls.forEach((url) => URL.revokeObjectURL(url));\n };\n }, [doc, width]);\n\n return thumbnails;\n}\n"]}