mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2025-07-28 18:24:38 -05:00
Merge branch 'dev' into feature-bulk-edit
This commit is contained in:
@@ -121,7 +121,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() {
|
||||
|
@@ -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>
|
||||
|
@@ -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 {
|
||||
|
@@ -1,4 +1,4 @@
|
||||
<div class="col p-2 h-100 document-card" style="width: 16rem;">
|
||||
<div class="col p-2 h-100 document-card">
|
||||
<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">
|
||||
@@ -10,7 +10,6 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div style="top: 0; right: 0; font-size: large" class="text-right position-absolute mr-1">
|
||||
<div *ngFor="let t of getTagsLimited$() | async">
|
||||
<app-tag [tag]="t" (click)="clickTag.emit(t.id)" [clickable]="true" linkTitle="Filter by tag"></app-tag>
|
||||
@@ -31,7 +30,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 +50,7 @@
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
<small class="text-muted">{{document.created | date}}</small>
|
||||
<small class="text-muted pl-1">{{document.created | date}}</small>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
@@ -86,7 +86,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>
|
||||
|
||||
@@ -129,7 +129,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">
|
||||
@@ -147,6 +147,6 @@
|
||||
</table>
|
||||
|
||||
|
||||
<div class=" m-n2 row" *ngIf="displayMode == 'smallCards'">
|
||||
<div class="m-n2 row row-cols-paperless-cards" *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>
|
||||
</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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -3,6 +3,7 @@ import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
|
||||
import { Observable } from 'rxjs';
|
||||
import { map } from 'rxjs/operators';
|
||||
import { FILTER_CORRESPONDENT } from 'src/app/data/filter-rule-type';
|
||||
import { PaperlessSavedView } from 'src/app/data/paperless-saved-view';
|
||||
import { DocumentListViewService } from 'src/app/services/document-list-view.service';
|
||||
import { CorrespondentService } from 'src/app/services/rest/correspondent.service';
|
||||
@@ -94,6 +95,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),
|
||||
|
@@ -1,7 +1,6 @@
|
||||
import { Component, EventEmitter, Input, Output, ElementRef, ViewChild, SimpleChange } from '@angular/core';
|
||||
import { NgbDate, NgbDateStruct, NgbDatepicker } from '@ng-bootstrap/ng-bootstrap';
|
||||
|
||||
|
||||
export interface DateSelection {
|
||||
before?: NgbDateStruct
|
||||
after?: NgbDateStruct
|
||||
@@ -72,7 +71,6 @@ export class FilterDropdownDateComponent {
|
||||
}
|
||||
|
||||
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) {
|
||||
@@ -92,18 +90,23 @@ export class FilterDropdownDateComponent {
|
||||
break
|
||||
}
|
||||
this._dateAfter = newDate
|
||||
this._dateBefore = null
|
||||
this.datesSet.emit({after: newDate, before: null})
|
||||
}
|
||||
|
||||
onBeforeSelected(date: NgbDateStruct) {
|
||||
this._dateBefore = date
|
||||
this.datesSet.emit({after: this._dateAfter, before: date})
|
||||
}
|
||||
|
||||
onAfterSelected(date: NgbDateStruct) {
|
||||
this._dateAfter = date
|
||||
this.datesSet.emit({after: date, before: this._dateBefore})
|
||||
}
|
||||
|
||||
clear() {
|
||||
this._dateBefore = null
|
||||
this._dateAfter = null
|
||||
this.datesSet.emit({after: null, before: null})
|
||||
}
|
||||
}
|
||||
|
@@ -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" 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 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,
|
||||
@@ -200,24 +220,21 @@ export class FilterEditorComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
setDateFilter(date: NgbDateStruct, dateRuleTypeID: number) {
|
||||
let filterRules = this.filterRules
|
||||
let existingRule = filterRules.find(rule => rule.rule_type == dateRuleTypeID)
|
||||
let existingRule = this.filterRules.find(rule => rule.rule_type == dateRuleTypeID)
|
||||
let newValue = this.dateParser.format(date)
|
||||
|
||||
if (existingRule) {
|
||||
existingRule.value = newValue
|
||||
} else {
|
||||
filterRules.push({rule_type: dateRuleTypeID, value: newValue})
|
||||
this.filterRules.push({rule_type: dateRuleTypeID, value: newValue})
|
||||
}
|
||||
|
||||
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"
|
||||
};
|
||||
|
@@ -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