mirror of
				https://github.com/paperless-ngx/paperless-ngx.git
				synced 2025-10-30 03:56:23 -05:00 
			
		
		
		
	Merge branch 'dev' into celery-tasks
This commit is contained in:
		| @@ -39,6 +39,8 @@ 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'; | ||||
| import { SortableDirective } from './directives/sortable.directive'; | ||||
| import { CookieService } from 'ngx-cookie-service'; | ||||
| import { CsrfInterceptor } from './interceptors/csrf.interceptor'; | ||||
| import { SavedViewWidgetComponent } from './components/dashboard/widgets/saved-view-widget/saved-view-widget.component'; | ||||
| import { ConsumerStatusWidgetComponent } from './components/dashboard/widgets/consumer-status-widget/consumer-status-widget.component'; | ||||
| import { StatisticsWidgetComponent } from './components/dashboard/widgets/statistics-widget/statistics-widget.component'; | ||||
| @@ -93,7 +95,12 @@ import { FileUploadWidgetComponent } from './components/dashboard/widgets/file-u | ||||
|     InfiniteScrollModule | ||||
|   ], | ||||
|   providers: [ | ||||
|     DatePipe | ||||
|     DatePipe, | ||||
|     CookieService, { | ||||
|       provide: HTTP_INTERCEPTORS, | ||||
|       useClass: CsrfInterceptor, | ||||
|       multi: true | ||||
|     } | ||||
|   ], | ||||
|   bootstrap: [AppComponent] | ||||
| }) | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| <nav class="navbar navbar-dark sticky-top bg-dark flex-md-nowrap p-0 shadow"> | ||||
|   <span class="navbar-brand col-md-3 col-lg-2 mr-0 px-3" href="#">Paperless</span> | ||||
|   <span class="navbar-brand col-md-3 col-lg-2 mr-0 px-3" href="#">Paperless-ng</span> | ||||
|   <button class="navbar-toggler position-absolute d-md-none collapsed" type="button" data-toggle="collapse" | ||||
|     data-target="#sidebarMenu" aria-controls="sidebarMenu" aria-expanded="false" aria-label="Toggle navigation"> | ||||
|     <span class="navbar-toggler-icon"></span> | ||||
| @@ -69,6 +69,14 @@ | ||||
|               {{d.title}} | ||||
|             </a> | ||||
|           </li> | ||||
|           <li class="nav-item w-100" *ngIf="openDocuments.length > 1"> | ||||
|             <a class="nav-link text-truncate" [routerLink]="" (click)="closeAll()"> | ||||
|               <svg class="sidebaricon" fill="currentColor"> | ||||
|                 <use xlink:href="assets/bootstrap-icons.svg#x"/> | ||||
|               </svg> | ||||
|               Close all | ||||
|             </a> | ||||
|           </li> | ||||
|         </ul> | ||||
|  | ||||
|         <h6 class="sidebar-heading d-flex justify-content-between align-items-center px-3 mt-4 mb-1 text-muted"> | ||||
| @@ -124,6 +132,28 @@ | ||||
|             </a> | ||||
|           </li> | ||||
|         </ul> | ||||
|  | ||||
|         <h6 class="sidebar-heading d-flex justify-content-between align-items-center px-3 mt-4 mb-1 text-muted"> | ||||
|           <span>Misc</span> | ||||
|         </h6> | ||||
|         <ul class="nav flex-column mb-2"> | ||||
|           <li class="nav-item"> | ||||
|             <a class="nav-link" href="https://paperless-ng.readthedocs.io/en/latest/"> | ||||
|               <svg class="sidebaricon" fill="currentColor"> | ||||
|                 <use xlink:href="assets/bootstrap-icons.svg#question-circle"/> | ||||
|               </svg> | ||||
|               Documentation | ||||
|             </a> | ||||
|           </li> | ||||
|           <li class="nav-item"> | ||||
|             <a class="nav-link" href="https://github.com/jonaswinkler/paperless-ng"> | ||||
|               <svg class="sidebaricon" fill="currentColor"> | ||||
|                 <use xlink:href="assets/bootstrap-icons.svg#link"/> | ||||
|               </svg> | ||||
|               Github | ||||
|             </a> | ||||
|           </li> | ||||
|         </ul> | ||||
|       </div> | ||||
|     </nav> | ||||
|  | ||||
|   | ||||
| @@ -1,12 +1,13 @@ | ||||
| import { Component, OnDestroy, OnInit } from '@angular/core'; | ||||
| import { FormControl } from '@angular/forms'; | ||||
| import { Router } from '@angular/router'; | ||||
| import { ActivatedRoute, Router } from '@angular/router'; | ||||
| import { from, Observable, Subscription } from 'rxjs'; | ||||
| import { debounceTime, distinctUntilChanged, map, switchMap } from 'rxjs/operators'; | ||||
| import { PaperlessDocument } from 'src/app/data/paperless-document'; | ||||
| import { OpenDocumentsService } from 'src/app/services/open-documents.service'; | ||||
| import { SearchService } from 'src/app/services/rest/search.service'; | ||||
| import { SavedViewConfigService } from 'src/app/services/saved-view-config.service'; | ||||
| import { DocumentDetailComponent } from '../document-detail/document-detail.component'; | ||||
|    | ||||
| @Component({ | ||||
|   selector: 'app-app-frame', | ||||
| @@ -17,6 +18,7 @@ export class AppFrameComponent implements OnInit, OnDestroy { | ||||
|  | ||||
|   constructor ( | ||||
|     public router: Router, | ||||
|     private activatedRoute: ActivatedRoute, | ||||
|     private openDocumentsService: OpenDocumentsService, | ||||
|     private searchService: SearchService, | ||||
|     public viewConfigService: SavedViewConfigService | ||||
| @@ -62,6 +64,19 @@ export class AppFrameComponent implements OnInit, OnDestroy { | ||||
|     this.router.navigate(['search'], {queryParams: {query: this.searchField.value}}) | ||||
|   } | ||||
|  | ||||
|   closeAll() { | ||||
|     this.openDocumentsService.closeAll() | ||||
|  | ||||
|     // TODO: is there a better way to do this? | ||||
|     let route = this.activatedRoute | ||||
|     while (route.firstChild) { | ||||
|       route = route.firstChild | ||||
|     } | ||||
|     if (route.component == DocumentDetailComponent) { | ||||
|       this.router.navigate([""]) | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   ngOnInit() { | ||||
|     this.openDocuments = this.openDocumentsService.getOpenDocuments() | ||||
|   } | ||||
|   | ||||
| @@ -86,7 +86,7 @@ export class TagsComponent implements OnInit, ControlValueAccessor { | ||||
|     var modal = this.modalService.open(TagEditDialogComponent, {backdrop: 'static'}) | ||||
|     modal.componentInstance.dialogMode = 'create' | ||||
|     modal.componentInstance.success.subscribe(newTag => { | ||||
|       this.tagService.list().subscribe(tags => { | ||||
|       this.tagService.listAll().subscribe(tags => { | ||||
|         this.tags = tags.results | ||||
|         this.addTag(newTag.id) | ||||
|       }) | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| <div class="row pt-3 pb-2 mb-3 border-bottom align-items-center"> | ||||
| <div class="row pt-3 pb-1 mb-3 border-bottom align-items-center" > | ||||
|   <div class="col text-truncate"> | ||||
|     <h1 class="h2 text-truncate">{{title}}</h1> | ||||
|     <h1 class="h2 text-truncate" style="line-height: 1.4">{{title}}</h1> | ||||
|   </div> | ||||
|   <div class="btn-toolbar col-auto"> | ||||
|     <ng-content></ng-content> | ||||
|   | ||||
| @@ -2,7 +2,7 @@ | ||||
| <app-page-header title="Dashboard"> | ||||
| </app-page-header> | ||||
|  | ||||
| <p>Welcome to paperless!</p> | ||||
| <p>Welcome to paperless-ng!</p> | ||||
|  | ||||
| <div class='row'> | ||||
|   <div class="col-lg"> | ||||
|   | ||||
| @@ -49,7 +49,6 @@ export class DocumentDetailComponent implements OnInit { | ||||
|     private route: ActivatedRoute, | ||||
|     private correspondentService: CorrespondentService, | ||||
|     private documentTypeService: DocumentTypeService, | ||||
|     private datePipe: DatePipe, | ||||
|     private router: Router, | ||||
|     private modalService: NgbModal, | ||||
|     private openDocumentService: OpenDocumentsService, | ||||
| @@ -89,7 +88,7 @@ export class DocumentDetailComponent implements OnInit { | ||||
|     var modal = this.modalService.open(DocumentTypeEditDialogComponent, {backdrop: 'static'}) | ||||
|     modal.componentInstance.dialogMode = 'create' | ||||
|     modal.componentInstance.success.subscribe(newDocumentType => { | ||||
|       this.documentTypeService.list().subscribe(documentTypes => { | ||||
|       this.documentTypeService.listAll().subscribe(documentTypes => { | ||||
|         this.documentTypes = documentTypes.results | ||||
|         this.documentForm.get('document_type_id').setValue(newDocumentType.id) | ||||
|       }) | ||||
| @@ -100,7 +99,7 @@ export class DocumentDetailComponent implements OnInit { | ||||
|     var modal = this.modalService.open(CorrespondentEditDialogComponent, {backdrop: 'static'}) | ||||
|     modal.componentInstance.dialogMode = 'create' | ||||
|     modal.componentInstance.success.subscribe(newCorrespondent => { | ||||
|       this.correspondentService.list().subscribe(correspondents => { | ||||
|       this.correspondentService.listAll().subscribe(correspondents => { | ||||
|         this.correspondents = correspondents.results | ||||
|         this.documentForm.get('correspondent_id').setValue(newCorrespondent.id) | ||||
|       }) | ||||
|   | ||||
| @@ -85,8 +85,8 @@ | ||||
|     <th>Correspondent</th> | ||||
|     <th>Title</th> | ||||
|     <th>Document type</th> | ||||
|     <th>Date created</th> | ||||
|     <th>Date added</th> | ||||
|     <th>Created</th> | ||||
|     <th>Added</th> | ||||
|   </thead> | ||||
|   <tbody> | ||||
|     <tr *ngFor="let d of docs.documents" routerLink="/documents/{{d.id}}"> | ||||
|   | ||||
| @@ -1,3 +1,7 @@ | ||||
| .log-entry-10 { | ||||
|   color: lightslategray !important; | ||||
| } | ||||
|  | ||||
| .log-entry-30 { | ||||
|   color: yellow !important; | ||||
| } | ||||
|   | ||||
| @@ -30,7 +30,7 @@ export class LogsComponent implements OnInit { | ||||
|   onScroll() { | ||||
|     let lastCreated = null | ||||
|     if (this.logs.length > 0) { | ||||
|       lastCreated = this.logs[this.logs.length-1].created | ||||
|       lastCreated = new Date(this.logs[this.logs.length-1].created).toISOString() | ||||
|     } | ||||
|     this.logService.list(1, 25, 'created', 'des', {'created__lt': lastCreated, 'level__gte': this.level}).subscribe(result => { | ||||
|       this.logs.push(...result.results) | ||||
|   | ||||
							
								
								
									
										16
									
								
								src-ui/src/app/interceptors/csrf.interceptor.spec.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								src-ui/src/app/interceptors/csrf.interceptor.spec.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,16 @@ | ||||
| import { TestBed } from '@angular/core/testing'; | ||||
|  | ||||
| import { CsrfInterceptor } from './csrf.interceptor'; | ||||
|  | ||||
| describe('CsrfInterceptor', () => { | ||||
|   beforeEach(() => TestBed.configureTestingModule({ | ||||
|     providers: [ | ||||
|       CsrfInterceptor | ||||
|       ] | ||||
|   })); | ||||
|  | ||||
|   it('should be created', () => { | ||||
|     const interceptor: CsrfInterceptor = TestBed.inject(CsrfInterceptor); | ||||
|     expect(interceptor).toBeTruthy(); | ||||
|   }); | ||||
| }); | ||||
							
								
								
									
										30
									
								
								src-ui/src/app/interceptors/csrf.interceptor.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								src-ui/src/app/interceptors/csrf.interceptor.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,30 @@ | ||||
| import { Injectable } from '@angular/core'; | ||||
| import { | ||||
|   HttpRequest, | ||||
|   HttpHandler, | ||||
|   HttpEvent, | ||||
|   HttpInterceptor | ||||
| } from '@angular/common/http'; | ||||
| import { Observable } from 'rxjs'; | ||||
| import { CookieService } from 'ngx-cookie-service'; | ||||
|  | ||||
| @Injectable() | ||||
| export class CsrfInterceptor implements HttpInterceptor { | ||||
|  | ||||
|   constructor(private cookieService: CookieService) { | ||||
|  | ||||
|   } | ||||
|  | ||||
|   intercept(request: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> { | ||||
|     let csrfToken = this.cookieService.get('csrftoken') | ||||
|     if (csrfToken) { | ||||
|      request = request.clone({ | ||||
|         setHeaders: { | ||||
|           'X-CSRFToken': csrfToken | ||||
|         } | ||||
|       }) | ||||
|     } | ||||
|  | ||||
|     return next.handle(request); | ||||
|   } | ||||
| } | ||||
| @@ -1,5 +1,4 @@ | ||||
| import { Injectable } from '@angular/core'; | ||||
| import { Observable, Subject } from 'rxjs'; | ||||
| import { PaperlessDocument } from '../data/paperless-document'; | ||||
| import { OPEN_DOCUMENT_SERVICE } from '../data/storage-keys'; | ||||
|  | ||||
| @@ -8,6 +7,8 @@ import { OPEN_DOCUMENT_SERVICE } from '../data/storage-keys'; | ||||
| }) | ||||
| export class OpenDocumentsService { | ||||
|  | ||||
|   private MAX_OPEN_DOCUMENTS = 5 | ||||
|  | ||||
|   constructor() {  | ||||
|     if (sessionStorage.getItem(OPEN_DOCUMENT_SERVICE.DOCUMENTS)) { | ||||
|       try { | ||||
| @@ -31,7 +32,10 @@ export class OpenDocumentsService { | ||||
|  | ||||
|   openDocument(doc: PaperlessDocument) { | ||||
|     if (this.openDocuments.find(d => d.id == doc.id) == null) { | ||||
|       this.openDocuments.push(doc) | ||||
|       this.openDocuments.unshift(doc) | ||||
|       if (this.openDocuments.length > this.MAX_OPEN_DOCUMENTS) { | ||||
|         this.openDocuments.pop() | ||||
|       } | ||||
|       this.save() | ||||
|     } | ||||
|   } | ||||
| @@ -44,6 +48,11 @@ export class OpenDocumentsService { | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   closeAll() { | ||||
|     this.openDocuments.splice(0, this.openDocuments.length) | ||||
|     this.save() | ||||
|   } | ||||
|  | ||||
|   save() { | ||||
|     sessionStorage.setItem(OPEN_DOCUMENT_SERVICE.DOCUMENTS, JSON.stringify(this.openDocuments)) | ||||
|   } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Jonas Winkler
					Jonas Winkler