mirror of
				https://github.com/paperless-ngx/paperless-ngx.git
				synced 2025-10-30 03:56:23 -05:00 
			
		
		
		
	Merge branch 'dev' into feature-bulk-editor
This commit is contained in:
		| @@ -56,6 +56,7 @@ import { FilterPipe } from './pipes/filter.pipe'; | ||||
| import { DocumentTitlePipe } from './pipes/document-title.pipe'; | ||||
| import { MetadataCollapseComponent } from './components/document-detail/metadata-collapse/metadata-collapse.component'; | ||||
| import { SelectDialogComponent } from './components/common/select-dialog/select-dialog.component'; | ||||
| import { NgSelectModule } from '@ng-select/ng-select'; | ||||
|  | ||||
| @NgModule({ | ||||
|   declarations: [ | ||||
| @@ -114,7 +115,8 @@ import { SelectDialogComponent } from './components/common/select-dialog/select- | ||||
|     ReactiveFormsModule, | ||||
|     NgxFileDropModule, | ||||
|     InfiniteScrollModule, | ||||
|     PdfViewerModule | ||||
|     PdfViewerModule, | ||||
|     NgSelectModule | ||||
|   ], | ||||
|   providers: [ | ||||
|     DatePipe, | ||||
| @@ -123,7 +125,8 @@ import { SelectDialogComponent } from './components/common/select-dialog/select- | ||||
|       useClass: CsrfInterceptor, | ||||
|       multi: true | ||||
|     }, | ||||
|     FilterPipe | ||||
|     FilterPipe, | ||||
|     DocumentTitlePipe | ||||
|   ], | ||||
|   bootstrap: [AppComponent] | ||||
| }) | ||||
|   | ||||
| @@ -17,6 +17,11 @@ | ||||
| <div class="container-fluid"> | ||||
|   <div class="row"> | ||||
|     <nav id="sidebarMenu" class="col-md-3 col-lg-2 d-md-block bg-light sidebar collapse" [ngbCollapse]="isMenuCollapsed"> | ||||
|  | ||||
|       <div style="position: absolute; bottom: 0; left: 0;" class="text-muted p-1"> | ||||
|         {{versionString}} | ||||
|       </div> | ||||
|  | ||||
|       <div class="sidebar-sticky pt-3"> | ||||
|         <ul class="nav flex-column"> | ||||
|           <li class="nav-item"> | ||||
| @@ -60,7 +65,7 @@ | ||||
|               <svg class="sidebaricon" fill="currentColor"> | ||||
|                 <use xlink:href="assets/bootstrap-icons.svg#file-text"/> | ||||
|               </svg> | ||||
|               {{d.title}} | ||||
|               {{d.title | documentTitle}} | ||||
|             </a> | ||||
|           </li> | ||||
|           <li class="nav-item w-100" *ngIf="openDocuments.length > 1"> | ||||
|   | ||||
| @@ -7,6 +7,7 @@ import { PaperlessDocument } from 'src/app/data/paperless-document'; | ||||
| import { OpenDocumentsService } from 'src/app/services/open-documents.service'; | ||||
| import { SavedViewService } from 'src/app/services/rest/saved-view.service'; | ||||
| import { SearchService } from 'src/app/services/rest/search.service'; | ||||
| import { environment } from 'src/environments/environment'; | ||||
| import { DocumentDetailComponent } from '../document-detail/document-detail.component'; | ||||
|    | ||||
| @Component({ | ||||
| @@ -25,6 +26,8 @@ export class AppFrameComponent implements OnInit, OnDestroy { | ||||
|     ) { | ||||
|   } | ||||
|  | ||||
|   versionString = `${environment.appTitle} ${environment.version}` | ||||
|  | ||||
|   isMenuCollapsed: boolean = true | ||||
|  | ||||
|   closeMenu() { | ||||
|   | ||||
| @@ -1,11 +1,15 @@ | ||||
| <div class="form-group"> | ||||
| <div class="form-group paperless-input-select"> | ||||
|   <label [for]="inputId">{{title}}</label> | ||||
|   <div [class.input-group]="showPlusButton()"> | ||||
|     <select class="form-control" [id]="inputId" [(ngModel)]="value" (change)="onChange(value)" (blur)="onTouched()" | ||||
|       [disabled]="disabled" [style.color]="textColor" [style.background]="backgroundColor"> | ||||
|       <option *ngIf="allowNull" [ngValue]="null" class="form-control">---</option> | ||||
|       <option *ngFor="let i of items" [ngValue]="i.id" class="form-control">{{i.name}}</option> | ||||
|     </select> | ||||
|     <ng-select name="correspondent" [(ngModel)]="value" | ||||
|       [disabled]="disabled" | ||||
|       [style.color]="textColor" | ||||
|       [style.background]="backgroundColor" | ||||
|       (change)="onChange(value)" | ||||
|       (blur)="onTouched()"> | ||||
|       <ng-option *ngFor="let i of items" [value]="i.id">{{i.name}}</ng-option> | ||||
|     </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"> | ||||
| @@ -15,4 +19,4 @@ | ||||
|     </div> | ||||
|   </div> | ||||
|   <small *ngIf="hint" class="form-text text-muted">{{hint}}</small> | ||||
| </div> | ||||
| </div> | ||||
|   | ||||
| @@ -0,0 +1 @@ | ||||
| // styles for ng-select child are in styles.scss | ||||
|   | ||||
| @@ -1,30 +1,41 @@ | ||||
| <div class="form-group"> | ||||
|   <label for="exampleFormControlTextarea1">Tags</label> | ||||
| <div class="form-group paperless-input-select paperless-input-tags"> | ||||
|   <label for="tags">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 flex-nowrap"> | ||||
|     <ng-select name="tags" [items]="tags" bindLabel="name" bindValue="id" [(ngModel)]="displayValue" | ||||
|       [multiple]="true" | ||||
|       [closeOnSelect]="false" | ||||
|       [disabled]="disabled" | ||||
|       (change)="ngSelectChange()"> | ||||
|  | ||||
|     <div class="input-group-append" ngbDropdown placement="top-right"> | ||||
|       <button class="btn btn-outline-secondary" type="button" ngbDropdownToggle></button> | ||||
|       <div ngbDropdownMenu class="scrollable-menu shadow"> | ||||
|         <button type="button" *ngFor="let tag of tags" ngbDropdownItem (click)="addTag(tag.id)"> | ||||
|           <app-tag [tag]="tag"></app-tag> | ||||
|         </button> | ||||
|       </div> | ||||
|     </div> | ||||
|       <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> | ||||
|         </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> | ||||
|         </div> | ||||
|       </ng-template> | ||||
|     </ng-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" *ngIf="hint">{{hint}}</small> | ||||
|  | ||||
| </div> | ||||
| </div> | ||||
|   | ||||
| @@ -1,10 +1,12 @@ | ||||
| .tags-form-control { | ||||
|   height: auto; | ||||
| .selected-icon { | ||||
|   min-width: 1em; | ||||
|   min-height: 1em; | ||||
| } | ||||
|  | ||||
| .tag-wrap { | ||||
|   font-size: 1rem; | ||||
| } | ||||
|  | ||||
| .scrollable-menu { | ||||
|   height: auto; | ||||
|   max-height: 300px; | ||||
|   overflow-x: hidden; | ||||
| } | ||||
| .tag-wrap-delete { | ||||
|   cursor: pointer; | ||||
| } | ||||
|   | ||||
| @@ -21,7 +21,7 @@ export class TagsComponent implements OnInit, ControlValueAccessor { | ||||
|  | ||||
|  | ||||
|   onChange = (newValue: number[]) => {}; | ||||
|    | ||||
|  | ||||
|   onTouched = () => {}; | ||||
|  | ||||
|   writeValue(newValue: number[]): void { | ||||
| @@ -66,29 +66,28 @@ export class TagsComponent implements OnInit, ControlValueAccessor { | ||||
|   removeTag(id) { | ||||
|     let index = this.displayValue.indexOf(id) | ||||
|     if (index > -1) { | ||||
|       this.displayValue.splice(index, 1) | ||||
|       let oldValue = this.displayValue | ||||
|       oldValue.splice(index, 1) | ||||
|       this.displayValue = [...oldValue] | ||||
|       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.listAll().subscribe(tags => { | ||||
|         this.tags = tags.results | ||||
|         this.addTag(newTag.id) | ||||
|         this.displayValue = [...this.displayValue, newTag.id] | ||||
|         this.onChange(this.displayValue) | ||||
|       }) | ||||
|     }) | ||||
|   } | ||||
|  | ||||
|   ngSelectChange() { | ||||
|     this.value = this.displayValue | ||||
|     this.onChange(this.displayValue) | ||||
|   } | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -13,7 +13,7 @@ | ||||
|     <tbody> | ||||
|       <tr *ngFor="let doc of documents" routerLink="/documents/{{doc.id}}"> | ||||
|         <td>{{doc.created | date}}</td> | ||||
|         <td>{{doc.title}}<app-tag [tag]="t" *ngFor="let t of doc.tags$ | async" class="ml-1"></app-tag> | ||||
|         <td>{{doc.title | documentTitle}}<app-tag [tag]="t" *ngFor="let t of doc.tags$ | async" class="ml-1"></app-tag> | ||||
|       </tr> | ||||
|     </tbody> | ||||
|   </table> | ||||
|   | ||||
| @@ -52,9 +52,9 @@ | ||||
|                         </div> | ||||
|                         <app-input-date-time titleDate="Date created" formControlName="created"></app-input-date-time> | ||||
|                         <app-input-select [items]="correspondents" title="Correspondent" formControlName="correspondent" | ||||
|                             allowNull="true" (createNew)="createCorrespondent()"></app-input-select> | ||||
|                             (createNew)="createCorrespondent()"></app-input-select> | ||||
|                         <app-input-select [items]="documentTypes" title="Document type" formControlName="document_type" | ||||
|                             allowNull="true" (createNew)="createDocumentType()"></app-input-select> | ||||
|                             (createNew)="createDocumentType()"></app-input-select> | ||||
|                         <app-input-tags formControlName="tags" title="Tags"></app-input-tags> | ||||
|  | ||||
|                     </ng-template> | ||||
| @@ -110,8 +110,8 @@ | ||||
|                             </tbody> | ||||
|                         </table> | ||||
|  | ||||
|                         <app-metadata-collapse title="Original document metadata" [metadata]="metadata.original_metadata" *ngIf="metadata?.original_metadata.length > 0"></app-metadata-collapse> | ||||
|                         <app-metadata-collapse title="Archived document metadata" [metadata]="metadata.archive_metadata" *ngIf="metadata?.archive_metadata.length > 0"></app-metadata-collapse> | ||||
|                         <app-metadata-collapse title="Original document metadata" [metadata]="metadata.original_metadata" *ngIf="metadata?.original_metadata?.length > 0"></app-metadata-collapse> | ||||
|                         <app-metadata-collapse title="Archived document metadata" [metadata]="metadata.archive_metadata" *ngIf="metadata?.archive_metadata?.length > 0"></app-metadata-collapse> | ||||
|  | ||||
|                     </ng-template> | ||||
|                 </li> | ||||
|   | ||||
| @@ -6,6 +6,7 @@ import { PaperlessCorrespondent } from 'src/app/data/paperless-correspondent'; | ||||
| import { PaperlessDocument } from 'src/app/data/paperless-document'; | ||||
| import { PaperlessDocumentMetadata } from 'src/app/data/paperless-document-metadata'; | ||||
| import { PaperlessDocumentType } from 'src/app/data/paperless-document-type'; | ||||
| import { DocumentTitlePipe } from 'src/app/pipes/document-title.pipe'; | ||||
| import { DocumentListViewService } from 'src/app/services/document-list-view.service'; | ||||
| import { OpenDocumentsService } from 'src/app/services/open-documents.service'; | ||||
| import { CorrespondentService } from 'src/app/services/rest/correspondent.service'; | ||||
| @@ -54,7 +55,8 @@ export class DocumentDetailComponent implements OnInit { | ||||
|     private router: Router, | ||||
|     private modalService: NgbModal, | ||||
|     private openDocumentService: OpenDocumentsService, | ||||
|     private documentListViewService: DocumentListViewService) { } | ||||
|     private documentListViewService: DocumentListViewService, | ||||
|     private documentTitlePipe: DocumentTitlePipe) { } | ||||
|  | ||||
|   getContentType() { | ||||
|     return this.metadata?.has_archive_version ? 'application/pdf' : this.metadata?.original_mime_type | ||||
| @@ -90,7 +92,7 @@ export class DocumentDetailComponent implements OnInit { | ||||
|     this.documentsService.getMetadata(doc.id).subscribe(result => { | ||||
|       this.metadata = result | ||||
|     }) | ||||
|     this.title = doc.title | ||||
|     this.title = this.documentTitlePipe.transform(doc.title) | ||||
|     this.documentForm.patchValue(doc) | ||||
|   } | ||||
|  | ||||
|   | ||||
| @@ -1,5 +1,6 @@ | ||||
| .result-content { | ||||
|   color: darkgray; | ||||
|   overflow-wrap: anywhere; | ||||
| } | ||||
|  | ||||
| .doc-img { | ||||
|   | ||||
| @@ -31,7 +31,7 @@ | ||||
|     </div> | ||||
|     <div class="card-footer"> | ||||
|  | ||||
|       <div class="d-flex justify-content-between align-items-center ml-n2"> | ||||
|       <div class="d-flex justify-content-between align-items-center mx-n2"> | ||||
|         <div class="btn-group"> | ||||
|           <a routerLink="/documents/{{document.id}}" class="btn btn-sm btn-outline-secondary" title="Edit"> | ||||
|             <svg width="1em" height="1em" viewBox="0 0 16 16" class="bi bi-pencil" fill="currentColor" xmlns="http://www.w3.org/2000/svg"> | ||||
| @@ -51,7 +51,7 @@ | ||||
|             </svg> | ||||
|           </a> | ||||
|         </div> | ||||
|         <small class="text-muted">{{document.created | date}}</small> | ||||
|         <small class="text-muted pl-1">{{document.created | date}}</small> | ||||
|       </div> | ||||
|  | ||||
|     </div> | ||||
|   | ||||
| @@ -77,7 +77,7 @@ | ||||
|  | ||||
| </app-page-header> | ||||
|  | ||||
| <div class="w-100 mb-4"> | ||||
| <div class="w-100 mb-2 mb-sm-4"> | ||||
|   <app-filter-editor [(filterRules)]="list.filterRules" #filterEditor></app-filter-editor> | ||||
| </div> | ||||
|  | ||||
| @@ -135,7 +135,7 @@ | ||||
|       </td> | ||||
|       <td> | ||||
|         <a routerLink="/documents/{{d.id}}" title="Edit document" style="overflow-wrap: anywhere;">{{d.title | documentTitle}}</a> | ||||
|         <app-tag [tag]="t" *ngFor="let t of d.tags$ | async" class="ml-1" clickable="true" linkTitle="Filter by tag" (click)="clickTag(t)"></app-tag> | ||||
|         <app-tag [tag]="t" *ngFor="let t of d.tags$ | async" class="ml-1" clickable="true" linkTitle="Filter by tag" (click)="clickTag(t.id)"></app-tag> | ||||
|       </td> | ||||
|       <td class="d-none d-xl-table-cell"> | ||||
|         <ng-container *ngIf="d.document_type"> | ||||
| @@ -154,5 +154,5 @@ | ||||
|  | ||||
|  | ||||
| <div class=" m-n2 row" *ngIf="displayMode == 'smallCards'"> | ||||
|   <app-document-card-small [selected]="list.isSelected(d)" (selectedChange)="list.setSelected(d, $event)"  [document]="d" *ngFor="let d of list.documents" (clickTag)="clickTag($event)" (clickCorrespondent)="clickCorrespondent($event)"></app-document-card-small> | ||||
|   <app-document-card-small [selected]="list.isSelected(d)" (selectedChange)="list.setSelected(d, $event)" [document]="d" *ngFor="let d of list.documents" (clickTag)="clickTag($event)" (clickCorrespondent)="clickCorrespondent($event)"></app-document-card-small> | ||||
| </div> | ||||
|   | ||||
| @@ -2,4 +2,26 @@ | ||||
|  | ||||
| .table-row-selected { | ||||
|   background-color: $primaryFaded; | ||||
| } | ||||
| } | ||||
|  | ||||
| $paperless-card-breakpoints: ( | ||||
|   0: 2, // xs | ||||
|   768px: 3, //md | ||||
|   992px: 4, //lg | ||||
|   1200px: 5, //xl | ||||
|   1400px: 6, // xxl | ||||
|   1600px: 7, | ||||
|   1800px: 8, | ||||
|   2000px: 9 | ||||
| ); | ||||
|  | ||||
| .row-cols-paperless-cards { | ||||
|   @each $width, $n_cols in $paperless-card-breakpoints { | ||||
|     @media(min-width: $width) { | ||||
|       > * { | ||||
|         flex: 0 0 auto; | ||||
|         width: 100% / $n_cols; | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -98,6 +98,7 @@ export class DocumentListComponent implements OnInit { | ||||
|  | ||||
|   saveViewConfigAs() { | ||||
|     let modal = this.modalService.open(SaveViewConfigDialogComponent, {backdrop: 'static'}) | ||||
|     modal.componentInstance.defaultName = this.filterEditor.generateFilterName() | ||||
|     modal.componentInstance.saveClicked.subscribe(formValue => { | ||||
|       let savedView = { | ||||
|         name: formValue.name, | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| import { Component, EventEmitter, OnInit, Output } from '@angular/core'; | ||||
| import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; | ||||
| import { FormControl, FormGroup } from '@angular/forms'; | ||||
| import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; | ||||
|  | ||||
| @@ -14,6 +14,19 @@ export class SaveViewConfigDialogComponent implements OnInit { | ||||
|   @Output() | ||||
|   public saveClicked = new EventEmitter() | ||||
|  | ||||
|   _defaultName = "" | ||||
|  | ||||
|   get defaultName() { | ||||
|     return this._defaultName | ||||
|   } | ||||
|  | ||||
|   @Input() | ||||
|   set defaultName(value: string) { | ||||
|     this._defaultName = value | ||||
|     this.saveViewConfigForm.patchValue({name: value}) | ||||
|   } | ||||
|    | ||||
|  | ||||
|   saveViewConfigForm = new FormGroup({ | ||||
|     name: new FormControl(''), | ||||
|     showInSideBar: new FormControl(false), | ||||
|   | ||||
| @@ -4,38 +4,39 @@ | ||||
|   </button> | ||||
|   <div class="dropdown-menu date-filter shadow pt-0" ngbDropdownMenu attr.aria-labelledby="dropdown{{title}}"> | ||||
|     <div class="list-group list-group-flush"> | ||||
|         <button class="list-group-item small list-goup list-group-item-action d-flex p-2 pl-3" (click)="clear()">Clear</button> | ||||
|         <button *ngFor="let range of [7, 30, 'month', 'year']" class="list-group-item small list-goup list-group-item-action d-flex p-2 pl-3" role="menuitem" (click)="setDateQuickFilter(range)"> | ||||
|           <ng-container *ngIf="isStringRange(range)">This </ng-container> | ||||
|           {{ range }} | ||||
|           <ng-container *ngIf="!isStringRange(range)"> days</ng-container> | ||||
|         <button *ngFor="let qf of quickFilters" class="list-group-item small list-goup list-group-item-action d-flex p-2 pl-3" role="menuitem" (click)="setDateQuickFilter(qf.id)"> | ||||
|           {{qf.name}} | ||||
|         </button> | ||||
|         <div class="list-group-item d-flex flex-column align-items-start" role="menuitem"> | ||||
|           <div>Before</div> | ||||
|  | ||||
|           <div class="mb-2 d-flex flex-row w-100 justify-content-between small"> | ||||
|             <div>After</div> | ||||
|             <a *ngIf="dateAfter" class="btn btn-link p-0 m-0" (click)="clearAfter()"> | ||||
|               <svg width="0.8em" height="0.8em" viewBox="0 0 16 16" class="bi bi-x" fill="currentColor" xmlns="http://www.w3.org/2000/svg"> | ||||
|                 <path fill-rule="evenodd" 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> | ||||
|               <small>Clear</small> | ||||
|             </a> | ||||
|           </div> | ||||
|  | ||||
|           <div class="input-group input-group-sm"> | ||||
|             <input class="form-control" type="text" placeholder="yyyy-mm-dd" name="before" [(ngModel)]="_dateBefore" [maxDate]="this._maxDate" ngbDatepicker (dateSelect)="onBeforeSelected($event)" #dpBefore="ngbDatepicker"> | ||||
|             <div class="input-group-append"> | ||||
|               <button class="btn btn-outline-secondary btn-sm" (click)="dpBefore.toggle()" type="button"> | ||||
|                 <svg width="1em" height="1em" viewBox="0 0 16 16" class="bi bi-calendar-date" fill="currentColor" xmlns="http://www.w3.org/2000/svg"> | ||||
|                   <path fill-rule="evenodd" 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"/> | ||||
|                   <path d="M6.445 11.688V6.354h-.633A12.6 12.6 0 0 0 4.5 7.16v.695c.375-.257.969-.62 1.258-.777h.012v4.61h.675zm1.188-1.305c.047.64.594 1.406 1.703 1.406 1.258 0 2-1.066 2-2.871 0-1.934-.781-2.668-1.953-2.668-.926 0-1.797.672-1.797 1.809 0 1.16.824 1.77 1.676 1.77.746 0 1.23-.376 1.383-.79h.027c-.004 1.316-.461 2.164-1.305 2.164-.664 0-1.008-.45-1.05-.82h-.684zm2.953-2.317c0 .696-.559 1.18-1.184 1.18-.601 0-1.144-.383-1.144-1.2 0-.823.582-1.21 1.168-1.21.633 0 1.16.398 1.16 1.23z"/> | ||||
|                 </svg> | ||||
|               </button> | ||||
|             </div> | ||||
|             <input type="date" class="form-control" id="date_after" [(ngModel)]="dateAfter" (change)="onChangeDebounce()"> | ||||
|           </div> | ||||
|         </div> | ||||
|         <div class="list-group-item d-flex flex-column align-items-start" role="menuitem"> | ||||
|           <div>After</div> | ||||
|  | ||||
|           <div class="mb-2 d-flex flex-row w-100 justify-content-between small"> | ||||
|             <div>Before</div> | ||||
|             <a *ngIf="dateBefore" class="btn btn-link p-0 m-0" (click)="clearBefore()"> | ||||
|               <svg width="0.8em" height="0.8em" viewBox="0 0 16 16" class="bi bi-x" fill="currentColor" xmlns="http://www.w3.org/2000/svg"> | ||||
|                 <path fill-rule="evenodd" 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> | ||||
|               <small>Clear</small> | ||||
|             </a> | ||||
|           </div> | ||||
|  | ||||
|           <div class="input-group input-group-sm"> | ||||
|             <input class="form-control form-control-sm" type="text" placeholder="yyyy-mm-dd" name="after" [(ngModel)]="_dateAfter" [maxDate]="this._maxDate" ngbDatepicker (dateSelect)="onAfterSelected($event)" #dpAfter="ngbDatepicker"> | ||||
|             <div class="input-group-append"> | ||||
|               <button class="btn btn-outline-secondary btn-sm" (click)="dpAfter.toggle()" type="button"> | ||||
|                 <svg width="1em" height="1em" viewBox="0 0 16 16" class="bi bi-calendar-date" fill="currentColor" xmlns="http://www.w3.org/2000/svg"> | ||||
|                   <path fill-rule="evenodd" 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"/> | ||||
|                   <path d="M6.445 11.688V6.354h-.633A12.6 12.6 0 0 0 4.5 7.16v.695c.375-.257.969-.62 1.258-.777h.012v4.61h.675zm1.188-1.305c.047.64.594 1.406 1.703 1.406 1.258 0 2-1.066 2-2.871 0-1.934-.781-2.668-1.953-2.668-.926 0-1.797.672-1.797 1.809 0 1.16.824 1.77 1.676 1.77.746 0 1.23-.376 1.383-.79h.027c-.004 1.316-.461 2.164-1.305 2.164-.664 0-1.008-.45-1.05-.82h-.684zm2.953-2.317c0 .696-.559 1.18-1.184 1.18-.601 0-1.144-.383-1.144-1.2 0-.823.582-1.21 1.168-1.21.633 0 1.16.398 1.16 1.23z"/> | ||||
|                 </svg> | ||||
|               </button> | ||||
|             </div> | ||||
|             <input type="date" class="form-control" id="date_before" [(ngModel)]="dateBefore" (change)="onChangeDebounce()"> | ||||
|           </div> | ||||
|         </div> | ||||
|     </div> | ||||
|   | ||||
| @@ -1,25 +1,37 @@ | ||||
| import { Component, EventEmitter, Input, Output, ElementRef, ViewChild, SimpleChange } from '@angular/core'; | ||||
| import { NgbDate, NgbDateStruct, NgbDatepicker } from '@ng-bootstrap/ng-bootstrap'; | ||||
|  | ||||
| import { formatDate } from '@angular/common'; | ||||
| import { Component, EventEmitter, Input, Output, OnInit, OnDestroy } from '@angular/core'; | ||||
| import { Subject, Subscription } from 'rxjs'; | ||||
| import { debounceTime, distinctUntilChanged } from 'rxjs/operators'; | ||||
|  | ||||
| export interface DateSelection { | ||||
|   before?: NgbDateStruct | ||||
|   after?: NgbDateStruct | ||||
|   before?: string | ||||
|   after?: string | ||||
| } | ||||
|  | ||||
| const FILTER_LAST_7_DAYS = 0 | ||||
| const FILTER_LAST_MONTH = 1 | ||||
| const FILTER_LAST_3_MONTHS = 2 | ||||
| const FILTER_LAST_YEAR = 3 | ||||
|  | ||||
| @Component({ | ||||
|   selector: 'app-filter-dropdown-date', | ||||
|   templateUrl: './filter-dropdown-date.component.html', | ||||
|   styleUrls: ['./filter-dropdown-date.component.scss'] | ||||
| }) | ||||
| export class FilterDropdownDateComponent { | ||||
| export class FilterDropdownDateComponent implements OnInit, OnDestroy { | ||||
|  | ||||
|   quickFilters = [ | ||||
|     {id: FILTER_LAST_7_DAYS, name: "Last 7 days"},  | ||||
|     {id: FILTER_LAST_MONTH, name: "Last month"}, | ||||
|     {id: FILTER_LAST_3_MONTHS, name: "Last 3 months"}, | ||||
|     {id: FILTER_LAST_YEAR, name: "Last year"} | ||||
|   ] | ||||
|  | ||||
|   @Input() | ||||
|   dateBefore: NgbDateStruct | ||||
|   dateBefore: string | ||||
|  | ||||
|   @Input() | ||||
|   dateAfter: NgbDateStruct | ||||
|   dateAfter: string | ||||
|  | ||||
|   @Input() | ||||
|   title: string | ||||
| @@ -27,83 +39,65 @@ export class FilterDropdownDateComponent { | ||||
|   @Output() | ||||
|   datesSet = new EventEmitter<DateSelection>() | ||||
|  | ||||
|   @ViewChild('dpAfter') dpAfter: NgbDatepicker | ||||
|   @ViewChild('dpBefore') dpBefore: NgbDatepicker | ||||
|   private datesSetDebounce$ = new Subject() | ||||
|  | ||||
|   _dateBefore: NgbDateStruct | ||||
|   _dateAfter: NgbDateStruct | ||||
|  | ||||
|   get _maxDate(): NgbDate { | ||||
|     let date = new Date() | ||||
|     return NgbDate.from({year: date.getFullYear(), month: date.getMonth() + 1, day: date.getDate()}) | ||||
|   private sub: Subscription | ||||
|    | ||||
|   ngOnInit() { | ||||
|     this.sub = this.datesSetDebounce$.pipe( | ||||
|       debounceTime(400) | ||||
|     ).subscribe(() => { | ||||
|       this.onChange() | ||||
|     }) | ||||
|   } | ||||
|  | ||||
|   isStringRange(range: any) { | ||||
|     return typeof range == 'string' | ||||
|   } | ||||
|  | ||||
|   ngOnChanges(changes: SimpleChange) { | ||||
|     // this is a hacky workaround perhaps because of https://github.com/angular/angular/issues/11097 | ||||
|     let dateString: string = '' | ||||
|     let dateAfterChange: SimpleChange | ||||
|     let dateBeforeChange: SimpleChange | ||||
|     if (changes) { | ||||
|       dateAfterChange = changes['dateAfter'] | ||||
|       dateBeforeChange = changes['dateBefore'] | ||||
|   ngOnDestroy() { | ||||
|     if (this.sub) { | ||||
|       this.sub.unsubscribe() | ||||
|     } | ||||
|   } | ||||
|  | ||||
|     if (this.dpBefore && this.dpAfter) { | ||||
|       let dpAfterElRef: ElementRef = this.dpAfter['_elRef'] | ||||
|       let dpBeforeElRef: ElementRef = this.dpBefore['_elRef'] | ||||
|   setDateQuickFilter(qf: number) { | ||||
|     this.dateBefore = null | ||||
|     let date = new Date() | ||||
|     switch (qf) { | ||||
|       case FILTER_LAST_7_DAYS: | ||||
|         date.setDate(date.getDate() - 7) | ||||
|         break; | ||||
|  | ||||
|       if (dateAfterChange && dateAfterChange.currentValue) { | ||||
|         let dateAfterDate = dateAfterChange.currentValue as NgbDateStruct | ||||
|         dateString = `${dateAfterDate.year}-${dateAfterDate.month.toString().padStart(2,'0')}-${dateAfterDate.day.toString().padStart(2,'0')}` | ||||
|         dpAfterElRef.nativeElement.value = dateString | ||||
|       } else if (dateBeforeChange && dateBeforeChange.currentValue) { | ||||
|         let dateBeforeDate = dateBeforeChange.currentValue as NgbDateStruct | ||||
|         dateString = `${dateBeforeDate.year}-${dateBeforeDate.month.toString().padStart(2,'0')}-${dateBeforeDate.day.toString().padStart(2,'0')}` | ||||
|         dpBeforeElRef.nativeElement.value = dateString | ||||
|       } else { | ||||
|         dpAfterElRef.nativeElement.value = dateString | ||||
|         dpBeforeElRef.nativeElement.value = dateString | ||||
|       case FILTER_LAST_MONTH: | ||||
|         date.setMonth(date.getMonth() - 1) | ||||
|         break; | ||||
|  | ||||
|       case FILTER_LAST_3_MONTHS: | ||||
|         date.setMonth(date.getMonth() - 3) | ||||
|         break | ||||
|  | ||||
|       case FILTER_LAST_YEAR: | ||||
|         date.setFullYear(date.getFullYear() - 1) | ||||
|         break | ||||
|    | ||||
|       } | ||||
|     } | ||||
|     this.dateAfter = formatDate(date, 'yyyy-MM-dd', "en-us", "UTC") | ||||
|     this.onChange() | ||||
|   } | ||||
|  | ||||
|   setDateQuickFilter(range: any) { | ||||
|     this._dateAfter = this._dateBefore = undefined | ||||
|     let date = new Date() | ||||
|     let newDate: NgbDateStruct = { year: date.getFullYear(), month: date.getMonth() + 1, day: date.getDate() } | ||||
|     switch (typeof range) { | ||||
|       case 'number': | ||||
|         date.setDate(date.getDate() - range) | ||||
|         newDate.year = date.getFullYear() | ||||
|         newDate.month = date.getMonth() + 1 | ||||
|         newDate.day = date.getDate() | ||||
|         break | ||||
|  | ||||
|       case 'string': | ||||
|         newDate.day = 1 | ||||
|         if (range == 'year') newDate.month = 1 | ||||
|         break | ||||
|  | ||||
|       default: | ||||
|         break | ||||
|     } | ||||
|     this._dateAfter = newDate | ||||
|     this.datesSet.emit({after: newDate, before: null}) | ||||
|   onChange() { | ||||
|     this.datesSet.emit({after: this.dateAfter, before: this.dateBefore}) | ||||
|   } | ||||
|  | ||||
|   onBeforeSelected(date: NgbDateStruct) { | ||||
|     this.datesSet.emit({after: this._dateAfter, before: date}) | ||||
|   onChangeDebounce() { | ||||
|     this.datesSetDebounce$.next({after: this.dateAfter, before: this.dateBefore}) | ||||
|   } | ||||
|  | ||||
|   onAfterSelected(date: NgbDateStruct) { | ||||
|     this.datesSet.emit({after: date, before: this._dateBefore}) | ||||
|   clearBefore() { | ||||
|     this.dateBefore = null; | ||||
|     this.onChange() | ||||
|   } | ||||
|  | ||||
|   clear() { | ||||
|     this.datesSet.emit({after: null, before: null}) | ||||
|   clearAfter() { | ||||
|     this.dateAfter = null; | ||||
|     this.onChange() | ||||
|   } | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -1,6 +1,16 @@ | ||||
| <div class="btn-group" ngbDropdown role="group" (openChange)="dropdownOpenChange($event)" #filterDropdown="ngbDropdown"> | ||||
|   <button class="btn btn-sm" id="dropdown{{title}}" ngbDropdownToggle [ngClass]="itemsSelected?.length > 0 ? 'btn-primary' : 'btn-outline-primary'"> | ||||
|     {{title}} | ||||
|     <div class="d-none d-md-inline">{{title}}</div> | ||||
|     <div class="d-inline-block d-md-none"> | ||||
|       <svg class="toolbaricon" fill="currentColor"> | ||||
|         <use attr.xlink:href="assets/bootstrap-icons.svg#{{icon}}" /> | ||||
|       </svg> | ||||
|     </div> | ||||
|     <ng-container *ngIf="itemsSelected?.length > 0"> | ||||
|       <div class="badge bg-secondary text-light rounded-pill badge-corner"> | ||||
|         {{itemsSelected?.length}} | ||||
|       </div> | ||||
|     </ng-container> | ||||
|   </button> | ||||
|   <div class="dropdown-menu py-0 shadow" ngbDropdownMenu attr.aria-labelledby="dropdown{{title}}"> | ||||
|     <div class="list-group list-group-flush"> | ||||
|   | ||||
| @@ -1,3 +1,9 @@ | ||||
| .badge-corner { | ||||
|   position: absolute; | ||||
|   top: -8px; | ||||
|   right: -8px; | ||||
| } | ||||
|  | ||||
| .dropdown-menu { | ||||
|   min-width: 250px; | ||||
|  | ||||
|   | ||||
| @@ -22,7 +22,7 @@ export class FilterDropdownComponent { | ||||
|   title: string | ||||
|  | ||||
|   @Input() | ||||
|   display: string | ||||
|   icon: string | ||||
|  | ||||
|   @Output() | ||||
|   toggle = new EventEmitter() | ||||
|   | ||||
| @@ -1,22 +1,27 @@ | ||||
| <div class="form-row form-group mb-0"> | ||||
|   <div class="col-auto"> | ||||
|     <div class="text-muted mt-1">Filter by:</div> | ||||
| <div class="row"> | ||||
|    <div class="col mb-2 mb-xl-0"> | ||||
|      <div class="form-inline d-flex"> | ||||
|          <label class="text-muted mr-2">Filter by:</label> | ||||
|          <input class="form-control form-control-sm flex-grow-1" type="text" [(ngModel)]="titleFilter" placeholder="Title"> | ||||
|      </div> | ||||
|   </div> | ||||
|   <div class="col"> | ||||
|     <input class="form-control form-control-sm" type="text" [(ngModel)]="titleFilter" placeholder="Title"> | ||||
|   </div> | ||||
|  | ||||
|   <app-filter-dropdown class="col-auto" [items]="tags" [itemsSelected]="selectedTags" title="Tags" (toggle)="toggleTag($event.id)"></app-filter-dropdown> | ||||
|   <app-filter-dropdown class="col-auto" [items]="correspondents" [itemsSelected]="selectedCorrespondents" title="Correspondents" (toggle)="toggleCorrespondent($event.id)"></app-filter-dropdown> | ||||
|   <app-filter-dropdown class="col-auto" [items]="documentTypes" [itemsSelected]="selectedDocumentTypes" title="Document types" (toggle)="toggleDocumentType($event.id)"></app-filter-dropdown> | ||||
|  | ||||
|   <app-filter-dropdown-date class="col-auto" [dateBefore]="dateCreatedBefore" [dateAfter]="dateCreatedAfter" title="Created" (datesSet)="onDatesCreatedSet($event)"></app-filter-dropdown-date> | ||||
|   <app-filter-dropdown-date class="col-auto" [dateBefore]="dateAddedBefore" [dateAfter]="dateAddedAfter" title="Added"  (datesSet)="onDatesAddedSet($event)"></app-filter-dropdown-date> | ||||
|  | ||||
|   <button class="btn btn-link btn-sm" [disabled]="!hasFilters()" (click)="clearSelected()"> | ||||
|     <svg width="1em" height="1em" viewBox="0 0 16 16" fill="currentColor"> | ||||
|       <use xlink:href="assets/bootstrap-icons.svg#x" /> | ||||
|     </svg> | ||||
|     Clear all filters | ||||
|   </button> | ||||
|   <div class="w-100 d-xl-none"></div> | ||||
|    <div class="col col-xl-auto mb-2 mb-xl-0"> | ||||
|      <div class="d-flex"> | ||||
|        <app-filter-dropdown class="mr-2 mr-md-3" [items]="tags" [itemsSelected]="selectedTags" title="Tags" icon="tag-fill" (toggle)="toggleTag($event.id)"></app-filter-dropdown> | ||||
|        <app-filter-dropdown class="mr-2 mr-md-3" [items]="correspondents" [itemsSelected]="selectedCorrespondents" title="Correspondents" icon="person-fill" (toggle)="toggleCorrespondent($event.id)"></app-filter-dropdown> | ||||
|        <app-filter-dropdown class="mr-2 mr-md-3" [items]="documentTypes" [itemsSelected]="selectedDocumentTypes" title="Document types" icon="file-earmark-fill" (toggle)="toggleDocumentType($event.id)"></app-filter-dropdown> | ||||
|        <app-filter-dropdown-date class="mr-2 mr-md-3" [dateBefore]="dateCreatedBefore" [dateAfter]="dateCreatedAfter" title="Created" (datesSet)="onDatesCreatedSet($event)"></app-filter-dropdown-date> | ||||
|        <app-filter-dropdown-date [dateBefore]="dateAddedBefore" [dateAfter]="dateAddedAfter" title="Added"  (datesSet)="onDatesAddedSet($event)"></app-filter-dropdown-date> | ||||
|      </div> | ||||
|    </div> | ||||
|    <div class="w-100 d-xl-none"></div> | ||||
|    <div class="col col-xl-auto mb-2 mb-xl-0"> | ||||
|      <button class="btn btn-link btn-sm px-0 mx-0 ml-xl-n4" [disabled]="!hasFilters()" (click)="clearSelected()"> | ||||
|        <svg width="1em" height="1em" viewBox="0 0 16 16" class="bi bi-x" fill="currentColor" xmlns="http://www.w3.org/2000/svg"> | ||||
|          <path fill-rule="evenodd" 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> | ||||
|        Clear all filters | ||||
|      </button> | ||||
|    </div> | ||||
| </div> | ||||
|   | ||||
| @@ -19,6 +19,26 @@ import { DateSelection } from './filter-dropdown-date/filter-dropdown-date.compo | ||||
| }) | ||||
| export class FilterEditorComponent implements OnInit, OnDestroy { | ||||
|  | ||||
|   generateFilterName() { | ||||
|     if (this.filterRules.length == 1) { | ||||
|       let rule = this.filterRules[0] | ||||
|       switch(this.filterRules[0].rule_type) { | ||||
|          | ||||
|         case FILTER_CORRESPONDENT: | ||||
|           return `Correspondent: ${this.correspondents.find(c => c.id == +rule.value)?.name}` | ||||
|  | ||||
|         case FILTER_DOCUMENT_TYPE: | ||||
|           return `Type: ${this.documentTypes.find(dt => dt.id == +rule.value)?.name}` | ||||
|  | ||||
|         case FILTER_HAS_TAG: | ||||
|           return `Tag: ${this.tags.find(t => t.id == +rule.value)?.name}` | ||||
|  | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     return "" | ||||
|   } | ||||
|  | ||||
|   constructor( | ||||
|     private documentTypeService: DocumentTypeService, | ||||
|     private tagService: TagService, | ||||
| @@ -159,65 +179,61 @@ export class FilterEditorComponent implements OnInit, OnDestroy { | ||||
|     this.applyFilters() | ||||
|   } | ||||
|  | ||||
|   get dateCreatedBefore(): NgbDateStruct { | ||||
|   get dateCreatedBefore(): string { | ||||
|     let createdBeforeRule: FilterRule = this.filterRules.find(fr => fr.rule_type == FILTER_CREATED_BEFORE) | ||||
|     return createdBeforeRule ? this.dateParser.parse(createdBeforeRule.value) : null | ||||
|     return createdBeforeRule ? createdBeforeRule.value : null | ||||
|   } | ||||
|  | ||||
|   get dateCreatedAfter(): NgbDateStruct { | ||||
|   get dateCreatedAfter(): string { | ||||
|     let createdAfterRule: FilterRule = this.filterRules.find(fr => fr.rule_type == FILTER_CREATED_AFTER) | ||||
|     return createdAfterRule ? this.dateParser.parse(createdAfterRule.value) : null | ||||
|     return createdAfterRule ? createdAfterRule.value : null | ||||
|   } | ||||
|  | ||||
|   get dateAddedBefore(): NgbDateStruct { | ||||
|   get dateAddedBefore(): string { | ||||
|     let addedBeforeRule: FilterRule = this.filterRules.find(fr => fr.rule_type == FILTER_ADDED_BEFORE) | ||||
|     return addedBeforeRule ? this.dateParser.parse(addedBeforeRule.value) : null | ||||
|     return addedBeforeRule ? addedBeforeRule.value : null | ||||
|   } | ||||
|  | ||||
|   get dateAddedAfter(): NgbDateStruct { | ||||
|   get dateAddedAfter(): string { | ||||
|     let addedAfterRule: FilterRule = this.filterRules.find(fr => fr.rule_type == FILTER_ADDED_AFTER) | ||||
|     return addedAfterRule ? this.dateParser.parse(addedAfterRule.value) : null | ||||
|     return addedAfterRule ? addedAfterRule.value : null | ||||
|   } | ||||
|  | ||||
|   setDateCreatedBefore(date?: NgbDateStruct) { | ||||
|   setDateCreatedBefore(date?: string) { | ||||
|     if (date) this.setDateFilter(date, FILTER_CREATED_BEFORE) | ||||
|     else this.clearDateFilter(FILTER_CREATED_BEFORE) | ||||
|   } | ||||
|  | ||||
|   setDateCreatedAfter(date?: NgbDateStruct) { | ||||
|   setDateCreatedAfter(date?: string) { | ||||
|     if (date) this.setDateFilter(date, FILTER_CREATED_AFTER) | ||||
|     else this.clearDateFilter(FILTER_CREATED_AFTER) | ||||
|   } | ||||
|  | ||||
|   setDateAddedBefore(date?: NgbDateStruct) { | ||||
|   setDateAddedBefore(date?: string) { | ||||
|     if (date) this.setDateFilter(date, FILTER_ADDED_BEFORE) | ||||
|     else this.clearDateFilter(FILTER_ADDED_BEFORE) | ||||
|   } | ||||
|  | ||||
|   setDateAddedAfter(date?: NgbDateStruct) { | ||||
|   setDateAddedAfter(date?: string) { | ||||
|     if (date) this.setDateFilter(date, FILTER_ADDED_AFTER) | ||||
|     else this.clearDateFilter(FILTER_ADDED_AFTER) | ||||
|   } | ||||
|  | ||||
|   setDateFilter(date: NgbDateStruct, dateRuleTypeID: number) { | ||||
|     let filterRules = this.filterRules | ||||
|     let existingRule = filterRules.find(rule => rule.rule_type == dateRuleTypeID) | ||||
|     let newValue = this.dateParser.format(date) | ||||
|   setDateFilter(date: string, dateRuleTypeID: number) { | ||||
|     let existingRule = this.filterRules.find(rule => rule.rule_type == dateRuleTypeID) | ||||
|  | ||||
|     if (existingRule) { | ||||
|       existingRule.value = newValue | ||||
|       existingRule.value = date | ||||
|     } else { | ||||
|       filterRules.push({rule_type: dateRuleTypeID, value: newValue}) | ||||
|       this.filterRules.push({rule_type: dateRuleTypeID, value: date}) | ||||
|     } | ||||
|  | ||||
|     this.filterRules = filterRules | ||||
|   } | ||||
|  | ||||
|   clearDateFilter(dateRuleTypeID: number) { | ||||
|     let filterRules = this.filterRules | ||||
|     let existingRule = filterRules.find(rule => rule.rule_type == dateRuleTypeID) | ||||
|     filterRules.splice(filterRules.indexOf(existingRule), 1) | ||||
|     this.filterRules = filterRules | ||||
|     let ruleIndex = this.filterRules.findIndex(rule => rule.rule_type == dateRuleTypeID) | ||||
|     if (ruleIndex != -1) { | ||||
|       this.filterRules.splice(ruleIndex, 1) | ||||
|     } | ||||
|   } | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -26,9 +26,26 @@ | ||||
|       <td scope="row">{{ correspondent.last_correspondence | date }}</td> | ||||
|         <td scope="row"> | ||||
|           <div class="btn-group"> | ||||
|           <button class="btn btn-sm btn-outline-secondary" (click)="openEditDialog(correspondent)">Edit</button> | ||||
|           <button class="btn btn-sm btn-outline-danger" (click)="openDeleteDialog(correspondent)">Delete</button> | ||||
|         </div> | ||||
|             <button class="btn btn-sm btn-outline-secondary" (click)="filterDocuments(correspondent)"> | ||||
|               <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-funnel" viewBox="0 0 16 16"> | ||||
|                 <path fill-rule="evenodd" d="M1.5 1.5A.5.5 0 0 1 2 1h12a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-.128.334L10 8.692V13.5a.5.5 0 0 1-.342.474l-3 1A.5.5 0 0 1 6 14.5V8.692L1.628 3.834A.5.5 0 0 1 1.5 3.5v-2zm1 .5v1.308l4.372 4.858A.5.5 0 0 1 7 8.5v5.306l2-.666V8.5a.5.5 0 0 1 .128-.334L13.5 3.308V2h-11z"/> | ||||
|               </svg> | ||||
|               Documents | ||||
|             </button> | ||||
|             <button class="btn btn-sm btn-outline-secondary" (click)="openEditDialog(correspondent)"> | ||||
|               <svg width="1em" height="1em" viewBox="0 0 16 16" class="bi bi-pencil" fill="currentColor" xmlns="http://www.w3.org/2000/svg"> | ||||
|                 <path fill-rule="evenodd" d="M12.146.146a.5.5 0 0 1 .708 0l3 3a.5.5 0 0 1 0 .708l-10 10a.5.5 0 0 1-.168.11l-5 2a.5.5 0 0 1-.65-.65l2-5a.5.5 0 0 1 .11-.168l10-10zM11.207 2.5L13.5 4.793 14.793 3.5 12.5 1.207 11.207 2.5zm1.586 3L10.5 3.207 4 9.707V10h.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.5h.293l6.5-6.5zm-9.761 5.175l-.106.106-1.528 3.821 3.821-1.528.106-.106A.5.5 0 0 1 5 12.5V12h-.5a.5.5 0 0 1-.5-.5V11h-.5a.5.5 0 0 1-.468-.325z"/> | ||||
|               </svg> | ||||
|               Edit | ||||
|             </button> | ||||
|             <button class="btn btn-sm btn-outline-danger" (click)="openDeleteDialog(correspondent)"> | ||||
|               <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-trash" viewBox="0 0 16 16"> | ||||
|                 <path d="M5.5 5.5A.5.5 0 0 1 6 6v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5zm2.5 0a.5.5 0 0 1 .5.5v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5zm3 .5a.5.5 0 0 0-1 0v6a.5.5 0 0 0 1 0V6z"/> | ||||
|                 <path fill-rule="evenodd" d="M14.5 3a1 1 0 0 1-1 1H13v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V4h-.5a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1H6a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1h3.5a1 1 0 0 1 1 1v1zM4.118 4L4 4.059V13a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1V4.059L11.882 4H4.118zM2.5 3V2h11v1h-11z"/> | ||||
|               </svg> | ||||
|               Delete | ||||
|             </button> | ||||
|           </div> | ||||
|         </td> | ||||
|     </tr> | ||||
|     </tbody> | ||||
|   | ||||
| @@ -1,6 +1,9 @@ | ||||
| import { Component, OnInit } from '@angular/core'; | ||||
| import { Router } from '@angular/router'; | ||||
| import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; | ||||
| import { FILTER_CORRESPONDENT } from 'src/app/data/filter-rule-type'; | ||||
| import { PaperlessCorrespondent } from 'src/app/data/paperless-correspondent'; | ||||
| import { DocumentListViewService } from 'src/app/services/document-list-view.service'; | ||||
| import { CorrespondentService } from 'src/app/services/rest/correspondent.service'; | ||||
| import { GenericListComponent } from '../generic-list/generic-list.component'; | ||||
| import { CorrespondentEditDialogComponent } from './correspondent-edit-dialog/correspondent-edit-dialog.component'; | ||||
| @@ -12,7 +15,10 @@ import { CorrespondentEditDialogComponent } from './correspondent-edit-dialog/co | ||||
| }) | ||||
| export class CorrespondentListComponent extends GenericListComponent<PaperlessCorrespondent> { | ||||
|  | ||||
|   constructor(correspondentsService: CorrespondentService, modalService: NgbModal,) {  | ||||
|   constructor(correspondentsService: CorrespondentService, modalService: NgbModal, | ||||
|     private router: Router, | ||||
|     private list: DocumentListViewService | ||||
|   ) {  | ||||
|     super(correspondentsService,modalService,CorrespondentEditDialogComponent) | ||||
|   } | ||||
|  | ||||
| @@ -20,4 +26,10 @@ export class CorrespondentListComponent extends GenericListComponent<PaperlessCo | ||||
|     return `correspondent '${object.name}'` | ||||
|   } | ||||
|  | ||||
|   filterDocuments(object: PaperlessCorrespondent) { | ||||
|     this.list.documentListView.filter_rules = [ | ||||
|       {rule_type: FILTER_CORRESPONDENT, value: object.id.toString()} | ||||
|     ] | ||||
|     this.router.navigate(["documents"]) | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -25,8 +25,25 @@ | ||||
|       <td scope="row">{{ document_type.document_count }}</td> | ||||
|       <td scope="row"> | ||||
|         <div class="btn-group"> | ||||
|           <button class="btn btn-sm btn-outline-secondary" (click)="openEditDialog(document_type)">Edit</button> | ||||
|           <button class="btn btn-sm btn-outline-danger" (click)="openDeleteDialog(document_type)">Delete</button> | ||||
|           <button class="btn btn-sm btn-outline-secondary" (click)="filterDocuments(document_type)"> | ||||
|             <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-funnel" viewBox="0 0 16 16"> | ||||
|               <path fill-rule="evenodd" d="M1.5 1.5A.5.5 0 0 1 2 1h12a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-.128.334L10 8.692V13.5a.5.5 0 0 1-.342.474l-3 1A.5.5 0 0 1 6 14.5V8.692L1.628 3.834A.5.5 0 0 1 1.5 3.5v-2zm1 .5v1.308l4.372 4.858A.5.5 0 0 1 7 8.5v5.306l2-.666V8.5a.5.5 0 0 1 .128-.334L13.5 3.308V2h-11z"/> | ||||
|             </svg> | ||||
|             Documents | ||||
|           </button> | ||||
|           <button class="btn btn-sm btn-outline-secondary" (click)="openEditDialog(document_type)"> | ||||
|             <svg width="1em" height="1em" viewBox="0 0 16 16" class="bi bi-pencil" fill="currentColor" xmlns="http://www.w3.org/2000/svg"> | ||||
|               <path fill-rule="evenodd" d="M12.146.146a.5.5 0 0 1 .708 0l3 3a.5.5 0 0 1 0 .708l-10 10a.5.5 0 0 1-.168.11l-5 2a.5.5 0 0 1-.65-.65l2-5a.5.5 0 0 1 .11-.168l10-10zM11.207 2.5L13.5 4.793 14.793 3.5 12.5 1.207 11.207 2.5zm1.586 3L10.5 3.207 4 9.707V10h.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.5h.293l6.5-6.5zm-9.761 5.175l-.106.106-1.528 3.821 3.821-1.528.106-.106A.5.5 0 0 1 5 12.5V12h-.5a.5.5 0 0 1-.5-.5V11h-.5a.5.5 0 0 1-.468-.325z"/> | ||||
|             </svg> | ||||
|             Edit | ||||
|           </button> | ||||
|           <button class="btn btn-sm btn-outline-danger" (click)="openDeleteDialog(document_type)"> | ||||
|             <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-trash" viewBox="0 0 16 16"> | ||||
|               <path d="M5.5 5.5A.5.5 0 0 1 6 6v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5zm2.5 0a.5.5 0 0 1 .5.5v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5zm3 .5a.5.5 0 0 0-1 0v6a.5.5 0 0 0 1 0V6z"/> | ||||
|               <path fill-rule="evenodd" d="M14.5 3a1 1 0 0 1-1 1H13v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V4h-.5a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1H6a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1h3.5a1 1 0 0 1 1 1v1zM4.118 4L4 4.059V13a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1V4.059L11.882 4H4.118zM2.5 3V2h11v1h-11z"/> | ||||
|             </svg> | ||||
|             Delete | ||||
|           </button> | ||||
|         </div> | ||||
|       </td> | ||||
|     </tr> | ||||
|   | ||||
| @@ -1,6 +1,9 @@ | ||||
| import { Component } from '@angular/core'; | ||||
| import { Router } from '@angular/router'; | ||||
| import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; | ||||
| import { FILTER_DOCUMENT_TYPE } from 'src/app/data/filter-rule-type'; | ||||
| import { PaperlessDocumentType } from 'src/app/data/paperless-document-type'; | ||||
| import { DocumentListViewService } from 'src/app/services/document-list-view.service'; | ||||
| import { DocumentTypeService } from 'src/app/services/rest/document-type.service'; | ||||
| import { GenericListComponent } from '../generic-list/generic-list.component'; | ||||
| import { DocumentTypeEditDialogComponent } from './document-type-edit-dialog/document-type-edit-dialog.component'; | ||||
| @@ -12,7 +15,10 @@ import { DocumentTypeEditDialogComponent } from './document-type-edit-dialog/doc | ||||
| }) | ||||
| export class DocumentTypeListComponent extends GenericListComponent<PaperlessDocumentType> { | ||||
|  | ||||
|   constructor(service: DocumentTypeService, modalService: NgbModal) { | ||||
|   constructor(service: DocumentTypeService, modalService: NgbModal, | ||||
|     private router: Router, | ||||
|     private list: DocumentListViewService | ||||
|   ) { | ||||
|     super(service, modalService, DocumentTypeEditDialogComponent) | ||||
|   } | ||||
|  | ||||
| @@ -20,4 +26,10 @@ export class DocumentTypeListComponent extends GenericListComponent<PaperlessDoc | ||||
|     return `document type '${object.name}'` | ||||
|   } | ||||
|  | ||||
|   filterDocuments(object: PaperlessDocumentType) { | ||||
|     this.list.documentListView.filter_rules = [ | ||||
|       {rule_type: FILTER_DOCUMENT_TYPE, value: object.id.toString()} | ||||
|     ] | ||||
|     this.router.navigate(["documents"]) | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -95,7 +95,7 @@ export abstract class GenericListComponent<T extends ObjectWithId> implements On | ||||
|     activeModal.componentInstance.message = "Associated documents will not be deleted." | ||||
|     activeModal.componentInstance.btnClass = "btn-danger" | ||||
|     activeModal.componentInstance.btnCaption = "Delete" | ||||
|     activeModal.componentInstance.confirmPressed.subscribe(() => { | ||||
|     activeModal.componentInstance.confirmClicked.subscribe(() => { | ||||
|       this.service.delete(object).subscribe(_ => { | ||||
|         activeModal.close() | ||||
|         this.reloadData() | ||||
|   | ||||
| @@ -50,16 +50,26 @@ export class SettingsComponent implements OnInit { | ||||
|     }) | ||||
|   } | ||||
|  | ||||
|   private saveLocalSettings() { | ||||
|     localStorage.setItem(GENERAL_SETTINGS.DOCUMENT_LIST_SIZE, this.settingsForm.value.documentListItemPerPage) | ||||
|     this.documentListViewService.updatePageSize() | ||||
|     this.toastService.showToast(Toast.make("Information", "Settings saved successfully.")) | ||||
|   } | ||||
|  | ||||
|   saveSettings() { | ||||
|     let x = [] | ||||
|     for (let id in this.savedViewGroup.value) { | ||||
|       x.push(this.savedViewGroup.value[id]) | ||||
|     } | ||||
|     this.savedViewService.patchMany(x).subscribe(s => { | ||||
|       this.toastService.showToast(Toast.make("Information", "Settings saved successfully.")) | ||||
|       localStorage.setItem(GENERAL_SETTINGS.DOCUMENT_LIST_SIZE, this.settingsForm.value.documentListItemPerPage) | ||||
|       this.documentListViewService.updatePageSize() | ||||
|     }) | ||||
|     if (x.length > 0) { | ||||
|       this.savedViewService.patchMany(x).subscribe(s => { | ||||
|         this.saveLocalSettings() | ||||
|       }, error => { | ||||
|         this.toastService.showToast(Toast.makeError(`Error while storing settings on server: ${JSON.stringify(error.error)}`)) | ||||
|       }) | ||||
|     } else { | ||||
|       this.saveLocalSettings() | ||||
|     } | ||||
|  | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -9,7 +9,7 @@ | ||||
|     aria-label="Default pagination"></ngb-pagination> | ||||
| </div> | ||||
|  | ||||
| <table class="table table-striped border shadow"> | ||||
| <table class="table table-striped border shadow-sm"> | ||||
|   <thead> | ||||
|     <tr> | ||||
|       <th scope="col" sortable="name" (sort)="onSort($event)">Name</th> | ||||
| @@ -28,8 +28,25 @@ | ||||
|       <td scope="row">{{ tag.document_count }}</td> | ||||
|       <td scope="row"> | ||||
|         <div class="btn-group"> | ||||
|           <button class="btn btn-sm btn-outline-secondary" (click)="openEditDialog(tag)">Edit</button> | ||||
|           <button class="btn btn-sm btn-outline-danger" (click)="openDeleteDialog(tag)">Delete</button> | ||||
|           <button class="btn btn-sm btn-outline-secondary" (click)="filterDocuments(tag)"> | ||||
|             <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-funnel" viewBox="0 0 16 16"> | ||||
|               <path fill-rule="evenodd" d="M1.5 1.5A.5.5 0 0 1 2 1h12a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-.128.334L10 8.692V13.5a.5.5 0 0 1-.342.474l-3 1A.5.5 0 0 1 6 14.5V8.692L1.628 3.834A.5.5 0 0 1 1.5 3.5v-2zm1 .5v1.308l4.372 4.858A.5.5 0 0 1 7 8.5v5.306l2-.666V8.5a.5.5 0 0 1 .128-.334L13.5 3.308V2h-11z"/> | ||||
|             </svg> | ||||
|             Documents | ||||
|           </button> | ||||
|           <button class="btn btn-sm btn-outline-secondary" (click)="openEditDialog(tag)"> | ||||
|             <svg width="1em" height="1em" viewBox="0 0 16 16" class="bi bi-pencil" fill="currentColor" xmlns="http://www.w3.org/2000/svg"> | ||||
|               <path fill-rule="evenodd" d="M12.146.146a.5.5 0 0 1 .708 0l3 3a.5.5 0 0 1 0 .708l-10 10a.5.5 0 0 1-.168.11l-5 2a.5.5 0 0 1-.65-.65l2-5a.5.5 0 0 1 .11-.168l10-10zM11.207 2.5L13.5 4.793 14.793 3.5 12.5 1.207 11.207 2.5zm1.586 3L10.5 3.207 4 9.707V10h.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.5h.293l6.5-6.5zm-9.761 5.175l-.106.106-1.528 3.821 3.821-1.528.106-.106A.5.5 0 0 1 5 12.5V12h-.5a.5.5 0 0 1-.5-.5V11h-.5a.5.5 0 0 1-.468-.325z"/> | ||||
|             </svg> | ||||
|             Edit | ||||
|           </button> | ||||
|           <button class="btn btn-sm btn-outline-danger" (click)="openDeleteDialog(tag)"> | ||||
|             <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-trash" viewBox="0 0 16 16"> | ||||
|               <path d="M5.5 5.5A.5.5 0 0 1 6 6v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5zm2.5 0a.5.5 0 0 1 .5.5v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5zm3 .5a.5.5 0 0 0-1 0v6a.5.5 0 0 0 1 0V6z"/> | ||||
|               <path fill-rule="evenodd" d="M14.5 3a1 1 0 0 1-1 1H13v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V4h-.5a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1H6a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1h3.5a1 1 0 0 1 1 1v1zM4.118 4L4 4.059V13a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1V4.059L11.882 4H4.118zM2.5 3V2h11v1h-11z"/> | ||||
|             </svg> | ||||
|             Delete | ||||
|           </button> | ||||
|         </div> | ||||
|       </td> | ||||
|     </tr> | ||||
|   | ||||
| @@ -1,6 +1,9 @@ | ||||
| import { Component } from '@angular/core'; | ||||
| import { Router } from '@angular/router'; | ||||
| import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; | ||||
| import { FILTER_HAS_TAG } from 'src/app/data/filter-rule-type'; | ||||
| import { TAG_COLOURS, PaperlessTag } from 'src/app/data/paperless-tag'; | ||||
| import { DocumentListViewService } from 'src/app/services/document-list-view.service'; | ||||
| import { TagService } from 'src/app/services/rest/tag.service'; | ||||
| import { GenericListComponent } from '../generic-list/generic-list.component'; | ||||
| import { TagEditDialogComponent } from './tag-edit-dialog/tag-edit-dialog.component'; | ||||
| @@ -12,7 +15,10 @@ import { TagEditDialogComponent } from './tag-edit-dialog/tag-edit-dialog.compon | ||||
| }) | ||||
| export class TagListComponent extends GenericListComponent<PaperlessTag> { | ||||
|  | ||||
|   constructor(tagService: TagService, modalService: NgbModal) { | ||||
|   constructor(tagService: TagService, modalService: NgbModal, | ||||
|     private router: Router, | ||||
|     private list: DocumentListViewService | ||||
|   ) { | ||||
|     super(tagService, modalService, TagEditDialogComponent) | ||||
|   } | ||||
|  | ||||
| @@ -23,4 +29,11 @@ export class TagListComponent extends GenericListComponent<PaperlessTag> { | ||||
|   getObjectName(object: PaperlessTag) { | ||||
|     return `tag '${object.name}'` | ||||
|   } | ||||
|  | ||||
|   filterDocuments(object: PaperlessTag) { | ||||
|     this.list.documentListView.filter_rules = [ | ||||
|       {rule_type: FILTER_HAS_TAG, value: object.id.toString()} | ||||
|     ] | ||||
|     this.router.navigate(["documents"]) | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -7,16 +7,21 @@ import { | ||||
| } from '@angular/common/http'; | ||||
| import { Observable } from 'rxjs'; | ||||
| import { CookieService } from 'ngx-cookie-service'; | ||||
| import { Meta } from '@angular/platform-browser'; | ||||
|  | ||||
| @Injectable() | ||||
| export class CsrfInterceptor implements HttpInterceptor { | ||||
|  | ||||
|   constructor(private cookieService: CookieService) { | ||||
|   constructor(private cookieService: CookieService, private meta: Meta) { | ||||
|  | ||||
|   } | ||||
|  | ||||
|   intercept(request: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> { | ||||
|     let csrfToken = this.cookieService.get('csrftoken') | ||||
|     let prefix = "" | ||||
|     if (this.meta.getTag('name=cookie_prefix')) { | ||||
|       prefix = this.meta.getTag('name=cookie_prefix').content | ||||
|     }     | ||||
|     let csrfToken = this.cookieService.get(`${prefix?prefix:''}csrftoken`) | ||||
|     if (csrfToken) { | ||||
|      request = request.clone({ | ||||
|         setHeaders: { | ||||
|   | ||||
| @@ -5,7 +5,7 @@ import { Pipe, PipeTransform } from '@angular/core'; | ||||
| }) | ||||
| export class DocumentTitlePipe implements PipeTransform { | ||||
|  | ||||
|   transform(value: string): unknown { | ||||
|   transform(value: string): string { | ||||
|     if (value) { | ||||
|       return value | ||||
|     } else { | ||||
|   | ||||
| @@ -116,14 +116,14 @@ export class DocumentListViewService { | ||||
|   set filterRules(filterRules: FilterRule[]) { | ||||
|     //we're going to clone the filterRules object, since we don't | ||||
|     //want changes in the filter editor to propagate into here right away. | ||||
|     this.view.filter_rules = cloneFilterRules(filterRules) | ||||
|     this.view.filter_rules = filterRules | ||||
|     this.reload() | ||||
|     this.reduceSelectionToFilter() | ||||
|     this.saveDocumentListView() | ||||
|   } | ||||
|  | ||||
|   get filterRules(): FilterRule[] { | ||||
|     return cloneFilterRules(this.view.filter_rules) | ||||
|     return this.view.filter_rules | ||||
|   } | ||||
|  | ||||
|   set sortField(field: string) { | ||||
| @@ -245,7 +245,7 @@ export class DocumentListViewService { | ||||
|         this.documentListView = null | ||||
|       } | ||||
|     } | ||||
|     if (!this.documentListView) { | ||||
|     if (!this.documentListView || !this.documentListView.filter_rules || !this.documentListView.sort_reverse || !this.documentListView.sort_field) { | ||||
|       this.documentListView = { | ||||
|         filter_rules: [], | ||||
|         sort_reverse: true, | ||||
|   | ||||
										
											Binary file not shown.
										
									
								
							| Before Width: | Height: | Size: 8.1 KiB After Width: | Height: | Size: 8.1 KiB | 
| @@ -1,5 +1,6 @@ | ||||
| export const environment = { | ||||
|   production: true, | ||||
|   apiBaseUrl: "/api/", | ||||
|   appTitle: "Paperless-ng" | ||||
|   appTitle: "Paperless-ng", | ||||
|   version: "0.9.8" | ||||
| }; | ||||
|   | ||||
| @@ -1,7 +1,6 @@ | ||||
| @import "theme"; | ||||
|  | ||||
| @import "node_modules/bootstrap/scss/bootstrap"; | ||||
|  | ||||
| @import "~@ng-select/ng-select/themes/default.theme.css"; | ||||
|  | ||||
| .toolbaricon { | ||||
|   width: 1.2em; | ||||
| @@ -20,7 +19,7 @@ | ||||
| } | ||||
|  | ||||
| body { | ||||
|   font-size: .875rem; | ||||
|   font-size: 0.875rem; | ||||
| } | ||||
|  | ||||
| .form-control-dark { | ||||
| @@ -65,4 +64,39 @@ body { | ||||
|   display: block; | ||||
|   background-size: 1rem; | ||||
|   float: right; | ||||
| } | ||||
| } | ||||
|  | ||||
| .paperless-input-select { | ||||
|   .ng-select { | ||||
|     position: relative; | ||||
|     flex: 1 1 auto; | ||||
|     margin-bottom: 0; | ||||
|     min-height: calc(1.5em + 0.75rem + 5px); | ||||
|     line-height: 1.5; | ||||
|  | ||||
|     .ng-select-container { | ||||
|       height: 100%; | ||||
|       border-top-right-radius: 0; | ||||
|       border-bottom-right-radius: 0; | ||||
|  | ||||
|       .ng-value-container .ng-input { | ||||
|         top: 10px; | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     .ng-dropdown-panel .ng-dropdown-panel-items .ng-option.ng-option-selected, | ||||
|     .ng-dropdown-panel .ng-dropdown-panel-items .ng-option.ng-option-selected.ng-option-marked { | ||||
|       background: none; | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | ||||
| .paperless-input-tags { | ||||
|   .ng-select.ng-select-multiple .ng-select-container .ng-value-container .ng-value { | ||||
|     background-color: transparent; | ||||
|   } | ||||
|  | ||||
|   .ng-select.ng-select-multiple .ng-select-container .ng-value-container { | ||||
|     padding-top: 1px; | ||||
|   } | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Michael Shamoon
					Michael Shamoon