initial commit for manual redaction tool

This commit is contained in:
EthanHealy01 2025-10-30 15:09:15 +00:00
parent 8060934ee9
commit 013122cb25
11 changed files with 1190 additions and 234 deletions

View File

@ -10,24 +10,25 @@
"license": "SEE LICENSE IN https://raw.githubusercontent.com/Stirling-Tools/Stirling-PDF/refs/heads/main/proprietary/LICENSE",
"dependencies": {
"@atlaskit/pragmatic-drag-and-drop": "^1.7.7",
"@embedpdf/core": "^1.3.14",
"@embedpdf/engines": "^1.3.14",
"@embedpdf/plugin-annotation": "^1.3.14",
"@embedpdf/plugin-export": "^1.3.14",
"@embedpdf/plugin-history": "^1.3.14",
"@embedpdf/plugin-interaction-manager": "^1.3.14",
"@embedpdf/plugin-loader": "^1.3.14",
"@embedpdf/plugin-pan": "^1.3.14",
"@embedpdf/plugin-render": "^1.3.14",
"@embedpdf/plugin-rotate": "^1.3.14",
"@embedpdf/plugin-scroll": "^1.3.14",
"@embedpdf/plugin-search": "^1.3.14",
"@embedpdf/plugin-selection": "^1.3.14",
"@embedpdf/plugin-spread": "^1.3.14",
"@embedpdf/plugin-thumbnail": "^1.3.14",
"@embedpdf/plugin-tiling": "^1.3.14",
"@embedpdf/plugin-viewport": "^1.3.14",
"@embedpdf/plugin-zoom": "^1.3.14",
"@embedpdf/core": "^1.4.1",
"@embedpdf/engines": "^1.4.1",
"@embedpdf/plugin-annotation": "^1.4.1",
"@embedpdf/plugin-export": "^1.4.1",
"@embedpdf/plugin-history": "^1.4.1",
"@embedpdf/plugin-interaction-manager": "^1.4.1",
"@embedpdf/plugin-loader": "^1.4.1",
"@embedpdf/plugin-pan": "^1.4.1",
"@embedpdf/plugin-redaction": "^1.4.1",
"@embedpdf/plugin-render": "^1.4.1",
"@embedpdf/plugin-rotate": "^1.4.1",
"@embedpdf/plugin-scroll": "^1.4.1",
"@embedpdf/plugin-search": "^1.4.1",
"@embedpdf/plugin-selection": "^1.4.1",
"@embedpdf/plugin-spread": "^1.4.1",
"@embedpdf/plugin-thumbnail": "^1.4.1",
"@embedpdf/plugin-tiling": "^1.4.1",
"@embedpdf/plugin-viewport": "^1.4.1",
"@embedpdf/plugin-zoom": "^1.4.1",
"@emotion/react": "^11.14.0",
"@emotion/styled": "^11.14.1",
"@iconify/react": "^6.0.2",
@ -502,63 +503,65 @@
}
},
"node_modules/@embedpdf/core": {
"version": "1.3.14",
"resolved": "https://registry.npmjs.org/@embedpdf/core/-/core-1.3.14.tgz",
"integrity": "sha512-lE/vfhA53CxamaCfGWEibrEPr+JeZT42QCF+cOELUwv4+Zt6b+IE6+4wsznx/8wjjJYwllXJ3GJ/un1UzTqARw==",
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/@embedpdf/core/-/core-1.4.1.tgz",
"integrity": "sha512-TGpxn2CvAKRnOJWJ3bsK+dKBiCp75ehxftRUmv7wAmPomhnG5XrDfoWJungvO+zbbqAwso6PocdeXINVt3hlAw==",
"license": "MIT",
"dependencies": {
"@embedpdf/engines": "1.3.14",
"@embedpdf/models": "1.3.14"
"@embedpdf/engines": "1.4.1",
"@embedpdf/models": "1.4.1"
},
"peerDependencies": {
"preact": "^10.26.4",
"react": ">=16.8.0",
"react-dom": ">=16.8.0",
"svelte": ">=5 <6",
"vue": ">=3.2.0"
}
},
"node_modules/@embedpdf/engines": {
"version": "1.3.14",
"resolved": "https://registry.npmjs.org/@embedpdf/engines/-/engines-1.3.14.tgz",
"integrity": "sha512-+/FPW2gAzj2lQYvsMH/Oj9+MEXgkyEuyYDC+HFkltTuXvmiP2S/3BD0YslZDX9K4BzcmMxnWB+BiQpNJokbDVg==",
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/@embedpdf/engines/-/engines-1.4.1.tgz",
"integrity": "sha512-yugIb5OwTI/1VnAaEvSYxAd2DvYBPkV/D7wytagyaOq98o3sqzcY2Q9zHt+LhnawA5KKG1e/FDPjCd4qm8gsvg==",
"license": "MIT",
"dependencies": {
"@embedpdf/models": "1.3.14",
"@embedpdf/pdfium": "1.3.14"
"@embedpdf/models": "1.4.1",
"@embedpdf/pdfium": "1.4.1"
},
"peerDependencies": {
"preact": "^10.26.4",
"react": ">=16.8.0",
"react-dom": ">=16.8.0",
"svelte": ">=5 <6",
"vue": ">=3.2.0"
}
},
"node_modules/@embedpdf/models": {
"version": "1.3.14",
"resolved": "https://registry.npmjs.org/@embedpdf/models/-/models-1.3.14.tgz",
"integrity": "sha512-BujY4bmr8b2DQdoZkOge03SzoRVoWxzfIQATLSPPtp4WiFh1U4BPp6cADlGuCwGkp6zBcH/aM4h8PwwA75d/eg==",
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/@embedpdf/models/-/models-1.4.1.tgz",
"integrity": "sha512-2nTg8Q1qpplBvspZJXMCZOA+/OILpfdNRPddlplxZXY/Upx0rzKXx/e6pXWW7AuOgtfGneT4h9tMs3A595/PdQ==",
"license": "MIT"
},
"node_modules/@embedpdf/pdfium": {
"version": "1.3.14",
"resolved": "https://registry.npmjs.org/@embedpdf/pdfium/-/pdfium-1.3.14.tgz",
"integrity": "sha512-TQMZabXzHmzvvfPwopubFcYgQuYV7POvMgjICYu3Pgfn3sgr+UdIUh3aNXR/COcl3q8sXPMFQ2GDuyOHR9QQnA==",
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/@embedpdf/pdfium/-/pdfium-1.4.1.tgz",
"integrity": "sha512-BekKEK4UNCwzj7xOffKn6WpL0FQHxq+mTj2iGI3N7OwAX2J/BO2G+rDOB+lvojQG+Dkpg8uqm427ZKJDRyLgVQ==",
"license": "MIT"
},
"node_modules/@embedpdf/plugin-annotation": {
"version": "1.3.14",
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-annotation/-/plugin-annotation-1.3.14.tgz",
"integrity": "sha512-JJYqEWwUKCdBZsXCDq/CW96p3pVLn8N+XZ4W3OyL7djI2fvYC9x6ys9m82vwlSathAVOxk1D7xXiY8AzJQVF0Q==",
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-annotation/-/plugin-annotation-1.4.1.tgz",
"integrity": "sha512-d4HibNy6ecyDqx2Y2R8VjaqppSdjNofAJmU6VenOd88wn080sAUqvnkeVJ6ehJH5BoND4ymQrcAkcbVeYK0myA==",
"license": "MIT",
"dependencies": {
"@embedpdf/models": "1.3.14",
"@embedpdf/utils": "1.3.14"
"@embedpdf/models": "1.4.1",
"@embedpdf/utils": "1.4.1"
},
"peerDependencies": {
"@embedpdf/core": "1.3.14",
"@embedpdf/plugin-history": "1.3.14",
"@embedpdf/plugin-interaction-manager": "1.3.14",
"@embedpdf/plugin-selection": "1.3.14",
"@embedpdf/core": "1.4.1",
"@embedpdf/plugin-history": "1.4.1",
"@embedpdf/plugin-interaction-manager": "1.4.1",
"@embedpdf/plugin-selection": "1.4.1",
"preact": "^10.26.4",
"react": ">=16.8.0",
"react-dom": ">=16.8.0",
@ -566,31 +569,32 @@
}
},
"node_modules/@embedpdf/plugin-export": {
"version": "1.3.14",
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-export/-/plugin-export-1.3.14.tgz",
"integrity": "sha512-fMGp2YxvI4uTRIViUKxfnJts2Jw/vktEM45XUNGNSjT/kAW6znVNgdceYjpK++xU8CGs2grAQ1i5UvMd3aRNDA==",
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-export/-/plugin-export-1.4.1.tgz",
"integrity": "sha512-g89fREFM/zkt2Ai2Q5dWwDkhXgC/JmVyUniaMgm1fTG/MZ0Z05E7f34DUzX/CKcJyVjxEgl6tojBTMeUbm15bA==",
"license": "MIT",
"dependencies": {
"@embedpdf/models": "1.3.14"
"@embedpdf/models": "1.4.1"
},
"peerDependencies": {
"@embedpdf/core": "1.3.14",
"@embedpdf/core": "1.4.1",
"preact": "^10.26.4",
"react": ">=16.8.0",
"react-dom": ">=16.8.0",
"svelte": ">=5 <6",
"vue": ">=3.2.0"
}
},
"node_modules/@embedpdf/plugin-history": {
"version": "1.3.14",
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-history/-/plugin-history-1.3.14.tgz",
"integrity": "sha512-77hnNLp0W0FHw8lT7SeqzCgp8bOClfeOAPZdcInu/jPDhVASUGYbtE/0fkLhiaqPH7kyMirNCLif4sF6n4b5vg==",
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-history/-/plugin-history-1.4.1.tgz",
"integrity": "sha512-5WLDiNMH6tACkLGGv/lJtNsDeozOhSbrh0mjD1btHun8u7Yscu/Vf8tdJRUOsd+nULivo2nQ2NFNKu0OTbVo8w==",
"license": "MIT",
"dependencies": {
"@embedpdf/models": "1.3.14"
"@embedpdf/models": "1.4.1"
},
"peerDependencies": {
"@embedpdf/core": "1.3.14",
"@embedpdf/core": "1.4.1",
"preact": "^10.26.4",
"react": ">=16.8.0",
"react-dom": ">=16.8.0",
@ -598,49 +602,71 @@
}
},
"node_modules/@embedpdf/plugin-interaction-manager": {
"version": "1.3.14",
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-interaction-manager/-/plugin-interaction-manager-1.3.14.tgz",
"integrity": "sha512-nR0ZxNoTQtGqOHhweFh6QJ+nUJ4S4Ag1wWur6vAUAi8U95HUOfZhOEa0polZo0zR9WmmblGqRWjFM+mVSOoi1w==",
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-interaction-manager/-/plugin-interaction-manager-1.4.1.tgz",
"integrity": "sha512-Ng02S9SFIAi9JZS5rI+NXSnZZ1Yk9YYRw4MlN2pig49qOyivZdz0oScZaYxQPewo8ccJkLeghjdeWswOBW/6cA==",
"license": "MIT",
"dependencies": {
"@embedpdf/models": "1.3.14"
"@embedpdf/models": "1.4.1"
},
"peerDependencies": {
"@embedpdf/core": "1.3.14",
"@embedpdf/core": "1.4.1",
"preact": "^10.26.4",
"react": ">=16.8.0",
"react-dom": ">=16.8.0",
"svelte": ">=5 <6",
"vue": ">=3.2.0"
}
},
"node_modules/@embedpdf/plugin-loader": {
"version": "1.3.14",
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-loader/-/plugin-loader-1.3.14.tgz",
"integrity": "sha512-KoJX1MacEWE2DrO1OeZeG/Ehz76//u+ida/xb4r9BfwqAp5TfYlksq09cOvcF8LMW5FY4pbAL+AHKI1Hjz+HNA==",
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-loader/-/plugin-loader-1.4.1.tgz",
"integrity": "sha512-m3ZOk8JygsLxoa4cZ+0BVB5pfRWuBCg2/gPqjhoFZNKTqAFw4J6HGUrhYKg94GRYe+w1cTJl/NbTBYuU5DOrsA==",
"license": "MIT",
"dependencies": {
"@embedpdf/models": "1.3.14"
"@embedpdf/models": "1.4.1"
},
"peerDependencies": {
"@embedpdf/core": "1.3.14",
"@embedpdf/core": "1.4.1",
"preact": "^10.26.4",
"react": ">=16.8.0",
"react-dom": ">=16.8.0",
"svelte": ">=5 <6",
"vue": ">=3.2.0"
}
},
"node_modules/@embedpdf/plugin-pan": {
"version": "1.3.14",
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-pan/-/plugin-pan-1.3.14.tgz",
"integrity": "sha512-7EG+I5nn8yDCV8pT4x/g5mv7zJli2t3wPrh6Kt8uIpUorPHNb6J0Z67gl0uc/8rEasNzuKOuT0er46Y6/UYLzQ==",
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-pan/-/plugin-pan-1.4.1.tgz",
"integrity": "sha512-zmOZJ9dUqXiaV0F5GPf/5WTWf3jAEkiv153Tl3x8HT9Rfff+WQhV48NruCIBAy/T4jVt4aH7D1zt/B/ftvcdkA==",
"license": "MIT",
"dependencies": {
"@embedpdf/models": "1.3.14"
"@embedpdf/models": "1.4.1"
},
"peerDependencies": {
"@embedpdf/core": "1.3.14",
"@embedpdf/plugin-interaction-manager": "1.3.14",
"@embedpdf/plugin-viewport": "1.3.14",
"@embedpdf/core": "1.4.1",
"@embedpdf/plugin-interaction-manager": "1.4.1",
"@embedpdf/plugin-viewport": "1.4.1",
"preact": "^10.26.4",
"react": ">=16.8.0",
"react-dom": ">=16.8.0",
"svelte": ">=5 <6",
"vue": ">=3.2.0"
}
},
"node_modules/@embedpdf/plugin-redaction": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-redaction/-/plugin-redaction-1.4.1.tgz",
"integrity": "sha512-lyVIDxAwU8mUbhxh8J1dhWZ90086YSZ1+RB50hfCAyaxftRF3XpHPhmLZhr6lr3z1lDx+w9VIcoCU6IvhcweTw==",
"license": "MIT",
"dependencies": {
"@embedpdf/models": "1.4.1",
"@embedpdf/utils": "1.4.1"
},
"peerDependencies": {
"@embedpdf/core": "1.4.1",
"@embedpdf/plugin-interaction-manager": "1.4.1",
"@embedpdf/plugin-selection": "1.4.1",
"preact": "^10.26.4",
"react": ">=16.8.0",
"react-dom": ">=16.8.0",
@ -648,182 +674,192 @@
}
},
"node_modules/@embedpdf/plugin-render": {
"version": "1.3.14",
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-render/-/plugin-render-1.3.14.tgz",
"integrity": "sha512-IPj7GCQXJBsY++JaU+z7y+FwX5NaDBj4YYV6hsHNtSGf42Y1AdlwJzDYetivG2bA84xmk7KgD1X2Y3eIFBhjwA==",
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-render/-/plugin-render-1.4.1.tgz",
"integrity": "sha512-gKCdNKw6WBHBEpTc2DLBWIWOxzsNnaNbpfeY6C4f2Bum0EO+XW3Hl2oIx1uaRHjIhhnXso1J3QweqelsPwDGwg==",
"license": "MIT",
"dependencies": {
"@embedpdf/models": "1.3.14"
"@embedpdf/models": "1.4.1"
},
"peerDependencies": {
"@embedpdf/core": "1.3.14",
"@embedpdf/core": "1.4.1",
"preact": "^10.26.4",
"react": ">=16.8.0",
"react-dom": ">=16.8.0",
"svelte": ">=5 <6",
"vue": ">=3.2.0"
}
},
"node_modules/@embedpdf/plugin-rotate": {
"version": "1.3.14",
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-rotate/-/plugin-rotate-1.3.14.tgz",
"integrity": "sha512-OroEm11x/fPPXI9C0X+nm9LOjwaI0MvsToZRH+HpV60/FbQeOJvt6D8wThCDVLK95Na6A+JeYIMEu+Hiix7H+A==",
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-rotate/-/plugin-rotate-1.4.1.tgz",
"integrity": "sha512-hVzHkKwMNH3tUhxqJGsj5qTLpYZXbj6E74AEcG0w/fz5FrK7EnofPqt0gRfYmIzxnQGIh+39BRtcp8gmx8UNnw==",
"license": "MIT",
"dependencies": {
"@embedpdf/models": "1.3.14"
"@embedpdf/models": "1.4.1"
},
"peerDependencies": {
"@embedpdf/core": "1.3.14",
"@embedpdf/core": "1.4.1",
"preact": "^10.26.4",
"react": ">=16.8.0",
"react-dom": ">=16.8.0",
"svelte": ">=5 <6",
"vue": ">=3.2.0"
}
},
"node_modules/@embedpdf/plugin-scroll": {
"version": "1.3.14",
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-scroll/-/plugin-scroll-1.3.14.tgz",
"integrity": "sha512-fQbt7OlRMLQJMuZj/Bzh0qpRxMw1ld5Qe/OTw8N54b/plljnFA52joE7cITl3H03huWWyHS3NKOScbw7f34dog==",
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-scroll/-/plugin-scroll-1.4.1.tgz",
"integrity": "sha512-Y9O+matB4j4fLim5s/jn7qIi+lMC9vmDJRpJhiWe8bvD9oYLP2xfD/DdhFgAjRKcNhPoxC+j8q8QN5BMeGAv2Q==",
"license": "MIT",
"dependencies": {
"@embedpdf/models": "1.3.14"
"@embedpdf/models": "1.4.1"
},
"peerDependencies": {
"@embedpdf/core": "1.3.14",
"@embedpdf/plugin-viewport": "1.3.14",
"@embedpdf/core": "1.4.1",
"@embedpdf/plugin-viewport": "1.4.1",
"preact": "^10.26.4",
"react": ">=16.8.0",
"react-dom": ">=16.8.0",
"svelte": ">=5 <6",
"vue": ">=3.2.0"
}
},
"node_modules/@embedpdf/plugin-search": {
"version": "1.3.14",
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-search/-/plugin-search-1.3.14.tgz",
"integrity": "sha512-tlZEgR2tG+GSNnh2u1SjCxhUHfTDgcr38sE/xRK1bRLDGPZWlr6Ln7qP7JSWqeYBGni75sGrj0iZqcZbPWyJag==",
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-search/-/plugin-search-1.4.1.tgz",
"integrity": "sha512-8JG4CbOcUsLuT0vHJJ4cECmu+Yn53EokWFUVXi2Mo/XvHjhrQuWmD7+y6s/qQPEpctFYWmUCXTDAX9ynPud+2Q==",
"license": "MIT",
"dependencies": {
"@embedpdf/models": "1.3.14"
"@embedpdf/models": "1.4.1"
},
"peerDependencies": {
"@embedpdf/core": "1.3.14",
"@embedpdf/plugin-loader": "1.3.14",
"@embedpdf/core": "1.4.1",
"@embedpdf/plugin-loader": "1.4.1",
"preact": "^10.26.4",
"react": ">=16.8.0",
"react-dom": ">=16.8.0",
"svelte": ">=5 <6",
"vue": ">=3.2.0"
}
},
"node_modules/@embedpdf/plugin-selection": {
"version": "1.3.14",
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-selection/-/plugin-selection-1.3.14.tgz",
"integrity": "sha512-EXENuaAsse3rT6cjA1nYzyrNvoy62ojJl28wblCng6zcs3HSlGPemIQZAvaYKPUxoY608M+6nKlcMQ5neRnk/A==",
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-selection/-/plugin-selection-1.4.1.tgz",
"integrity": "sha512-lo5Ytk1PH0PrRKv6zKVupm4t02VGsqIrnSIeP6NO8Ujx0wfqEhj//sqIuO/EwfFVJD8lcQIP9UUo9y8baCrEog==",
"license": "MIT",
"dependencies": {
"@embedpdf/models": "1.3.14"
"@embedpdf/models": "1.4.1"
},
"peerDependencies": {
"@embedpdf/core": "1.3.14",
"@embedpdf/plugin-interaction-manager": "1.3.14",
"@embedpdf/plugin-viewport": "1.3.14",
"@embedpdf/core": "1.4.1",
"@embedpdf/plugin-interaction-manager": "1.4.1",
"@embedpdf/plugin-viewport": "1.4.1",
"preact": "^10.26.4",
"react": ">=16.8.0",
"react-dom": ">=16.8.0",
"svelte": ">=5 <6",
"vue": ">=3.2.0"
}
},
"node_modules/@embedpdf/plugin-spread": {
"version": "1.3.14",
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-spread/-/plugin-spread-1.3.14.tgz",
"integrity": "sha512-DVlk6tDgUoDRkp2S4Jc3LrRTuf4DPMlph9vywJw5z6Qpbh0vgcMnObg896/S0Eu5FgACNAj0WGcXpLrcrn5b9Q==",
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-spread/-/plugin-spread-1.4.1.tgz",
"integrity": "sha512-l+SrDVGTiiItkt2cEtzv7V/X5HhmLbYHcQ8CFobGeIKdJtzKS1Nu/JSKqg7Ki7eCNgyPL1yMNfNE92bNKYVN4w==",
"license": "MIT",
"dependencies": {
"@embedpdf/models": "1.3.14"
"@embedpdf/models": "1.4.1"
},
"peerDependencies": {
"@embedpdf/core": "1.3.14",
"@embedpdf/plugin-loader": "1.3.14",
"@embedpdf/core": "1.4.1",
"@embedpdf/plugin-loader": "1.4.1",
"preact": "^10.26.4",
"react": ">=16.8.0",
"react-dom": ">=16.8.0",
"svelte": ">=5 <6",
"vue": ">=3.2.0"
}
},
"node_modules/@embedpdf/plugin-thumbnail": {
"version": "1.3.14",
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-thumbnail/-/plugin-thumbnail-1.3.14.tgz",
"integrity": "sha512-cnwb5dG8Jph8XSArys1WFCQ6kK2R5FKoO0B5mDrHFv9Fcm2pKszlmZC/NDoskX4pgNUgSnwhI1X3cP37ebF9Ng==",
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-thumbnail/-/plugin-thumbnail-1.4.1.tgz",
"integrity": "sha512-bN3msjI0PovazgbPK3LyugYVTwIDo0RyBUhBaG42FgJxeY3hmFOWTPgfUH1QF7twHlySnksIvHRFYR3nViryVw==",
"license": "MIT",
"dependencies": {
"@embedpdf/models": "1.3.14"
"@embedpdf/models": "1.4.1"
},
"peerDependencies": {
"@embedpdf/core": "1.3.14",
"@embedpdf/plugin-render": "1.3.14",
"@embedpdf/core": "1.4.1",
"@embedpdf/plugin-render": "1.4.1",
"preact": "^10.26.4",
"react": ">=16.8.0",
"react-dom": ">=16.8.0",
"svelte": ">=5 <6",
"vue": ">=3.2.0"
}
},
"node_modules/@embedpdf/plugin-tiling": {
"version": "1.3.14",
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-tiling/-/plugin-tiling-1.3.14.tgz",
"integrity": "sha512-SaCTo2LdZwGeE6jCqkwJxvwt8YKbsI3QGxa9S7Ez+5OcBchlhHeTfLQswcErDQ3WH2p8WHtGuucAcOLrVVOm0A==",
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-tiling/-/plugin-tiling-1.4.1.tgz",
"integrity": "sha512-wgTfj5T8HV6KP61iiR63DVNrbVp8sPxTqa1Sm+2/D0jY+EPSSCmpt1/qYWiAXd1X+t78foOjCnfbo7fEMn5/pg==",
"license": "MIT",
"dependencies": {
"@embedpdf/models": "1.3.14"
"@embedpdf/models": "1.4.1"
},
"peerDependencies": {
"@embedpdf/core": "1.3.14",
"@embedpdf/plugin-render": "1.3.14",
"@embedpdf/plugin-scroll": "1.3.14",
"@embedpdf/plugin-viewport": "1.3.14",
"@embedpdf/core": "1.4.1",
"@embedpdf/plugin-render": "1.4.1",
"@embedpdf/plugin-scroll": "1.4.1",
"@embedpdf/plugin-viewport": "1.4.1",
"preact": "^10.26.4",
"react": ">=16.8.0",
"react-dom": ">=16.8.0",
"svelte": ">=5 <6",
"vue": ">=3.2.0"
}
},
"node_modules/@embedpdf/plugin-viewport": {
"version": "1.3.14",
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-viewport/-/plugin-viewport-1.3.14.tgz",
"integrity": "sha512-mfJ7EbbU68eKk6oFvQ4ozGJNpxUxWbjQ5Gm3uuB+Gj5/tWgBocBOX36k/9LgivEEeX7g2S0tOgyErljApmH8Vg==",
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-viewport/-/plugin-viewport-1.4.1.tgz",
"integrity": "sha512-+TgFHKPCLTBiDYe2DdsmTS37hwQgcZ3dYIc7bE0l5cp+GVwouu1h0MTmjL+90loizeWwCiu10E/zXR6hz+CUaQ==",
"license": "MIT",
"dependencies": {
"@embedpdf/models": "1.3.14"
"@embedpdf/models": "1.4.1"
},
"peerDependencies": {
"@embedpdf/core": "1.3.14",
"@embedpdf/core": "1.4.1",
"preact": "^10.26.4",
"react": ">=16.8.0",
"react-dom": ">=16.8.0",
"svelte": ">=5 <6",
"vue": ">=3.2.0"
}
},
"node_modules/@embedpdf/plugin-zoom": {
"version": "1.3.14",
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-zoom/-/plugin-zoom-1.3.14.tgz",
"integrity": "sha512-/N5tyMk+8OzhObrS3O9yPkcmX8EPiuTo+WaT2QCVSmIUqKnOO4AnKpHJ6Vl0uVhcuXHCMwLucZKyhJ7tRqavwg==",
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-zoom/-/plugin-zoom-1.4.1.tgz",
"integrity": "sha512-9HocmXnPZxqN06q7kyNAmLjgDHOEW8/8QfgNE3nMpRyNHIgnAjxvsWc9lApgp5ErDPG0cSDt0Cduil6nB3wSBQ==",
"license": "MIT",
"dependencies": {
"@embedpdf/models": "1.3.14",
"@embedpdf/models": "1.4.1",
"hammerjs": "^2.0.8"
},
"peerDependencies": {
"@embedpdf/core": "1.3.14",
"@embedpdf/plugin-interaction-manager": "1.3.14",
"@embedpdf/plugin-scroll": "1.3.14",
"@embedpdf/plugin-viewport": "1.3.14",
"@embedpdf/core": "1.4.1",
"@embedpdf/plugin-interaction-manager": "1.4.1",
"@embedpdf/plugin-scroll": "1.4.1",
"@embedpdf/plugin-viewport": "1.4.1",
"preact": "^10.26.4",
"react": ">=16.8.0",
"react-dom": ">=16.8.0",
"svelte": ">=5 <6",
"vue": ">=3.2.0"
}
},
"node_modules/@embedpdf/utils": {
"version": "1.3.14",
"resolved": "https://registry.npmjs.org/@embedpdf/utils/-/utils-1.3.14.tgz",
"integrity": "sha512-gxEJD12nageCMqAjdbicNfDQolXU3nvnV0EX96OdZITRNj0Q1tisutVYoaxcCiJu3vvIEOzipjsAnQOubbFCEA==",
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/@embedpdf/utils/-/utils-1.4.1.tgz",
"integrity": "sha512-vvJ51Qsz3PyJWR2YvDMMpJXg4+YqdV7Vn2cusmW9sx+4EnAiBiw0HevEE+FepgFV8k+A0WbwXzmsujDIQJ7R4A==",
"license": "MIT",
"peerDependencies": {
"preact": "^10.26.4",
@ -3045,6 +3081,16 @@
"url": "https://github.com/sindresorhus/is?sponsor=1"
}
},
"node_modules/@sveltejs/acorn-typescript": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/@sveltejs/acorn-typescript/-/acorn-typescript-1.0.6.tgz",
"integrity": "sha512-4awhxtMh4cx9blePWl10HRHj8Iivtqj+2QdDCSMDzxG+XKa9+VCNupQuCuvzEhYPzZSrX+0gC+0lHA/0fFKKQQ==",
"license": "MIT",
"peer": true,
"peerDependencies": {
"acorn": "^8.9.0"
}
},
"node_modules/@swc/core": {
"version": "1.13.5",
"resolved": "https://registry.npmjs.org/@swc/core/-/core-1.13.5.tgz",
@ -3802,7 +3848,6 @@
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
"integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/gapi": {
@ -4605,13 +4650,13 @@
}
},
"node_modules/@vue/compiler-core": {
"version": "3.5.21",
"resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.21.tgz",
"integrity": "sha512-8i+LZ0vf6ZgII5Z9XmUvrCyEzocvWT+TeR2VBUVlzIH6Tyv57E20mPZ1bCS+tbejgUgmjrEh7q/0F0bibskAmw==",
"version": "3.5.22",
"resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.22.tgz",
"integrity": "sha512-jQ0pFPmZwTEiRNSb+i9Ow/I/cHv2tXYqsnHKKyCQ08irI2kdF5qmYedmF8si8mA7zepUFmJ2hqzS8CQmNOWOkQ==",
"license": "MIT",
"dependencies": {
"@babel/parser": "^7.28.3",
"@vue/shared": "3.5.21",
"@babel/parser": "^7.28.4",
"@vue/shared": "3.5.22",
"entities": "^4.5.0",
"estree-walker": "^2.0.2",
"source-map-js": "^1.2.1"
@ -4636,28 +4681,28 @@
"license": "MIT"
},
"node_modules/@vue/compiler-dom": {
"version": "3.5.21",
"resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.21.tgz",
"integrity": "sha512-jNtbu/u97wiyEBJlJ9kmdw7tAr5Vy0Aj5CgQmo+6pxWNQhXZDPsRr1UWPN4v3Zf82s2H3kF51IbzZ4jMWAgPlQ==",
"version": "3.5.22",
"resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.22.tgz",
"integrity": "sha512-W8RknzUM1BLkypvdz10OVsGxnMAuSIZs9Wdx1vzA3mL5fNMN15rhrSCLiTm6blWeACwUwizzPVqGJgOGBEN/hA==",
"license": "MIT",
"dependencies": {
"@vue/compiler-core": "3.5.21",
"@vue/shared": "3.5.21"
"@vue/compiler-core": "3.5.22",
"@vue/shared": "3.5.22"
}
},
"node_modules/@vue/compiler-sfc": {
"version": "3.5.21",
"resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.21.tgz",
"integrity": "sha512-SXlyk6I5eUGBd2v8Ie7tF6ADHE9kCR6mBEuPyH1nUZ0h6Xx6nZI29i12sJKQmzbDyr2tUHMhhTt51Z6blbkTTQ==",
"version": "3.5.22",
"resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.22.tgz",
"integrity": "sha512-tbTR1zKGce4Lj+JLzFXDq36K4vcSZbJ1RBu8FxcDv1IGRz//Dh2EBqksyGVypz3kXpshIfWKGOCcqpSbyGWRJQ==",
"license": "MIT",
"dependencies": {
"@babel/parser": "^7.28.3",
"@vue/compiler-core": "3.5.21",
"@vue/compiler-dom": "3.5.21",
"@vue/compiler-ssr": "3.5.21",
"@vue/shared": "3.5.21",
"@babel/parser": "^7.28.4",
"@vue/compiler-core": "3.5.22",
"@vue/compiler-dom": "3.5.22",
"@vue/compiler-ssr": "3.5.22",
"@vue/shared": "3.5.22",
"estree-walker": "^2.0.2",
"magic-string": "^0.30.18",
"magic-string": "^0.30.19",
"postcss": "^8.5.6",
"source-map-js": "^1.2.1"
}
@ -4669,67 +4714,67 @@
"license": "MIT"
},
"node_modules/@vue/compiler-ssr": {
"version": "3.5.21",
"resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.21.tgz",
"integrity": "sha512-vKQ5olH5edFZdf5ZrlEgSO1j1DMA4u23TVK5XR1uMhvwnYvVdDF0nHXJUblL/GvzlShQbjhZZ2uvYmDlAbgo9w==",
"version": "3.5.22",
"resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.22.tgz",
"integrity": "sha512-GdgyLvg4R+7T8Nk2Mlighx7XGxq/fJf9jaVofc3IL0EPesTE86cP/8DD1lT3h1JeZr2ySBvyqKQJgbS54IX1Ww==",
"license": "MIT",
"dependencies": {
"@vue/compiler-dom": "3.5.21",
"@vue/shared": "3.5.21"
"@vue/compiler-dom": "3.5.22",
"@vue/shared": "3.5.22"
}
},
"node_modules/@vue/reactivity": {
"version": "3.5.21",
"resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.21.tgz",
"integrity": "sha512-3ah7sa+Cwr9iiYEERt9JfZKPw4A2UlbY8RbbnH2mGCE8NwHkhmlZt2VsH0oDA3P08X3jJd29ohBDtX+TbD9AsA==",
"version": "3.5.22",
"resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.22.tgz",
"integrity": "sha512-f2Wux4v/Z2pqc9+4SmgZC1p73Z53fyD90NFWXiX9AKVnVBEvLFOWCEgJD3GdGnlxPZt01PSlfmLqbLYzY/Fw4A==",
"license": "MIT",
"peer": true,
"dependencies": {
"@vue/shared": "3.5.21"
"@vue/shared": "3.5.22"
}
},
"node_modules/@vue/runtime-core": {
"version": "3.5.21",
"resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.21.tgz",
"integrity": "sha512-+DplQlRS4MXfIf9gfD1BOJpk5RSyGgGXD/R+cumhe8jdjUcq/qlxDawQlSI8hCKupBlvM+3eS1se5xW+SuNAwA==",
"version": "3.5.22",
"resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.22.tgz",
"integrity": "sha512-EHo4W/eiYeAzRTN5PCextDUZ0dMs9I8mQ2Fy+OkzvRPUYQEyK9yAjbasrMCXbLNhF7P0OUyivLjIy0yc6VrLJQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"@vue/reactivity": "3.5.21",
"@vue/shared": "3.5.21"
"@vue/reactivity": "3.5.22",
"@vue/shared": "3.5.22"
}
},
"node_modules/@vue/runtime-dom": {
"version": "3.5.21",
"resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.21.tgz",
"integrity": "sha512-3M2DZsOFwM5qI15wrMmNF5RJe1+ARijt2HM3TbzBbPSuBHOQpoidE+Pa+XEaVN+czbHf81ETRoG1ltztP2em8w==",
"version": "3.5.22",
"resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.22.tgz",
"integrity": "sha512-Av60jsryAkI023PlN7LsqrfPvwfxOd2yAwtReCjeuugTJTkgrksYJJstg1e12qle0NarkfhfFu1ox2D+cQotww==",
"license": "MIT",
"peer": true,
"dependencies": {
"@vue/reactivity": "3.5.21",
"@vue/runtime-core": "3.5.21",
"@vue/shared": "3.5.21",
"@vue/reactivity": "3.5.22",
"@vue/runtime-core": "3.5.22",
"@vue/shared": "3.5.22",
"csstype": "^3.1.3"
}
},
"node_modules/@vue/server-renderer": {
"version": "3.5.21",
"resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.21.tgz",
"integrity": "sha512-qr8AqgD3DJPJcGvLcJKQo2tAc8OnXRcfxhOJCPF+fcfn5bBGz7VCcO7t+qETOPxpWK1mgysXvVT/j+xWaHeMWA==",
"version": "3.5.22",
"resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.22.tgz",
"integrity": "sha512-gXjo+ao0oHYTSswF+a3KRHZ1WszxIqO7u6XwNHqcqb9JfyIL/pbWrrh/xLv7jeDqla9u+LK7yfZKHih1e1RKAQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"@vue/compiler-ssr": "3.5.21",
"@vue/shared": "3.5.21"
"@vue/compiler-ssr": "3.5.22",
"@vue/shared": "3.5.22"
},
"peerDependencies": {
"vue": "3.5.21"
"vue": "3.5.22"
}
},
"node_modules/@vue/shared": {
"version": "3.5.21",
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.21.tgz",
"integrity": "sha512-+2k1EQpnYuVuu3N7atWyG3/xoFWIVJZq4Mz8XNOdScFI0etES75fbny/oU4lKWk/577P1zmg0ioYvpGEDZ3DLw==",
"version": "3.5.22",
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.22.tgz",
"integrity": "sha512-F4yc6palwq3TT0u+FYf0Ns4Tfl9GRFURDN2gWG7L1ecIaS/4fCIuFOjMTnCyjsu/OK6vaDKLCrGAa+KvvH+h4w==",
"license": "MIT"
},
"node_modules/abbrev": {
@ -4743,7 +4788,6 @@
"version": "8.15.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
"dev": true,
"license": "MIT",
"bin": {
"acorn": "bin/acorn"
@ -5147,6 +5191,16 @@
"proxy-from-env": "^1.1.0"
}
},
"node_modules/axobject-query": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz",
"integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==",
"license": "Apache-2.0",
"peer": true,
"engines": {
"node": ">= 0.4"
}
},
"node_modules/b4a": {
"version": "1.7.3",
"resolved": "https://registry.npmjs.org/b4a/-/b4a-1.7.3.tgz",
@ -7176,6 +7230,13 @@
"node": "*"
}
},
"node_modules/esm-env": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.2.2.tgz",
"integrity": "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==",
"license": "MIT",
"peer": true
},
"node_modules/espree": {
"version": "10.4.0",
"resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz",
@ -7234,6 +7295,16 @@
"node": ">=0.10"
}
},
"node_modules/esrap": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/esrap/-/esrap-2.1.1.tgz",
"integrity": "sha512-ebTT9B6lOtZGMgJ3o5r12wBacHctG7oEWazIda8UlPfA3HD/Wrv8FdXoVo73vzdpwCxNyXjPauyN2bbJzMkB9A==",
"license": "MIT",
"peer": true,
"dependencies": {
"@jridgewell/sourcemap-codec": "^1.4.15"
}
},
"node_modules/esrecurse": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
@ -8747,6 +8818,16 @@
"dev": true,
"license": "MIT"
},
"node_modules/is-reference": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.3.tgz",
"integrity": "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==",
"license": "MIT",
"peer": true,
"dependencies": {
"@types/estree": "^1.0.6"
}
},
"node_modules/is-regex": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz",
@ -9623,6 +9704,13 @@
"url": "https://github.com/sponsors/antfu"
}
},
"node_modules/locate-character": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/locate-character/-/locate-character-3.0.0.tgz",
"integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==",
"license": "MIT",
"peer": true
},
"node_modules/locate-path": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
@ -12959,6 +13047,42 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/svelte": {
"version": "5.43.0",
"resolved": "https://registry.npmjs.org/svelte/-/svelte-5.43.0.tgz",
"integrity": "sha512-1sRxVbgJAB+UGzwkc3GUoiBSzEOf0jqzccMaVoI2+pI+kASUe9qubslxace8+Mzhqw19k4syTA5niCIJwfXpOA==",
"license": "MIT",
"peer": true,
"dependencies": {
"@jridgewell/remapping": "^2.3.4",
"@jridgewell/sourcemap-codec": "^1.5.0",
"@sveltejs/acorn-typescript": "^1.0.5",
"@types/estree": "^1.0.5",
"acorn": "^8.12.1",
"aria-query": "^5.3.1",
"axobject-query": "^4.1.0",
"clsx": "^2.1.1",
"esm-env": "^1.2.1",
"esrap": "^2.1.0",
"is-reference": "^3.0.3",
"locate-character": "^3.0.0",
"magic-string": "^0.30.11",
"zimmerframe": "^1.1.2"
},
"engines": {
"node": ">=18"
}
},
"node_modules/svelte/node_modules/aria-query": {
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz",
"integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==",
"license": "Apache-2.0",
"peer": true,
"engines": {
"node": ">= 0.4"
}
},
"node_modules/symbol-tree": {
"version": "3.2.4",
"resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz",
@ -14026,17 +14150,17 @@
}
},
"node_modules/vue": {
"version": "3.5.21",
"resolved": "https://registry.npmjs.org/vue/-/vue-3.5.21.tgz",
"integrity": "sha512-xxf9rum9KtOdwdRkiApWL+9hZEMWE90FHh8yS1+KJAiWYh+iGWV1FquPjoO9VUHQ+VIhsCXNNyZ5Sf4++RVZBA==",
"version": "3.5.22",
"resolved": "https://registry.npmjs.org/vue/-/vue-3.5.22.tgz",
"integrity": "sha512-toaZjQ3a/G/mYaLSbV+QsQhIdMo9x5rrqIpYRObsJ6T/J+RyCSFwN2LHNVH9v8uIcljDNa3QzPVdv3Y6b9hAJQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"@vue/compiler-dom": "3.5.21",
"@vue/compiler-sfc": "3.5.21",
"@vue/runtime-dom": "3.5.21",
"@vue/server-renderer": "3.5.21",
"@vue/shared": "3.5.21"
"@vue/compiler-dom": "3.5.22",
"@vue/compiler-sfc": "3.5.22",
"@vue/runtime-dom": "3.5.22",
"@vue/server-renderer": "3.5.22",
"@vue/shared": "3.5.22"
},
"peerDependencies": {
"typescript": "*"
@ -14447,6 +14571,13 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/zimmerframe": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/zimmerframe/-/zimmerframe-1.1.4.tgz",
"integrity": "sha512-B58NGBEoc8Y9MWWCQGl/gq9xBCe4IiKM0a2x7GZdQKOW5Exr8S1W24J6OgM1njK8xCRGvAJIL/MxXHf6SkmQKQ==",
"license": "MIT",
"peer": true
},
"node_modules/zod": {
"version": "3.25.76",
"resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",

View File

@ -6,24 +6,25 @@
"proxy": "http://localhost:8080",
"dependencies": {
"@atlaskit/pragmatic-drag-and-drop": "^1.7.7",
"@embedpdf/core": "^1.3.14",
"@embedpdf/engines": "^1.3.14",
"@embedpdf/plugin-annotation": "^1.3.14",
"@embedpdf/plugin-export": "^1.3.14",
"@embedpdf/plugin-history": "^1.3.14",
"@embedpdf/plugin-interaction-manager": "^1.3.14",
"@embedpdf/plugin-loader": "^1.3.14",
"@embedpdf/plugin-pan": "^1.3.14",
"@embedpdf/plugin-render": "^1.3.14",
"@embedpdf/plugin-rotate": "^1.3.14",
"@embedpdf/plugin-scroll": "^1.3.14",
"@embedpdf/plugin-search": "^1.3.14",
"@embedpdf/plugin-selection": "^1.3.14",
"@embedpdf/plugin-spread": "^1.3.14",
"@embedpdf/plugin-thumbnail": "^1.3.14",
"@embedpdf/plugin-tiling": "^1.3.14",
"@embedpdf/plugin-viewport": "^1.3.14",
"@embedpdf/plugin-zoom": "^1.3.14",
"@embedpdf/core": "^1.4.1",
"@embedpdf/engines": "^1.4.1",
"@embedpdf/plugin-annotation": "^1.4.1",
"@embedpdf/plugin-export": "^1.4.1",
"@embedpdf/plugin-history": "^1.4.1",
"@embedpdf/plugin-interaction-manager": "^1.4.1",
"@embedpdf/plugin-loader": "^1.4.1",
"@embedpdf/plugin-pan": "^1.4.1",
"@embedpdf/plugin-redaction": "^1.4.1",
"@embedpdf/plugin-render": "^1.4.1",
"@embedpdf/plugin-rotate": "^1.4.1",
"@embedpdf/plugin-scroll": "^1.4.1",
"@embedpdf/plugin-search": "^1.4.1",
"@embedpdf/plugin-selection": "^1.4.1",
"@embedpdf/plugin-spread": "^1.4.1",
"@embedpdf/plugin-thumbnail": "^1.4.1",
"@embedpdf/plugin-tiling": "^1.4.1",
"@embedpdf/plugin-viewport": "^1.4.1",
"@embedpdf/plugin-zoom": "^1.4.1",
"@emotion/react": "^11.14.0",
"@emotion/styled": "^11.14.1",
"@iconify/react": "^6.0.2",

View File

@ -68,6 +68,11 @@
color: var(--right-rail-icon);
}
.right-rail-icon--active {
background-color: var(--accent-interactive) !important;
color: white !important;
}
.right-rail-icon[aria-disabled="true"],
.right-rail-icon[disabled] {
color: var(--right-rail-icon-disabled) !important;

View File

@ -0,0 +1,671 @@
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { createPluginRegistration } from '@embedpdf/core';
import { EmbedPDF } from '@embedpdf/core/react';
import { usePdfiumEngine } from '@embedpdf/engines/react';
import { Viewport, ViewportPluginPackage } from '@embedpdf/plugin-viewport/react';
import { Scroller, ScrollPluginPackage, ScrollStrategy } from '@embedpdf/plugin-scroll/react';
import { LoaderPluginPackage } from '@embedpdf/plugin-loader/react';
import { RenderPluginPackage } from '@embedpdf/plugin-render/react';
import { ZoomPluginPackage } from '@embedpdf/plugin-zoom/react';
import { InteractionManagerPluginPackage, PagePointerProvider, GlobalPointerProvider } from '@embedpdf/plugin-interaction-manager/react';
import { SelectionLayer, SelectionPluginPackage } from '@embedpdf/plugin-selection/react';
import { TilingLayer, TilingPluginPackage } from '@embedpdf/plugin-tiling/react';
import { SpreadPluginPackage, SpreadMode } from '@embedpdf/plugin-spread/react';
import { SearchPluginPackage } from '@embedpdf/plugin-search/react';
import { ThumbnailPluginPackage } from '@embedpdf/plugin-thumbnail/react';
import { RotatePluginPackage, Rotate } from '@embedpdf/plugin-rotate/react';
import { ExportPluginPackage } from '@embedpdf/plugin-export/react';
import { HistoryPluginPackage } from '@embedpdf/plugin-history/react';
import { RedactionPluginPackage, RedactionLayer, useRedaction } from '@embedpdf/plugin-redaction/react';
import type { SelectionMenuProps } from '@embedpdf/plugin-redaction/react';
import { Stack, Group, Text, Button, Alert, Loader } from '@mantine/core';
import { useTranslation } from 'react-i18next';
import CropFreeRoundedIcon from '@mui/icons-material/CropFreeRounded';
import TextFieldsRoundedIcon from '@mui/icons-material/TextFieldsRounded';
import UndoRoundedIcon from '@mui/icons-material/UndoRounded';
import RedoRoundedIcon from '@mui/icons-material/RedoRounded';
import ToolLoadingFallback from '@app/components/tools/ToolLoadingFallback';
import { alert } from '@app/components/toast';
import { useRightRailButtons, type RightRailButtonWithAction } from '@app/hooks/useRightRailButtons';
import { useNavigationActions } from '@app/contexts/NavigationContext';
import type { ManualRedactionWorkbenchData } from '@app/types/redact';
interface ManualRedactionWorkbenchViewProps {
data: ManualRedactionWorkbenchData | null;
}
const toPdfBlob = async (value: any): Promise<Blob | null> => {
if (!value) return null;
if (value instanceof Blob) return value;
if (value instanceof ArrayBuffer) return new Blob([value], { type: 'application/pdf' });
if (value instanceof Uint8Array) {
const copy = new Uint8Array(value.byteLength);
copy.set(value);
return new Blob([copy.buffer], { type: 'application/pdf' });
}
if (value.data instanceof ArrayBuffer) return new Blob([value.data], { type: 'application/pdf' });
if (value.blob instanceof Blob) return value.blob;
if (typeof value.toBlob === 'function') {
return value.toBlob();
}
if (typeof value.toPromise === 'function') {
const result = await value.toPromise();
if (result instanceof ArrayBuffer) return new Blob([result], { type: 'application/pdf' });
}
if (typeof value.arrayBuffer === 'function') {
const buffer = await value.arrayBuffer();
return new Blob([buffer], { type: 'application/pdf' });
}
return null;
};
const buildRedactedFileName = (name: string | undefined | null) => {
if (!name || name.trim() === '') {
return 'redacted.pdf';
}
const lower = name.toLowerCase();
if (lower.includes('redacted')) {
return name;
}
const dotIndex = name.lastIndexOf('.');
if (dotIndex === -1) {
return `${name}_redacted.pdf`;
}
const base = name.slice(0, dotIndex);
const ext = name.slice(dotIndex);
return `${base}_redacted${ext}`;
};
const ManualRedactionWorkbenchView = ({ data }: ManualRedactionWorkbenchViewProps) => {
const { t } = useTranslation();
const { actions: navigationActions } = useNavigationActions();
const redactionApiRef = useRef<Record<string, any> | null>(null);
const exportApiRef = useRef<Record<string, any> | null>(null);
const selectionApiRef = useRef<Record<string, any> | null>(null);
const historyApiRef = useRef<Record<string, any> | null>(null);
const [isReady, setIsReady] = useState(false);
const [isApplying, setIsApplying] = useState(false);
const [canUndo, setCanUndo] = useState(false);
const [canRedo, setCanRedo] = useState(false);
const [activeType, setActiveType] = useState<string | null>(null);
const [pdfUrl, setPdfUrl] = useState<string | null>(null);
const [objectUrl, setObjectUrl] = useState<string | null>(null);
const selectedFile = data?.file ?? null;
const redactionPluginPackage = RedactionPluginPackage;
const RedactionLayerComponent = RedactionLayer;
const hasRedactionSupport = Boolean(redactionPluginPackage && RedactionLayerComponent);
const exitWorkbench = useCallback(() => {
if (data?.onExit) {
data.onExit();
} else {
navigationActions.setWorkbench('fileEditor');
}
}, [data, navigationActions]);
useEffect(() => {
if (selectedFile) {
const url = URL.createObjectURL(selectedFile);
setPdfUrl(url);
setObjectUrl(url);
return () => {
URL.revokeObjectURL(url);
setObjectUrl(null);
};
}
setPdfUrl(null);
return () => {};
}, [selectedFile]);
const { engine, isLoading, error } = usePdfiumEngine();
const plugins = useMemo(() => {
if (!pdfUrl) return [];
const rootFontSize = typeof window !== 'undefined'
? parseFloat(getComputedStyle(document.documentElement).fontSize)
: 16;
const viewportGap = rootFontSize * 3.5;
const baseRegistrations: any[] = [
createPluginRegistration(LoaderPluginPackage, {
loadingOptions: {
type: 'url',
pdfFile: {
id: 'stirling-pdf-manual-redaction',
url: pdfUrl,
},
},
}),
createPluginRegistration(ViewportPluginPackage, { viewportGap }),
createPluginRegistration(ScrollPluginPackage, {
strategy: ScrollStrategy.Vertical,
initialPage: 0,
}),
createPluginRegistration(RenderPluginPackage),
createPluginRegistration(InteractionManagerPluginPackage),
createPluginRegistration(SelectionPluginPackage),
createPluginRegistration(HistoryPluginPackage),
// Intentionally omit Pan plugin here so drag gestures are captured by redaction/selection layers
createPluginRegistration(ZoomPluginPackage, {
defaultZoomLevel: 1.2,
minZoom: 0.25,
maxZoom: 4,
}),
createPluginRegistration(TilingPluginPackage, {
tileSize: 768,
overlapPx: 5,
extraRings: 1,
}),
createPluginRegistration(SpreadPluginPackage, {
defaultSpreadMode: SpreadMode.None,
}),
createPluginRegistration(SearchPluginPackage),
createPluginRegistration(ThumbnailPluginPackage),
createPluginRegistration(RotatePluginPackage),
createPluginRegistration(ExportPluginPackage, {
defaultFileName: buildRedactedFileName(data?.fileName),
}),
];
if (hasRedactionSupport) {
baseRegistrations.splice(6, 0, createPluginRegistration(redactionPluginPackage, { autoPreview: true }));
}
return baseRegistrations;
}, [pdfUrl, data?.fileName, hasRedactionSupport, redactionPluginPackage]);
const assignPluginApi = useCallback((plugin: any, ref: React.MutableRefObject<Record<string, any> | null>, onReady?: () => void) => {
if (!plugin || typeof plugin.provides !== 'function') return;
try {
const provided = plugin.provides();
if (provided && typeof provided.then === 'function') {
provided
.then((resolved: any) => {
ref.current = resolved ?? null;
onReady?.();
})
.catch((err: any) => {
console.warn('[manual-redaction] Failed to resolve plugin capability', err);
});
} else {
ref.current = provided ?? null;
onReady?.();
}
} catch (err) {
console.warn('[manual-redaction] Plugin capability unavailable', err);
}
}, [hasRedactionSupport]);
const handleInitialized = useCallback(async (registry: any) => {
const redactionPlugin = hasRedactionSupport ? registry.getPlugin?.('redaction') : null;
const exportPlugin = registry.getPlugin?.('export');
const historyPlugin = registry.getPlugin?.('history');
if (hasRedactionSupport) {
assignPluginApi(redactionPlugin, redactionApiRef, () => {
setIsReady(true);
// default to area redaction mode
enableAreaRedaction();
// no pan plugin: drags go to redaction/selection layers
// subscribe to state changes to drive undo/redo availability
try {
const api = redactionApiRef.current;
api?.onStateChange?.((state: any) => {
// heuristics: if there are any pending or previous operations, enable undo
const pending = Number(state?.pendingCount ?? 0);
setCanUndo(pending > 0 || Boolean(state?.canUndo));
setCanRedo(Boolean(state?.canRedo));
setActiveType(state?.activeType ?? null);
});
} catch {}
});
} else {
setIsReady(false);
}
assignPluginApi(exportPlugin, exportApiRef);
assignPluginApi(historyPlugin, historyApiRef);
const selectionPlugin = registry.getPlugin?.('selection');
assignPluginApi(selectionPlugin, selectionApiRef);
}, [assignPluginApi, hasRedactionSupport]);
const invokeRedactionMethod = useCallback((names: string[], args: any[] = []) => {
if (!hasRedactionSupport) return false;
const api = redactionApiRef.current;
if (!api) return false;
for (const name of names) {
const candidate = (api as Record<string, any>)[name];
if (typeof candidate === 'function') {
try {
const result = candidate.apply(api, args);
if (result && typeof result.then === 'function') {
// Fire and forget for interactive methods
result.catch((err: any) => console.warn(`[manual-redaction] ${name} failed`, err));
}
return true;
} catch (err) {
console.warn(`[manual-redaction] ${name} threw`, err);
}
}
}
return false;
}, []);
const invokeRedactionMethodAsync = useCallback(async (names: string[], args: any[] = []) => {
if (!hasRedactionSupport) return false;
const api = redactionApiRef.current;
if (!api) return false;
for (const name of names) {
const candidate = (api as Record<string, any>)[name];
if (typeof candidate === 'function') {
try {
const result = candidate.apply(api, args);
if (result && typeof result.then === 'function') {
await result;
}
return true;
} catch (err) {
console.warn(`[manual-redaction] ${name} failed`, err);
}
}
}
return false;
}, []);
const enableAreaRedaction = useCallback(() => {
if (!hasRedactionSupport) return;
const api = redactionApiRef.current;
// Ensure selection plugin is not intercepting as text selection
try { selectionApiRef.current?.setMode?.('none'); } catch {}
// Prefer official capability
if (api?.toggleMarqueeRedact) {
try { api.toggleMarqueeRedact(); setActiveType('marqueeRedact'); return; } catch {}
}
// Fall back to common method names
const areaNames = ['area', 'box', 'rectangle', 'shape'];
for (const mode of areaNames) {
if (invokeRedactionMethod(['activateAreaRedaction', 'startAreaRedaction', 'enableAreaRedaction', 'activateMode'], [mode])) return;
if (invokeRedactionMethod(['setRedactionMode', 'setMode'], [mode])) return;
if (invokeRedactionMethod(['setRedactionMode', 'setMode'], [{ mode }])) return;
if (invokeRedactionMethod(['setMode'], [{ type: mode }])) return;
if (invokeRedactionMethod(['setMode'], [mode.toUpperCase?.() ?? mode])) return;
}
console.warn('[manual-redaction] No compatible area redaction activation method found');
}, [hasRedactionSupport, invokeRedactionMethod]);
const enableTextRedaction = useCallback(() => {
if (!hasRedactionSupport) return;
const api = redactionApiRef.current;
// Ensure selection plugin is in text mode when redacting text
try { selectionApiRef.current?.setMode?.('text'); } catch {}
if (api?.toggleRedactSelection) {
try { api.toggleRedactSelection(); setActiveType('redactSelection'); return; } catch {}
}
const textModes = ['text', 'search', 'pattern'];
for (const mode of textModes) {
if (invokeRedactionMethod(['activateTextRedaction', 'startTextRedaction', 'enableTextRedaction', 'activateMode'], [mode])) return;
if (invokeRedactionMethod(['setRedactionMode', 'setMode'], [mode])) return;
if (invokeRedactionMethod(['setRedactionMode', 'setMode'], [{ mode }])) return;
if (invokeRedactionMethod(['setMode'], [{ type: mode }])) return;
if (invokeRedactionMethod(['setMode'], [mode.toUpperCase?.() ?? mode])) return;
}
console.warn('[manual-redaction] No compatible text redaction activation method found');
}, [hasRedactionSupport, invokeRedactionMethod]);
const handleUndo = useCallback(() => {
if (!hasRedactionSupport) return;
// Prefer redaction-aware undo
if (invokeRedactionMethod(['undo', 'stepBack', 'undoLast'])) {
return;
}
// Fallback: remove the most recent pending mark if available
try {
const state = (redactionApiRef.current?.getState?.() as any) || {};
const pendingMap = state.pending || {};
const pages = Object.keys(pendingMap).map(n => parseInt(n, 10)).sort((a,b) => b-a);
for (const page of pages) {
const items = pendingMap[page];
const last = Array.isArray(items) ? items[items.length - 1] : null;
if (last) {
redactionApiRef.current?.removePending?.(page, last.id);
return;
}
}
} catch {}
const historyApi = historyApiRef.current;
if (historyApi && typeof historyApi.undo === 'function') {
historyApi.undo();
return;
}
console.warn('[manual-redaction] Undo not available');
}, [hasRedactionSupport, invokeRedactionMethod]);
const handleRedo = useCallback(() => {
if (!hasRedactionSupport) return;
if (invokeRedactionMethod(['redo', 'stepForward', 'redoLast'])) {
return;
}
const historyApi = historyApiRef.current;
if (historyApi && typeof historyApi.redo === 'function') {
historyApi.redo();
return;
}
console.warn('[manual-redaction] Redo not available');
}, [hasRedactionSupport, invokeRedactionMethod]);
const exportRedactedBlob = useCallback(async (): Promise<Blob | null> => {
if (!hasRedactionSupport) {
throw new Error('Manual redaction plugin is not available.');
}
const redactionApi = redactionApiRef.current;
const exportApi = exportApiRef.current;
const tryCall = async (api: Record<string, any> | null, method: string, args: any[] = []): Promise<any> => {
if (!api) return null;
const candidate = api[method];
if (typeof candidate !== 'function') return null;
try {
const result = candidate.apply(api, args);
if (result && typeof result.then === 'function') {
return await result;
}
return result;
} catch (err) {
console.warn(`[manual-redaction] ${method} failed`, err);
return null;
}
};
const attempts: Array<[Record<string, any> | null, string, any[]]> = [
[redactionApi, 'exportRedactedDocument', [{ type: 'blob' }]],
[redactionApi, 'exportRedactedDocument', []],
[redactionApi, 'getRedactedDocument', []],
[redactionApi, 'getBlob', []],
[redactionApi, 'download', [{ type: 'blob' }]],
[exportApi, 'exportDocument', [{ type: 'blob' }]],
[exportApi, 'exportDocument', []],
[exportApi, 'download', [{ type: 'blob' }]],
];
for (const [api, method, args] of attempts) {
const result = await tryCall(api, method, args);
const blob = await toPdfBlob(result);
if (blob) return blob;
}
// Fallback: some export APIs return handles with toPromise()
if (exportApi && typeof exportApi.saveAsCopy === 'function') {
const handle = exportApi.saveAsCopy();
const blob = await toPdfBlob(handle);
if (blob) return blob;
}
return null;
}, [hasRedactionSupport]);
const handleApplyAndSave = useCallback(async () => {
if (!selectedFile) {
alert({
alertType: 'error',
title: t('redact.manual.noFileSelected', 'No PDF selected'),
body: t('redact.manual.noFileSelectedBody', 'Select a PDF before opening the redaction editor.'),
});
return;
}
setIsApplying(true);
try {
const applied = await invokeRedactionMethodAsync([
'applyRedactions',
'applyPendingRedactions',
'apply',
'commit',
'finalizeRedactions',
'performRedactions',
]);
if (!applied) {
console.warn('[manual-redaction] No compatible apply method found');
}
const blob = await exportRedactedBlob();
if (!blob) {
throw new Error('Unable to export redacted PDF');
}
const outputName = buildRedactedFileName(selectedFile.name);
const exportedFile = new File([blob], outputName, { type: 'application/pdf' });
if (data?.onExport) {
await data.onExport(exportedFile);
}
alert({
alertType: 'success',
title: t('redact.manual.exportSuccess', 'Redacted copy saved'),
body: t('redact.manual.exportSuccessBody', 'A redacted PDF has been added to your files.'),
});
exitWorkbench();
} catch (err) {
const message = err instanceof Error ? err.message : t('redact.manual.exportUnknownError', 'Failed to export redacted PDF.');
alert({
alertType: 'error',
title: t('redact.manual.exportFailed', 'Export failed'),
body: message,
});
} finally {
setIsApplying(false);
}
}, [data, exitWorkbench, exportRedactedBlob, invokeRedactionMethodAsync, navigationActions, selectedFile, t]);
useEffect(() => {
if (!isReady || !hasRedactionSupport) return;
return () => {
setIsReady(false);
redactionApiRef.current = null;
exportApiRef.current = null;
historyApiRef.current = null;
};
}, [hasRedactionSupport, isReady, objectUrl]);
const rightRailButtons = useMemo<RightRailButtonWithAction[]>(() => ([
{
id: 'manual-redaction-area',
icon: <CropFreeRoundedIcon fontSize="small" />,
tooltip: t('redact.manual.buttons.area', 'Mark area for redaction'),
ariaLabel: t('redact.manual.buttons.area', 'Mark area for redaction'),
section: 'top',
order: 0,
disabled: !isReady || !hasRedactionSupport,
className: activeType === 'marqueeRedact' ? 'right-rail-icon--active' : undefined,
onClick: enableAreaRedaction,
},
{
id: 'manual-redaction-text',
icon: <TextFieldsRoundedIcon fontSize="small" />,
tooltip: t('redact.manual.buttons.text', 'Mark text for redaction'),
ariaLabel: t('redact.manual.buttons.text', 'Mark text for redaction'),
section: 'top',
order: 1,
disabled: !isReady || !hasRedactionSupport,
className: activeType === 'redactSelection' ? 'right-rail-icon--active' : undefined,
onClick: enableTextRedaction,
},
{
id: 'manual-redaction-undo',
icon: <UndoRoundedIcon fontSize="small" />,
tooltip: t('redact.manual.buttons.undo', 'Undo last change'),
ariaLabel: t('redact.manual.buttons.undo', 'Undo last change'),
section: 'top',
order: 2,
disabled: !isReady || !hasRedactionSupport || !canUndo,
onClick: handleUndo,
},
{
id: 'manual-redaction-redo',
icon: <RedoRoundedIcon fontSize="small" />,
tooltip: t('redact.manual.buttons.redo', 'Redo change'),
ariaLabel: t('redact.manual.buttons.redo', 'Redo change'),
section: 'top',
order: 3,
disabled: !isReady || !hasRedactionSupport || !canRedo,
onClick: handleRedo,
},
]), [enableAreaRedaction, enableTextRedaction, handleUndo, handleRedo, hasRedactionSupport, isReady, t, canUndo, canRedo]);
useRightRailButtons(rightRailButtons);
if (!selectedFile) {
return (
<Stack gap="md" p="lg" h="100%" align="center" justify="center">
<Alert color="blue" variant="light">
{t('redact.manual.selectFilePrompt', 'Select a single PDF from the sidebar to start manual redaction.')}
</Alert>
</Stack>
);
}
if (isLoading || !engine || !pdfUrl) {
return <ToolLoadingFallback toolName="Manual Redaction Viewer" />;
}
if (error) {
return (
<Stack gap="sm" align="center" justify="center" h="100%" p="xl">
<Alert color="red" variant="light" title={t('redact.manual.loadFailed', 'Unable to open PDF')}>
{error.message}
</Alert>
</Stack>
);
}
return (
<Stack gap="sm" h="100%" p="md" className="manual-redaction-workbench">
<Group justify="space-between" align="center">
<Stack gap={2}>
<Text fw={600}>{t('redact.manual.editorHeading', 'Manual redaction')}</Text>
<Text size="sm" c="dimmed">
{t('redact.manual.editorSubheading', 'Draw rectangles or search for text to mark redactions, then apply the changes.')}
</Text>
<Text size="xs" c="dimmed">
{t('redact.manual.currentFile', 'Current file: {{name}}', { name: selectedFile.name })}
</Text>
</Stack>
<Group gap="sm">
<Button
variant="default"
onClick={exitWorkbench}
>
{t('redact.manual.exit', 'Back to files')}
</Button>
<Button
variant="filled"
color="dark"
onClick={handleApplyAndSave}
disabled={!isReady || isApplying}
leftSection={isApplying ? <Loader size="xs" color="white" /> : undefined}
>
{isApplying
? t('redact.manual.applying', 'Applying…')
: t('redact.manual.applyAndSave', 'Apply & save copy')}
</Button>
</Group>
</Group>
<div
style={{
flex: 1,
minHeight: 0,
minWidth: 0,
position: 'relative',
borderRadius: '0.5rem',
overflow: 'hidden',
boxShadow: 'var(--shadow-md)',
backgroundColor: 'var(--bg-elevated)',
}}
>
<EmbedPDF
engine={engine}
plugins={plugins}
onInitialized={handleInitialized}
>
<GlobalPointerProvider>
<Viewport
style={{
backgroundColor: 'var(--bg-background)',
height: '100%',
width: '100%',
maxHeight: '100%',
maxWidth: '100%',
overflow: 'auto',
position: 'relative',
flex: 1,
minHeight: 0,
minWidth: 0,
contain: 'strict',
}}
>
<Scroller
renderPage={({ document, width, height, pageIndex, scale, rotation }) => (
<Rotate key={document?.id} pageSize={{ width, height }}>
<PagePointerProvider pageIndex={pageIndex} pageWidth={width} pageHeight={height} scale={scale} rotation={rotation}>
<div
style={{
width,
height,
position: 'relative',
userSelect: 'none',
WebkitUserSelect: 'none',
MozUserSelect: 'none',
msUserSelect: 'none',
boxShadow: '0 2px 8px rgba(0, 0, 0, 0.08)',
backgroundColor: 'white',
cursor: activeType === 'marqueeRedact' ? 'crosshair' : activeType === 'redactSelection' ? 'text' : 'auto',
}}
>
<TilingLayer pageIndex={pageIndex} scale={scale} />
<SelectionLayer pageIndex={pageIndex} scale={scale} />
{hasRedactionSupport && RedactionLayerComponent && (
<RedactionLayerComponent
pageIndex={pageIndex}
scale={scale}
rotation={rotation}
selectionMenu={(props: SelectionMenuProps) => <InlineRedactionMenu {...props} />}
/>
)}
</div>
</PagePointerProvider>
</Rotate>
)}
/>
</Viewport>
</GlobalPointerProvider>
</EmbedPDF>
</div>
</Stack>
);
};
export default ManualRedactionWorkbenchView;
// Inline redaction menu displayed beneath selection/rectangle
function InlineRedactionMenu({ item, selected, menuWrapperProps }: SelectionMenuProps) {
const { provides } = useRedaction();
if (!selected) return null;
return (
<div {...menuWrapperProps} style={{ ...menuWrapperProps?.style, pointerEvents: 'auto' }}>
<Group gap="xs" p={4} style={{ background: 'var(--bg-surface)', border: '1px solid var(--border-default)', borderRadius: 8, boxShadow: 'var(--shadow-sm)' }}>
<Button size="xs" color="red" onClick={() => provides?.commitPending?.(item.page, item.id)}>
Apply
</Button>
<Button size="xs" variant="default" onClick={() => provides?.removePending?.(item.page, item.id)}>
Cancel
</Button>
</Group>
</div>
);
}

View File

@ -24,7 +24,6 @@ export default function RedactModeSelector({ mode, onModeChange, disabled }: Red
{
value: 'manual' as const,
label: t('redact.modeSelector.manual', 'Manual'),
disabled: true, // Keep manual mode disabled until implemented
},
]}
disabled={disabled}

View File

@ -15,7 +15,7 @@ export const useRedactModeTips = (): TooltipContent => {
},
{
title: t("redact.tooltip.mode.manual.title", "Manual Redaction"),
description: t("redact.tooltip.mode.manual.text", "Click and drag to manually select specific areas to redact. Gives you precise control over what gets redacted. (Coming soon)")
description: t("redact.tooltip.mode.manual.text", "Click and drag to manually select specific areas or search for text to redact. Gives you precise control over what gets removed.")
}
]
};

