mirror of
				https://github.com/paperless-ngx/paperless-ngx.git
				synced 2025-11-03 19:17:13 -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.
 | 
					    the produced PDF documents are A4 sized.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
PAPERLESS_OCR_MAX_IMAGE_PIXELS=<num>
 | 
					PAPERLESS_OCR_MAX_IMAGE_PIXELS=<num>
 | 
				
			||||||
    Paperless will not OCR images that have more pixels than this limit.
 | 
					    Paperless will raise a warning when OCRing images which are over this limit and
 | 
				
			||||||
    This is intended to prevent decompression bombs from overloading paperless.
 | 
					    will not OCR images which are more than twice this limit.  Note this does not
 | 
				
			||||||
    Increasing this limit is desired if you face a DecompressionBombError despite
 | 
					    prevent the document from being consumed, but could result in missing text content.
 | 
				
			||||||
    the concerning file not being malicious; this could e.g. be caused by invalidly
 | 
					
 | 
				
			||||||
    recognized metadata.
 | 
					    If unset, will default to the value determined by
 | 
				
			||||||
    If you have enough resources or if you are certain that your uploaded files
 | 
					    `Pillow <https://pillow.readthedocs.io/en/stable/reference/Image.html#PIL.Image.MAX_IMAGE_PIXELS>`_.
 | 
				
			||||||
    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.
 | 
					    .. 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>
 | 
					PAPERLESS_OCR_USER_ARGS=<json>
 | 
				
			||||||
    OCRmyPDF offers many more options. Use this parameter to specify any
 | 
					    OCRmyPDF offers many more options. Use this parameter to specify any
 | 
				
			||||||
 | 
				
			|||||||
@ -1340,7 +1340,7 @@
 | 
				
			|||||||
        </context-group>
 | 
					        </context-group>
 | 
				
			||||||
        <context-group purpose="location">
 | 
					        <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="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>
 | 
					        </context-group>
 | 
				
			||||||
      </trans-unit>
 | 
					      </trans-unit>
 | 
				
			||||||
      <trans-unit id="8659635229098859487" datatype="html">
 | 
					      <trans-unit id="8659635229098859487" datatype="html">
 | 
				
			||||||
@ -1884,7 +1884,7 @@
 | 
				
			|||||||
        </context-group>
 | 
					        </context-group>
 | 
				
			||||||
        <context-group purpose="location">
 | 
					        <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="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>
 | 
				
			||||||
        <context-group purpose="location">
 | 
					        <context-group purpose="location">
 | 
				
			||||||
          <context context-type="sourcefile">src/app/components/document-list/document-list.component.html</context>
 | 
					          <context context-type="sourcefile">src/app/components/document-list/document-list.component.html</context>
 | 
				
			||||||
@ -1899,7 +1899,7 @@
 | 
				
			|||||||
        </context-group>
 | 
					        </context-group>
 | 
				
			||||||
        <context-group purpose="location">
 | 
					        <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="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>
 | 
				
			||||||
        <context-group purpose="location">
 | 
					        <context-group purpose="location">
 | 
				
			||||||
          <context context-type="sourcefile">src/app/components/document-list/document-list.component.html</context>
 | 
					          <context context-type="sourcefile">src/app/components/document-list/document-list.component.html</context>
 | 
				
			||||||
@ -1914,7 +1914,7 @@
 | 
				
			|||||||
        </context-group>
 | 
					        </context-group>
 | 
				
			||||||
        <context-group purpose="location">
 | 
					        <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="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>
 | 
				
			||||||
        <context-group purpose="location">
 | 
					        <context-group purpose="location">
 | 
				
			||||||
          <context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context>
 | 
					          <context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context>
 | 
				
			||||||
@ -1964,7 +1964,7 @@
 | 
				
			|||||||
        </context-group>
 | 
					        </context-group>
 | 
				
			||||||
        <context-group purpose="location">
 | 
					        <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="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>
 | 
				
			||||||
        <context-group purpose="location">
 | 
					        <context-group purpose="location">
 | 
				
			||||||
          <context context-type="sourcefile">src/app/components/document-list/document-list.component.html</context>
 | 
					          <context context-type="sourcefile">src/app/components/document-list/document-list.component.html</context>
 | 
				
			||||||
@ -1979,7 +1979,7 @@
 | 
				
			|||||||
        </context-group>
 | 
					        </context-group>
 | 
				
			||||||
        <context-group purpose="location">
 | 
					        <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="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>
 | 
				
			||||||
        <context-group purpose="location">
 | 
					        <context-group purpose="location">
 | 
				
			||||||
          <context context-type="sourcefile">src/app/components/document-list/document-list.component.html</context>
 | 
					          <context context-type="sourcefile">src/app/components/document-list/document-list.component.html</context>
 | 
				
			||||||
@ -1994,7 +1994,7 @@
 | 
				
			|||||||
        </context-group>
 | 
					        </context-group>
 | 
				
			||||||
        <context-group purpose="location">
 | 
					        <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="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>
 | 
					        </context-group>
 | 
				
			||||||
      </trans-unit>
 | 
					      </trans-unit>
 | 
				
			||||||
      <trans-unit id="2030261243264601523" datatype="html">
 | 
					      <trans-unit id="2030261243264601523" datatype="html">
 | 
				
			||||||
@ -2005,7 +2005,7 @@
 | 
				
			|||||||
        </context-group>
 | 
					        </context-group>
 | 
				
			||||||
        <context-group purpose="location">
 | 
					        <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="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>
 | 
					        </context-group>
 | 
				
			||||||
      </trans-unit>
 | 
					      </trans-unit>
 | 
				
			||||||
      <trans-unit id="4235671847487610290" datatype="html">
 | 
					      <trans-unit id="4235671847487610290" datatype="html">
 | 
				
			||||||
@ -2016,7 +2016,7 @@
 | 
				
			|||||||
        </context-group>
 | 
					        </context-group>
 | 
				
			||||||
        <context-group purpose="location">
 | 
					        <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="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>
 | 
					        </context-group>
 | 
				
			||||||
      </trans-unit>
 | 
					      </trans-unit>
 | 
				
			||||||
      <trans-unit id="2332107018974972998" datatype="html">
 | 
					      <trans-unit id="2332107018974972998" datatype="html">
 | 
				
			||||||
