mirror of
				https://github.com/paperless-ngx/paperless-ngx.git
				synced 2025-11-04 03:27:12 -05:00 
			
		
		
		
	Add support for 'any' ('OR') of tags when filtering
This commit is contained in:
		
							parent
							
								
									da60fc8150
								
							
						
					
					
						commit
						861b2efb1d
					
				@ -12,6 +12,16 @@
 | 
				
			|||||||
  </button>
 | 
					  </button>
 | 
				
			||||||
  <div class="dropdown-menu py-0 shadow" ngbDropdownMenu attr.aria-labelledby="dropdown{{title}}">
 | 
					  <div class="dropdown-menu py-0 shadow" ngbDropdownMenu attr.aria-labelledby="dropdown{{title}}">
 | 
				
			||||||
    <div class="list-group list-group-flush">
 | 
					    <div class="list-group list-group-flush">
 | 
				
			||||||
 | 
					      <div *ngIf="!editing && multiple" class="list-group-item d-flex">
 | 
				
			||||||
 | 
					        <div class="btn-group btn-group-xs btn-group-toggle flex-fill" ngbRadioGroup [(ngModel)]="selectionModel.logicalOperator">
 | 
				
			||||||
 | 
					          <label ngbButtonLabel class="btn btn-outline-primary">
 | 
				
			||||||
 | 
					            <input ngbButton type="radio" name="logicalOperator" value="and"> All
 | 
				
			||||||
 | 
					          </label>
 | 
				
			||||||
 | 
					          <label ngbButtonLabel class="btn btn-outline-primary">
 | 
				
			||||||
 | 
					            <input ngbButton type="radio" name="logicalOperator" value="or"> Any
 | 
				
			||||||
 | 
					          </label>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
      <div class="list-group-item">
 | 
					      <div class="list-group-item">
 | 
				
			||||||
        <div class="input-group input-group-sm">
 | 
					        <div class="input-group input-group-sm">
 | 
				
			||||||
          <input class="form-control" type="text" [(ngModel)]="filterText" [placeholder]="filterPlaceholder" (keyup.enter)="listFilterEnter()" #listFilterTextInput>
 | 
					          <input class="form-control" type="text" [(ngModel)]="filterText" [placeholder]="filterPlaceholder" (keyup.enter)="listFilterEnter()" #listFilterTextInput>
 | 
				
			||||||
 | 
				
			|||||||
