mirror of
				https://github.com/paperless-ngx/paperless-ngx.git
				synced 2025-10-30 03:56:23 -05:00 
			
		
		
		
	Merge remote-tracking branch 'upstream/dev' into feature-bulk-editor
This commit is contained in:
		| @@ -1,4 +1,14 @@ | ||||
| <app-page-header [(title)]="title"> | ||||
|     <div class="input-group input-group-sm mr-5" *ngIf="getContentType() == 'application/pdf'"> | ||||
|       <div class="input-group-prepend"> | ||||
|         <div class="input-group-text">Page </div> | ||||
|       </div> | ||||
|       <input class="form-control flex-grow-0 w-auto" type="number" min="1" [max]="previewNumPages" [(ngModel)]="previewCurrentPage" /> | ||||
|       <div class="input-group-append"> | ||||
|         <div class="input-group-text">of {{previewNumPages}}</div> | ||||
|       </div> | ||||
|     </div> | ||||
|  | ||||
|     <button type="button" class="btn btn-sm btn-outline-danger mr-2" (click)="delete()"> | ||||
|         <svg class="buttonicon" fill="currentColor"> | ||||
|             <use xlink:href="assets/bootstrap-icons.svg#trash" /> | ||||
| @@ -24,6 +34,12 @@ | ||||
|  | ||||
|     </div> | ||||
|  | ||||
|     <button type="button" class="btn btn-sm btn-outline-primary mr-2" (click)="moreLike()"> | ||||
|         <svg class="buttonicon" fill="currentColor"> | ||||
|             <use xlink:href="assets/bootstrap-icons.svg#three-dots" /> | ||||
|         </svg> | ||||
|         <span class="d-none d-lg-inline"> More like this</span> | ||||
|     </button> | ||||
|  | ||||
|     <button type="button" class="btn btn-sm btn-outline-primary" (click)="close()"> | ||||
|         <svg class="buttonicon" fill="currentColor"> | ||||
| @@ -128,7 +144,7 @@ | ||||
|  | ||||
|     <div class="col-md-6 col-xl-8 mb-3"> | ||||
|       <div class="pdf-viewer-container" *ngIf="getContentType() == 'application/pdf'"> | ||||
|         <pdf-viewer [src]="previewUrl" [original-size]="false" [show-borders]="true"></pdf-viewer> | ||||
|         <pdf-viewer [src]="previewUrl" [original-size]="false" [show-borders]="true" [show-all]="true" [(page)]="previewCurrentPage" (after-load-complete)="pdfPreviewLoaded($event)"></pdf-viewer> | ||||
|       </div> | ||||
|     </div> | ||||
| </div> | ||||
|   | ||||
| @@ -15,6 +15,7 @@ import { DocumentService } from 'src/app/services/rest/document.service'; | ||||
| import { ConfirmDialogComponent } from '../common/confirm-dialog/confirm-dialog.component'; | ||||
| import { CorrespondentEditDialogComponent } from '../manage/correspondent-list/correspondent-edit-dialog/correspondent-edit-dialog.component'; | ||||
| import { DocumentTypeEditDialogComponent } from '../manage/document-type-list/document-type-edit-dialog/document-type-edit-dialog.component'; | ||||
| import { PDFDocumentProxy } from 'ng2-pdf-viewer'; | ||||
|  | ||||
| @Component({ | ||||
|   selector: 'app-document-detail', | ||||
| @@ -47,8 +48,11 @@ export class DocumentDetailComponent implements OnInit { | ||||
|     tags: new FormControl([]) | ||||
|   }) | ||||
|  | ||||
|   previewCurrentPage: number = 1 | ||||
|   previewNumPages: number = 1 | ||||
|  | ||||
|   constructor( | ||||
|     private documentsService: DocumentService,  | ||||
|     private documentsService: DocumentService, | ||||
|     private route: ActivatedRoute, | ||||
|     private correspondentService: CorrespondentService, | ||||
|     private documentTypeService: DocumentTypeService, | ||||
| @@ -126,7 +130,7 @@ export class DocumentDetailComponent implements OnInit { | ||||
|     }, error => {this.router.navigate(['404'])}) | ||||
|   } | ||||
|  | ||||
|   save() {     | ||||
|   save() { | ||||
|     this.documentsService.update(this.document).subscribe(result => { | ||||
|       this.close() | ||||
|     }) | ||||
| @@ -161,14 +165,23 @@ export class DocumentDetailComponent implements OnInit { | ||||
|     modal.componentInstance.btnCaption = "Delete document" | ||||
|     modal.componentInstance.confirmClicked.subscribe(() => { | ||||
|       this.documentsService.delete(this.document).subscribe(() => { | ||||
|         modal.close()   | ||||
|         modal.close() | ||||
|         this.close() | ||||
|       }) | ||||
|     }) | ||||
|  | ||||
|   } | ||||
|  | ||||
|   moreLike() { | ||||
|     this.router.navigate(["search"], {queryParams: {more_like:this.document.id}}) | ||||
|   } | ||||
|  | ||||
|   hasNext() { | ||||
|     return this.documentListViewService.hasNext(this.documentId) | ||||
|   } | ||||
|  | ||||
|   pdfPreviewLoaded(pdf: PDFDocumentProxy) { | ||||
|     this.previewNumPages = pdf.numPages | ||||
|   } | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -23,8 +23,14 @@ | ||||
|         </p> | ||||
|  | ||||
|  | ||||
|         <div class="d-flex justify-content-between align-items-center"> | ||||
|         <div class="d-flex align-items-center"> | ||||
|           <div class="btn-group"> | ||||
|             <a routerLink="/search" [queryParams]="{'more_like': document.id}" class="btn btn-sm btn-outline-secondary" *ngIf="moreLikeThis"> | ||||
|               <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-three-dots" viewBox="0 0 16 16"> | ||||
|                 <path fill-rule="evenodd" d="M3 9.5a1.5 1.5 0 1 1 0-3 1.5 1.5 0 0 1 0 3zm5 0a1.5 1.5 0 1 1 0-3 1.5 1.5 0 0 1 0 3zm5 0a1.5 1.5 0 1 1 0-3 1.5 1.5 0 0 1 0 3z"/> | ||||
|               </svg> | ||||
|               More like this | ||||
|             </a> | ||||
|             <a routerLink="/documents/{{document.id}}" class="btn btn-sm btn-outline-secondary"> | ||||
|               <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"/> | ||||
| @@ -45,10 +51,16 @@ | ||||
|               </svg> | ||||
|               Download | ||||
|             </a> | ||||
|              | ||||
|           </div> | ||||
|  | ||||
|           <small class="text-muted ml-auto">Score:</small> | ||||
|  | ||||
|           <ngb-progressbar *ngIf="searchScore" [type]="searchScoreClass" [value]="searchScore" class="search-score-bar mx-2" [max]="1"></ngb-progressbar> | ||||
|            | ||||
|           <small class="text-muted">Created: {{document.created | date}}</small> | ||||
|         </div> | ||||
|  | ||||
|          | ||||
|       </div> | ||||
|     </div> | ||||
|   </div> | ||||
|   | ||||
| @@ -9,4 +9,10 @@ | ||||
|   height: 100%; | ||||
|   position: absolute; | ||||
|  | ||||
| } | ||||
|  | ||||
| .search-score-bar { | ||||
|   width: 100px; | ||||
|   height: 5px; | ||||
|   margin-top: 2px; | ||||
| } | ||||
| @@ -12,6 +12,9 @@ export class DocumentCardLargeComponent implements OnInit { | ||||
|  | ||||
|   constructor(private documentService: DocumentService, private sanitizer: DomSanitizer) { } | ||||
|  | ||||
|   @Input() | ||||
|   moreLikeThis: boolean = false | ||||
|  | ||||
|   @Input() | ||||
|   document: PaperlessDocument | ||||
|  | ||||
| @@ -24,6 +27,19 @@ export class DocumentCardLargeComponent implements OnInit { | ||||
|   @Output() | ||||
|   clickCorrespondent = new EventEmitter<number>() | ||||
|  | ||||
|   @Input() | ||||
|   searchScore: number | ||||
|  | ||||
|   get searchScoreClass() { | ||||
|     if (this.searchScore > 0.7) { | ||||
|       return "success" | ||||
|     } else if (this.searchScore > 0.3) { | ||||
|       return "warning" | ||||
|     } else { | ||||
|       return "danger" | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   ngOnInit(): void { | ||||
|   } | ||||
|  | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| <div class="col p-2 h-100 document-card" style="width: 16rem;"> | ||||
| <div class="col p-2 h-100"> | ||||
|   <div class="card h-100 shadow-sm" [class.card-selected]="selected"> | ||||
|     <div class="border-bottom" [class.doc-img-background-selected]="selected"> | ||||
|       <img class="card-img doc-img" [src]="getThumbUrl()" (click)="selected = !selected"> | ||||
|   | ||||
| @@ -151,5 +151,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 [document]="d" [selected]="list.isSelected(d)" (selectedChange)="list.setSelected(d, $event)" *ngFor="let d of list.documents" (clickTag)="clickTag($event)" (clickCorrespondent)="clickCorrespondent($event)"></app-document-card-small> | ||||
| </div> | ||||
|   | ||||
| @@ -1,3 +1,3 @@ | ||||
| ... <span *ngFor="let fragment of highlights"> | ||||
|     <span *ngFor="let token of fragment" [ngClass]="token.term != null ? 'match term'+ token.term : ''">{{token.text}}</span> ...  | ||||
|     <span *ngFor="let token of fragment" [class.match]="token.highlight">{{token.text}}</span> ...  | ||||
| </span> | ||||
| @@ -1,4 +1,4 @@ | ||||
| .match { | ||||
|     color: black; | ||||
|     background-color: orange; | ||||
|     background-color: rgb(255, 211, 66); | ||||
| } | ||||
| @@ -3,7 +3,12 @@ | ||||
|  | ||||
| <div *ngIf="errorMessage" class="alert alert-danger">Invalid search query: {{errorMessage}}</div> | ||||
|  | ||||
| <p> | ||||
| <p *ngIf="more_like"> | ||||
|     Showing documents similar to | ||||
|     <a routerLink="/documents/{{more_like}}">{{more_like_doc?.original_file_name}}</a> | ||||
| </p> | ||||
|  | ||||
| <p *ngIf="query"> | ||||
|     Search string: <i>{{query}}</i> | ||||
|     <ng-container *ngIf="correctedQuery"> | ||||
|         - Did you mean "<a [routerLink]="" (click)="searchCorrectedQuery()">{{correctedQuery}}</a>"? | ||||
| @@ -15,7 +20,9 @@ | ||||
|     <p>{{resultCount}} result(s)</p> | ||||
|     <app-document-card-large *ngFor="let result of results" | ||||
|         [document]="result.document" | ||||
|         [details]="result.highlights"> | ||||
|         [details]="result.highlights" | ||||
|         [searchScore]="result.score / maxScore" | ||||
|         [moreLikeThis]="true"> | ||||
|  | ||||
| </app-document-card-large> | ||||
| </div> | ||||
|   | ||||
| @@ -1,6 +1,9 @@ | ||||
| import { Component, OnInit } from '@angular/core'; | ||||
| import { ActivatedRoute, Router } from '@angular/router'; | ||||
| import { PaperlessDocument } from 'src/app/data/paperless-document'; | ||||
| import { PaperlessDocumentType } from 'src/app/data/paperless-document-type'; | ||||
| import { SearchHit } from 'src/app/data/search-result'; | ||||
| import { DocumentService } from 'src/app/services/rest/document.service'; | ||||
| import { SearchService } from 'src/app/services/rest/search.service'; | ||||
|  | ||||
| @Component({ | ||||
| @@ -14,6 +17,10 @@ export class SearchComponent implements OnInit { | ||||
|  | ||||
|   query: string = "" | ||||
|  | ||||
|   more_like: number | ||||
|  | ||||
|   more_like_doc: PaperlessDocument | ||||
|  | ||||
|   searching = false | ||||
|  | ||||
|   currentPage = 1 | ||||
| @@ -26,11 +33,24 @@ export class SearchComponent implements OnInit { | ||||
|  | ||||
|   errorMessage: string | ||||
|  | ||||
|   constructor(private searchService: SearchService, private route: ActivatedRoute, private router: Router) { } | ||||
|   get maxScore() { | ||||
|     return this.results?.length > 0 ? this.results[0].score : 100 | ||||
|   } | ||||
|  | ||||
|   constructor(private searchService: SearchService, private route: ActivatedRoute, private router: Router, private documentService: DocumentService) { } | ||||
|  | ||||
|   ngOnInit(): void { | ||||
|     this.route.queryParamMap.subscribe(paramMap => { | ||||
|       window.scrollTo(0, 0) | ||||
|       this.query = paramMap.get('query') | ||||
|       this.more_like = paramMap.has('more_like') ? +paramMap.get('more_like') : null | ||||
|       if (this.more_like) { | ||||
|         this.documentService.get(this.more_like).subscribe(r => { | ||||
|           this.more_like_doc = r | ||||
|         }) | ||||
|       } else { | ||||
|         this.more_like_doc = null | ||||
|       } | ||||
|       this.searching = true | ||||
|       this.currentPage = 1 | ||||
|       this.loadPage() | ||||
| @@ -39,13 +59,14 @@ export class SearchComponent implements OnInit { | ||||
|   } | ||||
|  | ||||
|   searchCorrectedQuery() { | ||||
|     this.router.navigate(["search"], {queryParams: {query: this.correctedQuery}}) | ||||
|     this.router.navigate(["search"], {queryParams: {query: this.correctedQuery, more_like: this.more_like}}) | ||||
|   } | ||||
|  | ||||
|   loadPage(append: boolean = false) { | ||||
|     this.errorMessage = null | ||||
|     this.correctedQuery = null | ||||
|     this.searchService.search(this.query, this.currentPage).subscribe(result => { | ||||
|  | ||||
|     this.searchService.search(this.query, this.currentPage, this.more_like).subscribe(result => { | ||||
|       if (append) { | ||||
|         this.results.push(...result.results) | ||||
|       } else { | ||||
|   | ||||
| @@ -15,11 +15,17 @@ export class SearchService { | ||||
|    | ||||
|   constructor(private http: HttpClient, private documentService: DocumentService) { } | ||||
|  | ||||
|   search(query: string, page?: number): Observable<SearchResult> { | ||||
|     let httpParams = new HttpParams().set('query', query) | ||||
|   search(query: string, page?: number, more_like?: number): Observable<SearchResult> { | ||||
|     let httpParams = new HttpParams() | ||||
|     if (query) { | ||||
|       httpParams = httpParams.set('query', query) | ||||
|     } | ||||
|     if (page) { | ||||
|       httpParams = httpParams.set('page', page.toString()) | ||||
|     } | ||||
|     if (more_like) { | ||||
|       httpParams = httpParams.set('more_like', more_like.toString()) | ||||
|     } | ||||
|     return this.http.get<SearchResult>(`${environment.apiBaseUrl}search/`, {params: httpParams}).pipe( | ||||
|       map(result => { | ||||
|         result.results.forEach(hit => this.documentService.addObservablesToDocument(hit.document)) | ||||
|   | ||||
| @@ -5,7 +5,8 @@ | ||||
| export const environment = { | ||||
|   production: false, | ||||
|   apiBaseUrl: "http://localhost:8000/api/", | ||||
|   appTitle: "DEVELOPMENT P-NG" | ||||
|   appTitle: "Paperless-ng", | ||||
|   version: "DEVELOPMENT" | ||||
| }; | ||||
|  | ||||
| /* | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Michael Shamoon
					Michael Shamoon