diff --git a/src-ui/src/app/components/document-list/bulk-editor/bulk-editor.component.html b/src-ui/src/app/components/document-list/bulk-editor/bulk-editor.component.html
index cb4e200dd..cca53552c 100644
--- a/src-ui/src/app/components/document-list/bulk-editor/bulk-editor.component.html
+++ b/src-ui/src/app/components/document-list/bulk-editor/bulk-editor.component.html
@@ -69,7 +69,7 @@
-
-
+
+
+
+
+
+
+
+
diff --git a/src-ui/src/app/components/document-list/bulk-editor/bulk-editor.component.scss b/src-ui/src/app/components/document-list/bulk-editor/bulk-editor.component.scss
index e69de29bb..939f2c790 100644
--- a/src-ui/src/app/components/document-list/bulk-editor/bulk-editor.component.scss
+++ b/src-ui/src/app/components/document-list/bulk-editor/bulk-editor.component.scss
@@ -0,0 +1,7 @@
+.dropdown-toggle-split {
+ --bs-border-radius: .25rem;
+}
+
+.dropdown-menu{
+ --bs-dropdown-min-width: 12rem;
+}
diff --git a/src-ui/src/app/components/document-list/bulk-editor/bulk-editor.component.ts b/src-ui/src/app/components/document-list/bulk-editor/bulk-editor.component.ts
index 32ad28ba7..a69f1416c 100644
--- a/src-ui/src/app/components/document-list/bulk-editor/bulk-editor.component.ts
+++ b/src-ui/src/app/components/document-list/bulk-editor/bulk-editor.component.ts
@@ -28,6 +28,8 @@ import { SETTINGS_KEYS } from 'src/app/data/paperless-uisettings'
import { ComponentWithPermissions } from '../../with-permissions/with-permissions.component'
import { PermissionsDialogComponent } from '../../common/permissions-dialog/permissions-dialog.component'
import { PermissionsService } from 'src/app/services/permissions.service'
+import { FormControl, FormGroup } from '@angular/forms'
+import { first, Subject, takeUntil } from 'rxjs'
@Component({
selector: 'app-bulk-editor',
@@ -46,6 +48,14 @@ export class BulkEditorComponent extends ComponentWithPermissions {
storagePathsSelectionModel = new FilterableDropdownSelectionModel()
awaitingDownload: boolean
+ unsubscribeNotifier: Subject = new Subject()
+
+ downloadForm = new FormGroup({
+ downloadFileTypeArchive: new FormControl(true),
+ downloadFileTypeOriginals: new FormControl(false),
+ downloadUseFormatting: new FormControl(false),
+ })
+
constructor(
private documentTypeService: DocumentTypeService,
private tagService: TagService,
@@ -91,16 +101,46 @@ export class BulkEditorComponent extends ComponentWithPermissions {
ngOnInit() {
this.tagService
.listAll()
+ .pipe(first())
.subscribe((result) => (this.tags = result.results))
this.correspondentService
.listAll()
+ .pipe(first())
.subscribe((result) => (this.correspondents = result.results))
this.documentTypeService
.listAll()
+ .pipe(first())
.subscribe((result) => (this.documentTypes = result.results))
this.storagePathService
.listAll()
+ .pipe(first())
.subscribe((result) => (this.storagePaths = result.results))
+
+ this.downloadForm
+ .get('downloadFileTypeArchive')
+ .valueChanges.pipe(takeUntil(this.unsubscribeNotifier))
+ .subscribe((newValue) => {
+ if (!newValue) {
+ this.downloadForm
+ .get('downloadFileTypeOriginals')
+ .patchValue(true, { emitEvent: false })
+ }
+ })
+ this.downloadForm
+ .get('downloadFileTypeOriginals')
+ .valueChanges.pipe(takeUntil(this.unsubscribeNotifier))
+ .subscribe((newValue) => {
+ if (!newValue) {
+ this.downloadForm
+ .get('downloadFileTypeArchive')
+ .patchValue(true, { emitEvent: false })
+ }
+ })
+ }
+
+ ngOnDestroy(): void {
+ this.unsubscribeNotifier.next(this)
+ this.unsubscribeNotifier.complete()
}
private executeBulkOperation(modal, method: string, args) {
@@ -109,8 +149,9 @@ export class BulkEditorComponent extends ComponentWithPermissions {
}
this.documentService
.bulkEdit(Array.from(this.list.selected), method, args)
- .subscribe(
- (response) => {
+ .pipe(first())
+ .subscribe({
+ next: () => {
this.list.reload()
this.list.reduceSelectionToFilter()
this.list.selected.forEach((id) => {
@@ -120,7 +161,7 @@ export class BulkEditorComponent extends ComponentWithPermissions {
modal.close()
}
},
- (error) => {
+ error: (error) => {
if (modal) {
modal.componentInstance.buttonsEnabled = true
}
@@ -129,8 +170,8 @@ export class BulkEditorComponent extends ComponentWithPermissions {
error.error
)}`
)
- }
- )
+ },
+ })
}
private applySelectionData(
@@ -151,6 +192,7 @@ export class BulkEditorComponent extends ComponentWithPermissions {
openTagsDropdown() {
this.documentService
.getSelectionData(Array.from(this.list.selected))
+ .pipe(first())
.subscribe((s) => {
this.applySelectionData(s.selected_tags, this.tagSelectionModel)
})
@@ -159,6 +201,7 @@ export class BulkEditorComponent extends ComponentWithPermissions {
openDocumentTypeDropdown() {
this.documentService
.getSelectionData(Array.from(this.list.selected))
+ .pipe(first())
.subscribe((s) => {
this.applySelectionData(
s.selected_document_types,
@@ -170,6 +213,7 @@ export class BulkEditorComponent extends ComponentWithPermissions {
openCorrespondentDropdown() {
this.documentService
.getSelectionData(Array.from(this.list.selected))
+ .pipe(first())
.subscribe((s) => {
this.applySelectionData(
s.selected_correspondents,
@@ -181,6 +225,7 @@ export class BulkEditorComponent extends ComponentWithPermissions {
openStoragePathDropdown() {
this.documentService
.getSelectionData(Array.from(this.list.selected))
+ .pipe(first())
.subscribe((s) => {
this.applySelectionData(
s.selected_storage_paths,
@@ -257,12 +302,14 @@ export class BulkEditorComponent extends ComponentWithPermissions {
modal.componentInstance.btnClass = 'btn-warning'
modal.componentInstance.btnCaption = $localize`Confirm`
- modal.componentInstance.confirmClicked.subscribe(() => {
- this.executeBulkOperation(modal, 'modify_tags', {
- add_tags: changedTags.itemsToAdd.map((t) => t.id),
- remove_tags: changedTags.itemsToRemove.map((t) => t.id),
+ modal.componentInstance.confirmClicked
+ .pipe(takeUntil(this.unsubscribeNotifier))
+ .subscribe(() => {
+ this.executeBulkOperation(modal, 'modify_tags', {
+ add_tags: changedTags.itemsToAdd.map((t) => t.id),
+ remove_tags: changedTags.itemsToRemove.map((t) => t.id),
+ })
})
- })
} else {
this.executeBulkOperation(null, 'modify_tags', {
add_tags: changedTags.itemsToAdd.map((t) => t.id),
@@ -295,11 +342,13 @@ export class BulkEditorComponent extends ComponentWithPermissions {
}
modal.componentInstance.btnClass = 'btn-warning'
modal.componentInstance.btnCaption = $localize`Confirm`
- modal.componentInstance.confirmClicked.subscribe(() => {
- this.executeBulkOperation(modal, 'set_correspondent', {
- correspondent: correspondent ? correspondent.id : null,
+ modal.componentInstance.confirmClicked
+ .pipe(takeUntil(this.unsubscribeNotifier))
+ .subscribe(() => {
+ this.executeBulkOperation(modal, 'set_correspondent', {
+ correspondent: correspondent ? correspondent.id : null,
+ })
})
- })
} else {
this.executeBulkOperation(null, 'set_correspondent', {
correspondent: correspondent ? correspondent.id : null,
@@ -331,11 +380,13 @@ export class BulkEditorComponent extends ComponentWithPermissions {
}
modal.componentInstance.btnClass = 'btn-warning'
modal.componentInstance.btnCaption = $localize`Confirm`
- modal.componentInstance.confirmClicked.subscribe(() => {
- this.executeBulkOperation(modal, 'set_document_type', {
- document_type: documentType ? documentType.id : null,
+ modal.componentInstance.confirmClicked
+ .pipe(takeUntil(this.unsubscribeNotifier))
+ .subscribe(() => {
+ this.executeBulkOperation(modal, 'set_document_type', {
+ document_type: documentType ? documentType.id : null,
+ })
})
- })
} else {
this.executeBulkOperation(null, 'set_document_type', {
document_type: documentType ? documentType.id : null,
@@ -367,11 +418,13 @@ export class BulkEditorComponent extends ComponentWithPermissions {
}
modal.componentInstance.btnClass = 'btn-warning'
modal.componentInstance.btnCaption = $localize`Confirm`
- modal.componentInstance.confirmClicked.subscribe(() => {
- this.executeBulkOperation(modal, 'set_storage_path', {
- storage_path: storagePath ? storagePath.id : null,
+ modal.componentInstance.confirmClicked
+ .pipe(takeUntil(this.unsubscribeNotifier))
+ .subscribe(() => {
+ this.executeBulkOperation(modal, 'set_storage_path', {
+ storage_path: storagePath ? storagePath.id : null,
+ })
})
- })
} else {
this.executeBulkOperation(null, 'set_storage_path', {
storage_path: storagePath ? storagePath.id : null,
@@ -389,16 +442,30 @@ export class BulkEditorComponent extends ComponentWithPermissions {
modal.componentInstance.message = $localize`This operation cannot be undone.`
modal.componentInstance.btnClass = 'btn-danger'
modal.componentInstance.btnCaption = $localize`Delete document(s)`
- modal.componentInstance.confirmClicked.subscribe(() => {
- modal.componentInstance.buttonsEnabled = false
- this.executeBulkOperation(modal, 'delete', {})
- })
+ modal.componentInstance.confirmClicked
+ .pipe(takeUntil(this.unsubscribeNotifier))
+ .subscribe(() => {
+ modal.componentInstance.buttonsEnabled = false
+ this.executeBulkOperation(modal, 'delete', {})
+ })
}
- downloadSelected(content = 'archive') {
+ downloadSelected() {
this.awaitingDownload = true
+ let downloadFileType: string =
+ this.downloadForm.get('downloadFileTypeArchive').value &&
+ this.downloadForm.get('downloadFileTypeOriginals').value
+ ? 'both'
+ : this.downloadForm.get('downloadFileTypeArchive').value
+ ? 'archive'
+ : 'originals'
this.documentService
- .bulkDownload(Array.from(this.list.selected), content)
+ .bulkDownload(
+ Array.from(this.list.selected),
+ downloadFileType,
+ this.downloadForm.get('downloadUseFormatting').value
+ )
+ .pipe(first())
.subscribe((result: any) => {
saveAs(result, 'documents.zip')
this.awaitingDownload = false
@@ -414,10 +481,12 @@ export class BulkEditorComponent extends ComponentWithPermissions {
modal.componentInstance.message = $localize`This operation cannot be undone.`
modal.componentInstance.btnClass = 'btn-danger'
modal.componentInstance.btnCaption = $localize`Proceed`
- modal.componentInstance.confirmClicked.subscribe(() => {
- modal.componentInstance.buttonsEnabled = false
- this.executeBulkOperation(modal, 'redo_ocr', {})
- })
+ modal.componentInstance.confirmClicked
+ .pipe(takeUntil(this.unsubscribeNotifier))
+ .subscribe(() => {
+ modal.componentInstance.buttonsEnabled = false
+ this.executeBulkOperation(modal, 'redo_ocr', {})
+ })
}
setPermissions() {
diff --git a/src-ui/src/app/services/rest/document.service.ts b/src-ui/src/app/services/rest/document.service.ts
index 8e4d8eee9..4e7e97110 100644
--- a/src-ui/src/app/services/rest/document.service.ts
+++ b/src-ui/src/app/services/rest/document.service.ts
@@ -174,10 +174,18 @@ export class DocumentService extends AbstractPaperlessService
)
}
- bulkDownload(ids: number[], content = 'both') {
+ bulkDownload(
+ ids: number[],
+ content = 'both',
+ useFilenameFormatting: boolean = false
+ ) {
return this.http.post(
this.getResourceUrl(null, 'bulk_download'),
- { documents: ids, content: content },
+ {
+ documents: ids,
+ content: content,
+ follow_formatting: useFilenameFormatting,
+ },
{ responseType: 'blob' }
)
}
diff --git a/src-ui/src/theme.scss b/src-ui/src/theme.scss
index 3c166434d..69fbc68cc 100644
--- a/src-ui/src/theme.scss
+++ b/src-ui/src/theme.scss
@@ -216,9 +216,13 @@ $form-check-radio-checked-bg-image-dark: url("data:image/svg+xml,