mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2025-07-28 18:24:38 -05:00
Merge branch 'dev' into feature/any-all-filtering
This commit is contained in:
@@ -30,7 +30,7 @@ export class ConfirmDialogComponent implements OnInit {
|
||||
|
||||
@Input()
|
||||
buttonsEnabled = true
|
||||
|
||||
|
||||
confirmButtonEnabled = true
|
||||
seconds = 0
|
||||
|
||||
|
@@ -20,8 +20,17 @@
|
||||
</div>
|
||||
|
||||
<div class="input-group input-group-sm">
|
||||
<input type="date" class="form-control" id="date_after" [(ngModel)]="dateAfter" (change)="onChangeDebounce()">
|
||||
<input class="form-control" [placeholder]="datePlaceHolder" (dateSelect)="onChangeDebounce()" (change)="onChangeDebounce()"
|
||||
[(ngModel)]="dateAfter" ngbDatepicker #dateAfterPicker="ngbDatepicker">
|
||||
<div class="input-group-append">
|
||||
<button class="btn btn-outline-secondary" (click)="dateAfterPicker.toggle()" type="button">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" fill="currentColor" class="bi bi-calendar" viewBox="0 0 16 16">
|
||||
<path d="M3.5 0a.5.5 0 0 1 .5.5V1h8V.5a.5.5 0 0 1 1 0V1h1a2 2 0 0 1 2 2v11a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V3a2 2 0 0 1 2-2h1V.5a.5.5 0 0 1 .5-.5zM1 4v10a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V4H1z"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="list-group-item d-flex flex-column align-items-start" role="menuitem">
|
||||
|
||||
@@ -36,8 +45,17 @@
|
||||
</div>
|
||||
|
||||
<div class="input-group input-group-sm">
|
||||
<input type="date" class="form-control" id="date_before" [(ngModel)]="dateBefore" (change)="onChangeDebounce()">
|
||||
<input class="form-control" [placeholder]="datePlaceHolder" (dateSelect)="onChangeDebounce()" (change)="onChangeDebounce()"
|
||||
[(ngModel)]="dateBefore" ngbDatepicker #dateBeforePicker="ngbDatepicker">
|
||||
<div class="input-group-append">
|
||||
<button class="btn btn-outline-secondary" (click)="dateBeforePicker.toggle()" type="button">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" fill="currentColor" class="bi bi-calendar" viewBox="0 0 16 16">
|
||||
<path d="M3.5 0a.5.5 0 0 1 .5.5V1h8V.5a.5.5 0 0 1 1 0V1h1a2 2 0 0 1 2 2v11a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V3a2 2 0 0 1 2-2h1V.5a.5.5 0 0 1 .5-.5zM1 4v10a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V4H1z"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@@ -1,7 +1,10 @@
|
||||
import { formatDate } from '@angular/common';
|
||||
import { Component, EventEmitter, Input, Output, OnInit, OnDestroy } from '@angular/core';
|
||||
import { NgbDateAdapter } from '@ng-bootstrap/ng-bootstrap';
|
||||
import { Subject, Subscription } from 'rxjs';
|
||||
import { debounceTime, distinctUntilChanged } from 'rxjs/operators';
|
||||
import { debounceTime } from 'rxjs/operators';
|
||||
import { SettingsService } from 'src/app/services/settings.service';
|
||||
import { ISODateAdapter } from 'src/app/utils/ngb-iso-date-adapter';
|
||||
|
||||
export interface DateSelection {
|
||||
before?: string
|
||||
@@ -16,10 +19,17 @@ const LAST_YEAR = 3
|
||||
@Component({
|
||||
selector: 'app-date-dropdown',
|
||||
templateUrl: './date-dropdown.component.html',
|
||||
styleUrls: ['./date-dropdown.component.scss']
|
||||
styleUrls: ['./date-dropdown.component.scss'],
|
||||
providers: [
|
||||
{provide: NgbDateAdapter, useClass: ISODateAdapter},
|
||||
]
|
||||
})
|
||||
export class DateDropdownComponent implements OnInit, OnDestroy {
|
||||
|
||||
constructor(settings: SettingsService) {
|
||||
this.datePlaceHolder = settings.getLocalizedDateInputFormat()
|
||||
}
|
||||
|
||||
quickFilters = [
|
||||
{id: LAST_7_DAYS, name: $localize`Last 7 days`},
|
||||
{id: LAST_MONTH, name: $localize`Last month`},
|
||||
@@ -27,6 +37,8 @@ export class DateDropdownComponent implements OnInit, OnDestroy {
|
||||
{id: LAST_YEAR, name: $localize`Last year`}
|
||||
]
|
||||
|
||||
datePlaceHolder: string
|
||||
|
||||
@Input()
|
||||
dateBefore: string
|
||||
|
||||
|
@@ -11,7 +11,7 @@ export class AbstractInputComponent<T> implements OnInit, ControlValueAccessor {
|
||||
constructor() { }
|
||||
|
||||
onChange = (newValue: T) => {};
|
||||
|
||||
|
||||
onTouched = () => {};
|
||||
|
||||
writeValue(newValue: any): void {
|
||||
|
@@ -2,4 +2,4 @@
|
||||
<input type="checkbox" class="custom-control-input" [id]="inputId" [(ngModel)]="value" (change)="onChange(value)" (blur)="onTouched()" [disabled]="disabled">
|
||||
<label class="custom-control-label" [for]="inputId">{{title}}</label>
|
||||
<small *ngIf="hint" class="form-text text-muted">{{hint}}</small>
|
||||
</div>
|
||||
</div>
|
||||
|
@@ -15,7 +15,7 @@ import { AbstractInputComponent } from '../abstract-input';
|
||||
})
|
||||
export class CheckComponent extends AbstractInputComponent<boolean> {
|
||||
|
||||
constructor() {
|
||||
constructor() {
|
||||
super()
|
||||
}
|
||||
|
||||
|
@@ -0,0 +1,33 @@
|
||||
<div class="form-group">
|
||||
<label [for]="inputId">{{title}}</label>
|
||||
|
||||
<div class="input-group" [class.is-invalid]="error">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text" [style.background-color]="value"> </span>
|
||||
</div>
|
||||
|
||||
<ng-template #popContent>
|
||||
<div style="min-width: 200px;" class="pb-3">
|
||||
<color-slider [color]="value" (onChangeComplete)="colorChanged($event.color.hex)"></color-slider>
|
||||
</div>
|
||||
|
||||
</ng-template>
|
||||
|
||||
<input class="form-control" [class.is-invalid]="error" [id]="inputId" [(ngModel)]="value" (change)="onChange(value)" [autoClose]="'outside'" [ngbPopover]="popContent" placement="bottom" popoverClass="shadow">
|
||||
|
||||
<div class="input-group-append">
|
||||
<button class="btn btn-outline-secondary" type="button" (click)="randomize()">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-dice-5" viewBox="0 0 16 16">
|
||||
<path d="M13 1a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V3a2 2 0 0 1 2-2h10zM3 0a3 3 0 0 0-3 3v10a3 3 0 0 0 3 3h10a3 3 0 0 0 3-3V3a3 3 0 0 0-3-3H3z"/>
|
||||
<path d="M5.5 4a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0zm8 0a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0zm0 8a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0zm-8 0a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0zm4-4a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0z"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
<small *ngIf="hint" class="form-text text-muted">{{hint}}</small>
|
||||
<div class="invalid-feedback">
|
||||
{{error}}
|
||||
</div>
|
||||
</div>
|
@@ -1,20 +1,20 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { DateTimeComponent } from './date-time.component';
|
||||
import { ColorComponent } from './color.component';
|
||||
|
||||
describe('DateTimeComponent', () => {
|
||||
let component: DateTimeComponent;
|
||||
let fixture: ComponentFixture<DateTimeComponent>;
|
||||
describe('ColorComponent', () => {
|
||||
let component: ColorComponent;
|
||||
let fixture: ComponentFixture<ColorComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [ DateTimeComponent ]
|
||||
declarations: [ ColorComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(DateTimeComponent);
|
||||
fixture = TestBed.createComponent(ColorComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
@@ -0,0 +1,30 @@
|
||||
import { Component, forwardRef } from '@angular/core';
|
||||
import { NG_VALUE_ACCESSOR } from '@angular/forms';
|
||||
import { randomColor } from 'src/app/utils/color';
|
||||
import { AbstractInputComponent } from '../abstract-input';
|
||||
|
||||
@Component({
|
||||
providers: [{
|
||||
provide: NG_VALUE_ACCESSOR,
|
||||
useExisting: forwardRef(() => ColorComponent),
|
||||
multi: true
|
||||
}],
|
||||
selector: 'app-input-color',
|
||||
templateUrl: './color.component.html',
|
||||
styleUrls: ['./color.component.scss']
|
||||
})
|
||||
export class ColorComponent extends AbstractInputComponent<string> {
|
||||
|
||||
constructor() {
|
||||
super()
|
||||
}
|
||||
|
||||
randomize() {
|
||||
this.colorChanged(randomColor())
|
||||
}
|
||||
|
||||
colorChanged(value) {
|
||||
this.value = value
|
||||
this.onChange(value)
|
||||
}
|
||||
}
|
@@ -1,13 +0,0 @@
|
||||
<div class="form-row">
|
||||
<div class="form-group col">
|
||||
<label for="created_date">{{titleDate}}</label>
|
||||
<input type="date" class="form-control" id="created_date" [(ngModel)]="dateValue" (change)="dateOrTimeChanged()">
|
||||
</div>
|
||||
<div class="form-group col" *ngIf="titleTime">
|
||||
<label for="created_time">{{titleTime}}</label>
|
||||
<input type="time" class="form-control" id="created_time" [(ngModel)]="timeValue" (change)="dateOrTimeChanged()">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- <small *ngIf="hint" class="form-text text-muted">{{hint}}</small> -->
|
@@ -1,61 +0,0 @@
|
||||
import { formatDate } from '@angular/common';
|
||||
import { Component, forwardRef, Input, OnInit } from '@angular/core';
|
||||
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
|
||||
|
||||
@Component({
|
||||
providers: [{
|
||||
provide: NG_VALUE_ACCESSOR,
|
||||
useExisting: forwardRef(() => DateTimeComponent),
|
||||
multi: true
|
||||
}],
|
||||
selector: 'app-input-date-time',
|
||||
templateUrl: './date-time.component.html',
|
||||
styleUrls: ['./date-time.component.scss']
|
||||
})
|
||||
export class DateTimeComponent implements OnInit,ControlValueAccessor {
|
||||
|
||||
constructor() {
|
||||
}
|
||||
|
||||
onChange = (newValue: any) => {};
|
||||
|
||||
onTouched = () => {};
|
||||
|
||||
writeValue(newValue: any): void {
|
||||
this.dateValue = formatDate(newValue, 'yyyy-MM-dd', "en-US")
|
||||
this.timeValue = formatDate(newValue, 'HH:mm:ss', 'en-US')
|
||||
}
|
||||
registerOnChange(fn: any): void {
|
||||
this.onChange = fn;
|
||||
}
|
||||
registerOnTouched(fn: any): void {
|
||||
this.onTouched = fn;
|
||||
}
|
||||
setDisabledState?(isDisabled: boolean): void {
|
||||
this.disabled = isDisabled;
|
||||
}
|
||||
|
||||
@Input()
|
||||
titleDate: string = "Date"
|
||||
|
||||
@Input()
|
||||
titleTime: string
|
||||
|
||||
@Input()
|
||||
disabled: boolean = false
|
||||
|
||||
@Input()
|
||||
hint: string
|
||||
|
||||
timeValue
|
||||
|
||||
dateValue
|
||||
|
||||
ngOnInit(): void {
|
||||
}
|
||||
|
||||
dateOrTimeChanged() {
|
||||
this.onChange(formatDate(this.dateValue + "T" + this.timeValue,"yyyy-MM-ddTHH:mm:ssZZZZZ", "en-us", "UTC"))
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,16 @@
|
||||
<div class="form-group">
|
||||
<label [for]="inputId">{{title}}</label>
|
||||
<div class="input-group" [class.is-invalid]="error">
|
||||
<input class="form-control" [class.is-invalid]="error" [placeholder]="placeholder" [id]="inputId" (dateSelect)="onChange(value)" (change)="onChange(value)"
|
||||
name="dp" [(ngModel)]="value" ngbDatepicker #datePicker="ngbDatepicker" #datePickerContent="ngModel">
|
||||
<div class="input-group-append">
|
||||
<button class="btn btn-outline-secondary calendar" (click)="datePicker.toggle()" type="button">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-calendar" viewBox="0 0 16 16">
|
||||
<path d="M3.5 0a.5.5 0 0 1 .5.5V1h8V.5a.5.5 0 0 1 1 0V1h1a2 2 0 0 1 2 2v11a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V3a2 2 0 0 1 2-2h1V.5a.5.5 0 0 1 .5-.5zM1 4v10a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V4H1z"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="invalid-feedback" i18n>Invalid date.</div>
|
||||
<small *ngIf="hint" class="form-text text-muted">{{hint}}</small>
|
||||
</div>
|
@@ -0,0 +1,25 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { DateComponent } from './date.component';
|
||||
|
||||
describe('DateComponent', () => {
|
||||
let component: DateComponent;
|
||||
let fixture: ComponentFixture<DateComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [ DateComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(DateComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
@@ -0,0 +1,32 @@
|
||||
import { Component, forwardRef, Input, OnInit, ViewChild } from '@angular/core';
|
||||
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
|
||||
import { NgbDateAdapter, NgbDateParserFormatter, NgbDatepickerContent } from '@ng-bootstrap/ng-bootstrap';
|
||||
import { SettingsService } from 'src/app/services/settings.service';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { AbstractInputComponent } from '../abstract-input';
|
||||
|
||||
|
||||
@Component({
|
||||
providers: [{
|
||||
provide: NG_VALUE_ACCESSOR,
|
||||
useExisting: forwardRef(() => DateComponent),
|
||||
multi: true
|
||||
}],
|
||||
selector: 'app-input-date',
|
||||
templateUrl: './date.component.html',
|
||||
styleUrls: ['./date.component.scss']
|
||||
})
|
||||
export class DateComponent extends AbstractInputComponent<string> implements OnInit {
|
||||
|
||||
constructor(private settings: SettingsService) {
|
||||
super()
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
super.ngOnInit()
|
||||
this.placeholder = this.settings.getLocalizedDateInputFormat()
|
||||
}
|
||||
|
||||
placeholder: string
|
||||
|
||||
}
|
@@ -11,4 +11,4 @@
|
||||
</div>
|
||||
<small *ngIf="hint" class="form-text text-muted">{{hint}}</small>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
@@ -1,25 +1,38 @@
|
||||
<div class="form-group paperless-input-select">
|
||||
<label [for]="inputId">{{title}}</label>
|
||||
<div [class.input-group]="showPlusButton()">
|
||||
<ng-select name="inputId" [(ngModel)]="value"
|
||||
[disabled]="disabled"
|
||||
[style.color]="textColor"
|
||||
[style.background]="backgroundColor"
|
||||
[clearable]="allowNull"
|
||||
[items]="items"
|
||||
bindLabel="name"
|
||||
bindValue="id"
|
||||
(change)="onChange(value)"
|
||||
(blur)="onTouched()">
|
||||
</ng-select>
|
||||
|
||||
<div *ngIf="showPlusButton()" class="input-group-append">
|
||||
<button class="btn btn-outline-secondary" type="button" (click)="createNew.emit()">
|
||||
<svg class="buttonicon" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#plus" />
|
||||
</svg>
|
||||
</button>
|
||||
<div [class.input-group]="allowCreateNew">
|
||||
<ng-select name="inputId" [(ngModel)]="value"
|
||||
[disabled]="disabled"
|
||||
[style.color]="textColor"
|
||||
[style.background]="backgroundColor"
|
||||
[clearable]="allowNull"
|
||||
[items]="items"
|
||||
[addTag]="allowCreateNew && addItemRef"
|
||||
addTagText="Add item"
|
||||
i18n-addTagText="Used for both types and correspondents"
|
||||
bindLabel="name"
|
||||
bindValue="id"
|
||||
(change)="onChange(value)"
|
||||
(search)="onSearch($event)"
|
||||
(focus)="clearLastSearchTerm()"
|
||||
(clear)="clearLastSearchTerm()"
|
||||
(blur)="onBlur()">
|
||||
</ng-select>
|
||||
<div *ngIf="allowCreateNew" class="input-group-append">
|
||||
<button class="btn btn-outline-secondary" type="button" (click)="addItem()">
|
||||
<svg class="buttonicon" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#plus" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<small *ngIf="hint" class="form-text text-muted">{{hint}}</small>
|
||||
<small *ngIf="getSuggestions().length > 0">
|
||||
<span i18n>Suggestions:</span>
|
||||
<ng-container *ngFor="let s of getSuggestions()">
|
||||
<a (click)="value = s.id; onChange(value)" [routerLink]="">{{s.name}}</a>
|
||||
</ng-container>
|
||||
|
||||
|
||||
</small>
|
||||
</div>
|
||||
|
@@ -16,6 +16,7 @@ export class SelectComponent extends AbstractInputComponent<number> {
|
||||
|
||||
constructor() {
|
||||
super()
|
||||
this.addItemRef = this.addItem.bind(this)
|
||||
}
|
||||
|
||||
@Input()
|
||||
@@ -30,11 +31,51 @@ export class SelectComponent extends AbstractInputComponent<number> {
|
||||
@Input()
|
||||
allowNull: boolean = false
|
||||
|
||||
@Input()
|
||||
suggestions: number[]
|
||||
|
||||
@Output()
|
||||
createNew = new EventEmitter()
|
||||
|
||||
showPlusButton(): boolean {
|
||||
createNew = new EventEmitter<string>()
|
||||
|
||||
public addItemRef: (name) => void
|
||||
|
||||
private _lastSearchTerm: string
|
||||
|
||||
get allowCreateNew(): boolean {
|
||||
return this.createNew.observers.length > 0
|
||||
}
|
||||
|
||||
getSuggestions() {
|
||||
if (this.suggestions && this.items) {
|
||||
return this.suggestions.filter(id => id != this.value).map(id => this.items.find(item => item.id == id))
|
||||
} else {
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
addItem(name: string) {
|
||||
if (name) this.createNew.next(name)
|
||||
else this.createNew.next(this._lastSearchTerm)
|
||||
this.clearLastSearchTerm()
|
||||
}
|
||||
|
||||
clickNew() {
|
||||
this.createNew.next(this._lastSearchTerm)
|
||||
this.clearLastSearchTerm()
|
||||
}
|
||||
|
||||
clearLastSearchTerm() {
|
||||
this._lastSearchTerm = null
|
||||
}
|
||||
|
||||
onSearch($event) {
|
||||
this._lastSearchTerm = $event.term
|
||||
}
|
||||
|
||||
onBlur() {
|
||||
setTimeout(() => {
|
||||
this.clearLastSearchTerm()
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -2,30 +2,31 @@
|
||||
<label for="tags" i18n>Tags</label>
|
||||
|
||||
<div class="input-group flex-nowrap">
|
||||
<ng-select name="tags" [items]="tags" bindLabel="name" bindValue="id" [(ngModel)]="displayValue"
|
||||
<ng-select name="tags" [items]="tags" bindLabel="name" bindValue="id" [(ngModel)]="value"
|
||||
[multiple]="true"
|
||||
[closeOnSelect]="false"
|
||||
[clearSearchOnAdd]="true"
|
||||
[disabled]="disabled"
|
||||
[hideSelected]="true"
|
||||
(change)="ngSelectChange()">
|
||||
[addTag]="createTagRef"
|
||||
addTagText="Add tag"
|
||||
i18n-addTagText
|
||||
(change)="onChange(value)"
|
||||
(search)="onSearch($event)"
|
||||
(focus)="clearLastSearchTerm()"
|
||||
(clear)="clearLastSearchTerm()"
|
||||
(blur)="onBlur()">
|
||||
|
||||
<ng-template ng-label-tmp let-item="item">
|
||||
<span class="tag-wrap tag-wrap-delete" (click)="removeTag(item.id)">
|
||||
<svg width="1.2em" height="1em" viewBox="0 0 16 16" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#x"/>
|
||||
</svg>
|
||||
<app-tag style="background-color: none;" [tag]="getTag(item.id)"></app-tag>
|
||||
<app-tag *ngIf="item.id && tags" style="background-color: none;" [tag]="getTag(item.id)"></app-tag>
|
||||
</span>
|
||||
</ng-template>
|
||||
<ng-template ng-option-tmp let-item="item" let-index="index" let-search="searchTerm">
|
||||
<div class="tag-wrap">
|
||||
<div class="selected-icon d-inline-block mr-1">
|
||||
<svg *ngIf="displayValue.includes(item.id)" width="1em" height="1em" viewBox="0 0 16 16" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#check"/>
|
||||
</svg>
|
||||
</div>
|
||||
<app-tag class="mr-2" [tag]="getTag(item.id)"></app-tag>
|
||||
<app-tag *ngIf="item.id && tags" class="mr-2" [tag]="getTag(item.id)"></app-tag>
|
||||
</div>
|
||||
</ng-template>
|
||||
</ng-select>
|
||||
@@ -39,5 +40,13 @@
|
||||
</div>
|
||||
</div>
|
||||
<small class="form-text text-muted" *ngIf="hint">{{hint}}</small>
|
||||
<small *ngIf="getSuggestions().length > 0">
|
||||
<span i18n>Suggestions:</span>
|
||||
<ng-container *ngFor="let tag of getSuggestions()">
|
||||
<a (click)="addTag(tag.id)" [routerLink]="">{{tag.name}}</a>
|
||||
</ng-container>
|
||||
|
||||
|
||||
</small>
|
||||
|
||||
</div>
|
||||
|
@@ -17,8 +17,9 @@ import { TagService } from 'src/app/services/rest/tag.service';
|
||||
})
|
||||
export class TagsComponent implements OnInit, ControlValueAccessor {
|
||||
|
||||
constructor(private tagService: TagService, private modalService: NgbModal) { }
|
||||
|
||||
constructor(private tagService: TagService, private modalService: NgbModal) {
|
||||
this.createTagRef = this.createTag.bind(this)
|
||||
}
|
||||
|
||||
onChange = (newValue: number[]) => {};
|
||||
|
||||
@@ -26,9 +27,6 @@ export class TagsComponent implements OnInit, ControlValueAccessor {
|
||||
|
||||
writeValue(newValue: number[]): void {
|
||||
this.value = newValue
|
||||
if (this.tags) {
|
||||
this.displayValue = newValue
|
||||
}
|
||||
}
|
||||
registerOnChange(fn: any): void {
|
||||
this.onChange = fn;
|
||||
@@ -43,7 +41,6 @@ export class TagsComponent implements OnInit, ControlValueAccessor {
|
||||
ngOnInit(): void {
|
||||
this.tagService.listAll().subscribe(result => {
|
||||
this.tags = result.results
|
||||
this.displayValue = this.value
|
||||
})
|
||||
}
|
||||
|
||||
@@ -53,41 +50,74 @@ export class TagsComponent implements OnInit, ControlValueAccessor {
|
||||
@Input()
|
||||
hint
|
||||
|
||||
value: number[]
|
||||
@Input()
|
||||
suggestions: number[]
|
||||
|
||||
displayValue: number[] = []
|
||||
value: number[]
|
||||
|
||||
tags: PaperlessTag[]
|
||||
|
||||
getTag(id) {
|
||||
return this.tags.find(tag => tag.id == id)
|
||||
}
|
||||
public createTagRef: (name) => void
|
||||
|
||||
removeTag(id) {
|
||||
let index = this.displayValue.indexOf(id)
|
||||
if (index > -1) {
|
||||
let oldValue = this.displayValue
|
||||
oldValue.splice(index, 1)
|
||||
this.displayValue = [...oldValue]
|
||||
this.onChange(this.displayValue)
|
||||
private _lastSearchTerm: string
|
||||
|
||||
getTag(id) {
|
||||
if (this.tags) {
|
||||
return this.tags.find(tag => tag.id == id)
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
createTag() {
|
||||
removeTag(id) {
|
||||
let index = this.value.indexOf(id)
|
||||
if (index > -1) {
|
||||
let oldValue = this.value
|
||||
oldValue.splice(index, 1)
|
||||
this.value = [...oldValue]
|
||||
this.onChange(this.value)
|
||||
}
|
||||
}
|
||||
|
||||
createTag(name: string = null) {
|
||||
var modal = this.modalService.open(TagEditDialogComponent, {backdrop: 'static'})
|
||||
modal.componentInstance.dialogMode = 'create'
|
||||
if (name) modal.componentInstance.object = { name: name }
|
||||
else if (this._lastSearchTerm) modal.componentInstance.object = { name: this._lastSearchTerm }
|
||||
modal.componentInstance.success.subscribe(newTag => {
|
||||
this.tagService.listAll().subscribe(tags => {
|
||||
this.tags = tags.results
|
||||
this.displayValue = [...this.displayValue, newTag.id]
|
||||
this.onChange(this.displayValue)
|
||||
this.value = [...this.value, newTag.id]
|
||||
this.onChange(this.value)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
ngSelectChange() {
|
||||
this.value = this.displayValue
|
||||
this.onChange(this.displayValue)
|
||||
getSuggestions() {
|
||||
if (this.suggestions && this.tags) {
|
||||
return this.suggestions.filter(id => !this.value.includes(id)).map(id => this.tags.find(tag => tag.id == id))
|
||||
} else {
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
addTag(id) {
|
||||
this.value = [...this.value, id]
|
||||
this.onChange(this.value)
|
||||
}
|
||||
|
||||
clearLastSearchTerm() {
|
||||
this._lastSearchTerm = null
|
||||
}
|
||||
|
||||
onSearch($event) {
|
||||
this._lastSearchTerm = $event.term
|
||||
}
|
||||
|
||||
onBlur() {
|
||||
setTimeout(() => {
|
||||
this.clearLastSearchTerm()
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -5,4 +5,4 @@
|
||||
<div class="invalid-feedback">
|
||||
{{error}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@@ -12,4 +12,4 @@
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-outline-dark" (click)="cancelClicked()" i18n>Cancel</button>
|
||||
<button type="button" class="btn btn-primary" (click)="selectClicked.emit(selected)" i18n>Select</button>
|
||||
</div>
|
||||
</div>
|
||||
|
@@ -1,2 +1,2 @@
|
||||
<span *ngIf="!clickable" class="badge" [style.background]="getColour().value" [style.color]="getColour().textColor">{{tag.name}}</span>
|
||||
<a [routerLink]="" [title]="linkTitle" *ngIf="clickable" class="badge" [style.background]="getColour().value" [style.color]="getColour().textColor">{{tag.name}}</a>
|
||||
<span *ngIf="!clickable" class="badge" [style.background]="tag.color" [style.color]="tag.text_color">{{tag.name}}</span>
|
||||
<a [routerLink]="" [title]="linkTitle" *ngIf="clickable" class="badge" [style.background]="tag.color" [style.color]="tag.text_color">{{tag.name}}</a>
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import { Component, Input, OnInit } from '@angular/core';
|
||||
import { TAG_COLOURS, PaperlessTag } from 'src/app/data/paperless-tag';
|
||||
import { PaperlessTag } from 'src/app/data/paperless-tag';
|
||||
|
||||
@Component({
|
||||
selector: 'app-tag',
|
||||
@@ -22,8 +22,4 @@ export class TagComponent implements OnInit {
|
||||
ngOnInit(): void {
|
||||
}
|
||||
|
||||
getColour() {
|
||||
return TAG_COLOURS.find(c => c.id == this.tag.colour)
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -3,5 +3,6 @@
|
||||
[header]="toast.title" [autohide]="true" [delay]="toast.delay"
|
||||
[class]="toast.classname"
|
||||
(hide)="toastService.closeToast(toast)">
|
||||
{{toast.content}}
|
||||
</ngb-toast>
|
||||
<p>{{toast.content}}</p>
|
||||
<p *ngIf="toast.action"><button class="btn btn-sm btn-outline-secondary" (click)="toastService.closeToast(toast); toast.action()">{{toast.actionName}}</button></p>
|
||||
</ngb-toast>
|
||||
|
Reference in New Issue
Block a user