@ -2764,35 +2764,56 @@
 | 
				
			|||||||
        <source>Saved view "<x id="PH" equiv-text="savedView.name"/>" deleted.</source>
 | 
					        <source>Saved view "<x id="PH" equiv-text="savedView.name"/>" deleted.</source>
 | 
				
			||||||
        <context-group purpose="location">
 | 
					        <context-group purpose="location">
 | 
				
			||||||
          <context context-type="sourcefile">src/app/components/manage/settings/settings.component.ts</context>
 | 
					          <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>
 | 
					        </context-group>
 | 
				
			||||||
      </trans-unit>
 | 
					      </trans-unit>
 | 
				
			||||||
      <trans-unit id="5647210819299459618" datatype="html">
 | 
					      <trans-unit id="3891152409365583719" datatype="html">
 | 
				
			||||||
        <source>Settings saved successfully.</source>
 | 
					        <source>Settings saved</source>
 | 
				
			||||||
        <context-group purpose="location">
 | 
					        <context-group purpose="location">
 | 
				
			||||||
          <context context-type="sourcefile">src/app/components/manage/settings/settings.component.ts</context>
 | 
					          <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>
 | 
					        </context-group>
 | 
				
			||||||
      </trans-unit>
 | 
					      </trans-unit>
 | 
				
			||||||
      <trans-unit id="3011185103048412841" datatype="html">
 | 
					      <trans-unit id="3011185103048412841" datatype="html">
 | 
				
			||||||
        <source>An error occurred while saving settings.</source>
 | 
					        <source>An error occurred while saving settings.</source>
 | 
				
			||||||
        <context-group purpose="location">
 | 
					        <context-group purpose="location">
 | 
				
			||||||
          <context context-type="sourcefile">src/app/components/manage/settings/settings.component.ts</context>
 | 
					          <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>
 | 
					        </context-group>
 | 
				
			||||||
      </trans-unit>
 | 
					      </trans-unit>
 | 
				
			||||||
      <trans-unit id="6839066544204061364" datatype="html">
 | 
					      <trans-unit id="6839066544204061364" datatype="html">
 | 
				
			||||||
        <source>Use system language</source>
 | 
					        <source>Use system language</source>
 | 
				
			||||||
        <context-group purpose="location">
 | 
					        <context-group purpose="location">
 | 
				
			||||||
          <context context-type="sourcefile">src/app/components/manage/settings/settings.component.ts</context>
 | 
					          <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>
 | 
					        </context-group>
 | 
				
			||||||
      </trans-unit>
 | 
					      </trans-unit>
 | 
				
			||||||
      <trans-unit id="7729897675462249787" datatype="html">
 | 
					      <trans-unit id="7729897675462249787" datatype="html">
 | 
				
			||||||
        <source>Use date format of display language</source>
 | 
					        <source>Use date format of display language</source>
 | 
				
			||||||
        <context-group purpose="location">
 | 
					        <context-group purpose="location">
 | 
				
			||||||
          <context context-type="sourcefile">src/app/components/manage/settings/settings.component.ts</context>
 | 
					          <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>
 | 
					        </context-group>
 | 
				
			||||||
      </trans-unit>
 | 
					      </trans-unit>
 | 
				
			||||||
      <trans-unit id="8488620293789898901" datatype="html">
 | 
					      <trans-unit id="8488620293789898901" datatype="html">
 | 
				
			||||||
@ -2801,7 +2822,7 @@
 | 
				
			|||||||
            )"/></source>
 | 
					            )"/></source>
 | 
				
			||||||
        <context-group purpose="location">
 | 
					        <context-group purpose="location">
 | 
				
			||||||
          <context context-type="sourcefile">src/app/components/manage/settings/settings.component.ts</context>
 | 
					          <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>
 | 
					        </context-group>
 | 
				
			||||||
      </trans-unit>
 | 
					      </trans-unit>
 | 
				
			||||||
      <trans-unit id="5101757640976222639" datatype="html">
 | 
					      <trans-unit id="5101757640976222639" datatype="html">
 | 
				
			||||||
 | 
				
			|||||||
