diff --git a/src-ui/src/app/components/common/filterable-dropdown/filterable-dropdown.component.html b/src-ui/src/app/components/common/filterable-dropdown/filterable-dropdown.component.html
index 58c831456..f9bf78b6c 100644
--- a/src-ui/src/app/components/common/filterable-dropdown/filterable-dropdown.component.html
+++ b/src-ui/src/app/components/common/filterable-dropdown/filterable-dropdown.component.html
@@ -19,6 +19,12 @@
+
diff --git a/src-ui/src/app/components/common/filterable-dropdown/filterable-dropdown.component.ts b/src-ui/src/app/components/common/filterable-dropdown/filterable-dropdown.component.ts
index 05c77cd10..3f0b3c3ad 100644
--- a/src-ui/src/app/components/common/filterable-dropdown/filterable-dropdown.component.ts
+++ b/src-ui/src/app/components/common/filterable-dropdown/filterable-dropdown.component.ts
@@ -69,12 +69,18 @@ export class FilterableDropdownComponent {
@Input()
singular: boolean = false
+ @Input()
+ showRemoveAll: boolean = false
+
@Output()
toggle = new EventEmitter()
@Output()
open = new EventEmitter()
+ @Output()
+ removeAll = new EventEmitter()
+
@Output()
editingComplete = new EventEmitter()
@@ -110,7 +116,7 @@ export class FilterableDropdownComponent {
this.open.next()
} else {
this.filterText = ''
- if (this.type == FilterableDropdownType.Editing) this.editingComplete.emit(this.itemsSelected)
+ if (this.type == FilterableDropdownType.Editing) this.editingComplete.emit(this.toggleableItems)
}
}
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 35b6d5197..589739d78 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
@@ -29,9 +29,33 @@
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 053af8157..31f7b5566 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
@@ -11,6 +11,11 @@ import { DocumentService } from 'src/app/services/rest/document.service';
import { FilterableDropdownType } from 'src/app/components/common/filterable-dropdown/filterable-dropdown.component';
import { ToggleableItem, ToggleableItemState } from 'src/app/components/common/filterable-dropdown/toggleable-dropdown-button/toggleable-dropdown-button.component';
+interface ChangedItems {
+ itemsToAdd: any[],
+ itemsToRemove: any[]
+}
+
@Component({
selector: 'app-bulk-editor',
templateUrl: './bulk-editor.component.html',
@@ -33,6 +38,9 @@ export class BulkEditorComponent {
@Output()
selectNone = new EventEmitter()
+ @Output()
+ setTags = new EventEmitter()
+
@Output()
setCorrespondent = new EventEmitter()
@@ -40,18 +48,24 @@ export class BulkEditorComponent {
setDocumentType = new EventEmitter()
@Output()
- setTags = new EventEmitter()
+ delete = new EventEmitter()
@Output()
- delete = new EventEmitter()
+ removeTags = new EventEmitter()
+
+ @Output()
+ removeCorrespondents = new EventEmitter()
+
+ @Output()
+ removeDocumentTypes = new EventEmitter()
tags: PaperlessTag[]
correspondents: PaperlessCorrespondent[]
documentTypes: PaperlessDocumentType[]
- private initiallySelectedTagsToggleableItems: ToggleableItem[]
- private initiallySelectedCorrespondentsToggleableItems: ToggleableItem[]
- private initiallySelectedDocumentTypesToggleableItems: ToggleableItem[]
+ private initialTagsToggleableItems: ToggleableItem[]
+ private initialCorrespondentsToggleableItems: ToggleableItem[]
+ private initialDocumentTypesToggleableItems: ToggleableItem[]
dropdownTypes = FilterableDropdownType
@@ -59,6 +73,7 @@ export class BulkEditorComponent {
return this.selectedDocuments.size > this.viewDocuments.length || !Array.from(this.selectedDocuments).every(sd => this.viewDocuments.find(d => d.id == sd))
}
+ private _tagsToggleableItems: ToggleableItem[]
get tagsToggleableItems(): ToggleableItem[] {
let tagsToggleableItems = []
let selectedDocuments: PaperlessDocument[] = this.viewDocuments.filter(d => this.selectedDocuments.has(d.id))
@@ -71,9 +86,11 @@ export class BulkEditorComponent {
else if (selectedDocumentsWithTag.length > 0 && selectedDocumentsWithTag.length < selectedDocuments.length) state = ToggleableItemState.PartiallySelected
tagsToggleableItems.push({item: t, state: state, count: selectedDocumentsWithTag.length})
})
+ this._tagsToggleableItems = tagsToggleableItems
return tagsToggleableItems
}
+ private _correspondentsToggleableItems: ToggleableItem[]
get correspondentsToggleableItems(): ToggleableItem[] {
let correspondentsToggleableItems = []
let selectedDocuments: PaperlessDocument[] = this.viewDocuments.filter(d => this.selectedDocuments.has(d.id))
@@ -86,9 +103,11 @@ export class BulkEditorComponent {
else if (selectedDocumentsWithCorrespondent.length > 0 && selectedDocumentsWithCorrespondent.length < selectedDocuments.length) state = ToggleableItemState.PartiallySelected
correspondentsToggleableItems.push({item: c, state: state, count: selectedDocumentsWithCorrespondent.length})
})
+ this._correspondentsToggleableItems = correspondentsToggleableItems
return correspondentsToggleableItems
}
+ private _documentTypesToggleableItems: ToggleableItem[]
get documentTypesToggleableItems(): ToggleableItem[] {
let documentTypesToggleableItems = []
let selectedDocuments: PaperlessDocument[] = this.viewDocuments.filter(d => this.selectedDocuments.has(d.id))
@@ -101,6 +120,7 @@ export class BulkEditorComponent {
else if (selectedDocumentsWithDocumentType.length > 0 && selectedDocumentsWithDocumentType.length < selectedDocuments.length) state = ToggleableItemState.PartiallySelected
documentTypesToggleableItems.push({item: dt, state: state, count: selectedDocumentsWithDocumentType.length})
})
+ this._documentTypesToggleableItems = documentTypesToggleableItems
return documentTypesToggleableItems
}
@@ -118,39 +138,45 @@ export class BulkEditorComponent {
}
tagsDropdownOpen() {
- this.initiallySelectedTagsToggleableItems = this.tagsToggleableItems.filter(tti => tti.state == ToggleableItemState.Selected)
+ this.initialTagsToggleableItems = this._tagsToggleableItems
}
correspondentsDropdownOpen() {
- this.initiallySelectedCorrespondentsToggleableItems = this.correspondentsToggleableItems.filter(cti => cti.state == ToggleableItemState.Selected)
+ this.initialCorrespondentsToggleableItems = this._correspondentsToggleableItems
}
documentTypesDropdownOpen() {
- this.initiallySelectedDocumentTypesToggleableItems = this.documentTypesToggleableItems.filter(dtti => dtti.state == ToggleableItemState.Selected)
+ this.initialDocumentTypesToggleableItems = this._documentTypesToggleableItems
}
- applyTags(selectedTags: PaperlessTag[]) {
- let unchanged = this.equateItemsToToggleableItems(selectedTags, this.initiallySelectedTagsToggleableItems)
- if (!unchanged) this.setTags.emit(selectedTags)
- this.initiallySelectedTagsToggleableItems = []
+ applyTags(newTagsToggleableItems: ToggleableItem[], forceApply:boolean = false) {
+ let changedTags = this.checkForChangedItems(this.initialTagsToggleableItems, newTagsToggleableItems)
+ if (changedTags.itemsToAdd.length > 0) this.setTags.emit(changedTags.itemsToAdd)
+ if (changedTags.itemsToRemove.length > 0) this.removeTags.emit(changedTags.itemsToRemove)
}
- applyCorrespondent(selectedCorrespondents: PaperlessCorrespondent[]) {
- let unchanged = this.equateItemsToToggleableItems(selectedCorrespondents, this.initiallySelectedCorrespondentsToggleableItems)
- if (!unchanged) this.setCorrespondent.emit(selectedCorrespondents?.length > 0 ? selectedCorrespondents.shift() : null)
- this.initiallySelectedCorrespondentsToggleableItems = []
+ applyCorrespondent(newCorrespondentsToggleableItems: ToggleableItem[], forceApply:boolean = false) {
+ let changedCorrespondents = this.checkForChangedItems(this.initialCorrespondentsToggleableItems, newCorrespondentsToggleableItems)
+ if (changedCorrespondents.itemsToAdd.length > 0) this.setCorrespondent.emit(changedCorrespondents.itemsToAdd)
+ else if (changedCorrespondents.itemsToRemove.length > 0) this.removeCorrespondents.emit(changedCorrespondents.itemsToRemove)
}
- applyDocumentType(selectedDocumentTypes: PaperlessDocumentType[]) {
- let unchanged = this.equateItemsToToggleableItems(selectedDocumentTypes, this.initiallySelectedDocumentTypesToggleableItems)
- if (!unchanged) this.setDocumentType.emit(selectedDocumentTypes.length > 0 ? selectedDocumentTypes.shift() : null)
- this.initiallySelectedDocumentTypesToggleableItems = []
+ applyDocumentType(newDocumentTypesToggleableItems: ToggleableItem[], forceApply:boolean = false) {
+ let changedDocumentTypes = this.checkForChangedItems(this.initialDocumentTypesToggleableItems, newDocumentTypesToggleableItems)
+ if (changedDocumentTypes.itemsToAdd.length > 0) this.setDocumentType.emit(changedDocumentTypes.itemsToAdd)
+ else if (changedDocumentTypes.itemsToRemove.length > 0) this.removeDocumentTypes.emit(changedDocumentTypes.itemsToRemove)
}
- equateItemsToToggleableItems(items: ObjectWithId[], toggleableItems: ToggleableItem[]): boolean {
- // either both empty or all items must in toggleableItems and vice-versa
- return (toggleableItems.length == 0 && items.length == 0) ||
- (items.every(i => toggleableItems.find(ti => ti.item.id == i.id) !== undefined) && toggleableItems.every(ti => items.find(i => i.id == ti.item.id) !== undefined))
+ checkForChangedItems(toggleableItemsA: ToggleableItem[], toggleableItemsB: ToggleableItem[]): ChangedItems {
+ let itemsToAdd: any[] = []
+ let itemsToRemove: any[] = []
+ toggleableItemsA.forEach(oldItem => {
+ let newItem = toggleableItemsB.find(nTTI => nTTI.item.id == oldItem.item.id)
+
+ if (newItem.state == ToggleableItemState.Selected && (oldItem.state == ToggleableItemState.PartiallySelected || oldItem.state == ToggleableItemState.NotSelected)) itemsToAdd.push(newItem.item)
+ else if (newItem.state == ToggleableItemState.NotSelected && (oldItem.state == ToggleableItemState.Selected || oldItem.state == ToggleableItemState.PartiallySelected)) itemsToRemove.push(newItem.item)
+ })
+ return { itemsToAdd: itemsToAdd, itemsToRemove: itemsToRemove }
}
applyDelete() {
diff --git a/src-ui/src/app/components/document-list/document-list.component.html b/src-ui/src/app/components/document-list/document-list.component.html
index 36c6daa5b..d685e6770 100644
--- a/src-ui/src/app/components/document-list/document-list.component.html
+++ b/src-ui/src/app/components/document-list/document-list.component.html
@@ -87,8 +87,11 @@
(selectAll)="list.selectAll()"
(selectNone)="list.selectNone()"
(setTags)="bulkSetTags($event)"
+ (removeTags)="bulkRemoveTags($event)"
(setCorrespondent)="bulkSetCorrespondent($event)"
+ (removeCorrespondents)="bulkRemoveCorrespondents($event)"
(setDocumentType)="bulkSetDocumentType($event)"
+ (removeDocumentTypes)="bulkRemoveDocumentTypes($event)"
(delete)="bulkDelete()">
diff --git a/src-ui/src/app/components/document-list/document-list.component.ts b/src-ui/src/app/components/document-list/document-list.component.ts
index 2575e4b49..60d763f13 100644
--- a/src-ui/src/app/components/document-list/document-list.component.ts
+++ b/src-ui/src/app/components/document-list/document-list.component.ts
@@ -158,7 +158,7 @@ export class DocumentListComponent implements OnInit {
bulkSetCorrespondent(correspondent: PaperlessCorrespondent) {
let modal = this.modalService.open(ConfirmDialogComponent, {backdrop: 'static'})
- modal.componentInstance.title = "Confirm correspondent assignment"
+ modal.componentInstance.title = "Confirm Correspondent assignment"
let messageFragment = correspondent ? `assign the correspondent ${correspondent.name} to` : `remove all correspondents from`
modal.componentInstance.message = `This operation will ${messageFragment} all ${this.list.selected.size} selected document(s).`
modal.componentInstance.btnClass = "btn-warning"
@@ -172,6 +172,22 @@ export class DocumentListComponent implements OnInit {
})
}
+ bulkRemoveCorrespondents(correspondents: PaperlessCorrespondent[]) {
+ let modal = this.modalService.open(ConfirmDialogComponent, {backdrop: 'static'})
+ modal.componentInstance.title = "Confirm Correspondent Removal"
+ modal.componentInstance.message = `This operation will remove the correspondent(s) ${correspondents.map(t => t.name).join(', ')} from all ${this.list.selected.size} selected document(s).`
+ modal.componentInstance.btnClass = "btn-warning"
+ modal.componentInstance.btnCaption = "Confirm"
+ modal.componentInstance.confirmClicked.subscribe(() => {
+ // TODO: API endpoint for remove multiple correspondents
+ this.executeBulkOperation('remove_correspondents', {"correspondents": correspondents.map(t => t.id)}).subscribe(
+ response => {
+ modal.close()
+ }
+ )
+ })
+ }
+
bulkSetDocumentType(documentType: PaperlessDocumentType) {
let modal = this.modalService.open(ConfirmDialogComponent, {backdrop: 'static'})
modal.componentInstance.title = "Confirm Document Type assignment"
@@ -188,6 +204,22 @@ export class DocumentListComponent implements OnInit {
})
}
+ bulkRemoveDocumentTypes(documentTypes: PaperlessDocumentType[]) {
+ let modal = this.modalService.open(ConfirmDialogComponent, {backdrop: 'static'})
+ modal.componentInstance.title = "Confirm Document Type Removal"
+ modal.componentInstance.message = `This operation will remove the document type(s) ${documentTypes.map(t => t.name).join(', ')} all ${this.list.selected.size} selected document(s).`
+ modal.componentInstance.btnClass = "btn-warning"
+ modal.componentInstance.btnCaption = "Confirm"
+ modal.componentInstance.confirmClicked.subscribe(() => {
+ // TODO: API endpoint for remove multiple document types
+ this.executeBulkOperation('remove_document_types', {"document_types": documentTypes.map(t => t.id)}).subscribe(
+ response => {
+ modal.close()
+ }
+ )
+ })
+ }
+
bulkSetTags(tags: PaperlessTag[]) {
let modal = this.modalService.open(ConfirmDialogComponent, {backdrop: 'static'})
modal.componentInstance.title = "Confirm Tags assignment"
@@ -197,7 +229,23 @@ export class DocumentListComponent implements OnInit {
modal.componentInstance.btnCaption = "Confirm"
modal.componentInstance.confirmClicked.subscribe(() => {
// TODO: API endpoint for set multiple tags
- this.executeBulkOperation('set_tags', {"document_type": tags ? tags.map(t => t.id) : null}).subscribe(
+ this.executeBulkOperation('set_tags', {"tags": tags ? tags.map(t => t.id) : null}).subscribe(
+ response => {
+ modal.close()
+ }
+ )
+ })
+ }
+
+ bulkRemoveTags(tags: PaperlessTag[]) {
+ let modal = this.modalService.open(ConfirmDialogComponent, {backdrop: 'static'})
+ modal.componentInstance.title = "Confirm Tags Removal"
+ modal.componentInstance.message = `This operation will remove the tags ${tags.map(t => t.name).join(', ')} from all ${this.list.selected.size} selected document(s).`
+ modal.componentInstance.btnClass = "btn-warning"
+ modal.componentInstance.btnCaption = "Confirm"
+ modal.componentInstance.confirmClicked.subscribe(() => {
+ // TODO: API endpoint for remove multiple tags
+ this.executeBulkOperation('remove_tags', {"tags": tags.map(t => t.id)}).subscribe(
response => {
modal.close()
}