Merge remote-tracking branch 'upstream/dev' into feature/dark-mode

This commit is contained in:
Michael Shamoon
2020-12-30 19:50:29 -08:00
30 changed files with 1818 additions and 72 deletions

View File

@@ -16,11 +16,11 @@
<div class="list-group list-group-flush">
<div class="list-group-item">
<div class="input-group input-group-sm">
<input class="form-control" type="text" [(ngModel)]="filterText" placeholder="Filter {{title}}" (keyup.enter)="listFilterEnter()" #listFilterTextInput>
<input class="form-control" type="text" [(ngModel)]="filterText" [placeholder]="filterPlaceholder" (keyup.enter)="listFilterEnter()" #listFilterTextInput>
</div>
</div>
<div *ngIf="selectionModel.items" class="items">
<ng-container *ngFor="let item of selectionModel.items | filter: filterText">
<ng-container *ngFor="let item of (editing ? selectionModel.itemsSorted : selectionModel.items) | filter: filterText">
<app-toggleable-dropdown-button *ngIf="allowSelectNone || item.id" [item]="item" [state]="selectionModel.get(item.id)" (toggle)="selectionModel.toggle(item.id)"></app-toggleable-dropdown-button>
</ng-container>
</div>

View File

@@ -18,6 +18,18 @@ export class FilterableDropdownSelectionModel {
items: MatchingModel[] = []
get itemsSorted(): MatchingModel[] {
return this.items.sort((a,b) => {
if (this.getNonTemporary(a.id) == ToggleableItemState.NotSelected && this.getNonTemporary(b.id) != ToggleableItemState.NotSelected) {
return 1
} else if (this.getNonTemporary(a.id) != ToggleableItemState.NotSelected && this.getNonTemporary(b.id) == ToggleableItemState.NotSelected) {
return -1
} else {
return a.name.localeCompare(b.name)
}
})
}
private selectionStates = new Map<number, ToggleableItemState>()
private temporarySelectionStates = new Map<number, ToggleableItemState>()
@@ -69,6 +81,10 @@ export class FilterableDropdownSelectionModel {
}
private getNonTemporary(id: number) {
return this.selectionStates.get(id) || ToggleableItemState.NotSelected
}
get(id: number) {
return this.temporarySelectionStates.get(id) || ToggleableItemState.NotSelected
}
@@ -142,7 +158,7 @@ export class FilterableDropdownComponent {
if (items) {
this._selectionModel.items = Array.from(items)
this._selectionModel.items.unshift({
name: $localize`Not assigned`,
name: $localize`:Filter drop down element to filter for documents with no correspondent/type/tag assigned:Not assigned`,
id: null
})
}
@@ -186,6 +202,9 @@ export class FilterableDropdownComponent {
@Input()
title: string
@Input()
filterPlaceholder: string = ""
@Input()
icon: string

View File

@@ -5,7 +5,9 @@
<ng-select name="tags" [items]="tags" bindLabel="name" bindValue="id" [(ngModel)]="displayValue"
[multiple]="true"
[closeOnSelect]="false"
[clearSearchOnAdd]="true"
[disabled]="disabled"
[hideSelected]="true"
(change)="ngSelectChange()">
<ng-template ng-label-tmp let-item="item">

View File

@@ -10,7 +10,7 @@
</ngx-file-drop>
</form>
<div *ngIf="uploadVisible" class="mt-3">
<p i18n>Uploading {{uploadStatus.length}} file(s)</p>
<p i18n>Uploading {uploadStatus.length, plural, =1 {file} =other {{{uploadStatus.length}} files}}...</p>
<ngb-progressbar [value]="loadedSum" [max]="totalSum" [striped]="true" [animated]="uploadStatus.length > 0">
</ngb-progressbar>
</div>

View File

@@ -4,8 +4,8 @@
<img src="assets/save-filter.png" class="float-right">
<p i18n>Paperless is running! :)</p>
<p i18n>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 i18n>Paperless offers some more features that try to make your life easier, such as:</p>
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 they will appear on the dashboard instead of this message.</p>
<p i18n>Paperless offers some more features that try to make your life easier:</p>
<ul>
<li i18n>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 i18n>You can configure paperless to read your mails and add documents from attached files.</li>

View File

@@ -159,7 +159,7 @@ export class DocumentDetailComponent implements OnInit {
delete() {
let modal = this.modalService.open(ConfirmDialogComponent, {backdrop: 'static'})
modal.componentInstance.title = $localize`Confirm delete`
modal.componentInstance.messageBold = $localize`Do you really want to delete document '${this.document.title}'?`
modal.componentInstance.messageBold = $localize`Do you really want to delete document "${this.document.title}"?`
modal.componentInstance.message = $localize`The files for this document will be deleted permanently. This operation cannot be undone.`
modal.componentInstance.btnClass = "btn-danger"
modal.componentInstance.btnCaption = $localize`Delete document`

View File

@@ -26,7 +26,8 @@
<div class="col-auto mb-2 mb-xl-0">
<div class="d-flex">
<label class="ml-auto mt-1 mb-0 mr-2" i18n>Edit:</label>
<app-filterable-dropdown class="mr-2 mr-md-3" title="Tags" icon="tag-fill"
<app-filterable-dropdown class="mr-2 mr-md-3" title="Tags" icon="tag-fill" i18n-title
filterPlaceholder="Filter tags" i18n-filterPlaceholder
[items]="tags"
[editing]="true"
[multiple]="true"
@@ -35,7 +36,8 @@
[(selectionModel)]="tagSelectionModel"
(apply)="setTags($event)">
</app-filterable-dropdown>
<app-filterable-dropdown class="mr-2 mr-md-3" title="Correspondent" icon="person-fill"
<app-filterable-dropdown class="mr-2 mr-md-3" title="Correspondent" icon="person-fill" i18n-title
filterPlaceholder="Filter correspondents" i18n-filterPlaceholder
[items]="correspondents"
[editing]="true"
[applyOnClose]="applyOnClose"
@@ -43,7 +45,8 @@
[(selectionModel)]="correspondentSelectionModel"
(apply)="setCorrespondents($event)">
</app-filterable-dropdown>
<app-filterable-dropdown class="mr-2 mr-md-3" title="Document Type" icon="file-earmark-fill"
<app-filterable-dropdown class="mr-2 mr-md-3" title="Document type" icon="file-earmark-fill" i18n-title
filterPlaceholder="Filter document types" i18n-filterPlaceholder
[items]="documentTypes"
[editing]="true"
[applyOnClose]="applyOnClose"

View File

@@ -100,10 +100,10 @@ export class BulkEditorComponent {
} else if (items.length == 1) {
return items[0].name
} else if (items.length == 2) {
return $localize`${items[0].name} and ${items[1].name}`
return $localize`:This is for messages like 'modify "tag1" and "tag2"':"${items[0].name}" and "${items[1].name}"`
} else {
let list = items.slice(0, items.length - 1).map(i => i.name).join($localize`, `)
return $localize`${list} and ${items[items.length - 1].name}`
let list = items.slice(0, items.length - 1).map(i => $localize`"${i.name}"`).join($localize`:this is used to separate enumerations and should probably be a comma and a whitespace in most languages:, `)
return $localize`:this is for messages like 'modify "tag1", "tag2" and "tag3"':${list} and "${items[items.length - 1].name}"`
}
}
@@ -115,16 +115,16 @@ export class BulkEditorComponent {
modal.componentInstance.title = $localize`Confirm tags assignment`
if (changedTags.itemsToAdd.length == 1 && changedTags.itemsToRemove.length == 0) {
let tag = changedTags.itemsToAdd[0]
modal.componentInstance.message = $localize`This operation will add the tag ${tag.name} to all ${this.list.selected.size} selected document(s).`
modal.componentInstance.message = $localize`This operation will add the tag "${tag.name}" to ${this.list.selected.size} selected document(s).`
} else if (changedTags.itemsToAdd.length > 1 && changedTags.itemsToRemove.length == 0) {
modal.componentInstance.message = $localize`This operation will add the tags ${this._localizeList(changedTags.itemsToAdd)} to all ${this.list.selected.size} selected document(s).`
modal.componentInstance.message = $localize`This operation will add the tags ${this._localizeList(changedTags.itemsToAdd)} to ${this.list.selected.size} selected document(s).`
} else if (changedTags.itemsToAdd.length == 0 && changedTags.itemsToRemove.length == 1) {
let tag = changedTags.itemsToRemove[0]
modal.componentInstance.message = $localize`This operation will remove the tag ${tag.name} from all ${this.list.selected.size} selected document(s).`
modal.componentInstance.message = $localize`This operation will remove the tag "${tag.name}" from ${this.list.selected.size} selected document(s).`
} else if (changedTags.itemsToAdd.length == 0 && changedTags.itemsToRemove.length > 1) {
modal.componentInstance.message = $localize`This operation will remove the tags ${this._localizeList(changedTags.itemsToRemove)} from all ${this.list.selected.size} selected document(s).`
modal.componentInstance.message = $localize`This operation will remove the tags ${this._localizeList(changedTags.itemsToRemove)} from ${this.list.selected.size} selected document(s).`
} else {
modal.componentInstance.message = $localize`This operation will add the tags ${this._localizeList(changedTags.itemsToAdd)} and remove the tags ${this._localizeList(changedTags.itemsToRemove)} on all ${this.list.selected.size} selected document(s).`
modal.componentInstance.message = $localize`This operation will add the tags ${this._localizeList(changedTags.itemsToAdd)} and remove the tags ${this._localizeList(changedTags.itemsToRemove)} on ${this.list.selected.size} selected document(s).`
}
modal.componentInstance.btnClass = "btn-warning"
@@ -156,9 +156,9 @@ export class BulkEditorComponent {
let modal = this.modalService.open(ConfirmDialogComponent, {backdrop: 'static'})
modal.componentInstance.title = $localize`Confirm correspondent assignment`
if (correspondent) {
modal.componentInstance.message = $localize`This operation will assign the correspondent ${correspondent.name} to all ${this.list.selected.size} selected document(s).`
modal.componentInstance.message = $localize`This operation will assign the correspondent "${correspondent.name}" to ${this.list.selected.size} selected document(s).`
} else {
modal.componentInstance.message = $localize`This operation will remove the correspondent from all ${this.list.selected.size} selected document(s).`
modal.componentInstance.message = $localize`This operation will remove the correspondent from ${this.list.selected.size} selected document(s).`
}
modal.componentInstance.btnClass = "btn-warning"
modal.componentInstance.btnCaption = $localize`Confirm`
@@ -189,9 +189,9 @@ export class BulkEditorComponent {
let modal = this.modalService.open(ConfirmDialogComponent, {backdrop: 'static'})
modal.componentInstance.title = $localize`Confirm document type assignment`
if (documentType) {
modal.componentInstance.message = $localize`This operation will assign the document type ${documentType.name} to all ${this.list.selected.size} selected document(s).`
modal.componentInstance.message = $localize`This operation will assign the document type "${documentType.name}" to ${this.list.selected.size} selected document(s).`
} else {
modal.componentInstance.message = $localize`This operation will remove the document type from all ${this.list.selected.size} selected document(s).`
modal.componentInstance.message = $localize`This operation will remove the document type from ${this.list.selected.size} selected document(s).`
}
modal.componentInstance.btnClass = "btn-warning"
modal.componentInstance.btnCaption = $localize`Confirm`
@@ -217,7 +217,7 @@ export class BulkEditorComponent {
let modal = this.modalService.open(ConfirmDialogComponent, {backdrop: 'static'})
modal.componentInstance.delayConfirm(5)
modal.componentInstance.title = $localize`Delete confirm`
modal.componentInstance.messageBold = $localize`This operation will permanently delete all ${this.list.selected.size} selected document(s).`
modal.componentInstance.messageBold = $localize`This operation will permanently delete ${this.list.selected.size} selected document(s).`
modal.componentInstance.message = $localize`This operation cannot be undone.`
modal.componentInstance.btnClass = "btn-danger"
modal.componentInstance.btnCaption = $localize`Delete document(s)`

View File

@@ -78,8 +78,8 @@
</app-page-header>
<div class="w-100 mb-2 mb-sm-4">
<app-filter-editor *ngIf="!isBulkEditing" [(filterRules)]="list.filterRules" #filterEditor></app-filter-editor>
<app-bulk-editor *ngIf="isBulkEditing"></app-bulk-editor>
<app-filter-editor [hidden]="isBulkEditing" [(filterRules)]="list.filterRules" #filterEditor></app-filter-editor>
<app-bulk-editor [hidden]="!isBulkEditing"></app-bulk-editor>
</div>
<div class="d-flex justify-content-between align-items-center">

View File

@@ -6,13 +6,37 @@
</div>
</div>
<div class="w-100 d-xl-none"></div>
<div class="col col-xl-auto mb-2 mb-xl-0">
<div class="d-flex">
<app-filterable-dropdown class="mr-2 mr-md-3" [items]="tags" [(selectionModel)]="tagSelectionModel" (selectionModelChange)="updateRules()" [multiple]="true" [allowSelectNone]="true" title="Tags" icon="tag-fill" i18n-title></app-filterable-dropdown>
<app-filterable-dropdown class="mr-2 mr-md-3" [items]="correspondents" [(selectionModel)]="correspondentSelectionModel" (selectionModelChange)="updateRules()" [allowSelectNone]="true" title="Correspondents" icon="person-fill" i18n-title></app-filterable-dropdown>
<app-filterable-dropdown class="mr-2 mr-md-3" [items]="documentTypes" [(selectionModel)]="documentTypeSelectionModel" (selectionModelChange)="updateRules()" [allowSelectNone]="true" title="Document types" icon="file-earmark-fill" i18n-title></app-filterable-dropdown>
<app-date-dropdown class="mr-2 mr-md-3" [(dateBefore)]="dateCreatedBefore" [(dateAfter)]="dateCreatedAfter" title="Created" (datesSet)="updateRules()" i18n-title></app-date-dropdown>
<app-date-dropdown [(dateBefore)]="dateAddedBefore" [(dateAfter)]="dateAddedAfter" title="Added" (datesSet)="updateRules()" i18n-title></app-date-dropdown>
<div class="col col-xl-auto mb-2 mb-xl-0">
<div class="d-flex">
<app-filterable-dropdown class="mr-2 mr-md-3" title="Tags" icon="tag-fill" i18n-title
filterPlaceholder="Filter tags" i18n-filterPlaceholder
[items]="tags"
[(selectionModel)]="tagSelectionModel"
(selectionModelChange)="updateRules()"
[multiple]="true"
[allowSelectNone]="true"></app-filterable-dropdown>
<app-filterable-dropdown class="mr-2 mr-md-3" title="Correspondent" icon="person-fill" i18n-title
filterPlaceholder="Filter correspondents" i18n-filterPlaceholder
[items]="correspondents"
[(selectionModel)]="correspondentSelectionModel"
(selectionModelChange)="updateRules()"
[allowSelectNone]="true"></app-filterable-dropdown>
<app-filterable-dropdown class="mr-2 mr-md-3" title="Document type" icon="file-earmark-fill" i18n-title
filterPlaceholder="Filter document types" i18n-filterPlaceholder
[items]="documentTypes"
[(selectionModel)]="documentTypeSelectionModel"
(selectionModelChange)="updateRules()"
[allowSelectNone]="true"></app-filterable-dropdown>
<app-date-dropdown class="mr-2 mr-md-3"
title="Created" i18n-title
(datesSet)="updateRules()"
[(dateBefore)]="dateCreatedBefore"
[(dateAfter)]="dateCreatedAfter"></app-date-dropdown>
<app-date-dropdown
[(dateBefore)]="dateAddedBefore"
[(dateAfter)]="dateAddedAfter"
title="Added" i18n-title
(datesSet)="updateRules()"></app-date-dropdown>
</div>
</div>
<div class="w-100 d-xl-none"></div>

View File

@@ -1,5 +1,4 @@
import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';
import { Component } from '@angular/core';
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';
@@ -16,20 +15,16 @@ import { CorrespondentEditDialogComponent } from './correspondent-edit-dialog/co
export class CorrespondentListComponent extends GenericListComponent<PaperlessCorrespondent> {
constructor(correspondentsService: CorrespondentService, modalService: NgbModal,
private router: Router,
private list: DocumentListViewService
) {
super(correspondentsService,modalService,CorrespondentEditDialogComponent)
}
getDeleteMessage(object: PaperlessCorrespondent) {
return $localize`Do you really want to delete the correspondent ${object.name}?`
return $localize`Do you really want to delete the correspondent "${object.name}"?`
}
filterDocuments(object: PaperlessCorrespondent) {
this.list.documentListView.filter_rules = [
{rule_type: FILTER_CORRESPONDENT, value: object.id.toString()}
]
this.router.navigate(["documents"])
this.list.quickFilter([{rule_type: FILTER_CORRESPONDENT, value: object.id.toString()}])
}
}

View File

@@ -1,5 +1,4 @@
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';
@@ -16,21 +15,17 @@ import { DocumentTypeEditDialogComponent } from './document-type-edit-dialog/doc
export class DocumentTypeListComponent extends GenericListComponent<PaperlessDocumentType> {
constructor(service: DocumentTypeService, modalService: NgbModal,
private router: Router,
private list: DocumentListViewService
) {
super(service, modalService, DocumentTypeEditDialogComponent)
}
getDeleteMessage(object: PaperlessDocumentType) {
return $localize`Do you really want to delete the document type ${object.name}?`
return $localize`Do you really want to delete the 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"])
this.list.quickFilter([{rule_type: FILTER_DOCUMENT_TYPE, value: object.id.toString()}])
}
}

View File

@@ -1,5 +1,4 @@
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';
@@ -16,7 +15,6 @@ import { TagEditDialogComponent } from './tag-edit-dialog/tag-edit-dialog.compon
export class TagListComponent extends GenericListComponent<PaperlessTag> {
constructor(tagService: TagService, modalService: NgbModal,
private router: Router,
private list: DocumentListViewService
) {
super(tagService, modalService, TagEditDialogComponent)
@@ -27,13 +25,11 @@ export class TagListComponent extends GenericListComponent<PaperlessTag> {
}
getDeleteMessage(object: PaperlessTag) {
return $localize`Do you really want to delete the tag ${object.name}?`
return $localize`Do you really want to delete the tag "${object.name}"?`
}
filterDocuments(object: PaperlessTag) {
this.list.documentListView.filter_rules = [
{rule_type: FILTER_HAS_TAG, value: object.id.toString()}
]
this.router.navigate(["documents"])
this.list.quickFilter([{rule_type: FILTER_HAS_TAG, value: object.id.toString()}])
}
}