]> git.ipfire.org Git - thirdparty/paperless-ngx.git/commitdiff
Super basic UI stuff
authorshamoon <4887959+shamoon@users.noreply.github.com>
Mon, 8 Sep 2025 15:13:55 +0000 (08:13 -0700)
committershamoon <4887959+shamoon@users.noreply.github.com>
Sat, 20 Sep 2025 17:10:07 +0000 (10:10 -0700)
[ci skip]

src-ui/src/app/components/document-detail/document-detail.component.html
src-ui/src/app/components/document-detail/document-detail.component.ts
src-ui/src/app/data/document.ts
src-ui/src/app/services/rest/document.service.ts

index d8cd2d756ac927a6902bdfb8e418ca8fe330dc82..7ea9abfdfd990c143914d511cd6c7bee7ec72783 100644 (file)
     }
   </div>
 
+  @if (document?.versions?.length > 0) {
+    <div class="btn-group ms-2" ngbDropdown role="group">
+      <button class="btn btn-sm btn-outline-secondary dropdown-toggle" ngbDropdownToggle>
+        <i-bs name="layers"></i-bs>
+        <span class="d-none d-lg-inline ps-1" i18n>Version</span>
+      </button>
+      <div class="dropdown-menu shadow" ngbDropdownMenu>
+        <button ngbDropdownItem (click)="selectVersion(document.id)">
+          <span i18n>Current</span>
+          @if (selectedVersionId === document.id) { <span>&nbsp;✓</span> }
+        </button>
+        @for (vid of document.versions; track vid) {
+          <button ngbDropdownItem (click)="selectVersion(vid)">
+            <span i18n>Version</span> {{vid}}
+            @if (selectedVersionId === vid) { <span>&nbsp;✓</span> }
+          </button>
+        }
+      </div>
+    </div>
+  }
+
   <div class="ms-auto" ngbDropdown>
     <button class="btn btn-sm btn-outline-primary" id="actionsDropdown" ngbDropdownToggle>
       <i-bs name="three-dots"></i-bs>
       <button ngbDropdownItem (click)="editPdf()" [disabled]="!userIsOwner || !userCanEdit || originalContentRenderType !== ContentRenderType.PDF">
         <i-bs name="pencil"></i-bs>&nbsp;<ng-container i18n>PDF Editor</ng-container>
       </button>
+
+      <div class="dropdown-divider"></div>
+      <button ngbDropdownItem (click)="triggerUploadVersion()" [disabled]="!userIsOwner || !userCanEdit">
+        <i-bs name="file-earmark-plus"></i-bs>&nbsp;<ng-container i18n>Upload new version</ng-container>
+      </button>
+      <input #versionFileInput type="file" class="visually-hidden" (change)="onVersionFileSelected($event)" />
     </div>
   </div>
 
index bea5577dc609ca20b01a78f3719967fd7521b176..267c8f6d4c68e18fefb40af7364125ffbbba4a58 100644 (file)
@@ -222,6 +222,8 @@ export class DocumentDetailComponent
   titleSubject: Subject<string> = new Subject()
   previewUrl: string
   thumbUrl: string
+  // Versioning: which document ID to use for file preview/download
+  selectedVersionId: number
   previewText: string
   previewLoaded: boolean = false
   tiffURL: string
@@ -270,6 +272,7 @@ export class DocumentDetailComponent
   public readonly DataType = DataType
 
   @ViewChild('nav') nav: NgbNav
