Merge branch 'dev' into feature-bulk-edit

This commit is contained in:
jonaswinkler
2020-12-06 02:12:15 +01:00
115 changed files with 3607 additions and 1553 deletions
+3 -1
View File
@@ -45,6 +45,7 @@ import { SavedViewWidgetComponent } from './components/dashboard/widgets/saved-v
import { StatisticsWidgetComponent } from './components/dashboard/widgets/statistics-widget/statistics-widget.component';
import { UploadFileWidgetComponent } from './components/dashboard/widgets/upload-file-widget/upload-file-widget.component';
import { WidgetFrameComponent } from './components/dashboard/widgets/widget-frame/widget-frame.component';
import { WelcomeWidgetComponent } from './components/dashboard/widgets/welcome-widget/welcome-widget.component';
@NgModule({
declarations: [
@@ -82,7 +83,8 @@ import { WidgetFrameComponent } from './components/dashboard/widgets/widget-fram
SavedViewWidgetComponent,
StatisticsWidgetComponent,
UploadFileWidgetComponent,
WidgetFrameComponent
WidgetFrameComponent,
WelcomeWidgetComponent
],
imports: [
BrowserModule,
@@ -50,6 +50,7 @@
.sidebar .nav-link.active {
color: $primary;
font-weight: bold;
}
.sidebar .nav-link:hover .sidebaricon,
@@ -90,7 +90,9 @@ export class AppFrameComponent implements OnInit, OnDestroy {
}
ngOnDestroy() {
this.openDocumentsSubscription.unsubscribe()
if (this.openDocumentsSubscription) {
this.openDocumentsSubscription.unsubscribe()
}
}
}
@@ -3,23 +3,19 @@
</app-page-header>
<div class='row'>
<div class="col-lg">
<app-widget-frame title="Saved views" *ngIf="savedViews.length == 0">
<p class="card-text">This space is reserved to display your saved views. Go to your documents and save a view
to have it displayed
here!</p>
</app-widget-frame>
<div class="col-lg-8">
<app-welcome-widget *ngIf="savedViews.length == 0"></app-welcome-widget>
<ng-container *ngFor="let v of savedViews">
<app-saved-view-widget [savedView]="v"></app-saved-view-widget>
</ng-container>
</div>
<div class="col-lg">
<div class="col-lg-4">
<app-statistics-widget></app-statistics-widget>
<app-upload-file-widget></app-upload-file-widget>
</div>
</div>
</div>
@@ -1,6 +1,9 @@
<app-widget-frame [title]="savedView.title">
<table class="table table-sm table-hover table-borderless">
<a header-buttons [routerLink]="" (click)="showAll()">Show all</a>
<table content class="table table-sm table-hover table-borderless">
<thead>
<tr>
<th>Created</th>
@@ -10,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" class="ml-1"></app-tag>
<td>{{doc.title}}<app-tag [tag]="t" *ngFor="let t of doc.tags$ | async" class="ml-1"></app-tag>
</tr>
</tbody>
</table>
@@ -1,6 +1,8 @@
import { Component, Input, OnInit } from '@angular/core';
import { Router } from '@angular/router';
import { PaperlessDocument } from 'src/app/data/paperless-document';
import { SavedViewConfig } from 'src/app/data/saved-view-config';
import { DocumentListViewService } from 'src/app/services/document-list-view.service';
import { DocumentService } from 'src/app/services/rest/document.service';
@Component({
@@ -10,7 +12,10 @@ import { DocumentService } from 'src/app/services/rest/document.service';
})
export class SavedViewWidgetComponent implements OnInit {
constructor(private documentService: DocumentService) { }
constructor(
private documentService: DocumentService,
private router: Router,
private list: DocumentListViewService) { }
@Input()
savedView: SavedViewConfig
@@ -23,4 +28,9 @@ export class SavedViewWidgetComponent implements OnInit {
})
}
showAll() {
this.list.load(this.savedView)
this.router.navigate(["documents"])
}
}
@@ -1,4 +1,6 @@
<app-widget-frame title="Statistics">
<p class="card-text">Documents in inbox: {{statistics.documents_inbox}}</p>
<p class="card-text">Total documents: {{statistics.documents_total}}</p>
<ng-container content>
<p class="card-text">Documents in inbox: {{statistics.documents_inbox}}</p>
<p class="card-text">Total documents: {{statistics.documents_total}}</p>
</ng-container>
</app-widget-frame>
@@ -1,6 +1,6 @@
<app-widget-frame title="Upload new documents">
<form>
<form content>
<ngx-file-drop
dropZoneLabel="Drop documents here or" (onFileDrop)="dropped($event)"
(onFileOver)="fileOver($event)" (onFileLeave)="fileLeave($event)"
@@ -0,0 +1,16 @@
<app-widget-frame title="First steps">
<ng-container content>
<img src="assets/save-filter.png" class="float-right">
<p>Paperless is running! :)</p>
<p>You can start uploading documents by dropping them in the file upload box to the right or by dropping them in the configured consumption folder and they'll start showing up in the documents list.
After you've added some metadata to your documents, use the filtering mechanisms of paperless to create custom views (such as 'Recently added', 'Tagged TODO') and have them displayed on the dashboard instead of this message.</p>
<p>Paperless offers some more features that try to make your life easier, such as:</p>
<ul>
<li>Once you've got a couple documents in paperless and added metadata to them, paperless can assign that metadata to new documents automatically.</li>
<li>You can configure paperless to read your mails and add documents from attached files.</li>
</ul>
<p>Consult the documentation on how to use these features. The section on basic usage also has some information on how to use paperless in general.</p>
</ng-container>
</app-widget-frame>
@@ -0,0 +1,25 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { WelcomeWidgetComponent } from './welcome-widget.component';
describe('WelcomeWidgetComponent', () => {
let component: WelcomeWidgetComponent;
let fixture: ComponentFixture<WelcomeWidgetComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ WelcomeWidgetComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(WelcomeWidgetComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
@@ -0,0 +1,15 @@
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-welcome-widget',
templateUrl: './welcome-widget.component.html',
styleUrls: ['./welcome-widget.component.scss']
})
export class WelcomeWidgetComponent implements OnInit {
constructor() { }
ngOnInit(): void {
}
}
@@ -1,8 +1,12 @@
<div class="card mb-3 shadow">
<div class="card-header">
<h5 class="card-title mb-0">{{title}}</h5>
<div class="d-flex justify-content-between align-items-center">
<h5 class="card-title mb-0">{{title}}</h5>
<ng-content select ="[header-buttons]"></ng-content>
</div>
</div>
<div class="card-body text-dark">
<ng-content></ng-content>
<ng-content select ="[content]"></ng-content>
</div>
</div>
@@ -5,12 +5,26 @@
</svg>
<span class="d-none d-lg-inline"> Delete</span>
</button>
<a [href]="downloadUrl" class="btn btn-sm btn-outline-primary mr-2">
<svg class="buttonicon" fill="currentColor">
<use xlink:href="assets/bootstrap-icons.svg#download" />
</svg>
<span class="d-none d-lg-inline"> Download</span>
</a>
<div class="btn-group mr-2">
<a [href]="downloadUrl" class="btn btn-sm btn-outline-primary">
<svg class="buttonicon" fill="currentColor">
<use xlink:href="assets/bootstrap-icons.svg#download" />
</svg>
<span class="d-none d-lg-inline"> Download</span>
</a>
<div class="btn-group" ngbDropdown role="group" *ngIf="metadata?.paperless__has_archive_version">
<button class="btn btn-sm btn-outline-primary dropdown-toggle-split" ngbDropdownToggle></button>
<div class="dropdown-menu" ngbDropdownMenu>
<a ngbDropdownItem [href]="downloadOriginalUrl">Download original</a>
</div>
</div>
</div>
<button type="button" class="btn btn-sm btn-outline-primary" (click)="close()">
<svg class="buttonicon" fill="currentColor">
<use xlink:href="assets/bootstrap-icons.svg#x" />
@@ -39,11 +53,11 @@
<textarea class="form-control" id="content" rows="5" formControlName='content'></textarea>
</div>
<app-input-select [items]="correspondents" title="Correspondent" formControlName="correspondent_id" allowNull="true" (createNew)="createCorrespondent()"></app-input-select>
<app-input-select [items]="correspondents" title="Correspondent" formControlName="correspondent" allowNull="true" (createNew)="createCorrespondent()"></app-input-select>
<app-input-select [items]="documentTypes" title="Document type" formControlName="document_type_id" allowNull="true" (createNew)="createDocumentType()"></app-input-select>
<app-input-select [items]="documentTypes" title="Document type" formControlName="document_type" allowNull="true" (createNew)="createDocumentType()"></app-input-select>
<app-input-tags formControlName="tags_id" title="Tags"></app-input-tags>
<app-input-tags formControlName="tags" title="Tags"></app-input-tags>
<button type="button" class="btn btn-outline-secondary" (click)="discard()">Discard</button>&nbsp;
<button type="button" class="btn btn-outline-primary" (click)="saveEditNext()" *ngIf="hasNext()">Save & edit next</button>&nbsp;
@@ -4,6 +4,7 @@ import { ActivatedRoute, Router } from '@angular/router';
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
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 { DocumentListViewService } from 'src/app/services/document-list-view.service';
import { OpenDocumentsService } from 'src/app/services/open-documents.service';
@@ -23,9 +24,11 @@ export class DocumentDetailComponent implements OnInit {
documentId: number
document: PaperlessDocument
metadata: PaperlessDocumentMetadata
title: string
previewUrl: string
downloadUrl: string
downloadOriginalUrl: string
correspondents: PaperlessCorrespondent[]
documentTypes: PaperlessDocumentType[]
@@ -34,10 +37,10 @@ export class DocumentDetailComponent implements OnInit {
title: new FormControl(''),
content: new FormControl(''),
created: new FormControl(),
correspondent_id: new FormControl(),
document_type_id: new FormControl(),
correspondent: new FormControl(),
document_type: new FormControl(),
archive_serial_number: new FormControl(),
tags_id: new FormControl([])
tags: new FormControl([])
})
constructor(
@@ -62,6 +65,7 @@ export class DocumentDetailComponent implements OnInit {
this.documentId = +paramMap.get('id')
this.previewUrl = this.documentsService.getPreviewUrl(this.documentId)
this.downloadUrl = this.documentsService.getDownloadUrl(this.documentId)
this.downloadOriginalUrl = this.documentsService.getDownloadUrl(this.documentId, true)
if (this.openDocumentService.getOpenDocument(this.documentId)) {
this.updateComponent(this.openDocumentService.getOpenDocument(this.documentId))
} else {
@@ -76,6 +80,9 @@ export class DocumentDetailComponent implements OnInit {
updateComponent(doc: PaperlessDocument) {
this.document = doc
this.documentsService.getMetadata(doc.id).subscribe(result => {
this.metadata = result
})
this.title = doc.title
this.documentForm.patchValue(doc)
}
@@ -86,7 +93,7 @@ export class DocumentDetailComponent implements OnInit {
modal.componentInstance.success.subscribe(newDocumentType => {
this.documentTypeService.listAll().subscribe(documentTypes => {
this.documentTypes = documentTypes.results
this.documentForm.get('document_type_id').setValue(newDocumentType.id)
this.documentForm.get('document_type').setValue(newDocumentType.id)
})
})
}
@@ -97,7 +104,7 @@ export class DocumentDetailComponent implements OnInit {
modal.componentInstance.success.subscribe(newCorrespondent => {
this.correspondentService.listAll().subscribe(correspondents => {
this.correspondents = correspondents.results
this.documentForm.get('correspondent_id').setValue(newCorrespondent.id)
this.documentForm.get('correspondent').setValue(newCorrespondent.id)
})
})
}
@@ -9,9 +9,11 @@
<div class="d-flex justify-content-between align-items-center">
<h5 class="card-title">
<ng-container *ngIf="document.correspondent">
<a [routerLink]="" title="Filter by correspondent" (click)="clickCorrespondent.emit(document.correspondent)" class="font-weight-bold">{{document.correspondent.name}}</a>:
<a *ngIf="clickCorrespondent.observers.length ; else nolink" [routerLink]="" title="Filter by correspondent" (click)="clickCorrespondent.emit(document.correspondent)" class="font-weight-bold">{{(document.correspondent$ | async)?.name}}</a>
<ng-template #nolink>{{(document.correspondent$ | async)?.name}}</ng-template>:
</ng-container>
{{document.title}}<app-tag [tag]="t" linkTitle="Filter by tag" *ngFor="let t of document.tags" class="ml-1" (click)="clickTag.emit(t)" [clickable]="true"></app-tag>
{{document.title}}
<app-tag [tag]="t" linkTitle="Filter by tag" *ngFor="let t of document.tags$ | async" class="ml-1" (click)="clickTag.emit(t.id)" [clickable]="clickTag.observers.length"></app-tag>
</h5>
<h5 class="card-title" *ngIf="document.archive_serial_number">#{{document.archive_serial_number}}</h5>
</div>
@@ -20,10 +20,10 @@ export class DocumentCardLargeComponent implements OnInit {
details: any
@Output()
clickTag = new EventEmitter<PaperlessTag>()
clickTag = new EventEmitter<number>()
@Output()
clickCorrespondent = new EventEmitter<PaperlessDocument>()
clickCorrespondent = new EventEmitter<number>()
ngOnInit(): void {
}
@@ -1,15 +1,15 @@
<div class="col p-2 h-100" style="width: 16rem;">
<div class="card h-100 shadow-sm">
<div class=" border-bottom doc-img pr-1" [ngStyle]="{'background-image': 'url(' + getThumbUrl() + ')'}">
<div class="row" *ngFor="let t of document.tags">
<app-tag style="font-size: large;" [tag]="t" class="col text-right" (click)="clickTag.emit(t)" [clickable]="true" linkTitle="Filter by tag"></app-tag>
<div class="row" *ngFor="let t of document.tags$ | async">
<app-tag style="font-size: large;" [tag]="t" class="col text-right" (click)="clickTag.emit(t.id)" [clickable]="true" linkTitle="Filter by tag"></app-tag>
</div>
</div>
<div class="card-body p-2">
<p class="card-text">
<ng-container *ngIf="document.correspondent">
<a [routerLink]="" title="Filter by correspondent" (click)="clickCorrespondent.emit(document.correspondent)" class="font-weight-bold">{{document.correspondent.name}}</a>:
<a [routerLink]="" title="Filter by correspondent" (click)="clickCorrespondent.emit(document.correspondent)" class="font-weight-bold">{{(document.correspondent$ | async)?.name}}</a>:
</ng-container>
{{document.title}}
</p>
@@ -16,10 +16,10 @@ export class DocumentCardSmallComponent implements OnInit {
document: PaperlessDocument
@Output()
clickTag = new EventEmitter<PaperlessTag>()
clickTag = new EventEmitter<number>()
@Output()
clickCorrespondent = new EventEmitter<PaperlessDocument>()
clickCorrespondent = new EventEmitter<number>()
ngOnInit(): void {
}
@@ -96,11 +96,12 @@
<div class="card w-100 mb-3" [hidden]="!showFilter">
<div class="card-body">
<h5 class="card-title">Filter</h5>
<app-filter-editor [(filterRules)]="filterRules" (apply)="applyFilterRules()"></app-filter-editor>
<app-filter-editor [(filterRules)]="filterRules" (apply)="applyFilterRules()" (clear)="clearFilterRules()"></app-filter-editor>
</div>
</div>
<div class="row m-0 justify-content-end">
<div class="d-flex justify-content-between align-items-center">
<p>{{list.collectionSize || 0}} document(s)</p>
<ngb-pagination [pageSize]="list.currentPageSize" [collectionSize]="list.collectionSize" [(page)]="list.currentPage" [maxSize]="5"
[rotate]="true" (pageChange)="list.reload()" aria-label="Default pagination"></ngb-pagination>
</div>
@@ -126,16 +127,16 @@
</td>
<td class="d-none d-md-table-cell">
<ng-container *ngIf="d.correspondent">
<a [routerLink]="" (click)="filterByCorrespondent(d.correspondent)" title="Filter by correspondent">{{d.correspondent.name}}</a>
<a [routerLink]="" (click)="filterByCorrespondent(d.correspondent)" title="Filter by correspondent">{{(d.correspondent$ | async)?.name}}</a>
</ng-container>
</td>
<td>
<a routerLink="/documents/{{d.id}}" title="Edit document">{{d.title}}</a>
<app-tag [tag]="t" *ngFor="let t of d.tags" class="ml-1" clickable="true" linkTitle="Filter by tag" (click)="filterByTag(t)"></app-tag>
<app-tag [tag]="t" *ngFor="let t of d.tags$ | async" class="ml-1" clickable="true" linkTitle="Filter by tag" (click)="filterByTag(t.id)"></app-tag>
</td>
<td class="d-none d-xl-table-cell">
<ng-container *ngIf="d.document_type">
<a [routerLink]="" (click)="filterByDocumentType(d.document_type)" title="Filter by document type">{{d.document_type.name}}</a>
<a [routerLink]="" (click)="filterByDocumentType(d.document_type)" title="Filter by document type">{{(d.document_type$ | async)?.name}}</a>
</ng-container>
</td>
<td>
@@ -152,5 +153,3 @@
<div class=" m-n2 row" *ngIf="displayMode == 'smallCards'">
<app-document-card-small [document]="d" *ngFor="let d of list.documents" (clickTag)="filterByTag($event)" (clickCorrespondent)="filterByCorrespondent($event)"></app-document-card-small>
</div>
<p *ngIf="list.documents.length == 0" class="mx-auto">No results</p>
@@ -3,9 +3,6 @@ import { ActivatedRoute } from '@angular/router';
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
import { cloneFilterRules, FilterRule } from 'src/app/data/filter-rule';
import { FILTER_CORRESPONDENT, FILTER_DOCUMENT_TYPE, FILTER_HAS_TAG, FILTER_RULE_TYPES } from 'src/app/data/filter-rule-type';
import { PaperlessCorrespondent } from 'src/app/data/paperless-correspondent';
import { PaperlessDocumentType } from 'src/app/data/paperless-document-type';
import { PaperlessTag } from 'src/app/data/paperless-tag';
import { SavedViewConfig } from 'src/app/data/saved-view-config';
import { DocumentListViewService } from 'src/app/services/document-list-view.service';
import { DOCUMENT_SORT_FIELDS } from 'src/app/services/rest/document.service';
@@ -51,13 +48,14 @@ export class DocumentListComponent implements OnInit {
this.route.paramMap.subscribe(params => {
if (params.has('id')) {
this.list.savedView = this.savedViewConfigService.getConfig(params.get('id'))
this.filterRules = this.list.filterRules
this.showFilter = false
} else {
this.list.savedView = null
this.filterRules = this.list.filterRules
this.showFilter = this.filterRules.length > 0
}
this.filterRules = this.list.filterRules
//this.showFilter = this.filterRules.length > 0
// prevents temporarily visible results from previous views
this.list.documents = []
this.list.clear()
this.list.reload()
})
}
@@ -66,6 +64,11 @@ export class DocumentListComponent implements OnInit {
this.list.filterRules = this.filterRules
}
clearFilterRules() {
this.list.filterRules = this.filterRules
this.showFilter = false
}
loadViewConfig(config: SavedViewConfig) {
this.filterRules = cloneFilterRules(config.filterRules)
this.list.load(config)
@@ -91,32 +94,42 @@ export class DocumentListComponent implements OnInit {
})
}
filterByTag(t: PaperlessTag) {
if (this.filterRules.find(rule => rule.type.id == FILTER_HAS_TAG && rule.value == t.id)) {
filterByTag(tag_id: number) {
let filterRules = this.list.filterRules
if (filterRules.find(rule => rule.type.id == FILTER_HAS_TAG && rule.value == tag_id)) {
return
}
this.filterRules.push({type: FILTER_RULE_TYPES.find(t => t.id == FILTER_HAS_TAG), value: t.id})
filterRules.push({type: FILTER_RULE_TYPES.find(t => t.id == FILTER_HAS_TAG), value: tag_id})
this.filterRules = filterRules
this.applyFilterRules()
}
filterByCorrespondent(c: PaperlessCorrespondent) {
let existing_rule = this.filterRules.find(rule => rule.type.id == FILTER_CORRESPONDENT)
if (existing_rule) {
existing_rule.value = c.id
filterByCorrespondent(correspondent_id: number) {
let filterRules = this.list.filterRules
let existing_rule = filterRules.find(rule => rule.type.id == FILTER_CORRESPONDENT)
if (existing_rule && existing_rule.value == correspondent_id) {
return
} else if (existing_rule) {
existing_rule.value = correspondent_id
} else {
this.filterRules.push({type: FILTER_RULE_TYPES.find(t => t.id == FILTER_CORRESPONDENT), value: c.id})
filterRules.push({type: FILTER_RULE_TYPES.find(t => t.id == FILTER_CORRESPONDENT), value: correspondent_id})
}
this.filterRules = filterRules
this.applyFilterRules()
}
filterByDocumentType(dt: PaperlessDocumentType) {
let existing_rule = this.filterRules.find(rule => rule.type.id == FILTER_DOCUMENT_TYPE)
if (existing_rule) {
existing_rule.value = dt.id
filterByDocumentType(document_type_id: number) {
let filterRules = this.list.filterRules
let existing_rule = filterRules.find(rule => rule.type.id == FILTER_DOCUMENT_TYPE)
if (existing_rule && existing_rule.value == document_type_id) {
return
} else if (existing_rule) {
existing_rule.value = document_type_id
} else {
this.filterRules.push({type: FILTER_RULE_TYPES.find(t => t.id == FILTER_DOCUMENT_TYPE), value: dt.id})
filterRules.push({type: FILTER_RULE_TYPES.find(t => t.id == FILTER_DOCUMENT_TYPE), value: document_type_id})
}
this.filterRules = filterRules
this.applyFilterRules()
}
@@ -18,6 +18,9 @@ export class FilterEditorComponent implements OnInit {
constructor(private documentTypeService: DocumentTypeService, private tagService: TagService, private correspondentService: CorrespondentService) { }
@Output()
clear = new EventEmitter()
@Input()
filterRules: FilterRule[] = []
@@ -48,7 +51,7 @@ export class FilterEditorComponent implements OnInit {
clearClicked() {
this.filterRules.splice(0,this.filterRules.length)
this.apply.next()
this.clear.next()
}
ngOnInit(): void {
@@ -1,13 +1,21 @@
<app-page-header title="Search results">
</app-page-header>
<p>Search string: <i>{{query}}</i></p>
<div *ngIf="errorMessage" class="alert alert-danger">Invalid search query: {{errorMessage}}</div>
<div [class.result-content-searching]="searching" infiniteScroll (scrolled)="onScroll()">
<p>
Search string: <i>{{query}}</i>
<ng-container *ngIf="correctedQuery">
- Did you mean "<a [routerLink]="" (click)="searchCorrectedQuery()">{{correctedQuery}}</a>"?
</ng-container>
</p>
<div *ngIf="!errorMessage" [class.result-content-searching]="searching" infiniteScroll (scrolled)="onScroll()">
<p>{{resultCount}} result(s)</p>
<app-document-card-large *ngFor="let result of results"
[document]="result.document"
[details]="result.highlights">
</app-document-card-large>
</div>
</div>
@@ -1,5 +1,5 @@
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { ActivatedRoute, Router } from '@angular/router';
import { SearchHit } from 'src/app/data/search-result';
import { SearchService } from 'src/app/services/rest/search.service';
@@ -9,7 +9,7 @@ import { SearchService } from 'src/app/services/rest/search.service';
styleUrls: ['./search.component.scss']
})
export class SearchComponent implements OnInit {
results: SearchHit[] = []
query: string = ""
@@ -22,7 +22,11 @@ export class SearchComponent implements OnInit {
resultCount
constructor(private searchService: SearchService, private route: ActivatedRoute) { }
correctedQuery: string = null
errorMessage: string
constructor(private searchService: SearchService, private route: ActivatedRoute, private router: Router) { }
ngOnInit(): void {
this.route.queryParamMap.subscribe(paramMap => {
@@ -31,10 +35,16 @@ export class SearchComponent implements OnInit {
this.currentPage = 1
this.loadPage()
})
}
searchCorrectedQuery() {
this.router.navigate(["search"], {queryParams: {query: this.correctedQuery}})
}
loadPage(append: boolean = false) {
this.errorMessage = null
this.correctedQuery = null
this.searchService.search(this.query, this.currentPage).subscribe(result => {
if (append) {
this.results.push(...result.results)
@@ -44,12 +54,17 @@ export class SearchComponent implements OnInit {
this.pageCount = result.page_count
this.searching = false
this.resultCount = result.count
this.correctedQuery = result.corrected_query
}, error => {
this.searching = false
this.resultCount = 1
this.pageCount = 1
this.results = []
this.errorMessage = error.error
})
}
onScroll() {
console.log(this.currentPage)
console.log(this.pageCount)
if (this.currentPage < this.pageCount) {
this.currentPage += 1
this.loadPage(true)
@@ -0,0 +1,11 @@
export interface PaperlessDocumentMetadata {
paperless__checksum?: string
paperless__mime_type?: string
paperless__filename?: string
paperless__has_archive_version?: boolean
}
+7 -6
View File
@@ -2,16 +2,17 @@ import { PaperlessCorrespondent } from './paperless-correspondent'
import { ObjectWithId } from './object-with-id'
import { PaperlessTag } from './paperless-tag'
import { PaperlessDocumentType } from './paperless-document-type'
import { Observable } from 'rxjs'
export interface PaperlessDocument extends ObjectWithId {
correspondent?: PaperlessCorrespondent
correspondent$?: Observable<PaperlessCorrespondent>
correspondent_id?: number
correspondent?: number
document_type?: PaperlessDocumentType
document_type$?: Observable<PaperlessDocumentType>
document_type_id?: number
document_type?: number
title?: string
@@ -19,9 +20,9 @@ export interface PaperlessDocument extends ObjectWithId {
file_type?: string
tags?: PaperlessTag[]
tags$?: Observable<PaperlessTag[]>
tags_id?: number[]
tags?: number[]
checksum?: string
+3 -1
View File
@@ -21,7 +21,9 @@ export interface SearchResult {
page?: number
page_count?: number
corrected_query?: string
results?: SearchHit[]
}
}
@@ -82,6 +82,12 @@ export class DocumentListViewService {
this.reload()
}
clear() {
this.collectionSize = null
this.documents = []
this.currentPage = 1
}
reload(onFinish?) {
this.isReloading = true
this.documentService.list(
@@ -1,5 +1,6 @@
import { HttpClient, HttpParams } from '@angular/common/http'
import { Observable } from 'rxjs'
import { Observable, of, Subject } from 'rxjs'
import { map, publishReplay, refCount } from 'rxjs/operators'
import { ObjectWithId } from 'src/app/data/object-with-id'
import { Results } from 'src/app/data/results'
import { environment } from 'src/environments/environment'
@@ -51,8 +52,28 @@ export abstract class AbstractPaperlessService<T extends ObjectWithId> {
return this.http.get<Results<T>>(this.getResourceUrl(), {params: httpParams})
}
private _listAll: Observable<Results<T>>
listAll(ordering?: string, extraParams?): Observable<Results<T>> {
return this.list(1, 100000, ordering, extraParams)
if (!this._listAll) {
this._listAll = this.list(1, 100000, ordering, extraParams).pipe(
publishReplay(1),
refCount()
)
}
return this._listAll
}
getCached(id: number): Observable<T> {
return this.listAll().pipe(
map(list => list.results.find(o => o.id == id))
)
}
getCachedMany(ids: number[]): Observable<T[]> {
return this.listAll().pipe(
map(list => ids.map(id => list.results.find(o => o.id == id)))
)
}
get(id: number): Observable<T> {
@@ -60,14 +81,17 @@ export abstract class AbstractPaperlessService<T extends ObjectWithId> {
}
create(o: T): Observable<T> {
this._listAll = null
return this.http.post<T>(this.getResourceUrl(), o)
}
delete(o: T): Observable<any> {
this._listAll = null
return this.http.delete(this.getResourceUrl(o.id))
}
update(o: T): Observable<T> {
this._listAll = null
return this.http.put<T>(this.getResourceUrl(o.id), o)
}
}
@@ -1,10 +1,15 @@
import { Injectable } from '@angular/core';
import { PaperlessDocument } from 'src/app/data/paperless-document';
import { PaperlessDocumentMetadata } from 'src/app/data/paperless-document-metadata';
import { AbstractPaperlessService } from './abstract-paperless-service';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
import { Results } from 'src/app/data/results';
import { FilterRule } from 'src/app/data/filter-rule';
import { map } from 'rxjs/operators';
import { CorrespondentService } from './correspondent.service';
import { DocumentTypeService } from './document-type.service';
import { TagService } from './tag.service';
export const DOCUMENT_SORT_FIELDS = [
@@ -26,7 +31,7 @@ export const SORT_DIRECTION_DESCENDING = "des"
})
export class DocumentService extends AbstractPaperlessService<PaperlessDocument> {
constructor(http: HttpClient) {
constructor(http: HttpClient, private correspondentService: CorrespondentService, private documentTypeService: DocumentTypeService, private tagService: TagService) {
super(http, 'documents')
}
@@ -46,26 +51,56 @@ export class DocumentService extends AbstractPaperlessService<PaperlessDocument>
}
}
list(page?: number, pageSize?: number, sortField?: string, sortDirection?: string, filterRules?: FilterRule[]): Observable<Results<PaperlessDocument>> {
return super.list(page, pageSize, sortField, sortDirection, this.filterRulesToQueryParams(filterRules))
addObservablesToDocument(doc: PaperlessDocument) {
if (doc.correspondent) {
doc.correspondent$ = this.correspondentService.getCached(doc.correspondent)
}
if (doc.document_type) {
doc.document_type$ = this.documentTypeService.getCached(doc.document_type)
}
if (doc.tags) {
doc.tags$ = this.tagService.getCachedMany(doc.tags)
}
return doc
}
getPreviewUrl(id: number): string {
return this.getResourceUrl(id, 'preview')
list(page?: number, pageSize?: number, sortField?: string, sortDirection?: string, filterRules?: FilterRule[]): Observable<Results<PaperlessDocument>> {
return super.list(page, pageSize, sortField, sortDirection, this.filterRulesToQueryParams(filterRules)).pipe(
map(results => {
results.results.forEach(doc => this.addObservablesToDocument(doc))
return results
})
)
}
getPreviewUrl(id: number, original: boolean = false): string {
let url = this.getResourceUrl(id, 'preview')
if (original) {
url += "?original=true"
}
return url
}
getThumbUrl(id: number): string {
return this.getResourceUrl(id, 'thumb')
}
getDownloadUrl(id: number): string {
return this.getResourceUrl(id, 'download')
getDownloadUrl(id: number, original: boolean = false): string {
let url = this.getResourceUrl(id, 'download')
if (original) {
url += "?original=true"
}
return url
}
uploadDocument(formData) {
return this.http.post(this.getResourceUrl(null, 'post_document'), formData)
}
getMetadata(id: number): Observable<PaperlessDocumentMetadata> {
return this.http.get<PaperlessDocumentMetadata>(this.getResourceUrl(id, 'metadata'))
}
bulk_edit(ids: number[], method: string, args: any[]) {
return this.http.post(this.getResourceUrl(null, 'bulk_edit'), {
'ids': ids,
@@ -1,9 +1,11 @@
import { HttpClient, HttpParams } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { PaperlessDocument } from 'src/app/data/paperless-document';
import { SearchResult } from 'src/app/data/search-result';
import { environment } from 'src/environments/environment';
import { DocumentService } from './document.service';
@Injectable({
@@ -11,14 +13,19 @@ import { environment } from 'src/environments/environment';
})
export class SearchService {
constructor(private http: HttpClient) { }
constructor(private http: HttpClient, private documentService: DocumentService) { }
search(query: string, page?: number): Observable<SearchResult> {
let httpParams = new HttpParams().set('query', query)
if (page) {
httpParams = httpParams.set('page', page.toString())
}
return this.http.get<SearchResult>(`${environment.apiBaseUrl}search/`, {params: httpParams})
return this.http.get<SearchResult>(`${environment.apiBaseUrl}search/`, {params: httpParams}).pipe(
map(result => {
result.results.forEach(hit => this.documentService.addObservablesToDocument(hit.document))
return result
})
)
}
autocomplete(term: string): Observable<string[]> {
Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB