2025-12-04 14:02:20 +05:30
import { showLoader , hideLoader , showAlert } from '../ui.js' ;
2026-03-10 20:13:13 +01:00
import { t } from '../i18n/i18n' ;
2025-12-04 14:02:20 +05:30
import { createIcons , icons } from 'lucide' ;
import * as pdfjsLib from 'pdfjs-dist' ;
2026-03-26 12:11:12 +05:30
import { downloadFile , getPDFDocument , formatBytes } from '../utils/helpers.js' ;
import { loadPdfWithPasswordPrompt } from '../utils/password-prompt.js' ;
2025-12-04 14:02:20 +05:30
import { state } from '../state.js' ;
2026-01-27 15:26:11 +05:30
import {
renderPagesProgressively ,
cleanupLazyRendering ,
} from '../utils/render-utils.js' ;
2026-03-04 00:38:07 +05:30
import { initPagePreview } from '../utils/page-preview.js' ;
2026-01-27 15:26:11 +05:30
import { isCpdfAvailable } from '../utils/cpdf-helper.js' ;
import { showWasmRequiredDialog } from '../utils/wasm-provider.js' ;
2025-12-04 14:02:20 +05:30
import JSZip from 'jszip' ;
import { PDFDocument as PDFLibDocument } from 'pdf-lib' ;
// @ts-ignore
2026-01-27 15:26:11 +05:30
pdfjsLib . GlobalWorkerOptions . workerSrc = new URL (
'pdfjs-dist/build/pdf.worker.min.mjs' ,
import . meta . url
) . toString ( ) ;
2025-12-04 14:02:20 +05:30
document . addEventListener ( 'DOMContentLoaded' , ( ) = > {
2026-01-27 15:26:11 +05:30
let visualSelectorRendered = false ;
const fileInput = document . getElementById ( 'file-input' ) as HTMLInputElement ;
const dropZone = document . getElementById ( 'drop-zone' ) ;
const processBtn = document . getElementById ( 'process-btn' ) ;
const fileDisplayArea = document . getElementById ( 'file-display-area' ) ;
const splitOptions = document . getElementById ( 'split-options' ) ;
const backBtn = document . getElementById ( 'back-to-tools' ) ;
// Split Mode Elements
const splitModeSelect = document . getElementById (
'split-mode'
) as HTMLSelectElement ;
const rangePanel = document . getElementById ( 'range-panel' ) ;
const visualPanel = document . getElementById ( 'visual-select-panel' ) ;
const evenOddPanel = document . getElementById ( 'even-odd-panel' ) ;
const zipOptionWrapper = document . getElementById ( 'zip-option-wrapper' ) ;
const allPagesPanel = document . getElementById ( 'all-pages-panel' ) ;
const bookmarksPanel = document . getElementById ( 'bookmarks-panel' ) ;
const nTimesPanel = document . getElementById ( 'n-times-panel' ) ;
const nTimesWarning = document . getElementById ( 'n-times-warning' ) ;
if ( backBtn ) {
backBtn . addEventListener ( 'click' , ( ) = > {
window . location . href = import . meta . env . BASE_URL ;
} ) ;
}
const updateUI = async ( ) = > {
if ( state . files . length > 0 ) {
const file = state . files [ 0 ] ;
if ( fileDisplayArea ) {
fileDisplayArea . innerHTML = '' ;
const fileDiv = document . createElement ( 'div' ) ;
fileDiv . className =
'flex items-center justify-between bg-gray-700 p-3 rounded-lg text-sm' ;
const infoContainer = document . createElement ( 'div' ) ;
infoContainer . className = 'flex flex-col overflow-hidden' ;
const nameSpan = document . createElement ( 'div' ) ;
nameSpan . className = 'truncate font-medium text-gray-200 text-sm mb-1' ;
nameSpan . textContent = file . name ;
const metaSpan = document . createElement ( 'div' ) ;
metaSpan . className = 'text-xs text-gray-400' ;
2026-03-10 20:13:13 +01:00
metaSpan . textContent = ` ${ formatBytes ( file . size ) } • ${ t ( 'common.loadingPageCount' ) } ` ; // Placeholder
2026-01-27 15:26:11 +05:30
infoContainer . append ( nameSpan , metaSpan ) ;
// Add remove button
const removeBtn = document . createElement ( 'button' ) ;
removeBtn . className =
'ml-4 text-red-400 hover:text-red-300 flex-shrink-0' ;
removeBtn . innerHTML = '<i data-lucide="trash-2" class="w-4 h-4"></i>' ;
removeBtn . onclick = ( ) = > {
state . files = [ ] ;
state . pdfDoc = null ;
updateUI ( ) ;
} ;
fileDiv . append ( infoContainer , removeBtn ) ;
fileDisplayArea . appendChild ( fileDiv ) ;
createIcons ( { icons } ) ;
// Load PDF Document
try {
if ( ! state . pdfDoc ) {
2026-03-26 12:11:12 +05:30
const result = await loadPdfWithPasswordPrompt ( file ) ;
if ( ! result ) {
state . files = [ ] ;
updateUI ( ) ;
return ;
}
result . pdf . destroy ( ) ;
state . files [ 0 ] = result . file ;
2026-03-26 12:55:50 +05:30
state . pdfDoc = await PDFLibDocument . load ( result . bytes , {
ignoreEncryption : true ,
} ) ;
2026-01-27 15:26:11 +05:30
}
// Update page count
metaSpan . textContent = ` ${ formatBytes ( file . size ) } • ${ state . pdfDoc . getPageCount ( ) } pages ` ;
} catch ( error ) {
console . error ( 'Error loading PDF:' , error ) ;
showAlert ( 'Error' , 'Failed to load PDF file.' ) ;
state . files = [ ] ;
updateUI ( ) ;
return ;
2025-12-04 14:02:20 +05:30
}
2026-01-27 15:26:11 +05:30
}
2025-12-04 14:02:20 +05:30
2026-01-27 15:26:11 +05:30
if ( splitOptions ) splitOptions . classList . remove ( 'hidden' ) ;
} else {
if ( fileDisplayArea ) fileDisplayArea . innerHTML = '' ;
if ( splitOptions ) splitOptions . classList . add ( 'hidden' ) ;
state . pdfDoc = null ;
}
} ;
2025-12-04 14:02:20 +05:30
2026-01-27 15:26:11 +05:30
const renderVisualSelector = async ( ) = > {
if ( visualSelectorRendered ) return ;
2025-12-04 14:02:20 +05:30
2026-01-27 15:26:11 +05:30
const container = document . getElementById ( 'page-selector-grid' ) ;
if ( ! container ) return ;
2025-12-04 14:02:20 +05:30
2026-01-27 15:26:11 +05:30
visualSelectorRendered = true ;
container . textContent = '' ;
2025-12-04 14:02:20 +05:30
2026-01-27 15:26:11 +05:30
// Cleanup any previous lazy loading observers
cleanupLazyRendering ( ) ;
2025-12-04 14:02:20 +05:30
2026-01-27 15:26:11 +05:30
showLoader ( 'Rendering page previews...' ) ;
2025-12-04 14:02:20 +05:30
2026-01-27 15:26:11 +05:30
try {
if ( ! state . pdfDoc ) {
// If pdfDoc is not loaded yet (e.g. page refresh), try to load it from the first file
if ( state . files . length > 0 ) {
const file = state . files [ 0 ] ;
2026-03-26 12:11:12 +05:30
hideLoader ( ) ;
const result = await loadPdfWithPasswordPrompt ( file ) ;
if ( ! result ) {
showLoader ( 'Rendering page previews...' ) ;
throw new Error ( 'No PDF document loaded' ) ;
}
result . pdf . destroy ( ) ;
state . files [ 0 ] = result . file ;
2026-03-26 12:55:50 +05:30
state . pdfDoc = await PDFLibDocument . load ( result . bytes , {
ignoreEncryption : true ,
} ) ;
2026-03-26 12:11:12 +05:30
showLoader ( 'Rendering page previews...' ) ;
2026-01-27 15:26:11 +05:30
} else {
throw new Error ( 'No PDF document loaded' ) ;
2025-12-04 14:02:20 +05:30
}
2026-01-27 15:26:11 +05:30
}
2025-12-04 14:02:20 +05:30
2026-01-27 15:26:11 +05:30
const pdfData = await state . pdfDoc . save ( ) ;
const pdf = await getPDFDocument ( { data : pdfData } ) . promise ;
2025-12-04 14:02:20 +05:30
2026-01-27 15:26:11 +05:30
// Function to create wrapper element for each page
const createWrapper = ( canvas : HTMLCanvasElement , pageNumber : number ) = > {
const wrapper = document . createElement ( 'div' ) ;
wrapper . className =
2026-03-04 00:38:07 +05:30
'page-thumbnail-wrapper p-2 border-2 border-gray-600 rounded-lg cursor-pointer hover:border-indigo-500 bg-gray-700 transition-colors relative group flex flex-col items-center gap-1' ;
2026-01-27 15:26:11 +05:30
wrapper . dataset . pageIndex = ( pageNumber - 1 ) . toString ( ) ;
2026-03-04 00:38:07 +05:30
wrapper . dataset . pageNumber = pageNumber . toString ( ) ;
const imgContainer = document . createElement ( 'div' ) ;
imgContainer . className = 'relative' ;
2025-12-04 14:02:20 +05:30
2026-01-27 15:26:11 +05:30
const img = document . createElement ( 'img' ) ;
img . src = canvas . toDataURL ( ) ;
2026-03-04 00:38:07 +05:30
img . className = 'rounded-md shadow-md max-w-full h-auto' ;
2025-12-04 14:02:20 +05:30
2026-03-04 00:38:07 +05:30
const pageNumDiv = document . createElement ( 'div' ) ;
pageNumDiv . className =
'absolute top-1 left-1 bg-indigo-600 text-white text-xs px-2 py-1 rounded-md font-semibold shadow-lg z-10 pointer-events-none' ;
pageNumDiv . textContent = pageNumber . toString ( ) ;
2025-12-04 14:02:20 +05:30
2026-03-04 00:38:07 +05:30
imgContainer . append ( img , pageNumDiv ) ;
wrapper . appendChild ( imgContainer ) ;
2025-12-04 14:02:20 +05:30
2026-01-27 15:26:11 +05:30
const handleSelection = ( e : any ) = > {
e . preventDefault ( ) ;
e . stopPropagation ( ) ;
2025-12-04 14:02:20 +05:30
2026-01-27 15:26:11 +05:30
const isSelected = wrapper . classList . contains ( 'selected' ) ;
2025-12-04 14:02:20 +05:30
2026-01-27 15:26:11 +05:30
if ( isSelected ) {
wrapper . classList . remove ( 'selected' , 'border-indigo-500' ) ;
2026-03-04 00:38:07 +05:30
wrapper . classList . add ( 'border-gray-600' ) ;
2026-01-27 15:26:11 +05:30
} else {
wrapper . classList . add ( 'selected' , 'border-indigo-500' ) ;
2026-03-04 00:38:07 +05:30
wrapper . classList . remove ( 'border-gray-600' ) ;
2026-01-27 15:26:11 +05:30
}
} ;
2025-12-04 14:02:20 +05:30
2026-01-27 15:26:11 +05:30
wrapper . addEventListener ( 'click' , handleSelection ) ;
wrapper . addEventListener ( 'touchend' , handleSelection ) ;
2025-12-04 14:02:20 +05:30
2026-01-27 15:26:11 +05:30
wrapper . addEventListener ( 'touchstart' , ( e ) = > {
e . preventDefault ( ) ;
} ) ;
2025-12-04 14:02:20 +05:30
2026-01-27 15:26:11 +05:30
return wrapper ;
} ;
// Render pages progressively with lazy loading
await renderPagesProgressively ( pdf , container , createWrapper , {
batchSize : 8 ,
useLazyLoading : true ,
lazyLoadMargin : '400px' ,
onProgress : ( current , total ) = > {
showLoader ( ` Rendering page previews: ${ current } / ${ total } ` ) ;
} ,
onBatchComplete : ( ) = > {
createIcons ( { icons } ) ;
} ,
} ) ;
2026-03-04 00:38:07 +05:30
initPagePreview ( container , pdf ) ;
2026-01-27 15:26:11 +05:30
} catch ( error ) {
console . error ( 'Error rendering visual selector:' , error ) ;
showAlert ( 'Error' , 'Failed to render page previews.' ) ;
// Reset the flag on error so the user can try again.
visualSelectorRendered = false ;
} finally {
hideLoader ( ) ;
}
} ;
const resetState = ( ) = > {
state . files = [ ] ;
state . pdfDoc = null ;
// Reset visual selection
document
. querySelectorAll ( '.page-thumbnail-wrapper.selected' )
. forEach ( ( el ) = > {
el . classList . remove ( 'selected' , 'border-indigo-500' ) ;
el . classList . add ( 'border-transparent' ) ;
} ) ;
visualSelectorRendered = false ;
const container = document . getElementById ( 'page-selector-grid' ) ;
if ( container ) container . innerHTML = '' ;
// Reset inputs
const pageRangeInput = document . getElementById (
'page-range'
) as HTMLInputElement ;
if ( pageRangeInput ) pageRangeInput . value = '' ;
const nValueInput = document . getElementById (
'split-n-value'
) as HTMLInputElement ;
if ( nValueInput ) nValueInput . value = '5' ;
// Reset radio buttons to default (range)
const rangeRadio = document . querySelector (
'input[name="split-mode"][value="range"]'
) as HTMLInputElement ;
if ( rangeRadio ) {
rangeRadio . checked = true ;
rangeRadio . dispatchEvent ( new Event ( 'change' ) ) ;
}
// Reset split mode select
if ( splitModeSelect ) {
splitModeSelect . value = 'range' ;
splitModeSelect . dispatchEvent ( new Event ( 'change' ) ) ;
}
2025-12-04 14:02:20 +05:30
2026-01-27 15:26:11 +05:30
updateUI ( ) ;
} ;
const split = async ( ) = > {
const splitMode = splitModeSelect . value ;
const downloadAsZip =
( document . getElementById ( 'download-as-zip' ) as HTMLInputElement )
? . checked || false ;
showLoader ( 'Splitting PDF...' ) ;
try {
if ( ! state . pdfDoc ) throw new Error ( 'No PDF document loaded.' ) ;
const totalPages = state . pdfDoc . getPageCount ( ) ;
let indicesToExtract : number [ ] = [ ] ;
switch ( splitMode ) {
case 'range' :
const pageRangeInput = (
document . getElementById ( 'page-range' ) as HTMLInputElement
) . value ;
if ( ! pageRangeInput ) throw new Error ( 'Choose a valid page range.' ) ;
const ranges = pageRangeInput . split ( ',' ) ;
const rangeGroups : number [ ] [ ] = [ ] ;
for ( const range of ranges ) {
const trimmedRange = range . trim ( ) ;
if ( ! trimmedRange ) continue ;
const groupIndices : number [ ] = [ ] ;
if ( trimmedRange . includes ( '-' ) ) {
const [ start , end ] = trimmedRange . split ( '-' ) . map ( Number ) ;
if (
isNaN ( start ) ||
isNaN ( end ) ||
start < 1 ||
end > totalPages ||
start > end
)
continue ;
for ( let i = start ; i <= end ; i ++ ) groupIndices . push ( i - 1 ) ;
2025-12-04 14:02:20 +05:30
} else {
2026-01-27 15:26:11 +05:30
const pageNum = Number ( trimmedRange ) ;
if ( isNaN ( pageNum ) || pageNum < 1 || pageNum > totalPages )
continue ;
groupIndices . push ( pageNum - 1 ) ;
2025-12-04 14:02:20 +05:30
}
2026-01-27 15:26:11 +05:30
if ( groupIndices . length > 0 ) {
rangeGroups . push ( groupIndices ) ;
indicesToExtract . push ( . . . groupIndices ) ;
}
}
if ( rangeGroups . length > 1 ) {
showLoader ( 'Creating separate PDFs for each range...' ) ;
const zip = new JSZip ( ) ;
for ( let i = 0 ; i < rangeGroups . length ; i ++ ) {
const group = rangeGroups [ i ] ;
const newPdf = await PDFLibDocument . create ( ) ;
const copiedPages = await newPdf . copyPages ( state . pdfDoc , group ) ;
copiedPages . forEach ( ( page : any ) = > newPdf . addPage ( page ) ) ;
const pdfBytes = await newPdf . save ( ) ;
const minPage = Math . min ( . . . group ) + 1 ;
const maxPage = Math . max ( . . . group ) + 1 ;
const filename =
minPage === maxPage
? ` page- ${ minPage } .pdf `
: ` pages- ${ minPage } - ${ maxPage } .pdf ` ;
zip . file ( filename , pdfBytes ) ;
2025-12-04 14:02:20 +05:30
}
2026-01-27 15:26:11 +05:30
const zipBlob = await zip . generateAsync ( { type : 'blob' } ) ;
downloadFile ( zipBlob , 'split-pages.zip' ) ;
hideLoader ( ) ;
2025-12-04 14:02:20 +05:30
showAlert (
2026-01-27 15:26:11 +05:30
'Success' ,
` PDF split into ${ rangeGroups . length } files successfully! ` ,
'success' ,
( ) = > {
resetState ( ) ;
}
2025-12-04 14:02:20 +05:30
) ;
2026-01-27 15:26:11 +05:30
return ;
}
break ;
case 'even-odd' :
const choiceElement = document . querySelector (
'input[name="even-odd-choice"]:checked'
) as HTMLInputElement ;
if ( ! choiceElement )
throw new Error ( 'Please select even or odd pages.' ) ;
const choice = choiceElement . value ;
for ( let i = 0 ; i < totalPages ; i ++ ) {
if ( choice === 'even' && ( i + 1 ) % 2 === 0 )
indicesToExtract . push ( i ) ;
if ( choice === 'odd' && ( i + 1 ) % 2 !== 0 ) indicesToExtract . push ( i ) ;
}
break ;
case 'all' :
indicesToExtract = Array . from ( { length : totalPages } , ( _ , i ) = > i ) ;
break ;
case 'visual' :
indicesToExtract = Array . from (
document . querySelectorAll ( '.page-thumbnail-wrapper.selected' )
) . map ( ( el ) = > parseInt ( ( el as HTMLElement ) . dataset . pageIndex || '0' ) ) ;
break ;
case 'bookmarks' :
// Check if CPDF is configured
if ( ! isCpdfAvailable ( ) ) {
showWasmRequiredDialog ( 'cpdf' ) ;
2025-12-04 14:02:20 +05:30
hideLoader ( ) ;
2026-01-27 15:26:11 +05:30
return ;
}
const { getCpdf } = await import ( '../utils/cpdf-helper.js' ) ;
const cpdf = await getCpdf ( ) ;
const pdfBytes = await state . pdfDoc . save ( ) ;
const pdf = cpdf . fromMemory ( new Uint8Array ( pdfBytes ) , '' ) ;
cpdf . startGetBookmarkInfo ( pdf ) ;
const bookmarkCount = cpdf . numberBookmarks ( ) ;
const bookmarkLevel = (
document . getElementById ( 'bookmark-level' ) as HTMLSelectElement
) ? . value ;
const splitPages : number [ ] = [ ] ;
for ( let i = 0 ; i < bookmarkCount ; i ++ ) {
const level = cpdf . getBookmarkLevel ( i ) ;
const page = cpdf . getBookmarkPage ( pdf , i ) ;
if ( bookmarkLevel === 'all' || level === parseInt ( bookmarkLevel ) ) {
if ( page > 1 && ! splitPages . includes ( page - 1 ) ) {
splitPages . push ( page - 1 ) ; // Convert to 0-based index
}
}
}
cpdf . endGetBookmarkInfo ( ) ;
cpdf . deletePdf ( pdf ) ;
if ( splitPages . length === 0 ) {
throw new Error ( 'No bookmarks found at the selected level.' ) ;
}
splitPages . sort ( ( a , b ) = > a - b ) ;
const zip = new JSZip ( ) ;
for ( let i = 0 ; i < splitPages . length ; i ++ ) {
const startPage = i === 0 ? 0 : splitPages [ i ] ;
const endPage =
i < splitPages . length - 1
? splitPages [ i + 1 ] - 1
: totalPages - 1 ;
const newPdf = await PDFLibDocument . create ( ) ;
const pageIndices = Array . from (
{ length : endPage - startPage + 1 } ,
( _ , idx ) = > startPage + idx
) ;
const copiedPages = await newPdf . copyPages (
state . pdfDoc ,
pageIndices
) ;
copiedPages . forEach ( ( page : any ) = > newPdf . addPage ( page ) ) ;
const pdfBytes2 = await newPdf . save ( ) ;
zip . file ( ` split- ${ i + 1 } .pdf ` , pdfBytes2 ) ;
}
const zipBlob = await zip . generateAsync ( { type : 'blob' } ) ;
downloadFile ( zipBlob , 'split-by-bookmarks.zip' ) ;
hideLoader ( ) ;
showAlert ( 'Success' , 'PDF split successfully!' , 'success' , ( ) = > {
resetState ( ) ;
} ) ;
return ;
case 'n-times' :
const nValue = parseInt (
( document . getElementById ( 'split-n-value' ) as HTMLInputElement )
? . value || '5'
) ;
if ( nValue < 1 ) throw new Error ( 'N must be at least 1.' ) ;
const zip2 = new JSZip ( ) ;
const numSplits = Math . ceil ( totalPages / nValue ) ;
for ( let i = 0 ; i < numSplits ; i ++ ) {
const startPage = i * nValue ;
const endPage = Math . min ( startPage + nValue - 1 , totalPages - 1 ) ;
const pageIndices = Array . from (
{ length : endPage - startPage + 1 } ,
( _ , idx ) = > startPage + idx
) ;
2025-12-04 14:02:20 +05:30
2026-01-27 15:26:11 +05:30
const newPdf = await PDFLibDocument . create ( ) ;
const copiedPages = await newPdf . copyPages (
state . pdfDoc ,
pageIndices
) ;
copiedPages . forEach ( ( page : any ) = > newPdf . addPage ( page ) ) ;
const pdfBytes3 = await newPdf . save ( ) ;
zip2 . file ( ` split- ${ i + 1 } .pdf ` , pdfBytes3 ) ;
}
const zipBlob2 = await zip2 . generateAsync ( { type : 'blob' } ) ;
downloadFile ( zipBlob2 , 'split-n-times.zip' ) ;
hideLoader ( ) ;
showAlert ( 'Success' , 'PDF split successfully!' , 'success' , ( ) = > {
resetState ( ) ;
} ) ;
return ;
}
const uniqueIndices = [ . . . new Set ( indicesToExtract ) ] ;
if (
uniqueIndices . length === 0 &&
splitMode !== 'bookmarks' &&
splitMode !== 'n-times'
) {
throw new Error ( 'No pages were selected for splitting.' ) ;
}
if (
splitMode === 'all' ||
( [ 'range' , 'visual' ] . includes ( splitMode ) && downloadAsZip )
) {
showLoader ( 'Creating ZIP file...' ) ;
const zip = new JSZip ( ) ;
for ( const index of uniqueIndices ) {
const newPdf = await PDFLibDocument . create ( ) ;
const [ copiedPage ] = await newPdf . copyPages ( state . pdfDoc , [
index as number ,
] ) ;
newPdf . addPage ( copiedPage ) ;
const pdfBytes = await newPdf . save ( ) ;
// @ts-ignore
zip . file ( ` page- ${ index + 1 } .pdf ` , pdfBytes ) ;
2025-12-04 14:02:20 +05:30
}
2026-01-27 15:26:11 +05:30
const zipBlob = await zip . generateAsync ( { type : 'blob' } ) ;
downloadFile ( zipBlob , 'split-pages.zip' ) ;
} else {
const newPdf = await PDFLibDocument . create ( ) ;
const copiedPages = await newPdf . copyPages (
state . pdfDoc ,
uniqueIndices as number [ ]
) ;
copiedPages . forEach ( ( page : any ) = > newPdf . addPage ( page ) ) ;
const pdfBytes = await newPdf . save ( ) ;
downloadFile (
new Blob ( [ new Uint8Array ( pdfBytes ) ] , { type : 'application/pdf' } ) ,
'split-document.pdf'
) ;
}
if ( splitMode === 'visual' ) {
visualSelectorRendered = false ;
}
showAlert ( 'Success' , 'PDF split successfully!' , 'success' , ( ) = > {
resetState ( ) ;
} ) ;
} catch ( e : any ) {
console . error ( e ) ;
showAlert (
'Error' ,
e . message || 'Failed to split PDF. Please check your selection.'
) ;
} finally {
hideLoader ( ) ;
}
} ;
2025-12-04 14:02:20 +05:30
2026-01-27 15:26:11 +05:30
const handleFileSelect = async ( files : FileList | null ) = > {
if ( files && files . length > 0 ) {
// Split tool only supports one file at a time
state . files = [ files [ 0 ] ] ;
await updateUI ( ) ;
2025-12-04 14:02:20 +05:30
}
2026-01-27 15:26:11 +05:30
} ;
if ( fileInput && dropZone ) {
fileInput . addEventListener ( 'change' , ( e ) = > {
handleFileSelect ( ( e . target as HTMLInputElement ) . files ) ;
} ) ;
dropZone . addEventListener ( 'dragover' , ( e ) = > {
e . preventDefault ( ) ;
dropZone . classList . add ( 'bg-gray-700' ) ;
} ) ;
dropZone . addEventListener ( 'dragleave' , ( e ) = > {
e . preventDefault ( ) ;
dropZone . classList . remove ( 'bg-gray-700' ) ;
} ) ;
dropZone . addEventListener ( 'drop' , ( e ) = > {
e . preventDefault ( ) ;
dropZone . classList . remove ( 'bg-gray-700' ) ;
const files = e . dataTransfer ? . files ;
if ( files ) {
const pdfFiles = Array . from ( files ) . filter (
( f ) = >
f . type === 'application/pdf' ||
f . name . toLowerCase ( ) . endsWith ( '.pdf' )
) ;
if ( pdfFiles . length > 0 ) {
// Take only the first PDF
const dataTransfer = new DataTransfer ( ) ;
dataTransfer . items . add ( pdfFiles [ 0 ] ) ;
handleFileSelect ( dataTransfer . files ) ;
}
}
} ) ;
2025-12-04 14:02:20 +05:30
2026-01-27 15:26:11 +05:30
// Clear value on click to allow re-selecting the same file
fileInput . addEventListener ( 'click' , ( ) = > {
fileInput . value = '' ;
} ) ;
}
2025-12-04 14:02:20 +05:30
2026-01-27 15:26:11 +05:30
if ( splitModeSelect ) {
splitModeSelect . addEventListener ( 'change' , ( e ) = > {
const mode = ( e . target as HTMLSelectElement ) . value ;
2025-12-04 14:02:20 +05:30
2026-01-27 15:26:11 +05:30
if ( mode !== 'visual' ) {
visualSelectorRendered = false ;
const container = document . getElementById ( 'page-selector-grid' ) ;
if ( container ) container . innerHTML = '' ;
}
rangePanel ? . classList . add ( 'hidden' ) ;
visualPanel ? . classList . add ( 'hidden' ) ;
evenOddPanel ? . classList . add ( 'hidden' ) ;
allPagesPanel ? . classList . add ( 'hidden' ) ;
bookmarksPanel ? . classList . add ( 'hidden' ) ;
nTimesPanel ? . classList . add ( 'hidden' ) ;
zipOptionWrapper ? . classList . add ( 'hidden' ) ;
if ( nTimesWarning ) nTimesWarning . classList . add ( 'hidden' ) ;
if ( mode === 'range' ) {
rangePanel ? . classList . remove ( 'hidden' ) ;
zipOptionWrapper ? . classList . remove ( 'hidden' ) ;
} else if ( mode === 'visual' ) {
visualPanel ? . classList . remove ( 'hidden' ) ;
zipOptionWrapper ? . classList . remove ( 'hidden' ) ;
renderVisualSelector ( ) ;
} else if ( mode === 'even-odd' ) {
evenOddPanel ? . classList . remove ( 'hidden' ) ;
} else if ( mode === 'all' ) {
allPagesPanel ? . classList . remove ( 'hidden' ) ;
} else if ( mode === 'bookmarks' ) {
bookmarksPanel ? . classList . remove ( 'hidden' ) ;
zipOptionWrapper ? . classList . remove ( 'hidden' ) ;
} else if ( mode === 'n-times' ) {
nTimesPanel ? . classList . remove ( 'hidden' ) ;
zipOptionWrapper ? . classList . remove ( 'hidden' ) ;
const updateWarning = ( ) = > {
if ( ! state . pdfDoc ) return ;
const totalPages = state . pdfDoc . getPageCount ( ) ;
const nValue = parseInt (
( document . getElementById ( 'split-n-value' ) as HTMLInputElement )
? . value || '5'
) ;
const remainder = totalPages % nValue ;
if ( remainder !== 0 && nTimesWarning ) {
nTimesWarning . classList . remove ( 'hidden' ) ;
const warningText = document . getElementById ( 'n-times-warning-text' ) ;
if ( warningText ) {
warningText . textContent = ` The PDF has ${ totalPages } pages, which is not evenly divisible by ${ nValue } . The last PDF will contain ${ remainder } page(s). ` ;
2025-12-04 14:02:20 +05:30
}
2026-01-27 15:26:11 +05:30
} else if ( nTimesWarning ) {
nTimesWarning . classList . add ( 'hidden' ) ;
}
} ;
updateWarning ( ) ;
document
. getElementById ( 'split-n-value' )
? . addEventListener ( 'input' , updateWarning ) ;
}
} ) ;
}
if ( processBtn ) {
processBtn . addEventListener ( 'click' , split ) ;
}
2025-12-04 14:02:20 +05:30
} ) ;