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)) | ||||
|   } | ||||
|   | ||||
							
								
								
									
										69
									
								
								src-ui/src/assets/logo-dark-notext.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										69
									
								
								src-ui/src/assets/logo-dark-notext.svg
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,69 @@ | ||||
| <?xml version="1.0" encoding="UTF-8" standalone="no"?> | ||||
| <svg | ||||
|    xmlns:dc="http://purl.org/dc/elements/1.1/" | ||||
|    xmlns:cc="http://creativecommons.org/ns#" | ||||
|    xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" | ||||
|    xmlns:svg="http://www.w3.org/2000/svg" | ||||
|    xmlns="http://www.w3.org/2000/svg" | ||||
|    xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" | ||||
|    xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" | ||||
|    width="69.999977mm" | ||||
|    height="84.283669mm" | ||||
|    viewBox="0 0 69.999977 84.283669" | ||||
|    version="1.1" | ||||
|    id="svg4812" | ||||
|    inkscape:version="1.0.1 (3bc2e813f5, 2020-09-07)" | ||||
|    sodipodi:docname="logo-dark-notext.svg"> | ||||
|   <defs | ||||
|      id="defs4806" /> | ||||
|   <sodipodi:namedview | ||||
|      id="base" | ||||
|      pagecolor="#ffffff" | ||||
|      bordercolor="#666666" | ||||
|      borderopacity="1.0" | ||||
|      inkscape:pageopacity="0.0" | ||||
|      inkscape:pageshadow="2" | ||||
|      inkscape:zoom="0.98994949" | ||||
|      inkscape:cx="328.04904" | ||||
|      inkscape:cy="330.33332" | ||||
|      inkscape:document-units="mm" | ||||
|      inkscape:current-layer="SvgjsG1020" | ||||
|      inkscape:document-rotation="0" | ||||
|      showgrid="false" | ||||
|      inkscape:window-width="1920" | ||||
|      inkscape:window-height="1016" | ||||
|      inkscape:window-x="1280" | ||||
|      inkscape:window-y="27" | ||||
|      inkscape:window-maximized="1" /> | ||||
|   <metadata | ||||
|      id="metadata4809"> | ||||
|     <rdf:RDF> | ||||
|       <cc:Work | ||||
|          rdf:about=""> | ||||
|         <dc:format>image/svg+xml</dc:format> | ||||
|         <dc:type | ||||
|            rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> | ||||
|         <dc:title></dc:title> | ||||
|       </cc:Work> | ||||
|     </rdf:RDF> | ||||
|   </metadata> | ||||
|   <g | ||||
|      inkscape:label="Layer 1" | ||||
|      inkscape:groupmode="layer" | ||||
|      id="layer1" | ||||
|      transform="translate(-9.9999792,-10.000082)"> | ||||
|     <g | ||||
|        id="SvgjsG1020" | ||||
|        featureKey="symbol1" | ||||
|        fill="#ffffff" | ||||
|        transform="matrix(0.10341565,0,0,0.10341565,1.2287665,8.3453496)"> | ||||
|       <path | ||||
|          id="path57" | ||||
|          style="fill:#ffffff;stroke-width:1.10017" | ||||
|          d="M 752.4375,82.365234 C 638.02019,348.60552 87.938206,381.6089 263.96484,810.67383 c 2.20034,5.50083 -40.70621,63.80947 -69.31054,112.21679 -6.601,-24.20366 -14.30329,-50.6063 -13.20313,-52.80664 C 324.47281,700.65835 79.135592,604.94324 65.933594,466.32227 4.3242706,576.33891 -17.678136,768.86756 168.25,879.98438 c 1.10017,-10e-6 9.90207,41.80777 14.30273,62.71093 -4.40066,8.80133 -8.80162,17.60213 -11.00195,24.20313 -4.40066,11.00166 28.60352,9.90123 28.60352,12.10156 3.3005,-1.10017 81.41295,-138.62054 83.61328,-139.7207 C 726.0345,738.06398 804.14532,339.80419 752.4375,82.365234 Z M 526.9043,362.90625 C 320.073,547.73422 284.86775,685.25508 291.46875,752.36523 222.15826,588.44043 425.68898,408.01308 526.9043,362.90625 Z M 127.54297,626.94727 c 39.60599,36.30549 105.6163,147.4222 49.50781,212.33203 13.202,-29.7045 17.60234,-96.81455 -49.50781,-212.33203 z" | ||||
|          transform="matrix(0.90895334,0,0,0.90895334,65.06894,-58.865357)" /> | ||||
|       <defs | ||||
|          id="defs14302" /> | ||||
|     </g> | ||||
|   </g> | ||||
| </svg> | ||||
| After Width: | Height: | Size: 2.9 KiB | 
		Reference in New Issue
	
	Block a user
	 Jonas Winkler
					Jonas Winkler