mirror of
				https://github.com/paperless-ngx/paperless-ngx.git
				synced 2025-10-30 03:56:23 -05:00 
			
		
		
		
	Support excluding tags
This commit is contained in:
		| @@ -19,7 +19,7 @@ | |||||||
|       </div> |       </div> | ||||||
|       <div *ngIf="selectionModel.items" class="items"> |       <div *ngIf="selectionModel.items" class="items"> | ||||||
|         <ng-container *ngFor="let item of selectionModel.itemsSorted | filter: filterText"> |         <ng-container *ngFor="let item of selectionModel.itemsSorted | filter: filterText"> | ||||||
|           <app-toggleable-dropdown-button *ngIf="allowSelectNone || item.id" [item]="item" [state]="selectionModel.get(item.id)" (toggle)="selectionModel.toggle(item.id)"></app-toggleable-dropdown-button> |           <app-toggleable-dropdown-button *ngIf="allowSelectNone || item.id" [item]="item" [state]="selectionModel.get(item.id)" (toggle)="selectionModel.toggle(item.id)" (exclude)="selectionModel.exclude(item.id)"></app-toggleable-dropdown-button> | ||||||
|         </ng-container> |         </ng-container> | ||||||
|       </div> |       </div> | ||||||
|       <button *ngIf="editing" class="list-group-item list-group-item-action bg-light" (click)="applyClicked()" [disabled]="!selectionModel.isDirty()"> |       <button *ngIf="editing" class="list-group-item list-group-item-action bg-light" (click)="applyClicked()" [disabled]="!selectionModel.isDirty()"> | ||||||
|   | |||||||
| @@ -43,6 +43,10 @@ export class FilterableDropdownSelectionModel { | |||||||
|     return this.items.filter(i => this.temporarySelectionStates.get(i.id) == ToggleableItemState.Selected) |     return this.items.filter(i => this.temporarySelectionStates.get(i.id) == ToggleableItemState.Selected) | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   getExcludedItems() { | ||||||
|  |     return this.items.filter(i => this.temporarySelectionStates.get(i.id) == ToggleableItemState.Excluded) | ||||||
|  |   } | ||||||
|  |  | ||||||
|   set(id: number, state: ToggleableItemState, fireEvent = true) { |   set(id: number, state: ToggleableItemState, fireEvent = true) { | ||||||
|     if (state == ToggleableItemState.NotSelected) { |     if (state == ToggleableItemState.NotSelected) { | ||||||
|       this.temporarySelectionStates.delete(id) |       this.temporarySelectionStates.delete(id) | ||||||
| @@ -56,9 +60,9 @@ export class FilterableDropdownSelectionModel { | |||||||
|  |  | ||||||
|   toggle(id: number, fireEvent = true) { |   toggle(id: number, fireEvent = true) { | ||||||
|     let state = this.temporarySelectionStates.get(id) |     let state = this.temporarySelectionStates.get(id) | ||||||
|     if (state == null || state != ToggleableItemState.Selected) { |     if (state == null || (state != ToggleableItemState.Selected && state != ToggleableItemState.Excluded)) { | ||||||
|       this.temporarySelectionStates.set(id, ToggleableItemState.Selected) |       this.temporarySelectionStates.set(id, ToggleableItemState.Selected) | ||||||
|     } else if (state == ToggleableItemState.Selected) { |     } else if (state == ToggleableItemState.Selected || state == ToggleableItemState.Excluded) { | ||||||
|       this.temporarySelectionStates.delete(id) |       this.temporarySelectionStates.delete(id) | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -83,7 +87,30 @@ export class FilterableDropdownSelectionModel { | |||||||
|     if (fireEvent) { |     if (fireEvent) { | ||||||
|       this.changed.next(this) |       this.changed.next(this) | ||||||
|     } |     } | ||||||
|      |  | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   exclude(id: number, fireEvent:boolean = true) { | ||||||
|  |     console.log('exclude', id, fireEvent); | ||||||
|  |  | ||||||
|  |     let state = this.temporarySelectionStates.get(id) | ||||||
|  |     if (state == null || state != ToggleableItemState.Excluded) { | ||||||
|  |       this.temporarySelectionStates.set(id, ToggleableItemState.Excluded) | ||||||
|  |     } else if (state == ToggleableItemState.Excluded) { | ||||||
|  |       this.temporarySelectionStates.delete(id) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     if (!this.multiple) { | ||||||
|  |       for (let key of this.temporarySelectionStates.keys()) { | ||||||
|  |         if (key != id) { | ||||||
|  |           this.temporarySelectionStates.delete(key) | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     if (fireEvent) { | ||||||
|  |       this.changed.next(this) | ||||||
|  |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   private getNonTemporary(id: number) { |   private getNonTemporary(id: number) { | ||||||
|   | |||||||
| @@ -1,4 +1,4 @@ | |||||||
| <button class="list-group-item list-group-item-action d-flex align-items-center p-2 border-top-0 border-left-0 border-right-0 border-bottom" role="menuitem" (click)="toggleItem()"> | <button class="list-group-item list-group-item-action d-flex align-items-center p-2 border-top-0 border-left-0 border-right-0 border-bottom" role="menuitem" (click)="toggleItem($event)"> | ||||||
|   <div class="selected-icon mr-1"> |   <div class="selected-icon mr-1"> | ||||||
|     <ng-container *ngIf="isChecked()"> |     <ng-container *ngIf="isChecked()"> | ||||||
|       <svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" class="bi bi-check" viewBox="0 0 16 16"> |       <svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" class="bi bi-check" viewBox="0 0 16 16"> | ||||||
| @@ -10,7 +10,11 @@ | |||||||
|         <path d="M4 8a.5.5 0 0 1 .5-.5h7a.5.5 0 0 1 0 1h-7A.5.5 0 0 1 4 8z"/> |         <path d="M4 8a.5.5 0 0 1 .5-.5h7a.5.5 0 0 1 0 1h-7A.5.5 0 0 1 4 8z"/> | ||||||
|       </svg> |       </svg> | ||||||
|     </ng-container> |     </ng-container> | ||||||
|      |     <ng-container *ngIf="isExcluded()"> | ||||||
|  |       <svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" class="bi bi-x" viewBox="0 0 16 16"> | ||||||
|  |         <path d="M4.646 4.646a.5.5 0 0 1 .708 0L8 7.293l2.646-2.647a.5.5 0 0 1 .708.708L8.707 8l2.647 2.646a.5.5 0 0 1-.708.708L8 8.707l-2.646 2.647a.5.5 0 0 1-.708-.708L7.293 8 4.646 5.354a.5.5 0 0 1 0-.708z"/> | ||||||
|  |       </svg> | ||||||
|  |     </ng-container> | ||||||
|   </div> |   </div> | ||||||
|   <div class="mr-1"> |   <div class="mr-1"> | ||||||
|     <app-tag *ngIf="isTag; else displayName" [tag]="item" [clickable]="true" linkTitle="Filter by tag"></app-tag> |     <app-tag *ngIf="isTag; else displayName" [tag]="item" [clickable]="true" linkTitle="Filter by tag"></app-tag> | ||||||
|   | |||||||
| @@ -10,7 +10,8 @@ export interface ToggleableItem { | |||||||
| export enum ToggleableItemState { | export enum ToggleableItemState { | ||||||
|   NotSelected = 0, |   NotSelected = 0, | ||||||
|   Selected = 1, |   Selected = 1, | ||||||
|   PartiallySelected = 2 |   PartiallySelected = 2, | ||||||
|  |   Excluded = 3 | ||||||
| } | } | ||||||
|  |  | ||||||
| @Component({ | @Component({ | ||||||
| @@ -32,12 +33,19 @@ export class ToggleableDropdownButtonComponent { | |||||||
|   @Output() |   @Output() | ||||||
|   toggle = new EventEmitter() |   toggle = new EventEmitter() | ||||||
|  |  | ||||||
|  |   @Output() | ||||||
|  |   exclude = new EventEmitter() | ||||||
|  |  | ||||||
|   get isTag(): boolean { |   get isTag(): boolean { | ||||||
|     return 'is_inbox_tag' in this.item |     return 'is_inbox_tag' in this.item | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   toggleItem(): void { |   toggleItem(event: MouseEvent): void { | ||||||
|     this.toggle.emit() |     if (event.altKey) { | ||||||
|  |       this.exclude.emit() | ||||||
|  |     } else { | ||||||
|  |       this.toggle.emit() | ||||||
|  |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   isChecked() { |   isChecked() { | ||||||
| @@ -48,4 +56,7 @@ export class ToggleableDropdownButtonComponent { | |||||||
|     return this.state == ToggleableItemState.PartiallySelected |     return this.state == ToggleableItemState.PartiallySelected | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   isExcluded() { | ||||||
|  |     return this.state == ToggleableItemState.Excluded | ||||||
|  |   } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -8,7 +8,7 @@ import { DocumentTypeService } from 'src/app/services/rest/document-type.service | |||||||
| import { TagService } from 'src/app/services/rest/tag.service'; | import { TagService } from 'src/app/services/rest/tag.service'; | ||||||
| import { CorrespondentService } from 'src/app/services/rest/correspondent.service'; | import { CorrespondentService } from 'src/app/services/rest/correspondent.service'; | ||||||
| import { FilterRule } from 'src/app/data/filter-rule'; | import { FilterRule } from 'src/app/data/filter-rule'; | ||||||
| import { FILTER_ADDED_AFTER, FILTER_ADDED_BEFORE, FILTER_CORRESPONDENT, FILTER_CREATED_AFTER, FILTER_CREATED_BEFORE, FILTER_DOCUMENT_TYPE, FILTER_HAS_ANY_TAG, FILTER_HAS_TAG, FILTER_TITLE } from 'src/app/data/filter-rule-type'; | import { FILTER_ADDED_AFTER, FILTER_ADDED_BEFORE, FILTER_CORRESPONDENT, FILTER_CREATED_AFTER, FILTER_CREATED_BEFORE, FILTER_DOCUMENT_TYPE, FILTER_HAS_ANY_TAG, FILTER_HAS_TAG, FILTER_DOES_NOT_HAVE_TAG, FILTER_TITLE } from 'src/app/data/filter-rule-type'; | ||||||
| import { FilterableDropdownSelectionModel } from '../../common/filterable-dropdown/filterable-dropdown.component'; | import { FilterableDropdownSelectionModel } from '../../common/filterable-dropdown/filterable-dropdown.component'; | ||||||
| import { ToggleableItemState } from '../../common/filterable-dropdown/toggleable-dropdown-button/toggleable-dropdown-button.component'; | import { ToggleableItemState } from '../../common/filterable-dropdown/toggleable-dropdown-button/toggleable-dropdown-button.component'; | ||||||
|  |  | ||||||
| @@ -107,6 +107,9 @@ export class FilterEditorComponent implements OnInit, OnDestroy { | |||||||
|         case FILTER_HAS_ANY_TAG: |         case FILTER_HAS_ANY_TAG: | ||||||
|           this.tagSelectionModel.set(null, ToggleableItemState.Selected, false) |           this.tagSelectionModel.set(null, ToggleableItemState.Selected, false) | ||||||
|           break |           break | ||||||
|  |         case FILTER_DOES_NOT_HAVE_TAG: | ||||||
|  |           this.tagSelectionModel.set(rule.value ? +rule.value : null, ToggleableItemState.Excluded, false) | ||||||
|  |           break | ||||||
|         case FILTER_CORRESPONDENT: |         case FILTER_CORRESPONDENT: | ||||||
|           this.correspondentSelectionModel.set(rule.value ? +rule.value : null, ToggleableItemState.Selected, false) |           this.correspondentSelectionModel.set(rule.value ? +rule.value : null, ToggleableItemState.Selected, false) | ||||||
|           break |           break | ||||||
| @@ -128,6 +131,9 @@ export class FilterEditorComponent implements OnInit, OnDestroy { | |||||||
|       this.tagSelectionModel.getSelectedItems().filter(tag => tag.id).forEach(tag => { |       this.tagSelectionModel.getSelectedItems().filter(tag => tag.id).forEach(tag => { | ||||||
|         filterRules.push({rule_type: FILTER_HAS_TAG, value: tag.id?.toString()}) |         filterRules.push({rule_type: FILTER_HAS_TAG, value: tag.id?.toString()}) | ||||||
|       }) |       }) | ||||||
|  |       this.tagSelectionModel.getExcludedItems().filter(tag => tag.id).forEach(tag => { | ||||||
|  |         filterRules.push({rule_type: FILTER_DOES_NOT_HAVE_TAG, value: tag.id?.toString()}) | ||||||
|  |       }) | ||||||
|     } |     } | ||||||
|     this.correspondentSelectionModel.getSelectedItems().forEach(correspondent => { |     this.correspondentSelectionModel.getSelectedItems().forEach(correspondent => { | ||||||
|       filterRules.push({rule_type: FILTER_CORRESPONDENT, value: correspondent.id?.toString()}) |       filterRules.push({rule_type: FILTER_CORRESPONDENT, value: correspondent.id?.toString()}) | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Michael Shamoon
					Michael Shamoon