mirror of
				https://github.com/paperless-ngx/paperless-ngx.git
				synced 2025-11-03 03:16:10 -06:00 
			
		
		
		
	@@ -39,6 +39,7 @@ import { CheckComponent } from './components/common/input/check/check.component'
 | 
			
		||||
import { SaveViewConfigDialogComponent } from './components/document-list/save-view-config-dialog/save-view-config-dialog.component';
 | 
			
		||||
import { InfiniteScrollModule } from 'ngx-infinite-scroll';
 | 
			
		||||
import { DateTimeComponent } from './components/common/input/date-time/date-time.component';
 | 
			
		||||
import { TagsComponent } from './components/common/input/tags/tags.component';
 | 
			
		||||
 | 
			
		||||
@NgModule({
 | 
			
		||||
  declarations: [
 | 
			
		||||
@@ -71,7 +72,8 @@ import { DateTimeComponent } from './components/common/input/date-time/date-time
 | 
			
		||||
    SelectComponent,
 | 
			
		||||
    CheckComponent,
 | 
			
		||||
    SaveViewConfigDialogComponent,
 | 
			
		||||
    DateTimeComponent
 | 
			
		||||
    DateTimeComponent,
 | 
			
		||||
    TagsComponent
 | 
			
		||||
  ],
 | 
			
		||||
  imports: [
 | 
			
		||||
    BrowserModule,
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,10 @@
 | 
			
		||||
.tags-form-control {
 | 
			
		||||
  height: auto;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
.scrollable-menu {
 | 
			
		||||
  height: auto;
 | 
			
		||||
  max-height: 300px;
 | 
			
		||||
  overflow-x: hidden;
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,30 @@
 | 
			
		||||
<div class="form-group">
 | 
			
		||||
  <label for="exampleFormControlTextarea1">Tags</label>
 | 
			
		||||
 | 
			
		||||
  <div class="input-group">
 | 
			
		||||
    <div class="form-control tags-form-control" id="tags">
 | 
			
		||||
      <app-tag class="mr-2" *ngFor="let id of displayValue" [tag]="getTag(id)" (click)="removeTag(id)"></app-tag>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <div class="input-group-append" ngbDropdown placement="top-right">
 | 
			
		||||
      <button class="btn btn-outline-secondary" type="button" ngbDropdownToggle></button>
 | 
			
		||||
      <div ngbDropdownMenu class="scrollable-menu">
 | 
			
		||||
        <button type="button" *ngFor="let tag of tags" ngbDropdownItem (click)="addTag(tag.id)">
 | 
			
		||||
          <app-tag [tag]="tag"></app-tag>
 | 
			
		||||
        </button>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <div class="input-group-append">
 | 
			
		||||
 | 
			
		||||
      <button class="btn btn-outline-secondary" type="button" (click)="createTag()">
 | 
			
		||||
        <svg class="buttonicon" fill="currentColor">
 | 
			
		||||
          <use xlink:href="assets/bootstrap-icons.svg#plus" />
 | 
			
		||||
        </svg>
 | 
			
		||||
      </button>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
  </div>
 | 
			
		||||
  <small class="form-text text-muted" *ngIf="hint">{{hint}}</small>
 | 
			
		||||
 | 
			
		||||
</div>
 | 
			
		||||
@@ -0,0 +1,25 @@
 | 
			
		||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
 | 
			
		||||
 | 
			
		||||
import { TagsComponent } from './tags.component';
 | 
			
		||||
 | 
			
		||||
describe('TagsComponent', () => {
 | 
			
		||||
  let component: TagsComponent;
 | 
			
		||||
  let fixture: ComponentFixture<TagsComponent>;
 | 
			
		||||
 | 
			
		||||
  beforeEach(async () => {
 | 
			
		||||
    await TestBed.configureTestingModule({
 | 
			
		||||
      declarations: [ TagsComponent ]
 | 
			
		||||
    })
 | 
			
		||||
    .compileComponents();
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  beforeEach(() => {
 | 
			
		||||
    fixture = TestBed.createComponent(TagsComponent);
 | 
			
		||||
    component = fixture.componentInstance;
 | 
			
		||||
    fixture.detectChanges();
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  it('should create', () => {
 | 
			
		||||
    expect(component).toBeTruthy();
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
@@ -0,0 +1,96 @@
 | 
			
		||||
import { ThrowStmt } from '@angular/compiler';
 | 
			
		||||
import { Component, forwardRef, Input, OnInit } from '@angular/core';
 | 
			
		||||
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
 | 
			
		||||
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
 | 
			
		||||
import { Observable } from 'rxjs';
 | 
			
		||||
import { TagEditDialogComponent } from 'src/app/components/manage/tag-list/tag-edit-dialog/tag-edit-dialog.component';
 | 
			
		||||
import { PaperlessTag } from 'src/app/data/paperless-tag';
 | 
			
		||||
import { TagService } from 'src/app/services/rest/tag.service';
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  providers: [{
 | 
			
		||||
    provide: NG_VALUE_ACCESSOR,
 | 
			
		||||
    useExisting: forwardRef(() => TagsComponent),
 | 
			
		||||
    multi: true
 | 
			
		||||
  }],
 | 
			
		||||
  selector: 'app-input-tags',
 | 
			
		||||
  templateUrl: './tags.component.html',
 | 
			
		||||
  styleUrls: ['./tags.component.css']
 | 
			
		||||
})
 | 
			
		||||
export class TagsComponent implements OnInit, ControlValueAccessor {
 | 
			
		||||
 | 
			
		||||
  constructor(private tagService: TagService, private modalService: NgbModal) { }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  onChange = (newValue: number[]) => {};
 | 
			
		||||
  
 | 
			
		||||
  onTouched = () => {};
 | 
			
		||||
 | 
			
		||||
  writeValue(newValue: number[]): void {
 | 
			
		||||
    this.value = newValue
 | 
			
		||||
    if (this.tags) {
 | 
			
		||||
      this.displayValue = newValue
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  registerOnChange(fn: any): void {
 | 
			
		||||
    this.onChange = fn;
 | 
			
		||||
  }
 | 
			
		||||
  registerOnTouched(fn: any): void {
 | 
			
		||||
    this.onTouched = fn;
 | 
			
		||||
  }
 | 
			
		||||
  setDisabledState?(isDisabled: boolean): void {
 | 
			
		||||
    this.disabled = isDisabled;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ngOnInit(): void {
 | 
			
		||||
    this.tagService.listAll().subscribe(result => {
 | 
			
		||||
      this.tags = result.results
 | 
			
		||||
      this.displayValue = this.value
 | 
			
		||||
    })
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Input()
 | 
			
		||||
  disabled = false
 | 
			
		||||
 | 
			
		||||
  @Input()
 | 
			
		||||
  hint
 | 
			
		||||
 | 
			
		||||
  value: number[]
 | 
			
		||||
 | 
			
		||||
  displayValue: number[] = []
 | 
			
		||||
 | 
			
		||||
  tags: PaperlessTag[]
 | 
			
		||||
 | 
			
		||||
  getTag(id) {
 | 
			
		||||
    return this.tags.find(tag => tag.id == id)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  removeTag(id) {
 | 
			
		||||
    let index = this.displayValue.indexOf(id)
 | 
			
		||||
    if (index > -1) {
 | 
			
		||||
      this.displayValue.splice(index, 1)
 | 
			
		||||
      this.onChange(this.displayValue)
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  addTag(id) {
 | 
			
		||||
    let index = this.displayValue.indexOf(id)
 | 
			
		||||
    if (index == -1) {
 | 
			
		||||
      this.displayValue.push(id)
 | 
			
		||||
      this.onChange(this.displayValue)
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  createTag() {
 | 
			
		||||
    var modal = this.modalService.open(TagEditDialogComponent, {backdrop: 'static'})
 | 
			
		||||
    modal.componentInstance.dialogMode = 'create'
 | 
			
		||||
    modal.componentInstance.success.subscribe(newTag => {
 | 
			
		||||
      this.tagService.list().subscribe(tags => {
 | 
			
		||||
        this.tags = tags.results
 | 
			
		||||
        this.addTag(newTag.id)
 | 
			
		||||
      })
 | 
			
		||||
    })
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -3,19 +3,19 @@
 | 
			
		||||
        <svg class="buttonicon" fill="currentColor">
 | 
			
		||||
            <use xlink:href="assets/bootstrap-icons.svg#trash" />
 | 
			
		||||
        </svg>
 | 
			
		||||
        Delete
 | 
			
		||||
        <span class="d-none d-lg-inline">Delete</span>
 | 
			
		||||
    </button>
 | 
			
		||||
    <a [href]="downloadUrl" class="btn btn-sm btn-outline-secondary mr-2">
 | 
			
		||||
        <svg class="buttonicon" fill="currentColor">
 | 
			
		||||
            <use xlink:href="assets/bootstrap-icons.svg#download" />
 | 
			
		||||
        </svg>
 | 
			
		||||
        Download
 | 
			
		||||
        <span class="d-none d-lg-inline">Download</span>
 | 
			
		||||
    </a>
 | 
			
		||||
    <button type="button" class="btn btn-sm btn-outline-secondary" (click)="close()">
 | 
			
		||||
        <svg class="buttonicon" fill="currentColor">
 | 
			
		||||
            <use xlink:href="assets/bootstrap-icons.svg#x" />
 | 
			
		||||
        </svg>
 | 
			
		||||
        Close
 | 
			
		||||
        <span class="d-none d-lg-inline">Close</span>
 | 
			
		||||
    </button>
 | 
			
		||||
</app-page-header>
 | 
			
		||||
 | 
			
		||||
@@ -43,26 +43,9 @@
 | 
			
		||||
 | 
			
		||||
            <app-input-select [items]="documentTypes" title="Document type" formControlName="document_type_id" allowNull="true" (createNew)="createDocumentType()"></app-input-select>
 | 
			
		||||
 | 
			
		||||
            <div class="form-group">
 | 
			
		||||
                <label for="exampleFormControlTextarea1">Tags</label>
 | 
			
		||||
            <app-input-tags formControlName="tags_id" title="Tags"></app-input-tags>
 | 
			
		||||
 | 
			
		||||
                <div class="input-group">
 | 
			
		||||
                    <select multiple class="form-control" id="tags" formControlName="tags_id">
 | 
			
		||||
                        <option *ngFor="let t of tags" [ngValue]="t.id">{{t.name}}</option>
 | 
			
		||||
                    </select>
 | 
			
		||||
 | 
			
		||||
                    <div class="input-group-append">
 | 
			
		||||
                        <button class="btn btn-outline-secondary" type="button" (click)="createTag()">
 | 
			
		||||
                            <svg class="buttonicon" fill="currentColor">
 | 
			
		||||
                                <use xlink:href="assets/bootstrap-icons.svg#plus" />
 | 
			
		||||
                            </svg>
 | 
			
		||||
                        </button>
 | 
			
		||||
                    </div>
 | 
			
		||||
 | 
			
		||||
                </div>
 | 
			
		||||
                <small class="form-text text-muted">Hold CTRL to (de)select multiple tags.</small>
 | 
			
		||||
 | 
			
		||||
            </div>
 | 
			
		||||
            <button type="button" class="btn btn-outline-secondary" (click)="discard()">Discard</button> 
 | 
			
		||||
            <button type="button" class="btn btn-outline-primary" (click)="saveEditNext()" *ngIf="hasNext()">Save & edit next</button> 
 | 
			
		||||
            <button type="submit" class="btn btn-primary">Save</button> 
 | 
			
		||||
        </form>
 | 
			
		||||
 
 | 
			
		||||
@@ -33,7 +33,6 @@ export class DocumentDetailComponent implements OnInit {
 | 
			
		||||
 | 
			
		||||
  correspondents: PaperlessCorrespondent[]
 | 
			
		||||
  documentTypes: PaperlessDocumentType[]
 | 
			
		||||
  tags: PaperlessTag[]
 | 
			
		||||
 | 
			
		||||
  documentForm: FormGroup = new FormGroup({
 | 
			
		||||
    title: new FormControl(''),
 | 
			
		||||
@@ -50,7 +49,6 @@ export class DocumentDetailComponent implements OnInit {
 | 
			
		||||
    private route: ActivatedRoute,
 | 
			
		||||
    private correspondentService: CorrespondentService,
 | 
			
		||||
    private documentTypeService: DocumentTypeService,
 | 
			
		||||
    private tagService: TagService,
 | 
			
		||||
    private datePipe: DatePipe,
 | 
			
		||||
    private router: Router,
 | 
			
		||||
    private modalService: NgbModal,
 | 
			
		||||
@@ -64,7 +62,6 @@ export class DocumentDetailComponent implements OnInit {
 | 
			
		||||
 | 
			
		||||
    this.correspondentService.list(1,100000).subscribe(result => this.correspondents = result.results)
 | 
			
		||||
    this.documentTypeService.list(1,100000).subscribe(result => this.documentTypes = result.results)
 | 
			
		||||
    this.tagService.list(1,100000).subscribe(result => this.tags = result.results)
 | 
			
		||||
 | 
			
		||||
    this.route.paramMap.subscribe(paramMap => {
 | 
			
		||||
      this.documentId = +paramMap.get('id')
 | 
			
		||||
@@ -88,17 +85,6 @@ export class DocumentDetailComponent implements OnInit {
 | 
			
		||||
    this.documentForm.patchValue(doc)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  createTag() {
 | 
			
		||||
    var modal = this.modalService.open(TagEditDialogComponent, {backdrop: 'static'})
 | 
			
		||||
    modal.componentInstance.dialogMode = 'create'
 | 
			
		||||
    modal.componentInstance.success.subscribe(newTag => {
 | 
			
		||||
      this.tagService.list().subscribe(tags => {
 | 
			
		||||
        this.tags = tags.results
 | 
			
		||||
        this.documentForm.get('tags_id').setValue(this.documentForm.get('tags_id').value.concat([newTag.id]))
 | 
			
		||||
      })
 | 
			
		||||
    })
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  createDocumentType() {
 | 
			
		||||
    var modal = this.modalService.open(DocumentTypeEditDialogComponent, {backdrop: 'static'})
 | 
			
		||||
    modal.componentInstance.dialogMode = 'create'
 | 
			
		||||
@@ -121,28 +107,14 @@ export class DocumentDetailComponent implements OnInit {
 | 
			
		||||
    })
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  getTag(id: number): PaperlessTag {
 | 
			
		||||
    return this.tags.find(tag => tag.id == id)
 | 
			
		||||
  discard() {
 | 
			
		||||
    this.documentsService.get(this.documentId).subscribe(doc => {
 | 
			
		||||
      Object.assign(this.document, doc)
 | 
			
		||||
      this.title = doc.title
 | 
			
		||||
      this.documentForm.patchValue(doc)
 | 
			
		||||
    }, error => {this.router.navigate(['404'])})
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  getColour(id: number) {
 | 
			
		||||
    return TAG_COLOURS.find(c => c.id == this.getTag(id).colour)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  addTag(id: number) {
 | 
			
		||||
    if (this.documentForm.value.tags.indexOf(id) == -1) {
 | 
			
		||||
      this.documentForm.value.tags.push(id)
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  removeTag(id: number) {
 | 
			
		||||
    let index = this.documentForm.value.tags.indexOf(id)
 | 
			
		||||
    if (index > -1) {
 | 
			
		||||
      this.documentForm.value.tags.splice(index, 1)
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  save() {    
 | 
			
		||||
    this.documentsService.update(this.document).subscribe(result => {
 | 
			
		||||
      this.close()
 | 
			
		||||
 
 | 
			
		||||
@@ -40,6 +40,10 @@ export abstract class AbstractPaperlessService<T extends ObjectWithId> {
 | 
			
		||||
    return this.http.get<Results<T>>(this.getResourceUrl(), {params: httpParams})
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  listAll(ordering?: string, extraParams?): Observable<Results<T>> {
 | 
			
		||||
    return this.list(1, 100000, ordering, extraParams)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  get(id: number): Observable<T> {
 | 
			
		||||
    return this.http.get<T>(this.getResourceUrl(id))
 | 
			
		||||
  }
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user