@ -12,3 +12,22 @@
 | 
				
			|||||||
    overflow-y: scroll;
 | 
					    overflow-y: scroll;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.btn-group-xs {
 | 
				
			||||||
 | 
					  > .btn {
 | 
				
			||||||
 | 
					    padding: 0.2rem 0.25rem;
 | 
				
			||||||
 | 
					    font-size: 0.675rem;
 | 
				
			||||||
 | 
					    line-height: 1.2;
 | 
				
			||||||
 | 
					    border-radius: 0.15rem;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  > .btn:not(:first-child) {
 | 
				
			||||||
 | 
					    border-top-left-radius: 0;
 | 
				
			||||||
 | 
					    border-bottom-left-radius: 0;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  > .btn:not(:last-child) {
 | 
				
			||||||
 | 
					    border-top-right-radius: 0;
 | 
				
			||||||
 | 
					    border-bottom-right-radius: 0;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -15,6 +15,7 @@ export class FilterableDropdownSelectionModel {
 | 
				
			|||||||
  changed = new Subject<FilterableDropdownSelectionModel>()
 | 
					  changed = new Subject<FilterableDropdownSelectionModel>()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  multiple = false
 | 
					  multiple = false
 | 
				
			||||||
 | 
					  logicalOperator = 'and'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  items: MatchingModel[] = []
 | 
					  items: MatchingModel[] = []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -83,7 +84,7 @@ export class FilterableDropdownSelectionModel {
 | 
				
			|||||||
    if (fireEvent) {
 | 
					    if (fireEvent) {
 | 
				
			||||||
      this.changed.next(this)
 | 
					      this.changed.next(this)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    
 | 
					
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private getNonTemporary(id: number) {
 | 
					  private getNonTemporary(id: number) {
 | 
				
			||||||
 | 
				
			|||||||
@ -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_TAGS_ALL, FILTER_HAS_TAGS_ANY, 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';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -38,7 +38,7 @@ export class FilterEditorComponent implements OnInit, OnDestroy {
 | 
				
			|||||||
            return $localize`Without document type`
 | 
					            return $localize`Without document type`
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        case FILTER_HAS_TAG:
 | 
					        case FILTER_HAS_TAGS_ALL:
 | 
				
			||||||
          return $localize`Tag: ${this.tags.find(t => t.id == +rule.value)?.name}`
 | 
					          return $localize`Tag: ${this.tags.find(t => t.id == +rule.value)?.name}`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        case FILTER_HAS_ANY_TAG:
 | 
					        case FILTER_HAS_ANY_TAG:
 | 
				
			||||||
@ -101,7 +101,11 @@ export class FilterEditorComponent implements OnInit, OnDestroy {
 | 
				
			|||||||
        case FILTER_ADDED_BEFORE:
 | 
					        case FILTER_ADDED_BEFORE:
 | 
				
			||||||
          this.dateAddedBefore = rule.value
 | 
					          this.dateAddedBefore = rule.value
 | 
				
			||||||
          break
 | 
					          break
 | 
				
			||||||
        case FILTER_HAS_TAG:
 | 
					        case FILTER_HAS_TAGS_ALL:
 | 
				
			||||||
 | 
					          this.tagSelectionModel.set(rule.value ? +rule.value : null, ToggleableItemState.Selected, false)
 | 
				
			||||||
 | 
					          break
 | 
				
			||||||
 | 
					        case FILTER_HAS_TAGS_ANY:
 | 
				
			||||||
 | 
					          this.tagSelectionModel.logicalOperator = 'or'
 | 
				
			||||||
          this.tagSelectionModel.set(rule.value ? +rule.value : null, ToggleableItemState.Selected, false)
 | 
					          this.tagSelectionModel.set(rule.value ? +rule.value : null, ToggleableItemState.Selected, false)
 | 
				
			||||||
          break
 | 
					          break
 | 
				
			||||||
        case FILTER_HAS_ANY_TAG:
 | 
					        case FILTER_HAS_ANY_TAG:
 | 
				
			||||||
@ -125,8 +129,9 @@ export class FilterEditorComponent implements OnInit, OnDestroy {
 | 
				
			|||||||
    if (this.tagSelectionModel.isNoneSelected()) {
 | 
					    if (this.tagSelectionModel.isNoneSelected()) {
 | 
				
			||||||
      filterRules.push({rule_type: FILTER_HAS_ANY_TAG, value: "false"})
 | 
					      filterRules.push({rule_type: FILTER_HAS_ANY_TAG, value: "false"})
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
 | 
					      const tagFilterType = this.tagSelectionModel.logicalOperator == 'and' ? FILTER_HAS_TAGS_ALL : FILTER_HAS_TAGS_ANY
 | 
				
			||||||
      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: tagFilterType, value: tag.id?.toString()})
 | 
				
			||||||
      })
 | 
					      })
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    this.correspondentSelectionModel.getSelectedItems().forEach(correspondent => {
 | 
					    this.correspondentSelectionModel.getSelectedItems().forEach(correspondent => {
 | 
				
			||||||
 | 
				
			|||||||
@ -1,6 +1,6 @@
 | 
				
			|||||||
import { Component } from '@angular/core';
 | 
					import { Component } from '@angular/core';
 | 
				
			||||||
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
 | 
					import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
 | 
				
			||||||
import { FILTER_HAS_TAG } from 'src/app/data/filter-rule-type';
 | 
					import { FILTER_HAS_TAGS_ALL } from 'src/app/data/filter-rule-type';
 | 
				
			||||||
import { TAG_COLOURS, PaperlessTag } from 'src/app/data/paperless-tag';
 | 
					import { TAG_COLOURS, PaperlessTag } from 'src/app/data/paperless-tag';
 | 
				
			||||||
import { DocumentListViewService } from 'src/app/services/document-list-view.service';
 | 
					import { DocumentListViewService } from 'src/app/services/document-list-view.service';
 | 
				
			||||||
import { TagService } from 'src/app/services/rest/tag.service';
 | 
					import { TagService } from 'src/app/services/rest/tag.service';
 | 
				
			||||||
@ -31,7 +31,7 @@ export class TagListComponent extends GenericListComponent<PaperlessTag> {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  filterDocuments(object: PaperlessTag) {
 | 
					  filterDocuments(object: PaperlessTag) {
 | 
				
			||||||
    this.list.quickFilter([{rule_type: FILTER_HAS_TAG, value: object.id.toString()}])
 | 
					    this.list.quickFilter([{rule_type: FILTER_HAS_TAGS_ALL, value: object.id.toString()}])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -4,8 +4,9 @@ export const FILTER_ASN = 2
 | 
				
			|||||||
export const FILTER_CORRESPONDENT = 3
 | 
					export const FILTER_CORRESPONDENT = 3
 | 
				
			||||||
export const FILTER_DOCUMENT_TYPE = 4
 | 
					export const FILTER_DOCUMENT_TYPE = 4
 | 
				
			||||||
export const FILTER_IS_IN_INBOX = 5
 | 
					export const FILTER_IS_IN_INBOX = 5
 | 
				
			||||||
export const FILTER_HAS_TAG = 6
 | 
					export const FILTER_HAS_TAGS_ALL = 6
 | 
				
			||||||
export const FILTER_HAS_ANY_TAG = 7
 | 
					export const FILTER_HAS_ANY_TAG = 7
 | 
				
			||||||
 | 
					export const FILTER_HAS_TAGS_ANY = 19
 | 
				
			||||||
export const FILTER_CREATED_BEFORE = 8
 | 
					export const FILTER_CREATED_BEFORE = 8
 | 
				
			||||||
export const FILTER_CREATED_AFTER = 9
 | 
					export const FILTER_CREATED_AFTER = 9
 | 
				
			||||||
export const FILTER_CREATED_YEAR = 10
 | 
					export const FILTER_CREATED_YEAR = 10
 | 
				
			||||||
@ -31,7 +32,8 @@ export const FILTER_RULE_TYPES: FilterRuleType[] = [
 | 
				
			|||||||
  {id: FILTER_DOCUMENT_TYPE, filtervar: "document_type__id", isnull_filtervar: "document_type__isnull", datatype: "document_type", multi: false},
 | 
					  {id: FILTER_DOCUMENT_TYPE, filtervar: "document_type__id", isnull_filtervar: "document_type__isnull", datatype: "document_type", multi: false},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  {id: FILTER_IS_IN_INBOX, filtervar: "is_in_inbox", datatype: "boolean", multi: false, default: true},
 | 
					  {id: FILTER_IS_IN_INBOX, filtervar: "is_in_inbox", datatype: "boolean", multi: false, default: true},
 | 
				
			||||||
  {id: FILTER_HAS_TAG, filtervar: "tags__id__all", datatype: "tag", multi: true},
 | 
					  {id: FILTER_HAS_TAGS_ALL, filtervar: "tags__id__all", datatype: "tag", multi: true},
 | 
				
			||||||
 | 
					  {id: FILTER_HAS_TAGS_ANY, filtervar: "tags__id__in", datatype: "tag", multi: true},
 | 
				
			||||||
  {id: FILTER_DOES_NOT_HAVE_TAG, filtervar: "tags__id__none", datatype: "tag", multi: true},
 | 
					  {id: FILTER_DOES_NOT_HAVE_TAG, filtervar: "tags__id__none", datatype: "tag", multi: true},
 | 
				
			||||||
  {id: FILTER_HAS_ANY_TAG, filtervar: "is_tagged", datatype: "boolean", multi: false, default: true},
 | 
					  {id: FILTER_HAS_ANY_TAG, filtervar: "is_tagged", datatype: "boolean", multi: false, default: true},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user