mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2025-04-02 13:45:10 -05:00
Merge remote-tracking branch 'upstream/dev' into feature-mailActionCustomTag
This commit is contained in:
commit
708638b97f
@ -424,14 +424,23 @@ PAPERLESS_OCR_IMAGE_DPI=<num>
|
||||
the produced PDF documents are A4 sized.
|
||||
|
||||
PAPERLESS_OCR_MAX_IMAGE_PIXELS=<num>
|
||||
Paperless will not OCR images that have more pixels than this limit.
|
||||
This is intended to prevent decompression bombs from overloading paperless.
|
||||
Increasing this limit is desired if you face a DecompressionBombError despite
|
||||
the concerning file not being malicious; this could e.g. be caused by invalidly
|
||||
recognized metadata.
|
||||
If you have enough resources or if you are certain that your uploaded files
|
||||
are not malicious you can increase this value to your needs.
|
||||
The default value is 256000000, an image with more pixels than that would not be parsed.
|
||||
Paperless will raise a warning when OCRing images which are over this limit and
|
||||
will not OCR images which are more than twice this limit. Note this does not
|
||||
prevent the document from being consumed, but could result in missing text content.
|
||||
|
||||
If unset, will default to the value determined by
|
||||
`Pillow <https://pillow.readthedocs.io/en/stable/reference/Image.html#PIL.Image.MAX_IMAGE_PIXELS>`_.
|
||||
|
||||
.. note::
|
||||
|
||||
Increasing this limit could cause Paperless to consume additional resources
|
||||
when consuming a file. Be sure you have sufficient system resources.
|
||||
|
||||
.. caution::
|
||||
|
||||
The limit is intended to prevent malicious files from consuming system resources
|
||||
and causing crashes and other errors. Only increase this value if you are certain
|
||||
your documents are not malicious and you need the text which was not OCRed
|
||||
|
||||
PAPERLESS_OCR_USER_ARGS=<json>
|
||||
OCRmyPDF offers many more options. Use this parameter to specify any
|
||||
|
@ -1340,7 +1340,7 @@
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/document-card-small/document-card-small.component.html</context>
|
||||
<context context-type="linenumber">88</context>
|
||||
<context context-type="linenumber">86</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="8659635229098859487" datatype="html">
|
||||
@ -1884,7 +1884,7 @@
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/document-card-small/document-card-small.component.html</context>
|
||||
<context context-type="linenumber">26</context>
|
||||
<context context-type="linenumber">24</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/document-list.component.html</context>
|
||||
@ -1899,7 +1899,7 @@
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/document-card-small/document-card-small.component.html</context>
|
||||
<context context-type="linenumber">15</context>
|
||||
<context context-type="linenumber">14</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/document-list.component.html</context>
|
||||
@ -1914,7 +1914,7 @@
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/document-card-small/document-card-small.component.html</context>
|
||||
<context context-type="linenumber">72</context>
|
||||
<context context-type="linenumber">70</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context>
|
||||
@ -1964,7 +1964,7 @@
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/document-card-small/document-card-small.component.html</context>
|
||||
<context context-type="linenumber">33</context>
|
||||
<context context-type="linenumber">31</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/document-list.component.html</context>
|
||||
@ -1979,7 +1979,7 @@
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/document-card-small/document-card-small.component.html</context>
|
||||
<context context-type="linenumber">40</context>
|
||||
<context context-type="linenumber">38</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/document-list.component.html</context>
|
||||
@ -1994,7 +1994,7 @@
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/document-card-small/document-card-small.component.html</context>
|
||||
<context context-type="linenumber">50</context>
|
||||
<context context-type="linenumber">48</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="2030261243264601523" datatype="html">
|
||||
@ -2005,7 +2005,7 @@
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/document-card-small/document-card-small.component.html</context>
|
||||
<context context-type="linenumber">51</context>
|
||||
<context context-type="linenumber">49</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="4235671847487610290" datatype="html">
|
||||
@ -2016,7 +2016,7 @@
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/document-card-small/document-card-small.component.html</context>
|
||||
<context context-type="linenumber">52</context>
|
||||
<context context-type="linenumber">50</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="2332107018974972998" datatype="html">
|
||||
@ -2764,35 +2764,56 @@
|
||||
<source>Saved view "<x id="PH" equiv-text="savedView.name"/>" deleted.</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.ts</context>
|
||||
<context context-type="linenumber">167</context>
|
||||
<context context-type="linenumber">174</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="5647210819299459618" datatype="html">
|
||||
<source>Settings saved successfully.</source>
|
||||
<trans-unit id="3891152409365583719" datatype="html">
|
||||
<source>Settings saved</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.ts</context>
|
||||
<context context-type="linenumber">238</context>
|
||||
<context context-type="linenumber">247</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="7217000812750597833" datatype="html">
|
||||
<source>Settings were saved successfully.</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.ts</context>
|
||||
<context context-type="linenumber">248</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="525012668859298131" datatype="html">
|
||||
<source>Settings were saved successfully. Reload is required to apply some changes.</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.ts</context>
|
||||
<context context-type="linenumber">252</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="8491974984518503778" datatype="html">
|
||||
<source>Reload now</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.ts</context>
|
||||
<context context-type="linenumber">253</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="3011185103048412841" datatype="html">
|
||||
<source>An error occurred while saving settings.</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.ts</context>
|
||||
<context context-type="linenumber">242</context>
|
||||
<context context-type="linenumber">263</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="6839066544204061364" datatype="html">
|
||||
<source>Use system language</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.ts</context>
|
||||
<context context-type="linenumber">250</context>
|
||||
<context context-type="linenumber">271</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="7729897675462249787" datatype="html">
|
||||
<source>Use date format of display language</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.ts</context>
|
||||
<context context-type="linenumber">257</context>
|
||||
<context context-type="linenumber">278</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="8488620293789898901" datatype="html">
|
||||
@ -2801,7 +2822,7 @@
|
||||
)"/></source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.ts</context>
|
||||
<context context-type="linenumber">277,279</context>
|
||||
<context context-type="linenumber">298,300</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="5101757640976222639" datatype="html">
|
||||
|
@ -22,7 +22,6 @@ import {
|
||||
RemoteVersionService,
|
||||
AppRemoteVersion,
|
||||
} from 'src/app/services/rest/remote-version.service'
|
||||
import { QueryParamsService } from 'src/app/services/query-params.service'
|
||||
import { SettingsService } from 'src/app/services/settings.service'
|
||||
|
||||
@Component({
|
||||
@ -38,7 +37,7 @@ export class AppFrameComponent {
|
||||
private searchService: SearchService,
|
||||
public savedViewService: SavedViewService,
|
||||
private remoteVersionService: RemoteVersionService,
|
||||
private queryParamsService: QueryParamsService,
|
||||
private list: DocumentListViewService,
|
||||
public settingsService: SettingsService
|
||||
) {
|
||||
this.remoteVersionService
|
||||
@ -94,7 +93,7 @@ export class AppFrameComponent {
|
||||
|
||||
search() {
|
||||
this.closeMenu()
|
||||
this.queryParamsService.navigateWithFilterRules([
|
||||
this.list.quickFilter([
|
||||
{
|
||||
rule_type: FILTER_FULLTEXT_QUERY,
|
||||
value: (this.searchField.value as string).trim(),
|
||||
|
@ -2,7 +2,7 @@
|
||||
<label class="form-label" [for]="inputId">{{title}}</label>
|
||||
<div class="input-group" [class.is-invalid]="error">
|
||||
<input class="form-control" [class.is-invalid]="error" [placeholder]="placeholder" [id]="inputId" maxlength="10"
|
||||
(dateSelect)="onChange(value)" (change)="onChange(value)" (keypress)="onKeyPress($event)"
|
||||
(dateSelect)="onChange(value)" (change)="onChange(value)" (keypress)="onKeyPress($event)" (paste)="onPaste($event)"
|
||||
name="dp" [(ngModel)]="value" ngbDatepicker #datePicker="ngbDatepicker" #datePickerContent="ngModel">
|
||||
<button class="btn btn-outline-secondary calendar" (click)="datePicker.toggle()" type="button">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-calendar" viewBox="0 0 16 16">
|
||||
|
@ -1,6 +1,8 @@
|
||||
import { Component, forwardRef, OnInit } from '@angular/core'
|
||||
import { NG_VALUE_ACCESSOR } from '@angular/forms'
|
||||
import { NgbDateParserFormatter } from '@ng-bootstrap/ng-bootstrap'
|
||||
import { SettingsService } from 'src/app/services/settings.service'
|
||||
import { LocalizedDateParserFormatter } from 'src/app/utils/ngb-date-parser-formatter'
|
||||
import { AbstractInputComponent } from '../abstract-input'
|
||||
|
||||
@Component({
|
||||
@ -19,7 +21,10 @@ export class DateComponent
|
||||
extends AbstractInputComponent<string>
|
||||
implements OnInit
|
||||
{
|
||||
constructor(private settings: SettingsService) {
|
||||
constructor(
|
||||
private settings: SettingsService,
|
||||
private ngbDateParserFormatter: NgbDateParserFormatter
|
||||
) {
|
||||
super()
|
||||
}
|
||||
|
||||
@ -30,7 +35,20 @@ export class DateComponent
|
||||
|
||||
placeholder: string
|
||||
|
||||
// prevent chars other than numbers and separators
|
||||
onPaste(event: ClipboardEvent) {
|
||||
const clipboardData: DataTransfer =
|
||||
event.clipboardData || window['clipboardData']
|
||||
if (clipboardData) {
|
||||
event.preventDefault()
|
||||
let pastedText = clipboardData.getData('text')
|
||||
pastedText = pastedText.replace(/[\sa-z#!$%\^&\*;:{}=\-_`~()]+/g, '')
|
||||
const parsedDate = this.ngbDateParserFormatter.parse(pastedText)
|
||||
const formattedDate = this.ngbDateParserFormatter.format(parsedDate)
|
||||
this.writeValue(formattedDate)
|
||||
this.onChange(formattedDate)
|
||||
}
|
||||
}
|
||||
|
||||
onKeyPress(event: KeyboardEvent) {
|
||||
if ('Enter' !== event.key && !/[0-9,\.\/-]+/.test(event.key)) {
|
||||
event.preventDefault()
|
||||
|
@ -4,5 +4,5 @@
|
||||
[class]="toast.classname"
|
||||
(hidden)="toastService.closeToast(toast)">
|
||||
<p>{{toast.content}}</p>
|
||||
<p *ngIf="toast.action"><button class="btn btn-sm btn-outline-secondary" (click)="toastService.closeToast(toast); toast.action()">{{toast.actionName}}</button></p>
|
||||
<p class="mb-0" *ngIf="toast.action"><button class="btn btn-sm btn-outline-secondary" (click)="toastService.closeToast(toast); toast.action()">{{toast.actionName}}</button></p>
|
||||
</ngb-toast>
|
||||
|
@ -7,8 +7,8 @@ import { ConsumerStatusService } from 'src/app/services/consumer-status.service'
|
||||
import { DocumentService } from 'src/app/services/rest/document.service'
|
||||
import { PaperlessTag } from 'src/app/data/paperless-tag'
|
||||
import { FILTER_HAS_TAGS_ALL } from 'src/app/data/filter-rule-type'
|
||||
import { QueryParamsService } from 'src/app/services/query-params.service'
|
||||
import { OpenDocumentsService } from 'src/app/services/open-documents.service'
|
||||
import { DocumentListViewService } from 'src/app/services/document-list-view.service'
|
||||
|
||||
@Component({
|
||||
selector: 'app-saved-view-widget',
|
||||
@ -21,7 +21,7 @@ export class SavedViewWidgetComponent implements OnInit, OnDestroy {
|
||||
constructor(
|
||||
private documentService: DocumentService,
|
||||
private router: Router,
|
||||
private queryParamsService: QueryParamsService,
|
||||
private list: DocumentListViewService,
|
||||
private consumerStatusService: ConsumerStatusService,
|
||||
public openDocumentsService: OpenDocumentsService
|
||||
) {}
|
||||
@ -73,7 +73,7 @@ export class SavedViewWidgetComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
clickTag(tag: PaperlessTag) {
|
||||
this.queryParamsService.navigateWithFilterRules([
|
||||
this.list.quickFilter([
|
||||
{ rule_type: FILTER_HAS_TAGS_ALL, value: tag.id.toString() },
|
||||
])
|
||||
}
|
||||
|
@ -32,7 +32,6 @@ import {
|
||||
import { PaperlessDocumentSuggestions } from 'src/app/data/paperless-document-suggestions'
|
||||
import { FILTER_FULLTEXT_MORELIKE } from 'src/app/data/filter-rule-type'
|
||||
import { normalizeDateStr } from 'src/app/utils/date'
|
||||
import { QueryParamsService } from 'src/app/services/query-params.service'
|
||||
import { StoragePathService } from 'src/app/services/rest/storage-path.service'
|
||||
import { PaperlessStoragePath } from 'src/app/data/paperless-storage-path'
|
||||
import { StoragePathEditDialogComponent } from '../common/edit-dialog/storage-path-edit-dialog/storage-path-edit-dialog.component'
|
||||
@ -120,8 +119,7 @@ export class DocumentDetailComponent
|
||||
private documentTitlePipe: DocumentTitlePipe,
|
||||
private toastService: ToastService,
|
||||
private settings: SettingsService,
|
||||
private storagePathService: StoragePathService,
|
||||
private queryParamsService: QueryParamsService
|
||||
private storagePathService: StoragePathService
|
||||
) {}
|
||||
|
||||
titleKeyUp(event) {
|
||||
@ -494,7 +492,7 @@ export class DocumentDetailComponent
|
||||
}
|
||||
|
||||
moreLike() {
|
||||
this.queryParamsService.navigateWithFilterRules([
|
||||
this.documentListViewService.quickFilter([
|
||||
{
|
||||
rule_type: FILTER_FULLTEXT_MORELIKE,
|
||||
value: this.documentId.toString(),
|
||||
|
@ -93,7 +93,7 @@
|
||||
<span i18n *ngIf="list.selected.size == 0">{list.collectionSize, plural, =1 {One document} other {{{list.collectionSize || 0}} documents}}</span> <span i18n *ngIf="isFiltered">(filtered)</span>
|
||||
</ng-container>
|
||||
</p>
|
||||
<ngb-pagination [pageSize]="list.currentPageSize" [collectionSize]="list.collectionSize" [(page)]="list.currentPage" [maxSize]="5"
|
||||
<ngb-pagination [pageSize]="list.currentPageSize" [collectionSize]="list.collectionSize" (pageChange)="setPage($event)" [page]="list.currentPage" [maxSize]="5"
|
||||
[rotate]="true" aria-label="Default pagination"></ngb-pagination>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
@ -1,5 +1,4 @@
|
||||
import {
|
||||
AfterViewInit,
|
||||
Component,
|
||||
OnDestroy,
|
||||
OnInit,
|
||||
@ -21,7 +20,6 @@ import {
|
||||
import { ConsumerStatusService } from 'src/app/services/consumer-status.service'
|
||||
import { DocumentListViewService } from 'src/app/services/document-list-view.service'
|
||||
import { OpenDocumentsService } from 'src/app/services/open-documents.service'
|
||||
import { QueryParamsService } from 'src/app/services/query-params.service'
|
||||
import {
|
||||
DOCUMENT_SORT_FIELDS,
|
||||
DOCUMENT_SORT_FIELDS_FULLTEXT,
|
||||
@ -36,7 +34,7 @@ import { SaveViewConfigDialogComponent } from './save-view-config-dialog/save-vi
|
||||
templateUrl: './document-list.component.html',
|
||||
styleUrls: ['./document-list.component.scss'],
|
||||
})
|
||||
export class DocumentListComponent implements OnInit, OnDestroy, AfterViewInit {
|
||||
export class DocumentListComponent implements OnInit, OnDestroy {
|
||||
constructor(
|
||||
public list: DocumentListViewService,
|
||||
public savedViewService: SavedViewService,
|
||||
@ -45,7 +43,6 @@ export class DocumentListComponent implements OnInit, OnDestroy, AfterViewInit {
|
||||
private toastService: ToastService,
|
||||
private modalService: NgbModal,
|
||||
private consumerStatusService: ConsumerStatusService,
|
||||
private queryParamsService: QueryParamsService,
|
||||
public openDocumentsService: OpenDocumentsService
|
||||
) {}
|
||||
|
||||
@ -76,8 +73,6 @@ export class DocumentListComponent implements OnInit, OnDestroy, AfterViewInit {
|
||||
|
||||
set listSort(reverse: boolean) {
|
||||
this.list.sortReverse = reverse
|
||||
this.queryParamsService.sortField = this.list.sortField
|
||||
this.queryParamsService.sortReverse = reverse
|
||||
}
|
||||
|
||||
get listSort(): boolean {
|
||||
@ -86,14 +81,14 @@ export class DocumentListComponent implements OnInit, OnDestroy, AfterViewInit {
|
||||
|
||||
setSortField(field: string) {
|
||||
this.list.sortField = field
|
||||
this.queryParamsService.sortField = field
|
||||
this.queryParamsService.sortReverse = this.listSort
|
||||
}
|
||||
|
||||
onSort(event: SortEvent) {
|
||||
this.list.setSort(event.column, event.reverse)
|
||||
this.queryParamsService.sortField = event.column
|
||||
this.queryParamsService.sortReverse = event.reverse
|
||||
}
|
||||
|
||||
setPage(page: number) {
|
||||
this.list.currentPage = page
|
||||
}
|
||||
|
||||
get isBulkEditing(): boolean {
|
||||
@ -133,7 +128,6 @@ export class DocumentListComponent implements OnInit, OnDestroy, AfterViewInit {
|
||||
}
|
||||
this.list.activateSavedView(view)
|
||||
this.list.reload()
|
||||
this.queryParamsService.updateFromView(view)
|
||||
this.unmodifiedFilterRules = view.filter_rules
|
||||
})
|
||||
|
||||
@ -148,22 +142,12 @@ export class DocumentListComponent implements OnInit, OnDestroy, AfterViewInit {
|
||||
this.loadViewConfig(parseInt(queryParams.get('view')))
|
||||
} else {
|
||||
this.list.activateSavedView(null)
|
||||
this.queryParamsService.parseQueryParams(queryParams)
|
||||
this.list.loadFromQueryParams(queryParams)
|
||||
this.unmodifiedFilterRules = []
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
ngAfterViewInit(): void {
|
||||
this.filterEditor.filterRulesChange
|
||||
.pipe(takeUntil(this.unsubscribeNotifier))
|
||||
.subscribe({
|
||||
next: (filterRules) => {
|
||||
this.queryParamsService.updateFilterRules(filterRules)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
// unsubscribes all
|
||||
this.unsubscribeNotifier.next(this)
|
||||
@ -175,9 +159,8 @@ export class DocumentListComponent implements OnInit, OnDestroy, AfterViewInit {
|
||||
.getCached(viewId)
|
||||
.pipe(first())
|
||||
.subscribe((view) => {
|
||||
this.list.loadSavedView(view)
|
||||
this.list.activateSavedView(view)
|
||||
this.list.reload()
|
||||
this.queryParamsService.updateFromView(view)
|
||||
})
|
||||
}
|
||||
|
||||
@ -246,34 +229,26 @@ export class DocumentListComponent implements OnInit, OnDestroy, AfterViewInit {
|
||||
|
||||
clickTag(tagID: number) {
|
||||
this.list.selectNone()
|
||||
setTimeout(() => {
|
||||
this.filterEditor.addTag(tagID)
|
||||
})
|
||||
this.filterEditor.addTag(tagID)
|
||||
}
|
||||
|
||||
clickCorrespondent(correspondentID: number) {
|
||||
this.list.selectNone()
|
||||
setTimeout(() => {
|
||||
this.filterEditor.addCorrespondent(correspondentID)
|
||||
})
|
||||
this.filterEditor.addCorrespondent(correspondentID)
|
||||
}
|
||||
|
||||
clickDocumentType(documentTypeID: number) {
|
||||
this.list.selectNone()
|
||||
setTimeout(() => {
|
||||
this.filterEditor.addDocumentType(documentTypeID)
|
||||
})
|
||||
this.filterEditor.addDocumentType(documentTypeID)
|
||||
}
|
||||
|
||||
clickStoragePath(storagePathID: number) {
|
||||
this.list.selectNone()
|
||||
setTimeout(() => {
|
||||
this.filterEditor.addStoragePath(storagePathID)
|
||||
})
|
||||
this.filterEditor.addStoragePath(storagePathID)
|
||||
}
|
||||
|
||||
clickMoreLike(documentID: number) {
|
||||
this.queryParamsService.navigateWithFilterRules([
|
||||
this.list.quickFilter([
|
||||
{ rule_type: FILTER_FULLTEXT_MORELIKE, value: documentID.toString() },
|
||||
])
|
||||
}
|
||||
|
@ -313,7 +313,10 @@ export class FilterEditorComponent implements OnInit, OnDestroy {
|
||||
break
|
||||
case FILTER_ASN_ISNULL:
|
||||
this.textFilterTarget = TEXT_FILTER_TARGET_ASN
|
||||
this.textFilterModifier = TEXT_FILTER_MODIFIER_NULL
|
||||
this.textFilterModifier =
|
||||
rule.value == 'true' || rule.value == '1'
|
||||
? TEXT_FILTER_MODIFIER_NULL
|
||||
: TEXT_FILTER_MODIFIER_NOTNULL
|
||||
break
|
||||
case FILTER_ASN_GT:
|
||||
this.textFilterTarget = TEXT_FILTER_TARGET_ASN
|
||||
|
@ -3,7 +3,7 @@ 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 { CustomDatePipe } from 'src/app/pipes/custom-date.pipe'
|
||||
import { QueryParamsService } from 'src/app/services/query-params.service'
|
||||
import { DocumentListViewService } from 'src/app/services/document-list-view.service'
|
||||
import { CorrespondentService } from 'src/app/services/rest/correspondent.service'
|
||||
import { ToastService } from 'src/app/services/toast.service'
|
||||
import { CorrespondentEditDialogComponent } from '../../common/edit-dialog/correspondent-edit-dialog/correspondent-edit-dialog.component'
|
||||
@ -20,7 +20,7 @@ export class CorrespondentListComponent extends ManagementListComponent<Paperles
|
||||
correspondentsService: CorrespondentService,
|
||||
modalService: NgbModal,
|
||||
toastService: ToastService,
|
||||
queryParamsService: QueryParamsService,
|
||||
documentListViewService: DocumentListViewService,
|
||||
private datePipe: CustomDatePipe
|
||||
) {
|
||||
super(
|
||||
@ -28,7 +28,7 @@ export class CorrespondentListComponent extends ManagementListComponent<Paperles
|
||||
modalService,
|
||||
CorrespondentEditDialogComponent,
|
||||
toastService,
|
||||
queryParamsService,
|
||||
documentListViewService,
|
||||
FILTER_CORRESPONDENT,
|
||||
$localize`correspondent`,
|
||||
$localize`correspondents`,
|
||||
|
@ -2,7 +2,7 @@ import { Component } from '@angular/core'
|
||||
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 { QueryParamsService } from 'src/app/services/query-params.service'
|
||||
import { DocumentListViewService } from 'src/app/services/document-list-view.service'
|
||||
import { DocumentTypeService } from 'src/app/services/rest/document-type.service'
|
||||
import { ToastService } from 'src/app/services/toast.service'
|
||||
import { DocumentTypeEditDialogComponent } from '../../common/edit-dialog/document-type-edit-dialog/document-type-edit-dialog.component'
|
||||
@ -18,14 +18,14 @@ export class DocumentTypeListComponent extends ManagementListComponent<Paperless
|
||||
documentTypeService: DocumentTypeService,
|
||||
modalService: NgbModal,
|
||||
toastService: ToastService,
|
||||
queryParamsService: QueryParamsService
|
||||
documentListViewService: DocumentListViewService
|
||||
) {
|
||||
super(
|
||||
documentTypeService,
|
||||
modalService,
|
||||
DocumentTypeEditDialogComponent,
|
||||
toastService,
|
||||
queryParamsService,
|
||||
documentListViewService,
|
||||
FILTER_DOCUMENT_TYPE,
|
||||
$localize`document type`,
|
||||
$localize`document types`,
|
||||
|
@ -18,7 +18,7 @@ import {
|
||||
SortableDirective,
|
||||
SortEvent,
|
||||
} from 'src/app/directives/sortable.directive'
|
||||
import { QueryParamsService } from 'src/app/services/query-params.service'
|
||||
import { DocumentListViewService } from 'src/app/services/document-list-view.service'
|
||||
import { AbstractNameFilterService } from 'src/app/services/rest/abstract-name-filter-service'
|
||||
import { ToastService } from 'src/app/services/toast.service'
|
||||
import { ConfirmDialogComponent } from '../../common/confirm-dialog/confirm-dialog.component'
|
||||
@ -42,7 +42,7 @@ export abstract class ManagementListComponent<T extends ObjectWithId>
|
||||
private modalService: NgbModal,
|
||||
private editDialogComponent: any,
|
||||
private toastService: ToastService,
|
||||
private queryParamsService: QueryParamsService,
|
||||
private documentListViewService: DocumentListViewService,
|
||||
protected filterRuleType: number,
|
||||
public typeName: string,
|
||||
public typeNamePlural: string,
|
||||
@ -141,7 +141,7 @@ export abstract class ManagementListComponent<T extends ObjectWithId>
|
||||
}
|
||||
|
||||
filterDocuments(object: ObjectWithId) {
|
||||
this.queryParamsService.navigateWithFilterRules([
|
||||
this.documentListViewService.quickFilter([
|
||||
{ rule_type: this.filterRuleType, value: object.id.toString() },
|
||||
])
|
||||
}
|
||||
|
@ -22,7 +22,7 @@
|
||||
<option *ngFor="let lang of displayLanguageOptions" [ngValue]="lang.code">{{lang.name}}<span *ngIf="lang.code && currentLocale != 'en-US'"> - {{lang.englishName}}</span></option>
|
||||
</select>
|
||||
|
||||
<small class="form-text text-muted" i18n>You need to reload the page after applying a new language.</small>
|
||||
<small *ngIf="displayLanguageIsDirty" class="form-text text-primary" i18n>You need to reload the page after applying a new language.</small>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
@ -14,7 +14,7 @@ import {
|
||||
LanguageOption,
|
||||
SettingsService,
|
||||
} from 'src/app/services/settings.service'
|
||||
import { ToastService } from 'src/app/services/toast.service'
|
||||
import { Toast, ToastService } from 'src/app/services/toast.service'
|
||||
import { dirtyCheck, DirtyComponent } from '@ngneat/dirty-check-forms'
|
||||
import { Observable, Subscription, BehaviorSubject, first } from 'rxjs'
|
||||
import { SETTINGS_KEYS } from 'src/app/data/paperless-uisettings'
|
||||
@ -61,6 +61,13 @@ export class SettingsComponent implements OnInit, OnDestroy, DirtyComponent {
|
||||
)
|
||||
}
|
||||
|
||||
get displayLanguageIsDirty(): boolean {
|
||||
return (
|
||||
this.settingsForm.get('displayLanguage').value !=
|
||||
this.store?.getValue()['displayLanguage']
|
||||
)
|
||||
}
|
||||
|
||||
constructor(
|
||||
public savedViewService: SavedViewService,
|
||||
private documentListViewService: DocumentListViewService,
|
||||
@ -170,6 +177,7 @@ export class SettingsComponent implements OnInit, OnDestroy, DirtyComponent {
|
||||
}
|
||||
|
||||
private saveLocalSettings() {
|
||||
const reloadRequired = this.displayLanguageIsDirty // just this one, for now
|
||||
this.settings.set(
|
||||
SETTINGS_KEYS.BULK_EDIT_APPLY_ON_CLOSE,
|
||||
this.settingsForm.value.bulkEditApplyOnClose
|
||||
@ -235,7 +243,20 @@ export class SettingsComponent implements OnInit, OnDestroy, DirtyComponent {
|
||||
this.store.next(this.settingsForm.value)
|
||||
this.documentListViewService.updatePageSize()
|
||||
this.settings.updateAppearanceSettings()
|
||||
this.toastService.showInfo($localize`Settings saved successfully.`)
|
||||
let savedToast: Toast = {
|
||||
title: $localize`Settings saved`,
|
||||
content: $localize`Settings were saved successfully.`,
|
||||
delay: 500000,
|
||||
}
|
||||
if (reloadRequired) {
|
||||
;(savedToast.content = $localize`Settings were saved successfully. Reload is required to apply some changes.`),
|
||||
(savedToast.actionName = $localize`Reload now`)
|
||||
savedToast.action = () => {
|
||||
location.reload()
|
||||
}
|
||||
}
|
||||
|
||||
this.toastService.show(savedToast)
|
||||
},
|
||||
error: (error) => {
|
||||
this.toastService.showError(
|
||||
|
@ -3,7 +3,6 @@ import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
|
||||
import { FILTER_STORAGE_PATH } from 'src/app/data/filter-rule-type'
|
||||
import { PaperlessStoragePath } from 'src/app/data/paperless-storage-path'
|
||||
import { DocumentListViewService } from 'src/app/services/document-list-view.service'
|
||||
import { QueryParamsService } from 'src/app/services/query-params.service'
|
||||
import { StoragePathService } from 'src/app/services/rest/storage-path.service'
|
||||
import { ToastService } from 'src/app/services/toast.service'
|
||||
import { StoragePathEditDialogComponent } from '../../common/edit-dialog/storage-path-edit-dialog/storage-path-edit-dialog.component'
|
||||
@ -19,14 +18,14 @@ export class StoragePathListComponent extends ManagementListComponent<PaperlessS
|
||||
directoryService: StoragePathService,
|
||||
modalService: NgbModal,
|
||||
toastService: ToastService,
|
||||
queryParamsService: QueryParamsService
|
||||
documentListViewService: DocumentListViewService
|
||||
) {
|
||||
super(
|
||||
directoryService,
|
||||
modalService,
|
||||
StoragePathEditDialogComponent,
|
||||
toastService,
|
||||
queryParamsService,
|
||||
documentListViewService,
|
||||
FILTER_STORAGE_PATH,
|
||||
$localize`storage path`,
|
||||
$localize`storage paths`,
|
||||
|
@ -2,7 +2,7 @@ import { Component } from '@angular/core'
|
||||
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
|
||||
import { FILTER_HAS_TAGS_ALL } from 'src/app/data/filter-rule-type'
|
||||
import { PaperlessTag } from 'src/app/data/paperless-tag'
|
||||
import { QueryParamsService } from 'src/app/services/query-params.service'
|
||||
import { DocumentListViewService } from 'src/app/services/document-list-view.service'
|
||||
import { TagService } from 'src/app/services/rest/tag.service'
|
||||
import { ToastService } from 'src/app/services/toast.service'
|
||||
import { TagEditDialogComponent } from '../../common/edit-dialog/tag-edit-dialog/tag-edit-dialog.component'
|
||||
@ -18,14 +18,14 @@ export class TagListComponent extends ManagementListComponent<PaperlessTag> {
|
||||
tagService: TagService,
|
||||
modalService: NgbModal,
|
||||
toastService: ToastService,
|
||||
queryParamsService: QueryParamsService
|
||||
documentListViewService: DocumentListViewService
|
||||
) {
|
||||
super(
|
||||
tagService,
|
||||
modalService,
|
||||
TagEditDialogComponent,
|
||||
toastService,
|
||||
queryParamsService,
|
||||
documentListViewService,
|
||||
FILTER_HAS_TAGS_ALL,
|
||||
$localize`tag`,
|
||||
$localize`tags`,
|
||||
|
@ -1,34 +1,38 @@
|
||||
export const FILTER_TITLE = 0
|
||||
export const FILTER_CONTENT = 1
|
||||
|
||||
export const FILTER_ASN = 2
|
||||
export const FILTER_ASN_ISNULL = 18
|
||||
export const FILTER_ASN_GT = 23
|
||||
export const FILTER_ASN_LT = 24
|
||||
|
||||
export const FILTER_CORRESPONDENT = 3
|
||||
|
||||
export const FILTER_DOCUMENT_TYPE = 4
|
||||
|
||||
export const FILTER_IS_IN_INBOX = 5
|
||||
export const FILTER_HAS_TAGS_ALL = 6
|
||||
export const FILTER_HAS_ANY_TAG = 7
|
||||
export const FILTER_DOES_NOT_HAVE_TAG = 17
|
||||
export const FILTER_HAS_TAGS_ANY = 22
|
||||
|
||||
export const FILTER_STORAGE_PATH = 25
|
||||
|
||||
export const FILTER_CREATED_BEFORE = 8
|
||||
export const FILTER_CREATED_AFTER = 9
|
||||
export const FILTER_CREATED_YEAR = 10
|
||||
export const FILTER_CREATED_MONTH = 11
|
||||
export const FILTER_CREATED_DAY = 12
|
||||
|
||||
export const FILTER_ADDED_BEFORE = 13
|
||||
export const FILTER_ADDED_AFTER = 14
|
||||
|
||||
export const FILTER_MODIFIED_BEFORE = 15
|
||||
export const FILTER_MODIFIED_AFTER = 16
|
||||
|
||||
export const FILTER_DOES_NOT_HAVE_TAG = 17
|
||||
|
||||
export const FILTER_ASN_ISNULL = 18
|
||||
export const FILTER_ASN_GT = 19
|
||||
export const FILTER_ASN_LT = 20
|
||||
|
||||
export const FILTER_TITLE_CONTENT = 21
|
||||
|
||||
export const FILTER_FULLTEXT_QUERY = 22
|
||||
export const FILTER_FULLTEXT_MORELIKE = 23
|
||||
|
||||
export const FILTER_STORAGE_PATH = 30
|
||||
export const FILTER_TITLE_CONTENT = 19
|
||||
export const FILTER_FULLTEXT_QUERY = 20
|
||||
export const FILTER_FULLTEXT_MORELIKE = 21
|
||||
|
||||
export const FILTER_RULE_TYPES: FilterRuleType[] = [
|
||||
{
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { Injectable } from '@angular/core'
|
||||
import { ActivatedRoute, Params, Router } from '@angular/router'
|
||||
import { ParamMap, Router } from '@angular/router'
|
||||
import { Observable } from 'rxjs'
|
||||
import {
|
||||
cloneFilterRules,
|
||||
@ -10,13 +10,14 @@ import { PaperlessDocument } from '../data/paperless-document'
|
||||
import { PaperlessSavedView } from '../data/paperless-saved-view'
|
||||
import { SETTINGS_KEYS } from '../data/paperless-uisettings'
|
||||
import { DOCUMENT_LIST_SERVICE } from '../data/storage-keys'
|
||||
import { generateParams, parseParams } from '../utils/query-params'
|
||||
import { DocumentService, DOCUMENT_SORT_FIELDS } from './rest/document.service'
|
||||
import { SettingsService } from './settings.service'
|
||||
|
||||
/**
|
||||
* Captures the current state of the list view.
|
||||
*/
|
||||
interface ListViewState {
|
||||
export interface ListViewState {
|
||||
/**
|
||||
* Title of the document list view. Either "Documents" (localized) or the name of a saved view.
|
||||
*/
|
||||
@ -32,7 +33,7 @@ interface ListViewState {
|
||||
/**
|
||||
* Total amount of documents with the current filter rules. Used to calculate the number of pages.
|
||||
*/
|
||||
collectionSize: number
|
||||
collectionSize?: number
|
||||
|
||||
/**
|
||||
* Currently selected sort field.
|
||||
@ -85,6 +86,32 @@ export class DocumentListViewService {
|
||||
return this.activeListViewState.title
|
||||
}
|
||||
|
||||
constructor(
|
||||
private documentService: DocumentService,
|
||||
private settings: SettingsService,
|
||||
private router: Router
|
||||
) {
|
||||
let documentListViewConfigJson = localStorage.getItem(
|
||||
DOCUMENT_LIST_SERVICE.CURRENT_VIEW_CONFIG
|
||||
)
|
||||
if (documentListViewConfigJson) {
|
||||
try {
|
||||
let savedState: ListViewState = JSON.parse(documentListViewConfigJson)
|
||||
// Remove null elements from the restored state
|
||||
Object.keys(savedState).forEach((k) => {
|
||||
if (savedState[k] == null) {
|
||||
delete savedState[k]
|
||||
}
|
||||
})
|
||||
//only use restored state attributes instead of defaults if they are not null
|
||||
let newState = Object.assign(this.defaultListViewState(), savedState)
|
||||
this.listViewStates.set(null, newState)
|
||||
} catch (e) {
|
||||
localStorage.removeItem(DOCUMENT_LIST_SERVICE.CURRENT_VIEW_CONFIG)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private defaultListViewState(): ListViewState {
|
||||
return {
|
||||
title: null,
|
||||
@ -122,20 +149,40 @@ export class DocumentListViewService {
|
||||
if (closeCurrentView) {
|
||||
this._activeSavedViewId = null
|
||||
}
|
||||
|
||||
this.activeListViewState.filterRules = cloneFilterRules(view.filter_rules)
|
||||
this.activeListViewState.sortField = view.sort_field
|
||||
this.activeListViewState.sortReverse = view.sort_reverse
|
||||
if (this._activeSavedViewId) {
|
||||
this.activeListViewState.title = view.name
|
||||
}
|
||||
|
||||
this.reduceSelectionToFilter()
|
||||
|
||||
if (!this.router.routerState.snapshot.url.includes('/view/')) {
|
||||
this.router.navigate([], {
|
||||
queryParams: { view: view.id },
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
reload(onFinish?) {
|
||||
loadFromQueryParams(queryParams: ParamMap) {
|
||||
const paramsEmpty: boolean = queryParams.keys.length == 0
|
||||
let newState: ListViewState = this.listViewStates.get(null)
|
||||
if (!paramsEmpty) newState = parseParams(queryParams)
|
||||
if (newState == undefined) newState = this.defaultListViewState() // if nothing in local storage
|
||||
|
||||
this.activeListViewState.filterRules = newState.filterRules
|
||||
this.activeListViewState.sortField = newState.sortField
|
||||
this.activeListViewState.sortReverse = newState.sortReverse
|
||||
this.activeListViewState.currentPage = newState.currentPage
|
||||
this.reload(null, paramsEmpty) // update the params if there arent any
|
||||
}
|
||||
|
||||
reload(onFinish?, updateQueryParams: boolean = true) {
|
||||
this.isReloading = true
|
||||
this.error = null
|
||||
let activeListViewState = this.activeListViewState
|
||||
|
||||
this.documentService
|
||||
.listFiltered(
|
||||
activeListViewState.currentPage,
|
||||
@ -149,6 +196,14 @@ export class DocumentListViewService {
|
||||
this.isReloading = false
|
||||
activeListViewState.collectionSize = result.count
|
||||
activeListViewState.documents = result.results
|
||||
|
||||
if (updateQueryParams && !this._activeSavedViewId) {
|
||||
let base = ['/documents']
|
||||
this.router.navigate(base, {
|
||||
queryParams: generateParams(activeListViewState),
|
||||
})
|
||||
}
|
||||
|
||||
if (onFinish) {
|
||||
onFinish()
|
||||
}
|
||||
@ -191,6 +246,7 @@ export class DocumentListViewService {
|
||||
) {
|
||||
this.activeListViewState.sortField = 'created'
|
||||
}
|
||||
this._activeSavedViewId = null
|
||||
this.activeListViewState.filterRules = filterRules
|
||||
this.reload()
|
||||
this.reduceSelectionToFilter()
|
||||
@ -202,6 +258,7 @@ export class DocumentListViewService {
|
||||
}
|
||||
|
||||
set sortField(field: string) {
|
||||
this._activeSavedViewId = null
|
||||
this.activeListViewState.sortField = field
|
||||
this.reload()
|
||||
this.saveDocumentListView()
|
||||
@ -212,6 +269,7 @@ export class DocumentListViewService {
|
||||
}
|
||||
|
||||
set sortReverse(reverse: boolean) {
|
||||
this._activeSavedViewId = null
|
||||
this.activeListViewState.sortReverse = reverse
|
||||
this.reload()
|
||||
this.saveDocumentListView()
|
||||
@ -221,13 +279,6 @@ export class DocumentListViewService {
|
||||
return this.activeListViewState.sortReverse
|
||||
}
|
||||
|
||||
get sortParams(): Params {
|
||||
return {
|
||||
sortField: this.sortField,
|
||||
sortReverse: this.sortReverse,
|
||||
}
|
||||
}
|
||||
|
||||
get collectionSize(): number {
|
||||
return this.activeListViewState.collectionSize
|
||||
}
|
||||
@ -237,6 +288,8 @@ export class DocumentListViewService {
|
||||
}
|
||||
|
||||
set currentPage(page: number) {
|
||||
if (this.activeListViewState.currentPage == page) return
|
||||
this._activeSavedViewId = null
|
||||
this.activeListViewState.currentPage = page
|
||||
this.reload()
|
||||
this.saveDocumentListView()
|
||||
@ -273,6 +326,10 @@ export class DocumentListViewService {
|
||||
}
|
||||
}
|
||||
|
||||
quickFilter(filterRules: FilterRule[]) {
|
||||
this.filterRules = filterRules
|
||||
}
|
||||
|
||||
getLastPage(): number {
|
||||
return Math.ceil(this.collectionSize / this.currentPageSize)
|
||||
}
|
||||
@ -431,29 +488,4 @@ export class DocumentListViewService {
|
||||
documentIndexInCurrentView(documentID: number): number {
|
||||
return this.documents.map((d) => d.id).indexOf(documentID)
|
||||
}
|
||||
|
||||
constructor(
|
||||
private documentService: DocumentService,
|
||||
private settings: SettingsService
|
||||
) {
|
||||
let documentListViewConfigJson = localStorage.getItem(
|
||||
DOCUMENT_LIST_SERVICE.CURRENT_VIEW_CONFIG
|
||||
)
|
||||
if (documentListViewConfigJson) {
|
||||
try {
|
||||
let savedState: ListViewState = JSON.parse(documentListViewConfigJson)
|
||||
// Remove null elements from the restored state
|
||||
Object.keys(savedState).forEach((k) => {
|
||||
if (savedState[k] == null) {
|
||||
delete savedState[k]
|
||||
}
|
||||
})
|
||||
//only use restored state attributes instead of defaults if they are not null
|
||||
let newState = Object.assign(this.defaultListViewState(), savedState)
|
||||
this.listViewStates.set(null, newState)
|
||||
} catch (e) {
|
||||
localStorage.removeItem(DOCUMENT_LIST_SERVICE.CURRENT_VIEW_CONFIG)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,163 +0,0 @@
|
||||
import { Injectable } from '@angular/core'
|
||||
import { ParamMap, Params, Router } from '@angular/router'
|
||||
import { FilterRule } from '../data/filter-rule'
|
||||
import { FilterRuleType, FILTER_RULE_TYPES } from '../data/filter-rule-type'
|
||||
import { PaperlessSavedView } from '../data/paperless-saved-view'
|
||||
import { DocumentListViewService } from './document-list-view.service'
|
||||
|
||||
const SORT_FIELD_PARAMETER = 'sort'
|
||||
const SORT_REVERSE_PARAMETER = 'reverse'
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class QueryParamsService {
|
||||
constructor(private router: Router, private list: DocumentListViewService) {}
|
||||
|
||||
private filterParams: Params = {}
|
||||
private sortParams: Params = {}
|
||||
|
||||
updateFilterRules(
|
||||
filterRules: FilterRule[],
|
||||
updateQueryParams: boolean = true
|
||||
) {
|
||||
this.filterParams = filterRulesToQueryParams(filterRules)
|
||||
if (updateQueryParams) this.updateQueryParams()
|
||||
}
|
||||
|
||||
set sortField(field: string) {
|
||||
this.sortParams[SORT_FIELD_PARAMETER] = field
|
||||
this.updateQueryParams()
|
||||
}
|
||||
|
||||
set sortReverse(reverse: boolean) {
|
||||
if (!reverse) this.sortParams[SORT_REVERSE_PARAMETER] = undefined
|
||||
else this.sortParams[SORT_REVERSE_PARAMETER] = reverse
|
||||
this.updateQueryParams()
|
||||
}
|
||||
|
||||
get params(): Params {
|
||||
return {
|
||||
...this.sortParams,
|
||||
...this.filterParams,
|
||||
}
|
||||
}
|
||||
|
||||
private updateQueryParams() {
|
||||
// if we were on a saved view we navigate 'away' to /documents
|
||||
let base = []
|
||||
if (this.router.routerState.snapshot.url.includes('/view/'))
|
||||
base = ['/documents']
|
||||
|
||||
this.router.navigate(base, {
|
||||
queryParams: this.params,
|
||||
})
|
||||
}
|
||||
|
||||
public parseQueryParams(queryParams: ParamMap) {
|
||||
let filterRules = filterRulesFromQueryParams(queryParams)
|
||||
if (
|
||||
filterRules.length ||
|
||||
queryParams.has(SORT_FIELD_PARAMETER) ||
|
||||
queryParams.has(SORT_REVERSE_PARAMETER)
|
||||
) {
|
||||
this.list.filterRules = filterRules
|
||||
this.list.sortField = queryParams.get(SORT_FIELD_PARAMETER)
|
||||
this.list.sortReverse =
|
||||
queryParams.has(SORT_REVERSE_PARAMETER) ||
|
||||
(!queryParams.has(SORT_FIELD_PARAMETER) &&
|
||||
!queryParams.has(SORT_REVERSE_PARAMETER))
|
||||
this.list.reload()
|
||||
} else if (
|
||||
filterRules.length == 0 &&
|
||||
!queryParams.has(SORT_FIELD_PARAMETER)
|
||||
) {
|
||||
// this is navigating to /documents so we need to update the params from the list
|
||||
this.updateFilterRules(this.list.filterRules, false)
|
||||
this.sortParams[SORT_FIELD_PARAMETER] = this.list.sortField
|
||||
this.sortParams[SORT_REVERSE_PARAMETER] = this.list.sortReverse
|
||||
this.router.navigate([], {
|
||||
queryParams: this.params,
|
||||
replaceUrl: true,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
updateFromView(view: PaperlessSavedView) {
|
||||
if (!this.router.routerState.snapshot.url.includes('/view/')) {
|
||||
// navigation for /documents?view=
|
||||
this.router.navigate([], {
|
||||
queryParams: { view: view.id },
|
||||
})
|
||||
}
|
||||
// make sure params are up-to-date
|
||||
this.updateFilterRules(view.filter_rules, false)
|
||||
this.sortParams[SORT_FIELD_PARAMETER] = this.list.sortField
|
||||
this.sortParams[SORT_REVERSE_PARAMETER] = this.list.sortReverse
|
||||
}
|
||||
|
||||
navigateWithFilterRules(filterRules: FilterRule[]) {
|
||||
this.updateFilterRules(filterRules)
|
||||
this.router.navigate(['/documents'], {
|
||||
queryParams: this.params,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export function filterRulesToQueryParams(filterRules: FilterRule[]): Object {
|
||||
if (filterRules) {
|
||||
let params = {}
|
||||
for (let rule of filterRules) {
|
||||
let ruleType = FILTER_RULE_TYPES.find((t) => t.id == rule.rule_type)
|
||||
if (ruleType.multi) {
|
||||
params[ruleType.filtervar] = params[ruleType.filtervar]
|
||||
? params[ruleType.filtervar] + ',' + rule.value
|
||||
: rule.value
|
||||
} else if (ruleType.isnull_filtervar && rule.value == null) {
|
||||
params[ruleType.isnull_filtervar] = true
|
||||
} else {
|
||||
params[ruleType.filtervar] = rule.value
|
||||
}
|
||||
}
|
||||
return params
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
export function filterRulesFromQueryParams(queryParams: ParamMap) {
|
||||
const allFilterRuleQueryParams: string[] = FILTER_RULE_TYPES.map(
|
||||
(rt) => rt.filtervar
|
||||
)
|
||||
.concat(FILTER_RULE_TYPES.map((rt) => rt.isnull_filtervar))
|
||||
.filter((rt) => rt !== undefined)
|
||||
|
||||
// transform query params to filter rules
|
||||
let filterRulesFromQueryParams: FilterRule[] = []
|
||||
allFilterRuleQueryParams
|
||||
.filter((frqp) => queryParams.has(frqp))
|
||||
.forEach((filterQueryParamName) => {
|
||||
const rule_type: FilterRuleType = FILTER_RULE_TYPES.find(
|
||||
(rt) =>
|
||||
rt.filtervar == filterQueryParamName ||
|
||||
rt.isnull_filtervar == filterQueryParamName
|
||||
)
|
||||
const isNullRuleType = rule_type.isnull_filtervar == filterQueryParamName
|
||||
const valueURIComponent: string = queryParams.get(filterQueryParamName)
|
||||
const filterQueryParamValues: string[] = rule_type.multi
|
||||
? valueURIComponent.split(',')
|
||||
: [valueURIComponent]
|
||||
|
||||
filterRulesFromQueryParams = filterRulesFromQueryParams.concat(
|
||||
// map all values to filter rules
|
||||
filterQueryParamValues.map((val) => {
|
||||
return {
|
||||
rule_type: rule_type.id,
|
||||
value: isNullRuleType ? null : val,
|
||||
}
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
return filterRulesFromQueryParams
|
||||
}
|
@ -6,12 +6,12 @@ import { HttpClient, HttpParams } 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 { map, tap } from 'rxjs/operators'
|
||||
import { CorrespondentService } from './correspondent.service'
|
||||
import { DocumentTypeService } from './document-type.service'
|
||||
import { TagService } from './tag.service'
|
||||
import { PaperlessDocumentSuggestions } from 'src/app/data/paperless-document-suggestions'
|
||||
import { filterRulesToQueryParams } from '../query-params.service'
|
||||
import { queryParamsFromFilterRules } from '../../utils/query-params'
|
||||
import { StoragePathService } from './storage-path.service'
|
||||
|
||||
export const DOCUMENT_SORT_FIELDS = [
|
||||
@ -70,7 +70,13 @@ export class DocumentService extends AbstractPaperlessService<PaperlessDocument>
|
||||
doc.document_type$ = this.documentTypeService.getCached(doc.document_type)
|
||||
}
|
||||
if (doc.tags) {
|
||||
doc.tags$ = this.tagService.getCachedMany(doc.tags)
|
||||
doc.tags$ = this.tagService
|
||||
.getCachedMany(doc.tags)
|
||||
.pipe(
|
||||
tap((tags) =>
|
||||
tags.sort((tagA, tagB) => tagA.name.localeCompare(tagB.name))
|
||||
)
|
||||
)
|
||||
}
|
||||
if (doc.storage_path) {
|
||||
doc.storage_path$ = this.storagePathService.getCached(doc.storage_path)
|
||||
@ -91,7 +97,7 @@ export class DocumentService extends AbstractPaperlessService<PaperlessDocument>
|
||||
pageSize,
|
||||
sortField,
|
||||
sortReverse,
|
||||
Object.assign(extraParams, filterRulesToQueryParams(filterRules))
|
||||
Object.assign(extraParams, queryParamsFromFilterRules(filterRules))
|
||||
).pipe(
|
||||
map((results) => {
|
||||
results.results.forEach((doc) => this.addObservablesToDocument(doc))
|
||||
|
101
src-ui/src/app/utils/query-params.ts
Normal file
101
src-ui/src/app/utils/query-params.ts
Normal file
@ -0,0 +1,101 @@
|
||||
import { ParamMap, Params } from '@angular/router'
|
||||
import { FilterRule } from '../data/filter-rule'
|
||||
import { FilterRuleType, FILTER_RULE_TYPES } from '../data/filter-rule-type'
|
||||
import { ListViewState } from '../services/document-list-view.service'
|
||||
|
||||
const SORT_FIELD_PARAMETER = 'sort'
|
||||
const SORT_REVERSE_PARAMETER = 'reverse'
|
||||
const PAGE_PARAMETER = 'page'
|
||||
|
||||
export function generateParams(viewState: ListViewState): Params {
|
||||
let params = queryParamsFromFilterRules(viewState.filterRules)
|
||||
params[SORT_FIELD_PARAMETER] = viewState.sortField
|
||||
params[SORT_REVERSE_PARAMETER] = viewState.sortReverse ? 1 : undefined
|
||||
params[PAGE_PARAMETER] = isNaN(viewState.currentPage)
|
||||
? 1
|
||||
: viewState.currentPage
|
||||
return params
|
||||
}
|
||||
|
||||
export function parseParams(queryParams: ParamMap): ListViewState {
|
||||
let filterRules = filterRulesFromQueryParams(queryParams)
|
||||
let sortField = queryParams.get(SORT_FIELD_PARAMETER)
|
||||
let sortReverse =
|
||||
queryParams.has(SORT_REVERSE_PARAMETER) ||
|
||||
(!queryParams.has(SORT_FIELD_PARAMETER) &&
|
||||
!queryParams.has(SORT_REVERSE_PARAMETER))
|
||||
let currentPage = queryParams.has(PAGE_PARAMETER)
|
||||
? parseInt(queryParams.get(PAGE_PARAMETER))
|
||||
: 1
|
||||
return {
|
||||
currentPage: currentPage,
|
||||
filterRules: filterRules,
|
||||
sortField: sortField,
|
||||
sortReverse: sortReverse,
|
||||
}
|
||||
}
|
||||
|
||||
export function filterRulesFromQueryParams(
|
||||
queryParams: ParamMap
|
||||
): FilterRule[] {
|
||||
const allFilterRuleQueryParams: string[] = FILTER_RULE_TYPES.map(
|
||||
(rt) => rt.filtervar
|
||||
)
|
||||
.concat(FILTER_RULE_TYPES.map((rt) => rt.isnull_filtervar))
|
||||
.filter((rt) => rt !== undefined)
|
||||
|
||||
// transform query params to filter rules
|
||||
let filterRulesFromQueryParams: FilterRule[] = []
|
||||
allFilterRuleQueryParams
|
||||
.filter((frqp) => queryParams.has(frqp))
|
||||
.forEach((filterQueryParamName) => {
|
||||
const rule_type: FilterRuleType = FILTER_RULE_TYPES.find(
|
||||
(rt) =>
|
||||
rt.filtervar == filterQueryParamName ||
|
||||
rt.isnull_filtervar == filterQueryParamName
|
||||
)
|
||||
const isNullRuleType = rule_type.isnull_filtervar == filterQueryParamName
|
||||
const valueURIComponent: string = queryParams.get(filterQueryParamName)
|
||||
const filterQueryParamValues: string[] = rule_type.multi
|
||||
? valueURIComponent.split(',')
|
||||
: [valueURIComponent]
|
||||
|
||||
filterRulesFromQueryParams = filterRulesFromQueryParams.concat(
|
||||
// map all values to filter rules
|
||||
filterQueryParamValues.map((val) => {
|
||||
if (rule_type.datatype == 'boolean')
|
||||
val = val.replace('1', 'true').replace('0', 'false')
|
||||
return {
|
||||
rule_type: rule_type.id,
|
||||
value: isNullRuleType ? null : val,
|
||||
}
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
return filterRulesFromQueryParams
|
||||
}
|
||||
|
||||
export function queryParamsFromFilterRules(filterRules: FilterRule[]): Params {
|
||||
if (filterRules) {
|
||||
let params = {}
|
||||
for (let rule of filterRules) {
|
||||
let ruleType = FILTER_RULE_TYPES.find((t) => t.id == rule.rule_type)
|
||||
if (ruleType.multi) {
|
||||
params[ruleType.filtervar] = params[ruleType.filtervar]
|
||||
? params[ruleType.filtervar] + ',' + rule.value
|
||||
: rule.value
|
||||
} else if (ruleType.isnull_filtervar && rule.value == null) {
|
||||
params[ruleType.isnull_filtervar] = 1
|
||||
} else {
|
||||
params[ruleType.filtervar] = rule.value
|
||||
if (ruleType.datatype == 'boolean')
|
||||
params[ruleType.filtervar] =
|
||||
rule.value == 'true' || rule.value == '1' ? 1 : 0
|
||||
}
|
||||
}
|
||||
return params
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
}
|
@ -84,6 +84,10 @@ svg.logo {
|
||||
}
|
||||
}
|
||||
|
||||
.text-primary {
|
||||
color: var(--bs-primary) !important;
|
||||
}
|
||||
|
||||
.btn-outline-primary {
|
||||
border-color: var(--bs-primary) !important;
|
||||
color: var(--bs-primary) !important;
|
||||
|
@ -186,7 +186,8 @@ $form-check-radio-checked-bg-image-dark: url("data:image/svg+xml,<svg xmlns='htt
|
||||
|
||||
.toast,
|
||||
.toast .toast-header,
|
||||
.toast .btn-close {
|
||||
.toast .btn,
|
||||
.toast .btn-close, {
|
||||
color: var(--pngx-primary-text-contrast);
|
||||
}
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ import multiprocessing
|
||||
import os
|
||||
import re
|
||||
from typing import Final
|
||||
from typing import Optional
|
||||
from typing import Set
|
||||
from urllib.parse import urlparse
|
||||
|
||||
@ -551,10 +552,9 @@ OCR_ROTATE_PAGES_THRESHOLD = float(
|
||||
os.getenv("PAPERLESS_OCR_ROTATE_PAGES_THRESHOLD", 12.0),
|
||||
)
|
||||
|
||||
OCR_MAX_IMAGE_PIXELS = os.environ.get(
|
||||
"PAPERLESS_OCR_MAX_IMAGE_PIXELS",
|
||||
256000000,
|
||||
)
|
||||
OCR_MAX_IMAGE_PIXELS: Optional[int] = None
|
||||
if os.environ.get("PAPERLESS_OCR_MAX_IMAGE_PIXELS") is not None:
|
||||
OCR_MAX_IMAGE_PIXELS: int = int(os.environ.get("PAPERLESS_OCR_MAX_IMAGE_PIXELS"))
|
||||
|
||||
OCR_USER_ARGS = os.getenv("PAPERLESS_OCR_USER_ARGS", "{}")
|
||||
|
||||
|
@ -8,8 +8,6 @@ from documents.parsers import make_thumbnail_from_pdf
|
||||
from documents.parsers import ParseError
|
||||
from PIL import Image
|
||||
|
||||
Image.MAX_IMAGE_PIXELS = settings.OCR_MAX_IMAGE_PIXELS
|
||||
|
||||
|
||||
class NoTextFoundException(Exception):
|
||||
pass
|
||||
@ -225,6 +223,24 @@ class RasterisedDocumentParser(DocumentParser):
|
||||
f"they will not be used. Error: {e}",
|
||||
)
|
||||
|
||||
if settings.OCR_MAX_IMAGE_PIXELS is not None:
|
||||
# Convert pixels to mega-pixels and provide to ocrmypdf
|
||||
max_pixels_mpixels = settings.OCR_MAX_IMAGE_PIXELS / 1_000_000.0
|
||||
if max_pixels_mpixels > 0:
|
||||
|
||||
self.log(
|
||||
"debug",
|
||||
f"Calculated {max_pixels_mpixels} megapixels for OCR",
|
||||
)
|
||||
|
||||
ocrmypdf_args["max_image_mpixels"] = max_pixels_mpixels
|
||||
else:
|
||||
self.log(
|
||||
"warning",
|
||||
"There is an issue with PAPERLESS_OCR_MAX_IMAGE_PIXELS, "
|
||||
"this value must be at least 1 megapixel if set",
|
||||
)
|
||||
|
||||
return ocrmypdf_args
|
||||
|
||||
def parse(self, document_path, mime_type, file_name=None):
|
||||
|
@ -6,8 +6,6 @@ from PIL import Image
|
||||
from PIL import ImageDraw
|
||||
from PIL import ImageFont
|
||||
|
||||
Image.MAX_IMAGE_PIXELS = settings.OCR_MAX_IMAGE_PIXELS
|
||||
|
||||
|
||||
class TextDocumentParser(DocumentParser):
|
||||
"""
|
||||
@ -28,7 +26,7 @@ class TextDocumentParser(DocumentParser):
|
||||
font = ImageFont.truetype(
|
||||
font=settings.THUMBNAIL_FONT_NAME,
|
||||
size=20,
|
||||
layout_engine=ImageFont.LAYOUT_BASIC,
|
||||
layout_engine=ImageFont.Layout.BASIC,
|
||||
)
|
||||
draw.text((5, 5), read_text(), font=font, fill="black")
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user