View File

@ -17,8 +17,7 @@ export const buildRedactFormData = (parameters: RedactParameters, file: File): F
formData.append("customPadding", parameters.customPadding.toString());
formData.append("convertPDFToImage", parameters.convertPDFToImage.toString());
} else {
// Manual mode parameters would go here when implemented
throw new Error('Manual redaction not yet implemented');
throw new Error('Manual redaction is handled directly in the editor');
}
return formData;
@ -30,12 +29,11 @@ export const redactOperationConfig = {
buildFormData: buildRedactFormData,
operationType: 'redact',
endpoint: (parameters: RedactParameters) => {
if (parameters.mode === 'automatic') {
return '/api/v1/security/auto-redact';
} else {
// Manual redaction endpoint would go here when implemented
throw new Error('Manual redaction not yet implemented');
}
if (parameters.mode === 'automatic') {
return '/api/v1/security/auto-redact';
} else {
throw new Error('Manual redaction is handled directly in the editor');
}
},
defaultParameters,
} as const;

View File

@ -34,15 +34,13 @@ export const useRedactParameters = (): RedactParametersHook => {
if (params.mode === 'automatic') {
return '/api/v1/security/auto-redact';
}
// Manual redaction endpoint would go here when implemented
throw new Error('Manual redaction not yet implemented');
return '';
},
validateFn: (params) => {
if (params.mode === 'automatic') {
return params.wordsToRedact.length > 0 && params.wordsToRedact.some(word => word.trim().length > 0);
}
// Manual mode validation would go here when implemented
return false;
return true;
}
});
};

