mirror of
				https://github.com/paperless-ngx/paperless-ngx.git
				synced 2025-11-03 03:16:10 -06:00 
			
		
		
		
	@@ -1,23 +1,25 @@
 | 
			
		||||
import { AppPage } from './app.po';
 | 
			
		||||
import { browser, logging } from 'protractor';
 | 
			
		||||
import { AppPage } from './app.po'
 | 
			
		||||
import { browser, logging } from 'protractor'
 | 
			
		||||
 | 
			
		||||
describe('workspace-project App', () => {
 | 
			
		||||
  let page: AppPage;
 | 
			
		||||
  let page: AppPage
 | 
			
		||||
 | 
			
		||||
  beforeEach(() => {
 | 
			
		||||
    page = new AppPage();
 | 
			
		||||
  });
 | 
			
		||||
    page = new AppPage()
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  it('should display welcome message', () => {
 | 
			
		||||
    page.navigateTo();
 | 
			
		||||
    expect(page.getTitleText()).toEqual('paperless-ui app is running!');
 | 
			
		||||
  });
 | 
			
		||||
    page.navigateTo()
 | 
			
		||||
    expect(page.getTitleText()).toEqual('paperless-ui app is running!')
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  afterEach(async () => {
 | 
			
		||||
    // Assert that there are no errors emitted from the browser
 | 
			
		||||
    const logs = await browser.manage().logs().get(logging.Type.BROWSER);
 | 
			
		||||
    expect(logs).not.toContain(jasmine.objectContaining({
 | 
			
		||||
      level: logging.Level.SEVERE,
 | 
			
		||||
    } as logging.Entry));
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
    const logs = await browser.manage().logs().get(logging.Type.BROWSER)
 | 
			
		||||
    expect(logs).not.toContain(
 | 
			
		||||
      jasmine.objectContaining({
 | 
			
		||||
        level: logging.Level.SEVERE,
 | 
			
		||||
      } as logging.Entry)
 | 
			
		||||
    )
 | 
			
		||||
  })
 | 
			
		||||
})
 | 
			
		||||
 
 | 
			
		||||
@@ -1,11 +1,13 @@
 | 
			
		||||
import { browser, by, element } from 'protractor';
 | 
			
		||||
import { browser, by, element } from 'protractor'
 | 
			
		||||
 | 
			
		||||
export class AppPage {
 | 
			
		||||
  navigateTo(): Promise<unknown> {
 | 
			
		||||
    return browser.get(browser.baseUrl) as Promise<unknown>;
 | 
			
		||||
    return browser.get(browser.baseUrl) as Promise<unknown>
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  getTitleText(): Promise<string> {
 | 
			
		||||
    return element(by.css('app-root .content span')).getText() as Promise<string>;
 | 
			
		||||
    return element(
 | 
			
		||||
      by.css('app-root .content span')
 | 
			
		||||
    ).getText() as Promise<string>
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,39 +1,47 @@
 | 
			
		||||
import { NgModule } from '@angular/core';
 | 
			
		||||
import { Routes, RouterModule } from '@angular/router';
 | 
			
		||||
import { AppFrameComponent } from './components/app-frame/app-frame.component';
 | 
			
		||||
import { DashboardComponent } from './components/dashboard/dashboard.component';
 | 
			
		||||
import { DocumentDetailComponent } from './components/document-detail/document-detail.component';
 | 
			
		||||
import { DocumentListComponent } from './components/document-list/document-list.component';
 | 
			
		||||
import { CorrespondentListComponent } from './components/manage/correspondent-list/correspondent-list.component';
 | 
			
		||||
import { DocumentTypeListComponent } from './components/manage/document-type-list/document-type-list.component';
 | 
			
		||||
import { LogsComponent } from './components/manage/logs/logs.component';
 | 
			
		||||
import { SettingsComponent } from './components/manage/settings/settings.component';
 | 
			
		||||
import { TagListComponent } from './components/manage/tag-list/tag-list.component';
 | 
			
		||||
import { NotFoundComponent } from './components/not-found/not-found.component';
 | 
			
		||||
import {DocumentAsnComponent} from "./components/document-asn/document-asn.component";
 | 
			
		||||
import { DirtyFormGuard } from './guards/dirty-form.guard';
 | 
			
		||||
import { NgModule } from '@angular/core'
 | 
			
		||||
import { Routes, RouterModule } from '@angular/router'
 | 
			
		||||
import { AppFrameComponent } from './components/app-frame/app-frame.component'
 | 
			
		||||
import { DashboardComponent } from './components/dashboard/dashboard.component'
 | 
			
		||||
import { DocumentDetailComponent } from './components/document-detail/document-detail.component'
 | 
			
		||||
import { DocumentListComponent } from './components/document-list/document-list.component'
 | 
			
		||||
import { CorrespondentListComponent } from './components/manage/correspondent-list/correspondent-list.component'
 | 
			
		||||
import { DocumentTypeListComponent } from './components/manage/document-type-list/document-type-list.component'
 | 
			
		||||
import { LogsComponent } from './components/manage/logs/logs.component'
 | 
			
		||||
import { SettingsComponent } from './components/manage/settings/settings.component'
 | 
			
		||||
import { TagListComponent } from './components/manage/tag-list/tag-list.component'
 | 
			
		||||
import { NotFoundComponent } from './components/not-found/not-found.component'
 | 
			
		||||
import { DocumentAsnComponent } from './components/document-asn/document-asn.component'
 | 
			
		||||
import { DirtyFormGuard } from './guards/dirty-form.guard'
 | 
			
		||||
 | 
			
		||||
const routes: Routes = [
 | 
			
		||||
  {path: '', redirectTo: 'dashboard', pathMatch: 'full'},
 | 
			
		||||
  {path: '', component: AppFrameComponent, children: [
 | 
			
		||||
    {path: 'dashboard', component: DashboardComponent },
 | 
			
		||||
    {path: 'documents', component: DocumentListComponent },
 | 
			
		||||
    {path: 'view/:id', component: DocumentListComponent },
 | 
			
		||||
    {path: 'documents/:id', component: DocumentDetailComponent },
 | 
			
		||||
    {path: 'asn/:id', component: DocumentAsnComponent },
 | 
			
		||||
    {path: 'tags', component: TagListComponent },
 | 
			
		||||
    {path: 'documenttypes', component: DocumentTypeListComponent },
 | 
			
		||||
    {path: 'correspondents', component: CorrespondentListComponent },
 | 
			
		||||
    {path: 'logs', component: LogsComponent },
 | 
			
		||||
    {path: 'settings', component: SettingsComponent, canDeactivate: [DirtyFormGuard] },
 | 
			
		||||
  ]},
 | 
			
		||||
  { path: '', redirectTo: 'dashboard', pathMatch: 'full' },
 | 
			
		||||
  {
 | 
			
		||||
    path: '',
 | 
			
		||||
    component: AppFrameComponent,
 | 
			
		||||
    children: [
 | 
			
		||||
      { path: 'dashboard', component: DashboardComponent },
 | 
			
		||||
      { path: 'documents', component: DocumentListComponent },
 | 
			
		||||
      { path: 'view/:id', component: DocumentListComponent },
 | 
			
		||||
      { path: 'documents/:id', component: DocumentDetailComponent },
 | 
			
		||||
      { path: 'asn/:id', component: DocumentAsnComponent },
 | 
			
		||||
      { path: 'tags', component: TagListComponent },
 | 
			
		||||
      { path: 'documenttypes', component: DocumentTypeListComponent },
 | 
			
		||||
      { path: 'correspondents', component: CorrespondentListComponent },
 | 
			
		||||
      { path: 'logs', component: LogsComponent },
 | 
			
		||||
      {
 | 
			
		||||
        path: 'settings',
 | 
			
		||||
        component: SettingsComponent,
 | 
			
		||||
        canDeactivate: [DirtyFormGuard],
 | 
			
		||||
      },
 | 
			
		||||
    ],
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  {path: '404', component: NotFoundComponent},
 | 
			
		||||
  {path: '**', redirectTo: '/404', pathMatch: 'full'}
 | 
			
		||||
];
 | 
			
		||||
  { path: '404', component: NotFoundComponent },
 | 
			
		||||
  { path: '**', redirectTo: '/404', pathMatch: 'full' },
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
@NgModule({
 | 
			
		||||
  imports: [RouterModule.forRoot(routes, { relativeLinkResolution: 'legacy' })],
 | 
			
		||||
  exports: [RouterModule]
 | 
			
		||||
  exports: [RouterModule],
 | 
			
		||||
})
 | 
			
		||||
export class AppRoutingModule { }
 | 
			
		||||
export class AppRoutingModule {}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,35 +1,33 @@
 | 
			
		||||
import { TestBed } from '@angular/core/testing';
 | 
			
		||||
import { RouterTestingModule } from '@angular/router/testing';
 | 
			
		||||
import { AppComponent } from './app.component';
 | 
			
		||||
import { TestBed } from '@angular/core/testing'
 | 
			
		||||
import { RouterTestingModule } from '@angular/router/testing'
 | 
			
		||||
import { AppComponent } from './app.component'
 | 
			
		||||
 | 
			
		||||
describe('AppComponent', () => {
 | 
			
		||||
  beforeEach(async () => {
 | 
			
		||||
    await TestBed.configureTestingModule({
 | 
			
		||||
      imports: [
 | 
			
		||||
        RouterTestingModule
 | 
			
		||||
      ],
 | 
			
		||||
      declarations: [
 | 
			
		||||
        AppComponent
 | 
			
		||||
      ],
 | 
			
		||||
    }).compileComponents();
 | 
			
		||||
  });
 | 
			
		||||
      imports: [RouterTestingModule],
 | 
			
		||||
      declarations: [AppComponent],
 | 
			
		||||
    }).compileComponents()
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  it('should create the app', () => {
 | 
			
		||||
    const fixture = TestBed.createComponent(AppComponent);
 | 
			
		||||
    const app = fixture.componentInstance;
 | 
			
		||||
    expect(app).toBeTruthy();
 | 
			
		||||
  });
 | 
			
		||||
    const fixture = TestBed.createComponent(AppComponent)
 | 
			
		||||
    const app = fixture.componentInstance
 | 
			
		||||
    expect(app).toBeTruthy()
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  it(`should have as title 'paperless-ui'`, () => {
 | 
			
		||||
    const fixture = TestBed.createComponent(AppComponent);
 | 
			
		||||
    const app = fixture.componentInstance;
 | 
			
		||||
    expect(app.title).toEqual('paperless-ui');
 | 
			
		||||
  });
 | 
			
		||||
    const fixture = TestBed.createComponent(AppComponent)
 | 
			
		||||
    const app = fixture.componentInstance
 | 
			
		||||
    expect(app.title).toEqual('paperless-ui')
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  it('should render title', () => {
 | 
			
		||||
    const fixture = TestBed.createComponent(AppComponent);
 | 
			
		||||
    fixture.detectChanges();
 | 
			
		||||
    const compiled = fixture.nativeElement;
 | 
			
		||||
    expect(compiled.querySelector('.content span').textContent).toContain('paperless-ui app is running!');
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
    const fixture = TestBed.createComponent(AppComponent)
 | 
			
		||||
    fixture.detectChanges()
 | 
			
		||||
    const compiled = fixture.nativeElement
 | 
			
		||||
    expect(compiled.querySelector('.content span').textContent).toContain(
 | 
			
		||||
      'paperless-ui app is running!'
 | 
			
		||||
    )
 | 
			
		||||
  })
 | 
			
		||||
})
 | 
			
		||||
 
 | 
			
		||||
@@ -1,24 +1,28 @@
 | 
			
		||||
import { SettingsService, SETTINGS_KEYS } from './services/settings.service';
 | 
			
		||||
import { Component, OnDestroy, OnInit } from '@angular/core';
 | 
			
		||||
import { Router } from '@angular/router';
 | 
			
		||||
import { Subscription } from 'rxjs';
 | 
			
		||||
import { ConsumerStatusService } from './services/consumer-status.service';
 | 
			
		||||
import { ToastService } from './services/toast.service';
 | 
			
		||||
import { SettingsService, SETTINGS_KEYS } from './services/settings.service'
 | 
			
		||||
import { Component, OnDestroy, OnInit } from '@angular/core'
 | 
			
		||||
import { Router } from '@angular/router'
 | 
			
		||||
import { Subscription } from 'rxjs'
 | 
			
		||||
import { ConsumerStatusService } from './services/consumer-status.service'
 | 
			
		||||
import { ToastService } from './services/toast.service'
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'app-root',
 | 
			
		||||
  templateUrl: './app.component.html',
 | 
			
		||||
  styleUrls: ['./app.component.scss']
 | 
			
		||||
  styleUrls: ['./app.component.scss'],
 | 
			
		||||
})
 | 
			
		||||
export class AppComponent implements OnInit, OnDestroy {
 | 
			
		||||
  newDocumentSubscription: Subscription
 | 
			
		||||
  successSubscription: Subscription
 | 
			
		||||
  failedSubscription: Subscription
 | 
			
		||||
 | 
			
		||||
  newDocumentSubscription: Subscription;
 | 
			
		||||
  successSubscription: Subscription;
 | 
			
		||||
  failedSubscription: Subscription;
 | 
			
		||||
 | 
			
		||||
  constructor (private settings: SettingsService, private consumerStatusService: ConsumerStatusService, private toastService: ToastService, private router: Router) {
 | 
			
		||||
    let anyWindow = (window as any)
 | 
			
		||||
    anyWindow.pdfWorkerSrc = 'assets/js/pdf.worker.min.js';
 | 
			
		||||
  constructor(
 | 
			
		||||
    private settings: SettingsService,
 | 
			
		||||
    private consumerStatusService: ConsumerStatusService,
 | 
			
		||||
    private toastService: ToastService,
 | 
			
		||||
    private router: Router
 | 
			
		||||
  ) {
 | 
			
		||||
    let anyWindow = window as any
 | 
			
		||||
    anyWindow.pdfWorkerSrc = 'assets/js/pdf.worker.min.js'
 | 
			
		||||
    this.settings.updateAppearanceSettings()
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@@ -36,7 +40,12 @@ export class AppComponent implements OnInit, OnDestroy {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private showNotification(key) {
 | 
			
		||||
    if (this.router.url == '/dashboard' && this.settings.get(SETTINGS_KEYS.NOTIFICATIONS_CONSUMER_SUPPRESS_ON_DASHBOARD)) {
 | 
			
		||||
    if (
 | 
			
		||||
      this.router.url == '/dashboard' &&
 | 
			
		||||
      this.settings.get(
 | 
			
		||||
        SETTINGS_KEYS.NOTIFICATIONS_CONSUMER_SUPPRESS_ON_DASHBOARD
 | 
			
		||||
      )
 | 
			
		||||
    ) {
 | 
			
		||||
      return false
 | 
			
		||||
    }
 | 
			
		||||
    return this.settings.get(key)
 | 
			
		||||
@@ -45,26 +54,50 @@ export class AppComponent implements OnInit, OnDestroy {
 | 
			
		||||
  ngOnInit(): void {
 | 
			
		||||
    this.consumerStatusService.connect()
 | 
			
		||||
 | 
			
		||||
    this.successSubscription = this.consumerStatusService
 | 
			
		||||
      .onDocumentConsumptionFinished()
 | 
			
		||||
      .subscribe((status) => {
 | 
			
		||||
        if (
 | 
			
		||||
          this.showNotification(SETTINGS_KEYS.NOTIFICATIONS_CONSUMER_SUCCESS)
 | 
			
		||||
        ) {
 | 
			
		||||
          this.toastService.show({
 | 
			
		||||
            title: $localize`Document added`,
 | 
			
		||||
            delay: 10000,
 | 
			
		||||
            content: $localize`Document ${status.filename} was added to paperless.`,
 | 
			
		||||
            actionName: $localize`Open document`,
 | 
			
		||||
            action: () => {
 | 
			
		||||
              this.router.navigate(['documents', status.documentId])
 | 
			
		||||
            },
 | 
			
		||||
          })
 | 
			
		||||
        }
 | 
			
		||||
      })
 | 
			
		||||
 | 
			
		||||
    this.successSubscription = this.consumerStatusService.onDocumentConsumptionFinished().subscribe(status => {
 | 
			
		||||
      if (this.showNotification(SETTINGS_KEYS.NOTIFICATIONS_CONSUMER_SUCCESS)) {
 | 
			
		||||
        this.toastService.show({title: $localize`Document added`, delay: 10000, content: $localize`Document ${status.filename} was added to paperless.`, actionName: $localize`Open document`, action: () => {
 | 
			
		||||
          this.router.navigate(['documents', status.documentId])
 | 
			
		||||
        }})
 | 
			
		||||
      }
 | 
			
		||||
    })
 | 
			
		||||
    this.failedSubscription = this.consumerStatusService
 | 
			
		||||
      .onDocumentConsumptionFailed()
 | 
			
		||||
      .subscribe((status) => {
 | 
			
		||||
        if (
 | 
			
		||||
          this.showNotification(SETTINGS_KEYS.NOTIFICATIONS_CONSUMER_FAILED)
 | 
			
		||||
        ) {
 | 
			
		||||
          this.toastService.showError(
 | 
			
		||||
            $localize`Could not add ${status.filename}\: ${status.message}`
 | 
			
		||||
          )
 | 
			
		||||
        }
 | 
			
		||||
      })
 | 
			
		||||
 | 
			
		||||
    this.failedSubscription = this.consumerStatusService.onDocumentConsumptionFailed().subscribe(status => {
 | 
			
		||||
      if (this.showNotification(SETTINGS_KEYS.NOTIFICATIONS_CONSUMER_FAILED)) {
 | 
			
		||||
        this.toastService.showError($localize`Could not add ${status.filename}\: ${status.message}`)
 | 
			
		||||
      }
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
    this.newDocumentSubscription = this.consumerStatusService.onDocumentDetected().subscribe(status => {
 | 
			
		||||
      if (this.showNotification(SETTINGS_KEYS.NOTIFICATIONS_CONSUMER_NEW_DOCUMENT)) {
 | 
			
		||||
        this.toastService.show({title: $localize`New document detected`, delay: 5000, content: $localize`Document ${status.filename} is being processed by paperless.`})
 | 
			
		||||
      }
 | 
			
		||||
    })
 | 
			
		||||
    this.newDocumentSubscription = this.consumerStatusService
 | 
			
		||||
      .onDocumentDetected()
 | 
			
		||||
      .subscribe((status) => {
 | 
			
		||||
        if (
 | 
			
		||||
          this.showNotification(
 | 
			
		||||
            SETTINGS_KEYS.NOTIFICATIONS_CONSUMER_NEW_DOCUMENT
 | 
			
		||||
          )
 | 
			
		||||
        ) {
 | 
			
		||||
          this.toastService.show({
 | 
			
		||||
            title: $localize`New document detected`,
 | 
			
		||||
            delay: 5000,
 | 
			
		||||
            content: $localize`Document ${status.filename} is being processed by paperless.`,
 | 
			
		||||
          })
 | 
			
		||||
        }
 | 
			
		||||
      })
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,85 +1,88 @@
 | 
			
		||||
import { BrowserModule } from '@angular/platform-browser';
 | 
			
		||||
import { NgModule } from '@angular/core';
 | 
			
		||||
import { BrowserModule } from '@angular/platform-browser'
 | 
			
		||||
import { NgModule } from '@angular/core'
 | 
			
		||||
 | 
			
		||||
import { AppRoutingModule } from './app-routing.module';
 | 
			
		||||
import { AppComponent } from './app.component';
 | 
			
		||||
import { NgbDateAdapter, NgbDateParserFormatter, NgbModule } from '@ng-bootstrap/ng-bootstrap';
 | 
			
		||||
import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
 | 
			
		||||
import { DocumentListComponent } from './components/document-list/document-list.component';
 | 
			
		||||
import { DocumentDetailComponent } from './components/document-detail/document-detail.component';
 | 
			
		||||
import { DashboardComponent } from './components/dashboard/dashboard.component';
 | 
			
		||||
import { TagListComponent } from './components/manage/tag-list/tag-list.component';
 | 
			
		||||
import { DocumentTypeListComponent } from './components/manage/document-type-list/document-type-list.component';
 | 
			
		||||
import { LogsComponent } from './components/manage/logs/logs.component';
 | 
			
		||||
import { SettingsComponent } from './components/manage/settings/settings.component';
 | 
			
		||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
 | 
			
		||||
import { DatePipe, registerLocaleData } from '@angular/common';
 | 
			
		||||
import { NotFoundComponent } from './components/not-found/not-found.component';
 | 
			
		||||
import { CorrespondentListComponent } from './components/manage/correspondent-list/correspondent-list.component';
 | 
			
		||||
import { ConfirmDialogComponent } from './components/common/confirm-dialog/confirm-dialog.component';
 | 
			
		||||
import { CorrespondentEditDialogComponent } from './components/manage/correspondent-list/correspondent-edit-dialog/correspondent-edit-dialog.component';
 | 
			
		||||
import { TagEditDialogComponent } from './components/manage/tag-list/tag-edit-dialog/tag-edit-dialog.component';
 | 
			
		||||
import { DocumentTypeEditDialogComponent } from './components/manage/document-type-list/document-type-edit-dialog/document-type-edit-dialog.component';
 | 
			
		||||
import { TagComponent } from './components/common/tag/tag.component';
 | 
			
		||||
import { PageHeaderComponent } from './components/common/page-header/page-header.component';
 | 
			
		||||
import { AppFrameComponent } from './components/app-frame/app-frame.component';
 | 
			
		||||
import { ToastsComponent } from './components/common/toasts/toasts.component';
 | 
			
		||||
import { FilterEditorComponent } from './components/document-list/filter-editor/filter-editor.component';
 | 
			
		||||
import { FilterableDropdownComponent } from './components/common/filterable-dropdown/filterable-dropdown.component';
 | 
			
		||||
import { ToggleableDropdownButtonComponent } from './components/common/filterable-dropdown/toggleable-dropdown-button/toggleable-dropdown-button.component';
 | 
			
		||||
import { DateDropdownComponent } from './components/common/date-dropdown/date-dropdown.component';
 | 
			
		||||
import { DocumentCardLargeComponent } from './components/document-list/document-card-large/document-card-large.component';
 | 
			
		||||
import { DocumentCardSmallComponent } from './components/document-list/document-card-small/document-card-small.component';
 | 
			
		||||
import { BulkEditorComponent } from './components/document-list/bulk-editor/bulk-editor.component';
 | 
			
		||||
import { NgxFileDropModule } from 'ngx-file-drop';
 | 
			
		||||
import { TextComponent } from './components/common/input/text/text.component';
 | 
			
		||||
import { SelectComponent } from './components/common/input/select/select.component';
 | 
			
		||||
import { CheckComponent } from './components/common/input/check/check.component';
 | 
			
		||||
import { SaveViewConfigDialogComponent } from './components/document-list/save-view-config-dialog/save-view-config-dialog.component';
 | 
			
		||||
import { InfiniteScrollModule } from 'ngx-infinite-scroll';
 | 
			
		||||
import { TagsComponent } from './components/common/input/tags/tags.component';
 | 
			
		||||
import { SortableDirective } from './directives/sortable.directive';
 | 
			
		||||
import { CookieService } from 'ngx-cookie-service';
 | 
			
		||||
import { CsrfInterceptor } from './interceptors/csrf.interceptor';
 | 
			
		||||
import { SavedViewWidgetComponent } from './components/dashboard/widgets/saved-view-widget/saved-view-widget.component';
 | 
			
		||||
import { StatisticsWidgetComponent } from './components/dashboard/widgets/statistics-widget/statistics-widget.component';
 | 
			
		||||
import { UploadFileWidgetComponent } from './components/dashboard/widgets/upload-file-widget/upload-file-widget.component';
 | 
			
		||||
import { WidgetFrameComponent } from './components/dashboard/widgets/widget-frame/widget-frame.component';
 | 
			
		||||
import { PdfViewerModule } from 'ng2-pdf-viewer';
 | 
			
		||||
import { WelcomeWidgetComponent } from './components/dashboard/widgets/welcome-widget/welcome-widget.component';
 | 
			
		||||
import { YesNoPipe } from './pipes/yes-no.pipe';
 | 
			
		||||
import { FileSizePipe } from './pipes/file-size.pipe';
 | 
			
		||||
import { FilterPipe } from './pipes/filter.pipe';
 | 
			
		||||
import { DocumentTitlePipe } from './pipes/document-title.pipe';
 | 
			
		||||
import { MetadataCollapseComponent } from './components/document-detail/metadata-collapse/metadata-collapse.component';
 | 
			
		||||
import { SelectDialogComponent } from './components/common/select-dialog/select-dialog.component';
 | 
			
		||||
import { NgSelectModule } from '@ng-select/ng-select';
 | 
			
		||||
import { NumberComponent } from './components/common/input/number/number.component';
 | 
			
		||||
import { SafePipe } from './pipes/safe.pipe';
 | 
			
		||||
import { CustomDatePipe } from './pipes/custom-date.pipe';
 | 
			
		||||
import { DateComponent } from './components/common/input/date/date.component';
 | 
			
		||||
import { ISODateTimeAdapter } from './utils/ngb-iso-date-time-adapter';
 | 
			
		||||
import { LocalizedDateParserFormatter } from './utils/ngb-date-parser-formatter';
 | 
			
		||||
import { ApiVersionInterceptor } from './interceptors/api-version.interceptor';
 | 
			
		||||
import { ColorSliderModule } from 'ngx-color/slider';
 | 
			
		||||
import { ColorComponent } from './components/common/input/color/color.component';
 | 
			
		||||
import { DocumentAsnComponent } from './components/document-asn/document-asn.component';
 | 
			
		||||
 | 
			
		||||
import localeCs from '@angular/common/locales/cs';
 | 
			
		||||
import localeDa from '@angular/common/locales/da';
 | 
			
		||||
import localeDe from '@angular/common/locales/de';
 | 
			
		||||
import localeEnGb from '@angular/common/locales/en-GB';
 | 
			
		||||
import localeEs from '@angular/common/locales/es';
 | 
			
		||||
import localeFr from '@angular/common/locales/fr';
 | 
			
		||||
import localeIt from '@angular/common/locales/it';
 | 
			
		||||
import localeLb from '@angular/common/locales/lb';
 | 
			
		||||
import localeNl from '@angular/common/locales/nl';
 | 
			
		||||
import localePl from '@angular/common/locales/pl';
 | 
			
		||||
import localePt from '@angular/common/locales/pt';
 | 
			
		||||
import localeSv from '@angular/common/locales/sv';
 | 
			
		||||
import localeRo from '@angular/common/locales/ro';
 | 
			
		||||
import localeRu from '@angular/common/locales/ru';
 | 
			
		||||
import { AppRoutingModule } from './app-routing.module'
 | 
			
		||||
import { AppComponent } from './app.component'
 | 
			
		||||
import {
 | 
			
		||||
  NgbDateAdapter,
 | 
			
		||||
  NgbDateParserFormatter,
 | 
			
		||||
  NgbModule,
 | 
			
		||||
} from '@ng-bootstrap/ng-bootstrap'
 | 
			
		||||
import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http'
 | 
			
		||||
import { DocumentListComponent } from './components/document-list/document-list.component'
 | 
			
		||||
import { DocumentDetailComponent } from './components/document-detail/document-detail.component'
 | 
			
		||||
import { DashboardComponent } from './components/dashboard/dashboard.component'
 | 
			
		||||
import { TagListComponent } from './components/manage/tag-list/tag-list.component'
 | 
			
		||||
import { DocumentTypeListComponent } from './components/manage/document-type-list/document-type-list.component'
 | 
			
		||||
import { LogsComponent } from './components/manage/logs/logs.component'
 | 
			
		||||
import { SettingsComponent } from './components/manage/settings/settings.component'
 | 
			
		||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms'
 | 
			
		||||
import { DatePipe, registerLocaleData } from '@angular/common'
 | 
			
		||||
import { NotFoundComponent } from './components/not-found/not-found.component'
 | 
			
		||||
import { CorrespondentListComponent } from './components/manage/correspondent-list/correspondent-list.component'
 | 
			
		||||
import { ConfirmDialogComponent } from './components/common/confirm-dialog/confirm-dialog.component'
 | 
			
		||||
import { CorrespondentEditDialogComponent } from './components/manage/correspondent-list/correspondent-edit-dialog/correspondent-edit-dialog.component'
 | 
			
		||||
import { TagEditDialogComponent } from './components/manage/tag-list/tag-edit-dialog/tag-edit-dialog.component'
 | 
			
		||||
import { DocumentTypeEditDialogComponent } from './components/manage/document-type-list/document-type-edit-dialog/document-type-edit-dialog.component'
 | 
			
		||||
import { TagComponent } from './components/common/tag/tag.component'
 | 
			
		||||
import { PageHeaderComponent } from './components/common/page-header/page-header.component'
 | 
			
		||||
import { AppFrameComponent } from './components/app-frame/app-frame.component'
 | 
			
		||||
import { ToastsComponent } from './components/common/toasts/toasts.component'
 | 
			
		||||
import { FilterEditorComponent } from './components/document-list/filter-editor/filter-editor.component'
 | 
			
		||||
import { FilterableDropdownComponent } from './components/common/filterable-dropdown/filterable-dropdown.component'
 | 
			
		||||
import { ToggleableDropdownButtonComponent } from './components/common/filterable-dropdown/toggleable-dropdown-button/toggleable-dropdown-button.component'
 | 
			
		||||
import { DateDropdownComponent } from './components/common/date-dropdown/date-dropdown.component'
 | 
			
		||||
import { DocumentCardLargeComponent } from './components/document-list/document-card-large/document-card-large.component'
 | 
			
		||||
import { DocumentCardSmallComponent } from './components/document-list/document-card-small/document-card-small.component'
 | 
			
		||||
import { BulkEditorComponent } from './components/document-list/bulk-editor/bulk-editor.component'
 | 
			
		||||
import { NgxFileDropModule } from 'ngx-file-drop'
 | 
			
		||||
import { TextComponent } from './components/common/input/text/text.component'
 | 
			
		||||
import { SelectComponent } from './components/common/input/select/select.component'
 | 
			
		||||
import { CheckComponent } from './components/common/input/check/check.component'
 | 
			
		||||
import { SaveViewConfigDialogComponent } from './components/document-list/save-view-config-dialog/save-view-config-dialog.component'
 | 
			
		||||
import { InfiniteScrollModule } from 'ngx-infinite-scroll'
 | 
			
		||||
import { TagsComponent } from './components/common/input/tags/tags.component'
 | 
			
		||||
import { SortableDirective } from './directives/sortable.directive'
 | 
			
		||||
import { CookieService } from 'ngx-cookie-service'
 | 
			
		||||
import { CsrfInterceptor } from './interceptors/csrf.interceptor'
 | 
			
		||||
import { SavedViewWidgetComponent } from './components/dashboard/widgets/saved-view-widget/saved-view-widget.component'
 | 
			
		||||
import { StatisticsWidgetComponent } from './components/dashboard/widgets/statistics-widget/statistics-widget.component'
 | 
			
		||||
import { UploadFileWidgetComponent } from './components/dashboard/widgets/upload-file-widget/upload-file-widget.component'
 | 
			
		||||
import { WidgetFrameComponent } from './components/dashboard/widgets/widget-frame/widget-frame.component'
 | 
			
		||||
import { PdfViewerModule } from 'ng2-pdf-viewer'
 | 
			
		||||
import { WelcomeWidgetComponent } from './components/dashboard/widgets/welcome-widget/welcome-widget.component'
 | 
			
		||||
import { YesNoPipe } from './pipes/yes-no.pipe'
 | 
			
		||||
import { FileSizePipe } from './pipes/file-size.pipe'
 | 
			
		||||
import { FilterPipe } from './pipes/filter.pipe'
 | 
			
		||||
import { DocumentTitlePipe } from './pipes/document-title.pipe'
 | 
			
		||||
import { MetadataCollapseComponent } from './components/document-detail/metadata-collapse/metadata-collapse.component'
 | 
			
		||||
import { SelectDialogComponent } from './components/common/select-dialog/select-dialog.component'
 | 
			
		||||
import { NgSelectModule } from '@ng-select/ng-select'
 | 
			
		||||
import { NumberComponent } from './components/common/input/number/number.component'
 | 
			
		||||
import { SafePipe } from './pipes/safe.pipe'
 | 
			
		||||
import { CustomDatePipe } from './pipes/custom-date.pipe'
 | 
			
		||||
import { DateComponent } from './components/common/input/date/date.component'
 | 
			
		||||
import { ISODateTimeAdapter } from './utils/ngb-iso-date-time-adapter'
 | 
			
		||||
import { LocalizedDateParserFormatter } from './utils/ngb-date-parser-formatter'
 | 
			
		||||
import { ApiVersionInterceptor } from './interceptors/api-version.interceptor'
 | 
			
		||||
import { ColorSliderModule } from 'ngx-color/slider'
 | 
			
		||||
import { ColorComponent } from './components/common/input/color/color.component'
 | 
			
		||||
import { DocumentAsnComponent } from './components/document-asn/document-asn.component'
 | 
			
		||||
 | 
			
		||||
import localeCs from '@angular/common/locales/cs'
 | 
			
		||||
import localeDa from '@angular/common/locales/da'
 | 
			
		||||
import localeDe from '@angular/common/locales/de'
 | 
			
		||||
import localeEnGb from '@angular/common/locales/en-GB'
 | 
			
		||||
import localeEs from '@angular/common/locales/es'
 | 
			
		||||
import localeFr from '@angular/common/locales/fr'
 | 
			
		||||
import localeIt from '@angular/common/locales/it'
 | 
			
		||||
import localeLb from '@angular/common/locales/lb'
 | 
			
		||||
import localeNl from '@angular/common/locales/nl'
 | 
			
		||||
import localePl from '@angular/common/locales/pl'
 | 
			
		||||
import localePt from '@angular/common/locales/pt'
 | 
			
		||||
import localeSv from '@angular/common/locales/sv'
 | 
			
		||||
import localeRo from '@angular/common/locales/ro'
 | 
			
		||||
import localeRu from '@angular/common/locales/ru'
 | 
			
		||||
 | 
			
		||||
registerLocaleData(localeCs)
 | 
			
		||||
registerLocaleData(localeDa)
 | 
			
		||||
@@ -91,8 +94,8 @@ registerLocaleData(localeIt)
 | 
			
		||||
registerLocaleData(localeLb)
 | 
			
		||||
registerLocaleData(localeNl)
 | 
			
		||||
registerLocaleData(localePl)
 | 
			
		||||
registerLocaleData(localePt, "pt-BR")
 | 
			
		||||
registerLocaleData(localePt, "pt-PT")
 | 
			
		||||
registerLocaleData(localePt, 'pt-BR')
 | 
			
		||||
registerLocaleData(localePt, 'pt-PT')
 | 
			
		||||
registerLocaleData(localeRo)
 | 
			
		||||
registerLocaleData(localeRu)
 | 
			
		||||
registerLocaleData(localeSv)
 | 
			
		||||
@@ -146,7 +149,7 @@ registerLocaleData(localeSv)
 | 
			
		||||
    CustomDatePipe,
 | 
			
		||||
    DateComponent,
 | 
			
		||||
    ColorComponent,
 | 
			
		||||
    DocumentAsnComponent
 | 
			
		||||
    DocumentAsnComponent,
 | 
			
		||||
  ],
 | 
			
		||||
  imports: [
 | 
			
		||||
    BrowserModule,
 | 
			
		||||
@@ -159,24 +162,26 @@ registerLocaleData(localeSv)
 | 
			
		||||
    InfiniteScrollModule,
 | 
			
		||||
    PdfViewerModule,
 | 
			
		||||
    NgSelectModule,
 | 
			
		||||
    ColorSliderModule
 | 
			
		||||
    ColorSliderModule,
 | 
			
		||||
  ],
 | 
			
		||||
  providers: [
 | 
			
		||||
    DatePipe,
 | 
			
		||||
    CookieService, {
 | 
			
		||||
    CookieService,
 | 
			
		||||
    {
 | 
			
		||||
      provide: HTTP_INTERCEPTORS,
 | 
			
		||||
      useClass: CsrfInterceptor,
 | 
			
		||||
      multi: true
 | 
			
		||||
    },{
 | 
			
		||||
      multi: true,
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      provide: HTTP_INTERCEPTORS,
 | 
			
		||||
      useClass: ApiVersionInterceptor,
 | 
			
		||||
      multi: true
 | 
			
		||||
      multi: true,
 | 
			
		||||
    },
 | 
			
		||||
    FilterPipe,
 | 
			
		||||
    DocumentTitlePipe,
 | 
			
		||||
    {provide: NgbDateAdapter, useClass: ISODateTimeAdapter},
 | 
			
		||||
    {provide: NgbDateParserFormatter, useClass: LocalizedDateParserFormatter}
 | 
			
		||||
    { provide: NgbDateAdapter, useClass: ISODateTimeAdapter },
 | 
			
		||||
    { provide: NgbDateParserFormatter, useClass: LocalizedDateParserFormatter },
 | 
			
		||||
  ],
 | 
			
		||||
  bootstrap: [AppComponent]
 | 
			
		||||
  bootstrap: [AppComponent],
 | 
			
		||||
})
 | 
			
		||||
export class AppModule { }
 | 
			
		||||
export class AppModule {}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,25 +1,24 @@
 | 
			
		||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
 | 
			
		||||
import { ComponentFixture, TestBed } from '@angular/core/testing'
 | 
			
		||||
 | 
			
		||||
import { AppFrameComponent } from './app-frame.component';
 | 
			
		||||
import { AppFrameComponent } from './app-frame.component'
 | 
			
		||||
 | 
			
		||||
describe('AppFrameComponent', () => {
 | 
			
		||||
  let component: AppFrameComponent;
 | 
			
		||||
  let fixture: ComponentFixture<AppFrameComponent>;
 | 
			
		||||
  let component: AppFrameComponent
 | 
			
		||||
  let fixture: ComponentFixture<AppFrameComponent>
 | 
			
		||||
 | 
			
		||||
  beforeEach(async () => {
 | 
			
		||||
    await TestBed.configureTestingModule({
 | 
			
		||||
      declarations: [ AppFrameComponent ]
 | 
			
		||||
    })
 | 
			
		||||
    .compileComponents();
 | 
			
		||||
  });
 | 
			
		||||
      declarations: [AppFrameComponent],
 | 
			
		||||
    }).compileComponents()
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  beforeEach(() => {
 | 
			
		||||
    fixture = TestBed.createComponent(AppFrameComponent);
 | 
			
		||||
    component = fixture.componentInstance;
 | 
			
		||||
    fixture.detectChanges();
 | 
			
		||||
  });
 | 
			
		||||
    fixture = TestBed.createComponent(AppFrameComponent)
 | 
			
		||||
    component = fixture.componentInstance
 | 
			
		||||
    fixture.detectChanges()
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  it('should create', () => {
 | 
			
		||||
    expect(component).toBeTruthy();
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
    expect(component).toBeTruthy()
 | 
			
		||||
  })
 | 
			
		||||
})
 | 
			
		||||
 
 | 
			
		||||
@@ -1,26 +1,31 @@
 | 
			
		||||
import { Component } from '@angular/core';
 | 
			
		||||
import { FormControl } from '@angular/forms';
 | 
			
		||||
import { ActivatedRoute, Router, Params } from '@angular/router';
 | 
			
		||||
import { from, Observable, Subscription, BehaviorSubject } from 'rxjs';
 | 
			
		||||
import { debounceTime, distinctUntilChanged, map, switchMap, first } from 'rxjs/operators';
 | 
			
		||||
import { PaperlessDocument } from 'src/app/data/paperless-document';
 | 
			
		||||
import { OpenDocumentsService } from 'src/app/services/open-documents.service';
 | 
			
		||||
import { SavedViewService } from 'src/app/services/rest/saved-view.service';
 | 
			
		||||
import { SearchService } from 'src/app/services/rest/search.service';
 | 
			
		||||
import { environment } from 'src/environments/environment';
 | 
			
		||||
import { DocumentDetailComponent } from '../document-detail/document-detail.component';
 | 
			
		||||
import { Meta } from '@angular/platform-browser';
 | 
			
		||||
import { DocumentListViewService } from 'src/app/services/document-list-view.service';
 | 
			
		||||
import { FILTER_FULLTEXT_QUERY } from 'src/app/data/filter-rule-type';
 | 
			
		||||
import { Component } from '@angular/core'
 | 
			
		||||
import { FormControl } from '@angular/forms'
 | 
			
		||||
import { ActivatedRoute, Router, Params } from '@angular/router'
 | 
			
		||||
import { from, Observable, Subscription, BehaviorSubject } from 'rxjs'
 | 
			
		||||
import {
 | 
			
		||||
  debounceTime,
 | 
			
		||||
  distinctUntilChanged,
 | 
			
		||||
  map,
 | 
			
		||||
  switchMap,
 | 
			
		||||
  first,
 | 
			
		||||
} from 'rxjs/operators'
 | 
			
		||||
import { PaperlessDocument } from 'src/app/data/paperless-document'
 | 
			
		||||
import { OpenDocumentsService } from 'src/app/services/open-documents.service'
 | 
			
		||||
import { SavedViewService } from 'src/app/services/rest/saved-view.service'
 | 
			
		||||
import { SearchService } from 'src/app/services/rest/search.service'
 | 
			
		||||
import { environment } from 'src/environments/environment'
 | 
			
		||||
import { DocumentDetailComponent } from '../document-detail/document-detail.component'
 | 
			
		||||
import { Meta } from '@angular/platform-browser'
 | 
			
		||||
import { DocumentListViewService } from 'src/app/services/document-list-view.service'
 | 
			
		||||
import { FILTER_FULLTEXT_QUERY } from 'src/app/data/filter-rule-type'
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'app-app-frame',
 | 
			
		||||
  templateUrl: './app-frame.component.html',
 | 
			
		||||
  styleUrls: ['./app-frame.component.scss']
 | 
			
		||||
  styleUrls: ['./app-frame.component.scss'],
 | 
			
		||||
})
 | 
			
		||||
export class AppFrameComponent {
 | 
			
		||||
 | 
			
		||||
  constructor (
 | 
			
		||||
  constructor(
 | 
			
		||||
    public router: Router,
 | 
			
		||||
    private activatedRoute: ActivatedRoute,
 | 
			
		||||
    private openDocumentsService: OpenDocumentsService,
 | 
			
		||||
@@ -28,7 +33,7 @@ export class AppFrameComponent {
 | 
			
		||||
    public savedViewService: SavedViewService,
 | 
			
		||||
    private list: DocumentListViewService,
 | 
			
		||||
    private meta: Meta
 | 
			
		||||
    ) { }
 | 
			
		||||
  ) {}
 | 
			
		||||
 | 
			
		||||
  versionString = `${environment.appTitle} ${environment.version}`
 | 
			
		||||
 | 
			
		||||
@@ -48,14 +53,14 @@ export class AppFrameComponent {
 | 
			
		||||
    text$.pipe(
 | 
			
		||||
      debounceTime(200),
 | 
			
		||||
      distinctUntilChanged(),
 | 
			
		||||
      map(term => {
 | 
			
		||||
      map((term) => {
 | 
			
		||||
        if (term.lastIndexOf(' ') != -1) {
 | 
			
		||||
          return term.substring(term.lastIndexOf(' ') + 1)
 | 
			
		||||
        } else {
 | 
			
		||||
          return term
 | 
			
		||||
        }
 | 
			
		||||
      }),
 | 
			
		||||
      switchMap(term =>
 | 
			
		||||
      switchMap((term) =>
 | 
			
		||||
        term.length < 2 ? from([[]]) : this.searchService.autocomplete(term)
 | 
			
		||||
      )
 | 
			
		||||
    )
 | 
			
		||||
@@ -66,49 +71,60 @@ export class AppFrameComponent {
 | 
			
		||||
    let lastSpaceIndex = currentSearch.lastIndexOf(' ')
 | 
			
		||||
    if (lastSpaceIndex != -1) {
 | 
			
		||||
      currentSearch = currentSearch.substring(0, lastSpaceIndex + 1)
 | 
			
		||||
      currentSearch += event.item + " "
 | 
			
		||||
      currentSearch += event.item + ' '
 | 
			
		||||
    } else {
 | 
			
		||||
      currentSearch = event.item + " "
 | 
			
		||||
      currentSearch = event.item + ' '
 | 
			
		||||
    }
 | 
			
		||||
    this.searchField.patchValue(currentSearch)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  search() {
 | 
			
		||||
    this.closeMenu()
 | 
			
		||||
    this.list.quickFilter([{rule_type: FILTER_FULLTEXT_QUERY, value: this.searchField.value}])
 | 
			
		||||
    this.list.quickFilter([
 | 
			
		||||
      { rule_type: FILTER_FULLTEXT_QUERY, value: this.searchField.value },
 | 
			
		||||
    ])
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  closeDocument(d: PaperlessDocument) {
 | 
			
		||||
    this.openDocumentsService.closeDocument(d).pipe(first()).subscribe(confirmed => {
 | 
			
		||||
      if (confirmed) {
 | 
			
		||||
        this.closeMenu()
 | 
			
		||||
        let route = this.activatedRoute.snapshot
 | 
			
		||||
        while (route.firstChild) {
 | 
			
		||||
          route = route.firstChild
 | 
			
		||||
    this.openDocumentsService
 | 
			
		||||
      .closeDocument(d)
 | 
			
		||||
      .pipe(first())
 | 
			
		||||
      .subscribe((confirmed) => {
 | 
			
		||||
        if (confirmed) {
 | 
			
		||||
          this.closeMenu()
 | 
			
		||||
          let route = this.activatedRoute.snapshot
 | 
			
		||||
          while (route.firstChild) {
 | 
			
		||||
            route = route.firstChild
 | 
			
		||||
          }
 | 
			
		||||
          if (
 | 
			
		||||
            route.component == DocumentDetailComponent &&
 | 
			
		||||
            route.params['id'] == d.id
 | 
			
		||||
          ) {
 | 
			
		||||
            this.router.navigate([''])
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
        if (route.component == DocumentDetailComponent && route.params['id'] == d.id) {
 | 
			
		||||
          this.router.navigate([""])
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    })
 | 
			
		||||
      })
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  closeAll() {
 | 
			
		||||
    // user may need to confirm losing unsaved changes
 | 
			
		||||
    this.openDocumentsService.closeAll().pipe(first()).subscribe(confirmed => {
 | 
			
		||||
      if (confirmed) {
 | 
			
		||||
        this.closeMenu()
 | 
			
		||||
    this.openDocumentsService
 | 
			
		||||
      .closeAll()
 | 
			
		||||
      .pipe(first())
 | 
			
		||||
      .subscribe((confirmed) => {
 | 
			
		||||
        if (confirmed) {
 | 
			
		||||
          this.closeMenu()
 | 
			
		||||
 | 
			
		||||
        // TODO: is there a better way to do this?
 | 
			
		||||
        let route = this.activatedRoute
 | 
			
		||||
        while (route.firstChild) {
 | 
			
		||||
          route = route.firstChild
 | 
			
		||||
          // TODO: is there a better way to do this?
 | 
			
		||||
          let route = this.activatedRoute
 | 
			
		||||
          while (route.firstChild) {
 | 
			
		||||
            route = route.firstChild
 | 
			
		||||
          }
 | 
			
		||||
          if (route.component === DocumentDetailComponent) {
 | 
			
		||||
            this.router.navigate([''])
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
        if (route.component === DocumentDetailComponent) {
 | 
			
		||||
          this.router.navigate([""])
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    })
 | 
			
		||||
      })
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  get displayName() {
 | 
			
		||||
@@ -123,5 +139,4 @@ export class AppFrameComponent {
 | 
			
		||||
      return null
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,25 +1,24 @@
 | 
			
		||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
 | 
			
		||||
import { ComponentFixture, TestBed } from '@angular/core/testing'
 | 
			
		||||
 | 
			
		||||
import { ConfirmDialogComponent } from './confirm-dialog.component';
 | 
			
		||||
import { ConfirmDialogComponent } from './confirm-dialog.component'
 | 
			
		||||
 | 
			
		||||
describe('ConfirmDialogComponent', () => {
 | 
			
		||||
  let component: ConfirmDialogComponent;
 | 
			
		||||
  let fixture: ComponentFixture<ConfirmDialogComponent>;
 | 
			
		||||
  let component: ConfirmDialogComponent
 | 
			
		||||
  let fixture: ComponentFixture<ConfirmDialogComponent>
 | 
			
		||||
 | 
			
		||||
  beforeEach(async () => {
 | 
			
		||||
    await TestBed.configureTestingModule({
 | 
			
		||||
      declarations: [ ConfirmDialogComponent ]
 | 
			
		||||
    })
 | 
			
		||||
    .compileComponents();
 | 
			
		||||
  });
 | 
			
		||||
      declarations: [ConfirmDialogComponent],
 | 
			
		||||
    }).compileComponents()
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  beforeEach(() => {
 | 
			
		||||
    fixture = TestBed.createComponent(ConfirmDialogComponent);
 | 
			
		||||
    component = fixture.componentInstance;
 | 
			
		||||
    fixture.detectChanges();
 | 
			
		||||
  });
 | 
			
		||||
    fixture = TestBed.createComponent(ConfirmDialogComponent)
 | 
			
		||||
    component = fixture.componentInstance
 | 
			
		||||
    fixture.detectChanges()
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  it('should create', () => {
 | 
			
		||||
    expect(component).toBeTruthy();
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
    expect(component).toBeTruthy()
 | 
			
		||||
  })
 | 
			
		||||
})
 | 
			
		||||
 
 | 
			
		||||
@@ -1,15 +1,14 @@
 | 
			
		||||
import { Component, EventEmitter, Input, Output } from '@angular/core';
 | 
			
		||||
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
 | 
			
		||||
import { Subject } from 'rxjs';
 | 
			
		||||
import { Component, EventEmitter, Input, Output } from '@angular/core'
 | 
			
		||||
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
 | 
			
		||||
import { Subject } from 'rxjs'
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'app-confirm-dialog',
 | 
			
		||||
  templateUrl: './confirm-dialog.component.html',
 | 
			
		||||
  styleUrls: ['./confirm-dialog.component.scss']
 | 
			
		||||
  styleUrls: ['./confirm-dialog.component.scss'],
 | 
			
		||||
})
 | 
			
		||||
export class ConfirmDialogComponent {
 | 
			
		||||
 | 
			
		||||
  constructor(public activeModal: NgbActiveModal) { }
 | 
			
		||||
  constructor(public activeModal: NgbActiveModal) {}
 | 
			
		||||
 | 
			
		||||
  @Output()
 | 
			
		||||
  public confirmClicked = new EventEmitter()
 | 
			
		||||
@@ -24,7 +23,7 @@ export class ConfirmDialogComponent {
 | 
			
		||||
  message
 | 
			
		||||
 | 
			
		||||
  @Input()
 | 
			
		||||
  btnClass = "btn-primary"
 | 
			
		||||
  btnClass = 'btn-primary'
 | 
			
		||||
 | 
			
		||||
  @Input()
 | 
			
		||||
  btnCaption = $localize`Confirm`
 | 
			
		||||
 
 | 
			
		||||
@@ -1,25 +1,24 @@
 | 
			
		||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
 | 
			
		||||
import { ComponentFixture, TestBed } from '@angular/core/testing'
 | 
			
		||||
 | 
			
		||||
import { DateDropdownComponent } from './date-dropdown.component';
 | 
			
		||||
import { DateDropdownComponent } from './date-dropdown.component'
 | 
			
		||||
 | 
			
		||||
describe('DateDropdownComponent', () => {
 | 
			
		||||
  let component: DateDropdownComponent;
 | 
			
		||||
  let fixture: ComponentFixture<DateDropdownComponent>;
 | 
			
		||||
  let component: DateDropdownComponent
 | 
			
		||||
  let fixture: ComponentFixture<DateDropdownComponent>
 | 
			
		||||
 | 
			
		||||
  beforeEach(async () => {
 | 
			
		||||
    await TestBed.configureTestingModule({
 | 
			
		||||
      declarations: [ DateDropdownComponent ]
 | 
			
		||||
    })
 | 
			
		||||
    .compileComponents();
 | 
			
		||||
  });
 | 
			
		||||
      declarations: [DateDropdownComponent],
 | 
			
		||||
    }).compileComponents()
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  beforeEach(() => {
 | 
			
		||||
    fixture = TestBed.createComponent(DateDropdownComponent);
 | 
			
		||||
    component = fixture.componentInstance;
 | 
			
		||||
    fixture.detectChanges();
 | 
			
		||||
  });
 | 
			
		||||
    fixture = TestBed.createComponent(DateDropdownComponent)
 | 
			
		||||
    component = fixture.componentInstance
 | 
			
		||||
    fixture.detectChanges()
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  it('should create', () => {
 | 
			
		||||
    expect(component).toBeTruthy();
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
    expect(component).toBeTruthy()
 | 
			
		||||
  })
 | 
			
		||||
})
 | 
			
		||||
 
 | 
			
		||||
@@ -1,10 +1,17 @@
 | 
			
		||||
import { formatDate } from '@angular/common';
 | 
			
		||||
import { Component, EventEmitter, Input, Output, OnInit, OnDestroy } from '@angular/core';
 | 
			
		||||
import { NgbDateAdapter } from '@ng-bootstrap/ng-bootstrap';
 | 
			
		||||
import { Subject, Subscription } from 'rxjs';
 | 
			
		||||
import { debounceTime } from 'rxjs/operators';
 | 
			
		||||
import { SettingsService } from 'src/app/services/settings.service';
 | 
			
		||||
import { ISODateAdapter } from 'src/app/utils/ngb-iso-date-adapter';
 | 
			
		||||
import { formatDate } from '@angular/common'
 | 
			
		||||
import {
 | 
			
		||||
  Component,
 | 
			
		||||
  EventEmitter,
 | 
			
		||||
  Input,
 | 
			
		||||
  Output,
 | 
			
		||||
  OnInit,
 | 
			
		||||
  OnDestroy,
 | 
			
		||||
} from '@angular/core'
 | 
			
		||||
import { NgbDateAdapter } from '@ng-bootstrap/ng-bootstrap'
 | 
			
		||||
import { Subject, Subscription } from 'rxjs'
 | 
			
		||||
import { debounceTime } from 'rxjs/operators'
 | 
			
		||||
import { SettingsService } from 'src/app/services/settings.service'
 | 
			
		||||
import { ISODateAdapter } from 'src/app/utils/ngb-iso-date-adapter'
 | 
			
		||||
 | 
			
		||||
export interface DateSelection {
 | 
			
		||||
  before?: string
 | 
			
		||||
@@ -20,21 +27,18 @@ const LAST_YEAR = 3
 | 
			
		||||
  selector: 'app-date-dropdown',
 | 
			
		||||
  templateUrl: './date-dropdown.component.html',
 | 
			
		||||
  styleUrls: ['./date-dropdown.component.scss'],
 | 
			
		||||
  providers: [
 | 
			
		||||
    {provide: NgbDateAdapter, useClass: ISODateAdapter},
 | 
			
		||||
  ]
 | 
			
		||||
  providers: [{ provide: NgbDateAdapter, useClass: ISODateAdapter }],
 | 
			
		||||
})
 | 
			
		||||
export class DateDropdownComponent implements OnInit, OnDestroy {
 | 
			
		||||
 | 
			
		||||
  constructor(settings: SettingsService) {
 | 
			
		||||
    this.datePlaceHolder = settings.getLocalizedDateInputFormat()
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  quickFilters = [
 | 
			
		||||
    {id: LAST_7_DAYS, name: $localize`Last 7 days`},
 | 
			
		||||
    {id: LAST_MONTH, name: $localize`Last month`},
 | 
			
		||||
    {id: LAST_3_MONTHS, name: $localize`Last 3 months`},
 | 
			
		||||
    {id: LAST_YEAR, name: $localize`Last year`}
 | 
			
		||||
    { id: LAST_7_DAYS, name: $localize`Last 7 days` },
 | 
			
		||||
    { id: LAST_MONTH, name: $localize`Last month` },
 | 
			
		||||
    { id: LAST_3_MONTHS, name: $localize`Last 3 months` },
 | 
			
		||||
    { id: LAST_YEAR, name: $localize`Last year` },
 | 
			
		||||
  ]
 | 
			
		||||
 | 
			
		||||
  datePlaceHolder: string
 | 
			
		||||
@@ -62,9 +66,7 @@ export class DateDropdownComponent implements OnInit, OnDestroy {
 | 
			
		||||
  private sub: Subscription
 | 
			
		||||
 | 
			
		||||
  ngOnInit() {
 | 
			
		||||
    this.sub = this.datesSetDebounce$.pipe(
 | 
			
		||||
      debounceTime(400)
 | 
			
		||||
    ).subscribe(() => {
 | 
			
		||||
    this.sub = this.datesSetDebounce$.pipe(debounceTime(400)).subscribe(() => {
 | 
			
		||||
      this.onChange()
 | 
			
		||||
    })
 | 
			
		||||
  }
 | 
			
		||||
@@ -81,11 +83,11 @@ export class DateDropdownComponent implements OnInit, OnDestroy {
 | 
			
		||||
    switch (qf) {
 | 
			
		||||
      case LAST_7_DAYS:
 | 
			
		||||
        date.setDate(date.getDate() - 7)
 | 
			
		||||
        break;
 | 
			
		||||
        break
 | 
			
		||||
 | 
			
		||||
      case LAST_MONTH:
 | 
			
		||||
        date.setMonth(date.getMonth() - 1)
 | 
			
		||||
        break;
 | 
			
		||||
        break
 | 
			
		||||
 | 
			
		||||
      case LAST_3_MONTHS:
 | 
			
		||||
        date.setMonth(date.getMonth() - 3)
 | 
			
		||||
@@ -94,20 +96,22 @@ export class DateDropdownComponent implements OnInit, OnDestroy {
 | 
			
		||||
      case LAST_YEAR:
 | 
			
		||||
        date.setFullYear(date.getFullYear() - 1)
 | 
			
		||||
        break
 | 
			
		||||
 | 
			
		||||
      }
 | 
			
		||||
    this.dateAfter = formatDate(date, 'yyyy-MM-dd', "en-us", "UTC")
 | 
			
		||||
    }
 | 
			
		||||
    this.dateAfter = formatDate(date, 'yyyy-MM-dd', 'en-us', 'UTC')
 | 
			
		||||
    this.onChange()
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  onChange() {
 | 
			
		||||
    this.dateAfterChange.emit(this.dateAfter)
 | 
			
		||||
    this.dateBeforeChange.emit(this.dateBefore)
 | 
			
		||||
    this.datesSet.emit({after: this.dateAfter, before: this.dateBefore})
 | 
			
		||||
    this.datesSet.emit({ after: this.dateAfter, before: this.dateBefore })
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  onChangeDebounce() {
 | 
			
		||||
    this.datesSetDebounce$.next({after: this.dateAfter, before: this.dateBefore})
 | 
			
		||||
    this.datesSetDebounce$.next({
 | 
			
		||||
      after: this.dateAfter,
 | 
			
		||||
      before: this.dateBefore,
 | 
			
		||||
    })
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  clearBefore() {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,25 +1,24 @@
 | 
			
		||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
 | 
			
		||||
import { ComponentFixture, TestBed } from '@angular/core/testing'
 | 
			
		||||
 | 
			
		||||
import { EditDialogComponent } from './edit-dialog.component';
 | 
			
		||||
import { EditDialogComponent } from './edit-dialog.component'
 | 
			
		||||
 | 
			
		||||
describe('EditDialogComponent', () => {
 | 
			
		||||
  let component: EditDialogComponent;
 | 
			
		||||
  let fixture: ComponentFixture<EditDialogComponent>;
 | 
			
		||||
  let component: EditDialogComponent
 | 
			
		||||
  let fixture: ComponentFixture<EditDialogComponent>
 | 
			
		||||
 | 
			
		||||
  beforeEach(async () => {
 | 
			
		||||
    await TestBed.configureTestingModule({
 | 
			
		||||
      declarations: [ EditDialogComponent ]
 | 
			
		||||
    })
 | 
			
		||||
    .compileComponents();
 | 
			
		||||
  });
 | 
			
		||||
      declarations: [EditDialogComponent],
 | 
			
		||||
    }).compileComponents()
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  beforeEach(() => {
 | 
			
		||||
    fixture = TestBed.createComponent(EditDialogComponent);
 | 
			
		||||
    component = fixture.componentInstance;
 | 
			
		||||
    fixture.detectChanges();
 | 
			
		||||
  });
 | 
			
		||||
    fixture = TestBed.createComponent(EditDialogComponent)
 | 
			
		||||
    component = fixture.componentInstance
 | 
			
		||||
    fixture.detectChanges()
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  it('should create', () => {
 | 
			
		||||
    expect(component).toBeTruthy();
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
    expect(component).toBeTruthy()
 | 
			
		||||
  })
 | 
			
		||||
})
 | 
			
		||||
 
 | 
			
		||||
@@ -1,20 +1,22 @@
 | 
			
		||||
import { Directive, EventEmitter, Input, OnInit, Output } from '@angular/core';
 | 
			
		||||
import { FormGroup } from '@angular/forms';
 | 
			
		||||
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
 | 
			
		||||
import { Observable } from 'rxjs';
 | 
			
		||||
import { map } from 'rxjs/operators';
 | 
			
		||||
import { MATCHING_ALGORITHMS, MATCH_AUTO } from 'src/app/data/matching-model';
 | 
			
		||||
import { ObjectWithId } from 'src/app/data/object-with-id';
 | 
			
		||||
import { AbstractPaperlessService } from 'src/app/services/rest/abstract-paperless-service';
 | 
			
		||||
import { ToastService } from 'src/app/services/toast.service';
 | 
			
		||||
import { Directive, EventEmitter, Input, OnInit, Output } from '@angular/core'
 | 
			
		||||
import { FormGroup } from '@angular/forms'
 | 
			
		||||
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
 | 
			
		||||
import { Observable } from 'rxjs'
 | 
			
		||||
import { map } from 'rxjs/operators'
 | 
			
		||||
import { MATCHING_ALGORITHMS, MATCH_AUTO } from 'src/app/data/matching-model'
 | 
			
		||||
import { ObjectWithId } from 'src/app/data/object-with-id'
 | 
			
		||||
import { AbstractPaperlessService } from 'src/app/services/rest/abstract-paperless-service'
 | 
			
		||||
import { ToastService } from 'src/app/services/toast.service'
 | 
			
		||||
 | 
			
		||||
@Directive()
 | 
			
		||||
export abstract class EditDialogComponent<T extends ObjectWithId> implements OnInit {
 | 
			
		||||
 | 
			
		||||
export abstract class EditDialogComponent<T extends ObjectWithId>
 | 
			
		||||
  implements OnInit
 | 
			
		||||
{
 | 
			
		||||
  constructor(
 | 
			
		||||
    private service: AbstractPaperlessService<T>,
 | 
			
		||||
    private activeModal: NgbActiveModal,
 | 
			
		||||
    private toastService: ToastService) { }
 | 
			
		||||
    private toastService: ToastService
 | 
			
		||||
  ) {}
 | 
			
		||||
 | 
			
		||||
  @Input()
 | 
			
		||||
  dialogMode: string = 'create'
 | 
			
		||||
@@ -43,7 +45,7 @@ export abstract class EditDialogComponent<T extends ObjectWithId> implements OnI
 | 
			
		||||
    // wait to enable close button so it doesnt steal focus from input since its the first clickable element in the DOM
 | 
			
		||||
    setTimeout(() => {
 | 
			
		||||
      this.closeEnabled = true
 | 
			
		||||
    });
 | 
			
		||||
    })
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  getCreateTitle() {
 | 
			
		||||
@@ -65,7 +67,7 @@ export abstract class EditDialogComponent<T extends ObjectWithId> implements OnI
 | 
			
		||||
      case 'edit':
 | 
			
		||||
        return this.getEditTitle()
 | 
			
		||||
      default:
 | 
			
		||||
        break;
 | 
			
		||||
        break
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@@ -78,25 +80,31 @@ export abstract class EditDialogComponent<T extends ObjectWithId> implements OnI
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  save() {
 | 
			
		||||
    var newObject = Object.assign(Object.assign({}, this.object), this.objectForm.value)
 | 
			
		||||
    var newObject = Object.assign(
 | 
			
		||||
      Object.assign({}, this.object),
 | 
			
		||||
      this.objectForm.value
 | 
			
		||||
    )
 | 
			
		||||
    var serverResponse: Observable<T>
 | 
			
		||||
    switch (this.dialogMode) {
 | 
			
		||||
      case 'create':
 | 
			
		||||
        serverResponse = this.service.create(newObject)
 | 
			
		||||
        break;
 | 
			
		||||
        break
 | 
			
		||||
      case 'edit':
 | 
			
		||||
        serverResponse = this.service.update(newObject)
 | 
			
		||||
      default:
 | 
			
		||||
        break;
 | 
			
		||||
        break
 | 
			
		||||
    }
 | 
			
		||||
    this.networkActive = true
 | 
			
		||||
    serverResponse.subscribe(result => {
 | 
			
		||||
      this.activeModal.close()
 | 
			
		||||
      this.success.emit(result)
 | 
			
		||||
    }, error => {
 | 
			
		||||
      this.error = error.error
 | 
			
		||||
      this.networkActive = false
 | 
			
		||||
    })
 | 
			
		||||
    serverResponse.subscribe(
 | 
			
		||||
      (result) => {
 | 
			
		||||
        this.activeModal.close()
 | 
			
		||||
        this.success.emit(result)
 | 
			
		||||
      },
 | 
			
		||||
      (error) => {
 | 
			
		||||
        this.error = error.error
 | 
			
		||||
        this.networkActive = false
 | 
			
		||||
      }
 | 
			
		||||
    )
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  cancel() {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,25 +1,24 @@
 | 
			
		||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
 | 
			
		||||
import { ComponentFixture, TestBed } from '@angular/core/testing'
 | 
			
		||||
 | 
			
		||||
import { FilterableDropodownComponent } from './filterable-dropdown.component';
 | 
			
		||||
import { FilterableDropodownComponent } from './filterable-dropdown.component'
 | 
			
		||||
 | 
			
		||||
describe('FilterableDropodownComponent', () => {
 | 
			
		||||
  let component: FilterableDropodownComponent;
 | 
			
		||||
  let fixture: ComponentFixture<FilterableDropodownComponent>;
 | 
			
		||||
  let component: FilterableDropodownComponent
 | 
			
		||||
  let fixture: ComponentFixture<FilterableDropodownComponent>
 | 
			
		||||
 | 
			
		||||
  beforeEach(async () => {
 | 
			
		||||
    await TestBed.configureTestingModule({
 | 
			
		||||
      declarations: [ FilterableDropodownComponent ]
 | 
			
		||||
    })
 | 
			
		||||
    .compileComponents();
 | 
			
		||||
  });
 | 
			
		||||
      declarations: [FilterableDropodownComponent],
 | 
			
		||||
    }).compileComponents()
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  beforeEach(() => {
 | 
			
		||||
    fixture = TestBed.createComponent(FilterableDropodownComponent);
 | 
			
		||||
    component = fixture.componentInstance;
 | 
			
		||||
    fixture.detectChanges();
 | 
			
		||||
  });
 | 
			
		||||
    fixture = TestBed.createComponent(FilterableDropodownComponent)
 | 
			
		||||
    component = fixture.componentInstance
 | 
			
		||||
    fixture.detectChanges()
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  it('should create', () => {
 | 
			
		||||
    expect(component).toBeTruthy();
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
    expect(component).toBeTruthy()
 | 
			
		||||
  })
 | 
			
		||||
})
 | 
			
		||||
 
 | 
			
		||||
@@ -1,17 +1,23 @@
 | 
			
		||||
import { Component, EventEmitter, Input, Output, ElementRef, ViewChild } from '@angular/core';
 | 
			
		||||
import { FilterPipe } from  'src/app/pipes/filter.pipe';
 | 
			
		||||
import {
 | 
			
		||||
  Component,
 | 
			
		||||
  EventEmitter,
 | 
			
		||||
  Input,
 | 
			
		||||
  Output,
 | 
			
		||||
  ElementRef,
 | 
			
		||||
  ViewChild,
 | 
			
		||||
} from '@angular/core'
 | 
			
		||||
import { FilterPipe } from 'src/app/pipes/filter.pipe'
 | 
			
		||||
import { NgbDropdown } from '@ng-bootstrap/ng-bootstrap'
 | 
			
		||||
import { ToggleableItemState } from './toggleable-dropdown-button/toggleable-dropdown-button.component';
 | 
			
		||||
import { MatchingModel } from 'src/app/data/matching-model';
 | 
			
		||||
import { Subject } from 'rxjs';
 | 
			
		||||
import { ToggleableItemState } from './toggleable-dropdown-button/toggleable-dropdown-button.component'
 | 
			
		||||
import { MatchingModel } from 'src/app/data/matching-model'
 | 
			
		||||
import { Subject } from 'rxjs'
 | 
			
		||||
 | 
			
		||||
export interface ChangedItems {
 | 
			
		||||
  itemsToAdd: MatchingModel[],
 | 
			
		||||
  itemsToAdd: MatchingModel[]
 | 
			
		||||
  itemsToRemove: MatchingModel[]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class FilterableDropdownSelectionModel {
 | 
			
		||||
 | 
			
		||||
  changed = new Subject<FilterableDropdownSelectionModel>()
 | 
			
		||||
 | 
			
		||||
  multiple = false
 | 
			
		||||
@@ -22,14 +28,20 @@ export class FilterableDropdownSelectionModel {
 | 
			
		||||
 | 
			
		||||
  get itemsSorted(): MatchingModel[] {
 | 
			
		||||
    // TODO: this is getting called very often
 | 
			
		||||
    return this.items.sort((a,b) => {
 | 
			
		||||
    return this.items.sort((a, b) => {
 | 
			
		||||
      if (a.id == null && b.id != null) {
 | 
			
		||||
        return -1
 | 
			
		||||
      } else if (a.id != null && b.id == null) {
 | 
			
		||||
        return 1
 | 
			
		||||
      } else if (this.getNonTemporary(a.id) == ToggleableItemState.NotSelected && this.getNonTemporary(b.id) != ToggleableItemState.NotSelected) {
 | 
			
		||||
      } else if (
 | 
			
		||||
        this.getNonTemporary(a.id) == ToggleableItemState.NotSelected &&
 | 
			
		||||
        this.getNonTemporary(b.id) != ToggleableItemState.NotSelected
 | 
			
		||||
      ) {
 | 
			
		||||
        return 1
 | 
			
		||||
      } else if (this.getNonTemporary(a.id) != ToggleableItemState.NotSelected && this.getNonTemporary(b.id) == ToggleableItemState.NotSelected) {
 | 
			
		||||
      } else if (
 | 
			
		||||
        this.getNonTemporary(a.id) != ToggleableItemState.NotSelected &&
 | 
			
		||||
        this.getNonTemporary(b.id) == ToggleableItemState.NotSelected
 | 
			
		||||
      ) {
 | 
			
		||||
        return -1
 | 
			
		||||
      } else {
 | 
			
		||||
        return a.name.localeCompare(b.name)
 | 
			
		||||
@@ -42,11 +54,17 @@ export class FilterableDropdownSelectionModel {
 | 
			
		||||
  private temporarySelectionStates = new Map<number, ToggleableItemState>()
 | 
			
		||||
 | 
			
		||||
  getSelectedItems() {
 | 
			
		||||
    return this.items.filter(i => this.temporarySelectionStates.get(i.id) == ToggleableItemState.Selected)
 | 
			
		||||
    return this.items.filter(
 | 
			
		||||
      (i) =>
 | 
			
		||||
        this.temporarySelectionStates.get(i.id) == ToggleableItemState.Selected
 | 
			
		||||
    )
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  getExcludedItems() {
 | 
			
		||||
    return this.items.filter(i => this.temporarySelectionStates.get(i.id) == ToggleableItemState.Excluded)
 | 
			
		||||
    return this.items.filter(
 | 
			
		||||
      (i) =>
 | 
			
		||||
        this.temporarySelectionStates.get(i.id) == ToggleableItemState.Excluded
 | 
			
		||||
    )
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  set(id: number, state: ToggleableItemState, fireEvent = true) {
 | 
			
		||||
@@ -62,9 +80,16 @@ export class FilterableDropdownSelectionModel {
 | 
			
		||||
 | 
			
		||||
  toggle(id: number, fireEvent = true) {
 | 
			
		||||
    let state = this.temporarySelectionStates.get(id)
 | 
			
		||||
    if (state == null || (state != ToggleableItemState.Selected && state != ToggleableItemState.Excluded)) {
 | 
			
		||||
    if (
 | 
			
		||||
      state == null ||
 | 
			
		||||
      (state != ToggleableItemState.Selected &&
 | 
			
		||||
        state != ToggleableItemState.Excluded)
 | 
			
		||||
    ) {
 | 
			
		||||
      this.temporarySelectionStates.set(id, ToggleableItemState.Selected)
 | 
			
		||||
    } else if (state == ToggleableItemState.Selected || state == ToggleableItemState.Excluded) {
 | 
			
		||||
    } else if (
 | 
			
		||||
      state == ToggleableItemState.Selected ||
 | 
			
		||||
      state == ToggleableItemState.Excluded
 | 
			
		||||
    ) {
 | 
			
		||||
      this.temporarySelectionStates.delete(id)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -91,7 +116,7 @@ export class FilterableDropdownSelectionModel {
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  exclude(id: number, fireEvent:boolean = true) {
 | 
			
		||||
  exclude(id: number, fireEvent: boolean = true) {
 | 
			
		||||
    let state = this.temporarySelectionStates.get(id)
 | 
			
		||||
    if (state == null || state != ToggleableItemState.Excluded) {
 | 
			
		||||
      this.temporarySelectionStates.set(id, ToggleableItemState.Excluded)
 | 
			
		||||
@@ -130,7 +155,9 @@ export class FilterableDropdownSelectionModel {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  get(id: number) {
 | 
			
		||||
    return this.temporarySelectionStates.get(id) || ToggleableItemState.NotSelected
 | 
			
		||||
    return (
 | 
			
		||||
      this.temporarySelectionStates.get(id) || ToggleableItemState.NotSelected
 | 
			
		||||
    )
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  selectionSize() {
 | 
			
		||||
@@ -150,9 +177,19 @@ export class FilterableDropdownSelectionModel {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  isDirty() {
 | 
			
		||||
    if (!Array.from(this.temporarySelectionStates.keys()).every(id => this.temporarySelectionStates.get(id) == this.selectionStates.get(id))) {
 | 
			
		||||
    if (
 | 
			
		||||
      !Array.from(this.temporarySelectionStates.keys()).every(
 | 
			
		||||
        (id) =>
 | 
			
		||||
          this.temporarySelectionStates.get(id) == this.selectionStates.get(id)
 | 
			
		||||
      )
 | 
			
		||||
    ) {
 | 
			
		||||
      return true
 | 
			
		||||
    } else if (!Array.from(this.selectionStates.keys()).every(id => this.selectionStates.get(id) == this.temporarySelectionStates.get(id))) {
 | 
			
		||||
    } else if (
 | 
			
		||||
      !Array.from(this.selectionStates.keys()).every(
 | 
			
		||||
        (id) =>
 | 
			
		||||
          this.selectionStates.get(id) == this.temporarySelectionStates.get(id)
 | 
			
		||||
      )
 | 
			
		||||
    ) {
 | 
			
		||||
      return true
 | 
			
		||||
    } else if (this.temporaryLogicalOperator !== this._logicalOperator) {
 | 
			
		||||
      return true
 | 
			
		||||
@@ -162,7 +199,10 @@ export class FilterableDropdownSelectionModel {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  isNoneSelected() {
 | 
			
		||||
    return this.selectionSize() == 1 && this.get(null) == ToggleableItemState.Selected
 | 
			
		||||
    return (
 | 
			
		||||
      this.selectionSize() == 1 &&
 | 
			
		||||
      this.get(null) == ToggleableItemState.Selected
 | 
			
		||||
    )
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  init(map) {
 | 
			
		||||
@@ -187,8 +227,17 @@ export class FilterableDropdownSelectionModel {
 | 
			
		||||
 | 
			
		||||
  diff(): ChangedItems {
 | 
			
		||||
    return {
 | 
			
		||||
      itemsToAdd: this.items.filter(item => this.temporarySelectionStates.get(item.id) == ToggleableItemState.Selected && this.selectionStates.get(item.id) != ToggleableItemState.Selected),
 | 
			
		||||
      itemsToRemove: this.items.filter(item => !this.temporarySelectionStates.has(item.id) && this.selectionStates.has(item.id)),
 | 
			
		||||
      itemsToAdd: this.items.filter(
 | 
			
		||||
        (item) =>
 | 
			
		||||
          this.temporarySelectionStates.get(item.id) ==
 | 
			
		||||
            ToggleableItemState.Selected &&
 | 
			
		||||
          this.selectionStates.get(item.id) != ToggleableItemState.Selected
 | 
			
		||||
      ),
 | 
			
		||||
      itemsToRemove: this.items.filter(
 | 
			
		||||
        (item) =>
 | 
			
		||||
          !this.temporarySelectionStates.has(item.id) &&
 | 
			
		||||
          this.selectionStates.has(item.id)
 | 
			
		||||
      ),
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -196,10 +245,9 @@ export class FilterableDropdownSelectionModel {
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'app-filterable-dropdown',
 | 
			
		||||
  templateUrl: './filterable-dropdown.component.html',
 | 
			
		||||
  styleUrls: ['./filterable-dropdown.component.scss']
 | 
			
		||||
  styleUrls: ['./filterable-dropdown.component.scss'],
 | 
			
		||||
})
 | 
			
		||||
export class FilterableDropdownComponent {
 | 
			
		||||
 | 
			
		||||
  @ViewChild('listFilterTextInput') listFilterTextInput: ElementRef
 | 
			
		||||
  @ViewChild('dropdown') dropdown: NgbDropdown
 | 
			
		||||
 | 
			
		||||
@@ -211,7 +259,7 @@ export class FilterableDropdownComponent {
 | 
			
		||||
      this._selectionModel.items = Array.from(items)
 | 
			
		||||
      this._selectionModel.items.unshift({
 | 
			
		||||
        name: $localize`:Filter drop down element to filter for documents with no correspondent/type/tag assigned:Not assigned`,
 | 
			
		||||
        id: null
 | 
			
		||||
        id: null,
 | 
			
		||||
      })
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
@@ -229,7 +277,7 @@ export class FilterableDropdownComponent {
 | 
			
		||||
      model.items = this.selectionModel.items
 | 
			
		||||
      model.multiple = this.selectionModel.multiple
 | 
			
		||||
    }
 | 
			
		||||
    model.changed.subscribe(updatedModel => {
 | 
			
		||||
    model.changed.subscribe((updatedModel) => {
 | 
			
		||||
      this.selectionModelChange.next(updatedModel)
 | 
			
		||||
    })
 | 
			
		||||
    this._selectionModel = model
 | 
			
		||||
@@ -255,7 +303,7 @@ export class FilterableDropdownComponent {
 | 
			
		||||
  title: string
 | 
			
		||||
 | 
			
		||||
  @Input()
 | 
			
		||||
  filterPlaceholder: string = ""
 | 
			
		||||
  filterPlaceholder: string = ''
 | 
			
		||||
 | 
			
		||||
  @Input()
 | 
			
		||||
  icon: string
 | 
			
		||||
@@ -276,14 +324,17 @@ export class FilterableDropdownComponent {
 | 
			
		||||
  open = new EventEmitter()
 | 
			
		||||
 | 
			
		||||
  get operatorToggleEnabled(): boolean {
 | 
			
		||||
    return this.selectionModel.selectionSize() > 1 && this.selectionModel.getExcludedItems().length == 0
 | 
			
		||||
    return (
 | 
			
		||||
      this.selectionModel.selectionSize() > 1 &&
 | 
			
		||||
      this.selectionModel.getExcludedItems().length == 0
 | 
			
		||||
    )
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  modelIsDirty: boolean = false
 | 
			
		||||
 | 
			
		||||
  constructor(private filterPipe: FilterPipe) {
 | 
			
		||||
    this.selectionModel = new FilterableDropdownSelectionModel()
 | 
			
		||||
    this.selectionModelChange.subscribe(updatedModel => {
 | 
			
		||||
    this.selectionModelChange.subscribe((updatedModel) => {
 | 
			
		||||
      this.modelIsDirty = updatedModel.isDirty()
 | 
			
		||||
    })
 | 
			
		||||
  }
 | 
			
		||||
@@ -300,7 +351,7 @@ export class FilterableDropdownComponent {
 | 
			
		||||
  dropdownOpenChange(open: boolean): void {
 | 
			
		||||
    if (open) {
 | 
			
		||||
      setTimeout(() => {
 | 
			
		||||
        this.listFilterTextInput.nativeElement.focus();
 | 
			
		||||
        this.listFilterTextInput.nativeElement.focus()
 | 
			
		||||
      }, 0)
 | 
			
		||||
      if (this.editing) {
 | 
			
		||||
        this.selectionModel.reset()
 | 
			
		||||
 
 | 
			
		||||
@@ -1,25 +1,24 @@
 | 
			
		||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
 | 
			
		||||
import { ComponentFixture, TestBed } from '@angular/core/testing'
 | 
			
		||||
 | 
			
		||||
import { ToggleableDropdownButtonComponent } from './toggleable-dropdown-button.component';
 | 
			
		||||
import { ToggleableDropdownButtonComponent } from './toggleable-dropdown-button.component'
 | 
			
		||||
 | 
			
		||||
describe('ToggleableDropdownButtonComponent', () => {
 | 
			
		||||
  let component: ToggleableDropdownButtonComponent;
 | 
			
		||||
  let fixture: ComponentFixture<ToggleableDropdownButtonComponent>;
 | 
			
		||||
  let component: ToggleableDropdownButtonComponent
 | 
			
		||||
  let fixture: ComponentFixture<ToggleableDropdownButtonComponent>
 | 
			
		||||
 | 
			
		||||
  beforeEach(async () => {
 | 
			
		||||
    await TestBed.configureTestingModule({
 | 
			
		||||
      declarations: [ ToggleableDropdownButtonComponent ]
 | 
			
		||||
    })
 | 
			
		||||
    .compileComponents();
 | 
			
		||||
  });
 | 
			
		||||
      declarations: [ToggleableDropdownButtonComponent],
 | 
			
		||||
    }).compileComponents()
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  beforeEach(() => {
 | 
			
		||||
    fixture = TestBed.createComponent(ToggleableDropdownButtonComponent);
 | 
			
		||||
    component = fixture.componentInstance;
 | 
			
		||||
    fixture.detectChanges();
 | 
			
		||||
  });
 | 
			
		||||
    fixture = TestBed.createComponent(ToggleableDropdownButtonComponent)
 | 
			
		||||
    component = fixture.componentInstance
 | 
			
		||||
    fixture.detectChanges()
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  it('should create', () => {
 | 
			
		||||
    expect(component).toBeTruthy();
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
    expect(component).toBeTruthy()
 | 
			
		||||
  })
 | 
			
		||||
})
 | 
			
		||||
 
 | 
			
		||||
@@ -1,20 +1,19 @@
 | 
			
		||||
import { Component, EventEmitter, Input, Output, OnInit } from '@angular/core';
 | 
			
		||||
import { MatchingModel } from 'src/app/data/matching-model';
 | 
			
		||||
import { Component, EventEmitter, Input, Output, OnInit } from '@angular/core'
 | 
			
		||||
import { MatchingModel } from 'src/app/data/matching-model'
 | 
			
		||||
 | 
			
		||||
export enum ToggleableItemState {
 | 
			
		||||
  NotSelected = 0,
 | 
			
		||||
  Selected = 1,
 | 
			
		||||
  PartiallySelected = 2,
 | 
			
		||||
  Excluded = 3
 | 
			
		||||
  Excluded = 3,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'app-toggleable-dropdown-button',
 | 
			
		||||
  templateUrl: './toggleable-dropdown-button.component.html',
 | 
			
		||||
  styleUrls: ['./toggleable-dropdown-button.component.scss']
 | 
			
		||||
  styleUrls: ['./toggleable-dropdown-button.component.scss'],
 | 
			
		||||
})
 | 
			
		||||
export class ToggleableDropdownButtonComponent {
 | 
			
		||||
 | 
			
		||||
  @Input()
 | 
			
		||||
  item: MatchingModel
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,30 +1,29 @@
 | 
			
		||||
import { Directive, ElementRef, Input, OnInit, ViewChild } from '@angular/core';
 | 
			
		||||
import { ControlValueAccessor } from '@angular/forms';
 | 
			
		||||
import { v4 as uuidv4 } from 'uuid';
 | 
			
		||||
import { Directive, ElementRef, Input, OnInit, ViewChild } from '@angular/core'
 | 
			
		||||
import { ControlValueAccessor } from '@angular/forms'
 | 
			
		||||
import { v4 as uuidv4 } from 'uuid'
 | 
			
		||||
 | 
			
		||||
@Directive()
 | 
			
		||||
export class AbstractInputComponent<T> implements OnInit, ControlValueAccessor {
 | 
			
		||||
 | 
			
		||||
  @ViewChild("inputField")
 | 
			
		||||
  @ViewChild('inputField')
 | 
			
		||||
  inputField: ElementRef
 | 
			
		||||
 | 
			
		||||
  constructor() { }
 | 
			
		||||
  constructor() {}
 | 
			
		||||
 | 
			
		||||
  onChange = (newValue: T) => {};
 | 
			
		||||
  onChange = (newValue: T) => {}
 | 
			
		||||
 | 
			
		||||
  onTouched = () => {};
 | 
			
		||||
  onTouched = () => {}
 | 
			
		||||
 | 
			
		||||
  writeValue(newValue: any): void {
 | 
			
		||||
    this.value = newValue
 | 
			
		||||
  }
 | 
			
		||||
  registerOnChange(fn: any): void {
 | 
			
		||||
    this.onChange = fn;
 | 
			
		||||
    this.onChange = fn
 | 
			
		||||
  }
 | 
			
		||||
  registerOnTouched(fn: any): void {
 | 
			
		||||
    this.onTouched = fn;
 | 
			
		||||
    this.onTouched = fn
 | 
			
		||||
  }
 | 
			
		||||
  setDisabledState?(isDisabled: boolean): void {
 | 
			
		||||
    this.disabled = isDisabled;
 | 
			
		||||
    this.disabled = isDisabled
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  focus() {
 | 
			
		||||
@@ -37,7 +36,7 @@ export class AbstractInputComponent<T> implements OnInit, ControlValueAccessor {
 | 
			
		||||
  title: string
 | 
			
		||||
 | 
			
		||||
  @Input()
 | 
			
		||||
  disabled = false;
 | 
			
		||||
  disabled = false
 | 
			
		||||
 | 
			
		||||
  @Input()
 | 
			
		||||
  error: string
 | 
			
		||||
 
 | 
			
		||||
@@ -1,25 +1,24 @@
 | 
			
		||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
 | 
			
		||||
import { ComponentFixture, TestBed } from '@angular/core/testing'
 | 
			
		||||
 | 
			
		||||
import { CheckComponent } from './check.component';
 | 
			
		||||
import { CheckComponent } from './check.component'
 | 
			
		||||
 | 
			
		||||
describe('CheckComponent', () => {
 | 
			
		||||
  let component: CheckComponent;
 | 
			
		||||
  let fixture: ComponentFixture<CheckComponent>;
 | 
			
		||||
  let component: CheckComponent
 | 
			
		||||
  let fixture: ComponentFixture<CheckComponent>
 | 
			
		||||
 | 
			
		||||
  beforeEach(async () => {
 | 
			
		||||
    await TestBed.configureTestingModule({
 | 
			
		||||
      declarations: [ CheckComponent ]
 | 
			
		||||
    })
 | 
			
		||||
    .compileComponents();
 | 
			
		||||
  });
 | 
			
		||||
      declarations: [CheckComponent],
 | 
			
		||||
    }).compileComponents()
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  beforeEach(() => {
 | 
			
		||||
    fixture = TestBed.createComponent(CheckComponent);
 | 
			
		||||
    component = fixture.componentInstance;
 | 
			
		||||
    fixture.detectChanges();
 | 
			
		||||
  });
 | 
			
		||||
    fixture = TestBed.createComponent(CheckComponent)
 | 
			
		||||
    component = fixture.componentInstance
 | 
			
		||||
    fixture.detectChanges()
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  it('should create', () => {
 | 
			
		||||
    expect(component).toBeTruthy();
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
    expect(component).toBeTruthy()
 | 
			
		||||
  })
 | 
			
		||||
})
 | 
			
		||||
 
 | 
			
		||||
@@ -1,22 +1,22 @@
 | 
			
		||||
import { Component, forwardRef, Input, OnInit } from '@angular/core';
 | 
			
		||||
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
 | 
			
		||||
import { v4 as uuidv4 } from 'uuid';
 | 
			
		||||
import { AbstractInputComponent } from '../abstract-input';
 | 
			
		||||
import { Component, forwardRef, Input, OnInit } from '@angular/core'
 | 
			
		||||
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'
 | 
			
		||||
import { v4 as uuidv4 } from 'uuid'
 | 
			
		||||
import { AbstractInputComponent } from '../abstract-input'
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  providers: [{
 | 
			
		||||
    provide: NG_VALUE_ACCESSOR,
 | 
			
		||||
    useExisting: forwardRef(() => CheckComponent),
 | 
			
		||||
    multi: true
 | 
			
		||||
  }],
 | 
			
		||||
  providers: [
 | 
			
		||||
    {
 | 
			
		||||
      provide: NG_VALUE_ACCESSOR,
 | 
			
		||||
      useExisting: forwardRef(() => CheckComponent),
 | 
			
		||||
      multi: true,
 | 
			
		||||
    },
 | 
			
		||||
  ],
 | 
			
		||||
  selector: 'app-input-check',
 | 
			
		||||
  templateUrl: './check.component.html',
 | 
			
		||||
  styleUrls: ['./check.component.scss']
 | 
			
		||||
  styleUrls: ['./check.component.scss'],
 | 
			
		||||
})
 | 
			
		||||
export class CheckComponent extends AbstractInputComponent<boolean> {
 | 
			
		||||
 | 
			
		||||
  constructor() {
 | 
			
		||||
    super()
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,25 +1,24 @@
 | 
			
		||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
 | 
			
		||||
import { ComponentFixture, TestBed } from '@angular/core/testing'
 | 
			
		||||
 | 
			
		||||
import { ColorComponent } from './color.component';
 | 
			
		||||
import { ColorComponent } from './color.component'
 | 
			
		||||
 | 
			
		||||
describe('ColorComponent', () => {
 | 
			
		||||
  let component: ColorComponent;
 | 
			
		||||
  let fixture: ComponentFixture<ColorComponent>;
 | 
			
		||||
  let component: ColorComponent
 | 
			
		||||
  let fixture: ComponentFixture<ColorComponent>
 | 
			
		||||
 | 
			
		||||
  beforeEach(async () => {
 | 
			
		||||
    await TestBed.configureTestingModule({
 | 
			
		||||
      declarations: [ ColorComponent ]
 | 
			
		||||
    })
 | 
			
		||||
    .compileComponents();
 | 
			
		||||
  });
 | 
			
		||||
      declarations: [ColorComponent],
 | 
			
		||||
    }).compileComponents()
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  beforeEach(() => {
 | 
			
		||||
    fixture = TestBed.createComponent(ColorComponent);
 | 
			
		||||
    component = fixture.componentInstance;
 | 
			
		||||
    fixture.detectChanges();
 | 
			
		||||
  });
 | 
			
		||||
    fixture = TestBed.createComponent(ColorComponent)
 | 
			
		||||
    component = fixture.componentInstance
 | 
			
		||||
    fixture.detectChanges()
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  it('should create', () => {
 | 
			
		||||
    expect(component).toBeTruthy();
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
    expect(component).toBeTruthy()
 | 
			
		||||
  })
 | 
			
		||||
})
 | 
			
		||||
 
 | 
			
		||||
@@ -1,20 +1,21 @@
 | 
			
		||||
import { Component, forwardRef } from '@angular/core';
 | 
			
		||||
import { NG_VALUE_ACCESSOR } from '@angular/forms';
 | 
			
		||||
import { randomColor } from 'src/app/utils/color';
 | 
			
		||||
import { AbstractInputComponent } from '../abstract-input';
 | 
			
		||||
import { Component, forwardRef } from '@angular/core'
 | 
			
		||||
import { NG_VALUE_ACCESSOR } from '@angular/forms'
 | 
			
		||||
import { randomColor } from 'src/app/utils/color'
 | 
			
		||||
import { AbstractInputComponent } from '../abstract-input'
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  providers: [{
 | 
			
		||||
    provide: NG_VALUE_ACCESSOR,
 | 
			
		||||
    useExisting: forwardRef(() => ColorComponent),
 | 
			
		||||
    multi: true
 | 
			
		||||
  }],
 | 
			
		||||
  providers: [
 | 
			
		||||
    {
 | 
			
		||||
      provide: NG_VALUE_ACCESSOR,
 | 
			
		||||
      useExisting: forwardRef(() => ColorComponent),
 | 
			
		||||
      multi: true,
 | 
			
		||||
    },
 | 
			
		||||
  ],
 | 
			
		||||
  selector: 'app-input-color',
 | 
			
		||||
  templateUrl: './color.component.html',
 | 
			
		||||
  styleUrls: ['./color.component.scss']
 | 
			
		||||
  styleUrls: ['./color.component.scss'],
 | 
			
		||||
})
 | 
			
		||||
export class ColorComponent extends AbstractInputComponent<string> {
 | 
			
		||||
 | 
			
		||||
  constructor() {
 | 
			
		||||
    super()
 | 
			
		||||
  }
 | 
			
		||||
 
 | 
			
		||||
@@ -1,25 +1,24 @@
 | 
			
		||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
 | 
			
		||||
import { ComponentFixture, TestBed } from '@angular/core/testing'
 | 
			
		||||
 | 
			
		||||
import { DateComponent } from './date.component';
 | 
			
		||||
import { DateComponent } from './date.component'
 | 
			
		||||
 | 
			
		||||
describe('DateComponent', () => {
 | 
			
		||||
  let component: DateComponent;
 | 
			
		||||
  let fixture: ComponentFixture<DateComponent>;
 | 
			
		||||
  let component: DateComponent
 | 
			
		||||
  let fixture: ComponentFixture<DateComponent>
 | 
			
		||||
 | 
			
		||||
  beforeEach(async () => {
 | 
			
		||||
    await TestBed.configureTestingModule({
 | 
			
		||||
      declarations: [ DateComponent ]
 | 
			
		||||
    })
 | 
			
		||||
    .compileComponents();
 | 
			
		||||
  });
 | 
			
		||||
      declarations: [DateComponent],
 | 
			
		||||
    }).compileComponents()
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  beforeEach(() => {
 | 
			
		||||
    fixture = TestBed.createComponent(DateComponent);
 | 
			
		||||
    component = fixture.componentInstance;
 | 
			
		||||
    fixture.detectChanges();
 | 
			
		||||
  });
 | 
			
		||||
    fixture = TestBed.createComponent(DateComponent)
 | 
			
		||||
    component = fixture.componentInstance
 | 
			
		||||
    fixture.detectChanges()
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  it('should create', () => {
 | 
			
		||||
    expect(component).toBeTruthy();
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
    expect(component).toBeTruthy()
 | 
			
		||||
  })
 | 
			
		||||
})
 | 
			
		||||
 
 | 
			
		||||
@@ -1,21 +1,24 @@
 | 
			
		||||
import { Component, forwardRef, OnInit } from '@angular/core';
 | 
			
		||||
import { NG_VALUE_ACCESSOR } from '@angular/forms';
 | 
			
		||||
import { SettingsService } from 'src/app/services/settings.service';
 | 
			
		||||
import { AbstractInputComponent } from '../abstract-input';
 | 
			
		||||
 | 
			
		||||
import { Component, forwardRef, OnInit } from '@angular/core'
 | 
			
		||||
import { NG_VALUE_ACCESSOR } from '@angular/forms'
 | 
			
		||||
import { SettingsService } from 'src/app/services/settings.service'
 | 
			
		||||
import { AbstractInputComponent } from '../abstract-input'
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  providers: [{
 | 
			
		||||
    provide: NG_VALUE_ACCESSOR,
 | 
			
		||||
    useExisting: forwardRef(() => DateComponent),
 | 
			
		||||
    multi: true
 | 
			
		||||
  }],
 | 
			
		||||
  providers: [
 | 
			
		||||
    {
 | 
			
		||||
      provide: NG_VALUE_ACCESSOR,
 | 
			
		||||
      useExisting: forwardRef(() => DateComponent),
 | 
			
		||||
      multi: true,
 | 
			
		||||
    },
 | 
			
		||||
  ],
 | 
			
		||||
  selector: 'app-input-date',
 | 
			
		||||
  templateUrl: './date.component.html',
 | 
			
		||||
  styleUrls: ['./date.component.scss']
 | 
			
		||||
  styleUrls: ['./date.component.scss'],
 | 
			
		||||
})
 | 
			
		||||
export class DateComponent extends AbstractInputComponent<string> implements OnInit {
 | 
			
		||||
 | 
			
		||||
export class DateComponent
 | 
			
		||||
  extends AbstractInputComponent<string>
 | 
			
		||||
  implements OnInit
 | 
			
		||||
{
 | 
			
		||||
  constructor(private settings: SettingsService) {
 | 
			
		||||
    super()
 | 
			
		||||
  }
 | 
			
		||||
 
 | 
			
		||||
@@ -1,25 +1,24 @@
 | 
			
		||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
 | 
			
		||||
import { ComponentFixture, TestBed } from '@angular/core/testing'
 | 
			
		||||
 | 
			
		||||
import { NumberComponent } from './number.component';
 | 
			
		||||
import { NumberComponent } from './number.component'
 | 
			
		||||
 | 
			
		||||
describe('NumberComponent', () => {
 | 
			
		||||
  let component: NumberComponent;
 | 
			
		||||
  let fixture: ComponentFixture<NumberComponent>;
 | 
			
		||||
  let component: NumberComponent
 | 
			
		||||
  let fixture: ComponentFixture<NumberComponent>
 | 
			
		||||
 | 
			
		||||
  beforeEach(async () => {
 | 
			
		||||
    await TestBed.configureTestingModule({
 | 
			
		||||
      declarations: [ NumberComponent ]
 | 
			
		||||
    })
 | 
			
		||||
    .compileComponents();
 | 
			
		||||
  });
 | 
			
		||||
      declarations: [NumberComponent],
 | 
			
		||||
    }).compileComponents()
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  beforeEach(() => {
 | 
			
		||||
    fixture = TestBed.createComponent(NumberComponent);
 | 
			
		||||
    component = fixture.componentInstance;
 | 
			
		||||
    fixture.detectChanges();
 | 
			
		||||
  });
 | 
			
		||||
    fixture = TestBed.createComponent(NumberComponent)
 | 
			
		||||
    component = fixture.componentInstance
 | 
			
		||||
    fixture.detectChanges()
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  it('should create', () => {
 | 
			
		||||
    expect(component).toBeTruthy();
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
    expect(component).toBeTruthy()
 | 
			
		||||
  })
 | 
			
		||||
})
 | 
			
		||||
 
 | 
			
		||||
@@ -1,21 +1,22 @@
 | 
			
		||||
import { Component, forwardRef } from '@angular/core';
 | 
			
		||||
import { NG_VALUE_ACCESSOR } from '@angular/forms';
 | 
			
		||||
import { FILTER_ASN_ISNULL } from 'src/app/data/filter-rule-type';
 | 
			
		||||
import { DocumentService } from 'src/app/services/rest/document.service';
 | 
			
		||||
import { AbstractInputComponent } from '../abstract-input';
 | 
			
		||||
import { Component, forwardRef } from '@angular/core'
 | 
			
		||||
import { NG_VALUE_ACCESSOR } from '@angular/forms'
 | 
			
		||||
import { FILTER_ASN_ISNULL } from 'src/app/data/filter-rule-type'
 | 
			
		||||
import { DocumentService } from 'src/app/services/rest/document.service'
 | 
			
		||||
import { AbstractInputComponent } from '../abstract-input'
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  providers: [{
 | 
			
		||||
    provide: NG_VALUE_ACCESSOR,
 | 
			
		||||
    useExisting: forwardRef(() => NumberComponent),
 | 
			
		||||
    multi: true
 | 
			
		||||
  }],
 | 
			
		||||
  providers: [
 | 
			
		||||
    {
 | 
			
		||||
      provide: NG_VALUE_ACCESSOR,
 | 
			
		||||
      useExisting: forwardRef(() => NumberComponent),
 | 
			
		||||
      multi: true,
 | 
			
		||||
    },
 | 
			
		||||
  ],
 | 
			
		||||
  selector: 'app-input-number',
 | 
			
		||||
  templateUrl: './number.component.html',
 | 
			
		||||
  styleUrls: ['./number.component.scss']
 | 
			
		||||
  styleUrls: ['./number.component.scss'],
 | 
			
		||||
})
 | 
			
		||||
export class NumberComponent extends AbstractInputComponent<number> {
 | 
			
		||||
 | 
			
		||||
  constructor(private documentService: DocumentService) {
 | 
			
		||||
    super()
 | 
			
		||||
  }
 | 
			
		||||
@@ -24,16 +25,17 @@ export class NumberComponent extends AbstractInputComponent<number> {
 | 
			
		||||
    if (this.value) {
 | 
			
		||||
      return
 | 
			
		||||
    }
 | 
			
		||||
    this.documentService.listFiltered(1, 1, "archive_serial_number", true, [{rule_type: FILTER_ASN_ISNULL, value: "false"}]).subscribe(
 | 
			
		||||
      results => {
 | 
			
		||||
    this.documentService
 | 
			
		||||
      .listFiltered(1, 1, 'archive_serial_number', true, [
 | 
			
		||||
        { rule_type: FILTER_ASN_ISNULL, value: 'false' },
 | 
			
		||||
      ])
 | 
			
		||||
      .subscribe((results) => {
 | 
			
		||||
        if (results.count > 0) {
 | 
			
		||||
          this.value = results.results[0].archive_serial_number + 1
 | 
			
		||||
        } else {
 | 
			
		||||
          this.value = 1
 | 
			
		||||
        }
 | 
			
		||||
        this.onChange(this.value)
 | 
			
		||||
      }
 | 
			
		||||
    )
 | 
			
		||||
      })
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,25 +1,24 @@
 | 
			
		||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
 | 
			
		||||
import { ComponentFixture, TestBed } from '@angular/core/testing'
 | 
			
		||||
 | 
			
		||||
import { SelectComponent } from './select.component';
 | 
			
		||||
import { SelectComponent } from './select.component'
 | 
			
		||||
 | 
			
		||||
describe('SelectComponent', () => {
 | 
			
		||||
  let component: SelectComponent;
 | 
			
		||||
  let fixture: ComponentFixture<SelectComponent>;
 | 
			
		||||
  let component: SelectComponent
 | 
			
		||||
  let fixture: ComponentFixture<SelectComponent>
 | 
			
		||||
 | 
			
		||||
  beforeEach(async () => {
 | 
			
		||||
    await TestBed.configureTestingModule({
 | 
			
		||||
      declarations: [ SelectComponent ]
 | 
			
		||||
    })
 | 
			
		||||
    .compileComponents();
 | 
			
		||||
  });
 | 
			
		||||
      declarations: [SelectComponent],
 | 
			
		||||
    }).compileComponents()
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  beforeEach(() => {
 | 
			
		||||
    fixture = TestBed.createComponent(SelectComponent);
 | 
			
		||||
    component = fixture.componentInstance;
 | 
			
		||||
    fixture.detectChanges();
 | 
			
		||||
  });
 | 
			
		||||
    fixture = TestBed.createComponent(SelectComponent)
 | 
			
		||||
    component = fixture.componentInstance
 | 
			
		||||
    fixture.detectChanges()
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  it('should create', () => {
 | 
			
		||||
    expect(component).toBeTruthy();
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
    expect(component).toBeTruthy()
 | 
			
		||||
  })
 | 
			
		||||
})
 | 
			
		||||
 
 | 
			
		||||
@@ -1,23 +1,30 @@
 | 
			
		||||
import { Component, EventEmitter, forwardRef, Input, Output } from '@angular/core';
 | 
			
		||||
import { NG_VALUE_ACCESSOR } from '@angular/forms';
 | 
			
		||||
import { AbstractInputComponent } from '../abstract-input';
 | 
			
		||||
import {
 | 
			
		||||
  Component,
 | 
			
		||||
  EventEmitter,
 | 
			
		||||
  forwardRef,
 | 
			
		||||
  Input,
 | 
			
		||||
  Output,
 | 
			
		||||
} from '@angular/core'
 | 
			
		||||
import { NG_VALUE_ACCESSOR } from '@angular/forms'
 | 
			
		||||
import { AbstractInputComponent } from '../abstract-input'
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  providers: [{
 | 
			
		||||
    provide: NG_VALUE_ACCESSOR,
 | 
			
		||||
    useExisting: forwardRef(() => SelectComponent),
 | 
			
		||||
    multi: true
 | 
			
		||||
  }],
 | 
			
		||||
  providers: [
 | 
			
		||||
    {
 | 
			
		||||
      provide: NG_VALUE_ACCESSOR,
 | 
			
		||||
      useExisting: forwardRef(() => SelectComponent),
 | 
			
		||||
      multi: true,
 | 
			
		||||
    },
 | 
			
		||||
  ],
 | 
			
		||||
  selector: 'app-input-select',
 | 
			
		||||
  templateUrl: './select.component.html',
 | 
			
		||||
  styleUrls: ['./select.component.scss']
 | 
			
		||||
  styleUrls: ['./select.component.scss'],
 | 
			
		||||
})
 | 
			
		||||
export class SelectComponent extends AbstractInputComponent<number> {
 | 
			
		||||
 | 
			
		||||
  constructor() {
 | 
			
		||||
    super()
 | 
			
		||||
    this.addItemRef = this.addItem.bind(this)
 | 
			
		||||
   }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Input()
 | 
			
		||||
  items: any[]
 | 
			
		||||
@@ -47,7 +54,9 @@ export class SelectComponent extends AbstractInputComponent<number> {
 | 
			
		||||
 | 
			
		||||
  getSuggestions() {
 | 
			
		||||
    if (this.suggestions && this.items) {
 | 
			
		||||
      return this.suggestions.filter(id => id != this.value).map(id => this.items.find(item => item.id == id))
 | 
			
		||||
      return this.suggestions
 | 
			
		||||
        .filter((id) => id != this.value)
 | 
			
		||||
        .map((id) => this.items.find((item) => item.id == id))
 | 
			
		||||
    } else {
 | 
			
		||||
      return []
 | 
			
		||||
    }
 | 
			
		||||
@@ -75,7 +84,6 @@ export class SelectComponent extends AbstractInputComponent<number> {
 | 
			
		||||
  onBlur() {
 | 
			
		||||
    setTimeout(() => {
 | 
			
		||||
      this.clearLastSearchTerm()
 | 
			
		||||
    }, 3000);
 | 
			
		||||
    }, 3000)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,25 +1,24 @@
 | 
			
		||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
 | 
			
		||||
import { ComponentFixture, TestBed } from '@angular/core/testing'
 | 
			
		||||
 | 
			
		||||
import { TagsComponent } from './tags.component';
 | 
			
		||||
import { TagsComponent } from './tags.component'
 | 
			
		||||
 | 
			
		||||
describe('TagsComponent', () => {
 | 
			
		||||
  let component: TagsComponent;
 | 
			
		||||
  let fixture: ComponentFixture<TagsComponent>;
 | 
			
		||||
  let component: TagsComponent
 | 
			
		||||
  let fixture: ComponentFixture<TagsComponent>
 | 
			
		||||
 | 
			
		||||
  beforeEach(async () => {
 | 
			
		||||
    await TestBed.configureTestingModule({
 | 
			
		||||
      declarations: [ TagsComponent ]
 | 
			
		||||
    })
 | 
			
		||||
    .compileComponents();
 | 
			
		||||
  });
 | 
			
		||||
      declarations: [TagsComponent],
 | 
			
		||||
    }).compileComponents()
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  beforeEach(() => {
 | 
			
		||||
    fixture = TestBed.createComponent(TagsComponent);
 | 
			
		||||
    component = fixture.componentInstance;
 | 
			
		||||
    fixture.detectChanges();
 | 
			
		||||
  });
 | 
			
		||||
    fixture = TestBed.createComponent(TagsComponent)
 | 
			
		||||
    component = fixture.componentInstance
 | 
			
		||||
    fixture.detectChanges()
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  it('should create', () => {
 | 
			
		||||
    expect(component).toBeTruthy();
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
    expect(component).toBeTruthy()
 | 
			
		||||
  })
 | 
			
		||||
})
 | 
			
		||||
 
 | 
			
		||||
@@ -1,45 +1,46 @@
 | 
			
		||||
import { Component, forwardRef, Input, OnInit } from '@angular/core';
 | 
			
		||||
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
 | 
			
		||||
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
 | 
			
		||||
import { TagEditDialogComponent } from 'src/app/components/manage/tag-list/tag-edit-dialog/tag-edit-dialog.component';
 | 
			
		||||
import { PaperlessTag } from 'src/app/data/paperless-tag';
 | 
			
		||||
import { TagService } from 'src/app/services/rest/tag.service';
 | 
			
		||||
import { Component, forwardRef, Input, OnInit } from '@angular/core'
 | 
			
		||||
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'
 | 
			
		||||
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
 | 
			
		||||
import { TagEditDialogComponent } from 'src/app/components/manage/tag-list/tag-edit-dialog/tag-edit-dialog.component'
 | 
			
		||||
import { PaperlessTag } from 'src/app/data/paperless-tag'
 | 
			
		||||
import { TagService } from 'src/app/services/rest/tag.service'
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  providers: [{
 | 
			
		||||
    provide: NG_VALUE_ACCESSOR,
 | 
			
		||||
    useExisting: forwardRef(() => TagsComponent),
 | 
			
		||||
    multi: true
 | 
			
		||||
  }],
 | 
			
		||||
  providers: [
 | 
			
		||||
    {
 | 
			
		||||
      provide: NG_VALUE_ACCESSOR,
 | 
			
		||||
      useExisting: forwardRef(() => TagsComponent),
 | 
			
		||||
      multi: true,
 | 
			
		||||
    },
 | 
			
		||||
  ],
 | 
			
		||||
  selector: 'app-input-tags',
 | 
			
		||||
  templateUrl: './tags.component.html',
 | 
			
		||||
  styleUrls: ['./tags.component.scss']
 | 
			
		||||
  styleUrls: ['./tags.component.scss'],
 | 
			
		||||
})
 | 
			
		||||
export class TagsComponent implements OnInit, ControlValueAccessor {
 | 
			
		||||
 | 
			
		||||
  constructor(private tagService: TagService, private modalService: NgbModal) {
 | 
			
		||||
    this.createTagRef = this.createTag.bind(this)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  onChange = (newValue: number[]) => {};
 | 
			
		||||
  onChange = (newValue: number[]) => {}
 | 
			
		||||
 | 
			
		||||
  onTouched = () => {};
 | 
			
		||||
  onTouched = () => {}
 | 
			
		||||
 | 
			
		||||
  writeValue(newValue: number[]): void {
 | 
			
		||||
    this.value = newValue
 | 
			
		||||
  }
 | 
			
		||||
  registerOnChange(fn: any): void {
 | 
			
		||||
    this.onChange = fn;
 | 
			
		||||
    this.onChange = fn
 | 
			
		||||
  }
 | 
			
		||||
  registerOnTouched(fn: any): void {
 | 
			
		||||
    this.onTouched = fn;
 | 
			
		||||
    this.onTouched = fn
 | 
			
		||||
  }
 | 
			
		||||
  setDisabledState?(isDisabled: boolean): void {
 | 
			
		||||
    this.disabled = isDisabled;
 | 
			
		||||
    this.disabled = isDisabled
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ngOnInit(): void {
 | 
			
		||||
    this.tagService.listAll().subscribe(result => {
 | 
			
		||||
    this.tagService.listAll().subscribe((result) => {
 | 
			
		||||
      this.tags = result.results
 | 
			
		||||
    })
 | 
			
		||||
  }
 | 
			
		||||
@@ -63,7 +64,7 @@ export class TagsComponent implements OnInit, ControlValueAccessor {
 | 
			
		||||
 | 
			
		||||
  getTag(id) {
 | 
			
		||||
    if (this.tags) {
 | 
			
		||||
      return this.tags.find(tag => tag.id == id)
 | 
			
		||||
      return this.tags.find((tag) => tag.id == id)
 | 
			
		||||
    } else {
 | 
			
		||||
      return null
 | 
			
		||||
    }
 | 
			
		||||
@@ -80,12 +81,15 @@ export class TagsComponent implements OnInit, ControlValueAccessor {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  createTag(name: string = null) {
 | 
			
		||||
    var modal = this.modalService.open(TagEditDialogComponent, {backdrop: 'static'})
 | 
			
		||||
    var modal = this.modalService.open(TagEditDialogComponent, {
 | 
			
		||||
      backdrop: 'static',
 | 
			
		||||
    })
 | 
			
		||||
    modal.componentInstance.dialogMode = 'create'
 | 
			
		||||
    if (name) modal.componentInstance.object = { name: name }
 | 
			
		||||
    else if (this._lastSearchTerm) modal.componentInstance.object = { name: this._lastSearchTerm }
 | 
			
		||||
    modal.componentInstance.success.subscribe(newTag => {
 | 
			
		||||
      this.tagService.listAll().subscribe(tags => {
 | 
			
		||||
    else if (this._lastSearchTerm)
 | 
			
		||||
      modal.componentInstance.object = { name: this._lastSearchTerm }
 | 
			
		||||
    modal.componentInstance.success.subscribe((newTag) => {
 | 
			
		||||
      this.tagService.listAll().subscribe((tags) => {
 | 
			
		||||
        this.tags = tags.results
 | 
			
		||||
        this.value = [...this.value, newTag.id]
 | 
			
		||||
        this.onChange(this.value)
 | 
			
		||||
@@ -95,7 +99,9 @@ export class TagsComponent implements OnInit, ControlValueAccessor {
 | 
			
		||||
 | 
			
		||||
  getSuggestions() {
 | 
			
		||||
    if (this.suggestions && this.tags) {
 | 
			
		||||
      return this.suggestions.filter(id => !this.value.includes(id)).map(id => this.tags.find(tag => tag.id == id))
 | 
			
		||||
      return this.suggestions
 | 
			
		||||
        .filter((id) => !this.value.includes(id))
 | 
			
		||||
        .map((id) => this.tags.find((tag) => tag.id == id))
 | 
			
		||||
    } else {
 | 
			
		||||
      return []
 | 
			
		||||
    }
 | 
			
		||||
@@ -117,7 +123,6 @@ export class TagsComponent implements OnInit, ControlValueAccessor {
 | 
			
		||||
  onBlur() {
 | 
			
		||||
    setTimeout(() => {
 | 
			
		||||
      this.clearLastSearchTerm()
 | 
			
		||||
    }, 3000);
 | 
			
		||||
    }, 3000)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,25 +1,24 @@
 | 
			
		||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
 | 
			
		||||
import { ComponentFixture, TestBed } from '@angular/core/testing'
 | 
			
		||||
 | 
			
		||||
import { TextComponent } from './text.component';
 | 
			
		||||
import { TextComponent } from './text.component'
 | 
			
		||||
 | 
			
		||||
describe('TextComponent', () => {
 | 
			
		||||
  let component: TextComponent;
 | 
			
		||||
  let fixture: ComponentFixture<TextComponent>;
 | 
			
		||||
  let component: TextComponent
 | 
			
		||||
  let fixture: ComponentFixture<TextComponent>
 | 
			
		||||
 | 
			
		||||
  beforeEach(async () => {
 | 
			
		||||
    await TestBed.configureTestingModule({
 | 
			
		||||
      declarations: [ TextComponent ]
 | 
			
		||||
    })
 | 
			
		||||
    .compileComponents();
 | 
			
		||||
  });
 | 
			
		||||
      declarations: [TextComponent],
 | 
			
		||||
    }).compileComponents()
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  beforeEach(() => {
 | 
			
		||||
    fixture = TestBed.createComponent(TextComponent);
 | 
			
		||||
    component = fixture.componentInstance;
 | 
			
		||||
    fixture.detectChanges();
 | 
			
		||||
  });
 | 
			
		||||
    fixture = TestBed.createComponent(TextComponent)
 | 
			
		||||
    component = fixture.componentInstance
 | 
			
		||||
    fixture.detectChanges()
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  it('should create', () => {
 | 
			
		||||
    expect(component).toBeTruthy();
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
    expect(component).toBeTruthy()
 | 
			
		||||
  })
 | 
			
		||||
})
 | 
			
		||||
 
 | 
			
		||||
@@ -1,21 +1,21 @@
 | 
			
		||||
import { Component, forwardRef } from '@angular/core';
 | 
			
		||||
import { NG_VALUE_ACCESSOR } from '@angular/forms';
 | 
			
		||||
import { AbstractInputComponent } from '../abstract-input';
 | 
			
		||||
import { Component, forwardRef } from '@angular/core'
 | 
			
		||||
import { NG_VALUE_ACCESSOR } from '@angular/forms'
 | 
			
		||||
import { AbstractInputComponent } from '../abstract-input'
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  providers: [{
 | 
			
		||||
    provide: NG_VALUE_ACCESSOR,
 | 
			
		||||
    useExisting: forwardRef(() => TextComponent),
 | 
			
		||||
    multi: true
 | 
			
		||||
  }],
 | 
			
		||||
  providers: [
 | 
			
		||||
    {
 | 
			
		||||
      provide: NG_VALUE_ACCESSOR,
 | 
			
		||||
      useExisting: forwardRef(() => TextComponent),
 | 
			
		||||
      multi: true,
 | 
			
		||||
    },
 | 
			
		||||
  ],
 | 
			
		||||
  selector: 'app-input-text',
 | 
			
		||||
  templateUrl: './text.component.html',
 | 
			
		||||
  styleUrls: ['./text.component.scss']
 | 
			
		||||
  styleUrls: ['./text.component.scss'],
 | 
			
		||||
})
 | 
			
		||||
export class TextComponent extends AbstractInputComponent<string> {
 | 
			
		||||
 | 
			
		||||
  constructor() {
 | 
			
		||||
    super()
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,25 +1,24 @@
 | 
			
		||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
 | 
			
		||||
import { ComponentFixture, TestBed } from '@angular/core/testing'
 | 
			
		||||
 | 
			
		||||
import { PageHeaderComponent } from './page-header.component';
 | 
			
		||||
import { PageHeaderComponent } from './page-header.component'
 | 
			
		||||
 | 
			
		||||
describe('PageHeaderComponent', () => {
 | 
			
		||||
  let component: PageHeaderComponent;
 | 
			
		||||
  let fixture: ComponentFixture<PageHeaderComponent>;
 | 
			
		||||
  let component: PageHeaderComponent
 | 
			
		||||
  let fixture: ComponentFixture<PageHeaderComponent>
 | 
			
		||||
 | 
			
		||||
  beforeEach(async () => {
 | 
			
		||||
    await TestBed.configureTestingModule({
 | 
			
		||||
      declarations: [ PageHeaderComponent ]
 | 
			
		||||
    })
 | 
			
		||||
    .compileComponents();
 | 
			
		||||
  });
 | 
			
		||||
      declarations: [PageHeaderComponent],
 | 
			
		||||
    }).compileComponents()
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  beforeEach(() => {
 | 
			
		||||
    fixture = TestBed.createComponent(PageHeaderComponent);
 | 
			
		||||
    component = fixture.componentInstance;
 | 
			
		||||
    fixture.detectChanges();
 | 
			
		||||
  });
 | 
			
		||||
    fixture = TestBed.createComponent(PageHeaderComponent)
 | 
			
		||||
    component = fixture.componentInstance
 | 
			
		||||
    fixture.detectChanges()
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  it('should create', () => {
 | 
			
		||||
    expect(component).toBeTruthy();
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
    expect(component).toBeTruthy()
 | 
			
		||||
  })
 | 
			
		||||
})
 | 
			
		||||
 
 | 
			
		||||
@@ -1,17 +1,16 @@
 | 
			
		||||
import { Component, Input } from '@angular/core';
 | 
			
		||||
import { Title } from '@angular/platform-browser';
 | 
			
		||||
import { environment } from 'src/environments/environment';
 | 
			
		||||
import { Component, Input } from '@angular/core'
 | 
			
		||||
import { Title } from '@angular/platform-browser'
 | 
			
		||||
import { environment } from 'src/environments/environment'
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'app-page-header',
 | 
			
		||||
  templateUrl: './page-header.component.html',
 | 
			
		||||
  styleUrls: ['./page-header.component.scss']
 | 
			
		||||
  styleUrls: ['./page-header.component.scss'],
 | 
			
		||||
})
 | 
			
		||||
export class PageHeaderComponent {
 | 
			
		||||
  constructor(private titleService: Title) {}
 | 
			
		||||
 | 
			
		||||
  constructor(private titleService: Title) { }
 | 
			
		||||
 | 
			
		||||
  _title = ""
 | 
			
		||||
  _title = ''
 | 
			
		||||
 | 
			
		||||
  @Input()
 | 
			
		||||
  set title(title: string) {
 | 
			
		||||
@@ -24,6 +23,5 @@ export class PageHeaderComponent {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Input()
 | 
			
		||||
  subTitle: string = ""
 | 
			
		||||
 | 
			
		||||
  subTitle: string = ''
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,25 +1,24 @@
 | 
			
		||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
 | 
			
		||||
import { ComponentFixture, TestBed } from '@angular/core/testing'
 | 
			
		||||
 | 
			
		||||
import { SelectDialogComponent } from './select-dialog.component';
 | 
			
		||||
import { SelectDialogComponent } from './select-dialog.component'
 | 
			
		||||
 | 
			
		||||
describe('SelectDialogComponent', () => {
 | 
			
		||||
  let component: SelectDialogComponent;
 | 
			
		||||
  let fixture: ComponentFixture<SelectDialogComponent>;
 | 
			
		||||
  let component: SelectDialogComponent
 | 
			
		||||
  let fixture: ComponentFixture<SelectDialogComponent>
 | 
			
		||||
 | 
			
		||||
  beforeEach(async () => {
 | 
			
		||||
    await TestBed.configureTestingModule({
 | 
			
		||||
      declarations: [ SelectDialogComponent ]
 | 
			
		||||
    })
 | 
			
		||||
    .compileComponents();
 | 
			
		||||
  });
 | 
			
		||||
      declarations: [SelectDialogComponent],
 | 
			
		||||
    }).compileComponents()
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  beforeEach(() => {
 | 
			
		||||
    fixture = TestBed.createComponent(SelectDialogComponent);
 | 
			
		||||
    component = fixture.componentInstance;
 | 
			
		||||
    fixture.detectChanges();
 | 
			
		||||
  });
 | 
			
		||||
    fixture = TestBed.createComponent(SelectDialogComponent)
 | 
			
		||||
    component = fixture.componentInstance
 | 
			
		||||
    fixture.detectChanges()
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  it('should create', () => {
 | 
			
		||||
    expect(component).toBeTruthy();
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
    expect(component).toBeTruthy()
 | 
			
		||||
  })
 | 
			
		||||
})
 | 
			
		||||
 
 | 
			
		||||
@@ -1,15 +1,14 @@
 | 
			
		||||
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
 | 
			
		||||
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
 | 
			
		||||
import { ObjectWithId } from 'src/app/data/object-with-id';
 | 
			
		||||
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'
 | 
			
		||||
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
 | 
			
		||||
import { ObjectWithId } from 'src/app/data/object-with-id'
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'app-select-dialog',
 | 
			
		||||
  templateUrl: './select-dialog.component.html',
 | 
			
		||||
  styleUrls: ['./select-dialog.component.scss']
 | 
			
		||||
  styleUrls: ['./select-dialog.component.scss'],
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
export class SelectDialogComponent implements OnInit {
 | 
			
		||||
  constructor(public activeModal: NgbActiveModal) { }
 | 
			
		||||
  constructor(public activeModal: NgbActiveModal) {}
 | 
			
		||||
 | 
			
		||||
  @Output()
 | 
			
		||||
  public selectClicked = new EventEmitter()
 | 
			
		||||
@@ -25,8 +24,7 @@ export class SelectDialogComponent implements OnInit {
 | 
			
		||||
 | 
			
		||||
  selected: number
 | 
			
		||||
 | 
			
		||||
  ngOnInit(): void {
 | 
			
		||||
  }
 | 
			
		||||
  ngOnInit(): void {}
 | 
			
		||||
 | 
			
		||||
  cancelClicked() {
 | 
			
		||||
    this.activeModal.close()
 | 
			
		||||
 
 | 
			
		||||
@@ -1,25 +1,24 @@
 | 
			
		||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
 | 
			
		||||
import { ComponentFixture, TestBed } from '@angular/core/testing'
 | 
			
		||||
 | 
			
		||||
import { TagComponent } from './tag.component';
 | 
			
		||||
import { TagComponent } from './tag.component'
 | 
			
		||||
 | 
			
		||||
describe('TagComponent', () => {
 | 
			
		||||
  let component: TagComponent;
 | 
			
		||||
  let fixture: ComponentFixture<TagComponent>;
 | 
			
		||||
  let component: TagComponent
 | 
			
		||||
  let fixture: ComponentFixture<TagComponent>
 | 
			
		||||
 | 
			
		||||
  beforeEach(async () => {
 | 
			
		||||
    await TestBed.configureTestingModule({
 | 
			
		||||
      declarations: [ TagComponent ]
 | 
			
		||||
    })
 | 
			
		||||
    .compileComponents();
 | 
			
		||||
  });
 | 
			
		||||
      declarations: [TagComponent],
 | 
			
		||||
    }).compileComponents()
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  beforeEach(() => {
 | 
			
		||||
    fixture = TestBed.createComponent(TagComponent);
 | 
			
		||||
    component = fixture.componentInstance;
 | 
			
		||||
    fixture.detectChanges();
 | 
			
		||||
  });
 | 
			
		||||
    fixture = TestBed.createComponent(TagComponent)
 | 
			
		||||
    component = fixture.componentInstance
 | 
			
		||||
    fixture.detectChanges()
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  it('should create', () => {
 | 
			
		||||
    expect(component).toBeTruthy();
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
    expect(component).toBeTruthy()
 | 
			
		||||
  })
 | 
			
		||||
})
 | 
			
		||||
 
 | 
			
		||||
@@ -1,25 +1,22 @@
 | 
			
		||||
import { Component, Input, OnInit } from '@angular/core';
 | 
			
		||||
import { PaperlessTag } from 'src/app/data/paperless-tag';
 | 
			
		||||
import { Component, Input, OnInit } from '@angular/core'
 | 
			
		||||
import { PaperlessTag } from 'src/app/data/paperless-tag'
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'app-tag',
 | 
			
		||||
  templateUrl: './tag.component.html',
 | 
			
		||||
  styleUrls: ['./tag.component.scss']
 | 
			
		||||
  styleUrls: ['./tag.component.scss'],
 | 
			
		||||
})
 | 
			
		||||
export class TagComponent implements OnInit {
 | 
			
		||||
 | 
			
		||||
  constructor() { }
 | 
			
		||||
  constructor() {}
 | 
			
		||||
 | 
			
		||||
  @Input()
 | 
			
		||||
  tag: PaperlessTag
 | 
			
		||||
 | 
			
		||||
  @Input()
 | 
			
		||||
  linkTitle: string = ""
 | 
			
		||||
  linkTitle: string = ''
 | 
			
		||||
 | 
			
		||||
  @Input()
 | 
			
		||||
  clickable: boolean = false
 | 
			
		||||
 | 
			
		||||
  ngOnInit(): void {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ngOnInit(): void {}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,25 +1,24 @@
 | 
			
		||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
 | 
			
		||||
import { ComponentFixture, TestBed } from '@angular/core/testing'
 | 
			
		||||
 | 
			
		||||
import { ToastsComponent } from './toasts.component';
 | 
			
		||||
import { ToastsComponent } from './toasts.component'
 | 
			
		||||
 | 
			
		||||
describe('ToastsComponent', () => {
 | 
			
		||||
  let component: ToastsComponent;
 | 
			
		||||
  let fixture: ComponentFixture<ToastsComponent>;
 | 
			
		||||
  let component: ToastsComponent
 | 
			
		||||
  let fixture: ComponentFixture<ToastsComponent>
 | 
			
		||||
 | 
			
		||||
  beforeEach(async () => {
 | 
			
		||||
    await TestBed.configureTestingModule({
 | 
			
		||||
      declarations: [ ToastsComponent ]
 | 
			
		||||
    })
 | 
			
		||||
    .compileComponents();
 | 
			
		||||
  });
 | 
			
		||||
      declarations: [ToastsComponent],
 | 
			
		||||
    }).compileComponents()
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  beforeEach(() => {
 | 
			
		||||
    fixture = TestBed.createComponent(ToastsComponent);
 | 
			
		||||
    component = fixture.componentInstance;
 | 
			
		||||
    fixture.detectChanges();
 | 
			
		||||
  });
 | 
			
		||||
    fixture = TestBed.createComponent(ToastsComponent)
 | 
			
		||||
    component = fixture.componentInstance
 | 
			
		||||
    fixture.detectChanges()
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  it('should create', () => {
 | 
			
		||||
    expect(component).toBeTruthy();
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
    expect(component).toBeTruthy()
 | 
			
		||||
  })
 | 
			
		||||
})
 | 
			
		||||
 
 | 
			
		||||
@@ -1,15 +1,14 @@
 | 
			
		||||
import { Component, OnDestroy, OnInit } from '@angular/core';
 | 
			
		||||
import { Subscription } from 'rxjs';
 | 
			
		||||
import { Toast, ToastService } from 'src/app/services/toast.service';
 | 
			
		||||
import { Component, OnDestroy, OnInit } from '@angular/core'
 | 
			
		||||
import { Subscription } from 'rxjs'
 | 
			
		||||
import { Toast, ToastService } from 'src/app/services/toast.service'
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'app-toasts',
 | 
			
		||||
  templateUrl: './toasts.component.html',
 | 
			
		||||
  styleUrls: ['./toasts.component.scss']
 | 
			
		||||
  styleUrls: ['./toasts.component.scss'],
 | 
			
		||||
})
 | 
			
		||||
export class ToastsComponent implements OnInit, OnDestroy {
 | 
			
		||||
 | 
			
		||||
  constructor(private toastService: ToastService) { }
 | 
			
		||||
  constructor(private toastService: ToastService) {}
 | 
			
		||||
 | 
			
		||||
  subscription: Subscription
 | 
			
		||||
 | 
			
		||||
@@ -20,7 +19,8 @@ export class ToastsComponent implements OnInit, OnDestroy {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ngOnInit(): void {
 | 
			
		||||
    this.subscription = this.toastService.getToasts().subscribe(toasts => this.toasts = toasts)
 | 
			
		||||
    this.subscription = this.toastService
 | 
			
		||||
      .getToasts()
 | 
			
		||||
      .subscribe((toasts) => (this.toasts = toasts))
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,25 +1,24 @@
 | 
			
		||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
 | 
			
		||||
import { ComponentFixture, TestBed } from '@angular/core/testing'
 | 
			
		||||
 | 
			
		||||
import { DashboardComponent } from './dashboard.component';
 | 
			
		||||
import { DashboardComponent } from './dashboard.component'
 | 
			
		||||
 | 
			
		||||
describe('DashboardComponent', () => {
 | 
			
		||||
  let component: DashboardComponent;
 | 
			
		||||
  let fixture: ComponentFixture<DashboardComponent>;
 | 
			
		||||
  let component: DashboardComponent
 | 
			
		||||
  let fixture: ComponentFixture<DashboardComponent>
 | 
			
		||||
 | 
			
		||||
  beforeEach(async () => {
 | 
			
		||||
    await TestBed.configureTestingModule({
 | 
			
		||||
      declarations: [ DashboardComponent ]
 | 
			
		||||
    })
 | 
			
		||||
    .compileComponents();
 | 
			
		||||
  });
 | 
			
		||||
      declarations: [DashboardComponent],
 | 
			
		||||
    }).compileComponents()
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  beforeEach(() => {
 | 
			
		||||
    fixture = TestBed.createComponent(DashboardComponent);
 | 
			
		||||
    component = fixture.componentInstance;
 | 
			
		||||
    fixture.detectChanges();
 | 
			
		||||
  });
 | 
			
		||||
    fixture = TestBed.createComponent(DashboardComponent)
 | 
			
		||||
    component = fixture.componentInstance
 | 
			
		||||
    fixture.detectChanges()
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  it('should create', () => {
 | 
			
		||||
    expect(component).toBeTruthy();
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
    expect(component).toBeTruthy()
 | 
			
		||||
  })
 | 
			
		||||
})
 | 
			
		||||
 
 | 
			
		||||
@@ -1,20 +1,15 @@
 | 
			
		||||
import { Component, OnInit } from '@angular/core';
 | 
			
		||||
import { Meta } from '@angular/platform-browser';
 | 
			
		||||
import { PaperlessSavedView } from 'src/app/data/paperless-saved-view';
 | 
			
		||||
import { SavedViewService } from 'src/app/services/rest/saved-view.service';
 | 
			
		||||
 | 
			
		||||
import { Component, OnInit } from '@angular/core'
 | 
			
		||||
import { Meta } from '@angular/platform-browser'
 | 
			
		||||
import { PaperlessSavedView } from 'src/app/data/paperless-saved-view'
 | 
			
		||||
import { SavedViewService } from 'src/app/services/rest/saved-view.service'
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'app-dashboard',
 | 
			
		||||
  templateUrl: './dashboard.component.html',
 | 
			
		||||
  styleUrls: ['./dashboard.component.scss']
 | 
			
		||||
  styleUrls: ['./dashboard.component.scss'],
 | 
			
		||||
})
 | 
			
		||||
export class DashboardComponent implements OnInit {
 | 
			
		||||
 | 
			
		||||
  constructor(
 | 
			
		||||
    private savedViewService: SavedViewService,
 | 
			
		||||
    private meta: Meta
 | 
			
		||||
  ) { }
 | 
			
		||||
  constructor(private savedViewService: SavedViewService, private meta: Meta) {}
 | 
			
		||||
 | 
			
		||||
  get displayName() {
 | 
			
		||||
    let tagFullName = this.meta.getTag('name=full_name')
 | 
			
		||||
@@ -39,9 +34,10 @@ export class DashboardComponent implements OnInit {
 | 
			
		||||
  savedViews: PaperlessSavedView[] = []
 | 
			
		||||
 | 
			
		||||
  ngOnInit(): void {
 | 
			
		||||
    this.savedViewService.listAll().subscribe(results => {
 | 
			
		||||
      this.savedViews = results.results.filter(savedView => savedView.show_on_dashboard)
 | 
			
		||||
    this.savedViewService.listAll().subscribe((results) => {
 | 
			
		||||
      this.savedViews = results.results.filter(
 | 
			
		||||
        (savedView) => savedView.show_on_dashboard
 | 
			
		||||
      )
 | 
			
		||||
    })
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,25 +1,24 @@
 | 
			
		||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
 | 
			
		||||
import { ComponentFixture, TestBed } from '@angular/core/testing'
 | 
			
		||||
 | 
			
		||||
import { SavedViewWidgetComponent } from './saved-view-widget.component';
 | 
			
		||||
import { SavedViewWidgetComponent } from './saved-view-widget.component'
 | 
			
		||||
 | 
			
		||||
describe('SavedViewWidgetComponent', () => {
 | 
			
		||||
  let component: SavedViewWidgetComponent;
 | 
			
		||||
  let fixture: ComponentFixture<SavedViewWidgetComponent>;
 | 
			
		||||
  let component: SavedViewWidgetComponent
 | 
			
		||||
  let fixture: ComponentFixture<SavedViewWidgetComponent>
 | 
			
		||||
 | 
			
		||||
  beforeEach(async () => {
 | 
			
		||||
    await TestBed.configureTestingModule({
 | 
			
		||||
      declarations: [ SavedViewWidgetComponent ]
 | 
			
		||||
    })
 | 
			
		||||
    .compileComponents();
 | 
			
		||||
  });
 | 
			
		||||
      declarations: [SavedViewWidgetComponent],
 | 
			
		||||
    }).compileComponents()
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  beforeEach(() => {
 | 
			
		||||
    fixture = TestBed.createComponent(SavedViewWidgetComponent);
 | 
			
		||||
    component = fixture.componentInstance;
 | 
			
		||||
    fixture.detectChanges();
 | 
			
		||||
  });
 | 
			
		||||
    fixture = TestBed.createComponent(SavedViewWidgetComponent)
 | 
			
		||||
    component = fixture.componentInstance
 | 
			
		||||
    fixture.detectChanges()
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  it('should create', () => {
 | 
			
		||||
    expect(component).toBeTruthy();
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
    expect(component).toBeTruthy()
 | 
			
		||||
  })
 | 
			
		||||
})
 | 
			
		||||
 
 | 
			
		||||
@@ -1,24 +1,24 @@
 | 
			
		||||
import { Component, Input, OnDestroy, OnInit } from '@angular/core';
 | 
			
		||||
import { Router } from '@angular/router';
 | 
			
		||||
import { Subscription } from 'rxjs';
 | 
			
		||||
import { PaperlessDocument } from 'src/app/data/paperless-document';
 | 
			
		||||
import { PaperlessSavedView } from 'src/app/data/paperless-saved-view';
 | 
			
		||||
import { DocumentListViewService } from 'src/app/services/document-list-view.service';
 | 
			
		||||
import { ConsumerStatusService } from 'src/app/services/consumer-status.service';
 | 
			
		||||
import { DocumentService } from 'src/app/services/rest/document.service';
 | 
			
		||||
import { Component, Input, OnDestroy, OnInit } from '@angular/core'
 | 
			
		||||
import { Router } from '@angular/router'
 | 
			
		||||
import { Subscription } from 'rxjs'
 | 
			
		||||
import { PaperlessDocument } from 'src/app/data/paperless-document'
 | 
			
		||||
import { PaperlessSavedView } from 'src/app/data/paperless-saved-view'
 | 
			
		||||
import { DocumentListViewService } from 'src/app/services/document-list-view.service'
 | 
			
		||||
import { ConsumerStatusService } from 'src/app/services/consumer-status.service'
 | 
			
		||||
import { DocumentService } from 'src/app/services/rest/document.service'
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'app-saved-view-widget',
 | 
			
		||||
  templateUrl: './saved-view-widget.component.html',
 | 
			
		||||
  styleUrls: ['./saved-view-widget.component.scss']
 | 
			
		||||
  styleUrls: ['./saved-view-widget.component.scss'],
 | 
			
		||||
})
 | 
			
		||||
export class SavedViewWidgetComponent implements OnInit, OnDestroy {
 | 
			
		||||
 | 
			
		||||
  constructor(
 | 
			
		||||
    private documentService: DocumentService,
 | 
			
		||||
    private router: Router,
 | 
			
		||||
    private list: DocumentListViewService,
 | 
			
		||||
    private consumerStatusService: ConsumerStatusService) { }
 | 
			
		||||
    private consumerStatusService: ConsumerStatusService
 | 
			
		||||
  ) {}
 | 
			
		||||
 | 
			
		||||
  @Input()
 | 
			
		||||
  savedView: PaperlessSavedView
 | 
			
		||||
@@ -29,9 +29,11 @@ export class SavedViewWidgetComponent implements OnInit, OnDestroy {
 | 
			
		||||
 | 
			
		||||
  ngOnInit(): void {
 | 
			
		||||
    this.reload()
 | 
			
		||||
    this.subscription = this.consumerStatusService.onDocumentConsumptionFinished().subscribe(status => {
 | 
			
		||||
      this.reload()
 | 
			
		||||
    })
 | 
			
		||||
    this.subscription = this.consumerStatusService
 | 
			
		||||
      .onDocumentConsumptionFinished()
 | 
			
		||||
      .subscribe((status) => {
 | 
			
		||||
        this.reload()
 | 
			
		||||
      })
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ngOnDestroy(): void {
 | 
			
		||||
@@ -39,9 +41,17 @@ export class SavedViewWidgetComponent implements OnInit, OnDestroy {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  reload() {
 | 
			
		||||
    this.documentService.listFiltered(1,10,this.savedView.sort_field, this.savedView.sort_reverse, this.savedView.filter_rules).subscribe(result => {
 | 
			
		||||
      this.documents = result.results
 | 
			
		||||
    })
 | 
			
		||||
    this.documentService
 | 
			
		||||
      .listFiltered(
 | 
			
		||||
        1,
 | 
			
		||||
        10,
 | 
			
		||||
        this.savedView.sort_field,
 | 
			
		||||
        this.savedView.sort_reverse,
 | 
			
		||||
        this.savedView.filter_rules
 | 
			
		||||
      )
 | 
			
		||||
      .subscribe((result) => {
 | 
			
		||||
        this.documents = result.results
 | 
			
		||||
      })
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  showAll() {
 | 
			
		||||
@@ -49,8 +59,7 @@ export class SavedViewWidgetComponent implements OnInit, OnDestroy {
 | 
			
		||||
      this.router.navigate(['view', this.savedView.id])
 | 
			
		||||
    } else {
 | 
			
		||||
      this.list.loadSavedView(this.savedView, true)
 | 
			
		||||
      this.router.navigate(["documents"])
 | 
			
		||||
      this.router.navigate(['documents'])
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,25 +1,24 @@
 | 
			
		||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
 | 
			
		||||
import { ComponentFixture, TestBed } from '@angular/core/testing'
 | 
			
		||||
 | 
			
		||||
import { StatisticsWidgetComponent } from './statistics-widget.component';
 | 
			
		||||
import { StatisticsWidgetComponent } from './statistics-widget.component'
 | 
			
		||||
 | 
			
		||||
describe('StatisticsWidgetComponent', () => {
 | 
			
		||||
  let component: StatisticsWidgetComponent;
 | 
			
		||||
  let fixture: ComponentFixture<StatisticsWidgetComponent>;
 | 
			
		||||
  let component: StatisticsWidgetComponent
 | 
			
		||||
  let fixture: ComponentFixture<StatisticsWidgetComponent>
 | 
			
		||||
 | 
			
		||||
  beforeEach(async () => {
 | 
			
		||||
    await TestBed.configureTestingModule({
 | 
			
		||||
      declarations: [ StatisticsWidgetComponent ]
 | 
			
		||||
    })
 | 
			
		||||
    .compileComponents();
 | 
			
		||||
  });
 | 
			
		||||
      declarations: [StatisticsWidgetComponent],
 | 
			
		||||
    }).compileComponents()
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  beforeEach(() => {
 | 
			
		||||
    fixture = TestBed.createComponent(StatisticsWidgetComponent);
 | 
			
		||||
    component = fixture.componentInstance;
 | 
			
		||||
    fixture.detectChanges();
 | 
			
		||||
  });
 | 
			
		||||
    fixture = TestBed.createComponent(StatisticsWidgetComponent)
 | 
			
		||||
    component = fixture.componentInstance
 | 
			
		||||
    fixture.detectChanges()
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  it('should create', () => {
 | 
			
		||||
    expect(component).toBeTruthy();
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
    expect(component).toBeTruthy()
 | 
			
		||||
  })
 | 
			
		||||
})
 | 
			
		||||
 
 | 
			
		||||
@@ -1,24 +1,24 @@
 | 
			
		||||
import { HttpClient } from '@angular/common/http';
 | 
			
		||||
import { Component, OnDestroy, OnInit } from '@angular/core';
 | 
			
		||||
import { Observable, Subscription } from 'rxjs';
 | 
			
		||||
import { ConsumerStatusService } from 'src/app/services/consumer-status.service';
 | 
			
		||||
import { environment } from 'src/environments/environment';
 | 
			
		||||
import { HttpClient } from '@angular/common/http'
 | 
			
		||||
import { Component, OnDestroy, OnInit } from '@angular/core'
 | 
			
		||||
import { Observable, Subscription } from 'rxjs'
 | 
			
		||||
import { ConsumerStatusService } from 'src/app/services/consumer-status.service'
 | 
			
		||||
import { environment } from 'src/environments/environment'
 | 
			
		||||
 | 
			
		||||
export interface Statistics {
 | 
			
		||||
  documents_total?: number
 | 
			
		||||
  documents_inbox?: number
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'app-statistics-widget',
 | 
			
		||||
  templateUrl: './statistics-widget.component.html',
 | 
			
		||||
  styleUrls: ['./statistics-widget.component.scss']
 | 
			
		||||
  styleUrls: ['./statistics-widget.component.scss'],
 | 
			
		||||
})
 | 
			
		||||
export class StatisticsWidgetComponent implements OnInit, OnDestroy {
 | 
			
		||||
 | 
			
		||||
  constructor(private http: HttpClient,
 | 
			
		||||
    private consumerStatusService: ConsumerStatusService) { }
 | 
			
		||||
  constructor(
 | 
			
		||||
    private http: HttpClient,
 | 
			
		||||
    private consumerStatusService: ConsumerStatusService
 | 
			
		||||
  ) {}
 | 
			
		||||
 | 
			
		||||
  statistics: Statistics = {}
 | 
			
		||||
 | 
			
		||||
@@ -29,20 +29,21 @@ export class StatisticsWidgetComponent implements OnInit, OnDestroy {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  reload() {
 | 
			
		||||
    this.getStatistics().subscribe(statistics => {
 | 
			
		||||
    this.getStatistics().subscribe((statistics) => {
 | 
			
		||||
      this.statistics = statistics
 | 
			
		||||
    })
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ngOnInit(): void {
 | 
			
		||||
    this.reload()
 | 
			
		||||
    this.subscription = this.consumerStatusService.onDocumentConsumptionFinished().subscribe(status => {
 | 
			
		||||
      this.reload()
 | 
			
		||||
    })
 | 
			
		||||
    this.subscription = this.consumerStatusService
 | 
			
		||||
      .onDocumentConsumptionFinished()
 | 
			
		||||
      .subscribe((status) => {
 | 
			
		||||
        this.reload()
 | 
			
		||||
      })
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ngOnDestroy(): void {
 | 
			
		||||
    this.subscription.unsubscribe()
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,25 +1,24 @@
 | 
			
		||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
 | 
			
		||||
import { ComponentFixture, TestBed } from '@angular/core/testing'
 | 
			
		||||
 | 
			
		||||
import { UploadFileWidgetComponent } from './upload-file-widget.component';
 | 
			
		||||
import { UploadFileWidgetComponent } from './upload-file-widget.component'
 | 
			
		||||
 | 
			
		||||
describe('UploadFileWidgetComponent', () => {
 | 
			
		||||
  let component: UploadFileWidgetComponent;
 | 
			
		||||
  let fixture: ComponentFixture<UploadFileWidgetComponent>;
 | 
			
		||||
  let component: UploadFileWidgetComponent
 | 
			
		||||
  let fixture: ComponentFixture<UploadFileWidgetComponent>
 | 
			
		||||
 | 
			
		||||
  beforeEach(async () => {
 | 
			
		||||
    await TestBed.configureTestingModule({
 | 
			
		||||
      declarations: [ UploadFileWidgetComponent ]
 | 
			
		||||
    })
 | 
			
		||||
    .compileComponents();
 | 
			
		||||
  });
 | 
			
		||||
      declarations: [UploadFileWidgetComponent],
 | 
			
		||||
    }).compileComponents()
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  beforeEach(() => {
 | 
			
		||||
    fixture = TestBed.createComponent(UploadFileWidgetComponent);
 | 
			
		||||
    component = fixture.componentInstance;
 | 
			
		||||
    fixture.detectChanges();
 | 
			
		||||
  });
 | 
			
		||||
    fixture = TestBed.createComponent(UploadFileWidgetComponent)
 | 
			
		||||
    component = fixture.componentInstance
 | 
			
		||||
    fixture.detectChanges()
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  it('should create', () => {
 | 
			
		||||
    expect(component).toBeTruthy();
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
    expect(component).toBeTruthy()
 | 
			
		||||
  })
 | 
			
		||||
})
 | 
			
		||||
 
 | 
			
		||||
@@ -1,15 +1,19 @@
 | 
			
		||||
import { HttpEventType } from '@angular/common/http';
 | 
			
		||||
import { Component, OnInit } from '@angular/core';
 | 
			
		||||
import { FileSystemFileEntry, NgxFileDropEntry } from 'ngx-file-drop';
 | 
			
		||||
import { ConsumerStatusService, FileStatus, FileStatusPhase } from 'src/app/services/consumer-status.service';
 | 
			
		||||
import { DocumentService } from 'src/app/services/rest/document.service';
 | 
			
		||||
import { HttpEventType } from '@angular/common/http'
 | 
			
		||||
import { Component, OnInit } from '@angular/core'
 | 
			
		||||
import { FileSystemFileEntry, NgxFileDropEntry } from 'ngx-file-drop'
 | 
			
		||||
import {
 | 
			
		||||
  ConsumerStatusService,
 | 
			
		||||
  FileStatus,
 | 
			
		||||
  FileStatusPhase,
 | 
			
		||||
} from 'src/app/services/consumer-status.service'
 | 
			
		||||
import { DocumentService } from 'src/app/services/rest/document.service'
 | 
			
		||||
 | 
			
		||||
const MAX_ALERTS = 5
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'app-upload-file-widget',
 | 
			
		||||
  templateUrl: './upload-file-widget.component.html',
 | 
			
		||||
  styleUrls: ['./upload-file-widget.component.scss']
 | 
			
		||||
  styleUrls: ['./upload-file-widget.component.scss'],
 | 
			
		||||
})
 | 
			
		||||
export class UploadFileWidgetComponent implements OnInit {
 | 
			
		||||
  alertsExpanded = false
 | 
			
		||||
@@ -17,7 +21,7 @@ export class UploadFileWidgetComponent implements OnInit {
 | 
			
		||||
  constructor(
 | 
			
		||||
    private documentService: DocumentService,
 | 
			
		||||
    private consumerStatusService: ConsumerStatusService
 | 
			
		||||
  ) { }
 | 
			
		||||
  ) {}
 | 
			
		||||
 | 
			
		||||
  getStatus() {
 | 
			
		||||
    return this.consumerStatusService.getConsumerStatus().slice(0, MAX_ALERTS)
 | 
			
		||||
@@ -25,7 +29,8 @@ export class UploadFileWidgetComponent implements OnInit {
 | 
			
		||||
 | 
			
		||||
  getStatusSummary() {
 | 
			
		||||
    let strings = []
 | 
			
		||||
    let countUploadingAndProcessing =  this.consumerStatusService.getConsumerStatusNotCompleted().length
 | 
			
		||||
    let countUploadingAndProcessing =
 | 
			
		||||
      this.consumerStatusService.getConsumerStatusNotCompleted().length
 | 
			
		||||
    let countFailed = this.getStatusFailed().length
 | 
			
		||||
    let countSuccess = this.getStatusSuccess().length
 | 
			
		||||
    if (countUploadingAndProcessing > 0) {
 | 
			
		||||
@@ -37,16 +42,21 @@ export class UploadFileWidgetComponent implements OnInit {
 | 
			
		||||
    if (countSuccess > 0) {
 | 
			
		||||
      strings.push($localize`Added: ${countSuccess}`)
 | 
			
		||||
    }
 | 
			
		||||
    return strings.join($localize`:this string is used to separate processing, failed and added on the file upload widget:, `)
 | 
			
		||||
    return strings.join(
 | 
			
		||||
      $localize`:this string is used to separate processing, failed and added on the file upload widget:, `
 | 
			
		||||
    )
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  getStatusHidden() {
 | 
			
		||||
    if (this.consumerStatusService.getConsumerStatus().length < MAX_ALERTS) return []
 | 
			
		||||
    if (this.consumerStatusService.getConsumerStatus().length < MAX_ALERTS)
 | 
			
		||||
      return []
 | 
			
		||||
    else return this.consumerStatusService.getConsumerStatus().slice(MAX_ALERTS)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  getStatusUploading() {
 | 
			
		||||
    return this.consumerStatusService.getConsumerStatus(FileStatusPhase.UPLOADING)
 | 
			
		||||
    return this.consumerStatusService.getConsumerStatus(
 | 
			
		||||
      FileStatusPhase.UPLOADING
 | 
			
		||||
    )
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  getStatusFailed() {
 | 
			
		||||
@@ -64,7 +74,7 @@ export class UploadFileWidgetComponent implements OnInit {
 | 
			
		||||
    let current = 0
 | 
			
		||||
    let max = 0
 | 
			
		||||
 | 
			
		||||
    this.getStatusUploading().forEach(status => {
 | 
			
		||||
    this.getStatusUploading().forEach((status) => {
 | 
			
		||||
      current += status.currentPhaseProgress
 | 
			
		||||
      max += status.currentPhaseMaxProgress
 | 
			
		||||
    })
 | 
			
		||||
@@ -73,18 +83,21 @@ export class UploadFileWidgetComponent implements OnInit {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  isFinished(status: FileStatus) {
 | 
			
		||||
    return status.phase == FileStatusPhase.FAILED || status.phase == FileStatusPhase.SUCCESS
 | 
			
		||||
    return (
 | 
			
		||||
      status.phase == FileStatusPhase.FAILED ||
 | 
			
		||||
      status.phase == FileStatusPhase.SUCCESS
 | 
			
		||||
    )
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  getStatusColor(status: FileStatus) {
 | 
			
		||||
    switch (status.phase) {
 | 
			
		||||
      case FileStatusPhase.PROCESSING:
 | 
			
		||||
      case FileStatusPhase.UPLOADING:
 | 
			
		||||
          return "primary"
 | 
			
		||||
        return 'primary'
 | 
			
		||||
      case FileStatusPhase.FAILED:
 | 
			
		||||
        return "danger"
 | 
			
		||||
        return 'danger'
 | 
			
		||||
      case FileStatusPhase.SUCCESS:
 | 
			
		||||
        return "success"
 | 
			
		||||
        return 'success'
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@@ -96,20 +109,16 @@ export class UploadFileWidgetComponent implements OnInit {
 | 
			
		||||
    this.consumerStatusService.dismissCompleted()
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ngOnInit(): void {
 | 
			
		||||
  }
 | 
			
		||||
  ngOnInit(): void {}
 | 
			
		||||
 | 
			
		||||
  public fileOver(event){
 | 
			
		||||
  }
 | 
			
		||||
  public fileOver(event) {}
 | 
			
		||||
 | 
			
		||||
  public fileLeave(event){
 | 
			
		||||
  }
 | 
			
		||||
  public fileLeave(event) {}
 | 
			
		||||
 | 
			
		||||
  public dropped(files: NgxFileDropEntry[]) {
 | 
			
		||||
    for (const droppedFile of files) {
 | 
			
		||||
      if (droppedFile.fileEntry.isFile) {
 | 
			
		||||
 | 
			
		||||
      const fileEntry = droppedFile.fileEntry as FileSystemFileEntry;
 | 
			
		||||
        const fileEntry = droppedFile.fileEntry as FileSystemFileEntry
 | 
			
		||||
        fileEntry.file((file: File) => {
 | 
			
		||||
          let formData = new FormData()
 | 
			
		||||
          formData.append('document', file, file.name)
 | 
			
		||||
@@ -117,29 +126,37 @@ export class UploadFileWidgetComponent implements OnInit {
 | 
			
		||||
 | 
			
		||||
          status.message = $localize`Connecting...`
 | 
			
		||||
 | 
			
		||||
          this.documentService.uploadDocument(formData).subscribe(event => {
 | 
			
		||||
            if (event.type == HttpEventType.UploadProgress) {
 | 
			
		||||
              status.updateProgress(FileStatusPhase.UPLOADING, event.loaded, event.total)
 | 
			
		||||
              status.message = $localize`Uploading...`
 | 
			
		||||
            } else if (event.type == HttpEventType.Response) {
 | 
			
		||||
              status.taskId = event.body["task_id"]
 | 
			
		||||
              status.message = $localize`Upload complete, waiting...`
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
          }, error => {
 | 
			
		||||
            switch (error.status) {
 | 
			
		||||
              case 400: {
 | 
			
		||||
                this.consumerStatusService.fail(status, error.error.document)
 | 
			
		||||
                break;
 | 
			
		||||
          this.documentService.uploadDocument(formData).subscribe(
 | 
			
		||||
            (event) => {
 | 
			
		||||
              if (event.type == HttpEventType.UploadProgress) {
 | 
			
		||||
                status.updateProgress(
 | 
			
		||||
                  FileStatusPhase.UPLOADING,
 | 
			
		||||
                  event.loaded,
 | 
			
		||||
                  event.total
 | 
			
		||||
                )
 | 
			
		||||
                status.message = $localize`Uploading...`
 | 
			
		||||
              } else if (event.type == HttpEventType.Response) {
 | 
			
		||||
                status.taskId = event.body['task_id']
 | 
			
		||||
                status.message = $localize`Upload complete, waiting...`
 | 
			
		||||
              }
 | 
			
		||||
              default: {
 | 
			
		||||
                this.consumerStatusService.fail(status, $localize`HTTP error: ${error.status} ${error.statusText}`)
 | 
			
		||||
                break;
 | 
			
		||||
            },
 | 
			
		||||
            (error) => {
 | 
			
		||||
              switch (error.status) {
 | 
			
		||||
                case 400: {
 | 
			
		||||
                  this.consumerStatusService.fail(status, error.error.document)
 | 
			
		||||
                  break
 | 
			
		||||
                }
 | 
			
		||||
                default: {
 | 
			
		||||
                  this.consumerStatusService.fail(
 | 
			
		||||
                    status,
 | 
			
		||||
                    $localize`HTTP error: ${error.status} ${error.statusText}`
 | 
			
		||||
                  )
 | 
			
		||||
                  break
 | 
			
		||||
                }
 | 
			
		||||
              }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
          })
 | 
			
		||||
        });
 | 
			
		||||
          )
 | 
			
		||||
        })
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 
 | 
			
		||||
@@ -1,25 +1,24 @@
 | 
			
		||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
 | 
			
		||||
import { ComponentFixture, TestBed } from '@angular/core/testing'
 | 
			
		||||
 | 
			
		||||
import { WelcomeWidgetComponent } from './welcome-widget.component';
 | 
			
		||||
import { WelcomeWidgetComponent } from './welcome-widget.component'
 | 
			
		||||
 | 
			
		||||
describe('WelcomeWidgetComponent', () => {
 | 
			
		||||
  let component: WelcomeWidgetComponent;
 | 
			
		||||
  let fixture: ComponentFixture<WelcomeWidgetComponent>;
 | 
			
		||||
  let component: WelcomeWidgetComponent
 | 
			
		||||
  let fixture: ComponentFixture<WelcomeWidgetComponent>
 | 
			
		||||
 | 
			
		||||
  beforeEach(async () => {
 | 
			
		||||
    await TestBed.configureTestingModule({
 | 
			
		||||
      declarations: [ WelcomeWidgetComponent ]
 | 
			
		||||
    })
 | 
			
		||||
    .compileComponents();
 | 
			
		||||
  });
 | 
			
		||||
      declarations: [WelcomeWidgetComponent],
 | 
			
		||||
    }).compileComponents()
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  beforeEach(() => {
 | 
			
		||||
    fixture = TestBed.createComponent(WelcomeWidgetComponent);
 | 
			
		||||
    component = fixture.componentInstance;
 | 
			
		||||
    fixture.detectChanges();
 | 
			
		||||
  });
 | 
			
		||||
    fixture = TestBed.createComponent(WelcomeWidgetComponent)
 | 
			
		||||
    component = fixture.componentInstance
 | 
			
		||||
    fixture.detectChanges()
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  it('should create', () => {
 | 
			
		||||
    expect(component).toBeTruthy();
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
    expect(component).toBeTruthy()
 | 
			
		||||
  })
 | 
			
		||||
})
 | 
			
		||||
 
 | 
			
		||||
@@ -1,15 +1,12 @@
 | 
			
		||||
import { Component, OnInit } from '@angular/core';
 | 
			
		||||
import { Component, OnInit } from '@angular/core'
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'app-welcome-widget',
 | 
			
		||||
  templateUrl: './welcome-widget.component.html',
 | 
			
		||||
  styleUrls: ['./welcome-widget.component.scss']
 | 
			
		||||
  styleUrls: ['./welcome-widget.component.scss'],
 | 
			
		||||
})
 | 
			
		||||
export class WelcomeWidgetComponent implements OnInit {
 | 
			
		||||
  constructor() {}
 | 
			
		||||
 | 
			
		||||
  constructor() { }
 | 
			
		||||
 | 
			
		||||
  ngOnInit(): void {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ngOnInit(): void {}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,25 +1,24 @@
 | 
			
		||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
 | 
			
		||||
import { ComponentFixture, TestBed } from '@angular/core/testing'
 | 
			
		||||
 | 
			
		||||
import { WidgetFrameComponent } from './widget-frame.component';
 | 
			
		||||
import { WidgetFrameComponent } from './widget-frame.component'
 | 
			
		||||
 | 
			
		||||
describe('WidgetFrameComponent', () => {
 | 
			
		||||
  let component: WidgetFrameComponent;
 | 
			
		||||
  let fixture: ComponentFixture<WidgetFrameComponent>;
 | 
			
		||||
  let component: WidgetFrameComponent
 | 
			
		||||
  let fixture: ComponentFixture<WidgetFrameComponent>
 | 
			
		||||
 | 
			
		||||
  beforeEach(async () => {
 | 
			
		||||
    await TestBed.configureTestingModule({
 | 
			
		||||
      declarations: [ WidgetFrameComponent ]
 | 
			
		||||
    })
 | 
			
		||||
    .compileComponents();
 | 
			
		||||
  });
 | 
			
		||||
      declarations: [WidgetFrameComponent],
 | 
			
		||||
    }).compileComponents()
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  beforeEach(() => {
 | 
			
		||||
    fixture = TestBed.createComponent(WidgetFrameComponent);
 | 
			
		||||
    component = fixture.componentInstance;
 | 
			
		||||
    fixture.detectChanges();
 | 
			
		||||
  });
 | 
			
		||||
    fixture = TestBed.createComponent(WidgetFrameComponent)
 | 
			
		||||
    component = fixture.componentInstance
 | 
			
		||||
    fixture.detectChanges()
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  it('should create', () => {
 | 
			
		||||
    expect(component).toBeTruthy();
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
    expect(component).toBeTruthy()
 | 
			
		||||
  })
 | 
			
		||||
})
 | 
			
		||||
 
 | 
			
		||||
@@ -1,18 +1,15 @@
 | 
			
		||||
import { Component, Input, OnInit } from '@angular/core';
 | 
			
		||||
import { Component, Input, OnInit } from '@angular/core'
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'app-widget-frame',
 | 
			
		||||
  templateUrl: './widget-frame.component.html',
 | 
			
		||||
  styleUrls: ['./widget-frame.component.scss']
 | 
			
		||||
  styleUrls: ['./widget-frame.component.scss'],
 | 
			
		||||
})
 | 
			
		||||
export class WidgetFrameComponent implements OnInit {
 | 
			
		||||
 | 
			
		||||
  constructor() { }
 | 
			
		||||
  constructor() {}
 | 
			
		||||
 | 
			
		||||
  @Input()
 | 
			
		||||
  title: string
 | 
			
		||||
 | 
			
		||||
  ngOnInit(): void {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ngOnInit(): void {}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,25 +1,24 @@
 | 
			
		||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
 | 
			
		||||
import { ComponentFixture, TestBed } from '@angular/core/testing'
 | 
			
		||||
 | 
			
		||||
import { DocumentAsnComponent } from './document-asn.component';
 | 
			
		||||
import { DocumentAsnComponent } from './document-asn.component'
 | 
			
		||||
 | 
			
		||||
describe('DocumentASNComponentComponent', () => {
 | 
			
		||||
  let component: DocumentAsnComponent;
 | 
			
		||||
  let fixture: ComponentFixture<DocumentAsnComponent>;
 | 
			
		||||
  let component: DocumentAsnComponent
 | 
			
		||||
  let fixture: ComponentFixture<DocumentAsnComponent>
 | 
			
		||||
 | 
			
		||||
  beforeEach(async () => {
 | 
			
		||||
    await TestBed.configureTestingModule({
 | 
			
		||||
      declarations: [ DocumentAsnComponent ]
 | 
			
		||||
    })
 | 
			
		||||
    .compileComponents();
 | 
			
		||||
  });
 | 
			
		||||
      declarations: [DocumentAsnComponent],
 | 
			
		||||
    }).compileComponents()
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  beforeEach(() => {
 | 
			
		||||
    fixture = TestBed.createComponent(DocumentAsnComponent);
 | 
			
		||||
    component = fixture.componentInstance;
 | 
			
		||||
    fixture.detectChanges();
 | 
			
		||||
  });
 | 
			
		||||
    fixture = TestBed.createComponent(DocumentAsnComponent)
 | 
			
		||||
    component = fixture.componentInstance
 | 
			
		||||
    fixture.detectChanges()
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  it('should create', () => {
 | 
			
		||||
    expect(component).toBeTruthy();
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
    expect(component).toBeTruthy()
 | 
			
		||||
  })
 | 
			
		||||
})
 | 
			
		||||
 
 | 
			
		||||
@@ -1,34 +1,33 @@
 | 
			
		||||
import { Component, OnInit } from '@angular/core';
 | 
			
		||||
import {DocumentService} from "../../services/rest/document.service";
 | 
			
		||||
import {ActivatedRoute, Router} from "@angular/router";
 | 
			
		||||
import {FILTER_ASN} from "../../data/filter-rule-type";
 | 
			
		||||
import { Component, OnInit } from '@angular/core'
 | 
			
		||||
import { DocumentService } from '../../services/rest/document.service'
 | 
			
		||||
import { ActivatedRoute, Router } from '@angular/router'
 | 
			
		||||
import { FILTER_ASN } from '../../data/filter-rule-type'
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'app-document-asncomponent',
 | 
			
		||||
  templateUrl: './document-asn.component.html',
 | 
			
		||||
  styleUrls: ['./document-asn.component.scss']
 | 
			
		||||
  styleUrls: ['./document-asn.component.scss'],
 | 
			
		||||
})
 | 
			
		||||
export class DocumentAsnComponent implements OnInit {
 | 
			
		||||
 | 
			
		||||
  asn: string
 | 
			
		||||
  constructor(
 | 
			
		||||
    private documentsService: DocumentService,
 | 
			
		||||
    private route: ActivatedRoute,
 | 
			
		||||
    private router: Router) { }
 | 
			
		||||
 | 
			
		||||
    private router: Router
 | 
			
		||||
  ) {}
 | 
			
		||||
 | 
			
		||||
  ngOnInit(): void {
 | 
			
		||||
 | 
			
		||||
    this.route.paramMap.subscribe(paramMap => {
 | 
			
		||||
      this.asn = paramMap.get('id');
 | 
			
		||||
      this.documentsService.listAllFilteredIds([{rule_type: FILTER_ASN, value: this.asn}]).subscribe(documentId => {
 | 
			
		||||
        if (documentId.length == 1) {
 | 
			
		||||
          this.router.navigate(['documents', documentId[0]])
 | 
			
		||||
        } else {
 | 
			
		||||
          this.router.navigate(['404'])
 | 
			
		||||
        }
 | 
			
		||||
      })
 | 
			
		||||
    this.route.paramMap.subscribe((paramMap) => {
 | 
			
		||||
      this.asn = paramMap.get('id')
 | 
			
		||||
      this.documentsService
 | 
			
		||||
        .listAllFilteredIds([{ rule_type: FILTER_ASN, value: this.asn }])
 | 
			
		||||
        .subscribe((documentId) => {
 | 
			
		||||
          if (documentId.length == 1) {
 | 
			
		||||
            this.router.navigate(['documents', documentId[0]])
 | 
			
		||||
          } else {
 | 
			
		||||
            this.router.navigate(['404'])
 | 
			
		||||
          }
 | 
			
		||||
        })
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,25 +1,24 @@
 | 
			
		||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
 | 
			
		||||
import { ComponentFixture, TestBed } from '@angular/core/testing'
 | 
			
		||||
 | 
			
		||||
import { DocumentDetailComponent } from './document-detail.component';
 | 
			
		||||
import { DocumentDetailComponent } from './document-detail.component'
 | 
			
		||||
 | 
			
		||||
describe('DocumentDetailComponent', () => {
 | 
			
		||||
  let component: DocumentDetailComponent;
 | 
			
		||||
  let fixture: ComponentFixture<DocumentDetailComponent>;
 | 
			
		||||
  let component: DocumentDetailComponent
 | 
			
		||||
  let fixture: ComponentFixture<DocumentDetailComponent>
 | 
			
		||||
 | 
			
		||||
  beforeEach(async () => {
 | 
			
		||||
    await TestBed.configureTestingModule({
 | 
			
		||||
      declarations: [ DocumentDetailComponent ]
 | 
			
		||||
    })
 | 
			
		||||
    .compileComponents();
 | 
			
		||||
  });
 | 
			
		||||
      declarations: [DocumentDetailComponent],
 | 
			
		||||
    }).compileComponents()
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  beforeEach(() => {
 | 
			
		||||
    fixture = TestBed.createComponent(DocumentDetailComponent);
 | 
			
		||||
    component = fixture.componentInstance;
 | 
			
		||||
    fixture.detectChanges();
 | 
			
		||||
  });
 | 
			
		||||
    fixture = TestBed.createComponent(DocumentDetailComponent)
 | 
			
		||||
    component = fixture.componentInstance
 | 
			
		||||
    fixture.detectChanges()
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  it('should create', () => {
 | 
			
		||||
    expect(component).toBeTruthy();
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
    expect(component).toBeTruthy()
 | 
			
		||||
  })
 | 
			
		||||
})
 | 
			
		||||
 
 | 
			
		||||
@@ -1,38 +1,55 @@
 | 
			
		||||
import { Component, OnInit, OnDestroy, ViewChild, ElementRef } from '@angular/core';
 | 
			
		||||
import { FormControl, FormGroup } from '@angular/forms';
 | 
			
		||||
import { ActivatedRoute, Router } from '@angular/router';
 | 
			
		||||
import { NgbModal, NgbNav } from '@ng-bootstrap/ng-bootstrap';
 | 
			
		||||
import { PaperlessCorrespondent } from 'src/app/data/paperless-correspondent';
 | 
			
		||||
import { PaperlessDocument } from 'src/app/data/paperless-document';
 | 
			
		||||
import { PaperlessDocumentMetadata } from 'src/app/data/paperless-document-metadata';
 | 
			
		||||
import { PaperlessDocumentType } from 'src/app/data/paperless-document-type';
 | 
			
		||||
import { DocumentTitlePipe } from 'src/app/pipes/document-title.pipe';
 | 
			
		||||
import { DocumentListViewService } from 'src/app/services/document-list-view.service';
 | 
			
		||||
import { OpenDocumentsService } from 'src/app/services/open-documents.service';
 | 
			
		||||
import { CorrespondentService } from 'src/app/services/rest/correspondent.service';
 | 
			
		||||
import { DocumentTypeService } from 'src/app/services/rest/document-type.service';
 | 
			
		||||
import { DocumentService } from 'src/app/services/rest/document.service';
 | 
			
		||||
import { ConfirmDialogComponent } from '../common/confirm-dialog/confirm-dialog.component';
 | 
			
		||||
import { CorrespondentEditDialogComponent } from '../manage/correspondent-list/correspondent-edit-dialog/correspondent-edit-dialog.component';
 | 
			
		||||
import { DocumentTypeEditDialogComponent } from '../manage/document-type-list/document-type-edit-dialog/document-type-edit-dialog.component';
 | 
			
		||||
import { PDFDocumentProxy } from 'ng2-pdf-viewer';
 | 
			
		||||
import { ToastService } from 'src/app/services/toast.service';
 | 
			
		||||
import { TextComponent } from '../common/input/text/text.component';
 | 
			
		||||
import { SettingsService, SETTINGS_KEYS } from 'src/app/services/settings.service';
 | 
			
		||||
import { dirtyCheck, DirtyComponent } from '@ngneat/dirty-check-forms';
 | 
			
		||||
import { Observable, Subject, BehaviorSubject } from 'rxjs';
 | 
			
		||||
import { first, takeUntil, switchMap, map, debounceTime, distinctUntilChanged } from 'rxjs/operators';
 | 
			
		||||
import { PaperlessDocumentSuggestions } from 'src/app/data/paperless-document-suggestions';
 | 
			
		||||
import { FILTER_FULLTEXT_MORELIKE } from 'src/app/data/filter-rule-type';
 | 
			
		||||
import {
 | 
			
		||||
  Component,
 | 
			
		||||
  OnInit,
 | 
			
		||||
  OnDestroy,
 | 
			
		||||
  ViewChild,
 | 
			
		||||
  ElementRef,
 | 
			
		||||
} from '@angular/core'
 | 
			
		||||
import { FormControl, FormGroup } from '@angular/forms'
 | 
			
		||||
import { ActivatedRoute, Router } from '@angular/router'
 | 
			
		||||
import { NgbModal, NgbNav } from '@ng-bootstrap/ng-bootstrap'
 | 
			
		||||
import { PaperlessCorrespondent } from 'src/app/data/paperless-correspondent'
 | 
			
		||||
import { PaperlessDocument } from 'src/app/data/paperless-document'
 | 
			
		||||
import { PaperlessDocumentMetadata } from 'src/app/data/paperless-document-metadata'
 | 
			
		||||
import { PaperlessDocumentType } from 'src/app/data/paperless-document-type'
 | 
			
		||||
import { DocumentTitlePipe } from 'src/app/pipes/document-title.pipe'
 | 
			
		||||
import { DocumentListViewService } from 'src/app/services/document-list-view.service'
 | 
			
		||||
import { OpenDocumentsService } from 'src/app/services/open-documents.service'
 | 
			
		||||
import { CorrespondentService } from 'src/app/services/rest/correspondent.service'
 | 
			
		||||
import { DocumentTypeService } from 'src/app/services/rest/document-type.service'
 | 
			
		||||
import { DocumentService } from 'src/app/services/rest/document.service'
 | 
			
		||||
import { ConfirmDialogComponent } from '../common/confirm-dialog/confirm-dialog.component'
 | 
			
		||||
import { CorrespondentEditDialogComponent } from '../manage/correspondent-list/correspondent-edit-dialog/correspondent-edit-dialog.component'
 | 
			
		||||
import { DocumentTypeEditDialogComponent } from '../manage/document-type-list/document-type-edit-dialog/document-type-edit-dialog.component'
 | 
			
		||||
import { PDFDocumentProxy } from 'ng2-pdf-viewer'
 | 
			
		||||
import { ToastService } from 'src/app/services/toast.service'
 | 
			
		||||
import { TextComponent } from '../common/input/text/text.component'
 | 
			
		||||
import {
 | 
			
		||||
  SettingsService,
 | 
			
		||||
  SETTINGS_KEYS,
 | 
			
		||||
} from 'src/app/services/settings.service'
 | 
			
		||||
import { dirtyCheck, DirtyComponent } from '@ngneat/dirty-check-forms'
 | 
			
		||||
import { Observable, Subject, BehaviorSubject } from 'rxjs'
 | 
			
		||||
import {
 | 
			
		||||
  first,
 | 
			
		||||
  takeUntil,
 | 
			
		||||
  switchMap,
 | 
			
		||||
  map,
 | 
			
		||||
  debounceTime,
 | 
			
		||||
  distinctUntilChanged,
 | 
			
		||||
} from 'rxjs/operators'
 | 
			
		||||
import { PaperlessDocumentSuggestions } from 'src/app/data/paperless-document-suggestions'
 | 
			
		||||
import { FILTER_FULLTEXT_MORELIKE } from 'src/app/data/filter-rule-type'
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'app-document-detail',
 | 
			
		||||
  templateUrl: './document-detail.component.html',
 | 
			
		||||
  styleUrls: ['./document-detail.component.scss']
 | 
			
		||||
  styleUrls: ['./document-detail.component.scss'],
 | 
			
		||||
})
 | 
			
		||||
export class DocumentDetailComponent implements OnInit, OnDestroy, DirtyComponent {
 | 
			
		||||
 | 
			
		||||
  @ViewChild("inputTitle")
 | 
			
		||||
export class DocumentDetailComponent
 | 
			
		||||
  implements OnInit, OnDestroy, DirtyComponent
 | 
			
		||||
{
 | 
			
		||||
  @ViewChild('inputTitle')
 | 
			
		||||
  titleInput: TextComponent
 | 
			
		||||
 | 
			
		||||
  expandOriginalMetadata = false
 | 
			
		||||
@@ -63,7 +80,7 @@ export class DocumentDetailComponent implements OnInit, OnDestroy, DirtyComponen
 | 
			
		||||
    correspondent: new FormControl(),
 | 
			
		||||
    document_type: new FormControl(),
 | 
			
		||||
    archive_serial_number: new FormControl(),
 | 
			
		||||
    tags: new FormControl([])
 | 
			
		||||
    tags: new FormControl([]),
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  previewCurrentPage: number = 1
 | 
			
		||||
@@ -76,8 +93,13 @@ export class DocumentDetailComponent implements OnInit, OnDestroy, DirtyComponen
 | 
			
		||||
  @ViewChild('nav') nav: NgbNav
 | 
			
		||||
  @ViewChild('pdfPreview') set pdfPreview(element) {
 | 
			
		||||
    // this gets called when compontent added or removed from DOM
 | 
			
		||||
    if (element && element.nativeElement.offsetParent !== null && this.nav?.activeId == 4) { // its visible
 | 
			
		||||
      setTimeout(()=> this.nav?.select(1));
 | 
			
		||||
    if (
 | 
			
		||||
      element &&
 | 
			
		||||
      element.nativeElement.offsetParent !== null &&
 | 
			
		||||
      this.nav?.activeId == 4
 | 
			
		||||
    ) {
 | 
			
		||||
      // its visible
 | 
			
		||||
      setTimeout(() => this.nav?.select(1))
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@@ -92,16 +114,19 @@ export class DocumentDetailComponent implements OnInit, OnDestroy, DirtyComponen
 | 
			
		||||
    private documentListViewService: DocumentListViewService,
 | 
			
		||||
    private documentTitlePipe: DocumentTitlePipe,
 | 
			
		||||
    private toastService: ToastService,
 | 
			
		||||
    private settings: SettingsService) {
 | 
			
		||||
      this.titleSubject.pipe(
 | 
			
		||||
    private settings: SettingsService
 | 
			
		||||
  ) {
 | 
			
		||||
    this.titleSubject
 | 
			
		||||
      .pipe(
 | 
			
		||||
        debounceTime(1000),
 | 
			
		||||
        distinctUntilChanged(),
 | 
			
		||||
        takeUntil(this.unsubscribeNotifier)
 | 
			
		||||
      ).subscribe(titleValue => {
 | 
			
		||||
      )
 | 
			
		||||
      .subscribe((titleValue) => {
 | 
			
		||||
        this.title = titleValue
 | 
			
		||||
        this.documentForm.patchValue({'title': titleValue})
 | 
			
		||||
        this.documentForm.patchValue({ title: titleValue })
 | 
			
		||||
      })
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  titleKeyUp(event) {
 | 
			
		||||
    this.titleSubject.next(event.target?.value)
 | 
			
		||||
@@ -112,180 +137,291 @@ export class DocumentDetailComponent implements OnInit, OnDestroy, DirtyComponen
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  getContentType() {
 | 
			
		||||
    return this.metadata?.has_archive_version ? 'application/pdf' : this.metadata?.original_mime_type
 | 
			
		||||
    return this.metadata?.has_archive_version
 | 
			
		||||
      ? 'application/pdf'
 | 
			
		||||
      : this.metadata?.original_mime_type
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ngOnInit(): void {
 | 
			
		||||
    this.documentForm.valueChanges.pipe(takeUntil(this.unsubscribeNotifier)).subscribe(wow => {
 | 
			
		||||
      Object.assign(this.document, this.documentForm.value)
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
    this.correspondentService.listAll().pipe(first()).subscribe(result => this.correspondents = result.results)
 | 
			
		||||
    this.documentTypeService.listAll().pipe(first()).subscribe(result => this.documentTypes = result.results)
 | 
			
		||||
 | 
			
		||||
    this.route.paramMap.pipe(switchMap(paramMap => {
 | 
			
		||||
      const documentId = +paramMap.get('id')
 | 
			
		||||
      return this.documentsService.get(documentId)
 | 
			
		||||
    })).pipe(switchMap((doc) => {
 | 
			
		||||
      this.documentId = doc.id
 | 
			
		||||
      this.previewUrl = this.documentsService.getPreviewUrl(this.documentId)
 | 
			
		||||
      this.downloadUrl = this.documentsService.getDownloadUrl(this.documentId)
 | 
			
		||||
      this.downloadOriginalUrl = this.documentsService.getDownloadUrl(this.documentId, true)
 | 
			
		||||
      this.suggestions = null
 | 
			
		||||
      if (this.openDocumentService.getOpenDocument(this.documentId)) {
 | 
			
		||||
        this.updateComponent(this.openDocumentService.getOpenDocument(this.documentId))
 | 
			
		||||
      } else {
 | 
			
		||||
        this.openDocumentService.openDocument(doc)
 | 
			
		||||
        this.updateComponent(doc)
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      // Initialize dirtyCheck
 | 
			
		||||
      this.store = new BehaviorSubject({
 | 
			
		||||
        title: doc.title,
 | 
			
		||||
        content: doc.content,
 | 
			
		||||
        created: doc.created,
 | 
			
		||||
        correspondent: doc.correspondent,
 | 
			
		||||
        document_type: doc.document_type,
 | 
			
		||||
        archive_serial_number: doc.archive_serial_number,
 | 
			
		||||
        tags: [...doc.tags]
 | 
			
		||||
    this.documentForm.valueChanges
 | 
			
		||||
      .pipe(takeUntil(this.unsubscribeNotifier))
 | 
			
		||||
      .subscribe((wow) => {
 | 
			
		||||
        Object.assign(this.document, this.documentForm.value)
 | 
			
		||||
      })
 | 
			
		||||
 | 
			
		||||
      this.isDirty$ = dirtyCheck(this.documentForm, this.store.asObservable())
 | 
			
		||||
    this.correspondentService
 | 
			
		||||
      .listAll()
 | 
			
		||||
      .pipe(first())
 | 
			
		||||
      .subscribe((result) => (this.correspondents = result.results))
 | 
			
		||||
    this.documentTypeService
 | 
			
		||||
      .listAll()
 | 
			
		||||
      .pipe(first())
 | 
			
		||||
      .subscribe((result) => (this.documentTypes = result.results))
 | 
			
		||||
 | 
			
		||||
      return this.isDirty$.pipe(map(dirty => ({doc, dirty})))
 | 
			
		||||
    }))
 | 
			
		||||
    .pipe(takeUntil(this.unsubscribeNotifier))
 | 
			
		||||
    .subscribe(({doc, dirty}) => {
 | 
			
		||||
      this.openDocumentService.setDirty(doc.id, dirty)
 | 
			
		||||
    }, error => {this.router.navigate(['404'])})
 | 
			
		||||
    this.route.paramMap
 | 
			
		||||
      .pipe(
 | 
			
		||||
        switchMap((paramMap) => {
 | 
			
		||||
          const documentId = +paramMap.get('id')
 | 
			
		||||
          return this.documentsService.get(documentId)
 | 
			
		||||
        })
 | 
			
		||||
      )
 | 
			
		||||
      .pipe(
 | 
			
		||||
        switchMap((doc) => {
 | 
			
		||||
          this.documentId = doc.id
 | 
			
		||||
          this.previewUrl = this.documentsService.getPreviewUrl(this.documentId)
 | 
			
		||||
          this.downloadUrl = this.documentsService.getDownloadUrl(
 | 
			
		||||
            this.documentId
 | 
			
		||||
          )
 | 
			
		||||
          this.downloadOriginalUrl = this.documentsService.getDownloadUrl(
 | 
			
		||||
            this.documentId,
 | 
			
		||||
            true
 | 
			
		||||
          )
 | 
			
		||||
          this.suggestions = null
 | 
			
		||||
          if (this.openDocumentService.getOpenDocument(this.documentId)) {
 | 
			
		||||
            this.updateComponent(
 | 
			
		||||
              this.openDocumentService.getOpenDocument(this.documentId)
 | 
			
		||||
            )
 | 
			
		||||
          } else {
 | 
			
		||||
            this.openDocumentService.openDocument(doc)
 | 
			
		||||
            this.updateComponent(doc)
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          // Initialize dirtyCheck
 | 
			
		||||
          this.store = new BehaviorSubject({
 | 
			
		||||
            title: doc.title,
 | 
			
		||||
            content: doc.content,
 | 
			
		||||
            created: doc.created,
 | 
			
		||||
            correspondent: doc.correspondent,
 | 
			
		||||
            document_type: doc.document_type,
 | 
			
		||||
            archive_serial_number: doc.archive_serial_number,
 | 
			
		||||
            tags: [...doc.tags],
 | 
			
		||||
          })
 | 
			
		||||
 | 
			
		||||
          this.isDirty$ = dirtyCheck(
 | 
			
		||||
            this.documentForm,
 | 
			
		||||
            this.store.asObservable()
 | 
			
		||||
          )
 | 
			
		||||
 | 
			
		||||
          return this.isDirty$.pipe(map((dirty) => ({ doc, dirty })))
 | 
			
		||||
        })
 | 
			
		||||
      )
 | 
			
		||||
      .pipe(takeUntil(this.unsubscribeNotifier))
 | 
			
		||||
      .subscribe(
 | 
			
		||||
        ({ doc, dirty }) => {
 | 
			
		||||
          this.openDocumentService.setDirty(doc.id, dirty)
 | 
			
		||||
        },
 | 
			
		||||
        (error) => {
 | 
			
		||||
          this.router.navigate(['404'])
 | 
			
		||||
        }
 | 
			
		||||
      )
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ngOnDestroy() : void {
 | 
			
		||||
    this.unsubscribeNotifier.next();
 | 
			
		||||
    this.unsubscribeNotifier.complete();
 | 
			
		||||
  ngOnDestroy(): void {
 | 
			
		||||
    this.unsubscribeNotifier.next()
 | 
			
		||||
    this.unsubscribeNotifier.complete()
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  updateComponent(doc: PaperlessDocument) {
 | 
			
		||||
    this.document = doc
 | 
			
		||||
    this.documentsService.getMetadata(doc.id).pipe(first()).subscribe(result => {
 | 
			
		||||
      this.metadata = result
 | 
			
		||||
    }, error => {
 | 
			
		||||
      this.metadata = null
 | 
			
		||||
    })
 | 
			
		||||
    this.documentsService.getSuggestions(doc.id).pipe(first()).subscribe(result => {
 | 
			
		||||
      this.suggestions = result
 | 
			
		||||
    }, error => {
 | 
			
		||||
      this.suggestions = null
 | 
			
		||||
    })
 | 
			
		||||
    this.documentsService
 | 
			
		||||
      .getMetadata(doc.id)
 | 
			
		||||
      .pipe(first())
 | 
			
		||||
      .subscribe(
 | 
			
		||||
        (result) => {
 | 
			
		||||
          this.metadata = result
 | 
			
		||||
        },
 | 
			
		||||
        (error) => {
 | 
			
		||||
          this.metadata = null
 | 
			
		||||
        }
 | 
			
		||||
      )
 | 
			
		||||
    this.documentsService
 | 
			
		||||
      .getSuggestions(doc.id)
 | 
			
		||||
      .pipe(first())
 | 
			
		||||
      .subscribe(
 | 
			
		||||
        (result) => {
 | 
			
		||||
          this.suggestions = result
 | 
			
		||||
        },
 | 
			
		||||
        (error) => {
 | 
			
		||||
          this.suggestions = null
 | 
			
		||||
        }
 | 
			
		||||
      )
 | 
			
		||||
    this.title = this.documentTitlePipe.transform(doc.title)
 | 
			
		||||
    this.documentForm.patchValue(doc)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  createDocumentType(newName: string) {
 | 
			
		||||
    var modal = this.modalService.open(DocumentTypeEditDialogComponent, {backdrop: 'static'})
 | 
			
		||||
    var modal = this.modalService.open(DocumentTypeEditDialogComponent, {
 | 
			
		||||
      backdrop: 'static',
 | 
			
		||||
    })
 | 
			
		||||
    modal.componentInstance.dialogMode = 'create'
 | 
			
		||||
    if (newName) modal.componentInstance.object = { name: newName }
 | 
			
		||||
    modal.componentInstance.success.pipe(switchMap(newDocumentType => {
 | 
			
		||||
      return this.documentTypeService.listAll().pipe(map(documentTypes => ({newDocumentType, documentTypes})))
 | 
			
		||||
    }))
 | 
			
		||||
    .pipe(takeUntil(this.unsubscribeNotifier))
 | 
			
		||||
    .subscribe(({newDocumentType, documentTypes}) => {
 | 
			
		||||
      this.documentTypes = documentTypes.results
 | 
			
		||||
      this.documentForm.get('document_type').setValue(newDocumentType.id)
 | 
			
		||||
    })
 | 
			
		||||
    modal.componentInstance.success
 | 
			
		||||
      .pipe(
 | 
			
		||||
        switchMap((newDocumentType) => {
 | 
			
		||||
          return this.documentTypeService
 | 
			
		||||
            .listAll()
 | 
			
		||||
            .pipe(map((documentTypes) => ({ newDocumentType, documentTypes })))
 | 
			
		||||
        })
 | 
			
		||||
      )
 | 
			
		||||
      .pipe(takeUntil(this.unsubscribeNotifier))
 | 
			
		||||
      .subscribe(({ newDocumentType, documentTypes }) => {
 | 
			
		||||
        this.documentTypes = documentTypes.results
 | 
			
		||||
        this.documentForm.get('document_type').setValue(newDocumentType.id)
 | 
			
		||||
      })
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  createCorrespondent(newName: string) {
 | 
			
		||||
    var modal = this.modalService.open(CorrespondentEditDialogComponent, {backdrop: 'static'})
 | 
			
		||||
    var modal = this.modalService.open(CorrespondentEditDialogComponent, {
 | 
			
		||||
      backdrop: 'static',
 | 
			
		||||
    })
 | 
			
		||||
    modal.componentInstance.dialogMode = 'create'
 | 
			
		||||
    if (newName) modal.componentInstance.object = { name: newName }
 | 
			
		||||
    modal.componentInstance.success.pipe(switchMap(newCorrespondent => {
 | 
			
		||||
      return this.correspondentService.listAll().pipe(map(correspondents => ({newCorrespondent, correspondents})))
 | 
			
		||||
    }))
 | 
			
		||||
    .pipe(takeUntil(this.unsubscribeNotifier))
 | 
			
		||||
    .subscribe(({newCorrespondent, correspondents}) => {
 | 
			
		||||
      this.correspondents = correspondents.results
 | 
			
		||||
      this.documentForm.get('correspondent').setValue(newCorrespondent.id)
 | 
			
		||||
    })
 | 
			
		||||
    modal.componentInstance.success
 | 
			
		||||
      .pipe(
 | 
			
		||||
        switchMap((newCorrespondent) => {
 | 
			
		||||
          return this.correspondentService
 | 
			
		||||
            .listAll()
 | 
			
		||||
            .pipe(
 | 
			
		||||
              map((correspondents) => ({ newCorrespondent, correspondents }))
 | 
			
		||||
            )
 | 
			
		||||
        })
 | 
			
		||||
      )
 | 
			
		||||
      .pipe(takeUntil(this.unsubscribeNotifier))
 | 
			
		||||
      .subscribe(({ newCorrespondent, correspondents }) => {
 | 
			
		||||
        this.correspondents = correspondents.results
 | 
			
		||||
        this.documentForm.get('correspondent').setValue(newCorrespondent.id)
 | 
			
		||||
      })
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  discard() {
 | 
			
		||||
    this.documentsService.get(this.documentId).pipe(first()).subscribe(doc => {
 | 
			
		||||
      Object.assign(this.document, doc)
 | 
			
		||||
      this.title = doc.title
 | 
			
		||||
      this.documentForm.patchValue(doc)
 | 
			
		||||
    }, error => {this.router.navigate(['404'])})
 | 
			
		||||
    this.documentsService
 | 
			
		||||
      .get(this.documentId)
 | 
			
		||||
      .pipe(first())
 | 
			
		||||
      .subscribe(
 | 
			
		||||
        (doc) => {
 | 
			
		||||
          Object.assign(this.document, doc)
 | 
			
		||||
          this.title = doc.title
 | 
			
		||||
          this.documentForm.patchValue(doc)
 | 
			
		||||
        },
 | 
			
		||||
        (error) => {
 | 
			
		||||
          this.router.navigate(['404'])
 | 
			
		||||
        }
 | 
			
		||||
      )
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  save() {
 | 
			
		||||
    this.networkActive = true
 | 
			
		||||
    this.store.next(this.documentForm.value)
 | 
			
		||||
    this.documentsService.update(this.document).pipe(first()).subscribe(result => {
 | 
			
		||||
      this.close()
 | 
			
		||||
      this.networkActive = false
 | 
			
		||||
      this.error = null
 | 
			
		||||
    }, error => {
 | 
			
		||||
      this.networkActive = false
 | 
			
		||||
      this.error = error.error
 | 
			
		||||
    })
 | 
			
		||||
    this.documentsService
 | 
			
		||||
      .update(this.document)
 | 
			
		||||
      .pipe(first())
 | 
			
		||||
      .subscribe(
 | 
			
		||||
        (result) => {
 | 
			
		||||
          this.close()
 | 
			
		||||
          this.networkActive = false
 | 
			
		||||
          this.error = null
 | 
			
		||||
        },
 | 
			
		||||
        (error) => {
 | 
			
		||||
          this.networkActive = false
 | 
			
		||||
          this.error = error.error
 | 
			
		||||
        }
 | 
			
		||||
      )
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  saveEditNext() {
 | 
			
		||||
    this.networkActive = true
 | 
			
		||||
    this.store.next(this.documentForm.value)
 | 
			
		||||
    this.documentsService.update(this.document).pipe(switchMap(updateResult => {
 | 
			
		||||
      return this.documentListViewService.getNext(this.documentId).pipe(map(nextDocId => ({nextDocId, updateResult})))
 | 
			
		||||
    })).pipe(switchMap(({nextDocId, updateResult}) => {
 | 
			
		||||
      if (nextDocId && updateResult) return this.openDocumentService.closeDocument(this.document).pipe(map(closeResult => ({updateResult, nextDocId, closeResult})))
 | 
			
		||||
    }))
 | 
			
		||||
    .pipe(first())
 | 
			
		||||
    .subscribe(({updateResult, nextDocId, closeResult}) => {
 | 
			
		||||
      this.error = null
 | 
			
		||||
      this.networkActive = false
 | 
			
		||||
      if (closeResult && updateResult && nextDocId) {
 | 
			
		||||
        this.router.navigate(['documents', nextDocId])
 | 
			
		||||
        this.titleInput?.focus()
 | 
			
		||||
      }
 | 
			
		||||
    }, error => {
 | 
			
		||||
      this.networkActive = false
 | 
			
		||||
      this.error = error.error
 | 
			
		||||
    })
 | 
			
		||||
    this.documentsService
 | 
			
		||||
      .update(this.document)
 | 
			
		||||
      .pipe(
 | 
			
		||||
        switchMap((updateResult) => {
 | 
			
		||||
          return this.documentListViewService
 | 
			
		||||
            .getNext(this.documentId)
 | 
			
		||||
            .pipe(map((nextDocId) => ({ nextDocId, updateResult })))
 | 
			
		||||
        })
 | 
			
		||||
      )
 | 
			
		||||
      .pipe(
 | 
			
		||||
        switchMap(({ nextDocId, updateResult }) => {
 | 
			
		||||
          if (nextDocId && updateResult)
 | 
			
		||||
            return this.openDocumentService
 | 
			
		||||
              .closeDocument(this.document)
 | 
			
		||||
              .pipe(
 | 
			
		||||
                map((closeResult) => ({ updateResult, nextDocId, closeResult }))
 | 
			
		||||
              )
 | 
			
		||||
        })
 | 
			
		||||
      )
 | 
			
		||||
      .pipe(first())
 | 
			
		||||
      .subscribe(
 | 
			
		||||
        ({ updateResult, nextDocId, closeResult }) => {
 | 
			
		||||
          this.error = null
 | 
			
		||||
          this.networkActive = false
 | 
			
		||||
          if (closeResult && updateResult && nextDocId) {
 | 
			
		||||
            this.router.navigate(['documents', nextDocId])
 | 
			
		||||
            this.titleInput?.focus()
 | 
			
		||||
          }
 | 
			
		||||
        },
 | 
			
		||||
        (error) => {
 | 
			
		||||
          this.networkActive = false
 | 
			
		||||
          this.error = error.error
 | 
			
		||||
        }
 | 
			
		||||
      )
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  close() {
 | 
			
		||||
    this.openDocumentService.closeDocument(this.document).pipe(first()).subscribe(closed => {
 | 
			
		||||
      if (!closed) return;
 | 
			
		||||
      if (this.documentListViewService.activeSavedViewId) {
 | 
			
		||||
        this.router.navigate(['view', this.documentListViewService.activeSavedViewId])
 | 
			
		||||
      } else {
 | 
			
		||||
        this.router.navigate(['documents'])
 | 
			
		||||
      }
 | 
			
		||||
    })
 | 
			
		||||
    this.openDocumentService
 | 
			
		||||
      .closeDocument(this.document)
 | 
			
		||||
      .pipe(first())
 | 
			
		||||
      .subscribe((closed) => {
 | 
			
		||||
        if (!closed) return
 | 
			
		||||
        if (this.documentListViewService.activeSavedViewId) {
 | 
			
		||||
          this.router.navigate([
 | 
			
		||||
            'view',
 | 
			
		||||
            this.documentListViewService.activeSavedViewId,
 | 
			
		||||
          ])
 | 
			
		||||
        } else {
 | 
			
		||||
          this.router.navigate(['documents'])
 | 
			
		||||
        }
 | 
			
		||||
      })
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  delete() {
 | 
			
		||||
    let modal = this.modalService.open(ConfirmDialogComponent, {backdrop: 'static'})
 | 
			
		||||
    let modal = this.modalService.open(ConfirmDialogComponent, {
 | 
			
		||||
      backdrop: 'static',
 | 
			
		||||
    })
 | 
			
		||||
    modal.componentInstance.title = $localize`Confirm delete`
 | 
			
		||||
    modal.componentInstance.messageBold = $localize`Do you really want to delete document "${this.document.title}"?`
 | 
			
		||||
    modal.componentInstance.message = $localize`The files for this document will be deleted permanently. This operation cannot be undone.`
 | 
			
		||||
    modal.componentInstance.btnClass = "btn-danger"
 | 
			
		||||
    modal.componentInstance.btnClass = 'btn-danger'
 | 
			
		||||
    modal.componentInstance.btnCaption = $localize`Delete document`
 | 
			
		||||
    modal.componentInstance.confirmClicked.pipe(switchMap(() => {
 | 
			
		||||
      modal.componentInstance.buttonsEnabled = false
 | 
			
		||||
      return this.documentsService.delete(this.document)
 | 
			
		||||
    }))
 | 
			
		||||
    .pipe(takeUntil(this.unsubscribeNotifier))
 | 
			
		||||
    .subscribe(() => {
 | 
			
		||||
      modal.close()
 | 
			
		||||
      this.close()
 | 
			
		||||
    }, error => {
 | 
			
		||||
      this.toastService.showError($localize`Error deleting document: ${JSON.stringify(error)}`)
 | 
			
		||||
      modal.componentInstance.buttonsEnabled = true
 | 
			
		||||
    })
 | 
			
		||||
    modal.componentInstance.confirmClicked
 | 
			
		||||
      .pipe(
 | 
			
		||||
        switchMap(() => {
 | 
			
		||||
          modal.componentInstance.buttonsEnabled = false
 | 
			
		||||
          return this.documentsService.delete(this.document)
 | 
			
		||||
        })
 | 
			
		||||
      )
 | 
			
		||||
      .pipe(takeUntil(this.unsubscribeNotifier))
 | 
			
		||||
      .subscribe(
 | 
			
		||||
        () => {
 | 
			
		||||
          modal.close()
 | 
			
		||||
          this.close()
 | 
			
		||||
        },
 | 
			
		||||
        (error) => {
 | 
			
		||||
          this.toastService.showError(
 | 
			
		||||
            $localize`Error deleting document: ${JSON.stringify(error)}`
 | 
			
		||||
          )
 | 
			
		||||
          modal.componentInstance.buttonsEnabled = true
 | 
			
		||||
        }
 | 
			
		||||
      )
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  moreLike() {
 | 
			
		||||
    this.documentListViewService.quickFilter([{rule_type: FILTER_FULLTEXT_MORELIKE, value: this.documentId.toString()}])
 | 
			
		||||
    this.documentListViewService.quickFilter([
 | 
			
		||||
      {
 | 
			
		||||
        rule_type: FILTER_FULLTEXT_MORELIKE,
 | 
			
		||||
        value: this.documentId.toString(),
 | 
			
		||||
      },
 | 
			
		||||
    ])
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  hasNext() {
 | 
			
		||||
@@ -311,5 +447,4 @@ export class DocumentDetailComponent implements OnInit, OnDestroy, DirtyComponen
 | 
			
		||||
  pdfPreviewLoaded(pdf: PDFDocumentProxy) {
 | 
			
		||||
    this.previewNumPages = pdf.numPages
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,25 +1,24 @@
 | 
			
		||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
 | 
			
		||||
import { ComponentFixture, TestBed } from '@angular/core/testing'
 | 
			
		||||
 | 
			
		||||
import { MetadataCollapseComponent } from './metadata-collapse.component';
 | 
			
		||||
import { MetadataCollapseComponent } from './metadata-collapse.component'
 | 
			
		||||
 | 
			
		||||
describe('MetadataCollapseComponent', () => {
 | 
			
		||||
  let component: MetadataCollapseComponent;
 | 
			
		||||
  let fixture: ComponentFixture<MetadataCollapseComponent>;
 | 
			
		||||
  let component: MetadataCollapseComponent
 | 
			
		||||
  let fixture: ComponentFixture<MetadataCollapseComponent>
 | 
			
		||||
 | 
			
		||||
  beforeEach(async () => {
 | 
			
		||||
    await TestBed.configureTestingModule({
 | 
			
		||||
      declarations: [ MetadataCollapseComponent ]
 | 
			
		||||
    })
 | 
			
		||||
    .compileComponents();
 | 
			
		||||
  });
 | 
			
		||||
      declarations: [MetadataCollapseComponent],
 | 
			
		||||
    }).compileComponents()
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  beforeEach(() => {
 | 
			
		||||
    fixture = TestBed.createComponent(MetadataCollapseComponent);
 | 
			
		||||
    component = fixture.componentInstance;
 | 
			
		||||
    fixture.detectChanges();
 | 
			
		||||
  });
 | 
			
		||||
    fixture = TestBed.createComponent(MetadataCollapseComponent)
 | 
			
		||||
    component = fixture.componentInstance
 | 
			
		||||
    fixture.detectChanges()
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  it('should create', () => {
 | 
			
		||||
    expect(component).toBeTruthy();
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
    expect(component).toBeTruthy()
 | 
			
		||||
  })
 | 
			
		||||
})
 | 
			
		||||
 
 | 
			
		||||
@@ -1,13 +1,12 @@
 | 
			
		||||
import { Component, Input, OnInit } from '@angular/core';
 | 
			
		||||
import { Component, Input, OnInit } from '@angular/core'
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'app-metadata-collapse',
 | 
			
		||||
  templateUrl: './metadata-collapse.component.html',
 | 
			
		||||
  styleUrls: ['./metadata-collapse.component.scss']
 | 
			
		||||
  styleUrls: ['./metadata-collapse.component.scss'],
 | 
			
		||||
})
 | 
			
		||||
export class MetadataCollapseComponent implements OnInit {
 | 
			
		||||
 | 
			
		||||
  constructor() { }
 | 
			
		||||
  constructor() {}
 | 
			
		||||
 | 
			
		||||
  expand = false
 | 
			
		||||
 | 
			
		||||
@@ -17,7 +16,5 @@ export class MetadataCollapseComponent implements OnInit {
 | 
			
		||||
  @Input()
 | 
			
		||||
  title = $localize`Metadata`
 | 
			
		||||
 | 
			
		||||
  ngOnInit(): void {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ngOnInit(): void {}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,25 +1,24 @@
 | 
			
		||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
 | 
			
		||||
import { ComponentFixture, TestBed } from '@angular/core/testing'
 | 
			
		||||
 | 
			
		||||
import { BulkEditorComponent } from './bulk-editor.component';
 | 
			
		||||
import { BulkEditorComponent } from './bulk-editor.component'
 | 
			
		||||
 | 
			
		||||
describe('BulkEditorComponent', () => {
 | 
			
		||||
  let component: BulkEditorComponent;
 | 
			
		||||
  let fixture: ComponentFixture<BulkEditorComponent>;
 | 
			
		||||
  let component: BulkEditorComponent
 | 
			
		||||
  let fixture: ComponentFixture<BulkEditorComponent>
 | 
			
		||||
 | 
			
		||||
  beforeEach(async () => {
 | 
			
		||||
    await TestBed.configureTestingModule({
 | 
			
		||||
      declarations: [ BulkEditorComponent ]
 | 
			
		||||
    })
 | 
			
		||||
    .compileComponents();
 | 
			
		||||
  });
 | 
			
		||||
      declarations: [BulkEditorComponent],
 | 
			
		||||
    }).compileComponents()
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  beforeEach(() => {
 | 
			
		||||
    fixture = TestBed.createComponent(BulkEditorComponent);
 | 
			
		||||
    component = fixture.componentInstance;
 | 
			
		||||
    fixture.detectChanges();
 | 
			
		||||
  });
 | 
			
		||||
    fixture = TestBed.createComponent(BulkEditorComponent)
 | 
			
		||||
    component = fixture.componentInstance
 | 
			
		||||
    fixture.detectChanges()
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  it('should create', () => {
 | 
			
		||||
    expect(component).toBeTruthy();
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
    expect(component).toBeTruthy()
 | 
			
		||||
  })
 | 
			
		||||
})
 | 
			
		||||
 
 | 
			
		||||
@@ -1,29 +1,37 @@
 | 
			
		||||
import { Component } from '@angular/core';
 | 
			
		||||
import { PaperlessTag } from 'src/app/data/paperless-tag';
 | 
			
		||||
import { PaperlessCorrespondent } from 'src/app/data/paperless-correspondent';
 | 
			
		||||
import { PaperlessDocumentType } from 'src/app/data/paperless-document-type';
 | 
			
		||||
import { TagService } from 'src/app/services/rest/tag.service';
 | 
			
		||||
import { CorrespondentService } from 'src/app/services/rest/correspondent.service';
 | 
			
		||||
import { DocumentTypeService } from 'src/app/services/rest/document-type.service';
 | 
			
		||||
import { DocumentListViewService } from 'src/app/services/document-list-view.service';
 | 
			
		||||
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
 | 
			
		||||
import { DocumentService, SelectionDataItem } from 'src/app/services/rest/document.service';
 | 
			
		||||
import { OpenDocumentsService } from 'src/app/services/open-documents.service';
 | 
			
		||||
import { ConfirmDialogComponent } from 'src/app/components/common/confirm-dialog/confirm-dialog.component';
 | 
			
		||||
import { ChangedItems, FilterableDropdownSelectionModel } from '../../common/filterable-dropdown/filterable-dropdown.component';
 | 
			
		||||
import { ToggleableItemState } from '../../common/filterable-dropdown/toggleable-dropdown-button/toggleable-dropdown-button.component';
 | 
			
		||||
import { MatchingModel } from 'src/app/data/matching-model';
 | 
			
		||||
import { SettingsService, SETTINGS_KEYS } from 'src/app/services/settings.service';
 | 
			
		||||
import { ToastService } from 'src/app/services/toast.service';
 | 
			
		||||
import { saveAs } from 'file-saver';
 | 
			
		||||
import { Component } from '@angular/core'
 | 
			
		||||
import { PaperlessTag } from 'src/app/data/paperless-tag'
 | 
			
		||||
import { PaperlessCorrespondent } from 'src/app/data/paperless-correspondent'
 | 
			
		||||
import { PaperlessDocumentType } from 'src/app/data/paperless-document-type'
 | 
			
		||||
import { TagService } from 'src/app/services/rest/tag.service'
 | 
			
		||||
import { CorrespondentService } from 'src/app/services/rest/correspondent.service'
 | 
			
		||||
import { DocumentTypeService } from 'src/app/services/rest/document-type.service'
 | 
			
		||||
import { DocumentListViewService } from 'src/app/services/document-list-view.service'
 | 
			
		||||
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
 | 
			
		||||
import {
 | 
			
		||||
  DocumentService,
 | 
			
		||||
  SelectionDataItem,
 | 
			
		||||
} from 'src/app/services/rest/document.service'
 | 
			
		||||
import { OpenDocumentsService } from 'src/app/services/open-documents.service'
 | 
			
		||||
import { ConfirmDialogComponent } from 'src/app/components/common/confirm-dialog/confirm-dialog.component'
 | 
			
		||||
import {
 | 
			
		||||
  ChangedItems,
 | 
			
		||||
  FilterableDropdownSelectionModel,
 | 
			
		||||
} from '../../common/filterable-dropdown/filterable-dropdown.component'
 | 
			
		||||
import { ToggleableItemState } from '../../common/filterable-dropdown/toggleable-dropdown-button/toggleable-dropdown-button.component'
 | 
			
		||||
import { MatchingModel } from 'src/app/data/matching-model'
 | 
			
		||||
import {
 | 
			
		||||
  SettingsService,
 | 
			
		||||
  SETTINGS_KEYS,
 | 
			
		||||
} from 'src/app/services/settings.service'
 | 
			
		||||
import { ToastService } from 'src/app/services/toast.service'
 | 
			
		||||
import { saveAs } from 'file-saver'
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'app-bulk-editor',
 | 
			
		||||
  templateUrl: './bulk-editor.component.html',
 | 
			
		||||
  styleUrls: ['./bulk-editor.component.scss']
 | 
			
		||||
  styleUrls: ['./bulk-editor.component.scss'],
 | 
			
		||||
})
 | 
			
		||||
export class BulkEditorComponent {
 | 
			
		||||
 | 
			
		||||
  tags: PaperlessTag[]
 | 
			
		||||
  correspondents: PaperlessCorrespondent[]
 | 
			
		||||
  documentTypes: PaperlessDocumentType[]
 | 
			
		||||
@@ -42,43 +50,63 @@ export class BulkEditorComponent {
 | 
			
		||||
    private openDocumentService: OpenDocumentsService,
 | 
			
		||||
    private settings: SettingsService,
 | 
			
		||||
    private toastService: ToastService
 | 
			
		||||
  ) { }
 | 
			
		||||
  ) {}
 | 
			
		||||
 | 
			
		||||
  applyOnClose: boolean = this.settings.get(SETTINGS_KEYS.BULK_EDIT_APPLY_ON_CLOSE)
 | 
			
		||||
  showConfirmationDialogs: boolean = this.settings.get(SETTINGS_KEYS.BULK_EDIT_CONFIRMATION_DIALOGS)
 | 
			
		||||
  applyOnClose: boolean = this.settings.get(
 | 
			
		||||
    SETTINGS_KEYS.BULK_EDIT_APPLY_ON_CLOSE
 | 
			
		||||
  )
 | 
			
		||||
  showConfirmationDialogs: boolean = this.settings.get(
 | 
			
		||||
    SETTINGS_KEYS.BULK_EDIT_CONFIRMATION_DIALOGS
 | 
			
		||||
  )
 | 
			
		||||
 | 
			
		||||
  ngOnInit() {
 | 
			
		||||
    this.tagService.listAll().subscribe(result => this.tags = result.results)
 | 
			
		||||
    this.correspondentService.listAll().subscribe(result => this.correspondents = result.results)
 | 
			
		||||
    this.documentTypeService.listAll().subscribe(result => this.documentTypes = result.results)
 | 
			
		||||
    this.tagService
 | 
			
		||||
      .listAll()
 | 
			
		||||
      .subscribe((result) => (this.tags = result.results))
 | 
			
		||||
    this.correspondentService
 | 
			
		||||
      .listAll()
 | 
			
		||||
      .subscribe((result) => (this.correspondents = result.results))
 | 
			
		||||
    this.documentTypeService
 | 
			
		||||
      .listAll()
 | 
			
		||||
      .subscribe((result) => (this.documentTypes = result.results))
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private executeBulkOperation(modal, method: string, args) {
 | 
			
		||||
    if (modal) {
 | 
			
		||||
      modal.componentInstance.buttonsEnabled = false
 | 
			
		||||
    }
 | 
			
		||||
    this.documentService.bulkEdit(Array.from(this.list.selected), method, args).subscribe(
 | 
			
		||||
      response => {
 | 
			
		||||
        this.list.reload()
 | 
			
		||||
        this.list.reduceSelectionToFilter()
 | 
			
		||||
        this.list.selected.forEach(id => {
 | 
			
		||||
          this.openDocumentService.refreshDocument(id)
 | 
			
		||||
        })
 | 
			
		||||
        if (modal) {
 | 
			
		||||
          modal.close()
 | 
			
		||||
    this.documentService
 | 
			
		||||
      .bulkEdit(Array.from(this.list.selected), method, args)
 | 
			
		||||
      .subscribe(
 | 
			
		||||
        (response) => {
 | 
			
		||||
          this.list.reload()
 | 
			
		||||
          this.list.reduceSelectionToFilter()
 | 
			
		||||
          this.list.selected.forEach((id) => {
 | 
			
		||||
            this.openDocumentService.refreshDocument(id)
 | 
			
		||||
          })
 | 
			
		||||
          if (modal) {
 | 
			
		||||
            modal.close()
 | 
			
		||||
          }
 | 
			
		||||
        },
 | 
			
		||||
        (error) => {
 | 
			
		||||
          if (modal) {
 | 
			
		||||
            modal.componentInstance.buttonsEnabled = true
 | 
			
		||||
          }
 | 
			
		||||
          this.toastService.showError(
 | 
			
		||||
            $localize`Error executing bulk operation: ${JSON.stringify(
 | 
			
		||||
              error.error
 | 
			
		||||
            )}`
 | 
			
		||||
          )
 | 
			
		||||
        }
 | 
			
		||||
      }, error => {
 | 
			
		||||
        if (modal) {
 | 
			
		||||
          modal.componentInstance.buttonsEnabled = true
 | 
			
		||||
        }
 | 
			
		||||
        this.toastService.showError($localize`Error executing bulk operation: ${JSON.stringify(error.error)}`)
 | 
			
		||||
      }
 | 
			
		||||
    )
 | 
			
		||||
      )
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private applySelectionData(items: SelectionDataItem[], selectionModel: FilterableDropdownSelectionModel) {
 | 
			
		||||
  private applySelectionData(
 | 
			
		||||
    items: SelectionDataItem[],
 | 
			
		||||
    selectionModel: FilterableDropdownSelectionModel
 | 
			
		||||
  ) {
 | 
			
		||||
    let selectionData = new Map<number, ToggleableItemState>()
 | 
			
		||||
    items.forEach(i => {
 | 
			
		||||
    items.forEach((i) => {
 | 
			
		||||
      if (i.document_count == this.list.selected.size) {
 | 
			
		||||
        selectionData.set(i.id, ToggleableItemState.Selected)
 | 
			
		||||
      } else if (i.document_count > 0) {
 | 
			
		||||
@@ -89,129 +117,210 @@ export class BulkEditorComponent {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  openTagsDropdown() {
 | 
			
		||||
    this.documentService.getSelectionData(Array.from(this.list.selected)).subscribe(s => {
 | 
			
		||||
      this.applySelectionData(s.selected_tags, this.tagSelectionModel)
 | 
			
		||||
    })
 | 
			
		||||
    this.documentService
 | 
			
		||||
      .getSelectionData(Array.from(this.list.selected))
 | 
			
		||||
      .subscribe((s) => {
 | 
			
		||||
        this.applySelectionData(s.selected_tags, this.tagSelectionModel)
 | 
			
		||||
      })
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  openDocumentTypeDropdown() {
 | 
			
		||||
    this.documentService.getSelectionData(Array.from(this.list.selected)).subscribe(s => {
 | 
			
		||||
      this.applySelectionData(s.selected_document_types, this.documentTypeSelectionModel)
 | 
			
		||||
    })
 | 
			
		||||
    this.documentService
 | 
			
		||||
      .getSelectionData(Array.from(this.list.selected))
 | 
			
		||||
      .subscribe((s) => {
 | 
			
		||||
        this.applySelectionData(
 | 
			
		||||
          s.selected_document_types,
 | 
			
		||||
          this.documentTypeSelectionModel
 | 
			
		||||
        )
 | 
			
		||||
      })
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  openCorrespondentDropdown() {
 | 
			
		||||
    this.documentService.getSelectionData(Array.from(this.list.selected)).subscribe(s => {
 | 
			
		||||
      this.applySelectionData(s.selected_correspondents, this.correspondentSelectionModel)
 | 
			
		||||
    })
 | 
			
		||||
    this.documentService
 | 
			
		||||
      .getSelectionData(Array.from(this.list.selected))
 | 
			
		||||
      .subscribe((s) => {
 | 
			
		||||
        this.applySelectionData(
 | 
			
		||||
          s.selected_correspondents,
 | 
			
		||||
          this.correspondentSelectionModel
 | 
			
		||||
        )
 | 
			
		||||
      })
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private _localizeList(items: MatchingModel[]) {
 | 
			
		||||
    if (items.length == 0) {
 | 
			
		||||
      return ""
 | 
			
		||||
      return ''
 | 
			
		||||
    } else if (items.length == 1) {
 | 
			
		||||
      return $localize`"${items[0].name}"`
 | 
			
		||||
    } else if (items.length == 2) {
 | 
			
		||||
      return $localize`:This is for messages like 'modify "tag1" and "tag2"':"${items[0].name}" and "${items[1].name}"`
 | 
			
		||||
    } else {
 | 
			
		||||
      let list = items.slice(0, items.length - 1).map(i => $localize`"${i.name}"`).join($localize`:this is used to separate enumerations and should probably be a comma and a whitespace in most languages:, `)
 | 
			
		||||
      return $localize`:this is for messages like 'modify "tag1", "tag2" and "tag3"':${list} and "${items[items.length - 1].name}"`
 | 
			
		||||
      let list = items
 | 
			
		||||
        .slice(0, items.length - 1)
 | 
			
		||||
        .map((i) => $localize`"${i.name}"`)
 | 
			
		||||
        .join(
 | 
			
		||||
          $localize`:this is used to separate enumerations and should probably be a comma and a whitespace in most languages:, `
 | 
			
		||||
        )
 | 
			
		||||
      return $localize`:this is for messages like 'modify "tag1", "tag2" and "tag3"':${list} and "${
 | 
			
		||||
        items[items.length - 1].name
 | 
			
		||||
      }"`
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  setTags(changedTags: ChangedItems) {
 | 
			
		||||
    if (changedTags.itemsToAdd.length == 0 && changedTags.itemsToRemove.length == 0) return
 | 
			
		||||
    if (
 | 
			
		||||
      changedTags.itemsToAdd.length == 0 &&
 | 
			
		||||
      changedTags.itemsToRemove.length == 0
 | 
			
		||||
    )
 | 
			
		||||
      return
 | 
			
		||||
 | 
			
		||||
    if (this.showConfirmationDialogs) {
 | 
			
		||||
      let modal = this.modalService.open(ConfirmDialogComponent, {backdrop: 'static'})
 | 
			
		||||
      let modal = this.modalService.open(ConfirmDialogComponent, {
 | 
			
		||||
        backdrop: 'static',
 | 
			
		||||
      })
 | 
			
		||||
      modal.componentInstance.title = $localize`Confirm tags assignment`
 | 
			
		||||
      if (changedTags.itemsToAdd.length == 1 && changedTags.itemsToRemove.length == 0) {
 | 
			
		||||
      if (
 | 
			
		||||
        changedTags.itemsToAdd.length == 1 &&
 | 
			
		||||
        changedTags.itemsToRemove.length == 0
 | 
			
		||||
      ) {
 | 
			
		||||
        let tag = changedTags.itemsToAdd[0]
 | 
			
		||||
        modal.componentInstance.message = $localize`This operation will add the tag "${tag.name}" to ${this.list.selected.size} selected document(s).`
 | 
			
		||||
      } else if (changedTags.itemsToAdd.length > 1 && changedTags.itemsToRemove.length == 0) {
 | 
			
		||||
        modal.componentInstance.message = $localize`This operation will add the tags ${this._localizeList(changedTags.itemsToAdd)} to ${this.list.selected.size} selected document(s).`
 | 
			
		||||
      } else if (changedTags.itemsToAdd.length == 0 && changedTags.itemsToRemove.length == 1) {
 | 
			
		||||
      } else if (
 | 
			
		||||
        changedTags.itemsToAdd.length > 1 &&
 | 
			
		||||
        changedTags.itemsToRemove.length == 0
 | 
			
		||||
      ) {
 | 
			
		||||
        modal.componentInstance.message = $localize`This operation will add the tags ${this._localizeList(
 | 
			
		||||
          changedTags.itemsToAdd
 | 
			
		||||
        )} to ${this.list.selected.size} selected document(s).`
 | 
			
		||||
      } else if (
 | 
			
		||||
        changedTags.itemsToAdd.length == 0 &&
 | 
			
		||||
        changedTags.itemsToRemove.length == 1
 | 
			
		||||
      ) {
 | 
			
		||||
        let tag = changedTags.itemsToRemove[0]
 | 
			
		||||
        modal.componentInstance.message = $localize`This operation will remove the tag "${tag.name}" from ${this.list.selected.size} selected document(s).`
 | 
			
		||||
      } else if (changedTags.itemsToAdd.length == 0 && changedTags.itemsToRemove.length > 1) {
 | 
			
		||||
        modal.componentInstance.message = $localize`This operation will remove the tags ${this._localizeList(changedTags.itemsToRemove)} from ${this.list.selected.size} selected document(s).`
 | 
			
		||||
      } else if (
 | 
			
		||||
        changedTags.itemsToAdd.length == 0 &&
 | 
			
		||||
        changedTags.itemsToRemove.length > 1
 | 
			
		||||
      ) {
 | 
			
		||||
        modal.componentInstance.message = $localize`This operation will remove the tags ${this._localizeList(
 | 
			
		||||
          changedTags.itemsToRemove
 | 
			
		||||
        )} from ${this.list.selected.size} selected document(s).`
 | 
			
		||||
      } else {
 | 
			
		||||
        modal.componentInstance.message = $localize`This operation will add the tags ${this._localizeList(changedTags.itemsToAdd)} and remove the tags ${this._localizeList(changedTags.itemsToRemove)} on ${this.list.selected.size} selected document(s).`
 | 
			
		||||
        modal.componentInstance.message = $localize`This operation will add the tags ${this._localizeList(
 | 
			
		||||
          changedTags.itemsToAdd
 | 
			
		||||
        )} and remove the tags ${this._localizeList(
 | 
			
		||||
          changedTags.itemsToRemove
 | 
			
		||||
        )} on ${this.list.selected.size} selected document(s).`
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      modal.componentInstance.btnClass = "btn-warning"
 | 
			
		||||
      modal.componentInstance.btnClass = 'btn-warning'
 | 
			
		||||
      modal.componentInstance.btnCaption = $localize`Confirm`
 | 
			
		||||
      modal.componentInstance.confirmClicked.subscribe(() => {
 | 
			
		||||
        this.executeBulkOperation(modal, 'modify_tags', {"add_tags": changedTags.itemsToAdd.map(t => t.id), "remove_tags": changedTags.itemsToRemove.map(t => t.id)})
 | 
			
		||||
        this.executeBulkOperation(modal, 'modify_tags', {
 | 
			
		||||
          add_tags: changedTags.itemsToAdd.map((t) => t.id),
 | 
			
		||||
          remove_tags: changedTags.itemsToRemove.map((t) => t.id),
 | 
			
		||||
        })
 | 
			
		||||
      })
 | 
			
		||||
    } else {
 | 
			
		||||
      this.executeBulkOperation(null, 'modify_tags', {"add_tags": changedTags.itemsToAdd.map(t => t.id), "remove_tags": changedTags.itemsToRemove.map(t => t.id)})
 | 
			
		||||
      this.executeBulkOperation(null, 'modify_tags', {
 | 
			
		||||
        add_tags: changedTags.itemsToAdd.map((t) => t.id),
 | 
			
		||||
        remove_tags: changedTags.itemsToRemove.map((t) => t.id),
 | 
			
		||||
      })
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  setCorrespondents(changedCorrespondents: ChangedItems) {
 | 
			
		||||
    if (changedCorrespondents.itemsToAdd.length == 0 && changedCorrespondents.itemsToRemove.length == 0) return
 | 
			
		||||
    if (
 | 
			
		||||
      changedCorrespondents.itemsToAdd.length == 0 &&
 | 
			
		||||
      changedCorrespondents.itemsToRemove.length == 0
 | 
			
		||||
    )
 | 
			
		||||
      return
 | 
			
		||||
 | 
			
		||||
    let correspondent = changedCorrespondents.itemsToAdd.length > 0 ? changedCorrespondents.itemsToAdd[0] : null
 | 
			
		||||
    let correspondent =
 | 
			
		||||
      changedCorrespondents.itemsToAdd.length > 0
 | 
			
		||||
        ? changedCorrespondents.itemsToAdd[0]
 | 
			
		||||
        : null
 | 
			
		||||
 | 
			
		||||
    if (this.showConfirmationDialogs) {
 | 
			
		||||
      let modal = this.modalService.open(ConfirmDialogComponent, {backdrop: 'static'})
 | 
			
		||||
      let modal = this.modalService.open(ConfirmDialogComponent, {
 | 
			
		||||
        backdrop: 'static',
 | 
			
		||||
      })
 | 
			
		||||
      modal.componentInstance.title = $localize`Confirm correspondent assignment`
 | 
			
		||||
      if (correspondent) {
 | 
			
		||||
        modal.componentInstance.message = $localize`This operation will assign the correspondent "${correspondent.name}" to ${this.list.selected.size} selected document(s).`
 | 
			
		||||
      } else {
 | 
			
		||||
        modal.componentInstance.message = $localize`This operation will remove the correspondent from ${this.list.selected.size} selected document(s).`
 | 
			
		||||
      }
 | 
			
		||||
      modal.componentInstance.btnClass = "btn-warning"
 | 
			
		||||
      modal.componentInstance.btnClass = 'btn-warning'
 | 
			
		||||
      modal.componentInstance.btnCaption = $localize`Confirm`
 | 
			
		||||
      modal.componentInstance.confirmClicked.subscribe(() => {
 | 
			
		||||
        this.executeBulkOperation(modal, 'set_correspondent', {"correspondent": correspondent ? correspondent.id : null})
 | 
			
		||||
        this.executeBulkOperation(modal, 'set_correspondent', {
 | 
			
		||||
          correspondent: correspondent ? correspondent.id : null,
 | 
			
		||||
        })
 | 
			
		||||
      })
 | 
			
		||||
    } else {
 | 
			
		||||
      this.executeBulkOperation(null, 'set_correspondent', {"correspondent": correspondent ? correspondent.id : null})
 | 
			
		||||
      this.executeBulkOperation(null, 'set_correspondent', {
 | 
			
		||||
        correspondent: correspondent ? correspondent.id : null,
 | 
			
		||||
      })
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  setDocumentTypes(changedDocumentTypes: ChangedItems) {
 | 
			
		||||
    if (changedDocumentTypes.itemsToAdd.length == 0 && changedDocumentTypes.itemsToRemove.length == 0) return
 | 
			
		||||
    if (
 | 
			
		||||
      changedDocumentTypes.itemsToAdd.length == 0 &&
 | 
			
		||||
      changedDocumentTypes.itemsToRemove.length == 0
 | 
			
		||||
    )
 | 
			
		||||
      return
 | 
			
		||||
 | 
			
		||||
    let documentType = changedDocumentTypes.itemsToAdd.length > 0 ? changedDocumentTypes.itemsToAdd[0] : null
 | 
			
		||||
    let documentType =
 | 
			
		||||
      changedDocumentTypes.itemsToAdd.length > 0
 | 
			
		||||
        ? changedDocumentTypes.itemsToAdd[0]
 | 
			
		||||
        : null
 | 
			
		||||
 | 
			
		||||
    if (this.showConfirmationDialogs) {
 | 
			
		||||
      let modal = this.modalService.open(ConfirmDialogComponent, {backdrop: 'static'})
 | 
			
		||||
      let modal = this.modalService.open(ConfirmDialogComponent, {
 | 
			
		||||
        backdrop: 'static',
 | 
			
		||||
      })
 | 
			
		||||
      modal.componentInstance.title = $localize`Confirm document type assignment`
 | 
			
		||||
      if (documentType) {
 | 
			
		||||
        modal.componentInstance.message = $localize`This operation will assign the document type "${documentType.name}" to ${this.list.selected.size} selected document(s).`
 | 
			
		||||
      } else {
 | 
			
		||||
        modal.componentInstance.message = $localize`This operation will remove the document type from ${this.list.selected.size} selected document(s).`
 | 
			
		||||
      }
 | 
			
		||||
      modal.componentInstance.btnClass = "btn-warning"
 | 
			
		||||
      modal.componentInstance.btnClass = 'btn-warning'
 | 
			
		||||
      modal.componentInstance.btnCaption = $localize`Confirm`
 | 
			
		||||
      modal.componentInstance.confirmClicked.subscribe(() => {
 | 
			
		||||
        this.executeBulkOperation(modal, 'set_document_type', {"document_type": documentType ? documentType.id : null})
 | 
			
		||||
        this.executeBulkOperation(modal, 'set_document_type', {
 | 
			
		||||
          document_type: documentType ? documentType.id : null,
 | 
			
		||||
        })
 | 
			
		||||
      })
 | 
			
		||||
    } else {
 | 
			
		||||
      this.executeBulkOperation(null, 'set_document_type', {"document_type": documentType ? documentType.id : null})
 | 
			
		||||
      this.executeBulkOperation(null, 'set_document_type', {
 | 
			
		||||
        document_type: documentType ? documentType.id : null,
 | 
			
		||||
      })
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  applyDelete() {
 | 
			
		||||
    let modal = this.modalService.open(ConfirmDialogComponent, {backdrop: 'static'})
 | 
			
		||||
    let modal = this.modalService.open(ConfirmDialogComponent, {
 | 
			
		||||
      backdrop: 'static',
 | 
			
		||||
    })
 | 
			
		||||
    modal.componentInstance.delayConfirm(5)
 | 
			
		||||
    modal.componentInstance.title = $localize`Delete confirm`
 | 
			
		||||
    modal.componentInstance.messageBold = $localize`This operation will permanently delete ${this.list.selected.size} selected document(s).`
 | 
			
		||||
    modal.componentInstance.message = $localize`This operation cannot be undone.`
 | 
			
		||||
    modal.componentInstance.btnClass = "btn-danger"
 | 
			
		||||
    modal.componentInstance.btnClass = 'btn-danger'
 | 
			
		||||
    modal.componentInstance.btnCaption = $localize`Delete document(s)`
 | 
			
		||||
    modal.componentInstance.confirmClicked.subscribe(() => {
 | 
			
		||||
      modal.componentInstance.buttonsEnabled = false
 | 
			
		||||
      this.executeBulkOperation(modal, "delete", {})
 | 
			
		||||
      this.executeBulkOperation(modal, 'delete', {})
 | 
			
		||||
    })
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  downloadSelected(content = "archive") {
 | 
			
		||||
    this.documentService.bulkDownload(Array.from(this.list.selected), content).subscribe((result: any) => {
 | 
			
		||||
      saveAs(result, 'documents.zip');
 | 
			
		||||
    })
 | 
			
		||||
  downloadSelected(content = 'archive') {
 | 
			
		||||
    this.documentService
 | 
			
		||||
      .bulkDownload(Array.from(this.list.selected), content)
 | 
			
		||||
      .subscribe((result: any) => {
 | 
			
		||||
        saveAs(result, 'documents.zip')
 | 
			
		||||
      })
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,25 +1,24 @@
 | 
			
		||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
 | 
			
		||||
import { ComponentFixture, TestBed } from '@angular/core/testing'
 | 
			
		||||
 | 
			
		||||
import { DocumentCardLargeComponent } from './document-card-large.component';
 | 
			
		||||
import { DocumentCardLargeComponent } from './document-card-large.component'
 | 
			
		||||
 | 
			
		||||
describe('DocumentCardLargeComponent', () => {
 | 
			
		||||
  let component: DocumentCardLargeComponent;
 | 
			
		||||
  let fixture: ComponentFixture<DocumentCardLargeComponent>;
 | 
			
		||||
  let component: DocumentCardLargeComponent
 | 
			
		||||
  let fixture: ComponentFixture<DocumentCardLargeComponent>
 | 
			
		||||
 | 
			
		||||
  beforeEach(async () => {
 | 
			
		||||
    await TestBed.configureTestingModule({
 | 
			
		||||
      declarations: [ DocumentCardLargeComponent ]
 | 
			
		||||
    })
 | 
			
		||||
    .compileComponents();
 | 
			
		||||
  });
 | 
			
		||||
      declarations: [DocumentCardLargeComponent],
 | 
			
		||||
    }).compileComponents()
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  beforeEach(() => {
 | 
			
		||||
    fixture = TestBed.createComponent(DocumentCardLargeComponent);
 | 
			
		||||
    component = fixture.componentInstance;
 | 
			
		||||
    fixture.detectChanges();
 | 
			
		||||
  });
 | 
			
		||||
    fixture = TestBed.createComponent(DocumentCardLargeComponent)
 | 
			
		||||
    component = fixture.componentInstance
 | 
			
		||||
    fixture.detectChanges()
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  it('should create', () => {
 | 
			
		||||
    expect(component).toBeTruthy();
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
    expect(component).toBeTruthy()
 | 
			
		||||
  })
 | 
			
		||||
})
 | 
			
		||||
 
 | 
			
		||||
@@ -1,20 +1,36 @@
 | 
			
		||||
import { Component, EventEmitter, Input, OnInit, Output, ViewChild } from '@angular/core';
 | 
			
		||||
import { DomSanitizer } from '@angular/platform-browser';
 | 
			
		||||
import { PaperlessDocument } from 'src/app/data/paperless-document';
 | 
			
		||||
import { DocumentService } from 'src/app/services/rest/document.service';
 | 
			
		||||
import { SettingsService, SETTINGS_KEYS } from 'src/app/services/settings.service';
 | 
			
		||||
import { NgbPopover } from '@ng-bootstrap/ng-bootstrap';
 | 
			
		||||
import { DocumentListViewService } from 'src/app/services/document-list-view.service';
 | 
			
		||||
import { FILTER_FULLTEXT_MORELIKE } from 'src/app/data/filter-rule-type';
 | 
			
		||||
import {
 | 
			
		||||
  Component,
 | 
			
		||||
  EventEmitter,
 | 
			
		||||
  Input,
 | 
			
		||||
  OnInit,
 | 
			
		||||
  Output,
 | 
			
		||||
  ViewChild,
 | 
			
		||||
} from '@angular/core'
 | 
			
		||||
import { DomSanitizer } from '@angular/platform-browser'
 | 
			
		||||
import { PaperlessDocument } from 'src/app/data/paperless-document'
 | 
			
		||||
import { DocumentService } from 'src/app/services/rest/document.service'
 | 
			
		||||
import {
 | 
			
		||||
  SettingsService,
 | 
			
		||||
  SETTINGS_KEYS,
 | 
			
		||||
} from 'src/app/services/settings.service'
 | 
			
		||||
import { NgbPopover } from '@ng-bootstrap/ng-bootstrap'
 | 
			
		||||
import { DocumentListViewService } from 'src/app/services/document-list-view.service'
 | 
			
		||||
import { FILTER_FULLTEXT_MORELIKE } from 'src/app/data/filter-rule-type'
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'app-document-card-large',
 | 
			
		||||
  templateUrl: './document-card-large.component.html',
 | 
			
		||||
  styleUrls: ['./document-card-large.component.scss', '../popover-preview/popover-preview.scss']
 | 
			
		||||
  styleUrls: [
 | 
			
		||||
    './document-card-large.component.scss',
 | 
			
		||||
    '../popover-preview/popover-preview.scss',
 | 
			
		||||
  ],
 | 
			
		||||
})
 | 
			
		||||
export class DocumentCardLargeComponent implements OnInit {
 | 
			
		||||
 | 
			
		||||
  constructor(private documentService: DocumentService, private sanitizer: DomSanitizer, private settingsService: SettingsService) { }
 | 
			
		||||
  constructor(
 | 
			
		||||
    private documentService: DocumentService,
 | 
			
		||||
    private sanitizer: DomSanitizer,
 | 
			
		||||
    private settingsService: SettingsService
 | 
			
		||||
  ) {}
 | 
			
		||||
 | 
			
		||||
  @Input()
 | 
			
		||||
  selected = false
 | 
			
		||||
@@ -39,7 +55,7 @@ export class DocumentCardLargeComponent implements OnInit {
 | 
			
		||||
  clickDocumentType = new EventEmitter<number>()
 | 
			
		||||
 | 
			
		||||
  @Output()
 | 
			
		||||
  clickMoreLike= new EventEmitter()
 | 
			
		||||
  clickMoreLike = new EventEmitter()
 | 
			
		||||
 | 
			
		||||
  @ViewChild('popover') popover: NgbPopover
 | 
			
		||||
 | 
			
		||||
@@ -49,17 +65,16 @@ export class DocumentCardLargeComponent implements OnInit {
 | 
			
		||||
  get searchScoreClass() {
 | 
			
		||||
    if (this.document.__search_hit__) {
 | 
			
		||||
      if (this.document.__search_hit__.score > 0.7) {
 | 
			
		||||
        return "success"
 | 
			
		||||
        return 'success'
 | 
			
		||||
      } else if (this.document.__search_hit__.score > 0.3) {
 | 
			
		||||
        return "warning"
 | 
			
		||||
        return 'warning'
 | 
			
		||||
      } else {
 | 
			
		||||
        return "danger"
 | 
			
		||||
        return 'danger'
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ngOnInit(): void {
 | 
			
		||||
  }
 | 
			
		||||
  ngOnInit(): void {}
 | 
			
		||||
 | 
			
		||||
  getIsThumbInverted() {
 | 
			
		||||
    return this.settingsService.get(SETTINGS_KEYS.DARK_MODE_THUMB_INVERTED)
 | 
			
		||||
@@ -90,7 +105,7 @@ export class DocumentCardLargeComponent implements OnInit {
 | 
			
		||||
        } else {
 | 
			
		||||
          this.popover.close()
 | 
			
		||||
        }
 | 
			
		||||
      }, 600);
 | 
			
		||||
      }, 600)
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,25 +1,24 @@
 | 
			
		||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
 | 
			
		||||
import { ComponentFixture, TestBed } from '@angular/core/testing'
 | 
			
		||||
 | 
			
		||||
import { DocumentCardSmallComponent } from './document-card-small.component';
 | 
			
		||||
import { DocumentCardSmallComponent } from './document-card-small.component'
 | 
			
		||||
 | 
			
		||||
describe('DocumentCardSmallComponent', () => {
 | 
			
		||||
  let component: DocumentCardSmallComponent;
 | 
			
		||||
  let fixture: ComponentFixture<DocumentCardSmallComponent>;
 | 
			
		||||
  let component: DocumentCardSmallComponent
 | 
			
		||||
  let fixture: ComponentFixture<DocumentCardSmallComponent>
 | 
			
		||||
 | 
			
		||||
  beforeEach(async () => {
 | 
			
		||||
    await TestBed.configureTestingModule({
 | 
			
		||||
      declarations: [ DocumentCardSmallComponent ]
 | 
			
		||||
    })
 | 
			
		||||
    .compileComponents();
 | 
			
		||||
  });
 | 
			
		||||
      declarations: [DocumentCardSmallComponent],
 | 
			
		||||
    }).compileComponents()
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  beforeEach(() => {
 | 
			
		||||
    fixture = TestBed.createComponent(DocumentCardSmallComponent);
 | 
			
		||||
    component = fixture.componentInstance;
 | 
			
		||||
    fixture.detectChanges();
 | 
			
		||||
  });
 | 
			
		||||
    fixture = TestBed.createComponent(DocumentCardSmallComponent)
 | 
			
		||||
    component = fixture.componentInstance
 | 
			
		||||
    fixture.detectChanges()
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  it('should create', () => {
 | 
			
		||||
    expect(component).toBeTruthy();
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
    expect(component).toBeTruthy()
 | 
			
		||||
  })
 | 
			
		||||
})
 | 
			
		||||
 
 | 
			
		||||
@@ -1,18 +1,33 @@
 | 
			
		||||
import { Component, EventEmitter, Input, OnInit, Output, ViewChild } from '@angular/core';
 | 
			
		||||
import { map } from 'rxjs/operators';
 | 
			
		||||
import { PaperlessDocument } from 'src/app/data/paperless-document';
 | 
			
		||||
import { DocumentService } from 'src/app/services/rest/document.service';
 | 
			
		||||
import { SettingsService, SETTINGS_KEYS } from 'src/app/services/settings.service';
 | 
			
		||||
import { NgbPopover } from '@ng-bootstrap/ng-bootstrap';
 | 
			
		||||
import {
 | 
			
		||||
  Component,
 | 
			
		||||
  EventEmitter,
 | 
			
		||||
  Input,
 | 
			
		||||
  OnInit,
 | 
			
		||||
  Output,
 | 
			
		||||
  ViewChild,
 | 
			
		||||
} from '@angular/core'
 | 
			
		||||
import { map } from 'rxjs/operators'
 | 
			
		||||
import { PaperlessDocument } from 'src/app/data/paperless-document'
 | 
			
		||||
import { DocumentService } from 'src/app/services/rest/document.service'
 | 
			
		||||
import {
 | 
			
		||||
  SettingsService,
 | 
			
		||||
  SETTINGS_KEYS,
 | 
			
		||||
} from 'src/app/services/settings.service'
 | 
			
		||||
import { NgbPopover } from '@ng-bootstrap/ng-bootstrap'
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'app-document-card-small',
 | 
			
		||||
  templateUrl: './document-card-small.component.html',
 | 
			
		||||
  styleUrls: ['./document-card-small.component.scss', '../popover-preview/popover-preview.scss']
 | 
			
		||||
  styleUrls: [
 | 
			
		||||
    './document-card-small.component.scss',
 | 
			
		||||
    '../popover-preview/popover-preview.scss',
 | 
			
		||||
  ],
 | 
			
		||||
})
 | 
			
		||||
export class DocumentCardSmallComponent implements OnInit {
 | 
			
		||||
 | 
			
		||||
  constructor(private documentService: DocumentService, private settingsService: SettingsService) { }
 | 
			
		||||
  constructor(
 | 
			
		||||
    private documentService: DocumentService,
 | 
			
		||||
    private settingsService: SettingsService
 | 
			
		||||
  ) {}
 | 
			
		||||
 | 
			
		||||
  @Input()
 | 
			
		||||
  selected = false
 | 
			
		||||
@@ -39,8 +54,7 @@ export class DocumentCardSmallComponent implements OnInit {
 | 
			
		||||
  mouseOnPreview = false
 | 
			
		||||
  popoverHidden = true
 | 
			
		||||
 | 
			
		||||
  ngOnInit(): void {
 | 
			
		||||
  }
 | 
			
		||||
  ngOnInit(): void {}
 | 
			
		||||
 | 
			
		||||
  getIsThumbInverted() {
 | 
			
		||||
    return this.settingsService.get(SETTINGS_KEYS.DARK_MODE_THUMB_INVERTED)
 | 
			
		||||
@@ -60,7 +74,7 @@ export class DocumentCardSmallComponent implements OnInit {
 | 
			
		||||
 | 
			
		||||
  getTagsLimited$() {
 | 
			
		||||
    return this.document.tags$.pipe(
 | 
			
		||||
      map(tags => {
 | 
			
		||||
      map((tags) => {
 | 
			
		||||
        if (tags.length > 7) {
 | 
			
		||||
          this.moreTags = tags.length - 6
 | 
			
		||||
          return tags.slice(0, 6)
 | 
			
		||||
@@ -84,7 +98,7 @@ export class DocumentCardSmallComponent implements OnInit {
 | 
			
		||||
        } else {
 | 
			
		||||
          this.popover.close()
 | 
			
		||||
        }
 | 
			
		||||
      }, 600);
 | 
			
		||||
      }, 600)
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,25 +1,24 @@
 | 
			
		||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
 | 
			
		||||
import { ComponentFixture, TestBed } from '@angular/core/testing'
 | 
			
		||||
 | 
			
		||||
import { DocumentListComponent } from './document-list.component';
 | 
			
		||||
import { DocumentListComponent } from './document-list.component'
 | 
			
		||||
 | 
			
		||||
describe('DocumentListComponent', () => {
 | 
			
		||||
  let component: DocumentListComponent;
 | 
			
		||||
  let fixture: ComponentFixture<DocumentListComponent>;
 | 
			
		||||
  let component: DocumentListComponent
 | 
			
		||||
  let fixture: ComponentFixture<DocumentListComponent>
 | 
			
		||||
 | 
			
		||||
  beforeEach(async () => {
 | 
			
		||||
    await TestBed.configureTestingModule({
 | 
			
		||||
      declarations: [ DocumentListComponent ]
 | 
			
		||||
    })
 | 
			
		||||
    .compileComponents();
 | 
			
		||||
  });
 | 
			
		||||
      declarations: [DocumentListComponent],
 | 
			
		||||
    }).compileComponents()
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  beforeEach(() => {
 | 
			
		||||
    fixture = TestBed.createComponent(DocumentListComponent);
 | 
			
		||||
    component = fixture.componentInstance;
 | 
			
		||||
    fixture.detectChanges();
 | 
			
		||||
  });
 | 
			
		||||
    fixture = TestBed.createComponent(DocumentListComponent)
 | 
			
		||||
    component = fixture.componentInstance
 | 
			
		||||
    fixture.detectChanges()
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  it('should create', () => {
 | 
			
		||||
    expect(component).toBeTruthy();
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
    expect(component).toBeTruthy()
 | 
			
		||||
  })
 | 
			
		||||
})
 | 
			
		||||
 
 | 
			
		||||
@@ -1,27 +1,39 @@
 | 
			
		||||
import { Component, OnDestroy, OnInit, QueryList, ViewChild, ViewChildren } from '@angular/core';
 | 
			
		||||
import { ActivatedRoute, Router } from '@angular/router';
 | 
			
		||||
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
 | 
			
		||||
import { Subscription } from 'rxjs';
 | 
			
		||||
import { FilterRule, isFullTextFilterRule } from 'src/app/data/filter-rule';
 | 
			
		||||
import { FILTER_FULLTEXT_MORELIKE } from 'src/app/data/filter-rule-type';
 | 
			
		||||
import { PaperlessDocument } from 'src/app/data/paperless-document';
 | 
			
		||||
import { PaperlessSavedView } from 'src/app/data/paperless-saved-view';
 | 
			
		||||
import { SortableDirective, SortEvent } from 'src/app/directives/sortable.directive';
 | 
			
		||||
import { ConsumerStatusService } from 'src/app/services/consumer-status.service';
 | 
			
		||||
import { DocumentListViewService } from 'src/app/services/document-list-view.service';
 | 
			
		||||
import { DOCUMENT_SORT_FIELDS, DOCUMENT_SORT_FIELDS_FULLTEXT } from 'src/app/services/rest/document.service';
 | 
			
		||||
import { SavedViewService } from 'src/app/services/rest/saved-view.service';
 | 
			
		||||
import { ToastService } from 'src/app/services/toast.service';
 | 
			
		||||
import { FilterEditorComponent } from './filter-editor/filter-editor.component';
 | 
			
		||||
import { SaveViewConfigDialogComponent } from './save-view-config-dialog/save-view-config-dialog.component';
 | 
			
		||||
import {
 | 
			
		||||
  Component,
 | 
			
		||||
  OnDestroy,
 | 
			
		||||
  OnInit,
 | 
			
		||||
  QueryList,
 | 
			
		||||
  ViewChild,
 | 
			
		||||
  ViewChildren,
 | 
			
		||||
} from '@angular/core'
 | 
			
		||||
import { ActivatedRoute, Router } from '@angular/router'
 | 
			
		||||
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
 | 
			
		||||
import { Subscription } from 'rxjs'
 | 
			
		||||
import { FilterRule, isFullTextFilterRule } from 'src/app/data/filter-rule'
 | 
			
		||||
import { FILTER_FULLTEXT_MORELIKE } from 'src/app/data/filter-rule-type'
 | 
			
		||||
import { PaperlessDocument } from 'src/app/data/paperless-document'
 | 
			
		||||
import { PaperlessSavedView } from 'src/app/data/paperless-saved-view'
 | 
			
		||||
import {
 | 
			
		||||
  SortableDirective,
 | 
			
		||||
  SortEvent,
 | 
			
		||||
} from 'src/app/directives/sortable.directive'
 | 
			
		||||
import { ConsumerStatusService } from 'src/app/services/consumer-status.service'
 | 
			
		||||
import { DocumentListViewService } from 'src/app/services/document-list-view.service'
 | 
			
		||||
import {
 | 
			
		||||
  DOCUMENT_SORT_FIELDS,
 | 
			
		||||
  DOCUMENT_SORT_FIELDS_FULLTEXT,
 | 
			
		||||
} from 'src/app/services/rest/document.service'
 | 
			
		||||
import { SavedViewService } from 'src/app/services/rest/saved-view.service'
 | 
			
		||||
import { ToastService } from 'src/app/services/toast.service'
 | 
			
		||||
import { FilterEditorComponent } from './filter-editor/filter-editor.component'
 | 
			
		||||
import { SaveViewConfigDialogComponent } from './save-view-config-dialog/save-view-config-dialog.component'
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'app-document-list',
 | 
			
		||||
  templateUrl: './document-list.component.html',
 | 
			
		||||
  styleUrls: ['./document-list.component.scss']
 | 
			
		||||
  styleUrls: ['./document-list.component.scss'],
 | 
			
		||||
})
 | 
			
		||||
export class DocumentListComponent implements OnInit, OnDestroy {
 | 
			
		||||
 | 
			
		||||
  constructor(
 | 
			
		||||
    public list: DocumentListViewService,
 | 
			
		||||
    public savedViewService: SavedViewService,
 | 
			
		||||
@@ -30,12 +42,12 @@ export class DocumentListComponent implements OnInit, OnDestroy {
 | 
			
		||||
    private toastService: ToastService,
 | 
			
		||||
    private modalService: NgbModal,
 | 
			
		||||
    private consumerStatusService: ConsumerStatusService
 | 
			
		||||
  ) { }
 | 
			
		||||
  ) {}
 | 
			
		||||
 | 
			
		||||
  @ViewChild("filterEditor")
 | 
			
		||||
  @ViewChild('filterEditor')
 | 
			
		||||
  private filterEditor: FilterEditorComponent
 | 
			
		||||
 | 
			
		||||
  @ViewChildren(SortableDirective) headers: QueryList<SortableDirective>;
 | 
			
		||||
  @ViewChildren(SortableDirective) headers: QueryList<SortableDirective>
 | 
			
		||||
 | 
			
		||||
  displayMode = 'smallCards' // largeCards, smallCards, details
 | 
			
		||||
 | 
			
		||||
@@ -52,7 +64,9 @@ export class DocumentListComponent implements OnInit, OnDestroy {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  getSortFields() {
 | 
			
		||||
    return isFullTextFilterRule(this.list.filterRules) ? DOCUMENT_SORT_FIELDS_FULLTEXT : DOCUMENT_SORT_FIELDS
 | 
			
		||||
    return isFullTextFilterRule(this.list.filterRules)
 | 
			
		||||
      ? DOCUMENT_SORT_FIELDS_FULLTEXT
 | 
			
		||||
      : DOCUMENT_SORT_FIELDS
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  onSort(event: SortEvent) {
 | 
			
		||||
@@ -71,14 +85,16 @@ export class DocumentListComponent implements OnInit, OnDestroy {
 | 
			
		||||
    if (localStorage.getItem('document-list:displayMode') != null) {
 | 
			
		||||
      this.displayMode = localStorage.getItem('document-list:displayMode')
 | 
			
		||||
    }
 | 
			
		||||
    this.consumptionFinishedSubscription = this.consumerStatusService.onDocumentConsumptionFinished().subscribe(() => {
 | 
			
		||||
      this.list.reload()
 | 
			
		||||
    })
 | 
			
		||||
    this.route.paramMap.subscribe(params => {
 | 
			
		||||
    this.consumptionFinishedSubscription = this.consumerStatusService
 | 
			
		||||
      .onDocumentConsumptionFinished()
 | 
			
		||||
      .subscribe(() => {
 | 
			
		||||
        this.list.reload()
 | 
			
		||||
      })
 | 
			
		||||
    this.route.paramMap.subscribe((params) => {
 | 
			
		||||
      if (params.has('id')) {
 | 
			
		||||
        this.savedViewService.getCached(+params.get('id')).subscribe(view => {
 | 
			
		||||
        this.savedViewService.getCached(+params.get('id')).subscribe((view) => {
 | 
			
		||||
          if (!view) {
 | 
			
		||||
            this.router.navigate(["404"])
 | 
			
		||||
            this.router.navigate(['404'])
 | 
			
		||||
            return
 | 
			
		||||
          }
 | 
			
		||||
          this.list.activateSavedView(view)
 | 
			
		||||
@@ -110,19 +126,23 @@ export class DocumentListComponent implements OnInit, OnDestroy {
 | 
			
		||||
        id: this.list.activeSavedViewId,
 | 
			
		||||
        filter_rules: this.list.filterRules,
 | 
			
		||||
        sort_field: this.list.sortField,
 | 
			
		||||
        sort_reverse: this.list.sortReverse
 | 
			
		||||
        sort_reverse: this.list.sortReverse,
 | 
			
		||||
      }
 | 
			
		||||
      this.savedViewService.patch(savedView).subscribe(result => {
 | 
			
		||||
        this.toastService.showInfo($localize`View "${this.list.activeSavedViewTitle}" saved successfully.`)
 | 
			
		||||
      this.savedViewService.patch(savedView).subscribe((result) => {
 | 
			
		||||
        this.toastService.showInfo(
 | 
			
		||||
          $localize`View "${this.list.activeSavedViewTitle}" saved successfully.`
 | 
			
		||||
        )
 | 
			
		||||
        this.unmodifiedFilterRules = this.list.filterRules
 | 
			
		||||
      })
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  saveViewConfigAs() {
 | 
			
		||||
    let modal = this.modalService.open(SaveViewConfigDialogComponent, {backdrop: 'static'})
 | 
			
		||||
    let modal = this.modalService.open(SaveViewConfigDialogComponent, {
 | 
			
		||||
      backdrop: 'static',
 | 
			
		||||
    })
 | 
			
		||||
    modal.componentInstance.defaultName = this.filterEditor.generateFilterName()
 | 
			
		||||
    modal.componentInstance.saveClicked.subscribe(formValue => {
 | 
			
		||||
    modal.componentInstance.saveClicked.subscribe((formValue) => {
 | 
			
		||||
      modal.componentInstance.buttonsEnabled = false
 | 
			
		||||
      let savedView: PaperlessSavedView = {
 | 
			
		||||
        name: formValue.name,
 | 
			
		||||
@@ -130,16 +150,21 @@ export class DocumentListComponent implements OnInit, OnDestroy {
 | 
			
		||||
        show_in_sidebar: formValue.showInSideBar,
 | 
			
		||||
        filter_rules: this.list.filterRules,
 | 
			
		||||
        sort_reverse: this.list.sortReverse,
 | 
			
		||||
        sort_field: this.list.sortField
 | 
			
		||||
        sort_field: this.list.sortField,
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      this.savedViewService.create(savedView).subscribe(() => {
 | 
			
		||||
        modal.close()
 | 
			
		||||
        this.toastService.showInfo($localize`View "${savedView.name}" created successfully.`)
 | 
			
		||||
      }, error => {
 | 
			
		||||
        modal.componentInstance.error = error.error
 | 
			
		||||
        modal.componentInstance.buttonsEnabled = true
 | 
			
		||||
      })
 | 
			
		||||
      this.savedViewService.create(savedView).subscribe(
 | 
			
		||||
        () => {
 | 
			
		||||
          modal.close()
 | 
			
		||||
          this.toastService.showInfo(
 | 
			
		||||
            $localize`View "${savedView.name}" created successfully.`
 | 
			
		||||
          )
 | 
			
		||||
        },
 | 
			
		||||
        (error) => {
 | 
			
		||||
          modal.componentInstance.error = error.error
 | 
			
		||||
          modal.componentInstance.buttonsEnabled = true
 | 
			
		||||
        }
 | 
			
		||||
      )
 | 
			
		||||
    })
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@@ -170,7 +195,9 @@ export class DocumentListComponent implements OnInit, OnDestroy {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  clickMoreLike(documentID: number) {
 | 
			
		||||
    this.list.quickFilter([{rule_type: FILTER_FULLTEXT_MORELIKE, value: documentID.toString()}])
 | 
			
		||||
    this.list.quickFilter([
 | 
			
		||||
      { rule_type: FILTER_FULLTEXT_MORELIKE, value: documentID.toString() },
 | 
			
		||||
    ])
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  trackByDocumentId(index, item: PaperlessDocument) {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,25 +1,24 @@
 | 
			
		||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
 | 
			
		||||
import { ComponentFixture, TestBed } from '@angular/core/testing'
 | 
			
		||||
 | 
			
		||||
import { FilterEditorComponent } from './filter-editor.component';
 | 
			
		||||
import { FilterEditorComponent } from './filter-editor.component'
 | 
			
		||||
 | 
			
		||||
describe('FilterEditorComponent', () => {
 | 
			
		||||
  let component: FilterEditorComponent;
 | 
			
		||||
  let fixture: ComponentFixture<FilterEditorComponent>;
 | 
			
		||||
  let component: FilterEditorComponent
 | 
			
		||||
  let fixture: ComponentFixture<FilterEditorComponent>
 | 
			
		||||
 | 
			
		||||
  beforeEach(async () => {
 | 
			
		||||
    await TestBed.configureTestingModule({
 | 
			
		||||
      declarations: [ FilterEditorComponent ]
 | 
			
		||||
    })
 | 
			
		||||
    .compileComponents();
 | 
			
		||||
  });
 | 
			
		||||
      declarations: [FilterEditorComponent],
 | 
			
		||||
    }).compileComponents()
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  beforeEach(() => {
 | 
			
		||||
    fixture = TestBed.createComponent(FilterEditorComponent);
 | 
			
		||||
    component = fixture.componentInstance;
 | 
			
		||||
    fixture.detectChanges();
 | 
			
		||||
  });
 | 
			
		||||
    fixture = TestBed.createComponent(FilterEditorComponent)
 | 
			
		||||
    component = fixture.componentInstance
 | 
			
		||||
    fixture.detectChanges()
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  it('should create', () => {
 | 
			
		||||
    expect(component).toBeTruthy();
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
    expect(component).toBeTruthy()
 | 
			
		||||
  })
 | 
			
		||||
})
 | 
			
		||||
 
 | 
			
		||||
@@ -1,56 +1,85 @@
 | 
			
		||||
import { Component, EventEmitter, Input, Output, OnInit, OnDestroy, ViewChild, ElementRef } from '@angular/core';
 | 
			
		||||
import { PaperlessTag } from 'src/app/data/paperless-tag';
 | 
			
		||||
import { PaperlessCorrespondent } from 'src/app/data/paperless-correspondent';
 | 
			
		||||
import { PaperlessDocumentType } from 'src/app/data/paperless-document-type';
 | 
			
		||||
import { Subject, Subscription } from 'rxjs';
 | 
			
		||||
import { debounceTime, distinctUntilChanged } from 'rxjs/operators';
 | 
			
		||||
import { DocumentTypeService } from 'src/app/services/rest/document-type.service';
 | 
			
		||||
import { TagService } from 'src/app/services/rest/tag.service';
 | 
			
		||||
import { CorrespondentService } from 'src/app/services/rest/correspondent.service';
 | 
			
		||||
import { FilterRule } from 'src/app/data/filter-rule';
 | 
			
		||||
import { FILTER_ADDED_AFTER, FILTER_ADDED_BEFORE, FILTER_ASN, FILTER_CORRESPONDENT, FILTER_CREATED_AFTER, FILTER_CREATED_BEFORE, FILTER_DOCUMENT_TYPE, FILTER_FULLTEXT_MORELIKE, FILTER_FULLTEXT_QUERY, FILTER_HAS_ANY_TAG, FILTER_HAS_TAGS_ALL, FILTER_HAS_TAGS_ANY, FILTER_DOES_NOT_HAVE_TAG, FILTER_TITLE, FILTER_TITLE_CONTENT } from 'src/app/data/filter-rule-type';
 | 
			
		||||
import { FilterableDropdownSelectionModel } from '../../common/filterable-dropdown/filterable-dropdown.component';
 | 
			
		||||
import { ToggleableItemState } from '../../common/filterable-dropdown/toggleable-dropdown-button/toggleable-dropdown-button.component';
 | 
			
		||||
import { DocumentService } from 'src/app/services/rest/document.service';
 | 
			
		||||
import { PaperlessDocument } from 'src/app/data/paperless-document';
 | 
			
		||||
import {
 | 
			
		||||
  Component,
 | 
			
		||||
  EventEmitter,
 | 
			
		||||
  Input,
 | 
			
		||||
  Output,
 | 
			
		||||
  OnInit,
 | 
			
		||||
  OnDestroy,
 | 
			
		||||
  ViewChild,
 | 
			
		||||
  ElementRef,
 | 
			
		||||
} from '@angular/core'
 | 
			
		||||
import { PaperlessTag } from 'src/app/data/paperless-tag'
 | 
			
		||||
import { PaperlessCorrespondent } from 'src/app/data/paperless-correspondent'
 | 
			
		||||
import { PaperlessDocumentType } from 'src/app/data/paperless-document-type'
 | 
			
		||||
import { Subject, Subscription } from 'rxjs'
 | 
			
		||||
import { debounceTime, distinctUntilChanged } from 'rxjs/operators'
 | 
			
		||||
import { DocumentTypeService } from 'src/app/services/rest/document-type.service'
 | 
			
		||||
import { TagService } from 'src/app/services/rest/tag.service'
 | 
			
		||||
import { CorrespondentService } from 'src/app/services/rest/correspondent.service'
 | 
			
		||||
import { FilterRule } from 'src/app/data/filter-rule'
 | 
			
		||||
import {
 | 
			
		||||
  FILTER_ADDED_AFTER,
 | 
			
		||||
  FILTER_ADDED_BEFORE,
 | 
			
		||||
  FILTER_ASN,
 | 
			
		||||
  FILTER_CORRESPONDENT,
 | 
			
		||||
  FILTER_CREATED_AFTER,
 | 
			
		||||
  FILTER_CREATED_BEFORE,
 | 
			
		||||
  FILTER_DOCUMENT_TYPE,
 | 
			
		||||
  FILTER_FULLTEXT_MORELIKE,
 | 
			
		||||
  FILTER_FULLTEXT_QUERY,
 | 
			
		||||
  FILTER_HAS_ANY_TAG,
 | 
			
		||||
  FILTER_HAS_TAGS_ALL,
 | 
			
		||||
  FILTER_HAS_TAGS_ANY,
 | 
			
		||||
  FILTER_DOES_NOT_HAVE_TAG,
 | 
			
		||||
  FILTER_TITLE,
 | 
			
		||||
  FILTER_TITLE_CONTENT,
 | 
			
		||||
} from 'src/app/data/filter-rule-type'
 | 
			
		||||
import { FilterableDropdownSelectionModel } from '../../common/filterable-dropdown/filterable-dropdown.component'
 | 
			
		||||
import { ToggleableItemState } from '../../common/filterable-dropdown/toggleable-dropdown-button/toggleable-dropdown-button.component'
 | 
			
		||||
import { DocumentService } from 'src/app/services/rest/document.service'
 | 
			
		||||
import { PaperlessDocument } from 'src/app/data/paperless-document'
 | 
			
		||||
 | 
			
		||||
const TEXT_FILTER_TARGET_TITLE = "title"
 | 
			
		||||
const TEXT_FILTER_TARGET_TITLE_CONTENT = "title-content"
 | 
			
		||||
const TEXT_FILTER_TARGET_ASN = "asn"
 | 
			
		||||
const TEXT_FILTER_TARGET_FULLTEXT_QUERY = "fulltext-query"
 | 
			
		||||
const TEXT_FILTER_TARGET_FULLTEXT_MORELIKE = "fulltext-morelike"
 | 
			
		||||
const TEXT_FILTER_TARGET_TITLE = 'title'
 | 
			
		||||
const TEXT_FILTER_TARGET_TITLE_CONTENT = 'title-content'
 | 
			
		||||
const TEXT_FILTER_TARGET_ASN = 'asn'
 | 
			
		||||
const TEXT_FILTER_TARGET_FULLTEXT_QUERY = 'fulltext-query'
 | 
			
		||||
const TEXT_FILTER_TARGET_FULLTEXT_MORELIKE = 'fulltext-morelike'
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'app-filter-editor',
 | 
			
		||||
  templateUrl: './filter-editor.component.html',
 | 
			
		||||
  styleUrls: ['./filter-editor.component.scss']
 | 
			
		||||
  styleUrls: ['./filter-editor.component.scss'],
 | 
			
		||||
})
 | 
			
		||||
export class FilterEditorComponent implements OnInit, OnDestroy {
 | 
			
		||||
 | 
			
		||||
  generateFilterName() {
 | 
			
		||||
    if (this.filterRules.length == 1) {
 | 
			
		||||
      let rule = this.filterRules[0]
 | 
			
		||||
      switch(this.filterRules[0].rule_type) {
 | 
			
		||||
 | 
			
		||||
      switch (this.filterRules[0].rule_type) {
 | 
			
		||||
        case FILTER_CORRESPONDENT:
 | 
			
		||||
          if (rule.value) {
 | 
			
		||||
            return $localize`Correspondent: ${this.correspondents.find(c => c.id == +rule.value)?.name}`
 | 
			
		||||
            return $localize`Correspondent: ${
 | 
			
		||||
              this.correspondents.find((c) => c.id == +rule.value)?.name
 | 
			
		||||
            }`
 | 
			
		||||
          } else {
 | 
			
		||||
            return $localize`Without correspondent`
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
        case FILTER_DOCUMENT_TYPE:
 | 
			
		||||
          if (rule.value) {
 | 
			
		||||
            return $localize`Type: ${this.documentTypes.find(dt => dt.id == +rule.value)?.name}`
 | 
			
		||||
            return $localize`Type: ${
 | 
			
		||||
              this.documentTypes.find((dt) => dt.id == +rule.value)?.name
 | 
			
		||||
            }`
 | 
			
		||||
          } else {
 | 
			
		||||
            return $localize`Without document type`
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
        case FILTER_HAS_TAGS_ALL:
 | 
			
		||||
          return $localize`Tag: ${this.tags.find(t => t.id == +rule.value)?.name}`
 | 
			
		||||
          return $localize`Tag: ${
 | 
			
		||||
            this.tags.find((t) => t.id == +rule.value)?.name
 | 
			
		||||
          }`
 | 
			
		||||
 | 
			
		||||
        case FILTER_HAS_ANY_TAG:
 | 
			
		||||
          if (rule.value == "false") {
 | 
			
		||||
          if (rule.value == 'false') {
 | 
			
		||||
            return $localize`Without any tag`
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
@@ -62,7 +91,7 @@ export class FilterEditorComponent implements OnInit, OnDestroy {
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return ""
 | 
			
		||||
    return ''
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  constructor(
 | 
			
		||||
@@ -70,28 +99,37 @@ export class FilterEditorComponent implements OnInit, OnDestroy {
 | 
			
		||||
    private tagService: TagService,
 | 
			
		||||
    private correspondentService: CorrespondentService,
 | 
			
		||||
    private documentService: DocumentService
 | 
			
		||||
  ) { }
 | 
			
		||||
  ) {}
 | 
			
		||||
 | 
			
		||||
  @ViewChild("textFilterInput")
 | 
			
		||||
  @ViewChild('textFilterInput')
 | 
			
		||||
  textFilterInput: ElementRef
 | 
			
		||||
 | 
			
		||||
  tags: PaperlessTag[] = []
 | 
			
		||||
  correspondents: PaperlessCorrespondent[] = []
 | 
			
		||||
  documentTypes: PaperlessDocumentType[] = []
 | 
			
		||||
 | 
			
		||||
  _textFilter = ""
 | 
			
		||||
  _textFilter = ''
 | 
			
		||||
  _moreLikeId: number
 | 
			
		||||
  _moreLikeDoc: PaperlessDocument
 | 
			
		||||
 | 
			
		||||
  get textFilterTargets() {
 | 
			
		||||
    let targets = [
 | 
			
		||||
      {id: TEXT_FILTER_TARGET_TITLE, name: $localize`Title`},
 | 
			
		||||
      {id: TEXT_FILTER_TARGET_TITLE_CONTENT, name: $localize`Title & content`},
 | 
			
		||||
      {id: TEXT_FILTER_TARGET_ASN, name: $localize`ASN`},
 | 
			
		||||
      {id: TEXT_FILTER_TARGET_FULLTEXT_QUERY, name: $localize`Advanced search`}
 | 
			
		||||
      { id: TEXT_FILTER_TARGET_TITLE, name: $localize`Title` },
 | 
			
		||||
      {
 | 
			
		||||
        id: TEXT_FILTER_TARGET_TITLE_CONTENT,
 | 
			
		||||
        name: $localize`Title & content`,
 | 
			
		||||
      },
 | 
			
		||||
      { id: TEXT_FILTER_TARGET_ASN, name: $localize`ASN` },
 | 
			
		||||
      {
 | 
			
		||||
        id: TEXT_FILTER_TARGET_FULLTEXT_QUERY,
 | 
			
		||||
        name: $localize`Advanced search`,
 | 
			
		||||
      },
 | 
			
		||||
    ]
 | 
			
		||||
    if (this.textFilterTarget == TEXT_FILTER_TARGET_FULLTEXT_MORELIKE) {
 | 
			
		||||
      targets.push({id: TEXT_FILTER_TARGET_FULLTEXT_MORELIKE, name: $localize`More like`})
 | 
			
		||||
      targets.push({
 | 
			
		||||
        id: TEXT_FILTER_TARGET_FULLTEXT_MORELIKE,
 | 
			
		||||
        name: $localize`More like`,
 | 
			
		||||
      })
 | 
			
		||||
    }
 | 
			
		||||
    return targets
 | 
			
		||||
  }
 | 
			
		||||
@@ -99,10 +137,10 @@ export class FilterEditorComponent implements OnInit, OnDestroy {
 | 
			
		||||
  textFilterTarget = TEXT_FILTER_TARGET_TITLE_CONTENT
 | 
			
		||||
 | 
			
		||||
  get textFilterTargetName() {
 | 
			
		||||
    return this.textFilterTargets.find(t => t.id == this.textFilterTarget)?.name
 | 
			
		||||
    return this.textFilterTargets.find((t) => t.id == this.textFilterTarget)
 | 
			
		||||
      ?.name
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  tagSelectionModel = new FilterableDropdownSelectionModel()
 | 
			
		||||
  correspondentSelectionModel = new FilterableDropdownSelectionModel()
 | 
			
		||||
  documentTypeSelectionModel = new FilterableDropdownSelectionModel()
 | 
			
		||||
@@ -126,7 +164,7 @@ export class FilterEditorComponent implements OnInit, OnDestroy {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Input()
 | 
			
		||||
  set filterRules (value: FilterRule[]) {
 | 
			
		||||
  set filterRules(value: FilterRule[]) {
 | 
			
		||||
    this._filterRules = value
 | 
			
		||||
 | 
			
		||||
    this.documentTypeSelectionModel.clear(false)
 | 
			
		||||
@@ -139,7 +177,7 @@ export class FilterEditorComponent implements OnInit, OnDestroy {
 | 
			
		||||
    this.dateCreatedBefore = null
 | 
			
		||||
    this.dateCreatedAfter = null
 | 
			
		||||
 | 
			
		||||
    value.forEach(rule => {
 | 
			
		||||
    value.forEach((rule) => {
 | 
			
		||||
      switch (rule.rule_type) {
 | 
			
		||||
        case FILTER_TITLE:
 | 
			
		||||
          this._textFilter = rule.value
 | 
			
		||||
@@ -160,7 +198,7 @@ export class FilterEditorComponent implements OnInit, OnDestroy {
 | 
			
		||||
        case FILTER_FULLTEXT_MORELIKE:
 | 
			
		||||
          this._moreLikeId = +rule.value
 | 
			
		||||
          this.textFilterTarget = TEXT_FILTER_TARGET_FULLTEXT_MORELIKE
 | 
			
		||||
          this.documentService.get(this._moreLikeId).subscribe(result => {
 | 
			
		||||
          this.documentService.get(this._moreLikeId).subscribe((result) => {
 | 
			
		||||
            this._moreLikeDoc = result
 | 
			
		||||
            this._textFilter = result.title
 | 
			
		||||
          })
 | 
			
		||||
@@ -178,23 +216,43 @@ export class FilterEditorComponent implements OnInit, OnDestroy {
 | 
			
		||||
          this.dateAddedBefore = rule.value
 | 
			
		||||
          break
 | 
			
		||||
        case FILTER_HAS_TAGS_ALL:
 | 
			
		||||
          this.tagSelectionModel.set(rule.value ? +rule.value : null, ToggleableItemState.Selected, false)
 | 
			
		||||
          this.tagSelectionModel.set(
 | 
			
		||||
            rule.value ? +rule.value : null,
 | 
			
		||||
            ToggleableItemState.Selected,
 | 
			
		||||
            false
 | 
			
		||||
          )
 | 
			
		||||
          break
 | 
			
		||||
        case FILTER_HAS_TAGS_ANY:
 | 
			
		||||
          this.tagSelectionModel.logicalOperator = 'or'
 | 
			
		||||
          this.tagSelectionModel.set(rule.value ? +rule.value : null, ToggleableItemState.Selected, false)
 | 
			
		||||
          this.tagSelectionModel.set(
 | 
			
		||||
            rule.value ? +rule.value : null,
 | 
			
		||||
            ToggleableItemState.Selected,
 | 
			
		||||
            false
 | 
			
		||||
          )
 | 
			
		||||
          break
 | 
			
		||||
        case FILTER_HAS_ANY_TAG:
 | 
			
		||||
          this.tagSelectionModel.set(null, ToggleableItemState.Selected, false)
 | 
			
		||||
          break
 | 
			
		||||
        case FILTER_DOES_NOT_HAVE_TAG:
 | 
			
		||||
          this.tagSelectionModel.set(rule.value ? +rule.value : null, ToggleableItemState.Excluded, false)
 | 
			
		||||
          this.tagSelectionModel.set(
 | 
			
		||||
            rule.value ? +rule.value : null,
 | 
			
		||||
            ToggleableItemState.Excluded,
 | 
			
		||||
            false
 | 
			
		||||
          )
 | 
			
		||||
          break
 | 
			
		||||
        case FILTER_CORRESPONDENT:
 | 
			
		||||
          this.correspondentSelectionModel.set(rule.value ? +rule.value : null, ToggleableItemState.Selected, false)
 | 
			
		||||
          this.correspondentSelectionModel.set(
 | 
			
		||||
            rule.value ? +rule.value : null,
 | 
			
		||||
            ToggleableItemState.Selected,
 | 
			
		||||
            false
 | 
			
		||||
          )
 | 
			
		||||
          break
 | 
			
		||||
        case FILTER_DOCUMENT_TYPE:
 | 
			
		||||
          this.documentTypeSelectionModel.set(rule.value ? +rule.value : null, ToggleableItemState.Selected, false)
 | 
			
		||||
          this.documentTypeSelectionModel.set(
 | 
			
		||||
            rule.value ? +rule.value : null,
 | 
			
		||||
            ToggleableItemState.Selected,
 | 
			
		||||
            false
 | 
			
		||||
          )
 | 
			
		||||
          break
 | 
			
		||||
      }
 | 
			
		||||
    })
 | 
			
		||||
@@ -203,49 +261,104 @@ export class FilterEditorComponent implements OnInit, OnDestroy {
 | 
			
		||||
 | 
			
		||||
  get filterRules(): FilterRule[] {
 | 
			
		||||
    let filterRules: FilterRule[] = []
 | 
			
		||||
    if (this._textFilter && this.textFilterTarget == TEXT_FILTER_TARGET_TITLE_CONTENT) {
 | 
			
		||||
      filterRules.push({rule_type: FILTER_TITLE_CONTENT, value: this._textFilter})
 | 
			
		||||
    if (
 | 
			
		||||
      this._textFilter &&
 | 
			
		||||
      this.textFilterTarget == TEXT_FILTER_TARGET_TITLE_CONTENT
 | 
			
		||||
    ) {
 | 
			
		||||
      filterRules.push({
 | 
			
		||||
        rule_type: FILTER_TITLE_CONTENT,
 | 
			
		||||
        value: this._textFilter,
 | 
			
		||||
      })
 | 
			
		||||
    }
 | 
			
		||||
    if (this._textFilter && this.textFilterTarget == TEXT_FILTER_TARGET_TITLE) {
 | 
			
		||||
      filterRules.push({rule_type: FILTER_TITLE, value: this._textFilter})
 | 
			
		||||
      filterRules.push({ rule_type: FILTER_TITLE, value: this._textFilter })
 | 
			
		||||
    }
 | 
			
		||||
    if (this._textFilter && this.textFilterTarget == TEXT_FILTER_TARGET_ASN) {
 | 
			
		||||
      filterRules.push({rule_type: FILTER_ASN, value: this._textFilter})
 | 
			
		||||
      filterRules.push({ rule_type: FILTER_ASN, value: this._textFilter })
 | 
			
		||||
    }
 | 
			
		||||
    if (this._textFilter && this.textFilterTarget == TEXT_FILTER_TARGET_FULLTEXT_QUERY) {
 | 
			
		||||
      filterRules.push({rule_type: FILTER_FULLTEXT_QUERY, value: this._textFilter})
 | 
			
		||||
    if (
 | 
			
		||||
      this._textFilter &&
 | 
			
		||||
      this.textFilterTarget == TEXT_FILTER_TARGET_FULLTEXT_QUERY
 | 
			
		||||
    ) {
 | 
			
		||||
      filterRules.push({
 | 
			
		||||
        rule_type: FILTER_FULLTEXT_QUERY,
 | 
			
		||||
        value: this._textFilter,
 | 
			
		||||
      })
 | 
			
		||||
    }
 | 
			
		||||
    if (this._moreLikeId && this.textFilterTarget == TEXT_FILTER_TARGET_FULLTEXT_MORELIKE) {
 | 
			
		||||
      filterRules.push({rule_type: FILTER_FULLTEXT_MORELIKE, value: this._moreLikeId?.toString()})
 | 
			
		||||
    if (
 | 
			
		||||
      this._moreLikeId &&
 | 
			
		||||
      this.textFilterTarget == TEXT_FILTER_TARGET_FULLTEXT_MORELIKE
 | 
			
		||||
    ) {
 | 
			
		||||
      filterRules.push({
 | 
			
		||||
        rule_type: FILTER_FULLTEXT_MORELIKE,
 | 
			
		||||
        value: this._moreLikeId?.toString(),
 | 
			
		||||
      })
 | 
			
		||||
    }
 | 
			
		||||
    if (this.tagSelectionModel.isNoneSelected()) {
 | 
			
		||||
      filterRules.push({rule_type: FILTER_HAS_ANY_TAG, value: "false"})
 | 
			
		||||
      filterRules.push({ rule_type: FILTER_HAS_ANY_TAG, value: 'false' })
 | 
			
		||||
    } else {
 | 
			
		||||
      const tagFilterType = this.tagSelectionModel.logicalOperator == 'and' ? FILTER_HAS_TAGS_ALL : FILTER_HAS_TAGS_ANY
 | 
			
		||||
      this.tagSelectionModel.getSelectedItems().filter(tag => tag.id).forEach(tag => {
 | 
			
		||||
        filterRules.push({rule_type: tagFilterType, value: tag.id?.toString()})
 | 
			
		||||
      })
 | 
			
		||||
      this.tagSelectionModel.getExcludedItems().filter(tag => tag.id).forEach(tag => {
 | 
			
		||||
        filterRules.push({rule_type: FILTER_DOES_NOT_HAVE_TAG, value: tag.id?.toString()})
 | 
			
		||||
      })
 | 
			
		||||
      const tagFilterType =
 | 
			
		||||
        this.tagSelectionModel.logicalOperator == 'and'
 | 
			
		||||
          ? FILTER_HAS_TAGS_ALL
 | 
			
		||||
          : FILTER_HAS_TAGS_ANY
 | 
			
		||||
      this.tagSelectionModel
 | 
			
		||||
        .getSelectedItems()
 | 
			
		||||
        .filter((tag) => tag.id)
 | 
			
		||||
        .forEach((tag) => {
 | 
			
		||||
          filterRules.push({
 | 
			
		||||
            rule_type: tagFilterType,
 | 
			
		||||
            value: tag.id?.toString(),
 | 
			
		||||
          })
 | 
			
		||||
        })
 | 
			
		||||
      this.tagSelectionModel
 | 
			
		||||
        .getExcludedItems()
 | 
			
		||||
        .filter((tag) => tag.id)
 | 
			
		||||
        .forEach((tag) => {
 | 
			
		||||
          filterRules.push({
 | 
			
		||||
            rule_type: FILTER_DOES_NOT_HAVE_TAG,
 | 
			
		||||
            value: tag.id?.toString(),
 | 
			
		||||
          })
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
    this.correspondentSelectionModel.getSelectedItems().forEach(correspondent => {
 | 
			
		||||
      filterRules.push({rule_type: FILTER_CORRESPONDENT, value: correspondent.id?.toString()})
 | 
			
		||||
    })
 | 
			
		||||
    this.documentTypeSelectionModel.getSelectedItems().forEach(documentType => {
 | 
			
		||||
      filterRules.push({rule_type: FILTER_DOCUMENT_TYPE, value: documentType.id?.toString()})
 | 
			
		||||
    })
 | 
			
		||||
    this.correspondentSelectionModel
 | 
			
		||||
      .getSelectedItems()
 | 
			
		||||
      .forEach((correspondent) => {
 | 
			
		||||
        filterRules.push({
 | 
			
		||||
          rule_type: FILTER_CORRESPONDENT,
 | 
			
		||||
          value: correspondent.id?.toString(),
 | 
			
		||||
        })
 | 
			
		||||
      })
 | 
			
		||||
    this.documentTypeSelectionModel
 | 
			
		||||
      .getSelectedItems()
 | 
			
		||||
      .forEach((documentType) => {
 | 
			
		||||
        filterRules.push({
 | 
			
		||||
          rule_type: FILTER_DOCUMENT_TYPE,
 | 
			
		||||
          value: documentType.id?.toString(),
 | 
			
		||||
        })
 | 
			
		||||
      })
 | 
			
		||||
    if (this.dateCreatedBefore) {
 | 
			
		||||
      filterRules.push({rule_type: FILTER_CREATED_BEFORE, value: this.dateCreatedBefore})
 | 
			
		||||
      filterRules.push({
 | 
			
		||||
        rule_type: FILTER_CREATED_BEFORE,
 | 
			
		||||
        value: this.dateCreatedBefore,
 | 
			
		||||
      })
 | 
			
		||||
    }
 | 
			
		||||
    if (this.dateCreatedAfter) {
 | 
			
		||||
      filterRules.push({rule_type: FILTER_CREATED_AFTER, value: this.dateCreatedAfter})
 | 
			
		||||
      filterRules.push({
 | 
			
		||||
        rule_type: FILTER_CREATED_AFTER,
 | 
			
		||||
        value: this.dateCreatedAfter,
 | 
			
		||||
      })
 | 
			
		||||
    }
 | 
			
		||||
    if (this.dateAddedBefore) {
 | 
			
		||||
      filterRules.push({rule_type: FILTER_ADDED_BEFORE, value: this.dateAddedBefore})
 | 
			
		||||
      filterRules.push({
 | 
			
		||||
        rule_type: FILTER_ADDED_BEFORE,
 | 
			
		||||
        value: this.dateAddedBefore,
 | 
			
		||||
      })
 | 
			
		||||
    }
 | 
			
		||||
    if (this.dateAddedAfter) {
 | 
			
		||||
      filterRules.push({rule_type: FILTER_ADDED_AFTER, value: this.dateAddedAfter})
 | 
			
		||||
      filterRules.push({
 | 
			
		||||
        rule_type: FILTER_ADDED_AFTER,
 | 
			
		||||
        value: this.dateAddedAfter,
 | 
			
		||||
      })
 | 
			
		||||
    }
 | 
			
		||||
    return filterRules
 | 
			
		||||
  }
 | 
			
		||||
@@ -260,14 +373,20 @@ export class FilterEditorComponent implements OnInit, OnDestroy {
 | 
			
		||||
    if (this._unmodifiedFilterRules.length != this._filterRules.length) {
 | 
			
		||||
      modified = true
 | 
			
		||||
    } else {
 | 
			
		||||
      modified = this._unmodifiedFilterRules.some(rule => {
 | 
			
		||||
        return (this._filterRules.find(fri => fri.rule_type == rule.rule_type && fri.value == rule.value) == undefined)
 | 
			
		||||
      modified = this._unmodifiedFilterRules.some((rule) => {
 | 
			
		||||
        return (
 | 
			
		||||
          this._filterRules.find(
 | 
			
		||||
            (fri) => fri.rule_type == rule.rule_type && fri.value == rule.value
 | 
			
		||||
          ) == undefined
 | 
			
		||||
        )
 | 
			
		||||
      })
 | 
			
		||||
 | 
			
		||||
      if (!modified) {
 | 
			
		||||
        // only check other direction if we havent already determined is modified
 | 
			
		||||
        modified = this._filterRules.some(rule => {
 | 
			
		||||
          this._unmodifiedFilterRules.find(fr => fr.rule_type == rule.rule_type && fr.value == rule.value) == undefined
 | 
			
		||||
        modified = this._filterRules.some((rule) => {
 | 
			
		||||
          this._unmodifiedFilterRules.find(
 | 
			
		||||
            (fr) => fr.rule_type == rule.rule_type && fr.value == rule.value
 | 
			
		||||
          ) == undefined
 | 
			
		||||
        })
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
@@ -290,23 +409,27 @@ export class FilterEditorComponent implements OnInit, OnDestroy {
 | 
			
		||||
  subscription: Subscription
 | 
			
		||||
 | 
			
		||||
  ngOnInit() {
 | 
			
		||||
    this.tagService.listAll().subscribe(result => this.tags = result.results)
 | 
			
		||||
    this.correspondentService.listAll().subscribe(result => this.correspondents = result.results)
 | 
			
		||||
    this.documentTypeService.listAll().subscribe(result => this.documentTypes = result.results)
 | 
			
		||||
    this.tagService
 | 
			
		||||
      .listAll()
 | 
			
		||||
      .subscribe((result) => (this.tags = result.results))
 | 
			
		||||
    this.correspondentService
 | 
			
		||||
      .listAll()
 | 
			
		||||
      .subscribe((result) => (this.correspondents = result.results))
 | 
			
		||||
    this.documentTypeService
 | 
			
		||||
      .listAll()
 | 
			
		||||
      .subscribe((result) => (this.documentTypes = result.results))
 | 
			
		||||
 | 
			
		||||
    this.textFilterDebounce = new Subject<string>()
 | 
			
		||||
 | 
			
		||||
    this.subscription = this.textFilterDebounce.pipe(
 | 
			
		||||
      debounceTime(400),
 | 
			
		||||
      distinctUntilChanged()
 | 
			
		||||
    ).subscribe(text => {
 | 
			
		||||
      this._textFilter = text
 | 
			
		||||
      this.documentService.searchQuery = text
 | 
			
		||||
      this.updateRules()
 | 
			
		||||
    })
 | 
			
		||||
    this.subscription = this.textFilterDebounce
 | 
			
		||||
      .pipe(debounceTime(400), distinctUntilChanged())
 | 
			
		||||
      .subscribe((text) => {
 | 
			
		||||
        this._textFilter = text
 | 
			
		||||
        this.documentService.searchQuery = text
 | 
			
		||||
        this.updateRules()
 | 
			
		||||
      })
 | 
			
		||||
 | 
			
		||||
    if (this._textFilter) this.documentService.searchQuery = this._textFilter
 | 
			
		||||
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ngOnDestroy() {
 | 
			
		||||
@@ -324,11 +447,17 @@ export class FilterEditorComponent implements OnInit, OnDestroy {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  addCorrespondent(correspondentId: number) {
 | 
			
		||||
    this.correspondentSelectionModel.set(correspondentId, ToggleableItemState.Selected)
 | 
			
		||||
    this.correspondentSelectionModel.set(
 | 
			
		||||
      correspondentId,
 | 
			
		||||
      ToggleableItemState.Selected
 | 
			
		||||
    )
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  addDocumentType(documentTypeId: number) {
 | 
			
		||||
    this.documentTypeSelectionModel.set(documentTypeId, ToggleableItemState.Selected)
 | 
			
		||||
    this.documentTypeSelectionModel.set(
 | 
			
		||||
      documentTypeId,
 | 
			
		||||
      ToggleableItemState.Selected
 | 
			
		||||
    )
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  onTagsDropdownOpen() {
 | 
			
		||||
@@ -344,8 +473,11 @@ export class FilterEditorComponent implements OnInit, OnDestroy {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  changeTextFilterTarget(target) {
 | 
			
		||||
    if (this.textFilterTarget == TEXT_FILTER_TARGET_FULLTEXT_MORELIKE && target != TEXT_FILTER_TARGET_FULLTEXT_MORELIKE) {
 | 
			
		||||
      this._textFilter = ""
 | 
			
		||||
    if (
 | 
			
		||||
      this.textFilterTarget == TEXT_FILTER_TARGET_FULLTEXT_MORELIKE &&
 | 
			
		||||
      target != TEXT_FILTER_TARGET_FULLTEXT_MORELIKE
 | 
			
		||||
    ) {
 | 
			
		||||
      this._textFilter = ''
 | 
			
		||||
    }
 | 
			
		||||
    this.textFilterTarget = target
 | 
			
		||||
    this.textFilterInput.nativeElement.focus()
 | 
			
		||||
 
 | 
			
		||||
@@ -1,25 +1,24 @@
 | 
			
		||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
 | 
			
		||||
import { ComponentFixture, TestBed } from '@angular/core/testing'
 | 
			
		||||
 | 
			
		||||
import { SaveViewConfigDialogComponent } from './save-view-config-dialog.component';
 | 
			
		||||
import { SaveViewConfigDialogComponent } from './save-view-config-dialog.component'
 | 
			
		||||
 | 
			
		||||
describe('SaveViewConfigDialogComponent', () => {
 | 
			
		||||
  let component: SaveViewConfigDialogComponent;
 | 
			
		||||
  let fixture: ComponentFixture<SaveViewConfigDialogComponent>;
 | 
			
		||||
  let component: SaveViewConfigDialogComponent
 | 
			
		||||
  let fixture: ComponentFixture<SaveViewConfigDialogComponent>
 | 
			
		||||
 | 
			
		||||
  beforeEach(async () => {
 | 
			
		||||
    await TestBed.configureTestingModule({
 | 
			
		||||
      declarations: [ SaveViewConfigDialogComponent ]
 | 
			
		||||
    })
 | 
			
		||||
    .compileComponents();
 | 
			
		||||
  });
 | 
			
		||||
      declarations: [SaveViewConfigDialogComponent],
 | 
			
		||||
    }).compileComponents()
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  beforeEach(() => {
 | 
			
		||||
    fixture = TestBed.createComponent(SaveViewConfigDialogComponent);
 | 
			
		||||
    component = fixture.componentInstance;
 | 
			
		||||
    fixture.detectChanges();
 | 
			
		||||
  });
 | 
			
		||||
    fixture = TestBed.createComponent(SaveViewConfigDialogComponent)
 | 
			
		||||
    component = fixture.componentInstance
 | 
			
		||||
    fixture.detectChanges()
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  it('should create', () => {
 | 
			
		||||
    expect(component).toBeTruthy();
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
    expect(component).toBeTruthy()
 | 
			
		||||
  })
 | 
			
		||||
})
 | 
			
		||||
 
 | 
			
		||||
@@ -1,15 +1,14 @@
 | 
			
		||||
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
 | 
			
		||||
import { FormControl, FormGroup } from '@angular/forms';
 | 
			
		||||
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
 | 
			
		||||
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'
 | 
			
		||||
import { FormControl, FormGroup } from '@angular/forms'
 | 
			
		||||
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'app-save-view-config-dialog',
 | 
			
		||||
  templateUrl: './save-view-config-dialog.component.html',
 | 
			
		||||
  styleUrls: ['./save-view-config-dialog.component.scss']
 | 
			
		||||
  styleUrls: ['./save-view-config-dialog.component.scss'],
 | 
			
		||||
})
 | 
			
		||||
export class SaveViewConfigDialogComponent implements OnInit {
 | 
			
		||||
 | 
			
		||||
  constructor(private modal: NgbActiveModal) { }
 | 
			
		||||
  constructor(private modal: NgbActiveModal) {}
 | 
			
		||||
 | 
			
		||||
  @Output()
 | 
			
		||||
  public saveClicked = new EventEmitter()
 | 
			
		||||
@@ -22,7 +21,7 @@ export class SaveViewConfigDialogComponent implements OnInit {
 | 
			
		||||
 | 
			
		||||
  closeEnabled = false
 | 
			
		||||
 | 
			
		||||
  _defaultName = ""
 | 
			
		||||
  _defaultName = ''
 | 
			
		||||
 | 
			
		||||
  get defaultName() {
 | 
			
		||||
    return this._defaultName
 | 
			
		||||
@@ -31,7 +30,7 @@ export class SaveViewConfigDialogComponent implements OnInit {
 | 
			
		||||
  @Input()
 | 
			
		||||
  set defaultName(value: string) {
 | 
			
		||||
    this._defaultName = value
 | 
			
		||||
    this.saveViewConfigForm.patchValue({name: value})
 | 
			
		||||
    this.saveViewConfigForm.patchValue({ name: value })
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  saveViewConfigForm = new FormGroup({
 | 
			
		||||
@@ -44,7 +43,7 @@ export class SaveViewConfigDialogComponent implements OnInit {
 | 
			
		||||
    // wait to enable close button so it doesnt steal focus from input since its the first clickable element in the DOM
 | 
			
		||||
    setTimeout(() => {
 | 
			
		||||
      this.closeEnabled = true
 | 
			
		||||
    });
 | 
			
		||||
    })
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  save() {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,25 +1,24 @@
 | 
			
		||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
 | 
			
		||||
import { ComponentFixture, TestBed } from '@angular/core/testing'
 | 
			
		||||
 | 
			
		||||
import { CorrespondentEditDialogComponent } from './correspondent-edit-dialog.component';
 | 
			
		||||
import { CorrespondentEditDialogComponent } from './correspondent-edit-dialog.component'
 | 
			
		||||
 | 
			
		||||
describe('CorrespondentEditDialogComponent', () => {
 | 
			
		||||
  let component: CorrespondentEditDialogComponent;
 | 
			
		||||
  let fixture: ComponentFixture<CorrespondentEditDialogComponent>;
 | 
			
		||||
  let component: CorrespondentEditDialogComponent
 | 
			
		||||
  let fixture: ComponentFixture<CorrespondentEditDialogComponent>
 | 
			
		||||
 | 
			
		||||
  beforeEach(async () => {
 | 
			
		||||
    await TestBed.configureTestingModule({
 | 
			
		||||
      declarations: [ CorrespondentEditDialogComponent ]
 | 
			
		||||
    })
 | 
			
		||||
    .compileComponents();
 | 
			
		||||
  });
 | 
			
		||||
      declarations: [CorrespondentEditDialogComponent],
 | 
			
		||||
    }).compileComponents()
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  beforeEach(() => {
 | 
			
		||||
    fixture = TestBed.createComponent(CorrespondentEditDialogComponent);
 | 
			
		||||
    component = fixture.componentInstance;
 | 
			
		||||
    fixture.detectChanges();
 | 
			
		||||
  });
 | 
			
		||||
    fixture = TestBed.createComponent(CorrespondentEditDialogComponent)
 | 
			
		||||
    component = fixture.componentInstance
 | 
			
		||||
    fixture.detectChanges()
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  it('should create', () => {
 | 
			
		||||
    expect(component).toBeTruthy();
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
    expect(component).toBeTruthy()
 | 
			
		||||
  })
 | 
			
		||||
})
 | 
			
		||||
 
 | 
			
		||||
@@ -1,19 +1,22 @@
 | 
			
		||||
import { Component } from '@angular/core';
 | 
			
		||||
import { FormControl, FormGroup } from '@angular/forms';
 | 
			
		||||
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
 | 
			
		||||
import { EditDialogComponent } from 'src/app/components/common/edit-dialog/edit-dialog.component';
 | 
			
		||||
import { PaperlessCorrespondent } from 'src/app/data/paperless-correspondent';
 | 
			
		||||
import { CorrespondentService } from 'src/app/services/rest/correspondent.service';
 | 
			
		||||
import { ToastService } from 'src/app/services/toast.service';
 | 
			
		||||
import { Component } from '@angular/core'
 | 
			
		||||
import { FormControl, FormGroup } from '@angular/forms'
 | 
			
		||||
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
 | 
			
		||||
import { EditDialogComponent } from 'src/app/components/common/edit-dialog/edit-dialog.component'
 | 
			
		||||
import { PaperlessCorrespondent } from 'src/app/data/paperless-correspondent'
 | 
			
		||||
import { CorrespondentService } from 'src/app/services/rest/correspondent.service'
 | 
			
		||||
import { ToastService } from 'src/app/services/toast.service'
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'app-correspondent-edit-dialog',
 | 
			
		||||
  templateUrl: './correspondent-edit-dialog.component.html',
 | 
			
		||||
  styleUrls: ['./correspondent-edit-dialog.component.scss']
 | 
			
		||||
  styleUrls: ['./correspondent-edit-dialog.component.scss'],
 | 
			
		||||
})
 | 
			
		||||
export class CorrespondentEditDialogComponent extends EditDialogComponent<PaperlessCorrespondent> {
 | 
			
		||||
 | 
			
		||||
  constructor(service: CorrespondentService, activeModal: NgbActiveModal, toastService: ToastService) {
 | 
			
		||||
  constructor(
 | 
			
		||||
    service: CorrespondentService,
 | 
			
		||||
    activeModal: NgbActiveModal,
 | 
			
		||||
    toastService: ToastService
 | 
			
		||||
  ) {
 | 
			
		||||
    super(service, activeModal, toastService)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@@ -29,9 +32,8 @@ export class CorrespondentEditDialogComponent extends EditDialogComponent<Paperl
 | 
			
		||||
    return new FormGroup({
 | 
			
		||||
      name: new FormControl(''),
 | 
			
		||||
      matching_algorithm: new FormControl(1),
 | 
			
		||||
      match: new FormControl(""),
 | 
			
		||||
      is_insensitive: new FormControl(true)
 | 
			
		||||
      match: new FormControl(''),
 | 
			
		||||
      is_insensitive: new FormControl(true),
 | 
			
		||||
    })
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,25 +1,24 @@
 | 
			
		||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
 | 
			
		||||
import { ComponentFixture, TestBed } from '@angular/core/testing'
 | 
			
		||||
 | 
			
		||||
import { CorrespondentListComponent } from './correspondent-list.component';
 | 
			
		||||
import { CorrespondentListComponent } from './correspondent-list.component'
 | 
			
		||||
 | 
			
		||||
describe('CorrespondentListComponent', () => {
 | 
			
		||||
  let component: CorrespondentListComponent;
 | 
			
		||||
  let fixture: ComponentFixture<CorrespondentListComponent>;
 | 
			
		||||
  let component: CorrespondentListComponent
 | 
			
		||||
  let fixture: ComponentFixture<CorrespondentListComponent>
 | 
			
		||||
 | 
			
		||||
  beforeEach(async () => {
 | 
			
		||||
    await TestBed.configureTestingModule({
 | 
			
		||||
      declarations: [ CorrespondentListComponent ]
 | 
			
		||||
    })
 | 
			
		||||
    .compileComponents();
 | 
			
		||||
  });
 | 
			
		||||
      declarations: [CorrespondentListComponent],
 | 
			
		||||
    }).compileComponents()
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  beforeEach(() => {
 | 
			
		||||
    fixture = TestBed.createComponent(CorrespondentListComponent);
 | 
			
		||||
    component = fixture.componentInstance;
 | 
			
		||||
    fixture.detectChanges();
 | 
			
		||||
  });
 | 
			
		||||
    fixture = TestBed.createComponent(CorrespondentListComponent)
 | 
			
		||||
    component = fixture.componentInstance
 | 
			
		||||
    fixture.detectChanges()
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  it('should create', () => {
 | 
			
		||||
    expect(component).toBeTruthy();
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
    expect(component).toBeTruthy()
 | 
			
		||||
  })
 | 
			
		||||
})
 | 
			
		||||
 
 | 
			
		||||
@@ -1,25 +1,31 @@
 | 
			
		||||
import { Component } from '@angular/core';
 | 
			
		||||
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
 | 
			
		||||
import { FILTER_CORRESPONDENT } from 'src/app/data/filter-rule-type';
 | 
			
		||||
import { PaperlessCorrespondent } from 'src/app/data/paperless-correspondent';
 | 
			
		||||
import { DocumentListViewService } from 'src/app/services/document-list-view.service';
 | 
			
		||||
import { CorrespondentService } from 'src/app/services/rest/correspondent.service';
 | 
			
		||||
import { ToastService } from 'src/app/services/toast.service';
 | 
			
		||||
import { GenericListComponent } from '../generic-list/generic-list.component';
 | 
			
		||||
import { CorrespondentEditDialogComponent } from './correspondent-edit-dialog/correspondent-edit-dialog.component';
 | 
			
		||||
import { Component } from '@angular/core'
 | 
			
		||||
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
 | 
			
		||||
import { FILTER_CORRESPONDENT } from 'src/app/data/filter-rule-type'
 | 
			
		||||
import { PaperlessCorrespondent } from 'src/app/data/paperless-correspondent'
 | 
			
		||||
import { DocumentListViewService } from 'src/app/services/document-list-view.service'
 | 
			
		||||
import { CorrespondentService } from 'src/app/services/rest/correspondent.service'
 | 
			
		||||
import { ToastService } from 'src/app/services/toast.service'
 | 
			
		||||
import { GenericListComponent } from '../generic-list/generic-list.component'
 | 
			
		||||
import { CorrespondentEditDialogComponent } from './correspondent-edit-dialog/correspondent-edit-dialog.component'
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'app-correspondent-list',
 | 
			
		||||
  templateUrl: './correspondent-list.component.html',
 | 
			
		||||
  styleUrls: ['./correspondent-list.component.scss']
 | 
			
		||||
  styleUrls: ['./correspondent-list.component.scss'],
 | 
			
		||||
})
 | 
			
		||||
export class CorrespondentListComponent extends GenericListComponent<PaperlessCorrespondent> {
 | 
			
		||||
 | 
			
		||||
  constructor(correspondentsService: CorrespondentService, modalService: NgbModal,
 | 
			
		||||
  constructor(
 | 
			
		||||
    correspondentsService: CorrespondentService,
 | 
			
		||||
    modalService: NgbModal,
 | 
			
		||||
    private list: DocumentListViewService,
 | 
			
		||||
    toastService: ToastService
 | 
			
		||||
  ) {
 | 
			
		||||
    super(correspondentsService,modalService,CorrespondentEditDialogComponent, toastService)
 | 
			
		||||
    super(
 | 
			
		||||
      correspondentsService,
 | 
			
		||||
      modalService,
 | 
			
		||||
      CorrespondentEditDialogComponent,
 | 
			
		||||
      toastService
 | 
			
		||||
    )
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  getDeleteMessage(object: PaperlessCorrespondent) {
 | 
			
		||||
@@ -27,6 +33,8 @@ export class CorrespondentListComponent extends GenericListComponent<PaperlessCo
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  filterDocuments(object: PaperlessCorrespondent) {
 | 
			
		||||
    this.list.quickFilter([{rule_type: FILTER_CORRESPONDENT, value: object.id.toString()}])
 | 
			
		||||
    this.list.quickFilter([
 | 
			
		||||
      { rule_type: FILTER_CORRESPONDENT, value: object.id.toString() },
 | 
			
		||||
    ])
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,25 +1,24 @@
 | 
			
		||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
 | 
			
		||||
import { ComponentFixture, TestBed } from '@angular/core/testing'
 | 
			
		||||
 | 
			
		||||
import { DocumentTypeEditDialogComponent } from './document-type-edit-dialog.component';
 | 
			
		||||
import { DocumentTypeEditDialogComponent } from './document-type-edit-dialog.component'
 | 
			
		||||
 | 
			
		||||
describe('DocumentTypeEditDialogComponent', () => {
 | 
			
		||||
  let component: DocumentTypeEditDialogComponent;
 | 
			
		||||
  let fixture: ComponentFixture<DocumentTypeEditDialogComponent>;
 | 
			
		||||
  let component: DocumentTypeEditDialogComponent
 | 
			
		||||
  let fixture: ComponentFixture<DocumentTypeEditDialogComponent>
 | 
			
		||||
 | 
			
		||||
  beforeEach(async () => {
 | 
			
		||||
    await TestBed.configureTestingModule({
 | 
			
		||||
      declarations: [ DocumentTypeEditDialogComponent ]
 | 
			
		||||
    })
 | 
			
		||||
    .compileComponents();
 | 
			
		||||
  });
 | 
			
		||||
      declarations: [DocumentTypeEditDialogComponent],
 | 
			
		||||
    }).compileComponents()
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  beforeEach(() => {
 | 
			
		||||
    fixture = TestBed.createComponent(DocumentTypeEditDialogComponent);
 | 
			
		||||
    component = fixture.componentInstance;
 | 
			
		||||
    fixture.detectChanges();
 | 
			
		||||
  });
 | 
			
		||||
    fixture = TestBed.createComponent(DocumentTypeEditDialogComponent)
 | 
			
		||||
    component = fixture.componentInstance
 | 
			
		||||
    fixture.detectChanges()
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  it('should create', () => {
 | 
			
		||||
    expect(component).toBeTruthy();
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
    expect(component).toBeTruthy()
 | 
			
		||||
  })
 | 
			
		||||
})
 | 
			
		||||
 
 | 
			
		||||
@@ -1,19 +1,22 @@
 | 
			
		||||
import { Component } from '@angular/core';
 | 
			
		||||
import { FormControl, FormGroup } from '@angular/forms';
 | 
			
		||||
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
 | 
			
		||||
import { EditDialogComponent } from 'src/app/components/common/edit-dialog/edit-dialog.component';
 | 
			
		||||
import { PaperlessDocumentType } from 'src/app/data/paperless-document-type';
 | 
			
		||||
import { DocumentTypeService } from 'src/app/services/rest/document-type.service';
 | 
			
		||||
import { ToastService } from 'src/app/services/toast.service';
 | 
			
		||||
import { Component } from '@angular/core'
 | 
			
		||||
import { FormControl, FormGroup } from '@angular/forms'
 | 
			
		||||
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
 | 
			
		||||
import { EditDialogComponent } from 'src/app/components/common/edit-dialog/edit-dialog.component'
 | 
			
		||||
import { PaperlessDocumentType } from 'src/app/data/paperless-document-type'
 | 
			
		||||
import { DocumentTypeService } from 'src/app/services/rest/document-type.service'
 | 
			
		||||
import { ToastService } from 'src/app/services/toast.service'
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'app-document-type-edit-dialog',
 | 
			
		||||
  templateUrl: './document-type-edit-dialog.component.html',
 | 
			
		||||
  styleUrls: ['./document-type-edit-dialog.component.scss']
 | 
			
		||||
  styleUrls: ['./document-type-edit-dialog.component.scss'],
 | 
			
		||||
})
 | 
			
		||||
export class DocumentTypeEditDialogComponent extends EditDialogComponent<PaperlessDocumentType> {
 | 
			
		||||
 | 
			
		||||
  constructor(service: DocumentTypeService, activeModal: NgbActiveModal, toastService: ToastService) {
 | 
			
		||||
  constructor(
 | 
			
		||||
    service: DocumentTypeService,
 | 
			
		||||
    activeModal: NgbActiveModal,
 | 
			
		||||
    toastService: ToastService
 | 
			
		||||
  ) {
 | 
			
		||||
    super(service, activeModal, toastService)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@@ -29,9 +32,8 @@ export class DocumentTypeEditDialogComponent extends EditDialogComponent<Paperle
 | 
			
		||||
    return new FormGroup({
 | 
			
		||||
      name: new FormControl(''),
 | 
			
		||||
      matching_algorithm: new FormControl(1),
 | 
			
		||||
      match: new FormControl(""),
 | 
			
		||||
      is_insensitive: new FormControl(true)
 | 
			
		||||
      match: new FormControl(''),
 | 
			
		||||
      is_insensitive: new FormControl(true),
 | 
			
		||||
    })
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,25 +1,24 @@
 | 
			
		||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
 | 
			
		||||
import { ComponentFixture, TestBed } from '@angular/core/testing'
 | 
			
		||||
 | 
			
		||||
import { DocumentTypeListComponent } from './document-type-list.component';
 | 
			
		||||
import { DocumentTypeListComponent } from './document-type-list.component'
 | 
			
		||||
 | 
			
		||||
describe('DocumentTypeListComponent', () => {
 | 
			
		||||
  let component: DocumentTypeListComponent;
 | 
			
		||||
  let fixture: ComponentFixture<DocumentTypeListComponent>;
 | 
			
		||||
  let component: DocumentTypeListComponent
 | 
			
		||||
  let fixture: ComponentFixture<DocumentTypeListComponent>
 | 
			
		||||
 | 
			
		||||
  beforeEach(async () => {
 | 
			
		||||
    await TestBed.configureTestingModule({
 | 
			
		||||
      declarations: [ DocumentTypeListComponent ]
 | 
			
		||||
    })
 | 
			
		||||
    .compileComponents();
 | 
			
		||||
  });
 | 
			
		||||
      declarations: [DocumentTypeListComponent],
 | 
			
		||||
    }).compileComponents()
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  beforeEach(() => {
 | 
			
		||||
    fixture = TestBed.createComponent(DocumentTypeListComponent);
 | 
			
		||||
    component = fixture.componentInstance;
 | 
			
		||||
    fixture.detectChanges();
 | 
			
		||||
  });
 | 
			
		||||
    fixture = TestBed.createComponent(DocumentTypeListComponent)
 | 
			
		||||
    component = fixture.componentInstance
 | 
			
		||||
    fixture.detectChanges()
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  it('should create', () => {
 | 
			
		||||
    expect(component).toBeTruthy();
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
    expect(component).toBeTruthy()
 | 
			
		||||
  })
 | 
			
		||||
})
 | 
			
		||||
 
 | 
			
		||||
@@ -1,21 +1,22 @@
 | 
			
		||||
import { Component } from '@angular/core';
 | 
			
		||||
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
 | 
			
		||||
import { FILTER_DOCUMENT_TYPE } from 'src/app/data/filter-rule-type';
 | 
			
		||||
import { PaperlessDocumentType } from 'src/app/data/paperless-document-type';
 | 
			
		||||
import { DocumentListViewService } from 'src/app/services/document-list-view.service';
 | 
			
		||||
import { DocumentTypeService } from 'src/app/services/rest/document-type.service';
 | 
			
		||||
import { ToastService } from 'src/app/services/toast.service';
 | 
			
		||||
import { GenericListComponent } from '../generic-list/generic-list.component';
 | 
			
		||||
import { DocumentTypeEditDialogComponent } from './document-type-edit-dialog/document-type-edit-dialog.component';
 | 
			
		||||
import { Component } from '@angular/core'
 | 
			
		||||
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
 | 
			
		||||
import { FILTER_DOCUMENT_TYPE } from 'src/app/data/filter-rule-type'
 | 
			
		||||
import { PaperlessDocumentType } from 'src/app/data/paperless-document-type'
 | 
			
		||||
import { DocumentListViewService } from 'src/app/services/document-list-view.service'
 | 
			
		||||
import { DocumentTypeService } from 'src/app/services/rest/document-type.service'
 | 
			
		||||
import { ToastService } from 'src/app/services/toast.service'
 | 
			
		||||
import { GenericListComponent } from '../generic-list/generic-list.component'
 | 
			
		||||
import { DocumentTypeEditDialogComponent } from './document-type-edit-dialog/document-type-edit-dialog.component'
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'app-document-type-list',
 | 
			
		||||
  templateUrl: './document-type-list.component.html',
 | 
			
		||||
  styleUrls: ['./document-type-list.component.scss']
 | 
			
		||||
  styleUrls: ['./document-type-list.component.scss'],
 | 
			
		||||
})
 | 
			
		||||
export class DocumentTypeListComponent extends GenericListComponent<PaperlessDocumentType> {
 | 
			
		||||
 | 
			
		||||
  constructor(service: DocumentTypeService, modalService: NgbModal,
 | 
			
		||||
  constructor(
 | 
			
		||||
    service: DocumentTypeService,
 | 
			
		||||
    modalService: NgbModal,
 | 
			
		||||
    private list: DocumentListViewService,
 | 
			
		||||
    toastService: ToastService
 | 
			
		||||
  ) {
 | 
			
		||||
@@ -26,8 +27,9 @@ export class DocumentTypeListComponent extends GenericListComponent<PaperlessDoc
 | 
			
		||||
    return $localize`Do you really want to delete the document type "${object.name}"?`
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  filterDocuments(object: PaperlessDocumentType) {
 | 
			
		||||
    this.list.quickFilter([{rule_type: FILTER_DOCUMENT_TYPE, value: object.id.toString()}])
 | 
			
		||||
    this.list.quickFilter([
 | 
			
		||||
      { rule_type: FILTER_DOCUMENT_TYPE, value: object.id.toString() },
 | 
			
		||||
    ])
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,25 +1,24 @@
 | 
			
		||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
 | 
			
		||||
import { ComponentFixture, TestBed } from '@angular/core/testing'
 | 
			
		||||
 | 
			
		||||
import { GenericListComponent } from './generic-list.component';
 | 
			
		||||
import { GenericListComponent } from './generic-list.component'
 | 
			
		||||
 | 
			
		||||
describe('GenericListComponent', () => {
 | 
			
		||||
  let component: GenericListComponent;
 | 
			
		||||
  let fixture: ComponentFixture<GenericListComponent>;
 | 
			
		||||
  let component: GenericListComponent
 | 
			
		||||
  let fixture: ComponentFixture<GenericListComponent>
 | 
			
		||||
 | 
			
		||||
  beforeEach(async () => {
 | 
			
		||||
    await TestBed.configureTestingModule({
 | 
			
		||||
      declarations: [ GenericListComponent ]
 | 
			
		||||
    })
 | 
			
		||||
    .compileComponents();
 | 
			
		||||
  });
 | 
			
		||||
      declarations: [GenericListComponent],
 | 
			
		||||
    }).compileComponents()
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  beforeEach(() => {
 | 
			
		||||
    fixture = TestBed.createComponent(GenericListComponent);
 | 
			
		||||
    component = fixture.componentInstance;
 | 
			
		||||
    fixture.detectChanges();
 | 
			
		||||
  });
 | 
			
		||||
    fixture = TestBed.createComponent(GenericListComponent)
 | 
			
		||||
    component = fixture.componentInstance
 | 
			
		||||
    fixture.detectChanges()
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  it('should create', () => {
 | 
			
		||||
    expect(component).toBeTruthy();
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
    expect(component).toBeTruthy()
 | 
			
		||||
  })
 | 
			
		||||
})
 | 
			
		||||
 
 | 
			
		||||
@@ -1,25 +1,39 @@
 | 
			
		||||
import { Directive, OnDestroy, OnInit, QueryList, ViewChildren } from '@angular/core';
 | 
			
		||||
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
 | 
			
		||||
import { Subject, Subscription } from 'rxjs';
 | 
			
		||||
import { debounceTime, distinctUntilChanged } from 'rxjs/operators';
 | 
			
		||||
import { MatchingModel, MATCHING_ALGORITHMS, MATCH_AUTO } from 'src/app/data/matching-model';
 | 
			
		||||
import { ObjectWithId } from 'src/app/data/object-with-id';
 | 
			
		||||
import { SortableDirective, SortEvent } from 'src/app/directives/sortable.directive';
 | 
			
		||||
import { AbstractNameFilterService } from 'src/app/services/rest/abstract-name-filter-service';
 | 
			
		||||
import { ToastService } from 'src/app/services/toast.service';
 | 
			
		||||
import { ConfirmDialogComponent } from '../../common/confirm-dialog/confirm-dialog.component';
 | 
			
		||||
import {
 | 
			
		||||
  Directive,
 | 
			
		||||
  OnDestroy,
 | 
			
		||||
  OnInit,
 | 
			
		||||
  QueryList,
 | 
			
		||||
  ViewChildren,
 | 
			
		||||
} from '@angular/core'
 | 
			
		||||
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
 | 
			
		||||
import { Subject, Subscription } from 'rxjs'
 | 
			
		||||
import { debounceTime, distinctUntilChanged } from 'rxjs/operators'
 | 
			
		||||
import {
 | 
			
		||||
  MatchingModel,
 | 
			
		||||
  MATCHING_ALGORITHMS,
 | 
			
		||||
  MATCH_AUTO,
 | 
			
		||||
} from 'src/app/data/matching-model'
 | 
			
		||||
import { ObjectWithId } from 'src/app/data/object-with-id'
 | 
			
		||||
import {
 | 
			
		||||
  SortableDirective,
 | 
			
		||||
  SortEvent,
 | 
			
		||||
} from 'src/app/directives/sortable.directive'
 | 
			
		||||
import { AbstractNameFilterService } from 'src/app/services/rest/abstract-name-filter-service'
 | 
			
		||||
import { ToastService } from 'src/app/services/toast.service'
 | 
			
		||||
import { ConfirmDialogComponent } from '../../common/confirm-dialog/confirm-dialog.component'
 | 
			
		||||
 | 
			
		||||
@Directive()
 | 
			
		||||
export abstract class GenericListComponent<T extends ObjectWithId> implements OnInit, OnDestroy {
 | 
			
		||||
 | 
			
		||||
export abstract class GenericListComponent<T extends ObjectWithId>
 | 
			
		||||
  implements OnInit, OnDestroy
 | 
			
		||||
{
 | 
			
		||||
  constructor(
 | 
			
		||||
    private service: AbstractNameFilterService<T>,
 | 
			
		||||
    private modalService: NgbModal,
 | 
			
		||||
    private editDialogComponent: any,
 | 
			
		||||
    private toastService: ToastService) {
 | 
			
		||||
    }
 | 
			
		||||
    private toastService: ToastService
 | 
			
		||||
  ) {}
 | 
			
		||||
 | 
			
		||||
  @ViewChildren(SortableDirective) headers: QueryList<SortableDirective>;
 | 
			
		||||
  @ViewChildren(SortableDirective) headers: QueryList<SortableDirective>
 | 
			
		||||
 | 
			
		||||
  public data: T[] = []
 | 
			
		||||
 | 
			
		||||
@@ -38,9 +52,11 @@ export abstract class GenericListComponent<T extends ObjectWithId> implements On
 | 
			
		||||
    if (o.matching_algorithm == MATCH_AUTO) {
 | 
			
		||||
      return $localize`Automatic`
 | 
			
		||||
    } else if (o.match && o.match.length > 0) {
 | 
			
		||||
      return `${MATCHING_ALGORITHMS.find(a => a.id == o.matching_algorithm).shortName}: ${o.match}`
 | 
			
		||||
      return `${
 | 
			
		||||
        MATCHING_ALGORITHMS.find((a) => a.id == o.matching_algorithm).shortName
 | 
			
		||||
      }: ${o.match}`
 | 
			
		||||
    } else {
 | 
			
		||||
      return "-"
 | 
			
		||||
      return '-'
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@@ -50,20 +66,18 @@ export abstract class GenericListComponent<T extends ObjectWithId> implements On
 | 
			
		||||
    this.reloadData()
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  ngOnInit(): void {
 | 
			
		||||
    this.reloadData()
 | 
			
		||||
 | 
			
		||||
    this.nameFilterDebounce = new Subject<string>()
 | 
			
		||||
 | 
			
		||||
    this.subscription = this.nameFilterDebounce.pipe(
 | 
			
		||||
      debounceTime(400),
 | 
			
		||||
      distinctUntilChanged()
 | 
			
		||||
    ).subscribe(title => {
 | 
			
		||||
      this._nameFilter = title
 | 
			
		||||
      this.page = 1
 | 
			
		||||
      this.reloadData()
 | 
			
		||||
    })
 | 
			
		||||
    this.subscription = this.nameFilterDebounce
 | 
			
		||||
      .pipe(debounceTime(400), distinctUntilChanged())
 | 
			
		||||
      .subscribe((title) => {
 | 
			
		||||
        this._nameFilter = title
 | 
			
		||||
        this.page = 1
 | 
			
		||||
        this.reloadData()
 | 
			
		||||
      })
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ngOnDestroy() {
 | 
			
		||||
@@ -71,25 +85,37 @@ export abstract class GenericListComponent<T extends ObjectWithId> implements On
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  reloadData() {
 | 
			
		||||
    this.service.listFiltered(this.page, null, this.sortField, this.sortReverse, this._nameFilter).subscribe(c => {
 | 
			
		||||
      this.data = c.results
 | 
			
		||||
      this.collectionSize = c.count
 | 
			
		||||
    });
 | 
			
		||||
    this.service
 | 
			
		||||
      .listFiltered(
 | 
			
		||||
        this.page,
 | 
			
		||||
        null,
 | 
			
		||||
        this.sortField,
 | 
			
		||||
        this.sortReverse,
 | 
			
		||||
        this._nameFilter
 | 
			
		||||
      )
 | 
			
		||||
      .subscribe((c) => {
 | 
			
		||||
        this.data = c.results
 | 
			
		||||
        this.collectionSize = c.count
 | 
			
		||||
      })
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  openCreateDialog() {
 | 
			
		||||
    var activeModal = this.modalService.open(this.editDialogComponent, {backdrop: 'static'})
 | 
			
		||||
    var activeModal = this.modalService.open(this.editDialogComponent, {
 | 
			
		||||
      backdrop: 'static',
 | 
			
		||||
    })
 | 
			
		||||
    activeModal.componentInstance.dialogMode = 'create'
 | 
			
		||||
    activeModal.componentInstance.success.subscribe(o => {
 | 
			
		||||
    activeModal.componentInstance.success.subscribe((o) => {
 | 
			
		||||
      this.reloadData()
 | 
			
		||||
    })
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  openEditDialog(object: T) {
 | 
			
		||||
    var activeModal = this.modalService.open(this.editDialogComponent, {backdrop: 'static'})
 | 
			
		||||
    var activeModal = this.modalService.open(this.editDialogComponent, {
 | 
			
		||||
      backdrop: 'static',
 | 
			
		||||
    })
 | 
			
		||||
    activeModal.componentInstance.object = object
 | 
			
		||||
    activeModal.componentInstance.dialogMode = 'edit'
 | 
			
		||||
    activeModal.componentInstance.success.subscribe(o => {
 | 
			
		||||
    activeModal.componentInstance.success.subscribe((o) => {
 | 
			
		||||
      this.reloadData()
 | 
			
		||||
    })
 | 
			
		||||
  }
 | 
			
		||||
@@ -99,23 +125,31 @@ export abstract class GenericListComponent<T extends ObjectWithId> implements On
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  openDeleteDialog(object: T) {
 | 
			
		||||
    var activeModal = this.modalService.open(ConfirmDialogComponent, {backdrop: 'static'})
 | 
			
		||||
    var activeModal = this.modalService.open(ConfirmDialogComponent, {
 | 
			
		||||
      backdrop: 'static',
 | 
			
		||||
    })
 | 
			
		||||
    activeModal.componentInstance.title = $localize`Confirm delete`
 | 
			
		||||
    activeModal.componentInstance.messageBold = this.getDeleteMessage(object)
 | 
			
		||||
    activeModal.componentInstance.message = $localize`Associated documents will not be deleted.`
 | 
			
		||||
    activeModal.componentInstance.btnClass = "btn-danger"
 | 
			
		||||
    activeModal.componentInstance.btnClass = 'btn-danger'
 | 
			
		||||
    activeModal.componentInstance.btnCaption = $localize`Delete`
 | 
			
		||||
    activeModal.componentInstance.confirmClicked.subscribe(() => {
 | 
			
		||||
      activeModal.componentInstance.buttonsEnabled = false
 | 
			
		||||
      this.service.delete(object).subscribe(_ => {
 | 
			
		||||
        activeModal.close()
 | 
			
		||||
        this.reloadData()
 | 
			
		||||
      }, error => {
 | 
			
		||||
        activeModal.componentInstance.buttonsEnabled = true
 | 
			
		||||
        this.toastService.showError($localize`Error while deleting element: ${JSON.stringify(error.error)}`)
 | 
			
		||||
      })
 | 
			
		||||
    }
 | 
			
		||||
    )
 | 
			
		||||
      this.service.delete(object).subscribe(
 | 
			
		||||
        (_) => {
 | 
			
		||||
          activeModal.close()
 | 
			
		||||
          this.reloadData()
 | 
			
		||||
        },
 | 
			
		||||
        (error) => {
 | 
			
		||||
          activeModal.componentInstance.buttonsEnabled = true
 | 
			
		||||
          this.toastService.showError(
 | 
			
		||||
            $localize`Error while deleting element: ${JSON.stringify(
 | 
			
		||||
              error.error
 | 
			
		||||
            )}`
 | 
			
		||||
          )
 | 
			
		||||
        }
 | 
			
		||||
      )
 | 
			
		||||
    })
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  get nameFilter() {
 | 
			
		||||
@@ -125,7 +159,7 @@ export abstract class GenericListComponent<T extends ObjectWithId> implements On
 | 
			
		||||
  set nameFilter(nameFilter: string) {
 | 
			
		||||
    this.nameFilterDebounce.next(nameFilter)
 | 
			
		||||
  }
 | 
			
		||||
  
 | 
			
		||||
 | 
			
		||||
  onNameFilterKeyUp(event: KeyboardEvent) {
 | 
			
		||||
    if (event.code == 'Escape') this.nameFilterDebounce.next(null)
 | 
			
		||||
  }
 | 
			
		||||
 
 | 
			
		||||
@@ -1,25 +1,24 @@
 | 
			
		||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
 | 
			
		||||
import { ComponentFixture, TestBed } from '@angular/core/testing'
 | 
			
		||||
 | 
			
		||||
import { LogsComponent } from './logs.component';
 | 
			
		||||
import { LogsComponent } from './logs.component'
 | 
			
		||||
 | 
			
		||||
describe('LogsComponent', () => {
 | 
			
		||||
  let component: LogsComponent;
 | 
			
		||||
  let fixture: ComponentFixture<LogsComponent>;
 | 
			
		||||
  let component: LogsComponent
 | 
			
		||||
  let fixture: ComponentFixture<LogsComponent>
 | 
			
		||||
 | 
			
		||||
  beforeEach(async () => {
 | 
			
		||||
    await TestBed.configureTestingModule({
 | 
			
		||||
      declarations: [ LogsComponent ]
 | 
			
		||||
    })
 | 
			
		||||
    .compileComponents();
 | 
			
		||||
  });
 | 
			
		||||
      declarations: [LogsComponent],
 | 
			
		||||
    }).compileComponents()
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  beforeEach(() => {
 | 
			
		||||
    fixture = TestBed.createComponent(LogsComponent);
 | 
			
		||||
    component = fixture.componentInstance;
 | 
			
		||||
    fixture.detectChanges();
 | 
			
		||||
  });
 | 
			
		||||
    fixture = TestBed.createComponent(LogsComponent)
 | 
			
		||||
    component = fixture.componentInstance
 | 
			
		||||
    fixture.detectChanges()
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  it('should create', () => {
 | 
			
		||||
    expect(component).toBeTruthy();
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
    expect(component).toBeTruthy()
 | 
			
		||||
  })
 | 
			
		||||
})
 | 
			
		||||
 
 | 
			
		||||
@@ -1,14 +1,19 @@
 | 
			
		||||
import { Component, ElementRef, OnInit, AfterViewChecked, ViewChild } from '@angular/core';
 | 
			
		||||
import { LogService } from 'src/app/services/rest/log.service';
 | 
			
		||||
import {
 | 
			
		||||
  Component,
 | 
			
		||||
  ElementRef,
 | 
			
		||||
  OnInit,
 | 
			
		||||
  AfterViewChecked,
 | 
			
		||||
  ViewChild,
 | 
			
		||||
} from '@angular/core'
 | 
			
		||||
import { LogService } from 'src/app/services/rest/log.service'
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'app-logs',
 | 
			
		||||
  templateUrl: './logs.component.html',
 | 
			
		||||
  styleUrls: ['./logs.component.scss']
 | 
			
		||||
  styleUrls: ['./logs.component.scss'],
 | 
			
		||||
})
 | 
			
		||||
export class LogsComponent implements OnInit, AfterViewChecked {
 | 
			
		||||
 | 
			
		||||
  constructor(private logService: LogService) { }
 | 
			
		||||
  constructor(private logService: LogService) {}
 | 
			
		||||
 | 
			
		||||
  logs: string[] = []
 | 
			
		||||
 | 
			
		||||
@@ -19,7 +24,7 @@ export class LogsComponent implements OnInit, AfterViewChecked {
 | 
			
		||||
  @ViewChild('logContainer') logContainer: ElementRef
 | 
			
		||||
 | 
			
		||||
  ngOnInit(): void {
 | 
			
		||||
    this.logService.list().subscribe(result => {
 | 
			
		||||
    this.logService.list().subscribe((result) => {
 | 
			
		||||
      this.logFiles = result
 | 
			
		||||
      if (this.logFiles.length > 0) {
 | 
			
		||||
        this.activeLog = this.logFiles[0]
 | 
			
		||||
@@ -29,25 +34,28 @@ export class LogsComponent implements OnInit, AfterViewChecked {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ngAfterViewChecked() {
 | 
			
		||||
    this.scrollToBottom();
 | 
			
		||||
    this.scrollToBottom()
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  reloadLogs() {
 | 
			
		||||
    this.logService.get(this.activeLog).subscribe(result => {
 | 
			
		||||
      this.logs = result
 | 
			
		||||
    }, error => {
 | 
			
		||||
      this.logs = []
 | 
			
		||||
    })
 | 
			
		||||
    this.logService.get(this.activeLog).subscribe(
 | 
			
		||||
      (result) => {
 | 
			
		||||
        this.logs = result
 | 
			
		||||
      },
 | 
			
		||||
      (error) => {
 | 
			
		||||
        this.logs = []
 | 
			
		||||
      }
 | 
			
		||||
    )
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  getLogLevel(log: string) {
 | 
			
		||||
    if (log.indexOf("[DEBUG]") != -1) {
 | 
			
		||||
    if (log.indexOf('[DEBUG]') != -1) {
 | 
			
		||||
      return 10
 | 
			
		||||
    } else if (log.indexOf("[WARNING]") != -1) {
 | 
			
		||||
    } else if (log.indexOf('[WARNING]') != -1) {
 | 
			
		||||
      return 30
 | 
			
		||||
    } else if (log.indexOf("[ERROR]") != -1) {
 | 
			
		||||
    } else if (log.indexOf('[ERROR]') != -1) {
 | 
			
		||||
      return 40
 | 
			
		||||
    } else if (log.indexOf("[CRITICAL]") != -1) {
 | 
			
		||||
    } else if (log.indexOf('[CRITICAL]') != -1) {
 | 
			
		||||
      return 50
 | 
			
		||||
    } else {
 | 
			
		||||
      return 20
 | 
			
		||||
@@ -58,8 +66,7 @@ export class LogsComponent implements OnInit, AfterViewChecked {
 | 
			
		||||
    this.logContainer?.nativeElement.scroll({
 | 
			
		||||
      top: this.logContainer.nativeElement.scrollHeight,
 | 
			
		||||
      left: 0,
 | 
			
		||||
      behavior: 'auto'
 | 
			
		||||
    });
 | 
			
		||||
      behavior: 'auto',
 | 
			
		||||
    })
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,25 +1,24 @@
 | 
			
		||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
 | 
			
		||||
import { ComponentFixture, TestBed } from '@angular/core/testing'
 | 
			
		||||
 | 
			
		||||
import { SettingsComponent } from './settings.component';
 | 
			
		||||
import { SettingsComponent } from './settings.component'
 | 
			
		||||
 | 
			
		||||
describe('SettingsComponent', () => {
 | 
			
		||||
  let component: SettingsComponent;
 | 
			
		||||
  let fixture: ComponentFixture<SettingsComponent>;
 | 
			
		||||
  let component: SettingsComponent
 | 
			
		||||
  let fixture: ComponentFixture<SettingsComponent>
 | 
			
		||||
 | 
			
		||||
  beforeEach(async () => {
 | 
			
		||||
    await TestBed.configureTestingModule({
 | 
			
		||||
      declarations: [ SettingsComponent ]
 | 
			
		||||
    })
 | 
			
		||||
    .compileComponents();
 | 
			
		||||
  });
 | 
			
		||||
      declarations: [SettingsComponent],
 | 
			
		||||
    }).compileComponents()
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  beforeEach(() => {
 | 
			
		||||
    fixture = TestBed.createComponent(SettingsComponent);
 | 
			
		||||
    component = fixture.componentInstance;
 | 
			
		||||
    fixture.detectChanges();
 | 
			
		||||
  });
 | 
			
		||||
    fixture = TestBed.createComponent(SettingsComponent)
 | 
			
		||||
    component = fixture.componentInstance
 | 
			
		||||
    fixture.detectChanges()
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  it('should create', () => {
 | 
			
		||||
    expect(component).toBeTruthy();
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
    expect(component).toBeTruthy()
 | 
			
		||||
  })
 | 
			
		||||
})
 | 
			
		||||
 
 | 
			
		||||
@@ -1,39 +1,49 @@
 | 
			
		||||
import { Component, Inject, LOCALE_ID, OnInit, OnDestroy, Renderer2 } from '@angular/core';
 | 
			
		||||
import { FormControl, FormGroup } from '@angular/forms';
 | 
			
		||||
import { PaperlessSavedView } from 'src/app/data/paperless-saved-view';
 | 
			
		||||
import { DocumentListViewService } from 'src/app/services/document-list-view.service';
 | 
			
		||||
import { SavedViewService } from 'src/app/services/rest/saved-view.service';
 | 
			
		||||
import { LanguageOption, SettingsService, SETTINGS_KEYS } from 'src/app/services/settings.service';
 | 
			
		||||
import { ToastService } from 'src/app/services/toast.service';
 | 
			
		||||
import { dirtyCheck, DirtyComponent } from '@ngneat/dirty-check-forms';
 | 
			
		||||
import { Observable, Subscription, BehaviorSubject } from 'rxjs';
 | 
			
		||||
import {
 | 
			
		||||
  Component,
 | 
			
		||||
  Inject,
 | 
			
		||||
  LOCALE_ID,
 | 
			
		||||
  OnInit,
 | 
			
		||||
  OnDestroy,
 | 
			
		||||
  Renderer2,
 | 
			
		||||
} from '@angular/core'
 | 
			
		||||
import { FormControl, FormGroup } from '@angular/forms'
 | 
			
		||||
import { PaperlessSavedView } from 'src/app/data/paperless-saved-view'
 | 
			
		||||
import { DocumentListViewService } from 'src/app/services/document-list-view.service'
 | 
			
		||||
import { SavedViewService } from 'src/app/services/rest/saved-view.service'
 | 
			
		||||
import {
 | 
			
		||||
  LanguageOption,
 | 
			
		||||
  SettingsService,
 | 
			
		||||
  SETTINGS_KEYS,
 | 
			
		||||
} from 'src/app/services/settings.service'
 | 
			
		||||
import { ToastService } from 'src/app/services/toast.service'
 | 
			
		||||
import { dirtyCheck, DirtyComponent } from '@ngneat/dirty-check-forms'
 | 
			
		||||
import { Observable, Subscription, BehaviorSubject } from 'rxjs'
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'app-settings',
 | 
			
		||||
  templateUrl: './settings.component.html',
 | 
			
		||||
  styleUrls: ['./settings.component.scss']
 | 
			
		||||
  styleUrls: ['./settings.component.scss'],
 | 
			
		||||
})
 | 
			
		||||
export class SettingsComponent implements OnInit, OnDestroy, DirtyComponent {
 | 
			
		||||
 | 
			
		||||
  savedViewGroup = new FormGroup({})
 | 
			
		||||
 | 
			
		||||
  settingsForm = new FormGroup({
 | 
			
		||||
    'bulkEditConfirmationDialogs': new FormControl(null),
 | 
			
		||||
    'bulkEditApplyOnClose': new FormControl(null),
 | 
			
		||||
    'documentListItemPerPage': new FormControl(null),
 | 
			
		||||
    'darkModeUseSystem': new FormControl(null),
 | 
			
		||||
    'darkModeEnabled': new FormControl(null),
 | 
			
		||||
    'darkModeInvertThumbs': new FormControl(null),
 | 
			
		||||
    'themeColor': new FormControl(null),
 | 
			
		||||
    'useNativePdfViewer': new FormControl(null),
 | 
			
		||||
    'savedViews': this.savedViewGroup,
 | 
			
		||||
    'displayLanguage': new FormControl(null),
 | 
			
		||||
    'dateLocale': new FormControl(null),
 | 
			
		||||
    'dateFormat': new FormControl(null),
 | 
			
		||||
    'notificationsConsumerNewDocument': new FormControl(null),
 | 
			
		||||
    'notificationsConsumerSuccess': new FormControl(null),
 | 
			
		||||
    'notificationsConsumerFailed': new FormControl(null),
 | 
			
		||||
    'notificationsConsumerSuppressOnDashboard': new FormControl(null),
 | 
			
		||||
    bulkEditConfirmationDialogs: new FormControl(null),
 | 
			
		||||
    bulkEditApplyOnClose: new FormControl(null),
 | 
			
		||||
    documentListItemPerPage: new FormControl(null),
 | 
			
		||||
    darkModeUseSystem: new FormControl(null),
 | 
			
		||||
    darkModeEnabled: new FormControl(null),
 | 
			
		||||
    darkModeInvertThumbs: new FormControl(null),
 | 
			
		||||
    themeColor: new FormControl(null),
 | 
			
		||||
    useNativePdfViewer: new FormControl(null),
 | 
			
		||||
    savedViews: this.savedViewGroup,
 | 
			
		||||
    displayLanguage: new FormControl(null),
 | 
			
		||||
    dateLocale: new FormControl(null),
 | 
			
		||||
    dateFormat: new FormControl(null),
 | 
			
		||||
    notificationsConsumerNewDocument: new FormControl(null),
 | 
			
		||||
    notificationsConsumerSuccess: new FormControl(null),
 | 
			
		||||
    notificationsConsumerFailed: new FormControl(null),
 | 
			
		||||
    notificationsConsumerSuppressOnDashboard: new FormControl(null),
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  savedViews: PaperlessSavedView[]
 | 
			
		||||
@@ -44,7 +54,11 @@ export class SettingsComponent implements OnInit, OnDestroy, DirtyComponent {
 | 
			
		||||
  isDirty: Boolean = false
 | 
			
		||||
 | 
			
		||||
  get computedDateLocale(): string {
 | 
			
		||||
    return this.settingsForm.value.dateLocale || this.settingsForm.value.displayLanguage || this.currentLocale
 | 
			
		||||
    return (
 | 
			
		||||
      this.settingsForm.value.dateLocale ||
 | 
			
		||||
      this.settingsForm.value.displayLanguage ||
 | 
			
		||||
      this.currentLocale
 | 
			
		||||
    )
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  constructor(
 | 
			
		||||
@@ -53,48 +67,71 @@ export class SettingsComponent implements OnInit, OnDestroy, DirtyComponent {
 | 
			
		||||
    private toastService: ToastService,
 | 
			
		||||
    private settings: SettingsService,
 | 
			
		||||
    @Inject(LOCALE_ID) public currentLocale: string
 | 
			
		||||
  ) { }
 | 
			
		||||
  ) {}
 | 
			
		||||
 | 
			
		||||
  ngOnInit() {
 | 
			
		||||
    this.savedViewService.listAll().subscribe(r => {
 | 
			
		||||
    this.savedViewService.listAll().subscribe((r) => {
 | 
			
		||||
      this.savedViews = r.results
 | 
			
		||||
      let storeData = {
 | 
			
		||||
        'bulkEditConfirmationDialogs': this.settings.get(SETTINGS_KEYS.BULK_EDIT_CONFIRMATION_DIALOGS),
 | 
			
		||||
        'bulkEditApplyOnClose': this.settings.get(SETTINGS_KEYS.BULK_EDIT_APPLY_ON_CLOSE),
 | 
			
		||||
        'documentListItemPerPage': this.settings.get(SETTINGS_KEYS.DOCUMENT_LIST_SIZE),
 | 
			
		||||
        'darkModeUseSystem': this.settings.get(SETTINGS_KEYS.DARK_MODE_USE_SYSTEM),
 | 
			
		||||
        'darkModeEnabled': this.settings.get(SETTINGS_KEYS.DARK_MODE_ENABLED),
 | 
			
		||||
        'darkModeInvertThumbs': this.settings.get(SETTINGS_KEYS.DARK_MODE_THUMB_INVERTED),
 | 
			
		||||
        'themeColor': this.settings.get(SETTINGS_KEYS.THEME_COLOR),
 | 
			
		||||
        'useNativePdfViewer': this.settings.get(SETTINGS_KEYS.USE_NATIVE_PDF_VIEWER),
 | 
			
		||||
        'savedViews': {},
 | 
			
		||||
        'displayLanguage': this.settings.getLanguage(),
 | 
			
		||||
        'dateLocale': this.settings.get(SETTINGS_KEYS.DATE_LOCALE),
 | 
			
		||||
        'dateFormat': this.settings.get(SETTINGS_KEYS.DATE_FORMAT),
 | 
			
		||||
        'notificationsConsumerNewDocument': this.settings.get(SETTINGS_KEYS.NOTIFICATIONS_CONSUMER_NEW_DOCUMENT),
 | 
			
		||||
        'notificationsConsumerSuccess': this.settings.get(SETTINGS_KEYS.NOTIFICATIONS_CONSUMER_SUCCESS),
 | 
			
		||||
        'notificationsConsumerFailed': this.settings.get(SETTINGS_KEYS.NOTIFICATIONS_CONSUMER_FAILED),
 | 
			
		||||
        'notificationsConsumerSuppressOnDashboard': this.settings.get(SETTINGS_KEYS.NOTIFICATIONS_CONSUMER_SUPPRESS_ON_DASHBOARD),
 | 
			
		||||
        bulkEditConfirmationDialogs: this.settings.get(
 | 
			
		||||
          SETTINGS_KEYS.BULK_EDIT_CONFIRMATION_DIALOGS
 | 
			
		||||
        ),
 | 
			
		||||
        bulkEditApplyOnClose: this.settings.get(
 | 
			
		||||
          SETTINGS_KEYS.BULK_EDIT_APPLY_ON_CLOSE
 | 
			
		||||
        ),
 | 
			
		||||
        documentListItemPerPage: this.settings.get(
 | 
			
		||||
          SETTINGS_KEYS.DOCUMENT_LIST_SIZE
 | 
			
		||||
        ),
 | 
			
		||||
        darkModeUseSystem: this.settings.get(
 | 
			
		||||
          SETTINGS_KEYS.DARK_MODE_USE_SYSTEM
 | 
			
		||||
        ),
 | 
			
		||||
        darkModeEnabled: this.settings.get(SETTINGS_KEYS.DARK_MODE_ENABLED),
 | 
			
		||||
        darkModeInvertThumbs: this.settings.get(
 | 
			
		||||
          SETTINGS_KEYS.DARK_MODE_THUMB_INVERTED
 | 
			
		||||
        ),
 | 
			
		||||
        themeColor: this.settings.get(SETTINGS_KEYS.THEME_COLOR),
 | 
			
		||||
        useNativePdfViewer: this.settings.get(
 | 
			
		||||
          SETTINGS_KEYS.USE_NATIVE_PDF_VIEWER
 | 
			
		||||
        ),
 | 
			
		||||
        savedViews: {},
 | 
			
		||||
        displayLanguage: this.settings.getLanguage(),
 | 
			
		||||
        dateLocale: this.settings.get(SETTINGS_KEYS.DATE_LOCALE),
 | 
			
		||||
        dateFormat: this.settings.get(SETTINGS_KEYS.DATE_FORMAT),
 | 
			
		||||
        notificationsConsumerNewDocument: this.settings.get(
 | 
			
		||||
          SETTINGS_KEYS.NOTIFICATIONS_CONSUMER_NEW_DOCUMENT
 | 
			
		||||
        ),
 | 
			
		||||
        notificationsConsumerSuccess: this.settings.get(
 | 
			
		||||
          SETTINGS_KEYS.NOTIFICATIONS_CONSUMER_SUCCESS
 | 
			
		||||
        ),
 | 
			
		||||
        notificationsConsumerFailed: this.settings.get(
 | 
			
		||||
          SETTINGS_KEYS.NOTIFICATIONS_CONSUMER_FAILED
 | 
			
		||||
        ),
 | 
			
		||||
        notificationsConsumerSuppressOnDashboard: this.settings.get(
 | 
			
		||||
          SETTINGS_KEYS.NOTIFICATIONS_CONSUMER_SUPPRESS_ON_DASHBOARD
 | 
			
		||||
        ),
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      for (let view of this.savedViews) {
 | 
			
		||||
        storeData.savedViews[view.id.toString()] = {
 | 
			
		||||
          "id": view.id,
 | 
			
		||||
          "name": view.name,
 | 
			
		||||
          "show_on_dashboard": view.show_on_dashboard,
 | 
			
		||||
          "show_in_sidebar": view.show_in_sidebar
 | 
			
		||||
          id: view.id,
 | 
			
		||||
          name: view.name,
 | 
			
		||||
          show_on_dashboard: view.show_on_dashboard,
 | 
			
		||||
          show_in_sidebar: view.show_in_sidebar,
 | 
			
		||||
        }
 | 
			
		||||
        this.savedViewGroup.addControl(view.id.toString(), new FormGroup({
 | 
			
		||||
          "id": new FormControl(null),
 | 
			
		||||
          "name": new FormControl(null),
 | 
			
		||||
          "show_on_dashboard": new FormControl(null),
 | 
			
		||||
          "show_in_sidebar": new FormControl(null)
 | 
			
		||||
        }))
 | 
			
		||||
        this.savedViewGroup.addControl(
 | 
			
		||||
          view.id.toString(),
 | 
			
		||||
          new FormGroup({
 | 
			
		||||
            id: new FormControl(null),
 | 
			
		||||
            name: new FormControl(null),
 | 
			
		||||
            show_on_dashboard: new FormControl(null),
 | 
			
		||||
            show_in_sidebar: new FormControl(null),
 | 
			
		||||
          })
 | 
			
		||||
        )
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      this.store = new BehaviorSubject(storeData)
 | 
			
		||||
 | 
			
		||||
      this.storeSub = this.store.asObservable().subscribe(state => {
 | 
			
		||||
      this.storeSub = this.store.asObservable().subscribe((state) => {
 | 
			
		||||
        this.settingsForm.patchValue(state, { emitEvent: false })
 | 
			
		||||
      })
 | 
			
		||||
 | 
			
		||||
@@ -102,45 +139,93 @@ export class SettingsComponent implements OnInit, OnDestroy, DirtyComponent {
 | 
			
		||||
      this.isDirty$ = dirtyCheck(this.settingsForm, this.store.asObservable())
 | 
			
		||||
 | 
			
		||||
      // Record dirty in case we need to 'undo' appearance settings if not saved on close
 | 
			
		||||
      this.isDirty$.subscribe(dirty => {
 | 
			
		||||
      this.isDirty$.subscribe((dirty) => {
 | 
			
		||||
        this.isDirty = dirty
 | 
			
		||||
      })
 | 
			
		||||
      
 | 
			
		||||
 | 
			
		||||
      // "Live" visual changes prior to save
 | 
			
		||||
      this.settingsForm.valueChanges.subscribe(() => {
 | 
			
		||||
        this.settings.updateAppearanceSettings(this.settingsForm.get('darkModeUseSystem').value, this.settingsForm.get('darkModeEnabled').value, this.settingsForm.get('themeColor').value)
 | 
			
		||||
        this.settings.updateAppearanceSettings(
 | 
			
		||||
          this.settingsForm.get('darkModeUseSystem').value,
 | 
			
		||||
          this.settingsForm.get('darkModeEnabled').value,
 | 
			
		||||
          this.settingsForm.get('themeColor').value
 | 
			
		||||
        )
 | 
			
		||||
      })
 | 
			
		||||
    })
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ngOnDestroy() {
 | 
			
		||||
    if (this.isDirty) this.settings.updateAppearanceSettings() // in case user changed appearance but didnt save
 | 
			
		||||
    this.storeSub && this.storeSub.unsubscribe();
 | 
			
		||||
    this.storeSub && this.storeSub.unsubscribe()
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  deleteSavedView(savedView: PaperlessSavedView) {
 | 
			
		||||
    this.savedViewService.delete(savedView).subscribe(() => {
 | 
			
		||||
      this.savedViewGroup.removeControl(savedView.id.toString())
 | 
			
		||||
      this.savedViews.splice(this.savedViews.indexOf(savedView), 1)
 | 
			
		||||
      this.toastService.showInfo($localize`Saved view "${savedView.name}" deleted.`)
 | 
			
		||||
      this.toastService.showInfo(
 | 
			
		||||
        $localize`Saved view "${savedView.name}" deleted.`
 | 
			
		||||
      )
 | 
			
		||||
    })
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private saveLocalSettings() {
 | 
			
		||||
    this.settings.set(SETTINGS_KEYS.BULK_EDIT_APPLY_ON_CLOSE, this.settingsForm.value.bulkEditApplyOnClose)
 | 
			
		||||
    this.settings.set(SETTINGS_KEYS.BULK_EDIT_CONFIRMATION_DIALOGS, this.settingsForm.value.bulkEditConfirmationDialogs)
 | 
			
		||||
    this.settings.set(SETTINGS_KEYS.DOCUMENT_LIST_SIZE, this.settingsForm.value.documentListItemPerPage)
 | 
			
		||||
    this.settings.set(SETTINGS_KEYS.DARK_MODE_USE_SYSTEM, this.settingsForm.value.darkModeUseSystem)
 | 
			
		||||
    this.settings.set(SETTINGS_KEYS.DARK_MODE_ENABLED, (this.settingsForm.value.darkModeEnabled == true).toString())
 | 
			
		||||
    this.settings.set(SETTINGS_KEYS.DARK_MODE_THUMB_INVERTED, (this.settingsForm.value.darkModeInvertThumbs == true).toString())
 | 
			
		||||
    this.settings.set(SETTINGS_KEYS.THEME_COLOR, (this.settingsForm.value.themeColor).toString())
 | 
			
		||||
    this.settings.set(SETTINGS_KEYS.USE_NATIVE_PDF_VIEWER, this.settingsForm.value.useNativePdfViewer)
 | 
			
		||||
    this.settings.set(SETTINGS_KEYS.DATE_LOCALE, this.settingsForm.value.dateLocale)
 | 
			
		||||
    this.settings.set(SETTINGS_KEYS.DATE_FORMAT, this.settingsForm.value.dateFormat)
 | 
			
		||||
    this.settings.set(SETTINGS_KEYS.NOTIFICATIONS_CONSUMER_NEW_DOCUMENT, this.settingsForm.value.notificationsConsumerNewDocument)
 | 
			
		||||
    this.settings.set(SETTINGS_KEYS.NOTIFICATIONS_CONSUMER_SUCCESS, this.settingsForm.value.notificationsConsumerSuccess)
 | 
			
		||||
    this.settings.set(SETTINGS_KEYS.NOTIFICATIONS_CONSUMER_FAILED, this.settingsForm.value.notificationsConsumerFailed)
 | 
			
		||||
    this.settings.set(SETTINGS_KEYS.NOTIFICATIONS_CONSUMER_SUPPRESS_ON_DASHBOARD, this.settingsForm.value.notificationsConsumerSuppressOnDashboard)
 | 
			
		||||
    this.settings.set(
 | 
			
		||||
      SETTINGS_KEYS.BULK_EDIT_APPLY_ON_CLOSE,
 | 
			
		||||
      this.settingsForm.value.bulkEditApplyOnClose
 | 
			
		||||
    )
 | 
			
		||||
    this.settings.set(
 | 
			
		||||
      SETTINGS_KEYS.BULK_EDIT_CONFIRMATION_DIALOGS,
 | 
			
		||||
      this.settingsForm.value.bulkEditConfirmationDialogs
 | 
			
		||||
    )
 | 
			
		||||
    this.settings.set(
 | 
			
		||||
      SETTINGS_KEYS.DOCUMENT_LIST_SIZE,
 | 
			
		||||
      this.settingsForm.value.documentListItemPerPage
 | 
			
		||||
    )
 | 
			
		||||
    this.settings.set(
 | 
			
		||||
      SETTINGS_KEYS.DARK_MODE_USE_SYSTEM,
 | 
			
		||||
      this.settingsForm.value.darkModeUseSystem
 | 
			
		||||
    )
 | 
			
		||||
    this.settings.set(
 | 
			
		||||
      SETTINGS_KEYS.DARK_MODE_ENABLED,
 | 
			
		||||
      (this.settingsForm.value.darkModeEnabled == true).toString()
 | 
			
		||||
    )
 | 
			
		||||
    this.settings.set(
 | 
			
		||||
      SETTINGS_KEYS.DARK_MODE_THUMB_INVERTED,
 | 
			
		||||
      (this.settingsForm.value.darkModeInvertThumbs == true).toString()
 | 
			
		||||
    )
 | 
			
		||||
    this.settings.set(
 | 
			
		||||
      SETTINGS_KEYS.THEME_COLOR,
 | 
			
		||||
      this.settingsForm.value.themeColor.toString()
 | 
			
		||||
    )
 | 
			
		||||
    this.settings.set(
 | 
			
		||||
      SETTINGS_KEYS.USE_NATIVE_PDF_VIEWER,
 | 
			
		||||
      this.settingsForm.value.useNativePdfViewer
 | 
			
		||||
    )
 | 
			
		||||
    this.settings.set(
 | 
			
		||||
      SETTINGS_KEYS.DATE_LOCALE,
 | 
			
		||||
      this.settingsForm.value.dateLocale
 | 
			
		||||
    )
 | 
			
		||||
    this.settings.set(
 | 
			
		||||
      SETTINGS_KEYS.DATE_FORMAT,
 | 
			
		||||
      this.settingsForm.value.dateFormat
 | 
			
		||||
    )
 | 
			
		||||
    this.settings.set(
 | 
			
		||||
      SETTINGS_KEYS.NOTIFICATIONS_CONSUMER_NEW_DOCUMENT,
 | 
			
		||||
      this.settingsForm.value.notificationsConsumerNewDocument
 | 
			
		||||
    )
 | 
			
		||||
    this.settings.set(
 | 
			
		||||
      SETTINGS_KEYS.NOTIFICATIONS_CONSUMER_SUCCESS,
 | 
			
		||||
      this.settingsForm.value.notificationsConsumerSuccess
 | 
			
		||||
    )
 | 
			
		||||
    this.settings.set(
 | 
			
		||||
      SETTINGS_KEYS.NOTIFICATIONS_CONSUMER_FAILED,
 | 
			
		||||
      this.settingsForm.value.notificationsConsumerFailed
 | 
			
		||||
    )
 | 
			
		||||
    this.settings.set(
 | 
			
		||||
      SETTINGS_KEYS.NOTIFICATIONS_CONSUMER_SUPPRESS_ON_DASHBOARD,
 | 
			
		||||
      this.settingsForm.value.notificationsConsumerSuppressOnDashboard
 | 
			
		||||
    )
 | 
			
		||||
    this.settings.setLanguage(this.settingsForm.value.displayLanguage)
 | 
			
		||||
    this.store.next(this.settingsForm.value)
 | 
			
		||||
    this.documentListViewService.updatePageSize()
 | 
			
		||||
@@ -149,14 +234,14 @@ export class SettingsComponent implements OnInit, OnDestroy, DirtyComponent {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  get displayLanguageOptions(): LanguageOption[] {
 | 
			
		||||
    return [
 | 
			
		||||
      {code: "", name: $localize`Use system language`}
 | 
			
		||||
    ].concat(this.settings.getLanguageOptions())
 | 
			
		||||
    return [{ code: '', name: $localize`Use system language` }].concat(
 | 
			
		||||
      this.settings.getLanguageOptions()
 | 
			
		||||
    )
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  get dateLocaleOptions(): LanguageOption[] {
 | 
			
		||||
    return [
 | 
			
		||||
      {code: "", name: $localize`Use date format of display language`}
 | 
			
		||||
      { code: '', name: $localize`Use date format of display language` },
 | 
			
		||||
    ].concat(this.settings.getDateLocaleOptions())
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@@ -170,18 +255,24 @@ export class SettingsComponent implements OnInit, OnDestroy, DirtyComponent {
 | 
			
		||||
      x.push(this.savedViewGroup.value[id])
 | 
			
		||||
    }
 | 
			
		||||
    if (x.length > 0) {
 | 
			
		||||
      this.savedViewService.patchMany(x).subscribe(s => {
 | 
			
		||||
        this.saveLocalSettings()
 | 
			
		||||
      }, error => {
 | 
			
		||||
        this.toastService.showError($localize`Error while storing settings on server: ${JSON.stringify(error.error)}`)
 | 
			
		||||
      })
 | 
			
		||||
      this.savedViewService.patchMany(x).subscribe(
 | 
			
		||||
        (s) => {
 | 
			
		||||
          this.saveLocalSettings()
 | 
			
		||||
        },
 | 
			
		||||
        (error) => {
 | 
			
		||||
          this.toastService.showError(
 | 
			
		||||
            $localize`Error while storing settings on server: ${JSON.stringify(
 | 
			
		||||
              error.error
 | 
			
		||||
            )}`
 | 
			
		||||
          )
 | 
			
		||||
        }
 | 
			
		||||
      )
 | 
			
		||||
    } else {
 | 
			
		||||
      this.saveLocalSettings()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  clearThemeColor() {
 | 
			
		||||
    this.settingsForm.get('themeColor').patchValue('');
 | 
			
		||||
    this.settingsForm.get('themeColor').patchValue('')
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,25 +1,24 @@
 | 
			
		||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
 | 
			
		||||
import { ComponentFixture, TestBed } from '@angular/core/testing'
 | 
			
		||||
 | 
			
		||||
import { TagEditDialogComponent } from './tag-edit-dialog.component';
 | 
			
		||||
import { TagEditDialogComponent } from './tag-edit-dialog.component'
 | 
			
		||||
 | 
			
		||||
describe('TagEditDialogComponent', () => {
 | 
			
		||||
  let component: TagEditDialogComponent;
 | 
			
		||||
  let fixture: ComponentFixture<TagEditDialogComponent>;
 | 
			
		||||
  let component: TagEditDialogComponent
 | 
			
		||||
  let fixture: ComponentFixture<TagEditDialogComponent>
 | 
			
		||||
 | 
			
		||||
  beforeEach(async () => {
 | 
			
		||||
    await TestBed.configureTestingModule({
 | 
			
		||||
      declarations: [ TagEditDialogComponent ]
 | 
			
		||||
    })
 | 
			
		||||
    .compileComponents();
 | 
			
		||||
  });
 | 
			
		||||
      declarations: [TagEditDialogComponent],
 | 
			
		||||
    }).compileComponents()
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  beforeEach(() => {
 | 
			
		||||
    fixture = TestBed.createComponent(TagEditDialogComponent);
 | 
			
		||||
    component = fixture.componentInstance;
 | 
			
		||||
    fixture.detectChanges();
 | 
			
		||||
  });
 | 
			
		||||
    fixture = TestBed.createComponent(TagEditDialogComponent)
 | 
			
		||||
    component = fixture.componentInstance
 | 
			
		||||
    fixture.detectChanges()
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  it('should create', () => {
 | 
			
		||||
    expect(component).toBeTruthy();
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
    expect(component).toBeTruthy()
 | 
			
		||||
  })
 | 
			
		||||
})
 | 
			
		||||
 
 | 
			
		||||
@@ -1,20 +1,23 @@
 | 
			
		||||
import { Component } from '@angular/core';
 | 
			
		||||
import { FormControl, FormGroup } from '@angular/forms';
 | 
			
		||||
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
 | 
			
		||||
import { EditDialogComponent } from 'src/app/components/common/edit-dialog/edit-dialog.component';
 | 
			
		||||
import { PaperlessTag } from 'src/app/data/paperless-tag';
 | 
			
		||||
import { TagService } from 'src/app/services/rest/tag.service';
 | 
			
		||||
import { ToastService } from 'src/app/services/toast.service';
 | 
			
		||||
import { randomColor } from 'src/app/utils/color';
 | 
			
		||||
import { Component } from '@angular/core'
 | 
			
		||||
import { FormControl, FormGroup } from '@angular/forms'
 | 
			
		||||
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
 | 
			
		||||
import { EditDialogComponent } from 'src/app/components/common/edit-dialog/edit-dialog.component'
 | 
			
		||||
import { PaperlessTag } from 'src/app/data/paperless-tag'
 | 
			
		||||
import { TagService } from 'src/app/services/rest/tag.service'
 | 
			
		||||
import { ToastService } from 'src/app/services/toast.service'
 | 
			
		||||
import { randomColor } from 'src/app/utils/color'
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'app-tag-edit-dialog',
 | 
			
		||||
  templateUrl: './tag-edit-dialog.component.html',
 | 
			
		||||
  styleUrls: ['./tag-edit-dialog.component.scss']
 | 
			
		||||
  styleUrls: ['./tag-edit-dialog.component.scss'],
 | 
			
		||||
})
 | 
			
		||||
export class TagEditDialogComponent extends EditDialogComponent<PaperlessTag> {
 | 
			
		||||
 | 
			
		||||
  constructor(service: TagService, activeModal: NgbActiveModal, toastService: ToastService) {
 | 
			
		||||
  constructor(
 | 
			
		||||
    service: TagService,
 | 
			
		||||
    activeModal: NgbActiveModal,
 | 
			
		||||
    toastService: ToastService
 | 
			
		||||
  ) {
 | 
			
		||||
    super(service, activeModal, toastService)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@@ -32,9 +35,8 @@ export class TagEditDialogComponent extends EditDialogComponent<PaperlessTag> {
 | 
			
		||||
      color: new FormControl(randomColor()),
 | 
			
		||||
      is_inbox_tag: new FormControl(false),
 | 
			
		||||
      matching_algorithm: new FormControl(1),
 | 
			
		||||
      match: new FormControl(""),
 | 
			
		||||
      is_insensitive: new FormControl(true)
 | 
			
		||||
      match: new FormControl(''),
 | 
			
		||||
      is_insensitive: new FormControl(true),
 | 
			
		||||
    })
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,25 +1,24 @@
 | 
			
		||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
 | 
			
		||||
import { ComponentFixture, TestBed } from '@angular/core/testing'
 | 
			
		||||
 | 
			
		||||
import { TagListComponent } from './tag-list.component';
 | 
			
		||||
import { TagListComponent } from './tag-list.component'
 | 
			
		||||
 | 
			
		||||
describe('TagListComponent', () => {
 | 
			
		||||
  let component: TagListComponent;
 | 
			
		||||
  let fixture: ComponentFixture<TagListComponent>;
 | 
			
		||||
  let component: TagListComponent
 | 
			
		||||
  let fixture: ComponentFixture<TagListComponent>
 | 
			
		||||
 | 
			
		||||
  beforeEach(async () => {
 | 
			
		||||
    await TestBed.configureTestingModule({
 | 
			
		||||
      declarations: [ TagListComponent ]
 | 
			
		||||
    })
 | 
			
		||||
    .compileComponents();
 | 
			
		||||
  });
 | 
			
		||||
      declarations: [TagListComponent],
 | 
			
		||||
    }).compileComponents()
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  beforeEach(() => {
 | 
			
		||||
    fixture = TestBed.createComponent(TagListComponent);
 | 
			
		||||
    component = fixture.componentInstance;
 | 
			
		||||
    fixture.detectChanges();
 | 
			
		||||
  });
 | 
			
		||||
    fixture = TestBed.createComponent(TagListComponent)
 | 
			
		||||
    component = fixture.componentInstance
 | 
			
		||||
    fixture.detectChanges()
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  it('should create', () => {
 | 
			
		||||
    expect(component).toBeTruthy();
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
    expect(component).toBeTruthy()
 | 
			
		||||
  })
 | 
			
		||||
})
 | 
			
		||||
 
 | 
			
		||||
@@ -1,21 +1,22 @@
 | 
			
		||||
import { Component } from '@angular/core';
 | 
			
		||||
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
 | 
			
		||||
import { FILTER_HAS_TAGS_ALL } from 'src/app/data/filter-rule-type';
 | 
			
		||||
import { PaperlessTag } from 'src/app/data/paperless-tag';
 | 
			
		||||
import { DocumentListViewService } from 'src/app/services/document-list-view.service';
 | 
			
		||||
import { TagService } from 'src/app/services/rest/tag.service';
 | 
			
		||||
import { ToastService } from 'src/app/services/toast.service';
 | 
			
		||||
import { GenericListComponent } from '../generic-list/generic-list.component';
 | 
			
		||||
import { TagEditDialogComponent } from './tag-edit-dialog/tag-edit-dialog.component';
 | 
			
		||||
import { Component } from '@angular/core'
 | 
			
		||||
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
 | 
			
		||||
import { FILTER_HAS_TAGS_ALL } from 'src/app/data/filter-rule-type'
 | 
			
		||||
import { PaperlessTag } from 'src/app/data/paperless-tag'
 | 
			
		||||
import { DocumentListViewService } from 'src/app/services/document-list-view.service'
 | 
			
		||||
import { TagService } from 'src/app/services/rest/tag.service'
 | 
			
		||||
import { ToastService } from 'src/app/services/toast.service'
 | 
			
		||||
import { GenericListComponent } from '../generic-list/generic-list.component'
 | 
			
		||||
import { TagEditDialogComponent } from './tag-edit-dialog/tag-edit-dialog.component'
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'app-tag-list',
 | 
			
		||||
  templateUrl: './tag-list.component.html',
 | 
			
		||||
  styleUrls: ['./tag-list.component.scss']
 | 
			
		||||
  styleUrls: ['./tag-list.component.scss'],
 | 
			
		||||
})
 | 
			
		||||
export class TagListComponent extends GenericListComponent<PaperlessTag> {
 | 
			
		||||
 | 
			
		||||
  constructor(tagService: TagService, modalService: NgbModal,
 | 
			
		||||
  constructor(
 | 
			
		||||
    tagService: TagService,
 | 
			
		||||
    modalService: NgbModal,
 | 
			
		||||
    private list: DocumentListViewService,
 | 
			
		||||
    toastService: ToastService
 | 
			
		||||
  ) {
 | 
			
		||||
@@ -27,7 +28,8 @@ export class TagListComponent extends GenericListComponent<PaperlessTag> {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  filterDocuments(object: PaperlessTag) {
 | 
			
		||||
    this.list.quickFilter([{rule_type: FILTER_HAS_TAGS_ALL, value: object.id.toString()}])
 | 
			
		||||
 | 
			
		||||
    this.list.quickFilter([
 | 
			
		||||
      { rule_type: FILTER_HAS_TAGS_ALL, value: object.id.toString() },
 | 
			
		||||
    ])
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,25 +1,24 @@
 | 
			
		||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
 | 
			
		||||
import { ComponentFixture, TestBed } from '@angular/core/testing'
 | 
			
		||||
 | 
			
		||||
import { NotFoundComponent } from './not-found.component';
 | 
			
		||||
import { NotFoundComponent } from './not-found.component'
 | 
			
		||||
 | 
			
		||||
describe('NotFoundComponent', () => {
 | 
			
		||||
  let component: NotFoundComponent;
 | 
			
		||||
  let fixture: ComponentFixture<NotFoundComponent>;
 | 
			
		||||
  let component: NotFoundComponent
 | 
			
		||||
  let fixture: ComponentFixture<NotFoundComponent>
 | 
			
		||||
 | 
			
		||||
  beforeEach(async () => {
 | 
			
		||||
    await TestBed.configureTestingModule({
 | 
			
		||||
      declarations: [ NotFoundComponent ]
 | 
			
		||||
    })
 | 
			
		||||
    .compileComponents();
 | 
			
		||||
  });
 | 
			
		||||
      declarations: [NotFoundComponent],
 | 
			
		||||
    }).compileComponents()
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  beforeEach(() => {
 | 
			
		||||
    fixture = TestBed.createComponent(NotFoundComponent);
 | 
			
		||||
    component = fixture.componentInstance;
 | 
			
		||||
    fixture.detectChanges();
 | 
			
		||||
  });
 | 
			
		||||
    fixture = TestBed.createComponent(NotFoundComponent)
 | 
			
		||||
    component = fixture.componentInstance
 | 
			
		||||
    fixture.detectChanges()
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  it('should create', () => {
 | 
			
		||||
    expect(component).toBeTruthy();
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
    expect(component).toBeTruthy()
 | 
			
		||||
  })
 | 
			
		||||
})
 | 
			
		||||
 
 | 
			
		||||
@@ -1,15 +1,12 @@
 | 
			
		||||
import { Component, OnInit } from '@angular/core';
 | 
			
		||||
import { Component, OnInit } from '@angular/core'
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'app-not-found',
 | 
			
		||||
  templateUrl: './not-found.component.html',
 | 
			
		||||
  styleUrls: ['./not-found.component.scss']
 | 
			
		||||
  styleUrls: ['./not-found.component.scss'],
 | 
			
		||||
})
 | 
			
		||||
export class NotFoundComponent implements OnInit {
 | 
			
		||||
  constructor() {}
 | 
			
		||||
 | 
			
		||||
  constructor() { }
 | 
			
		||||
 | 
			
		||||
  ngOnInit(): void {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ngOnInit(): void {}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -27,40 +27,160 @@ export const FILTER_FULLTEXT_QUERY = 20
 | 
			
		||||
export const FILTER_FULLTEXT_MORELIKE = 21
 | 
			
		||||
 | 
			
		||||
export const FILTER_RULE_TYPES: FilterRuleType[] = [
 | 
			
		||||
  {
 | 
			
		||||
    id: FILTER_TITLE,
 | 
			
		||||
    filtervar: 'title__icontains',
 | 
			
		||||
    datatype: 'string',
 | 
			
		||||
    multi: false,
 | 
			
		||||
    default: '',
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    id: FILTER_CONTENT,
 | 
			
		||||
    filtervar: 'content__icontains',
 | 
			
		||||
    datatype: 'string',
 | 
			
		||||
    multi: false,
 | 
			
		||||
    default: '',
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  {id: FILTER_TITLE, filtervar: "title__icontains", datatype: "string", multi: false, default: ""},
 | 
			
		||||
  {id: FILTER_CONTENT, filtervar: "content__icontains", datatype: "string", multi: false, default: ""},
 | 
			
		||||
  {
 | 
			
		||||
    id: FILTER_ASN,
 | 
			
		||||
    filtervar: 'archive_serial_number',
 | 
			
		||||
    datatype: 'number',
 | 
			
		||||
    multi: false,
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  {id: FILTER_ASN, filtervar: "archive_serial_number", datatype: "number", multi: false},
 | 
			
		||||
  {
 | 
			
		||||
    id: FILTER_CORRESPONDENT,
 | 
			
		||||
    filtervar: 'correspondent__id',
 | 
			
		||||
    isnull_filtervar: 'correspondent__isnull',
 | 
			
		||||
    datatype: 'correspondent',
 | 
			
		||||
    multi: false,
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    id: FILTER_DOCUMENT_TYPE,
 | 
			
		||||
    filtervar: 'document_type__id',
 | 
			
		||||
    isnull_filtervar: 'document_type__isnull',
 | 
			
		||||
    datatype: 'document_type',
 | 
			
		||||
    multi: false,
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  {id: FILTER_CORRESPONDENT, filtervar: "correspondent__id", isnull_filtervar: "correspondent__isnull", datatype: "correspondent", multi: false},
 | 
			
		||||
  {id: FILTER_DOCUMENT_TYPE, filtervar: "document_type__id", isnull_filtervar: "document_type__isnull", datatype: "document_type", multi: false},
 | 
			
		||||
  {
 | 
			
		||||
    id: FILTER_IS_IN_INBOX,
 | 
			
		||||
    filtervar: 'is_in_inbox',
 | 
			
		||||
    datatype: 'boolean',
 | 
			
		||||
    multi: false,
 | 
			
		||||
    default: true,
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    id: FILTER_HAS_TAGS_ALL,
 | 
			
		||||
    filtervar: 'tags__id__all',
 | 
			
		||||
    datatype: 'tag',
 | 
			
		||||
    multi: true,
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    id: FILTER_HAS_TAGS_ANY,
 | 
			
		||||
    filtervar: 'tags__id__in',
 | 
			
		||||
    datatype: 'tag',
 | 
			
		||||
    multi: true,
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    id: FILTER_DOES_NOT_HAVE_TAG,
 | 
			
		||||
    filtervar: 'tags__id__none',
 | 
			
		||||
    datatype: 'tag',
 | 
			
		||||
    multi: true,
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    id: FILTER_HAS_ANY_TAG,
 | 
			
		||||
    filtervar: 'is_tagged',
 | 
			
		||||
    datatype: 'boolean',
 | 
			
		||||
    multi: false,
 | 
			
		||||
    default: true,
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  {id: FILTER_IS_IN_INBOX, filtervar: "is_in_inbox", datatype: "boolean", multi: false, default: true},
 | 
			
		||||
  {id: FILTER_HAS_TAGS_ALL, filtervar: "tags__id__all", datatype: "tag", multi: true},
 | 
			
		||||
  {id: FILTER_HAS_TAGS_ANY, filtervar: "tags__id__in", datatype: "tag", multi: true},
 | 
			
		||||
  {id: FILTER_DOES_NOT_HAVE_TAG, filtervar: "tags__id__none", datatype: "tag", multi: true},
 | 
			
		||||
  {id: FILTER_HAS_ANY_TAG, filtervar: "is_tagged", datatype: "boolean", multi: false, default: true},
 | 
			
		||||
  {
 | 
			
		||||
    id: FILTER_CREATED_BEFORE,
 | 
			
		||||
    filtervar: 'created__date__lt',
 | 
			
		||||
    datatype: 'date',
 | 
			
		||||
    multi: false,
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    id: FILTER_CREATED_AFTER,
 | 
			
		||||
    filtervar: 'created__date__gt',
 | 
			
		||||
    datatype: 'date',
 | 
			
		||||
    multi: false,
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  {id: FILTER_CREATED_BEFORE, filtervar: "created__date__lt", datatype: "date", multi: false},
 | 
			
		||||
  {id: FILTER_CREATED_AFTER, filtervar: "created__date__gt", datatype: "date", multi: false},
 | 
			
		||||
  {
 | 
			
		||||
    id: FILTER_CREATED_YEAR,
 | 
			
		||||
    filtervar: 'created__year',
 | 
			
		||||
    datatype: 'number',
 | 
			
		||||
    multi: false,
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    id: FILTER_CREATED_MONTH,
 | 
			
		||||
    filtervar: 'created__month',
 | 
			
		||||
    datatype: 'number',
 | 
			
		||||
    multi: false,
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    id: FILTER_CREATED_DAY,
 | 
			
		||||
    filtervar: 'created__day',
 | 
			
		||||
    datatype: 'number',
 | 
			
		||||
    multi: false,
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  {id: FILTER_CREATED_YEAR, filtervar: "created__year", datatype: "number", multi: false},
 | 
			
		||||
  {id: FILTER_CREATED_MONTH, filtervar: "created__month", datatype: "number", multi: false},
 | 
			
		||||
  {id: FILTER_CREATED_DAY, filtervar: "created__day", datatype: "number", multi: false},
 | 
			
		||||
  {
 | 
			
		||||
    id: FILTER_ADDED_BEFORE,
 | 
			
		||||
    filtervar: 'added__date__lt',
 | 
			
		||||
    datatype: 'date',
 | 
			
		||||
    multi: false,
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    id: FILTER_ADDED_AFTER,
 | 
			
		||||
    filtervar: 'added__date__gt',
 | 
			
		||||
    datatype: 'date',
 | 
			
		||||
    multi: false,
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  {id: FILTER_ADDED_BEFORE, filtervar: "added__date__lt", datatype: "date", multi: false},
 | 
			
		||||
  {id: FILTER_ADDED_AFTER, filtervar: "added__date__gt", datatype: "date", multi: false},
 | 
			
		||||
  {
 | 
			
		||||
    id: FILTER_MODIFIED_BEFORE,
 | 
			
		||||
    filtervar: 'modified__date__lt',
 | 
			
		||||
    datatype: 'date',
 | 
			
		||||
    multi: false,
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    id: FILTER_MODIFIED_AFTER,
 | 
			
		||||
    filtervar: 'modified__date__gt',
 | 
			
		||||
    datatype: 'date',
 | 
			
		||||
    multi: false,
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    id: FILTER_ASN_ISNULL,
 | 
			
		||||
    filtervar: 'archive_serial_number__isnull',
 | 
			
		||||
    datatype: 'boolean',
 | 
			
		||||
    multi: false,
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  {id: FILTER_MODIFIED_BEFORE, filtervar: "modified__date__lt", datatype: "date", multi: false},
 | 
			
		||||
  {id: FILTER_MODIFIED_AFTER, filtervar: "modified__date__gt", datatype: "date", multi: false},
 | 
			
		||||
  {id: FILTER_ASN_ISNULL, filtervar: "archive_serial_number__isnull", datatype: "boolean", multi: false},
 | 
			
		||||
  {
 | 
			
		||||
    id: FILTER_TITLE_CONTENT,
 | 
			
		||||
    filtervar: 'title_content',
 | 
			
		||||
    datatype: 'string',
 | 
			
		||||
    multi: false,
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  {id: FILTER_TITLE_CONTENT, filtervar: "title_content", datatype: "string", multi: false},
 | 
			
		||||
  {
 | 
			
		||||
    id: FILTER_FULLTEXT_QUERY,
 | 
			
		||||
    filtervar: 'query',
 | 
			
		||||
    datatype: 'string',
 | 
			
		||||
    multi: false,
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  {id: FILTER_FULLTEXT_QUERY, filtervar: "query", datatype: "string", multi: false},
 | 
			
		||||
 | 
			
		||||
  {id: FILTER_FULLTEXT_MORELIKE, filtervar: "more_like_id", datatype: "number", multi: false},
 | 
			
		||||
  {
 | 
			
		||||
    id: FILTER_FULLTEXT_MORELIKE,
 | 
			
		||||
    filtervar: 'more_like_id',
 | 
			
		||||
    datatype: 'number',
 | 
			
		||||
    multi: false,
 | 
			
		||||
  },
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
export interface FilterRuleType {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,10 +1,13 @@
 | 
			
		||||
import { FILTER_FULLTEXT_MORELIKE, FILTER_FULLTEXT_QUERY } from "./filter-rule-type"
 | 
			
		||||
import {
 | 
			
		||||
  FILTER_FULLTEXT_MORELIKE,
 | 
			
		||||
  FILTER_FULLTEXT_QUERY,
 | 
			
		||||
} from './filter-rule-type'
 | 
			
		||||
 | 
			
		||||
export function cloneFilterRules(filterRules: FilterRule[]): FilterRule[] {
 | 
			
		||||
  if (filterRules) {
 | 
			
		||||
    let newRules: FilterRule[] = []
 | 
			
		||||
    for (let rule of filterRules) {
 | 
			
		||||
      newRules.push({rule_type: rule.rule_type, value: rule.value})
 | 
			
		||||
      newRules.push({ rule_type: rule.rule_type, value: rule.value })
 | 
			
		||||
    }
 | 
			
		||||
    return newRules
 | 
			
		||||
  } else {
 | 
			
		||||
@@ -13,7 +16,13 @@ export function cloneFilterRules(filterRules: FilterRule[]): FilterRule[] {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function isFullTextFilterRule(filterRules: FilterRule[]): boolean {
 | 
			
		||||
  return filterRules.find(r => r.rule_type == FILTER_FULLTEXT_QUERY || r.rule_type == FILTER_FULLTEXT_MORELIKE) != null
 | 
			
		||||
  return (
 | 
			
		||||
    filterRules.find(
 | 
			
		||||
      (r) =>
 | 
			
		||||
        r.rule_type == FILTER_FULLTEXT_QUERY ||
 | 
			
		||||
        r.rule_type == FILTER_FULLTEXT_MORELIKE
 | 
			
		||||
    ) != null
 | 
			
		||||
  )
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface FilterRule {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,4 @@
 | 
			
		||||
import { ObjectWithId } from './object-with-id';
 | 
			
		||||
 | 
			
		||||
import { ObjectWithId } from './object-with-id'
 | 
			
		||||
 | 
			
		||||
export const MATCH_ANY = 1
 | 
			
		||||
export const MATCH_ALL = 2
 | 
			
		||||
@@ -9,26 +8,48 @@ export const MATCH_FUZZY = 5
 | 
			
		||||
export const MATCH_AUTO = 6
 | 
			
		||||
 | 
			
		||||
export const MATCHING_ALGORITHMS = [
 | 
			
		||||
    {id: MATCH_ANY, shortName: $localize`Any word`, name: $localize`Any: Document contains any of these words (space separated)`},
 | 
			
		||||
    {id: MATCH_ALL, shortName: $localize`All words`, name: $localize`All: Document contains all of these words (space separated)`},
 | 
			
		||||
    {id: MATCH_LITERAL, shortName: $localize`Exact match`, name: $localize`Exact: Document contains this string`},
 | 
			
		||||
    {id: MATCH_REGEX, shortName: $localize`Regular expression`, name: $localize`Regular expression: Document matches this regular expression`},
 | 
			
		||||
    {id: MATCH_FUZZY, shortName: $localize`Fuzzy word`, name: $localize`Fuzzy: Document contains a word similar to this word`},
 | 
			
		||||
    {id: MATCH_AUTO, shortName: $localize`Automatic`, name: $localize`Auto: Learn matching automatically`},
 | 
			
		||||
  {
 | 
			
		||||
    id: MATCH_ANY,
 | 
			
		||||
    shortName: $localize`Any word`,
 | 
			
		||||
    name: $localize`Any: Document contains any of these words (space separated)`,
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    id: MATCH_ALL,
 | 
			
		||||
    shortName: $localize`All words`,
 | 
			
		||||
    name: $localize`All: Document contains all of these words (space separated)`,
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    id: MATCH_LITERAL,
 | 
			
		||||
    shortName: $localize`Exact match`,
 | 
			
		||||
    name: $localize`Exact: Document contains this string`,
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    id: MATCH_REGEX,
 | 
			
		||||
    shortName: $localize`Regular expression`,
 | 
			
		||||
    name: $localize`Regular expression: Document matches this regular expression`,
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    id: MATCH_FUZZY,
 | 
			
		||||
    shortName: $localize`Fuzzy word`,
 | 
			
		||||
    name: $localize`Fuzzy: Document contains a word similar to this word`,
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    id: MATCH_AUTO,
 | 
			
		||||
    shortName: $localize`Automatic`,
 | 
			
		||||
    name: $localize`Auto: Learn matching automatically`,
 | 
			
		||||
  },
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
export interface MatchingModel extends ObjectWithId {
 | 
			
		||||
  name?: string
 | 
			
		||||
 | 
			
		||||
    name?: string
 | 
			
		||||
  slug?: string
 | 
			
		||||
 | 
			
		||||
    slug?: string
 | 
			
		||||
  match?: string
 | 
			
		||||
 | 
			
		||||
    match?: string
 | 
			
		||||
  matching_algorithm?: number
 | 
			
		||||
 | 
			
		||||
    matching_algorithm?: number
 | 
			
		||||
 | 
			
		||||
    is_insensitive?: boolean
 | 
			
		||||
 | 
			
		||||
    document_count?: number
 | 
			
		||||
  is_insensitive?: boolean
 | 
			
		||||
 | 
			
		||||
  document_count?: number
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,3 @@
 | 
			
		||||
export interface ObjectWithId {
 | 
			
		||||
 | 
			
		||||
    id?: number
 | 
			
		||||
 | 
			
		||||
  id?: number
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,5 @@
 | 
			
		||||
import { MatchingModel } from './matching-model';
 | 
			
		||||
import { MatchingModel } from './matching-model'
 | 
			
		||||
 | 
			
		||||
export interface PaperlessCorrespondent extends MatchingModel {
 | 
			
		||||
 | 
			
		||||
  last_correspondence?: Date
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,4 @@
 | 
			
		||||
export interface PaperlessDocumentMetadata {
 | 
			
		||||
 | 
			
		||||
  original_checksum?: string
 | 
			
		||||
 | 
			
		||||
  archived_checksum?: string
 | 
			
		||||
@@ -9,5 +8,4 @@ export interface PaperlessDocumentMetadata {
 | 
			
		||||
  media_filename?: string
 | 
			
		||||
 | 
			
		||||
  has_archive_version?: boolean
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,9 +1,7 @@
 | 
			
		||||
export interface PaperlessDocumentSuggestions {
 | 
			
		||||
 | 
			
		||||
  tags?: number[]
 | 
			
		||||
 | 
			
		||||
  correspondents?: number[]
 | 
			
		||||
 | 
			
		||||
  document_types?: number[]
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,3 @@
 | 
			
		||||
import { MatchingModel } from './matching-model';
 | 
			
		||||
import { MatchingModel } from './matching-model'
 | 
			
		||||
 | 
			
		||||
export interface PaperlessDocumentType extends MatchingModel {
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
export interface PaperlessDocumentType extends MatchingModel {}
 | 
			
		||||
 
 | 
			
		||||
@@ -5,50 +5,46 @@ import { PaperlessDocumentType } from './paperless-document-type'
 | 
			
		||||
import { Observable } from 'rxjs'
 | 
			
		||||
 | 
			
		||||
export interface SearchHit {
 | 
			
		||||
 | 
			
		||||
  score?: number
 | 
			
		||||
  rank?: number
 | 
			
		||||
 | 
			
		||||
  highlights?: string
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface PaperlessDocument extends ObjectWithId {
 | 
			
		||||
  correspondent$?: Observable<PaperlessCorrespondent>
 | 
			
		||||
 | 
			
		||||
    correspondent$?: Observable<PaperlessCorrespondent>
 | 
			
		||||
  correspondent?: number
 | 
			
		||||
 | 
			
		||||
    correspondent?: number
 | 
			
		||||
  document_type$?: Observable<PaperlessDocumentType>
 | 
			
		||||
 | 
			
		||||
    document_type$?: Observable<PaperlessDocumentType>
 | 
			
		||||
  document_type?: number
 | 
			
		||||
 | 
			
		||||
    document_type?: number
 | 
			
		||||
  title?: string
 | 
			
		||||
 | 
			
		||||
    title?: string
 | 
			
		||||
  content?: string
 | 
			
		||||
 | 
			
		||||
    content?: string
 | 
			
		||||
  file_type?: string
 | 
			
		||||
 | 
			
		||||
    file_type?: string
 | 
			
		||||
  tags$?: Observable<PaperlessTag[]>
 | 
			
		||||
 | 
			
		||||
    tags$?: Observable<PaperlessTag[]>
 | 
			
		||||
  tags?: number[]
 | 
			
		||||
 | 
			
		||||
    tags?: number[]
 | 
			
		||||
  checksum?: string
 | 
			
		||||
 | 
			
		||||
    checksum?: string
 | 
			
		||||
  created?: Date
 | 
			
		||||
 | 
			
		||||
    created?: Date
 | 
			
		||||
  modified?: Date
 | 
			
		||||
 | 
			
		||||
    modified?: Date
 | 
			
		||||
  added?: Date
 | 
			
		||||
 | 
			
		||||
    added?: Date
 | 
			
		||||
  file_name?: string
 | 
			
		||||
 | 
			
		||||
    file_name?: string
 | 
			
		||||
  download_url?: string
 | 
			
		||||
 | 
			
		||||
    download_url?: string
 | 
			
		||||
  thumbnail_url?: string
 | 
			
		||||
 | 
			
		||||
    thumbnail_url?: string
 | 
			
		||||
 | 
			
		||||
    archive_serial_number?: number
 | 
			
		||||
 | 
			
		||||
    __search_hit__?: SearchHit
 | 
			
		||||
  archive_serial_number?: number
 | 
			
		||||
 | 
			
		||||
  __search_hit__?: SearchHit
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user