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