View File

@ -1,6 +1,9 @@
import { useTranslation } from "react-i18next";
import { useState } from "react";
import { useCallback, useEffect, useMemo, useState } from "react";
import { Alert, Button, Stack, Text } from "@mantine/core";
import VisibilityOffRoundedIcon from "@mui/icons-material/VisibilityOffRounded";
import { createToolFlow } from "@app/components/tools/shared/createToolFlow";
import type { MiddleStepConfig } from "@app/components/tools/shared/createToolFlow";
import RedactModeSelector from "@app/components/tools/redact/RedactModeSelector";
import { useRedactParameters } from "@app/hooks/tools/redact/useRedactParameters";
import { useRedactOperation } from "@app/hooks/tools/redact/useRedactOperation";
@ -9,9 +12,28 @@ import { BaseToolProps, ToolComponent } from "@app/types/tool";
import { useRedactModeTips, useRedactWordsTips, useRedactAdvancedTips } from "@app/components/tooltips/useRedactTips";
import RedactAdvancedSettings from "@app/components/tools/redact/RedactAdvancedSettings";
import WordsToRedactInput from "@app/components/tools/redact/WordsToRedactInput";
import { useToolWorkflow } from "@app/contexts/ToolWorkflowContext";
import { useNavigationActions, useNavigationState } from "@app/contexts/NavigationContext";
import ManualRedactionWorkbenchView from "@app/components/tools/redact/ManualRedactionWorkbenchView";
import type { ManualRedactionWorkbenchData } from "@app/types/redact";
import { useFileContext } from "@app/contexts/file/fileHooks";
import type { StirlingFile } from "@app/types/fileContext";
const MANUAL_VIEW_ID = "manualRedactionWorkbench";
const MANUAL_WORKBENCH_ID = "custom:manualRedactionWorkbench" as const;
const Redact = (props: BaseToolProps) => {
const { t } = useTranslation();
const {
registerCustomWorkbenchView,
unregisterCustomWorkbenchView,
setCustomWorkbenchViewData,
clearCustomWorkbenchViewData,
} = useToolWorkflow();
const { actions: navigationActions } = useNavigationActions();
const navigationState = useNavigationState();
const { actions: fileActions } = useFileContext();
const manualWorkbenchIcon = useMemo(() => <VisibilityOffRoundedIcon fontSize="small" />, []);
// State for managing step collapse status
const [methodCollapsed, setMethodCollapsed] = useState(false);
@ -25,14 +47,92 @@ const Redact = (props: BaseToolProps) => {
props
);
const createManualWorkbenchData = useCallback((file: StirlingFile): ManualRedactionWorkbenchData => ({
fileId: file.fileId,
file,
fileName: file.name,
onExport: async (exportedFile: File) => {
await fileActions.addFiles([exportedFile], { selectFiles: true });
},
onExit: () => {
clearCustomWorkbenchViewData(MANUAL_VIEW_ID);
navigationActions.setWorkbench('fileEditor');
},
}), [clearCustomWorkbenchViewData, fileActions, navigationActions]);
const handleOpenManualEditor = useCallback(() => {
if (base.selectedFiles.length !== 1) {
return;
}
const [selected] = base.selectedFiles as [StirlingFile];
const workbenchData = createManualWorkbenchData(selected);
setCustomWorkbenchViewData(MANUAL_VIEW_ID, workbenchData);
navigationActions.setWorkbench(MANUAL_WORKBENCH_ID);
}, [base.selectedFiles, createManualWorkbenchData, navigationActions, setCustomWorkbenchViewData]);
useEffect(() => {
registerCustomWorkbenchView({
id: MANUAL_VIEW_ID,
workbenchId: MANUAL_WORKBENCH_ID,
label: t('redact.manual.workbenchLabel', 'Manual redaction'),
icon: manualWorkbenchIcon,
component: ManualRedactionWorkbenchView,
});
return () => {
clearCustomWorkbenchViewData(MANUAL_VIEW_ID);
unregisterCustomWorkbenchView(MANUAL_VIEW_ID);
};
}, [
clearCustomWorkbenchViewData,
manualWorkbenchIcon,
registerCustomWorkbenchView,
t,
unregisterCustomWorkbenchView,
]);
useEffect(() => {
if (base.params.parameters.mode !== 'manual') {
clearCustomWorkbenchViewData(MANUAL_VIEW_ID);
if (navigationState.workbench === MANUAL_WORKBENCH_ID) {
navigationActions.setWorkbench('fileEditor');
}
}
}, [
base.params.parameters.mode,
clearCustomWorkbenchViewData,
navigationActions,
navigationState.workbench,
]);
useEffect(() => {
if (
navigationState.workbench !== MANUAL_WORKBENCH_ID ||
base.params.parameters.mode !== 'manual' ||
base.selectedFiles.length !== 1
) {
return;
}
const [selected] = base.selectedFiles as [StirlingFile];
setCustomWorkbenchViewData(MANUAL_VIEW_ID, createManualWorkbenchData(selected));
}, [
base.params.parameters.mode,
base.selectedFiles,
createManualWorkbenchData,
navigationState.workbench,
setCustomWorkbenchViewData,
]);
// Tooltips for each step
const modeTips = useRedactModeTips();
const wordsTips = useRedactWordsTips();
const advancedTips = useRedactAdvancedTips();
const isManualMode = base.params.parameters.mode === 'manual';
const isExecuteDisabled = () => {
if (base.params.parameters.mode === 'manual') {
return true; // Manual mode not implemented yet
if (isManualMode) {
return true;
}
return !base.params.validateParameters() || !base.hasFiles || !base.endpointEnabled;
};
@ -44,7 +144,7 @@ const Redact = (props: BaseToolProps) => {
// Build conditional steps based on redaction mode
const buildSteps = () => {
const steps = [
const steps: MiddleStepConfig[] = [
// Method selection step (always present)
{
title: t("redact.modeSelector.title", "Redaction Method"),
@ -62,7 +162,7 @@ const Redact = (props: BaseToolProps) => {
];
// Add mode-specific steps
if (base.params.parameters.mode === 'automatic') {
if (!isManualMode && base.params.parameters.mode === 'automatic') {
steps.push(
{
title: t("redact.auto.settings.title", "Redaction Settings"),
@ -87,8 +187,43 @@ const Redact = (props: BaseToolProps) => {
/>,
},
);
} else if (base.params.parameters.mode === 'manual') {
// Manual mode steps would go here when implemented
} else if (isManualMode) {
const manualHasFile = base.selectedFiles.length > 0;
const manualHasSingleFile = base.selectedFiles.length === 1;
const manualTooManyFiles = base.selectedFiles.length > 1;
const selectedName = manualHasSingleFile ? base.selectedFiles[0].name : null;
steps.push({
title: t("redact.manual.stepTitle", "Manual redaction editor"),
isCollapsed: false,
content: (
<Stack gap="sm">
<Text size="sm">
{manualHasSingleFile
? t("redact.manual.stepDescriptionSelected", "Launch the editor to mark redactions on {{file}}.", { file: selectedName })
: t("redact.manual.stepDescription", "Open the embedded redaction editor to draw boxes or search for sensitive text.")}
</Text>
{manualTooManyFiles && (
<Alert color="red" variant="light">
{t("redact.manual.multipleNotSupported", "Manual redaction works on one PDF at a time. Deselect extra files to continue.")}
</Alert>
)}
{!manualHasFile && (
<Alert color="blue" variant="light">
{t("redact.manual.noFileSelectedInfo", "Select a PDF from the file sidebar to begin manual redaction.")}
</Alert>
)}
<Button
variant="filled"
color="dark"
disabled={!manualHasSingleFile || manualTooManyFiles}
onClick={handleOpenManualEditor}
>
{t("redact.manual.openEditorCta", "Open redaction editor")}
</Button>
</Stack>
),
});
}
return steps;
@ -98,9 +233,10 @@ const Redact = (props: BaseToolProps) => {
files: {
selectedFiles: base.selectedFiles,
isCollapsed: base.hasResults,
minFiles: isManualMode ? 1 : undefined,
},
steps: buildSteps(),
executeButton: {
executeButton: isManualMode ? undefined : {
text: t("redact.submit", "Redact"),
isVisible: !base.hasResults,
loadingText: t("loading"),
@ -108,7 +244,7 @@ const Redact = (props: BaseToolProps) => {
disabled: isExecuteDisabled(),
},
review: {
isVisible: base.hasResults,
isVisible: !isManualMode && base.hasResults,
operation: base.operation,
title: t("redact.title", "Redaction Results"),
onFileClick: base.handleThumbnailClick,

View File

@ -0,0 +1,11 @@
import type { FileId } from '@app/types/file';
import type { StirlingFile } from '@app/types/fileContext';
export interface ManualRedactionWorkbenchData {
fileId: FileId;
file: StirlingFile | null;
fileName: string;
onExport?: (file: File) => Promise<void>;
onExit?: () => void;
contextId?: string;
}

View File

@ -12,4 +12,10 @@ declare module 'assets/material-symbols-icons.json' {
export default value;
}
declare module '@embedpdf/plugin-redaction/react' {
export const RedactionPluginPackage: any;
export const RedactionLayer: any;
export function useRedactionCapability(): { provides?: () => any } | undefined;
}
export {};