diff --git a/static/app.js b/static/app.js index cf885a2..db97918 100644 --- a/static/app.js +++ b/static/app.js @@ -21,9 +21,8 @@ const audioCache = new Map(); let allModels = {}; let currentUser = null; -// PDF.js state -let pdfDoc = null; -const pageCanvases = []; // canvas elements per page +// PDF blob URL for native browser viewer +let pdfBlobUrl = null; // ── DOM ──────────────────────────────────────────────────────────── const $ = id => document.getElementById(id); @@ -184,70 +183,45 @@ function renderText(sentences) { // ── VIEW SWITCHING ───────────────────────────────────────────────── function setView(mode) { state.currentView = mode; - const textViewer = $('text-viewer'); - const pdfViewer = $('pdf-viewer'); const contentArea = $('content-area'); - const btnText = $('btn-view-text'); - const btnPdf = $('btn-view-pdf'); if (mode === 'pdf') { - textViewer.style.display = 'none'; - pdfViewer.style.display = ''; + $('text-viewer').style.display = 'none'; + $('pdf-viewer').style.display = 'flex'; contentArea.classList.add('pdf-mode'); - btnPdf.classList.add('active'); - btnText.classList.remove('active'); + $('btn-view-pdf').classList.add('active'); + $('btn-view-text').classList.remove('active'); $('doc-hint').textContent = 'Viewing original PDF · switch to Text to click sentences'; } else { - textViewer.style.display = ''; - pdfViewer.style.display = 'none'; + $('text-viewer').style.display = ''; + $('pdf-viewer').style.display = 'none'; contentArea.classList.remove('pdf-mode'); - btnText.classList.add('active'); - btnPdf.classList.remove('active'); + $('btn-view-text').classList.add('active'); + $('btn-view-pdf').classList.remove('active'); $('doc-hint').textContent = 'Click any sentence to jump there · Space to play/pause · ← → to skip'; } } window.setView = setView; -// ── PDF.js RENDERING ─────────────────────────────────────────────── -async function renderPDF(b64) { - if (typeof pdfjsLib === 'undefined') return; - pdfjsLib.GlobalWorkerOptions.workerSrc = - 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.worker.min.js'; - - const pdfPages = $('pdf-pages'); - pdfPages.innerHTML = ''; - pageCanvases.length = 0; +// ── PDF NATIVE RENDERING ─────────────────────────────────────────── +function renderPDF(b64) { + // Revoke previous blob to free memory + if (pdfBlobUrl) { URL.revokeObjectURL(pdfBlobUrl); pdfBlobUrl = null; } const binary = atob(b64); const bytes = new Uint8Array(binary.length); for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i); + const blob = new Blob([bytes], { type: 'application/pdf' }); + pdfBlobUrl = URL.createObjectURL(blob); - pdfDoc = await pdfjsLib.getDocument({ data: bytes }).promise; + const pdfPages = $('pdf-pages'); + pdfPages.innerHTML = ''; - for (let i = 1; i <= pdfDoc.numPages; i++) { - const page = await pdfDoc.getPage(i); - const vp = page.getViewport({ scale: 1.5 }); - - const wrap = document.createElement('div'); - wrap.className = 'pdf-page-wrap'; - wrap.dataset.page = i - 1; - - const canvas = document.createElement('canvas'); - canvas.width = vp.width; - canvas.height = vp.height; - canvas.style.width = Math.min(vp.width, 800) + 'px'; - canvas.style.height = (Math.min(vp.width, 800) / vp.width * vp.height) + 'px'; - - await page.render({ canvasContext: canvas.getContext('2d'), viewport: vp }).promise; - wrap.appendChild(canvas); - - const lbl = document.createElement('div'); - lbl.className = 'pdf-page-num'; - lbl.textContent = `Page ${i}`; - pdfPages.appendChild(wrap); - pdfPages.appendChild(lbl); - pageCanvases.push(wrap); - } + const iframe = document.createElement('iframe'); + iframe.id = 'pdf-iframe'; + iframe.src = pdfBlobUrl; + iframe.style.cssText = 'width:100%;height:100%;border:none;display:block;'; + pdfPages.appendChild(iframe); } function buildSentencePageMap(pageTexts, sentences) { @@ -269,8 +243,12 @@ function buildSentencePageMap(pageTexts, sentences) { } function scrollPDFToPage(pageIdx) { - if (pageIdx < 0 || pageIdx >= pageCanvases.length) return; - pageCanvases[pageIdx].scrollIntoView({ behavior: 'smooth', block: 'start' }); + if (!pdfBlobUrl) return; + const iframe = $('pdf-iframe'); + if (!iframe) return; + // #page= fragment navigates inside Chrome/Edge native PDF viewer + const target = pdfBlobUrl + '#page=' + (pageIdx + 1); + if (iframe.src !== target) iframe.src = target; } // ── LOAD DOCUMENT ────────────────────────────────────────────────── diff --git a/static/index.html b/static/index.html index aa66fd3..ea6f8f2 100644 --- a/static/index.html +++ b/static/index.html @@ -219,7 +219,6 @@

Loading…

- diff --git a/static/style.css b/static/style.css index ceb0796..dae5d20 100644 --- a/static/style.css +++ b/static/style.css @@ -201,8 +201,12 @@ textarea:focus, .text-field:focus { border-color: var(--accent); } .content-area { flex: 1; overflow-y: auto; padding: 44px 56px; display: flex; flex-direction: column; + min-height: 0; +} +.content-area.pdf-mode { + padding: 20px 20px 0; + overflow: hidden; /* iframe fills remaining height */ } -.content-area.pdf-mode { padding: 24px 24px 0; overflow: hidden; } .document-header { margin-bottom: 28px; } .doc-title-row { display: flex; align-items: flex-start; gap: 12px; margin-bottom: 6px; } .document-title { font-size: 22px; font-weight: 700; flex: 1; } @@ -281,22 +285,25 @@ textarea:focus, .text-field:focus { border-color: var(--accent); } /* ── PDF VIEWER ──────────────────────────── */ .pdf-viewer { - flex: 1; overflow-y: auto; padding: 24px; background: #1a1a1a; - display: flex; flex-direction: column; align-items: center; gap: 16px; + flex: 1; + min-height: 0; /* essential: lets flex child shrink below content size */ + display: flex; + flex-direction: column; + overflow: hidden; } -#pdf-pages { display: flex; flex-direction: column; gap: 16px; align-items: center; width: 100%; } -.pdf-page-wrap { - position: relative; box-shadow: 0 4px 24px rgba(0,0,0,.5); - border-radius: 4px; overflow: hidden; max-width: 100%; +#pdf-pages { + flex: 1; + min-height: 0; + display: flex; + flex-direction: column; + overflow: hidden; } -.pdf-page-wrap canvas { display: block; max-width: 100%; height: auto; } -.pdf-page-highlight { - position: absolute; border-radius: 2px; - background: rgba(124,58,237,.35); - pointer-events: none; transition: opacity .2s; -} -.pdf-page-num { - text-align: center; font-size: 11px; color: var(--text-d); margin-top: 4px; +#pdf-iframe { + flex: 1; + min-height: 0; + width: 100%; + border: none; + display: block; } /* ── SETTINGS SIDEBAR ────────────────────── */