From bc181c8c963890748dc9fdccee2f5fae02ffb952 Mon Sep 17 00:00:00 2001 From: abdullahalam123 Date: Sat, 8 Nov 2025 13:17:29 +0530 Subject: [PATCH] chore: update image sources and version bump to 1.2.0 - Changed image sources for GDPR, CCPA, and HIPAA compliance logos to local paths. - Updated package version to 1.2.0 in package-lock.json. - Enhanced README and SIMPLE_MODE documentation with Docker usage instructions. - Improved table-of-contents worker and related TypeScript definitions for better clarity and functionality. - Refactored CSS styles for consistency and improved UI elements. --- README.md | 4 + SIMPLE_MODE.md | 7 + index.html | 6 +- package-lock.json | 4 +- public/images/ccpa.svg | 6 + public/images/gdpr.svg | 5 + public/images/hipaa.svg | 9 + public/workers/table-of-contents.worker.d.ts | 4 +- public/workers/table-of-contents.worker.js | 10 +- src/css/bookmark.css | 1 - src/js/logic/bookmark-pdf.ts | 163 ++++-- src/js/logic/table-of-contents.ts | 95 +++- src/pages/bookmark.html | 23 +- src/pages/table-of-contents.html | 556 ++++++++++++------- src/types/coherentpdf.global.d.ts | 72 ++- vite.config.ts | 7 +- 16 files changed, 652 insertions(+), 320 deletions(-) create mode 100644 public/images/ccpa.svg create mode 100644 public/images/gdpr.svg create mode 100644 public/images/hipaa.svg diff --git a/README.md b/README.md index de6d74a..29f7534 100644 --- a/README.md +++ b/README.md @@ -110,11 +110,13 @@ You can also watch the video on how to set it up 👉 [BentoPDF Docker Setup](https://drive.google.com/file/d/1C4eJ2nqeaH__1Tlad-xuBHaF2Ha4fSBf/view?usp=drive_link) **Using Docker Hub:** + ```bash docker run -p 3000:8080 bentopdf/bentopdf:latest ``` **Using GitHub Container Registry:** + ```bash docker run -p 3000:8080 ghcr.io/alam00000/bentopdf:latest ``` @@ -182,11 +184,13 @@ For detailed security configuration, see [SECURITY.md](SECURITY.md). BentoPDF supports semantic versioning with multiple Docker tags available on both Docker Hub and GitHub Container Registry: **Docker Hub:** + - **Latest**: `bentopdf/bentopdf:latest` - **Specific Version**: `bentopdf/bentopdf:1.0.0` - **Version with Prefix**: `bentopdf/bentopdf:v1.0.0` **GitHub Container Registry:** + - **Latest**: `ghcr.io/alam00000/bentopdf:latest` - **Specific Version**: `ghcr.io/alam00000/bentopdf:1.0.0` - **Version with Prefix**: `ghcr.io/alam00000/bentopdf:v1.0.0` diff --git a/SIMPLE_MODE.md b/SIMPLE_MODE.md index a71439b..996caef 100644 --- a/SIMPLE_MODE.md +++ b/SIMPLE_MODE.md @@ -24,14 +24,17 @@ When enabled, Simple Mode will: Use the pre-built Simple Mode image directly: **Using Docker Hub:** + ```bash docker run -p 3000:8080 bentopdf/bentopdf-simple:latest ``` **Using GitHub Container Registry:** + ```bash docker run -p 3000:8080 ghcr.io/alam00000/bentopdf-simple:latest ``` + Or with Docker Compose: ```yaml @@ -151,20 +154,24 @@ When Simple Mode is working correctly, you should see: ### Normal Mode (Full Branding) **Docker Hub:** + - `bentopdf/bentopdf:latest` - `bentopdf/bentopdf:v1.0.0` (versioned) **GitHub Container Registry:** + - `ghcr.io/alam00000/bentopdf:latest` - `ghcr.io/alam00000/bentopdf:v1.0.0` (versioned) ### Simple Mode (Clean Interface) **Docker Hub:** + - `bentopdf/bentopdf-simple:latest` - `bentopdf/bentopdf-simple:v1.0.0` (versioned) **GitHub Container Registry:** + - `ghcr.io/alam00000/bentopdf-simple:latest` - `ghcr.io/alam00000/bentopdf-simple:v1.0.0` (versioned) diff --git a/index.html b/index.html index 8ed0554..c5e43ec 100644 --- a/index.html +++ b/index.html @@ -375,7 +375,7 @@ class="w-20 h-20 md:w-24 md:h-24 rounded-full bg-blue-600 flex items-center justify-center mb-4" > GDPR compliance @@ -396,7 +396,7 @@ class="w-20 h-20 md:w-24 md:h-24 rounded-full bg-blue-600 flex items-center justify-center mb-4" > CCPA compliance @@ -417,7 +417,7 @@ class="w-20 h-20 md:w-24 md:h-24 rounded-full bg-blue-600 flex items-center justify-center mb-4" > HIPAA compliance diff --git a/package-lock.json b/package-lock.json index 516ca30..8bb07b3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "bento-pdf", - "version": "1.1.1", + "version": "1.2.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "bento-pdf", - "version": "1.1.1", + "version": "1.2.0", "license": "Apache-2.0", "dependencies": { "@fontsource/cedarville-cursive": "^5.2.7", diff --git a/public/images/ccpa.svg b/public/images/ccpa.svg new file mode 100644 index 0000000..a08c2aa --- /dev/null +++ b/public/images/ccpa.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/public/images/gdpr.svg b/public/images/gdpr.svg new file mode 100644 index 0000000..9d7de6f --- /dev/null +++ b/public/images/gdpr.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/public/images/hipaa.svg b/public/images/hipaa.svg new file mode 100644 index 0000000..6d1084f --- /dev/null +++ b/public/images/hipaa.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/public/workers/table-of-contents.worker.d.ts b/public/workers/table-of-contents.worker.d.ts index 147d8a1..7f53354 100644 --- a/public/workers/table-of-contents.worker.d.ts +++ b/public/workers/table-of-contents.worker.d.ts @@ -1,4 +1,4 @@ -declare const coherentpdf: typeof import("../../src/types/coherentpdf.global").coherentpdf; +declare const coherentpdf: typeof import('../../src/types/coherentpdf.global').coherentpdf; interface GenerateTOCMessage { command: 'generate-toc'; @@ -19,4 +19,4 @@ interface TOCErrorResponse { message: string; } -type TOCResponse = TOCSuccessResponse | TOCErrorResponse; \ No newline at end of file +type TOCResponse = TOCSuccessResponse | TOCErrorResponse; diff --git a/public/workers/table-of-contents.worker.js b/public/workers/table-of-contents.worker.js index f251a83..d10883c 100644 --- a/public/workers/table-of-contents.worker.js +++ b/public/workers/table-of-contents.worker.js @@ -19,7 +19,8 @@ function generateTableOfContentsInWorker( coherentpdf.deletePdf(pdf); self.postMessage({ status: 'error', - message: 'This PDF does not have any bookmarks. Please add bookmarks first using the Bookmark tool.', + message: + 'This PDF does not have any bookmarks. Please add bookmarks first using the Bookmark tool.', }); return; } @@ -39,7 +40,10 @@ function generateTableOfContentsInWorker( } catch (error) { self.postMessage({ status: 'error', - message: error instanceof Error ? error.message : 'Unknown error occurred during table of contents generation.', + message: + error instanceof Error + ? error.message + : 'Unknown error occurred during table of contents generation.', }); } } @@ -54,4 +58,4 @@ self.onmessage = (e) => { e.data.addBookmark ); } -}; \ No newline at end of file +}; diff --git a/src/css/bookmark.css b/src/css/bookmark.css index efd2816..ff8bd61 100644 --- a/src/css/bookmark.css +++ b/src/css/bookmark.css @@ -129,4 +129,3 @@ body { pointer-events: none; z-index: 5; } - diff --git a/src/js/logic/bookmark-pdf.ts b/src/js/logic/bookmark-pdf.ts index 3b8c558..53486ee 100644 --- a/src/js/logic/bookmark-pdf.ts +++ b/src/js/logic/bookmark-pdf.ts @@ -246,10 +246,10 @@ function showInputModal(title, fields = [], defaultValues = {}) { // Store modal references savedModalOverlay = overlay; savedModal = modal; - + // Hide modal completely overlay.style.display = 'none'; - + startDestinationPicking((page, pdfX, pdfY) => { const destPageInput = modal.querySelector('#modal-dest-page'); const destXInput = modal.querySelector('#modal-dest-x'); @@ -258,10 +258,10 @@ function showInputModal(title, fields = [], defaultValues = {}) { if (destPageInput) destPageInput.value = page; if (destXInput) destXInput.value = Math.round(pdfX); if (destYInput) destYInput.value = Math.round(pdfY); - + // Restore modal overlay.style.display = ''; - + // Update preview to show the destination after a short delay to ensure modal is visible setTimeout(() => { updateDestinationPreview(); @@ -269,7 +269,7 @@ function showInputModal(title, fields = [], defaultValues = {}) { }); }); } - + // Add validation for page input const destPageInput = modal.querySelector('#modal-dest-page'); if (destPageInput) { @@ -285,7 +285,7 @@ function showInputModal(title, fields = [], defaultValues = {}) { } updateDestinationPreview(); }); - + destPageInput.addEventListener('blur', (e) => { const value = parseInt(e.target.value); const maxPages = parseInt(e.target.max) || 1; @@ -299,32 +299,34 @@ function showInputModal(title, fields = [], defaultValues = {}) { updateDestinationPreview(); }); } - + // Function to update destination preview function updateDestinationPreview() { if (!pdfJsDoc) return; - + const destPageInput = modal.querySelector('#modal-dest-page'); const destXInput = modal.querySelector('#modal-dest-x'); const destYInput = modal.querySelector('#modal-dest-y'); const destZoomSelect = modal.querySelector('#modal-dest-zoom'); - - const pageNum = destPageInput ? parseInt(destPageInput.value) : currentPage; + + const pageNum = destPageInput + ? parseInt(destPageInput.value) + : currentPage; const x = destXInput ? parseFloat(destXInput.value) : null; const y = destYInput ? parseFloat(destYInput.value) : null; const zoom = destZoomSelect ? destZoomSelect.value : null; - + if (pageNum >= 1 && pageNum <= pdfJsDoc.numPages) { // Render the page with zoom if specified renderPageWithDestination(pageNum, x, y, zoom); } } - + // Add listeners for X, Y, and zoom changes const destXInput = modal.querySelector('#modal-dest-x'); const destYInput = modal.querySelector('#modal-dest-y'); const destZoomSelect = modal.querySelector('#modal-dest-zoom'); - + if (destXInput) { destXInput.addEventListener('input', updateDestinationPreview); } @@ -434,7 +436,7 @@ function cancelDestinationPicking() { destinationMarker.remove(); destinationMarker = null; } - + // Remove coordinate display const coordDisplay = document.getElementById('destination-coord-display'); if (coordDisplay) { @@ -497,20 +499,22 @@ document.addEventListener('DOMContentLoaded', () => { const page = await pdfJsDoc.getPage(currentPage); viewport = page.getViewport({ scale: currentZoom }); } - + // Convert canvas pixel coordinates to PDF coordinates // The canvas CSS size matches viewport dimensions, so coordinates map directly // PDF uses bottom-left origin, canvas uses top-left const scaleX = viewport.width / rect.width; const scaleY = viewport.height / rect.height; const pdfX = canvasX * scaleX; - const pdfY = viewport.height - (canvasY * scaleY); + const pdfY = viewport.height - canvasY * scaleY; // Remove old marker and coordinate display if (destinationMarker) { destinationMarker.remove(); } - const oldCoordDisplay = document.getElementById('destination-coord-display'); + const oldCoordDisplay = document.getElementById( + 'destination-coord-display' + ); if (oldCoordDisplay) { oldCoordDisplay.remove(); } @@ -528,16 +532,21 @@ document.addEventListener('DOMContentLoaded', () => { const canvasRect = canvas.getBoundingClientRect(); const wrapperRect = canvasWrapper.getBoundingClientRect(); destinationMarker.style.position = 'absolute'; - destinationMarker.style.left = (canvasX + canvasRect.left - wrapperRect.left) + 'px'; - destinationMarker.style.top = (canvasY + canvasRect.top - wrapperRect.top) + 'px'; + destinationMarker.style.left = + canvasX + canvasRect.left - wrapperRect.left + 'px'; + destinationMarker.style.top = + canvasY + canvasRect.top - wrapperRect.top + 'px'; canvasWrapper.appendChild(destinationMarker); - + // Create persistent coordinate display const coordDisplay = document.createElement('div'); coordDisplay.id = 'destination-coord-display'; - coordDisplay.className = 'absolute bg-blue-500 text-white px-2 py-1 rounded text-xs font-mono z-50 pointer-events-none'; - coordDisplay.style.left = (canvasX + canvasRect.left - wrapperRect.left + 20) + 'px'; - coordDisplay.style.top = (canvasY + canvasRect.top - wrapperRect.top - 30) + 'px'; + coordDisplay.className = + 'absolute bg-blue-500 text-white px-2 py-1 rounded text-xs font-mono z-50 pointer-events-none'; + coordDisplay.style.left = + canvasX + canvasRect.left - wrapperRect.left + 20 + 'px'; + coordDisplay.style.top = + canvasY + canvasRect.top - wrapperRect.top - 30 + 'px'; coordDisplay.textContent = `X: ${Math.round(pdfX)}, Y: ${Math.round(pdfY)}`; canvasWrapper.appendChild(coordDisplay); @@ -639,6 +648,12 @@ const jsonInput = document.getElementById('json-input'); const autoExtractCheckbox = document.getElementById('auto-extract-checkbox'); const appEl = document.getElementById('app'); const uploaderEl = document.getElementById('uploader'); +const fileDisplayArea = document.getElementById( + 'file-display-area' +) as HTMLElement; +const backToToolsBtn = document.getElementById( + 'back-to-tools' +) as HTMLButtonElement; const canvas = document.getElementById('pdf-canvas'); const ctx = canvas.getContext('2d'); const pageIndicator = document.getElementById('page-indicator'); @@ -974,6 +989,37 @@ collapseAllBtn.addEventListener('click', () => { renderBookmarkTree(); }); +// Format bytes helper +function formatBytes(bytes: number): string { + if (bytes === 0) return '0 Bytes'; + const k = 1024; + const sizes = ['Bytes', 'KB', 'MB', 'GB']; + const i = Math.floor(Math.log(bytes) / Math.log(k)); + return Math.round((bytes / Math.pow(k, i)) * 100) / 100 + ' ' + sizes[i]; +} + +// Render file display +function renderFileDisplay(file: File) { + if (!fileDisplayArea) return; + fileDisplayArea.innerHTML = ''; + fileDisplayArea.classList.remove('hidden'); + + const fileDiv = document.createElement('div'); + fileDiv.className = + 'flex items-center justify-between bg-gray-700 p-3 rounded-lg text-sm'; + + const nameSpan = document.createElement('span'); + nameSpan.className = 'truncate font-medium text-gray-200'; + nameSpan.textContent = file.name; + + const sizeSpan = document.createElement('span'); + sizeSpan.className = 'flex-shrink-0 ml-4 text-gray-400'; + sizeSpan.textContent = formatBytes(file.size); + + fileDiv.append(nameSpan, sizeSpan); + fileDisplayArea.appendChild(fileDiv); +} + fileInput.addEventListener('change', loadPDF); async function loadPDF(e) { @@ -982,6 +1028,7 @@ async function loadPDF(e) { originalFileName = file.name.replace('.pdf', ''); filenameDisplay.textContent = originalFileName; + renderFileDisplay(file); const arrayBuffer = await file.arrayBuffer(); currentPage = 1; @@ -1057,47 +1104,47 @@ async function renderPage(num, zoom = null, destX = null, destY = null) { if (!pdfJsDoc) return; const page = await pdfJsDoc.getPage(num); - + let zoomScale = currentZoom; if (zoom !== null && zoom !== '' && zoom !== '0') { zoomScale = parseFloat(zoom) / 100; } - + const dpr = window.devicePixelRatio || 1; - + let viewport = page.getViewport({ scale: zoomScale }); currentViewport = viewport; canvas.height = viewport.height * dpr; canvas.width = viewport.width * dpr; - + // Set CSS size to maintain aspect ratio (this is what the browser displays) canvas.style.width = viewport.width + 'px'; canvas.style.height = viewport.height + 'px'; - + // Scale the canvas context to match device pixel ratio ctx.scale(dpr, dpr); await page.render({ canvasContext: ctx, viewport: viewport }).promise; - + // Draw destination marker if coordinates are provided if (destX !== null && destY !== null) { const canvasX = destX; const canvasY = viewport.height - destY; // Flip Y axis (PDF bottom-left, canvas top-left) - + // Draw marker on canvas with animation effect ctx.save(); ctx.strokeStyle = '#3b82f6'; ctx.fillStyle = '#3b82f6'; ctx.lineWidth = 3; - + ctx.shadowBlur = 10; ctx.shadowColor = 'rgba(59, 130, 246, 0.5)'; ctx.beginPath(); ctx.arc(canvasX, canvasY, 12, 0, 2 * Math.PI); ctx.fill(); ctx.shadowBlur = 0; - + // Draw crosshair ctx.beginPath(); ctx.moveTo(canvasX - 15, canvasY); @@ -1105,26 +1152,26 @@ async function renderPage(num, zoom = null, destX = null, destY = null) { ctx.moveTo(canvasX, canvasY - 15); ctx.lineTo(canvasX, canvasY + 15); ctx.stroke(); - + // Draw inner circle ctx.beginPath(); ctx.arc(canvasX, canvasY, 6, 0, 2 * Math.PI); ctx.fill(); ctx.stroke(); - + // Draw coordinate text background const text = `X: ${Math.round(destX)}, Y: ${Math.round(destY)}`; ctx.font = 'bold 12px monospace'; const textMetrics = ctx.measureText(text); const textWidth = textMetrics.width; const textHeight = 18; - + ctx.fillStyle = 'rgba(59, 130, 246, 0.95)'; ctx.fillRect(canvasX + 18, canvasY - 25, textWidth + 10, textHeight); - + ctx.fillStyle = 'white'; ctx.fillText(text, canvasX + 23, canvasY - 10); - + ctx.restore(); } @@ -1424,8 +1471,13 @@ function createNodeElement(node, level = 0) { // Check if bookmark has a custom destination if (node.destX !== null || node.destY !== null || node.zoom !== null) { // Render page with destination highlighted and zoom applied - await renderPageWithDestination(node.page, node.destX, node.destY, node.zoom); - + await renderPageWithDestination( + node.page, + node.destX, + node.destY, + node.zoom + ); + // Highlight the destination briefly (2 seconds) setTimeout(() => { // Re-render without highlight but keep the zoom if it was set @@ -1769,7 +1821,6 @@ extractExistingBtn.addEventListener('click', async () => { } }); - async function extractExistingBookmarks(doc) { try { const outlines = doc.catalog.lookup(PDFName.of('Outlines')); @@ -1792,7 +1843,9 @@ async function extractExistingBookmarks(doc) { try { function addNamePair(nameObj, destObj) { try { - const key = nameObj.decodeText ? nameObj.decodeText() : String(nameObj); + const key = nameObj.decodeText + ? nameObj.decodeText() + : String(nameObj); namedDests.set(key, resolveRef(destObj)); } catch (_) { // ignore malformed entry @@ -1804,7 +1857,9 @@ async function extractExistingBookmarks(doc) { node = resolveRef(node); if (!node) return; - const namesArray = node.lookup ? node.lookup(PDFName.of('Names')) : null; + const namesArray = node.lookup + ? node.lookup(PDFName.of('Names')) + : null; if (namesArray && namesArray.array) { for (let i = 0; i < namesArray.array.length; i += 2) { const n = namesArray.array[i]; @@ -1848,7 +1903,8 @@ async function extractExistingBookmarks(doc) { if (pageRef.numberValue !== undefined) { const numericIndex = pageRef.numberValue | 0; - if (numericIndex >= 0 && numericIndex < pages.length) return numericIndex; + if (numericIndex >= 0 && numericIndex < pages.length) + return numericIndex; } if (pageRef.objectNumber !== undefined) { @@ -1860,7 +1916,9 @@ async function extractExistingBookmarks(doc) { if (pageRef.toString) { const target = pageRef.toString(); - const idxByString = pages.findIndex((p) => p.ref.toString() === target); + const idxByString = pages.findIndex( + (p) => p.ref.toString() === target + ); if (idxByString !== -1) return idxByString; } @@ -1884,7 +1942,7 @@ async function extractExistingBookmarks(doc) { // Try Dest entry first let dest = item.lookup(PDFName.of('Dest')); - + // If no Dest, try Action/D if (!dest) { const action = resolveRef(item.lookup(PDFName.of('A'))); @@ -1902,7 +1960,10 @@ async function extractExistingBookmarks(doc) { } else if (dest.lookup) { // Some named destinations resolve to a dictionary with 'D' entry const maybeDict = resolveRef(dest); - const dictD = maybeDict && maybeDict.lookup ? maybeDict.lookup(PDFName.of('D')) : null; + const dictD = + maybeDict && maybeDict.lookup + ? maybeDict.lookup(PDFName.of('D')) + : null; if (dictD) dest = resolveRef(dictD); } } catch (_) { @@ -1975,7 +2036,7 @@ async function extractExistingBookmarks(doc) { style, destX, destY, - zoom + zoom, }; // Process children (make sure to resolve refs) @@ -1986,7 +2047,6 @@ async function extractExistingBookmarks(doc) { child = resolveRef(child.lookup(PDFName.of('Next'))); } - if (pageIndex === 0 && bookmark.children.length > 0) { const firstChild = bookmark.children[0]; if (firstChild) { @@ -2015,6 +2075,13 @@ async function extractExistingBookmarks(doc) { } } +// Back to tools button +if (backToToolsBtn) { + backToToolsBtn.addEventListener('click', () => { + window.location.href = '../../index.html#tools-header'; + }); +} + downloadBtn.addEventListener('click', async () => { const pages = pdfLibDoc.getPages(); const outlinesDict = pdfLibDoc.context.obj({}); diff --git a/src/js/logic/table-of-contents.ts b/src/js/logic/table-of-contents.ts index ea3c33c..5bfbd57 100644 --- a/src/js/logic/table-of-contents.ts +++ b/src/js/logic/table-of-contents.ts @@ -2,17 +2,29 @@ const worker = new Worker('/workers/table-of-contents.worker.js'); let pdfFile: File | null = null; -// Get DOM elements const dropZone = document.getElementById('drop-zone') as HTMLElement; const fileInput = document.getElementById('file-input') as HTMLInputElement; -const generateBtn = document.getElementById('generate-btn') as HTMLButtonElement; +const generateBtn = document.getElementById( + 'generate-btn' +) as HTMLButtonElement; const tocTitleInput = document.getElementById('toc-title') as HTMLInputElement; -const fontSizeSelect = document.getElementById('font-size') as HTMLSelectElement; -const fontFamilySelect = document.getElementById('font-family') as HTMLSelectElement; -const addBookmarkCheckbox = document.getElementById('add-bookmark') as HTMLInputElement; +const fontSizeSelect = document.getElementById( + 'font-size' +) as HTMLSelectElement; +const fontFamilySelect = document.getElementById( + 'font-family' +) as HTMLSelectElement; +const addBookmarkCheckbox = document.getElementById( + 'add-bookmark' +) as HTMLInputElement; const statusMessage = document.getElementById('status-message') as HTMLElement; +const fileDisplayArea = document.getElementById( + 'file-display-area' +) as HTMLElement; +const backToToolsBtn = document.getElementById( + 'back-to-tools' +) as HTMLButtonElement; -// Type definitions for the worker messages interface GenerateTOCMessage { command: 'generate-toc'; pdfData: ArrayBuffer; @@ -35,14 +47,17 @@ interface TOCErrorResponse { type TOCWorkerResponse = TOCSuccessResponse | TOCErrorResponse; // Show status message -function showStatus(message: string, type: 'success' | 'error' | 'info' = 'info') { +function showStatus( + message: string, + type: 'success' | 'error' | 'info' = 'info' +) { statusMessage.textContent = message; statusMessage.className = `mt-4 p-3 rounded-lg text-sm ${ type === 'success' ? 'bg-green-900 text-green-200' : type === 'error' - ? 'bg-red-900 text-red-200' - : 'bg-blue-900 text-blue-200' + ? 'bg-red-900 text-red-200' + : 'bg-blue-900 text-blue-200' }`; statusMessage.classList.remove('hidden'); } @@ -52,6 +67,36 @@ function hideStatus() { statusMessage.classList.add('hidden'); } +// Format bytes helper +function formatBytes(bytes: number): string { + if (bytes === 0) return '0 Bytes'; + const k = 1024; + const sizes = ['Bytes', 'KB', 'MB', 'GB']; + const i = Math.floor(Math.log(bytes) / Math.log(k)); + return Math.round((bytes / Math.pow(k, i)) * 100) / 100 + ' ' + sizes[i]; +} + +// Render file display +function renderFileDisplay(file: File) { + fileDisplayArea.innerHTML = ''; + fileDisplayArea.classList.remove('hidden'); + + const fileDiv = document.createElement('div'); + fileDiv.className = + 'flex items-center justify-between bg-gray-700 p-3 rounded-lg text-sm'; + + const nameSpan = document.createElement('span'); + nameSpan.className = 'truncate font-medium text-gray-200'; + nameSpan.textContent = file.name; + + const sizeSpan = document.createElement('span'); + sizeSpan.className = 'flex-shrink-0 ml-4 text-gray-400'; + sizeSpan.textContent = formatBytes(file.size); + + fileDiv.append(nameSpan, sizeSpan); + fileDisplayArea.appendChild(fileDiv); +} + // Handle file selection function handleFileSelect(file: File) { if (file.type !== 'application/pdf') { @@ -61,6 +106,7 @@ function handleFileSelect(file: File) { pdfFile = file; generateBtn.disabled = false; + renderFileDisplay(file); showStatus(`File selected: ${file.name}`, 'success'); } @@ -103,7 +149,7 @@ async function generateTableOfContents() { const arrayBuffer = await pdfFile.arrayBuffer(); - showStatus('Offloading table of contents generation to background Worker...', 'info'); + showStatus('Generating table of contents...', 'info'); const title = tocTitleInput.value || 'Table of Contents'; const fontSize = parseInt(fontSizeSelect.value, 10); @@ -142,20 +188,24 @@ worker.onmessage = (e: MessageEvent) => { const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; - a.download = pdfFile?.name.replace('.pdf', '_with_toc.pdf') || 'output_with_toc.pdf'; + a.download = + pdfFile?.name.replace('.pdf', '_with_toc.pdf') || 'output_with_toc.pdf'; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); - showStatus('Table of contents generated successfully! Download started.', 'success'); + showStatus( + 'Table of contents generated successfully! Download started.', + 'success' + ); - setTimeout(() => { - hideStatus(); - pdfFile = null; - fileInput.value = ''; - generateBtn.disabled = true; - }, 3000); + hideStatus(); + pdfFile = null; + fileInput.value = ''; + fileDisplayArea.innerHTML = ''; + fileDisplayArea.classList.add('hidden'); + generateBtn.disabled = true; } else if (e.data.status === 'error') { const errorMessage = e.data.message || 'Unknown error occurred in worker.'; console.error('Worker Error:', errorMessage); @@ -169,4 +219,11 @@ worker.onerror = (error) => { generateBtn.disabled = false; }; -generateBtn.addEventListener('click', generateTableOfContents); \ No newline at end of file +// Back to tools button +if (backToToolsBtn) { + backToToolsBtn.addEventListener('click', () => { + window.location.href = '../../index.html#tools-header'; + }); +} + +generateBtn.addEventListener('click', generateTableOfContents); diff --git a/src/pages/bookmark.html b/src/pages/bookmark.html index 02406d1..d44f6b1 100644 --- a/src/pages/bookmark.html +++ b/src/pages/bookmark.html @@ -103,8 +103,15 @@ class="min-h-screen flex items-center justify-center p-4 bg-gray-900" >
+

Upload a PDF to begin editing bookmarks

@@ -112,7 +119,7 @@
+ + +