@ -22,7 +22,6 @@ import {
 | 
				
			|||||||
  RemoteVersionService,
 | 
					  RemoteVersionService,
 | 
				
			||||||
  AppRemoteVersion,
 | 
					  AppRemoteVersion,
 | 
				
			||||||
} from 'src/app/services/rest/remote-version.service'
 | 
					} 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'
 | 
					import { SettingsService } from 'src/app/services/settings.service'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@Component({
 | 
					@Component({
 | 
				
			||||||
@ -38,7 +37,7 @@ export class AppFrameComponent {
 | 
				
			|||||||
    private searchService: SearchService,
 | 
					    private searchService: SearchService,
 | 
				
			||||||
    public savedViewService: SavedViewService,
 | 
					    public savedViewService: SavedViewService,
 | 
				
			||||||
    private remoteVersionService: RemoteVersionService,
 | 
					    private remoteVersionService: RemoteVersionService,
 | 
				
			||||||
    private queryParamsService: QueryParamsService,
 | 
					    private list: DocumentListViewService,
 | 
				
			||||||
    public settingsService: SettingsService
 | 
					    public settingsService: SettingsService
 | 
				
			||||||
  ) {
 | 
					  ) {
 | 
				
			||||||
    this.remoteVersionService
 | 
					    this.remoteVersionService
 | 
				
			||||||
@ -94,7 +93,7 @@ export class AppFrameComponent {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  search() {
 | 
					  search() {
 | 
				
			||||||
    this.closeMenu()
 | 
					    this.closeMenu()
 | 
				
			||||||
    this.queryParamsService.navigateWithFilterRules([
 | 
					    this.list.quickFilter([
 | 
				
			||||||
      {
 | 
					      {
 | 
				
			||||||
        rule_type: FILTER_FULLTEXT_QUERY,
 | 
					        rule_type: FILTER_FULLTEXT_QUERY,
 | 
				
			||||||
        value: (this.searchField.value as string).trim(),
 | 
					        value: (this.searchField.value as string).trim(),
 | 
				
			||||||
 | 
				
			|||||||
@ -2,7 +2,7 @@
 | 
				
			|||||||
  <label class="form-label" [for]="inputId">{{title}}</label>
 | 
					  <label class="form-label" [for]="inputId">{{title}}</label>
 | 
				
			||||||
  <div class="input-group" [class.is-invalid]="error">
 | 
					  <div class="input-group" [class.is-invalid]="error">
 | 
				
			||||||
    <input class="form-control" [class.is-invalid]="error" [placeholder]="placeholder" [id]="inputId" maxlength="10"
 | 
					    <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">
 | 
					          name="dp" [(ngModel)]="value" ngbDatepicker #datePicker="ngbDatepicker" #datePickerContent="ngModel">
 | 
				
			||||||
    <button class="btn btn-outline-secondary calendar" (click)="datePicker.toggle()" type="button">
 | 
					    <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">
 | 
					      <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 { Component, forwardRef, OnInit } from '@angular/core'
 | 
				
			||||||
import { NG_VALUE_ACCESSOR } from '@angular/forms'
 | 
					import { NG_VALUE_ACCESSOR } from '@angular/forms'
 | 
				
			||||||
 | 
					import { NgbDateParserFormatter } from '@ng-bootstrap/ng-bootstrap'
 | 
				
			||||||
import { SettingsService } from 'src/app/services/settings.service'
 | 
					import { SettingsService } from 'src/app/services/settings.service'
 | 
				
			||||||
 | 
					import { LocalizedDateParserFormatter } from 'src/app/utils/ngb-date-parser-formatter'
 | 
				
			||||||
import { AbstractInputComponent } from '../abstract-input'
 | 
					import { AbstractInputComponent } from '../abstract-input'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@Component({
 | 
					@Component({
 | 
				
			||||||
@ -19,7 +21,10 @@ export class DateComponent
 | 
				
			|||||||
  extends AbstractInputComponent<string>
 | 
					  extends AbstractInputComponent<string>
 | 
				
			||||||
  implements OnInit
 | 
					  implements OnInit
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
  constructor(private settings: SettingsService) {
 | 
					  constructor(
 | 
				
			||||||
 | 
					    private settings: SettingsService,
 | 
				
			||||||
 | 
					    private ngbDateParserFormatter: NgbDateParserFormatter
 | 
				
			||||||
 | 
					  ) {
 | 
				
			||||||
    super()
 | 
					    super()
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -30,7 +35,20 @@ export class DateComponent
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  placeholder: string
 | 
					  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) {
 | 
					  onKeyPress(event: KeyboardEvent) {
 | 
				
			||||||
    if ('Enter' !== event.key && !/[0-9,\.\/-]+/.test(event.key)) {
 | 
					    if ('Enter' !== event.key && !/[0-9,\.\/-]+/.test(event.key)) {
 | 
				
			||||||
      event.preventDefault()
 | 
					      event.preventDefault()
 | 
				
			||||||
 | 
				
			|||||||
@ -4,5 +4,5 @@
 | 
				
			|||||||
  [class]="toast.classname"
 | 
					  [class]="toast.classname"
 | 
				
			||||||
  (hidden)="toastService.closeToast(toast)">
 | 
					  (hidden)="toastService.closeToast(toast)">
 | 
				
			||||||
  <p>{{toast.content}}</p>
 | 
					  <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>
 | 
					</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 { DocumentService } from 'src/app/services/rest/document.service'
 | 
				
			||||||
import { PaperlessTag } from 'src/app/data/paperless-tag'
 | 
					import { PaperlessTag } from 'src/app/data/paperless-tag'
 | 
				
			||||||
import { FILTER_HAS_TAGS_ALL } from 'src/app/data/filter-rule-type'
 | 
					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 { OpenDocumentsService } from 'src/app/services/open-documents.service'
 | 
				
			||||||
 | 
					import { DocumentListViewService } from 'src/app/services/document-list-view.service'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@Component({
 | 
					@Component({
 | 
				
			||||||
  selector: 'app-saved-view-widget',
 | 
					  selector: 'app-saved-view-widget',
 | 
				
			||||||
@ -21,7 +21,7 @@ export class SavedViewWidgetComponent implements OnInit, OnDestroy {
 | 
				
			|||||||
  constructor(
 | 
					  constructor(
 | 
				
			||||||
    private documentService: DocumentService,
 | 
					    private documentService: DocumentService,
 | 
				
			||||||
    private router: Router,
 | 
					    private router: Router,
 | 
				
			||||||
    private queryParamsService: QueryParamsService,
 | 
					    private list: DocumentListViewService,
 | 
				
			||||||
    private consumerStatusService: ConsumerStatusService,
 | 
					    private consumerStatusService: ConsumerStatusService,
 | 
				
			||||||
    public openDocumentsService: OpenDocumentsService
 | 
					    public openDocumentsService: OpenDocumentsService
 | 
				
			||||||
  ) {}
 | 
					  ) {}
 | 
				
			||||||
@ -73,7 +73,7 @@ export class SavedViewWidgetComponent implements OnInit, OnDestroy {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  clickTag(tag: PaperlessTag) {
 | 
					  clickTag(tag: PaperlessTag) {
 | 
				
			||||||
    this.queryParamsService.navigateWithFilterRules([
 | 
					    this.list.quickFilter([
 | 
				
			||||||
      { rule_type: FILTER_HAS_TAGS_ALL, value: tag.id.toString() },
 | 
					      { 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 { PaperlessDocumentSuggestions } from 'src/app/data/paperless-document-suggestions'
 | 
				
			||||||
import { FILTER_FULLTEXT_MORELIKE } from 'src/app/data/filter-rule-type'
 | 
					import { FILTER_FULLTEXT_MORELIKE } from 'src/app/data/filter-rule-type'
 | 
				
			||||||
import { normalizeDateStr } from 'src/app/utils/date'
 | 
					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 { StoragePathService } from 'src/app/services/rest/storage-path.service'
 | 
				
			||||||
import { PaperlessStoragePath } from 'src/app/data/paperless-storage-path'
 | 
					import { PaperlessStoragePath } from 'src/app/data/paperless-storage-path'
 | 
				
			||||||
import { StoragePathEditDialogComponent } from '../common/edit-dialog/storage-path-edit-dialog/storage-path-edit-dialog.component'
 | 
					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 documentTitlePipe: DocumentTitlePipe,
 | 
				
			||||||
    private toastService: ToastService,
 | 
					    private toastService: ToastService,
 | 
				
			||||||
    private settings: SettingsService,
 | 
					    private settings: SettingsService,
 | 
				
			||||||
    private storagePathService: StoragePathService,
 | 
					    private storagePathService: StoragePathService
 | 
				
			||||||
    private queryParamsService: QueryParamsService
 | 
					 | 
				
			||||||
  ) {}
 | 
					  ) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  titleKeyUp(event) {
 | 
					  titleKeyUp(event) {
 | 
				
			||||||
@ -494,7 +492,7 @@ export class DocumentDetailComponent
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  moreLike() {
 | 
					  moreLike() {
 | 
				
			||||||
    this.queryParamsService.navigateWithFilterRules([
 | 
					    this.documentListViewService.quickFilter([
 | 
				
			||||||
      {
 | 
					      {
 | 
				
			||||||
        rule_type: FILTER_FULLTEXT_MORELIKE,
 | 
					        rule_type: FILTER_FULLTEXT_MORELIKE,
 | 
				
			||||||
        value: this.documentId.toString(),
 | 
					        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>
 | 
					        <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>
 | 
					      </ng-container>
 | 
				
			||||||
    </p>
 | 
					    </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>
 | 
					    [rotate]="true" aria-label="Default pagination"></ngb-pagination>
 | 
				
			||||||
  </div>
 | 
					  </div>
 | 
				
			||||||
</ng-template>
 | 
					</ng-template>
 | 
				
			||||||
 | 
				
			|||||||
@ -1,5 +1,4 @@
 | 
				
			|||||||
import {
 | 
					import {
 | 
				
			||||||
  AfterViewInit,
 | 
					 | 
				
			||||||
  Component,
 | 
					  Component,
 | 
				
			||||||
  OnDestroy,
 | 
					  OnDestroy,
 | 
				
			||||||
  OnInit,
 | 
					  OnInit,
 | 
				
			||||||
@ -21,7 +20,6 @@ import {
 | 
				
			|||||||
import { ConsumerStatusService } from 'src/app/services/consumer-status.service'
 | 
					import { ConsumerStatusService } from 'src/app/services/consumer-status.service'
 | 
				
			||||||
import { DocumentListViewService } from 'src/app/services/document-list-view.service'
 | 
					import { DocumentListViewService } from 'src/app/services/document-list-view.service'
 | 
				
			||||||
import { OpenDocumentsService } from 'src/app/services/open-documents.service'
 | 
					import { OpenDocumentsService } from 'src/app/services/open-documents.service'
 | 
				
			||||||
import { QueryParamsService } from 'src/app/services/query-params.service'
 | 
					 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
  DOCUMENT_SORT_FIELDS,
 | 
					  DOCUMENT_SORT_FIELDS,
 | 
				
			||||||
  DOCUMENT_SORT_FIELDS_FULLTEXT,
 | 
					  DOCUMENT_SORT_FIELDS_FULLTEXT,
 | 
				
			||||||
@ -36,7 +34,7 @@ import { SaveViewConfigDialogComponent } from './save-view-config-dialog/save-vi
 | 
				
			|||||||
  templateUrl: './document-list.component.html',
 | 
					  templateUrl: './document-list.component.html',
 | 
				
			||||||
  styleUrls: ['./document-list.component.scss'],
 | 
					  styleUrls: ['./document-list.component.scss'],
 | 
				
			||||||
})
 | 
					})
 | 
				
			||||||
export class DocumentListComponent implements OnInit, OnDestroy, AfterViewInit {
 | 
					export class DocumentListComponent implements OnInit, OnDestroy {
 | 
				
			||||||
  constructor(
 | 
					  constructor(
 | 
				
			||||||
    public list: DocumentListViewService,
 | 
					    public list: DocumentListViewService,
 | 
				
			||||||
    public savedViewService: SavedViewService,
 | 
					    public savedViewService: SavedViewService,
 | 
				
			||||||
@ -45,7 +43,6 @@ export class DocumentListComponent implements OnInit, OnDestroy, AfterViewInit {
 | 
				
			|||||||
    private toastService: ToastService,
 | 
					    private toastService: ToastService,
 | 
				
			||||||
    private modalService: NgbModal,
 | 
					    private modalService: NgbModal,
 | 
				
			||||||
    private consumerStatusService: ConsumerStatusService,
 | 
					    private consumerStatusService: ConsumerStatusService,
 | 
				
			||||||
    private queryParamsService: QueryParamsService,
 | 
					 | 
				
			||||||
    public openDocumentsService: OpenDocumentsService
 | 
					    public openDocumentsService: OpenDocumentsService
 | 
				
			||||||
  ) {}
 | 
					  ) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -76,8 +73,6 @@ export class DocumentListComponent implements OnInit, OnDestroy, AfterViewInit {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  set listSort(reverse: boolean) {
 | 
					  set listSort(reverse: boolean) {
 | 
				
			||||||
    this.list.sortReverse = reverse
 | 
					    this.list.sortReverse = reverse
 | 
				
			||||||
    this.queryParamsService.sortField = this.list.sortField
 | 
					 | 
				
			||||||
    this.queryParamsService.sortReverse = reverse
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  get listSort(): boolean {
 | 
					  get listSort(): boolean {
 | 
				
			||||||
@ -86,14 +81,14 @@ export class DocumentListComponent implements OnInit, OnDestroy, AfterViewInit {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  setSortField(field: string) {
 | 
					  setSortField(field: string) {
 | 
				
			||||||
    this.list.sortField = field
 | 
					    this.list.sortField = field
 | 
				
			||||||
    this.queryParamsService.sortField = field
 | 
					 | 
				
			||||||
    this.queryParamsService.sortReverse = this.listSort
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  onSort(event: SortEvent) {
 | 
					  onSort(event: SortEvent) {
 | 
				
			||||||
    this.list.setSort(event.column, event.reverse)
 | 
					    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 {
 | 
					  get isBulkEditing(): boolean {
 | 
				
			||||||
@ -133,7 +128,6 @@ export class DocumentListComponent implements OnInit, OnDestroy, AfterViewInit {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
        this.list.activateSavedView(view)
 | 
					        this.list.activateSavedView(view)
 | 
				
			||||||
        this.list.reload()
 | 
					        this.list.reload()
 | 
				
			||||||
        this.queryParamsService.updateFromView(view)
 | 
					 | 
				
			||||||
        this.unmodifiedFilterRules = view.filter_rules
 | 
					        this.unmodifiedFilterRules = view.filter_rules
 | 
				
			||||||
      })
 | 
					      })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -148,22 +142,12 @@ export class DocumentListComponent implements OnInit, OnDestroy, AfterViewInit {
 | 
				
			|||||||
          this.loadViewConfig(parseInt(queryParams.get('view')))
 | 
					          this.loadViewConfig(parseInt(queryParams.get('view')))
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
          this.list.activateSavedView(null)
 | 
					          this.list.activateSavedView(null)
 | 
				
			||||||
          this.queryParamsService.parseQueryParams(queryParams)
 | 
					          this.list.loadFromQueryParams(queryParams)
 | 
				
			||||||
          this.unmodifiedFilterRules = []
 | 
					          this.unmodifiedFilterRules = []
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      })
 | 
					      })
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  ngAfterViewInit(): void {
 | 
					 | 
				
			||||||
    this.filterEditor.filterRulesChange
 | 
					 | 
				
			||||||
      .pipe(takeUntil(this.unsubscribeNotifier))
 | 
					 | 
				
			||||||
      .subscribe({
 | 
					 | 
				
			||||||
        next: (filterRules) => {
 | 
					 | 
				
			||||||
          this.queryParamsService.updateFilterRules(filterRules)
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
      })
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  ngOnDestroy() {
 | 
					  ngOnDestroy() {
 | 
				
			||||||
    // unsubscribes all
 | 
					    // unsubscribes all
 | 
				
			||||||
    this.unsubscribeNotifier.next(this)
 | 
					    this.unsubscribeNotifier.next(this)
 | 
				
			||||||
@ -175,9 +159,8 @@ export class DocumentListComponent implements OnInit, OnDestroy, AfterViewInit {
 | 
				
			|||||||
      .getCached(viewId)
 | 
					      .getCached(viewId)
 | 
				
			||||||
      .pipe(first())
 | 
					      .pipe(first())
 | 
				
			||||||
      .subscribe((view) => {
 | 
					      .subscribe((view) => {
 | 
				
			||||||
        this.list.loadSavedView(view)
 | 
					        this.list.activateSavedView(view)
 | 
				
			||||||
        this.list.reload()
 | 
					        this.list.reload()
 | 
				
			||||||
        this.queryParamsService.updateFromView(view)
 | 
					 | 
				
			||||||
      })
 | 
					      })
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -246,34 +229,26 @@ export class DocumentListComponent implements OnInit, OnDestroy, AfterViewInit {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  clickTag(tagID: number) {
 | 
					  clickTag(tagID: number) {
 | 
				
			||||||
    this.list.selectNone()
 | 
					    this.list.selectNone()
 | 
				
			||||||
    setTimeout(() => {
 | 
					    this.filterEditor.addTag(tagID)
 | 
				
			||||||
      this.filterEditor.addTag(tagID)
 | 
					 | 
				
			||||||
    })
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  clickCorrespondent(correspondentID: number) {
 | 
					  clickCorrespondent(correspondentID: number) {
 | 
				
			||||||
    this.list.selectNone()
 | 
					    this.list.selectNone()
 | 
				
			||||||
    setTimeout(() => {
 | 
					    this.filterEditor.addCorrespondent(correspondentID)
 | 
				
			||||||
      this.filterEditor.addCorrespondent(correspondentID)
 | 
					 | 
				
			||||||
    })
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  clickDocumentType(documentTypeID: number) {
 | 
					  clickDocumentType(documentTypeID: number) {
 | 
				
			||||||
    this.list.selectNone()
 | 
					    this.list.selectNone()
 | 
				
			||||||
    setTimeout(() => {
 | 
					    this.filterEditor.addDocumentType(documentTypeID)
 | 
				
			||||||
      this.filterEditor.addDocumentType(documentTypeID)
 | 
					 | 
				
			||||||
    })
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  clickStoragePath(storagePathID: number) {
 | 
					  clickStoragePath(storagePathID: number) {
 | 
				
			||||||
    this.list.selectNone()
 | 
					    this.list.selectNone()
 | 
				
			||||||
    setTimeout(() => {
 | 
					    this.filterEditor.addStoragePath(storagePathID)
 | 
				
			||||||
      this.filterEditor.addStoragePath(storagePathID)
 | 
					 | 
				
			||||||
    })
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  clickMoreLike(documentID: number) {
 | 
					  clickMoreLike(documentID: number) {
 | 
				
			||||||
    this.queryParamsService.navigateWithFilterRules([
 | 
					    this.list.quickFilter([
 | 
				
			||||||
      { rule_type: FILTER_FULLTEXT_MORELIKE, value: documentID.toString() },
 | 
					      { rule_type: FILTER_FULLTEXT_MORELIKE, value: documentID.toString() },
 | 
				
			||||||
    ])
 | 
					    ])
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
				
			|||||||
@ -313,7 +313,10 @@ export class FilterEditorComponent implements OnInit, OnDestroy {
 | 
				
			|||||||
          break
 | 
					          break
 | 
				
			||||||
        case FILTER_ASN_ISNULL:
 | 
					        case FILTER_ASN_ISNULL:
 | 
				
			||||||
          this.textFilterTarget = TEXT_FILTER_TARGET_ASN
 | 
					          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
 | 
					          break
 | 
				
			||||||
        case FILTER_ASN_GT:
 | 
					        case FILTER_ASN_GT:
 | 
				
			||||||
          this.textFilterTarget = TEXT_FILTER_TARGET_ASN
 | 
					          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 { FILTER_CORRESPONDENT } from 'src/app/data/filter-rule-type'
 | 
				
			||||||
import { PaperlessCorrespondent } from 'src/app/data/paperless-correspondent'
 | 
					import { PaperlessCorrespondent } from 'src/app/data/paperless-correspondent'
 | 
				
			||||||
import { CustomDatePipe } from 'src/app/pipes/custom-date.pipe'
 | 
					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 { CorrespondentService } from 'src/app/services/rest/correspondent.service'
 | 
				
			||||||
import { ToastService } from 'src/app/services/toast.service'
 | 
					import { ToastService } from 'src/app/services/toast.service'
 | 
				
			||||||
import { CorrespondentEditDialogComponent } from '../../common/edit-dialog/correspondent-edit-dialog/correspondent-edit-dialog.component'
 | 
					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,
 | 
					    correspondentsService: CorrespondentService,
 | 
				
			||||||
    modalService: NgbModal,
 | 
					    modalService: NgbModal,
 | 
				
			||||||
    toastService: ToastService,
 | 
					    toastService: ToastService,
 | 
				
			||||||
    queryParamsService: QueryParamsService,
 | 
					    documentListViewService: DocumentListViewService,
 | 
				
			||||||
    private datePipe: CustomDatePipe
 | 
					    private datePipe: CustomDatePipe
 | 
				
			||||||
  ) {
 | 
					  ) {
 | 
				
			||||||
    super(
 | 
					    super(
 | 
				
			||||||
@ -28,7 +28,7 @@ export class CorrespondentListComponent extends ManagementListComponent<Paperles
 | 
				
			|||||||
      modalService,
 | 
					      modalService,
 | 
				
			||||||
      CorrespondentEditDialogComponent,
 | 
					      CorrespondentEditDialogComponent,
 | 
				
			||||||
      toastService,
 | 
					      toastService,
 | 
				
			||||||
      queryParamsService,
 | 
					      documentListViewService,
 | 
				
			||||||
      FILTER_CORRESPONDENT,
 | 
					      FILTER_CORRESPONDENT,
 | 
				
			||||||
      $localize`correspondent`,
 | 
					      $localize`correspondent`,
 | 
				
			||||||
      $localize`correspondents`,
 | 
					      $localize`correspondents`,
 | 
				
			||||||
 | 
				
			|||||||
@ -2,7 +2,7 @@ import { Component } from '@angular/core'
 | 
				
			|||||||
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
 | 
					import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
 | 
				
			||||||
import { FILTER_DOCUMENT_TYPE } from 'src/app/data/filter-rule-type'
 | 
					import { FILTER_DOCUMENT_TYPE } from 'src/app/data/filter-rule-type'
 | 
				
			||||||
import { PaperlessDocumentType } from 'src/app/data/paperless-document-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 { DocumentTypeService } from 'src/app/services/rest/document-type.service'
 | 
				
			||||||
import { ToastService } from 'src/app/services/toast.service'
 | 
					import { ToastService } from 'src/app/services/toast.service'
 | 
				
			||||||
import { DocumentTypeEditDialogComponent } from '../../common/edit-dialog/document-type-edit-dialog/document-type-edit-dialog.component'
 | 
					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,
 | 
					    documentTypeService: DocumentTypeService,
 | 
				
			||||||
    modalService: NgbModal,
 | 
					    modalService: NgbModal,
 | 
				
			||||||
    toastService: ToastService,
 | 
					    toastService: ToastService,
 | 
				
			||||||
    queryParamsService: QueryParamsService
 | 
					    documentListViewService: DocumentListViewService
 | 
				
			||||||
  ) {
 | 
					  ) {
 | 
				
			||||||
    super(
 | 
					    super(
 | 
				
			||||||
      documentTypeService,
 | 
					      documentTypeService,
 | 
				
			||||||
      modalService,
 | 
					      modalService,
 | 
				
			||||||
      DocumentTypeEditDialogComponent,
 | 
					      DocumentTypeEditDialogComponent,
 | 
				
			||||||
      toastService,
 | 
					      toastService,
 | 
				
			||||||
      queryParamsService,
 | 
					      documentListViewService,
 | 
				
			||||||
      FILTER_DOCUMENT_TYPE,
 | 
					      FILTER_DOCUMENT_TYPE,
 | 
				
			||||||
      $localize`document type`,
 | 
					      $localize`document type`,
 | 
				
			||||||
      $localize`document types`,
 | 
					      $localize`document types`,
 | 
				
			||||||
 | 
				
			|||||||
@ -18,7 +18,7 @@ import {
 | 
				
			|||||||
  SortableDirective,
 | 
					  SortableDirective,
 | 
				
			||||||
  SortEvent,
 | 
					  SortEvent,
 | 
				
			||||||
} from 'src/app/directives/sortable.directive'
 | 
					} 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 { AbstractNameFilterService } from 'src/app/services/rest/abstract-name-filter-service'
 | 
				
			||||||
import { ToastService } from 'src/app/services/toast.service'
 | 
					import { ToastService } from 'src/app/services/toast.service'
 | 
				
			||||||
import { ConfirmDialogComponent } from '../../common/confirm-dialog/confirm-dialog.component'
 | 
					import { ConfirmDialogComponent } from '../../common/confirm-dialog/confirm-dialog.component'
 | 
				
			||||||
@ -42,7 +42,7 @@ export abstract class ManagementListComponent<T extends ObjectWithId>
 | 
				
			|||||||
    private modalService: NgbModal,
 | 
					    private modalService: NgbModal,
 | 
				
			||||||
    private editDialogComponent: any,
 | 
					    private editDialogComponent: any,
 | 
				
			||||||
    private toastService: ToastService,
 | 
					    private toastService: ToastService,
 | 
				
			||||||
    private queryParamsService: QueryParamsService,
 | 
					    private documentListViewService: DocumentListViewService,
 | 
				
			||||||
    protected filterRuleType: number,
 | 
					    protected filterRuleType: number,
 | 
				
			||||||
    public typeName: string,
 | 
					    public typeName: string,
 | 
				
			||||||
    public typeNamePlural: string,
 | 
					    public typeNamePlural: string,
 | 
				
			||||||
@ -141,7 +141,7 @@ export abstract class ManagementListComponent<T extends ObjectWithId>
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  filterDocuments(object: ObjectWithId) {
 | 
					  filterDocuments(object: ObjectWithId) {
 | 
				
			||||||
    this.queryParamsService.navigateWithFilterRules([
 | 
					    this.documentListViewService.quickFilter([
 | 
				
			||||||
      { rule_type: this.filterRuleType, value: object.id.toString() },
 | 
					      { 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>
 | 
					              <option *ngFor="let lang of displayLanguageOptions" [ngValue]="lang.code">{{lang.name}}<span *ngIf="lang.code && currentLocale != 'en-US'"> - {{lang.englishName}}</span></option>
 | 
				
			||||||
            </select>
 | 
					            </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>
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
 | 
				
			|||||||
@ -14,7 +14,7 @@ import {
 | 
				
			|||||||
  LanguageOption,
 | 
					  LanguageOption,
 | 
				
			||||||
  SettingsService,
 | 
					  SettingsService,
 | 
				
			||||||
} from 'src/app/services/settings.service'
 | 
					} 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 { dirtyCheck, DirtyComponent } from '@ngneat/dirty-check-forms'
 | 
				
			||||||
import { Observable, Subscription, BehaviorSubject, first } from 'rxjs'
 | 
					import { Observable, Subscription, BehaviorSubject, first } from 'rxjs'
 | 
				
			||||||
import { SETTINGS_KEYS } from 'src/app/data/paperless-uisettings'
 | 
					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(
 | 
					  constructor(
 | 
				
			||||||
    public savedViewService: SavedViewService,
 | 
					    public savedViewService: SavedViewService,
 | 
				
			||||||
    private documentListViewService: DocumentListViewService,
 | 
					    private documentListViewService: DocumentListViewService,
 | 
				
			||||||
@ -170,6 +177,7 @@ export class SettingsComponent implements OnInit, OnDestroy, DirtyComponent {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private saveLocalSettings() {
 | 
					  private saveLocalSettings() {
 | 
				
			||||||
 | 
					    const reloadRequired = this.displayLanguageIsDirty // just this one, for now
 | 
				
			||||||
    this.settings.set(
 | 
					    this.settings.set(
 | 
				
			||||||
      SETTINGS_KEYS.BULK_EDIT_APPLY_ON_CLOSE,
 | 
					      SETTINGS_KEYS.BULK_EDIT_APPLY_ON_CLOSE,
 | 
				
			||||||
      this.settingsForm.value.bulkEditApplyOnClose
 | 
					      this.settingsForm.value.bulkEditApplyOnClose
 | 
				
			||||||
@ -235,7 +243,20 @@ export class SettingsComponent implements OnInit, OnDestroy, DirtyComponent {
 | 
				
			|||||||
          this.store.next(this.settingsForm.value)
 | 
					          this.store.next(this.settingsForm.value)
 | 
				
			||||||
          this.documentListViewService.updatePageSize()
 | 
					          this.documentListViewService.updatePageSize()
 | 
				
			||||||
          this.settings.updateAppearanceSettings()
 | 
					          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) => {
 | 
					        error: (error) => {
 | 
				
			||||||
          this.toastService.showError(
 | 
					          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 { FILTER_STORAGE_PATH } from 'src/app/data/filter-rule-type'
 | 
				
			||||||
import { PaperlessStoragePath } from 'src/app/data/paperless-storage-path'
 | 
					import { PaperlessStoragePath } from 'src/app/data/paperless-storage-path'
 | 
				
			||||||
import { DocumentListViewService } from 'src/app/services/document-list-view.service'
 | 
					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 { StoragePathService } from 'src/app/services/rest/storage-path.service'
 | 
				
			||||||
import { ToastService } from 'src/app/services/toast.service'
 | 
					import { ToastService } from 'src/app/services/toast.service'
 | 
				
			||||||
import { StoragePathEditDialogComponent } from '../../common/edit-dialog/storage-path-edit-dialog/storage-path-edit-dialog.component'
 | 
					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,
 | 
					    directoryService: StoragePathService,
 | 
				
			||||||
    modalService: NgbModal,
 | 
					    modalService: NgbModal,
 | 
				
			||||||
    toastService: ToastService,
 | 
					    toastService: ToastService,
 | 
				
			||||||
    queryParamsService: QueryParamsService
 | 
					    documentListViewService: DocumentListViewService
 | 
				
			||||||
  ) {
 | 
					  ) {
 | 
				
			||||||
    super(
 | 
					    super(
 | 
				
			||||||
      directoryService,
 | 
					      directoryService,
 | 
				
			||||||
      modalService,
 | 
					      modalService,
 | 
				
			||||||
      StoragePathEditDialogComponent,
 | 
					      StoragePathEditDialogComponent,
 | 
				
			||||||
      toastService,
 | 
					      toastService,
 | 
				
			||||||
      queryParamsService,
 | 
					      documentListViewService,
 | 
				
			||||||
      FILTER_STORAGE_PATH,
 | 
					      FILTER_STORAGE_PATH,
 | 
				
			||||||
      $localize`storage path`,
 | 
					      $localize`storage path`,
 | 
				
			||||||
      $localize`storage paths`,
 | 
					      $localize`storage paths`,
 | 
				
			||||||
 | 
				
			|||||||
@ -2,7 +2,7 @@ import { Component } from '@angular/core'
 | 
				
			|||||||
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
 | 
					import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
 | 
				
			||||||
import { FILTER_HAS_TAGS_ALL } from 'src/app/data/filter-rule-type'
 | 
					import { FILTER_HAS_TAGS_ALL } from 'src/app/data/filter-rule-type'
 | 
				
			||||||
import { PaperlessTag } from 'src/app/data/paperless-tag'
 | 
					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 { TagService } from 'src/app/services/rest/tag.service'
 | 
				
			||||||
import { ToastService } from 'src/app/services/toast.service'
 | 
					import { ToastService } from 'src/app/services/toast.service'
 | 
				
			||||||
import { TagEditDialogComponent } from '../../common/edit-dialog/tag-edit-dialog/tag-edit-dialog.component'
 | 
					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,
 | 
					    tagService: TagService,
 | 
				
			||||||
    modalService: NgbModal,
 | 
					    modalService: NgbModal,
 | 
				
			||||||
    toastService: ToastService,
 | 
					    toastService: ToastService,
 | 
				
			||||||
    queryParamsService: QueryParamsService
 | 
					    documentListViewService: DocumentListViewService
 | 
				
			||||||
  ) {
 | 
					  ) {
 | 
				
			||||||
    super(
 | 
					    super(
 | 
				
			||||||
      tagService,
 | 
					      tagService,
 | 
				
			||||||
      modalService,
 | 
					      modalService,
 | 
				
			||||||
      TagEditDialogComponent,
 | 
					      TagEditDialogComponent,
 | 
				
			||||||
      toastService,
 | 
					      toastService,
 | 
				
			||||||
      queryParamsService,
 | 
					      documentListViewService,
 | 
				
			||||||
      FILTER_HAS_TAGS_ALL,
 | 
					      FILTER_HAS_TAGS_ALL,
 | 
				
			||||||
      $localize`tag`,
 | 
					      $localize`tag`,
 | 
				
			||||||
      $localize`tags`,
 | 
					      $localize`tags`,
 | 
				
			||||||
 | 
				
			|||||||
@ -1,34 +1,38 @@
 | 
				
			|||||||
export const FILTER_TITLE = 0
 | 
					export const FILTER_TITLE = 0
 | 
				
			||||||
export const FILTER_CONTENT = 1
 | 
					export const FILTER_CONTENT = 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const FILTER_ASN = 2
 | 
					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_CORRESPONDENT = 3
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const FILTER_DOCUMENT_TYPE = 4
 | 
					export const FILTER_DOCUMENT_TYPE = 4
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const FILTER_IS_IN_INBOX = 5
 | 
					export const FILTER_IS_IN_INBOX = 5
 | 
				
			||||||
export const FILTER_HAS_TAGS_ALL = 6
 | 
					export const FILTER_HAS_TAGS_ALL = 6
 | 
				
			||||||
export const FILTER_HAS_ANY_TAG = 7
 | 
					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_HAS_TAGS_ANY = 22
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const FILTER_STORAGE_PATH = 25
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const FILTER_CREATED_BEFORE = 8
 | 
					export const FILTER_CREATED_BEFORE = 8
 | 
				
			||||||
export const FILTER_CREATED_AFTER = 9
 | 
					export const FILTER_CREATED_AFTER = 9
 | 
				
			||||||
export const FILTER_CREATED_YEAR = 10
 | 
					export const FILTER_CREATED_YEAR = 10
 | 
				
			||||||
export const FILTER_CREATED_MONTH = 11
 | 
					export const FILTER_CREATED_MONTH = 11
 | 
				
			||||||
export const FILTER_CREATED_DAY = 12
 | 
					export const FILTER_CREATED_DAY = 12
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const FILTER_ADDED_BEFORE = 13
 | 
					export const FILTER_ADDED_BEFORE = 13
 | 
				
			||||||
export const FILTER_ADDED_AFTER = 14
 | 
					export const FILTER_ADDED_AFTER = 14
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const FILTER_MODIFIED_BEFORE = 15
 | 
					export const FILTER_MODIFIED_BEFORE = 15
 | 
				
			||||||
export const FILTER_MODIFIED_AFTER = 16
 | 
					export const FILTER_MODIFIED_AFTER = 16
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const FILTER_DOES_NOT_HAVE_TAG = 17
 | 
					export const FILTER_TITLE_CONTENT = 19
 | 
				
			||||||
 | 
					export const FILTER_FULLTEXT_QUERY = 20
 | 
				
			||||||
export const FILTER_ASN_ISNULL = 18
 | 
					export const FILTER_FULLTEXT_MORELIKE = 21
 | 
				
			||||||
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_RULE_TYPES: FilterRuleType[] = [
 | 
					export const FILTER_RULE_TYPES: FilterRuleType[] = [
 | 
				
			||||||
  {
 | 
					  {
 | 
				
			||||||
 | 
				
			|||||||
@ -1,5 +1,5 @@
 | 
				
			|||||||
import { Injectable } from '@angular/core'
 | 
					import { Injectable } from '@angular/core'
 | 
				
			||||||
import { ActivatedRoute, Params, Router } from '@angular/router'
 | 
					import { ParamMap, Router } from '@angular/router'
 | 
				
			||||||
import { Observable } from 'rxjs'
 | 
					import { Observable } from 'rxjs'
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
  cloneFilterRules,
 | 
					  cloneFilterRules,
 | 
				
			||||||
@ -10,13 +10,14 @@ import { PaperlessDocument } from '../data/paperless-document'
 | 
				
			|||||||
import { PaperlessSavedView } from '../data/paperless-saved-view'
 | 
					import { PaperlessSavedView } from '../data/paperless-saved-view'
 | 
				
			||||||
import { SETTINGS_KEYS } from '../data/paperless-uisettings'
 | 
					import { SETTINGS_KEYS } from '../data/paperless-uisettings'
 | 
				
			||||||
import { DOCUMENT_LIST_SERVICE } from '../data/storage-keys'
 | 
					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 { DocumentService, DOCUMENT_SORT_FIELDS } from './rest/document.service'
 | 
				
			||||||
import { SettingsService } from './settings.service'
 | 
					import { SettingsService } from './settings.service'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Captures the current state of the list view.
 | 
					 * 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.
 | 
					   * 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.
 | 
					   * Total amount of documents with the current filter rules. Used to calculate the number of pages.
 | 
				
			||||||
   */
 | 
					   */
 | 
				
			||||||
  collectionSize: number
 | 
					  collectionSize?: number
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /**
 | 
					  /**
 | 
				
			||||||
   * Currently selected sort field.
 | 
					   * Currently selected sort field.
 | 
				
			||||||
@ -85,6 +86,32 @@ export class DocumentListViewService {
 | 
				
			|||||||
    return this.activeListViewState.title
 | 
					    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 {
 | 
					  private defaultListViewState(): ListViewState {
 | 
				
			||||||
    return {
 | 
					    return {
 | 
				
			||||||
      title: null,
 | 
					      title: null,
 | 
				
			||||||
@ -122,20 +149,40 @@ export class DocumentListViewService {
 | 
				
			|||||||
    if (closeCurrentView) {
 | 
					    if (closeCurrentView) {
 | 
				
			||||||
      this._activeSavedViewId = null
 | 
					      this._activeSavedViewId = null
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    this.activeListViewState.filterRules = cloneFilterRules(view.filter_rules)
 | 
					    this.activeListViewState.filterRules = cloneFilterRules(view.filter_rules)
 | 
				
			||||||
    this.activeListViewState.sortField = view.sort_field
 | 
					    this.activeListViewState.sortField = view.sort_field
 | 
				
			||||||
    this.activeListViewState.sortReverse = view.sort_reverse
 | 
					    this.activeListViewState.sortReverse = view.sort_reverse
 | 
				
			||||||
    if (this._activeSavedViewId) {
 | 
					    if (this._activeSavedViewId) {
 | 
				
			||||||
      this.activeListViewState.title = view.name
 | 
					      this.activeListViewState.title = view.name
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    this.reduceSelectionToFilter()
 | 
					    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.isReloading = true
 | 
				
			||||||
    this.error = null
 | 
					    this.error = null
 | 
				
			||||||
    let activeListViewState = this.activeListViewState
 | 
					    let activeListViewState = this.activeListViewState
 | 
				
			||||||
 | 
					 | 
				
			||||||
    this.documentService
 | 
					    this.documentService
 | 
				
			||||||
      .listFiltered(
 | 
					      .listFiltered(
 | 
				
			||||||
        activeListViewState.currentPage,
 | 
					        activeListViewState.currentPage,
 | 
				
			||||||
@ -149,6 +196,14 @@ export class DocumentListViewService {
 | 
				
			|||||||
          this.isReloading = false
 | 
					          this.isReloading = false
 | 
				
			||||||
          activeListViewState.collectionSize = result.count
 | 
					          activeListViewState.collectionSize = result.count
 | 
				
			||||||
          activeListViewState.documents = result.results
 | 
					          activeListViewState.documents = result.results
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          if (updateQueryParams && !this._activeSavedViewId) {
 | 
				
			||||||
 | 
					            let base = ['/documents']
 | 
				
			||||||
 | 
					            this.router.navigate(base, {
 | 
				
			||||||
 | 
					              queryParams: generateParams(activeListViewState),
 | 
				
			||||||
 | 
					            })
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          if (onFinish) {
 | 
					          if (onFinish) {
 | 
				
			||||||
            onFinish()
 | 
					            onFinish()
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
@ -191,6 +246,7 @@ export class DocumentListViewService {
 | 
				
			|||||||
    ) {
 | 
					    ) {
 | 
				
			||||||
      this.activeListViewState.sortField = 'created'
 | 
					      this.activeListViewState.sortField = 'created'
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					    this._activeSavedViewId = null
 | 
				
			||||||
    this.activeListViewState.filterRules = filterRules
 | 
					    this.activeListViewState.filterRules = filterRules
 | 
				
			||||||
    this.reload()
 | 
					    this.reload()
 | 
				
			||||||
    this.reduceSelectionToFilter()
 | 
					    this.reduceSelectionToFilter()
 | 
				
			||||||
@ -202,6 +258,7 @@ export class DocumentListViewService {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  set sortField(field: string) {
 | 
					  set sortField(field: string) {
 | 
				
			||||||
 | 
					    this._activeSavedViewId = null
 | 
				
			||||||
    this.activeListViewState.sortField = field
 | 
					    this.activeListViewState.sortField = field
 | 
				
			||||||
    this.reload()
 | 
					    this.reload()
 | 
				
			||||||
    this.saveDocumentListView()
 | 
					    this.saveDocumentListView()
 | 
				
			||||||
@ -212,6 +269,7 @@ export class DocumentListViewService {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  set sortReverse(reverse: boolean) {
 | 
					  set sortReverse(reverse: boolean) {
 | 
				
			||||||
 | 
					    this._activeSavedViewId = null
 | 
				
			||||||
    this.activeListViewState.sortReverse = reverse
 | 
					    this.activeListViewState.sortReverse = reverse
 | 
				
			||||||
    this.reload()
 | 
					    this.reload()
 | 
				
			||||||
    this.saveDocumentListView()
 | 
					    this.saveDocumentListView()
 | 
				
			||||||
@ -221,13 +279,6 @@ export class DocumentListViewService {
 | 
				
			|||||||
    return this.activeListViewState.sortReverse
 | 
					    return this.activeListViewState.sortReverse
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  get sortParams(): Params {
 | 
					 | 
				
			||||||
    return {
 | 
					 | 
				
			||||||
      sortField: this.sortField,
 | 
					 | 
				
			||||||
      sortReverse: this.sortReverse,
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  get collectionSize(): number {
 | 
					  get collectionSize(): number {
 | 
				
			||||||
    return this.activeListViewState.collectionSize
 | 
					    return this.activeListViewState.collectionSize
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
@ -237,6 +288,8 @@ export class DocumentListViewService {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  set currentPage(page: number) {
 | 
					  set currentPage(page: number) {
 | 
				
			||||||
 | 
					    if (this.activeListViewState.currentPage == page) return
 | 
				
			||||||
 | 
					    this._activeSavedViewId = null
 | 
				
			||||||
    this.activeListViewState.currentPage = page
 | 
					    this.activeListViewState.currentPage = page
 | 
				
			||||||
    this.reload()
 | 
					    this.reload()
 | 
				
			||||||
    this.saveDocumentListView()
 | 
					    this.saveDocumentListView()
 | 
				
			||||||
@ -273,6 +326,10 @@ export class DocumentListViewService {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  quickFilter(filterRules: FilterRule[]) {
 | 
				
			||||||
 | 
					    this.filterRules = filterRules
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  getLastPage(): number {
 | 
					  getLastPage(): number {
 | 
				
			||||||
    return Math.ceil(this.collectionSize / this.currentPageSize)
 | 
					    return Math.ceil(this.collectionSize / this.currentPageSize)
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
@ -431,29 +488,4 @@ export class DocumentListViewService {
 | 
				
			|||||||
  documentIndexInCurrentView(documentID: number): number {
 | 
					  documentIndexInCurrentView(documentID: number): number {
 | 
				
			||||||
    return this.documents.map((d) => d.id).indexOf(documentID)
 | 
					    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 { Observable } from 'rxjs'
 | 
				
			||||||
import { Results } from 'src/app/data/results'
 | 
					import { Results } from 'src/app/data/results'
 | 
				
			||||||
import { FilterRule } from 'src/app/data/filter-rule'
 | 
					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 { CorrespondentService } from './correspondent.service'
 | 
				
			||||||
import { DocumentTypeService } from './document-type.service'
 | 
					import { DocumentTypeService } from './document-type.service'
 | 
				
			||||||
import { TagService } from './tag.service'
 | 
					import { TagService } from './tag.service'
 | 
				
			||||||
import { PaperlessDocumentSuggestions } from 'src/app/data/paperless-document-suggestions'
 | 
					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'
 | 
					import { StoragePathService } from './storage-path.service'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const DOCUMENT_SORT_FIELDS = [
 | 
					export const DOCUMENT_SORT_FIELDS = [
 | 
				
			||||||
@ -70,7 +70,13 @@ export class DocumentService extends AbstractPaperlessService<PaperlessDocument>
 | 
				
			|||||||
      doc.document_type$ = this.documentTypeService.getCached(doc.document_type)
 | 
					      doc.document_type$ = this.documentTypeService.getCached(doc.document_type)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    if (doc.tags) {
 | 
					    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) {
 | 
					    if (doc.storage_path) {
 | 
				
			||||||
      doc.storage_path$ = this.storagePathService.getCached(doc.storage_path)
 | 
					      doc.storage_path$ = this.storagePathService.getCached(doc.storage_path)
 | 
				
			||||||
@ -91,7 +97,7 @@ export class DocumentService extends AbstractPaperlessService<PaperlessDocument>
 | 
				
			|||||||
      pageSize,
 | 
					      pageSize,
 | 
				
			||||||
      sortField,
 | 
					      sortField,
 | 
				
			||||||
      sortReverse,
 | 
					      sortReverse,
 | 
				
			||||||
      Object.assign(extraParams, filterRulesToQueryParams(filterRules))
 | 
					      Object.assign(extraParams, queryParamsFromFilterRules(filterRules))
 | 
				
			||||||
    ).pipe(
 | 
					    ).pipe(
 | 
				
			||||||
      map((results) => {
 | 
					      map((results) => {
 | 
				
			||||||
        results.results.forEach((doc) => this.addObservablesToDocument(doc))
 | 
					        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 {
 | 
					.btn-outline-primary {
 | 
				
			||||||
  border-color: var(--bs-primary) !important;
 | 
					  border-color: var(--bs-primary) !important;
 | 
				
			||||||
  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 .toast-header,
 | 
					  .toast .toast-header,
 | 
				
			||||||
  .toast .btn-close {
 | 
					  .toast .btn,
 | 
				
			||||||
 | 
					  .toast .btn-close, {
 | 
				
			||||||
    color: var(--pngx-primary-text-contrast);
 | 
					    color: var(--pngx-primary-text-contrast);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -5,6 +5,7 @@ import multiprocessing
 | 
				
			|||||||
import os
 | 
					import os
 | 
				
			||||||
import re
 | 
					import re
 | 
				
			||||||
from typing import Final
 | 
					from typing import Final
 | 
				
			||||||
 | 
					from typing import Optional
 | 
				
			||||||
from typing import Set
 | 
					from typing import Set
 | 
				
			||||||
from urllib.parse import urlparse
 | 
					from urllib.parse import urlparse
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -551,10 +552,9 @@ OCR_ROTATE_PAGES_THRESHOLD = float(
 | 
				
			|||||||
    os.getenv("PAPERLESS_OCR_ROTATE_PAGES_THRESHOLD", 12.0),
 | 
					    os.getenv("PAPERLESS_OCR_ROTATE_PAGES_THRESHOLD", 12.0),
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
OCR_MAX_IMAGE_PIXELS = os.environ.get(
 | 
					OCR_MAX_IMAGE_PIXELS: Optional[int] = None
 | 
				
			||||||
    "PAPERLESS_OCR_MAX_IMAGE_PIXELS",
 | 
					if os.environ.get("PAPERLESS_OCR_MAX_IMAGE_PIXELS") is not None:
 | 
				
			||||||
    256000000,
 | 
					    OCR_MAX_IMAGE_PIXELS: int = int(os.environ.get("PAPERLESS_OCR_MAX_IMAGE_PIXELS"))
 | 
				
			||||||
)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
OCR_USER_ARGS = os.getenv("PAPERLESS_OCR_USER_ARGS", "{}")
 | 
					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 documents.parsers import ParseError
 | 
				
			||||||
from PIL import Image
 | 
					from PIL import Image
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Image.MAX_IMAGE_PIXELS = settings.OCR_MAX_IMAGE_PIXELS
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
class NoTextFoundException(Exception):
 | 
					class NoTextFoundException(Exception):
 | 
				
			||||||
    pass
 | 
					    pass
 | 
				
			||||||
@ -225,6 +223,24 @@ class RasterisedDocumentParser(DocumentParser):
 | 
				
			|||||||
                    f"they will not be used. Error: {e}",
 | 
					                    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
 | 
					        return ocrmypdf_args
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def parse(self, document_path, mime_type, file_name=None):
 | 
					    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 ImageDraw
 | 
				
			||||||
from PIL import ImageFont
 | 
					from PIL import ImageFont
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Image.MAX_IMAGE_PIXELS = settings.OCR_MAX_IMAGE_PIXELS
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
class TextDocumentParser(DocumentParser):
 | 
					class TextDocumentParser(DocumentParser):
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
@ -28,7 +26,7 @@ class TextDocumentParser(DocumentParser):
 | 
				
			|||||||
        font = ImageFont.truetype(
 | 
					        font = ImageFont.truetype(
 | 
				
			||||||
            font=settings.THUMBNAIL_FONT_NAME,
 | 
					            font=settings.THUMBNAIL_FONT_NAME,
 | 
				
			||||||
            size=20,
 | 
					            size=20,
 | 
				
			||||||
            layout_engine=ImageFont.LAYOUT_BASIC,
 | 
					            layout_engine=ImageFont.Layout.BASIC,
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
        draw.text((5, 5), read_text(), font=font, fill="black")
 | 
					        draw.text((5, 5), read_text(), font=font, fill="black")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user