From dac15ea6bec32605f95d9cc59f987fcdd0e61f5f Mon Sep 17 00:00:00 2001
From: shamoon <4887959+shamoon@users.noreply.github.com>
Date: Mon, 8 Sep 2025 08:13:55 -0700
Subject: [PATCH] Super basic UI stuff
[ci skip]
---
.../document-detail.component.html | 27 ++++++++
.../document-detail.component.ts | 69 ++++++++++++++++++-
src-ui/src/app/data/document.ts | 4 ++
.../src/app/services/rest/document.service.ts | 10 +++
4 files changed, 107 insertions(+), 3 deletions(-)
diff --git a/src-ui/src/app/components/document-detail/document-detail.component.html b/src-ui/src/app/components/document-detail/document-detail.component.html
index c926c82d9..80a89f180 100644
--- a/src-ui/src/app/components/document-detail/document-detail.component.html
+++ b/src-ui/src/app/components/document-detail/document-detail.component.html
@@ -44,6 +44,27 @@
}
+ @if (document?.versions?.length > 0) {
+
+
+
+
+ @for (vid of document.versions; track vid) {
+
+ }
+
+
+ }
+
diff --git a/src-ui/src/app/components/document-detail/document-detail.component.ts b/src-ui/src/app/components/document-detail/document-detail.component.ts
index d139550c0..1c7386ea5 100644
--- a/src-ui/src/app/components/document-detail/document-detail.component.ts
+++ b/src-ui/src/app/components/document-detail/document-detail.component.ts
@@ -220,6 +220,8 @@ export class DocumentDetailComponent
titleSubject: Subject = 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
@@ -268,6 +270,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 (
@@ -396,7 +399,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(
@@ -411,7 +417,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(
@@ -632,6 +638,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) {
@@ -696,6 +704,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
}
@@ -1043,10 +1075,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
diff --git a/src-ui/src/app/data/document.ts b/src-ui/src/app/data/document.ts
index 8aae31945..8b7d32876 100644
--- a/src-ui/src/app/data/document.ts
+++ b/src-ui/src/app/data/document.ts
@@ -159,6 +159,10 @@ export interface Document extends ObjectWithPermissions {
page_count?: number
+ // Versioning
+ head_version?: number
+ versions?: number[]
+
// Frontend only
__changedFields?: string[]
}
diff --git a/src-ui/src/app/services/rest/document.service.ts b/src-ui/src/app/services/rest/document.service.ts
index 4f52633ea..b70e953b9 100644
--- a/src-ui/src/app/services/rest/document.service.ts
+++ b/src-ui/src/app/services/rest/document.service.ts
@@ -184,6 +184,16 @@ export class DocumentService extends AbstractPaperlessService {
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 {
return this.http.get(this.getResourceUrl(null, 'next_asn'))
}