mirror of
				https://github.com/paperless-ngx/paperless-ngx.git
				synced 2025-10-30 03:56:23 -05:00 
			
		
		
		
	Merge pull request #276 from paperless-ngx/prettier-cleanup-ts-js-md
Prettier cleanup for .ts .js and .md files
This commit is contained in:
		
							
								
								
									
										16
									
								
								.github/ISSUE_TEMPLATE/bug_report.md
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										16
									
								
								.github/ISSUE_TEMPLATE/bug_report.md
									
									
									
									
										vendored
									
									
								
							| @@ -1,10 +1,9 @@ | ||||
| --- | ||||
| name: Bug report | ||||
| about: Something is not working | ||||
| title: "[BUG] Concise description of the issue" | ||||
| title: '[BUG] Concise description of the issue' | ||||
| labels: '' | ||||
| assignees: '' | ||||
|  | ||||
| --- | ||||
|  | ||||
| <!--- | ||||
| @@ -24,6 +23,7 @@ A clear and concise description of what the bug is. | ||||
|  | ||||
| **To Reproduce** | ||||
| Steps to reproduce the behavior: | ||||
|  | ||||
| 1. Go to '...' | ||||
| 2. Click on '....' | ||||
| 3. Scroll down to '....' | ||||
| @@ -36,13 +36,15 @@ A clear and concise description of what you expected to happen. | ||||
| If applicable, add screenshots to help explain your problem. | ||||
|  | ||||
| **Webserver logs** | ||||
|  | ||||
| ``` | ||||
| If available, post any logs from the web server related to your issue. | ||||
| ``` | ||||
|  | ||||
| **Relevant information** | ||||
|  - Host OS of the machine running paperless: [e.g. Archlinux / Ubuntu 20.04] | ||||
|  - Browser [e.g. chrome, safari] | ||||
|  - Version [e.g. 1.0.0] | ||||
|  - Installation method: [docker / bare metal] | ||||
|  - Any configuration changes you made in `docker-compose.yml`, `docker-compose.env` or `paperless.conf`. | ||||
|  | ||||
| - Host OS of the machine running paperless: [e.g. Archlinux / Ubuntu 20.04] | ||||
| - Browser [e.g. chrome, safari] | ||||
| - Version [e.g. 1.0.0] | ||||
| - Installation method: [docker / bare metal] | ||||
| - Any configuration changes you made in `docker-compose.yml`, `docker-compose.env` or `paperless.conf`. | ||||
|   | ||||
							
								
								
									
										3
									
								
								.github/ISSUE_TEMPLATE/other.md
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								.github/ISSUE_TEMPLATE/other.md
									
									
									
									
										vendored
									
									
								
							| @@ -1,10 +1,9 @@ | ||||
| --- | ||||
| name: Other | ||||
| about: Anything that is not a feature request or bug. | ||||
| title: "[Other] Title of your issue" | ||||
| title: '[Other] Title of your issue' | ||||
| labels: '' | ||||
| assignees: '' | ||||
|  | ||||
| --- | ||||
|  | ||||
| <!-- | ||||
|   | ||||
| @@ -4,10 +4,10 @@ If you feel like contributing to the project, please do! Bug fixes and improveme | ||||
|  | ||||
| If you want to implement something big: | ||||
|  | ||||
| * Please start a discussion about that in the issues! Maybe something similar is already in development and we can make it happen together. | ||||
| * When making additions to the project, consider if the majority of users will benefit from your change. If not, you're probably better of forking the project. | ||||
| * Also consider if your change will get in the way of other users. A good change is a change that enhances the experience of some users who want that change and does not affect users who do not care about the change. | ||||
| * Please see the [paperless-ngx merge process](#merging-prs) below. | ||||
| - Please start a discussion about that in the issues! Maybe something similar is already in development and we can make it happen together. | ||||
| - When making additions to the project, consider if the majority of users will benefit from your change. If not, you're probably better of forking the project. | ||||
| - Also consider if your change will get in the way of other users. A good change is a change that enhances the experience of some users who want that change and does not affect users who do not care about the change. | ||||
| - Please see the [paperless-ngx merge process](#merging-prs) below. | ||||
|  | ||||
| ## Python | ||||
|  | ||||
| @@ -41,9 +41,9 @@ PRs deemed `non-trivial` will go through a stricter review process before being | ||||
|  | ||||
| Examples of `non-trivial` PRs might include: | ||||
|  | ||||
| * Additional features | ||||
| * Large changes to many distinct files | ||||
| * Breaking or depreciation of existing features | ||||
| - Additional features | ||||
| - Large changes to many distinct files | ||||
| - Breaking or depreciation of existing features | ||||
|  | ||||
| Our community review process for `non-trivial` PRs is the following: | ||||
|  | ||||
| @@ -75,18 +75,18 @@ If a language has already been added, and you would like to contribute new trans | ||||
| If you would like the project to be translated to another language, first head over to https://crwd.in/paperless-ngx to check if that language has already been enabled for translation. | ||||
| If not, please request the language to be added by creating an issue on GitHub. The issue should contain: | ||||
|  | ||||
| * English name of the language (the localized name can be added on Crowdin). | ||||
| * ISO language code. A list of those can be found here: https://support.crowdin.com/enterprise/language-codes/ | ||||
| * Date format commonly used for the language, e.g. dd/mm/yyyy, mm/dd/yyyy, etc. | ||||
| - English name of the language (the localized name can be added on Crowdin). | ||||
| - ISO language code. A list of those can be found here: https://support.crowdin.com/enterprise/language-codes/ | ||||
| - Date format commonly used for the language, e.g. dd/mm/yyyy, mm/dd/yyyy, etc. | ||||
|  | ||||
| After the language has been added and some translations have been made on Crowdin, the language needs to be enabled in the code. | ||||
| Note that there is no need to manually add a .po of .xlf file as those will be automatically generated and imported from Crowdin. | ||||
| The following files need to be changed: | ||||
|  | ||||
| * src-ui/angular.json (under the _projects/paperless-ui/i18n/locales_ JSON key) | ||||
| * src/paperless/settings.py (in the _LANGUAGES_ array) | ||||
| * src-ui/src/app/services/settings.service.ts (inside the _getLanguageOptions_ method) | ||||
| * src-ui/src/app/app.module.ts (import locale from _angular/common/locales_ and call _registerLocaleData_) | ||||
| - src-ui/angular.json (under the _projects/paperless-ui/i18n/locales_ JSON key) | ||||
| - src/paperless/settings.py (in the _LANGUAGES_ array) | ||||
| - src-ui/src/app/services/settings.service.ts (inside the _getLanguageOptions_ method) | ||||
| - src-ui/src/app/app.module.ts (import locale from _angular/common/locales_ and call _registerLocaleData_) | ||||
|  | ||||
| Please add the language in the correct order, alphabetically by locale. | ||||
| Note that _en-us_ needs to stay on top of the list, as it is the default project language | ||||
|   | ||||
							
								
								
									
										71
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										71
									
								
								README.md
									
									
									
									
									
								
							| @@ -10,23 +10,23 @@ | ||||
| </p> | ||||
|  | ||||
| <!-- omit in toc --> | ||||
|  | ||||
| # Paperless-ngx | ||||
|  | ||||
| Paperless-ngx is a document management system that transforms your physical documents into a searchable online archive so you can keep, well, *less paper*. | ||||
| Paperless-ngx is a document management system that transforms your physical documents into a searchable online archive so you can keep, well, _less paper_. | ||||
|  | ||||
| Paperless-ngx forked from [paperless-ng](https://github.com/jonaswinkler/paperless-ng) to continue the great work and distribute responsibility of supporting and advancing the project among a team of people. [Consider joining us!](#community-support) Discussion of this transition can be found in issues | ||||
| [#1599](https://github.com/jonaswinkler/paperless-ng/issues/1599) and [#1632](https://github.com/jonaswinkler/paperless-ng/issues/1632). | ||||
|  | ||||
| A demo is available at [demo.paperless-ngx.com](https://demo.paperless-ngx.com) using login `demo` / `demo`. *Note: demo content is reset frequently and confidential information should not be uploaded.* | ||||
|  | ||||
| A demo is available at [demo.paperless-ngx.com](https://demo.paperless-ngx.com) using login `demo` / `demo`. _Note: demo content is reset frequently and confidential information should not be uploaded._ | ||||
|  | ||||
| - [Features](#features) | ||||
| - [Getting started](#getting-started) | ||||
| - [Contributing](#contributing) | ||||
| 	- [Community Support](#community-support) | ||||
| 	- [Translation](#translation) | ||||
| 	- [Feature Requests](#feature-requests) | ||||
| 	- [Bugs](#bugs) | ||||
|   - [Community Support](#community-support) | ||||
|   - [Translation](#translation) | ||||
|   - [Feature Requests](#feature-requests) | ||||
|   - [Bugs](#bugs) | ||||
| - [Affiliated Projects](#affiliated-projects) | ||||
| - [Important Note](#important-note) | ||||
|  | ||||
| @@ -35,28 +35,28 @@ A demo is available at [demo.paperless-ngx.com](https://demo.paperless-ngx.com) | ||||
|  | ||||
|  | ||||
|  | ||||
| * Organize and index your scanned documents with tags, correspondents, types, and more. | ||||
| * Performs OCR on your documents, adds selectable text to image only documents and adds tags, correspondents and document types to your documents. | ||||
| * Supports PDF documents, images, plain text files, and Office documents (Word, Excel, Powerpoint, and LibreOffice equivalents). | ||||
| 	* Office document support is optional and provided by Apache Tika (see [configuration](https://paperless-ngx.readthedocs.io/en/latest/configuration.html#tika-settings)) | ||||
| * Paperless stores your documents plain on disk. Filenames and folders are managed by paperless and their format can be configured freely. | ||||
| * Single page application front end. | ||||
| 	* Includes a dashboard that shows basic statistics and has document upload. | ||||
| 	* Filtering by tags, correspondents, types, and more. | ||||
| 	* Customizable views can be saved and displayed on the dashboard. | ||||
| * Full text search helps you find what you need. | ||||
| 	* Auto completion suggests relevant words from your documents. | ||||
| 	* Results are sorted by relevance to your search query. | ||||
| 	* Highlighting shows you which parts of the document matched the query. | ||||
| 	* Searching for similar documents ("More like this") | ||||
| * Email processing: Paperless adds documents from your email accounts. | ||||
| 	* Configure multiple accounts and filters for each account. | ||||
| 	* When adding documents from mail, paperless can move these mail to a new folder, mark them as read, flag them as important or delete them. | ||||
| * Machine learning powered document matching. | ||||
| 	* Paperless-ngx learns from your documents and will be able to automatically assign tags, correspondents and types to documents once you've stored a few documents in paperless. | ||||
| * Optimized for multi core systems: Paperless-ngx consumes multiple documents in parallel. | ||||
| * The integrated sanity checker makes sure that your document archive is in good health. | ||||
| * [More screenshots are available in the documentation](https://paperless-ngx.readthedocs.io/en/latest/screenshots.html). | ||||
| - Organize and index your scanned documents with tags, correspondents, types, and more. | ||||
| - Performs OCR on your documents, adds selectable text to image only documents and adds tags, correspondents and document types to your documents. | ||||
| - Supports PDF documents, images, plain text files, and Office documents (Word, Excel, Powerpoint, and LibreOffice equivalents). | ||||
|   - Office document support is optional and provided by Apache Tika (see [configuration](https://paperless-ngx.readthedocs.io/en/latest/configuration.html#tika-settings)) | ||||
| - Paperless stores your documents plain on disk. Filenames and folders are managed by paperless and their format can be configured freely. | ||||
| - Single page application front end. | ||||
|   - Includes a dashboard that shows basic statistics and has document upload. | ||||
|   - Filtering by tags, correspondents, types, and more. | ||||
|   - Customizable views can be saved and displayed on the dashboard. | ||||
| - Full text search helps you find what you need. | ||||
|   - Auto completion suggests relevant words from your documents. | ||||
|   - Results are sorted by relevance to your search query. | ||||
|   - Highlighting shows you which parts of the document matched the query. | ||||
|   - Searching for similar documents ("More like this") | ||||
| - Email processing: Paperless adds documents from your email accounts. | ||||
|   - Configure multiple accounts and filters for each account. | ||||
|   - When adding documents from mail, paperless can move these mail to a new folder, mark them as read, flag them as important or delete them. | ||||
| - Machine learning powered document matching. | ||||
|   - Paperless-ngx learns from your documents and will be able to automatically assign tags, correspondents and types to documents once you've stored a few documents in paperless. | ||||
| - Optimized for multi core systems: Paperless-ngx consumes multiple documents in parallel. | ||||
| - The integrated sanity checker makes sure that your document archive is in good health. | ||||
| - [More screenshots are available in the documentation](https://paperless-ngx.readthedocs.io/en/latest/screenshots.html). | ||||
|  | ||||
| # Getting started | ||||
|  | ||||
| @@ -73,6 +73,7 @@ Alternatively, you can install the dependencies and setup apache and a database | ||||
| Migrating from Paperless-ng is easy, just drop in the new docker image! See the [documentation on migrating](https://paperless-ngx.readthedocs.io/en/latest/setup.html#migrating-from-paperless-ng) for more details. | ||||
|  | ||||
| <!-- omit in toc --> | ||||
|  | ||||
| ### Documentation | ||||
|  | ||||
| The documentation for Paperless-ngx is available on [ReadTheDocs](https://paperless-ngx.readthedocs.io/). | ||||
| @@ -101,18 +102,18 @@ For bugs please [open an issue](https://github.com/paperless-ngx/paperless-ngx/i | ||||
|  | ||||
| Paperless has been around a while now, and people are starting to build stuff on top of it. If you're one of those people, we can add your project to this list: | ||||
|  | ||||
| * [Paperless App](https://github.com/bauerj/paperless_app): An Android/iOS app for Paperless-ngx. Also works with the original Paperless and Paperless-ngx. | ||||
| * [Paperless Share](https://github.com/qcasey/paperless_share). Share any files from your Android application with paperless. Very simple, but works with all of the mobile scanning apps out there that allow you to share scanned documents. | ||||
| * [Scan to Paperless](https://github.com/sbrunner/scan-to-paperless): Scan and prepare (crop, deskew, OCR, ...) your documents for Paperless. | ||||
| - [Paperless App](https://github.com/bauerj/paperless_app): An Android/iOS app for Paperless-ngx. Also works with the original Paperless and Paperless-ngx. | ||||
| - [Paperless Share](https://github.com/qcasey/paperless_share). Share any files from your Android application with paperless. Very simple, but works with all of the mobile scanning apps out there that allow you to share scanned documents. | ||||
| - [Scan to Paperless](https://github.com/sbrunner/scan-to-paperless): Scan and prepare (crop, deskew, OCR, ...) your documents for Paperless. | ||||
|  | ||||
| These projects also exist, but their status and compatibility with paperless-ngx is unknown. | ||||
|  | ||||
| * [paperless-cli](https://github.com/stgarf/paperless-cli): A golang command line binary to interact with a Paperless instance. | ||||
| - [paperless-cli](https://github.com/stgarf/paperless-cli): A golang command line binary to interact with a Paperless instance. | ||||
|  | ||||
| This project also exists, but needs updates to be compatible with paperless-ngx. | ||||
|  | ||||
| * [Paperless Desktop](https://github.com/thomasbrueggemann/paperless-desktop): A desktop UI for your Paperless installation. Runs on Mac, Linux, and Windows. | ||||
| 	Known issues on Mac: (Could not load reminders and documents) | ||||
| - [Paperless Desktop](https://github.com/thomasbrueggemann/paperless-desktop): A desktop UI for your Paperless installation. Runs on Mac, Linux, and Windows. | ||||
|   Known issues on Mac: (Could not load reminders and documents) | ||||
|  | ||||
| # Important Note | ||||
|  | ||||
|   | ||||
| @@ -2,18 +2,16 @@ | ||||
| // Protractor configuration file, see link for more information | ||||
| // https://github.com/angular/protractor/blob/master/lib/config.ts | ||||
|  | ||||
| const { SpecReporter, StacktraceOption } = require('jasmine-spec-reporter'); | ||||
| const { SpecReporter, StacktraceOption } = require('jasmine-spec-reporter') | ||||
|  | ||||
| /** | ||||
|  * @type { import("protractor").Config } | ||||
|  */ | ||||
| exports.config = { | ||||
|   allScriptsTimeout: 11000, | ||||
|   specs: [ | ||||
|     './src/**/*.e2e-spec.ts' | ||||
|   ], | ||||
|   specs: ['./src/**/*.e2e-spec.ts'], | ||||
|   capabilities: { | ||||
|     browserName: 'chrome' | ||||
|     browserName: 'chrome', | ||||
|   }, | ||||
|   directConnect: true, | ||||
|   baseUrl: 'http://localhost:4200/', | ||||
| @@ -21,16 +19,18 @@ exports.config = { | ||||
|   jasmineNodeOpts: { | ||||
|     showColors: true, | ||||
|     defaultTimeoutInterval: 30000, | ||||
|     print: function() {} | ||||
|     print: function () {}, | ||||
|   }, | ||||
|   onPrepare() { | ||||
|     require('ts-node').register({ | ||||
|       project: require('path').join(__dirname, './tsconfig.json') | ||||
|     }); | ||||
|     jasmine.getEnv().addReporter(new SpecReporter({ | ||||
|       spec: { | ||||
|         displayStacktrace: StacktraceOption.PRETTY | ||||
|       } | ||||
|     })); | ||||
|   } | ||||
| }; | ||||
|       project: require('path').join(__dirname, './tsconfig.json'), | ||||
|     }) | ||||
|     jasmine.getEnv().addReporter( | ||||
|       new SpecReporter({ | ||||
|         spec: { | ||||
|           displayStacktrace: StacktraceOption.PRETTY, | ||||
|         }, | ||||
|       }) | ||||
|     ) | ||||
|   }, | ||||
| } | ||||
|   | ||||
| @@ -1,23 +1,25 @@ | ||||
| import { AppPage } from './app.po'; | ||||
| import { browser, logging } from 'protractor'; | ||||
| import { AppPage } from './app.po' | ||||
| import { browser, logging } from 'protractor' | ||||
|  | ||||
| describe('workspace-project App', () => { | ||||
|   let page: AppPage; | ||||
|   let page: AppPage | ||||
|  | ||||
|   beforeEach(() => { | ||||
|     page = new AppPage(); | ||||
|   }); | ||||
|     page = new AppPage() | ||||
|   }) | ||||
|  | ||||
|   it('should display welcome message', () => { | ||||
|     page.navigateTo(); | ||||
|     expect(page.getTitleText()).toEqual('paperless-ui app is running!'); | ||||
|   }); | ||||
|     page.navigateTo() | ||||
|     expect(page.getTitleText()).toEqual('paperless-ui app is running!') | ||||
|   }) | ||||
|  | ||||
|   afterEach(async () => { | ||||
|     // Assert that there are no errors emitted from the browser | ||||
|     const logs = await browser.manage().logs().get(logging.Type.BROWSER); | ||||
|     expect(logs).not.toContain(jasmine.objectContaining({ | ||||
|       level: logging.Level.SEVERE, | ||||
|     } as logging.Entry)); | ||||
|   }); | ||||
| }); | ||||
|     const logs = await browser.manage().logs().get(logging.Type.BROWSER) | ||||
|     expect(logs).not.toContain( | ||||
|       jasmine.objectContaining({ | ||||
|         level: logging.Level.SEVERE, | ||||
|       } as logging.Entry) | ||||
|     ) | ||||
|   }) | ||||
| }) | ||||
|   | ||||
| @@ -1,11 +1,13 @@ | ||||
| import { browser, by, element } from 'protractor'; | ||||
| import { browser, by, element } from 'protractor' | ||||
|  | ||||
| export class AppPage { | ||||
|   navigateTo(): Promise<unknown> { | ||||
|     return browser.get(browser.baseUrl) as Promise<unknown>; | ||||
|     return browser.get(browser.baseUrl) as Promise<unknown> | ||||
|   } | ||||
|  | ||||
|   getTitleText(): Promise<string> { | ||||
|     return element(by.css('app-root .content span')).getText() as Promise<string>; | ||||
|     return element( | ||||
|       by.css('app-root .content span') | ||||
|     ).getText() as Promise<string> | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -10,15 +10,15 @@ module.exports = function (config) { | ||||
|       require('karma-chrome-launcher'), | ||||
|       require('karma-jasmine-html-reporter'), | ||||
|       require('karma-coverage-istanbul-reporter'), | ||||
|       require('@angular-devkit/build-angular/plugins/karma') | ||||
|       require('@angular-devkit/build-angular/plugins/karma'), | ||||
|     ], | ||||
|     client: { | ||||
|       clearContext: false // leave Jasmine Spec Runner output visible in browser | ||||
|       clearContext: false, // leave Jasmine Spec Runner output visible in browser | ||||
|     }, | ||||
|     coverageIstanbulReporter: { | ||||
|       dir: require('path').join(__dirname, './coverage/paperless-ui'), | ||||
|       reports: ['html', 'lcovonly', 'text-summary'], | ||||
|       fixWebpackSourcePaths: true | ||||
|       fixWebpackSourcePaths: true, | ||||
|     }, | ||||
|     reporters: ['progress', 'kjhtml'], | ||||
|     port: 9876, | ||||
| @@ -27,6 +27,6 @@ module.exports = function (config) { | ||||
|     autoWatch: true, | ||||
|     browsers: ['Chrome'], | ||||
|     singleRun: false, | ||||
|     restartOnFileChange: true | ||||
|   }); | ||||
| }; | ||||
|     restartOnFileChange: true, | ||||
|   }) | ||||
| } | ||||
|   | ||||
| @@ -1,39 +1,47 @@ | ||||
| import { NgModule } from '@angular/core'; | ||||
| import { Routes, RouterModule } from '@angular/router'; | ||||
| import { AppFrameComponent } from './components/app-frame/app-frame.component'; | ||||
| import { DashboardComponent } from './components/dashboard/dashboard.component'; | ||||
| import { DocumentDetailComponent } from './components/document-detail/document-detail.component'; | ||||
| import { DocumentListComponent } from './components/document-list/document-list.component'; | ||||
| import { CorrespondentListComponent } from './components/manage/correspondent-list/correspondent-list.component'; | ||||
| import { DocumentTypeListComponent } from './components/manage/document-type-list/document-type-list.component'; | ||||
| import { LogsComponent } from './components/manage/logs/logs.component'; | ||||
| import { SettingsComponent } from './components/manage/settings/settings.component'; | ||||
| import { TagListComponent } from './components/manage/tag-list/tag-list.component'; | ||||
| import { NotFoundComponent } from './components/not-found/not-found.component'; | ||||
| import {DocumentAsnComponent} from "./components/document-asn/document-asn.component"; | ||||
| import { DirtyFormGuard } from './guards/dirty-form.guard'; | ||||
| import { NgModule } from '@angular/core' | ||||
| import { Routes, RouterModule } from '@angular/router' | ||||
| import { AppFrameComponent } from './components/app-frame/app-frame.component' | ||||
| import { DashboardComponent } from './components/dashboard/dashboard.component' | ||||
| import { DocumentDetailComponent } from './components/document-detail/document-detail.component' | ||||
| import { DocumentListComponent } from './components/document-list/document-list.component' | ||||
| import { CorrespondentListComponent } from './components/manage/correspondent-list/correspondent-list.component' | ||||
| import { DocumentTypeListComponent } from './components/manage/document-type-list/document-type-list.component' | ||||
| import { LogsComponent } from './components/manage/logs/logs.component' | ||||
| import { SettingsComponent } from './components/manage/settings/settings.component' | ||||
| import { TagListComponent } from './components/manage/tag-list/tag-list.component' | ||||
| import { NotFoundComponent } from './components/not-found/not-found.component' | ||||
| import { DocumentAsnComponent } from './components/document-asn/document-asn.component' | ||||
| import { DirtyFormGuard } from './guards/dirty-form.guard' | ||||
|  | ||||
| const routes: Routes = [ | ||||
|   {path: '', redirectTo: 'dashboard', pathMatch: 'full'}, | ||||
|   {path: '', component: AppFrameComponent, children: [ | ||||
|     {path: 'dashboard', component: DashboardComponent }, | ||||
|     {path: 'documents', component: DocumentListComponent }, | ||||
|     {path: 'view/:id', component: DocumentListComponent }, | ||||
|     {path: 'documents/:id', component: DocumentDetailComponent }, | ||||
|     {path: 'asn/:id', component: DocumentAsnComponent }, | ||||
|     {path: 'tags', component: TagListComponent }, | ||||
|     {path: 'documenttypes', component: DocumentTypeListComponent }, | ||||
|     {path: 'correspondents', component: CorrespondentListComponent }, | ||||
|     {path: 'logs', component: LogsComponent }, | ||||
|     {path: 'settings', component: SettingsComponent, canDeactivate: [DirtyFormGuard] }, | ||||
|   ]}, | ||||
|   { path: '', redirectTo: 'dashboard', pathMatch: 'full' }, | ||||
|   { | ||||
|     path: '', | ||||
|     component: AppFrameComponent, | ||||
|     children: [ | ||||
|       { path: 'dashboard', component: DashboardComponent }, | ||||
|       { path: 'documents', component: DocumentListComponent }, | ||||
|       { path: 'view/:id', component: DocumentListComponent }, | ||||
|       { path: 'documents/:id', component: DocumentDetailComponent }, | ||||
|       { path: 'asn/:id', component: DocumentAsnComponent }, | ||||
|       { path: 'tags', component: TagListComponent }, | ||||
|       { path: 'documenttypes', component: DocumentTypeListComponent }, | ||||
|       { path: 'correspondents', component: CorrespondentListComponent }, | ||||
|       { path: 'logs', component: LogsComponent }, | ||||
|       { | ||||
|         path: 'settings', | ||||
|         component: SettingsComponent, | ||||
|         canDeactivate: [DirtyFormGuard], | ||||
|       }, | ||||
|     ], | ||||
|   }, | ||||
|  | ||||
|   {path: '404', component: NotFoundComponent}, | ||||
|   {path: '**', redirectTo: '/404', pathMatch: 'full'} | ||||
| ]; | ||||
|   { path: '404', component: NotFoundComponent }, | ||||
|   { path: '**', redirectTo: '/404', pathMatch: 'full' }, | ||||
| ] | ||||
|  | ||||
| @NgModule({ | ||||
|   imports: [RouterModule.forRoot(routes, { relativeLinkResolution: 'legacy' })], | ||||
|   exports: [RouterModule] | ||||
|   exports: [RouterModule], | ||||
| }) | ||||
| export class AppRoutingModule { } | ||||
| export class AppRoutingModule {} | ||||
|   | ||||
| @@ -1,35 +1,33 @@ | ||||
| import { TestBed } from '@angular/core/testing'; | ||||
| import { RouterTestingModule } from '@angular/router/testing'; | ||||
| import { AppComponent } from './app.component'; | ||||
| import { TestBed } from '@angular/core/testing' | ||||
| import { RouterTestingModule } from '@angular/router/testing' | ||||
| import { AppComponent } from './app.component' | ||||
|  | ||||
| describe('AppComponent', () => { | ||||
|   beforeEach(async () => { | ||||
|     await TestBed.configureTestingModule({ | ||||
|       imports: [ | ||||
|         RouterTestingModule | ||||
|       ], | ||||
|       declarations: [ | ||||
|         AppComponent | ||||
|       ], | ||||
|     }).compileComponents(); | ||||
|   }); | ||||
|       imports: [RouterTestingModule], | ||||
|       declarations: [AppComponent], | ||||
|     }).compileComponents() | ||||
|   }) | ||||
|  | ||||
|   it('should create the app', () => { | ||||
|     const fixture = TestBed.createComponent(AppComponent); | ||||
|     const app = fixture.componentInstance; | ||||
|     expect(app).toBeTruthy(); | ||||
|   }); | ||||
|     const fixture = TestBed.createComponent(AppComponent) | ||||
|     const app = fixture.componentInstance | ||||
|     expect(app).toBeTruthy() | ||||
|   }) | ||||
|  | ||||
|   it(`should have as title 'paperless-ui'`, () => { | ||||
|     const fixture = TestBed.createComponent(AppComponent); | ||||
|     const app = fixture.componentInstance; | ||||
|     expect(app.title).toEqual('paperless-ui'); | ||||
|   }); | ||||
|     const fixture = TestBed.createComponent(AppComponent) | ||||
|     const app = fixture.componentInstance | ||||
|     expect(app.title).toEqual('paperless-ui') | ||||
|   }) | ||||
|  | ||||
|   it('should render title', () => { | ||||
|     const fixture = TestBed.createComponent(AppComponent); | ||||
|     fixture.detectChanges(); | ||||
|     const compiled = fixture.nativeElement; | ||||
|     expect(compiled.querySelector('.content span').textContent).toContain('paperless-ui app is running!'); | ||||
|   }); | ||||
| }); | ||||
|     const fixture = TestBed.createComponent(AppComponent) | ||||
|     fixture.detectChanges() | ||||
|     const compiled = fixture.nativeElement | ||||
|     expect(compiled.querySelector('.content span').textContent).toContain( | ||||
|       'paperless-ui app is running!' | ||||
|     ) | ||||
|   }) | ||||
| }) | ||||
|   | ||||
| @@ -1,24 +1,28 @@ | ||||
| import { SettingsService, SETTINGS_KEYS } from './services/settings.service'; | ||||
| import { Component, OnDestroy, OnInit } from '@angular/core'; | ||||
| import { Router } from '@angular/router'; | ||||
| import { Subscription } from 'rxjs'; | ||||
| import { ConsumerStatusService } from './services/consumer-status.service'; | ||||
| import { ToastService } from './services/toast.service'; | ||||
| import { SettingsService, SETTINGS_KEYS } from './services/settings.service' | ||||
| import { Component, OnDestroy, OnInit } from '@angular/core' | ||||
| import { Router } from '@angular/router' | ||||
| import { Subscription } from 'rxjs' | ||||
| import { ConsumerStatusService } from './services/consumer-status.service' | ||||
| import { ToastService } from './services/toast.service' | ||||
|  | ||||
| @Component({ | ||||
|   selector: 'app-root', | ||||
|   templateUrl: './app.component.html', | ||||
|   styleUrls: ['./app.component.scss'] | ||||
|   styleUrls: ['./app.component.scss'], | ||||
| }) | ||||
| export class AppComponent implements OnInit, OnDestroy { | ||||
|   newDocumentSubscription: Subscription | ||||
|   successSubscription: Subscription | ||||
|   failedSubscription: Subscription | ||||
|  | ||||
|   newDocumentSubscription: Subscription; | ||||
|   successSubscription: Subscription; | ||||
|   failedSubscription: Subscription; | ||||
|  | ||||
|   constructor (private settings: SettingsService, private consumerStatusService: ConsumerStatusService, private toastService: ToastService, private router: Router) { | ||||
|     let anyWindow = (window as any) | ||||
|     anyWindow.pdfWorkerSrc = 'assets/js/pdf.worker.min.js'; | ||||
|   constructor( | ||||
|     private settings: SettingsService, | ||||
|     private consumerStatusService: ConsumerStatusService, | ||||
|     private toastService: ToastService, | ||||
|     private router: Router | ||||
|   ) { | ||||
|     let anyWindow = window as any | ||||
|     anyWindow.pdfWorkerSrc = 'assets/js/pdf.worker.min.js' | ||||
|     this.settings.updateAppearanceSettings() | ||||
|   } | ||||
|  | ||||
| @@ -36,7 +40,12 @@ export class AppComponent implements OnInit, OnDestroy { | ||||
|   } | ||||
|  | ||||
|   private showNotification(key) { | ||||
|     if (this.router.url == '/dashboard' && this.settings.get(SETTINGS_KEYS.NOTIFICATIONS_CONSUMER_SUPPRESS_ON_DASHBOARD)) { | ||||
|     if ( | ||||
|       this.router.url == '/dashboard' && | ||||
|       this.settings.get( | ||||
|         SETTINGS_KEYS.NOTIFICATIONS_CONSUMER_SUPPRESS_ON_DASHBOARD | ||||
|       ) | ||||
|     ) { | ||||
|       return false | ||||
|     } | ||||
|     return this.settings.get(key) | ||||
| @@ -45,26 +54,50 @@ export class AppComponent implements OnInit, OnDestroy { | ||||
|   ngOnInit(): void { | ||||
|     this.consumerStatusService.connect() | ||||
|  | ||||
|     this.successSubscription = this.consumerStatusService | ||||
|       .onDocumentConsumptionFinished() | ||||
|       .subscribe((status) => { | ||||
|         if ( | ||||
|           this.showNotification(SETTINGS_KEYS.NOTIFICATIONS_CONSUMER_SUCCESS) | ||||
|         ) { | ||||
|           this.toastService.show({ | ||||
|             title: $localize`Document added`, | ||||
|             delay: 10000, | ||||
|             content: $localize`Document ${status.filename} was added to paperless.`, | ||||
|             actionName: $localize`Open document`, | ||||
|             action: () => { | ||||
|               this.router.navigate(['documents', status.documentId]) | ||||
|             }, | ||||
|           }) | ||||
|         } | ||||
|       }) | ||||
|  | ||||
|     this.successSubscription = this.consumerStatusService.onDocumentConsumptionFinished().subscribe(status => { | ||||
|       if (this.showNotification(SETTINGS_KEYS.NOTIFICATIONS_CONSUMER_SUCCESS)) { | ||||
|         this.toastService.show({title: $localize`Document added`, delay: 10000, content: $localize`Document ${status.filename} was added to paperless.`, actionName: $localize`Open document`, action: () => { | ||||
|           this.router.navigate(['documents', status.documentId]) | ||||
|         }}) | ||||
|       } | ||||
|     }) | ||||
|     this.failedSubscription = this.consumerStatusService | ||||
|       .onDocumentConsumptionFailed() | ||||
|       .subscribe((status) => { | ||||
|         if ( | ||||
|           this.showNotification(SETTINGS_KEYS.NOTIFICATIONS_CONSUMER_FAILED) | ||||
|         ) { | ||||
|           this.toastService.showError( | ||||
|             $localize`Could not add ${status.filename}\: ${status.message}` | ||||
|           ) | ||||
|         } | ||||
|       }) | ||||
|  | ||||
|     this.failedSubscription = this.consumerStatusService.onDocumentConsumptionFailed().subscribe(status => { | ||||
|       if (this.showNotification(SETTINGS_KEYS.NOTIFICATIONS_CONSUMER_FAILED)) { | ||||
|         this.toastService.showError($localize`Could not add ${status.filename}\: ${status.message}`) | ||||
|       } | ||||
|     }) | ||||
|  | ||||
|     this.newDocumentSubscription = this.consumerStatusService.onDocumentDetected().subscribe(status => { | ||||
|       if (this.showNotification(SETTINGS_KEYS.NOTIFICATIONS_CONSUMER_NEW_DOCUMENT)) { | ||||
|         this.toastService.show({title: $localize`New document detected`, delay: 5000, content: $localize`Document ${status.filename} is being processed by paperless.`}) | ||||
|       } | ||||
|     }) | ||||
|     this.newDocumentSubscription = this.consumerStatusService | ||||
|       .onDocumentDetected() | ||||
|       .subscribe((status) => { | ||||
|         if ( | ||||
|           this.showNotification( | ||||
|             SETTINGS_KEYS.NOTIFICATIONS_CONSUMER_NEW_DOCUMENT | ||||
|           ) | ||||
|         ) { | ||||
|           this.toastService.show({ | ||||
|             title: $localize`New document detected`, | ||||
|             delay: 5000, | ||||
|             content: $localize`Document ${status.filename} is being processed by paperless.`, | ||||
|           }) | ||||
|         } | ||||
|       }) | ||||
|   } | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -1,85 +1,88 @@ | ||||
| import { BrowserModule } from '@angular/platform-browser'; | ||||
| import { NgModule } from '@angular/core'; | ||||
| import { BrowserModule } from '@angular/platform-browser' | ||||
| import { NgModule } from '@angular/core' | ||||
|  | ||||
| import { AppRoutingModule } from './app-routing.module'; | ||||
| import { AppComponent } from './app.component'; | ||||
| import { NgbDateAdapter, NgbDateParserFormatter, NgbModule } from '@ng-bootstrap/ng-bootstrap'; | ||||
| import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http'; | ||||
| import { DocumentListComponent } from './components/document-list/document-list.component'; | ||||
| import { DocumentDetailComponent } from './components/document-detail/document-detail.component'; | ||||
| import { DashboardComponent } from './components/dashboard/dashboard.component'; | ||||
| import { TagListComponent } from './components/manage/tag-list/tag-list.component'; | ||||
| import { DocumentTypeListComponent } from './components/manage/document-type-list/document-type-list.component'; | ||||
| import { LogsComponent } from './components/manage/logs/logs.component'; | ||||
| import { SettingsComponent } from './components/manage/settings/settings.component'; | ||||
| import { FormsModule, ReactiveFormsModule } from '@angular/forms'; | ||||
| import { DatePipe, registerLocaleData } from '@angular/common'; | ||||
| import { NotFoundComponent } from './components/not-found/not-found.component'; | ||||
| import { CorrespondentListComponent } from './components/manage/correspondent-list/correspondent-list.component'; | ||||
| import { ConfirmDialogComponent } from './components/common/confirm-dialog/confirm-dialog.component'; | ||||
| import { CorrespondentEditDialogComponent } from './components/manage/correspondent-list/correspondent-edit-dialog/correspondent-edit-dialog.component'; | ||||
| import { TagEditDialogComponent } from './components/manage/tag-list/tag-edit-dialog/tag-edit-dialog.component'; | ||||
| import { DocumentTypeEditDialogComponent } from './components/manage/document-type-list/document-type-edit-dialog/document-type-edit-dialog.component'; | ||||
| import { TagComponent } from './components/common/tag/tag.component'; | ||||
| import { PageHeaderComponent } from './components/common/page-header/page-header.component'; | ||||
| import { AppFrameComponent } from './components/app-frame/app-frame.component'; | ||||
| import { ToastsComponent } from './components/common/toasts/toasts.component'; | ||||
| import { FilterEditorComponent } from './components/document-list/filter-editor/filter-editor.component'; | ||||
| import { FilterableDropdownComponent } from './components/common/filterable-dropdown/filterable-dropdown.component'; | ||||
| import { ToggleableDropdownButtonComponent } from './components/common/filterable-dropdown/toggleable-dropdown-button/toggleable-dropdown-button.component'; | ||||
| import { DateDropdownComponent } from './components/common/date-dropdown/date-dropdown.component'; | ||||
| import { DocumentCardLargeComponent } from './components/document-list/document-card-large/document-card-large.component'; | ||||
| import { DocumentCardSmallComponent } from './components/document-list/document-card-small/document-card-small.component'; | ||||
| import { BulkEditorComponent } from './components/document-list/bulk-editor/bulk-editor.component'; | ||||
| import { NgxFileDropModule } from 'ngx-file-drop'; | ||||
| import { TextComponent } from './components/common/input/text/text.component'; | ||||
| import { SelectComponent } from './components/common/input/select/select.component'; | ||||
| import { CheckComponent } from './components/common/input/check/check.component'; | ||||
| import { SaveViewConfigDialogComponent } from './components/document-list/save-view-config-dialog/save-view-config-dialog.component'; | ||||
| import { InfiniteScrollModule } from 'ngx-infinite-scroll'; | ||||
| import { TagsComponent } from './components/common/input/tags/tags.component'; | ||||
| import { SortableDirective } from './directives/sortable.directive'; | ||||
| import { CookieService } from 'ngx-cookie-service'; | ||||
| import { CsrfInterceptor } from './interceptors/csrf.interceptor'; | ||||
| import { SavedViewWidgetComponent } from './components/dashboard/widgets/saved-view-widget/saved-view-widget.component'; | ||||
| import { StatisticsWidgetComponent } from './components/dashboard/widgets/statistics-widget/statistics-widget.component'; | ||||
| import { UploadFileWidgetComponent } from './components/dashboard/widgets/upload-file-widget/upload-file-widget.component'; | ||||
| import { WidgetFrameComponent } from './components/dashboard/widgets/widget-frame/widget-frame.component'; | ||||
| import { PdfViewerModule } from 'ng2-pdf-viewer'; | ||||
| import { WelcomeWidgetComponent } from './components/dashboard/widgets/welcome-widget/welcome-widget.component'; | ||||
| import { YesNoPipe } from './pipes/yes-no.pipe'; | ||||
| import { FileSizePipe } from './pipes/file-size.pipe'; | ||||
| import { FilterPipe } from './pipes/filter.pipe'; | ||||
| import { DocumentTitlePipe } from './pipes/document-title.pipe'; | ||||
| import { MetadataCollapseComponent } from './components/document-detail/metadata-collapse/metadata-collapse.component'; | ||||
| import { SelectDialogComponent } from './components/common/select-dialog/select-dialog.component'; | ||||
| import { NgSelectModule } from '@ng-select/ng-select'; | ||||
| import { NumberComponent } from './components/common/input/number/number.component'; | ||||
| import { SafePipe } from './pipes/safe.pipe'; | ||||
| import { CustomDatePipe } from './pipes/custom-date.pipe'; | ||||
| import { DateComponent } from './components/common/input/date/date.component'; | ||||
| import { ISODateTimeAdapter } from './utils/ngb-iso-date-time-adapter'; | ||||
| import { LocalizedDateParserFormatter } from './utils/ngb-date-parser-formatter'; | ||||
| import { ApiVersionInterceptor } from './interceptors/api-version.interceptor'; | ||||
| import { ColorSliderModule } from 'ngx-color/slider'; | ||||
| import { ColorComponent } from './components/common/input/color/color.component'; | ||||
| import { DocumentAsnComponent } from './components/document-asn/document-asn.component'; | ||||
|  | ||||
| import localeCs from '@angular/common/locales/cs'; | ||||
| import localeDa from '@angular/common/locales/da'; | ||||
| import localeDe from '@angular/common/locales/de'; | ||||
| import localeEnGb from '@angular/common/locales/en-GB'; | ||||
| import localeEs from '@angular/common/locales/es'; | ||||
| import localeFr from '@angular/common/locales/fr'; | ||||
| import localeIt from '@angular/common/locales/it'; | ||||
| import localeLb from '@angular/common/locales/lb'; | ||||
| import localeNl from '@angular/common/locales/nl'; | ||||
| import localePl from '@angular/common/locales/pl'; | ||||
| import localePt from '@angular/common/locales/pt'; | ||||
| import localeSv from '@angular/common/locales/sv'; | ||||
| import localeRo from '@angular/common/locales/ro'; | ||||
| import localeRu from '@angular/common/locales/ru'; | ||||
| import { AppRoutingModule } from './app-routing.module' | ||||
| import { AppComponent } from './app.component' | ||||
| import { | ||||
|   NgbDateAdapter, | ||||
|   NgbDateParserFormatter, | ||||
|   NgbModule, | ||||
| } from '@ng-bootstrap/ng-bootstrap' | ||||
| import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http' | ||||
| import { DocumentListComponent } from './components/document-list/document-list.component' | ||||
| import { DocumentDetailComponent } from './components/document-detail/document-detail.component' | ||||
| import { DashboardComponent } from './components/dashboard/dashboard.component' | ||||
| import { TagListComponent } from './components/manage/tag-list/tag-list.component' | ||||
| import { DocumentTypeListComponent } from './components/manage/document-type-list/document-type-list.component' | ||||
| import { LogsComponent } from './components/manage/logs/logs.component' | ||||
| import { SettingsComponent } from './components/manage/settings/settings.component' | ||||
| import { FormsModule, ReactiveFormsModule } from '@angular/forms' | ||||
| import { DatePipe, registerLocaleData } from '@angular/common' | ||||
| import { NotFoundComponent } from './components/not-found/not-found.component' | ||||
| import { CorrespondentListComponent } from './components/manage/correspondent-list/correspondent-list.component' | ||||
| import { ConfirmDialogComponent } from './components/common/confirm-dialog/confirm-dialog.component' | ||||
| import { CorrespondentEditDialogComponent } from './components/manage/correspondent-list/correspondent-edit-dialog/correspondent-edit-dialog.component' | ||||
| import { TagEditDialogComponent } from './components/manage/tag-list/tag-edit-dialog/tag-edit-dialog.component' | ||||
| import { DocumentTypeEditDialogComponent } from './components/manage/document-type-list/document-type-edit-dialog/document-type-edit-dialog.component' | ||||
| import { TagComponent } from './components/common/tag/tag.component' | ||||
| import { PageHeaderComponent } from './components/common/page-header/page-header.component' | ||||
| import { AppFrameComponent } from './components/app-frame/app-frame.component' | ||||
| import { ToastsComponent } from './components/common/toasts/toasts.component' | ||||
| import { FilterEditorComponent } from './components/document-list/filter-editor/filter-editor.component' | ||||
| import { FilterableDropdownComponent } from './components/common/filterable-dropdown/filterable-dropdown.component' | ||||
| import { ToggleableDropdownButtonComponent } from './components/common/filterable-dropdown/toggleable-dropdown-button/toggleable-dropdown-button.component' | ||||
| import { DateDropdownComponent } from './components/common/date-dropdown/date-dropdown.component' | ||||
| import { DocumentCardLargeComponent } from './components/document-list/document-card-large/document-card-large.component' | ||||
| import { DocumentCardSmallComponent } from './components/document-list/document-card-small/document-card-small.component' | ||||
| import { BulkEditorComponent } from './components/document-list/bulk-editor/bulk-editor.component' | ||||
| import { NgxFileDropModule } from 'ngx-file-drop' | ||||
| import { TextComponent } from './components/common/input/text/text.component' | ||||
| import { SelectComponent } from './components/common/input/select/select.component' | ||||
| import { CheckComponent } from './components/common/input/check/check.component' | ||||
| import { SaveViewConfigDialogComponent } from './components/document-list/save-view-config-dialog/save-view-config-dialog.component' | ||||
| import { InfiniteScrollModule } from 'ngx-infinite-scroll' | ||||
| import { TagsComponent } from './components/common/input/tags/tags.component' | ||||
| import { SortableDirective } from './directives/sortable.directive' | ||||
| import { CookieService } from 'ngx-cookie-service' | ||||
| import { CsrfInterceptor } from './interceptors/csrf.interceptor' | ||||
| import { SavedViewWidgetComponent } from './components/dashboard/widgets/saved-view-widget/saved-view-widget.component' | ||||
| import { StatisticsWidgetComponent } from './components/dashboard/widgets/statistics-widget/statistics-widget.component' | ||||
| import { UploadFileWidgetComponent } from './components/dashboard/widgets/upload-file-widget/upload-file-widget.component' | ||||
| import { WidgetFrameComponent } from './components/dashboard/widgets/widget-frame/widget-frame.component' | ||||
| import { PdfViewerModule } from 'ng2-pdf-viewer' | ||||
| import { WelcomeWidgetComponent } from './components/dashboard/widgets/welcome-widget/welcome-widget.component' | ||||
| import { YesNoPipe } from './pipes/yes-no.pipe' | ||||
| import { FileSizePipe } from './pipes/file-size.pipe' | ||||
| import { FilterPipe } from './pipes/filter.pipe' | ||||
| import { DocumentTitlePipe } from './pipes/document-title.pipe' | ||||
| import { MetadataCollapseComponent } from './components/document-detail/metadata-collapse/metadata-collapse.component' | ||||
| import { SelectDialogComponent } from './components/common/select-dialog/select-dialog.component' | ||||
| import { NgSelectModule } from '@ng-select/ng-select' | ||||
| import { NumberComponent } from './components/common/input/number/number.component' | ||||
| import { SafePipe } from './pipes/safe.pipe' | ||||
| import { CustomDatePipe } from './pipes/custom-date.pipe' | ||||
| import { DateComponent } from './components/common/input/date/date.component' | ||||
| import { ISODateTimeAdapter } from './utils/ngb-iso-date-time-adapter' | ||||
| import { LocalizedDateParserFormatter } from './utils/ngb-date-parser-formatter' | ||||
| import { ApiVersionInterceptor } from './interceptors/api-version.interceptor' | ||||
| import { ColorSliderModule } from 'ngx-color/slider' | ||||
| import { ColorComponent } from './components/common/input/color/color.component' | ||||
| import { DocumentAsnComponent } from './components/document-asn/document-asn.component' | ||||
|  | ||||
| import localeCs from '@angular/common/locales/cs' | ||||
| import localeDa from '@angular/common/locales/da' | ||||
| import localeDe from '@angular/common/locales/de' | ||||
| import localeEnGb from '@angular/common/locales/en-GB' | ||||
| import localeEs from '@angular/common/locales/es' | ||||
| import localeFr from '@angular/common/locales/fr' | ||||
| import localeIt from '@angular/common/locales/it' | ||||
| import localeLb from '@angular/common/locales/lb' | ||||
| import localeNl from '@angular/common/locales/nl' | ||||
| import localePl from '@angular/common/locales/pl' | ||||
| import localePt from '@angular/common/locales/pt' | ||||
| import localeSv from '@angular/common/locales/sv' | ||||
| import localeRo from '@angular/common/locales/ro' | ||||
| import localeRu from '@angular/common/locales/ru' | ||||
|  | ||||
| registerLocaleData(localeCs) | ||||
| registerLocaleData(localeDa) | ||||
| @@ -91,8 +94,8 @@ registerLocaleData(localeIt) | ||||
| registerLocaleData(localeLb) | ||||
| registerLocaleData(localeNl) | ||||
| registerLocaleData(localePl) | ||||
| registerLocaleData(localePt, "pt-BR") | ||||
| registerLocaleData(localePt, "pt-PT") | ||||
| registerLocaleData(localePt, 'pt-BR') | ||||
| registerLocaleData(localePt, 'pt-PT') | ||||
| registerLocaleData(localeRo) | ||||
| registerLocaleData(localeRu) | ||||
| registerLocaleData(localeSv) | ||||
| @@ -146,7 +149,7 @@ registerLocaleData(localeSv) | ||||
|     CustomDatePipe, | ||||
|     DateComponent, | ||||
|     ColorComponent, | ||||
|     DocumentAsnComponent | ||||
|     DocumentAsnComponent, | ||||
|   ], | ||||
|   imports: [ | ||||
|     BrowserModule, | ||||
| @@ -159,24 +162,26 @@ registerLocaleData(localeSv) | ||||
|     InfiniteScrollModule, | ||||
|     PdfViewerModule, | ||||
|     NgSelectModule, | ||||
|     ColorSliderModule | ||||
|     ColorSliderModule, | ||||
|   ], | ||||
|   providers: [ | ||||
|     DatePipe, | ||||
|     CookieService, { | ||||
|     CookieService, | ||||
|     { | ||||
|       provide: HTTP_INTERCEPTORS, | ||||
|       useClass: CsrfInterceptor, | ||||
|       multi: true | ||||
|     },{ | ||||
|       multi: true, | ||||
|     }, | ||||
|     { | ||||
|       provide: HTTP_INTERCEPTORS, | ||||
|       useClass: ApiVersionInterceptor, | ||||
|       multi: true | ||||
|       multi: true, | ||||
|     }, | ||||
|     FilterPipe, | ||||
|     DocumentTitlePipe, | ||||
|     {provide: NgbDateAdapter, useClass: ISODateTimeAdapter}, | ||||
|     {provide: NgbDateParserFormatter, useClass: LocalizedDateParserFormatter} | ||||
|     { provide: NgbDateAdapter, useClass: ISODateTimeAdapter }, | ||||
|     { provide: NgbDateParserFormatter, useClass: LocalizedDateParserFormatter }, | ||||
|   ], | ||||
|   bootstrap: [AppComponent] | ||||
|   bootstrap: [AppComponent], | ||||
| }) | ||||
| export class AppModule { } | ||||
| export class AppModule {} | ||||
|   | ||||
| @@ -1,25 +1,24 @@ | ||||
| import { ComponentFixture, TestBed } from '@angular/core/testing'; | ||||
| import { ComponentFixture, TestBed } from '@angular/core/testing' | ||||
|  | ||||
| import { AppFrameComponent } from './app-frame.component'; | ||||
| import { AppFrameComponent } from './app-frame.component' | ||||
|  | ||||
| describe('AppFrameComponent', () => { | ||||
|   let component: AppFrameComponent; | ||||
|   let fixture: ComponentFixture<AppFrameComponent>; | ||||
|   let component: AppFrameComponent | ||||
|   let fixture: ComponentFixture<AppFrameComponent> | ||||
|  | ||||
|   beforeEach(async () => { | ||||
|     await TestBed.configureTestingModule({ | ||||
|       declarations: [ AppFrameComponent ] | ||||
|     }) | ||||
|     .compileComponents(); | ||||
|   }); | ||||
|       declarations: [AppFrameComponent], | ||||
|     }).compileComponents() | ||||
|   }) | ||||
|  | ||||
|   beforeEach(() => { | ||||
|     fixture = TestBed.createComponent(AppFrameComponent); | ||||
|     component = fixture.componentInstance; | ||||
|     fixture.detectChanges(); | ||||
|   }); | ||||
|     fixture = TestBed.createComponent(AppFrameComponent) | ||||
|     component = fixture.componentInstance | ||||
|     fixture.detectChanges() | ||||
|   }) | ||||
|  | ||||
|   it('should create', () => { | ||||
|     expect(component).toBeTruthy(); | ||||
|   }); | ||||
| }); | ||||
|     expect(component).toBeTruthy() | ||||
|   }) | ||||
| }) | ||||
|   | ||||
| @@ -1,26 +1,31 @@ | ||||
| import { Component } from '@angular/core'; | ||||
| import { FormControl } from '@angular/forms'; | ||||
| import { ActivatedRoute, Router, Params } from '@angular/router'; | ||||
| import { from, Observable, Subscription, BehaviorSubject } from 'rxjs'; | ||||
| import { debounceTime, distinctUntilChanged, map, switchMap, first } from 'rxjs/operators'; | ||||
| import { PaperlessDocument } from 'src/app/data/paperless-document'; | ||||
| import { OpenDocumentsService } from 'src/app/services/open-documents.service'; | ||||
| import { SavedViewService } from 'src/app/services/rest/saved-view.service'; | ||||
| import { SearchService } from 'src/app/services/rest/search.service'; | ||||
| import { environment } from 'src/environments/environment'; | ||||
| import { DocumentDetailComponent } from '../document-detail/document-detail.component'; | ||||
| import { Meta } from '@angular/platform-browser'; | ||||
| import { DocumentListViewService } from 'src/app/services/document-list-view.service'; | ||||
| import { FILTER_FULLTEXT_QUERY } from 'src/app/data/filter-rule-type'; | ||||
| import { Component } from '@angular/core' | ||||
| import { FormControl } from '@angular/forms' | ||||
| import { ActivatedRoute, Router, Params } from '@angular/router' | ||||
| import { from, Observable, Subscription, BehaviorSubject } from 'rxjs' | ||||
| import { | ||||
|   debounceTime, | ||||
|   distinctUntilChanged, | ||||
|   map, | ||||
|   switchMap, | ||||
|   first, | ||||
| } from 'rxjs/operators' | ||||
| import { PaperlessDocument } from 'src/app/data/paperless-document' | ||||
| import { OpenDocumentsService } from 'src/app/services/open-documents.service' | ||||
| import { SavedViewService } from 'src/app/services/rest/saved-view.service' | ||||
| import { SearchService } from 'src/app/services/rest/search.service' | ||||
| import { environment } from 'src/environments/environment' | ||||
| import { DocumentDetailComponent } from '../document-detail/document-detail.component' | ||||
| import { Meta } from '@angular/platform-browser' | ||||
| import { DocumentListViewService } from 'src/app/services/document-list-view.service' | ||||
| import { FILTER_FULLTEXT_QUERY } from 'src/app/data/filter-rule-type' | ||||
|  | ||||
| @Component({ | ||||
|   selector: 'app-app-frame', | ||||
|   templateUrl: './app-frame.component.html', | ||||
|   styleUrls: ['./app-frame.component.scss'] | ||||
|   styleUrls: ['./app-frame.component.scss'], | ||||
| }) | ||||
| export class AppFrameComponent { | ||||
|  | ||||
|   constructor ( | ||||
|   constructor( | ||||
|     public router: Router, | ||||
|     private activatedRoute: ActivatedRoute, | ||||
|     private openDocumentsService: OpenDocumentsService, | ||||
| @@ -28,7 +33,7 @@ export class AppFrameComponent { | ||||
|     public savedViewService: SavedViewService, | ||||
|     private list: DocumentListViewService, | ||||
|     private meta: Meta | ||||
|     ) { } | ||||
|   ) {} | ||||
|  | ||||
|   versionString = `${environment.appTitle} ${environment.version}` | ||||
|  | ||||
| @@ -48,14 +53,14 @@ export class AppFrameComponent { | ||||
|     text$.pipe( | ||||
|       debounceTime(200), | ||||
|       distinctUntilChanged(), | ||||
|       map(term => { | ||||
|       map((term) => { | ||||
|         if (term.lastIndexOf(' ') != -1) { | ||||
|           return term.substring(term.lastIndexOf(' ') + 1) | ||||
|         } else { | ||||
|           return term | ||||
|         } | ||||
|       }), | ||||
|       switchMap(term => | ||||
|       switchMap((term) => | ||||
|         term.length < 2 ? from([[]]) : this.searchService.autocomplete(term) | ||||
|       ) | ||||
|     ) | ||||
| @@ -66,49 +71,60 @@ export class AppFrameComponent { | ||||
|     let lastSpaceIndex = currentSearch.lastIndexOf(' ') | ||||
|     if (lastSpaceIndex != -1) { | ||||
|       currentSearch = currentSearch.substring(0, lastSpaceIndex + 1) | ||||
|       currentSearch += event.item + " " | ||||
|       currentSearch += event.item + ' ' | ||||
|     } else { | ||||
|       currentSearch = event.item + " " | ||||
|       currentSearch = event.item + ' ' | ||||
|     } | ||||
|     this.searchField.patchValue(currentSearch) | ||||
|   } | ||||
|  | ||||
|   search() { | ||||
|     this.closeMenu() | ||||
|     this.list.quickFilter([{rule_type: FILTER_FULLTEXT_QUERY, value: this.searchField.value}]) | ||||
|     this.list.quickFilter([ | ||||
|       { rule_type: FILTER_FULLTEXT_QUERY, value: this.searchField.value }, | ||||
|     ]) | ||||
|   } | ||||
|  | ||||
|   closeDocument(d: PaperlessDocument) { | ||||
|     this.openDocumentsService.closeDocument(d).pipe(first()).subscribe(confirmed => { | ||||
|       if (confirmed) { | ||||
|         this.closeMenu() | ||||
|         let route = this.activatedRoute.snapshot | ||||
|         while (route.firstChild) { | ||||
|           route = route.firstChild | ||||
|     this.openDocumentsService | ||||
|       .closeDocument(d) | ||||
|       .pipe(first()) | ||||
|       .subscribe((confirmed) => { | ||||
|         if (confirmed) { | ||||
|           this.closeMenu() | ||||
|           let route = this.activatedRoute.snapshot | ||||
|           while (route.firstChild) { | ||||
|             route = route.firstChild | ||||
|           } | ||||
|           if ( | ||||
|             route.component == DocumentDetailComponent && | ||||
|             route.params['id'] == d.id | ||||
|           ) { | ||||
|             this.router.navigate(['']) | ||||
|           } | ||||
|         } | ||||
|         if (route.component == DocumentDetailComponent && route.params['id'] == d.id) { | ||||
|           this.router.navigate([""]) | ||||
|         } | ||||
|       } | ||||
|     }) | ||||
|       }) | ||||
|   } | ||||
|  | ||||
|   closeAll() { | ||||
|     // user may need to confirm losing unsaved changes | ||||
|     this.openDocumentsService.closeAll().pipe(first()).subscribe(confirmed => { | ||||
|       if (confirmed) { | ||||
|         this.closeMenu() | ||||
|     this.openDocumentsService | ||||
|       .closeAll() | ||||
|       .pipe(first()) | ||||
|       .subscribe((confirmed) => { | ||||
|         if (confirmed) { | ||||
|           this.closeMenu() | ||||
|  | ||||
|         // TODO: is there a better way to do this? | ||||
|         let route = this.activatedRoute | ||||
|         while (route.firstChild) { | ||||
|           route = route.firstChild | ||||
|           // TODO: is there a better way to do this? | ||||
|           let route = this.activatedRoute | ||||
|           while (route.firstChild) { | ||||
|             route = route.firstChild | ||||
|           } | ||||
|           if (route.component === DocumentDetailComponent) { | ||||
|             this.router.navigate(['']) | ||||
|           } | ||||
|         } | ||||
|         if (route.component === DocumentDetailComponent) { | ||||
|           this.router.navigate([""]) | ||||
|         } | ||||
|       } | ||||
|     }) | ||||
|       }) | ||||
|   } | ||||
|  | ||||
|   get displayName() { | ||||
| @@ -123,5 +139,4 @@ export class AppFrameComponent { | ||||
|       return null | ||||
|     } | ||||
|   } | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -1,25 +1,24 @@ | ||||
| import { ComponentFixture, TestBed } from '@angular/core/testing'; | ||||
| import { ComponentFixture, TestBed } from '@angular/core/testing' | ||||
|  | ||||
| import { ConfirmDialogComponent } from './confirm-dialog.component'; | ||||
| import { ConfirmDialogComponent } from './confirm-dialog.component' | ||||
|  | ||||
| describe('ConfirmDialogComponent', () => { | ||||
|   let component: ConfirmDialogComponent; | ||||
|   let fixture: ComponentFixture<ConfirmDialogComponent>; | ||||
|   let component: ConfirmDialogComponent | ||||
|   let fixture: ComponentFixture<ConfirmDialogComponent> | ||||
|  | ||||
|   beforeEach(async () => { | ||||
|     await TestBed.configureTestingModule({ | ||||
|       declarations: [ ConfirmDialogComponent ] | ||||
|     }) | ||||
|     .compileComponents(); | ||||
|   }); | ||||
|       declarations: [ConfirmDialogComponent], | ||||
|     }).compileComponents() | ||||
|   }) | ||||
|  | ||||
|   beforeEach(() => { | ||||
|     fixture = TestBed.createComponent(ConfirmDialogComponent); | ||||
|     component = fixture.componentInstance; | ||||
|     fixture.detectChanges(); | ||||
|   }); | ||||
|     fixture = TestBed.createComponent(ConfirmDialogComponent) | ||||
|     component = fixture.componentInstance | ||||
|     fixture.detectChanges() | ||||
|   }) | ||||
|  | ||||
|   it('should create', () => { | ||||
|     expect(component).toBeTruthy(); | ||||
|   }); | ||||
| }); | ||||
|     expect(component).toBeTruthy() | ||||
|   }) | ||||
| }) | ||||
|   | ||||
| @@ -1,15 +1,14 @@ | ||||
| import { Component, EventEmitter, Input, Output } from '@angular/core'; | ||||
| import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; | ||||
| import { Subject } from 'rxjs'; | ||||
| import { Component, EventEmitter, Input, Output } from '@angular/core' | ||||
| import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap' | ||||
| import { Subject } from 'rxjs' | ||||
|  | ||||
| @Component({ | ||||
|   selector: 'app-confirm-dialog', | ||||
|   templateUrl: './confirm-dialog.component.html', | ||||
|   styleUrls: ['./confirm-dialog.component.scss'] | ||||
|   styleUrls: ['./confirm-dialog.component.scss'], | ||||
| }) | ||||
| export class ConfirmDialogComponent { | ||||
|  | ||||
|   constructor(public activeModal: NgbActiveModal) { } | ||||
|   constructor(public activeModal: NgbActiveModal) {} | ||||
|  | ||||
|   @Output() | ||||
|   public confirmClicked = new EventEmitter() | ||||
| @@ -24,7 +23,7 @@ export class ConfirmDialogComponent { | ||||
|   message | ||||
|  | ||||
|   @Input() | ||||
|   btnClass = "btn-primary" | ||||
|   btnClass = 'btn-primary' | ||||
|  | ||||
|   @Input() | ||||
|   btnCaption = $localize`Confirm` | ||||
|   | ||||
| @@ -1,25 +1,24 @@ | ||||
| import { ComponentFixture, TestBed } from '@angular/core/testing'; | ||||
| import { ComponentFixture, TestBed } from '@angular/core/testing' | ||||
|  | ||||
| import { DateDropdownComponent } from './date-dropdown.component'; | ||||
| import { DateDropdownComponent } from './date-dropdown.component' | ||||
|  | ||||
| describe('DateDropdownComponent', () => { | ||||
|   let component: DateDropdownComponent; | ||||
|   let fixture: ComponentFixture<DateDropdownComponent>; | ||||
|   let component: DateDropdownComponent | ||||
|   let fixture: ComponentFixture<DateDropdownComponent> | ||||
|  | ||||
|   beforeEach(async () => { | ||||
|     await TestBed.configureTestingModule({ | ||||
|       declarations: [ DateDropdownComponent ] | ||||
|     }) | ||||
|     .compileComponents(); | ||||
|   }); | ||||
|       declarations: [DateDropdownComponent], | ||||
|     }).compileComponents() | ||||
|   }) | ||||
|  | ||||
|   beforeEach(() => { | ||||
|     fixture = TestBed.createComponent(DateDropdownComponent); | ||||
|     component = fixture.componentInstance; | ||||
|     fixture.detectChanges(); | ||||
|   }); | ||||
|     fixture = TestBed.createComponent(DateDropdownComponent) | ||||
|     component = fixture.componentInstance | ||||
|     fixture.detectChanges() | ||||
|   }) | ||||
|  | ||||
|   it('should create', () => { | ||||
|     expect(component).toBeTruthy(); | ||||
|   }); | ||||
| }); | ||||
|     expect(component).toBeTruthy() | ||||
|   }) | ||||
| }) | ||||
|   | ||||
| @@ -1,10 +1,17 @@ | ||||
| import { formatDate } from '@angular/common'; | ||||
| import { Component, EventEmitter, Input, Output, OnInit, OnDestroy } from '@angular/core'; | ||||
| import { NgbDateAdapter } from '@ng-bootstrap/ng-bootstrap'; | ||||
| import { Subject, Subscription } from 'rxjs'; | ||||
| import { debounceTime } from 'rxjs/operators'; | ||||
| import { SettingsService } from 'src/app/services/settings.service'; | ||||
| import { ISODateAdapter } from 'src/app/utils/ngb-iso-date-adapter'; | ||||
| import { formatDate } from '@angular/common' | ||||
| import { | ||||
|   Component, | ||||
|   EventEmitter, | ||||
|   Input, | ||||
|   Output, | ||||
|   OnInit, | ||||
|   OnDestroy, | ||||
| } from '@angular/core' | ||||
| import { NgbDateAdapter } from '@ng-bootstrap/ng-bootstrap' | ||||
| import { Subject, Subscription } from 'rxjs' | ||||
| import { debounceTime } from 'rxjs/operators' | ||||
| import { SettingsService } from 'src/app/services/settings.service' | ||||
| import { ISODateAdapter } from 'src/app/utils/ngb-iso-date-adapter' | ||||
|  | ||||
| export interface DateSelection { | ||||
|   before?: string | ||||
| @@ -20,21 +27,18 @@ const LAST_YEAR = 3 | ||||
|   selector: 'app-date-dropdown', | ||||
|   templateUrl: './date-dropdown.component.html', | ||||
|   styleUrls: ['./date-dropdown.component.scss'], | ||||
|   providers: [ | ||||
|     {provide: NgbDateAdapter, useClass: ISODateAdapter}, | ||||
|   ] | ||||
|   providers: [{ provide: NgbDateAdapter, useClass: ISODateAdapter }], | ||||
| }) | ||||
| export class DateDropdownComponent implements OnInit, OnDestroy { | ||||
|  | ||||
|   constructor(settings: SettingsService) { | ||||
|     this.datePlaceHolder = settings.getLocalizedDateInputFormat() | ||||
|   } | ||||
|  | ||||
|   quickFilters = [ | ||||
|     {id: LAST_7_DAYS, name: $localize`Last 7 days`}, | ||||
|     {id: LAST_MONTH, name: $localize`Last month`}, | ||||
|     {id: LAST_3_MONTHS, name: $localize`Last 3 months`}, | ||||
|     {id: LAST_YEAR, name: $localize`Last year`} | ||||
|     { id: LAST_7_DAYS, name: $localize`Last 7 days` }, | ||||
|     { id: LAST_MONTH, name: $localize`Last month` }, | ||||
|     { id: LAST_3_MONTHS, name: $localize`Last 3 months` }, | ||||
|     { id: LAST_YEAR, name: $localize`Last year` }, | ||||
|   ] | ||||
|  | ||||
|   datePlaceHolder: string | ||||
| @@ -62,9 +66,7 @@ export class DateDropdownComponent implements OnInit, OnDestroy { | ||||
|   private sub: Subscription | ||||
|  | ||||
|   ngOnInit() { | ||||
|     this.sub = this.datesSetDebounce$.pipe( | ||||
|       debounceTime(400) | ||||
|     ).subscribe(() => { | ||||
|     this.sub = this.datesSetDebounce$.pipe(debounceTime(400)).subscribe(() => { | ||||
|       this.onChange() | ||||
|     }) | ||||
|   } | ||||
| @@ -81,11 +83,11 @@ export class DateDropdownComponent implements OnInit, OnDestroy { | ||||
|     switch (qf) { | ||||
|       case LAST_7_DAYS: | ||||
|         date.setDate(date.getDate() - 7) | ||||
|         break; | ||||
|         break | ||||
|  | ||||
|       case LAST_MONTH: | ||||
|         date.setMonth(date.getMonth() - 1) | ||||
|         break; | ||||
|         break | ||||
|  | ||||
|       case LAST_3_MONTHS: | ||||
|         date.setMonth(date.getMonth() - 3) | ||||
| @@ -94,20 +96,22 @@ export class DateDropdownComponent implements OnInit, OnDestroy { | ||||
|       case LAST_YEAR: | ||||
|         date.setFullYear(date.getFullYear() - 1) | ||||
|         break | ||||
|  | ||||
|       } | ||||
|     this.dateAfter = formatDate(date, 'yyyy-MM-dd', "en-us", "UTC") | ||||
|     } | ||||
|     this.dateAfter = formatDate(date, 'yyyy-MM-dd', 'en-us', 'UTC') | ||||
|     this.onChange() | ||||
|   } | ||||
|  | ||||
|   onChange() { | ||||
|     this.dateAfterChange.emit(this.dateAfter) | ||||
|     this.dateBeforeChange.emit(this.dateBefore) | ||||
|     this.datesSet.emit({after: this.dateAfter, before: this.dateBefore}) | ||||
|     this.datesSet.emit({ after: this.dateAfter, before: this.dateBefore }) | ||||
|   } | ||||
|  | ||||
|   onChangeDebounce() { | ||||
|     this.datesSetDebounce$.next({after: this.dateAfter, before: this.dateBefore}) | ||||
|     this.datesSetDebounce$.next({ | ||||
|       after: this.dateAfter, | ||||
|       before: this.dateBefore, | ||||
|     }) | ||||
|   } | ||||
|  | ||||
|   clearBefore() { | ||||
|   | ||||
| @@ -1,25 +1,24 @@ | ||||
| import { ComponentFixture, TestBed } from '@angular/core/testing'; | ||||
| import { ComponentFixture, TestBed } from '@angular/core/testing' | ||||
|  | ||||
| import { EditDialogComponent } from './edit-dialog.component'; | ||||
| import { EditDialogComponent } from './edit-dialog.component' | ||||
|  | ||||
| describe('EditDialogComponent', () => { | ||||
|   let component: EditDialogComponent; | ||||
|   let fixture: ComponentFixture<EditDialogComponent>; | ||||
|   let component: EditDialogComponent | ||||
|   let fixture: ComponentFixture<EditDialogComponent> | ||||
|  | ||||
|   beforeEach(async () => { | ||||
|     await TestBed.configureTestingModule({ | ||||
|       declarations: [ EditDialogComponent ] | ||||
|     }) | ||||
|     .compileComponents(); | ||||
|   }); | ||||
|       declarations: [EditDialogComponent], | ||||
|     }).compileComponents() | ||||
|   }) | ||||
|  | ||||
|   beforeEach(() => { | ||||
|     fixture = TestBed.createComponent(EditDialogComponent); | ||||
|     component = fixture.componentInstance; | ||||
|     fixture.detectChanges(); | ||||
|   }); | ||||
|     fixture = TestBed.createComponent(EditDialogComponent) | ||||
|     component = fixture.componentInstance | ||||
|     fixture.detectChanges() | ||||
|   }) | ||||
|  | ||||
|   it('should create', () => { | ||||
|     expect(component).toBeTruthy(); | ||||
|   }); | ||||
| }); | ||||
|     expect(component).toBeTruthy() | ||||
|   }) | ||||
| }) | ||||
|   | ||||
| @@ -1,20 +1,22 @@ | ||||
| import { Directive, EventEmitter, Input, OnInit, Output } from '@angular/core'; | ||||
| import { FormGroup } from '@angular/forms'; | ||||
| import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; | ||||
| import { Observable } from 'rxjs'; | ||||
| import { map } from 'rxjs/operators'; | ||||
| import { MATCHING_ALGORITHMS, MATCH_AUTO } from 'src/app/data/matching-model'; | ||||
| import { ObjectWithId } from 'src/app/data/object-with-id'; | ||||
| import { AbstractPaperlessService } from 'src/app/services/rest/abstract-paperless-service'; | ||||
| import { ToastService } from 'src/app/services/toast.service'; | ||||
| import { Directive, EventEmitter, Input, OnInit, Output } from '@angular/core' | ||||
| import { FormGroup } from '@angular/forms' | ||||
| import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap' | ||||
| import { Observable } from 'rxjs' | ||||
| import { map } from 'rxjs/operators' | ||||
| import { MATCHING_ALGORITHMS, MATCH_AUTO } from 'src/app/data/matching-model' | ||||
| import { ObjectWithId } from 'src/app/data/object-with-id' | ||||
| import { AbstractPaperlessService } from 'src/app/services/rest/abstract-paperless-service' | ||||
| import { ToastService } from 'src/app/services/toast.service' | ||||
|  | ||||
| @Directive() | ||||
| export abstract class EditDialogComponent<T extends ObjectWithId> implements OnInit { | ||||
|  | ||||
| export abstract class EditDialogComponent<T extends ObjectWithId> | ||||
|   implements OnInit | ||||
| { | ||||
|   constructor( | ||||
|     private service: AbstractPaperlessService<T>, | ||||
|     private activeModal: NgbActiveModal, | ||||
|     private toastService: ToastService) { } | ||||
|     private toastService: ToastService | ||||
|   ) {} | ||||
|  | ||||
|   @Input() | ||||
|   dialogMode: string = 'create' | ||||
| @@ -43,7 +45,7 @@ export abstract class EditDialogComponent<T extends ObjectWithId> implements OnI | ||||
|     // wait to enable close button so it doesnt steal focus from input since its the first clickable element in the DOM | ||||
|     setTimeout(() => { | ||||
|       this.closeEnabled = true | ||||
|     }); | ||||
|     }) | ||||
|   } | ||||
|  | ||||
|   getCreateTitle() { | ||||
| @@ -65,7 +67,7 @@ export abstract class EditDialogComponent<T extends ObjectWithId> implements OnI | ||||
|       case 'edit': | ||||
|         return this.getEditTitle() | ||||
|       default: | ||||
|         break; | ||||
|         break | ||||
|     } | ||||
|   } | ||||
|  | ||||
| @@ -78,25 +80,31 @@ export abstract class EditDialogComponent<T extends ObjectWithId> implements OnI | ||||
|   } | ||||
|  | ||||
|   save() { | ||||
|     var newObject = Object.assign(Object.assign({}, this.object), this.objectForm.value) | ||||
|     var newObject = Object.assign( | ||||
|       Object.assign({}, this.object), | ||||
|       this.objectForm.value | ||||
|     ) | ||||
|     var serverResponse: Observable<T> | ||||
|     switch (this.dialogMode) { | ||||
|       case 'create': | ||||
|         serverResponse = this.service.create(newObject) | ||||
|         break; | ||||
|         break | ||||
|       case 'edit': | ||||
|         serverResponse = this.service.update(newObject) | ||||
|       default: | ||||
|         break; | ||||
|         break | ||||
|     } | ||||
|     this.networkActive = true | ||||
|     serverResponse.subscribe(result => { | ||||
|       this.activeModal.close() | ||||
|       this.success.emit(result) | ||||
|     }, error => { | ||||
|       this.error = error.error | ||||
|       this.networkActive = false | ||||
|     }) | ||||
|     serverResponse.subscribe( | ||||
|       (result) => { | ||||
|         this.activeModal.close() | ||||
|         this.success.emit(result) | ||||
|       }, | ||||
|       (error) => { | ||||
|         this.error = error.error | ||||
|         this.networkActive = false | ||||
|       } | ||||
|     ) | ||||
|   } | ||||
|  | ||||
|   cancel() { | ||||
|   | ||||
| @@ -1,25 +1,24 @@ | ||||
| import { ComponentFixture, TestBed } from '@angular/core/testing'; | ||||
| import { ComponentFixture, TestBed } from '@angular/core/testing' | ||||
|  | ||||
| import { FilterableDropodownComponent } from './filterable-dropdown.component'; | ||||
| import { FilterableDropodownComponent } from './filterable-dropdown.component' | ||||
|  | ||||
| describe('FilterableDropodownComponent', () => { | ||||
|   let component: FilterableDropodownComponent; | ||||
|   let fixture: ComponentFixture<FilterableDropodownComponent>; | ||||
|   let component: FilterableDropodownComponent | ||||
|   let fixture: ComponentFixture<FilterableDropodownComponent> | ||||
|  | ||||
|   beforeEach(async () => { | ||||
|     await TestBed.configureTestingModule({ | ||||
|       declarations: [ FilterableDropodownComponent ] | ||||
|     }) | ||||
|     .compileComponents(); | ||||
|   }); | ||||
|       declarations: [FilterableDropodownComponent], | ||||
|     }).compileComponents() | ||||
|   }) | ||||
|  | ||||
|   beforeEach(() => { | ||||
|     fixture = TestBed.createComponent(FilterableDropodownComponent); | ||||
|     component = fixture.componentInstance; | ||||
|     fixture.detectChanges(); | ||||
|   }); | ||||
|     fixture = TestBed.createComponent(FilterableDropodownComponent) | ||||
|     component = fixture.componentInstance | ||||
|     fixture.detectChanges() | ||||
|   }) | ||||
|  | ||||
|   it('should create', () => { | ||||
|     expect(component).toBeTruthy(); | ||||
|   }); | ||||
| }); | ||||
|     expect(component).toBeTruthy() | ||||
|   }) | ||||
| }) | ||||
|   | ||||
| @@ -1,17 +1,23 @@ | ||||
| import { Component, EventEmitter, Input, Output, ElementRef, ViewChild } from '@angular/core'; | ||||
| import { FilterPipe } from  'src/app/pipes/filter.pipe'; | ||||
| import { | ||||
|   Component, | ||||
|   EventEmitter, | ||||
|   Input, | ||||
|   Output, | ||||
|   ElementRef, | ||||
|   ViewChild, | ||||
| } from '@angular/core' | ||||
| import { FilterPipe } from 'src/app/pipes/filter.pipe' | ||||
| import { NgbDropdown } from '@ng-bootstrap/ng-bootstrap' | ||||
| import { ToggleableItemState } from './toggleable-dropdown-button/toggleable-dropdown-button.component'; | ||||
| import { MatchingModel } from 'src/app/data/matching-model'; | ||||
| import { Subject } from 'rxjs'; | ||||
| import { ToggleableItemState } from './toggleable-dropdown-button/toggleable-dropdown-button.component' | ||||
| import { MatchingModel } from 'src/app/data/matching-model' | ||||
| import { Subject } from 'rxjs' | ||||
|  | ||||
| export interface ChangedItems { | ||||
|   itemsToAdd: MatchingModel[], | ||||
|   itemsToAdd: MatchingModel[] | ||||
|   itemsToRemove: MatchingModel[] | ||||
| } | ||||
|  | ||||
| export class FilterableDropdownSelectionModel { | ||||
|  | ||||
|   changed = new Subject<FilterableDropdownSelectionModel>() | ||||
|  | ||||
|   multiple = false | ||||
| @@ -22,14 +28,20 @@ export class FilterableDropdownSelectionModel { | ||||
|  | ||||
|   get itemsSorted(): MatchingModel[] { | ||||
|     // TODO: this is getting called very often | ||||
|     return this.items.sort((a,b) => { | ||||
|     return this.items.sort((a, b) => { | ||||
|       if (a.id == null && b.id != null) { | ||||
|         return -1 | ||||
|       } else if (a.id != null && b.id == null) { | ||||
|         return 1 | ||||
|       } else if (this.getNonTemporary(a.id) == ToggleableItemState.NotSelected && this.getNonTemporary(b.id) != ToggleableItemState.NotSelected) { | ||||
|       } else if ( | ||||
|         this.getNonTemporary(a.id) == ToggleableItemState.NotSelected && | ||||
|         this.getNonTemporary(b.id) != ToggleableItemState.NotSelected | ||||
|       ) { | ||||
|         return 1 | ||||
|       } else if (this.getNonTemporary(a.id) != ToggleableItemState.NotSelected && this.getNonTemporary(b.id) == ToggleableItemState.NotSelected) { | ||||
|       } else if ( | ||||
|         this.getNonTemporary(a.id) != ToggleableItemState.NotSelected && | ||||
|         this.getNonTemporary(b.id) == ToggleableItemState.NotSelected | ||||
|       ) { | ||||
|         return -1 | ||||
|       } else { | ||||
|         return a.name.localeCompare(b.name) | ||||
| @@ -42,11 +54,17 @@ export class FilterableDropdownSelectionModel { | ||||
|   private temporarySelectionStates = new Map<number, ToggleableItemState>() | ||||
|  | ||||
|   getSelectedItems() { | ||||
|     return this.items.filter(i => this.temporarySelectionStates.get(i.id) == ToggleableItemState.Selected) | ||||
|     return this.items.filter( | ||||
|       (i) => | ||||
|         this.temporarySelectionStates.get(i.id) == ToggleableItemState.Selected | ||||
|     ) | ||||
|   } | ||||
|  | ||||
|   getExcludedItems() { | ||||
|     return this.items.filter(i => this.temporarySelectionStates.get(i.id) == ToggleableItemState.Excluded) | ||||
|     return this.items.filter( | ||||
|       (i) => | ||||
|         this.temporarySelectionStates.get(i.id) == ToggleableItemState.Excluded | ||||
|     ) | ||||
|   } | ||||
|  | ||||
|   set(id: number, state: ToggleableItemState, fireEvent = true) { | ||||
| @@ -62,9 +80,16 @@ export class FilterableDropdownSelectionModel { | ||||
|  | ||||
|   toggle(id: number, fireEvent = true) { | ||||
|     let state = this.temporarySelectionStates.get(id) | ||||
|     if (state == null || (state != ToggleableItemState.Selected && state != ToggleableItemState.Excluded)) { | ||||
|     if ( | ||||
|       state == null || | ||||
|       (state != ToggleableItemState.Selected && | ||||
|         state != ToggleableItemState.Excluded) | ||||
|     ) { | ||||
|       this.temporarySelectionStates.set(id, ToggleableItemState.Selected) | ||||
|     } else if (state == ToggleableItemState.Selected || state == ToggleableItemState.Excluded) { | ||||
|     } else if ( | ||||
|       state == ToggleableItemState.Selected || | ||||
|       state == ToggleableItemState.Excluded | ||||
|     ) { | ||||
|       this.temporarySelectionStates.delete(id) | ||||
|     } | ||||
|  | ||||
| @@ -91,7 +116,7 @@ export class FilterableDropdownSelectionModel { | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   exclude(id: number, fireEvent:boolean = true) { | ||||
|   exclude(id: number, fireEvent: boolean = true) { | ||||
|     let state = this.temporarySelectionStates.get(id) | ||||
|     if (state == null || state != ToggleableItemState.Excluded) { | ||||
|       this.temporarySelectionStates.set(id, ToggleableItemState.Excluded) | ||||
| @@ -130,7 +155,9 @@ export class FilterableDropdownSelectionModel { | ||||
|   } | ||||
|  | ||||
|   get(id: number) { | ||||
|     return this.temporarySelectionStates.get(id) || ToggleableItemState.NotSelected | ||||
|     return ( | ||||
|       this.temporarySelectionStates.get(id) || ToggleableItemState.NotSelected | ||||
|     ) | ||||
|   } | ||||
|  | ||||
|   selectionSize() { | ||||
| @@ -150,9 +177,19 @@ export class FilterableDropdownSelectionModel { | ||||
|   } | ||||
|  | ||||
|   isDirty() { | ||||
|     if (!Array.from(this.temporarySelectionStates.keys()).every(id => this.temporarySelectionStates.get(id) == this.selectionStates.get(id))) { | ||||
|     if ( | ||||
|       !Array.from(this.temporarySelectionStates.keys()).every( | ||||
|         (id) => | ||||
|           this.temporarySelectionStates.get(id) == this.selectionStates.get(id) | ||||
|       ) | ||||
|     ) { | ||||
|       return true | ||||
|     } else if (!Array.from(this.selectionStates.keys()).every(id => this.selectionStates.get(id) == this.temporarySelectionStates.get(id))) { | ||||
|     } else if ( | ||||
|       !Array.from(this.selectionStates.keys()).every( | ||||
|         (id) => | ||||
|           this.selectionStates.get(id) == this.temporarySelectionStates.get(id) | ||||
|       ) | ||||
|     ) { | ||||
|       return true | ||||
|     } else if (this.temporaryLogicalOperator !== this._logicalOperator) { | ||||
|       return true | ||||
| @@ -162,7 +199,10 @@ export class FilterableDropdownSelectionModel { | ||||
|   } | ||||
|  | ||||
|   isNoneSelected() { | ||||
|     return this.selectionSize() == 1 && this.get(null) == ToggleableItemState.Selected | ||||
|     return ( | ||||
|       this.selectionSize() == 1 && | ||||
|       this.get(null) == ToggleableItemState.Selected | ||||
|     ) | ||||
|   } | ||||
|  | ||||
|   init(map) { | ||||
| @@ -187,8 +227,17 @@ export class FilterableDropdownSelectionModel { | ||||
|  | ||||
|   diff(): ChangedItems { | ||||
|     return { | ||||
|       itemsToAdd: this.items.filter(item => this.temporarySelectionStates.get(item.id) == ToggleableItemState.Selected && this.selectionStates.get(item.id) != ToggleableItemState.Selected), | ||||
|       itemsToRemove: this.items.filter(item => !this.temporarySelectionStates.has(item.id) && this.selectionStates.has(item.id)), | ||||
|       itemsToAdd: this.items.filter( | ||||
|         (item) => | ||||
|           this.temporarySelectionStates.get(item.id) == | ||||
|             ToggleableItemState.Selected && | ||||
|           this.selectionStates.get(item.id) != ToggleableItemState.Selected | ||||
|       ), | ||||
|       itemsToRemove: this.items.filter( | ||||
|         (item) => | ||||
|           !this.temporarySelectionStates.has(item.id) && | ||||
|           this.selectionStates.has(item.id) | ||||
|       ), | ||||
|     } | ||||
|   } | ||||
| } | ||||
| @@ -196,10 +245,9 @@ export class FilterableDropdownSelectionModel { | ||||
| @Component({ | ||||
|   selector: 'app-filterable-dropdown', | ||||
|   templateUrl: './filterable-dropdown.component.html', | ||||
|   styleUrls: ['./filterable-dropdown.component.scss'] | ||||
|   styleUrls: ['./filterable-dropdown.component.scss'], | ||||
| }) | ||||
| export class FilterableDropdownComponent { | ||||
|  | ||||
|   @ViewChild('listFilterTextInput') listFilterTextInput: ElementRef | ||||
|   @ViewChild('dropdown') dropdown: NgbDropdown | ||||
|  | ||||
| @@ -211,7 +259,7 @@ export class FilterableDropdownComponent { | ||||
|       this._selectionModel.items = Array.from(items) | ||||
|       this._selectionModel.items.unshift({ | ||||
|         name: $localize`:Filter drop down element to filter for documents with no correspondent/type/tag assigned:Not assigned`, | ||||
|         id: null | ||||
|         id: null, | ||||
|       }) | ||||
|     } | ||||
|   } | ||||
| @@ -229,7 +277,7 @@ export class FilterableDropdownComponent { | ||||
|       model.items = this.selectionModel.items | ||||
|       model.multiple = this.selectionModel.multiple | ||||
|     } | ||||
|     model.changed.subscribe(updatedModel => { | ||||
|     model.changed.subscribe((updatedModel) => { | ||||
|       this.selectionModelChange.next(updatedModel) | ||||
|     }) | ||||
|     this._selectionModel = model | ||||
| @@ -255,7 +303,7 @@ export class FilterableDropdownComponent { | ||||
|   title: string | ||||
|  | ||||
|   @Input() | ||||
|   filterPlaceholder: string = "" | ||||
|   filterPlaceholder: string = '' | ||||
|  | ||||
|   @Input() | ||||
|   icon: string | ||||
| @@ -276,14 +324,17 @@ export class FilterableDropdownComponent { | ||||
|   open = new EventEmitter() | ||||
|  | ||||
|   get operatorToggleEnabled(): boolean { | ||||
|     return this.selectionModel.selectionSize() > 1 && this.selectionModel.getExcludedItems().length == 0 | ||||
|     return ( | ||||
|       this.selectionModel.selectionSize() > 1 && | ||||
|       this.selectionModel.getExcludedItems().length == 0 | ||||
|     ) | ||||
|   } | ||||
|  | ||||
|   modelIsDirty: boolean = false | ||||
|  | ||||
|   constructor(private filterPipe: FilterPipe) { | ||||
|     this.selectionModel = new FilterableDropdownSelectionModel() | ||||
|     this.selectionModelChange.subscribe(updatedModel => { | ||||
|     this.selectionModelChange.subscribe((updatedModel) => { | ||||
|       this.modelIsDirty = updatedModel.isDirty() | ||||
|     }) | ||||
|   } | ||||
| @@ -300,7 +351,7 @@ export class FilterableDropdownComponent { | ||||
|   dropdownOpenChange(open: boolean): void { | ||||
|     if (open) { | ||||
|       setTimeout(() => { | ||||
|         this.listFilterTextInput.nativeElement.focus(); | ||||
|         this.listFilterTextInput.nativeElement.focus() | ||||
|       }, 0) | ||||
|       if (this.editing) { | ||||
|         this.selectionModel.reset() | ||||
|   | ||||
| @@ -1,25 +1,24 @@ | ||||
| import { ComponentFixture, TestBed } from '@angular/core/testing'; | ||||
| import { ComponentFixture, TestBed } from '@angular/core/testing' | ||||
|  | ||||
| import { ToggleableDropdownButtonComponent } from './toggleable-dropdown-button.component'; | ||||
| import { ToggleableDropdownButtonComponent } from './toggleable-dropdown-button.component' | ||||
|  | ||||
| describe('ToggleableDropdownButtonComponent', () => { | ||||
|   let component: ToggleableDropdownButtonComponent; | ||||
|   let fixture: ComponentFixture<ToggleableDropdownButtonComponent>; | ||||
|   let component: ToggleableDropdownButtonComponent | ||||
|   let fixture: ComponentFixture<ToggleableDropdownButtonComponent> | ||||
|  | ||||
|   beforeEach(async () => { | ||||
|     await TestBed.configureTestingModule({ | ||||
|       declarations: [ ToggleableDropdownButtonComponent ] | ||||
|     }) | ||||
|     .compileComponents(); | ||||
|   }); | ||||
|       declarations: [ToggleableDropdownButtonComponent], | ||||
|     }).compileComponents() | ||||
|   }) | ||||
|  | ||||
|   beforeEach(() => { | ||||
|     fixture = TestBed.createComponent(ToggleableDropdownButtonComponent); | ||||
|     component = fixture.componentInstance; | ||||
|     fixture.detectChanges(); | ||||
|   }); | ||||
|     fixture = TestBed.createComponent(ToggleableDropdownButtonComponent) | ||||
|     component = fixture.componentInstance | ||||
|     fixture.detectChanges() | ||||
|   }) | ||||
|  | ||||
|   it('should create', () => { | ||||
|     expect(component).toBeTruthy(); | ||||
|   }); | ||||
| }); | ||||
|     expect(component).toBeTruthy() | ||||
|   }) | ||||
| }) | ||||
|   | ||||
| @@ -1,20 +1,19 @@ | ||||
| import { Component, EventEmitter, Input, Output, OnInit } from '@angular/core'; | ||||
| import { MatchingModel } from 'src/app/data/matching-model'; | ||||
| import { Component, EventEmitter, Input, Output, OnInit } from '@angular/core' | ||||
| import { MatchingModel } from 'src/app/data/matching-model' | ||||
|  | ||||
| export enum ToggleableItemState { | ||||
|   NotSelected = 0, | ||||
|   Selected = 1, | ||||
|   PartiallySelected = 2, | ||||
|   Excluded = 3 | ||||
|   Excluded = 3, | ||||
| } | ||||
|  | ||||
| @Component({ | ||||
|   selector: 'app-toggleable-dropdown-button', | ||||
|   templateUrl: './toggleable-dropdown-button.component.html', | ||||
|   styleUrls: ['./toggleable-dropdown-button.component.scss'] | ||||
|   styleUrls: ['./toggleable-dropdown-button.component.scss'], | ||||
| }) | ||||
| export class ToggleableDropdownButtonComponent { | ||||
|  | ||||
|   @Input() | ||||
|   item: MatchingModel | ||||
|  | ||||
|   | ||||
| @@ -1,30 +1,29 @@ | ||||
| import { Directive, ElementRef, Input, OnInit, ViewChild } from '@angular/core'; | ||||
| import { ControlValueAccessor } from '@angular/forms'; | ||||
| import { v4 as uuidv4 } from 'uuid'; | ||||
| import { Directive, ElementRef, Input, OnInit, ViewChild } from '@angular/core' | ||||
| import { ControlValueAccessor } from '@angular/forms' | ||||
| import { v4 as uuidv4 } from 'uuid' | ||||
|  | ||||
| @Directive() | ||||
| export class AbstractInputComponent<T> implements OnInit, ControlValueAccessor { | ||||
|  | ||||
|   @ViewChild("inputField") | ||||
|   @ViewChild('inputField') | ||||
|   inputField: ElementRef | ||||
|  | ||||
|   constructor() { } | ||||
|   constructor() {} | ||||
|  | ||||
|   onChange = (newValue: T) => {}; | ||||
|   onChange = (newValue: T) => {} | ||||
|  | ||||
|   onTouched = () => {}; | ||||
|   onTouched = () => {} | ||||
|  | ||||
|   writeValue(newValue: any): void { | ||||
|     this.value = newValue | ||||
|   } | ||||
|   registerOnChange(fn: any): void { | ||||
|     this.onChange = fn; | ||||
|     this.onChange = fn | ||||
|   } | ||||
|   registerOnTouched(fn: any): void { | ||||
|     this.onTouched = fn; | ||||
|     this.onTouched = fn | ||||
|   } | ||||
|   setDisabledState?(isDisabled: boolean): void { | ||||
|     this.disabled = isDisabled; | ||||
|     this.disabled = isDisabled | ||||
|   } | ||||
|  | ||||
|   focus() { | ||||
| @@ -37,7 +36,7 @@ export class AbstractInputComponent<T> implements OnInit, ControlValueAccessor { | ||||
|   title: string | ||||
|  | ||||
|   @Input() | ||||
|   disabled = false; | ||||
|   disabled = false | ||||
|  | ||||
|   @Input() | ||||
|   error: string | ||||
|   | ||||
| @@ -1,25 +1,24 @@ | ||||
| import { ComponentFixture, TestBed } from '@angular/core/testing'; | ||||
| import { ComponentFixture, TestBed } from '@angular/core/testing' | ||||
|  | ||||
| import { CheckComponent } from './check.component'; | ||||
| import { CheckComponent } from './check.component' | ||||
|  | ||||
| describe('CheckComponent', () => { | ||||
|   let component: CheckComponent; | ||||
|   let fixture: ComponentFixture<CheckComponent>; | ||||
|   let component: CheckComponent | ||||
|   let fixture: ComponentFixture<CheckComponent> | ||||
|  | ||||
|   beforeEach(async () => { | ||||
|     await TestBed.configureTestingModule({ | ||||
|       declarations: [ CheckComponent ] | ||||
|     }) | ||||
|     .compileComponents(); | ||||
|   }); | ||||
|       declarations: [CheckComponent], | ||||
|     }).compileComponents() | ||||
|   }) | ||||
|  | ||||
|   beforeEach(() => { | ||||
|     fixture = TestBed.createComponent(CheckComponent); | ||||
|     component = fixture.componentInstance; | ||||
|     fixture.detectChanges(); | ||||
|   }); | ||||
|     fixture = TestBed.createComponent(CheckComponent) | ||||
|     component = fixture.componentInstance | ||||
|     fixture.detectChanges() | ||||
|   }) | ||||
|  | ||||
|   it('should create', () => { | ||||
|     expect(component).toBeTruthy(); | ||||
|   }); | ||||
| }); | ||||
|     expect(component).toBeTruthy() | ||||
|   }) | ||||
| }) | ||||
|   | ||||
| @@ -1,22 +1,22 @@ | ||||
| import { Component, forwardRef, Input, OnInit } from '@angular/core'; | ||||
| import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; | ||||
| import { v4 as uuidv4 } from 'uuid'; | ||||
| import { AbstractInputComponent } from '../abstract-input'; | ||||
| import { Component, forwardRef, Input, OnInit } from '@angular/core' | ||||
| import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms' | ||||
| import { v4 as uuidv4 } from 'uuid' | ||||
| import { AbstractInputComponent } from '../abstract-input' | ||||
|  | ||||
| @Component({ | ||||
|   providers: [{ | ||||
|     provide: NG_VALUE_ACCESSOR, | ||||
|     useExisting: forwardRef(() => CheckComponent), | ||||
|     multi: true | ||||
|   }], | ||||
|   providers: [ | ||||
|     { | ||||
|       provide: NG_VALUE_ACCESSOR, | ||||
|       useExisting: forwardRef(() => CheckComponent), | ||||
|       multi: true, | ||||
|     }, | ||||
|   ], | ||||
|   selector: 'app-input-check', | ||||
|   templateUrl: './check.component.html', | ||||
|   styleUrls: ['./check.component.scss'] | ||||
|   styleUrls: ['./check.component.scss'], | ||||
| }) | ||||
| export class CheckComponent extends AbstractInputComponent<boolean> { | ||||
|  | ||||
|   constructor() { | ||||
|     super() | ||||
|   } | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -1,25 +1,24 @@ | ||||
| import { ComponentFixture, TestBed } from '@angular/core/testing'; | ||||
| import { ComponentFixture, TestBed } from '@angular/core/testing' | ||||
|  | ||||
| import { ColorComponent } from './color.component'; | ||||
| import { ColorComponent } from './color.component' | ||||
|  | ||||
| describe('ColorComponent', () => { | ||||
|   let component: ColorComponent; | ||||
|   let fixture: ComponentFixture<ColorComponent>; | ||||
|   let component: ColorComponent | ||||
|   let fixture: ComponentFixture<ColorComponent> | ||||
|  | ||||
|   beforeEach(async () => { | ||||
|     await TestBed.configureTestingModule({ | ||||
|       declarations: [ ColorComponent ] | ||||
|     }) | ||||
|     .compileComponents(); | ||||
|   }); | ||||
|       declarations: [ColorComponent], | ||||
|     }).compileComponents() | ||||
|   }) | ||||
|  | ||||
|   beforeEach(() => { | ||||
|     fixture = TestBed.createComponent(ColorComponent); | ||||
|     component = fixture.componentInstance; | ||||
|     fixture.detectChanges(); | ||||
|   }); | ||||
|     fixture = TestBed.createComponent(ColorComponent) | ||||
|     component = fixture.componentInstance | ||||
|     fixture.detectChanges() | ||||
|   }) | ||||
|  | ||||
|   it('should create', () => { | ||||
|     expect(component).toBeTruthy(); | ||||
|   }); | ||||
| }); | ||||
|     expect(component).toBeTruthy() | ||||
|   }) | ||||
| }) | ||||
|   | ||||
| @@ -1,20 +1,21 @@ | ||||
| import { Component, forwardRef } from '@angular/core'; | ||||
| import { NG_VALUE_ACCESSOR } from '@angular/forms'; | ||||
| import { randomColor } from 'src/app/utils/color'; | ||||
| import { AbstractInputComponent } from '../abstract-input'; | ||||
| import { Component, forwardRef } from '@angular/core' | ||||
| import { NG_VALUE_ACCESSOR } from '@angular/forms' | ||||
| import { randomColor } from 'src/app/utils/color' | ||||
| import { AbstractInputComponent } from '../abstract-input' | ||||
|  | ||||
| @Component({ | ||||
|   providers: [{ | ||||
|     provide: NG_VALUE_ACCESSOR, | ||||
|     useExisting: forwardRef(() => ColorComponent), | ||||
|     multi: true | ||||
|   }], | ||||
|   providers: [ | ||||
|     { | ||||
|       provide: NG_VALUE_ACCESSOR, | ||||
|       useExisting: forwardRef(() => ColorComponent), | ||||
|       multi: true, | ||||
|     }, | ||||
|   ], | ||||
|   selector: 'app-input-color', | ||||
|   templateUrl: './color.component.html', | ||||
|   styleUrls: ['./color.component.scss'] | ||||
|   styleUrls: ['./color.component.scss'], | ||||
| }) | ||||
| export class ColorComponent extends AbstractInputComponent<string> { | ||||
|  | ||||
|   constructor() { | ||||
|     super() | ||||
|   } | ||||
|   | ||||
| @@ -1,25 +1,24 @@ | ||||
| import { ComponentFixture, TestBed } from '@angular/core/testing'; | ||||
| import { ComponentFixture, TestBed } from '@angular/core/testing' | ||||
|  | ||||
| import { DateComponent } from './date.component'; | ||||
| import { DateComponent } from './date.component' | ||||
|  | ||||
| describe('DateComponent', () => { | ||||
|   let component: DateComponent; | ||||
|   let fixture: ComponentFixture<DateComponent>; | ||||
|   let component: DateComponent | ||||
|   let fixture: ComponentFixture<DateComponent> | ||||
|  | ||||
|   beforeEach(async () => { | ||||
|     await TestBed.configureTestingModule({ | ||||
|       declarations: [ DateComponent ] | ||||
|     }) | ||||
|     .compileComponents(); | ||||
|   }); | ||||
|       declarations: [DateComponent], | ||||
|     }).compileComponents() | ||||
|   }) | ||||
|  | ||||
|   beforeEach(() => { | ||||
|     fixture = TestBed.createComponent(DateComponent); | ||||
|     component = fixture.componentInstance; | ||||
|     fixture.detectChanges(); | ||||
|   }); | ||||
|     fixture = TestBed.createComponent(DateComponent) | ||||
|     component = fixture.componentInstance | ||||
|     fixture.detectChanges() | ||||
|   }) | ||||
|  | ||||
|   it('should create', () => { | ||||
|     expect(component).toBeTruthy(); | ||||
|   }); | ||||
| }); | ||||
|     expect(component).toBeTruthy() | ||||
|   }) | ||||
| }) | ||||
|   | ||||
| @@ -1,21 +1,24 @@ | ||||
| import { Component, forwardRef, OnInit } from '@angular/core'; | ||||
| import { NG_VALUE_ACCESSOR } from '@angular/forms'; | ||||
| import { SettingsService } from 'src/app/services/settings.service'; | ||||
| import { AbstractInputComponent } from '../abstract-input'; | ||||
|  | ||||
| import { Component, forwardRef, OnInit } from '@angular/core' | ||||
| import { NG_VALUE_ACCESSOR } from '@angular/forms' | ||||
| import { SettingsService } from 'src/app/services/settings.service' | ||||
| import { AbstractInputComponent } from '../abstract-input' | ||||
|  | ||||
| @Component({ | ||||
|   providers: [{ | ||||
|     provide: NG_VALUE_ACCESSOR, | ||||
|     useExisting: forwardRef(() => DateComponent), | ||||
|     multi: true | ||||
|   }], | ||||
|   providers: [ | ||||
|     { | ||||
|       provide: NG_VALUE_ACCESSOR, | ||||
|       useExisting: forwardRef(() => DateComponent), | ||||
|       multi: true, | ||||
|     }, | ||||
|   ], | ||||
|   selector: 'app-input-date', | ||||
|   templateUrl: './date.component.html', | ||||
|   styleUrls: ['./date.component.scss'] | ||||
|   styleUrls: ['./date.component.scss'], | ||||
| }) | ||||
| export class DateComponent extends AbstractInputComponent<string> implements OnInit { | ||||
|  | ||||
| export class DateComponent | ||||
|   extends AbstractInputComponent<string> | ||||
|   implements OnInit | ||||
| { | ||||
|   constructor(private settings: SettingsService) { | ||||
|     super() | ||||
|   } | ||||
|   | ||||
| @@ -1,25 +1,24 @@ | ||||
| import { ComponentFixture, TestBed } from '@angular/core/testing'; | ||||
| import { ComponentFixture, TestBed } from '@angular/core/testing' | ||||
|  | ||||
| import { NumberComponent } from './number.component'; | ||||
| import { NumberComponent } from './number.component' | ||||
|  | ||||
| describe('NumberComponent', () => { | ||||
|   let component: NumberComponent; | ||||
|   let fixture: ComponentFixture<NumberComponent>; | ||||
|   let component: NumberComponent | ||||
|   let fixture: ComponentFixture<NumberComponent> | ||||
|  | ||||
|   beforeEach(async () => { | ||||
|     await TestBed.configureTestingModule({ | ||||
|       declarations: [ NumberComponent ] | ||||
|     }) | ||||
|     .compileComponents(); | ||||
|   }); | ||||
|       declarations: [NumberComponent], | ||||
|     }).compileComponents() | ||||
|   }) | ||||
|  | ||||
|   beforeEach(() => { | ||||
|     fixture = TestBed.createComponent(NumberComponent); | ||||
|     component = fixture.componentInstance; | ||||
|     fixture.detectChanges(); | ||||
|   }); | ||||
|     fixture = TestBed.createComponent(NumberComponent) | ||||
|     component = fixture.componentInstance | ||||
|     fixture.detectChanges() | ||||
|   }) | ||||
|  | ||||
|   it('should create', () => { | ||||
|     expect(component).toBeTruthy(); | ||||
|   }); | ||||
| }); | ||||
|     expect(component).toBeTruthy() | ||||
|   }) | ||||
| }) | ||||
|   | ||||
| @@ -1,21 +1,22 @@ | ||||
| import { Component, forwardRef } from '@angular/core'; | ||||
| import { NG_VALUE_ACCESSOR } from '@angular/forms'; | ||||
| import { FILTER_ASN_ISNULL } from 'src/app/data/filter-rule-type'; | ||||
| import { DocumentService } from 'src/app/services/rest/document.service'; | ||||
| import { AbstractInputComponent } from '../abstract-input'; | ||||
| import { Component, forwardRef } from '@angular/core' | ||||
| import { NG_VALUE_ACCESSOR } from '@angular/forms' | ||||
| import { FILTER_ASN_ISNULL } from 'src/app/data/filter-rule-type' | ||||
| import { DocumentService } from 'src/app/services/rest/document.service' | ||||
| import { AbstractInputComponent } from '../abstract-input' | ||||
|  | ||||
| @Component({ | ||||
|   providers: [{ | ||||
|     provide: NG_VALUE_ACCESSOR, | ||||
|     useExisting: forwardRef(() => NumberComponent), | ||||
|     multi: true | ||||
|   }], | ||||
|   providers: [ | ||||
|     { | ||||
|       provide: NG_VALUE_ACCESSOR, | ||||
|       useExisting: forwardRef(() => NumberComponent), | ||||
|       multi: true, | ||||
|     }, | ||||
|   ], | ||||
|   selector: 'app-input-number', | ||||
|   templateUrl: './number.component.html', | ||||
|   styleUrls: ['./number.component.scss'] | ||||
|   styleUrls: ['./number.component.scss'], | ||||
| }) | ||||
| export class NumberComponent extends AbstractInputComponent<number> { | ||||
|  | ||||
|   constructor(private documentService: DocumentService) { | ||||
|     super() | ||||
|   } | ||||
| @@ -24,16 +25,17 @@ export class NumberComponent extends AbstractInputComponent<number> { | ||||
|     if (this.value) { | ||||
|       return | ||||
|     } | ||||
|     this.documentService.listFiltered(1, 1, "archive_serial_number", true, [{rule_type: FILTER_ASN_ISNULL, value: "false"}]).subscribe( | ||||
|       results => { | ||||
|     this.documentService | ||||
|       .listFiltered(1, 1, 'archive_serial_number', true, [ | ||||
|         { rule_type: FILTER_ASN_ISNULL, value: 'false' }, | ||||
|       ]) | ||||
|       .subscribe((results) => { | ||||
|         if (results.count > 0) { | ||||
|           this.value = results.results[0].archive_serial_number + 1 | ||||
|         } else { | ||||
|           this.value = 1 | ||||
|         } | ||||
|         this.onChange(this.value) | ||||
|       } | ||||
|     ) | ||||
|       }) | ||||
|   } | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -1,25 +1,24 @@ | ||||
| import { ComponentFixture, TestBed } from '@angular/core/testing'; | ||||
| import { ComponentFixture, TestBed } from '@angular/core/testing' | ||||
|  | ||||
| import { SelectComponent } from './select.component'; | ||||
| import { SelectComponent } from './select.component' | ||||
|  | ||||
| describe('SelectComponent', () => { | ||||
|   let component: SelectComponent; | ||||
|   let fixture: ComponentFixture<SelectComponent>; | ||||
|   let component: SelectComponent | ||||
|   let fixture: ComponentFixture<SelectComponent> | ||||
|  | ||||
|   beforeEach(async () => { | ||||
|     await TestBed.configureTestingModule({ | ||||
|       declarations: [ SelectComponent ] | ||||
|     }) | ||||
|     .compileComponents(); | ||||
|   }); | ||||
|       declarations: [SelectComponent], | ||||
|     }).compileComponents() | ||||
|   }) | ||||
|  | ||||
|   beforeEach(() => { | ||||
|     fixture = TestBed.createComponent(SelectComponent); | ||||
|     component = fixture.componentInstance; | ||||
|     fixture.detectChanges(); | ||||
|   }); | ||||
|     fixture = TestBed.createComponent(SelectComponent) | ||||
|     component = fixture.componentInstance | ||||
|     fixture.detectChanges() | ||||
|   }) | ||||
|  | ||||
|   it('should create', () => { | ||||
|     expect(component).toBeTruthy(); | ||||
|   }); | ||||
| }); | ||||
|     expect(component).toBeTruthy() | ||||
|   }) | ||||
| }) | ||||
|   | ||||
| @@ -1,23 +1,30 @@ | ||||
| import { Component, EventEmitter, forwardRef, Input, Output } from '@angular/core'; | ||||
| import { NG_VALUE_ACCESSOR } from '@angular/forms'; | ||||
| import { AbstractInputComponent } from '../abstract-input'; | ||||
| import { | ||||
|   Component, | ||||
|   EventEmitter, | ||||
|   forwardRef, | ||||
|   Input, | ||||
|   Output, | ||||
| } from '@angular/core' | ||||
| import { NG_VALUE_ACCESSOR } from '@angular/forms' | ||||
| import { AbstractInputComponent } from '../abstract-input' | ||||
|  | ||||
| @Component({ | ||||
|   providers: [{ | ||||
|     provide: NG_VALUE_ACCESSOR, | ||||
|     useExisting: forwardRef(() => SelectComponent), | ||||
|     multi: true | ||||
|   }], | ||||
|   providers: [ | ||||
|     { | ||||
|       provide: NG_VALUE_ACCESSOR, | ||||
|       useExisting: forwardRef(() => SelectComponent), | ||||
|       multi: true, | ||||
|     }, | ||||
|   ], | ||||
|   selector: 'app-input-select', | ||||
|   templateUrl: './select.component.html', | ||||
|   styleUrls: ['./select.component.scss'] | ||||
|   styleUrls: ['./select.component.scss'], | ||||
| }) | ||||
| export class SelectComponent extends AbstractInputComponent<number> { | ||||
|  | ||||
|   constructor() { | ||||
|     super() | ||||
|     this.addItemRef = this.addItem.bind(this) | ||||
|    } | ||||
|   } | ||||
|  | ||||
|   @Input() | ||||
|   items: any[] | ||||
| @@ -47,7 +54,9 @@ export class SelectComponent extends AbstractInputComponent<number> { | ||||
|  | ||||
|   getSuggestions() { | ||||
|     if (this.suggestions && this.items) { | ||||
|       return this.suggestions.filter(id => id != this.value).map(id => this.items.find(item => item.id == id)) | ||||
|       return this.suggestions | ||||
|         .filter((id) => id != this.value) | ||||
|         .map((id) => this.items.find((item) => item.id == id)) | ||||
|     } else { | ||||
|       return [] | ||||
|     } | ||||
| @@ -75,7 +84,6 @@ export class SelectComponent extends AbstractInputComponent<number> { | ||||
|   onBlur() { | ||||
|     setTimeout(() => { | ||||
|       this.clearLastSearchTerm() | ||||
|     }, 3000); | ||||
|     }, 3000) | ||||
|   } | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -1,25 +1,24 @@ | ||||
| import { ComponentFixture, TestBed } from '@angular/core/testing'; | ||||
| import { ComponentFixture, TestBed } from '@angular/core/testing' | ||||
|  | ||||
| import { TagsComponent } from './tags.component'; | ||||
| import { TagsComponent } from './tags.component' | ||||
|  | ||||
| describe('TagsComponent', () => { | ||||
|   let component: TagsComponent; | ||||
|   let fixture: ComponentFixture<TagsComponent>; | ||||
|   let component: TagsComponent | ||||
|   let fixture: ComponentFixture<TagsComponent> | ||||
|  | ||||
|   beforeEach(async () => { | ||||
|     await TestBed.configureTestingModule({ | ||||
|       declarations: [ TagsComponent ] | ||||
|     }) | ||||
|     .compileComponents(); | ||||
|   }); | ||||
|       declarations: [TagsComponent], | ||||
|     }).compileComponents() | ||||
|   }) | ||||
|  | ||||
|   beforeEach(() => { | ||||
|     fixture = TestBed.createComponent(TagsComponent); | ||||
|     component = fixture.componentInstance; | ||||
|     fixture.detectChanges(); | ||||
|   }); | ||||
|     fixture = TestBed.createComponent(TagsComponent) | ||||
|     component = fixture.componentInstance | ||||
|     fixture.detectChanges() | ||||
|   }) | ||||
|  | ||||
|   it('should create', () => { | ||||
|     expect(component).toBeTruthy(); | ||||
|   }); | ||||
| }); | ||||
|     expect(component).toBeTruthy() | ||||
|   }) | ||||
| }) | ||||
|   | ||||
| @@ -1,45 +1,46 @@ | ||||
| import { Component, forwardRef, Input, OnInit } from '@angular/core'; | ||||
| import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; | ||||
| import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; | ||||
| import { TagEditDialogComponent } from 'src/app/components/manage/tag-list/tag-edit-dialog/tag-edit-dialog.component'; | ||||
| import { PaperlessTag } from 'src/app/data/paperless-tag'; | ||||
| import { TagService } from 'src/app/services/rest/tag.service'; | ||||
| import { Component, forwardRef, Input, OnInit } from '@angular/core' | ||||
| import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms' | ||||
| import { NgbModal } from '@ng-bootstrap/ng-bootstrap' | ||||
| import { TagEditDialogComponent } from 'src/app/components/manage/tag-list/tag-edit-dialog/tag-edit-dialog.component' | ||||
| import { PaperlessTag } from 'src/app/data/paperless-tag' | ||||
| import { TagService } from 'src/app/services/rest/tag.service' | ||||
|  | ||||
| @Component({ | ||||
|   providers: [{ | ||||
|     provide: NG_VALUE_ACCESSOR, | ||||
|     useExisting: forwardRef(() => TagsComponent), | ||||
|     multi: true | ||||
|   }], | ||||
|   providers: [ | ||||
|     { | ||||
|       provide: NG_VALUE_ACCESSOR, | ||||
|       useExisting: forwardRef(() => TagsComponent), | ||||
|       multi: true, | ||||
|     }, | ||||
|   ], | ||||
|   selector: 'app-input-tags', | ||||
|   templateUrl: './tags.component.html', | ||||
|   styleUrls: ['./tags.component.scss'] | ||||
|   styleUrls: ['./tags.component.scss'], | ||||
| }) | ||||
| export class TagsComponent implements OnInit, ControlValueAccessor { | ||||
|  | ||||
|   constructor(private tagService: TagService, private modalService: NgbModal) { | ||||
|     this.createTagRef = this.createTag.bind(this) | ||||
|   } | ||||
|  | ||||
|   onChange = (newValue: number[]) => {}; | ||||
|   onChange = (newValue: number[]) => {} | ||||
|  | ||||
|   onTouched = () => {}; | ||||
|   onTouched = () => {} | ||||
|  | ||||
|   writeValue(newValue: number[]): void { | ||||
|     this.value = newValue | ||||
|   } | ||||
|   registerOnChange(fn: any): void { | ||||
|     this.onChange = fn; | ||||
|     this.onChange = fn | ||||
|   } | ||||
|   registerOnTouched(fn: any): void { | ||||
|     this.onTouched = fn; | ||||
|     this.onTouched = fn | ||||
|   } | ||||
|   setDisabledState?(isDisabled: boolean): void { | ||||
|     this.disabled = isDisabled; | ||||
|     this.disabled = isDisabled | ||||
|   } | ||||
|  | ||||
|   ngOnInit(): void { | ||||
|     this.tagService.listAll().subscribe(result => { | ||||
|     this.tagService.listAll().subscribe((result) => { | ||||
|       this.tags = result.results | ||||
|     }) | ||||
|   } | ||||
| @@ -63,7 +64,7 @@ export class TagsComponent implements OnInit, ControlValueAccessor { | ||||
|  | ||||
|   getTag(id) { | ||||
|     if (this.tags) { | ||||
|       return this.tags.find(tag => tag.id == id) | ||||
|       return this.tags.find((tag) => tag.id == id) | ||||
|     } else { | ||||
|       return null | ||||
|     } | ||||
| @@ -80,12 +81,15 @@ export class TagsComponent implements OnInit, ControlValueAccessor { | ||||
|   } | ||||
|  | ||||
|   createTag(name: string = null) { | ||||
|     var modal = this.modalService.open(TagEditDialogComponent, {backdrop: 'static'}) | ||||
|     var modal = this.modalService.open(TagEditDialogComponent, { | ||||
|       backdrop: 'static', | ||||
|     }) | ||||
|     modal.componentInstance.dialogMode = 'create' | ||||
|     if (name) modal.componentInstance.object = { name: name } | ||||
|     else if (this._lastSearchTerm) modal.componentInstance.object = { name: this._lastSearchTerm } | ||||
|     modal.componentInstance.success.subscribe(newTag => { | ||||
|       this.tagService.listAll().subscribe(tags => { | ||||
|     else if (this._lastSearchTerm) | ||||
|       modal.componentInstance.object = { name: this._lastSearchTerm } | ||||
|     modal.componentInstance.success.subscribe((newTag) => { | ||||
|       this.tagService.listAll().subscribe((tags) => { | ||||
|         this.tags = tags.results | ||||
|         this.value = [...this.value, newTag.id] | ||||
|         this.onChange(this.value) | ||||
| @@ -95,7 +99,9 @@ export class TagsComponent implements OnInit, ControlValueAccessor { | ||||
|  | ||||
|   getSuggestions() { | ||||
|     if (this.suggestions && this.tags) { | ||||
|       return this.suggestions.filter(id => !this.value.includes(id)).map(id => this.tags.find(tag => tag.id == id)) | ||||
|       return this.suggestions | ||||
|         .filter((id) => !this.value.includes(id)) | ||||
|         .map((id) => this.tags.find((tag) => tag.id == id)) | ||||
|     } else { | ||||
|       return [] | ||||
|     } | ||||
| @@ -117,7 +123,6 @@ export class TagsComponent implements OnInit, ControlValueAccessor { | ||||
|   onBlur() { | ||||
|     setTimeout(() => { | ||||
|       this.clearLastSearchTerm() | ||||
|     }, 3000); | ||||
|     }, 3000) | ||||
|   } | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -1,25 +1,24 @@ | ||||
| import { ComponentFixture, TestBed } from '@angular/core/testing'; | ||||
| import { ComponentFixture, TestBed } from '@angular/core/testing' | ||||
|  | ||||
| import { TextComponent } from './text.component'; | ||||
| import { TextComponent } from './text.component' | ||||
|  | ||||
| describe('TextComponent', () => { | ||||
|   let component: TextComponent; | ||||
|   let fixture: ComponentFixture<TextComponent>; | ||||
|   let component: TextComponent | ||||
|   let fixture: ComponentFixture<TextComponent> | ||||
|  | ||||
|   beforeEach(async () => { | ||||
|     await TestBed.configureTestingModule({ | ||||
|       declarations: [ TextComponent ] | ||||
|     }) | ||||
|     .compileComponents(); | ||||
|   }); | ||||
|       declarations: [TextComponent], | ||||
|     }).compileComponents() | ||||
|   }) | ||||
|  | ||||
|   beforeEach(() => { | ||||
|     fixture = TestBed.createComponent(TextComponent); | ||||
|     component = fixture.componentInstance; | ||||
|     fixture.detectChanges(); | ||||
|   }); | ||||
|     fixture = TestBed.createComponent(TextComponent) | ||||
|     component = fixture.componentInstance | ||||
|     fixture.detectChanges() | ||||
|   }) | ||||
|  | ||||
|   it('should create', () => { | ||||
|     expect(component).toBeTruthy(); | ||||
|   }); | ||||
| }); | ||||
|     expect(component).toBeTruthy() | ||||
|   }) | ||||
| }) | ||||
|   | ||||
| @@ -1,21 +1,21 @@ | ||||
| import { Component, forwardRef } from '@angular/core'; | ||||
| import { NG_VALUE_ACCESSOR } from '@angular/forms'; | ||||
| import { AbstractInputComponent } from '../abstract-input'; | ||||
| import { Component, forwardRef } from '@angular/core' | ||||
| import { NG_VALUE_ACCESSOR } from '@angular/forms' | ||||
| import { AbstractInputComponent } from '../abstract-input' | ||||
|  | ||||
| @Component({ | ||||
|   providers: [{ | ||||
|     provide: NG_VALUE_ACCESSOR, | ||||
|     useExisting: forwardRef(() => TextComponent), | ||||
|     multi: true | ||||
|   }], | ||||
|   providers: [ | ||||
|     { | ||||
|       provide: NG_VALUE_ACCESSOR, | ||||
|       useExisting: forwardRef(() => TextComponent), | ||||
|       multi: true, | ||||
|     }, | ||||
|   ], | ||||
|   selector: 'app-input-text', | ||||
|   templateUrl: './text.component.html', | ||||
|   styleUrls: ['./text.component.scss'] | ||||
|   styleUrls: ['./text.component.scss'], | ||||
| }) | ||||
| export class TextComponent extends AbstractInputComponent<string> { | ||||
|  | ||||
|   constructor() { | ||||
|     super() | ||||
|   } | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -1,25 +1,24 @@ | ||||
| import { ComponentFixture, TestBed } from '@angular/core/testing'; | ||||
| import { ComponentFixture, TestBed } from '@angular/core/testing' | ||||
|  | ||||
| import { PageHeaderComponent } from './page-header.component'; | ||||
| import { PageHeaderComponent } from './page-header.component' | ||||
|  | ||||
| describe('PageHeaderComponent', () => { | ||||
|   let component: PageHeaderComponent; | ||||
|   let fixture: ComponentFixture<PageHeaderComponent>; | ||||
|   let component: PageHeaderComponent | ||||
|   let fixture: ComponentFixture<PageHeaderComponent> | ||||
|  | ||||
|   beforeEach(async () => { | ||||
|     await TestBed.configureTestingModule({ | ||||
|       declarations: [ PageHeaderComponent ] | ||||
|     }) | ||||
|     .compileComponents(); | ||||
|   }); | ||||
|       declarations: [PageHeaderComponent], | ||||
|     }).compileComponents() | ||||
|   }) | ||||
|  | ||||
|   beforeEach(() => { | ||||
|     fixture = TestBed.createComponent(PageHeaderComponent); | ||||
|     component = fixture.componentInstance; | ||||
|     fixture.detectChanges(); | ||||
|   }); | ||||
|     fixture = TestBed.createComponent(PageHeaderComponent) | ||||
|     component = fixture.componentInstance | ||||
|     fixture.detectChanges() | ||||
|   }) | ||||
|  | ||||
|   it('should create', () => { | ||||
|     expect(component).toBeTruthy(); | ||||
|   }); | ||||
| }); | ||||
|     expect(component).toBeTruthy() | ||||
|   }) | ||||
| }) | ||||
|   | ||||
| @@ -1,17 +1,16 @@ | ||||
| import { Component, Input } from '@angular/core'; | ||||
| import { Title } from '@angular/platform-browser'; | ||||
| import { environment } from 'src/environments/environment'; | ||||
| import { Component, Input } from '@angular/core' | ||||
| import { Title } from '@angular/platform-browser' | ||||
| import { environment } from 'src/environments/environment' | ||||
|  | ||||
| @Component({ | ||||
|   selector: 'app-page-header', | ||||
|   templateUrl: './page-header.component.html', | ||||
|   styleUrls: ['./page-header.component.scss'] | ||||
|   styleUrls: ['./page-header.component.scss'], | ||||
| }) | ||||
| export class PageHeaderComponent { | ||||
|   constructor(private titleService: Title) {} | ||||
|  | ||||
|   constructor(private titleService: Title) { } | ||||
|  | ||||
|   _title = "" | ||||
|   _title = '' | ||||
|  | ||||
|   @Input() | ||||
|   set title(title: string) { | ||||
| @@ -24,6 +23,5 @@ export class PageHeaderComponent { | ||||
|   } | ||||
|  | ||||
|   @Input() | ||||
|   subTitle: string = "" | ||||
|  | ||||
|   subTitle: string = '' | ||||
| } | ||||
|   | ||||
| @@ -1,25 +1,24 @@ | ||||
| import { ComponentFixture, TestBed } from '@angular/core/testing'; | ||||
| import { ComponentFixture, TestBed } from '@angular/core/testing' | ||||
|  | ||||
| import { SelectDialogComponent } from './select-dialog.component'; | ||||
| import { SelectDialogComponent } from './select-dialog.component' | ||||
|  | ||||
| describe('SelectDialogComponent', () => { | ||||
|   let component: SelectDialogComponent; | ||||
|   let fixture: ComponentFixture<SelectDialogComponent>; | ||||
|   let component: SelectDialogComponent | ||||
|   let fixture: ComponentFixture<SelectDialogComponent> | ||||
|  | ||||
|   beforeEach(async () => { | ||||
|     await TestBed.configureTestingModule({ | ||||
|       declarations: [ SelectDialogComponent ] | ||||
|     }) | ||||
|     .compileComponents(); | ||||
|   }); | ||||
|       declarations: [SelectDialogComponent], | ||||
|     }).compileComponents() | ||||
|   }) | ||||
|  | ||||
|   beforeEach(() => { | ||||
|     fixture = TestBed.createComponent(SelectDialogComponent); | ||||
|     component = fixture.componentInstance; | ||||
|     fixture.detectChanges(); | ||||
|   }); | ||||
|     fixture = TestBed.createComponent(SelectDialogComponent) | ||||
|     component = fixture.componentInstance | ||||
|     fixture.detectChanges() | ||||
|   }) | ||||
|  | ||||
|   it('should create', () => { | ||||
|     expect(component).toBeTruthy(); | ||||
|   }); | ||||
| }); | ||||
|     expect(component).toBeTruthy() | ||||
|   }) | ||||
| }) | ||||
|   | ||||
| @@ -1,15 +1,14 @@ | ||||
| import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; | ||||
| import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; | ||||
| import { ObjectWithId } from 'src/app/data/object-with-id'; | ||||
| import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core' | ||||
| import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap' | ||||
| import { ObjectWithId } from 'src/app/data/object-with-id' | ||||
|  | ||||
| @Component({ | ||||
|   selector: 'app-select-dialog', | ||||
|   templateUrl: './select-dialog.component.html', | ||||
|   styleUrls: ['./select-dialog.component.scss'] | ||||
|   styleUrls: ['./select-dialog.component.scss'], | ||||
| }) | ||||
|  | ||||
| export class SelectDialogComponent implements OnInit { | ||||
|   constructor(public activeModal: NgbActiveModal) { } | ||||
|   constructor(public activeModal: NgbActiveModal) {} | ||||
|  | ||||
|   @Output() | ||||
|   public selectClicked = new EventEmitter() | ||||
| @@ -25,8 +24,7 @@ export class SelectDialogComponent implements OnInit { | ||||
|  | ||||
|   selected: number | ||||
|  | ||||
|   ngOnInit(): void { | ||||
|   } | ||||
|   ngOnInit(): void {} | ||||
|  | ||||
|   cancelClicked() { | ||||
|     this.activeModal.close() | ||||
|   | ||||
| @@ -1,25 +1,24 @@ | ||||
| import { ComponentFixture, TestBed } from '@angular/core/testing'; | ||||
| import { ComponentFixture, TestBed } from '@angular/core/testing' | ||||
|  | ||||
| import { TagComponent } from './tag.component'; | ||||
| import { TagComponent } from './tag.component' | ||||
|  | ||||
| describe('TagComponent', () => { | ||||
|   let component: TagComponent; | ||||
|   let fixture: ComponentFixture<TagComponent>; | ||||
|   let component: TagComponent | ||||
|   let fixture: ComponentFixture<TagComponent> | ||||
|  | ||||
|   beforeEach(async () => { | ||||
|     await TestBed.configureTestingModule({ | ||||
|       declarations: [ TagComponent ] | ||||
|     }) | ||||
|     .compileComponents(); | ||||
|   }); | ||||
|       declarations: [TagComponent], | ||||
|     }).compileComponents() | ||||
|   }) | ||||
|  | ||||
|   beforeEach(() => { | ||||
|     fixture = TestBed.createComponent(TagComponent); | ||||
|     component = fixture.componentInstance; | ||||
|     fixture.detectChanges(); | ||||
|   }); | ||||
|     fixture = TestBed.createComponent(TagComponent) | ||||
|     component = fixture.componentInstance | ||||
|     fixture.detectChanges() | ||||
|   }) | ||||
|  | ||||
|   it('should create', () => { | ||||
|     expect(component).toBeTruthy(); | ||||
|   }); | ||||
| }); | ||||
|     expect(component).toBeTruthy() | ||||
|   }) | ||||
| }) | ||||
|   | ||||
| @@ -1,25 +1,22 @@ | ||||
| import { Component, Input, OnInit } from '@angular/core'; | ||||
| import { PaperlessTag } from 'src/app/data/paperless-tag'; | ||||
| import { Component, Input, OnInit } from '@angular/core' | ||||
| import { PaperlessTag } from 'src/app/data/paperless-tag' | ||||
|  | ||||
| @Component({ | ||||
|   selector: 'app-tag', | ||||
|   templateUrl: './tag.component.html', | ||||
|   styleUrls: ['./tag.component.scss'] | ||||
|   styleUrls: ['./tag.component.scss'], | ||||
| }) | ||||
| export class TagComponent implements OnInit { | ||||
|  | ||||
|   constructor() { } | ||||
|   constructor() {} | ||||
|  | ||||
|   @Input() | ||||
|   tag: PaperlessTag | ||||
|  | ||||
|   @Input() | ||||
|   linkTitle: string = "" | ||||
|   linkTitle: string = '' | ||||
|  | ||||
|   @Input() | ||||
|   clickable: boolean = false | ||||
|  | ||||
|   ngOnInit(): void { | ||||
|   } | ||||
|  | ||||
|   ngOnInit(): void {} | ||||
| } | ||||
|   | ||||
| @@ -1,25 +1,24 @@ | ||||
| import { ComponentFixture, TestBed } from '@angular/core/testing'; | ||||
| import { ComponentFixture, TestBed } from '@angular/core/testing' | ||||
|  | ||||
| import { ToastsComponent } from './toasts.component'; | ||||
| import { ToastsComponent } from './toasts.component' | ||||
|  | ||||
| describe('ToastsComponent', () => { | ||||
|   let component: ToastsComponent; | ||||
|   let fixture: ComponentFixture<ToastsComponent>; | ||||
|   let component: ToastsComponent | ||||
|   let fixture: ComponentFixture<ToastsComponent> | ||||
|  | ||||
|   beforeEach(async () => { | ||||
|     await TestBed.configureTestingModule({ | ||||
|       declarations: [ ToastsComponent ] | ||||
|     }) | ||||
|     .compileComponents(); | ||||
|   }); | ||||
|       declarations: [ToastsComponent], | ||||
|     }).compileComponents() | ||||
|   }) | ||||
|  | ||||
|   beforeEach(() => { | ||||
|     fixture = TestBed.createComponent(ToastsComponent); | ||||
|     component = fixture.componentInstance; | ||||
|     fixture.detectChanges(); | ||||
|   }); | ||||
|     fixture = TestBed.createComponent(ToastsComponent) | ||||
|     component = fixture.componentInstance | ||||
|     fixture.detectChanges() | ||||
|   }) | ||||
|  | ||||
|   it('should create', () => { | ||||
|     expect(component).toBeTruthy(); | ||||
|   }); | ||||
| }); | ||||
|     expect(component).toBeTruthy() | ||||
|   }) | ||||
| }) | ||||
|   | ||||
| @@ -1,15 +1,14 @@ | ||||
| import { Component, OnDestroy, OnInit } from '@angular/core'; | ||||
| import { Subscription } from 'rxjs'; | ||||
| import { Toast, ToastService } from 'src/app/services/toast.service'; | ||||
| import { Component, OnDestroy, OnInit } from '@angular/core' | ||||
| import { Subscription } from 'rxjs' | ||||
| import { Toast, ToastService } from 'src/app/services/toast.service' | ||||
|  | ||||
| @Component({ | ||||
|   selector: 'app-toasts', | ||||
|   templateUrl: './toasts.component.html', | ||||
|   styleUrls: ['./toasts.component.scss'] | ||||
|   styleUrls: ['./toasts.component.scss'], | ||||
| }) | ||||
| export class ToastsComponent implements OnInit, OnDestroy { | ||||
|  | ||||
|   constructor(private toastService: ToastService) { } | ||||
|   constructor(private toastService: ToastService) {} | ||||
|  | ||||
|   subscription: Subscription | ||||
|  | ||||
| @@ -20,7 +19,8 @@ export class ToastsComponent implements OnInit, OnDestroy { | ||||
|   } | ||||
|  | ||||
|   ngOnInit(): void { | ||||
|     this.subscription = this.toastService.getToasts().subscribe(toasts => this.toasts = toasts) | ||||
|     this.subscription = this.toastService | ||||
|       .getToasts() | ||||
|       .subscribe((toasts) => (this.toasts = toasts)) | ||||
|   } | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -1,25 +1,24 @@ | ||||
| import { ComponentFixture, TestBed } from '@angular/core/testing'; | ||||
| import { ComponentFixture, TestBed } from '@angular/core/testing' | ||||
|  | ||||
| import { DashboardComponent } from './dashboard.component'; | ||||
| import { DashboardComponent } from './dashboard.component' | ||||
|  | ||||
| describe('DashboardComponent', () => { | ||||
|   let component: DashboardComponent; | ||||
|   let fixture: ComponentFixture<DashboardComponent>; | ||||
|   let component: DashboardComponent | ||||
|   let fixture: ComponentFixture<DashboardComponent> | ||||
|  | ||||
|   beforeEach(async () => { | ||||
|     await TestBed.configureTestingModule({ | ||||
|       declarations: [ DashboardComponent ] | ||||
|     }) | ||||
|     .compileComponents(); | ||||
|   }); | ||||
|       declarations: [DashboardComponent], | ||||
|     }).compileComponents() | ||||
|   }) | ||||
|  | ||||
|   beforeEach(() => { | ||||
|     fixture = TestBed.createComponent(DashboardComponent); | ||||
|     component = fixture.componentInstance; | ||||
|     fixture.detectChanges(); | ||||
|   }); | ||||
|     fixture = TestBed.createComponent(DashboardComponent) | ||||
|     component = fixture.componentInstance | ||||
|     fixture.detectChanges() | ||||
|   }) | ||||
|  | ||||
|   it('should create', () => { | ||||
|     expect(component).toBeTruthy(); | ||||
|   }); | ||||
| }); | ||||
|     expect(component).toBeTruthy() | ||||
|   }) | ||||
| }) | ||||
|   | ||||
| @@ -1,20 +1,15 @@ | ||||
| import { Component, OnInit } from '@angular/core'; | ||||
| import { Meta } from '@angular/platform-browser'; | ||||
| import { PaperlessSavedView } from 'src/app/data/paperless-saved-view'; | ||||
| import { SavedViewService } from 'src/app/services/rest/saved-view.service'; | ||||
|  | ||||
| import { Component, OnInit } from '@angular/core' | ||||
| import { Meta } from '@angular/platform-browser' | ||||
| import { PaperlessSavedView } from 'src/app/data/paperless-saved-view' | ||||
| import { SavedViewService } from 'src/app/services/rest/saved-view.service' | ||||
|  | ||||
| @Component({ | ||||
|   selector: 'app-dashboard', | ||||
|   templateUrl: './dashboard.component.html', | ||||
|   styleUrls: ['./dashboard.component.scss'] | ||||
|   styleUrls: ['./dashboard.component.scss'], | ||||
| }) | ||||
| export class DashboardComponent implements OnInit { | ||||
|  | ||||
|   constructor( | ||||
|     private savedViewService: SavedViewService, | ||||
|     private meta: Meta | ||||
|   ) { } | ||||
|   constructor(private savedViewService: SavedViewService, private meta: Meta) {} | ||||
|  | ||||
|   get displayName() { | ||||
|     let tagFullName = this.meta.getTag('name=full_name') | ||||
| @@ -39,9 +34,10 @@ export class DashboardComponent implements OnInit { | ||||
|   savedViews: PaperlessSavedView[] = [] | ||||
|  | ||||
|   ngOnInit(): void { | ||||
|     this.savedViewService.listAll().subscribe(results => { | ||||
|       this.savedViews = results.results.filter(savedView => savedView.show_on_dashboard) | ||||
|     this.savedViewService.listAll().subscribe((results) => { | ||||
|       this.savedViews = results.results.filter( | ||||
|         (savedView) => savedView.show_on_dashboard | ||||
|       ) | ||||
|     }) | ||||
|   } | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -1,25 +1,24 @@ | ||||
| import { ComponentFixture, TestBed } from '@angular/core/testing'; | ||||
| import { ComponentFixture, TestBed } from '@angular/core/testing' | ||||
|  | ||||
| import { SavedViewWidgetComponent } from './saved-view-widget.component'; | ||||
| import { SavedViewWidgetComponent } from './saved-view-widget.component' | ||||
|  | ||||
| describe('SavedViewWidgetComponent', () => { | ||||
|   let component: SavedViewWidgetComponent; | ||||
|   let fixture: ComponentFixture<SavedViewWidgetComponent>; | ||||
|   let component: SavedViewWidgetComponent | ||||
|   let fixture: ComponentFixture<SavedViewWidgetComponent> | ||||
|  | ||||
|   beforeEach(async () => { | ||||
|     await TestBed.configureTestingModule({ | ||||
|       declarations: [ SavedViewWidgetComponent ] | ||||
|     }) | ||||
|     .compileComponents(); | ||||
|   }); | ||||
|       declarations: [SavedViewWidgetComponent], | ||||
|     }).compileComponents() | ||||
|   }) | ||||
|  | ||||
|   beforeEach(() => { | ||||
|     fixture = TestBed.createComponent(SavedViewWidgetComponent); | ||||
|     component = fixture.componentInstance; | ||||
|     fixture.detectChanges(); | ||||
|   }); | ||||
|     fixture = TestBed.createComponent(SavedViewWidgetComponent) | ||||
|     component = fixture.componentInstance | ||||
|     fixture.detectChanges() | ||||
|   }) | ||||
|  | ||||
|   it('should create', () => { | ||||
|     expect(component).toBeTruthy(); | ||||
|   }); | ||||
| }); | ||||
|     expect(component).toBeTruthy() | ||||
|   }) | ||||
| }) | ||||
|   | ||||
| @@ -1,24 +1,24 @@ | ||||
| import { Component, Input, OnDestroy, OnInit } from '@angular/core'; | ||||
| import { Router } from '@angular/router'; | ||||
| import { Subscription } from 'rxjs'; | ||||
| import { PaperlessDocument } from 'src/app/data/paperless-document'; | ||||
| import { PaperlessSavedView } from 'src/app/data/paperless-saved-view'; | ||||
| import { DocumentListViewService } from 'src/app/services/document-list-view.service'; | ||||
| import { ConsumerStatusService } from 'src/app/services/consumer-status.service'; | ||||
| import { DocumentService } from 'src/app/services/rest/document.service'; | ||||
| import { Component, Input, OnDestroy, OnInit } from '@angular/core' | ||||
| import { Router } from '@angular/router' | ||||
| import { Subscription } from 'rxjs' | ||||
| import { PaperlessDocument } from 'src/app/data/paperless-document' | ||||
| import { PaperlessSavedView } from 'src/app/data/paperless-saved-view' | ||||
| import { DocumentListViewService } from 'src/app/services/document-list-view.service' | ||||
| import { ConsumerStatusService } from 'src/app/services/consumer-status.service' | ||||
| import { DocumentService } from 'src/app/services/rest/document.service' | ||||
|  | ||||
| @Component({ | ||||
|   selector: 'app-saved-view-widget', | ||||
|   templateUrl: './saved-view-widget.component.html', | ||||
|   styleUrls: ['./saved-view-widget.component.scss'] | ||||
|   styleUrls: ['./saved-view-widget.component.scss'], | ||||
| }) | ||||
| export class SavedViewWidgetComponent implements OnInit, OnDestroy { | ||||
|  | ||||
|   constructor( | ||||
|     private documentService: DocumentService, | ||||
|     private router: Router, | ||||
|     private list: DocumentListViewService, | ||||
|     private consumerStatusService: ConsumerStatusService) { } | ||||
|     private consumerStatusService: ConsumerStatusService | ||||
|   ) {} | ||||
|  | ||||
|   @Input() | ||||
|   savedView: PaperlessSavedView | ||||
| @@ -29,9 +29,11 @@ export class SavedViewWidgetComponent implements OnInit, OnDestroy { | ||||
|  | ||||
|   ngOnInit(): void { | ||||
|     this.reload() | ||||
|     this.subscription = this.consumerStatusService.onDocumentConsumptionFinished().subscribe(status => { | ||||
|       this.reload() | ||||
|     }) | ||||
|     this.subscription = this.consumerStatusService | ||||
|       .onDocumentConsumptionFinished() | ||||
|       .subscribe((status) => { | ||||
|         this.reload() | ||||
|       }) | ||||
|   } | ||||
|  | ||||
|   ngOnDestroy(): void { | ||||
| @@ -39,9 +41,17 @@ export class SavedViewWidgetComponent implements OnInit, OnDestroy { | ||||
|   } | ||||
|  | ||||
|   reload() { | ||||
|     this.documentService.listFiltered(1,10,this.savedView.sort_field, this.savedView.sort_reverse, this.savedView.filter_rules).subscribe(result => { | ||||
|       this.documents = result.results | ||||
|     }) | ||||
|     this.documentService | ||||
|       .listFiltered( | ||||
|         1, | ||||
|         10, | ||||
|         this.savedView.sort_field, | ||||
|         this.savedView.sort_reverse, | ||||
|         this.savedView.filter_rules | ||||
|       ) | ||||
|       .subscribe((result) => { | ||||
|         this.documents = result.results | ||||
|       }) | ||||
|   } | ||||
|  | ||||
|   showAll() { | ||||
| @@ -49,8 +59,7 @@ export class SavedViewWidgetComponent implements OnInit, OnDestroy { | ||||
|       this.router.navigate(['view', this.savedView.id]) | ||||
|     } else { | ||||
|       this.list.loadSavedView(this.savedView, true) | ||||
|       this.router.navigate(["documents"]) | ||||
|       this.router.navigate(['documents']) | ||||
|     } | ||||
|   } | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -1,25 +1,24 @@ | ||||
| import { ComponentFixture, TestBed } from '@angular/core/testing'; | ||||
| import { ComponentFixture, TestBed } from '@angular/core/testing' | ||||
|  | ||||
| import { StatisticsWidgetComponent } from './statistics-widget.component'; | ||||
| import { StatisticsWidgetComponent } from './statistics-widget.component' | ||||
|  | ||||
| describe('StatisticsWidgetComponent', () => { | ||||
|   let component: StatisticsWidgetComponent; | ||||
|   let fixture: ComponentFixture<StatisticsWidgetComponent>; | ||||
|   let component: StatisticsWidgetComponent | ||||
|   let fixture: ComponentFixture<StatisticsWidgetComponent> | ||||
|  | ||||
|   beforeEach(async () => { | ||||
|     await TestBed.configureTestingModule({ | ||||
|       declarations: [ StatisticsWidgetComponent ] | ||||
|     }) | ||||
|     .compileComponents(); | ||||
|   }); | ||||
|       declarations: [StatisticsWidgetComponent], | ||||
|     }).compileComponents() | ||||
|   }) | ||||
|  | ||||
|   beforeEach(() => { | ||||
|     fixture = TestBed.createComponent(StatisticsWidgetComponent); | ||||
|     component = fixture.componentInstance; | ||||
|     fixture.detectChanges(); | ||||
|   }); | ||||
|     fixture = TestBed.createComponent(StatisticsWidgetComponent) | ||||
|     component = fixture.componentInstance | ||||
|     fixture.detectChanges() | ||||
|   }) | ||||
|  | ||||
|   it('should create', () => { | ||||
|     expect(component).toBeTruthy(); | ||||
|   }); | ||||
| }); | ||||
|     expect(component).toBeTruthy() | ||||
|   }) | ||||
| }) | ||||
|   | ||||
| @@ -1,24 +1,24 @@ | ||||
| import { HttpClient } from '@angular/common/http'; | ||||
| import { Component, OnDestroy, OnInit } from '@angular/core'; | ||||
| import { Observable, Subscription } from 'rxjs'; | ||||
| import { ConsumerStatusService } from 'src/app/services/consumer-status.service'; | ||||
| import { environment } from 'src/environments/environment'; | ||||
| import { HttpClient } from '@angular/common/http' | ||||
| import { Component, OnDestroy, OnInit } from '@angular/core' | ||||
| import { Observable, Subscription } from 'rxjs' | ||||
| import { ConsumerStatusService } from 'src/app/services/consumer-status.service' | ||||
| import { environment } from 'src/environments/environment' | ||||
|  | ||||
| export interface Statistics { | ||||
|   documents_total?: number | ||||
|   documents_inbox?: number | ||||
| } | ||||
|  | ||||
|  | ||||
| @Component({ | ||||
|   selector: 'app-statistics-widget', | ||||
|   templateUrl: './statistics-widget.component.html', | ||||
|   styleUrls: ['./statistics-widget.component.scss'] | ||||
|   styleUrls: ['./statistics-widget.component.scss'], | ||||
| }) | ||||
| export class StatisticsWidgetComponent implements OnInit, OnDestroy { | ||||
|  | ||||
|   constructor(private http: HttpClient, | ||||
|     private consumerStatusService: ConsumerStatusService) { } | ||||
|   constructor( | ||||
|     private http: HttpClient, | ||||
|     private consumerStatusService: ConsumerStatusService | ||||
|   ) {} | ||||
|  | ||||
|   statistics: Statistics = {} | ||||
|  | ||||
| @@ -29,20 +29,21 @@ export class StatisticsWidgetComponent implements OnInit, OnDestroy { | ||||
|   } | ||||
|  | ||||
|   reload() { | ||||
|     this.getStatistics().subscribe(statistics => { | ||||
|     this.getStatistics().subscribe((statistics) => { | ||||
|       this.statistics = statistics | ||||
|     }) | ||||
|   } | ||||
|  | ||||
|   ngOnInit(): void { | ||||
|     this.reload() | ||||
|     this.subscription = this.consumerStatusService.onDocumentConsumptionFinished().subscribe(status => { | ||||
|       this.reload() | ||||
|     }) | ||||
|     this.subscription = this.consumerStatusService | ||||
|       .onDocumentConsumptionFinished() | ||||
|       .subscribe((status) => { | ||||
|         this.reload() | ||||
|       }) | ||||
|   } | ||||
|  | ||||
|   ngOnDestroy(): void { | ||||
|     this.subscription.unsubscribe() | ||||
|   } | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -1,25 +1,24 @@ | ||||
| import { ComponentFixture, TestBed } from '@angular/core/testing'; | ||||
| import { ComponentFixture, TestBed } from '@angular/core/testing' | ||||
|  | ||||
| import { UploadFileWidgetComponent } from './upload-file-widget.component'; | ||||
| import { UploadFileWidgetComponent } from './upload-file-widget.component' | ||||
|  | ||||
| describe('UploadFileWidgetComponent', () => { | ||||
|   let component: UploadFileWidgetComponent; | ||||
|   let fixture: ComponentFixture<UploadFileWidgetComponent>; | ||||
|   let component: UploadFileWidgetComponent | ||||
|   let fixture: ComponentFixture<UploadFileWidgetComponent> | ||||
|  | ||||
|   beforeEach(async () => { | ||||
|     await TestBed.configureTestingModule({ | ||||
|       declarations: [ UploadFileWidgetComponent ] | ||||
|     }) | ||||
|     .compileComponents(); | ||||
|   }); | ||||
|       declarations: [UploadFileWidgetComponent], | ||||
|     }).compileComponents() | ||||
|   }) | ||||
|  | ||||
|   beforeEach(() => { | ||||
|     fixture = TestBed.createComponent(UploadFileWidgetComponent); | ||||
|     component = fixture.componentInstance; | ||||
|     fixture.detectChanges(); | ||||
|   }); | ||||
|     fixture = TestBed.createComponent(UploadFileWidgetComponent) | ||||
|     component = fixture.componentInstance | ||||
|     fixture.detectChanges() | ||||
|   }) | ||||
|  | ||||
|   it('should create', () => { | ||||
|     expect(component).toBeTruthy(); | ||||
|   }); | ||||
| }); | ||||
|     expect(component).toBeTruthy() | ||||
|   }) | ||||
| }) | ||||
|   | ||||
| @@ -1,15 +1,19 @@ | ||||
| import { HttpEventType } from '@angular/common/http'; | ||||
| import { Component, OnInit } from '@angular/core'; | ||||
| import { FileSystemFileEntry, NgxFileDropEntry } from 'ngx-file-drop'; | ||||
| import { ConsumerStatusService, FileStatus, FileStatusPhase } from 'src/app/services/consumer-status.service'; | ||||
| import { DocumentService } from 'src/app/services/rest/document.service'; | ||||
| import { HttpEventType } from '@angular/common/http' | ||||
| import { Component, OnInit } from '@angular/core' | ||||
| import { FileSystemFileEntry, NgxFileDropEntry } from 'ngx-file-drop' | ||||
| import { | ||||
|   ConsumerStatusService, | ||||
|   FileStatus, | ||||
|   FileStatusPhase, | ||||
| } from 'src/app/services/consumer-status.service' | ||||
| import { DocumentService } from 'src/app/services/rest/document.service' | ||||
|  | ||||
| const MAX_ALERTS = 5 | ||||
|  | ||||
| @Component({ | ||||
|   selector: 'app-upload-file-widget', | ||||
|   templateUrl: './upload-file-widget.component.html', | ||||
|   styleUrls: ['./upload-file-widget.component.scss'] | ||||
|   styleUrls: ['./upload-file-widget.component.scss'], | ||||
| }) | ||||
| export class UploadFileWidgetComponent implements OnInit { | ||||
|   alertsExpanded = false | ||||
| @@ -17,7 +21,7 @@ export class UploadFileWidgetComponent implements OnInit { | ||||
|   constructor( | ||||
|     private documentService: DocumentService, | ||||
|     private consumerStatusService: ConsumerStatusService | ||||
|   ) { } | ||||
|   ) {} | ||||
|  | ||||
|   getStatus() { | ||||
|     return this.consumerStatusService.getConsumerStatus().slice(0, MAX_ALERTS) | ||||
| @@ -25,7 +29,8 @@ export class UploadFileWidgetComponent implements OnInit { | ||||
|  | ||||
|   getStatusSummary() { | ||||
|     let strings = [] | ||||
|     let countUploadingAndProcessing =  this.consumerStatusService.getConsumerStatusNotCompleted().length | ||||
|     let countUploadingAndProcessing = | ||||
|       this.consumerStatusService.getConsumerStatusNotCompleted().length | ||||
|     let countFailed = this.getStatusFailed().length | ||||
|     let countSuccess = this.getStatusSuccess().length | ||||
|     if (countUploadingAndProcessing > 0) { | ||||
| @@ -37,16 +42,21 @@ export class UploadFileWidgetComponent implements OnInit { | ||||
|     if (countSuccess > 0) { | ||||
|       strings.push($localize`Added: ${countSuccess}`) | ||||
|     } | ||||
|     return strings.join($localize`:this string is used to separate processing, failed and added on the file upload widget:, `) | ||||
|     return strings.join( | ||||
|       $localize`:this string is used to separate processing, failed and added on the file upload widget:, ` | ||||
|     ) | ||||
|   } | ||||
|  | ||||
|   getStatusHidden() { | ||||
|     if (this.consumerStatusService.getConsumerStatus().length < MAX_ALERTS) return [] | ||||
|     if (this.consumerStatusService.getConsumerStatus().length < MAX_ALERTS) | ||||
|       return [] | ||||
|     else return this.consumerStatusService.getConsumerStatus().slice(MAX_ALERTS) | ||||
|   } | ||||
|  | ||||
|   getStatusUploading() { | ||||
|     return this.consumerStatusService.getConsumerStatus(FileStatusPhase.UPLOADING) | ||||
|     return this.consumerStatusService.getConsumerStatus( | ||||
|       FileStatusPhase.UPLOADING | ||||
|     ) | ||||
|   } | ||||
|  | ||||
|   getStatusFailed() { | ||||
| @@ -64,7 +74,7 @@ export class UploadFileWidgetComponent implements OnInit { | ||||
|     let current = 0 | ||||
|     let max = 0 | ||||
|  | ||||
|     this.getStatusUploading().forEach(status => { | ||||
|     this.getStatusUploading().forEach((status) => { | ||||
|       current += status.currentPhaseProgress | ||||
|       max += status.currentPhaseMaxProgress | ||||
|     }) | ||||
| @@ -73,18 +83,21 @@ export class UploadFileWidgetComponent implements OnInit { | ||||
|   } | ||||
|  | ||||
|   isFinished(status: FileStatus) { | ||||
|     return status.phase == FileStatusPhase.FAILED || status.phase == FileStatusPhase.SUCCESS | ||||
|     return ( | ||||
|       status.phase == FileStatusPhase.FAILED || | ||||
|       status.phase == FileStatusPhase.SUCCESS | ||||
|     ) | ||||
|   } | ||||
|  | ||||
|   getStatusColor(status: FileStatus) { | ||||
|     switch (status.phase) { | ||||
|       case FileStatusPhase.PROCESSING: | ||||
|       case FileStatusPhase.UPLOADING: | ||||
|           return "primary" | ||||
|         return 'primary' | ||||
|       case FileStatusPhase.FAILED: | ||||
|         return "danger" | ||||
|         return 'danger' | ||||
|       case FileStatusPhase.SUCCESS: | ||||
|         return "success" | ||||
|         return 'success' | ||||
|     } | ||||
|   } | ||||
|  | ||||
| @@ -96,20 +109,16 @@ export class UploadFileWidgetComponent implements OnInit { | ||||
|     this.consumerStatusService.dismissCompleted() | ||||
|   } | ||||
|  | ||||
|   ngOnInit(): void { | ||||
|   } | ||||
|   ngOnInit(): void {} | ||||
|  | ||||
|   public fileOver(event){ | ||||
|   } | ||||
|   public fileOver(event) {} | ||||
|  | ||||
|   public fileLeave(event){ | ||||
|   } | ||||
|   public fileLeave(event) {} | ||||
|  | ||||
|   public dropped(files: NgxFileDropEntry[]) { | ||||
|     for (const droppedFile of files) { | ||||
|       if (droppedFile.fileEntry.isFile) { | ||||
|  | ||||
|       const fileEntry = droppedFile.fileEntry as FileSystemFileEntry; | ||||
|         const fileEntry = droppedFile.fileEntry as FileSystemFileEntry | ||||
|         fileEntry.file((file: File) => { | ||||
|           let formData = new FormData() | ||||
|           formData.append('document', file, file.name) | ||||
| @@ -117,29 +126,37 @@ export class UploadFileWidgetComponent implements OnInit { | ||||
|  | ||||
|           status.message = $localize`Connecting...` | ||||
|  | ||||
|           this.documentService.uploadDocument(formData).subscribe(event => { | ||||
|             if (event.type == HttpEventType.UploadProgress) { | ||||
|               status.updateProgress(FileStatusPhase.UPLOADING, event.loaded, event.total) | ||||
|               status.message = $localize`Uploading...` | ||||
|             } else if (event.type == HttpEventType.Response) { | ||||
|               status.taskId = event.body["task_id"] | ||||
|               status.message = $localize`Upload complete, waiting...` | ||||
|             } | ||||
|  | ||||
|           }, error => { | ||||
|             switch (error.status) { | ||||
|               case 400: { | ||||
|                 this.consumerStatusService.fail(status, error.error.document) | ||||
|                 break; | ||||
|           this.documentService.uploadDocument(formData).subscribe( | ||||
|             (event) => { | ||||
|               if (event.type == HttpEventType.UploadProgress) { | ||||
|                 status.updateProgress( | ||||
|                   FileStatusPhase.UPLOADING, | ||||
|                   event.loaded, | ||||
|                   event.total | ||||
|                 ) | ||||
|                 status.message = $localize`Uploading...` | ||||
|               } else if (event.type == HttpEventType.Response) { | ||||
|                 status.taskId = event.body['task_id'] | ||||
|                 status.message = $localize`Upload complete, waiting...` | ||||
|               } | ||||
|               default: { | ||||
|                 this.consumerStatusService.fail(status, $localize`HTTP error: ${error.status} ${error.statusText}`) | ||||
|                 break; | ||||
|             }, | ||||
|             (error) => { | ||||
|               switch (error.status) { | ||||
|                 case 400: { | ||||
|                   this.consumerStatusService.fail(status, error.error.document) | ||||
|                   break | ||||
|                 } | ||||
|                 default: { | ||||
|                   this.consumerStatusService.fail( | ||||
|                     status, | ||||
|                     $localize`HTTP error: ${error.status} ${error.statusText}` | ||||
|                   ) | ||||
|                   break | ||||
|                 } | ||||
|               } | ||||
|             } | ||||
|  | ||||
|           }) | ||||
|         }); | ||||
|           ) | ||||
|         }) | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|   | ||||
| @@ -1,25 +1,24 @@ | ||||
| import { ComponentFixture, TestBed } from '@angular/core/testing'; | ||||
| import { ComponentFixture, TestBed } from '@angular/core/testing' | ||||
|  | ||||
| import { WelcomeWidgetComponent } from './welcome-widget.component'; | ||||
| import { WelcomeWidgetComponent } from './welcome-widget.component' | ||||
|  | ||||
| describe('WelcomeWidgetComponent', () => { | ||||
|   let component: WelcomeWidgetComponent; | ||||
|   let fixture: ComponentFixture<WelcomeWidgetComponent>; | ||||
|   let component: WelcomeWidgetComponent | ||||
|   let fixture: ComponentFixture<WelcomeWidgetComponent> | ||||
|  | ||||
|   beforeEach(async () => { | ||||
|     await TestBed.configureTestingModule({ | ||||
|       declarations: [ WelcomeWidgetComponent ] | ||||
|     }) | ||||
|     .compileComponents(); | ||||
|   }); | ||||
|       declarations: [WelcomeWidgetComponent], | ||||
|     }).compileComponents() | ||||
|   }) | ||||
|  | ||||
|   beforeEach(() => { | ||||
|     fixture = TestBed.createComponent(WelcomeWidgetComponent); | ||||
|     component = fixture.componentInstance; | ||||
|     fixture.detectChanges(); | ||||
|   }); | ||||
|     fixture = TestBed.createComponent(WelcomeWidgetComponent) | ||||
|     component = fixture.componentInstance | ||||
|     fixture.detectChanges() | ||||
|   }) | ||||
|  | ||||
|   it('should create', () => { | ||||
|     expect(component).toBeTruthy(); | ||||
|   }); | ||||
| }); | ||||
|     expect(component).toBeTruthy() | ||||
|   }) | ||||
| }) | ||||
|   | ||||
| @@ -1,15 +1,12 @@ | ||||
| import { Component, OnInit } from '@angular/core'; | ||||
| import { Component, OnInit } from '@angular/core' | ||||
|  | ||||
| @Component({ | ||||
|   selector: 'app-welcome-widget', | ||||
|   templateUrl: './welcome-widget.component.html', | ||||
|   styleUrls: ['./welcome-widget.component.scss'] | ||||
|   styleUrls: ['./welcome-widget.component.scss'], | ||||
| }) | ||||
| export class WelcomeWidgetComponent implements OnInit { | ||||
|   constructor() {} | ||||
|  | ||||
|   constructor() { } | ||||
|  | ||||
|   ngOnInit(): void { | ||||
|   } | ||||
|  | ||||
|   ngOnInit(): void {} | ||||
| } | ||||
|   | ||||
| @@ -1,25 +1,24 @@ | ||||
| import { ComponentFixture, TestBed } from '@angular/core/testing'; | ||||
| import { ComponentFixture, TestBed } from '@angular/core/testing' | ||||
|  | ||||
| import { WidgetFrameComponent } from './widget-frame.component'; | ||||
| import { WidgetFrameComponent } from './widget-frame.component' | ||||
|  | ||||
| describe('WidgetFrameComponent', () => { | ||||
|   let component: WidgetFrameComponent; | ||||
|   let fixture: ComponentFixture<WidgetFrameComponent>; | ||||
|   let component: WidgetFrameComponent | ||||
|   let fixture: ComponentFixture<WidgetFrameComponent> | ||||
|  | ||||
|   beforeEach(async () => { | ||||
|     await TestBed.configureTestingModule({ | ||||
|       declarations: [ WidgetFrameComponent ] | ||||
|     }) | ||||
|     .compileComponents(); | ||||
|   }); | ||||
|       declarations: [WidgetFrameComponent], | ||||
|     }).compileComponents() | ||||
|   }) | ||||
|  | ||||
|   beforeEach(() => { | ||||
|     fixture = TestBed.createComponent(WidgetFrameComponent); | ||||
|     component = fixture.componentInstance; | ||||
|     fixture.detectChanges(); | ||||
|   }); | ||||
|     fixture = TestBed.createComponent(WidgetFrameComponent) | ||||
|     component = fixture.componentInstance | ||||
|     fixture.detectChanges() | ||||
|   }) | ||||
|  | ||||
|   it('should create', () => { | ||||
|     expect(component).toBeTruthy(); | ||||
|   }); | ||||
| }); | ||||
|     expect(component).toBeTruthy() | ||||
|   }) | ||||
| }) | ||||
|   | ||||
| @@ -1,18 +1,15 @@ | ||||
| import { Component, Input, OnInit } from '@angular/core'; | ||||
| import { Component, Input, OnInit } from '@angular/core' | ||||
|  | ||||
| @Component({ | ||||
|   selector: 'app-widget-frame', | ||||
|   templateUrl: './widget-frame.component.html', | ||||
|   styleUrls: ['./widget-frame.component.scss'] | ||||
|   styleUrls: ['./widget-frame.component.scss'], | ||||
| }) | ||||
| export class WidgetFrameComponent implements OnInit { | ||||
|  | ||||
|   constructor() { } | ||||
|   constructor() {} | ||||
|  | ||||
|   @Input() | ||||
|   title: string | ||||
|  | ||||
|   ngOnInit(): void { | ||||
|   } | ||||
|  | ||||
|   ngOnInit(): void {} | ||||
| } | ||||
|   | ||||
| @@ -1,25 +1,24 @@ | ||||
| import { ComponentFixture, TestBed } from '@angular/core/testing'; | ||||
| import { ComponentFixture, TestBed } from '@angular/core/testing' | ||||
|  | ||||
| import { DocumentAsnComponent } from './document-asn.component'; | ||||
| import { DocumentAsnComponent } from './document-asn.component' | ||||
|  | ||||
| describe('DocumentASNComponentComponent', () => { | ||||
|   let component: DocumentAsnComponent; | ||||
|   let fixture: ComponentFixture<DocumentAsnComponent>; | ||||
|   let component: DocumentAsnComponent | ||||
|   let fixture: ComponentFixture<DocumentAsnComponent> | ||||
|  | ||||
|   beforeEach(async () => { | ||||
|     await TestBed.configureTestingModule({ | ||||
|       declarations: [ DocumentAsnComponent ] | ||||
|     }) | ||||
|     .compileComponents(); | ||||
|   }); | ||||
|       declarations: [DocumentAsnComponent], | ||||
|     }).compileComponents() | ||||
|   }) | ||||
|  | ||||
|   beforeEach(() => { | ||||
|     fixture = TestBed.createComponent(DocumentAsnComponent); | ||||
|     component = fixture.componentInstance; | ||||
|     fixture.detectChanges(); | ||||
|   }); | ||||
|     fixture = TestBed.createComponent(DocumentAsnComponent) | ||||
|     component = fixture.componentInstance | ||||
|     fixture.detectChanges() | ||||
|   }) | ||||
|  | ||||
|   it('should create', () => { | ||||
|     expect(component).toBeTruthy(); | ||||
|   }); | ||||
| }); | ||||
|     expect(component).toBeTruthy() | ||||
|   }) | ||||
| }) | ||||
|   | ||||
| @@ -1,34 +1,33 @@ | ||||
| import { Component, OnInit } from '@angular/core'; | ||||
| import {DocumentService} from "../../services/rest/document.service"; | ||||
| import {ActivatedRoute, Router} from "@angular/router"; | ||||
| import {FILTER_ASN} from "../../data/filter-rule-type"; | ||||
| import { Component, OnInit } from '@angular/core' | ||||
| import { DocumentService } from '../../services/rest/document.service' | ||||
| import { ActivatedRoute, Router } from '@angular/router' | ||||
| import { FILTER_ASN } from '../../data/filter-rule-type' | ||||
|  | ||||
| @Component({ | ||||
|   selector: 'app-document-asncomponent', | ||||
|   templateUrl: './document-asn.component.html', | ||||
|   styleUrls: ['./document-asn.component.scss'] | ||||
|   styleUrls: ['./document-asn.component.scss'], | ||||
| }) | ||||
| export class DocumentAsnComponent implements OnInit { | ||||
|  | ||||
|   asn: string | ||||
|   constructor( | ||||
|     private documentsService: DocumentService, | ||||
|     private route: ActivatedRoute, | ||||
|     private router: Router) { } | ||||
|  | ||||
|     private router: Router | ||||
|   ) {} | ||||
|  | ||||
|   ngOnInit(): void { | ||||
|  | ||||
|     this.route.paramMap.subscribe(paramMap => { | ||||
|       this.asn = paramMap.get('id'); | ||||
|       this.documentsService.listAllFilteredIds([{rule_type: FILTER_ASN, value: this.asn}]).subscribe(documentId => { | ||||
|         if (documentId.length == 1) { | ||||
|           this.router.navigate(['documents', documentId[0]]) | ||||
|         } else { | ||||
|           this.router.navigate(['404']) | ||||
|         } | ||||
|       }) | ||||
|     this.route.paramMap.subscribe((paramMap) => { | ||||
|       this.asn = paramMap.get('id') | ||||
|       this.documentsService | ||||
|         .listAllFilteredIds([{ rule_type: FILTER_ASN, value: this.asn }]) | ||||
|         .subscribe((documentId) => { | ||||
|           if (documentId.length == 1) { | ||||
|             this.router.navigate(['documents', documentId[0]]) | ||||
|           } else { | ||||
|             this.router.navigate(['404']) | ||||
|           } | ||||
|         }) | ||||
|     }) | ||||
|  | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -1,25 +1,24 @@ | ||||
| import { ComponentFixture, TestBed } from '@angular/core/testing'; | ||||
| import { ComponentFixture, TestBed } from '@angular/core/testing' | ||||
|  | ||||
| import { DocumentDetailComponent } from './document-detail.component'; | ||||
| import { DocumentDetailComponent } from './document-detail.component' | ||||
|  | ||||
| describe('DocumentDetailComponent', () => { | ||||
|   let component: DocumentDetailComponent; | ||||
|   let fixture: ComponentFixture<DocumentDetailComponent>; | ||||
|   let component: DocumentDetailComponent | ||||
|   let fixture: ComponentFixture<DocumentDetailComponent> | ||||
|  | ||||
|   beforeEach(async () => { | ||||
|     await TestBed.configureTestingModule({ | ||||
|       declarations: [ DocumentDetailComponent ] | ||||
|     }) | ||||
|     .compileComponents(); | ||||
|   }); | ||||
|       declarations: [DocumentDetailComponent], | ||||
|     }).compileComponents() | ||||
|   }) | ||||
|  | ||||
|   beforeEach(() => { | ||||
|     fixture = TestBed.createComponent(DocumentDetailComponent); | ||||
|     component = fixture.componentInstance; | ||||
|     fixture.detectChanges(); | ||||
|   }); | ||||
|     fixture = TestBed.createComponent(DocumentDetailComponent) | ||||
|     component = fixture.componentInstance | ||||
|     fixture.detectChanges() | ||||
|   }) | ||||
|  | ||||
|   it('should create', () => { | ||||
|     expect(component).toBeTruthy(); | ||||
|   }); | ||||
| }); | ||||
|     expect(component).toBeTruthy() | ||||
|   }) | ||||
| }) | ||||
|   | ||||
| @@ -1,38 +1,55 @@ | ||||
| import { Component, OnInit, OnDestroy, ViewChild, ElementRef } from '@angular/core'; | ||||
| import { FormControl, FormGroup } from '@angular/forms'; | ||||
| import { ActivatedRoute, Router } from '@angular/router'; | ||||
| import { NgbModal, NgbNav } from '@ng-bootstrap/ng-bootstrap'; | ||||
| import { PaperlessCorrespondent } from 'src/app/data/paperless-correspondent'; | ||||
| import { PaperlessDocument } from 'src/app/data/paperless-document'; | ||||
| import { PaperlessDocumentMetadata } from 'src/app/data/paperless-document-metadata'; | ||||
| import { PaperlessDocumentType } from 'src/app/data/paperless-document-type'; | ||||
| import { DocumentTitlePipe } from 'src/app/pipes/document-title.pipe'; | ||||
| import { DocumentListViewService } from 'src/app/services/document-list-view.service'; | ||||
| import { OpenDocumentsService } from 'src/app/services/open-documents.service'; | ||||
| import { CorrespondentService } from 'src/app/services/rest/correspondent.service'; | ||||
| import { DocumentTypeService } from 'src/app/services/rest/document-type.service'; | ||||
| import { DocumentService } from 'src/app/services/rest/document.service'; | ||||
| import { ConfirmDialogComponent } from '../common/confirm-dialog/confirm-dialog.component'; | ||||
| import { CorrespondentEditDialogComponent } from '../manage/correspondent-list/correspondent-edit-dialog/correspondent-edit-dialog.component'; | ||||
| import { DocumentTypeEditDialogComponent } from '../manage/document-type-list/document-type-edit-dialog/document-type-edit-dialog.component'; | ||||
| import { PDFDocumentProxy } from 'ng2-pdf-viewer'; | ||||
| import { ToastService } from 'src/app/services/toast.service'; | ||||
| import { TextComponent } from '../common/input/text/text.component'; | ||||
| import { SettingsService, SETTINGS_KEYS } from 'src/app/services/settings.service'; | ||||
| import { dirtyCheck, DirtyComponent } from '@ngneat/dirty-check-forms'; | ||||
| import { Observable, Subject, BehaviorSubject } from 'rxjs'; | ||||
| import { first, takeUntil, switchMap, map, debounceTime, distinctUntilChanged } from 'rxjs/operators'; | ||||
| import { PaperlessDocumentSuggestions } from 'src/app/data/paperless-document-suggestions'; | ||||
| import { FILTER_FULLTEXT_MORELIKE } from 'src/app/data/filter-rule-type'; | ||||
| import { | ||||
|   Component, | ||||
|   OnInit, | ||||
|   OnDestroy, | ||||
|   ViewChild, | ||||
|   ElementRef, | ||||
| } from '@angular/core' | ||||
| import { FormControl, FormGroup } from '@angular/forms' | ||||
| import { ActivatedRoute, Router } from '@angular/router' | ||||
| import { NgbModal, NgbNav } from '@ng-bootstrap/ng-bootstrap' | ||||
| import { PaperlessCorrespondent } from 'src/app/data/paperless-correspondent' | ||||
| import { PaperlessDocument } from 'src/app/data/paperless-document' | ||||
| import { PaperlessDocumentMetadata } from 'src/app/data/paperless-document-metadata' | ||||
| import { PaperlessDocumentType } from 'src/app/data/paperless-document-type' | ||||
| import { DocumentTitlePipe } from 'src/app/pipes/document-title.pipe' | ||||
| import { DocumentListViewService } from 'src/app/services/document-list-view.service' | ||||
| import { OpenDocumentsService } from 'src/app/services/open-documents.service' | ||||
| import { CorrespondentService } from 'src/app/services/rest/correspondent.service' | ||||
| import { DocumentTypeService } from 'src/app/services/rest/document-type.service' | ||||
| import { DocumentService } from 'src/app/services/rest/document.service' | ||||
| import { ConfirmDialogComponent } from '../common/confirm-dialog/confirm-dialog.component' | ||||
| import { CorrespondentEditDialogComponent } from '../manage/correspondent-list/correspondent-edit-dialog/correspondent-edit-dialog.component' | ||||
| import { DocumentTypeEditDialogComponent } from '../manage/document-type-list/document-type-edit-dialog/document-type-edit-dialog.component' | ||||
| import { PDFDocumentProxy } from 'ng2-pdf-viewer' | ||||
| import { ToastService } from 'src/app/services/toast.service' | ||||
| import { TextComponent } from '../common/input/text/text.component' | ||||
| import { | ||||
|   SettingsService, | ||||
|   SETTINGS_KEYS, | ||||
| } from 'src/app/services/settings.service' | ||||
| import { dirtyCheck, DirtyComponent } from '@ngneat/dirty-check-forms' | ||||
| import { Observable, Subject, BehaviorSubject } from 'rxjs' | ||||
| import { | ||||
|   first, | ||||
|   takeUntil, | ||||
|   switchMap, | ||||
|   map, | ||||
|   debounceTime, | ||||
|   distinctUntilChanged, | ||||
| } from 'rxjs/operators' | ||||
| import { PaperlessDocumentSuggestions } from 'src/app/data/paperless-document-suggestions' | ||||
| import { FILTER_FULLTEXT_MORELIKE } from 'src/app/data/filter-rule-type' | ||||
|  | ||||
| @Component({ | ||||
|   selector: 'app-document-detail', | ||||
|   templateUrl: './document-detail.component.html', | ||||
|   styleUrls: ['./document-detail.component.scss'] | ||||
|   styleUrls: ['./document-detail.component.scss'], | ||||
| }) | ||||
| export class DocumentDetailComponent implements OnInit, OnDestroy, DirtyComponent { | ||||
|  | ||||
|   @ViewChild("inputTitle") | ||||
| export class DocumentDetailComponent | ||||
|   implements OnInit, OnDestroy, DirtyComponent | ||||
| { | ||||
|   @ViewChild('inputTitle') | ||||
|   titleInput: TextComponent | ||||
|  | ||||
|   expandOriginalMetadata = false | ||||
| @@ -63,7 +80,7 @@ export class DocumentDetailComponent implements OnInit, OnDestroy, DirtyComponen | ||||
|     correspondent: new FormControl(), | ||||
|     document_type: new FormControl(), | ||||
|     archive_serial_number: new FormControl(), | ||||
|     tags: new FormControl([]) | ||||
|     tags: new FormControl([]), | ||||
|   }) | ||||
|  | ||||
|   previewCurrentPage: number = 1 | ||||
| @@ -76,8 +93,13 @@ export class DocumentDetailComponent implements OnInit, OnDestroy, DirtyComponen | ||||
|   @ViewChild('nav') nav: NgbNav | ||||
|   @ViewChild('pdfPreview') set pdfPreview(element) { | ||||
|     // this gets called when compontent added or removed from DOM | ||||
|     if (element && element.nativeElement.offsetParent !== null && this.nav?.activeId == 4) { // its visible | ||||
|       setTimeout(()=> this.nav?.select(1)); | ||||
|     if ( | ||||
|       element && | ||||
|       element.nativeElement.offsetParent !== null && | ||||
|       this.nav?.activeId == 4 | ||||
|     ) { | ||||
|       // its visible | ||||
|       setTimeout(() => this.nav?.select(1)) | ||||
|     } | ||||
|   } | ||||
|  | ||||
| @@ -92,16 +114,19 @@ export class DocumentDetailComponent implements OnInit, OnDestroy, DirtyComponen | ||||
|     private documentListViewService: DocumentListViewService, | ||||
|     private documentTitlePipe: DocumentTitlePipe, | ||||
|     private toastService: ToastService, | ||||
|     private settings: SettingsService) { | ||||
|       this.titleSubject.pipe( | ||||
|     private settings: SettingsService | ||||
|   ) { | ||||
|     this.titleSubject | ||||
|       .pipe( | ||||
|         debounceTime(1000), | ||||
|         distinctUntilChanged(), | ||||
|         takeUntil(this.unsubscribeNotifier) | ||||
|       ).subscribe(titleValue => { | ||||
|       ) | ||||
|       .subscribe((titleValue) => { | ||||
|         this.title = titleValue | ||||
|         this.documentForm.patchValue({'title': titleValue}) | ||||
|         this.documentForm.patchValue({ title: titleValue }) | ||||
|       }) | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   titleKeyUp(event) { | ||||
|     this.titleSubject.next(event.target?.value) | ||||
| @@ -112,180 +137,291 @@ export class DocumentDetailComponent implements OnInit, OnDestroy, DirtyComponen | ||||
|   } | ||||
|  | ||||
|   getContentType() { | ||||
|     return this.metadata?.has_archive_version ? 'application/pdf' : this.metadata?.original_mime_type | ||||
|     return this.metadata?.has_archive_version | ||||
|       ? 'application/pdf' | ||||
|       : this.metadata?.original_mime_type | ||||
|   } | ||||
|  | ||||
|   ngOnInit(): void { | ||||
|     this.documentForm.valueChanges.pipe(takeUntil(this.unsubscribeNotifier)).subscribe(wow => { | ||||
|       Object.assign(this.document, this.documentForm.value) | ||||
|     }) | ||||
|  | ||||
|     this.correspondentService.listAll().pipe(first()).subscribe(result => this.correspondents = result.results) | ||||
|     this.documentTypeService.listAll().pipe(first()).subscribe(result => this.documentTypes = result.results) | ||||
|  | ||||
|     this.route.paramMap.pipe(switchMap(paramMap => { | ||||
|       const documentId = +paramMap.get('id') | ||||
|       return this.documentsService.get(documentId) | ||||
|     })).pipe(switchMap((doc) => { | ||||
|       this.documentId = doc.id | ||||
|       this.previewUrl = this.documentsService.getPreviewUrl(this.documentId) | ||||
|       this.downloadUrl = this.documentsService.getDownloadUrl(this.documentId) | ||||
|       this.downloadOriginalUrl = this.documentsService.getDownloadUrl(this.documentId, true) | ||||
|       this.suggestions = null | ||||
|       if (this.openDocumentService.getOpenDocument(this.documentId)) { | ||||
|         this.updateComponent(this.openDocumentService.getOpenDocument(this.documentId)) | ||||
|       } else { | ||||
|         this.openDocumentService.openDocument(doc) | ||||
|         this.updateComponent(doc) | ||||
|       } | ||||
|  | ||||
|       // Initialize dirtyCheck | ||||
|       this.store = new BehaviorSubject({ | ||||
|         title: doc.title, | ||||
|         content: doc.content, | ||||
|         created: doc.created, | ||||
|         correspondent: doc.correspondent, | ||||
|         document_type: doc.document_type, | ||||
|         archive_serial_number: doc.archive_serial_number, | ||||
|         tags: [...doc.tags] | ||||
|     this.documentForm.valueChanges | ||||
|       .pipe(takeUntil(this.unsubscribeNotifier)) | ||||
|       .subscribe((wow) => { | ||||
|         Object.assign(this.document, this.documentForm.value) | ||||
|       }) | ||||
|  | ||||
|       this.isDirty$ = dirtyCheck(this.documentForm, this.store.asObservable()) | ||||
|     this.correspondentService | ||||
|       .listAll() | ||||
|       .pipe(first()) | ||||
|       .subscribe((result) => (this.correspondents = result.results)) | ||||
|     this.documentTypeService | ||||
|       .listAll() | ||||
|       .pipe(first()) | ||||
|       .subscribe((result) => (this.documentTypes = result.results)) | ||||
|  | ||||
|       return this.isDirty$.pipe(map(dirty => ({doc, dirty}))) | ||||
|     })) | ||||
|     .pipe(takeUntil(this.unsubscribeNotifier)) | ||||
|     .subscribe(({doc, dirty}) => { | ||||
|       this.openDocumentService.setDirty(doc.id, dirty) | ||||
|     }, error => {this.router.navigate(['404'])}) | ||||
|     this.route.paramMap | ||||
|       .pipe( | ||||
|         switchMap((paramMap) => { | ||||
|           const documentId = +paramMap.get('id') | ||||
|           return this.documentsService.get(documentId) | ||||
|         }) | ||||
|       ) | ||||
|       .pipe( | ||||
|         switchMap((doc) => { | ||||
|           this.documentId = doc.id | ||||
|           this.previewUrl = this.documentsService.getPreviewUrl(this.documentId) | ||||
|           this.downloadUrl = this.documentsService.getDownloadUrl( | ||||
|             this.documentId | ||||
|           ) | ||||
|           this.downloadOriginalUrl = this.documentsService.getDownloadUrl( | ||||
|             this.documentId, | ||||
|             true | ||||
|           ) | ||||
|           this.suggestions = null | ||||
|           if (this.openDocumentService.getOpenDocument(this.documentId)) { | ||||
|             this.updateComponent( | ||||
|               this.openDocumentService.getOpenDocument(this.documentId) | ||||
|             ) | ||||
|           } else { | ||||
|             this.openDocumentService.openDocument(doc) | ||||
|             this.updateComponent(doc) | ||||
|           } | ||||
|  | ||||
|           // Initialize dirtyCheck | ||||
|           this.store = new BehaviorSubject({ | ||||
|             title: doc.title, | ||||
|             content: doc.content, | ||||
|             created: doc.created, | ||||
|             correspondent: doc.correspondent, | ||||
|             document_type: doc.document_type, | ||||
|             archive_serial_number: doc.archive_serial_number, | ||||
|             tags: [...doc.tags], | ||||
|           }) | ||||
|  | ||||
|           this.isDirty$ = dirtyCheck( | ||||
|             this.documentForm, | ||||
|             this.store.asObservable() | ||||
|           ) | ||||
|  | ||||
|           return this.isDirty$.pipe(map((dirty) => ({ doc, dirty }))) | ||||
|         }) | ||||
|       ) | ||||
|       .pipe(takeUntil(this.unsubscribeNotifier)) | ||||
|       .subscribe( | ||||
|         ({ doc, dirty }) => { | ||||
|           this.openDocumentService.setDirty(doc.id, dirty) | ||||
|         }, | ||||
|         (error) => { | ||||
|           this.router.navigate(['404']) | ||||
|         } | ||||
|       ) | ||||
|   } | ||||
|  | ||||
|   ngOnDestroy() : void { | ||||
|     this.unsubscribeNotifier.next(); | ||||
|     this.unsubscribeNotifier.complete(); | ||||
|   ngOnDestroy(): void { | ||||
|     this.unsubscribeNotifier.next() | ||||
|     this.unsubscribeNotifier.complete() | ||||
|   } | ||||
|  | ||||
|   updateComponent(doc: PaperlessDocument) { | ||||
|     this.document = doc | ||||
|     this.documentsService.getMetadata(doc.id).pipe(first()).subscribe(result => { | ||||
|       this.metadata = result | ||||
|     }, error => { | ||||
|       this.metadata = null | ||||
|     }) | ||||
|     this.documentsService.getSuggestions(doc.id).pipe(first()).subscribe(result => { | ||||
|       this.suggestions = result | ||||
|     }, error => { | ||||
|       this.suggestions = null | ||||
|     }) | ||||
|     this.documentsService | ||||
|       .getMetadata(doc.id) | ||||
|       .pipe(first()) | ||||
|       .subscribe( | ||||
|         (result) => { | ||||
|           this.metadata = result | ||||
|         }, | ||||
|         (error) => { | ||||
|           this.metadata = null | ||||
|         } | ||||
|       ) | ||||
|     this.documentsService | ||||
|       .getSuggestions(doc.id) | ||||
|       .pipe(first()) | ||||
|       .subscribe( | ||||
|         (result) => { | ||||
|           this.suggestions = result | ||||
|         }, | ||||
|         (error) => { | ||||
|           this.suggestions = null | ||||
|         } | ||||
|       ) | ||||
|     this.title = this.documentTitlePipe.transform(doc.title) | ||||
|     this.documentForm.patchValue(doc) | ||||
|   } | ||||
|  | ||||
|   createDocumentType(newName: string) { | ||||
|     var modal = this.modalService.open(DocumentTypeEditDialogComponent, {backdrop: 'static'}) | ||||
|     var modal = this.modalService.open(DocumentTypeEditDialogComponent, { | ||||
|       backdrop: 'static', | ||||
|     }) | ||||
|     modal.componentInstance.dialogMode = 'create' | ||||
|     if (newName) modal.componentInstance.object = { name: newName } | ||||
|     modal.componentInstance.success.pipe(switchMap(newDocumentType => { | ||||
|       return this.documentTypeService.listAll().pipe(map(documentTypes => ({newDocumentType, documentTypes}))) | ||||
|     })) | ||||
|     .pipe(takeUntil(this.unsubscribeNotifier)) | ||||
|     .subscribe(({newDocumentType, documentTypes}) => { | ||||
|       this.documentTypes = documentTypes.results | ||||
|       this.documentForm.get('document_type').setValue(newDocumentType.id) | ||||
|     }) | ||||
|     modal.componentInstance.success | ||||
|       .pipe( | ||||
|         switchMap((newDocumentType) => { | ||||
|           return this.documentTypeService | ||||
|             .listAll() | ||||
|             .pipe(map((documentTypes) => ({ newDocumentType, documentTypes }))) | ||||
|         }) | ||||
|       ) | ||||
|       .pipe(takeUntil(this.unsubscribeNotifier)) | ||||
|       .subscribe(({ newDocumentType, documentTypes }) => { | ||||
|         this.documentTypes = documentTypes.results | ||||
|         this.documentForm.get('document_type').setValue(newDocumentType.id) | ||||
|       }) | ||||
|   } | ||||
|  | ||||
|   createCorrespondent(newName: string) { | ||||
|     var modal = this.modalService.open(CorrespondentEditDialogComponent, {backdrop: 'static'}) | ||||
|     var modal = this.modalService.open(CorrespondentEditDialogComponent, { | ||||
|       backdrop: 'static', | ||||
|     }) | ||||
|     modal.componentInstance.dialogMode = 'create' | ||||
|     if (newName) modal.componentInstance.object = { name: newName } | ||||
|     modal.componentInstance.success.pipe(switchMap(newCorrespondent => { | ||||
|       return this.correspondentService.listAll().pipe(map(correspondents => ({newCorrespondent, correspondents}))) | ||||
|     })) | ||||
|     .pipe(takeUntil(this.unsubscribeNotifier)) | ||||
|     .subscribe(({newCorrespondent, correspondents}) => { | ||||
|       this.correspondents = correspondents.results | ||||
|       this.documentForm.get('correspondent').setValue(newCorrespondent.id) | ||||
|     }) | ||||
|     modal.componentInstance.success | ||||
|       .pipe( | ||||
|         switchMap((newCorrespondent) => { | ||||
|           return this.correspondentService | ||||
|             .listAll() | ||||
|             .pipe( | ||||
|               map((correspondents) => ({ newCorrespondent, correspondents })) | ||||
|             ) | ||||
|         }) | ||||
|       ) | ||||
|       .pipe(takeUntil(this.unsubscribeNotifier)) | ||||
|       .subscribe(({ newCorrespondent, correspondents }) => { | ||||
|         this.correspondents = correspondents.results | ||||
|         this.documentForm.get('correspondent').setValue(newCorrespondent.id) | ||||
|       }) | ||||
|   } | ||||
|  | ||||
|   discard() { | ||||
|     this.documentsService.get(this.documentId).pipe(first()).subscribe(doc => { | ||||
|       Object.assign(this.document, doc) | ||||
|       this.title = doc.title | ||||
|       this.documentForm.patchValue(doc) | ||||
|     }, error => {this.router.navigate(['404'])}) | ||||
|     this.documentsService | ||||
|       .get(this.documentId) | ||||
|       .pipe(first()) | ||||
|       .subscribe( | ||||
|         (doc) => { | ||||
|           Object.assign(this.document, doc) | ||||
|           this.title = doc.title | ||||
|           this.documentForm.patchValue(doc) | ||||
|         }, | ||||
|         (error) => { | ||||
|           this.router.navigate(['404']) | ||||
|         } | ||||
|       ) | ||||
|   } | ||||
|  | ||||
|   save() { | ||||
|     this.networkActive = true | ||||
|     this.store.next(this.documentForm.value) | ||||
|     this.documentsService.update(this.document).pipe(first()).subscribe(result => { | ||||
|       this.close() | ||||
|       this.networkActive = false | ||||
|       this.error = null | ||||
|     }, error => { | ||||
|       this.networkActive = false | ||||
|       this.error = error.error | ||||
|     }) | ||||
|     this.documentsService | ||||
|       .update(this.document) | ||||
|       .pipe(first()) | ||||
|       .subscribe( | ||||
|         (result) => { | ||||
|           this.close() | ||||
|           this.networkActive = false | ||||
|           this.error = null | ||||
|         }, | ||||
|         (error) => { | ||||
|           this.networkActive = false | ||||
|           this.error = error.error | ||||
|         } | ||||
|       ) | ||||
|   } | ||||
|  | ||||
|   saveEditNext() { | ||||
|     this.networkActive = true | ||||
|     this.store.next(this.documentForm.value) | ||||
|     this.documentsService.update(this.document).pipe(switchMap(updateResult => { | ||||
|       return this.documentListViewService.getNext(this.documentId).pipe(map(nextDocId => ({nextDocId, updateResult}))) | ||||
|     })).pipe(switchMap(({nextDocId, updateResult}) => { | ||||
|       if (nextDocId && updateResult) return this.openDocumentService.closeDocument(this.document).pipe(map(closeResult => ({updateResult, nextDocId, closeResult}))) | ||||
|     })) | ||||
|     .pipe(first()) | ||||
|     .subscribe(({updateResult, nextDocId, closeResult}) => { | ||||
|       this.error = null | ||||
|       this.networkActive = false | ||||
|       if (closeResult && updateResult && nextDocId) { | ||||
|         this.router.navigate(['documents', nextDocId]) | ||||
|         this.titleInput?.focus() | ||||
|       } | ||||
|     }, error => { | ||||
|       this.networkActive = false | ||||
|       this.error = error.error | ||||
|     }) | ||||
|     this.documentsService | ||||
|       .update(this.document) | ||||
|       .pipe( | ||||
|         switchMap((updateResult) => { | ||||
|           return this.documentListViewService | ||||
|             .getNext(this.documentId) | ||||
|             .pipe(map((nextDocId) => ({ nextDocId, updateResult }))) | ||||
|         }) | ||||
|       ) | ||||
|       .pipe( | ||||
|         switchMap(({ nextDocId, updateResult }) => { | ||||
|           if (nextDocId && updateResult) | ||||
|             return this.openDocumentService | ||||
|               .closeDocument(this.document) | ||||
|               .pipe( | ||||
|                 map((closeResult) => ({ updateResult, nextDocId, closeResult })) | ||||
|               ) | ||||
|         }) | ||||
|       ) | ||||
|       .pipe(first()) | ||||
|       .subscribe( | ||||
|         ({ updateResult, nextDocId, closeResult }) => { | ||||
|           this.error = null | ||||
|           this.networkActive = false | ||||
|           if (closeResult && updateResult && nextDocId) { | ||||
|             this.router.navigate(['documents', nextDocId]) | ||||
|             this.titleInput?.focus() | ||||
|           } | ||||
|         }, | ||||
|         (error) => { | ||||
|           this.networkActive = false | ||||
|           this.error = error.error | ||||
|         } | ||||
|       ) | ||||
|   } | ||||
|  | ||||
|   close() { | ||||
|     this.openDocumentService.closeDocument(this.document).pipe(first()).subscribe(closed => { | ||||
|       if (!closed) return; | ||||
|       if (this.documentListViewService.activeSavedViewId) { | ||||
|         this.router.navigate(['view', this.documentListViewService.activeSavedViewId]) | ||||
|       } else { | ||||
|         this.router.navigate(['documents']) | ||||
|       } | ||||
|     }) | ||||
|     this.openDocumentService | ||||
|       .closeDocument(this.document) | ||||
|       .pipe(first()) | ||||
|       .subscribe((closed) => { | ||||
|         if (!closed) return | ||||
|         if (this.documentListViewService.activeSavedViewId) { | ||||
|           this.router.navigate([ | ||||
|             'view', | ||||
|             this.documentListViewService.activeSavedViewId, | ||||
|           ]) | ||||
|         } else { | ||||
|           this.router.navigate(['documents']) | ||||
|         } | ||||
|       }) | ||||
|   } | ||||
|  | ||||
|   delete() { | ||||
|     let modal = this.modalService.open(ConfirmDialogComponent, {backdrop: 'static'}) | ||||
|     let modal = this.modalService.open(ConfirmDialogComponent, { | ||||
|       backdrop: 'static', | ||||
|     }) | ||||
|     modal.componentInstance.title = $localize`Confirm delete` | ||||
|     modal.componentInstance.messageBold = $localize`Do you really want to delete document "${this.document.title}"?` | ||||
|     modal.componentInstance.message = $localize`The files for this document will be deleted permanently. This operation cannot be undone.` | ||||
|     modal.componentInstance.btnClass = "btn-danger" | ||||
|     modal.componentInstance.btnClass = 'btn-danger' | ||||
|     modal.componentInstance.btnCaption = $localize`Delete document` | ||||
|     modal.componentInstance.confirmClicked.pipe(switchMap(() => { | ||||
|       modal.componentInstance.buttonsEnabled = false | ||||
|       return this.documentsService.delete(this.document) | ||||
|     })) | ||||
|     .pipe(takeUntil(this.unsubscribeNotifier)) | ||||
|     .subscribe(() => { | ||||
|       modal.close() | ||||
|       this.close() | ||||
|     }, error => { | ||||
|       this.toastService.showError($localize`Error deleting document: ${JSON.stringify(error)}`) | ||||
|       modal.componentInstance.buttonsEnabled = true | ||||
|     }) | ||||
|     modal.componentInstance.confirmClicked | ||||
|       .pipe( | ||||
|         switchMap(() => { | ||||
|           modal.componentInstance.buttonsEnabled = false | ||||
|           return this.documentsService.delete(this.document) | ||||
|         }) | ||||
|       ) | ||||
|       .pipe(takeUntil(this.unsubscribeNotifier)) | ||||
|       .subscribe( | ||||
|         () => { | ||||
|           modal.close() | ||||
|           this.close() | ||||
|         }, | ||||
|         (error) => { | ||||
|           this.toastService.showError( | ||||
|             $localize`Error deleting document: ${JSON.stringify(error)}` | ||||
|           ) | ||||
|           modal.componentInstance.buttonsEnabled = true | ||||
|         } | ||||
|       ) | ||||
|   } | ||||
|  | ||||
|   moreLike() { | ||||
|     this.documentListViewService.quickFilter([{rule_type: FILTER_FULLTEXT_MORELIKE, value: this.documentId.toString()}]) | ||||
|     this.documentListViewService.quickFilter([ | ||||
|       { | ||||
|         rule_type: FILTER_FULLTEXT_MORELIKE, | ||||
|         value: this.documentId.toString(), | ||||
|       }, | ||||
|     ]) | ||||
|   } | ||||
|  | ||||
|   hasNext() { | ||||
| @@ -297,19 +433,22 @@ export class DocumentDetailComponent implements OnInit, OnDestroy, DirtyComponen | ||||
|   } | ||||
|  | ||||
|   nextDoc() { | ||||
|     this.documentListViewService.getNext(this.document.id).subscribe((nextDocId: number) => { | ||||
|       this.router.navigate(['documents', nextDocId]) | ||||
|     }) | ||||
|     this.documentListViewService | ||||
|       .getNext(this.document.id) | ||||
|       .subscribe((nextDocId: number) => { | ||||
|         this.router.navigate(['documents', nextDocId]) | ||||
|       }) | ||||
|   } | ||||
|    | ||||
|   previousDoc () { | ||||
|     this.documentListViewService.getPrevious(this.document.id).subscribe((prevDocId: number) => { | ||||
|       this.router.navigate(['documents', prevDocId]) | ||||
|     }) | ||||
|  | ||||
|   previousDoc() { | ||||
|     this.documentListViewService | ||||
|       .getPrevious(this.document.id) | ||||
|       .subscribe((prevDocId: number) => { | ||||
|         this.router.navigate(['documents', prevDocId]) | ||||
|       }) | ||||
|   } | ||||
|  | ||||
|   pdfPreviewLoaded(pdf: PDFDocumentProxy) { | ||||
|     this.previewNumPages = pdf.numPages | ||||
|   } | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -1,25 +1,24 @@ | ||||
| import { ComponentFixture, TestBed } from '@angular/core/testing'; | ||||
| import { ComponentFixture, TestBed } from '@angular/core/testing' | ||||
|  | ||||
| import { MetadataCollapseComponent } from './metadata-collapse.component'; | ||||
| import { MetadataCollapseComponent } from './metadata-collapse.component' | ||||
|  | ||||
| describe('MetadataCollapseComponent', () => { | ||||
|   let component: MetadataCollapseComponent; | ||||
|   let fixture: ComponentFixture<MetadataCollapseComponent>; | ||||
|   let component: MetadataCollapseComponent | ||||
|   let fixture: ComponentFixture<MetadataCollapseComponent> | ||||
|  | ||||
|   beforeEach(async () => { | ||||
|     await TestBed.configureTestingModule({ | ||||
|       declarations: [ MetadataCollapseComponent ] | ||||
|     }) | ||||
|     .compileComponents(); | ||||
|   }); | ||||
|       declarations: [MetadataCollapseComponent], | ||||
|     }).compileComponents() | ||||
|   }) | ||||
|  | ||||
|   beforeEach(() => { | ||||
|     fixture = TestBed.createComponent(MetadataCollapseComponent); | ||||
|     component = fixture.componentInstance; | ||||
|     fixture.detectChanges(); | ||||
|   }); | ||||
|     fixture = TestBed.createComponent(MetadataCollapseComponent) | ||||
|     component = fixture.componentInstance | ||||
|     fixture.detectChanges() | ||||
|   }) | ||||
|  | ||||
|   it('should create', () => { | ||||
|     expect(component).toBeTruthy(); | ||||
|   }); | ||||
| }); | ||||
|     expect(component).toBeTruthy() | ||||
|   }) | ||||
| }) | ||||
|   | ||||
| @@ -1,13 +1,12 @@ | ||||
| import { Component, Input, OnInit } from '@angular/core'; | ||||
| import { Component, Input, OnInit } from '@angular/core' | ||||
|  | ||||
| @Component({ | ||||
|   selector: 'app-metadata-collapse', | ||||
|   templateUrl: './metadata-collapse.component.html', | ||||
|   styleUrls: ['./metadata-collapse.component.scss'] | ||||
|   styleUrls: ['./metadata-collapse.component.scss'], | ||||
| }) | ||||
| export class MetadataCollapseComponent implements OnInit { | ||||
|  | ||||
|   constructor() { } | ||||
|   constructor() {} | ||||
|  | ||||
|   expand = false | ||||
|  | ||||
| @@ -17,7 +16,5 @@ export class MetadataCollapseComponent implements OnInit { | ||||
|   @Input() | ||||
|   title = $localize`Metadata` | ||||
|  | ||||
|   ngOnInit(): void { | ||||
|   } | ||||
|  | ||||
|   ngOnInit(): void {} | ||||
| } | ||||
|   | ||||
| @@ -1,25 +1,24 @@ | ||||
| import { ComponentFixture, TestBed } from '@angular/core/testing'; | ||||
| import { ComponentFixture, TestBed } from '@angular/core/testing' | ||||
|  | ||||
| import { BulkEditorComponent } from './bulk-editor.component'; | ||||
| import { BulkEditorComponent } from './bulk-editor.component' | ||||
|  | ||||
| describe('BulkEditorComponent', () => { | ||||
|   let component: BulkEditorComponent; | ||||
|   let fixture: ComponentFixture<BulkEditorComponent>; | ||||
|   let component: BulkEditorComponent | ||||
|   let fixture: ComponentFixture<BulkEditorComponent> | ||||
|  | ||||
|   beforeEach(async () => { | ||||
|     await TestBed.configureTestingModule({ | ||||
|       declarations: [ BulkEditorComponent ] | ||||
|     }) | ||||
|     .compileComponents(); | ||||
|   }); | ||||
|       declarations: [BulkEditorComponent], | ||||
|     }).compileComponents() | ||||
|   }) | ||||
|  | ||||
|   beforeEach(() => { | ||||
|     fixture = TestBed.createComponent(BulkEditorComponent); | ||||
|     component = fixture.componentInstance; | ||||
|     fixture.detectChanges(); | ||||
|   }); | ||||
|     fixture = TestBed.createComponent(BulkEditorComponent) | ||||
|     component = fixture.componentInstance | ||||
|     fixture.detectChanges() | ||||
|   }) | ||||
|  | ||||
|   it('should create', () => { | ||||
|     expect(component).toBeTruthy(); | ||||
|   }); | ||||
| }); | ||||
|     expect(component).toBeTruthy() | ||||
|   }) | ||||
| }) | ||||
|   | ||||
| @@ -1,29 +1,37 @@ | ||||
| import { Component } from '@angular/core'; | ||||
| import { PaperlessTag } from 'src/app/data/paperless-tag'; | ||||
| import { PaperlessCorrespondent } from 'src/app/data/paperless-correspondent'; | ||||
| import { PaperlessDocumentType } from 'src/app/data/paperless-document-type'; | ||||
| import { TagService } from 'src/app/services/rest/tag.service'; | ||||
| import { CorrespondentService } from 'src/app/services/rest/correspondent.service'; | ||||
| import { DocumentTypeService } from 'src/app/services/rest/document-type.service'; | ||||
| import { DocumentListViewService } from 'src/app/services/document-list-view.service'; | ||||
| import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; | ||||
| import { DocumentService, SelectionDataItem } from 'src/app/services/rest/document.service'; | ||||
| import { OpenDocumentsService } from 'src/app/services/open-documents.service'; | ||||
| import { ConfirmDialogComponent } from 'src/app/components/common/confirm-dialog/confirm-dialog.component'; | ||||
| import { ChangedItems, FilterableDropdownSelectionModel } from '../../common/filterable-dropdown/filterable-dropdown.component'; | ||||
| import { ToggleableItemState } from '../../common/filterable-dropdown/toggleable-dropdown-button/toggleable-dropdown-button.component'; | ||||
| import { MatchingModel } from 'src/app/data/matching-model'; | ||||
| import { SettingsService, SETTINGS_KEYS } from 'src/app/services/settings.service'; | ||||
| import { ToastService } from 'src/app/services/toast.service'; | ||||
| import { saveAs } from 'file-saver'; | ||||
| import { Component } from '@angular/core' | ||||
| import { PaperlessTag } from 'src/app/data/paperless-tag' | ||||
| import { PaperlessCorrespondent } from 'src/app/data/paperless-correspondent' | ||||
| import { PaperlessDocumentType } from 'src/app/data/paperless-document-type' | ||||
| import { TagService } from 'src/app/services/rest/tag.service' | ||||
| import { CorrespondentService } from 'src/app/services/rest/correspondent.service' | ||||
| import { DocumentTypeService } from 'src/app/services/rest/document-type.service' | ||||
| import { DocumentListViewService } from 'src/app/services/document-list-view.service' | ||||
| import { NgbModal } from '@ng-bootstrap/ng-bootstrap' | ||||
| import { | ||||
|   DocumentService, | ||||
|   SelectionDataItem, | ||||
| } from 'src/app/services/rest/document.service' | ||||
| import { OpenDocumentsService } from 'src/app/services/open-documents.service' | ||||
| import { ConfirmDialogComponent } from 'src/app/components/common/confirm-dialog/confirm-dialog.component' | ||||
| import { | ||||
|   ChangedItems, | ||||
|   FilterableDropdownSelectionModel, | ||||
| } from '../../common/filterable-dropdown/filterable-dropdown.component' | ||||
| import { ToggleableItemState } from '../../common/filterable-dropdown/toggleable-dropdown-button/toggleable-dropdown-button.component' | ||||
| import { MatchingModel } from 'src/app/data/matching-model' | ||||
| import { | ||||
|   SettingsService, | ||||
|   SETTINGS_KEYS, | ||||
| } from 'src/app/services/settings.service' | ||||
| import { ToastService } from 'src/app/services/toast.service' | ||||
| import { saveAs } from 'file-saver' | ||||
|  | ||||
| @Component({ | ||||
|   selector: 'app-bulk-editor', | ||||
|   templateUrl: './bulk-editor.component.html', | ||||
|   styleUrls: ['./bulk-editor.component.scss'] | ||||
|   styleUrls: ['./bulk-editor.component.scss'], | ||||
| }) | ||||
| export class BulkEditorComponent { | ||||
|  | ||||
|   tags: PaperlessTag[] | ||||
|   correspondents: PaperlessCorrespondent[] | ||||
|   documentTypes: PaperlessDocumentType[] | ||||
| @@ -42,43 +50,63 @@ export class BulkEditorComponent { | ||||
|     private openDocumentService: OpenDocumentsService, | ||||
|     private settings: SettingsService, | ||||
|     private toastService: ToastService | ||||
|   ) { } | ||||
|   ) {} | ||||
|  | ||||
|   applyOnClose: boolean = this.settings.get(SETTINGS_KEYS.BULK_EDIT_APPLY_ON_CLOSE) | ||||
|   showConfirmationDialogs: boolean = this.settings.get(SETTINGS_KEYS.BULK_EDIT_CONFIRMATION_DIALOGS) | ||||
|   applyOnClose: boolean = this.settings.get( | ||||
|     SETTINGS_KEYS.BULK_EDIT_APPLY_ON_CLOSE | ||||
|   ) | ||||
|   showConfirmationDialogs: boolean = this.settings.get( | ||||
|     SETTINGS_KEYS.BULK_EDIT_CONFIRMATION_DIALOGS | ||||
|   ) | ||||
|  | ||||
|   ngOnInit() { | ||||
|     this.tagService.listAll().subscribe(result => this.tags = result.results) | ||||
|     this.correspondentService.listAll().subscribe(result => this.correspondents = result.results) | ||||
|     this.documentTypeService.listAll().subscribe(result => this.documentTypes = result.results) | ||||
|     this.tagService | ||||
|       .listAll() | ||||
|       .subscribe((result) => (this.tags = result.results)) | ||||
|     this.correspondentService | ||||
|       .listAll() | ||||
|       .subscribe((result) => (this.correspondents = result.results)) | ||||
|     this.documentTypeService | ||||
|       .listAll() | ||||
|       .subscribe((result) => (this.documentTypes = result.results)) | ||||
|   } | ||||
|  | ||||
|   private executeBulkOperation(modal, method: string, args) { | ||||
|     if (modal) { | ||||
|       modal.componentInstance.buttonsEnabled = false | ||||
|     } | ||||
|     this.documentService.bulkEdit(Array.from(this.list.selected), method, args).subscribe( | ||||
|       response => { | ||||
|         this.list.reload() | ||||
|         this.list.reduceSelectionToFilter() | ||||
|         this.list.selected.forEach(id => { | ||||
|           this.openDocumentService.refreshDocument(id) | ||||
|         }) | ||||
|         if (modal) { | ||||
|           modal.close() | ||||
|     this.documentService | ||||
|       .bulkEdit(Array.from(this.list.selected), method, args) | ||||
|       .subscribe( | ||||
|         (response) => { | ||||
|           this.list.reload() | ||||
|           this.list.reduceSelectionToFilter() | ||||
|           this.list.selected.forEach((id) => { | ||||
|             this.openDocumentService.refreshDocument(id) | ||||
|           }) | ||||
|           if (modal) { | ||||
|             modal.close() | ||||
|           } | ||||
|         }, | ||||
|         (error) => { | ||||
|           if (modal) { | ||||
|             modal.componentInstance.buttonsEnabled = true | ||||
|           } | ||||
|           this.toastService.showError( | ||||
|             $localize`Error executing bulk operation: ${JSON.stringify( | ||||
|               error.error | ||||
|             )}` | ||||
|           ) | ||||
|         } | ||||
|       }, error => { | ||||
|         if (modal) { | ||||
|           modal.componentInstance.buttonsEnabled = true | ||||
|         } | ||||
|         this.toastService.showError($localize`Error executing bulk operation: ${JSON.stringify(error.error)}`) | ||||
|       } | ||||
|     ) | ||||
|       ) | ||||
|   } | ||||
|  | ||||
|   private applySelectionData(items: SelectionDataItem[], selectionModel: FilterableDropdownSelectionModel) { | ||||
|   private applySelectionData( | ||||
|     items: SelectionDataItem[], | ||||
|     selectionModel: FilterableDropdownSelectionModel | ||||
|   ) { | ||||
|     let selectionData = new Map<number, ToggleableItemState>() | ||||
|     items.forEach(i => { | ||||
|     items.forEach((i) => { | ||||
|       if (i.document_count == this.list.selected.size) { | ||||
|         selectionData.set(i.id, ToggleableItemState.Selected) | ||||
|       } else if (i.document_count > 0) { | ||||
| @@ -89,129 +117,210 @@ export class BulkEditorComponent { | ||||
|   } | ||||
|  | ||||
|   openTagsDropdown() { | ||||
|     this.documentService.getSelectionData(Array.from(this.list.selected)).subscribe(s => { | ||||
|       this.applySelectionData(s.selected_tags, this.tagSelectionModel) | ||||
|     }) | ||||
|     this.documentService | ||||
|       .getSelectionData(Array.from(this.list.selected)) | ||||
|       .subscribe((s) => { | ||||
|         this.applySelectionData(s.selected_tags, this.tagSelectionModel) | ||||
|       }) | ||||
|   } | ||||
|  | ||||
|   openDocumentTypeDropdown() { | ||||
|     this.documentService.getSelectionData(Array.from(this.list.selected)).subscribe(s => { | ||||
|       this.applySelectionData(s.selected_document_types, this.documentTypeSelectionModel) | ||||
|     }) | ||||
|     this.documentService | ||||
|       .getSelectionData(Array.from(this.list.selected)) | ||||
|       .subscribe((s) => { | ||||
|         this.applySelectionData( | ||||
|           s.selected_document_types, | ||||
|           this.documentTypeSelectionModel | ||||
|         ) | ||||
|       }) | ||||
|   } | ||||
|  | ||||
|   openCorrespondentDropdown() { | ||||
|     this.documentService.getSelectionData(Array.from(this.list.selected)).subscribe(s => { | ||||
|       this.applySelectionData(s.selected_correspondents, this.correspondentSelectionModel) | ||||
|     }) | ||||
|     this.documentService | ||||
|       .getSelectionData(Array.from(this.list.selected)) | ||||
|       .subscribe((s) => { | ||||
|         this.applySelectionData( | ||||
|           s.selected_correspondents, | ||||
|           this.correspondentSelectionModel | ||||
|         ) | ||||
|       }) | ||||
|   } | ||||
|  | ||||
|   private _localizeList(items: MatchingModel[]) { | ||||
|     if (items.length == 0) { | ||||
|       return "" | ||||
|       return '' | ||||
|     } else if (items.length == 1) { | ||||
|       return $localize`"${items[0].name}"` | ||||
|     } else if (items.length == 2) { | ||||
|       return $localize`:This is for messages like 'modify "tag1" and "tag2"':"${items[0].name}" and "${items[1].name}"` | ||||
|     } else { | ||||
|       let list = items.slice(0, items.length - 1).map(i => $localize`"${i.name}"`).join($localize`:this is used to separate enumerations and should probably be a comma and a whitespace in most languages:, `) | ||||
|       return $localize`:this is for messages like 'modify "tag1", "tag2" and "tag3"':${list} and "${items[items.length - 1].name}"` | ||||
|       let list = items | ||||
|         .slice(0, items.length - 1) | ||||
|         .map((i) => $localize`"${i.name}"`) | ||||
|         .join( | ||||
|           $localize`:this is used to separate enumerations and should probably be a comma and a whitespace in most languages:, ` | ||||
|         ) | ||||
|       return $localize`:this is for messages like 'modify "tag1", "tag2" and "tag3"':${list} and "${ | ||||
|         items[items.length - 1].name | ||||
|       }"` | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   setTags(changedTags: ChangedItems) { | ||||
|     if (changedTags.itemsToAdd.length == 0 && changedTags.itemsToRemove.length == 0) return | ||||
|     if ( | ||||
|       changedTags.itemsToAdd.length == 0 && | ||||
|       changedTags.itemsToRemove.length == 0 | ||||
|     ) | ||||
|       return | ||||
|  | ||||
|     if (this.showConfirmationDialogs) { | ||||
|       let modal = this.modalService.open(ConfirmDialogComponent, {backdrop: 'static'}) | ||||
|       let modal = this.modalService.open(ConfirmDialogComponent, { | ||||
|         backdrop: 'static', | ||||
|       }) | ||||
|       modal.componentInstance.title = $localize`Confirm tags assignment` | ||||
|       if (changedTags.itemsToAdd.length == 1 && changedTags.itemsToRemove.length == 0) { | ||||
|       if ( | ||||
|         changedTags.itemsToAdd.length == 1 && | ||||
|         changedTags.itemsToRemove.length == 0 | ||||
|       ) { | ||||
|         let tag = changedTags.itemsToAdd[0] | ||||
|         modal.componentInstance.message = $localize`This operation will add the tag "${tag.name}" to ${this.list.selected.size} selected document(s).` | ||||
|       } else if (changedTags.itemsToAdd.length > 1 && changedTags.itemsToRemove.length == 0) { | ||||
|         modal.componentInstance.message = $localize`This operation will add the tags ${this._localizeList(changedTags.itemsToAdd)} to ${this.list.selected.size} selected document(s).` | ||||
|       } else if (changedTags.itemsToAdd.length == 0 && changedTags.itemsToRemove.length == 1) { | ||||
|       } else if ( | ||||
|         changedTags.itemsToAdd.length > 1 && | ||||
|         changedTags.itemsToRemove.length == 0 | ||||
|       ) { | ||||
|         modal.componentInstance.message = $localize`This operation will add the tags ${this._localizeList( | ||||
|           changedTags.itemsToAdd | ||||
|         )} to ${this.list.selected.size} selected document(s).` | ||||
|       } else if ( | ||||
|         changedTags.itemsToAdd.length == 0 && | ||||
|         changedTags.itemsToRemove.length == 1 | ||||
|       ) { | ||||
|         let tag = changedTags.itemsToRemove[0] | ||||
|         modal.componentInstance.message = $localize`This operation will remove the tag "${tag.name}" from ${this.list.selected.size} selected document(s).` | ||||
|       } else if (changedTags.itemsToAdd.length == 0 && changedTags.itemsToRemove.length > 1) { | ||||
|         modal.componentInstance.message = $localize`This operation will remove the tags ${this._localizeList(changedTags.itemsToRemove)} from ${this.list.selected.size} selected document(s).` | ||||
|       } else if ( | ||||
|         changedTags.itemsToAdd.length == 0 && | ||||
|         changedTags.itemsToRemove.length > 1 | ||||
|       ) { | ||||
|         modal.componentInstance.message = $localize`This operation will remove the tags ${this._localizeList( | ||||
|           changedTags.itemsToRemove | ||||
|         )} from ${this.list.selected.size} selected document(s).` | ||||
|       } else { | ||||
|         modal.componentInstance.message = $localize`This operation will add the tags ${this._localizeList(changedTags.itemsToAdd)} and remove the tags ${this._localizeList(changedTags.itemsToRemove)} on ${this.list.selected.size} selected document(s).` | ||||
|         modal.componentInstance.message = $localize`This operation will add the tags ${this._localizeList( | ||||
|           changedTags.itemsToAdd | ||||
|         )} and remove the tags ${this._localizeList( | ||||
|           changedTags.itemsToRemove | ||||
|         )} on ${this.list.selected.size} selected document(s).` | ||||
|       } | ||||
|  | ||||
|       modal.componentInstance.btnClass = "btn-warning" | ||||
|       modal.componentInstance.btnClass = 'btn-warning' | ||||
|       modal.componentInstance.btnCaption = $localize`Confirm` | ||||
|       modal.componentInstance.confirmClicked.subscribe(() => { | ||||
|         this.executeBulkOperation(modal, 'modify_tags', {"add_tags": changedTags.itemsToAdd.map(t => t.id), "remove_tags": changedTags.itemsToRemove.map(t => t.id)}) | ||||
|         this.executeBulkOperation(modal, 'modify_tags', { | ||||
|           add_tags: changedTags.itemsToAdd.map((t) => t.id), | ||||
|           remove_tags: changedTags.itemsToRemove.map((t) => t.id), | ||||
|         }) | ||||
|       }) | ||||
|     } else { | ||||
|       this.executeBulkOperation(null, 'modify_tags', {"add_tags": changedTags.itemsToAdd.map(t => t.id), "remove_tags": changedTags.itemsToRemove.map(t => t.id)}) | ||||
|       this.executeBulkOperation(null, 'modify_tags', { | ||||
|         add_tags: changedTags.itemsToAdd.map((t) => t.id), | ||||
|         remove_tags: changedTags.itemsToRemove.map((t) => t.id), | ||||
|       }) | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   setCorrespondents(changedCorrespondents: ChangedItems) { | ||||
|     if (changedCorrespondents.itemsToAdd.length == 0 && changedCorrespondents.itemsToRemove.length == 0) return | ||||
|     if ( | ||||
|       changedCorrespondents.itemsToAdd.length == 0 && | ||||
|       changedCorrespondents.itemsToRemove.length == 0 | ||||
|     ) | ||||
|       return | ||||
|  | ||||
|     let correspondent = changedCorrespondents.itemsToAdd.length > 0 ? changedCorrespondents.itemsToAdd[0] : null | ||||
|     let correspondent = | ||||
|       changedCorrespondents.itemsToAdd.length > 0 | ||||
|         ? changedCorrespondents.itemsToAdd[0] | ||||
|         : null | ||||
|  | ||||
|     if (this.showConfirmationDialogs) { | ||||
|       let modal = this.modalService.open(ConfirmDialogComponent, {backdrop: 'static'}) | ||||
|       let modal = this.modalService.open(ConfirmDialogComponent, { | ||||
|         backdrop: 'static', | ||||
|       }) | ||||
|       modal.componentInstance.title = $localize`Confirm correspondent assignment` | ||||
|       if (correspondent) { | ||||
|         modal.componentInstance.message = $localize`This operation will assign the correspondent "${correspondent.name}" to ${this.list.selected.size} selected document(s).` | ||||
|       } else { | ||||
|         modal.componentInstance.message = $localize`This operation will remove the correspondent from ${this.list.selected.size} selected document(s).` | ||||
|       } | ||||
|       modal.componentInstance.btnClass = "btn-warning" | ||||
|       modal.componentInstance.btnClass = 'btn-warning' | ||||
|       modal.componentInstance.btnCaption = $localize`Confirm` | ||||
|       modal.componentInstance.confirmClicked.subscribe(() => { | ||||
|         this.executeBulkOperation(modal, 'set_correspondent', {"correspondent": correspondent ? correspondent.id : null}) | ||||
|         this.executeBulkOperation(modal, 'set_correspondent', { | ||||
|           correspondent: correspondent ? correspondent.id : null, | ||||
|         }) | ||||
|       }) | ||||
|     } else { | ||||
|       this.executeBulkOperation(null, 'set_correspondent', {"correspondent": correspondent ? correspondent.id : null}) | ||||
|       this.executeBulkOperation(null, 'set_correspondent', { | ||||
|         correspondent: correspondent ? correspondent.id : null, | ||||
|       }) | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   setDocumentTypes(changedDocumentTypes: ChangedItems) { | ||||
|     if (changedDocumentTypes.itemsToAdd.length == 0 && changedDocumentTypes.itemsToRemove.length == 0) return | ||||
|     if ( | ||||
|       changedDocumentTypes.itemsToAdd.length == 0 && | ||||
|       changedDocumentTypes.itemsToRemove.length == 0 | ||||
|     ) | ||||
|       return | ||||
|  | ||||
|     let documentType = changedDocumentTypes.itemsToAdd.length > 0 ? changedDocumentTypes.itemsToAdd[0] : null | ||||
|     let documentType = | ||||
|       changedDocumentTypes.itemsToAdd.length > 0 | ||||
|         ? changedDocumentTypes.itemsToAdd[0] | ||||
|         : null | ||||
|  | ||||
|     if (this.showConfirmationDialogs) { | ||||
|       let modal = this.modalService.open(ConfirmDialogComponent, {backdrop: 'static'}) | ||||
|       let modal = this.modalService.open(ConfirmDialogComponent, { | ||||
|         backdrop: 'static', | ||||
|       }) | ||||
|       modal.componentInstance.title = $localize`Confirm document type assignment` | ||||
|       if (documentType) { | ||||
|         modal.componentInstance.message = $localize`This operation will assign the document type "${documentType.name}" to ${this.list.selected.size} selected document(s).` | ||||
|       } else { | ||||
|         modal.componentInstance.message = $localize`This operation will remove the document type from ${this.list.selected.size} selected document(s).` | ||||
|       } | ||||
|       modal.componentInstance.btnClass = "btn-warning" | ||||
|       modal.componentInstance.btnClass = 'btn-warning' | ||||
|       modal.componentInstance.btnCaption = $localize`Confirm` | ||||
|       modal.componentInstance.confirmClicked.subscribe(() => { | ||||
|         this.executeBulkOperation(modal, 'set_document_type', {"document_type": documentType ? documentType.id : null}) | ||||
|         this.executeBulkOperation(modal, 'set_document_type', { | ||||
|           document_type: documentType ? documentType.id : null, | ||||
|         }) | ||||
|       }) | ||||
|     } else { | ||||
|       this.executeBulkOperation(null, 'set_document_type', {"document_type": documentType ? documentType.id : null}) | ||||
|       this.executeBulkOperation(null, 'set_document_type', { | ||||
|         document_type: documentType ? documentType.id : null, | ||||
|       }) | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   applyDelete() { | ||||
|     let modal = this.modalService.open(ConfirmDialogComponent, {backdrop: 'static'}) | ||||
|     let modal = this.modalService.open(ConfirmDialogComponent, { | ||||
|       backdrop: 'static', | ||||
|     }) | ||||
|     modal.componentInstance.delayConfirm(5) | ||||
|     modal.componentInstance.title = $localize`Delete confirm` | ||||
|     modal.componentInstance.messageBold = $localize`This operation will permanently delete ${this.list.selected.size} selected document(s).` | ||||
|     modal.componentInstance.message = $localize`This operation cannot be undone.` | ||||
|     modal.componentInstance.btnClass = "btn-danger" | ||||
|     modal.componentInstance.btnClass = 'btn-danger' | ||||
|     modal.componentInstance.btnCaption = $localize`Delete document(s)` | ||||
|     modal.componentInstance.confirmClicked.subscribe(() => { | ||||
|       modal.componentInstance.buttonsEnabled = false | ||||
|       this.executeBulkOperation(modal, "delete", {}) | ||||
|       this.executeBulkOperation(modal, 'delete', {}) | ||||
|     }) | ||||
|   } | ||||
|  | ||||
|   downloadSelected(content = "archive") { | ||||
|     this.documentService.bulkDownload(Array.from(this.list.selected), content).subscribe((result: any) => { | ||||
|       saveAs(result, 'documents.zip'); | ||||
|     }) | ||||
|   downloadSelected(content = 'archive') { | ||||
|     this.documentService | ||||
|       .bulkDownload(Array.from(this.list.selected), content) | ||||
|       .subscribe((result: any) => { | ||||
|         saveAs(result, 'documents.zip') | ||||
|       }) | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -1,25 +1,24 @@ | ||||
| import { ComponentFixture, TestBed } from '@angular/core/testing'; | ||||
| import { ComponentFixture, TestBed } from '@angular/core/testing' | ||||
|  | ||||
| import { DocumentCardLargeComponent } from './document-card-large.component'; | ||||
| import { DocumentCardLargeComponent } from './document-card-large.component' | ||||
|  | ||||
| describe('DocumentCardLargeComponent', () => { | ||||
|   let component: DocumentCardLargeComponent; | ||||
|   let fixture: ComponentFixture<DocumentCardLargeComponent>; | ||||
|   let component: DocumentCardLargeComponent | ||||
|   let fixture: ComponentFixture<DocumentCardLargeComponent> | ||||
|  | ||||
|   beforeEach(async () => { | ||||
|     await TestBed.configureTestingModule({ | ||||
|       declarations: [ DocumentCardLargeComponent ] | ||||
|     }) | ||||
|     .compileComponents(); | ||||
|   }); | ||||
|       declarations: [DocumentCardLargeComponent], | ||||
|     }).compileComponents() | ||||
|   }) | ||||
|  | ||||
|   beforeEach(() => { | ||||
|     fixture = TestBed.createComponent(DocumentCardLargeComponent); | ||||
|     component = fixture.componentInstance; | ||||
|     fixture.detectChanges(); | ||||
|   }); | ||||
|     fixture = TestBed.createComponent(DocumentCardLargeComponent) | ||||
|     component = fixture.componentInstance | ||||
|     fixture.detectChanges() | ||||
|   }) | ||||
|  | ||||
|   it('should create', () => { | ||||
|     expect(component).toBeTruthy(); | ||||
|   }); | ||||
| }); | ||||
|     expect(component).toBeTruthy() | ||||
|   }) | ||||
| }) | ||||
|   | ||||
| @@ -1,20 +1,36 @@ | ||||
| import { Component, EventEmitter, Input, OnInit, Output, ViewChild } from '@angular/core'; | ||||
| import { DomSanitizer } from '@angular/platform-browser'; | ||||
| import { PaperlessDocument } from 'src/app/data/paperless-document'; | ||||
| import { DocumentService } from 'src/app/services/rest/document.service'; | ||||
| import { SettingsService, SETTINGS_KEYS } from 'src/app/services/settings.service'; | ||||
| import { NgbPopover } from '@ng-bootstrap/ng-bootstrap'; | ||||
| import { DocumentListViewService } from 'src/app/services/document-list-view.service'; | ||||
| import { FILTER_FULLTEXT_MORELIKE } from 'src/app/data/filter-rule-type'; | ||||
| import { | ||||
|   Component, | ||||
|   EventEmitter, | ||||
|   Input, | ||||
|   OnInit, | ||||
|   Output, | ||||
|   ViewChild, | ||||
| } from '@angular/core' | ||||
| import { DomSanitizer } from '@angular/platform-browser' | ||||
| import { PaperlessDocument } from 'src/app/data/paperless-document' | ||||
| import { DocumentService } from 'src/app/services/rest/document.service' | ||||
| import { | ||||
|   SettingsService, | ||||
|   SETTINGS_KEYS, | ||||
| } from 'src/app/services/settings.service' | ||||
| import { NgbPopover } from '@ng-bootstrap/ng-bootstrap' | ||||
| import { DocumentListViewService } from 'src/app/services/document-list-view.service' | ||||
| import { FILTER_FULLTEXT_MORELIKE } from 'src/app/data/filter-rule-type' | ||||
|  | ||||
| @Component({ | ||||
|   selector: 'app-document-card-large', | ||||
|   templateUrl: './document-card-large.component.html', | ||||
|   styleUrls: ['./document-card-large.component.scss', '../popover-preview/popover-preview.scss'] | ||||
|   styleUrls: [ | ||||
|     './document-card-large.component.scss', | ||||
|     '../popover-preview/popover-preview.scss', | ||||
|   ], | ||||
| }) | ||||
| export class DocumentCardLargeComponent implements OnInit { | ||||
|  | ||||
|   constructor(private documentService: DocumentService, private sanitizer: DomSanitizer, private settingsService: SettingsService) { } | ||||
|   constructor( | ||||
|     private documentService: DocumentService, | ||||
|     private sanitizer: DomSanitizer, | ||||
|     private settingsService: SettingsService | ||||
|   ) {} | ||||
|  | ||||
|   @Input() | ||||
|   selected = false | ||||
| @@ -39,7 +55,7 @@ export class DocumentCardLargeComponent implements OnInit { | ||||
|   clickDocumentType = new EventEmitter<number>() | ||||
|  | ||||
|   @Output() | ||||
|   clickMoreLike= new EventEmitter() | ||||
|   clickMoreLike = new EventEmitter() | ||||
|  | ||||
|   @ViewChild('popover') popover: NgbPopover | ||||
|  | ||||
| @@ -49,17 +65,16 @@ export class DocumentCardLargeComponent implements OnInit { | ||||
|   get searchScoreClass() { | ||||
|     if (this.document.__search_hit__) { | ||||
|       if (this.document.__search_hit__.score > 0.7) { | ||||
|         return "success" | ||||
|         return 'success' | ||||
|       } else if (this.document.__search_hit__.score > 0.3) { | ||||
|         return "warning" | ||||
|         return 'warning' | ||||
|       } else { | ||||
|         return "danger" | ||||
|         return 'danger' | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   ngOnInit(): void { | ||||
|   } | ||||
|   ngOnInit(): void {} | ||||
|  | ||||
|   getIsThumbInverted() { | ||||
|     return this.settingsService.get(SETTINGS_KEYS.DARK_MODE_THUMB_INVERTED) | ||||
| @@ -90,7 +105,7 @@ export class DocumentCardLargeComponent implements OnInit { | ||||
|         } else { | ||||
|           this.popover.close() | ||||
|         } | ||||
|       }, 600); | ||||
|       }, 600) | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   | ||||
| @@ -1,25 +1,24 @@ | ||||
| import { ComponentFixture, TestBed } from '@angular/core/testing'; | ||||
| import { ComponentFixture, TestBed } from '@angular/core/testing' | ||||
|  | ||||
| import { DocumentCardSmallComponent } from './document-card-small.component'; | ||||
| import { DocumentCardSmallComponent } from './document-card-small.component' | ||||
|  | ||||
| describe('DocumentCardSmallComponent', () => { | ||||
|   let component: DocumentCardSmallComponent; | ||||
|   let fixture: ComponentFixture<DocumentCardSmallComponent>; | ||||
|   let component: DocumentCardSmallComponent | ||||
|   let fixture: ComponentFixture<DocumentCardSmallComponent> | ||||
|  | ||||
|   beforeEach(async () => { | ||||
|     await TestBed.configureTestingModule({ | ||||
|       declarations: [ DocumentCardSmallComponent ] | ||||
|     }) | ||||
|     .compileComponents(); | ||||
|   }); | ||||
|       declarations: [DocumentCardSmallComponent], | ||||
|     }).compileComponents() | ||||
|   }) | ||||
|  | ||||
|   beforeEach(() => { | ||||
|     fixture = TestBed.createComponent(DocumentCardSmallComponent); | ||||
|     component = fixture.componentInstance; | ||||
|     fixture.detectChanges(); | ||||
|   }); | ||||
|     fixture = TestBed.createComponent(DocumentCardSmallComponent) | ||||
|     component = fixture.componentInstance | ||||
|     fixture.detectChanges() | ||||
|   }) | ||||
|  | ||||
|   it('should create', () => { | ||||
|     expect(component).toBeTruthy(); | ||||
|   }); | ||||
| }); | ||||
|     expect(component).toBeTruthy() | ||||
|   }) | ||||
| }) | ||||
|   | ||||
| @@ -1,18 +1,33 @@ | ||||
| import { Component, EventEmitter, Input, OnInit, Output, ViewChild } from '@angular/core'; | ||||
| import { map } from 'rxjs/operators'; | ||||
| import { PaperlessDocument } from 'src/app/data/paperless-document'; | ||||
| import { DocumentService } from 'src/app/services/rest/document.service'; | ||||
| import { SettingsService, SETTINGS_KEYS } from 'src/app/services/settings.service'; | ||||
| import { NgbPopover } from '@ng-bootstrap/ng-bootstrap'; | ||||
| import { | ||||
|   Component, | ||||
|   EventEmitter, | ||||
|   Input, | ||||
|   OnInit, | ||||
|   Output, | ||||
|   ViewChild, | ||||
| } from '@angular/core' | ||||
| import { map } from 'rxjs/operators' | ||||
| import { PaperlessDocument } from 'src/app/data/paperless-document' | ||||
| import { DocumentService } from 'src/app/services/rest/document.service' | ||||
| import { | ||||
|   SettingsService, | ||||
|   SETTINGS_KEYS, | ||||
| } from 'src/app/services/settings.service' | ||||
| import { NgbPopover } from '@ng-bootstrap/ng-bootstrap' | ||||
|  | ||||
| @Component({ | ||||
|   selector: 'app-document-card-small', | ||||
|   templateUrl: './document-card-small.component.html', | ||||
|   styleUrls: ['./document-card-small.component.scss', '../popover-preview/popover-preview.scss'] | ||||
|   styleUrls: [ | ||||
|     './document-card-small.component.scss', | ||||
|     '../popover-preview/popover-preview.scss', | ||||
|   ], | ||||
| }) | ||||
| export class DocumentCardSmallComponent implements OnInit { | ||||
|  | ||||
|   constructor(private documentService: DocumentService, private settingsService: SettingsService) { } | ||||
|   constructor( | ||||
|     private documentService: DocumentService, | ||||
|     private settingsService: SettingsService | ||||
|   ) {} | ||||
|  | ||||
|   @Input() | ||||
|   selected = false | ||||
| @@ -39,8 +54,7 @@ export class DocumentCardSmallComponent implements OnInit { | ||||
|   mouseOnPreview = false | ||||
|   popoverHidden = true | ||||
|  | ||||
|   ngOnInit(): void { | ||||
|   } | ||||
|   ngOnInit(): void {} | ||||
|  | ||||
|   getIsThumbInverted() { | ||||
|     return this.settingsService.get(SETTINGS_KEYS.DARK_MODE_THUMB_INVERTED) | ||||
| @@ -60,7 +74,7 @@ export class DocumentCardSmallComponent implements OnInit { | ||||
|  | ||||
|   getTagsLimited$() { | ||||
|     return this.document.tags$.pipe( | ||||
|       map(tags => { | ||||
|       map((tags) => { | ||||
|         if (tags.length > 7) { | ||||
|           this.moreTags = tags.length - 6 | ||||
|           return tags.slice(0, 6) | ||||
| @@ -84,7 +98,7 @@ export class DocumentCardSmallComponent implements OnInit { | ||||
|         } else { | ||||
|           this.popover.close() | ||||
|         } | ||||
|       }, 600); | ||||
|       }, 600) | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   | ||||
| @@ -1,25 +1,24 @@ | ||||
| import { ComponentFixture, TestBed } from '@angular/core/testing'; | ||||
| import { ComponentFixture, TestBed } from '@angular/core/testing' | ||||
|  | ||||
| import { DocumentListComponent } from './document-list.component'; | ||||
| import { DocumentListComponent } from './document-list.component' | ||||
|  | ||||
| describe('DocumentListComponent', () => { | ||||
|   let component: DocumentListComponent; | ||||
|   let fixture: ComponentFixture<DocumentListComponent>; | ||||
|   let component: DocumentListComponent | ||||
|   let fixture: ComponentFixture<DocumentListComponent> | ||||
|  | ||||
|   beforeEach(async () => { | ||||
|     await TestBed.configureTestingModule({ | ||||
|       declarations: [ DocumentListComponent ] | ||||
|     }) | ||||
|     .compileComponents(); | ||||
|   }); | ||||
|       declarations: [DocumentListComponent], | ||||
|     }).compileComponents() | ||||
|   }) | ||||
|  | ||||
|   beforeEach(() => { | ||||
|     fixture = TestBed.createComponent(DocumentListComponent); | ||||
|     component = fixture.componentInstance; | ||||
|     fixture.detectChanges(); | ||||
|   }); | ||||
|     fixture = TestBed.createComponent(DocumentListComponent) | ||||
|     component = fixture.componentInstance | ||||
|     fixture.detectChanges() | ||||
|   }) | ||||
|  | ||||
|   it('should create', () => { | ||||
|     expect(component).toBeTruthy(); | ||||
|   }); | ||||
| }); | ||||
|     expect(component).toBeTruthy() | ||||
|   }) | ||||
| }) | ||||
|   | ||||
| @@ -1,27 +1,39 @@ | ||||
| import { Component, OnDestroy, OnInit, QueryList, ViewChild, ViewChildren } from '@angular/core'; | ||||
| import { ActivatedRoute, Router } from '@angular/router'; | ||||
| import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; | ||||
| import { Subscription } from 'rxjs'; | ||||
| import { FilterRule, isFullTextFilterRule } from 'src/app/data/filter-rule'; | ||||
| import { FILTER_FULLTEXT_MORELIKE } from 'src/app/data/filter-rule-type'; | ||||
| import { PaperlessDocument } from 'src/app/data/paperless-document'; | ||||
| import { PaperlessSavedView } from 'src/app/data/paperless-saved-view'; | ||||
| import { SortableDirective, SortEvent } from 'src/app/directives/sortable.directive'; | ||||
| import { ConsumerStatusService } from 'src/app/services/consumer-status.service'; | ||||
| import { DocumentListViewService } from 'src/app/services/document-list-view.service'; | ||||
| import { DOCUMENT_SORT_FIELDS, DOCUMENT_SORT_FIELDS_FULLTEXT } from 'src/app/services/rest/document.service'; | ||||
| import { SavedViewService } from 'src/app/services/rest/saved-view.service'; | ||||
| import { ToastService } from 'src/app/services/toast.service'; | ||||
| import { FilterEditorComponent } from './filter-editor/filter-editor.component'; | ||||
| import { SaveViewConfigDialogComponent } from './save-view-config-dialog/save-view-config-dialog.component'; | ||||
| import { | ||||
|   Component, | ||||
|   OnDestroy, | ||||
|   OnInit, | ||||
|   QueryList, | ||||
|   ViewChild, | ||||
|   ViewChildren, | ||||
| } from '@angular/core' | ||||
| import { ActivatedRoute, Router } from '@angular/router' | ||||
| import { NgbModal } from '@ng-bootstrap/ng-bootstrap' | ||||
| import { Subscription } from 'rxjs' | ||||
| import { FilterRule, isFullTextFilterRule } from 'src/app/data/filter-rule' | ||||
| import { FILTER_FULLTEXT_MORELIKE } from 'src/app/data/filter-rule-type' | ||||
| import { PaperlessDocument } from 'src/app/data/paperless-document' | ||||
| import { PaperlessSavedView } from 'src/app/data/paperless-saved-view' | ||||
| import { | ||||
|   SortableDirective, | ||||
|   SortEvent, | ||||
| } from 'src/app/directives/sortable.directive' | ||||
| import { ConsumerStatusService } from 'src/app/services/consumer-status.service' | ||||
| import { DocumentListViewService } from 'src/app/services/document-list-view.service' | ||||
| import { | ||||
|   DOCUMENT_SORT_FIELDS, | ||||
|   DOCUMENT_SORT_FIELDS_FULLTEXT, | ||||
| } from 'src/app/services/rest/document.service' | ||||
| import { SavedViewService } from 'src/app/services/rest/saved-view.service' | ||||
| import { ToastService } from 'src/app/services/toast.service' | ||||
| import { FilterEditorComponent } from './filter-editor/filter-editor.component' | ||||
| import { SaveViewConfigDialogComponent } from './save-view-config-dialog/save-view-config-dialog.component' | ||||
|  | ||||
| @Component({ | ||||
|   selector: 'app-document-list', | ||||
|   templateUrl: './document-list.component.html', | ||||
|   styleUrls: ['./document-list.component.scss'] | ||||
|   styleUrls: ['./document-list.component.scss'], | ||||
| }) | ||||
| export class DocumentListComponent implements OnInit, OnDestroy { | ||||
|  | ||||
|   constructor( | ||||
|     public list: DocumentListViewService, | ||||
|     public savedViewService: SavedViewService, | ||||
| @@ -30,12 +42,12 @@ export class DocumentListComponent implements OnInit, OnDestroy { | ||||
|     private toastService: ToastService, | ||||
|     private modalService: NgbModal, | ||||
|     private consumerStatusService: ConsumerStatusService | ||||
|   ) { } | ||||
|   ) {} | ||||
|  | ||||
|   @ViewChild("filterEditor") | ||||
|   @ViewChild('filterEditor') | ||||
|   private filterEditor: FilterEditorComponent | ||||
|  | ||||
|   @ViewChildren(SortableDirective) headers: QueryList<SortableDirective>; | ||||
|   @ViewChildren(SortableDirective) headers: QueryList<SortableDirective> | ||||
|  | ||||
|   displayMode = 'smallCards' // largeCards, smallCards, details | ||||
|  | ||||
| @@ -52,7 +64,9 @@ export class DocumentListComponent implements OnInit, OnDestroy { | ||||
|   } | ||||
|  | ||||
|   getSortFields() { | ||||
|     return isFullTextFilterRule(this.list.filterRules) ? DOCUMENT_SORT_FIELDS_FULLTEXT : DOCUMENT_SORT_FIELDS | ||||
|     return isFullTextFilterRule(this.list.filterRules) | ||||
|       ? DOCUMENT_SORT_FIELDS_FULLTEXT | ||||
|       : DOCUMENT_SORT_FIELDS | ||||
|   } | ||||
|  | ||||
|   onSort(event: SortEvent) { | ||||
| @@ -71,14 +85,16 @@ export class DocumentListComponent implements OnInit, OnDestroy { | ||||
|     if (localStorage.getItem('document-list:displayMode') != null) { | ||||
|       this.displayMode = localStorage.getItem('document-list:displayMode') | ||||
|     } | ||||
|     this.consumptionFinishedSubscription = this.consumerStatusService.onDocumentConsumptionFinished().subscribe(() => { | ||||
|       this.list.reload() | ||||
|     }) | ||||
|     this.route.paramMap.subscribe(params => { | ||||
|     this.consumptionFinishedSubscription = this.consumerStatusService | ||||
|       .onDocumentConsumptionFinished() | ||||
|       .subscribe(() => { | ||||
|         this.list.reload() | ||||
|       }) | ||||
|     this.route.paramMap.subscribe((params) => { | ||||
|       if (params.has('id')) { | ||||
|         this.savedViewService.getCached(+params.get('id')).subscribe(view => { | ||||
|         this.savedViewService.getCached(+params.get('id')).subscribe((view) => { | ||||
|           if (!view) { | ||||
|             this.router.navigate(["404"]) | ||||
|             this.router.navigate(['404']) | ||||
|             return | ||||
|           } | ||||
|           this.list.activateSavedView(view) | ||||
| @@ -110,19 +126,23 @@ export class DocumentListComponent implements OnInit, OnDestroy { | ||||
|         id: this.list.activeSavedViewId, | ||||
|         filter_rules: this.list.filterRules, | ||||
|         sort_field: this.list.sortField, | ||||
|         sort_reverse: this.list.sortReverse | ||||
|         sort_reverse: this.list.sortReverse, | ||||
|       } | ||||
|       this.savedViewService.patch(savedView).subscribe(result => { | ||||
|         this.toastService.showInfo($localize`View "${this.list.activeSavedViewTitle}" saved successfully.`) | ||||
|       this.savedViewService.patch(savedView).subscribe((result) => { | ||||
|         this.toastService.showInfo( | ||||
|           $localize`View "${this.list.activeSavedViewTitle}" saved successfully.` | ||||
|         ) | ||||
|         this.unmodifiedFilterRules = this.list.filterRules | ||||
|       }) | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   saveViewConfigAs() { | ||||
|     let modal = this.modalService.open(SaveViewConfigDialogComponent, {backdrop: 'static'}) | ||||
|     let modal = this.modalService.open(SaveViewConfigDialogComponent, { | ||||
|       backdrop: 'static', | ||||
|     }) | ||||
|     modal.componentInstance.defaultName = this.filterEditor.generateFilterName() | ||||
|     modal.componentInstance.saveClicked.subscribe(formValue => { | ||||
|     modal.componentInstance.saveClicked.subscribe((formValue) => { | ||||
|       modal.componentInstance.buttonsEnabled = false | ||||
|       let savedView: PaperlessSavedView = { | ||||
|         name: formValue.name, | ||||
| @@ -130,16 +150,21 @@ export class DocumentListComponent implements OnInit, OnDestroy { | ||||
|         show_in_sidebar: formValue.showInSideBar, | ||||
|         filter_rules: this.list.filterRules, | ||||
|         sort_reverse: this.list.sortReverse, | ||||
|         sort_field: this.list.sortField | ||||
|         sort_field: this.list.sortField, | ||||
|       } | ||||
|  | ||||
|       this.savedViewService.create(savedView).subscribe(() => { | ||||
|         modal.close() | ||||
|         this.toastService.showInfo($localize`View "${savedView.name}" created successfully.`) | ||||
|       }, error => { | ||||
|         modal.componentInstance.error = error.error | ||||
|         modal.componentInstance.buttonsEnabled = true | ||||
|       }) | ||||
|       this.savedViewService.create(savedView).subscribe( | ||||
|         () => { | ||||
|           modal.close() | ||||
|           this.toastService.showInfo( | ||||
|             $localize`View "${savedView.name}" created successfully.` | ||||
|           ) | ||||
|         }, | ||||
|         (error) => { | ||||
|           modal.componentInstance.error = error.error | ||||
|           modal.componentInstance.buttonsEnabled = true | ||||
|         } | ||||
|       ) | ||||
|     }) | ||||
|   } | ||||
|  | ||||
| @@ -170,7 +195,9 @@ export class DocumentListComponent implements OnInit, OnDestroy { | ||||
|   } | ||||
|  | ||||
|   clickMoreLike(documentID: number) { | ||||
|     this.list.quickFilter([{rule_type: FILTER_FULLTEXT_MORELIKE, value: documentID.toString()}]) | ||||
|     this.list.quickFilter([ | ||||
|       { rule_type: FILTER_FULLTEXT_MORELIKE, value: documentID.toString() }, | ||||
|     ]) | ||||
|   } | ||||
|  | ||||
|   trackByDocumentId(index, item: PaperlessDocument) { | ||||
|   | ||||
| @@ -1,25 +1,24 @@ | ||||
| import { ComponentFixture, TestBed } from '@angular/core/testing'; | ||||
| import { ComponentFixture, TestBed } from '@angular/core/testing' | ||||
|  | ||||
| import { FilterEditorComponent } from './filter-editor.component'; | ||||
| import { FilterEditorComponent } from './filter-editor.component' | ||||
|  | ||||
| describe('FilterEditorComponent', () => { | ||||
|   let component: FilterEditorComponent; | ||||
|   let fixture: ComponentFixture<FilterEditorComponent>; | ||||
|   let component: FilterEditorComponent | ||||
|   let fixture: ComponentFixture<FilterEditorComponent> | ||||
|  | ||||
|   beforeEach(async () => { | ||||
|     await TestBed.configureTestingModule({ | ||||
|       declarations: [ FilterEditorComponent ] | ||||
|     }) | ||||
|     .compileComponents(); | ||||
|   }); | ||||
|       declarations: [FilterEditorComponent], | ||||
|     }).compileComponents() | ||||
|   }) | ||||
|  | ||||
|   beforeEach(() => { | ||||
|     fixture = TestBed.createComponent(FilterEditorComponent); | ||||
|     component = fixture.componentInstance; | ||||
|     fixture.detectChanges(); | ||||
|   }); | ||||
|     fixture = TestBed.createComponent(FilterEditorComponent) | ||||
|     component = fixture.componentInstance | ||||
|     fixture.detectChanges() | ||||
|   }) | ||||
|  | ||||
|   it('should create', () => { | ||||
|     expect(component).toBeTruthy(); | ||||
|   }); | ||||
| }); | ||||
|     expect(component).toBeTruthy() | ||||
|   }) | ||||
| }) | ||||
|   | ||||
| @@ -1,56 +1,85 @@ | ||||
| import { Component, EventEmitter, Input, Output, OnInit, OnDestroy, ViewChild, ElementRef } from '@angular/core'; | ||||
| import { PaperlessTag } from 'src/app/data/paperless-tag'; | ||||
| import { PaperlessCorrespondent } from 'src/app/data/paperless-correspondent'; | ||||
| import { PaperlessDocumentType } from 'src/app/data/paperless-document-type'; | ||||
| import { Subject, Subscription } from 'rxjs'; | ||||
| import { debounceTime, distinctUntilChanged } from 'rxjs/operators'; | ||||
| import { DocumentTypeService } from 'src/app/services/rest/document-type.service'; | ||||
| import { TagService } from 'src/app/services/rest/tag.service'; | ||||
| import { CorrespondentService } from 'src/app/services/rest/correspondent.service'; | ||||
| import { FilterRule } from 'src/app/data/filter-rule'; | ||||
| import { FILTER_ADDED_AFTER, FILTER_ADDED_BEFORE, FILTER_ASN, FILTER_CORRESPONDENT, FILTER_CREATED_AFTER, FILTER_CREATED_BEFORE, FILTER_DOCUMENT_TYPE, FILTER_FULLTEXT_MORELIKE, FILTER_FULLTEXT_QUERY, FILTER_HAS_ANY_TAG, FILTER_HAS_TAGS_ALL, FILTER_HAS_TAGS_ANY, FILTER_DOES_NOT_HAVE_TAG, FILTER_TITLE, FILTER_TITLE_CONTENT } from 'src/app/data/filter-rule-type'; | ||||
| import { FilterableDropdownSelectionModel } from '../../common/filterable-dropdown/filterable-dropdown.component'; | ||||
| import { ToggleableItemState } from '../../common/filterable-dropdown/toggleable-dropdown-button/toggleable-dropdown-button.component'; | ||||
| import { DocumentService } from 'src/app/services/rest/document.service'; | ||||
| import { PaperlessDocument } from 'src/app/data/paperless-document'; | ||||
| import { | ||||
|   Component, | ||||
|   EventEmitter, | ||||
|   Input, | ||||
|   Output, | ||||
|   OnInit, | ||||
|   OnDestroy, | ||||
|   ViewChild, | ||||
|   ElementRef, | ||||
| } from '@angular/core' | ||||
| import { PaperlessTag } from 'src/app/data/paperless-tag' | ||||
| import { PaperlessCorrespondent } from 'src/app/data/paperless-correspondent' | ||||
| import { PaperlessDocumentType } from 'src/app/data/paperless-document-type' | ||||
| import { Subject, Subscription } from 'rxjs' | ||||
| import { debounceTime, distinctUntilChanged } from 'rxjs/operators' | ||||
| import { DocumentTypeService } from 'src/app/services/rest/document-type.service' | ||||
| import { TagService } from 'src/app/services/rest/tag.service' | ||||
| import { CorrespondentService } from 'src/app/services/rest/correspondent.service' | ||||
| import { FilterRule } from 'src/app/data/filter-rule' | ||||
| import { | ||||
|   FILTER_ADDED_AFTER, | ||||
|   FILTER_ADDED_BEFORE, | ||||
|   FILTER_ASN, | ||||
|   FILTER_CORRESPONDENT, | ||||
|   FILTER_CREATED_AFTER, | ||||
|   FILTER_CREATED_BEFORE, | ||||
|   FILTER_DOCUMENT_TYPE, | ||||
|   FILTER_FULLTEXT_MORELIKE, | ||||
|   FILTER_FULLTEXT_QUERY, | ||||
|   FILTER_HAS_ANY_TAG, | ||||
|   FILTER_HAS_TAGS_ALL, | ||||
|   FILTER_HAS_TAGS_ANY, | ||||
|   FILTER_DOES_NOT_HAVE_TAG, | ||||
|   FILTER_TITLE, | ||||
|   FILTER_TITLE_CONTENT, | ||||
| } from 'src/app/data/filter-rule-type' | ||||
| import { FilterableDropdownSelectionModel } from '../../common/filterable-dropdown/filterable-dropdown.component' | ||||
| import { ToggleableItemState } from '../../common/filterable-dropdown/toggleable-dropdown-button/toggleable-dropdown-button.component' | ||||
| import { DocumentService } from 'src/app/services/rest/document.service' | ||||
| import { PaperlessDocument } from 'src/app/data/paperless-document' | ||||
|  | ||||
| const TEXT_FILTER_TARGET_TITLE = "title" | ||||
| const TEXT_FILTER_TARGET_TITLE_CONTENT = "title-content" | ||||
| const TEXT_FILTER_TARGET_ASN = "asn" | ||||
| const TEXT_FILTER_TARGET_FULLTEXT_QUERY = "fulltext-query" | ||||
| const TEXT_FILTER_TARGET_FULLTEXT_MORELIKE = "fulltext-morelike" | ||||
| const TEXT_FILTER_TARGET_TITLE = 'title' | ||||
| const TEXT_FILTER_TARGET_TITLE_CONTENT = 'title-content' | ||||
| const TEXT_FILTER_TARGET_ASN = 'asn' | ||||
| const TEXT_FILTER_TARGET_FULLTEXT_QUERY = 'fulltext-query' | ||||
| const TEXT_FILTER_TARGET_FULLTEXT_MORELIKE = 'fulltext-morelike' | ||||
|  | ||||
| @Component({ | ||||
|   selector: 'app-filter-editor', | ||||
|   templateUrl: './filter-editor.component.html', | ||||
|   styleUrls: ['./filter-editor.component.scss'] | ||||
|   styleUrls: ['./filter-editor.component.scss'], | ||||
| }) | ||||
| export class FilterEditorComponent implements OnInit, OnDestroy { | ||||
|  | ||||
|   generateFilterName() { | ||||
|     if (this.filterRules.length == 1) { | ||||
|       let rule = this.filterRules[0] | ||||
|       switch(this.filterRules[0].rule_type) { | ||||
|  | ||||
|       switch (this.filterRules[0].rule_type) { | ||||
|         case FILTER_CORRESPONDENT: | ||||
|           if (rule.value) { | ||||
|             return $localize`Correspondent: ${this.correspondents.find(c => c.id == +rule.value)?.name}` | ||||
|             return $localize`Correspondent: ${ | ||||
|               this.correspondents.find((c) => c.id == +rule.value)?.name | ||||
|             }` | ||||
|           } else { | ||||
|             return $localize`Without correspondent` | ||||
|           } | ||||
|  | ||||
|         case FILTER_DOCUMENT_TYPE: | ||||
|           if (rule.value) { | ||||
|             return $localize`Type: ${this.documentTypes.find(dt => dt.id == +rule.value)?.name}` | ||||
|             return $localize`Type: ${ | ||||
|               this.documentTypes.find((dt) => dt.id == +rule.value)?.name | ||||
|             }` | ||||
|           } else { | ||||
|             return $localize`Without document type` | ||||
|           } | ||||
|  | ||||
|         case FILTER_HAS_TAGS_ALL: | ||||
|           return $localize`Tag: ${this.tags.find(t => t.id == +rule.value)?.name}` | ||||
|           return $localize`Tag: ${ | ||||
|             this.tags.find((t) => t.id == +rule.value)?.name | ||||
|           }` | ||||
|  | ||||
|         case FILTER_HAS_ANY_TAG: | ||||
|           if (rule.value == "false") { | ||||
|           if (rule.value == 'false') { | ||||
|             return $localize`Without any tag` | ||||
|           } | ||||
|  | ||||
| @@ -62,7 +91,7 @@ export class FilterEditorComponent implements OnInit, OnDestroy { | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     return "" | ||||
|     return '' | ||||
|   } | ||||
|  | ||||
|   constructor( | ||||
| @@ -70,28 +99,37 @@ export class FilterEditorComponent implements OnInit, OnDestroy { | ||||
|     private tagService: TagService, | ||||
|     private correspondentService: CorrespondentService, | ||||
|     private documentService: DocumentService | ||||
|   ) { } | ||||
|   ) {} | ||||
|  | ||||
|   @ViewChild("textFilterInput") | ||||
|   @ViewChild('textFilterInput') | ||||
|   textFilterInput: ElementRef | ||||
|  | ||||
|   tags: PaperlessTag[] = [] | ||||
|   correspondents: PaperlessCorrespondent[] = [] | ||||
|   documentTypes: PaperlessDocumentType[] = [] | ||||
|  | ||||
|   _textFilter = "" | ||||
|   _textFilter = '' | ||||
|   _moreLikeId: number | ||||
|   _moreLikeDoc: PaperlessDocument | ||||
|  | ||||
|   get textFilterTargets() { | ||||
|     let targets = [ | ||||
|       {id: TEXT_FILTER_TARGET_TITLE, name: $localize`Title`}, | ||||
|       {id: TEXT_FILTER_TARGET_TITLE_CONTENT, name: $localize`Title & content`}, | ||||
|       {id: TEXT_FILTER_TARGET_ASN, name: $localize`ASN`}, | ||||
|       {id: TEXT_FILTER_TARGET_FULLTEXT_QUERY, name: $localize`Advanced search`} | ||||
|       { id: TEXT_FILTER_TARGET_TITLE, name: $localize`Title` }, | ||||
|       { | ||||
|         id: TEXT_FILTER_TARGET_TITLE_CONTENT, | ||||
|         name: $localize`Title & content`, | ||||
|       }, | ||||
|       { id: TEXT_FILTER_TARGET_ASN, name: $localize`ASN` }, | ||||
|       { | ||||
|         id: TEXT_FILTER_TARGET_FULLTEXT_QUERY, | ||||
|         name: $localize`Advanced search`, | ||||
|       }, | ||||
|     ] | ||||
|     if (this.textFilterTarget == TEXT_FILTER_TARGET_FULLTEXT_MORELIKE) { | ||||
|       targets.push({id: TEXT_FILTER_TARGET_FULLTEXT_MORELIKE, name: $localize`More like`}) | ||||
|       targets.push({ | ||||
|         id: TEXT_FILTER_TARGET_FULLTEXT_MORELIKE, | ||||
|         name: $localize`More like`, | ||||
|       }) | ||||
|     } | ||||
|     return targets | ||||
|   } | ||||
| @@ -99,10 +137,10 @@ export class FilterEditorComponent implements OnInit, OnDestroy { | ||||
|   textFilterTarget = TEXT_FILTER_TARGET_TITLE_CONTENT | ||||
|  | ||||
|   get textFilterTargetName() { | ||||
|     return this.textFilterTargets.find(t => t.id == this.textFilterTarget)?.name | ||||
|     return this.textFilterTargets.find((t) => t.id == this.textFilterTarget) | ||||
|       ?.name | ||||
|   } | ||||
|  | ||||
|  | ||||
|   tagSelectionModel = new FilterableDropdownSelectionModel() | ||||
|   correspondentSelectionModel = new FilterableDropdownSelectionModel() | ||||
|   documentTypeSelectionModel = new FilterableDropdownSelectionModel() | ||||
| @@ -126,7 +164,7 @@ export class FilterEditorComponent implements OnInit, OnDestroy { | ||||
|   } | ||||
|  | ||||
|   @Input() | ||||
|   set filterRules (value: FilterRule[]) { | ||||
|   set filterRules(value: FilterRule[]) { | ||||
|     this._filterRules = value | ||||
|  | ||||
|     this.documentTypeSelectionModel.clear(false) | ||||
| @@ -139,7 +177,7 @@ export class FilterEditorComponent implements OnInit, OnDestroy { | ||||
|     this.dateCreatedBefore = null | ||||
|     this.dateCreatedAfter = null | ||||
|  | ||||
|     value.forEach(rule => { | ||||
|     value.forEach((rule) => { | ||||
|       switch (rule.rule_type) { | ||||
|         case FILTER_TITLE: | ||||
|           this._textFilter = rule.value | ||||
| @@ -160,7 +198,7 @@ export class FilterEditorComponent implements OnInit, OnDestroy { | ||||
|         case FILTER_FULLTEXT_MORELIKE: | ||||
|           this._moreLikeId = +rule.value | ||||
|           this.textFilterTarget = TEXT_FILTER_TARGET_FULLTEXT_MORELIKE | ||||
|           this.documentService.get(this._moreLikeId).subscribe(result => { | ||||
|           this.documentService.get(this._moreLikeId).subscribe((result) => { | ||||
|             this._moreLikeDoc = result | ||||
|             this._textFilter = result.title | ||||
|           }) | ||||
| @@ -178,23 +216,43 @@ export class FilterEditorComponent implements OnInit, OnDestroy { | ||||
|           this.dateAddedBefore = rule.value | ||||
|           break | ||||
|         case FILTER_HAS_TAGS_ALL: | ||||
|           this.tagSelectionModel.set(rule.value ? +rule.value : null, ToggleableItemState.Selected, false) | ||||
|           this.tagSelectionModel.set( | ||||
|             rule.value ? +rule.value : null, | ||||
|             ToggleableItemState.Selected, | ||||
|             false | ||||
|           ) | ||||
|           break | ||||
|         case FILTER_HAS_TAGS_ANY: | ||||
|           this.tagSelectionModel.logicalOperator = 'or' | ||||
|           this.tagSelectionModel.set(rule.value ? +rule.value : null, ToggleableItemState.Selected, false) | ||||
|           this.tagSelectionModel.set( | ||||
|             rule.value ? +rule.value : null, | ||||
|             ToggleableItemState.Selected, | ||||
|             false | ||||
|           ) | ||||
|           break | ||||
|         case FILTER_HAS_ANY_TAG: | ||||
|           this.tagSelectionModel.set(null, ToggleableItemState.Selected, false) | ||||
|           break | ||||
|         case FILTER_DOES_NOT_HAVE_TAG: | ||||
|           this.tagSelectionModel.set(rule.value ? +rule.value : null, ToggleableItemState.Excluded, false) | ||||
|           this.tagSelectionModel.set( | ||||
|             rule.value ? +rule.value : null, | ||||
|             ToggleableItemState.Excluded, | ||||
|             false | ||||
|           ) | ||||
|           break | ||||
|         case FILTER_CORRESPONDENT: | ||||
|           this.correspondentSelectionModel.set(rule.value ? +rule.value : null, ToggleableItemState.Selected, false) | ||||
|           this.correspondentSelectionModel.set( | ||||
|             rule.value ? +rule.value : null, | ||||
|             ToggleableItemState.Selected, | ||||
|             false | ||||
|           ) | ||||
|           break | ||||
|         case FILTER_DOCUMENT_TYPE: | ||||
|           this.documentTypeSelectionModel.set(rule.value ? +rule.value : null, ToggleableItemState.Selected, false) | ||||
|           this.documentTypeSelectionModel.set( | ||||
|             rule.value ? +rule.value : null, | ||||
|             ToggleableItemState.Selected, | ||||
|             false | ||||
|           ) | ||||
|           break | ||||
|       } | ||||
|     }) | ||||
| @@ -203,49 +261,104 @@ export class FilterEditorComponent implements OnInit, OnDestroy { | ||||
|  | ||||
|   get filterRules(): FilterRule[] { | ||||
|     let filterRules: FilterRule[] = [] | ||||
|     if (this._textFilter && this.textFilterTarget == TEXT_FILTER_TARGET_TITLE_CONTENT) { | ||||
|       filterRules.push({rule_type: FILTER_TITLE_CONTENT, value: this._textFilter}) | ||||
|     if ( | ||||
|       this._textFilter && | ||||
|       this.textFilterTarget == TEXT_FILTER_TARGET_TITLE_CONTENT | ||||
|     ) { | ||||
|       filterRules.push({ | ||||
|         rule_type: FILTER_TITLE_CONTENT, | ||||
|         value: this._textFilter, | ||||
|       }) | ||||
|     } | ||||
|     if (this._textFilter && this.textFilterTarget == TEXT_FILTER_TARGET_TITLE) { | ||||
|       filterRules.push({rule_type: FILTER_TITLE, value: this._textFilter}) | ||||
|       filterRules.push({ rule_type: FILTER_TITLE, value: this._textFilter }) | ||||
|     } | ||||
|     if (this._textFilter && this.textFilterTarget == TEXT_FILTER_TARGET_ASN) { | ||||
|       filterRules.push({rule_type: FILTER_ASN, value: this._textFilter}) | ||||
|       filterRules.push({ rule_type: FILTER_ASN, value: this._textFilter }) | ||||
|     } | ||||
|     if (this._textFilter && this.textFilterTarget == TEXT_FILTER_TARGET_FULLTEXT_QUERY) { | ||||
|       filterRules.push({rule_type: FILTER_FULLTEXT_QUERY, value: this._textFilter}) | ||||
|     if ( | ||||
|       this._textFilter && | ||||
|       this.textFilterTarget == TEXT_FILTER_TARGET_FULLTEXT_QUERY | ||||
|     ) { | ||||
|       filterRules.push({ | ||||
|         rule_type: FILTER_FULLTEXT_QUERY, | ||||
|         value: this._textFilter, | ||||
|       }) | ||||
|     } | ||||
|     if (this._moreLikeId && this.textFilterTarget == TEXT_FILTER_TARGET_FULLTEXT_MORELIKE) { | ||||
|       filterRules.push({rule_type: FILTER_FULLTEXT_MORELIKE, value: this._moreLikeId?.toString()}) | ||||
|     if ( | ||||
|       this._moreLikeId && | ||||
|       this.textFilterTarget == TEXT_FILTER_TARGET_FULLTEXT_MORELIKE | ||||
|     ) { | ||||
|       filterRules.push({ | ||||
|         rule_type: FILTER_FULLTEXT_MORELIKE, | ||||
|         value: this._moreLikeId?.toString(), | ||||
|       }) | ||||
|     } | ||||
|     if (this.tagSelectionModel.isNoneSelected()) { | ||||
|       filterRules.push({rule_type: FILTER_HAS_ANY_TAG, value: "false"}) | ||||
|       filterRules.push({ rule_type: FILTER_HAS_ANY_TAG, value: 'false' }) | ||||
|     } else { | ||||
|       const tagFilterType = this.tagSelectionModel.logicalOperator == 'and' ? FILTER_HAS_TAGS_ALL : FILTER_HAS_TAGS_ANY | ||||
|       this.tagSelectionModel.getSelectedItems().filter(tag => tag.id).forEach(tag => { | ||||
|         filterRules.push({rule_type: tagFilterType, value: tag.id?.toString()}) | ||||
|       }) | ||||
|       this.tagSelectionModel.getExcludedItems().filter(tag => tag.id).forEach(tag => { | ||||
|         filterRules.push({rule_type: FILTER_DOES_NOT_HAVE_TAG, value: tag.id?.toString()}) | ||||
|       }) | ||||
|       const tagFilterType = | ||||
|         this.tagSelectionModel.logicalOperator == 'and' | ||||
|           ? FILTER_HAS_TAGS_ALL | ||||
|           : FILTER_HAS_TAGS_ANY | ||||
|       this.tagSelectionModel | ||||
|         .getSelectedItems() | ||||
|         .filter((tag) => tag.id) | ||||
|         .forEach((tag) => { | ||||
|           filterRules.push({ | ||||
|             rule_type: tagFilterType, | ||||
|             value: tag.id?.toString(), | ||||
|           }) | ||||
|         }) | ||||
|       this.tagSelectionModel | ||||
|         .getExcludedItems() | ||||
|         .filter((tag) => tag.id) | ||||
|         .forEach((tag) => { | ||||
|           filterRules.push({ | ||||
|             rule_type: FILTER_DOES_NOT_HAVE_TAG, | ||||
|             value: tag.id?.toString(), | ||||
|           }) | ||||
|         }) | ||||
|     } | ||||
|     this.correspondentSelectionModel.getSelectedItems().forEach(correspondent => { | ||||
|       filterRules.push({rule_type: FILTER_CORRESPONDENT, value: correspondent.id?.toString()}) | ||||
|     }) | ||||
|     this.documentTypeSelectionModel.getSelectedItems().forEach(documentType => { | ||||
|       filterRules.push({rule_type: FILTER_DOCUMENT_TYPE, value: documentType.id?.toString()}) | ||||
|     }) | ||||
|     this.correspondentSelectionModel | ||||
|       .getSelectedItems() | ||||
|       .forEach((correspondent) => { | ||||
|         filterRules.push({ | ||||
|           rule_type: FILTER_CORRESPONDENT, | ||||
|           value: correspondent.id?.toString(), | ||||
|         }) | ||||
|       }) | ||||
|     this.documentTypeSelectionModel | ||||
|       .getSelectedItems() | ||||
|       .forEach((documentType) => { | ||||
|         filterRules.push({ | ||||
|           rule_type: FILTER_DOCUMENT_TYPE, | ||||
|           value: documentType.id?.toString(), | ||||
|         }) | ||||
|       }) | ||||
|     if (this.dateCreatedBefore) { | ||||
|       filterRules.push({rule_type: FILTER_CREATED_BEFORE, value: this.dateCreatedBefore}) | ||||
|       filterRules.push({ | ||||
|         rule_type: FILTER_CREATED_BEFORE, | ||||
|         value: this.dateCreatedBefore, | ||||
|       }) | ||||
|     } | ||||
|     if (this.dateCreatedAfter) { | ||||
|       filterRules.push({rule_type: FILTER_CREATED_AFTER, value: this.dateCreatedAfter}) | ||||
|       filterRules.push({ | ||||
|         rule_type: FILTER_CREATED_AFTER, | ||||
|         value: this.dateCreatedAfter, | ||||
|       }) | ||||
|     } | ||||
|     if (this.dateAddedBefore) { | ||||
|       filterRules.push({rule_type: FILTER_ADDED_BEFORE, value: this.dateAddedBefore}) | ||||
|       filterRules.push({ | ||||
|         rule_type: FILTER_ADDED_BEFORE, | ||||
|         value: this.dateAddedBefore, | ||||
|       }) | ||||
|     } | ||||
|     if (this.dateAddedAfter) { | ||||
|       filterRules.push({rule_type: FILTER_ADDED_AFTER, value: this.dateAddedAfter}) | ||||
|       filterRules.push({ | ||||
|         rule_type: FILTER_ADDED_AFTER, | ||||
|         value: this.dateAddedAfter, | ||||
|       }) | ||||
|     } | ||||
|     return filterRules | ||||
|   } | ||||
| @@ -260,14 +373,20 @@ export class FilterEditorComponent implements OnInit, OnDestroy { | ||||
|     if (this._unmodifiedFilterRules.length != this._filterRules.length) { | ||||
|       modified = true | ||||
|     } else { | ||||
|       modified = this._unmodifiedFilterRules.some(rule => { | ||||
|         return (this._filterRules.find(fri => fri.rule_type == rule.rule_type && fri.value == rule.value) == undefined) | ||||
|       modified = this._unmodifiedFilterRules.some((rule) => { | ||||
|         return ( | ||||
|           this._filterRules.find( | ||||
|             (fri) => fri.rule_type == rule.rule_type && fri.value == rule.value | ||||
|           ) == undefined | ||||
|         ) | ||||
|       }) | ||||
|  | ||||
|       if (!modified) { | ||||
|         // only check other direction if we havent already determined is modified | ||||
|         modified = this._filterRules.some(rule => { | ||||
|           this._unmodifiedFilterRules.find(fr => fr.rule_type == rule.rule_type && fr.value == rule.value) == undefined | ||||
|         modified = this._filterRules.some((rule) => { | ||||
|           this._unmodifiedFilterRules.find( | ||||
|             (fr) => fr.rule_type == rule.rule_type && fr.value == rule.value | ||||
|           ) == undefined | ||||
|         }) | ||||
|       } | ||||
|     } | ||||
| @@ -290,23 +409,27 @@ export class FilterEditorComponent implements OnInit, OnDestroy { | ||||
|   subscription: Subscription | ||||
|  | ||||
|   ngOnInit() { | ||||
|     this.tagService.listAll().subscribe(result => this.tags = result.results) | ||||
|     this.correspondentService.listAll().subscribe(result => this.correspondents = result.results) | ||||
|     this.documentTypeService.listAll().subscribe(result => this.documentTypes = result.results) | ||||
|     this.tagService | ||||
|       .listAll() | ||||
|       .subscribe((result) => (this.tags = result.results)) | ||||
|     this.correspondentService | ||||
|       .listAll() | ||||
|       .subscribe((result) => (this.correspondents = result.results)) | ||||
|     this.documentTypeService | ||||
|       .listAll() | ||||
|       .subscribe((result) => (this.documentTypes = result.results)) | ||||
|  | ||||
|     this.textFilterDebounce = new Subject<string>() | ||||
|  | ||||
|     this.subscription = this.textFilterDebounce.pipe( | ||||
|       debounceTime(400), | ||||
|       distinctUntilChanged() | ||||
|     ).subscribe(text => { | ||||
|       this._textFilter = text | ||||
|       this.documentService.searchQuery = text | ||||
|       this.updateRules() | ||||
|     }) | ||||
|     this.subscription = this.textFilterDebounce | ||||
|       .pipe(debounceTime(400), distinctUntilChanged()) | ||||
|       .subscribe((text) => { | ||||
|         this._textFilter = text | ||||
|         this.documentService.searchQuery = text | ||||
|         this.updateRules() | ||||
|       }) | ||||
|  | ||||
|     if (this._textFilter) this.documentService.searchQuery = this._textFilter | ||||
|  | ||||
|   } | ||||
|  | ||||
|   ngOnDestroy() { | ||||
| @@ -324,11 +447,17 @@ export class FilterEditorComponent implements OnInit, OnDestroy { | ||||
|   } | ||||
|  | ||||
|   addCorrespondent(correspondentId: number) { | ||||
|     this.correspondentSelectionModel.set(correspondentId, ToggleableItemState.Selected) | ||||
|     this.correspondentSelectionModel.set( | ||||
|       correspondentId, | ||||
|       ToggleableItemState.Selected | ||||
|     ) | ||||
|   } | ||||
|  | ||||
|   addDocumentType(documentTypeId: number) { | ||||
|     this.documentTypeSelectionModel.set(documentTypeId, ToggleableItemState.Selected) | ||||
|     this.documentTypeSelectionModel.set( | ||||
|       documentTypeId, | ||||
|       ToggleableItemState.Selected | ||||
|     ) | ||||
|   } | ||||
|  | ||||
|   onTagsDropdownOpen() { | ||||
| @@ -344,8 +473,11 @@ export class FilterEditorComponent implements OnInit, OnDestroy { | ||||
|   } | ||||
|  | ||||
|   changeTextFilterTarget(target) { | ||||
|     if (this.textFilterTarget == TEXT_FILTER_TARGET_FULLTEXT_MORELIKE && target != TEXT_FILTER_TARGET_FULLTEXT_MORELIKE) { | ||||
|       this._textFilter = "" | ||||
|     if ( | ||||
|       this.textFilterTarget == TEXT_FILTER_TARGET_FULLTEXT_MORELIKE && | ||||
|       target != TEXT_FILTER_TARGET_FULLTEXT_MORELIKE | ||||
|     ) { | ||||
|       this._textFilter = '' | ||||
|     } | ||||
|     this.textFilterTarget = target | ||||
|     this.textFilterInput.nativeElement.focus() | ||||
|   | ||||
| @@ -1,25 +1,24 @@ | ||||
| import { ComponentFixture, TestBed } from '@angular/core/testing'; | ||||
| import { ComponentFixture, TestBed } from '@angular/core/testing' | ||||
|  | ||||
| import { SaveViewConfigDialogComponent } from './save-view-config-dialog.component'; | ||||
| import { SaveViewConfigDialogComponent } from './save-view-config-dialog.component' | ||||
|  | ||||
| describe('SaveViewConfigDialogComponent', () => { | ||||
|   let component: SaveViewConfigDialogComponent; | ||||
|   let fixture: ComponentFixture<SaveViewConfigDialogComponent>; | ||||
|   let component: SaveViewConfigDialogComponent | ||||
|   let fixture: ComponentFixture<SaveViewConfigDialogComponent> | ||||
|  | ||||
|   beforeEach(async () => { | ||||
|     await TestBed.configureTestingModule({ | ||||
|       declarations: [ SaveViewConfigDialogComponent ] | ||||
|     }) | ||||
|     .compileComponents(); | ||||
|   }); | ||||
|       declarations: [SaveViewConfigDialogComponent], | ||||
|     }).compileComponents() | ||||
|   }) | ||||
|  | ||||
|   beforeEach(() => { | ||||
|     fixture = TestBed.createComponent(SaveViewConfigDialogComponent); | ||||
|     component = fixture.componentInstance; | ||||
|     fixture.detectChanges(); | ||||
|   }); | ||||
|     fixture = TestBed.createComponent(SaveViewConfigDialogComponent) | ||||
|     component = fixture.componentInstance | ||||
|     fixture.detectChanges() | ||||
|   }) | ||||
|  | ||||
|   it('should create', () => { | ||||
|     expect(component).toBeTruthy(); | ||||
|   }); | ||||
| }); | ||||
|     expect(component).toBeTruthy() | ||||
|   }) | ||||
| }) | ||||
|   | ||||
| @@ -1,15 +1,14 @@ | ||||
| import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; | ||||
| import { FormControl, FormGroup } from '@angular/forms'; | ||||
| import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; | ||||
| import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core' | ||||
| import { FormControl, FormGroup } from '@angular/forms' | ||||
| import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap' | ||||
|  | ||||
| @Component({ | ||||
|   selector: 'app-save-view-config-dialog', | ||||
|   templateUrl: './save-view-config-dialog.component.html', | ||||
|   styleUrls: ['./save-view-config-dialog.component.scss'] | ||||
|   styleUrls: ['./save-view-config-dialog.component.scss'], | ||||
| }) | ||||
| export class SaveViewConfigDialogComponent implements OnInit { | ||||
|  | ||||
|   constructor(private modal: NgbActiveModal) { } | ||||
|   constructor(private modal: NgbActiveModal) {} | ||||
|  | ||||
|   @Output() | ||||
|   public saveClicked = new EventEmitter() | ||||
| @@ -22,7 +21,7 @@ export class SaveViewConfigDialogComponent implements OnInit { | ||||
|  | ||||
|   closeEnabled = false | ||||
|  | ||||
|   _defaultName = "" | ||||
|   _defaultName = '' | ||||
|  | ||||
|   get defaultName() { | ||||
|     return this._defaultName | ||||
| @@ -31,7 +30,7 @@ export class SaveViewConfigDialogComponent implements OnInit { | ||||
|   @Input() | ||||
|   set defaultName(value: string) { | ||||
|     this._defaultName = value | ||||
|     this.saveViewConfigForm.patchValue({name: value}) | ||||
|     this.saveViewConfigForm.patchValue({ name: value }) | ||||
|   } | ||||
|  | ||||
|   saveViewConfigForm = new FormGroup({ | ||||
| @@ -44,7 +43,7 @@ export class SaveViewConfigDialogComponent implements OnInit { | ||||
|     // wait to enable close button so it doesnt steal focus from input since its the first clickable element in the DOM | ||||
|     setTimeout(() => { | ||||
|       this.closeEnabled = true | ||||
|     }); | ||||
|     }) | ||||
|   } | ||||
|  | ||||
|   save() { | ||||
|   | ||||
| @@ -1,25 +1,24 @@ | ||||
| import { ComponentFixture, TestBed } from '@angular/core/testing'; | ||||
| import { ComponentFixture, TestBed } from '@angular/core/testing' | ||||
|  | ||||
| import { CorrespondentEditDialogComponent } from './correspondent-edit-dialog.component'; | ||||
| import { CorrespondentEditDialogComponent } from './correspondent-edit-dialog.component' | ||||
|  | ||||
| describe('CorrespondentEditDialogComponent', () => { | ||||
|   let component: CorrespondentEditDialogComponent; | ||||
|   let fixture: ComponentFixture<CorrespondentEditDialogComponent>; | ||||
|   let component: CorrespondentEditDialogComponent | ||||
|   let fixture: ComponentFixture<CorrespondentEditDialogComponent> | ||||
|  | ||||
|   beforeEach(async () => { | ||||
|     await TestBed.configureTestingModule({ | ||||
|       declarations: [ CorrespondentEditDialogComponent ] | ||||
|     }) | ||||
|     .compileComponents(); | ||||
|   }); | ||||
|       declarations: [CorrespondentEditDialogComponent], | ||||
|     }).compileComponents() | ||||
|   }) | ||||
|  | ||||
|   beforeEach(() => { | ||||
|     fixture = TestBed.createComponent(CorrespondentEditDialogComponent); | ||||
|     component = fixture.componentInstance; | ||||
|     fixture.detectChanges(); | ||||
|   }); | ||||
|     fixture = TestBed.createComponent(CorrespondentEditDialogComponent) | ||||
|     component = fixture.componentInstance | ||||
|     fixture.detectChanges() | ||||
|   }) | ||||
|  | ||||
|   it('should create', () => { | ||||
|     expect(component).toBeTruthy(); | ||||
|   }); | ||||
| }); | ||||
|     expect(component).toBeTruthy() | ||||
|   }) | ||||
| }) | ||||
|   | ||||
| @@ -1,19 +1,22 @@ | ||||
| import { Component } from '@angular/core'; | ||||
| import { FormControl, FormGroup } from '@angular/forms'; | ||||
| import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; | ||||
| import { EditDialogComponent } from 'src/app/components/common/edit-dialog/edit-dialog.component'; | ||||
| import { PaperlessCorrespondent } from 'src/app/data/paperless-correspondent'; | ||||
| import { CorrespondentService } from 'src/app/services/rest/correspondent.service'; | ||||
| import { ToastService } from 'src/app/services/toast.service'; | ||||
| import { Component } from '@angular/core' | ||||
| import { FormControl, FormGroup } from '@angular/forms' | ||||
| import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap' | ||||
| import { EditDialogComponent } from 'src/app/components/common/edit-dialog/edit-dialog.component' | ||||
| import { PaperlessCorrespondent } from 'src/app/data/paperless-correspondent' | ||||
| import { CorrespondentService } from 'src/app/services/rest/correspondent.service' | ||||
| import { ToastService } from 'src/app/services/toast.service' | ||||
|  | ||||
| @Component({ | ||||
|   selector: 'app-correspondent-edit-dialog', | ||||
|   templateUrl: './correspondent-edit-dialog.component.html', | ||||
|   styleUrls: ['./correspondent-edit-dialog.component.scss'] | ||||
|   styleUrls: ['./correspondent-edit-dialog.component.scss'], | ||||
| }) | ||||
| export class CorrespondentEditDialogComponent extends EditDialogComponent<PaperlessCorrespondent> { | ||||
|  | ||||
|   constructor(service: CorrespondentService, activeModal: NgbActiveModal, toastService: ToastService) { | ||||
|   constructor( | ||||
|     service: CorrespondentService, | ||||
|     activeModal: NgbActiveModal, | ||||
|     toastService: ToastService | ||||
|   ) { | ||||
|     super(service, activeModal, toastService) | ||||
|   } | ||||
|  | ||||
| @@ -29,9 +32,8 @@ export class CorrespondentEditDialogComponent extends EditDialogComponent<Paperl | ||||
|     return new FormGroup({ | ||||
|       name: new FormControl(''), | ||||
|       matching_algorithm: new FormControl(1), | ||||
|       match: new FormControl(""), | ||||
|       is_insensitive: new FormControl(true) | ||||
|       match: new FormControl(''), | ||||
|       is_insensitive: new FormControl(true), | ||||
|     }) | ||||
|   } | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -1,25 +1,24 @@ | ||||
| import { ComponentFixture, TestBed } from '@angular/core/testing'; | ||||
| import { ComponentFixture, TestBed } from '@angular/core/testing' | ||||
|  | ||||
| import { CorrespondentListComponent } from './correspondent-list.component'; | ||||
| import { CorrespondentListComponent } from './correspondent-list.component' | ||||
|  | ||||
| describe('CorrespondentListComponent', () => { | ||||
|   let component: CorrespondentListComponent; | ||||
|   let fixture: ComponentFixture<CorrespondentListComponent>; | ||||
|   let component: CorrespondentListComponent | ||||
|   let fixture: ComponentFixture<CorrespondentListComponent> | ||||
|  | ||||
|   beforeEach(async () => { | ||||
|     await TestBed.configureTestingModule({ | ||||
|       declarations: [ CorrespondentListComponent ] | ||||
|     }) | ||||
|     .compileComponents(); | ||||
|   }); | ||||
|       declarations: [CorrespondentListComponent], | ||||
|     }).compileComponents() | ||||
|   }) | ||||
|  | ||||
|   beforeEach(() => { | ||||
|     fixture = TestBed.createComponent(CorrespondentListComponent); | ||||
|     component = fixture.componentInstance; | ||||
|     fixture.detectChanges(); | ||||
|   }); | ||||
|     fixture = TestBed.createComponent(CorrespondentListComponent) | ||||
|     component = fixture.componentInstance | ||||
|     fixture.detectChanges() | ||||
|   }) | ||||
|  | ||||
|   it('should create', () => { | ||||
|     expect(component).toBeTruthy(); | ||||
|   }); | ||||
| }); | ||||
|     expect(component).toBeTruthy() | ||||
|   }) | ||||
| }) | ||||
|   | ||||
| @@ -1,25 +1,31 @@ | ||||
| import { Component } from '@angular/core'; | ||||
| import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; | ||||
| import { FILTER_CORRESPONDENT } from 'src/app/data/filter-rule-type'; | ||||
| import { PaperlessCorrespondent } from 'src/app/data/paperless-correspondent'; | ||||
| import { DocumentListViewService } from 'src/app/services/document-list-view.service'; | ||||
| import { CorrespondentService } from 'src/app/services/rest/correspondent.service'; | ||||
| import { ToastService } from 'src/app/services/toast.service'; | ||||
| import { GenericListComponent } from '../generic-list/generic-list.component'; | ||||
| import { CorrespondentEditDialogComponent } from './correspondent-edit-dialog/correspondent-edit-dialog.component'; | ||||
| import { Component } from '@angular/core' | ||||
| import { NgbModal } from '@ng-bootstrap/ng-bootstrap' | ||||
| import { FILTER_CORRESPONDENT } from 'src/app/data/filter-rule-type' | ||||
| import { PaperlessCorrespondent } from 'src/app/data/paperless-correspondent' | ||||
| import { DocumentListViewService } from 'src/app/services/document-list-view.service' | ||||
| import { CorrespondentService } from 'src/app/services/rest/correspondent.service' | ||||
| import { ToastService } from 'src/app/services/toast.service' | ||||
| import { GenericListComponent } from '../generic-list/generic-list.component' | ||||
| import { CorrespondentEditDialogComponent } from './correspondent-edit-dialog/correspondent-edit-dialog.component' | ||||
|  | ||||
| @Component({ | ||||
|   selector: 'app-correspondent-list', | ||||
|   templateUrl: './correspondent-list.component.html', | ||||
|   styleUrls: ['./correspondent-list.component.scss'] | ||||
|   styleUrls: ['./correspondent-list.component.scss'], | ||||
| }) | ||||
| export class CorrespondentListComponent extends GenericListComponent<PaperlessCorrespondent> { | ||||
|  | ||||
|   constructor(correspondentsService: CorrespondentService, modalService: NgbModal, | ||||
|   constructor( | ||||
|     correspondentsService: CorrespondentService, | ||||
|     modalService: NgbModal, | ||||
|     private list: DocumentListViewService, | ||||
|     toastService: ToastService | ||||
|   ) { | ||||
|     super(correspondentsService,modalService,CorrespondentEditDialogComponent, toastService) | ||||
|     super( | ||||
|       correspondentsService, | ||||
|       modalService, | ||||
|       CorrespondentEditDialogComponent, | ||||
|       toastService | ||||
|     ) | ||||
|   } | ||||
|  | ||||
|   getDeleteMessage(object: PaperlessCorrespondent) { | ||||
| @@ -27,6 +33,8 @@ export class CorrespondentListComponent extends GenericListComponent<PaperlessCo | ||||
|   } | ||||
|  | ||||
|   filterDocuments(object: PaperlessCorrespondent) { | ||||
|     this.list.quickFilter([{rule_type: FILTER_CORRESPONDENT, value: object.id.toString()}]) | ||||
|     this.list.quickFilter([ | ||||
|       { rule_type: FILTER_CORRESPONDENT, value: object.id.toString() }, | ||||
|     ]) | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -1,25 +1,24 @@ | ||||
| import { ComponentFixture, TestBed } from '@angular/core/testing'; | ||||
| import { ComponentFixture, TestBed } from '@angular/core/testing' | ||||
|  | ||||
| import { DocumentTypeEditDialogComponent } from './document-type-edit-dialog.component'; | ||||
| import { DocumentTypeEditDialogComponent } from './document-type-edit-dialog.component' | ||||
|  | ||||
| describe('DocumentTypeEditDialogComponent', () => { | ||||
|   let component: DocumentTypeEditDialogComponent; | ||||
|   let fixture: ComponentFixture<DocumentTypeEditDialogComponent>; | ||||
|   let component: DocumentTypeEditDialogComponent | ||||
|   let fixture: ComponentFixture<DocumentTypeEditDialogComponent> | ||||
|  | ||||
|   beforeEach(async () => { | ||||
|     await TestBed.configureTestingModule({ | ||||
|       declarations: [ DocumentTypeEditDialogComponent ] | ||||
|     }) | ||||
|     .compileComponents(); | ||||
|   }); | ||||
|       declarations: [DocumentTypeEditDialogComponent], | ||||
|     }).compileComponents() | ||||
|   }) | ||||
|  | ||||
|   beforeEach(() => { | ||||
|     fixture = TestBed.createComponent(DocumentTypeEditDialogComponent); | ||||
|     component = fixture.componentInstance; | ||||
|     fixture.detectChanges(); | ||||
|   }); | ||||
|     fixture = TestBed.createComponent(DocumentTypeEditDialogComponent) | ||||
|     component = fixture.componentInstance | ||||
|     fixture.detectChanges() | ||||
|   }) | ||||
|  | ||||
|   it('should create', () => { | ||||
|     expect(component).toBeTruthy(); | ||||
|   }); | ||||
| }); | ||||
|     expect(component).toBeTruthy() | ||||
|   }) | ||||
| }) | ||||
|   | ||||
| @@ -1,19 +1,22 @@ | ||||
| import { Component } from '@angular/core'; | ||||
| import { FormControl, FormGroup } from '@angular/forms'; | ||||
| import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; | ||||
| import { EditDialogComponent } from 'src/app/components/common/edit-dialog/edit-dialog.component'; | ||||
| import { PaperlessDocumentType } from 'src/app/data/paperless-document-type'; | ||||
| import { DocumentTypeService } from 'src/app/services/rest/document-type.service'; | ||||
| import { ToastService } from 'src/app/services/toast.service'; | ||||
| import { Component } from '@angular/core' | ||||
| import { FormControl, FormGroup } from '@angular/forms' | ||||
| import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap' | ||||
| import { EditDialogComponent } from 'src/app/components/common/edit-dialog/edit-dialog.component' | ||||
| import { PaperlessDocumentType } from 'src/app/data/paperless-document-type' | ||||
| import { DocumentTypeService } from 'src/app/services/rest/document-type.service' | ||||
| import { ToastService } from 'src/app/services/toast.service' | ||||
|  | ||||
| @Component({ | ||||
|   selector: 'app-document-type-edit-dialog', | ||||
|   templateUrl: './document-type-edit-dialog.component.html', | ||||
|   styleUrls: ['./document-type-edit-dialog.component.scss'] | ||||
|   styleUrls: ['./document-type-edit-dialog.component.scss'], | ||||
| }) | ||||
| export class DocumentTypeEditDialogComponent extends EditDialogComponent<PaperlessDocumentType> { | ||||
|  | ||||
|   constructor(service: DocumentTypeService, activeModal: NgbActiveModal, toastService: ToastService) { | ||||
|   constructor( | ||||
|     service: DocumentTypeService, | ||||
|     activeModal: NgbActiveModal, | ||||
|     toastService: ToastService | ||||
|   ) { | ||||
|     super(service, activeModal, toastService) | ||||
|   } | ||||
|  | ||||
| @@ -29,9 +32,8 @@ export class DocumentTypeEditDialogComponent extends EditDialogComponent<Paperle | ||||
|     return new FormGroup({ | ||||
|       name: new FormControl(''), | ||||
|       matching_algorithm: new FormControl(1), | ||||
|       match: new FormControl(""), | ||||
|       is_insensitive: new FormControl(true) | ||||
|       match: new FormControl(''), | ||||
|       is_insensitive: new FormControl(true), | ||||
|     }) | ||||
|   } | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -1,25 +1,24 @@ | ||||
| import { ComponentFixture, TestBed } from '@angular/core/testing'; | ||||
| import { ComponentFixture, TestBed } from '@angular/core/testing' | ||||
|  | ||||
| import { DocumentTypeListComponent } from './document-type-list.component'; | ||||
| import { DocumentTypeListComponent } from './document-type-list.component' | ||||
|  | ||||
| describe('DocumentTypeListComponent', () => { | ||||
|   let component: DocumentTypeListComponent; | ||||
|   let fixture: ComponentFixture<DocumentTypeListComponent>; | ||||
|   let component: DocumentTypeListComponent | ||||
|   let fixture: ComponentFixture<DocumentTypeListComponent> | ||||
|  | ||||
|   beforeEach(async () => { | ||||
|     await TestBed.configureTestingModule({ | ||||
|       declarations: [ DocumentTypeListComponent ] | ||||
|     }) | ||||
|     .compileComponents(); | ||||
|   }); | ||||
|       declarations: [DocumentTypeListComponent], | ||||
|     }).compileComponents() | ||||
|   }) | ||||
|  | ||||
|   beforeEach(() => { | ||||
|     fixture = TestBed.createComponent(DocumentTypeListComponent); | ||||
|     component = fixture.componentInstance; | ||||
|     fixture.detectChanges(); | ||||
|   }); | ||||
|     fixture = TestBed.createComponent(DocumentTypeListComponent) | ||||
|     component = fixture.componentInstance | ||||
|     fixture.detectChanges() | ||||
|   }) | ||||
|  | ||||
|   it('should create', () => { | ||||
|     expect(component).toBeTruthy(); | ||||
|   }); | ||||
| }); | ||||
|     expect(component).toBeTruthy() | ||||
|   }) | ||||
| }) | ||||
|   | ||||
| @@ -1,21 +1,22 @@ | ||||
| import { Component } from '@angular/core'; | ||||
| import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; | ||||
| import { FILTER_DOCUMENT_TYPE } from 'src/app/data/filter-rule-type'; | ||||
| import { PaperlessDocumentType } from 'src/app/data/paperless-document-type'; | ||||
| import { DocumentListViewService } from 'src/app/services/document-list-view.service'; | ||||
| import { DocumentTypeService } from 'src/app/services/rest/document-type.service'; | ||||
| import { ToastService } from 'src/app/services/toast.service'; | ||||
| import { GenericListComponent } from '../generic-list/generic-list.component'; | ||||
| import { DocumentTypeEditDialogComponent } from './document-type-edit-dialog/document-type-edit-dialog.component'; | ||||
| import { Component } from '@angular/core' | ||||
| import { NgbModal } from '@ng-bootstrap/ng-bootstrap' | ||||
| import { FILTER_DOCUMENT_TYPE } from 'src/app/data/filter-rule-type' | ||||
| import { PaperlessDocumentType } from 'src/app/data/paperless-document-type' | ||||
| import { DocumentListViewService } from 'src/app/services/document-list-view.service' | ||||
| import { DocumentTypeService } from 'src/app/services/rest/document-type.service' | ||||
| import { ToastService } from 'src/app/services/toast.service' | ||||
| import { GenericListComponent } from '../generic-list/generic-list.component' | ||||
| import { DocumentTypeEditDialogComponent } from './document-type-edit-dialog/document-type-edit-dialog.component' | ||||
|  | ||||
| @Component({ | ||||
|   selector: 'app-document-type-list', | ||||
|   templateUrl: './document-type-list.component.html', | ||||
|   styleUrls: ['./document-type-list.component.scss'] | ||||
|   styleUrls: ['./document-type-list.component.scss'], | ||||
| }) | ||||
| export class DocumentTypeListComponent extends GenericListComponent<PaperlessDocumentType> { | ||||
|  | ||||
|   constructor(service: DocumentTypeService, modalService: NgbModal, | ||||
|   constructor( | ||||
|     service: DocumentTypeService, | ||||
|     modalService: NgbModal, | ||||
|     private list: DocumentListViewService, | ||||
|     toastService: ToastService | ||||
|   ) { | ||||
| @@ -26,8 +27,9 @@ export class DocumentTypeListComponent extends GenericListComponent<PaperlessDoc | ||||
|     return $localize`Do you really want to delete the document type "${object.name}"?` | ||||
|   } | ||||
|  | ||||
|  | ||||
|   filterDocuments(object: PaperlessDocumentType) { | ||||
|     this.list.quickFilter([{rule_type: FILTER_DOCUMENT_TYPE, value: object.id.toString()}]) | ||||
|     this.list.quickFilter([ | ||||
|       { rule_type: FILTER_DOCUMENT_TYPE, value: object.id.toString() }, | ||||
|     ]) | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -1,25 +1,24 @@ | ||||
| import { ComponentFixture, TestBed } from '@angular/core/testing'; | ||||
| import { ComponentFixture, TestBed } from '@angular/core/testing' | ||||
|  | ||||
| import { GenericListComponent } from './generic-list.component'; | ||||
| import { GenericListComponent } from './generic-list.component' | ||||
|  | ||||
| describe('GenericListComponent', () => { | ||||
|   let component: GenericListComponent; | ||||
|   let fixture: ComponentFixture<GenericListComponent>; | ||||
|   let component: GenericListComponent | ||||
|   let fixture: ComponentFixture<GenericListComponent> | ||||
|  | ||||
|   beforeEach(async () => { | ||||
|     await TestBed.configureTestingModule({ | ||||
|       declarations: [ GenericListComponent ] | ||||
|     }) | ||||
|     .compileComponents(); | ||||
|   }); | ||||
|       declarations: [GenericListComponent], | ||||
|     }).compileComponents() | ||||
|   }) | ||||
|  | ||||
|   beforeEach(() => { | ||||
|     fixture = TestBed.createComponent(GenericListComponent); | ||||
|     component = fixture.componentInstance; | ||||
|     fixture.detectChanges(); | ||||
|   }); | ||||
|     fixture = TestBed.createComponent(GenericListComponent) | ||||
|     component = fixture.componentInstance | ||||
|     fixture.detectChanges() | ||||
|   }) | ||||
|  | ||||
|   it('should create', () => { | ||||
|     expect(component).toBeTruthy(); | ||||
|   }); | ||||
| }); | ||||
|     expect(component).toBeTruthy() | ||||
|   }) | ||||
| }) | ||||
|   | ||||
| @@ -1,25 +1,39 @@ | ||||
| import { Directive, OnDestroy, OnInit, QueryList, ViewChildren } from '@angular/core'; | ||||
| import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; | ||||
| import { Subject, Subscription } from 'rxjs'; | ||||
| import { debounceTime, distinctUntilChanged } from 'rxjs/operators'; | ||||
| import { MatchingModel, MATCHING_ALGORITHMS, MATCH_AUTO } from 'src/app/data/matching-model'; | ||||
| import { ObjectWithId } from 'src/app/data/object-with-id'; | ||||
| import { SortableDirective, SortEvent } from 'src/app/directives/sortable.directive'; | ||||
| import { AbstractNameFilterService } from 'src/app/services/rest/abstract-name-filter-service'; | ||||
| import { ToastService } from 'src/app/services/toast.service'; | ||||
| import { ConfirmDialogComponent } from '../../common/confirm-dialog/confirm-dialog.component'; | ||||
| import { | ||||
|   Directive, | ||||
|   OnDestroy, | ||||
|   OnInit, | ||||
|   QueryList, | ||||
|   ViewChildren, | ||||
| } from '@angular/core' | ||||
| import { NgbModal } from '@ng-bootstrap/ng-bootstrap' | ||||
| import { Subject, Subscription } from 'rxjs' | ||||
| import { debounceTime, distinctUntilChanged } from 'rxjs/operators' | ||||
| import { | ||||
|   MatchingModel, | ||||
|   MATCHING_ALGORITHMS, | ||||
|   MATCH_AUTO, | ||||
| } from 'src/app/data/matching-model' | ||||
| import { ObjectWithId } from 'src/app/data/object-with-id' | ||||
| import { | ||||
|   SortableDirective, | ||||
|   SortEvent, | ||||
| } from 'src/app/directives/sortable.directive' | ||||
| import { AbstractNameFilterService } from 'src/app/services/rest/abstract-name-filter-service' | ||||
| import { ToastService } from 'src/app/services/toast.service' | ||||
| import { ConfirmDialogComponent } from '../../common/confirm-dialog/confirm-dialog.component' | ||||
|  | ||||
| @Directive() | ||||
| export abstract class GenericListComponent<T extends ObjectWithId> implements OnInit, OnDestroy { | ||||
|  | ||||
| export abstract class GenericListComponent<T extends ObjectWithId> | ||||
|   implements OnInit, OnDestroy | ||||
| { | ||||
|   constructor( | ||||
|     private service: AbstractNameFilterService<T>, | ||||
|     private modalService: NgbModal, | ||||
|     private editDialogComponent: any, | ||||
|     private toastService: ToastService) { | ||||
|     } | ||||
|     private toastService: ToastService | ||||
|   ) {} | ||||
|  | ||||
|   @ViewChildren(SortableDirective) headers: QueryList<SortableDirective>; | ||||
|   @ViewChildren(SortableDirective) headers: QueryList<SortableDirective> | ||||
|  | ||||
|   public data: T[] = [] | ||||
|  | ||||
| @@ -38,9 +52,11 @@ export abstract class GenericListComponent<T extends ObjectWithId> implements On | ||||
|     if (o.matching_algorithm == MATCH_AUTO) { | ||||
|       return $localize`Automatic` | ||||
|     } else if (o.match && o.match.length > 0) { | ||||
|       return `${MATCHING_ALGORITHMS.find(a => a.id == o.matching_algorithm).shortName}: ${o.match}` | ||||
|       return `${ | ||||
|         MATCHING_ALGORITHMS.find((a) => a.id == o.matching_algorithm).shortName | ||||
|       }: ${o.match}` | ||||
|     } else { | ||||
|       return "-" | ||||
|       return '-' | ||||
|     } | ||||
|   } | ||||
|  | ||||
| @@ -50,20 +66,18 @@ export abstract class GenericListComponent<T extends ObjectWithId> implements On | ||||
|     this.reloadData() | ||||
|   } | ||||
|  | ||||
|  | ||||
|   ngOnInit(): void { | ||||
|     this.reloadData() | ||||
|  | ||||
|     this.nameFilterDebounce = new Subject<string>() | ||||
|  | ||||
|     this.subscription = this.nameFilterDebounce.pipe( | ||||
|       debounceTime(400), | ||||
|       distinctUntilChanged() | ||||
|     ).subscribe(title => { | ||||
|       this._nameFilter = title | ||||
|       this.page = 1 | ||||
|       this.reloadData() | ||||
|     }) | ||||
|     this.subscription = this.nameFilterDebounce | ||||
|       .pipe(debounceTime(400), distinctUntilChanged()) | ||||
|       .subscribe((title) => { | ||||
|         this._nameFilter = title | ||||
|         this.page = 1 | ||||
|         this.reloadData() | ||||
|       }) | ||||
|   } | ||||
|  | ||||
|   ngOnDestroy() { | ||||
| @@ -71,25 +85,37 @@ export abstract class GenericListComponent<T extends ObjectWithId> implements On | ||||
|   } | ||||
|  | ||||
|   reloadData() { | ||||
|     this.service.listFiltered(this.page, null, this.sortField, this.sortReverse, this._nameFilter).subscribe(c => { | ||||
|       this.data = c.results | ||||
|       this.collectionSize = c.count | ||||
|     }); | ||||
|     this.service | ||||
|       .listFiltered( | ||||
|         this.page, | ||||
|         null, | ||||
|         this.sortField, | ||||
|         this.sortReverse, | ||||
|         this._nameFilter | ||||
|       ) | ||||
|       .subscribe((c) => { | ||||
|         this.data = c.results | ||||
|         this.collectionSize = c.count | ||||
|       }) | ||||
|   } | ||||
|  | ||||
|   openCreateDialog() { | ||||
|     var activeModal = this.modalService.open(this.editDialogComponent, {backdrop: 'static'}) | ||||
|     var activeModal = this.modalService.open(this.editDialogComponent, { | ||||
|       backdrop: 'static', | ||||
|     }) | ||||
|     activeModal.componentInstance.dialogMode = 'create' | ||||
|     activeModal.componentInstance.success.subscribe(o => { | ||||
|     activeModal.componentInstance.success.subscribe((o) => { | ||||
|       this.reloadData() | ||||
|     }) | ||||
|   } | ||||
|  | ||||
|   openEditDialog(object: T) { | ||||
|     var activeModal = this.modalService.open(this.editDialogComponent, {backdrop: 'static'}) | ||||
|     var activeModal = this.modalService.open(this.editDialogComponent, { | ||||
|       backdrop: 'static', | ||||
|     }) | ||||
|     activeModal.componentInstance.object = object | ||||
|     activeModal.componentInstance.dialogMode = 'edit' | ||||
|     activeModal.componentInstance.success.subscribe(o => { | ||||
|     activeModal.componentInstance.success.subscribe((o) => { | ||||
|       this.reloadData() | ||||
|     }) | ||||
|   } | ||||
| @@ -99,23 +125,31 @@ export abstract class GenericListComponent<T extends ObjectWithId> implements On | ||||
|   } | ||||
|  | ||||
|   openDeleteDialog(object: T) { | ||||
|     var activeModal = this.modalService.open(ConfirmDialogComponent, {backdrop: 'static'}) | ||||
|     var activeModal = this.modalService.open(ConfirmDialogComponent, { | ||||
|       backdrop: 'static', | ||||
|     }) | ||||
|     activeModal.componentInstance.title = $localize`Confirm delete` | ||||
|     activeModal.componentInstance.messageBold = this.getDeleteMessage(object) | ||||
|     activeModal.componentInstance.message = $localize`Associated documents will not be deleted.` | ||||
|     activeModal.componentInstance.btnClass = "btn-danger" | ||||
|     activeModal.componentInstance.btnClass = 'btn-danger' | ||||
|     activeModal.componentInstance.btnCaption = $localize`Delete` | ||||
|     activeModal.componentInstance.confirmClicked.subscribe(() => { | ||||
|       activeModal.componentInstance.buttonsEnabled = false | ||||
|       this.service.delete(object).subscribe(_ => { | ||||
|         activeModal.close() | ||||
|         this.reloadData() | ||||
|       }, error => { | ||||
|         activeModal.componentInstance.buttonsEnabled = true | ||||
|         this.toastService.showError($localize`Error while deleting element: ${JSON.stringify(error.error)}`) | ||||
|       }) | ||||
|     } | ||||
|     ) | ||||
|       this.service.delete(object).subscribe( | ||||
|         (_) => { | ||||
|           activeModal.close() | ||||
|           this.reloadData() | ||||
|         }, | ||||
|         (error) => { | ||||
|           activeModal.componentInstance.buttonsEnabled = true | ||||
|           this.toastService.showError( | ||||
|             $localize`Error while deleting element: ${JSON.stringify( | ||||
|               error.error | ||||
|             )}` | ||||
|           ) | ||||
|         } | ||||
|       ) | ||||
|     }) | ||||
|   } | ||||
|  | ||||
|   get nameFilter() { | ||||
| @@ -125,7 +159,7 @@ export abstract class GenericListComponent<T extends ObjectWithId> implements On | ||||
|   set nameFilter(nameFilter: string) { | ||||
|     this.nameFilterDebounce.next(nameFilter) | ||||
|   } | ||||
|    | ||||
|  | ||||
|   onNameFilterKeyUp(event: KeyboardEvent) { | ||||
|     if (event.code == 'Escape') this.nameFilterDebounce.next(null) | ||||
|   } | ||||
|   | ||||
| @@ -1,25 +1,24 @@ | ||||
| import { ComponentFixture, TestBed } from '@angular/core/testing'; | ||||
| import { ComponentFixture, TestBed } from '@angular/core/testing' | ||||
|  | ||||
| import { LogsComponent } from './logs.component'; | ||||
| import { LogsComponent } from './logs.component' | ||||
|  | ||||
| describe('LogsComponent', () => { | ||||
|   let component: LogsComponent; | ||||
|   let fixture: ComponentFixture<LogsComponent>; | ||||
|   let component: LogsComponent | ||||
|   let fixture: ComponentFixture<LogsComponent> | ||||
|  | ||||
|   beforeEach(async () => { | ||||
|     await TestBed.configureTestingModule({ | ||||
|       declarations: [ LogsComponent ] | ||||
|     }) | ||||
|     .compileComponents(); | ||||
|   }); | ||||
|       declarations: [LogsComponent], | ||||
|     }).compileComponents() | ||||
|   }) | ||||
|  | ||||
|   beforeEach(() => { | ||||
|     fixture = TestBed.createComponent(LogsComponent); | ||||
|     component = fixture.componentInstance; | ||||
|     fixture.detectChanges(); | ||||
|   }); | ||||
|     fixture = TestBed.createComponent(LogsComponent) | ||||
|     component = fixture.componentInstance | ||||
|     fixture.detectChanges() | ||||
|   }) | ||||
|  | ||||
|   it('should create', () => { | ||||
|     expect(component).toBeTruthy(); | ||||
|   }); | ||||
| }); | ||||
|     expect(component).toBeTruthy() | ||||
|   }) | ||||
| }) | ||||
|   | ||||
| @@ -1,14 +1,19 @@ | ||||
| import { Component, ElementRef, OnInit, AfterViewChecked, ViewChild } from '@angular/core'; | ||||
| import { LogService } from 'src/app/services/rest/log.service'; | ||||
| import { | ||||
|   Component, | ||||
|   ElementRef, | ||||
|   OnInit, | ||||
|   AfterViewChecked, | ||||
|   ViewChild, | ||||
| } from '@angular/core' | ||||
| import { LogService } from 'src/app/services/rest/log.service' | ||||
|  | ||||
| @Component({ | ||||
|   selector: 'app-logs', | ||||
|   templateUrl: './logs.component.html', | ||||
|   styleUrls: ['./logs.component.scss'] | ||||
|   styleUrls: ['./logs.component.scss'], | ||||
| }) | ||||
| export class LogsComponent implements OnInit, AfterViewChecked { | ||||
|  | ||||
|   constructor(private logService: LogService) { } | ||||
|   constructor(private logService: LogService) {} | ||||
|  | ||||
|   logs: string[] = [] | ||||
|  | ||||
| @@ -19,7 +24,7 @@ export class LogsComponent implements OnInit, AfterViewChecked { | ||||
|   @ViewChild('logContainer') logContainer: ElementRef | ||||
|  | ||||
|   ngOnInit(): void { | ||||
|     this.logService.list().subscribe(result => { | ||||
|     this.logService.list().subscribe((result) => { | ||||
|       this.logFiles = result | ||||
|       if (this.logFiles.length > 0) { | ||||
|         this.activeLog = this.logFiles[0] | ||||
| @@ -29,25 +34,28 @@ export class LogsComponent implements OnInit, AfterViewChecked { | ||||
|   } | ||||
|  | ||||
|   ngAfterViewChecked() { | ||||
|     this.scrollToBottom(); | ||||
|     this.scrollToBottom() | ||||
|   } | ||||
|  | ||||
|   reloadLogs() { | ||||
|     this.logService.get(this.activeLog).subscribe(result => { | ||||
|       this.logs = result | ||||
|     }, error => { | ||||
|       this.logs = [] | ||||
|     }) | ||||
|     this.logService.get(this.activeLog).subscribe( | ||||
|       (result) => { | ||||
|         this.logs = result | ||||
|       }, | ||||
|       (error) => { | ||||
|         this.logs = [] | ||||
|       } | ||||
|     ) | ||||
|   } | ||||
|  | ||||
|   getLogLevel(log: string) { | ||||
|     if (log.indexOf("[DEBUG]") != -1) { | ||||
|     if (log.indexOf('[DEBUG]') != -1) { | ||||
|       return 10 | ||||
|     } else if (log.indexOf("[WARNING]") != -1) { | ||||
|     } else if (log.indexOf('[WARNING]') != -1) { | ||||
|       return 30 | ||||
|     } else if (log.indexOf("[ERROR]") != -1) { | ||||
|     } else if (log.indexOf('[ERROR]') != -1) { | ||||
|       return 40 | ||||
|     } else if (log.indexOf("[CRITICAL]") != -1) { | ||||
|     } else if (log.indexOf('[CRITICAL]') != -1) { | ||||
|       return 50 | ||||
|     } else { | ||||
|       return 20 | ||||
| @@ -58,8 +66,7 @@ export class LogsComponent implements OnInit, AfterViewChecked { | ||||
|     this.logContainer?.nativeElement.scroll({ | ||||
|       top: this.logContainer.nativeElement.scrollHeight, | ||||
|       left: 0, | ||||
|       behavior: 'auto' | ||||
|     }); | ||||
|       behavior: 'auto', | ||||
|     }) | ||||
|   } | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -1,25 +1,24 @@ | ||||
| import { ComponentFixture, TestBed } from '@angular/core/testing'; | ||||
| import { ComponentFixture, TestBed } from '@angular/core/testing' | ||||
|  | ||||
| import { SettingsComponent } from './settings.component'; | ||||
| import { SettingsComponent } from './settings.component' | ||||
|  | ||||
| describe('SettingsComponent', () => { | ||||
|   let component: SettingsComponent; | ||||
|   let fixture: ComponentFixture<SettingsComponent>; | ||||
|   let component: SettingsComponent | ||||
|   let fixture: ComponentFixture<SettingsComponent> | ||||
|  | ||||
|   beforeEach(async () => { | ||||
|     await TestBed.configureTestingModule({ | ||||
|       declarations: [ SettingsComponent ] | ||||
|     }) | ||||
|     .compileComponents(); | ||||
|   }); | ||||
|       declarations: [SettingsComponent], | ||||
|     }).compileComponents() | ||||
|   }) | ||||
|  | ||||
|   beforeEach(() => { | ||||
|     fixture = TestBed.createComponent(SettingsComponent); | ||||
|     component = fixture.componentInstance; | ||||
|     fixture.detectChanges(); | ||||
|   }); | ||||
|     fixture = TestBed.createComponent(SettingsComponent) | ||||
|     component = fixture.componentInstance | ||||
|     fixture.detectChanges() | ||||
|   }) | ||||
|  | ||||
|   it('should create', () => { | ||||
|     expect(component).toBeTruthy(); | ||||
|   }); | ||||
| }); | ||||
|     expect(component).toBeTruthy() | ||||
|   }) | ||||
| }) | ||||
|   | ||||
| @@ -1,39 +1,49 @@ | ||||
| import { Component, Inject, LOCALE_ID, OnInit, OnDestroy, Renderer2 } from '@angular/core'; | ||||
| import { FormControl, FormGroup } from '@angular/forms'; | ||||
| import { PaperlessSavedView } from 'src/app/data/paperless-saved-view'; | ||||
| import { DocumentListViewService } from 'src/app/services/document-list-view.service'; | ||||
| import { SavedViewService } from 'src/app/services/rest/saved-view.service'; | ||||
| import { LanguageOption, SettingsService, SETTINGS_KEYS } from 'src/app/services/settings.service'; | ||||
| import { ToastService } from 'src/app/services/toast.service'; | ||||
| import { dirtyCheck, DirtyComponent } from '@ngneat/dirty-check-forms'; | ||||
| import { Observable, Subscription, BehaviorSubject } from 'rxjs'; | ||||
| import { | ||||
|   Component, | ||||
|   Inject, | ||||
|   LOCALE_ID, | ||||
|   OnInit, | ||||
|   OnDestroy, | ||||
|   Renderer2, | ||||
| } from '@angular/core' | ||||
| import { FormControl, FormGroup } from '@angular/forms' | ||||
| import { PaperlessSavedView } from 'src/app/data/paperless-saved-view' | ||||
| import { DocumentListViewService } from 'src/app/services/document-list-view.service' | ||||
| import { SavedViewService } from 'src/app/services/rest/saved-view.service' | ||||
| import { | ||||
|   LanguageOption, | ||||
|   SettingsService, | ||||
|   SETTINGS_KEYS, | ||||
| } from 'src/app/services/settings.service' | ||||
| import { ToastService } from 'src/app/services/toast.service' | ||||
| import { dirtyCheck, DirtyComponent } from '@ngneat/dirty-check-forms' | ||||
| import { Observable, Subscription, BehaviorSubject } from 'rxjs' | ||||
|  | ||||
| @Component({ | ||||
|   selector: 'app-settings', | ||||
|   templateUrl: './settings.component.html', | ||||
|   styleUrls: ['./settings.component.scss'] | ||||
|   styleUrls: ['./settings.component.scss'], | ||||
| }) | ||||
| export class SettingsComponent implements OnInit, OnDestroy, DirtyComponent { | ||||
|  | ||||
|   savedViewGroup = new FormGroup({}) | ||||
|  | ||||
|   settingsForm = new FormGroup({ | ||||
|     'bulkEditConfirmationDialogs': new FormControl(null), | ||||
|     'bulkEditApplyOnClose': new FormControl(null), | ||||
|     'documentListItemPerPage': new FormControl(null), | ||||
|     'darkModeUseSystem': new FormControl(null), | ||||
|     'darkModeEnabled': new FormControl(null), | ||||
|     'darkModeInvertThumbs': new FormControl(null), | ||||
|     'themeColor': new FormControl(null), | ||||
|     'useNativePdfViewer': new FormControl(null), | ||||
|     'savedViews': this.savedViewGroup, | ||||
|     'displayLanguage': new FormControl(null), | ||||
|     'dateLocale': new FormControl(null), | ||||
|     'dateFormat': new FormControl(null), | ||||
|     'notificationsConsumerNewDocument': new FormControl(null), | ||||
|     'notificationsConsumerSuccess': new FormControl(null), | ||||
|     'notificationsConsumerFailed': new FormControl(null), | ||||
|     'notificationsConsumerSuppressOnDashboard': new FormControl(null), | ||||
|     bulkEditConfirmationDialogs: new FormControl(null), | ||||
|     bulkEditApplyOnClose: new FormControl(null), | ||||
|     documentListItemPerPage: new FormControl(null), | ||||
|     darkModeUseSystem: new FormControl(null), | ||||
|     darkModeEnabled: new FormControl(null), | ||||
|     darkModeInvertThumbs: new FormControl(null), | ||||
|     themeColor: new FormControl(null), | ||||
|     useNativePdfViewer: new FormControl(null), | ||||
|     savedViews: this.savedViewGroup, | ||||
|     displayLanguage: new FormControl(null), | ||||
|     dateLocale: new FormControl(null), | ||||
|     dateFormat: new FormControl(null), | ||||
|     notificationsConsumerNewDocument: new FormControl(null), | ||||
|     notificationsConsumerSuccess: new FormControl(null), | ||||
|     notificationsConsumerFailed: new FormControl(null), | ||||
|     notificationsConsumerSuppressOnDashboard: new FormControl(null), | ||||
|   }) | ||||
|  | ||||
|   savedViews: PaperlessSavedView[] | ||||
| @@ -44,7 +54,11 @@ export class SettingsComponent implements OnInit, OnDestroy, DirtyComponent { | ||||
|   isDirty: Boolean = false | ||||
|  | ||||
|   get computedDateLocale(): string { | ||||
|     return this.settingsForm.value.dateLocale || this.settingsForm.value.displayLanguage || this.currentLocale | ||||
|     return ( | ||||
|       this.settingsForm.value.dateLocale || | ||||
|       this.settingsForm.value.displayLanguage || | ||||
|       this.currentLocale | ||||
|     ) | ||||
|   } | ||||
|  | ||||
|   constructor( | ||||
| @@ -53,48 +67,71 @@ export class SettingsComponent implements OnInit, OnDestroy, DirtyComponent { | ||||
|     private toastService: ToastService, | ||||
|     private settings: SettingsService, | ||||
|     @Inject(LOCALE_ID) public currentLocale: string | ||||
|   ) { } | ||||
|   ) {} | ||||
|  | ||||
|   ngOnInit() { | ||||
|     this.savedViewService.listAll().subscribe(r => { | ||||
|     this.savedViewService.listAll().subscribe((r) => { | ||||
|       this.savedViews = r.results | ||||
|       let storeData = { | ||||
|         'bulkEditConfirmationDialogs': this.settings.get(SETTINGS_KEYS.BULK_EDIT_CONFIRMATION_DIALOGS), | ||||
|         'bulkEditApplyOnClose': this.settings.get(SETTINGS_KEYS.BULK_EDIT_APPLY_ON_CLOSE), | ||||
|         'documentListItemPerPage': this.settings.get(SETTINGS_KEYS.DOCUMENT_LIST_SIZE), | ||||
|         'darkModeUseSystem': this.settings.get(SETTINGS_KEYS.DARK_MODE_USE_SYSTEM), | ||||
|         'darkModeEnabled': this.settings.get(SETTINGS_KEYS.DARK_MODE_ENABLED), | ||||
|         'darkModeInvertThumbs': this.settings.get(SETTINGS_KEYS.DARK_MODE_THUMB_INVERTED), | ||||
|         'themeColor': this.settings.get(SETTINGS_KEYS.THEME_COLOR), | ||||
|         'useNativePdfViewer': this.settings.get(SETTINGS_KEYS.USE_NATIVE_PDF_VIEWER), | ||||
|         'savedViews': {}, | ||||
|         'displayLanguage': this.settings.getLanguage(), | ||||
|         'dateLocale': this.settings.get(SETTINGS_KEYS.DATE_LOCALE), | ||||
|         'dateFormat': this.settings.get(SETTINGS_KEYS.DATE_FORMAT), | ||||
|         'notificationsConsumerNewDocument': this.settings.get(SETTINGS_KEYS.NOTIFICATIONS_CONSUMER_NEW_DOCUMENT), | ||||
|         'notificationsConsumerSuccess': this.settings.get(SETTINGS_KEYS.NOTIFICATIONS_CONSUMER_SUCCESS), | ||||
|         'notificationsConsumerFailed': this.settings.get(SETTINGS_KEYS.NOTIFICATIONS_CONSUMER_FAILED), | ||||
|         'notificationsConsumerSuppressOnDashboard': this.settings.get(SETTINGS_KEYS.NOTIFICATIONS_CONSUMER_SUPPRESS_ON_DASHBOARD), | ||||
|         bulkEditConfirmationDialogs: this.settings.get( | ||||
|           SETTINGS_KEYS.BULK_EDIT_CONFIRMATION_DIALOGS | ||||
|         ), | ||||
|         bulkEditApplyOnClose: this.settings.get( | ||||
|           SETTINGS_KEYS.BULK_EDIT_APPLY_ON_CLOSE | ||||
|         ), | ||||
|         documentListItemPerPage: this.settings.get( | ||||
|           SETTINGS_KEYS.DOCUMENT_LIST_SIZE | ||||
|         ), | ||||
|         darkModeUseSystem: this.settings.get( | ||||
|           SETTINGS_KEYS.DARK_MODE_USE_SYSTEM | ||||
|         ), | ||||
|         darkModeEnabled: this.settings.get(SETTINGS_KEYS.DARK_MODE_ENABLED), | ||||
|         darkModeInvertThumbs: this.settings.get( | ||||
|           SETTINGS_KEYS.DARK_MODE_THUMB_INVERTED | ||||
|         ), | ||||
|         themeColor: this.settings.get(SETTINGS_KEYS.THEME_COLOR), | ||||
|         useNativePdfViewer: this.settings.get( | ||||
|           SETTINGS_KEYS.USE_NATIVE_PDF_VIEWER | ||||
|         ), | ||||
|         savedViews: {}, | ||||
|         displayLanguage: this.settings.getLanguage(), | ||||
|         dateLocale: this.settings.get(SETTINGS_KEYS.DATE_LOCALE), | ||||
|         dateFormat: this.settings.get(SETTINGS_KEYS.DATE_FORMAT), | ||||
|         notificationsConsumerNewDocument: this.settings.get( | ||||
|           SETTINGS_KEYS.NOTIFICATIONS_CONSUMER_NEW_DOCUMENT | ||||
|         ), | ||||
|         notificationsConsumerSuccess: this.settings.get( | ||||
|           SETTINGS_KEYS.NOTIFICATIONS_CONSUMER_SUCCESS | ||||
|         ), | ||||
|         notificationsConsumerFailed: this.settings.get( | ||||
|           SETTINGS_KEYS.NOTIFICATIONS_CONSUMER_FAILED | ||||
|         ), | ||||
|         notificationsConsumerSuppressOnDashboard: this.settings.get( | ||||
|           SETTINGS_KEYS.NOTIFICATIONS_CONSUMER_SUPPRESS_ON_DASHBOARD | ||||
|         ), | ||||
|       } | ||||
|  | ||||
|       for (let view of this.savedViews) { | ||||
|         storeData.savedViews[view.id.toString()] = { | ||||
|           "id": view.id, | ||||
|           "name": view.name, | ||||
|           "show_on_dashboard": view.show_on_dashboard, | ||||
|           "show_in_sidebar": view.show_in_sidebar | ||||
|           id: view.id, | ||||
|           name: view.name, | ||||
|           show_on_dashboard: view.show_on_dashboard, | ||||
|           show_in_sidebar: view.show_in_sidebar, | ||||
|         } | ||||
|         this.savedViewGroup.addControl(view.id.toString(), new FormGroup({ | ||||
|           "id": new FormControl(null), | ||||
|           "name": new FormControl(null), | ||||
|           "show_on_dashboard": new FormControl(null), | ||||
|           "show_in_sidebar": new FormControl(null) | ||||
|         })) | ||||
|         this.savedViewGroup.addControl( | ||||
|           view.id.toString(), | ||||
|           new FormGroup({ | ||||
|             id: new FormControl(null), | ||||
|             name: new FormControl(null), | ||||
|             show_on_dashboard: new FormControl(null), | ||||
|             show_in_sidebar: new FormControl(null), | ||||
|           }) | ||||
|         ) | ||||
|       } | ||||
|  | ||||
|       this.store = new BehaviorSubject(storeData) | ||||
|  | ||||
|       this.storeSub = this.store.asObservable().subscribe(state => { | ||||
|       this.storeSub = this.store.asObservable().subscribe((state) => { | ||||
|         this.settingsForm.patchValue(state, { emitEvent: false }) | ||||
|       }) | ||||
|  | ||||
| @@ -102,45 +139,93 @@ export class SettingsComponent implements OnInit, OnDestroy, DirtyComponent { | ||||
|       this.isDirty$ = dirtyCheck(this.settingsForm, this.store.asObservable()) | ||||
|  | ||||
|       // Record dirty in case we need to 'undo' appearance settings if not saved on close | ||||
|       this.isDirty$.subscribe(dirty => { | ||||
|       this.isDirty$.subscribe((dirty) => { | ||||
|         this.isDirty = dirty | ||||
|       }) | ||||
|        | ||||
|  | ||||
|       // "Live" visual changes prior to save | ||||
|       this.settingsForm.valueChanges.subscribe(() => { | ||||
|         this.settings.updateAppearanceSettings(this.settingsForm.get('darkModeUseSystem').value, this.settingsForm.get('darkModeEnabled').value, this.settingsForm.get('themeColor').value) | ||||
|         this.settings.updateAppearanceSettings( | ||||
|           this.settingsForm.get('darkModeUseSystem').value, | ||||
|           this.settingsForm.get('darkModeEnabled').value, | ||||
|           this.settingsForm.get('themeColor').value | ||||
|         ) | ||||
|       }) | ||||
|     }) | ||||
|   } | ||||
|  | ||||
|   ngOnDestroy() { | ||||
|     if (this.isDirty) this.settings.updateAppearanceSettings() // in case user changed appearance but didnt save | ||||
|     this.storeSub && this.storeSub.unsubscribe(); | ||||
|     this.storeSub && this.storeSub.unsubscribe() | ||||
|   } | ||||
|  | ||||
|   deleteSavedView(savedView: PaperlessSavedView) { | ||||
|     this.savedViewService.delete(savedView).subscribe(() => { | ||||
|       this.savedViewGroup.removeControl(savedView.id.toString()) | ||||
|       this.savedViews.splice(this.savedViews.indexOf(savedView), 1) | ||||
|       this.toastService.showInfo($localize`Saved view "${savedView.name}" deleted.`) | ||||
|       this.toastService.showInfo( | ||||
|         $localize`Saved view "${savedView.name}" deleted.` | ||||
|       ) | ||||
|     }) | ||||
|   } | ||||
|  | ||||
|   private saveLocalSettings() { | ||||
|     this.settings.set(SETTINGS_KEYS.BULK_EDIT_APPLY_ON_CLOSE, this.settingsForm.value.bulkEditApplyOnClose) | ||||
|     this.settings.set(SETTINGS_KEYS.BULK_EDIT_CONFIRMATION_DIALOGS, this.settingsForm.value.bulkEditConfirmationDialogs) | ||||
|     this.settings.set(SETTINGS_KEYS.DOCUMENT_LIST_SIZE, this.settingsForm.value.documentListItemPerPage) | ||||
|     this.settings.set(SETTINGS_KEYS.DARK_MODE_USE_SYSTEM, this.settingsForm.value.darkModeUseSystem) | ||||
|     this.settings.set(SETTINGS_KEYS.DARK_MODE_ENABLED, (this.settingsForm.value.darkModeEnabled == true).toString()) | ||||
|     this.settings.set(SETTINGS_KEYS.DARK_MODE_THUMB_INVERTED, (this.settingsForm.value.darkModeInvertThumbs == true).toString()) | ||||
|     this.settings.set(SETTINGS_KEYS.THEME_COLOR, (this.settingsForm.value.themeColor).toString()) | ||||
|     this.settings.set(SETTINGS_KEYS.USE_NATIVE_PDF_VIEWER, this.settingsForm.value.useNativePdfViewer) | ||||
|     this.settings.set(SETTINGS_KEYS.DATE_LOCALE, this.settingsForm.value.dateLocale) | ||||
|     this.settings.set(SETTINGS_KEYS.DATE_FORMAT, this.settingsForm.value.dateFormat) | ||||
|     this.settings.set(SETTINGS_KEYS.NOTIFICATIONS_CONSUMER_NEW_DOCUMENT, this.settingsForm.value.notificationsConsumerNewDocument) | ||||
|     this.settings.set(SETTINGS_KEYS.NOTIFICATIONS_CONSUMER_SUCCESS, this.settingsForm.value.notificationsConsumerSuccess) | ||||
|     this.settings.set(SETTINGS_KEYS.NOTIFICATIONS_CONSUMER_FAILED, this.settingsForm.value.notificationsConsumerFailed) | ||||
|     this.settings.set(SETTINGS_KEYS.NOTIFICATIONS_CONSUMER_SUPPRESS_ON_DASHBOARD, this.settingsForm.value.notificationsConsumerSuppressOnDashboard) | ||||
|     this.settings.set( | ||||
|       SETTINGS_KEYS.BULK_EDIT_APPLY_ON_CLOSE, | ||||
|       this.settingsForm.value.bulkEditApplyOnClose | ||||
|     ) | ||||
|     this.settings.set( | ||||
|       SETTINGS_KEYS.BULK_EDIT_CONFIRMATION_DIALOGS, | ||||
|       this.settingsForm.value.bulkEditConfirmationDialogs | ||||
|     ) | ||||
|     this.settings.set( | ||||
|       SETTINGS_KEYS.DOCUMENT_LIST_SIZE, | ||||
|       this.settingsForm.value.documentListItemPerPage | ||||
|     ) | ||||
|     this.settings.set( | ||||
|       SETTINGS_KEYS.DARK_MODE_USE_SYSTEM, | ||||
|       this.settingsForm.value.darkModeUseSystem | ||||
|     ) | ||||
|     this.settings.set( | ||||
|       SETTINGS_KEYS.DARK_MODE_ENABLED, | ||||
|       (this.settingsForm.value.darkModeEnabled == true).toString() | ||||
|     ) | ||||
|     this.settings.set( | ||||
|       SETTINGS_KEYS.DARK_MODE_THUMB_INVERTED, | ||||
|       (this.settingsForm.value.darkModeInvertThumbs == true).toString() | ||||
|     ) | ||||
|     this.settings.set( | ||||
|       SETTINGS_KEYS.THEME_COLOR, | ||||
|       this.settingsForm.value.themeColor.toString() | ||||
|     ) | ||||
|     this.settings.set( | ||||
|       SETTINGS_KEYS.USE_NATIVE_PDF_VIEWER, | ||||
|       this.settingsForm.value.useNativePdfViewer | ||||
|     ) | ||||
|     this.settings.set( | ||||
|       SETTINGS_KEYS.DATE_LOCALE, | ||||
|       this.settingsForm.value.dateLocale | ||||
|     ) | ||||
|     this.settings.set( | ||||
|       SETTINGS_KEYS.DATE_FORMAT, | ||||
|       this.settingsForm.value.dateFormat | ||||
|     ) | ||||
|     this.settings.set( | ||||
|       SETTINGS_KEYS.NOTIFICATIONS_CONSUMER_NEW_DOCUMENT, | ||||
|       this.settingsForm.value.notificationsConsumerNewDocument | ||||
|     ) | ||||
|     this.settings.set( | ||||
|       SETTINGS_KEYS.NOTIFICATIONS_CONSUMER_SUCCESS, | ||||
|       this.settingsForm.value.notificationsConsumerSuccess | ||||
|     ) | ||||
|     this.settings.set( | ||||
|       SETTINGS_KEYS.NOTIFICATIONS_CONSUMER_FAILED, | ||||
|       this.settingsForm.value.notificationsConsumerFailed | ||||
|     ) | ||||
|     this.settings.set( | ||||
|       SETTINGS_KEYS.NOTIFICATIONS_CONSUMER_SUPPRESS_ON_DASHBOARD, | ||||
|       this.settingsForm.value.notificationsConsumerSuppressOnDashboard | ||||
|     ) | ||||
|     this.settings.setLanguage(this.settingsForm.value.displayLanguage) | ||||
|     this.store.next(this.settingsForm.value) | ||||
|     this.documentListViewService.updatePageSize() | ||||
| @@ -149,14 +234,14 @@ export class SettingsComponent implements OnInit, OnDestroy, DirtyComponent { | ||||
|   } | ||||
|  | ||||
|   get displayLanguageOptions(): LanguageOption[] { | ||||
|     return [ | ||||
|       {code: "", name: $localize`Use system language`} | ||||
|     ].concat(this.settings.getLanguageOptions()) | ||||
|     return [{ code: '', name: $localize`Use system language` }].concat( | ||||
|       this.settings.getLanguageOptions() | ||||
|     ) | ||||
|   } | ||||
|  | ||||
|   get dateLocaleOptions(): LanguageOption[] { | ||||
|     return [ | ||||
|       {code: "", name: $localize`Use date format of display language`} | ||||
|       { code: '', name: $localize`Use date format of display language` }, | ||||
|     ].concat(this.settings.getDateLocaleOptions()) | ||||
|   } | ||||
|  | ||||
| @@ -170,18 +255,24 @@ export class SettingsComponent implements OnInit, OnDestroy, DirtyComponent { | ||||
|       x.push(this.savedViewGroup.value[id]) | ||||
|     } | ||||
|     if (x.length > 0) { | ||||
|       this.savedViewService.patchMany(x).subscribe(s => { | ||||
|         this.saveLocalSettings() | ||||
|       }, error => { | ||||
|         this.toastService.showError($localize`Error while storing settings on server: ${JSON.stringify(error.error)}`) | ||||
|       }) | ||||
|       this.savedViewService.patchMany(x).subscribe( | ||||
|         (s) => { | ||||
|           this.saveLocalSettings() | ||||
|         }, | ||||
|         (error) => { | ||||
|           this.toastService.showError( | ||||
|             $localize`Error while storing settings on server: ${JSON.stringify( | ||||
|               error.error | ||||
|             )}` | ||||
|           ) | ||||
|         } | ||||
|       ) | ||||
|     } else { | ||||
|       this.saveLocalSettings() | ||||
|     } | ||||
|  | ||||
|   } | ||||
|  | ||||
|   clearThemeColor() { | ||||
|     this.settingsForm.get('themeColor').patchValue(''); | ||||
|     this.settingsForm.get('themeColor').patchValue('') | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -1,25 +1,24 @@ | ||||
| import { ComponentFixture, TestBed } from '@angular/core/testing'; | ||||
| import { ComponentFixture, TestBed } from '@angular/core/testing' | ||||
|  | ||||
| import { TagEditDialogComponent } from './tag-edit-dialog.component'; | ||||
| import { TagEditDialogComponent } from './tag-edit-dialog.component' | ||||
|  | ||||
| describe('TagEditDialogComponent', () => { | ||||
|   let component: TagEditDialogComponent; | ||||
|   let fixture: ComponentFixture<TagEditDialogComponent>; | ||||
|   let component: TagEditDialogComponent | ||||
|   let fixture: ComponentFixture<TagEditDialogComponent> | ||||
|  | ||||
|   beforeEach(async () => { | ||||
|     await TestBed.configureTestingModule({ | ||||
|       declarations: [ TagEditDialogComponent ] | ||||
|     }) | ||||
|     .compileComponents(); | ||||
|   }); | ||||
|       declarations: [TagEditDialogComponent], | ||||
|     }).compileComponents() | ||||
|   }) | ||||
|  | ||||
|   beforeEach(() => { | ||||
|     fixture = TestBed.createComponent(TagEditDialogComponent); | ||||
|     component = fixture.componentInstance; | ||||
|     fixture.detectChanges(); | ||||
|   }); | ||||
|     fixture = TestBed.createComponent(TagEditDialogComponent) | ||||
|     component = fixture.componentInstance | ||||
|     fixture.detectChanges() | ||||
|   }) | ||||
|  | ||||
|   it('should create', () => { | ||||
|     expect(component).toBeTruthy(); | ||||
|   }); | ||||
| }); | ||||
|     expect(component).toBeTruthy() | ||||
|   }) | ||||
| }) | ||||
|   | ||||
| @@ -1,20 +1,23 @@ | ||||
| import { Component } from '@angular/core'; | ||||
| import { FormControl, FormGroup } from '@angular/forms'; | ||||
| import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; | ||||
| import { EditDialogComponent } from 'src/app/components/common/edit-dialog/edit-dialog.component'; | ||||
| import { PaperlessTag } from 'src/app/data/paperless-tag'; | ||||
| import { TagService } from 'src/app/services/rest/tag.service'; | ||||
| import { ToastService } from 'src/app/services/toast.service'; | ||||
| import { randomColor } from 'src/app/utils/color'; | ||||
| import { Component } from '@angular/core' | ||||
| import { FormControl, FormGroup } from '@angular/forms' | ||||
| import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap' | ||||
| import { EditDialogComponent } from 'src/app/components/common/edit-dialog/edit-dialog.component' | ||||
| import { PaperlessTag } from 'src/app/data/paperless-tag' | ||||
| import { TagService } from 'src/app/services/rest/tag.service' | ||||
| import { ToastService } from 'src/app/services/toast.service' | ||||
| import { randomColor } from 'src/app/utils/color' | ||||
|  | ||||
| @Component({ | ||||
|   selector: 'app-tag-edit-dialog', | ||||
|   templateUrl: './tag-edit-dialog.component.html', | ||||
|   styleUrls: ['./tag-edit-dialog.component.scss'] | ||||
|   styleUrls: ['./tag-edit-dialog.component.scss'], | ||||
| }) | ||||
| export class TagEditDialogComponent extends EditDialogComponent<PaperlessTag> { | ||||
|  | ||||
|   constructor(service: TagService, activeModal: NgbActiveModal, toastService: ToastService) { | ||||
|   constructor( | ||||
|     service: TagService, | ||||
|     activeModal: NgbActiveModal, | ||||
|     toastService: ToastService | ||||
|   ) { | ||||
|     super(service, activeModal, toastService) | ||||
|   } | ||||
|  | ||||
| @@ -32,9 +35,8 @@ export class TagEditDialogComponent extends EditDialogComponent<PaperlessTag> { | ||||
|       color: new FormControl(randomColor()), | ||||
|       is_inbox_tag: new FormControl(false), | ||||
|       matching_algorithm: new FormControl(1), | ||||
|       match: new FormControl(""), | ||||
|       is_insensitive: new FormControl(true) | ||||
|       match: new FormControl(''), | ||||
|       is_insensitive: new FormControl(true), | ||||
|     }) | ||||
|   } | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -1,25 +1,24 @@ | ||||
| import { ComponentFixture, TestBed } from '@angular/core/testing'; | ||||
| import { ComponentFixture, TestBed } from '@angular/core/testing' | ||||
|  | ||||
| import { TagListComponent } from './tag-list.component'; | ||||
| import { TagListComponent } from './tag-list.component' | ||||
|  | ||||
| describe('TagListComponent', () => { | ||||
|   let component: TagListComponent; | ||||
|   let fixture: ComponentFixture<TagListComponent>; | ||||
|   let component: TagListComponent | ||||
|   let fixture: ComponentFixture<TagListComponent> | ||||
|  | ||||
|   beforeEach(async () => { | ||||
|     await TestBed.configureTestingModule({ | ||||
|       declarations: [ TagListComponent ] | ||||
|     }) | ||||
|     .compileComponents(); | ||||
|   }); | ||||
|       declarations: [TagListComponent], | ||||
|     }).compileComponents() | ||||
|   }) | ||||
|  | ||||
|   beforeEach(() => { | ||||
|     fixture = TestBed.createComponent(TagListComponent); | ||||
|     component = fixture.componentInstance; | ||||
|     fixture.detectChanges(); | ||||
|   }); | ||||
|     fixture = TestBed.createComponent(TagListComponent) | ||||
|     component = fixture.componentInstance | ||||
|     fixture.detectChanges() | ||||
|   }) | ||||
|  | ||||
|   it('should create', () => { | ||||
|     expect(component).toBeTruthy(); | ||||
|   }); | ||||
| }); | ||||
|     expect(component).toBeTruthy() | ||||
|   }) | ||||
| }) | ||||
|   | ||||
| @@ -1,21 +1,22 @@ | ||||
| import { Component } from '@angular/core'; | ||||
| import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; | ||||
| import { FILTER_HAS_TAGS_ALL } from 'src/app/data/filter-rule-type'; | ||||
| import { PaperlessTag } from 'src/app/data/paperless-tag'; | ||||
| import { DocumentListViewService } from 'src/app/services/document-list-view.service'; | ||||
| import { TagService } from 'src/app/services/rest/tag.service'; | ||||
| import { ToastService } from 'src/app/services/toast.service'; | ||||
| import { GenericListComponent } from '../generic-list/generic-list.component'; | ||||
| import { TagEditDialogComponent } from './tag-edit-dialog/tag-edit-dialog.component'; | ||||
| import { Component } from '@angular/core' | ||||
| import { NgbModal } from '@ng-bootstrap/ng-bootstrap' | ||||
| import { FILTER_HAS_TAGS_ALL } from 'src/app/data/filter-rule-type' | ||||
| import { PaperlessTag } from 'src/app/data/paperless-tag' | ||||
| import { DocumentListViewService } from 'src/app/services/document-list-view.service' | ||||
| import { TagService } from 'src/app/services/rest/tag.service' | ||||
| import { ToastService } from 'src/app/services/toast.service' | ||||
| import { GenericListComponent } from '../generic-list/generic-list.component' | ||||
| import { TagEditDialogComponent } from './tag-edit-dialog/tag-edit-dialog.component' | ||||
|  | ||||
| @Component({ | ||||
|   selector: 'app-tag-list', | ||||
|   templateUrl: './tag-list.component.html', | ||||
|   styleUrls: ['./tag-list.component.scss'] | ||||
|   styleUrls: ['./tag-list.component.scss'], | ||||
| }) | ||||
| export class TagListComponent extends GenericListComponent<PaperlessTag> { | ||||
|  | ||||
|   constructor(tagService: TagService, modalService: NgbModal, | ||||
|   constructor( | ||||
|     tagService: TagService, | ||||
|     modalService: NgbModal, | ||||
|     private list: DocumentListViewService, | ||||
|     toastService: ToastService | ||||
|   ) { | ||||
| @@ -27,7 +28,8 @@ export class TagListComponent extends GenericListComponent<PaperlessTag> { | ||||
|   } | ||||
|  | ||||
|   filterDocuments(object: PaperlessTag) { | ||||
|     this.list.quickFilter([{rule_type: FILTER_HAS_TAGS_ALL, value: object.id.toString()}]) | ||||
|  | ||||
|     this.list.quickFilter([ | ||||
|       { rule_type: FILTER_HAS_TAGS_ALL, value: object.id.toString() }, | ||||
|     ]) | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -1,25 +1,24 @@ | ||||
| import { ComponentFixture, TestBed } from '@angular/core/testing'; | ||||
| import { ComponentFixture, TestBed } from '@angular/core/testing' | ||||
|  | ||||
| import { NotFoundComponent } from './not-found.component'; | ||||
| import { NotFoundComponent } from './not-found.component' | ||||
|  | ||||
| describe('NotFoundComponent', () => { | ||||
|   let component: NotFoundComponent; | ||||
|   let fixture: ComponentFixture<NotFoundComponent>; | ||||
|   let component: NotFoundComponent | ||||
|   let fixture: ComponentFixture<NotFoundComponent> | ||||
|  | ||||
|   beforeEach(async () => { | ||||
|     await TestBed.configureTestingModule({ | ||||
|       declarations: [ NotFoundComponent ] | ||||
|     }) | ||||
|     .compileComponents(); | ||||
|   }); | ||||
|       declarations: [NotFoundComponent], | ||||
|     }).compileComponents() | ||||
|   }) | ||||
|  | ||||
|   beforeEach(() => { | ||||
|     fixture = TestBed.createComponent(NotFoundComponent); | ||||
|     component = fixture.componentInstance; | ||||
|     fixture.detectChanges(); | ||||
|   }); | ||||
|     fixture = TestBed.createComponent(NotFoundComponent) | ||||
|     component = fixture.componentInstance | ||||
|     fixture.detectChanges() | ||||
|   }) | ||||
|  | ||||
|   it('should create', () => { | ||||
|     expect(component).toBeTruthy(); | ||||
|   }); | ||||
| }); | ||||
|     expect(component).toBeTruthy() | ||||
|   }) | ||||
| }) | ||||
|   | ||||
| @@ -1,15 +1,12 @@ | ||||
| import { Component, OnInit } from '@angular/core'; | ||||
| import { Component, OnInit } from '@angular/core' | ||||
|  | ||||
| @Component({ | ||||
|   selector: 'app-not-found', | ||||
|   templateUrl: './not-found.component.html', | ||||
|   styleUrls: ['./not-found.component.scss'] | ||||
|   styleUrls: ['./not-found.component.scss'], | ||||
| }) | ||||
| export class NotFoundComponent implements OnInit { | ||||
|   constructor() {} | ||||
|  | ||||
|   constructor() { } | ||||
|  | ||||
|   ngOnInit(): void { | ||||
|   } | ||||
|  | ||||
|   ngOnInit(): void {} | ||||
| } | ||||
|   | ||||
| @@ -27,40 +27,160 @@ export const FILTER_FULLTEXT_QUERY = 20 | ||||
| export const FILTER_FULLTEXT_MORELIKE = 21 | ||||
|  | ||||
| export const FILTER_RULE_TYPES: FilterRuleType[] = [ | ||||
|   { | ||||
|     id: FILTER_TITLE, | ||||
|     filtervar: 'title__icontains', | ||||
|     datatype: 'string', | ||||
|     multi: false, | ||||
|     default: '', | ||||
|   }, | ||||
|   { | ||||
|     id: FILTER_CONTENT, | ||||
|     filtervar: 'content__icontains', | ||||
|     datatype: 'string', | ||||
|     multi: false, | ||||
|     default: '', | ||||
|   }, | ||||
|  | ||||
|   {id: FILTER_TITLE, filtervar: "title__icontains", datatype: "string", multi: false, default: ""}, | ||||
|   {id: FILTER_CONTENT, filtervar: "content__icontains", datatype: "string", multi: false, default: ""}, | ||||
|   { | ||||
|     id: FILTER_ASN, | ||||
|     filtervar: 'archive_serial_number', | ||||
|     datatype: 'number', | ||||
|     multi: false, | ||||
|   }, | ||||
|  | ||||
|   {id: FILTER_ASN, filtervar: "archive_serial_number", datatype: "number", multi: false}, | ||||
|   { | ||||
|     id: FILTER_CORRESPONDENT, | ||||
|     filtervar: 'correspondent__id', | ||||
|     isnull_filtervar: 'correspondent__isnull', | ||||
|     datatype: 'correspondent', | ||||
|     multi: false, | ||||
|   }, | ||||
|   { | ||||
|     id: FILTER_DOCUMENT_TYPE, | ||||
|     filtervar: 'document_type__id', | ||||
|     isnull_filtervar: 'document_type__isnull', | ||||
|     datatype: 'document_type', | ||||
|     multi: false, | ||||
|   }, | ||||
|  | ||||
|   {id: FILTER_CORRESPONDENT, filtervar: "correspondent__id", isnull_filtervar: "correspondent__isnull", datatype: "correspondent", multi: false}, | ||||
|   {id: FILTER_DOCUMENT_TYPE, filtervar: "document_type__id", isnull_filtervar: "document_type__isnull", datatype: "document_type", multi: false}, | ||||
|   { | ||||
|     id: FILTER_IS_IN_INBOX, | ||||
|     filtervar: 'is_in_inbox', | ||||
|     datatype: 'boolean', | ||||
|     multi: false, | ||||
|     default: true, | ||||
|   }, | ||||
|   { | ||||
|     id: FILTER_HAS_TAGS_ALL, | ||||
|     filtervar: 'tags__id__all', | ||||
|     datatype: 'tag', | ||||
|     multi: true, | ||||
|   }, | ||||
|   { | ||||
|     id: FILTER_HAS_TAGS_ANY, | ||||
|     filtervar: 'tags__id__in', | ||||
|     datatype: 'tag', | ||||
|     multi: true, | ||||
|   }, | ||||
|   { | ||||
|     id: FILTER_DOES_NOT_HAVE_TAG, | ||||
|     filtervar: 'tags__id__none', | ||||
|     datatype: 'tag', | ||||
|     multi: true, | ||||
|   }, | ||||
|   { | ||||
|     id: FILTER_HAS_ANY_TAG, | ||||
|     filtervar: 'is_tagged', | ||||
|     datatype: 'boolean', | ||||
|     multi: false, | ||||
|     default: true, | ||||
|   }, | ||||
|  | ||||
|   {id: FILTER_IS_IN_INBOX, filtervar: "is_in_inbox", datatype: "boolean", multi: false, default: true}, | ||||
|   {id: FILTER_HAS_TAGS_ALL, filtervar: "tags__id__all", datatype: "tag", multi: true}, | ||||
|   {id: FILTER_HAS_TAGS_ANY, filtervar: "tags__id__in", datatype: "tag", multi: true}, | ||||
|   {id: FILTER_DOES_NOT_HAVE_TAG, filtervar: "tags__id__none", datatype: "tag", multi: true}, | ||||
|   {id: FILTER_HAS_ANY_TAG, filtervar: "is_tagged", datatype: "boolean", multi: false, default: true}, | ||||
|   { | ||||
|     id: FILTER_CREATED_BEFORE, | ||||
|     filtervar: 'created__date__lt', | ||||
|     datatype: 'date', | ||||
|     multi: false, | ||||
|   }, | ||||
|   { | ||||
|     id: FILTER_CREATED_AFTER, | ||||
|     filtervar: 'created__date__gt', | ||||
|     datatype: 'date', | ||||
|     multi: false, | ||||
|   }, | ||||
|  | ||||
|   {id: FILTER_CREATED_BEFORE, filtervar: "created__date__lt", datatype: "date", multi: false}, | ||||
|   {id: FILTER_CREATED_AFTER, filtervar: "created__date__gt", datatype: "date", multi: false}, | ||||
|   { | ||||
|     id: FILTER_CREATED_YEAR, | ||||
|     filtervar: 'created__year', | ||||
|     datatype: 'number', | ||||
|     multi: false, | ||||
|   }, | ||||
|   { | ||||
|     id: FILTER_CREATED_MONTH, | ||||
|     filtervar: 'created__month', | ||||
|     datatype: 'number', | ||||
|     multi: false, | ||||
|   }, | ||||
|   { | ||||
|     id: FILTER_CREATED_DAY, | ||||
|     filtervar: 'created__day', | ||||
|     datatype: 'number', | ||||
|     multi: false, | ||||
|   }, | ||||
|  | ||||
|   {id: FILTER_CREATED_YEAR, filtervar: "created__year", datatype: "number", multi: false}, | ||||
|   {id: FILTER_CREATED_MONTH, filtervar: "created__month", datatype: "number", multi: false}, | ||||
|   {id: FILTER_CREATED_DAY, filtervar: "created__day", datatype: "number", multi: false}, | ||||
|   { | ||||
|     id: FILTER_ADDED_BEFORE, | ||||
|     filtervar: 'added__date__lt', | ||||
|     datatype: 'date', | ||||
|     multi: false, | ||||
|   }, | ||||
|   { | ||||
|     id: FILTER_ADDED_AFTER, | ||||
|     filtervar: 'added__date__gt', | ||||
|     datatype: 'date', | ||||
|     multi: false, | ||||
|   }, | ||||
|  | ||||
|   {id: FILTER_ADDED_BEFORE, filtervar: "added__date__lt", datatype: "date", multi: false}, | ||||
|   {id: FILTER_ADDED_AFTER, filtervar: "added__date__gt", datatype: "date", multi: false}, | ||||
|   { | ||||
|     id: FILTER_MODIFIED_BEFORE, | ||||
|     filtervar: 'modified__date__lt', | ||||
|     datatype: 'date', | ||||
|     multi: false, | ||||
|   }, | ||||
|   { | ||||
|     id: FILTER_MODIFIED_AFTER, | ||||
|     filtervar: 'modified__date__gt', | ||||
|     datatype: 'date', | ||||
|     multi: false, | ||||
|   }, | ||||
|   { | ||||
|     id: FILTER_ASN_ISNULL, | ||||
|     filtervar: 'archive_serial_number__isnull', | ||||
|     datatype: 'boolean', | ||||
|     multi: false, | ||||
|   }, | ||||
|  | ||||
|   {id: FILTER_MODIFIED_BEFORE, filtervar: "modified__date__lt", datatype: "date", multi: false}, | ||||
|   {id: FILTER_MODIFIED_AFTER, filtervar: "modified__date__gt", datatype: "date", multi: false}, | ||||
|   {id: FILTER_ASN_ISNULL, filtervar: "archive_serial_number__isnull", datatype: "boolean", multi: false}, | ||||
|   { | ||||
|     id: FILTER_TITLE_CONTENT, | ||||
|     filtervar: 'title_content', | ||||
|     datatype: 'string', | ||||
|     multi: false, | ||||
|   }, | ||||
|  | ||||
|   {id: FILTER_TITLE_CONTENT, filtervar: "title_content", datatype: "string", multi: false}, | ||||
|   { | ||||
|     id: FILTER_FULLTEXT_QUERY, | ||||
|     filtervar: 'query', | ||||
|     datatype: 'string', | ||||
|     multi: false, | ||||
|   }, | ||||
|  | ||||
|   {id: FILTER_FULLTEXT_QUERY, filtervar: "query", datatype: "string", multi: false}, | ||||
|  | ||||
|   {id: FILTER_FULLTEXT_MORELIKE, filtervar: "more_like_id", datatype: "number", multi: false}, | ||||
|   { | ||||
|     id: FILTER_FULLTEXT_MORELIKE, | ||||
|     filtervar: 'more_like_id', | ||||
|     datatype: 'number', | ||||
|     multi: false, | ||||
|   }, | ||||
| ] | ||||
|  | ||||
| export interface FilterRuleType { | ||||
|   | ||||
| @@ -1,10 +1,13 @@ | ||||
| import { FILTER_FULLTEXT_MORELIKE, FILTER_FULLTEXT_QUERY } from "./filter-rule-type" | ||||
| import { | ||||
|   FILTER_FULLTEXT_MORELIKE, | ||||
|   FILTER_FULLTEXT_QUERY, | ||||
| } from './filter-rule-type' | ||||
|  | ||||
| export function cloneFilterRules(filterRules: FilterRule[]): FilterRule[] { | ||||
|   if (filterRules) { | ||||
|     let newRules: FilterRule[] = [] | ||||
|     for (let rule of filterRules) { | ||||
|       newRules.push({rule_type: rule.rule_type, value: rule.value}) | ||||
|       newRules.push({ rule_type: rule.rule_type, value: rule.value }) | ||||
|     } | ||||
|     return newRules | ||||
|   } else { | ||||
| @@ -13,7 +16,13 @@ export function cloneFilterRules(filterRules: FilterRule[]): FilterRule[] { | ||||
| } | ||||
|  | ||||
| export function isFullTextFilterRule(filterRules: FilterRule[]): boolean { | ||||
|   return filterRules.find(r => r.rule_type == FILTER_FULLTEXT_QUERY || r.rule_type == FILTER_FULLTEXT_MORELIKE) != null | ||||
|   return ( | ||||
|     filterRules.find( | ||||
|       (r) => | ||||
|         r.rule_type == FILTER_FULLTEXT_QUERY || | ||||
|         r.rule_type == FILTER_FULLTEXT_MORELIKE | ||||
|     ) != null | ||||
|   ) | ||||
| } | ||||
|  | ||||
| export interface FilterRule { | ||||
|   | ||||
| @@ -1,5 +1,4 @@ | ||||
| import { ObjectWithId } from './object-with-id'; | ||||
|  | ||||
| import { ObjectWithId } from './object-with-id' | ||||
|  | ||||
| export const MATCH_ANY = 1 | ||||
| export const MATCH_ALL = 2 | ||||
| @@ -9,26 +8,48 @@ export const MATCH_FUZZY = 5 | ||||
| export const MATCH_AUTO = 6 | ||||
|  | ||||
| export const MATCHING_ALGORITHMS = [ | ||||
|     {id: MATCH_ANY, shortName: $localize`Any word`, name: $localize`Any: Document contains any of these words (space separated)`}, | ||||
|     {id: MATCH_ALL, shortName: $localize`All words`, name: $localize`All: Document contains all of these words (space separated)`}, | ||||
|     {id: MATCH_LITERAL, shortName: $localize`Exact match`, name: $localize`Exact: Document contains this string`}, | ||||
|     {id: MATCH_REGEX, shortName: $localize`Regular expression`, name: $localize`Regular expression: Document matches this regular expression`}, | ||||
|     {id: MATCH_FUZZY, shortName: $localize`Fuzzy word`, name: $localize`Fuzzy: Document contains a word similar to this word`}, | ||||
|     {id: MATCH_AUTO, shortName: $localize`Automatic`, name: $localize`Auto: Learn matching automatically`}, | ||||
|   { | ||||
|     id: MATCH_ANY, | ||||
|     shortName: $localize`Any word`, | ||||
|     name: $localize`Any: Document contains any of these words (space separated)`, | ||||
|   }, | ||||
|   { | ||||
|     id: MATCH_ALL, | ||||
|     shortName: $localize`All words`, | ||||
|     name: $localize`All: Document contains all of these words (space separated)`, | ||||
|   }, | ||||
|   { | ||||
|     id: MATCH_LITERAL, | ||||
|     shortName: $localize`Exact match`, | ||||
|     name: $localize`Exact: Document contains this string`, | ||||
|   }, | ||||
|   { | ||||
|     id: MATCH_REGEX, | ||||
|     shortName: $localize`Regular expression`, | ||||
|     name: $localize`Regular expression: Document matches this regular expression`, | ||||
|   }, | ||||
|   { | ||||
|     id: MATCH_FUZZY, | ||||
|     shortName: $localize`Fuzzy word`, | ||||
|     name: $localize`Fuzzy: Document contains a word similar to this word`, | ||||
|   }, | ||||
|   { | ||||
|     id: MATCH_AUTO, | ||||
|     shortName: $localize`Automatic`, | ||||
|     name: $localize`Auto: Learn matching automatically`, | ||||
|   }, | ||||
| ] | ||||
|  | ||||
| export interface MatchingModel extends ObjectWithId { | ||||
|   name?: string | ||||
|  | ||||
|     name?: string | ||||
|   slug?: string | ||||
|  | ||||
|     slug?: string | ||||
|   match?: string | ||||
|  | ||||
|     match?: string | ||||
|   matching_algorithm?: number | ||||
|  | ||||
|     matching_algorithm?: number | ||||
|  | ||||
|     is_insensitive?: boolean | ||||
|  | ||||
|     document_count?: number | ||||
|   is_insensitive?: boolean | ||||
|  | ||||
|   document_count?: number | ||||
| } | ||||
|   | ||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user
	 shamoon
					shamoon