+  @ViewChild('versionFileInput') versionFileInput
   @ViewChild('pdfPreview') set pdfPreview(element) {
     // this gets called when component added or removed from DOM
     if (
@@ -402,7 +405,10 @@ export class DocumentDetailComponent
   }
 
   private loadDocument(documentId: number): void {
-    this.previewUrl = this.documentsService.getPreviewUrl(documentId)
+    this.selectedVersionId = documentId
+    this.previewUrl = this.documentsService.getPreviewUrl(
+      this.selectedVersionId
+    )
     this.http
       .get(this.previewUrl, { responseType: 'text' })
       .pipe(
@@ -417,7 +423,7 @@ export class DocumentDetailComponent
             err.message ?? err.toString()
           }`),
       })
-    this.thumbUrl = this.documentsService.getThumbUrl(documentId)
+    this.thumbUrl = this.documentsService.getThumbUrl(this.selectedVersionId)
     this.documentsService
       .get(documentId)
       .pipe(
@@ -638,6 +644,8 @@ export class DocumentDetailComponent
 
   updateComponent(doc: Document) {
     this.document = doc
+    // Default selected version is the head document
+    this.selectedVersionId = doc.id
     this.requiresPassword = false
     this.updateFormForCustomFields()
     if (this.archiveContentRenderType === ContentRenderType.TIFF) {
@@ -702,6 +710,30 @@ export class DocumentDetailComponent
     this.prepareForm(doc)
   }
 
+  // Update file preview and download target to a specific version (by document id)
+  selectVersion(versionId: number) {
+    this.selectedVersionId = versionId
+    this.previewUrl = this.documentsService.getPreviewUrl(
+      this.selectedVersionId
+    )
+    this.thumbUrl = this.documentsService.getThumbUrl(this.selectedVersionId)
+    // For text previews, refresh content
+    this.http
+      .get(this.previewUrl, { responseType: 'text' })
+      .pipe(
+        first(),
+        takeUntil(this.unsubscribeNotifier),
+        takeUntil(this.docChangeNotifier)
+      )
+      .subscribe({
+        next: (res) => (this.previewText = res.toString()),
+        error: (err) =>
+          (this.previewText = $localize`An error occurred loading content: ${
+            err.message ?? err.toString()
+          }`),
+      })
+  }
+
   get customFieldFormFields(): FormArray {
     return this.documentForm.get('custom_fields') as FormArray
   }
@@ -1049,10 +1081,41 @@ export class DocumentDetailComponent
     })
   }
 
+  // Upload a new file version for this document
+  triggerUploadVersion() {
+    this.versionFileInput?.nativeElement?.click()
+  }
+
+  onVersionFileSelected(event: Event) {
+    const input = event.target as HTMLInputElement
+    if (!input?.files || input.files.length === 0) return
+    const file = input.files[0]
+    // Reset input to allow re-selection of the same file later
+    input.value = ''
+    this.documentsService
+      .uploadVersion(this.documentId, file)
+      .pipe(first())
+      .subscribe({
+        next: () => {
+          this.toastService.showInfo(
+            $localize`Uploading new version. Processing will happen in the background.`
+          )
+          // Refresh metadata to reflect that versions changed (when ready)
+          this.openDocumentService.refreshDocument(this.documentId)
+        },
+        error: (error) => {
+          this.toastService.showError(
+            $localize`Error uploading new version`,
+            error
+          )
+        },
+      })
+  }
+
   download(original: boolean = false) {
     this.downloading = true
     const downloadUrl = this.documentsService.getDownloadUrl(
-      this.documentId,
+      this.selectedVersionId || this.documentId,
       original
     )
     this.http
index 8aae31945e28e14c6b3924eb198024b916d5b766..8b7d328760f2fd45da11eca619b643c097b92f1c 100644 (file)
@@ -159,6 +159,10 @@ export interface Document extends ObjectWithPermissions {
 
   page_count?: number
 
+  // Versioning
+  head_version?: number
+  versions?: number[]
+
   // Frontend only
   __changedFields?: string[]
 }
index 4f52633eaca618bb4c08bea13dd8c611767a72c2..b70e953b99ee6b37a083fb4576f1500c5ecc7e6c 100644 (file)
@@ -184,6 +184,16 @@ export class DocumentService extends AbstractPaperlessService<Document> {
     return url
   }
 
+  uploadVersion(documentId: number, file: File) {
+    const formData = new FormData()
+    formData.append('document', file, file.name)
+    return this.http.post(
+      this.getResourceUrl(documentId, 'update_version'),
+      formData,
+      { reportProgress: true, observe: 'events' }
+    )
+  }
+
   getNextAsn(): Observable<number> {
     return this.http.get<number>(this.getResourceUrl(null, 'next_asn'))
   }