]> git.ipfire.org Git - thirdparty/paperless-ngx.git/commitdiff
Chore: Pngx pdf viewer fixes (#12083)
authorshamoon <4887959+shamoon@users.noreply.github.com>
Fri, 13 Feb 2026 16:38:49 +0000 (08:38 -0800)
committerGitHub <noreply@github.com>
Fri, 13 Feb 2026 16:38:49 +0000 (08:38 -0800)
src-ui/src/app/components/common/pdf-viewer/pdf-viewer.component.scss
src-ui/src/app/components/common/pdf-viewer/pdf-viewer.component.spec.ts
src-ui/src/app/components/common/pdf-viewer/pdf-viewer.component.ts
src-ui/src/app/components/document-detail/document-detail.component.html
src-ui/src/app/components/document-detail/document-detail.component.ts

index eac316f152c473a2e14d7d80c2996041f556579d..6863f54630b51800482170ef447da61af9028c5b 100644 (file)
   position: absolute;
   inset: 0;
   pointer-events: none;
+
+  & .annotationTextContent {
+    opacity: 0;
+  }
 }
index 4703e857d3821ed533bdf0d14e841a5b4004d05f..4e239a2a811b2299bd8655724bed816d7c5c0f8b 100644 (file)
@@ -65,6 +65,13 @@ describe('PngxPdfViewerComponent', () => {
     const pageSpy = jest.fn()
     component.pageChange.subscribe(pageSpy)
 
+    // In real usage the viewer may have multiple pages; our pdfjs mock defaults
+    // to a single page, so explicitly simulate a multi-page document here.
+    const pdf = (component as any).pdf as { numPages: number }
+    pdf.numPages = 3
+    const viewer = (component as any).pdfViewer as PDFViewer
+    viewer.setDocument(pdf)
+
     component.zoomScale = PdfZoomScale.PageFit
     component.zoom = PdfZoomLevel.Two
     component.rotation = 90
@@ -81,7 +88,6 @@ describe('PngxPdfViewerComponent', () => {
       page: new SimpleChange(undefined, 2, false),
     })
 
-    const viewer = (component as any).pdfViewer as PDFViewer
     expect(viewer.pagesRotation).toBe(90)
     expect(viewer.currentPageNumber).toBe(2)
     expect(pageSpy).toHaveBeenCalledWith(2)
@@ -196,6 +202,8 @@ describe('PngxPdfViewerComponent', () => {
     const scaleSpy = jest.spyOn(component as any, 'applyViewerState')
     const resizeSpy = jest.spyOn(component as any, 'setupResizeObserver')
 
+    // Angular sets the input value before calling ngOnChanges; mirror that here.
+    component.src = 'test.pdf'
     component.ngOnChanges({
       src: new SimpleChange(undefined, 'test.pdf', true),
       zoomScale: new SimpleChange(
@@ -211,6 +219,25 @@ describe('PngxPdfViewerComponent', () => {
     expect(scaleSpy).not.toHaveBeenCalled()
   })
 
+  it('resets viewer state on src change', () => {
+    const mockViewer = {
+      setDocument: jest.fn(),
+      currentPageNumber: 7,
+      cleanup: jest.fn(),
+    }
+    ;(component as any).pdfViewer = mockViewer
+    ;(component as any).loadingTask = { destroy: jest.fn() }
+    jest.spyOn(component as any, 'loadDocument').mockImplementation(() => {})
+
+    component.src = 'test.pdf'
+    component.ngOnChanges({
+      src: new SimpleChange(undefined, 'test.pdf', true),
+    })
+
+    expect(mockViewer.setDocument).toHaveBeenCalledWith(null)
+    expect(mockViewer.currentPageNumber).toBe(1)
+  })
+
   it('applies viewer state after view init when already loaded', () => {
     const applySpy = jest.spyOn(component as any, 'applyViewerState')
     ;(component as any).hasLoaded = true
index 4a7008b7b8886741de2d7768492bd1cf6a5e818b..032ccd3e07d2011917419d6c56e750c0fab55bd2 100644 (file)
@@ -81,7 +81,7 @@ export class PngxPdfViewerComponent
     this.dispatchFindIfReady()
     this.rendered.emit()
   }
-  private readonly onPagesInit = () => this.applyScale()
+  private readonly onPagesInit = () => this.applyViewerState()
   private readonly onPageChanging = (evt: { pageNumber: number }) => {
     // Avoid [(page)] two-way binding re-triggers navigation
     this.lastViewerPage = evt.pageNumber
@@ -90,8 +90,10 @@ export class PngxPdfViewerComponent
 
   ngOnChanges(changes: SimpleChanges): void {
     if (changes['src']) {
-      this.hasLoaded = false
-      this.loadDocument()
+      this.resetViewerState()
+      if (this.src) {
+        this.loadDocument()
+      }
       return
     }
 
@@ -139,6 +141,21 @@ export class PngxPdfViewerComponent
     this.pdfViewer = undefined
   }
 
+  private resetViewerState(): void {
+    this.hasLoaded = false
+    this.hasRenderedPage = false
+    this.lastFindQuery = ''
+    this.lastViewerPage = undefined
+    this.loadingTask?.destroy()
+    this.loadingTask = undefined
+    this.pdf = undefined
+    this.linkService.setDocument(null)
+    if (this.pdfViewer) {
+      this.pdfViewer.setDocument(null)
+      this.pdfViewer.currentPageNumber = 1
+    }
+  }
+
   private async loadDocument(): Promise<void> {
     if (this.hasLoaded) {
       return
@@ -222,7 +239,11 @@ export class PngxPdfViewerComponent
       hasPages &&
       this.page !== this.lastViewerPage
     ) {
-      this.pdfViewer.currentPageNumber = this.page
+      const nextPage = Math.min(
+        Math.max(Math.trunc(this.page), 1),
+        this.pdfViewer.pagesCount
+      )
+      this.pdfViewer.currentPageNumber = nextPage
     }
     if (this.page === this.lastViewerPage) {
       this.lastViewerPage = undefined
index c2b545b09c128ed87e238222451c26530e2d2094..f17fab55d5fd1d5c1adf7098b5f39675514d2145 100644 (file)
         @if (!useNativePdfViewer) {
           <div class="preview-sticky pdf-viewer-container">
             <pngx-pdf-viewer
-              [src]="{ url: previewUrl, password: password }"
+              [src]="pdfSource"
               [renderMode]="PdfRenderMode.All"
               [(page)]="previewCurrentPage"
               [zoomScale]="previewZoomScale"
index d11e11bcad8c039c87896817b8576dd66a0ea2f9..710bea91d820bc69164e66fdbbd2ae24548d6610 100644 (file)
@@ -110,6 +110,7 @@ import { PDFEditorComponent } from '../common/pdf-editor/pdf-editor.component'
 import { PngxPdfViewerComponent } from '../common/pdf-viewer/pdf-viewer.component'
 import {
   PdfRenderMode,
+  PdfSource,
   PdfZoomLevel,
   PdfZoomScale,
   PngxPdfDocumentProxy,
@@ -227,6 +228,7 @@ export class DocumentDetailComponent
   title: string
   titleSubject: Subject<string> = new Subject()
   previewUrl: string
+  pdfSource?: PdfSource
   thumbUrl: string
   previewText: string
   previewLoaded: boolean = false
@@ -345,6 +347,17 @@ export class DocumentDetailComponent
     return ContentRenderType.Other
   }
 
+  private updatePdfSource() {
+    if (!this.previewUrl) {
+      this.pdfSource = undefined
+      return
+    }
+    this.pdfSource = {
+      url: this.previewUrl,
+      password: this.password || undefined,
+    }
+  }
+
   get isRTL() {
     if (!this.metadata || !this.metadata.lang) return false
     else {
@@ -421,6 +434,7 @@ export class DocumentDetailComponent
 
   private loadDocument(documentId: number): void {
     this.previewUrl = this.documentsService.getPreviewUrl(documentId)
+    this.updatePdfSource()
     this.http
       .get(this.previewUrl, { responseType: 'text' })
       .pipe(
@@ -1230,6 +1244,7 @@ export class DocumentDetailComponent
   onPasswordKeyUp(event: KeyboardEvent) {
     if ('Enter' == event.key) {
       this.password = (event.target as HTMLInputElement).value
+      this.updatePdfSource()
     }
   }