mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2025-07-28 18:24:38 -05:00
Merge branch 'dev' into feature-permissions
This commit is contained in:
@@ -69,7 +69,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-auto ms-auto mb-2 mb-xl-0 d-flex">
|
||||
<div class="btn-toolbar me-2">
|
||||
<div class="btn-group btn-group-sm me-2">
|
||||
|
||||
<button type="button" class="btn btn-sm btn-outline-primary me-2" (click)="setPermissions()" [disabled]="!userOwnsAll">
|
||||
<svg width="1em" height="1em" viewBox="0 0 16 16" fill="currentColor">
|
||||
@@ -85,26 +85,57 @@
|
||||
<div class="d-none d-sm-inline"> <ng-container i18n>Actions</ng-container></div>
|
||||
</button>
|
||||
<div ngbDropdownMenu aria-labelledby="dropdownSelect" class="shadow">
|
||||
<button ngbDropdownItem [disabled]="awaitingDownload" (click)="downloadSelected()" i18n>
|
||||
Download
|
||||
<div *ngIf="awaitingDownload" class="spinner-border spinner-border-sm" role="status">
|
||||
<span class="visually-hidden">Preparing download...</span>
|
||||
</div>
|
||||
</button>
|
||||
<button ngbDropdownItem [disabled]="awaitingDownload" (click)="downloadSelected('originals')" i18n>
|
||||
Download originals
|
||||
<div *ngIf="awaitingDownload" class="spinner-border spinner-border-sm" role="status">
|
||||
<span class="visually-hidden">Preparing download...</span>
|
||||
</div>
|
||||
</button>
|
||||
<button ngbDropdownItem (click)="redoOcrSelected()" [disabled]="!userCanEditAll" i18n>Redo OCR</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button type="button" class="btn btn-sm btn-outline-danger" (click)="applyDelete()" *ifPermissions="{ action: PermissionAction.Delete, type: PermissionType.Document }" [disabled]="!userOwnsAll">
|
||||
<svg width="1em" height="1em" viewBox="0 0 16 16" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#trash" />
|
||||
</svg> <ng-container i18n>Delete</ng-container>
|
||||
</button>
|
||||
<div class="btn-group btn-group-sm me-2">
|
||||
<button class="btn btn-sm btn-outline-primary" [disabled]="awaitingDownload" (click)="downloadSelected()">
|
||||
<svg *ngIf="!awaitingDownload" class="toolbaricon" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#arrow-down" />
|
||||
</svg>
|
||||
<div *ngIf="awaitingDownload" class="spinner-border spinner-border-sm" role="status">
|
||||
<span class="visually-hidden">Preparing download...</span>
|
||||
</div>
|
||||
<div class="d-none d-sm-inline"> <ng-container i18n>Download</ng-container></div>
|
||||
</button>
|
||||
<div ngbDropdown class="me-2 d-flex btn-group" role="group">
|
||||
<button type="button" class="btn btn-sm btn-outline-primary dropdown-toggle-split rounded-end" ngbDropdownToggle></button>
|
||||
<div ngbDropdownMenu aria-labelledby="dropdownSelect" class="shadow">
|
||||
<form [formGroup]="downloadForm" class="px-3 py-1">
|
||||
<p class="mb-1" i18n>Include:</p>
|
||||
<div class="form-group ps-3 mb-2">
|
||||
<div class="form-check">
|
||||
<input type="checkbox" class="form-check-input" id="downloadFileType_archive" formControlName="downloadFileTypeArchive" />
|
||||
<label class="form-check-label" for="downloadFileType_archive" i18n>
|
||||
Archived files
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input type="checkbox" class="form-check-input" id="downloadFileType_originals" formControlName="downloadFileTypeOriginals" />
|
||||
<label class="form-check-label" for="downloadFileType_originals" i18n>
|
||||
Original files
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input type="checkbox" class="form-check-input" id="downloadUseFormatting" formControlName="downloadUseFormatting" />
|
||||
<label class="form-check-label" for="downloadUseFormatting" i18n>
|
||||
Use formatted filename
|
||||
</label>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="btn-group btn-group-sm me-2">
|
||||
<button type="button" class="btn btn-sm btn-outline-danger" (click)="applyDelete()" *ifPermissions="{ action: PermissionAction.Delete, type: PermissionType.Document }" [disabled]="!userOwnsAll">
|
||||
<svg width="1em" height="1em" viewBox="0 0 16 16" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#trash" />
|
||||
</svg> <ng-container i18n>Delete</ng-container>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@@ -0,0 +1,7 @@
|
||||
.dropdown-toggle-split {
|
||||
--bs-border-radius: .25rem;
|
||||
}
|
||||
|
||||
.dropdown-menu{
|
||||
--bs-dropdown-min-width: 12rem;
|
||||
}
|
||||
|
@@ -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<any> = 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() {
|
||||
|
Reference in New Issue
Block a user