@@ -112,6 +112,17 @@ document.addEventListener('DOMContentLoaded', () => {
112112 btnRedo . addEventListener ( 'click' , redo ) ;
113113 btnClearAll . addEventListener ( 'click' , handleClearAll ) ;
114114
115+ // Preview dialog — revoke blob URL on close to free memory
116+ const previewDialog = document . getElementById ( 'previewDialog' ) ;
117+ const closePreview = ( ) => {
118+ const frame = document . getElementById ( 'previewFrame' ) ;
119+ URL . revokeObjectURL ( frame . src ) ;
120+ frame . src = '' ;
121+ previewDialog . close ( ) ;
122+ } ;
123+ document . getElementById ( 'previewCloseBtn' ) . addEventListener ( 'click' , closePreview ) ;
124+ previewDialog . addEventListener ( 'cancel' , closePreview ) ;
125+
115126 // Context menu — hide on outside click or scroll
116127 document . addEventListener ( 'click' , e => {
117128 if ( ! contextMenu . contains ( e . target ) ) hideContextMenu ( ) ;
@@ -357,6 +368,23 @@ function downloadBlob(blob, filename) {
357368 URL . revokeObjectURL ( url ) ;
358369}
359370
371+ /** Open the assembled PDF in a preview modal (uses the browser's native viewer). */
372+ async function previewDoc ( docId ) {
373+ const doc = getDoc ( docId ) ;
374+ if ( ! doc || doc . pages . length === 0 ) return ;
375+ bsToast . show ( 'Building preview…' , 'secondary' ) ;
376+ try {
377+ const bytes = await buildPdf ( doc . pages ) ;
378+ const blob = new Blob ( [ bytes ] , { type : 'application/pdf' } ) ;
379+ const url = URL . createObjectURL ( blob ) ;
380+ document . getElementById ( 'previewModalLabel' ) . textContent = doc . name || 'Preview' ;
381+ document . getElementById ( 'previewFrame' ) . src = url ;
382+ document . getElementById ( 'previewDialog' ) . showModal ( ) ;
383+ } catch ( e ) {
384+ bsToast . show ( 'Preview failed: ' + e . message , 'danger' ) ;
385+ }
386+ }
387+
360388/** Sanitise a doc name for use in filenames. */
361389function safeFilename ( name ) {
362390 return name . replace ( / \. p d f $ / i, '' ) . replace ( / [ / \\ ? % * : | " < > ] / g, '-' ) || 'pdfworker' ;
@@ -491,6 +519,16 @@ function createDocPane(doc) {
491519 handleDocDownload ( doc . id , btnDownload ) ;
492520 } ) ;
493521
522+ // Preview button
523+ const btnPreview = document . createElement ( 'button' ) ;
524+ btnPreview . className = 'btn btn-outline-secondary btn-sm' ;
525+ btnPreview . innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="currentColor" class="me-1" viewBox="0 0 16 16"><path d="M16 8s-3-5.5-8-5.5S0 8 0 8s3 5.5 8 5.5S16 8 16 8zM1.173 8a13.133 13.133 0 0 1 1.66-2.043C4.12 4.668 5.88 3.5 8 3.5c2.12 0 3.879 1.168 5.168 2.457A13.133 13.133 0 0 1 14.828 8c-.058.087-.122.183-.195.288-.335.48-.83 1.12-1.465 1.755C11.879 11.332 10.119 12.5 8 12.5c-2.12 0-3.879-1.168-5.168-2.457A13.134 13.134 0 0 1 1.172 8z"/><path d="M8 5.5a2.5 2.5 0 1 0 0 5 2.5 2.5 0 0 0 0-5zM4.5 8a3.5 3.5 0 1 1 7 0 3.5 3.5 0 0 1-7 0z"/></svg>Preview` ;
526+ btnPreview . title = 'Preview this document as a PDF' ;
527+ btnPreview . addEventListener ( 'click' , e => {
528+ e . stopPropagation ( ) ;
529+ previewDoc ( doc . id ) ;
530+ } ) ;
531+
494532 // Burst button
495533 const btnBurst = document . createElement ( 'button' ) ;
496534 btnBurst . className = 'btn btn-outline-secondary btn-sm' ;
@@ -568,6 +606,7 @@ function createDocPane(doc) {
568606
569607 header . appendChild ( nameInput ) ;
570608 header . appendChild ( btnDownload ) ;
609+ header . appendChild ( btnPreview ) ;
571610 header . appendChild ( btnBurst ) ;
572611 header . appendChild ( btnZip ) ;
573612 header . appendChild ( btnSplitToggle ) ;
0 commit comments