diff --git a/package-lock.json b/package-lock.json index cc72c78..8aa8174 100644 --- a/package-lock.json +++ b/package-lock.json @@ -31,6 +31,7 @@ "cron-validator": "^1.3.1", "cronstrue": "^3.0.0", "dayjs": "^1.11.13", + "docx": "^9.5.1", "fast-xml-parser": "^5.2.5", "formik": "^2.4.6", "i18next": "^25.3.2", @@ -47,7 +48,7 @@ "notistack": "^3.0.1", "omggif": "^1.0.10", "pdf-lib": "^1.17.1", - "pdfjs-dist": "^5.2.133", + "pdfjs-dist": "^5.3.93", "playwright": "^1.45.0", "qrcode": "^1.5.4", "rc-slider": "^11.1.8", @@ -2360,35 +2361,36 @@ "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==" }, "node_modules/@napi-rs/canvas": { - "version": "0.1.70", - "resolved": "https://registry.npmjs.org/@napi-rs/canvas/-/canvas-0.1.70.tgz", - "integrity": "sha512-nD6NGa4JbNYSZYsTnLGrqe9Kn/lCkA4ybXt8sx5ojDqZjr2i0TWAHxx/vhgfjX+i3hCdKWufxYwi7CfXqtITSA==", - "license": "MIT", + "version": "0.1.74", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas/-/canvas-0.1.74.tgz", + "integrity": "sha512-pOIyzuS+5Bz1vAhD7tdhaw5/936mMJZUn4aVajojUdjYOGSWmfpDYSgt0nQLZPZVN5GLgWgutqXPOi7Jsm3k+Q==", "optional": true, + "workspaces": [ + "e2e/*" + ], "engines": { "node": ">= 10" }, "optionalDependencies": { - "@napi-rs/canvas-android-arm64": "0.1.70", - "@napi-rs/canvas-darwin-arm64": "0.1.70", - "@napi-rs/canvas-darwin-x64": "0.1.70", - "@napi-rs/canvas-linux-arm-gnueabihf": "0.1.70", - "@napi-rs/canvas-linux-arm64-gnu": "0.1.70", - "@napi-rs/canvas-linux-arm64-musl": "0.1.70", - "@napi-rs/canvas-linux-riscv64-gnu": "0.1.70", - "@napi-rs/canvas-linux-x64-gnu": "0.1.70", - "@napi-rs/canvas-linux-x64-musl": "0.1.70", - "@napi-rs/canvas-win32-x64-msvc": "0.1.70" + "@napi-rs/canvas-android-arm64": "0.1.74", + "@napi-rs/canvas-darwin-arm64": "0.1.74", + "@napi-rs/canvas-darwin-x64": "0.1.74", + "@napi-rs/canvas-linux-arm-gnueabihf": "0.1.74", + "@napi-rs/canvas-linux-arm64-gnu": "0.1.74", + "@napi-rs/canvas-linux-arm64-musl": "0.1.74", + "@napi-rs/canvas-linux-riscv64-gnu": "0.1.74", + "@napi-rs/canvas-linux-x64-gnu": "0.1.74", + "@napi-rs/canvas-linux-x64-musl": "0.1.74", + "@napi-rs/canvas-win32-x64-msvc": "0.1.74" } }, "node_modules/@napi-rs/canvas-android-arm64": { - "version": "0.1.70", - "resolved": "https://registry.npmjs.org/@napi-rs/canvas-android-arm64/-/canvas-android-arm64-0.1.70.tgz", - "integrity": "sha512-I/YOuQ0wbkVYxVaYtCgN42WKTYxNqFA0gTcTrHIGG1jfpDSyZWII/uHcjOo4nzd19io6Y4+/BqP8E5hJgf9OmQ==", + "version": "0.1.74", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-android-arm64/-/canvas-android-arm64-0.1.74.tgz", + "integrity": "sha512-aq5ode+9Z/ZR0H485dI2jdRdttg/hl9Ob+iPCt0nj+QFiirpxDrbUHKeTZWQWEtkWyC7vI5R2dMTbDINBfl9eg==", "cpu": [ "arm64" ], - "license": "MIT", "optional": true, "os": [ "android" @@ -2398,13 +2400,12 @@ } }, "node_modules/@napi-rs/canvas-darwin-arm64": { - "version": "0.1.70", - "resolved": "https://registry.npmjs.org/@napi-rs/canvas-darwin-arm64/-/canvas-darwin-arm64-0.1.70.tgz", - "integrity": "sha512-4pPGyXetHIHkw2TOJHujt3mkCP8LdDu8+CT15ld9Id39c752RcI0amDHSuMLMQfAjvusA9B5kKxazwjMGjEJpQ==", + "version": "0.1.74", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-darwin-arm64/-/canvas-darwin-arm64-0.1.74.tgz", + "integrity": "sha512-eO5Miz+ef1dEQyUMWDdcbAb1Wr7yMyxD9/CL9d4frQxO4pTTaCiMBUWup8XDPLr/g7XkSkGCZLP47xiXiyXSpQ==", "cpu": [ "arm64" ], - "license": "MIT", "optional": true, "os": [ "darwin" @@ -2414,13 +2415,12 @@ } }, "node_modules/@napi-rs/canvas-darwin-x64": { - "version": "0.1.70", - "resolved": "https://registry.npmjs.org/@napi-rs/canvas-darwin-x64/-/canvas-darwin-x64-0.1.70.tgz", - "integrity": "sha512-+2N6Os9LbkmDMHL+raknrUcLQhsXzc5CSXRbXws9C3pv/mjHRVszQ9dhFUUe9FjfPhCJznO6USVdwOtu7pOrzQ==", + "version": "0.1.74", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-darwin-x64/-/canvas-darwin-x64-0.1.74.tgz", + "integrity": "sha512-0EkO0IFkps7C3JpKC7lbM3IL+QDUYeUKagHLDbUry4PeQTghxp6JcgccpmU32ZbpFZgPnm7o0tTJO0J1d8S2rA==", "cpu": [ "x64" ], - "license": "MIT", "optional": true, "os": [ "darwin" @@ -2430,13 +2430,12 @@ } }, "node_modules/@napi-rs/canvas-linux-arm-gnueabihf": { - "version": "0.1.70", - "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm-gnueabihf/-/canvas-linux-arm-gnueabihf-0.1.70.tgz", - "integrity": "sha512-QjscX9OaKq/990sVhSMj581xuqLgiaPVMjjYvWaCmAJRkNQ004QfoSMEm3FoTqM4DRoquP8jvuEXScVJsc1rqQ==", + "version": "0.1.74", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm-gnueabihf/-/canvas-linux-arm-gnueabihf-0.1.74.tgz", + "integrity": "sha512-qAVJEN2JqGayEI1kSpJy1Xr6ZmCFV9QhRyV35yWsS7e9X1jm+T4DAlCxI4PlKIlqVSzYMYhKrxchST20XBSzHg==", "cpu": [ "arm" ], - "license": "MIT", "optional": true, "os": [ "linux" @@ -2446,13 +2445,12 @@ } }, "node_modules/@napi-rs/canvas-linux-arm64-gnu": { - "version": "0.1.70", - "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm64-gnu/-/canvas-linux-arm64-gnu-0.1.70.tgz", - "integrity": "sha512-LNakMOwwqwiHIwMpnMAbFRczQMQ7TkkMyATqFCOtUJNlE6LPP/QiUj/mlFrNbUn/hctqShJ60gWEb52ZTALbVw==", + "version": "0.1.74", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm64-gnu/-/canvas-linux-arm64-gnu-0.1.74.tgz", + "integrity": "sha512-lOnop22qy6MYxI94GGunMMjo6D80I//2W/6pqKUfwXaDQtOfvHsTcVVzDu5cFXUTNrb9ZRfMCeol5YEd+9FJvg==", "cpu": [ "arm64" ], - "license": "MIT", "optional": true, "os": [ "linux" @@ -2462,13 +2460,12 @@ } }, "node_modules/@napi-rs/canvas-linux-arm64-musl": { - "version": "0.1.70", - "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm64-musl/-/canvas-linux-arm64-musl-0.1.70.tgz", - "integrity": "sha512-wBTOllEYNfJCHOdZj9v8gLzZ4oY3oyPX8MSRvaxPm/s7RfEXxCyZ8OhJ5xAyicsDdbE5YBZqdmaaeP5+xKxvtg==", + "version": "0.1.74", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm64-musl/-/canvas-linux-arm64-musl-0.1.74.tgz", + "integrity": "sha512-tfFqLHGtSEabBigOnPUfZviSTGmW2xHv5tYZYPBWmgGiTkoNJ7lEWFUxHjwvV5HXGqLs8ok/O7g1enSpxO6lmQ==", "cpu": [ "arm64" ], - "license": "MIT", "optional": true, "os": [ "linux" @@ -2478,13 +2475,12 @@ } }, "node_modules/@napi-rs/canvas-linux-riscv64-gnu": { - "version": "0.1.70", - "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-riscv64-gnu/-/canvas-linux-riscv64-gnu-0.1.70.tgz", - "integrity": "sha512-GVUUPC8TuuFqHip0rxHkUqArQnlzmlXmTEBuXAWdgCv85zTCFH8nOHk/YCF5yo0Z2eOm8nOi90aWs0leJ4OE5Q==", + "version": "0.1.74", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-riscv64-gnu/-/canvas-linux-riscv64-gnu-0.1.74.tgz", + "integrity": "sha512-j6H9dHTMtr1y3tu/zGm1ythYIL9vTl4EEv9f6CMx0n3Zn2M+OruUUwh9ylCj4afzSNEK9T8cr6zMnmTPzkpBvQ==", "cpu": [ "riscv64" ], - "license": "MIT", "optional": true, "os": [ "linux" @@ -2494,13 +2490,12 @@ } }, "node_modules/@napi-rs/canvas-linux-x64-gnu": { - "version": "0.1.70", - "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-x64-gnu/-/canvas-linux-x64-gnu-0.1.70.tgz", - "integrity": "sha512-/kvUa2lZRwGNyfznSn5t1ShWJnr/m5acSlhTV3eXECafObjl0VBuA1HJw0QrilLpb4Fe0VLywkpD1NsMoVDROQ==", + "version": "0.1.74", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-x64-gnu/-/canvas-linux-x64-gnu-0.1.74.tgz", + "integrity": "sha512-73DIV4E7Y9CpIJuUXVl9H6+MEQXyRy4VJQoUGA1tOlcKQiStxqhq6UErL4decI28NxjyQXBhtYZKj5q8AJEuOg==", "cpu": [ "x64" ], - "license": "MIT", "optional": true, "os": [ "linux" @@ -2510,13 +2505,12 @@ } }, "node_modules/@napi-rs/canvas-linux-x64-musl": { - "version": "0.1.70", - "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-x64-musl/-/canvas-linux-x64-musl-0.1.70.tgz", - "integrity": "sha512-aqlv8MLpycoMKRmds7JWCfVwNf1fiZxaU7JwJs9/ExjTD8lX2KjsO7CTeAj5Cl4aEuzxUWbJPUUE2Qu9cZ1vfg==", + "version": "0.1.74", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-x64-musl/-/canvas-linux-x64-musl-0.1.74.tgz", + "integrity": "sha512-FgDMEFdGIJT3I2xejflRJ82/ZgDphyirS43RgtoLaIXI6zihLiZcQ7rczpqeWgAwlJNjR0He2EustsKe1SkUOg==", "cpu": [ "x64" ], - "license": "MIT", "optional": true, "os": [ "linux" @@ -2526,13 +2520,12 @@ } }, "node_modules/@napi-rs/canvas-win32-x64-msvc": { - "version": "0.1.70", - "resolved": "https://registry.npmjs.org/@napi-rs/canvas-win32-x64-msvc/-/canvas-win32-x64-msvc-0.1.70.tgz", - "integrity": "sha512-Q9QU3WIpwBTVHk4cPfBjGHGU4U0llQYRXgJtFtYqqGNEOKVN4OT6PQ+ve63xwIPODMpZ0HHyj/KLGc9CWc3EtQ==", + "version": "0.1.74", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-win32-x64-msvc/-/canvas-win32-x64-msvc-0.1.74.tgz", + "integrity": "sha512-x6bhwlhn0wU7dfiP46mt5Bi6PowSUH4CJ4PTzGj58LRQ1HVasEIJgoMx7MLC48F738eJpzbfg3WR/D8+e9CeTA==", "cpu": [ "x64" ], - "license": "MIT", "optional": true, "os": [ "win32" @@ -5463,6 +5456,52 @@ "node": ">=6.0.0" } }, + "node_modules/docx": { + "version": "9.5.1", + "resolved": "https://registry.npmjs.org/docx/-/docx-9.5.1.tgz", + "integrity": "sha512-ABDI7JEirFD2+bHhOBlsGZxaG1UgZb2M/QMKhLSDGgVNhxDesTCDcP+qoDnDGjZ4EOXTRfUjUgwHVuZ6VSTfWQ==", + "dependencies": { + "@types/node": "^24.0.1", + "hash.js": "^1.1.7", + "jszip": "^3.10.1", + "nanoid": "^5.1.3", + "xml": "^1.0.1", + "xml-js": "^1.6.8" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/docx/node_modules/@types/node": { + "version": "24.0.14", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.0.14.tgz", + "integrity": "sha512-4zXMWD91vBLGRtHK3YbIoFMia+1nqEz72coM42C5ETjnNCa/heoj7NT1G67iAfOqMmcfhuCZ4uNpyz8EjlAejw==", + "dependencies": { + "undici-types": "~7.8.0" + } + }, + "node_modules/docx/node_modules/nanoid": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.1.5.tgz", + "integrity": "sha512-Ir/+ZpE9fDsNH0hQ3C68uyThDXzYcim2EqcZ8zn8Chtt1iylPT9xXJB0kPCnqzgcEGikO9RxSrh63MsmVCU7Fw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.js" + }, + "engines": { + "node": "^18 || >=20" + } + }, + "node_modules/docx/node_modules/undici-types": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.8.0.tgz", + "integrity": "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==" + }, "node_modules/dom-accessibility-api": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz", @@ -6989,6 +7028,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/hash.js": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", + "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", + "dependencies": { + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.1" + } + }, "node_modules/hasown": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", @@ -8841,6 +8889,11 @@ "node": ">=4" } }, + "node_modules/minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==" + }, "node_modules/minimatch": { "version": "9.0.3", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", @@ -9541,15 +9594,14 @@ "license": "0BSD" }, "node_modules/pdfjs-dist": { - "version": "5.3.31", - "resolved": "https://registry.npmjs.org/pdfjs-dist/-/pdfjs-dist-5.3.31.tgz", - "integrity": "sha512-EhPdIjNX0fcdwYQO+e3BAAJPXt+XI29TZWC7COhIXs/K0JHcUt1Gdz1ITpebTwVMFiLsukdUZ3u0oTO7jij+VA==", - "license": "Apache-2.0", + "version": "5.3.93", + "resolved": "https://registry.npmjs.org/pdfjs-dist/-/pdfjs-dist-5.3.93.tgz", + "integrity": "sha512-w3fQKVL1oGn8FRyx5JUG5tnbblggDqyx2XzA5brsJ5hSuS+I0NdnJANhmeWKLjotdbPQucLBug5t0MeWr0AAdg==", "engines": { "node": ">=20.16.0 || >=22.3.0" }, "optionalDependencies": { - "@napi-rs/canvas": "^0.1.67" + "@napi-rs/canvas": "^0.1.71" } }, "node_modules/peek-readable": { @@ -12754,11 +12806,15 @@ "node": ">=0.8" } }, + "node_modules/xml": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/xml/-/xml-1.0.1.tgz", + "integrity": "sha512-huCv9IH9Tcf95zuYCsQraZtWnJvBtLVE0QHMOs8bWyZAFZNDcYjsPq1nEx8jKA9y+Beo9v+7OBPRisQTjinQMw==" + }, "node_modules/xml-js": { "version": "1.6.11", "resolved": "https://registry.npmjs.org/xml-js/-/xml-js-1.6.11.tgz", "integrity": "sha512-7rVi2KMfwfWFl+GpPg6m80IVMWXLRjO+PxTq7V2CDhoGak0wzYzFgUY2m4XJ47OGdXd8eLE8EmwfAmdjw7lC1g==", - "dev": true, "license": "MIT", "dependencies": { "sax": "^1.2.4" diff --git a/package.json b/package.json index 392bb6e..b570fa2 100644 --- a/package.json +++ b/package.json @@ -50,6 +50,7 @@ "cron-validator": "^1.3.1", "cronstrue": "^3.0.0", "dayjs": "^1.11.13", + "docx": "^9.5.1", "fast-xml-parser": "^5.2.5", "formik": "^2.4.6", "i18next": "^25.3.2", @@ -66,7 +67,7 @@ "notistack": "^3.0.1", "omggif": "^1.0.10", "pdf-lib": "^1.17.1", - "pdfjs-dist": "^5.2.133", + "pdfjs-dist": "^5.3.93", "playwright": "^1.45.0", "qrcode": "^1.5.4", "rc-slider": "^11.1.8", diff --git a/public/locales/en/pdf.json b/public/locales/en/pdf.json index eb4b98d..740791c 100644 --- a/public/locales/en/pdf.json +++ b/public/locales/en/pdf.json @@ -52,7 +52,7 @@ } }, "pdfToEpub": { - "description": "Transform PDF documents into EPUB files for better e-reader compatibility.', icon: 'material-symbols:import-contacts', component: lazy(() => import('./index')), keywords: ['pdf', 'epub', 'convert', 'ebook'], path: 'pdf-to-epub', i18n: { name: 'pdf:pdfToEpub.title', description: 'pdf:pdfToEpub.description", + "description": "Transform PDF documents into EPUB files for better e-reader compatibility.", "shortDescription": "Convert PDF files to EPUB format", "title": "PDF to EPUB" }, @@ -109,5 +109,10 @@ "description": "This tool allows you to extract specific pages from a PDF document. You can specify individual pages or ranges of pages to extract.", "title": "Split PDF" } + }, + "pdfToWord": { + "name": "PDF to Word", + "description": "Convert PDF documents to editable Word files (.docx)", + "shortDescription": "Convert PDF to Word" } } diff --git a/src/components/Hero.tsx b/src/components/Hero.tsx index 91143d6..7f2a0b7 100644 --- a/src/components/Hero.tsx +++ b/src/components/Hero.tsx @@ -127,6 +127,7 @@ export default function Hero() { {t('translation:hero.title')}{' '} = ({ ))} - {buttons.map((button) => ( - {button} + {buttons.map((button, index) => ( + {button} ))} ); diff --git a/src/pages/index.tsx b/src/pages/index.tsx new file mode 100644 index 0000000..d878c15 --- /dev/null +++ b/src/pages/index.tsx @@ -0,0 +1,60 @@ +import { useState } from 'react'; +import ToolContent from '@components/ToolContent'; +import ToolPdfInput from '@components/input/ToolPdfInput'; +import ToolFileResult from '@components/result/ToolFileResult'; +import { ToolComponentProps } from '@tools/defineTool'; +import { convertPdfToDocx } from './service'; + +export default function PdfToWord({ title }: ToolComponentProps) { + const [input, setInput] = useState(null); + const [outputFile, setOutputFile] = useState(null); + const [loading, setLoading] = useState(false); + + const compute = async (_: {}, file: File | null) => { + if (!file) return; + setLoading(true); + setOutputFile(null); + try { + const blob = await convertPdfToDocx(file); + const docxFile = new File([blob], file.name.replace(/\.pdf$/, '.docx'), { + type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' + }); + setOutputFile(docxFile); + } catch (err) { + console.error('PDF to Word conversion failed:', err); + } finally { + setLoading(false); + } + }; + + return ( + + } + resultComponent={ + + } + getGroups={null} + toolInfo={{ + title: 'Convert PDF to Word', + description: 'Upload your PDF and get a .docx file with extracted text.' + }} + /> + ); +} diff --git a/src/pages/tools/pdf/index.ts b/src/pages/tools/pdf/index.ts index 6c3e03f..426cb6a 100644 --- a/src/pages/tools/pdf/index.ts +++ b/src/pages/tools/pdf/index.ts @@ -7,6 +7,7 @@ import { tool as compressPdfTool } from './compress-pdf/meta'; import { tool as protectPdfTool } from './protect-pdf/meta'; import { meta as pdfToEpub } from './pdf-to-epub/meta'; import { tool as pdfEditor } from './editor/meta'; +import { tool as pdfToWord } from './pdf-to-word/meta'; export const pdfTools: DefinedTool[] = [ pdfEditor, @@ -16,5 +17,6 @@ export const pdfTools: DefinedTool[] = [ protectPdfTool, mergePdf, pdfToEpub, - pdfPdfToPng + pdfPdfToPng, + pdfToWord ]; diff --git a/src/pages/tools/pdf/pdf-to-word/1 b/src/pages/tools/pdf/pdf-to-word/1 new file mode 100644 index 0000000..9a5f3cc --- /dev/null +++ b/src/pages/tools/pdf/pdf-to-word/1 @@ -0,0 +1,63 @@ +import { useState } from 'react'; +import ToolContent from '@components/ToolContent'; +import ToolPdfInput from '@components/input/ToolPdfInput'; +import ToolFileResult from '@components/result/ToolFileResult'; +import { ToolComponentProps } from '@tools/defineTool'; +import { convertPdfToDocx } from './service'; + +export default function PdfToWord({ title }: ToolComponentProps) { + const [input, setInput] = useState(null); + const [outputFile, setOutputFile] = useState(null); + const [loading, setLoading] = useState(false); + + const compute = async (_: {}, file: File | null) => { + if (!file) return; + setLoading(true); + setOutputFile(null); + try { + const blob = await convertPdfToDocx(file); + const docxFile = new File([blob], file.name.replace(/\.pdf$/, '.docx'), { + type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + }); + setOutputFile(docxFile); + } catch (err) { + console.error('PDF to Word conversion failed:', err); + } finally { + setLoading(false); + } + }; +console.log('outputFile:', outputFile); + + + return ( + + } + resultComponent={ + + } + getGroups={null} + toolInfo={{ + title: 'Convert PDF to Word', + description: 'Upload your PDF and get a .docx file with extracted text.', + }} + /> + ); +} + diff --git a/src/pages/tools/pdf/pdf-to-word/index.tsx b/src/pages/tools/pdf/pdf-to-word/index.tsx new file mode 100644 index 0000000..0db4fe4 --- /dev/null +++ b/src/pages/tools/pdf/pdf-to-word/index.tsx @@ -0,0 +1,69 @@ +import { useState } from 'react'; +import ToolContent from '@components/ToolContent'; +import ToolPdfInput from '@components/input/ToolPdfInput'; +import { ToolComponentProps } from '@tools/defineTool'; +import { convertPdfToDocx } from './service'; + +export default function PdfToWord({ title }: ToolComponentProps) { + const [input, setInput] = useState(null); + const [outputFile, setOutputFile] = useState(null); + const [loading, setLoading] = useState(false); + + const compute = async (_: {}, file: File | null) => { + if (!file) return; + setLoading(true); + setOutputFile(null); + try { + const result = await convertPdfToDocx(file); + console.log('outputFile:', result); + setOutputFile(result); + } catch (err) { + console.error('PDF to Word conversion failed:', err); + } finally { + setLoading(false); + } + }; + + return ( + + } + resultComponent={ + outputFile ? ( + + Download {outputFile.name} + + ) : null + } + getGroups={null} + toolInfo={{ + title: 'Convert PDF to Word', + description: + 'Upload your PDF and get a real .docx Word document with extracted text.' + }} + /> + ); +} diff --git a/src/pages/tools/pdf/pdf-to-word/meta.ts b/src/pages/tools/pdf/pdf-to-word/meta.ts new file mode 100644 index 0000000..4091e6c --- /dev/null +++ b/src/pages/tools/pdf/pdf-to-word/meta.ts @@ -0,0 +1,15 @@ +import { defineTool } from '@tools/defineTool'; +import { lazy } from 'react'; + +export const tool = defineTool('pdf', { + i18n: { + name: 'pdf:pdfToWord.name', + description: 'pdf:pdfToWord.description', + shortDescription: 'pdf:pdfToWord.shortDescription', + longDescription: 'pdf:pdfToWord.description' + }, + path: 'pdf-to-word', + icon: 'mdi:file-word', // You can change this icon ID if needed + keywords: ['pdf', 'word', 'convert', 'docx'], + component: lazy(() => import('./index')) +}); diff --git a/src/pages/tools/pdf/pdf-to-word/service.ts b/src/pages/tools/pdf/pdf-to-word/service.ts new file mode 100644 index 0000000..4e369eb --- /dev/null +++ b/src/pages/tools/pdf/pdf-to-word/service.ts @@ -0,0 +1,34 @@ +import * as pdfjsLib from 'pdfjs-dist'; +import pdfjsWorker from 'pdfjs-dist/build/pdf.worker.min?url'; +import { Document, Packer, Paragraph } from 'docx'; + +pdfjsLib.GlobalWorkerOptions.workerSrc = pdfjsWorker; + +export async function convertPdfToDocx(pdfFile: File): Promise { + const arrayBuffer = await pdfFile.arrayBuffer(); + const pdf = await pdfjsLib.getDocument({ data: arrayBuffer }).promise; + + const textChunks: string[] = []; + + for (let i = 1; i <= pdf.numPages; i++) { + const page = await pdf.getPage(i); + const textContent = await page.getTextContent(); + const strings = textContent.items.map((item: any) => item.str); + textChunks.push(strings.join(' ')); + } + + const paragraphs = textChunks.map((text) => new Paragraph(text)); + const doc = new Document({ + sections: [ + { + properties: {}, + children: paragraphs + } + ] + }); + + const blob = await Packer.toBlob(doc); + return new File([blob], pdfFile.name.replace(/\.pdf$/i, '.docx'), { + type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' + }); +}